Wiktionary
zhwiktionary
https://zh.wiktionary.org/wiki/Wiktionary:%E9%A6%96%E9%A1%B5
MediaWiki 1.47.0-wmf.21
case-sensitive
Media
Special
Talk
User
User talk
Wiktionary
Wiktionary talk
File
File talk
MediaWiki
MediaWiki talk
Template
Template talk
Help
Help talk
Category
Category talk
Appendix
Appendix talk
Transwiki
Transwiki talk
Rhymes
Rhymes talk
Thesaurus
Thesaurus talk
Citations
Citations talk
Reconstruction
Reconstruction talk
TimedText
TimedText talk
Module
Module talk
Event
Event talk
Wiktionary:统计
4
1718
9908797
9908713
2026-09-29T02:38:59Z
TongcyBot
83009
機器人:更新每日統計數據(2026-09-29)
9908797
wikitext
text/x-wiki
{{Interwiki}}
{{shortcut|WT:STAT}}
==基本資料==
{| class="wikitable"
! 項目 !! 資料
|-
! MediaWiki版本
| {{CURRENTVERSION}}
|-
! 詞條數量
| [[Special:Statistics|'''{{NUMBEROFARTICLES}}''']]
|-
! 總頁面數
| {{NUMBEROFPAGES}}
|-
! 已編碼的語言數量
| {{#invoke:list of languages|count}}
|-
! 檔案數量
| {{NUMBEROFFILES}}
|-
! 註冊用戶數
| {{NUMBEROFUSERS}}
|-
! 活躍用戶數
| {{NUMBEROFACTIVEUSERS}}
|-
! 管理員數量
| {{NUMBEROFADMINS}}
|-
! 全站頁面編輯次數
| {{NUMBEROFEDITS}}
|}
其他資料請參考[[Special:Statistics]]。
==頁面==
本表是中文维基詞典詞條数依日期到達的里程碑,以每一万个词条为单位计数。<!-- 以下內容已不再更新
* 近期統計圖表(區分機器人與一般用戶,選擇zhwiktionary_p,每日自動更新):[https://tools.wmflabs.org/wmcharts/wmchart0001.php 編輯次數]{{·}}[https://tools.wmflabs.org/wmcharts/wmchart0002.php 新增詞條數量]-->
* 目前的詞條數:'''{{NUMBEROFARTICLES}}'''({{重新整理|更新}})。
{{Milestone}}
==与相邻语言版本的比较==
中文维基词典词条收录量为'''{{NUMBEROFARTICLES}}''',在所有语言版本的詞典中[[meta:Wiktionary#List of Wiktionaries|'''位列第4''']]。
{{autoStat
| 2026-09-01
| fr | mg | zh | th | el
| 6997535 | 6023134 | 2380192 | 2206521 | 1585904
| 6998238 | 6024873 | 2380554 | 2206523 | 1585917
| 7000127 | 6026546 | 2380887 | 2206607 | 1585946
| 7000656 | 6029380 | 2381509 | 2206651 | 1585971
| 7003003 | 6031154 | 2381927 | 2206689 | 1585985
| 7004167 | 6032755 | 2382273 | 2206693 | 1586008
| 7006837 | 6034370 | 2382636 | 2206694 | 1586076
| 7007218 | 6035533 | 2383103 | 2206700 | 1586104
| 7008103 | 6035941 | 2383540 | 2206787 | 1586119
| 7008906 | 6036586 | 2383959 | 2206792 | 1586128
| 7009409 | 6038227 | 2384261 | 2206803 | 1586142
| 7009787 | 6039498 | 2384800 | 2206835 | 1596586
| 7010299 | 6040280 | 2385265 | 2206838 | 1611050
| 7010652 | 6041105 | 2385801 | 2206852 | 1611062
| 7010891 | 6042008 | 2386137 | 2206856 | 1611079
| 7011114 | 6042770 | 2386453 | 2206890 | 1611165
| 7011512 | 6043840 | 2386815 | 2206977 | 1611178
| 7011875 | 6044192 | 2387177 | 2207036 | 1611191
| 7012192 | 6044642 | 2387486 | 2207045 | 1616152
| 7012627 | 6046884 | 2387788 | 2207045 | 1616861
| 7013224 | 6047546 | 2388205 | 2207131 | 1621664
| 7013869 | 6048082 | 2388988 | 2207162 | 1621699
| 7014495 | 6048392 | 2389021 | 2207187 | 1621721
| 7015177 | 6048873 | 2389437 | 2207217 | 1621746
| 7015484 | 6051500 | 2390060 | 2207218 | 1621762
| 7016417 | 6058398 | 2390641 | 2207222 | 1621777
| 7017175 | 6064817 | 2391068 | 2207245 | 1626804
| 7018084 | 6066870 | 2391703 | 2207247 | 1642410
| 7018560 | 6068253 | 2392193 | 2207247 | 1652475
| 7019074 | 6069355 | 2392720 | 2207247 | 1662451
}}
* 来源:[https://wikistats.wmcloud.org/display.php?t=wt Wikistats] 统计的具体时间为当日1时(UTC)左右,近似表示前一天的数据
* 关于里程碑,参见[[Wiktionary:宣告]]
==页面访问统计==
历史数据参见:[[Wiktionary:统计/存档]]
===总页面浏览量===
{{Collapse2|header=最近一年中文維基詞典的存取量|float=right
|{{Graph:PageViews| 365 | _ |scale=sqrt}}
}}
{{Chart
| chart = ZhWiktionary_Monthly_Pageviews_Since_2016.chart
| align = center
| title = 中文維基詞典歷年瀏覽量
}}
数据来源:[https://stats.wikimedia.org/#/zh.wiktionary.org/reading/total-page-views/normal|table|1-year|~total|monthly Total page views]
===热词===
页面浏览量最高的词条:
{{Wiktionary:統計/熱詞
|year=2024 |month=6
|t1=尛 |cat1=[[:Category:漢字|漢字]] |v1=5,385 |m1=82.8
|t2=。 |cat2=[[:Category:標點符號|標點符號]] |v2=5,355 |m2=94.4
|t3=! |cat3=[[:Category:標點符號|標點符號]] |v3=3,407 |m3=74.2
|t4=凪 |cat4=[[:Category:和製漢字|和製漢字]]|v4=3,120 |m4=62.0
|t5=雫 |cat5=[[:Category:和製漢字|和製漢字]] |v5= 3,114 |m5=64.6
|t6=又双叒叕 |cat6=[[:Category:漢語副詞|漢語副詞]] |v6=2,528 |m6=73.4
|t7=你好 |cat7=[[:Category:漢語感嘆詞|漢語感嘆詞]] |v7=2,513 |m7=41.1
|t8=昶 |cat8=[[:Category:漢字|漢字]] |v8=2,412 |m8=56.7
|t9=肉便器 |cat9=[[:Category:漢語名詞|漢語名詞]] |v9=2,345 |m9=78.4
|t10=辻 |cat10=[[:Category:和製漢字|和製漢字]] |v10=2,150 |m10=71.8
}}
数据来源:[https://pageviews.toolforge.org/topviews/?project=zh.wiktionary.org&platform=all-access&date={{CURRENTYEAR}}-{{CURRENTMONTH}}&excludes= Topviews Analysis - Most viewed pages of a project]
==收录词条==
{{also|Wiktionary:统计/各语言词条}}
==参看==
*[[Special:Statistics|基本统计]]
[[Category:維基詞典統計]]
9s85ueww2njww67iof5nv0x6eybxsog
stop
0
9586
9908775
8433185
2026-09-28T16:18:27Z
Chihunglu83
87715
9908775
wikitext
text/x-wiki
{{also|Stop|stóp|stôp|stɔ́p}}
{{minitoc}}
==英语==
===發音===
* {{a|en|RP}} {{enPR|stŏp}}, {{IPA|en|/stɒp/}}
* {{a|en|GenAm}} {{enPR|stäp}}, {{IPA|en|/stɑp/}}
* {{audio|en|en-us-stop.ogg|音頻(通用美式)}}
* {{audio|en|En-uk-a stop.ogg|音頻 (RP)}}
* {{rhymes|en|ɒp}}
===动词===
{{v-en|stop|stops|stopping|stopped|stopped}}
'''stop'''
#[[停止]]
* -{[[正體]]: '''(光)闌'''[電機工程]}-
* -{[[简体]]: '''停止'''[信息科技]}-
* -{[[正體]]: '''停止'''[計算機]}-
* -{[[正體]]: '''停止'''[自由軟體]}-
* -{[[简体]]: '''停止'''[自由软体]}-
* -{[[正體]]: '''停止'''[電子計算機]}-
'''stop'''
==西班牙语==
m. 停止,站住
[[Category:英语动词]]
[[Category:電機工程專有名詞]]
[[Category:信息科技專有名詞]]
[[Category:自由軟體專有名詞]]
[[Category:電子計算機專有名詞]]
[[Category:英语名词]]
[[Category:西班牙语]]
[[Category:西班牙语名词]]
t67tz9c689lb2wqq3g7urjsac40lguf
mauve
0
110871
9908796
9908696
2026-09-29T01:03:46Z
肉丝跑蛋
18924
/* 英語 */
9908796
wikitext
text/x-wiki
==英語==
===發音===
* {{a|en|UK}} {{IPA|en|/məʊv/}}
* {{a|en|US}} {{IPA|en|/mɔːv/|/moʊv/}}
* {{audio|en|En-uk-mauve.ogg|音頻(英式)}}
* {{audio|en|en-us-mauve.ogg|音頻(美式)}}
* {{audio|en|en-us-mauve2.ogg|音頻(美式)(异读)}}
* {{rhymes|en|əʊv}}
:'''mauve'''
#[[淡紫色]][[染料]]的一种
#[[淡紫色]]
#[[淡紫色的]]
==法語==
===詞源===
源自{{inh|fr|fro|mauve}},源自{{inh|fr|la|malva|t=錦葵}}。植物義始見於1256年;「淡紫色」之義源自花名,始見於1804年;形容詞用法源自顏色名,始見於1829年。
===發音===
* {{fr-IPA}}
* {{audio|fr|Fr-mauve.ogg}}
* {{rhymes|fr|ov}}
===名詞===
{{fr-noun|f}}
# {{lb|fr|植物學}} [[錦葵]]
===名詞===
{{fr-noun|m}}
# [[淡紫色]]
#: {{uxi|fr|Le '''mauve''' convient à votre teint.|'''淡紫色'''很襯你的膚色。}}
===形容詞===
{{fr-adj}}
# [[淡紫色]]的
#: {{uxi|fr|L'horizon était '''mauve''', et j'ai su qu'il ferait beau.|地平線呈'''淡紫色''',我就知道天氣會轉好。}}
===參見===
{{table:colors/fr}}
===延伸閱讀===
* {{R:fr:TLFi}}
[[Category:法語 顏色]]
[[Category:法語 植物]]
[[Category:英语名词]]
[[Category:待分類的英語詞]]
[[Category:英語]]
[[分类:拉丁文字]]
clqz5ii0ijeb74ienc5esud5rbcczf3
생물학
0
141500
9908743
7028358
2026-09-28T13:01:11Z
Sayonzei
40728
/* 詞源 */
9908743
wikitext
text/x-wiki
==朝鮮語==
===詞源===
{{ko-etym-Sino|生物||學|suffix=y}}。
===發音===
{{ko-ipa}}
===名詞===
{{ko-noun|hanja=生物學}}
# [[生物學]]
{{C|ko|生物學}}
2combamh3a0pplexqawonvqas5104cy
WR
0
163549
9908747
5515271
2026-09-28T13:04:49Z
Sayonzei
40728
9908747
wikitext
text/x-wiki
{{also|wr|ŵr|-wr}}
==英語==
===名詞===
{{en-noun}}
# {{lb|en|運動|競速破關}} {{initialism of|en|world record|t=世界紀錄}}。
#: {{cot|en|WB|OR|CR|TR|NR|GR|MR}}
# {{lb|en|American football|加拿大式足球}} {{initialism of|en|wide receiver|t=外接手}}。
#: {{cot|en|QB|FB|HB|LB}}
# {{lb|en|star}} {{initialism of|en|Wolf-Rayet star|t=沃尔夫-拉叶星}}。
#: {{cot|en|WD|RD|PSR}}
# {{init of|en|Wassermann reaction|t=瓦瑟曼氏反應}}。
===專有名詞===
{{en-proper noun}}
# {{lb|en|聯合式橄欖球}} {{initialism of|en|w:en:World Rugby|t={{w|世界橄欖球總會}}}}
# {{lb|en|British}} {{initialism of|en|[[William]] [[rex|Rex]]|t=英國{{w|lang=en|威廉四世}}的君主标志}}。
===異序詞===
* {{anagrams|en|a=rw|RW}}
i24wvtl9bqbzkagy30vb7640u5ubmq4
offiziell
0
297931
9908836
6040168
2026-09-29T05:08:04Z
Sayonzei
40728
9908836
wikitext
text/x-wiki
==德語==
===詞源===
源自{{der|de|fr|officiel}}。
===發音===
* {{IPA|de|/ˌɔfiˈtsjɛl/}}
* {{audio|de|De-offiziell.ogg|a=柏林}}
===形容詞===
{{de-adj|comp}}
# [[官方]]的;[[正式]]的
#: {{syn|de|amtlich<q:狹義>}}
#: {{ant|de|inoffiziell}}
#* {{quote-journal|de|author=Reimar Paul|title=Kosten für geplantes Atommüllendlager Konrad explodieren|work=Die Tageszeitung: taz|date=2025-11-28|url=https://taz.de/Kosten-fuer-geplantes-Atommuellendlager-Konrad-explodieren/!6133277/|issn=0931-9085|passage=Bei Beginn der Arbeiten wurden die Kosten für die Umrüstung des Bergwerks '''offiziell''' noch mit 1,6 Milliarden Euro beziffert.|t=工程开始时,'''官方'''估计该矿山改造的成本为16亿欧元。|archiveurl=https://web.archive.org/web/20260106173817/https://taz.de/Kosten-fuer-geplantes-Atommuellendlager-Konrad-explodieren/!6133277/}}
# [[名義]]上的,[[紙面]]上的
====變格====
{{de-adecl|comp}}
===延伸閱讀===
* {{R:de:Duden}}
* {{R:de:DWDS}}
b310bda5i8wfcp8z0zl4l30oh7pdjxm
offiziellen
0
298622
9908837
6055577
2026-09-29T05:08:39Z
Sayonzei
40728
9908837
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlən/}}
* {{audio|de|De-offiziellen.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offiziell}}
484ifc1ap9m7yydhxuaplaeqfmbjnji
offizielle
0
299650
9908838
6055576
2026-09-29T05:08:45Z
Sayonzei
40728
9908838
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlə/}}
* {{audio|de|De-offizielle.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offiziell}}
on6hxwpxyfvfuuvabmj5zr4k1qhb2xx
㑌
0
301908
9909202
6761876
2026-09-29T11:18:46Z
TongcyDai
53191
9909202
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=9|rad=人|as=06|sn=8|four=21211|canj=OSMG|ids=⿰亻匡}}
====參考資料====
* {{Han ref|kx=0101.210|dkj=00584|hdz=10143.120|uh=344C}}
==漢語==
{{zh-forms}}
===詞源1===
{{zh-see|尪|v}}
===詞源2===
====發音====
{{zh-pron
|m=kuāng
|cat=
}}
====釋義====
{{head|zh|漢字}}
# {{zh-only|㑌儴}}
==朝鮮語==
===漢字===
{{ko-hanja|hangeul=왕|rv=wang|mr=wang|y=wang}}
# {{rfdef|ko|sort=人06}}
aqm49b43lb201u3vr7yr0pbf2wjynf4
烄
0
310105
9908783
6765212
2026-09-28T22:29:45Z
Fglffer
55252
9908783
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=86|rad=火|as=06|sn=10|four=|canj=FYCK|ids=⿰火交}}
====來源====
* {{Han ref|kx=0669.110|dkj=18982|dj=1078.180|hdz=32200.180|uh=70C4}}
==漢語==
{{zh-forms}}
===字源===
{{Han etym}}
===詞源1===
可能來自{{inh|zh|sit-pro|*k/ʔ-r(ə/a)w|t=炒,烤,煮}}。與{{och-l|燎|夜間手持火把狩獵,焚燒,獻上燔祭}}、{{cog|my|ကြော်|t=炒}}同源 (STEDT #5862)。
====發音====
{{zh-pron
|m=jiǎo
|c=gaau2
|mc=y
|oc=y
|cat=v
}}
====釋義====
{{head|zh|漢字}}
# {{lb|zh|hist-dict}} [[焚燒]][[木柴]]作為[[祭品]]獻給[[上天]]
===詞源2===
與詞源1相關,雖然細節尚不清楚 (STEDT)。
====發音====
{{zh-pron
|m=yào
|c=
|cat=v
}}
====釋義====
{{head|zh|漢字}}
# [[煎]]
# [[炒]]
{{C|zh|火|烹飪}}
rqortiwvairmbi31kqvpff6mhxc0cfr
畀
0
310719
9908788
9660972
2026-09-28T22:59:14Z
Fglffer
55252
9908788
wikitext
text/x-wiki
{{also|畁|𢌿|𢍁|𢍉|異}}
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=102|rad=田|as=03|sn=8|four=60221|canj=WML|ids=⿱田丌}}
====衍生字====
* {{charlist|sc=Hani|㑭淠睤綥箅鼻痹}}
{{col-top|1|額外衍生字}}
* {{charlist|𡌸𢮧𥚈𦜠𧤃𧨬𧳠𩩚𱡜𤱿𡻼𢈷𧚽𣽠𢿯}}
* {{charlist|sc=Kore|𪰀}}
{{col-bottom}}
====來源====
{{Han ref|kx=0759.160|dkj=21748|dj=1169.170|hdz=42530.060|sbgy=355.16|ch=9120005.03|uh=7540}}
==漢語==
===字源===
{{Han etym}}
甲骨文中是{{liushu|p|nocap=1}}描繪一支平頭箭(參見「{{zh-l|*矢}}」,即箭) – {{och-l|錍|平頭箭}}的原始字形。
金文中,字的中央增添了一個點,後來演變為一條短線。該字形底部最終演變為「{{zh-l|*廾}}」或「{{zh-l|*丌}}」。《說文解字》根據後者的形狀,錯誤地將此字解讀為{{Han compound|甶|丌|ls=psc|c1=p|c2=s|t2=基座|nocap=1}}。
===詞源1===
{{zh-forms|alt=畁,𢌿,𢍁,𢍉,𢮅,𢮧,俾-粵語口語,比-粵語口語,捭-粵語口語}}
{{inh+|zh|sit-pro|*Pi(k)||給}}。同屬漢語族的其他同源詞包括{{zh-l|賓}}、{{zh-l|嬪}}、{{zh-l|殯}};漢語族以外的詞彙,與{{cog|bo|སྦྱིན||給,賜予}}、{{cog|my|ပေး||給}}等同源 (STEDT)。
====發音====
{{zh-pron
|m=bì
|m-s=
|c=bei2,bei3
|c_note=bei2 - 白讀; bei3 - 文讀
|c-dg=bai2
|c-t=ei2
|g=
|h=gd=bi4
|j=
|mb=
|md=bé
|mn=ml:pì
|mn-t=bi3/bi2
|w=sh:5pi
|x=
|mc=y
|oc=y
|ma=y
|cat=v,c:prep
}}
====釋義====
{{head|zh|漢字}}
# {{lb|zh|obsolete|or|Cantonese}} [[給]]
#* {{zh-x|王 崩,周 人 將 畀 ^虢 ^公 政。|(周)王已逝。周人將把權力交給虢公。|ref=Zuozhuan}}
#* {{quote-journal|zh|year=1853|work=遐邇貫珍|title=zh:序言<tr:->|volume=1|issue=1}}
#*: {{zh-q|上帝 創{cong3}造{zou6} 斯 世、各國 咸 畀 以 境土|{{...}} 上帝創造這個世界;各國都被'''賜予'''各自的領土 {{...}}|CL}}
#: {{zh-x|畀 三 張 紙 我。|'''給'''我三張衛生紙。|C}}
# {{lb|zh|Cantonese|dated}} [[用]]
# {{lb|zh|Cantonese|dated|or|uncommon}} [[加]]
#: {{zh-co|畀 啲 鹽|'''加'''些鹽|C}}
#: {{zh-co|畀油{jau4-2}|加油|C}}
# {{lb|zh|Cantonese}} [[付]]
#: {{zh-also|畀錢}}
#: {{zh-co|畀 入場 費|'''付'''入場費|C}}
# {{lb|zh|Cantonese}} 用...[[支付]]
#: {{zh-co|畀 信用卡|我用信用卡|C}}
# {{lb|zh|Cantonese}} [[允許]],[[讓]]
#: {{zh-x|老師 唔 畀 我哋 上{soeng5} 緊 堂 嗰時 飲水。|老師不'''讓'''我們在課堂上喝水。|C}}
# {{lb|zh|Cantonese}} [[為了]]
#: {{zh-x|原先 係 畀 你 食 嘅。|原本是要'''給'''你吃的。|C}}
# {{lb|zh|Cantonese}} [[受]]
#: {{zh-co|講 畀 你 知|告訴你|C}}
#: {{zh-x|送 份 禮物 畀 老師|送老師一份禮物|C}}
# {{lb|zh|Cantonese}} [[被]]
#: {{zh-x|我 畀 蚊 咬 呀。|我'''被'''蚊子咬了。|C}}
#: {{zh-x|我 隻 錶 畀 人 偷 咗。|我的錶'''被'''人偷了。|C}}
=====使用說明=====
* 在粵語中,當「{{lang|zh|畀}}」用作雙及物動詞表示「給予」時,間接賓語通常跟在直接賓語之後。
* 在粵語的被動語態句子中,「{{lang|zh|畀}}」後面總是跟一個賓語,而不是動詞;這與官話的「{{zh-l|被}}」不同,後者後面可以跟賓語或動詞。當行為者未被指明時,「{{zh-l|人|tr=jan4}}」可用作虛賓語。
=====同義詞=====
{{zh-dial|給}}
{{zh-dial|被}}
{{zh-dial|付}}
====組詞====
{{col3|zh|付畀|投畀豺虎|賦畀|畀錢|倚畀|委畀|投畀|畀付|畀矜|秉畀炎火|簡畀|蒸畀|顧畀|畀面|畀電話|畀人|畀個冬瓜你做膽|畀個官你做|交個波畀|寧畀人知,莫畀人見|畀天收|畀雷劈|畀心機|大蝦細畀屎餵|畀著//畀着|畀位|畀咭|畀鬼迷|畀鬼砸|死畀你睇|畀個天你做膽|畀都唔|畀油}}
===詞源2===
{{zh-forms}}
====發音====
{{zh-pron
|w=sh:7peq
|cat=v,prep
}}
====釋義====
{{head|zh|漢字}}
# {{lb|zh|Wu}} {{alt form|zh|撥|tr=-|t=給予;允許;施予;透過}}
===詞源3===
{{zh-forms}}
====發音====
{{zh-pron
|h=pfs=pí
|cat=
}}
====釋義====
{{head|zh|漢字}}
# {{lb|zh|Taiwanese Hakka}} {{contraction of|zh|[[分]][[佢]]}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=田03}}
# [[給]]
====讀法====
{{ja-readings
|goon=ひ
|kanon=ひ
|kun=あたえるもの
}}
====組詞====
* {{ja-r|畀予|ひよ}}
==朝鮮語==
===漢字===
{{ko-hanja|비}}
# [[給]]
==越南語==
===漢字===
{{vi-readings|rs=田03|reading=tý}}
# {{rfdef|vi|sort=田03}}
g0jzyhh68m8t7y9oune0xpfyzh9vxbt
畁
0
310720
9908784
5727108
2026-09-28T22:32:32Z
Fglffer
55252
9908784
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=102|rad=田|as=03|sn=8|four=|canj=LWML|ids=⿱由丌}}
====衍生字====
* {{charlist|sc=Hani|腗}}
{{col-top|1|額外衍生字}}
* {{charlist|sc=Hani|𡞑𤲦𥟗}}
{{col-bottom}}
====來源====
* {{Han ref|kx=0759.180|dkj=21749|hdz=42530.070|uh=7541}}
==漢語==
===字源===
{{Han etym}}
===詞源1===
{{zh-see|畀|v}}
===詞源2===
====發音====
{{zh-pron
|m=qí
|c=
|mc=y
|oc=y
|cat=v
}}
===釋義===
{{head|zh|漢字}}
# {{alt form|zh|𢍁}}
jg9339m8adi510x724eblgknft2dttv
疈
0
310777
9908791
8323815
2026-09-28T23:08:38Z
Fglffer
55252
9908791
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=102|rad=田|as=15|sn=20|four=|canj=MWILW|ids=⿲畐𬼀畐}}
====來源====
{{Han ref|kx=0767.040|dkj=21971|dj=1177.100|hdz=42555.030|uh=7588|sbgy=528.41|ch=9130006.01}}
==漢語==
{{zh-forms|alt=𠠦}}
===字源===
{{Han etym}}
{{Han compound|㽬|刀|alt2=𬼀|ls=psc|c1=p|c2=s|t2=刀子}}。
{{zh-m|*副}}的大篆形式。
===詞源===
{{zh-see|副|a|破開;剖分}}
===來源===
* {{R:twedu|A00365-003}}
==朝鮮語==
===漢字===
{{ko-hanja|벽}}
# {{rfdef|ko|sort=田15}}
o075o9ht44l07iapvwonw3m1ont043w
箅
0
311664
9908790
6765590
2026-09-28T23:04:17Z
Fglffer
55252
9908790
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=118|rad=竹|as=08|sn=14|four=88221|canj=HWML|ids=⿱𥫗畀}}
====衍生字====
* {{charlist|sc=Hani|𤀥}}
====來源====
* {{Han ref|kx=0886.320|dkj=26112|dj=1314.270|hdz=52982.040|ch=10150001.01|sbgy=373.43|uh=7B85}}
==漢語==
{{zh-forms}}
===字源===
{{Han etym}}
===發音===
{{zh-pron
|m=bì
|c=bei3
|g=pi
|mn=tw:pín
|h=gd=bi4
|x=bi2
|mc=y
|oc=y
|cat=n
}}
===釋義===
{{head|zh|漢字}}
# [[平面]]而有[[孔隙]]的[[竹器]]
#: {{zh-also|甗}}
====組詞====
{{col3|zh|箅庳|箅子}}
jkra0bj9jx6kg0hlz3y3z6kbep9osxf
箙
0
311679
9909127
9661381
2026-09-29T08:24:46Z
Fglffer
55252
9909127
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=118|rad=竹|as=08|sn=14|four=88247|canj=HBSE|ids=⿱𥫗服}}
====來源====
* {{Han ref|kx=0888.060|dkj=26148|dj=1316.020|hdz=52984.110|ch=10170004.01|sbgy=453.36|uh=7B99}}
==漢語==
{{zh-forms|alt=𥵩,𩋟,𤰈}}
===字源===
{{Han etym}}
原是{{liushu|p|nocap=y}} – 箭筒裡有一兩支箭。
金文中,「箭頭」與「箭筒」合為「{{zh-l|*用}}」,由此衍生出如今寫作「{{zh-l|*𤰈}}」的字形,並傳承至「{{zh-l|*備}}」字之中。
現在字形為{{Han compound|竹|服|ls=psc|c1=s|c2=p|alt1=𥫗|nocap=y}}。
===發音===
{{zh-pron
|m=fú
|c=fuk6
|mc=y
|oc=y
|cat=n
}}
===釋義===
{{head|zh|漢字}}
# [[箭袋]],[[箭筒]]
===組詞===
{{col3|zh|弩箙|矢箙|箭箙}}
===來源===
* {{R:yue:Hanzi}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=竹08}}
# {{rfdef|ja|sort=竹08}}
====讀法====
{{ja-readings
|goon=ぶく
|kanon=ふく
|kun=えびら-}}
===詞源===
{{ja-kanjitab|えびら|yomi=k}}
===名詞===
{{ja-noun|えびら}}
# {{lb|ja|archery}} [[盒子]]狀[[箭筒]]
====衍生詞====
* {{ja-r|箙藤|エビラフジ}}
{{C|ja|Bags}}
==朝鮮語==
===漢字===
{{ko-hanja|hangeul=[[복]]|eumhun=|rv=bok|mr=pok|y=pok}}
# {{rfdef|ko|sort=竹08}}
iusambbdbokrdv8ik6h99jwt7eyyjav
9909170
9909127
2026-09-29T09:07:32Z
Fglffer
55252
9909170
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=118|rad=竹|as=08|sn=14|four=88247|canj=HBSE|ids=⿱𥫗服}}
====來源====
* {{Han ref|kx=0888.060|dkj=26148|dj=1316.020|hdz=52984.110|ch=10170004.01|sbgy=453.36|uh=7B99}}
==漢語==
{{zh-forms|alt=𥵩,𩋟,𤰈}}
===字源===
{{Han etym}}
原是{{liushu|p|nocap=y}} – 箭筒裡有一兩支箭。
金文中,「箭頭」與「箭筒」合為「{{zh-l|*用}}」,由此衍生出如今寫作「{{zh-l|*𤰈}}」的字形,並傳承至「{{zh-l|*備}}」字之中。
現在字形為{{Han compound|竹|服|ls=psc|c1=s|c2=p|alt1=𥫗|nocap=y}}。
===發音===
{{zh-pron
|m=fú
|c=fuk6
|mc=y
|oc=y
|cat=n
}}
===釋義===
{{head|zh|漢字}}
# [[箭袋]],[[箭筒]]
===組詞===
{{col3|zh|弩箙|矢箙|箭箙}}
===來源===
* {{R:yue:Hanzi}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=竹08}}
# {{rfdef|ja|sort=竹08}}
====讀法====
{{ja-readings
|goon=ぶく
|kanon=ふく
|kun=えびら-}}
===詞源===
{{ja-kanjitab|えびら|yomi=k}}
===名詞===
{{ja-noun|えびら}}
# {{lb|ja|archery}} [[盒子]]狀[[箭筒]]
====衍生詞====
* {{ja-r|箙藤|エビラフジ}}
==朝鮮語==
===漢字===
{{ko-hanja|hangeul=[[복]]|eumhun=|rv=bok|mr=pok|y=pok}}
# {{rfdef|ko|sort=竹08}}
7kmpq17o4gwpmexqw0ljot7tp3ymv74
肔
0
312488
9909171
5724209
2026-09-29T09:09:34Z
Fglffer
55252
9909171
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=130|rad=肉|as=03|sn=7|four=|canj=BPD|ids=⿰月也}}
====來源====
* {{Han ref|kx=0973.230|dkj=29261|dj=1425.180|hdz=32046.060|sbgy=244.27|uh=8094}}
==漢語==
{{zh-forms}}
===字源===
{{Han etym}}
===發音===
{{zh-pron
|m=chǐ
|mc=y
|oc=y
|cat=v
}}
===釋義===
{{head|zh|漢字}}
# {{alt form|zh|胣}}
cfe7k3fdxqm1vmsxwyqhrizni360rar
胣
0
312547
9909169
6696337
2026-09-29T09:07:03Z
Fglffer
55252
9909169
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=130|rad=肉|as=05|sn=9|four=78212|canj=BOPD|ids=⿰月㐌}}
====來源====
* {{Han ref|kx=0980.030|dkj=29404|dj=1432.010|hdz=32061.020|ch=10950001.07|uh=80E3}}
==漢語==
{{zh-forms|alt=肔}}
===字源===
{{Han etym|肔}}
===發音===
{{zh-pron
|m=yǐ,chǐ
|c=ji5,ci2
|mc=y
|oc=y
|cat=v
}}
===釋義===
{{head|zh|漢字}}
# {{lb|zh|literary}} [[剖腹]];[[開膛]]
==越南語==
===字源===
{{Han compound|lang=vi|肉|㐌|ls=psc|c1=s|alt1=⺼|c2=p|tr2={{vi-l|đã}}}}。
===漢字===
{{vi-readings|rs=肉05|nom=dạ-tdcndg;tdcntd;gdhn;btcn;bonet;taberd, nhựa-tdcndg}}
# {{vi-Nom form of|dạ|[[腹部]]}}
# {{vi-Nom form of|nhựa|[[樹脂]];[[樹液]]}}
===來源===
{{reflist}}
0f40z7sp2erusv4mb699n43aw8y4ig5
도서관
0
336670
9908745
7576238
2026-09-28T13:01:20Z
Sayonzei
40728
/* 詞源 */
9908745
wikitext
text/x-wiki
==朝鮮語==
[[File:Daegu DNUE library.jpg|thumb|{{m-self|ko|도서관|t=圖書館}}]]
===詞源===
{{ko-etym-Sino|圖書||館|suffix=y}}。
===發音===
{{ko-IPA}}
===名詞===
{{ko-noun|hanja=圖書館}}
# [[圖書館]]
#: {{ko-x|저는 아우와 함께 도서관에 가고 싶어요.|我喜歡跟我弟弟一起去'''圖書館'''。}}
{{C|ko|建築|書}}
glvejifbjvdggmagoa9ltwpmszvz25s
법률학
0
418278
9908741
7163492
2026-09-28T13:00:37Z
Sayonzei
40728
/* 詞源 */
9908741
wikitext
text/x-wiki
==朝鮮語==
{{wikipedia|lang=ko}}
===詞源===
{{ko-etym-sino|法律||學|suffix=y}}。
===發音===
{{ko-IPA}}
===名詞===
{{ko-noun|hanja=法律學}}
# [[法律學]]
jibx7piiwx65l4vxid38y5wf86syyhf
보신탕
0
420904
9908759
9901079
2026-09-28T13:37:59Z
YeBoy371
43905
使用[[WP:HOTCAT|HotCat]]已移除[[Category:朝鮮語 糖]]; 已添加[[Category:朝鮮語 湯]]
9908759
wikitext
text/x-wiki
==朝鮮語==
===詞源===
{{ko-etym-sino|補身湯}}。該詞約於 1984 年提出,當時首爾市籌辦 1986 年[[亞運會]]與 1988 年[[奧運會]],政府為了迎接外賓、營造良好的城市形象,下令禁止販售狗肉,故稱為「補身湯」來規避狗肉禁令。
===發音===
{{ko-IPA|l=y}}
===名詞===
{{ko-noun|hanja=補身湯}}
# [[補身湯]],[[狗肉]][[湯]]
====近義詞====
* {{ko-l|개장국}}
* {{i|北韓}} {{ko-l|단고기탕}}
====後代詞====
* {{desc|en|bosintang|bor=1}}
{{C|ko|湯}}
85fia4xqmkwsywdhlat3bamoy8gpqdk
용의자
0
481132
9908742
8369087
2026-09-28T13:01:07Z
Sayonzei
40728
/* 詞源 */
9908742
wikitext
text/x-wiki
==朝鮮語==
===詞源===
{{ko-etym-Sino|容疑||者|suffix=y}}。
===發音===
{{ko-IPA|ui=2}}
===名詞===
{{ko-noun|hanja=容疑者}}
# [[犯罪]][[嫌疑人]]
nob92bqmmn8dknzzeylbeztte1xgc8b
응모자
0
490812
9908746
7916580
2026-09-28T13:01:26Z
Sayonzei
40728
/* 詞源 */
9908746
wikitext
text/x-wiki
==朝鮮語==
===詞源===
{{ko-etym-Sino|應募||者|suffix=y}}。
===發音===
{{ko-IPA|l=y}}
===名詞===
{{ko-noun|hanja=應募者}}
# [[報名]]人,[[申請]]人
#: {{uxi|ko|[[이번]] [[공채]][[-에는]] '''응모자'''[[가]] [[많다|많았다]].|這次公開招聘很多人報名。}}
====參見====
* {{m|ko|응모인(應募人)}}
ee87c17m29f13pa1uii9ukw1n6a1u2w
일본어
0
496970
9908744
8023544
2026-09-28T13:01:16Z
Sayonzei
40728
/* 詞源 */
9908744
wikitext
text/x-wiki
==朝鮮語==
{{wikipedia|lang=ko}}
===詞源===
{{ko-etym-Sino|日本||語|suffix=y}}。
===發音===
{{ko-IPA|cap=y}}
===專有名詞===
{{ko-propn|hanja=日本語}}
# [[日語]]
====近義詞====
* {{ko-l|^일어|4=日語}}
4me0dubr4dy6uiwst5ctrvsrrcr0o58
주정꾼
0
524509
9909114
1373742
2026-09-29T07:29:18Z
Sayonzei
40728
9909114
wikitext
text/x-wiki
==朝鮮語==
===詞源===
源自 {{affix|ko|주정(酒酊)|-꾼}}。
===發音===
{{ko-IPA|l=y}}
===名詞===
{{ko-noun}}
# [[酒鬼]],[[醉汉]]
====近義詞====
* {{ko-l|주정뱅이}}
====相關詞彙====
* {{ko-l|주정하다}}
qvahzsr2ud66mn08hohmzxmmjrsupmt
주정뱅이
0
524523
9909115
8348844
2026-09-29T07:30:16Z
Sayonzei
40728
9909115
wikitext
text/x-wiki
==朝鮮語==
===詞源===
源自 {{af|ko|주정(酒酊)|-뱅이}}。
===發音===
{{ko-IPA|l=y}}
===名詞===
{{ko-noun}}
# [[酒鬼]]
====近義詞====
* {{ko-l|주정꾼}}
====相關詞彙====
* {{ko-l|주정하다}}
b48kb6n85mfwof7i4j3j9tvvme1mz31
狗肉
0
608550
9909195
8912488
2026-09-29T10:34:32Z
Jiba1219
110530
/* 日語 */
9909195
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
{{zh-wp}}
===發音===
{{zh-pron
|m=gǒuròu
|c=gau2 juk6
|g='gieu3 nyiuh6
|h=gd=gêu3 ngiug5
|mn=xm,zz,tw,pn:káu-bah/qz,jj,ph:káu-mah/qz:káu-hia̍k
|mn-t=gao2 nêg8
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# [[狗]]的[[肉]] {{zh-mw|m:塊}}
# {{n-g|用於辱罵或淫褻}}
====同義詞====
{{zh-dial}}
====衍生詞====
{{col3|zh|狗肉數|掛羊頭賣狗肉|羊頭狗肉}}
====翻譯====
{{trans-top|狗的肉}}
* 荷兰语:{{t|nl|hondenvlees|n}}
* 英語:{{t+|en|dog meat}}、{{t+|en|dog}}
* 世界语:{{t|eo|hundoviando}}、{{qualifier|年輕狗的}} {{t|eo|hundidoviando}}、{{t|eo|hundaĵo}}、{{qualifier|年輕狗的}} {{t|eo|hundidaĵo}}
* 芬兰语:{{t+|fi|koira}}、{{t|fi|[[koira|koiran]] [[liha]]}}
* 法语:{{t|fr|viande de chien|f}}
* 德语:{{t+|de|Hundefleisch|n}}、{{qualifier|年輕狗的}} {{t|de|Hundchenfleisch|n}}
* 匈牙利语:{{t|hu|kutyahús}}
* 伊多语:{{t|io|hundokarno}}
* 意大利语:{{t|it|carne di cane|f}}
* 日语:{{t+|ja|狗肉|tr=くにく, kuniku}}
* 朝鲜语:{{t+|ko|개고기|sc=Kore}}、{{t+|ko|단고기}}
* 马来语:
*: 爪夷文字:{{t|ms|داݢيڠ انجيڠ|sc=Arab}}、{{t|ms|داݢيڠ اسو|sc=Arab}}
*: 马来语文字:{{t|ms|daging anjing}}、{{t|ms|daging asu}}、{{t|ms|daging kuyuk}}
* 蒙古语:{{t|mn|нохойн мах}}
* 挪威语:
*: 书面挪威语:{{t|no|hundekjøtt|n}}
*: 新挪威语:{{t|nn|hundekjøt|n}}、{{t|nn|hundekjøtt|n}}
* 葡萄牙语:{{t|pt|[[carne]] [[de]] [[cachorro]]/[[cão]]|f}}
* 俄语:{{t+|ru|пси́на|f}}、{{t+|ru|пся́тина|f}}、{{t+|ru|собача́тина|f}}、{{t+|ru|соба́чина|f}}
* 瑞典语:{{t+|sv|hundkött|n}}
* 泰语:{{t+|th|เนื้อหมา}}、{{t+|th|เนื้อสุนัข}}
* 土耳其语:{{t|tr|köpek eti}}
* 乌克兰语:{{t|uk|пси́на|f}}、{{t|uk|пся́тина|f}}、{{t|uk|собача́тина|f}}、{{t|uk|соба́чина|f}}
* 越南语:{{t|vi|thịt chó}}
* 威尔士语:{{t|cy|cig i gŵn|m}}
* 雅库特语:{{t|sah|ыт этэ}}
{{trans-bottom}}
{{zh-cat|肉}}
==日語==
{{ja-kanjitab|く|にく|yomi=goon}}
===發音===
{{ja-pron|くにく|acc=0|acc2=1}}
===名詞===
{{ja-noun|くにく}}
# [[#漢語|狗肉]]
====衍生詞====
* {{ja-r|羊%頭%狗%肉|よう%とう%く%にく}}
{{C|ja|肉|sort=くにく}}
==朝鮮語==
{{ko-hanjatab}}
===名詞===
{{ko-noun|hangeul=구육}}
# {{hanja form of|구육}}
8agmfshtivna1hhrizlj81rdg3emss6
aktualisieren
0
759439
9908894
9552328
2026-09-29T05:27:25Z
Sayonzei
40728
9908894
wikitext
text/x-wiki
==德語==
===詞源===
{{rfe|de|並非對應{{noncog|en|actualize}}。}}
===發音===
* {{IPA|de|/aktualiˈziːʁən/|[ʔaktʰualiˈziːɐ̯n]}}
* {{audio|de|De-aktualisieren.ogg}}
* {{audio|de|De-aktualisieren2.ogg|a=柏林}}
===動詞===
{{de-verb}}
# [[更新]],[[升级]],使最新
#: {{syn|de|auf den neuesten Stand bringen|aufdatieren|ajourieren|q3=奧地利}}
# {{lb|de|計算機}} [[刷新]]
====變位====
{{de-conj}}
====派生詞彙====
* {{l|de|Aktualisierer}}
* {{l|de|Aktualisierung}}
====相關詞彙====
* {{l|de|Aktualisieren}}
* {{l|de|aktuell}}
====派生語彙====
* {{desc|bor=1|sl|aktualizȋrati}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:UniLeipzig}}
* {{R:de:Duden}}
* {{R:de:OpenThesaurus}}
{{cln|de|含有後綴-ieren的詞}}
dpit6io4egxv3poawf4hqnjny7pym4d
aktualisiert
0
759441
9908895
4865735
2026-09-29T05:28:59Z
Sayonzei
40728
9908895
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯t/}}
* {{audio|de|De-aktualisiert.ogg|a=柏林}}
===分詞===
{{head|de|過去分詞}}
# {{past participle of|de|aktualisieren}}
====變格====
{{de-adecl}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||3|s|pres|;|2|p|pres|;|p|imp}}
bptkql74zsx6bs2g9ny7puvvf41j77r
attisch
0
762398
9908804
4873794
2026-09-29T04:39:45Z
Sayonzei
40728
9908804
wikitext
text/x-wiki
{{also|Attisch}}
==德語==
===詞源===
{{m|la|Atticus}} + {{m|de|-isch}}(並非 {{affix|de|Attika|-isch}})。
===發音===
* {{IPA|de|/ˈatɪʃ/}}
* {{audio|de|De-attisch.ogg|a=柏林}}
===形容詞===
{{de-adj}}
# {{lb|de|relational}} [[阿提卡]]的
====變格====
{{de-adecl}}
====派生詞彙====
* {{l|de|attisches Salz}}
fyp4hzixluemuhmxtq3f513nw5y58kr
Deutschlandlied
0
771456
9908833
4828614
2026-09-29T04:58:02Z
Sayonzei
40728
9908833
wikitext
text/x-wiki
==德語==
{{swp|de:}}
===詞源===
{{ety|de|:af|Deutschland|Lied}}
{{com+|de|Deutschland|Lied}}。
===發音===
* {{IPA|de|/ˈdɔɪ̯t͡ʃlantˌliːt/}}
* {{audio|de|De-Deutschlandlied.ogg|a=柏林}}
===專有名詞===
{{de-proper noun|n}}
# 《{{w|德意志之歌}}》{{gl|[[德國]][[國歌]]}}
#: {{synonyms|de|[[Lied]] [[der]] [[Deutscher|Deutschen]]}}
{{C|de|德國|國歌}}
6j935u4vbhevhca9wjy4c50ibn12v2l
Disko
0
772115
9908812
4828853
2026-09-29T04:45:11Z
Sayonzei
40728
9908812
wikitext
text/x-wiki
{{also|disko|diskó}}
==英語==
===專有名詞===
{{en-prop}}
# {{surname|en}}。
#* {{quote-journal|en|author=Wilson Walker|title=Community of Mountain House votes overwhelmingly to become 1st California city since 2011|magazine=w:CBS News|url=https://www.cbsnews.com/sanfrancisco/news/mountain-house-san-joaquin-county-california-city-incorporation/|date=2024-03-13|passage=Board Member Matt '''Disko''' is set to become Council Member '''Disko''' and says incorporation is primarily about greater control.|t=董事会成员马特·'''迪斯科'''即将成为市议员'''迪斯科''',他表示,公司化主要是为了获得更大的控制权。|archiveurl=https://web.archive.org/web/20250404161238/https://www.cbsnews.com/sanfrancisco/news/mountain-house-san-joaquin-county-california-city-incorporation/}}
====派生詞彙====
{{col|en|Disko Island}}
==德語==
{{wp|de:}}
===詞源===
{{ety|de|:bor|en:disco}}
{{bor+|de|en|disco}}。
===發音===
* {{IPA|de|/ˈdɪsko/}}
* {{hyph|de|Dis|ko}}
* {{audio|de|De-Disko.ogg|a=柏林}}
* {{homophones|de|Disco}}
===名詞===
{{de-noun|f,s}}
# {{alt form|de|Disco}}
====變格====
{{de-ndecl|f,s}}
===名詞===
{{de-noun|m,-.sg}}
# {{alt form|de|Disco}}
====變格====
{{de-ndecl|m,-.sg}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:UniLeipzig}}
{{C|de|建築}}
m5rz9gf213gqv71d5zwsvgigfpn0qv7
Diskothek
0
772133
9908814
4828860
2026-09-29T04:46:01Z
Sayonzei
40728
9908814
wikitext
text/x-wiki
==德語==
{{wp|de:}}
===詞源===
{{ety|de|:bor|fr:discothèque}}
{{bor+|de|fr|discothèque}}。
===發音===
* {{IPA|de|/dɪskoˈteːk/}}
* {{audio|de|De-Diskothek.ogg|a=柏林}}
* {{hyph|de|Dis|ko|thek}}
===名詞===
{{de-noun|f}}
# [[唱片]][[收藏室]]
# [[迪廳]],[[夜總會]]
#: {{syn|de|Disco|Disko|Tanzbar<q:過時>|Tanzlokal<q:過時>}}
====變格====
{{de-ndecl|f}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:Duden}}
* {{R:de:OWID|406032}}
* {{R:de:UniLeipzig}}
{{C|de|建築}}
3cd9ueqjuk4059vi59k37t7d71nhir1
geographisch
0
791004
9908883
4940535
2026-09-29T05:20:57Z
Sayonzei
40728
9908883
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡeoˈɡʁaːfɪʃ/}}
* {{audio|de|De-geographisch.ogg|a=柏林}}
===形容詞===
{{de-adj}}
# {{alternative spelling of|de|geografisch}}
====變格====
{{de-adecl}}
tc8n2j0oepoc1fgplkcp9qbilf4djqk
sterbend
0
796928
9908944
5032607
2026-09-29T05:39:51Z
Sayonzei
40728
9908944
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʃtɛʁbn̩t/|/ˈʃtɛʁbm̩t/}}
* {{audio|de|De-sterbend.ogg|a=柏林}}
===分詞===
{{head|de|現在分詞}}
# {{present participle of|de|sterben}}
===形容詞===
{{de-adj}}
# [[垂死]]的
====變格====
{{de-adecl}}
aujtwu69x0scg1o7phm0k4qqwqy5xml
linientreu
0
797291
9908977
4971002
2026-09-29T05:50:28Z
Sayonzei
40728
9908977
wikitext
text/x-wiki
==德語==
===詞源===
{{ety|de|:af|Linie|treu}}
源自 {{compound|de|Linie|treu}}。
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯/}}
* {{audio|de|De-linientreu.ogg|a=柏林}}
===形容詞===
{{de-adj|comp}}
# [[忠實]]於特定[[意識形態]]或[[政黨]][[路線]]的
====變格====
{{de-adecl|comp}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:UniLeipzig}}
tqgr06pifbbp4kcxkr0w81fl6od0st4
symbolisieren
0
801121
9908956
9474388
2026-09-29T05:42:59Z
Sayonzei
40728
9908956
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁən/}}
* {{audio|de|De-symbolisieren.ogg}}
* {{audio|de|De-symbolisieren2.ogg|a=柏林}}
* {{hyph|de|sym|bo|li|sie|ren}}
===動詞===
{{de-verb}}
# 用[[符號]][[表示]]
# [[象徵]]
====變位====
{{de-conj}}
====相關詞彙====
{{col4|de
|Symbol
|Symbolik
|符號isch
|Symbolismus
}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:UniLeipzig}}
* {{R:de:Duden}}
* {{R:de:OpenThesaurus}}
pvl9zxk2z5tne1olxue5jmz7lh6cq0k
9908957
9908956
2026-09-29T05:43:15Z
Sayonzei
40728
/* 相關詞彙 */
9908957
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁən/}}
* {{audio|de|De-symbolisieren.ogg}}
* {{audio|de|De-symbolisieren2.ogg|a=柏林}}
* {{hyph|de|sym|bo|li|sie|ren}}
===動詞===
{{de-verb}}
# 用[[符號]][[表示]]
# [[象徵]]
====變位====
{{de-conj}}
====相關詞彙====
{{col4|de
|Symbol
|Symbolik
|symbolisch
|Symbolismus
}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:UniLeipzig}}
* {{R:de:Duden}}
* {{R:de:OpenThesaurus}}
1cm861z034xnyzdp0qa67oq83zo6497
Marseillaise
0
802304
9908834
4842500
2026-09-29T04:59:00Z
Sayonzei
40728
9908834
wikitext
text/x-wiki
{{also|marseillaise}}
==英語==
{{wp|en:La +}}
===其他形式===
* {{alt|en|Marseillese<id:Q41180>}}
===詞源===
{{ety|en|:bor|fr:Marseillaise|text=+|tree=1}} {{doublet|en|Marseillese}}。
===發音===
* {{IPA|en|/mɑː.seɪˈjɛz/|a=UK}}
* {{IPA|en|/mɑːɹseɪˈjɛz/|a=US}}
===專有名詞===
{{en-proper noun|head=the Marseillaise}}
# [[馬賽曲]]
===延伸閱讀===
* [https://web.archive.org/web/20100414212735/http://www.elysee.fr/elysee/francais/les_symboles_de_la_republique/la_marseillaise/la_marseillaise.21106.html French text, from presidential website]
{{C|en|法國|國歌}}
{{cln|en|派生自地名的詞}}
==法語==
===發音===
* {{fr-IPA}}
* {{rhymes|fr|ɛz|s=3}}
* {{audio|fr|LL-Q150 (fra)-Ncfavier-Marseillaise.wav}}
===名詞===
{{fr-noun|f}}
# {{female equivalent of|fr|Marseillais}}
===專有名詞===
{{fr-proper noun|f|head=La Marseillaise}}
# [[馬賽曲]]
====派生詞彙====
* {{l|fr|chanter la Marseillaise en breton}}
{{C|fr|國歌}}
7ah2490yvkly77cvs2lb9eje0fdunua
Tanzbar
0
802436
9908816
5726698
2026-09-29T04:46:46Z
Sayonzei
40728
9908816
wikitext
text/x-wiki
{{also|tanzbar}}
==德語==
{{wp|de:Diskothek}}
===詞源===
{{ety|de|:af|Tanz|Bar}}
源自 {{af|de|Tanz|Bar}}。
===發音===
* {{IPA|de|/ˈtant͡sbaːɐ̯/}}
* {{audio|de|De-Tanzbar.ogg|a=柏林}}
* {{hyph|de|Tanz|bar}}
* {{homophones|de|tanzbar}}
===名詞===
{{de-noun|f,s}}
# {{lb|de|過時}} 可[[跳舞]]的[[酒吧]];[[夜總會]]
#: {{syn|de|Diskothek|Disco|Disko|Tanzlokal<q:過時>}}
====變格====
{{de-ndecl|f,s}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:Duden}}
* {{R:de:OWID|103058}}
* {{R:de:UniLeipzig}}
{{C|de|建築}}
780senx6kxcnfh9kts3508gixsfd4mx
Tanzlokal
0
802468
9908818
4854341
2026-09-29T04:47:27Z
Sayonzei
40728
9908818
wikitext
text/x-wiki
==德語==
{{wp|de:Diskothek}}
===詞源===
{{ety|de|:af|Tanz|Lokal}}
源自 {{af|de|Tanz|Lokal}}。
===發音===
* {{IPA|de|/ˈtant͡sloˌkaːl/}}
* {{audio|de|De-Tanzlokal.ogg|a=柏林}}
* {{hyph|de|Tanz|lo|kal}}
===名詞===
{{de-noun|n,(e)s}}
# {{lb|de|過時}} 可以[[跳舞]]的[[餐厅]]或[[酒吧]]
#: {{syn|de|Diskothek|Disco|Disko|Tanzbar<q:過時>}}
#: {{cot|de|Tanzcafé}}
====變格====
{{de-ndecl|n,(e)s}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:Duden}}
* {{R:de:OWID|103059}}
* {{R:de:UniLeipzig}}
{{C|de|建築}}
mzqn4aa4nh09nprsub2glo7n2snd0ul
inoffiziell
0
810746
9908852
4957824
2026-09-29T05:11:15Z
Sayonzei
40728
9908852
wikitext
text/x-wiki
==德語==
===詞源===
{{ety|de|:af|in-|offiziell}}
{{prefix|de|in|offiziell}}
===發音===
* {{IPA|de|/ˈʔɪnʔɔfiˌtsi̯ɛl/}}
* {{audio|de|De-inoffiziell.ogg|a=柏林}}
===形容詞===
{{de-adj|comp}}
# 非[[官方]]的;[[非正式]]的
====變格====
{{de-adecl|comp}}
===延伸閱讀===
* {{R:de:Duden}}
* {{R:de:DWDS}}
1jzhi18n1b5wysgc1ojxrf7z6p1ro8p
inspirieren
0
810959
9908919
4958221
2026-09-29T05:35:15Z
Sayonzei
40728
9908919
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁən/}}
* {{audio|de|De-inspirieren.ogg}}
* {{audio|de|De-inspirieren2.ogg|a=柏林}}
===動詞===
{{de-verb}}
# [[激励]],[[鼓舞]]
# [[啟示]],[[啟發]]
====變位====
{{de-conj}}
====相關詞彙====
* {{l|de|Inspiration}}
====派生語彙====
* {{desc|et|inspireerima}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:UniLeipzig}}
* {{R:de:Duden}}
* {{R:de:OpenThesaurus}}
{{cln|de|含有後綴-ieren的詞}}
b7vy2nx8rn4zzwwc3t7uy1kl0vu4pfa
inspirierend
0
810961
9908920
4958222
2026-09-29T05:35:46Z
Sayonzei
40728
9908920
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁənt/}}
* {{audio|de|De-inspirierend.ogg|a=柏林}}
===分詞===
{{head|de|現在分詞}}
# {{present participle of|de|inspirieren}}
====變格====
{{de-adecl}}
bzl240t36bs0ubtxgby60czvui1psx0
inspiriert
0
810962
9908921
4958223
2026-09-29T05:35:50Z
Sayonzei
40728
9908921
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯t/}}
* {{audio|de|De-inspiriert.ogg|a=柏林}}
===分詞===
{{head|de|過去分詞}}
# {{past participle of|de|inspirieren}}
====變格====
{{de-adecl}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||3|s|pres|;|2|p|pres|;|p|imp}}
c7fmpn3u9b9pj7wibv2nemnwpxvehe7
Nachtklub
0
811323
9908824
4844089
2026-09-29T04:48:48Z
Sayonzei
40728
9908824
wikitext
text/x-wiki
==德語==
===其他形式===
* {{alter|de|Nachtclub}}
===詞源===
{{ety|de|:af|Nacht|Klub}}
{{compound|de|Nacht|Klub}}
===發音===
* {{IPA|de|/ˈnaxtˌklʊp/}}
* {{audio|de|De-Nachtklub.ogg|a=柏林}}
===名詞===
{{de-noun|m,,s}}
# [[夜總會]]
#: {{syn|de|Nachtbar}}
====變格====
{{de-ndecl|m,,s}}
===延伸閱讀===
* {{R:de:Duden}}
{{C|de|酒吧|商業實體}}
sjvat31h79yifrbgsgbu0d4roykxbmg
Nachtlokal
0
811337
9908827
4844092
2026-09-29T04:50:18Z
Sayonzei
40728
9908827
wikitext
text/x-wiki
==德語==
===詞源===
{{ety|de|:af|Nacht|Lokal}}
{{compound|de|Nacht|Lokal}}
===發音===
* {{IPA|de|/ˈnaxtloˌkaːl/}}
* {{audio|de|De-Nachtlokal.ogg|a=柏林}}
===名詞===
{{de-noun|n}}
# [[夜总会]]
#: {{syn|de|Nachtclub|Nachtbar}}
====變格====
{{de-ndecl|n}}
{{C|de|酒吧}}
eqnymlrluq2d3eiq7b2nqake53hwdvn
interpretieren
0
812007
9908998
4959336
2026-09-29T06:02:39Z
Sayonzei
40728
9908998
wikitext
text/x-wiki
==德語==
===詞源===
源自 {{af|de|lang1=fr|interpréter|-ieren}},源自{{der|de|fro|enterpreter}},源自{{der|de|la|interpretor||解释、阐述、诠释}}。
===發音===
* {{IPA|de|/ʔɪntɐpʁeˈtiːʁən/}}
* {{audio|de|De-at-interpretieren.ogg|a=Austria}}
* {{audio|de|De-interpretieren.ogg|a=柏林}}
===動詞===
{{de-verb}}
# [[解釋]],[[解讀]]
#: {{ux|de|Die Geschichte kann auf verschiedene Weise '''interpretiert''' werden.|这个故事可以有多种'''解读'''。}}
# {{lb|de|音樂}} [[表演]],[[演奏]]
# {{lb|de|編程}} [[直譯]]
====變位====
{{de-conj}}
====相關詞彙====
* {{l|de|Interpret}}
* {{l|de|interpretierbar}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:UniLeipzig}}
* {{R:de:Duden}}
* {{R:de:OpenThesaurus}}
kpyulhdehpouc214oio9f9df8ry0qn0
interpretierend
0
812010
9908999
4959337
2026-09-29T06:03:07Z
Sayonzei
40728
9908999
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁənt/}}
* {{audio|de|De-interpretierend.ogg|a=柏林}}
===分詞===
{{head|de|現在分詞}}
# {{present participle of|de|interpretieren}}
====變格====
{{de-adecl}}
0kazf8a2knayhzl6dqma2ivzvjsnmvw
interpretiert
0
812012
9909000
4959338
2026-09-29T06:03:16Z
Sayonzei
40728
9909000
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯t/}}
* {{audio|de|De-interpretiert.ogg|a=柏林}}
===分詞===
{{head|de|過去分詞}}
# {{past participle of|de|interpretieren}}
====變格====
{{de-adecl}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||3|s|pres|;|2|p|pres|;|p|imp}}
kl95isyetzdpw6ih66qgg78iyln08l3
Nationalhymne
0
812193
9908831
4844244
2026-09-29T04:56:26Z
Sayonzei
40728
9908831
wikitext
text/x-wiki
==德語==
{{wp|de:}}
===詞源===
{{af|de|national|t1=國家的|Hymne|t2=頌歌}}
===發音===
* {{IPA|de|/ˌnat͡si̯oˈnaːlˌhʏmnə/}}
* {{audio|de|De-Nationalhymne.ogg|a=柏林}}
===名詞===
{{de-noun|f}}
# [[國歌]]
====變格====
{{de-ndecl|f}}
===延伸閱讀===
* {{R:de:Duden}}
5ia7k5hpch71pvuftho62skqx42t8d8
出資
0
908507
9909196
9148070
2026-09-29T10:52:30Z
Jiba1219
110530
/* 日语 */
9909196
wikitext
text/x-wiki
==漢語==
{{zh-forms|s=出资}}
===發音===
{{zh-pron
|m=chūzī
|mn=chhut-chu
|cat=v,n
}}
===動詞===
{{zh-verb}}
# [[提供]][[資金]]
===名詞===
{{head|zh|名詞}}
# {{Rfv-sense|zh}} [[投資]]
====衍生詞====
{{col3|zh|出資額}}
{{C|zh|金融|商業}}
==日语==
{{ja-kanjitab|しゅつ|し|k1=しゅっ|yomi=kanon,on}}
===發音===
{{ja-pron|しゅっし|acc=0|acc_ref=DJR,NHK}}
===名詞===
{{ja-noun|しゅっし}}
# [[#漢語|出資]],[[投資]]
====近義詞====
* {{ja-r|投%資|とう%し}}
====衍生詞====
* {{ja-r|出%資%者|しゅっ%し%しゃ|投資者,出資人}}
===動詞===
{{ja-verb-suru|tr=intrans|しゅっし}}
# [[出錢]],[[投資]]
====活用====
{{ja-suru|しゅっし}}
===參考資料===
<references/>
{{c|ja|金融|商業|sort=しゅっし}}
==朝鮮語==
{{ko-hanjatab}}
===名詞===
{{ko-noun|hangeul=출자}}
# {{hanja form of|출자}}
c83ffjvsises8xitm8fkeutm8jt475t
結合
0
937342
9909193
8404486
2026-09-29T10:16:57Z
Jiba1219
110530
9909193
wikitext
text/x-wiki
==日語==
{{ja-kanjitab|けつ|ごう|yomi=kanon,goon}}
===發音===
{{ja-pron|けつごう}}
===名詞===
{{ja-noun|けつごう}}
# (原子的)键
# 耦合,连接
# 结合
===動詞===
{{ja-verb-suru|けつごう}}
# [[结合]], [[耦合]], [[连接]]
====活用====
{{ja-suru|けつごう}}
nz5b7p1ejq7pkjr44riw79oa9w5u61o
𦣹
0
1031288
9908793
7644078
2026-09-28T23:16:56Z
Fglffer
55252
9908793
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=132|rad=自|as=0|sn=6|four=|canj=|ids=⿶凵⿱人二}}
====參考資料====
{{Han ref|kx=1000.090|hdz=53046.020|uh=268F9}}
==漢語==
{{zh-see|自|v}}
iijzk0qspqlltu2yt6avjz056dv38le
𦤮
0
1033959
9908795
5632758
2026-09-28T23:21:34Z
Fglffer
55252
9908795
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=132|rad=自|as=14|sn=20|canj=|ids=⿰退臭}}
====來源====
* {{Han ref|kx=1001.101|uh=2692E}}
==越南語==
===字源===
{{Han compound|退|臭|lang=vi|ls=psc|tr={{vi-l|thối}}|c1=p|tr1={{vi-l|thoái}}、{{vi-l|thối}}|c2=s|t2=[[臭味]]}}。
===漢字===
{{vi-readings|rs=自14|nom=thối}}
# {{vi-Nom form of|thối|腐爛;散發惡臭}}
===來源===
* [https://nomfoundation.org/common/nom_details.php?codepoint=2692E Nom Foundation]
7jkxsattmg3q7vbuenpootffacjnqka
潲
0
1056814
9909168
9745542
2026-09-29T09:01:24Z
~2026-52239-77
135486
/* 释义 */ 语义错误, 潲雨指风把雨吹得斜着打过来,常说“潲雨了”。我是中国北方人,能确定含义错误。
9909168
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=85|rad=水|as=12|sn=15|four=39127|canj=EHDB|ids=⿰氵稍}}
====來源====
{{Han ref|kx=0650.270|dkj=18285|dj=1061.080|hdz=31743.040|sbgy=416.24|uh=6F72}}
==漢語==
===字源===
{{Han etym}}
{{Han compound|水|稍|ls=psc|c1=s|c2=p}}。
===詞源1===
{{zh-forms|alt=捎-動詞}}
====發音====
{{zh-pron
|m=shào
|c=saau3
|g=sau4
|j=sau3
|mn=sàu/xm:sáu
|mn-t=sao3
|px=pt,xy:sao4
|w=sh:5sau
|x=sau4
|x-h=sau4
|mc=y
|oc=y
|cat=n,v
}}
====釋義====
{{head|zh|漢字}}
# [[洗滌]]
# {{lb|zh|colloquial}} [[雨倾斜下]]
#: {{zh-x|潲 雨 了,咱 趕緊 關 窗戶{hu}。|'''雨斜下'''进来了,我們趕緊關上窗戶。}}
# {{lb|zh|dialectal|包括|_|北京官話|中原官話|粵語|晉語}} [[灑]];[[飛沫]]
====組詞====
{{col3|zh|潲水|豬潲}}
===詞源2===
{{zh-forms|alt=韶,精,𪷰,洨,佋,溲,屑,邵,哨,梢,稍,消,肖,紹,𩩓,㞞,嬲-Hokkien,漦-Hakka,液-Puxian Min}}
根據{{zh-ref|Yang (2013)}}的說法,與{{zh-l|鳥|gloss=陰莖}}相關。
====發音====
{{zh-pron
|h=pfs=siàu
|mn=siâu
|mn-t=siao5
|px=pt,xy:sieo2
|px_note=僅用於組詞
|cat=n,suf
}}
====釋義====
{{head|zh|漢字}}
# {{lb|zh|Hokkien|Teochew|Hakka|vulgar}} [[精液]]
# {{lb|zh|Hokkien|vulgar|postpositive}} {{n-g|粗俗語氣加強後綴。}}
#: {{zh-x|啥潲{siâu}|什麼鬼|MN}}
#: {{zh-x|嘐{hau}潲{siâu}|吹牛,說謊|MN}}
#: {{zh-x|衰潲{siâu}|真是倒楣|MN}}
#: {{zh-x|吵 潲{siâu}|吵什麼|MN}}
#: {{zh-x|空{khong}歁{khám} 潲{siâu}|發什麼瘋|MN}}
#: {{zh-x|笑 潲{siâu}|笑什麼|MN}}
#: {{zh-x|洽{ka̍p}潲{siâu}|蠢得要死|MN}}
====同義詞====
{{zh-dial|精液}}
====組詞====
{{col3|zh|nan:㤉潲<tr:gê-siâu>|nan:孽潲<tr:gia̍t-siâu>|nan:嘐潲<tr:hau-siâu>|nan:嘐潲話<tr:hau-siâu-ōe>|nan:講嘐潲話|nan:啥潲<tr:siáⁿ-siâu>|nan:訕潲<tr:soān-siâu>|nan:衰潲<tr:soe-siâu>|nan:插潲<tr:chhap-siâu>|nan:挐潲潲<tr:jû-siâu-siâu>|譀狗潲|譀潲|謔潲|貓潲|洽潲|潲面|nan:創啥潲}}
===來源===
* {{R:yue:Hanzi}}
* {{thcwd|11184}}
* {{R:cpx:PHMZ|207}}
bzd8dc9lcdhuixoollpci5pyk2y20lc
9909194
9909168
2026-09-29T10:26:09Z
Sayonzei
40728
/* 釋義 */
9909194
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=85|rad=水|as=12|sn=15|four=39127|canj=EHDB|ids=⿰氵稍}}
====來源====
{{Han ref|kx=0650.270|dkj=18285|dj=1061.080|hdz=31743.040|sbgy=416.24|uh=6F72}}
==漢語==
===字源===
{{Han etym}}
{{Han compound|水|稍|ls=psc|c1=s|c2=p}}。
===詞源1===
{{zh-forms|alt=捎-動詞}}
====發音====
{{zh-pron
|m=shào
|c=saau3
|g=sau4
|j=sau3
|mn=sàu/xm:sáu
|mn-t=sao3
|px=pt,xy:sao4
|w=sh:5sau
|x=sau4
|x-h=sau4
|mc=y
|oc=y
|cat=n,v
}}
====釋義====
{{head|zh|漢字}}
# [[洗滌]]
# {{lb|zh|colloquial}} [[雨]][[倾斜]]下
#: {{zh-x|潲 雨 了,咱 趕緊 關 窗戶{hu}。|'''雨斜下'''进来了,我們趕緊關上窗戶。}}
# {{lb|zh|dialectal|包括|_|北京官話|中原官話|粵語|晉語}} [[灑]];[[飛沫]]
====組詞====
{{col3|zh|潲水|豬潲}}
===詞源2===
{{zh-forms|alt=韶,精,𪷰,洨,佋,溲,屑,邵,哨,梢,稍,消,肖,紹,𩩓,㞞,嬲-Hokkien,漦-Hakka,液-Puxian Min}}
根據{{zh-ref|Yang (2013)}}的說法,與{{zh-l|鳥|gloss=陰莖}}相關。
====發音====
{{zh-pron
|h=pfs=siàu
|mn=siâu
|mn-t=siao5
|px=pt,xy:sieo2
|px_note=僅用於組詞
|cat=n,suf
}}
====釋義====
{{head|zh|漢字}}
# {{lb|zh|Hokkien|Teochew|Hakka|vulgar}} [[精液]]
# {{lb|zh|Hokkien|vulgar|postpositive}} {{n-g|粗俗語氣加強後綴。}}
#: {{zh-x|啥潲{siâu}|什麼鬼|MN}}
#: {{zh-x|嘐{hau}潲{siâu}|吹牛,說謊|MN}}
#: {{zh-x|衰潲{siâu}|真是倒楣|MN}}
#: {{zh-x|吵 潲{siâu}|吵什麼|MN}}
#: {{zh-x|空{khong}歁{khám} 潲{siâu}|發什麼瘋|MN}}
#: {{zh-x|笑 潲{siâu}|笑什麼|MN}}
#: {{zh-x|洽{ka̍p}潲{siâu}|蠢得要死|MN}}
====同義詞====
{{zh-dial|精液}}
====組詞====
{{col3|zh|nan:㤉潲<tr:gê-siâu>|nan:孽潲<tr:gia̍t-siâu>|nan:嘐潲<tr:hau-siâu>|nan:嘐潲話<tr:hau-siâu-ōe>|nan:講嘐潲話|nan:啥潲<tr:siáⁿ-siâu>|nan:訕潲<tr:soān-siâu>|nan:衰潲<tr:soe-siâu>|nan:插潲<tr:chhap-siâu>|nan:挐潲潲<tr:jû-siâu-siâu>|譀狗潲|譀潲|謔潲|貓潲|洽潲|潲面|nan:創啥潲}}
===來源===
* {{R:yue:Hanzi}}
* {{thcwd|11184}}
* {{R:cpx:PHMZ|207}}
86fj3xr34ydnejotqqd4j58xzssvuf9
9909197
9909194
2026-09-29T11:04:48Z
Fglffer
55252
/* 漢語 */
9909197
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=85|rad=水|as=12|sn=15|four=39127|canj=EHDB|ids=⿰氵稍}}
====來源====
{{Han ref|kx=0650.270|dkj=18285|dj=1061.080|hdz=31743.040|sbgy=416.24|uh=6F72}}
==漢語==
===字源===
{{Han etym}}
{{Han compound|水|稍|ls=psc|c1=s|c2=p}}。
===詞源1===
{{zh-forms|alt=捎-動詞}}
====發音====
{{zh-pron
|m=shào
|c=saau3
|g=sau4
|j=sau3
|mn=sàu/xm:sáu
|mn-t=sao3
|px=pt,xy:sao4
|w=sh:5sau
|x=sau4
|x-h=sau4
|mc=y
|oc=y
|cat=n,v
}}
====釋義====
{{head|zh|漢字}}
# [[洗滌]]
# {{lb|zh|colloquial}} [[雨水]][[傾斜]][[落下]]
#: {{zh-x|潲 雨 了,咱 趕緊 關 窗戶{hu}。|'''雨斜下'''进来了,我們趕緊關上窗戶。}}
# {{lb|zh|dialectal|包括|_|北京官話|中原官話|粵語|晉語}} [[灑]];[[飛沫]]
====組詞====
{{col3|zh|潲水|豬潲}}
===詞源2===
{{zh-forms|alt=韶,精,𪷰,洨,佋,溲,屑,邵,哨,梢,稍,消,肖,紹,𩩓,㞞,嬲-Hokkien,漦-Hakka,液-Puxian Min}}
根據{{zh-ref|Yang (2013)}}的說法,與{{zh-l|鳥|gloss=陰莖}}相關。
====發音====
{{zh-pron
|h=pfs=siàu
|mn=siâu
|mn-t=siao5
|px=pt,xy:sieo2
|px_note=僅用於組詞
|cat=n,suf
}}
====釋義====
{{head|zh|漢字}}
# {{lb|zh|Hokkien|Teochew|Hakka|vulgar}} [[精液]]
# {{lb|zh|Hokkien|vulgar|postpositive}} {{n-g|粗俗語氣加強後綴。}}
#: {{zh-x|啥潲{siâu}|什麼鬼|MN}}
#: {{zh-x|嘐{hau}潲{siâu}|吹牛,說謊|MN}}
#: {{zh-x|衰潲{siâu}|真是倒楣|MN}}
#: {{zh-x|吵 潲{siâu}|吵什麼|MN}}
#: {{zh-x|空{khong}歁{khám} 潲{siâu}|發什麼瘋|MN}}
#: {{zh-x|笑 潲{siâu}|笑什麼|MN}}
#: {{zh-x|洽{ka̍p}潲{siâu}|蠢得要死|MN}}
====同義詞====
{{zh-dial|精液}}
====組詞====
{{col3|zh|nan:㤉潲<tr:gê-siâu>|nan:孽潲<tr:gia̍t-siâu>|nan:嘐潲<tr:hau-siâu>|nan:嘐潲話<tr:hau-siâu-ōe>|nan:講嘐潲話|nan:啥潲<tr:siáⁿ-siâu>|nan:訕潲<tr:soān-siâu>|nan:衰潲<tr:soe-siâu>|nan:插潲<tr:chhap-siâu>|nan:挐潲潲<tr:jû-siâu-siâu>|譀狗潲|譀潲|謔潲|貓潲|洽潲|潲面|nan:創啥潲}}
===來源===
* {{R:yue:Hanzi}}
* {{thcwd|11184}}
* {{R:cpx:PHMZ|207}}
qht3cnt4g76lq6pcghtka7791wrks4a
尩
0
1092828
9909200
9664554
2026-09-29T11:17:12Z
TongcyDai
53191
9909200
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=43|rad=尢|as=04|sn=7|four=|canj=CUMG|ids=⿺尣王}}
====參考資料====
* {{Han ref|kx=0298.120|dkj=07557|dj=0593.160|hdz=10553.020|uh=5C29}}
==漢語==
{{zh-see|尪|v}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=尢04}}
# [[虛弱]]的
# [[搖晃|搖搖晃晃]]的人
# [[瘦弱]]的
====讀音====
* {{ja-readings|on=おう|kun=あしなえ, すねのまがったひと}}
eue3yyvwawalbpmxk976ur91cixkxbt
尪
0
1092840
9909199
9664555
2026-09-29T11:16:16Z
TongcyDai
53191
9909199
wikitext
text/x-wiki
{{also|尫|𡯁}}
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=43|rad=尢|as=04|sn=7|four=41011|canj=KUMG|ids=⿺尢王}}
====派生漢字====
* {{charlist|sc=Hani|𤷀}}
====參考資料====
{{Han ref|kx=0298.130|dkj=07558|dj=0593.170|hdz=10552.140|uh=5C2A|sbgy=182.23|ch=4550001.01}}
==漢語==
{{zh-forms|alt=㑌,尫,尩,翁-泉漳話}}
===字源===
{{Han etym}}
{{Han compound|尢|王|ls=psc|c1=s|c2=p|t1=}}
===發音===
{{zh-pron
|m=wāng
|c=wong1
|mn=ang
|mc=y
|oc=y
|cat=a
}}
===釋義===
{{head|zh|漢字}}
# {{lb|zh|書面}} [[跛]]
# {{lb|zh|書面}} [[虛弱]];[[消瘦]]
# {{lb|zh|閩南語}} [[木偶]];[[玩偶]]
===複合詞===
{{col3|zh|尪羸|尪仔標|尪仔<tr:ang-á>}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=尢04}}
# {{rfdef|ja|sort=尢04}}
====讀音====
{{ja-readings
|on=おう
|kun=まがる, よわい
}}
==越南語==
===漢字===
{{vi-readings|rs=尢04|reading=uông}}
# {{rfdef|vi|sort=尢04}}
====參考資料====
* [http://nomfoundation.org/common/nom_details.php?codepoint=5c2a Nom Foundation]
7bfyflrwcmgg7ppd3hggwvkau3ys92y
9909201
9909199
2026-09-29T11:18:38Z
TongcyDai
53191
9909201
wikitext
text/x-wiki
{{also|尫|𡯁}}
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=43|rad=尢|as=04|sn=7|four=41011|canj=KUMG|ids=⿺尢王}}
====派生漢字====
* {{charlist|sc=Hani|𤷀}}
====參考資料====
{{Han ref|kx=0298.130|dkj=07558|dj=0593.170|hdz=10552.140|uh=5C2A|sbgy=182.23|ch=4550001.01}}
==漢語==
{{zh-forms|alt=㑌,尫,尩,翁-泉漳話}}
===字源===
{{Han etym}}
{{Han compound|尢|王|ls=psc|c1=s|c2=p|t1=}}
===發音===
{{zh-pron
|m=wāng
|c=wong1
|mn=ang
|mc=y
|oc=y
|cat=a
}}
===釋義===
{{head|zh|漢字}}
# {{lb|zh|書面}} [[胸]]、[[脛]]、[[背]]等處骨骼[[彎曲]]的{{gl|指病症或人}}
# {{lb|zh|書面}} [[虛弱]];[[消瘦]]
# {{lb|zh|閩南語}} [[木偶]];[[玩偶]]
===複合詞===
{{col3|zh|尪羸|尪仔標|尪仔<tr:ang-á>}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=尢04}}
# {{rfdef|ja|sort=尢04}}
====讀音====
{{ja-readings
|on=おう
|kun=まがる, よわい
}}
==越南語==
===漢字===
{{vi-readings|rs=尢04|reading=uông}}
# {{rfdef|vi|sort=尢04}}
====參考資料====
* [http://nomfoundation.org/common/nom_details.php?codepoint=5c2a Nom Foundation]
lso7g2yr55tk8vyscoexs2ofb0r1t84
尫
0
1092843
9909198
9664556
2026-09-29T11:14:32Z
TongcyDai
53191
9909198
wikitext
text/x-wiki
{{also|尪}}
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=43|rad=尢|as=04|sn=7|four=|canj=MUMG|ids=⿺兀王}}
====派生漢字====
* {{charlist|sc=Hani|𣒸𥆦}}
====參考資料====
* {{Han ref|kx=0298.140|dkj=07559|dj=0593.150|hdz=10553.010|uh=5C2B}}
==漢語==
{{zh-see|尪}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=尢04}}
# {{rfdef|ja|sort=尢04}}
====讀音====
{{ja-readings|on=おう|kun=ま-がる, よわ-い}}
==朝鮮語==
===漢字===
{{ko-hanja|hangeul=왕|rv=wang|mr=wang}}
# {{rfdef|ko|sort=尢04}}
o1y9nzdvystqn21vwid7f3p8ytehx2i
屋舍
0
1102299
9908768
6932610
2026-09-28T14:05:07Z
徐元昊
122104
新詞條
9908768
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|m=wūshè
|c=uk1 se3,uk1 se5
|h=gd=vug5 sa4
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|literary}} [[房屋]];[[房舍]]
#* {{zh-x|不 治 產業,不 受 施惠,為{wéi} 屋舍 財 足 自容。||CL|ref=晉·{{w|陳壽}}《[[s:三國志/卷62#是儀|三國志·卷六十二·吳書·是儀傳]]》}}
#* {{zh-x|^益州 既 定,時 議 欲 以 ^成都 中 屋舍 及 城外 園地 桑田 分賜 諸 將{jiàng}。||CL|ref=晉·{{w|陳壽}}《[[s:三國志/卷36#趙雲|三國志·卷三十六·蜀書·趙雲傳]]》{{w|裴松之}}注引《雲別傳》}}
#* {{zh-x|地 薄 桑麻 瘦,村 貧 屋舍 低。早苗 多 間{jiàn} 草,濁水 半 和 泥。||TCP|ref=唐·{{w|白居易}}〈[[s:茅城驛|茅城驛]]〉}}
# {{lb|zh|literary|figurative|Buddhism|or|Daoism}} 人的[[軀體]];[[色身]]
#* {{zh-x|生死 無有 量,往來 無 端緒,求 於 屋舍 者,數{shuò}數{shuò} 受 胞胎。||CL|ref=後秦·{{w|竺佛念}}譯《[[s:出曜經/卷二十八|出曜經·卷二十八·心意品]]》}}
#* {{zh-x|譬如 屋舍,從 眾緣 生,無 有 定性。見 諸 眾生 四大 五陰 之 所 成立,推 無 定性,無 定性 故,菩薩 於 中 不 生 貪著{zhuó}。||CL|ref=北涼·{{w|曇無讖}}譯《大般涅槃經·卷二十二·光明遍照高貴德王菩薩品》}}
#* {{zh-x|種種 形貌,喻如 屋舍。捨 驢-屋 入 人-屋,捨 人身 至 天身,乃至 聲聞、緣覺{jué}、菩薩、佛-屋,皆 是 汝 取捨 處,所以 有 別;本源 之 性,何 得 有 別?||CL|ref=唐·{{w|黃檗希運}}《黃檗斷際禪師宛陵錄》}}
#* {{zh-x|先生 曰:「但 屋舍 已 壞 矣。」先生 即 從 舍 出,乃 呼 地界 而 問 曰:「刺史 女 因 產 為{wéi} 鬼 所 殺,屋舍 已 壞,如何?」||CL|ref=宋·陳葆光《三洞群仙錄·卷十四》引《仙傳拾遺》}}
#* {{zh-x|舍 者,屋舍 也,比 四大 五蘊 色身。利子 者,舍 中 之 本來 一 點 真靈,即 主張 形骸 者 是 也。||CL|ref=明·何道全《般若心經註解》}}
====同義詞====
{{syn-saurus|zh|住宅}}
{{zh-dial|房子}}
====衍生詞====
{{col3|zh|屋舍儼然}}
===參考資料===
* {{R:zh:HYDCD}}
adnvxrhl2x546tubc1mjhcx3y7yf30i
9908769
9908768
2026-09-28T14:53:41Z
Sayonzei
40728
/* 名詞 */
9908769
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|m=wūshè
|c=uk1 se3,uk1 se5
|h=gd=vug5 sa4
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|literary}} [[房屋]];[[房舍]]
#* {{zh-x|不 治 產業,不 受 施惠,為{wéi} 屋舍 財 足 自容。||CL|ref=晉·{{w|陳壽}}《[[s:三國志/卷62#是儀|三國志·卷六十二·吳書·是儀傳]]》}}
#* {{zh-x|^益州 既 定,時 議 欲 以 ^成都{dū} 中 屋舍 及 城外 園地 桑田 分賜 諸 將{jiàng}。||CL|ref=晉·{{w|陳壽}}《[[s:三國志/卷36#趙雲|三國志·卷三十六·蜀書·趙雲傳]]》{{w|裴松之}}注引《雲別傳》}}
#* {{zh-x|地 薄 桑麻 瘦,村 貧 屋舍 低。早苗 多 間{jiàn} 草,濁水 半 和 泥。||TCP|ref=唐·{{w|白居易}}〈[[s:茅城驛|茅城驛]]〉}}
# {{lb|zh|literary|figurative|Buddhism|or|Daoism}} 人的[[軀體]];[[色身]]
#* {{zh-x|生死 無有 量,往來 無 端緒,求 於 屋舍 者,數{shuò}數{shuò} 受 胞胎。||CL|ref=後秦·{{w|竺佛念}}譯《[[s:出曜經/卷二十八|出曜經·卷二十八·心意品]]》}}
#* {{zh-x|譬如 屋舍,從 眾緣 生,無 有 定性。見 諸 眾生 四大 五陰 之 所 成立,推 無 定性,無 定性 故,菩薩 於 中 不 生 貪著[着]{zhuó}。||CL|ref=北涼·{{w|曇無讖}}譯《大般涅槃經·卷二十二·光明遍照高貴德王菩薩品》}}
#* {{zh-x|種種 形貌,喻 如 屋舍。捨 驢-屋 入 人-屋,捨 人身 至 天身,乃至 聲聞、緣覺{jué}、菩薩、佛-屋,皆 是 汝 取捨 處,所以 有 別;本源 之 性,何 得 有 別?||CL|ref=唐·{{w|黃檗希運}}《黃檗斷際禪師宛陵錄》}}
#* {{zh-x|先生 曰:「但 屋舍 已 壞 矣。」先生 即 從 舍 出,乃 呼 地界 而 問 曰:「刺史 女 因 產 為{wéi} 鬼 所 殺,屋舍 已 壞,如何?」||CL|ref=宋·陳葆光《三洞群仙錄·卷十四》引《仙傳拾遺》}}
#* {{zh-x|舍 者,屋舍 也,比 四大 五蘊 色身。利子 者,舍 中 之 本來 一 點 真靈,即 主張 形骸 者 是 也。||CL|ref=明·何道全《般若心經註解》}}
====同義詞====
{{syn-saurus|zh|住宅}}
{{zh-dial|房子}}
====衍生詞====
{{col3|zh|屋舍儼然}}
===參考資料===
* {{R:zh:HYDCD}}
9033zsud4s3ihp58xmj8v2ljcy38wmw
9908771
9908769
2026-09-28T14:54:18Z
Sayonzei
40728
/* 參考資料 */
9908771
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|m=wūshè
|c=uk1 se3,uk1 se5
|h=gd=vug5 sa4
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|literary}} [[房屋]];[[房舍]]
#* {{zh-x|不 治 產業,不 受 施惠,為{wéi} 屋舍 財 足 自容。||CL|ref=晉·{{w|陳壽}}《[[s:三國志/卷62#是儀|三國志·卷六十二·吳書·是儀傳]]》}}
#* {{zh-x|^益州 既 定,時 議 欲 以 ^成都{dū} 中 屋舍 及 城外 園地 桑田 分賜 諸 將{jiàng}。||CL|ref=晉·{{w|陳壽}}《[[s:三國志/卷36#趙雲|三國志·卷三十六·蜀書·趙雲傳]]》{{w|裴松之}}注引《雲別傳》}}
#* {{zh-x|地 薄 桑麻 瘦,村 貧 屋舍 低。早苗 多 間{jiàn} 草,濁水 半 和 泥。||TCP|ref=唐·{{w|白居易}}〈[[s:茅城驛|茅城驛]]〉}}
# {{lb|zh|literary|figurative|Buddhism|or|Daoism}} 人的[[軀體]];[[色身]]
#* {{zh-x|生死 無有 量,往來 無 端緒,求 於 屋舍 者,數{shuò}數{shuò} 受 胞胎。||CL|ref=後秦·{{w|竺佛念}}譯《[[s:出曜經/卷二十八|出曜經·卷二十八·心意品]]》}}
#* {{zh-x|譬如 屋舍,從 眾緣 生,無 有 定性。見 諸 眾生 四大 五陰 之 所 成立,推 無 定性,無 定性 故,菩薩 於 中 不 生 貪著[着]{zhuó}。||CL|ref=北涼·{{w|曇無讖}}譯《大般涅槃經·卷二十二·光明遍照高貴德王菩薩品》}}
#* {{zh-x|種種 形貌,喻 如 屋舍。捨 驢-屋 入 人-屋,捨 人身 至 天身,乃至 聲聞、緣覺{jué}、菩薩、佛-屋,皆 是 汝 取捨 處,所以 有 別;本源 之 性,何 得 有 別?||CL|ref=唐·{{w|黃檗希運}}《黃檗斷際禪師宛陵錄》}}
#* {{zh-x|先生 曰:「但 屋舍 已 壞 矣。」先生 即 從 舍 出,乃 呼 地界 而 問 曰:「刺史 女 因 產 為{wéi} 鬼 所 殺,屋舍 已 壞,如何?」||CL|ref=宋·陳葆光《三洞群仙錄·卷十四》引《仙傳拾遺》}}
#* {{zh-x|舍 者,屋舍 也,比 四大 五蘊 色身。利子 者,舍 中 之 本來 一 點 真靈,即 主張 形骸 者 是 也。||CL|ref=明·何道全《般若心經註解》}}
====同義詞====
{{syn-saurus|zh|住宅}}
{{zh-dial|房子}}
====衍生詞====
{{col3|zh|屋舍儼然}}
===參考資料===
* {{R:zh:HYDCD}}
==日語==
{{ja-kanjitab|おく|しゃ|yomi=o}}
===名詞===
{{ja-noun|おくしゃ|hhira=をくしや}}
# {{ja-kyu sp|屋舎}}
==朝鮮語==
{{ko-hanjatab}}
===名詞===
{{ko-noun|hangeul=옥사}}
# {{hanja form of|옥사}}
dl27crnub3ne1eyk6btmqr6lza5qtm1
탯줄
0
1130545
9909117
8350230
2026-09-29T07:37:11Z
Sayonzei
40728
9909117
wikitext
text/x-wiki
==朝鲜语==
===词源===
{{af|ko|태(胎)|-ㅅ-|줄|t3=绳}}
===发音===
{{ko-IPA|nobc=1}}
===名词===
{{ko-noun}}
# [[脐带]]
{{c|ko|解剖學}}
pvd2q5d35hjts2aa4vdoynsvpyulqdd
蛞
0
1157543
9908785
9667169
2026-09-28T22:36:05Z
Fglffer
55252
9908785
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=142|rad=虫|as=06|sn=12|four=52164|canj=LIHJR|ids=⿰虫舌}}
====來源====
* {{Han ref|kx=1081.360|dkj=33008|dj=1550.050|hdz=42849.130|sbgy=486.13|ch=11890002.04|uh=86DE}}
==漢語==
{{zh-forms}}
===字源===
{{Han etym}}
{{Han compound|虫|𠯑|ls=psc|c1=s|c2=p}}。
===發音===
{{zh-pron
|m=kuò
|c=kut3,fut3
|c-t=fot1
|h=gd=guad5
|mn=khoat
|mn-t=guêg4/guag4
|mn-t_note=guêg4 - 潮州; guag4 - 汕頭、揭陽、澄海、朝陽
|mc=y
|oc=y
|cat=
}}
===釋義===
{{head|zh|漢字}}
# {{zh-used2|c}}
===組詞===
{{col3|zh|蛞蝓|蛞螻}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=虫06}}
# {{rfdef|ja|sort=虫06}}
====讀法====
{{ja-readings|goon=かち|kanon=かつ}}
====組詞====
{{col|ja
|{{ja-r|蛞蝓|なめくじ}}
}}
==朝鮮語==
===漢字===
{{ko-hanja|hangeul=활|eumhun=|rv=hwal|mr=hwal|y=hwal}}
# {{rfdef|ko|sort=虫06}}
====組詞====
* {{ko-l|활유|蛞蝓}}
9tdjmxqpwxr6ykg9oma3ktokk3r3byu
蝓
0
1157698
9908786
9667176
2026-09-28T22:39:00Z
Fglffer
55252
9908786
wikitext
text/x-wiki
{{character info}}
==Translingual==
===Han character===
{{Han char|rn=142|rad=虫|as=09|sn=15|four=58121|canj=LIOMN|ids=⿰虫俞}}
====來源====
* {{Han ref|kx=1089.070|dkj=33261|dj=1555.180|hdz=42872.060|ch=11930004.06|sbgy=76.35|uh=8753}}
==漢語==
{{zh-forms}}
===字源===
{{Han etym}}
===發音===
{{zh-pron
|m=yú
|c=jyu4
|mc=y
|oc=y
|cat=n
}}
===釋義===
{{head|zh|漢字}}
# [[蝸牛]]
====組詞====
{{col3|zh|蛞蝓|蠋蝓|螔蝓|𧋣蝓|虒蝓}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=虫09}}
# {{rfdef|ja|sort=虫09}}
====讀法====
{{ja-readings|goon=ゆ|kanon=ゆ|kun=でんでんむし, かたつむり}}
====組詞====
{{col|ja
|{{ja-r|蛞蝓|なめくじ}}
}}
==朝鮮語==
===漢字===
{{ko-hanja|hangeul=유|eumhun=|rv=yu|mr=yu|y=yu}}
# {{rfdef|ko|sort=虫09}}
====組詞====
* {{ko-l|활유|蛞蝓}}
7g3yra61p2otkaxbxgqwv4v68acecqr
9908787
9908786
2026-09-28T22:39:16Z
Fglffer
55252
9908787
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=142|rad=虫|as=09|sn=15|four=58121|canj=LIOMN|ids=⿰虫俞}}
====來源====
* {{Han ref|kx=1089.070|dkj=33261|dj=1555.180|hdz=42872.060|ch=11930004.06|sbgy=76.35|uh=8753}}
==漢語==
{{zh-forms}}
===字源===
{{Han etym}}
===發音===
{{zh-pron
|m=yú
|c=jyu4
|mc=y
|oc=y
|cat=n
}}
===釋義===
{{head|zh|漢字}}
# [[蝸牛]]
====組詞====
{{col3|zh|蛞蝓|蠋蝓|螔蝓|𧋣蝓|虒蝓}}
==日語==
===漢字===
{{ja-kanji|grade=|rs=虫09}}
# {{rfdef|ja|sort=虫09}}
====讀法====
{{ja-readings|goon=ゆ|kanon=ゆ|kun=でんでんむし, かたつむり}}
====組詞====
{{col|ja
|{{ja-r|蛞蝓|なめくじ}}
}}
==朝鮮語==
===漢字===
{{ko-hanja|hangeul=유|eumhun=|rv=yu|mr=yu|y=yu}}
# {{rfdef|ko|sort=虫09}}
====組詞====
* {{ko-l|활유|蛞蝓}}
s1vq6fa7ji20b8c8v3wp2ywkthh4ajz
𠠦
0
1175903
9908792
5645963
2026-09-28T23:09:06Z
Fglffer
55252
9908792
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=18|rad=刀|as=18|sn=20|canj=MWLNW|four=11666|ids=⿰副畐}}
===來源===
* {{Han ref|kx=145.401|dkj=|dj=|hdz=10363.020|uh=20826}}
==漢語==
{{zh-see|疈|hv}}
===來源===
* {{R:twedu|A00365-001}}
08m5c3kwrblutaid14ldpiwcakum704
𢌿
0
1188094
9908789
5665655
2026-09-28T23:00:26Z
Fglffer
55252
9908789
wikitext
text/x-wiki
{{also|畀|畁|𢍁|𢍉}}
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=55|rad=廾|as=5|sn=8|four=|canj=|ids=⿱田廾}}
====衍生字====
* {{charlist|sc=Jpan|鼻}}
{{col-top|1|額外衍生字}}
* {{charlist|sc=Hani|𠉶𡌹𢮅𨡟𰼺𩫜𬼮𢍞𪰀𫑬}}
{{col-bottom}}
====來源====
{{Han ref|kx=0354.071|hdz=10516.060|uh=2233F}}
==漢語==
===詞源1===
{{zh-see|畀|v}}
===詞源2===
{{zh-see|卑|v}}
===詞源1===
{{zh-see|𢍁|v}}
mi84vtsbbzonkyrt09ctdnv2yoxth4z
𥵩
0
1210830
9909128
5753414
2026-09-29T08:26:07Z
Fglffer
55252
9909128
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=118|rad=竹|as=14|sn=20|four=|canj=|ids=⿱𥫗複}}
====來源====
* {{Han ref|kx=0903.010|dkj=|dj=|hdz=53027.010|uh=25D69}}
==漢語==
{{zh-see|箙}}
1526ogogf8lx7s6q573adwcktkqk6qk
𦙹
0
1214310
9909129
6985758
2026-09-29T08:30:12Z
Fglffer
55252
9909129
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=130|rad=肉|as=5|sn=9|four=|canj=|ids=⿰月⿸尸乂}}
====來源====
* {{Han ref|kx=0980.110|dkj=|dj=|hdz=32064.040|uh=26679}}
==漢語==
{{zh-see|𦙀}}
j4hra8zk2zym1z5vy1qdumlqi94nkhg
𦤇
0
1215181
9908794
6986096
2026-09-28T23:19:50Z
Fglffer
55252
9908794
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=132|rad=自|as=04|sn=10|four=21647,22647|canj=HUHE|ids=⿰自反}}
====來源====
* {{Han ref|kx=1000.201|hdz=53047.060|uh=26907}}
==漢語==
===字源===
{{Han compound|自|反|ls=ic|t2=返回|nocap=y}}。首次出現於[[南北朝]]時期。後來訛化為{{zh-l|*皈}} ― {{Han compound|白|反}}。
===釋義===
{{zh-see|歸|v}}
oj5v80ozn437bclyxtkuf6wumog56v8
𧡹
0
1221431
9908764
6988413
2026-09-28T14:02:28Z
TongcyDai
53191
9908764
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=147|rad=見|as=10|sn=17|four=|canj=|ids=⿰𠧴見}}
====參考資料====
* {{Han ref|kx=1136.300|dkj=|dj=|hdz=63672.100|uh=27879|sbgy=205.19}}
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|m=yóu
}}
===釋義===
{{head|zh|漢字}}
# [[眼神]][[低]][[視]]
rxsdoyapeo7m3mxzn9wni91sgix31ir
9908765
9908764
2026-09-28T14:02:55Z
TongcyDai
53191
9908765
wikitext
text/x-wiki
{{character info}}
==跨語言==
===漢字===
{{Han char|rn=147|rad=見|as=10|sn=17|four=|canj=|ids=⿰𠧴見}}
====參考資料====
* {{Han ref|kx=1136.300|dkj=|dj=|hdz=63672.100|uh=27879|sbgy=205.19}}
==漢語==
{{zh-forms|alt=䚃}}
===發音===
{{zh-pron
|m=yóu
}}
===釋義===
{{head|zh|漢字}}
# [[眼神]][[低]][[視]]
dr0oyq3v9ug29vtmsbai4pszsjqthkz
입덧
0
1350733
9909116
6759668
2026-09-29T07:33:33Z
Sayonzei
40728
9909116
wikitext
text/x-wiki
==朝鮮語==
{{wp|ko:}}
===詞源===
可能源自 {{com|ko|입|t1=嘴|덧-|t2=重复}}。
===發音===
{{ko-IPA}}
===名詞===
{{ko-noun}}
# [[孕吐]]
#: {{ux|ko|아내는 유난히 '''입덧'''이 심해서 음식을 거의 먹지 못했다.|我妻子'''孕吐'''异常严重,几乎吃不下东西。}}
# {{lb|ko|比喻}} 极其[[厌恶]]
====派生詞彙====
* {{ko-l|입덧하다}}
{{C|ko|醫學|懷孕}}
982qh5mcqd6254fi2vmkh93zxzryoua
Module:Etymology
828
1363183
9909034
9881993
2026-09-29T07:23:50Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909034
Scribunto
text/plain
local export = {}
-- For testing
local force_cat = false
local debug_track_module = "Module:debug/track"
local languages_module = "Module:languages"
local links_module = "Module:links"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local concat = table.concat
local insert = table.insert
local new_title = mw.title.new
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function full_link(...)
full_link = require(links_module).full_link
return full_link(...)
end
local function get_language_data_module_name(...)
get_language_data_module_name = require(languages_module).getDataModuleName
return get_language_data_module_name(...)
end
local function get_link_page(...)
get_link_page = require(links_module).get_link_page
return get_link_page(...)
end
local function language_link(...)
language_link = require(links_module).language_link
return language_link(...)
end
local function serial_comma_join(...)
serial_comma_join = require(table_module).serialCommaJoin
return serial_comma_join(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function track(page, code)
local tracking_page = "etymology/" .. page
debug_track(tracking_page)
if code then
debug_track(tracking_page .. "/" .. code)
end
end
local function join_segs(segs, conj)
if not segs[2] then
return segs[1]
end
-- l10n: 模板/#invoke 端可能沿用 enwikt 的 conj=and/or,先映射成中文連詞
if conj == "and" then
conj = "和"
elseif conj == "or" then
conj = "或"
end
if conj == "和" or conj == "或" then
return serial_comma_join(segs, {conj = conj})
end
local sep
if conj == "," or conj == ";" then
sep = conj .. " "
elseif conj == "/" then
sep = "/"
elseif conj == "~" then
sep = " ~ "
elseif conj then
error(("Internal error: Unrecognized conjunction \"%s\""):format(conj))
else
error(("Internal error: No value supplied for conjunction"):format(conj))
end
return concat(segs, sep)
end
-- Returns true if `lang` is the same as `source`, or a variety of it.
local function lang_is_source(lang, source)
return lang:getCode() == source:getCode() or lang:hasParent(source)
end
--[==[
Format one or more links as specified in `termobjs`, a list of term objects of the format accepted by `full_link()` in
[[Module:links]], including decorations (qualifiers, labels and references). `conj` is used to join multiple terms
and must be specified if there is more than one term. `template_name` is the template name used in debug tracking and
must be specified. Optional `sourcetext` is text to prepend to the concatenated terms, separated by a space if the
concatenated terms are non-empty (which is always the case unless there is a single term with the value "-"). If
`decorations_on_outside` is given, any decorations specified in the first term go on the outside of (i.e before)
`sourcetext`; otherwise they will end up on the inside.
]==]
function export.format_links(termobjs, conj, template_name, sourcetext, decorations_on_outside)
if not template_name then
error("Internal error: Must specify `template_name` to format_links()")
end
for i, termobj in ipairs(termobjs) do
if termobj.lang:hasType("family") or termobj.lang:getFamilyCode() == "qfa-sub" then
if termobj.term and termobj.term ~= "-" then
debug_track(template_name .. "/family-with-term")
end
termobj.term = "-"
end
if termobj.term == "-" then
--[=[
[[Special:WhatLinksHere/Wiktionary:Tracking/cognate/no-term]]
[[Special:WhatLinksHere/Wiktionary:Tracking/derived/no-term]]
[[Special:WhatLinksHere/Wiktionary:Tracking/borrowed/no-term]]
[[Special:WhatLinksHere/Wiktionary:Tracking/calque/no-term]]
]=]
debug_track(template_name .. "/no-term")
termobjs[i] = i == 1 and sourcetext or ""
else
if i == 1 and decorations_on_outside and sourcetext then
termobj.pretext = sourcetext .. " "
sourcetext = nil
end
-- l10n: zhwikt 的 [[Module:parameter utilities]] 不會自動設定 show_decorations,需在此設定
termobj.show_decorations = true
termobjs[i] = (i == 1 and sourcetext and sourcetext .. " " or "") ..
full_link(termobj, "term")
end
end
return join_segs(termobjs, conj)
end
function export.get_display_and_cat_name(source, raw)
local display, cat_name
if source:getCode() == "und" then
display = "未知語言"
cat_name = "其他語言"
elseif source:getCode() == "mul" then
display = raw and "跨語言" or "[[w:跨語言|跨語言]]"
cat_name = "跨語言"
elseif source:getCode() == "mul-tax" then
display = raw and "分類學名稱" or "[[w:二名法|分類學名稱]]"
cat_name = "分類學名稱"
else
display = raw and source:getCanonicalName() or source:makeWikipediaLink()
cat_name = source:getDisplayForm()
end
return display, cat_name
end
function export.insert_source_cat_get_display(data)
local categories, lang, source = data.categories, data.lang, data.source
local display, cat_name = export.get_display_and_cat_name(source, data.raw)
if lang and not data.nocat then
-- Add the category, but only if there is a current language
if not categories then
categories = {}
end
local langname = lang:getFullName()
-- L10N:分類邏輯略有不同
if lang_is_source(lang, source) then
insert(categories, langname .. "回借詞")
else
local action = data.borrowing_type and "源自" or "派生自"
local suffix = (data.borrowing_type or "") .. "詞"
insert(categories, action .. cat_name .. "的" .. langname .. suffix)
end
end
return display, categories
end
function export.format_source(data)
local lang, sort_key = data.lang, data.sort_key
-- [[Special:WhatLinksHere/Wiktionary:Tracking/etymology/sortkey]]
if sort_key then
track("sortkey")
end
local display, categories = export.insert_source_cat_get_display(data)
if lang and not data.nocat then
-- Format categories, but only if there is a current language; {{cog}} currently gets no categories
categories = format_categories(categories, lang, sort_key, nil, data.force_cat or force_cat)
else
categories = ""
end
return "<span class=\"etyl\">" .. display .. categories .. "</span>"
end
--[==[
Format sources for etymology templates such as {{tl|bor}}, {{tl|der}}, {{tl|inh}} and {{tl|cog}}. There may potentially
be more than one source language (except currently {{tl|inh}}, which doesn't support it because it doesn't really
make sense). In that case, all but the last source language is linked to the first term, but only if there is such a
term and this linking makes sense, i.e. either (1) the term page exists after stripping diacritics according to the
source language in question, or (2) the result of stripping diacritics according to the source language in question
results in a different page from the same process applied with the last source language. For example, {{m|ru|соля́нка}}
will link to [[солянка]] but {{m|en|соля́нка}} will link to [[соля́нка]] with an accent, and since they are different
pages, the use of English as a non-final source with term 'соля́нка' will link to [[соля́нка]] even though it doesn't
exist, on the assumption that it is merely a redlink that might exist. If none of the above criteria apply, a non-final
source language will be linked to the Wikipedia entry for the language, just as final source languages always are.
`data` contains the following fields:
* `lang`: The destination language object into which the terms were borrowed, inherited or otherwise derived. Used for
categorization and can be nil, as with {{tl|cog}}.
* `sources`: List of source objects. Most commonly there is only one. If there are multiple, the non-final ones are
handled specially; see above.
* `terms`: List of term objects. Most commonly there is only one. If there are multiple source objects as well as
multiple term objects, the non-final source objects link to the first term object.
* `sort_key`: Sort key for categories. Usually nil.
* `categories`: Categories to add to the page. Additional categories may be added to `categories` based on the source
languages ('''in which case `categories` is destructively modified'''). If `lang` is nil, no categories will be
added.
* `nocat`: Don't add any categories to the page.
* `sourceconj`: Conjunction used to separate multiple source languages. Defaults to {"and"}. Currently recognized
values are `and`, `or`, `,`, `;`, `/` and `~`.
* `borrowing_type`: Borrowing type used in categories, such as {"learned borrowings"}. Defaults to {"terms derived"}.
* `force_cat`: Force category generation on non-mainspace pages.
]==]
function export.format_sources(data)
local lang, sources, terms, borrowing_type, sort_key, categories, nocat =
data.lang, data.sources, data.terms, data.borrowing_type, data.sort_key, data.categories, data.nocat
local term1, sources_n, source_segs = terms[1], #sources, {}
local final_link_page
local term1_term, term1_sc = term1.term, term1.sc
if sources_n > 1 and term1_term and term1_term ~= "-" then
final_link_page = get_link_page(term1_term, sources[sources_n], term1_sc)
end
for i, source in ipairs(sources) do
local seg, display_term
if i < sources_n and term1_term and term1_term ~= "-" then
local link_page = get_link_page(term1_term, source, term1_sc)
display_term = (link_page ~= final_link_page) or (link_page and not not new_title(link_page):getContent())
end
-- TODO: if the display forms or transliterations are different, display the terms separately.
if display_term then
local display, this_cats = export.insert_source_cat_get_display{
lang = lang,
source = source,
borrowing_type = borrowing_type,
raw = true,
categories = categories,
nocat = nocat,
}
seg = language_link {
lang = source,
term = term1_term,
alt = display,
tr = "-",
}
if lang and not nocat then
-- Format categories, but only if there is a current language; {{cog}} currently gets no categories
this_cats = format_categories(this_cats, lang, sort_key, nil, data.force_cat or force_cat)
else
this_cats = ""
end
seg = "<span class=\"etyl\">" .. seg .. this_cats .. "</span>"
else
seg = export.format_source{
lang = lang,
source = source,
borrowing_type = borrowing_type,
sort_key = sort_key,
categories = categories,
nocat = nocat,
}
end
insert(source_segs, seg)
end
return join_segs(source_segs, data.sourceconj or "和")
end
-- Internal implementation of {{cognate}}/{{cog}} template.
function export.format_cognate(data)
return export.format_derived {
sources = data.sources,
terms = data.terms,
sort_key = data.sort_key,
sourceconj = data.sourceconj,
conj = data.conj,
template_name = "cognate",
force_cat = data.force_cat,
}
end
--[==[
Internal implementation of {{derived}}/{{der}} template. This is called externally from [[Module:affix]],
[[Module:affixusex]] and [[Module:see]] and needs to support decorations (qualifiers, labels and references) on the
outside of the sources for use by those modules.
`data` contains the following fields:
* `lang`: The destination language object into which the terms were derived. Used for categorization and can be nil, as
with {{tl|cog}}; in this case, no categories are added.
* `sources`: List of source objects. Most commonly there is only one. If there are multiple, the non-final ones are
handled specially; see `format_sources()`.
* `terms`: List of term objects. Most commonly there is only one. If there are multiple source objects as well as
multiple term objects, the non-final source objects link to the first term object.
* `conj`: Conjunction used to separate multiple terms. '''Required'''. Currently recognized values are `and`, `or`, `,`,
`;`, `/` and `~`.
* `sourceconj`: Conjunction used to separate multiple source languages. Defaults to {"and"}. Currently recognized
values are as for `conj` above.
* `decorations_on_outside`: If specified, any decorations (qualifiers, labels or references) in the first term in
`terms` will be displayed on the outside of (before) the source language(s) in `sources`. Normally this should be
specified if there is only one term possible in `terms`.
* `template_name`: Name of the template invoking this function. Must be specified. Only used for tracking pages.
* `sort_key`: Sort key for categories. Usually nil.
* `categories`: Categories to add to the page. Additional categories may be added to `categories` based on the source
languages ('''in which case `categories` is destructively modified'''). If `lang` is nil, no categories will be
added.
* `nocat`: Don't add any categories to the page.
* `borrowing_type`: Borrowing type used in categories, such as {"learned borrowings"}. Defaults to {"terms derived"}.
* `force_cat`: Force category generation on non-mainspace pages.
]==]
function export.format_derived(data)
local terms = data.terms
local sourcetext = export.format_sources(data)
return export.format_links(terms, data.conj, data.template_name, sourcetext, data.decorations_on_outside)
end
function export.insert_borrowed_cat(categories, lang, source)
if lang_is_source(lang, source) then
return
end
-- If both are the same, we want e.g. [[:Category:English terms borrowed back into English]] not
-- [[:Category:English terms borrowed from English]]; the former is inserted automatically by format_source().
-- The second parameter here doesn't matter as it only affects `display`, which we don't use.
insert(categories, "源自" .. select(2, export.get_display_and_cat_name(source, "raw")) .. "的" .. lang:getFullName() .. "借詞")
end
-- Internal implementation of {{borrowed}}/{{bor}} template.
function export.format_borrowed(data)
local categories = {}
if not data.nocat then
local lang = data.lang
for _, source in ipairs(data.sources) do
export.insert_borrowed_cat(categories, lang, source)
end
end
data = shallow_copy(data)
data.categories = categories
return export.format_links(data.terms, data.conj, "borrowed", export.format_sources(data))
end
do
-- Generate the non-ancestor error message.
local function show_language(lang)
local retval = ("%s (%s)"):format(lang:makeCategoryLink(), lang:getCode())
if lang:hasType("etymology-only") then
retval = retval .. ("(僅用於詞源的語言,其常規親代語言是%s)"):format(
show_language(lang:getParent()))
end
return retval
end
-- Check that `lang` has `otherlang` (which may be an etymology-only language) as an ancestor. Throw an error if
-- not. When `lang` is a family, verifies that `otherlang` is a language in that family.
function export.check_ancestor(lang, otherlang)
-- When `lang` is a family, verify `otherlang` is in that family or in its parent family.
if lang.hasType and lang:hasType("family") then
local family_code = lang:getCode()
local function in_family_code(fcode, other)
if not fcode or fcode == "" then return false end
if other.inFamily and other:inFamily(fcode) then return true end
if other.getFamilyCode and other:getFamilyCode() == fcode then return true end
return false
end
local in_family = in_family_code(family_code, otherlang)
if not in_family then
local parent_code
if lang.getParent then
local parent_family = lang:getParent()
if parent_family and parent_family.getCode then
parent_code = parent_family:getCode()
end
end
if not parent_code and family_code:find("-", 1, true) then
parent_code = family_code:match("^(.+)-[^-]+$")
end
if parent_code then
in_family = in_family_code(parent_code, otherlang)
end
end
if not in_family then
local other_display = (otherlang.getCanonicalName and otherlang:getCanonicalName()) or (otherlang.getCode and otherlang:getCode()) or tostring(otherlang)
local fam_display = (lang.getCanonicalName and lang:getCanonicalName()) or family_code
error(("%s is not in family %s; inherited ancestor under a family must be a language in that family or its parent family.")
:format(other_display, fam_display))
end
return
end
-- FIXME: I don't know if this function works correctly with etym-only languages in `lang`. I have fixed up
-- the module link code appropriately (June 2024) but the remaining logic is untouched.
if lang:hasAncestor(otherlang) then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/etymology/variety]]
-- Track inheritance from varieties of Latin that shouldn't have any descendants (everything except Old Latin, Classical Latin and Vulgar Latin).
if otherlang:getFullCode() == "la" then
otherlang = otherlang:getCode()
if not (otherlang == "itc-ola" or otherlang == "la-cla" or otherlang == "la-vul") then
track("bad ancestor", otherlang)
end
end
return
end
local ancestors, postscript = lang:getAncestors()
local etym_module_link = lang:hasType("etymology-only") and "[[Module:etymology languages/data]] or " or ""
local module_link = "[[" .. get_language_data_module_name(lang:getFullCode()) .. "]]"
if not ancestors[1] then
postscript = show_language(lang) .. "沒有祖語。"
else
local ancestor_list = {}
for _, ancestor in ipairs(ancestors) do
insert(ancestor_list, show_language(ancestor))
end
postscript = ("%s的祖語為%s。"):format(
lang:getCanonicalName(),
concat(ancestor_list, "和"))
end
error(("%s is not set as an ancestor of %s in %s%s. %s")
:format(show_language(otherlang), show_language(lang), etym_module_link, module_link, postscript))
end
end
-- Internal implementation of {{inherited}}/{{inh}} template.
function export.format_inherited(data)
local lang, terms, nocat = data.lang, data.terms, data.nocat
local source = terms[1].lang
local categories = {}
if not nocat then
insert(categories, "源自" .. source:getCanonicalName() .. "的" .. lang:getFullName() .. "繼承詞")
end
export.check_ancestor(lang, source)
data = shallow_copy(data)
data.categories = categories
data.source = source
return export.format_links(terms, data.conj, "inherited", export.format_source(data))
end
-- Internal implementation of "misc variant" templates such as {{abbrev}}, {{clipping}}, {{reduplication}} and the like.
function export.format_misc_variant(data)
local lang, notext, terms, cats, parts = data.lang, data.notext, data.terms, data.cats, {}
if terms[1] then
local termparts = {}
-- Make links out of all the parts.
for _, termobj in ipairs(terms) do
local result
if termobj.lang then
result = export.format_derived {
lang = lang,
terms = {termobj},
sources = termobj.termlangs or {termobj.lang},
template_name = "misc_variant",
decorations_on_outside = true,
force_cat = data.force_cat,
nocat = true, -- L10N:修正多餘的回借詞分類
}
else
termobj.lang = lang
result = export.format_links({termobj}, nil, "misc_variant")
end
table.insert(termparts, result)
end
-- l10n: 注意語序!應為 linktext (來源詞) + oftext (的) + text (說明文字)
local linktext = join_segs(termparts, data.conj)
insert(parts, linktext)
if not notext and linktext ~= "" then
-- FIXME: If term is given as '-', we should consider displaying just "Clipping" not "Clipping of".
insert(parts, " " .. (data.oftext or "的"))
end
end
if not notext then
insert(parts, data.text)
end
local categories = {}
if not data.nocat and cats then
for _, cat in ipairs(cats) do
insert(categories, lang:getFullName() .. cat)
end
end
if categories[1] then
insert(parts, format_categories(categories, lang, data.sort_key, nil, data.force_cat or force_cat))
end
-- l10n: fix spaces
local result = concat(parts)
if result:sub(1, 1) == " " then
result = result:sub(2)
end
return result
end
-- Implementation of miscellaneous templates such as {{unknown}} and {{onomatopoeia}} that have no associated terms.
function export.format_misc_variant_no_term(data)
local parts = {}
if not data.notext then
insert(parts, data.title)
end
if not data.nocat and data.cat then
local lang, categories = data.lang, {}
insert(categories, data.cat:find("的") and data.cat:gsub("的", "的" .. lang:getFullName()) or lang:getFullName() .. data.cat) -- L10N
insert(parts, format_categories(categories, lang, data.sort_key, nil, data.force_cat or force_cat))
end
return concat(parts)
end
return export
6kkwnrck6ilkjglurjwobfy9uu9bj1x
9909181
9909034
2026-09-29T09:47:27Z
TongcyBot
83009
移除手動 show_decorations
9909181
Scribunto
text/plain
local export = {}
-- For testing
local force_cat = false
local debug_track_module = "Module:debug/track"
local languages_module = "Module:languages"
local links_module = "Module:links"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local concat = table.concat
local insert = table.insert
local new_title = mw.title.new
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function full_link(...)
full_link = require(links_module).full_link
return full_link(...)
end
local function get_language_data_module_name(...)
get_language_data_module_name = require(languages_module).getDataModuleName
return get_language_data_module_name(...)
end
local function get_link_page(...)
get_link_page = require(links_module).get_link_page
return get_link_page(...)
end
local function language_link(...)
language_link = require(links_module).language_link
return language_link(...)
end
local function serial_comma_join(...)
serial_comma_join = require(table_module).serialCommaJoin
return serial_comma_join(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function track(page, code)
local tracking_page = "etymology/" .. page
debug_track(tracking_page)
if code then
debug_track(tracking_page .. "/" .. code)
end
end
local function join_segs(segs, conj)
if not segs[2] then
return segs[1]
end
-- l10n: 模板/#invoke 端可能沿用 enwikt 的 conj=and/or,先映射成中文連詞
if conj == "and" then
conj = "和"
elseif conj == "or" then
conj = "或"
end
if conj == "和" or conj == "或" then
return serial_comma_join(segs, {conj = conj})
end
local sep
if conj == "," or conj == ";" then
sep = conj .. " "
elseif conj == "/" then
sep = "/"
elseif conj == "~" then
sep = " ~ "
elseif conj then
error(("Internal error: Unrecognized conjunction \"%s\""):format(conj))
else
error(("Internal error: No value supplied for conjunction"):format(conj))
end
return concat(segs, sep)
end
-- Returns true if `lang` is the same as `source`, or a variety of it.
local function lang_is_source(lang, source)
return lang:getCode() == source:getCode() or lang:hasParent(source)
end
--[==[
Format one or more links as specified in `termobjs`, a list of term objects of the format accepted by `full_link()` in
[[Module:links]], including decorations (qualifiers, labels and references). `conj` is used to join multiple terms
and must be specified if there is more than one term. `template_name` is the template name used in debug tracking and
must be specified. Optional `sourcetext` is text to prepend to the concatenated terms, separated by a space if the
concatenated terms are non-empty (which is always the case unless there is a single term with the value "-"). If
`decorations_on_outside` is given, any decorations specified in the first term go on the outside of (i.e before)
`sourcetext`; otherwise they will end up on the inside.
]==]
function export.format_links(termobjs, conj, template_name, sourcetext, decorations_on_outside)
if not template_name then
error("Internal error: Must specify `template_name` to format_links()")
end
for i, termobj in ipairs(termobjs) do
if termobj.lang:hasType("family") or termobj.lang:getFamilyCode() == "qfa-sub" then
if termobj.term and termobj.term ~= "-" then
debug_track(template_name .. "/family-with-term")
end
termobj.term = "-"
end
if termobj.term == "-" then
--[=[
[[Special:WhatLinksHere/Wiktionary:Tracking/cognate/no-term]]
[[Special:WhatLinksHere/Wiktionary:Tracking/derived/no-term]]
[[Special:WhatLinksHere/Wiktionary:Tracking/borrowed/no-term]]
[[Special:WhatLinksHere/Wiktionary:Tracking/calque/no-term]]
]=]
debug_track(template_name .. "/no-term")
termobjs[i] = i == 1 and sourcetext or ""
else
if i == 1 and decorations_on_outside and sourcetext then
termobj.pretext = sourcetext .. " "
sourcetext = nil
end
termobjs[i] = (i == 1 and sourcetext and sourcetext .. " " or "") ..
full_link(termobj, "term")
end
end
return join_segs(termobjs, conj)
end
function export.get_display_and_cat_name(source, raw)
local display, cat_name
if source:getCode() == "und" then
display = "未知語言"
cat_name = "其他語言"
elseif source:getCode() == "mul" then
display = raw and "跨語言" or "[[w:跨語言|跨語言]]"
cat_name = "跨語言"
elseif source:getCode() == "mul-tax" then
display = raw and "分類學名稱" or "[[w:二名法|分類學名稱]]"
cat_name = "分類學名稱"
else
display = raw and source:getCanonicalName() or source:makeWikipediaLink()
cat_name = source:getDisplayForm()
end
return display, cat_name
end
function export.insert_source_cat_get_display(data)
local categories, lang, source = data.categories, data.lang, data.source
local display, cat_name = export.get_display_and_cat_name(source, data.raw)
if lang and not data.nocat then
-- Add the category, but only if there is a current language
if not categories then
categories = {}
end
local langname = lang:getFullName()
-- L10N:分類邏輯略有不同
if lang_is_source(lang, source) then
insert(categories, langname .. "回借詞")
else
local action = data.borrowing_type and "源自" or "派生自"
local suffix = (data.borrowing_type or "") .. "詞"
insert(categories, action .. cat_name .. "的" .. langname .. suffix)
end
end
return display, categories
end
function export.format_source(data)
local lang, sort_key = data.lang, data.sort_key
-- [[Special:WhatLinksHere/Wiktionary:Tracking/etymology/sortkey]]
if sort_key then
track("sortkey")
end
local display, categories = export.insert_source_cat_get_display(data)
if lang and not data.nocat then
-- Format categories, but only if there is a current language; {{cog}} currently gets no categories
categories = format_categories(categories, lang, sort_key, nil, data.force_cat or force_cat)
else
categories = ""
end
return "<span class=\"etyl\">" .. display .. categories .. "</span>"
end
--[==[
Format sources for etymology templates such as {{tl|bor}}, {{tl|der}}, {{tl|inh}} and {{tl|cog}}. There may potentially
be more than one source language (except currently {{tl|inh}}, which doesn't support it because it doesn't really
make sense). In that case, all but the last source language is linked to the first term, but only if there is such a
term and this linking makes sense, i.e. either (1) the term page exists after stripping diacritics according to the
source language in question, or (2) the result of stripping diacritics according to the source language in question
results in a different page from the same process applied with the last source language. For example, {{m|ru|соля́нка}}
will link to [[солянка]] but {{m|en|соля́нка}} will link to [[соля́нка]] with an accent, and since they are different
pages, the use of English as a non-final source with term 'соля́нка' will link to [[соля́нка]] even though it doesn't
exist, on the assumption that it is merely a redlink that might exist. If none of the above criteria apply, a non-final
source language will be linked to the Wikipedia entry for the language, just as final source languages always are.
`data` contains the following fields:
* `lang`: The destination language object into which the terms were borrowed, inherited or otherwise derived. Used for
categorization and can be nil, as with {{tl|cog}}.
* `sources`: List of source objects. Most commonly there is only one. If there are multiple, the non-final ones are
handled specially; see above.
* `terms`: List of term objects. Most commonly there is only one. If there are multiple source objects as well as
multiple term objects, the non-final source objects link to the first term object.
* `sort_key`: Sort key for categories. Usually nil.
* `categories`: Categories to add to the page. Additional categories may be added to `categories` based on the source
languages ('''in which case `categories` is destructively modified'''). If `lang` is nil, no categories will be
added.
* `nocat`: Don't add any categories to the page.
* `sourceconj`: Conjunction used to separate multiple source languages. Defaults to {"and"}. Currently recognized
values are `and`, `or`, `,`, `;`, `/` and `~`.
* `borrowing_type`: Borrowing type used in categories, such as {"learned borrowings"}. Defaults to {"terms derived"}.
* `force_cat`: Force category generation on non-mainspace pages.
]==]
function export.format_sources(data)
local lang, sources, terms, borrowing_type, sort_key, categories, nocat =
data.lang, data.sources, data.terms, data.borrowing_type, data.sort_key, data.categories, data.nocat
local term1, sources_n, source_segs = terms[1], #sources, {}
local final_link_page
local term1_term, term1_sc = term1.term, term1.sc
if sources_n > 1 and term1_term and term1_term ~= "-" then
final_link_page = get_link_page(term1_term, sources[sources_n], term1_sc)
end
for i, source in ipairs(sources) do
local seg, display_term
if i < sources_n and term1_term and term1_term ~= "-" then
local link_page = get_link_page(term1_term, source, term1_sc)
display_term = (link_page ~= final_link_page) or (link_page and not not new_title(link_page):getContent())
end
-- TODO: if the display forms or transliterations are different, display the terms separately.
if display_term then
local display, this_cats = export.insert_source_cat_get_display{
lang = lang,
source = source,
borrowing_type = borrowing_type,
raw = true,
categories = categories,
nocat = nocat,
}
seg = language_link {
lang = source,
term = term1_term,
alt = display,
tr = "-",
}
if lang and not nocat then
-- Format categories, but only if there is a current language; {{cog}} currently gets no categories
this_cats = format_categories(this_cats, lang, sort_key, nil, data.force_cat or force_cat)
else
this_cats = ""
end
seg = "<span class=\"etyl\">" .. seg .. this_cats .. "</span>"
else
seg = export.format_source{
lang = lang,
source = source,
borrowing_type = borrowing_type,
sort_key = sort_key,
categories = categories,
nocat = nocat,
}
end
insert(source_segs, seg)
end
return join_segs(source_segs, data.sourceconj or "和")
end
-- Internal implementation of {{cognate}}/{{cog}} template.
function export.format_cognate(data)
return export.format_derived {
sources = data.sources,
terms = data.terms,
sort_key = data.sort_key,
sourceconj = data.sourceconj,
conj = data.conj,
template_name = "cognate",
force_cat = data.force_cat,
}
end
--[==[
Internal implementation of {{derived}}/{{der}} template. This is called externally from [[Module:affix]],
[[Module:affixusex]] and [[Module:see]] and needs to support decorations (qualifiers, labels and references) on the
outside of the sources for use by those modules.
`data` contains the following fields:
* `lang`: The destination language object into which the terms were derived. Used for categorization and can be nil, as
with {{tl|cog}}; in this case, no categories are added.
* `sources`: List of source objects. Most commonly there is only one. If there are multiple, the non-final ones are
handled specially; see `format_sources()`.
* `terms`: List of term objects. Most commonly there is only one. If there are multiple source objects as well as
multiple term objects, the non-final source objects link to the first term object.
* `conj`: Conjunction used to separate multiple terms. '''Required'''. Currently recognized values are `and`, `or`, `,`,
`;`, `/` and `~`.
* `sourceconj`: Conjunction used to separate multiple source languages. Defaults to {"and"}. Currently recognized
values are as for `conj` above.
* `decorations_on_outside`: If specified, any decorations (qualifiers, labels or references) in the first term in
`terms` will be displayed on the outside of (before) the source language(s) in `sources`. Normally this should be
specified if there is only one term possible in `terms`.
* `template_name`: Name of the template invoking this function. Must be specified. Only used for tracking pages.
* `sort_key`: Sort key for categories. Usually nil.
* `categories`: Categories to add to the page. Additional categories may be added to `categories` based on the source
languages ('''in which case `categories` is destructively modified'''). If `lang` is nil, no categories will be
added.
* `nocat`: Don't add any categories to the page.
* `borrowing_type`: Borrowing type used in categories, such as {"learned borrowings"}. Defaults to {"terms derived"}.
* `force_cat`: Force category generation on non-mainspace pages.
]==]
function export.format_derived(data)
local terms = data.terms
local sourcetext = export.format_sources(data)
return export.format_links(terms, data.conj, data.template_name, sourcetext, data.decorations_on_outside)
end
function export.insert_borrowed_cat(categories, lang, source)
if lang_is_source(lang, source) then
return
end
-- If both are the same, we want e.g. [[:Category:English terms borrowed back into English]] not
-- [[:Category:English terms borrowed from English]]; the former is inserted automatically by format_source().
-- The second parameter here doesn't matter as it only affects `display`, which we don't use.
insert(categories, "源自" .. select(2, export.get_display_and_cat_name(source, "raw")) .. "的" .. lang:getFullName() .. "借詞")
end
-- Internal implementation of {{borrowed}}/{{bor}} template.
function export.format_borrowed(data)
local categories = {}
if not data.nocat then
local lang = data.lang
for _, source in ipairs(data.sources) do
export.insert_borrowed_cat(categories, lang, source)
end
end
data = shallow_copy(data)
data.categories = categories
return export.format_links(data.terms, data.conj, "borrowed", export.format_sources(data))
end
do
-- Generate the non-ancestor error message.
local function show_language(lang)
local retval = ("%s (%s)"):format(lang:makeCategoryLink(), lang:getCode())
if lang:hasType("etymology-only") then
retval = retval .. ("(僅用於詞源的語言,其常規親代語言是%s)"):format(
show_language(lang:getParent()))
end
return retval
end
-- Check that `lang` has `otherlang` (which may be an etymology-only language) as an ancestor. Throw an error if
-- not. When `lang` is a family, verifies that `otherlang` is a language in that family.
function export.check_ancestor(lang, otherlang)
-- When `lang` is a family, verify `otherlang` is in that family or in its parent family.
if lang.hasType and lang:hasType("family") then
local family_code = lang:getCode()
local function in_family_code(fcode, other)
if not fcode or fcode == "" then return false end
if other.inFamily and other:inFamily(fcode) then return true end
if other.getFamilyCode and other:getFamilyCode() == fcode then return true end
return false
end
local in_family = in_family_code(family_code, otherlang)
if not in_family then
local parent_code
if lang.getParent then
local parent_family = lang:getParent()
if parent_family and parent_family.getCode then
parent_code = parent_family:getCode()
end
end
if not parent_code and family_code:find("-", 1, true) then
parent_code = family_code:match("^(.+)-[^-]+$")
end
if parent_code then
in_family = in_family_code(parent_code, otherlang)
end
end
if not in_family then
local other_display = (otherlang.getCanonicalName and otherlang:getCanonicalName()) or (otherlang.getCode and otherlang:getCode()) or tostring(otherlang)
local fam_display = (lang.getCanonicalName and lang:getCanonicalName()) or family_code
error(("%s is not in family %s; inherited ancestor under a family must be a language in that family or its parent family.")
:format(other_display, fam_display))
end
return
end
-- FIXME: I don't know if this function works correctly with etym-only languages in `lang`. I have fixed up
-- the module link code appropriately (June 2024) but the remaining logic is untouched.
if lang:hasAncestor(otherlang) then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/etymology/variety]]
-- Track inheritance from varieties of Latin that shouldn't have any descendants (everything except Old Latin, Classical Latin and Vulgar Latin).
if otherlang:getFullCode() == "la" then
otherlang = otherlang:getCode()
if not (otherlang == "itc-ola" or otherlang == "la-cla" or otherlang == "la-vul") then
track("bad ancestor", otherlang)
end
end
return
end
local ancestors, postscript = lang:getAncestors()
local etym_module_link = lang:hasType("etymology-only") and "[[Module:etymology languages/data]] or " or ""
local module_link = "[[" .. get_language_data_module_name(lang:getFullCode()) .. "]]"
if not ancestors[1] then
postscript = show_language(lang) .. "沒有祖語。"
else
local ancestor_list = {}
for _, ancestor in ipairs(ancestors) do
insert(ancestor_list, show_language(ancestor))
end
postscript = ("%s的祖語為%s。"):format(
lang:getCanonicalName(),
concat(ancestor_list, "和"))
end
error(("%s is not set as an ancestor of %s in %s%s. %s")
:format(show_language(otherlang), show_language(lang), etym_module_link, module_link, postscript))
end
end
-- Internal implementation of {{inherited}}/{{inh}} template.
function export.format_inherited(data)
local lang, terms, nocat = data.lang, data.terms, data.nocat
local source = terms[1].lang
local categories = {}
if not nocat then
insert(categories, "源自" .. source:getCanonicalName() .. "的" .. lang:getFullName() .. "繼承詞")
end
export.check_ancestor(lang, source)
data = shallow_copy(data)
data.categories = categories
data.source = source
return export.format_links(terms, data.conj, "inherited", export.format_source(data))
end
-- Internal implementation of "misc variant" templates such as {{abbrev}}, {{clipping}}, {{reduplication}} and the like.
function export.format_misc_variant(data)
local lang, notext, terms, cats, parts = data.lang, data.notext, data.terms, data.cats, {}
if terms[1] then
local termparts = {}
-- Make links out of all the parts.
for _, termobj in ipairs(terms) do
local result
if termobj.lang then
result = export.format_derived {
lang = lang,
terms = {termobj},
sources = termobj.termlangs or {termobj.lang},
template_name = "misc_variant",
decorations_on_outside = true,
force_cat = data.force_cat,
nocat = true, -- L10N:修正多餘的回借詞分類
}
else
termobj.lang = lang
result = export.format_links({termobj}, nil, "misc_variant")
end
table.insert(termparts, result)
end
-- l10n: 注意語序!應為 linktext (來源詞) + oftext (的) + text (說明文字)
local linktext = join_segs(termparts, data.conj)
insert(parts, linktext)
if not notext and linktext ~= "" then
-- FIXME: If term is given as '-', we should consider displaying just "Clipping" not "Clipping of".
insert(parts, " " .. (data.oftext or "的"))
end
end
if not notext then
insert(parts, data.text)
end
local categories = {}
if not data.nocat and cats then
for _, cat in ipairs(cats) do
insert(categories, lang:getFullName() .. cat)
end
end
if categories[1] then
insert(parts, format_categories(categories, lang, data.sort_key, nil, data.force_cat or force_cat))
end
-- l10n: fix spaces
local result = concat(parts)
if result:sub(1, 1) == " " then
result = result:sub(2)
end
return result
end
-- Implementation of miscellaneous templates such as {{unknown}} and {{onomatopoeia}} that have no associated terms.
function export.format_misc_variant_no_term(data)
local parts = {}
if not data.notext then
insert(parts, data.title)
end
if not data.nocat and data.cat then
local lang, categories = data.lang, {}
insert(categories, data.cat:find("的") and data.cat:gsub("的", "的" .. lang:getFullName()) or lang:getFullName() .. data.cat) -- L10N
insert(parts, format_categories(categories, lang, data.sort_key, nil, data.force_cat or force_cat))
end
return concat(parts)
end
return export
asj7oyk091gz5bk8r7r0kad7i0jnmip
Module:Och-pron
828
1363918
9909203
9867162
2026-09-29T11:35:13Z
TongcyBot
83009
同步英語維基詞典:IPAchar → IPA
9909203
Scribunto
text/plain
local export = {}
local m_string_utils = require("Module:string utilities")
local codepoint = m_string_utils.codepoint
local gsub = m_string_utils.gsub
local len = m_string_utils.len
local safe_require = require("Module:load").safe_require
local u = m_string_utils.char
local colour_1 = "var(--wikt-palette-lightblue, #d9ebff)"
local colour_2 = "var(--wikt-palette-cyan, #eaffff)"
local colour_3 = "var(--wikt-palette-paleblue, #f8f9fa)"
local function zh_fmt(text)
return text ~= "" and '<span class="Hani" lang="zh">' .. text .. '</span>' or ""
end
local function insert_pron(reading_temp, text, system, i, return_note, index)
reading_temp = gsub(reading_temp, "([̥̊]) ", "%1")
local part = mw.text.split(reading_temp, " ")
local IPA = gsub(part[1], "^%*", "")
table.remove(part, 1)
return ((i == 1 and (len(text) == 1 or (index or 1) == 1)) and "/*" or "") .. IPA ..
(len(text) == i and "/" or "") ..
((#part > 0 and return_note) and (" " .. table.concat(part, " ")) or "")
end
local function pron_table(titlechar, reading, system, reading_index, count, i)
return system == "BS"
and {
nil,
-- [[Special:PermanentLink/6736383#該如何使其不轉換]] 在一些漢字條目,如:[[圣]]、[[袜]],有時出現的上古、中古發音,裡頭的簡體字會被轉換,使其不轉換。
'<b>' .. zh_fmt('-{' .. titlechar .. '}-') .. '</b>',
reading_index .. "/" .. count,
reading[1],
'‹ <i>' .. gsub(reading[2], '([XH])', '<sup>%1</sup>') .. '</i> ›',
'<span class="IPA">' .. gsub(insert_pron(reading[3], titlechar, "BS", 1, true), "ˤ", "ˁ") .. '</span>',
reading[4]
}
or {
nil,
-- [[Special:PermanentLink/6736383#該如何使其不轉換]] 在一些漢字條目,如:[[圣]]、[[袜]],有時出現的上古、中古發音,裡頭的簡體字會被轉換,使其不轉換。
'<b>' .. zh_fmt('-{' .. titlechar .. '}-') .. '</b>',
reading_index .. "/" .. count,
reading[1],
zh_fmt("-{[["..reading[2].."]]}-"),
zh_fmt("-{[["..reading[3].."]]}-"),
reading[4],
zh_fmt("-{[["..reading[5].."]]}-"),
'<span class="IPA">/*' .. reading[6] .. '/</span>',
gsub(gsub(gsub(reading[7], "([一-鿿㐀-䶿﨎﨏﨑﨓﨔﨟﨡﨣﨤﨧-﨩𠀀-𪛟𪜀-𰀀-,]+)", zh_fmt("%1")), "([^>,][^>,][^>,][^>,][^>,][^>,],)", "%1<br>"), "([一-鿿㐀-䶿﨎﨏﨑﨓﨔﨟﨡﨣﨤﨧-﨩𠀀-𪛟𪜀-𰀀-])", "[[%1]]")
}
end
local function get_data(system, ch)
return safe_require(("Module:zh/data/och-pron-%s/%s"):format(system, ch)) or nil
end
function export.ipa(index_text, preview)
local titlechar = mw.loadData("Module:headword/data").pagename
local reading_index = mw.text.split(index_text, ";")
local output_text = {}
local systems = { "BS", "ZS" }
local indiv_pronunciation = { ["BS"] = {}, ["ZS"] = {} }
local rand = ""
local fields = {
["BS"] = {
"[[w:白一平|白一平]]–[[w:沙加爾|沙加爾]]系統1.1 " ..
"([http://ocbaxtersagart.lsait.lsa.umich.edu/ 2014])",
"<small>字</small>",
"<small>讀音 #</small>",
"<small>現代北京音<br/>(拼音)</small>",
"<small>構擬中古音</small>",
"<small>構擬上古音</small>",
"<small>英語翻譯</small>"
},
["ZS"] = {
"[[w:鄭張尚芳|鄭張]]系統 (2003)",
"<small>字</small>",
"<small>讀音 #</small>",
"<small>序號</small>",
"<small>聲符</small>",
"<small>韻部</small>",
"<small>小分部</small>",
"<small>對應中古韻</small>",
"<small>構擬上古音</small>",
"<small>注釋</small>"
}
}
for system_seq, system in ipairs(systems) do
for i, cp in ipairs { codepoint(titlechar, 1, -1) } do
local ch = u(cp)
local data_module = get_data(system, ch)
if data_module then
local count = 0
for index, value in ipairs(data_module) do
count = count + 1
end
local reading_number = reading_index[i] and (mw.text.split(reading_index[i], ',')[system_seq] or reading_index[i]) or "y"
if reading_number == "y" then
for reading_index, reading in ipairs(data_module) do
table.insert(indiv_pronunciation[system], pron_table(ch, reading, system, reading_index, count, i))
end
elseif reading_number == "n" then
break
else
for indiv_number in mw.text.gsplit(reading_number, '%+') do
table.insert(indiv_pronunciation[system], pron_table(ch, data_module[tonumber(indiv_number)], system, indiv_number, count, i))
end
end
end
end
if indiv_pronunciation[system][1] then
local hash, results = {}, {}
local value_eff
for _, value in ipairs(indiv_pronunciation[system]) do
local valeur = value
table.remove(valeur, 1)
value_eff = table.concat(valeur)
if not hash[value_eff] then
hash[value_eff] = true
table.insert(value, 1, nil)
results[#results + 1] = value
end
end
rand = rand ~= "" and rand or gsub("oc-" .. value_eff, "[^A-Za-z0-9]", codepoint)
local fmt = {
header = '\n{| class="wikitable mw-collapsible mw-collapsed"' ..
'style="width:100%; margin:0; text-align:center; border-collapse: collapse; border-style: hidden; display: table;"',
lv1 = '\n|-\n! style="background-color:' .. colour_1 .. '" colspan=' .. #results+1 .. '|',
lv2 = '\n|-\n! style="background-color:' .. colour_2 .. '; width:8em"|',
lv3 = '\n| style="background-color:' .. colour_3 .. '"|',
closing = '\n|}',
BS_note = '\n|-\n|colspan=' .. #results+1 ..
[=[ style="text-align:left; font-size:90%"|<div class="standard-box mw-collapsible mw-collapsed">
'''來自白一平–沙加爾系統的上古漢語標註筆記:'''
<div class="mw-collapsible-content">
* 圓括號 "()" 表示存在不確定;<br>
* 方括號 "[]" 表示身分不確定,例如 *[t] 的尾音有可能是 *-t 或 *-p;<br>
* 尖角括號 "<>" 表示中綴;<br>
* 連字號 "-" 表示語素範圍;<br>
* 句號 "." 表示音節範圍。</div>
</div>]=]
}
table.insert(output_text, fmt.header)
for field_index, field in ipairs(fields[system]) do
if field:find("small") then
local field_set = {}
for _, result in ipairs(results) do
table.insert(field_set, result[field_index])
end
if table.concat(field_set) ~= "" then
table.insert(output_text, fmt.lv2 .. field .. fmt.lv3 .. table.concat(field_set, fmt.lv3))
end
else
table.insert(output_text, fmt.lv1 .. field)
end
end
table.insert(output_text, (system == "BS" and fmt.BS_note or "") .. fmt.closing)
end
end
local fold = '\n* <div title="expand" class="mw-customtoggle-oc' .. rand .. '"> ' ..
'[[w:上古漢語|上古]]<span style="float:right; border:1px solid var(--wikt-palette-lightgrey,#ccc); border-radius:1px;' ..
' padding:0 0; font-size:90%">▼</span>' .. preview .. '</div>'
return output_text[1] and fold .. gsub(table.concat(output_text), "%[%[%]%]", "") or ""
end
function export.retrieve_pron(text, reconstruction, no_intro, index)
if type(text) == "table" then text = text.args[1] end
text = require("Module:links").remove_links(text)
local retrieve_result = {}
local intro = no_intro and "" or "<span style='font-style: normal'>[[w:上古漢語|上古]]</span> "
if not reconstruction then
local index_set
if index and index ~= "y" then
index_set = mw.text.split(index, ",")
end
for char_index, cp in ipairs { codepoint(text, 1, -1) } do
local char_pronunciation = {}
local ch = u(cp)
local data_module = get_data("ZS", ch)
if data_module then
local reading_no = index_set and index_set[char_index] or "y"
if reading_no ~= "y" then
for number in mw.text.gsplit(reading_no, "+") do
table.insert(char_pronunciation, data_module[tonumber(number)][6])
end
else
for _, reading in ipairs(data_module) do
table.insert(char_pronunciation, reading[6])
end
end
else
return nil
end
table.insert(retrieve_result, table.concat(char_pronunciation, len(text) == 1 and ", *" or "/"))
end
end
return intro .. (reconstruction or "<span class=\"IPA\" lang=\"och-Latn-fonipa\">" .. "*" .. table.concat(retrieve_result, " ") .. "</span>")
end
function export.generate_show(text, index)
local index_set = mw.text.split(index, ";")
local extract_results = { ["BS"] = {}, ["ZS"] = {} }
local result = {}
local position = { ["BS"] = 3, ["ZS"] = 6 }
local fmt = {
beginning = {
["BS"] = "\n*: <small>([[w:白一平|白]]–[[w:沙加爾|沙]])</small>:" ..
'<span style="font-size:95%">',
["ZS"] = "\n*: <small>([[w:鄭張尚芳|鄭張]])</small>:" ..
'<span style="font-size:95%">',
},
ending = {
["BS"] = "</span>", ["ZS"] = "</span>"
}
}
for system_seq, system in ipairs({ "BS", "ZS" }) do
for i, cp in ipairs { codepoint(text, 1, -1) } do
local char_pronunciation = {}
local ch = u(cp)
local data_module = get_data(system, ch)
if data_module then
local existing_pron = {}
local reading_number = index_set[i] and (mw.text.split(index_set[i], ',')[system_seq] or index_set[i]) or "y"
if reading_number == "y" then
index = 0
for _, reading in ipairs(data_module) do
index = index + 1
local reading_temp = reading[position[system]]
if not existing_pron[reading_temp] then
table.insert(char_pronunciation, insert_pron(reading_temp, text, system, i, false, index))
existing_pron[reading_temp] = true
end
end
elseif reading_number == "n" then
break
else
index = 0
for indiv_number in mw.text.gsplit(reading_number, '%+') do
index = index + 1
local reading_temp = data_module[tonumber(indiv_number)][position[system]]
if not existing_pron[reading_temp] then
table.insert(char_pronunciation, insert_pron(reading_temp, text, system, i, false, index))
existing_pron[reading_temp] = true
end
end
end
table.insert(extract_results[system], table.concat(char_pronunciation, len(text) == 1 and ", " or "|"))
else
extract_results[system] = {}
break
end
end
if extract_results[system][1] then
table.insert(result, fmt.beginning[system] ..
'<span class="IPA">' ..
table.concat(extract_results[system], " ") ..
'</span>' ..
fmt.ending[system])
end
end
text = result[1] and gsub(table.concat(result), "|%*", "|") or nil
text = text and gsub(text, "/|", "|") or nil
text = text and gsub(text, "|", '<span style="padding-left:2px; padding-right:2px">|</span>')
return text
end
function export.link(frame, arg)
local args = arg or frame:getParent().args
local text, meaning, lit = args[1], args[2] or args['gloss'] or nil, args['lit'] or nil
return require("Module:zh/link").link(frame, nil, { "*" .. text, tr = export.retrieve_pron(text, args["tr"] or false, args["no_intro"] or false, args["id"] or false), gloss = meaning, lit = lit }, mw.loadData("Module:headword/data").pagename)
end
return export
98miq54rf6gmm7j5a60xjrziqbw6rvg
Module:Links
828
1364589
9909027
9849281
2026-09-29T07:21:56Z
TongcyDai
53191
同步 enwikt:pron qualifier → decorations
9909027
Scribunto
text/plain
local export = {}
--[=[
[[Unsupported titles]], pages with high memory usage,
extraction modules and part-of-speech names are listed
at [[Module:links/data]].
Other modules used:
[[Module:script utilities]]
[[Module:scripts]]
[[Module:languages]] and its submodules
[[Module:gender and number]]
[[Module:debug/track]]
]=]
local anchors_module = "Module:anchors"
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local gender_and_number_module = "Module:gender and number"
local languages_module = "Module:languages"
local load_module = "Module:load"
local memoize_module = "Module:memoize"
local pages_module = "Module:pages"
local scripts_module = "Module:scripts"
local script_utilities_module = "Module:script utilities"
local string_encode_entities_module = "Module:string/encode entities"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local concat = table.concat
local find = string.find
local get_current_title = mw.title.getCurrentTitle
local insert = table.insert
local ipairs = ipairs
local match = string.match
local new_title = mw.title.new
local pairs = pairs
local remove = table.remove
local sub = string.sub
local toNFC = mw.ustring.toNFC
local tostring = tostring
local type = type
local unstrip = mw.text.unstrip
local NAMESPACE = get_current_title().nsText
local function anchor_encode(...)
anchor_encode = require(memoize_module)(mw.uri.anchorEncode, true)
return anchor_encode(...)
end
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function decode_entities(...)
decode_entities = require(string_utilities_module).decode_entities
return decode_entities(...)
end
local function decode_uri(...)
decode_uri = require(string_utilities_module).decode_uri
return decode_uri(...)
end
-- Can't yet replace, as the [[Module:string utilities]] version no longer has automatic double-encoding prevention, which requires changes here to account for.
local function encode_entities(...)
encode_entities = require(string_encode_entities_module)
return encode_entities(...)
end
local function extend(...)
extend = require(table_module).extend
return extend(...)
end
local function find_best_script_without_lang(...)
find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang
return find_best_script_without_lang(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function format_genders(...)
format_genders = require(gender_and_number_module).format_genders
return format_genders(...)
end
local function format_decorations(...)
format_decorations = require(decorations_module).format_decorations
return format_decorations(...)
end
local function get_current_L2(...)
get_current_L2 = require(pages_module).get_current_L2
return get_current_L2(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function get_script(...)
get_script = require(scripts_module).getByCode
return get_script(...)
end
local function language_anchor(...)
language_anchor = require(anchors_module).language_anchor
return language_anchor(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
local function request_script(...)
request_script = require(script_utilities_module).request_script
return request_script(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function tag_text(...)
tag_text = require(script_utilities_module).tag_text
return tag_text(...)
end
local function tag_translit(...)
tag_translit = require(script_utilities_module).tag_translit
return tag_translit(...)
end
local function trim(...)
trim = require(string_utilities_module).trim
return trim(...)
end
local function u(...)
u = require(string_utilities_module).char
return u(...)
end
local function ulower(...)
ulower = require(string_utilities_module).lower
return ulower(...)
end
local function umatch(...)
umatch = require(string_utilities_module).match
return umatch(...)
end
local m_headword_data
local function get_headword_data()
m_headword_data = load_data("Module:headword/data")
return m_headword_data
end
local function track(page, code)
local tracking_page = "links/" .. page
debug_track(tracking_page)
if code then
debug_track(tracking_page .. "/" .. code)
end
end
local function selective_trim(...)
-- Unconditionally trimmed charset.
local always_trim =
"\194\128-\194\159" .. -- U+0080-009F (C1 control characters)
"\194\173" .. -- U+00AD (soft hyphen)
"\226\128\170-\226\128\174" .. -- U+202A-202E (directionality formatting characters)
"\226\129\166-\226\129\169" -- U+2066-2069 (directionality formatting characters)
-- Standard trimmed charset.
local standard_trim = "%s" .. -- (default whitespace charset)
"\226\128\139-\226\128\141" .. -- U+200B-200D (zero-width spaces)
always_trim
-- If there are non-whitespace characters, trim all characters in `standard_trim`.
-- Otherwise, only trim the characters in `always_trim`.
selective_trim = function(text)
if text == "" then
return text
end
local trimmed = trim(text, standard_trim)
if trimmed ~= "" then
return trimmed
end
return trim(text, always_trim)
end
return selective_trim(...)
end
local function escape(text, str)
local rep
repeat
text, rep = text:gsub("\\\\(\\*" .. str .. ")", "\5%1")
until rep == 0
return (text:gsub("\\" .. str, "\6"))
end
local function unescape(text, str)
return (text
:gsub("\5", "\\")
:gsub("\6", str))
end
-- Remove bold, italics, soft hyphens, strip markers and HTML tags.
local function remove_formatting(str)
str = str
:gsub("('*)'''(.-'*)'''", "%1%2")
:gsub("('*)''(.-'*)''", "%1%2")
:gsub("", "")
return (unstrip(str)
:gsub("<[^<>]+>", ""))
end
--[==[Takes an input and splits on a double slash (taking account of escaping backslashes).]==]
function export.split_on_slashes(text)
if text:find("\\", nil, true) then
track("escaped", "split_on_slashes")
end
text = split(escape(text, "//"), "//", true) or {}
for i, v in ipairs(text) do
text[i] = unescape(v, "//")
if v == "" then
text[i] = false
end
end
return text
end
--[==[Takes a wikilink and outputs the link target and display text. By default, the link target will be returned as a title object, but if `allow_bad_target` is set it will be returned as a string, and no check will be performed as to whether it is a valid link target.]==]
function export.get_wikilink_parts(text, allow_bad_target)
-- TODO: replace `allow_bad_target` with `allow_unsupported`, with support for links to unsupported titles, including escape sequences.
if ( -- Filters out anything but "[[...]]" with no intermediate "[[" or "]]".
not match(text, "^()%[%[") or -- Faster than sub(text, 1, 2) ~= "[[".
find(text, "[[", 3, true) or
find(text, "]]", 3, true) ~= #text - 1
) then
return nil, nil
end
local pipe, title, display = find(text, "|", 3, true)
if pipe then
title, display = sub(text, 3, pipe - 1), sub(text, pipe + 1, -3)
else
title = sub(text, 3, -3)
display = title
end
if allow_bad_target then
return title, display
end
title = new_title(title)
-- No title object means the target is invalid.
if title == nil then
return nil, nil
-- If the link target starts with "#" then mw.title.new returns a broken
-- title object, so grab the current title and give it the correct fragment.
elseif title.prefixedText == "" then
local fragment = title.fragment
if fragment == "" then -- [[#]] isn't valid
return nil, nil
end
title = get_current_title()
title.fragment = fragment
end
return title, display
end
-- Does the work of export.get_fragment, but can be called directly to avoid unnecessary checks for embedded links.
local function get_fragment(text)
text = escape(text, "#")
-- Replace numeric character references with the corresponding character (' → '),
-- as they contain #, which causes the numeric character reference to be
-- misparsed (wa'a → wa'a → pagename wa&, fragment 39;a).
text = decode_entities(text)
local target, fragment = text:match("^(.-)#(.+)$")
target = target or text
target = unescape(target, "#")
fragment = fragment and unescape(fragment, "#")
return target, fragment
end
--[==[Takes a link target and outputs the actual target and the fragment (if any).]==]
function export.get_fragment(text)
if text:find("\\", nil, true) then
track("escaped", "get_fragment")
end
-- If there are no embedded links, process input.
local open = find(text, "[[", nil, true)
if not open then
return get_fragment(text)
end
local close = find(text, "]]", open + 2, true)
if not close then
return get_fragment(text)
-- If there is one, but it's redundant (i.e. encloses everything with no pipe), remove and process.
elseif open == 1 and close == #text - 1 and not find(text, "|", 3, true) then
return get_fragment(sub(text, 3, -3))
end
-- Otherwise, return the input.
return text
end
--[==[
Given a link target as passed to `full_link()`, get the actual page that the target refers to. This removes
bold, italics, strip markets and HTML; calls `makeEntryName()` for the language in question; converts targets
beginning with `*` to the Reconstruction namespace; and converts appendix-constructed languages to the Appendix
namespace. Returns up to three values:
# the actual page to link to, or {nil} to not link to anything;
# how the target should be displayed as, if the user didn't explicitly specify any display text; generally the
same as the original target, but minus any anti-asterisk !!;
# the value `true` if the target had a backslash-escaped * in it (FIXME: explain this more clearly).
]==]
function export.get_link_page_with_auto_display(target, lang, sc, plain)
local orig_target = target
if not target then
return nil
elseif target:find("\\", nil, true) then
track("escaped", "get_link_page")
end
target = remove_formatting(target)
if target:sub(1, 1) == ":" then
track("initial colon")
-- FIXME, the auto_display (second return value) should probably remove the colon
return target:sub(2), orig_target
end
local prefix = target:match("^(.-):")
-- Convert any escaped colons
target = target:gsub("\\:", ":")
if prefix then
-- If this is an a link to another namespace or an interwiki link, ensure there's an initial colon and then
-- return what we have (so that it works as a conventional link, and doesn't do anything weird like add the term
-- to a category.)
prefix = ulower(trim(prefix))
if prefix ~= "" and (
load_data("Module:data/namespaces")[prefix] or
load_data("Module:data/interwikis")[prefix]
) then
return target, orig_target
end
end
-- Check if the term is reconstructed and remove any asterisk. Also check for anti-asterisk (!!).
-- Otherwise, handle the escapes.
local reconstructed, escaped, anti_asterisk
if not plain then
target, reconstructed = target:gsub("^%*(.)", "%1")
if reconstructed == 0 then
target, anti_asterisk = target:gsub("^!!(.)", "%1")
if anti_asterisk == 1 then
-- Remove !! from original. FIXME! We do it this way because the call to remove_formatting() above
-- may cause non-initial !! to be interpreted as anti-asterisks. We should surely move the
-- remove_formatting() call later.
orig_target = orig_target:gsub("^!!", "")
end
end
end
target, escaped = target:gsub("^(\\-)\\%*", "%1*")
if not (sc and sc:getCode() ~= "None") then
sc = lang:findBestScript(target)
end
-- Remove carets if they are used to capitalize parts of transliterations (unless they have been escaped).
if (not sc:hasCapitalization()) and sc:isTransliterated() and target:match("%^") then
target = escape(target, "^")
:gsub("%^", "")
target = unescape(target, "^")
end
-- Get the entry name for the language.
target = lang:makeEntryName(target, sc, reconstructed == 1 or lang:hasType("appendix-constructed"))
-- If the link contains unexpanded template parameters, then don't create a link.
if target:match("{{{.-}}}") then
-- FIXME: Should we return the original target as the default display value (second return value)?
return nil
end
-- Link to appendix for reconstructed terms and terms in appendix-only languages. Plain links interpret *
-- literally, however.
if reconstructed == 1 then
if lang:getFullCode() == "und" then
-- Return the original target as default display value. If we don't do this, we wrongly get
-- [Term?] displayed instead.
return nil, orig_target
end
target = "Reconstruction:" .. lang:getFullName() .. "/" .. target
-- Reconstructed languages and substrates require an initial *.
elseif anti_asterisk ~= 1 and (lang:hasType("reconstructed") or lang:getFamilyCode() == "qfa-sub") then
error(("The specified language %s is unattested, while the term '%s' does not begin with '*' to indicate that it is reconstructed."):
format(lang:getCanonicalName(), orig_target))
elseif lang:hasType("appendix-constructed") then
target = "Appendix:" .. lang:getFullName() .. "/" .. target
else
target = target
end
return target, orig_target, escaped > 0
end
function export.get_link_page(target, lang, sc, plain)
local target, auto_display, escaped = export.get_link_page_with_auto_display(target, lang, sc, plain)
return target, escaped
end
-- Make a link from a given link's parts
local function make_link(link, lang, sc, id, isolated, cats, no_alt_ast, plain)
-- Convert percent encoding to plaintext.
link.target = link.target and decode_uri(link.target, "PATH")
link.fragment = link.fragment and decode_uri(link.fragment, "PATH")
-- Find fragments (if one isn't already set).
-- Prevents {{l|en|word#Etymology 2|word}} from linking to [[word#Etymology 2#English]].
-- # can be escaped as \#.
if link.target and link.fragment == nil then
link.target, link.fragment = get_fragment(link.target)
end
-- Process the target
local auto_display, escaped
link.target, auto_display, escaped = export.get_link_page_with_auto_display(link.target, lang, sc, plain)
-- Create a default display form.
-- If the target is "" then it's a link like [[#English]], which refers to the current page.
if auto_display == "" then
auto_display = (m_headword_data or get_headword_data()).pagename
end
-- If the display is the target and the reconstruction * has been escaped, remove the escaping backslash.
if escaped then
auto_display = auto_display:gsub("\\([^\\]*%*)", "%1", 1)
end
-- Process the display form.
if link.display then
local orig_display = link.display
link.display = lang:makeDisplayText(link.display, sc, true)
if cats then
auto_display = lang:makeDisplayText(auto_display, sc)
-- If the alt text is the same as what would have been automatically generated, then the alt parameter is redundant (e.g. {{l|en|foo|foo}}, {{l|en|w:foo|foo}}, but not {{l|en|w:foo|w:foo}}).
-- If they're different, but the alt text could have been entered as the term parameter without it affecting the target page, then the target parameter is redundant (e.g. {{l|ru|фу|фу́}}).
-- If `no_alt_ast` is true, use pcall to catch the error which will be thrown if this is a reconstructed lang and the alt text doesn't have *.
if link.display == auto_display then
-- insert(cats, "帶有多餘alt參數的" .. lang:getFullName() .. "鏈接")
else
local ok, check
if no_alt_ast then
ok, check = pcall(export.get_link_page, orig_display, lang, sc, plain)
else
ok = true
check = export.get_link_page(orig_display, lang, sc, plain)
end
if ok and link.target == check then
-- insert(cats, "帶有多餘target參數的" .. lang:getFullName() .. "鏈接")
end
end
end
else
link.display = lang:makeDisplayText(auto_display, sc)
end
if not link.target then
return link.display
end
-- If the target is the same as the current page, there is no sense id
-- and either the language code is "und" or the current L2 is the current
-- language then return a "self-link" like the software does.
if link.target == get_current_title().prefixedText then
local fragment, current_L2 = link.fragment, get_current_L2()
local s2t_current_L2 = require("Module:zh").st(current_L2) -- zh l10n: try to convert current L2 to trad. Chinese to match it.
if (
fragment and (fragment == current_L2 or fragment == s2t_current_L2) or
not (id or fragment) and (lang:getFullCode() == "und" or lang:getFullName() == current_L2 or lang:getFullName() == s2t_current_L2)
) then
return tostring(mw.html.create("strong")
:addClass("selflink")
:wikitext(link.display))
end
end
-- Add fragment. Do not add a section link to "Undetermined", as such sections do not exist and are invalid.
-- TabbedLanguages handles links without a section by linking to the "last visited" section, but adding
-- "Undetermined" would break that feature. For localized prefixes that make syntax error, please use the
-- format: ["xyz"] = true.
local prefix = link.target:match("^:*([^:]+):")
prefix = prefix and ulower(prefix)
if prefix ~= "category" and not (prefix and load_data("Module:data/interwikis")[prefix]) then
if (link.fragment or link.target:sub(-1) == "#") and not plain then
track("fragment", lang:getFullCode())
if cats then
-- insert(cats, "帶有手動片段的" .. lang:getFullName() .. "鏈接")
end
end
if not link.fragment then
if id then
link.fragment = lang:getFullCode() == "und" and anchor_encode(id) or language_anchor(lang, id)
elseif lang:getFullCode() ~= "und" and not (link.target:match("^Appendix:") or link.target:match("^Reconstruction:")) then
link.fragment = anchor_encode(lang:getFullName())
end
end
end
-- Put inward-facing square brackets around a link to isolated spacing character(s).
if isolated and #link.display > 0 and not umatch(decode_entities(link.display), "%S") then
link.display = "]" .. link.display .. "["
end
link.target = link.target:gsub("^(:?)(.*)", function(m1, m2)
return m1 .. encode_entities(m2, "#%&+/:<=>@[\\]_{|}")
end)
link.fragment = link.fragment and encode_entities(remove_formatting(link.fragment), "#%&+/:<=>@[\\]_{|}")
return "<!-- -->[[" .. link.target:gsub("^[^:]", ":%0") .. (link.fragment and "#" .. link.fragment or "") .. "|-{" .. link.display .. "}-]]" -- l10n,注意冒號
end
-- Split a link into its parts
local function parse_link(linktext)
local link = {target = linktext}
local target = link.target
link.target, link.display = target:match("^(..-)|(.+)$")
if not link.target then
link.target = target
link.display = target
end
-- There's no point in processing these, as they aren't real links.
local target_lower = link.target:lower()
for _, false_positive in ipairs({"category", "cat", "file", "image"}) do
if target_lower:match("^" .. false_positive .. ":") then
return nil
end
end
link.display = decode_entities(link.display)
link.target, link.fragment = get_fragment(link.target)
-- So that make_link does not look for a fragment again.
if not link.fragment then
link.fragment = false
end
return link
end
local function check_params_ignored_when_embedded(alt, lang, id, cats)
if alt then
track("alt-ignored")
if cats then
-- insert(data.cats, "忽略alt參數的" .. data.lang:getFullName() .. "連結")
end
end
if id then
track("id-ignored")
if cats then
-- insert(data.cats, "忽略id參數的" .. data.lang:getFullName() .. "連結")
end
end
end
-- Find embedded links and ensure they link to the correct section.
local function process_embedded_links(text, alt, lang, sc, id, cats, no_alt_ast, plain)
-- Process the non-linked text.
text = lang:makeDisplayText(text, sc, true)
-- If the text begins with * and another character, then act as if each link begins with *. However, don't do this if the * is contained within a link at the start. E.g. `|*[[foo]]` would set all_reconstructed to true, while `|[[*foo]]` would not.
local all_reconstructed = false
if not plain then
-- anchor_encode removes links etc.
if anchor_encode(text):sub(1, 1) == "*" then
all_reconstructed = true
end
-- Otherwise, handle any escapes.
text = text:gsub("^(\\-)\\%*", "%1*")
end
check_params_ignored_when_embedded(alt, lang, id, cats)
local function process_link(space1, linktext, space2)
local capture = "[[" .. linktext .. "]]"
local link = parse_link(linktext)
-- Return unprocessed false positives untouched (e.g. categories).
if not link then
return capture
end
if all_reconstructed then
if link.target:find("^!!") then
-- Check for anti-asterisk !! at the beginning of a target, indicating that a reconstructed term
-- wants a part of the term to link to a non-reconstructed term, e.g. Old English
-- {{ang-noun|m|head=*[[!!Crist|Cristes]] [[!!mæsseǣfen]]}}.
link.target = link.target:sub(3)
-- Also remove !! from the display, which may have been copied from the target (as in mæsseǣfen in
-- the example above).
link.display = link.display:gsub("^!!", "")
elseif not link.target:match("^%*") then
link.target = "*" .. link.target
end
end
linktext = make_link(link, lang, sc, id, false, nil, no_alt_ast, plain)
:gsub("^%[%[", "\3")
:gsub("%]%]$", "\4")
return space1 .. linktext .. space2
end
-- Use chars 1 and 2 as temporary substitutions, so that we can use charsets. These are converted to chars 3 and 4 by process_link, which means we can convert any remaining chars 1 and 2 back to square brackets (i.e. those not part of a link).
text = text
:gsub("%[%[", "\1")
:gsub("%]%]", "\2")
-- If the script uses ^ to capitalize transliterations, make sure that any carets preceding links are on the inside, so that they get processed with the following text.
if (
text:find("^", nil, true) and
not sc:hasCapitalization() and
sc:isTransliterated()
) then
text = escape(text, "^")
:gsub("%^\1", "\1%^")
text = unescape(text, "^")
end
text = text:gsub("\1(%s*)([^\1\2]-)(%s*)\2", process_link)
-- Remove the extra * at the beginning of a language link if it's immediately followed by a link whose display begins with * too.
if all_reconstructed then
text = text:gsub("^%*\3([^|\1-\4]+)|%*", "\3%1|*")
end
return (text
:gsub("[\1\3]", "[[")
:gsub("[\2\4]", "]]")
)
end
local function simple_link(term, fragment, alt, lang, sc, id, cats, no_alt_ast, srwc)
local plain
if lang == nil then
lang, plain = get_lang("und"), true
end
-- Get the link target and display text. If the term is the empty string, treat the input as a link to the current page.
if term == "" then
term = get_current_title().prefixedText
elseif term then
local new_term, new_alt = export.get_wikilink_parts(term, true)
if new_term then
check_params_ignored_when_embedded(alt, lang, id, cats)
-- [[|foo]] links are treated as plaintext "[[|foo]]".
-- FIXME: Pipes should be handled via a proper escape sequence, as they can occur in unsupported titles.
if new_term == "" then
term, alt = nil, term
else
local title = new_title(new_term)
if title then
local ns = title.namespace
-- File: and Category: links should be returned as-is.
if ns == 6 or ns == 14 then
return term
end
end
term, alt = new_term, new_alt
if cats then
if not (srwc and srwc(term, alt)) then
-- insert(data.cats, "帶有多餘維基鏈接的" .. data.lang:getFullName() .. "鏈接")
end
end
end
end
end
if alt then
alt = selective_trim(alt)
if alt == "" then
alt = nil
end
end
-- If there's nothing to process, return nil.
if not (term or alt) then
return nil
end
-- If there is no script, get one.
if not sc then
sc = lang:findBestScript(alt or term)
end
-- Embedded wikilinks need to be processed individually.
if term then
local open = find(term, "[[", nil, true)
if open and find(term, "]]", open + 2, true) then
return process_embedded_links(term, alt, lang, sc, id, cats, no_alt_ast, plain)
end
term = selective_trim(term)
end
-- If not, make a link using the parameters.
return make_link({
target = term,
display = alt,
fragment = fragment
}, lang, sc, id, true, cats, no_alt_ast, plain)
end
--[==[Creates a basic link to the given term. It links to the language section (such as <code>==English==</code>), but it does not add language and script wrappers, so any code that uses this function should call the <code class="n">[[Module:script utilities#tag_text|tag_text]]</code> from [[Module:script utilities]] to add such wrappers itself at some point.
The first argument, <code class="n">data</code>, may contain the following items, a subset of the items used in the <code class="n">data</code> argument of <code class="n">full_link</code>. If any other items are included, they are ignored.
{ {
term = entry_to_link_to,
alt = link_text_or_displayed_text,
lang = language_object,
id = sense_id,
} }
; <code class="n">term</code>
: Text to turn into a link. This is generally the name of a page. The text can contain wikilinks already embedded in it. These are processed individually just like a single link would be. The <code class="n">alt</code> argument is ignored in this case.
; <code class="n">alt</code> (''optional'')
: The alternative display for the link, if different from the linked page. If this is {{code|lua|nil}}, the <code class="n">text</code> argument is used instead (much like regular wikilinks). If <code class="n">text</code> contains wikilinks in it, this argument is ignored and has no effect. (Links in which the alt is ignored are tracked with the tracking template {{whatlinkshere|tracking=links/alt-ignored}}.)
; <code class="n">lang</code>
: The [[Module:languages#Language objects|language object]] for the term being linked. If this argument is defined, the function will determine the language's canonical name (see [[Template:language data documentation]]), and point the link or links in the <code class="n">term</code> to the language's section of an entry, or to a language-specific senseid if the <code class="n">id</code> argument is defined.
; <code class="n">id</code> (''optional'')
: Sense id string. If this argument is defined, the link will point to a language-specific sense id ({{ll|en|identifier|id=HTML}}) created by the template {{temp|senseid}}. A sense id consists of the language's canonical name, a hyphen (<code>-</code>), and the string that was supplied as the <code class="n">id</code> argument. This is useful when a term has more than one sense in a language. If the <code class="n">term</code> argument contains wikilinks, this argument is ignored. (Links in which the sense id is ignored are tracked with the tracking template {{whatlinkshere|tracking=links/id-ignored}}.)
The second argument is as follows:
; <code class="n">allow_self_link</code>
: If {{code|lua|true}}, the function will also generate links to the current page. The default ({{code|lua|false}}) will not generate a link but generate a bolded "self link" instead.
The following special options are processed for each link (both simple text and with embedded wikilinks):
* The target page name will be processed to generate the correct entry name. This is done by the [[Module:languages#makeEntryName|makeEntryName]] function in [[Module:languages]], using the <code class="n">entry_name</code> replacements in the language's data file (see [[Template:language data documentation]] for more information). This function is generally used to automatically strip dictionary-only diacritics that are not part of the normal written form of a language.
* If the text starts with <code class="n">*</code>, then the term is considered a reconstructed term, and a link to the Reconstruction: namespace will be created. If the text contains embedded wikilinks, then <code class="n">*</code> is automatically applied to each one individually, while preserving the displayed form of each link as it was given. This allows linking to phrases containing multiple reconstructed terms, while only showing the * once at the beginning.
* If the text starts with <code class="n">:</code>, then the link is treated as "raw" and the above steps are skipped. This can be used in rare cases where the page name begins with <code class="n">*</code> or if diacritics should not be stripped. For example:
** {{temp|l|en|*nix}} links to the nonexistent page [[Reconstruction:English/nix]] (<code class="n">*</code> is interpreted as a reconstruction), but {{temp|l|en|:*nix}} links to [[*nix]].
** {{temp|l|sl|Franche-Comté}} links to the nonexistent page [[Franche-Comte]] (<code>é</code> is converted to <code>e</code> by <code class="n">makeEntryName</code>), but {{temp|l|sl|:Franche-Comté}} links to [[Franche-Comté]].]==]
function export.language_link(data)
if type(data) ~= "table" then
error("The first argument to the function language_link must be a table. See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "language_link")
end
-- Categorize links to "und".
local lang, cats = data.lang, data.cats
if cats and lang:getCode() == "und" then
insert(cats, "不確定的語言連結")
end
return simple_link(
data.term,
data.fragment,
data.alt,
lang,
data.sc,
data.id,
cats,
data.no_alt_ast,
data.suppress_redundant_wikilink_cat
)
end
function export.plain_link(data)
if type(data) ~= "table" then
error("The first argument to the function plain_link must be a table. See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "plain_link")
end
return simple_link(
data.term,
data.fragment,
data.alt,
nil,
data.sc,
data.id,
data.cats,
data.no_alt_ast,
data.suppress_redundant_wikilink_cat
)
end
--[==[Replace any links with links to the correct section, but don't link the whole text if no embedded links are found. Returns the display text form.]==]
function export.embedded_language_links(data)
if type(data) ~= "table" then
error("The first argument to the function embedded_language_links must be a table. See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "embedded_language_links")
end
local term, lang, sc = data.term, data.lang, data.sc
-- If we don't have a script, get one.
if not sc then
sc = lang:findBestScript(term)
end
-- Do we have embedded wikilinks? If so, they need to be processed individually.
local open = find(term, "[[", nil, true)
if open and find(term, "]]", open + 2, true) then
return process_embedded_links(term, data.alt, lang, sc, data.id, data.cats, data.no_alt_ast)
end
-- If not, return the display text.
term = selective_trim(term)
-- FIXME: Double-escape any percent-signs, because we don't want to treat non-linked text as having percent-encoded characters. This is a hack: percent-decoding should come out of [[Module:languages]] and only dealt with in this module, as it's specific to links.
term = term:gsub("%%", "%%25")
return lang:makeDisplayText(term, sc, true)
end
function export.mark(text, item_type, face, lang)
local tag = { "", "" }
if item_type == "gloss" then
tag = { '<span class="mention-gloss-double-quote">“</span><span class="mention-gloss">',
'</span><span class="mention-gloss-double-quote">”</span>' }
if type(text) == "string" and text:match("^''[^'].*''$") then
-- Temporary tracking for mention glosses that are entirely italicized or bolded, which is probably
-- wrong. (Note that this will also find bolded mention glosses since they use triple apostrophes.)
track("italicized-mention-gloss", lang and lang:getFullCode() or nil)
end
elseif item_type == "tr" then
if face == "term" then
tag = { '<span lang="' .. lang:getFullCode() .. '" class="tr mention-tr Latn">',
'</span>' }
else
tag = { '<span lang="' .. lang:getFullCode() .. '" class="tr Latn">', '</span>' }
end
elseif item_type == "ts" then
-- \226\129\160 = word joiner (zero-width non-breaking space) U+2060
tag = { '<span class="ts mention-ts Latn">/\226\129\160', '\226\129\160/</span>' }
elseif item_type == "pos" then
tag = { '<span class="ann-pos">', '</span>' }
elseif item_type == "non-gloss" then
tag = { '<span class="ann-non-gloss">', '</span>' }
elseif item_type == "annotations" then
tag = { '<span class="mention-gloss-paren annotation-paren">(</span>',
'<span class="mention-gloss-paren annotation-paren">)</span>' }
end
if type(text) == "string" then
return tag[1] .. text .. tag[2]
else
return ""
end
end
local pos_tags
--[==[Formats the annotations that are displayed with a link created by {{code|lua|full_link}}. Annotations are the extra bits of information that are displayed following the linked term, and include things such as gender, transliteration, gloss and so on.
* The first argument is a table possessing some or all of the following keys:
*:; <code class="n">genders</code>
*:: Table containing a list of gender specifications in the style of [[Module:gender and number]].
*:; <code class="n">tr</code>
*:: Transliteration.
*:; <code class="n">gloss</code>
*:: Gloss that translates the term in the link, or gives some other descriptive information.
*:; <code class="n">pos</code>
*:: Part of speech of the linked term. If the given argument matches one of the aliases in `pos_aliases` in [[Module:headword/data]], or consists of a part of speech or alias followed by `f` (for a non-lemma form), expand it appropriately. Otherwise, just show the given text as it is.
*:; <code class="n">ng</code>
*:: Arbitrary non-gloss descriptive text for the link. This should be used in preference to putting descriptive text in `gloss` or `pos`.
*:; <code class="n">lit</code>
*:: Literal meaning of the term, if the usual meaning is figurative or idiomatic.
*:Any of the above values can be omitted from the <code class="n">info</code> argument. If a completely empty table is given (with no annotations at all), then an empty string is returned.
* The second argument is a string. Valid values are listed in [[Module:script utilities/data]] "data.translit" table.]==]
function export.format_link_annotations(data, face)
local output = {}
-- Interwiki link
if data.interwiki then
insert(output, data.interwiki)
end
-- Genders
if type(data.genders) ~= "table" then
data.genders = { data.genders }
end
if data.genders and #data.genders > 0 then
local genders, gender_cats = format_genders(data.genders, data.lang)
insert(output, " " .. genders)
if gender_cats then
local cats = data.cats
if cats then
extend(cats, gender_cats)
end
end
end
local annotations = {}
-- Transliteration and transcription
if data.tr and data.tr[1] or data.ts and data.ts[1] then
local kind
if face == "term" then
kind = face
else
kind = "default"
end
if data.tr[1] and data.ts[1] then
insert(annotations, tag_translit(data.tr[1], data.lang, kind) .. " " .. export.mark(data.ts[1], "ts"))
elseif data.ts[1] then
insert(annotations, export.mark(data.ts[1], "ts"))
else
insert(annotations, tag_translit(data.tr[1], data.lang, kind))
end
end
-- Gloss/translation
if data.gloss then
insert(annotations, export.mark(data.gloss, "gloss"))
end
-- Part of speech
if data.pos then
-- debug category for pos= containing transcriptions
if data.pos:match("/[^><]-/") then
data.pos = data.pos .. "[[Category:links likely containing transcriptions in pos]]"
end
-- Canonicalize part of speech aliases as well as non-lemma aliases like 'nf' or 'nounf' for "noun form".
pos_tags = pos_tags or (m_headword_data or get_headword_data()).pos_aliases
local pos = pos_tags[data.pos]
if not pos and data.pos:find("f$") then
local pos_form = data.pos:sub(1, -2)
-- We only expand something ending in 'f' if the result is a recognized non-lemma POS.
pos_form = (pos_tags[pos_form] or pos_form) .. " form"
if (m_headword_data or get_headword_data()).nonlemmas[pos_form .. "s"] then
pos = pos_form
end
end
insert(annotations, export.mark(pos or data.pos, "pos"))
end
-- Non-gloss text
if data.ng then
insert(annotations, export.mark(data.ng, "non-gloss"))
end
-- Literal/sum-of-parts meaning
if data.lit then
insert(annotations, "字面意思是" .. export.mark(data.lit, "gloss"))
end
-- Provide a hook to insert additional annotations such as nested inflections.
if data.postprocess_annotations then
data.postprocess_annotations {
data = data,
annotations = annotations
}
end
if #annotations > 0 then
insert(output, " " .. export.mark(concat(annotations, ","), "annotations"))
end
return concat(output)
end
-- Encode certain characters to avoid various delimiter-related issues at various stages. We need to encode < and >
-- because they end up forming part of CSS class names inside of <span ...> and will interfere with finding the end
-- of the HTML tag. I first tried converting them to URL encoding, i.e. %3C and %3E; they then appear in the URL as
-- %253C and %253E, which get mapped back to %3C and %3E when passed to [[Module:accel]]. But mapping them to <
-- and > somehow works magically without any further work; they appear in the URL as < and >, and get passed to
-- [[Module:accel]] as < and >. I have no idea who along the chain of calls is doing the encoding and decoding. If
-- someone knows, please modify this comment appropriately!
local accel_char_map
local function get_accel_char_map()
accel_char_map = {
["%"] = ".",
[" "] = "_",
["_"] = u(0xFFF0),
["<"] = "<",
[">"] = ">",
}
return accel_char_map
end
local function encode_accel_param_chars(param)
return (param:gsub("[% <>_]", accel_char_map or get_accel_char_map()))
end
local function encode_accel_param(prefix, param)
if not param then
return ""
end
if type(param) == "table" then
local filled_params = {}
-- There may be gaps in the sequence, especially for translit params.
local maxindex = 0
for k in pairs(param) do
if type(k) == "number" and k > maxindex then
maxindex = k
end
end
for i = 1, maxindex do
filled_params[i] = param[i] or ""
end
-- [[Module:accel]] splits these up again.
param = concat(filled_params, "*~!")
end
-- This is decoded again by [[WT:ACCEL]].
return prefix .. encode_accel_param_chars(param)
end
local function insert_if_not_blank(list, item)
if item == "" then
return
end
insert(list, item)
end
local function get_class(lang, tr, accel, nowrap)
if not accel and not nowrap then
return ""
end
local classes = {}
if accel then
insert(classes, "form-of lang-" .. lang:getFullCode())
local form = accel.form
if form then
insert(classes, encode_accel_param_chars(form) .. "-form-of")
end
insert_if_not_blank(classes, encode_accel_param("gender-", accel.gender))
insert_if_not_blank(classes, encode_accel_param("pos-", accel.pos))
insert_if_not_blank(classes, encode_accel_param("transliteration-", accel.translit or (tr ~= "-" and tr or nil)))
insert_if_not_blank(classes, encode_accel_param("target-", accel.target))
insert_if_not_blank(classes, encode_accel_param("origin-", accel.lemma))
insert_if_not_blank(classes, encode_accel_param("origin_transliteration-", accel.lemma_translit))
if accel.no_store then
insert(classes, "form-of-nostore")
end
end
if nowrap then
insert(classes, nowrap)
end
return concat(classes, " ")
end
-- Add any decorations (left or right regular or accent qualifiers, labels or references) to a formatted term. `data`
-- is the object specifying the term, which should optionally contain:
-- * a language object in `lang`; required if any accent qualifiers or labels are given;
-- * left regular qualifiers in `q` (an array of strings or a single string); an empty array or blank string will be
-- ignored;
-- * right regular qualifiers in `qq` (an array of strings or a single string); an empty array or blank string will be
-- ignored;
-- * left accent qualifiers in `a` (an array of strings); an empty array will be ignored;
-- * right accent qualifiers in `aa` (an array of strings); an empty array will be ignored;
-- * left labels in `l` (an array of strings); an empty array will be ignored;
-- * right labels in `ll` (an array of strings); an empty array will be ignored;
-- * references in `refs`, an array either of strings (formatted reference text) or objects containing fields `text`
-- (formatted reference text) and optionally `name` and/or `group`.
-- `formatted` is the formatted version of the term itself.
local function add_text_decorations(data, formatted)
local q = data.q
if type(q) == "string" then
q = {q}
end
local qq = data.qq
if type(qq) == "string" then
qq = {qq}
end
if q and q[1] or qq and qq[1] or data.a and data.a[1] or data.aa and data.aa[1] or data.l and data.l[1] or
data.ll and data.ll[1] or data.refs and data.refs[1] then
formatted = format_decorations {
lang = data.lang,
text = formatted,
q = q,
qq = qq,
a = data.a,
aa = data.aa,
l = data.l,
ll = data.ll,
refs = data.refs,
}
end
return formatted
end
--[==[
Creates a full link, with annotations (see `[[#format_link_annotations|format_link_annotations]]`), in the style of {{tl|l}} or {{tl|m}}.
The first argument, `data`, must be a table. It contains the various elements that can be supplied as parameters to {{tl|l}} or {{tl|m}}:
{ {
term = entry_to_link_to,
alt = link_text_or_displayed_text,
lang = language_object,
sc = script_object,
track_sc = boolean,
no_nonstandard_sc_cat = boolean,
fragment = link_fragment,
id = sense_id,
genders = { "gender1", "gender2", ... },
tr = transliteration,
respect_link_tr = boolean,
ts = transcription,
gloss = gloss,
pos = part_of_speech_tag,
ng = non-gloss text,
lit = literal_translation,
no_alt_ast = boolean,
accel = {accelerated_creation_tags},
interwiki = interwiki,
pretext = "text_at_beginning" or nil,
posttext = "text_at_end" or nil,
q = { "left_qualifier1", "left_qualifier2", ...} or "left_qualifier",
qq = { "right_qualifier1", "right_qualifier2", ...} or "right_qualifier",
l = { "left_label1", "left_label2", ...},
ll = { "right_label1", "right_label2", ...},
a = { "left_accent_qualifier1", "left_accent_qualifier2", ...},
aa = { "right_accent_qualifier1", "right_accent_qualifier2", ...},
refs = { "formatted_ref1", "formatted_ref2", ...} or { {text = "text", name = "name", group = "group"}, ... },
show_decorations = boolean,
} }
Any one of the items in the `data` table may be {nil}, but an error will be shown if neither `term` nor `alt` nor `tr`
is present. Thus, calling {full_link{ term = term, lang = lang, sc = sc }}, where `term` is the page to link to (which
may have diacritics that will be stripped and/or embedded bracketed links) and `lang` is a
[[Module:languages#Language objects|language object]] from [[Module:languages]], will give a plain link similar to the
one produced by the template {{tl|l}}, and calling {full_link( { term = term, lang = lang, sc = sc }, "term" )} will
give a link similar to the one produced by the template {{tl|m}}.
The function will:
* Try to determine the script, based on the characters found in the `term` or `alt` argument, if the script was not
given. If a script is given and `track_sc` is {true}, it will check whether the input script is the same as the one
which would have been automatically generated and add the category [[:Category:LANG terms with redundant script codes]]
if yes, or [[:Category:LANG terms with non-redundant manual script codes]] if no. This should be used when the input
script object is directly determined by a template's `sc` parameter.
* Call `[[#language_link|language_link]]` on the `term` or `alt` forms, to remove diacritics in the page name, process
any embedded wikilinks and create links to Reconstruction or Appendix pages when necessary.
* Call `[[Module:script utilities#tag_text]]` to add the appropriate language and script tags to the term and
italicize terms written in the Latin script if necessary. Accelerated creation tags, as used by [[WT:ACCEL]], are
included.
* Generate a transliteration, based on the `alt` or `term` arguments, if the script is not Latin, no transliteration was
provided in `tr` and the combination of the term's language and script support automatic transliteration. The
transliteration itself will be linked if both `.respect_link_tr` is specified and the language of the term has the
`link_tr` property set for the script of the term; but not otherwise.
* Add the annotations (transliteration, gender, gloss, etc.) after the link.
* If `no_alt_ast` is specified, then the `alt` text does not need to contain an asterisk if the language is
reconstructed. This should only be used by modules which really need to allow links to reconstructions that don't
display asterisks (e.g. number boxes).
* If `pretext` or `posttext` is specified, this is text to (respectively) prepend or append to the output, directly
before processing decorations (qualifiers, labels and references). This can be used to add arbitrary extra text inside
of the decorations.
* If `show_decorations` is specified, then decorations specified in `data` (i.e. left and right qualifiers, accent
qualifiers, labels and references) will be displayed, otherwise they will be ignored. (This is because a fair amount
of code stores decorations in these fields and displays them itself, rather than expecting {full_link()} to display
them.) '''NOTE:''' `data.show_qualifiers` and the `show_qualifiers` fourth argument both have the same effect as
`data.show_decorations`. Both are deprecated (and will be removed eventually).]==]
function export.full_link(data, face, allow_self_link, show_qualifiers)
if type(data) ~= "table" then
error("The first argument to the function full_link must be a table. "
.. "See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "full_link")
end
if show_qualifiers then
-- FIXME: Convert to error once we've removed all uses, then eventually remove the error code
track("full_link show_qualifiers param")
track("full_link show_qualifiers")
end
if data.show_qualifiers then
-- FIXME: Convert to error once we've removed all uses, then eventually remove the error code
track("full_link show_qualifiers data")
track("full_link show_qualifiers")
end
-- Prevent data from being destructively modified.
local data = shallow_copy(data)
data.show_decorations = data.show_decorations or data.show_qualifiers or show_qualifiers
-- FIXME: this shouldn't be added to `data`, as that means the input table needs to be cloned.
data.cats = {}
-- Categorize links to "und".
local lang, cats = data.lang, data.cats
if cats and lang:getCode() == "und" then
insert(cats, "不確定的語言連結")
end
local terms = {true}
-- Generate multiple forms if applicable.
for _, param in ipairs{"term", "alt"} do
if type(data[param]) == "string" and data[param]:find("//", nil, true) then
data[param] = export.split_on_slashes(data[param])
elseif type(data[param]) == "string" and not (type(data.term) == "string" and data.term:find("//", nil, true)) then
if not data.no_generate_forms then
data[param] = lang:generateForms(data[param])
else
data[param] = {data[param]}
end
else
data[param] = {}
end
end
for _, param in ipairs{"sc", "tr", "ts"} do
data[param] = {data[param]}
end
for _, param in ipairs{"term", "alt", "sc", "tr", "ts"} do
for i in pairs(data[param]) do
terms[i] = true
end
end
-- Create the link
local output = {}
local id, no_alt_ast, srwc, accel, nevercalltr = data.id, data.no_alt_ast, data.suppress_redundant_wikilink_cat,
data.accel, data.never_call_transliteration_module
local link_tr = data.respect_link_tr and lang:link_tr(data.sc[1])
for i in ipairs(terms) do
local link
-- Is there any text to show?
if (data.term[i] or data.alt[i]) then
-- Try to detect the script if it was not provided
local display_term = data.alt[i] or data.term[i]
local best = lang:findBestScript(display_term)
-- no_nonstandard_sc_cat is intended for use in [[Module:interproject]]
if (
not data.no_nonstandard_sc_cat and
best:getCode() == "None" and
find_best_script_without_lang(display_term):getCode() ~= "None"
) then
-- insert(data.cats, "使用非標準文字的" .. data.lang:getFullName() .. "詞")
end
if not data.sc[i] then
data.sc[i] = best
-- Track uses of sc parameter.
elseif data.track_sc then
if data.sc[i]:getCode() == best:getCode() then
-- insert(data.cats, "有多餘文字代碼的" .. data.lang:getFullName() .. "詞")
else
-- insert(data.cats, "有非多餘手動文字代碼的" .. data.lang:getFullName() .. "詞")
end
end
-- If using a discouraged character sequence, add to maintenance category
if data.sc[i]:hasNormalizationFixes() == true then
if (data.term[i] and data.sc[i]:fixDiscouragedSequences(toNFC(data.term[i])) ~= toNFC(data.term[i])) or (data.alt[i] and data.sc[i]:fixDiscouragedSequences(toNFC(data.alt[i])) ~= toNFC(data.alt[i])) then
insert(cats, "Pages using discouraged character sequences")
end
end
link = simple_link(
data.term[i],
data.fragment,
data.alt[i],
lang,
data.sc[i],
id,
cats,
no_alt_ast,
srwc
)
end
-- simple_link can return nil, so check if a link has been generated.
if link then
-- Add "nowrap" class to prefixes in order to prevent wrapping after the hyphen
local nowrap
local display_term = data.alt[i] or data.term[i]
if display_term and (display_term:find("^%-") or display_term:find("^־")) then -- Hebrew maqqef -- FIXME, use hyphens from [[Module:affix]]
nowrap = "nowrap"
end
link = tag_text(link, lang, data.sc[i], face, get_class(lang, data.tr[i], accel, nowrap))
else
--[[ No term to show.
Is there at least a transliteration we can work from? ]]
link = request_script(lang, data.sc[i])
-- No link to show, and no transliteration either. Show a term request (unless it's a substrate, as they rarely take terms).
if (link == "" or (not data.tr[i]) or data.tr[i] == "-") and lang:getFamilyCode() ~= "qfa-sub" then
-- If there are multiple terms, break the loop instead.
if i > 1 then
remove(output)
break
elseif NAMESPACE ~= "Template" then
insert(data.cats, data.lang:getFullName() .. "詞請求")
end
link = "<small>[具體何詞?]</small>"
end
end
insert(output, link)
if i < #terms then insert(output, "<span class=\"Zsym mention\" style=\"font-size:100%;\">/</span>") end
end
-- TODO: Currently only handles the first transliteration, pending consensus on how to handle multiple translits for multiple forms, as this is not always desirable (e.g. traditional/simplified Chinese).
if data.tr[1] == "" or data.tr[1] == "-" then
data.tr[1] = nil
else
local phonetic_extraction = load_data("Module:links/data").phonetic_extraction
phonetic_extraction = phonetic_extraction[lang:getCode()] or phonetic_extraction[lang:getFullCode()]
if phonetic_extraction then
data.tr[1] = data.tr[1] or require(phonetic_extraction).getTranslit(export.remove_links(data.alt[1] or data.term[1]))
elseif (data.term[1] or data.alt[1]) and data.sc[1]:isTransliterated() then
-- Track whenever there is manual translit. The categories below like 'terms with redundant transliterations'
-- aren't sufficient because they only work with reference to automatic translit and won't operate at all in
-- languages without any automatic translit, like Persian and Hebrew.
if data.tr[1] then
local full_code = lang:getFullCode()
track("manual-tr", full_code)
end
if not nevercalltr then
-- Try to generate a transliteration.
local text = data.alt[1] or data.term[1]
if not link_tr then
text = export.remove_links(text, true)
end
local automated_tr = lang:transliterate(text, data.sc[1])
if automated_tr then
local manual_tr = data.tr[1]
if manual_tr then
if export.remove_links(manual_tr) == export.remove_links(automated_tr) then
insert(cats, "有多餘轉寫的" .. lang:getFullName() .. "詞")
else
-- Prevents Arabic root categories from flooding the tracking categories.
if NAMESPACE ~= "Category" then
-- insert(cats, "有非多餘手動轉寫的" .. lang:getFullName() .. "詞")
end
end
end
if not manual_tr or lang:overrideManualTranslit(data.sc[1]) then
data.tr[1] = automated_tr
end
end
end
end
end
-- Link to the transliteration entry for languages that require this
if data.tr[1] and link_tr and not data.tr[1]:match("%[%[(.-)%]%]") then
data.tr[1] = simple_link(
data.tr[1],
nil,
nil,
lang,
get_script("Latn"),
nil,
cats,
no_alt_ast,
srwc
)
elseif data.tr[1] and not link_tr then
-- Remove the pseudo-HTML tags added by remove_links.
data.tr[1] = data.tr[1]:gsub("</?link>", "")
end
if data.tr[1] and not umatch(data.tr[1], "[^%s%p]") then data.tr[1] = nil end
insert(output, export.format_link_annotations(data, face))
if data.pretext then
insert(output, 1, data.pretext)
end
if data.posttext then
insert(output, data.posttext)
end
local categories = cats[1] and format_categories(cats, lang, "-", nil, nil, data.sc) or ""
output = concat(output)
if data.show_decorations then
output = add_text_decorations(data, output)
end
return output .. categories
end
--[==[Replaces all wikilinks with their displayed text, and removes any categories. This function can be invoked either from a template or from another module.
-- Strips links: deletes category links, the targets of piped links, and any double square brackets involved in links (other than file links, which are untouched). If `tag` is set, then any links removed will be given pseudo-HTML tags, which allow the substitution functions in [[Module:languages]] to properly subdivide the text in order to reduce the chance of substitution failures in modules which scrape pages like [[Module:zh-translit]].
-- FIXME: This is quite hacky. We probably want this to be integrated into [[Module:languages]], but we can't do that until we know that nothing is pushing pipe linked transliterations through it for languages which don't have link_tr set.
* <code><nowiki>[[page|displayed text]]</nowiki></code> → <code><nowiki>displayed text</nowiki></code>
* <code><nowiki>[[page and displayed text]]</nowiki></code> → <code><nowiki>page and displayed text</nowiki></code>
* <code><nowiki>[[Category:English lemmas|WORD]]</nowiki></code> → ''(nothing)'']==]
function export.remove_links(text, tag)
if type(text) == "table" then
text = text.args[1]
end
if not text or text == "" then
return ""
end
text = text
:gsub("%[%[", "\1")
:gsub("%]%]", "\2")
-- Parse internal links for the display text.
text = text:gsub("(\1)([^\1\2]-)(\2)",
function(c1, c2, c3)
-- Don't remove files.
for _, false_positive in ipairs({"file", "image"}) do
if c2:lower():match("^" .. false_positive .. ":") then return c1 .. c2 .. c3 end
end
-- Remove categories completely.
for _, false_positive in ipairs({"category", "cat"}) do
if c2:lower():match("^" .. false_positive .. ":") then return "" end
end
-- In piped links, remove all text before the pipe, unless it's the final character (i.e. the pipe trick), in which case just remove the pipe.
c2 = c2:match("^[^|]*|(.+)") or c2:match("([^|]+)|$") or c2
if tag then
return "<link>" .. c2 .. "</link>"
else
return c2
end
end)
text = text
:gsub("\1", "[[")
:gsub("\2", "]]")
text = text:gsub("-{", "") -- l10n
text = text:gsub("}%-", "") -- l10n
return text
end
function export.section_link(link)
if type(link) ~= "string" then
error("The first argument to section_link was a " .. type(link) .. ", but it should be a string.")
elseif link:find("\\", nil, true) then
track("escaped", "section_link")
end
local target, section = get_fragment((link:gsub("_", " ")))
if not section then
error("No \"#\" delineating a section name")
end
return simple_link(
target,
section,
target .. " § " .. section
)
end
return export
tt0qvo4ccm1cmj17x2uvhacgk5j99og
9909125
9909027
2026-09-29T07:42:03Z
TongcyDai
53191
同步 enwikt:舊介面改為報錯
9909125
Scribunto
text/plain
local export = {}
--[=[
[[Unsupported titles]], pages with high memory usage,
extraction modules and part-of-speech names are listed
at [[Module:links/data]].
Other modules used:
[[Module:script utilities]]
[[Module:scripts]]
[[Module:languages]] and its submodules
[[Module:gender and number]]
[[Module:debug/track]]
]=]
local anchors_module = "Module:anchors"
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local gender_and_number_module = "Module:gender and number"
local languages_module = "Module:languages"
local load_module = "Module:load"
local memoize_module = "Module:memoize"
local pages_module = "Module:pages"
local scripts_module = "Module:scripts"
local script_utilities_module = "Module:script utilities"
local string_encode_entities_module = "Module:string/encode entities"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local concat = table.concat
local find = string.find
local get_current_title = mw.title.getCurrentTitle
local insert = table.insert
local ipairs = ipairs
local match = string.match
local new_title = mw.title.new
local pairs = pairs
local remove = table.remove
local sub = string.sub
local toNFC = mw.ustring.toNFC
local tostring = tostring
local type = type
local unstrip = mw.text.unstrip
local NAMESPACE = get_current_title().nsText
local function anchor_encode(...)
anchor_encode = require(memoize_module)(mw.uri.anchorEncode, true)
return anchor_encode(...)
end
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function decode_entities(...)
decode_entities = require(string_utilities_module).decode_entities
return decode_entities(...)
end
local function decode_uri(...)
decode_uri = require(string_utilities_module).decode_uri
return decode_uri(...)
end
-- Can't yet replace, as the [[Module:string utilities]] version no longer has automatic double-encoding prevention, which requires changes here to account for.
local function encode_entities(...)
encode_entities = require(string_encode_entities_module)
return encode_entities(...)
end
local function extend(...)
extend = require(table_module).extend
return extend(...)
end
local function find_best_script_without_lang(...)
find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang
return find_best_script_without_lang(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function format_genders(...)
format_genders = require(gender_and_number_module).format_genders
return format_genders(...)
end
local function format_decorations(...)
format_decorations = require(decorations_module).format_decorations
return format_decorations(...)
end
local function get_current_L2(...)
get_current_L2 = require(pages_module).get_current_L2
return get_current_L2(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function get_script(...)
get_script = require(scripts_module).getByCode
return get_script(...)
end
local function language_anchor(...)
language_anchor = require(anchors_module).language_anchor
return language_anchor(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
local function request_script(...)
request_script = require(script_utilities_module).request_script
return request_script(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function tag_text(...)
tag_text = require(script_utilities_module).tag_text
return tag_text(...)
end
local function tag_translit(...)
tag_translit = require(script_utilities_module).tag_translit
return tag_translit(...)
end
local function trim(...)
trim = require(string_utilities_module).trim
return trim(...)
end
local function u(...)
u = require(string_utilities_module).char
return u(...)
end
local function ulower(...)
ulower = require(string_utilities_module).lower
return ulower(...)
end
local function umatch(...)
umatch = require(string_utilities_module).match
return umatch(...)
end
local m_headword_data
local function get_headword_data()
m_headword_data = load_data("Module:headword/data")
return m_headword_data
end
local function track(page, code)
local tracking_page = "links/" .. page
debug_track(tracking_page)
if code then
debug_track(tracking_page .. "/" .. code)
end
end
local function selective_trim(...)
-- Unconditionally trimmed charset.
local always_trim =
"\194\128-\194\159" .. -- U+0080-009F (C1 control characters)
"\194\173" .. -- U+00AD (soft hyphen)
"\226\128\170-\226\128\174" .. -- U+202A-202E (directionality formatting characters)
"\226\129\166-\226\129\169" -- U+2066-2069 (directionality formatting characters)
-- Standard trimmed charset.
local standard_trim = "%s" .. -- (default whitespace charset)
"\226\128\139-\226\128\141" .. -- U+200B-200D (zero-width spaces)
always_trim
-- If there are non-whitespace characters, trim all characters in `standard_trim`.
-- Otherwise, only trim the characters in `always_trim`.
selective_trim = function(text)
if text == "" then
return text
end
local trimmed = trim(text, standard_trim)
if trimmed ~= "" then
return trimmed
end
return trim(text, always_trim)
end
return selective_trim(...)
end
local function escape(text, str)
local rep
repeat
text, rep = text:gsub("\\\\(\\*" .. str .. ")", "\5%1")
until rep == 0
return (text:gsub("\\" .. str, "\6"))
end
local function unescape(text, str)
return (text
:gsub("\5", "\\")
:gsub("\6", str))
end
-- Remove bold, italics, soft hyphens, strip markers and HTML tags.
local function remove_formatting(str)
str = str
:gsub("('*)'''(.-'*)'''", "%1%2")
:gsub("('*)''(.-'*)''", "%1%2")
:gsub("", "")
return (unstrip(str)
:gsub("<[^<>]+>", ""))
end
--[==[Takes an input and splits on a double slash (taking account of escaping backslashes).]==]
function export.split_on_slashes(text)
if text:find("\\", nil, true) then
track("escaped", "split_on_slashes")
end
text = split(escape(text, "//"), "//", true) or {}
for i, v in ipairs(text) do
text[i] = unescape(v, "//")
if v == "" then
text[i] = false
end
end
return text
end
--[==[Takes a wikilink and outputs the link target and display text. By default, the link target will be returned as a title object, but if `allow_bad_target` is set it will be returned as a string, and no check will be performed as to whether it is a valid link target.]==]
function export.get_wikilink_parts(text, allow_bad_target)
-- TODO: replace `allow_bad_target` with `allow_unsupported`, with support for links to unsupported titles, including escape sequences.
if ( -- Filters out anything but "[[...]]" with no intermediate "[[" or "]]".
not match(text, "^()%[%[") or -- Faster than sub(text, 1, 2) ~= "[[".
find(text, "[[", 3, true) or
find(text, "]]", 3, true) ~= #text - 1
) then
return nil, nil
end
local pipe, title, display = find(text, "|", 3, true)
if pipe then
title, display = sub(text, 3, pipe - 1), sub(text, pipe + 1, -3)
else
title = sub(text, 3, -3)
display = title
end
if allow_bad_target then
return title, display
end
title = new_title(title)
-- No title object means the target is invalid.
if title == nil then
return nil, nil
-- If the link target starts with "#" then mw.title.new returns a broken
-- title object, so grab the current title and give it the correct fragment.
elseif title.prefixedText == "" then
local fragment = title.fragment
if fragment == "" then -- [[#]] isn't valid
return nil, nil
end
title = get_current_title()
title.fragment = fragment
end
return title, display
end
-- Does the work of export.get_fragment, but can be called directly to avoid unnecessary checks for embedded links.
local function get_fragment(text)
text = escape(text, "#")
-- Replace numeric character references with the corresponding character (' → '),
-- as they contain #, which causes the numeric character reference to be
-- misparsed (wa'a → wa'a → pagename wa&, fragment 39;a).
text = decode_entities(text)
local target, fragment = text:match("^(.-)#(.+)$")
target = target or text
target = unescape(target, "#")
fragment = fragment and unescape(fragment, "#")
return target, fragment
end
--[==[Takes a link target and outputs the actual target and the fragment (if any).]==]
function export.get_fragment(text)
if text:find("\\", nil, true) then
track("escaped", "get_fragment")
end
-- If there are no embedded links, process input.
local open = find(text, "[[", nil, true)
if not open then
return get_fragment(text)
end
local close = find(text, "]]", open + 2, true)
if not close then
return get_fragment(text)
-- If there is one, but it's redundant (i.e. encloses everything with no pipe), remove and process.
elseif open == 1 and close == #text - 1 and not find(text, "|", 3, true) then
return get_fragment(sub(text, 3, -3))
end
-- Otherwise, return the input.
return text
end
--[==[
Given a link target as passed to `full_link()`, get the actual page that the target refers to. This removes
bold, italics, strip markets and HTML; calls `makeEntryName()` for the language in question; converts targets
beginning with `*` to the Reconstruction namespace; and converts appendix-constructed languages to the Appendix
namespace. Returns up to three values:
# the actual page to link to, or {nil} to not link to anything;
# how the target should be displayed as, if the user didn't explicitly specify any display text; generally the
same as the original target, but minus any anti-asterisk !!;
# the value `true` if the target had a backslash-escaped * in it (FIXME: explain this more clearly).
]==]
function export.get_link_page_with_auto_display(target, lang, sc, plain)
local orig_target = target
if not target then
return nil
elseif target:find("\\", nil, true) then
track("escaped", "get_link_page")
end
target = remove_formatting(target)
if target:sub(1, 1) == ":" then
track("initial colon")
-- FIXME, the auto_display (second return value) should probably remove the colon
return target:sub(2), orig_target
end
local prefix = target:match("^(.-):")
-- Convert any escaped colons
target = target:gsub("\\:", ":")
if prefix then
-- If this is an a link to another namespace or an interwiki link, ensure there's an initial colon and then
-- return what we have (so that it works as a conventional link, and doesn't do anything weird like add the term
-- to a category.)
prefix = ulower(trim(prefix))
if prefix ~= "" and (
load_data("Module:data/namespaces")[prefix] or
load_data("Module:data/interwikis")[prefix]
) then
return target, orig_target
end
end
-- Check if the term is reconstructed and remove any asterisk. Also check for anti-asterisk (!!).
-- Otherwise, handle the escapes.
local reconstructed, escaped, anti_asterisk
if not plain then
target, reconstructed = target:gsub("^%*(.)", "%1")
if reconstructed == 0 then
target, anti_asterisk = target:gsub("^!!(.)", "%1")
if anti_asterisk == 1 then
-- Remove !! from original. FIXME! We do it this way because the call to remove_formatting() above
-- may cause non-initial !! to be interpreted as anti-asterisks. We should surely move the
-- remove_formatting() call later.
orig_target = orig_target:gsub("^!!", "")
end
end
end
target, escaped = target:gsub("^(\\-)\\%*", "%1*")
if not (sc and sc:getCode() ~= "None") then
sc = lang:findBestScript(target)
end
-- Remove carets if they are used to capitalize parts of transliterations (unless they have been escaped).
if (not sc:hasCapitalization()) and sc:isTransliterated() and target:match("%^") then
target = escape(target, "^")
:gsub("%^", "")
target = unescape(target, "^")
end
-- Get the entry name for the language.
target = lang:makeEntryName(target, sc, reconstructed == 1 or lang:hasType("appendix-constructed"))
-- If the link contains unexpanded template parameters, then don't create a link.
if target:match("{{{.-}}}") then
-- FIXME: Should we return the original target as the default display value (second return value)?
return nil
end
-- Link to appendix for reconstructed terms and terms in appendix-only languages. Plain links interpret *
-- literally, however.
if reconstructed == 1 then
if lang:getFullCode() == "und" then
-- Return the original target as default display value. If we don't do this, we wrongly get
-- [Term?] displayed instead.
return nil, orig_target
end
target = "Reconstruction:" .. lang:getFullName() .. "/" .. target
-- Reconstructed languages and substrates require an initial *.
elseif anti_asterisk ~= 1 and (lang:hasType("reconstructed") or lang:getFamilyCode() == "qfa-sub") then
error(("The specified language %s is unattested, while the term '%s' does not begin with '*' to indicate that it is reconstructed."):
format(lang:getCanonicalName(), orig_target))
elseif lang:hasType("appendix-constructed") then
target = "Appendix:" .. lang:getFullName() .. "/" .. target
else
target = target
end
return target, orig_target, escaped > 0
end
function export.get_link_page(target, lang, sc, plain)
local target, auto_display, escaped = export.get_link_page_with_auto_display(target, lang, sc, plain)
return target, escaped
end
-- Make a link from a given link's parts
local function make_link(link, lang, sc, id, isolated, cats, no_alt_ast, plain)
-- Convert percent encoding to plaintext.
link.target = link.target and decode_uri(link.target, "PATH")
link.fragment = link.fragment and decode_uri(link.fragment, "PATH")
-- Find fragments (if one isn't already set).
-- Prevents {{l|en|word#Etymology 2|word}} from linking to [[word#Etymology 2#English]].
-- # can be escaped as \#.
if link.target and link.fragment == nil then
link.target, link.fragment = get_fragment(link.target)
end
-- Process the target
local auto_display, escaped
link.target, auto_display, escaped = export.get_link_page_with_auto_display(link.target, lang, sc, plain)
-- Create a default display form.
-- If the target is "" then it's a link like [[#English]], which refers to the current page.
if auto_display == "" then
auto_display = (m_headword_data or get_headword_data()).pagename
end
-- If the display is the target and the reconstruction * has been escaped, remove the escaping backslash.
if escaped then
auto_display = auto_display:gsub("\\([^\\]*%*)", "%1", 1)
end
-- Process the display form.
if link.display then
local orig_display = link.display
link.display = lang:makeDisplayText(link.display, sc, true)
if cats then
auto_display = lang:makeDisplayText(auto_display, sc)
-- If the alt text is the same as what would have been automatically generated, then the alt parameter is redundant (e.g. {{l|en|foo|foo}}, {{l|en|w:foo|foo}}, but not {{l|en|w:foo|w:foo}}).
-- If they're different, but the alt text could have been entered as the term parameter without it affecting the target page, then the target parameter is redundant (e.g. {{l|ru|фу|фу́}}).
-- If `no_alt_ast` is true, use pcall to catch the error which will be thrown if this is a reconstructed lang and the alt text doesn't have *.
if link.display == auto_display then
-- insert(cats, "帶有多餘alt參數的" .. lang:getFullName() .. "鏈接")
else
local ok, check
if no_alt_ast then
ok, check = pcall(export.get_link_page, orig_display, lang, sc, plain)
else
ok = true
check = export.get_link_page(orig_display, lang, sc, plain)
end
if ok and link.target == check then
-- insert(cats, "帶有多餘target參數的" .. lang:getFullName() .. "鏈接")
end
end
end
else
link.display = lang:makeDisplayText(auto_display, sc)
end
if not link.target then
return link.display
end
-- If the target is the same as the current page, there is no sense id
-- and either the language code is "und" or the current L2 is the current
-- language then return a "self-link" like the software does.
if link.target == get_current_title().prefixedText then
local fragment, current_L2 = link.fragment, get_current_L2()
local s2t_current_L2 = require("Module:zh").st(current_L2) -- zh l10n: try to convert current L2 to trad. Chinese to match it.
if (
fragment and (fragment == current_L2 or fragment == s2t_current_L2) or
not (id or fragment) and (lang:getFullCode() == "und" or lang:getFullName() == current_L2 or lang:getFullName() == s2t_current_L2)
) then
return tostring(mw.html.create("strong")
:addClass("selflink")
:wikitext(link.display))
end
end
-- Add fragment. Do not add a section link to "Undetermined", as such sections do not exist and are invalid.
-- TabbedLanguages handles links without a section by linking to the "last visited" section, but adding
-- "Undetermined" would break that feature. For localized prefixes that make syntax error, please use the
-- format: ["xyz"] = true.
local prefix = link.target:match("^:*([^:]+):")
prefix = prefix and ulower(prefix)
if prefix ~= "category" and not (prefix and load_data("Module:data/interwikis")[prefix]) then
if (link.fragment or link.target:sub(-1) == "#") and not plain then
track("fragment", lang:getFullCode())
if cats then
-- insert(cats, "帶有手動片段的" .. lang:getFullName() .. "鏈接")
end
end
if not link.fragment then
if id then
link.fragment = lang:getFullCode() == "und" and anchor_encode(id) or language_anchor(lang, id)
elseif lang:getFullCode() ~= "und" and not (link.target:match("^Appendix:") or link.target:match("^Reconstruction:")) then
link.fragment = anchor_encode(lang:getFullName())
end
end
end
-- Put inward-facing square brackets around a link to isolated spacing character(s).
if isolated and #link.display > 0 and not umatch(decode_entities(link.display), "%S") then
link.display = "]" .. link.display .. "["
end
link.target = link.target:gsub("^(:?)(.*)", function(m1, m2)
return m1 .. encode_entities(m2, "#%&+/:<=>@[\\]_{|}")
end)
link.fragment = link.fragment and encode_entities(remove_formatting(link.fragment), "#%&+/:<=>@[\\]_{|}")
return "<!-- -->[[" .. link.target:gsub("^[^:]", ":%0") .. (link.fragment and "#" .. link.fragment or "") .. "|-{" .. link.display .. "}-]]" -- l10n,注意冒號
end
-- Split a link into its parts
local function parse_link(linktext)
local link = {target = linktext}
local target = link.target
link.target, link.display = target:match("^(..-)|(.+)$")
if not link.target then
link.target = target
link.display = target
end
-- There's no point in processing these, as they aren't real links.
local target_lower = link.target:lower()
for _, false_positive in ipairs({"category", "cat", "file", "image"}) do
if target_lower:match("^" .. false_positive .. ":") then
return nil
end
end
link.display = decode_entities(link.display)
link.target, link.fragment = get_fragment(link.target)
-- So that make_link does not look for a fragment again.
if not link.fragment then
link.fragment = false
end
return link
end
local function check_params_ignored_when_embedded(alt, lang, id, cats)
if alt then
track("alt-ignored")
if cats then
-- insert(data.cats, "忽略alt參數的" .. data.lang:getFullName() .. "連結")
end
end
if id then
track("id-ignored")
if cats then
-- insert(data.cats, "忽略id參數的" .. data.lang:getFullName() .. "連結")
end
end
end
-- Find embedded links and ensure they link to the correct section.
local function process_embedded_links(text, alt, lang, sc, id, cats, no_alt_ast, plain)
-- Process the non-linked text.
text = lang:makeDisplayText(text, sc, true)
-- If the text begins with * and another character, then act as if each link begins with *. However, don't do this if the * is contained within a link at the start. E.g. `|*[[foo]]` would set all_reconstructed to true, while `|[[*foo]]` would not.
local all_reconstructed = false
if not plain then
-- anchor_encode removes links etc.
if anchor_encode(text):sub(1, 1) == "*" then
all_reconstructed = true
end
-- Otherwise, handle any escapes.
text = text:gsub("^(\\-)\\%*", "%1*")
end
check_params_ignored_when_embedded(alt, lang, id, cats)
local function process_link(space1, linktext, space2)
local capture = "[[" .. linktext .. "]]"
local link = parse_link(linktext)
-- Return unprocessed false positives untouched (e.g. categories).
if not link then
return capture
end
if all_reconstructed then
if link.target:find("^!!") then
-- Check for anti-asterisk !! at the beginning of a target, indicating that a reconstructed term
-- wants a part of the term to link to a non-reconstructed term, e.g. Old English
-- {{ang-noun|m|head=*[[!!Crist|Cristes]] [[!!mæsseǣfen]]}}.
link.target = link.target:sub(3)
-- Also remove !! from the display, which may have been copied from the target (as in mæsseǣfen in
-- the example above).
link.display = link.display:gsub("^!!", "")
elseif not link.target:match("^%*") then
link.target = "*" .. link.target
end
end
linktext = make_link(link, lang, sc, id, false, nil, no_alt_ast, plain)
:gsub("^%[%[", "\3")
:gsub("%]%]$", "\4")
return space1 .. linktext .. space2
end
-- Use chars 1 and 2 as temporary substitutions, so that we can use charsets. These are converted to chars 3 and 4 by process_link, which means we can convert any remaining chars 1 and 2 back to square brackets (i.e. those not part of a link).
text = text
:gsub("%[%[", "\1")
:gsub("%]%]", "\2")
-- If the script uses ^ to capitalize transliterations, make sure that any carets preceding links are on the inside, so that they get processed with the following text.
if (
text:find("^", nil, true) and
not sc:hasCapitalization() and
sc:isTransliterated()
) then
text = escape(text, "^")
:gsub("%^\1", "\1%^")
text = unescape(text, "^")
end
text = text:gsub("\1(%s*)([^\1\2]-)(%s*)\2", process_link)
-- Remove the extra * at the beginning of a language link if it's immediately followed by a link whose display begins with * too.
if all_reconstructed then
text = text:gsub("^%*\3([^|\1-\4]+)|%*", "\3%1|*")
end
return (text
:gsub("[\1\3]", "[[")
:gsub("[\2\4]", "]]")
)
end
local function simple_link(term, fragment, alt, lang, sc, id, cats, no_alt_ast, srwc)
local plain
if lang == nil then
lang, plain = get_lang("und"), true
end
-- Get the link target and display text. If the term is the empty string, treat the input as a link to the current page.
if term == "" then
term = get_current_title().prefixedText
elseif term then
local new_term, new_alt = export.get_wikilink_parts(term, true)
if new_term then
check_params_ignored_when_embedded(alt, lang, id, cats)
-- [[|foo]] links are treated as plaintext "[[|foo]]".
-- FIXME: Pipes should be handled via a proper escape sequence, as they can occur in unsupported titles.
if new_term == "" then
term, alt = nil, term
else
local title = new_title(new_term)
if title then
local ns = title.namespace
-- File: and Category: links should be returned as-is.
if ns == 6 or ns == 14 then
return term
end
end
term, alt = new_term, new_alt
if cats then
if not (srwc and srwc(term, alt)) then
-- insert(data.cats, "帶有多餘維基鏈接的" .. data.lang:getFullName() .. "鏈接")
end
end
end
end
end
if alt then
alt = selective_trim(alt)
if alt == "" then
alt = nil
end
end
-- If there's nothing to process, return nil.
if not (term or alt) then
return nil
end
-- If there is no script, get one.
if not sc then
sc = lang:findBestScript(alt or term)
end
-- Embedded wikilinks need to be processed individually.
if term then
local open = find(term, "[[", nil, true)
if open and find(term, "]]", open + 2, true) then
return process_embedded_links(term, alt, lang, sc, id, cats, no_alt_ast, plain)
end
term = selective_trim(term)
end
-- If not, make a link using the parameters.
return make_link({
target = term,
display = alt,
fragment = fragment
}, lang, sc, id, true, cats, no_alt_ast, plain)
end
--[==[Creates a basic link to the given term. It links to the language section (such as <code>==English==</code>), but it does not add language and script wrappers, so any code that uses this function should call the <code class="n">[[Module:script utilities#tag_text|tag_text]]</code> from [[Module:script utilities]] to add such wrappers itself at some point.
The first argument, <code class="n">data</code>, may contain the following items, a subset of the items used in the <code class="n">data</code> argument of <code class="n">full_link</code>. If any other items are included, they are ignored.
{ {
term = entry_to_link_to,
alt = link_text_or_displayed_text,
lang = language_object,
id = sense_id,
} }
; <code class="n">term</code>
: Text to turn into a link. This is generally the name of a page. The text can contain wikilinks already embedded in it. These are processed individually just like a single link would be. The <code class="n">alt</code> argument is ignored in this case.
; <code class="n">alt</code> (''optional'')
: The alternative display for the link, if different from the linked page. If this is {{code|lua|nil}}, the <code class="n">text</code> argument is used instead (much like regular wikilinks). If <code class="n">text</code> contains wikilinks in it, this argument is ignored and has no effect. (Links in which the alt is ignored are tracked with the tracking template {{whatlinkshere|tracking=links/alt-ignored}}.)
; <code class="n">lang</code>
: The [[Module:languages#Language objects|language object]] for the term being linked. If this argument is defined, the function will determine the language's canonical name (see [[Template:language data documentation]]), and point the link or links in the <code class="n">term</code> to the language's section of an entry, or to a language-specific senseid if the <code class="n">id</code> argument is defined.
; <code class="n">id</code> (''optional'')
: Sense id string. If this argument is defined, the link will point to a language-specific sense id ({{ll|en|identifier|id=HTML}}) created by the template {{temp|senseid}}. A sense id consists of the language's canonical name, a hyphen (<code>-</code>), and the string that was supplied as the <code class="n">id</code> argument. This is useful when a term has more than one sense in a language. If the <code class="n">term</code> argument contains wikilinks, this argument is ignored. (Links in which the sense id is ignored are tracked with the tracking template {{whatlinkshere|tracking=links/id-ignored}}.)
The second argument is as follows:
; <code class="n">allow_self_link</code>
: If {{code|lua|true}}, the function will also generate links to the current page. The default ({{code|lua|false}}) will not generate a link but generate a bolded "self link" instead.
The following special options are processed for each link (both simple text and with embedded wikilinks):
* The target page name will be processed to generate the correct entry name. This is done by the [[Module:languages#makeEntryName|makeEntryName]] function in [[Module:languages]], using the <code class="n">entry_name</code> replacements in the language's data file (see [[Template:language data documentation]] for more information). This function is generally used to automatically strip dictionary-only diacritics that are not part of the normal written form of a language.
* If the text starts with <code class="n">*</code>, then the term is considered a reconstructed term, and a link to the Reconstruction: namespace will be created. If the text contains embedded wikilinks, then <code class="n">*</code> is automatically applied to each one individually, while preserving the displayed form of each link as it was given. This allows linking to phrases containing multiple reconstructed terms, while only showing the * once at the beginning.
* If the text starts with <code class="n">:</code>, then the link is treated as "raw" and the above steps are skipped. This can be used in rare cases where the page name begins with <code class="n">*</code> or if diacritics should not be stripped. For example:
** {{temp|l|en|*nix}} links to the nonexistent page [[Reconstruction:English/nix]] (<code class="n">*</code> is interpreted as a reconstruction), but {{temp|l|en|:*nix}} links to [[*nix]].
** {{temp|l|sl|Franche-Comté}} links to the nonexistent page [[Franche-Comte]] (<code>é</code> is converted to <code>e</code> by <code class="n">makeEntryName</code>), but {{temp|l|sl|:Franche-Comté}} links to [[Franche-Comté]].]==]
function export.language_link(data)
if type(data) ~= "table" then
error("The first argument to the function language_link must be a table. See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "language_link")
end
-- Categorize links to "und".
local lang, cats = data.lang, data.cats
if cats and lang:getCode() == "und" then
insert(cats, "不確定的語言連結")
end
return simple_link(
data.term,
data.fragment,
data.alt,
lang,
data.sc,
data.id,
cats,
data.no_alt_ast,
data.suppress_redundant_wikilink_cat
)
end
function export.plain_link(data)
if type(data) ~= "table" then
error("The first argument to the function plain_link must be a table. See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "plain_link")
end
return simple_link(
data.term,
data.fragment,
data.alt,
nil,
data.sc,
data.id,
data.cats,
data.no_alt_ast,
data.suppress_redundant_wikilink_cat
)
end
--[==[Replace any links with links to the correct section, but don't link the whole text if no embedded links are found. Returns the display text form.]==]
function export.embedded_language_links(data)
if type(data) ~= "table" then
error("The first argument to the function embedded_language_links must be a table. See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "embedded_language_links")
end
local term, lang, sc = data.term, data.lang, data.sc
-- If we don't have a script, get one.
if not sc then
sc = lang:findBestScript(term)
end
-- Do we have embedded wikilinks? If so, they need to be processed individually.
local open = find(term, "[[", nil, true)
if open and find(term, "]]", open + 2, true) then
return process_embedded_links(term, data.alt, lang, sc, data.id, data.cats, data.no_alt_ast)
end
-- If not, return the display text.
term = selective_trim(term)
-- FIXME: Double-escape any percent-signs, because we don't want to treat non-linked text as having percent-encoded characters. This is a hack: percent-decoding should come out of [[Module:languages]] and only dealt with in this module, as it's specific to links.
term = term:gsub("%%", "%%25")
return lang:makeDisplayText(term, sc, true)
end
function export.mark(text, item_type, face, lang)
local tag = { "", "" }
if item_type == "gloss" then
tag = { '<span class="mention-gloss-double-quote">“</span><span class="mention-gloss">',
'</span><span class="mention-gloss-double-quote">”</span>' }
if type(text) == "string" and text:match("^''[^'].*''$") then
-- Temporary tracking for mention glosses that are entirely italicized or bolded, which is probably
-- wrong. (Note that this will also find bolded mention glosses since they use triple apostrophes.)
track("italicized-mention-gloss", lang and lang:getFullCode() or nil)
end
elseif item_type == "tr" then
if face == "term" then
tag = { '<span lang="' .. lang:getFullCode() .. '" class="tr mention-tr Latn">',
'</span>' }
else
tag = { '<span lang="' .. lang:getFullCode() .. '" class="tr Latn">', '</span>' }
end
elseif item_type == "ts" then
-- \226\129\160 = word joiner (zero-width non-breaking space) U+2060
tag = { '<span class="ts mention-ts Latn">/\226\129\160', '\226\129\160/</span>' }
elseif item_type == "pos" then
tag = { '<span class="ann-pos">', '</span>' }
elseif item_type == "non-gloss" then
tag = { '<span class="ann-non-gloss">', '</span>' }
elseif item_type == "annotations" then
tag = { '<span class="mention-gloss-paren annotation-paren">(</span>',
'<span class="mention-gloss-paren annotation-paren">)</span>' }
end
if type(text) == "string" then
return tag[1] .. text .. tag[2]
else
return ""
end
end
local pos_tags
--[==[Formats the annotations that are displayed with a link created by {{code|lua|full_link}}. Annotations are the extra bits of information that are displayed following the linked term, and include things such as gender, transliteration, gloss and so on.
* The first argument is a table possessing some or all of the following keys:
*:; <code class="n">genders</code>
*:: Table containing a list of gender specifications in the style of [[Module:gender and number]].
*:; <code class="n">tr</code>
*:: Transliteration.
*:; <code class="n">gloss</code>
*:: Gloss that translates the term in the link, or gives some other descriptive information.
*:; <code class="n">pos</code>
*:: Part of speech of the linked term. If the given argument matches one of the aliases in `pos_aliases` in [[Module:headword/data]], or consists of a part of speech or alias followed by `f` (for a non-lemma form), expand it appropriately. Otherwise, just show the given text as it is.
*:; <code class="n">ng</code>
*:: Arbitrary non-gloss descriptive text for the link. This should be used in preference to putting descriptive text in `gloss` or `pos`.
*:; <code class="n">lit</code>
*:: Literal meaning of the term, if the usual meaning is figurative or idiomatic.
*:Any of the above values can be omitted from the <code class="n">info</code> argument. If a completely empty table is given (with no annotations at all), then an empty string is returned.
* The second argument is a string. Valid values are listed in [[Module:script utilities/data]] "data.translit" table.]==]
function export.format_link_annotations(data, face)
local output = {}
-- Interwiki link
if data.interwiki then
insert(output, data.interwiki)
end
-- Genders
if type(data.genders) ~= "table" then
data.genders = { data.genders }
end
if data.genders and #data.genders > 0 then
local genders, gender_cats = format_genders(data.genders, data.lang)
insert(output, " " .. genders)
if gender_cats then
local cats = data.cats
if cats then
extend(cats, gender_cats)
end
end
end
local annotations = {}
-- Transliteration and transcription
if data.tr and data.tr[1] or data.ts and data.ts[1] then
local kind
if face == "term" then
kind = face
else
kind = "default"
end
if data.tr[1] and data.ts[1] then
insert(annotations, tag_translit(data.tr[1], data.lang, kind) .. " " .. export.mark(data.ts[1], "ts"))
elseif data.ts[1] then
insert(annotations, export.mark(data.ts[1], "ts"))
else
insert(annotations, tag_translit(data.tr[1], data.lang, kind))
end
end
-- Gloss/translation
if data.gloss then
insert(annotations, export.mark(data.gloss, "gloss"))
end
-- Part of speech
if data.pos then
-- debug category for pos= containing transcriptions
if data.pos:match("/[^><]-/") then
data.pos = data.pos .. "[[Category:links likely containing transcriptions in pos]]"
end
-- Canonicalize part of speech aliases as well as non-lemma aliases like 'nf' or 'nounf' for "noun form".
pos_tags = pos_tags or (m_headword_data or get_headword_data()).pos_aliases
local pos = pos_tags[data.pos]
if not pos and data.pos:find("f$") then
local pos_form = data.pos:sub(1, -2)
-- We only expand something ending in 'f' if the result is a recognized non-lemma POS.
pos_form = (pos_tags[pos_form] or pos_form) .. " form"
if (m_headword_data or get_headword_data()).nonlemmas[pos_form .. "s"] then
pos = pos_form
end
end
insert(annotations, export.mark(pos or data.pos, "pos"))
end
-- Non-gloss text
if data.ng then
insert(annotations, export.mark(data.ng, "non-gloss"))
end
-- Literal/sum-of-parts meaning
if data.lit then
insert(annotations, "字面意思是" .. export.mark(data.lit, "gloss"))
end
-- Provide a hook to insert additional annotations such as nested inflections.
if data.postprocess_annotations then
data.postprocess_annotations {
data = data,
annotations = annotations
}
end
if #annotations > 0 then
insert(output, " " .. export.mark(concat(annotations, ","), "annotations"))
end
return concat(output)
end
-- Encode certain characters to avoid various delimiter-related issues at various stages. We need to encode < and >
-- because they end up forming part of CSS class names inside of <span ...> and will interfere with finding the end
-- of the HTML tag. I first tried converting them to URL encoding, i.e. %3C and %3E; they then appear in the URL as
-- %253C and %253E, which get mapped back to %3C and %3E when passed to [[Module:accel]]. But mapping them to <
-- and > somehow works magically without any further work; they appear in the URL as < and >, and get passed to
-- [[Module:accel]] as < and >. I have no idea who along the chain of calls is doing the encoding and decoding. If
-- someone knows, please modify this comment appropriately!
local accel_char_map
local function get_accel_char_map()
accel_char_map = {
["%"] = ".",
[" "] = "_",
["_"] = u(0xFFF0),
["<"] = "<",
[">"] = ">",
}
return accel_char_map
end
local function encode_accel_param_chars(param)
return (param:gsub("[% <>_]", accel_char_map or get_accel_char_map()))
end
local function encode_accel_param(prefix, param)
if not param then
return ""
end
if type(param) == "table" then
local filled_params = {}
-- There may be gaps in the sequence, especially for translit params.
local maxindex = 0
for k in pairs(param) do
if type(k) == "number" and k > maxindex then
maxindex = k
end
end
for i = 1, maxindex do
filled_params[i] = param[i] or ""
end
-- [[Module:accel]] splits these up again.
param = concat(filled_params, "*~!")
end
-- This is decoded again by [[WT:ACCEL]].
return prefix .. encode_accel_param_chars(param)
end
local function insert_if_not_blank(list, item)
if item == "" then
return
end
insert(list, item)
end
local function get_class(lang, tr, accel, nowrap)
if not accel and not nowrap then
return ""
end
local classes = {}
if accel then
insert(classes, "form-of lang-" .. lang:getFullCode())
local form = accel.form
if form then
insert(classes, encode_accel_param_chars(form) .. "-form-of")
end
insert_if_not_blank(classes, encode_accel_param("gender-", accel.gender))
insert_if_not_blank(classes, encode_accel_param("pos-", accel.pos))
insert_if_not_blank(classes, encode_accel_param("transliteration-", accel.translit or (tr ~= "-" and tr or nil)))
insert_if_not_blank(classes, encode_accel_param("target-", accel.target))
insert_if_not_blank(classes, encode_accel_param("origin-", accel.lemma))
insert_if_not_blank(classes, encode_accel_param("origin_transliteration-", accel.lemma_translit))
if accel.no_store then
insert(classes, "form-of-nostore")
end
end
if nowrap then
insert(classes, nowrap)
end
return concat(classes, " ")
end
-- Add any decorations (left or right regular or accent qualifiers, labels or references) to a formatted term. `data`
-- is the object specifying the term, which should optionally contain:
-- * a language object in `lang`; required if any accent qualifiers or labels are given;
-- * left regular qualifiers in `q` (an array of strings or a single string); an empty array or blank string will be
-- ignored;
-- * right regular qualifiers in `qq` (an array of strings or a single string); an empty array or blank string will be
-- ignored;
-- * left accent qualifiers in `a` (an array of strings); an empty array will be ignored;
-- * right accent qualifiers in `aa` (an array of strings); an empty array will be ignored;
-- * left labels in `l` (an array of strings); an empty array will be ignored;
-- * right labels in `ll` (an array of strings); an empty array will be ignored;
-- * references in `refs`, an array either of strings (formatted reference text) or objects containing fields `text`
-- (formatted reference text) and optionally `name` and/or `group`.
-- `formatted` is the formatted version of the term itself.
local function add_text_decorations(data, formatted)
local q = data.q
if type(q) == "string" then
q = {q}
end
local qq = data.qq
if type(qq) == "string" then
qq = {qq}
end
if q and q[1] or qq and qq[1] or data.a and data.a[1] or data.aa and data.aa[1] or data.l and data.l[1] or
data.ll and data.ll[1] or data.refs and data.refs[1] then
formatted = format_decorations {
lang = data.lang,
text = formatted,
q = q,
qq = qq,
a = data.a,
aa = data.aa,
l = data.l,
ll = data.ll,
refs = data.refs,
}
end
return formatted
end
--[==[
Creates a full link, with annotations (see `[[#format_link_annotations|format_link_annotations]]`), in the style of {{tl|l}} or {{tl|m}}.
The first argument, `data`, must be a table. It contains the various elements that can be supplied as parameters to {{tl|l}} or {{tl|m}}:
{ {
term = entry_to_link_to,
alt = link_text_or_displayed_text,
lang = language_object,
sc = script_object,
track_sc = boolean,
no_nonstandard_sc_cat = boolean,
fragment = link_fragment,
id = sense_id,
genders = { "gender1", "gender2", ... },
tr = transliteration,
respect_link_tr = boolean,
ts = transcription,
gloss = gloss,
pos = part_of_speech_tag,
ng = non-gloss text,
lit = literal_translation,
no_alt_ast = boolean,
accel = {accelerated_creation_tags},
interwiki = interwiki,
pretext = "text_at_beginning" or nil,
posttext = "text_at_end" or nil,
q = { "left_qualifier1", "left_qualifier2", ...} or "left_qualifier",
qq = { "right_qualifier1", "right_qualifier2", ...} or "right_qualifier",
l = { "left_label1", "left_label2", ...},
ll = { "right_label1", "right_label2", ...},
a = { "left_accent_qualifier1", "left_accent_qualifier2", ...},
aa = { "right_accent_qualifier1", "right_accent_qualifier2", ...},
refs = { "formatted_ref1", "formatted_ref2", ...} or { {text = "text", name = "name", group = "group"}, ... },
show_decorations = boolean,
} }
Any one of the items in the `data` table may be {nil}, but an error will be shown if neither `term` nor `alt` nor `tr`
is present. Thus, calling {full_link{ term = term, lang = lang, sc = sc }}, where `term` is the page to link to (which
may have diacritics that will be stripped and/or embedded bracketed links) and `lang` is a
[[Module:languages#Language objects|language object]] from [[Module:languages]], will give a plain link similar to the
one produced by the template {{tl|l}}, and calling {full_link( { term = term, lang = lang, sc = sc }, "term" )} will
give a link similar to the one produced by the template {{tl|m}}.
The function will:
* Try to determine the script, based on the characters found in the `term` or `alt` argument, if the script was not
given. If a script is given and `track_sc` is {true}, it will check whether the input script is the same as the one
which would have been automatically generated and add the category [[:Category:LANG terms with redundant script codes]]
if yes, or [[:Category:LANG terms with non-redundant manual script codes]] if no. This should be used when the input
script object is directly determined by a template's `sc` parameter.
* Call `[[#language_link|language_link]]` on the `term` or `alt` forms, to remove diacritics in the page name, process
any embedded wikilinks and create links to Reconstruction or Appendix pages when necessary.
* Call `[[Module:script utilities#tag_text]]` to add the appropriate language and script tags to the term and
italicize terms written in the Latin script if necessary. Accelerated creation tags, as used by [[WT:ACCEL]], are
included.
* Generate a transliteration, based on the `alt` or `term` arguments, if the script is not Latin, no transliteration was
provided in `tr` and the combination of the term's language and script support automatic transliteration. The
transliteration itself will be linked if both `.respect_link_tr` is specified and the language of the term has the
`link_tr` property set for the script of the term; but not otherwise.
* Add the annotations (transliteration, gender, gloss, etc.) after the link.
* If `no_alt_ast` is specified, then the `alt` text does not need to contain an asterisk if the language is
reconstructed. This should only be used by modules which really need to allow links to reconstructions that don't
display asterisks (e.g. number boxes).
* If `pretext` or `posttext` is specified, this is text to (respectively) prepend or append to the output, directly
before processing decorations (qualifiers, labels and references). This can be used to add arbitrary extra text inside
of the decorations.
* If `show_decorations` is specified, then decorations specified in `data` (i.e. left and right qualifiers, accent
qualifiers, labels and references) will be displayed, otherwise they will be ignored. (This is because a fair amount
of code stores decorations in these fields and displays them itself, rather than expecting {full_link()} to display
them.)]==]
function export.full_link(data, face, allow_self_link, show_qualifiers)
if type(data) ~= "table" then
error("The first argument to the function full_link must be a table. "
.. "See Module:links/documentation for more information.")
elseif data.term and data.term:find("\\", nil, true) or data.alt and data.alt:find("\\", nil, true) then
track("escaped", "full_link")
end
if show_qualifiers then
-- FIXME: Eventually remove the error code. Added 2026-09-17, remove after 2026-10-17 or so.
error("Can't pass fourth parameter `show_qualifiers` any more. Set `show_decorations = true` on data.")
end
if data.show_qualifiers then
-- FIXME: Eventually remove the error code. Added 2026-09-18, remove after 2026-10-18 or so.
error("Can't set field `show_qualifiers`; use `show_decorations`")
end
-- Prevent data from being destructively modified.
local data = shallow_copy(data)
-- FIXME: this shouldn't be added to `data`, as that means the input table needs to be cloned.
data.cats = {}
-- Categorize links to "und".
local lang, cats = data.lang, data.cats
if cats and lang:getCode() == "und" then
insert(cats, "不確定的語言連結")
end
local terms = {true}
-- Generate multiple forms if applicable.
for _, param in ipairs{"term", "alt"} do
if type(data[param]) == "string" and data[param]:find("//", nil, true) then
data[param] = export.split_on_slashes(data[param])
elseif type(data[param]) == "string" and not (type(data.term) == "string" and data.term:find("//", nil, true)) then
if not data.no_generate_forms then
data[param] = lang:generateForms(data[param])
else
data[param] = {data[param]}
end
else
data[param] = {}
end
end
for _, param in ipairs{"sc", "tr", "ts"} do
data[param] = {data[param]}
end
for _, param in ipairs{"term", "alt", "sc", "tr", "ts"} do
for i in pairs(data[param]) do
terms[i] = true
end
end
-- Create the link
local output = {}
local id, no_alt_ast, srwc, accel, nevercalltr = data.id, data.no_alt_ast, data.suppress_redundant_wikilink_cat,
data.accel, data.never_call_transliteration_module
local link_tr = data.respect_link_tr and lang:link_tr(data.sc[1])
for i in ipairs(terms) do
local link
-- Is there any text to show?
if (data.term[i] or data.alt[i]) then
-- Try to detect the script if it was not provided
local display_term = data.alt[i] or data.term[i]
local best = lang:findBestScript(display_term)
-- no_nonstandard_sc_cat is intended for use in [[Module:interproject]]
if (
not data.no_nonstandard_sc_cat and
best:getCode() == "None" and
find_best_script_without_lang(display_term):getCode() ~= "None"
) then
-- insert(data.cats, "使用非標準文字的" .. data.lang:getFullName() .. "詞")
end
if not data.sc[i] then
data.sc[i] = best
-- Track uses of sc parameter.
elseif data.track_sc then
if data.sc[i]:getCode() == best:getCode() then
-- insert(data.cats, "有多餘文字代碼的" .. data.lang:getFullName() .. "詞")
else
-- insert(data.cats, "有非多餘手動文字代碼的" .. data.lang:getFullName() .. "詞")
end
end
-- If using a discouraged character sequence, add to maintenance category
if data.sc[i]:hasNormalizationFixes() == true then
if (data.term[i] and data.sc[i]:fixDiscouragedSequences(toNFC(data.term[i])) ~= toNFC(data.term[i])) or (data.alt[i] and data.sc[i]:fixDiscouragedSequences(toNFC(data.alt[i])) ~= toNFC(data.alt[i])) then
insert(cats, "Pages using discouraged character sequences")
end
end
link = simple_link(
data.term[i],
data.fragment,
data.alt[i],
lang,
data.sc[i],
id,
cats,
no_alt_ast,
srwc
)
end
-- simple_link can return nil, so check if a link has been generated.
if link then
-- Add "nowrap" class to prefixes in order to prevent wrapping after the hyphen
local nowrap
local display_term = data.alt[i] or data.term[i]
if display_term and (display_term:find("^%-") or display_term:find("^־")) then -- Hebrew maqqef -- FIXME, use hyphens from [[Module:affix]]
nowrap = "nowrap"
end
link = tag_text(link, lang, data.sc[i], face, get_class(lang, data.tr[i], accel, nowrap))
else
--[[ No term to show.
Is there at least a transliteration we can work from? ]]
link = request_script(lang, data.sc[i])
-- No link to show, and no transliteration either. Show a term request (unless it's a substrate, as they rarely take terms).
if (link == "" or (not data.tr[i]) or data.tr[i] == "-") and lang:getFamilyCode() ~= "qfa-sub" then
-- If there are multiple terms, break the loop instead.
if i > 1 then
remove(output)
break
elseif NAMESPACE ~= "Template" then
insert(data.cats, data.lang:getFullName() .. "詞請求")
end
link = "<small>[具體何詞?]</small>"
end
end
insert(output, link)
if i < #terms then insert(output, "<span class=\"Zsym mention\" style=\"font-size:100%;\">/</span>") end
end
-- TODO: Currently only handles the first transliteration, pending consensus on how to handle multiple translits for multiple forms, as this is not always desirable (e.g. traditional/simplified Chinese).
if data.tr[1] == "" or data.tr[1] == "-" then
data.tr[1] = nil
else
local phonetic_extraction = load_data("Module:links/data").phonetic_extraction
phonetic_extraction = phonetic_extraction[lang:getCode()] or phonetic_extraction[lang:getFullCode()]
if phonetic_extraction then
data.tr[1] = data.tr[1] or require(phonetic_extraction).getTranslit(export.remove_links(data.alt[1] or data.term[1]))
elseif (data.term[1] or data.alt[1]) and data.sc[1]:isTransliterated() then
-- Track whenever there is manual translit. The categories below like 'terms with redundant transliterations'
-- aren't sufficient because they only work with reference to automatic translit and won't operate at all in
-- languages without any automatic translit, like Persian and Hebrew.
if data.tr[1] then
local full_code = lang:getFullCode()
track("manual-tr", full_code)
end
if not nevercalltr then
-- Try to generate a transliteration.
local text = data.alt[1] or data.term[1]
if not link_tr then
text = export.remove_links(text, true)
end
local automated_tr = lang:transliterate(text, data.sc[1])
if automated_tr then
local manual_tr = data.tr[1]
if manual_tr then
if export.remove_links(manual_tr) == export.remove_links(automated_tr) then
insert(cats, "有多餘轉寫的" .. lang:getFullName() .. "詞")
else
-- Prevents Arabic root categories from flooding the tracking categories.
if NAMESPACE ~= "Category" then
-- insert(cats, "有非多餘手動轉寫的" .. lang:getFullName() .. "詞")
end
end
end
if not manual_tr or lang:overrideManualTranslit(data.sc[1]) then
data.tr[1] = automated_tr
end
end
end
end
end
-- Link to the transliteration entry for languages that require this
if data.tr[1] and link_tr and not data.tr[1]:match("%[%[(.-)%]%]") then
data.tr[1] = simple_link(
data.tr[1],
nil,
nil,
lang,
get_script("Latn"),
nil,
cats,
no_alt_ast,
srwc
)
elseif data.tr[1] and not link_tr then
-- Remove the pseudo-HTML tags added by remove_links.
data.tr[1] = data.tr[1]:gsub("</?link>", "")
end
if data.tr[1] and not umatch(data.tr[1], "[^%s%p]") then data.tr[1] = nil end
insert(output, export.format_link_annotations(data, face))
if data.pretext then
insert(output, 1, data.pretext)
end
if data.posttext then
insert(output, data.posttext)
end
local categories = cats[1] and format_categories(cats, lang, "-", nil, nil, data.sc) or ""
output = concat(output)
if data.show_decorations then
output = add_text_decorations(data, output)
end
return output .. categories
end
--[==[Replaces all wikilinks with their displayed text, and removes any categories. This function can be invoked either from a template or from another module.
-- Strips links: deletes category links, the targets of piped links, and any double square brackets involved in links (other than file links, which are untouched). If `tag` is set, then any links removed will be given pseudo-HTML tags, which allow the substitution functions in [[Module:languages]] to properly subdivide the text in order to reduce the chance of substitution failures in modules which scrape pages like [[Module:zh-translit]].
-- FIXME: This is quite hacky. We probably want this to be integrated into [[Module:languages]], but we can't do that until we know that nothing is pushing pipe linked transliterations through it for languages which don't have link_tr set.
* <code><nowiki>[[page|displayed text]]</nowiki></code> → <code><nowiki>displayed text</nowiki></code>
* <code><nowiki>[[page and displayed text]]</nowiki></code> → <code><nowiki>page and displayed text</nowiki></code>
* <code><nowiki>[[Category:English lemmas|WORD]]</nowiki></code> → ''(nothing)'']==]
function export.remove_links(text, tag)
if type(text) == "table" then
text = text.args[1]
end
if not text or text == "" then
return ""
end
text = text
:gsub("%[%[", "\1")
:gsub("%]%]", "\2")
-- Parse internal links for the display text.
text = text:gsub("(\1)([^\1\2]-)(\2)",
function(c1, c2, c3)
-- Don't remove files.
for _, false_positive in ipairs({"file", "image"}) do
if c2:lower():match("^" .. false_positive .. ":") then return c1 .. c2 .. c3 end
end
-- Remove categories completely.
for _, false_positive in ipairs({"category", "cat"}) do
if c2:lower():match("^" .. false_positive .. ":") then return "" end
end
-- In piped links, remove all text before the pipe, unless it's the final character (i.e. the pipe trick), in which case just remove the pipe.
c2 = c2:match("^[^|]*|(.+)") or c2:match("([^|]+)|$") or c2
if tag then
return "<link>" .. c2 .. "</link>"
else
return c2
end
end)
text = text
:gsub("\1", "[[")
:gsub("\2", "]]")
text = text:gsub("-{", "") -- l10n
text = text:gsub("}%-", "") -- l10n
return text
end
function export.section_link(link)
if type(link) ~= "string" then
error("The first argument to section_link was a " .. type(link) .. ", but it should be a string.")
elseif link:find("\\", nil, true) then
track("escaped", "section_link")
end
local target, section = get_fragment((link:gsub("_", " ")))
if not section then
error("No \"#\" delineating a section name")
end
return simple_link(
target,
section,
target .. " § " .. section
)
end
return export
4xldrh5g03bms1dzbvh3564pl8e6f1v
Module:Headword
828
1364895
9909074
9882751
2026-09-29T07:26:24Z
TongcyDai
53191
同步 enwikt:pron qualifier → decorations
9909074
Scribunto
text/plain
local export = {}
-- Named constants for all modules used, to make it easier to swap out sandbox versions.
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local en_utilities_module = "Module:en-utilities"
local gender_and_number_module = "Module:gender and number"
local headword_data_module = "Module:headword/data"
local headword_page_module = "Module:headword/page"
local links_module = "Module:links"
local load_module = "Module:load"
local pages_module = "Module:pages"
local palindromes_module = "Module:palindromes"
local scripts_module = "Module:scripts"
local scripts_data_module = "Module:scripts/data"
local script_utilities_module = "Module:script utilities"
local script_utilities_data_module = "Module:script utilities/data"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local concat = table.concat
local dump = mw.dumpObject
local insert = table.insert
local ipairs = ipairs
local max = math.max
local new_title = mw.title.new
local pairs = pairs
local require = require
local toNFC = mw.ustring.toNFC
local toNFD = mw.ustring.toNFD
local type = type
local ufind = mw.ustring.find
local ugmatch = mw.ustring.gmatch
local ugsub = mw.ustring.gsub
local umatch = mw.ustring.match
--[==[
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 debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function contains(...)
contains = require(table_module).contains
return contains(...)
end
local function encode_entities(...)
encode_entities = require(string_utilities_module).encode_entities
return encode_entities(...)
end
local function extend(...)
extend = require(table_module).extend
return extend(...)
end
local function find_best_script_without_lang(...)
find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang
return find_best_script_without_lang(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function format_genders(...)
format_genders = require(gender_and_number_module).format_genders
return format_genders(...)
end
local function format_decorations(...)
format_decorations = require(decorations_module).format_decorations
return format_decorations(...)
end
local function full_link(...)
full_link = require(links_module).full_link
return full_link(...)
end
local function get_current_L2(...)
get_current_L2 = require(pages_module).get_current_L2
return get_current_L2(...)
end
local function get_link_page(...)
get_link_page = require(links_module).get_link_page
return get_link_page(...)
end
local function get_script(...)
get_script = require(scripts_module).getByCode
return get_script(...)
end
local function is_palindrome(...)
is_palindrome = require(palindromes_module).is_palindrome
return is_palindrome(...)
end
local function language_link(...)
language_link = require(links_module).language_link
return language_link(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
local function pattern_escape(...)
pattern_escape = require(string_utilities_module).pattern_escape
return pattern_escape(...)
end
local function pluralize(...)
pluralize = require(en_utilities_module).pluralize
return pluralize(...)
end
local function process_page(...)
process_page = require(headword_page_module).process_page
return process_page(...)
end
local function remove_links(...)
remove_links = require(links_module).remove_links
return remove_links(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function tag_text(...)
tag_text = require(script_utilities_module).tag_text
return tag_text(...)
end
local function tag_transcription(...)
tag_transcription = require(script_utilities_module).tag_transcription
return tag_transcription(...)
end
local function tag_translit(...)
tag_translit = require(script_utilities_module).tag_translit
return tag_translit(...)
end
local function trim(...)
trim = require(string_utilities_module).trim
return trim(...)
end
local function ulen(...)
ulen = require(string_utilities_module).len
return ulen(...)
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 m_data
local function get_data()
m_data = load_data(headword_data_module)
return m_data
end
local script_data
local function get_script_data()
script_data = load_data(scripts_data_module)
return script_data
end
local script_utilities_data
local function get_script_utilities_data()
script_utilities_data = load_data(script_utilities_data_module)
return script_utilities_data
end
-- If set to true, categories always appear, even in non-mainspace pages
local test_force_categories = false
-- Add a tracking category to track entries with certain (unusually undesirable) properties. `track_id` is an identifier
-- for the particular property being tracked and goes into the tracking page. Specifically, this adds a link in the
-- page text to [[Wiktionary:Tracking/headword/TRACK_ID]], meaning you can find all entries with the `track_id` property
-- by visiting [[Special:WhatLinksHere/Wiktionary:Tracking/headword/TRACK_ID]].
--
-- If `lang` (a language object) is given, an additional tracking page [[Wiktionary:Tracking/headword/TRACK_ID/CODE]] is
-- linked to where CODE is the language code of `lang`, and you can find all entries in the combination of `track_id`
-- and `lang` by visiting [[Special:WhatLinksHere/Wiktionary:Tracking/headword/TRACK_ID/CODE]]. This makes it possible to
-- isolate only the entries with a specific tracking property that are in a given language. Note that if `lang`
-- references at etymology-only language, both that language's code and its full parent's code are tracked.
local function track(track_id, lang)
local tracking_page = "headword/" .. track_id
if lang and lang:hasType("etymology-only") then
debug_track{tracking_page, tracking_page .. "/" .. lang:getCode(),
tracking_page .. "/" .. lang:getFullCode()}
elseif lang then
debug_track{tracking_page, tracking_page .. "/" .. lang:getCode()}
else
debug_track(tracking_page)
end
return true
end
local function text_in_script(text, script_code)
local sc = get_script(script_code)
if not sc then
error("Internal error: Bad script code " .. script_code)
end
local characters = sc.characters
local out
if characters then
text = ugsub(text, "%W", "")
out = ufind(text, "[" .. characters .. "]")
end
if out then
return true
else
return false
end
end
local spacingPunctuation = "[%s%p]+"
--[[ List of punctuation or spacing characters that are found inside of words.
Used to exclude characters from the regex above. ]]
local wordPunc = "-#%%&@־׳״'.·*’་•:᠊"
local notWordPunc = "[^" .. wordPunc .. "]+"
--[=[
Format a term (either a head term or an inflection term) along with any decorations (left or right qualifiers, labels,
references or customized separator). `part` is the object specifying the term (and `lang` the language of the term),
which should optionally contain:
* left qualifiers in `q`, an array of strings;
* right qualifiers in `qq`, an array of strings;
* left labels in `l`, an array of strings;
* right labels in `ll`, an array of strings;
* references in `refs`, an array either of strings (formatted reference text) or objects containing fields `text`
(formatted reference text) and optionally `name` and/or `group`;
* a separator in `separator`, defaulting to " <small>或</small> " if this is not the first term (j > 1), otherwise "".
`formatted` is the formatted version of the term itself, and `j` is the index of the term.
]=]
local function format_term_with_decorations(lang, part, formatted, j)
local function part_non_empty(field)
local list = part[field]
if not list then
return nil
end
if type(list) ~= "table" then
error(("Internal error: Wrong type for `part.%s`=%s, should be \"table\""):format(field, dump(list)))
end
return list[1]
end
if part_non_empty("q") or part_non_empty("qq") or part_non_empty("l") or
part_non_empty("ll") or part_non_empty("refs") then
formatted = format_decorations {
lang = lang,
text = formatted,
q = part.q,
qq = part.qq,
l = part.l,
ll = part.ll,
refs = part.refs,
}
end
local separator = part.separator or j > 1 and " <small>或</small> " -- use "" to request no separator
if separator then
formatted = separator .. formatted
end
return formatted
end
--[==[Return true if the given head is multiword according to the algorithm used in full_headword().]==]
function export.head_is_multiword(head)
for possibleWordBreak in ugmatch(head, spacingPunctuation) do
if umatch(possibleWordBreak, notWordPunc) then
return true
end
end
return false
end
do
local function workaround_to_exclude_chars(s)
return (ugsub(s, notWordPunc, "\2%1\1"))
end
--[==[
Add appropriate links to `head`, correctly handling multiword terms. This is intended for multiword terms but can
be used for any term if you want links added to single-word terms as well. If you want to only add
links to multiword terms, first check that the term is multiword using `head_is_multiword`.
If `default` is specified, this will escape colons so that they don't get interpreted as interwiki links. This
should generally only be used when `head` is an actual pagename or is taken from a {{para|pagename}} parameter, not
when taken from a {{para|head}} parameter.
]==]
function export.add_multiword_links(head, default)
head = "\1" .. ugsub(head, spacingPunctuation, workaround_to_exclude_chars) .. "\2"
if default then
head = head
:gsub("(\1[^\2]*)\\([:#][^\2]*\2)", "%1\\\\%2")
:gsub("(\1[^\2]*)([:#][^\2]*\2)", "%1\\%2")
end
--Escape any remaining square brackets to stop them breaking links (e.g. "[citation needed]").
head = encode_entities(head, "[]", true, true)
--[=[
use this when workaround is no longer needed:
head = "[[" .. ugsub(head, WORDBREAKCHARS, "]]%1[[") .. "]]"
Remove any empty links, which could have been created above
at the beginning or end of the string.
]=]
return (head
:gsub("\1\2", "")
:gsub("[\1\2]", {["\1"] = "[[", ["\2"] = "]]"}))
end
end
local function non_categorizable(full_raw_pagename)
return full_raw_pagename:find("^Appendix:Gestures/") or
-- Unsupported titles with descriptive names.
(full_raw_pagename:find("^Unsupported titles/") and not full_raw_pagename:find("`"))
end
local function tag_text_and_add_decorations(data, head, formatted, j)
-- Add language and script wrapper.
formatted = tag_text(formatted, data.lang, head.sc, "head", nil, j == 1 and data.id or nil)
-- Add decorations (qualifiers, labels, references and separator).
return format_term_with_decorations(data.lang, head, formatted, j)
end
-- Format a headword with transliterations.
local function format_headword(data)
-- Are there non-empty transliterations?
local has_translits = false
local has_manual_translits = false
------ Format the headwords. ------
local head_parts = {}
local unique_head_parts = {}
local has_multiple_heads = not not data.heads[2]
for j, head in ipairs(data.heads) do
if head.tr or head.ts then
has_translits = true
end
if head.tr and head.tr_manual or head.ts then
has_manual_translits = true
end
local formatted
-- Apply processing to the headword, for formatting links and such.
if head.term:find("[[", nil, true) and head.sc:getCode() ~= "Image" then
formatted = language_link{term = head.term, lang = data.lang}
else
formatted = data.lang:makeDisplayText(head.term, head.sc, true)
end
local head_part = tag_text_and_add_decorations(data, head, formatted, j)
insert(head_parts, head_part)
-- If multiple heads, try to determine whether all heads display the same. To do this we need to effectively
-- rerun the text tagging and addition of decorations, using 1 for all indices.
if has_multiple_heads then
local unique_head_part
if j == 1 then
unique_head_part = head_part
else
unique_head_part = tag_text_and_add_decorations(data, head, formatted, 1)
end
unique_head_parts[unique_head_part] = true
end
end
local set_size = 0
if has_multiple_heads then
for _ in pairs(unique_head_parts) do
set_size = set_size + 1
end
end
if set_size == 1 then
head_parts = head_parts[1]
else
head_parts = concat(head_parts)
end
if has_manual_translits then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/manual-tr]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/manual-tr/LANGCODE]]
track("manual-tr", data.lang)
end
------ Format the transliterations and transcriptions. ------
local translits_formatted
if has_translits then
local translit_parts = {}
for _, head in ipairs(data.heads) do
if head.tr or head.ts then
local this_parts = {}
if head.tr then
insert(this_parts, tag_translit(head.tr, data.lang:getCode(), "head", nil, head.tr_manual))
if head.ts then
insert(this_parts, " ")
end
end
if head.ts then
insert(this_parts, "/" .. tag_transcription(head.ts, data.lang:getCode(), "head") .. "/")
end
insert(translit_parts, concat(this_parts))
end
end
translits_formatted = " (" .. concat(translit_parts, " <small>或</small> ") .. ")"
local function format_transliteration_page_link(langname)
-- l10n: 轉寫說明頁的頁名是「語言名+轉寫」
local transliteration_pagename = langname .. "轉寫"
local transliteration_page = new_title(transliteration_pagename, "Wiktionary")
if transliteration_page and transliteration_page:getContent() then
return ("[[Wiktionary:%s|•]]"):format(transliteration_pagename)
end
return nil
end
local translit_page_link = format_transliteration_page_link(data.lang:getCanonicalName())
-- If data.lang is an etymology-only language and we didn't find a translation page for it, fall back to the
-- full parent.
if not translit_page_link and data.lang:hasType("etymology-only") then
translit_page_link = format_transliteration_page_link(data.lang:getFullName())
end
if translit_page_link then
translits_formatted = " " .. translit_page_link .. translits_formatted
end
else
translits_formatted = ""
end
------ Paste heads and transliterations/transcriptions. ------
local lemma_gloss
if data.gloss then
lemma_gloss = ' <span class="ib-content qualifier-content">' .. data.gloss .. '</span>'
else
lemma_gloss = ""
end
return head_parts .. translits_formatted .. lemma_gloss
end
local function format_headword_genders(data, is_varform_only)
local retval = ""
if data.genders and data.genders[1] then
if data.gloss then
retval = ","
end
local pos_for_cat
if not data.nogendercat and not is_varform_only then
local no_gender_cat = (m_data or get_data()).no_gender_cat
if not (no_gender_cat[data.lang:getCode()] or no_gender_cat[data.lang:getFullCode()]) then
pos_for_cat = (m_data or get_data()).pos_for_gender_number_cat[data.pos_category:gsub("^reconstructed ", "")]
end
end
local text, cats = format_genders(data.genders, data.lang, pos_for_cat)
if cats then
extend(data.categories, cats)
end
retval = retval .. " " .. text
end
return retval
end
-- Forward reference
local format_inflections
local function format_inflection_parts(data, parts)
for j, part in ipairs(parts) do
if type(part) ~= "table" then
part = {term = part}
end
local partaccel = part.accel
local face = part.face or "bold"
if face ~= "bold" and face ~= "plain" and face ~= "hypothetical" then
error("The face `" .. face .. "` " .. (
(script_utilities_data or get_script_utilities_data()).faces[face] and
"should not be used for non-headword terms on the headword line." or
"is invalid."
))
end
-- Here the final part 'or data.nolinkinfl' allows to have 'nolinkinfl=true'
-- right into the 'data' table to disable inflection links of the entire headword
-- when inflected forms aren't entry-worthy, e.g.: in Vulgar Latin
local nolinkinfl = part.face == "hypothetical" or (part.nolink and track("nolink") or part.nolinkinfl) or (
data.nolink and track("nolink") or data.nolinkinfl)
local formatted
if part.label then
-- FIXME: There should be a better way of italicizing a label. As is, this isn't customizable.
formatted = "<i>" .. part.label .. "</i>"
else
-- Convert the term into a full link. Don't show a transliteration here unless enable_auto_translit is
-- requested, either at the `parts` level (i.e. per inflection) or at the `data.inflections` level (i.e.
-- specified for all inflections). This is controllable in {{head}} using autotrinfl=1 for all inflections,
-- or fNautotr=1 for an individual inflection (remember that a single inflection may be associated with
-- multiple terms). The reason for doing this is to avoid clutter in headword lines by default in languages
-- where the script is relatively straightforward to read by learners (e.g. Greek, Russian), but allow it
-- to be enabled in languages with more complex scripts (e.g. Arabic).
--
-- FIXME: With nested inflections, should we also respect `enable_auto_translit` at the top level of the
-- nested inflections structure?
local tr = part.tr or not (parts.enable_auto_translit or data.inflections.enable_auto_translit) and "-" or nil
local postprocess_annotations
if part.inflections then
postprocess_annotations = function(infldata)
insert(infldata.annotations, format_inflections(data, part.inflections))
end
end
formatted = full_link(
{
term = not nolinkinfl and part.term or nil,
alt = part.alt or (nolinkinfl and part.term or nil),
lang = part.lang or data.lang,
sc = part.sc or parts.sc or nil,
gloss = part.gloss,
pos = part.pos,
lit = part.lit,
id = part.id,
genders = part.genders,
tr = tr,
ts = part.ts,
accel = partaccel or parts.accel,
postprocess_annotations = postprocess_annotations,
},
face
)
end
parts[j] = format_term_with_decorations(part.lang or data.lang, part,
formatted, j)
end
local parts_output
if parts[1] then
parts_output = (parts.label and " " or "") .. concat(parts)
elseif parts.request then
parts_output = " <small>[請提供]</small>"
insert(data.categories, data.lang:getFullName() .. "詞條屈折請求")
else
parts_output = ""
end
-- l10n: 標籤不用斜體;屈折形式以 -{}- 隔絕字詞轉換
local parts_label = parts.label or ""
return format_term_with_decorations(data.lang, parts, parts_label .. "-{" .. parts_output .. "}-", 1)
end
-- Format the inflections following the headword or nested after a given inflection. Declared local above.
function format_inflections(data, inflections)
if inflections and inflections[1] then
-- Format each inflection individually.
for key, infl in ipairs(inflections) do
inflections[key] = format_inflection_parts(data, infl)
end
return concat(inflections, ",")
else
return ""
end
end
-- Format the top-level inflections following the headword. Currently this just adds parens around the
-- formatted comma-separated inflections in `data.inflections`.
local function format_top_level_inflections(data)
local result = format_inflections(data, data.inflections)
if result ~= "" then
return " (" .. result .. ")"
else
return result
end
end
-- Forward reference
local check_red_link_inflections
-- Check a single inflection (which consists of a label and zero or more terms, each possibly with nested inflections)
-- for red links. If so, insert a red-link category based on `plpos` (the plural part of speech to insert in the
-- category), stop further processing, and return true. If no red links found, return false.
local function check_red_link_inflection_parts(data, parts, plpos)
for _, part in ipairs(parts) do
if type(part) ~= "table" then
part = {term = part}
end
local term = part.term
if term and not term:find("%[%[") then
local stripped_physical_term = get_link_page(term, data.lang, part.sc or parts.sc or nil)
if stripped_physical_term then
local title = mw.title.new(stripped_physical_term)
if title and not title:getContent() then
insert(data.categories, "標題行有紅鏈的" .. data.lang:getFullName() .. plpos)
return true
end
end
end
if part.inflections then
if check_red_link_inflections(data, part.inflections, plpos) then
return true
end
end
end
return false
end
-- Check a set of inflections (each of which describes a single inflection of the term, such as feminine or plural, and
-- consists of a label and zero or more terms, each possibly with nested inflections) for red links. If so, insert a
-- red-link category based on `plpos` (the plural part of speech to insert in the category), stop further processing,
-- and return true. If no red links found, return false.
function check_red_link_inflections(data, inflections, plpos)
if inflections and inflections[1] then
-- Check each inflection individually.
for key, infl in ipairs(inflections) do
if check_red_link_inflection_parts(data, infl, plpos) then
return true
end
end
end
return false
end
-- Check the top-level inflections in `data.inflections`, along with any nested inflections, for red links. If so,
-- insert a red-link category based on `plpos` (the plural part of speech to insert in the category), stop further
-- processing, and return true. If no red links found, return false.
local function check_red_link_inflections_top_level(data, plpos)
return check_red_link_inflections(data, data.inflections, plpos)
end
--[==[
Returns the plural form of `pos`, a raw part of speech input, which could be singular or
plural. Irregular plural POS are taken into account (e.g. "kanji" pluralizes to
"kanji").
]==]
function export.pluralize_pos(pos)
-- l10n: 漢語詞類名稱無複數形式
return (m_data or get_data()).irregular_plurals[pos] or
pos:sub(-1) == "s" and pos or
pos
end
--[==[
Return "lemma" if the given POS is a lemma, "non-lemma form" if a non-lemma form, or nil
if unknown. The POS passed in must be in its plural form ("nouns", "prefixes", etc.).
If you have a POS in its singular form, call {export.pluralize_pos()} above to pluralize it
in a smart fashion that knows when to add "-s" and when to add "-es", and also takes
into account any irregular plurals.
If `best_guess` is given and the POS is in neither the lemma nor non-lemma list, guess
based on whether it ends in " forms"; otherwise, return nil.
]==]
function export.pos_lemma_or_nonlemma(plpos, best_guess)
local m_headword_data = m_data or get_data()
local isLemma = m_headword_data.lemmas
-- Is it a lemma category?
if isLemma[plpos] then
return "詞元"
end
local plpos_no_recon = plpos:gsub("^reconstructed ", "")
if isLemma[plpos_no_recon] then
return "詞元"
end
-- Is it a nonlemma category?
local isNonLemma = m_headword_data.nonlemmas
if isNonLemma[plpos] or isNonLemma[plpos_no_recon] then
return "非詞元形式"
end
local plpos_no_mut = plpos:gsub("^mutated ", "")
if isLemma[plpos_no_mut] or isNonLemma[plpos_no_mut] then
return "非詞元形式"
elseif best_guess then
return (plpos:find("變格形$") or plpos:find("變位形式$")) and "非詞元形式" or "詞元"
else
return nil
end
end
--[==[
Canonicalize a part of speech as specified in 2= in {{tl|head}}. This checks for POS aliases and non-lemma form
aliases ending in 'f', and then pluralizes if the POS term does not have an invariable plural.
]==]
function export.canonicalize_pos(pos)
-- FIXME: Temporary code to throw an error for alias 'pre' (= preposition) that will go away.
if pos == "pre" then
-- Don't throw error on 'pref' as it's an alias for "prefix".
error("POS 'pre' for 'preposition' no longer allowed as it's too ambiguous; use 'prep'")
end
-- Likewise for pro = pronoun.
if pos == "pro" or pos == "prof" then
error("POS 'pro' for 'pronoun' no longer allowed as it's too ambiguous; use 'pron'")
end
local m_headword_data = m_data or get_data()
if m_headword_data.pos_aliases[pos] then
pos = m_headword_data.pos_aliases[pos]
elseif pos:sub(-1) == "f" then
pos = pos:sub(1, -2)
pos = (m_headword_data.pos_aliases[pos] or pos) .. "變格形"
end
return export.pluralize_pos(pos)
end
-- Find and return the maximum index in the array `data[element]` (which may have gaps in it), and initialize it to a
-- zero-length array if unspecified. Check to make sure all keys are numeric (other than "maxindex", which is set by
-- [[Module:parameters]] for list parameters), all values are strings, and unless `allow_blank_string` is given,
-- no blank (zero-length) strings are present.
local function init_and_find_maximum_index(data, element, allow_blank_string)
local maxind = 0
if not data[element] then
data[element] = {}
end
local typ = type(data[element])
if typ ~= "table" then
error(("Internal error: In full_headword(), `data.%s` must be an array but is a %s"):format(element, typ))
end
for k, v in pairs(data[element]) do
if k ~= "maxindex" then
if type(k) ~= "number" then
error(("Internal error: Unrecognized non-numeric key '%s' in `data.%s`"):format(k, element))
end
if k > maxind then
maxind = k
end
if v then
if type(v) ~= "string" then
error(("Internal error: For key '%s' in `data.%s`, value should be a string but is a %s"):format(k, element, type(v)))
end
if not allow_blank_string and v == "" then
error(("Internal error: For key '%s' in `data.%s`, blank string not allowed; use 'false' for the default"):format(k, element))
end
end
end
end
return maxind
end
--[==[
-- Add the page to various maintenance categories for the language and the
-- whole page. These are placed in the headword somewhat arbitrarily, but
-- mainly because headword templates are mandatory for entries (meaning that
-- in theory it provides full coverage).
--
-- This is provided as an external entry point so that modules which transclude
-- information from other entries (such as {{tl|ja-see}}) can take advantage
-- of this feature as well, because they are used in place of a conventional
-- headword template.]==]
do
-- Handle any manual sortkeys that have been specified in raw categories
-- by tracking if they are the same or different from the automatically-
-- generated sortkey, so that we can track them in maintenance
-- categories.
local function handle_raw_sortkeys(tbl, sortkey, page, lang, lang_cats)
sortkey = sortkey or lang:makeSortKey(page.pagename)
-- If there are raw categories with no sortkey, then they will be
-- sorted based on the default MediaWiki sortkey, so we check against
-- that.
if tbl == true then
if page.raw_defaultsort ~= sortkey then
-- insert(lang_cats, "有非多餘非自動排序鍵的" .. lang:getFullName() .. "詞")
end
return
end
local redundant, different
for k in pairs(tbl) do
if k == sortkey then
redundant = true
else
different = true
end
end
if redundant then
-- insert(lang_cats, "有多餘排序鍵的" .. lang:getFullName() .. "詞")
end
if different then
-- insert(lang_cats, "有非多餘非自動排序鍵的" .. lang:getFullName() .. "詞")
end
return sortkey
end
function export.maintenance_cats(page, lang, lang_cats, page_cats)
extend(page_cats, page.cats)
lang = lang:getFull() -- since we are just generating categories
local canonical = lang:getCanonicalName()
local tbl = page.wikitext_topic_cat[lang:getCode()]
local sortkey = nil
if tbl then
sortkey = handle_raw_sortkeys(tbl, sortkey, page, lang, lang_cats)
-- insert(lang_cats, "使用原始標記加入主題分類的" .. canonical .. "詞條")
end
tbl = page.wikitext_langname_cat[canonical]
if tbl then
handle_raw_sortkeys(tbl, sortkey, page, lang, lang_cats)
-- insert(lang_cats, "使用原始標記加入語言名稱分類的" .. canonical .. "詞條")
end
if get_current_L2() ~= canonical then
-- insert(lang_cats, "有不正確語言標頭的" .. canonical .. "詞條")
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/incorrect language header]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/incorrect language header/LANGCODE]]
track("incorrect language header", lang)
end
end
end
--[==[This is the primary external entry point.
{{lua|full_headword(data)}}
This is used by {{temp|head}} and various language-specific headword templates (e.g. {{temp|ru-adj}} for Russian adjectives, {{temp|de-noun}} for German nouns, etc.) to display an entire headword line.
See [[#Further explanations for full_headword()]]
]==]
function export.full_headword(data)
-- Prevent data from being destructively modified.
data = shallow_copy(data)
------------ 1. Basic checks for old-style (multi-arg) calling convention. ------------
if data.getCanonicalName then
error("Internal error: In full_headword(), the first argument `data` needs to be a Lua object (table) of properties, not a language object")
end
if not data.lang or type(data.lang) ~= "table" or not data.lang.getCode then
error("Internal error: In full_headword(), the first argument `data` needs to be a Lua object (table) and `data.lang` must be a language object")
end
if data.id and type(data.id) ~= "string" then
error("Internal error: The id in the data table should be a string.")
end
------------ 2. Initialize pagename etc. ------------
local langcode = data.lang:getCode()
local full_langcode = data.lang:getFullCode()
local langname = data.lang:getCanonicalName()
local full_langname = data.lang:getFullName()
local raw_pagename = data.pagename
local page
local m_headword_data = m_data or get_data()
if raw_pagename and raw_pagename ~= m_headword_data.pagename then -- for testing, doc pages, etc.
-- data.pagename is often set on documentation and test pages through the pagename= parameter of various
-- templates, to emulate running on that page. Having a large number of such test templates on a single
-- page often leads to timeouts, because we fetch and parse the contents of each page in turn. However,
-- we don't really need to do that and can function fine without fetching and parsing the contents of a
-- given page, so turn off content fetching/parsing (and also setting the DEFAULTSORT key through a parser
-- function, which is *slooooow*) in certain namespaces where test and documentation templates are likely to
-- be found and where actual content does not live (User, Template, Module).
local actual_namespace = m_headword_data.page.namespace
local no_fetch_content = actual_namespace == "User" or actual_namespace == "Template" or
actual_namespace == "Module"
page = process_page(raw_pagename, no_fetch_content)
else
page = m_headword_data.page
end
local namespace = page.namespace
if data.altform then
-- Temporary tracking for use of old altform=
track("altform", data.lang)
end
local is_varform_only = data.var and data.var ~= "both"
local is_varform_both = data.var == "both"
------------ 3. Initialize `data.heads` table; if old-style, convert to new-style. ------------
if type(data.heads) == "table" and type(data.heads[1]) == "table" then
-- new-style
if data.translits or data.transcriptions then
error("Internal error: In full_headword(), if `data.heads` is new-style (array of head objects), `data.translits` and `data.transcriptions` cannot be given")
end
else
-- convert old-style `heads`, `translits` and `transcriptions` to new-style
local maxind = max(
init_and_find_maximum_index(data, "heads"),
init_and_find_maximum_index(data, "translits", true),
init_and_find_maximum_index(data, "transcriptions", true)
)
for i = 1, maxind do
data.heads[i] = {
term = data.heads[i],
tr = data.translits[i],
ts = data.transcriptions[i],
}
end
end
-- Make sure there's at least one head.
if not data.heads[1] then
data.heads[1] = {}
end
------------ 4. Initialize and validate `data.categories` and `data.whole_page_categories`, and determine `pos_category` if not given, and add basic categories. ------------
init_and_find_maximum_index(data, "categories")
init_and_find_maximum_index(data, "whole_page_categories")
local pos_category_already_present = false
if data.categories[1] then
local escaped_langname = pattern_escape(full_langname)
local matches_lang_pattern = "^" .. escaped_langname
for _, cat in ipairs(data.categories) do
-- Does the category begin with the language name? If not, tag it with a tracking category.
if not cat:find(matches_lang_pattern) then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/no lang category]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/no lang category/LANGCODE]]
track("no lang category", data.lang)
end
end
-- If `pos_category` not given, try to infer it from the first specified category. If this doesn't work, we
-- throw an error below.
if not data.pos_category and data.categories[1]:find(matches_lang_pattern) then
data.pos_category = data.categories[1]:gsub(matches_lang_pattern, "")
-- Optimization to avoid inserting category already present.
pos_category_already_present = true
end
end
if not data.pos_category then
error("Internal error: `data.pos_category` not specified and could not be inferred from the categories given in "
.. "`data.categories`. Either specify the plural part of speech in `data.pos_category` "
.. "(e.g. \"proper nouns\") or ensure that the first category in `data.categories` is formed from the "
.. "language's canonical name plus the plural part of speech (e.g. \"Norwegian Bokmål proper nouns\")."
)
end
-- Insert a category at the beginning for the part of speech unless it's already present or `data.noposcat` given.
if not pos_category_already_present and not data.noposcat and not is_varform_only then
local pos_category = full_langname .. data.pos_category
-- FIXME: [[User:Theknightwho]] Why is this special case here? Please add an explanatory comment.
if pos_category ~= "Translingual Han characters" then
insert(data.categories, 1, pos_category)
end
end
-- Try to determine whether the part of speech refers to a lemma or a non-lemma form; if we can figure this out,
-- add an appropriate category.
local postype = export.pos_lemma_or_nonlemma(data.pos_category)
if not postype then
-- We don't know what this category is, so tag it with a tracking category.
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/unrecognized pos]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/unrecognized pos/LANGCODE]]
track("unrecognized pos", data.lang)
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/unrecognized pos/POS]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/unrecognized pos/POS/LANGCODE]]
track("unrecognized pos/pos/" .. data.pos_category, data.lang)
elseif not data.noposcat and not is_varform_only then
insert(data.categories, 1, full_langname .. postype)
end
-- Categorize variant forms into 'variant lemmas' or 'variant non-lemma forms'. Originally proposed in
-- [[Wiktionary:Beer parlour/2024/June#Decluttering the altform mess]] as 'alternative forms'; renamed in
-- [[Wiktionary:Beer parlour/2026/July#Renaming the "alternative forms" categories]].
if (is_varform_only or is_varform_both) and postype then
insert(data.categories, 1, full_langname .. "變體" .. postype)
end
------------ 5. Create a default headword, and add links to multiword page names. ------------
-- Determine if this is an "anti-asterisk" term, i.e. an attested term in a language that must normally be
-- reconstructed.
local is_anti_asterisk = data.heads[1].term and data.heads[1].term:find("^!!")
local lang_reconstructed = data.lang:hasType("reconstructed")
if is_anti_asterisk then
if not lang_reconstructed then
error("Anti-asterisk feature (head= beginning with !!) can only be used with reconstructed languages")
end
lang_reconstructed = false
end
-- Determine if term is reconstructed
local is_reconstructed = namespace == "Reconstruction" or lang_reconstructed
-- Create a default headword based on the pagename, which is determined in
-- advance by the data module so that it only needs to be done once.
local default_head = page.pagename
-- Add links to multi-word page names when appropriate
if not (is_reconstructed or data.nolinkhead) then
local no_links = m_headword_data.no_multiword_links
if not (no_links[langcode] or no_links[full_langcode]) and export.head_is_multiword(default_head) then
default_head = export.add_multiword_links(default_head, true)
end
end
if is_reconstructed then
default_head = "*" .. default_head
end
------------ 6. Check the namespace against the language type. ------------
if namespace == "" then
if lang_reconstructed then
error("Entries in " .. langname .. " must be placed in the Reconstruction: namespace")
elseif data.lang:hasType("appendix-constructed") then
error("Entries in " .. langname .. " must be placed in the Appendix: namespace")
end
elseif namespace == "Citations" or namespace == "Thesaurus" then
error("Headword templates should not be used in the " .. namespace .. ": namespace.")
end
------------ 7. Fill in missing values in `data.heads`. ------------
-- True if any script among the headword scripts has spaces in it.
local any_script_has_spaces = false
-- True if any term has a redundant head= param.
local has_redundant_head_param = false
for _, head in ipairs(data.heads) do
------ 7a. If missing head, replace with default head.
if not head.term then
head.term = default_head
elseif head.term == default_head then
has_redundant_head_param = true
elseif is_anti_asterisk and head.term == "!!" then
-- If explicit head=!! is given, it's an anti-asterisk term and we fill in the default head.
head.term = "!!" .. default_head
elseif head.term:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
head.term = default_head .. head.term
end
head.term_no_initial_bang_bang = is_anti_asterisk and head.term:sub(3) or head.term
if is_reconstructed then
local head_term = head.term
if head_term:find("%[%[") then
head_term = remove_links(head_term)
end
if head_term:sub(1, 1) ~= "*" then
error("The headword '" .. head_term .. "' must begin with '*' to indicate that it is reconstructed.")
end
end
------ 7b. Try to detect the script(s) if not provided. If a per-head script is provided, that takes precedence,
------ otherwise fall back to the overall script if given. If neither given, autodetect the script.
local auto_sc = data.lang:findBestScript(head.term)
if (
auto_sc:getCode() == "None" and
find_best_script_without_lang(head.term):getCode() ~= "None"
) then
insert(data.categories, "以非標準文字書寫的" .. full_langname .. "詞")
end
if not (head.sc or data.sc) then -- No script code given, so use autodetected script.
head.sc = auto_sc
else
if not head.sc then -- Overall script code given.
head.sc = data.sc
end
-- Track uses of sc parameter.
if head.sc:getCode() == auto_sc:getCode() then
track("redundant script code", data.lang)
if not data.no_script_code_cat then
-- insert(data.categories, "有多餘文字代碼的" .. full_langname .. "詞")
end
else
track("non-redundant manual script code", data.lang)
if not data.no_script_code_cat then
-- insert(data.categories, "有非多餘手動文字代碼的" .. full_langname .. "詞")
end
end
end
-- If using a discouraged character sequence, add to maintenance category.
if head.sc:hasNormalizationFixes() == true then
local composed_head = toNFC(head.term)
if head.sc:fixDiscouragedSequences(composed_head) ~= composed_head then
insert(data.whole_page_categories, "使用不建議字元序列的頁面")
end
end
any_script_has_spaces = any_script_has_spaces or head.sc:hasSpaces()
------ 7c. Create automatic transliterations for any non-Latin headwords without manual translit given
------ (provided automatic translit is available, e.g. not in Persian or Hebrew).
-- Make transliterations
head.tr_manual = nil
-- Try to generate a transliteration if necessary
if head.tr == "-" then
head.tr = nil
else
local notranslit = m_headword_data.notranslit
if not (notranslit[langcode] or notranslit[full_langcode]) and head.sc:isTransliterated() then
head.tr_manual = not not head.tr
local text = head.term_no_initial_bang_bang
if not data.lang:link_tr(head.sc) then
text = remove_links(text)
end
local automated_tr = data.lang:transliterate(text, head.sc)
if automated_tr then
local manual_tr = head.tr
if manual_tr then
if remove_links(manual_tr) == remove_links(automated_tr) then
insert(data.categories, "有多餘轉寫的" .. full_langname .. "詞")
else
insert(data.categories, "手動轉寫與自動轉寫不同的" .. full_langname .. "詞")
end
end
if not manual_tr then
head.tr = automated_tr
end
end
-- There is still no transliteration?
-- Add the entry to a cleanup category.
if not head.tr then
head.tr = "<small>需要補充轉寫</small>"
-- FIXME: No current support for 'Request for transliteration of Classical Persian terms' or similar.
-- Consider adding this support in [[Module:category tree/poscatboiler/data/entry maintenance]].
insert(data.categories, full_langname .. "詞轉寫請求")
else
-- Otherwise, trim it.
head.tr = trim(head.tr)
end
end
end
-- Link to the transliteration entry for languages that require this.
if head.tr and data.lang:link_tr(head.sc) then
head.tr = full_link{
term = head.tr,
lang = data.lang,
sc = get_script("Latn"),
tr = "-"
}
end
end
------------ 8. Maybe tag the title with the appropriate script code, using the `display_title` mechanism. ------------
-- Assumes that the scripts in "toBeTagged" will never occur in the Reconstruction namespace.
-- (FIXME: Don't make assumptions like this, and if you need to do so, throw an error if the assumption is violated.)
-- Avoid tagging ASCII as Hani even when it is tagged as Hani in the headword, as in [[check]]. The check for ASCII
-- might need to be expanded to a check for any Latin characters and whitespace or punctuation.
local display_title
-- Where there are multiple headwords, use the script for the first. This assumes the first headword is similar to
-- the pagename, and that headwords that are in different scripts from the pagename aren't first. This seems to be
-- about the best we can do (alternatively we could potentially do script detection on the pagename).
local dt_script = data.heads[1].sc
local dt_script_code = dt_script:getCode()
local page_non_ascii = namespace == "" and not page.pagename:find("^[%z\1-\127]+$")
local unsupported_pagename, unsupported = page.full_raw_pagename:gsub("^不支援的頁面名稱/", "")
if unsupported == 1 and page.unsupported_titles[unsupported_pagename] then
display_title = '不支援的頁面名稱/<span class="' .. dt_script_code .. '">' .. page.unsupported_titles[unsupported_pagename] .. '</span>'
elseif page_non_ascii and m_headword_data.toBeTagged[dt_script_code]
or (dt_script_code == "Jpan" and (text_in_script(page.pagename, "Hira") or text_in_script(page.pagename, "Kana")))
or (dt_script_code == "Kore" and text_in_script(page.pagename, "Hang")) then
display_title = '<span class="' .. dt_script_code .. '">' .. page.full_raw_pagename .. '</span>'
-- Keep Han entries region-neutral in the display title.
elseif page_non_ascii and (dt_script_code == "Hant" or dt_script_code == "Hans") then
display_title = '<span class="Hani">' .. page.full_raw_pagename .. '</span>'
elseif namespace == "Reconstruction" then
local matched
display_title, matched = ugsub(
page.full_raw_pagename,
"^(Reconstruction:[^/]+/)(.+)$",
function(before, term)
return before .. tag_text(term, data.lang, dt_script)
end
)
if matched == 0 then
display_title = nil
end
end
-- FIXME: Generalize this.
-- If the current language uses Aran (Nastaliq), e.g. Urdu, and there's more than one language on the page, don't
-- set the display title because we don't want Nastaliq for terms that also exist in other languages that don't
-- display in Nastaliq (e.g. Arabic or Persian). Because the word "Urdu" occurs near the end of the alphabet, Urdu
-- fonts tend to override the fonts of other languages. FIXME: This is checking for more than one language on the
-- page but instead needs to check if there are any languages using scripts other than Aran.
if dt_script_code == "Aran" and page.L2_list.n > 1 then
display_title = nil
end
if display_title then
mw.getCurrentFrame():callParserFunction(
"DISPLAYTITLE",
display_title
)
end
------------ 9. Insert additional categories. ------------
if data.force_cat_output then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/force cat output]]
track("force cat output")
end
if has_redundant_head_param then
if not data.no_redundant_head_cat then
-- This is not the right way to go about this; too many exceptions and problems due to language-specific headword
-- handling customization. If we want this, it should be opt-in by a given language passing in the default headword.
-- insert(data.categories, "有多餘標題行參數的" .. full_langname .. "詞")
end
end
-- If the first head is multiword (after removing links), maybe insert into "LANG multiword terms".
if not data.nomultiwordcat and not is_varform_only and any_script_has_spaces and postype == "詞元" then
local no_multiword_cat = m_headword_data.no_multiword_cat
if not (no_multiword_cat[langcode] or no_multiword_cat[full_langcode]) then
-- Check for spaces or hyphens, but exclude prefixes and suffixes.
-- Use the pagename, not the head= value, because the latter may have extra
-- junk in it, e.g. superscripted text that throws off the algorithm.
local no_hyphen = m_headword_data.hyphen_not_multiword_sep
-- Exclude hyphens if the data module states that they should for this language.
local checkpattern = (no_hyphen[langcode] or no_hyphen[full_langcode]) and ".[%s፡]." or ".[%s%-፡]."
local is_multiword = umatch(page.pagename, checkpattern)
if is_multiword and not non_categorizable(page.full_raw_pagename) then
insert(data.categories, full_langname .. "詞組")
elseif not is_multiword then
local long_word_threshold = m_headword_data.long_word_thresholds[langcode] or
m_headword_data.long_word_thresholds[full_langcode]
if long_word_threshold and ulen(page.pagename) >= long_word_threshold then
insert(data.categories, full_langname .. "長單詞")
end
end
end
end
-- Determine whether to insert a category 'LANGNAME POS in SCRIPT'. If there are multiple heads, we may need to check
-- each head, as the heads may (theoretically) have different scripts.
local default_sccat = m_headword_data.default_sccat
if data.sccat or not is_varform_only and (default_sccat[langcode] or langcode ~= full_langcode and default_sccat[full_langcode]) then
local function needs_sccat(sccat_entry, sc)
if sccat_entry == true or not sccat_entry then
return sccat_entry
end
if type(sccat_entry) == "table" then
local in_list = contains(sccat_entry, sc:getCode())
if sccat_entry[1] == "not" then
in_list = not in_list
end
return in_list
end
return nil
end
for _, head in ipairs(data.heads) do
-- First check the `sccat` specified at the {{head}} level.
local this_needs_sccat = needs_sccat(data.sccat, head.sc)
-- If that wasn't given, check the default sccat at the language level for the lang code.
if this_needs_sccat == nil and not is_varform_only then
this_needs_sccat = needs_sccat(default_sccat[langcode], head.sc)
end
-- If that wasn't found and the lang code is an etym code, check the default sccat at the parent language level.
if this_needs_sccat == nil and not is_varform_only and langcode ~= full_langcode then
this_needs_sccat = needs_sccat(default_sccat[full_langcode], head.sc)
end
if this_needs_sccat then
insert(data.categories, "以" .. head.sc:getDisplayForm(data.lang) .. "書寫的" ..
full_langname .. data.pos_category)
end
end
end
-- Reconstructed terms often use weird combinations of scripts and realistically aren't spelled so much as notated.
if namespace ~= "Reconstruction" and not is_varform_only then
-- Map from languages to a string containing the characters to ignore when considering whether a term has
-- multiple written scripts in it. Typically these are Greek or Cyrillic letters used for their phonetic
-- values.
local characters_to_ignore = {
["aaq"] = "αάὰ", -- Penobscot (Algonquian)
["acy"] = "δθ", -- Cypriot Arabic
["aez"] = "β", -- Aeka (Trans-New Guinea)
["anc"] = "γ", -- Ngas (Chadic/Afroasiatic)
["aou"] = "χ", -- A'ou (Kra-Dai)
["art-blk"] = "ч", -- Bolak (conlang)
["awg"] = "β", -- Anguthimri (Pama-Nyungan)
["az"] = "ь", -- Azerbaijani (Turkic; Yañalif Latin spelling, c. 1928 - 1938)
["ba"] = "ь", -- Bashkir (Turkic; Yañalif Latin spelling, c. 1928 - 1938)
["bhp"] = "β", -- Bima (Austronesian)
["bjz"] = "β", -- Baruga (Trans-New Guinea)
["byk"] = "θ", -- Biao (Kra-Dai)
["cdy"] = "θ", -- Chadong (Kra-Dai)
["chp"] = "θ", -- Chipewyan (Athabaskan)
["cjh"] = "χ", -- Upper Chehalis (Salishan)
["clm"] = "χ", -- Klallam (Salishan)
["col"] = "χ", -- Colombia-Wenatchi (Salishan)
["coo"] = "χθ", -- Comox (Salishan)
["crx"] = "θ", -- Carrier (Athabaskan)
["ets"] = "θ", -- Yekhee (Edoid/Niger-Congo)
["ett"] = "χ", -- Etruscan (isolate; in romanizations)
["fla"] = "χ", -- Montana Salish (Salishan)
["grt"] = "་", -- Garo (South Asian Sino-Tibetan)
["gmw-gts"] = "χ", -- Gottscheerish (Bavarian variant spoken in Slovenia)
["hur"] = "χθ", -- Halkomelem (Salishan)
["itc-psa"] = "f", -- Pre-Samnite (Italic; normally written in Greek)
["izh"] = "ь", -- Ingrian (Finnic)
["kic"] = "θ", -- Kickapoo (Algonquian)
["kk"] = "ь", -- Kazakh (Turkic; Yañalif Latin spelling, c. 1928 - 1938)
["ky"] = "ь", -- Kyrgyz (Turkic; Yañalif Latin spelling, c. 1928 - 1938)
["lil"] = "χ", -- Lillooet (Salishan)
["lsi"] = "ꓹ", -- Lashi (Lolo-Burmese/Sino-Tibetan; represents a glottal stop)
["mhz"] = "β", -- Mor (Austronesian)
["mqn"] = "β", -- Moronene (Austronesian)
["neg"]= "ӡā", -- Negidal (Tungusic; normally in Cyrillic)
["oka"] = "χ", -- Okanagan (Salishan)
["ole"] = "θ", -- Olekha (Sino-Tibetan)
["oui"] = "γβ", -- Old Uyghur (Turkic; FIXME: others? E.g. Greek delta (δ)?)
["pox"] = "χ", -- Polabian (West Slavic)
["rif"] = "ε", -- Tarifit (Berber)
["rom"] = "Θθ", -- Romani (Indic: International Standard; two different thetas???)
["rpn"] = "β", -- Repanbitip (Austronesian)
["sah"] = "ь", -- Yakut (Turkic; 1929 - 1939 Latin spelling)
["sit-jap"] = "χ", -- Japhug (Sino-Tibetan)
["sjw"] = "θ", -- Shawnee (Algonquian)
["squ"] = "χ", -- Squamish (Salishan)
["str"] = "χθ", -- Saanich (Salishan)
["teh"] = "χ", -- Tehuelche (Chonan; spoken in Argentina)
["tep"] = "η", -- Tepecano (Uto-Aztecan)
["thp"] = "χ", -- Thompson (Salishan)
["tk"] = "ь", -- Turkmen (Turkic; Yañalif Latin spelling, c. 1928 - 1938)
["tt"] = "ь", -- Kazakh (Turkic; Yañalif Latin spelling, c. 1928 - 1938)
["twa"] = "χ", -- Twana (Salishan)
["wbl"] = "ы", -- Wakhi (Iranian)
["xbc"] = "ϸ", -- Bactrian (Iranian; represents š; normally written in Greek)
["yha"] = "θ", -- Baha (Kra-Dai)
["za"] = "зч", -- Zhuang (Tai/Kra-Dai); 1957-1982 alphabet used two Cyrillic letters (as well as some others like
-- ƃ, ƅ, ƨ, ɯ and ɵ that look like Cyrillic or Greek but are actually Latin)
["zlw-slv"] = "χђћ", -- Slovincian (West Slavic; FIXME: χ is Greek, the other two are Cyrillic, but I'm not sure
-- the currect characters are being chosen in the entry names)
["zng"] = "θ", -- Mang (Mon-Khmer)
["ztp"] = "θ", -- Loxicha Zapotec (Zapotecan)
}
-- Determine how many real scripts are found in the pagename, where we exclude symbols and such. We exclude
-- scripts whose `character_category` is false as well as Zmth (mathematical notation symbols), which has a
-- category of "Mathematical notation symbols". When counting scripts, we need to elide language-specific
-- variants because e.g. Beng and as-Beng have slightly different characters but we don't want to consider them
-- two different scripts (e.g. [[এৰ]] has two characters which are detected respectively as Beng and as-Beng).
local seen_scripts = {}
local num_seen_scripts = 0
local num_loops = 0
local canon_pagename = page.pagename
local ch_to_ignore = characters_to_ignore[full_langcode]
if ch_to_ignore then
canon_pagename = ugsub(canon_pagename, "[" .. ch_to_ignore .. "]", "")
end
while true do
if canon_pagename == "" or num_seen_scripts >= 2 or num_loops >= 10 then
break
end
-- Make sure we don't get into a loop checking the same script over and over again; happens with e.g. [[ᠪᡳ]]
num_loops = num_loops + 1
local pagename_script = find_best_script_without_lang(canon_pagename, "None only as last resort")
local script_chars = pagename_script.characters
if not script_chars then
-- we are stuck; this happens with None
break
end
local script_code = pagename_script:getCode()
local replaced
canon_pagename, replaced = ugsub(canon_pagename, "[" .. script_chars .. "]", "")
if (
replaced and
script_code ~= "Zmth" and
(script_data or get_script_data())[script_code] and
script_data[script_code].character_category ~= false
) then
script_code = script_code:gsub("^.-%-", "")
if not seen_scripts[script_code] then
seen_scripts[script_code] = true
num_seen_scripts = num_seen_scripts + 1
end
end
end
if num_seen_scripts > 1 then
insert(data.categories, "用多種文字書寫的" .. full_langname .. "詞")
end
end
-- Categorise for unusual characters. Takes into account combining characters, so that we can categorise for characters with diacritics that aren't encoded as atomic characters (e.g. U̠). These can be in two formats: single combining characters (i.e. character + diacritic(s)) or double combining characters (i.e. character + diacritic(s) + character). Each can have any number of diacritics.
local standard = data.lang:getStandardCharacters()
if not is_varform_only and standard and not non_categorizable(page.full_raw_pagename) then
local function char_category(char)
local specials = {
["#"] = "number sign",
["("] = "parentheses",
[")"] = "parentheses",
["<"] = "angle brackets",
[">"] = "angle brackets",
["["] = "square brackets",
["]"] = "square brackets",
["_"] = "underscore",
["{"] = "braces",
["|"] = "vertical line",
["}"] = "braces",
["ß"] = "ẞ",
["\205\133"] = "", -- this is UTF-8 for U+0345 ( ͅ)
["\239\191\189"] = "replacement character",
}
char = toNFD(char)
:gsub(".[\128-\191]*", function(m)
local new_m = specials[m]
new_m = new_m or m:uupper()
return new_m
end)
return toNFC(char)
end
if full_langcode ~= "hi" and full_langcode ~= "lo" then
local standard_chars_scripts = {}
for _, head in ipairs(data.heads) do
standard_chars_scripts[head.sc:getCode()] = true
end
-- Iterate over the scripts, in case there is more than one (as they can have different sets of standard characters).
for code in pairs(standard_chars_scripts) do
local sc_standard = data.lang:getStandardCharacters(code)
if sc_standard then
if page.pagename_len > 1 then
local explode_standard = {}
local function explode(char)
explode_standard[char] = true
return ""
end
local sc_standard_exploded = ugsub(sc_standard, page.comb_chars.combined_double, explode)
-- The following is correct; it relies on side-effecing the explode_standard[] table.
ugsub(sc_standard_exploded, page.comb_chars.combined_single, explode):gsub(".[\128-\191]*", explode)
local num_cat_inserted
for char in pairs(page.explode_pagename) do
if not explode_standard[char] then
if char:find("[0-9]") then
if not num_cat_inserted then
insert(data.categories, "帶數字的" .. full_langname .. "詞")
num_cat_inserted = true
end
elseif ufind(char, page.emoji_pattern) then
insert(data.categories, "帶繪文字的" .. full_langname .. "詞")
else
local upper = char_category(char)
if not explode_standard[upper] then
char = upper
end
insert(data.categories, "帶「" .. char .. "」的" .. full_langname .. "詞")
end
end
end
end
-- If a diacritic doesn't appear in any of the standard characters, also categorise for it generally.
sc_standard = toNFD(sc_standard)
for diacritic in ugmatch(page.decompose_pagename, page.comb_chars.diacritics_single) do
if not umatch(sc_standard, diacritic) then
insert(data.categories, "帶「◌" .. diacritic .. "」的" .. full_langname .. "詞")
end
end
for diacritic in ugmatch(page.decompose_pagename, page.comb_chars.diacritics_double) do
if not umatch(sc_standard, diacritic) then
insert(data.categories, "帶「◌" .. diacritic .. "◌」的" .. full_langname .. "詞")
end
end
end
end
-- Ancient Greek, Hindi and Lao handled the old way for now, as their standard chars still need to be converted to the new format (because there are a lot of them).
elseif ulen(page.pagename) ~= 1 then
for character in ugmatch(page.pagename, "([^" .. standard .. "])") do
local upper = char_category(character)
if not umatch(upper, "[" .. standard .. "]") then
character = upper
end
insert(data.categories, "帶「" .. character .. "」的" .. full_langname .. "詞")
end
end
end
-- l10n: zhwikt 的書寫系統代碼是中文
if not is_varform_only and data.heads[1].sc:isSystem("全音素文字") then
local pagename, i = page.pagename:ulower(), 2
while umatch(pagename, "(%a)" .. ("%1"):rep(i)) do
i = i + 1
insert(data.categories, "同一字母連續出現" .. i .. "次的" .. full_langname .. "詞")
end
end
-- Categorise for palindromes
if not is_varform_only and not data.nopalindromecat and namespace ~= "Reconstruction" and ulen(page.pagename) > 2
-- FIXME: Use of first script here seems hacky. What is the clean way of doing this in the presence of
-- multiple scripts?
and is_palindrome(page.pagename, data.lang, data.heads[1].sc) then
insert(data.categories, full_langname .. "迴文")
end
if namespace == "" and not lang_reconstructed then
for _, head in ipairs(data.heads) do
if page.full_raw_pagename ~= get_link_page(remove_links(head.term), data.lang, head.sc) then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/pagename spelling mismatch]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/headword/pagename spelling mismatch/LANGCODE]]
track("pagename spelling mismatch", data.lang)
break
end
end
end
-- Add red link category if called for and we're not a "large" page, where such checks are disabled.
if data.checkredlinks and not m_headword_data.large_pages[m_headword_data.pagename] then
local plposcat = type(data.checkredlinks) == "string" and data.checkredlinks or data.pos_category
check_red_link_inflections_top_level(data, plposcat)
end
-- Add to various maintenance categories.
export.maintenance_cats(page, data.lang, data.categories, data.whole_page_categories)
------------ 10. Format and return headwords, genders, inflections and categories. ------------
-- Format and return all the gathered information. This may add more categories (e.g. gender/number categories),
-- so make sure we do it before evaluating `data.categories`.
local text = '<span class="headword-line">' ..
format_headword(data) ..
format_headword_genders(data, is_varform_only) ..
format_top_level_inflections(data) .. '</span>'
-- Language-specific categories.
local cats = format_categories(
data.categories, data.lang, data.sort_key, page.encoded_pagename,
data.force_cat_output or test_force_categories, data.heads[1].sc
)
-- Language-agnostic categories.
local whole_page_cats = format_categories(
data.whole_page_categories, nil, "-"
)
return text .. cats .. whole_page_cats
end
return export
si9l3r3wnk0bigwvqkg8f8tb3b3gsce
Module:Zh-glyph
828
1364903
9909189
9678930
2026-09-29T09:54:46Z
TongcyBot
83009
同步英語維基詞典:只顯示存在的圖檔
9909189
Scribunto
text/plain
local concat = table.concat
local insert = table.insert
local load_data = mw.loadData
local new_title = mw.title.new
local process_params = require("Module:parameters").process
local data = load_data("Module:zh/data/glyph-data")
local export = {}
local large_pages = {["大"] = true, ["子"] = true, ["年"] = true, ["方"] = true, ["日"] = true, ["王"] = true}
-- FIXME: temporary solution to avoid going over the expensive function call limit
-- these pages generally have very few actually existing images so we could perhaps modify the data module instead
function export.main(frame)
local boolean = {type = "boolean"}
local args = process_params(frame:getParent().args, {
[1] = true,
["no_img"] = boolean,
["no_phon"] = boolean,
})
local target_page = args[1] or load_data("Module:headword/data").pagename
local showEras, showScripts, showImgs, hideText = {}, {}, {}, {}
local success, data_module = pcall(load_data, "Module:zh/data/glyph-data/" .. target_page)
local char_data = {
["bronze"] = {},
["oracle"] = {},
["silk"] = {},
["slip"] = {},
["seal"] = {},
["bigseal"] = {},
}
local count = 0
if not args.no_img then
for _, era in ipairs(data.eras) do
local count_era = 0
for _, script in ipairs(era[2]) do
local img_link = target_page .. "-" .. script .. ".svg"
local img_file = new_title("Media:" .. img_link)
if img_file.exists and not img_file.isRedirect then
count = count + 1
count_era = count_era + 1
insert(showScripts, "\n!" .. data.scripts[script])
insert(showImgs, "\n| style=\"padding: 8px;\"|[[File:" .. img_link .. "|60px|class=mw-invert skin-invert]]")
end
end
if count_era > 0 then
insert(showEras, "\n! colspan=\"" .. count_era .. "\"|" .. era[1])
end
end
end
if success and not large_pages[target_page] then
success = false
local titles = {}
for _, img in ipairs(data_module[1]) do
table.insert(titles, "Media:ACC-" .. img .. ".svg")
end
local batch = mw.title.newBatch(titles):lookupExistence():getTitles()
for i = 1, #(batch) do
img = data_module[1][i]
if batch[i].exists then
success = true
insert(char_data[data.script_abbrev[img:match("^.[\128-\191]*")]], '<div class="gallerybox" style="display:inline-block"' ..
'><div style="text-align:center">[[File:ACC-' .. img ..
'.svg|60px|class=mw-invert skin-invert]]<div class="gallerytext"><small>' .. img .. '</small></div></div></div>')
end
end
if success then
for i, script in ipairs(data.hide_scripts) do
if char_data[script][1] then
if i ~= 1 then
insert(hideText, "<hr>")
end
insert(hideText, "<div class=\"gallery\"><div><b>" .. data.scripts[script] .. "</b></div>" .. concat(char_data[script]) .. "</div>")
end
end
-- l10n: Chinese reference note
insert(hideText, '\n|- class="vsHide"\n| width="100%" align="left" colspan="' .. (count == 0 and 1 or count) ..
'|\n<div class="standard-box mw-collapsible mw-collapsed"><b>參考</b>:' ..
'<div class="mw-collapsible-content">\n主要來自[[w:理查德·西尔斯 (学者)|理查德·西爾斯]]的' ..
"[http://hanziyuan.net/#" ..
target_page .. " 漢字字源網]([[c:Commons:Ancient Chinese characters/Richard Sears Agreement" ..
"|允許使用說明]]),<br>收集來自不同形式的古文字的繪圖資料" ..
",包括:\n* 《說文解字》(小篆)\n* 《金文編》(金文)" ..
"\n* 《六書通》(傳抄古文字)\n* 《殷墟甲骨文編》(甲骨文)</div></div>")
end
end
local glyph_forms
if ( success or count > 0 ) and not args.no_img then
glyph_forms = "<div style=\"overflow:auto;width:fit-content;max-width:100%\">\n" .. [=[{| class="wikitable zh-glyph]=] .. (success and ' vsSwitcher" data-toggle-category="forms' or "") ..
[=[" cellpadding=7 style="border-spacing: 1px; border: 1px solid var(--wikt-palette-grey-7,darkgray); text-align:center"
! class="vsToggleElement" colspan="]=] .. (count == 0 and 1 or count) ..
-- l10n: Chinese caption; -{ }- keeps the character out of variant conversion
[=[" |古代字體(<span lang="zh-Hant" class="Hani">-{]=] .. target_page .. [=[}-</span>)]=] ..
(count > 0
and "\n|-" .. concat(showEras) .. "\n|-" .. concat(showScripts) .. "\n|-" .. concat(showImgs)
or "") ..
(success
and "\n|- class=\"vsHide\"\n| align=\"center\" colspan=\"" .. (count == 0 and 1 or count) ..
"|" .. concat(hideText, "")
or "") ..
"\n|}\n</div>"
else
glyph_forms = ""
end
local phonText = {}
local list = load_data("Module:zh-glyph/phonetic/list")
local phonComp = list[target_page] or nil
if phonComp and not args.no_phon then
insert(phonText,
'\n' ..
'<div class="NavFrame" style="border:0px; max-width: 37em; text-align:center;">' ..
'<div class="NavHead" style="font-size:105%; border:1px solid var(--border-color-base, #a2a9b1); background-color:var(--wikt-palette-indigo-3, #e0defa); color:inherit; text-align:center; margin:0">' ..
-- l10n: Chinese heading and project pages; -{ }- keeps the character out of variant conversion
'[[Wiktionary:關於漢語/諧聲系|同聲符字]]' ..
' (<span class="Hani">-{[[' .. phonComp .. ']]}-</span>)' ..
' <small>([[Wiktionary:關於漢語/參考文獻|鄭張尚芳 (2003)]])</small> ' ..
'</div>' ..
'<div class="NavContent" style="border-top: 0">' ..
'\n{| class="wikitable zh-glyph" style="margin-top: 0; width: 100%; display: table; border-top: 0"' ..
-- l10n: Chinese column header
'\n|-\n! align="center" style="border-top: 0"|\n! style="border-top: 0" | 上古漢語'
)
local phonData = load_data("Module:zh-glyph/phonetic")
local m_och = require("Module:och-pron").retrieve_pron
for item in phonData[phonComp]:gmatch(".[\128-\191]*") do
-- l10n: Chinese section anchor; -{ }- keeps the character out of variant conversion
insert(phonText, '\n|-\n| align="center" |<span class="Hani">-{[[' .. item .. '#漢語|' .. item .. ']]}-</span>')
local IPA = m_och(item, nil, true)
if IPA then
insert(phonText, '\n|<small><span class="IPA">' .. IPA .. '</span></small>')
end
end
insert(phonText, '\n|}\n</div></div>')
end
return glyph_forms .. concat(phonText)
end
return export
oh7ktpwwiw3apnbadw178zs1krp5rgy
Module:Zh-glyph/phonetic/list
828
1364904
9909131
7648582
2026-09-29T08:36:24Z
TongcyDai
53191
9909131
Scribunto
text/plain
return {
["愛"] = "愛",
["曖"] = "愛",
["僾"] = "愛",
["靉"] = "愛",
["薆"] = "愛",
["璦"] = "愛",
["瞹"] = "愛",
["安"] = "安",
["鞍"] = "安",
["鞌"] = "安",
["侒"] = "安",
["按"] = "安",
["案"] = "安",
["洝"] = "安",
["荌"] = "安",
["晏"] = "安",
["鴳"] = "安",
["騴"] = "安",
["鷃"] = "安",
["頞"] = "安",
["胺"] = "安",
["咹"] = "安",
["卬"] = "卬",
["枊"] = "卬",
["昂"] = "卬",
["仰"] = "卬",
["迎"] = "卬",
["熬"] = "敖",
["敖"] = "敖",
["嗷"] = "敖",
["螯"] = "敖",
["聱"] = "敖",
["驁"] = "敖",
["鰲"] = "敖",
["遨"] = "敖",
["獒"] = "敖",
["滶"] = "敖",
["蔜"] = "敖",
["鷔"] = "敖",
["鼇"] = "敖",
["謷"] = "敖",
["嶅"] = "敖",
["嗸"] = "敖",
["摮"] = "敖",
["嫯"] = "敖",
["鏊"] = "敖",
["傲"] = "敖",
["磝"] = "敖",
["襖"] = "奧",
["懊"] = "奧",
["燠"] = "奧",
["奧"] = "奧",
["墺"] = "奧",
["隩"] = "奧",
["澳"] = "奧",
["噢"] = "奧",
["薁"] = "奧",
["䉛"] = "奧",
["扒"] = "八",
["八"] = "八",
["𩡩"] = "八",
["朳"] = "八",
["玐"] = "八",
["釟"] = "八",
["汃"] = "八",
["巴"] = "巴",
["芭"] = "巴",
["笆"] = "巴",
["豝"] = "巴",
["鈀"] = "巴",
["吧"] = "巴",
["蚆"] = "巴",
["疤"] = "巴",
["把"] = "巴",
["爸"] = "巴",
["弝"] = "巴",
["靶"] = "巴",
["葩"] = "巴",
["舥"] = "巴",
["妑"] = "巴",
["帊"] = "巴",
["琶"] = "巴",
["爬"] = "巴",
["杷"] = "巴",
["跁"] = "巴",
["耙"] = "巴",
["軷"] = "犮",
["韍"] = "犮",
["祓"] = "犮",
["蛂"] = "犮",
["拔"] = "犮",
["菝"] = "犮",
["袚"] = "犮",
["茇"] = "犮",
["盋"] = "犮",
["帗"] = "犮",
["跋"] = "犮",
["魃"] = "犮",
["䣮"] = "犮",
["馛"] = "犮",
["炦"] = "犮",
["颰"] = "犮",
["妭"] = "犮",
["犮"] = "犮",
["胈"] = "犮",
["鈸"] = "犮",
["苃"] = "犮",
["坺"] = "犮",
["髮"] = "犮",
["冹"] = "犮",
["瞂"] = "犮",
["紱"] = "犮",
["黻"] = "犮",
["翇"] = "犮",
["發"] = "癹",
["癹"] = "癹",
["擺"] = "罷",
["犤"] = "罷",
["罷"] = "罷",
["矲"] = "罷",
["羆"] = "罷",
["襬"] = "罷",
["藣"] = "罷",
["壩"] = "霸",
["霸"] = "霸",
["灞"] = "霸",
["欛"] = "霸",
["怕"] = "白",
["帕"] = "白",
["粕"] = "白",
["胉"] = "白",
["泊"] = "白",
["箔"] = "白",
["魄"] = "白",
["皕"] = "白",
["伯"] = "白",
["百"] = "白",
["迫"] = "白",
["敀"] = "白",
["柏"] = "白",
["湐"] = "白",
["拍"] = "白",
["珀"] = "白",
["皛"] = "白",
["洦"] = "白",
["白"] = "白",
["帛"] = "白",
["舶"] = "白",
["鮊"] = "白",
["陌"] = "白",
["帞"] = "白",
["袹"] = "白",
["蛨"] = "白",
["貊"] = "白",
["佰"] = "白",
["銆"] = "白",
["碧"] = "白",
["咟"] = "白",
["拜"] = "拜",
["湃"] = "拜",
["班"] = "班",
["斑"] = "班",
["螌"] = "般",
["般"] = "般",
["媻"] = "般",
["搫"] = "般",
["搬"] = "般",
["盤"] = "般",
["磐"] = "般",
["瘢"] = "般",
["幋"] = "般",
["槃"] = "般",
["縏"] = "般",
["鎜"] = "般",
["鞶"] = "般",
["半"] = "半",
["絆"] = "半",
["靽"] = "半",
["姅"] = "半",
["拌"] = "半",
["拚"] = "半",
["坢"] = "半",
["判"] = "半",
["泮"] = "半",
["頖"] = "半",
["胖"] = "半",
["冸"] = "半",
["牉"] = "半",
["柈"] = "半",
["伴"] = "半",
["叛"] = "半",
["畔"] = "半",
["袢"] = "半",
["袍"] = "勹",
["袌"] = "勹",
["軳"] = "勹",
["抱"] = "勹",
["勽"] = "勹",
["菢"] = "勹",
["勹"] = "勹",
["包"] = "勹",
["胞"] = "勹",
["苞"] = "勹",
["枹"] = "勹",
["飽"] = "勹",
["泡"] = "勹",
["炮"] = "勹",
["皰"] = "勹",
["疱"] = "勹",
["跑"] = "勹",
["咆"] = "勹",
["庖"] = "勹",
["匏"] = "勹",
["刨"] = "勹",
["狍"] = "勹",
["炰"] = "勹",
["鉋"] = "勹",
["瓟"] = "勹",
["颮"] = "勹",
["鞄"] = "勹",
["鮑"] = "勹",
["骲"] = "勹",
["玸"] = "勹",
["雹"] = "勹",
["窇"] = "勹",
["襃"] = "保",
["保"] = "保",
["堡"] = "保",
["褓"] = "保",
["堢"] = "保",
["緥"] = "保",
["葆"] = "保",
["賲"] = "保",
["宲"] = "保",
["椺"] = "保",
["鴇"] = "鴇",
["駂"] = "鴇",
["暴"] = "暴",
["曝"] = "暴",
["瀑"] = "暴",
["虣"] = "暴",
["爆"] = "暴",
["襮"] = "暴",
["嚗"] = "暴",
["謈"] = "暴",
["犦"] = "暴",
["懪"] = "暴",
["捭"] = "卑",
["牌"] = "卑",
["郫"] = "卑",
["蠯"] = "卑",
["猈"] = "卑",
["稗"] = "卑",
["粺"] = "卑",
["𥱼"] = "卑",
["豍"] = "卑",
["箄"] = "卑",
["錍"] = "卑",
["睥"] = "卑",
["渒"] = "卑",
["鼙"] = "卑",
["鞞"] = "卑",
["椑"] = "卑",
["崥"] = "卑",
["髀"] = "卑",
["卑"] = "卑",
["鵯"] = "卑",
["裨"] = "卑",
["痺"] = "卑",
["碑"] = "卑",
["婢"] = "卑",
["諀"] = "卑",
["脾"] = "卑",
["埤"] = "卑",
["陴"] = "卑",
["蜱"] = "卑",
["焷"] = "卑",
["螷"] = "卑",
["庳"] = "卑",
["琕"] = "卑",
["萆"] = "卑",
["綼"] = "卑",
["背"] = "北",
["鄁"] = "北",
["偝"] = "北",
["邶"] = "北",
["苝"] = "北",
["北"] = "北",
["貝"] = "貝",
["狽"] = "貝",
["鋇"] = "貝",
["浿"] = "貝",
["垻"] = "貝",
["敗"] = "貝",
["唄"] = "貝",
["憊"] = "備",
["韛"] = "備",
["備"] = "備",
["俻"] = "備",
["鞴"] = "備",
["糒"] = "備",
["犕"] = "備",
["賁"] = "奔",
["蟦"] = "奔",
["膹"] = "奔",
["橨"] = "奔",
["奔"] = "奔",
["錛"] = "奔",
["噴"] = "奔",
["濆"] = "奔",
["歕"] = "奔",
["獖"] = "奔",
["餴"] = "奔",
["饙"] = "奔",
["僨"] = "奔",
["墳"] = "奔",
["燌"] = "奔",
["豶"] = "奔",
["羵"] = "奔",
["蕡"] = "奔",
["鐼"] = "奔",
["馩"] = "奔",
["轒"] = "奔",
["鼖"] = "奔",
["憤"] = "奔",
["鱝"] = "奔",
["本"] = "本",
["笨"] = "本",
["苯"] = "本",
["翉"] = "本",
["体"] = "本",
["鉢"] = "本",
["缽"] = "本",
["匕"] = "匕",
["庀"] = "匕",
["疕"] = "匕",
["朼"] = "匕",
["牝"] = "匕",
["梐"] = "比",
["狴"] = "比",
["悂"] = "比",
["螕"] = "比",
["鎞"] = "比",
["蓖"] = "比",
["篦"] = "比",
["批"] = "比",
["砒"] = "比",
["鈚"] = "比",
["磇"] = "比",
["媲"] = "比",
["笓"] = "比",
["膍"] = "比",
["陛"] = "比",
["仳"] = "比",
["吡"] = "比",
["紕"] = "比",
["比"] = "比",
["妣"] = "比",
["秕"] = "比",
["沘"] = "比",
["枇"] = "比",
["粊"] = "比",
["庇"] = "比",
["屁"] = "比",
["琵"] = "比",
["毗"] = "比",
["貔"] = "比",
["豼"] = "比",
["蚍"] = "比",
["芘"] = "比",
["肶"] = "比",
["魮"] = "比",
["阰"] = "比",
["坒"] = "比",
["玭"] = "比",
["鄙"] = "啚",
["啚"] = "啚",
["泌"] = "必",
["秘"] = "必",
["毖"] = "必",
["閟"] = "必",
["柲"] = "必",
["鉍"] = "必",
["邲"] = "必",
["覕"] = "必",
["咇"] = "必",
["苾"] = "必",
["馝"] = "必",
["飶"] = "必",
["鴓"] = "必",
["鮅"] = "必",
["必"] = "必",
["珌"] = "必",
["佖"] = "必",
["駜"] = "必",
["怭"] = "必",
["妼"] = "必",
["宓"] = "必",
["樒"] = "必",
["密"] = "必",
["蔤"] = "必",
["滵"] = "必",
["蜜"] = "必",
["謐"] = "必",
["榓"] = "必",
["淧"] = "必",
["瑟"] = "必",
["璱"] = "必",
["飋"] = "必",
["虙"] = "必",
["箅"] = "畀",
["濞"] = "畀",
["畀"] = "畀",
["痹"] = "畀",
["淠"] = "畀",
["嚊"] = "畀",
["鼻"] = "畀",
["襣"] = "畀",
["畢"] = "畢",
["蓽"] = "畢",
["篳"] = "畢",
["蹕"] = "畢",
["韠"] = "畢",
["滭"] = "畢",
["鷝"] = "畢",
["熚"] = "畢",
["彃"] = "畢",
["縪"] = "畢",
["饆"] = "畢",
["鏎"] = "畢",
["罼"] = "畢",
["蔽"] = "敝",
["鄨"] = "敝",
["鷩"] = "敝",
["彆"] = "敝",
["潎"] = "敝",
["斃"] = "敝",
["敝"] = "敝",
["幣"] = "敝",
["弊"] = "敝",
["鱉"] = "敝",
["虌"] = "敝",
["憋"] = "敝",
["瞥"] = "敝",
["鐅"] = "敝",
["嫳"] = "敝",
["撇"] = "敝",
["撆"] = "敝",
["暼"] = "敝",
["蹩"] = "敝",
["襒"] = "敝",
["閉"] = "閉",
["閇"] = "閉",
["嬖"] = "辟",
["薜"] = "辟",
["鐾"] = "辟",
["臂"] = "辟",
["譬"] = "辟",
["避"] = "辟",
["檗"] = "辟",
["擘"] = "辟",
["糪"] = "辟",
["掰"] = "辟",
["繴"] = "辟",
["辟"] = "辟",
["璧"] = "辟",
["鐴"] = "辟",
["躄"] = "辟",
["襞"] = "辟",
["僻"] = "辟",
["癖"] = "辟",
["廦"] = "辟",
["擗"] = "辟",
["躃"] = "辟",
["闢"] = "辟",
["壁"] = "辟",
["鼊"] = "辟",
["霹"] = "辟",
["劈"] = "辟",
["澼"] = "辟",
["憵"] = "辟",
["甓"] = "辟",
["鷿"] = "辟",
["幦"] = "辟",
["斒"] = "扁",
["鯿"] = "扁",
["編"] = "扁",
["揙"] = "扁",
["褊"] = "扁",
["篇"] = "扁",
["偏"] = "扁",
["翩"] = "扁",
["媥"] = "扁",
["扁"] = "扁",
["萹"] = "扁",
["騗"] = "扁",
["騙"] = "扁",
["諞"] = "扁",
["楄"] = "扁",
["蝙"] = "扁",
["甂"] = "扁",
["猵"] = "扁",
["牑"] = "扁",
["蹁"] = "扁",
["匾"] = "扁",
["碥"] = "扁",
["惼"] = "扁",
["糄"] = "扁",
["徧"] = "扁",
["遍"] = "扁",
["頨"] = "扁",
["艑"] = "扁",
["汴"] = "卞",
["卞"] = "卞",
["忭"] = "卞",
["抃"] = "卞",
["犿"] = "卞",
["笇"] = "卞",
["飰"] = "卞",
["鴘"] = "弁",
["弁"] = "弁",
["拚"] = "弁",
["閞"] = "弁",
["昪"] = "弁",
["匥"] = "弁",
["笲"] = "弁",
["軬"] = "弁",
["畚"] = "弁",
["鞭"] = "便",
["鯾"] = "便",
["箯"] = "便",
["便"] = "便",
["緶"] = "便",
["楩"] = "便",
["辬"] = "辡",
["瓣"] = "辡",
["辦"] = "辡",
["辨"] = "辡",
["辡"] = "辡",
["辯"] = "辡",
["辮"] = "辡",
["彪"] = "彪",
["淲"] = "彪",
["滮"] = "彪",
["飆"] = "猋",
["猋"] = "猋",
["贆"] = "猋",
["藨"] = "麃",
["麃"] = "麃",
["臕"] = "麃",
["鑣"] = "麃",
["儦"] = "麃",
["瀌"] = "麃",
["犥"] = "麃",
["皫"] = "麃",
["表"] = "表",
["裱"] = "表",
["俵"] = "表",
["捌"] = "别",
["哵"] = "别",
["別"] = "别",
["莂"] = "别",
["虨"] = "彬",
["彬"] = "彬",
["霦"] = "彬",
["斌"] = "斌",
["贇"] = "斌",
["燹"] = "豩",
["豳"] = "豩",
["蠙"] = "賓",
["賓"] = "賓",
["檳"] = "賓",
["鑌"] = "賓",
["濱"] = "賓",
["顮"] = "賓",
["儐"] = "賓",
["矉"] = "賓",
["擯"] = "賓",
["殯"] = "賓",
["鬢"] = "賓",
["繽"] = "賓",
["嬪"] = "賓",
["薲"] = "賓",
["獱"] = "賓",
["臏"] = "賓",
["髕"] = "賓",
["冫"] = "仌",
["冰"] = "仌",
["砅"] = "仌",
["馮"] = "仌",
["憑"] = "仌",
["浜"] = "兵",
["捠"] = "兵",
["兵"] = "兵",
["更"] = "丙",
["粳"] = "丙",
["埂"] = "丙",
["浭"] = "丙",
["稉"] = "丙",
["梗"] = "丙",
["哽"] = "丙",
["綆"] = "丙",
["鯁"] = "丙",
["挭"] = "丙",
["骾"] = "丙",
["郠"] = "丙",
["硬"] = "丙",
["鞕"] = "丙",
["丙"] = "丙",
["炳"] = "丙",
["邴"] = "丙",
["怲"] = "丙",
["苪"] = "丙",
["蛃"] = "丙",
["昺"] = "丙",
["昞"] = "丙",
["窉"] = "丙",
["柄"] = "丙",
["鈵"] = "丙",
["寎"] = "丙",
["病"] = "丙",
["秉"] = "秉",
["棅"] = "秉",
["燷"] = "禀",
["壈"] = "禀",
["燣"] = "禀",
["顲"] = "禀",
["稟"] = "禀",
["廩"] = "禀",
["懍"] = "禀",
["凜"] = "禀",
["癛"] = "禀",
["檁"] = "禀",
["鵧"] = "并",
["駢"] = "并",
["胼"] = "并",
["骿"] = "并",
["軿"] = "并",
["賆"] = "并",
["跰"] = "并",
["恲"] = "并",
["鮩"] = "并",
["絣"] = "并",
["拼"] = "并",
["迸"] = "并",
["姘"] = "并",
["皏"] = "并",
["併"] = "并",
["并"] = "并",
["栟"] = "并",
["屏"] = "并",
["箳"] = "并",
["餅"] = "并",
["鉼"] = "并",
["摒"] = "并",
["庰"] = "并",
["偋"] = "并",
["艵"] = "并",
["頩"] = "并",
["竮"] = "并",
["瓶"] = "并",
["缾"] = "并",
["蛢"] = "并",
["荓"] = "并",
["郱"] = "并",
["洴"] = "并",
["蓱"] = "并",
["湴"] = "並",
["跰"] = "並",
["並"] = "並",
["竝"] = "並",
["誖"] = "孛",
["悖"] = "孛",
["孛"] = "孛",
["哱"] = "孛",
["馞"] = "孛",
["勃"] = "孛",
["餑"] = "孛",
["郣"] = "孛",
["浡"] = "孛",
["桲"] = "孛",
["挬"] = "孛",
["脖"] = "孛",
["鵓"] = "孛",
["渤"] = "孛",
["綍"] = "孛",
["仆"] = "卜",
["赴"] = "卜",
["訃"] = "卜",
["朴"] = "卜",
["圤"] = "卜",
["卜"] = "卜",
["鳪"] = "卜",
["扑"] = "卜",
["攴"] = "卜",
["肧"] = "不",
["俖"] = "不",
["娝"] = "不",
["痞"] = "不",
["丕"] = "不",
["伾"] = "不",
["秠"] = "不",
["駓"] = "不",
["怌"] = "不",
["豾"] = "不",
["髬"] = "不",
["魾"] = "不",
["鉟"] = "不",
["嚭"] = "不",
["邳"] = "不",
["岯"] = "不",
["否"] = "不",
["抔"] = "不",
["不"] = "不",
["紑"] = "不",
["鴀"] = "不",
["衃"] = "不",
["罘"] = "不",
["芣"] = "不",
["杯"] = "不",
["盃"] = "不",
["桮"] = "不",
["坯"] = "不",
["胚"] = "不",
["荹"] = "步",
["步"] = "步",
["踄"] = "步",
["部"] = "部",
["蔀"] = "部",
["篰"] = "部",
["茬"] = "才",
["材"] = "才",
["才"] = "才",
["財"] = "才",
["鼒"] = "才",
["在"] = "才",
["豺"] = "才",
["采"] = "采",
["睬"] = "采",
["彩"] = "采",
["採"] = "采",
["綵"] = "采",
["寀"] = "采",
["菜"] = "采",
["埰"] = "采",
["棌"] = "采",
["篸"] = "參",
["參"] = "參",
["驂"] = "參",
["傪"] = "參",
["慘"] = "參",
["黲"] = "參",
["謲"] = "參",
["毿"] = "參",
["蔘"] = "參",
["犙"] = "參",
["糝"] = "參",
["槮"] = "參",
["鬖"] = "參",
["摻"] = "參",
["醦"] = "參",
["縿"] = "參",
["穇"] = "參",
["嵾"] = "參",
["磣"] = "參",
["墋"] = "參",
["曑"] = "參",
["瘮"] = "參",
["滲"] = "參",
["餐"] = "粲",
["湌"] = "粲",
["粲"] = "粲",
["燦"] = "粲",
["璨"] = "粲",
["薒"] = "粲",
["殩"] = "粲",
["艙"] = "倉",
["倉"] = "倉",
["蒼"] = "倉",
["滄"] = "倉",
["傖"] = "倉",
["鶬"] = "倉",
["凔"] = "倉",
["槍"] = "倉",
["搶"] = "倉",
["蹌"] = "倉",
["瑲"] = "倉",
["牄"] = "倉",
["嗆"] = "倉",
["創"] = "倉",
["瘡"] = "倉",
["愴"] = "倉",
["鎗"] = "倉",
["遭"] = "曹",
["糟"] = "曹",
["醩"] = "曹",
["傮"] = "曹",
["槽"] = "曹",
["曹"] = "曹",
["螬"] = "曹",
["漕"] = "曹",
["嘈"] = "曹",
["艚"] = "曹",
["褿"] = "曹",
["蓸"] = "曹",
["慒"] = "曹",
["栅"] = "冊",
["冊"] = "冊",
["笧"] = "冊",
["稷"] = "畟",
["畟"] = "畟",
["溭"] = "畟",
["謖"] = "畟",
["叉"] = "叉",
["杈"] = "叉",
["靫"] = "叉",
["汊"] = "叉",
["衩"] = "叉",
["扠"] = "叉",
["釵"] = "叉",
["芆"] = "叉",
["訍"] = "叉",
["偛"] = "臿",
["插"] = "臿",
["鍤"] = "臿",
["臿"] = "臿",
["喢"] = "臿",
["牐"] = "臿",
["歃"] = "臿",
["敮"] = "臿",
["孱"] = "孱",
["潺"] = "孱",
["僝"] = "孱",
["轏"] = "孱",
["樼"] = "孱",
["纏"] = "廛",
["緾"] = "廛",
["躔"] = "廛",
["瀍"] = "廛",
["鄽"] = "廛",
["廛"] = "廛",
["纔"] = "毚",
["讒"] = "毚",
["饞"] = "毚",
["毚"] = "毚",
["欃"] = "毚",
["攙"] = "毚",
["儳"] = "毚",
["酁"] = "毚",
["瀺"] = "毚",
["嚵"] = "毚",
["巉"] = "毚",
["劖"] = "毚",
["艬"] = "毚",
["鑱"] = "毚",
["昌"] = "昌",
["倡"] = "昌",
["猖"] = "昌",
["菖"] = "昌",
["閶"] = "昌",
["鯧"] = "昌",
["裮"] = "昌",
["琩"] = "昌",
["唱"] = "昌",
["張"] = "長",
["餦"] = "長",
["粻"] = "長",
["漲"] = "長",
["長"] = "長",
["脹"] = "長",
["痮"] = "長",
["帳"] = "長",
["賬"] = "長",
["倀"] = "長",
["鼚"] = "長",
["鋹"] = "長",
["悵"] = "長",
["韔"] = "長",
["萇"] = "長",
["瓺"] = "長",
["棖"] = "長",
["掁"] = "長",
["璅"] = "巢",
["繅"] = "巢",
["薻"] = "巢",
["罺"] = "巢",
["摷"] = "巢",
["勦"] = "巢",
["巢"] = "巢",
["轈"] = "巢",
["樔"] = "巢",
["鄛"] = "巢",
["剿"] = "巢",
["漅"] = "巢",
["嘲"] = "朝",
["廟"] = "朝",
["朝"] = "朝",
["潮"] = "朝",
["車"] = "車",
["硨"] = "車",
["厙"] = "車",
["庫"] = "車",
["徹"] = "徹",
["撤"] = "徹",
["澈"] = "徹",
["轍"] = "徹",
["臤"] = "臣",
["掔"] = "臣",
["慳"] = "臣",
["藖"] = "臣",
["堅"] = "臣",
["鋻"] = "臣",
["鰹"] = "臣",
["蜸"] = "臣",
["菣"] = "臣",
["賢"] = "臣",
["礥"] = "臣",
["臔"] = "臣",
["挋"] = "臣",
["臣"] = "臣",
["茞"] = "臣",
["腎"] = "臣",
["頣"] = "臣",
["緊"] = "臣",
["嚚"] = "臣",
["鏗"] = "臣",
["摼"] = "臣",
["硻"] = "臣",
["辴"] = "辰",
["屒"] = "辰",
["振"] = "辰",
["侲"] = "辰",
["桭"] = "辰",
["唇"] = "辰",
["帪"] = "辰",
["賑"] = "辰",
["裖"] = "辰",
["敐"] = "辰",
["震"] = "辰",
["娠"] = "辰",
["蜃"] = "辰",
["辰"] = "辰",
["晨"] = "辰",
["宸"] = "辰",
["鷐"] = "辰",
["麎"] = "辰",
["祳"] = "辰",
["脤"] = "辰",
["鋠"] = "辰",
["磭"] = "辰",
["陙"] = "辰",
["脣"] = "辰",
["漘"] = "辰",
["陳"] = "陳",
["敶"] = "陳",
["陣"] = "陳",
["讖"] = "韱",
["攕"] = "韱",
["櫼"] = "韱",
["懺"] = "韱",
["殲"] = "韱",
["瀸"] = "韱",
["鑯"] = "韱",
["虃"] = "韱",
["籤"] = "韱",
["韯"] = "韱",
["纖"] = "韱",
["孅"] = "韱",
["韱"] = "韱",
["襳"] = "韱",
["稱"] = "爯",
["爯"] = "爯",
["偁"] = "爯",
["蒸"] = "丞",
["烝"] = "丞",
["篜"] = "丞",
["脀"] = "丞",
["拯"] = "丞",
["氶"] = "丞",
["承"] = "丞",
["丞"] = "丞",
["巹"] = "丞",
["成"] = "成",
["城"] = "成",
["誠"] = "成",
["宬"] = "成",
["郕"] = "成",
["筬"] = "成",
["盛"] = "成",
["珹"] = "成",
["晟"] = "成",
["墭"] = "成",
["挰"] = "呈",
["鋥"] = "呈",
["逞"] = "呈",
["裎"] = "呈",
["悜"] = "呈",
["睈"] = "呈",
["呈"] = "呈",
["程"] = "呈",
["酲"] = "呈",
["珵"] = "呈",
["徎"] = "呈",
["郢"] = "呈",
["浧"] = "呈",
["桯"] = "呈",
["鞓"] = "呈",
["壬"] = "呈",
["鐵"] = "呈",
["驖"] = "呈",
["戜"] = "呈",
["乘"] = "乘",
["溗"] = "乘",
["塖"] = "乘",
["騬"] = "乘",
["椉"] = "乘",
["剩"] = "乘",
["嵊"] = "乘",
["尺"] = "尺",
["蚇"] = "尺",
["蠡"] = "彖",
["盠"] = "彖",
["彖"] = "彖",
["墬"] = "彖",
["喙"] = "彖",
["餯"] = "彖",
["剶"] = "彖",
["猭"] = "彖",
["鶨"] = "彖",
["椽"] = "彖",
["篆"] = "彖",
["瑑"] = "彖",
["堟"] = "彖",
["蝝"] = "彖",
["緣"] = "彖",
["櫞"] = "彖",
["掾"] = "彖",
["褖"] = "彖",
["湪"] = "彖",
["浾"] = "赤",
["嚇"] = "赤",
["赦"] = "赤",
["郝"] = "赤",
["赫"] = "赤",
["爀"] = "赤",
["赤"] = "赤",
["烾"] = "赤",
["螫"] = "赤",
["赥"] = "赤",
["捇"] = "赤",
["焃"] = "赤",
["敕"] = "敕",
["勅"] = "敕",
["鷘"] = "敕",
["慗"] = "敕",
["遫"] = "敕",
["統"] = "充",
["充"] = "充",
["珫"] = "充",
["茺"] = "充",
["銃"] = "充",
["樁"] = "舂",
["憃"] = "舂",
["舂"] = "舂",
["摏"] = "舂",
["蹖"] = "舂",
["痋"] = "蟲",
["鉵"] = "蟲",
["赨"] = "蟲",
["爞"] = "蟲",
["蟲"] = "蟲",
["融"] = "蟲",
["瀜"] = "蟲",
["犨"] = "雔",
["犫"] = "雔",
["雔"] = "雔",
["售"] = "雔",
["讎"] = "雔",
["峱"] = "丑",
["紐"] = "丑",
["扭"] = "丑",
["狃"] = "丑",
["鈕"] = "丑",
["忸"] = "丑",
["杻"] = "丑",
["莥"] = "丑",
["羞"] = "丑",
["丑"] = "丑",
["沑"] = "丑",
["粈"] = "丑",
["衄"] = "丑",
["臭"] = "臭",
["殠"] = "臭",
["糗"] = "臭",
["嗅"] = "臭",
["齅"] = "臭",
["聉"] = "出",
["贅"] = "出",
["祟"] = "出",
["出"] = "出",
["茁"] = "出",
["欪"] = "出",
["貀"] = "出",
["袦"] = "出",
["咄"] = "出",
["柮"] = "出",
["拙"] = "出",
["炪"] = "出",
["掘"] = "出",
["鈯"] = "出",
["啒"] = "出",
["淈"] = "出",
["窟"] = "出",
["泏"] = "出",
["堀"] = "出",
["胐"] = "出",
["窋"] = "出",
["絀"] = "出",
["黜"] = "出",
["屈"] = "出",
["趉"] = "出",
["鶌"] = "出",
["詘"] = "出",
["倔"] = "出",
["崛"] = "出",
["煼"] = "芻",
["謅"] = "芻",
["縐"] = "芻",
["鄒"] = "芻",
["騶"] = "芻",
["搊"] = "芻",
["皺"] = "芻",
["篘"] = "芻",
["㑳"] = "芻",
["趍"] = "芻",
["趨"] = "芻",
["芻"] = "芻",
["犓"] = "芻",
["雛"] = "芻",
["鶵"] = "芻",
["媰"] = "芻",
["齺"] = "芻",
["冢"] = "豖",
["塚"] = "豖",
["涿"] = "豖",
["琢"] = "豖",
["啄"] = "豖",
["諑"] = "豖",
["剢"] = "豖",
["瘃"] = "豖",
["豖"] = "豖",
["川"] = "川",
["釧"] = "川",
["巛"] = "川",
["巡"] = "川",
["馴"] = "川",
["紃"] = "川",
["甽"] = "川",
["順"] = "川",
["訓"] = "川",
["舛"] = "舛",
["荈"] = "舛",
["僢"] = "舛",
["舜"] = "舛",
["瞬"] = "舛",
["蕣"] = "舛",
["梁"] = "刅",
["粱"] = "刅",
["刱"] = "刅",
["吹"] = "吹",
["炊"] = "吹",
["焁"] = "吹",
["埵"] = "垂",
["綞"] = "垂",
["涶"] = "垂",
["唾"] = "垂",
["厜"] = "垂",
["腄"] = "垂",
["箠"] = "垂",
["娷"] = "垂",
["諈"] = "垂",
["錘"] = "垂",
["甀"] = "垂",
["硾"] = "垂",
["捶"] = "垂",
["垂"] = "垂",
["陲"] = "垂",
["倕"] = "垂",
["菙"] = "垂",
["睡"] = "垂",
["辤"] = "辭",
["辭"] = "辭",
["些"] = "此",
["跐"] = "此",
["柴"] = "此",
["祡"] = "此",
["茈"] = "此",
["眦"] = "此",
["砦"] = "此",
["寨"] = "此",
["啙"] = "此",
["泚"] = "此",
["玼"] = "此",
["皉"] = "此",
["鮆"] = "此",
["眥"] = "此",
["貲"] = "此",
["髭"] = "此",
["頾"] = "此",
["訾"] = "此",
["鴜"] = "此",
["鈭"] = "此",
["姕"] = "此",
["觜"] = "此",
["紫"] = "此",
["訿"] = "此",
["呰"] = "此",
["嘴"] = "此",
["雌"] = "此",
["此"] = "此",
["佌"] = "此",
["庛"] = "此",
["疵"] = "此",
["骴"] = "此",
["胔"] = "此",
["飺"] = "此",
["齜"] = "此",
["債"] = "朿",
["積"] = "朿",
["刺"] = "朿",
["莿"] = "朿",
["㓨"] = "朿",
["朿"] = "朿",
["漬"] = "朿",
["耫"] = "朿",
["嘖"] = "朿",
["栜"] = "朿",
["梀"] = "朿",
["拺"] = "朿",
["捒"] = "朿",
["幘"] = "朿",
["簀"] = "朿",
["嫧"] = "朿",
["謮"] = "朿",
["責"] = "朿",
["皟"] = "朿",
["憡"] = "朿",
["策"] = "朿",
["筴"] = "朿",
["拺"] = "朿",
["賾"] = "朿",
["鰿"] = "朿",
["擌"] = "朿",
["蹟"] = "朿",
["襀"] = "朿",
["磧"] = "朿",
["洓"] = "朿",
["績"] = "朿",
["勣"] = "朿",
["樍"] = "朿",
["咨"] = "次",
["資"] = "次",
["姿"] = "次",
["粢"] = "次",
["諮"] = "次",
["恣"] = "次",
["澬"] = "次",
["趑"] = "次",
["蠀"] = "次",
["次"] = "次",
["佽"] = "次",
["絘"] = "次",
["薋"] = "次",
["瓷"] = "次",
["茨"] = "次",
["餈"] = "次",
["垐"] = "次",
["楶"] = "次",
["牎"] = "囱",
["窗"] = "囱",
["窓"] = "囱",
["囱"] = "囱",
["總"] = "囱",
["熜"] = "囱",
["傯"] = "囱",
["緫"] = "囱",
["揔"] = "囱",
["偬"] = "囱",
["囪"] = "囱",
["璁"] = "囱",
["驄"] = "囱",
["蔥"] = "囱",
["匆"] = "囱",
["怱"] = "囱",
["葱"] = "囱",
["楤"] = "囱",
["聦"] = "囱",
["繱"] = "囱",
["騘"] = "囱",
["鍯"] = "囱",
["聰"] = "囱",
["愡"] = "囱",
["檧"] = "囱",
["憽"] = "囱",
["幒"] = "囱",
["摐"] = "从",
["鏦"] = "从",
["豵"] = "从",
["嵸"] = "从",
["蓯"] = "从",
["蹤"] = "从",
["縱"] = "从",
["樅"] = "从",
["磫"] = "从",
["熧"] = "从",
["瘲"] = "从",
["暰"] = "从",
["瑽"] = "从",
["從"] = "从",
["从"] = "从",
["慫"] = "从",
["聳"] = "从",
["傱"] = "从",
["躥"] = "竄",
["竄"] = "竄",
["攛"] = "竄",
["鑹"] = "竄",
["嗺"] = "崔",
["催"] = "崔",
["璀"] = "崔",
["皠"] = "崔",
["漼"] = "崔",
["鏙"] = "崔",
["摧"] = "崔",
["崔"] = "崔",
["慛"] = "崔",
["槯"] = "崔",
["繀"] = "崔",
["膗"] = "崔",
["膬"] = "毳",
["毳"] = "毳",
["竁"] = "毳",
["橇"] = "毳",
["荐"] = "存",
["洊"] = "存",
["臶"] = "存",
["栫"] = "存",
["袸"] = "存",
["存"] = "存",
["拵"] = "存",
["村"] = "寸",
["忖"] = "寸",
["刌"] = "寸",
["寸"] = "寸",
["踏"] = "沓",
["濌"] = "沓",
["錔"] = "沓",
["鞜"] = "沓",
["沓"] = "沓",
["涾"] = "沓",
["誻"] = "沓",
["羍"] = "羍",
["達"] = "羍",
["闥"] = "羍",
["撻"] = "羍",
["躂"] = "羍",
["澾"] = "羍",
["薘"] = "羍",
["龖"] = "龖",
["襲"] = "龖",
["馱"] = "大",
["大"] = "大",
["忕"] = "大",
["太"] = "大",
["汰"] = "大",
["汏"] = "大",
["軑"] = "大",
["釱"] = "大",
["达"] = "大",
["杕"] = "大",
["褱"] = "眔",
["鰥"] = "眔",
["鳏"] = "眔",
["嚃"] = "眔",
["眔"] = "眔",
["遝"] = "眔",
["帶"] = "帶",
["蹛"] = "帶",
["艜"] = "帶",
["慸"] = "帶",
["滯"] = "帶",
["懘"] = "帶",
["蔕"] = "帶",
["螮"] = "帶",
["摕"] = "帶",
["僀"] = "帶",
["殢"] = "帶",
["遰"] = "帶",
["墆"] = "帶",
["嵽"] = "帶",
["蒂"] = "帶",
["丹"] = "丹",
["旃"] = "丹",
["栴"] = "丹",
["嚲"] = "單",
["癉"] = "單",
["奲"] = "單",
["驒"] = "單",
["鼉"] = "單",
["磾"] = "單",
["鷤"] = "單",
["觶"] = "單",
["單"] = "單",
["鄲"] = "單",
["殫"] = "單",
["簞"] = "單",
["匰"] = "單",
["襌"] = "單",
["啴"] = "單",
["彈"] = "單",
["貚"] = "單",
["撣"] = "單",
["潬"] = "單",
["僤"] = "單",
["憚"] = "單",
["樿"] = "單",
["燀"] = "單",
["戰"] = "單",
["闡"] = "單",
["繟"] = "單",
["幝"] = "單",
["嘽"] = "單",
["灛"] = "單",
["蟬"] = "單",
["禪"] = "單",
["嬋"] = "單",
["墠"] = "單",
["鱓"] = "單",
["蕇"] = "單",
["皽"] = "旦",
["担"] = "旦",
["胆"] = "旦",
["疸"] = "旦",
["觛"] = "旦",
["亶"] = "旦",
["嬗"] = "旦",
["笪"] = "旦",
["狚"] = "旦",
["旦"] = "旦",
["鴠"] = "旦",
["譠"] = "旦",
["坦"] = "旦",
["但"] = "旦",
["檀"] = "旦",
["壇"] = "旦",
["儃"] = "旦",
["驙"] = "旦",
["袒"] = "旦",
["襢"] = "旦",
["繵"] = "旦",
["膻"] = "旦",
["澶"] = "旦",
["邅"] = "旦",
["鱣"] = "旦",
["饘"] = "旦",
["旜"] = "旦",
["氈"] = "旦",
["鸇"] = "旦",
["顫"] = "旦",
["蟺"] = "旦",
["擅"] = "旦",
["羶"] = "旦",
["呾"] = "旦",
["怛"] = "旦",
["妲"] = "旦",
["靼"] = "旦",
["炟"] = "旦",
["宕"] = "宕",
["菪"] = "宕",
["刀"] = "刀",
["忉"] = "刀",
["魛"] = "刀",
["舠"] = "刀",
["朷"] = "刀",
["叨"] = "刀",
["倒"] = "刀",
["到"] = "刀",
["菿"] = "刀",
["鞀"] = "刀",
["鳭"] = "刀",
["灱"] = "刀",
["菬"] = "刀",
["超"] = "刀",
["怊"] = "刀",
["欩"] = "刀",
["召"] = "刀",
["昭"] = "刀",
["招"] = "刀",
["鉊"] = "刀",
["沼"] = "刀",
["照"] = "刀",
["詔"] = "刀",
["炤"] = "刀",
["弨"] = "刀",
["眧"] = "刀",
["韶"] = "刀",
["佋"] = "刀",
["軺"] = "刀",
["玿"] = "刀",
["柖"] = "刀",
["紹"] = "刀",
["袑"] = "刀",
["綤"] = "刀",
["邵"] = "刀",
["劭"] = "刀",
["卲"] = "刀",
["刁"] = "刀",
["芀"] = "刀",
["貂"] = "刀",
["蛁"] = "刀",
["迢"] = "刀",
["苕"] = "刀",
["髫"] = "刀",
["岧"] = "刀",
["得"] = "得",
["淂"] = "得",
["棏"] = "得",
["登"] = "登",
["璒"] = "登",
["燈"] = "登",
["簦"] = "登",
["蹬"] = "登",
["磴"] = "登",
["鐙"] = "登",
["嶝"] = "登",
["隥"] = "登",
["橙"] = "登",
["凳"] = "登",
["墱"] = "登",
["鼟"] = "登",
["膯"] = "登",
["澄"] = "登",
["鄧"] = "登",
["僜"] = "登",
["覴"] = "登",
["瞪"] = "登",
["憕"] = "登",
["撜"] = "登",
["證"] = "登",
["逖"] = "狄",
["悐"] = "狄",
["狄"] = "狄",
["荻"] = "狄",
["梑"] = "狄",
["櫂"] = "翟",
["濯"] = "翟",
["耀"] = "翟",
["曜"] = "翟",
["燿"] = "翟",
["糶"] = "翟",
["粜"] = "翟",
["嬥"] = "翟",
["藋"] = "翟",
["躍"] = "翟",
["戳"] = "翟",
["擢"] = "翟",
["蠗"] = "翟",
["鸐"] = "翟",
["翟"] = "翟",
["趯"] = "翟",
["籊"] = "翟",
["糴"] = "翟",
["䊮"] = "翟",
["低"] = "氐",
["氐"] = "氐",
["袛"] = "氐",
["羝"] = "氐",
["眡"] = "氐",
["岻"] = "氐",
["奃"] = "氐",
["趆"] = "氐",
["柢"] = "氐",
["邸"] = "氐",
["底"] = "氐",
["詆"] = "氐",
["坻"] = "氐",
["抵"] = "氐",
["牴"] = "氐",
["觝"] = "氐",
["弤"] = "氐",
["軧"] = "氐",
["骶"] = "氐",
["砥"] = "氐",
["胝"] = "氐",
["疷"] = "氐",
["秪"] = "氐",
["泜"] = "氐",
["蚳"] = "氐",
["彽"] = "氐",
["阺"] = "氐",
["貾"] = "氐",
["祗"] = "氐",
["厎"] = "氐",
["茋"] = "氐",
["鴟"] = "氐",
["汦"] = "氐",
["梯"] = "弟",
["睇"] = "弟",
["涕"] = "弟",
["挮"] = "弟",
["鬀"] = "弟",
["剃"] = "弟",
["綈"] = "弟",
["蕛"] = "弟",
["稊"] = "弟",
["苐"] = "弟",
["鵜"] = "弟",
["銻"] = "弟",
["鮷"] = "弟",
["罤"] = "弟",
["弟"] = "弟",
["娣"] = "弟",
["悌"] = "弟",
["第"] = "弟",
["膪"] = "帝",
["揥"] = "帝",
["腣"] = "帝",
["諦"] = "帝",
["偙"] = "帝",
["渧"] = "帝",
["帝"] = "帝",
["楴"] = "帝",
["啼"] = "帝",
["蹄"] = "帝",
["締"] = "帝",
["崹"] = "帝",
["鶙"] = "帝",
["禘"] = "帝",
["啻"] = "帝",
["嫡"] = "帝",
["摘"] = "帝",
["謫"] = "帝",
["讁"] = "帝",
["擿"] = "帝",
["蹢"] = "帝",
["適"] = "帝",
["啇"] = "帝",
["滴"] = "帝",
["鏑"] = "帝",
["甋"] = "帝",
["樀"] = "帝",
["敵"] = "帝",
["典"] = "典",
["錪"] = "典",
["腆"] = "典",
["痶"] = "典",
["淟"] = "典",
["琠"] = "典",
["賟"] = "典",
["晪"] = "典",
["奠"] = "奠",
["鄭"] = "奠",
["擲"] = "奠",
["躑"] = "奠",
["殿"] = "殿",
["澱"] = "殿",
["壂"] = "殿",
["臀"] = "殿",
["盄"] = "弔",
["弔"] = "弔",
["伄"] = "弔",
["曡"] = "疊",
["疊"] = "疊",
["圢"] = "丁",
["町"] = "丁",
["打"] = "丁",
["盯"] = "丁",
["朾"] = "丁",
["丁"] = "丁",
["玎"] = "丁",
["虰"] = "丁",
["揨"] = "丁",
["釘"] = "丁",
["靪"] = "丁",
["仃"] = "丁",
["叮"] = "丁",
["疔"] = "丁",
["頂"] = "丁",
["奵"] = "丁",
["耵"] = "丁",
["酊"] = "丁",
["葶"] = "丁",
["矴"] = "丁",
["訂"] = "丁",
["飣"] = "丁",
["汀"] = "丁",
["艼"] = "丁",
["庁"] = "丁",
["婷"] = "丁",
["停"] = "丁",
["聤"] = "丁",
["渟"] = "丁",
["楟"] = "丁",
["亭"] = "丁",
["鼎"] = "鼎",
["薡"] = "鼎",
["濎"] = "鼎",
["疼"] = "冬",
["冬"] = "冬",
["苳"] = "冬",
["笗"] = "冬",
["炵"] = "冬",
["佟"] = "冬",
["鼕"] = "冬",
["鉖"] = "冬",
["終"] = "冬",
["螽"] = "冬",
["鼨"] = "冬",
["柊"] = "冬",
["鴤"] = "冬",
["泈"] = "冬",
["蔠"] = "冬",
["東"] = "東",
["菄"] = "東",
["鶇"] = "東",
["倲"] = "東",
["涷"] = "東",
["蝀"] = "東",
["凍"] = "東",
["鯟"] = "東",
["崠"] = "東",
["埬"] = "東",
["棟"] = "東",
["兜"] = "兜",
["篼"] = "兜",
["斗"] = "斗",
["抖"] = "斗",
["蚪"] = "斗",
["枓"] = "斗",
["阧"] = "斗",
["斢"] = "斗",
["鈄"] = "斗",
["侸"] = "豆",
["剅"] = "豆",
["郖"] = "豆",
["鬪"] = "豆",
["鋀"] = "豆",
["頭"] = "豆",
["豆"] = "豆",
["逗"] = "豆",
["痘"] = "豆",
["梪"] = "豆",
["脰"] = "豆",
["荳"] = "豆",
["浢"] = "豆",
["餖"] = "豆",
["毭"] = "豆",
["豎"] = "豆",
["裋"] = "豆",
["短"] = "豆",
["鬥"] = "鬥",
["鬭"] = "鬥",
["瑇"] = "毒",
["纛"] = "毒",
["碡"] = "毒",
["毒"] = "毒",
["蝳"] = "毒",
["揣"] = "耑",
["褍"] = "耑",
["惴"] = "耑",
["圌"] = "耑",
["篅"] = "耑",
["瑞"] = "耑",
["端"] = "耑",
["剬"] = "耑",
["偳"] = "耑",
["鍴"] = "耑",
["耑"] = "耑",
["踹"] = "耑",
["湍"] = "耑",
["煓"] = "耑",
["貒"] = "耑",
["諯"] = "耑",
["顓"] = "耑",
["喘"] = "耑",
["遄"] = "耑",
["輲"] = "耑",
["椯"] = "耑",
["歂"] = "耑",
["腨"] = "耑",
["鍛"] = "段",
["腶"] = "段",
["碫"] = "段",
["瑖"] = "段",
["緞"] = "段",
["段"] = "段",
["椴"] = "段",
["毈"] = "段",
["毤"] = "兌",
["蛻"] = "兌",
["娧"] = "兌",
["駾"] = "兌",
["裞"] = "兌",
["兌"] = "兌",
["綐"] = "兌",
["銳"] = "兌",
["帨"] = "兌",
["稅"] = "兌",
["說"] = "兌",
["涚"] = "兌",
["䫄"] = "兌",
["脫"] = "兌",
["侻"] = "兌",
["挩"] = "兌",
["莌"] = "兌",
["梲"] = "兌",
["鮵"] = "兌",
["敓"] = "兌",
["痥"] = "兌",
["悅"] = "兌",
["閱"] = "兌",
["對"] = "對",
["轛"] = "對",
["薱"] = "對",
["濧"] = "對",
["懟"] = "對",
["弴"] = "敦",
["敦"] = "敦",
["錞"] = "敦",
["憝"] = "敦",
["憞"] = "敦",
["譈"] = "敦",
["鐓"] = "敦",
["鷻"] = "敦",
["鶉"] = "敦",
["惇"] = "敦",
["墩"] = "敦",
["驐"] = "敦",
["燉"] = "敦",
["焞"] = "敦",
["啍"] = "敦",
["暾"] = "敦",
["噋"] = "敦",
["諄"] = "敦",
["埻"] = "敦",
["稕"] = "敦",
["醇"] = "敦",
["淳"] = "敦",
["犉"] = "敦",
["躉"] = "屯",
["窀"] = "屯",
["沌"] = "屯",
["盹"] = "屯",
["頓"] = "屯",
["扽"] = "屯",
["黗"] = "屯",
["吨"] = "屯",
["屯"] = "屯",
["飩"] = "屯",
["豘"] = "屯",
["軘"] = "屯",
["坉"] = "屯",
["邨"] = "屯",
["忳"] = "屯",
["芚"] = "屯",
["庉"] = "屯",
["囤"] = "屯",
["迍"] = "屯",
["杶"] = "屯",
["瑃"] = "屯",
["椿"] = "屯",
["偆"] = "屯",
["肫"] = "屯",
["訰"] = "屯",
["純"] = "屯",
["春"] = "屯",
["蠢"] = "屯",
["踳"] = "屯",
["惷"] = "屯",
["蒓"] = "屯",
["賰"] = "屯",
["鬊"] = "屯",
["盾"] = "盾",
["遁"] = "盾",
["循"] = "盾",
["揗"] = "盾",
["楯"] = "盾",
["輴"] = "盾",
["腯"] = "盾",
["鍎"] = "盾",
["多"] = "多",
["哆"] = "多",
["跢"] = "多",
["痑"] = "多",
["爹"] = "多",
["陊"] = "多",
["橠"] = "多",
["袲"] = "多",
["奓"] = "多",
["栘"] = "多",
["黟"] = "多",
["趍"] = "多",
["誃"] = "多",
["簃"] = "多",
["眵"] = "多",
["恀"] = "多",
["侈"] = "多",
["姼"] = "多",
["鉹"] = "多",
["垑"] = "多",
["袳"] = "多",
["卶"] = "多",
["宜"] = "多",
["誼"] = "多",
["竩"] = "多",
["移"] = "多",
["迻"] = "多",
["扅"] = "多",
["拸"] = "多",
["熪"] = "多",
["謻"] = "多",
["蛥"] = "多",
["桗"] = "朵",
["挆"] = "朵",
["朵"] = "朵",
["朶"] = "朵",
["躲"] = "朵",
["剁"] = "朵",
["垛"] = "朵",
["呃"] = "戹",
["阸"] = "戹",
["厄"] = "戹",
["蚅"] = "戹",
["扼"] = "戹",
["軛"] = "戹",
["戹"] = "戹",
["豟"] = "戹",
["呝"] = "戹",
["軶"] = "戹",
["耐"] = "而",
["耏"] = "而",
["而"] = "而",
["鮞"] = "而",
["鴯"] = "而",
["栭"] = "而",
["陑"] = "而",
["峏"] = "而",
["輀"] = "而",
["胹"] = "而",
["洏"] = "而",
["髵"] = "而",
["恧"] = "而",
["掜"] = "兒",
["誽"] = "兒",
["倪"] = "兒",
["蜺"] = "兒",
["霓"] = "兒",
["郳"] = "兒",
["齯"] = "兒",
["婗"] = "兒",
["輗"] = "兒",
["棿"] = "兒",
["猊"] = "兒",
["麑"] = "兒",
["貎"] = "兒",
["鯢"] = "兒",
["兒"] = "兒",
["觬"] = "兒",
["堄"] = "兒",
["晲"] = "兒",
["睨"] = "兒",
["唲"] = "兒",
["隉"] = "兒",
["鶂"] = "兒",
["鬩"] = "兒",
["妳"] = "尔",
["弥"] = "尔",
["尒"] = "尔",
["尔"] = "尔",
["迩"] = "尔",
["你"] = "尔",
["苶"] = "尔",
["佴"] = "耳",
["麛"] = "耳",
["弭"] = "耳",
["渳"] = "耳",
["葞"] = "耳",
["髶"] = "耳",
["聻"] = "耳",
["恥"] = "耳",
["咡"] = "耳",
["誀"] = "耳",
["耳"] = "耳",
["洱"] = "耳",
["駬"] = "耳",
["餌"] = "耳",
["珥"] = "耳",
["衈"] = "耳",
["毦"] = "耳",
["刵"] = "耳",
["眲"] = "耳",
["嬭"] = "爾",
["禰"] = "爾",
["镾"] = "爾",
["薾"] = "爾",
["檷"] = "爾",
["鑈"] = "爾",
["瀰"] = "爾",
["獼"] = "爾",
["瓕"] = "爾",
["彌"] = "爾",
["鸍"] = "爾",
["擟"] = "爾",
["濔"] = "爾",
["壐"] = "爾",
["璽"] = "爾",
["爾"] = "爾",
["邇"] = "爾",
["獮"] = "爾",
["籋"] = "爾",
["膩"] = "二",
["二"] = "二",
["弍"] = "二",
["貳"] = "二",
["樲"] = "二",
["廢"] = "發",
["癈"] = "發",
["橃"] = "發",
["撥"] = "發",
["鱍"] = "發",
["襏"] = "發",
["驋"] = "發",
["鏺"] = "發",
["潑"] = "發",
["醱"] = "發",
["砭"] = "乏",
["貶"] = "乏",
["窆"] = "乏",
["泛"] = "乏",
["姂"] = "乏",
["䒦"] = "乏",
["柉"] = "乏",
["覂"] = "乏",
["眨"] = "乏",
["鴔"] = "乏",
["乏"] = "乏",
["茷"] = "伐",
["筏"] = "伐",
["伐"] = "伐",
["閥"] = "伐",
["垡"] = "伐",
["栰"] = "伐",
["罰"] = "罰",
["藅"] = "罰",
["法"] = "法",
["灋"] = "法",
["皤"] = "番",
["嶓"] = "番",
["番"] = "番",
["播"] = "番",
["譒"] = "番",
["鄱"] = "番",
["潘"] = "番",
["蟠"] = "番",
["磻"] = "番",
["蕃"] = "番",
["轓"] = "番",
["鱕"] = "番",
["鐇"] = "番",
["藩"] = "番",
["籓"] = "番",
["橎"] = "番",
["翻"] = "番",
["幡"] = "番",
["飜"] = "番",
["旛"] = "番",
["繙"] = "番",
["燔"] = "番",
["蹯"] = "番",
["膰"] = "番",
["羳"] = "番",
["鷭"] = "番",
["璠"] = "番",
["襎"] = "番",
["墦"] = "番",
["嵐"] = "凡",
["葻"] = "凡",
["釩"] = "凡",
["汎"] = "凡",
["凡"] = "凡",
["帆"] = "凡",
["颿"] = "凡",
["軓"] = "凡",
["梵"] = "凡",
["芃"] = "凡",
["風"] = "凡",
["飌"] = "凡",
["楓"] = "凡",
["猦"] = "凡",
["偑"] = "凡",
["檒"] = "凡",
["瘋"] = "凡",
["諷"] = "凡",
["堸"] = "凡",
["渢"] = "凡",
["鳳"] = "凡",
["攀"] = "棥",
["襻"] = "棥",
["棥"] = "棥",
["礬"] = "棥",
["樊"] = "棥",
["蠜"] = "棥",
["煩"] = "煩",
["薠"] = "煩",
["繁"] = "緐",
["緐"] = "緐",
["蘩"] = "緐",
["瀿"] = "緐",
["瀪"] = "緐",
["扳"] = "反",
["板"] = "反",
["版"] = "反",
["瓪"] = "反",
["昄"] = "反",
["鈑"] = "反",
["蝂"] = "反",
["眅"] = "反",
["阪"] = "反",
["魬"] = "反",
["粄"] = "反",
["反"] = "反",
["返"] = "反",
["坂"] = "反",
["畈"] = "反",
["販"] = "反",
["汳"] = "反",
["飯"] = "反",
["氾"] = "氾",
["犯"] = "氾",
["範"] = "氾",
["范"] = "氾",
["笵"] = "氾",
["匚"] = "匚",
["匠"] = "匚",
["縍"] = "方",
["榜"] = "方",
["蒡"] = "方",
["牓"] = "方",
["螃"] = "方",
["搒"] = "方",
["舫"] = "方",
["謗"] = "方",
["雱"] = "方",
["磅"] = "方",
["霶"] = "方",
["滂"] = "方",
["鎊"] = "方",
["髈"] = "方",
["彷"] = "方",
["趽"] = "方",
["房"] = "方",
["篣"] = "方",
["騯"] = "方",
["傍"] = "方",
["膀"] = "方",
["旁"] = "方",
["徬"] = "方",
["閍"] = "方",
["祊"] = "方",
["嗙"] = "方",
["方"] = "方",
["汸"] = "方",
["坊"] = "方",
["邡"] = "方",
["枋"] = "方",
["鈁"] = "方",
["蚄"] = "方",
["肪"] = "方",
["鴋"] = "方",
["牥"] = "方",
["昉"] = "方",
["瓬"] = "方",
["放"] = "方",
["倣"] = "方",
["芳"] = "方",
["淓"] = "方",
["妨"] = "方",
["紡"] = "方",
["仿"] = "方",
["髣"] = "方",
["鶭"] = "方",
["訪"] = "方",
["防"] = "方",
["魴"] = "方",
["圮"] = "妃",
["妃"] = "妃",
["啡"] = "非",
["排"] = "非",
["俳"] = "非",
["輫"] = "非",
["悲"] = "非",
["棑"] = "非",
["輩"] = "非",
["裴"] = "非",
["徘"] = "非",
["琲"] = "非",
["痱"] = "非",
["非"] = "非",
["扉"] = "非",
["緋"] = "非",
["猆"] = "非",
["斐"] = "非",
["馡"] = "非",
["騑"] = "非",
["誹"] = "非",
["餥"] = "非",
["匪"] = "非",
["棐"] = "非",
["蜚"] = "非",
["篚"] = "非",
["榧"] = "非",
["鯡"] = "非",
["霏"] = "非",
["菲"] = "非",
["婔"] = "非",
["裶"] = "非",
["悱"] = "非",
["奜"] = "非",
["腓"] = "非",
["陫"] = "非",
["屝"] = "非",
["厞"] = "非",
["翡"] = "非",
["飛"] = "飛",
["騛"] = "飛",
["蜰"] = "肥",
["肥"] = "肥",
["淝"] = "肥",
["萉"] = "肥",
["朏"] = "朏",
["昢"] = "朏",
["扮"] = "分",
["盼"] = "分",
["頒"] = "分",
["朌"] = "分",
["鳻"] = "分",
["份"] = "分",
["汃"] = "分",
["玢"] = "分",
["邠"] = "分",
["攽"] = "分",
["砏"] = "分",
["貧"] = "分",
["湓"] = "分",
["盆"] = "分",
["葐"] = "分",
["坌"] = "分",
["坋"] = "分",
["分"] = "分",
["吩"] = "分",
["粉"] = "分",
["黺"] = "分",
["芬"] = "分",
["紛"] = "分",
["衯"] = "分",
["棻"] = "分",
["氛"] = "分",
["雰"] = "分",
["忿"] = "分",
["魵"] = "分",
["汾"] = "分",
["羒"] = "分",
["枌"] = "分",
["棼"] = "分",
["妢"] = "分",
["梤"] = "分",
["馚"] = "分",
["鼢"] = "分",
["蚡"] = "分",
["弅"] = "分",
["秎"] = "分",
["糞"] = "糞",
["瀵"] = "糞",
["邦"] = "丰",
["梆"] = "丰",
["垹"] = "丰",
["肨"] = "丰",
["蚌"] = "丰",
["玤"] = "丰",
["棒"] = "丰",
["蜯"] = "丰",
["琫"] = "丰",
["菶"] = "丰",
["俸"] = "丰",
["髼"] = "丰",
["蜂"] = "丰",
["韸"] = "丰",
["蓬"] = "丰",
["篷"] = "丰",
["唪"] = "丰",
["埲"] = "丰",
["丰"] = "丰",
["妦"] = "丰",
["仹"] = "丰",
["峯"] = "丰",
["峰"] = "丰",
["鋒"] = "丰",
["烽"] = "丰",
["蠭"] = "丰",
["桻"] = "丰",
["莑"] = "丰",
["夆"] = "丰",
["捧"] = "丰",
["逢"] = "丰",
["縫"] = "丰",
["漨"] = "丰",
["捀"] = "丰",
["奉"] = "丰",
["豐"] = "豐",
["蘴"] = "豐",
["灃"] = "豐",
["寷"] = "豐",
["麷"] = "豐",
["酆"] = "豐",
["幫"] = "封",
["鞤"] = "封",
["封"] = "封",
["葑"] = "封",
["犎"] = "封",
["崶"] = "封",
["湗"] = "封",
["胮"] = "夆",
["逄"] = "夆",
["舽"] = "夆",
["寶"] = "缶",
["珤"] = "缶",
["缶"] = "缶",
["缹"] = "缶",
["趺"] = "夫",
["肤"] = "夫",
["邞"] = "夫",
["鈇"] = "夫",
["衭"] = "夫",
["玞"] = "夫",
["夫"] = "夫",
["鳺"] = "夫",
["扶"] = "夫",
["妋"] = "夫",
["麩"] = "夫",
["蚨"] = "夫",
["芙"] = "夫",
["颫"] = "夫",
["枎"] = "夫",
["敷"] = "旉",
["璷"] = "旉",
["沛"] = "巿",
["芾"] = "巿",
["伂"] = "巿",
["霈"] = "巿",
["旆"] = "巿",
["肺"] = "巿",
["杮"] = "巿",
["巿"] = "巿",
["鄪"] = "弗",
["費"] = "弗",
["砩"] = "弗",
["沸"] = "弗",
["疿"] = "弗",
["茀"] = "弗",
["髴"] = "弗",
["昲"] = "弗",
["靅"] = "弗",
["狒"] = "弗",
["怫"] = "弗",
["胇"] = "弗",
["艴"] = "弗",
["弗"] = "弗",
["紼"] = "弗",
["柫"] = "弗",
["笰"] = "弗",
["拂"] = "弗",
["刜"] = "弗",
["彿"] = "弗",
["佛"] = "弗",
["坲"] = "弗",
["咈"] = "弗",
["炥"] = "弗",
["絥"] = "伏",
["伏"] = "伏",
["垘"] = "伏",
["茯"] = "伏",
["洑"] = "伏",
["栿"] = "伏",
["殍"] = "孚",
["脬"] = "孚",
["捊"] = "孚",
["莩"] = "孚",
["哹"] = "孚",
["浮"] = "孚",
["蜉"] = "孚",
["桴"] = "孚",
["罦"] = "孚",
["琈"] = "孚",
["粰"] = "孚",
["烰"] = "孚",
["艀"] = "孚",
["俘"] = "孚",
["孚"] = "孚",
["孵"] = "孚",
["稃"] = "孚",
["郛"] = "孚",
["筟"] = "孚",
["菔"] = "服",
["服"] = "服",
["鵩"] = "服",
["箙"] = "服",
["棴"] = "服",
["富"] = "畐",
["輻"] = "畐",
["副"] = "畐",
["褔"] = "畐",
["蔔"] = "畐",
["匐"] = "畐",
["逼"] = "畐",
["偪"] = "畐",
["幅"] = "畐",
["楅"] = "畐",
["湢"] = "畐",
["堛"] = "畐",
["愊"] = "畐",
["踾"] = "畐",
["揊"] = "畐",
["稫"] = "畐",
["畐"] = "畐",
["疈"] = "畐",
["腷"] = "畐",
["鶝"] = "畐",
["福"] = "畐",
["蝠"] = "畐",
["葍"] = "畐",
["愎"] = "复",
["鍑"] = "复",
["覆"] = "复",
["復"] = "复",
["椱"] = "复",
["複"] = "复",
["馥"] = "复",
["腹"] = "复",
["輹"] = "复",
["蝮"] = "复",
["蕧"] = "复",
["复"] = "复",
["鰒"] = "复",
["澓"] = "复",
["豧"] = "甫",
["縛"] = "甫",
["逋"] = "甫",
["餔"] = "甫",
["晡"] = "甫",
["庯"] = "甫",
["陠"] = "甫",
["鵏"] = "甫",
["峬"] = "甫",
["誧"] = "甫",
["秿"] = "甫",
["鯆"] = "甫",
["補"] = "甫",
["圃"] = "甫",
["鋪"] = "甫",
["痡"] = "甫",
["浦"] = "甫",
["烳"] = "甫",
["溥"] = "甫",
["悑"] = "甫",
["酺"] = "甫",
["匍"] = "甫",
["蜅"] = "甫",
["脯"] = "甫",
["葡"] = "甫",
["蒱"] = "甫",
["蒲"] = "甫",
["簿"] = "甫",
["捕"] = "甫",
["哺"] = "甫",
["簠"] = "甫",
["甫"] = "甫",
["黼"] = "甫",
["莆"] = "甫",
["俌"] = "甫",
["郙"] = "甫",
["傅"] = "甫",
["搏"] = "甫",
["麱"] = "甫",
["尃"] = "甫",
["榑"] = "甫",
["輔"] = "甫",
["鬴"] = "甫",
["賻"] = "甫",
["博"] = "甫",
["髆"] = "甫",
["鎛"] = "甫",
["猼"] = "甫",
["餺"] = "甫",
["簙"] = "甫",
["鑮"] = "甫",
["欂"] = "甫",
["蒪"] = "甫",
["膊"] = "甫",
["薄"] = "甫",
["礡"] = "甫",
["抪"] = "父",
["布"] = "父",
["佈"] = "父",
["怖"] = "父",
["斧"] = "父",
["父"] = "父",
["蚥"] = "父",
["滏"] = "父",
["釜"] = "父",
["鉜"] = "付",
["跗"] = "付",
["柎"] = "付",
["腑"] = "付",
["俯"] = "付",
["府"] = "付",
["付"] = "付",
["咐"] = "付",
["怤"] = "付",
["泭"] = "付",
["姇"] = "付",
["紨"] = "付",
["拊"] = "付",
["弣"] = "付",
["符"] = "付",
["苻"] = "付",
["坿"] = "付",
["腐"] = "付",
["附"] = "付",
["駙"] = "付",
["鮒"] = "付",
["袝"] = "付",
["蚹"] = "付",
["胕"] = "付",
["阜"] = "阜",
["埠"] = "阜",
["負"] = "負",
["萯"] = "負",
["蝜"] = "負",
["偩"] = "負",
["丐"] = "丐",
["匄"] = "丐",
["訐"] = "干",
["肝"] = "干",
["竿"] = "干",
["干"] = "干",
["奸"] = "干",
["玕"] = "干",
["汗"] = "干",
["迀"] = "干",
["忓"] = "干",
["鳱"] = "干",
["邗"] = "干",
["秆"] = "干",
["矸"] = "干",
["仠"] = "干",
["皯"] = "干",
["衦"] = "干",
["簳"] = "干",
["擀"] = "干",
["旰"] = "干",
["盰"] = "干",
["幹"] = "干",
["杆"] = "干",
["骭"] = "干",
["桿"] = "干",
["趕"] = "干",
["稈"] = "干",
["刊"] = "干",
["靬"] = "干",
["衎"] = "干",
["犴"] = "干",
["豻"] = "干",
["頇"] = "干",
["岸"] = "干",
["鼾"] = "干",
["罕"] = "干",
["焊"] = "干",
["蔊"] = "干",
["虷"] = "干",
["旱"] = "干",
["皔"] = "干",
["釬"] = "干",
["扞"] = "干",
["閈"] = "干",
["馯"] = "干",
["捍"] = "干",
["悍"] = "干",
["銲"] = "干",
["垾"] = "干",
["駻"] = "干",
["睅"] = "干",
["娨"] = "干",
["飦"] = "干",
["赶"] = "干",
["軒"] = "干",
["蓒"] = "干",
["酐"] = "干",
["澣"] = "干",
["紺"] = "甘",
["姏"] = "甘",
["嵌"] = "甘",
["甘"] = "甘",
["柑"] = "甘",
["苷"] = "甘",
["泔"] = "甘",
["坩"] = "甘",
["蚶"] = "甘",
["酣"] = "甘",
["甝"] = "甘",
["魽"] = "甘",
["邯"] = "甘",
["鉗"] = "甘",
["箝"] = "甘",
["黚"] = "甘",
["拑"] = "甘",
["撖"] = "敢",
["澉"] = "敢",
["噉"] = "敢",
["敢"] = "敢",
["橄"] = "敢",
["瞰"] = "敢",
["闞"] = "敢",
["憨"] = "敢",
["譀"] = "敢",
["巖"] = "敢",
["礹"] = "敢",
["豃"] = "敢",
["讝"] = "敢",
["玁"] = "敢",
["嚴"] = "敢",
["儼"] = "敢",
["曮"] = "敢",
["釅"] = "敢",
["乾"] = "倝",
["漧"] = "倝",
["倝"] = "倝",
["榦"] = "倝",
["韓"] = "倝",
["鶾"] = "倝",
["翰"] = "倝",
["瀚"] = "倝",
["螒"] = "倝",
["雗"] = "倝",
["斡"] = "倝",
["岡"] = "岡",
["崗"] = "岡",
["剛"] = "岡",
["掆"] = "岡",
["鋼"] = "岡",
["綱"] = "岡",
["犅"] = "岡",
["堈"] = "岡",
["羔"] = "羔",
["糕"] = "羔",
["餻"] = "羔",
["窯"] = "羔",
["溔"] = "羔",
["高"] = "高",
["膏"] = "高",
["篙"] = "高",
["稿"] = "高",
["稾"] = "高",
["槁"] = "高",
["縞"] = "高",
["暠"] = "高",
["槀"] = "高",
["藁"] = "高",
["燺"] = "高",
["薧"] = "高",
["犒"] = "高",
["蒿"] = "高",
["藃"] = "高",
["豪"] = "高",
["毫"] = "高",
["壕"] = "高",
["濠"] = "高",
["鎬"] = "高",
["滈"] = "高",
["鄗"] = "高",
["鰝"] = "高",
["薃"] = "高",
["搞"] = "高",
["敲"] = "高",
["髇"] = "高",
["嗃"] = "高",
["巐"] = "高",
["歊"] = "高",
["謞"] = "高",
["熇"] = "高",
["碻"] = "高",
["塙"] = "高",
["毃"] = "高",
["翯"] = "高",
["瀥"] = "高",
["睾"] = "皋",
["皋"] = "皋",
["臯"] = "皐",
["橰"] = "皐",
["鷱"] = "皐",
["翱"] = "皐",
["翺"] = "皐",
["噑"] = "皐",
["獋"] = "皐",
["暭"] = "皐",
["夰"] = "夰",
["昦"] = "夰",
["杲"] = "杲",
["菒"] = "杲",
["造"] = "告",
["慥"] = "告",
["艁"] = "告",
["糙"] = "告",
["告"] = "告",
["誥"] = "告",
["郜"] = "告",
["祰"] = "告",
["靠"] = "告",
["浩"] = "告",
["晧"] = "告",
["皓"] = "告",
["窖"] = "告",
["簉"] = "告",
["梏"] = "告",
["硞"] = "告",
["牿"] = "告",
["鵠"] = "告",
["酷"] = "告",
["焅"] = "告",
["嚳"] = "告",
["頶"] = "告",
["戕"] = "戈",
["戈"] = "戈",
["划"] = "戈",
["革"] = "革",
["諽"] = "革",
["緙"] = "革",
["鞂"] = "革",
["隔"] = "鬲",
["膈"] = "鬲",
["搹"] = "鬲",
["鬲"] = "鬲",
["槅"] = "鬲",
["嗝"] = "鬲",
["虉"] = "鬲",
["翮"] = "鬲",
["滆"] = "鬲",
["蒚"] = "鬲",
["鎘"] = "鬲",
["髂"] = "各",
["路"] = "各",
["輅"] = "各",
["賂"] = "各",
["虂"] = "各",
["露"] = "各",
["潞"] = "各",
["鷺"] = "各",
["璐"] = "各",
["簬"] = "各",
["簵"] = "各",
["洛"] = "各",
["駱"] = "各",
["絡"] = "各",
["酪"] = "各",
["烙"] = "各",
["雒"] = "各",
["珞"] = "各",
["硌"] = "各",
["袼"] = "各",
["笿"] = "各",
["鉻"] = "各",
["鮥"] = "各",
["鵅"] = "各",
["挌"] = "各",
["落"] = "各",
["各"] = "各",
["胳"] = "各",
["閣"] = "各",
["格"] = "各",
["擱"] = "各",
["恪"] = "各",
["愙"] = "各",
["貉"] = "各",
["狢"] = "各",
["佫"] = "各",
["略"] = "各",
["茖"] = "各",
["骼"] = "各",
["觡"] = "各",
["蛒"] = "各",
["敋"] = "各",
["客"] = "各",
["喀"] = "各",
["揢"] = "各",
["額"] = "各",
["峉"] = "各",
["頟"] = "各",
["詻"] = "各",
["垎"] = "各",
["楁"] = "各",
["艱"] = "艮",
["齦"] = "艮",
["眼"] = "艮",
["蛝"] = "艮",
["限"] = "艮",
["硍"] = "艮",
["豤"] = "艮",
["詪"] = "艮",
["根"] = "艮",
["跟"] = "艮",
["珢"] = "艮",
["艮"] = "艮",
["茛"] = "艮",
["墾"] = "艮",
["懇"] = "艮",
["垠"] = "艮",
["泿"] = "艮",
["痕"] = "艮",
["拫"] = "艮",
["鞎"] = "艮",
["很"] = "艮",
["恨"] = "艮",
["銀"] = "艮",
["檭"] = "艮",
["烜"] = "亘",
["貆"] = "亘",
["峘"] = "亘",
["狟"] = "亘",
["絙"] = "亘",
["荁"] = "亘",
["宣"] = "亘",
["咺"] = "亘",
["暅"] = "亘",
["桓"] = "亘",
["洹"] = "亘",
["揎"] = "亘",
["愃"] = "亘",
["瑄"] = "亘",
["渲"] = "亘",
["喧"] = "亘",
["萱"] = "亘",
["暄"] = "亘",
["諠"] = "亘",
["翧"] = "亘",
["蝖"] = "亘",
["垣"] = "亘",
["楦"] = "亘",
["唐"] = "庚",
["煻"] = "庚",
["糖"] = "庚",
["搪"] = "庚",
["蓎"] = "庚",
["瑭"] = "庚",
["餹"] = "庚",
["篖"] = "庚",
["螗"] = "庚",
["塘"] = "庚",
["鶶"] = "庚",
["赯"] = "庚",
["磄"] = "庚",
["溏"] = "庚",
["傏"] = "庚",
["鎕"] = "庚",
["榶"] = "庚",
["嵣"] = "庚",
["庚"] = "庚",
["賡"] = "庚",
["鹒"] = "庚",
["康"] = "庚",
["穅"] = "庚",
["糠"] = "庚",
["槺"] = "庚",
["漮"] = "庚",
["躿"] = "庚",
["慷"] = "庚",
["耿"] = "耿",
["褧"] = "耿",
["缸"] = "工",
["篢"] = "工",
["贑"] = "工",
["涳"] = "工",
["江"] = "工",
["肛"] = "工",
["扛"] = "工",
["杠"] = "工",
["豇"] = "工",
["茳"] = "工",
["釭"] = "工",
["矼"] = "工",
["玒"] = "工",
["虹"] = "工",
["腔"] = "工",
["崆"] = "工",
["羫"] = "工",
["控"] = "工",
["椌"] = "工",
["悾"] = "工",
["跫"] = "工",
["啌"] = "工",
["谾"] = "工",
["舡"] = "工",
["缻"] = "工",
["項"] = "工",
["屸"] = "工",
["功"] = "工",
["工"] = "工",
["疘"] = "工",
["魟"] = "工",
["攻"] = "工",
["愩"] = "工",
["碽"] = "工",
["貢"] = "工",
["羾"] = "工",
["空"] = "工",
["箜"] = "工",
["硿"] = "工",
["埪"] = "工",
["鵼"] = "工",
["倥"] = "工",
["鞚"] = "工",
["叿"] = "工",
["嗊"] = "工",
["訌"] = "工",
["紅"] = "工",
["仜"] = "工",
["葒"] = "工",
["渱"] = "工",
["鴻"] = "工",
["汞"] = "工",
["澒"] = "工",
["鞏"] = "工",
["巩"] = "工",
["銎"] = "工",
["恐"] = "工",
["蛩"] = "工",
["筇"] = "工",
["桏"] = "工",
["邛"] = "工",
["弓"] = "弓",
["穹"] = "弓",
["焪"] = "弓",
["芎"] = "弓",
["慃"] = "公",
["袞"] = "公",
["蔉"] = "公",
["滾"] = "公",
["忩"] = "公",
["鬆"] = "公",
["凇"] = "公",
["公"] = "公",
["蚣"] = "公",
["翁"] = "公",
["螉"] = "公",
["蓊"] = "公",
["嗡"] = "公",
["滃"] = "公",
["暡"] = "公",
["勜"] = "公",
["塕"] = "公",
["瓮"] = "公",
["崧"] = "公",
["菘"] = "公",
["硹"] = "公",
["淞"] = "公",
["蜙"] = "公",
["倯"] = "公",
["松"] = "公",
["訟"] = "公",
["頌"] = "公",
["忪"] = "公",
["彸"] = "公",
["衳"] = "公",
["伀"] = "公",
["妐"] = "公",
["炂"] = "公",
["鈆"] = "公",
["肱"] = "厷",
["宏"] = "厷",
["閎"] = "厷",
["吰"] = "厷",
["竑"] = "厷",
["谹"] = "厷",
["翃"] = "厷",
["紘"] = "厷",
["鈜"] = "厷",
["浤"] = "厷",
["耾"] = "厷",
["雄"] = "厷",
["躬"] = "躬",
["匑"] = "躬",
["躳"] = "躬",
["窮"] = "躬",
["藭"] = "躬",
["港"] = "共",
["巷"] = "共",
["衖"] = "共",
["鬨"] = "共",
["烘"] = "共",
["洪"] = "共",
["鉷"] = "共",
["谼"] = "共",
["葓"] = "共",
["哄"] = "共",
["蕻"] = "共",
["恭"] = "共",
["供"] = "共",
["龔"] = "共",
["珙"] = "共",
["共"] = "共",
["髸"] = "共",
["拱"] = "共",
["拲"] = "共",
["蛬"] = "共",
["栱"] = "共",
["輁"] = "共",
["舼"] = "共",
["輂"] = "共",
["溝"] = "冓",
["篝"] = "冓",
["鞲"] = "冓",
["韝"] = "冓",
["褠"] = "冓",
["冓"] = "冓",
["構"] = "冓",
["購"] = "冓",
["媾"] = "冓",
["覯"] = "冓",
["遘"] = "冓",
["煹"] = "冓",
["搆"] = "冓",
["講"] = "冓",
["耩"] = "冓",
["傋"] = "冓",
["斠"] = "冓",
["箇"] = "古",
["個"] = "古",
["居"] = "古",
["橭"] = "古",
["嫴"] = "古",
["姑"] = "古",
["辜"] = "古",
["酤"] = "古",
["蛄"] = "古",
["鴣"] = "古",
["沽"] = "古",
["盬"] = "古",
["古"] = "古",
["罟"] = "古",
["估"] = "古",
["鈷"] = "古",
["詁"] = "古",
["牯"] = "古",
["故"] = "古",
["固"] = "古",
["稒"] = "古",
["痼"] = "古",
["錮"] = "古",
["鯝"] = "古",
["棝"] = "古",
["凅"] = "古",
["枯"] = "古",
["軲"] = "古",
["跍"] = "古",
["骷"] = "古",
["苦"] = "古",
["葫"] = "古",
["餬"] = "古",
["瑚"] = "古",
["湖"] = "古",
["鶘"] = "古",
["猢"] = "古",
["醐"] = "古",
["糊"] = "古",
["箶"] = "古",
["蝴"] = "古",
["胡"] = "古",
["瓳"] = "古",
["怙"] = "古",
["祜"] = "古",
["岵"] = "古",
["婟"] = "古",
["楛"] = "古",
["据"] = "古",
["裾"] = "古",
["琚"] = "古",
["椐"] = "古",
["鶋"] = "古",
["蜛"] = "古",
["崌"] = "古",
["涺"] = "古",
["腒"] = "古",
["鋸"] = "古",
["倨"] = "古",
["踞"] = "古",
["涸"] = "古",
["螸"] = "谷",
["裕"] = "谷",
["容"] = "谷",
["溶"] = "谷",
["鎔"] = "谷",
["蓉"] = "谷",
["傛"] = "谷",
["褣"] = "谷",
["搈"] = "谷",
["瑢"] = "谷",
["嵱"] = "谷",
["熔"] = "谷",
["榕"] = "谷",
["塎"] = "谷",
["谷"] = "谷",
["唂"] = "谷",
["焀"] = "谷",
["俗"] = "谷",
["欲"] = "谷",
["浴"] = "谷",
["鵒"] = "谷",
["峪"] = "谷",
["鋊"] = "谷",
["輍"] = "谷",
["慾"] = "谷",
["股"] = "股",
["羖"] = "股",
["顝"] = "骨",
["滑"] = "骨",
["猾"] = "骨",
["磆"] = "骨",
["螖"] = "骨",
["鶻"] = "骨",
["嗗"] = "骨",
["骨"] = "骨",
["縎"] = "骨",
["愲"] = "骨",
["蓇"] = "骨",
["尳"] = "骨",
["榾"] = "骨",
["搰"] = "骨",
["核"] = "骨",
["皷"] = "鼓",
["鼓"] = "鼓",
["瞽"] = "鼓",
["瓜"] = "瓜",
["坬"] = "瓜",
["窊"] = "瓜",
["畖"] = "瓜",
["搲"] = "瓜",
["攨"] = "瓜",
["孤"] = "瓜",
["苽"] = "瓜",
["胍"] = "瓜",
["泒"] = "瓜",
["呱"] = "瓜",
["觚"] = "瓜",
["柧"] = "瓜",
["罛"] = "瓜",
["軱"] = "瓜",
["菰"] = "瓜",
["箛"] = "瓜",
["狐"] = "瓜",
["弧"] = "瓜",
["碢"] = "冎",
["腡"] = "冎",
["過"] = "冎",
["渦"] = "冎",
["鍋"] = "冎",
["楇"] = "冎",
["瘑"] = "冎",
["緺"] = "冎",
["堝"] = "冎",
["濄"] = "冎",
["鐹"] = "冎",
["薖"] = "冎",
["簻"] = "冎",
["禍"] = "冎",
["窩"] = "冎",
["萵"] = "冎",
["檛"] = "冎",
["膼"] = "冎",
["蝸"] = "冎",
["媧"] = "冎",
["騧"] = "冎",
["冎"] = "冎",
["剮"] = "冎",
["諣"] = "冎",
["歄"] = "冎",
["咼"] = "冎",
["喎"] = "冎",
["箉"] = "枴",
["拐"] = "枴",
["枴"] = "枴",
["袂"] = "夬",
["夬"] = "夬",
["快"] = "夬",
["駃"] = "夬",
["筷"] = "夬",
["炔"] = "夬",
["觖"] = "夬",
["缺"] = "夬",
["蒛"] = "夬",
["吷"] = "夬",
["妜"] = "夬",
["決"] = "夬",
["訣"] = "夬",
["抉"] = "夬",
["玦"] = "夬",
["芵"] = "夬",
["赽"] = "夬",
["鴂"] = "夬",
["鈌"] = "夬",
["决"] = "夬",
["疦"] = "夬",
["蚗"] = "夬",
["趹"] = "夬",
["官"] = "官",
["棺"] = "官",
["倌"] = "官",
["悹"] = "官",
["涫"] = "官",
["管"] = "官",
["琯"] = "官",
["痯"] = "官",
["輨"] = "官",
["錧"] = "官",
["館"] = "官",
["婠"] = "官",
["悺"] = "官",
["逭"] = "官",
["菅"] = "官",
["綰"] = "官",
["捾"] = "官",
["毌"] = "毌",
["貫"] = "毌",
["樌"] = "毌",
["鏆"] = "毌",
["遦"] = "毌",
["慣"] = "毌",
["摜"] = "毌",
["瑻"] = "毌",
["謴"] = "毌",
["觀"] = "雚",
["灌"] = "雚",
["鸛"] = "雚",
["瓘"] = "雚",
["矔"] = "雚",
["懽"] = "雚",
["爟"] = "雚",
["雚"] = "雚",
["罐"] = "雚",
["鑵"] = "雚",
["歡"] = "雚",
["驩"] = "雚",
["貛"] = "雚",
["酄"] = "雚",
["獾"] = "雚",
["讙"] = "雚",
["嚾"] = "雚",
["權"] = "雚",
["顴"] = "雚",
["孉"] = "雚",
["蠸"] = "雚",
["虇"] = "雚",
["勸"] = "雚",
["光"] = "光",
["灮"] = "光",
["洸"] = "光",
["桄"] = "光",
["胱"] = "光",
["垙"] = "光",
["輄"] = "光",
["恍"] = "光",
["茪"] = "光",
["侊"] = "光",
["晃"] = "光",
["幌"] = "光",
["榥"] = "光",
["滉"] = "光",
["觥"] = "光",
["駫"] = "光",
["街"] = "圭",
["鞋"] = "圭",
["娾"] = "圭",
["佳"] = "圭",
["鮭"] = "圭",
["涯"] = "圭",
["崖"] = "圭",
["啀"] = "圭",
["厓"] = "圭",
["捱"] = "圭",
["睚"] = "圭",
["娃"] = "圭",
["洼"] = "圭",
["哇"] = "圭",
["胿"] = "圭",
["溎"] = "圭",
["觟"] = "圭",
["黊"] = "圭",
["蘳"] = "圭",
["蛙"] = "圭",
["窪"] = "圭",
["卦"] = "圭",
["挂"] = "圭",
["掛"] = "圭",
["詿"] = "圭",
["罣"] = "圭",
["絓"] = "圭",
["鼃"] = "圭",
["圭"] = "圭",
["珪"] = "圭",
["邽"] = "圭",
["閨"] = "圭",
["袿"] = "圭",
["窐"] = "圭",
["茥"] = "圭",
["桂"] = "圭",
["筀"] = "圭",
["奎"] = "圭",
["刲"] = "圭",
["蝰"] = "圭",
["楏"] = "圭",
["睳"] = "圭",
["畦"] = "圭",
["眭"] = "圭",
["烓"] = "圭",
["跬"] = "圭",
["恚"] = "圭",
["硅"] = "圭",
["嫢"] = "規",
["規"] = "規",
["槻"] = "規",
["鬹"] = "規",
["摫"] = "規",
["瞡"] = "規",
["窺"] = "規",
["闚"] = "規",
["鬮"] = "龜",
["龜"] = "龜",
["孈"] = "巂",
["纗"] = "巂",
["攜"] = "巂",
["蠵"] = "巂",
["鑴"] = "巂",
["驨"] = "巂",
["酅"] = "巂",
["雟"] = "巂",
["讗"] = "巂",
["觽"] = "巂",
["儶"] = "巂",
["觿"] = "巂",
["欈"] = "巂",
["瓗"] = "巂",
["携"] = "巂",
["睽"] = "癸",
["湀"] = "癸",
["聧"] = "癸",
["藈"] = "癸",
["癸"] = "癸",
["葵"] = "癸",
["鄈"] = "癸",
["楑"] = "癸",
["戣"] = "癸",
["鍨"] = "癸",
["騤"] = "癸",
["揆"] = "癸",
["猤"] = "癸",
["闋"] = "癸",
["謉"] = "鬼",
["瑰"] = "鬼",
["傀"] = "鬼",
["藱"] = "鬼",
["魁"] = "鬼",
["磈"] = "鬼",
["塊"] = "鬼",
["嵬"] = "鬼",
["隗"] = "鬼",
["槐"] = "鬼",
["瘣"] = "鬼",
["廆"] = "鬼",
["溾"] = "鬼",
["螝"] = "鬼",
["褢"] = "鬼",
["騩"] = "鬼",
["愧"] = "鬼",
["媿"] = "鬼",
["聭"] = "鬼",
["餽"] = "鬼",
["鬼"] = "鬼",
["魏"] = "鬼",
["巍"] = "鬼",
["犩"] = "鬼",
["簋"] = "簋",
["孂"] = "簋",
["藬"] = "貴",
["僓"] = "貴",
["頹"] = "貴",
["穨"] = "貴",
["蘈"] = "貴",
["隤"] = "貴",
["墤"] = "貴",
["尵"] = "貴",
["蹪"] = "貴",
["瀢"] = "貴",
["鞼"] = "貴",
["膭"] = "貴",
["憒"] = "貴",
["靧"] = "貴",
["潰"] = "貴",
["繢"] = "貴",
["嬇"] = "貴",
["殨"] = "貴",
["闠"] = "貴",
["簣"] = "貴",
["嘳"] = "貴",
["蕢"] = "貴",
["聵"] = "貴",
["樻"] = "貴",
["䙡"] = "貴",
["匱"] = "貴",
["臾"] = "貴",
["饋"] = "貴",
["櫃"] = "貴",
["遺"] = "貴",
["壝"] = "貴",
["瞶"] = "貴",
["貴"] = "貴",
["郭"] = "郭",
["埻"] = "郭",
["崞"] = "郭",
["椁"] = "郭",
["槨"] = "郭",
["噋"] = "郭",
["廓"] = "郭",
["鞹"] = "郭",
["漷"] = "郭",
["霩"] = "郭",
["裸"] = "果",
["躶"] = "果",
["果"] = "果",
["菓"] = "果",
["猓"] = "果",
["輠"] = "果",
["裹"] = "果",
["蜾"] = "果",
["惈"] = "果",
["粿"] = "果",
["窠"] = "果",
["稞"] = "果",
["課"] = "果",
["髁"] = "果",
["棵"] = "果",
["顆"] = "果",
["堁"] = "果",
["敤"] = "果",
["夥"] = "果",
["婐"] = "果",
["踝"] = "果",
["祼"] = "果",
["該"] = "亥",
["垓"] = "亥",
["賅"] = "亥",
["陔"] = "亥",
["豥"] = "亥",
["荄"] = "亥",
["郂"] = "亥",
["姟"] = "亥",
["絯"] = "亥",
["晐"] = "亥",
["峐"] = "亥",
["侅"] = "亥",
["胲"] = "亥",
["頦"] = "亥",
["奒"] = "亥",
["輆"] = "亥",
["欬"] = "亥",
["硋"] = "亥",
["閡"] = "亥",
["咳"] = "亥",
["孩"] = "亥",
["亥"] = "亥",
["劾"] = "亥",
["痎"] = "亥",
["烗"] = "亥",
["骸"] = "亥",
["駭"] = "亥",
["刻"] = "亥",
["餩"] = "亥",
["核"] = "亥",
["轄"] = "害",
["害"] = "害",
["犗"] = "害",
["幰"] = "害",
["攇"] = "害",
["憲"] = "害",
["瀗"] = "害",
["割"] = "害",
["磍"] = "害",
["瞎"] = "害",
["鎋"] = "害",
["鶷"] = "害",
["縖"] = "害",
["豁"] = "害",
["蜬"] = "函",
["函"] = "函",
["涵"] = "函",
["顄"] = "函",
["圅"] = "函",
["菡"] = "函",
["寒"] = "寒",
["蹇"] = "寒",
["謇"] = "寒",
["搴"] = "寒",
["藆"] = "寒",
["騫"] = "寒",
["褰"] = "寒",
["攐"] = "寒",
["鑳"] = "寒",
["寋"] = "寒",
["鶱"] = "寒",
["厂"] = "厂",
["屵"] = "厂",
["儺"] = "暵",
["臡"] = "暵",
["嘆"] = "暵",
["灘"] = "暵",
["擹"] = "暵",
["攤"] = "暵",
["癱"] = "暵",
["歎"] = "暵",
["難"] = "暵",
["暵"] = "暵",
["熯"] = "暵",
["漢"] = "暵",
["戁"] = "暵",
["薅"] = "好",
["好"] = "好",
["饕"] = "号",
["號"] = "号",
["号"] = "号",
["枵"] = "号",
["呺"] = "号",
["鴞"] = "号",
["昊"] = "昊",
["顥"] = "昊",
["灝"] = "昊",
["科"] = "禾",
["萪"] = "禾",
["蝌"] = "禾",
["犐"] = "禾",
["禾"] = "禾",
["和"] = "禾",
["咊"] = "禾",
["盉"] = "禾",
["龢"] = "禾",
["鉌"] = "禾",
["俰"] = "禾",
["龕"] = "合",
["媕"] = "合",
["頜"] = "合",
["答"] = "合",
["畣"] = "合",
["荅"] = "合",
["嗒"] = "合",
["褡"] = "合",
["撘"] = "合",
["搭"] = "合",
["榙"] = "合",
["佮"] = "合",
["蛤"] = "合",
["鴿"] = "合",
["合"] = "合",
["郃"] = "合",
["匌"] = "合",
["鉿"] = "合",
["閤"] = "合",
["鞈"] = "合",
["韐"] = "合",
["鮯"] = "合",
["哈"] = "合",
["姶"] = "合",
["欱"] = "合",
["盒"] = "合",
["耠"] = "合",
["詥"] = "合",
["塔"] = "合",
["鞳"] = "合",
["劄"] = "合",
["跲"] = "合",
["袷"] = "合",
["餄"] = "合",
["恰"] = "合",
["帢"] = "合",
["洽"] = "合",
["祫"] = "合",
["珨"] = "合",
["烚"] = "合",
["歙"] = "合",
["湁"] = "合",
["拾"] = "合",
["給"] = "合",
["翕"] = "合",
["噏"] = "合",
["潝"] = "合",
["熻"] = "合",
["嬆"] = "合",
["翖"] = "合",
["闟"] = "合",
["愒"] = "曷",
["馤"] = "曷",
["靄"] = "曷",
["餲"] = "曷",
["喝"] = "曷",
["揭"] = "曷",
["偈"] = "曷",
["猲"] = "曷",
["葛"] = "曷",
["輵"] = "曷",
["獦"] = "曷",
["渴"] = "曷",
["嵑"] = "曷",
["暍"] = "曷",
["曷"] = "曷",
["褐"] = "曷",
["鶡"] = "曷",
["毼"] = "曷",
["蝎"] = "曷",
["鞨"] = "曷",
["遏"] = "曷",
["齃"] = "曷",
["堨"] = "曷",
["擖"] = "曷",
["楬"] = "曷",
["朅"] = "曷",
["藒"] = "曷",
["竭"] = "曷",
["碣"] = "曷",
["羯"] = "曷",
["鍻"] = "曷",
["歇"] = "曷",
["蠍"] = "曷",
["謁"] = "曷",
["蓋"] = "盍",
["盖"] = "盍",
["溘"] = "盍",
["搕"] = "盍",
["嗑"] = "盍",
["榼"] = "盍",
["磕"] = "盍",
["瞌"] = "盍",
["盍"] = "盍",
["闔"] = "盍",
["篕"] = "盍",
["熆"] = "盍",
["鰪"] = "盍",
["廅"] = "盍",
["圔"] = "盍",
["饁"] = "盍",
["鶴"] = "寉",
["髉"] = "寉",
["篧"] = "寉",
["榷"] = "寉",
["搉"] = "寉",
["傕"] = "寉",
["確"] = "寉",
["寉"] = "寉",
["蒮"] = "寉",
["嚜"] = "黑",
["嫼"] = "黑",
["墨"] = "黑",
["默"] = "黑",
["纆"] = "黑",
["黑"] = "黑",
["潶"] = "黑",
["亨"] = "亨",
["享"] = "亨",
["烹"] = "亨",
["脝"] = "亨",
["悙"] = "亨",
["哼"] = "亨",
["搄"] = "亙",
["縆"] = "亙",
["絙"] = "亙",
["亙"] = "亙",
["堩"] = "亙",
["恆"] = "亙",
["峘"] = "亙",
["弘"] = "弘",
["鞃"] = "弘",
["苰"] = "弘",
["泓"] = "弘",
["宖"] = "弘",
["紭"] = "弘",
["緱"] = "侯",
["侯"] = "侯",
["喉"] = "侯",
["猴"] = "侯",
["瘊"] = "侯",
["篌"] = "侯",
["糇"] = "侯",
["帿"] = "侯",
["睺"] = "侯",
["矦"] = "侯",
["餱"] = "侯",
["翭"] = "侯",
["葔"] = "侯",
["鄇"] = "侯",
["鍭"] = "侯",
["鯸"] = "侯",
["候"] = "侯",
["堠"] = "侯",
["垢"] = "后",
["詬"] = "后",
["茩"] = "后",
["姤"] = "后",
["銗"] = "后",
["骺"] = "后",
["后"] = "后",
["垕"] = "后",
["郈"] = "后",
["逅"] = "后",
["鲘"] = "后",
["缿"] = "后",
["罅"] = "乎",
["呼"] = "乎",
["軤"] = "乎",
["苸"] = "乎",
["嘑"] = "乎",
["歑"] = "乎",
["謼"] = "乎",
["滹"] = "乎",
["乎"] = "乎",
["虖"] = "乎",
["嫭"] = "乎",
["唬"] = "虍",
["戲"] = "虍",
["巇"] = "虍",
["隵"] = "虍",
["嚱"] = "虍",
["盧"] = "虍",
["鑪"] = "虍",
["壚"] = "虍",
["籚"] = "虍",
["蘆"] = "虍",
["顱"] = "虍",
["髗"] = "虍",
["鱸"] = "虍",
["攎"] = "虍",
["櫨"] = "虍",
["轤"] = "虍",
["黸"] = "虍",
["獹"] = "虍",
["鸕"] = "虍",
["艫"] = "虍",
["纑"] = "虍",
["瀘"] = "虍",
["瓐"] = "虍",
["爐"] = "虍",
["嚧"] = "虍",
["矑"] = "虍",
["罏"] = "虍",
["蠦"] = "虍",
["虜"] = "虍",
["擄"] = "虍",
["艣"] = "虍",
["鐪"] = "虍",
["虖"] = "虍",
["虍"] = "虍",
["雐"] = "虍",
["虎"] = "虍",
["琥"] = "虍",
["萀"] = "虍",
["臚"] = "虍",
["廬"] = "虍",
["驢"] = "虍",
["藘"] = "虍",
["爈"] = "虍",
["櫖"] = "虍",
["儢"] = "虍",
["慮"] = "虍",
["勴"] = "虍",
["鑢"] = "虍",
["濾"] = "虍",
["攄"] = "虍",
["處"] = "虍",
["豦"] = "虍",
["據"] = "虍",
["鐻"] = "虍",
["澽"] = "虍",
["虛"] = "虍",
["墟"] = "虍",
["懅"] = "虍",
["蘧"] = "虍",
["籧"] = "虍",
["醵"] = "虍",
["璩"] = "虍",
["虡"] = "虍",
["遽"] = "虍",
["勮"] = "虍",
["嘘"] = "虍",
["驉"] = "虍",
["歔"] = "虍",
["魖"] = "虍",
["膚"] = "虍",
["虧"] = "虍",
["噱"] = "虍",
["臄"] = "虍",
["劇"] = "虍",
["諕"] = "虍",
["魱"] = "互",
["互"] = "互",
["冱"] = "互",
["枑"] = "互",
["妒"] = "戸",
["雇"] = "戸",
["顧"] = "戸",
["戽"] = "戸",
["戶"] = "戸",
["扈"] = "戸",
["帍"] = "戸",
["昈"] = "戸",
["滬"] = "戸",
["簄"] = "戸",
["齭"] = "戸",
["所"] = "戸",
["鞾"] = "華",
["華"] = "華",
["譁"] = "華",
["樺"] = "華",
["嘩"] = "華",
["驊"] = "華",
["鏵"] = "華",
["崋"] = "華",
["訛"] = "化",
["吪"] = "化",
["鈋"] = "化",
["囮"] = "化",
["魤"] = "化",
["靴"] = "化",
["貨"] = "化",
["花"] = "化",
["化"] = "化",
["杹"] = "化",
["𠤎"] = "化",
["畫"] = "畫",
["澅"] = "畫",
["繣"] = "畫",
["劃"] = "畫",
["嫿"] = "畫",
["瓌"] = "褱",
["櫰"] = "褱",
["褱"] = "褱",
["懷"] = "褱",
["瀤"] = "褱",
["壞"] = "褱",
["蘾"] = "褱",
["寬"] = "萈",
["髖"] = "萈",
["萈"] = "萈",
["臗"] = "萈",
["渙"] = "奐",
["瘓"] = "奐",
["喚"] = "奐",
["煥"] = "奐",
["奐"] = "奐",
["寏"] = "奐",
["換"] = "奐",
["漶"] = "患",
["贃"] = "患",
["患"] = "患",
["槵"] = "患",
["僙"] = "黃",
["廣"] = "黃",
["鄺"] = "黃",
["懬"] = "黃",
["爌"] = "黃",
["曠"] = "黃",
["矌"] = "黃",
["壙"] = "黃",
["纊"] = "黃",
["黃"] = "黃",
["璜"] = "黃",
["潢"] = "黃",
["簧"] = "黃",
["癀"] = "黃",
["鱑"] = "黃",
["獚"] = "黃",
["蟥"] = "黃",
["趪"] = "黃",
["櫎"] = "黃",
["擴"] = "黃",
["瀇"] = "黃",
["獷"] = "黃",
["觵"] = "黃",
["礦"] = "黃",
["鑛"] = "黃",
["穬"] = "黃",
["橫"] = "黃",
["黌"] = "黃",
["鐄"] = "黃",
["嚝"] = "黃",
["彉"] = "黃",
["彍"] = "黃",
["脄"] = "灰",
["盔"] = "灰",
["詼"] = "灰",
["恢"] = "灰",
["灰"] = "灰",
["鬌"] = "隓",
["隋"] = "隓",
["墮"] = "隓",
["媠"] = "隓",
["橢"] = "隓",
["嫷"] = "隓",
["毻"] = "隓",
["堶"] = "隓",
["憜"] = "隓",
["惰"] = "隓",
["嶞"] = "隓",
["髓"] = "隓",
["瀡"] = "隓",
["隨"] = "隓",
["隓"] = "隓",
["隳"] = "隓",
["撱"] = "隓",
["回"] = "回",
["徊"] = "回",
["茴"] = "回",
["洄"] = "回",
["迴"] = "回",
["佪"] = "回",
["蛔"] = "回",
["毀"] = "毀",
["燬"] = "毀",
["檓"] = "毀",
["毇"] = "毀",
["譭"] = "毀",
["卉"] = "卉",
["芔"] = "卉",
["泋"] = "卉",
["櫘"] = "彗",
["彗"] = "彗",
["篲"] = "彗",
["鏏"] = "彗",
["槥"] = "彗",
["轊"] = "彗",
["熭"] = "彗",
["嘒"] = "彗",
["暳"] = "彗",
["嚖"] = "彗",
["慧"] = "彗",
["蔧"] = "彗",
["雪"] = "彗",
["韢"] = "惠",
["繐"] = "惠",
["憓"] = "惠",
["潓"] = "惠",
["惠"] = "惠",
["蟪"] = "惠",
["蕙"] = "惠",
["橞"] = "惠",
["譓"] = "惠",
["鏸"] = "惠",
["穗"] = "惠",
["譮"] = "會",
["會"] = "會",
["儈"] = "會",
["膾"] = "會",
["鱠"] = "會",
["襘"] = "會",
["禬"] = "會",
["檜"] = "會",
["旝"] = "會",
["澮"] = "會",
["鄶"] = "會",
["廥"] = "會",
["鬠"] = "會",
["劊"] = "會",
["獪"] = "會",
["繪"] = "會",
["懀"] = "會",
["薈"] = "會",
["嬒"] = "會",
["瞺"] = "會",
["噲"] = "會",
["璯"] = "會",
["鍲"] = "昏",
["抿"] = "昏",
["緡"] = "昏",
["捪"] = "昏",
["湣"] = "昏",
["昏"] = "昏",
["婚"] = "昏",
["閽"] = "昏",
["惛"] = "昏",
["棔"] = "昏",
["殙"] = "昏",
["溷"] = "圂",
["慁"] = "圂",
["圂"] = "圂",
["火"] = "火",
["邩"] = "火",
["伙"] = "火",
["幗"] = "或",
["慖"] = "或",
["簂"] = "或",
["蔮"] = "或",
["國"] = "或",
["或"] = "或",
["惑"] = "或",
["蜮"] = "或",
["閾"] = "或",
["緎"] = "或",
["淢"] = "或",
["域"] = "或",
["罭"] = "或",
["棫"] = "或",
["琙"] = "或",
["魊"] = "或",
["馘"] = "或",
["聝"] = "或",
["膕"] = "或",
["摑"] = "或",
["漍"] = "或",
["嘓"] = "或",
["蟈"] = "或",
["掝"] = "或",
["彧"] = "或",
["稶"] = "或",
["檴"] = "蒦",
["鱯"] = "蒦",
["護"] = "蒦",
["頀"] = "蒦",
["濩"] = "蒦",
["韄"] = "蒦",
["擭"] = "蒦",
["瓁"] = "蒦",
["劐"] = "蒦",
["穫"] = "蒦",
["鑊"] = "蒦",
["矆"] = "蒦",
["鸌"] = "蒦",
["雘"] = "蒦",
["蠖"] = "蒦",
["臒"] = "蒦",
["嬳"] = "蒦",
["彠"] = "蒦",
["嚄"] = "蒦",
["獲"] = "蒦",
["靃"] = "霍",
["臛"] = "霍",
["籗"] = "霍",
["霍"] = "霍",
["藿"] = "霍",
["攉"] = "霍",
["矐"] = "霍",
["瀖"] = "霍",
["癨"] = "霍",
["繫"] = "毄",
["檕"] = "毄",
["轚"] = "毄",
["蘻"] = "毄",
["罊"] = "毄",
["蟿"] = "毄",
["礊"] = "毄",
["擊"] = "毄",
["墼"] = "毄",
["趿"] = "及",
["鈒"] = "及",
["靸"] = "及",
["馺"] = "及",
["砐"] = "及",
["魥"] = "及",
["扱"] = "及",
["笈"] = "及",
["衱"] = "及",
["极"] = "及",
["昅"] = "及",
["雭"] = "及",
["急"] = "及",
["級"] = "及",
["汲"] = "及",
["芨"] = "及",
["伋"] = "及",
["彶"] = "及",
["及"] = "及",
["岌"] = "及",
["吸"] = "及",
["秸"] = "吉",
["髻"] = "吉",
["夡"] = "吉",
["鴶"] = "吉",
["袺"] = "吉",
["頡"] = "吉",
["劼"] = "吉",
["硈"] = "吉",
["咭"] = "吉",
["黠"] = "吉",
["聐"] = "吉",
["趌"] = "吉",
["結"] = "吉",
["拮"] = "吉",
["桔"] = "吉",
["狤"] = "吉",
["擷"] = "吉",
["襭"] = "吉",
["翓"] = "吉",
["纈"] = "吉",
["吉"] = "吉",
["郆"] = "吉",
["詰"] = "吉",
["蛣"] = "吉",
["佶"] = "吉",
["鮚"] = "吉",
["姞"] = "吉",
["欯"] = "吉",
["恄"] = "吉",
["卪"] = "即",
["癤"] = "即",
["幯"] = "即",
["節"] = "即",
["蝍"] = "即",
["蠞"] = "即",
["堲"] = "即",
["唧"] = "即",
["楖"] = "即",
["揤"] = "即",
["櫛"] = "即",
["瀄"] = "即",
["擳"] = "即",
["即"] = "即",
["鯽"] = "即",
["亟"] = "茍",
["茍"] = "茍",
["殛"] = "茍",
["極"] = "茍",
["嫉"] = "疾",
["疾"] = "疾",
["蒺"] = "疾",
["愱"] = "疾",
["槉"] = "疾",
["棘"] = "棘",
["襋"] = "棘",
["蕀"] = "棘",
["雜"] = "集",
["磼"] = "集",
["囃"] = "集",
["鏶"] = "集",
["穕"] = "集",
["潗"] = "集",
["集"] = "集",
["肌"] = "几",
["飢"] = "几",
["机"] = "几",
["虮"] = "几",
["几"] = "几",
["麂"] = "几",
["改"] = "己",
["跽"] = "己",
["己"] = "己",
["紀"] = "己",
["妀"] = "己",
["記"] = "己",
["杞"] = "己",
["屺"] = "己",
["芑"] = "己",
["邔"] = "己",
["玘"] = "己",
["忌"] = "己",
["鵋"] = "己",
["誋"] = "己",
["梞"] = "己",
["丮"] = "丮",
["谻"] = "丮",
["脊"] = "脊",
["蹐"] = "脊",
["鶺"] = "脊",
["塉"] = "脊",
["瘠"] = "脊",
["膌"] = "脊",
["機"] = "幾",
["譏"] = "幾",
["嘰"] = "幾",
["磯"] = "幾",
["鞿"] = "幾",
["饑"] = "幾",
["禨"] = "幾",
["幾"] = "幾",
["鐖"] = "幾",
["僟"] = "幾",
["璣"] = "幾",
["蟣"] = "幾",
["穖"] = "幾",
["畿"] = "幾",
["戟"] = "戟",
["撠"] = "戟",
["溉"] = "旡",
["槩"] = "旡",
["概"] = "旡",
["摡"] = "旡",
["慨"] = "旡",
["嘅"] = "旡",
["穊"] = "旡",
["暨"] = "旡",
["鱀"] = "旡",
["塈"] = "旡",
["既"] = "旡",
["旡"] = "旡",
["蔇"] = "旡",
["炁"] = "旡",
["櫭"] = "旡",
["曁"] = "旡",
["季"] = "季",
["悸"] = "季",
["痵"] = "季",
["蔡"] = "祭",
["瘵"] = "祭",
["祭"] = "祭",
["際"] = "祭",
["穄"] = "祭",
["鰶"] = "祭",
["傺"] = "祭",
["摖"] = "祭",
["擦"] = "祭",
["攃"] = "祭",
["礤"] = "祭",
["察"] = "祭",
["詧"] = "祭",
["冀"] = "冀",
["驥"] = "冀",
["懻"] = "冀",
["罽"] = "罽",
["瀱"] = "罽",
["蘮"] = "罽",
["繼"] = "繼",
["檵"] = "繼",
["妿"] = "加",
["賀"] = "加",
["迦"] = "加",
["茄"] = "加",
["伽"] = "加",
["枷"] = "加",
["嘉"] = "加",
["加"] = "加",
["笳"] = "加",
["痂"] = "加",
["袈"] = "加",
["跏"] = "加",
["珈"] = "加",
["鴐"] = "加",
["架"] = "加",
["駕"] = "加",
["瘸"] = "加",
["瘞"] = "夾",
["瘱"] = "夾",
["脥"] = "夾",
["浹"] = "夾",
["挾"] = "夾",
["夾"] = "夾",
["郟"] = "夾",
["筴"] = "夾",
["裌"] = "夾",
["鵊"] = "夾",
["狹"] = "夾",
["峽"] = "夾",
["硤"] = "夾",
["陜"] = "夾",
["陿"] = "夾",
["翜"] = "夾",
["梜"] = "夾",
["頰"] = "夾",
["莢"] = "夾",
["鋏"] = "夾",
["蛺"] = "夾",
["唊"] = "夾",
["悏"] = "夾",
["匧"] = "夾",
["愜"] = "夾",
["篋"] = "夾",
["俠"] = "夾",
["綊"] = "夾",
["家"] = "家",
["傢"] = "家",
["幏"] = "家",
["稼"] = "家",
["嫁"] = "家",
["戛"] = "戛",
["嘎"] = "戛",
["笚"] = "甲",
["閘"] = "甲",
["鉀"] = "甲",
["甲"] = "甲",
["胛"] = "甲",
["岬"] = "甲",
["押"] = "甲",
["玾"] = "甲",
["呷"] = "甲",
["匣"] = "甲",
["狎"] = "甲",
["柙"] = "甲",
["翈"] = "甲",
["炠"] = "甲",
["鴨"] = "甲",
["庘"] = "甲",
["葭"] = "叚",
["麚"] = "叚",
["瘕"] = "叚",
["豭"] = "叚",
["貑"] = "叚",
["猳"] = "叚",
["犌"] = "叚",
["假"] = "叚",
["嘏"] = "叚",
["婽"] = "叚",
["椵"] = "叚",
["叚"] = "叚",
["蝦"] = "叚",
["煆"] = "叚",
["霞"] = "叚",
["遐"] = "叚",
["瑕"] = "叚",
["鰕"] = "叚",
["碬"] = "叚",
["蕸"] = "叚",
["赮"] = "叚",
["鍜"] = "叚",
["騢"] = "叚",
["暇"] = "叚",
["殘"] = "戔",
["戔"] = "戔",
["帴"] = "戔",
["虥"] = "戔",
["盞"] = "戔",
["醆"] = "戔",
["琖"] = "戔",
["剗"] = "戔",
["棧"] = "戔",
["輚"] = "戔",
["嶘"] = "戔",
["錢"] = "戔",
["俴"] = "戔",
["籛"] = "戔",
["淺"] = "戔",
["濺"] = "戔",
["踐"] = "戔",
["諓"] = "戔",
["餞"] = "戔",
["賤"] = "戔",
["綫"] = "戔",
["箋"] = "戔",
["牋"] = "戔",
["顅"] = "肩",
["掮"] = "肩",
["肩"] = "肩",
["猏"] = "肩",
["菺"] = "肩",
["鵳"] = "肩",
["姦"] = "姦",
["葌"] = "姦",
["譧"] = "兼",
["賺"] = "兼",
["尲"] = "兼",
["鹻"] = "兼",
["歉"] = "兼",
["槏"] = "兼",
["鰜"] = "兼",
["稴"] = "兼",
["豏"] = "兼",
["甉"] = "兼",
["廉"] = "兼",
["鎌"] = "兼",
["鬑"] = "兼",
["鐮"] = "兼",
["簾"] = "兼",
["薕"] = "兼",
["蠊"] = "兼",
["嬚"] = "兼",
["溓"] = "兼",
["嵰"] = "兼",
["隒"] = "兼",
["熑"] = "兼",
["濂"] = "兼",
["燫"] = "兼",
["兼"] = "兼",
["縑"] = "兼",
["鶼"] = "兼",
["蒹"] = "兼",
["搛"] = "兼",
["謙"] = "兼",
["膁"] = "兼",
["嗛"] = "兼",
["慊"] = "兼",
["傔"] = "兼",
["馦"] = "兼",
["嫌"] = "兼",
["鼸"] = "兼",
["尷"] = "兼",
["藍"] = "監",
["襤"] = "監",
["籃"] = "監",
["懢"] = "監",
["儖"] = "監",
["蘫"] = "監",
["攬"] = "監",
["欖"] = "監",
["覽"] = "監",
["爁"] = "監",
["擥"] = "監",
["灠"] = "監",
["濫"] = "監",
["嚂"] = "監",
["纜"] = "監",
["監"] = "監",
["礛"] = "監",
["鑑"] = "監",
["艦"] = "監",
["檻"] = "監",
["壏"] = "監",
["轞"] = "監",
["鹽"] = "監",
["蘭"] = "柬",
["攔"] = "柬",
["欄"] = "柬",
["瀾"] = "柬",
["讕"] = "柬",
["闌"] = "柬",
["韊"] = "柬",
["幱"] = "柬",
["籣"] = "柬",
["躝"] = "柬",
["爛"] = "柬",
["爤"] = "柬",
["鑭"] = "柬",
["糷"] = "柬",
["斕"] = "柬",
["揀"] = "柬",
["柬"] = "柬",
["暕"] = "柬",
["諫"] = "柬",
["練"] = "柬",
["鍊"] = "柬",
["煉"] = "柬",
["楝"] = "柬",
["萰"] = "柬",
["堜"] = "柬",
["湅"] = "柬",
["襺"] = "繭",
["繭"] = "繭",
["蠒"] = "繭",
["笄"] = "幵",
["枅"] = "幵",
["蚈"] = "幵",
["羿"] = "幵",
["栞"] = "幵",
["雃"] = "幵",
["詽"] = "幵",
["揅"] = "幵",
["幵"] = "幵",
["鳽"] = "幵",
["豣"] = "幵",
["麉"] = "幵",
["趼"] = "幵",
["汧"] = "幵",
["岍"] = "幵",
["研"] = "幵",
["妍"] = "幵",
["訮"] = "幵",
["鈃"] = "幵",
["涀"] = "見",
["莧"] = "見",
["粯"] = "見",
["晛"] = "見",
["筧"] = "見",
["絸"] = "見",
["垷"] = "見",
["挸"] = "見",
["見"] = "見",
["俔"] = "見",
["蜆"] = "見",
["硯"] = "見",
["峴"] = "見",
["哯"] = "見",
["睍"] = "見",
["現"] = "見",
["犍"] = "建",
["騝"] = "建",
["鍵"] = "建",
["揵"] = "建",
["鞬"] = "建",
["腱"] = "建",
["湕"] = "建",
["建"] = "建",
["旔"] = "建",
["楗"] = "建",
["健"] = "建",
["間"] = "閒",
["簡"] = "閒",
["澗"] = "閒",
["鐧"] = "閒",
["蕑"] = "閒",
["覵"] = "閒",
["襉"] = "閒",
["僩"] = "閒",
["鬜"] = "閒",
["癇"] = "閒",
["瞯"] = "閒",
["憪"] = "閒",
["橌"] = "閒",
["撊"] = "閒",
["瀳"] = "薦",
["韉"] = "薦",
["薦"] = "薦",
["畺"] = "畺",
["畕"] = "畺",
["礓"] = "畺",
["繮"] = "畺",
["韁"] = "畺",
["橿"] = "畺",
["薑"] = "畺",
["疆"] = "畺",
["壃"] = "畺",
["僵"] = "畺",
["彊"] = "畺",
["麠"] = "畺",
["贛"] = "夅",
["灨"] = "夅",
["竷"] = "夅",
["戇"] = "夅",
["洚"] = "夅",
["降"] = "夅",
["絳"] = "夅",
["栙"] = "夅",
["夅"] = "夅",
["跭"] = "夅",
["隆"] = "夅",
["癃"] = "夅",
["窿"] = "夅",
["霳"] = "夅",
["烄"] = "交",
["礮"] = "交",
["交"] = "交",
["郊"] = "交",
["茭"] = "交",
["蛟"] = "交",
["鮫"] = "交",
["鵁"] = "交",
["咬"] = "交",
["鉸"] = "交",
["佼"] = "交",
["詨"] = "交",
["絞"] = "交",
["狡"] = "交",
["姣"] = "交",
["筊"] = "交",
["校"] = "交",
["珓"] = "交",
["跤"] = "交",
["頝"] = "交",
["骹"] = "交",
["齩"] = "交",
["洨"] = "交",
["胶"] = "交",
["效"] = "交",
["効"] = "交",
["傚"] = "交",
["皎"] = "交",
["恔"] = "交",
["晈"] = "交",
["窔"] = "交",
["駮"] = "交",
["癄"] = "焦",
["焦"] = "焦",
["蕉"] = "焦",
["鷦"] = "焦",
["僬"] = "焦",
["噍"] = "焦",
["膲"] = "焦",
["鐎"] = "焦",
["蟭"] = "焦",
["燋"] = "焦",
["醮"] = "焦",
["潐"] = "焦",
["趭"] = "焦",
["樵"] = "焦",
["譙"] = "焦",
["憔"] = "焦",
["劁"] = "焦",
["顦"] = "焦",
["嶕"] = "焦",
["撨"] = "焦",
["穛"] = "焦",
["捔"] = "角",
["桷"] = "角",
["角"] = "角",
["埆"] = "角",
["确"] = "角",
["斛"] = "角",
["槲"] = "角",
["蔛"] = "角",
["礉"] = "敫",
["邀"] = "敫",
["憿"] = "敫",
["徼"] = "敫",
["儌"] = "敫",
["璬"] = "敫",
["皦"] = "敫",
["繳"] = "敫",
["敫"] = "敫",
["譥"] = "敫",
["激"] = "敫",
["噭"] = "敫",
["獥"] = "敫",
["鸄"] = "敫",
["墽"] = "敫",
["竅"] = "敫",
["覈"] = "敫",
["燩"] = "敫",
["檄"] = "敫",
["薂"] = "敫",
["皆"] = "皆",
["偕"] = "皆",
["階"] = "皆",
["喈"] = "皆",
["稭"] = "皆",
["堦"] = "皆",
["楷"] = "皆",
["鶛"] = "皆",
["湝"] = "皆",
["蝔"] = "皆",
["鍇"] = "皆",
["揩"] = "皆",
["諧"] = "皆",
["瑎"] = "皆",
["龤"] = "皆",
["孑"] = "孑",
["𨥂"] = "孑",
["劫"] = "劫",
["蜐"] = "劫",
["疀"] = "疌",
["萐"] = "疌",
["箑"] = "疌",
["啑"] = "疌",
["睫"] = "疌",
["婕"] = "疌",
["緁"] = "疌",
["捷"] = "疌",
["疌"] = "疌",
["寁"] = "疌",
["倢"] = "疌",
["崨"] = "疌",
["踕"] = "疌",
["誱"] = "疌",
["徢"] = "疌",
["蜨"] = "疌",
["桀"] = "桀",
["傑"] = "桀",
["榤"] = "桀",
["嵥"] = "桀",
["滐"] = "桀",
["搩"] = "桀",
["謋"] = "桀",
["巀"] = "截",
["截"] = "截",
["蠽"] = "截",
["薢"] = "解",
["解"] = "解",
["檞"] = "解",
["懈"] = "解",
["繲"] = "解",
["廨"] = "解",
["蟹"] = "解",
["獬"] = "解",
["澥"] = "解",
["嶰"] = "解",
["邂"] = "解",
["夆"] = "丯",
["丯"] = "丯",
["瘛"] = "丯",
["觢"] = "丯",
["契"] = "丯",
["栔"] = "丯",
["禊"] = "丯",
["稧"] = "丯",
["楔"] = "丯",
["葜"] = "丯",
["猰"] = "丯",
["窫"] = "丯",
["揳"] = "丯",
["偰"] = "丯",
["潔"] = "丯",
["絜"] = "丯",
["鍥"] = "丯",
["挈"] = "丯",
["蛪"] = "丯",
["齧"] = "丯",
["褉"] = "丯",
["喫"] = "丯",
["妎"] = "介",
["芥"] = "介",
["尬"] = "介",
["界"] = "介",
["介"] = "介",
["疥"] = "介",
["玠"] = "介",
["砎"] = "介",
["魪"] = "介",
["价"] = "介",
["衸"] = "介",
["齘"] = "介",
["骱"] = "介",
["扴"] = "介",
["圿"] = "介",
["忦"] = "介",
["祴"] = "戒",
["駴"] = "戒",
["戒"] = "戒",
["誡"] = "戒",
["悈"] = "戒",
["械"] = "戒",
["裓"] = "戒",
["巾"] = "巾",
["帘"] = "巾",
["貪"] = "今",
["嗿"] = "今",
["僋"] = "今",
["酓"] = "今",
["馠"] = "今",
["谽"] = "今",
["唅"] = "今",
["含"] = "今",
["肣"] = "今",
["頷"] = "今",
["筨"] = "今",
["梒"] = "今",
["鋡"] = "今",
["莟"] = "今",
["琀"] = "今",
["浛"] = "今",
["盦"] = "今",
["韽"] = "今",
["玪"] = "今",
["妗"] = "今",
["欦"] = "今",
["黔"] = "今",
["鈐"] = "今",
["鳹"] = "今",
["雂"] = "今",
["念"] = "今",
["梣"] = "今",
["枔"] = "今",
["岑"] = "今",
["笒"] = "今",
["涔"] = "今",
["侺"] = "今",
["今"] = "今",
["黅"] = "今",
["衿"] = "今",
["衾"] = "今",
["坅"] = "今",
["搇"] = "今",
["琴"] = "今",
["禽"] = "今",
["芩"] = "今",
["庈"] = "今",
["耹"] = "今",
["靲"] = "今",
["擒"] = "今",
["檎"] = "今",
["紟"] = "今",
["吟"] = "今",
["訡"] = "今",
["廞"] = "今",
["陰"] = "今",
["霠"] = "今",
["𩃬"] = "今",
["飲"] = "今",
["蔭"] = "今",
["廕"] = "今",
["矜"] = "今",
["蘄"] = "斤",
["祈"] = "斤",
["頎"] = "斤",
["旂"] = "斤",
["圻"] = "斤",
["蚚"] = "斤",
["岓"] = "斤",
["玂"] = "斤",
["沂"] = "斤",
["掀"] = "斤",
["釿"] = "斤",
["齗"] = "斤",
["听"] = "斤",
["垽"] = "斤",
["斤"] = "斤",
["菦"] = "斤",
["靳"] = "斤",
["劤"] = "斤",
["勁"] = "斤",
["赾"] = "斤",
["芹"] = "斤",
["近"] = "斤",
["斦"] = "斤",
["欣"] = "斤",
["忻"] = "斤",
["昕"] = "斤",
["訢"] = "斤",
["炘"] = "斤",
["邤"] = "斤",
["庍"] = "斤",
["焮"] = "斤",
["淦"] = "金",
["顉"] = "金",
["金"] = "金",
["錦"] = "金",
["欽"] = "金",
["菳"] = "金",
["嶔"] = "金",
["撳"] = "金",
["捦"] = "金",
["鵭"] = "金",
["唫"] = "金",
["鈙"] = "金",
["崟"] = "金",
["趛"] = "金",
["簪"] = "兓",
["撍"] = "兓",
["鐕"] = "兓",
["憯"] = "兓",
["噆"] = "兓",
["朁"] = "兓",
["蠶"] = "兓",
["蚕"] = "兓",
["糣"] = "兓",
["熸"] = "兓",
["鬵"] = "兓",
["灊"] = "兓",
["潛"] = "兓",
["僭"] = "兓",
["兓"] = "兓",
["嶜"] = "兓",
["兂"] = "兓",
["譖"] = "兓",
["葏"] = "津",
["津"] = "津",
["囏"] = "堇",
["螼"] = "堇",
["堇"] = "堇",
["墐"] = "堇",
["𥎊"] = "堇",
["僅"] = "堇",
["瑾"] = "堇",
["覲"] = "堇",
["殣"] = "堇",
["饉"] = "堇",
["廑"] = "堇",
["瘽"] = "堇",
["歏"] = "堇",
["鄞"] = "堇",
["謹"] = "堇",
["槿"] = "堇",
["漌"] = "堇",
["慬"] = "堇",
["勤"] = "堇",
["懃"] = "堇",
["懄"] = "堇",
["濜"] = "盡",
["盡"] = "盡",
["賮"] = "盡",
["燼"] = "盡",
["藎"] = "盡",
["壗"] = "盡",
["璶"] = "盡",
["鄑"] = "晉",
["戩"] = "晉",
["榗"] = "晉",
["晉"] = "晉",
["縉"] = "晉",
["搢"] = "晉",
["瑨"] = "晉",
["璡"] = "進",
["進"] = "進",
["羥"] = "坙",
["俓"] = "坙",
["牼"] = "坙",
["硁"] = "坙",
["誙"] = "坙",
["娙"] = "坙",
["莖"] = "坙",
["痙"] = "坙",
["葝"] = "巠",
["頸"] = "巠",
["勁"] = "巠",
["鑋"] = "巠",
["輕"] = "巠",
["涇"] = "巠",
["經"] = "巠",
["鵛"] = "巠",
["巠"] = "巠",
["剄"] = "巠",
["烴"] = "巠",
["徑"] = "巠",
["逕"] = "巠",
["桱"] = "巠",
["蛵"] = "巠",
["陘"] = "巠",
["鋞"] = "巠",
["脛"] = "巠",
["踁"] = "巠",
["涼"] = "京",
["凉"] = "京",
["椋"] = "京",
["綡"] = "京",
["輬"] = "京",
["諒"] = "京",
["掠"] = "京",
["弶"] = "京",
["京"] = "京",
["麖"] = "京",
["鶁"] = "京",
["景"] = "京",
["鯨"] = "京",
["黥"] = "京",
["勍"] = "京",
["剠"] = "京",
["倞"] = "京",
["影"] = "京",
["璟"] = "京",
["憬"] = "京",
["暻"] = "京",
["澋"] = "京",
["兢"] = "兢",
["殑"] = "兢",
["硎"] = "井",
["耕"] = "井",
["荊"] = "井",
["井"] = "井",
["丼"] = "井",
["妌"] = "井",
["穽"] = "井",
["阱"] = "井",
["汫"] = "井",
["刑"] = "井",
["形"] = "井",
["邢"] = "井",
["鉶"] = "井",
["型"] = "井",
["侀"] = "井",
["滰"] = "竟",
["境"] = "竟",
["竟"] = "竟",
["鏡"] = "竟",
["獍"] = "竟",
["樈"] = "竟",
["璄"] = "竟",
["摬"] = "竟",
["蟼"] = "敬",
["憼"] = "敬",
["驚"] = "敬",
["警"] = "敬",
["儆"] = "敬",
["璥"] = "敬",
["檠"] = "敬",
["敬"] = "敬",
["擎"] = "敬",
["曔"] = "敬",
["誩"] = "誩",
["競"] = "誩",
["苘"] = "冋",
["詗"] = "冋",
["駉"] = "冋",
["坰"] = "冋",
["冋"] = "冋",
["絅"] = "冋",
["扃"] = "冋",
["炯"] = "冋",
["迥"] = "冋",
["泂"] = "冋",
["臩"] = "臦",
["臦"] = "臦",
["艽"] = "丩",
["芁"] = "丩",
["荍"] = "丩",
["叫"] = "丩",
["訆"] = "丩",
["嘂"] = "丩",
["收"] = "丩",
["糾"] = "丩",
["朻"] = "丩",
["丩"] = "丩",
["赳"] = "丩",
["虬"] = "丩",
["虯"] = "丩",
["觓"] = "丩",
["尻"] = "九",
["訄"] = "九",
["艽"] = "九",
["虓"] = "九",
["鳩"] = "九",
["勼"] = "九",
["九"] = "九",
["究"] = "九",
["仇"] = "九",
["鼽"] = "九",
["犰"] = "九",
["叴"] = "九",
["厹"] = "九",
["頄"] = "九",
["朹"] = "九",
["馗"] = "九",
["扏"] = "九",
["訅"] = "九",
["釚"] = "九",
["肍"] = "九",
["軌"] = "九",
["宄"] = "九",
["氿"] = "九",
["匭"] = "九",
["泦"] = "九",
["旭"] = "九",
["畂"] = "久",
["久"] = "久",
["灸"] = "久",
["玖"] = "久",
["疚"] = "久",
["柩"] = "久",
["羑"] = "久",
["臼"] = "臼",
["齨"] = "臼",
["桕"] = "臼",
["舅"] = "臼",
["舊"] = "臼",
["匶"] = "臼",
["閰"] = "臼",
["咎"] = "咎",
["鼛"] = "咎",
["櫜"] = "咎",
["倃"] = "咎",
["綹"] = "咎",
["麔"] = "咎",
["晷"] = "咎",
["厬"] = "咎",
["僦"] = "就",
["就"] = "就",
["鷲"] = "就",
["殧"] = "就",
["噈"] = "就",
["蹴"] = "就",
["廄"] = "廄",
["匓"] = "廄",
["菊"] = "匊",
["鞠"] = "匊",
["掬"] = "匊",
["匊"] = "匊",
["鵴"] = "匊",
["椈"] = "匊",
["諊"] = "匊",
["踘"] = "匊",
["趜"] = "匊",
["蘜"] = "匊",
["鞫"] = "匊",
["巈"] = "匊",
["麴"] = "匊",
["毱"] = "匊",
["驧"] = "匊",
["鋦"] = "局",
["挶"] = "局",
["梮"] = "局",
["局"] = "局",
["跼"] = "局",
["侷"] = "局",
["瞁"] = "狊",
["湨"] = "狊",
["郹"] = "狊",
["狊"] = "狊",
["鶪"] = "狊",
["鼳"] = "狊",
["犑"] = "狊",
["闃"] = "狊",
["柜"] = "巨",
["渠"] = "巨",
["磲"] = "巨",
["蕖"] = "巨",
["蟝"] = "巨",
["璖"] = "巨",
["巨"] = "巨",
["距"] = "巨",
["拒"] = "巨",
["苣"] = "巨",
["炬"] = "巨",
["詎"] = "巨",
["鉅"] = "巨",
["秬"] = "巨",
["粔"] = "巨",
["駏"] = "巨",
["歫"] = "巨",
["矩"] = "巨",
["榘"] = "巨",
["勾"] = "句",
["鉤"] = "句",
["鴝"] = "句",
["句"] = "句",
["夠"] = "句",
["枸"] = "句",
["軥"] = "句",
["狗"] = "句",
["苟"] = "句",
["岣"] = "句",
["笱"] = "句",
["玽"] = "句",
["耇"] = "句",
["豿"] = "句",
["敂"] = "句",
["怐"] = "句",
["雊"] = "句",
["竘"] = "句",
["齁"] = "句",
["呴"] = "句",
["蚼"] = "句",
["豞"] = "句",
["訽"] = "句",
["佝"] = "句",
["拘"] = "句",
["駒"] = "句",
["眗"] = "句",
["跔"] = "句",
["鮈"] = "句",
["痀"] = "句",
["蒟"] = "句",
["絇"] = "句",
["邭"] = "句",
["劬"] = "句",
["朐"] = "句",
["鼩"] = "句",
["斪"] = "句",
["翑"] = "句",
["葋"] = "句",
["姁"] = "句",
["欨"] = "句",
["喣"] = "句",
["煦"] = "句",
["昫"] = "句",
["俱"] = "具",
["椇"] = "具",
["具"] = "具",
["埧"] = "具",
["颶"] = "具",
["瞿"] = "瞿",
["衢"] = "瞿",
["氍"] = "瞿",
["癯"] = "瞿",
["蠷"] = "瞿",
["臞"] = "瞿",
["欋"] = "瞿",
["鸜"] = "瞿",
["灈"] = "瞿",
["躣"] = "瞿",
["忂"] = "瞿",
["戵"] = "瞿",
["鑺"] = "瞿",
["懼"] = "瞿",
["潫"] = "龹",
["豢"] = "龹",
["勬"] = "龹",
["韏"] = "龹",
["眷"] = "龹",
["桊"] = "龹",
["絭"] = "龹",
["觠"] = "龹",
["飬"] = "龹",
["弮"] = "龹",
["拳"] = "龹",
["卷"] = "龹",
["齤"] = "龹",
["券"] = "龹",
["腃"] = "卷",
["卷"] = "卷",
["菤"] = "卷",
["捲"] = "卷",
["埢"] = "卷",
["睠"] = "卷",
["弮"] = "卷",
["犈"] = "卷",
["棬"] = "卷",
["鬈"] = "卷",
["圈"] = "卷",
["蜷"] = "卷",
["踡"] = "卷",
["婘"] = "卷",
["蔨"] = "卷",
["倦"] = "卷",
["淃"] = "卷",
["綣"] = "卷",
["裷"] = "卷",
["隽"] = "雋",
["檇"] = "雋",
["臇"] = "雋",
["嶲"] = "雋",
["鐫"] = "雋",
["雋"] = "雋",
["儁"] = "雋",
["寯"] = "雋",
["鱖"] = "欮",
["蹶"] = "欮",
["厥"] = "欮",
["欮"] = "欮",
["蕨"] = "欮",
["劂"] = "欮",
["瘚"] = "欮",
["蟨"] = "欮",
["蟩"] = "欮",
["橜"] = "欮",
["撅"] = "欮",
["闕"] = "欮",
["橛"] = "欮",
["鷢"] = "欮",
["蹷"] = "欮",
["憠"] = "欮",
["蕝"] = "絕",
["脃"] = "絕",
["脆"] = "絕",
["絕"] = "絕",
["釂"] = "爵",
["皭"] = "爵",
["爝"] = "爵",
["爵"] = "爵",
["嚼"] = "爵",
["穱"] = "爵",
["灂"] = "爵",
["钁"] = "矍",
["攫"] = "矍",
["矍"] = "矍",
["躩"] = "矍",
["玃"] = "矍",
["貜"] = "矍",
["彏"] = "矍",
["戄"] = "矍",
["籰"] = "矍",
["涒"] = "君",
["麏"] = "君",
["莙"] = "君",
["輑"] = "君",
["蜠"] = "君",
["峮"] = "君",
["窘"] = "君",
["僒"] = "君",
["君"] = "君",
["桾"] = "君",
["宭"] = "君",
["鮶"] = "君",
["捃"] = "君",
["群"] = "君",
["羣"] = "君",
["裙"] = "君",
["裠"] = "君",
["帬"] = "君",
["焄"] = "君",
["郡"] = "君",
["揮"] = "軍",
["輝"] = "軍",
["翬"] = "軍",
["暉"] = "軍",
["煇"] = "軍",
["楎"] = "軍",
["瀈"] = "軍",
["韗"] = "軍",
["褌"] = "軍",
["鶤"] = "軍",
["緷"] = "軍",
["睴"] = "軍",
["璭"] = "軍",
["瘒"] = "軍",
["顐"] = "軍",
["諢"] = "軍",
["餫"] = "軍",
["渾"] = "軍",
["琿"] = "軍",
["鼲"] = "軍",
["堚"] = "軍",
["鯶"] = "軍",
["軍"] = "軍",
["皸"] = "軍",
["齳"] = "軍",
["喗"] = "軍",
["葷"] = "軍",
["惲"] = "軍",
["賱"] = "軍",
["運"] = "軍",
["暈"] = "軍",
["鄆"] = "軍",
["諐"] = "侃",
["侃"] = "侃",
["偘"] = "侃",
["笐"] = "亢",
["亢"] = "亢",
["牨"] = "亢",
["苀"] = "亢",
["魧"] = "亢",
["迒"] = "亢",
["邟"] = "亢",
["忼"] = "亢",
["骯"] = "亢",
["抗"] = "亢",
["炕"] = "亢",
["伉"] = "亢",
["閌"] = "亢",
["犺"] = "亢",
["蚢"] = "亢",
["砊"] = "亢",
["阬"] = "亢",
["頏"] = "亢",
["航"] = "亢",
["杭"] = "亢",
["沆"] = "亢",
["肮"] = "亢",
["吭"] = "亢",
["秔"] = "亢",
["坑"] = "亢",
["劥"] = "亢",
["丂"] = "丂",
["攷"] = "丂",
["考"] = "丂",
["烤"] = "丂",
["拷"] = "丂",
["栲"] = "丂",
["洘"] = "丂",
["銬"] = "丂",
["巧"] = "丂",
["朽"] = "丂",
["彀"] = "殻",
["鷇"] = "殻",
["瞉"] = "殻",
["豰"] = "殻",
["瑴"] = "殻",
["愨"] = "殻",
["殼"] = "殻",
["觳"] = "殻",
["嗀"] = "殻",
["穀"] = "殻",
["糓"] = "殻",
["轂"] = "殻",
["榖"] = "殻",
["瀔"] = "殻",
["縠"] = "殻",
["砢"] = "可",
["柯"] = "可",
["菏"] = "可",
["牁"] = "可",
["滒"] = "可",
["哥"] = "可",
["歌"] = "可",
["謌"] = "可",
["鴚"] = "可",
["哿"] = "可",
["舸"] = "可",
["笴"] = "可",
["軻"] = "可",
["珂"] = "可",
["可"] = "可",
["岢"] = "可",
["坷"] = "可",
["蚵"] = "可",
["呵"] = "可",
["訶"] = "可",
["抲"] = "可",
["荷"] = "可",
["何"] = "可",
["河"] = "可",
["苛"] = "可",
["魺"] = "可",
["袔"] = "可",
["阿"] = "可",
["妸"] = "可",
["疴"] = "可",
["鈳"] = "可",
["娿"] = "可",
["痾"] = "可",
["跒"] = "可",
["閜"] = "可",
["克"] = "克",
["剋"] = "克",
["勊"] = "克",
["肯"] = "肯",
["肎"] = "肯",
["吼"] = "孔",
["孔"] = "孔",
["口"] = "口",
["叩"] = "口",
["扣"] = "口",
["釦"] = "口",
["筘"] = "口",
["滱"] = "寇",
["寇"] = "寇",
["簆"] = "寇",
["蔻"] = "寇",
["怪"] = "圣",
["恠"] = "圣",
["圣"] = "圣",
["凷"] = "凷",
["屆"] = "凷",
["款"] = "款",
["欵"] = "款",
["歀"] = "款",
["窾"] = "款",
["夔"] = "夔",
["犪"] = "夔",
["躨"] = "夔",
["昆"] = "昆",
["琨"] = "昆",
["鯤"] = "昆",
["菎"] = "昆",
["崐"] = "昆",
["蜫"] = "昆",
["惃"] = "昆",
["錕"] = "昆",
["猑"] = "昆",
["騉"] = "昆",
["緄"] = "昆",
["輥"] = "昆",
["棍"] = "昆",
["餛"] = "昆",
["混"] = "昆",
["焜"] = "昆",
["倱"] = "昆",
["掍"] = "昆",
["箟"] = "昆",
["硱"] = "困",
["梱"] = "困",
["捆"] = "困",
["綑"] = "困",
["閫"] = "困",
["悃"] = "困",
["稛"] = "困",
["裍"] = "困",
["齫"] = "困",
["困"] = "困",
["涃"] = "困",
["咶"] = "桰",
["話"] = "桰",
["刮"] = "桰",
["鴰"] = "桰",
["趏"] = "桰",
["舌"] = "桰",
["姡"] = "桰",
["頢"] = "桰",
["括"] = "桰",
["活"] = "桰",
["适"] = "桰",
["栝"] = "桰",
["佸"] = "桰",
["髺"] = "桰",
["聒"] = "桰",
["銛"] = "桰",
["葀"] = "桰",
["懖"] = "桰",
["筈"] = "桰",
["萿"] = "桰",
["蛞"] = "桰",
["闊"] = "桰",
["秳"] = "桰",
["莿"] = "剌",
["鵣"] = "剌",
["辣"] = "剌",
["辢"] = "剌",
["剌"] = "剌",
["揧"] = "剌",
["瘌"] = "剌",
["楋"] = "剌",
["蝲"] = "剌",
["來"] = "來",
["萊"] = "來",
["淶"] = "來",
["徠"] = "來",
["崍"] = "來",
["郲"] = "來",
["騋"] = "來",
["猍"] = "來",
["鯠"] = "來",
["鶆"] = "來",
["庲"] = "來",
["棶"] = "來",
["斄"] = "來",
["逨"] = "來",
["麳"] = "來",
["唻"] = "來",
["賚"] = "來",
["睞"] = "來",
["勑"] = "來",
["誺"] = "來",
["倈"] = "來",
["憖"] = "來",
["猌"] = "來",
["麥"] = "來",
["賴"] = "賴",
["籟"] = "賴",
["癩"] = "賴",
["瀨"] = "賴",
["藾"] = "賴",
["攋"] = "賴",
["懶"] = "賴",
["嬾"] = "賴",
["獺"] = "賴",
["牢"] = "牢",
["窂"] = "牢",
["哰"] = "牢",
["勞"] = "勞",
["撈"] = "勞",
["嘮"] = "勞",
["癆"] = "勞",
["簩"] = "勞",
["蟧"] = "勞",
["憥"] = "勞",
["澇"] = "勞",
["僗"] = "勞",
["髝"] = "勞",
["膋"] = "勞",
["犖"] = "勞",
["老"] = "老",
["姥"] = "老",
["栳"] = "老",
["恅"] = "老",
["樂"] = "樂",
["轢"] = "樂",
["濼"] = "樂",
["躒"] = "樂",
["擽"] = "樂",
["爍"] = "樂",
["鑠"] = "樂",
["藥"] = "樂",
["櫟"] = "樂",
["纅"] = "樂",
["礫"] = "樂",
["瓅"] = "樂",
["皪"] = "樂",
["觻"] = "樂",
["嚛"] = "樂",
["摞"] = "畾",
["騾"] = "畾",
["螺"] = "畾",
["蔂"] = "畾",
["虆"] = "畾",
["瘰"] = "畾",
["礌"] = "畾",
["雷"] = "畾",
["擂"] = "畾",
["攂"] = "畾",
["礧"] = "畾",
["儡"] = "畾",
["瓃"] = "畾",
["櫑"] = "畾",
["鑘"] = "畾",
["鐳"] = "畾",
["畾"] = "畾",
["轠"] = "畾",
["罍"] = "畾",
["蕾"] = "畾",
["癗"] = "畾",
["磥"] = "畾",
["累"] = "畾",
["儽"] = "畾",
["樏"] = "畾",
["鸓"] = "畾",
["嫘"] = "畾",
["纍"] = "畾",
["欙"] = "畾",
["纝"] = "畾",
["壘"] = "畾",
["櫐"] = "畾",
["蘽"] = "畾",
["藟"] = "畾",
["讄"] = "畾",
["絫"] = "厽",
["厽"] = "厽",
["垒"] = "厽",
["頛"] = "耒",
["郲"] = "耒",
["耒"] = "耒",
["銇"] = "耒",
["洡"] = "耒",
["誄"] = "耒",
["藞"] = "磊",
["磊"] = "磊",
["蘱"] = "頪",
["纇"] = "頪",
["類"] = "頪",
["頪"] = "頪",
["禷"] = "頪",
["謧"] = "离",
["璃"] = "离",
["離"] = "离",
["縭"] = "离",
["离"] = "离",
["醨"] = "离",
["樆"] = "离",
["褵"] = "离",
["漓"] = "离",
["黐"] = "离",
["籬"] = "离",
["灕"] = "离",
["蘺"] = "离",
["攡"] = "离",
["魑"] = "离",
["螭"] = "离",
["摛"] = "离",
["犛"] = "釐",
["釐"] = "釐",
["嫠"] = "釐",
["剺"] = "釐",
["孷"] = "釐",
["漦"] = "釐",
["氂"] = "釐",
["盠"] = "蠡",
["欚"] = "蠡",
["劙"] = "蠡",
["埋"] = "里",
["霾"] = "里",
["薶"] = "里",
["榸"] = "里",
["厘"] = "里",
["狸"] = "里",
["貍"] = "里",
["梩"] = "里",
["艃"] = "里",
["里"] = "里",
["理"] = "里",
["鯉"] = "里",
["俚"] = "里",
["悝"] = "里",
["娌"] = "里",
["裏"] = "里",
["荲"] = "里",
["體"] = "豊",
["軆"] = "豊",
["禮"] = "豊",
["澧"] = "豊",
["醴"] = "豊",
["豊"] = "豊",
["鱧"] = "豊",
["勒"] = "力",
["肋"] = "力",
["扐"] = "力",
["仂"] = "力",
["艻"] = "力",
["朸"] = "力",
["玏"] = "力",
["泐"] = "力",
["阞"] = "力",
["竻"] = "力",
["力"] = "力",
["屴"] = "力",
["鳨"] = "力",
["涖"] = "立",
["莅"] = "立",
["蒞"] = "立",
["位"] = "立",
["拉"] = "立",
["菈"] = "立",
["颯"] = "立",
["立"] = "立",
["粒"] = "立",
["笠"] = "立",
["砬"] = "立",
["岦"] = "立",
["苙"] = "立",
["鴗"] = "立",
["雴"] = "立",
["泣"] = "立",
["犁"] = "利",
["黎"] = "利",
["莉"] = "利",
["黧"] = "利",
["鯬"] = "利",
["筣"] = "利",
["蔾"] = "利",
["藜"] = "利",
["邌"] = "利",
["瓈"] = "利",
["鵹"] = "利",
["棃"] = "利",
["梨"] = "利",
["蟍"] = "利",
["蜊"] = "利",
["鑗"] = "利",
["利"] = "利",
["痢"] = "利",
["浰"] = "利",
["灑"] = "丽",
["籭"] = "丽",
["躧"] = "丽",
["曬"] = "丽",
["廲"] = "丽",
["驪"] = "丽",
["鱺"] = "丽",
["欐"] = "丽",
["丽"] = "丽",
["麗"] = "丽",
["儷"] = "丽",
["鸝"] = "丽",
["酈"] = "丽",
["孋"] = "丽",
["矖"] = "丽",
["穲"] = "丽",
["邐"] = "丽",
["彲"] = "丽",
["釃"] = "丽",
["襹"] = "丽",
["纚"] = "丽",
["錑"] = "戾",
["戻"] = "戾",
["戾"] = "戾",
["綟"] = "戾",
["唳"] = "戾",
["蜧"] = "戾",
["捩"] = "戾",
["悷"] = "戾",
["淚"] = "戾",
["曃"] = "隶",
["埭"] = "隶",
["逮"] = "隶",
["靆"] = "隶",
["齂"] = "隶",
["棣"] = "隶",
["隸"] = "隶",
["肆"] = "隶",
["蕼"] = "隶",
["肂"] = "隶",
["殔"] = "隶",
["隶"] = "隶",
["栗"] = "栗",
["溧"] = "栗",
["慄"] = "栗",
["鷅"] = "栗",
["凓"] = "栗",
["篥"] = "栗",
["麜"] = "栗",
["瑮"] = "栗",
["塛"] = "栗",
["搮"] = "栗",
["秝"] = "秝",
["歷"] = "秝",
["曆"] = "秝",
["磿"] = "秝",
["靂"] = "秝",
["癧"] = "秝",
["櫪"] = "秝",
["藶"] = "秝",
["瀝"] = "秝",
["攊"] = "秝",
["厤"] = "秝",
["連"] = "連",
["漣"] = "連",
["鰱"] = "連",
["翴"] = "連",
["鏈"] = "連",
["璉"] = "連",
["僆"] = "連",
["摙"] = "連",
["蓮"] = "連",
["嗹"] = "連",
["縺"] = "連",
["剆"] = "良",
["郎"] = "良",
["稂"] = "良",
["桹"] = "良",
["鋃"] = "良",
["硠"] = "良",
["浪"] = "良",
["蜋"] = "良",
["琅"] = "良",
["狼"] = "良",
["欴"] = "良",
["踉"] = "良",
["莨"] = "良",
["艆"] = "良",
["駺"] = "良",
["躴"] = "良",
["筤"] = "良",
["閬"] = "良",
["哴"] = "良",
["蓈"] = "良",
["廊"] = "良",
["榔"] = "良",
["螂"] = "良",
["瑯"] = "良",
["朗"] = "良",
["朖"] = "良",
["誏"] = "良",
["俍"] = "良",
["崀"] = "良",
["埌"] = "良",
["蒗"] = "良",
["娘"] = "良",
["良"] = "良",
["粮"] = "良",
["悢"] = "良",
["倆"] = "兩",
["兩"] = "兩",
["脼"] = "兩",
["緉"] = "兩",
["蜽"] = "兩",
["魎"] = "兩",
["輛"] = "兩",
["糧"] = "量",
["量"] = "量",
["釕"] = "了",
["了"] = "了",
["料"] = "料",
["炓"] = "料",
["簝"] = "尞",
["潦"] = "尞",
["獠"] = "尞",
["轑"] = "尞",
["橑"] = "尞",
["燎"] = "尞",
["繚"] = "尞",
["璙"] = "尞",
["憭"] = "尞",
["爒"] = "尞",
["僚"] = "尞",
["嫽"] = "尞",
["療"] = "尞",
["膫"] = "尞",
["鷯"] = "尞",
["遼"] = "尞",
["撩"] = "尞",
["嘹"] = "尞",
["寮"] = "尞",
["飉"] = "尞",
["竂"] = "尞",
["鐐"] = "尞",
["蟟"] = "尞",
["瞭"] = "尞",
["嶚"] = "尞",
["镽"] = "尞",
["例"] = "列",
["鮤"] = "列",
["栵"] = "列",
["洌"] = "列",
["銐"] = "列",
["峛"] = "列",
["列"] = "列",
["裂"] = "列",
["烈"] = "列",
["冽"] = "列",
["迾"] = "列",
["蛚"] = "列",
["茢"] = "列",
["颲"] = "列",
["鴷"] = "列",
["挒"] = "列",
["姴"] = "列",
["鑞"] = "巤",
["蠟"] = "巤",
["邋"] = "巤",
["臘"] = "巤",
["臈"] = "巤",
["擸"] = "巤",
["擖"] = "巤",
["獵"] = "巤",
["躐"] = "巤",
["鬣"] = "巤",
["儠"] = "巤",
["犣"] = "巤",
["鱲"] = "巤",
["巤"] = "巤",
["獦"] = "巤",
["婪"] = "林",
["惏"] = "林",
["啉"] = "林",
["漤"] = "林",
["醂"] = "林",
["林"] = "林",
["琳"] = "林",
["淋"] = "林",
["痳"] = "林",
["箖"] = "林",
["霖"] = "林",
["菻"] = "林",
["罧"] = "林",
["棽"] = "林",
["綝"] = "林",
["郴"] = "林",
["禁"] = "林",
["襟"] = "林",
["僸"] = "林",
["澿"] = "林",
["噤"] = "林",
["凚"] = "林",
["齽"] = "林",
["潾"] = "粦",
["膦"] = "粦",
["憐"] = "粦",
["燐"] = "粦",
["鄰"] = "粦",
["轔"] = "粦",
["嶙"] = "粦",
["粼"] = "粦",
["磷"] = "粦",
["疄"] = "粦",
["麟"] = "粦",
["鱗"] = "粦",
["璘"] = "粦",
["翷"] = "粦",
["瞵"] = "粦",
["獜"] = "粦",
["驎"] = "粦",
["鏻"] = "粦",
["繗"] = "粦",
["僯"] = "粦",
["橉"] = "粦",
["撛"] = "粦",
["遴"] = "粦",
["粦"] = "粦",
["甐"] = "粦",
["麐"] = "吝",
["吝"] = "吝",
["悋"] = "吝",
["藺"] = "閵",
["閵"] = "閵",
["躪"] = "閵",
["轥"] = "閵",
["棱"] = "夌",
["稜"] = "夌",
["輘"] = "夌",
["倰"] = "夌",
["祾"] = "夌",
["踜"] = "夌",
["凌"] = "夌",
["菱"] = "夌",
["陵"] = "夌",
["綾"] = "夌",
["鯪"] = "夌",
["淩"] = "夌",
["夌"] = "夌",
["蔆"] = "夌",
["崚"] = "夌",
["掕"] = "夌",
["𩜁"] = "夌",
["庱"] = "夌",
["睖"] = "夌",
["靈"] = "霝",
["霛"] = "霝",
["麢"] = "霝",
["霝"] = "霝",
["醽"] = "霝",
["櫺"] = "霝",
["蘦"] = "霝",
["孁"] = "霝",
["龗"] = "霝",
["酃"] = "霝",
["爧"] = "霝",
["欞"] = "霝",
["令"] = "令",
["怜"] = "令",
["零"] = "令",
["魿"] = "令",
["命"] = "令",
["冷"] = "令",
["跉"] = "令",
["嶺"] = "令",
["領"] = "令",
["阾"] = "令",
["柃"] = "令",
["袊"] = "令",
["詅"] = "令",
["旍"] = "令",
["舲"] = "令",
["齡"] = "令",
["囹"] = "令",
["鴒"] = "令",
["蛉"] = "令",
["鈴"] = "令",
["苓"] = "令",
["伶"] = "令",
["泠"] = "令",
["瓴"] = "令",
["拎"] = "令",
["刢"] = "令",
["玲"] = "令",
["聆"] = "令",
["竛"] = "令",
["軨"] = "令",
["笭"] = "令",
["翎"] = "令",
["閝"] = "令",
["鹷"] = "令",
["昤"] = "令",
["駖"] = "令",
["彾"] = "令",
["呤"] = "令",
["狑"] = "令",
["秢"] = "令",
["岭"] = "令",
["紷"] = "令",
["砱"] = "令",
["羚"] = "令",
["姈"] = "令",
["蕶"] = "令",
["澪"] = "令",
["流"] = "流",
["硫"] = "流",
["琉"] = "流",
["旒"] = "流",
["瑬"] = "流",
["裗"] = "流",
["六"] = "六",
["圥"] = "六",
["醪"] = "翏",
["嫪"] = "翏",
["嘐"] = "翏",
["賿"] = "翏",
["顟"] = "翏",
["膠"] = "翏",
["轇"] = "翏",
["摎"] = "翏",
["髎"] = "翏",
["熮"] = "翏",
["廖"] = "翏",
["憀"] = "翏",
["豂"] = "翏",
["漻"] = "翏",
["翏"] = "翏",
["寥"] = "翏",
["蓼"] = "翏",
["鄝"] = "翏",
["鷚"] = "翏",
["繆"] = "翏",
["勠"] = "翏",
["疁"] = "翏",
["飂"] = "翏",
["鏐"] = "翏",
["僇"] = "翏",
["瘳"] = "翏",
["璆"] = "翏",
["謬"] = "翏",
["蟉"] = "翏",
["樛"] = "翏",
["磟"] = "翏",
["戮"] = "翏",
["剹"] = "翏",
["穋"] = "翏",
["驡"] = "龍",
["龐"] = "龍",
["瀧"] = "龍",
["籠"] = "龍",
["豅"] = "龍",
["朧"] = "龍",
["龓"] = "龍",
["聾"] = "龍",
["礱"] = "龍",
["嚨"] = "龍",
["蘢"] = "龍",
["櫳"] = "龍",
["襱"] = "龍",
["瓏"] = "龍",
["曨"] = "龍",
["鸗"] = "龍",
["蠪"] = "龍",
["巃"] = "龍",
["竉"] = "龍",
["攏"] = "龍",
["儱"] = "龍",
["龍"] = "龍",
["躘"] = "龍",
["隴"] = "龍",
["壠"] = "龍",
["壟"] = "龍",
["寵"] = "龍",
["龏"] = "龍",
["樓"] = "婁",
["摟"] = "婁",
["耬"] = "婁",
["僂"] = "婁",
["婁"] = "婁",
["螻"] = "婁",
["髏"] = "婁",
["蔞"] = "婁",
["鞻"] = "婁",
["艛"] = "婁",
["膢"] = "婁",
["廔"] = "婁",
["嘍"] = "婁",
["瞜"] = "婁",
["簍"] = "婁",
["慺"] = "婁",
["鷜"] = "婁",
["褸"] = "婁",
["遱"] = "婁",
["謱"] = "婁",
["嶁"] = "婁",
["甊"] = "婁",
["塿"] = "婁",
["漊"] = "婁",
["鏤"] = "婁",
["瘻"] = "婁",
["擻"] = "婁",
["藪"] = "婁",
["籔"] = "婁",
["氀"] = "婁",
["縷"] = "婁",
["屢"] = "婁",
["數"] = "婁",
["屨"] = "婁",
["窶"] = "婁",
["貗"] = "婁",
["漏"] = "屚",
["瘺"] = "屚",
["屚"] = "屚",
["滷"] = "鹵",
["鹵"] = "鹵",
["樐"] = "鹵",
["蓾"] = "鹵",
["鏀"] = "鹵",
["魯"] = "魯",
["櫓"] = "魯",
["穭"] = "魯",
["灶"] = "圥",
["竈"] = "圥",
["鼀"] = "圥",
["睦"] = "坴",
["坴"] = "坴",
["陸"] = "坴",
["稑"] = "坴",
["鵱"] = "坴",
["鯥"] = "坴",
["淕"] = "坴",
["踛"] = "坴",
["剝"] = "彔",
["龣"] = "彔",
["祿"] = "彔",
["淥"] = "彔",
["睩"] = "彔",
["琭"] = "彔",
["箓"] = "彔",
["碌"] = "彔",
["盝"] = "彔",
["娽"] = "彔",
["彔"] = "彔",
["趢"] = "彔",
["簶"] = "彔",
["綠"] = "彔",
["逯"] = "彔",
["錄"] = "彔",
["醁"] = "彔",
["騄"] = "彔",
["菉"] = "彔",
["籙"] = "彔",
["鄜"] = "鹿",
["鹿"] = "鹿",
["漉"] = "鹿",
["轆"] = "鹿",
["簏"] = "鹿",
["螰"] = "鹿",
["麓"] = "鹿",
["騼"] = "鹿",
["摝"] = "鹿",
["廘"] = "鹿",
["蔍"] = "鹿",
["鏕"] = "鹿",
["櫚"] = "呂",
["閭"] = "呂",
["吕"] = "呂",
["侣"] = "呂",
["梠"] = "呂",
["郘"] = "呂",
["絽"] = "呂",
["莒"] = "呂",
["筥"] = "呂",
["酹"] = "寽",
["脟"] = "寽",
["捋"] = "寽",
["蛶"] = "寽",
["埒"] = "寽",
["鋝"] = "寽",
["浖"] = "寽",
["哷"] = "寽",
["寽"] = "寽",
["玈"] = "旅",
["旅"] = "旅",
["膂"] = "旅",
["祣"] = "旅",
["率"] = "率",
["繂"] = "率",
["膟"] = "率",
["蟀"] = "率",
["蠻"] = "䜌",
["矕"] = "䜌",
["變"] = "䜌",
["鸞"] = "䜌",
["巒"] = "䜌",
["灤"] = "䜌",
["欒"] = "䜌",
["鑾"] = "䜌",
["臠"] = "䜌",
["羉"] = "䜌",
["䜌"] = "䜌",
["灓"] = "䜌",
["圝"] = "䜌",
["曫"] = "䜌",
["孿"] = "䜌",
["彎"] = "䜌",
["灣"] = "䜌",
["攣"] = "䜌",
["孌"] = "䜌",
["戀"] = "䜌",
["㪻"] = "䜌",
["卵"] = "卵",
["關"] = "卵",
["関"] = "卵",
["聯"] = "卵",
["覼"] = "亂",
["亂"] = "亂",
["薍"] = "亂",
["蜦"] = "侖",
["綸"] = "侖",
["論"] = "侖",
["崘"] = "侖",
["掄"] = "侖",
["菕"] = "侖",
["惀"] = "侖",
["睔"] = "侖",
["碖"] = "侖",
["溣"] = "侖",
["倫"] = "侖",
["淪"] = "侖",
["輪"] = "侖",
["侖"] = "侖",
["陯"] = "侖",
["鯩"] = "侖",
["棆"] = "侖",
["踚"] = "侖",
["耣"] = "侖",
["錀"] = "侖",
["羅"] = "羅",
["蘿"] = "羅",
["籮"] = "羅",
["儸"] = "羅",
["饠"] = "羅",
["欏"] = "羅",
["囉"] = "羅",
["鑼"] = "羅",
["邏"] = "羅",
["驘"] = "蠃",
["鸁"] = "蠃",
["蠃"] = "蠃",
["臝"] = "蠃",
["羸"] = "蠃",
["麻"] = "麻",
["痲"] = "麻",
["犘"] = "麻",
["縻"] = "麻",
["醿"] = "麻",
["爢"] = "麻",
["蘼"] = "麻",
["靡"] = "麻",
["摩"] = "麻",
["魔"] = "麻",
["磨"] = "麻",
["劘"] = "麻",
["麽"] = "麻",
["麼"] = "麻",
["塺"] = "麻",
["麾"] = "麻",
["馬"] = "馬",
["碼"] = "馬",
["罵"] = "馬",
["鷌"] = "馬",
["鰢"] = "馬",
["瑪"] = "馬",
["榪"] = "馬",
["傌"] = "馬",
["禡"] = "馬",
["媽"] = "馬",
["買"] = "買",
["蕒"] = "買",
["嘪"] = "買",
["鷶"] = "買",
["賣"] = "買",
["獌"] = "曼",
["鬘"] = "曼",
["謾"] = "曼",
["慢"] = "曼",
["嫚"] = "曼",
["縵"] = "曼",
["曼"] = "曼",
["饅"] = "曼",
["蔓"] = "曼",
["鰻"] = "曼",
["墁"] = "曼",
["鏝"] = "曼",
["槾"] = "曼",
["漫"] = "曼",
["幔"] = "曼",
["蟃"] = "曼",
["厖"] = "尨",
["駹"] = "尨",
["狵"] = "尨",
["尨"] = "尨",
["浝"] = "尨",
["哤"] = "尨",
["牻"] = "尨",
["娏"] = "尨",
["蛖"] = "尨",
["𥆙"] = "尨",
["痝"] = "尨",
["𪁪"] = "尨",
["莽"] = "茻",
["茻"] = "茻",
["蟒"] = "莽",
["漭"] = "莽",
["毛"] = "毛",
["髦"] = "毛",
["旄"] = "毛",
["芼"] = "毛",
["氂"] = "毛",
["枆"] = "毛",
["酕"] = "毛",
["牦"] = "毛",
["耄"] = "毛",
["覒"] = "毛",
["眊"] = "毛",
["耗"] = "毛",
["秏"] = "毛",
["毣"] = "毛",
["堥"] = "矛",
["蓩"] = "矛",
["猱"] = "矛",
["茅"] = "矛",
["罞"] = "矛",
["蝥"] = "矛",
["鶜"] = "矛",
["瞀"] = "矛",
["愗"] = "矛",
["袤"] = "矛",
["雺"] = "矛",
["懋"] = "矛",
["楙"] = "矛",
["矛"] = "矛",
["蟊"] = "矛",
["髳"] = "矛",
["鍪"] = "矛",
["鞪"] = "矛",
["糅"] = "矛",
["腬"] = "矛",
["柔"] = "矛",
["揉"] = "矛",
["蹂"] = "矛",
["鍒"] = "矛",
["騥"] = "矛",
["蝚"] = "矛",
["葇"] = "矛",
["鞣"] = "矛",
["鰇"] = "矛",
["瑈"] = "矛",
["鶔"] = "矛",
["楺"] = "矛",
["煣"] = "矛",
["輮"] = "矛",
["韖"] = "矛",
["敄"] = "矛",
["務"] = "矛",
["鶩"] = "矛",
["騖"] = "矛",
["婺"] = "矛",
["霚"] = "矛",
["嵍"] = "矛",
["霧"] = "矛",
["霿"] = "矛",
["楘"] = "矛",
["卯"] = "卯",
["昴"] = "卯",
["泖"] = "卯",
["茆"] = "卯",
["奅"] = "卯",
["窌"] = "卯",
["聊"] = "卯",
["貿"] = "卯",
["鄮"] = "卯",
["劉"] = "卯",
["留"] = "卯",
["蒥"] = "卯",
["鶹"] = "卯",
["騮"] = "卯",
["榴"] = "卯",
["瑠"] = "卯",
["瘤"] = "卯",
["遛"] = "卯",
["鎦"] = "卯",
["飀"] = "卯",
["鰡"] = "卯",
["嵧"] = "卯",
["餾"] = "卯",
["瀏"] = "卯",
["懰"] = "卯",
["柳"] = "卯",
["珋"] = "卯",
["罶"] = "卯",
["嬼"] = "卯",
["溜"] = "卯",
["霤"] = "卯",
["廇"] = "卯",
["塯"] = "卯",
["媢"] = "冃",
["冃"] = "冃",
["冒"] = "冃",
["帽"] = "冃",
["瑁"] = "冃",
["賵"] = "冃",
["艒"] = "冃",
["萺"] = "冃",
["勗"] = "冃",
["勖"] = "冃",
["貌"] = "皃",
["皃"] = "皃",
["藐"] = "皃",
["邈"] = "皃",
["眉"] = "眉",
["楣"] = "眉",
["嵋"] = "眉",
["湄"] = "眉",
["鶥"] = "眉",
["瑂"] = "眉",
["郿"] = "眉",
["葿"] = "眉",
["媚"] = "眉",
["篃"] = "眉",
["蝞"] = "眉",
["煝"] = "眉",
["美"] = "美",
["渼"] = "美",
["媄"] = "美",
["蔄"] = "門",
["闅"] = "門",
["閩"] = "門",
["蕄"] = "門",
["門"] = "門",
["捫"] = "門",
["悶"] = "門",
["聞"] = "門",
["閿"] = "門",
["問"] = "門",
["蒙"] = "冡",
["冡"] = "冡",
["濛"] = "冡",
["艨"] = "冡",
["朦"] = "冡",
["矇"] = "冡",
["饛"] = "冡",
["檬"] = "冡",
["鸏"] = "冡",
["幪"] = "冡",
["蠓"] = "冡",
["靀"] = "冡",
["曚"] = "冡",
["蘉"] = "夢",
["瞢"] = "夢",
["懜"] = "夢",
["懵"] = "夢",
["甍"] = "夢",
["薨"] = "夢",
["夢"] = "夢",
["鄸"] = "夢",
["謎"] = "米",
["迷"] = "米",
["米"] = "米",
["眯"] = "米",
["洣"] = "米",
["蔝"] = "米",
["麊"] = "米",
["冞"] = "米",
["糜"] = "米",
["敉"] = "米",
["侎"] = "米",
["麋"] = "米",
["蘪"] = "米",
["攗"] = "米",
["羋"] = "羋",
["蝆"] = "羋",
["冟"] = "冖",
["冖"] = "冖",
["冪"] = "冖",
["羃"] = "冖",
["濗"] = "冖",
["鼏"] = "冖",
["覓"] = "覓",
["漞"] = "覓",
["芇"] = "芇",
["蹣"] = "芇",
["瞞"] = "芇",
["顢"] = "芇",
["樠"] = "芇",
["慲"] = "芇",
["鬗"] = "芇",
["滿"] = "芇",
["鏋"] = "芇",
["懣"] = "芇",
["璊"] = "芇",
["綿"] = "綿",
["棉"] = "綿",
["緜"] = "綿",
["矊"] = "綿",
["臱"] = "臱",
["矏"] = "臱",
["櫋"] = "臱",
["籩"] = "臱",
["邊"] = "臱",
["汅"] = "丏",
["沔"] = "丏",
["丏"] = "丏",
["眄"] = "丏",
["麪"] = "丏",
["免"] = "免",
["娩"] = "免",
["勉"] = "免",
["俛"] = "免",
["鮸"] = "免",
["冕"] = "免",
["絻"] = "免",
["婏"] = "免",
["浼"] = "免",
["鞔"] = "免",
["悗"] = "免",
["嬎"] = "免",
["晚"] = "免",
["挽"] = "免",
["輓"] = "免",
["脕"] = "免",
["莬"] = "免",
["蝒"] = "面",
["緬"] = "面",
["湎"] = "面",
["愐"] = "面",
["偭"] = "面",
["勔"] = "面",
["面"] = "面",
["麵"] = "面",
["貓"] = "苗",
["描"] = "苗",
["媌"] = "苗",
["緢"] = "苗",
["苗"] = "苗",
["猫"] = "苗",
["庿"] = "苗",
["礣"] = "蔑",
["蔑"] = "蔑",
["篾"] = "蔑",
["蠛"] = "蔑",
["懱"] = "蔑",
["幭"] = "蔑",
["鱴"] = "蔑",
["衊"] = "蔑",
["瀎"] = "蔑",
["襪"] = "蔑",
["韤"] = "蔑",
["韈"] = "蔑",
["眠"] = "民",
["民"] = "民",
["泯"] = "民",
["怋"] = "民",
["岷"] = "民",
["珉"] = "民",
["罠"] = "民",
["笢"] = "民",
["鈱"] = "民",
["抿"] = "民",
["刡"] = "民",
["敃"] = "民",
["愍"] = "民",
["暋"] = "民",
["蟁"] = "民",
["猛"] = "皿",
["艋"] = "皿",
["蜢"] = "皿",
["皿"] = "皿",
["孟"] = "皿",
["憫"] = "閔",
["閔"] = "閔",
["潣"] = "閔",
["簢"] = "閔",
["燘"] = "閔",
["澠"] = "黽",
["黽"] = "黽",
["僶"] = "黽",
["繩"] = "黽",
["譝"] = "黽",
["憴"] = "黽",
["鱦"] = "黽",
["蠅"] = "黽",
["鄳"] = "黽",
["鼆"] = "黽",
["名"] = "名",
["洺"] = "名",
["眳"] = "名",
["詺"] = "名",
["銘"] = "名",
["茗"] = "名",
["酩"] = "名",
["姳"] = "名",
["莔"] = "明",
["明"] = "明",
["盟"] = "明",
["萌"] = "明",
["奣"] = "明",
["瞑"] = "冥",
["嫇"] = "冥",
["慏"] = "冥",
["冥"] = "冥",
["榠"] = "冥",
["鄍"] = "冥",
["溟"] = "冥",
["螟"] = "冥",
["猽"] = "冥",
["蓂"] = "冥",
["覭"] = "冥",
["暝"] = "冥",
["幎"] = "冥",
["幂"] = "冥",
["塓"] = "冥",
["眛"] = "末",
["沬"] = "末",
["佅"] = "末",
["韎"] = "末",
["帓"] = "末",
["粖"] = "末",
["末"] = "末",
["昩"] = "末",
["秣"] = "末",
["靺"] = "末",
["眜"] = "末",
["抹"] = "末",
["妺"] = "末",
["怽"] = "末",
["沫"] = "末",
["袜"] = "末",
["沒"] = "沒",
["歿"] = "沒",
["莈"] = "沒",
["蟆"] = "莫",
["鬕"] = "莫",
["饃"] = "莫",
["模"] = "莫",
["摸"] = "莫",
["嫫"] = "莫",
["謨"] = "莫",
["膜"] = "莫",
["摹"] = "莫",
["暮"] = "莫",
["慕"] = "莫",
["募"] = "莫",
["墓"] = "莫",
["慔"] = "莫",
["莫"] = "莫",
["寞"] = "莫",
["漠"] = "莫",
["幕"] = "莫",
["鏌"] = "莫",
["瘼"] = "莫",
["鄚"] = "莫",
["瞙"] = "莫",
["嗼"] = "莫",
["塻"] = "莫",
["貘"] = "莫",
["驀"] = "莫",
["牟"] = "牟",
["眸"] = "牟",
["侔"] = "牟",
["蛑"] = "牟",
["麰"] = "牟",
["劺"] = "牟",
["恈"] = "牟",
["鴾"] = "牟",
["某"] = "某",
["謀"] = "某",
["媒"] = "某",
["煤"] = "某",
["腜"] = "某",
["禖"] = "某",
["脢"] = "母",
["挴"] = "母",
["海"] = "母",
["毐"] = "母",
["呣"] = "母",
["拇"] = "母",
["母"] = "母",
["胟"] = "母",
["姆"] = "母",
["畮"] = "母",
["踇"] = "母",
["苺"] = "母",
["莓"] = "母",
["敏"] = "母",
["鰵"] = "母",
["慜"] = "母",
["毋"] = "母",
["梅"] = "母",
["酶"] = "母",
["鋂"] = "母",
["每"] = "母",
["毎"] = "母",
["痗"] = "母",
["晦"] = "母",
["誨"] = "母",
["悔"] = "母",
["霉"] = "母",
["畝"] = "畝",
["畞"] = "畝",
["姥"] = "姥",
["峔"] = "姥",
["瞐"] = "目",
["苜"] = "目",
["目"] = "目",
["木"] = "木",
["沐"] = "木",
["朷"] = "木",
["蚞"] = "木",
["霂"] = "木",
["乃"] = "乃",
["鼐"] = "乃",
["疓"] = "乃",
["奶"] = "乃",
["仍"] = "乃",
["艿"] = "乃",
["辸"] = "乃",
["礽"] = "乃",
["扔"] = "乃",
["芿"] = "乃",
["奈"] = "奈",
["柰"] = "奈",
["渿"] = "奈",
["捺"] = "奈",
["南"] = "南",
["楠"] = "南",
["腩"] = "南",
["湳"] = "南",
["揇"] = "南",
["萳"] = "南",
["罱"] = "南",
["喃"] = "南",
["諵"] = "南",
["獿"] = "夒",
["犪"] = "夒",
["擾"] = "夒",
["腦"] = "匘",
["惱"] = "匘",
["瑙"] = "匘",
["匘"] = "匘",
["碯"] = "匘",
["硇"] = "匘",
["𣳦"] = "匘",
["妠"] = "內",
["內"] = "內",
["笍"] = "內",
["芮"] = "內",
["枘"] = "內",
["汭"] = "內",
["鈉"] = "內",
["蜹"] = "內",
["抐"] = "內",
["納"] = "內",
["衲"] = "內",
["魶"] = "內",
["軜"] = "內",
["蒳"] = "內",
["豽"] = "內",
["肭"] = "內",
["吶"] = "內",
["焫"] = "內",
["訥"] = "內",
["態"] = "能",
["能"] = "能",
["螚"] = "能",
["褦"] = "能",
["泥"] = "尼",
["屔"] = "尼",
["苨"] = "尼",
["坭"] = "尼",
["迡"] = "尼",
["埿"] = "尼",
["旎"] = "尼",
["狔"] = "尼",
["抳"] = "尼",
["尼"] = "尼",
["呢"] = "尼",
["怩"] = "尼",
["柅"] = "尼",
["蚭"] = "尼",
["跜"] = "尼",
["秜"] = "尼",
["痆"] = "尼",
["昵"] = "尼",
["孨"] = "孴",
["孴"] = "孴",
["斥"] = "屰",
["訴"] = "屰",
["泝"] = "屰",
["愬"] = "屰",
["遡"] = "屰",
["塑"] = "屰",
["溯"] = "屰",
["遻"] = "屰",
["柝"] = "屰",
["鶚"] = "屰",
["諤"] = "屰",
["齶"] = "屰",
["鍔"] = "屰",
["遌"] = "屰",
["崿"] = "屰",
["偔"] = "屰",
["堮"] = "屰",
["湂"] = "屰",
["鱷"] = "屰",
["噩"] = "屰",
["咢"] = "屰",
["鑩"] = "屰",
["鄂"] = "屰",
["鰐"] = "屰",
["萼"] = "屰",
["愕"] = "屰",
["顎"] = "屰",
["朔"] = "屰",
["槊"] = "屰",
["蒴"] = "屰",
["坼"] = "屰",
["拆"] = "屰",
["逆"] = "屰",
["屰"] = "屰",
["縌"] = "屰",
["蝷"] = "屰",
["暱"] = "匿",
["慝"] = "匿",
["匿"] = "匿",
["輦"] = "輦",
["鄻"] = "輦",
["攆"] = "輦",
["淰"] = "念",
["唸"] = "念",
["埝"] = "念",
["趝"] = "念",
["諗"] = "念",
["稔"] = "念",
["棯"] = "念",
["腍"] = "念",
["捻"] = "念",
["錜"] = "念",
["敜"] = "念",
["惗"] = "念",
["菍"] = "念",
["島"] = "鳥",
["搗"] = "鳥",
["鵃"] = "鳥",
["瞗"] = "鳥",
["鳥"] = "鳥",
["蔦"] = "鳥",
["窵"] = "鳥",
["甈"] = "臬",
["臬"] = "臬",
["嵲"] = "臬",
["臲"] = "臬",
["闑"] = "臬",
["攝"] = "聶",
["聶"] = "聶",
["鑷"] = "聶",
["躡"] = "聶",
["襵"] = "聶",
["懾"] = "聶",
["囁"] = "聶",
["欇"] = "聶",
["灄"] = "聶",
["顳"] = "聶",
["讘"] = "聶",
["濘"] = "寧",
["獰"] = "寧",
["檸"] = "寧",
["儜"] = "寧",
["薴"] = "寧",
["鬡"] = "寧",
["鑏"] = "寧",
["嬣"] = "寧",
["寍"] = "寧",
["寗"] = "寧",
["甯"] = "寧",
["寧"] = "寧",
["鸋"] = "寧",
["嚀"] = "寧",
["聹"] = "寧",
["嶩"] = "農",
["噥"] = "農",
["鬞"] = "農",
["擃"] = "農",
["齈"] = "農",
["癑"] = "農",
["農"] = "農",
["辳"] = "農",
["憹"] = "農",
["膿"] = "農",
["儂"] = "農",
["濃"] = "農",
["醲"] = "農",
["襛"] = "農",
["穠"] = "農",
["檂"] = "農",
["筭"] = "弄",
["弄"] = "弄",
["梇"] = "弄",
["哢"] = "弄",
["硦"] = "弄",
["拿"] = "女",
["拏"] = "女",
["詉"] = "女",
["蒘"] = "女",
["笯"] = "女",
["挐"] = "女",
["絮"] = "女",
["呶"] = "女",
["怓"] = "女",
["帑"] = "女",
["奴"] = "女",
["砮"] = "女",
["駑"] = "女",
["孥"] = "女",
["努"] = "女",
["弩"] = "女",
["怒"] = "女",
["袽"] = "女",
["帤"] = "女",
["女"] = "女",
["籹"] = "女",
["恕"] = "女",
["如"] = "女",
["茹"] = "女",
["洳"] = "女",
["鴽"] = "女",
["蕠"] = "女",
["汝"] = "女",
["肗"] = "女",
["虐"] = "虐",
["瘧"] = "虐",
["謔"] = "虐",
["庍"] = "派",
["派"] = "派",
["脈"] = "派",
["脉"] = "派",
["衇"] = "派",
["眽"] = "派",
["覛"] = "派",
["霢"] = "派",
["佩"] = "佩",
["珮"] = "佩",
["配"] = "配",
["嶏"] = "配",
["崩"] = "朋",
["堋"] = "朋",
["漰"] = "朋",
["倗"] = "朋",
["朋"] = "朋",
["鵬"] = "朋",
["棚"] = "朋",
["鬅"] = "朋",
["掤"] = "朋",
["淜"] = "朋",
["輣"] = "朋",
["繃"] = "朋",
["弸"] = "朋",
["澎"] = "彭",
["彭"] = "彭",
["膨"] = "彭",
["蟚"] = "彭",
["蟛"] = "彭",
["憉"] = "彭",
["陂"] = "皮",
["詖"] = "皮",
["彼"] = "皮",
["柀"] = "皮",
["佊"] = "皮",
["貱"] = "皮",
["跛"] = "皮",
["披"] = "皮",
["鈹"] = "皮",
["帔"] = "皮",
["鮍"] = "皮",
["耚"] = "皮",
["狓"] = "皮",
["翍"] = "皮",
["旇"] = "皮",
["秛"] = "皮",
["紴"] = "皮",
["皮"] = "皮",
["疲"] = "皮",
["被"] = "皮",
["髲"] = "皮",
["鞁"] = "皮",
["骳"] = "皮",
["波"] = "皮",
["碆"] = "皮",
["菠"] = "皮",
["簸"] = "皮",
["駊"] = "皮",
["頗"] = "皮",
["坡"] = "皮",
["玻"] = "皮",
["破"] = "皮",
["婆"] = "皮",
["蔢"] = "皮",
["匹"] = "匹",
["鴄"] = "匹",
["爿"] = "片",
["片"] = "片",
["沜"] = "片",
["標"] = "票",
["鏢"] = "票",
["瘭"] = "票",
["幖"] = "票",
["熛"] = "票",
["蔈"] = "票",
["膘"] = "票",
["褾"] = "票",
["票"] = "票",
["飄"] = "票",
["漂"] = "票",
["螵"] = "票",
["瞟"] = "票",
["嫖"] = "票",
["旚"] = "票",
["僄"] = "票",
["慓"] = "票",
["彯"] = "票",
["摽"] = "票",
["翲"] = "票",
["嘌"] = "票",
["縹"] = "票",
["醥"] = "票",
["顠"] = "票",
["篻"] = "票",
["剽"] = "票",
["勡"] = "票",
["薸"] = "票",
["瓢"] = "票",
["鰾"] = "票",
["驃"] = "票",
["瀕"] = "頻",
["頻"] = "頻",
["顰"] = "頻",
["蘋"] = "頻",
["嚬"] = "頻",
["臨"] = "品",
["瀶"] = "品",
["品"] = "品",
["俜"] = "甹",
["甹"] = "甹",
["聘"] = "甹",
["娉"] = "甹",
["騁"] = "甹",
["梬"] = "甹",
["平"] = "平",
["怦"] = "平",
["砰"] = "平",
["抨"] = "平",
["閛"] = "平",
["伻"] = "平",
["評"] = "平",
["坪"] = "平",
["苹"] = "平",
["枰"] = "平",
["泙"] = "平",
["蚲"] = "平",
["胓"] = "平",
["匉"] = "平",
["萍"] = "平",
["蓓"] = "咅",
["倍"] = "咅",
["菩"] = "咅",
["掊"] = "咅",
["剖"] = "咅",
["婄"] = "咅",
["踣"] = "咅",
["箁"] = "咅",
["咅"] = "咅",
["部"] = "咅",
["培"] = "咅",
["犃"] = "咅",
["瓿"] = "咅",
["殕"] = "咅",
["醅"] = "咅",
["棓"] = "咅",
["涪"] = "咅",
["賠"] = "咅",
["陪"] = "咅",
["毰"] = "咅",
["焙"] = "咅",
["樸"] = "菐",
["璞"] = "菐",
["墣"] = "菐",
["撲"] = "菐",
["轐"] = "菐",
["獛"] = "菐",
["蹼"] = "菐",
["襆"] = "菐",
["濮"] = "菐",
["纀"] = "菐",
["醭"] = "菐",
["僕"] = "菐",
["菐"] = "菐",
["穙"] = "菐",
["鏷"] = "菐",
["幞"] = "菐",
["譜"] = "普",
["普"] = "普",
["切"] = "七",
["砌"] = "七",
["沏"] = "七",
["七"] = "七",
["柒"] = "七",
["叱"] = "七",
["妻"] = "妻",
["萋"] = "妻",
["淒"] = "妻",
["凄"] = "妻",
["悽"] = "妻",
["鶈"] = "妻",
["郪"] = "妻",
["緀"] = "妻",
["霋"] = "妻",
["棲"] = "妻",
["漆"] = "桼",
["桼"] = "桼",
["膝"] = "桼",
["厀"] = "桼",
["基"] = "其",
["箕"] = "其",
["朞"] = "其",
["稘"] = "其",
["萁"] = "其",
["期"] = "其",
["諅"] = "其",
["其"] = "其",
["錤"] = "其",
["諆"] = "其",
["欺"] = "其",
["娸"] = "其",
["魌"] = "其",
["僛"] = "其",
["唭"] = "其",
["棋"] = "其",
["旗"] = "其",
["淇"] = "其",
["祺"] = "其",
["琪"] = "其",
["騏"] = "其",
["綦"] = "其",
["麒"] = "其",
["蜞"] = "其",
["綨"] = "其",
["蜝"] = "其",
["棊"] = "其",
["碁"] = "其",
["鯕"] = "其",
["踑"] = "其",
["藄"] = "其",
["櫀"] = "其",
["璂"] = "其",
["禥"] = "其",
["惎"] = "其",
["帺"] = "其",
["奇"] = "奇",
["畸"] = "奇",
["剞"] = "奇",
["羇"] = "奇",
["掎"] = "奇",
["攲"] = "奇",
["躸"] = "奇",
["踦"] = "奇",
["寄"] = "奇",
["徛"] = "奇",
["欹"] = "奇",
["崎"] = "奇",
["觭"] = "奇",
["碕"] = "奇",
["綺"] = "奇",
["婍"] = "奇",
["騎"] = "奇",
["琦"] = "奇",
["鵸"] = "奇",
["錡"] = "奇",
["輢"] = "奇",
["齮"] = "奇",
["犄"] = "奇",
["猗"] = "奇",
["椅"] = "奇",
["旖"] = "奇",
["陭"] = "奇",
["檹"] = "奇",
["漪"] = "奇",
["倚"] = "奇",
["畁"] = "畁",
["萆"] = "畁",
["儕"] = "齊",
["麡"] = "齊",
["齋"] = "齊",
["穧"] = "齊",
["擠"] = "齊",
["躋"] = "齊",
["齏"] = "齊",
["齎"] = "齊",
["櫅"] = "齊",
["齌"] = "齊",
["隮"] = "齊",
["賷"] = "齊",
["虀"] = "齊",
["濟"] = "齊",
["癠"] = "齊",
["霽"] = "齊",
["齊"] = "齊",
["臍"] = "齊",
["蠐"] = "齊",
["懠"] = "齊",
["薺"] = "齊",
["鱭"] = "齊",
["嚌"] = "齊",
["劑"] = "齊",
["齍"] = "齊",
["乞"] = "乞",
["盵"] = "乞",
["齕"] = "乞",
["紇"] = "乞",
["籺"] = "乞",
["圪"] = "乞",
["訖"] = "乞",
["吃"] = "乞",
["扢"] = "乞",
["疙"] = "乞",
["屹"] = "乞",
["仡"] = "乞",
["迄"] = "乞",
["釳"] = "乞",
["忔"] = "乞",
["汔"] = "乞",
["矻"] = "乞",
["麧"] = "乞",
["獃"] = "豈",
["剴"] = "豈",
["凱"] = "豈",
["愷"] = "豈",
["塏"] = "豈",
["鎧"] = "豈",
["颽"] = "豈",
["暟"] = "豈",
["闓"] = "豈",
["豈"] = "豈",
["隑"] = "豈",
["皚"] = "豈",
["嵦"] = "豈",
["敳"] = "豈",
["磑"] = "豈",
["螘"] = "豈",
["覬"] = "豈",
["溰"] = "豈",
["顗"] = "豈",
["啓"] = "啓",
["啟"] = "啓",
["棨"] = "啓",
["綮"] = "啓",
["晵"] = "啓",
["闙"] = "啓",
["启"] = "啓",
["愾"] = "气",
["忾"] = "气",
["刏"] = "气",
["汽"] = "气",
["氣"] = "气",
["气"] = "气",
["忥"] = "气",
["餼"] = "气",
["鎎"] = "气",
["熂"] = "气",
["霼"] = "气",
["犔"] = "气",
["楫"] = "咠",
["檝"] = "咠",
["湒"] = "咠",
["咠"] = "咠",
["葺"] = "咠",
["緝"] = "咠",
["諿"] = "咠",
["輯"] = "咠",
["箿"] = "咠",
["蕺"] = "咠",
["戢"] = "咠",
["濈"] = "咠",
["霵"] = "咠",
["揖"] = "咠",
["牽"] = "牽",
["縴"] = "牽",
["厱"] = "僉",
["歛"] = "僉",
["臉"] = "僉",
["醶"] = "僉",
["鹼"] = "僉",
["薟"] = "僉",
["匳"] = "僉",
["獫"] = "僉",
["蘞"] = "僉",
["籢"] = "僉",
["斂"] = "僉",
["瀲"] = "僉",
["撿"] = "僉",
["羷"] = "僉",
["殮"] = "僉",
["簽"] = "僉",
["僉"] = "僉",
["憸"] = "僉",
["譣"] = "僉",
["檢"] = "僉",
["瞼"] = "僉",
["儉"] = "僉",
["顩"] = "僉",
["嬐"] = "僉",
["嶮"] = "僉",
["噞"] = "僉",
["驗"] = "僉",
["險"] = "僉",
["劒"] = "僉",
["劍"] = "僉",
["𩏩"] = "僉",
["遷"] = "遷",
["櫏"] = "遷",
["韆"] = "遷",
["僊"] = "遷",
["躚"] = "遷",
["蹮"] = "遷",
["媊"] = "前",
["煎"] = "前",
["湔"] = "前",
["鬋"] = "前",
["剪"] = "前",
["翦"] = "前",
["譾"] = "前",
["揃"] = "前",
["箭"] = "前",
["葥"] = "前",
["前"] = "前",
["騚"] = "前",
["歬"] = "前",
["虔"] = "虔",
["鰬"] = "虔",
["榩"] = "虔",
["遣"] = "遣",
["繾"] = "遣",
["譴"] = "遣",
["坎"] = "欠",
["扻"] = "欠",
["砍"] = "欠",
["芡"] = "欠",
["欠"] = "欠",
["杴"] = "欠",
["鍁"] = "欠",
["臧"] = "爿",
["牂"] = "爿",
["戕"] = "爿",
["贓"] = "爿",
["藏"] = "爿",
["奘"] = "爿",
["臟"] = "爿",
["將"] = "爿",
["漿"] = "爿",
["鱂"] = "爿",
["蔣"] = "爿",
["螿"] = "爿",
["槳"] = "爿",
["獎"] = "爿",
["醬"] = "爿",
["鏘"] = "爿",
["蹡"] = "爿",
["嶈"] = "爿",
["斨"] = "爿",
["爿"] = "爿",
["牆"] = "爿",
["妝"] = "爿",
["莊"] = "爿",
["裝"] = "爿",
["壯"] = "爿",
["疒"] = "爿",
["床"] = "爿",
["牀"] = "爿",
["狀"] = "爿",
["繈"] = "強",
["襁"] = "強",
["膙"] = "強",
["鏹"] = "強",
["勥"] = "強",
["強"] = "強",
["薔"] = "牆",
["廧"] = "牆",
["蘠"] = "牆",
["嬙"] = "牆",
["檣"] = "牆",
["驕"] = "喬",
["嬌"] = "喬",
["憍"] = "喬",
["穚"] = "喬",
["鷮"] = "喬",
["蕎"] = "喬",
["喬"] = "喬",
["簥"] = "喬",
["撟"] = "喬",
["矯"] = "喬",
["鱎"] = "喬",
["敽"] = "喬",
["蟜"] = "喬",
["譑"] = "喬",
["蹻"] = "喬",
["繑"] = "喬",
["鞽"] = "喬",
["趫"] = "喬",
["嘺"] = "喬",
["橋"] = "喬",
["僑"] = "喬",
["鐈"] = "喬",
["毊"] = "喬",
["轎"] = "喬",
["嶠"] = "喬",
["獢"] = "喬",
["屩"] = "喬",
["虘"] = "且",
["蔖"] = "且",
["摣"] = "且",
["袓"] = "且",
["怚"] = "且",
["罝"] = "且",
["謯"] = "且",
["姐"] = "且",
["抯"] = "且",
["飷"] = "且",
["且"] = "且",
["趄"] = "且",
["笡"] = "且",
["查"] = "且",
["柤"] = "且",
["樝"] = "且",
["皻"] = "且",
["渣"] = "且",
["楂"] = "且",
["苴"] = "且",
["駔"] = "且",
["租"] = "且",
["蒩"] = "且",
["祖"] = "且",
["組"] = "且",
["珇"] = "且",
["靻"] = "且",
["粗"] = "且",
["徂"] = "且",
["殂"] = "且",
["麆"] = "且",
["伹"] = "且",
["蛆"] = "且",
["沮"] = "且",
["咀"] = "且",
["疽"] = "且",
["雎"] = "且",
["狙"] = "且",
["岨"] = "且",
["砠"] = "且",
["坥"] = "且",
["刞"] = "且",
["覰"] = "且",
["覷"] = "且",
["跙"] = "且",
["筯"] = "且",
["菹"] = "且",
["葅"] = "且",
["阻"] = "且",
["俎"] = "且",
["詛"] = "且",
["鉏"] = "且",
["豠"] = "且",
["鋤"] = "且",
["耡"] = "且",
["齟"] = "且",
["助"] = "且",
["霎"] = "妾",
["翣"] = "妾",
["帹"] = "妾",
["接"] = "妾",
["椄"] = "妾",
["菨"] = "妾",
["鯜"] = "妾",
["妾"] = "妾",
["淁"] = "妾",
["踥"] = "妾",
["鋟"] = "侵",
["綅"] = "侵",
["祲"] = "侵",
["埐"] = "侵",
["梫"] = "侵",
["濅"] = "侵",
["浸"] = "侵",
["侵"] = "侵",
["駸"] = "侵",
["寢"] = "侵",
["寑"] = "侵",
["薓"] = "侵",
["轃"] = "秦",
["秦"] = "秦",
["螓"] = "秦",
["臻"] = "秦",
["蓁"] = "秦",
["搸"] = "秦",
["溱"] = "秦",
["榛"] = "秦",
["顈"] = "頃",
["傾"] = "頃",
["頃"] = "頃",
["穎"] = "頃",
["潁"] = "頃",
["熲"] = "頃",
["殸"] = "殸",
["聲"] = "殸",
["謦"] = "殸",
["磬"] = "殸",
["罄"] = "殸",
["馨"] = "殸",
["慶"] = "慶",
["羻"] = "慶",
["丘"] = "丘",
["邱"] = "丘",
["蚯"] = "丘",
["丠"] = "丘",
["鍫"] = "秋",
["篍"] = "秋",
["愀"] = "秋",
["湫"] = "秋",
["揪"] = "秋",
["揫"] = "秋",
["啾"] = "秋",
["鬏"] = "秋",
["秋"] = "秋",
["鰍"] = "秋",
["楸"] = "秋",
["秌"] = "秋",
["鞦"] = "秋",
["鶖"] = "秋",
["萩"] = "秋",
["甃"] = "秋",
["醔"] = "秋",
["愁"] = "秋",
["僽"] = "秋",
["鮂"] = "囚",
["囚"] = "囚",
["泅"] = "囚",
["苬"] = "囚",
["救"] = "求",
["求"] = "求",
["球"] = "求",
["逑"] = "求",
["賕"] = "求",
["蛷"] = "求",
["莍"] = "求",
["殏"] = "求",
["梂"] = "求",
["俅"] = "求",
["脙"] = "求",
["絿"] = "求",
["銶"] = "求",
["毬"] = "求",
["捄"] = "求",
["觩"] = "求",
["摳"] = "區",
["彄"] = "區",
["剾"] = "區",
["謳"] = "區",
["歐"] = "區",
["區"] = "區",
["鷗"] = "區",
["甌"] = "區",
["漚"] = "區",
["瞘"] = "區",
["嘔"] = "區",
["櫙"] = "區",
["膒"] = "區",
["蓲"] = "區",
["醧"] = "區",
["鏂"] = "區",
["毆"] = "區",
["塸"] = "區",
["貙"] = "區",
["樞"] = "區",
["驅"] = "區",
["軀"] = "區",
["嶇"] = "區",
["鰸"] = "區",
["敺"] = "區",
["傴"] = "區",
["嫗"] = "區",
["饇"] = "區",
["陬"] = "取",
["棷"] = "取",
["掫"] = "取",
["緅"] = "取",
["趣"] = "取",
["取"] = "取",
["鯫"] = "取",
["棸"] = "取",
["鄹"] = "取",
["郰"] = "取",
["齱"] = "取",
["菆"] = "取",
["箃"] = "取",
["黀"] = "取",
["驟"] = "取",
["最"] = "取",
["襊"] = "取",
["蕞"] = "取",
["嘬"] = "取",
["諏"] = "取",
["娵"] = "取",
["娶"] = "取",
["聚"] = "取",
["埾"] = "取",
["藂"] = "取",
["叢"] = "取",
["繓"] = "取",
["撮"] = "取",
["佉"] = "去",
["呿"] = "去",
["抾"] = "去",
["弆"] = "去",
["祛"] = "去",
["袪"] = "去",
["阹"] = "去",
["胠"] = "去",
["魼"] = "去",
["去"] = "去",
["麮"] = "去",
["鼁"] = "去",
["砝"] = "去",
["劫"] = "去",
["鉣"] = "去",
["蜐"] = "去",
["怯"] = "去",
["拴"] = "全",
["栓"] = "全",
["跧"] = "全",
["痊"] = "全",
["詮"] = "全",
["荃"] = "全",
["銓"] = "全",
["筌"] = "全",
["輇"] = "全",
["硂"] = "全",
["佺"] = "全",
["駩"] = "全",
["絟"] = "全",
["恮"] = "全",
["峑"] = "全",
["全"] = "全",
["仝"] = "全",
["牷"] = "全",
["線"] = "泉",
["泉"] = "泉",
["葲"] = "泉",
["灥"] = "泉",
["猚"] = "犬",
["畎"] = "犬",
["汱"] = "犬",
["甽"] = "犬",
["犬"] = "犬",
["腳"] = "郤",
["脚"] = "郤",
["卻"] = "郤",
["却"] = "郤",
["谻"] = "郤",
["郤"] = "郤",
["綌"] = "郤",
["菌"] = "囷",
["麇"] = "囷",
["麕"] = "囷",
["囷"] = "囷",
["箘"] = "囷",
["攈"] = "囷",
["嘫"] = "肰",
["然"] = "肰",
["肰"] = "肰",
["繎"] = "肰",
["燃"] = "肰",
["蹨"] = "肰",
["橪"] = "肰",
["撚"] = "肰",
["那"] = "冉",
["挪"] = "冉",
["娜"] = "冉",
["柟"] = "冉",
["抩"] = "冉",
["聃"] = "冉",
["舑"] = "冉",
["坍"] = "冉",
["髯"] = "冉",
["蚺"] = "冉",
["呥"] = "冉",
["冉"] = "冉",
["袡"] = "冉",
["苒"] = "冉",
["姌"] = "冉",
["千"] = "人",
["仟"] = "人",
["芊"] = "人",
["阡"] = "人",
["汘"] = "人",
["迁"] = "人",
["杄"] = "人",
["人"] = "人",
["仁"] = "人",
["朲"] = "人",
["佞"] = "人",
["紝"] = "壬",
["鵀"] = "壬",
["拰"] = "壬",
["賃"] = "壬",
["任"] = "壬",
["壬"] = "壬",
["恁"] = "壬",
["銋"] = "壬",
["荏"] = "壬",
["餁"] = "壬",
["栠"] = "壬",
["袵"] = "壬",
["飪"] = "壬",
["妊"] = "壬",
["衽"] = "壬",
["涊"] = "刃",
["紉"] = "刃",
["忍"] = "刃",
["荵"] = "刃",
["刃"] = "刃",
["認"] = "刃",
["仞"] = "刃",
["軔"] = "刃",
["韌"] = "刃",
["肕"] = "刃",
["牣"] = "刃",
["杒"] = "刃",
["訒"] = "刃",
["涅"] = "日",
["捏"] = "日",
["篞"] = "日",
["衵"] = "日",
["日"] = "日",
["馹"] = "日",
["娀"] = "戎",
["戎"] = "戎",
["茙"] = "戎",
["駥"] = "戎",
["狨"] = "戎",
["絨"] = "戎",
["茸"] = "茸",
["髶"] = "茸",
["搑"] = "茸",
["榵"] = "茸",
["穁"] = "茸",
["宂"] = "冗",
["冗"] = "冗",
["朒"] = "肉",
["肉"] = "肉",
["啂"] = "乳",
["乳"] = "乳",
["耨"] = "辱",
["槈"] = "辱",
["鎒"] = "辱",
["搙"] = "辱",
["褥"] = "辱",
["傉"] = "辱",
["辱"] = "辱",
["縟"] = "辱",
["蓐"] = "辱",
["鄏"] = "辱",
["溽"] = "辱",
["媷"] = "辱",
["嗕"] = "辱",
["陾"] = "耎",
["腝"] = "耎",
["堧"] = "耎",
["稬"] = "耎",
["愞"] = "耎",
["煗"] = "耎",
["餪"] = "耎",
["渜"] = "耎",
["偄"] = "耎",
["瑌"] = "耎",
["軟"] = "耎",
["輭"] = "耎",
["蝡"] = "耎",
["碝"] = "耎",
["耎"] = "耎",
["緛"] = "耎",
["媆"] = "耎",
["甤"] = "甤",
["蕤"] = "甤",
["睿"] = "睿",
["叡"] = "睿",
["璿"] = "睿",
["壡"] = "睿",
["濬"] = "睿",
["撋"] = "閏",
["瞤"] = "閏",
["潤"] = "閏",
["閏"] = "閏",
["若"] = "若",
["婼"] = "若",
["惹"] = "若",
["諾"] = "若",
["蠚"] = "若",
["逽"] = "若",
["蹃"] = "若",
["箬"] = "若",
["楉"] = "若",
["鄀"] = "若",
["嫋"] = "弱",
["弱"] = "弱",
["溺"] = "弱",
["蒻"] = "弱",
["搦"] = "弱",
["愵"] = "弱",
["賽"] = "塞",
["塞"] = "塞",
["簺"] = "塞",
["僿"] = "塞",
["寨"] = "塞",
["三"] = "三",
["弎"] = "三",
["散"] = "散",
["糤"] = "散",
["繖"] = "散",
["饊"] = "散",
["鏾"] = "散",
["潸"] = "散",
["霰"] = "散",
["撒"] = "散",
["桒"] = "桑",
["桑"] = "桑",
["磉"] = "桑",
["顙"] = "桑",
["嗓"] = "桑",
["搡"] = "桑",
["掃"] = "掃",
["埽"] = "掃",
["色"] = "色",
["赩"] = "色",
["濇"] = "嗇",
["嗇"] = "嗇",
["穡"] = "嗇",
["薔"] = "嗇",
["轖"] = "嗇",
["繬"] = "嗇",
["嬙"] = "嗇",
["懎"] = "嗇",
["澀"] = "歰",
["歰"] = "歰",
["澁"] = "歰",
["閷"] = "杀",
["鎩"] = "杀",
["殺"] = "杀",
["蔱"] = "杀",
["摋"] = "杀",
["煞"] = "杀",
["榝"] = "杀",
["刹"] = "杀",
["硰"] = "沙",
["娑"] = "沙",
["挱"] = "沙",
["挲"] = "沙",
["桫"] = "沙",
["渻"] = "沙",
["沙"] = "沙",
["紗"] = "沙",
["砂"] = "沙",
["鯊"] = "沙",
["裟"] = "沙",
["桬"] = "沙",
["髿"] = "沙",
["魦"] = "沙",
["莎"] = "沙",
["莏"] = "沙",
["杉"] = "彡",
["彡"] = "彡",
["衫"] = "彡",
["髟"] = "彡",
["釤"] = "彡",
["䑣"] = "彡",
["彤"] = "彡",
["浵"] = "彡",
["肜"] = "彡",
["山"] = "山",
["疝"] = "山",
["邖"] = "山",
["汕"] = "山",
["訕"] = "山",
["仙"] = "山",
["秈"] = "山",
["苮"] = "山",
["屾"] = "山",
["珊"] = "删",
["跚"] = "删",
["姗"] = "删",
["删"] = "删",
["栅"] = "删",
["羴"] = "羴",
["羼"] = "羴",
["扇"] = "扇",
["煽"] = "扇",
["傓"] = "扇",
["橏"] = "善",
["嫸"] = "善",
["善"] = "善",
["譱"] = "善",
["蟮"] = "善",
["鱔"] = "善",
["僐"] = "善",
["鄯"] = "善",
["墡"] = "善",
["磰"] = "善",
["繕"] = "善",
["膳"] = "善",
["饍"] = "善",
["商"] = "商",
["蔏"] = "商",
["螪"] = "商",
["丄"] = "上",
["上"] = "上",
["當"] = "尚",
["襠"] = "尚",
["鐺"] = "尚",
["簹"] = "尚",
["璫"] = "尚",
["檔"] = "尚",
["儅"] = "尚",
["蟷"] = "尚",
["擋"] = "尚",
["黨"] = "尚",
["讜"] = "尚",
["欓"] = "尚",
["譡"] = "尚",
["闣"] = "尚",
["瓽"] = "尚",
["鏜"] = "尚",
["闛"] = "尚",
["鼞"] = "尚",
["曭"] = "尚",
["儻"] = "尚",
["戃"] = "尚",
["矘"] = "尚",
["爣"] = "尚",
["攩"] = "尚",
["淌"] = "尚",
["倘"] = "尚",
["躺"] = "尚",
["趟"] = "尚",
["堂"] = "尚",
["坣"] = "尚",
["棠"] = "尚",
["糛"] = "尚",
["螳"] = "尚",
["隚"] = "尚",
["橖"] = "尚",
["掌"] = "尚",
["廠"] = "尚",
["敞"] = "尚",
["僘"] = "尚",
["氅"] = "尚",
["常"] = "尚",
["嘗"] = "尚",
["裳"] = "尚",
["徜"] = "尚",
["尚"] = "尚",
["甞"] = "尚",
["鋿"] = "尚",
["嫦"] = "尚",
["償"] = "尚",
["賞"] = "尚",
["瞠"] = "尚",
["撐"] = "尚",
["樘"] = "尚",
["橕"] = "尚",
["牚"] = "尚",
["埫"] = "尚",
["豹"] = "勺",
["趵"] = "勺",
["尥"] = "勺",
["箹"] = "勺",
["杓"] = "勺",
["約"] = "勺",
["扚"] = "勺",
["釣"] = "勺",
["瘹"] = "勺",
["芍"] = "勺",
["酌"] = "勺",
["灼"] = "勺",
["勺"] = "勺",
["彴"] = "勺",
["犳"] = "勺",
["妁"] = "勺",
["汋"] = "勺",
["仢"] = "勺",
["葯"] = "勺",
["礿"] = "勺",
["肑"] = "勺",
["瓝"] = "勺",
["的"] = "勺",
["靮"] = "勺",
["馰"] = "勺",
["玓"] = "勺",
["魡"] = "勺",
["絬"] = "舌",
["舌"] = "舌",
["謝"] = "射",
["榭"] = "射",
["射"] = "射",
["麝"] = "射",
["設"] = "設",
["蔎"] = "設",
["電"] = "申",
["胂"] = "申",
["申"] = "申",
["伸"] = "申",
["呻"] = "申",
["紳"] = "申",
["眒"] = "申",
["柛"] = "申",
["訷"] = "申",
["抻"] = "申",
["神"] = "申",
["朄"] = "申",
["身"] = "身",
["銵"] = "身",
["探"] = "罙",
["棎"] = "罙",
["賝"] = "罙",
["琛"] = "罙",
["穼"] = "罙",
["深"] = "罙",
["宷"] = "宷",
["審"] = "宷",
["瀋"] = "宷",
["嬸"] = "宷",
["昚"] = "昚",
["傄"] = "昚",
["湛"] = "甚",
["媅"] = "甚",
["黮"] = "甚",
["糂"] = "甚",
["堪"] = "甚",
["戡"] = "甚",
["嵁"] = "甚",
["歁"] = "甚",
["勘"] = "甚",
["磡"] = "甚",
["墈"] = "甚",
["偡"] = "甚",
["碪"] = "甚",
["椹"] = "甚",
["揕"] = "甚",
["踸"] = "甚",
["鍖"] = "甚",
["斟"] = "甚",
["諶"] = "甚",
["愖"] = "甚",
["瘎"] = "甚",
["煁"] = "甚",
["甚"] = "甚",
["葚"] = "甚",
["升"] = "升",
["昇"] = "升",
["陞"] = "升",
["抍"] = "升",
["猜"] = "生",
["輤"] = "生",
["綪"] = "生",
["倩"] = "生",
["棈"] = "生",
["蒨"] = "生",
["篟"] = "生",
["生"] = "生",
["牲"] = "生",
["笙"] = "生",
["甥"] = "生",
["鉎"] = "生",
["珄"] = "生",
["鼪"] = "生",
["猩"] = "生",
["狌"] = "生",
["眚"] = "生",
["貹"] = "生",
["崝"] = "生",
["精"] = "生",
["菁"] = "生",
["鶄"] = "生",
["蜻"] = "生",
["鼱"] = "生",
["婧"] = "生",
["睛"] = "生",
["箐"] = "生",
["聙"] = "生",
["旌"] = "生",
["清"] = "生",
["圊"] = "生",
["請"] = "生",
["凊"] = "生",
["䝼"] = "生",
["靚"] = "生",
["情"] = "生",
["晴"] = "生",
["夝"] = "生",
["靜"] = "生",
["靖"] = "生",
["睲"] = "生",
["惺"] = "生",
["性"] = "生",
["姓"] = "生",
["靗"] = "生",
["鯖"] = "生",
["青"] = "生",
["靘"] = "生",
["掅"] = "生",
["胜"] = "生",
["曐"] = "生",
["星"] = "生",
["鮏"] = "生",
["腥"] = "生",
["鯹"] = "生",
["醒"] = "生",
["篂"] = "生",
["省"] = "省",
["渻"] = "省",
["箵"] = "省",
["檉"] = "聖",
["蟶"] = "聖",
["聖"] = "聖",
["聽"] = "聖",
["廳"] = "聖",
["杘"] = "尸",
["尸"] = "尸",
["鳲"] = "尸",
["屍"] = "尸",
["屎"] = "尸",
["眣"] = "失",
["蛈"] = "失",
["迭"] = "失",
["跌"] = "失",
["瓞"] = "失",
["昳"] = "失",
["胅"] = "失",
["軼"] = "失",
["苵"] = "失",
["镻"] = "失",
["詄"] = "失",
["泆"] = "失",
["趃"] = "失",
["柣"] = "失",
["抶"] = "失",
["秩"] = "失",
["帙"] = "失",
["紩"] = "失",
["袠"] = "失",
["翐"] = "失",
["妷"] = "失",
["失"] = "失",
["佚"] = "失",
["劮"] = "失",
["篩"] = "師",
["師"] = "師",
["鰤"] = "師",
["蒒"] = "師",
["獅"] = "師",
["螄"] = "師",
["儑"] = "濕",
["濕"] = "濕",
["漯"] = "濕",
["礘"] = "濕",
["隰"] = "濕",
["溼"] = "濕",
["針"] = "十",
["汁"] = "十",
["卙"] = "十",
["十"] = "十",
["什"] = "十",
["柘"] = "石",
["妬"] = "石",
["蠹"] = "石",
["沰"] = "石",
["拓"] = "石",
["橐"] = "石",
["袥"] = "石",
["驝"] = "石",
["斫"] = "石",
["磭"] = "石",
["磔"] = "石",
["跖"] = "石",
["石"] = "石",
["碩"] = "石",
["祏"] = "石",
["鉐"] = "石",
["鼫"] = "石",
["飤"] = "食",
["食"] = "食",
["蝕"] = "食",
["飾"] = "食",
["飭"] = "食",
["吏"] = "史",
["倳"] = "史",
["剚"] = "史",
["事"] = "史",
["史"] = "史",
["使"] = "史",
["駛"] = "史",
["彘"] = "矢",
["璏"] = "矢",
["薙"] = "矢",
["知"] = "矢",
["蜘"] = "矢",
["智"] = "矢",
["鼅"] = "矢",
["潪"] = "矢",
["踟"] = "矢",
["雉"] = "矢",
["矢"] = "矢",
["痴"] = "矢",
["茌"] = "士",
["士"] = "士",
["仕"] = "士",
["氏"] = "氏",
["衹"] = "氏",
["紙"] = "氏",
["帋"] = "氏",
["扺"] = "氏",
["坁"] = "氏",
["汦"] = "氏",
["忯"] = "氏",
["眂"] = "氏",
["舐"] = "氏",
["芪"] = "氏",
["祇"] = "氏",
["疧"] = "氏",
["軝"] = "氏",
["蚔"] = "氏",
["貰"] = "世",
["跇"] = "世",
["迣"] = "世",
["世"] = "世",
["泄"] = "世",
["枻"] = "世",
["詍"] = "世",
["靾"] = "世",
["袣"] = "世",
["抴"] = "世",
["呭"] = "世",
["勩"] = "世",
["屜"] = "世",
["鰈"] = "世",
["渫"] = "世",
["煠"] = "世",
["喋"] = "世",
["鞢"] = "世",
["殜"] = "世",
["葉"] = "世",
["弽"] = "世",
["韘"] = "世",
["枼"] = "世",
["楪"] = "世",
["揲"] = "世",
["鍱"] = "世",
["偞"] = "世",
["蝶"] = "世",
["牒"] = "世",
["蹀"] = "世",
["諜"] = "世",
["堞"] = "世",
["牃"] = "世",
["褋"] = "世",
["惵"] = "世",
["碟"] = "世",
["屧"] = "世",
["屟"] = "世",
["紲"] = "世",
["齛"] = "世",
["疶"] = "世",
["緤"] = "世",
["媟"] = "世",
["示"] = "示",
["祁"] = "示",
["狋"] = "示",
["視"] = "示",
["眎"] = "示",
["沶"] = "示",
["鞮"] = "是",
["隄"] = "是",
["堤"] = "是",
["鍉"] = "是",
["蝭"] = "是",
["醍"] = "是",
["緹"] = "是",
["提"] = "是",
["瑅"] = "是",
["題"] = "是",
["媞"] = "是",
["褆"] = "是",
["禔"] = "是",
["騠"] = "是",
["趧"] = "是",
["鯷"] = "是",
["踶"] = "是",
["徥"] = "是",
["匙"] = "是",
["是"] = "是",
["諟"] = "是",
["翨"] = "是",
["睼"] = "是",
["寔"] = "是",
["湜"] = "是",
["遈"] = "是",
["噬"] = "筮",
["筮"] = "筮",
["簭"] = "筮",
["澨"] = "筮",
["遾"] = "筮",
["奭"] = "奭",
["襫"] = "奭",
["杽"] = "手",
["手"] = "手",
["守"] = "守",
["狩"] = "守",
["道"] = "首",
["導"] = "首",
["首"] = "首",
["受"] = "受",
["綬"] = "受",
["授"] = "受",
["禱"] = "壽",
["擣"] = "壽",
["檮"] = "壽",
["壔"] = "壽",
["懤"] = "壽",
["濤"] = "壽",
["燾"] = "壽",
["翿"] = "壽",
["幬"] = "壽",
["儔"] = "壽",
["譸"] = "壽",
["籌"] = "壽",
["疇"] = "壽",
["躊"] = "壽",
["燽"] = "壽",
["薵"] = "壽",
["魗"] = "壽",
["醻"] = "壽",
["壽"] = "壽",
["璹"] = "壽",
["鑄"] = "壽",
["獸"] = "嘼",
["嘼"] = "嘼",
["吺"] = "殳",
["祋"] = "殳",
["投"] = "殳",
["骰"] = "殳",
["酘"] = "殳",
["坄"] = "殳",
["殳"] = "殳",
["杸"] = "殳",
["椒"] = "尗",
["茮"] = "尗",
["踧"] = "尗",
["蔋"] = "尗",
["惄"] = "尗",
["戚"] = "尗",
["慼"] = "尗",
["鏚"] = "尗",
["慽"] = "尗",
["磩"] = "尗",
["寂"] = "尗",
["嘁"] = "尗",
["墄"] = "尗",
["摵"] = "尗",
["督"] = "尗",
["錖"] = "尗",
["裻"] = "尗",
["傶"] = "尗",
["蹙"] = "尗",
["槭"] = "尗",
["縬"] = "尗",
["顣"] = "尗",
["敊"] = "尗",
["琡"] = "尗",
["俶"] = "尗",
["埱"] = "尗",
["淑"] = "尗",
["婌"] = "尗",
["尗"] = "尗",
["叔"] = "尗",
["掓"] = "尗",
["菽"] = "尗",
["鮛"] = "尗",
["疋"] = "疋",
["蝑"] = "疋",
["壻"] = "疋",
["婿"] = "疋",
["胥"] = "疋",
["稰"] = "疋",
["楈"] = "疋",
["諝"] = "疋",
["湑"] = "疋",
["揟"] = "疋",
["醑"] = "疋",
["糈"] = "疋",
["楚"] = "疋",
["礎"] = "疋",
["憷"] = "疋",
["齼"] = "疋",
["濋"] = "疋",
["儊"] = "疋",
["綀"] = "疋",
["疎"] = "疋",
["疏"] = "疋",
["蔬"] = "疋",
["梳"] = "疋",
["熟"] = "孰",
["孰"] = "孰",
["塾"] = "孰",
["鼠"] = "鼠",
["癙"] = "鼠",
["襡"] = "蜀",
["噣"] = "蜀",
["斣"] = "蜀",
["歜"] = "蜀",
["斀"] = "蜀",
["孎"] = "蜀",
["濁"] = "蜀",
["鐲"] = "蜀",
["鸀"] = "蜀",
["擉"] = "蜀",
["獨"] = "蜀",
["髑"] = "蜀",
["韣"] = "蜀",
["斸"] = "蜀",
["钃"] = "蜀",
["欘"] = "蜀",
["躅"] = "蜀",
["蠋"] = "蜀",
["燭"] = "蜀",
["囑"] = "蜀",
["矚"] = "蜀",
["屬"] = "蜀",
["属"] = "蜀",
["蠾"] = "蜀",
["觸"] = "蜀",
["臅"] = "蜀",
["觕"] = "蜀",
["蜀"] = "蜀",
["訹"] = "朮",
["怵"] = "朮",
["朮"] = "朮",
["炢"] = "朮",
["術"] = "朮",
["述"] = "朮",
["秫"] = "朮",
["沭"] = "朮",
["蒁"] = "朮",
["涑"] = "束",
["鏉"] = "束",
["摗"] = "束",
["駷"] = "束",
["嗽"] = "束",
["瘶"] = "束",
["欶"] = "束",
["漱"] = "束",
["捒"] = "束",
["悚"] = "束",
["竦"] = "束",
["娕"] = "束",
["梀"] = "束",
["速"] = "束",
["餗"] = "束",
["殐"] = "束",
["遬"] = "束",
["蔌"] = "束",
["樕"] = "束",
["藗"] = "束",
["誎"] = "束",
["束"] = "束",
["褯"] = "庶",
["遮"] = "庶",
["蔗"] = "庶",
["鷓"] = "庶",
["樜"] = "庶",
["蟅"] = "庶",
["嗻"] = "庶",
["鍍"] = "庶",
["度"] = "庶",
["渡"] = "庶",
["庶"] = "庶",
["剫"] = "庶",
["踱"] = "庶",
["喥"] = "庶",
["謶"] = "庶",
["席"] = "庶",
["蓆"] = "庶",
["墌"] = "庶",
["摭"] = "庶",
["蹠"] = "庶",
["涮"] = "刷",
["刷"] = "刷",
["蓑"] = "衰",
["缞"] = "衰",
["衰"] = "衰",
["榱"] = "衰",
["雙"] = "雙",
["艭"] = "雙",
["愯"] = "雙",
["㩳"] = "雙",
["摤"] = "爽",
["磢"] = "爽",
["漺"] = "爽",
["傸"] = "爽",
["鷞"] = "爽",
["騻"] = "爽",
["爽"] = "爽",
["塽"] = "爽",
["樉"] = "爽",
["沝"] = "水",
["水"] = "水",
["私"] = "厶",
["厶"] = "厶",
["司"] = "司",
["伺"] = "司",
["覗"] = "司",
["笥"] = "司",
["詞"] = "司",
["祠"] = "司",
["柌"] = "司",
["嗣"] = "司",
["孠"] = "司",
["飼"] = "司",
["呞"] = "司",
["擨"] = "虒",
["搋"] = "虒",
["鷈"] = "虒",
["謕"] = "虒",
["嗁"] = "虒",
["蹏"] = "虒",
["鼶"] = "虒",
["遞"] = "虒",
["虒"] = "虒",
["榹"] = "虒",
["磃"] = "虒",
["傂"] = "虒",
["螔"] = "虒",
["禠"] = "虒",
["褫"] = "虒",
["篪"] = "虒",
["歋"] = "虒",
["嘶"] = "斯",
["撕"] = "斯",
["斯"] = "斯",
["廝"] = "斯",
["蟴"] = "斯",
["燍"] = "斯",
["鐁"] = "斯",
["凘"] = "斯",
["澌"] = "斯",
["絲"] = "絲",
["蕬"] = "絲",
["鷥"] = "絲",
["巳"] = "巳",
["祀"] = "巳",
["汜"] = "巳",
["戺"] = "巳",
["起"] = "巳",
["熙"] = "巳",
["媐"] = "巳",
["圯"] = "巳",
["异"] = "巳",
["巸"] = "巳",
["訵"] = "四",
["四"] = "四",
["泗"] = "四",
["駟"] = "四",
["柶"] = "四",
["牭"] = "四",
["呬"] = "四",
["等"] = "寺",
["待"] = "寺",
["寺"] = "寺",
["持"] = "寺",
["峙"] = "寺",
["痔"] = "寺",
["跱"] = "寺",
["歭"] = "寺",
["洔"] = "寺",
["畤"] = "寺",
["秲"] = "寺",
["庤"] = "寺",
["時"] = "寺",
["鰣"] = "寺",
["塒"] = "寺",
["蒔"] = "寺",
["榯"] = "寺",
["恃"] = "寺",
["侍"] = "寺",
["詩"] = "寺",
["邿"] = "寺",
["特"] = "寺",
["浨"] = "宋",
["俕"] = "宋",
["宋"] = "宋",
["㮴"] = "叟",
["艘"] = "叟",
["嫂"] = "叟",
["瞍"] = "叟",
["謏"] = "叟",
["叜"] = "叟",
["叟"] = "叟",
["傁"] = "叟",
["廋"] = "叟",
["醙"] = "叟",
["遚"] = "叟",
["餿"] = "叟",
["搜"] = "叟",
["颼"] = "叟",
["溲"] = "叟",
["鎪"] = "叟",
["獀"] = "叟",
["鄋"] = "叟",
["螋"] = "叟",
["騪"] = "叟",
["瘦"] = "叟",
["洬"] = "夙",
["夙"] = "夙",
["素"] = "素",
["傃"] = "素",
["嗉"] = "素",
["膆"] = "素",
["塐"] = "素",
["宿"] = "宿",
["蓿"] = "宿",
["縮"] = "宿",
["樎"] = "宿",
["蹜"] = "宿",
["摍"] = "宿",
["粟"] = "粟",
["憟"] = "粟",
["簫"] = "肅",
["蕭"] = "肅",
["瀟"] = "肅",
["潚"] = "肅",
["彇"] = "肅",
["蟰"] = "肅",
["橚"] = "肅",
["嘯"] = "肅",
["歗"] = "肅",
["熽"] = "肅",
["繡"] = "肅",
["鏽"] = "肅",
["肅"] = "肅",
["鷫"] = "肅",
["驌"] = "肅",
["鱐"] = "肅",
["璛"] = "肅",
["蒜"] = "祘",
["祘"] = "祘",
["纂"] = "算",
["繤"] = "算",
["算"] = "算",
["匴"] = "算",
["篹"] = "算",
["篡"] = "算",
["攥"] = "算",
["隊"] = "遂",
["邃"] = "遂",
["譢"] = "遂",
["遂"] = "遂",
["隧"] = "遂",
["燧"] = "遂",
["襚"] = "遂",
["旞"] = "遂",
["璲"] = "遂",
["檖"] = "遂",
["澻"] = "遂",
["鐆"] = "遂",
["鐩"] = "遂",
["穟"] = "遂",
["墜"] = "遂",
["噦"] = "歲",
["翽"] = "歲",
["鐬"] = "歲",
["濊"] = "歲",
["歲"] = "歲",
["劌"] = "歲",
["顪"] = "歲",
["穢"] = "歲",
["薉"] = "歲",
["獩"] = "歲",
["饖"] = "歲",
["奯"] = "歲",
["孫"] = "孫",
["猻"] = "孫",
["蓀"] = "孫",
["搎"] = "孫",
["遜"] = "孫",
["愻"] = "孫",
["飧"] = "飧",
["蕵"] = "飧",
["隼"] = "隼",
["榫"] = "隼",
["准"] = "隼",
["準"] = "隼",
["索"] = "索",
["溹"] = "索",
["惢"] = "惢",
["蕊"] = "惢",
["蘂"] = "惢",
["繠"] = "惢",
["鎖"] = "瑣",
["瑣"] = "瑣",
["溑"] = "瑣",
["佗"] = "它",
["拕"] = "它",
["它"] = "它",
["蛇"] = "它",
["袉"] = "它",
["鴕"] = "它",
["駝"] = "它",
["紽"] = "它",
["鮀"] = "它",
["陀"] = "它",
["沱"] = "它",
["跎"] = "它",
["詑"] = "它",
["酡"] = "它",
["迱"] = "它",
["舵"] = "它",
["柁"] = "它",
["鉈"] = "它",
["傝"] = "蹋",
["搨"] = "蹋",
["蹹"] = "蹋",
["榻"] = "蹋",
["遢"] = "蹋",
["鰨"] = "蹋",
["毾"] = "蹋",
["塌"] = "蹋",
["溻"] = "蹋",
["蹋"] = "蹋",
["躢"] = "蹋",
["闒"] = "蹋",
["誩"] = "譶",
["譶"] = "譶",
["霅"] = "譶",
["胎"] = "台",
["台"] = "台",
["邰"] = "台",
["鮐"] = "台",
["孡"] = "台",
["苔"] = "台",
["抬"] = "台",
["駘"] = "台",
["炱"] = "台",
["菭"] = "台",
["跆"] = "台",
["殆"] = "台",
["怠"] = "台",
["迨"] = "台",
["紿"] = "台",
["詒"] = "台",
["軩"] = "台",
["咍"] = "台",
["佁"] = "台",
["枲"] = "台",
["辝"] = "台",
["鈶"] = "台",
["笞"] = "台",
["齝"] = "台",
["眙"] = "台",
["治"] = "台",
["始"] = "台",
["怡"] = "台",
["貽"] = "台",
["飴"] = "台",
["瓵"] = "台",
["珆"] = "台",
["懛"] = "臺",
["儓"] = "臺",
["臺"] = "臺",
["擡"] = "臺",
["薹"] = "臺",
["嬯"] = "臺",
["檯"] = "臺",
["籉"] = "臺",
["鷣"] = "覃",
["撢"] = "覃",
["憛"] = "覃",
["潭"] = "覃",
["譚"] = "覃",
["覃"] = "覃",
["鐔"] = "覃",
["藫"] = "覃",
["橝"] = "覃",
["蟫"] = "覃",
["燂"] = "覃",
["醰"] = "覃",
["禫"] = "覃",
["嘾"] = "覃",
["贉"] = "覃",
["瞫"] = "覃",
["蕈"] = "覃",
["鱏"] = "覃",
["簟"] = "覃",
["驔"] = "覃",
["磹"] = "覃",
["曇"] = "曇",
["壜"] = "曇",
["炭"] = "炭",
["湠"] = "炭",
["弢"] = "弢",
["詜"] = "弢",
["掏"] = "匋",
["蜪"] = "匋",
["陶"] = "匋",
["萄"] = "匋",
["啕"] = "匋",
["淘"] = "匋",
["騊"] = "匋",
["匋"] = "匋",
["祹"] = "匋",
["綯"] = "匋",
["替"] = "替",
["暜"] = "替",
["僣"] = "替",
["添"] = "天",
["忝"] = "天",
["悿"] = "天",
["舔"] = "天",
["菾"] = "天",
["天"] = "天",
["吞"] = "天",
["祆"] = "天",
["蚕"] = "天",
["田"] = "田",
["佃"] = "田",
["畋"] = "田",
["畇"] = "田",
["鈿"] = "田",
["沺"] = "田",
["甸"] = "田",
["銛"] = "甜",
["栝"] = "甜",
["餂"] = "甜",
["恬"] = "甜",
["湉"] = "甜",
["甜"] = "甜",
["狧"] = "甜",
["蜓"] = "廷",
["涏"] = "廷",
["綎"] = "廷",
["珽"] = "廷",
["脡"] = "廷",
["侹"] = "廷",
["頲"] = "廷",
["廷"] = "廷",
["庭"] = "廷",
["鼮"] = "廷",
["莛"] = "廷",
["筳"] = "廷",
["霆"] = "廷",
["娗"] = "廷",
["挺"] = "廷",
["艇"] = "廷",
["鋌"] = "廷",
["梃"] = "廷",
["誔"] = "廷",
["鮦"] = "同",
["侗"] = "同",
["恫"] = "同",
["痌"] = "同",
["同"] = "同",
["仝"] = "同",
["銅"] = "同",
["桐"] = "同",
["峒"] = "同",
["硐"] = "同",
["筒"] = "同",
["洞"] = "同",
["烔"] = "同",
["挏"] = "同",
["酮"] = "同",
["鲖"] = "同",
["眮"] = "同",
["衕"] = "同",
["哃"] = "同",
["絧"] = "同",
["姛"] = "同",
["詷"] = "同",
["胴"] = "同",
["駧"] = "同",
["迵"] = "同",
["戙"] = "同",
["禿"] = "禿",
["鵚"] = "禿",
["突"] = "突",
["宊"] = "突",
["揬"] = "突",
["鼵"] = "突",
["葖"] = "突",
["鶟"] = "突",
["堗"] = "突",
["啚"] = "圖",
["圖"] = "圖",
["社"] = "土",
["肚"] = "土",
["土"] = "土",
["吐"] = "土",
["芏"] = "土",
["杜"] = "土",
["靯"] = "土",
["荰"] = "土",
["菟"] = "兔",
["兔"] = "兔",
["鵵"] = "兔",
["梚"] = "兔",
["腿"] = "退",
["退"] = "退",
["褪"] = "退",
["豚"] = "豚",
["遯"] = "豚",
["妥"] = "妥",
["鵎"] = "妥",
["挼"] = "妥",
["骽"] = "妥",
["脮"] = "妥",
["餒"] = "妥",
["浽"] = "妥",
["娞"] = "妥",
["鮾"] = "妥",
["綏"] = "妥",
["荽"] = "妥",
["鋖"] = "妥",
["桵"] = "妥",
["瓦"] = "瓦",
["邷"] = "瓦",
["骫"] = "丸",
["丸"] = "丸",
["紈"] = "丸",
["芄"] = "丸",
["汍"] = "丸",
["肒"] = "丸",
["糲"] = "萬",
["犡"] = "萬",
["噧"] = "萬",
["邁"] = "萬",
["勱"] = "萬",
["蠆"] = "萬",
["厲"] = "萬",
["勵"] = "萬",
["礪"] = "萬",
["蠣"] = "萬",
["蠇"] = "萬",
["癘"] = "萬",
["濿"] = "萬",
["櫔"] = "萬",
["巁"] = "萬",
["贎"] = "萬",
["萬"] = "萬",
["忙"] = "亡",
["芒"] = "亡",
["茫"] = "亡",
["恾"] = "亡",
["吂"] = "亡",
["汒"] = "亡",
["朚"] = "亡",
["邙"] = "亡",
["杗"] = "亡",
["荒"] = "亡",
["肓"] = "亡",
["衁"] = "亡",
["巟"] = "亡",
["詤"] = "亡",
["慌"] = "亡",
["謊"] = "亡",
["喪"] = "亡",
["亡"] = "亡",
["望"] = "亡",
["莣"] = "亡",
["朢"] = "亡",
["鋩"] = "亡",
["硭"] = "亡",
["忘"] = "亡",
["网"] = "亡",
["罔"] = "亡",
["蛧"] = "亡",
["網"] = "亡",
["輞"] = "亡",
["棢"] = "亡",
["惘"] = "亡",
["菵"] = "亡",
["誷"] = "亡",
["魍"] = "亡",
["妄"] = "亡",
["盲"] = "亡",
["蝱"] = "亡",
["虻"] = "亡",
["氓"] = "亡",
["甿"] = "亡",
["皇"] = "王",
["惶"] = "王",
["遑"] = "王",
["堭"] = "王",
["煌"] = "王",
["餭"] = "王",
["騜"] = "王",
["艎"] = "王",
["隍"] = "王",
["湟"] = "王",
["徨"] = "王",
["篁"] = "王",
["蝗"] = "王",
["凰"] = "王",
["偟"] = "王",
["媓"] = "王",
["韹"] = "王",
["葟"] = "王",
["皝"] = "王",
["汪"] = "王",
["尪"] = "王",
["迋"] = "王",
["逛"] = "王",
["誑"] = "王",
["匡"] = "王",
["筐"] = "王",
["框"] = "王",
["眶"] = "王",
["誆"] = "王",
["邼"] = "王",
["恇"] = "王",
["劻"] = "王",
["洭"] = "王",
["軭"] = "王",
["狂"] = "王",
["軖"] = "王",
["鵟"] = "王",
["俇"] = "王",
["王"] = "王",
["蚟"] = "王",
["彺"] = "王",
["旺"] = "王",
["諻"] = "王",
["喤"] = "王",
["瑝"] = "王",
["鍠"] = "王",
["揘"] = "王",
["暀"] = "往",
["誑"] = "往",
["枉"] = "往",
["往"] = "往",
["鮠"] = "危",
["峞"] = "危",
["頠"] = "危",
["詭"] = "危",
["垝"] = "危",
["陒"] = "危",
["觤"] = "危",
["恑"] = "危",
["蛫"] = "危",
["祪"] = "危",
["洈"] = "危",
["姽"] = "危",
["桅"] = "危",
["佹"] = "危",
["跪"] = "危",
["危"] = "危",
["峗"] = "危",
["硊"] = "危",
["卼"] = "危",
["崴"] = "威",
["威"] = "威",
["葳"] = "威",
["隇"] = "威",
["蝛"] = "威",
["鰄"] = "威",
["楲"] = "威",
["矀"] = "微",
["覹"] = "微",
["黴"] = "微",
["薇"] = "微",
["媺"] = "微",
["微"] = "微",
["溦"] = "微",
["癓"] = "微",
["徽"] = "微",
["幑"] = "微",
["禕"] = "韋",
["郼"] = "韋",
["褘"] = "韋",
["諱"] = "韋",
["媁"] = "韋",
["幃"] = "韋",
["違"] = "韋",
["圍"] = "韋",
["韋"] = "韋",
["闈"] = "韋",
["湋"] = "韋",
["鍏"] = "韋",
["潿"] = "韋",
["韙"] = "韋",
["煒"] = "韋",
["暐"] = "韋",
["偉"] = "韋",
["瑋"] = "韋",
["葦"] = "韋",
["椲"] = "韋",
["韡"] = "韋",
["愇"] = "韋",
["緯"] = "韋",
["匯"] = "唯",
["擓"] = "唯",
["淮"] = "唯",
["睢"] = "唯",
["嶉"] = "唯",
["趡"] = "唯",
["踓"] = "唯",
["雖"] = "唯",
["濉"] = "唯",
["倠"] = "唯",
["婎"] = "唯",
["蓶"] = "唯",
["維"] = "唯",
["惟"] = "唯",
["唯"] = "唯",
["濰"] = "唯",
["琟"] = "唯",
["鷕"] = "唯",
["帷"] = "唯",
["鵻"] = "唯",
["譌"] = "爲",
["嬀"] = "爲",
["潙"] = "爲",
["蟡"] = "爲",
["僞"] = "爲",
["噅"] = "爲",
["撝"] = "爲",
["爲"] = "爲",
["鄬"] = "爲",
["蔿"] = "爲",
["儰"] = "爲",
["寪"] = "爲",
["蘤"] = "爲",
["娓"] = "尾",
["尾"] = "尾",
["浘"] = "尾",
["矮"] = "委",
["躷"] = "委",
["捼"] = "委",
["倭"] = "委",
["涹"] = "委",
["踒"] = "委",
["餧"] = "委",
["諉"] = "委",
["痿"] = "委",
["萎"] = "委",
["逶"] = "委",
["覣"] = "委",
["蜲"] = "委",
["委"] = "委",
["緌"] = "委",
["寐"] = "未",
["魅"] = "未",
["鬽"] = "未",
["昧"] = "未",
["妹"] = "未",
["眛"] = "未",
["沬"] = "未",
["未"] = "未",
["味"] = "未",
["鮇"] = "未",
["菋"] = "未",
["煨"] = "畏",
["偎"] = "畏",
["隈"] = "畏",
["椳"] = "畏",
["鰃"] = "畏",
["渨"] = "畏",
["葨"] = "畏",
["揋"] = "畏",
["猥"] = "畏",
["嵔"] = "畏",
["鍡"] = "畏",
["碨"] = "畏",
["腲"] = "畏",
["餵"] = "畏",
["畏"] = "畏",
["喟"] = "胃",
["胃"] = "胃",
["謂"] = "胃",
["媦"] = "胃",
["蝟"] = "胃",
["渭"] = "胃",
["煟"] = "胃",
["緭"] = "胃",
["尉"] = "尉",
["熨"] = "尉",
["慰"] = "尉",
["罻"] = "尉",
["犚"] = "尉",
["蔚"] = "尉",
["螱"] = "尉",
["褽"] = "尉",
["鳚"] = "尉",
["衞"] = "衛",
["讏"] = "衛",
["躛"] = "衛",
["媼"] = "𥁕",
["蝹"] = "𥁕",
["溫"] = "𥁕",
["𥁕"] = "𥁕",
["轀"] = "𥁕",
["薀"] = "𥁕",
["殟"] = "𥁕",
["豱"] = "𥁕",
["縕"] = "𥁕",
["韞"] = "𥁕",
["瘟"] = "𥁕",
["搵"] = "𥁕",
["氳"] = "𥁕",
["熅"] = "𥁕",
["馧"] = "𥁕",
["蒕"] = "𥁕",
["蘊"] = "𥁕",
["褞"] = "𥁕",
["醞"] = "𥁕",
["慍"] = "𥁕",
["膃"] = "𥁕",
["榲"] = "𥁕",
["嗢"] = "𥁕",
["旻"] = "文",
["旼"] = "文",
["汶"] = "文",
["忞"] = "文",
["盿"] = "文",
["枚"] = "文",
["玫"] = "文",
["玟"] = "文",
["文"] = "文",
["紋"] = "文",
["蚊"] = "文",
["雯"] = "文",
["彣"] = "文",
["馼"] = "文",
["鳼"] = "文",
["鼤"] = "文",
["抆"] = "文",
["紊"] = "文",
["莪"] = "我",
["哦"] = "我",
["娥"] = "我",
["峨"] = "我",
["峩"] = "我",
["鵝"] = "我",
["俄"] = "我",
["蛾"] = "我",
["睋"] = "我",
["涐"] = "我",
["誐"] = "我",
["硪"] = "我",
["我"] = "我",
["騀"] = "我",
["餓"] = "我",
["義"] = "我",
["儀"] = "我",
["轙"] = "我",
["檥"] = "我",
["蟻"] = "我",
["艤"] = "我",
["礒"] = "我",
["羛"] = "我",
["議"] = "我",
["犧"] = "我",
["曦"] = "我",
["羲"] = "我",
["巫"] = "巫",
["誣"] = "巫",
["莁"] = "巫",
["鵐"] = "巫",
["握"] = "屋",
["幄"] = "屋",
["偓"] = "屋",
["渥"] = "屋",
["齷"] = "屋",
["楃"] = "屋",
["喔"] = "屋",
["腛"] = "屋",
["媉"] = "屋",
["屋"] = "屋",
["剭"] = "屋",
["烏"] = "烏",
["嗚"] = "烏",
["鰞"] = "烏",
["歍"] = "烏",
["鎢"] = "烏",
["瑦"] = "烏",
["鄔"] = "烏",
["螐"] = "烏",
["隖"] = "烏",
["塢"] = "烏",
["溩"] = "烏",
["吳"] = "吳",
["蜈"] = "吳",
["鋘"] = "吳",
["麌"] = "吳",
["祦"] = "吳",
["誤"] = "吳",
["悞"] = "吳",
["娛"] = "吳",
["虞"] = "吳",
["澞"] = "吳",
["鸆"] = "吳",
["俁"] = "吳",
["噳"] = "吳",
["墲"] = "無",
["橅"] = "無",
["膴"] = "無",
["幠"] = "無",
["鄦"] = "無",
["撫"] = "無",
["無"] = "無",
["瞴"] = "無",
["蕪"] = "無",
["璑"] = "無",
["鷡"] = "無",
["譕"] = "無",
["憮"] = "無",
["舞"] = "無",
["廡"] = "無",
["嫵"] = "無",
["甒"] = "無",
["潕"] = "無",
["儛"] = "無",
["吾"] = "五",
["衙"] = "五",
["鼯"] = "五",
["浯"] = "五",
["珸"] = "五",
["郚"] = "五",
["齬"] = "五",
["鯃"] = "五",
["娪"] = "五",
["梧"] = "五",
["峿"] = "五",
["五"] = "五",
["伍"] = "五",
["寤"] = "五",
["啎"] = "五",
["晤"] = "五",
["悟"] = "五",
["逜"] = "五",
["窹"] = "五",
["捂"] = "五",
["鋙"] = "五",
["語"] = "五",
["圄"] = "五",
["敔"] = "五",
["卸"] = "午",
["汻"] = "午",
["滸"] = "午",
["午"] = "午",
["仵"] = "午",
["旿"] = "午",
["忤"] = "午",
["迕"] = "午",
["杵"] = "午",
["籞"] = "午",
["蘌"] = "午",
["篽"] = "午",
["禦"] = "午",
["許"] = "午",
["御"] = "午",
["賦"] = "武",
["陚"] = "武",
["武"] = "武",
["鵡"] = "武",
["珷"] = "武",
["碔"] = "武",
["娬"] = "武",
["虺"] = "兀",
["豗"] = "兀",
["鼿"] = "兀",
["虫"] = "兀",
["髡"] = "兀",
["軏"] = "兀",
["扤"] = "兀",
["兀"] = "兀",
["杌"] = "兀",
["屼"] = "兀",
["矹"] = "兀",
["脗"] = "勿",
["吻"] = "勿",
["刎"] = "勿",
["伆"] = "勿",
["勽"] = "勿",
["沕"] = "勿",
["忽"] = "勿",
["昒"] = "勿",
["惚"] = "勿",
["匫"] = "勿",
["笏"] = "勿",
["曶"] = "勿",
["淴"] = "勿",
["物"] = "勿",
["勿"] = "勿",
["芴"] = "勿",
["岉"] = "勿",
["茂"] = "戊",
["戊"] = "戊",
["夕"] = "夕",
["汐"] = "夕",
["穸"] = "夕",
["盻"] = "兮",
["兮"] = "兮",
["枍"] = "兮",
["粞"] = "西",
["洒"] = "西",
["西"] = "西",
["卤"] = "西",
["卥"] = "西",
["栖"] = "西",
["茜"] = "西",
["哂"] = "西",
["桸"] = "希",
["郗"] = "希",
["絺"] = "希",
["脪"] = "希",
["瓻"] = "希",
["希"] = "希",
["晞"] = "希",
["莃"] = "希",
["鵗"] = "希",
["稀"] = "希",
["睎"] = "希",
["豨"] = "希",
["悕"] = "希",
["俙"] = "希",
["欷"] = "希",
["浠"] = "希",
["唏"] = "希",
["借"] = "昔",
["唶"] = "昔",
["藉"] = "昔",
["躤"] = "昔",
["蜡"] = "昔",
["齰"] = "昔",
["醋"] = "昔",
["錯"] = "昔",
["措"] = "昔",
["厝"] = "昔",
["逪"] = "昔",
["剒"] = "昔",
["鵲"] = "昔",
["碏"] = "昔",
["踖"] = "昔",
["趞"] = "昔",
["皵"] = "昔",
["舄"] = "昔",
["斮"] = "昔",
["簎"] = "昔",
["諎"] = "昔",
["矠"] = "昔",
["庴"] = "昔",
["籍"] = "昔",
["耤"] = "昔",
["猎"] = "昔",
["昔"] = "昔",
["惜"] = "昔",
["腊"] = "昔",
["焟"] = "昔",
["棤"] = "昔",
["菥"] = "析",
["蜤"] = "析",
["析"] = "析",
["皙"] = "析",
["蜥"] = "析",
["淅"] = "析",
["惁"] = "析",
["晰"] = "析",
["厗"] = "屖",
["屖"] = "屖",
["遟"] = "屖",
["謘"] = "屖",
["稺"] = "屖",
["息"] = "息",
["熄"] = "息",
["媳"] = "息",
["鄎"] = "息",
["瘜"] = "息",
["蒠"] = "息",
["僁"] = "悉",
["悉"] = "悉",
["蟋"] = "悉",
["窸"] = "悉",
["摨"] = "犀",
["犀"] = "犀",
["遲"] = "犀",
["墀"] = "犀",
["橀"] = "醯",
["醯"] = "醯",
["謑"] = "奚",
["膎"] = "奚",
["鞵"] = "奚",
["榽"] = "奚",
["鷄"] = "奚",
["雞"] = "奚",
["谿"] = "奚",
["嵠"] = "奚",
["溪"] = "奚",
["磎"] = "奚",
["螇"] = "奚",
["鸂"] = "奚",
["奚"] = "奚",
["豯"] = "奚",
["徯"] = "奚",
["蹊"] = "奚",
["騱"] = "奚",
["傒"] = "奚",
["鼷"] = "奚",
["蒵"] = "奚",
["貕"] = "奚",
["慀"] = "奚",
["摺"] = "習",
["磖"] = "習",
["褶"] = "習",
["慴"] = "習",
["霫"] = "習",
["習"] = "習",
["鰼"] = "習",
["騽"] = "習",
["飁"] = "習",
["槢"] = "習",
["熠"] = "習",
["簁"] = "徙",
["徙"] = "徙",
["褷"] = "徙",
["屣"] = "徙",
["縰"] = "徙",
["蓰"] = "徙",
["嘻"] = "喜",
["嬉"] = "喜",
["禧"] = "喜",
["熹"] = "喜",
["僖"] = "喜",
["歖"] = "喜",
["譆"] = "喜",
["瞦"] = "喜",
["喜"] = "喜",
["憙"] = "喜",
["蟢"] = "喜",
["糦"] = "喜",
["饎"] = "喜",
["係"] = "系",
["系"] = "系",
["寫"] = "舄",
["瀉"] = "舄",
["舄"] = "舄",
["獡"] = "舄",
["潟"] = "舄",
["磶"] = "舄",
["蕮"] = "舄",
["虩"] = "隙",
["隙"] = "隙",
["丅"] = "下",
["下"] = "下",
["芐"] = "下",
["榎"] = "夏",
["廈"] = "夏",
["嗄"] = "夏",
["夏"] = "夏",
["洗"] = "先",
["先"] = "先",
["銑"] = "先",
["跣"] = "先",
["毨"] = "先",
["姺"] = "先",
["筅"] = "先",
["冼"] = "先",
["箲"] = "先",
["駪"] = "先",
["兟"] = "先",
["詵"] = "先",
["侁"] = "先",
["鮮"] = "鮮",
["廯"] = "鮮",
["癬"] = "鮮",
["蘚"] = "鮮",
["感"] = "咸",
["鱤"] = "咸",
["顑"] = "咸",
["轗"] = "咸",
["撼"] = "咸",
["憾"] = "咸",
["喊"] = "咸",
["緘"] = "咸",
["瑊"] = "咸",
["黬"] = "咸",
["減"] = "咸",
["羬"] = "咸",
["麙"] = "咸",
["咸"] = "咸",
["鹹"] = "咸",
["醎"] = "咸",
["諴"] = "咸",
["椷"] = "咸",
["輱"] = "咸",
["鍼"] = "咸",
["箴"] = "咸",
["葴"] = "咸",
["鱵"] = "咸",
["閑"] = "閑",
["嫻"] = "閑",
["鷴"] = "閑",
["顯"] = "顯",
["韅"] = "顯",
["萏"] = "臽",
["窞"] = "臽",
["惂"] = "臽",
["輡"] = "臽",
["錎"] = "臽",
["埳"] = "臽",
["臽"] = "臽",
["淊"] = "臽",
["欿"] = "臽",
["蜭"] = "臽",
["啗"] = "臽",
["鵮"] = "臽",
["餡"] = "臽",
["陷"] = "臽",
["爓"] = "臽",
["諂"] = "臽",
["讇"] = "臽",
["閻"] = "臽",
["焰"] = "臽",
["壛"] = "臽",
["櫩"] = "臽",
["燄"] = "臽",
["掐"] = "臽",
["縣"] = "縣",
["懸"] = "縣",
["相"] = "相",
["廂"] = "相",
["湘"] = "相",
["緗"] = "相",
["箱"] = "相",
["葙"] = "相",
["想"] = "相",
["霜"] = "相",
["孀"] = "相",
["鸘"] = "相",
["驦"] = "相",
["香"] = "香",
["萫"] = "香",
["曏"] = "鄉",
["鄉"] = "鄉",
["薌"] = "鄉",
["膷"] = "鄉",
["響"] = "鄉",
["饗"] = "鄉",
["蠁"] = "鄉",
["嚮"] = "鄉",
["卿"] = "鄉",
["囊"] = "襄",
["蠰"] = "襄",
["曩"] = "襄",
["灢"] = "襄",
["儾"] = "襄",
["孃"] = "襄",
["瓤"] = "襄",
["鑲"] = "襄",
["釀"] = "襄",
["瓖"] = "襄",
["驤"] = "襄",
["襄"] = "襄",
["纕"] = "襄",
["忀"] = "襄",
["欀"] = "襄",
["饟"] = "襄",
["攘"] = "襄",
["禳"] = "襄",
["穰"] = "襄",
["躟"] = "襄",
["瀼"] = "襄",
["獽"] = "襄",
["儴"] = "襄",
["蘘"] = "襄",
["鬤"] = "襄",
["勷"] = "襄",
["壤"] = "襄",
["嚷"] = "襄",
["讓"] = "襄",
["懹"] = "襄",
["攮"] = "襄",
["晌"] = "向",
["餉"] = "向",
["向"] = "向",
["珦"] = "向",
["潒"] = "象",
["像"] = "象",
["象"] = "象",
["橡"] = "象",
["蟓"] = "象",
["襐"] = "象",
["勨"] = "象",
["鱌"] = "象",
["嶑"] = "象",
["梟"] = "梟",
["蟂"] = "梟",
["鄡"] = "梟",
["囂"] = "囂",
["虈"] = "囂",
["觘"] = "小",
["抄"] = "小",
["鈔"] = "小",
["訬"] = "小",
["炒"] = "小",
["吵"] = "小",
["耖"] = "小",
["仯"] = "小",
["眇"] = "小",
["渺"] = "小",
["杪"] = "小",
["秒"] = "小",
["篎"] = "小",
["妙"] = "小",
["玅"] = "小",
["釥"] = "小",
["小"] = "小",
["少"] = "小",
["麨"] = "小",
["尠"] = "小",
["梢"] = "肖",
["捎"] = "肖",
["筲"] = "肖",
["鞘"] = "肖",
["艄"] = "肖",
["蛸"] = "肖",
["髾"] = "肖",
["輎"] = "肖",
["旓"] = "肖",
["弰"] = "肖",
["鮹"] = "肖",
["綃"] = "肖",
["颵"] = "肖",
["莦"] = "肖",
["娋"] = "肖",
["稍"] = "肖",
["哨"] = "肖",
["揱"] = "肖",
["潲"] = "肖",
["睄"] = "肖",
["悄"] = "肖",
["俏"] = "肖",
["峭"] = "肖",
["陗"] = "肖",
["帩"] = "肖",
["誚"] = "肖",
["消"] = "肖",
["宵"] = "肖",
["霄"] = "肖",
["硝"] = "肖",
["銷"] = "肖",
["逍"] = "肖",
["魈"] = "肖",
["痟"] = "肖",
["焇"] = "肖",
["肖"] = "肖",
["韒"] = "肖",
["趙"] = "肖",
["箾"] = "肖",
["踃"] = "肖",
["削"] = "肖",
["矟"] = "肖",
["酵"] = "孝",
["窙"] = "孝",
["哮"] = "孝",
["庨"] = "孝",
["涍"] = "孝",
["孝"] = "孝",
["栛"] = "劦",
["珕"] = "劦",
["荔"] = "劦",
["脅"] = "劦",
["姭"] = "劦",
["搚"] = "劦",
["拹"] = "劦",
["愶"] = "劦",
["嗋"] = "劦",
["熁"] = "劦",
["協"] = "劦",
["勰"] = "劦",
["劦"] = "劦",
["叶"] = "劦",
["瀣"] = "韰",
["韰"] = "韰",
["薤"] = "韰",
["燮"] = "燮",
["躞"] = "燮",
["沁"] = "心",
["吣"] = "心",
["心"] = "心",
["杺"] = "心",
["伈"] = "心",
["親"] = "辛",
["寴"] = "辛",
["儭"] = "辛",
["瀙"] = "辛",
["辛"] = "辛",
["新"] = "辛",
["薪"] = "辛",
["襯"] = "辛",
["櫬"] = "辛",
["嚫"] = "辛",
["莘"] = "辛",
["騂"] = "辛",
["垶"] = "辛",
["觪"] = "辛",
["崽"] = "囟",
["偲"] = "囟",
["腮"] = "囟",
["鰓"] = "囟",
["揌"] = "囟",
["顋"] = "囟",
["愢"] = "囟",
["毢"] = "囟",
["諰"] = "囟",
["細"] = "囟",
["思"] = "囟",
["緦"] = "囟",
["罳"] = "囟",
["禗"] = "囟",
["楒"] = "囟",
["葸"] = "囟",
["恖"] = "囟",
["颸"] = "囟",
["囟"] = "囟",
["顖"] = "囟",
["毸"] = "囟",
["釁"] = "釁",
["舋"] = "釁",
["亹"] = "釁",
["斖"] = "釁",
["虋"] = "釁",
["璺"] = "釁",
["興"] = "興",
["臖"] = "興",
["嬹"] = "興",
["筕"] = "行",
["桁"] = "行",
["行"] = "行",
["胻"] = "行",
["衡"] = "行",
["蘅"] = "行",
["珩"] = "行",
["洐"] = "行",
["荇"] = "行",
["絎"] = "行",
["杏"] = "杏",
["莕"] = "杏",
["幸"] = "幸",
["倖"] = "幸",
["婞"] = "幸",
["涬"] = "幸",
["緈"] = "幸",
["悻"] = "幸",
["睪"] = "幸",
["酗"] = "凶",
["凶"] = "凶",
["匈"] = "凶",
["兇"] = "凶",
["訩"] = "凶",
["胸"] = "凶",
["胷"] = "凶",
["洶"] = "凶",
["恟"] = "凶",
["詾"] = "凶",
["怳"] = "兄",
["況"] = "兄",
["貺"] = "兄",
["况"] = "兄",
["兄"] = "兄",
["讂"] = "夐",
["矎"] = "夐",
["夐"] = "夐",
["瓊"] = "夐",
["藑"] = "夐",
["觼"] = "夐",
["茠"] = "休",
["烋"] = "休",
["恘"] = "休",
["休"] = "休",
["貅"] = "休",
["咻"] = "休",
["庥"] = "休",
["鵂"] = "休",
["髤"] = "休",
["髹"] = "休",
["透"] = "秀",
["莠"] = "秀",
["誘"] = "秀",
["蜏"] = "秀",
["琇"] = "秀",
["秀"] = "秀",
["銹"] = "秀",
["滅"] = "戌",
["搣"] = "戌",
["烕"] = "戌",
["戌"] = "戌",
["珬"] = "戌",
["銊"] = "戌",
["剨"] = "砉",
["湱"] = "砉",
["砉"] = "砉",
["盨"] = "須",
["須"] = "須",
["鬚"] = "須",
["嬃"] = "須",
["蕦"] = "須",
["羺"] = "需",
["獳"] = "需",
["擩"] = "需",
["譳"] = "需",
["懦"] = "需",
["糯"] = "需",
["需"] = "需",
["繻"] = "需",
["鑐"] = "需",
["儒"] = "需",
["濡"] = "需",
["襦"] = "需",
["嚅"] = "需",
["顬"] = "需",
["鱬"] = "需",
["嬬"] = "需",
["臑"] = "需",
["醹"] = "需",
["蠕"] = "需",
["孺"] = "需",
["畜"] = "畜",
["蓄"] = "畜",
["稸"] = "畜",
["滀"] = "畜",
["傗"] = "畜",
["鄐"] = "畜",
["慉"] = "畜",
["弦"] = "玄",
["絃"] = "玄",
["舷"] = "玄",
["痃"] = "玄",
["胘"] = "玄",
["蚿"] = "玄",
["伭"] = "玄",
["婱"] = "玄",
["玄"] = "玄",
["眩"] = "玄",
["玹"] = "玄",
["玆"] = "玄",
["詃"] = "玄",
["泫"] = "玄",
["鉉"] = "玄",
["炫"] = "玄",
["袨"] = "玄",
["衒"] = "玄",
["惤"] = "玄",
["旋"] = "旋",
["漩"] = "旋",
["璇"] = "旋",
["琁"] = "旋",
["蜁"] = "旋",
["嫙"] = "旋",
["鏇"] = "旋",
["縼"] = "旋",
["躠"] = "薛",
["嶭"] = "薛",
["櫱"] = "薛",
["薛"] = "薛",
["辥"] = "薛",
["孼"] = "薛",
["蘖"] = "薛",
["蠥"] = "薛",
["孽"] = "薛",
["糵"] = "薛",
["攪"] = "學",
["覺"] = "學",
["澩"] = "學",
["斆"] = "學",
["鱟"] = "學",
["嶨"] = "學",
["礐"] = "學",
["觷"] = "學",
["學"] = "學",
["鷽"] = "學",
["泬"] = "穴",
["坹"] = "穴",
["穴"] = "穴",
["袕"] = "穴",
["鴪"] = "穴",
["侐"] = "血",
["血"] = "血",
["恤"] = "血",
["卹"] = "血",
["賉"] = "血",
["欰"] = "血",
["洫"] = "血",
["殈"] = "血",
["櫄"] = "熏",
["薰"] = "熏",
["曛"] = "熏",
["勲"] = "熏",
["熏"] = "熏",
["燻"] = "熏",
["獯"] = "熏",
["纁"] = "熏",
["醺"] = "熏",
["臐"] = "熏",
["爋"] = "熏",
["襑"] = "尋",
["憳"] = "尋",
["蕁"] = "尋",
["撏"] = "尋",
["燖"] = "尋",
["尋"] = "尋",
["潯"] = "尋",
["樳"] = "尋",
["鄩"] = "尋",
["汛"] = "卂",
["軐"] = "卂",
["卂"] = "卂",
["訊"] = "卂",
["訙"] = "卂",
["迅"] = "卂",
["阠"] = "卂",
["扟"] = "卂",
["籸"] = "卂",
["蝨"] = "卂",
["撰"] = "巽",
["饌"] = "巽",
["譔"] = "巽",
["鐉"] = "巽",
["選"] = "巽",
["繏"] = "巽",
["潠"] = "巽",
["蟤"] = "巽",
["僎"] = "巽",
["襈"] = "巽",
["巽"] = "巽",
["簨"] = "巽",
["邪"] = "牙",
["衺"] = "牙",
["牙"] = "牙",
["芽"] = "牙",
["齖"] = "牙",
["呀"] = "牙",
["枒"] = "牙",
["雅"] = "牙",
["厊"] = "牙",
["庌"] = "牙",
["訝"] = "牙",
["迓"] = "牙",
["砑"] = "牙",
["犽"] = "牙",
["疨"] = "牙",
["岈"] = "牙",
["谺"] = "牙",
["颬"] = "牙",
["鴉"] = "牙",
["釾"] = "牙",
["賈"] = "襾",
["檟"] = "襾",
["價"] = "襾",
["覀"] = "襾",
["襾"] = "襾",
["椏"] = "亞",
["錏"] = "亞",
["鵶"] = "亞",
["啞"] = "亞",
["瘂"] = "亞",
["亞"] = "亞",
["婭"] = "亞",
["俹"] = "亞",
["稏"] = "亞",
["惡"] = "亞",
["噁"] = "亞",
["堊"] = "亞",
["蝁"] = "亞",
["嘕"] = "焉",
["嫣"] = "焉",
["焉"] = "焉",
["鄢"] = "焉",
["蔫"] = "焉",
["漹"] = "焉",
["傿"] = "焉",
["唌"] = "延",
["誕"] = "延",
["蜑"] = "延",
["蛋"] = "延",
["硟"] = "延",
["涎"] = "延",
["脠"] = "延",
["梴"] = "延",
["鋋"] = "延",
["埏"] = "延",
["挻"] = "延",
["鯅"] = "延",
["延"] = "延",
["筵"] = "延",
["蜒"] = "延",
["狿"] = "延",
["郔"] = "延",
["綖"] = "延",
["莚"] = "延",
["唁"] = "言",
["這"] = "言",
["言"] = "言",
["琂"] = "言",
["圁"] = "言",
["誾"] = "言",
["訔"] = "言",
["狺"] = "言",
["船"] = "沿",
["沿"] = "沿",
["鉛"] = "沿",
["倓"] = "炎",
["緂"] = "炎",
["毯"] = "炎",
["菼"] = "炎",
["裧"] = "炎",
["賧"] = "炎",
["睒"] = "炎",
["舕"] = "炎",
["談"] = "炎",
["郯"] = "炎",
["惔"] = "炎",
["錟"] = "炎",
["淡"] = "炎",
["痰"] = "炎",
["餤"] = "炎",
["啖"] = "炎",
["腅"] = "炎",
["剡"] = "炎",
["覢"] = "炎",
["掞"] = "炎",
["炎"] = "炎",
["琰"] = "炎",
["棪"] = "炎",
["扊"] = "炎",
["庵"] = "奄",
["鵪"] = "奄",
["菴"] = "奄",
["馣"] = "奄",
["晻"] = "奄",
["唵"] = "奄",
["黤"] = "奄",
["埯"] = "奄",
["淹"] = "奄",
["閹"] = "奄",
["崦"] = "奄",
["醃"] = "奄",
["掩"] = "奄",
["奄"] = "奄",
["罨"] = "奄",
["裺"] = "奄",
["俺"] = "奄",
["腌"] = "奄",
["殗"] = "奄",
["餣"] = "奄",
["弇"] = "弇",
["媕"] = "弇",
["黭"] = "弇",
["揜"] = "弇",
["渰"] = "弇",
["鞥"] = "弇",
["愆"] = "衍",
["衍"] = "衍",
["堰"] = "妟",
["躽"] = "妟",
["宴"] = "妟",
["蝘"] = "妟",
["偃"] = "妟",
["郾"] = "妟",
["鶠"] = "妟",
["褗"] = "妟",
["匽"] = "妟",
["鼴"] = "妟",
["鰋"] = "妟",
["揠"] = "妟",
["喭"] = "彦",
["鏟"] = "彦",
["産"] = "彦",
["簅"] = "彦",
["摌"] = "彦",
["嵼"] = "彦",
["滻"] = "彦",
["顏"] = "彦",
["楌"] = "彦",
["偐"] = "彦",
["齴"] = "彦",
["遃"] = "彦",
["嵃"] = "彦",
["彦"] = "彦",
["諺"] = "彦",
["猒"] = "猒",
["懕"] = "猒",
["饜"] = "猒",
["嬮"] = "猒",
["厭"] = "猒",
["黶"] = "猒",
["檿"] = "猒",
["魘"] = "猒",
["厴"] = "猒",
["擪"] = "猒",
["壓"] = "猒",
["靨"] = "猒",
["雁"] = "雁",
["鳫"] = "雁",
["鴈"] = "雁",
["贗"] = "雁",
["燕"] = "燕",
["驠"] = "燕",
["嬿"] = "燕",
["鷰"] = "燕",
["醼"] = "燕",
["讌"] = "燕",
["嚥"] = "燕",
["酀"] = "燕",
["獻"] = "鬳",
["甗"] = "鬳",
["巘"] = "鬳",
["讞"] = "鬳",
["鬳"] = "鬳",
["瓛"] = "鬳",
["囐"] = "鬳",
["齾"] = "鬳",
["钀"] = "鬳",
["豔"] = "豔",
["灩"] = "豔",
["鴦"] = "央",
["佒"] = "央",
["咉"] = "央",
["眏"] = "央",
["姎"] = "央",
["坱"] = "央",
["映"] = "央",
["泱"] = "央",
["盎"] = "央",
["駚"] = "央",
["軮"] = "央",
["醠"] = "央",
["央"] = "央",
["秧"] = "央",
["殃"] = "央",
["鞅"] = "央",
["鉠"] = "央",
["雵"] = "央",
["胦"] = "央",
["柍"] = "央",
["詇"] = "央",
["岟"] = "央",
["炴"] = "央",
["怏"] = "央",
["紻"] = "央",
["英"] = "央",
["瑛"] = "央",
["霙"] = "央",
["韺"] = "央",
["媖"] = "央",
["渶"] = "央",
["鶧"] = "央",
["楧"] = "央",
["暎"] = "央",
["样"] = "羊",
["佯"] = "羊",
["詳"] = "羊",
["洋"] = "羊",
["翔"] = "羊",
["庠"] = "羊",
["祥"] = "羊",
["痒"] = "羊",
["姜"] = "羊",
["羌"] = "羊",
["蜣"] = "羊",
["猐"] = "羊",
["唴"] = "羊",
["羊"] = "羊",
["徉"] = "羊",
["烊"] = "羊",
["蛘"] = "羊",
["眻"] = "羊",
["鴹"] = "羊",
["恙"] = "羊",
["羕"] = "羊",
["樣"] = "羊",
["漾"] = "羊",
["湯"] = "昜",
["踼"] = "昜",
["蝪"] = "昜",
["薚"] = "昜",
["簜"] = "昜",
["盪"] = "昜",
["偒"] = "昜",
["蕩"] = "昜",
["燙"] = "昜",
["啺"] = "昜",
["碭"] = "昜",
["婸"] = "昜",
["愓"] = "昜",
["璗"] = "昜",
["崵"] = "昜",
["逿"] = "昜",
["暢"] = "昜",
["畼"] = "昜",
["腸"] = "昜",
["場"] = "昜",
["傷"] = "昜",
["殤"] = "昜",
["觴"] = "昜",
["慯"] = "昜",
["禓"] = "昜",
["塲"] = "昜",
["陽"] = "昜",
["楊"] = "昜",
["揚"] = "昜",
["瘍"] = "昜",
["煬"] = "昜",
["鍚"] = "昜",
["暘"] = "昜",
["颺"] = "昜",
["昜"] = "昜",
["輰"] = "昜",
["敭"] = "昜",
["鰑"] = "昜",
["諹"] = "昜",
["瑒"] = "昜",
["鸉"] = "昜",
["餳"] = "昜",
["鯗"] = "養",
["養"] = "養",
["癢"] = "養",
["瀁"] = "養",
["幺"] = "幺",
["吆"] = "幺",
["笑"] = "夭",
["夭"] = "夭",
["镺"] = "夭",
["芺"] = "夭",
["妖"] = "夭",
["祅"] = "夭",
["枖"] = "夭",
["訞"] = "夭",
["殀"] = "夭",
["仸"] = "夭",
["妋"] = "夭",
["飫"] = "夭",
["沃"] = "夭",
["鋈"] = "夭",
["崤"] = "爻",
["郩"] = "爻",
["敎"] = "爻",
["較"] = "爻",
["肴"] = "爻",
["淆"] = "爻",
["倄"] = "爻",
["爻"] = "爻",
["餚"] = "爻",
["殽"] = "爻",
["笅"] = "爻",
["駁"] = "爻",
["撓"] = "垚",
["鐃"] = "垚",
["譊"] = "垚",
["橈"] = "垚",
["磽"] = "垚",
["墝"] = "垚",
["穘"] = "垚",
["顤"] = "垚",
["燒"] = "垚",
["饒"] = "垚",
["蟯"] = "垚",
["蕘"] = "垚",
["襓"] = "垚",
["繞"] = "垚",
["遶"] = "垚",
["嬈"] = "垚",
["趬"] = "垚",
["蹺"] = "垚",
["翹"] = "垚",
["澆"] = "垚",
["驍"] = "垚",
["僥"] = "垚",
["堯"] = "垚",
["垚"] = "垚",
["嶢"] = "垚",
["獟"] = "垚",
["膮"] = "垚",
["嘵"] = "垚",
["憢"] = "垚",
["曉"] = "垚",
["皢"] = "垚",
["搖"] = "謠",
["遙"] = "謠",
["瑤"] = "謠",
["徭"] = "謠",
["鰩"] = "謠",
["媱"] = "謠",
["傜"] = "謠",
["颻"] = "謠",
["窰"] = "謠",
["愮"] = "謠",
["鷂"] = "謠",
["榣"] = "謠",
["嗂"] = "謠",
["猺"] = "謠",
["謠"] = "謠",
["鎐"] = "謠",
["繇"] = "繇",
["蘨"] = "繇",
["櫾"] = "繇",
["滔"] = "舀",
["韜"] = "舀",
["縚"] = "舀",
["謟"] = "舀",
["慆"] = "舀",
["幍"] = "舀",
["槄"] = "舀",
["搯"] = "舀",
["瑫"] = "舀",
["稻"] = "舀",
["蹈"] = "舀",
["舀"] = "舀",
["腰"] = "要",
["要"] = "要",
["葽"] = "要",
["喓"] = "要",
["闄"] = "要",
["偠"] = "要",
["騕"] = "要",
["婹"] = "要",
["耶"] = "耶",
["椰"] = "耶",
["爺"] = "耶",
["瑘"] = "耶",
["鎁"] = "耶",
["他"] = "也",
["拖"] = "也",
["駞"] = "也",
["池"] = "也",
["灺"] = "也",
["鍦"] = "也",
["虵"] = "也",
["也"] = "也",
["忚"] = "也",
["髢"] = "也",
["杝"] = "也",
["肔"] = "也",
["馳"] = "也",
["阤"] = "也",
["施"] = "也",
["絁"] = "也",
["葹"] = "也",
["弛"] = "也",
["箷"] = "也",
["衪"] = "也",
["暆"] = "也",
["迆"] = "也",
["酏"] = "也",
["匜"] = "也",
["迤"] = "也",
["扡"] = "也",
["崺"] = "也",
["貤"] = "也",
["地"] = "也",
["洩"] = "曳",
["曳"] = "曳",
["拽"] = "曳",
["礏"] = "業",
["業"] = "業",
["鄴"] = "業",
["驜"] = "業",
["嶪"] = "業",
["鸈"] = "業",
["澲"] = "業",
["曄"] = "曄",
["爗"] = "曄",
["燁"] = "曄",
["曅"] = "曄",
["皣"] = "曄",
["瞱"] = "曄",
["一"] = "一",
["弌"] = "一",
["哀"] = "衣",
["依"] = "衣",
["衣"] = "衣",
["扆"] = "衣",
["庡"] = "衣",
["偯"] = "衣",
["曀"] = "壹",
["殪"] = "壹",
["豷"] = "壹",
["懿"] = "壹",
["饐"] = "壹",
["鷧"] = "壹",
["撎"] = "壹",
["亄"] = "壹",
["噎"] = "壹",
["壹"] = "壹",
["桋"] = "夷",
["荑"] = "夷",
["銕"] = "夷",
["鴺"] = "夷",
["鮧"] = "夷",
["洟"] = "夷",
["羠"] = "夷",
["咦"] = "夷",
["夷"] = "夷",
["姨"] = "夷",
["痍"] = "夷",
["峓"] = "夷",
["恞"] = "夷",
["眱"] = "夷",
["蛦"] = "夷",
["胰"] = "夷",
["跠"] = "夷",
["茝"] = "頤",
["洍"] = "頤",
["姬"] = "頤",
["宧"] = "頤",
["頤"] = "頤",
["弬"] = "頤",
["礙"] = "疑",
["儗"] = "疑",
["懝"] = "疑",
["譺"] = "疑",
["癡"] = "疑",
["疑"] = "疑",
["嶷"] = "疑",
["觺"] = "疑",
["擬"] = "疑",
["薿"] = "疑",
["凝"] = "疑",
["劜"] = "乙",
["札"] = "乙",
["扎"] = "乙",
["蚻"] = "乙",
["紮"] = "乙",
["軋"] = "乙",
["圠"] = "乙",
["穵"] = "乙",
["挖"] = "乙",
["鳦"] = "乙",
["耴"] = "乙",
["乙"] = "乙",
["肊"] = "乙",
["似"] = "以",
["姒"] = "以",
["耜"] = "以",
["佀"] = "以",
["以"] = "以",
["苡"] = "以",
["苢"] = "以",
["唉"] = "矣",
["埃"] = "矣",
["欸"] = "矣",
["娭"] = "矣",
["挨"] = "矣",
["騃"] = "矣",
["俟"] = "矣",
["竢"] = "矣",
["涘"] = "矣",
["誒"] = "矣",
["矣"] = "矣",
["艾"] = "乂",
["鴱"] = "乂",
["餀"] = "乂",
["刈"] = "乂",
["乂"] = "乂",
["戴"] = "弋",
["襶"] = "弋",
["貸"] = "弋",
["代"] = "弋",
["袋"] = "弋",
["岱"] = "弋",
["黛"] = "弋",
["玳"] = "弋",
["帒"] = "弋",
["甙"] = "弋",
["酨"] = "弋",
["試"] = "弋",
["弒"] = "弋",
["忒"] = "弋",
["貣"] = "弋",
["鴏"] = "弋",
["栻"] = "弋",
["侙"] = "弋",
["恜"] = "弋",
["式"] = "弋",
["軾"] = "弋",
["拭"] = "弋",
["鉽"] = "弋",
["烒"] = "弋",
["弋"] = "弋",
["芅"] = "弋",
["黓"] = "弋",
["隿"] = "弋",
["杙"] = "弋",
["釴"] = "弋",
["夜"] = "亦",
["鵺"] = "亦",
["迹"] = "亦",
["跡"] = "亦",
["亦"] = "亦",
["弈"] = "亦",
["奕"] = "亦",
["帟"] = "亦",
["腋"] = "亦",
["掖"] = "亦",
["焲"] = "亦",
["液"] = "亦",
["椺"] = "役",
["役"] = "役",
["疫"] = "役",
["垼"] = "役",
["炈"] = "役",
["鈠"] = "役",
["豛"] = "役",
["坄"] = "役",
["唈"] = "邑",
["浥"] = "邑",
["裛"] = "邑",
["俋"] = "邑",
["挹"] = "邑",
["邑"] = "邑",
["悒"] = "邑",
["賜"] = "易",
["儩"] = "易",
["舓"] = "易",
["易"] = "易",
["敡"] = "易",
["鬄"] = "易",
["睗"] = "易",
["晹"] = "易",
["痬"] = "易",
["蜴"] = "易",
["埸"] = "易",
["逷"] = "易",
["剔"] = "易",
["踢"] = "易",
["惕"] = "易",
["錫"] = "易",
["裼"] = "易",
["緆"] = "易",
["鷖"] = "医",
["翳"] = "医",
["嫛"] = "医",
["黳"] = "医",
["繄"] = "医",
["医"] = "医",
["嫕"] = "医",
["蘙"] = "医",
["瞖"] = "医",
["殹"] = "医",
["贀"] = "医",
["醫"] = "医",
["毉"] = "医",
["蠮"] = "医",
["洕"] = "佾",
["佾"] = "佾",
["屑"] = "佾",
["榍"] = "佾",
["糏"] = "佾",
["隘"] = "益",
["賹"] = "益",
["搤"] = "益",
["縊"] = "益",
["螠"] = "益",
["謚"] = "益",
["諡"] = "益",
["蠲"] = "益",
["溢"] = "益",
["鎰"] = "益",
["齸"] = "益",
["貖"] = "益",
["益"] = "益",
["嗌"] = "益",
["膉"] = "益",
["鷁"] = "益",
["艗"] = "益",
["褹"] = "埶",
["勢"] = "埶",
["藝"] = "埶",
["囈"] = "埶",
["埶"] = "埶",
["蓺"] = "埶",
["槸"] = "埶",
["爇"] = "埶",
["褻"] = "埶",
["暬"] = "埶",
["熱"] = "埶",
["槷"] = "埶",
["禩"] = "異",
["異"] = "異",
["潩"] = "異",
["廙"] = "異",
["趩"] = "異",
["瀷"] = "異",
["翼"] = "異",
["熼"] = "異",
["豙"] = "豙",
["藙"] = "豙",
["顡"] = "豙",
["毅"] = "豙",
["斁"] = "睪",
["籜"] = "睪",
["蘀"] = "睪",
["鐸"] = "睪",
["凙"] = "睪",
["襗"] = "睪",
["擇"] = "睪",
["澤"] = "睪",
["鸅"] = "睪",
["蠌"] = "睪",
["檡"] = "睪",
["釋"] = "睪",
["繹"] = "睪",
["睪"] = "睪",
["譯"] = "睪",
["嶧"] = "睪",
["懌"] = "睪",
["驛"] = "睪",
["醳"] = "睪",
["圛"] = "睪",
["墿"] = "睪",
["燡"] = "睪",
["噫"] = "意",
["譩"] = "意",
["醷"] = "意",
["意"] = "意",
["鷾"] = "意",
["憶"] = "意",
["億"] = "意",
["臆"] = "意",
["繶"] = "意",
["澺"] = "意",
["薏"] = "意",
["檍"] = "意",
["癔"] = "意",
["欭"] = "因",
["烟"] = "因",
["胭"] = "因",
["咽"] = "因",
["恩"] = "因",
["煾"] = "因",
["因"] = "因",
["姻"] = "因",
["茵"] = "因",
["鞇"] = "因",
["氤"] = "因",
["洇"] = "因",
["駰"] = "因",
["絪"] = "因",
["裀"] = "因",
["黫"] = "垔",
["甄"] = "垔",
["薽"] = "垔",
["籈"] = "垔",
["煙"] = "垔",
["湮"] = "垔",
["堙"] = "垔",
["禋"] = "垔",
["闉"] = "垔",
["垔"] = "垔",
["陻"] = "垔",
["諲"] = "垔",
["歅"] = "垔",
["鄄"] = "垔",
["諳"] = "音",
["腤"] = "音",
["嬜"] = "音",
["喑"] = "音",
["揞"] = "音",
["隌"] = "音",
["罯"] = "音",
["暗"] = "音",
["闇"] = "音",
["黯"] = "音",
["歆"] = "音",
["愔"] = "音",
["韾"] = "音",
["音"] = "音",
["瘖"] = "音",
["窨"] = "音",
["湆"] = "音",
["殷"] = "殷",
["慇"] = "殷",
["溵"] = "殷",
["磤"] = "殷",
["冘"] = "冘",
["抌"] = "冘",
["狖"] = "冘",
["貁"] = "冘",
["耽"] = "冘",
["眈"] = "冘",
["酖"] = "冘",
["妉"] = "冘",
["黕"] = "冘",
["衴"] = "冘",
["瓭"] = "冘",
["馾"] = "冘",
["髧"] = "冘",
["紞"] = "冘",
["鈂"] = "冘",
["沈"] = "冘",
["沉"] = "冘",
["枕"] = "冘",
["莐"] = "冘",
["霃"] = "冘",
["鴆"] = "冘",
["訦"] = "冘",
["忱"] = "冘",
["邥"] = "冘",
["魫"] = "冘",
["寅"] = "寅",
["蔩"] = "寅",
["鏔"] = "寅",
["演"] = "寅",
["縯"] = "寅",
["戭"] = "寅",
["夤"] = "寅",
["螾"] = "寅",
["濥"] = "寅",
["瞚"] = "寅",
["淫"] = "淫",
["霪"] = "淫",
["婬"] = "淫",
["芛"] = "尹",
["伊"] = "尹",
["咿"] = "尹",
["蛜"] = "尹",
["笋"] = "尹",
["尹"] = "尹",
["紖"] = "引",
["矧"] = "引",
["訠"] = "引",
["弞"] = "引",
["引"] = "引",
["蚓"] = "引",
["鈏"] = "引",
["靷"] = "引",
["隱"] = "隱",
["櫽"] = "隱",
["嶾"] = "隱",
["癮"] = "隱",
["懚"] = "隱",
["檼"] = "隱",
["穩"] = "隱",
["印"] = "印",
["鮣"] = "印",
["胤"] = "胤",
["酳"] = "胤",
["瀴"] = "賏",
["甖"] = "賏",
["鸚"] = "賏",
["櫻"] = "賏",
["嚶"] = "賏",
["罌"] = "賏",
["鸎"] = "賏",
["譻"] = "賏",
["賏"] = "賏",
["嬰"] = "賏",
["瓔"] = "賏",
["纓"] = "賏",
["攖"] = "賏",
["蘡"] = "賏",
["癭"] = "賏",
["廮"] = "賏",
["巊"] = "賏",
["應"] = "鷹",
["鷹"] = "鷹",
["膺"] = "鷹",
["譍"] = "鷹",
["噟"] = "鷹",
["盈"] = "盈",
["楹"] = "盈",
["鶯"] = "熒",
["罃"] = "熒",
["嫈"] = "熒",
["褮"] = "熒",
["覮"] = "熒",
["謍"] = "熒",
["䁝"] = "熒",
["嶸"] = "熒",
["嵤"] = "熒",
["榮"] = "熒",
["瑩"] = "熒",
["蠑"] = "熒",
["禜"] = "熒",
["醟"] = "熒",
["檾"] = "熒",
["煢"] = "熒",
["焭"] = "熒",
["縈"] = "熒",
["營"] = "熒",
["鎣"] = "熒",
["塋"] = "熒",
["濴"] = "熒",
["熒"] = "熒",
["螢"] = "熒",
["滎"] = "熒",
["濙"] = "熒",
["瀅"] = "熒",
["嬴"] = "贏",
["瀛"] = "贏",
["籝"] = "贏",
["贏"] = "贏",
["攍"] = "贏",
["郺"] = "邕",
["齆"] = "邕",
["甕"] = "邕",
["罋"] = "邕",
["雍"] = "邕",
["邕"] = "邕",
["嗈"] = "邕",
["灉"] = "邕",
["癰"] = "邕",
["廱"] = "邕",
["雝"] = "邕",
["壅"] = "邕",
["饔"] = "邕",
["噰"] = "邕",
["澭"] = "邕",
["擁"] = "邕",
["臃"] = "邕",
["永"] = "永",
["栐"] = "永",
["泳"] = "永",
["詠"] = "永",
["咏"] = "永",
["通"] = "用",
["俑"] = "用",
["蓪"] = "用",
["桶"] = "用",
["捅"] = "用",
["痛"] = "用",
["筩"] = "用",
["誦"] = "用",
["傭"] = "用",
["慵"] = "用",
["鱅"] = "用",
["滽"] = "用",
["墉"] = "用",
["鏞"] = "用",
["鄘"] = "用",
["槦"] = "用",
["銿"] = "用",
["甬"] = "用",
["勇"] = "用",
["涌"] = "用",
["踊"] = "用",
["蛹"] = "用",
["恿"] = "用",
["慂"] = "用",
["悀"] = "用",
["埇"] = "用",
["踴"] = "用",
["庸"] = "用",
["用"] = "用",
["佣"] = "用",
["絛"] = "攸",
["窱"] = "攸",
["蓧"] = "攸",
["條"] = "攸",
["鋚"] = "攸",
["鞗"] = "攸",
["樤"] = "攸",
["鰷"] = "攸",
["莜"] = "攸",
["翛"] = "攸",
["筱"] = "攸",
["篠"] = "攸",
["修"] = "攸",
["脩"] = "攸",
["樇"] = "攸",
["滫"] = "攸",
["悠"] = "攸",
["攸"] = "攸",
["浟"] = "攸",
["蓨"] = "攸",
["滌"] = "攸",
["儵"] = "攸",
["倐"] = "攸",
["倏"] = "攸",
["虪"] = "攸",
["鏖"] = "麀",
["𤏶"] = "麀",
["麀"] = "麀",
["憂"] = "憂",
["優"] = "憂",
["瀀"] = "憂",
["櫌"] = "憂",
["鄾"] = "憂",
["嚘"] = "憂",
["耰"] = "憂",
["獶"] = "憂",
["纋"] = "憂",
["懮"] = "憂",
["尤"] = "尤",
["疣"] = "尤",
["肬"] = "尤",
["沋"] = "尤",
["訧"] = "尤",
["忧"] = "尤",
["蚘"] = "尤",
["袖"] = "由",
["岫"] = "由",
["牰"] = "由",
["抽"] = "由",
["妯"] = "由",
["紬"] = "由",
["怞"] = "由",
["鮋"] = "由",
["菗"] = "由",
["宙"] = "由",
["胄"] = "由",
["冑"] = "由",
["伷"] = "由",
["駎"] = "由",
["油"] = "由",
["由"] = "由",
["蚰"] = "由",
["秞"] = "由",
["邮"] = "由",
["釉"] = "由",
["柚"] = "由",
["鼬"] = "由",
["苖"] = "由",
["笛"] = "由",
["迪"] = "由",
["頔"] = "由",
["軸"] = "由",
["舳"] = "由",
["游"] = "斿",
["遊"] = "斿",
["蝣"] = "斿",
["斿"] = "斿",
["楢"] = "酉",
["逎"] = "酉",
["遒"] = "酉",
["蝤"] = "酉",
["揂"] = "酉",
["酒"] = "酉",
["媨"] = "酉",
["鞧"] = "酉",
["緧"] = "酉",
["趥"] = "酉",
["酋"] = "酉",
["崷"] = "酉",
["鰌"] = "酉",
["煪"] = "酉",
["醜"] = "酉",
["猶"] = "酉",
["猷"] = "酉",
["輶"] = "酉",
["偤"] = "酉",
["莤"] = "酉",
["蕕"] = "酉",
["庮"] = "酉",
["槱"] = "酉",
["梄"] = "酉",
["丣"] = "酉",
["酉"] = "酉",
["郵"] = "郵",
["腄"] = "郵",
["絠"] = "又",
["醢"] = "又",
["珛"] = "又",
["栯"] = "又",
["有"] = "又",
["友"] = "又",
["右"] = "又",
["宥"] = "又",
["囿"] = "又",
["侑"] = "又",
["酭"] = "又",
["姷"] = "又",
["又"] = "又",
["佑"] = "又",
["祐"] = "又",
["賄"] = "又",
["蛕"] = "又",
["烠"] = "又",
["倄"] = "又",
["鮪"] = "又",
["洧"] = "又",
["痏"] = "又",
["郁"] = "又",
["戫"] = "又",
["哊"] = "又",
["黝"] = "幼",
["坳"] = "幼",
["軪"] = "幼",
["眑"] = "幼",
["拗"] = "幼",
["鴢"] = "幼",
["靿"] = "幼",
["狕"] = "幼",
["袎"] = "幼",
["怮"] = "幼",
["窈"] = "幼",
["苭"] = "幼",
["泑"] = "幼",
["呦"] = "幼",
["蚴"] = "幼",
["幼"] = "幼",
["侉"] = "于",
["夸"] = "于",
["姱"] = "于",
["胯"] = "于",
["跨"] = "于",
["誇"] = "于",
["垮"] = "于",
["銙"] = "于",
["骻"] = "于",
["咵"] = "于",
["釫"] = "于",
["摦"] = "于",
["鮬"] = "于",
["嶀"] = "于",
["扝"] = "于",
["刳"] = "于",
["郀"] = "于",
["挎"] = "于",
["袴"] = "于",
["絝"] = "于",
["褲"] = "于",
["恗"] = "于",
["洿"] = "于",
["瓠"] = "于",
["鄠"] = "于",
["污"] = "于",
["杇"] = "于",
["圬"] = "于",
["鴮"] = "于",
["盓"] = "于",
["汙"] = "于",
["樗"] = "于",
["摴"] = "于",
["荂"] = "于",
["雩"] = "于",
["吁"] = "于",
["盱"] = "于",
["訏"] = "于",
["扜"] = "于",
["旴"] = "于",
["冔"] = "于",
["紆"] = "于",
["陓"] = "于",
["迂"] = "于",
["虶"] = "于",
["于"] = "于",
["盂"] = "于",
["竽"] = "于",
["邘"] = "于",
["玗"] = "于",
["芋"] = "于",
["杅"] = "于",
["釪"] = "于",
["骬"] = "于",
["謣"] = "于",
["宇"] = "于",
["野"] = "予",
["壄"] = "予",
["序"] = "予",
["抒"] = "予",
["芧"] = "予",
["杼"] = "予",
["墅"] = "予",
["舒"] = "予",
["紓"] = "予",
["妤"] = "予",
["伃"] = "予",
["予"] = "予",
["預"] = "予",
["忬"] = "予",
["豫"] = "予",
["蕷"] = "予",
["澦"] = "予",
["斜"] = "余",
["茶"] = "余",
["荼"] = "余",
["梌"] = "余",
["搽"] = "余",
["塗"] = "余",
["佘"] = "余",
["賒"] = "余",
["畬"] = "余",
["舍"] = "余",
["捨"] = "余",
["騇"] = "余",
["涻"] = "余",
["稌"] = "余",
["悇"] = "余",
["庩"] = "余",
["捈"] = "余",
["途"] = "余",
["酴"] = "余",
["駼"] = "余",
["鵌"] = "余",
["涂"] = "余",
["嵞"] = "余",
["峹"] = "余",
["筡"] = "余",
["蒤"] = "余",
["徐"] = "余",
["俆"] = "余",
["敘"] = "余",
["漵"] = "余",
["除"] = "余",
["篨"] = "余",
["滁"] = "余",
["蒢"] = "余",
["蜍"] = "余",
["鵨"] = "余",
["瑹"] = "余",
["余"] = "余",
["餘"] = "余",
["艅"] = "余",
["狳"] = "余",
["雓"] = "余",
["悆"] = "余",
["閼"] = "於",
["菸"] = "於",
["於"] = "於",
["淤"] = "於",
["箊"] = "於",
["唹"] = "於",
["扵"] = "於",
["瘀"] = "於",
["棜"] = "於",
["萸"] = "臾",
["臾"] = "臾",
["腴"] = "臾",
["諛"] = "臾",
["楰"] = "臾",
["庾"] = "臾",
["斞"] = "臾",
["褕"] = "俞",
["偷"] = "俞",
["媮"] = "俞",
["鍮"] = "俞",
["緰"] = "俞",
["牏"] = "俞",
["揄"] = "俞",
["窬"] = "俞",
["歈"] = "俞",
["俞"] = "俞",
["隃"] = "俞",
["毹"] = "俞",
["輸"] = "俞",
["鄃"] = "俞",
["腧"] = "俞",
["逾"] = "俞",
["榆"] = "俞",
["愉"] = "俞",
["渝"] = "俞",
["瑜"] = "俞",
["蕍"] = "俞",
["覦"] = "俞",
["蝓"] = "俞",
["踰"] = "俞",
["崳"] = "俞",
["羭"] = "俞",
["堬"] = "俞",
["瘉"] = "俞",
["癒"] = "俞",
["萮"] = "俞",
["騟"] = "俞",
["愈"] = "俞",
["貐"] = "俞",
["喻"] = "俞",
["諭"] = "俞",
["攑"] = "舁",
["藇"] = "舁",
["嶼"] = "舁",
["鱮"] = "舁",
["舉"] = "舁",
["櫸"] = "舁",
["舁"] = "舁",
["與"] = "舁",
["輿"] = "舁",
["旟"] = "舁",
["璵"] = "舁",
["歟"] = "舁",
["譽"] = "舁",
["嬩"] = "舁",
["擧"] = "舁",
["鸒"] = "舁",
["礜"] = "舁",
["穥"] = "舁",
["齵"] = "禺",
["偶"] = "禺",
["耦"] = "禺",
["藕"] = "禺",
["髃"] = "禺",
["蕅"] = "禺",
["愚"] = "禺",
["隅"] = "禺",
["禺"] = "禺",
["湡"] = "禺",
["堣"] = "禺",
["嵎"] = "禺",
["鰅"] = "禺",
["鍝"] = "禺",
["遇"] = "禺",
["寓"] = "禺",
["庽"] = "禺",
["媀"] = "禺",
["喁"] = "禺",
["顒"] = "禺",
["蘇"] = "魚",
["穌"] = "魚",
["魚"] = "魚",
["漁"] = "魚",
["䱷"] = "魚",
["詡"] = "羽",
["栩"] = "羽",
["珝"] = "羽",
["祤"] = "羽",
["羽"] = "羽",
["頨"] = "羽",
["踽"] = "禹",
["萭"] = "禹",
["聥"] = "禹",
["楀"] = "禹",
["齲"] = "禹",
["禹"] = "禹",
["瑀"] = "禹",
["鄅"] = "禹",
["珏"] = "玉",
["砡"] = "玉",
["匤"] = "玉",
["玉"] = "玉",
["鳿"] = "玉",
["頊"] = "玉",
["筆"] = "聿",
["潷"] = "聿",
["硉"] = "聿",
["律"] = "聿",
["葎"] = "聿",
["聿"] = "聿",
["銉"] = "聿",
["育"] = "育",
["毓"] = "育",
["棛"] = "育",
["錥"] = "育",
["焴"] = "育",
["淯"] = "育",
["蜟"] = "育",
["堉"] = "育",
["逳"] = "育",
["蘛"] = "育",
["煜"] = "昱",
["翊"] = "昱",
["翌"] = "昱",
["昱"] = "昱",
["喅"] = "昱",
["瞲"] = "矞",
["璚"] = "矞",
["劀"] = "矞",
["鱊"] = "矞",
["譎"] = "矞",
["潏"] = "矞",
["鐍"] = "矞",
["憰"] = "矞",
["噊"] = "矞",
["驈"] = "矞",
["橘"] = "矞",
["繘"] = "矞",
["獝"] = "矞",
["燏"] = "矞",
["遹"] = "矞",
["鷸"] = "矞",
["矞"] = "矞",
["霱"] = "矞",
["嶽"] = "獄",
["鸑"] = "獄",
["哭"] = "獄",
["獄"] = "獄",
["竇"] = "𧶠",
["續"] = "𧶠",
["覿"] = "𧶠",
["黷"] = "𧶠",
["讟"] = "𧶠",
["殰"] = "𧶠",
["讀"] = "𧶠",
["櫝"] = "𧶠",
["牘"] = "𧶠",
["儥"] = "𧶠",
["贕"] = "𧶠",
["皾"] = "𧶠",
["瓄"] = "𧶠",
["瀆"] = "𧶠",
["豄"] = "𧶠",
["韇"] = "𧶠",
["嬻"] = "𧶠",
["犢"] = "𧶠",
["匵"] = "𧶠",
["藚"] = "𧶠",
["襩"] = "𧶠",
["贖"] = "𧶠",
["𧶠"] = "𧶠",
["鬱"] = "鬱",
["欝"] = "鬱",
["灪"] = "鬱",
["爩"] = "鬱",
["絹"] = "肙",
["狷"] = "肙",
["蜎"] = "肙",
["弲"] = "肙",
["娟"] = "肙",
["悁"] = "肙",
["捐"] = "肙",
["涓"] = "肙",
["鵑"] = "肙",
["睊"] = "肙",
["焆"] = "肙",
["鞙"] = "肙",
["埍"] = "肙",
["罥"] = "肙",
["羂"] = "肙",
["瓹"] = "肙",
["梋"] = "肙",
["鋗"] = "肙",
["駽"] = "肙",
["琄"] = "肙",
["剈"] = "肙",
["肙"] = "肙",
["冤"] = "冤",
["蒬"] = "冤",
["婣"] = "淵",
["鼘"] = "淵",
["囦"] = "淵",
["淵"] = "淵",
["裫"] = "淵",
["奫"] = "淵",
["冠"] = "元",
["莞"] = "元",
["脘"] = "元",
["筦"] = "元",
["梡"] = "元",
["岏"] = "元",
["刓"] = "元",
["园"] = "元",
["忨"] = "元",
["蚖"] = "元",
["黿"] = "元",
["抏"] = "元",
["玩"] = "元",
["貦"] = "元",
["翫"] = "元",
["妧"] = "元",
["完"] = "元",
["捖"] = "元",
["垸"] = "元",
["綄"] = "元",
["院"] = "元",
["皖"] = "元",
["晥"] = "元",
["浣"] = "元",
["頑"] = "元",
["睆"] = "元",
["鯇"] = "元",
["元"] = "元",
["沅"] = "元",
["芫"] = "元",
["杬"] = "元",
["邧"] = "元",
["阮"] = "元",
["俒"] = "元",
["暖"] = "爰",
["煖"] = "爰",
["喛"] = "爰",
["緩"] = "爰",
["鰀"] = "爰",
["嵈"] = "爰",
["鍰"] = "爰",
["湲"] = "爰",
["媛"] = "爰",
["瑗"] = "爰",
["援"] = "爰",
["褑"] = "爰",
["諼"] = "爰",
["愋"] = "爰",
["楥"] = "爰",
["爰"] = "爰",
["鶢"] = "爰",
["蝯"] = "爰",
["猨"] = "爰",
["羱"] = "原",
["豲"] = "原",
["謜"] = "原",
["縓"] = "原",
["原"] = "原",
["源"] = "原",
["厵"] = "原",
["嫄"] = "原",
["騵"] = "原",
["榞"] = "原",
["獂"] = "原",
["蒝"] = "原",
["願"] = "原",
["傆"] = "原",
["愿"] = "原",
["員"] = "員",
["圓"] = "員",
["塤"] = "員",
["損"] = "員",
["縜"] = "員",
["隕"] = "員",
["殞"] = "員",
["溳"] = "員",
["磒"] = "員",
["霣"] = "員",
["愪"] = "員",
["勛"] = "員",
["鄖"] = "員",
["篔"] = "員",
["貟"] = "員",
["韻"] = "員",
["薳"] = "袁",
["袁"] = "袁",
["園"] = "袁",
["轅"] = "袁",
["猿"] = "袁",
["榬"] = "袁",
["溒"] = "袁",
["遠"] = "袁",
["癏"] = "睘",
["擐"] = "睘",
["還"] = "睘",
["環"] = "睘",
["鬟"] = "睘",
["寰"] = "睘",
["闤"] = "睘",
["糫"] = "睘",
["圜"] = "睘",
["鐶"] = "睘",
["轘"] = "睘",
["澴"] = "睘",
["繯"] = "睘",
["檈"] = "睘",
["儇"] = "睘",
["翾"] = "睘",
["蠉"] = "睘",
["嬛"] = "睘",
["譞"] = "睘",
["懁"] = "睘",
["獧"] = "睘",
["噮"] = "睘",
["彋"] = "睘",
["睘"] = "睘",
["涴"] = "夗",
["埦"] = "夗",
["豌"] = "夗",
["剜"] = "夗",
["眢"] = "夗",
["蜿"] = "夗",
["帵"] = "夗",
["碗"] = "夗",
["椀"] = "夗",
["盌"] = "夗",
["腕"] = "夗",
["惋"] = "夗",
["捥"] = "夗",
["睕"] = "夗",
["琬"] = "夗",
["鴛"] = "夗",
["鵷"] = "夗",
["惌"] = "夗",
["宛"] = "夗",
["怨"] = "夗",
["葾"] = "夗",
["夗"] = "夗",
["婉"] = "夗",
["菀"] = "夗",
["苑"] = "夗",
["踠"] = "夗",
["畹"] = "夗",
["倇"] = "夗",
["晼"] = "夗",
["黦"] = "夗",
["焥"] = "夗",
["曰"] = "曰",
["汩"] = "曰",
["𦚢"] = "𦚢",
["𩨭"] = "𦚢",
["刖"] = "月",
["枂"] = "月",
["月"] = "月",
["跀"] = "月",
["抈"] = "月",
["鈅"] = "月",
["玥"] = "月",
["蚏"] = "月",
["泧"] = "戉",
["眓"] = "戉",
["越"] = "戉",
["狘"] = "戉",
["戉"] = "戉",
["鉞"] = "戉",
["樾"] = "戉",
["怴"] = "戉",
["岳"] = "岳",
["捳"] = "岳",
["讑"] = "龠",
["籲"] = "龠",
["爚"] = "龠",
["禴"] = "龠",
["蘥"] = "龠",
["鑰"] = "龠",
["瀹"] = "龠",
["龠"] = "龠",
["籥"] = "龠",
["鸙"] = "龠",
["魂"] = "云",
["沄"] = "云",
["忶"] = "云",
["囩"] = "云",
["夽"] = "云",
["雲"] = "云",
["芸"] = "云",
["耘"] = "云",
["妘"] = "云",
["紜"] = "云",
["云"] = "云",
["耺"] = "云",
["蕓"] = "云",
["澐"] = "云",
["橒"] = "云",
["抎"] = "云",
["枃"] = "匀",
["笉"] = "匀",
["郇"] = "匀",
["絢"] = "匀",
["眴"] = "匀",
["盷"] = "匀",
["訇"] = "匀",
["迿"] = "匀",
["姰"] = "匀",
["畇"] = "匀",
["荀"] = "匀",
["詢"] = "匀",
["洵"] = "匀",
["恂"] = "匀",
["峋"] = "匀",
["珣"] = "匀",
["槆"] = "匀",
["筍"] = "匀",
["箰"] = "匀",
["旬"] = "匀",
["殉"] = "匀",
["徇"] = "匀",
["侚"] = "匀",
["均"] = "匀",
["鈞"] = "匀",
["袀"] = "匀",
["汮"] = "匀",
["呁"] = "匀",
["筠"] = "匀",
["荺"] = "匀",
["匀"] = "匀",
["韵"] = "匀",
["輷"] = "匀",
["揈"] = "匀",
["鍧"] = "匀",
["渹"] = "匀",
["赹"] = "匀",
["憌"] = "匀",
["惸"] = "匀",
["咰"] = "匀",
["唆"] = "允",
["梭"] = "允",
["葰"] = "允",
["誜"] = "允",
["朘"] = "允",
["捘"] = "允",
["荾"] = "允",
["酸"] = "允",
["狻"] = "允",
["痠"] = "允",
["鋑"] = "允",
["脧"] = "允",
["悛"] = "允",
["吮"] = "允",
["萒"] = "允",
["沇"] = "允",
["抁"] = "允",
["馻"] = "允",
["兖"] = "允",
["渷"] = "允",
["兗"] = "允",
["焌"] = "允",
["允"] = "允",
["狁"] = "允",
["玧"] = "允",
["俊"] = "允",
["晙"] = "允",
["餕"] = "允",
["畯"] = "允",
["駿"] = "允",
["竣"] = "允",
["皴"] = "允",
["逡"] = "允",
["踆"] = "允",
["夋"] = "允",
["埈"] = "允",
["陖"] = "允",
["鵕"] = "允",
["浚"] = "允",
["峻"] = "允",
["帀"] = "帀",
["沞"] = "帀",
["魳"] = "帀",
["迊"] = "帀",
["匝"] = "帀",
["咂"] = "帀",
["韴"] = "帀",
["砸"] = "帀",
["灾"] = "災",
["災"] = "災",
["𢦏"] = "𢦏",
["栽"] = "𢦏",
["哉"] = "𢦏",
["烖"] = "𢦏",
["載"] = "𢦏",
["渽"] = "𢦏",
["睵"] = "𢦏",
["溨"] = "𢦏",
["裁"] = "𢦏",
["酨"] = "𢦏",
["蛓"] = "𢦏",
["胾"] = "𢦏",
["宰"] = "宰",
["縡"] = "宰",
["梓"] = "宰",
["滓"] = "宰",
["再"] = "再",
["洅"] = "再",
["贊"] = "贊",
["趲"] = "贊",
["讚"] = "贊",
["酇"] = "贊",
["饡"] = "贊",
["灒"] = "贊",
["襸"] = "贊",
["攢"] = "贊",
["瓚"] = "贊",
["禶"] = "贊",
["穳"] = "贊",
["囋"] = "贊",
["鑽"] = "贊",
["劗"] = "贊",
["纘"] = "贊",
["儹"] = "贊",
["鄼"] = "贊",
["欑"] = "贊",
["籫"] = "贊",
["巑"] = "贊",
["攅"] = "贊",
["髒"] = "葬",
["葬"] = "葬",
["早"] = "早",
["草"] = "早",
["愺"] = "早",
["騲"] = "早",
["皁"] = "早",
["皂"] = "早",
["蚤"] = "蚤",
["慅"] = "蚤",
["騷"] = "蚤",
["搔"] = "蚤",
["鰠"] = "蚤",
["溞"] = "蚤",
["颾"] = "蚤",
["瘙"] = "蚤",
["瑵"] = "蚤",
["糔"] = "蚤",
["鼜"] = "蚤",
["藻"] = "喿",
["澡"] = "喿",
["璪"] = "喿",
["繰"] = "喿",
["躁"] = "喿",
["趮"] = "喿",
["操"] = "喿",
["幧"] = "喿",
["懆"] = "喿",
["鄵"] = "喿",
["鐰"] = "喿",
["臊"] = "喿",
["鱢"] = "喿",
["燥"] = "喿",
["噪"] = "喿",
["髞"] = "喿",
["喿"] = "喿",
["譟"] = "喿",
["劋"] = "喿",
["橾"] = "喿",
["廁"] = "則",
["則"] = "則",
["賊"] = "則",
["鰂"] = "則",
["鱡"] = "則",
["蠈"] = "則",
["側"] = "則",
["萴"] = "則",
["測"] = "則",
["惻"] = "則",
["崱"] = "則",
["仄"] = "仄",
["昃"] = "仄",
["潧"] = "曾",
["繒"] = "曾",
["增"] = "曾",
["憎"] = "曾",
["磳"] = "曾",
["曾"] = "曾",
["矰"] = "曾",
["罾"] = "曾",
["熷"] = "曾",
["竲"] = "曾",
["橧"] = "曾",
["譄"] = "曾",
["蹭"] = "曾",
["層"] = "曾",
["贈"] = "曾",
["僧"] = "曾",
["鬙"] = "曾",
["甑"] = "曾",
["鄫"] = "曾",
["驓"] = "曾",
["嶒"] = "曾",
["噌"] = "曾",
["鏳"] = "曾",
["作"] = "乍",
["鮓"] = "乍",
["痄"] = "乍",
["厏"] = "乍",
["詐"] = "乍",
["咋"] = "乍",
["笮"] = "乍",
["榨"] = "乍",
["炸"] = "乍",
["乍"] = "乍",
["拃"] = "乍",
["酢"] = "乍",
["祚"] = "乍",
["胙"] = "乍",
["阼"] = "乍",
["飵"] = "乍",
["秨"] = "乍",
["迮"] = "乍",
["柞"] = "乍",
["昨"] = "乍",
["怍"] = "乍",
["砟"] = "乍",
["莋"] = "乍",
["岝"] = "乍",
["鈼"] = "乍",
["筰"] = "乍",
["葃"] = "乍",
["舴"] = "乍",
["窄"] = "乍",
["蚱"] = "乍",
["齚"] = "乍",
["泎"] = "乍",
["頕"] = "占",
["煔"] = "占",
["炶"] = "占",
["詀"] = "占",
["站"] = "占",
["檆"] = "占",
["黏"] = "占",
["粘"] = "占",
["枮"] = "占",
["霑"] = "占",
["沾"] = "占",
["覘"] = "占",
["鉆"] = "占",
["占"] = "占",
["颭"] = "占",
["佔"] = "占",
["苫"] = "占",
["痁"] = "占",
["蛅"] = "占",
["阽"] = "占",
["敁"] = "占",
["掂"] = "占",
["點"] = "占",
["玷"] = "占",
["店"] = "占",
["坫"] = "占",
["黇"] = "占",
["扂"] = "占",
["拈"] = "占",
["鮎"] = "占",
["砧"] = "占",
["笘"] = "占",
["跕"] = "占",
["貼"] = "占",
["帖"] = "占",
["怗"] = "占",
["呫"] = "占",
["甔"] = "詹",
["擔"] = "詹",
["儋"] = "詹",
["聸"] = "詹",
["膽"] = "詹",
["黵"] = "詹",
["澹"] = "詹",
["憺"] = "詹",
["瞻"] = "詹",
["詹"] = "詹",
["蟾"] = "詹",
["噡"] = "詹",
["譫"] = "詹",
["幨"] = "詹",
["襜"] = "詹",
["韂"] = "詹",
["贍"] = "詹",
["檐"] = "詹",
["簷"] = "詹",
["舚"] = "詹",
["碾"] = "展",
["輾"] = "展",
["展"] = "展",
["搌"] = "展",
["慚"] = "斬",
["慙"] = "斬",
["鏨"] = "斬",
["槧"] = "斬",
["暫"] = "斬",
["蹔"] = "斬",
["斬"] = "斬",
["獑"] = "斬",
["嶃"] = "斬",
["覱"] = "斬",
["摲"] = "斬",
["漸"] = "斬",
["塹"] = "斬",
["壍"] = "斬",
["嬱"] = "斬",
["螹"] = "斬",
["蔪"] = "斬",
["章"] = "章",
["樟"] = "章",
["彰"] = "章",
["漳"] = "章",
["鄣"] = "章",
["璋"] = "章",
["嫜"] = "章",
["獐"] = "章",
["慞"] = "章",
["墇"] = "章",
["障"] = "章",
["麞"] = "章",
["蔁"] = "章",
["暲"] = "章",
["嶂"] = "章",
["瘴"] = "章",
["丈"] = "丈",
["杖"] = "丈",
["仗"] = "丈",
["挑"] = "兆",
["洮"] = "兆",
["桃"] = "兆",
["逃"] = "兆",
["咷"] = "兆",
["鼗"] = "兆",
["鞉"] = "兆",
["駣"] = "兆",
["𣂁"] = "兆",
["脁"] = "兆",
["晁"] = "兆",
["兆"] = "兆",
["旐"] = "兆",
["狣"] = "兆",
["鮡"] = "兆",
["垗"] = "兆",
["姚"] = "兆",
["珧"] = "兆",
["銚"] = "兆",
["恌"] = "兆",
["烑"] = "兆",
["餆"] = "兆",
["筄"] = "兆",
["艞"] = "兆",
["佻"] = "兆",
["祧"] = "兆",
["朓"] = "兆",
["庣"] = "兆",
["趒"] = "兆",
["聎"] = "兆",
["眺"] = "兆",
["覜"] = "兆",
["頫"] = "兆",
["絩"] = "兆",
["跳"] = "兆",
["窕"] = "兆",
["誂"] = "兆",
["肁"] = "肁",
["肇"] = "肁",
["咤"] = "乇",
["吒"] = "乇",
["灹"] = "乇",
["矺"] = "乇",
["奼"] = "乇",
["侘"] = "乇",
["詫"] = "乇",
["秅"] = "乇",
["秺"] = "乇",
["亳"] = "乇",
["托"] = "乇",
["託"] = "乇",
["魠"] = "乇",
["馲"] = "乇",
["侂"] = "乇",
["飥"] = "乇",
["仛"] = "乇",
["虴"] = "乇",
["杔"] = "乇",
["乇"] = "乇",
["宅"] = "乇",
["厇"] = "乇",
["晢"] = "折",
["狾"] = "折",
["誓"] = "折",
["逝"] = "折",
["銴"] = "折",
["折"] = "折",
["娎"] = "折",
["哳"] = "折",
["踅"] = "折",
["哲"] = "折",
["蜇"] = "折",
["悊"] = "折",
["嚞"] = "折",
["喆"] = "折",
["硩"] = "折",
["浙"] = "折",
["晣"] = "折",
["焎"] = "折",
["觰"] = "者",
["諸"] = "者",
["者"] = "者",
["堵"] = "者",
["赭"] = "者",
["撦"] = "者",
["扯"] = "者",
["闍"] = "者",
["奢"] = "者",
["鍺"] = "者",
["都"] = "者",
["醏"] = "者",
["覩"] = "者",
["睹"] = "者",
["暏"] = "者",
["賭"] = "者",
["帾"] = "者",
["楮"] = "者",
["屠"] = "者",
["瘏"] = "者",
["廜"] = "者",
["鷵"] = "者",
["緒"] = "者",
["豬"] = "者",
["猪"] = "者",
["瀦"] = "者",
["藸"] = "者",
["櫫"] = "者",
["褚"] = "者",
["著"] = "者",
["箸"] = "者",
["儲"] = "者",
["躇"] = "者",
["櫧"] = "者",
["藷"] = "者",
["蠩"] = "者",
["煮"] = "者",
["渚"] = "者",
["煑"] = "者",
["陼"] = "者",
["翥"] = "者",
["署"] = "者",
["薯"] = "者",
["曙"] = "者",
["書"] = "者",
["暑"] = "者",
["鐯"] = "者",
["擆"] = "者",
["櫡"] = "者",
["踂"] = "耴",
["輒"] = "耴",
["耴"] = "耴",
["鮿"] = "耴",
["偵"] = "貞",
["幀"] = "貞",
["湞"] = "貞",
["貞"] = "貞",
["楨"] = "貞",
["禎"] = "貞",
["赬"] = "貞",
["遉"] = "貞",
["寘"] = "真",
["䡩"] = "真",
["黰"] = "真",
["顛"] = "真",
["滇"] = "真",
["齻"] = "真",
["槙"] = "真",
["瘨"] = "真",
["傎"] = "真",
["蹎"] = "真",
["厧"] = "真",
["巔"] = "真",
["癲"] = "真",
["瑱"] = "真",
["填"] = "真",
["窴"] = "真",
["闐"] = "真",
["磌"] = "真",
["鷏"] = "真",
["嗔"] = "真",
["搷"] = "真",
["鎮"] = "真",
["縝"] = "真",
["真"] = "真",
["禛"] = "真",
["稹"] = "真",
["蒖"] = "真",
["鬒"] = "真",
["瞋"] = "真",
["謓"] = "真",
["慎"] = "真",
["沴"] = "疹",
["趁"] = "疹",
["紾"] = "疹",
["跈"] = "疹",
["殄"] = "疹",
["抮"] = "疹",
["駗"] = "疹",
["珍"] = "疹",
["診"] = "疹",
["畛"] = "疹",
["疹"] = "疹",
["軫"] = "疹",
["胗"] = "疹",
["袗"] = "疹",
["眕"] = "疹",
["飻"] = "疹",
["餮"] = "疹",
["黱"] = "朕",
["朕"] = "朕",
["鰧"] = "朕",
["螣"] = "朕",
["栚"] = "朕",
["眹"] = "朕",
["騰"] = "朕",
["滕"] = "朕",
["縢"] = "朕",
["儯"] = "朕",
["幐"] = "朕",
["藤"] = "朕",
["謄"] = "朕",
["膡"] = "朕",
["籐"] = "朕",
["勝"] = "朕",
["蕂"] = "朕",
["榺"] = "朕",
["塍"] = "朕",
["媵"] = "朕",
["賸"] = "朕",
["琤"] = "爭",
["崢"] = "爭",
["鬇"] = "爭",
["爭"] = "爭",
["箏"] = "爭",
["猙"] = "爭",
["埩"] = "爭",
["諍"] = "爭",
["凈"] = "爭",
["錚"] = "爭",
["棦"] = "爭",
["竫"] = "爭",
["睜"] = "爭",
["淨"] = "爭",
["徵"] = "徵",
["癥"] = "徵",
["澂"] = "徵",
["懲"] = "徵",
["綻"] = "正",
["淀"] = "正",
["靛"] = "正",
["症"] = "正",
["竀"] = "正",
["征"] = "正",
["怔"] = "正",
["正"] = "正",
["鉦"] = "正",
["鴊"] = "正",
["眐"] = "正",
["佂"] = "正",
["整"] = "正",
["政"] = "正",
["証"] = "正",
["定"] = "正",
["顁"] = "正",
["錠"] = "正",
["掟"] = "正",
["之"] = "之",
["芝"] = "之",
["志"] = "之",
["痣"] = "之",
["娡"] = "之",
["誌"] = "之",
["蚩"] = "之",
["妛"] = "之",
["嗤"] = "之",
["媸"] = "之",
["旹"] = "之",
["滍"] = "之",
["支"] = "支",
["枝"] = "支",
["肢"] = "支",
["汥"] = "支",
["衼"] = "支",
["馶"] = "支",
["鳷"] = "支",
["忮"] = "支",
["伎"] = "支",
["豉"] = "支",
["翅"] = "支",
["翄"] = "支",
["妓"] = "支",
["庋"] = "支",
["跂"] = "支",
["蚑"] = "支",
["吱"] = "支",
["岐"] = "支",
["歧"] = "支",
["鬾"] = "支",
["技"] = "支",
["芰"] = "支",
["攱"] = "支",
["庪"] = "支",
["頍"] = "支",
["屐"] = "支",
["巵"] = "巵",
["梔"] = "巵",
["識"] = "戠",
["幟"] = "戠",
["織"] = "戠",
["熾"] = "戠",
["樴"] = "戠",
["職"] = "戠",
["軄"] = "戠",
["戠"] = "戠",
["膱"] = "戠",
["蟙"] = "戠",
["置"] = "直",
["值"] = "直",
["植"] = "直",
["埴"] = "直",
["惪"] = "直",
["德"] = "直",
["稙"] = "直",
["淔"] = "直",
["直"] = "直",
["犆"] = "直",
["殖"] = "直",
["騺"] = "執",
["摯"] = "執",
["贄"] = "執",
["鷙"] = "執",
["墊"] = "執",
["慹"] = "執",
["謺"] = "執",
["褺"] = "執",
["蓻"] = "執",
["縶"] = "執",
["漐"] = "執",
["蟄"] = "執",
["執"] = "執",
["瓡"] = "執",
["祉"] = "止",
["址"] = "止",
["止"] = "止",
["趾"] = "止",
["芷"] = "止",
["沚"] = "止",
["阯"] = "止",
["齒"] = "止",
["紕"] = "止",
["只"] = "只",
["胑"] = "只",
["疻"] = "只",
["軹"] = "只",
["咫"] = "只",
["枳"] = "只",
["抧"] = "只",
["伿"] = "只",
["齞"] = "只",
["稽"] = "旨",
["詣"] = "旨",
["栺"] = "旨",
["榰"] = "旨",
["脂"] = "旨",
["鴲"] = "旨",
["指"] = "旨",
["旨"] = "旨",
["恉"] = "旨",
["嗜"] = "旨",
["蓍"] = "旨",
["鰭"] = "旨",
["耆"] = "旨",
["鮨"] = "旨",
["鬐"] = "旨",
["致"] = "至",
["輊"] = "至",
["駤"] = "至",
["跮"] = "至",
["荎"] = "至",
["緻"] = "至",
["至"] = "至",
["鵄"] = "至",
["胵"] = "至",
["痓"] = "至",
["咥"] = "至",
["窒"] = "至",
["蛭"] = "至",
["垤"] = "至",
["耋"] = "至",
["姪"] = "至",
["絰"] = "至",
["恎"] = "至",
["挃"] = "至",
["庢"] = "至",
["銍"] = "至",
["秷"] = "至",
["螲"] = "至",
["侄"] = "至",
["桎"] = "至",
["郅"] = "至",
["晊"] = "至",
["室"] = "至",
["制"] = "制",
["淛"] = "制",
["製"] = "制",
["掣"] = "制",
["痸"] = "制",
["猘"] = "制",
["陟"] = "陟",
["騭"] = "陟",
["啑"] = "疐",
["疐"] = "疐",
["嚏"] = "疐",
["懥"] = "疐",
["質"] = "質",
["躓"] = "質",
["懫"] = "質",
["礩"] = "質",
["櫍"] = "質",
["劕"] = "質",
["鑕"] = "質",
["中"] = "中",
["忠"] = "中",
["衷"] = "中",
["忡"] = "中",
["浺"] = "中",
["盅"] = "中",
["种"] = "中",
["沖"] = "中",
["翀"] = "中",
["仲"] = "中",
["撞"] = "重",
["幢"] = "重",
["橦"] = "重",
["噇"] = "重",
["𩪘"] = "重",
["艟"] = "重",
["憧"] = "重",
["畽"] = "重",
["董"] = "重",
["蕫"] = "重",
["箽"] = "重",
["懂"] = "重",
["湩"] = "重",
["曈"] = "重",
["童"] = "重",
["僮"] = "重",
["瞳"] = "重",
["罿"] = "重",
["犝"] = "重",
["潼"] = "重",
["穜"] = "重",
["動"] = "重",
["慟"] = "重",
["堹"] = "重",
["諥"] = "重",
["蹱"] = "重",
["重"] = "重",
["緟"] = "重",
["蝩"] = "重",
["褈"] = "重",
["鐘"] = "重",
["鍾"] = "重",
["籦"] = "重",
["種"] = "重",
["腫"] = "重",
["踵"] = "重",
["歱"] = "重",
["喠"] = "重",
["偅"] = "重",
["衝"] = "重",
["揰"] = "重",
["尰"] = "重",
["潨"] = "眾",
["眾"] = "眾",
["洲"] = "州",
["州"] = "州",
["喌"] = "州",
["酬"] = "州",
["詶"] = "州",
["鵃"] = "舟",
["輈"] = "舟",
["侜"] = "舟",
["矪"] = "舟",
["舟"] = "舟",
["裯"] = "周",
["啁"] = "周",
["禂"] = "周",
["綢"] = "周",
["翢"] = "周",
["錭"] = "周",
["雕"] = "周",
["凋"] = "周",
["琱"] = "周",
["鯛"] = "周",
["鵰"] = "周",
["彫"] = "周",
["奝"] = "周",
["調"] = "周",
["蜩"] = "周",
["惆"] = "周",
["婤"] = "周",
["稠"] = "周",
["椆"] = "周",
["周"] = "周",
["輖"] = "周",
["賙"] = "周",
["郮"] = "周",
["晭"] = "周",
["倜"] = "周",
["粥"] = "粥",
["鬻"] = "粥",
["肘"] = "肘",
["疛"] = "肘",
["紂"] = "肘",
["葤"] = "肘",
["酎"] = "肘",
["婦"] = "帚",
["帚"] = "帚",
["箒"] = "帚",
["鯞"] = "帚",
["咮"] = "朱",
["株"] = "朱",
["誅"] = "朱",
["邾"] = "朱",
["鼄"] = "朱",
["跦"] = "朱",
["袾"] = "朱",
["鴸"] = "朱",
["蛛"] = "朱",
["殊"] = "朱",
["趎"] = "朱",
["珠"] = "朱",
["朱"] = "朱",
["侏"] = "朱",
["絑"] = "朱",
["祩"] = "朱",
["鮢"] = "朱",
["硃"] = "朱",
["姝"] = "朱",
["銖"] = "朱",
["茱"] = "朱",
["洙"] = "朱",
["陎"] = "朱",
["竺"] = "竹",
["篤"] = "竹",
["竹"] = "竹",
["筑"] = "竹",
["築"] = "竹",
["茿"] = "竹",
["篴"] = "逐",
["蓫"] = "逐",
["逐"] = "逐",
["鱁"] = "逐",
["飳"] = "主",
["妵"] = "主",
["黈"] = "主",
["蘣"] = "主",
["罜"] = "主",
["拄"] = "主",
["柱"] = "主",
["駐"] = "主",
["註"] = "主",
["鉒"] = "主",
["軴"] = "主",
["住"] = "主",
["跓"] = "主",
["嵀"] = "主",
["主"] = "主",
["麈"] = "主",
["宔"] = "主",
["炷"] = "主",
["注"] = "主",
["疰"] = "主",
["蛀"] = "主",
["霔"] = "主",
["貯"] = "宁",
["詝"] = "宁",
["柠"] = "宁",
["宁"] = "宁",
["佇"] = "宁",
["竚"] = "宁",
["苧"] = "宁",
["紵"] = "宁",
["羜"] = "宁",
["眝"] = "宁",
["壴"] = "壴",
["廚"] = "壴",
["躕"] = "壴",
["櫥"] = "壴",
["幮"] = "壴",
["樹"] = "壴",
["澍"] = "壴",
["尌"] = "壴",
["祝"] = "祝",
["呪"] = "祝",
["咒"] = "祝",
["柷"] = "祝",
["抓"] = "爪",
["爪"] = "爪",
["笊"] = "爪",
["找"] = "爪",
["竱"] = "專",
["團"] = "專",
["摶"] = "專",
["篿"] = "專",
["慱"] = "專",
["剸"] = "專",
["鄟"] = "專",
["漙"] = "專",
["鷒"] = "專",
["轉"] = "專",
["傳"] = "專",
["囀"] = "專",
["縳"] = "專",
["專"] = "專",
["磚"] = "專",
["甎"] = "專",
["塼"] = "專",
["嫥"] = "專",
["膞"] = "專",
["鱄"] = "專",
["蓴"] = "專",
["孨"] = "孨",
["孱"] = "孨",
["蜼"] = "隹",
["堆"] = "隹",
["嵟"] = "隹",
["碓"] = "隹",
["推"] = "隹",
["蓷"] = "隹",
["陮"] = "隹",
["魋"] = "隹",
["嶊"] = "隹",
["崔"] = "隹",
["猚"] = "隹",
["椎"] = "隹",
["顀"] = "隹",
["隹"] = "隹",
["錐"] = "隹",
["萑"] = "隹",
["騅"] = "隹",
["鵻"] = "隹",
["誰"] = "隹",
["脽"] = "隹",
["蘬"] = "追",
["磓"] = "追",
["塠"] = "追",
["頧"] = "追",
["鴭"] = "追",
["鎚"] = "追",
["搥"] = "追",
["縋"] = "追",
["膇"] = "追",
["槌"] = "追",
["追"] = "追",
["桘"] = "追",
["巋"] = "追",
["歸"] = "追",
["悼"] = "卓",
["淖"] = "卓",
["罩"] = "卓",
["鵫"] = "卓",
["趠"] = "卓",
["踔"] = "卓",
["棹"] = "卓",
["鋽"] = "卓",
["掉"] = "卓",
["逴"] = "卓",
["焯"] = "卓",
["綽"] = "卓",
["婥"] = "卓",
["繛"] = "卓",
["卓"] = "卓",
["桌"] = "卓",
["倬"] = "卓",
["啅"] = "卓",
["晫"] = "卓",
["蝃"] = "叕",
["欼"] = "叕",
["綴"] = "叕",
["醊"] = "叕",
["畷"] = "叕",
["啜"] = "叕",
["餟"] = "叕",
["腏"] = "叕",
["錣"] = "叕",
["輟"] = "叕",
["娺"] = "叕",
["窡"] = "叕",
["鵽"] = "叕",
["掇"] = "叕",
["剟"] = "叕",
["裰"] = "叕",
["敠"] = "叕",
["惙"] = "叕",
["罬"] = "叕",
["叕"] = "叕",
["歠"] = "叕",
["逫"] = "叕",
["輜"] = "甾",
["淄"] = "甾",
["錙"] = "甾",
["緇"] = "甾",
["甾"] = "甾",
["菑"] = "甾",
["鶅"] = "甾",
["椔"] = "甾",
["鯔"] = "甾",
["糍"] = "兹",
["茲"] = "兹",
["滋"] = "兹",
["孳"] = "兹",
["嵫"] = "兹",
["嗞"] = "兹",
["鎡"] = "兹",
["鰦"] = "兹",
["鶿"] = "兹",
["稵"] = "兹",
["螆"] = "兹",
["慈"] = "兹",
["鷀"] = "兹",
["磁"] = "兹",
["礠"] = "兹",
["濨"] = "兹",
["孜"] = "子",
["仔"] = "子",
["孖"] = "子",
["子"] = "子",
["虸"] = "子",
["耔"] = "子",
["秄"] = "子",
["杍"] = "子",
["籽"] = "子",
["字"] = "子",
["茡"] = "子",
["芓"] = "子",
["牸"] = "子",
["李"] = "子",
["姊"] = "秭",
["秭"] = "秭",
["趀"] = "秭",
["笫"] = "秭",
["胏"] = "秭",
["柹"] = "秭",
["柿"] = "秭",
["郋"] = "自",
["自"] = "自",
["洎"] = "自",
["臮"] = "自",
["垍"] = "自",
["詯"] = "自",
["淙"] = "宗",
["鬃"] = "宗",
["漴"] = "宗",
["粽"] = "宗",
["宗"] = "宗",
["倧"] = "宗",
["綜"] = "宗",
["錝"] = "宗",
["賨"] = "宗",
["琮"] = "宗",
["悰"] = "宗",
["孮"] = "宗",
["誴"] = "宗",
["崇"] = "宗",
["崈"] = "宗",
["踪"] = "宗",
["奏"] = "奏",
["凑"] = "奏",
["腠"] = "奏",
["輳"] = "奏",
["楱"] = "奏",
["湊"] = "奏",
["足"] = "足",
["莡"] = "足",
["捉"] = "足",
["齪"] = "足",
["娖"] = "足",
["浞"] = "足",
["鋜"] = "足",
["趗"] = "足",
["珿"] = "足",
["哫"] = "足",
["促"] = "足",
["晬"] = "卒",
["祽"] = "卒",
["綷"] = "卒",
["淬"] = "卒",
["啐"] = "卒",
["倅"] = "卒",
["焠"] = "卒",
["碎"] = "卒",
["誶"] = "卒",
["濢"] = "卒",
["噿"] = "卒",
["臎"] = "卒",
["醉"] = "卒",
["翠"] = "卒",
["悴"] = "卒",
["萃"] = "卒",
["瘁"] = "卒",
["顇"] = "卒",
["粹"] = "卒",
["睟"] = "卒",
["賥"] = "卒",
["卒"] = "卒",
["稡"] = "卒",
["猝"] = "卒",
["椊"] = "卒",
["捽"] = "卒",
["崪"] = "卒",
["窣"] = "卒",
["崒"] = "卒",
["踤"] = "卒",
["錊"] = "卒",
["蔟"] = "族",
["嗾"] = "族",
["鷟"] = "族",
["鏃"] = "族",
["瘯"] = "族",
["簇"] = "族",
["族"] = "族",
["罪"] = "罪",
["辠"] = "罪",
["𢶀"] = "罪",
["尊"] = "尊",
["樽"] = "尊",
["罇"] = "尊",
["嶟"] = "尊",
["繜"] = "尊",
["撙"] = "尊",
["噂"] = "尊",
["譐"] = "尊",
["僔"] = "尊",
["蹲"] = "尊",
["鱒"] = "尊",
["鐏"] = "尊",
["遵"] = "尊",
["鷷"] = "尊",
["墫"] = "尊",
["竴"] = "尊",
["左"] = "左",
["佐"] = "左",
["袏"] = "左",
["蹉"] = "左",
["瑳"] = "左",
["搓"] = "左",
["磋"] = "左",
["溠"] = "左",
["傞"] = "左",
["醝"] = "左",
["瘥"] = "左",
["鹺"] = "左",
["嵯"] = "左",
["嵳"] = "左",
["蒫"] = "左",
["艖"] = "左",
["齹"] = "左",
["縒"] = "左",
["褨"] = "左",
["嗟"] = "左",
["差"] = "左",
["鎈"] = "左",
["槎"] = "左",
["髊"] = "左",
["脞"] = "坐",
["侳"] = "坐",
["挫"] = "坐",
["夎"] = "坐",
["遳"] = "坐",
["銼"] = "坐",
["剉"] = "坐",
["莝"] = "坐",
["趖"] = "坐",
["矬"] = "坐",
["痤"] = "坐",
["睉"] = "坐",
["坐"] = "坐",
["座"] = "坐",
["髽"] = "坐",
["鑿"] = "丵",
["糳"] = "丵",
["艐"] = "翪",
["堫"] = "翪",
["葼"] = "翪",
["鬷"] = "翪",
["嵕"] = "翪",
["猣"] = "翪",
["鯼"] = "翪",
["椶"] = "翪",
["騣"] = "翪",
["蝬"] = "翪",
["翪"] = "翪",
["緵"] = "翪",
["稯"] = "翪",
["鬉"] = "翪",
["糉"] = "翪",
}
5yadar74lz1a1jnwi6xomv7nxpvdp59
Module:Zh-glyph/phonetic
828
1364905
9909130
8566091
2026-09-29T08:35:45Z
TongcyDai
53191
9909130
Scribunto
text/plain
return {
["愛"]="愛曖僾靉薆璦瞹",
["安"]="安鞍鞌侒按案洝荌晏鴳騴鷃頞胺咹",
["卬"]="卬枊昂仰迎",
["敖"]="熬敖嗷螯聱驁鰲遨獒滶蔜鷔鼇謷嶅嗸摮嫯鏊傲磝",
["奧"]="襖懊燠奧墺隩澳噢薁䉛",
["八"]="扒八𩡩朳玐釟汃",
["巴"]="巴芭笆豝鈀吧蚆疤把爸弝靶葩舥妑帊琶爬杷跁耙",
["犮"]="軷韍祓蛂拔菝袚茇盋帗跋魃䣮馛炦颰妭犮胈鈸苃坺髮冹瞂紱黻翇",
["癹"]="發癹",
["罷"]="擺犤罷矲羆襬藣",
["霸"]="壩霸灞欛",
["白"]="怕帕粕胉泊箔魄皕伯百迫敀柏湐拍珀皛洦白帛舶鮊陌帞袹蛨貊佰銆碧咟",
["拜"]="拜湃",
["班"]="班斑",
["般"]="螌般媻搫搬盤磐瘢幋槃縏鎜鞶",
["半"]="半絆靽姅拌拚坢判泮頖胖冸牉柈伴叛畔袢",
["勹"]="袍袌軳抱勽菢勹包胞苞枹飽泡炮皰疱跑咆庖匏刨狍炰鉋瓟颮鞄鮑骲玸雹窇",
["保"]="襃保堡褓堢緥葆賲宲椺",
["鴇"]="鴇駂",
["暴"]="暴曝瀑虣爆襮嚗謈犦懪",
["卑"]="捭牌郫蠯猈稗粺𥱼豍箄錍睥渒鼙鞞椑崥髀卑鵯裨痺碑婢諀脾埤陴蜱焷螷庳琕萆綼",
["北"]="背鄁偝邶苝北",
["貝"]="貝狽鋇浿垻敗唄",
["備"]="憊韛備俻鞴糒犕",
["奔"]="賁蟦膹橨奔錛噴濆歕獖餴饙僨墳燌豶羵蕡鐼馩轒鼖憤鱝",
["本"]="本笨苯翉体鉢缽",
["匕"]="匕庀疕朼牝",
["比"]="梐狴悂螕鎞蓖篦批砒鈚磇媲笓膍陛仳吡紕比妣秕沘枇粊庇屁琵毗貔豼蚍芘肶魮阰坒玭",
["啚"]="鄙啚",
["必"]="泌秘毖閟柲鉍邲覕咇苾馝飶鴓鮅必珌佖駜怭妼宓樒密蔤滵蜜謐榓淧瑟璱飋虙",
["畀"]="箅濞畀痹淠嚊鼻襣",
["畢"]="畢蓽篳蹕韠滭鷝熚彃縪饆鏎罼",
["敝"]="蔽鄨鷩彆潎斃敝幣弊鱉虌憋瞥鐅嫳撇撆暼蹩襒",
["閉"]="閉閇",
["辟"]="嬖薜鐾臂譬避檗擘糪掰繴辟璧鐴躄襞僻癖廦擗躃闢壁鼊霹劈澼憵甓鷿幦",
["扁"]="斒鯿編揙褊篇偏翩媥扁萹騗騙諞楄蝙甂猵牑蹁匾碥惼糄徧遍頨艑",
["卞"]="汴卞忭抃犿笇飰",
["弁"]="鴘弁拚閞昪匥笲軬畚",
["便"]="鞭鯾箯便緶楩",
["辡"]="辬瓣辦辨辡辯辮",
["彪"]="彪淲滮",
["猋"]="飆猋贆",
["麃"]="藨麃臕鑣儦瀌犥皫",
["表"]="表裱俵",
["别"]="捌哵別莂",
["彬"]="虨彬霦",
["斌"]="斌贇",
["豩"]="燹豳",
["賓"]="蠙賓檳鑌濱顮儐矉擯殯鬢繽嬪薲獱臏髕",
["仌"]="冫冰砅馮憑",
["兵"]="浜捠兵",
["丙"]="更粳埂浭稉梗哽綆鯁挭骾郠硬鞕丙炳邴怲苪蛃昺昞窉柄鈵寎病",
["秉"]="秉棅",
["禀"]="燷壈燣顲稟廩懍凜癛檁",
["并"]="鵧駢胼骿軿賆跰恲鮩絣拼迸姘皏併并栟屏箳餅鉼摒庰偋艵頩竮瓶缾蛢荓郱洴蓱",
["並"]="湴跰並竝",
["孛"]="誖悖孛哱馞勃餑郣浡桲挬脖鵓渤綍",
["卜"]="仆赴訃朴圤卜鳪扑攴",
["不"]="肧俖娝痞丕伾秠駓怌豾髬魾鉟嚭邳岯否抔不紑鴀衃罘芣杯盃桮坯胚",
["步"]="荹步踄",
["部"]="部蔀篰",
["才"]="茬材才財鼒在豺",
["采"]="采睬彩採綵寀菜埰棌",
["參"]="篸參驂傪慘黲謲毿蔘犙糝槮鬖摻醦縿穇嵾磣墋曑瘮滲",
["粲"]="餐湌粲燦璨薒殩",
["倉"]="艙倉蒼滄傖鶬凔槍搶蹌瑲牄嗆創瘡愴鎗",
["曹"]="遭糟醩傮槽曹螬漕嘈艚褿蓸慒",
["冊"]="栅冊笧",
["畟"]="稷畟溭謖",
["叉"]="叉杈靫汊衩扠釵芆訍",
["臿"]="偛插鍤臿喢牐歃敮",
["孱"]="孱潺僝轏樼",
["廛"]="纏緾躔瀍鄽廛",
["毚"]="纔讒饞毚欃攙儳酁瀺嚵巉劖艬鑱",
["昌"]="昌倡猖菖閶鯧裮琩唱",
["長"]="張餦粻漲長脹痮帳賬倀鼚鋹悵韔萇瓺棖掁",
["巢"]="璅繅薻罺摷勦巢轈樔鄛剿漅",
["朝"]="嘲廟朝潮",
["車"]="車硨厙庫",
["徹"]="徹撤澈轍",
["臣"]="臤掔慳藖堅鋻鰹蜸菣賢礥臔挋臣茞腎頣緊嚚鏗摼硻",
["辰"]="辴屒振侲桭唇帪賑裖敐震娠蜃辰晨宸鷐麎祳脤鋠磭陙脣漘",
["陳"]="陳敶陣",
["韱"]="讖攕櫼懺殲瀸鑯虃籤韯纖孅韱襳",
["爯"]="稱爯偁",
["丞"]="蒸烝篜脀拯氶承丞巹",
["成"]="成城誠宬郕筬盛珹晟墭",
["呈"]="挰鋥逞裎悜睈呈程酲珵徎郢浧桯鞓壬鐵驖戜",
["乘"]="乘溗塖騬椉剩嵊",
["尺"]="尺蚇",
["彖"]="蠡盠彖墬喙餯剶猭鶨椽篆瑑堟蝝緣櫞掾褖湪",
["赤"]="浾嚇赦郝赫爀赤烾螫赥捇焃",
["敕"]="敕勅鷘慗遫",
["充"]="統充珫茺銃",
["舂"]="樁憃舂摏蹖",
["蟲"]="痋鉵赨爞蟲融瀜",
["雔"]="犨犫雔售讎",
["丑"]="峱紐扭狃鈕忸杻莥羞丑沑粈衄",
["臭"]="臭殠糗嗅齅",
["出"]="聉贅祟出茁欪貀袦咄柮拙炪掘鈯啒淈窟泏堀胐窋絀黜屈趉鶌詘倔崛",
["芻"]="煼謅縐鄒騶搊皺篘㑳趍趨芻犓雛鶵媰齺",
["豖"]="冢塚涿琢啄諑剢瘃豖",
["川"]="川釧巛巡馴紃甽順訓",
["舛"]="舛荈僢舜瞬蕣",
["刅"]="梁粱刱",
["吹"]="吹炊焁",
["垂"]="埵綞涶唾厜腄箠娷諈錘甀硾捶垂陲倕菙睡",
["辭"]="辤辭",
["此"]="些跐柴祡茈眦砦寨啙泚玼皉鮆眥貲髭頾訾鴜鈭姕觜紫訿呰嘴雌此佌庛疵骴胔飺齜",
["朿"]="債積刺莿㓨朿漬耫嘖栜梀拺捒幘簀嫧謮責皟憡策筴拺賾鰿擌蹟襀磧洓績勣樍",
["次"]="咨資姿粢諮恣澬趑蠀次佽絘薋瓷茨餈垐楶",
["囱"]="牎窗窓囱總熜傯緫揔偬囪璁驄蔥匆怱葱楤聦繱騘鍯聰愡檧憽幒",
["从"]="摐鏦豵嵸蓯蹤縱樅磫熧瘲暰瑽從从慫聳傱",
["竄"]="躥竄攛鑹",
["崔"]="嗺催璀皠漼鏙摧崔慛槯繀膗",
["毳"]="膬毳竁橇",
["存"]="荐洊臶栫袸存拵",
["寸"]="村忖刌寸",
["沓"]="踏濌錔鞜沓涾誻",
["羍"]="羍達闥撻躂澾薘",
["龖"]="龖襲",
["大"]="馱大忕太汰汏軑釱达杕",
["眔"]="褱鰥嚃眔遝",
["帶"]="帶蹛艜慸滯懘蔕螮摕僀殢遰墆嵽蒂",
["丹"]="丹旃栴",
["單"]="嚲癉奲驒鼉磾鷤觶單鄲殫簞匰襌彈貚撣潬僤憚樿燀戰闡繟幝嘽灛蟬禪嬋墠鱓蕇",
["旦"]="皽担胆疸觛亶嬗笪狚旦鴠譠坦但檀壇儃驙袒襢繵膻澶邅鱣饘旜氈鸇顫蟺擅羶呾怛妲靼炟",
["宕"]="宕菪",
["刀"]="刀忉魛舠朷叨倒到菿鞀鳭灱菬超怊欩召昭招鉊沼照詔炤弨眧韶佋軺玿柖紹袑綤邵劭卲刁芀貂蛁迢苕髫岧",
["得"]="得淂棏",
["登"]="登璒燈簦蹬磴鐙嶝隥橙凳墱鼟膯澄鄧僜覴瞪憕撜證",
["狄"]="逖悐狄荻梑",
["翟"]="櫂濯耀曜燿糶粜嬥藋躍戳擢蠗鸐翟趯籊糴䊮",
["氐"]="低氐袛羝眡岻奃趆柢邸底詆坻抵牴觝弤軧骶砥胝疷秪泜蚳彽阺貾祗厎茋鴟汦",
["弟"]="梯睇涕挮鬀剃綈蕛稊苐鵜銻鮷罤弟娣悌第",
["帝"]="膪揥腣諦偙渧帝楴啼蹄締崹鶙禘啻嫡摘謫讁擿蹢適啇滴鏑甋樀敵",
["典"]="典錪腆痶淟琠賟晪",
["奠"]="奠鄭擲躑",
["殿"]="殿澱壂臀",
["弔"]="盄弔伄",
["疊"]="曡疊",
["丁"]="圢町打盯朾丁玎虰揨釘靪仃叮疔頂奵耵酊葶矴訂飣汀艼庁婷停聤渟楟亭",
["鼎"]="鼎薡濎",
["冬"]="疼冬苳笗炵佟鼕鉖終螽鼨柊鴤泈蔠",
["東"]="東菄鶇倲涷蝀凍鯟崠埬棟",
["兜"]="兜篼",
["斗"]="斗抖蚪枓阧斢鈄",
["豆"]="侸剅郖鬪鋀頭豆逗痘梪脰荳浢餖毭豎裋短",
["鬥"]="鬥鬭",
["毒"]="瑇纛碡毒蝳",
["耑"]="揣褍惴圌篅瑞端剬偳鍴耑踹湍煓貒諯顓喘遄輲椯歂腨",
["段"]="鍛腶碫瑖緞段椴毈",
["兌"]="毤蛻娧駾裞兌綐銳帨稅說涚䫄脫侻挩莌梲鮵敓痥悅閱",
["對"]="對轛薱濧懟",
["敦"]="弴敦錞憝憞譈鐓鷻鶉惇墩驐燉焞啍暾噋諄埻稕醇淳犉",
["屯"]="躉窀沌盹頓扽黗吨屯飩豘軘坉邨忳芚庉囤迍杶瑃椿偆肫訰純春蠢踳惷蒓賰鬊",
["盾"]="盾遁循揗楯輴腯鍎",
["多"]="多哆跢痑爹陊橠袲奓栘黟趍誃簃眵恀侈姼鉹垑袳卶宜誼竩移迻扅拸熪謻蛥",
["朵"]="桗挆朵朶躲剁垛",
["戹"]="呃阸厄蚅扼軛戹豟呝軶",
["而"]="耐耏而鮞鴯栭陑峏輀胹洏髵恧",
["兒"]="掜誽倪蜺霓郳齯婗輗棿猊麑貎鯢兒觬堄晲睨唲隉鶂鬩",
["尔"]="妳弥尒尔迩你苶",
["耳"]="佴麛弭渳葞髶聻恥咡誀耳洱駬餌珥衈毦刵眲",
["爾"]="嬭禰镾薾檷鑈瀰獼瓕彌鸍擟濔壐璽爾邇獮籋",
["二"]="膩二弍貳樲",
["發"]="廢癈橃撥鱍襏驋鏺潑醱",
["乏"]="砭貶窆泛姂䒦柉覂眨鴔乏",
["伐"]="茷筏伐閥垡栰",
["罰"]="罰藅",
["法"]="法灋",
["番"]="皤嶓番播譒鄱潘蟠磻蕃轓鱕鐇藩籓橎翻幡飜旛繙燔蹯膰羳鷭璠襎墦",
["凡"]="嵐葻釩汎凡帆颿軓梵芃風飌楓猦偑檒瘋諷堸渢鳳",
["棥"]="攀襻棥礬樊蠜",
["煩"]="煩薠",
["緐"]="繁緐蘩瀿瀪",
["反"]="扳板版瓪昄鈑蝂眅阪魬粄反返坂畈販汳飯",
["氾"]="氾犯範范笵",
["匚"]="匚匠",
["方"]="縍榜蒡牓螃搒舫謗雱磅霶滂鎊髈彷趽房篣騯傍膀旁徬閍祊嗙方汸坊邡枋鈁蚄肪鴋牥昉瓬放倣芳淓妨紡仿髣鶭訪防魴",
["妃"]="圮妃",
["非"]="啡排俳輫悲棑輩裴徘琲痱非扉緋猆斐馡騑誹餥匪棐蜚篚榧鯡霏菲婔裶悱奜腓陫屝厞翡",
["飛"]="飛騛",
["肥"]="蜰肥淝萉",
["朏"]="朏昢",
["分"]="扮盼頒朌鳻份汃玢邠攽砏貧湓盆葐坌坋分吩粉黺芬紛衯棻氛雰忿魵汾羒枌棼妢梤馚鼢蚡弅秎",
["糞"]="糞瀵",
["丰"]="邦梆垹肨蚌玤棒蜯琫菶俸髼蜂韸蓬篷唪埲丰妦仹峯峰鋒烽蠭桻莑夆捧逢縫漨捀奉",
["豐"]="豐蘴灃寷麷酆",
["封"]="幫鞤封葑犎崶湗",
["夆"]="胮逄舽",
["缶"]="寶珤缶缹",
["夫"]="趺肤邞鈇衭玞夫鳺扶妋麩蚨芙颫枎",
["旉"]="敷璷",
["巿"]="沛芾伂霈旆肺杮巿",
["弗"]="鄪費砩沸疿茀髴昲靅狒怫胇艴弗紼柫笰拂刜彿佛坲咈炥",
["伏"]="絥伏垘茯洑栿",
["孚"]="殍脬捊莩哹浮蜉桴罦琈粰烰艀俘孚孵稃郛筟",
["服"]="菔服鵩箙棴",
["畐"]="富輻副褔蔔匐逼偪幅楅湢堛愊踾揊稫畐疈腷鶝福蝠葍",
["复"]="愎鍑覆復椱複馥腹輹蝮蕧复鰒澓",
["甫"]="豧縛逋餔晡庯陠鵏峬誧秿鯆補圃鋪痡浦烳溥悑酺匍蜅脯葡蒱蒲簿捕哺簠甫黼莆俌郙傅搏麱尃榑輔鬴賻博髆鎛猼餺簙鑮欂蒪膊薄礡",
["父"]="抪布佈怖斧父蚥滏釜",
["付"]="鉜跗柎腑俯府付咐怤泭姇紨拊弣符苻坿腐附駙鮒袝蚹胕",
["阜"]="阜埠",
["負"]="負萯蝜偩",
["丐"]="丐匄",
["干"]="訐肝竿干奸玕汗迀忓鳱邗秆矸仠皯衦簳擀旰盰幹杆骭桿趕稈刊靬衎犴豻頇岸鼾罕焊蔊虷旱皔釬扞閈馯捍悍銲垾駻睅娨飦赶軒蓒酐澣",
["甘"]="紺姏嵌甘柑苷泔坩蚶酣甝魽邯鉗箝黚拑",
["敢"]="撖澉噉敢橄瞰闞憨譀巖礹豃讝玁嚴儼曮釅",
["倝"]="乾漧倝榦韓鶾翰瀚螒雗斡",
["岡"]="岡崗剛掆鋼綱犅堈",
["羔"]="羔糕餻窯溔",
["高"]="高膏篙稿稾槁縞暠槀藁燺薧犒蒿藃豪毫壕濠鎬滈鄗鰝薃搞敲髇嗃巐歊謞熇碻塙毃翯瀥",
["皋"]="睾皋",
["皐"]="臯橰鷱翱翺噑獋暭",
["夰"]="夰昦",
["杲"]="杲菒",
["告"]="造慥艁糙告誥郜祰靠浩晧皓窖簉梏硞牿鵠酷焅嚳頶",
["戈"]="戕戈划",
["革"]="革諽緙鞂",
["鬲"]="隔膈搹鬲槅嗝虉翮滆蒚鎘",
["各"]="髂路輅賂虂露潞鷺璐簬簵洛駱絡酪烙雒珞硌袼笿鉻鮥鵅挌落各胳閣格擱恪愙貉狢佫略茖骼觡蛒敋客喀揢額峉頟詻垎楁",
["艮"]="艱齦眼蛝限硍豤詪根跟珢艮茛墾懇垠泿痕拫鞎很恨銀檭",
["亘"]="烜貆峘狟絙荁宣咺暅桓洹揎愃瑄渲喧萱暄諠翧蝖垣楦",
["庚"]="唐煻糖搪蓎瑭餹篖螗塘鶶赯磄溏傏鎕榶嵣庚賡鹒康穅糠槺漮躿慷",
["耿"]="耿褧",
["工"]="缸篢贑涳江肛扛杠豇茳釭矼玒虹腔崆羫控椌悾跫啌谾舡缻項屸功工疘魟攻愩碽貢羾空箜硿埪鵼倥鞚叿嗊訌紅仜葒渱鴻汞澒鞏巩銎恐蛩筇桏邛",
["弓"]="弓穹焪芎",
["公"]="慃袞蔉滾忩鬆凇公蚣翁螉蓊嗡滃暡勜塕瓮崧菘硹淞蜙倯松訟頌忪彸衳伀妐炂鈆",
["厷"]="肱宏閎吰竑谹翃紘鈜浤耾雄",
["躬"]="躬匑躳窮藭",
["共"]="港巷衖鬨烘洪鉷谼葓哄蕻恭供龔珙共髸拱拲蛬栱輁舼輂",
["冓"]="溝篝鞲韝褠冓構購媾覯遘煹搆講耩傋斠",
["古"]="箇個居橭嫴姑辜酤蛄鴣沽盬古罟估鈷詁牯故固稒痼錮鯝棝凅枯軲跍骷苦葫餬瑚湖鶘猢醐糊箶蝴胡瓳怙祜岵婟楛据裾琚椐鶋蜛崌涺腒鋸倨踞涸",
["谷"]="螸裕容溶鎔蓉傛褣搈瑢嵱熔榕塎谷唂焀俗欲浴鵒峪鋊輍慾",
["股"]="股羖",
["骨"]="顝滑猾磆螖鶻嗗骨縎愲蓇尳榾搰核",
["鼓"]="皷鼓瞽",
["瓜"]="瓜坬窊畖搲攨孤苽胍泒呱觚柧罛軱菰箛狐弧",
["冎"]="碢腡過渦鍋楇瘑緺堝濄鐹薖簻禍窩萵檛膼蝸媧騧冎剮諣歄咼喎",
["枴"]="箉拐枴",
["夬"]="袂夬快駃筷炔觖缺蒛吷妜決訣抉玦芵赽鴂鈌决疦蚗趹",
["官"]="官棺倌悹涫管琯痯輨錧館婠悺逭菅綰捾",
["毌"]="毌貫樌鏆遦慣摜瑻謴",
["雚"]="觀灌鸛瓘矔懽爟雚罐鑵歡驩貛酄獾讙嚾權顴孉蠸虇勸",
["光"]="光灮洸桄胱垙輄恍茪侊晃幌榥滉觥駫",
["圭"]="街鞋娾佳鮭涯崖啀厓捱睚娃洼哇胿溎觟黊蘳蛙窪卦挂掛詿罣絓鼃圭珪邽閨袿窐茥桂筀奎刲蝰楏睳畦眭烓跬恚硅",
["規"]="嫢規槻鬹摫瞡窺闚",
["龜"]="鬮龜",
["巂"]="孈纗攜蠵鑴驨酅雟讗觽儶觿欈瓗携",
["癸"]="睽湀聧藈癸葵鄈楑戣鍨騤揆猤闋",
["鬼"]="謉瑰傀藱魁磈塊嵬隗槐瘣廆溾螝褢騩愧媿聭餽鬼魏巍犩",
["簋"]="簋孂",
["貴"]="藬僓頹穨蘈隤墤尵蹪瀢鞼膭憒靧潰繢嬇殨闠簣嘳蕢聵樻䙡匱臾饋櫃遺壝瞶貴",
["郭"]="郭埻崞椁槨噋廓鞹漷霩",
["果"]="裸躶果菓猓輠裹蜾惈粿窠稞課髁棵顆堁敤夥婐踝祼",
["亥"]="該垓賅陔豥荄郂姟絯晐峐侅胲頦奒輆欬硋閡咳孩亥劾痎烗骸駭刻餩核",
["害"]="轄害犗幰攇憲瀗割磍瞎鎋鶷縖豁",
["函"]="蜬函涵顄圅菡",
["寒"]="寒蹇謇搴藆騫褰攐鑳寋鶱",
["厂"]="厂屵",
["暵"]="儺臡嘆灘擹攤癱歎難暵熯漢戁",
["好"]="薅好",
["号"]="饕號号枵呺鴞",
["昊"]="昊顥灝",
["禾"]="科萪蝌犐禾和咊盉龢鉌俰",
["合"]="龕媕頜答畣荅嗒褡撘搭榙佮蛤鴿合郃匌鉿閤鞈韐鮯哈姶欱盒耠詥塔鞳劄跲袷餄恰帢洽祫珨烚歙湁拾給翕噏潝熻嬆翖闟",
["曷"]="愒馤靄餲喝揭偈猲葛輵獦渴嵑暍曷褐鶡毼蝎鞨遏齃堨擖楬朅藒竭碣羯鍻歇蠍謁",
["盍"]="蓋盖溘搕嗑榼磕瞌盍闔篕熆鰪廅圔饁",
["寉"]="鶴髉篧榷搉傕確寉蒮",
["黑"]="嚜嫼墨默纆黑潶",
["亨"]="亨享烹脝悙哼",
["亙"]="搄縆絙亙堩恆峘",
["弘"]="弘鞃苰泓宖紭",
["侯"]="緱侯喉猴瘊篌糇帿睺矦餱翭葔鄇鍭鯸候堠",
["后"]="垢詬茩姤銗骺后垕郈逅鲘缿",
["乎"]="罅呼軤苸嘑歑謼滹乎虖嫭",
["虍"]="唬戲巇隵嚱盧鑪壚籚蘆顱髗鱸攎櫨轤黸獹鸕艫纑瀘瓐爐嚧矑罏蠦虜擄艣鐪虖虍雐虎琥萀臚廬驢藘爈櫖儢慮勴鑢濾攄處豦據鐻澽虛墟懅蘧籧醵璩虡遽勮噓驉歔魖膚虧噱臄劇諕",
["互"]="魱互冱枑",
["戸"]="妒雇顧戽戶扈帍昈滬簄齭所",
["華"]="鞾華譁樺嘩驊鏵崋",
["化"]="訛吪鈋囮魤靴貨花化杹匕",
["畫"]="畫澅繣劃嫿",
["褱"]="瓌櫰褱懷瀤壞蘾",
["萈"]="寬髖萈臗",
["奐"]="渙瘓喚煥奐寏換",
["患"]="漶贃患槵",
["黃"]="僙廣鄺懬爌曠矌壙纊黃璜潢簧癀鱑獚蟥趪櫎擴瀇獷觵礦鑛穬橫黌鐄嚝彉彍",
["灰"]="脄盔詼恢灰",
["隓"]="鬌隋墮媠橢嫷毻堶憜惰嶞髓瀡隨隓隳撱",
["回"]="回徊茴洄迴佪蛔",
["毀"]="毀燬檓毇譭",
["卉"]="卉芔泋",
["彗"]="櫘彗篲鏏槥轊熭嘒暳嚖慧蔧雪",
["惠"]="韢繐憓潓惠蟪蕙橞譓鏸穗",
["會"]="譮會儈膾鱠襘禬檜旝澮鄶廥鬠劊獪繪懀薈嬒瞺噲璯",
["昏"]="鍲抿緡捪湣昏婚閽惛棔殙",
["圂"]="溷慁圂",
["火"]="火邩伙",
["或"]="幗慖簂蔮國或惑蜮閾緎淢域罭棫琙魊馘聝膕摑漍嘓蟈掝彧稶",
["蒦"]="檴鱯護頀濩韄擭瓁劐穫鑊矆鸌雘蠖臒嬳彠嚄獲",
["霍"]="靃臛籗霍藿攉矐瀖癨",
["毄"]="繫檕轚蘻罊蟿礊擊墼",
["及"]="趿鈒靸馺砐魥扱笈衱极昅雭急級汲芨伋彶及岌吸",
["吉"]="秸髻夡鴶袺頡劼硈咭黠聐趌結拮桔狤擷襭翓纈吉郆詰蛣佶鮚姞欯恄",
["即"]="卪癤幯節蝍蠞堲唧楖揤櫛瀄擳即鯽",
["茍"]="亟茍殛極",
["疾"]="嫉疾蒺愱槉",
["棘"]="棘襋蕀",
["集"]="雜磼囃鏶穕潗集",
["几"]="肌飢机虮几麂",
["己"]="改跽己紀妀記杞屺芑邔玘忌鵋誋梞",
["丮"]="丮谻",
["脊"]="脊蹐鶺塉瘠膌",
["幾"]="機譏嘰磯鞿饑禨幾鐖僟璣蟣穖畿",
["戟"]="戟撠",
["旡"]="溉槩概摡慨嘅穊暨鱀塈既旡蔇炁櫭曁",
["季"]="季悸痵",
["祭"]="蔡瘵祭際穄鰶傺摖擦攃礤察詧",
["冀"]="冀驥懻",
["罽"]="罽瀱蘮",
["繼"]="繼檵",
["加"]="妿賀迦茄伽枷嘉加笳痂袈跏珈鴐架駕瘸",
["夾"]="瘞瘱脥浹挾夾郟筴裌鵊狹峽硤陜陿翜梜頰莢鋏蛺唊悏匧愜篋俠綊",
["家"]="家傢幏稼嫁",
["戛"]="戛嘎",
["甲"]="笚閘鉀甲胛岬押玾呷匣狎柙翈炠鴨庘",
["叚"]="葭麚瘕豭貑猳犌假嘏婽椵叚蝦煆霞遐瑕鰕碬蕸赮鍜騢暇",
["戔"]="殘戔帴虥盞醆琖剗棧輚嶘錢俴籛淺濺踐諓餞賤綫箋牋",
["肩"]="顅掮肩猏菺鵳",
["姦"]="姦葌",
["兼"]="譧賺尲鹻歉槏鰜稴豏甉廉鎌鬑鐮簾薕蠊嬚溓嵰隒熑濂燫兼縑鶼蒹搛謙膁嗛慊傔馦嫌鼸尷",
["監"]="藍襤籃懢儖蘫攬欖覽爁擥灠濫嚂纜監礛鑑艦檻壏轞鹽",
["柬"]="蘭攔欄瀾讕闌韊幱籣躝爛爤鑭糷斕揀柬暕諫練鍊煉楝萰堜湅",
["繭"]="襺繭蠒",
["幵"]="笄枅蚈羿栞雃詽揅幵鳽豣麉趼汧岍研妍訮鈃",
["見"]="涀莧粯晛筧絸垷挸見俔蜆硯峴哯睍現",
["建"]="犍騝鍵揵鞬腱湕建旔楗健",
["閒"]="間簡澗鐧蕑覵襉僩鬜癇瞯憪橌撊",
["薦"]="瀳韉薦",
["畺"]="畺畕礓繮韁橿薑疆壃僵彊麠",
["夅"]="贛灨竷戇洚降絳栙夅跭隆癃窿霳",
["交"]="烄礮交郊茭蛟鮫鵁咬鉸佼詨絞狡姣筊校珓跤頝骹齩洨胶效効傚皎恔晈窔駮",
["焦"]="癄焦蕉鷦僬噍膲鐎蟭燋醮潐趭樵譙憔劁顦嶕撨穛",
["角"]="捔桷角埆确斛槲蔛",
["敫"]="礉邀憿徼儌璬皦繳敫譥激噭獥鸄墽竅覈燩檄薂",
["皆"]="皆偕階喈稭堦楷鶛湝蝔鍇揩諧瑎龤",
["孑"]="孑𨥂",
["劫"]="劫蜐",
["疌"]="疀萐箑啑睫婕緁捷疌寁倢崨踕誱徢蜨",
["桀"]="桀傑榤嵥滐搩謋",
["截"]="巀截蠽",
["解"]="薢解檞懈繲廨蟹獬澥嶰邂",
["丯"]="夆丯瘛觢契栔禊稧楔葜猰窫揳偰潔絜鍥挈蛪齧褉喫",
["介"]="妎芥尬界介疥玠砎魪价衸齘骱扴圿忦",
["戒"]="祴駴戒誡悈械裓",
["巾"]="巾帘",
["今"]="貪嗿僋酓馠谽唅含肣頷筨梒鋡莟琀浛盦韽玪妗欦黔鈐鳹雂念梣枔岑笒涔侺今黅衿衾坅搇琴禽芩庈耹靲擒檎紟吟訡廞陰𩃬飲蔭廕矜",
["斤"]="蘄祈頎旂圻蚚岓玂沂掀釿齗听垽斤菦靳劤勁赾芹近斦欣忻昕訢炘邤庍焮",
["金"]="淦顉金錦欽菳嶔撳捦鵭唫鈙崟趛",
["兓"]="簪撍鐕憯噆朁蠶蚕糣熸鬵灊潛僭兓嶜兂譖",
["津"]="葏津",
["堇"]="囏螼堇墐𥎊僅瑾覲殣饉廑瘽歏鄞謹槿漌慬勤懃懄",
["盡"]="濜盡賮燼藎壗璶",
["晉"]="鄑戩榗晉縉搢瑨",
["進"]="璡進",
["坙"]="羥俓牼硁誙娙莖痙",
["巠"]="葝頸勁鑋輕涇經鵛巠剄烴徑逕桱蛵陘鋞脛踁",
["京"]="涼凉椋綡輬諒掠弶京麖鶁景鯨黥勍剠倞影璟憬暻澋",
["兢"]="兢殑",
["井"]="硎耕荊井丼妌穽阱汫刑形邢鉶型侀",
["竟"]="滰境竟鏡獍樈璄摬",
["敬"]="蟼憼驚警儆璥檠敬擎曔",
["誩"]="誩競",
["冋"]="苘詗駉坰冋絅扃炯迥泂",
["臦"]="臩臦",
["丩"]="艽芁荍叫訆嘂收糾朻丩赳虬虯觓",
["九"]="尻訄艽虓鳩勼九究仇鼽犰叴厹頄朹馗扏訅釚肍軌宄氿匭泦旭",
["久"]="畂久灸玖疚柩羑",
["臼"]="臼齨桕舅舊匶閰",
["咎"]="咎鼛櫜倃綹麔晷厬",
["就"]="僦就鷲殧噈蹴",
["廄"]="廄匓",
["匊"]="菊鞠掬匊鵴椈諊踘趜蘜鞫巈麴毱驧",
["局"]="鋦挶梮局跼侷",
["狊"]="瞁湨郹狊鶪鼳犑闃",
["巨"]="柜渠磲蕖蟝璖巨距拒苣炬詎鉅秬粔駏歫矩榘",
["句"]="勾鉤鴝句夠枸軥狗苟岣笱玽耇豿敂怐雊竘齁呴蚼豞訽佝拘駒眗跔鮈痀蒟絇邭劬朐鼩斪翑葋姁欨喣煦昫",
["具"]="俱椇具埧颶",
["瞿"]="瞿衢氍癯蠷臞欋鸜灈躣忂戵鑺懼",
["龹"]="潫豢勬韏眷桊絭觠飬弮拳卷齤券",
["卷"]="腃卷菤捲埢睠弮犈棬鬈圈蜷踡婘蔨倦淃綣裷",
["雋"]="隽檇臇嶲鐫雋儁寯",
["欮"]="鱖蹶厥欮蕨劂瘚蟨蟩橜撅闕橛鷢蹷憠",
["絕"]="蕝脃脆絕",
["爵"]="釂皭爝爵嚼穱灂",
["矍"]="钁攫矍躩玃貜彏戄籰",
["君"]="涒麏莙輑蜠峮窘僒君桾宭鮶捃群羣裙裠帬焄郡",
["軍"]="揮輝翬暉煇楎瀈韗褌鶤緷睴璭瘒顐諢餫渾琿鼲堚鯶軍皸齳喗葷惲賱運暈鄆",
["侃"]="諐侃偘",
["亢"]="笐亢牨苀魧迒邟忼骯抗炕伉閌犺蚢砊阬頏航杭沆肮吭秔坑劥",
["丂"]="丂攷考烤拷栲洘銬巧朽",
["殻"]="彀鷇瞉豰瑴愨殼觳嗀穀糓轂榖瀔縠",
["可"]="砢柯菏牁滒哥歌謌鴚哿舸笴軻珂可岢坷蚵呵訶抲荷何河苛魺袔阿妸疴鈳娿痾跒閜",
["克"]="克剋勊",
["肯"]="肯肎",
["孔"]="吼孔",
["口"]="口叩扣釦筘",
["寇"]="滱寇簆蔻",
["圣"]="怪恠圣",
["凷"]="凷屆",
["款"]="款欵歀窾",
["夔"]="夔犪躨",
["昆"]="昆琨鯤菎崐蜫惃錕猑騉緄輥棍餛混焜倱掍箟",
["困"]="硱梱捆綑閫悃稛裍齫困涃",
["桰"]="咶話刮鴰趏舌姡頢括活适栝佸髺聒銛葀懖筈萿蛞闊秳",
["剌"]="莿鵣辣辢剌揧瘌楋蝲",
["來"]="來萊淶徠崍郲騋猍鯠鶆庲棶斄逨麳唻賚睞勑誺倈憖猌麥",
["賴"]="賴籟癩瀨藾攋懶嬾獺",
["牢"]="牢窂哰",
["勞"]="勞撈嘮癆簩蟧憥澇僗髝膋犖",
["老"]="老姥栳恅",
["樂"]="樂轢濼躒擽爍鑠藥櫟纅礫瓅皪觻嚛",
["畾"]="摞騾螺蔂虆瘰礌雷擂攂礧儡瓃櫑鑘鐳畾轠罍蕾癗磥累儽樏鸓嫘纍欙纝壘櫐蘽藟讄",
["厽"]="絫厽垒",
["耒"]="頛郲耒銇洡誄",
["磊"]="藞磊",
["頪"]="蘱纇類頪禷",
["离"]="謧璃離縭离醨樆褵漓黐籬灕蘺攡魑螭摛",
["釐"]="犛釐嫠剺孷漦氂",
["蠡"]="盠欚劙",
["里"]="埋霾薶榸厘狸貍梩艃里理鯉俚悝娌裏荲",
["豊"]="體軆禮澧醴豊鱧",
["力"]="勒肋扐仂艻朸玏泐阞竻力屴鳨",
["立"]="涖莅蒞位拉菈颯立粒笠砬岦苙鴗雴泣",
["利"]="犁黎莉黧鯬筣蔾藜邌瓈鵹棃梨蟍蜊鑗利痢浰",
["丽"]="灑籭躧曬廲驪鱺欐丽麗儷鸝酈孋矖穲邐彲釃襹纚",
["戾"]="錑戻戾綟唳蜧捩悷淚",
["隶"]="曃埭逮靆齂棣隸肆蕼肂殔隶",
["栗"]="栗溧慄鷅凓篥麜瑮塛搮",
["秝"]="秝歷曆磿靂癧櫪藶瀝攊厤",
["連"]="連漣鰱翴鏈璉僆摙蓮嗹縺",
["良"]="剆郎稂桹鋃硠浪蜋琅狼欴踉莨艆駺躴筤閬哴蓈廊榔螂瑯朗朖誏俍崀埌蒗娘良粮悢",
["兩"]="倆兩脼緉蜽魎輛",
["量"]="糧量",
["了"]="釕了",
["料"]="料炓",
["尞"]="簝潦獠轑橑燎繚璙憭爒僚嫽療膫鷯遼撩嘹寮飉竂鐐蟟瞭嶚镽",
["列"]="例鮤栵洌銐峛列裂烈冽迾蛚茢颲鴷挒姴",
["巤"]="鑞蠟邋臘臈擸擖獵躐鬣儠犣鱲巤獦",
["林"]="婪惏啉漤醂林琳淋痳箖霖菻罧棽綝郴禁襟僸澿噤凚齽",
["粦"]="潾膦憐燐鄰轔嶙粼磷疄麟鱗璘翷瞵獜驎鏻繗僯橉撛遴粦甐",
["吝"]="麐吝悋",
["閵"]="藺閵躪轥",
["夌"]="棱稜輘倰祾踜凌菱陵綾鯪淩夌蔆崚掕𩜁庱睖",
["霝"]="靈霛麢霝醽櫺蘦孁龗酃爧欞",
["令"]="令怜零魿命冷跉嶺領阾柃袊詅旍舲齡囹鴒蛉鈴苓伶泠瓴拎刢玲聆竛軨笭翎閝鹷昤駖彾呤狑秢岭紷砱羚姈蕶澪",
["流"]="流硫琉旒瑬裗",
["六"]="六圥",
["翏"]="醪嫪嘐賿顟膠轇摎髎熮廖憀豂漻翏寥蓼鄝鷚繆勠疁飂鏐僇瘳璆謬蟉樛磟戮剹穋",
["龍"]="驡龐瀧籠豅朧龓聾礱嚨蘢櫳襱瓏曨鸗蠪巃竉攏儱龍躘隴壠壟寵龏",
["婁"]="樓摟耬僂婁螻髏蔞鞻艛膢廔嘍瞜簍慺鷜褸遱謱嶁甊塿漊鏤瘻擻藪籔氀縷屢數屨窶貗",
["屚"]="漏瘺屚",
["鹵"]="滷鹵樐蓾鏀",
["魯"]="魯櫓穭",
["圥"]="灶竈鼀",
["坴"]="睦坴陸稑鵱鯥淕踛",
["彔"]="剝龣祿淥睩琭箓碌盝娽彔趢簶綠逯錄醁騄菉籙",
["鹿"]="鄜鹿漉轆簏螰麓騼摝廘蔍鏕",
["呂"]="櫚閭吕侣梠郘絽莒筥",
["寽"]="酹脟捋蛶埒鋝浖哷寽",
["旅"]="玈旅膂祣",
["率"]="率繂膟蟀",
["䜌"]="蠻矕變鸞巒灤欒鑾臠羉䜌灓圝曫孿彎灣攣孌戀㪻",
["卵"]="卵關関聯",
["亂"]="覼亂薍",
["侖"]="蜦綸論崘掄菕惀睔碖溣倫淪輪侖陯鯩棆踚耣錀",
["羅"]="羅蘿籮儸饠欏囉鑼邏",
["蠃"]="驘鸁蠃臝羸",
["麻"]="麻痲犘縻醿爢蘼靡摩魔磨劘麽麼塺麾",
["馬"]="馬碼罵鷌鰢瑪榪傌禡媽",
["買"]="買蕒嘪鷶賣",
["曼"]="獌鬘謾慢嫚縵曼饅蔓鰻墁鏝槾漫幔蟃",
["尨"]="厖駹狵尨浝哤牻娏蛖𥆙痝𪁪",
["茻"]="莽茻",
["莽"]="蟒漭",
["毛"]="毛髦旄芼氂枆酕牦耄覒眊耗秏毣",
["矛"]="堥蓩猱茅罞蝥鶜瞀愗袤雺懋楙矛蟊髳鍪鞪糅腬柔揉蹂鍒騥蝚葇鞣鰇瑈鶔楺煣輮韖敄務鶩騖婺霚嵍霧霿楘",
["卯"]="卯昴泖茆奅窌聊貿鄮劉留蒥鶹騮榴瑠瘤遛鎦飀鰡嵧餾瀏懰柳珋罶嬼溜霤廇塯",
["冃"]="媢冃冒帽瑁賵艒萺勗勖",
["皃"]="貌皃藐邈",
["眉"]="眉楣嵋湄鶥瑂郿葿媚篃蝞煝",
["美"]="美渼媄",
["門"]="蔄闅閩蕄門捫悶聞閿問",
["冡"]="蒙冡濛艨朦矇饛檬鸏幪蠓靀曚",
["夢"]="蘉瞢懜懵甍薨夢鄸",
["米"]="謎迷米眯洣蔝麊冞糜敉侎麋蘪攗",
["羋"]="羋蝆",
["冖"]="冟冖冪羃濗鼏",
["覓"]="覓漞",
["芇"]="芇蹣瞞顢樠慲鬗滿鏋懣璊",
["綿"]="綿棉緜矊",
["臱"]="臱矏櫋籩邊",
["丏"]="汅沔丏眄麪",
["免"]="免娩勉俛鮸冕絻婏浼鞔悗嬎晚挽輓脕莬",
["面"]="蝒緬湎愐偭勔面麵",
["苗"]="貓描媌緢苗猫庿",
["蔑"]="礣蔑篾蠛懱幭鱴衊瀎襪韤韈",
["民"]="眠民泯怋岷珉罠笢鈱抿刡敃愍暋蟁",
["皿"]="猛艋蜢皿孟",
["閔"]="憫閔潣簢燘",
["黽"]="澠黽僶繩譝憴鱦蠅鄳鼆",
["名"]="名洺眳詺銘茗酩姳",
["明"]="莔明盟萌奣",
["冥"]="瞑嫇慏冥榠鄍溟螟猽蓂覭暝幎幂塓",
["末"]="眛沬佅韎帓粖末昩秣靺眜抹妺怽沫袜",
["沒"]="沒歿莈",
["莫"]="蟆鬕饃模摸嫫謨膜摹暮慕募墓慔莫寞漠幕鏌瘼鄚瞙嗼塻貘驀",
["牟"]="牟眸侔蛑麰劺恈鴾",
["某"]="某謀媒煤腜禖",
["母"]="脢挴海毐呣拇母胟姆畮踇苺莓敏鰵慜毋梅酶鋂每毎痗晦誨悔霉",
["畝"]="畝畞",
["姥"]="姥峔",
["目"]="瞐苜目",
["木"]="木沐朷蚞霂",
["乃"]="乃鼐疓奶仍艿辸礽扔芿",
["奈"]="奈柰渿捺",
["南"]="南楠腩湳揇萳罱喃諵",
["夒"]="獿犪擾",
["匘"]="腦惱瑙匘碯硇𣳦",
["內"]="妠內笍芮枘汭鈉蜹抐納衲魶軜蒳豽肭吶焫訥",
["能"]="態能螚褦",
["尼"]="泥屔苨坭迡埿旎狔抳尼呢怩柅蚭跜秜痆昵",
["孴"]="孨孴",
["屰"]="斥訴泝愬遡塑溯遻柝鶚諤齶鍔遌崿偔堮湂鱷噩咢鑩鄂鰐萼愕顎朔槊蒴坼拆逆屰縌蝷",
["匿"]="暱慝匿",
["輦"]="輦鄻攆",
["念"]="淰唸埝趝諗稔棯腍捻錜敜惗菍",
["鳥"]="島搗鵃瞗鳥蔦窵",
["臬"]="甈臬嵲臲闑",
["聶"]="攝聶鑷躡襵懾囁欇灄顳讘",
["寧"]="濘獰檸儜薴鬡鑏嬣寍寗甯寧鸋嚀聹",
["農"]="嶩噥鬞擃齈癑農辳憹膿儂濃醲襛穠檂",
["弄"]="筭弄梇哢硦",
["女"]="拿拏詉蒘笯挐絮呶怓帑奴砮駑孥努弩怒袽帤女籹恕如茹洳鴽蕠汝肗",
["虐"]="虐瘧謔",
["派"]="庍派脈脉衇眽覛霢",
["佩"]="佩珮",
["配"]="配嶏",
["朋"]="崩堋漰倗朋鵬棚鬅掤淜輣繃弸",
["彭"]="澎彭膨蟚蟛憉",
["皮"]="陂詖彼柀佊貱跛披鈹帔鮍耚狓翍旇秛紴皮疲被髲鞁骳波碆菠簸駊頗坡玻破婆蔢",
["匹"]="匹鴄",
["片"]="爿片沜",
["票"]="標鏢瘭幖熛蔈膘褾票飄漂螵瞟嫖旚僄慓彯摽翲嘌縹醥顠篻剽勡薸瓢鰾驃",
["頻"]="瀕頻顰蘋嚬",
["品"]="臨瀶品",
["甹"]="俜甹聘娉騁梬",
["平"]="平怦砰抨閛伻評坪苹枰泙蚲胓匉萍",
["咅"]="蓓倍菩掊剖婄踣箁咅部培犃瓿殕醅棓涪賠陪毰焙",
["菐"]="樸璞墣撲轐獛蹼襆濮纀醭僕菐穙鏷幞",
["普"]="譜普",
["七"]="切砌沏七柒叱",
["妻"]="妻萋淒凄悽鶈郪緀霋棲",
["桼"]="漆桼膝厀",
["其"]="基箕朞稘萁期諅其錤諆欺娸魌僛唭棋旗淇祺琪騏綦麒蜞綨蜝棊碁鯕踑藄櫀璂禥惎帺",
["奇"]="奇畸剞羇掎攲躸踦寄徛欹崎觭碕綺婍騎琦鵸錡輢齮犄猗椅旖陭檹漪倚",
["畁"]="畁萆",
["齊"]="儕麡齋穧擠躋齏齎櫅齌隮賷虀濟癠霽齊臍蠐懠薺鱭嚌劑齍",
["乞"]="乞盵齕紇籺圪訖吃扢疙屹仡迄釳忔汔矻麧",
["豈"]="獃剴凱愷塏鎧颽暟闓豈隑皚嵦敳磑螘覬溰顗",
["啓"]="啓啟棨綮晵闙启",
["气"]="愾忾刏汽氣气忥餼鎎熂霼犔",
["咠"]="楫檝湒咠葺緝諿輯箿蕺戢濈霵揖",
["牽"]="牽縴",
["僉"]="厱歛臉醶鹼薟匳獫蘞籢斂瀲撿羷殮簽僉憸譣檢瞼儉顩嬐嶮噞驗險劒劍𩏩",
["遷"]="遷櫏韆僊躚蹮",
["前"]="媊煎湔鬋剪翦譾揃箭葥前騚歬",
["虔"]="虔鰬榩",
["遣"]="遣繾譴",
["欠"]="坎扻砍芡欠杴鍁",
["爿"]="臧牂戕贓藏奘臟將漿鱂蔣螿槳獎醬鏘蹡嶈斨爿牆妝莊裝壯疒床牀狀",
["強"]="繈襁膙鏹勥強",
["牆"]="薔廧蘠嬙檣",
["喬"]="驕嬌憍穚鷮蕎喬簥撟矯鱎敽蟜譑蹻繑鞽趫嘺橋僑鐈毊轎嶠獢屩",
["且"]="虘蔖摣袓怚罝謯姐抯飷且趄笡查柤樝皻渣楂苴駔租蒩祖組珇靻粗徂殂麆伹蛆沮咀疽雎狙岨砠坥刞覰覷跙筯菹葅阻俎詛鉏豠鋤耡齟助",
["妾"]="霎翣帹接椄菨鯜妾淁踥",
["侵"]="鋟綅祲埐梫濅浸侵駸寢寑薓",
["秦"]="轃秦螓臻蓁搸溱榛",
["頃"]="顈傾頃穎潁熲",
["殸"]="殸聲謦磬罄馨",
["慶"]="慶羻",
["丘"]="丘邱蚯丠",
["秋"]="鍫篍愀湫揪揫啾鬏秋鰍楸秌鞦鶖萩甃醔愁僽",
["囚"]="鮂囚泅苬",
["求"]="救求球逑賕蛷莍殏梂俅脙絿銶毬捄觩",
["區"]="摳彄剾謳歐區鷗甌漚瞘嘔櫙膒蓲醧鏂毆塸貙樞驅軀嶇鰸敺傴嫗饇",
["取"]="陬棷掫緅趣取鯫棸鄹郰齱菆箃黀驟最襊蕞嘬諏娵娶聚埾藂叢繓撮",
["去"]="佉呿抾弆祛袪阹胠魼去麮鼁砝劫鉣蜐怯",
["全"]="拴栓跧痊詮荃銓筌輇硂佺駩絟恮峑全仝牷",
["泉"]="線泉葲灥",
["犬"]="猚畎汱甽犬",
["郤"]="腳脚卻却谻郤綌",
["囷"]="菌麇麕囷箘攈",
["肰"]="嘫然肰繎燃蹨橪撚",
["冉"]="那挪娜柟抩聃舑坍髯蚺呥冉袡苒姌",
["人"]="千仟芊阡汘迁杄人仁朲佞",
["壬"]="紝鵀拰賃任壬恁銋荏餁栠袵飪妊衽",
["刃"]="涊紉忍荵刃認仞軔韌肕牣杒訒",
["日"]="涅捏篞衵日馹",
["戎"]="娀戎茙駥狨絨",
["茸"]="茸髶搑榵穁",
["冗"]="宂冗",
["肉"]="朒肉",
["乳"]="啂乳",
["辱"]="耨槈鎒搙褥傉辱縟蓐鄏溽媷嗕",
["耎"]="陾腝堧稬愞煗餪渜偄瑌軟輭蝡碝耎緛媆",
["甤"]="甤蕤",
["睿"]="睿叡璿壡濬",
["閏"]="撋瞤潤閏",
["若"]="若婼惹諾蠚逽蹃箬楉鄀",
["弱"]="嫋弱溺蒻搦愵",
["塞"]="賽塞簺僿寨",
["三"]="三弎",
["散"]="散糤繖饊鏾潸霰撒",
["桑"]="桒桑磉顙嗓搡",
["掃"]="掃埽",
["色"]="色赩",
["嗇"]="濇嗇穡薔轖繬嬙懎",
["歰"]="澀歰澁",
["杀"]="閷鎩殺蔱摋煞榝刹",
["沙"]="硰娑挱挲桫渻沙紗砂鯊裟桬髿魦莎莏",
["彡"]="杉彡衫髟釤䑣彤浵肜",
["山"]="山疝邖汕訕仙秈苮屾",
["删"]="珊跚姗删栅",
["羴"]="羴羼",
["扇"]="扇煽傓",
["善"]="橏嫸善譱蟮鱔僐鄯墡磰繕膳饍",
["商"]="商蔏螪",
["上"]="丄上",
["尚"]="當襠鐺簹璫檔儅蟷擋黨讜欓譡闣瓽鏜闛鼞曭儻戃矘爣攩淌倘躺趟堂坣棠糛螳隚橖掌廠敞僘氅常嘗裳徜尚甞鋿嫦償賞瞠撐樘橕牚埫",
["勺"]="豹趵尥箹杓約扚釣瘹芍酌灼勺彴犳妁汋仢葯礿肑瓝的靮馰玓魡",
["舌"]="絬舌",
["射"]="謝榭射麝",
["設"]="設蔎",
["申"]="電胂申伸呻紳眒柛訷抻神朄",
["身"]="身銵",
["罙"]="探棎賝琛穼深",
["宷"]="宷審瀋嬸",
["昚"]="昚傄",
["甚"]="湛媅黮糂堪戡嵁歁勘磡墈偡碪椹揕踸鍖斟諶愖瘎煁甚葚",
["升"]="升昇陞抍",
["生"]="猜輤綪倩棈蒨篟生牲笙甥鉎珄鼪猩狌眚貹崝精菁鶄蜻鼱婧睛箐聙旌清圊請凊䝼靚情晴夝靜靖睲惺性姓靗鯖青靘掅胜曐星鮏腥鯹醒篂",
["省"]="省渻箵",
["聖"]="檉蟶聖聽廳",
["尸"]="杘尸鳲屍屎",
["失"]="眣蛈迭跌瓞昳胅軼苵镻詄泆趃柣抶秩帙紩袠翐妷失佚劮",
["師"]="篩師鰤蒒獅螄",
["濕"]="儑濕漯礘隰溼",
["十"]="針汁卙十什",
["石"]="柘妬蠹沰拓橐袥驝斫磭磔跖石碩祏鉐鼫",
["食"]="飤食蝕飾飭",
["史"]="吏倳剚事史使駛",
["矢"]="彘璏薙知蜘智鼅潪踟雉矢痴",
["士"]="茌士仕",
["氏"]="氏衹紙帋扺坁汦忯眂舐芪祇疧軝蚔",
["世"]="貰跇迣世泄枻詍靾袣抴呭勩屜鰈渫煠喋鞢殜葉弽韘枼楪揲鍱偞蝶牒蹀諜堞牃褋惵碟屧屟紲齛疶緤媟",
["示"]="示祁狋視眎沶",
["是"]="鞮隄堤鍉蝭醍緹提瑅題媞褆禔騠趧鯷踶徥匙是諟翨睼寔湜遈",
["筮"]="噬筮簭澨遾",
["奭"]="奭襫",
["手"]="杽手",
["守"]="守狩",
["首"]="道導首",
["受"]="受綬授",
["壽"]="禱擣檮壔懤濤燾翿幬儔譸籌疇躊燽薵魗醻壽璹鑄",
["嘼"]="獸嘼",
["殳"]="吺祋投骰酘坄殳杸",
["尗"]="椒茮踧蔋惄戚慼鏚慽磩寂嘁墄摵督錖裻傶蹙槭縬顣敊琡俶埱淑婌尗叔掓菽鮛",
["疋"]="疋蝑壻婿胥稰楈諝湑揟醑糈楚礎憷齼濋儊綀疎疏蔬梳",
["孰"]="熟孰塾",
["鼠"]="鼠癙",
["蜀"]="襡噣斣歜斀孎濁鐲鸀擉獨髑韣斸钃欘躅蠋燭囑矚屬属蠾觸臅觕蜀",
["朮"]="訹怵朮炢術述秫沭蒁",
["束"]="涑鏉摗駷嗽瘶欶漱捒悚竦娕梀速餗殐遬蔌樕藗誎束",
["庶"]="褯遮蔗鷓樜蟅嗻鍍度渡庶剫踱喥謶席蓆墌摭蹠",
["刷"]="涮刷",
["衰"]="蓑缞衰榱",
["雙"]="雙艭愯㩳",
["爽"]="摤磢漺傸鷞騻爽塽樉",
["水"]="沝水",
["厶"]="私厶",
["司"]="司伺覗笥詞祠柌嗣孠飼呞",
["虒"]="擨搋鷈謕嗁蹏鼶遞虒榹磃傂螔禠褫篪歋",
["斯"]="嘶撕斯廝蟴燍鐁凘澌",
["絲"]="絲蕬鷥",
["巳"]="巳祀汜戺起熙媐圯异巸",
["四"]="訵四泗駟柶牭呬",
["寺"]="等待寺持峙痔跱歭洔畤秲庤時鰣塒蒔榯恃侍詩邿特",
["宋"]="浨俕宋",
["叟"]="㮴艘嫂瞍謏叜叟傁廋醙遚餿搜颼溲鎪獀鄋螋騪瘦",
["夙"]="洬夙",
["素"]="素傃嗉膆塐",
["宿"]="宿蓿縮樎蹜摍",
["粟"]="粟憟",
["肅"]="簫蕭瀟潚彇蟰橚嘯歗熽繡鏽肅鷫驌鱐璛",
["祘"]="蒜祘",
["算"]="纂繤算匴篹篡攥",
["遂"]="隊邃譢遂隧燧襚旞璲檖澻鐆鐩穟墜",
["歲"]="噦翽鐬濊歲劌顪穢薉獩饖奯",
["孫"]="孫猻蓀搎遜愻",
["飧"]="飧蕵",
["隼"]="隼榫准準",
["索"]="索溹",
["惢"]="惢蕊蘂繠",
["瑣"]="鎖瑣溑",
["它"]="佗拕它蛇袉鴕駝紽鮀陀沱跎詑酡迱舵柁鉈",
["蹋"]="傝搨蹹榻遢鰨毾塌溻蹋躢闒",
["譶"]="誩譶霅",
["台"]="胎台邰鮐孡苔抬駘炱菭跆殆怠迨紿詒軩咍佁枲辝鈶笞齝眙治始怡貽飴瓵珆",
["臺"]="懛儓臺擡薹嬯檯籉",
["覃"]="鷣撢憛潭譚覃鐔藫橝蟫燂醰禫嘾贉瞫蕈鱏簟驔磹",
["曇"]="曇壜",
["炭"]="炭湠",
["弢"]="弢詜",
["匋"]="掏蜪陶萄啕淘騊匋祹綯",
["替"]="替暜僣",
["天"]="添忝悿舔菾天吞祆蚕",
["田"]="田佃畋畇鈿沺甸",
["甜"]="銛栝餂恬湉甜狧",
["廷"]="蜓涏綎珽脡侹頲廷庭鼮莛筳霆娗挺艇鋌梃誔",
["同"]="鮦侗恫痌同仝銅桐峒硐筒洞烔挏酮鲖眮衕哃絧姛詷胴駧迵戙",
["禿"]="禿鵚",
["突"]="突宊揬鼵葖鶟堗",
["圖"]="啚圖",
["土"]="社肚土吐芏杜靯荰",
["兔"]="菟兔鵵梚",
["退"]="腿退褪",
["豚"]="豚遯",
["妥"]="妥鵎挼骽脮餒浽娞鮾綏荽鋖桵",
["瓦"]="瓦邷",
["丸"]="骫丸紈芄汍肒",
["萬"]="糲犡噧邁勱蠆厲勵礪蠣蠇癘濿櫔巁贎萬",
["亡"]="忙芒茫恾吂汒朚邙杗荒肓衁巟詤慌謊喪亡望莣朢鋩硭忘网罔蛧網輞棢惘菵誷魍妄盲蝱虻氓甿",
["王"]="皇惶遑堭煌餭騜艎隍湟徨篁蝗凰偟媓韹葟皝汪尪迋逛誑匡筐框眶誆邼恇劻洭軭狂軖鵟俇王蚟彺旺諻喤瑝鍠揘",
["往"]="暀誑枉往",
["危"]="鮠峞頠詭垝陒觤恑蛫祪洈姽桅佹跪危峗硊卼",
["威"]="崴威葳隇蝛鰄楲",
["微"]="矀覹黴薇媺微溦癓徽幑",
["韋"]="禕郼褘諱媁幃違圍韋闈湋鍏潿韙煒暐偉瑋葦椲韡愇緯",
["唯"]="匯擓淮睢嶉趡踓雖濉倠婎蓶維惟唯濰琟鷕帷鵻",
["爲"]="譌嬀潙蟡僞噅撝爲鄬蔿儰寪蘤",
["尾"]="娓尾浘",
["委"]="矮躷捼倭涹踒餧諉痿萎逶覣蜲委緌",
["未"]="寐魅鬽昧妹眛沬未味鮇菋",
["畏"]="煨偎隈椳鰃渨葨揋猥嵔鍡碨腲餵畏",
["胃"]="喟胃謂媦蝟渭煟緭",
["尉"]="尉熨慰罻犚蔚螱褽鳚",
["衛"]="衞讏躛",
["𥁕"]="媼蝹溫𥁕轀薀殟豱縕韞瘟搵氳熅馧蒕蘊褞醞慍膃榲嗢",
["文"]="旻旼汶忞盿枚玫玟文紋蚊雯彣馼鳼鼤抆紊",
["我"]="莪哦娥峨峩鵝俄蛾睋涐誐硪我騀餓義儀轙檥蟻艤礒羛議犧曦羲",
["巫"]="巫誣莁鵐",
["屋"]="握幄偓渥齷楃喔腛媉屋剭",
["烏"]="烏嗚鰞歍鎢瑦鄔螐隖塢溩",
["吳"]="吳蜈鋘麌祦誤悞娛虞澞鸆俁噳",
["無"]="墲橅膴幠鄦撫無瞴蕪璑鷡譕憮舞廡嫵甒潕儛",
["五"]="吾衙鼯浯珸郚齬鯃娪梧峿五伍寤啎晤悟逜窹捂鋙語圄敔",
["午"]="卸汻滸午仵旿忤迕杵籞蘌篽禦許御",
["武"]="賦陚武鵡珷碔娬",
["兀"]="虺豗鼿虫髡軏扤兀杌屼矹",
["勿"]="脗吻刎伆勽沕忽昒惚匫笏曶淴物勿芴岉",
["戊"]="茂戊",
["夕"]="夕汐穸",
["兮"]="盻兮枍",
["西"]="粞洒西卤卥栖茜哂",
["希"]="桸郗絺脪瓻希晞莃鵗稀睎豨悕俙欷浠唏",
["昔"]="借唶藉躤蜡齰醋錯措厝逪剒鵲碏踖趞皵舄斮簎諎矠庴籍耤猎昔惜腊焟棤",
["析"]="菥蜤析皙蜥淅惁晰",
["屖"]="厗屖遟謘稺",
["息"]="息熄媳鄎瘜蒠",
["悉"]="僁悉蟋窸",
["犀"]="摨犀遲墀",
["醯"]="橀醯",
["奚"]="謑膎鞵榽鷄雞谿嵠溪磎螇鸂奚豯徯蹊騱傒鼷蒵貕慀",
["習"]="摺磖褶慴霫習鰼騽飁槢熠",
["徙"]="簁徙褷屣縰蓰",
["喜"]="嘻嬉禧熹僖歖譆瞦喜憙蟢糦饎",
["系"]="係系",
["舄"]="寫瀉舄獡潟磶蕮",
["隙"]="虩隙",
["下"]="丅下芐",
["夏"]="榎廈嗄夏",
["先"]="洗先銑跣毨姺筅冼箲駪兟詵侁",
["鮮"]="鮮廯癬蘚",
["咸"]="感鱤顑轗撼憾喊緘瑊黬減羬麙咸鹹醎諴椷輱鍼箴葴鱵",
["閑"]="閑嫻鷴",
["顯"]="顯韅",
["臽"]="萏窞惂輡錎埳臽淊欿蜭啗鵮餡陷爓諂讇閻焰壛櫩燄掐",
["縣"]="縣懸",
["相"]="相廂湘緗箱葙想霜孀鸘驦",
["香"]="香萫",
["鄉"]="曏鄉薌膷響饗蠁嚮卿",
["襄"]="囊蠰曩灢儾孃瓤鑲釀瓖驤襄纕忀欀饟攘禳穰躟瀼獽儴蘘鬤勷壤嚷讓懹攮",
["向"]="晌餉向珦",
["象"]="潒像象橡蟓襐勨鱌嶑",
["梟"]="梟蟂鄡",
["囂"]="囂虈",
["小"]="觘抄鈔訬炒吵耖仯眇渺杪秒篎妙玅釥小少麨尠",
["肖"]="梢捎筲鞘艄蛸髾輎旓弰鮹綃颵莦娋稍哨揱潲睄悄俏峭陗帩誚消宵霄硝銷逍魈痟焇肖韒趙箾踃削矟",
["孝"]="酵窙哮庨涍孝",
["劦"]="栛珕荔脅姭搚拹愶嗋熁協勰劦叶",
["韰"]="瀣韰薤",
["燮"]="燮躞",
["心"]="沁吣心杺伈",
["辛"]="親寴儭瀙辛新薪襯櫬嚫莘騂垶觪",
["囟"]="崽偲腮鰓揌顋愢毢諰細思緦罳禗楒葸恖颸囟顖毸",
["釁"]="釁舋亹斖虋璺",
["興"]="興臖嬹",
["行"]="筕桁行胻衡蘅珩洐荇絎",
["杏"]="杏莕",
["幸"]="幸倖婞涬緈悻睪",
["凶"]="酗凶匈兇訩胸胷洶恟詾",
["兄"]="怳況貺况兄",
["夐"]="讂矎夐瓊藑觼",
["休"]="茠烋恘休貅咻庥鵂髤髹",
["秀"]="透莠誘蜏琇秀銹",
["戌"]="滅搣烕戌珬銊",
["砉"]="剨湱砉",
["須"]="盨須鬚嬃蕦",
["需"]="羺獳擩譳懦糯需繻鑐儒濡襦嚅顬鱬嬬臑醹蠕孺",
["畜"]="畜蓄稸滀傗鄐慉",
["玄"]="弦絃舷痃胘蚿伭婱玄眩玹玆詃泫鉉炫袨衒惤",
["旋"]="旋漩璇琁蜁嫙鏇縼",
["薛"]="躠嶭櫱薛辥孼蘖蠥孽糵",
["學"]="攪覺澩斆鱟嶨礐觷學鷽",
["穴"]="泬坹穴袕鴪",
["血"]="侐血恤卹賉欰洫殈",
["熏"]="櫄薰曛勲熏燻獯纁醺臐爋",
["尋"]="襑憳蕁撏燖尋潯樳鄩",
["卂"]="汛軐卂訊訙迅阠扟籸蝨",
["巽"]="撰饌譔鐉選繏潠蟤僎襈巽簨",
["牙"]="邪衺牙芽齖呀枒雅厊庌訝迓砑犽疨岈谺颬鴉釾",
["襾"]="賈檟價覀襾",
["亞"]="椏錏鵶啞瘂亞婭俹稏惡噁堊蝁",
["焉"]="嘕嫣焉鄢蔫漹傿",
["延"]="唌誕蜑蛋硟涎脠梴鋋埏挻鯅延筵蜒狿郔綖莚",
["言"]="唁這言琂圁誾訔狺",
["沿"]="船沿鉛",
["炎"]="倓緂毯菼裧賧睒舕談郯惔錟淡痰餤啖腅剡覢掞炎琰棪扊",
["奄"]="庵鵪菴馣晻唵黤埯淹閹崦醃掩奄罨裺俺腌殗餣",
["弇"]="弇媕黭揜渰鞥",
["衍"]="愆衍",
["妟"]="堰躽宴蝘偃郾鶠褗匽鼴鰋揠",
["彦"]="喭鏟産簅摌嵼滻顏楌偐齴遃嵃彦諺",
["猒"]="猒懕饜嬮厭黶檿魘厴擪壓靨",
["雁"]="雁鳫鴈贗",
["燕"]="燕驠嬿鷰醼讌嚥酀",
["鬳"]="獻甗巘讞鬳瓛囐齾钀",
["豔"]="豔灩",
["央"]="鴦佒咉眏姎坱映泱盎駚軮醠央秧殃鞅鉠雵胦柍詇岟炴怏紻英瑛霙韺媖渶鶧楧暎",
["羊"]="样佯詳洋翔庠祥痒姜羌蜣猐唴羊徉烊蛘眻鴹恙羕樣漾",
["昜"]="湯踼蝪薚簜盪偒蕩燙啺碭婸愓璗崵逿暢畼腸場傷殤觴慯禓塲陽楊揚瘍煬鍚暘颺昜輰敭鰑諹瑒鸉餳",
["養"]="鯗養癢瀁",
["幺"]="幺吆",
["夭"]="笑夭镺芺妖祅枖訞殀仸妋飫沃鋈",
["爻"]="崤郩敎較肴淆倄爻餚殽笅駁",
["垚"]="撓鐃譊橈磽墝穘顤燒饒蟯蕘襓繞遶嬈趬蹺翹澆驍僥堯垚嶢獟膮嘵憢曉皢",
["謠"]="搖遙瑤徭鰩媱傜颻窰愮鷂榣嗂猺謠鎐",
["繇"]="繇蘨櫾",
["舀"]="滔韜縚謟慆幍槄搯瑫稻蹈舀",
["要"]="腰要葽喓闄偠騕婹",
["耶"]="耶椰爺瑘鎁",
["也"]="他拖駞池灺鍦虵也忚髢杝肔馳阤施絁葹弛箷衪暆迆酏匜迤扡崺貤地",
["曳"]="洩曳拽",
["業"]="礏業鄴驜嶪鸈澲",
["曄"]="曄爗燁曅皣瞱",
["一"]="一弌",
["衣"]="哀依衣扆庡偯",
["壹"]="曀殪豷懿饐鷧撎亄噎壹",
["夷"]="桋荑銕鴺鮧洟羠咦夷姨痍峓恞眱蛦胰跠",
["頤"]="茝洍姬宧頤弬",
["疑"]="礙儗懝譺癡疑嶷觺擬薿凝",
["乙"]="劜札扎蚻紮軋圠穵挖鳦耴乙肊",
["以"]="似姒耜佀以苡苢",
["矣"]="唉埃欸娭挨騃俟竢涘誒矣",
["乂"]="艾鴱餀刈乂",
["弋"]="戴襶貸代袋岱黛玳帒甙酨試弒忒貣鴏栻侙恜式軾拭鉽烒弋芅黓隿杙釴",
["亦"]="夜鵺迹跡亦弈奕帟腋掖焲液",
["役"]="椺役疫垼炈鈠豛坄",
["邑"]="唈浥裛俋挹邑悒",
["易"]="賜儩舓易敡鬄睗晹痬蜴埸逷剔踢惕錫裼緆",
["医"]="鷖翳嫛黳繄医嫕蘙瞖殹贀醫毉蠮",
["佾"]="洕佾屑榍糏",
["益"]="隘賹搤縊螠謚諡蠲溢鎰齸貖益嗌膉鷁艗",
["埶"]="褹勢藝囈埶蓺槸爇褻暬熱槷",
["異"]="禩異潩廙趩瀷翼熼",
["豙"]="豙藙顡毅",
["睪"]="斁籜蘀鐸凙襗擇澤鸅蠌檡釋繹睪譯嶧懌驛醳圛墿燡",
["意"]="噫譩醷意鷾憶億臆繶澺薏檍癔",
["因"]="欭烟胭咽恩煾因姻茵鞇氤洇駰絪裀",
["垔"]="黫甄薽籈煙湮堙禋闉垔陻諲歅鄄",
["音"]="諳腤嬜喑揞隌罯暗闇黯歆愔韾音瘖窨湆",
["殷"]="殷慇溵磤",
["冘"]="冘抌狖貁耽眈酖妉黕衴瓭馾髧紞鈂沈沉枕莐霃鴆訦忱邥魫",
["寅"]="寅蔩鏔演縯戭夤螾濥瞚",
["淫"]="淫霪婬",
["尹"]="芛伊咿蛜笋尹",
["引"]="紖矧訠弞引蚓鈏靷",
["隱"]="隱櫽嶾癮懚檼穩",
["印"]="印鮣",
["胤"]="胤酳",
["賏"]="瀴甖鸚櫻嚶罌鸎譻賏嬰瓔纓攖蘡癭廮巊",
["鷹"]="應鷹膺譍噟",
["盈"]="盈楹",
["熒"]="鶯罃嫈褮覮謍䁝嶸嵤榮瑩蠑禜醟檾煢焭縈營鎣塋濴熒螢滎濙瀅",
["贏"]="嬴瀛籝贏攍",
["邕"]="郺齆甕罋雍邕嗈灉癰廱雝壅饔噰澭擁臃",
["永"]="永栐泳詠咏",
["用"]="通俑蓪桶捅痛筩誦傭慵鱅滽墉鏞鄘槦銿甬勇涌踊蛹恿慂悀埇踴庸用佣",
["攸"]="絛窱蓧條鋚鞗樤鰷莜翛筱篠修脩樇滫悠攸浟蓨滌儵倐倏虪",
["麀"]="鏖𤏶麀",
["憂"]="憂優瀀櫌鄾嚘耰獶纋懮",
["尤"]="尤疣肬沋訧忧蚘",
["由"]="袖岫牰抽妯紬怞鮋菗宙胄冑伷駎油由蚰秞邮釉柚鼬苖笛迪頔軸舳",
["斿"]="游遊蝣斿",
["酉"]="楢逎遒蝤揂酒媨鞧緧趥酋崷鰌煪醜猶猷輶偤莤蕕庮槱梄丣酉",
["郵"]="郵腄",
["又"]="絠醢珛栯有友右宥囿侑酭姷又佑祐賄蛕烠倄鮪洧痏郁戫哊",
["幼"]="黝坳軪眑拗鴢靿狕袎怮窈苭泑呦蚴幼",
["于"]="侉夸姱胯跨誇垮銙骻咵釫摦鮬嶀扝刳郀挎袴絝褲恗洿瓠鄠污杇圬鴮盓汙樗摴荂雩吁盱訏扜旴冔紆陓迂虶于盂竽邘玗芋杅釪骬謣宇",
["予"]="野壄序抒芧杼墅舒紓妤伃予預忬豫蕷澦",
["余"]="斜茶荼梌搽塗佘賒畬舍捨騇涻稌悇庩捈途酴駼鵌涂嵞峹筡蒤徐俆敘漵除篨滁蒢蜍鵨瑹余餘艅狳雓悆",
["於"]="閼菸於淤箊唹扵瘀棜",
["臾"]="萸臾腴諛楰庾斞",
["俞"]="褕偷媮鍮緰牏揄窬歈俞隃毹輸鄃腧逾榆愉渝瑜蕍覦蝓踰崳羭堬瘉萮騟愈貐喻諭",
["舁"]="攑藇嶼鱮舉櫸舁與輿旟璵歟譽嬩擧鸒礜穥",
["禺"]="齵偶耦藕髃蕅愚隅禺湡堣嵎鰅鍝遇寓庽媀喁顒",
["魚"]="蘇穌魚漁䱷",
["羽"]="詡栩珝祤羽頨",
["禹"]="踽萭聥楀齲禹瑀鄅",
["玉"]="珏砡匤玉鳿頊",
["聿"]="筆潷硉律葎聿銉",
["育"]="育毓棛錥焴淯蜟堉逳蘛",
["昱"]="煜翊翌昱喅",
["矞"]="瞲璚劀鱊譎潏鐍憰噊驈橘繘獝燏遹鷸矞霱",
["獄"]="嶽鸑哭獄",
["𧶠"]="竇續覿黷讟殰讀櫝牘儥贕皾瓄瀆豄韇嬻犢匵藚襩贖𧶠",
["鬱"]="鬱欝灪爩",
["肙"]="絹狷蜎弲娟悁捐涓鵑睊焆鞙埍罥羂瓹梋鋗駽琄剈肙",
["冤"]="冤蒬",
["淵"]="婣鼘囦淵裫奫",
["元"]="冠莞脘筦梡岏刓园忨蚖黿抏玩貦翫妧完捖垸綄院皖晥浣頑睆鯇元沅芫杬邧阮俒",
["爰"]="暖煖喛緩鰀嵈鍰湲媛瑗援褑諼愋楥爰鶢蝯猨",
["原"]="羱豲謜縓原源厵嫄騵榞獂蒝願傆愿",
["員"]="員圓塤損縜隕殞溳磒霣愪勛鄖篔貟韻",
["袁"]="薳袁園轅猿榬溒遠",
["睘"]="癏擐還環鬟寰闤糫圜鐶轘澴繯檈儇翾蠉嬛譞懁獧噮彋睘",
["夗"]="涴埦豌剜眢蜿帵碗椀盌腕惋捥睕琬鴛鵷惌宛怨葾夗婉菀苑踠畹倇晼黦焥",
["曰"]="曰汩",
["𦚢"]="𦚢𩨭",
["月"]="刖枂月跀抈鈅玥蚏",
["戉"]="泧眓越狘戉鉞樾怴",
["岳"]="岳捳",
["龠"]="讑籲爚禴蘥鑰瀹龠籥鸙",
["云"]="魂沄忶囩夽雲芸耘妘紜云耺蕓澐橒抎",
["匀"]="枃笉郇絢眴盷訇迿姰畇荀詢洵恂峋珣槆筍箰旬殉徇侚均鈞袀汮呁筠荺匀韵輷揈鍧渹赹憌惸咰",
["允"]="唆梭葰誜朘捘荾酸狻痠鋑脧悛吮萒沇抁馻兖渷兗焌允狁玧俊晙餕畯駿竣皴逡踆夋埈陖鵕浚峻",
["帀"]="帀沞魳迊匝咂韴砸",
["災"]="灾災",
["𢦏"]="𢦏栽哉烖載渽睵溨裁酨蛓胾",
["宰"]="宰縡梓滓",
["再"]="再洅",
["贊"]="贊趲讚酇饡灒襸攢瓚禶穳囋鑽劗纘儹鄼欑籫巑攅",
["葬"]="髒葬",
["早"]="早草愺騲皁皂",
["蚤"]="蚤慅騷搔鰠溞颾瘙瑵糔鼜",
["喿"]="藻澡璪繰躁趮操幧懆鄵鐰臊鱢燥噪髞喿譟劋橾",
["則"]="廁則賊鰂鱡蠈側萴測惻崱",
["仄"]="仄昃",
["曾"]="潧繒增憎磳曾矰罾熷竲橧譄蹭層贈僧鬙甑鄫驓嶒噌鏳",
["乍"]="作鮓痄厏詐咋笮榨炸乍拃酢祚胙阼飵秨迮柞昨怍砟莋岝鈼筰葃舴窄蚱齚泎",
["占"]="頕煔炶詀站檆黏粘枮霑沾覘鉆占颭佔苫痁蛅阽敁掂點玷店坫黇扂拈鮎砧笘跕貼帖怗呫",
["詹"]="甔擔儋聸膽黵澹憺瞻詹蟾噡譫幨襜韂贍檐簷舚",
["展"]="碾輾展搌",
["斬"]="慚慙鏨槧暫蹔斬獑嶃覱摲漸塹壍嬱螹蔪",
["章"]="章樟彰漳鄣璋嫜獐慞墇障麞蔁暲嶂瘴",
["丈"]="丈杖仗",
["兆"]="挑洮桃逃咷鼗鞉駣𣂁脁晁兆旐狣鮡垗姚珧銚恌烑餆筄艞佻祧朓庣趒聎眺覜頫絩跳窕誂",
["肁"]="肁肇",
["乇"]="咤吒灹矺奼侘詫秅秺亳托託魠馲侂飥仛虴杔乇宅厇",
["折"]="晢狾誓逝銴折娎哳踅哲蜇悊嚞喆硩浙晣焎",
["者"]="觰諸者堵赭撦扯闍奢鍺都醏覩睹暏賭帾楮屠瘏廜鷵緒豬猪瀦藸櫫褚著箸儲躇櫧藷蠩煮渚煑陼翥署薯曙書暑鐯擆櫡",
["耴"]="踂輒耴鮿",
["貞"]="偵幀湞貞楨禎赬遉",
["真"]="寘䡩黰顛滇齻槙瘨傎蹎厧巔癲瑱填窴闐磌鷏嗔搷鎮縝真禛稹蒖鬒瞋謓慎",
["疹"]="沴趁紾跈殄抮駗珍診畛疹軫胗袗眕飻餮",
["朕"]="黱朕鰧螣栚眹騰滕縢儯幐藤謄膡籐勝蕂榺塍媵賸",
["爭"]="琤崢鬇爭箏猙埩諍凈錚棦竫睜淨",
["徵"]="徵癥澂懲",
["正"]="綻淀靛症竀征怔正鉦鴊眐佂整政証定顁錠掟",
["之"]="之芝志痣娡誌蚩妛嗤媸旹滍",
["支"]="支枝肢汥衼馶鳷忮伎豉翅翄妓庋跂蚑吱岐歧鬾技芰攱庪頍屐",
["巵"]="巵梔",
["戠"]="識幟織熾樴職軄戠膱蟙",
["直"]="置值植埴惪德稙淔直犆殖",
["執"]="騺摯贄鷙墊慹謺褺蓻縶漐蟄執瓡",
["止"]="祉址止趾芷沚阯齒紕",
["只"]="只胑疻軹咫枳抧伿齞",
["旨"]="稽詣栺榰脂鴲指旨恉嗜蓍鰭耆鮨鬐",
["至"]="致輊駤跮荎緻至鵄胵痓咥窒蛭垤耋姪絰恎挃庢銍秷螲侄桎郅晊室",
["制"]="制淛製掣痸猘",
["陟"]="陟騭",
["疐"]="啑疐嚏懥",
["質"]="質躓懫礩櫍劕鑕",
["中"]="中忠衷忡浺盅种沖翀仲",
["重"]="撞幢橦噇𩪘艟憧畽董蕫箽懂湩曈童僮瞳罿犝潼穜動慟堹諥蹱重緟蝩褈鐘鍾籦種腫踵歱喠偅衝揰尰",
["眾"]="潨眾",
["州"]="洲州喌酬詶",
["舟"]="鵃輈侜矪舟",
["周"]="裯啁禂綢翢錭雕凋琱鯛鵰彫奝調蜩惆婤稠椆周輖賙郮晭倜",
["粥"]="粥鬻",
["肘"]="肘疛紂葤酎",
["帚"]="婦帚箒鯞",
["朱"]="咮株誅邾鼄跦袾鴸蛛殊趎珠朱侏絑祩鮢硃姝銖茱洙陎",
["竹"]="竺篤竹筑築茿",
["逐"]="篴蓫逐鱁",
["主"]="飳妵黈蘣罜拄柱駐註鉒軴住跓嵀主麈宔炷注疰蛀霔",
["宁"]="貯詝柠宁佇竚苧紵羜眝",
["壴"]="壴廚躕櫥幮樹澍尌",
["祝"]="祝呪咒柷",
["爪"]="抓爪笊找",
["專"]="竱團摶篿慱剸鄟漙鷒轉傳囀縳專磚甎塼嫥膞鱄蓴",
["孨"]="孨孱",
["隹"]="蜼堆嵟碓推蓷陮魋嶊崔猚椎顀隹錐萑騅鵻誰脽",
["追"]="蘬磓塠頧鴭鎚搥縋膇槌追桘巋歸",
["卓"]="悼淖罩鵫趠踔棹鋽掉逴焯綽婥繛卓桌倬啅晫",
["叕"]="蝃欼綴醊畷啜餟腏錣輟娺窡鵽掇剟裰敠惙罬叕歠逫",
["甾"]="輜淄錙緇甾菑鶅椔鯔",
["兹"]="糍茲滋孳嵫嗞鎡鰦鶿稵螆慈鷀磁礠濨",
["子"]="孜仔孖子虸耔秄杍籽字茡芓牸李",
["秭"]="姊秭趀笫胏柹柿",
["自"]="郋自洎臮垍詯",
["宗"]="淙鬃漴粽宗倧綜錝賨琮悰孮誴崇崈踪",
["奏"]="奏凑腠輳楱湊",
["足"]="足莡捉齪娖浞鋜趗珿哫促",
["卒"]="晬祽綷淬啐倅焠碎誶濢噿臎醉翠悴萃瘁顇粹睟賥卒稡猝椊捽崪窣崒踤錊",
["族"]="蔟嗾鷟鏃瘯簇族",
["罪"]="罪辠𢶀",
["尊"]="尊樽罇嶟繜撙噂譐僔蹲鱒鐏遵鷷墫竴",
["左"]="左佐袏蹉瑳搓磋溠傞醝瘥鹺嵯嵳蒫艖齹縒褨嗟差鎈槎髊",
["坐"]="脞侳挫夎遳銼剉莝趖矬痤睉坐座髽",
["丵"]="鑿糳",
["翪"]="艐堫葼鬷嵕猣鯼椶騣蝬翪緵稯鬉糉",
}
hd2unu9tq2t7kgb24ic90mow2x7y3cc
Module:Links/templates
828
1364931
9909083
9613044
2026-09-29T07:26:58Z
TongcyDai
53191
同步 enwikt:pron qualifier → decorations
9909083
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>[具體何詞?]</small>" ..
require("Module:utilities").format_categories(
{lang:getFullName() .. "詞請求"},
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
bf39narhj4ph9qnku22phd4bkfh3byi
Module:Columns
828
1364953
9909050
9807198
2026-09-29T07:24:50Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909050
Scribunto
text/plain
local export = {}
local collation_module = "Module:collation"
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local headword_data_module = "Module:headword/data"
local JSON_module = "Module:JSON"
local languages_module = "Module:languages"
local links_module = "Module:links"
local pages_module = "Module:pages"
local parameter_utilities_module = "Module:parameter utilities"
local parameters_module = "Module:parameters"
local parse_utilities_module = "Module:parse utilities"
local qualifier_module = "Module:qualifier"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local m_str_utils = require(string_utilities_module)
local concat = table.concat
local html = mw.html.create
local is_substing = mw.isSubsting
local insert = table.insert
local rmatch = m_str_utils.match
local remove = table.remove
local sub = string.sub
local trim = m_str_utils.trim
local u = m_str_utils.char
local dump = mw.dumpObject
local function track(page)
require(debug_track_module)("columns/" .. page)
return true
end
local function deepEquals(...)
deepEquals = require(table_module).deepEquals
return deepEquals(...)
end
local function term_already_linked(term)
return term == "?" or -- signals an unknown term
-- optimization to avoid unnecessarily loading [[Module:parse utilities]]
(term:find("[<{]") and require(parse_utilities_module).term_already_linked(term))
end
local function convert_delimiter_to_separator(item, itemind, args)
if itemind == 1 then
item.separator = nil
elseif item.delimiter == " " then
item.separator = args.space_delim
elseif item.delimiter == "~" then
item.separator = args.tilde_delim
else
item.separator = args.comma_delim
end
end
local function get_horizontal_separator(args_horiz, embedded_comma)
return args_horiz == "bullet" and " · " or embedded_comma and "; " or ", "
end
-- Suppress false positives in categories like [[Category:English links with redundant wikilinks]] so people won't
-- be tempted to "correct" them; terms like embedded ~ like [[Micros~1]] or embedded comma not followed by a space
-- such as [[1,6-Cleves acid]] need to have a link around them to avoid the tilde or comma being interpreted as a
-- delimiter.
local function suppress_redundant_wikilink_cat(term, alt)
return term:find("~") or term:find(",%S")
end
local function full_link_and_track_self_links(item, face, nochecktr)
if item.term then
local pagename = mw.loadData(headword_data_module).pagename
local term_is_pagename = item.term == pagename
local term_contains_pagename = item.term:find("%[%[" .. m_str_utils.pattern_escape(pagename) .. "[|%]]")
if term_is_pagename or term_contains_pagename then
local current_L2 = require(pages_module).get_current_L2()
if current_L2 then
local current_L2_lang = require(languages_module).getByCanonicalName(current_L2)
if current_L2_lang and current_L2_lang:getCode() == item.lang:getCode() then
if term_is_pagename then
track("term-is-pagename")
else
track("term-contains-pagename")
end
end
end
end
end
item.suppress_redundant_wikilink_cat = suppress_redundant_wikilink_cat
item.never_call_transliteration_module = nochecktr
return require(links_module).full_link(item, face)
end
local function format_subitem(subitem, lang, face, compute_embedded_comma, nochecktr)
local embedded_comma = false
local text
if subitem.term and term_already_linked(subitem.term) then
text = subitem.term
if compute_embedded_comma then
embedded_comma = not not require(utilities_module).get_plaintext(text):find(",")
end
else
text = full_link_and_track_self_links(subitem, face, nochecktr)
if compute_embedded_comma then
-- We don't check decoration text for commas as it's inside parens or displayed elsewhere.
local subitem_plaintext = subitem.alt or subitem.term
if subitem_plaintext then
embedded_comma = not not subitem_plaintext:find(",")
end
end
end
-- We could use the `show_decorations` field to full_link() but not when term_already_linked().
if subitem.q and subitem.q[1] or subitem.qq and subitem.qq[1] or subitem.l and subitem.l[1] or
subitem.ll and subitem.ll[1] or subitem.refs and subitem.refs[1] then
text = require(decorations_module).format_decorations {
lang = subitem.lang or args.lang,
text = text,
q = subitem.q,
qq = subitem.qq,
l = subitem.l,
ll = subitem.ll,
refs = subitem.refs,
}
end
return text, embedded_comma
end
function export.format_item(item, args, face)
local compute_embedded_comma = args.horiz == "comma"
local embedded_comma = false
local nochecktr = args.noautotr
if type(item) == "table" then
if item.terms then
local parts = {}
local is_first = true
for _, subitem in ipairs(item.terms) do
if subitem == false then
-- omitted subitem; do nothing
else
local separator = subitem.separator or not is_first and (args.subitem_separator or ", ")
if separator then
if compute_embedded_comma then
embedded_comma = embedded_comma or not not separator:find(",")
end
insert(parts, separator)
end
local formatted, this_embedded_comma = format_subitem(subitem, args.lang, face,
compute_embedded_comma, nochecktr)
embedded_comma = embedded_comma or this_embedded_comma
insert(parts, formatted)
is_first = false
end
end
return concat(parts), embedded_comma
else
return format_subitem(item, args.lang, face, compute_embedded_comma, nochecktr)
end
else
if compute_embedded_comma then
embedded_comma = not not require(utilities_module).get_plaintext(item):find(",")
end
if args.lang and not term_already_linked(item) then
return full_link_and_track_self_links({lang = args.lang, term = item, sc = args.sc}, face, nochecktr), embedded_comma
else
return item, embedded_comma
end
end
end
function export.construct_old_style_header(header, horiz)
local old_style_header
local function ib_colon()
return tostring(html("span"):addClass("ib-colon"):addClass("ib-content"):wikitext(":"))
end
if horiz then
old_style_header = require(qualifier_module).format_qualifiers {
qualifiers = header,
open = false,
close = false,
} .. ib_colon() .. " "
else
old_style_header = require(qualifier_module).format_qualifiers {
qualifiers = header
} .. ib_colon()
old_style_header = tostring(html("div"):wikitext(old_style_header))
end
return old_style_header
end
-- Construct the sort base of a single term. As a hack, sort appendices after mainspace items.
local function term_sortbase(val)
if not val then
-- This should not normally happen.
return u(0x10FFFF)
elseif val:find("^%[*Appendix:") then
return u(0x10FFFE) .. val
else
return val
end
end
-- Construct the sort base of a single item, using the display form preferentially, otherwise the term itself.
-- As a hack, sort appendices after mainspace items.
local function item_sortbase(item)
return term_sortbase(item.alt or item.term)
end
local function make_sortbase(item)
if item == false then
return "*" -- doesn't matter, will be omitted in create_list()
elseif type(item) == "table" then
if item.terms then
-- Optimize for the common case of only a single term
if item.terms[2] then
local parts = {}
-- multiple terms
local first = true
for _, subitem in ipairs(item.terms) do
if subitem ~= false then
if not first then
insert(parts, ", ")
end
insert(parts, item_sortbase(subitem))
first = false
end
end
if parts[1] then
return concat(parts)
end
else
local subitem = item.terms[1]
if subitem ~= false then
return item_sortbase(subitem)
end
end
return "*" -- doesn't matter, entire group will be omitted in create_list()
else
return item_sortbase(item)
end
else
return item
end
end
local function make_node_sortbase(node)
return make_sortbase(node.item)
end
-- Sort a sublist of `list` in place, keeping the first `keepfirst` and last `keeplast` items fixed.
-- `lang` is the language of the items and `make_sortbase` creates the appropriate sort base.
local function sort_sublist(list, lang, make_sortbase, keepfirst, keeplast)
if keepfirst == 0 and keeplast == 0 then
require(collation_module).sort(list, lang, make_sortbase)
else
local sublist = {}
for i = keepfirst + 1, #list - keeplast do
sublist[i - keepfirst] = list[i]
end
require(collation_module).sort(sublist, lang, make_sortbase)
for i = keepfirst + 1, #list - keeplast do
list[i] = sublist[i - keepfirst]
end
end
end
-- URL-encode only the characters that serve as template delimiters (left and right brace, vertical bar, equal sign
-- and percent sign since it's the escape character).
local function bot_url_encode(txt)
return (txt:gsub("[%%|{}=&]",
{["%"] = "%25", ["|"] = "%7C", ["{"] = "%7B", ["}"] = "%7D", ["="] = "%3D", ["&"] = "%26"}))
end
-- Reverse the action of bot_url_encode().
local function bot_url_decode(txt)
return (txt:gsub("%%7([BCD])", {B = "{", C = "|", D = "}"}):gsub("%%3D", "="):gsub("%%26", "&"):gsub("%%25", "%%"))
end
--[==[
Bot-callable function to generate a number of sortkeys simultaneously. {{para|1}} contains the langcode, and remaining
numeric parameters contain "bot-URL-encoded" strings whose sort keys will be computed and returned as a JSON array.
Here, "bot-URL-encoded" means that the six characters `{ | } = & %` should be converted to
their URL-encoded representation (respectively `%7B %7C %7D %3D %26 %25`), and will be decoded appropriately
before computing the sortkey.
]==]
function export.make_sortkey(frame)
local iparams = {
[1] = {type = "language"},
[2] = {list = true},
}
local iargs = require(parameters_module).process(frame.args, iparams)
local make_sortkey = require(collation_module).make_lang_sortkey_function(iargs[1], term_sortbase)
local retval = {}
for _, arg in ipairs(iargs[2]) do
arg = bot_url_decode(arg)
insert(retval, make_sortkey(arg))
end
return require(JSON_module).toJSON(retval)
end
local large_text_scripts = {
["Arab"] = true,
["Beng"] = true,
["Deva"] = true,
["Gujr"] = true,
["Guru"] = true,
["Hebr"] = true,
["Khmr"] = true,
["Knda"] = true,
["Laoo"] = true,
["Mlym"] = true,
["Mong"] = true,
["Mymr"] = true,
["Orya"] = true,
["Sinh"] = true,
["Syrc"] = true,
["Taml"] = true,
["Telu"] = true,
["Tfng"] = true,
["Thai"] = true,
["Tibt"] = true,
}
--[==[
Format a list of items using HTML. `args` is an object specifying the items to add and related properties, with the
following fields:
* `content`: A list of the items to format. See below for the format of the items.
* `lang`: The language object of the items to format, if the items in `content` are strings.
* `sc`: The script object of the items to format, if the items in `content` are strings.
* `raw`: If true, return the list raw, without any collapsing or columns.
* `class`: The CSS class of the surrounding {<div>}.
* `column_count`: Number of columns to format the list into.
* `alphabetize`: If true, sort the items in the table.
* `collapse`: If true, make the table partially collapsed by default, with a "Show more" button at the bottom.
* `toggle_category`: Value of `data-toggle-category` property grouping collapsible elements.
* `header`: If specified, Wikicode to prepend to the output.
* `title_new_style`: If true, the header is treated as a title and displayed in a new style. This is ignored if `horiz`
is non-nil.
* `subitem_separator`: Separator used between subitems when multiple subitems occur on a line, if not specified in the
subitem itself (using the `separator` field). Defaults to {", "}.
* `keepfirst`: If > 0, keep this many rows unsorted at the beginning of the top level.
* `keeplast`: If > 0, keep this many rows unsorted at the end of the top level.
* `horiz`: If non-nil, format the items horizontally. If the value is "bullet", put a center dot/bullet (·) between
items. If the value is "comma", put a comma between items (but if there is an embedded comma in any item,
put a semicolon between all items).
Each item in `content` is in one of the following formats:
* A string. This is for compatibility and should not be used by new callers.
* An object describing an item to format, in the format expected by full_link() in [[Module:links]], including
decorations (left or right qualifiers, left or right labels, or references).
* An object describing a list of subitems to format, displayed side-by-side, separated by a comma or other separator.
This format is identified by the presence of a key `terms` specifying the list of subitems. Each subitem is in
the same format as for a single top-level item, except that it should also have a `separator` field specifying the
separator to display before each item (which will typically be a blank string before the first item).
]==]
function export.create_list(args)
if type(args) ~= "table" then
error("expected table, got " .. type(args))
end
local column_count = args.column_count or 1
local toggle_category = args.toggle_category or "derived terms"
local keepfirst = args.keepfirst or 0
local keeplast = args.keeplast or 0
if keepfirst > 0 then
track("keepfirst")
end
if keeplast > 0 then
track("keeplast")
end
-- maybe construct old-style header
local old_style_header = nil
if args.header and (args.horiz or not args.title_new_style) then
old_style_header = export.construct_old_style_header(args.header, args.horiz)
end
if args.horiz then
old_style_header = "* " .. (old_style_header or "")
end
local list
local any_extra_indented_item = false
for _, item in ipairs(args.content) do
if item == false then
-- do nothing
elseif type(item) == "table" and item.extra_indent and item.extra_indent > 0 then
any_extra_indented_item = true
break
end
end
-- If any extra indented item, convert the items to a nested structure, which is necessary both for sorting and
-- for converting to HTML.
if any_extra_indented_item then
local function make_node(item)
return {
item = item
}
end
local root_node = make_node(nil)
local node_stack = {root_node}
local last_indent = 0
local function append_subnode(node, subnode)
if not node.subnodes then
node.subnodes = {}
end
insert(node.subnodes, subnode)
end
for i, item in ipairs(args.content) do
if item == false then
-- do nothing
else
local this_indent
if type(item) ~= "table" then
this_indent = 1
else
this_indent = (item.extra_indent or 0) + 1
end
local node = make_node(item)
if this_indent == last_indent then
append_subnode(node_stack[#node_stack], node)
elseif this_indent > last_indent + 1 then
error(("Element #%s (%s) has indent %s, which is more than one greater than the previous item with indent %s"):format(
i, make_sortbase(item), this_indent, last_indent))
elseif this_indent > last_indent then
-- Start a new sublist attached to the last item of the sublist one level up; but we need special
-- handling for the root node (last_indent == 0).
if last_indent > 0 then
local subnodes = node_stack[#node_stack].subnodes
if not subnodes then
error(("Internal error: Not first item and no subnodes at preceding level %s: %s"):format(
#node_stack, dump(node_stack)))
end
insert(node_stack, subnodes[#subnodes])
end
append_subnode(node_stack[#node_stack], node)
last_indent = this_indent
else
while last_indent > this_indent do
local finished_node = table.remove(node_stack)
if args.alphabetize then
require(collation_module).sort(finished_node.subnodes, args.lang, make_node_sortbase)
end
last_indent = last_indent - 1
end
append_subnode(node_stack[#node_stack], node)
end
end
end
if args.alphabetize then
while node_stack[1] do
local finished_node = table.remove(node_stack)
if node_stack[1] then
-- We're sorting something other than the root node.
require(collation_module).sort(finished_node.subnodes, args.lang, make_node_sortbase)
else
-- We're sorting the root node; honor `keepfirst` and `keeplast`.
sort_sublist(finished_node.subnodes, args.lang, make_node_sortbase, keepfirst, keeplast)
end
end
end
local function format_node(node, depth)
local sublist
local embedded_comma = false
if node.subnodes then
if args.horiz then
sublist = {}
else
sublist = html("ul")
end
local prevnode = nil
for _, subnode in ipairs(node.subnodes) do
local thisnode, this_embedded_comma = format_node(subnode, depth + 1)
embedded_comma = embedded_comma or this_embedded_comma
if not prevnode or not args.alphabetize or not deepEquals(prevnode, thisnode) then
if args.horiz then
table.insert(sublist, thisnode)
else
sublist = sublist:node(thisnode)
end
prevnode = thisnode
end
end
if args.horiz then
sublist = table.concat(sublist, get_horizontal_separator(args.horiz, embedded_comma))
end
end
if not node.item then
-- At the root.
return sublist, embedded_comma
end
local formatted, listitem
-- Ignore embedded commas in subitems inside of parens or square brackets.
formatted, embedded_comma = export.format_item(node.item, args)
if args.horiz then
listitem = formatted
if sublist then
-- Use parens for the first, third, fifth, etc. sublists and square brackets for the remainder.
if depth % 2 == 1 then
listitem = ("%s (%s)"):format(listitem, sublist)
else
listitem = ("%s [%s]"):format(listitem, sublist)
end
end
else
listitem = html("li"):wikitext(formatted)
if sublist then
listitem = listitem:node(sublist)
end
end
return listitem, embedded_comma
end
list = format_node(root_node, 0)
else
if args.alphabetize then
sort_sublist(args.content, args.lang, make_sortbase, keepfirst, keeplast)
end
if args.horiz then
list = {}
else
list = html("ul")
end
local previtem = nil
local embedded_comma = false
for _, item in ipairs(args.content) do
if item == false then
-- omitted item; do nothing
else
local thisitem, this_embedded_comma = export.format_item(item, args)
embedded_comma = embedded_comma or this_embedded_comma
if not previtem or not args.alphabetize or previtem ~= thisitem then
if args.horiz then
table.insert(list, thisitem)
else
list = list:node(html("li"):wikitext(thisitem))
end
previtem = thisitem
end
end
end
if args.horiz then
list = table.concat(list, get_horizontal_separator(args.horiz, embedded_comma))
end
end
local output
if args.horiz then
output = list
else
output = html("div"):addClass("term-list"):node(list)
if args.class then
output:addClass(args.class)
end
if not args.raw then
output:addClass("ul-column-count")
:attr("data-column-count", column_count)
if args.collapse then
output = html("div")
:node(output)
:addClass("list-switcher")
:attr("data-toggle-category", toggle_category)
-- identify commonly used scripts that use large text and
-- provide a special CSS class to make the template bigger
local sc = args.sc
if sc == nil then
local scripts = args.lang:getScripts()
if #scripts > 0 then
sc = scripts[1]
end
end
if sc ~= nil then
local scriptcode = sc:getParentCode()
if scriptcode == "top" then
scriptcode = sc:getCode()
end
if large_text_scripts[scriptcode] then
output:addClass("list-switcher-large-text")
end
end
end
end
if args.collapse or args.title_new_style then
-- wrap in wrapper to prevent interference from floating elements
local list_switcher_wrapper = html("div")
:addClass("list-switcher-wrapper")
if args.title_new_style then
list_switcher_wrapper
:node(
html("div")
:addClass("list-switcher-header")
:wikitext(args.header)
)
end
list_switcher_wrapper:node(output)
output = list_switcher_wrapper
end
output = tostring(output)
end
return (old_style_header or "") .. output
end
-- This function is for compatibility with earlier version of [[Module:columns]]
-- (now found in [[Module:columns/old]]).
function export.create_table(...)
-- Earlier arguments to create_table:
-- n_columns, content, alphabetize, bg, collapse, class, title, column_width, line_start, lang
local args = {}
args.column_count, args.content, args.alphabetize,
args.collapse, args.class, args.header, args.column_width,
args.line_start, args.lang = ...
return export.create_list(args)
end
function export.display_from(frame_args, parent_args, frame)
local boolean = {type = "boolean"}
local iparams = {
["class"] = true,
-- Default for auto-collapse. Overridable by template |collapse= param.
["collapse"] = boolean,
-- If specified, this specifies the number of columns, and no columns parameter is available on the template.
-- Otherwise, the columns parameter is named |n=.
["columns"] = {type = "number"},
-- If specified, this specifies the default language code, which can be overridden using |lang= in the template.
-- Otherwise, the language-code parameter is required and normally found in |1=, but for compatibility can be
-- specified as |lang= (which leads to deprecation handling).
["lang"] = {type = "language"},
-- Default for auto-sort. Overridable by template |sort= param.
["sort"] = boolean,
["toggle_category"] = true,
-- Minimum number of rows required to format into a multicolumn list. If below this, the list is displayed "raw"
-- (no columns, no collapsbility).
["minrows"] = {type = "number", default = 5},
-- Disables automatic transliteration; entries without a manual transliteration will have none at all.
-- Used on large pages, especially Chinese ones, because zh-translit works by fetching and parsing
-- the target of the page, which is a performance killer on large pages with potentially thousands
-- of link targets.
-- Note: noautotr also disables redundant transliteration checks.
["noautotr"] = boolean,
}
local iargs = require(parameters_module).process(frame_args, iparams)
local langcode_in_lang = iargs.lang or parent_args.lang
local lang_param = langcode_in_lang and "lang" or 1
local deprecated = not iargs.lang and langcode_in_lang
local ret = export.handle_display_from_or_topic_list(iargs, parent_args, nil)
return deprecated and frame:expandTemplate{title = "check deprecated lang param usage",
args = {ret, lang = args[lang_param]}} or ret
end
--[==[
Implement `display_from()` [the internal entry point for {{tl|col}} and variants, which enter originally through
`display()`] as well as regular (column-oriented) topic lists, invoked through [[Module:topic list]].
`iargs` are the invocation args of {{tl|col}}, and `raw_item_args` are the arguments specifying the values of
each row as well as other properties, corresponding to the user-specified template arguments of {{tl|col}}. Note that
`show()` in [[Module:topic list]] is normally invoked directly by a topic list template, whose invocation
arguments are passed in using `raw_item_args` and are similar to the template arguments of {{tl|col}}. `iargs` for
topic-list invocations is hard-coded, and template arguments to a topic-list template are processed in
[[Module:topic list]] itself. Note that the handling of topic lists is currently implemented almost entirely
through callbacks in `topic_list_data` (which is nil if we're processing {{tl|col}} rather than a topic list) in an
attempt to reduce the coupling and keep the topic-list-specific code in [[Module:topic list]], but IMO the coupling
is still too tight. Probably the control structure should be reversed and the following function split up into
subfunctions, which are invoked as needed by {{tl|col}} and/or [[Module:topic list]].
]==]
function export.handle_display_from_or_topic_list(iargs, raw_item_args, topic_list_data)
local boolean = {type = "boolean"}
local langcode_in_lang = iargs.lang or raw_item_args.lang
local lang_param = langcode_in_lang and "lang" or 1
local first_content_param = langcode_in_lang and 1 or 2
local params = {
[lang_param] = {required = not iargs.lang, type = "language",
template_default = not iargs.lang and "und" or nil},
["n"] = not iargs.columns and {type = "number"} or nil,
[first_content_param] = {list = true, allow_holes = true},
["title"] = {},
["collapse"] = boolean,
["sort"] = boolean,
["sc"] = {type = "script"},
-- used when calling from [[Module:saurus]] so the page displaying the synonyms/antonyms doesn't occur in the
-- list
["omit"] = {list = true},
["keepfirst"] = {type = "number", default = 0},
["keeplast"] = {type = "number", default = 0},
["horiz"] = {},
["notr"] = boolean,
["noautotr"] = boolean,
["allow_space_delim"] = boolean,
["tilde_delim"] = {},
["space_delim"] = {},
["comma_delim"] = {},
}
if topic_list_data then
topic_list_data.add_topic_list_params(params)
end
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{default = true, require_index = true},
{group = "link"}, -- sc has separate_no_index = true; that's the only one
-- It makes no sense to have overall l=, ll=, q= or qq= params for columnar display.
{group = {"ref", "l", "q"}, require_index = true},
}
m_param_utils.augment_params_with_modifiers(params, param_mods)
local processed_args = require(parameters_module).process(raw_item_args, params)
local horiz = processed_args.horiz
if horiz and horiz ~= "comma" and horiz ~= "bullet" then
horiz = require(yesno_module)(horiz)
if horiz == nil then
error(("Unrecognized value |horiz=%s; should be 'comma', 'bullet' or a recognized Boolean value such " ..
"as 'yes' or '1' (same as 'bullet') or 'no' or '0'"):format(processed_args.horiz))
end
if horiz == true then
horiz = "bullet"
end
processed_args.horiz = horiz
end
-- If default argument values specified, set them after parsing the caller-specified arguments in `raw_item_args`.
if topic_list_data then
topic_list_data.set_default_arguments(processed_args)
end
-- Now set defaults for the various delimiters, depending in some cases on whether horiz was set.
-- We can't set these defaults (even regardless of their dependency on horiz=) in `local params` above
-- because we want any defaults specified in `default_props` to override these.
if not processed_args.tilde_delim then
local tilde_with_abbr = '<abbr title="near equivalent">~</abbr>'
processed_args.tilde_delim = processed_args.horiz and tilde_with_abbr or " " .. tilde_with_abbr .. " "
end
if not processed_args.space_delim then
processed_args.space_delim = " "
end
if not processed_args.comma_delim then
processed_args.comma_delim = processed_args.horiz and "/" or ", "
end
-- Check for extra term indent. Do this before calling parse_list_with_inline_modifiers_and_separate_params()
-- because sometimes space is a delimiter and the space in the indent will confuse things and get interpreted as a
-- delimiter.
local extra_indent_by_termno = {}
local termargs = processed_args[first_content_param]
for i = 1, termargs.maxindex do
local term = termargs[i]
if term then
local extra_indent, actual_term = rmatch(term, "^(%*+)%s+(.-)$")
if extra_indent then
termargs[i] = actual_term
extra_indent_by_termno[i] = #extra_indent
end
end
end
local groups, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
param_mods = param_mods,
processed_args = processed_args,
termarg = first_content_param,
parse_lang_prefix = true,
allow_multiple_lang_prefixes = true,
disallow_custom_separators = true,
track_module = "columns",
lang = iargs.lang or lang_param,
sc = "sc.default",
splitchar = processed_args.allow_space_delim and "[,~ ]" or "[,~]",
no_show_decorations = true, -- since we handle them ourselves in format_subitem()
}
local lang = iargs.lang or args[lang_param]
local langcode = lang:getCode()
local fulllangcode = lang:getFullCode()
local sc = args.sc.default
local sort = iargs.sort
if args.sort ~= nil then
if not args.sort then
track("nosort")
end
sort = args.sort
else
-- HACK! For Japanese-script languages (Japanese, Okinawan, Miyako, etc.), sorting doesn't yet work properly, so
-- disable it.
for _, langsc in ipairs(lang:getScriptCodes()) do
if langsc == "Jpan" then
sort = false
break
end
end
end
local collapse = iargs.collapse
if args.collapse ~= nil then
if not args.collapse then
track("nocollapse")
end
collapse = args.collapse
end
local title = args.title
local formatted_cats
if topic_list_data then
title, formatted_cats = topic_list_data.get_title_and_formatted_cats(args, lang, sc, topic_list_data)
end
local number_of_groups = 0
for i, group in ipairs(groups) do
local number_of_items = 0
group.extra_indent = extra_indent_by_termno[group.orig_index]
for j, item in ipairs(group.terms) do
convert_delimiter_to_separator(item, j, args)
if args.notr then
item.tr = "-"
elseif args.noautotr then
item.tr = item.tr or "-"
end
-- If a separate language code was given for the term, display the language name as a right qualifier.
-- (Briefly we made them labels but this leads to non-obvious behavior e.g. "French" becoming "France" under
-- some circumstances.) Otherwise it may not be obvious that the term is in a separate language (e.g. if the
-- main language is 'zh' and the term language is a Chinese lect such as Min Nan). But don't do this for
-- Translingual terms, which are often added to the list of English and other-language terms.
if item.termlangs then
local qqs = {}
for _, termlang in ipairs(item.termlangs) do
local termlangcode = termlang:getCode()
if termlangcode ~= langcode and termlangcode ~= "mul" then
insert(qqs, termlang:getCanonicalName())
end
end
if item.qq then
for _, qq in ipairs(item.qq) do
insert(qqs, qq)
end
end
item.qq = qqs
end
local omitted = false
for _, omitted_item in ipairs(args.omit) do
if omitted_item == item.term then
omitted = true
break
end
end
if omitted then
-- signal create_list() to omit this item
group.terms[j] = false
else
number_of_items = number_of_items + 1
end
end
if number_of_items == 0 then
-- omit the whole group
groups[i] = false
else
number_of_groups = number_of_groups + 1
end
end
local column_count = iargs.columns or args.n
-- FIXME: This needs a total rewrite.
if column_count == nil then
column_count = number_of_groups <= 3 and 1 or
number_of_groups <= 9 and 2 or
number_of_groups <= 27 and 3 or
number_of_groups <= 81 and 4 or
5
end
local raw = number_of_groups < iargs.minrows
local horiz_edit_button
if topic_list_data and args.horiz then
-- append edit button to title
horiz_edit_button = topic_list_data.make_horiz_edit_button(topic_list_data.topic_list_template)
end
return export.create_list {
column_count = column_count,
raw = raw,
content = groups,
alphabetize = sort,
header = title,
title_new_style = (title ~= nil and title ~= ''),
collapse = collapse,
toggle_category = iargs.toggle_category,
-- columns-bg (in [[MediaWiki:Gadget-Site.css]]) provides the background color
class = (iargs.class and iargs.class .. " columns-bg" or "columns-bg"),
lang = lang,
sc = sc,
subitem_separator = ", ",
keepfirst = args.keepfirst,
keeplast = args.keeplast,
horiz = args.horiz,
noautotr = args.noautotr,
} .. (horiz_edit_button or "") .. (formatted_cats or "")
end
function export.display(frame)
if not is_substing() then
return export.display_from(frame.args, frame:getParent().args, frame, false)
end
-- If substed, unsubst template with newlines between each term, redundant wikilinks removed, and remove duplicates + sort terms if sort is enabled.
local m_table = require("Module:table")
local m_template_parser = require("Module:template parser")
local parent = frame:getParent()
local elems = m_table.shallowCopy(parent.args)
local code = remove(elems, 1)
code = code and trim(code)
local lang = require("Module:languages").getByCode(code, 1)
local i = 1
while true do
local elem = elems[i]
while elem do
elem = trim(elem, "%s")
if elem ~= "" then
break
end
remove(elems, i)
elem = elems[i]
end
if not elem then
break
elseif not ( -- Strip redundant wikilinks.
not elem:match("^()%[%[") or
elem:find("[[", 3, true) or
elem:find("]]", 3, true) ~= #elem - 1 or
elem:find("|", 3, true)
) then
elem = sub(elem, 3, -3)
elem = trim(elem, "%s")
end
elems[i] = elem .. "\n"
i = i + 1
end
-- If sort is enabled, remove duplicates then sort elements.
if require("Module:yesno")(frame.args.sort) then
elems = m_table.removeDuplicates(elems)
require("Module:collation").sort(elems, lang)
end
-- Readd the langcode.
insert(elems, 1, code .. "\n")
-- TODO: Place non-numbered parameters after 1 and before 2.
local template = m_template_parser.getTemplateInvocationName(mw.title.new(parent:getTitle()))
return "{{" .. concat(m_template_parser.buildTemplate(template, elems), "|") .. "}}"
end
return export
h3lvl9lmozilvfv7q6bmma08122l1tj
Module:Category tree/names
828
1365072
9909011
9389187
2026-09-29T06:08:23Z
TongcyDai
53191
9909011
Scribunto
text/plain
local labels = {}
local raw_categories = {}
local handlers = {}
local raw_handlers = {}
-----------------------------------------------------------------------------
-- --
-- LABELS --
-- --
-----------------------------------------------------------------------------
labels["名字"] = {
description = "用於指代特定個人或群體的{{{langname}}}詞語。",
umbrella_parents = {name = "依語義分類的詞", is_label = true, sort = " "},
parents = {"依語義分類的詞", "專有名詞"},
}
labels["女性名字指大詞"] = {
description = "給予女性個體的{{{langname}}}指大名稱。",
breadcrumb = "指大詞",
parents = {"女性名字", "指大名詞"},
}
labels["男性名字指大詞"] = {
description = "給予男性個體的{{{langname}}}指大名字。",
breadcrumb = "指大詞",
parents = {"男性名字", "指大名詞"},
}
labels["中性名字指大詞"] = {
description = "給予女性或男性個體的{{{langname}}}指大名字。",
breadcrumb = "指大詞",
parents = {"中性名字", "指大名詞"},
}
labels["通性姓氏"] = {
description = "男性和女性家庭成員共用的{{{langname}}}名字,而該語言區分男性和女性姓氏。",
breadcrumb = "通性",
parents = {"姓氏"},
}
labels["女性名字指小詞"] = {
description = "賦予女性個體的{{{langname}}}指小詞。",
breadcrumb = "指小詞",
parents = {"女性名字", "指小名詞"},
}
labels["男性名字指小詞"] = {
description = "賦予男性個體的{{{langname}}}指小詞。",
breadcrumb = "指小詞",
parents = {"男性名字", "指小名詞"},
}
labels["中性名字指小詞"] = {
description = "{賦予女性或男性個體的{{{langname}}}指小詞。",
breadcrumb = "指小詞",
parents = {"中性名字", "指小名詞"},
}
labels["女性名字"] = {
description = "賦予女性個體的{{{langname}}}名字。",
breadcrumb = "女性",
parents = {"人名"},
}
labels["female skin names"] = {
description = "{{{langname}}} skin names given to female individuals.",
breadcrumb = "女性",
parents = {"skin names"},
}
labels["女性姓氏"] = {
description = "{{{langname}}}中女性家庭成員共用的名稱。",
breadcrumb = "女性",
parents = {"姓氏"},
}
labels["人名"] = {
description = "賦予個體的{{{langname}}}名字。",
parents = {"名字"},
}
labels["男性名字"] = {
description = "賦予男性個體的{{{langname}}}名字。",
breadcrumb = "男性",
parents = {"人名"},
}
labels["male skin names"] = {
description = "{{{langname}}} skin names given to male individuals.",
breadcrumb = "male",
parents = {"skin names"},
}
labels["男性姓氏"] = {
description = "{{{langname}}}中男性家庭成員共用的名稱。",
breadcrumb = "男性",
parents = {"姓氏"},
}
labels["母名"] = {
description = "表明一個人的母親、祖母或較早的女性祖先的{{{langname}}}名字。",
parents = {"名字"},
}
labels["nomina gentilia"] = {
description = "A Roman [[nomen gentile]] was the \"[[family name]]\" in the [[w:Roman naming convention|convential Roman name]].",
parents = {"名字"},
}
labels["父名"] = {
description = "表明一個人的父親、祖父或較早的男性祖先的{{{langname}}}名字。",
parents = {"名字"},
}
labels["skin names"] = {
description = "{{{langname}}} terms given at birth that are used to refer to individuals from specific marital classes.",
parents = {"proper nouns", "names"},
}
labels["姓氏"] = {
description = "用於表示一個人的家族血緣關係的{{{langname}}}標誌和符號。",
parents = {"名字"},
}
labels["中性名字"] = {
description = "賦予男性或女性個體的{{{langname}}}名字。",
breadcrumb = "中性",
parents = {"人名"},
}
labels["unisex skin names"] = {
description = "{{{langname}}} skin names given either to male or to female individuals.",
breadcrumb = "unisex",
parents = {"skin names"},
}
labels["外名"] = {
description = "{{{langname}}}外名或他稱,即{{{langname}}}名稱與來源語言名稱不同的地名詞語。",
parents = {"名字"},
}
labels["外語人名的譯名"] = {
description = "來自外語人名的{{{langname}}}轉寫、重新拼寫或其他譯名。",
parents = {"名字"},
}
labels["氏族名"] = {
description = "{{{langname}}}中氏族的名稱。",
parents = {"名字"},
}
labels["家名"] = {
description = "{{{langname}}}中家屋的名稱。",
parents = {"名字"},
}
labels["地名"] = {
description = "{{{langname}}}中某地的名字。",
parents = {"名字"},
}
------------------------------------------- 動物名 -------------------------------------------
-- l10n: 對應 enwt 的 animal names / cat names / dog names…({{given name|xx|狗}} 等)
labels["動物名"] = {
description = "賦予動物個體的{{{langname}}}名字。",
parents = {"名字"},
}
for _, animal in ipairs {"貓", "牛", "狗", "馬", "豬"} do
labels[animal .. "名"] = {
description = "賦予[[" .. animal .. "]]的{{{langname}}}名字。",
breadcrumb = animal,
parents = {"動物名"},
}
end
for _, animal in ipairs {"動物", "貓", "牛", "狗", "馬", "豬"} do
labels[animal .. "名指小詞"] = {
description = "賦予" .. (animal == "動物" and "動物個體" or "[[" .. animal .. "]]") ..
"的{{{langname}}}指小名字。",
breadcrumb = "指小詞",
parents = {animal .. "名", "指小名詞"},
}
labels[animal .. "名指大詞"] = {
description = "賦予" .. (animal == "動物" and "動物個體" or "[[" .. animal .. "]]") ..
"的{{{langname}}}指大名字。",
breadcrumb = "指大詞",
parents = {animal .. "名", "指大名詞"},
}
end
------------------------------------------- 父名/母名的性別細分 -------------------------------------------
-- l10n: 對應 enwt 的 male/female/common-gender patronymics 與 matronymics
-- ({{patronymic|ru|男性}}、{{matronymic|ru|女性}} 等)
for _, nymic in ipairs {"父名", "母名"} do
local ancestor = nymic == "母名" and "母親、祖母或較早的女性祖先" or
"父親、祖父或較早的男性祖先"
labels["通性" .. nymic] = {
description = "男女皆可用來表明其" .. ancestor .. "的{{{langname}}}名字,而該語言區分男性和女性" ..
nymic .. "。",
breadcrumb = "通性",
parents = {nymic},
}
labels["男性" .. nymic] = {
description = "男性用來表明其" .. ancestor .. "的{{{langname}}}名字。",
breadcrumb = "男性",
parents = {nymic},
}
labels["女性" .. nymic] = {
description = "女性用來表明其" .. ancestor .. "的{{{langname}}}名字。",
breadcrumb = "女性",
parents = {nymic},
}
end
-- Add 'umbrella_parents' key if not already present.
for key, data in pairs(labels) do
if not data.umbrella_parents then
data.umbrella_parents = "名字子分類"
end
end
-----------------------------------------------------------------------------
-- --
-- RAW CATEGORIES --
-- --
-----------------------------------------------------------------------------
raw_categories["名字子分類"] = {
description = "涵蓋與名字相關主題的綜合分類。",
additional = "{{{umbrella_meta_msg}}}",
parents = {
"綜合元分類",
{name = "名字", is_label = true, sort = " "},
},
}
-----------------------------------------------------------------------------
-- --
-- HANDLERS --
-- --
-----------------------------------------------------------------------------
local function source_name_to_source(nametype, source_name)
local special_sources
if nametype:find("名字") then
special_sources = require("Module:table").listToSet {
"姓氏", "地名", "新詞", "聖經", "月份名"
}
elseif nametype:find("姓氏") then
special_sources = require("Module:table").listToSet {
"人名", "地名", "職業", "父名", "母名",
"普通名詞", "暱稱", "民族名稱", "名字" -- "名字": temp
}
else
-- l10n: 動物名(狗名、貓名……)與父名/母名也會產生「來自……的」分類,
-- 這裡接受上面兩組來源關鍵字的聯集。
special_sources = require("Module:table").listToSet {
"姓氏", "人名", "地名", "職業", "父名", "母名",
"普通名詞", "暱稱", "民族名稱", "新詞", "聖經", "月份名",
"名字" -- "名字": temp
}
end
if special_sources[source_name] then
return source_name
else
return require("Module:languages").getByCanonicalName(source_name, nil,
"allow etym langs", "allow families")
end
end
local function get_source_text(source)
if type(source) == "table" then
return source:getDisplayForm()
else
return source
end
end
local function get_description(lang, nametype, source)
local origintext, addltext
if source == "姓氏" then
origintext = "來自姓氏"
elseif source == "人名" then
origintext = "來自人名"
elseif source == "暱稱" then
origintext = "來自暱稱"
elseif source == "地名" then
origintext = "來自地名"
addltext = " 對於亦作姓用的地名,請參見" .. (
lang and "[[:Category:來自姓氏的{{{langname}}} " .. nametype .. "]]" or
"[[:Category:來自姓氏的" .. nametype .. "]]"
) .. "。"
elseif source == "普通名詞" then
origintext = "來自普通名詞"
elseif source == "月份名" then
-- l10n: 舊版漏譯了 enwt 的 "month names",導致「來自月份名的……」分類頁報 Lua 錯誤
origintext = "轉變自月份名"
elseif source == "新詞" then
origintext = "起源於人造新詞"
addltext = "包括人造名字、基於虛構人物的名字、兩個單詞或名字的組合。對於來源不明的名字,如果強烈懷疑它們是新詞,也可以收錄在本分類中。"
elseif source == "職業" then
origintext = "起源於職業"
elseif source == "父名" then
origintext = "起源於父名"
elseif source == "母名" then
origintext = "起源於母名"
elseif source == "民族名稱" then
origintext = "起源於民族名稱"
elseif source == "聖經" then
-- Hack esp. for Hawaiian names. We should consider changing them to
-- have the source as Biblical Hebrew and mention the derivation from
-- the Bible some other way.
origintext = "起源於聖經"
elseif type(source) == "string" then
error("內部錯誤:無法識別的來源\"" .. source .. "\",只能填寫特定的幾種")
else
local catname = source:getCategoryName()
origintext = "源自[[:Category:" .. catname .. "|" .. catname .. "]]"
if lang and source:getCode() == lang:getCode() then
addltext = "這些名字來源於普通的名詞、當地的神話等。"
end
end
local introtext
if lang then
introtext = "{{{langname}}}"
else
introtext = "本分類收錄"
end
return introtext .. nametype .. origintext ..
"。(包括在該語言的較早階段派生出的名字。)" .. (addltext or "")
end
-- If one of the following families occurs in any of the ancestral families
-- of a given language, use it instead of the three-letter parent
-- (or immediate parent if no three-letter parent).
local high_level_families = require("Module:table").listToSet {
-- Indo-European
"gem", -- Germanic (for gme, gmq, gmw)
"inc", -- Indic (for e.g. pra = Prakrit)
"ine-ana", -- Anatolian (don't keep going to ine)
"ine-toc", -- Tocharian (don't keep going to ine)
"ira", -- Iranian (for e.g. xme = Median, xsc = Scythian)
"sla", -- Slavic (for zle, zls, zlw)
-- Other
"ath", -- Athabaskan (for e.g. apa = Apachean)
"poz", -- Malayo-Polynesian (for e.g. pqe = Eastern Malayo-Polynesian)
"cau-nwc", -- Northwest Caucasian
"cau-nec", -- Northeast Caucasian
}
local function find_high_level_family(lang)
local family = lang:getFamily()
-- (1) If no family, return nil (e.g. for Pictish).
if not family then
return nil
end
-- (2) See if any ancestor family is in `high_level_families`.
-- if so, return it.
local high_level_family = family
while high_level_family do
local high_level_code = high_level_family:getCode()
if high_level_code == "qfa-not" then
-- "not a family"; its own parent, causing an infinite loop.
-- Break rather than return so we get categories like
-- [[Category:English female given names from sign languages]] and
-- [[Category:English female given names from constructed languages]].
break
end
if high_level_families[high_level_code] then
return high_level_family
end
high_level_family = high_level_family:getFamily()
end
-- (3) If the family is of the form 'FOO-BAR', see if 'FOO' is a family.
-- If so, return it.
local basic_family = family:getCode():match("^(.-)%-.*$")
if basic_family then
basic_family = require("Module:families").getByCode(basic_family)
if basic_family then
return basic_family
end
end
-- (4) Fall back to just the family itself.
return family
end
local function match_gendered_nametype(nametype)
local gender, label = mw.ustring.match(nametype, "^([男女中]性)(名字)$")
if gender then
return gender, label
end
end
local function get_parents(lang, nametype, source)
local parents = {}
if lang then
table.insert(parents, {name = nametype, sort = get_source_text(source)})
if type(source) == "table" then
table.insert(parents, {name = "派生自" .. source:getDisplayForm() .. "的詞", sort = " "})
-- If the source is a regular language, put it in a parent category for the high-level language family, e.g. for
-- "Russian female given names from German", put it in a parent category "Russian female given names from Germanic languages"
-- (skipping over West Germanic languages).
--
-- If the source is an etymology language, put it in a parent category for the parent non-etymology language, e.g. for
-- "French male given names from Gascon", put it in a parent category "French male given names from Occitan".
--
-- If the source is a family, put it in a parent category for the parent family.
if source:hasType("family") then
local parent_family = source:getFamily()
if parent_family and parent_family:getCode() ~= "qfa-not" then
table.insert(parents, {
name = "來自" .. parent_family:getDisplayForm() .. "的" .. nametype,
sort = source:getCanonicalName()
})
end
elseif source:hasType("etymology-only") then
local source_parent = source:getFull()
if source_parent and source_parent:getCode() ~= "und" then
table.insert(parents, {
name = "來自" ..source_parent:getDisplayForm() .. "的" .. nametype,
sort = source:getCanonicalName()
})
end
else
local high_level_family = find_high_level_family(source)
if high_level_family then -- may not exist, e.g. for Pictish
table.insert(parents,
{name = "來自" .. high_level_family:getDisplayForm() .. "的" .. nametype,
sort = source:getCanonicalName()
})
end
end
end
local gender, label = match_gendered_nametype(nametype)
if gender then
table.insert(parents, {name = "來自" .. get_source_text(source) .. "的" .. label, sort = gender})
end
else
local gender, label = match_gendered_nametype(nametype)
if gender then
table.insert(parents, {name = "來自" .. get_source_text(source) .. "的" .. label, is_label = true, sort = " "})
elseif type(source) == "table" then
-- FIXME! This is duplicated in [[Module:category tree/poscatboiler/data/terms by etymology]] in the
-- handler for umbrella categories 'Terms derived from SOURCE'.
local first_umbrella_parent =
source:hasType("family") and {name = source:getCategoryName(), raw = true, sort = " "} or
source:hasType("etymology-only") and {name = "Category:" .. source:getCategoryName(), sort = nametype} or
{name = source:getCategoryName(), raw = true, sort = nametype}
table.insert(parents, first_umbrella_parent)
end
table.insert(parents, "名字子分類")
end
return parents
end
table.insert(handlers, function(data)
local source_name, nametype = data.label:match("^來自(.+)的(.*[字稱名姓氏詞])$")
if nametype then
local m_table = require("Module:table")
local personal_name_types = m_table.listToSet(require("Module:names").personal_name_types)
if not personal_name_types[nametype] then
return nil
end
local source = source_name_to_source(nametype, source_name)
if not source then
return nil
end
return {
description = get_description(data.lang, nametype, source),
breadcrumb = "來自" .. get_source_text(source),
parents = get_parents(data.lang, nametype, source),
umbrella = {
description = get_description(nil, nametype, source),
parents = get_parents(nil, nametype, source),
},
}
end
end)
-----------------------------------------------------------------------------
-- --
-- RAW HANDLERS --
-- --
-----------------------------------------------------------------------------
-- Handler for e.g. 'en:Russian male given names'.
table.insert(raw_handlers, function(data)
local langcode, label = data.category:match("^([a-z-]+):(.*)$")
if langcode then
local personal_name_types = require("Module:names").personal_name_types
for _, nametype in ipairs(personal_name_types) do
local sourcename = label:match("^(.+) " .. nametype .. "$")
if sourcename then
local source = require("Module:languages").getByCanonicalName(sourcename, nil, "allow etym")
if source then
return {
description = "Transliterations of " .. source:makeCategoryLink() .. " " .. nametype .. " into {{{langlink}}}.",
lang = langcode,
breadcrumb = sourcename .. " " .. nametype,
parents = {
{ module = "topic cat", args = {code = "{{{langcode}}}", label = "Transliteration of personal names"}, sort = sourcename },
{ name = nametype, is_label = true, lang = source:getCode(), sort = "{{{langname}}}" },
},
}
end
end
end
end
end)
-- Handler for e.g. 'English renderings of Russian male given names' / '俄語男性名字的英語譯名'.
table.insert(handlers, function(data)
local label = data.label:match("^(.*)譯名$")
if label then
local personal_name_types = require("Module:names").personal_name_types
for _, nametype in ipairs(personal_name_types) do
local sourcename = label:match("^(.+)" .. nametype .. "的$")
if sourcename then
local source = require("Module:languages").getByCanonicalName(sourcename, nil, "allow etym")
if source then
return {
description = "來自".. source:makeCategoryLink() .. nametype .."的{{{langlink}}}轉寫、重新拼寫或其他譯文。",
lang = data.lang,
breadcrumb = sourcename .. nametype,
parents = {
{ name = "外語人名的譯名", sort = sourcename },
{ name = nametype, lang = source:getCode(), sort = "{{{langname}}}" },
},
umbrella = {
description = "各語言中來自" .. source:makeCategoryLink() .. nametype .. "的轉寫、重新拼寫或其他譯文",
parents = {{name = "外語人名的譯名", is_label = true, sort = label}},
},
}
end
end
end
end
end)
return {LABELS = labels, RAW_CATEGORIES = raw_categories, HANDLERS = handlers, RAW_HANDLERS = raw_handlers}
6uow9ud0sxjbotgkq77pq23h9c18ks9
Module:Form of
828
1365350
9909065
9739602
2026-09-29T07:25:51Z
TongcyDai
53191
同步 enwikt:pron qualifier → decorations
9909065
Scribunto
text/plain
local export = {}
export.force_cat = false -- for testing; set to true to display categories even on non-mainspace pages
local debug_track_module = "Module:debug/track"
local etymology_module = "Module:etymology"
local form_of_cats_module = "Module:form of/cats"
local form_of_data_module = "Module:form of/data"
local form_of_data1_module = "Module:form of/data/1"
local form_of_data2_module = "Module:form of/data/2"
local form_of_functions_module = "Module:form of/functions"
local form_of_lang_data_module_prefix = "Module:form of/lang-data/"
local function_module = "Module:fun"
local headword_data_module = "Module:headword/data"
local json_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local load_module = "Module:load"
local parse_utilities_module = "Module:parse utilities"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local table_deep_equals_module = "Module:table/deepEquals"
local utilities_module = "Module:utilities"
local anchor_encode = mw.uri.anchorEncode
local concat = table.concat
local dump = mw.dumpObject
local fetch_categories_and_labels -- Defined below.
local format_form_of -- Defined below.
local get_tag_display_form -- Defined below.
local get_tag_set_display_form -- Defined below.
local insert = table.insert
local ipairs = ipairs
local is_link_or_html -- Defined below.
local list_to_text = mw.text.listToText
local lookup_shortcut -- Defined below.
local lookup_tag -- Defined below.
local normalize_tag_set -- Defined below.
local parse_tag_set_properties -- Defined below.
local require = require
local sort = table.sort
local split_tag_set -- Defined below.
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 append(...)
append = require(table_module).append
return append(...)
end
local function contains(...)
contains = require(table_module).contains
return contains(...)
end
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function deep_copy(...)
deep_copy = require(table_module).deepCopy
return deep_copy(...)
end
local function deep_equals(...)
deep_equals = require(table_deep_equals_module)
return deep_equals(...)
end
local function extend(...)
extend = require(table_module).extend
return extend(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function full_link(...)
full_link = require(links_module).full_link
return full_link(...)
end
local function insert_if_not(...)
insert_if_not = require(table_module).insertIfNot
return insert_if_not(...)
end
local function is_subset_list(...)
is_subset_list = require(table_module).isSubsetList
return is_subset_list(...)
end
local function iterate_from(...)
iterate_from = require(function_module).iterateFrom
return iterate_from(...)
end
local function join_multiparts(...)
join_multiparts = require(form_of_functions_module).join_multiparts
return join_multiparts(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
local function parse_inline_modifiers(...)
parse_inline_modifiers = require(parse_utilities_module).parse_inline_modifiers
return parse_inline_modifiers(...)
end
local function remove_links(...)
remove_links = require(links_module).remove_links
return remove_links(...)
end
local function safe_load_data(...)
safe_load_data = require(load_module).safe_load_data
return safe_load_data(...)
end
local function safe_require(...)
safe_require = require(load_module).safe_require
return safe_require(...)
end
local function serial_comma_join(...)
serial_comma_join = require(table_module).serialCommaJoin
return serial_comma_join(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function show_labels(...)
show_labels = require(labels_module).show_labels
return show_labels(...)
end
local function slice(...)
slice = require(table_module).slice
return slice(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function ucfirst(...)
ucfirst = require(string_utilities_module).ucfirst
return ucfirst(...)
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 cat_functions
local function get_cat_functions()
cat_functions, get_cat_functions = require(form_of_functions_module).cat_functions, nil
return cat_functions
end
local default_pagename
local function get_default_pagename()
default_pagename, get_default_pagename = load_data(headword_data_module).pagename, nil
return default_pagename
end
local display_handlers
local function get_display_handlers()
display_handlers, get_display_handlers = require(form_of_functions_module).display_handlers, nil
return display_handlers
end
local m_cats_data
local function get_m_cats_data()
m_cats_data, get_m_cats_data = load_data(form_of_cats_module), nil
return m_cats_data
end
local m_data
local function get_m_data()
-- Needs require.
m_data, get_m_data = require(form_of_data_module), nil
return m_data
end
local m_data1
local function get_m_data1()
m_data1, get_m_data1 = load_data(form_of_data1_module), nil
return m_data1
end
local m_data2
local function get_m_data2()
m_data2, get_m_data2 = load_data(form_of_data2_module), nil
return m_data2
end
local m_pos_data
local function get_m_pos_data()
m_pos_data, get_m_pos_data = load_data(headword_data_module).pos_aliases, nil
return m_pos_data
end
--[==[ intro:
This module implements the underlying processing of {{tl|form of}}, {{tl|inflection of}} and specific variants such as
{{tl|past participle of}} and {{tl|alternative spelling of}}. Most of the logic in this file is to handle tags in
{{tl|inflection of}}. Other related files:
* [[Module:form of/templates]] contains the majority of the logic that implements the templates themselves.
* [[Module:form of/data/1]] is a data-only file containing information on the more common inflection tags, listing the
tags, their shortcuts, the category they belong to (tense-aspect, case, gender, voice-valence, etc.), the appropriate
glossary link and the wikidata ID.
* [[Module:form of/data/2]] is a data-only file containing information on the less common inflection tags, in the same
format as [[Module:form of/data/1]].
* [[Module:form of/lang-data/LANGCODE]] is a data-only file containing information on the language-specific inflection
tags for the language with code LANGCODE, in the same format as [[Module:form of/data/1]]. Language-specific tags
override general tags.
* [[Module:form of/cats]] is a data-only file listing the language-specific categories that are added when the
appropriate combinations of tags are seen for a given language.
* [[Module:form of/functions]] contains functions for use with [[Module:form of/data/1]] and [[Module:form of/cats]].
They are contained in this module because data-only modules can't contain code. The functions in this file are of two
types:
*# Display handlers allow for customization of the display of multipart tags (see below). Currently there is only
one such handler, for handling multipart person tags such as `1//2//3`.
*# Cat functions allow for more complex categorization logic, and are referred to by name in [[Module:form of/cats]].
Currently no such functions exist.
The following terminology is used in conjunction with {{tl|inflection of}}:
* A ''tag'' is a single grammatical item, as specified in a single numbered parameter of {{tl|inflection of}}. Examples
are `masculine`, `nominative`, or `first-person`. Tags may be abbreviated, e.g. `m` for `masculine`, `nom` for
`nominative`, or `1` for `first-person`. Such abbreviations are called ''aliases'', and some tags have multiple
equivalent aliases (e.g. `p` or `pl` for `plural`). The full, non-abbreviated form of a tag is called its
''canonical form''.
* The ''display form'' of a tag is the way it's displayed to the user. Usually the displayed text of the tag is the same
as its canonical form, and it normally functions as a link to a glossary entry explaining the tag. Usually the link is
to an entry in [[Appendix:Glossary]], but sometimes the tag is linked to an individual dictionary entry or to a
Wikipedia entry. Occasionally, the display text differs from the canonical form of the tag. An example is the tag
`comparative case`, which has the display text read as simply `comparative`. Normally, tags referring to cases don't
have the word "case" in them, but in this case the tag `comparative` was already used as an alias for the tag
`comparative degree`, so the tag was named `comparative case` to avoid clashing. A similar situation occurs with
`adverbial case` vs. the grammar tag `adverbial` (as in `adverbial participle`).
* A ''tag set'' is an ordered list of tags, which together express a single inflection, for example, `1|s|pres|ind`,
which can be expanded to canonical-form tags as `first-person|singular|present|indicative`.
* A ''conjoined tag set'' is a tag set that consists of multiple individual tag sets separated by a semicolon, e.g.
`1|s|pres|ind|;|2|s|imp`, which specifies two tag sets, `1|s|pres|ind` as above and `2|s|imp` (in canonical form,
`second-person|singular|imperative`). Multiple tag sets specified in a single call to {{tl|inflection of}} are
specified in this fashion. Conjoined tag sets can also occur in list-tag shortcuts.
* A ''multipart tag'' is a tag that embeds multiple tags within it, such as `f//n` or `nom//acc//voc`. These are used in
the case of [[syncretism]], when the same form applies to multiple inflections. Examples are the Spanish present
subjunctive, where the first-person and third-person singular have the same form (e.g. {{m|es|siga}} from
{{m|es|seguir|t=to follow}}), or Latin third-declension adjectives, where the dative and ablative plural of all
genders have the same form (e.g. {{m|la|omnibus}} from {{m|la|omnis|t=all}}). These would be expressed respectively as
`1//3|s|pres|sub` and `dat//abl|m//f//n|p`, where the use of the multipart tag compactly encodes the syncretism and
avoids the need to individually list out all of the inflections. Multipart tags currently display as a list separated
by a slash, e.g. ''dative/ablative'' or ''masculine/feminine/neuter'' where each individual word is linked
appropriately. As a special case, multipart tags involving persons display specially; for example, the multipart tag
`1//2//3` displays as ''first-, second- and third-person'', with the word "person" occurring only once.
* A ''two-level multipart tag'' is a special type of multipart tag that joins two or more tag sets instead of joining
individual tags. The tags within the tag set are joined by a colon, e.g. `1:s//3:p`, which is displayed as
''first-person singular and third-person plural'', e.g. for use with the form {{m|grc|μέλλον}} of the verb
{{m|grc|μέλλω|t=to intend}}, which uses the tag set `1:s//3:p|impf|actv|indc|unaugmented` to express the syncretism
between the first singular and third plural forms of the imperfect active indicative unaugmented conjugation.
Two-level multipart tags should be used sparingly; if in doubt, list out the inflections separately. [FIXME: Make
two-level multipart tags obsolete.]
* A ''shortcut'' is a tag that expands to any type of tag described above, or to any type of tag set described above.
Aliases are a particular type of shortcut whose expansion is a single non-multipart tag.
* A ''multipart shortcut'' is a shortcut that expands into a multipart tag, for example `123`, which expands to the
multipart tag `1//2//3`. Only the most common such combinations exist as shortcuts.
* A ''list shortcut'' is a special type of shortcut that expands to a list of tags instead of a single tag. For example,
the shortcut `1s` expands to `1|s` (first-person singular). Only the most common such combinations exist as shortcuts.
* A ''conjoined shortcut'' is a special type of list shortcut that consists of a conjoined tag set (multiple logical tag
sets). For example, the English language-specific shortcut `ed-form` expands to `spast|;|past|part`, expressing the
common syncretism between simple past and past participle in English (and in this case, `spast` is itself a list
shortcut that expands to `simple|past`).]==]
-- Add tracking category for PAGE when called from {{inflection of}} or
-- similar TEMPLATE. The tracking category linked to is
-- [[Wiktionary:Tracking/inflection of/PAGE]].
local function track(page)
debug_track("inflection of/" ..
-- avoid including links in pages (may cause error)
page:gsub("%[", "("):gsub("%]", ")"):gsub("|", "!")
)
end
local function wrap_in_span(text, classes)
if classes then
return ("<span class='%s'>%s</span>"):format(classes, text)
else
return text
end
end
local function show_linked_term(data)
local termobj, face, span_classes, ok_to_destructively_modify, overall_lang, text_classes =
data.termobj, data.face, data.span_classes, data.ok_to_destructively_modify, data.overall_lang,
data.text_classes
local need_to_copy, pretext_lang
local categories = {}
if overall_lang and overall_lang:getCode() ~= termobj.lang:getCode() then
local lang_display
lang_display, categories = require(etymology_module).insert_source_cat_get_display {
lang = data.overall_lang,
source = termobj.lang,
}
pretext_lang = wrap_in_span(lang_display, text_classes)
end
local has_decoration = termobj.q or termobj.qq or termobj.a or termobj.aa or termobj.l or termobj.ll or termobj.refs
-- `show_decorations` is set by parse_{term,list}_with_inline_modifiers_and_separate_params in
-- [[Module:parameter utilities]], called in [[Module:form of/templates]], so in most circumstances
-- it's already set and we don't need to do a shallow-copy to set it.
local need_to_set_show_decorations = has_decoration and not termobj.show_decorations
need_to_copy = not ok_to_destructively_modify and (pretext_lang or need_to_set_show_decorations)
if need_to_copy then
termobj = shallow_copy(termobj)
end
if pretext_lang then
termobj.pretext = pretext_lang
end
if need_to_set_show_decorations then
termobj.show_decorations = true
end
return wrap_in_span(full_link(termobj, face), span_classes), categories
end
--[==[
Lowest-level implementation of form-of templates, including the general {{tl|form of}} as well as those that deal with
inflection tags, such as the general {{tl|inflection of}}, semi-specific variants such as {{tl|participle of}}, and
specific variants such as {{tl|past participle of}}. `data` contains all the information controlling the display, with
the following fields:
* `.lang`: Overall language of the form-of template. If specified, any lemmas, enclitics or base lemmas that are of a
different language will have that language displayed before the term in question.
* `.text`: Text to insert before the lemmas. Wrapped in the value of `.text_classes`, or its default; see below.
* `.lemmas`: List of objects describing the lemma(s) of which the term in question is a non-lemma form. These are passed
directly to {full_link()} in [[Module:links]]. Each object should have at minimum a `.lang` field containing the
language of the lemma and a `.term` field containing the lemma itself. Each object is formatted using {full_link()}
and then if there are more than one, they are joined using {serialCommaJoin()} in [[Module:table]]. Alternatively,
`.lemmas` can be a string, which is displayed directly, or omitted, to show no lemma links and omit the connecting
text.
* `.lemma_face`: "Face" to use when displaying the lemma objects. Usually should be set to {"term"}.
* `.conj`: Conjunction or separator to use when joining multiple lemma objects. If {nil}, defaults to {"and"}. If this
has the value {false}, the lemmas are preceded with the `.separator` field in each lemma.
* `.enclitics`: List of enclitics to display after the lemmas, in parens.
* `.enclitic_conj`: Conjunction or separator to use when joining multiple enclitics. Defaults to {"and"}.
* `.base_lemmas`: List of base lemmas to display after the lemmas, in the case where the lemmas in `.lemmas` are
themselves forms of another lemma (the base lemma), e.g. a comparative, superlative or participle. Each object is of
the form { { paramobj = PARAM_OBJ, lemmas = {LEMMA_OBJ, LEMMA_OBJ, ...}, conj = "CONJ" }} where PARAM_OBJ describes
the properties of the base lemma parameter (i.e. the relationship between the intermediate and base lemmas);
LEMMA_OBJ is an object suitable to be passed to {full_link()} in [[Module:links]]; and CONJ is the conjunction to
join multiple lemmas with, defaulting to {"and"}. PARAM_OBJ is of the format
{ { param = "PARAM", tags = {"TAG", "TAG", ...} } where PARAM is the name of the parameter to {{tl|inflection of}}
etc. that holds the base lemma(s) of the specified relationship and the tags describe the relationship, such as
{ {"comd"}} or { {"past", "part"}}.
* `.text_classes`: CSS classes used to wrap the tag text and lemma links. Default is
{"form-of-definition use-with-mention"}. Use `false` for no wrapping.
* `.lemma_classes`: Additional CSS classes used to wrap the lemma links. Default is {"form-of-definition-link"}.
Use `false` for no wrapping.
* `.posttext`: Additional text to display after the lemma links.
* `.ok_to_destructively_modify`: If set, data structures (including the nested lemma structures) can be modified
in-place to save memory; otherwise they will be copied before modifying.
Returns two values, the formatted string and any categories to add the page to (which will arise if `.lang` is
specified and a language other than `.lang` is given in one of the lemmas in `.lemmas` or enclitics in `.enclitics`).
]==]
function export.format_form_of(data)
if type(data) ~= "table" then
error("Internal error: First argument must now be a table of arguments")
end
local text_classes = data.text_classes
if text_classes == nil and not data.nowrap then
text_classes = "form-of-definition use-with-mention"
end
local lemma_classes = data.lemma_classes
if lemma_classes == nil then
lemma_classes = "form-of-definition-link"
end
local parts = {}
if text_classes then
insert(parts, "<span class='" .. text_classes .. "'>")
end
local categories = {}
if data.lemmas then
if type(data.lemmas) == "string" then
insert(parts, wrap_in_span(data.lemmas, lemma_classes))
else
local formatted_terms = {}
for _, lemma in ipairs(data.lemmas) do
local linked_term, this_categories = show_linked_term {
termobj = lemma,
face = data.lemma_face,
span_classes = lemma_classes,
ok_to_destructively_modify = data.ok_to_destructively_modify,
overall_lang = data.lang,
text_classes = text_classes
}
if this_categories[1] then
extend(categories, this_categories)
end
if data.conj == false and lemma.separator then
insert(formatted_terms, lemma.separator)
end
insert(formatted_terms, linked_term)
end
if data.conj == false then
insert(parts, concat(formatted_terms))
else
insert(parts, serial_comma_join(formatted_terms, {conj = data.conj or " 和 ", separator = "、", skip_serial_comma = true}))
end
end
if data.lit then
insert(parts, ",字面意思是" .. require(links_module).mark(data.lit, "gloss"))
end
end
-- 中文語序:先輸出 lemma,再加上 " 的 ",最後才是 text (形式)
if data.of_text then
insert(parts, data.of_text)
end
if data.text ~= "" then
insert(parts, data.text)
end
if data.enclitics and #data.enclitics > 0 then
if text_classes then
insert(parts, "</span>")
end
local formatted_terms = {}
for _, enclitic in ipairs(data.enclitics) do
local linked_term, this_categories = show_linked_term {
termobj = enclitic,
face = data.lemma_face,
span_classes = lemma_classes,
ok_to_destructively_modify = data.ok_to_destructively_modify,
overall_lang = data.lang,
text_classes = text_classes
}
if this_categories[1] then
extend(categories, this_categories)
end
insert(formatted_terms, linked_term)
end
insert(parts, " (")
insert(parts, wrap_in_span("帶附著語素", text_classes))
insert(parts, serial_comma_join(formatted_terms, {conj = data.enclitic_conj or " 和 ", separator = "、", skip_serial_comma = true}))
insert(parts, ")")
if text_classes then
insert(parts, "<span class='" .. text_classes .. "'>")
end
end
if data.base_lemmas and #data.base_lemmas > 0 then
for _, base_lemma in ipairs(data.base_lemmas) do
insert(parts, ",")
if text_classes then
insert(parts, "</span>")
end
insert(parts, (export.tagged_inflections {
lang = data.lang or base_lemma.lemmas[1].lang,
tags = base_lemma.paramobj.tags,
lemmas = base_lemma.lemmas,
conj = base_lemma.conj or " 和 ",
lemma_face = data.lemma_face,
no_format_categories = true,
nocat = true,
text_classes = data.text_classes,
ok_to_destructively_modify = data.ok_to_destructively_modify,
}))
if text_classes then
insert(parts, "<span class='" .. text_classes .. "'>")
end
end
end
if data.base_lemmas and #data.base_lemmas > 0 then
for _, base_lemma in ipairs(data.base_lemmas) do
insert(parts, ", the ")
if text_classes then
insert(parts, "</span>")
end
insert(parts, (export.tagged_inflections {
lang = data.lang or base_lemma.lemmas[1].lang,
tags = base_lemma.paramobj.tags,
lemmas = base_lemma.lemmas,
conj = base_lemma.conj or "and",
lemma_face = data.lemma_face,
no_format_categories = true,
nocat = true,
text_classes = data.text_classes,
ok_to_destructively_modify = data.ok_to_destructively_modify,
}))
if text_classes then
insert(parts, "<span class='" .. text_classes .. "'>")
end
end
end
-- FIXME, should posttext go before enclitics? If so we need to have separate handling for the
-- final colon when there are multiple tag sets in tagged_inflections().
if data.posttext then
insert(parts, data.posttext)
end
if text_classes then
insert(parts, "</span>")
end
return concat(parts), categories
end
format_form_of = export.format_form_of
--[==[
Return true if `tag` contains an internal link or HTML.]==]
function export.is_link_or_html(tag)
return tag:find("[[", nil, true) or tag:find("|", nil, true) or tag:find("<", nil, true)
end
is_link_or_html = export.is_link_or_html
--[==[
Look up a tag (either a shortcut of any sort of a canonical long-form tag) and return its expansion. The expansion
will be a string unless the shortcut is a list-tag shortcut such as `1s`; in that case, the expansion will be a
list. The caller must handle both cases. Only one level of expansion happens; hence, `acc` expands to {"accusative"},
`1s` expands to { {"1", "s"}} (not to { {"first", "singular"}}) and `123` expands to {"1//2//3"}. The expansion will be
the same as the passed-in tag in the following circumstances:
# The tag is `;` (this is special-cased, and no lookup is done).
# The tag is a multipart tag such as `nom//acc` (this is special-cased, and no lookup is done).
# The tag contains a raw link (this is special-cased, and no lookup is done).
# The tag contains HTML (this is special-cased, and no lookup is done).
# The tag is already a canonical long-form tag.
# The tag is unrecognized.
This function first looks up in the lang-specific data module [[Module:form of/lang-data/LANGCODE]], then in
[[Module:form of/data/1]] (which includes more common non-lang-specific tags) and finally (only if the tag is not
recognized as a shortcut or canonical tag, and is not of types 1-4 above) in [[Module:form of/data/2]].
If the expansion is a string and is different from the tag, track it if `do_track` is true.]==]
function export.lookup_shortcut(tag, lang, do_track)
-- If there is HTML or a link in the tag, return it directly; don't try
-- to look it up, which will fail.
if tag == ";" or tag:find("//", nil, true) or is_link_or_html(tag) then
return tag
end
local expansion
while lang do
local langdata = safe_load_data(form_of_lang_data_module_prefix .. lang:getCode())
-- If this is a canonical long-form tag, just return it, and don't check for shortcuts. This is an
-- optimization; see below.
if langdata then
if langdata.tags[tag] then
return tag
end
expansion = langdata.shortcuts[tag]
if expansion then
break
end
end
-- If the language has a parent (i.e. a superordinate variety), try again with that.
lang = lang:getParent()
end
if not expansion then
-- If this is a canonical long-form tag, just return it, and don't check for shortcuts (which will cause
-- [[Module:form of/data/2]] to be loaded, because there won't be a shortcut entry in [[Module:form of/data/1]] --
-- or, for that matter, in [[Module:form of/data/2]]). This is an optimization; the code will still work without
-- it, but will use up more memory.
if (m_data1 or get_m_data1()).tags[tag] then
return tag
end
expansion = m_data1.shortcuts[tag]
end
if not expansion then
expansion = (m_data2 or get_m_data2()).shortcuts[tag]
end
if not expansion then
return tag
end
-- Maybe track the expansion if it's not the same as the raw tag.
if do_track and expansion ~= tag and type(expansion) == "string" then
track("tag/" .. tag)
end
return expansion
end
lookup_shortcut = export.lookup_shortcut
--[==[
Look up a normalized/canonicalized tag and return the data object associated with it. If the tag isn't found, return
nil. This first looks up in the lang-specific data module [[Module:form of/lang-data/LANGCODE]], then in
[[Module:form of/data/1]] (which includes more common non-lang-specific tags) and then finally in
[[Module:form of/data/2]].]==]
function export.lookup_tag(tag, lang)
while lang do
local langdata = safe_load_data(form_of_lang_data_module_prefix .. lang:getCode())
local tag = langdata and langdata.tags[tag]
if tag then
return tag
end
-- If the language has a parent (i.e. a superordinate variety), try again with that.
lang = lang:getParent()
end
local tagobj = (m_data1 or get_m_data1()).tags[tag]
if tagobj then
return tagobj
end
local tagobj2 = (m_data2 or get_m_data2()).tags[tag]
if tagobj2 then
return tagobj2
end
return nil
end
lookup_tag = export.lookup_tag
-- Normalize a single tag, which may be a shortcut but should not be a multipart tag, a multipart shortcut or a list
-- shortcut.
local function normalize_single_tag(tag, lang, do_track)
local expansion = lookup_shortcut(tag, lang, do_track)
if type(expansion) ~= "string" then
error("Tag '" .. tag .. "' is a list shortcut, which is not allowed here")
end
tag = expansion
if not lookup_tag(tag, lang) and do_track then
-- If after all expansions and normalizations we don't recognize the canonical tag, track it.
track("unknown")
track("unknown/" .. tag)
end
return tag
end
--[=[
Normalize a component of a multipart tag. This should not have any // in it, but may join multiple individual tags with
a colon, and may be a single list-tag shortcut, which is treated as if colon-separated. The return value may be a list
of tags.
]=]
local function normalize_multipart_component(tag, lang, do_track)
-- If there is HTML or a link in the tag, don't try to split on colon. A colon may legitimately occur in either one,
-- and we don't want these things parsed. Note that we don't do this check before splitting on //, which we don't
-- expect to occur in links or HTML; see comment in normalize_tag().
if is_link_or_html(tag) then
return tag
end
local components = split(tag, ":", true)
if #components == 1 then
-- We allow list-tag shortcuts inside of multipart tags, e.g.
-- '1s//3p'. Check for this now.
tag = lookup_shortcut(tag, lang, do_track)
if type(tag) == "table" then
-- Temporary tracking as we will disallow this.
track("list-tag-inside-of-multipart")
-- We found a list-tag shortcut; treat as if colon-separated.
components = tag
else
return normalize_single_tag(tag, lang, do_track)
end
end
local normtags = {}
-- Temporary tracking as we will disallow this.
track("two-level-multipart")
for _, component in ipairs(components) do
if do_track then
-- There are multiple components; track each of the individual
-- raw tags.
track("tag/" .. component)
end
insert(normtags, normalize_single_tag(component, lang, do_track))
end
return normtags
end
--[=[
Normalize a single tag. The return value may be a list (in the case of multipart tags), which will contain nested lists
in the case of two-level multipart tags.
]=]
local function normalize_tag(tag, lang, do_track)
-- We don't check for links or HTML before splitting on //, which we don't expect to occur in links or HTML. Doing
-- it this way allows for a tag like '{{lb|grc|Epic}}//{{lb|grc|Ionic}}' to function correctly (the template calls
-- will be expanded before we process the tag, and will contain links and HTML). The only check we do is for a URL,
-- which shouldn't normally occur, but might if the user tries to put an external link into the tag. URL's with //
-- normally have the sequence ://, which should never normally occur when // and : are used in their normal ways.
if tag:find("://", nil, true) then
return tag
end
local split_tags = split(tag, "//", true)
if #split_tags == 1 then
local retval = normalize_multipart_component(tag, lang, do_track)
if type(retval) == "table" then
-- The user gave a tag like '1:s', i.e. with colon but without //. Allow this, but we need to return a
-- nested list.
return {retval}
end
return retval
end
local normtags = {}
for _, single_tag in ipairs(split_tags) do
if do_track then
-- If the tag was a multipart tag, track each of individual raw tags.
track("tag/" .. single_tag)
end
insert(normtags, normalize_multipart_component(single_tag, lang, do_track))
end
return normtags
end
--[==[
Normalize a tag set (a list of tags) into its canonical-form tags. The return value is a list of normalized tag sets
(a list because of there may be conjoined shortcuts among the input tags). A normalized tag set is a list of tag
elements, where each element is either a string (the canonical form of a tag), a list of such strings (in the case of
multipart tags) or a list of lists of such strings (in the case of two-level multipart tags). For example, the multipart
tag `nom//acc//voc` will be represented in canonical form as { {"nominative", "accusative", "vocative"}}, and the
two-level multipart tag `1:s//3:p` will be represented as { {{"first-person", "singular"}, {"third-person", "plural"}}}.
Example 1:
{normalize_tag_set({"nom//acc//voc", "n", "p"})} = { {{{"nominative", "accusative", "vocative"}, "masculine", "plural"}}}
Example 2:
{normalize_tag_set({"ed-form"}, ENGLISH)} = { {{"simple", "past"}, {"past", "participle"}}}
Example 3:
{normalize_tag_set({"archaic", "ed-form"}, ENGLISH)} = { {{"archaic", "simple", "past"}, {"archaic", "past", "participle"}}}]==]
function export.normalize_tag_set(tag_set, lang, do_track)
-- We track usage of shortcuts, normalized forms and (in the case of multipart tags or list tags) intermediate
-- forms. For example, if the tags 1s|mn|gen|indefinite are passed in, we track the following:
-- [[Wiktionary:Tracking/inflection of/tag/1s]]
-- [[Wiktionary:Tracking/inflection of/tag/1]]
-- [[Wiktionary:Tracking/inflection of/tag/s]]
-- [[Wiktionary:Tracking/inflection of/tag/first-person]]
-- [[Wiktionary:Tracking/inflection of/tag/singular]]
-- [[Wiktionary:Tracking/inflection of/tag/mn]]
-- [[Wiktionary:Tracking/inflection of/tag/m//n]]
-- [[Wiktionary:Tracking/inflection of/tag/m]]
-- [[Wiktionary:Tracking/inflection of/tag/n]]
-- [[Wiktionary:Tracking/inflection of/tag/masculine]]
-- [[Wiktionary:Tracking/inflection of/tag/neuter]]
-- [[Wiktionary:Tracking/inflection of/tag/gen]]
-- [[Wiktionary:Tracking/inflection of/tag/genitive]]
-- [[Wiktionary:Tracking/inflection of/tag/indefinite]]
local output_tag_set = {}
local saw_semicolon = false
for _, tag in ipairs(tag_set) do
if do_track then
-- Track the raw tag.
track("tag/" .. tag)
end
-- Expand the tag, which may generate a new tag (either a fully canonicalized tag, a multipart tag, or a list
-- of tags).
tag = lookup_shortcut(tag, lang, do_track)
if type(tag) == "table" then
if contains(tag, ";") then
-- If we saw a conjoined shortcut, we need to use a more general algorithm that can expand a single
-- tag set into multiple.
saw_semicolon = true
break
end
for _, t in ipairs(tag) do
if do_track then
-- If the tag expands to a list of raw tags, track each of those.
track("tag/" .. t)
end
insert(output_tag_set, normalize_tag(t, lang, do_track))
end
else
insert(output_tag_set, normalize_tag(tag, lang, do_track))
end
end
if not saw_semicolon then
return {output_tag_set}
end
-- Use a more general algorithm that handles conjoined shortcuts.
output_tag_set = {}
for i, tag in ipairs(tag_set) do
if do_track then
-- Track the raw tag.
track("tag/" .. tag)
end
-- Expand the tag, which may generate a new tag (either a fully canonicalized tag, a multipart tag, or a list
-- of tags).
tag = lookup_shortcut(tag, lang, do_track)
if type(tag) == "table" then
local output_tag_sets = {}
local shortcut_tag_sets = split_tag_set(tag)
local normalized_shortcut_tag_sets = {}
for _, shortcut_tag_set in ipairs(shortcut_tag_sets) do
extend(normalized_shortcut_tag_sets,
normalize_tag_set(shortcut_tag_set, lang, do_track))
end
local after_tags = slice(tag_set, i + 1)
local normalized_after_tags_sets = normalize_tag_set(after_tags, lang, do_track)
for _, normalized_shortcut_tag_set in ipairs(normalized_shortcut_tag_sets) do
for _, normalized_after_tags_set in ipairs(normalized_after_tags_sets) do
insert(output_tag_sets, append(output_tag_set, normalized_shortcut_tag_set,
normalized_after_tags_set))
end
end
return output_tag_sets
else
insert(output_tag_set, normalize_tag(tag, lang, do_track))
end
end
error("Internal error: Should not get here")
end
normalize_tag_set = export.normalize_tag_set
--[==[
Split a tag set that may consist of multiple semicolon-separated tag sets into the component tag sets.]==]
function export.split_tag_set(tag_set)
local split_tag_sets = {}
local cur_tag_set = {}
for _, tag in ipairs(tag_set) do
if tag == ";" then
if #cur_tag_set > 0 then
insert(split_tag_sets, cur_tag_set)
end
cur_tag_set = {}
else
insert(cur_tag_set, tag)
end
end
if #cur_tag_set > 0 then
insert(split_tag_sets, cur_tag_set)
end
return split_tag_sets
end
split_tag_set = export.split_tag_set
local tag_set_param_mods = {
lb = {
item_dest = "labels",
convert = function(arg, _parse_err)
return split(arg, "//", true)
end,
}
}
--[==[
Parse tag set properties from a tag set (list of tags). Currently no per-tag properties are recognized, and the only
per-tag-set property recognized is `<lb:...>` for specifing label(s) for the tag set. Per-tag-set properties must be
attached to the last tag.]==]
function export.parse_tag_set_properties(tag_set)
local function generate_tag_set_obj(last_tag)
tag_set[#tag_set] = last_tag
return {tags = tag_set}
end
local last_tag = tag_set[#tag_set]
-- Check for inline modifier, e.g. מרים<tr:Miryem>. But exclude HTML entry with <span ...>, <i ...>, <br/> or
-- similar in it, caused by wrapping an argument in {{l|...}}, {{af|...}} or similar. Basically, all tags of
-- the sort we parse here should consist of a less-than sign, plus letters, plus a colon, e.g. <lb:...>, so if
-- we see a tag on the outer level that isn't in this format, we don't try to parse it. The restriction to the
-- outer level is to allow generated HTML inside of e.g. qualifier tags, such as foo<q:similar to {{m|fr|bar}}>.
if last_tag:find("<", nil, true) and not last_tag:find("^[^<]*<%l*[^%l:]") then
return parse_inline_modifiers(last_tag, {
param_mods = tag_set_param_mods,
generate_obj = generate_tag_set_obj,
})
else
return generate_tag_set_obj(last_tag)
end
end
parse_tag_set_properties = export.parse_tag_set_properties
local function normalize_pos(pos)
if not pos then
return nil
end
return (m_pos_data or get_m_pos_data())[pos] or pos
end
-- Return the display form of a single canonical-form tag. The value
-- passed in must be a string (i.e. it cannot be a list describing a
-- multipart tag). To handle multipart tags, use get_tag_display_form().
-- A truthy `nolink` suppresses linking.
local function get_single_tag_display_form(normtag, lang, nolink)
local data = lookup_tag(normtag, lang)
local display = normtag
-- If the tag has a special display form, use it
if data and data.display then
display = data.display
if nolink then
display = remove_links(display)
end
end
-- If there is a nonempty glossary index, then show a link to it
if not nolink then
local glossary = data and data[(m_data or get_m_data()).GLOSSARY]
if glossary ~= nil then
if glossary == m_data.WIKT then
display = "[[" .. normtag .. "|" .. display .. "]]"
elseif glossary == m_data.WP then
display = "[[w:" .. normtag .. "|" .. display .. "]]"
elseif glossary == m_data.APPENDIX then
display = "[[Appendix:Glossary#" .. anchor_encode(normtag) .. "|" .. display .. "]]"
elseif type(glossary) ~= "string" then
error(("Internal error: Wrong type %s for glossary value %s for tag %s"):format(
type(glossary), dump(glossary), normtag))
else
local link = glossary:match("^wikt:(.*)")
if link then
display = "[[" .. link .. "|" .. display .. "]]"
end
if not link then
link = glossary:match("^w:(.*)")
if link then
display = "[[w:" .. link .. "|" .. display .. "]]"
end
end
if not link then
display = "[[Appendix:Glossary#" .. anchor_encode(glossary) .. "|" .. display .. "]]"
end
end
end
end
return display
end
--[==[
Turn a canonicalized tag spec (which describes a single, possibly multipart tag) into the displayed form. The tag spec
may be a string (a canonical-form tag); a list of canonical-form tags (in the case of a simple multipart tag); or a
list of mixed canonical-form tags and lists of such tags (in the case of a two-level multipart tag). `joiner` indicates
how to join the parts of a multipart tag, and can be either {"and"} ("foo and bar", or "foo, bar and baz" for 3 or
more), {"slash"} ("foo/bar"), {"en-dash"} ("foo–bar") or {nil}, which uses the global default found in
{multipart_join_strategy()} in [[Module:form of/functions]]. (NOTE: The global default is {"slash"} and this seems
unlikely to change.) A truthy `nolink` suppresses linking.]==]
function export.get_tag_display_form(tagspec, lang, joiner, nolink)
if type(tagspec) == "string" then
return get_single_tag_display_form(tagspec, lang, nolink)
end
-- We have a multipart tag. See if there's a display handler to display them specially.
for _, handler in ipairs(display_handlers or get_display_handlers()) do
local displayval = handler(tagspec, joiner)
if displayval then
if nolink then
displayval = remove_links(displayval)
end
return displayval
end
end
-- No display handler.
local displayed_tags = {}
for _, first_level_tag in ipairs(tagspec) do
if type(first_level_tag) == "string" then
insert(displayed_tags, get_single_tag_display_form(first_level_tag, lang, nolink))
else
-- A first-level element of a two-level multipart tag. Currently we just separate the individual components
-- with spaces, but other ways are possible, e.g. using an underscore, colon, parens or braces.
local components = {}
for _, component in ipairs(first_level_tag) do
insert(components, get_single_tag_display_form(component, lang, nolink))
end
insert(displayed_tags, concat(components, ""))
end
end
return join_multiparts(displayed_tags, joiner)
end
get_tag_display_form = export.get_tag_display_form
--[==[
Given a normalized tag set (i.e. as output by {normalize_tag_set()}; all tags are in canonical form, multipart tags are
represented as lists, and two-level multipart tags as lists of lists), convert to displayed form (a string). See
{get_tag_display_form()} for the meaning of `joiner`. A truthy `nolink` suppresses linking.]==]
function export.get_tag_set_display_form(normalized_tag_set, lang, joiner, nolink)
local parts = {}
for _, tagspec in ipairs(normalized_tag_set) do
local to_insert = get_tag_display_form(tagspec, lang, joiner, nolink)
-- Maybe insert a space before inserting the display form of the tag. We insert a space if
-- (a) we're not the first tag; and
-- (b) the tag we're about to insert doesn't have the "no_space_on_left" property; and
-- (c) the preceding tag doesn't have the "no_space_on_right" property.
-- NOTE: We depend here on the fact that
-- (1) all tags with either of the above properties set have the same display form as canonical form, and
-- (2) all tags with either of the above properties set are single-character tags.
-- The second property is an optimization to avoid looking up display forms resulting from multipart tags,
-- which won't be found and which will trigger loading of [[Module:form of/data/2]]. If multichar punctuation is
-- added in the future, it's ok to change the == 1 below to <= 2 or <= 3.
--
-- If the first property above fails to hold in the future, we need to track the canonical form of each tag
-- (including the previous one) as well as the display form. This would also avoid the need for the == 1 check.
if #parts > 0 then
local most_recent_tagobj = parts[#parts]:match("^.[\128-\191]*$") and lookup_tag(parts[#parts], lang)
local to_insert_tagobj = to_insert:match("^.[\128-\191]*$") and lookup_tag(to_insert, lang)
if (
(not most_recent_tagobj or not most_recent_tagobj.no_space_on_right) and
(not to_insert_tagobj or not to_insert_tagobj.no_space_on_left)
) then
insert(parts, "")
end
end
insert(parts, to_insert)
end
return concat(parts)
end
get_tag_set_display_form = export.get_tag_set_display_form
--[==[
Split a tag set containing two-level multipart tags into one or more tag sets not containing such tags.
Single-level multipart tags are left alone. (If we need to, a slight modification of the following code
will also split single-level multipart tags.) This assumes that multipart tags are represented as lists
and two-level multipart tags are represented as lists of lists, as is output by {normalize_tag_set()}.
NOTE: We have to be careful to properly handle imbalanced two-level multipart tags such as
`def:s//p` (or the reverse, `s//def:p`).]==]
local function split_two_level_multipart_tag_set(tag_set)
for i, tag in ipairs(tag_set) do
if type(tag) == "table" then
-- We saw a multipart tag. Check if any of the parts are two-level.
local saw_two_level_tag = false
for _, first_level_tag in ipairs(tag) do
if type(first_level_tag) == "table" then
saw_two_level_tag = true
break
end
end
if saw_two_level_tag then
-- We found a two-level multipart tag.
-- (1) Extract the preceding tags.
local pre_tags = slice(tag_set, 1, i - 1)
-- (2) Extract the following tags.
local post_tags = slice(tag_set, i + 1)
-- (3) Loop over each tag set alternant in the two-level multipart tag.
-- For each alternant, form the tag set consisting of pre_tags + alternant + post_tags,
-- and recursively split that tag set.
local resulting_tag_sets = {}
for _, first_level_tag_set in ipairs(tag) do
local expanded_tag_set = {}
extend(expanded_tag_set, pre_tags)
-- The second level may have a string or a list.
if type(first_level_tag_set) == "table" then
extend(expanded_tag_set, first_level_tag_set)
else
insert(expanded_tag_set, first_level_tag_set)
end
extend(expanded_tag_set, post_tags)
extend(resulting_tag_sets, split_two_level_multipart_tag_set(expanded_tag_set))
end
return resulting_tag_sets
end
end
end
return {tag_set}
end
local function try_lang_specific_module(langcode, modules_tried, name, data)
local lang_specific_module = form_of_lang_data_module_prefix .. langcode .. "/functions"
local langdata = safe_require(lang_specific_module)
if langdata then
insert(modules_tried, lang_specific_module)
if langdata.cat_functions then
local fn = langdata.cat_functions[name]
if fn then
return fn(data), true
end
end
end
return nil, false
end
-- Call a named function, either from the lang-specific data in
-- [[Module:form of/lang-specific/LANGCODE/functions]] or in [[Module:form of/functions]].
local function call_named_function(name, funtype, normalized_tag_set, lang, POS, pagename, lemmas)
local data = {
pagename = pagename or default_pagename or get_default_pagename(),
lemmas = lemmas,
tag_set = normalized_tag_set,
lang = lang,
POS = POS
}
local modules_tried = {}
-- First try lang-specific.
while lang do
local retval, found_it = try_lang_specific_module(lang:getCode(), modules_tried, name, data)
if found_it then
return retval
end
-- If the language has a parent (i.e. a superordinate variety), try again with that.
lang = lang:getParent()
end
-- Try lang-independent.
insert(modules_tried, form_of_functions_module)
local fn = (cat_functions or get_cat_functions())[name]
if fn then
return fn(data)
end
for i, modname in ipairs(modules_tried) do
modules_tried[i] = "[[" .. modname .. "]]"
end
error(("No %s function named '%s' in %s"):format(funtype, name, list_to_text(modules_tried, nil, " or ")))
end
-- Given a tag from the current tag set (which may be a list in case of a multipart tag),
-- and a tag from a categorization spec, check that the two match.
-- (1) If both are strings, we just check for equality.
-- (2) If the spec tag is a string and the tag set tag is a list (i.e. it originates from a
-- multipart tag), we check that the spec tag is in the list. This is because we want to treat
-- multipart tags in user-specified tag sets as if the user had specified multiple tag sets.
-- For example, if the user said "1//3|s|pres|ind" and the categorization spec says {"has", "1"},
-- we want this to match, because "1//3|s|pres|ind" should be treated equivalently to two tag
-- sets "1|s|pres|ind" and "3|s|pres|ind", and the former matches the categorization spec.
-- (3) If the spec tag is a list (i.e. it originates from a multipart tag), we check that the
-- tag set tag is also a list and is a superset of the spec tag. For example, if the categorization
-- spec says {"has", "1//3"}, then the tag set tag must be a multipart tag that has both "1" and "3"
-- in it. "1//3" works, as does "1//2//3".
local function tag_set_tag_matches_spec_tag(tag_set_tag, spec_tag)
if type(spec_tag) == "table" then
if type(tag_set_tag) == "table" and is_subset_list(spec_tag, tag_set_tag) then
return true
end
elseif type(tag_set_tag) == "table" then
if contains(tag_set_tag, spec_tag) then
return true
end
elseif tag_set_tag == spec_tag then
return true
end
return false
end
-- Check that the current tag set matches the given spec tag. This means that any of the tags
-- in the current tag set match, according to tag_set_tag_matches_spec_tag(); see above. If the
-- current tag set contains only string tags (i.e. no multipart tags), and the spec tag is a
-- string (i.e. not a multipart tag), this boils down to list containment, but it gets more
-- complex when multipart tags are present.
local function tag_set_matches_spec_tag(spec_tag, tag_set, lang)
spec_tag = normalize_tag(spec_tag, lang)
for _, tag_set_tag in ipairs(tag_set) do
if tag_set_tag_matches_spec_tag(tag_set_tag, spec_tag) then
return true
end
end
return false
end
-- Check whether the given spec matches the current tag set. Two values are returned:
-- (1) whether the spec matches the tag set; (2) the index of the category to add if
-- the spec matches.
local function check_condition(spec, tag_set, normalized_tag_set, lang, POS, pagename, lemmas)
if type(spec) == "boolean" then
return spec
elseif type(spec) ~= "table" then
error("Wrong type of condition " .. spec .. ": " .. type(spec))
end
local predicate = spec[1]
if predicate == "has" then
return tag_set_matches_spec_tag(spec[2], tag_set, lang), 3
elseif predicate == "hasall" then
for _, tag in ipairs(spec[2]) do
if not tag_set_matches_spec_tag(tag, tag_set, lang) then
return false, 3
end
end
return true, 3
elseif predicate == "hasany" then
for _, tag in ipairs(spec[2]) do
if tag_set_matches_spec_tag(tag, tag_set, lang) then
return true, 3
end
end
return false, 3
elseif predicate == "tags=" then
local normalized_spec_tag_sets = normalize_tag_set(spec[2], lang)
if #normalized_spec_tag_sets > 1 then
error("Internal error: No support for conjoined shortcuts in category/label specs in "
.. "[[Module:form of/cats]] when processing spec tag set " .. concat(spec[2], "|"))
end
local normalized_spec_tag_set = normalized_spec_tag_sets[1]
-- Check for and disallow two-level multipart tags in the specs. FIXME: Remove this when we remove
-- support for two-level multipart tags.
for _, tag in ipairs(normalized_spec_tag_set) do
if type(tag) == "table" then
for _, subtag in ipairs(tag) do
if type(subtag) == "table" then
error("Internal error: No support for two-level multipart tags in category/label specs"
.. "[[Module:form of/cats]] when processing spec tag set "
.. concat(spec[2], "|"))
end
end
end
end
-- Allow tags to be in different orders, and multipart tags to be in different orders. To handle this,
-- we first check that both tag set tags and spec tags have the same length. If so, we sort the
-- multipart tags in the tag set tags and spec tags, and then check that all tags in the spec tags are
-- in the tag set tags.
if #tag_set ~= #normalized_spec_tag_set then
return false, 3
end
local tag_set_tags = deep_copy(tag_set)
for i=1,#tag_set_tags do
if type(tag_set_tags[i]) == "table" then
sort(tag_set_tags[i])
end
if type(normalized_spec_tag_set[i]) == "table" then
sort(normalized_spec_tag_set[i])
end
end
for i=1,#tag_set_tags do
if not contains(tag_set_tags, normalized_spec_tag_set[i]) then
return false, 3
end
end
return true, 3
elseif predicate == "p=" then
return POS == normalize_pos(spec[2]), 3
elseif predicate == "pany" then
for _, specpos in ipairs(spec[2]) do
if POS == normalize_pos(specpos) then
return true, 3
end
end
return false, 3
elseif predicate == "pexists" then
return POS ~= nil, 2
elseif predicate == "not" then
local condval = check_condition(spec[2], tag_set, normalized_tag_set, lang, POS, pagename, lemmas)
return not condval, 3
elseif predicate == "and" then
local condval = check_condition(spec[2], tag_set, normalized_tag_set, lang, POS, pagename, lemmas)
if condval then
condval = check_condition(spec[3], tag_set, normalized_tag_set, lang, POS, pagename, lemmas)
end
return condval, 4
elseif predicate == "or" then
local condval = check_condition(spec[2], tag_set, normalized_tag_set, lang, POS, pagename, lemmas)
if not condval then
condval = check_condition(spec[3], tag_set, normalized_tag_set, lang, POS, pagename, lemmas)
end
return condval, 4
elseif predicate == "call" then
return call_named_function(spec[2], "condition", normalized_tag_set, lang, POS, pagename, lemmas), 3
else
error("Unrecognized predicate: " .. predicate)
end
end
-- Process a given spec. This checks any conditions in the spec against the
-- tag set, and insert any resulting categories into `categories`. Return value
-- is true if the outermost condition evaluated to true and a category was inserted
-- (this is used in {"cond" ...} conditions, which stop when a subcondition evaluates
-- to true).
local function process_spec(spec, tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels)
if not spec then
return false
elseif type(spec) == "string" then
-- A category. Substitute POS request with user-specified part of speech or default.
spec = spec:gsub("<<p=(.-)>>", function(default)
return POS or normalize_pos(default)
end)
insert(categories, spec:find("的") and spec:gsub("的", "的" .. lang:getFullName()) or lang:getFullName() .. spec) -- L10N
return true
elseif type(spec) == "table" and spec.labels then
-- A label spec.
for _, label in ipairs(spec.labels) do
insert_if_not(labels, label)
end
return true
elseif type(spec) ~= "table" then
error("Wrong type of specification " .. spec .. ": " .. type(spec))
end
local predicate = spec[1]
if predicate == "multi" then
for _, sp in iterate_from(2, ipairs(spec)) do -- Iterate from 2.
process_spec(sp, tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels)
end
return true
elseif predicate == "cond" then
for _, sp in iterate_from(2, ipairs(spec)) do -- Iterate from 2.
if process_spec(sp, tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels) then
return true
end
end
return false
elseif predicate == "call" then
return process_spec(
call_named_function(spec[2], "spec", normalized_tag_set, lang, POS, pagename, lemmas),
tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels
)
else
local condval, ifspec = check_condition(spec, tag_set, normalized_tag_set, lang, POS, pagename, lemmas)
if condval then
process_spec(spec[ifspec], tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels)
return true
else
process_spec(spec[ifspec + 1], tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels)
-- FIXME: Are we sure this is correct?
return false
end
end
end
--[==[
Given a normalized tag set (i.e. as output by {normalize_tag_set()}; all tags are in canonical form, multipart tags are
represented as lists, and two-level multipart tags as lists of lists), fetch the associated categories and labels.
Return two values, a list of categories and a list of labels. `lang` is the language of term represented by the tag set,
and `POS` is the user-provided part of speech (which may be {nil}).]==]
function export.fetch_categories_and_labels(normalized_tag_set, lang, POS, pagename, lemmas)
local categories, labels = {}, {}
POS = normalize_pos(POS)
-- First split any two-level multipart tags into multiple sets, to make our life easier.
for _, tag_set in ipairs(split_two_level_multipart_tag_set(normalized_tag_set)) do
local langcode = lang:getCode()
local langspecs = (m_cats_data or get_m_cats_data())[langcode]
if langspecs then
for _, spec in ipairs(langspecs) do
process_spec(spec, tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels)
end
end
local full_code = lang:getFullCode()
if full_code ~= langcode then
local langspecs = (m_cats_data or get_m_cats_data())[full_code]
if langspecs then
for _, spec in ipairs(langspecs) do
process_spec(spec, tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels)
end
end
end
if full_code ~= "und" then
local langspecs = (m_cats_data or get_m_cats_data())["und"]
if langspecs then
for _, spec in ipairs(langspecs) do
process_spec(spec, tag_set, normalized_tag_set, lang, POS, pagename, lemmas, categories, labels)
end
end
end
end
return categories, labels
end
fetch_categories_and_labels = export.fetch_categories_and_labels
local function format_labels(labels, data, notext)
if labels and #labels > 0 then
return show_labels{
labels = labels,
lang = data.lang,
sort = data.sort,
nocat = data.nocat
} .. (notext and (data.pretext or "") == "" and "" or " ")
else
return ""
end
end
--[==[
Implementation of templates that display inflection tags, such as the general {{tl|inflection of}}, semi-specific
variants such as {{tl|participle of}}, and specific variants such as {{tl|past participle of}}. `data` contains all the
information controlling the display, with the following fields:
* `.lang`: ('''''required''''') Language to use when looking up language-specific inflection tags, categories and
labels, and for displaying categories and labels.
* `.tags`: ('''''required''' unless `.tag_sets` is given'') List of non-canonicalized inflection tags. Multiple tag sets
can be indicated by a {";"} as one of the tags, and tag-set properties may be attached to the last tag of a tag set.
The tags themselves may come directly from the user (as in {{tl|inflection of}}); come partly from the user (as in
{{tl|participle of}}, which adds the tag `part` to user-specified inflection tags); or be entirely specified by the
template (as in {{tl|past participle of}}).
* `.tag_sets`: ('''''required''' unless `.tags` is given'') List of non-canonicalized tag sets and associated
per-tag-set properties. Each element of the list is an object of the form
{ {tags = {"TAG", "TAG", ...}, labels = {"LABEL", "LABEL", ...}}. If `.tag_sets` is specified, `.tags` should not be
given and vice-versa. Specifying `.tag_sets` in place of tags allowed per-tag set labels to be specified; otherwise,
there is no advantage. [[Module:pt-gl-inflections]] uses this functionality to supply labels like {"Brazil"} and
{"Portugal"} associated with specific tag sets.
* `.lemmas`: ('''''recommended''''') List of objects describing the lemma(s) of which the term in question is a
non-lemma form. These are passed directly to {full_link()} in [[Module:links]]. Each object should have at minimum a
`.lang` field containing the language of the lemma and a `.term` field containing the lemma itself. Each object is
formatted using {full_link()} and then if there are more than one, they are joined using {serialCommaJoin()} in
[[Module:table]]. Alternatively, `.lemmas` can be a string, which is displayed directly. If omitted entirely, no lemma
links are shown and the connecting "of" is also omitted.
* `.lemma_face`: ('''''recommended''''') "Face" to use when displaying the lemma objects. Usually should be set to
{"term"}.
* `.POS`: ('''''recommended''''') Categorizing part-of-speech tag. Comes from the {{para|p}} or {{para|POS}} argument of
{{tl|inflection of}}.
* `.pagename`: Page name of "current" page or nil to use the actual page title; for testing purposes.
* `.conj`: Conjunction or separator to use when joining multiple lemma objects. Defaults to {"and"}.
* `.enclitics`: List of enclitics to display after the lemmas, in parens.
* `.enclitic_conj`: Conjunction or separator to use when joining multiple enclitics. Defaults to {"and"}.
* `.no_format_categories`: If true, don't format the categories derived from the inflection tags; just return them.
* `.sort`: Sort key for formatted categories. Ignored when `.no_format_categories` = {true}.
* `.nocat`: Suppress computation of categories (even if `.no_format_categories` is not given).
* `.notext`: Disable display of all tag text and `inflection of` text. (FIXME: Maybe not implemented correctly.)
* `.nolink`: Suppress linking of terms in inflection tags.
* `.capfirst`: Capitalize the first word displayed.
* `.pretext`: Additional text to display before the inflection tags, but after any top-level labels.
* `.posttext`: Additional text to display after the lemma links.
* `.text_classes`: CSS classes used to wrap the tag text and lemma links. Default is
{"form-of-definition use-with-mention"}.
* `.lemma_classes`: Additional CSS classes used to wrap the lemma links. Default is {"form-of-definition-link"}.
* `.joiner`: Override the joiner (normally a slash) used to join multipart tags. You should normally not specify this.
* `.nowrap`: Do not wrap the form-of definitions in a span, unless `.text_classes` is specified.
* `.ok_to_destructively_modify`: If set, data structures (including the nested lemma structures) can be modified
in-place to save memory; otherwise they will be copied before modifying.
A typical call might look like this (for {{m+|es|amo}}): {
local lang = require("Module:languages").getByCode("es")
local lemma_obj = {
lang = lang,
term = "amar",
}
return m_form_of.tagged_inflections({
lang = lang, tags = {"1", "s", "pres", "ind"}, lemmas = {lemma_obj}, lemma_face = "term", POS = "verb"
})
}
Normally, one value is returned, the formatted text, which has appended to it the formatted categories derived from the
tag-set-related categories generated by the specs in [Module:form of/cats]]. To suppress this, set
`data.no_format_categories` = {true}, in which case two values are returned, the formatted text without any formatted
categories appended and a list of the categories to be formatted.
NOTE: There are two sets of categories that may be generated: (1) categories derived directly from the tag sets, as
specified in [[Module:form of/cats]]; (2) categories derived from tag-set labels, either (a) set explicitly by the
caller in `data.tag_sets`, (b) specified by the user using `<lb:...>` attached to the last tag in a tag set, or
(c) specified in [[Module:form of/cats]]. The second type (label-related categories) are currently not returned in
the second return value of {tagged_inflections()}, and are currently inserted into the output text even if
`data.no_format_categories` is set to {true}; but they can be suppressed by setting `data.nocat` = {true} (which also
suppresses the first type of categories, those derived directly from tag sets, even if `data.no_format_categories` is
set to {true}).]==]
function export.tagged_inflections(data)
if not data.tags and not data.tag_sets then
error("First argument must be a table of arguments, and `.tags` or `.tag_sets` must be specified")
end
if data.tags and data.tag_sets then
error("Both `.tags` and `.tag_sets` cannot be specified")
end
local tag_sets = data.tag_sets
if not tag_sets then
tag_sets = split_tag_set(data.tags)
for i, tag_set in ipairs(tag_sets) do
tag_sets[i] = parse_tag_set_properties(tag_set)
end
end
local inflections = {}
local categories = {}
for _, tag_set in ipairs(tag_sets) do
local normalized_tag_sets = normalize_tag_set(tag_set.tags, data.lang, "do-track")
for _, normalized_tag_set in ipairs(normalized_tag_sets) do
local this_categories, this_labels = fetch_categories_and_labels(normalized_tag_set, data.lang,
data.POS, data.pagename, type(data.lemmas) == "table" and data.lemmas or nil)
if not data.nocat then
extend(categories, this_categories)
end
local cur_infl = get_tag_set_display_form(normalized_tag_set, data.lang, data.joiner, data.nolink)
if #cur_infl > 0 then
if tag_set.labels then
this_labels = append(tag_set.labels, this_labels)
end
insert(inflections, {infl_text = cur_infl, labels = this_labels})
end
end
end
local overall_labels, need_per_tag_set_labels
for _, inflection in ipairs(inflections) do
if overall_labels == nil then
overall_labels = inflection.labels
elseif not deep_equals(overall_labels, inflection.labels) then
need_per_tag_set_labels = true
overall_labels = nil
break
end
end
if not need_per_tag_set_labels then
for _, inflection in ipairs(inflections) do
inflection.labels = nil
end
end
local format_data = shallow_copy(data)
local of_text = data.lemmas and " 的" or ""
local formatted_text, this_categories
if #inflections == 1 then
if need_per_tag_set_labels then
error("Internal error: need_per_tag_set_labels should not be set with one inflection")
end
format_data.of_text = of_text
format_data.text = format_labels(overall_labels, data, data.notext) .. (data.pretext or "") .. (data.notext and "" or
(data.capfirst and ucfirst(inflections[1].infl_text) or inflections[1].infl_text))
formatted_text, this_categories = format_form_of(format_data)
else
format_data.of_text = of_text
format_data.text = format_labels(overall_labels, data, data.notext) .. (data.pretext or "") .. (data.notext and "" or
(data.capfirst and "屈折" or "屈折"))
format_data.posttext = (data.posttext or "") .. ":"
local link
link, this_categories = format_form_of(format_data)
local text_classes = data.text_classes
if text_classes == nil and not data.nowrap then
text_classes = "form-of-definition use-with-mention"
end
for i, inflection in ipairs(inflections) do
inflections[i] = "\n## " .. format_labels(inflection.labels, data, false) ..
wrap_in_span(inflection.infl_text, text_classes)
end
formatted_text = link .. concat(inflections)
end
if this_categories[1] then
extend(categories, this_categories)
end
if not data.no_format_categories then
if categories[1] then
formatted_text = formatted_text .. format_categories(categories, data.lang,
data.sort, nil, export.force_cat)
end
return formatted_text
end
return formatted_text, categories
end
function export.dump_form_of_data(_frame)
local data = {
require(form_of_data1_module),
require(form_of_data2_module)
}
return require(json_module).toJSON(data)
end
export.form_of_cats_module = form_of_cats_module
export.form_of_data1_module = form_of_data1_module
export.form_of_data2_module = form_of_data2_module
export.form_of_functions_module = form_of_functions_module
export.form_of_lang_data_module_prefix = form_of_lang_data_module_prefix
export.headword_data_module = headword_data_module -- so all form-of modules stay in sync
return export
to2aqvd9jiyddlch8o05gcxnexdwixu
Template:字源/doc
10
1367094
9909191
6305601
2026-09-29T09:54:54Z
TongcyBot
83009
oracle-zhouyuan → oracle-western;修正 slip
9909191
wikitext
text/x-wiki
顯示字源(甲骨文、金文、楚系簡帛文字、秦系簡牘文字、《說文》小篆、《六書通》傳抄古文字)的模板。
=== 用例 ===
*通常:頁面名字源圖像
<code><nowiki>{{字源}}</nowiki></code>
*簡化字、古字、異體字的情況:在參數中制定參照文字
:如「-{买}-」,指定「買」作爲參数
<code><nowiki>{{字源|買}}</nowiki></code>
*上傳文件時
**-oracle代表商代甲骨文
***-oracle-western代表西周甲骨文
**-bronze代表西周金文
***-bronze-shang代表商代金文
***-bronze-spring代表春秋金文
***-bronze-warring代表戰國金文
**-silk代表楚系簡帛文字
**-slip代表秦系簡牘文字
**-seal代表《說文》小篆
***-ancient代表《說文》古文
***-zhou代表《說文》籀文
***-odd代表《說文》奇字
**-bigseal代表《六書通》傳抄古文字
===參見===
* {{temp|stroke order}}
<includeonly>
[[Category:漢字模板]]
</includeonly>
068nizttfc3xluwognc7wvu1mw2bks6
Module:Nyms
828
1368464
9909091
9689534
2026-09-29T07:27:27Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909091
Scribunto
text/plain
local export = {}
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
local parse_utilities_module = "Module:parse utilities"
local function track(page)
return require(debug_track_module)("nyms/" .. page)
end
local function wrap_span(text, lang, sc)
return '<span class="' .. sc .. '" lang="' .. lang .. '">' .. text .. '</span>'
end
local function term_already_linked(term)
-- optimization to avoid unnecessarily loading [[Module:parse utilities]]
return term:find("[<{]") and require(parse_utilities_module).term_already_linked(term)
end
function export.nyms(frame)
local parent_args = frame:getParent().args
-- FIXME: Temporary error message and tracking.
for arg, _ in pairs(parent_args) do
if type(arg) == "string" and arg:find("^lb[0-9]*$") then
local llarg = arg:gsub("^lb", "ll")
error(("%s= is deprecated; use %s= instead, per the documentation"):format(arg, llarg))
end
if arg == "q" or arg == "qq" then
track(arg)
end
if type(arg) == "string" and arg:find("^tag[0-9]*$") then
local larg = arg:gsub("^tag", "l")
local llarg = arg:gsub("^tag", "ll")
error(("Use %s= (on the left) or %s= (on the right) instead of %s="):format(larg, llarg, arg))
end
end
local params = {
[1] = {required = true, type = "language", default = "und"},
[2] = {list = true, allow_holes = true, required = true},
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "l"}},
-- For compatibility, we don't distinguish q= from q1= and qq= from q1=. FIXME: Maybe we should change this.
{group = "q", separate_no_index = false},
{param = "lb", deprecated = true},
}
local special_separators = mw.clone(m_param_utils.default_special_separators)
special_separators["<"] = " < "
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
parse_lang_prefix = true,
track_module = "nyms",
lang = 1,
special_separators = special_separators,
sc = "sc.default",
-- because we show them ourselves below so e.g. we can handle already-linked terms
no_show_decorations = true,
}
local nym_type = frame.args[1]
local nym_type_class = string.gsub(nym_type, "%s", "-")
local lang = args[1]
local langcode = lang:getCode()
local data = {
lang = lang,
items = items,
sc = args.sc.default,
l = args.l.default,
ll = args.ll.default,
q = args.q.default,
qq = args.qq.default,
}
local parts = {}
local thesaurus_parts = {}
for i, item in ipairs(data.items) do
if item.lb then
error("Inline modifier <lb:...> is deprecated; use <ll:...> per the documentation")
end
local explicit_item_lang = item.lang
item.lang = item.lang or data.lang
item.sc = item.sc or data.sc
local text
local is_thesaurus
if item.term and item.term:find("^Thesaurus:") then
is_thesaurus = true
for k, _ in pairs(item) do
if m_param_utils.item_key_is_property(k) and k ~= "lang" and k ~= "sc" and k ~= "q" and k ~= "qq" and
k ~= "l" and k ~= "ll" and k ~= "refs" then
error(("You cannot use most named parameters and inline modifiers with Thesaurus links, but saw %s%s= or its equivalent inline modifier <%s:...>"):format(
k, item.itemno, k))
end
end
local term = item.term:match("^Thesaurus:(.*)$")
-- Chop off fragment
term = term:match("^(.-)#.*$") or term
local lang = item.lang
local sccode = (item.sc or lang:findBestScript(term)):getCode()
-- FIXME: I assume it's better to include full-language codes in the CSS rather than etym-language codes,
-- which are generally specific to Wiktionary. However, we should probably instead be using the functions
-- from [[Module:script utilities]] in preference to rolling our own.
text = "[[" .. item.term .. "#" .. lang:getFullName() .. "|Thesaurus:" .. wrap_span(term, lang:getFullCode(), sccode) .. "]]"
else
if thesaurus_parts[1] then
error("Links to the Thesaurus must follow all non-Thesaurus links")
end
local raw_term = item.alt or item.term
if raw_term and term_already_linked(raw_term) then
text = raw_term
else
text = require(links_module).full_link(item)
end
end
local qq = item.qq
-- If a separate language code was given for the term, display the language name as a right qualifier.
-- Otherwise it may not be obvious that the term is in a separate language (e.g. if the main language is 'zh'
-- and the term language is a Chinese lect such as Min Nan). But don't do this for Translingual terms, which
-- are often added to the list of English and other-language terms.
if explicit_item_lang then
local explicit_code = explicit_item_lang:getCode()
if explicit_code ~= langcode and explicit_code ~= "mul" then
qq = mw.clone(qq) or {}
table.insert(qq, 1, explicit_item_lang:getCanonicalName())
end
end
if item.q and item.q[1] or qq and qq[1] or item.l and item.l[1] or item.ll and item.ll[1] or
item.refs and item.refs[1] then
text = require(decorations_module).format_decorations {
lang = item.lang,
text = text,
q = item.q,
qq = qq,
l = item.l,
ll = item.ll,
refs = item.refs,
}
end
local insert_place = is_thesaurus and thesaurus_parts or parts
-- Don't include the separator if this is the first item of this class that we're inserting.
table.insert(insert_place, insert_place[1] and item.separator or "")
table.insert(insert_place, text)
end
local text = table.concat(parts)
local thesaurus_text = table.concat(thesaurus_parts)
local caption = "<span style=\"font-size: smaller\">" .. mw.getContentLanguage():ucfirst(nym_type) ..
":</span>"
text = caption .. text
local function decoration_error_if_no_terms()
if not parts[1] then
error("Cannot specify overall decorations if no non-Thesaurus terms given")
end
end
if data.q and data.q[1] or data.qq and data.qq[1] or data.l and data.l[1] then
decoration_error_if_no_terms()
text = require(decorations_module).format_decorations {
lang = data.lang,
text = text,
q = data.q,
qq = data.qq,
l = data.l,
-- ll handled specially for compatibility's sake
}
end
if data.ll and data.ll[1] then
decoration_error_if_no_terms()
text = text .. " — " .. require(labels_module).show_labels {
lang = data.lang,
labels = data.ll,
nocat = true,
open = false,
close = false,
no_track_already_seen = true,
}
end
if thesaurus_text ~= "" then
local thesaurus_intro = parts[1] and ";亦參見" or "參見"
text = text .. thesaurus_intro .. thesaurus_text
end
return "<span class=\"nyms " .. nym_type_class .. "\">" .. text .. "</span>"
end
return export
brr5qc3gtresxs7ewv2xi0stf0x51wi
Module:Ru-common
828
1369662
9909103
9596494
2026-09-29T07:28:10Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909103
Scribunto
text/plain
local export = {}
--[==[ intro:
Author: Benwing; some very early work by Rua and Atitarev
This module holds some commonly used functions for the Russian language. It's generally for use from other modules, not
`#invoke`, although some functions can be invoked from a template (`export.iotation()`, `export.reduce_stem()`,
`export.dereduce_stem()`, actually added to support calling from a bot script rather than from a user template; and
`export.make_unstressed()`, called from {{tl|R:ru:Vasmer}}). There may be issues when invoking such functions from
templates when transliteration is present, due to the need for the transliteration to be decomposed, as mentioned below
(all strings from Wiktionary pages are normally in composed form).
NOTE NOTE NOTE: All functions assume that transliteration (but not Russian) has had its acute and grave accents
decomposed using `export.decompose()`. This is the first thing that should be done to all user-specified
transliteration and any transliteration we compute that we expect to work with.
]==]
local m_str_utils = require("Module:string utilities")
local m_table_tools = require("Module:table tools")
-- Prevents an infinite require loop since ru-translit requires a different function in this module.
local m_ru_translit = require("Module:require when needed")("Module:ru-translit")
local m_headword_utilities = require("Module:require when needed")("Module:headword utilities")
local m_table = require("Module:require when needed")("Module:table")
local gsplit = m_str_utils.gsplit
local split = m_str_utils.split
local toNFD = mw.ustring.toNFD
local u = m_str_utils.char
local ufind = mw.ustring.find
local ugsub = mw.ustring.gsub
local ulen = m_str_utils.len
local ulower = m_str_utils.lower
local umatch = mw.ustring.match
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local usub = m_str_utils.sub
local insert = table.insert
local concat = table.concat
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local BREVE = u(0x0306) -- breve ̆
local DIA = u(0x0308) -- diaeresis = ̈
local CARON = u(0x030C) -- caron ̌
local OGONEK = u(0x0328) -- ogonek ̨
local PSEUDOVOWEL = u(0xFFF1) -- pseudovowel placeholder
local PSEUDOCONS = u(0xFFF2) -- pseudoconsonant placeholder
-- any stress
export.stress = AC .. GR
-- regex for any optional stress(s)
export.opt_stress = "[" .. export.stress .. "]*"
-- any accent
export.accent = export.stress .. DIA .. BREVE .. CARON .. OGONEK
-- regex for any optional accent(s)
export.opt_accent = "[" .. export.accent .. "]*"
-- any composed Cyrillic vowel with grave accent
export.composed_grave_vowel = "ѐЀѝЍ"
-- any Cyrillic vowel except ёЁ
export.vowel_no_jo = "аАеЕиИӥӤіІїЇоОуУыЫѣѢэЭюЮяЯѵѴ" .. PSEUDOVOWEL .. export.composed_grave_vowel
-- any Cyrillic vowel, including ёЁ
export.vowel = export.vowel_no_jo .. "ёЁ"
-- any vowel in transliteration
export.tr_vowel = "aAeEěĚɛƐiIoOǒǑǫǪuUyY" .. PSEUDOVOWEL
-- any consonant in transliteration, omitting "j" and the soft/hard sign
export.tr_cons_no_approx = "bBcCčČdDfFgGkKlLmMnNpPrRsSšŠtTvVxXzZžŽ" .. PSEUDOCONS
-- any consonant in transliteration, including soft/hard sign
export.tr_cons = export.tr_cons_no_approx .. "jJʹʺ"
-- regex for any consonant in transliteration, including soft/hard sign,
-- optionally followed by any accent
export.tr_cons_acc_re = "[" .. export.tr_cons .. "]" .. export.opt_accent
-- any Cyrillic consonant except sibilants and ц
export.cons_except_sib_c = "бБвВгГдДзЗйЙкКлЛмМнНпПрРсСтТфФхХъЪьЬѳѲ" .. PSEUDOCONS
-- Cyrillic sibilant consonants
export.sib = "жЖчЧшШщЩ"
-- Cyrillic sibilant consonants and ц
export.sib_c = export.sib .. "цЦ"
-- any Cyrillic consonant
export.cons = export.cons_except_sib_c .. export.sib_c
-- Cyrillic velar consonants
export.velar = "гГкКхХ"
-- uppercase Cyrillic
export.uppercase = "АБВГДЕЀЁЖЗИЍӤІЇЙКЛМНОПРСТУФХЦЧШЩЪЫЬѢЭЮЯѲѴ"
local function ine(x)
return x ~= "" and x or nil
end
--[==[
Apply Proto-Slavic iotation. This is the change that is affected by a Slavic -j- after a consonant.
`stem` is the Cyrillic stem and `tr` its optional manual translit. If `shch` is given, т iotates to щ instead of ч.
Normally return two values, the iotated Cyrillic stem and corresponding transliteration (which will be {nil} if passed
in as {nil}). If invoked from a template or bot, however, the return value will be a single string; if manual
transliteration is present, the return value will be a string of the form `CYRILLIC//TRANSLIT` with two slahes
separating the Cyrillic from the translit.
]==]
function export.iotation(stem, tr, shch)
local combine_tr = false
-- so this can be called from a template (or usually, a bot)
if type(stem) == "table" then
stem, tr, shch = ine(stem.args[1]), ine(stem.args[2]), ine(stem.args[3])
combine_tr = true
end
stem = ugsub(stem, "[сх]$", "ш")
stem = ugsub(stem, "с[кт]$", "щ")
stem = ugsub(stem, "[кц]$", "ч")
-- normally "т" is iotated as "ч" but there are many verbs that are iotated with "щ"
if shch == "щ" then
stem = stem:gsub("т$", "щ")
else
stem = stem:gsub("т$", "ч")
end
stem = ugsub(stem, "[гдз]$", "ж")
stem = ugsub(stem, "[бвмпфѳ]$", "%0л")
-- ѵ is equivalent to в after certain vowels
stem = ugsub(stem, "[аАеЕѐЀиИѝЍӥӤіІїЇѣѢэЭяЯ]" .. export.opt_stress .. "ѵ$", "%0л")
if tr then
tr = tr:gsub("[sx]$", "š")
:gsub("s[kt]$", "šč")
:gsub("[kc]$", "č")
-- normally "т" is iotated as "ч" but there are many verbs that are iotated with "щ"
if shch == "щ" then
tr = tr:gsub("t$", "šč")
else
tr = tr:gsub("t$", "č")
end
tr = tr:gsub("[dgz]$", "ž")
:gsub("[bfmpv]$", "%0l")
end
if combine_tr then
return export.combine_russian_tr(stem, tr)
end
return stem, tr
end
do
-- A word needs accents if it is unstressed and contains more than one
-- vowel, unless it's a prefix or suffix
local function word_needs_accents(word)
return not (word:sub(1, 1) == "-" or word:sub(-1) == "-") and
export.is_unstressed(word) and not export.is_monosyllabic(word)
end
-- Does a set of Cyrillic words in connected text need accents? We need to
-- split by word and check each one.
function export.needs_accents(text)
for word in gsplit(text, "%s+") do
if word_needs_accents(word) then
return true
end
end
return false
end
end
--[==[
True if either:
# A vowel is marked with explicit primary stress.
# The word has a single jo in it (which doesn't have secondary stress).
This is because a word with multiple jos requires explicit stress to be marked, since we can't infer where primary
stress lies. `Jo` can be `ё`, `ѣ̈`, or `я̈` (e.g. `сѣ̈дла`, plural of `сѣдло́`).
]==]
function export.is_stressed(word)
if word:find(AC) then
return true
end
word = toNFD(word)
return (
select(2, ugsub(word, "[еѣяЕѢЯ]" .. DIA, "")) == 1 and
not umatch(word, "[еѣяЕѢЯ]" .. DIA .. GR)
)
end
--[==[
True if a Cyrillic word requires explicit stress.
]==]
function export.is_unstressed(word)
return not export.is_stressed(word)
end
--[==[
True if Cyrillic word is stressed on the last syllable.
]==]
function export.is_ending_stressed(word)
return ufind(word, "[ёЁ][^" .. export.vowel .. "]*$") or
ufind(word, "[" .. export.vowel .. "][́̈][^" .. export.vowel .. "]*$")
end
do
local function word_is_multi_stressed(word)
-- Allow ё along with another stress, e.g. [[трёхэта́жный]], [[гёрлфре́нд]] or [[Са́мсё]].
return ufind(word, "[" .. export.vowel .. "][́̈].*[" .. export.vowel .. "][́̈]")
end
--[==[
True if a Cyrillic term has two or more stresses (acute or diaeresis) in a given word.
]==]
function export.is_multi_stressed(text)
for word in gsplit(text, "[%s-]+") do
if word_is_multi_stressed(word) then
return true
end
end
return false
end
end
--[==[
True if Cyrillic word is stressed on the first syllable.
]==]
function export.is_beginning_stressed(word)
return ufind(word, "^[^" .. export.vowel .. "]*[ёЁ]") or
ufind(word, "^[^" .. export.vowel .. "]*[" .. export.vowel .. "]́")
end
--[==[
True if Cyrillic word has no vowel. Don't treat suffixes as nonsyllabic even if they have no vowel, as they are
generally added onto words with vowels.
]==]
function export.is_nonsyllabic(word)
return not ufind(word, "^%-") and not ufind(word, "[" .. export.vowel .. "]")
end
--[==[
True if Cyrillic word has no more than one vowel; includes non-syllabic stems such as `льд-`.
]==]
function export.is_monosyllabic(word)
return not ufind(word, "[" .. export.vowel .. "].*[" .. export.vowel .. "]")
end
local recomposer = {
-- Cyrillic letters
["е" .. DIA] = "ё",
["Е" .. DIA] = "Ё",
["и" .. DIA] = "ӥ",
["И" .. DIA] = "Ӥ",
["и" .. BREVE] = "й",
["И" .. BREVE] = "Й",
["і" .. DIA] = "ї",
["І" .. DIA] = "Ї",
-- Latin letters
["c" .. CARON] = "č",
["C" .. CARON] = "Č",
["e" .. CARON] = "ě",
["E" .. CARON] = "Ě",
["o" .. CARON] = "ǒ",
["O" .. CARON] = "Ǒ",
["o" .. OGONEK] = "ǫ",
["O" .. OGONEK] = "Ǫ",
["s" .. CARON] = "š",
["S" .. CARON] = "Š",
["z" .. CARON] = "ž",
["Z" .. CARON] = "Ž",
-- used in ru-pron:
["ж" .. BREVE] = "ӂ", -- used in ru-pron
["Ж" .. BREVE] = "Ӂ",
["j" .. CFLEX] = "ĵ",
["J" .. CFLEX] = "Ĵ",
["j" .. CARON] = "ǰ",
-- no composed uppercase equivalent of J-caron
["ʒ" .. CARON] = "ǯ",
["Ʒ" .. CARON] = "Ǯ",
}
--[==[
Decompose acute, grave, etc. on letters (esp. Latin) into individivual character + combining accent. But recompose
Cyrillic and Latin characters that we want to treat as units and get caught in the crossfire. We mostly want acute and
grave decomposed; perhaps should just explicitly decompose those and no others.
]==]
function export.decompose(text)
return (ugsub(toNFD(text), ".[" .. BREVE .. DIA .. CARON .. OGONEK .. "]", recomposer))
end
function export.assert_decomposed(text)
assert(not ufind(text, "[áéíóúýàèìòùỳäëïöüÿÁÉÍÓÚÝÀÈÌÒÙỲÄËÏÖÜŸ]"))
end
--[==[
Transliterate text and then apply acute/grave decomposition.
]==]
function export.translit(text, no_include_monosyllabic_jo_accent)
local jo_accent = not no_include_monosyllabic_jo_accent and "mono" or nil
return export.decompose(m_ru_translit.tr(text, nil, nil, jo_accent))
end
--[==[
Recompose acutes and graves into preceding vowels. Probably not necessary.
]==]
function export.recompose(text)
return mw.ustring.toNFC(text)
end
local grave_decomposer = {
["ѐ"] = "е" .. GR,
["Ѐ"] = "Е" .. GR,
["ѝ"] = "и" .. GR,
["Ѝ"] = "И" .. GR,
}
--[==[
Decompose precomposed Cyrillic chars w/grave accent; not necessary for acute accent as there aren't precomposed Cyrillic
chars w/acute accent, and undesirable for precomposed `ё` and `Ё`.
]==]
function export.decompose_grave(word)
return (ugsub(word, "[ѐЀѝЍ]", grave_decomposer))
end
local grave_deaccenter = {
[GR] = "", -- grave accent
["ѐ"] = "е", -- composed Cyrillic chars w/grave accent
["Ѐ"] = "Е",
["ѝ"] = "и",
["Ѝ"] = "И",
}
local deaccenter = mw.clone(grave_deaccenter)
deaccenter[AC] = "" -- acute accent
--[==[
Remove acute and grave accents; don't affect composed diaeresis in `ёЁ` or uncomposed diaeresis in `-ѣ̈-` (as in plural
`сѣ̈дла` of `сѣдло́`). NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.remove_accents(word, tr)
local ru_removed = ugsub(word, "[́̀ѐЀѝЍ]", deaccenter)
if not tr then
return ru_removed, nil
end
return ru_removed, (ugsub(tr, "[" .. AC .. GR .. "]", deaccenter))
end
--[==[
Remove grave accents; don't affect acute or composed diaeresis in `ёЁ` or uncomposed diaeresis in `-ѣ̈-` (as in plural
`сѣ̈дла` of `сѣдло́`). NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.remove_grave_accents(word, tr)
local ru_removed = ugsub(word, "[̀ѐЀѝЍ]", grave_deaccenter)
if not tr then
return ru_removed, nil
end
return ru_removed, (ugsub(tr, GR, ""))
end
--[==[
Remove acute and grave accents in monosyllabic words; don't affect diaeresis (composed or uncomposed) because it
indicates a change in vowel quality, which still applies to monosyllabic words. Don't change suffixes, where a
"monosyllabic" stress is still significant (e.g. `-ча́т` short masculine of `-ча́тый`, vs. `-́чат` short masculine of
`-́чатый`). NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.remove_monosyllabic_accents(word, tr)
if export.is_monosyllabic(word) and not ufind(word, "^%-") then
return export.remove_accents(word, tr)
else
return word, tr
end
end
local destresser = mw.clone(deaccenter)
destresser["ё"] = "е"
destresser["Ё"] = "Е"
destresser[DIA] = "" -- diaeresis
-- Subfunction of split_syllables(). On input we get sections of text consisting of CONSONANT - VOWEL - CONSONANT -
-- VOWEL ... - CONSONANT, where CONSONANT consists of zero or more consonants and VOWEL consists of exactly one vowel
-- plus any following accent(s); we combine these into syllables as required by split_syllables().
local function combine_captures(captures)
if captures[1] and not captures[2] then
return captures
end
local combined = {}
for i = 1, (#captures - 1), 2 do
insert(combined, captures[i] .. captures[i+1])
end
combined[#combined] = combined[#combined] .. captures[#captures]
return combined
end
--[==[
Split Russian text and transliteration into syllables. Syllables end with vowel + accent(s), except for the last
syllable, which includes any trailing consonants. NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.split_syllables(ru, tr)
-- Split into alternating consonant/vowel sequences, as described in
-- combine_captures().
local rusyllables = combine_captures(split(ru, "([" .. export.vowel .. "]" .. export.opt_accent .. ")"))
local trsyllables
if tr then
export.assert_decomposed(tr)
trsyllables = combine_captures(split(tr, "([" .. export.tr_vowel .. "]" .. export.opt_accent .. ")"))
if #rusyllables ~= #trsyllables then
error("Russian " .. ru .. " doesn't have same number of syllables as translit " .. tr)
end
end
--error(concat(rusyllables, "/") .. "(" .. #rusyllables .. (trsyllables and (") || " .. concat(trsyllables, "/") .. "(" .. #trsyllables .. ")") or ""))
return rusyllables, trsyllables
end
--[==[
Split Russian word and transliteration into hyphen-separated components. Rejoining with `concat(..., "-")` will
recover the original word. If the original word ends in a hyphen, that hyphen gets included with the preceding component(this is the only case when an individual component has a hyphen in it).
]==]
function export.split_hyphens(ru, tr)
local rucomponents = split(ru, "%-")
if rucomponents[#rucomponents] == "" and #rucomponents > 1 then
rucomponents[#rucomponents - 1] = rucomponents[#rucomponents - 1] .. "-"
table.remove(rucomponents)
end
local trcomponents
if tr then
trcomponents = split(tr, "%-")
if trcomponents[#trcomponents] == "" and #trcomponents > 1 then
trcomponents[#trcomponents - 1] = trcomponents[#trcomponents - 1] .. "-"
table.remove(trcomponents)
end
if #rucomponents ~= #trcomponents then
error("Russian " .. ru .. " doesn't have same number of hyphenated components as translit " .. tr)
end
end
return rucomponents, trcomponents
end
--[==[
Apply `j` correction, converting `je` to `e` after consonants, `jo` to `o` after a sibilant, `ju` to `u` after hard
sibilant. NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.j_correction(tr)
tr = ugsub(tr, "([" .. export.tr_cons_no_approx .. "]" .. export.opt_accent ..")[Jj]([EeĚě])", "%1%2")
tr = ugsub(tr, "([žščŽŠČ])[Jj]([Oo])", "%1%2")
return (ugsub(tr, "([žšŽŠ])[Jj]([Uu])", "%1%2"))
end
local function make_unstressed_ru(ru)
-- The following regexp has grave+acute+diaeresis after the bracket
return (ugsub(ru, "[̀́̈ёЁѐЀѝЍ]", destresser))
end
--[==[
Remove all stress marks (acute, grave, diaeresis). NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.make_unstressed(ru, tr)
-- so this can be called from a template (specifically {{R:ru:Vasmer}})
if type(ru) == "table" then
ru, tr = ine(ru.args[1]), ine(ru.args[2])
end
if not tr then
return make_unstressed_ru(ru), nil
end
-- In the presence of TR, we need to do things the hard way: Splitting
-- into syllables and only converting Latin o to e opposite a ё.
local rusyl, trsyl = export.split_syllables(ru, tr)
for i=1,#rusyl do
if ufind(rusyl[i], "[ёЁ]") then
trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"})
end
rusyl[i] = make_unstressed_ru(rusyl[i])
-- the following should still work as it will affect accents only
trsyl[i] = make_unstressed_ru(trsyl[i])
end
-- Also need to apply j correction as otherwise we'll have je after cons, etc.
return concat(rusyl, ""), export.j_correction(concat(trsyl, ""))
end
local function remove_jo_ru(word)
return (ugsub(word, "[̈ёЁ]", destresser))
end
local function make_unstressed_once_ru(word)
-- leave graves alone
return (ugsub(word, "([́̈ёЁ])([^́̈ёЁ]*)$", function(x, rest) return destresser[x] .. rest; end, 1))
end
--[==[
Remove diaeresis stress marks only. NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.remove_jo(ru, tr)
if not tr then
return remove_jo_ru(ru), nil
end
-- In the presence of TR, we need to do things the hard way: Splitting
-- into syllables and only converting Latin o to e opposite a ё.
local rusyl, trsyl = export.split_syllables(ru, tr)
for i=1,#rusyl do
if ufind(rusyl[i], "[ёЁ]") then
trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"})
end
rusyl[i] = remove_jo_ru(rusyl[i])
-- the following should still work as it will affect accents only
trsyl[i] = make_unstressed_once_ru(trsyl[i])
end
-- Also need to apply j correction as otherwise we'll have je after cons, etc.
return concat(rusyl, ""),
export.j_correction(concat(trsyl, ""))
end
local function map_last_hyphenated_component(fn, ru, tr)
if ufind(ru, "%-") then
-- If there is a hyphen, do it the hard way by splitting into
-- individual components and doing the last one. Otherwise we just do
-- the whole string.
local rucomponents, trcomponents = export.split_hyphens(ru, tr)
local lastru, lasttr = fn(rucomponents[#rucomponents],
trcomponents and trcomponents[#trcomponents] or nil)
rucomponents[#rucomponents] = lastru
ru = concat(rucomponents, "-")
if trcomponents then
trcomponents[#trcomponents] = lasttr
tr = concat(trcomponents, "-")
end
return ru, tr
end
return fn(ru, tr)
end
-- Make last stressed syllable (acute or diaeresis) unstressed; leave unstressed; leave graves alone; if `NOCONCAT`,
-- return individual syllables. NOTE: Translit must already be decomposed! See comment at top.
local function make_unstressed_once_after_hyphen_split(ru, tr, noconcat)
if not tr then
return make_unstressed_once_ru(ru), nil
end
-- In the presence of TR, we need to do things the hard way, as with
-- make_unstressed().
local rusyl, trsyl = export.split_syllables(ru, tr)
for i=#rusyl,1,-1 do
local stressed = export.is_stressed(rusyl[i])
if stressed then
if ufind(rusyl[i], "[ёЁ]") then
trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"})
end
rusyl[i] = make_unstressed_once_ru(rusyl[i])
-- the following should still work as it will affect accents only
trsyl[i] = make_unstressed_once_ru(trsyl[i])
break
end
end
if noconcat then
return rusyl, trsyl
end
-- Also need to apply j correction as otherwise we'll have je after cons
return concat(rusyl, ""),
export.j_correction(concat(trsyl, ""))
end
--[==[
Make last stressed syllable (acute or diaeresis) to the right of any hyphen unstressed (unless the hyphen is
word-final); leave graves alone. We don't destress a syllable to the left of a hyphen unless the hyphen is word-final
(i.e. a prefix). Otherwise e.g. the accents in the first part of words like ко́е-како́й and а́льфа-лу́ч won't remain. NOTE:
Translit must already be decomposed! See comment at top.
]==]
function export.make_unstressed_once(ru, tr)
return map_last_hyphenated_component(make_unstressed_once_after_hyphen_split, ru, tr)
end
local function make_unstressed_once_at_beginning_ru(word)
-- leave graves alone
return (ugsub(word, "^([^́̈ёЁ]*)([́̈ёЁ])", function(rest, x) return rest .. destresser[x]; end, 1))
end
--[==[
Make first stressed syllable (acute or diaeresis) unstressed; leave graves alone; if `NOCONCAT`, return individual
syllables. NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.make_unstressed_once_at_beginning(ru, tr, noconcat)
if not tr then
return make_unstressed_once_at_beginning_ru(ru), nil
end
-- In the presence of TR, we need to do things the hard way, as with
-- make_unstressed().
local rusyl, trsyl = export.split_syllables(ru, tr)
for i=1,#rusyl do
local stressed = export.is_stressed(rusyl[i])
if stressed then
if ufind(rusyl[i], "[ёЁ]") then
trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"})
end
rusyl[i] = make_unstressed_once_at_beginning_ru(rusyl[i])
-- the following should still work as it will affect accents only
trsyl[i] = make_unstressed_once_at_beginning_ru(trsyl[i])
break
end
end
if noconcat then
return rusyl, trsyl
end
-- Also need to apply j correction as otherwise we'll have je after cons
return concat(rusyl, ""),
export.j_correction(concat(trsyl, ""))
end
--[==[
Subfunction of `make_ending_stressed()`, `make_beginning_stressed()`, which add an acute accent to a syllable that may
already have a grave accent; in such a case, remove the grave. NOTE: Translit must already be decomposed! See comment at
top.
]==]
function export.correct_grave_acute_clash(word, tr)
word = ugsub(word, "([̀ѐЀѝЍ])́", function(x) return grave_deaccenter[x] .. AC; end)
word = ugsub(word, AC .. GR, AC)
if not tr then
return word, nil
end
tr = ugsub(tr, GR .. AC, AC)
tr = ugsub(tr, AC .. GR, AC)
return word, tr
end
local function make_ending_stressed_ru(word)
-- If already ending stressed, just return word so we don't mess up ё
if export.is_ending_stressed(word) then
return word
end
-- Destress the last stressed syllable
word = make_unstressed_once_ru(word)
-- Add an acute to the last syllable
word = ugsub(word, "([" .. export.vowel_no_jo .. "])([^" .. export.vowel .. "]*)$",
"%1́%2")
-- If that caused an acute and grave next to each other, remove the grave
return export.correct_grave_acute_clash(word)
end
-- Remove the last primary stress from the word and put it on the final syllable. Leave grave accents alone except in
-- the last syllable. If final syllable already has primary stress, do nothing. NOTE: Translit must already be
-- decomposed! See comment at top.
local function make_ending_stressed_after_hyphen_split(ru, tr)
if not tr then
return make_ending_stressed_ru(ru), nil
end
-- If already ending stressed, just return ru/tr so we don't mess up ё
if export.is_ending_stressed(ru) then
return ru, tr
end
-- Destress the last stressed syllable; pass in "noconcat" so we get
-- the individual syllables back
local rusyl, trsyl = make_unstressed_once_after_hyphen_split(ru, tr, "noconcat")
-- Add an acute to the last syllable of both Russian and translit
rusyl[#rusyl] = ugsub(rusyl[#rusyl], "([" .. export.vowel_no_jo .. "])",
"%1" .. AC)
trsyl[#trsyl] = ugsub(trsyl[#trsyl], "([" .. export.tr_vowel .. "])",
"%1" .. AC)
-- If that caused an acute and grave next to each other, remove the grave
rusyl[#rusyl], trsyl[#trsyl] =
export.correct_grave_acute_clash(rusyl[#rusyl], trsyl[#trsyl])
-- j correction didn't get applied in make_unstressed_once because
-- we short-circuited it and made it return lists of syllables
return concat(rusyl, ""),
export.j_correction(concat(trsyl, ""))
end
--[==[
Remove the last primary stress from the portion of the word to the right of any hyphen (unless the hyphen is word-final)
and put it on the final syllable. Leave grave accents alone except in the last syllable. If final syllable already has
primary stress, do nothing. (See `make_unstressed_once()` for why we don't affect stresses to the left of a hyphen.)
NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.make_ending_stressed(ru, tr)
return map_last_hyphenated_component(make_ending_stressed_after_hyphen_split, ru, tr)
end
local function make_beginning_stressed_ru(word)
-- If already beginning stressed, just return word so we don't mess up ё
if export.is_beginning_stressed(word) then
return word
end
-- Destress the first stressed syllable
word = make_unstressed_once_at_beginning_ru(word)
-- Add an acute to the first syllable
word = ugsub(word, "^([^" .. export.vowel .. "]*)([" .. export.vowel_no_jo .. "])",
"%1%2́")
-- If that caused an acute and grave next to each other, remove the grave
return export.correct_grave_acute_clash(word)
end
--[==[
Remove the first primary stress from the word and put it on the initial syllable. Leave grave accents alone except in
the first syllable. If initial syllable already has primary stress, do nothing. NOTE: Translit must already be
decomposed! See comment at top.
]==]
function export.make_beginning_stressed(ru, tr)
if not tr then
return make_beginning_stressed_ru(ru), nil
end
-- If already beginning stressed, just return ru/tr so we don't mess up ё
if export.is_beginning_stressed(ru) then
return ru, tr
end
-- Destress the first stressed syllable; pass in "noconcat" so we get
-- the individual syllables back
local rusyl, trsyl = export.make_unstressed_once_at_beginning(ru, tr, "noconcat")
-- Add an acute to the first syllable of both Russian and translit
rusyl[1] = ugsub(rusyl[1], "([" .. export.vowel_no_jo .. "])",
"%1" .. AC)
trsyl[1] = ugsub(trsyl[1], "([" .. export.tr_vowel .. "])",
"%1" .. AC)
-- If that caused an acute and grave next to each other, remove the grave
rusyl[1], trsyl[1] = export.correct_grave_acute_clash(rusyl[1], trsyl[1])
-- j correction didn't get applied in make_unstressed_once_at_beginning
-- because we short-circuited it and made it return lists of syllables
return concat(rusyl, ""),
export.j_correction(concat(trsyl, ""))
end
-- used for tracking and categorization
local hard_cons = {"hard-cons", "cons"}
local hard_vowel = {"vowel", "hard-vowel"}
local i_vowel = {"i", "vowel", "soft-vowel"}
local sibilant_cons = {"sibilant", "cons"}
local soft_vowel = {"vowel", "soft-vowel"}
local velar_cons = {"velar", "cons"}
local trailing_letter_type = {
["ш"] = sibilant_cons,
["щ"] = sibilant_cons,
["ч"] = sibilant_cons,
["ж"] = sibilant_cons,
["ц"] = {"c", "cons"},
["к"] = velar_cons,
["г"] = velar_cons,
["х"] = velar_cons,
["ь"] = {"soft-cons", "cons"},
["ъ"] = hard_cons,
["й"] = {"palatal", "cons"},
["а"] = hard_vowel,
["я"] = soft_vowel,
["э"] = hard_vowel,
["е"] = soft_vowel,
["ѐ"] = soft_vowel,
["ѣ"] = soft_vowel,
["и"] = i_vowel,
["ѝ"] = i_vowel,
["ӥ"] = i_vowel,
["і"] = i_vowel,
["ї"] = i_vowel,
["ѵ"] = i_vowel,
["ы"] = hard_vowel,
["о"] = hard_vowel,
["ё"] = soft_vowel,
["у"] = hard_vowel,
["ю"] = soft_vowel,
}
function export.get_stem_trailing_letter_type(stem)
return trailing_letter_type[ulower(usub(export.remove_accents(stem), -1))] or hard_cons
end
--[==[
Reduce stem by eliminating the "epenthetic" vowel. Applies to nominative singular masculine 2nd-declension hard and
soft, and 3rd-declension feminine in -ь (e.g. [[любовь]]). `STEM` should be the result after calling
`detect_stem_type()`, but with final -й if present. Normally returns two arguments (`STEM` and `TR`), but can be called
from a template or bot using `#invoke` and will return one argument (`STEM`, or `STEM//TR` if `TR` is present). Returns
{nil} if unable to reduce. NOTE: Translit must already be decomposed! See comment at top.
]==]
function export.reduce_stem(stem, tr, soft_n)
local pre, letter, post
local pretr, lettertr, posttr
local combine_tr = false
-- test cases with translit:
-- =p.reduce_stem("фе́ез", "fɛ́jez") -> фе́йз, fɛ́jz
-- =p.reduce_stem("фе́йез", "fɛ́jez") -> фе́йз, fɛ́jz
-- =p.reduce_stem("фе́без", "fɛ́bez") -> фе́бз, fɛ́bz
-- =p.reduce_stem("фе́лез", "fɛ́lez") -> фе́льз, fɛ́lʹz
-- =p.reduce_stem("феёз", p.decompose("fɛjóz")) -> фейз, fɛjz
-- =p.reduce_stem("фейёз", p.decompose("fɛjjóz")) -> фейз, fɛjz
-- =p.reduce_stem("фебёз", p.decompose("fɛbjóz")) -> фебз, fɛbz
-- =p.reduce_stem("фелёз", p.decompose("fɛljóz")) -> фельз, fɛlʹz
-- =p.reduce_stem("фе́бей", "fɛ́bej") -> фе́бь, fɛ́bʹ
-- =p.reduce_stem("фебёй", p.decompose("fɛbjój")) -> фебь, fɛbʹ
-- =p.reduce_stem("фе́ей", "fɛ́jej") -> фе́йй, fɛ́jj
-- =p.reduce_stem("феёй", p.decompose("fɛjój")) -> фейй, fɛjj
-- so this can be called from a template (or usually, a bot)
if type(stem) == "table" then
stem, tr = ine(stem.args[1]), ine(stem.args[2])
combine_tr = true
end
pre, letter, post = umatch(stem, "^(.*)([оОеЕёЁ])́?([" .. export.cons .. "]+)$")
if not pre then
return nil, nil
end
if tr then
-- FIXME, may not be necessary to write the posttr portion as a
-- consonant + zero or more consonant/accent combinations -- when will
-- we ever get an accent after a consonant? That would indicate a
-- failure of the decompose mechanism.
pretr, lettertr, posttr = umatch(tr, "^(.*)([oOeE])́?([" .. export.tr_cons .. "][" .. export.tr_cons .. export.accent .. "]*)$")
if not pretr then
return nil, nil -- should not happen unless tr is really messed up
end
-- Remove any trailing j's on the translit stem which don't have a
-- corresponding й on the Cyrillic stem. This avoids excess j's with
-- cases like индонези́ец//indonɛzíjec, but retains multiple j's where
-- necessary.
pretr = ugsub(pretr, "[jJ]+$", function(m)
return usub(m, 1, ulen(umatch(pre, "[йЙ]*$")))
end)
end
if letter == "О" or letter == "о" then
-- FIXME, what about when the accent is on the removed letter?
if post == "й" or post == "Й" then
-- FIXME, is this correct?
return nil, nil
end
letter = ""
else
local is_upper = ufind(post, "[" .. export.uppercase .. "]")
if ufind(pre, "[" .. export.vowel .. "]́?$") then
letter = is_upper and "Й" or "й"
elseif post == "й" or post == "Й" then
letter = is_upper and "Ь" or "ь"
post = ""
if posttr then
posttr = ""
end
elseif ufind(post, "[" .. export.velar .. "]$") and ufind(pre, "[" .. export.cons_except_sib_c .. "]$") or
ufind(post, "[^йЙ" .. export.velar .. "]$") and ufind(pre, "[лЛ]$") or
soft_n and ufind(pre, "[нН]$") then
letter = is_upper and "Ь" or "ь"
else
letter = ""
end
end
stem = pre .. letter .. post
if tr then
tr = pretr .. export.translit(letter) .. posttr
end
if combine_tr then
return export.combine_russian_tr(stem, tr)
else
return stem, tr
end
end
--[==[
Generate the dereduced stem given `STEM` and `EPENTHETIC_STRESS` (which indicates whether the epenthetic vowel should be
stressed); this is without any terminating non-syllabic ending, which is added if needed by the calling function.
Normally returns two arguments (`STEM` and `TR`), but can be called from a template using `#invoke` (or by bot) and will
return one argument (`STEM`, or `STEM//TR` if `TR` is present). Returns {nil} if unable to dereduce. NOTE: Translit
must already be decomposed! See comment at top.
]==]
function export.dereduce_stem(stem, tr, epenthetic_stress)
local combine_tr = false
-- so this can be called from a template (or usually, a bot)
if type(stem) == "table" then
stem, tr, epenthetic_stress = ine(stem.args[1]), ine(stem.args[2]), ine(stem.args[3])
combine_tr = true
end
if epenthetic_stress then
stem, tr = export.make_unstressed_once(stem, tr)
end
local pre, letter, post
local pretr, lettertr, posttr
-- FIXME!!! Deal with this special case
--if not (z.stem_type == "soft" and _.equals(z.stress_type, {"b", "f"}) -- we should ignore asterix for 2*b and 2*f (so to process it just like 2b or 2f)
-- or _.contains(z.specific, "(2)") and _.equals(z.stem_type, {"velar", "letter-ц", "vowel"})) -- and also the same for (2)-specific and 3,5,6 stem-types
--then
-- I think this corresponds to our -ья and -ье types, which we
-- handle separately
--if z.stem_type == "vowel" then -- 1).
-- if _.equals(z.stress_type, {"b", "c", "e", "f", "f'", "b'" }) then -- gen_pl ending stressed -- TODO: special vars for that
-- z.stems["gen_pl"] = _.replace(z.stems["gen_pl"], "ь$", "е́")
-- else
-- z.stems["gen_pl"] = _.replace(z.stems["gen_pl"], "ь$", "и")
-- end
--end
pre, letter, post = umatch(stem, "^(.*)([" .. export.cons .. "])([" .. export.cons .. "])$")
if tr then
pretr, lettertr, posttr = umatch(tr, "^(.*)(" .. export.tr_cons_acc_re .. ")(" .. export.tr_cons_acc_re .. ")$")
if pre and not pretr then
return nil, nil -- should not happen unless tr is really messed up
end
end
if pre then
local is_upper = ufind(post, "[" .. export.uppercase .. "]")
local epvowel
if ufind(letter, "[ьйЬЙ]") then
letter = ""
lettertr = ""
if ufind(post, "[цЦ]$") or not epenthetic_stress then
epvowel = is_upper and "Е" or "е"
else
epvowel = is_upper and "Ё" or "ё"
end
elseif ufind(letter, "[" .. export.cons_except_sib_c .. "]") and ufind(post, "[" .. export.velar .. "]") or
ufind(letter, "[" .. export.velar .. "]") then
epvowel = is_upper and "О" or "о"
elseif post == "ц" or post == "Ц" then
epvowel = is_upper and "Е" or "е"
elseif epenthetic_stress then
if ufind(letter, "[" .. export.sib .. "]") then
epvowel = is_upper and "О́" or "о́"
else
epvowel = is_upper and "Ё" or "ё"
end
else
epvowel = is_upper and "Е" or "е"
end
assert(epvowel)
stem = pre .. letter .. epvowel .. post
if tr then
tr = pretr .. lettertr .. export.translit(epvowel) .. posttr
tr = export.j_correction(tr)
end
if epenthetic_stress then
stem, tr = export.make_ending_stressed(stem, tr)
end
if combine_tr then
return export.combine_russian_tr(stem, tr)
else
return stem, tr
end
end
return nil, nil
end
--[==[
Parse an entry that potentially has final footnote symbols and initial `*` for a hypothetical entry into initial
symbols, text and final symbols.
]==]
function export.split_symbols(entry, do_subscript)
local prefentry, finalnotes = m_table_tools.separate_notes(entry)
local initnotes, text = umatch(prefentry, "(%*?)(.*)$")
return initnotes, text, finalnotes
end
--------------------------------------------------------------------------
-- Used for manual translit --
--------------------------------------------------------------------------
--[==[
Transliterate text after removing any links. The translit is decomposed in the process.
]==]
function export.translit_no_links(text)
return export.translit(require("Module:links").remove_links(text))
end
--[==[
Split a combined Cyrillic/translit string (either in the form `CYRILLIC//TRANSLIT` or just `CYRILLIC`) into its
components, decompose the translit, and either return a two-element list of `CYRILLIC` and `TRANSLIT` (if `dopair` is
given), or two separate return values (if `dopair` is not specified).
]==]
function export.split_russian_tr(term, dopair)
local ru, tr
if not term:find("//") then
ru = term
else
local splitvals = split(term, "//")
if #splitvals ~= 2 then
error("Must have at most one // in a Russian//translit expr: '" .. term .. "'")
end
ru, tr = splitvals[1], export.decompose(splitvals[2])
end
if dopair then
return {ru, tr}
else
return ru, tr
end
end
--[==[
Combine Cyrillic and translit into a single string separated by `//`. If translit is {nil}, the return value will be
the same as the passed-in Cyrillic. `ru` can be a string (which any manual translit in `tr`) or a two-element table of
`{CYRILLIC, TRANSLIT}`.
]==]
function export.combine_russian_tr(ru, tr)
if type(ru) == "table" then
ru, tr = unpack(ru)
end
if tr then
return ru .. "//" .. tr
else
return ru
end
end
local function concat_maybe_moving_notes(x, y, movenotes)
if movenotes then
local xentry, xnotes = m_table_tools.separate_notes(x)
local yentry, ynotes = m_table_tools.separate_notes(y)
return xentry .. yentry .. xnotes .. ynotes
else
return x .. y
end
end
--[==[
Concatenate two Cyrillic strings `RU1` and `RU2` that may have corresponding manual transliteration `TR1` and `TR2`
(which should be {nil} if there is no manual translit, and otherwise should be decomposed). If `DOPAIR`, return a
two-item list of the combined Cyrillic and manual translit (which will be {nil} if both `TR1` and `TR2` are {nil});
else, return two values, the combined Cyrillic and manual translit. If `MOVENOTES`, extract any footnote symbols at the
end of `RU1` and move them to the end of the concatenated string, before any footnote symbols for `RU2`; same thing goes
for `TR1` and `TR2`.
]==]
function export.concat_russian_tr(ru1, tr1, ru2, tr2, dopair, movenotes)
local ru, tr
if not tr1 and not tr2 then
ru = concat_maybe_moving_notes(ru1, ru2, movenotes)
else
if not tr1 then
tr1 = export.translit_no_links(ru1)
end
if not tr2 then
tr2 = export.translit_no_links(ru2)
end
ru, tr = concat_maybe_moving_notes(ru1, ru2, movenotes), export.j_correction(concat_maybe_moving_notes(tr1, tr2, movenotes))
end
if dopair then
return {ru, tr}
else
return ru, tr
end
end
--[==[
Concatenate two Cyrillic/translit combinations (where each combination is a two-element list of `{CYRILLIC, TRANSLIT}`
where `TRANSLIT` may be nil) by individually concatenating the Cyrillic and translit portions, and return a concatenated
combination as a two-element list. If the manual translit portions of both terms on entry are {nil}, the result will
also have {nil} manual translit. If `MOVENOTES`, extract any footnote symbols at the end of `TERM1` and move them after
the concatenated string and before any footnote symbols at the end of `TERM2`.
]==]
function export.concat_paired_russian_tr(term1, term2, movenotes)
assert(type(term1) == "table")
assert(type(term2) == "table")
local ru1, tr1 = term1[1], term1[2]
local ru2, tr2 = term2[1], term2[2]
return export.concat_russian_tr(ru1, tr1, ru2, tr2, "dopair", movenotes)
end
function export.concat_forms(forms)
local joined_rutr = {}
for _, form in ipairs(forms) do
insert(joined_rutr, export.combine_russian_tr(form))
end
return concat(joined_rutr, ",")
end
--[==[
Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, strip footnote symbols from the
end of the Cyrillic and translit.
]==]
function export.strip_notes_from_forms(forms)
local newforms = {}
for _, form in ipairs(forms) do
local ru, tr = form[1], form[2]
ru, _ = m_table_tools.separate_notes(ru)
if tr then
tr, _ = m_table_tools.separate_notes(tr)
end
insert(newforms, {ru, tr})
end
return newforms
end
--[==[
Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, unzip into parallel lists of
Cyrillic and translit. The latter list may have gaps in it.
]==]
function export.unzip_forms(forms)
local rulist = {}
local trlist = {}
for i, form in ipairs(forms) do
local ru, tr = form[1], form[2]
rulist[i] = ru
trlist[i] = tr
end
return rulist, trlist
end
--[==[
Given parallel lists of Cyrillic and translit (where the latter list may have gaps in it), return a list of forms,
where each form is a two-element list of `{CYRILLIC, TRANSLIT}`.
]==]
function export.zip_forms(rulist, trlist)
local forms = {}
for i, ru in ipairs(rulist) do
insert(forms, {ru, trlist[i]})
end
return forms
end
local function any_forms_have_translit(forms)
for _, form in ipairs(forms) do
if form[2] then
return true
end
end
return false
end
--[==[
Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, combine forms with identical
Cyrillic, concatenating the translit with a comma+space in between.
]==]
function export.combine_translit_of_duplicate_forms(forms)
if not forms[1] then
return forms
end
-- Optimization to avoid creating a new list in the majority case when no translit exists.
if not any_forms_have_translit(forms) then
return forms
end
local newforms = {}
insert(newforms, {forms[1][1], forms[1][2]})
for i = 2, #forms do
local found_duplicate = false
for j = 1, #newforms do
-- If the Russian of the next form is the same as that of the last one, combine their translits and modify
-- newforms[] in-place. Otherwise add the next form to newforms[]. Make sure to clone the form rather than
-- just appending it directly since we may modify it in-place; we don't want to side-effect `forms` as
-- passed in.
if forms[i][1] == newforms[j][1] then
local tr1 = newforms[j][2]
local tr2 = forms[i][2]
if not tr1 and not tr2 then
-- this shouldn't normally happen
else
tr1 = tr1 or export.translit_no_links(newforms[j][1])
tr2 = tr2 or export.translit_no_links(forms[i][1])
if tr1 == tr2 then
-- this shouldn't normally happen
else
newforms[j][2] = tr1 .. ", " .. tr2
end
end
found_duplicate = true
break
end
end
if not found_duplicate then
insert(newforms, {forms[i][1], forms[i][2]})
end
end
return newforms
end
function export.any_termobjs_have_translit(termobjs)
for _, termobj in ipairs(termobjs) do
if termobj.tr then
return true
end
end
return false
end
--[==[
Given a list of term objects, where each object is a table of `{term = CYRILLIC, tr = TRANSLIT, ...}`, combine forms
with identical Cyrillic and identical ancillary properties, concatenating the translit with a comma+space in between.
Also recompose all transliteration.
]==]
function export.combine_translit_of_duplicate_termobjs_and_recompose(termobjs)
if not termobjs[1] then
return termobjs
end
-- Optimization to avoid creating a new list in the majority case when no translit exists.
if not export.any_termobjs_have_translit(termobjs) then
return termobjs
end
local newtermobjs = {}
insert(newtermobjs, m_table.shallowCopy(termobjs[1]))
for i = 2, #termobjs do
local found_duplicate = false
for j = 1, #newtermobjs do
-- If the Russian of the next termobj is the same as that of the last one, combine their translits and
-- modify newtermobjs[] in-place. Otherwise add the next termobj to newtermobjs[]. Make sure to clone the
-- termobj rather than just appending it directly since we may modify it in-place; we don't want to
-- side-effect `termobjs` as passed in.
if termobjs[i].term == newtermobjs[j].term and
m_headword_utilities.termobj_decorations_equal(termobjs[i], newtermobjs[j]) then
local tr1 = newtermobjs[j].tr
local tr2 = termobjs[i].tr
if not tr1 and not tr2 then
-- this shouldn't normally happen
else
tr1 = tr1 or export.translit_no_links(newtermobjs[j].term)
tr2 = tr2 or export.translit_no_links(termobjs[i].term)
if tr1 == tr2 then
-- this shouldn't normally happen
else
newtermobjs[j].tr = tr1 .. ", " .. tr2
end
end
found_duplicate = true
break
end
end
if not found_duplicate then
insert(newtermobjs, m_table.shallowCopy(termobjs[i]))
end
end
for _, newtermobj in ipairs(newtermobjs) do
if newtermobj.tr then
newtermobj.tr = export.recompose(newtermobj.tr)
end
end
return newtermobjs
end
--[==[
Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, split cases where two different
transliterations have been packed into a single translit field by creating two adjacent term/translit pairs. This is
the opposite operation of `combine_translit_of_duplicate_forms()`.
]==]
function export.split_translit_of_duplicate_forms(forms)
if not forms[1] then
return forms
end
-- Optimization to avoid creating a new list in the majority case when no translit exists.
if not export.any_forms_have_translit(forms) then
return forms
end
local newforms = {}
for _, form in ipairs(forms) do
local ru, tr = unpack(form)
if not tr or not tr:find(",") or ru:find(",") then
insert(newforms, form)
else
local split_trs = split(tr, ",%s*")
local default_tr = export.translit_no_links(ru)
for _, split_tr in ipairs(split_trs) do
if split_tr == default_tr then
split_tr = nil
end
insert(newforms, {ru, split_tr})
end
end
end
return newforms
end
--[==[
Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, split cases where two different
transliterations have been packed into a single translit field by creating two adjacent term/translit pairs. This is
the opposite operation of `combine_translit_of_duplicate_forms()`.
]==]
function export.split_translit_of_duplicate_termobjs_and_decompose(termobjs)
if not termobjs[1] then
return termobjs
end
-- Optimization to avoid creating a new list in the majority case when no translit exists.
if not export.any_termobjs_have_translit(termobjs) then
return termobjs
end
local newobjs = {}
for _, obj in ipairs(termobjs) do
local tr = obj.tr
if tr then
tr = export.decompose(tr)
end
if not tr or not tr:find(",") or obj.term:find(",[^%s]") then
if not tr or tr == obj.tr then
insert(newobjs, obj)
else
obj = m_table.shallowCopy(obj)
obj.tr = tr
insert(newobjs, obj)
end
else
local term = obj.term
local embedded_comma_in_term = not not term:find(",")
-- If there's an embedded comma in the term, we want to split on commas not followed by a space; otherwise,
-- we split on commas whether or not followed by a space. To implement "comma not followed by space", we
-- can use a "frontier pattern"; the one below matches if the character after the comma is in the specified
-- set (which is anything but a comma or space) and the comma itself is not in the specified set (in other
-- words, it is a comma or space, which matches because it's a comma). This doesn't work correctly if there
-- are two commas in a row, but that should not happen as there should not be an empty translit.
local split_trs = split(tr, embedded_comma_in_term and ",%f[^,%s]" or ",%s*")
local default_tr = export.translit_no_links(term)
for _, split_tr in ipairs(split_trs) do
if split_tr == default_tr then
split_tr = nil
end
local newobj = m_table.shallowCopy(obj)
newobj.tr = split_tr
insert(newobjs, newobj)
end
end
end
return newobjs
end
function export.strip_ending(ru, tr, ending)
local strippedru = ugsub(ru, ending .. "$", "")
if strippedru == ru then
error("Argument " .. ru .. " doesn't end with expected ending " .. ending)
end
ru = strippedru
tr = export.strip_tr_ending(tr, ending)
return ru, tr
end
function export.strip_tr_ending(tr, ending)
if not tr then return nil end
local endingtr = ugsub(export.translit_no_links(ending), "^([Jj])", "%1?")
local strippedtr = ugsub(tr, endingtr .. "$", "")
if strippedtr == tr then
error("Translit " .. tr .. " doesn't end with expected ending " .. endingtr)
end
return strippedtr
end
return export
cinwuq7bsjbmvq2o8hkfqhc3tn6d4lh
Module:IPA
828
1369671
9909030
9652257
2026-09-29T07:22:07Z
TongcyDai
53191
同步 enwikt:pron qualifier → decorations
9909030
Scribunto
text/plain
local export = {}
local force_cat = false -- for testing
local decorations_module = "Module:decorations"
local pages_module = "Module:pages"
local qualifier_module = "Module:qualifier"
local string_utilities_module = "Module:string utilities"
local syllables_module = "Module:syllables"
local utilities_module = "Module:utilities"
local m_data = mw.loadData("Module:IPA/data")
local m_str_utils = require(string_utilities_module)
local m_syllables -- [[Module:syllables]]; loaded below if needed
local m_symbols = mw.loadData("Module:IPA/data/symbols")
local concat = table.concat
local decode_entities = m_str_utils.decode_entities
local find = string.find
local gmatch = m_str_utils.gmatch
local gsub = string.gsub
local insert = table.insert
local is_preview = require(pages_module).is_preview
local len = m_str_utils.len
local listToText = mw.text.listToText
local match = string.match
local pattern_escape = m_str_utils.pattern_escape
local sub = string.sub
local u = m_str_utils.char
local ugsub = m_str_utils.gsub
local umatch = m_str_utils.match
local usub = m_str_utils.sub
local namespace = mw.title.getCurrentTitle().nsText
local function is_content_page(lang, namespace)
return namespace == "" or namespace == "Reconstruction" or
lang and lang:hasType("appendix-constructed") and namespace == "Appendix"
end
local function track(page)
require("Module:debug/track")("IPA/" .. page)
return true
end
local function process_maybe_split_categories(split_output, categories, prontext, lang, errtext)
if split_output ~= "raw" then
if categories[1] then
categories = require(utilities_module).format_categories(categories, lang, nil, nil, force_cat)
else
categories = ""
end
end
if split_output then -- for use of IPA in links, etc.
if errtext then
return prontext, categories, errtext
else
return prontext, categories
end
else
return prontext .. (errtext or "") .. categories
end
end
--[==[
Format a line of one or more IPA pronunciations as {{tl|IPA}} would do it, i.e. with a preceding {"IPA:"} followed by
the word {"key"} linking to an Appendix page describing the language's phonology, and with an added category
` ``lang`` terms with IPA pronunciation`. Other than the extra preceding text and category, this is identical
to {format_IPA_multiple()}, and the considerations described there in the documentation apply here as well. There is a
single parameter `data`, an object with the following fields:
* `lang`: Object representing the language of the pronunciations, which is used when adding cleanup categories for
pronunciations with invalid phonemes; for determining how many syllables the pronunciations have in them, in order to
add a category such as [[:Category:Italian 2-syllable words]] (for certain languages only); for adding a category
` ``lang`` terms with IPA pronunciation`; and for determining the proper sort keys for categories. Unlike
for {format_IPA_multiple()}, `lang` may not be {nil}.
* `items`: List of pronunciations, in exactly the same format as for {format_IPA_multiple()}.
* `err`: If not {nil}, a string containing an error message to use in place of the link to the language's phonology.
* `separator`: The default separator to use when separating formatted items. Defaults to {", "}. Does not apply to the
first item, where the default separator is always the empty string. Overridden by the per-item `separator` field in
`items`.
* `sort_key`: Explicit sort key used for categories.
* `no_count`: Suppress adding a {#-syllable words} category such as [[:Category:Italian 2-syllable words]]. Note that
only certain languages add such categories to begin with, because it depends on knowing how to count syllables in a
given language, which depends on the phonology of the language. Also, this does not suppress the addition of cleanup
or other categories. If you need them suppressed, use `split_output` to return the categories separately and ignore
them.
* `split_output`: If not given, the return value is a concatenation of the formatted pronunciation 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 a combination of category
strings and category objects of 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.
* `include_langname`: If specified, prefix the result with the language name, followed by a colon.
* `q`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display at the beginning, before the formatted
pronunciations and preceding {"IPA:"}.
* `qq`: {nil} or a list of right qualifiers to display after all formatted pronunciations.
* `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display at the beginning, before the formatted
pronunciations and preceding {"IPA:"}.
* `aa`: {nil} or a list of right accent qualifiers to display after all formatted pronunciations.
]==]
function export.format_IPA_full(data)
if type(data) ~= "table" or data.getCode then
error("Must now supply a table of arguments to format_IPA_full(); first argument should be that table, not a language object")
end
local lang = data.lang
local items = data.items
local err = data.err
local separator = data.separator
local sort_key = data.sort_key
local no_count = data.no_count
local split_output = data.split_output
local q = data.q
local qq = data.qq
local a = data.a
local aa = data.aa
local include_langname = data.include_langname
local hasKey = m_data.langs_with_infopages
if not lang or not lang.getCode then
error("Must specify language to format_IPA_full()")
end
local langname = lang:getCanonicalName()
local prefix_text
if err then
prefix_text = '<span class="error">' .. err .. '</span>'
else
if hasKey[lang:getCode()] then
prefix_text = "Appendix:" .. langname .. "發音"
else
prefix_text = "wikipedia:" .. langname .. "音系"
end
prefix_text = "[[" .. prefix_text .. "|幫助]]"
end
local prefix = "[[Wiktionary:國際音標|國際音標]]<sup>(" .. prefix_text .. ")</sup>: "
local IPAs, categories = export.format_IPA_multiple(lang, items, separator, no_count, "raw")
if is_content_page(lang, namespace) then
insert(categories, {
cat = "有國際音標的" .. langname .. "詞",
sort_key = sort_key
})
end
local prontext = prefix .. IPAs
if q and q[1] or qq and qq[1] or a and a[1] or aa and aa[1] then
prontext = require(decorations_module).format_decorations {
lang = lang,
text = prontext,
q = q,
qq = qq,
a = a,
aa = aa,
}
end
if include_langname then
prontext = langname .. ":" .. prontext
end
return process_maybe_split_categories(split_output, categories, prontext, lang)
end
local function split_phonemic_phonetic(pron)
local reconstructed, phonemic, phonetic = match(pron, "^(%*?)(/.-/)%s+(%[.-%])$")
if reconstructed then
return reconstructed .. phonemic, reconstructed .. phonetic
else
return pron, nil
end
end
local function determine_repr(pron)
local reconstructed
-- Temporarily remove any initial asterisk before representation marks,
-- which avoids having to account for it in the data, but set the
-- `reconstructed` flag.
if sub(pron, 1, 1) == "*" then
reconstructed = true
pron = sub(pron, 2)
end
-- Some representation types have aliases for convenience (e.g. "// //" is
-- an alias for "⫽ ⫽"). and these need to be substituted in before checking
-- for other data.
local opening, n = match(pron, "^.[\128-\191]*")
local subs_data = m_data.representation_subs[opening]
if subs_data then
pron, n = ugsub(pron, subs_data[1], subs_data[2])
-- If the substitution was made, `opening` needs to be changed to the
-- new opening character.
if n ~= 0 then
opening = subs_data[3]
end
end
-- Get the type data based on the opening character (if any), and set the
-- representation type if the closing character matches.
local type_data, repr, closing = m_data.representation_types[opening]
if type_data then
closing = type_data[2]
if type_data and match(pron, pattern_escape(closing) .. "$", #opening + 1) then
repr = type_data[1]
end
end
-- Default to the empty string.
if not repr then
opening, closing = "", ""
end
-- Reattach the asterisk if reconstructed.
if reconstructed then
pron = "*" .. pron
end
return pron, repr, opening, closing, reconstructed
end
local function hasInvalidSeparators(transcription)
-- Escape certain characters as well as pauses, which have the format "(...)" (with any number of dots), to avoid false-positives.
transcription = transcription:gsub(".[\128-\191]*", m_symbols.separator_escapes)
:gsub("%(%.+%)", "\3")
:gsub("[()]+", "")
return (
transcription:find("..", nil, true) or
transcription:match("%.%f[%z \1\2\3,:;]") or
transcription:match("\1%f[%z \2\3,:;]") or
transcription:match("\2%f[%z \1\3,:;]") or
transcription:match("\3[:;]") or
transcription:match("%f[^%z \1\2\3,]%.")
) and true or false
end
--[==[
Format a line of one or more bare IPA pronunciations (i.e. without any preceding {"IPA:"} and without adding to a
category ` ``lang`` terms with IPA pronunciation`). Individual pronunciations are formatted using
{format_IPA()} and are combined with separators, decorations, pre-text, post-text, etc. to form a line of pronunciations.
Parameters accepted are:
* `lang` is an object representing the language of the pronunciations, which is used when adding cleanup categories for
pronunciations with invalid phonemes; for determining how many syllables the pronunciations have in them, in order to
add a category such as [[:Category:Italian 2-syllable words]] (for certain languages only); and for computing the
proper sort keys for categories. `lang` may be {nil}.
* `items` is a list of pronunciations, each of which is an object with the following properties:
** `pron`: the pronunciation, in the same format as is accepted by {format_IPA()}, i.e. it should be either phonemic
(surrounded by {/.../}), phonetic (surrounded by {[...]}), orthographic (surrounded by {⟨...⟩}) or a rhyme
(beginning with a hyphen);
** `pretext`: text to display directly before the formatted pronunciation, inside of any qualifiers or accent
qualifiers;
** `posttext`: text to display directly after the formatted pronunciation, inside of any qualifiers or accent
qualifiers;
** `q` or `qualifiers`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display before the formatted
pronunciation; note that `qualifiers` is deprecated;
** `qq`: {nil} or a list of right qualifiers to display after the formatted pronunciation;
** `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display before the formatted pronunciation;
** `aa`: {nil} or a list of right accent qualifiers to after before the formatted pronunciation;
** `refs`: {nil} or a list of references or reference specs to add after the pronunciation and any posttext and
qualifiers; the value of a list item 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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}}
or {{cd|<nowiki><ref name="foo" /></nowiki>}}) and/or `group` (the group of the reference, as in
{{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section;
** `gloss`: {nil} or a gloss (definition) for this item, if different definitions have different pronunciations;
** `pos`: {nil} or a part of speech for this item, if different parts of speech have different pronunciations;
** `separator`: the separator text to insert directly before the formatted pronunciation and all decorations
and pre-text; defaults to the outer `separator` parameter.
* `separator`: The default separator to use when separating formatted items. Defaults to {", "}. Does not apply to the
first item, where the default separator is always the empty string. Overridden by the per-item `separator` field in
`items`.
* `no_count`: Suppress adding a {#-syllable words} category such as [[:Category:Italian 2-syllable words]]. Note that
only certain languages add such categories to begin with, because it depends on knowing how to count syllables in a
given language, which depends on the phonology of the language. Also, this does not suppress the addition of cleanup
categories. If you need them suppressed, use `split_output` to return the categories separately and ignore them.
* `split_output`: If not given, the return value is a concatenation of the formatted pronunciation 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 a combination of category
strings and category objects of 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.
]==]
function export.format_IPA_multiple(lang, items, separator, no_count, split_output)
local categories = {}
separator = separator or ", "
if not lang then
track("format-multiple-nolang")
end
-- Format
if not items[1] then
if namespace == "Template" then
insert(items, {pron = "/aɪ piː ˈeɪ/"})
else
insert(categories, "使用發音模板而缺少發音的詞條")
end
end
local bits = {}
for i, item in ipairs(items) do
local bit
-- If the pronunciation is entirely empty, allow this and don't do anything, so that e.g. the pretext and/or
-- posttext can be specified to force something like ''unknown'' to appear in place of the pronunciation
-- (as happens e.g. when ? is used as a respelling in [[Module:ca-IPA]]; see [[guèiser]] for an example).
if item.pron == "" then
bit = ""
else
local item_categories, errtext
bit, item_categories, errtext = export.format_IPA(lang, item.pron, "raw")
bit = bit .. errtext
for _, cat in ipairs(item_categories) do
insert(categories, cat)
end
end
if item.pretext then
bit = item.pretext .. bit
end
if item.posttext then
bit = bit .. item.posttext
end
local has_decorations = item.q and item.q[1] or item.qq and item.qq[1] or item.qualifiers and item.qualifiers[1]
or item.a and item.a[1] or item.aa and item.aa[1] or item.refs and item.refs[1]
local has_gloss_or_pos = item.gloss or item.pos
if has_decorations or has_gloss_or_pos then
-- FIXME: Currently we tack the gloss and POS (in that order) onto the end of the regular left qualifiers.
-- Should we do something different?
local q = item.q
if has_gloss_or_pos then
q = mw.clone(item.q) or {}
if item.gloss then
local m_qualifier = require(qualifier_module)
insert(q, m_qualifier.wrap_qualifier_css("“", "quote") .. item.gloss ..
m_qualifier.wrap_qualifier_css("”", "quote"))
end
if item.pos then
-- FIXME: Consider expanding aliases as found in [[Module:headword/data]] or similar.
insert(q, item.pos)
end
end
bit = require(decorations_module).format_decorations {
lang = lang,
text = bit,
q = q,
qq = item.qq,
qualifiers = item.qualifiers,
a = item.a,
aa = item.aa,
refs = item.refs,
}
end
bit = (item.separator or (i == 1 and "" or separator)) .. bit
insert(bits, bit)
--[=[ [[Special:WhatLinksHere/Wiktionary:Tracking/IPA/syntax-error]]
The length or gemination symbol should not appear after a syllable break or stress symbol. ]=]
-- The nature of the following pattern match is such that we don't have to split a combined '/.../ [...]' spec
-- into its parts in order to process.
if match(item.pron, "[.\203][\136\140]?\203[\144\145]") then -- [.ˈˌ][ːˑ]
track("syntax-error")
end
if lang then
-- Add syllable count if the language's diphthongs are listed in [[Module:syllables]].
-- Don't do this if the term has spaces, a liaison mark (‿) or isn't in mainspace.
if not no_count and namespace == "" then
m_syllables = m_syllables or require(syllables_module)
local langcode = lang:getCode()
if m_data.langs_to_generate_syllable_count_categories[langcode] then
local raw_phonemic, phonetic, use_it = split_phonemic_phonetic(item.pron)
local phonemic, repr = determine_repr(raw_phonemic)
if not phonetic then -- not a '/.../ [...]' combined pronunciation
if m_data.langs_to_use_phonetic_notation[langcode] then
use_it = repr == "phonetic" and phonemic or nil
else
use_it = repr == "phonemic" and phonemic or nil
end
elseif repr == "phonetic" then
use_it = phonetic
elseif repr == "phonemic" then
use_it = phonemic
end
-- Note: two uses of find with plain patterns is much faster than umatch with [ ‿].
if use_it and not (find(use_it, " ") or find(use_it, "‿")) then
local syllable_count = m_syllables.getVowels(use_it, lang)
if syllable_count then
insert(categories, lang:getCanonicalName() .. syllable_count ..
"音節詞")
end
end
end
end
end
end
return process_maybe_split_categories(split_output, categories, concat(bits), lang)
end
--[=[
Format a single IPA pronunciation, which cannot be a combined spec (such as {/.../ [...]}). This has been extracted from
{format_IPA()} to allow the latter to handle such combined specs. This works like {format_IPA()} but requires that
pre-created {err} (for error messages) and {categories} lists be passed in, and adds any generated error messages and
categories to those lists. A single value is returned, the pronunciation, which is usually the same as passed in, but
may have HTML added surrounding invalid characters so they appear in red.
]=]
local function format_one_IPA(lang, raw_pron, err, categories)
-- Disallow wikilinks.
if match(raw_pron, "%[%[.-%]%]") then
error("IPA input must not contain wikilinks.")
end
raw_pron = decode_entities(raw_pron)
-- Detect the type of transcription.
local pron, repr, opening, closing, reconstructed = determine_repr(raw_pron)
-- Strip any reconstruction asterisk and representation marks.
pron = sub(pron, #opening + 1 + (reconstructed and 1 or 0), -#closing - 1)
if not repr then
insert(categories, "使用無效表示符號的國際音標發音")
-- insert(err, "invalid representation marks")
-- Removed because it's annoying when previewing pronunciation pages.
end
if repr ~= "orthographic" and lang and lang:getCode() == "en" and hasInvalidSeparators(pron) then
insert(categories, "國際音標使用.ˈ或.ˌ的英語詞")
end
if pron == "" then
insert(categories, "沒有發音的國際音標發音")
end
-- Check for obsolete and nonstandard symbols
for _, symbol in ipairs(m_data.nonstandard) do
local result
for nonstandard in gmatch(pron, symbol) do
if not result then
result = {}
end
insert(result, nonstandard)
insert(categories,
{cat = "使用廢棄或非標準字符的國際音標發音", sort_key = nonstandard}
)
end
if result then
insert(err, "obsolete or nonstandard characters (" .. concat(result) .. ")")
break
end
end
--[[ Check for invalid symbols after removing the following:
1. wikilinks (handled above)
2. paired HTML tags
3. bolding
4. italics
5. asterisk at beginning of transcription
6. comma followed by spacing characters
7. superscripts enclosed in superscript parentheses ]]
local found_HTML
local result = gsub(pron, "<(%a+)[^>]*>([^<]+)</%1>",
function(tagName, content)
found_HTML = true
return content
end)
result = gsub(result, "'''([^']*)'''", "%1")
result = gsub(result, "''([^']*)''", "%1")
result = gsub(result, "^%*", "")
result = ugsub(result, ",%s+", "")
-- VS15
local vs15_class = "[" .. m_symbols.add_vs15 .. "]"
if umatch(pron, vs15_class) then
local vs15 = u(0xFE0E)
if find(result, vs15) then
result = gsub(result, vs15, "")
pron = gsub(pron, vs15, "")
end
pron = ugsub(pron, vs15_class, "%0" .. vs15)
end
if result ~= "" then
if lang then
-- Get the per_lang_valid data, and convert any per-language valid sequences to spaces.
local per_lang_valid = m_symbols.per_lang_valid[lang:getCode()]
if per_lang_valid then
if type(per_lang_valid) == "table" then
for _, pattern in pairs(per_lang_valid) do
result = ugsub(result, pattern, " ")
end
else -- Should be a string.
result = ugsub(result, per_lang_valid, " ")
end
end
end
local suggestions
-- Check for any invalid sequences, excluding anything in the per-language lookup table.
for k, v in pairs(m_symbols.invalid) do
if find(result, k, nil, true) then
if not suggestions then
suggestions = {}
end
insert(suggestions, k .. " with " .. v)
end
end
if suggestions and suggestions[1] then
suggestions = listToText(suggestions)
if is_content_page(lang, namespace) then
error("Invalid IPA: replace " .. suggestions)
end
insert(err, "replace " .. suggestions)
end
-- Convert any valid character sequences to spaces
for _, pattern in pairs(m_symbols.valid) do
result = ugsub(result, pattern, " ")
end
if not match(result, "^ *$") then
local category = "使用無效表示符號的國際音標發音"
if not is_content_page(lang, namespace) then
category = category .. "/non_mainspace"
end
insert(categories, category)
insert(err, "invalid IPA characters (" .. result .. ")")
end
end
if found_HTML then
insert(categories, "使用成對HTML標籤的國際音標發音")
end
if (repr == "phonemic" or repr == "rhyme") and lang and m_data.phonemes[lang:getCode()] then
local valid_phonemes = m_data.phonemes[lang:getCode()]
local rest = pron
local phonemes = {}
while #rest > 0 do
local longestmatch, longestmatch_len = "", 0
local rest_init = sub(rest, 1, 1)
if rest_init == "(" or rest_init == ")" then
longestmatch = rest_init
longestmatch_len = 1
else
for _, phoneme in ipairs(valid_phonemes) do
local phoneme_len = len(phoneme)
if phoneme_len > longestmatch_len and usub(rest, 1, phoneme_len) == phoneme then
longestmatch = phoneme
longestmatch_len = len(longestmatch)
end
end
end
if longestmatch_len > 0 then
insert(phonemes, longestmatch)
rest = usub(rest, longestmatch_len + 1)
else
local phoneme = usub(rest, 1, 1)
insert(phonemes, "<span style=\"color: var(--wikt-palette-red,red)\">" .. phoneme .. "</span>")
rest = usub(rest, 2)
insert(categories, "含有無效音位的國際音標發音/" .. lang:getCode())
track("invalid phonemes/" .. phoneme)
end
end
pron = concat(phonemes)
end
return (reconstructed and "*" or "") .. opening .. pron .. closing
end
--[==[
Format an IPA pronunciation. This wraps the pronunciation in appropriate CSS classes and adds cleanup categories and
error messages as needed. The pronunciation `pron` should be either phonemic (surrounded by {/.../}), phonetic
(surrounded by {[...]}), orthographic (surrounded by {⟨...⟩}), a rhyme (beginning with a hyphen) or a combined
phonemic/phonetic spec (of the form {/.../ [...]}). `lang` indicates the language of the pronunciation and can be {nil}.
If not {nil}, and the specified language has data in [[Module:IPA/data]] indicating the allowed phonemes, then the page
will be added to a cleanup category and an error message displayed next to the outputted pronunciation. Note that {lang}
also determines sort key processing in the added cleanup categories. If `split_output` is not given, the return value is
a concatenation of the formatted pronunciation, error messages and formatted cleanup categories. Otherwise, three values
are returned: the formatted pronunciation, the cleanup categories and the concatenated error messages. If `split_output`
is the value {"raw"}, the cleanup categories are returned in list form, where the list elements are a combination of
category strings and category objects of the form suitable for passing to {format_categories()} in [[Module:utilities]].
If `split_output` is any other value besides {nil}, the cleanup categories are returned as a pre-formatted concatenated
string.
]==]
function export.format_IPA(lang, pron, split_output)
local err = {}
local categories = {}
-- `pron` shouldn't contain ref tags.
if match(pron, "\127'\"`UNIQ%-%-ref%-[%dA-F]+%-QINU`\"'\127") then
error("<ref> tags found inside pronunciation parameter.")
end
if not lang then
track("format-nolang")
end
local phonemic, phonetic = split_phonemic_phonetic(pron)
pron = format_one_IPA(lang, phonemic, err, categories)
if phonetic then
track("phonemic-phonetic") -- There's no benefit to supporting the "/.../ [...]" format within one parameter.
phonetic = format_one_IPA(lang, phonetic, err, categories)
pron = pron .. " " .. phonetic
end
if err[1] and is_preview() then
err = '<span class="error" style="font-size: small;> ' .. concat(err, ", ") .. "</span>"
else
err = ""
end
return process_maybe_split_categories(split_output, categories, '<span class="IPA nowrap">' .. pron .. "</span>", lang,
err)
end
--[==[
Format a line of one or more enPR pronunciations as {{tl|enPR}} would do it, i.e. with a preceding {"enPR:"} (linked to
[[Appendix:English pronunciation]]) followed by one or more formatted, comma-separated enPR pronunciations. The
pronunciations are formatted by wrapping them in the `AHD` and `enPR` CSS classes and adding any decorations
(qualifiers, accent qualifiers and references). In addition, the overall result is wrapped in any overall decorations.
There is a single parameter `data`, an object with the following fields:
* `items` is a list of enPR pronunciations, each of which is an object with the following properties:
** `pron`: the enPR pronunciation;
** `q`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display before the formatted pronunciation;
** `qq`: {nil} or a list of right qualifiers to display after the formatted pronunciation;
** `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display before the formatted pronunciation;
** `aa`: {nil} or a list of right accent qualifiers to after before the formatted pronunciation.
* `q`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display at the beginning, before the formatted
pronunciations and preceding {"enPR:"}.
* `qq`: {nil} or a list of right qualifiers to display after all formatted pronunciations.
* `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display at the beginning, before the formatted
pronunciations and preceding {"enPR:"}.
* `aa`: {nil} or a list of right accent qualifiers to display after all formatted pronunciations.
]==]
function export.format_enPR_full(data)
local prefix = "[[Appendix:英語發音|enPR]]:"
local lang = require("Module:languages").getByCode("en")
local parts = {}
for _, item in ipairs(data.items) do
local part = '<span class="AHD enPR">' .. item.pron .. "</span>"
if item.q and item.q[1] or item.qq and item.qq[1] or item.a and item.a[1] or item.aa and item.aa[1] then
part = require(decorations_module).format_decorations {
lang = lang,
text = part,
q = item.q,
qq = item.qq,
a = item.a,
aa = item.aa,
}
end
insert(parts, part)
end
local prontext = prefix .. concat(parts, ", ")
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
prontext = require(decorations_module).format_decorations {
lang = lang,
text = prontext,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
return prontext
end
return export
8992hgtbnhxltiwvl5gwoej53taak5t
9909119
9909030
2026-09-29T07:41:37Z
TongcyDai
53191
同步 enwikt:舊介面改為報錯
9909119
Scribunto
text/plain
local export = {}
local force_cat = false -- for testing
local decorations_module = "Module:decorations"
local pages_module = "Module:pages"
local qualifier_module = "Module:qualifier"
local string_utilities_module = "Module:string utilities"
local syllables_module = "Module:syllables"
local utilities_module = "Module:utilities"
local m_data = mw.loadData("Module:IPA/data")
local m_str_utils = require(string_utilities_module)
local m_syllables -- [[Module:syllables]]; loaded below if needed
local m_symbols = mw.loadData("Module:IPA/data/symbols")
local concat = table.concat
local decode_entities = m_str_utils.decode_entities
local find = string.find
local gmatch = m_str_utils.gmatch
local gsub = string.gsub
local insert = table.insert
local is_preview = require(pages_module).is_preview
local len = m_str_utils.len
local listToText = mw.text.listToText
local match = string.match
local pattern_escape = m_str_utils.pattern_escape
local sub = string.sub
local u = m_str_utils.char
local ugsub = m_str_utils.gsub
local umatch = m_str_utils.match
local usub = m_str_utils.sub
local namespace = mw.title.getCurrentTitle().nsText
local function is_content_page(lang, namespace)
return namespace == "" or namespace == "Reconstruction" or
lang and lang:hasType("appendix-constructed") and namespace == "Appendix"
end
local function track(page)
require("Module:debug/track")("IPA/" .. page)
return true
end
local function process_maybe_split_categories(split_output, categories, prontext, lang, errtext)
if split_output ~= "raw" then
if categories[1] then
categories = require(utilities_module).format_categories(categories, lang, nil, nil, force_cat)
else
categories = ""
end
end
if split_output then -- for use of IPA in links, etc.
if errtext then
return prontext, categories, errtext
else
return prontext, categories
end
else
return prontext .. (errtext or "") .. categories
end
end
--[==[
Format a line of one or more IPA pronunciations as {{tl|IPA}} would do it, i.e. with a preceding {"IPA:"} followed by
the word {"key"} linking to an Appendix page describing the language's phonology, and with an added category
` ``lang`` terms with IPA pronunciation`. Other than the extra preceding text and category, this is identical
to {format_IPA_multiple()}, and the considerations described there in the documentation apply here as well. There is a
single parameter `data`, an object with the following fields:
* `lang`: Object representing the language of the pronunciations, which is used when adding cleanup categories for
pronunciations with invalid phonemes; for determining how many syllables the pronunciations have in them, in order to
add a category such as [[:Category:Italian 2-syllable words]] (for certain languages only); for adding a category
` ``lang`` terms with IPA pronunciation`; and for determining the proper sort keys for categories. Unlike
for {format_IPA_multiple()}, `lang` may not be {nil}.
* `items`: List of pronunciations, in exactly the same format as for {format_IPA_multiple()}.
* `err`: If not {nil}, a string containing an error message to use in place of the link to the language's phonology.
* `separator`: The default separator to use when separating formatted items. Defaults to {", "}. Does not apply to the
first item, where the default separator is always the empty string. Overridden by the per-item `separator` field in
`items`.
* `sort_key`: Explicit sort key used for categories.
* `no_count`: Suppress adding a {#-syllable words} category such as [[:Category:Italian 2-syllable words]]. Note that
only certain languages add such categories to begin with, because it depends on knowing how to count syllables in a
given language, which depends on the phonology of the language. Also, this does not suppress the addition of cleanup
or other categories. If you need them suppressed, use `split_output` to return the categories separately and ignore
them.
* `split_output`: If not given, the return value is a concatenation of the formatted pronunciation 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 a combination of category
strings and category objects of 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.
* `include_langname`: If specified, prefix the result with the language name, followed by a colon.
* `q`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display at the beginning, before the formatted
pronunciations and preceding {"IPA:"}.
* `qq`: {nil} or a list of right qualifiers to display after all formatted pronunciations.
* `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display at the beginning, before the formatted
pronunciations and preceding {"IPA:"}.
* `aa`: {nil} or a list of right accent qualifiers to display after all formatted pronunciations.
]==]
function export.format_IPA_full(data)
if type(data) ~= "table" or data.getCode then
error("Must now supply a table of arguments to format_IPA_full(); first argument should be that table, not a language object")
end
local lang = data.lang
local items = data.items
local err = data.err
local separator = data.separator
local sort_key = data.sort_key
local no_count = data.no_count
local split_output = data.split_output
local q = data.q
local qq = data.qq
local a = data.a
local aa = data.aa
local include_langname = data.include_langname
if data.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("overall `.qualifiers` is no longer supported; change the code to use `.q` or `.qq`")
end
local hasKey = m_data.langs_with_infopages
if not lang or not lang.getCode then
error("Must specify language to format_IPA_full()")
end
local langname = lang:getCanonicalName()
local prefix_text
if err then
prefix_text = '<span class="error">' .. err .. '</span>'
else
if hasKey[lang:getCode()] then
prefix_text = "Appendix:" .. langname .. "發音"
else
prefix_text = "wikipedia:" .. langname .. "音系"
end
prefix_text = "[[" .. prefix_text .. "|幫助]]"
end
local prefix = "[[Wiktionary:國際音標|國際音標]]<sup>(" .. prefix_text .. ")</sup>: "
local IPAs, categories = export.format_IPA_multiple(lang, items, separator, no_count, "raw")
if is_content_page(lang, namespace) then
insert(categories, {
cat = "有國際音標的" .. langname .. "詞",
sort_key = sort_key
})
end
local prontext = prefix .. IPAs
if q and q[1] or qq and qq[1] or a and a[1] or aa and aa[1] then
prontext = require(decorations_module).format_decorations {
lang = lang,
text = prontext,
q = q,
qq = qq,
a = a,
aa = aa,
}
end
if include_langname then
prontext = langname .. ":" .. prontext
end
return process_maybe_split_categories(split_output, categories, prontext, lang)
end
local function split_phonemic_phonetic(pron)
local reconstructed, phonemic, phonetic = match(pron, "^(%*?)(/.-/)%s+(%[.-%])$")
if reconstructed then
return reconstructed .. phonemic, reconstructed .. phonetic
else
return pron, nil
end
end
local function determine_repr(pron)
local reconstructed
-- Temporarily remove any initial asterisk before representation marks,
-- which avoids having to account for it in the data, but set the
-- `reconstructed` flag.
if sub(pron, 1, 1) == "*" then
reconstructed = true
pron = sub(pron, 2)
end
-- Some representation types have aliases for convenience (e.g. "// //" is
-- an alias for "⫽ ⫽"). and these need to be substituted in before checking
-- for other data.
local opening, n = match(pron, "^.[\128-\191]*")
local subs_data = m_data.representation_subs[opening]
if subs_data then
pron, n = ugsub(pron, subs_data[1], subs_data[2])
-- If the substitution was made, `opening` needs to be changed to the
-- new opening character.
if n ~= 0 then
opening = subs_data[3]
end
end
-- Get the type data based on the opening character (if any), and set the
-- representation type if the closing character matches.
local type_data, repr, closing = m_data.representation_types[opening]
if type_data then
closing = type_data[2]
if type_data and match(pron, pattern_escape(closing) .. "$", #opening + 1) then
repr = type_data[1]
end
end
-- Default to the empty string.
if not repr then
opening, closing = "", ""
end
-- Reattach the asterisk if reconstructed.
if reconstructed then
pron = "*" .. pron
end
return pron, repr, opening, closing, reconstructed
end
local function hasInvalidSeparators(transcription)
-- Escape certain characters as well as pauses, which have the format "(...)" (with any number of dots), to avoid false-positives.
transcription = transcription:gsub(".[\128-\191]*", m_symbols.separator_escapes)
:gsub("%(%.+%)", "\3")
:gsub("[()]+", "")
return (
transcription:find("..", nil, true) or
transcription:match("%.%f[%z \1\2\3,:;]") or
transcription:match("\1%f[%z \2\3,:;]") or
transcription:match("\2%f[%z \1\3,:;]") or
transcription:match("\3[:;]") or
transcription:match("%f[^%z \1\2\3,]%.")
) and true or false
end
--[==[
Format a line of one or more bare IPA pronunciations (i.e. without any preceding {"IPA:"} and without adding to a
category ` ``lang`` terms with IPA pronunciation`). Individual pronunciations are formatted using
{format_IPA()} and are combined with separators, decorations, pre-text, post-text, etc. to form a line of pronunciations.
Parameters accepted are:
* `lang` is an object representing the language of the pronunciations, which is used when adding cleanup categories for
pronunciations with invalid phonemes; for determining how many syllables the pronunciations have in them, in order to
add a category such as [[:Category:Italian 2-syllable words]] (for certain languages only); and for computing the
proper sort keys for categories. `lang` may be {nil}.
* `items` is a list of pronunciations, each of which is an object with the following properties:
** `pron`: the pronunciation, in the same format as is accepted by {format_IPA()}, i.e. it should be either phonemic
(surrounded by {/.../}), phonetic (surrounded by {[...]}), orthographic (surrounded by {⟨...⟩}) or a rhyme
(beginning with a hyphen);
** `pretext`: text to display directly before the formatted pronunciation, inside of any qualifiers or accent
qualifiers;
** `posttext`: text to display directly after the formatted pronunciation, inside of any qualifiers or accent
qualifiers;
** `q`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display before the formatted pronunciation;
** `qq`: {nil} or a list of right qualifiers to display after the formatted pronunciation;
** `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display before the formatted pronunciation;
** `aa`: {nil} or a list of right accent qualifiers to after before the formatted pronunciation;
** `refs`: {nil} or a list of references or reference specs to add after the pronunciation and any posttext and
qualifiers; the value of a list item 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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}}
or {{cd|<nowiki><ref name="foo" /></nowiki>}}) and/or `group` (the group of the reference, as in
{{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section;
** `gloss`: {nil} or a gloss (definition) for this item, if different definitions have different pronunciations;
** `pos`: {nil} or a part of speech for this item, if different parts of speech have different pronunciations;
** `separator`: the separator text to insert directly before the formatted pronunciation and all decorations
and pre-text; defaults to the outer `separator` parameter.
* `separator`: The default separator to use when separating formatted items. Defaults to {", "}. Does not apply to the
first item, where the default separator is always the empty string. Overridden by the per-item `separator` field in
`items`.
* `no_count`: Suppress adding a {#-syllable words} category such as [[:Category:Italian 2-syllable words]]. Note that
only certain languages add such categories to begin with, because it depends on knowing how to count syllables in a
given language, which depends on the phonology of the language. Also, this does not suppress the addition of cleanup
categories. If you need them suppressed, use `split_output` to return the categories separately and ignore them.
* `split_output`: If not given, the return value is a concatenation of the formatted pronunciation 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 a combination of category
strings and category objects of 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.
]==]
function export.format_IPA_multiple(lang, items, separator, no_count, split_output)
local categories = {}
separator = separator or ", "
if not lang then
track("format-multiple-nolang")
end
-- Format
if not items[1] then
if namespace == "Template" then
insert(items, {pron = "/aɪ piː ˈeɪ/"})
else
insert(categories, "使用發音模板而缺少發音的詞條")
end
end
local bits = {}
for i, item in ipairs(items) do
local bit
-- If the pronunciation is entirely empty, allow this and don't do anything, so that e.g. the pretext and/or
-- posttext can be specified to force something like ''unknown'' to appear in place of the pronunciation
-- (as happens e.g. when ? is used as a respelling in [[Module:ca-IPA]]; see [[guèiser]] for an example).
if item.pron == "" then
bit = ""
else
local item_categories, errtext
bit, item_categories, errtext = export.format_IPA(lang, item.pron, "raw")
bit = bit .. errtext
for _, cat in ipairs(item_categories) do
insert(categories, cat)
end
end
if item.pretext then
bit = item.pretext .. bit
end
if item.posttext then
bit = bit .. item.posttext
end
if item.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("`.qualifiers` is no longer supported; change the code to use `.q` or `.qq`")
end
local has_decorations = item.q and item.q[1] or item.qq and item.qq[1] or item.a and item.a[1] or
item.aa and item.aa[1] or item.refs and item.refs[1]
local has_gloss_or_pos = item.gloss or item.pos
if has_decorations or has_gloss_or_pos then
-- FIXME: Currently we tack the gloss and POS (in that order) onto the end of the regular left qualifiers.
-- Should we do something different?
local q = item.q
if has_gloss_or_pos then
q = mw.clone(item.q) or {}
if item.gloss then
local m_qualifier = require(qualifier_module)
insert(q, m_qualifier.wrap_qualifier_css("“", "quote") .. item.gloss ..
m_qualifier.wrap_qualifier_css("”", "quote"))
end
if item.pos then
-- FIXME: Consider expanding aliases as found in [[Module:headword/data]] or similar.
insert(q, item.pos)
end
end
bit = require(decorations_module).format_decorations {
lang = lang,
text = bit,
q = q,
qq = item.qq,
a = item.a,
aa = item.aa,
refs = item.refs,
}
end
bit = (item.separator or (i == 1 and "" or separator)) .. bit
insert(bits, bit)
--[=[ [[Special:WhatLinksHere/Wiktionary:Tracking/IPA/syntax-error]]
The length or gemination symbol should not appear after a syllable break or stress symbol. ]=]
-- The nature of the following pattern match is such that we don't have to split a combined '/.../ [...]' spec
-- into its parts in order to process.
if match(item.pron, "[.\203][\136\140]?\203[\144\145]") then -- [.ˈˌ][ːˑ]
track("syntax-error")
end
if lang then
-- Add syllable count if the language's diphthongs are listed in [[Module:syllables]].
-- Don't do this if the term has spaces, a liaison mark (‿) or isn't in mainspace.
if not no_count and namespace == "" then
m_syllables = m_syllables or require(syllables_module)
local langcode = lang:getCode()
if m_data.langs_to_generate_syllable_count_categories[langcode] then
local raw_phonemic, phonetic, use_it = split_phonemic_phonetic(item.pron)
local phonemic, repr = determine_repr(raw_phonemic)
if not phonetic then -- not a '/.../ [...]' combined pronunciation
if m_data.langs_to_use_phonetic_notation[langcode] then
use_it = repr == "phonetic" and phonemic or nil
else
use_it = repr == "phonemic" and phonemic or nil
end
elseif repr == "phonetic" then
use_it = phonetic
elseif repr == "phonemic" then
use_it = phonemic
end
-- Note: two uses of find with plain patterns is much faster than umatch with [ ‿].
if use_it and not (find(use_it, " ") or find(use_it, "‿")) then
local syllable_count = m_syllables.getVowels(use_it, lang)
if syllable_count then
insert(categories, lang:getCanonicalName() .. syllable_count ..
"音節詞")
end
end
end
end
end
end
return process_maybe_split_categories(split_output, categories, concat(bits), lang)
end
--[=[
Format a single IPA pronunciation, which cannot be a combined spec (such as {/.../ [...]}). This has been extracted from
{format_IPA()} to allow the latter to handle such combined specs. This works like {format_IPA()} but requires that
pre-created {err} (for error messages) and {categories} lists be passed in, and adds any generated error messages and
categories to those lists. A single value is returned, the pronunciation, which is usually the same as passed in, but
may have HTML added surrounding invalid characters so they appear in red.
]=]
local function format_one_IPA(lang, raw_pron, err, categories)
-- Disallow wikilinks.
if match(raw_pron, "%[%[.-%]%]") then
error("IPA input must not contain wikilinks.")
end
raw_pron = decode_entities(raw_pron)
-- Detect the type of transcription.
local pron, repr, opening, closing, reconstructed = determine_repr(raw_pron)
-- Strip any reconstruction asterisk and representation marks.
pron = sub(pron, #opening + 1 + (reconstructed and 1 or 0), -#closing - 1)
if not repr then
insert(categories, "使用無效表示符號的國際音標發音")
-- insert(err, "invalid representation marks")
-- Removed because it's annoying when previewing pronunciation pages.
end
if repr ~= "orthographic" and lang and lang:getCode() == "en" and hasInvalidSeparators(pron) then
insert(categories, "國際音標使用.ˈ或.ˌ的英語詞")
end
if pron == "" then
insert(categories, "沒有發音的國際音標發音")
end
-- Check for obsolete and nonstandard symbols
for _, symbol in ipairs(m_data.nonstandard) do
local result
for nonstandard in gmatch(pron, symbol) do
if not result then
result = {}
end
insert(result, nonstandard)
insert(categories,
{cat = "使用廢棄或非標準字符的國際音標發音", sort_key = nonstandard}
)
end
if result then
insert(err, "obsolete or nonstandard characters (" .. concat(result) .. ")")
break
end
end
--[[ Check for invalid symbols after removing the following:
1. wikilinks (handled above)
2. paired HTML tags
3. bolding
4. italics
5. asterisk at beginning of transcription
6. comma followed by spacing characters
7. superscripts enclosed in superscript parentheses ]]
local found_HTML
local result = gsub(pron, "<(%a+)[^>]*>([^<]+)</%1>",
function(tagName, content)
found_HTML = true
return content
end)
result = gsub(result, "'''([^']*)'''", "%1")
result = gsub(result, "''([^']*)''", "%1")
result = gsub(result, "^%*", "")
result = ugsub(result, ",%s+", "")
-- VS15
local vs15_class = "[" .. m_symbols.add_vs15 .. "]"
if umatch(pron, vs15_class) then
local vs15 = u(0xFE0E)
if find(result, vs15) then
result = gsub(result, vs15, "")
pron = gsub(pron, vs15, "")
end
pron = ugsub(pron, vs15_class, "%0" .. vs15)
end
if result ~= "" then
if lang then
-- Get the per_lang_valid data, and convert any per-language valid sequences to spaces.
local per_lang_valid = m_symbols.per_lang_valid[lang:getCode()]
if per_lang_valid then
if type(per_lang_valid) == "table" then
for _, pattern in pairs(per_lang_valid) do
result = ugsub(result, pattern, " ")
end
else -- Should be a string.
result = ugsub(result, per_lang_valid, " ")
end
end
end
local suggestions
-- Check for any invalid sequences, excluding anything in the per-language lookup table.
for k, v in pairs(m_symbols.invalid) do
if find(result, k, nil, true) then
if not suggestions then
suggestions = {}
end
insert(suggestions, k .. " with " .. v)
end
end
if suggestions and suggestions[1] then
suggestions = listToText(suggestions)
if is_content_page(lang, namespace) then
error("Invalid IPA: replace " .. suggestions)
end
insert(err, "replace " .. suggestions)
end
-- Convert any valid character sequences to spaces
for _, pattern in pairs(m_symbols.valid) do
result = ugsub(result, pattern, " ")
end
if not match(result, "^ *$") then
local category = "使用無效表示符號的國際音標發音"
if not is_content_page(lang, namespace) then
category = category .. "/non_mainspace"
end
insert(categories, category)
insert(err, "invalid IPA characters (" .. result .. ")")
end
end
if found_HTML then
insert(categories, "使用成對HTML標籤的國際音標發音")
end
if (repr == "phonemic" or repr == "rhyme") and lang and m_data.phonemes[lang:getCode()] then
local valid_phonemes = m_data.phonemes[lang:getCode()]
local rest = pron
local phonemes = {}
while #rest > 0 do
local longestmatch, longestmatch_len = "", 0
local rest_init = sub(rest, 1, 1)
if rest_init == "(" or rest_init == ")" then
longestmatch = rest_init
longestmatch_len = 1
else
for _, phoneme in ipairs(valid_phonemes) do
local phoneme_len = len(phoneme)
if phoneme_len > longestmatch_len and usub(rest, 1, phoneme_len) == phoneme then
longestmatch = phoneme
longestmatch_len = len(longestmatch)
end
end
end
if longestmatch_len > 0 then
insert(phonemes, longestmatch)
rest = usub(rest, longestmatch_len + 1)
else
local phoneme = usub(rest, 1, 1)
insert(phonemes, "<span style=\"color: var(--wikt-palette-red,red)\">" .. phoneme .. "</span>")
rest = usub(rest, 2)
insert(categories, "含有無效音位的國際音標發音/" .. lang:getCode())
track("invalid phonemes/" .. phoneme)
end
end
pron = concat(phonemes)
end
return (reconstructed and "*" or "") .. opening .. pron .. closing
end
--[==[
Format an IPA pronunciation. This wraps the pronunciation in appropriate CSS classes and adds cleanup categories and
error messages as needed. The pronunciation `pron` should be either phonemic (surrounded by {/.../}), phonetic
(surrounded by {[...]}), orthographic (surrounded by {⟨...⟩}), a rhyme (beginning with a hyphen) or a combined
phonemic/phonetic spec (of the form {/.../ [...]}). `lang` indicates the language of the pronunciation and can be {nil}.
If not {nil}, and the specified language has data in [[Module:IPA/data]] indicating the allowed phonemes, then the page
will be added to a cleanup category and an error message displayed next to the outputted pronunciation. Note that {lang}
also determines sort key processing in the added cleanup categories. If `split_output` is not given, the return value is
a concatenation of the formatted pronunciation, error messages and formatted cleanup categories. Otherwise, three values
are returned: the formatted pronunciation, the cleanup categories and the concatenated error messages. If `split_output`
is the value {"raw"}, the cleanup categories are returned in list form, where the list elements are a combination of
category strings and category objects of the form suitable for passing to {format_categories()} in [[Module:utilities]].
If `split_output` is any other value besides {nil}, the cleanup categories are returned as a pre-formatted concatenated
string.
]==]
function export.format_IPA(lang, pron, split_output)
local err = {}
local categories = {}
-- `pron` shouldn't contain ref tags.
if match(pron, "\127'\"`UNIQ%-%-ref%-[%dA-F]+%-QINU`\"'\127") then
error("<ref> tags found inside pronunciation parameter.")
end
if not lang then
track("format-nolang")
end
local phonemic, phonetic = split_phonemic_phonetic(pron)
pron = format_one_IPA(lang, phonemic, err, categories)
if phonetic then
track("phonemic-phonetic") -- There's no benefit to supporting the "/.../ [...]" format within one parameter.
phonetic = format_one_IPA(lang, phonetic, err, categories)
pron = pron .. " " .. phonetic
end
if err[1] and is_preview() then
err = '<span class="error" style="font-size: small;> ' .. concat(err, ", ") .. "</span>"
else
err = ""
end
return process_maybe_split_categories(split_output, categories, '<span class="IPA nowrap">' .. pron .. "</span>", lang,
err)
end
--[==[
Format a line of one or more enPR pronunciations as {{tl|enPR}} would do it, i.e. with a preceding {"enPR:"} (linked to
[[Appendix:English pronunciation]]) followed by one or more formatted, comma-separated enPR pronunciations. The
pronunciations are formatted by wrapping them in the `AHD` and `enPR` CSS classes and adding any decorations
(qualifiers, accent qualifiers and references). In addition, the overall result is wrapped in any overall decorations.
There is a single parameter `data`, an object with the following fields:
* `items` is a list of enPR pronunciations, each of which is an object with the following properties:
** `pron`: the enPR pronunciation;
** `q`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display before the formatted pronunciation;
** `qq`: {nil} or a list of right qualifiers to display after the formatted pronunciation;
** `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display before the formatted pronunciation;
** `aa`: {nil} or a list of right accent qualifiers to after before the formatted pronunciation.
* `q`: {nil} or a list of left qualifiers (as in {{tl|q}}) to display at the beginning, before the formatted
pronunciations and preceding {"enPR:"}.
* `qq`: {nil} or a list of right qualifiers to display after all formatted pronunciations.
* `a`: {nil} or a list of left accent qualifiers (as in {{tl|a}}) to display at the beginning, before the formatted
pronunciations and preceding {"enPR:"}.
* `aa`: {nil} or a list of right accent qualifiers to display after all formatted pronunciations.
]==]
function export.format_enPR_full(data)
local prefix = "[[Appendix:英語發音|enPR]]:"
local lang = require("Module:languages").getByCode("en")
local parts = {}
for _, item in ipairs(data.items) do
local part = '<span class="AHD enPR">' .. item.pron .. "</span>"
if item.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("`.qualifiers` is no longer supported; change the code to use `.q` or `.qq`")
end
if item.q and item.q[1] or item.qq and item.qq[1] or item.a and item.a[1] or item.aa and item.aa[1] then
part = require(decorations_module).format_decorations {
lang = lang,
text = part,
q = item.q,
qq = item.qq,
a = item.a,
aa = item.aa,
}
end
insert(parts, part)
end
local prontext = prefix .. concat(parts, ", ")
if data.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("overall `.qualifiers` is no longer supported; change the code to use `.q` or `.qq`")
end
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
prontext = require(decorations_module).format_decorations {
lang = lang,
text = prontext,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
return prontext
end
return export
qdl721l9mhlusyuk5f8eboqum5duenj
Module:Gender and number
828
1373247
9909029
9682798
2026-09-29T07:22:04Z
TongcyDai
53191
同步 enwikt:pron qualifier → decorations
9909029
Scribunto
text/plain
local export = {}
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local load_module = "Module:load"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local concat = table.concat
local insert = table.insert
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function deep_copy(...)
deep_copy = require(table_module).deepCopy
return deep_copy(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function format_decorations(...)
format_decorations = require(decorations_module).format_decorations
return format_decorations(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local gender_and_number_data
local function get_gender_and_number_data()
gender_and_number_data, get_gender_and_number_data = load_data("Module:gender and number/data"), nil
return gender_and_number_data
end
--[==[ intro:
This module creates standardised displays for gender and number. It converts a gender specification into Wiki/HTML format.
A gender/number specification consists of one or more gender/number elements, separated by hyphens. Examples are:
{"n"} (neuter gender), {"f-p"} (feminine plural), {"m-an-p"} (masculine animate plural),
{"pf"} (perfective aspect). Each gender/number element has the following properties:
# A code, as used in the spec, e.g. {"f"} for feminine, {"p"} for plural.
# A type, e.g. `gender`, `number` or `animacy`. Each element in a given spec must be of a different type.
# A display form, which in turn consists of a display code and a tooltip gloss. The display code
may not be the same as the spec code, e.g. the spec code {"an"} has display code {"anim"} and tooltip
gloss ''animate''.
# A category into which lemmas of the right part of speech are placed if they have a gender/number
spec containing the given element. For example, a noun with gender/number spec {"m-an-p"} is placed
into the categories `<var>lang</var> masculine nouns`, `<var>lang</var> animate nouns` and `<var>lang</var> pluralia tantum`.
]==]
--[==[
Version of format_list that can be invoked from a template.
]==]
function export.show_list(frame)
local params = {
[1] = {list = true},
["lang"] = {type = "language"},
}
local iargs = process_params(frame.args, params)
return export.format_list(iargs[1], iargs.lang)
end
--[==[
Older entry point; equivalent to format_genders() except that it formats the
categories and returns them appended to the formatted gender text rather than
returning the formatted text and categories separately.
]==]
function export.format_list(specs, lang, pos_for_cat, sort_key)
debug_track("gender and number/old-format-list")
local text, cats = export.format_genders(specs, lang, pos_for_cat)
if not cats then
return text
end
return text .. format_categories(cats, lang, sort_key)
end
local function autoadd_abbr(display)
if not display then
error("Internal error: '.display' for gender/number code is missing")
end
if display:find("<abbr", nil, true) then
return display
end
return ('%s'):format(display, display)
end
-- Add decorations (qualifiers, labels and references) to a formatted gender/number spec. `spec` is the object
-- describing the gender/number spec, which should optionally contain:
-- * left qualifiers in `q` or (for compatibility) `qualifiers`, an array of strings;
-- * right qualifiers in `qq`, an array of strings;
-- * left labels in `l`, an array of strings;
-- * right labels in `ll`, an array of strings;
-- * references in `refs`, an array either of strings (formatted reference text) or objects containing fields `text`
-- (formatted reference text) and optionally `name` and/or `group`;
-- `formatted` is the formatted version of the term itself, and `lang` is the optional language object passed into
-- format_genders().
local function add_decorations(formatted, spec, lang)
local function field_non_empty(field)
local list = spec[field]
if not list then
return nil
end
if type(list) ~= "table" then
error(("Internal error: Wrong type for `spec.%s`=%s, should be \"table\""):format(
field, mw.dumpObject(list)))
end
return list[1]
end
if field_non_empty("q") or field_non_empty("qq") or field_non_empty("l") or field_non_empty("ll") or
field_non_empty("qualifiers") or field_non_empty("refs") then
formatted = format_decorations {
lang = lang,
text = formatted,
q = spec.q,
qq = spec.qq,
qualifiers = spec.qualifiers,
l = spec.l,
ll = spec.ll,
refs = spec.refs,
}
end
return formatted
end
--[==[
Format one or more gender/number specifications. Each spec is either a string, e.g. {"f-p"}, or a table of the form
{ {spec = "SPEC", qualifiers = {"QUALIFIER", "QUALIFIER", ...}}} where `.spec` is a gender/number spec such as {"f-p"}
and `.qualifiers` is a list of qualifiers to display before the formatted gender/number spec. `.spec` must be present
but `.qualifiers` may be omitted.
The function returns two values:
# the formatted text;
# a list of the categories to add.
If `lang` (which should be a language object) and `pos_for_cat` (which should be a plural part of speech) are given,
gender categories such as `German masculine nouns` or `Russian imperfective verbs` are added to the categories, and
request categories such as `Requests for gender in <var>lang</var> entries` or
`Requests for animacy in <var>lang</var> entries` may also be added. Otherwise, if only `lang` is given, only request
categories may be returned. If both are omitted, the returned list is empty.
]==]
function export.format_genders(specs, lang, pos_for_cat)
local formatted_specs, categories, seen_types = {}
local all_is_nounclass = nil
local full_langname = lang and lang:getFullName() or nil
local function do_gender_spec(spec, parts)
local types = {}
local codes = (gender_and_number_data or get_gender_and_number_data()).codes
for key, code in ipairs(parts) do
-- Is this code valid?
if not codes[code] then
error('The tag "' .. code .. '" in the gender specification "' .. spec.spec .. '" is not valid. See [[Module:gender and number]] for a list of valid tags.')
end
-- Check for multiple genders/numbers/animacies in a single spec.
local typ = codes[code].type
if typ ~= "other" and types[typ] then
error('The gender specification "' .. spec.spec .. '" contains multiple tags of type "' .. typ .. '".')
end
types[typ] = true
parts[key] = autoadd_abbr(codes[code].display)
-- Generate categories if called for.
if lang and pos_for_cat then
local cat = codes[code].cat
if cat then
if not categories then
categories = {}
end
insert(categories, mw.ustring.find(cat, "的") and mw.ustring.gsub(cat, "的", "的" .. full_langname) or full_langname .. cat)
end
if not seen_types then
seen_types = {}
elseif seen_types[typ] and seen_types[typ] ~= code then
cat = (gender_and_number_data or get_gender_and_number_data()).multicode_cats[typ]
if cat then
if not categories then
categories = {}
end
insert(categories, mw.ustring.find(cat, "的") and mw.ustring.gsub(cat, "的", "的" .. full_langname) or full_langname .. cat)
end
end
seen_types[typ] = code
end
if lang and codes[code].req then
local type_for_req = typ
if code == "?" then
-- Keep in mind `pos_for_cat` may be nil here.
type_for_req = pos_for_cat == "動詞" and "體" or "性別"
end
if not categories then
categories = {}
end
insert(categories, full_langname .. "詞條" .. type_for_req .. "請求")
end
end
-- Add the processed codes together with non-breaking spaces
if not parts[2] and parts[1] then
return parts[1]
else
return concat(parts, " ")
end
end
for _, spec in ipairs(specs) do
if type(spec) ~= "table" then
spec = {spec = spec}
end
local spec_spec, is_nounclass = spec.spec
-- If the specification starts with cX, then it is a noun class specification.
if spec_spec:match("^%d") or spec_spec:match("^c[^-]") then
is_nounclass = true
local code = spec_spec:gsub("^c", "")
local text
if code == "?" then
text = '<abbr class="noun-class" title="名詞類別缺失">?</abbr>'
if lang then
if not categories then
categories = {}
end
insert(categories, "Requests for noun class in " .. full_langname .. " entries")
end
else
text = '<abbr class="noun-class" title="名詞類別' .. code .. '">' .. code .. "</abbr>"
if lang and pos_for_cat then
if not categories then
categories = {}
end
insert(categories, full_langname .. "第" .. code .. "類POS")
end
end
local text_with_decorations = add_decorations(text, spec, lang)
insert(formatted_specs, text_with_decorations)
else
-- Split the parts and iterate over each part, converting it into its display form
local parts = split(spec.spec, "-", true, true)
local combined_codes = (gender_and_number_data or get_gender_and_number_data()).combinations
if lang then
-- Check if the specification is valid
--elseif langinfo.genders then
-- local valid_genders = {}
-- for _, g in ipairs(langinfo.genders) do valid_genders[g] = true end
--
-- if not valid_genders[spec.spec] then
-- local valid_string = {}
-- for i, g in ipairs(langinfo.genders) do valid_string[i] = g end
-- error('The gender specification "' .. spec.spec .. '" is not valid for ' .. langinfo.names[1] .. ". Valid are: " .. concat(valid_string, ", "))
-- end
--end
end
local has_combined = false
for _, code in ipairs(parts) do
if combined_codes[code] then
has_combined = true
break
end
end
if not has_combined then
if formatted_specs[1] then
insert(formatted_specs, " <small>或</small> ")
end
insert(formatted_specs, add_decorations(do_gender_spec(spec, parts), spec, lang))
else
-- This logic is to handle combined gender specs like 'mf' and 'mfbysense'.
local all_parts = {{}}
local extra_displays
local this_formatted_specs = {}
for _, code in ipairs(parts) do
if combined_codes[code] then
local new_all_parts = {}
for _, one_parts in ipairs(all_parts) do
for _, one_code in ipairs(combined_codes[code].codes) do
local new_combined_parts = deep_copy(one_parts)
insert(new_combined_parts, one_code)
insert(new_all_parts, new_combined_parts)
end
end
all_parts = new_all_parts
if lang and pos_for_cat then
local extra_cat = combined_codes[code].cat
if extra_cat then
if not categories then
categories = {}
end
rindex_de = mw.ustring.find(extra_cat, '的[^的]*$') -- L10N
if rindex_de then
extra_cat = mw.ustring.sub(extra_cat, 1, rindex_de) .. lang:getCanonicalName() .. mw.ustring.sub(extra_cat, rindex_de + 1)
else
extra_cat = lang:getCanonicalName() .. extra_cat
end
table.insert(categories, extra_cat)
end
end
local extra_display = combined_codes[code].display
if extra_display then
if not extra_displays then
extra_displays = {}
end
insert(extra_displays, autoadd_abbr(extra_display))
end
else
for _, one_parts in ipairs(all_parts) do
insert(one_parts, code)
end
end
end
for _, parts in ipairs(all_parts) do
if this_formatted_specs[1] then
insert(this_formatted_specs, " <small>或</small> ")
end
insert(this_formatted_specs, do_gender_spec(spec, parts))
end
if extra_displays then
for _, display in ipairs(extra_displays) do
insert(this_formatted_specs, display)
end
end
insert(formatted_specs, add_decorations(
concat(this_formatted_specs, " "), spec, lang))
end
is_nounclass = false
end
-- Ensure that the specifications are either all noun classes, or none are.
if all_is_nounclass == nil then
all_is_nounclass = is_nounclass
elseif all_is_nounclass ~= is_nounclass then
error("Noun classes and genders cannot be mixed. Please use either one or the other.")
end
end
if categories and lang and pos_for_cat then
for i, cat in ipairs(categories) do
categories[i] = cat:gsub("POS", pos_for_cat)
end
end
if all_is_nounclass then
-- Add the processed codes together with slashes
return '<span class="gender">類別' .. concat(formatted_specs, "/") .. "</span>", categories
else
-- Add the processed codes together with spaces
return '<span class="gender">' .. concat(formatted_specs, " ") .. "</span>", categories
end
end
return export
m1jxi0zgkate8tlouidppp056t7cmhk
9909123
9909029
2026-09-29T07:41:54Z
TongcyDai
53191
同步 enwikt:舊介面改為報錯
9909123
Scribunto
text/plain
local export = {}
local decorations_module = "Module:decorations"
local load_module = "Module:load"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local concat = table.concat
local insert = table.insert
local function deep_copy(...)
deep_copy = require(table_module).deepCopy
return deep_copy(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function format_decorations(...)
format_decorations = require(decorations_module).format_decorations
return format_decorations(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local gender_and_number_data
local function get_gender_and_number_data()
gender_and_number_data, get_gender_and_number_data = load_data("Module:gender and number/data"), nil
return gender_and_number_data
end
--[==[ intro:
This module creates standardised displays for gender and number. It converts a gender specification into Wiki/HTML format.
A gender/number specification consists of one or more gender/number elements, separated by hyphens. Examples are:
{"n"} (neuter gender), {"f-p"} (feminine plural), {"m-an-p"} (masculine animate plural),
{"pf"} (perfective aspect). Each gender/number element has the following properties:
# A code, as used in the spec, e.g. {"f"} for feminine, {"p"} for plural.
# A type, e.g. `gender`, `number` or `animacy`. Each element in a given spec must be of a different type.
# A display form, which in turn consists of a display code and a tooltip gloss. The display code
may not be the same as the spec code, e.g. the spec code {"an"} has display code {"anim"} and tooltip
gloss ''animate''.
# A category into which lemmas of the right part of speech are placed if they have a gender/number
spec containing the given element. For example, a noun with gender/number spec {"m-an-p"} is placed
into the categories `<var>lang</var> masculine nouns`, `<var>lang</var> animate nouns` and `<var>lang</var> pluralia tantum`.
]==]
--[==[
Version of format_genders() that can be invoked from a template.
]==]
function export.show_list(frame)
local params = {
[1] = {list = true},
["lang"] = {type = "language"},
}
local iargs = process_params(frame.args, params)
local text, cats = export.format_genders(iargs[1], iargs.lang)
if not cats then
return text
end
return text .. format_categories(cats, iargs.lang)
end
function export.format_list(...)
-- FIXME: Added 2026-09-17. Remove after 2026-10-17.
error("Use format_genders instead of format_list")
end
local function autoadd_abbr(display)
if not display then
error("Internal error: '.display' for gender/number code is missing")
end
if display:find("<abbr", nil, true) then
return display
end
return ('%s'):format(display, display)
end
-- Add decorations (qualifiers, labels and references) to a formatted gender/number spec. `spec` is the object
-- describing the gender/number spec, which should optionally contain:
-- * left qualifiers in `q`, an array of strings;
-- * right qualifiers in `qq`, an array of strings;
-- * left labels in `l`, an array of strings;
-- * right labels in `ll`, an array of strings;
-- * references in `refs`, an array either of strings (formatted reference text) or objects containing fields `text`
-- (formatted reference text) and optionally `name` and/or `group`;
-- `formatted` is the formatted version of the term itself, and `lang` is the optional language object passed into
-- format_genders().
local function add_decorations(formatted, spec, lang)
local function field_non_empty(field)
local list = spec[field]
if not list then
return nil
end
if type(list) ~= "table" then
error(("Internal error: Wrong type for `spec.%s`=%s, should be \"table\""):format(
field, mw.dumpObject(list)))
end
return list[1]
end
if field_non_empty("q") or field_non_empty("qq") or field_non_empty("l") or field_non_empty("ll") or
field_non_empty("refs") then
formatted = format_decorations {
lang = lang,
text = formatted,
q = spec.q,
qq = spec.qq,
l = spec.l,
ll = spec.ll,
refs = spec.refs,
}
end
return formatted
end
--[==[
Format one or more gender/number specifications. Each spec is either a string, e.g. {"f-p"}, or a table of the form
{ {spec = "SPEC",
q = {"LEFT_QUALIFIER", "LEFT_QUALIFIER", ...},
qq = {"RIGHT_QUALIFIER", "RIGHT_QUALIFIER", ...},
l = {"LEFT_LABEL", "LEFT_LABEL", ...},
ll = {"RIGHT_LABEL", "RIGHT_LABEL", ...},
refs = {"FORMATTED_REFERENCE_TEXT", {text = "FORMATTED_REFERENCE_TEXT", name = "REFNAME", group = "REFGROUP"}, ...},
}} In the table format, only `spec` is required, and in the `refs` object format, only `text` is required.
The function returns two values:
# the formatted text;
# a list of the categories to add.
If `lang` (which should be a language object) and `pos_for_cat` (which should be a plural part of speech) are given,
gender categories such as `German masculine nouns` or `Russian imperfective verbs` are added to the categories, and
request categories such as `Requests for gender in <var>lang</var> entries` or
`Requests for animacy in <var>lang</var> entries` may also be added. Otherwise, if only `lang` is given, only request
categories may be returned. If both are omitted, the returned list is empty.
]==]
function export.format_genders(specs, lang, pos_for_cat)
local formatted_specs, categories, seen_types = {}
local all_is_nounclass = nil
local full_langname = lang and lang:getFullName() or nil
local function do_gender_spec(spec, parts)
local types = {}
local codes = (gender_and_number_data or get_gender_and_number_data()).codes
for key, code in ipairs(parts) do
-- Is this code valid?
if not codes[code] then
error('The tag "' .. code .. '" in the gender specification "' .. spec.spec .. '" is not valid. See [[Module:gender and number]] for a list of valid tags.')
end
-- Check for multiple genders/numbers/animacies in a single spec.
local typ = codes[code].type
if typ ~= "other" and types[typ] then
error('The gender specification "' .. spec.spec .. '" contains multiple tags of type "' .. typ .. '".')
end
types[typ] = true
parts[key] = autoadd_abbr(codes[code].display)
-- Generate categories if called for.
if lang and pos_for_cat then
local cat = codes[code].cat
if cat then
if not categories then
categories = {}
end
insert(categories, mw.ustring.find(cat, "的") and mw.ustring.gsub(cat, "的", "的" .. full_langname) or full_langname .. cat)
end
if not seen_types then
seen_types = {}
elseif seen_types[typ] and seen_types[typ] ~= code then
cat = (gender_and_number_data or get_gender_and_number_data()).multicode_cats[typ]
if cat then
if not categories then
categories = {}
end
insert(categories, mw.ustring.find(cat, "的") and mw.ustring.gsub(cat, "的", "的" .. full_langname) or full_langname .. cat)
end
end
seen_types[typ] = code
end
if lang and codes[code].req then
local type_for_req = typ
if code == "?" then
-- Keep in mind `pos_for_cat` may be nil here.
type_for_req = pos_for_cat == "動詞" and "體" or "性別"
end
if not categories then
categories = {}
end
insert(categories, full_langname .. "詞條" .. type_for_req .. "請求")
end
end
-- Add the processed codes together with non-breaking spaces
if not parts[2] and parts[1] then
return parts[1]
else
return concat(parts, " ")
end
end
for _, spec in ipairs(specs) do
if type(spec) ~= "table" then
spec = {spec = spec}
end
local spec_spec, is_nounclass = spec.spec
-- If the specification starts with cX, then it is a noun class specification.
if spec_spec:match("^%d") or spec_spec:match("^c[^-]") then
is_nounclass = true
local code = spec_spec:gsub("^c", "")
local text
if code == "?" then
text = '<abbr class="noun-class" title="名詞類別缺失">?</abbr>'
if lang then
if not categories then
categories = {}
end
insert(categories, "Requests for noun class in " .. full_langname .. " entries")
end
else
text = '<abbr class="noun-class" title="名詞類別' .. code .. '">' .. code .. "</abbr>"
if lang and pos_for_cat then
if not categories then
categories = {}
end
insert(categories, full_langname .. "第" .. code .. "類POS")
end
end
local text_with_decorations = add_decorations(text, spec, lang)
insert(formatted_specs, text_with_decorations)
else
-- Split the parts and iterate over each part, converting it into its display form
local parts = split(spec.spec, "-", true, true)
local combined_codes = (gender_and_number_data or get_gender_and_number_data()).combinations
if lang then
-- Check if the specification is valid
--elseif langinfo.genders then
-- local valid_genders = {}
-- for _, g in ipairs(langinfo.genders) do valid_genders[g] = true end
--
-- if not valid_genders[spec.spec] then
-- local valid_string = {}
-- for i, g in ipairs(langinfo.genders) do valid_string[i] = g end
-- error('The gender specification "' .. spec.spec .. '" is not valid for ' .. langinfo.names[1] .. ". Valid are: " .. concat(valid_string, ", "))
-- end
--end
end
local has_combined = false
for _, code in ipairs(parts) do
if combined_codes[code] then
has_combined = true
break
end
end
if not has_combined then
if formatted_specs[1] then
insert(formatted_specs, " <small>或</small> ")
end
insert(formatted_specs, add_decorations(do_gender_spec(spec, parts), spec, lang))
else
-- This logic is to handle combined gender specs like 'mf' and 'mfbysense'.
local all_parts = {{}}
local extra_displays
local this_formatted_specs = {}
for _, code in ipairs(parts) do
if combined_codes[code] then
local new_all_parts = {}
for _, one_parts in ipairs(all_parts) do
for _, one_code in ipairs(combined_codes[code].codes) do
local new_combined_parts = deep_copy(one_parts)
insert(new_combined_parts, one_code)
insert(new_all_parts, new_combined_parts)
end
end
all_parts = new_all_parts
if lang and pos_for_cat then
local extra_cat = combined_codes[code].cat
if extra_cat then
if not categories then
categories = {}
end
rindex_de = mw.ustring.find(extra_cat, '的[^的]*$') -- L10N
if rindex_de then
extra_cat = mw.ustring.sub(extra_cat, 1, rindex_de) .. lang:getCanonicalName() .. mw.ustring.sub(extra_cat, rindex_de + 1)
else
extra_cat = lang:getCanonicalName() .. extra_cat
end
table.insert(categories, extra_cat)
end
end
local extra_display = combined_codes[code].display
if extra_display then
if not extra_displays then
extra_displays = {}
end
insert(extra_displays, autoadd_abbr(extra_display))
end
else
for _, one_parts in ipairs(all_parts) do
insert(one_parts, code)
end
end
end
for _, parts in ipairs(all_parts) do
if this_formatted_specs[1] then
insert(this_formatted_specs, " <small>或</small> ")
end
insert(this_formatted_specs, do_gender_spec(spec, parts))
end
if extra_displays then
for _, display in ipairs(extra_displays) do
insert(this_formatted_specs, display)
end
end
insert(formatted_specs, add_decorations(
concat(this_formatted_specs, " "), spec, lang))
end
is_nounclass = false
end
-- Ensure that the specifications are either all noun classes, or none are.
if all_is_nounclass == nil then
all_is_nounclass = is_nounclass
elseif all_is_nounclass ~= is_nounclass then
error("Noun classes and genders cannot be mixed. Please use either one or the other.")
end
end
if categories and lang and pos_for_cat then
for i, cat in ipairs(categories) do
categories[i] = cat:gsub("POS", pos_for_cat)
end
end
if all_is_nounclass then
-- Add the processed codes together with slashes
return '<span class="gender">類別' .. concat(formatted_specs, "/") .. "</span>", categories
else
-- Add the processed codes together with spaces
return '<span class="gender">' .. concat(formatted_specs, " ") .. "</span>", categories
end
end
return export
gryyzk8sge8c1q48shgzg0e8128iqdh
Module:Translations
828
1374396
9909107
9612709
2026-09-29T07:28:25Z
TongcyDai
53191
同步 enwikt:pron qualifier → decorations
9909107
Scribunto
text/plain
local export = {}
local anchors_module = "Module:anchors"
local debug_track_module = "Module:debug/track"
local languages_module = "Module:languages"
local links_module = "Module:links"
local pages_module = "Module:pages"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local templatestyles_module = "Module:TemplateStyles"
local utilities_module = "Module:utilities"
local wikimedia_languages_module = "Module:wikimedia languages"
local concat = table.concat
local html_create = mw.html.create
local insert = table.insert
local load_data = mw.loadData
local new_title = mw.title.new
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 decode_uri(...)
decode_uri = require(string_utilities_module).decode_uri
return decode_uri(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function full_link(...)
full_link = require(links_module).full_link
return full_link(...)
end
local function get_link_page(...)
get_link_page = require(links_module).get_link_page
return get_link_page(...)
end
local function get_wikimedia_lang(...)
get_wikimedia_lang = require(wikimedia_languages_module).getByCode
return get_wikimedia_lang(...)
end
local function language_link(...)
language_link = require(links_module).language_link
return language_link(...)
end
local function normalize_anchor(...)
normalize_anchor = require(anchors_module).normalize_anchor
return normalize_anchor(...)
end
local function plain_link(...)
plain_link = require(links_module).plain_link
return plain_link(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function remove_links(...)
remove_links = require(links_module).remove_links
return remove_links(...)
end
local function split_on_slashes(...)
split_on_slashes = require(links_module).split_on_slashes
return split_on_slashes(...)
end
local function templatestyles(...)
templatestyles = require(templatestyles_module)
return templatestyles(...)
end
local function track(...)
track = require(debug_track_module)
return track(...)
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 en
local function get_en()
en, get_en = require(languages_module).getByCode("en"), nil
return en
end
local headword_data
local function get_headword_data()
headword_data, get_headword_data = load_data("Module:headword/data"), nil
return headword_data
end
local parameters_data
local function get_parameters_data()
parameters_data, get_parameters_data = load_data("Module:parameters/data"), nil
return parameters_data
end
local translations_data
local function get_translations_data()
translations_data, get_translations_data = load_data("Module:translations/data"), nil
return translations_data
end
local function is_translation_subpage(pagename)
if (headword_data or get_headword_data()).page.namespace ~= "" then
return false
elseif not pagename then
pagename = (headword_data or get_headword_data()).encoded_pagename
end
return pagename:match("./translations$") and true or false
end
local function canonical_pagename()
local pagename = (headword_data or get_headword_data()).encoded_pagename
return is_translation_subpage(pagename) and pagename:sub(1, -14) or pagename
end
local function interwiki(terminfo, term, lang, langcode)
-- No interwiki link if term is empty/missing
if not term or #term < 1 then
terminfo.interwiki = false
return
end
-- Percent-decode the term.
term = decode_uri(terminfo.term, "PATH")
-- Don't show an interwiki link if it's an invalid title.
if not new_title(term) then
terminfo.interwiki = false
return
end
local interwiki_langcode = (translations_data or get_translations_data()).interwiki_langs[langcode]
local wmlangs = interwiki_langcode and {get_wikimedia_lang(interwiki_langcode)} or lang:getWikimediaLanguages()
-- Don't show the interwiki link if the language is not recognised by Wikimedia.
if #wmlangs == 0 then
terminfo.interwiki = false
return
end
local sc = terminfo.sc
local target_page = get_link_page(term, lang, sc)
local split = split_on_slashes(target_page)
if not split[1] then
terminfo.interwiki = false
return
end
target_page = split[1]
local wmlangcode = wmlangs[1]:getCode()
local interwiki_link = language_link{
lang = lang,
sc = sc,
term = wmlangcode .. ":" .. target_page,
alt = "(" .. wmlangcode .. ")",
tr = "-"
}
terminfo.interwiki = tostring(html_create("span")
:addClass("tpos")
:wikitext(" " .. interwiki_link)
)
end
function export.show_terminfo(terminfo, check)
local lang = terminfo.lang
local langcode, langname = lang:getCode(), lang:getCanonicalName()
-- Translations must be for mainspace languages.
if not lang:hasType("regular") then
error("Translations must be for attested and approved main-namespace languages.")
else
local disallowed = (translations_data or get_translations_data()).disallowed
local err_msg = disallowed[langcode]
if err_msg then
error("Translations not allowed in " .. langname .. " (" .. langcode .. "). " .. langname .. " translations should " .. err_msg)
end
local fullcode = lang:getFullCode()
if fullcode ~= langcode then
err_msg = disallowed[fullcode]
if err_msg then
langname = lang:getFullName()
error("Translations not allowed in " .. langname .. " (" .. fullcode .. "). " .. langname .. " translations should " .. err_msg)
end
end
end
if langcode == "zh" or langcode == "cmn" then
if terminfo.interwiki then
error("不允許使用跨維基翻譯來翻譯漢語;它們應該始終連結至其他維基詞典。")
end
local current_L2 = require(pages_module).get_current_L2()
if current_L2 ~= "跨語言" and mw.title.getCurrentTitle().nsText ~= "Wiktionary" then
if current_L2 then
error("漢語翻譯僅允許使用於跨語言章節,而不應使用於" .. current_L2 .. "章節。" )
else
error("漢語翻譯僅允許使用於跨語言章節,而不應使用任何L2標題以外。" )
end
end
end
local term = terminfo.term
-- Check if there is a term. Don't show the interwiki link if there is nothing to link to.
if not term then
-- Track entries that don't provide a term.
-- FIXME: This should be a category.
track("translations/no term")
track("translations/no term/" .. langcode)
end
if terminfo.interwiki then
interwiki(terminfo, term, lang, langcode)
end
langcode = lang:getFullCode()
if (translations_data or get_translations_data()).need_super[langcode] then
local tr = terminfo.tr
if tr ~= nil then
terminfo.tr = tr:gsub("%d[%d%*%-]*%f[^%d%*]", "<sup>%0</sup>")
end
end
terminfo.show_decorations = true
local link = full_link(terminfo, "translation")
local categories = {"有" .. lang:getFullName() .. "翻譯的詞"}
if check then
link = tostring(html_create("span")
:addClass("ttbc")
:tag("sup")
:addClass("ttbc")
:wikitext("(請[[:en:WT:Translations#Translations to be checked|驗證]]此翻譯)")
:done()
:wikitext(" " .. link)
)
insert(categories, langname .. "翻譯檢查請求")
end
return link .. format_categories(categories, en or get_en(), nil, canonical_pagename())
end
-- Implements {{t}}, {{t+}}, {{t-check}} and {{t+check}}.
function export.show(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["translation"])
local check = frame.args.check
return export.show_terminfo({
lang = args[1],
sc = args.sc,
track_sc = true,
term = args[2],
alt = args.alt,
id = args.id,
genders = args[3],
tr = args.tr,
ts = args.ts,
lit = args.lit,
q = args.q,
qq = args.qq,
l = args.l,
ll = args.ll,
refs = args.ref,
interwiki = frame.args.interwiki,
}, check and check ~= "")
end
local function add_id(div, id)
return id and div:attr("id", normalize_anchor("Translations-" .. id)) or div
end
-- Implements {{trans-top}} and part of {{trans-top-also}}.
local function top(args, title, id, navhead)
local column_width = (args["column-width"] == "wide" or args["column-width"] == "narrow") and "-" .. args["column-width"] or ""
local div = html_create("div")
:addClass("NavFrame")
:node(navhead)
:tag("div")
:addClass("NavContent")
:tag("table")
:addClass("translations")
:attr("role", "presentation")
:attr("data-gloss", title or "")
:tag("tr")
:tag("td")
:addClass("translations-cell")
:addClass("multicolumn-list" .. column_width)
:attr("colspan", "3")
:allDone()
div = add_id(div, id)
local categories = {}
if not title then
insert(categories, "缺少詞義的翻譯表")
end
local pagename = canonical_pagename()
if is_translation_subpage() then
insert(categories, "翻譯子頁面")
end
return (tostring(div):gsub("</td></tr></table></div></div>$", "")) ..
(#categories > 0 and format_categories(categories, en or get_en(), nil, pagename) or "") ..
-- Category to trigger [[MediaWiki:Gadget-TranslationAdder.js]]; we want this even on
-- user pages and such.
format_categories("有翻譯框的詞條", nil, nil, nil, true) ..
templatestyles("Module:translations/styles.css")
end
-- Entry point for {{trans-top}}.
function export.top(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["trans-top"])
local title = args[1]
local id = args.id or title
title = title and remove_links(title)
return top(args, title, id, html_create("div")
:addClass("NavHead")
:css("text-align", "left")
:wikitext(title or "翻譯")
)
end
-- Entry point for {{checktrans-top}}.
function export.check_top(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["checktrans-top"])
local text = "\n:需要檢查下面的翻譯並將其插入上面相應的翻譯表中。請參閱" ..
frame:expandTemplate{
title = "section link",
args = {"Wiktionary:佈局解釋#翻譯"}
} ..
"中的說明。\n"
local header = html_create("div")
:addClass("checktrans")
:wikitext(text)
local subtitle = args[1]
local title = "待檢查的翻譯"
if subtitle then
title = title .. "‌: \"" .. subtitle .. "\""
end
-- No ID, since these should always accompany proper translation tables, and can't be trusted anyway (i.e. there's no use-case for links).
return tostring(header) .. "\n" .. top(args, title, nil, html_create("div")
:addClass("NavHead")
:css("text-align", "left")
:wikitext(title or "翻譯")
)
end
-- Implements {{trans-bottom}}.
function export.bottom(frame)
-- Check nothing is being passed as a parameter.
process_params(frame:getParent().args, (parameters_data or get_parameters_data())["trans-bottom"])
return "</table></div></div>"
end
-- Implements {{trans-see}} and part of {{trans-top-also}}.
local function see(args, see_text)
local navhead = html_create("div")
:addClass("NavHead")
:css("text-align", "left")
:wikitext(args[1] .. " ")
:tag("span")
:css("font-weight", "normal")
:wikitext("— ")
:tag("i")
:wikitext(see_text)
:allDone()
local terms, id = args[2], args.id
if #terms == 0 then
terms[1] = args[1]
end
for i = 1, #terms do
local term_id = id[i] or id.default
local data = {
term = terms[i],
id = term_id and "Translations-" .. term_id or "翻譯",
}
terms[i] = plain_link(data)
end
return navhead:wikitext(concat(terms, ",‎ "))
end
-- Entry point for {{trans-see}}.
function export.see(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["trans-see"])
local div = html_create("div")
:addClass("pseudo")
:addClass("NavFrame")
:node(see(args, "參見"))
return tostring(add_id(div, args.id.default or args[1]))
end
-- Entry point for {{trans-top-also}}.
function export.top_also(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["trans-top-also"])
local navhead = see(args, "另請參見")
local title = args[1]
local id = args.id.default or title
title = remove_links(title)
return top(args, title, id, navhead)
end
-- Implements {{translation subpage}}.
function export.subpage(frame)
process_params(frame:getParent().args, (parameters_data or get_parameters_data())["translation subpage"])
if not is_translation_subpage() then
error("This template should only be used on translation subpages, which have titles that end with '/translations'.")
end
-- "Translation subpages" category is handled by {{trans-top}}.
return ("本頁包含「%s」的翻譯。請參閱主條目以了解更多資訊。"):format(full_link{
lang = en or get_en(),
term = canonical_pagename(),
})
end
-- Implements {{t-needed}}.
function export.needed(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["t-needed"])
local lang, category = args[1], ""
local span = html_create("span")
:addClass("trreq")
:attr("data-lang", lang:getCode())
:tag("i")
:wikitext("如果可以的話,請加上這個翻譯")
:done()
if not args.nocat then
local type, sort = args[2], args.sort
if type == "quote" then
category = lang:getCanonicalName() .. "引文翻譯請求"
elseif type == "usex" then
category = lang:getCanonicalName() .. "使用例翻譯請求"
else
category = lang:getCanonicalName() .. "翻譯請求"
lang = en or get_en()
end
category = format_categories(category, lang, sort, not sort and canonical_pagename() or nil)
end
return tostring(span) .. category
end
-- Implements {{no equivalent translation}}.
function export.no_equivalent(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["no equivalent translation"])
local text = args[1]:getCanonicalName() .. "中沒有對應的詞語"
if not args.noend then
text = text .. ",但請參見"
end
return tostring(html_create("span"):wikitext(text)) -- L10N:不使用斜體
end
-- Implements {{no attested translation}}.
function export.no_attested(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["no attested translation"])
local langname = args[1]:getCanonicalName()
local text = langname .. "中沒有[[WT:ATTEST|出現過]]對應的詞語對應的詞語"
local category = ""
if not args.noend then
text = text .. ",但請參見"
local sort = args.sort
category = format_categories(langname .. "無紀錄翻譯", en or get_en(), sort, not sort and canonical_pagename() or nil)
end
return tostring(html_create("i"):wikitext(text)) .. category
end
-- Implements {{not used}}.
function export.not_used(frame)
local args = process_params(frame:getParent().args, (parameters_data or get_parameters_data())["not used"])
return tostring(args[1]:getCanonicalName() .. html_create("span"):wikitext((args[2] or "不使用此詞"))) -- L10N:不使用斜體
end
return export
tjpok15wp81vv5shzhpk6ie3hsq76m9
Module:Es-pronunc
828
1374883
9909057
9871291
2026-09-29T07:25:17Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909057
Scribunto
text/plain
--[=[
This module implements the templates {{es-pr}} and {{es-IPA}}.
Author: Benwing2
]=]
local export = {}
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local decorations_module = "Module:decorations"
local headword_data_module = "Module:headword/data"
local homophones_module = "Module:homophones"
local hyphenation_module = "Module:hyphenation"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local parse_utilities_module = "Module:parse utilities"
local references_module = "Module:references"
local rhymes_module = "Module:rhymes"
local force_cat = false -- for testing
--[=[
FIXME:
1. Port latest changes to production module. [DONE]
2. Finish work on rhymes and hyphenation. [DONE]
3. Handle <hmp:...> for homophones. [DONE]
4. Don't add comma before phonetic IPA. [DONE]
5. Handle secondary stress, suffixes, etc. in syllabification. [DONE]
6. Need some changes to syllable splitting in consonant clusters. (e.g. 'cum‧min‧gto‧ni‧ta') [DONE]
7. Fix handling of references to correspond to Portuguese module. [DONE]
8. Propagate qualifiers on individual pronun terms to rhymes and hyph.
9. Support raw phonemic/phonetic pronunciations. [DONE]
10. Support overall audio. [DONE]
11. Keep th/ph/kh/gh/tz ([[Ertzaintza]]) together when syllabifying (but not bh due to [[subhumano]], [[subhistoria]], etc.). [DONE]
12. Support <q:...> and <qq:...> on audio. [DONE]
13. Support <a:...> and <aa:...> (using {{a|...}}, left and right) on terms, rhymes, hyphenation, homophones and
audio. [DONE]
14. Support # instead of ; as separator between audio file and gloss and make sure it works if gloss has embedded # or
;. [DONE]
15. Use parse_inline_modifiers() in [[Module:parse utilities]]. [DONE]
]=]
--[=[
About styles, dialects and isoglosses:
From the standpoint of pronunciation, a given dialect is defined by isoglosses, which specify differences
in the way of pronouncing certain phonemes. You can think of a dialect as a collection of isoglosses.
For example, one isogloss is "distinción" (pronouncing written ''s'' and ''c/z'' differently) vs. "seseo"
(pronouncing them the same). Another is "lleísmo" (pronouncing written ''ll'' and ''y'' differently) vs.
"yeísmo" (pronouncing them the same). The dominant pronunciation in Spain can be described as
distinción + yeísmo, while the pronunciation in rural northern Spain can be described as distinción + lléismo
and the pronunciation across much of the Andes mountains, Paraguay, and the Philippines can be described as seseo + lléismo.
Specifically, the following isoglosses are recognized (note, the isogloss specs as used in this module
dispense with written accents):
-- "distincion" = pronouncing ''s'' and ''c/z'' differently
-- "seseo" = pronouncing ''s'' and ''c/z'' the same
-- "lleismo" = pronouncing ''ll'' and ''y'' differently
-- "yeismo" = pronouncing ''ll'' and ''y'' the same
-- "rioplatense" = Rioplatense speech, i.e. seseo+yeismo with ''ll'' and ''y'' pronounced specially, and a
clear distinction between initial ''hi-'' vs. initial ''ll-/y-''
-- "sheismo" = a type of Rioplatense speech, characteristic of Buenos Aires, where ''ll'' and ''y'' are
pronounced as /ʃ/
-- "zheismo" = a type of Rioplatense speech, found outside of Buenos Aires, where ''ll'' and ''y'' are
pronounced as /ʒ/
-- "quito" = seseo + lleismo, but pronouncing ''ll'' as /ʒ/
-- "yucatan" = seseo + yeismo, intervocalic ''y'' is pronounced ''i'' and lost in contact with ''i'' or ''e''
These isoglosses can be combined to yield one of the following eight dialects:
-- "distincion-lleismo": distinción + lleísmo
-- "distincion-yeismo": distinción + yeísmo
-- "seseo-lleismo": seseo + lleísmo
-- "seseo-yeismo": seseo + yeísmo
-- "rioplatense-sheismo": Rioplatense with /ʃ/ (Buenos Aires)
-- "rioplatense-zheismo": Rioplatense with /ʒ/ (non-Buenos Aires)
-- "quito"
-- "yucatan"
A "style" here is a set of dialects that pronounce a given word in a given fashion. For example, if we are only
considering the distinción/seseo and lleísmo/yeísmo isoglosses, there are four conceivable dialects (all of
which in fact exist). However, for a given word, more than one dialect may pronounce it the same. For
example, a word like [[paz]] has a ''z'' but no ''ll'', and so there are only two possible pronunciations for
the four dialects. Here, the two styles are "Spain" and "Latin America". Correspondingly, a word like [[pollo]]
with an ''ll'' but no ''z'' has two styles, which can approximately be described as "most of Spain and Latin
America" vs. "rural northern Spain, Andes Mountains, Paraguay, Philippines".
A "style spec" (indicated by the style= parameter to {{es-IPA}}) restricts the output to certain styles.
A style spec can be one of the following:
1. An isogloss, e.g. "distincion", "rioplatense"; if specified, only styles containing this isogloss are output.
2. A negated isogloss, e.g. "-rioplatense".
3. An intersection of isoglosses ("A and B"), e.g. "distincion+lleismo". This can be used to restrict to specific
dialects.
4. A union of isoglosses ("A or B"), e.g. "distincion,zheismo". If both plus and comma are used, plus takes
precedence, e.g. "seseo+lleismo,zheismo" means either the "seseo+lleismo" dialect or the "rioplatense-zheismo"
dialect.
An example where the style= parameter might be used is with the word [[bluetooth]], which has one pronunciation
in Spain/distinción (respelled "blutuz") but another in Latin America/seseo (respelled "blutud"). This might be
represented using {{es-pr}} as {{es-pr|blutuz<style:distincion>|blutud<style:seseo>}}.
]=]
local lang = require("Module:languages").getByCode("es")
local decompose = require("Module:es-common").decompose
local u = m_str_utils.char
local rfind = m_str_utils.find
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local ulower = m_str_utils.lower
local ulen = m_str_utils.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeiouüyAEIOUÜY" -- vowel; include y so we get single-word y correct and for syllabifying from spelling
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR .. CFLEX
local accent_c = "[" .. accent .. "]"
local stress = AC .. GR
local stress_c = "[" .. AC .. GR .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class including h
local C_NOT_H = "[^" .. vowel .. separator .. "h]" -- consonant class not including h
local C_OR_WORDSEP = "[^" .. vowel .. separator_not_wordsep .. "]" -- consonant class including h, or word separator
local T = "[^" .. vowel .. "lrɾjw" .. separator .. "]" -- obstruent or nasal
local unstressed_words = m_table.listToSet({
"el", "la", "los", "las", -- definite articles
"un", -- single-syllable indefinite articles
"me", "te", "se", "lo", "le", "nos", "os", "les", -- unstressed object pronouns
"mi", "mis", "tu", "tus", "su", "sus", -- unstressed possessive pronouns
"que", "si", -- subordinating conjunctions
"y", "e", "o", "u", "mas", -- coordinating conjunctions
"de", "del", "a", "al", -- basic prepositions + combinations with articles
"por", "en", "con", -- other prepositions
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function split_on_comma(term)
if not term then
return nil
end
if term:find(",%s") then
return require(parse_utilities_module).split_on_comma(term)
elseif term:find(",") then
return rsplit(term, ",")
else
return {term}
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require(links_module).remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
local function construct_default_differences(dialect)
if dialect == "distincion-lleismo" then
return {
distincion_different = false,
lleismo_different = false,
sheismo_different = false,
need_rioplat = false,
need_quito = false,
need_yucatan = false,
}
end
return nil
end
-- Main syllable-division algorithm. Can be called either directly on spelling (when hyphenating) or after
-- non-trivial processing of respelling in the direction of pronunciation (when generating pronunciation).
local function syllabify_from_spelling_or_pronun(text, is_spelling)
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels (but don't divide a VhV
-- sequence; [[prohibir]] should be prohi.bir). Then move the syllable division marker leftwards over clusters that
-- can form onsets.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C_NOT_H .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
-- Puerto Rico + most of Spain divide tl as t.l. Mexico and the Canary Islands have .tl. Unclear what other regions
-- do. Here we choose to go with .tl. See https://catalog.ldc.upenn.edu/docs/LDC2019S07/Syllabification_Rules_in_Spanish.pdf
-- and https://www.spanishdict.com/guide/spanish-syllables-and-syllabification-rules.
-- NOTE: When run on pronun, we have already eliminated c and v, but not when run on spelling.
-- When run on pronun, don't include r, which at this point represents the trill.
local cluster_r = is_spelling and "rɾ" or "ɾ"
-- Don't divide Cl or Cr where C is a stop or fricative, except for dl.
text = rsub(text, "([pbfvkctg])%.([l" .. cluster_r .. "])", ".%1%2")
text = text:gsub("d%.([" .. cluster_r .. "])", ".d%1")
-- Don't divide ch, sh, ph, th, dh, fh, kh or gh. Do allow bh to be divided ([[subhumano]], [[subhúmedo]], etc.).
text = rsub(text, "([csptdfkg])%.h", ".%1h")
-- Don't divide ll or rr.
text = rsub(text, "([lr])%.%1", ".%1%1")
-- Don't divide tz ([[Ertzaintza]], [[quetzal]], [[hertziano]] and other words of Basque, Nahuatl and German
-- origin).
text = rsub(text, "t%.z", ".tz")
-- Per https://catalog.ldc.upenn.edu/docs/LDC2019S07/Syllabification_Rules_in_Spanish.pdf, tl at the end of a word
-- (as in nahuatl, Popocatepetl etc.) is divided .tl from the previous vowel.
if is_spelling then
text = text:gsub("([^. %-])tl$", "%1.tl")
text = text:gsub("([^. %-])(tl[ %-])", "%1.%2")
else
text = text:gsub("([^.#])tl#", "%1.tl")
end
-- Part 2: Divide hiatuses. Any aeo, or stressed iuüy, should be syllabically divided from a following aeo or
-- stressed iuüy. Also divide ii and uu sequences ([[antiincendios]], [[shiita]], [[vacuum]]). Note that words with
-- ii or uu next to a vowel (e.g. [[hawaiiano]]) will not make it to this point unchanged; the i or u adjacent to
-- a vowel (or the second one if both are adjacent to vowels) will get converted to a consonant symbol (temporarily
-- when syllabifying spelling).
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?[aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub(text, "([iuüyIUÜY]" .. stress_c .. ")(h?[aeo])", "%1.%2")
text = rsub_repeatedly(text, "([iuüyIUÜY]" .. stress_c .. ")(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "([iI]" .. accent_c .. "*)(h?i)", "%1.%2")
text = rsub_repeatedly(text, "([uU]" .. accent_c .. "*)(h?u)", "%1.%2")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants.
local TEMP_I = u(0xFFF1)
local TEMP_U = u(0xFFF2)
local TEMP_Y_CONS = u(0xFFF3)
local TEMP_QU = u(0xFFF4)
local TEMP_QU_CAPS = u(0xFFF5)
local TEMP_GU = u(0xFFF6)
local TEMP_GU_CAPS = u(0xFFF7)
local TEMP_H = u(0xFFF8)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = text:gsub("%.", SYLDIV)
text = rsub(text, "y(" .. V .. ")", TEMP_Y_CONS .. "%1")
-- We don't want to break -sh- except in desh-, e.g. [[deshuesar]], [[deshonra]], [[deshecho]]. Normally, -sh- is
-- automatically preserved, so we replace the h with a temporary symbol to avoid this.
text = text:gsub("^([Dd]es)h", "%1" .. TEMP_H)
text = text:gsub("([ %-][Dd]es)h", "%1" .. TEMP_H)
-- qu mostly handled correctly automatically, but not in quietud
text = rsub(text, "qu(" .. V .. ")", TEMP_QU .. "%1")
text = rsub(text, "Qu(" .. V .. ")", TEMP_QU_CAPS .. "%1")
text = rsub(text, "gu(" .. V .. ")", TEMP_GU .. "%1")
text = rsub(text, "Gu(" .. V .. ")", TEMP_GU_CAPS .. "%1")
local vowel_to_glide = { ["i"] = TEMP_I, ["u"] = TEMP_U }
-- i and u between vowels -> consonant-like substitutions: [[paranoia]], [[baiano]], [[abreuense]], [[alauita]],
-- [[Malaui]], etc.; also with h, as in [[marihuana]], [[parihuela]], [[antihielo]], [[pelluhuano]], [[náhuatl]],
-- etc. When we do this we need to help the syllabification particularly of words with -hiV- and -huV- in them,
-- otherwise we get e.g. 'an.tih.ie.lo' because we converted the i following the h to a consonant. Add .* at the
-- beginning so we go right-to-left, in the case of [[hawaiiano]] -> ha.wai.iano.
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*)(h?)([iu])(" .. V .. ")",
function (v1, h, iu, v2) return v1 .. "." .. h .. vowel_to_glide[iu] .. v2 end
)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
text = text:gsub(SYLDIV, ".")
text = text:gsub(TEMP_I, "i")
text = text:gsub(TEMP_U, "u")
text = text:gsub(TEMP_Y_CONS, "y")
text = text:gsub(TEMP_QU, "qu")
text = text:gsub(TEMP_QU_CAPS, "Qu")
text = text:gsub(TEMP_GU, "gu")
text = text:gsub(TEMP_GU_CAPS, "Gu")
text = text:gsub(TEMP_H, "h")
text = unfc(text)
-- No qualifiers from dialect tags because we assume all dialects hyphenate the same way.
-- FIXME: There are region-specific ways of hyphenating -tl-. See above. We don't currently handle this properly.
return text
end
-- Generate the IPA of a given respelling, where a respelling is the representation of the pronunciation of a given
-- Spanish term using Spanish spelling conventions (augmented in a few cases with extra conventions such as 'sh' for
-- /ʃ/).
-- ɟ and ĉ are used internally to represent [ʝ⁓ɟ͡ʝ] and [t͡ʃ]
--
function export.IPA(text, dialect, phonetic)
local distincion = dialect == "distincion-lleismo" or dialect == "distincion-yeismo"
local lleismo = dialect == "distincion-lleismo" or dialect == "seseo-lleismo" or dialect == "quito"
local rioplat = dialect == "rioplatense-sheismo" or dialect == "rioplatense-zheismo"
local sheismo = dialect == "rioplatense-sheismo"
local quito = dialect == "quito"
local yucatan = dialect == "yucatan"
local distincion_different = false
local lleismo_different = false
local need_rioplat = false
local need_quito = false
local need_yucatan = false
local initial_hi = false
local sheismo_different = false
-- start at FFF1 because FFF0 is used for SYLDIV
local TEMP_Y = u(0xFFF1)
local TEMP_W = u(0xFFF2)
text = ulower(text or mw.loadData("Module:headword/data").pagename)
-- decompose everything but ç, ñ and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain
-- monosyllabic words (e.g. [[el]], [[la]], [[de]], [[en]], etc.) without stress marks be
-- unstressed.
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") and not rfind(word, accent_c) or unstressed_words[word] then
-- add CFLEX to the last vowel not the first one, or we will mess up 'que' by
-- adding the CFLEX after the 'u'
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CFLEX)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces, to handle [[Austria-Hungría]], [[franco-italiano]], etc.
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--determining whether "y" is a consonant or a vowel
text = rsub(text, "y(" .. V .. ")", "ɟ%1") -- not the real sound
-- word-final -ay/-ey/-oy/-uy is stressed whereas word-final -ai/-ei/-oi/-ui is not; in addition,
-- word-final -uy is /uj/ whereas word-final -ui is /wi/ (e.g. [[muy]] vs. [[fui]])
text = rsub(text, "([aeou])y#", "%1" .. TEMP_Y .. "#") -- a temporary symbol; replaced with i below
text = rsub(text, "y", "i")
-- handle certain combinations; sh handling needs to go before x handling to avoid issues with [[exhausto]]
text = rsub(text, "ch", "ĉ") --not the real sound
-- We want to keep desh- ([[deshuesar]]) as-is. Converting to des- won't work because we want it syllabified as
-- 'des.we.saɾ' not #'de.swe.saɾ' (cf. [[desuelo]] /de.swe.lo/ from [[desolar]]).
text = rsub(text, "#desh", "!") --temporary symbol
text = rsub(text, "sh", "ʃ")
text = rsub(text, "!", "#desh") --restore
text = rsub(text, "#[ckp]([st])", "#%1") -- [[ctónico]], [[psicología]], [[pterodáctilo]]
--x
text = rsub(text, "#x", "#s") -- xenofobia, xilófono, etc.
text = rsub(text, "x", "ks")
--c, g, q
text = rsub(text, "c([ie])", (distincion and "θ" or "z") .. "%1") -- not the real LatAm sound
text = rsub(text, "g([ie])", "x%1") -- must happen after handling of x above
text = rsub(text, "gu([ie])", "g%1")
text = rsub(text, "gü([ie])", "gu%1")
-- following must happen before stress assignment; [[branding]] has initial stress like 'brandin'
text = rsub(text, "ng([^aeiouüwhlr])", "n%1") -- [[Bangkok]], [[ángstrom]], [[branding]]
text = rsub(text, "qu([ie])", "k%1")
text = rsub(text, "ü", "u") -- [[Düsseldorf]], [[hübnerita]], obsolete [[freqüentemente]], etc.
text = rsub(text, "q", "k") -- [[quark]], [[Qatar]], [[burqa]], [[Iraq]], etc.
text = rsub(text, "[zç]", distincion and "θ" or "z") -- not the real LatAm sound; "ç" became "z" in 1815
if rfind(text, "[θz]") then
distincion_different = true
end
-- map various consonants to their phoneme equivalent
text = rsub(text, "[cjñrv]", {["c"]="k", ["j"]="x", ["ñ"]="ɲ", ["r"]="ɾ", ["v"]="b" })
-- handle word- and syllable-initial hiV ([[hielo]], [[enhiesto]], [[deshielo]], ...)
local word_initial_hi, syl_initial_hi
text, word_initial_hi = rsubb(text, "#h?i(" .. V .. ")", rioplat and "#j%1" or "#ɟ%1")
text, syl_initial_hi = rsubb(text, "(" .. C .. sylsep_c .. "*)hi(" .. V .. ")", rioplat and "%1j%2" or "%1ɟ%2")
initial_hi = word_initial_hi or syl_initial_hi
-- handle word- and syllable-initial huV ([[huevo]], [[deshuesar]])
text = rsubb(text, "(" .. C_OR_WORDSEP .. sylsep_c .. "*)hu(" .. V .. ")", "%1" .. TEMP_W .. "%2")
-- handle double consonants that have a pronunciation different from their single equivalents
-- double l
lleismo_different = rfind(text, "ll")
need_quito = lleismo_different and rfind(text, "ɟ")
text = rsub(text, "ll", lleismo and "ʎ" or "ɟ")
-- handle intervocalic -y-
need_yucatan = rfind(text, V .. accent_c .. "*" .. sylsep_c .. "*[ʎɟ]" .. V)
if yucatan then
text = rsub_repeatedly(text, "([ei]" .. accent_c .. "*" .. sylsep_c .. "*)ɟ(" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. sylsep_c .. "*)ɟ" .. "([ei])", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .."*" .. sylsep_c .. "*)ɟ(" .. V .. ")", "%1i%2")
end
-- trill in #r, lr ([[alrededor]], [[malrotar]]), nr ([[enriquecer]], [[sonrisa]], etc.), sr ([[Israel]],
-- [[desregular]], etc.), zr ([[Azrael]], [[cruzrojista]]), rr
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "([#lnszθ])ɾ", "%1r")
-- double n (e.g. [[[ennoblecer]])
text = rsub(text, "nn", "N")
-- double b (e.g. [[subbase]])
text = rsub(text, "bb", "B")
-- reduce any remaining double consonants ([[Addis Abeba]], [[cappa]], [[descender]] in Latin America ...);
-- do this before handling of -nm- e.g. in [[inmigración]], which generates a double consonant, and do this
-- before voicing stops before obstruents, to avoid problems with [[cappa]] and [[crackear]]
text = rsub(text, "(" .. C .. ")%1", "%1")
-- also reduce sz (Latin American in [[fascinante]], etc.)
text = rsub(text, "sz", "s")
-- restore double n, b
text = rsub(text, "N", "nn")
text = rsub(text, "B", "bb")
-- voiceless stop to voiced before obstruent or nasal; but intercept -ts-, -tz-
local voice_stop = { ["p"] = "b", ["t"] = "d", ["k"] = "g" }
text = rsub(text, "t(" .. separator_c .. "*[szθ])", "!%1") -- temporary symbol
text = rsub(text, "([ptk])(" .. separator_c .. "*" .. T .. ")",
function(stop, after) return voice_stop[stop] .. after end)
text = rsub(text, "!", "t")
text = rsub(text, "n([# .]*[bpm])", "m%1")
-- remove silent h before syllable division
text = rsub(text, "h", "")
-- convert i/u between vowels to glide
local vowel_to_glide = { ["i"] = "j", ["u"] = "w" }
-- i and u between vowels -> consonant-like substitutions: [[paranoia]], [[baiano]], [[abreuense]], [[alauita]],
-- [[Malaui]], etc.; also with h, as in [[marihuana]], [[parihuela]], [[antihielo]], [[pelluhuano]], [[náhuatl]],
-- etc. Add .* at the beginning so we go right-to-left, in the case of [[hawaiiano]] -> ha.wai.iano.
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*h?)([iu])(" .. V .. ")",
function (v1, iu, v2) return v1 .. vowel_to_glide[iu] .. v2 end
)
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--diphthongs; do not include TEMP_Y here
text = rsub(text, "i([aeou])", "j%1")
text = rsub(text, "u([aeio])", "w%1")
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = "ˌ", [CFLEX] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including ^ indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and (rfind(word, "[^" .. vowel .. "ns#]#") or rfind(word, C .. "[ns]#")) or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
if rfind(word, "men%.te#") then
local mente_syllables
-- Words ends in -mente (converted above to ménte); add a stress to the preceding portion
-- (e.g. [[agriamente]] -> 'ágriaménte') unless already stressed (e.g. [[rápidamente]]).
-- It will be converted to secondary stress further below. Essentially, we rip the word apart
-- into two words ('mente' and the preceding portion) and stress each one independently.
mente_syllables = {}
mente_syllables[2] = table.remove(syllables)
mente_syllables[1] = table.remove(syllables)
accent_word(table.concat(syllables, "."), syllables)
accent_word(table.concat(mente_syllables, "."), mente_syllables)
table.insert(syllables, mente_syllables[1])
table.insert(syllables, mente_syllables[2])
else
accent_word(word, syllables)
end
-- Vowels are nasalized if followed by nasal in same syllable.
if phonetic then
for i = 1, #syllables do
-- first check for two vowels (veinte)
syllables[i] = rsub(syllables[i], "(" .. V .. ")(" .. V .. ")([mnɲ])",
"%1" .. TILDE .. "%2" .. TILDE .. "%3")
-- then for one vowel
syllables[i] = rsub(syllables[i], "(" .. V .. ")([mnɲ])", "%1" .. TILDE .. "%2")
end
end
-- Reconstruct the word.
words[j] = table.concat(syllables, ".")
end
text = table.concat(words, " ")
text = rsub(text, TEMP_Y, "i") --final -ay/-ey/-oy/-uy
text = rsub(text, "z", "s") --real sound of LatAm Z
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ")
if (not initial_hi and rfind(text, "[ʎɟ]")) or (rfind(text, sylsep_c .. "[ʎɟ]")) then
sheismo_different = true
end
if rioplat then
if not initial_hi then
if sheismo then
text = rsub(text, "ɟ", "ʃ")
else
text = rsub(text, "ɟ", "ʒ")
end
else
if sheismo then
text = rsub(text, sylsep_c .. "(ɟ)", "ʃ")
else
text = rsub(text, sylsep_c .. "(ɟ)", "ʒ")
end
end
end
if quito then text = rsub(text, "ʎ", "ʒ") end
--phonetic transcription
if phonetic then
-- θ, s, f before voiced consonants
local voiced = "mnɲbdɟgʎ" .. TEMP_W
local r = "ɾr"
local tovoiced = {
["θ"] = "θ̬",
["s"] = "z",
["f"] = "v",
}
local function voice(sound, following)
return tovoiced[sound] .. following
end
text = rsub(text, "([θs])(" .. separator_c .. "*[" .. voiced .. r .. "])", voice)
text = rsub(text, "(f)(" .. separator_c .. "*[" .. voiced .. "])", voice)
-- fricative vs. stop allophones; first convert stops to fricatives, then back to stops
-- after nasals and sometimes after l
local stop_to_fricative = {["b"] = "β", ["d"] = "ð", ["ɟ"] = "ʝ", ["g"] = "ɣ"}
local fricative_to_stop = {["β"] = "b", ["ð"] = "d", ["ʝ"] = "ɟ", ["ɣ"] = "g"}
text = rsub(text, "[bdɟg]", stop_to_fricative)
text = rsub(text, "([mnɲ]" .. separator_c .. "*)([βɣ])",
function(nasal, fricative) return nasal .. fricative_to_stop[fricative] end
)
text = rsub(text, "([lʎmnɲ]" .. separator_c .. "*)([ðʝ])",
function(nasal_l, fricative) return nasal_l .. fricative_to_stop[fricative] end
)
text = rsub(text, "(##" .. ipa_stress_c .. "*)([βɣðʝ])",
function(stress, fricative) return stress .. fricative_to_stop[fricative] end
)
text = rsub(text, "[td]", {["t"] = "t̪", ["d"] = "d̪"})
-- nasal assimilation before consonants
local labiodental, dentialveolar, dental, alveolopalatal, palatal, velar =
"ɱ", "n̪", "n̟", "nʲ", "ɲ", "ŋ"
local nasal_assimilation = {
["f"] = labiodental,
["t"] = dentialveolar, ["d"] = dentialveolar,
["θ"] = dental,
["ĉ"] = alveolopalatal,
["ʃ"] = alveolopalatal,
["ʒ"] = alveolopalatal,
["ɟ"] = palatal, ["ʎ"] = palatal,
["k"] = velar, ["x"] = velar, ["g"] = velar,
}
text = rsub(text, "n(" .. separator_c .. "*)(.)",
function(stress, following) return (nasal_assimilation[following] or "n") .. stress .. following end
)
-- lateral assimilation before consonants
text = rsub(text, "l(" .. separator_c .. "*)(.)",
function(stress, following)
local l = "l"
if following == "t" or following == "d" then -- dentialveolar
l = "l̪"
elseif following == "θ" then -- dental
l = "l̟"
elseif following == "ĉ" or following == "ʃ" then -- alveolopalatal
l = "lʲ"
end
return l .. stress .. following
end)
--semivowels
text = rsub(text, "([aeouãẽõũ][iĩ])", "%1̯")
text = rsub(text, "([aeioãẽĩõ][uũ])", "%1̯")
-- voiced fricatives are actually approximants
text = rsub(text, "([βðɣ])", "%1̞")
end
-- convert fake symbols to real ones
local final_conversions = {
["ħ"] = "h", -- fake aspirated "h" to real "h"
["ĉ"] = "t͡ʃ", -- fake "ch" to real "ch"
["ɟ"] = phonetic and "ɟ͡ʝ" or "ʝ", -- fake "y" to real "y"
-- do the following at the very end so we can use regular g throughout
["g"] = "ɡ", -- U+0067 LATIN SMALL LETTER G → U+0261 LATIN SMALL LETTER SCRIPT G
[TEMP_W] = "w̝", -- see https://en.wikipedia.org/wiki/Spanish_orthography for this
}
text = rsub(text, "[ħĉɟg" .. TEMP_W .. "]", final_conversions)
-- remove # symbols at word and text boundaries
text = rsub(text, "#", "")
text = unfc(text)
-- The values in `differences` are only accurate when the dialect is 'distincion-lleismo'
-- because we look for sounds like /θ/ and /ʎ/ that are only present in that dialect.
-- The calling code knows to only use this structure in conjunction with this dialect.
-- but to make sure of this we set the structure to nil for other dialects.
local differences = nil
if dialect == "distincion-lleismo" then
differences = {
distincion_different = distincion_different,
lleismo_different = lleismo_different,
need_rioplat = initial_hi or sheismo_different,
sheismo_different = sheismo_different,
need_quito = need_quito,
need_yucatan = need_yucatan,
}
end
local ret = {
text = text,
differences = differences,
}
return ret
end
-- For bot usage; {{#invoke:es-pronunc|IPA_string|SPELLING|style=STYLE|phonetic=PHONETIC}}
-- where
--
-- 1. SPELLING is the word or respelling to generate pronunciation for;
-- 2. required parameter style= indicates the pronunciation style to generate
-- (e.g. "distincion-yeismo" for distinción+yeísmo, as is common in Spain;
-- see the comment above export.IPA() above for the full list);
-- 3. phonetic=1 specifies to generate the phonetic rather than phonemic pronunciation;
function export.IPA_string(frame)
local iparams = {
[1] = {},
["style"] = {required = true},
["phonetic"] = {type = "boolean"},
}
local iargs = require(parameters_module).process(frame.args, iparams)
local retval = export.IPA(iargs[1], iargs.style, iargs.phonetic)
return retval.text
end
-- Generate all relevant dialect pronunciations and group into styles. See the comment above about dialects and styles.
-- A "pronunciation" here could be for example the IPA phonemic/phonetic representation of the term or the IPA form of
-- the rhyme that the term belongs to. If `style_spec` is nil, this generates all styles for all dialects, but
-- `style_spec` can also be a style spec such as "seseo" or "distincion+yeismo" (see comment above) to restrict the
-- output. `dodialect` is a function of two arguments, `ret` and `dialect`, where `ret` is the return-value table (see
-- below), and `dialect` is a string naming a particular dialect, such as "distincion-lleismo" or "rioplatense-sheismo".
-- `dodialect` should side-effect the `ret` table by adding an entry to `ret.pronun` for the dialect in question.
--
-- The return value is a table of the form
--
-- {
-- pronun = {DIALECT = {PRONUN, PRONUN, ...}, DIALECT = {PRONUN, PRONUN, ...}, ...},
-- expressed_styles = {STYLE_GROUP, STYLE_GROUP, ...},
-- }
--
-- where:
-- 1. DIALECT is a string such as "distincion-lleismo" naming a specific dialect.
-- 2. PRONUN is a table describing a particular pronunciation. If the dialect is "distincion-lleismo", there should be
-- a field in this table named `differences`, but where other fields may vary depending on the type of pronunciation
-- (e.g. phonemic/phonetic or rhyme). See below for the form of the PRONUN table for phonemic/phonetic pronunciation
-- vs. rhyme and the form of the `differences` field.
-- 3. STYLE_GROUP is a table of the form {tag = "HIDDEN_TAG", styles = {INNER_STYLE, INNER_STYLE, ...}}. This describes
-- a group of related styles (such as those for Latin America) that by default (the "hidden" form) are displayed as
-- a single line, with an icon on the right to "open" the style group into the "shown" form, with multiple lines
-- for each style in the group. The tag of the style group is the text displayed before the pronunciation in the
-- default "hidden" form, such as "Spain" or "Latin America". It can have the special value of `false` to indicate
-- that no tag text is to be displayed. Note that the pronunciation shown in the default "hidden" form is taken
-- from the first style in the style group.
-- 4. INNER_STYLE is a table of the form {tag = "SHOWN_TAG", pronun = {PRONUN, PRONUN, ...}}. This describes a single
-- style (such as for the Andes Mountains and Paraguay in the case where the seseo+lleismo accent differs from all others), to
-- be shown on a single line. `tag` is the text preceding the displayed pronunciation, or `false` if no tag text
-- is to be displayed. PRONUN is a table as described above and describes a particular pronunciation.
--
-- The PRONUN table has the following form for the full phonemic/phonetic pronunciation:
--
-- {
-- phonemic = "PHONEMIC",
-- phonetic = "PHONETIC",
-- differences = {FLAG = BOOLEAN, FLAG = BOOLEAN, ...},
-- }
--
-- Here, `phonemic` is the phonemic pronunciation (displayed as /.../) and `phonetic` is the phonetic pronunciation
-- (displayed as [...]).
--
-- The PRONUN table has the following form for the rhyme pronunciation:
--
-- {
-- rhyme = "RHYME_PRONUN",
-- num_syl = {NUM, NUM, ...},
-- q = nil or {QUALIFIER, QUALIFIER, ...},
-- differences = {FLAG = BOOLEAN, FLAG = BOOLEAN, ...},
-- }
--
-- Here, `rhyme` is a phonemic pronunciation such as "ado" for [[abogado]] or "iʝa"/"iʎa" for [[tortilla]] (depending
-- on the dialect), and `num_syl` is a list of the possible numbers of syllables for the term(s) that have this rhyme
-- (e.g. {4} for [[abogado]], {3} for [[tortilla]] and {4, 5} for [[biología]], which may be syllabified as
-- bio.lo.gí.a or bi.o.lo.gí.a). `num_syl` is used to generate syllable-count categories such as
-- [[Category:Rhymes:Spanish/ia/4 syllables]] in addition to [[Category:Rhymes:Spanish/ia]]. `num_syl` may be nil to
-- suppress the generation of syllable-count categories; this is typically the case with multiword terms.
-- `q`, if non-nil, comes from the user using the syntax e.g. <rhyme:iʃa<q:Buenos Aires>>.
--
-- The value of the `differences` field in the PRONUN table (which, as noted above, only needs to be present for the
-- "distincion-lleismo" dialect, and otherwise should be nil) is a table containing flags indicating whether and how
-- the per-dialect pronunciations differ. This is an optimization to avoid having to generate all six dialectal
-- pronunciations and compare them. It has the following form:
--
-- {
-- distincion_different = BOOLEAN,
-- lleismo_different = BOOLEAN,
-- need_rioplat = BOOLEAN,
-- sheismo_different = BOOLEAN,
-- need_quito = BOOLEAN,
-- need_yucatan = BOOLEAN,
-- }
--
-- where:
-- 1. `distincion_different` should be `true` if the "distincion" and "seseo" pronunciations differ;
-- 2. `lleismo_different` should be `true` if the "lleismo" and "yeismo" pronunciations differ;
-- 3. `need_rioplat` should be `true` if the Rioplatense pronunciations differ from the seseo+yeismo pronunciation;
-- 4. `sheismo_different` should be `true` if the "sheismo" and "zheismo" pronunciations differ.
-- 5. `need_quito` should be `true` if the "quito" and "zheismo" pronunciations differ.
-- 6. `need_yucatan` should be `true` if the "yucatan" and "yeismo" pronunciations differ;
local function express_all_styles(style_spec, dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
local need_rioplat
local need_quito
local need_yucatan
-- Add a style object (see INNER_STYLE above) that represents a particular style to `ret.expressed_styles`.
-- `hidden_tag` is the tag text to be used when the style group containing the style is in the default "hidden"
-- state (e.g. "Spain", "Latin America" or false if there is only one style group and no tag text should be
-- shown), while `tag` is the tag text to be used when the individual style is shown (e.g. a description such as
-- "most of Spain and Latin America", "Andes Mountains and Paraguay" or "everywhere but Argentina and Uruguay").
-- `representative_dialect` is one of the dialects that this style represents, and whose pronunciation is stored in
-- the style object. `matching_styles` is a hyphen separated string listing the isoglosses described by this style.
-- For example, if the term has an ''ll'' but no ''c/z'', the `tag` text for the yeismo pronunciation will be
-- "most of Spain and Latin America" and `matching_styles` will be "distincion-seseo-yeismo", indicating that
-- it corresponds to both the "distincion" and "seseo" isoglosses as well as the "yeismo" isogloss. This is used
-- when a particular style spec is given. If `matching_styles` is omitted, it takes its value from
-- `representative_dialect`; this is used when the style contains only a single dialect.
local function express_style(hidden_tag, tag, representative_dialect, matching_styles)
matching_styles = matching_styles or representative_dialect
-- If the Rioplatense pronunciation isn't distinctive, add all Rioplatense isoglosses.
if not need_rioplat then
matching_styles = matching_styles .. "-rioplatense-sheismo-zheismo"
end
-- also Quito
if not need_quito then
matching_styles = matching_styles .. "-quito"
end
-- Yucatan
if not need_yucatan then
matching_styles = matching_styles .. "-yucatan"
end
-- If style specified, make sure it matches the requested style.
local style_matches
if not style_spec then
style_matches = true
else
local style_parts = rsplit(matching_styles, "%-")
local or_styles = rsplit(style_spec, "%s*,%s*")
for _, or_style in ipairs(or_styles) do
local and_styles = rsplit(or_style, "%s*%+%s*")
local and_matches = true
for _, and_style in ipairs(and_styles) do
local negate
if and_style:find("^%-") then
and_style = and_style:gsub("^%-", "")
negate = true
end
local this_style_matches = false
for _, part in ipairs(style_parts) do
if part == and_style then
this_style_matches = true
break
end
end
if negate then
this_style_matches = not this_style_matches
end
if not this_style_matches then
and_matches = false
end
end
if and_matches then
style_matches = true
break
end
end
end
if not style_matches then
return
end
-- Fetch the representative dialect's pronunciation if not already present.
if not ret.pronun[representative_dialect] then
dodialect(ret, representative_dialect)
end
-- Insert the new style into the style group, creating the group if necessary.
local new_style = {
tag = tag,
pronun = ret.pronun[representative_dialect],
}
for _, hidden_tag_style in ipairs(ret.expressed_styles) do
if hidden_tag_style.tag == hidden_tag then
table.insert(hidden_tag_style.styles, new_style)
return
end
end
table.insert(ret.expressed_styles, {
tag = hidden_tag,
styles = {new_style},
})
end
-- For each type of difference, figure out if the difference exists in any of the given respellings. We do this by
-- generating the pronunciation for the dialect "distincion-lleismo", for each respelling. In the process of
-- generating the pronunciation for a given respelling, it computes how the other dialects for that respelling
-- differ. Then we take the union of these differences across the respellings.
dodialect(ret, "distincion-lleismo")
local differences = {}
for _, difftype in ipairs { "distincion_different", "lleismo_different", "need_rioplat", "sheismo_different", "need_quito", "need_yucatan" } do
for _, pronun in ipairs(ret.pronun["distincion-lleismo"]) do
if pronun.differences[difftype] then
differences[difftype] = true
end
end
end
local distincion_different = differences.distincion_different
local lleismo_different = differences.lleismo_different
need_rioplat = differences.need_rioplat
local sheismo_different = differences.sheismo_different
need_quito = differences.need_quito
need_yucatan = differences.need_yucatan
-- Now, based on the observed differences, figure out how to combine the individual dialects into styles and
-- style groups.
if not distincion_different and not lleismo_different then
if not need_rioplat then
if not need_yucatan then
express_style(false, false, "distincion-lleismo", "distincion-seseo-lleismo-yeismo")
else
express_style(false, "除<<墨西哥>>北部、<<尤卡坦>>和<<中美洲>>(<<巴拿馬>>除外)之外的各地", "distincion-lleismo", "distincion-seseo-lleismo-yeismo")
end
else
if not need_yucatan then
express_style(false, "除<<{{w|潘帕斯}}>>、<<阿根廷>>南部和<<烏拉圭>>之外的各地", "distincion-lleismo",
"distincion-seseo-lleismo-yeismo")
else
express_style(false, "除<<{{w|潘帕斯}}>>、<<阿根廷>>南部、<<烏拉圭>>、<<墨西哥>>北部、<<尤卡坦>>和<<中美洲>>(<<巴拿馬>>除外)之外的各地", "distincion-lleismo", "distincion-seseo-lleismo-yeismo")
end
end
elseif distincion_different and not lleismo_different then
express_style("<<赤道幾內亞>>、<<西班牙>>", "<<赤道幾內亞>>、<<西班牙>>", "distincion-lleismo", "distincion-lleismo-yeismo")
if not need_rioplat and not need_yucatan then
express_style("<<拉丁美洲>>、<<菲律賓>>", "<<拉丁美洲>>、<<菲律賓>>", "seseo-lleismo", "seseo-lleismo-yeismo")
else
express_style("<<拉丁美洲>>、<<菲律賓>>", "<<拉丁美洲>>大部分地區、<<菲律賓>>", "seseo-lleismo", "seseo-lleismo-yeismo")
end
elseif not distincion_different and lleismo_different then
express_style(false, "<<赤道幾內亞>>、<<拉丁美洲>>的大部分地區和<<西班牙>>", "distincion-yeismo", "distincion-seseo-yeismo")
express_style(false, "<<西班牙北部>><<鄉村>>、<<安第斯>>北部和中部(<<厄瓜多爾>>中部除外)、<<玻利維亞>>、<<巴拉圭>>、<<阿根廷>>東北部、<<菲律賓>>", "distincion-lleismo", "distincion-seseo-lleismo")
else
express_style("<<赤道幾內亞>>、<<西班牙>>", "<<赤道幾內亞>>、<<西班牙>>大部分地區", "distincion-yeismo")
express_style("<<拉丁美洲>>", "<<拉丁美洲>>大部分地區", "seseo-yeismo")
express_style("<<赤道幾內亞>>、<<西班牙>>", "<<西班牙北部>><<鄉村>>", "distincion-lleismo")
express_style("<<拉丁美洲>>", "<<安第斯>>北部和中部(<<厄瓜多爾>>中部除外)、<<玻利維亞>>、<<巴拉圭>>、<<阿根廷>>東北部、<<菲律賓>>", "seseo-lleismo")
end
if need_rioplat then
if lleismo_different then
local hidden_tag = distincion_different and "<<拉丁美洲>>" or false
if sheismo_different then
if not need_quito then
express_style(hidden_tag, "<<布宜諾斯艾利斯>>及其周邊", "rioplatense-sheismo", "seseo-rioplatense-sheismo")
express_style(hidden_tag, "<<厄瓜多爾>>中部、<<聖地亞哥-德爾埃斯特羅>>及其周邊、<<{{w|潘帕斯}}>>的其他地區及<<阿根廷>>南部、<<烏拉圭>>", "rioplatense-zheismo", "seseo-rioplatense-zheismo-quito")
else
express_style(hidden_tag, "<<厄瓜多爾>>中部、<<聖地亞哥-德爾埃斯特羅>>及其周邊", "quito", "seseo-quito")
express_style(hidden_tag, "<<布宜諾斯艾利斯>>及其周邊", "rioplatense-sheismo", "seseo-rioplatense-sheismo")
express_style(hidden_tag, "<<{{w|潘帕斯}}>>的其他地區及<<阿根廷>>南部、<<烏拉圭>>", "rioplatense-zheismo", "seseo-rioplatense-zheismo")
end
else
express_style(hidden_tag, "<<{{w|潘帕斯}}>>及<<阿根廷>>南部、<<烏拉圭>>", "rioplatense-sheismo", "seseo-rioplatense-sheismo-zheismo")
end
else
local hidden_tag = distincion_different and "<<拉丁美洲>>、<<菲律賓>>" or false
if sheismo_different then
express_style(hidden_tag, "<<布宜諾斯艾利斯>>及其周邊", "rioplatense-sheismo", "seseo-rioplatense-sheismo")
express_style(hidden_tag, "<<{{w|潘帕斯}}>>的其他地區及<<阿根廷>>南部、<<烏拉圭>>", "rioplatense-zheismo", "seseo-rioplatense-zheismo")
else
express_style(hidden_tag, "<<{{w|潘帕斯}}>>及<<阿根廷>>南部、<<烏拉圭>>", "rioplatense-sheismo", "seseo-rioplatense-sheismo-zheismo")
end
end
end
if need_yucatan then
local hidden_tag = distincion_different and (lleismo_different and "<<拉丁美洲>>" or "<<拉丁美洲>>、<<菲律賓>>") or false
express_style(hidden_tag, "<<墨西哥>>北部、<<尤卡坦>>、<<中美洲>>(<<巴拿馬>>除外)", "yucatan")
end
-- If only one style group, don't indicate the style.
-- Not clear we want this in reality.
--if #ret.expressed_styles == 1 then
-- ret.expressed_styles[1].tag = false
-- if #ret.expressed_styles[1].styles == 1 then
-- ret.expressed_styles[1].styles[1].tag = false
-- end
--end
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
else
style_group.formatted, style_group.formatted_len =
format_style(style_group.tag, style_group.styles[1], i == 1)
for j, style in ipairs(style_group.styles) do
style.formatted, style.formatted_len =
format_style(style.tag, style, i == 1 and j == 1)
end
end
end
local maxlen = 0
for i, style_group in ipairs(expressed_styles) do
local this_len = style_group.formatted_len
if #style_group.styles > 1 then
for _, style in ipairs(style_group.styles) do
this_len = math.max(this_len, style.formatted_len)
end
end
maxlen = math.max(maxlen, this_len)
end
local lines = {}
local need_major_hack = false
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
table.insert(lines, style_group.formatted)
need_major_hack = false
else
local inline = '\n<div class="vsShow" style="display:none">\n' .. style_group.formatted .. "</div>"
local full_prons = {}
for _, style in ipairs(style_group.styles) do
table.insert(full_prons, style.formatted)
end
local full = '\n<div class="vsHide">\n' .. table.concat(full_prons, "\n") .. "</div>"
local em_length = math.floor(maxlen * 0.68) -- from [[Module:grc-pronunciation]]
table.insert(lines, '<div class="vsSwitcher" data-toggle-category="pronunciations" style="width: ' .. em_length .. 'em; max-width:100%;"><span class="vsToggleElement" style="float: right;"> </span>' .. inline .. full .. "</div>")
need_major_hack = true
end
end
-- major hack to get bullets working on the next line after a div box
return table.concat(lines, "\n") .. (need_major_hack and "\n<span></span>" or "")
end
local function dodialect_pronun(args, ret, dialect)
ret.pronun[dialect] = {}
for i, term in ipairs(args.terms) do
local phonemic, phonetic, differences
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
differences = construct_default_differences(dialect)
else
phonemic = export.IPA(term.term, dialect, false)
phonetic = export.IPA(term.term, dialect, true)
differences = phonemic.differences
phonemic = phonemic.text
phonetic = phonetic.text
end
ret.pronun[dialect][i] = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = term.refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
differences = differences,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret, dialect)
dodialect_pronun(args, ret, dialect)
end
local ret = express_all_styles(args.style, this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]], and also create an approximation of the formatted version so that we can
-- compute the appropriate width of the HTML switcher div box that holds the different per-dialect variants.
-- NOTE: The code below constructs the formatted approximation out-of-order in some cases but that doesn't
-- currently matter because we assume all characters have the same width. If we change the width computation
-- in a way that requires the correct order, we need changes to the code below.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to right accent qualifiers if last one
local aas = pronun.aa
if j == #expressed_style.pronun and tag then
if aas then
aas = m_table.deepCopy(aas)
table.insert(aas, tag)
else
aas = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
ins(bracket_pron)
end
local last_pronun = #pronunciations
if pronun.q then
pronunciations[first_pronun].q = pronun.q
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if aas then
pronunciations[last_pronun].aa = aas
end
if pronun.q or pronun.qq or pronun.a or aas then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = pronun.q,
qq = pronun.qq,
a = pronun.a,
aa = aas,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre ..
m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- External entry point for {{es-IPA}}.
function export.show(frame)
local params = {
[1] = {},
["pre"] = {},
["post"] = {},
["ref"] = {},
["style"] = {},
["bullets"] = {type = "number", default = 1},
}
local parargs = frame:getParent().args
local args = require(parameters_module).process(parargs, params)
local text = args[1] or mw.loadData("Module:headword/data").pagename
args.terms = {{term = text}}
local ret = generate_pronun(args)
return ret.text
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no
-- hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡ʃ", "tʃ")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CFLEX then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. [[Austria-Hungría]])
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun[dialect]) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
qq = rhyme.qq,
a = rhyme.a,
aa = rhyme.aa,
differences = construct_default_differences(dialect),
})
end
end
local q_qq_inline_modifier_spec = {
store = "insert-flattened",
type = "qualifier",
}
local a_aa_inline_modifier_spec = {
store = "insert-flattened",
type = "labels",
}
local ref_inline_modifier_spec = {
store = "insert-flattened",
item_dest = "refs",
type = "references",
}
-- Parse a pronunciation modifier in `arg`, the argument portion in an inline modifier (after the prefix), which
-- specifies a pronunciation property such as rhyme, hyphenation/syllabification, homophones or audio. The argument
-- can itself have inline modifiers, e.g. <audio:Foo.ogg<a:Colombia>>. The allowed inline modifiers are specified
-- by `param_mods` (of the format expected by `parse_inline_modifiers()`); in addition to any modifiers specified
-- there, the modifiers <q:...>, <qq:...>, <a:...>, <aa:...> and <ref:...> are always accepted (and can be repeated).
-- `generate_obj` and `parse_err` are like in `parse_inline_modifiers()` and specify respectively a function to
-- generate the object into which modifier properties are stored given the non-modifier part of the argument, and
-- a function to generate an error message (given the message). Normally, a comma-separated list of pronunciation
-- properties is accepted and parsed, where each element in the list can have its own inline modifiers and where
-- no spaces are allowed next to the commas in order for them to be recognized as separators. If `no_split_on_comma`
-- is given, only a single pronunciation property is accepted. In all cases, however, the return value is a list
-- of property objects (when `no_split_on_comma` is given, the return value is a one-element list).
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
if arg:find("<") then
param_mods.q = q_qq_inline_modifier_spec
param_mods.qq = q_qq_inline_modifier_spec
param_mods.a = a_aa_inline_modifier_spec
param_mods.aa = a_aa_inline_modifier_spec
param_mods.ref = ref_inline_modifier_spec
local retval = require(parse_utilities_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
if no_split_on_comma then
retval = {retval}
end
return retval
elseif no_split_on_comma then
return {generate_obj(arg)}
else
local retval = {}
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
return retval
end
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
type = "number",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- [[Module:links]] expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
-- No tr=, ts=, or sc=; doesn't make sense for Spanish.
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- [[Module:links]] expects the genders in "genders".
item_dest = "genders",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, caption = arg:match("^(.-)%s*#%s*(.*)$")
file = file or arg
return {file = file, caption = caption}
end
local function parse_audio(arg, parse_err)
local param_mods = {
IPA = {
sublist = true,
},
text = {},
t = {
item_dest = "gloss",
},
-- No tr=, ts=, or sc=; doesn't make sense for Spanish.
gloss = {},
pos = {},
-- No alt=; text= already goes in alt=.
lit = {},
-- No id=; text= already goes in alt= and isn't normally linked.
g = {
item_dest = "genders",
sublist = true,
},
bad = {},
}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
local retvals = parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
local retval = retvals[1]
retval.lang = lang
local textobj = require(audio_module).construct_audio_textobj(retval)
retval.text = textobj
retval.gloss = nil
retval.pos = nil
retval.lit = nil
retval.genders = nil
return retval
end
-- External entry point for {{es-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {convert = parse_rhyme},
["hyph"] = {convert = parse_hyph},
["hmp"] = {convert = parse_homophone},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require(parameters_module).process(parargs, params)
local pagename = args.pagename or mw.loadData(headword_data_module).pagename
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local overall_rhyme = args.rhyme
local overall_hyph = args.hyph
local overall_hmp = args.hmp
local overall_audio
if args.audio then
-- We can't specify parse_audio() as a `convert` function because it needs access to `pagename` (i.e. another
-- parameter).
overall_audio = {}
for i, audio in ipairs(args.audio) do
local function parse_err(msg)
error(("%s: parameter audio%s=%s"):format(msg, i == 1 and "" or i, audio))
end
local parsed_audio = parse_audio(audio, parse_err, pagename)
table.insert(overall_audio, parsed_audio)
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
style = { overall = true },
bullets = {
overall = true,
type = "number",
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert",
convert = function(arg, parse_err)
return parse_audio(arg, parse_err, pagename)
end,
},
ref = ref_inline_modifier_spec,
q = q_qq_inline_modifier_spec,
qq = q_qq_inline_modifier_spec,
a = a_aa_inline_modifier_spec,
aa = a_aa_inline_modifier_spec,
}
local parsed = require(parse_utilities_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "=" .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic or term.raw_phonetic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
-- It's possible the pronunciation for a passed-in dialect was never generated. This happens e.g. with
-- {{es-pr|cebolla<style:seseo>}}. The initial call to generate_pronun() fails to generate a pronunciation
-- for the dialect 'distinction-yeismo' because the pronunciation of 'cebolla' differs between distincion
-- and seseo and so the seseo style restriction rules out generation of pronunciation for distincion
-- dialects (other than 'distincion-lleismo', which always gets generated so as to determine on which axes
-- the dialects differ). However, when generating the rhyme, it is based only on -olla, whose pronunciation
-- does not differ between distincion and seseo, but does differ between lleismo and yeismo, so it needs to
-- generate a yeismo-specific rhyme, and 'distincion-yeismo' is the representative dialect for yeismo in the
-- situation where distincion and seseo do not have distinct results (based on the following line in
-- express_all_styles()):
-- express_style(false, "<<Equatorial Guinea>>, most of <<Latin America>> and <<Spain>>", "distincion-yeismo", "distincion-seseo-yeismo")
-- In this case we need to generate the missing overall pronunciation ourselves since we need it to generate
-- the dialect-specific rhyme pronunciation.
if not parsed.pronun.pronun[dialect] then
dodialect_pronun(parsed, parsed.pronun, dialect)
end
for _, pronun in ipairs(parsed.pronun.pronun[dialect]) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun[dialect]) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
local rhyme_diffs = nil
if dialect == "distincion-lleismo" then
rhyme_diffs = {}
if rhyme:find("θ") then
rhyme_diffs.distincion_different = true
end
if rhyme:find("ʎ") then
rhyme_diffs.lleismo_different = true
if rhyme:find("ɟ") then
rhyme_diffs.need_quito = true
end
end
if rfind(rhyme, "[ʎɟ]") then
rhyme_diffs.sheismo_different = true
rhyme_diffs.need_rioplat = true
if rfind(rhyme, V .. "[ʎɟ]" .. V) then
rhyme_diffs.need_yucatan = true
end
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = rhyme,
num_syl = {num_syl},
differences = rhyme_diffs,
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(parsed.style, dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret, dialect)
end
parsed.rhyme = express_all_styles(parsed.style, this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret, dialect)
end
overall_rhyme = express_all_styles(parsed.style, dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
aa = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require(rhymes_module).format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
table.insert(formatted_for_len_parts, require(decorations_module).format_decorations {
lang = lang,
text = "",
q = pronun.q,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require(hyphenation_module).format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require(homophones_module).format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio(audio)
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
f0p6idmvo3y0ur0drxuvdaovxfaokhv
Module:It-pronunciation
828
1377057
9909078
8797833
2026-09-29T07:26:40Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909078
Scribunto
text/plain
--[=[
This module implements the templates {{it-pr}} and {{it-IPA}}.
Author: benwing2
FIXME:
1. Support raw pronunciations in {{it-pr}}. (DONE)
2. ahimè should generate aj.mɛ not a.i.mɛ. (DONE)
3. oriuòlo should divide as o.riuò.lo, both in phonemic and hyphenation. (DONE)
4. Handle <hmp:...> for homophones. (DONE)
5. Raw pronunciations need to use raw:, esp. for [...] phonetic pronunciation. (DONE)
6. Handle hyphenation of Uppsala, massmediale correctly. (DONE)
7. Homophone may end up before rhyme/hyphenation when it should come after, e.g. in [[hanno]].
8. Cases like [[Katmandu]] with respelling ''Katmandù'' should auto-hyphenate.
]=]
local export = {}
local force_cat = false -- for testing
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local com = require("Module:it-common")
local put_module = "Module:parse utilities"
local put -- replaced with module reference as needed
local strutils = require("Module:string utilities")
local u = m_str_utils.char
local rfind = m_str_utils.find
local rsub = com.rsub
local rsubb = com.rsubb
local rsubn = m_str_utils.gsub
local rsub_repeatedly = com.rsub_repeatedly
local rmatch = m_str_utils.match
local toNFC = mw.ustring.toNFC
local ulower = m_str_utils.lower
local usub = m_str_utils.sub
local ulen = m_str_utils.len
local pattern_escape = strutils.pattern_escape
local replacement_escape = strutils.replacement_escape
local split = strutils.split
local lang = require("Module:languages").getByCode("it")
-- Temporarily substitutes for comma+space.
local TEMP1 = u(0xFFF0)
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local WORDDIV = u(0xFFF1) -- used to represent a user-specific word divider (.) so we won't change it
local TEMP_Z = u(0xFFF2)
local TEMP_S = u(0xFFF3)
local TEMP_H = u(0xFFF4)
local TEMP_X = u(0xFFF5)
local glide = "jwJW"
local liquid = "lrLR"
local tie = "‿⁀'"
local W = "[" .. glide .. "]"
local W_OR_TIE = "[" .. glide .. tie .. "]"
-- We include both phonemic and spelling forms of vowels and both lowercase and uppercase
-- for flexibility in applying at various stages of the transformation from spelling -> phonemes.
local vowel_not_high = "aeɛoɔøöüAEƐOƆØÖÜ"
local vowel_not_i = vowel_not_high .. "uU"
local vowel_not_u = vowel_not_high .. "iyIY"
local vowel = vowel_not_high .. "iuyIUY"
local V = "[" .. vowel .. "]"
local V_NOT_HIGH = "[" .. vowel_not_high .. "]"
local V_NOT_I = "[" .. vowel_not_i .. "]"
local V_NOT_U = "[" .. vowel_not_u .. "]"
local VW = "[" .. vowel .. glide .. "]"
local NV = "[^" .. vowel .. "]"
local charsep_not_tie = com.accent .. "." .. SYLDIV
local charsep_not_tie_c = "[" .. charsep_not_tie .. "]"
local charsep = charsep_not_tie .. tie
local charsep_c = "[" .. charsep .. "]"
local wordsep_not_tie = charsep_not_tie .. " #"
local wordsep = charsep .. " #"
local wordsep_c = "[" .. wordsep .. "]"
local cons_guts = "^" .. vowel .. wordsep .. "_" -- guts of consonant class
local C = "[" .. cons_guts .. "]" -- consonant
local C_NOT_SRZ = "[" .. cons_guts .. "srzSRZ]" -- consonant not including srz
local C_NOT_SIBILANT_OR_R = "[" .. cons_guts .. "rszʃʒʦʣʧʤRSZ" .. TEMP_S .. TEMP_Z .. "]" -- consonant not including r or sibilant
local C_NOT_H = "[" .. cons_guts .. "hH]" -- consonant not including h
local C_OR_EOW_NOT_GLIDE_LIQUID = "[^" .. vowel .. charsep .. " _" .. glide .. liquid .. "]" -- consonant not lrjw, or end of word
local C_OR_TIE = "[^" .. vowel .. wordsep_not_tie .. "_]" -- consonant or tie (‿⁀')
local front = "eɛij"
local front_c = "[" .. front .. "]"
local voiced_C_c = "[bdglmnrvʣʤŋʎɲ]"
local pron_sign = "#!*°"
local pron_sign_c = "[" .. pron_sign .. "]"
local pron_sign_or_punc = pron_sign .. "?|,"
local pron_sign_or_punc_c = "[" .. pron_sign_or_punc .. "]"
local full_affricates = { ["ʦ"] = "t͡s", ["ʣ"] = "d͡z", ["ʧ"] = "t͡ʃ", ["ʤ"] = "d͡ʒ" }
local recognized_suffixes = {
-- -(m)ente, -(m)ento
{"izzamento", "iddzaménto"}, -- must precede -mento below
{"ment([eo])", "mént%1"}, -- must precede -ente/o below; must follow -izzamento above
{"ent([eo])", "ènt%1"}, -- must follow -mente/o above
-- verbs
{"izzare", "iddzàre"}, -- must precede -are below
{"izzarsi", "iddzàrsi"}, -- must precede -arsi below
{"([ai])re", "%1" .. com.GR .. "re"}, -- must follow -izzare above
{"([ai])rsi", "%1" .. com.GR .. "rsi"}, -- must follow -izzarsi above
-- nouns
{"izzatore", "iddzatóre"}, -- must precede -tore below
{"([st])ore", "%1óre"}, -- must follow -izzatore above
{"izzatrice", "iddzatrìce"}, -- must precede -trice below
{"trice", "trìce"}, -- must follow -izzatrice above
{"izzazione", "iddzatsióne"}, -- must precede -zione below
{"zione", "tsióne"}, -- must precede -one below and follow -izzazione above
{"one", "óne"}, -- must follow -zione above
{"acchio", "àcchio"},
{"acci([ao])", "àcci%1"},
{"([aiu])ggine", "%1" .. com.GR .. "ggine"},
{"aggio", "àggio"},
{"([ai])gli([ao])", "%1" .. com.GR .. "gli%2"},
{"ai([ao])", "ài%1"},
{"([au])me", "%1" .. com.GR .. "me"},
{"([ae])nza", "%1" .. com.GR .. "ntsa"},
{"ario", "àrio"},
{"([st])orio", "%1òrio"},
{"astr([ao])", "àstr%1"},
{"ell([ao])", "èll%1"},
-- exceptions to the following: antimatèria, artèria, cattivèria, fèria, Ibèria, Libèria, matèria, misèria, Nigèria, Sibèria, Valèria
{"eria", "erìa"},
{"etta", "étta"},
-- do not include -etto, both ètto and étto are common
{"ezza", "éttsa"},
{"ficio", "fìcio"},
{"ier([ao])", "ièr%1"},
-- do not include -iere, lots of verbs in unstressed -iere
{"ifero", "ìfero"},
{"ismo", "ìsmo"},
{"ista", "ìsta"},
{"izi([ao])", "ìtsi%1"},
{"logia", "logìa"},
-- do not include -otto, both òtto and ótto are common
{"tudine", "tùdine"},
{"ura", "ùra"},
{"([^aeo])uro", "%1ùro"},
-- adjectives
{"izzante", "iddzànte"}, -- must precede -ante below
{"ante", "ànte"}, -- must follow -izzante above
{"izzando", "iddzàndo"}, -- must precede -ando below
{"([ae])ndo", "%1" .. com.GR .. "ndo"}, -- must follow -izzando above
{"izzabile", "iddzàbile"}, -- must precede -abile below
{"([ai])bile", "%1" .. com.GR .. "bile"}, --must follow -izzabile above
{"ale", "àle"},
{"([aeiou])nico", "%1" .. com.GR .. "nico"},
{"([ai])stic([ao])", "%1" .. com.GR .. "stic%2"},
{"izzat([ao])", "iddzàt%1"}, -- must precede -at[ao] below
-- exceptions to the following: àbato, àcato, acròbata, àgata, apòstata, àstato, cìato, fégato, omeòpata,
-- sàb(b)ato, others?
{"at([ao])", "àt%1"}, -- must follow -izzat[ao] above
-- exceptions to the following: (s)còmputo, (pre)scòrbuto, tànguto; cùscuta, dìsputa, rècluta, lui vàluta
{"([^aeo])ut([ao])", "%1ùt%2"},
{"([ae])tic([ao])", "%1" .. com.GR .. "tic%2"},
{"ense", "ènse"},
{"esc([ao])", "ésc%1"},
{"evole", "évole"},
-- FIXME: Systematic exceptions to the following in 3rd plural present tense verb forms
{"ian([ao])", "iàn%1"},
{"iv([ao])", "ìv%1"},
{"oide", "òide"},
{"oso", "óso"},
}
local unstressed_words = m_table.listToSet {
"il", "lo", "la", "i", "gli", "le", -- definite articles
"un", -- indefinite articles
"mi", "ti", "si", "ci", "vi", "li", -- object pronouns
"me", "te", "se", "ce", "ve", "ne", -- conjunctive object pronouns
"e", "ed", "o", "od", -- conjunctions
"ho", "hai", "ha", -- forms of [[avere]]
"chi", "che", "non", -- misc particles
"di", "del", "dei", -- prepositions
"a", "ad", "al", "ai",
"da", "dal", "dai",
"in", "nel", "nei",
"con", "col", "coi",
"su", "sul", "sui",
"per", "pei",
"tra", "fra",
}
-- Canonicalize multiple spaces and remove leading and trailing spaces.
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
-- Remove word-final accents on monosyllabic words. NOTE: `text` on entry must be decomposed using decompose().
local function remove_final_monosyllabic_accents(text)
local words = com.split_but_rejoin_affixes(text)
for i, word in ipairs(words) do
if (i % 2) == 1 then -- an actual word, not a separator
word = rsub(word, "^(" .. NV .. "*" .. V .. ")" .. com.accent_c .. "$", "%1")
words[i] = word
end
end
return table.concat(words)
end
-- Return true if all words in `term` have vowels. NOTE: `term` on entry must be decomposed using com.decompose().
local function all_words_have_vowels(term)
local words = com.split_but_rejoin_affixes(term)
for i, word in ipairs(words) do
if (i % 2) == 1 and not rfind(word, V) then -- an actual word, not a separator; check for a vowel
return false
end
end
return true
end
-- Convert respelling conventions back to the original spelling. This does not affect accents or syllable dividers.
local function convert_respelling_to_original(respelling)
-- discard second return value
respelling = respelling:gsub("ddz", "zz"):gsub("tts", "zz"):gsub("dz", "z"):gsub("ts", "z")
:gsub("Dz", "Z"):gsub("Ts", "Z"):gsub("%[([szh])%]", "%1"):gsub("%[w%]", "u")
:gsub("ʎi", "gli"):gsub("ʎ", "gli")
return respelling
end
-- Given raw respelling, canonicalize it and apply auto-accenting where warranted. This does the following:
-- (1) Convert abbreviated specs like ^à to the appropriate accented page name (hence the page name must be passed in).
-- (2) Decompose the text, normalize áíú and similar to àìù, convert commas and em/en dashes to foot boundaries and
-- similarly with other punctuation.
-- (3) Apply suffix respellings as appropriate, e.g -zione -> -tsióne.
-- (4) Auto-accent monosyllabic and bisyllabic words when possible.
-- (5) Throw an error if non-unstressed words remain without accents on them.
local function canonicalize_and_auto_accent(text, pagename)
text = com.decompose(text)
pagename = com.decompose(pagename)
-- First apply abbrev spec e.g. ^à or ^Ó if given.
if text == "+" then
text = pagename
elseif rfind(text, "^%[[wxzsh]%]$") or text == "[tʃ]" or text == "[dʒ]" then
-- Don't do anything to bare use of [C] notation; don't interpret as substitution.
else
if rfind(text, "^%^[aeiouAEIOU]" .. com.GR .. "$") or rfind(text, "^%^[eoEO]" .. com.AC .. "$") then
text = "[" .. text .. "]"
end
-- Implement substitution notation.
if rfind(text, "^%[.*%]$") then
local subs = split(rmatch(text, "^%[(.*)%]$"), ",", true)
text = pagename
local function err(msg)
error(msg .. ": " .. text)
end
for _, sub in ipairs(subs) do
if rfind(sub, "^%^[aeiouAEIOU]" .. com.GR .. "$") or rfind(sub, "^%^[eoEO]" .. com.AC .. "$") then
-- single vowel spec
local abbrev_text = sub
if text:find(" ") or text:find("[^ ]%-[^ ]") then
err("With abbreviated vowel spec " .. abbrev_text .. ", the processed page name should be a single word")
end
if rfind(text, com.quality_c) then
err("With abbreviated vowel spec " .. abbrev_text .. ", the processed page name should not already have an accent")
end
local vowel_count = ulen(rsub(text, NV, ""))
local abbrev_sub = abbrev_text:gsub("%^", "")
local abbrev_vowel = usub(abbrev_sub, 1, 1)
if vowel_count == 0 then
err("Abbreviated spec " .. abbrev_text .. " can't be used with nonsyllabic word")
elseif vowel_count == 1 then
local before, vow, after = rmatch(text, "^(.*)(" .. V .. ")(" .. NV .. "*)$")
if not before then
err("Internal error: Couldn't match monosyllabic word")
end
if abbrev_vowel ~= vow then
err("Abbreviated spec " .. abbrev_text .. " doesn't match vowel " .. vow)
end
text = before .. abbrev_sub .. after
else
local before, penultimate, after = rmatch(text, "^(.-)(" .. V .. ")(" .. NV .. "*" .. V .. NV .. "*)$")
if not before then
err("Internal error: Couldn't match multisyllabic word")
end
local before2, antepenultimate, after2 = rmatch(before, "^(.-)(" .. V .. ")(" .. NV .. "*)$")
if abbrev_vowel ~= penultimate and abbrev_vowel ~= antepenultimate then
err("Abbreviated spec " .. abbrev_text .. " doesn't match penultimate vowel " ..
penultimate .. (antepenultimate and " or antepenultimate vowel " ..
antepenultimate or ""))
end
if penultimate == antepenultimate then
err("Can't use abbreviated spec " .. abbrev_text .. " here because penultimate and " ..
"antepenultimate are the same")
end
if abbrev_vowel == antepenultimate then
text = before2 .. abbrev_sub .. after2 .. penultimate .. after
elseif abbrev_vowel == penultimate then
text = before .. abbrev_sub .. after
else
err("Internal error: abbrev_vowel from abbrev_text " .. abbrev_text ..
" didn't match any vowel or glide")
end
end
else
local from, escaped_from, to, escaped_to
if sub:find(":") then
from, to = rmatch(sub, "^(.-):(.*)$")
else
to = sub
from = convert_respelling_to_original(to)
from = com.remove_accents(from)
from = from:gsub("[.*]", "")
end
if rfind(from, "^~") then
-- whole-word match
from = rmatch(from, "^~(.*)$")
escaped_from = "%f[%a]" .. pattern_escape(from) .. "%f[%A]"
else
escaped_from = pattern_escape(from)
end
escaped_to = replacement_escape(to)
local subbed_text, nsubs = rsubn(text, escaped_from, escaped_to)
if nsubs == 0 then
err(("Substitution spec %s -> %s didn't match processed pagename"):format(from, to))
elseif nsubs > 1 then
err(("Substitution spec %s -> %s matched multiple substrings in processed pagename, add more context"):format(from, to))
else
text = subbed_text
end
end
end
end
end
text = rsub(text, "([aiuöüAIUÖÜ])" .. com.AC, "%1" .. com.GR) -- áíú -> àìù
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub_repeatedly(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub_repeatedly(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
text = rsub(text, "[!?]", "") -- eliminate remaining punctuation
text = canon_spaces(text)
local words = com.split_but_rejoin_affixes(text)
for i, word in ipairs(words) do
if (i % 2) == 1 then -- an actual word, not a separator
local function err(msg)
error(msg .. ": " .. words[i])
end
local is_prefix = word:find("%-$")
local is_suffix = word:find("^%-")
if not is_prefix then
if not rfind(word, com.quality_c) then
-- Apply suffix respellings.
for _, suffix_pair in ipairs(recognized_suffixes) do
local orig, respelling = unpack(suffix_pair)
local replaced
word, replaced = rsubb(word, orig .. "$", respelling)
if replaced then
-- Decompose again because suffix replacements may have accented chars.
word = com.decompose(word)
break
end
end
end
-- Auto-stress some monosyllabic and bisyllabic words. Don't auto-stress inherently unstressed words
-- (including those with a * at the end of them indicating syntactic gemination).
if not unstressed_words[rsub(word, "%*$", "")] and not rfind(word, "[" .. com.AC .. com.GR .. com.DOTOVER .. "]") then
vowel_count = ulen(rsub(word, NV, ""))
if vowel_count > 2 then
err("With more than two vowels and an unrecognized suffix, stress must be explicitly given")
elseif not is_suffix or vowel_count == 2 then -- don't try to stress suffixes with only one vowel
local before, vow, after = rmatch(word, "^(.-)(" .. V .. ")(.*)$")
if before then
if rfind(vow, "^[eoEO]$") then
err("When stressed vowel is e or o, it must be marked é/è or ó/ò to indicate quality")
end
word = before .. vow .. com.GR .. after
end
end
end
end
words[i] = word
end
end
return words
end
function export.to_phonemic(text, pagename)
local orig_respelling = text
local words = canonicalize_and_auto_accent(text, pagename)
text = table.concat(words)
local canon_respelling = text
text = ulower(text)
text = rsub(text, com.CFLEX, "") -- eliminate circumflex over î, etc.
text = rsub(text, "y", "i")
text = rsub_repeatedly(text, "([^ ])'([^ ])", "%1‿%2") -- apostrophe between letters is a tie
text = rsub(text, "(" .. C .. ")'$", "%1‿") -- final apostrophe after a consonant is a tie, e.g. [[anch']]
text = rsub(text, "(" .. C .. ")' ", "%1‿ ") -- final apostrophe in non-utterance-final word is a tie
text = rsub(text, "'", "") -- other apostrophes just get removed, e.g. [['ndragheta]], [[ca']].
-- For now, use a special marker of syntactic gemination at beginning of word; later we will
-- convert to ‿ and remove the space.
text = rsub(text, "%*([ %-])(" .. C .. ")", "%1⁀%2")
if rfind(text, "%*[ %-]") then
error("* for syntactic gemination can only be used when the next word begins with a consonant: " .. canon_respelling)
end
local words = com.split_but_rejoin_affixes(text)
for i, word in ipairs(words) do
if (i % 2) == 1 then -- an actual word, not a separator
-- Words marked with an acute or grave (quality marker) not followed by an indicator of secondary stress
-- or non-stress, and not marked with com.DOTOVER (unstressed word), get primary stress.
if not word:find(com.DOTOVER) then
word = rsub(word, "(" .. com.quality_c .. ")([^" .. com.DOTUNDER .. com.LINEUNDER .. "])", "%1ˈ%2")
word = rsub(word, "(" .. com.quality_c .. ")$", "%1ˈ")
end
-- Apply quality markers: è -> ɛ, ò -> ɔ
word = rsub(word, "[eo]" .. com.GR, {
["e" .. com.GR] = "ɛ",
["o" .. com.GR] = "ɔ",
})
-- Eliminate quality markers and com.DOTOVER/com.DOTUNDER, which have served their purpose.
word = rsub(word, "[" .. com.quality .. com.DOTOVER .. com.DOTUNDER .. "]", "")
-- com.LINEUNDER means secondary stress.
word = rsub(word, com.LINEUNDER, "ˌ")
-- Make prefixes unstressed. Primary stress markers become secondary.
if word:find("%-$") then
word = rsub(word, "ˈ", "ˌ")
end
words[i] = word
end
end
text = table.concat(words)
-- Convert hyphens to spaces, to handle [[Austria-Hungria]], [[franco-italiano]], etc.
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
-- Random consonant substitutions.
text = rsub(text, "%[w%]", "w") -- [w] means /w/ when the spelling is ⟨u⟩, esp. in ⟨ui⟩ sequences. This helps with hyphenation.
text = rsub(text, "%[x%]", TEMP_X) -- [x] means /x/
text = rsub(text, "#ex(" .. V .. ")", "eg[z]%1")
text = text:gsub("x", "ks"):gsub("ck", "k"):gsub("sh", "ʃ")
text = rsub(text, TEMP_X, "x")
text = rsub(text, "%[z%]", TEMP_Z) -- [z] means /z/
text = rsub(text, "%[s%]", TEMP_S) -- [z] means /s/
text = rsub(text, "%[h%]", TEMP_H) -- [h] means /h/
-- ci, gi + vowel
-- Do ci, gi + e, é, è sometimes contain /j/?
text = rsub(text,
"([cg])([cg]?)i(" .. V .. ")", function(c, double, v)
local out_cons
if c == "c" then
out_cons = "ʧ"
else
out_cons = "ʤ"
end
if double ~= "" then
if double ~= c then
error("Invalid sequence " .. c .. double .. ".")
end
out_cons = out_cons .. out_cons
end
return out_cons .. v
end)
-- Handle gl and gn.
text = rsub(text, "gn", "ɲ")
-- The vast majority of words beginning with gli- have /ɡl/ not /ʎ/ so don't substitute here, although we special-case
-- [[gli]]. Use ʎ exlicitly to get it in [[glielo]] and such.
text = rsub(text, "#gli#", "ʎi")
text = rsub_repeatedly(text, "([^#])gli(" .. V .. ")", "%1ʎ%2")
text = rsub_repeatedly(text, "([^#])gl(‿?i)", "%1ʎ%2")
-- Handle other cases of c, g.
text = rsub(text, "([cg])([cg]?)(h?)(" .. charsep_c .. "*.)", function(first, double, h, after)
-- Don't allow the combinations cg, gc. Or do something else?
if double ~= "" and double ~= first then
error("Invalid sequence " .. first .. double .. ".")
end
-- c, g is soft before e, i.
local cons
if rfind(after, front_c) and not rfind(h, "h") then
if first == "c" then
cons = "ʧ"
else
cons = "ʤ"
end
else
if first == "c" then
cons = "k"
else
cons = "g"
end
end
if double ~= "" then
cons = cons .. cons
end
return cons .. after
end)
-- sc before e, i is /ʃ/, doubled after a vowel.
text = text:gsub("sʧ", "ʃ")
text = rsub(text, "%[tʃ%]", "ʧ")
text = rsub(text, "%[dʒ%]", "ʤ")
text = rsub(text, "ddz", "ʣʣ")
text = rsub(text, "dz", "ʣ")
text = rsub(text, "tts", "ʦʦ")
text = rsub(text, "ts", "ʦ")
if rfind(text, "z") then
error("z must be respelled (d)dz or (t)ts: " .. canon_respelling)
end
-- ⟨qu⟩ represents /kw/.
text = text:gsub("qu", "kw")
-- ⟨gu⟩ (unstressed) + vowel represents /gw/.
text = text:gsub("gu(" .. V .. ")", "gw%1")
text = rsub(text, "q", "k") -- [[soqquadro]], [[qatariota]], etc.
-- Assimilate n before labial, including across word boundaries; DiPI marks pronunciations like
-- /ʤanˈpaolo/ for [[Gian Paolo]] as wrong. To prevent this, use _ or h between n and following labial.
text = rsub(text, "n(" .. wordsep_c .. "*[mpb])", "m%1")
-- Remove 'h' before converting vowels to glides; h should not block e.g. ahimè -> aj.mɛ.
text = text:gsub("h", "")
-- Unaccented u or i following a non-high vowel (with or without accent) is a semivowel. Exclude high vowels because
-- 'iu' should be interpreted as /ju/ not /iw/, and 'ii' (as in [[sii]]) and ''uu'' (as in [[duumvirato]]), should
-- remain as vowels. We handle ui specially. By preceding the conversion of glides before vowels, this works
-- correctly in the common sequence 'aiuo' e.g. [[guerraiuola]], [[acquaiuolo]]. Note that ci, gi + vowel, gli, qu
-- must be dealt with beforehand.
text = rsub_repeatedly(text, "(" .. V_NOT_HIGH .. com.accent_c .. "*)([iu])([^" .. com.accent .. "])", function(v, gl, acc)
return v .. (gl == "i" and "j" or "w") .. acc
end)
text = rsub_repeatedly(text, "(u" .. com.accent_c .. "*)i([^" .. com.accent .. "])", "%1j%2")
-- Unaccented i or u before another vowel is a glide. Separate into i and u cases to avoid converting ii or uu
-- except in the sequences iiV or uuV. Do i first so [[oriuolo]] -> or.jwɔ.lo.
text = rsub(text, "i(" .. V_NOT_I .. ")", "j%1")
text = rsub(text, "u(" .. V_NOT_U .. ")", "w%1")
-- Double consonant followed by end of word (e.g. [[stress]], [[staff]], [[jazz]]), or followed by a consonant
-- other than a glide or liquid (e.g. [[pullman]], [[Uppsala]]), should be reduced to single. Should not affect double
-- consonants between vowels or before glides (e.g. [[occhio]], [[acqua]]) or liquids ([[pubblico]], [[febbraio]]),
-- or words before a tie ([[mezz']], [[tutt']]).
text = rsub_repeatedly(text, "(" .. C .. ")%1(" .. charsep_not_tie_c .. "*" .. C_OR_EOW_NOT_GLIDE_LIQUID .. ")", "%1%2")
-- Between vowels (including glides), /ʃ ʎ ɲ t͡s d͡z/ are doubled (unless already doubled).
-- Not simply after a vowel; 'z' is not doubled in e.g. [[azteco]].
text = rsub_repeatedly(text, "(" .. VW .. com.stress_c .. "?" .. charsep_c .. "*)([ʦʣʃʎɲ])(" .. charsep_c .. "*" .. VW .. ")",
"%1%2%2%3")
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = rsub(text, "%.", SYLDIV)
-- Divide into syllables.
-- First remove '_', which has served its purpose of preventing context-dependent changes.
-- It should not interfere with syllabification.
text = text:gsub("_", "")
-- Also now convert ⁀ into a copy of the following consonant with the preceding space converted to ⁀
-- (which we will eventually convert to a tie symbol ‿, but for awhile we need to distinguish the two
-- because automatic syllabic gemination in final-stress words happens only in multisyllabic words,
-- and we don't want it to happen in monosyllabic words joined to a previous word by ⁀). We want to do
-- this after all consonants have been converted to IPA (so the correct consonant is geminated)
-- but before syllabification, since e.g. 'va* bène' should be treated as a single word 'va⁀b.bɛne' for
-- syllabification.
text = rsub(text, "# #⁀(‿?)(.)", "⁀%2%2")
-- Divide before the last consonant (possibly followed by a glide). We then move the syllable division marker
-- leftwards over clusters that can form onsets.
text = rsub_repeatedly(text, "(" .. V .. com.accent_c .. "*[‿⁀]?" .. C_OR_TIE .. "-)(" .. C .. W_OR_TIE .. "*" .. V .. ")", "%1.%2")
-- The previous regex divided VjjV as V.jjV but we want Vj.jV; same for VwwV. Correct this now.
text = rsub_repeatedly(text, "(" .. V .. com.accent_c .. "*[‿⁀]?)%.(" .. W .. ")([‿⁀]?)(%2[‿⁀]?" .. V .. ")", "%1%2.%3%4")
-- Existing hyphenations of [[atlante]], [[Betlemme]], [[genetliaco]], [[betlemita]] all divide as .tl,
-- and none divide as t.l. No examples of -dl- but it should be the same per
-- http://www.italianlanguageguide.com/pronunciation/syllabication.asp.
text = rsub(text, "([pbfvkgtd][‿⁀]?)%.([lr])", ".%1%2")
-- Italian appears to divide sCV as .sCV e.g. pé.sca for [[pesca]], and similarly for sCh, sCl, sCr. Exceptions are
-- ss, sr, sz and possibly others.
text = rsub(text, "(s[‿⁀]?)%.(" .. C_NOT_SIBILANT_OR_R .. ")", ".%1%2")
-- Several existing hyphenations divide .pn and .ps and Olivetti agrees. We do this after moving across s so that
-- dispnea is divided dis.pnea. Olivetti has tec.no.lo.gì.a for [[tecnologia]], showing that cn divides as c.n, and
-- clàc.son, fuc.sì.na, ric.siò for [[clacson]], [[fucsina]], [[ricsiò]], showing that cs divides as c.s.
text = rsub(text, "(p[‿⁀]?)%.([ns])", ".%1%2")
text = rsub_repeatedly(text, "(" .. V .. com.accent_c .. "*[‿⁀]?)(" .. V .. ")", "%1.%2")
-- User-specified syllable divider should now be treated like regular one.
text = rsub(text, SYLDIV, ".")
text = rsub(text, TEMP_H, "h")
-- Do the following after syllabification so we can distinguish written s from z, e.g. u.sbè.co but uz.bè.co per Olivetti.
-- Single ⟨s⟩ between vowels is /z/.
text = rsub_repeatedly(text, "(" .. VW .. com.stress_c .. "?" .. charsep_c .. "*)s(" .. charsep_c .. "*" .. VW .. ")", "%1z%2")
-- ⟨s⟩ immediately before a voiced consonant is always /z/
text = rsub(text, "s(" .. charsep_c .. "*" .. voiced_C_c .. ")", "z%1")
text = rsub(text, TEMP_Z, "z")
text = rsub(text, TEMP_S, "s")
-- French/German vowels
text = rsub(text, "ü", "y")
text = rsub(text, "ö", "ø")
text = rsub(text, "g", "ɡ") -- U+0261 LATIN SMALL LETTER SCRIPT G
local last_word_self_gemination = rfind(text, "[ʦʣʃʎɲ]" .. com.stress_c .."*##$") and not
-- In case the user used t͡ʃ explicitly
rfind(text, "t͡ʃ" .. com.stress_c .."*##$")
local first_word_self_gemination = rfind(text, "^##" .. com.stress_c .. "*[ʦʣʃʎɲ]")
text = rsub(text, "([ʦʣʧʤ])(" .. charsep_c .. "*%.?)([ʦʣʧʤ]*)", function(affricate1, divider, affricate2)
local full_affricate = full_affricates[affricate1]
if affricate2 ~= "" then
return usub(full_affricate, 1, 1) .. divider .. full_affricate
end
return full_affricate .. divider
end)
local last_word_ends_in_primary_stressed_vowel = rfind(text, "ˈ##$")
-- Last word is multisyllabic if it has a syllable marker in it. This should not happen across word boundaries
-- (spaces) including ⁀, marking where two words were joined by syntactic gemination.
local last_word_is_multisyllabic = rfind(text, "%.[^ ⁀]*$")
local retval = {
orig_respelling = orig_respelling,
canon_respelling = canon_respelling,
-- Automatic co-gemination (syntactic gemination of the following consonant in a multisyllabic word ending in
-- a stressed vowel)
auto_cogemination = last_word_ends_in_primary_stressed_vowel and last_word_is_multisyllabic,
-- Last word ends in a vowel (an explicit * indicates co-gemination, i.e. syntactic gemination of the
-- following consonant)
last_word_ends_in_vowel = rfind(text, V .. com.stress_c .. "*" .. "##$"),
-- Last word ends in a consonant (an explicit * indicates self-gemination of this consonant, i.e. the
-- consonant doubles before a following vowel)
last_word_ends_in_consonant = rfind(text, C .. "##$"),
-- Last word ends in a consonant (ts dz ʃ ʎ ɲ) that triggers self-gemination before a following vowel
auto_final_self_gemination = last_word_self_gemination,
-- First word begins in a consonant (ts dz ʃ ʎ ɲ) that triggers self-gemination after a preceding vowel
auto_initial_self_gemination = first_word_self_gemination,
}
-- Now that ⁀ has served its purpose, convert to a regular tie ‿.
text = rsub(text, "⁀", "‿")
-- Stress marks.
-- Move IPA stress marks to the beginning of the syllable.
text = rsub_repeatedly(text, "([#.])([^#.]*)(" .. com.stress_c .. ")", "%1%3%2")
-- Suppress syllable mark before IPA stress indicator.
text = rsub(text, "%.(" .. com.stress_c .. ")", "%1")
-- Make all primary stresses but the last one in a given word be secondary. May be fed by the first rule above.
text = rsub_repeatedly(text, "ˈ([^ #]+)ˈ", "ˌ%1ˈ")
-- Remove # symbols at word/text boundaries and recompose.
text = rsub(text, "#", "")
text = toNFC(text)
retval.phonemic = text
return retval
end
-- For bot usage; {{#invoke:it-pronunciation|to_phonemic_bot|SPELLING}}
function export.to_phonemic_bot(frame)
local iparams = {
[1] = {},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
return export.to_phonemic(iargs[1], mw.title.getCurrentTitle().text).phonemic
end
--[=[
Entry point to construct the arguments to a call to m_IPA.format_IPA_full() and make a call to this function.
This formats one line of pronunciation, potentially including multiple individual pronunciations (representing
differing pronunciations of the same underlying term), potentialy along with attached qualifiers and/or references.
`data` is a table currently containing two fields, as follows:
{
terms = {{term = RESPELLING, qual = {QUALIFIER, QUALIFIER, ...}, ref = {REFSPEC, REFSPEC, ...}}, ...},
pagename = PAGENAME,
}
Here:
* RESPELLING is a pronunciation respelling of the term in question and may contain initial and/or final specs
indicating the presence, absence and nature of self-gemination and co-gemination along with initial specs
indicating the register of the pronunciation (traditional, careful style, elevated style).
* QUALIFIER is an arbitrary string to be displayed as a qualifier before the pronunciation in question; multiple
qualifiers will be comma-separated. `qual` should always be given as a table even if it's empty.
* REFSPEC is a string of the same format as is passed to {{IPA}}; see the documentation of [[Template:IPA]] for more
info. `ref`, as with `qual`, should always be given as a table even if it's empty.
* PAGENAME is the name of the page, used when an abbreviated spec like '^ò' is given.
The return value is an object with the following structure:
{
formatted = FORMATTED_IPA_LINE,
terms = {
{phonemic = PHONEMIC_PRON,
raw = "phonemic" or "phonetic",
auto_cogemination = BOOLEAN,
last_word_ends_in_vowel = BOOLEAN,
last_word_ends_in_consonant = BOOLEAN,
auto_final_self_gemination = BOOLEAN,
auto_initial_self_gemination = BOOLEAN,
orig_respelling = ORIG_RESPELLING,
canon_respelling = CANON_RESPELLING,
prespec = nil or PRESPEC,
pretext = nil or PRETEXT,
postspec = nil or POSTSPEC,
posttext = nil or POSTTEXT,
qualifiers = {QUALIFIER, QUALIFIER, ...},
refs = nil or {{text = TEXT, name = nil or NAME, group = nil or GROUP}, ...},
}, ...
}
}
Here:
* FORMATTED_IPA_LINE is the output of format_IPA_full(), a string.
* `terms` contains one entry per term in the input object to show_IPA_full().
* PHONEMIC_PRON is the phonemic IPA pronunciation of the respelling passed in, generated by to_phonemic().
* RAW is "phonemic" if the user specified a raw pronunciation using /.../, "phonetic" if using [...]. In such a case,
PHONEMIC_PRON is the user-specified pronunciation without the surrounding slashes or brackets.
* `last_word_ends_in_vowel` and the other boolean properties are documented in the source code of to_phonemic().
* ORIG_RESPELLING is the respelling for which the phonemic IPA pronunciation was generated. This is the same as was
passed in to show_IPA_full(), but stripped of initial and final spec symbols such as *, **, °, °°, #, !, !!.
* CANON_RESPELLING is the canonicalized, Unicode-decomposed version of ORIG_RESPELLING, as output by
canonicalize_and_auto_accent(). This includes expansion of abbreviations such as ^à, suffix application such as
-zione -> tsióne, and other auto-accenting.
* PRESPEC is a string containing the raw symbol(s) added before the phonemic output, currently one or *, **, ° or °°.
These may be explicitly specified by the user or added automatically based on the properties in the `phonemic`
structure described above.
* PRETEXT is the actual text added before the phonemic output, based on the prespec symbols annotated with HTML that
causes a tooltip to be displayed when the cursor hovers over the symbol.
* POSTSPEC is like PRESPEC but contains the raw symbol(s) added after the phonemic output rather than before.
* POSTTEXT is like PRETEXT but based on POSTSPEC.
* `qualifiers` are the actual qualifiers passed to format_IPA_full() for this term. The qualifiers come from the
qualifiers passed in the per-term input and/or qualifiers added based on symbols preceding the respelling such as
#, ! or !!.
* `refs` is either nil or a list of objects describing parsed references, as returned by parse_references() in
[[Module:references]].
]=]
function export.show_IPA_full(data)
local final_cogemination = "triggers final cogemination (syntactic gemination of the initial consonant of the following word)"
local final_non_cogemination = "does not trigger final cogemination (syntactic gemination of the initial consonant of the following word)"
local final_self_cogemination = "triggers final self-gemination (syntactic gemination of the final consonant before a vowel)"
local final_non_self_cogemination = "does not trigger final self-gemination (syntactic gemination of the final consonant before a vowel)"
local initial_self_gemination = "triggers initial self-gemination (syntactic gemination of the initial consonant following a vowel)"
local initial_non_cogemination = "blocks initial cogemination (syntactic gemination of the initial consonant when it would normally occur, i.e. following a stressed final vowel)"
local initial_symbol_specs = {
{"**", "optionally " .. initial_self_gemination},
{"*", initial_self_gemination},
{"°°", "optionally " .. initial_non_cogemination},
{"°", initial_non_cogemination},
}
local final_vowel_symbol_specs = {
{"**", "optionally " .. final_cogemination},
{"*", final_cogemination},
{"°", final_non_cogemination},
}
local final_consonant_symbol_specs = {
{"**", "optionally " .. final_self_cogemination},
{"*", final_self_cogemination},
{"°", final_non_self_cogemination},
}
local retval = {terms = {}}
local transcriptions = {}
for _, term in ipairs(data.terms) do
local respelling = term.term
local qualifiers = term.qual
local prespec, actual_respelling, postspec = rmatch(respelling, "^(" .. pron_sign_c .. "*)(.-)([*°]*)$")
respelling = actual_respelling
if prespec:find("!!") then
table.insert(qualifiers, 1, "高尚語調")
prespec = prespec:gsub("!!", "")
end
if prespec:find("!") then
table.insert(qualifiers, 1, "謹慎語調")
prespec = prespec:gsub("!", "")
end
if prespec:find("#") then
table.insert(qualifiers, 1, "傳統")
prespec = prespec:gsub("#", "")
end
local pron, pretext, posttext
local raw, raw_type
raw = rmatch(respelling, "^raw:/(.*)/$")
if raw then
raw_type = "phonemic"
else
raw = rmatch(respelling, "^raw:%[(.*)%]$")
if raw then
raw_type = "phonetic"
end
end
if raw then
pron = {
orig_respelling = respelling,
canon_respelling = com.decompose(respelling),
phonemic = raw,
raw = raw_type,
}
else
pron = export.to_phonemic(respelling, data.pagename)
end
if prespec == "" and pron.auto_initial_self_gemination then
prespec = "*"
end
if postspec == "" and (pron.auto_cogemination or pron.auto_final_self_gemination) then
postspec = "*"
end
local function check_symbol_spec(spec, recognized_specs, is_pre)
for _, symbol_spec in ipairs(recognized_specs) do
local symbol, text = unpack(symbol_spec)
if symbol == spec then
local abbr = '<abbr title="' .. text .. '"><sup>' .. symbol .. "</sup></abbr>"
if is_pre then
pretext = abbr
else
posttext = abbr
end
return
end
end
error("Unrecognized " .. (is_pre and "initial" or "final") .. " symbol " .. spec)
end
if prespec ~= "" then
check_symbol_spec(prespec, initial_symbol_specs, true)
end
if postspec ~= "" then
if pron.last_word_ends_in_vowel then
check_symbol_spec(postspec, final_vowel_symbol_specs, false)
elseif pron.last_word_ends_in_consonant then
check_symbol_spec(postspec, final_consonant_symbol_specs, false)
else
error("Last word ends in neither vowel nor consonant; final symbol " .. spec .. " not allowed here")
end
end
local refs
if #term.ref == 0 then
refs = nil
elseif #term.ref == 1 then
refs = require("Module:references").parse_references(term.ref[1])
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
pron.prespec = prespec
pron.pretext = pretext
pron.postspec = postspec
pron.posttext = posttext
pron.qualifiers = qualifiers
pron.refs = refs
table.insert(retval.terms, pron)
table.insert(transcriptions, {
pron = pron.raw == "phonetic" and "[" .. pron.phonemic .. "]" or "/" .. pron.phonemic .. "/",
q = #qualifiers > 0 and qualifiers or nil,
pretext = pretext,
posttext = posttext,
refs = refs,
})
end
retval.formatted = require("Module:IPA").format_IPA_full { lang = lang, items = transcriptions }
return retval
end
-- External entry point for {{it-IPA}}.
function export.show(frame)
local args = require("Module:parameters").process(
frame:getParent().args,
{
-- terms to transcribe
[1] = { list = true },
["qual"] = { list = true, allow_holes = true },
["ref"] = { list = true, allow_holes = true },
["pagename"] = {}, -- for testing
})
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
local respellings = args[1]
if #respellings == 0 then
respellings = {pagename}
end
local data = {terms = {}, pagename = pagename}
for i, respelling in ipairs(respellings) do
table.insert(data.terms, {term = respelling, qual = {args.qual[i]}, ref = {args.ref[i]}})
end
local retval_IPA_full = export.show_IPA_full(data)
return retval_IPA_full.formatted
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no
-- hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words
if not phonemic:find(" ") then
words = {phonemic}
else
words = split(phonemic, "( +)")
end
for i, word in ipairs(words) do
if (i % 2) == 1 then -- an actual word, not a separator
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
else
-- Convert spaces and word-separating hyphens into syllable divisions.
words[i] = "."
end
end
phonemic = table.concat(words)
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Given the output structure from show_IPA_full, generate a list of rhyme objects. The resulting list can be directly
-- passed in as the `rhymes` field of the data object passed into format_rhymes() in [[Module:rhymes]].
local function generate_rhymes_from_phonemic_output(ipa_full_output, always_rhyme)
local rhymes = {}
for _, termobj in ipairs(ipa_full_output.terms) do
local pronun = termobj.phonemic
local no_rhyme
if always_rhyme then
if termobj.raw == "phonetic" then
error("Can't generate rhyme for raw phonetic output")
else
no_rhyme = false
end
else
local words = com.split_but_rejoin_affixes(termobj.canon_respelling)
-- Figure out if we should not generate a rhyme for this term.
no_rhyme =
termobj.raw -- raw phonemic or phonetic output given
or #words > 1 -- more than one word
or words[1]:find("%-$") -- a prefix
or words[1]:find("^%-") and ( -- an unstressed suffix:
-- (1): com.DOTOVER explicitly indicates unstressed suffix
words[1]:find(com.DOTOVER)
-- (2): com.DOTUNDER directly after acute or grave indicates unstressed vowel; after discounting
-- such vowels, we see no stressed words.
or not rfind(rsub(words[1], com.quality_c .. com.DOTUNDER, ""), com.quality_c)
)
end
if not no_rhyme then
local rhyme_pronun = rsub(rsub(pronun, ".*[ˌˈ]", ""), "^[^aeiouɛɔ]*", ""):gsub(com.TIE, ""):gsub("%.", "")
if rhyme_pronun ~= "" then
local nsyl = get_num_syl_from_phonemic(pronun)
local saw_rhyme = false
for _, rhyme in ipairs(rhymes) do
if rhyme.rhyme == rhyme_pronun then
-- already saw rhyme
local saw_nsyl = false
for _, this_nsyl in ipairs(rhyme.num_syl) do
if this_nsyl == nsyl then
saw_nsyl = true
break
end
end
if not saw_nsyl then
table.insert(rhyme.num_syl, nsyl)
end
saw_rhyme = true
break
end
end
if not saw_rhyme then
-- Only show pronunciation qualifiers with rhymes if there's more than one pronunciation given.
-- Otherwise the qualifier is just redundant since it's already specified before the pronunciation
-- and doesn't serve to distinguish rhymes. Also, only set the qualifier for new rhymes; don't
-- combine qualifiers from different pronunciations with the same rhyme. For example, for
-- {{it-pr|scandìnavo,#!scandinàvo}} we want to see
--
-- Rhymes: -inavo, ''(traditional, careful style)'' -avo
--
-- but for {{it-pr|quàlche°,#quàlche*}} we do *NOT* want to see ''(traditional)'' by the rhyme -alke,
-- which would be incorrect as the same rhyme applies to both pronunciations and only one of the
-- pronunciations is traditional.
local quals = #ipa_full_output.terms > 1 and termobj.qualifiers and #termobj.qualifiers > 0 and termobj.qualifiers or nil
table.insert(rhymes, {rhyme = rhyme_pronun, q = quals, num_syl = {nsyl}})
end
end
end
end
return rhymes
end
-- Syllabify a single word based on its spelling. The text should be decomposed using com.decompose() and have extraneous
-- characters (e.g. initial or final *) removed.
local function syllabify_word_from_spelling(text)
-- NOTE: In all of the following, we have to be careful to allow for apostrophes between letters and for capital
-- letters in the middle of words, as in [[anch'io]], [[all'osso]], [[altr'ieri]], [[cardellino dell'Himalaya]],
-- [[UEFA]], etc.
local TEMP_I = u(0xFFF2)
local TEMP_I_CAPS = u(0xFFF3)
local TEMP_U = u(0xFFF4)
local TEMP_U_CAPS = u(0xFFF5)
local TEMP_Y = u(0xFFF6)
local TEMP_Y_CAPS = u(0xFFF7)
local TEMP_G = u(0xFFF8)
local TEMP_G_CAPS = u(0xFFF9)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = text:gsub("%.", SYLDIV)
-- We propagate underscore this far specifically so we can distinguish g_n ([[wagneriano]]) from gn.
-- g_n should end up as g.n but gn should end up as .gn.
local g_to_temp_g = {["g"] = TEMP_G, ["G"] = TEMP_G_CAPS}
text = rsub(text, "([gG])('?)_('?[nN])", function (g, sep, n) return g_to_temp_g[g] .. sep .. n end)
-- Now remove underscores before any further processing.
text = text:gsub("_", "")
-- i, u, y between vowels -> consonant-like substitutions:
-- With i: [[paranoia]], [[febbraio]], [[abbaiare]], [[aiutare]], etc.
-- With u: [[portauovo]], [[schopenhaueriano]], [[Malaui]], [[oltreuomo]], [[palauano]], [[tauone]], etc.
-- With y: [[ayatollah]], [[coyote]], [[hathayoga]], [[kayak]], [[uruguayano]], etc. [[kefiyyah]] needs special
-- handling.
-- Also with h, as in [[nahuatl]], [[ahia]], etc.
-- With h not dividing diphthongs: [[ahi]], [[ehi]], [[ahimè]], [[ehilà]], [[ohimè]], [[ohilà]], etc.
-- But in the common sequence -Ciuo- ([[figliuolo]], [[begliuomini]], [[giuoco]], [[nocciuola]], [[stacciuolo]],
-- [[oriuolo]], [[guerricciuola]], [[ghiaggiuolo]], etc.), both i and u are glides. In the sequence -quiV-
-- ([[quieto]], [[reliquia]], etc.), both u and i are glides, and probably also in -guiV-, but not in other -CuiV-
-- sequences such as [[buio]], [[abbuiamento]], [[gianduia]], [[cuiusso]], [[alleluia]], etc.). Special cases are
-- French-origin words like [[feuilleton]], [[rousseauiano]], [[gargouille]]; it's unlikely we can handle these
-- correctly automatically.
--
-- We handle these cases as follows:
-- 1. q+TEMP_U etc. replace sequences of qu and gu with consonant-type codes. This allows us to distinguish
-- -quiV-/-guiV- from other -CuiV-.
-- 2. We convert i in -ViV- sequences to consonant-type TEMP_I, but similarly for u in -VuV- sequences only if the
-- first V isn't i, so -CiuV- remains with two vowels. The syllabification algorithm below will not divide iu
-- or uV unless in each case the first vowel is stressed, so -CiuV- remains in a single syllable.
-- 3. As soon as we convert i to TEMP_I, we undo the u -> TEMP_U change for -quiV-/-guiV-, before u -> TEMP_U in
-- -VuV- sequences.
local u_to_temp_u = {["u"] = TEMP_U, ["U"] = TEMP_U_CAPS}
text = rsub(text, "([qQgG])([uU])('?" .. V .. ")", function(qg, u, v) return qg .. u_to_temp_u[u] .. v end)
local i_to_temp_i = {["i"] = TEMP_I, ["I"] = TEMP_I_CAPS, ["y"] = TEMP_Y, ["Y"] = TEMP_Y_CAPS}
text = rsub_repeatedly(text, "(" .. V .. com.accent_c .. "*[hH]?)([iIyY])(" .. V .. ")",
function(v1, iy, v2) return v1 .. i_to_temp_i[iy] .. v2 end)
text = text:gsub(TEMP_U, "u")
text = text:gsub(TEMP_U_CAPS, "U")
text = rsub_repeatedly(text, "(" .. V_NOT_I .. com.accent_c .. "*[hH]?)([uU])(" .. V .. ")",
function(v1, u, v2) return v1 .. u_to_temp_u[u] .. v2 end)
-- Divide VCV as V.CV; but don't divide if C == h, e.g. [[ahimè]] should be ahi.mè.
text = rsub_repeatedly(text, "(" .. V .. com.accent_c .. "*'?)(" .. C_NOT_H .. "'?" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. com.accent_c .. "*'?" .. C .. C_OR_TIE .. "*)(" .. C .. "'?" .. V .. ")", "%1.%2")
-- Examples in Olivetti like [[hathayoga]], [[telethon]], [[cellophane]], [[skyphos]], [[piranha]], [[bilharziosi]]
-- divide as .Ch. Exceptions are [[wahhabismo]], [[amharico]], [[kinderheim]], [[schopenhaueriano]] but the latter
-- three seem questionable as the pronunciation puts the first consonant in the following syllable and makes the h
-- silent.
text = rsub(text, "(" .. C_NOT_H .. "'?)%.([hH])", ".%1%2")
-- gn represents a single sound so it should not be divided.
text = rsub(text, "([gG])%.([nN])", ".%1%2")
-- Existing hyphenations of [[atlante]], [[Betlemme]], [[genetliaco]], [[betlemita]] all divide as .tl,
-- and none divide as t.l. No examples of -dl- but it should be the same per
-- http://www.italianlanguageguide.com/pronunciation/syllabication.asp.
text = rsub(text, "([pbfvkcgqtdPBFVKCGQTD]'?)%.([lrLR])", ".%1%2")
-- Italian appears to divide sCV as .sCV e.g. pé.sca for [[pesca]], and similarly for sCh, sCl, sCr. Exceptions are
-- ss, sr, sz and possibly others. We are careful not to move across s in [[massmediale]], [[password]], etc.
text = rsub(text, "([^sS])([sS]'?)%.(" .. C_NOT_SRZ .. ")", "%1.%2%3")
-- Several existing hyphenations divide .pn and .ps and Olivetti agrees. We do this after moving across s so that
-- dispnea is divided dis.pnea. We are careful not to move across p in [[Uppsala]]. Olivetti has tec.no.lo.gì.a for
-- [[tecnologia]], showing that cn divides as c.n, and clàc.son, fuc.sì.na, ric.siò for [[clacson]], [[fucsina]],
-- [[ricsiò]], showing that cs divides as c.s.
text = rsub(text, "([^pP])([pP]'?)%.([nsNS])", "%1.%2%3")
-- Any aeoö, or stressed iuüy, should be syllabically divided from a following aeoö or stressed iuüy.
-- A stressed vowel might be followed by another accent such as com.LINEUNDER (which we put after the acute/grave in
-- com.decompose()).
text = rsub_repeatedly(text, "([aeoöAEOÖ]" .. com.accent_c .. "*'?)([hH]?'?[aeoöAEOÖ])", "%1.%2")
text = rsub_repeatedly(text, "([aeoöAEOÖ]" .. com.accent_c .. "*'?)([hH]?'?" .. V .. com.quality_c .. ")", "%1.%2")
text = rsub(text, "([iuüyIUÜY]" .. com.quality_c .. com.accent_c .. "*'?)([hH]?'?[aeoöAEOÖ])", "%1.%2")
text = rsub_repeatedly(text, "([iuüyIUÜY]" .. com.quality_c .. com.accent_c .. "*'?)([hH]?'?" .. V .. com.quality_c .. ")", "%1.%2")
-- We divide ii as i.i ([[sii]]), but not iy or yi, which should hopefully cause [[kefiyyah]] to be handled
-- correctly as ke.fiy.yah. Only example with Cyi is [[dandyismo]], which may be exceptional.
text = rsub_repeatedly(text, "([iI]" .. com.accent_c .. "*'?)([hH]?'?[iI])", "%1.%2")
text = rsub_repeatedly(text, "([uüUÜ]" .. com.accent_c .. "*'?)([hH]?'?[uüUÜ])", "%1.%2")
text = text:gsub(SYLDIV, ".")
text = text:gsub(TEMP_I, "i")
text = text:gsub(TEMP_I_CAPS, "I")
text = text:gsub(TEMP_U, "u")
text = text:gsub(TEMP_U_CAPS, "U")
text = text:gsub(TEMP_Y, "y")
text = text:gsub(TEMP_Y_CAPS, "Y")
text = text:gsub(TEMP_G, "g")
text = text:gsub(TEMP_G_CAPS, "G")
return text
end
-- Syllabify text based on its spelling. The text should be decomposed using com.decompose() and have extraneous
-- characters (e.g. initial *) removed.
local function syllabify_from_spelling(text)
-- Convert spaces and word-separating hyphens into syllable divisions.
local words = com.split_but_rejoin_affixes(text)
for i, word in ipairs(words) do
if (i % 2) == 0 then -- a separator
words[i] = WORDDIV
else
words[i] = syllabify_word_from_spelling(word)
end
end
text = table.concat(words)
-- Convert word divisions into periods, but first into spaces so we can call com.remove_secondary_stress().
-- We have to call com.remove_secondary_stress() after syllabification so we correctly syllabify words like
-- bìobibliografìa.
text = text:gsub(WORDDIV, " ")
text = com.remove_secondary_stress(text)
text = text:gsub(" ", ".")
return text
end
-- Given the canon_respelling field in the structure output by show_IPA_full(), normalize it into the form that can
-- (a) be passed to syllabify_from_spelling() to produce the syllabification that is used to generate hyphenation
-- output, (b) be further processed to determine whether to generate hyphenation at all (by comparing the
-- further-processed result to the original pagename). NOTE: canon_respelling must be decomposed using com.decompose().
local function normalize_for_syllabification(respelling)
-- Remove IPA foot boundaries.
respelling = respelling:gsub("|", " ")
respelling = canon_spaces(respelling)
-- Convert respelling conventions back to the original spelling.
respelling = convert_respelling_to_original(respelling)
return respelling
end
-- Given the output of normalize_for_syllabification() (which should be decomposed using com.decompose()), see if it
-- matches the page name. If so, we auto-generate hyphenation output based on the respelling.
local function spelling_normalized_for_syllabification_matches_pagename(text, pagename)
pagename = com.decompose(pagename)
text = com.remove_secondary_stress(text)
text = text:gsub("_", "")
if text == pagename then
return true
end
text = text:gsub("%.", "")
if text == pagename then -- e.g. [[Abraàm]], [[piùe]] with non-final accent in the page name
return true
end
text = com.remove_non_final_accents(text)
-- Check if the normalized pronunciation is the same as the page name. If a word in the page name is a single
-- syllable, it may or may not have an accent on it, so also remove final monosyllabic accents from the normalized
-- pronunciation when comparing. (Don't remove from both normalized pronunciation and page name because we don't
-- want pronunciation rè to match page name ré or vice versa.)
return text == pagename or remove_final_monosyllabic_accents(text) == pagename
end
-- Given the output structure from show_IPA_full, generate a list of hyphenation objects. The resulting list can be
-- directly passed in as the `hyphs` field of the data object passed into format_hyphenation() in
-- [[Module:hyphenation]].
local function generate_hyphenation_from_phonemic_output(ipa_full_output, pagename)
local hyphs = {}
for _, termobj in ipairs(ipa_full_output.terms) do
local normtext
-- Figure out if we should not generate a hyphenation for this term.
local no_hyph
if termobj.raw then
no_hyph = true
else
normtext = normalize_for_syllabification(termobj.canon_respelling)
no_hyph = not all_words_have_vowels(normtext)
or not spelling_normalized_for_syllabification_matches_pagename(normtext, pagename)
end
if not no_hyph then
local syllabification = syllabify_from_spelling(normtext)
local saw_hyph = false
for _, hyph in ipairs(hyphs) do
if hyph.syllabification == syllabification then
-- already saw hyphenation
saw_hyph = true
break
end
end
if not saw_hyph then
-- Only show pronunciation qualifiers with hyphenations if there's more than one pronunciation given,
-- and only set the qualifier for new hyphenations. See generate_rhymes_from_phonemic_output().
local quals = #ipa_full_output.terms > 1 and termobj.qualifiers and #termobj.qualifiers > 0 and termobj.qualifiers or nil
table.insert(hyphs, {syllabification = syllabification, hyph = split(syllabification, ".", true, true),
q = quals})
end
end
end
return hyphs
end
local function parse_rhyme(arg, put, parse_err)
if not put then
put = require(put_module)
end
local retval = {}
local rhyme_segments = put.parse_balanced_segment_run(arg, "<", ">")
local comma_separated_groups = put.split_alternating_runs(rhyme_segments, "%s*,%s*")
for _, group in ipairs(comma_separated_groups) do
local rhyme_obj = {rhyme = group[1]}
for j = 2, #group - 1, 2 do
if group[j + 1] ~= "" then
parse_err("Extraneous text '" .. group[j + 1] .. "' after modifier")
end
local modtext = group[j]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Rhyme modifier '" .. group[j] .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+):(.*)$")
if not prefix then
parse_err("Modifier " .. group[j] .. " lacks a prefix, should begin with one of 's:' or 'qual:'")
end
if prefix == "s" then
local nsyls = split(arg, "%s*,%s*")
for _, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' in rhyme " ..
table.concat(group) .. " should be numeric")
end
if not rhyme_obj.num_syl then
rhyme_obj.num_syl = {}
end
table.insert(rhyme_obj.num_syl, tonumber(nsyl))
end
elseif prefix == "qual" then
if not rhyme_obj.q then
rhyme_obj.q = {}
end
table.insert(rhyme_obj.q, arg)
else
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[j]
.. ", should be 's' or 'qual'")
end
end
table.insert(retval, rhyme_obj)
end
return retval
end
local function parse_hyph(arg, put, parse_err)
if not put then
put = require(put_module)
end
local retval = {}
local hyph_segments = put.parse_balanced_segment_run(arg, "<", ">")
local comma_separated_groups = put.split_alternating_runs(hyph_segments, "%s*,%s*")
for _, group in ipairs(comma_separated_groups) do
local hyph_obj = {syllabification = group[1], hyph = split(group[1], ".", true, true)}
for j = 2, #group - 1, 2 do
if group[j + 1] ~= "" then
parse_err("Extraneous text '" .. group[j + 1] .. "' after modifier")
end
local modtext = group[j]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. group[j] .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+):(.*)$")
if not prefix then
parse_err("Modifier " .. group[j] .. " lacks a prefix, should begin with 'qual:'")
end
if prefix == "qual" then
if not hyph_obj.q then
hyph_obj.q = {}
end
table.insert(hyph_obj.q, arg)
else
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[j] .. ", should be 'qual'")
end
end
table.insert(retval, hyph_obj)
end
return retval
end
local function parse_homophone(arg, put, parse_err)
if not put then
put = require(put_module)
end
local retval = {}
local hmp_segments = put.parse_balanced_segment_run(arg, "<", ">")
local comma_separated_groups = put.split_alternating_runs(hmp_segments, "%s*,%s*")
for _, group in ipairs(comma_separated_groups) do
local hmp_obj = {term = group[1]}
for j = 2, #group - 1, 2 do
if group[j + 1] ~= "" then
parse_err("Extraneous text '" .. group[j + 1] .. "' after modifier")
end
local modtext = group[j]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. group[j] .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+):(.*)$")
if not prefix then
parse_err("Modifier " .. group[j] .. " lacks a prefix, should begin with one of 'qual:', 't:', 'alt:' or 'pos:'")
end
if prefix == "qual" then
if not hmp_obj.qq then
hmp_obj.qq = {}
end
table.insert(hmp_obj.qq, arg)
elseif prefix == "t" or prefix == "alt" or prefix == "pos" then
local key = prefix == "t" and "gloss" or prefix
if hmp_obj[key] then
parse_err("Modifier '" .. prefix .. "' specified more than once")
end
hmp_obj[key] = arg
else
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[j]
.. ", should be 'qual', 't', 'alt' or 'pos'")
end
end
table.insert(retval, hmp_obj)
end
return retval
end
-- External entry point for {{it-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["pagename"] = {}, -- for testing
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
for i, respelling in ipairs(respellings) do
if respelling:find("[<%[]") then
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
if not put then
put = require(put_module)
end
-- We don't want to split off a comma followed by a space, as in [[uomo avvisato, mezzo salvato]], so replace
-- comma+space with a special character that we later undo.
respelling = respelling:gsub(", ", TEMP1)
-- Parse balanced segment runs involving either [...] (substitution notation) or <...> (inline modifiers). We do this
-- because we don't want commas inside of square or angle brackets to count as respelling delimiters. However, we
-- need to rejoin square-bracketed segments with nearby ones after splitting alternating runs on comma. For example,
-- if we are given "a[x]a<q:learned>,[tts,ìo]<q:nonstandard>", after calling
-- parse_multi_delimiter_balanced_segment_run() we get the following output:
--
-- {"a", "[x]", "a", "<q:learned>", ",", "[tts,ìo]", "", "<q:nonstandard>", ""}
--
-- After calling split_alternating_runs(), we get the following:
--
-- {{"a", "[x]", "a", "<q:learned>", ""}, {"", "[tts,ìo]", "", "<q:nonstandard>", ""}}
--
-- We need to rejoin stuff on either side of the square-bracketed portions.
local segments = put.parse_multi_delimiter_balanced_segment_run(respelling, {{"<", ">"}, {"[", "]"}})
-- Not with spaces around the comma; see above for why we don't want to split off comma followed by space.
local comma_separated_groups = put.split_alternating_runs(segments, ",")
local parsed = {terms = {}, audio = {}}
for i, group in ipairs(comma_separated_groups) do
-- Rejoin bracketed segments with nearby ones, as described above.
local j = 2
while j <= #group do
if group[j]:find("^%[") then
group[j - 1] = group[j - 1] .. group[j] .. group[j + 1]
table.remove(group, j)
table.remove(group, j)
else
j = j + 2
end
end
-- Undo replacement of comma+space.
for j, segment in ipairs(group) do
group[j] = segment:gsub(TEMP1, ", ")
end
local term = {term = group[1], ref = {}, qual = {}}
for j = 2, #group - 1, 2 do
if group[j + 1] ~= "" then
parse_err("Extraneous text '" .. group[j + 1] .. "' after modifier")
end
local modtext = group[j]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. group[j] .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+):(.*)$")
if not prefix then
parse_err("Modifier " .. group[j] .. " lacks a prefix, should begin with one of " ..
"'pre:', 'post:', 'ref:', 'r:', 'bullets:', 'audio:', 'rhyme:', 'hyph:', 'hmp:' or 'qual:'")
end
if prefix == "ref" or prefix == "qual" then
table.insert(term[prefix], arg)
elseif prefix == "r" then
table.insert(term.ref, com.parse_abbreviated_references_spec(arg))
elseif prefix == "pre" or prefix == "post" or prefix == "bullets" or prefix == "rhyme"
or prefix == "hyph" or prefix == "hmp" or prefix == "audio" then
if i < #comma_separated_groups then
parse_err("Modifier '" .. prefix .. "' should occur after the last comma-separated term")
end
if prefix == "rhyme" then
local parsed_rhymes = parse_rhyme(arg, put, parse_err)
if not parsed.rhyme then
parsed.rhyme = parsed_rhymes
else
for _, parsed_rhyme in ipairs(parsed_rhymes) do
table.insert(parsed.rhyme, parsed_rhyme)
end
end
elseif prefix == "hyph" then
local parsed_hyphs = parse_hyph(arg, put, parse_err)
if not parsed.hyph then
parsed.hyph = parsed_hyphs
else
for _, parsed_hyph in ipairs(parsed_hyphs) do
table.insert(parsed.hyph, parsed_hyph)
end
end
elseif prefix == "hmp" then
local parsed_homophones = parse_homophone(arg, put, parse_err)
if not parsed.hmp then
parsed.hmp = parsed_homophones
else
for _, parsed_homophone in ipairs(parsed_homophones) do
table.insert(parsed.hmp, parsed_homophone)
end
end
elseif prefix == "audio" then
local file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
file = file or arg
table.insert(parsed.audio, {file = file, gloss = gloss})
else
if parsed[prefix] then
parse_err("Modifier '" .. prefix .. "' occurs twice, second occurrence " .. group[j])
end
if prefix == "bullets" then
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument")
end
parsed.bullets = tonumber(arg)
else
parsed[prefix] = arg
end
end
else
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[j]
.. ", should be one of 'pre', 'post', 'ref', 'r', 'bullets', 'audio', 'rhyme', 'hyph', 'hmp'"
.. " or 'qual'")
end
end
table.insert(parsed.terms, term)
end
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local terms = {}
-- We don't want to split on comma+space, which should become a foot boundary as in
-- [[uomo avvisato, mezzo salvato]].
local subbed_respelling = respelling:gsub(", ", TEMP1)
for _, term in ipairs(split(subbed_respelling, ",", true)) do
term = term:gsub(TEMP1, ", ")
table.insert(terms, {term = term, ref = {}, qual = {}})
end
table.insert(parsed_respellings, {
terms = terms,
audio = {},
bullets = 1,
})
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
-- Generate the phonemic pronunciation.
parsed.ipa_full = export.show_IPA_full {terms = parsed.terms, pagename = pagename}
-- Generate the rhymes.
local rhymes = {}
if not parsed.rhyme or #parsed.rhyme == 0 then
rhymes = generate_rhymes_from_phonemic_output(parsed.ipa_full)
else
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
rhymes = {}
break
elseif rhyme.rhyme == "+" then
local auto_rhymes = generate_rhymes_from_phonemic_output(parsed.ipa_full, "always rhyme")
for _, auto_rhyme in ipairs(auto_rhymes) do
table.insert(rhymes, auto_rhyme)
end
else
-- If user specified the rhyme explicitly but not the number of syllables, get this from the
-- phonemic representation of the terms.
if not rhyme.num_syl then
rhyme.num_syl = {}
for _, term in ipairs(parsed.ipa_full.terms) do
local nsyl = get_num_syl_from_phonemic(term.phonemic)
m_table.insertIfNot(rhyme.num_syl, nsyl)
end
end
table.insert(rhymes, rhyme)
end
end
end
parsed.rhyme = rhymes
-- Generate the hyphenations.
local hyphs = {}
if not parsed.hyph or #parsed.hyph == 0 then
hyphs = generate_hyphenation_from_phonemic_output(parsed.ipa_full, pagename)
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "-" then
hyphs = {}
break
else
m_table.insertIfNot(hyphs, hyph)
end
end
end
parsed.hyph = hyphs
-- Generate the homophones.
parsed.hmp = parsed.hmp or {}
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_phonemic(parsed)
local pre = parsed.pre and parsed.pre .. " " or ""
local post = parsed.post and " " .. parsed.post or ""
return string.rep("*", parsed.bullets) .. pre .. parsed.ipa_full.formatted .. post
end
local function format_rhymes(rhymes, num_bullets)
local rhymetext = require("Module:rhymes").format_rhymes { lang = lang, rhymes = rhymes, force_cat = force_cat }
return string.rep("*", num_bullets) .. " " .. rhymetext
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
table.insert(ret, string.rep("*", num_bullets) .. " " .. require("Module:audio").format_audio {
lang = lang, file = audio.file, caption = audio.gloss
})
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, format_phonemic(parsed))
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and #parsed.rhyme > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_rhymes(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if #parsed.hmp > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the homophones with the same number of bullets, otherwise
-- indent homophones by one more bullet.
table.insert(textparts, format_homophones(parsed.hmp,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
end
if all_rhyme_sets_eq and #first_rhyme_ret > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_rhymes(first_rhyme_ret, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
return table.concat(textparts)
end
return export
o1sa6gmggpd7xnnvkfmhw07q86ndkwy
Module:Ar-pronunciation
828
1379433
9909043
9604433
2026-09-29T07:24:23Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909043
Scribunto
text/plain
local export = {}
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local parse_utilities_module = "Module:parse utilities"
local rfind = m_str_utils.find
local rsplit = m_str_utils.split
local ugsub = m_str_utils.gsub
local ulen = m_str_utils.len
local ulower = m_str_utils.lower
local usub = m_str_utils.sub
local concat = table.concat
local insert = table.insert
local lang = require("Module:languages").getByCode("ar")
local sc = require("Module:scripts").getByCode("Arab")
local correspondences = {
["ʾ"] = "ʔ",
["ṯ"] = "θ",
["j"] = "d͡ʒ",
["ḥ"] = "ħ",
["ḵ"] = "x",
["ḏ"] = "ð",
["š"] = "ʃ",
["ṣ"] = "sˤ",
["ḍ"] = "dˤ",
["ṭ"] = "tˤ",
["ẓ"] = "ðˤ",
["ž"] = "ʒ",
["ʿ"] = "ʕ",
["ḡ"] = "ɣ",
["ḷ"] = "lˤ",
["ū"] = "uː",
["ī"] = "iː",
["ā"] = "aː",
["y"] = "j",
["g"] = "ɡ",
["ē"] = "eː",
["ō"] = "oː",
[""] = "",
}
local vowels = "aāeēiīoōuū"
local vowel = "[" .. vowels .. "]"
local long_vowels = "āēīōū"
local long_vowel = "[" .. long_vowels .. "]"
local consonant = "[^" .. vowels .. ". -]"
local syllabify_pattern = "(" .. vowel .. ")(" .. consonant .. "?)(" .. consonant .. "?)(" .. vowel .. ")"
local tie = "‿"
local closed_syllable_shortening_pattern = "(" .. long_vowel .. ")(" .. tie .. ")" .. "(" .. consonant .. ")"
local function rsub(term, foo, bar)
local retval = ugsub(term, foo, bar)
return retval
end
local function generate_obj(respelling)
return { respelling = respelling }
end
local function split_on_comma(term)
if not term then
return nil
end
if term:find(",%s") or term:find("\\") then
return require(parse_utilities_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
local function parse_respellings_with_modifiers(respelling, paramname)
if respelling:find("[<%[]") then
local put = require(parse_utilities_module)
local segments = put.parse_multi_delimiter_balanced_segment_run(respelling, { { "<", ">" }, { "[", "]" } })
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
local retval = {}
for _, group in ipairs(comma_separated_groups) do
local j = 2
while j <= #group do
if not group[j]:find("^<.*>$") then
group[j - 1] = group[j - 1] .. group[j] .. group[j + 1]
table.remove(group, j)
table.remove(group, j)
else
j = j + 2
end
end
local param_mods = {
q = { type = "qualifier" },
qq = { type = "qualifier" },
a = { type = "labels" },
aa = { type = "labels" },
ref = { item_dest = "refs", type = "references" },
}
table.insert(retval, put.parse_inline_modifiers_from_segments {
group = group,
arg = respelling,
props = {
paramname = paramname,
param_mods = param_mods,
generate_obj = generate_obj,
},
})
end
return retval
else
local retval = {}
for _, item in ipairs(split_on_comma(respelling)) do
table.insert(retval, generate_obj(item))
end
return retval
end
end
local function parse_pron_modifier(arg, paramname, generate_obj, param_mods, splitchar)
splitchar = splitchar or ","
if arg:find("<") then
param_mods.q = { type = "qualifier" }
param_mods.qq = { type = "qualifier" }
param_mods.a = { type = "labels" }
param_mods.aa = { type = "labels" }
param_mods.ref = { item_dest = "refs", type = "references" }
return require(parse_utilities_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
paramname = paramname,
splitchar = splitchar,
})
else
local retval = {}
local split_arg = splitchar == "," and split_on_comma(arg) or rsplit(arg, splitchar)
for _, term in ipairs(split_arg) do
table.insert(retval, generate_obj(term))
end
return retval
end
end
local function parse_audio(lang, arg, pagename, paramname)
local param_mods = {
IPA = { sublist = true },
text = {},
t = { item_dest = "gloss" },
gloss = {},
pos = {},
lit = {},
g = { item_dest = "genders", sublist = true },
bad = {},
cap = { item_dest = "caption" },
}
local function process_special_chars(val)
if not val then
return val
end
return (val:gsub("#", pagename))
end
local function generate_audio_obj(arg)
return { file = process_special_chars(arg) }
end
local retvals = parse_pron_modifier(arg, paramname, generate_audio_obj, param_mods, "%s*;%s*")
for _, retval in ipairs(retvals) do
retval.lang = lang
retval.text = process_special_chars(retval.text)
retval.caption = process_special_chars(retval.caption)
local textobj = require(audio_module).construct_audio_textobj(retval)
retval.text = textobj
retval.gloss = nil
retval.pos = nil
retval.lit = nil
retval.genders = nil
end
return retvals
end
local function parse_regional_phonetics(ph_arg, pagename)
if not ph_arg or ph_arg == "" then
return {}
end
local regionals = {}
for _, item in ipairs(rsplit(ph_arg, "%s*;%s*")) do
local audio = nil
local item_no_mod = item:gsub("<a:([^>]+)>", function(a)
audio = a:gsub("#", pagename)
return ""
end)
local region, ipa = item_no_mod:match("^([^:]+):(.+)$")
if region and ipa then
local regions = rsplit(region, "%s*,%s*")
table.insert(regionals, { regions = regions, ipa = ipa, audio = audio })
end
end
return regionals
end
local function syllabify(text)
text = ugsub(text, "%-(" .. consonant .. ")%-(" .. consonant .. ")", "%1.%2")
text = ugsub(text, "%-", ".")
for _ = 1, 2 do
text = ugsub(
text,
syllabify_pattern,
function(a, b, c, d)
if c == "" and b ~= "" then
c, b = b, ""
end
return a .. b .. "." .. c .. d
end
)
end
text = ugsub(text, "(" .. vowel .. ") (" .. consonant .. ")%.?(" ..
consonant .. ")", "%1" .. tie .. "%2.%3")
return text
end
local function closed_syllable_shortening(text)
local shorten = {
["ā"] = "a",
["ē"] = "e",
["ī"] = "i",
["ō"] = "o",
["ū"] = "u",
}
text = ugsub(text,
closed_syllable_shortening_pattern,
function(vowel, tie, consonant)
return shorten[vowel] .. tie .. consonant
end)
return text
end
function export.link(term)
return require("Module:links").full_link { term = term, lang = lang, sc = sc }
end
function export.toIPA(list, silent_error)
local translit
if list.tr then
translit = list.tr
elseif list.term then
require("Module:script utilities").checkScript(list.term, "Arab")
translit = lang:transliterate(list.term)
if not translit then
if silent_error then
return ''
else
error('Module:ar-translit failed to generate a transliteration from "' .. list.term .. '".')
end
end
else
if silent_error then
return ''
else
error('No Arabic text or transliteration was provided to the function "toIPA".')
end
end
translit = ugsub(translit, "llāh", "ḷḷāh")
translit = ugsub(translit, "([iī] ?)ḷḷ", "%1ll")
translit = ugsub(translit, "%(t%)", "")
translit = ugsub(translit, "(" .. vowel .. ") " .. vowel, "%1 ")
translit = ugsub(translit, "%-?l%-?", "l")
translit = syllabify(translit)
translit = closed_syllable_shortening(translit)
local output = ugsub(translit, ".", correspondences)
output = ugsub(output, "%-", "")
return output
end
function export.get_pron_info(terms, pagename, paramname)
if #terms == 1 and terms[1].respelling == "-" then
return { pron_list = nil }
end
local pron_list = {}
local brackets = "/%s/"
for _, term in ipairs(terms) do
local respelling = term.respelling
local ar_term, tr
if not respelling or respelling == "" or respelling == "#" then
ar_term = pagename
elseif rfind(respelling, "[a-zA-Z]") then
tr = respelling
elseif respelling:find("[ء-ي]") then
ar_term = respelling
else
tr = respelling
end
local pron = export.toIPA({ term = ar_term, tr = tr }, false)
if pron and pron ~= "" then
local bracketed_pron = brackets:format(pron)
table.insert(pron_list, {
pron = bracketed_pron,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
refs = term.refs,
})
end
end
return { pron_list = pron_list }
end
function export.show_old(frame)
local params = {
[1] = { list = true, allow_holes = true },
["tr"] = { list = true, allow_holes = true },
["qual"] = { list = true, allow_holes = true },
["nl"] = { type = "boolean" },
["ann"] = {},
}
local args = require("Module:parameters").process(frame:getParent().args, params)
local ar_terms = args[1]
local transliterations = args.tr
local qualifiers = args.qual
local nl = args.nl
if not (ar_terms.maxindex > 0 or transliterations.maxindex > 0) then
if mw.title.getCurrentTitle().nsText == "Template" then
ar_terms[1] = "كَلِمَة"
ar_terms.maxindex = 1
else
error(
'Please provide vocalized Arabic in the first parameter of {{[[Template:ar-IPA|ar-IPA]]}}, or transliteration in the "tr" parameter.')
end
end
local pronunciations = {}
for i = 1, math.max(ar_terms.maxindex, transliterations.maxindex) do
local ar_term = ar_terms[i]
local tr = transliterations[i]
local qual = qualifiers[i]
if not (ar_term or tr) then
error("There is a gap in the parameters. Provide either |" .. i .. "= or |tr" .. i .. "=.")
elseif ar_term and tr then
mw.logObject("Duplicate parameters |" .. i .. "= and |tr" .. i .. "= in {{ar-IPA}},")
end
local pron = export.toIPA { term = ar_term, tr = tr }
table.insert(pronunciations, { pron = "/" .. pron .. "/", q = qual and { qual } or nil })
end
local anntext = ""
if args.ann then
anntext = args.ann
if args.ann:find("%+") then
local anndefs = {}
for i = 1, ar_terms.maxindex do
local ar_term = ar_terms[i]
if ar_term then
table.insert(anndefs, "'''" .. ar_term .. "'''")
end
end
if anndefs[1] then
anndefs = table.concat(anndefs, ", ")
anntext = anntext:gsub("%+", require("Module:string utilities").replacement_escape(anndefs))
end
end
anntext = require("Module:qualifier").format_qualifier(anntext, "", "") .. ": "
end
if nl then
return anntext .. require("Module:IPA").format_IPA_multiple(lang, pronunciations)
else
return anntext .. require("Module:IPA").format_IPA_full { lang = lang, items = pronunciations }
end
end
function export.show(frame)
local parent_args = frame:getParent().args
local process = require("Module:parameters").process
local params = {
[1] = {},
["audios"] = {},
["a"] = { alias_of = "audios" },
["ph"] = {},
["pagename"] = {},
["indent"] = {},
["ann"] = {},
}
local args = process(parent_args, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local indent = args.indent or "*"
local termspec = args[1] or "#"
local terms = parse_respellings_with_modifiers(termspec, 1)
local pronobj = export.get_pron_info(terms, pagename, 1)
local regional_phonetics = parse_regional_phonetics(args.ph, pagename)
local parts = {}
local function ins(text)
table.insert(parts, text)
end
local anntext = ""
if args.ann then
anntext = args.ann
if args.ann:find("%+") then
local anndefs = {}
for _, term in ipairs(terms) do
local respelling = term.respelling
if respelling and respelling:find("[ء-ي]") then
table.insert(anndefs, "'''" .. respelling .. "'''")
end
end
if anndefs[1] then
anndefs = table.concat(anndefs, ", ")
anntext = anntext:gsub("%+", require("Module:string utilities").replacement_escape(anndefs))
end
end
anntext = require("Module:qualifier").format_qualifier(anntext, "", "") .. ": "
end
if pronobj.pron_list and #pronobj.pron_list > 0 then
local formatted = require("Module:IPA").format_IPA_full { lang = lang, items = pronobj.pron_list }
ins(indent .. anntext .. mw.ustring.toNFC(formatted))
end
if args.audios then
local format_audio = require("Module:audio").format_audio
local audio_objs = parse_audio(lang, args.audios, pagename, "audios")
for i, audio_obj in ipairs(audio_objs) do
if #audio_objs > 1 and not audio_obj.caption then
audio_obj.caption = "Audio " .. i
end
ins("\n" .. indent .. " " .. format_audio(audio_obj))
end
end
if #regional_phonetics > 0 then
local m_IPA = require("Module:IPA")
local m_accent = require("Module:accent qualifier")
for _, regional in ipairs(regional_phonetics) do
local regions = regional.regions
local ipa = regional.ipa
local pron_item = { pron = "[" .. ipa .. "]" }
local formatted_ipa = m_IPA.format_IPA_full { lang = lang, items = { pron_item } }
local formatted_region = m_accent.format_qualifiers(lang, regions)
local line = "\n" .. indent .. indent .. " " .. formatted_region .. " " .. mw.ustring.toNFC(formatted_ipa)
if regional.audio then
local audio_obj = {
lang = lang,
file = regional.audio,
}
local textobj = require(audio_module).construct_audio_textobj(audio_obj)
audio_obj.text = textobj
line = line .. " " .. require("Module:audio").format_audio(audio_obj)
end
ins(line)
end
end
return concat(parts)
end
return export
5fgfg64x6pqtsaczm7x9u3q3t4t78ua
Module:Ar-verb
828
1379435
9909044
9688400
2026-09-29T07:24:28Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909044
Scribunto
text/plain
-- new version
local export = {}
--[=[
This module implements {{ar-conj}} and provides the underlying conjugation functions for {{ar-verb}}
(whose actual formatting is done in [[Module:ar-headword]]).
Author: User:Benwing, from an early version (2013-2014) by User:Atitarev, User:ZxxZxxZ.
]=]
--[=[
TERMINOLOGY:
-- "slot" = A particular combination of tense/mood/person/number/etc.
Example slot names for verbs are "past_1s" (past tense first-person singular), "juss_pass_3fp" (non-past jussive
passive third-person feminine plural) "ap" (active participle). Each slot is filled with zero or more forms.
-- "form" = The conjugated Arabic form representing the value of a given slot.
-- "lemma" = The dictionary form of a given Arabic term. For Arabic, normally the third person masculine singular past,
although other forms may be used if this form is missing (e.g. in passive-only verbs or verbs lacking the past).
]=]
--[=[
FIXME:
1. Finish unimplemented conjugation types. Only IX-final-weak left (extremely rare, possibly only one verb اِعْمَايَ
(according to Haywood and Nahmad p. 244, who are very specific about the irregular occurrence of alif + yā instead
of expected اِعْمَيَّ with doubled yā). Not in Hans Wehr. NOTE: Not true about this, cf. form IX اِرْعَوَى "to desist,
to repent, to see the light". Also note form XII اِخْضَوْضَرَ = form IX اِخْضَرَّ "to be or become green".
[DONE except for اِعْمَايَ]
2. Implement irregular verbs as special cases and recognize them, e.g.
-- laysa "to not be"; only exists in the past tense, no non-past, no imperative, no participles, no passive, no
verbal noun. Irregular alternation las-/lays-. [IMPLEMENTABLE USING OVERRIDES]
-- istaḥā yastaḥī "be ashamed of" -- this is complex according to Hans Wehr because there are two verbs, regular
istaḥyā yastaḥyī "to spare (someone)'s life" and irregular istaḥyā yastaḥyī "to be ashamed to face (someone)",
which is irregular because it has the alternate irregular form istaḥā yastaḥī which only applies to this meaning.
Currently we follow Haywood and Nahmad in saying that both varieties can be spelled istaḥyā/istaḥā/istaḥḥā, but we
should instead use a variant= param similar to حَيَّ to distinguish the two possibilities, and maybe not include
istaḥḥā.
-- ʿayya/ʿayiya yaʿayyu/yaʿyā "to not find the right way, be incapable of, stammer, falter, fall ill". This appears
to be a mixture of a geminate and final-weak verb. Unclear what the whole paradigm looks like. Do the
consonant-ending parts in the past follow the final-weak paradigm? Is it the same in the non-past? Or can you
conjugate the non-past fully as either geminate or final-weak?
-- اِنْمَحَى inmaḥā or يمَّحَى immaḥā "to be effaced, obliterated; to disappear, vanish" has irregular assimilation of inm-
to imm- as an alternative. inmalasa "to become smooth; to glide; to slip away; to escape" also has immalasa as an
alternative. The only other form VII verbs in Hans Wehr beginning with -m- are inmalaḵa "to be pulled out, torn
out, wrenched" and inmāʿa "to be melted, to melt, to dissolve", which are not listed with imm- alternatives, but
might have them; if so, we should handle this generally. [DONE]
-- يَرَعَ yaraʕa yariʕu "to be a coward, to be chickenhearted" as an alternative form of يَرِعَ yariʕa yayraʕu (as given in
Wehr). [IMPLEMENTABLE USING OVERRIDES]
3. Implement individual override parameters for each paradigm part. See Module:fro-verb for an example of how to do this
generally. Note that {{temp|ar-conj-I}} and other of the older templates already had such individual override params.
[DONE]
Irregular verbs already implemented:
-- [ḥayya/ḥayiya yaḥyā "live" -- behaves like a normal final-weak verb
(e.g. past first singular ḥayītu) except in the past-tense parts with
vowel-initial endings (all the third person except for the third feminine
plural). The normal singular and dual endings have -yiya- in them, which
compresses to -yya-, with the normal endings the less preferred ones.
In masculine third plural, expected ḥayū is replaced by ḥayyū by
analogy to the -yy- parts, and the regular form is not given as an
alternant in John Mace. Barron's 201 verbs appears to have the regular
ḥayū as the part, however. Note also that final -yā appears with tall
alif. This appears to be a spelling convention of Arabic, also applying
in ḥayyā (form II, "to keep (someone) alive") and 'aḥyā (form IV,
"to animate, revive, give birth to, give new life to").] -- implemented
-- [ittaxadha yattaxidhu "take"] -- implemented
-- [sa'ala yas'alu "ask" with alternative jussive/imperative yasal/sal] -- implemented
-- [ra'ā yarā "see"] -- implemented
-- ['arā yurī "show"] -- implemented
-- ['akala ya'kulu "eat" with imperative kul] -- implemented
-- ['axadha ya'xudhu "take" with imperative xudh] -- implemented
-- ['amara ya'muru "order" with imperative mur] -- implemented
--]=]
local force_cat = false -- set to true for debugging
-- if true, always maintain manual translit during processing, and compare against full translit at the end
local debug_translit = false
local lang = require("Module:languages").getByCode("ar")
local m_links = require("Module:links")
local m_string_utilities = require("Module:string utilities")
local m_table = require("Module:table")
local ar_utilities = require("Module:ar-utilities")
local ar_nominals = require("Module:ar-nominals")
local decorations_module = "Module:decorations"
local iut = require("Module:inflection utilities")
local put = require("Module:parse utilities")
local list_to_text = mw.text.listToText
local rfind = m_string_utilities.find
local rsubn = m_string_utilities.gsub
local rmatch = m_string_utilities.match
local rsplit = m_string_utilities.split
local usub = m_string_utilities.sub
local ulen = m_string_utilities.len
local u = m_string_utilities.char
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local dump = mw.dumpObject
-- Within this module, conjugations are the functions that do the actual
-- conjugating by creating the parts of a basic verb.
-- They are defined further down.
local conjugations = {}
-- hamza variants
local HAMZA = u(0x0621) -- hamza on the line (stand-alone hamza) = ء
local HAMZA_ON_ALIF = u(0x0623)
local HAMZA_ON_W = u(0x0624)
local HAMZA_UNDER_ALIF = u(0x0625)
local HAMZA_ON_Y = u(0x0626)
local HAMZA_ANY = "[" .. HAMZA .. HAMZA_ON_ALIF .. HAMZA_UNDER_ALIF .. HAMZA_ON_W .. HAMZA_ON_Y .. "]"
local HAMZA_PH = u(0xFFF0) -- hamza placeholder
local BAD = u(0xFFF1)
local BORDER = u(0xFFF2)
-- diacritics
local A = u(0x064E) -- fatḥa
local AN = u(0x064B) -- fatḥatān (fatḥa tanwīn)
local U = u(0x064F) -- ḍamma
local UN = u(0x064C) -- ḍammatān (ḍamma tanwīn)
local I = u(0x0650) -- kasra
local IN = u(0x064D) -- kasratān (kasra tanwīn)
local SK = u(0x0652) -- sukūn = no vowel
local SH = u(0x0651) -- šadda = gemination of consonants
local DAGGER_ALIF = u(0x0670)
local DIACRITIC_ANY_BUT_SH = "[" .. A .. I .. U .. AN .. IN .. UN .. SK .. DAGGER_ALIF .. "]"
-- Pattern matching short vowels
local AIU = "[" .. A .. I .. U .. "]"
-- Pattern matching short vowels or sukūn
local AIUSK = "[" .. A .. I .. U .. SK .. "]"
-- Pattern matching any diacritics that may be on a consonant
local DIACRITIC = SH .. "?" .. DIACRITIC_ANY_BUT_SH
-- translit_patterns
local vowels = "aeiouāēīōū"
local NV = "[^" .. vowels .. "]"
local dia = {a = A, i = I, u = U}
local undia = {[A] = "a", [I] = "i", [U] = "u", ["-"] = "-"}
-- various letters and signs
local ALIF = u(0x0627) -- ʾalif = ا
local AMAQ = u(0x0649) -- ʾalif maqṣūra = ى
local AMAD = u(0x0622) -- ʾalif madda = آ
local TAM = u(0x0629) -- tāʾ marbūṭa = ة
local T = u(0x062A) -- tāʾ = ت
local HYPHEN = u(0x0640)
local N = u(0x0646) -- nūn = ن
local W = u(0x0648) -- wāw = و
local Y = u(0x064A) -- yāʾ = ي
local S = "س"
local M = "م"
local LRM = u(0x200e) -- left-to-right mark
-- common combinations
local AH = A .. TAM
local AT = A .. T
local AA = A .. ALIF
local AAMAQ = A .. AMAQ
local AAH = AA .. TAM
local AAT = AA .. T
local II = I .. Y
local UU = U .. W
local AY = A .. Y
local AW = A .. W
local AYSK = AY .. SK
local AWSK = AW .. SK
local NA = N .. A
local NI = N .. I
local AAN = AA .. N
local AANI = AA .. NI
local AYNI = AYSK .. NI
local AWNA = AWSK .. NA
local AYNA = AYSK .. NA
local AYAAT = AY .. AAT
local UNU = "[" .. UN .. U .. "]"
local MA = M .. A
local MU = M .. U
local TA = T .. A
local TU = T .. U
local _I = ALIF .. I
local _U = ALIF .. U
local translit_cache = {
-- hamza variants
[HAMZA] = "ʔ",
[HAMZA_ON_ALIF] = "ʔ",
[HAMZA_ON_W] = "ʔ",
[HAMZA_UNDER_ALIF] = "ʔ",
[HAMZA_ON_Y] = "ʔ",
[HAMZA_PH] = "ʔ",
-- diacritics
[A] = "a",
[AN] = "an",
[U] = "u",
[UN] = "un",
[I] = "i",
[IN] = "in",
[SK] = "",
[SH] = "*", -- handled specially
[DAGGER_ALIF] = "ā",
-- various letters and signs
[""] = "",
[ALIF] = BAD, -- we should never be transliterating ALIF by itself, as its translit in isolation is ambiguous
[AMAQ] = BAD,
[AMAD] = "ʔā",
[TAM] = "",
[T] = "t",
[N] = "n",
[W] = "w",
[Y] = "y",
[S] = "s",
[M] = "m",
[LRM] = "",
-- common combinations
[AH] = "a",
[AT] = "at",
[AA] = "ā",
[AAMAQ] = "ā",
[AAH] = "āh",
[AAT] = "āt",
[II] = "ī",
[UU] = "ū",
[AY] = "ay",
[AW] = "aw",
[AYSK] = "ay",
[AWSK] = "aw",
[NA] = "na",
[NI] = "ni",
[AAN] = "ān",
[AANI] = "āni",
[AYNI] = "ayni",
[AWNA] = "awna",
[AYNA] = "ayna",
[AYAAT] = "ayāt",
[MA] = "ma",
[MU] = "mu",
[TA] = "ta",
[TU] = "tu",
[_I] = "i",
[_U] = "u",
}
local weakness_translations = { -- L10N
["sound"] = "健康",
["final-weak"] = "缺尾",
["assimilated"] = "如剛",
["hollow"] = "中空",
["geminate"] = "重母",
["assimilated+final-weak"] = "如剛+缺尾",
["none-sound"] = "非健康",
["none-hollow"] = "非中空",
["none-geminate"] = "非重複",
["none-final-weak"] = "非尾弱"
}
local function transliterate(text)
local cached = translit_cache[text]
if cached then
if cached == BAD then
error(("Internal error: Unable to transliterate %s because explicitly marked as BAD"):format(text))
end
return cached
end
local tr = (lang:transliterate(text))
if not tr then
error(("Internal error: Unable to transliterate: %s"):format(text))
end
translit_cache[text] = tr
return tr
end
local all_person_number_list = {
"1s",
"2ms",
"2fs",
"3ms",
"3fs",
"2d",
"3md",
"3fd",
"1p",
"2mp",
"2fp",
"3mp",
"3fp"
}
local function make_person_number_slot_accel_list(list)
local slot_accel_list = {}
return slot_accel_list
end
local imp_person_number_list = {}
for _, pn in ipairs(all_person_number_list) do
if pn:find("^2") then
table.insert(imp_person_number_list, pn)
end
end
local passive_types = m_table.listToSet {
"pass", -- verb has both active and passive
"ipass", -- verb is active with impersonal passive
"nopass", -- verb is active-only
"onlypass", -- verb is passive-only
"onlypass-impers", -- verb itself is impersonal, meaning passive-only with impersonal passive
}
local indicator_flags = m_table.listToSet {
"nopast", "no_nonpast", "noimp",
"nocat", -- don't categorize or include annotations about this; useful in suppletive parts of verbs
"reduced", -- verb has assimilation/reduction of initial coronals
"altgem", -- form X with alternative past geminate forms with final-weak endings
}
export.potential_lemma_slots = {"past_3ms", "past_pass_3ms", "ind_3ms", "ind_pass_3ms", "imp_2ms"}
export.unsettable_slots = {}
for _, potential_lemma_slot in ipairs(export.potential_lemma_slots) do
table.insert(export.unsettable_slots, potential_lemma_slot .. "_linked")
end
-- We don't set the active participle directly for form I because we don't want stative verbs (with past vowel i or u)
-- to default to فَاعِل. Instead we set the special slot 'ap1' and later copy it to 'ap' for non-stative verbs. The user
-- meanwhile can explicitly request the فَاعِل form for active participles for stative verbs using `ap:+`.
table.insert(export.unsettable_slots, "ap1") -- primary default فَاعِل for form I active participles
table.insert(export.unsettable_slots, "ap2") -- secondary default فَعِيل for form I active participles (stative I)
table.insert(export.unsettable_slots, "ap3") -- secondary default فَعِل for form I active participles (stative II)
table.insert(export.unsettable_slots, "apcd") -- secondary default أَفْعَل for form I active participles (color/defect)
table.insert(export.unsettable_slots, "apan") -- secondary default فَعْلَان for form I active participles (in -ān)
table.insert(export.unsettable_slots, "pp2") -- secondary default فَعِيل for form I passive participles (same as ap2)
table.insert(export.unsettable_slots, "vn2") -- secondary default فِعَال for form III verbal nouns
export.unsettable_slots_set = m_table.listToSet(export.unsettable_slots)
local default_indicator_to_active_participle_slot = {
["+"] = "ap1",
["++"] = "ap2",
["+++"] = "ap3",
["+cd"] = "apcd",
["+an"] = "apan",
}
local slots_that_may_be_uncertain = {
vn = "動詞性名詞",
ap = "主動分詞",
}
-- Initialize all the slots for which we generate forms.
local function add_slots(alternant_multiword_spec)
alternant_multiword_spec.verb_slots = {
{"ap", "act|part"},
{"pp", "pass|part"},
{"vn", "vnoun"},
}
for _, unsettable_slot in ipairs(export.unsettable_slots) do
table.insert(alternant_multiword_spec.verb_slots, {unsettable_slot, "-"})
end
-- Add entries for a slot with person/number variants.
-- `slot_prefix` is the prefix of the slot, typically specifying the tense/aspect.
-- `tag_suffix` is a string listing the set of inflection tags to add after the person/number tags.
-- `person_number_list` is a list of the person/number slot suffixes to add to `slot_prefix`.
local function add_personal_slot(slot_prefix, tag_suffix, person_number_list)
for _, persnum in ipairs(person_number_list) do
local slot = slot_prefix .. "_" .. persnum
local accel = persnum:gsub("(.)", "%1|") .. tag_suffix
table.insert(alternant_multiword_spec.verb_slots, {slot, accel})
end
end
local tenses = {
{"past", "past|%s"},
{"ind", "non-past|%s|ind"},
{"sub", "non-past|%s|sub"},
{"juss", "non-past|%s|juss"},
}
for _, slot_accel in ipairs(tenses) do
local slot, accel = unpack(slot_accel)
for _, voice in ipairs {"act", "pass"} do
add_personal_slot(voice == "act" and slot or slot .. "_pass", accel:format(voice),
all_person_number_list)
end
end
add_personal_slot("imp", "imp", imp_person_number_list)
alternant_multiword_spec.verb_slots_map = {}
for _, slot_accel in ipairs(alternant_multiword_spec.verb_slots) do
local slot, accel = unpack(slot_accel)
alternant_multiword_spec.verb_slots_map[slot] = accel
end
end
local overridable_stems = {}
local slot_override_param_mods = {
footnote = {
item_dest = "footnotes",
store = "insert",
},
alt = {},
t = {
-- [[Module:links]] expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
g = {
-- [[Module:links]] expects the genders in "g". `sublist = true` automatically splits on comma (optionally
-- with surrounding whitespace).
item_dest = "genders",
sublist = true,
},
pos = {},
lit = {},
id = {},
-- Qualifiers and labels
q = {
type = "qualifier",
},
qq = {
type = "qualifier",
},
l = {
type = "labels",
},
ll = {
type = "labels",
},
}
local function generate_obj(formval, parse_err, prefix, is_slot_override)
local val, uncertain = formval:match("^(.*)(%?)$")
val = val or formval
uncertain = not not uncertain
local ar, translit = val:match("^(.*)//(.*)$")
if not ar then
ar = val
end
if ar == "" then
if uncertain then
ar = "?"
else
error(("Can't specify blank value for override for %s override '%s'"):format(
is_slot_override and "slot" or "stem", prefix))
end
end
return {form = ar, translit = translit, uncertain = uncertain}
end
local function parse_inline_modifiers(comma_separated_group, parse_err, prefix, is_slot_override)
local function this_generate_obj(formval, parse_err)
return generate_obj(formval, parse_err, prefix, is_slot_override)
end
return put.parse_inline_modifiers_from_segments {
group = comma_separated_group,
props = {
param_mods = slot_override_param_mods,
parse_err = parse_err,
generate_obj = this_generate_obj,
pre_normalize_modifiers = function(data)
local modtext = data.modtext
modtext = modtext:match("^(%[.*%])$")
if modtext then
return ("<footnote:%s>"):format(modtext)
end
return data.modtext
end,
},
}
end
local function allow_multiple_values_for_override(comma_separated_groups, data, is_slot_override)
local retvals = {}
for _, comma_separated_group in ipairs(comma_separated_groups) do
local retval
if is_slot_override then
retval = parse_inline_modifiers(comma_separated_group, data.parse_err)
else
retval = generate_obj(comma_separated_group[1], data.parse_err, data.prefix, is_slot_override)
retval.footnotes = data.fetch_footnotes(comma_separated_group)
end
table.insert(retvals, retval)
end
for _, form in ipairs(retvals) do
if form.form == "+" or default_indicator_to_active_participle_slot[form.form] then
if form.form ~= "+" and default_indicator_to_active_participle_slot[form.form] and not is_slot_override then
error(("Stem override '%s' cannot use %s to request a secondary default"):format(
data.prefix, form.form))
end
data.base.slot_override_uses_default[data.prefix] = true
end
end
for _, form in ipairs(retvals) do
if form.form == "-" then
data.base.slot_explicitly_missing[data.prefix] = true
break
end
end
if data.base.slot_explicitly_missing[data.prefix] then
for _, form in ipairs(retvals) do
if form.form ~= "-" then
data.parse_err(("For slot or stem '%s', saw both - and a value other than -, which isn't allowed"):
format(data.prefix))
end
end
return nil
end
return retvals
end
local function simple_choice(choices)
return function(separated_groups, data)
if #separated_groups > 1 then
data.parse_err("For spec '" .. data.prefix .. ":', only one value currently allowed")
end
if #separated_groups[1] > 1 then
data.parse_err("For spec '" .. data.prefix .. ":', no footnotes currently allowed")
end
local choice = separated_groups[1][1]
if not m_table.contains(choices, choice) then
data.parse_err("For spec '" .. data.prefix .. ":', saw value '" .. choice .. "' but expected one of '" ..
table.concat(choices, ",") .. "'")
end
return choice
end
end
for _, overridable_stem in ipairs {
"past",
"past_v",
"past_c",
"past_pass",
"past_pass_v",
"past_pass_c",
"nonpast",
"nonpast_v",
"nonpast_c",
"nonpast_pass",
"nonpast_pass_v",
"nonpast_pass_c",
"imp",
"imp_v",
"imp_c",
} do
overridable_stems[overridable_stem] = allow_multiple_values_for_override
end
overridable_stems.past_final_weak_vowel = simple_choice { "ay", "aw", "ī", "ū" }
overridable_stems.past_pass_final_weak_vowel = simple_choice { "ay", "aw", "ī", "ū" }
overridable_stems.nonpast_final_weak_vowel = simple_choice { "ā", "ī", "ū" }
overridable_stems.nonpast_pass_final_weak_vowel = simple_choice { "ā", "ī", "ū" }
-------------------------------------------------------------------------------
-- Utility functions --
-------------------------------------------------------------------------------
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
return (rsubn(term, foo, bar))
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- Concatenate one or more strings or form objects.
local function q(...)
local not_all_strings = debug_translit
local has_manual_translit = debug_translit
for i = 1, select("#", ...) do
local argt = select(i, ...)
if not argt then
error(("Internal error: Saw nil at index %s: %s"):format(i, dump({...})))
end
if type(argt) ~= "string" then
not_all_strings = true
if argt.translit then
has_manual_translit = true
break
end
end
end
if not not_all_strings then
-- just strings, concatenate directly
return table.concat({...})
end
local formvals = {}
local translit = has_manual_translit and {} or nil
local footnotes
for i = 1, select("#", ...) do
local argt = select(i, ...)
if type(argt) == "string" then
formvals[i] = argt
if has_manual_translit then
translit[i] = transliterate(argt)
end
else
formvals[i] = argt.form
if has_manual_translit then
translit[i] = argt.translit or transliterate(argt.form)
end
footnotes = iut.combine_footnotes(footnotes, argt.footnotes)
end
end
-- FIXME: Do we want to support other properties?
return {
form = table.concat(formvals),
translit = has_manual_translit and table.concat(translit) or nil,
footnotes = footnotes,
}
end
-- Return the formval associated with `rad` (a radical or past/non-past vowel, either a string or form object).
local function rget(rad)
if type(rad) == "string" then
return rad
elseif type(rad) == "table" then
return rad.form
else
error(("Internal error: Unexpected type for radical or past/non-past vowel: %s"):format(dump(rad)))
end
end
export.rget = rget -- for use in [[Module:ar-headword]]
-- Return the footnotes associated with `rad` (a radical or past/non-past vowel, either a string or form object).
local function rget_footnotes(rad)
if type(rad) == "string" then
return nil
elseif type(rad) == "table" then
return rad.footnotes
else
error(("Internal error: Unexpected type for radical or past/non-past vowel: %s"):format(dump(rad)))
end
end
-- Return true if the formval associated with `rad` (a radical or past/non-past vowel, either a string or form object)
-- is `val`.
local function req(rad, val)
return rget(rad) == val
end
-- Map `vow` (a past/non-past vowel, either a string or form object without translit) by passing the formval through
-- `fn`. Don't call this on radicals because they may have manual translit and it isn't clear how to handle that.
local function map_vowel(vow, fn)
if type(vow) == "string" then
return fn(vow)
elseif type(vow) == "table" then
return {form = fn(vow.form), footnotes = vow.footnotes}
else
error(("Internal error: Unexpected type for past/non-past vowel: %s"):format(dump(vow)))
end
end
local function get_radicals_3(vowel_spec)
return vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3, vowel_spec.past, vowel_spec.nonpast
end
local function get_radicals_4(vowel_spec)
return vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3, vowel_spec.rad4
end
local function is_final_weak(base, vowel_spec)
return vowel_spec.weakness == "final-weak" or base.form == "XV"
end
local function link_term(text, face, id)
return m_links.full_link({lang = lang, term = text, tr = "-", id = id}, face)
end
local function tag_text(text, tag, class)
return m_links.full_link({lang = lang, alt = text, tr = "-"})
end
local function track(page)
require("Module:debug/track")("ar-verb/" .. page)
return true
end
local function track_if_ar_conj(base, page)
if base.alternant_multiword_spec.source_template == "ar-conj" then
require("Module:debug/track")("ar-verb/" .. page)
end
return true
end
local function reorder_shadda(word)
-- shadda+short-vowel (including tanwīn vowels, i.e. -an -in -un) gets
-- replaced with short-vowel+shadda during NFC normalisation, which
-- MediaWiki does for all Unicode strings; however, it makes various
-- processes inconvenient, so undo it.
word = rsub(word, "(" .. DIACRITIC_ANY_BUT_SH .. ")" .. SH, SH .. "%1")
return word
end
-------------------------------------------------------------------------------
-- Basic functions to inflect tenses --
-------------------------------------------------------------------------------
local function skip_slot(base, slot, allow_overrides)
if base.slot_explicitly_missing[slot] then
return true
end
if not allow_overrides and base.slot_overrides[slot] and not base.slot_override_uses_default[slot] then
-- Skip any slots for which there are overrides, except those that request the default value using +, ++, etc.
return true
end
if base.passive == "nopass" and (slot == "pp" or slot:find("_pass")) then
return true
elseif base.passive == "onlypass" and slot ~= "pp" and slot ~= "vn" and not slot:find("_pass") then
return true
elseif base.passive == "ipass" and slot:find("_pass") and not slot:find("3ms") then
return true
elseif base.passive == "onlypass-impers" and slot ~= "pp" and slot ~= "vn" and (not slot:find("_pass") or
slot:find("_pass") and not slot:find("3ms")) then
return true
end
if base.nopast and slot:find("^past_") then
return true
end
if base.noimp and slot:find("^imp_") then
return true
end
if base.no_nonpast and (slot:find("^ind_") or slot:find("^sub_") or slot:find("^juss")) then
return true
end
return false
end
local function basic_combine_stem_ending(stem, ending)
return stem .. ending
end
local function basic_combine_stem_ending_tr(stem, ending)
return stem .. ending
end
-- Concatenate `prefixes`, `stems` and `endings` (any of which may be an abbreviate form list, i.e. strings, form
-- objects or lists of strings or form objects) and store into `slot`. If a user-supplied override exists for the slot,
-- nothing will happen unless `allow_overrides` is provided.
local function add3(base, slot, prefixes, stems, endings, allow_overrides)
if skip_slot(base, slot, allow_overrides) then
return
end
-- Optimization since the prefixes are almost always single strings.
if type(prefixes) == "string" then
local function do_combine_stem_ending(stem, ending)
return prefixes .. stem .. ending
end
local function do_combine_stem_ending_tr(stem, ending)
return transliterate(prefixes) .. stem .. ending
end
iut.add_forms(base.forms, slot, stems, endings, do_combine_stem_ending, transliterate,
do_combine_stem_ending_tr, base.form_footnotes)
else
iut.add_multiple_forms(base.forms, slot, {prefixes, stems, endings}, basic_combine_stem_ending, transliterate,
basic_combine_stem_ending_tr, base.form_footnotes)
end
end
-- Insert one or more forms in `form_or_forms` into `slot`. `form_or_forms` is an abbreviated form list (see comment at
-- top of [[Module:inflection utilities]]). If a user-supplied override exists for the slot, nothing will happen unless
-- `allow_overrides` is provided. BEWARE: One form object should never occur in two different slots, or twice in a given
-- slot; if taking a form object from an existing slot, make sure to shallowCopy() it.
local function insert_form_or_forms(base, slot, form_or_forms, allow_overrides, uncertain)
if not skip_slot(base, slot, allow_overrides) then
-- Some optimizations of the most common case of inserting a single string.
if type(form_or_forms) == "string" and not base.form_footnotes then
form_or_forms = {form = form_or_forms, uncertain = uncertain}
iut.insert_form(base.forms, slot, form_or_forms)
else
local list = iut.convert_to_general_list_form(form_or_forms, base.form_footnotes)
if uncertain then
for _, formobj in ipairs(list) do
formobj.uncertain = true
end
end
iut.insert_forms(base.forms, slot, list)
end
end
end
-- Insert `string_or_form` into both the ap2 and pp2 slots, shallowCopying a form object to make sure no form objects
-- occur in two slots.
local function insert_ap2_pp2(base, string_or_form)
insert_form_or_forms(base, "ap2", string_or_form)
if type(string_or_form) == "table" then
string_or_form = m_table.shallowCopy(string_or_form)
end
insert_form_or_forms(base, "pp2", string_or_form)
end
-- Convert `stemforms` (a string, a form object, or a list of strings and/or form objects) into "general form" (a list
-- of form objects) and map `fn` over the list of objects. `fn` is passed two arguments (form value and translit) and
-- should likewise return the new form value and translit. Footnotes will be preserved. FIXME: Preserve other metadata.
local function map_general(stemforms, fn)
return iut.map_forms(iut.convert_to_general_list_form(stemforms), fn)
end
-- Similar to map_general() except that `fn` should return a single value (one or more strings or form objects), instead
-- of two values (form value and translit), and the resulting value(s) from all calls to `fn` will be flattened to
-- construct the overall return value. Footnotes will be preserved. FIXME: Preserve other metadata.
local function flatmap_general(stemforms, fn)
return iut.flatmap_forms(iut.convert_to_general_list_form(stemforms), fn)
end
-- Given user-supplied stem overrides in `base`, construct any derived stem overrides (e.g. vowel-specific or
-- consonant-specific variants), and truncate initial y-/ي- in any non-past overrides.
local function construct_stems(base)
local stems = base.stem_overrides
stems.past_v = stems.past_v or stems.past
stems.past_c = stems.past_c or stems.past
stems.past_pass_v = stems.past_pass_v or stems.past_pass
stems.past_pass_c = stems.past_pass_c or stems.past_pass
stems.nonpast_v = stems.nonpast_v or stems.nonpast
stems.nonpast_c = stems.nonpast_c or stems.nonpast
stems.nonpast_pass_v = stems.nonpast_pass_v or stems.nonpast_pass
stems.nonpast_pass_c = stems.nonpast_pass_c or stems.nonpast_pass
stems.imp_v = stems.imp_v or stems.imp
stems.imp_c = stems.imp_c or stems.imp
local function truncate_nonpast_initial_cons(stem_type, form, translit)
if form == "+" then
return form, translit
end
if not form:find("^" .. Y) then
error(("Form value %s for stem type '%s' should begin with ي"):format(form, stem_type))
end
form = form:gsub("^" .. Y, "")
if translit then
if not translit:find("^y") then
error(("Translit value %s for stem type '%s' should begin with y"):format(translit, stem_type))
end
translit = translit:gsub("^y", "")
end
return form, translit
end
for _, nonpast_stem_type in ipairs { "nonpast_v", "nonpast_c", "nonpast_pass_v", "nonpast_pass_c" } do
if stems[nonpast_stem_type] then
stems[nonpast_stem_type] = map_general(stems[nonpast_stem_type], function(form, translit)
return truncate_nonpast_initial_cons(nonpast_stem_type, form, translit)
end)
end
end
end
-- Given user-specified overrides for stem `stemname`, return overrides with occurrences of + replaced by
-- `default_stem`. If no overrides, return `default_stem`, or {} if no default.
local function override_stem_if_needed(base, stemname, default_stem)
local overrides = base.stem_overrides[stemname]
if not overrides then
return default_stem or {}
end
return map_general(overrides, function(form, translit)
if form ~= "+" and default_indicator_to_active_participle_slot[form] then
error(("Stem overrides cannot use secondary default indicators but saw %s in stem override '%s'"):format(
form, stemname))
end
if form == "+" then
if translit then
error(("Cannot supply manual translit along with + for stem override '%s'"):format(stemname))
end
if not default_stem then
error(("Cannot use + for stem override '%s' because no default is available"):format(stemname))
end
if type(default_stem) ~= "string" then
error(("Internal error: Default stem for '%s' is not a string: %s"):format(stemname, dump(default_stem)))
end
return default_stem
end
return form, translit
end)
end
-------------------------------------------------------------------------------
-- Properties of different verbal forms --
-------------------------------------------------------------------------------
local allowed_vforms = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX",
"X", "XI", "XII", "XIII", "XIV", "XV", "Iq", "IIq", "IIIq", "IVq"}
local allowed_vforms_set = m_table.listToSet(allowed_vforms)
local allowed_vforms_with_weakness = m_table.shallowCopy(allowed_vforms)
-- The user needs to be able to explicitly specify that a form-I verb (specifically one whose initial radical is و) is
-- sound. Cf. wajiʕa yawjaʕu (not #yajaʕu) "to ache, to hurt". In general, i~a and u~u verbs whose initial radical is و
-- seem to not assimilate the first radical; cf. وقح "to be shameless", variously waqaḥa~yaqiḥu, waquḥa~yawquḥu and
-- waqiḥa~yawqaḥu, whereas a~i verbs (wafaḍa~yafiḍu "to rush"), i~i verbs (wafiqa~yafiqu "to be proper, to be suitable")
-- and a~a verbs (waḍaʕa~yaḍaʕu "to set down, to place") do assimilate. But there are naturally exceptions, e.g.
-- waṭiʔa~yaṭaʔu "to tread, to trample"; wasiʕa~yasaʕu "to be spacious; to be well-off"; waṯiʔa~yaṯaʔu "to get bruised,
-- to be sprained". Also beware of waniya~yawnā "to be faint; to languish", which is sound in the first radical and
-- final-weak in the last radical. Nonetheless, the regularity of the patterns mentioned above suggest we should provide
-- them as defaults.
-- Note that there are other cases of unexpectedly sound verbs, e.g. izdawaja~yazdawiju "to be in pairs", layisa~yalyasu
-- "to be valiant, to be brave", ʔaḥwaja~yuḥwiju "to need", istahwana~yastahwinu "to consider easy", sawisa~yaswasu "to
-- be or become moth-eaten or worm-eaten" (vs. sāsa~yasūsu "to govern, to rule" from the same radicals), ʕawira~yaʕwaru
-- "to be one-eyed", istajwaba~yastajwibu "to interrogate", etc. But in these cases there is no need for explicit user
-- specification as the lemma itself specifies the unexpected soundness.
for _, form_with_weakness in ipairs { "I-sound", "I-assimilated", "none-sound", "none-hollow", "none-geminate",
"none-final-weak" } do
table.insert(allowed_vforms_with_weakness, form_with_weakness)
end
local allowed_vforms_with_weakness_set = m_table.listToSet(allowed_vforms_with_weakness)
local function vform_supports_final_weak(vform)
return vform ~= "XI" and vform ~= "XV" and vform ~= "IVq"
end
local function vform_supports_geminate(vform)
return vform == "I" or vform == "III" or vform == "IV" or vform == "VI" or vform == "VII" or vform == "VIII" or
vform == "X"
end
local function vform_supports_hollow(vform)
return vform == "I" or vform == "IV" or vform == "VII" or vform == "VIII" or vform == "X"
end
local function vform_probably_impersonal_passive(vform, weakness, past_vowel, nonpast_vowel)
return vform == "I" and req(past_vowel, I) or vform == "V" or vform == "VI" or vform == "X" or vform == "IIq"
end
local function vform_probably_full_passive(vform)
return vform == "II" or vform == "III" or vform == "IV" or vform == "Iq"
end
local function vform_probably_no_passive(vform, weakness, past_vowel, nonpast_vowel)
return vform == "I" and req(past_vowel, U) or vform == "VII" or vform == "IX" or
vform == "XI" or vform == "XII" or vform == "XIII" or vform == "XIV" or vform == "XV" or
vform == "IIIq" or vform == "IVq"
end
-- Active vforms II, III, IV, Iq use non-past prefixes in -u- instead of -a-.
local function prefix_vowel_from_vform(vform)
if vform == "II" or vform == "III" or vform == "IV" or vform == "Iq" then
return "u"
else
return "a"
end
end
-- True if the active non-past takes a-vocalization rather than i-vocalization in its last syllable.
local function vform_nonpast_a_vowel(vform)
return vform == "V" or vform == "VI" or vform == "XV" or vform == "IIq"
end
-- True if the `passive` spec indicates a passive-only verb.
local function is_passive_only(passive)
return passive == "onlypass" or passive == "onlypass-impers"
end
export.is_passive_only = is_passive_only -- for use in [[Module:ar-headword]]
-------------------------------------------------------------------------------
-- Properties of specific sounds --
-------------------------------------------------------------------------------
-- Is radical wāw (و) or yāʾ (ي)?
local function is_waw_ya(rad)
return req(rad, W) or req(rad, Y)
end
-- Check that radical is wāw (و) or yāʾ (ي), error if not
local function check_waw_ya(rad)
if not is_waw_ya(rad) then
error("Expecting weak radical: '" .. rget(rad) .. "' should be " .. W .. " or " .. Y)
end
end
-- Form-I verb حيّ or حيي and form-X verb استحيا or استحى
local function hayy_radicals(rad1, rad2, rad3)
return req(rad1, "ح") and req(rad2, Y) and is_waw_ya(rad3)
end
-- FUCK ME HARD. "Lua error at line 1514: main function has more than 200 local variables".
local function create_conjugations()
-------------------------------------------------------------------------------
-- Radicals associated with various irregular verbs --
-------------------------------------------------------------------------------
-- Form-I verb أخذ or form-VIII verb اتخذ
local function axadh_radicals(rad1, rad2, rad3)
return req(rad1, HAMZA) and req(rad2, "خ") and req(rad3, "ذ")
end
-- Form-I verb whose imperative has a reduced form: أكل and أخذ and أمر. Return "shortonly" if only
-- short-form imperatives exist (أكل and أخذ) or "shortlong" if long-form imperatives also exist (أمر);
-- they are used after a clitic like فَ and وَ.
local function reduced_imperative_verb(rad1, rad2, rad3)
return axadh_radicals(rad1, rad2, rad3) and "shortonly" or
req(rad1, HAMZA) and req(rad2, "ك") and req(rad3, "ل") and "shortonly" or
req(rad1, HAMZA) and req(rad2, "م") and req(rad3, "ر") and "shortlong"
end
-- Form-I verb رأى and form-IV verb أرى
local function raa_radicals(rad1, rad2, rad3)
return req(rad1, "ر") and req(rad2, HAMZA) and is_waw_ya(rad3)
end
-- Form-I verb سأل
local function saal_radicals(rad1, rad2, rad3)
return req(rad1, "س") and req(rad2, HAMZA) and req(rad3, "ل")
end
-- Form-I verb كان
local function kaan_radicals(rad1, rad2, rad3)
return req(rad1, "ك") and req(rad2, W) and req(rad3, N)
end
-------------------------------------------------------------------------------
-- Sets of past endings --
-------------------------------------------------------------------------------
-- The 13 endings of the sound/hollow/geminate past tense.
local past_endings = {
-- singular
SK .. TU, SK .. TA, SK .. "تِ", A, A .. "تْ",
--dual
SK .. "تُمَا", AA, A .. "تَا",
-- plural
SK .. "نَا", SK .. "تُمْ",
-- shadda + vowel diacritic ends up in the wrong order due to Unicode
-- bug, so keep them separate to avoid this
SK .. "تُن" .. SH .. A, UU .. ALIF, SK .. "نَ"
}
-- Make endings for final-weak past in -aytu or -awtu. AYAW is AY or AW as appropriate. Note that AA and AW are
-- global variables.
local function make_past_endings_ay_aw(ayaw, third_sg_masc)
return {
-- singular
ayaw .. SK .. TU, ayaw .. SK .. TA, ayaw .. SK .. "تِ",
third_sg_masc, A .. "تْ",
--dual
ayaw .. SK .. "تُمَا", ayaw .. AA, A .. "تَا",
-- plural
ayaw .. SK .. "نَا", ayaw .. SK .. "تُمْ",
-- shadda + vowel diacritic ends up in the wrong order due to Unicode
-- bug, so keep them separate to avoid this
ayaw .. SK .. "تُن" .. SH .. A, AW .. SK .. ALIF, ayaw .. SK .. "نَ"
}
end
-- past final-weak -aytu endings
local past_endings_ay = make_past_endings_ay_aw(AY, AAMAQ)
-- past final-weak -awtu endings
local past_endings_aw = make_past_endings_ay_aw(AW, AA)
-- used for alternative endings for form-X geminate verbs like اِسْتَمَرَّ
local past_endings_ay_12_person_only = {
-- singular
AY .. SK .. TU, AY .. SK .. TA, AY .. SK .. "تِ",
{}, {},
--dual
AY .. SK .. "تُمَا", {}, {},
-- plural
AY .. SK .. "نَا", AY .. SK .. "تُمْ",
-- shadda + vowel diacritic ends up in the wrong order due to Unicode
-- bug, so keep them separate to avoid this
AY .. SK .. "تُن" .. SH .. A, {}, {},
}
-- Make endings for final-weak past in -ītu or -ūtu. IIUU is ī or ū as appropriate. Note that AA and UU are global
-- variables.
local function make_past_endings_ii_uu(iiuu)
return {
-- singular
iiuu .. TU, iiuu .. TA, iiuu .. "تِ", iiuu .. A, iiuu .. A .. "تْ",
--dual
iiuu .. "تُمَا", iiuu .. AA, iiuu .. A .. "تَا",
-- plural
iiuu .. "نَا", iiuu .. "تُمْ",
-- shadda + vowel diacritic ends up in the wrong order due to Unicode
-- bug, so keep them separate to avoid this
iiuu .. "تُن" .. SH .. A, UU .. ALIF, iiuu .. "نَ"
}
end
-- past final-weak -ītu endings
local past_endings_ii = make_past_endings_ii_uu(II)
-- past final-weak -ūtu endings
local past_endings_uu = make_past_endings_ii_uu(UU)
-------------------------------------------------------------------------------
-- Sets of non-past prefixes and endings --
-------------------------------------------------------------------------------
local nonpast_prefix_consonants = {
-- singular
HAMZA, T, T, Y, T,
-- dual
T, Y, T,
-- plural
N, T, T, Y, Y
}
-- There are only five distinct endings in all non-past verbs. Make any set of non-past endings given these five
-- distinct endings.
local function make_nonpast_endings(null, fem, dual, pl, fempl)
return {
-- singular
null, null, fem, null, null,
-- dual
dual, dual, dual,
-- plural
null, pl, fempl, pl, fempl
}
end
-- endings for non-past indicative
local ind_endings = make_nonpast_endings(
U,
II .. NA,
AANI,
UU .. NA,
SK .. NA
)
-- Make the endings for non-past subjunctive/jussive, given the vowel diacritic used in "null" endings
-- (1s/2ms/3ms/3fs/1p).
local function make_sub_juss_endings(dia_null)
return make_nonpast_endings(
dia_null,
II,
AA,
UU .. ALIF,
SK .. NA
)
end
-- endings for non-past subjunctive
local sub_endings = make_sub_juss_endings(A)
-- endings for non-past jussive
local juss_endings = make_sub_juss_endings(SK)
-- endings for alternative geminate non-past jussive in -a; same as subjunctive
local juss_endings_alt_a = sub_endings
-- endings for alternative geminate non-past jussive in -i
local juss_endings_alt_i = make_sub_juss_endings(I)
-- Endings for final-weak non-past indicative in -ā. Note that AY, AW and AAMAQ are global variables.
local ind_endings_aa = make_nonpast_endings(
AAMAQ,
AYSK .. NA,
AY .. AANI,
AWSK .. NA,
AYSK .. NA
)
-- Make endings for final-weak non-past indicative in -ī or -ū; IIUU is ī or ū as appropriate. Note that II and UU
-- are global variables.
local function make_ind_endings_ii_uu(iiuu)
return make_nonpast_endings(
iiuu,
II .. NA,
iiuu .. AANI,
UU .. NA,
iiuu .. NA
)
end
-- endings for final-weak non-past indicative in -ī
local ind_endings_ii = make_ind_endings_ii_uu(II)
-- endings for final-weak non-past indicative in -ū
local ind_endings_uu = make_ind_endings_ii_uu(UU)
-- Endings for final-weak non-past subjunctive in -ā. Note that AY, AW, ALIF, AAMAQ are global variables.
local sub_endings_aa = make_nonpast_endings(
AAMAQ,
AYSK,
AY .. AA,
AWSK .. ALIF,
AYSK .. NA
)
-- Make endings for final-weak non-past subjunctive in -ī or -ū. IIUU is ī or ū as appropriate. Note that AA, II,
-- UU, ALIF are global variables.
local function make_sub_endings_ii_uu(iiuu)
return make_nonpast_endings(
iiuu .. A,
II,
iiuu .. AA,
UU .. ALIF,
iiuu .. NA
)
end
-- endings for final-weak non-past subjunctive in -ī
local sub_endings_ii = make_sub_endings_ii_uu(II)
-- endings for final-weak non-past subjunctive in -ū
local sub_endings_uu = make_sub_endings_ii_uu(UU)
-- endings for final-weak non-past jussive in -ā
local juss_endings_aa = make_nonpast_endings(
A,
AYSK,
AY .. AA,
AWSK .. ALIF,
AYSK .. NA
)
-- Make endings for final-weak non-past jussive in -ī or -ū. IU is short i or u, IIUU is long ī or ū as appropriate.
-- Note that AA, II, UU, ALIF are global variables.
local function make_juss_endings_ii_uu(iu, iiuu)
return make_nonpast_endings(
iu,
II,
iiuu .. AA,
UU .. ALIF,
iiuu .. NA
)
end
-- endings for final-weak non-past jussive in -ī
local juss_endings_ii = make_juss_endings_ii_uu(I, II)
-- endings for final-weak non-past jussive in -ū
local juss_endings_uu = make_juss_endings_ii_uu(U, UU)
-------------------------------------------------------------------------------
-- Sets of imperative endings --
-------------------------------------------------------------------------------
-- Extract the second person jussive endings to get corresponding imperative endings.
local function imperative_endings_from_jussive(endings)
return {endings[2], endings[3], endings[6], endings[10], endings[11]}
end
-- normal imperative endings
local imp_endings = imperative_endings_from_jussive(juss_endings)
-- alternative geminate imperative endings in -a
local imp_endings_alt_a = imperative_endings_from_jussive(juss_endings_alt_a)
-- alternative geminate imperative endings in -i
local imp_endings_alt_i = imperative_endings_from_jussive(juss_endings_alt_i)
-- final-weak imperative endings in -ā
local imp_endings_aa = imperative_endings_from_jussive(juss_endings_aa)
-- final-weak imperative endings in -ī
local imp_endings_ii = imperative_endings_from_jussive(juss_endings_ii)
-- final-weak imperative endings in -ū
local imp_endings_uu = imperative_endings_from_jussive(juss_endings_uu)
-------------------------------------------------------------------------------
-- Basic functions to inflect tenses --
-------------------------------------------------------------------------------
-- Add to `base` the inflections for the tense indicated by `tense` (the prefix in the slot names, e.g. 'past'
-- or 'juss_pass'), formed by combining the `prefixes`, `stems` and `endings`. Each of `prefixes`, `stems` and
-- `endings` is either a sequence of 5 (for the imperative) or 13 (for other tenses) abbreviated form lists (each of
-- which is either a string, a form object, or a list of strings and/or form objects; see
-- [[Module:inflection utilities]] for more info). Alternatively, any of `prefixes`, `stems` or `endings` can be a
-- single-element list containing an abbreviated form list, with an additional key `all_same` set to true, or (as a
-- special case) a single string; in the latter cases, the same value is used for all 5 or 13 slots. If existing
-- inflections already exist, they will be added to, not overridden. `pnums` is the list of person/number slot name
-- suffixes, which must match up with the elements in `prefixes`, `stems` and `endings` (i.e. 5 for imperative, 13
-- otherwise).
local function inflect_tense_1(base, tense, prefixes, stems, endings, pnums)
if not prefixes or not stems or not endings then
return
end
local function verify_affixes(affixname, affixes)
local function interr(msg)
error(("Internal error: For tense '%s', '%s' %s: %s"):format(tense, affixname, msg, dump(affixes)))
end
if type(affixes) == "string" then
-- do nothing
elseif type(affixes) ~= "table" then
interr("is not a table or string")
elseif affixes.all_same then
if #affixes ~= 1 then
interr(("with all_same = true should have length 1 but has length %s"):format(#affixes))
end
else
if #affixes ~= #pnums then
interr(("should have length %s but has length %s"):format(#pnums, #affixes))
end
end
end
verify_affixes("prefixes", prefixes)
verify_affixes("stems", stems)
verify_affixes("endings", endings)
local function get_affix(affixes, i)
if type(affixes) == "string" then
return affixes
elseif affixes.all_same then
return affixes[1]
else
return affixes[i]
end
end
for i, pnum in ipairs(pnums) do
local prefix = get_affix(prefixes, i)
local stem = get_affix(stems, i)
local ending = get_affix(endings, i)
local slot = tense .. "_" .. pnum
add3(base, slot, prefix, stem, ending)
end
end
-- Add to `base` the inflections for the tense indicated by `tense` (the prefix in the slot names, e.g. 'past'
-- or 'juss_pass'), formed by combining the `prefixes`, `stems` and `endings`. This is a simple wrapper around
-- inflect_tense_1() that applies to all tenses other than the imperative; see inflect_tense_1() for more
-- information about the parameters.
local function inflect_tense(base, tense, prefixes, stems, endings)
inflect_tense_1(base, tense, prefixes, stems, endings, all_person_number_list)
end
-- Like inflect_tense() but for the imperative, which has only five parts instead of 13 and no prefixes.
local function inflect_tense_imp(base, stems, endings)
inflect_tense_1(base, "imp", "", stems, endings, imp_person_number_list)
end
-------------------------------------------------------------------------------
-- Functions to inflect the past tense --
-------------------------------------------------------------------------------
-- Generate past verbs using specified vowel and consonant stems; works for sound, assimilated, hollow, and geminate
-- verbs, active and passive.
local function past_2stem_conj(base, tense, v_stem, c_stem, footnote_12)
local passive = tense:find("_pass") and "_pass" or ""
-- Override stems with user-specified stems if available.
v_stem = override_stem_if_needed(base, "past" .. passive .. "_v", v_stem)
local c_stem_12 = c_stem
if footnote_12 then
c_stem_12 = iut.combine_form_and_footnotes(c_stem_12, footnote_12)
end
c_stem_12 = override_stem_if_needed(base, "past" .. passive .. "_c", c_stem_12)
local c_stem_3 = override_stem_if_needed(base, "past" .. passive .. "_c", c_stem)
inflect_tense(base, tense, "", {
-- singular
c_stem_12, c_stem_12, c_stem_12, v_stem, v_stem,
--dual
c_stem_12, v_stem, v_stem,
-- plural
c_stem_12, c_stem_12, c_stem_12, v_stem, c_stem_3
}, past_endings)
end
-- Generate past verbs using single specified stem; works for sound and assimilated verbs, active and passive.
local function past_1stem_conj(base, tense, stem)
past_2stem_conj(base, tense, stem, stem)
end
-------------------------------------------------------------------------------
-- Functions to inflect non-past tenses --
-------------------------------------------------------------------------------
-- Generate non-past conjugation, with two stems, for vowel-initial and consonant-initial endings, respectively.
-- Useful for active and passive; for all forms; for all weaknesses (sound, assimilated, hollow, final-weak and
-- geminate) and for all types of non-past (indicative, subjunctive, jussive) except for the imperative. (There is a
-- separate wrapper function below for geminate jussives because they have three alternants.) Both stems may be the
-- same, e.g. for sound verbs.
-- `prefix_vowel` will be either "a" or "u". `endings` should be an array of 13 items. If `endings` is nil or
-- omitted, infer the endings from the tense. If `jussive` is true, or `endings` is nil and `tense` indicatives
-- jussive, use the jussive pattern of vowel/consonant stems (different from the normal ones).
local function nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, endings, jussive)
local passive = tense:find("_pass") and "_pass" or ""
-- Override stems with user-specified stems if available.
v_stem = override_stem_if_needed(base, "nonpast" .. passive .. "_v",
v_stem and q(dia[prefix_vowel], v_stem) or nil)
c_stem = override_stem_if_needed(base, "nonpast" .. passive .. "_c",
c_stem and q(dia[prefix_vowel], c_stem) or nil)
if not endings then
if tense:find("^ind") then
endings = ind_endings
elseif tense:find("^sub") then
endings = sub_endings
elseif tense:find("^juss") then
jussive = true
endings = juss_endings
else
error("Internal error: Unrecognized tense '" .. tense .."'")
end
end
if not jussive then
inflect_tense(base, tense, nonpast_prefix_consonants, {
-- singular
v_stem, v_stem, v_stem, v_stem, v_stem,
--dual
v_stem, v_stem, v_stem,
-- plural
v_stem, v_stem, c_stem, v_stem, c_stem
}, endings)
else
inflect_tense(base, tense, nonpast_prefix_consonants, {
-- singular
-- 'adlul, tadlul, tadullī, yadlul, tadlul
c_stem, c_stem, v_stem, c_stem, c_stem,
--dual
-- tadullā, yadullā, tadullā
v_stem, v_stem, v_stem,
-- plural
-- nadlul, tadullū, tadlulna, yadullū, yadlulna
c_stem, v_stem, c_stem, v_stem, c_stem
}, endings)
end
end
-- Generate non-past conjugation with one stem (no distinct stems for vowel-initial and consonant-initial endings).
-- See nonpast_2stem_conj().
local function nonpast_1stem_conj(base, tense, prefix_vowel, stem, endings, jussive)
nonpast_2stem_conj(base, tense, prefix_vowel, stem, stem, endings, jussive)
end
-- Generate active/passive jussive geminative. There are three alternants, two with terminations -a and -i and one
-- in a null termination with a distinct pattern of vowel/consonant stem usage. See nonpast_2stem_conj() for a
-- description of the arguments.
local function jussive_gem_conj(base, tense, prefix_vowel, v_stem, c_stem)
-- alternative in -a
nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings_alt_a)
-- alternative in -i
nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings_alt_i)
-- alternative in -null; requires different combination of v_stem and
-- c_stem since the null endings require the c_stem (e.g. "tadlul" here)
-- whereas the corresponding endings above in -a or -i require the v_stem
-- (e.g. "tadulla, tadulli" above)
nonpast_2stem_conj(base, tense, prefix_vowel, v_stem, c_stem, juss_endings, "jussive")
end
-------------------------------------------------------------------------------
-- Functions to inflect the imperative --
-------------------------------------------------------------------------------
-- Generate imperative conjugation, with two stems, for vowel-initial and consonant-initial endings, respectively.
-- Useful for all forms, and for all weaknesses other than final-weak. Note that the two stems may be the same
-- (specifically for sound and assimilated verbs). If `endings` is nil or omitted, use `imp_endings`. If `alt_gem`
-- is specified, use the pattern of vowel and consonant stems appropriate for the alternative geminate imperatives
-- that use a null ending of -a or -i instead of an empty ending.
local function make_2stem_imperative(base, v_stem, c_stem, endings, alt_gem)
endings = endings or imp_endings
-- Override stems with user-specified stems if available.
v_stem = override_stem_if_needed(base, "imp_v", v_stem)
c_stem = override_stem_if_needed(base, "imp_c", c_stem)
if alt_gem then
inflect_tense_imp(base, {v_stem, v_stem, v_stem, v_stem, c_stem}, endings)
else
inflect_tense_imp(base, {c_stem, v_stem, v_stem, v_stem, c_stem}, endings)
end
end
-- Generate imperative parts for sound or assimilated verbs.
local function make_1stem_imperative(base, stem)
make_2stem_imperative(base, stem, stem)
end
-- Generate imperative parts for geminate verbs form I (also IV, VII, VIII, X).
local function make_gem_imperative(base, v_stem, c_stem)
make_2stem_imperative(base, v_stem, c_stem, imp_endings_alt_a, "alt gem")
make_2stem_imperative(base, v_stem, c_stem, imp_endings_alt_i, "alt gem")
make_2stem_imperative(base, v_stem, c_stem)
end
-------------------------------------------------------------------------------
-- Functions to inflect entire verbs --
-------------------------------------------------------------------------------
-- Generate finite parts of a sound verb (also works for assimilated verbs) from five stems (past and non-past,
-- active and passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u").
local function make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem,
prefix_vowel)
past_1stem_conj(base, "past", past_stem)
past_1stem_conj(base, "past_pass", past_pass_stem)
nonpast_1stem_conj(base, "ind", prefix_vowel, nonpast_stem)
nonpast_1stem_conj(base, "sub", prefix_vowel, nonpast_stem)
nonpast_1stem_conj(base, "juss", prefix_vowel, nonpast_stem)
nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem)
nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem)
nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem)
make_1stem_imperative(base, imp_stem)
end
local function past_final_weak_endings_from_vowel(vowel)
if vowel == "ay" then
return past_endings_ay
elseif vowel == "aw" then
return past_endings_aw
elseif vowel == "ī" then
return past_endings_ii
elseif vowel == "ū" then
return past_endings_uu
elseif not vowel then
return nil
else
error(("Internal error: Unrecognized past final-weak vowel spec '%s'"):format(vowel))
end
end
local function nonpast_final_weak_endings_from_vowel(vowel)
if vowel == "ā" then
return ind_endings_aa, sub_endings_aa, juss_endings_aa, imp_endings_aa
elseif vowel == "ī" then
return ind_endings_ii, sub_endings_ii, juss_endings_ii, imp_endings_ii
elseif vowel == "ū" then
return ind_endings_uu, sub_endings_uu, juss_endings_uu, imp_endings_uu
elseif not vowel then
return nil
else
error(("Internal error: Unrecognized non-past final-weak vowel spec '%s'"):format(vowel))
end
end
-- Generate finite parts of a final-weak verb from five stems (past and non-past, active and passive, plus
-- imperative), the past active ending vowel (ay, aw, ī or ū), the non-past active ending vowel (ā, ī or ū) and the
-- prefix vowel in the active non-past (a or u).
local function make_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem,
past_ending_vowel, nonpast_ending_vowel, prefix_vowel)
past_stem = override_stem_if_needed(base, "past", past_stem)
past_pass_stem = override_stem_if_needed(base, "past_pass", past_pass_stem)
-- Don't call override_stem_if_needed() here for non-past stems; it's called in nonpast_2stem_conj().
imp_stem = override_stem_if_needed(base, "imp", imp_stem)
-- + not supported for ending vowel overrides
past_ending_vowel = base.stem_overrides.past_final_weak_vowel or past_ending_vowel
local past_pass_ending_vowel = base.stem_overrides.past_pass_final_weak_vowel or "ī"
nonpast_ending_vowel = base.stem_overrides.nonpast_final_weak_vowel or nonpast_ending_vowel
local nonpast_pass_ending_vowel = base.stem_overrides.nonpast_pass_final_weak_vowel or "ā"
local past_endings = past_final_weak_endings_from_vowel(past_ending_vowel)
local past_pass_endings = past_final_weak_endings_from_vowel(past_pass_ending_vowel)
local ind_endings, sub_endings, juss_endings, imp_endings =
nonpast_final_weak_endings_from_vowel(nonpast_ending_vowel)
local ind_pass_endings, sub_pass_endings, juss_pass_endings =
nonpast_final_weak_endings_from_vowel(nonpast_pass_ending_vowel)
inflect_tense(base, "past", "", {past_stem, all_same = 1}, past_endings)
inflect_tense(base, "past_pass", "", {past_pass_stem, all_same = 1}, past_pass_endings)
nonpast_1stem_conj(base, "ind", prefix_vowel, nonpast_stem, ind_endings)
nonpast_1stem_conj(base, "sub", prefix_vowel, nonpast_stem, sub_endings)
nonpast_1stem_conj(base, "juss", prefix_vowel, nonpast_stem, juss_endings)
nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem, ind_pass_endings)
nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem, sub_pass_endings)
nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem, juss_pass_endings)
inflect_tense_imp(base, {imp_stem, all_same = 1}, imp_endings)
end
-- Generate finite parts of an augmented (form II+) final-weak verb from five stems (past and non-past, active and
-- passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u") and a flag indicating if it
-- behaves like a form V/VI verb in taking non-past endings in -ā instead of -ī.
local function make_augmented_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem,
imp_stem, prefix_vowel, form56)
make_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, "ay",
form56 and "ā" or "ī", prefix_vowel)
end
-- Generate finite parts of an augmented (form II+) sound or final-weak verb, given:
-- * `base` (conjugation data structure);
-- * `vowel_spec` (radicals, weakness);
-- * `past_stem_base` (active past stem minus last syllable (= -al or -ā));
-- * `nonpast_stem_base` (non-past stem minus last syllable (= -al/-il or -ā/-ī);
-- * `past_pass_stem_base` (passive past stem minus last syllable (= -il or -ī));
-- * `vn` (verbal noun).
local function make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base,
past_pass_stem_base, vn)
insert_form_or_forms(base, "vn", vn)
local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3
local final_weak = is_final_weak(base, vowel_spec)
local prefix_vowel = prefix_vowel_from_vform(base.verb_form)
local form56 = vform_nonpast_a_vowel(base.verb_form)
local a_base_suffix = final_weak and "" or q(A, lastrad)
local i_base_suffix = final_weak and "" or q(I, lastrad)
-- past and non-past stems, active and passive
local past_stem = q(past_stem_base, a_base_suffix)
-- In forms 5 and 6, non-past has /a/ as last stem vowel in the non-past
-- in both active and passive, but /i/ in the active participle and /a/
-- in the passive participle. Elsewhere, consistent /i/ in active non-past
-- and participle, consistent /a/ in passive non-past and participle.
-- Hence, forms 5 and 6 differ only in the non-past active (but not
-- active participle), so we have to split the finite non-past stem and
-- active participle stem.
local nonpast_stem = q(nonpast_stem_base, form56 and a_base_suffix or i_base_suffix)
local ap_stem = q(nonpast_stem_base, i_base_suffix)
local past_pass_stem = q(past_pass_stem_base, i_base_suffix)
local nonpast_pass_stem = q(nonpast_stem_base, a_base_suffix)
-- imperative stem
local imp_stem = q(past_stem_base, form56 and a_base_suffix or i_base_suffix)
-- make parts
if final_weak then
make_augmented_final_weak_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem,
prefix_vowel, form56)
else
make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, prefix_vowel)
end
-- active and passive participle
if final_weak then
insert_form_or_forms(base, "ap", q(MU, ap_stem, IN))
insert_form_or_forms(base, "pp", q(MU, nonpast_pass_stem, AN, AMAQ))
else
insert_form_or_forms(base, "ap", q(MU, ap_stem))
insert_form_or_forms(base, "pp", q(MU, nonpast_pass_stem))
end
end
-- Generate finite parts of a hollow or geminate verb from ten stems (vowel and consonant stems for each of past and
-- non-past, active and passive, plus imperative) plus the prefix vowel in the active non-past ("a" or "u"), plus a
-- flag indicating if we are a geminate verb.
local function make_hollow_geminate_verb(base, geminate, past_v_stem, past_c_stem, past_pass_v_stem,
past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem, nonpast_pass_c_stem, imp_v_stem,
imp_c_stem, prefix_vowel, altgem_note)
past_2stem_conj(base, "past", past_v_stem, past_c_stem, altgem_note)
past_2stem_conj(base, "past_pass", past_pass_v_stem, past_pass_c_stem)
nonpast_2stem_conj(base, "ind", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
nonpast_2stem_conj(base, "sub", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
nonpast_2stem_conj(base, "ind_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem)
nonpast_2stem_conj(base, "sub_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem)
if geminate then
jussive_gem_conj(base, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
jussive_gem_conj(base, "juss_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem)
make_gem_imperative(base, imp_v_stem, imp_c_stem)
else
nonpast_2stem_conj(base, "juss", prefix_vowel, nonpast_v_stem, nonpast_c_stem)
nonpast_2stem_conj(base, "juss_pass", "u", nonpast_pass_v_stem, nonpast_pass_c_stem)
make_2stem_imperative(base, imp_v_stem, imp_c_stem)
end
end
-- Generate finite parts of an augmented (form II+) hollow verb, given:
-- * `base` (conjugation data structure);
-- * `vowel_spec` (radicals, weakness);
-- * `past_stem_base` (invariable part of active past stem);
-- * `nonpast_stem_base` (invariable part of nonpast stem);
-- * `past_pass_stem_base` (invariable part of passive past stem);
-- * `vn` (verbal noun).
local function make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base,
vn)
insert_form_or_forms(base, "vn", vn)
local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3
local form410 = base.verb_form == "IV" or base.verb_form == "X"
local prefix_vowel = prefix_vowel_from_vform(base.verb_form)
local a_base_suffix_v, a_base_suffix_c
local i_base_suffix_v, i_base_suffix_c
a_base_suffix_v = q(AA, lastrad) -- 'af-āl-a, inf-āl-a
a_base_suffix_c = q(A, lastrad) -- 'af-al-tu, inf-al-tu
i_base_suffix_v = q(II, lastrad) -- 'uf-īl-a, unf-īl-a
i_base_suffix_c = q(I, lastrad) -- 'uf-il-tu, unf-il-tu
-- past and non-past stems, active and passive, for vowel-initial and
-- consonant-initial endings
local past_v_stem = q(past_stem_base, a_base_suffix_v)
local past_c_stem = q(past_stem_base, a_base_suffix_c)
-- yu-f-īl-u, ya-staf-īl-u but yanf-āl-u, yaft-āl-u
local nonpast_v_stem = q(nonpast_stem_base, form410 and i_base_suffix_v or a_base_suffix_v)
local nonpast_c_stem = q(nonpast_stem_base, form410 and i_base_suffix_c or a_base_suffix_c)
local past_pass_v_stem = q(past_pass_stem_base, i_base_suffix_v)
local past_pass_c_stem = q(past_pass_stem_base, i_base_suffix_c)
local nonpast_pass_v_stem = q(nonpast_stem_base, a_base_suffix_v)
local nonpast_pass_c_stem = q(nonpast_stem_base, a_base_suffix_c)
-- imperative stem
local imp_v_stem = q(past_stem_base, form410 and i_base_suffix_v or a_base_suffix_v)
local imp_c_stem = q(past_stem_base, form410 and i_base_suffix_c or a_base_suffix_c)
-- make parts
make_hollow_geminate_verb(base, false, past_v_stem, past_c_stem, past_pass_v_stem,
past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem,
nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel)
-- active participle
insert_form_or_forms(base, "ap", q(MU, nonpast_v_stem))
-- passive participle
insert_form_or_forms(base, "pp", q(MU, nonpast_pass_v_stem))
end
-- Generate finite parts of an augmented (form II+) geminate verb, given:
-- * `base` (conjugation data structure);
-- * `vowel_spec` (radicals, weakness);
-- * `past_stem_base` (invariable part of active past stem; this and the stem bases below will end with a consonant
-- for forms IV, X, IVq, and a short vowel for the others);
-- * `nonpast_stem_base` (invariable part of nonpast stem);
-- * `past_pass_stem_base` (invariable part of passive past stem);
-- * `vn` (verbal noun);
-- * `altgem_note` (footnote to add to active past 1/2-person forms, when alternative forms are supplied [form X]).
local function make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base,
past_pass_stem_base, vn, altgem_note)
insert_form_or_forms(base, "vn", vn)
local vform = base.verb_form
local lastrad = base.quadlit and vowel_spec.rad4 or vowel_spec.rad3
local prefix_vowel = prefix_vowel_from_vform(vform)
local a_base_suffix_v, a_base_suffix_c
local i_base_suffix_v, i_base_suffix_c
if vform == "IV" or vform == "X" or vform == "IVq" then
a_base_suffix_v = q(A, lastrad, SH) -- 'af-all
a_base_suffix_c = q(SK, lastrad, A, lastrad) -- 'af-lal
i_base_suffix_v = q(I, lastrad, SH) -- yuf-ill
i_base_suffix_c = q(SK, lastrad, I, lastrad) -- yuf-lil
else
a_base_suffix_v = q(lastrad, SH) -- fā-ll, infa-ll
a_base_suffix_c = q(lastrad, A, lastrad) -- fā-lal, infa-lal
i_base_suffix_v = q(lastrad, SH) -- yufā-ll, yanfa-ll
i_base_suffix_c = q(lastrad, I, lastrad) -- yufā-lil, yanfa-lil
end
-- past and non-past stems, active and passive, for vowel-initial and
-- consonant-initial endings
local past_v_stem = q(past_stem_base, a_base_suffix_v)
local past_c_stem = q(past_stem_base, a_base_suffix_c)
local nonpast_v_stem = q(nonpast_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_v or i_base_suffix_v)
local nonpast_c_stem = q(nonpast_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_c or i_base_suffix_c)
-- NOTE: Formerly had a comment that "vform III and VI passive past do not have contracted parts, only
-- uncontracted parts, which are added separately by those functions". This is based on Mace
-- "Arabic Verbs and Essential Grammar" (1999) entry 63 (continued), which shows passive ḥūjija but no ḥūjja;
-- but that is apparently a mistake, as (1) verb tables in other books do show contracted passive parts for
-- these forms; (2) there is no mention of such an exception on p. 99, which explains how geminate ("doubled")
-- verbs work (on the contrary, it says "The contracted and uncontracted pairs (see above) are found all
-- over Forms III and VI of the doubled verbs").
local past_pass_v_stem = q(past_pass_stem_base, i_base_suffix_v)
local past_pass_c_stem = q(past_pass_stem_base, i_base_suffix_c)
local nonpast_pass_v_stem = q(nonpast_stem_base, a_base_suffix_v)
local nonpast_pass_c_stem = q(nonpast_stem_base, a_base_suffix_c)
-- imperative stem
local imp_v_stem = q(past_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_v or i_base_suffix_v)
local imp_c_stem = q(past_stem_base, vform_nonpast_a_vowel(vform) and a_base_suffix_c or i_base_suffix_c)
-- make parts
make_hollow_geminate_verb(base, "geminate", past_v_stem, past_c_stem, past_pass_v_stem,
past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem,
nonpast_pass_c_stem, imp_v_stem, imp_c_stem, prefix_vowel, altgem_note)
-- active participle
insert_form_or_forms(base, "ap", q(MU, nonpast_v_stem))
-- passive participle
insert_form_or_forms(base, "pp", q(MU, nonpast_pass_v_stem))
end
-------------------------------------------------------------------------------
-- Conjugation functions for specific conjugation types --
-------------------------------------------------------------------------------
local function form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1)
local imp_vowel = map_vowel(nonpast_vowel, function(vow)
if vow == A or vow == I then
return I
elseif vow == U then
return U
elseif not skip_slot(base, "imp_2ms") then
error(("Internal error: Non-past vowel %s isn't a, i, or u, should have been caught earlier"):format(
dump(nonpast_vowel)))
else
-- Passive-only; imperative won't ever be displayed so it doesn't matter.
return I
end
end)
-- Mace ("Arabic Verbs and Essentials of Grammar" p. 63: [https://archive.org/details/arabicverbsessen00john/page/62/mode/2up])
-- claims that initial hamza is assimilated/elided into a long vowel in the form-I imperative, but apparently
-- this isn't corrrect.
local vowel_on_alif = map_vowel(imp_vowel, function(vow)
return ALIF .. vow
end)
return q(vowel_on_alif, rad1, SK)
end
-- Implement form-I sound or assimilated verb. ASSIMILATED is true for assimilated verbs.
local function make_form_i_sound_assimilated_verb(base, vowel_spec, assimilated)
local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec)
-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied
-- past and non-past stems, active and passive
local past_stem = q(rad1, A, rad2, past_vowel, rad3)
local nonpast_stem = assimilated and q(rad2, nonpast_vowel, rad3) or
q(rad1, SK, rad2, nonpast_vowel, rad3)
local past_pass_stem = q(rad1, U, rad2, I, rad3)
local nonpast_pass_stem = q(rad1, SK, rad2, A, rad3)
-- imperative stem
-- check for irregular verb with reduced imperative (أَخَذَ or أَكَلَ or أَمَرَ)
local reducedimp = reduced_imperative_verb(rad1, rad2, rad3)
if reducedimp then
base.irregular = true
end
local imp_stem_suffix = q(rad2, nonpast_vowel, rad3)
local long_imp_stem_base = form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1)
local short_imp_stem_base = ""
local imp_stem = q((assimilated or reducedimp) and "" or long_imp_stem_base, imp_stem_suffix)
-- make parts
make_sound_verb(base, past_stem, past_pass_stem, nonpast_stem, nonpast_pass_stem, imp_stem, "a")
if reducedimp == "shortlong" then
make_1stem_imperative(base, iut.combine_form_and_footnotes(q(long_imp_stem_base, imp_stem_suffix),
mw.getCurrentFrame():preprocess("[used especially with a clitic such as {{m|ar|فَ}} or {{m|ar|وَ}}]")))
end
-- Check for irregular verb سَأَلَ with alternative jussive and imperative. Calling this after make_sound_verb()
-- adds additional entries to the paradigm parts.
if saal_radicals(rad1, rad2, rad3) then
base.irregular = true
nonpast_1stem_conj(base, "juss", "a", "سَل")
nonpast_1stem_conj(base, "juss_pass", "u", "سَل")
make_1stem_imperative(base, "سَل")
end
-- Active participle.
insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, I, rad3))
-- Insert alternative active participle (stative type I) فَعِيل. Since not all verbs have this, we require that
-- verbs that do have it specify it explicitly; a shortcut ++ is provided to make this easier (e.g. <ap:++> to
-- indicate that the alternative form should be used for the active participle, <ap:+,++> to indicate that both
-- forms can be used, and <ap:-> to indicate that there is no active participle). The same form is used for
-- secondary default passive participle.
insert_ap2_pp2(base, q(rad1, A, rad2, II, rad3))
-- Active participle, stative type II فَعِل (+++).
insert_form_or_forms(base, "ap3", q(rad1, A, rad2, I, rad3))
-- Active participle, color/defect أَفْعَل (+cd).
insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, A, rad3))
-- Active participle, -ān فَعْلَان (+an).
insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN))
-- Passive participle.
insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, UU, rad3))
end
conjugations["I-sound"] = function(base, vowel_spec)
make_form_i_sound_assimilated_verb(base, vowel_spec, false)
end
conjugations["none-sound"] = function(base, vowel_spec)
-- All default stems are nil.
make_sound_verb(base)
end
conjugations["none-hollow"] = function(base, vowel_spec)
-- All default stems are nil.
make_hollow_geminate_verb(base, false)
end
conjugations["none-geminate"] = function(base, vowel_spec)
-- All default stems are nil.
make_hollow_geminate_verb(base, "geminate")
end
conjugations["none-final-weak"] = function(base, vowel_spec)
-- All default stems are nil.
make_final_weak_verb(base)
end
conjugations["I-assimilated"] = function(base, vowel_spec)
make_form_i_sound_assimilated_verb(base, vowel_spec, "assimilated")
end
local function make_form_i_hayy_verb(base, vowel_spec)
-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied
base.irregular = true
-- past and non-past stems, active and passive, and imperative stem
local past_c_stem = "حَيِي"
local past_v_stem_long = past_c_stem
local past_v_stem_short = "حَيّ"
local past_pass_c_stem = "حُيِي"
local past_pass_v_stem_long = past_pass_c_stem
local past_pass_v_stem_short = "حُيّ"
local nonpast_stem = "حْي"
local nonpast_pass_stem = nonpast_stem
local imp_stem = _I .. nonpast_stem
-- make parts
past_2stem_conj(base, "past", {}, past_c_stem)
past_2stem_conj(base, "past_pass", {}, past_pass_c_stem)
local variant = vowel_spec.variant or "both"
if variant == "short" or variant == "both" then
past_2stem_conj(base, "past", past_v_stem_short, {})
past_2stem_conj(base, "past_pass", past_pass_v_stem_short, {})
end
function inflect_long_variant(tense, long_stem, short_stem)
inflect_tense_1(base, tense, "",
{long_stem, long_stem, long_stem, long_stem, short_stem},
{past_endings[4], past_endings[5], past_endings[7], past_endings[8],
past_endings[12]},
{"3ms", "3fs", "3md", "3fd", "3mp"})
end
if variant == "long" or variant == "both" then
inflect_long_variant("past", past_v_stem_long, past_v_stem_short)
inflect_long_variant("past_pass", past_pass_v_stem_long, past_pass_v_stem_short)
end
nonpast_1stem_conj(base, "ind", "a", nonpast_stem, ind_endings_aa)
nonpast_1stem_conj(base, "sub", "a", nonpast_stem, sub_endings_aa)
nonpast_1stem_conj(base, "juss", "a", nonpast_stem, juss_endings_aa)
nonpast_1stem_conj(base, "ind_pass", "u", nonpast_pass_stem, ind_endings_aa)
nonpast_1stem_conj(base, "sub_pass", "u", nonpast_pass_stem, sub_endings_aa)
nonpast_1stem_conj(base, "juss_pass", "u", nonpast_pass_stem, juss_endings_aa)
inflect_tense_imp(base, {imp_stem, all_same = 1}, imp_endings_aa)
-- active and passive participles apparently do not exist for this verb
end
-- Implement form-I final-weak assimilated+final-weak verb. ASSIMILATED is true for assimilated verbs.
local function make_form_i_final_weak_verb(base, vowel_spec, assimilated)
local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec)
-- حَيَّ or حَيِيَ is weird enough that we handle it as a separate function.
if hayy_radicals(rad1, rad2, rad3) then
make_form_i_hayy_verb(base, vowel_spec)
return
end
-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied.
-- Past and non-past stems, active and passive, and imperative stem.
local past_stem = q(rad1, A, rad2)
local past_pass_stem = q(rad1, U, rad2)
local nonpast_stem, nonpast_pass_stem, imp_stem
if raa_radicals(rad1, rad2, rad3) then
base.irregular = true
nonpast_stem = rad1
nonpast_pass_stem = rad1
imp_stem = rad1
else
nonpast_pass_stem = q(rad1, SK, rad2)
if assimilated then
nonpast_stem = rad2
imp_stem = rad2
else
nonpast_stem = nonpast_pass_stem
imp_stem = q(form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1), rad2)
end
end
-- Make parts.
local past_ending_vowel =
req(rad3, Y) and req(past_vowel, A) and "ay" or
req(rad3, W) and req(past_vowel, A) and "aw" or
req(past_vowel, I) and "ī" or "ū"
-- Try to preserve footnotes attached to the third radical and/or past and/or non-past vowels.
local past_footnotes = iut.combine_footnotes(rget_footnotes(rad3), rget_footnotes(past_vowel))
local nonpast_ending_vowel = req(nonpast_vowel, A) and "ā" or req(nonpast_vowel, I) and "ī" or "ū"
local nonpast_footnotes = iut.combine_footnotes(rget_footnotes(rad3), rget_footnotes(nonpast_vowel))
make_final_weak_verb(base,
iut.combine_form_and_footnotes(past_stem, past_footnotes),
iut.combine_form_and_footnotes(past_pass_stem, past_footnotes),
iut.combine_form_and_footnotes(nonpast_stem, nonpast_footnotes),
iut.combine_form_and_footnotes(nonpast_pass_stem, nonpast_footnotes),
iut.combine_form_and_footnotes(imp_stem, nonpast_footnotes),
past_ending_vowel, nonpast_ending_vowel, "a")
-- Active participle.
insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, IN))
-- Active participle, stative type I فَعِيّ (++). FIXME: Is this correct when rad3 is W?
insert_ap2_pp2(base, q(rad1, A, rad2, II, SH))
-- Active participle, stative type II فَعٍ (+++). FIXME: Any examples of this to verify it's correct?
insert_form_or_forms(base, "ap3", q(rad1, A, rad2, IN))
-- Active participle, color/defect أَفْعَى (+cd).
insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, AAMAQ))
-- Active participle, -ān فَعْيَان or فَعْوَان (+an).
-- FIXME: Any examples of this for both rad3 = W and y to verify it's correct?
insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN))
-- Passive participle.
insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, req(rad3, Y) and II or UU, SH))
end
conjugations["I-final-weak"] = function(base, vowel_spec)
make_form_i_final_weak_verb(base, vowel_spec, false)
end
conjugations["I-assimilated+final-weak"] = function(base, vowel_spec)
make_form_i_final_weak_verb(base, vowel_spec, "assimilated")
end
conjugations["I-hollow"] = function(base, vowel_spec)
local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec)
-- In some sense, hollow vowels i~i and u~u are more "correct" than a~i and a~u, but the latter follow the
-- pattern of other form-I verbs, so we map i~i to a~i and u~u to a~u in infer_radicals(). Now however we have
-- to undo this to get the actual past vowel based on the non-past vowel.
if req(past_vowel, A) then
past_vowel = map_vowel(past_vowel, function(vow)
return req(nonpast_vowel, A) and I or rget(nonpast_vowel)
end)
end
local lengthened_nonpast = map_vowel(nonpast_vowel, function(vow)
return vow == U and UU or vow == I and II or AA
end)
-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied.
-- active past stems - vowel (v) and consonant (c)
local past_v_stem = q(rad1, AA, rad3)
local past_c_stem = q(rad1, past_vowel, rad3)
-- active non-past stems - vowel (v) and consonant (c)
local nonpast_v_stem = q(rad1, lengthened_nonpast, rad3)
local nonpast_c_stem = q(rad1, nonpast_vowel, rad3)
-- passive past stems - vowel (v) and consonant (c)
-- 'ufīla, 'ufiltu
local past_pass_v_stem = q(rad1, II, rad3)
local past_pass_c_stem = q(rad1, I, rad3)
-- passive non-past stems - vowel (v) and consonant (c)
-- yufāla/yufalna
-- stem is built differently but conjugation is identical to sound verbs
local nonpast_pass_v_stem = q(rad1, AA, rad3)
local nonpast_pass_c_stem = q(rad1, A, rad3)
-- imperative stem
local imp_v_stem = nonpast_v_stem
local imp_c_stem = nonpast_c_stem
-- make parts
make_hollow_geminate_verb(base, false, past_v_stem, past_c_stem, past_pass_v_stem,
past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem,
nonpast_pass_c_stem, imp_v_stem, imp_c_stem, "a")
if kaan_radicals(rad1, rad2, rad3) then
local endings = make_nonpast_endings(U, {}, {}, {}, {})
inflect_tense(base, "juss", nonpast_prefix_consonants, q(A, rad1), endings)
base.irregular = true
end
-- Active participle.
insert_form_or_forms(base, "ap1", req(rad3, HAMZA) and q(rad1, AA, HAMZA, IN) or
q(rad1, AA, HAMZA, I, rad3))
-- Active participle, stative type I فَيِّد (++). FIXME: Any examples of this to verify it's correct?
insert_ap2_pp2(base, q(rad1, A, Y, SH, I, rad3))
-- Active participle, stative type II فَيِد (+++). FIXME: Any examples of this to verify it's correct?
insert_form_or_forms(base, "ap3", q(rad1, A, Y, I, rad3))
-- Active participle, color/defect أَفّيَد or أَفّوَد (+cd). FIXME: Any examples of this to verify it's correct?
insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, SK, rad2, A, rad3))
-- Active participle, -ān فَيْدَان or فَوْدَان (+an). Example: جَاعَ "to be hungry", act part جَوْعَان
insert_form_or_forms(base, "apan", q(rad1, A, rad2, SK, rad3, AAN))
-- Passive participle.
insert_form_or_forms(base, "pp", q(MA, rad1, req(rad2, Y) and II or UU, rad3))
end
conjugations["I-geminate"] = function(base, vowel_spec)
local rad1, rad2, rad3, past_vowel, nonpast_vowel = get_radicals_3(vowel_spec)
-- Verbal nouns (maṣādir) for form I are unpredictable and have to be supplied.
-- active past stems - vowel (v) and consonant (c)
local past_v_stem = q(rad1, A, rad2, SH)
local past_c_stem = q(rad1, A, rad2, past_vowel, rad2)
-- active non-past stems - vowel (v) and consonant (c)
local nonpast_v_stem = q(rad1, nonpast_vowel, rad2, SH)
local nonpast_c_stem = q(rad1, SK, rad2, nonpast_vowel, rad2)
-- passive past stems - vowel (v) and consonant (c)
-- dulla/dulilta
local past_pass_v_stem = q(rad1, U, rad2, SH)
local past_pass_c_stem = q(rad1, U, rad2, I, rad2)
-- passive non-past stems - vowel (v) and consonant (c)
--yudallu/yudlalna
-- stem is built differently but conjugation is identical to sound verbs
local nonpast_pass_v_stem = q(rad1, A, rad2, SH)
local nonpast_pass_c_stem = q(rad1, SK, rad2, A, rad2)
-- imperative stem
local imp_v_stem = q(rad1, nonpast_vowel, rad2, SH)
local imp_c_stem = q(form_i_imp_stem_through_rad1(base, nonpast_vowel, rad1), rad2, nonpast_vowel, rad2)
-- make parts
make_hollow_geminate_verb(base, "geminate", past_v_stem, past_c_stem, past_pass_v_stem,
past_pass_c_stem, nonpast_v_stem, nonpast_c_stem, nonpast_pass_v_stem,
nonpast_pass_c_stem, imp_v_stem, imp_c_stem, "a")
-- Active participle.
insert_form_or_forms(base, "ap1", q(rad1, AA, rad2, SH))
-- Active participle, stative type I فَعِيع (++). FIXME: Any examples of this to verify it's correct?
insert_ap2_pp2(base, q(rad1, A, rad2, II, rad2))
-- Active participle, stative type II فَعّ (+++). Example: بَرَّ "to be pious", active participle بَرّ
insert_form_or_forms(base, "ap3", q(rad1, A, rad2, SH))
-- Active participle, color/defect أَفَعّ (+cd).
-- Example: لَصَّ "to be thievish, to steal repeatedly", active participle أَلَصّ.
insert_form_or_forms(base, "apcd", q(HAMZA, A, rad1, A, rad2, SH))
-- Active participle, -ān فَعَّان (+an). FIXME: Any examples of this to verify it's correct?
insert_form_or_forms(base, "apan", q(rad1, A, rad2, SH, AAN))
-- Passive participle.
insert_form_or_forms(base, "pp", q(MA, rad1, SK, rad2, UU, rad2))
end
-- Return the ta- (active, past and non-past) and tu- (passive past) prefixes for a form II/III/V/VI verb.
-- Form V and VI verbs normally use ta- and tu-, but reduced (base.reduced) verbs use different prefixes. Form II
-- and III verbs have no prefix.
local function form_ii_iii_v_vi_ta_tu_prefix(base, rad1)
local vform = base.verb_form
if vform == "V" or vform == "VI" then
if base.reduced then
-- To simplify the code, we generate two rad1's with a sukūn between them, which is cleaned up in
-- postprocessing.
return q(_I, rad1, SK), q(rad1, SK), q(_U, rad1, SK)
else
return TA, TA, TU
end
else
return "", "", ""
end
end
-- Make form II or V sound or final-weak verb.
local function make_form_ii_v_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local final_weak = is_final_weak(base, vowel_spec)
local vform = base.verb_form
local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1)
local vn = vform == "V" and
q(ta_past_prefix, rad1, A, rad2, SH, final_weak and IN or q(U, rad3)) or
q(TA, rad1, SK, rad2, II, final_weak and AH or rad3)
-- various stem bases
local past_stem_base = q(ta_past_prefix, rad1, A, rad2, SH)
local nonpast_stem_base = q(ta_nonpast_prefix, rad1, A, rad2, SH)
local past_pass_stem_base = q(tu_past_prefix, rad1, U, rad2, SH)
-- make parts
make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base,
vn)
end
conjugations["II-sound"] = function(base, vowel_spec)
make_form_ii_v_sound_final_weak_verb(base, vowel_spec)
end
conjugations["II-final-weak"] = function(base, vowel_spec)
make_form_ii_v_sound_final_weak_verb(base, vowel_spec)
end
local function make_form_iii_alt_vn(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local final_weak = is_final_weak(base, vowel_spec)
-- Insert alternative verbal noun فِعَال. Since not all verbs have this, we require that verbs that do have it
-- specify it explicitly; a shortcut ++ is provided to make this easier (e.g. <vn:+,++> to indicate that
-- both the normal verbal noun مُفَاعَلَة and secondary verbal noun فِعَال are available).
insert_form_or_forms(base, "vn2", q(rad1, I, rad2, AA, final_weak and HAMZA or rad3))
end
-- Make form III or VI sound or final-weak verb.
local function make_form_iii_vi_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local final_weak = is_final_weak(base, vowel_spec)
local vform = base.verb_form
local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1)
local vn = vform == "VI" and
q(ta_past_prefix, rad1, AA, rad2, final_weak and IN or q(U, rad3)) or
q(MU, rad1, AA, rad2, final_weak and AAH or q(A, rad3, AH))
-- various stem bases
local past_stem_base = q(ta_past_prefix, rad1, AA, rad2)
local nonpast_stem_base = q(ta_nonpast_prefix, rad1, AA, rad2)
local past_pass_stem_base = q(tu_past_prefix, rad1, UU, rad2)
-- make parts
make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base,
vn)
if vform == "III" then
make_form_iii_alt_vn(base, vowel_spec)
end
end
conjugations["III-sound"] = function(base, vowel_spec)
make_form_iii_vi_sound_final_weak_verb(base, vowel_spec)
end
conjugations["III-final-weak"] = function(base, vowel_spec)
make_form_iii_vi_sound_final_weak_verb(base, vowel_spec)
end
-- Make form III or VI geminate verb.
local function make_form_iii_vi_geminate_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vform = base.verb_form
local ta_past_prefix, ta_nonpast_prefix, tu_past_prefix = form_ii_iii_v_vi_ta_tu_prefix(base, rad1)
-- Alternative verbal noun فِعَال will be inserted when we add sound parts below.
local vn = vform == "VI" and q(ta_past_prefix, rad1, AA, rad2, SH) or q(MU, rad1, AA, rad2, SH, AH)
-- Various stem bases.
local past_stem_base = q(ta_past_prefix, rad1, AA)
local nonpast_stem_base = q(ta_nonpast_prefix, rad1, AA)
local past_pass_stem_base = q(tu_past_prefix, rad1, UU)
-- Make parts.
local variant = vowel_spec.variant or "short"
if variant == "short" or variant == "both" then
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
-- Also add alternative sound (non-compressed) parts. This will lead to some duplicate entries, but they are
-- removed during addition.
if variant == "long" or variant == "both" then
make_form_iii_vi_sound_final_weak_verb(base, vowel_spec)
elseif vform == "III" then
-- Still need to add the alternative form-III verbal noun.
make_form_iii_alt_vn(base, vowel_spec)
end
end
conjugations["III-geminate"] = function(base, vowel_spec)
make_form_iii_vi_geminate_verb(base, vowel_spec)
end
-- Make form IV sound or final-weak verb.
local function make_form_iv_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local final_weak = is_final_weak(base, vowel_spec)
-- core of stem base, minus stem prefixes
local stem_core
-- check for irregular verb أَرَى
local is_raa = raa_radicals(rad1, rad2, rad3)
if is_raa then
base.irregular = true
stem_core = rad1
else
stem_core = q(rad1, SK, rad2)
end
-- verbal noun
local vn = is_raa and
q(HAMZA, I, stem_core, AA, HAMZA, AH) or
q(HAMZA, I, stem_core, AA, final_weak and HAMZA or rad3)
-- various stem bases
local past_stem_base = q(HAMZA, A, stem_core)
local nonpast_stem_base = stem_core
local past_pass_stem_base = q(HAMZA, U, stem_core)
-- make parts
make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base,
vn)
end
conjugations["IV-sound"] = function(base, vowel_spec)
make_form_iv_sound_final_weak_verb(base, vowel_spec)
end
conjugations["IV-final-weak"] = function(base, vowel_spec)
make_form_iv_sound_final_weak_verb(base, vowel_spec)
end
conjugations["IV-hollow"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
-- verbal noun
local vn = q(HAMZA, I, rad1, AA, rad3, AH)
-- various stem bases
local past_stem_base = q(HAMZA, A, rad1)
local nonpast_stem_base = rad1
local past_pass_stem_base = q(HAMZA, U, rad1)
-- make parts
make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
conjugations["IV-geminate"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vn = q(HAMZA, I, rad1, SK, rad2, AA, rad2)
-- various stem bases
local past_stem_base = q(HAMZA, A, rad1)
local nonpast_stem_base = rad1
local past_pass_stem_base = q(HAMZA, U, rad1)
-- make parts
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
conjugations["V-sound"] = function(base, vowel_spec)
make_form_ii_v_sound_final_weak_verb(base, vowel_spec)
end
conjugations["V-final-weak"] = function(base, vowel_spec)
make_form_ii_v_sound_final_weak_verb(base, vowel_spec)
end
conjugations["VI-sound"] = function(base, vowel_spec)
make_form_iii_vi_sound_final_weak_verb(base, vowel_spec)
end
conjugations["VI-final-weak"] = function(base, vowel_spec)
make_form_iii_vi_sound_final_weak_verb(base, vowel_spec)
end
conjugations["VI-geminate"] = function(base, vowel_spec)
make_form_iii_vi_geminate_verb(base, vowel_spec)
end
-- Make a verbal noun of the general form that applies to forms VII and above. RAD12 is the first consonant cluster
-- (after initial اِ) and RAD34 is the second consonant cluster. RAD5 is the final consonant.
local function high_form_verbal_noun(rad12, rad34, rad5)
return q(_I, rad12, I, rad34, AA, rad5)
end
-- Populate a sound or final-weak verb for any of the various high-numbered augmented forms (form VII and up) that
-- have up to 5 consonants in two clusters in the stem and the same pattern of vowels between. Some of these
-- consonants in certain verb parts are w's, which leads to apparent anomalies in certain stems of these parts, but
-- these anomalies are handled automatically in postprocessing, where we resolve sequences of iwC -> īC, uwC -> ūC,
-- w + sukūn + w -> w + shadda.
-- RAD12 is the first consonant cluster (after initial اِ) and RAD34 is the second consonant cluster. RAD5 is the
-- final consonant.
local function make_high_form_sound_final_weak_verb(base, vowel_spec, rad12, rad34, rad5)
local final_weak = is_final_weak(base, vowel_spec)
local vn = high_form_verbal_noun(rad12, rad34, final_weak and HAMZA or rad5)
-- various stem bases
local nonpast_stem_base = q(rad12, A, rad34)
local past_stem_base = q(_I, nonpast_stem_base)
local past_pass_stem_base = q(_U, rad12, U, rad34)
-- make parts
make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base,
vn)
end
local function form_vii_nrad1(base, rad1)
if base.reduced then
if not req(rad1, M) then
error(("Internal error: Form VII first radical %s is not م but .reduced specified; should have been caught earlier"):
format(rget(rad1)))
end
return M .. SH
else
return q("نْ", rad1)
end
end
-- Make form VII sound or final-weak verb.
local function make_form_vii_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
make_high_form_sound_final_weak_verb(base, vowel_spec, form_vii_nrad1(base, rad1), rad2, rad3)
end
conjugations["VII-sound"] = function(base, vowel_spec)
make_form_vii_sound_final_weak_verb(base, vowel_spec)
end
conjugations["VII-final-weak"] = function(base, vowel_spec)
make_form_vii_sound_final_weak_verb(base, vowel_spec)
end
conjugations["VII-hollow"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local nrad1 = form_vii_nrad1(base, rad1)
local vn = high_form_verbal_noun(nrad1, Y, rad3)
-- various stem bases
local nonpast_stem_base = nrad1
local past_stem_base = q(_I, nonpast_stem_base)
local past_pass_stem_base = q(_U, nrad1)
-- make parts
make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
conjugations["VII-geminate"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local nrad1 = form_vii_nrad1(base, rad1)
local vn = high_form_verbal_noun(nrad1, rad2, rad2)
-- various stem bases
local nonpast_stem_base = q(nrad1, A)
local past_stem_base = q(_I, nonpast_stem_base)
local past_pass_stem_base = q(_U, nrad1, U)
-- make parts
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
-- Return Form VIII verbal noun.
local function form_viii_verbal_noun(base, vowel_spec, rad1, rad2, rad3)
local final_weak = is_final_weak(base, vowel_spec)
rad3 = final_weak and HAMZA or rad3
return {high_form_verbal_noun(vowel_spec.form_viii_assim, rad2, rad3)}
end
-- Make form VIII sound or final-weak verb.
local function make_form_viii_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
-- check for irregular verb اِتَّخَذَ
if axadh_radicals(rad1, rad2, rad3) then
base.irregular = true
rad1 = T
end
make_high_form_sound_final_weak_verb(base, vowel_spec, vowel_spec.form_viii_assim, rad2, rad3)
end
conjugations["VIII-sound"] = function(base, vowel_spec)
make_form_viii_sound_final_weak_verb(base, vowel_spec)
end
conjugations["VIII-final-weak"] = function(base, vowel_spec)
make_form_viii_sound_final_weak_verb(base, vowel_spec)
end
conjugations["VIII-hollow"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vn = form_viii_verbal_noun(base, vowel_spec, rad1, Y, rad3)
-- various stem bases
local nonpast_stem_base = vowel_spec.form_viii_assim
local past_stem_base = q(_I, nonpast_stem_base)
local past_pass_stem_base = q(_U, nonpast_stem_base)
-- make parts
make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
conjugations["VIII-geminate"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vn = form_viii_verbal_noun(base, vowel_spec, rad1, rad2, rad2)
-- various stem bases
local nonpast_stem_base = q(vowel_spec.form_viii_assim, A)
local past_stem_base = q(_I, nonpast_stem_base)
local past_pass_stem_base = q(_U, vowel_spec.form_viii_assim, U)
-- make parts
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
conjugations["IX-sound"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vn = q(_I, rad1, SK, rad2, I, rad3, AA, rad3)
-- various stem bases
local nonpast_stem_base = q(rad1, SK, rad2, A)
local past_stem_base = q(_I, nonpast_stem_base)
local past_pass_stem_base = q(_U, rad1, SK, rad2, U)
-- make parts
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
conjugations["IX-final-weak"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
make_high_form_sound_final_weak_verb(base, vowel_spec, q(rad1, SK, rad2), rad3, rad3)
end
-- Populate a sound or final-weak verb for any of the various high-numbered
-- augmented forms that have 5 consonants in the stem and the same pattern of
-- vowels. Some of these consonants in certain verb parts are w's, which leads to
-- apparent anomalies in certain stems of these parts, but these anomalies
-- are handled automatically in postprocessing, where we resolve sequences of
-- iwC -> īC, uwC -> ūC, w + sukūn + w -> w + shadda.
local function make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, rad3, rad4, rad5)
make_high_form_sound_final_weak_verb(base, vowel_spec, q(rad1, SK, rad2), q(rad3, SK, rad4), rad5)
end
-- Make form X sound or final-weak verb.
local function make_form_x_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
-- check for irregular verb اِسْتَحْيَا (also اِسْتَحَى)
local is_hayy = hayy_radicals(rad1, rad2, rad3)
local variant = vowel_spec.variant or "both"
if not is_hayy or variant == "long" or variant == "both" then
make_high5_form_sound_final_weak_verb(base, vowel_spec, S, T, rad1, rad2, rad3)
end
if is_hayy and (variant == "short" or variant == "both") then
base.irregular = true
-- Add alternative entries to the verbal paradigms. Any duplicates are removed during addition.
make_high_form_sound_final_weak_verb(base, vowel_spec, S .. SK .. T, rad1, rad3)
end
end
conjugations["X-sound"] = function(base, vowel_spec)
make_form_x_sound_final_weak_verb(base, vowel_spec)
end
conjugations["X-final-weak"] = function(base, vowel_spec)
make_form_x_sound_final_weak_verb(base, vowel_spec)
end
conjugations["X-hollow"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vn = q(base.reduced and "اِسْ" or "اِسْتِ", rad1, AA, rad3, AH)
-- various stem bases
local past_stem_base = q(base.reduced and "اِسْ" or "اِسْتَ", rad1)
local nonpast_stem_base = q(base.reduced and "سْ" or "سْتَ", rad1)
local past_pass_stem_base = q(base.reduced and "اُسْ" or "اُسْتُ", rad1)
-- make parts
make_augmented_hollow_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
conjugations["X-geminate"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vn = q("اِسْتِ", rad1, SK, rad2, AA, rad2)
-- various stem bases
local past_stem_base = q("اِسْتَ", rad1)
local nonpast_stem_base = q("سْتَ", rad1)
local past_pass_stem_base = q("اُسْتُ", rad1)
-- make parts
if base.altgem then
inflect_tense(base, "past", "", {q(past_stem_base, A, rad2, SH), all_same = 1},
past_endings_ay_12_person_only)
end
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn,
base.altgem and "[uncommon]" or nil)
end
conjugations["XI-sound"] = function(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local vn = q(_I, rad1, SK, rad2, II, rad3, AA, rad3)
-- various stem bases
local nonpast_stem_base = q(rad1, SK, rad2, AA)
local past_stem_base = q(_I, nonpast_stem_base)
local past_pass_stem_base = q(_U, rad1, SK, rad2, UU)
-- make parts
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
-- Probably no form XI final-weak, since already geminate in form; would behave as XI-sound.
-- Make form XII sound or final-weak verb.
local function make_form_xii_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, W, rad2, rad3)
end
conjugations["XII-sound"] = function(base, vowel_spec)
make_form_xii_sound_final_weak_verb(base, vowel_spec)
end
conjugations["XII-final-weak"] = function(base, vowel_spec)
make_form_xii_sound_final_weak_verb(base, vowel_spec)
end
-- Make form XIII sound or final-weak verb.
local function make_form_xiii_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, W, W, rad3)
end
conjugations["XIII-sound"] = function(base, vowel_spec)
make_form_xiii_sound_final_weak_verb(base, vowel_spec)
end
conjugations["XIII-final-weak"] = function(base, vowel_spec)
make_form_xiii_sound_final_weak_verb(base, vowel_spec)
end
-- Make a form XIV or XV sound or final-weak verb. Last radical appears twice (if`anlala / yaf`anlilu) so if it were
-- w or y you'd get if`anwā / yaf`anwī or if`anyā / yaf`anyī, i.e. unlike for most augmented verbs, the identity of
-- the radical matters.
local function make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3 = get_radicals_3(vowel_spec)
local lastrad = base.verb_form == "XV" and Y or rad3
make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, N, rad3, lastrad)
end
conjugations["XIV-sound"] = function(base, vowel_spec)
make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec)
end
conjugations["XIV-final-weak"] = function(base, vowel_spec)
make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec)
end
conjugations["XV-sound"] = function(base, vowel_spec)
make_form_xiv_xv_sound_final_weak_verb(base, vowel_spec)
end
-- Probably no form XV final-weak, since already final-weak in form; would behave as XV-sound.
-- Make form Iq or IIq sound or final-weak verb.
local function make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec)
local final_weak = is_final_weak(base, vowel_spec)
local vform = base.verb_form
local vn = vform == "IIq" and
q(TA, rad1, A, rad2, SK, rad3, (final_weak and IN or q(U, rad4))) or
q(rad1, A, rad2, SK, rad3, (final_weak and AAH or q(A, rad4, AH)))
local ta_pref = vform == "IIq" and TA or ""
local tu_pref = vform == "IIq" and TU or ""
-- various stem bases
local past_stem_base = q(ta_pref, rad1, A, rad2, SK, rad3)
local nonpast_stem_base = past_stem_base
local past_pass_stem_base = q(tu_pref, rad1, U, rad2, SK, rad3)
-- make parts
make_augmented_sound_final_weak_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base,
vn)
end
conjugations["Iq-sound"] = function(base, vowel_spec)
make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec)
end
conjugations["Iq-final-weak"] = function(base, vowel_spec)
make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec)
end
conjugations["IIq-sound"] = function(base, vowel_spec)
make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec)
end
conjugations["IIq-final-weak"] = function(base, vowel_spec)
make_form_iq_iiq_sound_final_weak_verb(base, vowel_spec)
end
-- Make form IIIq sound or final-weak verb.
local function make_form_iiiq_sound_final_weak_verb(base, vowel_spec)
local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec)
make_high5_form_sound_final_weak_verb(base, vowel_spec, rad1, rad2, N, rad3, rad4)
end
conjugations["IIIq-sound"] = function(base, vowel_spec)
make_form_iiiq_sound_final_weak_verb(base, vowel_spec)
end
conjugations["IIIq-final-weak"] = function(base, vowel_spec)
make_form_iiiq_sound_final_weak_verb(base, vowel_spec)
end
conjugations["IVq-sound"] = function(base, vowel_spec)
local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec)
local vn = q(_I, rad1, SK, rad2, I, rad3, SK, rad4, AA, rad4)
-- various stem bases
local past_stem_base = q(_I, rad1, SK, rad2, A, rad3)
local nonpast_stem_base = q(rad1, SK, rad2, A, rad3)
local past_pass_stem_base = q(_U, rad1, SK, rad2, U, rad3)
-- make parts
make_augmented_geminate_verb(base, vowel_spec, past_stem_base, nonpast_stem_base, past_pass_stem_base, vn)
end
-- Probably no form IVq final-weak, since already geminate in form; would behave as IVq-sound.
end
create_conjugations()
-------------------------------------------------------------------------------
-- Guts of main conjugation function --
-------------------------------------------------------------------------------
-- Given form, weakness and radicals, check to make sure the radicals present are allowable for the weakness. Hamzas on
-- alif/wāw/yāʾ seats are never allowed (should always appear as hamza-on-the-line), and various weaknesses have various
-- strictures on allowable consonants.
local function check_radicals(form, weakness, rad1, rad2, rad3, rad4)
local function hamza_check(index, rad)
if rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or
rad == HAMZA_ON_W or rad == HAMZA_ON_Y then
error("Radical " .. index .. " is " .. rad .. " but should be ء (hamza on the line)")
end
end
local function check_waw_ya(index, rad)
if not is_waw_ya(rad) then
error("Radical " .. index .. " is " .. rad .. " but should be و or ي")
end
end
local function check_not_waw_ya(index, rad)
if is_waw_ya(rad) then
error("In a sound verb, radical " .. index .. " should not be و or ي")
end
end
hamza_check(rad1)
hamza_check(rad2)
hamza_check(rad3)
hamza_check(rad4)
if weakness == "assimilated" or weakness == "assimilated+final-weak" then
if rad1 ~= W then
error("Radical 1 is " .. rad1 .. " but should be و")
end
-- don't check that non-assimilated form I verbs don't have wāw as their
-- first radical because some form-I verbs exist where a first-radical wāw
-- behaves as sound, e.g. wajuha yawjuhu "to be distinguished".
end
if weakness == "final-weak" or weakness == "assimilated+final-weak" then
if rad4 then
check_waw_ya(4, rad4)
else
check_waw_ya(3, rad3)
end
elseif vform_supports_final_weak(form) then
-- non-final-weak verbs cannot have weak final radical if there's a corresponding
-- final-weak verb category. I think this is safe. We may have problems with
-- ḥayya/ḥayiya yaḥyā if we treat it as a geminate verb.
if rad4 then
check_not_waw_ya(4, rad4)
else
check_not_waw_ya(3, rad3)
end
end
if weakness == "hollow" then
check_waw_ya(2, rad2)
-- don't check that non-hollow verbs in forms that support hollow verbs
-- don't have wāw or yāʾ as their second radical because some verbs exist
-- where a middle-radical wāw/yāʾ behaves as sound, e.g. form-VIII izdawaja
-- "to be in pairs".
end
if weakness == "geminate" then
if rad4 then
error("Internal error: No geminate quadrilaterals, should not be seen")
end
if rad2 ~= rad3 then
error("Weakness is geminate; radical 3 is " .. rad3 .. " but should be same as radical 2 " .. rad2)
end
elseif vform_supports_geminate(form) then
-- non-geminate verbs cannot have second and third radical same if there's
-- a corresponding geminate verb category. I think this is safe. We
-- don't fuss over double wāw or double yāʾ because this could legitimately
-- be a final-weak verb with middle wāw/yāʾ, treated as sound.
if rad4 then
error("Internal error: No quadrilaterals should support geminate verbs")
end
if rad2 == rad3 and not is_waw_ya(rad2) then
error("Weakness is '" .. weakness .. "'; radical 2 and 3 are same at " .. rad2 .. " but should not be; consider making weakness 'geminate'")
end
end
end
-- array of substitutions; each element is a 2-entry array FROM, TO; do it
-- this way so the concatenations only get evaluated once
local postprocess_subs = {
-- reorder short-vowel + shadda -> shadda + short-vowel for easier processing
{"(" .. AIU .. ")" .. SH, SH .. "%1"},
----------same letter separated by sukūn should instead use shadda---------
------------happens e.g. in kun-nā "we were".-----------------
{"(.)" .. SK .. "%1", "%1" .. SH},
---------------------------- assimilated verbs ----------------------------
-- iw, iy -> ī (assimilated verbs)
{I .. W .. SK, II},
{I .. Y .. SK, II},
-- uw, uy -> ū (assimilated verbs)
{U .. W .. SK, UU},
{U .. Y .. SK, UU},
-------------- final -yā uses tall alif not alif maqṣūra ------------------
{"(" .. Y .. SH .. "?" .. A .. ")" .. AMAQ, "%1" .. ALIF},
----------------------- handle hamza assimilation -------------------------
-- initial hamza + short-vowel + hamza + sukūn -> hamza + long vowel
{HAMZA .. A .. HAMZA .. SK, HAMZA .. A .. ALIF},
{HAMZA .. I .. HAMZA .. SK, HAMZA .. I .. Y},
{HAMZA .. U .. HAMZA .. SK, HAMZA .. U .. W}
}
local postprocess_tr_subs = {
{"ī([" .. vowels .. "y*])", "iy%1"},
{"ū([" .. vowels .. "w*])", "uw%1"},
{"(.)%*", "%1%1"}, -- implement shadda
---------------------------- assimilated verbs ----------------------------
-- iw, iy -> ī (assimilated verbs)
{"iw([^" .. vowels .. "w])", "ī%1"},
{"iy([^" .. vowels .. "y])", "ī%1"},
-- uw, uy -> ū (assimilated verbs)
{"uw([^" .. vowels .. "w])", "ū%1"},
{"uy([^" .. vowels .. "y])", "ū%1"},
----------------------- handle hamza assimilation -------------------------
-- initial hamza + short-vowel + hamza + sukūn -> hamza + long vowel
{"ʔaʔ(" .. NV .. ")", "ʔā%1"},
{"ʔiʔ(" .. NV .. ")", "ʔī%1"},
{"ʔuʔ(" .. NV .. ")", "ʔū%1"},
}
-- Post-process verb parts to eliminate phonological anomalies. Many of the changes, particularly the tricky ones,
-- involve converting hamza to have the proper seat. The rules for this are complicated and are documented on the
-- [[w:Hamza]] Wikipedia page. In some cases there are alternatives allowed, and we handle them below by returning
-- multiple possibilities.
local function postprocess_term(term)
if term == "?" then
return "?"
end
-- Add BORDER at text boundaries.
term = BORDER .. term .. BORDER
-- Do the main post-processing, based on the pattern substitutions in postprocess_subs.
for _, sub in ipairs(postprocess_subs) do
term = rsub(term, sub[1], sub[2])
end
term = term:gsub(BORDER, "")
if not rfind(term, HAMZA) then
return term
end
term = term:gsub(HAMZA, HAMZA_PH)
term = ar_utilities.process_hamza(term)
if #term == 1 then
term = term[1]
end
return term
end
local function postprocess_translit(translit)
if translit == "?" then
return "?"
end
-- Add BORDER at text boundaries.
translit = BORDER .. translit .. BORDER
-- Do the main post-processing, based on the pattern substitutions in postprocess_tr_subs.
for _, sub in ipairs(postprocess_tr_subs) do
translit = rsub(translit, sub[1], sub[2])
end
translit = translit:gsub(BORDER, "")
return translit
end
local function postprocess_forms(base)
local converted_values = {}
for slot, forms in pairs(base.forms) do
local need_dedup = false
for i, form in ipairs(forms) do
local term = postprocess_term(form.form)
local translit = form.translit and postprocess_translit(form.translit) or nil
if term ~= form.form or translit ~= form.translit then
need_dedup = true
end
converted_values[i] = {term, translit}
end
if need_dedup then
local temp_dedup = {}
for i = 1, #forms do
local new_term, new_translit = unpack(converted_values[i])
if type(new_term) == "table" then
for _, nt in ipairs(new_term) do
local new_formobj = {
form = nt,
translit = new_translit,
footnotes = forms[i].footnotes,
}
iut.insert_form(temp_dedup, "temp", new_formobj)
end
else
local new_formobj = {
form = new_term,
translit = new_translit,
footnotes = forms[i].footnotes,
}
iut.insert_form(temp_dedup, "temp", new_formobj)
end
end
base.forms[slot] = temp_dedup.temp
end
end
end
local function process_slot_overrides(base)
for slot, forms in pairs(base.slot_overrides) do
local existing_values = base.forms[slot]
base.forms[slot] = nil
for _, form in ipairs(forms) do
-- + in active participle for form I requests slot ap1
if form.form == "+" and (base.verb_form ~= "I" or slot ~= "ap") then
if not existing_values then
error(("Slot '%s' requested the default value but no such value available"):format(slot))
end
-- We maintain an invariant that no two slots share a form object (although they may share the footnote
-- lists inside the form objects). However, there is no need to copy the form objects here because there
-- is a one-to-one correspondence between slots and slot overrides, i.e. you can't have a default value
-- go into two slots.
insert_form_or_forms(base, slot, existing_values, "allow overrides", form.uncertain)
elseif default_indicator_to_active_participle_slot[form.form] then
if form.form == "++" then
if slot ~= "vn" and slot ~= "ap" and slot ~= "pp" then
error(("Secondary default value request '++' only applicable to verbal nouns and pariciples, but found in slot '%s'"):
format(slot))
end
else
if slot ~= "ap" then
error(("Secondary default value request '%s' only applicable to active pariciples, but found in slot '%s'"):
format(form.form, slot))
end
end
local secondary_default_slot =
slot == "vn" and "vn2" or slot == "pp" and "pp2" or
default_indicator_to_active_participle_slot[form.form]
local existing_values = base.forms[secondary_default_slot]
if not existing_values then
error(("Slot '%s' requested a secondary default value using '%s' but no such value available"):
format(slot, form.form))
end
-- See comment above about the lack of need to copy the form objects.
insert_form_or_forms(base, slot, existing_values, "allow overrides", form.uncertain)
-- To make sure there aren't shared form objects.
base.forms[secondary_default_slot] = nil
else
insert_form_or_forms(base, slot, form, "allow overrides", form.uncertain)
end
end
end
-- Now, for non-stative form-I verbs, fill the active participle slot from ap1 unless it should be missing (e.g.
-- passive-only or user specified 'ap:-').
if base.verb_form == "I" and not base.forms.ap and base.forms.ap1 and not skip_slot(base, "ap") then
local saw_non_stative = false
for _, vowel_spec in ipairs(base.conj_vowels) do
if req(vowel_spec.past, A) then
saw_non_stative = true
break
end
end
if saw_non_stative then
base.forms.ap = base.forms.ap1
-- To make sure there aren't shared form objects.
base.forms.ap1 = nil
end
end
end
local function handle_lemma_linked(base)
-- Compute linked versions of potential lemma slots, for use in {{ar-verb}}. We substitute the original lemma
-- (before removing links) for forms that are the same as the lemma, if the original lemma has links.
for _, slot in ipairs(export.potential_lemma_slots) do
if base.forms[slot] then
insert_form_or_forms(base, slot .. "_linked", iut.map_forms(base.forms[slot], function(form)
if form == base.lemma and rfind(base.linked_lemma, "%[%[") then
return base.linked_lemma
else
return form
end
end))
end
end
end
-- Process specs given by the user using 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'.
local function process_addnote_specs(base)
for _, spec in ipairs(base.addnote_specs) do
for _, slot_spec in ipairs(spec.slot_specs) do
slot_spec = "^" .. slot_spec .. "$"
for slot, forms in pairs(base.forms) do
if rfind(slot, slot_spec) then
-- To save on memory, side-effect the existing forms.
for _, form in ipairs(forms) do
form.footnotes = iut.combine_footnotes(form.footnotes, spec.footnotes)
end
end
end
end
end
end
local function add_missing_links_to_forms(base)
-- Any forms without links should get them now. Redundant ones will be stripped later.
for slot, forms in pairs(base.forms) do
for _, form in ipairs(forms) do
if not form.form:find("%[%[") then
form.form = "[[" .. form.form .. "]]"
end
end
end
end
local function conjugate_verb(base)
construct_stems(base)
for _, vowel_spec in ipairs(base.conj_vowels) do
-- Reconstruct conjugation type from verb form and (possibly inferred) weakness.
conj_type = base.verb_form .. "-" .. vowel_spec.weakness
-- Check that the conjugation type is recognized.
if not conjugations[conj_type] then
error("Unknown conjugation type '" .. conj_type .. "'")
end
-- The way the conjugation functions work is they always add entries to the appropriate parts of the paradigm
-- (each of which is an array), rather than setting the values. This makes it possible to call more than one
-- conjugation function and essentially get a paradigm of the "either A or B" kind. Doing this may insert
-- duplicate entries into a particular paradigm part, but this is not a problem because we check for duplicate
-- entries when adding them, and don't insert in that case.
conjugations[conj_type](base, vowel_spec)
end
postprocess_forms(base)
process_slot_overrides(base)
-- This should happen before add_missing_links_to_forms() so that the comparison `form == base.lemma` in
-- handle_lemma_linked() works correctly and compares unlinked forms to unlinked forms.
handle_lemma_linked(base)
process_addnote_specs(base)
if not base.alternant_multiword_spec.args.noautolinkverb then
add_missing_links_to_forms(base)
end
end
local function parse_indicator_spec(angle_bracket_spec)
-- Store the original angle bracket spec so we can reconstruct the overall conj spec with the lemma(s) in them.
local base = {
angle_bracket_spec = angle_bracket_spec,
conj_vowels = {},
root_consonants = {},
user_stem_overrides = {},
user_slot_overrides = {},
slot_explicitly_missing = {},
slot_uncertain = {},
slot_override_uses_default = {},
addnote_specs = {},
}
local function parse_err(msg)
error(msg .. ": " .. 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
parse_err("Extraneous text after bracketed footnotes: '" .. table.concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
table.insert(footnotes, separated_group[j])
end
return footnotes
end
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_multi_delimiter_balanced_segment_run(inside, {{"[", "]"}, {"<", ">"}})
local dot_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "%.")
-- The first dot-separated element must specify the verb form, e.g. IV or IIq. If the form is I, it needs to include
-- the the past and non-past vowels, e.g. I/a~u for kataba ~ yaktubu. More than one vowel can be given,
-- comma-separated, and more than one past~non-past pair can be given, slash-separated, e.g. I/a,u~u/i~a for form I
-- كمل, which can be conjugated as kamala/kamula ~ yakmulu or kamila ~ yakmalu. An individual vowel spec must be one
-- of a, i or u and in general (a) at least one past~non-past pair most be given, and (b) both past and non-past
-- vowels must be given even though sometimes the vowel can be determined from the unvocalized form. An exception is
-- passive-only verbs, where the vowels can't in general be determined (except indirectly in some cases by looking
-- at an associated non-passive verb); in that case, the vowel~vowel spec can left out.
local slash_separated_groups = put.split_alternating_runs_and_strip_spaces(dot_separated_groups[1], "/")
local form_spec = slash_separated_groups[1]
base.form_footnotes = fetch_footnotes(form_spec)
if form_spec[1] == "" then
parse_err("Missing verb form")
end
if not allowed_vforms_with_weakness_set[form_spec[1]] then
parse_err(("Unrecognized verb form '%s', should be one of %s"):format(
form_spec[1], list_to_text(allowed_vforms, nil, " or ")))
end
if form_spec[1]:find("%-") then
base.verb_form, base.explicit_weakness = form_spec[1]:match("^(.-)%-(.*)$")
else
base.verb_form = form_spec[1]
end
if #slash_separated_groups > 1 then
if base.verb_form ~= "I" then
parse_err(("Past~non-past vowels can only be specified when verb form is I, but saw form '%s'"):format(
base.verb_form))
end
for i = 2, #slash_separated_groups do
local slash_separated_group = slash_separated_groups[i]
local tilde_separated_groups = put.split_alternating_runs_and_strip_spaces(slash_separated_group, "~")
if #tilde_separated_groups ~= 2 then
parse_err(("Expected two tilde-separated vowel specs: %s"):format(table.concat(slash_separated_group)))
end
local function parse_conj_vowels(tilde_separated_group, vtype)
local conj_vowel_objects = {}
local comma_separated_groups = put.split_alternating_runs_and_strip_spaces(tilde_separated_group, ",")
for _, comma_separated_group in ipairs(comma_separated_groups) do
local conj_vowel = comma_separated_group[1]
if conj_vowel ~= "a" and conj_vowel ~= "i" and conj_vowel ~= "u" then
parse_err(("Expected %s conjugation vowel '%s' to be one of a, i or u in %s"):format(
vtype, conj_vowel, table.concat(slash_separated_group)))
end
conj_vowel = dia[conj_vowel]
local conj_vowel_footnotes = fetch_footnotes(comma_separated_group)
-- Try to use strings when possible as it makes q() significantly more efficient.
if conj_vowel_footnotes then
table.insert(conj_vowel_objects, {form = conj_vowel, footnotes = conj_vowel_footnotes})
else
table.insert(conj_vowel_objects, conj_vowel)
end
end
return conj_vowel_objects
end
local conj_vowel_spec = {
past = parse_conj_vowels(tilde_separated_groups[1], "past"),
nonpast = parse_conj_vowels(tilde_separated_groups[2], "non-past"),
}
table.insert(base.conj_vowels, conj_vowel_spec)
end
end
for i = 2, #dot_separated_groups do
local dot_separated_group = dot_separated_groups[i]
local first_element = dot_separated_group[1]
if first_element == "addnote" then
local spec_and_footnotes = fetch_footnotes(dot_separated_group)
if #spec_and_footnotes < 2 then
parse_err("Spec with 'addnote' should be of the form 'addnote[SLOTSPEC][FOOTNOTE][FOOTNOTE][...]'")
end
local slot_spec = table.remove(spec_and_footnotes, 1)
local slot_spec_inside = rmatch(slot_spec, "^%[(.*)%]$")
if not slot_spec_inside then
parse_err("Internal error: slot_spec " .. slot_spec .. " should be surrounded with brackets")
end
local slot_specs = rsplit(slot_spec_inside, ",")
-- FIXME: Here, [[Module:it-verb]] called strip_spaces(). Generally we don't do this. Should we?
table.insert(base.addnote_specs, {slot_specs = slot_specs, footnotes = spec_and_footnotes})
elseif first_element:find("^var:") then
if #dot_separated_group > 1 then
parse_err(("Can't attach footnotes to 'var:' spec '%s'"):format(first_element))
end
base.variant = first_element:match("^var:(.*)$")
elseif first_element:find("^I+V?:") then
local root_cons, root_cons_value = first_element:match("^(I+V?):(.*)$")
local root_index
if root_cons == "I" then
root_index = 1
elseif root_cons == "II" then
root_index = 2
elseif root_cons == "III" then
root_index = 3
elseif root_cons == "IV" then
root_index = 4
if not base.verb_form:find("q$") then
parse_err(("Can't specify root consonant IV for non-quadriliteral verb form '%s': %s"):format(
base.verb_form, first_element))
end
end
local cons, translit = root_cons_value:match("^(.*)//(.*)$")
if not cons then
cons = root_cons_value
end
local root_footnotes = fetch_footnotes(dot_separated_group)
if not translit and not root_footnotes then
base.root_consonants[root_index] = cons
else
base.root_consonants[root_index] = {form = cons, translit = translit, footnotes = root_footnotes}
end
elseif first_element:find("^[a-z][a-z0-9_]*:") then
local slot_or_stem, remainder = first_element:match("^(.-):(.*)$")
dot_separated_group[1] = remainder
local comma_separated_groups = put.split_alternating_runs_and_strip_spaces(dot_separated_group, "[,،]")
if overridable_stems[slot_or_stem] then
if base.user_stem_overrides[slot_or_stem] then
parse_err("Overridable stem '" .. slot_or_stem .. "' specified twice")
end
base.user_stem_overrides[slot_or_stem] = overridable_stems[slot_or_stem](comma_separated_groups,
{prefix = slot_or_stem, base = base, parse_err = parse_err, fetch_footnotes = fetch_footnotes})
else -- assume a form override; we validate further later when the possible slots are available
if base.user_slot_overrides[slot_or_stem] then
parse_err("Form override '" .. slot_or_stem .. "' specified twice")
end
base.user_slot_overrides[slot_or_stem] = allow_multiple_values_for_override(comma_separated_groups,
{prefix = slot_or_stem, base = base, parse_err = parse_err, fetch_footnotes = fetch_footnotes},
"is form override")
end
elseif indicator_flags[first_element] then
if #dot_separated_group > 1 then
parse_err("No footnotes allowed with '" .. first_element .. "' spec")
end
if base[first_element] then
parse_err("Spec '" .. first_element .. "' specified twice")
end
base[first_element] = true
else
local passive, uncertain = first_element:match("^(.*)(%?)$")
passive = passive or first_element
uncertain = not not uncertain
if passive_types[passive] then
if #dot_separated_group > 1 then
parse_err("No footnotes allowed with '" .. passive .. "' spec")
end
if base.passive then
parse_err("Value for passive type specified twice")
end
base.passive = passive
base.passive_uncertain = uncertain
else
parse_err("Unrecognized spec '" .. first_element .. "'")
end
end
end
return base
end
-- Normalize all lemmas, substituting the pagename for blank lemmas and adding links to multiword lemmas.
local function normalize_all_lemmas(alternant_multiword_spec, head)
-- (1) Add links to all before and after text. Remember the original text so we can reconstruct the verb spec later.
if not alternant_multiword_spec.args.noautolinktext then
iut.add_links_to_before_and_after_text(alternant_multiword_spec, "remember original")
end
-- (2) Remove any links from the lemma, but remember the original form so we can use it below in the 'lemma_linked'
-- form.
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = head
end
base.user_specified_lemma = base.lemma
base.lemma = m_links.remove_links(base.lemma)
base.user_specified_verb = base.lemma
base.verb = base.user_specified_verb
local linked_lemma
if alternant_multiword_spec.args.noautolinkverb or base.user_specified_lemma:find("%[%[") then
linked_lemma = base.user_specified_lemma
else
-- Add links to the lemma so the user doesn't specifically need to, since we preserve
-- links in multiword lemmas and include links in non-lemma forms rather than allowing
-- the entire form to be a link.
linked_lemma = iut.add_links(base.user_specified_lemma)
end
base.linked_lemma = linked_lemma
end)
end
-- Determine weakness from radicals. Used when root given in place of lemma (e.g. for {{ar-verb forms}}).
local function weakness_from_radicals(form, rad1, rad2, rad3, rad4)
local weakness = nil
local quadlit = form:find("q$")
-- If weakness unspecified, derive from radicals.
if not quadlit then
if is_waw_ya(rad3) and rad1 == W and form == "I" then
weakness = "assimilated+final-weak"
elseif is_waw_ya(rad3) and vform_supports_final_weak(form) then
weakness = "final-weak"
elseif rad2 == rad3 and vform_supports_geminate(form) then
weakness = "geminate"
elseif is_waw_ya(rad2) and vform_supports_hollow(form) then
weakness = "hollow"
elseif rad1 == W and form == "I" then
weakness = "assimilated"
else
weakness = "sound"
end
else
if is_waw_ya(rad4) then
weakness = "final-weak"
else
weakness = "sound"
end
end
return weakness
end
-- Join the infixed tāʔ (ت) to the first radical in form VIII verbs. This may cause assimilation of the tāʔ to the
-- radical or in some cases the radical to the tāʔ. Used when a root is supplied instead of a lemma (which already has
-- the appropriate assimilation in it).
local function form_viii_join_ta(rad)
if rad == W or rad == Y or rad == "ت" then return "تّ"
elseif rad == "د" then return "دّ"
elseif rad == "ث" then return "ثّ"
elseif rad == "ذ" then return "ذّ"
elseif rad == "ز" then return "زْد"
elseif rad == "ص" then return "صْط"
elseif rad == "ض" then return "ضْط"
elseif rad == "ط" then return "طّ"
elseif rad == "ظ" then return "ظّ"
else return rad .. SK .. "ت"
end
end
local function detect_indicator_spec(base)
base.forms = {}
base.stem_overrides = {}
base.slot_overrides = {}
if not base.conj_vowels[1] then
-- These may be converted to inferred vowels. If not, we throw an error if form I and not passive-only.
base.conj_vowels = {{
past = "-",
nonpast = "-",
}}
else
-- If multiple vowels specified for a given vowel type (e.g. a,u~u), expand so that each spec in
local expansion = {}
for _, spec in ipairs(base.conj_vowels) do
for _, past in ipairs(spec.past) do
for _, nonpast in ipairs(spec.nonpast) do
table.insert(expansion, {past = past, nonpast = nonpast})
end
end
end
base.conj_vowels = expansion
end
local vform = base.verb_form
-- check for quadriliteral form (Iq, IIq, IIIq, IVq)
base.quadlit = not not vform:find("q$")
-- Infer radicals as necessary. We infer a separate set of radicals for each past~non-past vowel combination because
-- they may be different (particularly with form-I hollow verbs).
for _, vowel_spec in ipairs(base.conj_vowels) do
-- NOTE: rad1, rad2, etc. refer to user-specified radicals, which are formobj tables that optionally specify an
-- explicit manual translit, whereas ir1, ir2, etc. refer to inferred radicals, which are either strings or
-- lists of possible radicals.
local rads = base.root_consonants
local rad1, rad2, rad3, rad4 = rads[1], rads[2], rads[3], rads[4]
-- Default any unspecified radicals to radicals determined from the headword. The returned radicals may be
-- lists of possible radicals, where the first radical should be chosen if the user didn't explicitly specify a
-- radical but all are allowed. If `ambig = true` is set in the table, the radical is considered ambiguous and
-- categories won't be created for weak radicals.
local weakness, ir1, ir2, ir3, ir4
if vform ~= "none" then
ir1, ir2, ir3 = rmatch(base.lemma, "^([^_])_([^_])_([^_])$")
if not ir1 then
ir1, ir2, ir3, ir4 = rmatch(base.lemma, "^([^_])_([^_])_([^_])_([^_])$")
end
if ir1 then
-- root given instead of lemma
weakness = weakness_from_radicals(vform, ir1, ir2, ir3, ir4)
if vform == "VIII" then
vowel_spec.form_viii_assim = form_viii_join_ta(ir1)
end
else
local ret = export.infer_radicals {
headword = base.lemma,
vform = vform,
passive = base.passive,
past_vowel = vowel_spec.past,
nonpast_vowel = vowel_spec.nonpast,
is_reduced = base.reduced,
}
weakness, ir1, ir2, ir3, ir4 = ret.weakness, ret.rad1, ret.rad2, ret.rad3, ret.rad4
vowel_spec.form_viii_assim = ret.form_viii_assim
vowel_spec.past = ret.past_vowel
vowel_spec.nonpast = ret.nonpast_vowel
vowel_spec.variant = base.variant or ret.variant
end
end
-- For most ambiguous radicals, the choice of radical doesn't matter because it doesn't affect the conjugation
-- one way or another. For form I hollow verbs, however, it definitely does. In fact, the choice of radical is
-- critical even beyond the past and non-past vowels because it affects the form of the passive participle. So,
-- check for this and signal an error if the radical could not be inferred and is not given explicitly.
if vform == "I" and type(ir2) == "table" and ir2.need_radical and not rad2 then
error("Unable to guess middle radical of hollow form I verb; need to specify radical explicitly")
end
if vform == "I" and not is_passive_only(base.passive) and (
rget(vowel_spec.past) == "-" or rget(vowel_spec.nonpast) == "-") then
error("Form I verb that isn't passive-only or final-weak must have past~non-past vowels specified")
end
-- Convert ambiguous radicals.
local function regularize_inferred_radical(rad)
if type(rad) == "table" then
if rad.ambig then
return {form = rad[1], ambig = true}
else
return rad[1]
end
else
return rad
end
end
-- Return the appropriate radical at index `index` (1 through 4), based either on the user-specified radical
-- `user_radical` or (if unspecified) `inferred_radical`, inferred from the unvocalized lemma. Two values are
-- returned, the "regularized" version of the radical (where ambiguous inferred radicals are converted to their
-- most likely actual radical) and the non-regularized version. The returned values are form objects rather than
-- strings.
local function fetch_radical(user_radical, inferred_radical, index)
if not user_radical then
return regularize_inferred_radical(inferred_radical), inferred_radical
else
local rad_formval = rget(user_radical)
if type(inferred_radical) == "table" then
local allowed_radical_set = m_table.listToSet(inferred_radical)
if not allowed_radical_set[rad_formval] then
error(("For lemma %s, radical %s ambiguously inferred as %s but user radical incompatibly given as %s"):
format(base.lemma, index,
list_to_text(inferred_radical, nil, " or "), rad_formval))
end
elseif rad_formval ~= inferred_radical then
error(("For lemma %s, radical %s inferred as %s but user radical incompatibly given as %s"):
format(base.lemma, index, inferred_radical, rad_formval))
end
return user_radical, user_radical
end
end
if vform ~= "none" then
vowel_spec.rad1, vowel_spec.unreg_rad1 = fetch_radical(rad1, ir1, 1)
vowel_spec.rad2, vowel_spec.unreg_rad2 = fetch_radical(rad2, ir2, 2)
vowel_spec.rad3, vowel_spec.unreg_rad3 = fetch_radical(rad3, ir3, 3)
if base.quadlit then
vowel_spec.rad4, vowel_spec.unreg_rad4 = fetch_radical(rad4, ir4, 4)
end
end
if vform == "I" then
-- If explicit weakness given using 'I-sound' or 'I-assimilated', we may need to adjust the inferred weakness.
if base.explicit_weakness == "sound" then
if weakness == "assimilated" then
weakness = "sound"
elseif weakness == "assimilated+final-weak" then
-- Verbs like waniya~yawnā "to be faint; to languish" (although the defaults should handle this
-- correctly)
weakness = "final-weak"
else
error(("Can't specify form 'I-sound' when inferred weakness is '%s' for lemma %s"):format(
weakness, base.lemma))
end
elseif base.explicit_weakness == "assimilated" then
if weakness == "sound" then
-- i~a verbs like waṭiʔa~yaṭaʔu "to tread, to trample"; wasiʕa~yasaʕu "to be spacious; to be well-off";
-- waṯiʔa~yaṯaʔu "to get bruised, to be sprained", which would default to sound.
weakness = "assimilated"
elseif weakness == "final-weak" then
-- For completeness; not clear if any verbs occur where this is needed. (There are plenty of
-- assimilated+final-weak verbs but the defaults should take care of them.)
weakness = "assimilated+final-weak"
else
error(("Can't specify form 'I-assimilated' when inferred weakness is '%s' for lemma %s"):format(
weakness, base.lemma))
end
elseif base.explicit_weakness then
error(("Internal error: Unrecognized value '%s' for base.explicit_weakness"):format(base.explicit_weakness))
end
elseif vform == "none" then
weakness = base.explicit_weakness
elseif base.explicit_weakness then
error(("Internal error: Explicit weakness should not be specifiable except with forms I and none, but saw explicit weakness '%s' with verb form '%s'"):
format(base.explicit_weakness, vform))
end
vowel_spec.weakness = weakness
if vform ~= "none" then
-- Error if radicals are wrong given the weakness. More likely to happen if the weakness is explicitly given
-- rather than inferred. Will also happen if certain incorrect letters are included as radicals e.g. hamza on
-- top of various letters, alif maqṣūra, tā' marbūṭa.
check_radicals(vform, weakness, rget(vowel_spec.rad1), rget(vowel_spec.rad2), rget(vowel_spec.rad3),
base.quadlit and rget(vowel_spec.rad4) or nil)
end
-- Check the variant value.
local form_iii_vi_geminate = (vform == "III" or vform == "VI") and rget(vowel_spec.rad2) == rget(vowel_spec.rad3) and
not req(vowel_spec.rad2, Y)
local hayy_i_x = hayy_radicals(vowel_spec.rad1, vowel_spec.rad2, vowel_spec.rad3) and (vform == "I" or vform == "X")
if form_iii_vi_geminate or hayy_i_x then
if vowel_spec.variant and vowel_spec.variant ~= "long" and vowel_spec.variant ~= "short" and vowel_spec.variant ~= "both" then
error(("For form-III/VI geminate verb or form-I/X verb with ح-ي-ي radicals, saw unrecognized 'var:%s' value; should be 'var:long', 'var:short' or 'var:both'"):format(
vowel_spec.variant))
end
elseif vowel_spec.variant then
error(("Variant value 'var:%s' not allowed in this context"):format(vowel_spec.variant))
end
end
-- If form I, regroup expanded vowels for display purposes.
if vform == "I" then
local group_by_past = {}
for _, vowel_spec in ipairs(base.conj_vowels) do
m_table.insertIfNot(group_by_past, {
past = undia[rget(vowel_spec.past)],
nonpasts = {undia[rget(vowel_spec.nonpast)]},
}, {
key = function(obj) return obj.past end,
combine = function(obj1, obj2)
for _, nonpast in ipairs(obj2.nonpasts) do
m_table.insertIfNot(obj1.nonpasts, nonpast)
end
end,
})
end
local group_by_nonpast = {}
for _, vowel_spec in ipairs(group_by_past) do
m_table.insertIfNot(group_by_nonpast, {
pasts = {vowel_spec.past},
nonpasts = vowel_spec.nonpasts,
}, {
key = function(obj) return obj.nonpasts end,
combine = function(obj1, obj2)
for _, past in ipairs(obj2.pasts) do
m_table.insertIfNot(obj1.pasts, past)
end
end,
})
end
base.grouped_conj_vowels = group_by_nonpast
end
-- Set value of passive. If not specified, default is yes for forms II, III, IV and Iq; no but uncertainly for
-- forms VII, IX, XI - XV and IIIq - IVq, as well as form I with past vowel u; impersonal but uncertainly for form
-- V, VI, X and IIq, as well as form I with past vowel i; and yes but uncertainly for the remainder (form I with
-- past vowel only a and form VIII).
if not base.passive then
base.passive_defaulted = true
-- Temporary tracking for defaulted passives by verb form, weakness and (for form I) past/non-past vowels.
track_if_ar_conj(base, "passive-defaulted/" .. vform)
for _, vowel_spec in ipairs(base.conj_vowels) do
track_if_ar_conj(base, "passive-defaulted/" .. vform.. "/" .. vowel_spec.weakness)
if vform == "I" then
local past_nonpast = ("%s~%s"):format(undia[vowel_spec.past], undia[vowel_spec.nonpast])
track_if_ar_conj(base, "passive-defaulted/I/" .. past_nonpast)
track_if_ar_conj(base, "passive-defaulted/I/" .. vowel_spec.weakness .. "/" .. past_nonpast)
end
end
if vform_probably_full_passive(vform) then
base.passive = "pass"
else
base.passive_uncertain = true
for _, vowel_spec in ipairs(base.conj_vowels) do
if vform_probably_no_passive(vform, vowel_spec.weakness, vowel_spec.past, vowel_spec.nonpast) then
base.passive = "nopass"
break
elseif vform_probably_impersonal_passive(vform, vowel_spec.weakness, vowel_spec.past,
vowel_spec.nonpast) then
base.passive = "ipass"
break
end
end
base.passive = base.passive or "pass"
end
end
-- NOTE: Currently there are no built-in stems or form overrides for Arabic; this code is inherited from
-- [[Module:ca-verb]], where such things do exist, and is kept for generality in case we decide in the future to
-- implement such things.
-- Override built-in verb stems and overrides with user-specified ones.
for stem, values in pairs(base.user_stem_overrides) do
base.stem_overrides[stem] = values
end
for slot, values in pairs(base.user_slot_overrides) do
if not base.alternant_multiword_spec.verb_slots_map[slot] then
error("Unrecognized override slot '" .. slot .. "': " .. base.angle_bracket_spec)
end
if export.unsettable_slots_set[slot] then
error("Slot '" .. slot .. "' cannot be set using an override: " .. base.angle_bracket_spec)
end
if skip_slot(base, slot, "allow overrides") then
error("Override slot '" .. slot ..
"' would be skipped based on the passive, 'noimp' and/or 'no_nonpast' settings: " ..
base.angle_bracket_spec)
end
base.slot_overrides[slot] = values
end
if base.verb_form == "none-final-weak" then
for _, stem_type in ipairs { "past", "past_pass", "nonpast", "nonpast_pass" } do
if base.stem_overrides[stem_type .. "_c"] or base.stem_overrides[stem_type .. "_v"] then
error(("Specify past stem for verb type 'none-final-weak' using '%s:...' not '%s_c:...' or '%s_v:...'"):
format(stem_type, stem_type, stem_type))
end
end
for _, stem_type in ipairs { "past", "nonpast" } do
if base.stem_overrides[stem_type] or not base.stem_overrides[stem_type .. "_final_weak_vowel"] then
error(("For verb type 'none-final-weak', if '%s:...' specified, so must '%s_final_weak_vowel:...'"):
format(stem_type, stem_type))
end
end
end
end
local function detect_all_indicator_specs(alternant_multiword_spec)
add_slots(alternant_multiword_spec)
alternant_multiword_spec.verb_forms = {}
-- This means at least one individual base had the slot marked as explicitly missing. Another base (e.g. when
-- there are multiple alternants) might have a value for the slot. In practice, we only respect this when there are
-- no overall values in the slot and `slot_uncertain` isn't set; in this case, we display "no ..." for the slot
-- instead of simply not displaying anything for the slot.
alternant_multiword_spec.slot_explicitly_missing = {}
-- This means at least one individual base had no values for the slot and the slot marked as explicitly uncertain.
-- Note that this is different from a value being present but marked as uncertain (e.g. if an override was given
-- with a ? after it); this causes the form object for the value to have `uncertain = true` set. If there are no
-- overall values in the slot and `slot_uncertain` is set, we display this in the headword.
alternant_multiword_spec.slot_uncertain = {}
iut.map_word_specs(alternant_multiword_spec, function(base)
-- So arguments, etc. can be accessed. WARNING: Creates circular reference.
base.alternant_multiword_spec = alternant_multiword_spec
detect_indicator_spec(base)
if not base.nocat then
m_table.insertIfNot(alternant_multiword_spec.verb_forms, base.verb_form)
end
if base.passive_uncertain then
alternant_multiword_spec.passive_uncertain = true
end
for slot, _ in pairs(base.slot_explicitly_missing) do
alternant_multiword_spec.slot_explicitly_missing[slot] = true
end
end)
end
local function determine_slot_uncertainty_from_forms(alternant_multiword_spec)
iut.map_word_specs(alternant_multiword_spec, function(base)
-- If no verbal noun and verb form is not 'none' (manually-specified stems) — which currently only happens for
-- form I — and the verbal noun wasn't explicitly indicated as missing using <vn:->, we assume it's just
-- unknown/unspecified rather than missing. Same with active participles.
for uncertain_slot, _ in pairs(slots_that_may_be_uncertain) do
if not base.forms[uncertain_slot] and vform ~= "none" and not skip_slot(base, uncertain_slot) then
base.slot_uncertain[uncertain_slot] = true
end
end
-- Propagate slot uncertainty up. Currently only the verbal noun can have this set but we write the code
-- generally.
for slot, _ in pairs(base.slot_uncertain) do
alternant_multiword_spec.slot_uncertain[slot] = true
end
end)
-- If slot is uncertain and has no value, explicitly set its value to "?".
for uncertain_slot, _ in pairs(slots_that_may_be_uncertain) do
if not alternant_multiword_spec.forms[uncertain_slot] and
alternant_multiword_spec.slot_uncertain[uncertain_slot] then
alternant_multiword_spec.forms[uncertain_slot] = {{form = "?"}}
end
end
end
-- Determine certain properties of the verb from the overall forms, such as whether the verb is active-only or
-- passive-only, is impersonal, lacks an imperative, etc.
local function determine_verb_properties_from_forms(alternant_multiword_spec)
alternant_multiword_spec.has_active = false
alternant_multiword_spec.has_passive = false
alternant_multiword_spec.has_non_impers_active = false
alternant_multiword_spec.has_non_impers_passive = false
alternant_multiword_spec.has_imp = false
alternant_multiword_spec.has_past = false
alternant_multiword_spec.has_nonpast = false
for slot, _ in pairs(alternant_multiword_spec.forms) do
if slot == "ap" or slot:find("[123]") and not slot:find("_pass") then
alternant_multiword_spec.has_active = true
end
if slot == "pp" or slot:find("[123]") and slot:find("_pass") then
alternant_multiword_spec.has_passive = true
end
if slot:find("[123]") and not slot:find("pass_[123]") and not slot:find("3ms") then
alternant_multiword_spec.has_non_impers_active = true
end
if slot:find("pass_[123]") and not slot:find("3ms") then
alternant_multiword_spec.has_non_impers_passive = true
end
if slot:find("^imp_") then
alternant_multiword_spec.has_imp = true
end
if slot:find("^past_") then
alternant_multiword_spec.has_past = true
end
if slot:find("^ind_") or slot:find("^sub_") or slot:find("^juss_") then
alternant_multiword_spec.has_nonpast = true
end
end
end
local function add_categories_and_annotation(alternant_multiword_spec, base, multiword_lemma, insert_ann, insert_cat)
-- Useful e.g. in constructing suppletive verbs out of parts. For a verb like جاء or أتى whose imperative comes
-- from the unrelated verb تعالى, we don't want the latter verb showing up in categories or annotations.
if base.nocat then
return
end
local vform = base.verb_form
if vform ~= "none" then
insert_ann("form", vform)
insert_cat("第" .. vform .. "類動詞")
end
if base.reduced then
insert_ann("reduced", "簡化")
if vform ~= "none" then
insert_cat("第" .. vform .. "類簡化動詞")
end
end
if base.quadlit then
insert_cat("四母動詞")
end
if base.passive_defaulted then
insert_cat("預設為被動語態的動詞")
end
for _, vowel_spec in ipairs(base.conj_vowels) do
local rad1, rad2, rad3, rad4 = get_radicals_4(vowel_spec)
local final_weak = is_final_weak(base, vowel_spec)
local weakness = vowel_spec.weakness
-- We have to distinguish weakness by form and weakness by conjugation. Weakness by form merely indicates the
-- presence of weak letters in certain positions in the radicals. Weakness by conjugation is related to how the
-- verbs are conjugated. For example, form-II verbs that are "hollow by form" (middle radical is wāw or yāʾ) are
-- conjugated as sound verbs. Another example: form-I verbs with initial wāw are "assimilated by form" and most
-- are assimilated by conjugation as well, but a few are sound by conjugation, e.g. wajuha yawjuhu "to be
-- distinguished" (rather than wajuha yajuhu); similarly for some hollow-by-form verbs in various forms, e.g.
-- form VIII izdawaja yazdawiju "to be in pairs" (rather than izdāja yazdāju). Categories referring to weakness
-- always refer to weakness by conjugation; weakness by form is distinguished only by categories such as
-- [[:Category:Arabic form-III verbs with و as second radical]].
local translated_weakness = weakness_translations[weakness] or weakness -- L10N
insert_ann("weakness", translated_weakness)
if vform ~= "none" then
insert_cat(("第%s類%s動詞"):format(vform, translated_weakness))
end
local function radical_is_ambiguous(rad)
return type(rad) == "table" and rad.ambig
end
local function radical_is_unambiguous_weak(rad)
return not radical_is_ambiguous(rad) and (is_waw_ya(rad) or req(rad, HAMZA))
end
if vform ~= "none" then
local ur1, ur2, ur3, ur4 =
vowel_spec.unreg_rad1, vowel_spec.unreg_rad2, vowel_spec.unreg_rad3, vowel_spec.unreg_rad4
-- Create headword categories based on the radicals. Do the following before
-- converting the Latin radicals into Arabic ones so we distinguish
-- between ambiguous and non-ambiguous radicals.
if radical_is_ambiguous(ur1) or radical_is_ambiguous(ur2) or radical_is_ambiguous(ur3) or
ur4 and radical_is_ambiguous(ur4) then
insert_cat("有歧義字母的動詞") -- 暫時將 radical 翻譯成字母
end
if radical_is_unambiguous_weak(ur1) then
insert_cat("第一個字母為" .. rget(ur1) .. "的第" .. vform .. "類動詞")
end
if radical_is_unambiguous_weak(ur2) then
insert_cat("第二個字母為" .. rget(ur2) .. "的第" .. vform .. "類動詞")
end
if radical_is_unambiguous_weak(ur3) then
insert_cat("第三個字母為" .. rget(ur3) .. "的第" .. vform .. "類動詞")
end
if ur4 and radical_is_unambiguous_weak(ur4) then
insert_cat("第四個字母為" .. rget(ur4) .. "的第" .. vform .. "類動詞")
end
end
end
if vform == "I" and not is_passive_only(base.passive) then
for _, vowel_spec in ipairs(base.grouped_conj_vowels) do
insert_ann("vowels",
("%s ~ %s"):format(table.concat(vowel_spec.pasts, "/"), table.concat(vowel_spec.nonpasts, "/")))
for _, past in ipairs(vowel_spec.pasts) do
for _, nonpast in ipairs(vowel_spec.nonpasts) do
if past == "-" or nonpast == "-" then
error("Internal error: Saw form I past vowel %s and non-past vowel %s but - in place of vowel should have triggered an error earlier")
end
insert_cat(("有過去時元音%s和非過去時元音%s的第I類動詞"):format(past, nonpast))
end
end
end
end
for slot, name in pairs(slots_that_may_be_uncertain) do
if base.slot_uncertain[slot] then
-- An unspecified and non-defaulted verbal noun (form I) is considered uncertain rather than explicitly
-- missing. Use <vn:-> to explicitly indicate the lack of verbal noun. Same for form-I stative active
-- participles.
insert_cat(("有未知或不確定%s的動詞"):format(name))
end
end
if base.irregular then
insert_ann("irreg", "不規則")
insert_cat("不規則動詞")
end
end
-- Compute the categories to add the verb to, as well as the annotation to display in the conjugation title bar. We
-- combine the code to do these functions as both categories and title bar contain similar information.
local function compute_categories_and_annotation(alternant_multiword_spec)
alternant_multiword_spec.categories = {}
local ann = {}
alternant_multiword_spec.annotation = ann
ann.form = {}
ann.weakness = {}
ann.vowels = {}
ann.passive = nil
ann.reduced = {}
ann.irreg = {}
ann.defective = {}
local multiword_lemma = false
for _, slot in ipairs(export.potential_lemma_slots) do
if alternant_multiword_spec.forms[slot] then
for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do
if formobj.form:find(" ") then
multiword_lemma = true
break
end
end
break
end
end
local function insert_ann(anntype, value)
m_table.insertIfNot(alternant_multiword_spec.annotation[anntype], value)
end
local function insert_cat(cat, also_when_multiword)
-- Don't place multiword terms in categories like 'Arabic form-II verbs' to avoid spamming the categories with
-- such terms.
if also_when_multiword or not multiword_lemma then
m_table.insertIfNot(alternant_multiword_spec.categories, cat:find("的") and cat:gsub("的", "的阿拉伯語") or "阿拉伯語" .. cat) -- L10N
end
end
iut.map_word_specs(alternant_multiword_spec, function(base)
add_categories_and_annotation(alternant_multiword_spec, base, multiword_lemma, insert_ann, insert_cat)
end)
for slot, name in pairs(slots_that_may_be_uncertain) do
if alternant_multiword_spec.forms[slot] then
for _, form in ipairs(alternant_multiword_spec.forms[slot]) do
if form.uncertain then
if form.form == "?" then
insert_cat(("有明確未知%s的動詞"):format(name))
else
insert_cat(("需要%s檢查的動詞"):format(name))
end
break
end
end
end
end
if alternant_multiword_spec.has_active then
if alternant_multiword_spec.has_passive and alternant_multiword_spec.has_non_impers_passive then
insert_cat("有完整被動形式的動詞")
ann.passive = "完整被動"
elseif alternant_multiword_spec.has_passive then
insert_cat("有無人稱被動形式的動詞")
ann.passive = "無人稱被動"
else
insert_cat("缺少被動形式的動詞")
ann.passive = "無被動"
end
else
if alternant_multiword_spec.has_non_impers_passive then
insert_cat("被動動詞")
insert_cat("有完整被動形式的動詞")
ann.passive = "僅用被動"
else
insert_cat("被動動詞")
insert_cat("無人稱動詞")
insert_cat("有無人稱被動形式的動詞")
ann.passive = "無人稱 (僅用被動)"
end
end
if alternant_multiword_spec.passive_uncertain then
insert_cat("需要檢查被動形式的動詞")
ann.passive = ann.passive .. ' <abbr title="被動語態狀態不確定">(?)</abbr>'
end
if alternant_multiword_spec.has_active and not alternant_multiword_spec.has_imp then
insert_ann("defective", "無祈使式")
insert_cat("缺少祈使形式的動詞")
end
if not alternant_multiword_spec.has_past then
insert_ann("defective", "無過去時")
insert_cat("缺少過去時形式的動詞")
end
if not alternant_multiword_spec.has_nonpast then
insert_ann("defective", "無非過去時")
insert_cat("缺少非過去時形式的動詞")
end
local ann_parts = {}
local function insert_ann_part(part, conj)
local val = table.concat(ann[part], conj or " 或 ")
if val ~= "" and val ~= "regular" then
table.insert(ann_parts, val)
end
end
insert_ann_part("form")
insert_ann_part("weakness")
insert_ann_part("reduced")
insert_ann_part("vowels")
if ann.passive then
table.insert(ann_parts, ann.passive)
end
insert_ann_part("irreg")
insert_ann_part("defective", ", ")
alternant_multiword_spec.annotation = table.concat(ann_parts, ",")
end
local function show_forms(alternant_multiword_spec)
local lemmas = {}
for _, slot in ipairs(export.potential_lemma_slots) do
if alternant_multiword_spec.forms[slot] then
for _, formobj in ipairs(alternant_multiword_spec.forms[slot]) do
table.insert(lemmas, formobj)
end
break
end
end
alternant_multiword_spec.lemmas = lemmas -- save for later use in make_table()
alternant_multiword_spec.vn = alternant_multiword_spec.forms.vn -- save for later use in make_table()
-- Reconstruct the original verb spec without overrides for verbal nouns and participles, since those specific slots
-- are ignored by {{ar-verb form}}. Compute this once beforehand; `transform_accel_obj` is called repeatedly on each
-- form and we don't want to compute this repeatedly.
local reconstructed_verb_spec = iut.reconstruct_original_spec(alternant_multiword_spec, {
preprocess_angle_bracket_spec = function(spec)
spec = spec:match("^<(.*)>$")
assert(spec)
local segments = put.parse_multi_delimiter_balanced_segment_run(spec, {{"[", "]"}, {"<", ">"}})
local dot_separated_groups = put.split_alternating_runs_and_strip_spaces(segments, "%.")
-- Rejoin each dot-separated group into a single string, since we aren't actually going to do any parsing
-- of bracket-bounded textual runs; then filter out overrides for verbal nouns and participles.
local filtered_indicators = {}
for _, dot_separated_group in ipairs(dot_separated_groups) do
local indicator = table.concat(dot_separated_group)
-- FIXME: Do we want to filter out any other indicators?
if not (indicator:find("^vn:") or indicator:find("^[ap]p:")) then
table.insert(filtered_indicators, indicator)
end
end
return ("<%s>"):format(table.concat(filtered_indicators, "."))
end,
})
-- If we're dealing with a single word, no alternants and a single verb form, use the auto-conjugation-fetching
-- variant.
local reconstructed_lemma, inside = reconstructed_verb_spec:match("^([^ <>()]+)(%b<>)$")
if inside and alternant_multiword_spec.verb_forms[1] and not alternant_multiword_spec.verb_forms[2] then
reconstructed_verb_spec = ("+%s<%s>"):format(reconstructed_lemma, alternant_multiword_spec.verb_forms[1])
end
local function transform_accel_obj(slot, formobj, accel_obj)
if not accel_obj then
return accel_obj
end
if slot == "ap" or slot == "pp" or slot == "vn" then
-- FIXME: [[Module:accel]] can't correctly handle more than one verb form for participles and verbal nouns
accel_obj.form = slot .. "-" .. table.concat(alternant_multiword_spec.verb_forms, ",")
else
accel_obj.form = "verb-form-" .. reconstructed_verb_spec
end
return accel_obj
end
local function generate_link(data)
local form = data.form
local term = form.formval_for_link
local alt = form.alt
if term == "?" then
term = nil
alt = "?"
end
local link = m_links.full_link {
lang = lang, term = term, tr = "-", accel = form.accel_obj,
alt = alt, gloss = form.gloss, genders = form.genders, pos = form.pos, lit = form.lit, id = form.id,
} .. iut.get_footnote_text(form.footnotes, data.footnote_obj)
if form.q and form.q[1] or form.qq and form.qq[1] or form.l and form.l[1] or form.ll and form.ll[1] then
link = require(decorations_module).format_decorations {
lang = lang,
text = link,
q = form.q,
qq = form.qq,
l = form.l,
ll = form.ll,
}
end
return link
end
local props = {
lang = lang,
lemmas = lemmas,
transform_accel_obj = transform_accel_obj,
generate_link = generate_link,
slot_list = alternant_multiword_spec.verb_slots,
include_translit = true,
}
iut.show_forms(alternant_multiword_spec.forms, props)
end
-------------------------------------------------------------------------------
-- Functions to create inflection tables --
-------------------------------------------------------------------------------
-- Make the conjugation table. Called from export.show().
local function make_table(alternant_multiword_spec)
local text = mw.getCurrentFrame():expandTemplate{
title = 'inflection-table-top',
args = {
title = '{title} 的變位',
tall = 'yes',
palette = "green",
category = 'conjugation',
class = 'tr-alongside', -- temp hack to prevent extra line break
}
}
text = text .. [=[
! colspan="6" | 動詞性名詞<br /><<الْمَصْدَر>>
| colspan="7" | {vn}
]=]
if alternant_multiword_spec.has_active then
text = text .. [=[
|-
! colspan="6" | 主動分詞<br /><<اِسْم الْفَاعِل>>
| colspan="7" | {ap}
]=]
end
if alternant_multiword_spec.has_passive then
text = text .. [=[
|-
! colspan="6" | 被動分詞<br /><<اِسْم الْمَفْعُول>>
| colspan="7" | {pp}
]=]
end
text = text .. [=[
|-
! colspan="999" class="separator" |
]=]
if alternant_multiword_spec.has_active then
text = text .. [=[
|-
! colspan="12" class="outer" | 主動語態<br /><<الْفِعْل الْمَعْلُوم>>
|-
! colspan="2" |
! colspan="3" | 單數<br /><<الْمُفْرَد>>
! rowspan="12" class="separator" |
! colspan="2" | 雙數<br /><<الْمُثَنَّى>>
! rowspan="12" class="separator" |
! colspan="3"| 複數<br /><<الْجَمْع>>
|-
! colspan="2" |
! 第一人稱<br /><<الْمُتَكَلِّم>>
! 第二人稱<br /><<الْمُخَاطَب>>
! 第三人稱<br /><<الْغَائِب>>
! 第二人稱<br /><<الْمُخَاطَب>>
! 第三人稱<br /><<الْغَائِب>>
! 第一人稱<br /><<الْمُتَكَلِّم>>
! 第二人稱<br /><<الْمُخَاطَب>>
! 第三人稱<br /><<الْغَائِب>>
|-
! rowspan="2" | 過去(完成)直陳語氣<br /><<الْمَاضِي>>
! class="secondary" | 陽
| rowspan="2" | {past_1s}
| {past_2ms}
| {past_3ms}
| rowspan="2" | {past_2d}
| {past_3md}
| rowspan="2" | {past_1p}
| {past_2mp}
| {past_3mp}
|-
! class="secondary" | 陰
| {past_2fs}
| {past_3fs}
| {past_3fd}
| {past_2fp}
| {past_3fp}
|-
! rowspan="2" | 非過去(完成)直陳語氣<br /><<الْمُضَارِع الْمَرْفُوع>>
! class="secondary" | 陽
| rowspan="2" | {ind_1s}
| {ind_2ms}
| {ind_3ms}
| rowspan="2" | {ind_2d}
| {ind_3md}
| rowspan="2" | {ind_1p}
| {ind_2mp}
| {ind_3mp}
|-
! class="secondary" | 陰
| {ind_2fs}
| {ind_3fs}
| {ind_3fd}
| {ind_2fp}
| {ind_3fp}
|-
! rowspan="2" | 假設語氣<br /><<الْمُضَارِع الْمَنْصُوب>>
! class="secondary" | 陽
| rowspan="2" | {sub_1s}
| {sub_2ms}
| {sub_3ms}
| rowspan="2" | {sub_2d}
| {sub_3md}
| rowspan="2" | {sub_1p}
| {sub_2mp}
| {sub_3mp}
|-
! class="secondary" | 陰
| {sub_2fs}
| {sub_3fs}
| {sub_3fd}
| {sub_2fp}
| {sub_3fp}
|-
! rowspan="2" | 命令語氣<br /><<الْمُضَارِع الْمَجْزُوم>>
! class="secondary" | 陽
| rowspan="2" | {juss_1s}
| {juss_2ms}
| {juss_3ms}
| rowspan="2" | {juss_2d}
| {juss_3md}
| rowspan="2" | {juss_1p}
| {juss_2mp}
| {juss_3mp}
|-
! class="secondary" | 陰
| {juss_2fs}
| {juss_3fs}
| {juss_3fd}
| {juss_2fp}
| {juss_3fp}
|-
! rowspan="2" | 祈使語氣<br /><<الْأَمْر>>
! class="secondary" | 陽
| rowspan="2" |
| {imp_2ms}
| rowspan="2" |
| rowspan="2" | {imp_2d}
| rowspan="2" |
| rowspan="2" |
| {imp_2mp}
| rowspan="2" |
|-
! class="secondary" | 陰
| {imp_2fs}
| {imp_2fp}
]=]
end
if alternant_multiword_spec.has_passive then
text = text .. [=[
|-
! colspan="999" class="separator" |
|-
! colspan="12" class="outer" | 被動語態<br /><<الْفِعْل الْمَجْهُول>>
|-
! colspan="2" |
! colspan="3" | 單數<br /><<الْمُفْرَد>>
! rowspan="10" class="separator" |
! colspan="2" | 雙數<br /><<الْمُثَنَّى>>
! rowspan="10" class="separator" |
! colspan="3" | 複數<br /><<الْجَمْع>>
|-
! colspan="2" |
! 第一人稱<br /><<الْمُتَكَلِّم>>
! 第二人稱<br /><<الْمُخَاطَب>>
! 第三人稱<br /><<الْغَائِب>>
! 第二人稱<br /><<الْمُخَاطَب>>
! 第三人稱<br /><<الْغَائِب>>
! 第一人稱<br /><<الْمُتَكَلِّم>>
! 第二人稱<br /><<الْمُخَاطَب>>
! 第三人稱<br /><<الْغَائِب>>
|-
! rowspan="2" | 過去(完成)直陳語氣<br /><<الْمَاضِي>>
! class="secondary" | m
| rowspan="2" | {past_pass_1s}
| {past_pass_2ms}
| {past_pass_3ms}
| rowspan="2" | {past_pass_2d}
| {past_pass_3md}
| rowspan="2" | {past_pass_1p}
| {past_pass_2mp}
| {past_pass_3mp}
|-
! class="secondary" | 陰
| {past_pass_2fs}
| {past_pass_3fs}
| {past_pass_3fd}
| {past_pass_2fp}
| {past_pass_3fp}
|-
! rowspan="2" | 非過去(完成)直陳語氣<br /><<الْمُضَارِع الْمَرْفُوع>>
! class="secondary" | 陽
| rowspan="2" | {ind_pass_1s}
| {ind_pass_2ms}
| {ind_pass_3ms}
| rowspan="2" | {ind_pass_2d}
| {ind_pass_3md}
| rowspan="2" | {ind_pass_1p}
| {ind_pass_2mp}
| {ind_pass_3mp}
|-
! class="secondary" | 陰
| {ind_pass_2fs}
| {ind_pass_3fs}
| {ind_pass_3fd}
| {ind_pass_2fp}
| {ind_pass_3fp}
|-
! rowspan="2" | 假設語氣<br /><<الْمُضَارِع الْمَنْصُوب>>
! class="secondary" | 陽
| rowspan="2" | {sub_pass_1s}
| {sub_pass_2ms}
| {sub_pass_3ms}
| rowspan="2" | {sub_pass_2d}
| {sub_pass_3md}
| rowspan="2" | {sub_pass_1p}
| {sub_pass_2mp}
| {sub_pass_3mp}
|-
! class="secondary" | 陰
| {sub_pass_2fs}
| {sub_pass_3fs}
| {sub_pass_3fd}
| {sub_pass_2fp}
| {sub_pass_3fp}
|-
! rowspan="2" | 命令語氣<br /><<الْمُضَارِع الْمَجْزُوم>>
! class="secondary" | 陽
| rowspan="2" | {juss_pass_1s}
| {juss_pass_2ms}
| {juss_pass_3ms}
| rowspan="2" | {juss_pass_2d}
| {juss_pass_3md}
| rowspan="2" | {juss_pass_1p}
| {juss_pass_2mp}
| {juss_pass_3mp}
|-
! class="secondary" | 陰
| {juss_pass_2fs}
| {juss_pass_3fs}
| {juss_pass_3fd}
| {juss_pass_2fp}
| {juss_pass_3fp}
]=]
end
text = text .. mw.getCurrentFrame():expandTemplate{
title = 'inflection-table-bottom',
args = {
notes = '{footnote}',
}
}
mw.logObject(text)
local forms = alternant_multiword_spec.forms
if not alternant_multiword_spec.lemmas then
forms.title = "—"
else
local linked_lemmas = {}
for _, form in ipairs(alternant_multiword_spec.lemmas) do
table.insert(linked_lemmas, link_term(form.form, "term"))
end
forms.title = table.concat(linked_lemmas, ",")
end
local ann_parts = {}
if alternant_multiword_spec.annotation ~= "" then
table.insert(ann_parts, alternant_multiword_spec.annotation)
end
if alternant_multiword_spec.vn then
local linked_vns = {}
for _, form in ipairs(alternant_multiword_spec.vn) do
table.insert(linked_vns, link_term(form.form, "term"))
end
table.insert(ann_parts, (#linked_vns > 1 and "動詞性名詞" or "動詞性名詞") ..
table.concat(linked_vns, ","))
end
local annotation = table.concat(ann_parts, ",")
if annotation ~= "" then
forms.title = forms.title .. "(" .. annotation .. ")"
end
-- Format the table.
local tagged_table = rsub(text, "<<(.-)>>", tag_text)
return m_string_utilities.format(tagged_table, forms)
end
-------------------------------------------------------------------------------
-- External entry points --
-------------------------------------------------------------------------------
-- Append two lists `l1` and `l2`, removing duplicates. If either is {nil}, just return the other.
local function combine_lists(l1, l2)
-- combine_footnotes() does exactly what we want.
return iut.combine_footnotes(l1, l2)
end
local function combine_metadata(data)
local src1 = data.form1
local src2 = data.form2
local dest = data.dest_form
dest.uncertain = src1.uncertain or src2.uncertain
if src1.genders and src2.genders and not m_table.deepEquals(src1.genders, src2.genders) then
-- do nothing
else
dest.genders = src1.genders or src2.genders
end
if src1.pos and src2.pos and src1.pos ~= src2.pos then
-- do nothing
else
dest.pos = src1.pos or src2.pos
end
-- Don't copy .alt, .gloss, .lit, .id, which describe a single term and don't extend to multiword terms.
dest.q = combine_lists(src1.q, src2.q)
dest.qq = combine_lists(src1.qq, src2.qq)
dest.l = combine_lists(src1.l, src2.l)
dest.ll = combine_lists(src1.ll, src2.ll)
end
-- Externally callable function to parse and conjugate a verb given user-specified arguments.
-- Return value is WORD_SPEC, an object where the conjugated forms are in `WORD_SPEC.forms`
-- for each slot. If there are no values for a slot, the slot key will be missing. The value
-- for a given slot is a list of objects {form=FORM, footnotes=FOOTNOTES}.
function export.do_generate_forms(args, source_template, headword_head)
local PAGENAME = mw.loadData("Module:headword/data").pagename
local function in_template_space()
return mw.title.getCurrentTitle().nsText == "Template"
end
-- Determine the verb spec we're being asked to generate the conjugation of. This may be taken from the current page
-- title or the value of |pagename=; but not when called from {{ar-verb form}}, where the page title is a
-- non-lemma form. Note that the verb spec may omit the lemma; e.g. it may be "<II>". For this reason, we use the
-- value of `pagename` computed here down below, when calling normalize_all_lemmas().
local pagename = (source_template ~= "ar-verb form" or source_template ~= "Ar-verb form") and args.pagename or PAGENAME
local head = headword_head or pagename
local arg1 = args[1]
if not arg1 then
if (pagename == "ar-conj" or pagename == "ar-verb" or pagename == "ar-verb form" or pagename == "Ar-conj" or pagename == "Ar-verb" or pagename == "Ar-verb form") and in_template_space() then
arg1 = "كتب<I/a~u.pass>"
else
arg1 = "<>"
end
end
-- When called from {{ar-verb form}}, determine the non-lemma form whose inflections we're being asked to
-- determine. This normally comes from the page title or the value of |pagename=.
local verb_form_of_form
if source_template == "ar-verb form" then
verb_form_of_form = args.pagename
if not verb_form_of_form then
if (PAGENAME == "ar-verb form" or PAGENAME == "Ar-verb form") and in_template_space() then
verb_form_of_form = "كتبت"
else
verb_form_of_form = PAGENAME
end
end
end
local incorporated_headword_head_into_lemma = false
if arg1:find("^<.*>$") then -- missing lemma
if head:find(" ") then
-- If multiword lemma, try to add arg spec after the first word.
-- Try to preserve the brackets in the part after the verb, but don't do it
-- if there aren't the same number of left and right brackets in the verb
-- (which means the verb was linked as part of a larger expression).
local first_word, post = rmatch(head, "^(.-)( .*)$")
local left_brackets = rsub(first_word, "[^%[]", "")
local right_brackets = rsub(first_word, "[^%]]", "")
if #left_brackets == #right_brackets then
arg1 = iut.remove_redundant_links(first_word) .. arg1 .. post
incorporated_headword_head_into_lemma = true
else
-- Try again using the form without links.
local linkless_head = m_links.remove_links(head)
if linkless_head:find(" ") then
first_word, post = rmatch(linkless_head, "^(.-)( .*)$")
arg1 = first_word .. arg1 .. post
else
error("Unable to incorporate <...> spec into explicit head due to a multiword linked verb or " ..
"unbalanced brackets; please include <> explicitly: " .. arg1)
end
end
else
-- Will be incorporated through `head` below in the call to normalize_all_lemmas().
incorporated_headword_head_into_lemma = true
end
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
angle_brackets_omittable = true,
allow_blank_lemma = true,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg1, parse_props)
alternant_multiword_spec.pos = pos or "verbs"
alternant_multiword_spec.args = args
alternant_multiword_spec.source_template = source_template
alternant_multiword_spec.verb_form_of_form = verb_form_of_form
alternant_multiword_spec.incorporated_headword_head_into_lemma = incorporated_headword_head_into_lemma
normalize_all_lemmas(alternant_multiword_spec, head)
detect_all_indicator_specs(alternant_multiword_spec)
local inflect_props = {
lang = lang,
slot_list = alternant_multiword_spec.verb_slots,
inflect_word_spec = conjugate_verb,
combine_metadata = combine_metadata,
-- We add links around the generated verbal forms rather than allow the entire multiword
-- expression to be a link, so ensure that user-specified links get included as well.
include_user_specified_links = true,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
if debug_translit then
for slot, forms in pairs(alternant_multiword_spec.forms) do
for _, form in ipairs(forms) do
if form.translit then
local full_form_translit = (lang:transliterate(m_links.remove_links(form.form)))
if full_form_translit ~= form.translit then
error(("Internal error: For slot '%s', form '%s' incremental translit '%s' not same as full translit '%s'"):
format(slot, form.form, form.translit, full_form_translit))
end
end
form.form = iut.remove_redundant_links(form.form)
end
end
end
-- Remove redundant brackets around entire forms.
for slot, forms in pairs(alternant_multiword_spec.forms) do
for _, form in ipairs(forms) do
form.form = iut.remove_redundant_links(form.form)
end
end
determine_slot_uncertainty_from_forms(alternant_multiword_spec)
determine_verb_properties_from_forms(alternant_multiword_spec)
compute_categories_and_annotation(alternant_multiword_spec)
if args.json and (source_template == "ar-conj" or source_template == "Ar-conj") then
-- There is a circular reference in `base.alternant_multiword_spec`, which points back to top level.
iut.map_word_specs(alternant_multiword_spec, function(base)
base.alternant_multiword_spec = nil
end)
return require("Module:JSON").toJSON(alternant_multiword_spec)
end
return alternant_multiword_spec
end
-- Entry point for {{ar-conj}}. Template-callable function to parse and conjugate a verb given
-- user-specified arguments and generate a displayable table of the conjugated forms.
function export.show(frame)
local parent_args = frame:getParent().args
local params = {
[1] = {},
["noautolinktext"] = {type = "boolean"},
["noautolinkverb"] = {type = "boolean"},
["t"] = {}, -- for use by {{ar-verb form}}; otherwise ignored
["id"] = {}, -- for use by {{ar-verb form}}; otherwise ignored
["pagename"] = {}, -- for testing/documentation pages
["json"] = {type = "boolean"}, -- for bot use
}
local args = require("Module:parameters").process(parent_args, params)
local alternant_multiword_spec = export.do_generate_forms(args, "ar-conj")
if type(alternant_multiword_spec) == "string" then
-- JSON return value
return alternant_multiword_spec
end
show_forms(alternant_multiword_spec)
return make_table(alternant_multiword_spec) ..
require("Module:utilities").format_categories(alternant_multiword_spec.categories, lang, nil, nil, force_cat)
end
function export.verb_forms(frame)
local parargs = frame:getParent().args
local params = {
[1] = {},
[2] = {},
[3] = {},
[4] = {},
[5] = {},
pagename = {},
}
for _, form in ipairs(allowed_vforms) do
-- FIXME: We go up to 5 here. The code supports unlimited variants but it's unlikely we will ever see more than
-- 2.
for index = 1, 5 do
local prefix = index == 1 and form or form .. index
params[prefix .. "-pv"] = {}
for _, extn in ipairs { "", "-vn", "-ap", "-pp" } do
params[prefix .. extn] = {}
params[prefix .. extn .. "-head"] = {}
-- FIXME: No -tr?
params[prefix .. extn .. "-gloss"] = {}
end
end
end
local args = require("Module:parameters").process(parargs, params)
local i = 1
local past_vowel_re = "^[aui,]*$"
local combined_root = nil
if not args[i] or rfind(args[i], past_vowel_re) then
combined_root = args.pagename or mw.loadData("Module:headword/data").pagename
if not rfind(combined_root, "^([^ ]) ([^ ]) ([^ ])$") and not
rfind(combined_root, "^([^ ]) ([^ ]) ([^ ]) ([^ ])$") then
error("When inferring roots from page title, need three or four space-separated radicals: " .. combined_root)
end
elseif rfind(args[i], " ") then
combined_root = args[i]
i = i + 1
else
local separate_roots = {}
while args[i] and not rfind(args[i], past_vowel_re) do
table.insert(separate_roots, args[i])
i = i + 1
end
combined_root = table.concat(separate_roots, " ")
end
local past_vowel = args[i]
i = i + 1
if past_vowel and not rfind(past_vowel, past_vowel_re) then
error("Unrecognized past vowel, should be 'a', 'i', 'u', 'a,u', etc. or empty: " .. past_vowel)
end
-- Spaces interfere with parsing as a unit in [[Module:inflection utilities]], so replace with underscore.
combined_root = combined_root:gsub(" ", "_")
local split_root = rsplit(combined_root, "_")
-- Map from verb forms (I, II, etc.) to a table of verb properties,
-- which has entries e.g. for "verb" (either true to autogenerate the verb
-- head, or an explicitly specified verb head using e.g. argument "I-head"),
-- and for "verb-gloss" (which comes from e.g. the argument "I" or "I-gloss"),
-- and for "vn" and "vn-gloss", "ap" and "ap-gloss", "pp" and "pp-gloss".
local verb_properties = {}
for _, form in ipairs(allowed_vforms) do
local formpropslist = {}
local derivs = {{"verb", ""}, {"vn", "-vn"}, {"ap", "-ap"}, {"pp", "-pp"}}
local index = 1
while true do
local formprops = {}
local prefix = index == 1 and form or form .. index
if prefix == "I" then
formprops.pv = past_vowel
end
if args[prefix .. "-pv"] then
formprops.pv = args[prefix .. "-pv"]
end
for _, deriv in ipairs(derivs) do
local prop = deriv[1]
local extn = deriv[2]
if args[prefix .. extn] == "+" then
formprops[prop] = true
elseif args[prefix .. extn] == "-" then
formprops[prop] = false
elseif args[prefix .. extn] then
formprops[prop] = true
formprops[prop .. "-gloss"] = args[prefix .. extn]
end
if args[prefix .. extn .. "-head"] then
if formprops[prop] == nil then
formprops[prop] = true
end
formprops[prop] = args[prefix .. extn .. "-head"]
end
if args[prefix .. extn .. "-gloss"] then
if formprops[prop] == nil then
formprops[prop] = true
end
formprops[prop .. "-gloss"] = args[prefix .. extn .. "-gloss"]
end
end
if formprops.verb then
-- If a verb form specified, also turn on vn (unless form I, with
-- unpredictable vn) and ap, and maybe pp, according to form,
-- weakness and past vowel. But don't turn these on if there's
-- an explicit on/off specification for them (e.g. I-pp=-).
if form ~= "I" and formprops.vn == nil then
formprops.vn = true
end
if formprops.ap == nil then
formprops.ap = true
end
local weakness = weakness_from_radicals(form, split_root[1], split_root[2], split_root[3],
split_root[4])
if formprops.pp == nil and not vform_probably_no_passive(form,
weakness, rsplit(formprops.pv or "", ","), {}) then
formprops.pp = true
end
if formprops.verb == true or formprops.vn == true or formprops.ap == true or formprops.pp == true then
formprops.need_autogen = true
end
table.insert(formpropslist, formprops)
index = index + 1
else
break
end
end
table.insert(verb_properties, {form, formpropslist})
end
-- Go through and create the verb form derivations as necessary, when they haven't been explicitly given.
for _, vplist in ipairs(verb_properties) do
local vform = vplist[1]
for _, props in ipairs(vplist[2]) do
if props.need_autogen then
local form_with_vowels
if vform == "I" then
local pv = props.pv
if not pv then
-- Make up likely past vowels based on weakness and actual radical.
if split_root[3] == W then -- final-weak
form_with_vowels = "I/a~u"
elseif split_root[3] == Y then
form_with_vowels = "I/a~i"
elseif split_root[2] == W then --hollow
form_with_vowels = "I/u~u"
elseif split_root[2] == Y then
form_with_vowels = "I/i~i"
else
-- most common; doesn't matter so much since we're not displaying the non-past
form_with_vowels = "I/a~u"
end
else
local pvs = rsplit(pv, ",")
local vowel_sufs = {}
for _, pv in ipairs(pvs) do
local vowel_spec
if pv == "a" then
-- Make up likely past vowels based on weakness and actual radical.
if split_root[3] == W then -- final-weak
vowel_spec = "a~u"
elseif split_root[3] == Y then
vowel_spec = "a~i"
elseif split_root[2] == W then --hollow
vowel_spec = "a~u"
elseif split_root[2] == Y then
vowel_spec = "a~i"
else
-- most common; doesn't matter so much since we're not displaying the non-past
vowel_spec = "a~u"
end
elseif pv == "i" then
-- most common; doesn't matter so much since we're not displaying the non-past
vowel_spec = "i~a"
elseif pv == "u" then
-- most common; doesn't matter so much since we're not displaying the non-past
vowel_spec = "u~u"
else
error(("Internal error: Bad past vowel '%s' in {{ar-verb forms}}"):format(pv))
end
table.insert(vowel_sufs, vowel_spec)
end
form_with_vowels = "I/" .. table.concat(vowel_sufs, "/")
end
else
form_with_vowels = vform
end
local angle_bracket_spec = ("%s<%s.pass>"):format(combined_root, form_with_vowels)
local alternant_multiword_spec = export.do_generate_forms({angle_bracket_spec}, "ar-verb forms")
local function format_forms(forms)
if not forms then
return "-" -- FIXME: Throw an error?
end
local formatted = {}
for _, form in ipairs(forms) do
if form.translit then
table.insert(formatted, ("%s//%s"):format(form.form, form.translit))
else
table.insert(formatted, form.form)
end
end
return table.concat(formatted, ",")
end
if props.verb == true then
props.verb = format_forms(alternant_multiword_spec.forms.past_3ms)
end
for _, deriv in ipairs({"vn", "ap", "pp"}) do
if props[deriv] == true then
props[deriv] = format_forms(alternant_multiword_spec.forms[deriv])
end
end
end
end
end
-- Go through and output the result
local formtextarr = {}
for _, vplist in ipairs(verb_properties) do
local form = vplist[1]
for _, props in ipairs(vplist[2]) do
local textarr = {}
if props.verb then
local text = "* '''[[Appendix:阿拉伯語動詞#第" .. form .. "類|第" .. form .. "類]]''':"
local linktext = {}
local splitheads = rsplit(props.verb, "[,،]")
for _, head in ipairs(splitheads) do
table.insert(linktext, m_links.full_link({lang = lang, term = head, gloss = props["verb-gloss"]}))
end
text = text .. table.concat(linktext, ", ")
table.insert(textarr, text)
for _, derivengl in ipairs({{"vn", "動詞性名詞"}, {"ap", "主動分詞"}, {"pp", "被動分詞"}}) do
local deriv = derivengl[1]
local engl = derivengl[2]
if props[deriv] then
local text = "** " .. engl .. ": "
local linktext = {}
local splitheads = rsplit(props[deriv], "[,،]")
for _, head in ipairs(splitheads) do
local ar, translit = head:match("^(.*)//(.-)$")
if not ar then
ar = head
end
table.insert(linktext, m_links.full_link {lang = lang, term = ar, tr = translit,
gloss = props[deriv .. "-gloss"]} )
end
text = text .. table.concat(linktext, ", ")
table.insert(textarr, text)
end
end
table.insert(formtextarr, table.concat(textarr, "\n"))
end
end
end
return table.concat(formtextarr, "\n")
end
-- Infer radicals from lemma headword (i.e. 3rd masculine singular past) and verb form (I, II, etc.). Throw an error if
-- headword is malformed. A given returned radical may be actually be a list of possible radicals, where the first one
-- should be used if the user didn't explicitly give the radical. If the list contains a field `ambig = true`, the
-- radical is considered ambiguous and should not be categorized. `is_reduced` indicates that the user specified
-- `.reduced` to indicate that the verb form is reduced by assimilation and/or haplology (typically archaic Koranic
-- forms such as اِدَّارَأَ instead of تَدَارَأَ; or اِسْطَاعَ instead of اِسْتِطَاعَ; etc.
function export.infer_radicals(data)
local headword, vform, passive, past_vowel, nonpast_vowel, is_reduced =
data.headword, data.vform, data.passive, data.past_vowel, data.nonpast_vowel, data.is_reduced
past_vowel = past_vowel or "-"
nonpast_vowel = nonpast_vowel or "-"
local function verify_vowel(vowel, param)
if vowel ~= A and vowel ~= I and vowel ~= U and vowel ~= "-" then
error(("Internal error: Bad value for %s: %s (should be Arabic diacritic vowel or '-')"):format(
param, vowel))
end
end
verify_vowel(past_vowel, "past_vowel")
verify_vowel(nonpast_vowel, "nonpast_vowel")
local ch = {}
local form_viii_assim, variant
-- sub out alif-madda for easier processing
headword = rsub(headword, AMAD, HAMZA .. ALIF)
local function infer_err(msg, noann)
local anns = {}
local nohead, novform
if noann == "nohead" then
nohead = true
elseif noann == "novform" then
novform = true
elseif noann == "nohead-vform" then
nohead = true
novform = true
elseif noann then
error(("Internal error: Unrecognized value for 'noann': %s"):format(dump(noann)))
end
if not nohead then
table.insert(anns, ("headword=%s"):format(data.headword))
end
if not novform then
table.insert(anns, ("verb form=%s"):format(data.vform))
end
anns = table.concat(anns, ", ")
if anns ~= "" then
anns = ": " .. anns
end
error(msg .. anns)
end
local len = ulen(headword)
local expected_length
-- extract the headword letters into an array
for i = 1, len do
table.insert(ch, usub(headword, i, i))
end
-- check that the letter at the given index is the given string, or
-- is one of the members of the given array
local function check(index, must)
local letter = ch[index]
if type(must) == "string" then
if not letter then
infer_err("Letter " .. index .. " is nil")
end
if letter ~= must then
infer_err(("For verb form %s, letter %s must be %s, not %s"):format(vform, index, must, letter),
"novform")
end
elseif not m_table.contains(must, letter) then
infer_err("For verb form " .. vform .. ", radical " .. index ..
" must be one of " .. table.concat(must, " ") .. ", not " .. letter, "novform")
end
end
-- Check that length of headword is within [min, max]
local function check_len(min, max)
if min and len < min then
infer_err(("Not enough letters for verb form %s, expected at least %s"):format(vform, min), "novform")
end
if max and len > max then
infer_err(("Too many letters for verb form %s, expected at most %s"):format(vform, max), "novform")
end
end
-- If the vowels are i~a or u~u, a form I verb beginning with w- normally keeps the w in the non-past. Otherwise it
-- loses it (i.e. it is "assimilated").
local function form_I_w_non_assimilated()
return req(past_vowel, I) and req(nonpast_vowel, A) or req(past_vowel, U) and req(nonpast_vowel, U)
end
-- Convert radicals to canonical form (handle various hamza varieties and check for misplaced alif or alif maqṣūra;
-- legitimate cases of these letters are handled above).
local function convert(rad, index)
if type(rad) == "table" then
for i, r in ipairs(rad) do
rad[i] = convert(r, index)
end
return rad
elseif rad == HAMZA_ON_ALIF or rad == HAMZA_UNDER_ALIF or
rad == HAMZA_ON_W or rad == HAMZA_ON_Y then
return HAMZA
elseif rad == AMAQ then
infer_err("Radical " .. index .. " must not be alif maqṣūra")
elseif rad == ALIF then
infer_err("Radical " .. index .. " must not be alif")
else
return rad
end
end
local quadlit = vform:find("q$")
-- find first radical, start of second/third radicals, check for
-- required letters
local radstart, rad1, rad2, rad3, rad4
local weakness
if vform == "I" or vform == "II" then
rad1 = ch[1]
radstart = 2
elseif vform == "III" then
rad1 = ch[1]
check(2, {ALIF, W}) -- W occurs in passive-only verbs
radstart = 3
elseif vform == "IV" then
-- this would be alif-madda but we replaced it with hamza-alif above.
if ch[1] == HAMZA and ch[2] == ALIF then
rad1 = HAMZA
else
check(1, HAMZA_ON_ALIF)
rad1 = ch[2]
end
radstart = 3
elseif vform == "V" then
check(1, is_reduced and ALIF or T)
rad1 = ch[2]
radstart = 3
elseif vform == "VI" then
check(1, is_reduced and ALIF or T)
if ch[2] == AMAD then
rad1 = HAMZA
radstart = 3
else
rad1 = ch[2]
check(3, {ALIF, W}) -- W occurs in passive-only verbs
radstart = 4
end
elseif vform == "VII" then
check(1, ALIF)
if is_reduced then
check(2, M)
rad1 = M
radstart = 3
else
check(2, N)
rad1 = ch[3]
radstart = 4
end
elseif vform == "VIII" then
check(1, ALIF)
rad1 = ch[2]
if rad1 == "د" then
rad1 = {"د", "ذ"} -- not considered ambiguous since it's usually د
radstart = 3
form_viii_assim = "دّ"
elseif rad1 == "ظ" and ch[3] == "ط" and len >= 5 then
-- [[اظطلم]], variant of [[اظلم]]
radstart = 4
form_viii_assim = "ظْط"
elseif rad1 == "ذ" and ch[3] == "د" and len >= 5 then
-- [[اذدكر]], variant of [[اذكر]]
radstart = 4
form_viii_assim = "ذْد"
elseif rad1 == T or rad1 == "ث" or rad1 == "ذ" or rad1 == "ط" or rad1 == "ظ" then
radstart = 3
form_viii_assim = rad1 .. SH
elseif rad1 == "ز" then
check(3, "د")
radstart = 4
form_viii_assim = "زْد"
elseif rad1 == "ص" or rad1 == "ض" then
check(3, "ط")
radstart = 4
form_viii_assim = rad1 .. SK .. "ط"
else
check(3, T)
radstart = 4
rad1 = convert(rad1, 1)
form_viii_assim = rad1 .. SK .. "ت"
end
if rad1 == T then
-- Radical is ambiguous, might be ت or و or ي but doesn't affect conjugation. Note that there are no
-- form-VIII verbs with initial radical ي given in Hans Wehr but Lane mentions at least:
-- - (page 2973) اِتَّأَسَ, with assimilation of the ي to ت, from root ي ء س;
-- - (page 2975) اِتَّبَسَ non-past يَتَّبِسُ and alternative اِيتَبَسَ non-past يَاتَبِسُ from the root ي ب س;
-- - (page 2976) اِتَّسَرَ non-past يَتَّسِرُ or alternatively يَأْتَسِرُ with hamza preserved from the root ي س ر.
-- These alternative forms seem very rare and probably not worth worrying about, but if we want to handle
-- them, we can do it when the time comes.
rad1 = {T, W, Y, ambig = true}
-- اِتَّخَذَ irregularly has hamza as the radical but assimilates like و
if ch[3] == "خ" and ch[4] == "ذ" then
rad1[4] = HAMZA
end
end
elseif vform == "IX" then
check(1, ALIF)
rad1 = ch[2]
radstart = 3
elseif vform == "X" then
check(1, ALIF)
check(2, S)
if is_reduced then
rad1 = ch[3]
radstart = 4
else
check(3, T)
rad1 = ch[4]
radstart = 5
end
elseif vform == "Iq" then
rad1 = ch[1]
rad2 = ch[2]
radstart = 3
elseif vform == "IIq" then
check(1, T)
rad1 = ch[2]
rad2 = ch[3]
radstart = 4
elseif vform == "IIIq" then
check(1, ALIF)
rad1 = ch[2]
rad2 = ch[3]
check(4, N)
radstart = 5
elseif vform == "IVq" then
check(1, ALIF)
rad1 = ch[2]
rad2 = ch[3]
radstart = 4
elseif vform == "XI" then
check_len(5, 5)
check(1, ALIF)
rad1 = ch[2]
rad2 = ch[3]
check(4, ALIF)
rad3 = ch[5]
weakness = "sound"
elseif vform == "XII" then
check(1, ALIF)
rad1 = ch[2]
if ch[3] ~= ch[5] then
infer_err("For verb form XII, letters 3 and 5 should be the same", "novform")
end
check(4, W)
radstart = 5
elseif vform == "XIII" then
check_len(5, 5)
check(1, ALIF)
rad1 = ch[2]
rad2 = ch[3]
check(4, W)
rad3 = ch[5]
if rad3 == AMAQ then
weakness = "final-weak"
else
weakness = "sound"
end
elseif vform == "XIV" then
check_len(6, 6)
check(1, ALIF)
rad1 = ch[2]
rad2 = ch[3]
check(4, N)
rad3 = ch[5]
if ch[6] == AMAQ then
check_waw_ya(rad3)
weakness = "final-weak"
else
if ch[5] ~= ch[6] then
infer_err("For verb form XIV, letters 5 and 6 should be the same", "novform")
end
weakness = "sound"
end
elseif vform == "XV" then
check_len(6, 6)
check(1, ALIF)
rad1 = ch[2]
rad2 = ch[3]
check(4, N)
rad3 = ch[5]
if rad3 == Y then
check(6, ALIF)
else
check(6, AMAQ)
end
weakness = "sound"
else
error("Internal error: Unrecognized verb form " .. vform)
end
-- Process the last two radicals. RADSTART is the index of the first of the two. If it's nil then all radicals have
-- already been processed above, and we don't do anything.
if radstart then
-- There must (normally) be one or two letters left.
if len == radstart then
if vform == "I" and ch[len] == Y then
-- short form حَيَّ
weakness = "final-weak"
rad2 = Y
rad3 = Y
variant = "short"
elseif vform == "IV" and rad1 == "ر" and ch[len] == AMAQ then
-- irregular verb أَرَى
weakness = "final-weak"
rad2 = HAMZA
rad3 = Y
elseif vform == "X" and rad1 == "ح" and ch[len] == AMAQ then
-- irregular verb اِسْتَحَى
weakness = "final-weak"
rad2 = Y
rad3 = Y
variant = "short"
else
-- If one letter left, then it's a geminate verb. If the letter is alif or alif maqṣūra, it will trigger
-- an error down the line.
if vform_supports_geminate(vform) then
weakness = "geminate"
rad2 = ch[len]
rad3 = ch[len]
if vform == "III" or vform == "VI" then
variant = "short"
end
else
infer_err("Apparent geminate verb, but geminate verbs not allowed for this verb form")
end
end
elseif quadlit then
-- Process last two radicals of a quadriliteral verb form.
rad3 = ch[radstart]
rad4 = ch[radstart + 1]
expected_length = radstart + 1
check_len(expected_length)
if rad4 == AMAQ or rad4 == ALIF and rad3 == Y or rad4 == Y then
-- rad4 can be Y in passive-only verbs.
if vform_supports_final_weak(vform) then
weakness = "final-weak"
-- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws in a row); it could be wāw or
-- yāʾ, but doesn't affect the conjugation.
rad4 = rad3 == W and {Y, W, ambig = true} or {W, Y, ambig = true}
else
infer_err("Last radical is " .. rad4 .. " but verb form " .. vform ..
" doesn't support final-weak verbs", "novform")
end
else
weakness = "sound"
end
else
-- Process last two radicals of a triliteral verb form.
rad2 = ch[radstart]
rad3 = ch[radstart + 1]
expected_length = radstart + 1
check_len(expected_length)
if vform == "I" and (is_waw_ya(rad3) or rad3 == ALIF or rad3 == AMAQ) then
local inferred_past_vowel, inferred_nonpast_vowel
-- Check for final-weak form I verb. It can end in tall alif (rad3 = wāw) or alif maqṣūra (rad3 = yāʾ)
-- or a wāw or yāʾ (with a past vowel of i or u, e.g. nasiya/yansā "forget" or with a passive-only
-- verb).
if rad1 == W and not form_I_w_non_assimilated() then
weakness = "assimilated+final-weak"
else
weakness = "final-weak"
end
if rad3 == ALIF then
rad3 = W
inferred_past_vowel = A
inferred_nonpast_vowel = U
if is_passive_only(passive) then
infer_err("Final-weak form-I passive verbs should end in yāʔ (ي), not tall alif (ا)", "novform")
end
elseif rad3 == AMAQ then
rad3 = Y
inferred_past_vowel = A
inferred_nonpast_vowel = I
if is_passive_only(passive) then
infer_err("Final-weak form-I passive verbs should end in yāʔ (ي), not alif maqṣūra (ى)",
"novform")
end
elseif rad1 == "ح" and rad2 == Y and rad3 == Y then
-- Long variant حَيِيَ.
inferred_past_vowel = I
inferred_nonpast_vowel = A
variant = "long"
else
if not is_passive_only(passive) then
-- does a non-passive final-weak verb in -uwa ever happen? (YES: e.g. [[رجو]] "to be slack")
inferred_past_vowel = rad3 == Y and I or U
inferred_nonpast_vowel = A
end
-- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws); it could be wāw or yāʾ, but
-- doesn't affect the conjugation.
rad3 = (rad1 == W or rad2 == W) and {Y, W, ambig = true} or {W, Y, ambig = true} -- ambiguous
end
if inferred_past_vowel then
local raw_past_vowel = rget(past_vowel)
local raw_nonpast_vowel = rget(nonpast_vowel)
if raw_past_vowel ~= "-" then
if raw_past_vowel ~= inferred_past_vowel then
infer_err(("Final-weak form-I verb inferred past vowel %s, which disagrees with " ..
"explicitly specified %s"):format(undia[inferred_past_vowel], undia[raw_past_vowel]), "novform")
else
-- in case of footnote in past_vowel
inferred_past_vowel = past_vowel
end
end
if raw_nonpast_vowel ~= "-" and raw_nonpast_vowel ~= A and inferred_nonpast_vowel == U then
-- if inferred as I or A, the reality can be the reverse; form-I final-weak verbs with a~a and
-- i~i exist, e.g. سَعَى/يَسْعَى, وَلِيَ/يَلِي. Weird verb [[صها]] (also written [[صهى]]) has non-past
-- يصهى so we can't throw an error in this situation.
if raw_nonpast_vowel ~= inferred_nonpast_vowel then
infer_err(("Final-weak form-I verb inferred non-past vowel %s, which disagrees with " ..
"explicitly specified %s"):format(undia[inferred_nonpast_vowel], undia[raw_nonpast_vowel]), "novform")
else
-- in case of footnote in nonpast_vowel
inferred_nonpast_vowel = nonpast_vowel
end
end
end
if not is_passive_only(passive) then
if rget(past_vowel) == "-" then
past_vowel = inferred_past_vowel
end
if rget(nonpast_vowel) == "-" then
nonpast_vowel = inferred_nonpast_vowel
end
end
elseif vform == "IX" and is_waw_ya(rad3) and len == radstart + 2 and ch[len] == AMAQ then
-- Final-weak form IX verbs like اِرْعَوَى "to desist, to repent, to see the light".
weakness = "final-weak"
expected_length = radstart + 2
elseif vform == "X" and rad1 == "ح" and rad2 == Y and rad3 == ALIF then
-- Long variant اِسْتَحْيَا.
weakness = "final-weak"
rad3 = Y
variant = "long"
elseif rad3 == AMAQ or rad2 == Y and rad3 == ALIF or rad3 == Y then
-- rad3 == Y happens in passive-only verbs.
if vform_supports_final_weak(vform) then
weakness = "final-weak"
else
infer_err("Last radical is " .. rad3 .. " but verb form doesn't support final-weak verbs")
end
-- Ambiguous radical; randomly pick wāw as radical (but avoid two wāws); it could be wāw or yāʾ, but
-- doesn't affect the conjugation.
rad3 = (rad1 == W or rad2 == W) and {Y, W, ambig = true} or {W, Y, ambig = true}
elseif rad2 == ALIF then
if vform_supports_hollow(vform) then
weakness = "hollow"
local function set_past_to_a()
if req(past_vowel, A) then
-- already set
elseif req(past_vowel, "-") or req(past_vowel, rget(nonpast_vowel)) then
past_vowel = A
else
infer_err(("Form I hollow verb with nonpast vowel set to '%s' must have past vowel set to 'a' or the same value, not %s"):
format(undia[rget(nonpast_vowel)], undia[rget(past_vowel)]), "novform")
end
end
if vform == "I" and req(nonpast_vowel, U) then
rad2 = W
set_past_to_a()
elseif vform == "I" and req(nonpast_vowel, I) then
rad2 = Y
set_past_to_a()
else
if req(nonpast_vowel, A) and not req(past_vowel, I) then
infer_err(("Form I hollow verb with nonpast vowel set to 'a' must have past vowel set to 'i', not %s"):
format(undia[rget(past_vowel)]), "novform")
end
-- Ambiguous radical; could be wāw or yāʾ; if verb form I, it's critical to get this right, and
-- the caller checks for this situation and throws an error if non-past vowel is "a" and second
-- radical isn't explicitly given.
rad2 = {W, Y, ambig = true, need_radical = true}
end
else
infer_err("Second radical is alif but verb form doesn't support hollow verbs")
end
elseif vform == "I" and rad1 == W and not form_I_w_non_assimilated() then
weakness = "assimilated"
elseif rad2 == rad3 and (vform == "III" or vform == "VI") then
weakness = "geminate"
variant = "long"
else
weakness = "sound"
end
end
if expected_length then
check_len(expected_length, expected_length)
end
end
rad1 = convert(rad1, 1)
rad2 = convert(rad2, 2)
rad3 = convert(rad3, 3)
rad4 = convert(rad4, 4)
if not weakness then
error("Internal error: Returned weakness from infer_radicals() is nil")
end
return {
weakness = weakness,
rad1 = rad1,
rad2 = rad2,
rad3 = rad3,
rad4 = rad4,
past_vowel = past_vowel,
nonpast_vowel = nonpast_vowel,
form_viii_assim = form_viii_assim,
variant = variant,
}
end
-- bot interface to infer_radicals()
function export.infer_radicals_json(frame)
local iparams = {
headword = {},
vform = {},
passive = {},
past_vowel = {},
nonpast_vowel = {},
is_reduced = {type = "boolean"},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
return require("Module:JSON").toJSON(export.infer_radicals(iargs))
end
-- Infer vocalization from participle headword (active or passive), verb form (I, II, etc.) and whether the headword is
-- active or passive. Throw an error if headword is malformed. Returned radicals may contain Latin letters "t", "w" or "y"
-- indicating ambiguous radicals guessed to be tāʾ, wāw or yāʾ respectively.
function export.infer_participle_vocalization(headword, vform, weakness, is_active)
local chars = {}
local orig_headword = headword
-- Sub out alif-madda for easier processing.
headword = rsub(headword, AMAD, HAMZA .. ALIF)
local len = ulen(headword)
-- Extract the headword letters into an array.
for i = 1, len do
table.insert(chars, usub(headword, i, i))
end
local function form_intro_error_msg()
return ("For verb form %s %s%s participle %s, "):format(vform, orig_headword ~= headword and "normalized " or
"", is_active and "active" or "passive", headword)
end
local function err(msg)
error(form_intro_error_msg() .. msg, 1)
end
-- Check that length of headword is within [min, max].
local function check_len(min, max)
if min and len < min then
err(("expected at least %s letters but saw %s"):format(min, len))
elseif max and len > max then
err(("expected at most %s letters but saw %s"):format(max, len))
end
end
-- Get the character at `ind`, making sure it exists.
local function c(ind)
check_len(ind)
return chars[ind]
end
-- Check that the letter at the given index is the given string, or is one of the members of the given array
local function check(index, must)
local letter = chars[index]
local function make_possible_values()
if type(must) == "string" then
return must
else
return list_to_text(must, nil, " or ")
end
end
if not letter then
err(("expected a letter (specifically %s) at position %s, but participle is too short"):format(
make_possible_values(), index))
end
local matches
if type(must) == "string" then
matches = letter == must
else
matches = m_table.contains(must, letter)
end
if not matches then
err(("letter %s at index %s must be %s"):format(letter, index, make_possible_values()))
end
end
local function check_weakness(values, allow_missing, invert_condition)
local function make_possible_weaknesses()
for i, val in ipairs(values) do
values[i] = "'" .. val .. "'"
end
return list_to_text(values, nil, " or ")
end
if allow_missing and invert_condition then
error("Internal error: Can't specify both allow_missing and invert_condition")
end
if not weakness then
if allow_missing or invert_condition then
return
else
err(("weakness is unspecified but must be %s"):format(make_possible_weaknesses()))
end
else
local matches = m_table.contains(values, weakness)
if invert_condition and matches then
err(("weakness '%s' must not be %s"):format(weakness, make_possible_weaknesses()))
elseif not invert_condition and not matches then
err(("weakness '%s' must be %s"):format(weakness, make_possible_weaknesses()))
end
end
end
local vocalized
local function handle_possibly_final_weak(sound_prefix, expected_length)
check_len(expected_length, expected_length)
if c(expected_length) == AMAQ then
-- passive final-weak
if is_active then
err("participle in -ِى only allowed for passive participles")
end
check_weakness({"final-weak", "assimilated+final-weak"}, "allow missing")
vocalized = sound_prefix .. AN .. AMAQ
else
-- all others behave as if sound
check_weakness({"final-weak", "assimilated+final-weak"}, nil, "invert condition")
vocalized = sound_prefix .. (is_active and I or A) .. c(expected_length)
end
end
if not (vform == "I" and is_active) then
-- all participles except verb form I active begin in م-.
check(1, M)
end
if vform == "I" then
if is_active then
check(2, ALIF)
local sound_prefix = c(1) .. AA .. c(3)
if len == 3 then
if c(3) == HAMZA then
-- Either hollow with hamzated third radical, e.g. [[شاء]] active participle 'شَاءٍ', or final-weak
-- with hamzated second radical, e.g. [[رأى]] active participle 'رَاءٍ'. Theoretically (?), also
-- geminate with hamzated second/third radical, but I don't know if any such verbs exist.
if weakness == "geminate" then
vocalized = sound_prefix .. SH
else
check_weakness({"hollow", "final-weak"}, "allow missing")
vocalized = sound_prefix .. IN
end
else
check_weakness({"final-weak", "geminate"})
if weakness == "geminate" then
vocalized = sound_prefix .. SH
else
vocalized = sound_prefix .. IN
end
end
else
check_len(4, 4)
-- we will convert back to alif maqṣūra below as needed
vocalized = sound_prefix .. I .. c(4)
end
else
-- assimilated verbs: regular, e.g. مَوْزُون "weighed"
-- geminate verbs: regular, e.g. مَبْلُول "moistened"
-- third-hamzated verbs: مَبْرُوء
-- hollow verbs: مَقُود "led, driven"; مَزِيد "added, increased"
-- hollow first-hamzated verbs: مَئِيض "returned, reverted"; مَأْيُوس "despaired" (NOTE: formation is sound);
-- مَأُود or مَؤُود "bent; depleted"
-- hollow third-hamzated verbs: مَشِيء "willed, intended", مَضُوء "glittered?"
-- final-weak: مَلْقِيّ "found, encountered"; مَصْغُوّ "inclined"
-- hollow + final-weak: مَشْوِيّ "fried, grilled", مَهْوِيّ "loved"
-- first-hamzated + hollow + final-weak: مَأْوِيّ "received hospitably"
local sound_prefix = MA .. c(2) .. SK .. c(3)
if len == 5 then
-- sound, assimilated or geminate
check(4, W)
vocalized = sound_prefix .. UU .. c(5)
else
check_len(4, 4)
if c(4) == W then
-- final-weak third-wāw
vocalized = sound_prefix .. U .. W .. SH
elseif c(4) == Y then
-- final-weak third-yāʾ
vocalized = sound_prefix .. I .. Y .. SH
else
-- hollow
check(3, {W, Y})
if c(3) == W then
vocalized = MA .. c(2) .. UU .. c(4)
else
vocalized = MA .. c(2) .. II .. c(4)
end
end
end
end
elseif vform == "II" or vform == "V" or vform == "XII" or vform == "XIII" or vform == "Iq" or vform == "IIq" or
vform == "IIIq" then
local sound_prefix, expected_length
if vform == "II" then
sound_prefix = MU .. c(2) .. A .. c(3) .. SH
expected_length = 4
elseif vform == "V" then
check(2, T)
sound_prefix = MU .. T .. A .. c(3) .. A .. c(4) .. SH
expected_length = 5
elseif vform == "XII" then
-- e.g. [[احدودب]] "to be or become convex or humpbacked", مُحْدَوْدِب (active);
-- [[اثنونى]] "to be bent; to be doubled up", مُثْنَوْنٍ (active)
check(4, W)
if c(3) ~= c(5) then
err(("third letter %s should be the same as the fifth letter %s"):format(c(3), c(5)))
end
sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. W .. SK .. c(5)
expected_length = 6
elseif vform == "XIII" then
-- e.g. [[اخروط]] "to get entangled; to extend", مُخْرَوِّط (active), مُخْرَوَّط (passive)
check(4, W)
sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. W .. SH
expected_length = 5
elseif vform == "Iq" then
sound_prefix = MU .. c(2) .. A .. c(3) .. SK .. c(4)
expected_length = 5
elseif vform == "IIq" then
check(2, T)
sound_prefix = MU .. T .. A .. c(3) .. A .. c(4) .. SK .. c(5)
expected_length = 6
elseif vform == "IIIq" then
-- e.g. [[اخرنطم]] "to be proud and angry"
check(4, T)
sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. N .. SK .. c(5)
expected_length = 6
else
error("Internal error: Unhandled verb form " .. vform)
end
if len == expected_length - 1 then
-- active final-weak
if not is_active then
err(("length-%s participle only allowed for active participles"):format(len))
end
check_weakness({"final-weak", "assimilated+final-weak"}, "allow missing")
vocalized = sound_prefix .. IN
else
handle_possibly_final_weak(sound_prefix, expected_length)
end
elseif vform == "III" or vform == "VI" then
local sound_prefix, expected_length
if vform == "VI" then
check(2, T)
check(4, ALIF)
sound_prefix = MU .. T .. A .. c(3) .. AA .. c(5)
expected_length = 6
else
sound_prefix = MU .. c(2) .. AA .. c(4)
expected_length = 5
end
if len == expected_length - 1 then
-- active final-weak or active or passive geminate
if is_active then
check_weakness({"geminate", "final-weak", "assimilated+final-weak"})
if weakness == "geminate" then
vocalized = sound_prefix .. SH
else
vocalized = sound_prefix .. IN
end
else
check_weakness({"geminate"}, "allow missing")
vocalized = sound_prefix .. SH
end
else
handle_possibly_final_weak(sound_prefix, expected_length)
end
elseif vform == "IV" or vform == "X" then
-- form IV:
-- sound: مُرْسِخ (active, "entrenching"), مُرْسَخ (passive, "entrenched")
-- first-hamzated (like sound): مُؤْيِس (active, "causing to despair"), مُؤْيَس (passive, "caused to despair")
-- final-weak: مُكْرٍ (active, "renting out"), مُكْرًى (passive, "rented out")
-- assimilated: مُورِد (active, "transferring"), مُورَد (passive, "transferred"); same when first-Y, e.g.
-- أَيْقَنَ "to be certain of": مُوقِن (active), مُوقَن (passive)
-- assimilated + final-weak: مُورٍ (active, "setting fire, kindling"), مُورًى (passive, "set fire, kindled")
-- geminate: مُمِدّ (active, "granting, helping"), مُمَدّ (passive, "granted, helped")
-- hollow: مُزِيل (active, "eliminating"), مُزَال (passive, "eliminated")
-- hollow + final-weak: مُعْيٍ (active, "tiring"), مُعْيًى (passive, "tired")
local sound_prefix, expected_length
if vform == "X" then
check(2, S)
check(3, T)
sound_prefix = MU .. S .. SK .. T .. A .. c(4)
expected_length = 6
else
sound_prefix = MU .. c(2)
expected_length = 4
end
if len == expected_length and c(len - 1) == Y and c(len) ~= AMAQ then
-- active hollow
if not is_active then
err("this shape only allowed for active participles")
end
check_weakness({"hollow"}, "allow missing")
vocalized = sound_prefix .. II .. c(len)
elseif len == expected_length and c(len - 1) == ALIF then
-- passive hollow
if is_active then
err("this shape only allowed for passive participles")
end
check_weakness({"hollow"}, "allow missing")
vocalized = sound_prefix .. AA .. c(len)
elseif len == expected_length - 1 then
-- active final-weak or active or passive geminate
if is_active then
check_weakness({"geminate", "final-weak", "assimilated+final-weak"})
if weakness == "geminate" then
vocalized = sound_prefix .. I .. c(len) .. SH
elseif vform == "IV" and c(2) == W then
-- assimilated final-weak
vocalized = sound_prefix .. c(len) .. IN
else
vocalized = sound_prefix .. SK .. c(len) .. IN
end
else
check_weakness({"geminate"}, "allow missing")
vocalized = sound_prefix .. A .. c(len) .. SH
end
else
if vform == "IV" and c(2) == W then
-- assimilated, possibly final-weak
sound_prefix = sound_prefix .. c(expected_length - 1)
else
sound_prefix = sound_prefix .. SK .. c(expected_length - 1)
end
handle_possibly_final_weak(sound_prefix, expected_length)
end
elseif vform == "VII" or vform == "VIII" then
-- form VII (passive participles are fairly rare but do exist):
-- sound: مُنْكَتِب (active "subscribing"), مُنْكَتَب (passive "subscribed")
-- geminate: مُنْضَمّ (both active "joining, containing" and passive "joined, contained")
-- final-weak: مُنْطَلٍ (active "fooling (someone)"), مُنْطَلًى (passive "fooled")
-- final-weak with medial wāw: مُنْطَوٍ (active "involving"), مُنْطَوًى (passive "involved")
-- hollow: مُنْقَاد (both active "complying with" and passive "complied with")
--
-- for form VIII, the same variants exist but things are complicated by assimilations involving the template T.
-- sound third-hamzated no assimilation: مُبْتَدِئ (active "beginning"), مُبْتَدَأ (passive "begun")
-- geminate no assimilation: مُبْتَزّ (both active "robbing" and passive "robbed")
-- final-weak no assimilation: مُبْتَنٍ (active "building"), مُبْتَنًى (passive "built")
-- final-weak with medial wāw no assimilation: مُحْتَوٍ (active "containing"), مُحْتَوًى (passive "contained")
-- hollow no assimilation: مُخْتَار (both active "choosing" and passive "chosen")
--
-- sound with total assimilation: مُتَّبِع (active "following"), مُتَّبَع (passive "followed")
-- sound with total assimilation, assimilating wāw: مُتَّعِد (active "threatening"), مُتَّعَد (passive "threatened")
-- sound with total assimilation, irregularly assimilating hamza: مُتَّخِذ (active "taking"), مُتَّخَذ (passive "taken")
-- sound with total assimilation (to ḏāl, producing dāl): مُدَّخِر (active "reserving"), مُدَّخَر (passive "reserved")
-- sound with total assimilation (to ḏāl): مُذَّكِر (active "remembering"), مُذَّكَر (passive "remembered")
-- sound with total assimilation (to ṭāʔ): مُطَّرِح (active "discarding"), مُطَّرَح (passive "discarded")
-- sound with total assimilation (to ẓāʔ): مُظَّلِم (active "tolerating"), مُظَّلَم (passive "tolerated")
-- final-weak with total assimilation, assimilating wāw: مُتَّقٍ (active "guarding against"), مُتَّقًى (passive "guarded against")
-- final-weak with total assimilation (to ṯāʔ): مُثَّنٍ (active "undulating"), مُثَّنًى (passive "undulated")
-- final-weak with total assimilation (to dāl): مُدَّعٍ (active "claiming"), مُدَّعًى (passive "claimed")
-- sound with partial assimilation (to zayn): مُزْدَهِر (active "thriving"), مُزْدَهَر (passive "thrived")
-- sound with medial wāw with partial assimilation (to zayn): مُزْدَوِج (active "appearing twice")
-- sound with partial assimilation (to ṣād): مُصْطَبِح (active "illuminating"), مُصْطَبَح (passive, "illuminated")
-- sound with partial assimilation (to ḍād): مُضْطَرِب (active "to be disturbed"; no passive)
-- geminate with partial assimilation (to ṣād): مُصْطَبّ (both active "effusing" and passive "effused")
-- geminate with partial assimilation (to ḍād): مُضْطَرّ (both active "forcing" and passive "forced")
-- final-weak with partial assimilation (to ṣād): مُصْطَلٍ (active "warming"), مُصْطَلًى (passive "warmed")
-- hollow with partial assimilation (to zayn): مُزْدَاد (both active "increasing" and passive "increased")
-- hollow with partial assimilation (to ṣad): مُصْطَاد (both active "hunting" and passive "hunted")
local sound_prefix, sufind
if vform == "VII" then
check(2, N)
sound_prefix = MU .. N .. SK .. c(3)
sufind = 4
else
local c2 = c(2)
if c2 == T or c2 == "د" or c2 == "ث" or c2 == "ذ" or c2 == "ط" or c2 == "ظ" then
-- full assimilation
sound_prefix = MU .. c2 .. SH
sufind = 3
else
-- partial or no assimilation
if c2 == "ز" then
check(3, "د")
elseif c2 == "ص" or c2 == "ض" then
check(3, "ط")
else
check(3, T)
end
sound_prefix = MU .. c2 .. SK .. c(3)
sufind = 4
end
end
if c(sufind) == ALIF then
-- hollow, active or passive
check_len(sufind + 1, sufind + 1)
check_weakness({"hollow"}, "allow missing")
vocalized = sound_prefix .. AA .. c(sufind + 1)
elseif len == sufind then
-- active final-weak or active or passive geminate
if is_active then
check_weakness({"geminate", "final-weak", "assimilated+final-weak"})
if weakness == "geminate" then
vocalized = sound_prefix .. A .. c(len) .. SH
else
vocalized = sound_prefix .. A .. c(len) .. IN
end
else
check_weakness({"geminate"}, "allow missing")
vocalized = sound_prefix .. A .. c(len) .. SH
end
else
sound_prefix = sound_prefix .. A .. c(sufind)
handle_possibly_final_weak(sound_prefix, sufind + 1)
end
elseif vform == "IX" then
check_len(4, 4)
vocalized = MU .. c(2) .. SK .. c(3) .. A .. c(4) .. SH
elseif vform == "IVq" then
-- e.g. [[اذلعب]] "to scamper away", مُذْلَعِبّ (active), مُذْلَعَبّ (passive);
-- [[اطمأن]] "to remain quietly; to be certain", مُطْمَئِنّ (active), مُطْمَأَنّ (passive)
check_len(5, 5)
local sound_prefix = MU .. c(2) .. SK .. c(3) .. A .. c(4)
if is_active then
vocalized = sound_prefix .. I .. c(5) .. SH
else
vocalized = sound_prefix .. A .. c(5) .. SH
end
elseif vform == "XI" then
check_len(5, 5)
check(4, ALIF)
vocalized = MU .. c(2) .. SK .. c(3) .. AA .. c(5) .. SH
-- e.g. [[احمار]] "to turn red, to blush", مُحْمَارّ (active)
elseif vform == "XIV" or vform == "XV" then
-- FIXME: Implement. No examples in Wiktionary currently; need to look up in a grammar.
error("Support for verb form " .. vform .. " not implemented yet")
else
error("Don't recognize verb form " .. vform)
end
vocalized = rsub(vocalized, HAMZA .. AA, AMAD)
local reconstructed_headword = lang:stripDiacritics(vocalized)
if reconstructed_headword ~= orig_headword then
error(("Internal error: Vocalized participle %s doesn't match original participle %s"):format(
vocalized, orig_headword))
end
return vocalized
end
function export.infer_participle_vocalization_json(frame)
local iparams = {
[1] = {required = true},
[2] = {required = true},
["weakness"] = {},
["passive"] = {type = "boolean"}
}
local iargs = require("Module:parameters").process(frame.args, iparams)
return export.infer_participle_vocalization(iargs[1], iargs[2], iargs.weakness, not iargs.passive)
end
return export
7seftnw56ctdx6pnu1ar0v50lye7hdd
Module:Ar-headword
828
1379453
9909042
9651094
2026-09-29T07:24:19Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909042
Scribunto
text/plain
-- Author: primarily Benwing2; some work by Fenakhay, Erutuon; early version by Rua
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local ar_translit = require("Module:ar-translit")
local ar_verb_module = "Module:ar-verb"
local ar_utilities_module = "Module:ar-utilities"
local ar = require(ar_utilities_module)
local en_utilities_module = "Module:en-utilities"
local headword_module = "Module:headword"
local headword_utilities_module = "Module:headword utilities"
local links_module = "Module:links"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local require_when_needed = require("Module:utilities/require when needed")
local remove_links = require_when_needed(links_module, "remove_links")
local m_table = require("Module:table")
local m_str_utils = require("Module:string utilities")
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local boolean_param = {type = "boolean"}
local list_to_set = m_table.listToSet
local rfind = m_str_utils.find
local rmatch = m_str_utils.match
local rsubn = m_str_utils.gsub
local u = m_str_utils.char
local rsplit = m_str_utils.split
local insert = table.insert
local concat = table.concat
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local langcode = "ar"
local lang = require("Module:languages").getByCode(langcode)
local langname = lang:getCanonicalName()
local TEMPCOMMA = u(0xFFF0)
local TEMPARCOMMA = u(0xFFF1)
local misc_pos_with_gender = list_to_set {
"後綴",
"形容詞變格形",
"名詞變格形",
"專有名詞變格形",
"代詞變格形",
"限定詞變格形",
}
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local dump = mw.dumpObject
-- version of mw.ustring.gsub() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
local function ine(val)
if val == "" then return nil else return val end
end
-- Replace comma with a temporary char in comma + whitespace.
local function escape_comma_whitespace(run)
local escaped = false
if run:find("\\,") then
run = run:gsub("\\,", "\\" .. TEMPCOMMA)
escaped = true
end
if run:find("\\،") then
run = run:gsub("\\،", "\\" .. TEMPARCOMMA)
escaped = true
end
if run:find(",%s") then
run = run:gsub(",(%s)", TEMPCOMMA .. "%1")
escaped = true
end
if run:find("،%s") then
run = run:gsub("،(%s)", TEMPARCOMMA .. "%1")
escaped = true
end
return run, escaped
end
-- Undo replacement of comma with a temporary char in comma + whitespace.
local function unescape_comma_whitespace(run)
return (run:gsub(TEMPCOMMA, ","):gsub(TEMPARCOMMA, "،"))
end
-- Split an argument on comma or Arabic comma, but not either type of comma followed by whitespace.
local function split_on_comma(val)
if rfind(val, "[,،]%s") or val:find("\\") then
return export.split_escaping(val, "[,،]", false, escape_comma_whitespace, unescape_comma_whitespace)
else
return rsplit(val, "[,،]")
end
end
local function replace_tr_ending(tr, from, to)
if not tr then
return nil
end
local pref = tr:match("^(.*)" .. from .. "$")
if not pref then
error(("Translit '%s' does not end in -%s, as expected"):format(tr, from))
end
return pref .. to
end
-----------------------------------------------------------------------------------------
-- Tracking functions --
-----------------------------------------------------------------------------------------
local trackfn = require("Module:debug/track")
local function track(page)
trackfn(langcode .. "-headword/" .. page)
return true
end
--[==[
Examples of what you can find by looking at what links to the given
pages:
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized]]
all unvocalized pages
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized/pl]]
all unvocalized pages where the plural is unvocalized,
whether specified using pl=, pl2=, etc.
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized/head]]
all unvocalized pages where the head is unvocalized
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized/head/nouns]]
all nouns excluding proper nouns, collective nouns,
singulative nouns where the head is unvocalized
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized/head/proper]]
nouns all proper nouns where the head is unvocalized
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized/head/not]]
proper nouns all words that are not proper nouns
where the head is unvocalized
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized/adjectives]]
all adjectives where any parameter is unvocalized;
currently only works for heads,
so equivalent to .../unvocalized/head/adjectives
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized-empty-head]]
all pages with an empty head
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized-manual-translit]]
all unvocalized pages with manual translit
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized-manual-translit/head/nouns]]
all nouns where the head is unvocalized but has manual translit
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/unvocalized-no-translit]]
all unvocalized pages without manual translit
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/i3rab]]
all pages with any parameter containing i3rab
of either -un, -u, -a or -i
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/i3rab-un]]
all pages with any parameter containing an -un i3rab ending
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/i3rab-un/pl]]
all pages where a form specified using pl=, pl2=, etc.
contains an -un i3rab ending
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/i3rab-u/head]]
all pages with a head containing an -u i3rab ending
[[Special:WhatLinksHere/Wiktionary:Tracking/ar-headword/i3rab/head/proper]]
nouns (all proper nouns with a head containing i3rab
of either -un, -u, -a or -i)
In general, the format is one of the following:
Wiktionary:Tracking/ar-headword/FIRSTLEVEL
Wiktionary:Tracking/ar-headword/FIRSTLEVEL/ARGNAME
Wiktionary:Tracking/ar-headword/FIRSTLEVEL/POS
Wiktionary:Tracking/ar-headword/FIRSTLEVEL/ARGNAME/POS
FIRSTLEVEL can be one of "unvocalized", "unvocalized-empty-head" or its
opposite "unvocalized-specified", "unvocalized-manual-translit" or its
opposite "unvocalized-no-translit", "i3rab", "i3rab-un", "i3rab-u",
"i3rab-a", or "i3rab-i".
ARGNAME is either "head" or an argument such as "pl", "f", "cons", etc.
This automatically includes arguments specified as head2=, pl3=, etc.
POS is a part of speech, lowercase and singular, e.g. "noun",
"adjective", "proper noun", "collective noun", etc. or
"not proper noun", which includes all parts of speech but proper nouns.
]==]
local function track_form(argname, form, translit, pos)
form = ar.reorder_shadda(remove_links(form))
function dotrack(page)
track(page)
track(page .. "/" .. argname)
if pos then
track(page .. "/" .. pos)
track(page .. "/" .. argname .. "/" .. pos)
if pos ~= "專有名詞" then
track(page .. "/not proper noun")
track(page .. "/" .. argname .. "/not proper noun")
end
end
end
function track_i3rab(arabic, tr)
if rfind(form, arabic .. "$") then
dotrack("i3rab")
dotrack("i3rab-" .. tr)
end
end
track_i3rab(ar.UN, "un")
track_i3rab(ar.U, "u")
track_i3rab(ar.A, "a")
track_i3rab(ar.I, "i")
if form == "" or not (lang:transliterate(form)) then
dotrack("unvocalized")
if form == "" then
dotrack("unvocalized-empty-head")
else
dotrack("unvocalized-specified")
end
if translit then
dotrack("unvocalized-manual-translit")
else
dotrack("unvocalized-no-translit")
end
end
end
-----------------------------------------------------------------------------------------
-- Inflection-parsing functions --
-----------------------------------------------------------------------------------------
-- Construct the default construct state or informal form of a term in lemma format. Usually this is the same as the
-- lemma but is different for final-weak nouns and adjectives ending in -n in their lemma. NOTE: Input must be
-- shadda-reordered for this to work properly.
local function default_construct_state_or_informal(term, tr)
local pref = term:match("^(.*)" .. ar.HAMZA .. ar.IN .."$")
-- Hamza on the line with -in changes to hamza-on-yā with -ī.
if pref then
return pref .. ar.HAMZA_ON_YA .. ar.II, replace_tr_ending(tr, "in", "ī")
end
-- Otherwise just change -in to -ī.
pref = term:match("^(.*)" .. ar.IN .. "$")
if pref then
return pref .. ar.II, replace_tr_ending(tr, "in", "ī")
end
-- Change -an with alif maqṣūra to -ā with alif maqṣūra.
pref = term:match("^(.*)" .. ar.AN .. ar.AMAQ .. "$")
if pref then
return pref .. ar.AAMAQ, replace_tr_ending(tr, "an", "ā")
end
-- Change -an with tall alif (e.g. عَصًا) to -ā with tall alif.
pref = term:match("^(.*)" .. ar.AN .. ar.ALIF .. "$")
if pref then
return pref .. ar.AA, replace_tr_ending(tr, "an", "ā")
end
return term, tr
end
local function generate_construct_state_or_informal_default(data, args)
local heads = data.heads
local consobjs = {}
local different_cons = false
for _, headobj in ipairs(data.heads) do
local consterm, constr = default_construct_state_or_informal(headobj.term, headobj.tr)
different_cons = different_cons or consterm ~= headobj.term or constr ~= headobj.tr
local consobj = m_table.shallowCopy(headobj)
consobj.term = consterm
consobj.tr = constr
insert(consobjs, consobj)
end
if different_cons then
return consobjs
else
return {}
end
end
local noun_field_cons = {
field = "cons", label = "<<結構態>>", generate_default = generate_construct_state_or_informal_default,
default_when_not_explicit = function(args, data) return true end,
}
local noun_field_inf = {field = "inf", label = "非正式"}
local noun_field_obl = {field = "obl", label = "<<斜格>>"}
local noun_field_def = {field = "def", label = "<<限定>>態"}
local noun_inflections = {
noun_field_cons,
noun_field_inf,
noun_field_obl,
noun_field_def,
}
local adj_field_inf = {
field = "inf", label = "非正式", generate_default = generate_construct_state_or_informal_default,
default_when_not_explicit = function(args, data) return true end,
}
local adj_field_obl = noun_field_obl
local adj_field_def = noun_field_def
local adjective_inflections = {
adj_field_inf,
adj_field_obl,
adj_field_def,
}
local function has_construct_state(data)
return data.pos_category ~= "adjectives"
end
local function parse_nominal_inflection(paramname, val, parse_err)
return m_headword_utilities.parse_term_with_modifiers {
val = val,
paramname = paramname,
splitchar = ",",
include_mods = {"tr", "g"},
}
end
local function make_nominal_inflection_param_mod_spec(paramname)
return {convert = function(val, parse_err)
return parse_nominal_inflection(paramname, val, parse_err)
end}
end
-- Parse an inflection. The raw arguments come from `args[field]`, which is parsed for inline modifiers. Multiple
-- comma-separated values are allowed.
local function parse_inflection(data, args, field, is_head)
local argfield = field
local argpref = field
if type(argfield) == "table" then
argpref = argfield[2]
argfield = argfield[1]
end
local include_mods
if is_head then
include_mods = {"tr"}
else
include_mods = {"tr", "g"}
for _, spec in ipairs(has_construct_state(data) and noun_inflections or adjective_inflections) do
insert(include_mods, {spec.field, make_nominal_inflection_param_mod_spec(argpref .. "." .. spec.field)})
end
end
if is_head then
local retval
if args[argfield] then
retval = m_headword_utilities.parse_term_with_modifiers {
val = args[argfield],
paramname = field,
splitchar = ",",
is_head = is_head,
include_mods = include_mods,
}
end
return retval or {}
else
return m_headword_utilities.parse_term_list_with_modifiers {
forms = args[argfield],
paramname = field,
splitchar = ",",
is_head = is_head,
include_mods = include_mods,
}
end
end
local function insert_inflection(data, terms, label, accel, defgender, track_field, no_label, usually_no_label)
local track_pos = data.pos_category
for _, termobj in ipairs(terms) do
-- If the user supplied a construct state or informal form for the term with a value of "+", substitute the
-- default value for the term. If the user supplied a value of "--", they want no value displayed. Otherwise,
-- if the user didn't supply any value, we check to see if the default construct state or informal form is
-- different from the lemma and display it if so; this applies particularly to terms in '-in' and '-an', where
-- the default construct state or informal form is almost always correct.
local field = has_construct_state(data) and "cons" or "inf"
if not termobj[field] then
local defcons, defconstr = default_construct_state_or_informal(termobj.term, termobj.tr)
if termobj.term ~= defcons or termobj.tr ~= defconstr then
-- We don't want to copy decorations from the term object because we're a subinflection of the term
-- object.
termobj[field] = {{term = defcons, tr = defconstr}}
end
elseif termobj[field][1].term == "--" then
if termobj[field][2] then
error("Can't specify more than one value for <" .. field .. ":...> if first value is '--', meaning \"don't insert anything\"")
end
termobj[field] = nil
else
for i, consobj in ipairs(termobj[field]) do
if consobj.term == "+" then
if consobj.tr then
error("Can't specify translit for default value '+'")
end
consobj.term, consobj.tr = default_construct_state_or_informal(termobj.term, termobj.tr)
elseif consobj.term == "~" then
if consobj.tr then
error("Can't specify translit for term-requesting value '~'")
end
consobj.term, consobj.tr = termobj.term, termobj.tr
end
end
end
if defgender and not termobj.genders then
termobj.genders = {{spec = defgender}}
end
local function insert_nested_inflection(field, label)
if termobj[field] then
m_headword_utilities.insert_inflection {
headdata = data,
inflobj = termobj,
terms = termobj[field],
label = label
}
end
end
for _, spec in ipairs(has_construct_state(data) and noun_inflections or adjective_inflections) do
insert_nested_inflection(spec.field, spec.label)
end
track_form(track_field, termobj.term, termobj.tr, track_pos)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
no_label = no_label,
usually_no_label = usually_no_label,
}
end
-----------------------------------------------------------------------------------------
-- Main entry point --
-----------------------------------------------------------------------------------------
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:lower() == "template:" .. langcode .. "-head" and "感嘆詞" or -- L10N
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and (misc_pos_with_gender[poscat] and "head_with_gender" or "head") or poscat
local params = {
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["id"] = true,
["json"] = boolean_param,
["pagename"] = {}, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local head_is_head = pos_functions[indexing_poscat] and pos_functions[indexing_poscat].head_is_not_1
local headfield = head_is_head and "head" or pos_in_1 and 2 or 1
params[headfield] = head_is_head and true or {default = "+"}
params.head2 = {replaced_by = false, instead = "use multiple comma-separated values in |" .. headfield .. "="}
local tr_replaced_by = {replaced_by = false, instead = "use <tr:...> inline modifier on |" .. headfield .. "="}
params.tr = tr_replaced_by
params.tr2 = tr_replaced_by
if pos_functions[indexing_poscat] then
for key, val in pairs(pos_functions[indexing_poscat].params()) do
params[key] = val
end
end
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local data = {
lang = lang,
pos_category = poscat,
orig_pos_category = poscat,
categories = {},
heads = {},
genders = {},
inflections = {enable_auto_translit = true},
pagename = pagename,
id = args.id,
sort_key = args.sort,
force_cat_output = force_cat,
-- We expect a head always so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
}
data.heads = parse_inflection(data, args, headfield, "is_head")
for _, headobj in ipairs(data.heads) do
if headobj.term == "+" then
headobj.term = pagename
end
end
data.is_suffix = false
if args.suffix or (
not args.nosuffix and pagename:find("^%-") and poscat ~= "後綴" and poscat ~= "後綴變格形"
) then
data.is_suffix = true
data.pos_category = "後綴"
local singular_poscat = poscat
insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[indexing_poscat] then
pos_functions[indexing_poscat].func(data, args)
end
-- Do this after calling pos_functions[poscat].func() as it may modify data.heads (as verbs do).
local irreg_translit = false
for _, head in ipairs(data.heads) do
if ar_translit.irregular_translit(head.term, head.tr) then
irreg_translit = true
break
end
end
if irreg_translit then
insert(data.categories, "有不規則發音的" .. langname .. "詞")
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require(headword_module).full_headword(data)
end
-----------------------------------------------------------------------------------------
-- Gender handling --
-----------------------------------------------------------------------------------------
local valid_bare_genders = {false, "m", "f", "mf", "mfbysense", "mfequiv"}
local valid_bare_numbers = {false, "d", "p"}
local valid_bare_animacies = {false, "pr", "np"}
local valid_genders = {}
for _, gender in ipairs(valid_bare_genders) do
for _, number in ipairs(valid_bare_numbers) do
for _, animacy in ipairs(valid_bare_animacies) do
local parts = {}
local function ins_part(part)
if part then
insert(parts, part)
end
end
ins_part(gender)
ins_part(number)
ins_part(animacy)
local full_gender = concat(parts, "-")
valid_genders[full_gender == "" and "?" or full_gender] = true
end
end
end
local function is_masc_sg(g)
return g == "m" or g == "m-pr" or g == "m-np"
end
local function is_fem_sg(g)
return g == "f" or g == "f-pr" or g == "f-np"
end
local function is_masc_fem_sg(g)
g = g:gsub("%-pr", ""):gsub("%-np", "")
return g == "mf" or g == "mfequiv" or g == "mfbysense"
end
local function add_gender_params(params, default)
params[2] = {type = "genders", default = default or "?", template_default = "m"}
params["g2"] = {replaced_by = false, instead = "use comma-separated values in |g="}
end
-- Handle gender in params 2=, inserting into `data.genders`. Also, if a lemma, insert categories into `data.categories`
-- if the gender is unexpected for the form of the noun. (Note: If there are multiple genders,
-- [[Module:gender and number]] will automatically insert 'Arabic POS with multiple genders'.)
local function handle_gender(data, args, nonlemma, field)
if not args[field or 2] then
return
end
for _, gspec in ipairs(args[field or 2]) do
if not valid_genders[gspec.spec] then
error("Unrecognized gender: " .. gspec.spec)
end
end
data.genders = args[field or 2]
if nonlemma then
return
end
for _, gspec in ipairs(data.genders) do
local g = gspec.spec
if is_masc_sg(g) or is_fem_sg(g) or is_masc_fem_sg(g) then
local head = data.heads[1]
if head then
head = rsub(ar.reorder_shadda(remove_links(head.term)), ar.UNUOPT .. "$", "")
local ends_with_tam = rfind(head, "^[^ ]*" .. ar.TAM .. "$") or
rfind(head, "^[^ ]*" .. ar.TAM .. " ")
if (is_masc_sg(g) or is_masc_fem_sg(g)) and ends_with_tam then
insert(data.categories, "有陰性詞尾的" .. langname .. "陽性詞")
elseif (is_fem_sg(g) or is_masc_fem_sg(g)) and not ends_with_tam and
not rfind(head, "[" .. ar.ALIF .. ar.AMAQ .. "]$") and
not rfind(head, ar.ALIF .. ar.HAMZA .. "$") then
insert(data.categories, "缺少陰性詞尾的" .. langname .. "陰性詞")
end
end
end
end
end
-----------------------------------------------------------------------------------------
-- Inflection handlers --
-----------------------------------------------------------------------------------------
-- Add list parameters to `params` (a structure as passed to [[Module:parameters]]) for a parameter named `argpref`.
-- If `argpref` is "*", add the nominal inflection parameters for construct state, definite state, etc. Related
-- transliteration and gender parameters are no longer supported in favor of inline modifiers, and error messages are
-- output if these parameters are used.
local function add_infl_params(params, argpref)
params[argpref] = {list = true, disallow_holes = true}
params[argpref .. "tr"] = {replaced_by = false, instead = "use <tr:...> inline modifier on |" .. argpref .. "="}
params[argpref .. "g"] = {replaced_by = false, instead = "use <g:...> inline modifier on |" .. argpref .. "="}
end
--[=[
Fetch a list of inflections from the arguments in `args` based on argument `field` (e.g. "pl"). Label with `label`
(e.g. "plural"), which will appear in the headword. Insert into `data.inflections`, where `data` is the structure
passed to [[Module:headword]]. If `generate_default` is specified, it should be a function of two arguments
(`data`, `args`), which should generate the default value if no values are specified or if "+" is explicitly given.
If `generate_default` isn't specified and the user gave no values, no inflection will be inserted.
]=]
local function handle_infl(data, args, spec)
local newinfls = parse_inflection(data, args, spec.field, false)
if not newinfls[1] and spec.default_when_not_explicit and spec.default_when_not_explicit(data, args) then
newinfls = {{term = "+"}}
end
if spec.handle then
spec.handle(data, args, newinfls)
end
local default_specs = spec.allowed_defspecs
if not default_specs then
default_specs = spec.generate_default and {["+"] = true} or {}
end
local saw_defspec = false
for _, newinfl in ipairs(newinfls) do
if default_specs[newinfl.term] or newinfl.term == "~" then
saw_defspec = true
break
end
end
if saw_defspec then
local newnewinfls = {}
for _, newinfl in ipairs(newinfls) do
if default_specs[newinfl.term] then
if newinfl.tr then
error("Can't specify translit for default value '" .. newinfl.term .. "'")
end
local definfls = spec.generate_default(data, args, newinfl.term)
for _, definfl in ipairs(definfls) do
m_headword_utilities.combine_termobj_decorations(definfl, newinfl)
insert(newnewinfls, definfl)
end
elseif newinfl.term == "~" then
if newinfl.tr then
error("Can't specify translit for head-requesting value '~'")
end
for _, headobj in ipairs(data.heads) do
headobj = m_table.shallowCopy(headobj)
m_headword_utilities.combine_termobj_decorations(headobj, newinfl)
insert(newnewinfls, headobj)
end
else
insert(newnewinfls, newinfl)
end
end
newinfls = newnewinfls
end
if newinfls[1] then
if newinfls[1].term == "--" then
if newinfls[2] then
error("Can't specify more than one term if first term is '--', meaning \"don't insert anything\"")
end
else
insert_inflection(data, newinfls, spec.label, nil, spec.defgender, spec.field, spec.no_label,
spec.usually_no_label)
end
end
end
local function add_infl_list_params(params, infl_list)
for _, infl in ipairs(infl_list) do
add_infl_params(params, infl.field)
end
end
local function handle_infl_list_args(data, args, infl_list)
for _, infl in ipairs(infl_list) do
handle_infl(data, args, infl)
end
end
-----------------------------------------------------------------------------------------
-- Default ending generators --
-----------------------------------------------------------------------------------------
local function make_conditional_default(specs)
return function(data, args)
local heads = data.heads
if not heads[1] then
heads = {{term = data.pagename}}
end
local newobjs = {}
for _, headobj in ipairs(heads) do
local term = ar.reorder_shadda(headobj.term)
local tr = headobj.tr
local matched = false
for _, spec in ipairs(specs) do
local from, fromtr, to, totr = unpack(spec)
if from:find("^%^") then
pref = rmatch(term, from .. "$")
else
pref = rmatch(term, "^(.*)" .. from .. "$")
end
if pref then
term = pref .. to
tr = replace_tr_ending(tr, fromtr, totr)
matched = true
headobj = m_table.shallowCopy(headobj)
headobj.term = ar.undo_reorder_shadda(term)
headobj.tr = tr
insert(newobjs, headobj)
break
end
end
if not matched then
error(("Internal error: No matching spec: head=%s"):format(dump(headobj)))
end
end
return newobjs
end
end
local default_feminine = make_conditional_default {
{ar.AN .. ar.AMAQ, "an", ar.AAH, "āh"},
{ar.AN .. ar.ALIF, "an", ar.AAH, "āh"}, -- e.g. مُحْيًا
{ar.HAMZA .. ar.IN, "in", ar.HAMZA_ON_YA .. ar.IYAH, "iya"},
{ar.IN, "in", ar.IYAH, "iya"},
{"", "", ar.AH, "a"},
}
local default_masculine = make_conditional_default {
-- tall alif substitutes for alif maqṣūra after a yāʔ
{ar.Y .. ar.AAH, "āh", ar.AN .. ar.ALIF, "an"},
{ar.AAH, "āh", ar.AN .. ar.AMAQ, "an"},
-- handle the common case of final-weak feminine active participle with preceding hamza;
-- the hamza-on-yāʔ always converts back to hamza on the line when preceded by ā (alif) but
-- may not otherwise, so we just leave it alone in that case
{ar.ALIF .. ar.HAMZA_ON_YA .. ar.IYAH, "iya", ar.HAMZA .. ar.IN, "in"},
{ar.IYAH, "iya", ar.IN, "in"},
{ar.AH, "a", "", ""},
{"", "", "", ""},
}
local default_masculine_plural = make_conditional_default {
{ar.AN .. ar.AMAQ, "an", ar.AWN, "awn"},
{ar.AN .. ar.ALIF, "an", ar.AWN, "awn"}, -- e.g. مُحْيًا
{ar.HAMZA .. ar.IN, "in", ar.HAMZA_ON_WAW .. ar.UUN, "ūn"},
{ar.IN, "in", ar.UUN, "ūn"},
{"", "", ar.UUN, "ūn"},
}
local default_feminine_plural = make_conditional_default {
-- صَلَاة pl. صَلَوَات and أَدَاة pl. أَدَوَات and similar; but نَوَاة and وَفَاة with a و in them become نَوَيَات and وَفَيَات;
-- and longer terms like مُبَارَاة and كُمَّثْرَاة invariably form their plural in -يَات.
{"^([^و]" .. ar.A .. "[^و])" .. ar.AAH, "āh", ar.A .. ar.W .. ar.AAT, "awāt"},
{ar.AAH, "āh", ar.AYAAT, "ayāt"},
{ar.AN .. ar.AMAQ, "an", ar.AYAAT, "ayāt"},
{ar.AN .. ar.ALIF, "an", ar.AYAAT, "ayāt"}, -- e.g. مُحْيًا
{ar.HAMZA .. ar.IN, "in", ar.HAMZA_ON_YA .. ar.IYAAT, "iyāt"},
{ar.IN, "in", ar.IYAAT, "iyāt"},
{ar.AH, "a", ar.AAT, "āt"},
{"", "", ar.AAT, "āt"},
}
local default_masculine_dual = make_conditional_default {
{ar.AN .. ar.AMAQ, "an", ar.AYAAN, "ayān"},
{ar.AN .. ar.ALIF, "an", ar.AYAAN, "ayān"}, -- e.g. مُحْيًا
{ar.HAMZA .. ar.IN, "in", ar.HAMZA_ON_YA .. ar.IYAAN, "iyān"},
{ar.IN, "in", ar.IYAAN, "iyān"},
{"", "", ar.AAN, "ān"},
}
local default_feminine_dual = make_conditional_default {
{ar.AN .. ar.AMAQ, "an", ar.AATAAN, "ātān"},
{ar.AN .. ar.ALIF, "an", ar.AATAAN, "ātān"}, -- e.g. مُحْيًا
{ar.HAMZA .. ar.IN, "in", ar.HAMZA_ON_YA .. ar.IY .. ar.ATAAN, "iyatān"},
{ar.IN, "in", ar.IY .. ar.ATAAN, "iyatān"},
{"", "", ar.ATAAN, "atān"},
}
-- Return whether `term` is a nisba noun or adjective, ending in -iyy or -iyyah. `nisba_val` is the value of
-- args.nisba; if non-nil, it overrides any auto-determination based on the shape of the term.
local function term_is_nisba(term, nisba_val)
if nisba_val ~= nil then
return nisba_val
end
term = ar.reorder_shadda(term) -- necessary to avoid issues with e.g. أُورُوبِّيّ.
local pref = rmatch(term, "^(.*)" .. ar.IYY .. ar.UN .. "?$")
if not pref then
pref = rmatch(term, "^(.*)" .. ar.IYYAH .. ar.UN .. "?$")
end
-- Avoid false positives for words like قَوِيّ "strong" and صَبِيّ "boy". There may be other false positives
-- but this should catch most of them and will avoid very many false negatives.
return pref and not rfind(pref, "^[^ا]" .. ar.A .. ".$")
end
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
local function is_defaulting_adjective(data, args)
return data.orig_pos_category == "預設形容詞"
end
local adj_field_elative = {field = "el", label = "<<出格>>"} -- elative
local adj_inflections = {
adj_field_inf,
adj_field_obl,
adj_field_def,
{field = "f", label = "陰性", generate_default = default_feminine,
default_when_not_explicit = is_defaulting_adjective},
{field = "d", label = "陽性雙數", generate_default = default_masculine_dual},
{field = "fd", label = "陰性雙數", generate_default = default_feminine_dual},
{field = "cpl", label = "共同複數"},
{field = "pl", label = "陽性複數", generate_default = default_masculine_plural,
default_when_not_explicit = is_defaulting_adjective},
{field = "fpl", label = "陰性複數", generate_default = default_feminine_plural,
default_when_not_explicit = is_defaulting_adjective},
}
local function get_adj_params()
local params = {}
add_infl_list_params(params, adj_inflections)
add_infl_params(params, "el")
params.nisba = boolean_param
return params
end
local function handle_adj_args(data, args)
handle_infl_list_args(data, args, adj_inflections)
handle_infl(data, args, adj_field_elative)
for _, headobj in ipairs(data.heads) do
if term_is_nisba(headobj.term, args.nisba) then
insert(data.categories, langname .. "關係形容詞")
break
end
end
end
pos_functions["形容詞"] = {
params = get_adj_params,
func = handle_adj_args,
}
pos_functions["預設形容詞"] = {
params = get_adj_params,
func = function(data, args)
data.pos_category = "形容詞"
handle_adj_args(data, args)
end,
}
-----------------------------------------------------------------------------------------
-- Nouns, etc. --
-----------------------------------------------------------------------------------------
local function get_masc_or_feminine_gender(data, default_type)
local saw_m, saw_f, saw_mf
for _, gender in ipairs(data.genders) do
if is_masc_sg(gender.spec) then
saw_m = true
elseif is_fem_sg(gender.spec) then
saw_f = true
elseif is_masc_fem_sg(gender.spec) then
saw_mf = true
end
end
if saw_mf or saw_m and saw_f then
error("Can't generate default for " .. default_type .. " when gender is both masculine and feminine")
elseif saw_m then
return "m"
elseif saw_f then
return "f"
else
error("Can't generate default for " .. default_type .. " when gender is not specified as " ..
"masculine or feminine singular")
end
end
local function is_defaulting_noun(data, args)
return data.orig_pos_category == "預設名詞"
end
local noun_field_dual = {
field = "d", label = "雙數",
generate_default = function(data, args)
local gender = get_masc_or_feminine_gender(data, "名詞雙數")
if gender == "m" then
return default_masculine_dual(data, args)
else
return default_feminine_dual(data, args)
end
end,
}
local noun_field_plural = {
field = "pl", label = "複數",
generate_default = function(data, args, defspec)
local gender = get_masc_or_feminine_gender(data, "名詞複數")
if gender == "m" then
if defspec == "+f" then
return default_feminine_plural(data, args)
else
return default_masculine_plural(data, args)
end
elseif defspec == "+f" then
error("Can't specify '+f' with feminine gender; just use '+'")
else
return default_feminine_plural(data, args)
end
end,
-- Handle the case where pl=-, indicating an uncountable noun.
handle = function(data, args, terms)
if terms[1] and terms[1] == "-" then
insert(data.categories, langname .. "不可數")
if args.pauc and args.pauc[1] then
error("Can't specify paucals when pl=-")
end
end
end,
allowed_defspecs = {["+"] = true, ["+f"] = true},
default_when_not_explicit = is_defaulting_noun,
no_label = "<<不可數>>",
usually_no_label = "通常<<不可數>>",
}
local noun_field_paucal = {
field = "pauc", label = "<<少數>>", generate_default = default_feminine_plural,
}
local noun_field_feminine = {
field = "f", label = "陰性", generate_default = default_feminine,
default_when_not_explicit = function(data, args)
if data.orig_pos_category ~= "預設名詞" then
return nil
end
local gender = get_masc_or_feminine_gender(data, "defaulting-if-masculine noun feminine")
return gender == "m"
end,
}
local noun_field_masculine = {
field = "m", label = "陽性", generate_default = default_masculine,
default_when_not_explicit = function(data, args)
if data.orig_pos_category ~= "預設名詞" then
return nil
end
local gender = get_masc_or_feminine_gender(data, "defaulting-if-feminine noun masculine")
return gender == "f"
end,
}
local noun_basic_inflections = {
noun_field_cons,
noun_field_inf,
noun_field_obl,
noun_field_def,
}
local noun_shared_inflections = {
noun_field_dual,
noun_field_plural,
}
local noun_extra_inflections = {
noun_field_paucal,
noun_field_feminine,
noun_field_masculine,
}
local function get_noun_params()
local params = {}
add_gender_params(params)
add_infl_list_params(params, noun_basic_inflections)
add_infl_list_params(params, noun_shared_inflections)
add_infl_list_params(params, noun_extra_inflections)
params.nisba = boolean_param
return params
end
local function handle_noun_args(data, args)
handle_gender(data, args)
handle_infl_list_args(data, args, noun_basic_inflections)
handle_infl_list_args(data, args, noun_shared_inflections)
handle_infl_list_args(data, args, noun_extra_inflections)
for _, headobj in ipairs(data.heads) do
if term_is_nisba(headobj.term, args.nisba) then
insert(data.categories, langname .. "關係名詞")
break
end
end
end
pos_functions["名詞"] = {
params = get_noun_params,
func = handle_noun_args,
}
pos_functions["預設名詞"] = {
params = get_noun_params,
func = function(data, args)
data.pos_category = "名詞"
handle_noun_args(data, args)
end,
}
local noun_field_singulative = {field = "sing", label = "<<個體數>>", defgender = "f", generate_default = default_feminine}
local noun_field_collective = {field = "coll", label = "<<集合>>", defgender = "m", generate_default = default_masculine}
local function handle_sing_coll_noun_infls(data, args, otherinfl, otherlabel, othergender)
-- Handle sing= (corresponding singulative noun) or coll= (corresponding collective noun) and their gender
handle_infl(data, args, otherinfl, otherlabel, nil, othergender)
handle_infl_list_args(data, args, sing_coll_noun_inflections)
end
local function get_singulative_collective_noun_params(defgender, otherinfl)
local params = {}
add_gender_params(params, defgender)
add_infl_list_params(params, noun_basic_inflections)
add_infl_params(params, otherinfl)
add_infl_list_params(params, noun_shared_inflections)
add_infl_params(params, "pauc")
return params
end
pos_functions["集合名詞"] = {
params = function() return get_singulative_collective_noun_params("m", "sing") end,
func = function(data, args)
data.pos_category = "名詞"
insert(data.categories, langname .. "集合名詞")
m_headword_utilities.insert_fixed_inflection {
headdata = data,
label = "<<集合>>",
}
handle_gender(data, args)
handle_infl_list_args(data, args, noun_basic_inflections)
handle_infl(data, args, noun_field_singulative)
handle_infl_list_args(data, args, noun_shared_inflections)
handle_infl(data, args, noun_field_paucal)
end
}
pos_functions["個體數名詞"] = {
params = function() return get_singulative_collective_noun_params("f", "coll") end,
func = function(data, args)
data.pos_category = "名詞"
insert(data.categories, langname .. "個體數名詞")
m_headword_utilities.insert_fixed_inflection {
headdata = data,
label = "<<個體數>>",
}
handle_gender(data, args)
handle_infl_list_args(data, args, noun_basic_inflections)
handle_infl(data, args, noun_field_collective)
handle_infl_list_args(data, args, noun_shared_inflections)
handle_infl(data, args, noun_field_paucal)
end
}
-- FIXME: Do numerals really behave almost as nouns? They vary by masc/fem.
pos_functions["數詞"] = {
params = get_noun_params,
func = function(data, args)
insert(data.categories, langname .. "基數詞")
handle_noun_args(data, args)
end
}
pos_functions["專有名詞"] = {
params = get_noun_params,
func = handle_noun_args,
}
local function get_pronoun_params()
local params = {}
add_gender_params(params, defgender)
add_infl_list_params(params, noun_basic_inflections)
add_infl_list_params(params, noun_shared_inflections)
add_infl_params(params, "f")
return params
end
pos_functions["代詞"] = {
params = get_pronoun_params,
func = function(data, args)
handle_gender(data, args)
handle_infl_list_args(data, args, noun_basic_inflections)
handle_infl_list_args(data, args, noun_shared_inflections)
handle_infl(data, args, noun_field_feminine)
end
}
-----------------------------------------------------------------------------------------
-- Non-lemma forms --
-----------------------------------------------------------------------------------------
local valid_forms = list_to_set(
{ "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII",
"XIII", "XIV", "XV", "Iq", "IIq", "IIIq", "IVq" })
-- FIXME: Partly duplicated in [[Module:ar-inflections]].
local function handle_conj_form(data, args)
local form = args[2]
if form then
if not valid_forms[form] then
error("Invalid verb conjugation form " .. form)
end
insert(data.inflections, { label = "[[Appendix:阿拉伯語動詞#第" .. form .. "類|第" .. form .. "類]]"})
end
end
pos_functions["動詞變位形式"] = {
params = function()
return {
[2] = {},
}
end,
func = function(data, args)
handle_conj_form(data, args)
end
}
local function get_participle_params()
local params = get_adj_params()
params[2] = {}
return params
end
pos_functions["主動分詞"] = {
params = get_participle_params,
func = function(data, args)
data.pos_category = "分詞"
insert(data.categories, langname .. "主動分詞")
handle_conj_form(data, args)
handle_infl_list_args(data, args, adj_inflections)
end
}
pos_functions["被動分詞"] = {
params = get_participle_params,
func = function(data, args)
data.pos_category = "分詞"
insert(data.categories, langname .. "被動分詞")
handle_conj_form(data, args)
handle_infl_list_args(data, args, adj_inflections)
end
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["動詞"] = {
head_is_not_1 = true,
params = function() return {
[1] = {},
-- Comma-separated lists with possible inline modifiers
["past"] = {},
["past1s"] = {},
["nonpast"] = {},
["vn"] = {},
["noautolinktext"] = {type = "boolean"},
["noautolinkverb"] = {type = "boolean"},
} end,
func = function(data, args)
local ar_verb = require(ar_verb_module)
local alternant_multiword_spec =
args[1] ~= "-" and ar_verb.do_generate_forms(args, "ar-verb", data.pagename) or nil
local function do_slot(slots_to_check, override, label, slot_is_headword)
-- Do this even with an override so we can return the correct filled slot.
local slot, slotval
if alternant_multiword_spec then
for _, potential_slot in ipairs(slots_to_check) do
slotval = alternant_multiword_spec.forms[potential_slot]
if slotval then
slot = potential_slot
break
end
end
end
local function get_slot_values()
local terms = {}
for _, form in ipairs(slotval) do
local term = {
term = form.form,
id = form.id,
genders = form.genders,
pos = form.pos,
lit = form.lit,
}
term.tr = form.translit
if form.footnotes then
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(form.footnotes)
term.q = quals
term.refs = refs
end
insert(terms, term)
end
return terms
end
if override then
local override_param_mods = {
alt = {},
t = {
-- [[Module:headword]] expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
g = {
-- [[Module:headword]] expects the genders in "genders".
item_dest = "genders",
type = "genders",
},
pos = {},
lit = {},
id = {},
-- Qualifiers and labels
q = {
type = "qualifier",
},
qq = {
type = "qualifier",
},
l = {
type = "labels",
},
ll = {
type = "labels",
},
ref = {
-- [[Module:headword]] expects the references in "refs".
item_dest = "refs",
type = "references",
},
}
local function generate_obj(formval, parse_err)
if formval == "+" then
return {term = "+", underlying_terms = get_slot_values()}
end
local val, uncertain = formval:match("^(.*)(%?)$")
val = val or formval
uncertain = not not uncertain
local ar, translit = val:match("^(.*)//(.*)$")
if not ar then
ar = formval
end
local retval = {term = ar, uncertain = uncertain}
retval.tr = translit
end
local terms
if override:find("<") then
terms = require(parse_utilities_module).parse_inline_modifiers(override, {
paramname = paramname,
param_mods = override_param_mods,
generate_obj = generate_obj,
splitchar = "[,،]",
escape_fun = escape_comma_whitespace,
unescape_fun = unescape_comma_whitespace,
})
else
terms = split_on_comma(override)
for i, split in ipairs(terms) do
terms[i] = generate_obj(split)
end
end
-- See if + was supplied and we have to potentially flatten multiple default terms and harmonize
-- default properties with override properties.
local saw_underlying_terms = false
for _, term in ipairs(terms) do
if term.underlying_terms then
saw_underlying_terms = true
break
end
end
if saw_underlying_terms then
-- Flatten any default terms, copying the corresponding override properties over the default
-- properties. Non-default terms get inserted directly.
local flattened = {}
for _, term in ipairs(terms) do
if term.underlying_terms then
for _, underlying in ipairs(term.underlying_terms) do
for k, v in pairs(term) do
if k ~= "term" and k ~= "underlying_terms" then
if k == "uncertain" then
underlying.uncertain = underlying.uncertain or v
elseif type(v) ~= "table" or v[1] then
-- Don't copy empty lists (which are the default) over possibly non-empty
-- lists.
underlying[k] = v
end
end
end
insert(flattened, underlying)
end
else
insert(flattened, term)
end
end
terms = flattened
end
if not slot_is_headword then
terms.label = label
end
return terms, slot
elseif not alternant_multiword_spec then
return nil, slot
else
if not slotval then
if slot_is_headword then
-- FIXME, put "uncertain" as qualifier? Does this ever happen?
return nil, slot
elseif alternant_multiword_spec.slot_uncertain[slot] then
return {label = label .. " uncertain"}, slot
elseif alternant_multiword_spec.slot_explicitly_missing[slot] then
return {label = "無" .. label}, slot
else
-- just say nothing about this slot
return nil, slot
end
end
local terms = get_slot_values()
if not slot_is_headword then
terms.label = label
end
return terms, slot
end
end
local gloss_parts = {}
for _, vform in ipairs(alternant_multiword_spec.verb_forms) do
insert(gloss_parts, "[[Appendix:阿拉伯語動詞#第" .. vform .. "類|第" .. vform .. "類]]")
end
if gloss_parts[1] then
data.gloss = concat(gloss_parts, ", ")
end
if data.heads[1] and args.past then
error("Can't specify both head= and past= to {{ar-verb}}; prefer past=")
end
if not alternant_multiword_spec.has_active then
insert(data.inflections, {label = "僅被動"})
end
-- Do this always so `past_slot` is correctly filled.
local past, past_slot = do_slot(ar_verb.potential_lemma_slots, args.past, "-", "slot is headword")
if data.heads[1] then
-- user specified head=; don't override with past= or slot 'past_3sm' etc.
else
if past then
data.heads = past
end
end
local should_do_past1s = not not args.past1s
if not should_do_past1s then
local is_form_I = false
for _, vform in ipairs(alternant_multiword_spec.verb_forms) do
if vform == "I" then
is_form_I = true
break
end
end
if is_form_I then
require(inflection_utilities_module).map_word_specs(alternant_multiword_spec, function(base)
if base.verb_form == "I" then
for _, vowel_spec in ipairs(base.conj_vowels) do
-- For form-I geminate verbs, the final vowel of the past is elided in the citation form.
-- We want to display it for all cases other than active a~u and a~i (the most common
-- cases).
if vowel_spec.weakness == "geminate" then
if ar_verb.is_passive_only(base.passive) then
should_do_past1s = true
break
end
local past_vowel = ar_verb.rget(vowel_spec.past)
local nonpast_vowel = ar_verb.rget(vowel_spec.nonpast)
if not (past_vowel == ar.A and (nonpast_vowel == ar.U or nonpast_vowel == ar.I)) then
should_do_past1s = true
break
end
end
end
-- FIXME, provide way of breaking early from map_word_specs().
end
end)
end
end
local past1s
if should_do_past1s then
past1s, _ = do_slot({"past_1s", "past_pass_1s"}, args.past1s, "第一人稱單數過去時")
if past1s then
insert(data.inflections, past1s)
end
end
local nonpast_slots
if not past_slot or past_slot:find("^past_") then
nonpast_slots = {"ind_3ms", "ind_pass_3ms", "imp_2ms"}
else
nonpast_slots = {}
end
local nonpast, _ = do_slot(nonpast_slots, args.nonpast, "非過去時")
if nonpast then
insert(data.inflections, nonpast)
end
local vn, _ = do_slot({"vn"}, args.vn, "動詞性名詞")
if vn then
insert(data.inflections, vn)
end
-- FIXME: Should we insert categories? Conjugation also does it and is more likely to be accurate.
--for _, cat in ipairs(alternant_multiword_spec.categories) do
-- insert(data.categories, cat)
--end
--[=[
-- FIXME: Review this to see if we need to port it.
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:pt-verb]] in place of the user-specified or auto-generated head. This was copied from
-- [[Module:it-headword]], where doing this gets accents marked on the verb(s). We don't have accents marked on
-- the verb but by doing this we do get any footnotes on the infinitive propagated here. Don't do this if the
-- user gave multiple heads or gave a head with a multiword-linked verbal expression such as Italian
-- '[[dare esca]] [[al]] [[fuoco]]' (FIXME: give Portuguese equivalent).
if not data.user_specified_heads[1] or (
not data.user_specified_heads[2] and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
]=]
end
}
-----------------------------------------------------------------------------------------
-- Generic parts of speech --
-----------------------------------------------------------------------------------------
pos_functions.head_with_gender = {
params = function()
return {
[3] = {type = "genders"},
}
end,
func = function(data, args)
handle_gender(data, args, "nonlemma", 3)
end,
}
return export
lkz1ary37va8fn8sdtq2wffw76j8a75
Module:Ka-IPA
828
1381484
9909079
8797871
2026-09-29T07:26:43Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909079
Scribunto
text/plain
local export = {}
local ipa = require("Module:IPA")
local lang = require("Module:languages").getByCode("ka")
local gsub = mw.ustring.gsub
local lower = mw.ustring.lower
local mapping = {
["ა"] = "a", ["ბ"] = "b", ["გ"] = "ɡ", ["დ"] = "d",
["ე"] = "e", ["ვ"] = "v", ["ზ"] = "z", ["თ"] = "tʰ",
["ი"] = "i", ["კ"] = "kʼ", ["ლ"] = "l", ["მ"] = "m",
["ნ"] = "n", ["ო"] = "o", ["პ"] = "pʼ", ["ჟ"] = "ʒ",
["რ"] = "r", ["ს"] = "s", ["ტ"] = "tʼ", ["უ"] = "u",
["ფ"] = "pʰ", ["ქ"] = "kʰ", ["ღ"] = "ɣ", ["ყ"] = "qʼ",
["შ"] = "ʃ", ["ჩ"] = "t͡ʃ", ["ც"] = "t͡s", ["ძ"] = "d͡z",
["წ"] = "t͡sʼ", ["ჭ"] = "t͡ʃʼ", ["ხ"] = "x", ["ჯ"] = "d͡ʒ",
["ჰ"] = "h",
["ჶ"] = "f" -- special character
}
local narrow = {
["რ"] = "ɾ", ["ღ"] = "ʁ", ["ყ"] = "χʼ", ["ჩ"] = "t͡ʃʰ",
["ც"] = "t͡sʰ", ["ხ"] = "χ"
}
function export.pronunciation(word, is_phonetic)
-- make text lowercase
word = lower(word)
require("Module:script utilities").checkScript(word, "Geor")
if is_phonetic then
-- /v/ is labialised to the previous consonant
word = gsub(word, "([ბგდვზთკლმნპჟრსტფქღყშჩცძწჭხჯჰ])ვ", "%1ʷ")
-- /v/ is devoiced before a devoiced consonant
word = gsub(word, "ვ([თკპსტფქშჩცწჭხჰ])", "f%1")
-- /l/ is velarised before a back vowel
word = gsub(word, "ლ([აოუ])", "ɫ%1")
-- /n/ is velarised before a velar consonant
word = gsub(word, "ნ([ქკგ])", "ŋ%1")
-- word-initial /b, d, ɡ/ are devoiced
local bdg_initial = { ["ბ"] = "b̥", ["დ"] = "d̥", ["გ"] = "ɡ̊" }
word = gsub(word, "^([ბდგ])", bdg_initial)
word = gsub(word, "(%s)([ბდგ])", function(s, c) return s .. bdg_initial[c] end)
-- word-final /b, d, ɡ/ are devoiced and aspirated
local bdg_final = { ["ბ"] = "ფ", ["დ"] = "თ", ["გ"] = "ქ" }
word = gsub(word, "([ბდგ])$", bdg_final)
word = gsub(word, "([ბდგ])(%s)", function(c, s) return bdg_final[c] .. s end)
word = gsub(word, ".", narrow)
end
word = gsub(word, "%p", "")
word = gsub(word, ".", mapping)
return word
end
function export.show(frame)
local args = frame:getParent().args
local pagetitle = mw.title.getCurrentTitle().text
local p, results = {}, {}
if args[1] then
for _, v in ipairs(args) do
if v == "+" then v = pagetitle end
table.insert(p, (v ~= "") and v or nil)
end
else
if mw.title.getCurrentTitle().nsText == "Template" then
p = {"ქართული ენა"}
else p = { pagetitle } end
end
local has_f = false
for input_index, word in ipairs(p) do
-- check for character ჶ /f/
if mw.ustring.find(word, "ჶ") then
has_f = true
end
local phonemic = export.pronunciation(word, false)
local phonetic = export.pronunciation(word, true)
table.insert(results, { pron = "[" .. phonetic .. "]" })
local qual = args["q"..input_index]
if qual then
results[#results].q = { qual }
end
end
local ret = ipa.format_IPA_full { lang = lang, items = results }
if has_f then
ret = ret .. require("Module:utilities").format_categories({"Georgian terms with /f/"}, lang)
end
return ret
end
return export
ktpwptwkf3dg7daasi5az9rb1i2x7t0
踎低
0
1382619
9908779
9257406
2026-09-28T17:34:20Z
Sayonzei
40728
/* 漢語 */
9908779
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|c=mau1 dai1
|cat=v
}}
===動詞===
{{zh-verb}}
# {{lb|zh|粵語}} [[蹲下]]
====近義詞====
{{zh-dial|蹲}}
823esb6yfaz353rk3tolcealj2u9hve
západní
0
1383210
9909145
8115396
2026-09-29T08:45:06Z
TongcyDai
53191
9909145
wikitext
text/x-wiki
{{also|zapadni}}
==捷克語==
===詞源===
{{ety|cs|:af|západ|-ní|text=+|tree=1}}
===發音===
* {{cs-IPA}}
* {{audio|cs|Cs-západní.ogg}}
===形容詞===
{{cs-adj|západnější|adv=západně}}
# [[西方]]的
#: {{ant|cs|východní}}
====變格====
{{cs-adecl}}
===延伸閱讀===
* {{R:cs:PSJC}}
* {{R:cs:SSJC}}
* {{R:cs:IJP}}
sltfhy8ey30h50zdo8pcgpugyq2yxg9
Module:Pl-noun
828
1385732
9909094
8534114
2026-09-29T07:27:39Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909094
Scribunto
text/plain
local export = {}
local m_links = require('Module:links')
local m_adj = require('Module:pl-adj')
local m_g = require('Module:gender and number')
local lang = require("Module:languages").getByCode("pl")
-- local consonants = "[bcćdfghjklłmnńpqrsśtvwxzżź]";
-- case information
local cases = {
{ key = "nom"; en = "主格"; pl = "mianownik (kto? co?)" },
{ key = "gen"; en = "屬格"; pl = "dopełniacz (kogo? czego?)" },
{ key = "dat"; en = "與格"; pl = "celownik (komu? czemu?)" },
{ key = "acc"; en = "賓格"; pl = "biernik (kogo? co?)" },
{ key = "ins"; en = "工具格"; pl = "narzędnik (kim? czym?)" },
{ key = "loc"; en = "方位格"; pl = "miejscownik (o kim? o czym?)" },
{ key = "voc"; en = "呼格"; pl = "wołacz (o!)" },
}
-- columns for normal nouns
local noun_cols = {
{ key = "s"; title = "單數" },
{ key = "p"; title = "複數" },
}
-- columns for Old Polish nouns
local noun_dual_cols = {
{ key = "s"; title = "單數" },
{ key = "d"; title = "雙數" },
{ key = "p"; title = "複數" },
}
-- columns for pronouns
local pronoun_cols = {
{ key = "sm"; title = m_g.format_genders({"m"}) },
{ key = "sf"; title = m_g.format_genders({"f"}) },
{ key = "sn"; title = m_g.format_genders({"n"}) },
{ key = "pm"; title = m_g.format_genders({"vr-p"}) },
{ key = "po"; title = m_g.format_genders({"nv-p"}) },
}
local altsep = "/"
function empty_item(text)
return (not text) or text == "" or text == "-" or text == "–" or text == "—"
end
-- add link markers, with links separated by any of splitchars
-- exported for use in testcases
-- normally the separator is altsep
function export.make_links(text, splitchars, title)
if not title then
title = mw.title.getCurrentTitle().fullText
end
if empty_item(text) then
return "—"
elseif not splitchars or splitchars == "" then
return ("[[%s#波蘭語|%s]]"):format(text, text)
else
items = {}
for word in mw.ustring.gmatch(text, "[^" .. splitchars .. "]+") do
add_archaic = ""
add_deprecative = ""
if word:sub(-string.len(" (古舊)")) == " (古舊)" then
word = (mw.text.split(word, "% %(archaic%)"))[1]
add_archaic = " (古舊)"
end
if word:sub(-string.len(" (貶義)")) == " (貶義)" then
word = (mw.text.split(word, "% %(貶義%)"))[1]
add_deprecative = " (貶義)"
end
if word == title then
table.insert(items, ("[[%s]]"):format(word) .. add_archaic .. add_deprecative)
else
table.insert(items, ("[[%s#波蘭語|%s]]"):format(word, word) .. add_archaic .. add_deprecative)
end
end
if #items > 1 then
require("Module:debug").track("pl-noun/splitchars")
end
return table.concat(items, "/")
end
end
local function linkify_info(declinfo, splitchars, nolinks)
if nolinks then
require("Module:debug").track("pl-noun/nolinks")
end
local linked = {}
local title = mw.title.getCurrentTitle().fullText
for k, v in pairs(declinfo) do
if v == title then
linked[k] = "[[" .. v .. "]]"
elseif nolinks then
linked[k] = v
else
linked[k] = export.make_links(v, splitchars, title)
end
end
return linked
end
local function nowiki_info(declinfo)
require("Module:debug").track("pl-noun/nowiki")
local nowikied = {}
for k, v in pairs(declinfo) do
nowikied[k] = mw.text.nowiki(v)
end
return nowikied
end
local function override_col_titles(heads, cols)
if not heads then
return cols or {}
end
local new_cols = {}
local index = 1
for word in mw.ustring.gmatch(heads, "[^,]+") do
if cols[index] then
table.insert(new_cols, { key = cols[index].key; title = word } )
else
table.insert(new_cols, { key = tostring(index); title = word } )
end
index = index + 1
end
for ci, col in ipairs(cols) do
if ci >= index then
table.insert(new_cols, cols[index])
end
end
return new_cols
end
local function normalize_tantum(pargs)
-- support "num" as fallback for "tantum" to match Latin templates
local tantum = pargs.tantum or pargs.num
if not tantum then
return nil
end
if tantum == "sg" then
tantum = "s"
elseif tantum == "pl" then
tantum = "p"
end
return tantum
end
local function guess_width(declinfo, cols)
local maxl = 0
for k, v in pairs(declinfo) do
local l = mw.ustring.len(mw.text.trim(v))
if maxl < l then
maxl = l
end
end
local width = math.floor(maxl * 0.78) -- number obtained by anecdotal evidence
width = (width < 10) and 10 or width
width = 9 + (width * #cols)
return width
end
-- generate the HTML code of an inflection table
-- each entry in heads must have "key" and "title"
local function make_table(declinfo, cols, preproc, width, title, tantum, nolinks)
local result = {}
if not cols or not cols[1] then
error("make_table: invalid cols parameter")
end
local lemma_key = cases[1].key .. cols[1].key
local lemma = m_links.remove_links(declinfo[lemma_key])
title = title or ('<span class="Latn mention" lang="pl" xml:lang="pl">%s</span> 的變格'):format(lemma)
local emwidth = width or guess_width(declinfo, cols)
if preproc == "nowiki" then
declinfo = nowiki_info(declinfo)
elseif preproc == "linkify" then
declinfo = linkify_info(declinfo, altsep, nolinks)
end
if cols and (#cols > 0) then
table.insert(result, '|-\n! style="background:#d9ebff; width: 8em;" |\n')
for i, col in ipairs(cols) do
table.insert(result, ('! style="background:#d9ebff;" scope="col" | %s\n'):format(col.title))
end
end
local maxl = 0
for i, case in ipairs(cases) do
table.insert(result, ('|-\n! title="%s" style="background:#eff7ff;" scope="row" | %s\n'):format(case.pl, case.en))
for _, col in ipairs(cols) do
local declkey = case.key .. col.key
local item = mw.text.trim(declinfo[declkey])
if empty_item(item) then
table.insert(result, '| —\n')
else
local link = m_links.full_link({lang = lang, term = item, accel = {form = ("%s|%s"):format(case.key, col.key)}})
table.insert(result, ('| %s\n'):format(link))
end
end
end
local outtext = ([=[<div class="NavFrame" style="display: block; max-width: %uem;">
<div class="NavHead" style="background:#eff7ff" >%s</div>
<div class="NavContent">
{| style="background:#F9F9F9; text-align:center; width: 100%%; margin: 0;" class="inflection-table"
]=]):format(emwidth, title) .. table.concat(result, "") .. "|}</div></div>"
if tantum == "s" then
outtext = outtext .. "[[Category:波蘭語唯單名詞]]"
elseif tantum == "p" then
outtext = outtext .. "[[Category:波蘭語唯複名詞]]"
end
return outtext
end
local function get_mode()
local frame = mw.getCurrentFrame()
if mw.isSubsting() then
return 'subst'
elseif frame:getParent():getTitle() == mw.title.getCurrentTitle().fullText then
return 'demo'
else
return 'xclude'
end
end
local function make_table_from_pargs(pargs, cols, tantum)
local width = pargs.width and tonumber(pargs.width)
local declinfo = {}
if get_mode() == 'demo' then
if not cols then
cols = {
{ key = "1"; title = "column 1" },
{ key = "2"; title = "column 2" },
{ key = "3"; title = "column 3" },
}
end
for i = 1, 7 do
for j, col in ipairs(cols) do
local case_key = cases[i].key .. col.key
local argn = (i-1) * #cols + j
declinfo[case_key] = '{{{' .. case_key .. '| {{{' .. argn .. '}}} }}}'
end
end
return make_table(declinfo, cols, "nowiki", width, nil, nil, nil)
else
cols = override_col_titles(pargs.heads, cols or {})
for i = 1, 7 do
for j, col in ipairs(cols) do
local case_key = cases[i].key .. col.key
local argn = ((i-1) * #cols) + j
declinfo[case_key] = m_links.remove_links(mw.text.trim(pargs[case_key] or pargs[argn] or "-"))
end
end
return make_table(declinfo, cols, "linkify", pargs.width, pargs.title, normalize_tantum(pargs), pargs.nolinks)
end
end
-- Generate declension table for a singulare tantum with all forms passed explicitly as parameters
function export.template_decl_noun_sg(frame)
local pargs = frame:getParent().args
local cols = { noun_cols[1] }
return make_table_from_pargs(pargs, cols, "s")
end
-- Generate declension table for a plurale tantum with all forms passed explicitly as parameters
function export.template_decl_noun_pl(frame)
local pargs = frame:getParent().args
local cols = { noun_cols[2] }
return make_table_from_pargs(pargs, cols, "p")
end
-- Generate declension table for a regular noun with all forms passed explicitly as parameters
function export.template_decl_noun(frame)
local pargs = frame:getParent().args
return make_table_from_pargs(pargs, noun_cols, nil)
end
function export.template_decl_pronoun(frame)
local pargs = frame:getParent().args
return make_table_from_pargs(pargs, pronoun_cols, nil)
end
-- this probably belongs in a module for Old Polish (zlw-opl).
-- which reminds me: someone should write [[WT:AZLW-OPL]].
-- Generate declension table for a noun with dual number with all forms passed explicitly as parameters
function export.template_decl_noun_dual(frame)
local pargs = frame:getParent().args
return make_table_from_pargs(pargs, noun_dual_cols, nil)
end
function export.template_decl_generic(frame)
local pargs = frame:getParent().args
local heads = pargs.heads
if not heads then
if get_mode() == "demo" then
heads = "column 1,column 2"
else
error("No column headings defined!")
end
end
local cols = override_col_titles(heads, {})
return make_table_from_pargs(pargs, cols, nil)
end
-- -----------------------------------------------------------------------------
-- -----------------------------------------------------------------------------
-- ------------- Semi-automatic generation of inflected forms ------------------
-- -----------------------------------------------------------------------------
-- -----------------------------------------------------------------------------
local function nonempty(str)
if not str or str == "" then
return nil
else
return str
end
end
-- Generate a table contains true for each space-separated word in str
local function make_lookup_table(str)
local ret = {}
for i in mw.ustring.gmatch(str, "%a+") do
ret[i] = true
end
return ret
end
-- table that converts nominative soft endings to their genitive form
-- required for proper functioning of masc_common
local soft_ending_lookup = {
["ć"] = "ci";
["dź"] = "dzi";
["ń"] = "ni";
["ś"] = "si";
["ź"] = "zi";
}
-- Given a word and a lookup table of accepted endings,
-- split the word into a stem and an ending.
-- Ending is returned in genitive form, i.e. soft consonants
-- and digraphs such as ć, ś, dź are converted to midword
-- i-forms ci, si, dzi.
local function split_stem(word, lookup)
-- match the longest possible ending
local last, last_gen
local limit = math.min(mw.ustring.len(word), 5)
for i = -limit, -1 do
if not last_gen then
last = mw.ustring.sub(word, i)
if lookup[last] or soft_ending_lookup[last] then
last_gen = soft_ending_lookup[last] or last
end
end
end
if last_gen then
local stem = mw.ustring.sub(word, 1, -mw.ustring.len(last)-1)
return stem, last_gen
else
return nil, nil
end
end
local function check_split(stem, last)
if not stem then
error("nil stem encountered in declension pattern")
end
if not last then
error("nil ending encountered in declension pattern")
end
end
local function handle_overrides(pargs, declinfo, ovinfo)
-- process cases in order
local ret = {}
local singpl = { "s", "p" }
for i = 1, 14 do
local caseno = math.floor((i+1)/2)
local sp = 2 - (i % 2)
local case_key = cases[caseno].key .. singpl[sp]
if pargs[case_key] then
ret[case_key] = pargs[case_key]
if ovinfo[case_key] then
for dummy, v in pairs(ovinfo[case_key]) do
ret[v] = pargs[case_key]
end
end
elseif not ret[case_key] then
ret[case_key] = declinfo[case_key]
end
end
return ret
end
-- This table will hold patterns
local patterns = {}
-- -----------------------------------------------------------------------------
-- -------------------------- Masculine declension -----------------------------
-- -----------------------------------------------------------------------------
-- accepted masculine endings in genitive singular form
local masc_endings = make_lookup_table(
"acz b bi c ch ci ciec ciel cz d dz dzi dziec ek el eł f g giel h iec ieł ik j k kiel l ł m n ni niec p pi r rz rzeł " ..
"s si seł siec sł sm sn st sz t unek w wi x z zi ż zd zeł ziec zł zm zn")
-- common function for masculine declensions
local function masc_common(pattern, stem, last, gens_ending, noms_form, nomp_form, altgenp, explicit_stem)
-- verify that the word ending is supported
if not masc_endings[last] then
-- suppress module error on the template page
if not last or not mw.ustring.match(last, "^{{{") then
error("Unsupported word ending: " .. (last or "nil"))
end
end
local initial_last = last
-- nominative singular
local noms_lookup = { bi = "b"; ci = "ć"; dzi = "dź"; ni = "ń"; ["pi"] = "p"; si = "ś"; wi = "w"; zi = "ź"; }
if not noms_form then
noms_form = stem .. (noms_lookup[last] or last)
end
-- fix the only exceptions to -iec vowel elision
if last == "iec" and (stem == "w" or stem == "p") then
if not gens_ending and stem == "p" then
gens_ending = "a"
end
stem = stem .. "ie"
last = "c"
end
-- limited guessing of masculine inanimate endings here
-- personal and animate nouns (except for ''wół'') always have "a"
local gens_a = make_lookup_table("acz ciec ciel dzi dziec ek el eł iec ieł ik kiel giel ni niec seł siec rz rzeł zeł")
if not gens_ending then
if gens_a[last] then
gens_ending = "a"
else
gens_ending = "u"
end
end
-- handle the following things here:
-- regular vowel elisions
-- overlong endings used only for genitive singular guessing, e.g. acz, unek
-- x becoming ks in inflected forms
-- stem-final ó becoming o in inflected forms
local last_lookup = {
acz = "cz";
ciec = "c"; ciel = "l";
dziec = "c";
ek = "k"; el = "l"; ["eł"] = "ł";
iec = "c"; ["ieł"] = "ł"; ik = "k";
kiel = "l";
giel = "l";
niec = "c";
["rzeł"] = "ł";
["seł"] = "sł"; siec = "c";
unek = "k";
x = "s";
["zeł"] = "zł"; ziec = "c";
}
local stem_add = {
acz = "a";
ciec = "ć"; ciel = "cie";
dziec = "dź";
ik = "i";
kiel = "k";
giel = "g";
niec = "ń";
["rzeł"] = "r";
siec = "ś";
unek = "un";
x = "k";
ziec = "ź";
}
stem = stem .. (stem_add[last] or "")
last = last_lookup[last] or last
if mw.ustring.match(stem, "ó$") and not explicit_stem then
stem = mw.ustring.sub(stem, 1, -2) .. "o"
end
-- genitive singular
local gens_form = stem .. last .. gens_ending
-- accusative singular
local accs_form = (pattern == "m-in") and noms_form or gens_form
-- instrumental singular
local inss_form
if (last == "g") or (last == "k") then
inss_form = stem .. last .. "iem"
else
inss_form = stem .. last .. "em"
end
-- locative singular
local locs_lookup = {
b = "bie"; bi = "biu";
c = "cu"; ch = "chu"; ci = "ciu"; cz = "czu";
d = "dzie"; dz = "dzu"; dzi = "dziu";
f = "fie";
g = "gu";
h = "hu";
j = "ju";
k = "ku";
l = "lu"; ["ł"] = "le";
m = "mie";
n = "nie"; ni = "niu";
p = "pie"; ["pi"] = "piu";
r = "rze"; rz = "rzu";
s = "sie"; si = "siu"; ["sł"] = "śle"; sm = "śmie"; sn = "śnie";
st = "ście"; sz = "szu";
t = "cie";
w = "wie"; wi = "wiu";
z = "zie"; zd = "ździe"; zi = "ziu"; ["zł"] = "źle"; zm = "zmie";
zn = "źnie"; ["ż"] = "żu";
-- the -zm ending should not be palatalized, since it normally occurs
-- in borrowed nouns such as "marazm", "komunizm", "faszyzm"
}
local locs_form = stem .. (locs_lookup[last] or last)
-- vocative singular is the same as locative singular, with the exception of -iec
local vocs_form = locs_form
if pattern == "m-pr" and mw.ustring.match(initial_last, "iec$") then
vocs_form = stem .. "cze"
end
-- nominative plural
-- accusative and vocative plural are the same
if not nomp_form then
local nomp_e_ending = make_lookup_table("bi c ci cz dz dzi j l ni pi rz si sz wi zi ż")
if (last == "g") or (last == "k") then
nomp_form = stem .. last .. "i"
elseif nomp_e_ending[last] then
nomp_form = stem ..last .. "e"
else
nomp_form = stem .. last .. "y"
end
end
-- genitive plural
local genp_form
if mw.ustring.match(last, "i$") and (last ~= "pi") then
genp_form = stem .. last
elseif (last == "l") then
genp_form = stem .. last .. "i"
elseif (last == "j") then
genp_form = stem .. last .. "ów"
if not altgenp or (altgenp == "") then
altgenp = stem .. "i"
end
elseif (last == "cz") or (last == "rz") or (last == "sz") or (last == "ż") then
genp_form = stem .. last .. "y"
else
genp_form = stem .. last .. "ów"
end
if nonempty(altgenp) then
genp_form = genp_form .. "/" .. altgenp
end
local deprecate_nomp_form = ""
if pattern == "m-pr" then
local nomp_e_ending = make_lookup_table("bi c ci cz dz dzi j l ni pi rz si sz wi zi ż")
if (last == "g") or (last == "k") then
deprecate_nomp_form = stem .. last .. "i"
elseif nomp_e_ending[last] then
deprecate_nomp_form = stem ..last .. "e"
else
deprecate_nomp_form = stem .. last .. "y"
end
end
-- accusative plural - same as genitive or nominative depending on animacy
local accp_form = (pattern == "m-pr") and genp_form or nomp_form
local full_nomp_form = ((pattern == "m-pr") and deprecate_nomp_form ~= nomp_form and nomp_form .. "/" .. deprecate_nomp_form .. " (貶義)") or nomp_form
return {
noms = noms_form; nomp = full_nomp_form;
gens = gens_form; genp = genp_form;
dats = stem .. last .. "owi"; datp = stem .. last .. "om";
accs = accs_form; accp = accp_form;
inss = inss_form; insp = stem .. last .. "ami";
locs = locs_form; locp = stem .. last .. "ach";
vocs = vocs_form; vocp = nomp_form;
}
end
-- masculine inanimate nouns, e.g. bruk, beton, słup, szczaw, kurnik
-- default genitive singular ending is "u", but "a" endings are common as well
patterns["m-in"] = function (pargs, word)
local stem, last = split_stem(word, masc_endings)
local explicit_stem = false
if nonempty(pargs[2]) then
stem = pargs[1] or ""
last = pargs[2]
explicit_stem = true
end
local gens_ending = pargs[3] or gens_ending
local noms_form = nonempty(pargs[4])
local altgenp = nonempty(pargs[5])
local declinfo = masc_common("m-in", stem, last, gens_ending, noms_form,
nil, altgenp, explicit_stem)
return handle_overrides(pargs, declinfo,
{ noms = {"accs"}; locs = {"vocs"}; nomp = {"accp", "vocp"} })
end
-- masculine animate nouns, e.g. bocian, dzik, jeleń
patterns["m-an"] = function (pargs, word)
local stem, last = split_stem(word, masc_endings)
local explicit_stem = false
if nonempty(pargs[2]) then
stem = pargs[1] or ""
last = pargs[2]
explicit_stem = true
end
local noms_form = nonempty(pargs[3])
local altgenp = nonempty(pargs[4])
local declinfo = masc_common("m-an", stem, last, "a", noms_form,
nil, altgenp, explicit_stem)
return handle_overrides(pargs, declinfo,
{ gens = {"accs"}; locs = {"vocs"}; nomp = {"accp", "vocp"} })
end
-- masculine personal nouns, e.g. policjant, sknerus, polityk
patterns["m-pr"] = function (pargs, word)
if not pargs[1] and not pargs[2] then
if mw.ustring.match(word, "a$") then
return patterns["m-pr-a"](pargs, word)
elseif mw.ustring.match(word, "[yi]$") then
return patterns["m-pr-adj"](pargs, word)
elseif mw.ustring.match(word, "nin$") then
return patterns["m-pr-nin"](pargs, word)
end
end
local stem, last = split_stem(word, masc_endings)
local explicit_stem = false
if nonempty(pargs[2]) then
stem = pargs[1] or ""
last = pargs[2] or ""
explicit_stem = true
end
local nomp_form = nonempty(pargs[3]) or nonempty(pargs.nomp)
local noms_form = nonempty(pargs[4]) or nonempty(pargs.noms)
local altgenp = nonempty(pargs[5])
-- note: this is only a default, and will need to be overridden often
if not nomp_form then
nomp_lookup = {
acz = "acze"; -- palacz
ch = "chowie"; ci = "cie"; ciec = "ćcy"; cz = "cze";
ciel = "ciele"; -- eunuch, cieć, ?, ?, nauczyciel
d = "dzi", dziec = "dźcy"; -- kloszard, jeździec
ek = "kowie"; el = "ele"; -- świadek, ?
f = "fowie"; -- szeryf
g = "dzy"; -- szpieg
h = "howie"; -- druh
iec = "cy"; ik = "icy"; -- pogrobowiec, czytelnik
j = "je"; -- lokaj
k = "cy"; -- alkoholik
l = "le"; ["ł"] = "łowie"; -- ?, apostoł
m = "mi"; -- pielgrzym
n = "ni"; ni = "nie"; niec = "ńcy"; -- kompan, leń, jeniec
p = "pi"; -- chłop
r = "rzy"; rz = "rze"; -- konduktor, murarz
s = "si"; si = "sie"; siec = "ścy"; sz = "sze"; -- ordynans, rabuś, ?, jarosz
t = "ci"; -- policjant
z = "zi"; zi = "zie"; ziec = "źcy"; ["ż"] = "żowie"; -- intruz, kniaź, ?, mąż
}
if not nomp_lookup[last] then
error("Unsupported word ending: " .. last ..
"; please define the nominative plural form")
end
if last == "g" and mw.ustring.match(stem, "lo$") then
nomp_form = stem .. "dzy/" .. stem .. "gowie"
else
nomp_form = stem .. nomp_lookup[last]
end
end
local declinfo = masc_common("m-pr", stem, last, "a", noms_form,
nomp_form, altgenp, explicit_stem)
return handle_overrides(pargs, declinfo,
{ gens = {"accs"}; locs = {"vocs"}; nomp = {"vocp"}; genp = {"accp"} })
end
-- masculine personal nouns with adjectival declension
-- e.g. radny, łowczy, salowy
patterns["m-pr-adj"] = function (pargs, title)
local word = pargs[1] or title
if mw.ustring.match(word, "^{{{") then
word = "przykładowy"
end
local decl = m_adj.autoinflect(word)
local declinfo = {
noms = decl[1]; nomp = decl[4];
gens = decl[6]; genp = decl[8];
dats = decl[9]; datp = decl[10];
accs = decl[6]; accp = decl[8];
inss = decl[12]; insp = decl[13];
locs = decl[12]; locp = decl[8];
vocs = decl[1]; vocp = decl[4];
}
return handle_overrides(pargs, declinfo,
{ noms = {"vocs"}; gens = {"accs"}; nomp = {"vocp"}; genp = {"accp", "locp"} })
end
-- masculine personal nouns that end in -a
-- e.g. stażysta, dawca, banita
patterns["m-pr-a"] = function (pargs, word)
local word_no_a = mw.ustring.match(word, "^(.*)a$") or ""
local endings = make_lookup_table("b bi c ch d g j p r st t zd zn ż")
local stem, last = split_stem(word_no_a, endings)
stem = nonempty(pargs[1]) or stem
last = nonempty(pargs[2]) or last
if not endings[last] then
-- suppress module error on the template page
if not mw.ustring.match(last, "^{{{") then
error("Unsupported word ending: " .. last)
end
end
local gens_form = stem .. last .. "y"
if last == "j" then
gens_form = stem .. "i"
elseif last == "g" then
gens_form = stem .. "gi"
end
local dats_lookup = {
b = "bie"; -- Barnaba
bi = "bi";
c = "cy"; -- zdrajca
ch = "sze"; -- monarcha
d = "dzie"; -- nomada (?)
g = "dze"; -- sługa
j = "i"; -- kaznodzieja
p = "pie"; -- satrapa
r = "rze"; -- sknera
st = "ście"; -- starosta
t = "cie"; -- idiota
zd = "ździe"; -- gazda
zn = "źnie"; -- mężczyzna
["ż"] = "ży"; -- doża
}
local dats_form = stem .. (dats_lookup[last] or last)
local nomp_lookup = {
b = "bowie";
bi = "biowie";
c = "cy";
ch = "chowie";
d = "dzi";
g = "dzy";
j = "je";
p = "powie";
r = "rzy";
st = "ści";
t = "ci";
zd = "zdowie";
zn = "źni";
["ż"] = "żowie";
}
local nomp_form = stem .. (nomp_lookup[last] or (last .. "i"))
local genp_form = stem .. last .. "ów"
if last == "zn" then
genp_form = stem .. last
end
local deprecate_nomp_form = ""
local nomp_e_ending = make_lookup_table("bi c ci cz dz dzi j l ni pi rz si sz wi zi ż")
if (last == "g") or (last == "k") then
deprecate_nomp_form = stem .. last .. "i"
elseif nomp_e_ending[last] then
deprecate_nomp_form = stem ..last .. "e"
else
deprecate_nomp_form = stem .. last .. "y"
end
local full_nomp_form = (deprecate_nomp_form ~= nomp_form and nomp_form .. "/" .. deprecate_nomp_form .. " (貶義)") or nomp_form
-- TODO: alternative adjectival declension for -bia
local declinfo = {
noms = stem .. last .. "a"; nomp = full_nomp_form;
gens = gens_form; genp = genp_form;
dats = dats_form; datp = stem .. last .. "om";
accs = stem .. last .. "ę"; accp = genp_form;
inss = stem .. last .. "ą"; insp = stem .. last .. "ami";
locs = dats_form; locp = stem .. last .. "ach";
vocs = stem .. last .. "o"; vocp = nomp_form;
}
return handle_overrides(pargs, declinfo,
{ dats = {"locs"}; nomp = {"vocp"}; genp = {"accp"} })
end
-- masculine nouns ending in -log, both personal and inanimate
-- inanimate form is chosen when the first parameter is empty
patterns["log"] = function (pargs, title)
local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)log$") or ""
local nomp_form = nonempty(pargs[2])
local inanimate = not nonempty(pargs[1])
if not nomp_form then
if inanimate then
nomp_form = stem .. "logi"
else
nomp_form = stem .. "lodzy/" .. stem .. "logowie"
end
end
local gens_form = stem .. "loga"
if inanimate then
gens_form = stem .. "logu"
end
local accs_form = stem .. "loga"
if inanimate then
accs_form = stem .. "log"
end
local declinfo = {
noms = stem .. "log"; nomp = nomp_form;
gens = gens_form; genp = stem .. "logów";
dats = stem .. "logowi"; datp = stem .. "logom";
accs = accs_form; accp = stem .. "logów";
inss = stem .. "logiem"; insp = stem .. "logami";
locs = stem .. "logu"; locp = stem .. "logach";
vocs = stem .. "logu"; vocp = nomp_form;
}
return handle_overrides(pargs, declinfo,
{ locs = {"vocs"}; nomp = {"vocp"}; genp = {"accp"} })
end
-- masculine personal nouns ending with -nin, e.g. Rosjanin, łodzianin
-- popular for demonyms
patterns["m-pr-nin"] = function (pargs, word)
local stem = nonempty(pargs[1]) or mw.ustring.match(word, "^(.*)nin$")
local genp_ending = pargs[2] or "n"
local declinfo = {
noms = stem .. "nin"; nomp = stem .. "nie";
gens = stem .. "nina"; genp = stem .. genp_ending;
dats = stem .. "ninowi"; datp = stem .. "nom";
accs = stem .. "nina"; accp = stem .. genp_ending;
inss = stem .. "ninem"; insp = stem .. "nami";
locs = stem .. "ninie"; locp = stem .. "nach";
vocs = stem .. "ninie"; vocp = stem .. "nie";
}
return handle_overrides(pargs, declinfo,
{ gens = {"accs"}; locs = {"vocs"}; nomp = {"vocp"}; genp = {"accp"} })
end
-- masculine inanimate adjectives in noun phrases
patterns["adj-m-in"] = function (pargs, word)
word = nonempty(pargs[1]) or word
if mw.ustring.match(word, "^{{{") then
word = "przykładowy"
end
local decl = m_adj.autoinflect(word)
local declinfo = {
noms = decl[1]; nomp = decl[5];
gens = decl[6]; genp = decl[8];
dats = decl[9]; datp = decl[10];
accs = decl[1]; accp = decl[5];
inss = decl[12]; insp = decl[13];
locs = decl[12]; locp = decl[8];
vocs = decl[1]; vocp = decl[5];
}
return handle_overrides(pargs, declinfo,
{ noms = {"accs", "vocs"}; inss = {"locs"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end
-- masculine animate adjectives in noun phrases
patterns["adj-m-an"] = function (pargs, word)
word = nonempty(pargs[1]) or word
if mw.ustring.match(word, "^{{{") then
word = "przykładowy"
end
local decl = m_adj.autoinflect(word)
local declinfo = {
noms = decl[1]; nomp = decl[5];
gens = decl[6]; genp = decl[8];
dats = decl[9]; datp = decl[10];
accs = decl[6]; accp = decl[5];
inss = decl[12]; insp = decl[13];
locs = decl[12]; locp = decl[8];
vocs = decl[1]; vocp = decl[5];
}
return handle_overrides(pargs, declinfo,
{ noms = {"vocs"}; gens = {"accs"}; inss = {"locs"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end
-- masculine personal adjectives in noun phrases are identical
-- to masculine personal adjectival declension
-- -----------------------------------------------------------------------------
-- -------------------------- Feminine declension ------------------------------
-- -----------------------------------------------------------------------------
local fem_endings = make_lookup_table(
"b bi c ch ci cz d dz dzi dż f g h j k l ł m n ni p pi r rz " ..
"s si sł sm sn st sz t w wi z zd zi zł zm zn ż")
-- most feminine nouns ending in -a
-- note that some nouns ending in -ia have this declension and others
-- follow the "f-ia" pattern - it's impossible to tell from the word alone.
patterns["f"] = function (pargs, word)
-- to handle pluralia tantum as well
local word_no_a = mw.ustring.match(word, "^(.*)[aeiy]$")
local pars3 = pargs[1] and pargs[2] and pargs[3]
local nopars = not pargs[1] and not pargs[2] and not pargs[3]
-- 2 positional parameters given OR a at the and -> use a-final declension
-- 3+ positional parameters given OR consonant at the end -> use f-softcons
if nopars then
if mw.ustring.match(word, "ia$") then
native_ia = mw.ustring.match(word, "[bcdjklłrśtwzź]nia$")
native_ia = native_ia or mw.ustring.match(word, "[csz]ia$")
if not native_ia then
return patterns["f-ia"](pargs, word)
end
elseif mw.ustring.match(word, "ni$") then
return patterns["f-ni"](pargs, word)
end
end
if pars3 or not word_no_a then
return patterns["f-softcons"](pargs, word)
end
word_no_a = word_no_a or word
local stem, last = split_stem(word_no_a, fem_endings)
stem = nonempty(pargs[1]) or stem
last = nonempty(pargs[2]) or last
if not fem_endings[last] then
-- suppress module error on the template page
if not mw.ustring.match(last, "^{{{") then
error("Unsupported word ending: " .. last)
end
end
local soft_endings = make_lookup_table("bi ci dzi ni pi si wi zi")
local gens_form = stem .. last .. "y"
if soft_endings[last] then
gens_form = stem .. last
elseif last == "j" then
if mw.ustring.match(stem, "[aąeęioóuy]$") then
gens_form = stem .. "i"
else
gens_form = stem .. last .. "i"
end
elseif (last == "g") or (last == "k") or (last == "l") then
gens_form = stem .. last .. "i"
end
local dats_lookup = {
b = "bie"; bi = "bi";
c = "cy"; ch = "sze"; ci = "ci"; cz = "czy";
d = "dzie"; dz = "dzy"; dzi = "dzi"; ["dż"] = "dży";
f = "fie";
g = "dze";
h = "że";
j = "ji";
k = "ce";
l = "li"; ["ł"] = "le";
m = "mie";
n = "nie"; ni = "ni";
p = "pie"; ["pi"] = "pi";
r = "rze"; rz = "rzy";
s = "sie"; si = "si"; ["sł"] = "śle"; sm = "śmie"; sn = "śnie";
st = "ście"; sz = "szy";
t = "cie";
w = "wie"; wi = "wi";
z = "zie"; zd = "ździe"; zi = "zi"; ["zł"] = "źle"; zm = "zmie";
zn = "źnie"; ["ż"] = "ży";
-- -zm should not be palatalized
}
local dats_form = stem .. (dats_lookup[last] or last)
if last == "j" and mw.ustring.match(stem, "[aąeęioóuy]$") then
dats_form = stem .. "i"
end
local nomp_e_ending = make_lookup_table("bi c ci cz dz dzi dż j l ni pi rz si sz wi zi ż")
local nomp_form = stem .. last .. "y"
if (last == "g") or (last == "k") then
nomp_form = stem .. last .. "i"
elseif nomp_e_ending[last] then
nomp_form = stem .. last .. "e"
end
local genp_form = stem .. last
if last == "j" then
if mw.ustring.match(stem, "[aąeęioóuy]$") then
-- zgraja, breja, żmija, koja, szuja, chryja ->
-- zgraj, brej, żmij, koj, szuj, chryj
genp_form = stem .. last
else
-- e.g. gracja, torsja
genp_form = stem .. "ji/" .. stem .. "yj" .. " (古舊)";
end
elseif last == "l" then
if mw.ustring.match(stem, "[aąeęioóuy]$") then
-- hala, tabela, mila, rola, kula
genp_form = stem .. "l"
else
-- hodowla, grobla, bernikla
genp_form = stem .. "li"
end
elseif last == "k" then
local kstem, klast = split_stem(stem, soft_ending_lookup)
if kstem then
kstem = kstem .. klast
else
kstem = stem
end
if mw.ustring.match(stem, "[aąeęioóuy]$") then
genp_form = kstem .. "k"
else
genp_form = kstem .. "ek"
end
end
local declinfo = {
noms = stem .. last .. "a"; nomp = nomp_form;
gens = gens_form; genp = genp_form;
dats = dats_form; datp = stem .. last .. "om";
accs = stem .. last .. "ę"; accp = nomp_form;
inss = stem .. last .. "ą"; insp = stem .. last .. "ami";
locs = dats_form; locp = stem .. last .. "ach";
vocs = stem .. last .. "o"; vocp = nomp_form;
}
return handle_overrides(pargs, declinfo,
{ dats = {"locs"}; nomp = {"accp", "vocp"} })
end
-- feminine nouns ending with a consonant, e.g. stal, sól, brew
patterns["f-softcons"] = function (pargs, title)
local endings = make_lookup_table("c ć cz dz dź ew iew j l ń rz ś sz w z ż ź")
local accepted_lasts = make_lookup_table("c ci cz dz dzi j l ni rz si sz wi zi ż")
local stem, last = split_stem(title, endings)
local explicit_stem = false
stem = nonempty(pargs[1]) or stem
last = nonempty(pargs[2]) or last
if nonempty(pargs[2]) then
stem = pargs[1] or ""
last = pargs[2] or ""
explicit_stem = true
end
local nomp_ending = pargs[3] -- may be empty
local noms_form = nonempty(pargs[4])
-- Narew, brukiew, żagiew, brew - elide -e- or -ie-
if last == "iew" or last == "ew" then
if not noms_form then
noms_form = stem .. last
end
last = "wi"
end
if last == "w" or last == "z" then
last = last .. "i"
end
if not accepted_lasts[last] then
-- suppress module error on the template page
if not last or not mw.ustring.match(last, "^{{{") then
error("Unsupported word ending: " .. (last or "nil"))
end
end
-- nominative singular
if not noms_form or (noms_form == "") then
local noms_lookup = {
ci = "ć"; dzi = "dź"; ni = "ń"; si = "ś"; wi = "w"; zi = "ź"; }
noms_form = stem .. (noms_lookup[last] or last)
end
-- Stem-final ó becoming o in inflected forms
if mw.ustring.match(stem, "ó$") and not explicit_stem then
stem = mw.ustring.sub(stem, 1, -2) .. "o"
end
-- genitive singular
local gens_form = stem .. last
local gens_y_ending = make_lookup_table("c cz dz sz rz ż")
if last == "j" then
gens_form = stem .. "i"
elseif last == "l" then
gens_form = gens_form .. "i"
elseif gens_y_ending[last] then
gens_form = gens_form .. "y"
end
-- nominative plural
if not nomp_ending then
if last == "ci" then
-- some words have -e, but this is a lot more common
nomp_ending = ""
else
nomp_ending = "e"
end
end
-- this is trivial, but used in 3 places in the table
local nomp_form = stem .. last .. nomp_ending;
local declinfo = {
noms = noms_form; nomp = nomp_form;
gens = gens_form; genp = gens_form;
dats = gens_form; datp = stem .. last .. "om";
accs = noms_form; accp = nomp_form;
inss = stem .. last .. "ą"; insp = stem .. last .. "ami";
locs = gens_form; locp = stem .. last .. "ach";
vocs = gens_form; vocp = nomp_form;
}
return handle_overrides(pargs, declinfo,
{ noms = {"accs"}; gens = {"dats", "locs", "vocs", "genp"}; nomp = {"accp", "vocp"} })
end
-- feminine nouns with adjectival declension
patterns["f-adj"] = function (pargs, title)
local word = nonempty(pargs[1]) or title
if mw.ustring.match(word, "^{{{") then
word = "przykładowa"
end
local decl = m_adj.autoinflect(word)
local declinfo = {
noms = decl[2]; nomp = decl[5];
gens = decl[7]; genp = decl[8];
dats = decl[7]; datp = decl[10];
accs = decl[11]; accp = decl[5];
inss = decl[11]; insp = decl[13];
locs = decl[7]; locp = decl[8];
vocs = decl[2]; vocp = decl[5];
}
return handle_overrides(pargs, declinfo,
{ gens = {"dats", "locs"}; accs = {"inss"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
-- "vocs" may not always be equal to "noms" (e.g. "teściowa")
end
-- feminine nouns ending with -ja, e.g. fuzja, anomizja, animacja
-- obsolete - "f" handles them as well
patterns["f-ja"] = function (pargs, title)
local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ja$")
local yj = "yj"
if pargs[2] and (pargs[2] ~= "") then
yj = "ij"
end
genp_form = stem .. "ji/" .. stem .. yj .. " (古舊)";
local declinfo = {
noms = stem .. "ja"; nomp = stem .. "je";
gens = stem .. "ji"; genp = genp_form;
dats = stem .. "ji"; datp = stem .. "jom";
accs = stem .. "ję"; accp = stem .. "je";
inss = stem .. "ją"; insp = stem .. "jami";
locs = stem .. "ji"; locp = stem .. "jach";
vocs = stem .. "jo"; vocp = stem .. "je";
}
return handle_overrides(pargs, declinfo,
{ gens = {"dats", "locs"}; nomp = {"accp", "vocp"} })
end
-- most feminine nouns ending with -ia, e.g. mafia, kopia, balia
-- note that some nouns follow the "f" pattern instead, e.g. konopia, głębia
patterns["f-ia"] = function (pargs, title)
local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ia$")
local oldgenp = stem .. "ij"
if mw.ustring.match(stem, "[dtr]$") then
oldgenp = stem .. "yj"
end
local genp_form = stem .. "ii/" .. oldgenp .. " (古舊)";
local declinfo = {
noms = stem .. "ia"; nomp = stem .. "ie";
gens = stem .. "ii"; genp = genp_form;
dats = stem .. "ii"; datp = stem .. "iom";
accs = stem .. "ię"; accp = stem .. "ie";
inss = stem .. "ią"; insp = stem .. "iami";
locs = stem .. "ii"; locp = stem .. "iach";
vocs = stem .. "io"; vocp = stem .. "ie";
}
return handle_overrides(pargs, declinfo,
{ gens = {"dats", "locs"}; nomp = {"accp", "vocp"} })
end
-- feminine nouns ending in -ni, e.g. mistrzyni, mędrczyni
patterns["f-ni"] = function (pargs, title)
local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ni$")
local declinfo = {
noms = stem .. "ni"; nomp = stem .. "nie";
gens = stem .. "ni"; genp = stem .. "ń";
dats = stem .. "ni"; datp = stem .. "niom";
accs = stem .. "nię"; accp = stem .. "nie";
inss = stem .. "nią"; insp = stem .. "niami";
locs = stem .. "ni"; locp = stem .. "niach";
vocs = stem .. "ni"; vocp = stem .. "nie";
}
return handle_overrides(pargs, declinfo,
{ noms = {"gens", "dats", "locs", "vocs"}; nomp = {"accp", "vocp"} })
end
-- feminine adjectives in noun phrases
patterns["adj-f"] = function (pargs, title)
local word = nonempty(pargs[1]) or title
if mw.ustring.match(word, "^{{{") then
word = "przykładowa"
end
local decl = m_adj.autoinflect(word)
local declinfo = {
noms = decl[2]; nomp = decl[5];
gens = decl[7]; genp = decl[8];
dats = decl[7]; datp = decl[10];
accs = decl[11]; accp = decl[5];
inss = decl[11]; insp = decl[13];
locs = decl[7]; locp = decl[8];
vocs = decl[2]; vocp = decl[5];
}
return handle_overrides(pargs, declinfo,
{ noms = {"vocs"}; gens = {"dats", "locs"}; accs = {"inss"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end
-- -----------------------------------------------------------------------------
-- -------------------------- Neuter declension -----------------------------
-- -----------------------------------------------------------------------------
-- neuter nouns ending in -o, e.g. jajko, jarzmo, gusło, cudo
patterns["n"] = function (pargs, word)
if pargs[1] and mw.ustring.match(pargs[1], "^{{{") then
word = "jajko"
end
-- forward to other patterns depending on the last letter
if not nonempty(pargs[1]) and not nonempty(pargs[2]) then
if mw.ustring.match(word, "e$") then
return patterns["n-e"](pargs, word)
elseif mw.ustring.match(word, "ę$") then
return patterns["n-ę"](pargs, word)
elseif mw.ustring.match(word, "um$") then
return patterns["n-um"](pargs, word)
end
end
local word_no_o = mw.ustring.match(word, "^(.*)o$")
local endings = make_lookup_table(
"b bn c ch d f g gn j k kn l ł m mn n nd p r rz rzm " ..
"s sk sł sm sn st t tt tw w wn z zd zł zm zn zz")
local stem, last
if word_no_o then
stem, last = split_stem(word_no_o, endings)
end
stem = nonempty(pargs[1]) or stem
last = nonempty(pargs[2]) or last
local genp_form = nonempty(pargs[3])
if not endings[last] then
-- suppress module error on the template page
if not mw.ustring.match(last, "^{{{") then
error("Unsupported word ending: " .. last)
end
end
local inss_form = stem .. last .. "em"
if last == "g" or last == "k" then
inss_form = stem .. last .. "iem"
end
local locs_lookup = {
b = "bie"; bn = "bnie";
c = "cu"; ch = "chu";
d = "dzie";
f = "fie";
g = "gu"; gn = "gnie";
j = "ju";
k = "ku"; kn = "knie";
l = "lu"; ["ł"] = "le";
m = "mie"; mn = "mnie";
n = "nie"; nd = "ndzie";
p = "pie";
r = "rze"; rz = "rzu"; rzm = "rzmie"; -- piórze, scherzu, jarzmie
s = "sie"; sk = "sku"; ["sł"] = "śle"; sm = "śmie"; sn = "śnie"; st = "ście";
t = "cie"; tt = "tcie"; tw = "twie";
w = "wie"; wn = "wnie";
z = "zie"; zd = "ździe"; ["zł"] = "źle"; zm = "źmie"; zn = "źnie"; zz = "zzu";
}
locs_form = stem .. (locs_lookup[last] or (last .. "u"))
if not genp_form then
if last == "sn" or last == "zn" then
genp_form = stem .. mw.ustring.sub(last, 1, 1) .. "en" -- e.g. krosno -> krosen
elseif mw.ustring.match(last, "^.n$") then
genp_form = stem .. mw.ustring.sub(last, 1, 1) .. "ien" -- e.g. bagno -> bagien
elseif last == "tw" or last == "sk" or mw.ustring.match(stem, "[aąeęioóuy]$") then
genp_form = stem .. last -- e.g. bogactwo -> bogactw, dyktando -> dyktand (nd treated as digraph)
else
if mw.ustring.match(stem, "[gk]$") then
genp_form = stem .. "ie" .. last -- e.g. szkło -> szkieł
else
genp_form = stem .. "e" .. last -- e.g. jajko -> jajek
end
end
end
local declinfo = {
noms = stem .. last .. "o"; nomp = stem .. last .. "a";
gens = stem .. last .. "a"; genp = genp_form;
dats = stem .. last .. "u"; datp = stem .. last .. "om";
accs = stem .. last .. "o"; accp = stem .. last .. "a";
inss = inss_form; insp = stem .. last .. "ami";
locs = locs_form; locp = stem .. last .. "ach";
vocs = stem .. last .. "o"; vocp = stem .. last .. "a";
}
return handle_overrides(pargs, declinfo,
{ noms = {"accs", "vocs"}; gens = {"nomp", "accp", "vocp"}; nomp = {"accp", "vocp"} })
end
-- neuter nouns ending in -e
patterns["n-e"] = function (pargs, word)
local word_no_e = mw.ustring.match(word, "^(.*)e$")
local endings = make_lookup_table("bi c ci cz dz dzi fi j l mi ni pi rz si sz wi zi ż")
local stem, last
if word_no_e then
stem, last = split_stem(word_no_e, endings)
end
if nonempty(pargs[1]) then
stem = pargs[1]
last = ""
end
local genp_lookup = { ci = "ć"; cz = "czy"; ni = "ń"; rz = "rzy"; sz = "szy"; ["ż"] = "ży"; }
local genp_form = pargs[3] or (stem .. (genp_lookup[last] or last))
local sl = stem .. last
local declinfo = {
noms = sl .. "e"; nomp = sl .. "a";
gens = sl .. "a"; genp = genp_form;
dats = sl .. "u"; datp = sl .. "om";
accs = sl .. "e"; accp = sl .. "a";
inss = sl .. "em"; insp = sl .. "ami";
locs = sl .. "u"; locp = sl .. "ach";
vocs = sl .. "e"; vocp = sl .. "a";
}
return handle_overrides(pargs, declinfo,
{ noms = {"accs", "vocs"}; gens = {"nomp", "accp", "vocp"}; dats = {"locs"}; nomp = {"accp", "vocp"} })
end
-- neuter nouns ending in -ę but not in -mię, e.g. kocię, szczenię, dziecię
patterns["n-ę"] = function (pargs, title)
local mstem = mw.ustring.match(title, "^(.*)mię$")
if mstem then
pargs[1] = mstem
return patterns["n-mię"](pargs, title)
end
local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)ę$")
local declinfo = {
noms = stem .. "ę"; nomp = stem .. "ęta";
gens = stem .. "ęcia"; genp = stem .. "ąt";
dats = stem .. "ęciu"; datp = stem .. "ętom";
accs = stem .. "ę"; accp = stem .. "ęta";
inss = stem .. "ęciem"; insp = stem .. "ętami";
locs = stem .. "ęciu"; locp = stem .. "ętach";
vocs = stem .. "ę"; vocp = stem .. "ęta";
}
return handle_overrides(pargs, declinfo,
{ noms = {"accs", "vocs"}; dats = {"locs"}; nomp = {"accp", "vocp"} })
end
-- neuter nouns ending in -mię, e.g. wymię, znamię, plemię
patterns["n-mię"] = function (pargs, title)
local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)mię$")
local declinfo = {
noms = stem .. "mię"; nomp = stem .. "miona";
gens = stem .. "mienia"; genp = stem .. "mion";
dats = stem .. "mieniu"; datp = stem .. "mionom";
accs = stem .. "mię"; accp = stem .. "miona";
inss = stem .. "mieniem"; insp = stem .. "mionami";
locs = stem .. "mieniu"; locp = stem .. "mionach";
vocs = stem .. "mię"; vocp = stem .. "miona";
}
return handle_overrides(pargs, declinfo,
{ noms = {"accs", "vocs"}; dats = {"locs"}; nomp = {"accp", "vocp"} })
end
-- neuter nouns with adjectival declension, e.g. bykowe
patterns["n-adj"] = function (pargs, title)
local word = nonempty(pargs[1]) or title
if mw.ustring.match(word, "^{{{") then
word = "przykładowe"
end
local decl = m_adj.autoinflect(word)
local declinfo = {
noms = decl[3]; nomp = decl[5];
gens = decl[6]; genp = decl[8];
dats = decl[9]; datp = decl[10];
accs = decl[3]; accp = decl[5];
inss = decl[12]; insp = decl[13];
locs = decl[12]; locp = decl[8];
vocs = decl[3]; vocp = decl[5];
}
return handle_overrides(pargs, declinfo,
{ noms = {"accs", "vocs"}; inss = {"locs"}; nomp = {"accp", "vocp"}; genp = {"locp"} })
end
-- neuter nouns ending in -um, e.g. liceum, gimnazjum, kryterium
-- mostly nouns borrow from ancient Greek
patterns["n-um"] = function (pargs, title)
local stem = nonempty(pargs[1]) or mw.ustring.match(title, "^(.*)um$")
local declinfo = {
noms = stem .. "um"; nomp = stem .. "a";
gens = stem .. "um"; genp = stem .. "ów";
dats = stem .. "um"; datp = stem .. "om";
accs = stem .. "um"; accp = stem .. "a";
inss = stem .. "um"; insp = stem .. "ami";
locs = stem .. "um"; locp = stem .. "ach";
vocs = stem .. "um"; vocp = stem .. "a";
}
return handle_overrides(pargs, declinfo,
{ nomp = {"accp", "vocp"} })
end
-- highly irregular nouns
patterns["irreg"] = function (pargs, title)
local word = make_lookup_table("brat dziecko ksiądz książę rok")
if not word[title] then
if not mw.ustring.match(title, "^{{{") then
error("Unsupported word")
end
end
-- brat
if title == "brat" then
noms_form = "brat"
gens_form = "brata"
dats_form = "bratu"
accs_form = "brata"
inss_form = "bratem"
locs_form = "bracie"
vocs_form = "bracie"
nomp_form = "bracia"
genp_form = "braci"
datp_form = "braciom"
accp_form = "braci"
insp_form = "braćmi"
locp_form = "braciach"
vocp_form = "bracia"
end
-- dziecko
if title == "dziecko" then
noms_form = "dziecko"
gens_form = "dziecka"
dats_form = "dziecku"
accs_form = "dziecko"
inss_form = "dzieckiem"
locs_form = "dziecku"
vocs_form = "dziecko"
nomp_form = "dzieci"
genp_form = "dzieci"
datp_form = "dzieciom"
accp_form = "dzieci"
insp_form = "dziećmi"
locp_form = "dzieciach"
vocp_form = "dzieci"
end
--ksiądz
if title == "ksiądz" then
noms_form = "ksiądz"
gens_form = "księdza"
dats_form = "księdzu"
accs_form = "księdza"
inss_form = "księdzem"
locs_form = "księdzu"
vocs_form = "księże"
nomp_form = "księża"
genp_form = "księży"
datp_form = "księżom"
accp_form = "księży"
insp_form = "księżmi"
locp_form = "księżach"
vocp_form = "księża"
end
--książę
if title == "książę" then
noms_form = "książę"
gens_form = "księcia"
dats_form = "księciu"
accs_form = "księcia"
inss_form = "księciem"
locs_form = "księciu"
vocs_form = "książę"
nomp_form = "książęta"
genp_form = "książąt"
datp_form = "książętom"
accp_form = "książąt"
insp_form = "książętami"
locp_form = "książętach"
vocp_form = "książęta"
end
-- rok
if title == "rok" then
noms_form = "rok"
gens_form = "roku"
dats_form = "rokowi"
accs_form = "rok"
inss_form = "rokiem"
locs_form = "roku"
vocs_form = "roku"
nomp_form = "lata"
genp_form = "lat"
datp_form = "latom"
accp_form = "lata"
insp_form = "latami"
locp_form = "latach"
vocp_form = "lata"
end
local declinfo = {
noms = noms_form; nomp = nomp_form;
gens = gens_form; genp = genp_form;
dats = dats_form; datp = datp_form;
accs = accs_form; accp = accp_form;
inss = inss_form; insp = insp_form;
locs = locs_form; locp = locp_form;
vocs = vocs_form; vocp = vocp_form;
}
return handle_overrides(pargs, declinfo, {})
end
-- indeclinable pattern, i.e. same word for all cases
patterns["indec"] = function (pargs, title)
local word = nonempty(pargs[1]) or title
local declinfo = {}
for i = 1, 7 do
for _, col in ipairs(noun_cols) do
declinfo[cases[i].key .. col.key] = word
end
end
return handle_overrides(pargs, declinfo, {})
end
-- shorthands
patterns["nin"] = patterns["m-pr-nin"]
patterns["ia"] = patterns["fem-ia"]
-- aliases for adjectives in phrases
patterns["adj-m-pr"] = patterns["m-pr-adj"]
patterns["adj-n"] = patterns["n-adj"]
-- used for autodetection of adjective genders in phrases
local function pattern_gender(pattern)
if pattern == "m-in" or pattern == "adj-m-in" then
return "m-in"
elseif pattern == "m-an" or pattern == "adj-m-an" or pattern == "m-anml" or pattern == "adj-m-anml" then
return "m-an"
elseif string.match(pattern, "^m-pr") or pattern == "adj-m-pr" then
return "m-pr"
elseif string.match(pattern, "^f") or pattern == "adj-f" then
return "f"
elseif string.match(pattern, "^n") or pattern == "adj-n" then
return "n"
end
return nil
end
-- generate inflected forms given pattern, parameters, and full word
-- returned words do not contain links
function export.autoinflect(pattern, pargs, word)
if not pattern then
error("Declension pattern not specified")
end
if mw.ustring.match(pattern, "^{{{") then
pattern = "m-in"
end
if not patterns[pattern] then
error("Invalid declension pattern: " .. pattern)
end
return patterns[pattern](pargs, pargs["lemma"] or word)
end
local function make_substitutable_table(pargs, declinfo, preproc)
local tantum = normalize_tantum(pargs)
if mw.isSubsting() then
if tantum == "s" or tantum == "p" then
local tname = "sing"
if tantum == "p" then
tname = "pl"
end
local rows = {}
for i = 1, 7 do
local case_key = cases[i].key .. tantum
table.insert(rows, ("| <!-- %s --> %s"):format(case_key, m_links.remove_links(declinfo[case_key])))
end
return "{{pl-decl-noun-" .. tname .. "\n" .. table.concat(rows, "\n") .. "\n}}"
end
local rows = {}
for i = 1, 7 do
local case_skey = cases[i].key .. "s"
local case_pkey = cases[i].key .. "p"
table.insert(rows, ("| <!-- %s --> %s"):format(case_skey, m_links.remove_links(declinfo[case_skey])))
table.insert(rows, ("| <!-- %s --> %s"):format(case_pkey, m_links.remove_links(declinfo[case_pkey])))
end
return "{{pl-decl-noun\n" .. table.concat(rows, "\n") .. "\n}}"
else
local cols = noun_cols
if tantum == "s" then
cols = { noun_cols[1] }
elseif tantum == "p" then
cols = { noun_cols[2] }
end
cols = override_col_titles(pargs.heads, cols)
if pargs.nocat then
tantum = nil
end
return make_table(declinfo, cols, preproc, pargs.width, pargs.title, tantum, pargs.nolinks)
end
end
-- Generate declension table for a specified declension pattern
function export.template_decl_pattern(frame)
local args = frame.args
local pargs = frame:getParent().args
local pagetitle = mw.title.getCurrentTitle().fullText
-- support "num" as fallback for "tantum" to match Latin templates
local tantum = pargs.tantum or pargs.num
if tantum == "sg" then
tantum = "s"
elseif tantum == "pl" then
tantum = "p"
end
if get_mode() == 'demo' then
pargs = { "{{{1}}}", "{{{2}}}", "{{{3}}}", "{{{4}}}", "{{{5}}}" }
end
-- if args is empty, use pargs[1] as the pattern name and shift pargs
local offset = 0
local pattern = args[1]
if not pattern then
-- extreme brokenness: table.remove(pargs, 1) has absolutely no effect!
-- is this because pargs is somehow immutable?
pattern = pargs[1]
offset = -1
end
local fixed_pargs = {}
for key, parg in pairs(pargs) do
if type(key) == "number" then
local i = key + offset
if i >= 1 and parg then
fixed_pargs[i] = m_links.remove_links(parg)
end
else
fixed_pargs[key] = m_links.remove_links(parg)
end
end
declinfo = export.autoinflect(pattern, fixed_pargs, pagetitle)
return make_substitutable_table(pargs, declinfo, "linkify")
end
local function push_down_indices(tbl)
local ret = {}
for index, decl in ipairs(tbl) do
for k, v in pairs(decl) do
ret[k] = ret[k] or {}
table.insert(ret[k], v)
end
end
return ret
end
-- Generate declension table for a noun phrase
function export.template_decl_phrase(frame)
local args = frame.args
local pargs = frame:getParent().args
local page_title = mw.title.getCurrentTitle().fullText
local lemma = pargs["lemma"] or page_title
local words = {}
if get_mode() == "demo" then
pargs = { "adj", "n", "adj" }
lemma = "przykładowe wyrażenie rzeczownikowe"
end
-- split title into words
for t in mw.ustring.gmatch(lemma, "[^ ]+") do
table.insert(words, t)
end
local word_args = {}
for index, word in ipairs(words) do
-- parse arguments to declension patterns
word_args[index] = {}
local numkey = 1
local arg = pargs[index] or ""
for i in mw.ustring.gmatch(arg, "[^!]+") do
local param = mw.text.trim(i)
local k, v = mw.ustring.match(param, "^([^:]*):(.*)$")
if k and v then
word_args[index][k] = v
else
word_args[index][numkey] = param
numkey = numkey + 1
end
end
end
local adj_gender = nil
for index, word in ipairs(words) do
-- find the first gendered pattern and use it to match adjectives
if not adj_gender then
adj_gender = pattern_gender(word_args[index][1])
end
end
local result = {}
local result_raw = {}
for index, word in ipairs(words) do
local word_result = {}
local pattern = table.remove(word_args[index], 1)
if pattern == "adj" then
if adj_gender then
pattern = "adj-" .. adj_gender
else
error("Unable to guess adjective gender")
end
end
if not patterns[pattern] then
-- indeclinable
pattern = "indec"
end
result_raw[index] = export.autoinflect(pattern, word_args[index], word)
result[index] = linkify_info(result_raw[index], altsep, true)
end
-- rearrange the table so that word indices are at the lower level
result_raw = push_down_indices(result_raw)
result = push_down_indices(result)
-- make table entries
for case, v in pairs(result_raw) do
local phrase = table.concat(v, " ")
if phrase == page_title then
-- bold self-link if the phrase is equal to the page title
result[case] = "[[" .. page_title .. "]]"
else
result[case] = table.concat(result[case], " ")
-- do not split if linking full phrase declensions
result[case] = export.make_links(result[case], nil)
end
end
-- width determination
if not pargs.width then
local result_concat = {}
for case, v in pairs(result_raw) do
result_concat[case] = table.concat(v, " ")
end
pargs.width = guess_width(result_concat, noun_cols)
end
-- disable linkifying, since we already made per-word links
return make_substitutable_table(pargs, result, nil)
end
return export
slzue9rj8jg0i4xzrrb5mc6947u56ze
Module:Alternative forms
828
1390090
9909040
9641219
2026-09-29T07:24:11Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909040
Scribunto
text/plain
local export = {}
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
local function track(page)
require("Module:debug/track")("alter/" .. page)
end
--[==[
Main function for displaying alternative forms. Extracted out from the template-callable function so this can be
called by other modules (in particular, [[Module:descendants tree]]). `allow_self_link` causes terms the same as the
pagename to be shown normally; otherwise they are displayed unlinked. `default_separator` controls the separator between
terms when the user didn't use a special separator term like ";" (defaulting to ", ").
]==]
function export.display_alternative_forms(parent_args, pagename, allow_self_link, default_separator)
local params = {
[1] = {required = true, type = "language", default = "en"},
[2] = {list = true, allow_holes = true},
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref"}},
-- For compatibility, we need to turn off separate_no_index for q= and qq=.
{group = "q", separate_no_index = false},
-- We currently don't support unindexed l= and ll=.
{group = "l", require_index = true},
}
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
parse_lang_prefix = true,
track_module = "alter",
lang = 1,
sc = "sc.default",
stop_when = function(data)
local stop = not data.any_param_at_index
if stop and parent_args[data.orig_index + 1] == nil then
track("actual hole in params")
end
return stop
end,
default_separator = default_separator,
}
if not items[1] then
error("No items found!")
end
local lang = args[1]
local raw_labels = {}
-- Extract the labels and make sure none are blank or omitted.
local last_item_index = items[#items].orig_index
if last_item_index < args[2].maxindex then
for i = last_item_index + 2, args[2].maxindex do
if not args[2][i] then
-- Indices in i start at 1 but parameters start at 2 to add 1 to shown index.
error("Missing/blank item not allowed in [[Template:alt]] labels, but saw such an item in parameter "
.. (i + 1))
end
table.insert(raw_labels, args[2][i])
end
end
-- Make sure there aren't property parameters after the last item (i.e. corresponding to labels).
for k, v in pairs(args) do
-- Look for named list parameters. We check:
-- (1) key is a string (excludes the term param, which is a number);
-- (2) value is a table, i.e. a list;
-- (3) v.maxindex is set (i.e. allow_holes was used);
-- (4) v.maxindex is past the index of the last term.
if type(k) == "string" and type(v) == "table" and v.maxindex and v.maxindex > last_item_index then
local set_values = {}
for i = last_item_index + 1, v.maxindex do
if v[i] then
table.insert(set_values, i)
end
end
error(("Extraneous values for %s= (set at position%s %s)"):format(k, #set_values > 1 and "s" or "",
table.concat(set_values, ",")))
end
end
if not allow_self_link then
-- If the to-be-linked term is the same as the pagename, display it unlinked.
for _, item in ipairs(items) do
if not item.term and lang:stripDiacritics(item.term) == pagename then
track("term is pagename")
item.alt = item.alt or item.term
item.term = nil
end
end
end
local labels
if #raw_labels > 0 then
labels = require(labels_module).process_raw_labels { labels = raw_labels, lang = lang, nocat = true }
end
local parts = {}
local function ins(part)
table.insert(parts, part)
end
-- Construct the final output.
-- First the items, including separators, left and right regular qualifiers and left and right per-item labels.
for _, item in ipairs(items) do
ins(item.separator)
-- l10n: zhwikt 的 [[Module:parameter utilities]] 不會自動設定 show_decorations,需在此設定
item.show_decorations = true
local text = require(links_module).full_link(item, nil, allow_self_link)
ins(text)
end
-- If there are labels, construct them now and append to final output.
if labels then
if lang:hasTranslit() then
ins(" — " .. require(labels_module).format_processed_labels {
labels = labels, lang = lang
})
else
ins(" " .. require(labels_module).format_processed_labels {
labels = labels, lang = lang, open = "(", close = ")"
})
end
end
return table.concat(parts)
end
--[==[
Template-callable function for displaying alternative forms.
]==]
function export.create(frame)
local parent_args = frame:getParent().args
return export.display_alternative_forms(parent_args, mw.loadData("Module:headword/data").pagename)
end
return export
ozh5ipa77ubpmsqympm4lp5wfhcipwg
9909185
9909040
2026-09-29T09:47:45Z
TongcyBot
83009
移除手動 show_decorations
9909185
Scribunto
text/plain
local export = {}
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
local function track(page)
require("Module:debug/track")("alter/" .. page)
end
--[==[
Main function for displaying alternative forms. Extracted out from the template-callable function so this can be
called by other modules (in particular, [[Module:descendants tree]]). `allow_self_link` causes terms the same as the
pagename to be shown normally; otherwise they are displayed unlinked. `default_separator` controls the separator between
terms when the user didn't use a special separator term like ";" (defaulting to ", ").
]==]
function export.display_alternative_forms(parent_args, pagename, allow_self_link, default_separator)
local params = {
[1] = {required = true, type = "language", default = "en"},
[2] = {list = true, allow_holes = true},
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref"}},
-- For compatibility, we need to turn off separate_no_index for q= and qq=.
{group = "q", separate_no_index = false},
-- We currently don't support unindexed l= and ll=.
{group = "l", require_index = true},
}
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
parse_lang_prefix = true,
track_module = "alter",
lang = 1,
sc = "sc.default",
stop_when = function(data)
local stop = not data.any_param_at_index
if stop and parent_args[data.orig_index + 1] == nil then
track("actual hole in params")
end
return stop
end,
default_separator = default_separator,
}
if not items[1] then
error("No items found!")
end
local lang = args[1]
local raw_labels = {}
-- Extract the labels and make sure none are blank or omitted.
local last_item_index = items[#items].orig_index
if last_item_index < args[2].maxindex then
for i = last_item_index + 2, args[2].maxindex do
if not args[2][i] then
-- Indices in i start at 1 but parameters start at 2 to add 1 to shown index.
error("Missing/blank item not allowed in [[Template:alt]] labels, but saw such an item in parameter "
.. (i + 1))
end
table.insert(raw_labels, args[2][i])
end
end
-- Make sure there aren't property parameters after the last item (i.e. corresponding to labels).
for k, v in pairs(args) do
-- Look for named list parameters. We check:
-- (1) key is a string (excludes the term param, which is a number);
-- (2) value is a table, i.e. a list;
-- (3) v.maxindex is set (i.e. allow_holes was used);
-- (4) v.maxindex is past the index of the last term.
if type(k) == "string" and type(v) == "table" and v.maxindex and v.maxindex > last_item_index then
local set_values = {}
for i = last_item_index + 1, v.maxindex do
if v[i] then
table.insert(set_values, i)
end
end
error(("Extraneous values for %s= (set at position%s %s)"):format(k, #set_values > 1 and "s" or "",
table.concat(set_values, ",")))
end
end
if not allow_self_link then
-- If the to-be-linked term is the same as the pagename, display it unlinked.
for _, item in ipairs(items) do
if not item.term and lang:stripDiacritics(item.term) == pagename then
track("term is pagename")
item.alt = item.alt or item.term
item.term = nil
end
end
end
local labels
if #raw_labels > 0 then
labels = require(labels_module).process_raw_labels { labels = raw_labels, lang = lang, nocat = true }
end
local parts = {}
local function ins(part)
table.insert(parts, part)
end
-- Construct the final output.
-- First the items, including separators, left and right regular qualifiers and left and right per-item labels.
for _, item in ipairs(items) do
ins(item.separator)
local text = require(links_module).full_link(item, nil, allow_self_link)
ins(text)
end
-- If there are labels, construct them now and append to final output.
if labels then
if lang:hasTranslit() then
ins(" — " .. require(labels_module).format_processed_labels {
labels = labels, lang = lang
})
else
ins(" " .. require(labels_module).format_processed_labels {
labels = labels, lang = lang, open = "(", close = ")"
})
end
end
return table.concat(parts)
end
--[==[
Template-callable function for displaying alternative forms.
]==]
function export.create(frame)
local parent_args = frame:getParent().args
return export.display_alternative_forms(parent_args, mw.loadData("Module:headword/data").pagename)
end
return export
dcgoy575zkmc8ljrs2bbrk6ltya3p97
坐低
0
1390952
9908778
9255835
2026-09-28T17:22:39Z
Sayonzei
40728
/* 漢語 */
9908778
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|c=co5 dai1
|cat=v
}}
===動詞===
{{zh-verb}}
# {{lb|zh|粵語}} [[坐下]]
d6rwe9yzk3s8qaa16txqqt9itxfsfds
Module:Category tree/topic/Places
828
1391042
9909175
9790152
2026-09-29T09:13:12Z
TongcyBot
83009
更新至最新修正版
9909175
Scribunto
text/plain
-- Chinese Wiktionary port of enwikt [[Module:category tree/topic/Places]].
-- Generates descriptions and parent-category specs for place-related category pages.
--
-- Architecture mirrors enwikt: two tables exported as LABELS and HANDLERS.
-- LABELS — static label → spec mappings (top-level grouping categories)
-- HANDLERS — ordered list of functions; first non-nil return wins
--
-- Chinese-specific adaptations vs. enwikt:
-- * Pattern matching on 的 instead of English "in"/"of"
-- * Handler 3 bare-location parent: CONTAINER的TYPE (order reversed vs. enwikt)
-- Special case: TYPE=="地點" → CONTAINERTYPE (no 的)
-- * Handler 6 uses forward zh_name lookup (div.type → zh_name) instead of
-- a reverse map, because many English placetypes share the same Chinese name
-- (e.g. state/oblast/canton all → "州"; province/department/voivodeship → "省")
-- * Handler 7 (new): compound capital label + location ("美國的州首府")
-- Compound = zh_name(holonym_type) .. zh_cap_label(holonym_type)
-- Built at load time; skips country/constituent country (no 國首都 category)
-- * No article handling (Chinese has no definite article)
-- * Serial enumeration: 、(non-final) 和(final)
-- * Description format: {{{langname}}}中LOCATION_DESC[[PLACETYPE]]的名稱。
local labels = {}
local handlers = {}
local m_table = require("Module:table")
local m_locations = require("Module:place/locations")
local m_placetypes = require("Module:place/placetypes")
local m_zh_data = require("Module:place/data")
local internal_error = m_locations.internal_error
local zh_strings = m_placetypes.zh_strings
local zh_cap_by_holonym = m_placetypes.zh_capital_label_by_holonym_type
local format_zh_name = m_placetypes.format_zh_name
local insert = table.insert
local concat = table.concat
local function is_callable(val) return type(val) == "function" end
-- Returns 的 for 1-char zh_type strings; "" for 2+ chars.
-- Mirrors export.zh_cat_de in place-placetypes.lua but takes an already-formatted string.
local function zh_de(zh_type)
if not zh_type then return "" end
if mw.ustring.len(zh_type) == 1 then
return zh_strings.de_particle
end
if mw.ustring.match(zh_type, "^.和") then
return zh_strings.de_particle
end
return ""
end
-- Append candidate splits for 2+ char zh_names used as bare suffixes (no 的 form).
-- name_map: any table whose keys are zh_name strings (zh_name_to_pt_keys, etc.).
-- Skips suffixes whose preceding text ends in 的 (already captured by split_at_de).
local function add_no_de_suffix_splits(splits, label, name_map)
local de_bytes = #zh_strings.de_particle
for zh_type in pairs(name_map) do
if mw.ustring.len(zh_type) >= 2 then
local bytes = #zh_type
if #label > bytes and label:sub(-bytes) == zh_type then
local place_str = label:sub(1, #label - bytes)
if place_str:sub(-de_bytes) ~= zh_strings.de_particle then
insert(splits, {place = place_str, placetype = zh_type})
end
end
end
end
end
-- Literal-string gsub (no regex metacharacters interpreted in `from` or `to`).
local function gsub_literally(str, from, to)
return (str:gsub(from:gsub("[%(%)%.%%%+%-%*%?%[%^%$]", "%%%1"),
to:gsub("%%", "%%%%")))
end
-- Get the first (primary) placetype from spec.
local function fetch_primary_placetype(key, spec)
local placetype = spec.placetype
if type(placetype) == "table" then placetype = placetype[1] end
return placetype
end
-- True for continent/supercontinent/planet specs (used to omit 的 in 非洲國家).
local function spec_is_broad(spec)
local pt = spec and spec.placetype
if not pt then return false end
local pts = type(pt) == "table" and pt or {pt}
for _, p in ipairs(pts) do
if p == "continent" or p == "supercontinent" or p == "planet"
or p == "continental region" then return true end
end
return false
end
-- ---------------------------------------------------------------------------
-- Chinese serial joiner: {"A","B","C"} → "A、B和C"
-- ---------------------------------------------------------------------------
local function zh_join(items)
local n = #items
if n == 0 then return "" end
if n == 1 then return items[1] end
local parts = {}
for i = 1, n - 1 do
insert(parts, items[i])
if i < n - 1 then insert(parts, zh_strings.enum_sep) end
end
insert(parts, zh_strings.and_conj)
insert(parts, items[n])
return concat(parts)
end
-- ---------------------------------------------------------------------------
-- Placetype class → Chinese bare parent category label
-- ---------------------------------------------------------------------------
local class_to_bare_category_parent = {
["polity"] = "政治實體",
["subpolity"] = "政治區劃",
["settlement"] = "聚居地",
["non-admin settlement"] = "聚居地",
["capital"] = "首都",
["natural feature"] = "自然",
["man-made structure"] = "人造構築物",
["geographic region"] = "地理文化區域",
}
local class_is_political_division = {
["polity"] = true,
["subpolity"] = true,
["settlement"] = true,
["non-admin settlement"] = false,
["capital"] = true,
["natural feature"] = false,
["man-made structure"] = false,
["geographic region"] = false,
["generic place"] = false,
}
-- Enwikt bare_category_parent / addl_bare_category_parents → zhwikt label.
-- false = no equivalent, skip.
local en_category_to_zh = {
["islands"] = "島嶼",
["cities"] = "城市",
["political divisions"] = "政治區劃",
["settlements"] = "聚居地",
["places"] = "地點",
["former places"] = "已不存在的地點",
["former polities"] = "已不存在的政治實體",
["former political divisions"] = "已不存在的政治區劃",
["former settlements"] = "已不存在的聚居地",
["country-like entities"]= "類國家實體",
["names"] = "名稱",
["man-made structures"] = "人造構築物",
["countries"] = "國家",
["bodies of water"] = "水體",
["water"] = "水",
["landforms"] = "地形",
["ecosystems"] = "生態系統",
["forestry"] = "林學",
["mountains"] = "山",
["seas"] = "海",
["bridges"] = "橋",
["buildings"] = "建築",
["roads"] = "道路",
}
-- Translate enwikt category parent string to zhwikt equivalent.
-- Returns zh string, or nil if should be skipped.
-- Non-English strings (assumed already Chinese) pass through unchanged.
local function translate_cat_parent(val)
if type(val) ~= "string" then return nil end
local mapped = en_category_to_zh[val]
if mapped == false then return nil end
if mapped then return mapped end
return val
end
-- ---------------------------------------------------------------------------
-- Load-time reverse maps
-- ---------------------------------------------------------------------------
-- Chinese capital label → holonym type (e.g. "首都" → "country")
local zh_cap_label_to_holonym = {}
for holonym_type, zh_label in pairs(zh_cap_by_holonym) do
if not zh_cap_label_to_holonym[zh_label] then
zh_cap_label_to_holonym[zh_label] = holonym_type
end
end
-- Chinese placetype name → list of English placetype keys (one-to-many).
-- Used by handler 2 (bare placetype) and handler 4 (generic X的Y).
local zh_name_to_pt_keys = {}
do
local function add(name, en_key)
zh_name_to_pt_keys[name] = zh_name_to_pt_keys[name] or {}
insert(zh_name_to_pt_keys[name], en_key)
end
for en_key, entry in pairs(m_zh_data.zh_placetype_data) do
if entry.zh_name then
local names = type(entry.zh_name) == "table" and entry.zh_name or {entry.zh_name}
for _, name in ipairs(names) do add(name, en_key) end
end
if entry.zh_name_by_holonym_type then
for _, zh_name_val in pairs(entry.zh_name_by_holonym_type) do
local names = type(zh_name_val) == "table" and zh_name_val or {zh_name_val}
for _, name in ipairs(names) do add(name, en_key) end
end
end
end
end
-- Compound capital label → list of holonym placetype keys.
-- Compound = zh_name(holonym_type) .. zh_cap_label(holonym_type).
-- E.g. "州首府" → {"state"}, "地區首府" → {"territory", "region"}.
-- Skip country/constituent country (no 國首都 category per design).
-- Skip entries where compound == cap_label (e.g. "省省會"/"縣縣治" excluded to
-- avoid ambiguous matches — these bare labels are already handled by handler 5).
local zh_compound_cap_to_placetypes = {}
do
local function add_compound(compound, pt)
zh_compound_cap_to_placetypes[compound] = zh_compound_cap_to_placetypes[compound] or {}
insert(zh_compound_cap_to_placetypes[compound], pt)
end
for holonym_type, cap_label in pairs(zh_cap_by_holonym) do
if holonym_type ~= "country" and holonym_type ~= "constituent country" then
local zh_name_raw = m_placetypes.get_zh_placetype_name(holonym_type, nil)
if zh_name_raw then
local zh_name_str = format_zh_name(zh_name_raw)
local compound = zh_name_str .. cap_label
if compound ~= cap_label then
add_compound(compound, holonym_type)
end
end
end
end
end
-- ---------------------------------------------------------------------------
-- Location-linking helpers
-- ---------------------------------------------------------------------------
local function construct_linked_location(group, key, spec)
local full_placename = m_locations.key_to_placename(group, key)
return m_locations.construct_linked_placename(spec, full_placename)
end
-- Returns a location description string for embedding in category descriptions.
-- Shows one level of container for sub-national entries:
-- country → [[法國]]
-- state → [[加州]]([[美國]]的[[州]])
-- city → [[舊金山]]([[加州]]的[[城市]])
local function construct_location_desc(group, key, spec)
local linked = construct_linked_location(group, key, spec)
if spec.no_include_container_in_desc then return linked end
local container_iterator = m_locations.iterate_containers(group, key, spec)
local containers = container_iterator()
if not containers then return linked end
local location_type = spec.placetype
if type(location_type) == "table" then location_type = location_type[1] end
local zh_name_raw = location_type and m_placetypes.get_zh_placetype_name(location_type, nil)
local zh_type = zh_name_raw and format_zh_name(zh_name_raw)
local cont_links = {}
for _, c in ipairs(containers) do
insert(cont_links, construct_linked_location(c.group, c.key, c.spec))
end
local cont_str = zh_join(cont_links)
if zh_type then
return linked .. "(" .. cont_str .. "的" .. zh_type .. ")"
else
return linked .. "(位於" .. cont_str .. ")"
end
end
-- Fetch or construct the location description, substituting "+++" with the
-- auto-constructed description if present in spec.keydesc.
local function fetch_or_construct_location_desc(group, key, spec)
local val = spec.keydesc
if is_callable(val) then
val = val(group, key, spec)
spec.keydesc = val
end
if val then
if val:find("%+%+%+", 1, true) then
local base = construct_location_desc(group, key, spec)
val = gsub_literally(val, "+++", base)
end
return val
end
return construct_location_desc(group, key, spec)
end
-- ---------------------------------------------------------------------------
-- Split label at every occurrence of 的
-- Returns list of {place, placetype} tables (all possible splits left→right).
-- ---------------------------------------------------------------------------
local function split_at_de(label)
local results = {}
local de = "的"
local start = 1
while true do
local s, e = label:find(de, start, true)
if not s then break end
local place_part = label:sub(1, s - 1)
local type_part = label:sub(e + 1)
if #place_part > 0 and #type_part > 0 then
insert(results, {place = place_part, placetype = type_part})
end
start = e + 1
end
return results
end
-- Locate group + key + spec for a Chinese location string.
local function find_place(place_str)
local group, spec = m_locations.find_canonical_key(place_str)
if group then return group, place_str, spec end
return nil
end
-- No articles in Chinese; just return the key.
local function get_prefixed_key(key, spec)
if m_placetypes.get_prefixed_key then
return m_placetypes.get_prefixed_key(key, spec)
end
return key
end
-- Check whether zh_name_val (string or array) contains type_zh.
local function zh_name_has(zh_name_val, type_zh)
if type(zh_name_val) == "table" then
for _, n in ipairs(zh_name_val) do
if n == type_zh then return true end
end
return false
end
return zh_name_val == type_zh
end
-- Search divs list for a div whose Chinese name matches type_zh.
-- Singularizes div.type before zh_name lookup (divs use plural English keys
-- like "states", while zh_placetype_data uses singular keys like "state").
-- Uses forward lookup (English key → zh_name) to handle many-to-one collisions.
-- Returns: div_parent (English key or false), prep, matched_en_type.
-- div_parent == nil means no match; div_parent == false means match but no parent cat.
-- holonym_pt: the primary placetype of the container spec (e.g. "empire" for Roman Empire).
-- Passed to get_zh_placetype_name so context-sensitive zh_name_by_holonym_type overrides apply
-- (e.g. "province" + holonym_pt "empire" → "行省" instead of "省").
local function find_div_by_zh_name(divs, type_zh, holonym_pt)
if not divs then return nil, nil, nil end
if type(divs) ~= "table" then divs = {divs} end
for _, div in ipairs(divs) do
if type(div) == "string" then div = {type = div} end
local cat_as = div.cat_as or div.type
if type(cat_as) ~= "table" or cat_as.type then cat_as = {cat_as} end
for _, pt_cat_as in ipairs(cat_as) do
if type(pt_cat_as) == "string" then pt_cat_as = {type = pt_cat_as} end
-- Try direct key first (handles combined keys like "provinces and autonomous regions"),
-- then singularize as fallback (handles standard plural keys like "provinces").
local lookup_type = m_placetypes.maybe_singularize_placetype(pt_cat_as.type)
or pt_cat_as.type
local div_zh_raw = m_placetypes.get_zh_placetype_name(pt_cat_as.type, holonym_pt)
or m_placetypes.get_zh_placetype_name(lookup_type, holonym_pt)
if div_zh_raw and zh_name_has(div_zh_raw, type_zh) then
local div_parent = pt_cat_as.container_parent_type
if div_parent == nil then div_parent = div.container_parent_type end
if div_parent == nil then div_parent = pt_cat_as.type end
return div_parent, pt_cat_as.prep or div.prep or "of", lookup_type
end
end
end
return nil, nil, nil
end
-- Look up Chinese category name from a container's div cat_as, mirroring enwikt's
-- PL_PLACETYPE resolution. Used by handler 3 so that provinces + autonomous regions
-- of China both resolve to "省和自治區" rather than their individual zh_names.
-- Returns zh_name string (e.g. "省和自治區"), or nil to fall back to parent_zh_type.
local function find_cat_zh_from_container_divs(loc_type, container_spec)
if not loc_type or not container_spec then return nil end
local pl_type = m_placetypes.pluralize_placetype(loc_type)
if not pl_type then return nil end
for _, div_list in ipairs({container_spec.divs, container_spec.addl_divs}) do
if div_list then
local divs = type(div_list) == "table" and div_list or {div_list}
for _, div in ipairs(divs) do
if type(div) == "string" then div = {type = div} end
if div.type == pl_type then
local cat_as = div.cat_as
if cat_as and type(cat_as) == "string" then
-- Try cat_as directly first (handles combined keys like
-- "provinces and autonomous regions" which singularize incorrectly).
local zh_raw = m_placetypes.get_zh_placetype_name(cat_as, nil)
if not zh_raw then
local sg = m_placetypes.maybe_singularize_placetype(cat_as)
if sg then zh_raw = m_placetypes.get_zh_placetype_name(sg, nil) end
end
if zh_raw then return format_zh_name(zh_raw) end
end
return nil -- found div but no usable cat_as
end
end
end
end
return nil
end
-- ===========================================================================
-- Handler 1 — Bare capital category labels
-- e.g. "首都", "省會", "首府", "縣治"
-- ===========================================================================
insert(handlers, function(label)
local holonym_type = zh_cap_label_to_holonym[label]
if not holonym_type then return nil end
local zh_raw = m_placetypes.get_zh_placetype_name(holonym_type, nil)
local zh_holonym = zh_raw and format_zh_name(zh_raw) or holonym_type
return {
type = "name",
topic = label,
description = "{{{langname}}}中" .. zh_holonym .. "[[" .. label .. "]]的名稱。",
parents = {"首都"},
}
end)
-- ===========================================================================
-- Handler 2 — Bare placetype categories
-- e.g. "城市", "河流", "山脈", "國家"
-- ===========================================================================
insert(handlers, function(label)
local en_keys = zh_name_to_pt_keys[label]
if not en_keys then return nil end
for _, en_key in ipairs(en_keys) do
local canon_pt, ptdata = m_placetypes.get_placetype_data(en_key, "from category")
if canon_pt then
local from_cat = {from_category = true, no_split_qualifiers = true}
local bare_parent = m_placetypes.get_equiv_placetype_prop(canon_pt, function(pt)
local bcp = m_placetypes.get_placetype_prop(pt, "bare_category_parent")
if bcp then
local zh = translate_cat_parent(bcp)
if zh then return zh end
end
local cls = m_placetypes.get_placetype_prop(pt, "class")
if cls then return class_to_bare_category_parent[cls] end
end, from_cat) or "地點"
local addl_parents_raw = m_placetypes.get_equiv_placetype_prop(canon_pt, function(pt)
return m_placetypes.get_placetype_prop(pt, "addl_bare_category_parents")
end, from_cat)
local addl_parents
if addl_parents_raw then
addl_parents = {}
for _, v in ipairs(addl_parents_raw) do
local zh = translate_cat_parent(v)
if zh then m_table.insertIfNot(addl_parents, zh) end
end
if #addl_parents == 0 then addl_parents = nil end
end
local breadcrumb = m_placetypes.get_equiv_placetype_prop(canon_pt, function(pt)
return m_placetypes.get_placetype_prop(pt, "bare_category_breadcrumb")
end, from_cat)
if type(bare_parent) == "string" and breadcrumb then
bare_parent = {name = bare_parent, sort = breadcrumb}
end
local parents = {bare_parent}
if addl_parents then m_table.extend(parents, addl_parents) end
return {
type = "name",
topic = label,
description = "{{{langname}}}中[[" .. label .. "]]的名稱。",
breadcrumb = breadcrumb,
parents = parents,
}
end
end
end)
-- ===========================================================================
-- Handler 3 — Bare placename categories (known locations)
-- e.g. "美國", "法國", "北京", "加利福尼亞州"
-- Supports: overriding_bare_label_parents, bare_category_parent_type,
-- "+++" expansion, addl_parents, wp/wpcat/commonscat, fulldesc.
-- "+++" expands to: CONTAINER的TYPE (Chinese order) or CONTAINERTYPE for "地點".
-- ===========================================================================
insert(handlers, function(label)
local group, spec = m_locations.find_canonical_key(label)
if not group then return nil end
local wp = spec.wp
if wp == nil then wp = true end
local wpcat = spec.wpcat
if wpcat == nil then wpcat = wp end
local commonscat = spec.commonscat
if commonscat == nil then commonscat = wpcat end
local full_placename = m_locations.key_to_placename(group, label)
local full_container_placename
local container_iterator = m_locations.iterate_containers(group, label, spec)
local containers = container_iterator()
if containers then
full_container_placename = m_locations.key_to_placename(
containers[1].group, containers[1].key)
end
-- Substitute %l (full placename), %e (same, no elliptical form in Chinese),
-- %c (container placename) in wp/wpcat/commonscat values.
local function format_boxval(val)
if val == true then val = "%l" end
if type(val) == "string" then
val = gsub_literally(val, "%l", full_placename)
val = gsub_literally(val, "%e", full_placename)
if val:find("%%c", 1, true) and full_container_placename then
val = gsub_literally(val, "%c", full_container_placename)
end
end
return val
end
-- Determine Chinese type label for parent category construction.
-- bare_category_parent_type overrides the location's own placetype.
local parent_zh_type
do
local override = spec.bare_category_parent_type
if override then
local t = type(override) == "table"
and (override.type or override[1]) or override
local raw = type(t) == "string"
and m_placetypes.get_zh_placetype_name(t, nil)
parent_zh_type = raw and format_zh_name(raw)
end
if not parent_zh_type then
local loc_type = fetch_primary_placetype(label, spec)
local raw = loc_type and m_placetypes.get_zh_placetype_name(loc_type, nil)
parent_zh_type = raw and format_zh_name(raw)
end
end
local parents = {}
local bare_label_parents = spec.overriding_bare_label_parents
if not bare_label_parents then
bare_label_parents = {"+++"}
end
local inserted_containers = false
local loc_type = fetch_primary_placetype(label, spec)
for _, parent in ipairs(bare_label_parents) do
if parent == "+++" then
-- Expand to Chinese parent: CONTAINER的TYPE or CONTAINERTYPE for "地點";
-- omit 的 when TYPE=="國家" and container is broad (大洲/大陸地區 → 亞洲國家/加勒比地區國家);
-- use bare container when TYPE=="國家" but container is not broad (新西蘭 not 新西蘭的國家).
-- First, try container's div cat_as (mirrors enwikt PL_PLACETYPE resolution),
-- so that e.g. provinces and ARs of China both resolve to "省和自治區".
if containers then
for _, container in ipairs(containers) do
local ck = get_prefixed_key(container.key, container.spec)
local effective_zh_type = (loc_type
and find_cat_zh_from_container_divs(loc_type, container.spec))
or parent_zh_type
local parent_cat
if effective_zh_type == "地點" then
parent_cat = ck .. "地點"
elseif effective_zh_type == "國家" then
if spec_is_broad(container.spec) then
parent_cat = ck .. "國家"
else
parent_cat = ck
end
elseif effective_zh_type then
parent_cat = ck .. zh_de(effective_zh_type) .. effective_zh_type
else
parent_cat = ck
end
m_table.insertIfNot(parents, parent_cat)
end
inserted_containers = true
end
else
m_table.insertIfNot(parents, parent)
end
end
-- If "+++" wasn't present in bare_label_parents and containers exist, insert
-- bare container keys as fallback (mirrors enwikt behaviour).
if not inserted_containers and containers then
for _, container in ipairs(containers) do
m_table.insertIfNot(parents, container.key)
end
end
if spec.addl_parents then
for _, p in ipairs(spec.addl_parents) do
m_table.insertIfNot(parents, p)
end
end
if #parents == 0 then insert(parents, "地點") end
local description = spec.fulldesc or (
"{{{langname}}}中與" ..
fetch_or_construct_location_desc(group, label, spec) ..
"相關的詞語。")
return {
type = "topic",
description = description,
breadcrumb = full_placename,
parents = parents,
wp = format_boxval(wp),
wpcat = format_boxval(wpcat),
commonscat = format_boxval(commonscat),
}
end)
-- ===========================================================================
-- Handler 4 — Generic placetype + location
-- e.g. "法國的城市", "德國的河流", "亞洲的國家"
-- Matches placetypes that carry generic_before_non_cities or generic_before_cities.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_name_to_pt_keys)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
local en_keys = zh_name_to_pt_keys[type_zh]
if en_keys then
for _, en_key in ipairs(en_keys) do
local canon_pt, ptdata = m_placetypes.get_placetype_data(en_key, "from category")
if canon_pt and ptdata and
(ptdata.generic_before_non_cities or ptdata.generic_before_cities) then
local group, key, spec = find_place(place_str)
if group then
local allow = true
if spec.is_former_place and en_key ~= "place" then allow = false end
if allow then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = key})
local spt = spec.placetype
if spt == "country" or (type(spt) == "table" and m_table.contains(spt, "country")) then
insert(parents, "特定國家的政治區劃")
end
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(type_zh) .. type_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end
end
end
end)
-- ===========================================================================
-- Handler 4b — Continent/broad location + 國家 (no 的)
-- e.g. "非洲國家", "亞洲國家"
-- Handles the no-的 form generated for country categories under continents.
-- ===========================================================================
insert(handlers, function(label)
local zh_type = "國家"
local zh_type_len = #zh_type -- byte length (6 for 2 Chinese chars in UTF-8)
if #label <= zh_type_len or label:sub(-zh_type_len) ~= zh_type then return nil end
local place_str = label:sub(1, #label - zh_type_len)
local group, key, spec = find_place(place_str)
if not group or not spec_is_broad(spec) then return nil end
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. zh_type .. "]]的名稱。"
local parents = {key, {name = zh_type, sort = " " .. key}}
return {
type = "name",
topic = label,
description = desc,
breadcrumb = zh_type,
parents = parents,
}
end)
-- ===========================================================================
-- Handler 4c — Generic "X地點" categories (no 的)
-- e.g. "臺灣地點", "美國地點", "加州地點"
-- generic_place_cat_handler in place-placetypes.lua emits this no-的 form.
-- Handler 4 (split_at_de) cannot match it because there is no 的 separator.
-- ===========================================================================
insert(handlers, function(label)
local zh_type = "地點"
local zh_type_len = #zh_type -- 6 bytes (2 UTF-8 chars)
if #label <= zh_type_len or label:sub(-zh_type_len) ~= zh_type then return nil end
local place_str = label:sub(1, #label - zh_type_len)
local group, key, spec = find_place(place_str)
if not group then return nil end
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. zh_type .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = zh_type, sort = " " .. key})
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_type,
sort = key
})
end
else
insert(parents, {name = zh_type, sort = " " .. key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = zh_type,
parents = parents,
}
end)
-- ===========================================================================
-- Handler 5 — Capital + location (bare capital label)
-- e.g. "美國的首都", "四川省的省會", "德克薩斯州的首府"
-- Handles bare capital labels from zh_cap_label_to_holonym.
-- Must precede handler 6 (compound capital) and handler 7 (division) to avoid
-- mis-matching capital labels that also appear in a location's divs via cat_as.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_cap_label_to_holonym)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
if zh_cap_label_to_holonym[type_zh] then
local group, key, spec = find_place(place_str)
if group then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = key})
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(type_zh) .. type_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end)
-- ===========================================================================
-- Handler 6 — Compound capital label + location
-- e.g. "美國的州首府", "法國的行政區首府", "澳大利亞的地區首府"
-- Compound label = zh_name(holonym_type) .. zh_cap_label(holonym_type).
-- Built at load time in zh_compound_cap_to_placetypes.
-- Must precede handler 7 (division) to avoid mis-matching.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_compound_cap_to_placetypes)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
local target_pts = zh_compound_cap_to_placetypes[type_zh]
if target_pts then
local group, key, spec = find_place(place_str)
if group then
-- Check if any target placetype appears in this location's divs.
-- Div types are plural English strings ("states"); singularize before comparing.
local found = false
for _, divlist in ipairs({spec.divs, spec.addl_divs,
spec.addl_divs_for_categorization}) do
if divlist and not found then
if type(divlist) ~= "table" then divlist = {divlist} end
for _, div in ipairs(divlist) do
if type(div) == "string" then div = {type = div} end
local function check_div_type(pt_type)
local sg = m_placetypes.maybe_singularize_placetype(pt_type)
or pt_type
for _, target_pt in ipairs(target_pts) do
if sg == target_pt or pt_type == target_pt then
found = true
break
end
end
end
check_div_type(div.type)
if not found and div.cat_as then
local ca = div.cat_as
if type(ca) ~= "table" or ca.type then ca = {ca} end
for _, pt_ca in ipairs(ca) do
if type(pt_ca) == "string" then
pt_ca = {type = pt_ca}
end
check_div_type(pt_ca.type)
if found then break end
end
end
if found then break end
end
end
if found then break end
end
if found then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc
.. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = key})
local spt = spec.placetype
if spt == "country" or
(type(spt) == "table" and m_table.contains(spt, "country")) then
insert(parents, "特定國家的政治區劃")
end
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(type_zh) .. type_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end
end)
-- ===========================================================================
-- Handler 7 — Administrative division + location
-- e.g. "美國的州", "法國的省", "英國的構成國", "日本的縣"
-- Uses forward zh_name lookup (English div type → zh_name) to avoid collision
-- between English types that share the same Chinese name (state/oblast/canton → "州").
-- Singularizes plural div types ("states" → "state") before zh_name lookup.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_name_to_pt_keys)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
local group, key, spec = find_place(place_str)
if group then
local primary_pt = fetch_primary_placetype(key, spec)
local div_parent, div_prep, matched_type = find_div_by_zh_name(spec.divs, type_zh, primary_pt)
if div_parent == nil then
div_parent, div_prep, matched_type = find_div_by_zh_name(spec.addl_divs, type_zh, primary_pt)
end
if div_parent == nil then
div_parent, div_prep, matched_type =
find_div_by_zh_name(spec.addl_divs_for_categorization, type_zh, primary_pt)
end
if div_parent ~= nil then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if div_parent then -- div_parent == false means suppress parent cat
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = " " .. key})
local spt = spec.placetype
if spt == "country" or (type(spt) == "table" and m_table.contains(spt, "country")) then
insert(parents, "特定國家的政治區劃")
end
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
-- Map English div_parent key back to Chinese for parent category name.
local dp_zh
if type(div_parent) == "string" then
local dp_raw = m_placetypes.get_zh_placetype_name(div_parent, nil)
if not dp_raw then
local sg_dp = m_placetypes.maybe_singularize_placetype(div_parent)
if sg_dp then dp_raw = m_placetypes.get_zh_placetype_name(sg_dp, nil) end
end
dp_zh = dp_raw and format_zh_name(dp_raw) or type_zh
else
dp_zh = type_zh
end
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(dp_zh) .. dp_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = " " .. key})
end
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end)
-- ===========================================================================
-- Static labels — top-level grouping categories
-- ===========================================================================
labels["地點"] = {
type = "grouping",
description = "{{{langname}}}中地點的分類。",
parents = {"名稱"},
}
labels["政治實體"] = {
type = "grouping",
description = "{{{langname}}}中政治實體的名稱。",
parents = {"地點"},
}
labels["類國家實體"] = {
type = "grouping",
description = "{{{langname}}}中類國家實體(海外屬地、未被普遍承認之國家等)的名稱。",
parents = {"政治實體"},
}
labels["聚居地"] = {
type = "grouping",
description = "{{{langname}}}中聚落地名。",
parents = {"地點"},
}
labels["首都"] = {
type = "grouping",
description = "{{{langname}}}中首都及各類首府的名稱。",
parents = {"聚居地"},
}
labels["地形"] = {
type = "grouping",
description = "{{{langname}}}中地形的名稱。",
parents = {"自然"},
}
labels["人造構築物"] = {
type = "grouping",
description = "{{{langname}}}中人造構築物的名稱。",
parents = {"地點"},
}
labels["地理文化區域"] = {
type = "grouping",
description = "{{{langname}}}中地理及文化區域的名稱。",
parents = {"地點"},
}
labels["政治區劃"] = {
type = "grouping",
description = "{{{langname}}}中政治區劃(行政區及其他非主權行政分區)的名稱。",
parents = {"地點"},
}
-- Former places. The names mirror enwikt's `bare_category_parent` chain: 已不存在的地點 is the
-- root, and each tier keeps the breadcrumb of its live counterpart. 歷史首府 and 古代定居點 are
-- not 已不存在的X: a former capital, and often an ancient settlement, still exists as a town.
labels["已不存在的地點"] = {
type = "grouping",
description = "{{{langname}}}中已不存在的各類地點的名稱。",
breadcrumb = "已不存在",
parents = {"地點"},
}
labels["已不存在的政治實體"] = {
type = "grouping",
description = "{{{langname}}}中已消亡的政治實體(國家、王國、帝國等)的名稱。",
breadcrumb = "政治實體",
parents = {"已不存在的地點"},
}
labels["已不存在的國家和類國家實體"] = {
type = "grouping",
description = "{{{langname}}}中已不存在的國家及類似國家的政治實體的名稱。",
breadcrumb = "國家和類國家實體",
parents = {"已不存在的政治實體"},
}
labels["已不存在的政治區劃"] = {
type = "grouping",
description = "{{{langname}}}中已不存在的政治區劃(州、省、縣等)的名稱。",
breadcrumb = "政治區劃",
parents = {"已不存在的地點"},
}
labels["已不存在的聚居地"] = {
type = "grouping",
description = "{{{langname}}}中已不存在、已併入他處或已改制的聚居地(城市、鎮、村等)的名稱。",
breadcrumb = "聚居地",
parents = {"已不存在的政治區劃"},
}
labels["已不存在的屬地"] = {
type = "grouping",
description = "{{{langname}}}中已不存在的屬地(殖民地、保護國等)的名稱。",
breadcrumb = "屬地",
parents = {"已不存在的政治區劃"},
}
labels["已不存在的自然地物"] = {
type = "grouping",
description = "{{{langname}}}中已不存在的自然地物(湖泊、河流、島嶼等)的名稱。",
breadcrumb = "自然地物",
parents = {"已不存在的地點"},
}
labels["已不存在的人造構築物"] = {
type = "grouping",
description = "{{{langname}}}中已不存在的人造構築物(機場、公園等)的名稱。",
breadcrumb = "人造構築物",
parents = {"已不存在的地點"},
}
labels["歷史首府"] = {
type = "grouping",
description = "{{{langname}}}中曾為首都或首府的聚居地的名稱。",
parents = {"聚居地"},
}
labels["古代定居點"] = {
type = "grouping",
description = "{{{langname}}}中存在於古代的聚居地的名稱。",
parents = {"已不存在的聚居地"},
}
labels["特定國家的政治區劃"] = {
type = "grouping",
description = "{{{langname}}}中特定國家政治區劃的分類。",
parents = {"地點"},
}
-- ---------------------------------------------------------------------------
-- Form-of categories (@abbreviation of, @nickname for, @former name of, …)
-- ---------------------------------------------------------------------------
-- The names are the `default` specs of the `DIRECTIVE placetype` entries in
-- Module:place/placetypes — rename one there and it has to be renamed here too.
-- Parents follow enwikt's `bare_category_parent` chain; 暱稱 is defined in
-- Module:category tree/topic/Names. Rows are {name, parent, breadcrumb, description,
-- additional parents…}.
local form_of_labels = {
{"地名縮寫", "地點", "縮寫", "地名的縮寫"},
{"國家縮寫", "地名縮寫", "國家", "國家名稱的縮寫"},
{"政治區劃縮寫", "地名縮寫", "政治區劃", "政治區劃名稱的縮寫"},
{"已不存在地點的縮寫", "已不存在的地點", "縮寫", "已不存在地點名稱的縮寫", {name = "地名縮寫", sort = "已不存在"}},
{"已不存在國家的縮寫", "已不存在地點的縮寫", "國家", "已不存在國家名稱的縮寫"},
{"地名古舊形式", "地點", "古舊形式", "地名的古舊形式"},
{"地名過時形式", "地點", "過時形式", "地名的過時形式"},
{"地名棄用形式", "地點", "棄用形式", "地名的棄用形式"},
{"地名截斷形式", "地點", "截斷形式", "地名的截斷形式"},
{"地名縮略形式", "地點", "縮略形式", "地名的縮略形式"},
{"地名別稱", "暱稱", "地名", "地名的別稱", "地點"},
{"城市別稱", "地名別稱", "城市", "城市的別稱", "城市"},
{"大洲別稱", "地名別稱", "大洲", "大洲的別稱", "大洲"},
{"國家別稱", "地名別稱", "國家", "國家的別稱", "國家"},
{"州別稱", "地名別稱", "州", "州的別稱", "州"},
{"地名官方別稱", "地名別稱", "官方", "地名的官方別稱"},
{"州官方別稱", "州別稱", "官方", "州的官方別稱"},
{"地名前別稱", "地名別稱", "前別稱", "地名的前別稱", {name = "地名舊名", sort = "別稱"}},
{"城市前別稱", "地名前別稱", "城市", "城市的前別稱", "城市別稱"},
{"地名貶稱", "地名別稱", "貶稱", "地名的貶稱"},
{"城市貶稱", "地名貶稱", "城市", "城市的貶稱", "城市別稱"},
{"大洲貶稱", "地名貶稱", "大洲", "大洲的貶稱", "大洲別稱"},
{"國家貶稱", "地名貶稱", "國家", "國家的貶稱", "國家別稱"},
{"州貶稱", "地名貶稱", "州", "州的貶稱", "州別稱"},
{"地名舊名", "地點", "舊名", "地點的舊名"},
{"國家舊名", "地名舊名", "國家", "國家的舊名"},
{"政治實體舊名", "地名舊名", "政治實體", "政治實體的舊名"},
{"政治區劃舊名", "地名舊名", "政治區劃", "政治區劃的舊名"},
{"聚落舊名", "政治區劃舊名", "聚落", "聚落的舊名"},
{"首都舊名", "聚落舊名", "首都", "首都及各類首府的舊名"},
{"地名全稱", "地點", "全稱", "地名的全稱"},
{"國家全稱", "地名全稱", "國家", "國家的全稱"},
{"地名前全稱", "地名舊名", "全稱", "地名的前全稱"},
{"國家前全稱", "地名前全稱", "國家", "國家的前全稱", {name = "國家舊名", sort = "全稱"}},
{"地名官方名稱", "地點", "官方名稱", "地點的官方名稱"},
{"國家官方名稱", "地名官方名稱", "國家", "國家的官方名稱"},
{"地名前官方名稱", "地名舊名", "官方名稱", "地點的前官方名稱"},
{"國家前官方名稱", "地名前官方名稱", "國家", "國家的前官方名稱", {name = "國家舊名", sort = "官方名稱"}},
{"已不存在地點的官方名稱", "已不存在的地點", "官方名稱", "已不存在地點的官方名稱", {name = "地名官方名稱", sort = "已不存在"}},
{"已不存在國家的官方名稱", "已不存在地點的官方名稱", "國家", "已不存在國家的官方名稱"},
}
for _, row in ipairs(form_of_labels) do
local parents = {row[2]}
for i = 5, #row do
insert(parents, row[i])
end
labels[row[1]] = {
type = "name",
description = "{{{langname}}}中" .. row[4] .. "。",
breadcrumb = row[3],
parents = parents,
}
end
-- ---------------------------------------------------------------------------
-- Misc. place-related labels
-- ---------------------------------------------------------------------------
labels["外名"] = {
type = "name",
description = "{{{langname}}}[[外名]]。",
parents = {"地點"},
}
labels["古埃及行政區"] = {
type = "name",
description = "{{{langname}}}中[[古埃及]][[行政區]]的名稱。",
breadcrumb = "行政區",
parents = {"古埃及"},
}
-- ---------------------------------------------------------------------------
-- Sui generis place categories (cross-jurisdictional / transcontinental regions)
-- ---------------------------------------------------------------------------
labels["大西洋"] = {
type = "related-to",
description = "{{{langname}}}中與[[大西洋]]相關的詞語。",
parents = {"地球"},
}
labels["不列顛群島"] = {
type = "related-to",
description = "{{{langname}}}中與[[大不列顛島]]、[[愛爾蘭島]]及鄰近島嶼相關的詞語。",
parents = {"歐洲", "島嶼"},
}
labels["歐盟"] = {
type = "related-to",
description = "{{{langname}}}中與[[歐盟]]相關的詞語。",
parents = {"歐洲"},
}
labels["加斯科涅"] = {
type = "related-to",
description = "{{{langname}}}中與[[加斯科涅]]相關的詞語。",
parents = {"法國"},
}
labels["印度次大陸"] = {
type = "related-to",
description = "{{{langname}}}中與[[印度次大陸]]相關的詞語。",
parents = {"南亞"},
}
labels["孟加拉"] = {
type = "related-to",
description = "{{{langname}}}中與[[孟加拉]]地區相關的詞語。",
parents = {"印度次大陸"},
}
labels["克什米爾"] = {
type = "related-to",
description = "{{{langname}}}中與[[克什米爾]]相關的詞語。",
parents = {"印度次大陸"},
}
labels["克什米爾(印度)"] = {
type = "related-to",
description = "{{{langname}}}中[[印度]]管轄的[[克什米爾]]地名。",
parents = {"印度", "克什米爾"},
}
labels["朝鮮半島"] = {
type = "related-to",
description = "{{{langname}}}中與[[朝鮮半島]]相關的詞語。",
parents = {"亞洲"},
}
labels["朗格多克"] = {
type = "related-to",
description = "{{{langname}}}中與[[朗格多克]]相關的詞語。",
parents = {"法國"},
}
labels["拉普蘭"] = {
type = "related-to",
description = "{{{langname}}}中與[[拉普蘭]]相關的詞語。",
parents = {"歐洲", "芬蘭", "挪威", "俄羅斯", "瑞典"},
}
labels["中東"] = {
type = "related-to",
description = "{{{langname}}}中與[[中東]]相關的詞語。",
parents = {"非洲", "亞洲"},
}
labels["荷屬安的列斯"] = {
type = "related-to",
description = "{{{langname}}}中與[[荷屬安的列斯]]相關的詞語。",
parents = {"荷蘭", "北美洲"},
}
labels["海外法國"] = {
type = "related-to",
description = "{{{langname}}}中與[[海外法國]]相關的詞語。",
parents = {"法國"},
}
labels["普羅旺斯"] = {
type = "related-to",
description = "{{{langname}}}中與[[普羅旺斯]]相關的詞語。",
parents = {"法國"},
}
labels["波蘭人民共和國"] = {
type = "related-to",
description = "{{{langname}}}中與[[波蘭人民共和國]]相關的詞語。",
parents = {"波蘭"},
}
labels["南亞"] = {
type = "related-to",
description = "{{{langname}}}中與[[南亞]]相關的詞語。",
parents = {"歐亞大陸", "亞洲"},
}
return {LABELS = labels, HANDLERS = handlers}
6se8uxda9yk3wep5e18hh2963wc6m55
Module:Rhymes
828
1395082
9909031
9137757
2026-09-29T07:22:11Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909031
Scribunto
text/plain
local export = {}
local force_cat = false -- for testing
local decorations_module = "Module:decorations"
local IPA_module = "Module:IPA"
local parameters_module = "Module:parameters"
local parameter_utilities_module = "Module:parameter utilities"
local rhymes_styles_css_module = "Module:rhymes/styles.css"
local TemplateStyles_module = "Module:TemplateStyles"
local utilities_module = "Module:utilities"
local concat = table.concat
local insert = table.insert
local function track(page)
require("Module:debug/track")("rhymes/" .. page)
return true
end
local function tag_rhyme(rhyme, lang)
local formatted_rhyme, cats, err
formatted_rhyme, cats, err = require(IPA_module).format_IPA(lang, rhyme, "raw")
return formatted_rhyme, cats, err
end
local function make_rhyme_link(lang, link_rhyme, display_rhyme)
local retval, cats
if not link_rhyme then
retval = concat{"[[Rhymes:", lang:getCanonicalName(), "|", lang:getCanonicalName(), "]]"}
cats = {}
else
local formatted_rhyme
formatted_rhyme, cats, err = tag_rhyme(display_rhyme or link_rhyme, lang)
retval = concat{"[[Rhymes:", lang:getCanonicalName(), "/", link_rhyme, "|", formatted_rhyme, "]]", err}
end
return retval, cats
end
--[==[
Implementation of {{tl|rhymes row}}.
]==]
function export.show_row(frame)
local args = require(parameters_module).process(
frame.getParent and frame:getParent().args or frame,
{
[1] = {required = true, type = "full language"},
[2] = {required = true},
[3] = {},
}
)
if not args[1] then
return "[[Rhymes:英語/aɪmz|<span class=\"IPA\">-aɪmz</span>]]"
end
-- Discard cleanup categories from make_rhyme_link().
return (make_rhyme_link(args[1], args[2], "-" .. args[2])) .. (args[3] and (" (''" .. args[3] .. "'')") or "")
end
do
local function add_syllable_categories(categories, lang, rhyme, num_syl)
local prefix = "Rhymes:" .. lang .. "/" .. rhyme
insert(categories, prefix)
if num_syl then
for _, n in ipairs(num_syl) do
local c
if n > 1 then
c = prefix .. "/" .. n .. "音節"
else
c = prefix .. "/1音節"
end
insert(categories, c)
end
end
end
--[==[
Meant to be called from a module. `data` is a table containing the following fields:
* `lang`: language object for the rhymes;
* `rhymes`: a list of rhymes, each described by an object which specifies the rhyme, optional number of syllables, and
optional decoration fields:
** `rhyme`: the rhyme itself;
** `num_syl`: {nil} or a list of numbers, specifying the number of syllables of the word with this rhyme; optional and
currently used only for categorization; if omitted, defaults to the top-level `num_syl`;
** `separator`: {nil} or the string used to separate this rhyme from the preceding one when displayed; defaults to the
top-level `separator`;
** `q`: {nil} or a list of left regular qualifier strings, displayed directly before the rhyme in question;
** `qq`: {nil} or a list of right regular qualifier strings, displayed directly after the rhyme in question;
** `qualifiers`: {nil} or a list of qualifier strings; also displayed on the left; for compatibility purposes only, do
not use in new code;
** `a`: {nil} or a list of left accent qualifier strings (see [[Module:accent qualifier]]), displayed directly before
the rhyme in question;
** `aa`: {nil} or a list of right accent qualifier strings, displayed directly after the rhyme in question;
** `refs`: {nil} or a list of references or reference specs to add directly after the rhyme; the value of a list item 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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}} or {{cd|<nowiki><ref name="foo" /></nowiki>}})
and/or `group` (the group of the reference, as in {{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section;
** `nocat`: if {true}, suppress categorization for this rhyme only;
* `num_syl`: {nil} or a list of numbers, specifying the number of syllables for all rhymes; optional and currently used
only for categorization; overridable at the individual rhyme level;
* `separator`: {nil} or a string, specifying the separator displayed before all rhymes but the first; by default,
{", "}; overridable at the individual rhyme level;
* `q`: {nil} or a list of overall left regular qualifier strings, displayed before the initial caption;
* `qq`: {nil} or a list of overall right regular qualifier strings, displayed after all rhymes;
* `qualifiers`: {nil} or a list of overall left regular qualifier strings; for compatibility purposes only, do not use
in new code;
* `a`: {nil} or a list of overall left accent qualifier strings (see [[Module:accent qualifier]]), displayed before the
initial caption;
* `aa`: {nil} or a list of right accent qualifier strings, displayed after all rhymes;
* `sort`: {nil} or sort key;
* `caption`: {nil} or string specifying the caption to use, in place of {"Rhymes"}; a colon and space is automatically
added after the caption;
* `nocaption`: if {true}, suppress the caption display;
* `nocat`: if {true}, suppress categorization;
* `force_cat`: if {true}, force categorization even on non-mainspace pages.
If both regular and accent qualifiers on the same side and at the same level are specified, the accent qualifiers
precede the regular qualifiers on both left and right.
'''WARNING''': Destructively modifies the objects inside the `rhymes` field.
Note that the number of syllables is currently used only for categorization; if present, an extra category will
be added such as [[Category:Rhymes:Italian/ino/3 syllables]] in addition to [[Category:Rhymes:Italian/ino]].
]==]
function export.format_rhymes(data)
local langname = data.lang:getFullName()
local parts = {}
local categories = {}
local overall_sep = data.separator or ", "
for i, r in ipairs(data.rhymes) do
local rhyme = r.rhyme
local link, link_cats = make_rhyme_link(data.lang, rhyme, "-" .. rhyme)
if not r.nocat and not data.nocat then
for _, cat in ipairs(link_cats) do
insert(categories, cat)
end
end
if r.q and r.q[1] or r.qq and r.qq[1] or r.qualifiers and r.qualifiers[1]
or r.a and r.a[1] or r.aa and r.aa[1] or r.refs and r.refs[1] then
link = require(decorations_module).format_decorations {
lang = data.lang,
text = link,
q = r.q,
qq = r.qq,
qualifiers = r.qualifiers,
a = r.a,
aa = r.aa,
refs = r.refs,
}
end
insert(parts, r.separator or i > 1 and overall_sep or "")
insert(parts, link)
if not r.nocat and not data.nocat then
add_syllable_categories(categories, langname, rhyme, r.num_syl or data.num_syl)
end
end
local text = concat(parts)
if not data.nocaption then
text = (data.caption or "韻部") .. ":" .. text
end
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
text = require(decorations_module).format_decorations {
lang = data.lang,
text = text,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
if categories[1] then
local cat_text = require(utilities_module).format_categories(categories, data.lang, data.sort, nil,
force_cat or data.force_cat)
text = text .. cat_text
end
return text
end
end
--[==[
Implementation of {{tl|rhymes}}.
]==]
function export.show(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
local lang_param = compat and "lang" or 1
local plain = {}
local boolean = {type = "boolean"}
local params = {
[lang_param] = {required = true, type = "language", default = "en"},
[1 + offset] = {list = true, required = true, disallow_holes = true, default = "aɪmz"},
["caption"] = plain,
["nocaption"] = boolean,
["nocat"] = boolean,
["sort"] = plain,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{
param = "s",
item_dest = "num_syl",
separate_no_index = true,
type = "number",
sublist = true,
},
{group = {"q", "a", "ref"}},
}
local rhymes, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 1 + offset,
term_dest = "rhyme",
track_module = "rhymes",
}
local lang = args[lang_param]
local data = {
lang = lang,
rhymes = rhymes,
num_syl = args.s.default,
caption = args.caption,
nocaption = args.nocaption,
nocat = args.nocat,
sort = args.sort,
q = args.q.default,
qq = args.qq.default,
a = args.a.default,
aa = args.aa.default,
}
return export.format_rhymes(data)
end
--[==[
Implementation of {{tl|rhymes nav}}.
]==]
function export.show_nav(frame)
local args = require(parameters_module).process(
frame:getParent().args,
{
[1] = {required = true, type = "full language", default = "und"},
[2] = {list = true, allow_holes = true},
["nocat"] = {type = "boolean"},
}
)
local lang = args[1]
local langname = lang:getCanonicalName()
local parts = args[2]
-- Create steps
-- FIXME: We should probably use format_categories() in [[Module:utilities]] rather than constructing categories
-- manually.
local categories = {}
-- Here and below, we ignore any cleanup categories coming out of make_rhyme_link() by adding an extra set of parens
-- around the call to make_rhyme_link() to cause the second argument (the categories) to be ignored. {{rhymes nav}}
-- is run on a rhymes page so it's not clear we want the page to be added to any such categories, if they exist.
local steps = {"[[Wiktionary:韻部|韻部]]", (make_rhyme_link(lang))}
if #parts > 0 then
local last = parts[#parts]
parts[#parts] = nil
local prefix = ""
for i, part in ipairs(parts) do
prefix = prefix .. part
parts[i] = prefix
end
for _, part in ipairs(parts) do
insert(steps, (make_rhyme_link(lang, part .. "-", "-" .. part .. "-")))
end
if last == "-" then
insert(steps, (make_rhyme_link(lang, prefix, "-" .. prefix)))
insert(categories, "[[Category:" .. langname .. "韻部" .. (prefix == "" and "" or "/" .. prefix .. "-") .. "| ]]")
elseif mw.title.getCurrentTitle().text == langname .. "/" .. prefix .. last .. "-" then
insert(steps, (make_rhyme_link(lang, prefix .. last .. "-", "-" .. prefix .. last .. "-")))
insert(categories, "[[Category:" .. langname .. "韻部/" .. prefix .. last .. "-|-]]")
else
insert(steps, (make_rhyme_link(lang, prefix .. last, "-" .. prefix .. last)))
insert(categories, "[[Category:" .. langname .. "韻部" .. (prefix == "" and "" or "/" .. prefix .. "-") .. "|" .. last .. "]]")
end
elseif lang:getCode() ~= "und" then
insert(categories, "[[Category:" .. langname .. "韻部| ]]")
end
if mw.title.getCurrentTitle().nsText == "Rhymes" then
frame:callParserFunction("DISPLAYTITLE",
mw.title.getCurrentTitle().fullText:gsub(
"/(.+)$",
function (rhyme)
return "/" .. (tag_rhyme(rhyme, lang)) -- ignore cleanup categories
end))
end
local templateStyles = require(TemplateStyles_module)(rhymes_styles_css_module)
local ol = mw.html.create("ol")
for _, step in ipairs(steps) do
ol:node(mw.html.create("li"):wikitext(step))
end
local div = mw.html.create("div")
:attr("role", "navigation")
:attr("aria-label", "Breadcrumb")
:addClass("ts-rhymesBreadcrumbs")
:node(ol)
local formatted_cats = args.nocat and "" or concat(categories)
return templateStyles .. tostring(div) .. formatted_cats
end
return export
crdkxy0y479dry3t12u13q30wl528pf
9909120
9909031
2026-09-29T07:41:44Z
TongcyBot
83009
同步 enwikt:舊介面改為報錯
9909120
Scribunto
text/plain
local export = {}
local force_cat = false -- for testing
local decorations_module = "Module:decorations"
local IPA_module = "Module:IPA"
local parameters_module = "Module:parameters"
local parameter_utilities_module = "Module:parameter utilities"
local rhymes_styles_css_module = "Module:rhymes/styles.css"
local TemplateStyles_module = "Module:TemplateStyles"
local utilities_module = "Module:utilities"
local concat = table.concat
local insert = table.insert
local function track(page)
require("Module:debug/track")("rhymes/" .. page)
return true
end
local function tag_rhyme(rhyme, lang)
local formatted_rhyme, cats, err
formatted_rhyme, cats, err = require(IPA_module).format_IPA(lang, rhyme, "raw")
return formatted_rhyme, cats, err
end
local function make_rhyme_link(lang, link_rhyme, display_rhyme)
local retval, cats
if not link_rhyme then
retval = concat{"[[Rhymes:", lang:getCanonicalName(), "|", lang:getCanonicalName(), "]]"}
cats = {}
else
local formatted_rhyme
formatted_rhyme, cats, err = tag_rhyme(display_rhyme or link_rhyme, lang)
retval = concat{"[[Rhymes:", lang:getCanonicalName(), "/", link_rhyme, "|", formatted_rhyme, "]]", err}
end
return retval, cats
end
--[==[
Implementation of {{tl|rhymes row}}.
]==]
function export.show_row(frame)
local args = require(parameters_module).process(
frame.getParent and frame:getParent().args or frame,
{
[1] = {required = true, type = "full language"},
[2] = {required = true},
[3] = {},
}
)
if not args[1] then
return "[[Rhymes:英語/aɪmz|<span class=\"IPA\">-aɪmz</span>]]"
end
-- Discard cleanup categories from make_rhyme_link().
return (make_rhyme_link(args[1], args[2], "-" .. args[2])) .. (args[3] and (" (''" .. args[3] .. "'')") or "")
end
do
local function add_syllable_categories(categories, lang, rhyme, num_syl)
local prefix = "Rhymes:" .. lang .. "/" .. rhyme
insert(categories, prefix)
if num_syl then
for _, n in ipairs(num_syl) do
local c
if n > 1 then
c = prefix .. "/" .. n .. "音節"
else
c = prefix .. "/1音節"
end
insert(categories, c)
end
end
end
--[==[
Meant to be called from a module. `data` is a table containing the following fields:
* `lang`: language object for the rhymes;
* `rhymes`: a list of rhymes, each described by an object which specifies the rhyme, optional number of syllables, and
optional decoration fields:
** `rhyme`: the rhyme itself;
** `num_syl`: {nil} or a list of numbers, specifying the number of syllables of the word with this rhyme; optional and
currently used only for categorization; if omitted, defaults to the top-level `num_syl`;
** `separator`: {nil} or the string used to separate this rhyme from the preceding one when displayed; defaults to the
top-level `separator`;
** `q`: {nil} or a list of left regular qualifier strings, displayed directly before the rhyme in question;
** `qq`: {nil} or a list of right regular qualifier strings, displayed directly after the rhyme in question;
** `a`: {nil} or a list of left accent qualifier strings (see [[Module:accent qualifier]]), displayed directly before
the rhyme in question;
** `aa`: {nil} or a list of right accent qualifier strings, displayed directly after the rhyme in question;
** `refs`: {nil} or a list of references or reference specs to add directly after the rhyme; the value of a list item 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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}} or {{cd|<nowiki><ref name="foo" /></nowiki>}})
and/or `group` (the group of the reference, as in {{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section;
** `nocat`: if {true}, suppress categorization for this rhyme only;
* `num_syl`: {nil} or a list of numbers, specifying the number of syllables for all rhymes; optional and currently used
only for categorization; overridable at the individual rhyme level;
* `separator`: {nil} or a string, specifying the separator displayed before all rhymes but the first; by default,
{", "}; overridable at the individual rhyme level;
* `q`: {nil} or a list of overall left regular qualifier strings, displayed before the initial caption;
* `qq`: {nil} or a list of overall right regular qualifier strings, displayed after all rhymes;
* `a`: {nil} or a list of overall left accent qualifier strings (see [[Module:accent qualifier]]), displayed before the
initial caption;
* `aa`: {nil} or a list of right accent qualifier strings, displayed after all rhymes;
* `sort`: {nil} or sort key;
* `caption`: {nil} or string specifying the caption to use, in place of {"Rhymes"}; a colon and space is automatically
added after the caption;
* `nocaption`: if {true}, suppress the caption display;
* `nocat`: if {true}, suppress categorization;
* `force_cat`: if {true}, force categorization even on non-mainspace pages.
If both regular and accent qualifiers on the same side and at the same level are specified, the accent qualifiers
precede the regular qualifiers on both left and right.
'''WARNING''': Destructively modifies the objects inside the `rhymes` field.
Note that the number of syllables is currently used only for categorization; if present, an extra category will
be added such as [[Category:Rhymes:Italian/ino/3 syllables]] in addition to [[Category:Rhymes:Italian/ino]].
]==]
function export.format_rhymes(data)
local langname = data.lang:getFullName()
local parts = {}
local categories = {}
local overall_sep = data.separator or ", "
for i, r in ipairs(data.rhymes) do
local rhyme = r.rhyme
local link, link_cats = make_rhyme_link(data.lang, rhyme, "-" .. rhyme)
if not r.nocat and not data.nocat then
for _, cat in ipairs(link_cats) do
insert(categories, cat)
end
end
if r.qualifiers then
-- FIXME; Added 2026-09-18; remove in a month
error("qualifiers= not allowed here; use q=")
end
if r.q and r.q[1] or r.qq and r.qq[1] or r.a and r.a[1] or r.aa and r.aa[1] or r.refs and r.refs[1] then
link = require(decorations_module).format_decorations {
lang = data.lang,
text = link,
q = r.q,
qq = r.qq,
a = r.a,
aa = r.aa,
refs = r.refs,
}
end
insert(parts, r.separator or i > 1 and overall_sep or "")
insert(parts, link)
if not r.nocat and not data.nocat then
add_syllable_categories(categories, langname, rhyme, r.num_syl or data.num_syl)
end
end
local text = concat(parts)
if not data.nocaption then
text = (data.caption or "韻部") .. ":" .. text
end
if data.qualifiers then
-- FIXME; Added 2026-09-18; remove in a month
error("overall qualifiers= not allowed here; use q=")
end
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
text = require(decorations_module).format_decorations {
lang = data.lang,
text = text,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
if categories[1] then
local cat_text = require(utilities_module).format_categories(categories, data.lang, data.sort, nil,
force_cat or data.force_cat)
text = text .. cat_text
end
return text
end
end
--[==[
Implementation of {{tl|rhymes}}.
]==]
function export.show(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
local lang_param = compat and "lang" or 1
local plain = {}
local boolean = {type = "boolean"}
local params = {
[lang_param] = {required = true, type = "language", default = "en"},
[1 + offset] = {list = true, required = true, disallow_holes = true, default = "aɪmz"},
["caption"] = plain,
["nocaption"] = boolean,
["nocat"] = boolean,
["sort"] = plain,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{
param = "s",
item_dest = "num_syl",
separate_no_index = true,
type = "number",
sublist = true,
},
{group = {"q", "a", "ref"}},
}
local rhymes, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 1 + offset,
term_dest = "rhyme",
track_module = "rhymes",
}
local lang = args[lang_param]
local data = {
lang = lang,
rhymes = rhymes,
num_syl = args.s.default,
caption = args.caption,
nocaption = args.nocaption,
nocat = args.nocat,
sort = args.sort,
q = args.q.default,
qq = args.qq.default,
a = args.a.default,
aa = args.aa.default,
}
return export.format_rhymes(data)
end
--[==[
Implementation of {{tl|rhymes nav}}.
]==]
function export.show_nav(frame)
local args = require(parameters_module).process(
frame:getParent().args,
{
[1] = {required = true, type = "full language", default = "und"},
[2] = {list = true, allow_holes = true},
["nocat"] = {type = "boolean"},
}
)
local lang = args[1]
local langname = lang:getCanonicalName()
local parts = args[2]
-- Create steps
-- FIXME: We should probably use format_categories() in [[Module:utilities]] rather than constructing categories
-- manually.
local categories = {}
-- Here and below, we ignore any cleanup categories coming out of make_rhyme_link() by adding an extra set of parens
-- around the call to make_rhyme_link() to cause the second argument (the categories) to be ignored. {{rhymes nav}}
-- is run on a rhymes page so it's not clear we want the page to be added to any such categories, if they exist.
local steps = {"[[Wiktionary:韻部|韻部]]", (make_rhyme_link(lang))}
if #parts > 0 then
local last = parts[#parts]
parts[#parts] = nil
local prefix = ""
for i, part in ipairs(parts) do
prefix = prefix .. part
parts[i] = prefix
end
for _, part in ipairs(parts) do
insert(steps, (make_rhyme_link(lang, part .. "-", "-" .. part .. "-")))
end
if last == "-" then
insert(steps, (make_rhyme_link(lang, prefix, "-" .. prefix)))
insert(categories, "[[Category:" .. langname .. "韻部" .. (prefix == "" and "" or "/" .. prefix .. "-") .. "| ]]")
elseif mw.title.getCurrentTitle().text == langname .. "/" .. prefix .. last .. "-" then
insert(steps, (make_rhyme_link(lang, prefix .. last .. "-", "-" .. prefix .. last .. "-")))
insert(categories, "[[Category:" .. langname .. "韻部/" .. prefix .. last .. "-|-]]")
else
insert(steps, (make_rhyme_link(lang, prefix .. last, "-" .. prefix .. last)))
insert(categories, "[[Category:" .. langname .. "韻部" .. (prefix == "" and "" or "/" .. prefix .. "-") .. "|" .. last .. "]]")
end
elseif lang:getCode() ~= "und" then
insert(categories, "[[Category:" .. langname .. "韻部| ]]")
end
if mw.title.getCurrentTitle().nsText == "Rhymes" then
frame:callParserFunction("DISPLAYTITLE",
mw.title.getCurrentTitle().fullText:gsub(
"/(.+)$",
function (rhyme)
return "/" .. (tag_rhyme(rhyme, lang)) -- ignore cleanup categories
end))
end
local templateStyles = require(TemplateStyles_module)(rhymes_styles_css_module)
local ol = mw.html.create("ol")
for _, step in ipairs(steps) do
ol:node(mw.html.create("li"):wikitext(step))
end
local div = mw.html.create("div")
:attr("role", "navigation")
:attr("aria-label", "Breadcrumb")
:addClass("ts-rhymesBreadcrumbs")
:node(ol)
local formatted_cats = args.nocat and "" or concat(categories)
return templateStyles .. tostring(div) .. formatted_cats
end
return export
l1ep0p0sa1yllcyqm43nsm4ihlg6sd6
Module:Ru-headword
828
1400905
9909104
9650263
2026-09-29T07:28:14Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909104
Scribunto
text/plain
--[=[
This module implements the headword templates {{ru-noun}}, {{ru-adj}}, {{ru-adv}}, {{ru-noun+}}, etc. The main entry
point is show(), which is meant to be called from one of the above templates. However, {{ru-noun+}} uses the entry
point noun_plus(). When calling show(), the first parameter of the #invoke call is the part of speech. Other
parameters are taken from the parent template call.
The implementations for different types of headwords (different parts of speech) are set in pos_functions[POS] for a
given POS (part of speech).
]=]--
local export = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local lang = require("Module:languages").getByCode("ru")
local langname = lang:getCanonicalName()
local com = require("Module:ru-common")
local m_links = require("Module:links")
local m_table_tools = require("Module:table tools")
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local json_module = "Module:JSON"
local headword_module = "Module:headword"
local headword_utilities_module = "Module:headword utilities"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local m_string_utilities = require_when_needed(string_utilities_module)
local m_table = require_when_needed(table_module)
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local boolean_param = {type = "boolean"}
local list_param = {list = true, disallow_holes = true}
local list_comp = {list = "comp", disallow_holes = true}
local list_sup = {list = "sup", disallow_holes = true}
local u = m_string_utilities.char
local rfind = m_string_utilities.find
local rsubn = m_string_utilities.gsub
local rmatch = m_string_utilities.match
local rsplit = m_string_utilities.split
local concat = table.concat
local insert = table.insert
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local IRREGMARKER = "△"
local HYPMARKER = "⟐"
-- Forward references
local do_noun
local AC = u(0x0301) -- acute = ́
local pos_functions = {}
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
local function track(page)
require("Module:debug/track")("ru-headword/" .. page)
return true
end
-- Clone args while also assigning nil to empty strings.
local function clone_args(in_args)
local args = {}
for pname, param in pairs(in_args) do
if param == "" then args[pname] = nil
else args[pname] = param
end
end
return args
end
local function make_qualifier_text(text)
return require("Module:qualifier").format_qualifier(text)
end
-- Split a list of "RUSSIAN" or "RUSSIAN//TRANSLIT" strings into a list of {RUSSIAN, TRANSLIT} objects.
local function split_list_into_russian_tr(list)
local splitlist = {}
for _, item in ipairs(list) do
insert(splitlist, com.split_russian_tr(item, "dopair"))
end
return splitlist
end
local function check_if_accent_needed(termobj, data, is_head)
local val = termobj.term
local val_no_links = m_links.remove_links(val)
if com.needs_accents(val_no_links) then
if not termobj.unknown_stress and not termobj.nostress then
error("Stress must be supplied using an acute accent or ё: '" .. val_no_links .. "' (use inline modifier <unknown_stress:1> if stress is unknown and <nostress:1> if a multisyllable word really has no stress, as in ''до́ смерти'')")
end
if not termobj.nostress then
local pos = data.pos_category
insert(data.categories, langname .. pos .. "詞條重音請求")
end
end
if com.is_multi_stressed(val_no_links) then
error("Multi-stressed form '" .. val_no_links .. "' not allowed")
end
end
-- Parse the forms of an inflection. The raw arguments are specified in `forms`, a list of forms which are parsed for
-- inline modifiers. Multiple comma-separated values are allowed.
local function parse_inflection_forms(data, forms, field, is_head)
local retobjs = m_headword_utilities.parse_term_list_with_modifiers {
forms = forms,
paramname = field,
splitchar = ",",
is_head = is_head,
include_mods = {
"tr",
{"nostress", {type = "boolean"}},
{"unknown_stress", {type = "boolean"}},
},
}
for _, retobj in ipairs(retobjs) do
if is_head and retobj.term == "+" then
retobj.term = data.pagename
end
check_if_accent_needed(retobj, data, is_head)
if is_head then
-- Catch errors in arguments where headword doesn't match page title; for the moment, do only with
-- tracking.
local head_no_links = m_links.remove_links(retobj.term)
local head_noaccent = com.remove_accents(head_no_links)
if head_noaccent ~= data.pagename then
track("bad-headword")
--error("Headword " .. term .. " doesn't match pagename " .. data.pagename)
end
end
end
return retobjs
end
-- Parse an inflection. The raw arguments come from `args[field]`, which is parsed for inline modifiers. Multiple
-- comma-separated values are allowed.
local function parse_inflection(data, args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return parse_inflection_forms(data, args[argfield], field, is_head)
end
-- Add a full inflection (e.g. genitive singular of nouns, abstract noun of adjectives) to `data.inflections`. `label`
-- is the label of the inflection (e.g. "abstract noun"), which can have <<...>> glossary references. `terms` is a list
-- of term objects. `accel_form` is the accelerator form (e.g. "gen|s" for genitive singular) of the inflection, or nil
-- to add no accelerator. `accel_pos` is the part of speech of the inflection, if different from the lemma.
--
-- This is a wrapper around insert_inflection() in [[Module:headword utilities]], but handles hypothetical markers in
-- the term (which are converted into the `hypothetical` face) as well as the fact that the accelerator spec may differ
-- from term to term if we need to specify a target (i.e. the value of |head= used in {{head}} or similar) that's
-- different from the term itself. This happens in particular in comparative forms, where the term reads e.g.
-- "([[покраснее|по]])[[краснее|красне́е]]" but we want the target to be just красне́е.
local function insert_inflection(data, terms, label, accel_form, accel_pos)
if not terms[1] then
return
end
for _, termobj in ipairs(terms) do
local term = termobj.term
local tr = termobj.tr
local sawhyp_term, sawhyp_tr
term, sawhyp_term = rsubb(term, HYPMARKER, "")
if tr then
tr, sawhyp_tr = rsubb(tr, HYPMARKER, "")
end
local accel
local target_term = termobj.target_term or term
-- If the target was given without a translit, don't fall back to the term translit because the target itself
-- may be different from the term.
local target_tr = termobj.target_tr or not termobj.target_term and tr or nil
if accel_form then
local lemmas = {}
local lemma_translits = {}
for i, headobj in ipairs(data.heads) do
lemmas[i] = headobj.term
lemma_translits[i] = headobj.tr
end
accel = {
form = accel_form, lemma = lemmas, lemma_translit = lemma_translits, pos = accel_pos,
target = target_term, translit = target_tr
}
end
termobj.term = term
-- Currently we don't display translits for inflections, so null out any manual translit. If we want to change
-- this, we need to set enable_auto_translit in the call to insert_inflection().
termobj.tr = nil
termobj.face = (sawhyp_term or sawhyp_tr) and "hypothetical" or nil
termobj.accel = accel
if com.needs_accents(m_links.remove_links(target_term)) then
local pos = data.pos_category
insert(data.categories, langname .. pos .. "詞條重音請求")
end
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
}
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. Multiple comma-separated values are allowed. `label` is the
-- label that the inflections are given; sections enclosed in <<...>> are linked to the glossary. `accel_form` is the
-- accelerator form (or nil), and `accel_pos` if specified overrides the accelerator part of speech. Note that
-- [[Module:headword utilities]] provides a parse_and_insert_inflection() that is usually sufficient, but in our case
-- we do a bunch of additional processing when inserting inflections, so we do the parsing and inserting separately
-- using functions defined above.
local function parse_and_insert_inflection(data, args, field, label, accel_form, accel_pos)
local terms = parse_inflection(data, args, field)
insert_inflection(data, terms, label, accel_form, accel_pos)
end
local function get_split_decomposed_heads(data)
if not data.split_decomposed_heads then
data.split_decomposed_heads = com.split_translit_of_duplicate_termobjs_and_decompose(data.heads)
end
return data.split_decomposed_heads
end
local function add_common_all_pos_params(params)
params["notrcat"] = boolean_param -- don't add 'irregular pronunciations' tracking category
params["pagename"] = true
params["id"] = true
return params
end
-- The main entry point.
function export.show(frame)
local iparams = {
[1] = {required = true, desc = "part of speech"},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local poscat = iargs[1]
local params = add_common_all_pos_params {
[1] = {list = "head", disallow_holes = true},
["tr"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <tr:...> inline modifier on the headword"},
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local data = {
lang = lang,
no_redundant_head_cat = true,
pos_category = poscat,
categories = {},
genders = {},
inflections = {},
id = args.id,
pagename = pagename,
}
data.heads = parse_inflection(data, args, {1, "head"}, "is_head")
if not data.heads[1] then
data.heads = {{term = pagename}}
end
local unknown_stress = false
for i, headobj in ipairs(data.heads) do
unknown_stress = unknown_stress or headobj.unknown_stress
local tr = headobj.tr
if tr then
tr = com.decompose(tr)
local tr_gen = com.translit_no_links(headobj.term)
if tr == tr_gen then
track("redundant-translit")
elseif not args.notrcat then
insert(data.categories, "發音不規則的" .. langname .. "詞")
end
end
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if unknown_stress then
track("unknown-stress")
if not pos_functions[poscat] or not pos_functions[poscat].no_insert_unknown_stress_label or
not pos_functions[poscat].no_insert_unknown_stress_label(args) then
insert(data.inflections, {label = "unknown stress"})
end
end
return require(headword_module).full_headword(data)
end
-- Common params shared by {{ru-noun}} and {{ru-noun+}}.
local function add_common_noun_params(params)
params["unknown_decl"] = boolean_param -- declension unknown
params["unknown_pattern"] = boolean_param -- stress pattern (a, b, b', ...) unknown
params["unknown_gender"] = boolean_param -- gender unknown
params["unknown_animacy"] = boolean_param -- animacy unknown
params["unknown_stress"] = boolean_param -- stress unknown
params["f"] = list_param -- feminine equivalent(s)
params["m"] = list_param -- masculine equivalent(s)
params["adj"] = list_param -- relational adjective(s)
params["poss"] = list_param -- possessive adjective(s)
params["coll"] = list_param -- collective(s)
params["dim"] = list_param -- diminutive(s)
params["aug"] = list_param -- augmentative(s)
params["hypocor"] = list_param -- hypocorism(s)
params["pej"] = list_param -- pejorative(s)
params["pejcoll"] = list_param -- pejorative collective(s)
params["clip"] = list_param -- clipping(s)
params["expr"] = list_param -- expressive term(s)
params["dem"] = list_param -- demonym(s)
params["fdem"] = list_param -- female demonym(s)
return params
end
-- Implementation of {{ru-noun+}}. We should redo this implementation along the lines of {{uk-ndecl}}. For example,
-- instead of existing {{ru-noun-table|[[дви́гатель]]|m|_|[[внутренний|вну́треннего]]|+$|_|[[сгорание|сгора́ния]]|$}}, it
-- should look more like {{ru-ndecl|дви́гатель<M> [[внутренний|вну́треннего]] [[сгорание|сгора́ния]]}}.
function export.noun_plus(frame)
local iparams = {
[1] = {required = true, desc = "part of speech"},
["old"] = boolean_param,
["ndef"] = {},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local poscat = iargs[1]
local params = add_common_noun_params(add_common_all_pos_params {
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true}, -- genders
["notes"] = list_param, -- "footnotes" displayed after headword
})
local parargs = frame:getParent().args
local headword_args, args = require("Module:parameters").process(parargs, params, "return unknown")
args = clone_args(args)
-- default value of n=, used in ru-proper noun+ where ndef=sg is set
args.ndef = args.ndef or iargs.ndef
local m_noun = require("Module:ru-noun")
args = m_noun.do_generate_forms(args, iargs.old)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local data = {
lang = lang,
no_redundant_head_cat = true,
pos_category = poscat,
categories = {},
genders = {},
inflections = {},
id = args.id,
pagename = pagename,
}
-- Do explicit genders using g=, g2=, etc.
data.genders = headword_args.g
-- If none, do inferred or explicit genders taken from declension.
if not data.genders[1] then
local gender_specs = {}
for _, g in ipairs(args.genders) do
insert(gender_specs, {spec = g})
end
data.genders = gender_specs
end
local saw_note = false
-- Given a list of {RU, TR} pairs, where TR may be nil, separate off the footnote symbols from RU and TR; link the
-- remainder if it's not already linked; remove monosyllabic accents (but not from multiword expressions); and
-- return the result in termobj format, i.e. {term = RU, tr = TR}.
local function convert_paired_terms_to_termobjs(paired_list, ishead)
if not paired_list or not paired_list[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, x in ipairs(paired_list) do
local ru, tr = x[1], x[2]
-- separate_notes() just returns the note, but get_notes() adds
-- <sup>...</sup>. We want the former for checking whether the
-- note is nonempty after removing IRREGMARKER (if we use the
-- latter we'll get <sup></sup> in the case of just IRREGMARKER),
-- but the latter when generating the inflectional form.
if not ishead and (rfind(ru, "[%[|%]]") or tr and rfind(tr, "[%[|%]]")) then
track("form-with-link")
end
local ruentry, runotes = m_table_tools.separate_notes(ru)
local sawhyp
runotes = rsub(runotes, IRREGMARKER, "") -- remove note of irregularity
runotes, sawhyp = rsubb(runotes, HYPMARKER, "")
if runotes ~= "" then
saw_note = true
end
runotes = m_table_tools.superscript_notes(runotes)
local trentry, trnotes
if tr then
trentry, trnotes = m_table_tools.separate_notes(tr)
trnotes = rsub(trnotes, IRREGMARKER, "") -- remove note of irregularity
trnotes = m_table_tools.superscript_notes(trnotes)
end
ruentry, trentry = com.remove_monosyllabic_accents(ruentry, trentry)
if sawhyp then
insert(termobjs,
{term = ruentry .. runotes .. HYPMARKER, tr = trentry and trentry .. trnotes .. HYPMARKER or nil}
)
elseif ishead then
insert(termobjs,
{term = ruentry .. runotes, tr = trentry and trentry .. trnotes or nil})
else
local ruspan, trspan
if ruentry == "-" then
ruspan = "-"
elseif rfind(ruentry, "[%[|%]]") then
-- don't add links around a form that's already linked
ruspan = ruentry .. runotes
else
ruspan = "[[" .. ruentry .. "]]" .. runotes
end
if trentry then
trspan = trentry .. trnotes
end
insert(termobjs, {term = ruspan, tr = trspan})
end
end
return com.combine_translit_of_duplicate_termobjs_and_recompose(termobjs)
end
local argsn = args.n or args.ndef
local heads, genitives, plurals, genpls
if argsn == "p" then
heads = convert_paired_terms_to_termobjs(args.nom_pl_linked, "ishead")
genitives = convert_paired_terms_to_termobjs(args.gen_pl)
plurals = {{term = "-"}}
genpls = {{term = "-"}}
else
heads = convert_paired_terms_to_termobjs(args.nom_sg_linked, "ishead")
genitives = convert_paired_terms_to_termobjs(args.gen_sg)
plurals = argsn == "s" and {{term = "-"}} or convert_paired_terms_to_termobjs(args.nom_pl)
genpls = argsn == "s" and {{term = "-"}} or convert_paired_terms_to_termobjs(args.gen_pl)
end
data.heads = heads
if com.any_termobjs_have_translit(data.heads) and not args.notrcat then
insert(data.categories, "發音不規則的" .. langname .. "詞")
end
do_noun(data, headword_args, argsn == "s", genitives, plurals, genpls, poscat)
local notes = headword_args.notes
local notes_segments = {}
if saw_note then
for _, note in ipairs(notes) do
insert(notes_segments, " " .. make_qualifier_text(note))
end
end
local notes_text = concat(notes_segments, "")
return require(headword_module).full_headword(data) .. notes_text
end
-- Implementation of {{ru-noun}} and {{ru-proper noun}}.
local function get_noun_pos(pos)
return {
params = add_common_noun_params({
[2] = {list = "g", disallow_holes = true, required = true, default = "?", type = "genders",
flatten = true}, -- genders
[3] = {list = "gen", disallow_holes = true}, -- genitive singulars, or - for indeclinable
[4] = {list = "pl", disallow_holes = true}, -- nominative plurals
[5] = {list = "genpl", disallow_holes = true}, -- genitive plurals
["altyo"] = boolean_param, -- called from {{ru-noun-alt-ё}} or variants
["manual"] = boolean_param, -- allow manual specification of principal parts
}),
func = function(args, data)
data.genders = args[2]
local genitives = parse_inflection(data, args, {3, "gen"})
local plurals = parse_inflection(data, args, {4, "pl"})
local genpls = parse_inflection(data, args, {5, "genpl"})
local unknown_stress = false
for _, headobj in ipairs(data.heads) do
unknown_stress = unknown_stress or headobj.unknown_stress
end
if not args.altyo and not args.manual and (not genitives[1] or genitives[1].term ~= "-") and
mw.title.getCurrentTitle().nsText == "" and
not args.unknown_decl and not unknown_stress and not args.unknown_stress and
not args.unknown_pattern and not args.unknown_gender and
not args.unknown_animacy then
error("[[Template:ru-noun]] can now only be used with indeclinable and manually-declined nouns; use [[Template:ru-noun+]] instead")
end
do_noun(data, args, pos == "專有名詞", genitives, plurals, genpls, pos)
end,
no_insert_unknown_stress_label = function(args)
return true -- we do it ourselves
end,
}
end
pos_functions["專有名詞"] = get_noun_pos("專有名詞")
pos_functions["代詞"] = get_noun_pos("代詞")
-- Display additional inflection information for a noun.
pos_functions["名詞"] = get_noun_pos("名詞")
-- Guts of {{ru-noun}} and {{ru-noun+}}.
do_noun = function(data, args, no_plural, genitives, plurals, genitive_plurals, pos)
local recognized_genders = {
"", -- not allowed when singular; this is needed because some invariant plural-only words have no gender to speak of
"m",
"f",
"n",
"mf",
"mfbysense",
}
local recognized_animacies = {
"",
"?",
"an",
"in",
}
local recognized_numbers = {
"",
"p",
}
local function insert_if_not_blank(seq, part)
if part ~= "" then
insert(seq, part)
end
end
local singular_genders = {} -- a set
local plural_genders = {} -- a set
-- Generate the allowed gender/number/animacy specs.
for _, number in ipairs(recognized_numbers) do
for _, gender in ipairs(recognized_genders) do
for _, animacy in ipairs(recognized_animacies) do
local set = number == "" and singular_genders or plural_genders
if gender ~= "" or number == "p" then -- disallow blank gender unless plural
local gender_number = {}
insert_if_not_blank(gender_number, gender)
insert_if_not_blank(gender_number, animacy)
insert_if_not_blank(gender_number, number)
local spec = concat(gender_number, "-")
set[spec] = true
end
end
end
end
for _, gspec in ipairs(data.genders) do
local g = gspec.spec
if g == "m" then
g = "m-?"
elseif g == "m-p" then
g = "m-?-p"
elseif g == "f" and plurals[1] ~= "-" and not no_plural then
g = "f-?"
elseif g == "f-p" then
g = "f-?-p"
elseif g == "p" then
g = "?-p"
end
if not singular_genders[g] and not plural_genders[g] and g ~= "?" and g ~= "?-in" and g ~= "?-an" then
error("Unrecognized gender: " .. g)
end
gspec.spec = g
-- Categorize by number
if plural_genders[g] then
if g == "?-p" or g == "an-p" or g == "in-p" then
insert(data.categories, "性別不完全的" .. langname .. "唯複名詞")
end
end
end
-- Add the genitive forms
if genitives[1] and genitives[1].term == "-" then
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折名詞")
else
insert_inflection(data, genitives, "屬格")
end
local plural_only = not not plural_genders[data.genders[1].spec]
-- Add the plural forms.
if genitives[1] and genitives[1].term == "-" then
if plurals[1] or genitive_plurals[1] then
error("Can't specify nominative or genitive plurals of a plural-only term")
end
elseif plural_genders[data.genders[1].spec] then
insert(data.inflections, {label = glossary_link("唯複")})
elseif plurals[1] and plurals[1].term == "-" then
if pos ~= "proper nouns" then
insert(data.inflections, {label = glossary_link("不可數")})
insert(data.categories, langname .. "不可數名詞")
end
else
insert_inflection(data, plurals, "主格複數")
insert_inflection(data, genitive_plurals, "屬格複數")
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; <<..>> in the label is linked to the glossary). `accel_form` is the accelerator form, or nil.
local function handle_infl(field, label, accel_form)
parse_and_insert_inflection(data, args, field, label, accel_form)
end
-- Add the masculine forms; intentionally no accelerator as the masculine forms are lemmas and need manual handling
handle_infl("m", "陽性等價詞")
-- Add the feminine forms
handle_infl("f", "陰性等價詞", "f")
-- Add the relational adjective forms; intentionally no accelerator, need manual handling
handle_infl("adj", "<<關係形容詞>>")
-- Add the possessive adjective forms; intentionally no accelerator, need manual handling
handle_infl("poss", "<<所有格形容詞>>")
-- Add the collective forms; intentionally no accelerator, need manual handling
handle_infl("coll", "<<集合詞>>")
-- Add the diminutive forms
handle_infl("dim", "<<指小詞>>", "指小詞")
-- Add the augmentative forms
handle_infl("aug", "<<指大詞>>", "指大詞")
-- Add the hypocorisms
handle_infl("hypocor", "<<暱稱詞>>", "暱稱詞") -- hypocorism
-- Add the pejorative forms
handle_infl("pej", "<<貶義>>", "貶義詞")
-- Add the pejorative collective forms; intentionally no accelerator, need manual handling
handle_infl("pejcoll", "<<貶義>><<集合>>詞")
-- Add the clippings
handle_infl("clip", "<<截斷形式>>", "截斷形式")
-- Add the expressive terms
handle_infl("expr", "<<expressive>> term", "expressive term")
-- Add the demonyms
handle_infl("dem", "<<居民稱謂詞>>", "居民稱謂詞")
-- Add the female demonyms
handle_infl("fdem", "女性<<居民稱謂詞>>", "女性居民稱謂詞")
if args.unknown_decl then
track("unknown-decl")
insert(data.inflections, {label = "變格未知"})
end
local unknown_stress = false
for _, headobj in ipairs(data.heads) do
unknown_stress = unknown_stress or headobj.unknown_stress
end
if unknown_stress or args.unknown_stress then
track("unknown-stress")
insert(data.inflections, {label = "重音未知"})
end
if args.unknown_pattern then
track("unknown-pattern")
insert(data.inflections, {label = "重音模式未知"})
end
if args.unknown_gender then
track("unknown-gender")
insert(data.inflections, {label = "性別未知"})
end
if args.unknown_animacy then
track("unknown-animacy")
insert(data.inflections, {label = "有生性未知"})
end
end
local function generate_informal_comp(compobj)
local ru, tr = compobj.term, compobj.tr
if rfind(ru, "е́?е$") then
ru, tr = com.strip_ending(ru, tr, "е") -- Cyrillic е
compobj = shallowCopy(compobj)
compobj.term, compobj.tr = com.concat_russian_tr(ru, tr, "й", nil)
return compobj
else
return nil
end
end
local function convert_to_po_variant(compobj)
local ru, tr = compobj.term, compobj.tr
if rfind(ru, "е$") or rfind(ru, "е́?й$") then
ru = "[[по" .. ru .. "|(по)]][[" .. ru .. "]]"
tr = tr and "(po)" .. tr or nil
compobj.term, compobj.tr = ru, tr
end
end
local function convert_to_nai_variant(compobj)
local ru, tr = compobj.term, compobj.tr
if ru:find("^наи") then
-- e.g. наибо́льший, наиме́ньший
return
end
ru = "[[наи" .. ru .. "|(наи)]][[" .. ru .. "]]"
tr = tr and "(nai)" .. tr or nil
compobj.term, compobj.tr = ru, tr
end
local allowed_endings = {
"ый",
"ий",
"о́й",
--old
"ій",
-- last two for adverbs
"о",
"о́",
}
local velar_to_translit = {
["к"] = "k",
["г"] = "g",
["х"] = "x"
}
local velar_to_palatal = {
["к"] = "ч",
["г"] = "ж",
["х"] = "ш",
["k"] = "č",
["g"] = "ž",
["x"] = "š"
}
-- Generate the comparative(s) given the positive(s). `positives` is a list of term objects, with the translit already
-- split and decomposed. `compspecobj` is the term object containing the comparative spec (either + or a spec giving an
-- adjectival accent pattern, such as +c'). If + is given, the default is +a unless the positive is ending-stressed, in
-- which case the default is +b. Return value is a list of term objects.
local function generate_comparative(positives, compspecobj)
local comps = {}
local compspec = compspecobj.term
if compspecobj.tr then
error("Can't specify manual translit with a '+...' comparative spec")
end
if not rfind(compspec, "^%+") then
error("Compspec '" .. compspec .. "' must begin with + in this function")
end
if compspec ~= "+" and not rfind(compspec, "^%+[abc]'*$") then
error("Compspec '" .. compspec .. "' has an illegal format, should be e.g. + or +c''")
end
compspec = rsub(compspec, "^%+", "")
for _, positive in ipairs(positives) do
local ru, tr = positive.term, positive.tr
ru = m_links.remove_links(ru)
local removed_ending = false
for _, allowed_ending in ipairs(allowed_endings) do
if rfind(ru, allowed_ending .. "$") then
if allowed_ending == "о́й" or allowed_ending == "о́" then
if compspec == "a" then
error("Short stress pattern a not allowed with ending-stressed adjectives/adverbs")
elseif compspec == "" then
compspec = "b"
end
end
ru, tr = com.strip_ending(ru, tr, allowed_ending)
removed_ending = true
break
end
end
if not removed_ending then
error("Russian '" .. ru .. "' doesn't end with expected ending")
end
local comp, comptr
if rfind(ru, "[кгх]$") then
local stemru, lastruchar = rmatch(ru, "^(.*)(.)$")
local stemtr, lasttrchar
if tr then
stemtr, lasttrchar = rmatch(tr, "^(.*)(.)$")
if velar_to_translit[lastruchar] ~= lasttrchar then
error("Translit '" .. tr .. "' doesn't end with transliterated equivalent of last char '" ..
lastruchar .. "' of Russian '" .. ru .. "'")
end
end
comp, comptr = com.make_ending_stressed(stemru, stemtr)
comp = comp .. velar_to_palatal[lastruchar] .. "е" -- Cyrillic е
if comptr then
comptr = comptr .. velar_to_palatal[lasttrchar] .. "e" -- Latin e
end
elseif compspec == "" or compspec == "a" then
comp = ru .. "ее" -- Cyrillic ее
if comptr then
comptr = tr .. "ee" -- Latin ee
end
else -- end-stressed comparative, including pattern a'
comp, comptr = com.make_unstressed_once(ru, tr)
comp = comp .. "е́е" -- Cyrillic е́е
if comptr then
comptr = comptr .. "e" .. AC .. "e" -- Latin decomposed ée
end
end
local compobj = shallowCopy(positive)
compobj.term = comp
compobj.tr = comptr
m_headword_utilities.combine_termobj_decorations(compobj, compspecobj)
insert(comps, compobj)
end
return comps
end
-- Meant to be called from a bot
function export.generate_comparative(frame)
local iparams = {
[1] = {required = true, desc = "比較級"},
[2] = {default = "+"},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local comps = iargs[1]
local compspec = iargs[2]
comps = rsplit(comps, ",")
for i, comp in ipairs(comps) do
local ru, tr = com.split_russian_tr(comp)
comps[i] = {term = ru, tr = tr}
end
comps = generate_comparative(comps, {term = compspec})
comps = com.combine_translit_of_duplicate_termobjs_and_recompose(comps)
return require(json_module).toJSON(comps)
end
-- Handle comparative and superlative inflections. If an explicit form is given such as коро́че or красне́е, we add
-- it in a "hacked" format that notes that e.g. покоро́че or покрасне́е is a possible variant. We also generate an
-- informal form in -ей if possible, e.g. красне́й, with по-hacking applied (but no such variant is possible for коро́че).
-- We also handle autogenerating comparatives when specified as + or +b, +c'', etc. (All specifications with an accent
-- pattern are equivalent other than +a.) Finally, we allow and handle periphrastic comparatives noted using "peri".
local function handle_comparatives_and_superlatives(data, args)
local noinf = args.noinf
local comps = parse_inflection(data, args, {2, "comp"})
comps = com.split_translit_of_duplicate_termobjs_and_decompose(comps)
if comps[1] and comps[1].term == "-" then
local nocomp = table.remove(comps, 1)
if comps[1] then
insert_fixed_inflection(data, "不總是有<<比較級>>", nocomp)
else
insert_fixed_inflection(data, "無<<比較級>>", nocomp)
end
track("nocomp")
end
if comps[1] then
local comp_parts = {}
local function insert_comp(compobj)
local informal
if not noinf then
informal = generate_informal_comp(compobj)
if informal then
convert_to_po_variant(informal)
end
end
convert_to_po_variant(compobj)
m_headword_utilities.insert_termobj_combining_duplicates(comp_parts, compobj)
if informal then
m_headword_utilities.insert_termobj_combining_duplicates(comp_parts, informal)
end
end
for _, compobj in ipairs(comps) do
local term = compobj.term
if term == "peri" then
if compobj.tr then
error("Can't specify manual translit with 'peri' comparative spec")
end
for _, positive in ipairs(get_split_decomposed_heads(data)) do
local pericomp = shallowCopy(positive)
pericomp.term, pericomp.tr = com.concat_russian_tr("[[бо́лее]] ", nil, pericomp.term, pericomp.tr)
m_headword_utilities.combine_termobj_decorations(pericomp, compobj)
m_headword_utilities.insert_termobj_combining_duplicates(comp_parts, pericomp)
end
track("pericomp")
elseif rfind(term, "^+") then
local autocomps = generate_comparative(get_split_decomposed_heads(data), compobj)
for _, autocomp in ipairs(autocomps) do
insert_comp(autocomp)
end
else
insert_comp(compobj)
end
end
comp_parts = com.combine_translit_of_duplicate_termobjs_and_recompose(comp_parts)
for _, compobj in ipairs(comp_parts) do
local ru, tr = compobj.term, compobj.tr
-- WARNING: This has intimate knowledge of how convert_to_po_variant() works. To avoid this, we could
-- maintain the un-po-hacked target in each form in comp_parts, but then we'd have to modify
-- com.combine_translit_of_duplicate_termobjs_and_recompose() to preserve the extra target info when
-- combining duplicate forms, or use a map from hacked Russian form to target.
local un_po_hacked_ru = m_links.remove_links(rsub(ru, "^%[%[.-%]%]", ""))
local un_po_hacked_tr = tr and rsub(tr, rsub(tr, "^%(po%)", ""), ", %(po%)", ", ") or nil
compobj.target_term = un_po_hacked_ru
compobj.target_tr = un_po_hacked_tr
end
insert_inflection(data, comp_parts, "<<比較級>>", "比較級")
end
-- Add the superlatives
local sups = parse_inflection(data, args, {3, "sup"})
sups = com.split_translit_of_duplicate_termobjs_and_decompose(sups)
if sups[1] then
local sup_parts = {}
for _, supobj in ipairs(sups) do
local term = supobj.term
if term == "peri" then
if supobj.tr then
error("Can't specify manual translit with 'peri' superlative spec")
end
for _, positive in ipairs(get_split_decomposed_heads(data)) do
local perisup = shallowCopy(positive)
perisup.term, perisup.tr = com.concat_russian_tr("[[са́мый]] ", nil, perisup.term, perisup.tr)
m_headword_utilities.combine_termobj_decorations(perisup, supobj)
m_headword_utilities.insert_termobj_combining_duplicates(sup_parts, perisup)
end
track("perisup")
else
convert_to_nai_variant(supobj)
m_headword_utilities.insert_termobj_combining_duplicates(sup_parts, supobj)
end
end
sup_parts = com.combine_translit_of_duplicate_termobjs_and_recompose(sup_parts)
for _, supobj in ipairs(sup_parts) do
local ru, tr = supobj.term, supobj.tr
-- WARNING: This has intimate knowledge of how convert_to_nai_variant() works. To avoid this, we could
-- maintain the un-nai-hacked target in each form in sup_parts, but then we'd have to modify
-- com.combine_translit_of_duplicate_termobjs_and_recompose() to preserve the extra target info when
-- combining duplicate forms, or use a map from hacked Russian form to target.
local un_nai_hacked_ru = m_links.remove_links(rsub(ru, "^%[%[.-%]%]", ""))
local un_nai_hacked_tr = tr and rsub(tr, rsub(tr, "^%(nai%)", ""), ", %(nai%)", ", ") or nil
supobj.target_term = un_nai_hacked_ru
supobj.target_tr = un_nai_hacked_tr
end
insert_inflection(data, sup_parts, "<<最高級>>", "最高級")
end
end
-- Display additional inflection information for an adjective
pos_functions["形容詞"] = {
params = {
["indecl"] = boolean_param, --indeclinable
["noinf"] = boolean_param, --suppress informal comparatives
[2] = list_comp, --comparative(s)
[3] = list_sup, --superlative(s)
["adv"] = list_param, --corresponding adverb(s)
["absn"] = list_param, --corresponding abstract noun(s)
["dim"] = list_param, --corresponding diminutive(s)
["aug"] = list_param, --corresponding augmentative(s)
["intens"] = list_param, --corresponding intensive(s)
["pej"] = list_param, --corresponding pejorative(s)
},
func = function(args, data)
if args.indecl then
insert(data.inflections, {label = "無屈折"})
insert(data.categories, langname .. "無屈折名詞")
end
handle_comparatives_and_superlatives(data, args)
local function parse_and_insert_adj_inflection(field, label, accel_form, accel_pos)
parse_and_insert_inflection(data, args, field, label, accel_form, accel_pos)
end
-- Add the adverbs
parse_and_insert_adj_inflection("adv", "副詞")
-- Add the abstract nouns
local absn_objs = parse_inflection(data, args, "absn")
local saw_plus = false
for _, absnobj in ipairs(absn_objs) do
if absnobj.term == "+" then
saw_plus = true
break
end
end
if saw_plus then
local normalized_absn_objs = {}
absn_objs = com.split_translit_of_duplicate_termobjs_and_decompose(absn_objs)
for _, absnobj in ipairs(absn_objs) do
if absnobj.term == "+" then
if absnobj.tr then
error("Can't specify manual translit with '+' default abstract noun spec")
end
local lemmas = get_split_decomposed_heads(data)
for _, lemma in ipairs(lemmas) do
local ru, tr = lemma.term, lemma.tr
if rfind(ru, "о́?й$") then
error("Can't form default abstract noun of ending-stressed adjective " .. ru)
end
if rfind(ru, "ий$") then
ru, tr = com.strip_ending(ru, tr, "ий")
elseif rfind(ru, "ій$") then
ru, tr = com.strip_ending(ru, tr, "ій")
else
ru, tr = com.strip_ending(ru, tr, "ый")
end
ru, tr = com.concat_russian_tr(ru, tr, "ость", nil)
local lemma_absn = shallowCopy(lemma)
lemma_absn.term, lemma_absn.tr = ru, tr
m_headword_utilities.combine_termobj_decorations(lemma_absn, absnobj)
m_headword_utilities.insert_termobj_combining_duplicates(normalized_absn_objs, lemma_absn)
end
else
m_headword_utilities.insert_termobj_combining_duplicates(normalized_absn_objs, absnobj)
end
end
normalized_absn_objs = com.combine_translit_of_duplicate_termobjs_and_recompose(normalized_absn_objs)
insert_inflection(data, normalized_absn_objs, "抽象名詞", "抽象名詞", "名詞")
else
insert_inflection(data, absn_objs, "抽象名詞", "抽象名詞", "名詞")
end
-- Add the diminutives
parse_and_insert_adj_inflection("dim", "<<指小詞>>", "指小詞")
-- Add the augmentatives
parse_and_insert_adj_inflection("aug", "<<指大詞>>", "指大詞")
-- Add the intensives
parse_and_insert_adj_inflection("intens", "<<intensive>>", "加強") -- intensive
-- Add the pejoratives
parse_and_insert_adj_inflection("pej", "<<貶義>>", "貶義詞")
end
}
-- Display additional inflection information for an adverb
pos_functions["副詞"] = {
params = {
["noinf"] = boolean_param, --suppress informal comparatives
[2] = list_comp, --comparative(s)
[3] = list_sup, --superlative(s)
["adj"] = list_param, --corresponding adjective(s)
["dim"] = list_param, --corresponding diminutive(s)
["aug"] = list_param, --corresponding augmentative(s)
["pej"] = list_param, --corresponding pejorative(s)
},
func = function(args, data)
local comps = args[2]
handle_comparatives_and_superlatives(data, args)
local function parse_and_insert_adv_inflection(field, label, accel_form)
parse_and_insert_inflection(data, args, field, label, accel_form)
end
-- Add the adjectives
parse_and_insert_adv_inflection("adj", "<<adjective>>", "形容詞")
-- Add the diminutives
parse_and_insert_adv_inflection("dim", "<<diminutive>>", "指小")
-- Add the augmentatives
parse_and_insert_adv_inflection("aug", "<<augmentative>>", "指大")
-- Add the pejoratives
parse_and_insert_adv_inflection("pej", "<<pejorative>>", "貶義")
end
}
-- Display additional inflection information for a verb and verbal combining form
local function get_verb_pos(pos)
return {
params = {
[2] = {required = true, default = "?"}, --aspect
["impf"] = list_param, -- imperfective(s),
["pf"] = list_param, -- perfective(s),
["vn"] = list_param, -- verbal noun(s),
},
func = function(args, data)
local cform = pos == "verbal combining forms"
if cform then
insert(data.categories, "Russian verbs")
end
-- Aspect
local aspect = args[2]
if aspect == "both" then
insert(data.genders, "biasp")
elseif aspect == "pf" or aspect == "impf" or aspect == "biasp" or aspect == "?" then
insert(data.genders, aspect)
else
error("Invalid Russian verb aspect '" .. aspect .. "', should be 'pf', 'impf', 'both', 'biasp' or '?'")
end
local function add_verb_forms(field, label, accel_form, accel_pos)
parse_and_insert_inflection(data, args, field, label, accel_form, accel_pos)
end
-- Add the imperfective forms; intentionally no accelerator, need manual handling
if args.impf[1] and aspect == "impf" then
error("Can't specify imperfective counterparts for an imperfective verb")
end
add_verb_forms("impf", "非完整體")
-- Add the perfective forms; intentionally no accelerator, need manual handling
if args.pf[1] and aspect == "pf" then
error("Can't specify perfective counterparts for a perfective verb")
end
add_verb_forms("pf", "完整體")
-- Add the verbal nouns
add_verb_forms("vn", "動詞性名詞", "動詞性名詞", "名詞")
end,
}
end
pos_functions["動詞"] = get_verb_pos("動詞")
pos_functions["verbal combining forms"] = get_verb_pos("verbal combining forms")
return export
gmbqbya26mk1j7ccp4edp7f501new3t
Template:Initialism of
10
1405310
9909118
9649140
2026-09-29T07:41:19Z
TongcyDai
53191
9909118
wikitext
text/x-wiki
{{ {{#if:{{{lang|}}}|check deprecated lang param usage|no deprecated lang param usage}}|lang={{{lang|}}}|<!--
-->{{#invoke:form of/templates|form_of_t| 的{{glossary|首字母縮略詞}}|cat=首字母縮略詞<!--|conj=or-->}}<!--
-->}}<!--
--><noinclude>{{documentation}}</noinclude>
i841tdtbhyj4eh1dw73s6eahi27jrpr
Module:Names
828
1413518
9909018
9651553
2026-09-29T06:09:28Z
TongcyDai
53191
9909018
Scribunto
text/plain
local export = {}
local m_languages = require("Module:languages")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
-- l10n: enwt 已把 [[Module:pron qualifier]] 更名為 [[Module:decorations]],入口函式
-- format_qualifiers() 也一併更名為 format_decorations()。zhwt 尚未跟進該次更名,因此本模組
-- 沿用舊模組名與舊函式名;日後 zhwt 同步更名時只需改這一行與三處呼叫。
local decorations_module = "Module:pron qualifier"
local parameter_utilities_module = "Module:parameter utilities"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local enlang = m_languages.getByCode("en")
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local u = m_str_utils.char
local function rsub(str, from, to)
return (rsubn(str, from, to))
end
local TEMP_LESS_THAN = u(0xFFF2)
local force_cat = false -- for testing
--[=[
FIXME:
1. from=the Bible (DONE)
2. origin=18th century [DONE]
3. popular= (DONE)
4. varoftype= (DONE)
5. eqtype= [DONE]
6. dimoftype= [DONE]
7. from=de:Elisabeth (same language) (DONE)
8. blendof=, blendof2= [DONE]
9. varform, dimform [DONE]
10. from=English < Latin [DONE]
11. usage=rare -> categorize as rare?
12. dimeq= (also vareq=?) [DONE]
13. fromtype= [DONE]
14. <tr:...> and similar params [DONE]
]=]
-- Used in category code; name types which are full-word end-matching substrings of longer name types (e.g. "surnames"
-- of "male surnames", but not "male surnames" of "female surnames" because "male" only matches a part of the word
-- "female") should follow the longer name.
-- l10n: 分類名一律中文化。本列表會被 [[Module:Category tree/names]] 讀取,改動前請一併檢查該模組。
export.personal_name_types = {
"男性姓氏", "女性姓氏", "通性姓氏", "姓氏",
"父名", "母名",
}
export.personal_name_type_set = m_table.listToSet(export.personal_name_types)
-- l10n: 性別關鍵字中文化。動物類的鍵同時是顯示時的連結目標(「狗」→ [[狗]])與分類名的前綴
-- (「狗」→「狗名」),因此必須是可獨立成詞的中文。
export.given_name_genders = {
["男性"] = {type = "human"},
["女性"] = {type = "human"},
["中性"] = {type = "human", cat = {"男性名字", "女性名字", "中性名字"}},
["性別不明"] = {type = "human", cat = {}, track = true},
["動物"] = {type = "animal", track = true},
["貓"] = {type = "animal"},
["牛"] = {type = "animal"},
["狗"] = {type = "animal"},
["馬"] = {type = "animal"},
["豬"] = {type = "animal"},
}
-- l10n: 條目中仍有大量英文性別值({{given name|xx|male}} 等約四百處),以及 zhwt 舊有的
-- 「未知性別」寫法,一律先對應回上表的中文鍵。
local given_name_gender_aliases = {
["male"] = "男性",
["female"] = "女性",
["unisex"] = "中性",
["unknown"] = "性別不明",
["unknown gender"] = "性別不明",
["unknown-gender"] = "性別不明",
["未知性別"] = "性別不明",
["?"] = "性別不明",
["animal"] = "動物",
["cat"] = "貓",
["cow"] = "牛",
["dog"] = "狗",
["horse"] = "馬",
["pig"] = "豬",
}
-- l10n: {{surname}}/{{patronymic}}/{{matronymic}} 的 type= 是模板內部值,保持英文;顯示與
-- 分類所需的中文名稱在此對照。
local surname_types = {
["surname"] = "姓氏",
["patronymic"] = "父名",
["matronymic"] = "母名",
}
-- l10n: 姓氏類性別值;同時接受 enwt 的英文寫法與 zhwt 既有的中文寫法。
local surname_genders = {
["unknown"] = "性別不明",
["unknown gender"] = "性別不明",
["unknown-gender"] = "性別不明",
["未知性別"] = "性別不明",
["性別不明"] = "性別不明",
["?"] = "性別不明",
["unisex"] = "通性",
["common gender"] = "通性",
["common-gender"] = "通性",
["c"] = "通性",
["通性"] = "通性",
["m"] = "男性",
["male"] = "男性",
["男性"] = "男性",
["f"] = "女性",
["female"] = "女性",
["女性"] = "女性",
}
local function get_given_name_cats(gender, props)
local cats = props.cat
if not cats then
if props.type == "animal" then
cats = {gender .. "名"} -- l10n: 例如「狗名」
else
cats = {gender .. "名字"} -- l10n: 例如「男性名字」
end
end
return cats
end
do
local function do_cat(cat)
if not export.personal_name_type_set[cat] then
export.personal_name_type_set[cat] = true
table.insert(export.personal_name_types, cat)
end
end
for gender, props in pairs(export.given_name_genders) do
local cats = get_given_name_cats(gender, props)
for _, cat in ipairs(cats) do
do_cat(cat .. "指小詞") -- l10n: 中文把「指小詞」接在名稱之後
do_cat(cat .. "指大詞") -- l10n
do_cat(cat)
end
end
do_cat("名字") -- l10n: zhwt 以「名字」作為 given names 的分類名(見下方 insert_cats)
end
-- l10n: {{name translit}} 系列 type= 的合法值,中文化。
local translit_name_type_list = {
"姓氏", "男性名字", "女性名字", "中性名字",
"父名"
}
local function track(page)
require("Module:debug").track("names/" .. page)
end
--[=[
Parse a term and associated properties. This works with parameters of the form 'Karlheinz' or
'Kunigunde<q:medieval, now rare>' or 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' where the modifying properties
are contained in <...> specifications after the term. `term` is the full parameter value including any angle brackets
and colons; `paramname` is the name of the parameter that this value comes from, for error purposes; `deflang` is a
language object used in the return value when the language isn't specified (e.g. in the examples 'Karlheinz' and
'Kunigunde<q:medieval, now rare>' above); `allow_explicit_lang` indicates whether the language can be explicitly given
(e.g. in the examples 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' above).
Normally the return value is a terminfo object that can be passed to full_link() in [[Module:links]]), additionally
with optional fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as the returned
terminfo object. However, if `allow_multiple_terms` is given, multiple comma-separated names can be given in `term`,
and the return value is a list of objects of the form described just above.
]=]
local function parse_term_with_annotations(term, paramname, deflang, allow_explicit_lang, allow_multiple_terms)
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"link", "l", "q", "ref"}},
{param = "eq", convert = function(eqval, parse_err)
return parse_term_with_annotations(eqval, paramname .. ".eq", enlang, false, "allow multiple terms")
end},
}
local function generate_obj(term, parse_err)
local termlang
if allow_explicit_lang then
local actual_term
actual_term, termlang = require(parse_interface_module).parse_term_with_lang {
term = term,
parse_err = parse_err,
paramname = paramname,
}
term = actual_term or term
end
return {
term = term,
lang = termlang or deflang,
}
end
return require(parse_interface_module).parse_inline_modifiers(term, {
param_mods = param_mods,
paramname = paramname,
generate_obj = generate_obj,
splitchar = allow_multiple_terms and "," or nil,
})
end
--[=[
Link a single term. If `do_language_link` is given and a given term's language is English, the link will be constructed
using language_link() in [[Module:links]]; otherwise, with full_link(). `termobj` is an object as returned by
parse_term_with_annotations(), i.e. it is suitable for passing to [[Module:links]] and additionally contains optional
fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as `termobj`).
]=]
local function link_one_term(termobj, do_language_link)
local link
if do_language_link and termobj.lang:getCode() == "en" then
link = m_links.language_link(termobj)
else
link = m_links.full_link(termobj)
end
if termobj.q and termobj.q[1] or termobj.qq and termobj.qq[1] or
termobj.l and termobj.l[1] or termobj.ll and termobj.ll[1] or termobj.refs and termobj.refs[1] then
link = require(decorations_module).format_qualifiers {
lang = termobj.lang,
text = link,
q = termobj.q,
qq = termobj.qq,
l = termobj.l,
ll = termobj.ll,
refs = termobj.refs,
}
end
if termobj.eq then
local eqtext = {}
for _, eqobj in ipairs(termobj.eq) do
table.insert(eqtext, link_one_term(eqobj, true))
end
link = link .. " [=" .. m_table.serialCommaJoin(eqtext, {conj = "或"}) .. "]" -- l10n
end
return link
end
--[=[
Link the terms in `terms`, and join them using the conjunction in `conj` (defaulting to "or"). Joining is done using
serialCommaJoin() in [[Module:table]], so that e.g. two terms are joined as "TERM or TERM" while three terms are joined
as "TERM, TERM or TERM" with special CSS spans before the final "or" to allow an "Oxford comma" to appear if configured
appropriately. (However, if `conj` is the special value ", ", joining is done directly using that value.)
If `include_langname` is given, the language of the first term will be prepended to the joined terms. If
`do_language_link` is given and a given term's language is English, the link will be constructed using language_link()
in [[Module:links]]; otherwise, with full_link(). Each term in `terms` is an object as returned by
parse_term_with_annotations().
]=]
local function join_terms(terms, include_langname, do_language_link, conj)
local links = {}
local langnametext
for _, termobj in ipairs(terms) do
if include_langname and not langnametext then
langnametext = termobj.lang:getCanonicalName() -- l10n: 中文語言名與詞語間不加空格
end
table.insert(links, link_one_term(termobj, do_language_link))
end
local joined_terms
if conj == "," then -- l10n: 「直接以逗號連接」的特殊值改用全角逗號
joined_terms = table.concat(links, conj)
else
joined_terms = m_table.serialCommaJoin(links, {conj = conj or "或"}) -- l10n
end
return (langnametext or "") .. joined_terms
end
--[=[
Gather the parameters for multiple names and link each name using full_link() (for foreign names) or language_link()
(for English names), joining the names using serialCommaJoin() in [[Module:table]] with the conjunction `conj`
(defaulting to "or"). (However, if `conj` is the special value ", ", joining is done directly using that value.)
This can be used, for example, to fetch and join all the masculine equivalent names for a feminine given name. Each
name is specified using parameters beginning with `pname` in `args`, e.g. "m", "m2", "m3", etc. `lang` is a language
object specifying the language of the names (defaulting to English), for use in linking them. If `allow_explicit_lang`
is given, the language of the terms can be specified explicitly by prefixing a term with a language code, e.g.
'sv:Björn' or 'la:[[Nicolaus|Nīcolāī]]'. This function assumes that the parameters have already been parsed by
[[Module:parameters]] and gathered into lists, so that e.g. all "mN" parameters are in a list in args["m"].
]=]
local function join_names(lang, args, pname, conj, allow_explicit_lang)
local termobjs = {}
local do_language_link = false
if not lang then
lang = enlang
do_language_link = true
end
local function process_one_term(term, i)
for _, termobj in ipairs(parse_term_with_annotations(term, pname .. (i == 1 and "" or i), lang,
allow_explicit_lang, "allow multiple terms")) do
table.insert(termobjs, termobj)
end
end
if not args[pname] then
return "", 0
elseif type(args[pname]) == "table" then
for i, term in ipairs(args[pname]) do
process_one_term(term, i)
end
else
process_one_term(args[pname], 1)
end
return join_terms(termobjs, nil, do_language_link, conj or "和"), #termobjs -- l10n
end
local function get_eqtext(args)
local eqsegs = {}
local lastlang = nil
local last_eqseg = {}
local function process_one_term(term, i)
for _, termobj in ipairs(parse_term_with_annotations(term, "eq" .. (i == 1 and "" or i), enlang,
"allow explicit lang", "allow multiple terms")) do
local termlang = termobj.lang:getCode()
if lastlang and lastlang ~= termlang then
if #last_eqseg > 0 then
table.insert(eqsegs, last_eqseg)
end
last_eqseg = {}
end
lastlang = termlang
table.insert(last_eqseg, termobj)
end
end
if type(args.eq) == "table" then
for i, term in ipairs(args.eq) do
process_one_term(term, i)
end
elseif type(args.eq) == "string" then
process_one_term(args.eq, 1)
end
if #last_eqseg > 0 then
table.insert(eqsegs, last_eqseg)
end
local eqtextsegs = {}
for _, eqseg in ipairs(eqsegs) do
table.insert(eqtextsegs, join_terms(eqseg, "include langname"))
end
return m_table.serialCommaJoin(eqtextsegs, {conj = "或"}) -- l10n
end
local function get_fromtext(lang, args)
local catparts = {}
local fromsegs = {}
local i = 1
local function parse_from(from)
local unrecognized = false
local prefix, suffix
-- l10n: from= 的關鍵字與前後綴全部中文化。中文沒有單複數,因此 enwt 的去 -s 處理,以及
-- 「as a X」與「as an X」兩個只差在冠詞的分支,在此都不需要,合併為同一個分支。
if from == "名字" then from = "人名" end -- l10n: zhwt 舊有寫法,等同 given names
if from == "姓氏" or from == "人名" or from == "暱稱" or from == "地名" or from == "普通名詞" or from == "月份名" then
prefix = "轉變自"
suffix = from
table.insert(catparts, from)
elseif from == "父名" or from == "母名" or from == "新詞" or from == "職業" or from == "民族名稱" then
prefix = "來自"
suffix = from
table.insert(catparts, from)
elseif from == "聖經" then
prefix = "來自"
suffix = "聖經"
table.insert(catparts, from)
else
prefix = "源自"
if from:find(":") then
local termobj = parse_term_with_annotations(from, "from" .. (i == 1 and "" or i), lang,
"allow explicit lang")
local fromlangname = ""
if termobj.lang:getCode() ~= lang:getCode() then
-- If name is derived from another name in the same language, don't include lang name after text
-- "from " or create a category like "German male given names derived from German".
local canonical_name = termobj.lang:getCanonicalName()
fromlangname = canonical_name -- l10n: 中文不加空格
table.insert(catparts, canonical_name)
end
suffix = fromlangname .. link_one_term(termobj)
else
-- l10n: 英文靠字尾的 " languages" 先判斷是語系還是語言,因為語系顯示時要加
-- "the";中文兩者顯示完全相同,差別只在該查哪張表,所以依序查語系表和語言表就好,
-- 不必比對字尾。(順帶修掉舊版的錯誤:Lua 模式的字元類是逐位元組比對的,
-- [語语] 這種寫法對多位元組漢字無效,語系永遠判斷失敗也就永遠不會分類。)
if require("Module:families").getByCanonicalName(from) or
m_languages.getByCanonicalName(from, nil, "allow etym") then
table.insert(catparts, from)
else
unrecognized = true
end
suffix = from
end
end
if unrecognized then
track("unrecognized from")
track("unrecognized from/" .. from)
end
return prefix, suffix
end
local last_fromseg = nil
local put = require(parse_utilities_module)
local from_args = args.from or {}
if type(from_args) == "string" then
from_args = {from_args}
end
while from_args[i] do
-- We may have multiple comma-separated items, each of which may have multiple items separated by a
-- space-delimited < sign, each of which may have inline modifiers with embedded commas in them. To handle
-- this correctly, first replace space-delimited < signs with a special character, then split on balanced
-- <...> and [...] signs, then split on comma, then rejoin the stuff between commas. We will then split on
-- TEMP_LESS_THAN (the replacement for space-delimited < signs) and reparse.
local rawfroms = rsub(from_args[i], "%s+<%s+", TEMP_LESS_THAN)
local segments = put.parse_multi_delimiter_balanced_segment_run(rawfroms, {{"<", ">"}, {"[", "]"}})
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
for j, comma_separated_group in ipairs(comma_separated_groups) do
comma_separated_groups[j] = table.concat(comma_separated_group)
end
for _, rawfrom in ipairs(comma_separated_groups) do
local froms = rsplit(rawfrom, TEMP_LESS_THAN)
if #froms == 1 then
local prefix, suffix = parse_from(froms[1])
if last_fromseg and (last_fromseg.has_multiple_froms or last_fromseg.prefix ~= prefix) then
table.insert(fromsegs, last_fromseg)
last_fromseg = nil
end
if not last_fromseg then
last_fromseg = {prefix = prefix, suffixes = {}}
end
table.insert(last_fromseg.suffixes, suffix)
else
if last_fromseg then
table.insert(fromsegs, last_fromseg)
last_fromseg = nil
end
local first_suffixpart = ""
local rest_suffixparts = {}
for j, from in ipairs(froms) do
local prefix, suffix = parse_from(from)
if j == 1 then
first_suffixpart = prefix .. suffix
else
table.insert(rest_suffixparts, prefix .. suffix)
end
end
-- l10n: enwt 作「X [in turn from Y, in turn from Z]」,中文改為「X(再源自Y,再源自Z)」。
local full_suffix = first_suffixpart .. "(再" .. table.concat(rest_suffixparts, ",再") .. ")"
last_fromseg = {prefix = "", has_multiple_froms = true, suffixes = {full_suffix}}
end
end
i = i + 1
end
table.insert(fromsegs, last_fromseg)
local fromtextsegs = {}
for _, fromseg in ipairs(fromsegs) do
table.insert(fromtextsegs, fromseg.prefix .. m_table.serialCommaJoin(fromseg.suffixes, {conj = "或"})) -- l10n
end
return m_table.serialCommaJoin(fromtextsegs, {conj = "或"}), catparts -- l10n
end
local function parse_given_name_genders(genderspec)
genderspec = given_name_gender_aliases[genderspec] or genderspec -- l10n
if export.given_name_genders[genderspec] then -- optimization
return {{
type = genderspec,
props = export.given_name_genders[genderspec],
}}, export.given_name_genders[genderspec].type == "animal"
end
local is_animal = nil
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"l", "q", "ref"}},
{param = "text"}, -- l10n: 中文不輸出冠詞,因此不支援 <article:...>
}
local function generate_obj(term, parse_err)
term = given_name_gender_aliases[term] or term -- l10n
if not export.given_name_genders[term] then
local valid_genders = {}
for k, _ in pairs(export.given_name_genders) do
table.insert(valid_genders, k)
end
table.sort(valid_genders)
parse_err(("Unrecognized gender '%s': valid genders are %s"):format(
term, table.concat(valid_genders, ", ")))
end
return {
type = term,
props = export.given_name_genders[term],
}
end
local retval = require(parse_interface_module).parse_inline_modifiers(genderspec, {
param_mods = param_mods,
paramname = "2",
generate_obj = generate_obj,
splitchar = ",",
})
for _, spec in ipairs(retval) do
local this_is_animal = spec.props.type == "animal"
if is_animal == nil then
is_animal = this_is_animal
elseif is_animal ~= this_is_animal then
error("Can't mix animal and human genders")
end
end
return retval, is_animal
end
-- l10n: 中文不輸出不定冠詞,因此不像 enwt 一併回傳 article。
local function generate_given_name_genders(lang, genders)
local parts = {}
for _, spec in ipairs(genders) do
local text
if spec.text then
-- NOTE: This assumes no % sign in the gender type, which seems safe.
text = spec.text:gsub("%+", spec.type)
else
if spec.props.type == "animal" then
text = "[[" .. spec.type .. "]]"
else
text = spec.type
end
end
if spec.q and spec.q[1] or spec.qq and spec.qq[1] or spec.l and spec.l[1] or spec.ll and spec.ll[1] or
spec.refs and spec.refs[1] then
text = require(decorations_module).format_qualifiers {
lang = lang,
text = text,
q = spec.q,
qq = spec.qq,
l = spec.l,
ll = spec.ll,
refs = spec.refs,
raw = true,
}
end
table.insert(parts, text)
end
return m_table.serialCommaJoin(parts, {conj = "或"}) -- l10n
end
-- The entry point for {{given name}}.
function export.given_name(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
local lang_index = compat and "lang" or 1
local boolean = {type = "boolean"}
local list = {list = true}
-- l10n: zhwt 條目大量使用 from2=、xlit2=、eq2=、dim2= 等編號參數,因此凡是 enwt 改成
-- 單一參數(值內以逗號分隔)的名稱類參數,在此一律保留 list = true;上游的 join_names()、
-- get_eqtext()、get_fromtext() 本來就同時支援字串與列表,逗號分隔的新寫法照樣可用。
local args = require("Module:parameters").process(parent_args, {
[lang_index] = {required = true, type = "language", default = "und"},
["gender"] = {default = "性別不明"}, -- l10n
[1 + offset] = {alias_of = "gender"},
["or"] = true, -- l10n: zhwt 舊有的第二性別參數,等同 enwt 的 gender=甲,乙
["orq"] = true, -- l10n: zhwt 舊有的第二性別修飾語,等同 enwt 的 gender=甲,乙<q:...>
["usage"] = true,
["origin"] = true,
["popular"] = true,
["populartype"] = true,
["meaning"] = list,
["meaningtype"] = true,
["addl"] = true,
["q"] = {alias_of = "addl"}, -- l10n: zhwt 舊有寫法,作用與 addl= 相同
["nocap"] = boolean,
-- initial article: A or An
["A"] = true,
["sort"] = true,
["from"] = list, -- l10n
[2 + offset] = {alias_of = "from", list = false},
["fromtype"] = true,
["xlit"] = list, -- l10n
["eq"] = list, -- l10n
["eqtype"] = true,
["varof"] = list, -- l10n
["varoftype"] = true,
["var"] = {alias_of = "varof", list = true},
["vartype"] = {alias_of = "varoftype"},
["varform"] = list, -- l10n
["varformtype"] = true,
["dimof"] = list, -- l10n
["dimoftype"] = true,
["dim"] = {alias_of = "dimof", list = true},
["dimtype"] = {alias_of = "dimoftype"},
["diminutive"] = {alias_of = "dimof", list = true}, -- l10n: zhwt 舊有別名
["diminutivetype"] = {alias_of = "dimoftype"}, -- l10n: zhwt 舊有別名
["dimform"] = list, -- l10n
["dimformtype"] = true,
["augof"] = list, -- l10n
["augoftype"] = true,
["aug"] = {alias_of = "augof", list = true},
["augtype"] = {alias_of = "augoftype"},
["augmentative"] = {alias_of = "augof", list = true}, -- l10n: zhwt 舊有別名
["augmentativetype"] = {alias_of = "augoftype"}, -- l10n: zhwt 舊有別名
["augform"] = list, -- l10n
["augformtype"] = true,
["clipof"] = list, -- l10n
["clipoftype"] = true,
["blend"] = list, -- l10n
["blendtype"] = true,
["m"] = list, -- l10n
["mtype"] = true,
["f"] = list, -- l10n
["ftype"] = true,
["nocat"] = boolean,
})
local textsegs = {}
local lang = args[lang_index]
-- l10n: 中文在型別詞後不加空格
local function fetch_typetext(param)
return args[param] or ""
end
-- l10n: 把 zhwt 舊有的 or=/orq= 併進 gender=,交給同一個解析器處理
if args["or"] then
args.gender = args.gender .. "," .. args["or"] ..
(args.orq and "<q:" .. args.orq .. ">" or "")
end
local genders, is_animal = parse_given_name_genders(args.gender)
local dimoftext, numdimofs = join_names(lang, args, "dimof")
local augoftext, numaugofs = join_names(lang, args, "augof")
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend") -- formerly the only one that used "and" instead of "or"
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local varformtext = join_names(lang, args, "varform", ",") -- l10n
local dimformtext = join_names(lang, args, "dimform", ",") -- l10n
local augformtext = join_names(lang, args, "augform", ",") -- l10n
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '“' .. meaning .. '”')
end
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "或"}) -- l10n
local eqtext = get_eqtext(args)
local function ins(txt)
table.insert(textsegs, txt)
end
-- l10n: 中文語序與英文相反。英文作「a 〔型別〕 diminutive, 〔轉寫〕, of the 〔性別〕 given
-- name 〔基礎名字〕」,中文作「〔性別〕人名〔基礎名字〕的〔型別〕指小詞,〔轉寫〕」;動物名
-- 則作「〔動物〕的名字」而非英文的「a given name for a 〔動物〕」。中文也沒有單複數,因此
-- enwt 的 given name/given names 之別在此不存在。
ins(generate_given_name_genders(lang, genders))
if is_animal then
ins("的[[名字]]")
else
ins("[[人名]]")
end
if numdimofs > 0 then
if args.dimof[1] == "-" then
dimoftext = ""
end
ins(dimoftext .. "的" .. fetch_typetext("dimoftype") .. "[[指小詞]]")
elseif numaugofs > 0 then
ins(augoftext .. "的" .. fetch_typetext("augoftype") .. "[[指大詞]]")
end
if xlittext ~= "" then
ins("," .. xlittext)
end
-- l10n: 中文的補述子句前面必定已有「人名」等中心語,因此一律以逗號分隔,不需要 enwt 的
-- need_comma 判斷(舊版 zhwt 模組漏了這個判斷,才會出現「轉變自地名用於現代」這類漏逗號)。
local from_catparts = {}
if args.from[1] then
ins("," .. fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
end
ins(textseg)
end
if meaningtext ~= "" then
ins("," .. fetch_typetext("meaningtype") .. "表示" .. meaningtext)
end
if args.origin then
ins(",來自" .. args.origin)
end
if args.usage then
ins(",用於" .. args.usage)
end
if varoftext ~= "" then
ins("," .. varoftext .. "的" .. fetch_typetext("varoftype") .. "變體")
end
if clipoftext ~= "" then
-- l10n: 「截斷形式」與 {{clipping of}} 用詞一致
ins("," .. clipoftext .. "的" .. fetch_typetext("clipoftype") .. "截斷形式")
end
if blendtext ~= "" then
ins("," .. blendtext .. "的" .. fetch_typetext("blendtype") .. "混成詞")
end
if args.popular then
ins("," .. fetch_typetext("populartype") .. "流行" .. args.popular)
end
if mtext ~= "" then
ins("," .. fetch_typetext("mtype") .. "陽性等價詞 " .. mtext)
end
if ftext ~= "" then
ins("," .. fetch_typetext("ftype") .. "陰性等價詞 " .. ftext)
end
if eqtext ~= "" then
ins("," .. fetch_typetext("eqtype") .. "等價於" .. eqtext)
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
if varformtext ~= "" then
ins(";" .. fetch_typetext("varformtype") .. "變體形式 " .. varformtext)
end
if dimformtext ~= "" then
ins(";" .. fetch_typetext("dimformtype") .. "指小詞 " .. dimformtext)
end
if augformtext ~= "" then
ins(";" .. fetch_typetext("augformtype") .. "指大詞 " .. augformtext)
end
-- l10n: 中文不輸出冠詞
local text = "<span class='use-with-mention'>" .. table.concat(textsegs) .. "</span>"
if args.nocat then
return text
end
local categories = {}
local langname = lang:getCanonicalName() -- l10n: 中文語言名與分類名之間不加空格
local function insert_cats(dimaugof)
if dimaugof == "" and genders[1].props.type == "human" then
-- No category such as "English diminutives of given names"
table.insert(categories, langname .. "名字")
end
-- l10n: 中文把「指小詞」接在分類名最後,來源則放在最前面作「來自〔來源〕的……」。
local function insert_cat(cat)
table.insert(categories, langname .. cat .. dimaugof)
for _, catpart in ipairs(from_catparts) do
table.insert(categories, "來自" .. catpart .. "的" .. langname .. cat .. dimaugof)
end
end
for _, spec in ipairs(genders) do
local typ = spec.type
if spec.props.track then
track(typ)
end
local cats = get_given_name_cats(spec.type, spec.props)
for _, cat in ipairs(cats) do
insert_cat(cat)
end
end
end
insert_cats("")
if numdimofs > 0 then
insert_cats("指小詞") -- l10n
elseif numaugofs > 0 then
insert_cats("指大詞") -- l10n
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
-- The entry point for {{surname}}, {{patronymic}} and {{matronymic}}.
function export.surname(frame)
local iargs = require("Module:parameters").process(frame.args, {
["type"] = {required = true, set = {"surname", "patronymic", "matronymic"}},
})
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
if parent_args.dot or parent_args.nodot then
error("dot= and nodot= are no longer supported in [[Template:" .. iargs.type .. "]] because a trailing " ..
"period is no longer added by default; if you want it, add it explicitly after the template")
end
local lang_index = compat and "lang" or 1
local boolean = {type = "boolean"}
local list = {list = true}
local gender_arg = iargs.type == "surname" and "g" or 1 + offset
local adj_arg = iargs.type == "surname" and 1 + offset or 2 + offset
-- l10n: 名稱類參數保留 list = true 的理由同 export.given_name。
local args = require("Module:parameters").process(parent_args, {
[lang_index] = {required = true, type = "language", template_default = "und"},
[gender_arg] = iargs.type == "surname" and list or {required = true, template_default = "unknown"}, -- gender(s)
[adj_arg] = true, -- adjective/qualifier
["usage"] = true,
["origin"] = true,
["popular"] = true,
["populartype"] = true,
["meaning"] = list,
["meaningtype"] = true,
["parent"] = list, -- l10n
["addl"] = true,
["q"] = {alias_of = "addl"}, -- l10n: zhwt 舊有寫法,作用與 addl= 相同
["nocap"] = boolean,
-- initial article: by default A or An (English), a or an (otherwise)
["A"] = true,
["sort"] = true,
["from"] = list, -- l10n
["fromtype"] = true,
["xlit"] = list, -- l10n
["eq"] = list, -- l10n
["eqtype"] = true,
["varof"] = list, -- l10n
["varoftype"] = true,
["var"] = {alias_of = "varof", list = true},
["vartype"] = {alias_of = "varoftype"},
["varform"] = list, -- l10n
["varformtype"] = true,
["clipof"] = list, -- l10n
["clipoftype"] = true,
["blend"] = list, -- l10n
["blendtype"] = true,
["m"] = list, -- l10n
["mtype"] = true,
["f"] = list, -- l10n
["ftype"] = true,
["nocat"] = boolean,
})
local textsegs = {}
local lang = args[lang_index]
-- l10n: 中文在型別詞後不加空格
local function fetch_typetext(param)
return args[param] or ""
end
local saw_male = false
local saw_female = false
local genders = {}
-- l10n: {{surname}} 的 g= 是 list 參數(zhwt 舊條目有 g2= 的用法),
-- {{patronymic}}/{{matronymic}} 的性別則是位置參數,只會是字串。
local gender_specs = args[gender_arg]
if type(gender_specs) == "string" then
gender_specs = {gender_specs}
end
for _, gender_spec in ipairs(gender_specs or {}) do
for _, g in ipairs(require(parse_interface_module).split_on_comma(gender_spec)) do
local canon_g = surname_genders[g] -- l10n
if not canon_g then
error("Unrecognized gender: " .. g)
end
if canon_g == "性別不明" then
track("unknown gender")
elseif canon_g == "通性" then
saw_male = true
saw_female = true
elseif canon_g == "男性" then
saw_male = true
elseif canon_g == "女性" then
saw_female = true
end
table.insert(genders, canon_g)
end
end
local adj = args[adj_arg]
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend", "和") -- l10n
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local parenttext = join_names(lang, args, "parent", nil, "allow explicit lang")
local varformtext = join_names(lang, args, "varform", ",") -- l10n
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '“' .. meaning .. '”') -- l10n: 舊版此處誤用半角引號,統一為全角
end
if parenttext ~= "" then
-- l10n: 中文語序為「〔父母〕之子」
local child = saw_male and not saw_female and "子" or saw_female and not saw_male and "女" or
"子或女"
table.insert(meaningsegs, ("“%s之%s”"):format(parenttext, child))
end
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "或"}) -- l10n
local eqtext = get_eqtext(args)
local function ins(txt)
table.insert(textsegs, txt)
end
ins("<span class='use-with-mention'>")
-- l10n: 中文不輸出冠詞,也不需要 enwt 為了決定 a/an 而做的判斷。from= 子句在中文裡放在
-- 中心語之前,例如「轉變自地名的俄羅斯姓氏」;語言前綴寫法(from=de:Wolf)結尾是拉丁字母,
-- 故在「的」前補一個空格。
local from_catparts = {}
if args.from[1] then
ins(fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
end
ins(textseg .. (args.from[1]:find(":") and " 的" or "的"))
end
if #genders > 0 then
ins(table.concat(genders, "或")) -- l10n
end
if adj then
ins(adj)
end
ins("[[" .. surname_types[iargs.type] .. "]]") -- l10n
if xlittext ~= "" then
ins("," .. xlittext)
end
-- l10n: 補述子句一律以逗號分隔,理由同 export.given_name。
if meaningtext ~= "" then
ins("," .. fetch_typetext("meaningtype") .. "表示" .. meaningtext)
end
if args.origin then
ins(",來自" .. args.origin)
end
if args.usage then
ins(",用於" .. args.usage)
end
if varoftext ~= "" then
ins("," .. varoftext .. "的" .. fetch_typetext("varoftype") .. "變體")
end
if clipoftext ~= "" then
-- l10n: 「截斷形式」與 {{clipping of}} 用詞一致
ins("," .. clipoftext .. "的" .. fetch_typetext("clipoftype") .. "截斷形式")
end
if blendtext ~= "" then
ins("," .. blendtext .. "的" .. fetch_typetext("blendtype") .. "混成詞")
end
if args.popular then
ins("," .. fetch_typetext("populartype") .. "流行" .. args.popular)
end
if mtext ~= "" then
ins("," .. fetch_typetext("mtype") .. "陽性等價詞 " .. mtext)
end
if ftext ~= "" then
ins("," .. fetch_typetext("ftype") .. "陰性等價詞 " .. ftext)
end
if eqtext ~= "" then
ins("," .. fetch_typetext("eqtype") .. "等價於" .. eqtext)
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
if varformtext ~= "" then
ins(";" .. fetch_typetext("varformtype") .. "變體形式 " .. varformtext)
end
ins("</span>")
local text = table.concat(textsegs)
if args.nocat then
return text
end
local categories = {}
local langname = lang:getCanonicalName() -- l10n: 中文語言名與分類名之間不加空格
local nametype = surname_types[iargs.type] -- l10n
local function insert_cats(g)
g = g or ""
table.insert(categories, langname .. g .. nametype)
for _, catpart in ipairs(from_catparts) do
table.insert(categories, "來自" .. catpart .. "的" .. langname .. g .. nametype) -- l10n
end
end
insert_cats(nil)
local function insert_cats_gender(g)
if g == "性別不明" then -- l10n
return
end
if g == "通性" then -- l10n
insert_cats_gender("男性")
insert_cats_gender("女性")
end
insert_cats(g)
end
for _, g in ipairs(genders) do
insert_cats_gender(g)
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
-- The entry point for {{name translit}}, {{name respelling}}, {{name obor}} and {{foreign name}}.
function export.name_translit(frame)
local boolean = {type = "boolean"}
local iargs = require("Module:parameters").process(frame.args, {
["desctext"] = {required = true},
["obor"] = boolean,
["foreign_name"] = boolean,
})
local parent_args = frame:getParent().args
local params = {
[1] = {required = true, type = "language", template_default = "en"},
[2] = {required = true, type = "language", sublist = true, template_default = "ru"},
[3] = {list = true, allow_holes = true},
["type"] = {required = true, set = translit_name_type_list, sublist = true, default = "父名"}, -- l10n
["dim"] = boolean,
["aug"] = boolean,
["nocap"] = boolean,
["addl"] = true,
["sort"] = true,
["pagename"] = true,
["nocat"] = boolean,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "q", "l", "ref"}},
{param = {"xlit", "eq"}},
}
local names, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 3,
track_module = "names/name translit",
disallow_custom_separators = true,
-- Use the first source language as the language of the specified names.
lang = function(args) return args[2][1] end,
sc = "sc.default",
-- We display decorations ourselves so we can display them "raw", as we surround everything with
-- 'use-with-mention' and want to avoid double-tagging.
no_show_decorations = true,
}
local lang = args[1]
local sources = args[2]
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local textsegs = {}
local function ins(txt)
table.insert(textsegs, txt)
end
ins("<span class='use-with-mention'>")
local desctext = iargs.desctext -- l10n: 中文沒有首字母大寫,因此不處理 nocap=
local langsegs = {}
for i, source in ipairs(sources) do
local sourcename = source:getCanonicalName()
local function get_source_link()
local term_to_link = names[1] and names[1].term or pagename
-- We link the language name to either the first specified name or the pagename, in the following
-- circumstances:
-- (1) More than one language was given along with at least one name; or
-- (2) We're handling {{foreign name}} or {{name obor}}, and no name was given.
-- The reason for (1) is that if more than one language was given, we want a link to the name
-- in each language, as the name that's displayed is linked only to the first specified language.
-- However, if only one language was given, linking the language to the name is redundant.
-- The reason for (2) is that {{foreign name}} is often used when the name in the destination language
-- is spelled the same as the name in the source language (e.g. [[Clinton]] or [[Obama]] in Italian),
-- and in that case no name will be explicitly specified but we still want a link to the name in the
-- source language. The reason we restrict this to {{foreign name}} or {{name obor}}, not to
-- {{name translit}} or {{name respelling}}, is that {{name translit}} and {{name respelling}} ought to be
-- used for names spelled differently in the destination language (either transliterated or respelled), so
-- assuming the pagename is the name in the source language is wrong.
if names[1] and #sources > 1 or (iargs.foreign_name or iargs.obor) and not names[1] then
return m_links.language_link{
lang = sources[i], term = term_to_link, alt = sourcename, tr = "-"
}
else
return sourcename
end
end
if i == 1 and not iargs.foreign_name then
-- If at least one name is given, we say "A transliteration of the LANG surname FOO", linking LANG to FOO.
-- Otherwise we say "A transliteration of a LANG surname".
if names[1] then
table.insert(langsegs, get_source_link()) -- l10n: 中文不需要 "the"
else
table.insert(langsegs, sourcename) -- l10n: 中文不需要不定冠詞
end
else
table.insert(langsegs, get_source_link())
end
end
local langseg_text = m_table.serialCommaJoin(langsegs, {conj = "或"}) -- l10n
local augdim_text
if args.dim then
augdim_text = "[[指小詞]]" -- l10n
elseif args.aug then
augdim_text = "[[指大詞]]" -- l10n
else
augdim_text = ""
end
-- l10n: type= 的值本身已是中文(姓氏、男性名字……),不像英文還要把單數形對應成連結形。
local nametype_text = m_table.serialCommaJoin(args["type"], {conj = "和"}) .. augdim_text
if not iargs.foreign_name then
ins(langseg_text)
-- l10n: 未指定名字時,desctext 直接接在類型之後(「俄語姓氏的轉寫」);舊版在這種情況下
-- 會整個漏掉 desctext,只輸出「俄語姓氏」。
ins(nametype_text .. (names[1] and "" or "的" .. desctext))
else
-- l10n: {{foreign name}} 的中文語序是「使用於〔語言〕的〔類型〕」
ins(desctext .. langseg_text .. "的" .. nametype_text)
if names[1] then
ins(",")
end
end
local linked_names = {}
local embedded_comma = false
for _, name in ipairs(names) do
local linked_name = m_links.full_link(name, "term")
if name.q and name.q[1] or name.qq and name.qq[1] or name.l and name.l[1] or name.ll and name.ll[1] or
name.refs and name.refs[1] then
linked_name = require(decorations_module).format_qualifiers {
lang = name.lang,
text = linked_name,
q = name.q,
qq = name.qq,
l = name.l,
ll = name.ll,
refs = name.refs,
raw = true, -- since we put 'use-with-mention' around the entire text
}
end
if name.xlit then
embedded_comma = true
linked_name = linked_name .. "," .. m_links.language_link { lang = enlang, term = name.xlit } -- l10n
end
-- l10n: 中文把 desctext(轉寫/形譯詞……)接在名字之後,例如「Фру́нзе的轉寫」。
if not iargs.foreign_name then
linked_name = linked_name .. "的" .. desctext
end
if name.eq then
embedded_comma = true
linked_name = linked_name .. ",等同於 " .. m_links.language_link { lang = enlang, term = name.eq } -- l10n
end
table.insert(linked_names, linked_name)
end
if embedded_comma then
ins(table.concat(linked_names, ";或")) -- l10n
else
ins(m_table.serialCommaJoin(linked_names, {conj = "或"})) -- l10n
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
ins("</span>")
local text = table.concat(textsegs)
if args.nocat then
return text
end
local categories = {}
local langfullname = lang:getFullName()
-- l10n: 中文分類名把目標語言放在中間(「英語男性名字的漢語譯名」),不像 enwt 統一加在最前面。
for _, nametype in ipairs(args.type) do
local function insert_cats(dimaugof)
local function insert_cats_type(ty)
if ty == "中性名字" then -- l10n
insert_cats_type("男性名字")
insert_cats_type("女性名字")
end
for _, source in ipairs(sources) do
local sourcename = source:getCanonicalName()
table.insert(categories, sourcename .. ty .. dimaugof .. "的" .. langfullname .. "譯名")
table.insert(categories, "派生自" .. sourcename .. "的" .. langfullname .. "詞")
table.insert(categories, "源自" .. sourcename .. "的" .. langfullname .. "借詞")
if iargs.obor then
table.insert(categories, "源自" .. sourcename .. "的" .. langfullname .. "形譯詞")
end
if source:getCode() ~= source:getFullCode() then
-- etymology language
table.insert(categories,
source:getFullName() .. ty .. dimaugof .. "的" .. langfullname .. "譯名")
end
end
end
insert_cats_type(nametype)
end
insert_cats("")
if args.dim then
insert_cats("指小詞") -- l10n
end
if args.aug then
insert_cats("指大詞") -- l10n
end
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
return export
fjo835krv2r2paeyio241gx8oped8cm
9909020
9909018
2026-09-29T06:13:23Z
TongcyDai
53191
9909020
Scribunto
text/plain
local export = {}
local m_languages = require("Module:languages")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local decorations_module = "Module:pron qualifier"
local parameter_utilities_module = "Module:parameter utilities"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local enlang = m_languages.getByCode("en")
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local u = m_str_utils.char
local function rsub(str, from, to)
return (rsubn(str, from, to))
end
local TEMP_LESS_THAN = u(0xFFF2)
local force_cat = false -- for testing
--[=[
FIXME:
1. from=the Bible (DONE)
2. origin=18th century [DONE]
3. popular= (DONE)
4. varoftype= (DONE)
5. eqtype= [DONE]
6. dimoftype= [DONE]
7. from=de:Elisabeth (same language) (DONE)
8. blendof=, blendof2= [DONE]
9. varform, dimform [DONE]
10. from=English < Latin [DONE]
11. usage=rare -> categorize as rare?
12. dimeq= (also vareq=?) [DONE]
13. fromtype= [DONE]
14. <tr:...> and similar params [DONE]
]=]
-- Used in category code; name types which are full-word end-matching substrings of longer name types (e.g. "surnames"
-- of "male surnames", but not "male surnames" of "female surnames" because "male" only matches a part of the word
-- "female") should follow the longer name.
-- l10n: 分類名一律中文化。本列表會被 [[Module:Category tree/names]] 讀取,改動前請一併檢查該模組。
export.personal_name_types = {
"男性姓氏", "女性姓氏", "通性姓氏", "姓氏",
"父名", "母名",
}
export.personal_name_type_set = m_table.listToSet(export.personal_name_types)
-- l10n: 性別關鍵字中文化。動物類的鍵同時是顯示時的連結目標(「狗」→ [[狗]])與分類名的前綴
-- (「狗」→「狗名」),因此必須是可獨立成詞的中文。
export.given_name_genders = {
["男性"] = {type = "human"},
["女性"] = {type = "human"},
["中性"] = {type = "human", cat = {"男性名字", "女性名字", "中性名字"}},
["性別不明"] = {type = "human", cat = {}, track = true},
["動物"] = {type = "animal", track = true},
["貓"] = {type = "animal"},
["牛"] = {type = "animal"},
["狗"] = {type = "animal"},
["馬"] = {type = "animal"},
["豬"] = {type = "animal"},
}
-- l10n: 條目中仍有大量英文性別值({{given name|xx|male}} 等約四百處),以及 zhwt 舊有的
-- 「未知性別」寫法,一律先對應回上表的中文鍵。
local given_name_gender_aliases = {
["male"] = "男性",
["female"] = "女性",
["unisex"] = "中性",
["unknown"] = "性別不明",
["unknown gender"] = "性別不明",
["unknown-gender"] = "性別不明",
["未知性別"] = "性別不明",
["?"] = "性別不明",
["animal"] = "動物",
["cat"] = "貓",
["cow"] = "牛",
["dog"] = "狗",
["horse"] = "馬",
["pig"] = "豬",
}
-- l10n: {{surname}}/{{patronymic}}/{{matronymic}} 的 type= 是模板內部值,保持英文;顯示與
-- 分類所需的中文名稱在此對照。
local surname_types = {
["surname"] = "姓氏",
["patronymic"] = "父名",
["matronymic"] = "母名",
}
-- l10n: 姓氏類性別值;同時接受 enwt 的英文寫法與 zhwt 既有的中文寫法。
local surname_genders = {
["unknown"] = "性別不明",
["unknown gender"] = "性別不明",
["unknown-gender"] = "性別不明",
["未知性別"] = "性別不明",
["性別不明"] = "性別不明",
["?"] = "性別不明",
["unisex"] = "通性",
["common gender"] = "通性",
["common-gender"] = "通性",
["c"] = "通性",
["通性"] = "通性",
["m"] = "男性",
["male"] = "男性",
["男性"] = "男性",
["f"] = "女性",
["female"] = "女性",
["女性"] = "女性",
}
local function get_given_name_cats(gender, props)
local cats = props.cat
if not cats then
if props.type == "animal" then
cats = {gender .. "名"} -- l10n: 例如「狗名」
else
cats = {gender .. "名字"} -- l10n: 例如「男性名字」
end
end
return cats
end
do
local function do_cat(cat)
if not export.personal_name_type_set[cat] then
export.personal_name_type_set[cat] = true
table.insert(export.personal_name_types, cat)
end
end
for gender, props in pairs(export.given_name_genders) do
local cats = get_given_name_cats(gender, props)
for _, cat in ipairs(cats) do
do_cat(cat .. "指小詞") -- l10n: 中文把「指小詞」接在名稱之後
do_cat(cat .. "指大詞") -- l10n
do_cat(cat)
end
end
do_cat("名字") -- l10n: zhwt 以「名字」作為 given names 的分類名(見下方 insert_cats)
end
-- l10n: {{name translit}} 系列 type= 的合法值,中文化。
local translit_name_type_list = {
"姓氏", "男性名字", "女性名字", "中性名字",
"父名"
}
local function track(page)
require("Module:debug").track("names/" .. page)
end
--[=[
Parse a term and associated properties. This works with parameters of the form 'Karlheinz' or
'Kunigunde<q:medieval, now rare>' or 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' where the modifying properties
are contained in <...> specifications after the term. `term` is the full parameter value including any angle brackets
and colons; `paramname` is the name of the parameter that this value comes from, for error purposes; `deflang` is a
language object used in the return value when the language isn't specified (e.g. in the examples 'Karlheinz' and
'Kunigunde<q:medieval, now rare>' above); `allow_explicit_lang` indicates whether the language can be explicitly given
(e.g. in the examples 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' above).
Normally the return value is a terminfo object that can be passed to full_link() in [[Module:links]]), additionally
with optional fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as the returned
terminfo object. However, if `allow_multiple_terms` is given, multiple comma-separated names can be given in `term`,
and the return value is a list of objects of the form described just above.
]=]
local function parse_term_with_annotations(term, paramname, deflang, allow_explicit_lang, allow_multiple_terms)
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"link", "l", "q", "ref"}},
{param = "eq", convert = function(eqval, parse_err)
return parse_term_with_annotations(eqval, paramname .. ".eq", enlang, false, "allow multiple terms")
end},
}
local function generate_obj(term, parse_err)
local termlang
if allow_explicit_lang then
local actual_term
actual_term, termlang = require(parse_interface_module).parse_term_with_lang {
term = term,
parse_err = parse_err,
paramname = paramname,
}
term = actual_term or term
end
return {
term = term,
lang = termlang or deflang,
}
end
return require(parse_interface_module).parse_inline_modifiers(term, {
param_mods = param_mods,
paramname = paramname,
generate_obj = generate_obj,
splitchar = allow_multiple_terms and "," or nil,
})
end
--[=[
Link a single term. If `do_language_link` is given and a given term's language is English, the link will be constructed
using language_link() in [[Module:links]]; otherwise, with full_link(). `termobj` is an object as returned by
parse_term_with_annotations(), i.e. it is suitable for passing to [[Module:links]] and additionally contains optional
fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as `termobj`).
]=]
local function link_one_term(termobj, do_language_link)
local link
if do_language_link and termobj.lang:getCode() == "en" then
link = m_links.language_link(termobj)
else
link = m_links.full_link(termobj)
end
if termobj.q and termobj.q[1] or termobj.qq and termobj.qq[1] or
termobj.l and termobj.l[1] or termobj.ll and termobj.ll[1] or termobj.refs and termobj.refs[1] then
link = require(decorations_module).format_qualifiers {
lang = termobj.lang,
text = link,
q = termobj.q,
qq = termobj.qq,
l = termobj.l,
ll = termobj.ll,
refs = termobj.refs,
}
end
if termobj.eq then
local eqtext = {}
for _, eqobj in ipairs(termobj.eq) do
table.insert(eqtext, link_one_term(eqobj, true))
end
link = link .. " [=" .. m_table.serialCommaJoin(eqtext, {conj = "或"}) .. "]" -- l10n
end
return link
end
--[=[
Link the terms in `terms`, and join them using the conjunction in `conj` (defaulting to "or"). Joining is done using
serialCommaJoin() in [[Module:table]], so that e.g. two terms are joined as "TERM or TERM" while three terms are joined
as "TERM, TERM or TERM" with special CSS spans before the final "or" to allow an "Oxford comma" to appear if configured
appropriately. (However, if `conj` is the special value ", ", joining is done directly using that value.)
If `include_langname` is given, the language of the first term will be prepended to the joined terms. If
`do_language_link` is given and a given term's language is English, the link will be constructed using language_link()
in [[Module:links]]; otherwise, with full_link(). Each term in `terms` is an object as returned by
parse_term_with_annotations().
]=]
local function join_terms(terms, include_langname, do_language_link, conj)
local links = {}
local langnametext
for _, termobj in ipairs(terms) do
if include_langname and not langnametext then
langnametext = termobj.lang:getCanonicalName() -- l10n: 中文語言名與詞語間不加空格
end
table.insert(links, link_one_term(termobj, do_language_link))
end
local joined_terms
if conj == "," then -- l10n: 「直接以逗號連接」的特殊值改用全角逗號
joined_terms = table.concat(links, conj)
else
joined_terms = m_table.serialCommaJoin(links, {conj = conj or "或"}) -- l10n
end
return (langnametext or "") .. joined_terms
end
--[=[
Gather the parameters for multiple names and link each name using full_link() (for foreign names) or language_link()
(for English names), joining the names using serialCommaJoin() in [[Module:table]] with the conjunction `conj`
(defaulting to "or"). (However, if `conj` is the special value ", ", joining is done directly using that value.)
This can be used, for example, to fetch and join all the masculine equivalent names for a feminine given name. Each
name is specified using parameters beginning with `pname` in `args`, e.g. "m", "m2", "m3", etc. `lang` is a language
object specifying the language of the names (defaulting to English), for use in linking them. If `allow_explicit_lang`
is given, the language of the terms can be specified explicitly by prefixing a term with a language code, e.g.
'sv:Björn' or 'la:[[Nicolaus|Nīcolāī]]'. This function assumes that the parameters have already been parsed by
[[Module:parameters]] and gathered into lists, so that e.g. all "mN" parameters are in a list in args["m"].
]=]
local function join_names(lang, args, pname, conj, allow_explicit_lang)
local termobjs = {}
local do_language_link = false
if not lang then
lang = enlang
do_language_link = true
end
local function process_one_term(term, i)
for _, termobj in ipairs(parse_term_with_annotations(term, pname .. (i == 1 and "" or i), lang,
allow_explicit_lang, "allow multiple terms")) do
table.insert(termobjs, termobj)
end
end
if not args[pname] then
return "", 0
elseif type(args[pname]) == "table" then
for i, term in ipairs(args[pname]) do
process_one_term(term, i)
end
else
process_one_term(args[pname], 1)
end
return join_terms(termobjs, nil, do_language_link, conj or "和"), #termobjs -- l10n
end
local function get_eqtext(args)
local eqsegs = {}
local lastlang = nil
local last_eqseg = {}
local function process_one_term(term, i)
for _, termobj in ipairs(parse_term_with_annotations(term, "eq" .. (i == 1 and "" or i), enlang,
"allow explicit lang", "allow multiple terms")) do
local termlang = termobj.lang:getCode()
if lastlang and lastlang ~= termlang then
if #last_eqseg > 0 then
table.insert(eqsegs, last_eqseg)
end
last_eqseg = {}
end
lastlang = termlang
table.insert(last_eqseg, termobj)
end
end
if type(args.eq) == "table" then
for i, term in ipairs(args.eq) do
process_one_term(term, i)
end
elseif type(args.eq) == "string" then
process_one_term(args.eq, 1)
end
if #last_eqseg > 0 then
table.insert(eqsegs, last_eqseg)
end
local eqtextsegs = {}
for _, eqseg in ipairs(eqsegs) do
table.insert(eqtextsegs, join_terms(eqseg, "include langname"))
end
return m_table.serialCommaJoin(eqtextsegs, {conj = "或"}) -- l10n
end
local function get_fromtext(lang, args)
local catparts = {}
local fromsegs = {}
local i = 1
local function parse_from(from)
local unrecognized = false
local prefix, suffix
-- l10n: from= 的關鍵字與前後綴全部中文化。中文沒有單複數,因此 enwt 的去 -s 處理,以及
-- 「as a X」與「as an X」兩個只差在冠詞的分支,在此都不需要,合併為同一個分支。
if from == "名字" then from = "人名" end -- l10n: zhwt 舊有寫法,等同 given names
if from == "姓氏" or from == "人名" or from == "暱稱" or from == "地名" or from == "普通名詞" or from == "月份名" then
prefix = "轉變自"
suffix = from
table.insert(catparts, from)
elseif from == "父名" or from == "母名" or from == "新詞" or from == "職業" or from == "民族名稱" then
prefix = "來自"
suffix = from
table.insert(catparts, from)
elseif from == "聖經" then
prefix = "來自"
suffix = "聖經"
table.insert(catparts, from)
else
prefix = "源自"
if from:find(":") then
local termobj = parse_term_with_annotations(from, "from" .. (i == 1 and "" or i), lang,
"allow explicit lang")
local fromlangname = ""
if termobj.lang:getCode() ~= lang:getCode() then
-- If name is derived from another name in the same language, don't include lang name after text
-- "from " or create a category like "German male given names derived from German".
local canonical_name = termobj.lang:getCanonicalName()
fromlangname = canonical_name -- l10n: 中文不加空格
table.insert(catparts, canonical_name)
end
suffix = fromlangname .. link_one_term(termobj)
else
-- l10n: 英文靠字尾的 " languages" 先判斷是語系還是語言,因為語系顯示時要加
-- "the";中文兩者顯示完全相同,差別只在該查哪張表,所以依序查語系表和語言表就好,
-- 不必比對字尾。(順帶修掉舊版的錯誤:Lua 模式的字元類是逐位元組比對的,
-- [語语] 這種寫法對多位元組漢字無效,語系永遠判斷失敗也就永遠不會分類。)
if require("Module:families").getByCanonicalName(from) or
m_languages.getByCanonicalName(from, nil, "allow etym") then
table.insert(catparts, from)
else
unrecognized = true
end
suffix = from
end
end
if unrecognized then
track("unrecognized from")
track("unrecognized from/" .. from)
end
return prefix, suffix
end
local last_fromseg = nil
local put = require(parse_utilities_module)
local from_args = args.from or {}
if type(from_args) == "string" then
from_args = {from_args}
end
while from_args[i] do
-- We may have multiple comma-separated items, each of which may have multiple items separated by a
-- space-delimited < sign, each of which may have inline modifiers with embedded commas in them. To handle
-- this correctly, first replace space-delimited < signs with a special character, then split on balanced
-- <...> and [...] signs, then split on comma, then rejoin the stuff between commas. We will then split on
-- TEMP_LESS_THAN (the replacement for space-delimited < signs) and reparse.
local rawfroms = rsub(from_args[i], "%s+<%s+", TEMP_LESS_THAN)
local segments = put.parse_multi_delimiter_balanced_segment_run(rawfroms, {{"<", ">"}, {"[", "]"}})
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
for j, comma_separated_group in ipairs(comma_separated_groups) do
comma_separated_groups[j] = table.concat(comma_separated_group)
end
for _, rawfrom in ipairs(comma_separated_groups) do
local froms = rsplit(rawfrom, TEMP_LESS_THAN)
if #froms == 1 then
local prefix, suffix = parse_from(froms[1])
if last_fromseg and (last_fromseg.has_multiple_froms or last_fromseg.prefix ~= prefix) then
table.insert(fromsegs, last_fromseg)
last_fromseg = nil
end
if not last_fromseg then
last_fromseg = {prefix = prefix, suffixes = {}}
end
table.insert(last_fromseg.suffixes, suffix)
else
if last_fromseg then
table.insert(fromsegs, last_fromseg)
last_fromseg = nil
end
local first_suffixpart = ""
local rest_suffixparts = {}
for j, from in ipairs(froms) do
local prefix, suffix = parse_from(from)
if j == 1 then
first_suffixpart = prefix .. suffix
else
table.insert(rest_suffixparts, prefix .. suffix)
end
end
-- l10n: enwt 作「X [in turn from Y, in turn from Z]」,中文改為「X(再源自Y,再源自Z)」。
local full_suffix = first_suffixpart .. "(再" .. table.concat(rest_suffixparts, ",再") .. ")"
last_fromseg = {prefix = "", has_multiple_froms = true, suffixes = {full_suffix}}
end
end
i = i + 1
end
table.insert(fromsegs, last_fromseg)
local fromtextsegs = {}
for _, fromseg in ipairs(fromsegs) do
table.insert(fromtextsegs, fromseg.prefix .. m_table.serialCommaJoin(fromseg.suffixes, {conj = "或"})) -- l10n
end
return m_table.serialCommaJoin(fromtextsegs, {conj = "或"}), catparts -- l10n
end
local function parse_given_name_genders(genderspec)
genderspec = given_name_gender_aliases[genderspec] or genderspec -- l10n
if export.given_name_genders[genderspec] then -- optimization
return {{
type = genderspec,
props = export.given_name_genders[genderspec],
}}, export.given_name_genders[genderspec].type == "animal"
end
local is_animal = nil
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"l", "q", "ref"}},
{param = "text"}, -- l10n: 中文不輸出冠詞,因此不支援 <article:...>
}
local function generate_obj(term, parse_err)
term = given_name_gender_aliases[term] or term -- l10n
if not export.given_name_genders[term] then
local valid_genders = {}
for k, _ in pairs(export.given_name_genders) do
table.insert(valid_genders, k)
end
table.sort(valid_genders)
parse_err(("Unrecognized gender '%s': valid genders are %s"):format(
term, table.concat(valid_genders, ", ")))
end
return {
type = term,
props = export.given_name_genders[term],
}
end
local retval = require(parse_interface_module).parse_inline_modifiers(genderspec, {
param_mods = param_mods,
paramname = "2",
generate_obj = generate_obj,
splitchar = ",",
})
for _, spec in ipairs(retval) do
local this_is_animal = spec.props.type == "animal"
if is_animal == nil then
is_animal = this_is_animal
elseif is_animal ~= this_is_animal then
error("Can't mix animal and human genders")
end
end
return retval, is_animal
end
-- l10n: 中文不輸出不定冠詞,因此不像 enwt 一併回傳 article。
local function generate_given_name_genders(lang, genders)
local parts = {}
for _, spec in ipairs(genders) do
local text
if spec.text then
-- NOTE: This assumes no % sign in the gender type, which seems safe.
text = spec.text:gsub("%+", spec.type)
else
if spec.props.type == "animal" then
text = "[[" .. spec.type .. "]]"
else
text = spec.type
end
end
if spec.q and spec.q[1] or spec.qq and spec.qq[1] or spec.l and spec.l[1] or spec.ll and spec.ll[1] or
spec.refs and spec.refs[1] then
text = require(decorations_module).format_qualifiers {
lang = lang,
text = text,
q = spec.q,
qq = spec.qq,
l = spec.l,
ll = spec.ll,
refs = spec.refs,
raw = true,
}
end
table.insert(parts, text)
end
return m_table.serialCommaJoin(parts, {conj = "或"}) -- l10n
end
-- The entry point for {{given name}}.
function export.given_name(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
local lang_index = compat and "lang" or 1
local boolean = {type = "boolean"}
local list = {list = true}
-- l10n: zhwt 條目大量使用 from2=、xlit2=、eq2=、dim2= 等編號參數,因此凡是 enwt 改成
-- 單一參數(值內以逗號分隔)的名稱類參數,在此一律保留 list = true;上游的 join_names()、
-- get_eqtext()、get_fromtext() 本來就同時支援字串與列表,逗號分隔的新寫法照樣可用。
local args = require("Module:parameters").process(parent_args, {
[lang_index] = {required = true, type = "language", default = "und"},
["gender"] = {default = "性別不明"}, -- l10n
[1 + offset] = {alias_of = "gender"},
["or"] = true, -- l10n: zhwt 舊有的第二性別參數,等同 enwt 的 gender=甲,乙
["orq"] = true, -- l10n: zhwt 舊有的第二性別修飾語,等同 enwt 的 gender=甲,乙<q:...>
["usage"] = true,
["origin"] = true,
["popular"] = true,
["populartype"] = true,
["meaning"] = list,
["meaningtype"] = true,
["addl"] = true,
["q"] = {alias_of = "addl"}, -- l10n: zhwt 舊有寫法,作用與 addl= 相同
["nocap"] = boolean,
-- initial article: A or An
["A"] = true,
["sort"] = true,
["from"] = list, -- l10n
[2 + offset] = {alias_of = "from", list = false},
["fromtype"] = true,
["xlit"] = list, -- l10n
["eq"] = list, -- l10n
["eqtype"] = true,
["varof"] = list, -- l10n
["varoftype"] = true,
["var"] = {alias_of = "varof", list = true},
["vartype"] = {alias_of = "varoftype"},
["varform"] = list, -- l10n
["varformtype"] = true,
["dimof"] = list, -- l10n
["dimoftype"] = true,
["dim"] = {alias_of = "dimof", list = true},
["dimtype"] = {alias_of = "dimoftype"},
["diminutive"] = {alias_of = "dimof", list = true}, -- l10n: zhwt 舊有別名
["diminutivetype"] = {alias_of = "dimoftype"}, -- l10n: zhwt 舊有別名
["dimform"] = list, -- l10n
["dimformtype"] = true,
["augof"] = list, -- l10n
["augoftype"] = true,
["aug"] = {alias_of = "augof", list = true},
["augtype"] = {alias_of = "augoftype"},
["augmentative"] = {alias_of = "augof", list = true}, -- l10n: zhwt 舊有別名
["augmentativetype"] = {alias_of = "augoftype"}, -- l10n: zhwt 舊有別名
["augform"] = list, -- l10n
["augformtype"] = true,
["clipof"] = list, -- l10n
["clipoftype"] = true,
["blend"] = list, -- l10n
["blendtype"] = true,
["m"] = list, -- l10n
["mtype"] = true,
["f"] = list, -- l10n
["ftype"] = true,
["nocat"] = boolean,
})
local textsegs = {}
local lang = args[lang_index]
-- l10n: 中文在型別詞後不加空格
local function fetch_typetext(param)
return args[param] or ""
end
-- l10n: 把 zhwt 舊有的 or=/orq= 併進 gender=,交給同一個解析器處理
if args["or"] then
args.gender = args.gender .. "," .. args["or"] ..
(args.orq and "<q:" .. args.orq .. ">" or "")
end
local genders, is_animal = parse_given_name_genders(args.gender)
local dimoftext, numdimofs = join_names(lang, args, "dimof")
local augoftext, numaugofs = join_names(lang, args, "augof")
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend") -- formerly the only one that used "and" instead of "or"
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local varformtext = join_names(lang, args, "varform", ",") -- l10n
local dimformtext = join_names(lang, args, "dimform", ",") -- l10n
local augformtext = join_names(lang, args, "augform", ",") -- l10n
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '“' .. meaning .. '”')
end
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "或"}) -- l10n
local eqtext = get_eqtext(args)
local function ins(txt)
table.insert(textsegs, txt)
end
-- l10n: 中文語序與英文相反。英文作「a 〔型別〕 diminutive, 〔轉寫〕, of the 〔性別〕 given
-- name 〔基礎名字〕」,中文作「〔性別〕人名〔基礎名字〕的〔型別〕指小詞,〔轉寫〕」;動物名
-- 則作「〔動物〕的名字」而非英文的「a given name for a 〔動物〕」。中文也沒有單複數,因此
-- enwt 的 given name/given names 之別在此不存在。
ins(generate_given_name_genders(lang, genders))
if is_animal then
ins("的[[名字]]")
else
ins("[[人名]]")
end
if numdimofs > 0 then
if args.dimof[1] == "-" then
dimoftext = ""
end
ins(dimoftext .. "的" .. fetch_typetext("dimoftype") .. "[[指小詞]]")
elseif numaugofs > 0 then
ins(augoftext .. "的" .. fetch_typetext("augoftype") .. "[[指大詞]]")
end
if xlittext ~= "" then
ins("," .. xlittext)
end
-- l10n: 中文的補述子句前面必定已有「人名」等中心語,因此一律以逗號分隔,不需要 enwt 的
-- need_comma 判斷(舊版 zhwt 模組漏了這個判斷,才會出現「轉變自地名用於現代」這類漏逗號)。
local from_catparts = {}
if args.from[1] then
ins("," .. fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
end
ins(textseg)
end
if meaningtext ~= "" then
ins("," .. fetch_typetext("meaningtype") .. "表示" .. meaningtext)
end
if args.origin then
ins(",來自" .. args.origin)
end
if args.usage then
ins(",用於" .. args.usage)
end
if varoftext ~= "" then
ins("," .. varoftext .. "的" .. fetch_typetext("varoftype") .. "變體")
end
if clipoftext ~= "" then
-- l10n: 「截斷形式」與 {{clipping of}} 用詞一致
ins("," .. clipoftext .. "的" .. fetch_typetext("clipoftype") .. "截斷形式")
end
if blendtext ~= "" then
ins("," .. blendtext .. "的" .. fetch_typetext("blendtype") .. "混成詞")
end
if args.popular then
ins("," .. fetch_typetext("populartype") .. "流行" .. args.popular)
end
if mtext ~= "" then
ins("," .. fetch_typetext("mtype") .. "陽性等價詞 " .. mtext)
end
if ftext ~= "" then
ins("," .. fetch_typetext("ftype") .. "陰性等價詞 " .. ftext)
end
if eqtext ~= "" then
ins("," .. fetch_typetext("eqtype") .. "等價於" .. eqtext)
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
if varformtext ~= "" then
ins(";" .. fetch_typetext("varformtype") .. "變體形式 " .. varformtext)
end
if dimformtext ~= "" then
ins(";" .. fetch_typetext("dimformtype") .. "指小詞 " .. dimformtext)
end
if augformtext ~= "" then
ins(";" .. fetch_typetext("augformtype") .. "指大詞 " .. augformtext)
end
-- l10n: 中文不輸出冠詞
local text = "<span class='use-with-mention'>" .. table.concat(textsegs) .. "</span>"
if args.nocat then
return text
end
local categories = {}
local langname = lang:getCanonicalName() -- l10n: 中文語言名與分類名之間不加空格
local function insert_cats(dimaugof)
if dimaugof == "" and genders[1].props.type == "human" then
-- No category such as "English diminutives of given names"
table.insert(categories, langname .. "名字")
end
-- l10n: 中文把「指小詞」接在分類名最後,來源則放在最前面作「來自〔來源〕的……」。
local function insert_cat(cat)
table.insert(categories, langname .. cat .. dimaugof)
for _, catpart in ipairs(from_catparts) do
table.insert(categories, "來自" .. catpart .. "的" .. langname .. cat .. dimaugof)
end
end
for _, spec in ipairs(genders) do
local typ = spec.type
if spec.props.track then
track(typ)
end
local cats = get_given_name_cats(spec.type, spec.props)
for _, cat in ipairs(cats) do
insert_cat(cat)
end
end
end
insert_cats("")
if numdimofs > 0 then
insert_cats("指小詞") -- l10n
elseif numaugofs > 0 then
insert_cats("指大詞") -- l10n
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
-- The entry point for {{surname}}, {{patronymic}} and {{matronymic}}.
function export.surname(frame)
local iargs = require("Module:parameters").process(frame.args, {
["type"] = {required = true, set = {"surname", "patronymic", "matronymic"}},
})
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
if parent_args.dot or parent_args.nodot then
error("dot= and nodot= are no longer supported in [[Template:" .. iargs.type .. "]] because a trailing " ..
"period is no longer added by default; if you want it, add it explicitly after the template")
end
local lang_index = compat and "lang" or 1
local boolean = {type = "boolean"}
local list = {list = true}
local gender_arg = iargs.type == "surname" and "g" or 1 + offset
local adj_arg = iargs.type == "surname" and 1 + offset or 2 + offset
-- l10n: 名稱類參數保留 list = true 的理由同 export.given_name。
local args = require("Module:parameters").process(parent_args, {
[lang_index] = {required = true, type = "language", template_default = "und"},
[gender_arg] = iargs.type == "surname" and list or {required = true, template_default = "unknown"}, -- gender(s)
[adj_arg] = true, -- adjective/qualifier
["usage"] = true,
["origin"] = true,
["popular"] = true,
["populartype"] = true,
["meaning"] = list,
["meaningtype"] = true,
["parent"] = list, -- l10n
["addl"] = true,
["q"] = {alias_of = "addl"}, -- l10n: zhwt 舊有寫法,作用與 addl= 相同
["nocap"] = boolean,
-- initial article: by default A or An (English), a or an (otherwise)
["A"] = true,
["sort"] = true,
["from"] = list, -- l10n
["fromtype"] = true,
["xlit"] = list, -- l10n
["eq"] = list, -- l10n
["eqtype"] = true,
["varof"] = list, -- l10n
["varoftype"] = true,
["var"] = {alias_of = "varof", list = true},
["vartype"] = {alias_of = "varoftype"},
["varform"] = list, -- l10n
["varformtype"] = true,
["clipof"] = list, -- l10n
["clipoftype"] = true,
["blend"] = list, -- l10n
["blendtype"] = true,
["m"] = list, -- l10n
["mtype"] = true,
["f"] = list, -- l10n
["ftype"] = true,
["nocat"] = boolean,
})
local textsegs = {}
local lang = args[lang_index]
-- l10n: 中文在型別詞後不加空格
local function fetch_typetext(param)
return args[param] or ""
end
local saw_male = false
local saw_female = false
local genders = {}
-- l10n: {{surname}} 的 g= 是 list 參數(zhwt 舊條目有 g2= 的用法),
-- {{patronymic}}/{{matronymic}} 的性別則是位置參數,只會是字串。
local gender_specs = args[gender_arg]
if type(gender_specs) == "string" then
gender_specs = {gender_specs}
end
for _, gender_spec in ipairs(gender_specs or {}) do
for _, g in ipairs(require(parse_interface_module).split_on_comma(gender_spec)) do
local canon_g = surname_genders[g] -- l10n
if not canon_g then
error("Unrecognized gender: " .. g)
end
if canon_g == "性別不明" then
track("unknown gender")
elseif canon_g == "通性" then
saw_male = true
saw_female = true
elseif canon_g == "男性" then
saw_male = true
elseif canon_g == "女性" then
saw_female = true
end
table.insert(genders, canon_g)
end
end
local adj = args[adj_arg]
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend", "和") -- l10n
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local parenttext = join_names(lang, args, "parent", nil, "allow explicit lang")
local varformtext = join_names(lang, args, "varform", ",") -- l10n
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '“' .. meaning .. '”') -- l10n: 舊版此處誤用半角引號,統一為全角
end
if parenttext ~= "" then
-- l10n: 中文語序為「〔父母〕之子」
local child = saw_male and not saw_female and "子" or saw_female and not saw_male and "女" or
"子或女"
table.insert(meaningsegs, ("“%s之%s”"):format(parenttext, child))
end
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "或"}) -- l10n
local eqtext = get_eqtext(args)
local function ins(txt)
table.insert(textsegs, txt)
end
ins("<span class='use-with-mention'>")
-- l10n: 中文不輸出冠詞,也不需要 enwt 為了決定 a/an 而做的判斷。from= 子句在中文裡放在
-- 中心語之前,例如「轉變自地名的俄羅斯姓氏」;語言前綴寫法(from=de:Wolf)結尾是拉丁字母,
-- 故在「的」前補一個空格。
local from_catparts = {}
if args.from[1] then
ins(fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
end
ins(textseg .. (args.from[1]:find(":") and " 的" or "的"))
end
if #genders > 0 then
ins(table.concat(genders, "或")) -- l10n
end
if adj then
ins(adj)
end
ins("[[" .. surname_types[iargs.type] .. "]]") -- l10n
if xlittext ~= "" then
ins("," .. xlittext)
end
-- l10n: 補述子句一律以逗號分隔,理由同 export.given_name。
if meaningtext ~= "" then
ins("," .. fetch_typetext("meaningtype") .. "表示" .. meaningtext)
end
if args.origin then
ins(",來自" .. args.origin)
end
if args.usage then
ins(",用於" .. args.usage)
end
if varoftext ~= "" then
ins("," .. varoftext .. "的" .. fetch_typetext("varoftype") .. "變體")
end
if clipoftext ~= "" then
-- l10n: 「截斷形式」與 {{clipping of}} 用詞一致
ins("," .. clipoftext .. "的" .. fetch_typetext("clipoftype") .. "截斷形式")
end
if blendtext ~= "" then
ins("," .. blendtext .. "的" .. fetch_typetext("blendtype") .. "混成詞")
end
if args.popular then
ins("," .. fetch_typetext("populartype") .. "流行" .. args.popular)
end
if mtext ~= "" then
ins("," .. fetch_typetext("mtype") .. "陽性等價詞 " .. mtext)
end
if ftext ~= "" then
ins("," .. fetch_typetext("ftype") .. "陰性等價詞 " .. ftext)
end
if eqtext ~= "" then
ins("," .. fetch_typetext("eqtype") .. "等價於" .. eqtext)
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
if varformtext ~= "" then
ins(";" .. fetch_typetext("varformtype") .. "變體形式 " .. varformtext)
end
ins("</span>")
local text = table.concat(textsegs)
if args.nocat then
return text
end
local categories = {}
local langname = lang:getCanonicalName() -- l10n: 中文語言名與分類名之間不加空格
local nametype = surname_types[iargs.type] -- l10n
local function insert_cats(g)
g = g or ""
table.insert(categories, langname .. g .. nametype)
for _, catpart in ipairs(from_catparts) do
table.insert(categories, "來自" .. catpart .. "的" .. langname .. g .. nametype) -- l10n
end
end
insert_cats(nil)
local function insert_cats_gender(g)
if g == "性別不明" then -- l10n
return
end
if g == "通性" then -- l10n
insert_cats_gender("男性")
insert_cats_gender("女性")
end
insert_cats(g)
end
for _, g in ipairs(genders) do
insert_cats_gender(g)
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
-- The entry point for {{name translit}}, {{name respelling}}, {{name obor}} and {{foreign name}}.
function export.name_translit(frame)
local boolean = {type = "boolean"}
local iargs = require("Module:parameters").process(frame.args, {
["desctext"] = {required = true},
["obor"] = boolean,
["foreign_name"] = boolean,
})
local parent_args = frame:getParent().args
local params = {
[1] = {required = true, type = "language", template_default = "en"},
[2] = {required = true, type = "language", sublist = true, template_default = "ru"},
[3] = {list = true, allow_holes = true},
["type"] = {required = true, set = translit_name_type_list, sublist = true, default = "父名"}, -- l10n
["dim"] = boolean,
["aug"] = boolean,
["nocap"] = boolean,
["addl"] = true,
["sort"] = true,
["pagename"] = true,
["nocat"] = boolean,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "q", "l", "ref"}},
{param = {"xlit", "eq"}},
}
local names, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 3,
track_module = "names/name translit",
disallow_custom_separators = true,
-- Use the first source language as the language of the specified names.
lang = function(args) return args[2][1] end,
sc = "sc.default",
-- We display decorations ourselves so we can display them "raw", as we surround everything with
-- 'use-with-mention' and want to avoid double-tagging.
no_show_decorations = true,
}
local lang = args[1]
local sources = args[2]
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local textsegs = {}
local function ins(txt)
table.insert(textsegs, txt)
end
ins("<span class='use-with-mention'>")
local desctext = iargs.desctext -- l10n: 中文沒有首字母大寫,因此不處理 nocap=
local langsegs = {}
for i, source in ipairs(sources) do
local sourcename = source:getCanonicalName()
local function get_source_link()
local term_to_link = names[1] and names[1].term or pagename
-- We link the language name to either the first specified name or the pagename, in the following
-- circumstances:
-- (1) More than one language was given along with at least one name; or
-- (2) We're handling {{foreign name}} or {{name obor}}, and no name was given.
-- The reason for (1) is that if more than one language was given, we want a link to the name
-- in each language, as the name that's displayed is linked only to the first specified language.
-- However, if only one language was given, linking the language to the name is redundant.
-- The reason for (2) is that {{foreign name}} is often used when the name in the destination language
-- is spelled the same as the name in the source language (e.g. [[Clinton]] or [[Obama]] in Italian),
-- and in that case no name will be explicitly specified but we still want a link to the name in the
-- source language. The reason we restrict this to {{foreign name}} or {{name obor}}, not to
-- {{name translit}} or {{name respelling}}, is that {{name translit}} and {{name respelling}} ought to be
-- used for names spelled differently in the destination language (either transliterated or respelled), so
-- assuming the pagename is the name in the source language is wrong.
if names[1] and #sources > 1 or (iargs.foreign_name or iargs.obor) and not names[1] then
return m_links.language_link{
lang = sources[i], term = term_to_link, alt = sourcename, tr = "-"
}
else
return sourcename
end
end
if i == 1 and not iargs.foreign_name then
-- If at least one name is given, we say "A transliteration of the LANG surname FOO", linking LANG to FOO.
-- Otherwise we say "A transliteration of a LANG surname".
if names[1] then
table.insert(langsegs, get_source_link()) -- l10n: 中文不需要 "the"
else
table.insert(langsegs, sourcename) -- l10n: 中文不需要不定冠詞
end
else
table.insert(langsegs, get_source_link())
end
end
local langseg_text = m_table.serialCommaJoin(langsegs, {conj = "或"}) -- l10n
local augdim_text
if args.dim then
augdim_text = "[[指小詞]]" -- l10n
elseif args.aug then
augdim_text = "[[指大詞]]" -- l10n
else
augdim_text = ""
end
-- l10n: type= 的值本身已是中文(姓氏、男性名字……),不像英文還要把單數形對應成連結形。
local nametype_text = m_table.serialCommaJoin(args["type"], {conj = "和"}) .. augdim_text
if not iargs.foreign_name then
ins(langseg_text)
-- l10n: 未指定名字時,desctext 直接接在類型之後(「俄語姓氏的轉寫」);舊版在這種情況下
-- 會整個漏掉 desctext,只輸出「俄語姓氏」。
ins(nametype_text .. (names[1] and "" or "的" .. desctext))
else
-- l10n: {{foreign name}} 的中文語序是「使用於〔語言〕的〔類型〕」
ins(desctext .. langseg_text .. "的" .. nametype_text)
if names[1] then
ins(",")
end
end
local linked_names = {}
local embedded_comma = false
for _, name in ipairs(names) do
local linked_name = m_links.full_link(name, "term")
if name.q and name.q[1] or name.qq and name.qq[1] or name.l and name.l[1] or name.ll and name.ll[1] or
name.refs and name.refs[1] then
linked_name = require(decorations_module).format_qualifiers {
lang = name.lang,
text = linked_name,
q = name.q,
qq = name.qq,
l = name.l,
ll = name.ll,
refs = name.refs,
raw = true, -- since we put 'use-with-mention' around the entire text
}
end
if name.xlit then
embedded_comma = true
linked_name = linked_name .. "," .. m_links.language_link { lang = enlang, term = name.xlit } -- l10n
end
-- l10n: 中文把 desctext(轉寫/形譯詞……)接在名字之後,例如「Фру́нзе的轉寫」。
if not iargs.foreign_name then
linked_name = linked_name .. "的" .. desctext
end
if name.eq then
embedded_comma = true
linked_name = linked_name .. ",等同於 " .. m_links.language_link { lang = enlang, term = name.eq } -- l10n
end
table.insert(linked_names, linked_name)
end
if embedded_comma then
ins(table.concat(linked_names, ";或")) -- l10n
else
ins(m_table.serialCommaJoin(linked_names, {conj = "或"})) -- l10n
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
ins("</span>")
local text = table.concat(textsegs)
if args.nocat then
return text
end
local categories = {}
local langfullname = lang:getFullName()
-- l10n: 中文分類名把目標語言放在中間(「英語男性名字的漢語譯名」),不像 enwt 統一加在最前面。
for _, nametype in ipairs(args.type) do
local function insert_cats(dimaugof)
local function insert_cats_type(ty)
if ty == "中性名字" then -- l10n
insert_cats_type("男性名字")
insert_cats_type("女性名字")
end
for _, source in ipairs(sources) do
local sourcename = source:getCanonicalName()
table.insert(categories, sourcename .. ty .. dimaugof .. "的" .. langfullname .. "譯名")
table.insert(categories, "派生自" .. sourcename .. "的" .. langfullname .. "詞")
table.insert(categories, "源自" .. sourcename .. "的" .. langfullname .. "借詞")
if iargs.obor then
table.insert(categories, "源自" .. sourcename .. "的" .. langfullname .. "形譯詞")
end
if source:getCode() ~= source:getFullCode() then
-- etymology language
table.insert(categories,
source:getFullName() .. ty .. dimaugof .. "的" .. langfullname .. "譯名")
end
end
end
insert_cats_type(nametype)
end
insert_cats("")
if args.dim then
insert_cats("指小詞") -- l10n
end
if args.aug then
insert_cats("指大詞") -- l10n
end
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
return export
99t8fz4jjzgv1agh5rx8enqbg5w8c93
9909088
9909020
2026-09-29T07:27:17Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909088
Scribunto
text/plain
local export = {}
local m_languages = require("Module:languages")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local decorations_module = "Module:decorations"
local parameter_utilities_module = "Module:parameter utilities"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local enlang = m_languages.getByCode("en")
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local u = m_str_utils.char
local function rsub(str, from, to)
return (rsubn(str, from, to))
end
local TEMP_LESS_THAN = u(0xFFF2)
local force_cat = false -- for testing
--[=[
FIXME:
1. from=the Bible (DONE)
2. origin=18th century [DONE]
3. popular= (DONE)
4. varoftype= (DONE)
5. eqtype= [DONE]
6. dimoftype= [DONE]
7. from=de:Elisabeth (same language) (DONE)
8. blendof=, blendof2= [DONE]
9. varform, dimform [DONE]
10. from=English < Latin [DONE]
11. usage=rare -> categorize as rare?
12. dimeq= (also vareq=?) [DONE]
13. fromtype= [DONE]
14. <tr:...> and similar params [DONE]
]=]
-- Used in category code; name types which are full-word end-matching substrings of longer name types (e.g. "surnames"
-- of "male surnames", but not "male surnames" of "female surnames" because "male" only matches a part of the word
-- "female") should follow the longer name.
-- l10n: 分類名一律中文化。本列表會被 [[Module:Category tree/names]] 讀取,改動前請一併檢查該模組。
export.personal_name_types = {
"男性姓氏", "女性姓氏", "通性姓氏", "姓氏",
"父名", "母名",
}
export.personal_name_type_set = m_table.listToSet(export.personal_name_types)
-- l10n: 性別關鍵字中文化。動物類的鍵同時是顯示時的連結目標(「狗」→ [[狗]])與分類名的前綴
-- (「狗」→「狗名」),因此必須是可獨立成詞的中文。
export.given_name_genders = {
["男性"] = {type = "human"},
["女性"] = {type = "human"},
["中性"] = {type = "human", cat = {"男性名字", "女性名字", "中性名字"}},
["性別不明"] = {type = "human", cat = {}, track = true},
["動物"] = {type = "animal", track = true},
["貓"] = {type = "animal"},
["牛"] = {type = "animal"},
["狗"] = {type = "animal"},
["馬"] = {type = "animal"},
["豬"] = {type = "animal"},
}
-- l10n: 條目中仍有大量英文性別值({{given name|xx|male}} 等約四百處),以及 zhwt 舊有的
-- 「未知性別」寫法,一律先對應回上表的中文鍵。
local given_name_gender_aliases = {
["male"] = "男性",
["female"] = "女性",
["unisex"] = "中性",
["unknown"] = "性別不明",
["unknown gender"] = "性別不明",
["unknown-gender"] = "性別不明",
["未知性別"] = "性別不明",
["?"] = "性別不明",
["animal"] = "動物",
["cat"] = "貓",
["cow"] = "牛",
["dog"] = "狗",
["horse"] = "馬",
["pig"] = "豬",
}
-- l10n: {{surname}}/{{patronymic}}/{{matronymic}} 的 type= 是模板內部值,保持英文;顯示與
-- 分類所需的中文名稱在此對照。
local surname_types = {
["surname"] = "姓氏",
["patronymic"] = "父名",
["matronymic"] = "母名",
}
-- l10n: 姓氏類性別值;同時接受 enwt 的英文寫法與 zhwt 既有的中文寫法。
local surname_genders = {
["unknown"] = "性別不明",
["unknown gender"] = "性別不明",
["unknown-gender"] = "性別不明",
["未知性別"] = "性別不明",
["性別不明"] = "性別不明",
["?"] = "性別不明",
["unisex"] = "通性",
["common gender"] = "通性",
["common-gender"] = "通性",
["c"] = "通性",
["通性"] = "通性",
["m"] = "男性",
["male"] = "男性",
["男性"] = "男性",
["f"] = "女性",
["female"] = "女性",
["女性"] = "女性",
}
local function get_given_name_cats(gender, props)
local cats = props.cat
if not cats then
if props.type == "animal" then
cats = {gender .. "名"} -- l10n: 例如「狗名」
else
cats = {gender .. "名字"} -- l10n: 例如「男性名字」
end
end
return cats
end
do
local function do_cat(cat)
if not export.personal_name_type_set[cat] then
export.personal_name_type_set[cat] = true
table.insert(export.personal_name_types, cat)
end
end
for gender, props in pairs(export.given_name_genders) do
local cats = get_given_name_cats(gender, props)
for _, cat in ipairs(cats) do
do_cat(cat .. "指小詞") -- l10n: 中文把「指小詞」接在名稱之後
do_cat(cat .. "指大詞") -- l10n
do_cat(cat)
end
end
do_cat("名字") -- l10n: zhwt 以「名字」作為 given names 的分類名(見下方 insert_cats)
end
-- l10n: {{name translit}} 系列 type= 的合法值,中文化。
local translit_name_type_list = {
"姓氏", "男性名字", "女性名字", "中性名字",
"父名"
}
local function track(page)
require("Module:debug").track("names/" .. page)
end
--[=[
Parse a term and associated properties. This works with parameters of the form 'Karlheinz' or
'Kunigunde<q:medieval, now rare>' or 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' where the modifying properties
are contained in <...> specifications after the term. `term` is the full parameter value including any angle brackets
and colons; `paramname` is the name of the parameter that this value comes from, for error purposes; `deflang` is a
language object used in the return value when the language isn't specified (e.g. in the examples 'Karlheinz' and
'Kunigunde<q:medieval, now rare>' above); `allow_explicit_lang` indicates whether the language can be explicitly given
(e.g. in the examples 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' above).
Normally the return value is a terminfo object that can be passed to full_link() in [[Module:links]]), additionally
with optional fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as the returned
terminfo object. However, if `allow_multiple_terms` is given, multiple comma-separated names can be given in `term`,
and the return value is a list of objects of the form described just above.
]=]
local function parse_term_with_annotations(term, paramname, deflang, allow_explicit_lang, allow_multiple_terms)
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"link", "l", "q", "ref"}},
{param = "eq", convert = function(eqval, parse_err)
return parse_term_with_annotations(eqval, paramname .. ".eq", enlang, false, "allow multiple terms")
end},
}
local function generate_obj(term, parse_err)
local termlang
if allow_explicit_lang then
local actual_term
actual_term, termlang = require(parse_interface_module).parse_term_with_lang {
term = term,
parse_err = parse_err,
paramname = paramname,
}
term = actual_term or term
end
return {
term = term,
lang = termlang or deflang,
}
end
return require(parse_interface_module).parse_inline_modifiers(term, {
param_mods = param_mods,
paramname = paramname,
generate_obj = generate_obj,
splitchar = allow_multiple_terms and "," or nil,
})
end
--[=[
Link a single term. If `do_language_link` is given and a given term's language is English, the link will be constructed
using language_link() in [[Module:links]]; otherwise, with full_link(). `termobj` is an object as returned by
parse_term_with_annotations(), i.e. it is suitable for passing to [[Module:links]] and additionally contains optional
fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as `termobj`).
]=]
local function link_one_term(termobj, do_language_link)
local link
if do_language_link and termobj.lang:getCode() == "en" then
link = m_links.language_link(termobj)
else
link = m_links.full_link(termobj)
end
if termobj.q and termobj.q[1] or termobj.qq and termobj.qq[1] or
termobj.l and termobj.l[1] or termobj.ll and termobj.ll[1] or termobj.refs and termobj.refs[1] then
link = require(decorations_module).format_decorations {
lang = termobj.lang,
text = link,
q = termobj.q,
qq = termobj.qq,
l = termobj.l,
ll = termobj.ll,
refs = termobj.refs,
}
end
if termobj.eq then
local eqtext = {}
for _, eqobj in ipairs(termobj.eq) do
table.insert(eqtext, link_one_term(eqobj, true))
end
link = link .. " [=" .. m_table.serialCommaJoin(eqtext, {conj = "或"}) .. "]" -- l10n
end
return link
end
--[=[
Link the terms in `terms`, and join them using the conjunction in `conj` (defaulting to "or"). Joining is done using
serialCommaJoin() in [[Module:table]], so that e.g. two terms are joined as "TERM or TERM" while three terms are joined
as "TERM, TERM or TERM" with special CSS spans before the final "or" to allow an "Oxford comma" to appear if configured
appropriately. (However, if `conj` is the special value ", ", joining is done directly using that value.)
If `include_langname` is given, the language of the first term will be prepended to the joined terms. If
`do_language_link` is given and a given term's language is English, the link will be constructed using language_link()
in [[Module:links]]; otherwise, with full_link(). Each term in `terms` is an object as returned by
parse_term_with_annotations().
]=]
local function join_terms(terms, include_langname, do_language_link, conj)
local links = {}
local langnametext
for _, termobj in ipairs(terms) do
if include_langname and not langnametext then
langnametext = termobj.lang:getCanonicalName() -- l10n: 中文語言名與詞語間不加空格
end
table.insert(links, link_one_term(termobj, do_language_link))
end
local joined_terms
if conj == "," then -- l10n: 「直接以逗號連接」的特殊值改用全角逗號
joined_terms = table.concat(links, conj)
else
joined_terms = m_table.serialCommaJoin(links, {conj = conj or "或"}) -- l10n
end
return (langnametext or "") .. joined_terms
end
--[=[
Gather the parameters for multiple names and link each name using full_link() (for foreign names) or language_link()
(for English names), joining the names using serialCommaJoin() in [[Module:table]] with the conjunction `conj`
(defaulting to "or"). (However, if `conj` is the special value ", ", joining is done directly using that value.)
This can be used, for example, to fetch and join all the masculine equivalent names for a feminine given name. Each
name is specified using parameters beginning with `pname` in `args`, e.g. "m", "m2", "m3", etc. `lang` is a language
object specifying the language of the names (defaulting to English), for use in linking them. If `allow_explicit_lang`
is given, the language of the terms can be specified explicitly by prefixing a term with a language code, e.g.
'sv:Björn' or 'la:[[Nicolaus|Nīcolāī]]'. This function assumes that the parameters have already been parsed by
[[Module:parameters]] and gathered into lists, so that e.g. all "mN" parameters are in a list in args["m"].
]=]
local function join_names(lang, args, pname, conj, allow_explicit_lang)
local termobjs = {}
local do_language_link = false
if not lang then
lang = enlang
do_language_link = true
end
local function process_one_term(term, i)
for _, termobj in ipairs(parse_term_with_annotations(term, pname .. (i == 1 and "" or i), lang,
allow_explicit_lang, "allow multiple terms")) do
table.insert(termobjs, termobj)
end
end
if not args[pname] then
return "", 0
elseif type(args[pname]) == "table" then
for i, term in ipairs(args[pname]) do
process_one_term(term, i)
end
else
process_one_term(args[pname], 1)
end
return join_terms(termobjs, nil, do_language_link, conj or "和"), #termobjs -- l10n
end
local function get_eqtext(args)
local eqsegs = {}
local lastlang = nil
local last_eqseg = {}
local function process_one_term(term, i)
for _, termobj in ipairs(parse_term_with_annotations(term, "eq" .. (i == 1 and "" or i), enlang,
"allow explicit lang", "allow multiple terms")) do
local termlang = termobj.lang:getCode()
if lastlang and lastlang ~= termlang then
if #last_eqseg > 0 then
table.insert(eqsegs, last_eqseg)
end
last_eqseg = {}
end
lastlang = termlang
table.insert(last_eqseg, termobj)
end
end
if type(args.eq) == "table" then
for i, term in ipairs(args.eq) do
process_one_term(term, i)
end
elseif type(args.eq) == "string" then
process_one_term(args.eq, 1)
end
if #last_eqseg > 0 then
table.insert(eqsegs, last_eqseg)
end
local eqtextsegs = {}
for _, eqseg in ipairs(eqsegs) do
table.insert(eqtextsegs, join_terms(eqseg, "include langname"))
end
return m_table.serialCommaJoin(eqtextsegs, {conj = "或"}) -- l10n
end
local function get_fromtext(lang, args)
local catparts = {}
local fromsegs = {}
local i = 1
local function parse_from(from)
local unrecognized = false
local prefix, suffix
-- l10n: from= 的關鍵字與前後綴全部中文化。中文沒有單複數,因此 enwt 的去 -s 處理,以及
-- 「as a X」與「as an X」兩個只差在冠詞的分支,在此都不需要,合併為同一個分支。
if from == "名字" then from = "人名" end -- l10n: zhwt 舊有寫法,等同 given names
if from == "姓氏" or from == "人名" or from == "暱稱" or from == "地名" or from == "普通名詞" or from == "月份名" then
prefix = "轉變自"
suffix = from
table.insert(catparts, from)
elseif from == "父名" or from == "母名" or from == "新詞" or from == "職業" or from == "民族名稱" then
prefix = "來自"
suffix = from
table.insert(catparts, from)
elseif from == "聖經" then
prefix = "來自"
suffix = "聖經"
table.insert(catparts, from)
else
prefix = "源自"
if from:find(":") then
local termobj = parse_term_with_annotations(from, "from" .. (i == 1 and "" or i), lang,
"allow explicit lang")
local fromlangname = ""
if termobj.lang:getCode() ~= lang:getCode() then
-- If name is derived from another name in the same language, don't include lang name after text
-- "from " or create a category like "German male given names derived from German".
local canonical_name = termobj.lang:getCanonicalName()
fromlangname = canonical_name -- l10n: 中文不加空格
table.insert(catparts, canonical_name)
end
suffix = fromlangname .. link_one_term(termobj)
else
-- l10n: 英文靠字尾的 " languages" 先判斷是語系還是語言,因為語系顯示時要加
-- "the";中文兩者顯示完全相同,差別只在該查哪張表,所以依序查語系表和語言表就好,
-- 不必比對字尾。(順帶修掉舊版的錯誤:Lua 模式的字元類是逐位元組比對的,
-- [語语] 這種寫法對多位元組漢字無效,語系永遠判斷失敗也就永遠不會分類。)
if require("Module:families").getByCanonicalName(from) or
m_languages.getByCanonicalName(from, nil, "allow etym") then
table.insert(catparts, from)
else
unrecognized = true
end
suffix = from
end
end
if unrecognized then
track("unrecognized from")
track("unrecognized from/" .. from)
end
return prefix, suffix
end
local last_fromseg = nil
local put = require(parse_utilities_module)
local from_args = args.from or {}
if type(from_args) == "string" then
from_args = {from_args}
end
while from_args[i] do
-- We may have multiple comma-separated items, each of which may have multiple items separated by a
-- space-delimited < sign, each of which may have inline modifiers with embedded commas in them. To handle
-- this correctly, first replace space-delimited < signs with a special character, then split on balanced
-- <...> and [...] signs, then split on comma, then rejoin the stuff between commas. We will then split on
-- TEMP_LESS_THAN (the replacement for space-delimited < signs) and reparse.
local rawfroms = rsub(from_args[i], "%s+<%s+", TEMP_LESS_THAN)
local segments = put.parse_multi_delimiter_balanced_segment_run(rawfroms, {{"<", ">"}, {"[", "]"}})
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
for j, comma_separated_group in ipairs(comma_separated_groups) do
comma_separated_groups[j] = table.concat(comma_separated_group)
end
for _, rawfrom in ipairs(comma_separated_groups) do
local froms = rsplit(rawfrom, TEMP_LESS_THAN)
if #froms == 1 then
local prefix, suffix = parse_from(froms[1])
if last_fromseg and (last_fromseg.has_multiple_froms or last_fromseg.prefix ~= prefix) then
table.insert(fromsegs, last_fromseg)
last_fromseg = nil
end
if not last_fromseg then
last_fromseg = {prefix = prefix, suffixes = {}}
end
table.insert(last_fromseg.suffixes, suffix)
else
if last_fromseg then
table.insert(fromsegs, last_fromseg)
last_fromseg = nil
end
local first_suffixpart = ""
local rest_suffixparts = {}
for j, from in ipairs(froms) do
local prefix, suffix = parse_from(from)
if j == 1 then
first_suffixpart = prefix .. suffix
else
table.insert(rest_suffixparts, prefix .. suffix)
end
end
-- l10n: enwt 作「X [in turn from Y, in turn from Z]」,中文改為「X(再源自Y,再源自Z)」。
local full_suffix = first_suffixpart .. "(再" .. table.concat(rest_suffixparts, ",再") .. ")"
last_fromseg = {prefix = "", has_multiple_froms = true, suffixes = {full_suffix}}
end
end
i = i + 1
end
table.insert(fromsegs, last_fromseg)
local fromtextsegs = {}
for _, fromseg in ipairs(fromsegs) do
table.insert(fromtextsegs, fromseg.prefix .. m_table.serialCommaJoin(fromseg.suffixes, {conj = "或"})) -- l10n
end
return m_table.serialCommaJoin(fromtextsegs, {conj = "或"}), catparts -- l10n
end
local function parse_given_name_genders(genderspec)
genderspec = given_name_gender_aliases[genderspec] or genderspec -- l10n
if export.given_name_genders[genderspec] then -- optimization
return {{
type = genderspec,
props = export.given_name_genders[genderspec],
}}, export.given_name_genders[genderspec].type == "animal"
end
local is_animal = nil
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"l", "q", "ref"}},
{param = "text"}, -- l10n: 中文不輸出冠詞,因此不支援 <article:...>
}
local function generate_obj(term, parse_err)
term = given_name_gender_aliases[term] or term -- l10n
if not export.given_name_genders[term] then
local valid_genders = {}
for k, _ in pairs(export.given_name_genders) do
table.insert(valid_genders, k)
end
table.sort(valid_genders)
parse_err(("Unrecognized gender '%s': valid genders are %s"):format(
term, table.concat(valid_genders, ", ")))
end
return {
type = term,
props = export.given_name_genders[term],
}
end
local retval = require(parse_interface_module).parse_inline_modifiers(genderspec, {
param_mods = param_mods,
paramname = "2",
generate_obj = generate_obj,
splitchar = ",",
})
for _, spec in ipairs(retval) do
local this_is_animal = spec.props.type == "animal"
if is_animal == nil then
is_animal = this_is_animal
elseif is_animal ~= this_is_animal then
error("Can't mix animal and human genders")
end
end
return retval, is_animal
end
-- l10n: 中文不輸出不定冠詞,因此不像 enwt 一併回傳 article。
local function generate_given_name_genders(lang, genders)
local parts = {}
for _, spec in ipairs(genders) do
local text
if spec.text then
-- NOTE: This assumes no % sign in the gender type, which seems safe.
text = spec.text:gsub("%+", spec.type)
else
if spec.props.type == "animal" then
text = "[[" .. spec.type .. "]]"
else
text = spec.type
end
end
if spec.q and spec.q[1] or spec.qq and spec.qq[1] or spec.l and spec.l[1] or spec.ll and spec.ll[1] or
spec.refs and spec.refs[1] then
text = require(decorations_module).format_decorations {
lang = lang,
text = text,
q = spec.q,
qq = spec.qq,
l = spec.l,
ll = spec.ll,
refs = spec.refs,
raw = true,
}
end
table.insert(parts, text)
end
return m_table.serialCommaJoin(parts, {conj = "或"}) -- l10n
end
-- The entry point for {{given name}}.
function export.given_name(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
local lang_index = compat and "lang" or 1
local boolean = {type = "boolean"}
local list = {list = true}
-- l10n: zhwt 條目大量使用 from2=、xlit2=、eq2=、dim2= 等編號參數,因此凡是 enwt 改成
-- 單一參數(值內以逗號分隔)的名稱類參數,在此一律保留 list = true;上游的 join_names()、
-- get_eqtext()、get_fromtext() 本來就同時支援字串與列表,逗號分隔的新寫法照樣可用。
local args = require("Module:parameters").process(parent_args, {
[lang_index] = {required = true, type = "language", default = "und"},
["gender"] = {default = "性別不明"}, -- l10n
[1 + offset] = {alias_of = "gender"},
["or"] = true, -- l10n: zhwt 舊有的第二性別參數,等同 enwt 的 gender=甲,乙
["orq"] = true, -- l10n: zhwt 舊有的第二性別修飾語,等同 enwt 的 gender=甲,乙<q:...>
["usage"] = true,
["origin"] = true,
["popular"] = true,
["populartype"] = true,
["meaning"] = list,
["meaningtype"] = true,
["addl"] = true,
["q"] = {alias_of = "addl"}, -- l10n: zhwt 舊有寫法,作用與 addl= 相同
["nocap"] = boolean,
-- initial article: A or An
["A"] = true,
["sort"] = true,
["from"] = list, -- l10n
[2 + offset] = {alias_of = "from", list = false},
["fromtype"] = true,
["xlit"] = list, -- l10n
["eq"] = list, -- l10n
["eqtype"] = true,
["varof"] = list, -- l10n
["varoftype"] = true,
["var"] = {alias_of = "varof", list = true},
["vartype"] = {alias_of = "varoftype"},
["varform"] = list, -- l10n
["varformtype"] = true,
["dimof"] = list, -- l10n
["dimoftype"] = true,
["dim"] = {alias_of = "dimof", list = true},
["dimtype"] = {alias_of = "dimoftype"},
["diminutive"] = {alias_of = "dimof", list = true}, -- l10n: zhwt 舊有別名
["diminutivetype"] = {alias_of = "dimoftype"}, -- l10n: zhwt 舊有別名
["dimform"] = list, -- l10n
["dimformtype"] = true,
["augof"] = list, -- l10n
["augoftype"] = true,
["aug"] = {alias_of = "augof", list = true},
["augtype"] = {alias_of = "augoftype"},
["augmentative"] = {alias_of = "augof", list = true}, -- l10n: zhwt 舊有別名
["augmentativetype"] = {alias_of = "augoftype"}, -- l10n: zhwt 舊有別名
["augform"] = list, -- l10n
["augformtype"] = true,
["clipof"] = list, -- l10n
["clipoftype"] = true,
["blend"] = list, -- l10n
["blendtype"] = true,
["m"] = list, -- l10n
["mtype"] = true,
["f"] = list, -- l10n
["ftype"] = true,
["nocat"] = boolean,
})
local textsegs = {}
local lang = args[lang_index]
-- l10n: 中文在型別詞後不加空格
local function fetch_typetext(param)
return args[param] or ""
end
-- l10n: 把 zhwt 舊有的 or=/orq= 併進 gender=,交給同一個解析器處理
if args["or"] then
args.gender = args.gender .. "," .. args["or"] ..
(args.orq and "<q:" .. args.orq .. ">" or "")
end
local genders, is_animal = parse_given_name_genders(args.gender)
local dimoftext, numdimofs = join_names(lang, args, "dimof")
local augoftext, numaugofs = join_names(lang, args, "augof")
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend") -- formerly the only one that used "and" instead of "or"
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local varformtext = join_names(lang, args, "varform", ",") -- l10n
local dimformtext = join_names(lang, args, "dimform", ",") -- l10n
local augformtext = join_names(lang, args, "augform", ",") -- l10n
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '“' .. meaning .. '”')
end
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "或"}) -- l10n
local eqtext = get_eqtext(args)
local function ins(txt)
table.insert(textsegs, txt)
end
-- l10n: 中文語序與英文相反。英文作「a 〔型別〕 diminutive, 〔轉寫〕, of the 〔性別〕 given
-- name 〔基礎名字〕」,中文作「〔性別〕人名〔基礎名字〕的〔型別〕指小詞,〔轉寫〕」;動物名
-- 則作「〔動物〕的名字」而非英文的「a given name for a 〔動物〕」。中文也沒有單複數,因此
-- enwt 的 given name/given names 之別在此不存在。
ins(generate_given_name_genders(lang, genders))
if is_animal then
ins("的[[名字]]")
else
ins("[[人名]]")
end
if numdimofs > 0 then
if args.dimof[1] == "-" then
dimoftext = ""
end
ins(dimoftext .. "的" .. fetch_typetext("dimoftype") .. "[[指小詞]]")
elseif numaugofs > 0 then
ins(augoftext .. "的" .. fetch_typetext("augoftype") .. "[[指大詞]]")
end
if xlittext ~= "" then
ins("," .. xlittext)
end
-- l10n: 中文的補述子句前面必定已有「人名」等中心語,因此一律以逗號分隔,不需要 enwt 的
-- need_comma 判斷(舊版 zhwt 模組漏了這個判斷,才會出現「轉變自地名用於現代」這類漏逗號)。
local from_catparts = {}
if args.from[1] then
ins("," .. fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
end
ins(textseg)
end
if meaningtext ~= "" then
ins("," .. fetch_typetext("meaningtype") .. "表示" .. meaningtext)
end
if args.origin then
ins(",來自" .. args.origin)
end
if args.usage then
ins(",用於" .. args.usage)
end
if varoftext ~= "" then
ins("," .. varoftext .. "的" .. fetch_typetext("varoftype") .. "變體")
end
if clipoftext ~= "" then
-- l10n: 「截斷形式」與 {{clipping of}} 用詞一致
ins("," .. clipoftext .. "的" .. fetch_typetext("clipoftype") .. "截斷形式")
end
if blendtext ~= "" then
ins("," .. blendtext .. "的" .. fetch_typetext("blendtype") .. "混成詞")
end
if args.popular then
ins("," .. fetch_typetext("populartype") .. "流行" .. args.popular)
end
if mtext ~= "" then
ins("," .. fetch_typetext("mtype") .. "陽性等價詞 " .. mtext)
end
if ftext ~= "" then
ins("," .. fetch_typetext("ftype") .. "陰性等價詞 " .. ftext)
end
if eqtext ~= "" then
ins("," .. fetch_typetext("eqtype") .. "等價於" .. eqtext)
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
if varformtext ~= "" then
ins(";" .. fetch_typetext("varformtype") .. "變體形式 " .. varformtext)
end
if dimformtext ~= "" then
ins(";" .. fetch_typetext("dimformtype") .. "指小詞 " .. dimformtext)
end
if augformtext ~= "" then
ins(";" .. fetch_typetext("augformtype") .. "指大詞 " .. augformtext)
end
-- l10n: 中文不輸出冠詞
local text = "<span class='use-with-mention'>" .. table.concat(textsegs) .. "</span>"
if args.nocat then
return text
end
local categories = {}
local langname = lang:getCanonicalName() -- l10n: 中文語言名與分類名之間不加空格
local function insert_cats(dimaugof)
if dimaugof == "" and genders[1].props.type == "human" then
-- No category such as "English diminutives of given names"
table.insert(categories, langname .. "名字")
end
-- l10n: 中文把「指小詞」接在分類名最後,來源則放在最前面作「來自〔來源〕的……」。
local function insert_cat(cat)
table.insert(categories, langname .. cat .. dimaugof)
for _, catpart in ipairs(from_catparts) do
table.insert(categories, "來自" .. catpart .. "的" .. langname .. cat .. dimaugof)
end
end
for _, spec in ipairs(genders) do
local typ = spec.type
if spec.props.track then
track(typ)
end
local cats = get_given_name_cats(spec.type, spec.props)
for _, cat in ipairs(cats) do
insert_cat(cat)
end
end
end
insert_cats("")
if numdimofs > 0 then
insert_cats("指小詞") -- l10n
elseif numaugofs > 0 then
insert_cats("指大詞") -- l10n
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
-- The entry point for {{surname}}, {{patronymic}} and {{matronymic}}.
function export.surname(frame)
local iargs = require("Module:parameters").process(frame.args, {
["type"] = {required = true, set = {"surname", "patronymic", "matronymic"}},
})
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
if parent_args.dot or parent_args.nodot then
error("dot= and nodot= are no longer supported in [[Template:" .. iargs.type .. "]] because a trailing " ..
"period is no longer added by default; if you want it, add it explicitly after the template")
end
local lang_index = compat and "lang" or 1
local boolean = {type = "boolean"}
local list = {list = true}
local gender_arg = iargs.type == "surname" and "g" or 1 + offset
local adj_arg = iargs.type == "surname" and 1 + offset or 2 + offset
-- l10n: 名稱類參數保留 list = true 的理由同 export.given_name。
local args = require("Module:parameters").process(parent_args, {
[lang_index] = {required = true, type = "language", template_default = "und"},
[gender_arg] = iargs.type == "surname" and list or {required = true, template_default = "unknown"}, -- gender(s)
[adj_arg] = true, -- adjective/qualifier
["usage"] = true,
["origin"] = true,
["popular"] = true,
["populartype"] = true,
["meaning"] = list,
["meaningtype"] = true,
["parent"] = list, -- l10n
["addl"] = true,
["q"] = {alias_of = "addl"}, -- l10n: zhwt 舊有寫法,作用與 addl= 相同
["nocap"] = boolean,
-- initial article: by default A or An (English), a or an (otherwise)
["A"] = true,
["sort"] = true,
["from"] = list, -- l10n
["fromtype"] = true,
["xlit"] = list, -- l10n
["eq"] = list, -- l10n
["eqtype"] = true,
["varof"] = list, -- l10n
["varoftype"] = true,
["var"] = {alias_of = "varof", list = true},
["vartype"] = {alias_of = "varoftype"},
["varform"] = list, -- l10n
["varformtype"] = true,
["clipof"] = list, -- l10n
["clipoftype"] = true,
["blend"] = list, -- l10n
["blendtype"] = true,
["m"] = list, -- l10n
["mtype"] = true,
["f"] = list, -- l10n
["ftype"] = true,
["nocat"] = boolean,
})
local textsegs = {}
local lang = args[lang_index]
-- l10n: 中文在型別詞後不加空格
local function fetch_typetext(param)
return args[param] or ""
end
local saw_male = false
local saw_female = false
local genders = {}
-- l10n: {{surname}} 的 g= 是 list 參數(zhwt 舊條目有 g2= 的用法),
-- {{patronymic}}/{{matronymic}} 的性別則是位置參數,只會是字串。
local gender_specs = args[gender_arg]
if type(gender_specs) == "string" then
gender_specs = {gender_specs}
end
for _, gender_spec in ipairs(gender_specs or {}) do
for _, g in ipairs(require(parse_interface_module).split_on_comma(gender_spec)) do
local canon_g = surname_genders[g] -- l10n
if not canon_g then
error("Unrecognized gender: " .. g)
end
if canon_g == "性別不明" then
track("unknown gender")
elseif canon_g == "通性" then
saw_male = true
saw_female = true
elseif canon_g == "男性" then
saw_male = true
elseif canon_g == "女性" then
saw_female = true
end
table.insert(genders, canon_g)
end
end
local adj = args[adj_arg]
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend", "和") -- l10n
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local parenttext = join_names(lang, args, "parent", nil, "allow explicit lang")
local varformtext = join_names(lang, args, "varform", ",") -- l10n
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '“' .. meaning .. '”') -- l10n: 舊版此處誤用半角引號,統一為全角
end
if parenttext ~= "" then
-- l10n: 中文語序為「〔父母〕之子」
local child = saw_male and not saw_female and "子" or saw_female and not saw_male and "女" or
"子或女"
table.insert(meaningsegs, ("“%s之%s”"):format(parenttext, child))
end
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "或"}) -- l10n
local eqtext = get_eqtext(args)
local function ins(txt)
table.insert(textsegs, txt)
end
ins("<span class='use-with-mention'>")
-- l10n: 中文不輸出冠詞,也不需要 enwt 為了決定 a/an 而做的判斷。from= 子句在中文裡放在
-- 中心語之前,例如「轉變自地名的俄羅斯姓氏」;語言前綴寫法(from=de:Wolf)結尾是拉丁字母,
-- 故在「的」前補一個空格。
local from_catparts = {}
if args.from[1] then
ins(fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
end
ins(textseg .. (args.from[1]:find(":") and " 的" or "的"))
end
if #genders > 0 then
ins(table.concat(genders, "或")) -- l10n
end
if adj then
ins(adj)
end
ins("[[" .. surname_types[iargs.type] .. "]]") -- l10n
if xlittext ~= "" then
ins("," .. xlittext)
end
-- l10n: 補述子句一律以逗號分隔,理由同 export.given_name。
if meaningtext ~= "" then
ins("," .. fetch_typetext("meaningtype") .. "表示" .. meaningtext)
end
if args.origin then
ins(",來自" .. args.origin)
end
if args.usage then
ins(",用於" .. args.usage)
end
if varoftext ~= "" then
ins("," .. varoftext .. "的" .. fetch_typetext("varoftype") .. "變體")
end
if clipoftext ~= "" then
-- l10n: 「截斷形式」與 {{clipping of}} 用詞一致
ins("," .. clipoftext .. "的" .. fetch_typetext("clipoftype") .. "截斷形式")
end
if blendtext ~= "" then
ins("," .. blendtext .. "的" .. fetch_typetext("blendtype") .. "混成詞")
end
if args.popular then
ins("," .. fetch_typetext("populartype") .. "流行" .. args.popular)
end
if mtext ~= "" then
ins("," .. fetch_typetext("mtype") .. "陽性等價詞 " .. mtext)
end
if ftext ~= "" then
ins("," .. fetch_typetext("ftype") .. "陰性等價詞 " .. ftext)
end
if eqtext ~= "" then
ins("," .. fetch_typetext("eqtype") .. "等價於" .. eqtext)
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
if varformtext ~= "" then
ins(";" .. fetch_typetext("varformtype") .. "變體形式 " .. varformtext)
end
ins("</span>")
local text = table.concat(textsegs)
if args.nocat then
return text
end
local categories = {}
local langname = lang:getCanonicalName() -- l10n: 中文語言名與分類名之間不加空格
local nametype = surname_types[iargs.type] -- l10n
local function insert_cats(g)
g = g or ""
table.insert(categories, langname .. g .. nametype)
for _, catpart in ipairs(from_catparts) do
table.insert(categories, "來自" .. catpart .. "的" .. langname .. g .. nametype) -- l10n
end
end
insert_cats(nil)
local function insert_cats_gender(g)
if g == "性別不明" then -- l10n
return
end
if g == "通性" then -- l10n
insert_cats_gender("男性")
insert_cats_gender("女性")
end
insert_cats(g)
end
for _, g in ipairs(genders) do
insert_cats_gender(g)
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
-- The entry point for {{name translit}}, {{name respelling}}, {{name obor}} and {{foreign name}}.
function export.name_translit(frame)
local boolean = {type = "boolean"}
local iargs = require("Module:parameters").process(frame.args, {
["desctext"] = {required = true},
["obor"] = boolean,
["foreign_name"] = boolean,
})
local parent_args = frame:getParent().args
local params = {
[1] = {required = true, type = "language", template_default = "en"},
[2] = {required = true, type = "language", sublist = true, template_default = "ru"},
[3] = {list = true, allow_holes = true},
["type"] = {required = true, set = translit_name_type_list, sublist = true, default = "父名"}, -- l10n
["dim"] = boolean,
["aug"] = boolean,
["nocap"] = boolean,
["addl"] = true,
["sort"] = true,
["pagename"] = true,
["nocat"] = boolean,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "q", "l", "ref"}},
{param = {"xlit", "eq"}},
}
local names, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 3,
track_module = "names/name translit",
disallow_custom_separators = true,
-- Use the first source language as the language of the specified names.
lang = function(args) return args[2][1] end,
sc = "sc.default",
-- We display decorations ourselves so we can display them "raw", as we surround everything with
-- 'use-with-mention' and want to avoid double-tagging.
no_show_decorations = true,
}
local lang = args[1]
local sources = args[2]
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local textsegs = {}
local function ins(txt)
table.insert(textsegs, txt)
end
ins("<span class='use-with-mention'>")
local desctext = iargs.desctext -- l10n: 中文沒有首字母大寫,因此不處理 nocap=
local langsegs = {}
for i, source in ipairs(sources) do
local sourcename = source:getCanonicalName()
local function get_source_link()
local term_to_link = names[1] and names[1].term or pagename
-- We link the language name to either the first specified name or the pagename, in the following
-- circumstances:
-- (1) More than one language was given along with at least one name; or
-- (2) We're handling {{foreign name}} or {{name obor}}, and no name was given.
-- The reason for (1) is that if more than one language was given, we want a link to the name
-- in each language, as the name that's displayed is linked only to the first specified language.
-- However, if only one language was given, linking the language to the name is redundant.
-- The reason for (2) is that {{foreign name}} is often used when the name in the destination language
-- is spelled the same as the name in the source language (e.g. [[Clinton]] or [[Obama]] in Italian),
-- and in that case no name will be explicitly specified but we still want a link to the name in the
-- source language. The reason we restrict this to {{foreign name}} or {{name obor}}, not to
-- {{name translit}} or {{name respelling}}, is that {{name translit}} and {{name respelling}} ought to be
-- used for names spelled differently in the destination language (either transliterated or respelled), so
-- assuming the pagename is the name in the source language is wrong.
if names[1] and #sources > 1 or (iargs.foreign_name or iargs.obor) and not names[1] then
return m_links.language_link{
lang = sources[i], term = term_to_link, alt = sourcename, tr = "-"
}
else
return sourcename
end
end
if i == 1 and not iargs.foreign_name then
-- If at least one name is given, we say "A transliteration of the LANG surname FOO", linking LANG to FOO.
-- Otherwise we say "A transliteration of a LANG surname".
if names[1] then
table.insert(langsegs, get_source_link()) -- l10n: 中文不需要 "the"
else
table.insert(langsegs, sourcename) -- l10n: 中文不需要不定冠詞
end
else
table.insert(langsegs, get_source_link())
end
end
local langseg_text = m_table.serialCommaJoin(langsegs, {conj = "或"}) -- l10n
local augdim_text
if args.dim then
augdim_text = "[[指小詞]]" -- l10n
elseif args.aug then
augdim_text = "[[指大詞]]" -- l10n
else
augdim_text = ""
end
-- l10n: type= 的值本身已是中文(姓氏、男性名字……),不像英文還要把單數形對應成連結形。
local nametype_text = m_table.serialCommaJoin(args["type"], {conj = "和"}) .. augdim_text
if not iargs.foreign_name then
ins(langseg_text)
-- l10n: 未指定名字時,desctext 直接接在類型之後(「俄語姓氏的轉寫」);舊版在這種情況下
-- 會整個漏掉 desctext,只輸出「俄語姓氏」。
ins(nametype_text .. (names[1] and "" or "的" .. desctext))
else
-- l10n: {{foreign name}} 的中文語序是「使用於〔語言〕的〔類型〕」
ins(desctext .. langseg_text .. "的" .. nametype_text)
if names[1] then
ins(",")
end
end
local linked_names = {}
local embedded_comma = false
for _, name in ipairs(names) do
local linked_name = m_links.full_link(name, "term")
if name.q and name.q[1] or name.qq and name.qq[1] or name.l and name.l[1] or name.ll and name.ll[1] or
name.refs and name.refs[1] then
linked_name = require(decorations_module).format_decorations {
lang = name.lang,
text = linked_name,
q = name.q,
qq = name.qq,
l = name.l,
ll = name.ll,
refs = name.refs,
raw = true, -- since we put 'use-with-mention' around the entire text
}
end
if name.xlit then
embedded_comma = true
linked_name = linked_name .. "," .. m_links.language_link { lang = enlang, term = name.xlit } -- l10n
end
-- l10n: 中文把 desctext(轉寫/形譯詞……)接在名字之後,例如「Фру́нзе的轉寫」。
if not iargs.foreign_name then
linked_name = linked_name .. "的" .. desctext
end
if name.eq then
embedded_comma = true
linked_name = linked_name .. ",等同於 " .. m_links.language_link { lang = enlang, term = name.eq } -- l10n
end
table.insert(linked_names, linked_name)
end
if embedded_comma then
ins(table.concat(linked_names, ";或")) -- l10n
else
ins(m_table.serialCommaJoin(linked_names, {conj = "或"})) -- l10n
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(args.addl:sub(2))
else
ins("," .. args.addl)
end
end
ins("</span>")
local text = table.concat(textsegs)
if args.nocat then
return text
end
local categories = {}
local langfullname = lang:getFullName()
-- l10n: 中文分類名把目標語言放在中間(「英語男性名字的漢語譯名」),不像 enwt 統一加在最前面。
for _, nametype in ipairs(args.type) do
local function insert_cats(dimaugof)
local function insert_cats_type(ty)
if ty == "中性名字" then -- l10n
insert_cats_type("男性名字")
insert_cats_type("女性名字")
end
for _, source in ipairs(sources) do
local sourcename = source:getCanonicalName()
table.insert(categories, sourcename .. ty .. dimaugof .. "的" .. langfullname .. "譯名")
table.insert(categories, "派生自" .. sourcename .. "的" .. langfullname .. "詞")
table.insert(categories, "源自" .. sourcename .. "的" .. langfullname .. "借詞")
if iargs.obor then
table.insert(categories, "源自" .. sourcename .. "的" .. langfullname .. "形譯詞")
end
if source:getCode() ~= source:getFullCode() then
-- etymology language
table.insert(categories,
source:getFullName() .. ty .. dimaugof .. "的" .. langfullname .. "譯名")
end
end
end
insert_cats_type(nametype)
end
insert_cats("")
if args.dim then
insert_cats("指小詞") -- l10n
end
if args.aug then
insert_cats("指大詞") -- l10n
end
end
return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
return export
5mjonscf4izby7gb7e3e69fotsxcjc3
Module:Fi-pronunciation
828
1422015
9909064
9278179
2026-09-29T07:25:45Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909064
Scribunto
text/plain
local export = {}
-- used by cartesian_make and cartesian_combine. loaded as necessary
local m_bit32
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local find = m_str_utils.find
local gmatch = m_str_utils.gmatch
local gsub = m_str_utils.gsub
local len = m_str_utils.len
local lower = m_str_utils.lower
local match = m_str_utils.match
local sub = m_str_utils.sub
local U = m_str_utils.char
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local gsub_lookahead = require("Module:gsub lookahead")
local langcode = "fi"
-- spelled vowels
local vowels = "aeiouyåäö"
-- any of 'vowels' as a pattern
local vowel = "[" .. vowels .. "]"
-- spelled consonants
local consonants = "bcdfghjklmnpqrstvwxzšžʔ"
-- any of 'consonants' as a pattern
local consonant = "[" .. consonants .. "]"
-- straight apostrophe
local apostrophe = "'"
-- "tertiary stress", a weaker secondary stress (either rhythmic or in some compound words). is there a better way to represent this?
local tertiary = "ˌ"
-- include ˣ because final gemination does not affect rhymes
local ipa_symb = "ˣˈˌ"..tertiary.."̯̝̞̠̪"
-- orthographic symbols that signify separation of syllables
local sep_symbols = "'’./ +\"-"
-- these signify that the next syllable is an "initial" syllable in a word
-- all symbols from here should also be in sep_symbols
local stress_indicators = "ˈˌ" .. tertiary .. "/ +-"
local stress_indicator = "[" .. stress_indicators .. "]"
-- <<SYLLABIFICATION START>>
-- syllabification must also consider * a consonant
local syl_consonants = consonants .. "*"
local syl_consonant = "[" .. syl_consonants .. "]"
-- diphthongs and long vowels
-- 1 = long vowel, 2 = Vi, 3 = VU, 4 = ie/UO
-- 1 and 2 are long vowels/diphthongs everywhere
-- 3 are diphthongs in wordlet-initial syllables or open syllables
-- 4 are diphthongs in wordlet-initial syllables only
local vowel_sequences = {
["aa"] = 1, ["ee"] = 1, ["ii"] = 1, ["oo"] = 1,
["uu"] = 1, ["yy"] = 1, ["ää"] = 1, ["öö"] = 1,
["ai"] = 2, ["ei"] = 2, ["oi"] = 2,
["ui"] = 2, ["yi"] = 2, ["äi"] = 2, ["öi"] = 2,
["au"] = 3, ["eu"] = 3, ["iu"] = 3, ["ou"] = 3,
["äy"] = 3, ["ey"] = 3, ["iy"] = 3, ["öy"] = 3,
["ie"] = 4, ["uo"] = 4, ["yö"] = 4
}
-- sonority by consonant (higher number is more sonorous)
local sonorities = {
["j"] = -1, ["l"] = -1, ["r"] = -1, ["v"] = -1, ["w"] = -1,
["m"] = -2, ["n"] = -2, ["ŋ"] = -2,
["h"] = -3, ["z"] = -3, ["ž"] = -3,
["s"] = -4, ["š"] = -4, ["f"] = -4,
["b"] = -5, ["d"] = -5, ["g"] = -5,
["k"] = -6, ["p"] = -6, ["t"] = -6,
}
-- 'native' consonant clusters
local native_consonants = {
["lkk"] = true, ["nkk"] = true, ["rkk"] = true,
["nks"] = true,
["lpp"] = true, ["mpp"] = true, ["rpp"] = true,
["lsk"] = true, ["nsk"] = true, ["rsk"] = true,
["lss"] = true, ["nss"] = true, ["rss"] = true,
["lst"] = true, ["nst"] = true, ["rst"] = true,
["lts"] = true, ["rts"] = true,
["ltt"] = true, ["ntt"] = true, ["rtt"] = true,
}
local function split_by_sonority(cluster)
local i = len(cluster)
-- always split two-consonant clusters evenly
if i <= 2 then
return i - 1
end
-- always split 'native' three-consonant clusters to AB.C
if i == 3 and native_consonants[cluster] then
return 2
end
local ng = find(cluster, "ng")
-- never split ng
if ng then
return ng + 1 + split_by_sonority(sub(cluster, ng + 2))
end
local max_sonority = 0
while i > 1 do
local sonority = sonorities[sub(cluster, i, i)]
if sonority == nil then return len(cluster) - 1 end
if sonority >= max_sonority then break end
max_sonority = sonority
i = i - 1
end
return i
end
-- used for syllabification overrides (any text between these is not syllabified)
local syl_override_start = U(0xEFF0)
local syl_override_end = U(0xEFF1)
local syl_is_consonant = {}
local syl_is_vowel = {}
local syl_is_sep = {}
local syl_is_stressed = {}
for c in gmatch(syl_consonants, ".") do syl_is_consonant[c] = true end
for c in gmatch(vowels, ".") do syl_is_vowel[c] = true end
for c in gmatch(sep_symbols, ".") do syl_is_sep[c] = true end
for c in gmatch(stress_indicators, ".") do syl_is_stressed[c] = true end
-- mode may be one of the following:
-- default: naive hyphenation using Finnish hyphenation rules
-- "dots": use default rules, but add dots between syllable boundaries if allowed by keep_sep_symbols
-- "sonority": split consonant clusters according to the sonority sequencing principle
-- add dots for new splits (i.e. not those incurred by symbols) followed by multiple consonants if keep_sep_symbols allows it.
local function syllabify(word, keep_sep_symbols, mode)
local result = {} -- output syllables
local syllable_prefix = nil -- separator symbol if kept
local syllable_start = 1
local has_syllable = false
local cursor = 1
local found_vowel = false
local initial_syllable = true -- note: initial in wordlet, not word
local lower_word = lower(word)
local function keep_sep(k)
return (keep_sep_symbols == true or (type(keep_sep_symbols) == "string" and find(keep_sep_symbols, k, 1, true)))
end
local dots = ((mode == "dots") and keep_sep(".")) and "." or nil
local sonority = mode == "sonority"
local function add_syllable()
-- add a syllable if not empty
if has_syllable then
local syllable_text = sub(word, syllable_start, cursor - 1)
if syllable_prefix then
syllable_text = syllable_prefix .. syllable_text
end
table.insert(result, syllable_text)
-- reset state
syllable_prefix = dots
initial_syllable = false
syllable_start = cursor
has_syllable = false
found_vowel = false
end
end
local len_word = len(word)
-- prebuild patterns
local pattern_cc = "^" .. syl_consonant .. "+"
local pattern_vnc = "^" .. vowel .. "+[^" .. syl_consonants .. "]"
local pattern_vcv = "^" .. vowel .. "+" .. syl_consonant .. vowel
local pattern_ccv = pattern_cc .. vowel
local pattern_sep = "^%(?([" .. sep_symbols .. "])%)?"
local pattern_stressed = "^%(?[" .. stress_indicators .. "]%)?"
while cursor <= len_word do
local symbol = sub(lower_word, cursor, cursor)
if syl_is_consonant[symbol] then
-- C...
local symbol2 = sub(lower_word, cursor + 1, cursor + 1)
if syl_is_vowel[symbol2] then
-- CV: end current syllable if we have found a vowel
if found_vowel then
add_syllable()
found_vowel = false
end
cursor = cursor + 1
elseif (sonority and found_vowel and syllable_start < cursor
and syl_is_consonant[symbol2] and match(lower_word, pattern_ccv, cursor)) then
-- (V)C+V: split by sonority
local cluster = match(lower_word, pattern_cc, cursor)
local split_point = split_by_sonority(cluster)
if split_point ~= nil then
-- split at the specified position.
cursor = cursor + split_point
add_syllable()
local len_cluster = len(cluster)
-- add a dot if dots are allowed, and if the split point is not before the final consonant
local add_dot = keep_sep(".") and split_point ~= len_cluster - 1
if add_dot then syllable_prefix = "." end
cursor = cursor + len_cluster - split_point
found_vowel = false
end
else
-- C: continue
cursor = cursor + 1
end
has_syllable = true
elseif syl_is_vowel[symbol] then
if found_vowel then
-- already found a vowel, end current syllable
add_syllable()
end
local vowel_length = 1
found_vowel = true
local vv = sub(lower_word, cursor, cursor + 1)
local rank = vowel_sequences[vv]
if rank then
-- check for diphthongs or long vowels
local max_rank
if initial_syllable then
max_rank = 4
else
-- check for an open syllable
-- the syllable is open if it is followed by a consonant, except if it is followed by a vowel
local f1 = sub(lower_word, cursor + 2, cursor + 2)
local f2 = sub(lower_word, cursor + 3, cursor + 3)
if syl_is_consonant[f1] and not syl_is_vowel[f2] then
-- closed syllable
max_rank = 2
else
-- open syllable
max_rank = 3
end
end
if rank <= max_rank then
vowel_length = 2
end
end
cursor = cursor + vowel_length
has_syllable = true
else
local sepchar
if symbol == "(" then
-- look for e.g. (.)
sepchar = match(lower_word, pattern_sep, cursor)
else
sepchar = syl_is_sep[symbol] and symbol
end
if sepchar then
-- separates syllables
add_syllable()
syllable_prefix = nil
initial_syllable = syl_is_stressed[sepchar]
cursor = cursor + (symbol == "(" and 3 or 1)
if not keep_sep(sepchar) then
syllable_start = cursor
end
found_vowel = false
elseif symbol == syl_override_start then
-- override. separate syllables
if has_syllable then
add_syllable()
elseif syllable_start < cursor then
-- has retained separator character, add to prefix
syllable_prefix = (syllable_prefix or "") .. sub(lower_word, syllable_start, cursor - 1)
end
syllable_start = cursor + (keep_sep(syl_override_start) and 0 or 1)
local override_end = find(lower_word, syl_override_end, syllable_start)
if not override_end then error("syl_override_start must end in syl_override_end") end
has_syllable = true
if keep_sep(syl_override_end) then
cursor = override_end + 1
add_syllable()
else
cursor = override_end
add_syllable()
cursor = cursor + 1
end
syllable_start = cursor
else
-- ?: continue
cursor = cursor + 1
has_syllable = true
end
end
end
add_syllable()
return result
end
export.syllabify = syllabify
-- <<SYLLABIFICATION END>>
-- <<IPA START>>
-- IPA data
-- Finnish vowels in IPA
local ipa_vowels = "ɑeiouyæø"
local ipa_vowel = "[" .. ipa_vowels .. "]"
-- Finnish consonants in IPA
local ipa_consonants = "kptɡgbdfʔsnmŋlrhʋʃʒrjçɦx"
local ipa_consonant = "[" .. ipa_consonants .. "]"
-- IPA diacritics used in transcriptions of Finnish
local ipa_diacritics = "̝̞̠̪"
local ipa_diacritic = "[" .. ipa_diacritics .. "]"
local nonsyllabic = U(0x32F) -- inverted breve below
local unreleased = U(0x31A)
local nasalized = U(0x303)
local long = "ː"
-- IPA: either a spelled consonant/vowel or an IPA consonant/vowel
local spelled_consonant = "[" .. ipa_consonants .. "cšvwxzž]"
local spelled_vowel = "[" .. ipa_vowels .. "aäö]"
local plosives = "kptbdɡ"
local wordlet_sep = "[/-]"
local stress_p = "[ˈˌ" .. tertiary .. "]"
local stress_s = "[ˌ" .. tertiary .. "]"
local stress_pd = "[ˈˌ" .. tertiary .. "%.]"
local stress_sd = "[ˌ" .. tertiary .. "%.]"
local letter_sequence_phonemes = {
-- ALL FIRST PARTS MUST START WITH ^. longest sequence must come first.
{ "^ng", "ŋː" },
{ "^qu", "kʋ" },
{ "^zz", "ts" },
}
local letters_phonemes = {
-- symbols outside this list are not changed
["a"] = "ɑ",
["ä"] = "æ",
["ö"] = "ø",
["å"] = "o",
["c"] = "k",
["g"] = "ɡ",
["q"] = "k",
["v"] = "ʋ",
["š"] = "ʃ",
["ž"] = "ʒ",
["x"] = "ks",
["*"] = "ˣ",
["'"] = ".",
}
local replacements_narrow = {
["ɑ"] = "ɑ̝",
["e"] = "e̞",
["ø"] = "ø̞",
["o"] = "o̞",
["t"] = "t̪",
["s"] = "s̠"
}
-- This adds letters_phonemes["e"] = "e", letters_phonemes["i"] = "i", etc.
for letter in gmatch("eiouydhfjklmnprstu", ".") do
letters_phonemes[letter] = letter
end
local function apply_post_fixes(p)
-- initial <gn> is /gn/
p = gsub(p, "ˈŋn", "ˈɡn")
-- ŋ is short before consonant (by default)
p = gsub(p, "ŋ"..long.."("..ipa_consonant..")", "ŋ%1")
-- dissimilation of vowels by sandhi
p = gsub(p, "("..ipa_vowel..ipa_diacritic.."*)(["..long..nonsyllabic.."]?)("..stress_s..")%1", "%1%2%3(ʔ)%1")
-- C1(.):C2 -> C1(.)(:)C2
p = gsub(p, "("..ipa_consonant..ipa_diacritic.."*)"..long.."("..ipa_consonant..ipa_diacritic.."*)", "%1("..long..")%2")
p = gsub(p, "("..ipa_consonant..ipa_diacritic.."*)%.%1("..ipa_consonant..ipa_diacritic.."*)", "%1.(%1)%2")
p = gsub(p, "("..ipa_consonant..ipa_diacritic.."*)%(%.?%)%1("..ipa_consonant..ipa_diacritic.."*)", "%1(.%1)%2")
return p
end
local function apply_post_fixes_narrow(p)
-- C1:+C2 -> C1+(C1)C2
p = gsub(p, "("..ipa_consonant..ipa_diacritic.."*)"..long..tertiary.."("..ipa_consonant..ipa_diacritic.."*)", "%1"..tertiary.."(%1)%2")
-- C1+C1C2 -> C1+(C1)C2
-- but C1C2+C2C3 --> C1(C2)+C2C3
p = gsub(p, "("..ipa_consonant..ipa_diacritic..")("..ipa_consonant..ipa_diacritic.."*)"..tertiary.."(%.?)%2("..ipa_consonant..ipa_diacritic.."*)", "%1(%2)"..tertiary.."%2%3%4")
p = gsub(p, "("..ipa_consonant..ipa_diacritic.."*)"..tertiary.."(%.?)%1("..ipa_consonant..ipa_diacritic.."*)", "%1"..tertiary.."(%1)%2%3")
p = gsub(p, "("..ipa_consonant..ipa_diacritic.."*)"..tertiary.."%(?%.?%)%1("..ipa_consonant..ipa_diacritic.."*)", "%1"..tertiary.."(.%1)%2")
-- t is alveolar in /ts/ and /st/
p = gsub(p, "t̪("..stress_pd.."?%s*)s̠", "t%1s̠")
p = gsub(p, "s̠("..stress_pd.."?%s*)t̪", "s̠%1t")
-- n and l become dental in /lt/ /nt/ /tl/ /tn/
p = gsub(p, "([ln])("..stress_pd.."?%s*)t̪", "%1̪%2t̪")
p = gsub(p, "t̪("..stress_pd.."?%s*)([ln])", "t̪%1%2̪")
-- long j, v after i, u diphthong
p = gsub(p, "(i"..nonsyllabic..")j("..ipa_vowel..")", "%1j("..long..")%2")
-- /ʋ/ after /u/ usually realized as /w/ (see Suomi, Toivanen and Ylitalo 2008, p. )
p = gsub(p, "(u"..nonsyllabic..")ʋ("..ipa_vowel..")", "%1w("..long..")%2")
-- cleanup
p = gsub(p, "("..stress_s..")%.", "%1")
p = gsub(p, "("..stress_pd..")"..stress_s, "%1")
-- tautosyllabic nasals nasalize vowels between them (see Suomi, Toivanen and Ylitalo 2008, p. 22)
--p = gsub(p, "([mnŋ]"..long.."?)("..ipa_vowel..")("..ipa_diacritic.."*)([mnŋ])(.?)", function (n0, nv, nvd, n1, anchor)
-- -- this cannot be simplified to "(.?)" => "([^" .. ipa_vowels .. "]?)", otherwise a vowel after would match
-- if not find(anchor, ipa_vowel) then
-- return n0 .. nv .. nasalized .. nvd .. n1 .. anchor
-- end
--end)
-- sandhi: nm > mm, np > mp, nb > mb, nk > ŋk, ng > ŋg
p = gsub(p, "nm", "m"..long)
p = gsub(p, "n("..stress_pd.."?%s*)([ɡk])", "ŋ%1%2")
p = gsub(p, "n("..stress_pd.."?%s*)([mpb])", "m%1%2")
p = gsub(p, "[nm]("..stress_pd.."?%s*)([f])", "ɱ%1%2")
-- handle potentially long consonants over secondary stresses
p = gsub(p, "("..ipa_consonant..ipa_diacritic.."*)%("..long.."%)("..stress_s..")", "%2%1("..long..")")
p = gsub(p, "("..stress_s..")("..ipa_consonant..ipa_diacritic.."*)%("..long.."%)", "(%2)%1%2")
p = gsub(p, "(["..plosives.."]"..ipa_diacritic.."*)("..stress_s..")%1", "%1"..unreleased.."%2%1")
p = gsub(p, "%((["..plosives.."]"..ipa_diacritic.."*)%)("..stress_s..")%1", "(%1"..unreleased..")%2%1")
-- [k] allophone before front vowels (see Suomi, Toivanen and Ylitalo 2008, p. 27)
p = gsub(p, "k([eiyæø])", "k̟%1")
return p
end
-- -1 = degenerate
-- 0 = light
-- 1 = heavy with coda
-- 2 = heavy without coda (long vowel/diphthong)
-- 99 = stressed
local class_stressed_syllable = 99
local function classify_syllable(syllable)
if match(syllable, "^" .. stress_indicator) then
return class_stressed_syllable
end
local cleaned = gsub(syllable, "%(?%*?%)?$", "")
if match(cleaned, spelled_vowel .. spelled_vowel .. "$") then
return 2
elseif match(cleaned, spelled_consonant .. "$") then
return match(cleaned, spelled_vowel) and 1 or -1
elseif match(cleaned, spelled_vowel) then
return 0
end
return -1
end
local function has_hiatus(left, right)
return match(left, spelled_vowel .. "$") and match(right, "^%(?%.?%)?" .. spelled_vowel)
end
local trad_lookahead = 2 -- how many unstressed syllables at most in a single unit, thus max consecutive unstressed syllables
local function add_rhythmic_stress_ltr(hyph, classes)
-- traditional syllable stress assigning algorithm (e.g. in (V)ISK)
-- assign from left to right, but shift to later syllables if a light syllable is followed by a heavy syllable
local add_stress = {}
local n = #classes
local prev_stress
for index, class in ipairs(classes) do
local stressed = class == class_stressed_syllable
if not stressed and not prev_stress and index < n then
-- shift stress if current syllable light and a heavy syllable occurs later (except as the last syllable)
local has_later_heavy_syllable
local lookahead_end = math.min(index + trad_lookahead, n - 1)
for i = index + 1, lookahead_end do
if classes[i] > 0 then
has_later_heavy_syllable = true
break
end
end
stressed = index == n - 1 or (class > 0) or not has_later_heavy_syllable
end
-- check if next syllable already stressed, and do not stress this syllable if so
if stressed and index < n then
stressed = stressed and classes[index + 1] ~= class_stressed_syllable
end
if index > 1 and stressed then
add_stress[index] = true
end
prev_stress = stressed
end
return add_stress
end
local function add_rhythmic_stress_rtl(hyph, classes)
-- based on Karvonen (2005) "Word prosody in Finnish"
-- run algorithm for assigning stresses:
-- (1) analyze each segment started by an already stressed syllable
-- as a separate prosodic word
-- (2) assign from *right to left*
-- (3) final syllable is never stressed (not by this algorithm)
-- (4) stress may shift back by a single (unstressed) syllable
-- to a better candidate:
-- (C)VC is better than (C)V
-- (C)VV is better than (C)V and (C)VC
-- (5) if the final syllable in a prosodic word begins with a vowel
-- and the previous syllable ends in a vowel, skip it
local add_stress = {}
local foot = 0
local word_final = true
local index = #classes
while index > 0 do
local stressed = classes[index] == class_stressed_syllable
if stressed then
-- reset
foot = 0
word_final = true
elseif word_final and index > 1 and has_hiatus(hyph[index - 1], hyph[index]) then
-- hiatus break at the end of a prosodic word: do nothing
elseif foot == 0 then
foot = foot + 1
-- try to assign a stress.
-- is the previous syllable stressed?
elseif index > 1 and classes[index - 1] == class_stressed_syllable then
-- no two stresses in a row, so do nothing.
else
-- we will assign a stress somewhere.
local class = classes[index]
-- is this syllable preceded by an unstressed syllable
-- with more priority?
if index > 2 and classes[index - 2] ~= class_stressed_syllable and
classes[index - 1] > classes[index] then
-- shift back by one syllable
index = index - 1
end
-- assign stress, and continue.
add_stress[index] = true
foot = 0
end
index = index - 1
word_final = false
end
return add_stress
end
local function add_rhythmic_stress_collect(hyph, classes, add_stress)
-- add stress marks
local rhythmic_stress = tertiary
local collected = {}
for index = 1, #hyph do
if add_stress[index] and classes[index] < class_stressed_syllable then
local syllable = hyph[index]
if index > 1 and match(syllable, "^g") and match(hyph[index - 1], "[nŋ]$") then
-- make sure [nŋ].g > ŋ.ŋ
collected[index - 1] = gsub(collected[index - 1], "n$", "ŋ")
syllable = gsub(syllable, "^g", "ŋ")
end
collected[index] = rhythmic_stress .. gsub(syllable, "^%.", "")
else
collected[index] = hyph[index]
end
end
return table.concat(collected)
end
-- applied *before* IPA conversion
-- returns one result, or possibly two results
local function add_rhythmic_stress(word, assign_rtl)
-- keep_sep_symbols = true
local hyph = syllabify(word, true, "sonority")
local final_heavy = false
local collected
local add_rhythmic_stress_impl = assign_rtl and add_rhythmic_stress_rtl or add_rhythmic_stress_ltr
-- find stressed syllables and classify all syllables
local classes = {}
for index, syllable in ipairs(hyph) do
if index == 1 then
classes[index] = class_stressed_syllable
else
classes[index] = classify_syllable(syllable)
end
end
local add_stress
-- final heavy syllable may be stressed if preceded by an unstressed
-- light syllable (in words with at least 3 syllables)
final_heavy = #classes >= 3 and classes[#classes] > 0 and classes[#classes - 1] == 0
add_stress = add_rhythmic_stress_impl(hyph, classes)
local res1 = add_rhythmic_stress_collect(hyph, classes, add_stress)
local res2
if final_heavy then
-- make final syllable stressed
local actual_final = classes[#classes]
classes[#classes] = class_stressed_syllable
add_stress = add_rhythmic_stress_impl(hyph, classes)
-- so that add_rhythmic_stress_collect actually adds it
classes[#classes] = actual_final
add_stress[#classes] = true
res2 = add_rhythmic_stress_collect(hyph, classes, add_stress)
end
return res1, res2
end
local pattern_syllable_c = "^" .. consonant
local pattern_syllable_open = "^" .. consonant .. vowel
local function IPA_handle_diphthongs_replace_open_noninitial(diphthong, after1, after2)
if syl_is_consonant[after1] then
-- consonant after diphthong
-- must be followed by vowel so that it's part of the
-- following syllable, else it's in this syllable
-- and thus this syllabie is closed
if syl_is_vowel[after2] then
-- single consonant followed by vowel -> this syllable is open
return diphthong .. nonsyllabic, after1 .. after2
else
-- single consonant not followed by vowel -> this syllable is closed
return diphthong, after1 .. after2
end
end
if after1 == nonsyllabic then
-- already marked as nonsyllabic
return diphthong, after1 .. after2
end
-- no consonant after diphthong -> this syllable is open
return diphthong .. nonsyllabic, after1 .. after2
end
local pattern_word_initial = "^[^" .. stress_indicators .. vowels .. "]*"
local pattern_after_sep = stress_indicator .. "[^" .. vowels .. "]*"
local function IPA_handle_diphthongs_word_initial(IPA, diphthong)
IPA = gsub(IPA, pattern_word_initial .. diphthong, "%0" .. nonsyllabic)
IPA = gsub(IPA, pattern_after_sep .. diphthong, "%0" .. nonsyllabic)
return IPA
end
local function IPA_handle_diphthongs_word_initial_or_open(IPA, diphthong)
IPA = gsub(IPA, pattern_word_initial .. diphthong, "%0" .. nonsyllabic)
IPA = gsub(IPA, pattern_after_sep .. diphthong, "%0" .. nonsyllabic)
IPA = gsub_lookahead(IPA, "(" .. diphthong .. ")(.?)(.?)", IPA_handle_diphthongs_replace_open_noninitial)
return IPA
end
-- Add nonsyllabic diacritic after last vowel of diphthong.
local function IPA_handle_diphthongs(IPA)
-- /Vi/ everywhere
IPA = gsub(IPA, "[aeouyäö]i", "%0" .. nonsyllabic)
-- /ie/, /uo/, /yö/ root-initially
IPA = IPA_handle_diphthongs_word_initial(IPA, "ie")
IPA = IPA_handle_diphthongs_word_initial(IPA, "uo")
IPA = IPA_handle_diphthongs_word_initial(IPA, "yö")
-- /VU/ root-initially or in open syllables
IPA = IPA_handle_diphthongs_word_initial_or_open(IPA, "[aoei]u")
IPA = IPA_handle_diphthongs_word_initial_or_open(IPA, "[äöei]y")
return IPA
end
local function fi_IPA_wordlet(term, is_narrow)
local cursor = 1
local len_term = len(term)
local phonemes = {}
while cursor <= len_term do
local target
-- try letter_sequence_phonemes first
for _, replacement in ipairs(letter_sequence_phonemes) do
if match(term, replacement[1], cursor) then
target = replacement[2]
cursor = cursor + len(replacement[1]) - 1 -- account for ^
break
end
end
-- then letters_phonemes
if not target then
local letter = sub(term, cursor, cursor)
target = letters_phonemes[letter] or letter
cursor = cursor + 1
end
table.insert(phonemes, target or letter)
end
local result = table.concat(phonemes)
result = gsub(result, "nk", "ŋk")
result = gsub(result, "ŋ[ŋ" .. long .. "]$", "ŋ")
if is_narrow then
-- articulation of h (Suomi, Toivanen & Ylitalo 2008, p. 28)
result = gsub_lookahead(result, "(.?)h(.?)",
function (before, after)
local h
if after ~= "" and after ~= "h" then
if before ~= "" and (ipa_vowels:find(before) or before == nonsyllabic) then
if ipa_consonants:find(after) then
-- vihma, yhtiö
if before == "i" or before == "y" then
h = "ç"
-- mahti, kohme, tuhka
elseif before == "ɑ" or before == "o" or before == "u" then
h = "x"
end
-- maha
elseif ipa_vowels:find(after) then
h = "ɦ"
end
end
end
if h then
return before .. h, after
end
end)
-- double letter replacement must be handled earlier here
result = gsub(result, "(%a)%1", "%1" .. long)
for letter, phoneme in pairs(replacements_narrow) do
result = gsub(result, letter, phoneme)
end
end
return result
end
local function fi_IPA_termwordlets(term, is_narrow, hyphenstress)
local wordlet_start = 1
local wordlet_IPAs = {}
local split_pattern = "(" .. wordlet_sep .. ")"
while true do
local wordlet_sep_at, _, wordlet_sep = find(term, split_pattern, wordlet_start)
local wordlet_end = wordlet_sep_at and (wordlet_sep_at - 1)
local part = sub(term, wordlet_start, wordlet_end)
local stress
if wordlet_start == 1 then
stress = "ˈ" -- primary
elseif wordlet_sep == "/" then
stress = "ˌ" -- secondary
else
stress = hyphenstress
end
table.insert(wordlet_IPAs, stress .. fi_IPA_wordlet(part, is_narrow))
if wordlet_sep_at == nil then
break
else
wordlet_start = wordlet_sep_at + 1
end
end
return wordlet_IPAs
end
-- Generates the broad and optionally also the narrow IPA transcription.
-- If make_narrow is true, the narrow transcription is provided;
-- otherwise it is invalid.
local function fi_IPA_combined_term(term, make_narrow)
local hyphenstress = "ˌ" -- secondary by default
local is_prefix, is_suffix
if find(term, "%/") then
hyphenstress = tertiary -- tertiary if we have slashes
end
term = lower(term)
term, is_suffix = gsub(term, "^%-+", "")
term, is_prefix = gsub(term, "%-+$", "")
term = IPA_handle_diphthongs(term)
-- Run IPA for wordlets.
local IPA = table.concat(fi_IPA_termwordlets(term, false, hyphenstress))
-- Replace double letters (vowels or consonants) with single letter plus length sign,
-- and apply post fixes.
IPA = apply_post_fixes(gsub(IPA, "(%a)%1", "%1" .. long))
local IPA_narrow
if make_narrow then
local term_narrow = add_rhythmic_stress(term)
IPA_narrow = table.concat(fi_IPA_termwordlets(term_narrow, true, hyphenstress))
-- Handle * in narrow transcription.
if find(IPA_narrow, "ˣ", nil, true) then
IPA_narrow = gsub(IPA_narrow, "ˣ(%)?%s*"..stress_p.."?)((.?)" .. ipa_diacritic .. "*)",
function (post, after, potential_consonant)
if potential_consonant == "" then
if find(post, "^%)") then
return "ʔ" .. post .. after
else
return post .. "(ʔ)" .. after
end
elseif ipa_consonants:find(potential_consonant) then
if #post > 0 then
local amark = ""
if plosives:find(sub(after, 1, 1)) then
amark = unreleased
end
return after .. amark .. post .. after
else
return post .. after .. long
end
else
return post .. "(ʔ)" .. after
end
end)
IPA_narrow = gsub(IPA_narrow, "%(%)", "")
end
-- Apply post fixes.
IPA_narrow = apply_post_fixes_narrow(apply_post_fixes(IPA_narrow))
if is_prefix > 0 then
IPA_narrow = IPA_narrow .. "-"
end
if is_suffix > 0 then
IPA_narrow = "-" .. IPA_narrow
end
end
if is_prefix > 0 then
IPA = IPA .. "-"
end
if is_suffix > 0 then
IPA = "-" .. IPA
end
return IPA, IPA_narrow
end
local function fi_IPA_term(term, is_narrow)
local broad, narrow = fi_IPA_combined_term(term, is_narrow)
return is_narrow and narrow or broad
end
-- exports for tests etc.
export.add_rhythmic_stress = add_rhythmic_stress
export.fi_IPA_term = fi_IPA_term
-- {{fi-IPA}}
function export.fi_IPA_entrypoint(term)
if type(term) == "table" then
term = term:getParent().args[1]
end
local title = mw.title.getCurrentTitle().text
if not term then
term = title
elseif term == "*" then
term = title .. "*"
end
local no_count = match(term, " ")
IPA_narrow = fi_IPA_term(term, true)
IPA = fi_IPA_term(term, false)
return m_IPA.format_IPA_full {
lang = require("Module:languages").getByCode(langcode),
items = {{pron = "/" .. IPA .. "/"}, {pron = "[" .. IPA_narrow .. "]"}},
no_count = no_count,
}
end
-- <<IPA END>>
local function cleanup_title(x)
return lower(gsub(x, "–", "-"))
end
-- these two functions are used to build Cartesian products of triplets XAB,
-- such that e.g. (X1, A1, B1), (X2, A2, B2), (X3, _, _)
-- creates the combinations
-- X1A1X2A2X3, X1A1X2B2X3, X1B1X2A2X3, X1B1X2B2X3
local function cartesian_make(parts, n)
local result = parts[1][1]
local k = 1
for i = 2, #parts do
if m_bit32.band(n, k) > 0 then
result = result .. parts[i - 1][3] .. parts[i][1]
else
result = result .. parts[i - 1][2] .. parts[i][1]
end
k = m_bit32.lshift(k, 1)
end
return result
end
local function cartesian_combine(parts)
m_bit32 = m_bit32 or require("Module:bit32")
local n = m_bit32.lshift(1, #parts - 1)
local results = {}
for i = 0, n - 1 do
table.insert(results, cartesian_make(parts, i))
end
return results
end
local function split_by_optional_break(word, only_breaking_diphthongs, prefer_hyphen_first)
local parts = {}
local i = 1
local found = false
while true do
local j, je = find(word, "%([.-]%)", i)
if j == nil then break end
local allow_break = true
if only_breaking_diphthongs then
local prefix = sub(word, i, j - 1)
local diphthong = sub(word, j - 1, j - 1) .. sub(word, j + 3, j + 3)
-- if a dot, only when breaks a diphthong
allow_break = sub(word, j + 1, j + 1) ~= "." or (
-- never a diphthong if two vowels precede
not match(prefix, vowel .. vowel .. "$")
and vowel_sequences[diphthong])
end
if allow_break then
found = true
-- prefer dots last if vowels on both sides
local prev = sub(word, i, j - 1)
local symbol = sub(word, j + 1, j + 1)
local prefer_first
if symbol == "." then
prefer_first = not (match(prev, vowel .. "$") and match(word, "^" .. vowel, j + 3))
elseif symbol == "-" then
prefer_first = prefer_hyphen_first
end
if prefer_first then
table.insert(parts, { prev, symbol, "" })
else
table.insert(parts, { prev, "", symbol })
end
end
i = je + 1
end
if not found then return { word } end
table.insert(parts, { sub(word, i), "", "" })
return cartesian_combine(parts)
end
local function get_autohyphenate_forms(word, title)
word = gsub(word, "%([*ˣ:ː]%)", "")
word = gsub(word, "(.)ː", "%1%1")
word = gsub(word, "[" .. ipa_symb .. "ˣ*]", "")
word = gsub(word, "/", "-")
word = gsub(word, "^-", "")
word = gsub(word, "ŋn", "gn")
if lower(title) == title then
word = lower(word)
else
-- find letters in title and try to match them
local letters = {}
for letter in gmatch(title, "%a") do
table.insert(letters, letter)
end
local respelled = ""
local letter_index = 1
for character in gmatch(word, ".") do
if match(character, "%a") then
local next_letter = letters[letter_index]
if lower(next_letter) == lower(character) then
respelled = respelled .. next_letter
letter_index = letter_index + 1
else
respelled = respelled .. character
end
else
respelled = respelled .. character
end
end
word = respelled
end
return split_by_optional_break(word, nil, true)
end
local function get_autorhyme_forms(word)
word = lower(word)
word = gsub(word, "%([*ˣ:ː]%)", "")
-- applies gemination mid-word for rhymes
word = gsub(word, "[*ˣ](" .. consonant .. ")", "%1ː")
word = gsub(word, "[*ˣ]", "")
-- convert long vowel marks
word = gsub(word, "(.)ː", "%1%1")
-- remove IPA symbols and normalize +/ to -
word = gsub(word, "[" .. ipa_symb .. "]", "")
word = gsub(word, "[/+]", "-")
return split_by_optional_break(word, nil, true)
end
local function generate_rhyme(word)
-- convert syllable weight to hyphen for next routine
-- (just in case these are included manually... even if they shouldn't be)
local fmtword = gsub(word, "[ˈˌ"..tertiary.."]", "-")
fmtword = gsub(word, "'", ".")
local sylcount = #syllabify(fmtword, ".")
-- get final wordlet of a compound word
local last_hyph = find(fmtword, "%-[^%-]*$") or 0
local last_wordlet = sub(fmtword, last_hyph + 1)
-- use fi-IPA rhythmic stress generator
local stressed = add_rhythmic_stress(last_wordlet)
-- find last stress mark and extract everything after that
local res = match(stressed, ".*" .. stress_indicator .. "(.*)")
if not res or res == "" then res = last_wordlet end
-- remove initial consonants, convert to IPA, remove IPA symbols
res = gsub(res, "^%.", "")
res = gsub(res, "^" .. consonant .. "+", "")
res = fi_IPA_combined_term(res, false)
res = gsub(res, "[" .. ipa_symb .. "]", "")
res = gsub(res, "^%.", "")
return res, sylcount
end
-- for testing
export.generate_rhyme = generate_rhyme
local function add_trivowel_dots(pron)
-- find sequences of >= 3 vowels and record their indices
local toggles = {}
local scan = 1
while true do
local i0, i1 = find(pron, "[aeiouyäö][aeiouyäö][aeiouyäö]+", scan)
if i0 == nil then break end
table.insert(toggles, i0 + 1)
table.insert(toggles, i1)
scan = i1 + 1
end
table.insert(toggles, len(pron) + 1)
-- generate hyphenation, and add dots within multivowel sequences
local dots = {}
local hyph = syllabify(pron, true)
local index = 0
local recons = ""
scan = 1
for _, hpart in ipairs(hyph) do
index = index + len(hpart)
while index >= toggles[scan] do
scan = scan + 1
end
recons = recons .. hpart
if scan % 2 == 0 then
recons = recons .. "."
end
end
return recons
end
local function pron_equal(title, pron)
if not pron or pron == "" then
return true
end
-- handle slashes, pluses and quotes as hyphens
pron = gsub(pron, '[/+"]', "-")
-- remove optional lengthening/shortening/syllable break/gemination, should not cause any issues
pron = gsub(pron, "%([*ˣ.:ː-]%)", "")
-- remove gemination asterisks and syllable separating dots
pron = gsub(pron, "[*%." .. syl_override_start .. syl_override_end .. "]", "")
-- map existing glottal stops to apostrophes
pron = gsub(pron, "%(?ʔ%)?", apostrophe)
-- /ŋn/ for /gn/ is fine
pron = gsub(pron, "ŋn", "gn")
-- remove hyphens but also apostrophes right after hyphens
-- (so that glottal stop is allowed after hyphen separating two same vowels)
pron = gsub(pron, "-" .. apostrophe .. "?", "")
title = gsub(cleanup_title(title), "-", "")
return pron == lower(title)
end
local function pron_equal_special_cases(title, pronunciation)
if find(title, "ruoa") then
-- very common exception - support it
local pronunciation_ruua = gsub(pronunciation, "ruua", "ruoa")
if pron_equal(title, lower(pronunciation_ruua)) then
return pronunciation_ruua
end
end
-- fall through to return nil
end
local bracket_delimiters = {
["/"] = "[%/]",
["-"] = "[%/%-]",
['"'] = '[%/%-%"]',
}
local function treat_brackets(pronunciation_with_brackets)
local result = {}
local cursor = 1
while true do
local bracket_start, bracket_end, bracket_text = find(pronunciation_with_brackets, "(%b[])", cursor)
if bracket_start == nil then
table.insert(result, sub(pronunciation_with_brackets, cursor))
break
end
local delimiter = match(bracket_text, '^.([%/"])')
-- find last slash/hyphen
local terminate = match(sub(pronunciation_with_brackets, 1, bracket_start), "^.*()" .. bracket_delimiters[delimiter or "-"])
if cursor > 1 and (not terminate or terminate < cursor) then
error("bracket {} replacement string had no matching delimiter " .. (delimiter or "-"))
end
if terminate then
table.insert(result, sub(pronunciation_with_brackets, cursor, terminate))
end
-- tag replacement with overrides for syllabification
table.insert(result, syl_override_start .. sub(bracket_text, delimiter and 3 or 2, -2) .. syl_override_end)
cursor = bracket_end + 1
end
return table.concat(result)
end
local function convert_quotes_to(syllabification_cache, text, stress, nostress)
if find(text, '"') then
if nostress and stress ~= nostress then
-- syllabify, and determine which way to go
-- output nostress if the preceding syllable was stressed,
-- otherwise output stress
local syllables = syllabification_cache[text]
if not syllables then
syllables = syllabify(text, true)
syllabification_cache[text] = syllables
end
local last_stressed = false
for i, syl in ipairs(syllables) do
-- first syllable always stressed
if i == 1 or match(syl, "^" .. stress_indicator) then
last_stressed = true
else
if match(syl, '^"') then
local next_stressed = i == #syllables or match(syllables[i + 1], "^" .. stress_indicator)
syllables[i] = gsub(syl, '^"', (last_stressed or next_stressed) and nostress or stress)
end
last_stressed = false
end
end
text = table.concat(syllables)
else
text = gsub(text, '"', stress)
end
end
return text
end
local function fi_cleanup_syllabification(syl, all_dots)
syl = gsub(syl, '[ /"]', "-")
syl = gsub(syl, "[+'’]", ".")
if all_dots then syl = gsub(syl, "%-", ".") end
return syl
end
local function fi_hyphenation_from_syllabification(syl)
syl = fi_cleanup_syllabification(syl)
-- all syllable boundaries are now - or .
-- first, ban any dots adjacent to a hyphen
syl = gsub(syl, "%.([^.]-)%-", "%1-")
syl = gsub(syl, "%-([^.]-)%.", "-%1")
-- then ban any dots that only have a single vowel
syl = gsub(syl, "^(" .. vowel .. ")%.", "%1")
syl = gsub(syl, "%.(" .. vowel .. ")$", "%1")
-- in the case x.V.y, technically both xV.y and x.Vy are valid
-- but here, we only allow xV.y, because there is no way to mark
-- "allow this or this" without listing all allowed options separately
-- which can result in exponentially many hyphenations shown
syl = mw.ustring.gsub(syl, "%.(" .. vowel .. ")%.", "%1.")
return mw.text.split(syl, "[.-]")
end
function export.show(frame)
local title = mw.title.getCurrentTitle().text
local lang = require("Module:languages").getByCode(langcode)
local pronunciation = { "" }
local ipa = { nil }
local audio = { }
local qualifiers = { }
local rhymes = { nil }
local syllabifications = { nil }
local homophones = { }
local syllabification_labels = { }
local rhyme_labels = { }
local homophone_labels = { }
local nohyphen = false
local norhymes = false
local compound_suffix = false
local categories = { }
if type(frame) == "table" then
local params = {
[1] = { list = true, default = "", allow_holes = true },
["ipa"] = { list = true, default = nil, allow_holes = true },
["h"] = { list = true, default = nil, allow_holes = true }, ["hyphen"] = {},
["r"] = { list = true, default = nil, allow_holes = true }, ["rhymes"] = {},
["a"] = { list = true, default = nil }, ["audio"] = {},
["ac"] = { list = true, default = nil }, ["caption"] = {},
["hh"] = { default = "" }, ["homophones"] = {},
["q"] = { list = true, default = nil, allow_holes = true },
["hp"] = { list = true, default = nil, allow_holes = true },
["rp"] = { list = true, default = nil, allow_holes = true },
["hhp"] = { list = true, default = nil, allow_holes = true },
["nohyphen"] = { type = "boolean", default = false },
["norhymes"] = { type = "boolean", default = false },
["csuffix"] = { type = "boolean", default = false },
["title"] = {}, -- for debugging or demonstration only
}
local args, further = require("Module:parameters").process(frame:getParent().args, params, true)
title = args["title"] or title
pronunciation = args[1]
ipa = args["ipa"]
syllabifications = args["h"]
rhymes = args["r"]
qualifiers = args["q"]
syllabification_labels = args["hp"]
rhyme_labels = args["rp"]
nohyphen = args["nohyphen"]
norhymes = args["norhymes"]
compound_suffix = args["csuffix"]
homophones = mw.text.split(args["hh"], ",")
homophone_labels = args["hhp"]
-- hacks; if the first parameter is empty then we want an empty string
if args[2] and args[1] == nil then args[1] = "" end
if ipa[2] and ipa[1] == nil then ipa[1] = "" end
-- clear homophones if empty
if #homophones == 1 and homophones[1] == "" then homophones = {} end
if args["hyphen"] then syllabifications[1] = args["hyphen"] end
if args["rhymes"] then rhymes[1] = args["rhymes"] end
if args["homophones"] then homophones = mw.text.split(args["homophones"], ",") end
local audios = args["a"]
local captions = args["ac"]
if args["audio"] then audios[1] = args["audio"] end
if args["captions"] then captions[1] = args["caption"] end
for i, audiofile in ipairs(audios) do
if audiofile then
table.insert(audio, {lang = lang, file = audiofile, caption = captions[i]})
end
end
end
for i, p in ipairs(pronunciation) do
if p == "" or p == "^" then
pronunciation[i] = cleanup_title(title)
elseif p == "*" or p == "(*)" then
pronunciation[i] = cleanup_title(title) .. p
elseif find(p, "[!#]") then
p = gsub(p, "t!s", "ts")
p = gsub(p, "t#s", "ts")
-- TODO deprecate and remove the two codes below
p = gsub(p, "%.%!", "(.)")
p = gsub(p, "%-%!", "-")
pronunciation[i] = p
end
end
-- make sure #pronunciation >= #IPA, because we use #pronunciation
-- as the source of how many pronunciations we have
for i, p in ipairs(ipa) do
if not pronunciation[i] then
pronunciation[i] = ""
end
end
-- whether hyphenation and rhyme data can be generated automatically
local can_auto_hyphenate = true
local can_auto_rhyme = true
-- when generating rhyme or syllabification data automatically, use
-- model_pronunciation as the source of pronunciation data
local model_pronunciation
local i
-- preprocessing
local split_optional_breaks = false
local do_not_split_optional_breaks = false
local model_pronunciation_index = 1
local brackets = false
i = 1
while i <= #pronunciation do
-- split (.), but only if there is only one
local dot_start, dot_end = find(pronunciation[i], "%(%.%)")
if dot_start then
split_optional_breaks = true
if find(pronunciation[i], "%(%.%)", dot_end) then
do_not_split_optional_breaks = true
end
end
-- add syllable break Vii -> V.ii
if find(pronunciation[i], "[aeouyäö]ii") then
pronunciation[i] = gsub(pronunciation[i], "([aeouyäö])(ii)", "%1.%2")
end
-- handle % (long consonant usually pronounced as short)
if find(pronunciation[i], "%", 1, true) then
local original = pronunciation[i]
local short = gsub(original, "%%", "")
local long = gsub(original, "(.)%%", "%1%1")
pronunciation[i] = short
-- use long consonant in model_pronunciation
if i == model_pronunciation_index then
model_pronunciation_index = i + 1
end
i = i + 1
table.insert(pronunciation, i, long)
do_not_split_optional_breaks = true
end
if find(pronunciation[i], "[", 1, true) then
brackets = true
end
i = i + 1
end
local model_pronunciation = pronunciation[model_pronunciation_index]
if brackets then
brackets = model_pronunciation
-- remove brackets from all pronunciation, keeping only in brackets
-- for later processing
for i, p in ipairs(pronunciation) do
pronunciation[i] = gsub(p, "%b[]", "")
end
model_pronunciation = pronunciation[model_pronunciation_index]
end
local model_pronunciation_max_index = model_pronunciation_index
-- split a single optional break into two pronunciations
-- model_pronunciation will still have the (.)
-- syllabification and rhyme logic can handle it
if #pronunciation == 1 and split_optional_breaks and not do_not_split_optional_breaks then
local new_pronunciation = {}
for _, p in ipairs(pronunciation) do
-- local split_i = split_by_optional_break(p, true)
local split_i = split_by_optional_break(p)
for _, np in ipairs(split_i) do
table.insert(new_pronunciation, np)
end
end
pronunciation = new_pronunciation
model_pronunciation_max_index = #pronunciation
end
local syllabification_cache = {}
-- generate IPA rows
local results = {}
local has_spaces = match(title, " ") or (model_pronunciation and match(model_pronunciation, " "))
local is_suffix = match(title, "^-")
local is_affix = not compound_suffix and (match(title, "-$") or is_suffix)
for i, p in ipairs(pronunciation) do
local qual = qualifiers[i] or ""
if #qual > 0 then
qual = " " .. require("Module:qualifier").format_qualifier(qualifiers[i])
end
if ipa[i] and ipa[i] ~= "" then
table.insert(results, "* " .. m_IPA.format_IPA_full {
lang = lang,
items = {{pron = ipa[i]}},
no_count = has_spaces,
} .. qual)
-- cannot do automatic rhyme/hyph with IPA pronunciations
if i <= model_pronunciation_max_index then
can_auto_hyphenate = false
can_auto_rhyme = false
end
else
-- do basic cleanup to IPA parameters first:
-- replace + with - for secondary stress, and colon with long
if find(p, ":") then p = gsub(p, ":", long) end
if find(p, "%+") then p = gsub(p, "%+", "-") end
-- add clarifying dots to three-vowel sequences
if find(p, "[aeiouyäö][aeiouyäö][aeiouyäö]") then
p = add_trivowel_dots(p)
end
-- handle quotes
if find(p, '"') then
p = convert_quotes_to(syllabification_cache, p, "-", ".")
end
local IPA, IPA_narrow = fi_IPA_combined_term(p, true)
-- multi-word stress
if has_spaces then
IPA_narrow = gsub(IPA_narrow, " ([^ˈˌ"..tertiary.."])", " ˈ%1")
IPA = gsub(IPA, " ([^ˈˌ"..tertiary.."])", " ˈ%1")
end
-- remove initial stress if suffix
if is_suffix then
if compound_suffix then
IPA_narrow = gsub(IPA_narrow, "^(%-?)ˈ", "%1ˌ")
IPA = gsub(IPA, "^(%-?)ˈ", "%1ˌ")
else
IPA_narrow = gsub(IPA_narrow, "^(%-?)ˈ", "%1")
IPA = gsub(IPA, "^(%-?)ˈ", "%1")
end
end
table.insert(results, "* " .. m_IPA.format_IPA_full {
lang = lang,
items = {{pron = "/" .. IPA .. "/"}, {pron = "[" .. IPA_narrow .. "]"}},
no_count = has_spaces,
} .. qual)
end
end
local should_have_automatic_hyphenation = not (has_spaces or is_affix)
can_auto_rhyme = can_auto_rhyme and should_have_automatic_hyphenation
-- terms with spaces, or affixes, never have automatic syllabification/rhymes
local model_pronunciation_hyphenation
if not should_have_automatic_hyphenation then
can_auto_hyphenate = false
elseif can_auto_hyphenate then
can_auto_hyphenate = pron_equal(title, lower(model_pronunciation))
if not can_auto_hyphenate then
model_pronunciation_hyphenation = model_pronunciation
if brackets then
-- check for brackets
model_pronunciation_hyphenation = treat_brackets(brackets)
can_auto_hyphenate = pron_equal(title, lower(model_pronunciation_hyphenation))
end
if not can_auto_hyphenate then
-- handle special cases
local special_cases_checked = pron_equal_special_cases(title, model_pronunciation_hyphenation)
if special_cases_checked then
can_auto_hyphenate = true
model_pronunciation_hyphenation = special_cases_checked
end
end
end
end
local automatic_syllabifications = false
if can_auto_hyphenate and not syllabifications[1] then
-- generate automatic syllabifications
automatic_syllabifications = true
local quotes_converted = convert_quotes_to(syllabification_cache, model_pronunciation_hyphenation or model_pronunciation, "(-)")
local forms = get_autohyphenate_forms(quotes_converted, title)
local seen_syllabifications = {}
local syllabification_count = 1
for _, form in ipairs(forms) do
if syllabifications[syllabification_count] then break end
local syllabified = table.concat(syllabify(form, sep_symbols, "dots"))
if not seen_syllabifications[syllabified] then
syllabifications[syllabification_count] = syllabified
seen_syllabifications[syllabified] = true
syllabification_count = syllabification_count + 1
end
end
elseif #syllabifications == 1 and syllabifications[1] == "-" then
syllabifications = {}
should_have_automatic_hyphenation = false
end
if can_auto_rhyme and not rhymes[1] then
-- generate automatic rhymes
local forms = get_autorhyme_forms(convert_quotes_to(syllabification_cache, model_pronunciation, "-", "."))
for i, form in ipairs(forms) do
if rhymes[i] then break end
rhymes[i] = { generate_rhyme(form) }
end
elseif #rhymes == 1 and rhymes[1] == "-" then
rhymes = {}
end
for i, a in ipairs(audio) do
table.insert(results, "* " .. require("Module:audio").format_audio(a))
end
-- generate rhyme rows from rhyme data
if not norhymes then
local syllabification_split_counts = nil
-- generate rhyme rows
if #rhymes > 0 then
-- merge rhymes if they have identical labels
local last_label = false
local new_rhymes = {}
local new_rhyme_labels = {}
local current_list = {}
for i, r in ipairs(rhymes) do
local label = rhyme_labels[i] or ""
if last_label == label then
table.insert(current_list, r)
else
if #current_list > 0 then
table.insert(new_rhymes, current_list)
end
if last_label ~= false then
table.insert(new_rhyme_labels, last_label)
end
current_list = { r }
last_label = label
end
end
table.insert(new_rhymes, current_list)
table.insert(new_rhyme_labels, last_label)
rhymes = new_rhymes
rhyme_labels = new_rhyme_labels
end
-- add rhymes
for i, subrhymes in ipairs(rhymes) do
local label = ""
if rhyme_labels[i] and #rhyme_labels[i] > 0 then
label = " " .. require("Module:qualifier").format_qualifier(rhyme_labels[i])
end
if #subrhymes >= 1 then
-- indexed by rhyme first and syllable count second
local existing_rhymes = {}
local seen_rhymes = {}
-- if at least one rhyme doesn't specify the syllable count,
-- we will try to determine it automatically
local must_syllabify = false
local syllable_count
-- final syllable counts not passed as part of any rhyme
local fallback_syllable_counts = nil
for _, rhyme in ipairs(subrhymes) do
if type(rhyme) == "table" then
rhyme, syllable_count = unpack(rhyme)
elseif find(rhyme, "%/") then
-- parse syllable count
rhyme, syllable_count = match(rhyme, "(.+)/(.+)")
syllable_count = tonumber(syllable_count)
else
syllable_count = nil
end
if syllable_count then
local existing_rhyme = seen_rhymes[rhyme]
if not existing_rhyme then
-- add the rhyme if not present
local new_rhyme = { rhyme = rhyme, num_syl = {syllable_count} }
table.insert(existing_rhymes, new_rhyme)
seen_rhymes[rhyme] = { [syllable_count] = true, object = new_rhyme }
elseif not existing_rhyme[syllable_count] then
-- add the syllable count to an existing rhyme
table.insert(existing_rhyme.object.num_syl, syllable_count)
existing_rhyme[syllable_count] = true
end
else
must_syllabify = true
local existing_rhyme = seen_rhymes[rhyme]
if not existing_rhyme then
local new_rhyme = { rhyme = rhyme }
table.insert(existing_rhymes, new_rhyme)
seen_rhymes[rhyme] = { object = new_rhyme }
end
end
end
if must_syllabify and (can_auto_hyphenate or not automatic_syllabifications) then
fallback_syllable_counts = {}
local seen_syllable_counts = {}
if not manual_syllable_counts then
-- generate syllable counts from provided syllabifications
manual_syllable_counts = {}
for i, syl in ipairs(syllabifications) do
if type(syl) == "string" then
manual_syllable_counts[i] = #mw.text.split(syl, '[' .. sep_symbols .. ']')
else
manual_syllable_counts[i] = #syllabifications[i]
end
end
end
-- get all possible syllable counts from syllabifications
for i, n in ipairs(manual_syllable_counts) do
if n > 0 and not seen_syllable_counts[n] then
table.insert(fallback_syllable_counts, n)
seen_syllable_counts[n] = true
end
end
end
table.insert(results, "* " .. require("Module:rhymes").format_rhymes(
{ lang = lang, rhymes = existing_rhymes, num_syl = fallback_syllable_counts }) .. label)
end
end
end
-- generate homophone rows from homophone data
if #homophones > 0 then
local homophone_rows = {}
for i, homophone in ipairs(homophones) do
table.insert(homophone_rows, {
["term"] = homophone,
["qq"] = homophone_labels[i] and { homophone_labels[i] } or nil
})
end
table.insert(results, "* " .. require("Module:homophones").format_homophones(
{ lang = lang, homophones = homophone_rows }))
end
-- generate syllabification and hyphenation rows from syllabification data
if not nohyphen and #syllabifications > 0 then
local syllabification_rows = {}
local hyphenation_rows = {}
local seen_syllabifications = {}
local seen_hyphenations = {}
for i, syllabification in ipairs(syllabifications) do
local syllabification_key = fi_cleanup_syllabification(syllabification, true)
-- deduplicate syllabifications
local syllabification_row = seen_syllabifications[syllabification_key]
if not syllabification_row then
syllabification_row = {
["hyph"] = m_str_utils.split(syllabification_key, "%."),
["q"] = nil
}
seen_syllabifications[syllabification_key] = syllabification_row
table.insert(syllabification_rows, syllabification_row)
end
local hyphenation = fi_hyphenation_from_syllabification(syllabification)
local hyphenation_key = table.concat(hyphenation, ".")
-- deduplicate hyphenations
local hyphenation_row = seen_hyphenations[hyphenation_key]
if not hyphenation_row then
hyphenation_row = {
["hyph"] = hyphenation,
["q"] = nil
}
seen_hyphenations[hyphenation_key] = hyphenation_row
table.insert(hyphenation_rows, hyphenation_row)
end
-- add labels
if syllabification_labels[i] then
syllabification_row.q = syllabification_row.q or { }
table.insert(syllabification_row.q, syllabification_labels[i])
hyphenation_row.q = hyphenation_row.q or { }
table.insert(hyphenation_row.q, syllabification_labels[i])
end
end
table.insert(results, "* " .. require("Module:hyphenation").format_hyphenations(
{ lang = lang, hyphs = syllabification_rows, caption = "音節化<sup>([[Appendix:芬蘭語斷字|幫助]])</sup>" }))
table.insert(results, "* " .. require("Module:hyphenation").format_hyphenations(
{ lang = lang, hyphs = hyphenation_rows, caption = "斷字<sup>([[Appendix:芬蘭語斷字|幫助]])</sup>" }))
end
if should_have_automatic_hyphenation and not syllabifications[1] then
table.insert(categories, "缺少自動斷字的fi-pronunciation")
end
return table.concat(results, "\n") .. require("Module:utilities").format_categories(categories, lang)
end
return export
dbsxi9h93bokok3smvl3k1dspvxbpg1
Module:It-headword
828
1426245
9909077
9835149
2026-09-29T07:26:36Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909077
Scribunto
text/plain
-- This module contains code for Italian headword templates.
-- Templates covered are:
-- * {{it-noun}}, {{it-proper noun}};
-- * {{it-verb}};
-- * {{it-adj}}, {{it-adj-comp}}, {{it-adj-sup}};
-- * {{it-det}};
-- * {{it-art}};
-- * {{it-pron-adj}};
-- * {{it-pp}};
-- * {{it-presp}};
-- * {{it-card-noun}}, {{it-card-adj}}, {{it-card-inv}};
-- * {{it-adv}};
-- * {{it-pos}};
-- * {{it-suffix form}}.
-- See [[Module:it-verb]] for Italian conjugation templates.
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local m_strutils = require("Module:string utilities")
local usub = m_strutils.sub
local require_when_needed = require("Module:utilities/require when needed")
local insert = table.insert
local remove = table.remove
local m_table = require("Module:table")
local com = require("Module:it-common")
local headword_module = "Module:headword"
local headword_utilities_module = "Module:headword utilities"
local inflection_utilities_module = "Module:inflection utilities"
local it_verb_module = "Module:it-verb"
local parse_interface_module = "Module:parse interface"
local romut_module = "Module:romance utilities"
local lang = require("Module:languages").getByCode("it")
local langname = lang:getCanonicalName()
local m_headword_utilities = require_when_needed(headword_utilities_module)
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local no_split_apostrophe_words = {
["c'è"] = true,
["c'era"] = true,
["c'erano"] = true,
}
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function track(page)
require("Module:debug/track")("it-headword/" .. page)
return true
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, frob)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
frob = frob,
}
end
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture
-- replace expression.
lemma = lemma:gsub("%%", "%%%%")
return (term:gsub("#", lemma))
end
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
-----------------------------------------------------------------------------------------
-- Main entry point --
-----------------------------------------------------------------------------------------
function export.show(frame)
local poscat = frame.args[1]
or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local parargs = frame:getParent().args
local params = {
["head"] = list_param,
["id"] = true,
["sort"] = true,
["apoc"] = boolean_param,
["splithyph"] = boolean_param,
["nolinkhead"] = boolean_param,
["nolink"] = {type = "boolean", alias_of = "nolinkhead"},
["json"] = boolean_param,
["pagename"] = true, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolinkhead then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph,
no_split_apostrophe_words)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
if head == auto_linked_head then
track("redundant-head")
end
end
end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
sort_key = args.sort,
force_cat_output = force_cat,
checkredlinks = pos_functions[poscat] and pos_functions[poscat].redlink_pos or true,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = true
local singular_poscat = poscat
insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if args.apoc then
-- Apocopated form of a term; do this after calling pos_functions[], because the function might modify
-- data.pos_category.
local pos = data.pos_category
if not (pos:find("變格形") or pos:find("變位形式")) then
-- Apocopated forms are non-lemma forms.
local singular_poscat = pos
data.pos_category = (singular_poscat == "動詞") and (singular_poscat .. "變位形式") or (singular_poscat .. "變格形")
end
-- If this is a suffix, insert label 'apocopated' after 'FOO-forming suffix', otherwise insert at the beginning.
insert(data.inflections, data.is_suffix and 2 or 1, {label = glossary_link("省尾")})
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require(headword_module).full_headword(data)
end
local deriv_params = {
{"dim", glossary_link("指小")},
{"dim_dim", "雙重" .. glossary_link("指小")},
{"aug_dim", glossary_link("指大") .. "-" .. glossary_link("指小")},
{"aug", glossary_link("指大")},
{"dim_aug", glossary_link("指小") .. "-" .. glossary_link("指大")},
{"aug_aug", "雙重" .. glossary_link("指大")},
{"pej", glossary_link("貶義")},
{"dim_pej", glossary_link("指小") .. "-" .. glossary_link("貶義")},
{"aug_pej", glossary_link("指大") .. "-" .. glossary_link("貶義")},
{"pej_pej", "雙重" .. glossary_link("貶義")},
{"end", glossary_link("愛稱")},
{"dim_end", glossary_link("指小") .. "-" .. glossary_link("愛稱")},
{"aug_end", glossary_link("指大") .. "-" .. glossary_link("愛稱")},
{"derog", glossary_link("貶義")},
{"dim_derog", glossary_link("指小") .. "-" .. glossary_link("貶義")},
{"aug_derog", glossary_link("指大") .. "-" .. glossary_link("貶義")},
{"end_derog", glossary_link("愛稱") .. "-" .. glossary_link("貶義")},
}
local function insert_deriv_params(params)
for _, deriv_param in ipairs(deriv_params) do
local param = unpack(deriv_param)
params[param] = list_param
end
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed part, because that is what
-- [[Module:links]] expects.
item_dest = "gloss",
},
gloss = {},
-- no 'tr' or 'ts', doesn't make sense for Italian
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed part, because that is what
-- [[Module:links]] expects.
item_dest = "genders",
sublist = true,
},
id = {},
alt = {},
q = {type = "qualifier"},
qq = {type = "qualifier"},
lit = {},
pos = {},
-- no 'sc', doesn't make sense for Italian
}
local function parse_term_with_modifiers(paramname, val)
local function generate_obj(term)
local decomp = com.decompose(term)
local lemma = com.remove_non_final_accents(decomp)
if lemma ~= decomp then
term = com.compose("[[" .. lemma .. "|" .. decomp .. "]]")
end
return {term = term}
end
local retval = require(parse_interface_module).parse_inline_modifiers(val, {
paramname = paramname,
param_mods = param_mods,
generate_obj = generate_obj,
splitchar = "[/;,]",
preserve_splitchar = true,
})
for _, obj in ipairs(retval) do
if obj.delimiter == ";" then
obj.separator = "; "
elseif obj.delimiter == "/" then
obj.separator = "/"
-- default to nil for comma
end
end
return retval
end
local function insert_deriv_inflections(data, args)
for _, deriv_param in ipairs(deriv_params) do
local param, desc = unpack(deriv_param)
if #args[param] > 0 then
local inflection = {label = desc}
for _, term in ipairs(args[param]) do
local parsed_terms = parse_term_with_modifiers(param, term)
for _, parsed_term in ipairs(parsed_terms) do
insert(inflection, parsed_term)
end
end
insert(data.inflections, inflection)
end
end
end
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet(
{"m", "f", "mf", "mfbysense", "mfequiv", "gneut", "n", "m-p", "f-p", "mf-p", "mfbysense-p", "mfequiv-p", "gneut-p", "n-p", "?", "?-p"}
)
local function validate_genders(genders)
for _, g in ipairs(genders) do
if type(g) == "table" then
g = g.spec
end
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
end
end
local function do_noun(args, data, is_proper)
local is_plurale_tantum = false
local has_singular = false
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
validate_genders(args[1])
data.genders = args[1]
local saw_m = false
local saw_f = false
local gender_for_default_plural
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if type(g) == "table" then
g = g.spec
end
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
end
end
if saw_m and saw_f then
gender_for_default_plural = "mf"
elseif saw_f then
gender_for_default_plural = "f"
else
gender_for_default_plural = "m"
end
local lemma = data.pagename
local function inscat(cat)
insert(data.categories, cat:find("的") and cat:gsub("的", "的" .. langname) or langname .. cat)
end
local function insert_noun_inflection(terms, label, accel, no_inv)
for _, term in ipairs(terms) do
if not no_inv and term.term == lemma then
term.term = nil
term.label = glossary_link("無屈折")
end
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
-- Plural
local plurals = {}
-- Fetch explicit masculine and feminine plurals here because we may change them below when processing plurals.
local mpls = m_headword_utilities.parse_term_list_with_modifiers {
paramname = "mpl",
forms = args.mpl,
splitchar = ",",
}
local fpls = m_headword_utilities.parse_term_list_with_modifiers {
paramname = "fpl",
forms = args.fpl,
splitchar = ",",
}
if is_plurale_tantum and not has_singular then
if args[2][1] then
error("Can't specify plurals of plurale tantum " .. category_pos)
end
insert(data.inflections, {label = glossary_link("唯複")})
elseif args.apoc then
-- apocopated noun
if args[2][1] then
error("Can't specify plurals of apocopated " .. category_pos)
end
else
-- Fetch plurals and associated decorations (qualifiers and labels) and genders.
plurals = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "pl"},
forms = args[2],
splitchar = ",",
include_mods = {"g"},
}
-- Check for special plural signals
local mode = nil
local pl1 = plurals[1]
if pl1 and #pl1.term == 1 then
mode = pl1.term
if mode == "?" or mode == "!" or mode == "-" or mode == "~" then
pl1.term = nil
if next(pl1) then
error(("Can't specify inline modifiers with plural code '%s'"):format(mode))
end
remove(plurals, 1) -- Remove the mode parameter
elseif mode ~= "+" and mode ~= "#" then
error(("Unexpected plural code '%s'"):format(mode))
end
end
if is_plurale_tantum then
-- both singular and plural
insert(data.inflections, {label = "有時" .. glossary_link("唯複") .. ",有變體"})
end
if mode == "?" then
-- Plural is unknown
insert(data.categories, "有未知或不確定複數的" .. langname .. category_plpos)
elseif mode == "!" then
-- Plural is not attested
insert(data.inflections, {label = "複數無紀錄"})
insert(data.categories, "有無紀錄複數的" .. langname .. category_plpos)
if plurals[1] then
error("Can't specify any plurals along with unattested plural code '!'")
end
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
insert(data.categories, langname .. "不可數" .. category_plpos)
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
insert(data.categories, langname .. "可數" .. category_plpos)
else
insert(data.inflections, {label = glossary_link("不可數")})
end
else
-- Countable or mixed countable/uncountable
-- If no plurals, use the default plural unless mpl= or fpl= explicitly given.
if not plurals[1] and not mpls[1] and not fpls[1] and not is_proper then
plurals[1] = {term = "+"}
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
insert(data.inflections, {label = glossary_link("可數") .. "和" .. glossary_link("不可數")})
insert(data.categories, langname .. "不可數" .. category_plpos)
insert(data.categories, langname .. "可數" .. category_plpos)
elseif plurals[1] then
-- Countable nouns
insert(data.categories, langname .. "可數" .. category_plpos)
else
-- Uncountable nouns
insert(data.categories, langname .. "不可數" .. category_plpos)
end
end
-- Process plurals, handling requests for default plurals.
local has_default_or_hash = false
for _, pl in ipairs(plurals) do
if pl.term:find("^%+") or pl.term:find("#") or pl.term == "cap*" or pl.term == "cap*+" then
has_default_or_hash = true
break
end
end
if has_default_or_hash then
local newpls = {}
local function insert_pl(pl, defpl)
pl.term = defpl
insert(newpls, pl)
end
local function make_gendered_plural(pl, special)
if gender_for_default_plural == "mf" then
local default_mpl = com.make_plural(lemma, "m", special)
local default_fpl = com.make_plural(lemma, "f", special)
if default_mpl then
if default_mpl == default_fpl then
insert_pl(pl, default_mpl)
else
if args.mpl[1] or args.fpl[1] then
error("Can't specify gendered plural spec '" .. (special or "+") ..
"' along with gender=" .. gender_for_default_plural ..
" and also specify mpl= or fpl=")
end
mpls = {m_table.shallowCopy(pl)}
mpls[1].term = default_mpl
fpls = {pl}
fpls[1].term = default_fpl
end
end
else
local defpl = com.make_plural(lemma, gender_for_default_plural, special)
if defpl then
insert_pl(pl, defpl)
end
end
end
for _, pl in ipairs(plurals) do
if pl.term == "cap*" or pl.term == "cap*+" then
make_gendered_plural(pl, pl.term)
elseif pl.term == "+" then
make_gendered_plural(pl)
elseif pl.term:find("^%+") then
local special = require(romut_module).get_special_indicator(pl.term)
make_gendered_plural(pl, special)
else
insert_pl(pl, replace_hash_with_lemma(pl.term, lemma))
end
end
plurals = newpls
end
if plurals[2] then
inscat("有多個複數的" .. category_plpos)
end
-- If the first or only plural is the same as the singular, replace it with 'invariable', or 'usually
-- invariable' if there is more than one plural.
pl1 = plurals[1]
if pl1 and pl1.term == lemma then
if plurals[2] then
insert(data.inflections, {label = "通常" .. glossary_link("無屈折"),
q = pl1.q, qq = pl1.qq, l = pl1.l, ll = pl1.ll, refs = pl1.refs
})
else
insert(data.inflections, {label = glossary_link("無屈折"),
q = pl1.q, qq = pl1.qq, l = pl1.l, ll = pl1.ll, refs = pl1.refs
})
end
remove(plurals, 1)
inscat("無屈折" .. category_plpos)
end
if plurals[1] then
-- Check for gender-changing plurals.
for _, pl in ipairs(plurals) do
if pl.genders then
for _, g in ipairs(pl.genders) do
if type(g) ~= "table" then
g = {spec = g}
end
if g.spec == "m" and not saw_m or g.spec == "f" and not saw_f then
inscat("複數改變性別的" .. category_plpos)
end
end
end
end
end
end
-- Gather masculines/feminines. For each one, generate the corresponding plural. `field` is the name of the field
-- containing the masculine or feminine forms (normally "m" or "f"); `inflect` is a function of one or two arguments
-- to generate the default masculine or feminine from the lemma (the arguments are the lemma and optionally a
-- "special" flag to indicate how to handle multiword lemmas, and the function is normally make_feminine or
-- make_masculine from [[Module:it-common]]); and `default_plurals` is a list into which the corresponding default
-- plurals of the gathered or generated masculine or feminine forms are stored.
local function handle_mf(field, inflect, default_plurals)
local special
local mfs = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
frob = function(term)
if term == "+" then
-- Generate default masculine/feminine.
term = inflect(lemma)
else
term = replace_hash_with_lemma(term, lemma)
end
special = require(romut_module).get_special_indicator(term)
if special then
term = inflect(lemma, special)
end
return term
end
}
for _, mf in ipairs(mfs) do
local plobj = m_table.shallowCopy(mf)
plobj.term = com.make_plural(mf.term, field, special)
if plobj.term then
-- Add an accelerator for each masculine/feminine plural whose lemma is the corresponding singular, so that
-- the accelerated entry that is generated has a definition that looks like
-- # {{plural of|it|MFSING}}
plobj.accel = {form = "p", lemma = mf.term}
insert(default_plurals, plobj)
end
end
return mfs
end
local feminine_plurals = {}
local feminines = handle_mf("f", com.make_feminine, feminine_plurals)
local masculine_plurals = {}
local masculines = handle_mf("m", com.make_masculine, masculine_plurals)
local function handle_mf_plural(mfplfield, mfpls, gender, default_plurals, singulars)
if is_plurale_tantum then
return mfpls, true
end
local new_mfpls = {}
local saw_plus
local noinv
for i, mfpl in ipairs(mfpls) do
local accel
if #mfpls == #singulars then
-- If same number of overriding masculine/feminine plurals as singulars, assume each plural goes with
-- the corresponding singular and use each corresponding singular as the lemma in the accelerator. The
-- generated entry will have
-- # {{plural of|it|SINGULAR}}
-- as the definition.
accel = {form = "p", lemma = singulars[i].term}
else
accel = nil
end
if mfpl.term == "+" then
-- We should never see + twice. If we do, it will lead to problems since we overwrite the values of
-- default_plurals the first time around.
if saw_plus then
error(("Saw + twice when handling %s="):format(mfplfield))
end
saw_plus = true
if not default_plurals[1] then
local defpl = com.make_plural(lemma, gender)
if not defpl then
error("Unable to generate default plural of '" .. lemma .. "'")
end
default_plurals[1] = {term = defpl}
end
for _, defpl in ipairs(default_plurals) do
-- defpl is already a table and has an accel field
m_headword_utilities.combine_termobj_decorations(defpl, mfpl)
insert(new_mfpls, defpl)
end
-- don't use "invariable" because the plural is not with respect to the lemma but with respect to the
-- masc/fem singular
noinv = true
elseif mfpl.term == "cap*" or mfpl.term == "cap*+" or mfpl.term:find("^%+") then
if mfpl.term:find("^%+") then
mfpl.term = require(romut_module).get_special_indicator(mfpl.term)
end
if singulars[1] then
for _, mf in ipairs(singulars) do
local mfplobj = m_table.shallowCopy(mfpl)
mfplobj.term = com.make_plural(mf.term, gender, mfpl.term)
if mfplobj.term then
mfplobj.accel = accel
m_headword_utilities.combine_termobj_decorations(mfplobj, mf)
insert(new_mfpls, mfplobj)
end
-- don't use "invariable" because the plural is not with respect to the lemma but with respect
-- to the masc/fem singular
noinv = true
-- FIXME: Should we throw an error if no plural could be generated?
end
else
-- FIXME: This clause didn't exist in the corresponding code in [[Module:pt-headword]]. Is it
-- correct?
mfpl.term = com.make_plural(lemma, gender, mfpl.term)
if mfpl.term then
insert(new_mfpls, mfpl)
end
end
else
mfpl.accel = accel
mfpl.term = replace_hash_with_lemma(mfpl.term, lemma)
insert(new_mfpls, mfpl)
-- don't use "invariable" if masc/fem singular present because the plural is not with respect to
-- the lemma but with respect to the masc/fem singular
noinv = noinv or #singulars > 0
end
end
return new_mfpls, noinv
end
local mpl_noinv, fpl_noinv
-- Not fpls[1] because if the user didn't specify any explicit mpl= or fpl= but the lemma gender is mf or mfbysense
-- and has separate masculine and feminine plural forms (e.g. any term in -ista), we don't want to reprocess those
-- auto-generated forms.
if args.fpl[1] then
-- Override any existing feminine plurals.
feminine_plurals, fpl_noinv = handle_mf_plural("fpl", fpls, "f", feminine_plurals, feminines)
else
feminine_plurals, fpl_noinv = fpls, false
end
if args.mpl[1] then
-- Override any existing masculine plurals.
masculine_plurals, mpl_noinv = handle_mf_plural("mpl", mpls, "m", masculine_plurals, masculines)
else
masculine_plurals, mpl_noinv = mpls, false
end
local function redundant_plural(pl)
for _, p in ipairs(plurals) do
if p.term == pl.term then
return true
end
end
return false
end
for _, mpl in ipairs(masculine_plurals) do
if redundant_plural(mpl) then
track("noun-redundant-mpl")
end
end
for _, fpl in ipairs(feminine_plurals) do
if redundant_plural(fpl) then
track("noun-redundant-fpl")
end
end
if plurals[1] then
-- Set 'noinv' because we already took care of invariable plurals above.
insert_noun_inflection(plurals, "複數", "p", "noinv")
end
insert_noun_inflection(masculines, "陽性")
insert_noun_inflection(masculine_plurals, "陽性複數", nil, mpl_noinv)
insert_noun_inflection(feminines, "陰性", "f")
insert_noun_inflection(feminine_plurals, "陰性複數", nil, fpl_noinv)
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
parse_and_insert_noun_inflection("adj", glossary_link("關係", "關係形容詞"))
parse_and_insert_noun_inflection("adv", glossary_link("副詞"))
parse_and_insert_noun_inflection("dem", glossary_link("居民稱謂詞"))
parse_and_insert_noun_inflection("fdem", "女性" .. glossary_link("居民稱謂詞"))
insert_deriv_inflections(data, args)
-- Maybe add category 'Italian nouns with irregular gender' (or similar)
local irreg_gender_lemma = lemma:gsub(" .*", "") -- only look at first word
if (irreg_gender_lemma:find("o$") and (gender_for_default_plural == "f" or gender_for_default_plural == "mf"
or gender_for_default_plural == "mfbysense")) or
(irreg_gender_lemma:find("a$") and (gender_for_default_plural == "m" or gender_for_default_plural == "mf"
or gender_for_default_plural == "mfbysense")) then
inscat("有不規則性別的" .. category_plpos)
end
end
local function get_noun_params(nountype)
local params = {
[1] = {list = "g", disallow_holes = true, required = nountype ~= "proper", default = "?", type = "genders",
flatten = true},
[2] = {list = "pl", disallow_holes = true},
["m"] = list_param,
["f"] = list_param,
["mpl"] = list_param,
["fpl"] = list_param,
["adj"] = list_param, --adjective(s)
["adv"] = list_param, --adverb(s)
["dem"] = list_param, --demonym(s)
["fdem"] = list_param, --female demonym(s)
}
insert_deriv_params(params)
return params
end
pos_functions["名詞"] = {
params = get_noun_params("base"),
func = do_noun,
}
pos_functions["專有名詞"] = {
params = get_noun_params("proper"),
func = function(args, data)
do_noun(args, data, "is proper noun")
end,
}
pos_functions["基數名詞"] = {
params = get_noun_params("base"),
func = function(args, data)
do_noun(args, data)
insert(data.categories, 1, langname .. "基數詞")
end,
pos_category = "數詞",
}
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
local function do_adjective(args, data, is_superlative)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
-- Use "participle" not "past participle" for categories such as 'invariable participles'
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
insert(indicators, "'" .. indic .. "'")
end
table.sort(indicators)
error("Special inflection indicator beginning can only be " ..
mw.text.listToText(indicators) .. ": " .. args.sp)
end
end
local lemma = data.pagename
local function fetch_inflections(field)
local retval = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
}
if not retval[1] then
return {{term = "+"}}
end
return retval
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if args.inv then
-- invariable adjective
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
end
if args.noforms then
-- [[bello]] and any others too complicated to describe in headword
insert(data.inflections, {label = "屈折請見下表"})
end
if args.inv or args.apoc or args.noforms then
if args.sp or args.f[1] or args.pl[1] or args.mpl[1] or args.fpl[1] then
error("Can't specify inflections with an invariable or apocopated adjective or with noforms=")
end
elseif args.fonly then
-- feminine-only
if args.f[1] then
error("Can't specify explicit feminines with feminine-only " .. category_pos)
end
if args.pl[1] then
error("Can't specify explicit plurals with feminine-only " .. category_pos .. ", use fpl=")
end
if args.mpl[1] then
error("Can't specify explicit masculine plurals with feminine-only " .. category_pos)
end
local argsfpl = fetch_inflections("fpl")
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
local defpl = com.make_plural(lemma, "f", args.sp)
if not defpl then
error("Unable to generate default plural of '" .. lemma .. "'")
end
fpl.term = defpl
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
end
insert(feminine_plurals, fpl)
end
insert(data.inflections, {label = "僅陰性"})
insert_inflection(feminine_plurals, "陰性複數", "f|p")
else
-- Gather feminines.
for _, f in ipairs(fetch_inflections("f")) do
if f.term == "+" then
-- Generate default feminine.
f.term = com.make_feminine(lemma, args.sp)
else
f.term = replace_hash_with_lemma(f.term, lemma)
end
insert(feminines, f)
end
local fem_like_lemma = #feminines == 1 and feminines[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(feminines[1])
if fem_like_lemma then
insert(data.categories, langname .. "兩性" .. category_plpos)
end
local mpl_field = "mpl"
local fpl_field = "fpl"
if args.pl[1] then
if args.mpl[1] or args.fpl[1] then
error("Can't specify both pl= and mpl=/fpl=")
end
mpl_field = "pl"
fpl_field = "pl"
end
local argsmpl = fetch_inflections(mpl_field)
local argsfpl = fetch_inflections(fpl_field)
for _, mpl in ipairs(argsmpl) do
if mpl.term == "+" then
-- Generate default masculine plural.
local defpl = com.make_plural(lemma, "m", args.sp)
if not defpl then
error("Unable to generate default plural of '" .. lemma .. "'")
end
mpl.term = defpl
else
mpl.term = replace_hash_with_lemma(mpl.term, lemma)
end
insert(masculine_plurals, mpl)
end
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local fplobj = m_table.shallowCopy(fpl)
local defpl = com.make_plural(f.term, "f", args.sp)
if not defpl then
error("Unable to generate default plural of '" .. f.term .. "'")
end
fplobj.term = defpl
m_headword_utilities.combine_termobj_decorations(fplobj, f)
insert(feminine_plurals, fplobj)
end
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
insert(feminine_plurals, fpl)
end
end
local fem_pl_like_masc_pl = masculine_plurals[1] and feminine_plurals[1] and
m_table.deepEquals(masculine_plurals, feminine_plurals)
local masc_pl_like_lemma = #masculine_plurals == 1 and masculine_plurals[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(masculine_plurals[1])
if fem_like_lemma and fem_pl_like_masc_pl and masc_pl_like_lemma then
-- actually invariable
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
-- Make sure there are feminines given and not same as lemma.
if not fem_like_lemma then
insert_inflection(feminines, "陰性", "f|s")
elseif args.gneut then
data.genders = {"gneut"}
else
data.genders = {"mfbysense"}
end
if fem_pl_like_masc_pl then
if args.gneut then
insert_inflection(masculine_plurals, "複數", "p")
else
-- This is how the Spanish module works.
-- insert_inflection(masculine_plurals, "masculine and feminine plural", "p")
insert_inflection(masculine_plurals, "複數", "p")
end
else
insert_inflection(masculine_plurals, "陽性複數", "m|p")
insert_inflection(feminine_plurals, "陰性複數", "f|p")
end
end
end
local function parse_and_insert_adj_inflection(field, label, accel, frob)
parse_and_insert_inflection(data, args, field, label, accel, frob)
end
parse_and_insert_adj_inflection("n", "中性")
parse_and_insert_adj_inflection("comp", glossary_link("比較級"))
parse_and_insert_adj_inflection("sup", glossary_link("最高級"))
parse_and_insert_adj_inflection("adv", glossary_link("副詞"))
insert_deriv_inflections(data, args)
if args.irreg and is_superlative then
insert(data.categories, langname .. "不規則" .. category_plpos .. "最高級")
end
end
local function get_adjective_params(adjtype)
local params = {
["inv"] = boolean_param, --invariable
["noforms"] = boolean_param, --too complicated to list forms except in a table
["sp"] = true, -- special indicator: "first", "first-last", etc.
["f"] = list_param, --feminine form(s)
["pl"] = list_param, --plural override(s)
["fpl"] = list_param, --feminine plural override(s)
["mpl"] = list_param, --masculine plural override(s)
["adv"] = list_param, --adverb(s)
}
if adjtype == "base" or adjtype == "part" or adjtype == "det" then
params["comp"] = list_param --comparative(s)
params["sup"] = list_param --superlative(s)
params["fonly"] = boolean_param -- feminine only
end
if adjtype == "sup" then
params["irreg"] = boolean_param
end
insert_deriv_params(params)
return params
end
pos_functions["形容詞"] = {
params = get_adjective_params("base"),
func = do_adjective,
}
pos_functions["形容詞比較級"] = {
params = get_adjective_params("comp"),
func = do_adjective,
pos_category = "形容詞",
}
pos_functions["形容詞最高級"] = {
params = get_adjective_params("sup"),
func = function(args, data)
do_adjective(args, data, "is superlative")
end,
pos_category = "形容詞",
}
pos_functions["基數形容詞"] = {
params = get_adjective_params("card"),
func = function(args, data)
do_adjective(args, data)
insert(data.categories, 1, langname .. "基數詞")
end,
pos_category = "數詞",
}
pos_functions["過去分詞"] = {
params = get_adjective_params("part"),
func = do_adjective,
redlink_pos = "分詞",
pos_category = "過去分詞",
}
pos_functions["現在分詞"] = {
params = get_adjective_params("part"),
func = do_adjective,
redlink_pos = "分詞",
pos_category = "現在分詞",
}
pos_functions["限定詞"] = {
params = get_adjective_params("det"),
func = do_adjective,
}
pos_functions["冠詞"] = {
params = get_adjective_params("det"),
func = do_adjective,
}
pos_functions["類似形容詞的代詞"] = {
params = get_adjective_params("pron"),
func = do_adjective,
pos_category = "代詞",
}
pos_functions["無屈折基數"] = {
params = {},
func = function(args, data)
insert(data.categories, langname .. "基數詞")
insert(data.categories, langname .. "無屈折數詞")
insert(data.inflections, {label = glossary_link("無屈折")})
end,
pos_category = "數詞",
}
-----------------------------------------------------------------------------------------
-- Adverbs --
-----------------------------------------------------------------------------------------
local function do_adverb(args, data)
local function parse_and_insert_adv_inflection(field, label, accel, frob)
parse_and_insert_inflection(data, args, field, label, accel, frob)
end
parse_and_insert_adv_inflection("comp", glossary_link("比較級"))
parse_and_insert_adv_inflection("sup", glossary_link("最高級"))
parse_and_insert_adv_inflection("adj", glossary_link("形容詞"))
end
local function get_adverb_params(advtype)
local params = {
["adj"] = list_param, --adjective(s)
}
if advtype == "base" then
params["comp"] = list_param --comparative(s)
params["sup"] = list_param --superlative(s)
end
return params
end
pos_functions["副詞"] = {
params = get_adverb_params("base"),
func = do_adverb,
}
pos_functions["副詞比較級"] = {
params = get_adverb_params("comp"),
func = do_adverb,
pos_category = "副詞",
}
pos_functions["副詞最高級"] = {
params = get_adverb_params("sup"),
func = do_adverb,
pos_category = "副詞",
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["動詞"] = {
params = {
[1] = {},
["noautolinktext"] = boolean_param,
["noautolinkverb"] = boolean_param,
},
func = function(args, data)
if args[1] then
local alternant_multiword_spec = require(it_verb_module).do_generate_forms(args, "from headword", data.heads[1])
local function do_verb_form(slot, label, rowslot, rowlabel)
local forms = alternant_multiword_spec.forms[slot]
local retval
if alternant_multiword_spec.rowprops.all_defective[rowslot] then
if not alternant_multiword_spec.rowprops.defective[rowslot] then
-- No forms, but none expected; don't display anything
return
end
retval = {label = "無" .. rowlabel}
elseif not forms then
retval = {label = "無" .. label}
elseif alternant_multiword_spec.rowprops.all_unknown[rowslot] then
retval = {label = "未知" .. rowlabel}
elseif forms[1].form == "?" then
retval = {label = "未知" .. label}
else
-- Disable accelerators for now because we don't want the added accents going into the headwords.
-- FIXME: We now have support in [[Module:accel]] to specify the target explicitly; we can use this
-- so we can add the accelerators back with a param to avoid the accents.
local accel_form = nil -- all_verb_slots[slot]
retval = {label = label, accel = accel_form and {form = accel_form} or nil}
local prev_footnotes = nil
-- If the footnotes for this form are the same as the footnotes for the preceding form or
-- contain the preceding footnotes, replace the footnotes that are the same with "ditto".
-- This avoids repetition on pages like [[succedere]] where the form ''succedétti'' has a long
-- footnote which gets repeated in the traditional form ''succedètti'' (which also has the
-- footnote "[traditional]").
for _, form in ipairs(forms) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(form.footnotes)
local quals_with_ditto = quals
if quals and prev_footnotes then
local quals_contains_previous = true
for _, qual in ipairs(prev_footnotes) do
if not m_table.contains(quals, qual) then
quals_contains_previous = false
break
end
end
if quals_contains_previous then
local inserted_ditto = false
quals_with_ditto = {}
for _, qual in ipairs(quals) do
if m_table.contains(prev_footnotes, qual) then
if not inserted_ditto then
insert(quals_with_ditto, "ditto")
inserted_ditto = true
end
else
insert(quals_with_ditto, qual)
end
end
end
end
prev_footnotes = quals
insert(retval, {term = form.form, q = quals_with_ditto, refs = refs})
end
end
insert(data.inflections, retval)
end
if alternant_multiword_spec.props.is_pronominal then
insert(data.inflections, {label = glossary_link("代詞性")})
end
if alternant_multiword_spec.props.impers then
insert(data.inflections, {label = glossary_link("無人稱")})
end
if alternant_multiword_spec.props.thirdonly then
insert(data.inflections, {label = "僅第三人稱"})
end
local thirdonly = alternant_multiword_spec.props.impers or alternant_multiword_spec.props.thirdonly
local sing_label = thirdonly and "第三人稱單數" or "第一人稱單數"
for _, rowspec in ipairs {
{"pres", "現在時", true},
{"phis", "先過去時", true},
{"pp", "過去分詞", true},
{"imperf", "未完成時"},
{"fut", "未來時"},
{"sub", "現在時虛擬式"},
{"impsub", "未完成時虛擬式"},
} do
local rowslot, desc, always_show = unpack(rowspec)
local slot = rowslot .. (thirdonly and "3s" or "1s")
local must_show = alternant_multiword_spec.is_irreg[slot]
if always_show then
must_show = true
elseif rowslot == "imperf" and alternant_multiword_spec.props.has_explicit_stem_spec then
-- If there is an explicit stem spec, make sure it gets displayed; the imperfect is a good way of
-- showing this.
must_show = true
elseif not alternant_multiword_spec.forms[slot] then
-- If the principal part is unexpectedly missing, make sure we show this.
must_show = true
elseif alternant_multiword_spec.forms[slot][1].form == "?" then
-- If the principal part is unknown, make sure we show this.
must_show = true
end
if must_show then
if rowslot == "pp" then
do_verb_form(rowslot, desc, rowslot, desc)
else
do_verb_form(slot, sing_label .. " " .. desc, rowslot, desc)
end
end
end
-- Also do the imperative, but not for third-only verbs, which are always missing the imperative.
if not thirdonly and (alternant_multiword_spec.is_irreg.imp2s
or not alternant_multiword_spec.forms.imp2s) then
do_verb_form("imp2s", "第二人稱單數祈使式", "imp", "祈使式")
end
-- If there is a past participle but no auxiliary (e.g. [[malfare]]), explicitly add "no auxiliary". In
-- cases where there's no past participle and no auxiliary (e.g. [[irrompere]]), we don't do this as we
-- already get "no past participle" displayed. Don't display an auxiliary in any case if the lemma
-- consists entirely of reflexive verbs (for which the auxiliary is always [[essere]]).
if alternant_multiword_spec.props.is_non_reflexive and (
alternant_multiword_spec.forms.aux or alternant_multiword_spec.forms.pp
) then
do_verb_form("aux", "助動詞", "aux", "助動詞")
end
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:it-verb]] it in place of the user-specified or auto-generated head so that we get accents marked
-- on the verb(s). Don't do this if the user gave multiple heads or gave a head with a multiword-linked
-- verbal expression such as '[[dare esca]] [[al]] [[fuoco]]'.
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.inf) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
end
end
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, "構成" .. typ .. "的後綴")
end
insert(data.inflections, {label = m_table.serialCommaJoin(suffix_type, {conj = "or"}) .. "的非詞元形式"})
end,
}
-----------------------------------------------------------------------------------------
-- Arbitrary parts of speech --
-----------------------------------------------------------------------------------------
pos_functions["任意詞性"] = {
params = {
[1] = {required = true},
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
},
func = function(args, data)
if data.is_suffix then
error("Can't use [[Template:it-pos]] with suffixes")
end
validate_genders(args.g)
data.genders = args.g
local plpos = args[1]
data.pos_category = plpos
end,
}
return export
5fxrre91g2dy4462bfnx8sy8nq9i7w9
Module:Fr-headword
828
1426246
9909066
9835824
2026-09-29T07:25:55Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909066
Scribunto
text/plain
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local check_missing = false -- whether to check for missing forms
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local lang = require("Module:languages").getByCode("fr")
local langname = lang:getCanonicalName()
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local romut_module = "Module:romance utilities"
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local boolean_param = {type = "boolean"}
local list_param = {list = true, disallow_holes = true}
local concat = table.concat
local insert = table.insert
local remove = table.remove
local sort = table.sort
local u = mw.ustring.char
local nnbsp = u(0x202F) -- NNBSP = narrow non-breaking space, used before ? and ! in French
local prepositions = {
"à ",
"aux? ",
"d[eu] ",
"d['’]",
"des ",
"en ",
"sous ",
"sur ",
"avec ",
"pour ",
"par ",
"dans ",
"contre ",
"sans ",
"comme ",
"jusqu['’]",
-- We could list others but you get diminishing returns
}
local no_split_apostrophe_words = {
["c'est"] = true,
["quelqu'un"] = true,
["aujourd'hui"] = true,
}
local function track(page)
require("Module:debug").track("fr-headword/" .. page)
return true
end
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture replace
-- expression.
lemma = lemma:gsub("%%", "%%%%")
-- Assign to a variable to discard second return value.
term = term:gsub("#", lemma)
return term
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
local function make_plural(form, special)
local retval = require(romut_module).handle_multiword(form, special, make_plural, prepositions)
if retval then
if #retval > 1 then
error("Internal error: Got multiple plurals from handle_multiword(): " .. concat(retval))
end
return retval[1]
end
if form:match("[sxz]$") then
return form
elseif form:match("au$") then
return form .. "x"
elseif form:match("al$") then
return form:gsub("al$", "aux")
end
return form .. "s"
end
local function make_feminine(form, special)
local retval = require(romut_module).handle_multiword(form, special, make_feminine, prepositions)
if retval then
if #retval > 1 then
error("Internal error: Got multiple feminines from handle_multiword(): " .. concat(retval))
end
return retval[1]
end
if form:match("e$") then
return form
elseif form:match("en$") then
return form .. "ne"
elseif form:match("er$") then
return form:gsub("er$", "ère")
elseif form:match("el$") then
return form .. "le"
elseif form:match("et$") then
return form .. "te"
elseif form:match("on$") then
return form .. "ne"
elseif form:match("ieur$") then
return form .. "e"
elseif form:match("teur$") then
return form:gsub("teur$", "trice")
elseif form:match("eu[rx]$") then
return form:gsub("eu[rx]$", "euse")
elseif form:match("if$") then
return form:gsub("if$", "ive")
elseif form:match("c$") then
return form:gsub("c$", "que")
elseif form:match("eau$") then
return form:gsub("eau$", "elle")
else
return form .. "e"
end
end
-- For bot use
function export.make_feminine(frame)
local masc = frame.args[1] or error("Masculine in 1= is required.")
local special = frame.args[2]
return make_feminine(masc, special)
end
-- Handle generation of feminine or plural inflections when "+" is encountered. `list` is the existing base forms
-- (strings or form objects) that need to be inflected. `defobj` is the form object whose `term` field is "+"; any
-- decorations from that form object will be combined with those of the base form. `inflfun` is the function to do the
-- inflection, which is passed two arguments, the base form value (a string) and the `special` value specified for
-- handling multiword expressions (or nil if no value specified). Returns the new list of inflected form objects.
local function make_inflection(list, defobj, inflfun, special)
local newlist = {}
for _, form in ipairs(list) do
if type(form) == "string" then
form = {term = form}
end
local infl = inflfun(form.term, special)
local formobj = m_table.shallowCopy(form)
formobj.term = infl
m_headword_utilities.combine_termobj_decorations(formobj, defobj)
insert(newlist, formobj)
end
return newlist
end
-----------------------------------------------------------------------------------------
-- Main entry point --
-----------------------------------------------------------------------------------------
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["splithyph"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean", alias_of = "nolink"},
["abbr"] = list_param,
["pagename"] = true, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolink then
if not heads[1] then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph, no_split_apostrophe_words)
if not heads[1] then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, head:sub(2))
heads[i] = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head after a
-- NNBSP (thin non-breaking space).
head = auto_linked_head .. nnbsp .. head
heads[i] = head
end
if head == auto_linked_head then
track("redundant-head")
end
end
end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = not user_specified_heads[1],
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
checkredlinks = check_missing and (pos_functions[poscat] and pos_functions[poscat].redlink_pos or true) or false,
}
if pagename:find("^%-") and poscat ~= "後綴形式" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = check_missing
local singular_poscat = poscat:gsub("s$", "")
insert(data.categories, langname .. "構成" .. singular_poscat .. "的後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
parse_and_insert_inflection(data, args, "abbr", "縮寫")
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet {
"m", "f", "mf", "mfbysense", "mfequiv", "gneut", "m-p", "f-p", "mf-p", "mfbysense-p", "mfequiv-p", "gneut-p",
"?", "?-p",
}
local additional_allowed_pronoun_genders = m_table.listToSet {
"m-s", "f-s", "mf-s",
"p", -- mf-p doesn't make sense for e.g. [[iels]]/[[ielles]]
"n", -- e.g. [[ceci]]/[[cela]]
}
local function validate_genders(genders, is_pronoun)
for _, g in ipairs(genders) do
if type(g) == "table" then
g = g.spec
end
if not allowed_genders[g] and (not is_pronoun or not additional_allowed_pronoun_genders[g]) then
error("Unrecognized gender: " .. g)
end
end
end
local function make_qual_replaced_by(replacement)
return {list = true, allow_holes = true, replaced_by = false,
instead = ("use an inline modifier on |%s= such as <q:...>, <qq:...>, <l:...> or <ll:...>"):format(replacement)
}
end
local function make_alias_replaced_by(replacement, reason)
return {list = true, disallow_holes = true, replaced_by = replacement, reason = reason or
"for consistency with the corresponding parameter in other Romance-language headword templates"
}
end
local function get_noun_params(is_proper)
return {
[1] = {list = "g", disallow_holes = true, required = not is_proper, default = "?", type = "genders",
flatten = true}, -- gender(s)
["g"] = {replaced_by = 1, reason = "for consistency"},
[2] = list_param, --plural override(s)
["pqual"] = make_qual_replaced_by("2"),
["f"] = list_param, --feminine form(s)
["fqual"] = make_qual_replaced_by("f"),
["m"] = list_param, --masculine form(s)
["fqual"] = make_qual_replaced_by("m"),
["dim"] = list_param, --diminutive(s)
["dimqual"] = make_qual_replaced_by("dim"),
["aug"] = list_param, --diminutive(s)
["pej"] = list_param, --pejorative(s)
["dem"] = list_param, --demonym(s)
["fdem"] = list_param, --female demonym(s)
}
end
local function do_noun(args, data, pos, is_proper)
local is_plurale_tantum = false
local has_singular = false
if pos == "基數名詞" then
pos = "數詞"
insert(data.categories, 1, langname .. "基數詞")
end
if data.is_suffix then
pos = "後綴"
end
local plpos = pos
validate_genders(args[1])
data.genders = args[1]
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if type(g) == "table" then
g = g.spec
end
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
end
end
local lemma = data.pagename
-- Plural
local plurals = {}
local function insert_noun_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if is_plurale_tantum and not has_singular then
if args[2][1] then
error("Can't specify plurals of plurale tantum " .. pos)
end
insert(data.inflections, {label = "唯複"})
else
plurals = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "pl"},
forms = args[2],
splitchar = ",",
}
-- Check for special plural signals
local mode = nil
local pl1 = plurals[1]
if pl1 and #pl1.term == 1 then
mode = pl1.term
if mode == "?" or mode == "!" or mode == "-" or mode == "~" then
pl1.term = nil
if next(pl1) then
error(("Can't specify inline modifiers with plural code '%s'"):format(mode))
end
table.remove(plurals, 1) -- Remove the mode parameter
elseif mode ~= "+" and mode ~= "#" and not mode:find("[a-zA-Z]") then
error(("Unexpected plural code '%s'"):format(mode))
end
end
if is_plurale_tantum then
-- both singular and plural
insert(data.inflections, {label = "有時" .. glossary_link("唯複") .. ",存在變體"})
end
if mode == "?" then
-- Plural is unknown
insert(data.categories, langname .. "有未知或不確定複數的" .. plpos)
elseif mode == "!" then
-- Plural is not attested
insert(data.inflections, {label = "複數未證實"})
insert(data.categories, langname .. "有未證實複數的" .. plpos)
if plurals[1] then
error("Can't specify any plurals along with unattested plural code '!'")
end
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
insert(data.categories, langname .. "不可數" .. plpos)
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
insert(data.categories, langname .. "可數" .. plpos)
else
insert(data.inflections, {label = glossary_link("不可數")})
end
else
-- Countable or mixed countable/uncountable
if not plurals[1] and not is_proper then
plurals[1] = {term = "+"}
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
insert(data.inflections, {label = glossary_link("可數") .. "和" .. glossary_link("不可數")})
insert(data.categories, langname .. "不可數" .. plpos)
insert(data.categories, langname .. "可數" .. plpos)
elseif plurals[1] then
-- Countable nouns
insert(data.categories, langname .. "可數" .. plpos)
else
-- Uncountable nouns
insert(data.categories, langname .. "不可數" .. plpos)
end
end
-- Gather plurals, handling requests for default plurals.
for _, pl in ipairs(plurals) do
if pl.term == "+" then
pl.term = make_plural(lemma)
elseif pl.term:find("^%+") then
pl.term = require(romut_module).get_special_indicator(pl.term)
pl.term = make_plural(lemma, pl.term)
elseif pl.term == "s" or pl.term == "x" then
-- FIXME, convert to #s or #x
pl.term = lemma .. pl.term
else
pl.term = replace_hash_with_lemma(pl.term, lemma)
end
end
local pl1 = plurals[1]
if pl1 and not plurals[2] and pl1.term == lemma then
insert(data.inflections, {label = glossary_link("無屈折"),
q = pl1.q, qq = pl1.qq, l = pl1.l, ll = pl1.ll, refs = pl1.refs
})
insert(data.categories, langname .. "無屈折" .. plpos)
else
insert_noun_inflection(plurals, "複數", "p")
end
if plurals[2] then
insert(data.categories, langname .. "有多個複數的" .. plpos)
end
end
-- Gather masculines/feminines. For each one, generate the corresponding plural. `field` is the name of the field
-- containing the masculine or feminine forms (normally "m" or "f"); and `inflect` is a function of one or two
-- arguments to generate the default masculine or feminine from the lemma (the arguments are the lemma and
-- optionally a "special" flag to indicate how to handle multiword expressions (or nil if no value specified). Returns the new list
-- of inflected form objects.
local function handle_mf(field, inflect, default_plurals)
local function call_inflect(special)
if inflect then
-- Generate default feminine.
return inflect(lemma, special)
else
-- FIXME
error("Can't generate default masculine currently")
end
end
local mfs = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
frob = function(term)
if term == "+" then
-- Generate default masculine/feminine.
term = call_inflect()
elseif term == "e" and field == "f" then
-- FIXME: remove this special case
term = lemma .. "e"
else
term = replace_hash_with_lemma(term, lemma)
end
local special = require(romut_module).get_special_indicator(term)
if special then
term = call_inflect(special)
end
return term
end
}
return mfs
end
local feminines = handle_mf("f", make_feminine)
local masculine_plurals = {}
local masculines = handle_mf("m", nil)
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
insert_noun_inflection(feminines, "陰性", "f")
insert_noun_inflection(masculines, "陽性")
parse_and_insert_noun_inflection("dim", "指小詞")
parse_and_insert_noun_inflection("aug", "指大詞")
parse_and_insert_noun_inflection("pej", "貶義詞")
parse_and_insert_noun_inflection("dem", "居民稱謂詞")
parse_and_insert_noun_inflection("fdem", "女性居民稱謂詞")
end
pos_functions["名詞"] = {
params = get_noun_params(),
func = function(args, data)
do_noun(args, data, "名詞")
end,
}
pos_functions["專有名詞"] = {
params = get_noun_params("is proper"),
func = function(args, data)
do_noun(args, data, "專有名詞", "is proper")
end,
}
pos_functions["基數名詞"] = {
params = get_noun_params(),
func = function(args, data)
do_noun(args, data, "基數名詞")
end,
pos_category = "數詞",
}
local function get_pronoun_pos(pos)
return {
params = {
[1] = {list = "g", disallow_holes = true, required = true, default = "?", type = "genders",
flatten = true}, -- gender(s)
["g"] = {replaced_by = 1, reason = "for consistency"},
["f"] = list_param,
["fqual"] = make_qual_replaced_by("f"),
["m"] = list_param,
["mqual"] = make_qual_replaced_by("m"),
["mv"] = list_param,
["mvqual"] = make_qual_replaced_by("mv"),
["fpl"] = list_param,
["fp"] = make_alias_replaced_by("fpl"),
["fpqual"] = make_qual_replaced_by("fpl"),
["mpl"] = list_param,
["mp"] = make_alias_replaced_by("mpl"),
["mpqual"] = make_qual_replaced_by("mpl"),
["pl"] = list_param,
["p"] = make_alias_replaced_by("pl"),
["pqual"] = make_qual_replaced_by("pl"),
["type"] = list_param,
},
func = function(args, data)
-- Validate and add genders.
validate_genders(args[1], "is pronoun")
data.genders = args[1]
local plpos = pos
local function parse_and_insert_pronoun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
-- Parse and insert all inflections.
parse_and_insert_pronoun_inflection("m", "陽性")
parse_and_insert_pronoun_inflection("mv", "元音前陽性單數")
parse_and_insert_pronoun_inflection("f", "陰性")
parse_and_insert_pronoun_inflection("mpl", "陽性複數")
parse_and_insert_pronoun_inflection("fpl", "陰性複數")
parse_and_insert_pronoun_inflection("pl", "複數")
-- Categorize by "type"
for _, ty in ipairs(args.type) do
local category
if ty == "indefinite" then
category = "不定"
elseif ty == "interrogative" then
category = "疑問"
elseif ty == "personal" then
category = "人稱"
elseif ty == "possessive" then
category = "所有格"
elseif ty == "reflexive" then
category = "反身"
elseif ty == "relative" then
category = "關係"
end
if category then
if type(category) == "table" then
for _, cat in ipairs(category) do
insert(data.categories, langname .. cat .. pos)
end
else
insert(data.categories, langname .. category .. pos)
end
end
end
end
}
end
pos_functions["代詞"] = get_pronoun_pos("代詞")
pos_functions["限定詞"] = get_pronoun_pos("限定詞")
local function get_misc_pos()
return {
params = {
[1] = {replaced_by = "head", reason = "for consistency with other French headword templates"},
},
func = function(args, data)
end
}
end
pos_functions["副詞"] = get_misc_pos()
pos_functions["介詞"] = get_misc_pos()
pos_functions["短語"] = get_misc_pos()
pos_functions["介詞短語"] = get_misc_pos()
pos_functions["諺語"] = get_misc_pos()
pos_functions["標點符號"] = get_misc_pos()
pos_functions["附加符號"] = get_misc_pos()
pos_functions["感嘆詞"] = get_misc_pos()
pos_functions["前綴"] = get_misc_pos()
pos_functions["縮寫"] = get_misc_pos()
local function do_adjective(pos)
return {
params = {
[1] = true,
["inv"] = boolean_param,
["sp"] = true, -- special indicator: "first", "first-last", etc.
["onlyg"] = true,
["m"] = list_param,
["mqual"] = make_qual_replaced_by("m"),
["mv"] = list_param,
["mvqual"] = make_qual_replaced_by("mv"),
["f"] = list_param,
["fqual"] = make_qual_replaced_by("f"),
["mpl"] = list_param,
["mp"] = make_alias_replaced_by("mpl"),
["mpqual"] = make_qual_replaced_by("mpl"),
["fpl"] = list_param,
["fp"] = make_alias_replaced_by("fpl"),
["fpqual"] = make_qual_replaced_by("fpl"),
["pl"] = list_param,
["p"] = make_alias_replaced_by("pl"),
["pqual"] = make_qual_replaced_by("pl"),
["base"] = list_param,
["current"] = make_alias_replaced_by("base", "because the old name was obscure and did not clarify the purpose of the parameter"),
["comp"] = list_param,
["compqual"] = make_qual_replaced_by("comp"),
["sup"] = list_param,
["supqual"] = make_qual_replaced_by("sup"),
["intr"] = boolean_param,
},
pos_category = pos == "基數形容詞" and "數詞" or nil,
redlink_pos = pos == "分詞" and "分詞" or nil,
func = function(args, data)
local is_numeral = false
if pos == "基數形容詞" then
pos = "數詞"
is_numeral = true
insert(data.categories, 1, langname .. "基數詞")
end
if data.is_suffix then
pos = "後綴"
end
local plpos = pos
if not is_numeral then
if args.onlyg == "p" or args.onlyg == "m-p" or args.onlyg == "f-p" then
insert(data.categories, langname .. "唯複數詞")
end
if args.onlyg == "s" or args.onlyg == "f-s" or args.onlyg == "f-s" then
insert(data.categories, langname .. "唯單數詞")
end
if args.onlyg then
insert(data.categories, langname .. "不完全變化" .. plpos)
end
end
local lemma = data.pagename
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
local function process_inflection(label, arg, accel, get_default, explicit_default_only)
local orig_infls = m_headword_utilities.parse_term_list_with_modifiers {
paramname = arg,
forms = args[arg],
splitchar = ",",
}
if not orig_infls[1] then
if explicit_default_only or not get_default then
orig_infls = {}
else
orig_infls = {{term = "+"}}
end
end
local infls = {}
if orig_infls[1] then
for _, infl in ipairs(orig_infls) do
if infl.term == "+" then
local defs
if get_default then
defs = get_default(infl)
else
error("Can't use '+' with " .. arg .. "=; no default available")
end
for _, def in ipairs(defs) do
insert(infls, def)
end
elseif infl.term == "e" or infl.term == "s" or infl.term == "x" then
-- FIXME: delete this in favor of #e, #s, #x
infl.term = lemma .. infl.term
insert(infls, infl)
else
infl.term = replace_hash_with_lemma(infl.term, lemma)
insert(infls, infl)
end
end
insert_inflection(infls, label, accel)
end
return infls
end
if args.sp and not require(romut_module).allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(require(romut_module).allowed_special_indicators) do
insert(indicators, "'" .. indic .. "'")
end
sort(indicators)
error("Special inflection indicator beginning can only be " ..
mw.text.listToText(indicators) .. ": " .. args.sp)
end
local function get_base()
return args.base[1] and args.base or {lemma}
end
if args.onlyg == "p" then
insert(data.inflections, {label = "唯複"})
if args[1] ~= "mf" then
-- Handle feminine plurals
process_inflection("陰性複數", "fpl", "f|p")
end
elseif args.onlyg == "s" then
insert(data.inflections, {label = "唯單"})
if not (args[1] == "mf" or not args.f[1] and lemma:match("e$")) then
-- Handle feminines
process_inflection("陰性單數", "f", "f", function(defobj)
return make_inflection(get_base(), defobj, make_feminine, args.sp)
end)
end
elseif args.onlyg == "m" then
insert(data.genders, "m")
insert(data.inflections, {label = "僅陽性"})
-- Handle masculine plurals
process_inflection("陽性複數", "mpl", "m|p", function(defobj)
return make_inflection(get_base(), defobj, make_plural, args.sp)
end)
elseif args.onlyg == "f" then
insert(data.genders, "f")
insert(data.inflections, {label = "僅陰性"})
-- Handle feminine plurals
process_inflection("陰性複數", "fpl", "f|p", function(defobj)
return make_inflection(get_base(), defobj, make_plural, args.sp)
end)
elseif args.onlyg then
insert(data.genders, args.onlyg)
insert(data.inflections, {label = "不完全變化"})
else
-- Gather genders
local gender = args[1]
-- Default to mf if base form ends in -e and no feminine,
-- feminine plural or gender specified
if not gender and not args.f[1] and not args.fpl[1] and lemma:match("e$")
and not lemma:find(" ", nil, true) then
gender = "mf"
end
if args.intr then
insert(data.inflections, {label = glossary_link("不及物")})
insert(data.inflections, {label = "因此" .. glossary_link("無屈折")})
args.inv = true
elseif args.inv then
insert(data.inflections, {label = glossary_link("無屈折")})
end
-- Handle plurals of mf adjectives
if not args.inv and gender == "mf" then
process_inflection("複數", "pl", "p", function(defobj)
return make_inflection(get_base(), defobj, make_plural, args.sp)
end)
end
if not args.inv and gender ~= "mf" then
-- Handle masculine form if not same as lemma; e.g. [[sûr de soi]] with m=+, m2=sûr de lui
process_inflection("陽性單數", "m", "m|s",
function(defobj)
defobj = m_table.shallowCopy(defobj)
defobj.term = lemma
return {defobj}
end, "explicit default only")
-- Handle case of special masculine singular before vowel
process_inflection("元音前陽性單數", "mv", "m|s")
-- Handle feminines
local feminines = process_inflection("陰性", "f", "f|s", function(defobj)
return make_inflection(get_base(), defobj, make_feminine, args.sp)
end)
-- Handle masculine plurals
process_inflection("陽性複數", "mpl", "m|p", function(defobj)
return make_inflection(get_base(), defobj, make_plural, args.sp)
end)
-- Handle feminine plurals
process_inflection("陰性複數", "fpl", "f|p", function(defobj)
return make_inflection(feminines, defobj, make_plural, args.sp)
end)
end
end
-- Handle comparatives
process_inflection("比較級", "comp", "comparative")
-- Handle superlatives
process_inflection("最高級", "sup", "superlative")
end
}
end
pos_functions["形容詞"] = do_adjective("形容詞")
pos_functions["過去分詞"] = do_adjective("分詞")
pos_functions["基數形容詞"] = do_adjective("基數形容詞")
pos_functions["動詞"] = {
params = {
["type"] = list_param,
},
func = function(args, data)
local pos = "動詞"
for _, ty in ipairs(args.type) do
local category, label
if ty == "auxiliary" then
category = "助"
elseif ty == "defective" then
category = "不完全變化"
label = glossary_link("不完全變化")
elseif ty == "impersonal" then
category = "無人稱"
label = glossary_link("無人稱")
elseif ty == "modal" then
category = "情態"
elseif ty == "reflexive" then
category = "自反"
elseif ty == "transitive" then
label = glossary_link("及物")
category = "及物"
elseif ty == "intransitive" then
label = glossary_link("不及物")
category = "不及物"
elseif ty == "ambitransitive" or ty == "ambi" then
category = {"及物", "不及物"}
label = glossary_link("及物") .. "和" .. glossary_link("不及物")
end
if category then
if type(category) == "table" then
for _, cat in ipairs(category) do
insert(data.categories, langname .. cat .. pos)
end
else
insert(data.categories, langname .. category .. pos)
end
end
if label then
insert(data.inflections, {label = label})
end
end
end
}
pos_functions["cardinal invariable"] = {
params = {},
func = function(args, data)
data.pos_category = "數詞"
insert(data.categories, langname .. "基數詞")
insert(data.categories, langname .. "無屈折數詞")
insert(data.inflections, {label = glossary_link("無屈折")})
end
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, "構成" .. typ .. "的後綴")
end
insert(data.inflections, {label = m_table.serialCommaJoin(suffix_type, {conj = "or"}) .. "的非詞元形式"})
end,
}
return export
ks9lu2123oqozyuvydr6gqghvymq4d0
Module:Pt-headword
828
1426258
9909099
9883378
2026-09-29T07:27:53Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909099
Scribunto
text/plain
-- This module contains code for Portuguese headword templates.
-- Templates covered are:
-- * {{pt-noun}}, {{pt-proper noun}};
-- * {{pt-verb}};
-- * {{pt-adj}}, {{pt-adj-comp}}, {{pt-adj-sup}};
-- * {{pt-det}};
-- * {{pt-pron-adj}};
-- * {{pt-contr-adj}};
-- * {{pt-pp}};
-- * {{pt-cardinal}};
-- * {{pt-adv}}.
-- See [[Module:pt-verb]] for Portuguese conjugation templates.
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local m_strutils = require("Module:string utilities")
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local com = require("Module:pt-common")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local inflection_utilities_module = "Module:inflection utilities"
local romut_module = "Module:romance utilities"
local pt_verb_module = "Module:pt-verb"
local lang = require("Module:languages").getByCode("pt")
local langname = lang:getCanonicalName()
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local rfind = m_strutils.find
local rmatch = m_strutils.match
local ulower = m_strutils.lower
local usub = m_strutils.sub
local uupper = m_strutils.upper
-- When followed by a hyphen in a hyphenated compound, the hyphen will be included with the prefix when linked.
local include_hyphen_prefixes = m_table.listToSet {
"ab",
"afro",
"anarco",
"anglo",
"ântero",
"anti",
"auto",
"contra",
"ex",
"franco",
"hiper",
"infra",
"inter",
"intra",
"macro",
"micro",
"neo",
"pan",
"pós",
"pré",
"pró",
"proto",
"sobre",
"sub",
"super",
"vice",
}
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function track(page)
require("Module:debug/track")("pt-headword/" .. page)
return true
end
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
local metaphonic_label = "[[Appendix:葡萄牙語發音#元音變化|元音變化]]"
local function make_plural(sg, special)
local pl = com.make_plural(sg, special)
if not pl then
error(("Internal error: Unable to generate default plural of '%s'"):format(sg))
end
return pl
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;
-- `plpos` is the plural part of speech, used in [[Category:LANGNAME PLPOS with red links in their headword lines]].
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel, frob)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
include_mods = {"g"},
label = label,
accel = accel and {form = accel} or nil,
frob = frob,
}
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local parargs = frame:getParent().args
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["splithyph"] = boolean_param,
["nolinkhead"] = boolean_param,
["nolink"] = {type = "boolean", alias_of = "nolinkhead"},
["abbr"] = list_param,
["pagename"] = true, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolinkhead then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph, nil,
include_hyphen_prefixes)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
end
end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
checkredlinks = pos_functions[poscat] and pos_functions[poscat].redlink_pos or true,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = true
local singular_poscat = poscat
insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
local pagename_lower = ulower(pagename)
for _, ch in ipairs{"gü", "jh", "kh", "ph", "qü", "sh", "th", "tl", "ts", "tz", "wh", "zh"} do
if pagename_lower:find(ch) then
insert(data.categories, "帶「" .. uupper(ch) .. "」的" .. langname .. "詞")
end
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
parse_and_insert_inflection(data, args, "abbr", "縮寫")
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture replace
-- expression.
lemma = lemma:gsub("%%", "%%%%")
return (term:gsub("#", lemma))
end
local function is_metaphonic(args, lemma)
if args.nometa then
return false
end
if args.meta then
return true
end
-- Anything in -oso with a preceding vowel (e.g. [[gostoso]], [[curioso]]) is normally metaphonic.
return rfind(lemma, com.V .. ".*oso$")
end
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet(
{"m", "f", "mf", "mfbysense", "mfequiv", "gneut", "n", "m-p", "f-p", "mf-p", "mfbysense-p", "mfequiv-p", "gneut-p", "n-p", "?", "?-p"}
)
local function validate_genders(genders)
for _, g in ipairs(genders) do
if type(g) == "table" then
g = g.spec
end
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
end
end
local function do_noun(args, data, is_proper)
local is_plurale_tantum = false
local has_singular = false
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
validate_genders(args[1])
data.genders = args[1]
local saw_m = false
local saw_f = false
local saw_gneut = false
local gender_for_irreg_ending
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if type(g) == "table" then
g = g.spec
end
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
if g == "gneut" then
saw_gneut = true
end
end
end
if saw_m and saw_f then
gender_for_irreg_ending = "mf"
elseif saw_f then
gender_for_irreg_ending = "f"
else
gender_for_irreg_ending = "m"
end
local lemma = data.pagename
local plurals = {}
local function insert_noun_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if is_plurale_tantum and not has_singular then
if args[2][1] then
error("Can't specify plurals of plurale tantum " .. category_pos)
end
insert(data.inflections, {label = glossary_link("唯複")})
else
plurals = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "pl"},
forms = args[2],
splitchar = ",",
}
-- Check for special plural signals
local mode = nil
local pl1 = plurals[1]
if pl1 and #pl1.term == 1 then
mode = pl1.term
if mode == "?" or mode == "!" or mode == "-" or mode == "~" then
pl1.term = nil
if next(pl1) then
error(("Can't specify inline modifiers with plural code '%s'"):format(mode))
end
table.remove(plurals, 1) -- Remove the mode parameter
elseif mode ~= "+" and mode ~= "#" then
error(("Unexpected plural code '%s'"):format(mode))
end
end
if is_plurale_tantum then
-- both singular and plural
insert(data.inflections, {label = "有時" .. glossary_link("唯複") .. ",在變化中"})
end
if mode == "?" then
-- Plural is unknown
insert(data.categories, "有未知或不確定複數的" .. langname .. category_plpos)
elseif mode == "!" then
-- Plural is not attested
insert(data.inflections, {label = "複數無紀錄"})
insert(data.categories, "有無紀錄複數的" .. langname .. category_plpos)
if plurals[1] then
error("Can't specify any plurals along with unattested plural code '!'")
end
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
insert(data.categories, langname .. "不可數" .. category_plpos)
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
insert(data.categories, langname .. "可數" .. category_plpos)
else
insert(data.inflections, {label = glossary_link("不可數")})
end
else
-- Countable or mixed countable/uncountable
if not plurals[1] and not is_proper then
plurals[1] = {term = "+"}
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
insert(data.inflections, {label = glossary_link("可數") .. "和" .. glossary_link("不可數")})
insert(data.categories, langname .. "不可數" .. category_plpos)
insert(data.categories, langname .. "可數" .. category_plpos)
elseif plurals[1] then
-- Countable nouns
insert(data.categories, langname .. "可數" .. category_plpos)
else
-- Uncountable nouns
insert(data.categories, langname .. "不可數" .. category_plpos)
end
end
-- Gather plurals, handling requests for default plurals.
local has_default_or_hash = false
for _, pl in ipairs(plurals) do
if pl.term:find("^%+") or pl.term:find("#") then
has_default_or_hash = true
break
end
end
if has_default_or_hash then
for _, pl in ipairs(plurals) do
if pl.term == "+" then
pl.term = make_plural(lemma)
elseif pl.term:find("^%+") then
pl.term = require(romut_module).get_special_indicator(pl.term)
pl.term = make_plural(lemma, pl.term)
else
pl.term = replace_hash_with_lemma(pl.term, lemma)
end
end
end
local pl1 = plurals[1]
if pl1 and not plurals[2] and pl1.term == lemma then
insert(data.inflections, {label = glossary_link("無屈折"),
q = pl1.q, qq = pl1.qq, l = pl1.l, ll = pl1.ll, refs = pl1.refs
})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
insert_noun_inflection(plurals, "複數", "p")
end
if plurals[2] then
insert(data.categories, "有多個複數的" .. langname .. category_plpos)
end
end
-- Gather masculines/feminines. For each one, generate the corresponding plural. `field` is the name of the field
-- containing the masculine or feminine forms (normally "m" or "f"); `inflect` is a function of one or two arguments
-- to generate the default masculine or feminine from the lemma (the arguments are the lemma and optionally a
-- "special" flag to indicate how to handle multiword lemmas, and the function is normally make_feminine or
-- make_masculine from [[Module:pt-common]]); and `default_plurals` is a list into which the corresponding default
-- plurals of the gathered or generated masculine or feminine forms are stored.
local function handle_mf(field, inflect, default_plurals)
local specials = {} -- Tracks the specific plural indicators for each entry in a multi-item list
local term_index = 0
local mfs = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
frob = function(term)
term_index = term_index + 1
if term == "+" then
-- Generate default masculine/feminine.
term = inflect(lemma)
else
term = replace_hash_with_lemma(term, lemma)
end
local special = require(romut_module).get_special_indicator(term)
if special then
specials[term_index] = special -- Store indicator unique to this specific item's index
term = inflect(lemma, special)
end
return term
end
}
for i, mf in ipairs(mfs) do
local plobj = m_table.shallowCopy(mf)
local term_special = specials[i] -- Retrieve indicator matching this exact singular's index
plobj.term = make_plural(mf.term, term_special)
-- Add an accelerator for each masculine/feminine plural whose lemma is the corresponding singular, so that
-- the accelerated entry that is generated has a definition that looks like
-- # {{plural of|pt|MFSING}}
plobj.accel = {form = "p", lemma = mf.term}
insert(default_plurals, plobj)
end
return mfs
end
local feminine_plurals = {}
local feminines = handle_mf("f", com.make_feminine, feminine_plurals)
local masculine_plurals = {}
local masculines = handle_mf("m", com.make_masculine, masculine_plurals)
local function handle_mf_plural(mfplfield, default_plurals, singulars)
local mfpl = m_headword_utilities.parse_term_list_with_modifiers {
paramname = mfplfield,
forms = args[mfplfield],
splitchar = ",",
}
if is_plurale_tantum then
return mfpl
end
local new_mfpls = {}
local saw_plus
for i, mfpl in ipairs(mfpl) do
local accel
if #mfpl == #singulars then
-- If same number of overriding masculine/feminine plurals as singulars, assume each plural goes with
-- the corresponding singular and use each corresponding singular as the lemma in the accelerator. The
-- generated entry will have
-- # {{plural of|pt|SINGULAR}}
-- as the definition.
accel = {form = "p", lemma = singulars[i].term}
else
accel = nil
end
if mfpl.term == "+" then
-- We should never see + twice. If we do, it will lead to problems since we overwrite the values of
-- default_plurals the first time around.
if saw_plus then
error(("Saw + twice when handling %s="):format(mfplfield))
end
saw_plus = true
if not default_plurals[1] then
-- FIXME: Can this happen? Not in corresponding Spanish code and the old Portuguese code tried to
-- handle this condition by generating the default plural from the lemma.
error("Internal error: Something wrong, no generated default m/f plurals at this stage")
end
for _, defpl in ipairs(default_plurals) do
-- defpl is already a table and has an accel field
m_headword_utilities.combine_termobj_decorations(defpl, mfpl)
insert(new_mfpls, defpl)
end
elseif mfpl.term:find("^%+") then
mfpl.term = require(romut_module).get_special_indicator(mfpl.term)
for _, mf in ipairs(singulars) do
local mfplobj = m_table.shallowCopy(mfpl)
mfplobj.term = make_plural(mf.term, mfpl.term)
mfplobj.accel = accel
m_headword_utilities.combine_termobj_decorations(mfplobj, mf)
insert(new_mfpls, mfplobj)
end
else
mfpl.accel = accel
mfpl.term = replace_hash_with_lemma(mfpl.term, lemma)
insert(new_mfpls, mfpl)
end
end
return new_mfpls
end
if args.fpl[1] then
-- Override any existing feminine plurals.
feminine_plurals = handle_mf_plural("fpl", feminine_plurals, feminines)
end
if args.mpl[1] then
-- Override any existing masculine plurals.
masculine_plurals = handle_mf_plural("mpl", masculine_plurals, masculines)
end
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
insert_noun_inflection(feminines, "陰性", "f")
insert_noun_inflection(feminine_plurals, "陰性複數")
insert_noun_inflection(masculines, "陽性")
insert_noun_inflection(masculine_plurals, "陽性複數")
if is_metaphonic(args, lemma) then
insert(data.inflections, {label = metaphonic_label})
insert(data.categories, "有元音變音的" .. langname .. category_plpos)
end
parse_and_insert_noun_inflection("dim", "指小")
parse_and_insert_noun_inflection("aug", "指大")
parse_and_insert_noun_inflection("pej", "貶義")
parse_and_insert_noun_inflection("dem", "居民稱謂詞")
parse_and_insert_noun_inflection("fdem", "女性居民稱謂詞")
-- Maybe add category 'Portuguese nouns with irregular gender' (or similar)
local irreg_gender_lemma = com.rsub(lemma, " .*", "") -- only look at first word
if (rfind(irreg_gender_lemma, "[^ã]o$") and (gender_for_irreg_ending == "f" or gender_for_irreg_ending == "mf")) or
(irreg_gender_lemma:find("a$") and (gender_for_irreg_ending == "m" or gender_for_irreg_ending == "mf")) then
insert(data.categories, "有不規則性別的" .. langname .. category_plpos)
end
end
local function get_noun_params(is_proper)
return {
[1] = {list = "g", disallow_holes = true, required = not is_proper, default = "?", type = "genders",
flatten = true},
[2] = {list = "pl", disallow_holes = true},
["m"] = list_param,
["f"] = list_param,
["mpl"] = list_param,
["fpl"] = list_param,
["dim"] = list_param, --diminutive(s)
["aug"] = list_param, --diminutive(s)
["pej"] = list_param, --pejorative(s)
["dem"] = list_param, --demonym(s)
["fdem"] = list_param, --female demonym(s)
["meta"] = boolean_param, -- metaphonic
["nometa"] = boolean_param, -- explicitly not metaphonic
}
end
pos_functions["名詞"] = {
params = get_noun_params(),
func = do_noun,
}
pos_functions["專有名詞"] = {
params = get_noun_params("is proper"),
func = function(args, data)
do_noun(args, data, "is proper noun")
end,
}
-----------------------------------------------------------------------------------------
-- Pronouns --
-----------------------------------------------------------------------------------------
local function do_pronoun(args, data)
local lemma = data.pagename
validate_genders(args[1])
data.genders = args[1]
local function do_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
do_inflection("m", "陽性")
do_inflection("f", "陰性")
do_inflection("sg", "單數")
do_inflection("pl", "複數")
do_inflection("mpl", "陽性複數")
do_inflection("fpl", "陰性複數")
do_inflection("n", "中性")
end
local function get_pronoun_params()
local params = {
[1] = {list = "g", disallow_holes = true, type = "genders", flatten = true}, --gender(s)
["m"] = list_param, --masculine form(s)
["f"] = list_param, --feminine form(s)
["sg"] = list_param, --singular form(s)
["pl"] = list_param, --plural form(s)
["mpl"] = list_param, --masculine plural form(s)
["fpl"] = list_param, --feminine plural form(s)
["n"] = list_param, --neuter form(s)
}
return params
end
pos_functions["代詞"] = {
params = get_pronoun_params(),
func = do_pronoun,
}
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
-- Handle comparatives and superlatives for adjectives and adverbs, including user-specified comparatives and
-- superlatives, default-requested comparatives/superlatives using '+', autogenerated comparatives/superlatives,
-- and hascomp=. Code is the same for adjectives and adverbs.
local function handle_adj_adv_comp(args, data, plpos, is_adv)
local lemma = data.pagename
local stem
if is_adv then
stem = com.rsub(lemma, "mente$", "")
else
stem = lemma
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
local function make_absolute_superlative(special)
if is_adv then
return com.make_adverbial_absolute_superlative(stem, special)
else
return com.make_absolute_superlative(stem, special)
end
end
-- Maybe autogenerate default comparative/superlative.
local comp, sup
if args.comp and args.sup then-- comp= and sup= were given as options to the user
-- If no comp, but a non-default sup given, then add the default comparative/superlative.
-- This is useful when an absolute superlative is given.
comp = m_headword_utilities.parse_term_list_with_modifiers {
paramname = "comp",
forms = args.comp,
splitchar = ",",
}
sup = m_headword_utilities.parse_term_list_with_modifiers {
paramname = "sup",
forms = args.sup,
splitchar = ",",
}
local saw_sup_plus = false
if not comp[1] and sup[1] then
for _, supval in ipairs(sup) do
if supval.term == "+" then
saw_sup_plus = true
end
end
if not saw_sup_plus then
comp = {{term = "+"}}
insert(sup, 1, {term = "+"})
end
end
-- If comp=+, use default comparative 'mais ...', and set a default superlative if unspecified.
local saw_comp_plus = false
for _, compval in ipairs(comp) do
if compval.term == "+" then
saw_comp_plus = true
compval.term = "[[mais]] [[" .. lemma .. "]]"
end
end
if saw_comp_plus and not sup[1] then
sup = {{term = "+"}}
end
-- If sup=+ (possibly from comp=+), use default superlative 'o mais ...'. Also handle absolute superlatives.
for _, supval in ipairs(sup) do
if supval.term == "+" then
supval.term = "[[o]] [[mais]] [[" .. lemma .. "]]"
elseif supval.term == "+abs" then
supval.term = make_absolute_superlative()
elseif rfind(supval.term, "^%+abs:") then
local sp = rmatch(supval.term, "^%+abs:(.*)$")
supval.term = make_absolute_superlative(sp)
end
end
end
if args.hascomp then
if args.hascomp == "both" then
insert(data.inflections, {label = "有時" .. glossary_link("有比較級")})
insert(data.categories, langname .. "有比較級" .. plpos)
insert(data.categories, langname .. "無比較級" .. plpos)
else
local hascomp = require("Module:yesno")(args.hascomp)
if hascomp == true then
insert(data.inflections, {label = glossary_link("比較級")})
insert(data.categories, langname .. "有比較級" .. plpos)
elseif hascomp == false then
insert(data.inflections, {label = "無" .. glossary_link("比較級")})
insert(data.categories, langname .. "無比較級" .. plpos)
else
error("Unrecognized value for hascomp=: " .. args.hascomp)
end
end
elseif comp and comp[1] or sup and sup[1] then
insert(data.inflections, {label = glossary_link("比較級")})
insert(data.categories, langname .. "有比較級" .. plpos)
end
insert_inflection(comp, "比較級")
insert_inflection(sup, "最高級")
end
local function do_adjective(args, data, is_superlative)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
-- Use "participle" not "past participle" for categories such as 'invariable participles'
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
insert(indicators, "'" .. indic .. "'")
end
table.sort(indicators)
error("Special inflection indicator beginning can only be " ..
mw.text.listToText(indicators) .. ": " .. args.sp)
end
end
local lemma = data.pagename
local function fetch_inflections(field)
local retval = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
}
if not retval[1] then
return {{term = "+"}}
end
return retval
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if args.short then
insert(data.inflections, {label = "[[Appendix:葡萄牙語動詞#分詞|短分詞]]"})
end
if args.inv then
-- invariable adjective
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
if args.sp or args.f[1] or args.pl[1] or args.mpl[1] or args.fpl[1] then
error("Can't specify inflections with an invariable " .. category_pos)
end
elseif args.fonly then
-- feminine-only
if args.f[1] then
error("Can't specify explicit feminines with feminine-only " .. category_pos)
end
if args.pl[1] then
error("Can't specify explicit plurals with feminine-only " .. category_pos .. ", use fpl=")
end
if args.mpl[1] then
error("Can't specify explicit masculine plurals with feminine-only " .. category_pos)
end
local argsfpl = fetch_inflections("fpl")
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
fpl.term = make_plural(lemma, args.sp)
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
end
insert(feminine_plurals, fpl)
end
insert(data.inflections, {label = "僅陰性"})
insert_inflection(feminine_plurals, "陰性複數", "f|p")
else
-- Gather feminines.
for _, f in ipairs(fetch_inflections("f")) do
if f.term == "+" then
-- Generate default feminine.
f.term = com.make_feminine(lemma, args.sp)
else
f.term = replace_hash_with_lemma(f.term, lemma)
end
insert(feminines, f)
end
local fem_like_lemma = #feminines == 1 and feminines[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(feminines[1])
if fem_like_lemma then
insert(data.categories, langname .. "兩性" .. category_plpos)
end
local mpl_field = "mpl"
local fpl_field = "fpl"
if args.pl[1] then
if args.mpl[1] or args.fpl[1] then
error("Can't specify both pl= and mpl=/fpl=")
end
mpl_field = "pl"
fpl_field = "pl"
end
local argsmpl = fetch_inflections(mpl_field)
local argsfpl = fetch_inflections(fpl_field)
for _, mpl in ipairs(argsmpl) do
if mpl.term == "+" then
-- Generate default masculine plural.
mpl.term = make_plural(lemma, args.sp)
else
mpl.term = replace_hash_with_lemma(mpl.term, lemma)
end
insert(masculine_plurals, mpl)
end
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local fplobj = m_table.shallowCopy(fpl)
fplobj.term = make_plural(f.term, args.sp)
m_headword_utilities.combine_termobj_decorations(fplobj, f)
insert(feminine_plurals, fplobj)
end
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
insert(feminine_plurals, fpl)
end
end
local fem_pl_like_masc_pl = masculine_plurals[1] and feminine_plurals[1] and
m_table.deepEquals(masculine_plurals, feminine_plurals)
local masc_pl_like_lemma = #masculine_plurals == 1 and masculine_plurals[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(masculine_plurals[1])
if fem_like_lemma and fem_pl_like_masc_pl and masc_pl_like_lemma then
-- actually invariable
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
-- Make sure there are feminines given and not same as lemma.
if not fem_like_lemma then
insert_inflection(feminines, "陰性", "f|s")
elseif args.gneut then
data.genders = {"gneut"}
else
data.genders = {"mf"}
end
if fem_pl_like_masc_pl then
if args.gneut then
insert_inflection(masculine_plurals, "複數", "p")
else
-- This is how the Spanish module works.
-- insert_inflection(masculine_plurals, "陽性和陰性複數", "p")
insert_inflection(masculine_plurals, "複數", "p")
end
else
insert_inflection(masculine_plurals, "陽性複數", "m|p")
insert_inflection(feminine_plurals, "陰性複數", "f|p")
end
end
end
local function parse_and_insert_adj_inflection(field, label, accel, frob)
parse_and_insert_inflection(data, args, field, label, accel, frob)
end
parse_and_insert_adj_inflection("n", "中性")
handle_adj_adv_comp(args, data, category_plpos, false)
parse_and_insert_adj_inflection("dim", "指小", nil, function(term)
if term == "+" then
term = com.make_diminutive(lemma)
elseif term:find("^%+") then
term = require(romut_module).get_special_indicator(term)
term = com.make_diminutive(lemma, term)
end
return term
end)
parse_and_insert_adj_inflection("aug", "指大", nil, function(term)
if term == "+" then
term = com.make_augmentative(lemma)
elseif term:find("^%+") then
term = require(romut_module).get_special_indicator(term)
term = com.make_augmentative(lemma, term)
end
return term
end)
if is_metaphonic(args, lemma) then
insert(data.inflections, {label = metaphonic_label})
insert(data.categories, "有元音變音的" .. langname .. category_plpos)
end
if args.irreg and is_superlative then
insert(data.categories, langname .. "不規則最高級" .. category_plpos)
end
end
local function get_adjective_params(adjtype)
local params = {
["inv"] = boolean_param, --invariable
["sp"] = true, -- special indicator: "first", "first-last", etc.
["f"] = list_param, --feminine form(s)
["pl"] = list_param, --plural override(s)
["mpl"] = list_param, --masculine plural override(s)
["fpl"] = list_param, --feminine plural override(s)
["meta"] = boolean_param, -- metaphonic
["nometa"] = boolean_param, -- explicitly not metaphonic
}
if adjtype == "base" then
params["comp"] = list_param --comparative(s)
params["sup"] = list_param --superlative(s)
params["dim"] = list_param --diminutive(s)
params["aug"] = list_param --augmentative(s)
params["fonly"] = boolean_param -- feminine only
params["gneut"] = boolean_param -- gender-neutral adjective e.g. [[latine]] (FIXME: do these exist in Portuguese?)
params["hascomp"] = true -- has comparative
end
if adjtype == "part" then
params["short"] = boolean_param -- short participle
end
if adjtype == "sup" then
params["irreg"] = boolean_param
end
if adjtype == "pron" or adjtype == "contr" then
params["n"] = list_param --neuter form(s)
end
return params
end
pos_functions["形容詞"] = {
params = get_adjective_params("base"),
func = do_adjective,
}
pos_functions["形容詞比較級"] = {
params = get_adjective_params("comp"),
func = do_adjective,
pos_category = "形容詞",
}
pos_functions["形容詞最高級"] = {
params = get_adjective_params("sup"),
func = function(args, data)
do_adjective(args, data, "is superlative")
end,
pos_category = "形容詞",
}
pos_functions["過去分詞"] = {
params = get_adjective_params("part"),
func = do_adjective,
redlink_pos = "分詞",
}
pos_functions["限定詞"] = {
params = get_adjective_params("det"),
func = do_adjective,
}
pos_functions["類形容詞代詞"] = {
params = get_adjective_params("pron"),
func = do_adjective,
pos_category = "代詞",
}
pos_functions["類形容詞縮約形"] = {
params = get_adjective_params("contr"),
func = do_adjective,
pos_category = "縮約形",
}
-----------------------------------------------------------------------------------------
-- Adverbs --
-----------------------------------------------------------------------------------------
local function do_adverb(args, data)
handle_adj_adv_comp(args, data, "adverbs", "is adv")
end
local function get_adverb_params(advtype)
local params = {}
if advtype == "base" then
params["comp"] = list_param --comparative(s)
params["sup"] = list_param --superlative(s)
params["hascomp"] = true -- has comparative
end
return params
end
pos_functions["副詞"] = {
params = get_adverb_params("base"),
func = do_adverb,
}
pos_functions["副詞比較級"] = {
params = get_adverb_params("comp"),
func = do_adverb,
pos_category = "副詞",
}
pos_functions["副詞最高級"] = {
params = get_adverb_params("sup"),
func = do_adverb,
pos_category = "副詞",
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["動詞"] = {
params = {
[1] = true,
["pres"] = list_param, --present
["pres_qual"] = {list = "pres\1_qual", allow_holes = true},
["pres3s"] = list_param, --third-singular present
["pres3s_qual"] = {list = "pres3s\1_qual", allow_holes = true},
["pret"] = list_param, --preterite
["pret_qual"] = {list = "pret\1_qual", allow_holes = true},
["part"] = list_param, --participle
["part_qual"] = {list = "part\1_qual", allow_holes = true},
["short_part"] = list_param, --short participle
["short_part_qual"] = {list = "short_part\1_qual", allow_holes = true},
["noautolinktext"] = boolean_param,
["noautolinkverb"] = boolean_param,
["attn"] = boolean_param,
},
func = function(args, data, frame)
local preses, preses_3s, prets, parts, short_parts
if args.attn then
insert(data.categories, "Requests for attention concerning " .. langname)
return
end
local pt_verb = require(pt_verb_module)
local alternant_multiword_spec = pt_verb.do_generate_forms(args, "pt-verb", data.heads[1])
local specforms = alternant_multiword_spec.forms
local function slot_exists(slot)
return specforms[slot] and #specforms[slot] > 0
end
local function do_finite(slot_tense, label_tense)
-- Use pres_3s if it exists and pres_1s doesn't exist (e.g. impersonal verbs); similarly for pres_3p
-- (only3p verbs); but fall back to pres_1s if neither pres_1s nor pres_3s nor pres_3p exist (e.g.
-- [[empedernir]]).
local has_1s = slot_exists(slot_tense .. "_1s")
local has_3s = slot_exists(slot_tense .. "_3s")
local has_3p = slot_exists(slot_tense .. "_3p")
if has_1s or (not has_3s and not has_3p) then
return {
slot = slot_tense .. "_1s",
label = ("第一人稱單數%s"):format(label_tense),
}, true
elseif has_3s then
return {
slot = slot_tense .. "_3s",
label = ("第三人稱單數%s"):format(label_tense),
}, false
else
return {
slot = slot_tense .. "_3p",
label = ("第三人稱複數%s"):format(label_tense),
}, false
end
end
local did_pres_1s
preses, did_pres_1s = do_finite("pres", "現在時")
preses_3s = {
slot = "pres_3s",
label = "第三人稱單數現在時",
}
prets = do_finite("pret", "過去時")
parts = {
slot = "pp_ms",
label = "過去分詞",
}
short_parts = {
slot = "short_pp_ms",
label = "短過去分詞",
}
if args.pres[1] or args.pres3s[1] or args.pret[1] or args.part[1] or args.short_part[1] then
track("verb-old-multiarg")
end
local function strip_brackets(qualifiers)
if not qualifiers then
return nil
end
local stripped_qualifiers = {}
for _, qualifier in ipairs(qualifiers) do
local stripped_qualifier = qualifier:match("^%[(.*)%]$")
if not stripped_qualifier then
error("Internal error: Qualifier should be surrounded by brackets at this stage: " .. qualifier)
end
insert(stripped_qualifiers, stripped_qualifier)
end
return stripped_qualifiers
end
local function do_verb_form(args, qualifiers, slot_desc, skip_if_empty)
local forms
local to_insert
if #args == 0 then
forms = specforms[slot_desc.slot]
if not forms or #forms == 0 then
if skip_if_empty then
return
end
forms = {{form = "-"}}
end
elseif #args == 1 and args[1] == "-" then
forms = {{form = "-"}}
else
forms = {}
for i, arg in ipairs(args) do
local qual = qualifiers[i]
if qual then
-- FIXME: It's annoying we have to add brackets and strip them out later. The inflection
-- code adds all footnotes with brackets around them; we should change this.
qual = {"[" .. qual .. "]"}
end
local form = arg
if not args.noautolinkverb then
-- [[Module:inflection utilities]] already loaded by [[Module:pt-verb]]
form = require(inflection_utilities_module).add_links(form)
end
insert(forms, {form = form, footnotes = qual})
end
end
if forms[1].form == "-" then
to_insert = {label = "無" .. slot_desc.label}
else
local into_table = {label = slot_desc.label}
for _, form in ipairs(forms) do
local qualifiers = strip_brackets(form.footnotes)
-- Strip redundant brackets surrounding entire form. These may get generated e.g.
-- if we use the angle bracket notation with a single word.
local stripped_form = rmatch(form.form, "^%[%[([^%[%]]*)%]%]$") or form.form
stripped_form = pt_verb.remove_variant_codes(stripped_form)
-- Don't include accelerators if brackets remain in form, as the result will be wrong.
-- FIXME: For now, don't include accelerators. We should use the new {{pt-verb form of}} once
-- implemented.
-- local this_accel = not stripped_form:find("%[%[") and accel or nil
local this_accel = nil
insert(into_table, {term = stripped_form, q = qualifiers, accel = this_accel})
end
to_insert = into_table
end
insert(data.inflections, to_insert)
end
local skip_pres_if_empty
if alternant_multiword_spec.no_pres1_and_sub then
insert(data.inflections, {label = "無第一人稱單數現在時"})
insert(data.inflections, {label = "無現在時虛擬語氣"})
end
if alternant_multiword_spec.no_pres_stressed then
insert(data.inflections, {label = "無重音現在時直陳語氣或虛擬語氣"})
skip_pres_if_empty = true
end
if alternant_multiword_spec.only3s then
insert(data.inflections, {label = glossary_link("無人稱")})
elseif alternant_multiword_spec.only3sp then
insert(data.inflections, {label = "僅第三人稱"})
elseif alternant_multiword_spec.only3p then
insert(data.inflections, {label = "僅第三人稱複數"})
end
local has_vowel_alt
if alternant_multiword_spec.vowel_alt then
for _, vowel_alt in ipairs(alternant_multiword_spec.vowel_alt) do
if vowel_alt ~= "+" and vowel_alt ~= "í" and vowel_alt ~= "ú" then
has_vowel_alt = true
break
end
end
end
do_verb_form(args.pres, args.pres_qual, preses, skip_pres_if_empty)
-- We want to include both the pres_1s and pres_3s if there is a vowel alternation in the present singular. But
-- we don't want to redundantly include the pres_3s if we already included it.
if did_pres_1s and has_vowel_alt then
do_verb_form(args.pres3s, args.pres3s_qual, preses_3s, skip_pres_if_empty)
end
do_verb_form(args.pret, args.pret_qual, prets)
do_verb_form(args.part, args.part_qual, parts)
do_verb_form(args.short_part, args.short_part_qual, short_parts, "skip if empty")
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:pt-verb]] in place of the user-specified or auto-generated head. This was copied from
-- [[Module:it-headword]], where doing this gets accents marked on the verb(s). We don't have accents marked on
-- the verb but by doing this we do get any footnotes on the infinitive propagated here. Don't do this if the
-- user gave multiple heads or gave a head with a multiword-linked verbal expression such as Italian
-- '[[dare esca]] [[al]] [[fuoco]]' (FIXME: give Portuguese equivalent).
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
end
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, "構成" .. typ .. "的後綴")
end
insert(data.inflections, {label = m_table.serialCommaJoin(suffix_type, {conj = "或"}) .. "的非詞元形式"})
end,
}
return export
oeod5gm54y2e3g4z4889dsej1dpdbkx
Module:Es-headword
828
1426264
9909056
9882750
2026-09-29T07:25:13Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909056
Scribunto
text/plain
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local com = require("Module:es-common")
local en_utilities_module = "Module:en-utilities"
local es_verb_module = "Module:es-verb"
local headword_module = "Module:headword"
local headword_utilities_module = "Module:headword utilities"
local inflection_utilities_module = "Module:inflection utilities"
local romut_module = "Module:romance utilities"
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local m_string_utilities = require_when_needed("Module:string utilities")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local lang = require("Module:languages").getByCode("es")
local langname = lang:getCanonicalName()
local insert = table.insert
local remove = table.remove
local rsub = com.rsub
local sort = table.sort
local ulower = mw.ustring.lower
local usub = mw.ustring.sub
local uupper = mw.ustring.upper
local function track(page)
require("Module:debug").track("es-headword/" .. page)
return true
end
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local poscat = frame.args[1]
or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local parargs = frame:getParent().args
local params = {
["head"] = list_param,
["id"] = true,
["splithyph"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {alias_of = "nolink"},
["json"] = boolean_param,
["pagename"] = true, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolink then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
end
end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
checkredlinks = pos_functions[poscat] and pos_functions[poscat].redlink_pos or true,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = true
local singular_poscat = poscat
insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
local pagename_lower = ulower(pagename)
for _, ch in ipairs{"jh", "kh", "ph", "qü", "sh", "th", "tl", "ts", "tz", "wh", "zh", "ze", "zi"} do
if pagename_lower:find(ch) then
insert(data.categories, "帶「" .. uupper(ch) .. "」的" .. langname .. "詞")
end
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require(headword_module).full_headword(data)
end
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture
-- replace expression.
lemma = m_string_utilities.replacement_escape(lemma)
return (term:gsub("#", lemma)) -- discard second retval
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
-- Insert default plurals generated when a given plural had the value of + and default plurals were fetched as a result.
-- `plobj` is the parsed object whose `term` field is "+". `defpls` is the list of default plurals. `dest` is the list
-- into which the plurals are inserted (which inherit their decorations from `plobj`).
local function insert_defpls(defpls, plobj, dest)
if not defpls then
-- Happens e.g. with [[S.A.]] where the default plural algorithm returns nothing.
return
end
if #defpls == 1 then
plobj.term = defpls[1]
insert(dest, plobj)
else
for _, defpl in ipairs(defpls) do
local newplobj = m_table.shallowCopy(plobj)
newplobj.term = defpl
insert(dest, newplobj)
end
end
end
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
local function do_adjective(args, data, is_superlative)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
-- Use "participle" not "past participle" for categories such as 'invariable participles'
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
insert(indicators, "'" .. indic .. "'")
end
sort(indicators)
error("Special inflection indicator beginning can only be " ..
mw.text.listToText(indicators) .. ": " .. args.sp)
end
end
local lemma = data.pagename
local function fetch_inflections(field)
local retval = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
}
if not retval[1] then
return {{term = "+"}}
end
return retval
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if args.inv then
-- invariable adjective
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
if args.sp or args.f[1] or args.pl[1] or args.mpl[1] or args.fpl[1] then
error("Can't specify inflections with an invariable " .. category_pos)
end
elseif args.fonly then
-- feminine-only
if args.f[1] then
error("Can't specify explicit feminines with feminine-only " .. category_pos)
end
if args.pl[1] then
error("Can't specify explicit plurals with feminine-only " .. category_pos .. ", use fpl=")
end
if args.mpl[1] then
error("Can't specify explicit masculine plurals with feminine-only " .. category_pos)
end
local argsfpl = fetch_inflections("fpl")
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
-- Generate default feminine plural.
local defpls = com.make_plural(lemma, "f", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
insert_defpls(defpls, fpl, feminine_plurals)
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
insert(feminine_plurals, fpl)
end
end
insert(data.inflections, {label = "僅陰性"})
insert_inflection(feminine_plurals, "陰性複數", "f|p")
else
-- Gather feminines.
for _, f in ipairs(fetch_inflections("f")) do
if f.term == "+" then
-- Generate default feminine.
f.term = com.make_feminine(lemma, args.sp)
else
f.term = replace_hash_with_lemma(f.term, lemma)
end
insert(feminines, f)
end
local fem_like_lemma = #feminines == 1 and feminines[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(feminines[1])
if fem_like_lemma then
insert(data.categories, langname .. "兩性" .. category_plpos)
end
local mpl_field = "mpl"
local fpl_field = "fpl"
if args.pl[1] then
if args.mpl[1] or args.fpl[1] then
error("Can't specify both pl= and mpl=/fpl=")
end
mpl_field = "pl"
fpl_field = "pl"
end
local argsmpl = fetch_inflections(mpl_field)
local argsfpl = fetch_inflections(fpl_field)
for _, mpl in ipairs(argsmpl) do
if mpl.term == "+" then
-- Generate default masculine plural.
local defpls = com.make_plural(lemma, "m", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
insert_defpls(defpls, mpl, masculine_plurals)
else
mpl.term = replace_hash_with_lemma(mpl.term, lemma)
insert(masculine_plurals, mpl)
end
end
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local defpls = com.make_plural(f.term, "f", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. f.term .. "'")
end
for _, defpl in ipairs(defpls) do
local fplobj = m_table.shallowCopy(fpl)
fplobj.term = defpl
m_headword_utilities.combine_termobj_decorations(fplobj, f)
insert(feminine_plurals, fplobj)
end
end
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
insert(feminine_plurals, fpl)
end
end
parse_and_insert_inflection(data, args, "mapoc", "在名詞前為陽性單數")
local fem_pl_like_masc_pl = masculine_plurals[1] and feminine_plurals[1] and
m_table.deepEquals(masculine_plurals, feminine_plurals)
local masc_pl_like_lemma = #masculine_plurals == 1 and masculine_plurals[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(masculine_plurals[1])
if fem_like_lemma and fem_pl_like_masc_pl and masc_pl_like_lemma then
-- actually invariable
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
-- Make sure there are feminines given and not same as lemma.
if not fem_like_lemma then
insert_inflection(feminines, "陰性", "f|s")
elseif args.gneut then
data.genders = {"gneut"}
else
data.genders = {"mf"}
end
if fem_pl_like_masc_pl then
if args.gneut then
insert_inflection(masculine_plurals, "複數", "p")
else
insert_inflection(masculine_plurals, "陽性和陰性複數", "p")
end
else
insert_inflection(masculine_plurals, "陽性複數", "m|p")
insert_inflection(feminine_plurals, "陰性複數", "f|p")
end
end
end
parse_and_insert_inflection(data, args, "comp", "比較級")
parse_and_insert_inflection(data, args, "sup", "最高級")
parse_and_insert_inflection(data, args, "dim", "指小")
parse_and_insert_inflection(data, args, "aug", "指大")
if args.irreg and is_superlative then
insert(data.categories, langname .. "不規則最高級" .. category_plpos)
end
end
local function get_adjective_params(adjtype)
local params = {
["inv"] = boolean_param, --invariable
["sp"] = true, -- special indicator: "first", "first-last", etc.
}
local function ins_infl(field)
params[field] = list_param --feminine form(s)
end
ins_infl("f") -- feminine form(s)
ins_infl("pl") -- plural override(s)
ins_infl("mpl") -- masculine plural override(s)
ins_infl("fpl") -- feminine plural override(s)
if adjtype == "base" then
ins_infl("mapoc") --masculine apocopated (before a noun)
ins_infl("comp") --comparative(s)
ins_infl("sup") --superlative(s)
ins_infl("dim") --diminutive(s)
ins_infl("aug") --augmentative(s)
params["fonly"] = boolean_param -- feminine only
params["gneut"] = boolean_param -- gender-neutral adjective e.g. [[latine]]
params["hascomp"] = true -- has comparative
end
if adjtype == "sup" then
params["irreg"] = boolean_param
end
return params
end
pos_functions["形容詞"] = {
params = get_adjective_params("base"),
func = do_adjective,
}
pos_functions["過去分詞"] = {
params = get_adjective_params("part"),
func = do_adjective,
redlink_pos = "分詞",
}
pos_functions["限定詞"] = {
params = get_adjective_params("det"),
func = do_adjective,
}
pos_functions["代詞"] = {
params = get_adjective_params("pron"),
func = do_adjective,
}
pos_functions["形容詞比較級"] = {
params = get_adjective_params("comp"),
func = do_adjective,
pos_category = "形容詞",
}
pos_functions["形容詞最高級"] = {
params = get_adjective_params("sup"),
func = function(args, data)
do_adjective(args, data, true)
end,
pos_category = "形容詞",
}
-----------------------------------------------------------------------------------------
-- Adverbs --
-----------------------------------------------------------------------------------------
pos_functions["副詞"] = {
params = {
["sup"] = list_param, --superlative(s)
},
func = function(args, data)
parse_and_insert_inflection(data, args, "sup", "最高級")
end,
}
-----------------------------------------------------------------------------------------
-- Numerals --
-----------------------------------------------------------------------------------------
pos_functions["基數詞"] = {
params = {
["f"] = list_param, --feminine(s)
["mapoc"] = list_param, --masculine apocopated form(s)
},
func = function(args, data)
insert(data.categories, 1, langname .. "基數詞")
if args.f[1] then
insert(data.genders, "m")
parse_and_insert_inflection(data, args, "f", "陰性")
end
parse_and_insert_inflection(data, args, "mapoc", "在名詞前為陽性")
end,
pos_category = "數詞",
}
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet(
{"m", "f", "mf", "mfbysense", "mfequiv", "gneut", "n", "m-p", "f-p", "mf-p", "mfbysense-p", "mfequiv-p", "gneut-p", "n-p", "?", "?-p"}
)
local function validate_genders(genders)
for _, g in ipairs(genders) do
if type(g) == "table" then
g = g.spec
end
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
end
end
-- Display additional inflection information for a noun
local function do_noun(args, data, is_proper)
local is_plurale_tantum = false
local has_singular = false
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
validate_genders(args[1])
data.genders = args[1]
local saw_m = false
local saw_f = false
local saw_gneut = false
local gender_for_irreg_ending, gender_for_make_plural
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if type(g) == "table" then
g = g.spec
end
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
if g == "gneut" then
saw_gneut = true
end
end
end
if saw_m and saw_f then
gender_for_irreg_ending = "mf"
elseif saw_f then
gender_for_irreg_ending = "f"
else
gender_for_irreg_ending = "m"
end
gender_for_make_plural = saw_gneut and "gneut" or gender_for_irreg_ending
local lemma = data.pagename
local plurals = {}
if is_plurale_tantum and not has_singular then
if args[2][1] then
error("Can't specify plurals of plurale tantum " .. category_pos)
end
insert(data.inflections, {label = glossary_link("唯複")})
else
plurals = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "pl"},
forms = args[2],
splitchar = ",",
}
-- Check for special plural signals
local mode = nil
local pl1 = plurals[1]
if pl1 and #pl1.term == 1 then
mode = pl1.term
if mode == "?" or mode == "!" or mode == "-" or mode == "~" then
pl1.term = nil
if next(pl1) then
error(("Can't specify inline modifiers with plural code '%s'"):format(mode))
end
remove(plurals, 1) -- Remove the mode parameter
elseif mode ~= "+" and mode ~= "#" then
error(("Unexpected plural code '%s'"):format(mode))
end
end
if mode == "?" then
-- Plural is unknown
insert(data.categories, "有未知或不確定複數的" .. langname .. category_plpos)
elseif mode == "!" then
-- Plural is not attested
insert(data.inflections, {label = "複數無紀錄"})
insert(data.categories, "有無紀錄複數的" .. langname .. category_plpos)
if plurals[1] then
error("Can't specify any plurals along with unattested plural code '!'")
end
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
insert(data.categories, langname .. "不可數" .. category_plpos)
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
insert(data.categories, langname .. "可數" .. category_plpos)
else
insert(data.inflections, {label = glossary_link("不可數")})
end
else
-- Countable or mixed countable/uncountable
if not plurals[1] and not is_proper then
plurals[1] = {term = "+"}
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
insert(data.inflections, {label = glossary_link("可數") .. "和" .. glossary_link("不可數")})
insert(data.categories, langname .. "不可數" .. category_plpos)
insert(data.categories, langname .. "可數" .. category_plpos)
elseif plurals[1] then
-- Countable nouns
insert(data.categories, langname .. "可數" .. category_plpos)
else
-- Uncountable nouns
insert(data.categories, langname .. "不可數" .. category_plpos)
end
end
-- Gather plurals, handling requests for default plurals.
local has_default_or_hash = false
for _, pl in ipairs(plurals) do
if pl.term:find("^%+") or pl.term:find("#") then
has_default_or_hash = true
break
end
end
if has_default_or_hash then
local newpls = {}
for _, pl in ipairs(plurals) do
if pl.term == "+" then
local default_pls = com.make_plural(lemma, gender_for_make_plural)
insert_defpls(default_pls, pl, newpls)
elseif pl.term:find("^%+") then
pl.term = require(romut_module).get_special_indicator(pl.term)
local default_pls = com.make_plural(lemma, gender_for_make_plural, pl.term)
insert_defpls(default_pls, pl, newpls)
else
pl.term = replace_hash_with_lemma(pl.term, lemma)
insert(newpls, pl)
end
end
plurals = newpls
end
end
if #plurals > 1 then
insert(data.categories, "有多個複數的" .. langname .. category_plpos)
end
-- Gather masculines/feminines. For each one, generate the corresponding plural(s). `field` is the name of the
-- field containing the masculine or feminine forms (normally "m" or "f"); `gender` is "m" or "f" for the gender
-- of the forms; `inflect` is a function of one or two arguments to generate the default masculine or feminine from
-- the lemma (the arguments are the lemma and optionally a "special" flag to indicate how to handle multiword
-- lemmas, and the function is normally make_feminine or make_masculine from [[Module:es-common]]); and
-- `default_plurals` is a list into which the corresponding default plurals of the gathered or generated masculine
-- or feminine forms are stored. Note that there may be more default plurals than masculines or feminines, because
-- some terms have multiple possible plurals.
local function handle_mf(field, gender, inflect, default_plurals)
local mfs = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
frob = function(term)
if term == "+" then
-- Generate default masculine/feminine.
term = inflect(lemma)
else
term = replace_hash_with_lemma(term, lemma)
end
local special = require(romut_module).get_special_indicator(term)
if special then
term = inflect(lemma, special)
end
return term
end
}
for _, mf in ipairs(mfs) do
local mfpls = com.make_plural(mf.term, gender, special)
if mfpls then
for _, mfpl in ipairs(mfpls) do
local plobj = m_table.shallowCopy(mf)
plobj.term = mfpl
-- Add an accelerator for each masculine/feminine plural whose lemma
-- is the corresponding singular, so that the accelerated entry
-- that is generated has a definition that looks like
-- # {{plural of|es|MFSING}}
plobj.accel = {form = "p", lemma = mf.term}
insert(default_plurals, plobj)
end
end
end
return mfs
end
local feminine_plurals = {}
local feminines = handle_mf("f", "f", com.make_feminine, feminine_plurals)
local masculine_plurals = {}
local masculines = handle_mf("m", "m", com.make_masculine, masculine_plurals)
local function handle_mf_plural(mfplfield, gender, default_plurals, singulars)
local mfpl = m_headword_utilities.parse_term_list_with_modifiers {
paramname = mfplfield,
forms = args[mfplfield],
splitchar = ",",
}
local new_mfpls = {}
local saw_plus
for i, mfpl in ipairs(mfpl) do
local accel
if #mfpl == #singulars then
-- If same number of overriding masculine/feminine plurals as singulars,
-- assume each plural goes with the corresponding singular
-- and use each corresponding singular as the lemma in the accelerator.
-- The generated entry will have # {{plural of|es|SINGULAR}} as the
-- definition.
accel = {form = "p", lemma = singulars[i].term}
else
accel = nil
end
if mfpl.term == "+" then
-- We should never see + twice. If we do, it will lead to problems since we overwrite the values of
-- default_plurals the first time around.
if saw_plus then
error(("Saw + twice when handling %s="):format(mfplfield))
end
saw_plus = true
for _, defpl in ipairs(default_plurals) do
-- defpl is already a table and has an accel field
m_headword_utilities.combine_termobj_decorations(defpl, mfpl)
insert(new_mfpls, defpl)
end
elseif mfpl.term:find("^%+") then
mfpl.term = require(romut_module).get_special_indicator(mfpl.term)
for _, mf in ipairs(singulars) do
local default_mfpls = com.make_plural(mf.term, gender, mfpl.term)
for _, defp in ipairs(default_mfpls) do
local mfplobj = m_table.shallowCopy(mfpl)
mfplobj.term = defp
mfplobj.accel = accel
m_headword_utilities.combine_termobj_decorations(mfplobj, mf)
insert(new_mfpls, mfplobj)
end
end
else
mfpl.accel = accel
mfpl.term = replace_hash_with_lemma(mfpl.term, lemma)
insert(new_mfpls, mfpl)
end
end
return new_mfpls
end
if args.fpl[1] then
-- Override any existing feminine plurals.
feminine_plurals = handle_mf_plural("fpl", "f", feminine_plurals, feminines)
end
if args.mpl[1] then
-- Override any existing masculine plurals.
masculine_plurals = handle_mf_plural("mpl", "m", masculine_plurals, masculines)
end
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
local function insert_noun_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
insert_noun_inflection(plurals, "複數", "p")
insert_noun_inflection(feminines, "陰性", "f")
insert_noun_inflection(feminine_plurals, "陰性複數")
insert_noun_inflection(masculines, "陽性")
insert_noun_inflection(masculine_plurals, "陽性複數")
parse_and_insert_noun_inflection("dim", "指小")
parse_and_insert_noun_inflection("aug", "指大")
parse_and_insert_noun_inflection("pej", "貶義")
parse_and_insert_noun_inflection("dem", "居民稱謂詞")
parse_and_insert_noun_inflection("fdem", "女性居民稱謂詞")
-- Maybe add category 'Spanish nouns with irregular gender' (or similar)
local irreg_gender_lemma = rsub(lemma, " .*", "") -- only look at first word
if (rfind(irreg_gender_lemma, "o$") and (gender_for_irreg_ending == "f" or gender_for_irreg_ending == "mf")) or
(irreg_gender_lemma:find("a$") and (gender_for_irreg_ending == "m" or gender_for_irreg_ending == "mf")) then
insert(data.categories, "有不規則性別的" .. langname .. "名詞")
end
end
local function get_noun_params(is_proper)
return {
[1] = {list = "g", disallow_holes = true, required = not is_proper, default = "?", type = "genders",
flatten = true}, -- gender(s)
[2] = {list = "pl", disallow_holes = true}, --plural override(s)
["f"] = list_param, --feminine form(s)
["m"] = list_param, --masculine form(s)
["fpl"] = list_param, --feminine plural override(s)
["mpl"] = list_param, --masculine plural override(s)
["dim"] = list_param, --diminutive(s)
["aug"] = list_param, --diminutive(s)
["pej"] = list_param, --pejorative(s)
["dem"] = list_param, --demonym(s)
["fdem"] = list_param, --female demonym(s)
}
end
pos_functions["名詞"] = {
params = get_noun_params(),
func = do_noun,
}
pos_functions["專有名詞"] = {
params = get_noun_params("is proper"),
func = function(args, data)
do_noun(args, data, "is proper")
end,
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["動詞"] = {
params = {
[1] = {},
["pres"] = list_param, --present
["pres_qual"] = {list = "pres\1_qual", allow_holes = true},
["pret"] = list_param, --preterite
["pret_qual"] = {list = "pret\1_qual", allow_holes = true},
["part"] = list_param, --participle
["part_qual"] = {list = "part\1_qual", allow_holes = true},
["pagename"] = {}, -- for testing
["noautolinktext"] = boolean_param,
["noautolinkverb"] = boolean_param,
["attn"] = boolean_param,
},
func = function(args, data)
local preses, prets, parts
if args.attn then
insert(data.categories, "Requests for attention concerning " .. langname)
return
end
local es_verb = require(es_verb_module)
local alternant_multiword_spec = es_verb.do_generate_forms(args, "es-verb", data.heads[1])
local specforms = alternant_multiword_spec.forms
local function slot_exists(slot)
return specforms[slot] and specforms[slot][1]
end
local function do_finite(slot_tense, label_tense)
-- Use pres_3s if it exists and pres_1s doesn't exist (e.g. impersonal verbs); similarly for pres_3p (only3p verbs);
-- but fall back to pres_1s if neither pres_1s nor pres_3s nor pres_3p exist (e.g. [[empedernir]]).
local has_1s = slot_exists(slot_tense .. "_1s")
local has_3s = slot_exists(slot_tense .. "_3s")
local has_3p = slot_exists(slot_tense .. "_3p")
if has_1s or (not has_3s and not has_3p) then
return {
slot = slot_tense .. "_1s",
label = ("第一人稱單數%s"):format(label_tense),
}
elseif has_3s then
return {
slot = slot_tense .. "_3s",
label = ("第三人稱單數%s"):format(label_tense),
}
else
return {
slot = slot_tense .. "_3p",
label = ("第三人稱複數%s"):format(label_tense),
}
end
end
preses = do_finite("pres", "現在時")
prets = do_finite("pret", "過去時")
parts = {
slot = "pp_ms",
label = "過去分詞",
}
if args.pres[1] or args.pret[1] or args.part[1] then
track("verb-old-multiarg")
end
local function strip_brackets(qualifiers)
if not qualifiers then
return nil
end
local stripped_qualifiers = {}
for _, qualifier in ipairs(qualifiers) do
local stripped_qualifier = qualifier:match("^%[(.*)%]$")
if not stripped_qualifier then
error("Internal error: Qualifier should be surrounded by brackets at this stage: " .. qualifier)
end
insert(stripped_qualifiers, stripped_qualifier)
end
return stripped_qualifiers
end
local function do_verb_form(args, qualifiers, slot_desc, skip_if_empty)
local forms
local to_insert
if #args == 0 then
forms = specforms[slot_desc.slot]
if not forms or #forms == 0 then
if skip_if_empty then
return
end
forms = {{form = "-"}}
end
elseif #args == 1 and args[1] == "-" then
forms = {{form = "-"}}
else
forms = {}
for i, arg in ipairs(args) do
local qual = qualifiers[i]
if qual then
-- FIXME: It's annoying we have to add brackets and strip them out later. The inflection
-- code adds all footnotes with brackets around them; we should change this.
qual = {"[" .. qual .. "]"}
end
local form = arg
if not args.noautolinkverb then
-- [[Module:inflection utilities]] already loaded by [[Module:es-verb]]
form = require(inflection_utilities_module).add_links(form)
end
insert(forms, {form = form, footnotes = qual})
end
end
if forms[1].form == "-" then
to_insert = {label = "無" .. slot_desc.label}
else
local into_table = {label = slot_desc.label}
for _, form in ipairs(forms) do
local qualifiers = strip_brackets(form.footnotes)
-- Strip redundant brackets surrounding entire form. These may get generated e.g.
-- if we use the angle bracket notation with a single word.
local stripped_form = rmatch(form.form, "^%[%[([^%[%]]*)%]%]$") or form.form
-- Don't include accelerators if brackets remain in form, as the result will be wrong.
-- FIXME: For now, don't include accelerators. We should use {{es-verb form of}} instead.
-- local this_accel = not stripped_form:find("%[%[") and accel or nil
local this_accel = nil
insert(into_table, {term = stripped_form, q = qualifiers, accel = this_accel})
end
to_insert = into_table
end
insert(data.inflections, to_insert)
end
local skip_pres_if_empty
if alternant_multiword_spec.no_pres1_and_sub then
insert(data.inflections, {label = "無第一人稱單數現在時"})
insert(data.inflections, {label = "無現在時虛擬語氣"})
end
if alternant_multiword_spec.no_pres_stressed then
insert(data.inflections, {label = "無重讀現在時或虛擬語氣"})
skip_pres_if_empty = true
end
if alternant_multiword_spec.only3s then
insert(data.inflections, {label = glossary_link("無人稱")})
elseif alternant_multiword_spec.only3sp then
insert(data.inflections, {label = "僅第三人稱"})
elseif alternant_multiword_spec.only3p then
insert(data.inflections, {label = "僅第三人稱複數"})
end
do_verb_form(args.pres, args.pres_qual, preses, skip_pres_if_empty)
do_verb_form(args.pret, args.pret_qual, prets)
do_verb_form(args.part, args.part_qual, parts)
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:es-verb]] in place of the user-specified or auto-generated head. This was copied from
-- [[Module:it-headword]], where doing this gets accents marked on the verb(s). We don't have accents marked on
-- the verb but by doing this we do get any footnotes on the infinitive propagated here. Don't do this if the
-- user gave multiple heads or gave a head with a multiword-linked verbal expression such as Italian
-- '[[dare esca]] [[al]] [[fuoco]]' (FIXME: give Spanish equivalent).
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
end
}
-----------------------------------------------------------------------------------------
-- Phrases --
-----------------------------------------------------------------------------------------
pos_functions["短語"] = {
params = {
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
["m"] = list_param,
["f"] = list_param,
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
parse_and_insert_inflection(data, args, "m", "陽性")
parse_and_insert_inflection(data, args, "f", "陰性")
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, "構成" .. typ .. "的後綴")
end
insert(data.inflections, {label = require("Module:table").serialCommaJoin(suffix_type, {conj = "或"}) .. "的非詞元形式"})
end,
}
return export
2fx9phdet3nwtwnl7rq8xzp8789yis6
Module:Eu-pron
828
1426813
9909060
8436928
2026-09-29T07:25:29Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909060
Scribunto
text/plain
-- Based on [[Module:es-pronunc]] by: Benwing
-- Adapted by Santi2222
local export = {}
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local lang = require("Module:languages").getByCode("eu")
local u = m_str_utils.char
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local toNFC = mw.ustring.toNFC
local toNFD = mw.ustring.toNFD
local ulower = m_str_utils.lower
local ulen = m_str_utils.len
local TILDE = u(0x0303) -- tilde = ̃
local vowel = "aeiou" -- vowel;
local V = "[" .. vowel .. "]"
local W = "[jw]" -- glide
local separator = "# ."
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- style == one of the following:
-- "northern_style": lack of palatalization, /h/, <j> = /j/
-- "southern_style": palatalization, lack of /h/, <j> = /x/
function export.IPA(text, style, phonetic)
local northern = style == "northern_style"
local n_diff = false
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ñ
text = toNFD(text)
text = rsub(text, ".[" .. TILDE .. "]", {
["n" .. TILDE] = "ñ",
})
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[!?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--cases in which <j> is always /x/ (requires respelling)
text = rsubb(text, "kh", "X")
--determining whether "h" denotes a phoneme, or the lack of palatalization, palatalization when needed
text = rsub(text, "nh", "N")
text = rsub(text, "lh", "L")
text = rsub(text, "([aeo])il([# .h]*[aeiou])", (northern and "%1ił%2" or "%1ĺ%2"))
text = rsub(text, "([aeo])in([# .h]*[aeiou])", (northern and "%1iň%2" or "%1ń%2"))
text = rsub(text, "uin([# .h]*[aeiou])", (northern and "uiň%1" or "uiń%1"))
text = rsub(text, "uil([# .h]*[aeiou])", (northern and "uił%1" or "uiĺ%1"))
text = rsub(text, "il([# .h]*[aeiou])", (northern and "ił%1" or "iĺ%1"))
text = rsub(text, "in([# .h]*[aeiou])", (northern and "iň%1" or "iń%1"))
if text:find("[łňńĺh]") then
n_diff = true
end
text = rsub(text, "ĺ", "ʎ")
text = rsub(text, "ń", "ɲ")
text = rsub(text, "N", "n")
text = rsub(text, "L", "l")
text = rsub(text, "ň", "n")
text = rsub(text, "ł", "l")
--c, g, q
text = rsub(text, "ng([^aeiouüwhlr])", "n%1") -- [[Bangkok]], [[angstrom]], etc.
text = rsub(text, "q", "k") -- [[quark]], [[Qatar]], [[burqa]], [[Iraq]], etc.
text = rsub(text, "c", "k") -- [[campus]], etc.
--alphabet-to-phoneme
text = rsub(text, "tx", "C") --not the real sound
text = rsub(text, "tz", "Ś") --not the real sound
text = rsub(text, "ts", "S") --not the real sound
text = rsubb(text, "ll", "ʎ")
text = rsubb(text, "dd", "ɟ")
text = rsubb(text, "tt", "c")
text = rsub(text, "x", "ʃ")
text = rsub(text, "#p([st])", "#%1") -- [[psicologia]], [[pterodaktilo]]
text = rsub(text, "[gñrvz]",
{ ["g"] = "ɡ", ["ñ"] = "ɲ", ["v"] = "b", ["z"] = "ś" })
--/x/ sound
text = rsubb(text, "j", (northern and "J" or "x"))
if text:find("[Jx]") then
n_diff = true
end
text = rsub(text, "J", "j")
--cases in which <j> is always /j/ (requires respelling)
text = rsubb(text, "y", "j")
-- trilled r (in all cases except between vowels)
text = rsub(text, "([aeiou])r([aeiou])", "%1ɾ%2")
text = rsub(text, "([aeiou])r([aeiou])", "%1ɾ%2") -- it has to be applied twice in order to handle VrVrV sequences
text = rsub(text, "rr", "r")
text = rsub(text, "n([# .]*[bpm])", "m%1")
-- lack of /h/ in Southern dialects
text = rsub(text, "aha", (northern and "aha" or "a"))
text = rsub(text, "ehe", (northern and "ehe" or "e"))
text = rsub(text, "ihi", (northern and "ihi" or "i"))
text = rsub(text, "oho", (northern and "oho" or "o"))
text = rsub(text, "uhu", (northern and "uhu" or "u"))
text = rsub(text, "h", (northern and "h" or ""))
----------------------
--syllable division
-- i and u between vowels -> i. and u.
text = rsub_repeatedly(text, "ui", "U") --this dipthong requires special treatment
local vowel_to_glide = { ["i"] = "i.", ["u"] = "u." }
text = rsub_repeatedly(text, "(" .. V .. "*)([iu])(" .. V .. ")",
function(v1, iu, v2)
return v1 .. vowel_to_glide[iu] .. v2
end
)
text = rsub_repeatedly(text, "U", "ui")
text = rsub_repeatedly(text, "(" .. V .. ")(" .. C .. W .. "?" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. C .. ")(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. C .. C .. ")(" .. C .. C .. V .. ")", "%1.%2")
text = rsub(text, "([pbktdɡ])%.([lrɾ])", ".%1%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
text = rsub_repeatedly(text, "([ae]" .. ")ui", "%1u.i")
text = rsub_repeatedly(text, "([aeiou]" .. ")([aeo])", "%1.%2")
text = rsub_repeatedly(text, "ii", "i.i")
text = rsub_repeatedly(text, "([iou]" .. ")u", "%1.u")
local words = rsplit(text, " ")
for j, word in ipairs(words) do
local syllables = rsplit(word, "%.")
-- Vowels are nasalized if followed by nasal in same syllable.
if phonetic then
for i = 1, #syllables do
-- first check for two vowels (veinte)
syllables[i] = rsub(syllables[i], "(" .. V .. ")(" .. V .. ")([mnɲ])",
"%1" .. TILDE .. "%2" .. TILDE .. "%3")
-- then for one vowel
syllables[i] = rsub(syllables[i], "(" .. V .. ")([mnɲ])", "%1" .. TILDE .. "%2")
end
end
-- Reconstruct the word.
words[j] = table.concat(syllables, phonetic and "." or "")
end
text = table.concat(words, " ")
--phonetic transcription
if phonetic then
-- s, z, f before voiced consonants
local voiced = "mnɲbdɟɡʎl"
local r = "ɾr"
local tovoiced = {
["s"] = "z̺",
["ś"] = "z̻",
}
local function voice(sound, following)
return tovoiced[sound] .. following
end
text = rsub(text, "([sś])(" .. separator_c .. "*[" .. voiced .. r .. "])", voice)
-- fricative vs. stop allophones; first convert stops to fricatives, then back to stops
-- after nasals and sometimes after l
local stop_to_fricative = { ["b"] = "β", ["d"] = "ð", ["ɡ"] = "ɣ" }
local fricative_to_stop = { ["β"] = "b", ["ð"] = "d", ["ɣ"] = "ɡ" }
text = rsub(text, "[bdɡ]", stop_to_fricative)
text = rsub(text, "([mnɲ]" .. separator_c .. "*)([βɣ])",
function(nasal, fricative)
return nasal .. fricative_to_stop[fricative]
end
)
text = rsub(text, "([lʎmnɲ]" .. separator_c .. "*)([ð])",
function(nasal_l, fricative)
return nasal_l .. fricative_to_stop[fricative]
end
)
text = rsub(text, "##β", "##b")
text = rsub(text, "##ð", "##d")
text = rsub(text, "##ɣ", "##ɡ")
text = rsub(text, "[td]", { ["t"] = "t̪", ["d"] = "d̪" })
-- nasal assimilation before consonants
local labiodental, dentialveolar, alveolopalatal, palatal, velar = "ɱ", "n̪", "nʲ", "ɲ", "ŋ"
local nasal_assimilation = {
["f"] = labiodental,
["t"] = dentialveolar, ["d"] = dentialveolar,
["C"] = alveolopalatal,
["ʃ"] = alveolopalatal,
["ɟ"] = palatal, ["c"] = palatal, ["ʎ"] = palatal, ["j"] = palatal,
["k"] = velar, ["x"] = velar, ["ɡ"] = velar,
}
text = rsub(text, "n(" .. separator_c .. "*)(.)",
function(stress, following)
return (nasal_assimilation[following] or "n") .. stress .. following
end
)
-- lateral assimilation before consonants
text = rsub(text, "l(" .. separator_c .. "*)(.)",
function(stress, following)
local l = "l"
if following == "t" or following == "d" then
-- dentialveolar
l = "l̪"
elseif following == "C" or following == "ʃ" then
-- alveolopalatal
l = "lʲ"
elseif following == "ɟ" or following == "c" or following == "j" then
-- alveolopalatal
l = "ʎ"
end
return l .. stress .. following
end)
-- voiced fricatives are actually approximants
text = rsub(text, "([βðɣ])", "%1̞")
-- northern /h/ is usually [ɦ]
text = rsub(text, "h", "ɦ")
end
--semivowels
text = rsub(text, "([aeou])" .. TILDE .. "([i])", "%1" .. TILDE .. "%2̯")
text = rsub(text, "([ae])" .. TILDE .. "([u])", "%1" .. TILDE .. "%2̯")
text = rsub(text, "([aeou][i])", "%1̯")
text = rsub(text, "([ae][u])", "%1̯")
text = rsub(text, "u̯i̯", "u̯i") --correct the mistake appeareing in words with aui, eui
if phonetic then
-- /e/ and /o/ are actually lowered
text = rsub(text, "[eo]", { ["e"] = "e̞", ["o"] = "o̞" })
end
-- convert fake symbols to real ones
local final_conversions = {
["S"] = "t͡s̺",
["Ś"] = "t͡s̻",
["s"] = "s̺",
["ś"] = "s̻",
["C"] = "t͡ʃ",
["X"] = "x"
}
text = rsub(text, "[SŚsśCX]", final_conversions)
-- remove # symbols at word and text boundaries
text = rsub(text, "#", "")
text = toNFC(text)
local ret = {
text = text,
northern_different = n_diff,
}
return ret
end
-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function export.show(frame)
local params = {
[1] = {},
["pre"] = {},
["post"] = {},
["ref"] = {},
["style"] = {},
["bullets"] = { type = "number", default = 1 },
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local phonemic = {}
local phonetic = {}
local expressed_styles = {}
local text = args[1] or mw.title.getCurrentTitle().text
local function dostyle(style)
phonemic[style] = export.IPA(text, style, false)
phonetic[style] = export.IPA(text, style, true)
end
local function express_style(hidden_tag, tag, style)
if not phonemic[style] then
dostyle(style)
end
local new_style = {
tag = tag,
phonemic = phonemic[style],
phonetic = phonetic[style],
}
for _, hidden_tag_style in ipairs(expressed_styles) do
if hidden_tag_style.tag == hidden_tag then
table.insert(hidden_tag_style.styles, new_style)
return
end
end
table.insert(expressed_styles, {
tag = hidden_tag,
styles = { new_style },
})
end
dostyle("northern_style")
local northern_different = phonemic["northern_style"].northern_different
if not northern_different then
express_style(false, false, "southern_style")
else
express_style("Southern", "Southern", "southern_style")
express_style("Northern", "Northern", "northern_style")
end
-- If only one style group, don't indicate the style.
if #expressed_styles == 1 then
expressed_styles[1].tag = false
if #expressed_styles[1].styles == 1 then
expressed_styles[1].styles[1].tag = false
end
end
local lines = {}
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
table.insert(pronunciations, {
pron = "/" .. expressed_style.phonemic.text .. "/",
q = tag and { tag } or nil,
})
table.insert(pronunciations, {
pron = "[" .. expressed_style.phonetic.text .. "]",
})
local bullet = string.rep("*", args.bullets) .. " "
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and (args.ref or "") .. (args.post and " " .. args.post or "") or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. (tag and "(" .. tag .. ") " or "") ..
"/" .. expressed_style.phonemic.text .. "/, [" .. expressed_style.phonetic.text .. "]" .. post
return formatted, formatted_for_len
end
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
style_group.formatted, style_group.formatted_for_len = format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
else
style_group.formatted, style_group.formatted_for_len = format_style(style_group.tag, style_group.styles[1], i == 1)
for j, style in ipairs(style_group.styles) do
style.formatted, style.formatted_for_len = format_style(style.tag, style, i == 1 and j == 1)
end
end
end
local maxlen = 0
for _, style_group in ipairs(expressed_styles) do
local this_len = ulen(style_group.formatted_for_len)
if #style_group.styles > 1 then
for _, style in ipairs(style_group.styles) do
this_len = math.max(this_len, ulen(style.formatted_for_len))
end
end
maxlen = math.max(maxlen, this_len)
end
for _, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
table.insert(lines, style_group.formatted)
else
local inline = '\n<div class="vsShow" style="display:none">\n' .. style_group.formatted .. "</div>"
local full_prons = {}
for _, style in ipairs(style_group.styles) do
table.insert(full_prons, style.formatted)
end
local full = '\n<div class="vsHide">\n' .. table.concat(full_prons, "\n") .. "</div>"
local em_length = math.floor(maxlen * 0.68) -- from [[Module:grc-pronunciation]]
table.insert(lines, '<div class="vsSwitcher" data-toggle-category="pronunciations" style="width: ' .. em_length .. 'em; max-width:100%;"><span class="vsToggleElement" style="float: right;"> </span>' .. inline .. full .. "</div>")
end
end
return table.concat(lines, "\n")
end
return export
q6ap3dilyn9cfob0ruf0zck79q845cu
glon
0
1428328
9909134
8665420
2026-09-29T08:39:24Z
TongcyBot
83009
Bot: 從英語維基詞典更新參數
9909134
wikitext
text/x-wiki
==波蘭語==
{{wikipedia|lang=pl|glony}}
[[Image:Marine Algae on the Adriatic Coast (1).jpg|thumb|250px|glony]]
===詞源===
源自{{inh|pl|zlw-opl|glan}} ← {{inh|pl|sla-pro|*glěnь}} ← {{inh|pl|ine-pro||*gloh₁i-n-i-s}} ← {{der|pl|ine-pro|*gley-|*gleh₁i-}}。
===發音===
* {{pl-pr|a=Pl-glon.ogg|kuj=#|zyw=#|las=#|low=#}}
* {{audio|pl|Pl-glon.ogg|音頻}}
===名詞===
{{pl-noun|m-in|adj=glonowy}}
# {{生}} [[藻]]
====變格====
{{pl-decl-noun-m-in}}
====派生詞====
* {{l|pl|glonowy}}
===拓展閱讀===
* {{R:pl:WSJP}}
* {{R:PWN}}
4eur0b5noln6ml85j3iw4mggclnp11g
Module:Ary-IPA
828
1430384
9909045
8797895
2026-09-29T07:24:31Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909045
Scribunto
text/plain
local export = {}
local lang = require("Module:languages").getByCode("ary")
local m_IPA = require("Module:IPA")
local gsub = mw.ustring.gsub
local correspondences = {
["g"] = "ɡ",
["ḵ"] = "x",
["ḥ"] = "ħ",
["ḡ"] = "ɣ",
["ṣ"] = "sˤ",
["ḍ"] = "dˤ",
["ẓ"] = "ðˤ",
["ṭ"] = "tˤ",
["š"] = "ʃ",
["j"] = "ʒ",
["ā"] = "aː",
["ū"] = "uː",
["ō"] = "oː",
["ī"] = "iː",
["e"] = "ɪ",
["y"] = "j"
}
local vowels = "aeiuoāīūōɪ"
local vowel = "[" .. vowels .. "]"
local long_vowels = "āīūō"
local long_vowel = "[" .. long_vowels .. "]"
local consonant = "[^" .. vowels .. ". -]"
local tie = "‿"
local syllabify_pattern = "(" .. vowel .. ")(" .. consonant .. "?)(" ..
consonant .. "?)(" .. vowel .. ")"
local function parse_params(item)
local term = item
local refs
local qualifier = {}
while true do
local new_term, angle_bracketed = term:match("^(.-)(%b<>)$")
if not new_term then
break
end
local prefix, content = angle_bracketed:match "^<(%w+):(.+)>$"
if not prefix then
break
end
if prefix == "q" then
table.insert(qualifier, 1, content)
elseif prefix == "ref" then
refs = require("Module:references").parse_references(content)
else
error("Unrecognized prefix '" .. prefix .. "'")
break
end
term = new_term
end
return term, refs, qualifier
end
local function syllabify(text)
text = gsub(text, "%-(" .. consonant .. ")%-(" .. consonant .. ")", "%1.%2")
text = gsub(text, "%-", ".")
-- Add syllable breaks.
for _ = 1, 2 do
text = gsub(text, syllabify_pattern, function(a, b, c, d)
if c == "" and b ~= "" then
c, b = b, ""
end
return a .. b .. "." .. c .. d
end)
end
text = gsub(text, "(" .. consonant .. ") (" .. vowel .. ")%.?(" ..
consonant .. ")", ".%1" .. tie .. "%2%3")
text = gsub(text, "(" .. vowel .. ") (" .. consonant .. ")%.?(" ..
consonant .. ")", "%1" .. tie .. "%2.%3")
return text
end
local function pronunciation(word)
word = syllabify(word)
word = gsub(word, '.', correspondences)
return word
end
function export.show(frame)
local args = require("Module:parameters").process(frame:getParent().args, {
[1] = { list = true }
})
local Array = require("Module:array")
local words = args[1]
local transcriptions = Array(words):map(function(word, _)
local term, refs, qualifiers = parse_params(word)
return {
pron = "/" .. pronunciation(term) .. "/",
q = #qualifiers > 0 and qualifiers or nil,
refs = refs,
}
end)
return m_IPA.format_IPA_full { lang = lang, items = transcriptions }
end
return export
j14uzv088ptv4dwypztcsuxeh1bvdmy
Module:He-noun
828
1443639
9909073
9611206
2026-09-29T07:26:20Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909073
Scribunto
text/plain
local m_links = require("Module:links")
local m_strutils = require("Module:string utilities")
local m_gn = require("Module:gender and number")
local com = require("Module:he-common")
local lang = require("Module:languages").getByCode("he")
local export = {}
local function process_arg(arg)
arg = com.fix_nikud(arg)
local i = mw.ustring.find(arg, "[\\/]")
if i then
arg = {mw.text.trim(mw.ustring.sub(arg, 1, i - 1)), mw.text.trim(mw.ustring.sub(arg, i + 1))}
end
return arg
end
local function getv(x)
if type(x) == "table" then
return x[2]
else
return x
end
end
local function getnv(x)
if type(x) == "table" then
return x[1]
else
return mw.ustring.gsub(x, "[^%^$()%%.%[%]*+%-?]+", function(c1) return lang:stripDiacritics(c1) end)
end
end
local function getnvrx(x)
return mw.ustring.gsub(getnv(x), "%[%][%?%+%-]?", "")
end
local function append_parts_2(a, b)
if type(a) == "table" then
if type(b) == "table" then
return {a[1] .. b[1], a[2] .. b[2]}
else
return {a[1] .. lang:stripDiacritics(b), a[2] .. b}
end
else
if type(b) == "table" then
return {lang:stripDiacritics(a) .. b[1], a .. b[2]}
else
return a .. b
end
end
end
local function append_parts(a, ...)
for _, b in ipairs({...}) do
a = append_parts_2(a, b)
end
return a
end
local function equal(a, b)
if type(a) == "table" then
return type(b) == "table" and a[1] == b[1] and a[2] == b[2]
else
return a == b
end
end
local function gsub_form(form, regex, repl, const_repl)
if type(form) == "table" or type(regex) == "table" or (not const_repl and type(repl) == "table") then
local replv = repl
local replnv = repl
if not const_repl then
replv = getv(repl)
replnv = getnv(repl)
end
local retv = mw.ustring.gsub(getv(form), getv(regex), replv)
local retnv = mw.ustring.gsub(getnv(form), getnvrx(regex), replnv)
return {retnv, retv}
else
return mw.ustring.gsub(form, regex, repl)
end
end
local function match_form(form, regex)
if type(form) == "table" or type(regex) == "table" then
local retv = mw.ustring.match(getv(form), getv(regex))
local retnv = mw.ustring.match(getnv(form), getnvrx(regex))
if retv and retnv then
return {retnv, retv}
else
return nil
end
else
return mw.ustring.match(form, regex)
end
end
local final_to_unfinal = {
["ך"] = "כ",
["ם"] = "מ",
["ן"] = "נ",
["ף"] = "פ",
["ץ"] = "צ",
}
local function unfinalize(form)
if match_form(form, "[חע]ַ$") or match_form(form, "הּ[ַ]?$") then
return gsub_form(form, "([החע])[ּ]?[ַ]?$", "%1")
elseif match_form(form, "[םןףץ]$") or match_form(form, "ךְ$") then
return gsub_form(form, "([ךםןףץ])[ְ]?$", final_to_unfinal, true)
else
return form
end
end
local function make_link(x, is_construct)
local dolink = true
if SUPPRESS_LINKS then
dolink = nil
end
local maqaf = is_construct and "־" or ""
if type(x) == "table" then
local pg = lang:stripDiacritics(x[1])
-- FIXME: allow_self_link is the third argument to full_link, it's not a field in the first argument;
-- maintained in case we switch to having allow_self_link be a field in the first argument
return m_links.full_link({lang = lang, allow_self_link = false, term = dolink and pg, alt = (pg ~= lang:stripDiacritics(x[2]) and pg .. maqaf .. " / " or "") .. x[2] .. maqaf, tr = "-"})
else
if x == "-" then
return "—" -- m-dash
else
-- FIXME: see above for allow_self_link
return m_links.full_link({lang = lang, term = dolink and x, alt = x .. maqaf, tr = "-", allow_self_link = false})
end
end
end
local numbers = {
["s"] = "單數",
["d"] = "雙數",
["p"] = "複數",
["ms"] = "陽性單數",
["md"] = "陽性雙數",
["mp"] = "陽性複數",
["fs"] = "陰性單數",
["fd"] = "陰性雙數",
["fp"] = "陰性複數",
}
local singulars = {
["s"] = true,
["ms"] = true,
["fs"] = true,
}
local function split_defv(form)
local ret = mw.ustring.gsub(form, "^הַ([וזטילמנסצקש][ׁׂ]?)ּ", "%1")
if ret ~= form then return ret end
ret = mw.ustring.gsub(form, "^הַ([בגדיכפת])", "%1")
if ret ~= form then return ret end
ret = mw.ustring.gsub(form, "^הָ(ר)", "%1")
if ret ~= form then return ret end
ret = mw.ustring.gsub(form, "^ה[ֶַָ]([אהחע])", "%1")
if ret ~= form then return ret end
return nil
end
local function split_def(form)
if type(form) == "table" then
local wv = split_defv(form[2])
local nv = mw.ustring.gsub(form[1], "^ה", "")
if wv and (nv ~= form[1]) then
return {nv, wv}
else
return nil
end
else
return split_defv(form)
end
end
local function attach_def(form)
if match_form(form, "^[בגדהחכפת]") then
return append_parts("הַ", form)
elseif match_form(form, "^[אער]") then
return append_parts("הָ", form)
elseif match_form(form, "^ו") then
return gsub_form(form, "^ו", {"הוו", "הַוּ"})
elseif match_form(form, "^[זטילמנסצק]") or match_form(form, "^ש[ׁׂ]") then
return gsub_form(form, "^([זטילמנסצקש][ׁׂ]?)", "הַ%1ּ")
else
error("Unrecognized initial consonant in indefinite form.")
end
end
local function remove_maqaf(x)
if type(x) == "table" then
return {mw.ustring.gsub(x[1], "־$", ""), mw.ustring.gsub(x[2], "־$", "")}
else
return mw.ustring.gsub(x, "־$", "")
end
end
local function process_args(args)
for key, val in pairs(args) do
val = mw.text.trim(val)
if val == "" then val = nil end
args[key] = val
end
local forms = {}
if not args[1] then return forms end
local i = 1
local num = "s"
while num do
local section = {["number"] = num}
local touched = false
local likely_def = nil
local initial_hes = nil
while args[i] and not numbers[args[i]] do
local form = process_arg(args[i])
local wv = getv(form)
if likely_def then
local cur_initial_hes = mw.ustring.len(mw.ustring.match(getnv(form), "^ה*"))
if cur_initial_hes < initial_hes then
section["d"] = likely_def
elseif cur_initial_hes > initial_hes and split_defv(wv) then
section["i"] = likely_def
else
error("Definite form must either be first or second.")
end
likely_def = nil
end
if (not section["d"]) and split_defv(wv) then
if touched then
section["d"] = form
else
likely_def = form
initial_hes = mw.ustring.len(mw.ustring.match(getnv(form), "^ה*"))
end
elseif not touched then
section["i"] = form
elseif (not section["2ms"]) and mw.ustring.match(wv, "ךָ$") then
section["2ms"] = form
elseif (not section["2mp"]) and mw.ustring.match(wv, "כֶם$") then
section["2mp"] = form
elseif (not section["3ms"]) and (mw.ustring.match(wv, "וֹ$") or mw.ustring.match(wv, "ו$") or mw.ustring.match(wv, "הוּ$")) then
section["3ms"] = form
elseif (not section["c"]) and (mw.ustring.match(wv, "־$") or mw.ustring.match(getnv(form), "־$")) then
section["c"] = remove_maqaf(form)
else
error("Unrecognized or duplicate form: " .. wv)
end
touched = true
i = i + 1
end
if likely_def then
if section["d"] then error("Unrecognized or duplicate form: " .. getv(section["d"])) end
section["d"] = likely_def
end
if touched then
table.insert(forms, section)
end
num = args[i]
i = i + 1
end
return forms
end
local endings_c = {
["3p_heavy"] = false,
["1s"] = "ִי",
["2ms"] = "ְךָ",
["2fs"] = "ֵךְ",
["3ms"] = "וֹ",
["3fs"] = "ָהּ",
["1p"] = "ֵנוּ",
["2mp"] = "ְכֶם",
["2fp"] = "ְכֶן",
["3mp"] = "ָם",
["3fp"] = "ָן",
}
-- most have alternative forms that are not yet supported
local endings_e = {
["3p_heavy"] = true,
["1s"] = "ִי",
["2ms"] = "ֶךָ",
["2fs"] = "ֵךְ",
["3ms"] = "ֵהוּ",
["3fs"] = "ֶהָ",
["1p"] = "ֵנוּ",
["2mp"] = "ֵכֶם",
["2fp"] = "ֵכֶן",
["3mp"] = "ֵהֶם",
["3fp"] = "ֵהֶן",
}
local endings_i = {
["3p_heavy"] = true,
["1s"] = "",
["2ms"] = "ךָ",
["2fs"] = "ךְ",
["3ms"] = "ו", -- and "הוּ", for when I allow multiple forms
["3fs"] = "הָ",
["1p"] = "נוּ",
["2mp"] = "כֶם",
["2fp"] = "כֶן",
["3mp"] = "הֶם",
["3fp"] = "הֶן",
}
-- endings for ים ending
local endings_pm = {
["3p_heavy"] = true,
["1s"] = {"יי", "ַי"},
["2ms"] = "ֶיךָ",
["2fs"] = {"ייך", "ַיִךְ"},
["3ms"] = "ָיו",
["3fs"] = "ֶיהָ",
["1p"] = "ֵינוּ",
["2mp"] = "ֵיכֶם",
["2fp"] = "ֵיכֶן",
["3mp"] = "ֵיהֶם",
["3fp"] = "ֵיהֶן",
}
-- endings for ות ending
local endings_pf = {
["3p_heavy"] = true,
["1s"] = {"יי", "ַי"},
["2ms"] = "ֶיךָ",
["2fs"] = {"ייך", "ַיִךְ"},
["3ms"] = "ָיו",
["3fs"] = "ֶיהָ",
["1p"] = "ֵינוּ",
["2mp"] = "ֵיכֶם",
["2fp"] = "ֵיכֶן",
["3mp"] = "ָם",--add extra יהם ending
["3fp"] = "ָן",--add extra יהן ending
}
local light_endings = {
"1s",
"2ms",
"2fs",
"3ms",
"3fs",
"1p",
}
local heavy_endings = {
"2mp",
"2fp",
}
local maybe_endings = {
"3mp",
"3fp",
}
local function attach_ending(stem, ending)
if match_form(ending, "^ְ") and match_form(stem, "[אהחע]$") then
ending = gsub_form(ending, "^ְ", "ֲ")
elseif match_form(ending, "^ָ") and match_form(stem, "ַח$") then
stem = gsub_form(stem, "ַח$", "ֶח")
elseif type(ending) == "table" and mw.ustring.match(getnv(stem), "י$") and mw.ustring.len(mw.ustring.match(getnv(ending), "^י*")) > mw.ustring.len(mw.ustring.match(getnv(getv(ending)), "^י*")) then
ending = gsub_form(ending, {"^י", "^"}, "")
end
return append_parts(stem, ending)
end
local function infer_forms(forms)
for _, section in pairs(forms) do
local light_stem = nil
local heavy_stem = nil
local endings = nil
if section["d"] then
section["i"] = section["i"] or split_def(section["d"])
elseif section["i"] then
section["d"] = section["d"] or attach_def(section["i"])
else
error("Must have either definite or indefinite form.")
end
if singulars[section["number"]] then
local is_fem = match_form(section["i"], "ָ['׳]?ה$")
local is_e = match_form(section["i"], "ֶה$")
if not section["c"] then
if is_fem then
section["c"] = gsub_form(section["i"], "ָ(['׳]?)ה$", "ַ%1ת")
elseif is_e then
section["c"] = gsub_form(section["i"], "ֶה$", "ֵה")
elseif match_form(section["i"], "ָ[ב-ת][ׁׂ]?$") then
section["c"] = gsub_form(section["i"], "ָ([ב-ת][ׁׂ]?)$", "ַ%1")
else
section["c"] = section["i"]
end
end
if section["3ms"] then
if match_form(section["3ms"], "וֹ$") then
light_stem = gsub_form(section["3ms"], "וֹ$", "")
endings = endings_c
elseif match_form(section["3ms"], "ֵהוּ$") then
light_stem = gsub_form(section["3ms"], "ֵהוּ$", "")
endings = endings_c
elseif match_form(section["3ms"], "ִיהוּ$") then
light_stem = gsub_form(section["3ms"], "הוּ$", "")
endings = endings_i
elseif match_form(section["3ms"], "ִיו$") then
light_stem = gsub_form(section["3ms"], "ו$", "")
endings = endings_i
else
error("Unrecognized 3ms suffix pattern.")
end
elseif match_form(section["c"], "ֵה$") then
if is_e then
light_stem = gsub_form(section["i"], "ֶה$", "")
else
light_stem = gsub_form(section["c"], "ֵה$", "")
end
endings = endings_e
elseif match_form(unfinalize(section["c"]), "ַ[א-ת][ׁׂ]?$") then
light_stem = gsub_form(unfinalize(section["c"]), "ַ([בגדהוזחטכלמנסעפצקרשת][ׁׂ]?)$", "ָ%1")
endings = endings_c
else
light_stem = unfinalize(section["c"])
if match_form(section["c"], "ִי$") then
endings = endings_i
else
endings = endings_c
end
end
if section["2mp"] then
if endings == endings_c then
if not match_form(section["2mp"], "[^אהחע]ְכֶם$") and not match_form(section["2mp"], "[אהחע]ֲכֶם$") then
error("Unrecognized 2mp suffix pattern.")
end
heavy_stem = gsub_form(section["2mp"], "[ְֲ]כֶם$", "")
else
if not match_form(section["2mp"], "כֶם$") then
error("Unrecognized 2mp suffix pattern.")
end
heavy_stem = gsub_form(section["2mp"], "כֶם$", "")
end
elseif section["2ms"] and endings == endings_c and match_form(section["2ms"], "ְךָ$") then
heavy_stem = gsub_form(section["2ms"], "ְךָ$", "")
elseif match_form(light_stem, "ּ$") or match_form(light_stem, "ְ[א-ת][ּׁׂ]?$") then
heavy_stem = light_stem
elseif match_form(section["c"], "ֵה$") then
if endings == endings_e then
heavy_stem = gsub_form(section["c"], "ה$", "")
else
heavy_stem = light_stem
end
else
heavy_stem = unfinalize(section["c"])
end
else
endings = endings_pm
if not section["c"] then
if match_form(section["i"], "וֹת$") then
endings = endings_pf
section["c"] = section["i"]
elseif match_form(section["i"], "ִים$") then
section["c"] = gsub_form(section["i"], "ִים$", "ֵי")
elseif match_form(section["i"], {"יי?ם$", "ַיִם$"}) then
section["c"] = gsub_form(section["i"], {"יי?ם$", "ַיִם$"}, "ֵי")
else
error("Unrecognized plural pattern.")
end
end
if match_form(section["c"], "ֵי$") then
heavy_stem = gsub_form(section["c"], "ֵי$", "")
if match_form(section["i"], "ִים$") then
light_stem = gsub_form(section["i"], "ִים$", "")
elseif match_form(section["i"], {"יי?ם$", "ַיִם$"}) then
light_stem = gsub_form(section["i"], {"יי?ם$", "ַיִם$"}, "")
else
light_stem = heavy_stem
end
elseif match_form(section["c"], "וֹת$") then
endings = endings_pf
heavy_stem = section["c"]
light_stem = heavy_stem
else
error("Unrecognized plural construct pattern.")
end
end
local maybe_stem = endings["3p_heavy"] and heavy_stem or light_stem
for _, p in pairs(light_endings) do
section[p] = section[p] or attach_ending(light_stem, endings[p])
end
for _, p in pairs(heavy_endings) do
section[p] = section[p] or attach_ending(heavy_stem, endings[p])
end
for _, p in pairs(maybe_endings) do
section[p] = section[p] or attach_ending(maybe_stem, endings[p])
end
end
end
local example_s = {
["number"] = "s",
["i"] = "בַּיִת",
["d"] = "הַבַּיִת",
["c"] = "בֵּית",
["1s"] = "בֵּיתִי",
["2ms"] = "בֵּיתְךָ",
["2fs"] = "בֵּיתֵךְ",
["3ms"] = "בֵּיתוֹ",
["3fs"] = "בֵּיתָהּ",
["1p"] = "בֵּיתֵנוּ",
["2mp"] = "בֵּיתְכֶם",
["2fp"] = "בֵּיתְכֶן",
["3mp"] = "בֵּיתָם",
["3fp"] = "בֵּיתָן",
}
local example_p = {
["number"] = "p",
["i"] = "בָּתִּים",
["d"] = "הַבָּתִּים",
["c"] = "בָּתֵּי",
["1s"] = {"בתיי", "בָּתַּי"},
["2ms"] = "בָּתֶּיךָ",
["2fs"] = {"בתייך", "בָּתַּיִךְ"},
["3ms"] = "בָּתָּיו",
["3fs"] = "בָּתֶּיהָ",
["1p"] = "בָּתֵּינוּ",
["2mp"] = "בָּתֵּיכֶם",
["2fp"] = "בָּתֵּיכֶן",
["3mp"] = "בָּתֵּיהֶם",
["3fp"] = "בָּתֵּיהֶן",
}
local example = {example_s, example_p}
local table_top = [===[
! class="outer" |
! class="outer" colspan="2" | 單獨形式
! class="outer" colspan="5" | 帶所有格代詞
|-
! rowspan="2" | 數
! rowspan="2" | 態 !! rowspan="2" | 詞形
! rowspan="2" | 人稱 !! colspan="2" | 單數 !! colspan="2" | 複數
|-
! class="secondary" | {m}
! class="secondary" | {f}
! class="secondary" | {m}
! class="secondary" | {f}
]===]
local table_section = [===[|-
! rowspan="3" | {number} !! 不定
| {i}
! 第一
| colspan="2" | {1s} || colspan="2" | {1p}
|-
! 定指
| {d}
! 第二
| {2ms} || {2fs} || {2mp} || {2fp}
|-
! 結構
| {c}
! 第三
| {3ms} || {3fs} || {3mp} || {3fp}
]===]
local forms_names = {
"i",
"d",
"c",
"1s",
"2ms",
"2fs",
"3ms",
"3fs",
"1p",
"2mp",
"2fp",
"3mp",
"3fp",
}
local function make_table(forms)
local title = forms and forms[1] and forms[1]["i"] and (make_link(forms[1]["i"]) .. "的變格") or "變格"
local output = {}
table.insert(output, mw.getCurrentFrame():expandTemplate{
title = 'inflection-table-top',
args = {
title = title,
palette = 'blue',
tall = 'yes',
}
})
table.insert(output, m_strutils.format(table_top, {
m = m_gn.format_genders({"m"}),
f = m_gn.format_genders({"f"}),
}))
for _, section in ipairs(forms) do
section["number"] = numbers[section["number"]]
for _, form_name in pairs(forms_names) do
section[form_name] = make_link(section[form_name] or "-", section[form_name] and form_name == "c")
end
table.insert(output, m_strutils.format(table_section, section))
end
table.insert(output, mw.getCurrentFrame():expandTemplate{ title = 'inflection-table-bottom' })
return table.concat(output)
end
function export.show(frame)
local args = frame:getParent().args
PAGENAME = mw.loadData("Module:headword/data").pagename
NAMESPACE = mw.title.getCurrentTitle().nsText
if args["nolink"] and args["nolink"] ~= "" then
SUPPRESS_LINKS = true
end
local forms = nil
if frame.args[1] == "example" then
forms = example
else
forms = process_args(args)
infer_forms(forms)
end
return make_table(forms)
end
return export
kna7wrwd56ki26bxf95o1dqn2le3h12
Module:Nl-verbs
828
1450938
9909090
9882968
2026-09-29T07:27:24Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909090
Scribunto
text/plain
local m_utilities = require("Module:utilities")
local m_links = require("Module:links")
local lang = require("Module:languages").getByCode("nl")
local export = {}
-- Functions that do the actual inflecting by creating the forms of a basic term.
local inflections = {}
local irregular = {}
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local infl_type = frame.args[1] or error("Inflection type has not been specified. Please pass parameter 1 to the module invocation.")
if not inflections[infl_type] then
error("Unknown inflection type '" .. infl_type .. "'")
end
local params = {
["aux"] = {},
["aux2"] = {},
["bot"] = {type = "boolean"},
["pref"] = {},
["sep"] = {},
["trans"] = {},
["trans2"] = {},
["vnoun"] = {},
["vnoung"] = {},
["vnoun2"] = {},
}
for key, val in pairs(inflections[infl_type].params) do
params[key] = val
end
local args = require("Module:parameters").process(frame:getParent().args, params)
local data = {forms = {}, title = nil, categories = {}}
inflections[infl_type].func(args, data)
postprocess(args, data)
if (args["bot"] or "") ~= "" then
return make_bot_list(data, args["sep"])
else
return make_table(data, args["sep"]) .. m_utilities.format_categories(data.categories, lang)
end
end
function postprocess(args, data)
-- Verbal noun
local vnoun = args["vnoun"]
local vnoun2 = args["vnoun2"]
if vnoun then
data.forms["vnoun"] = {vnoun, vnoun2}
end
-- Prefixed and separable
local pref = args["pref"]
local sep = args["sep"]
if pref then
table.insert(data.categories, "荷蘭語不可分動詞") --原作Dutch prefixed verbs帶前綴動詞,但荷蘭語詞典給的是onscheidbaar werkwoord
-- table.insert(data.categories, "Dutch prefixed verbs with " .. pref .. "-")
data.title = data.title .. ",不可分"
end
if sep then
table.insert(data.categories, "荷蘭語可分動詞")
-- table.insert(data.categories, "Dutch separable verbs with " .. sep)
data.title = data.title .. ",可分"
end
if not pref and not sep then
table.insert(data.categories, "荷蘭語動詞") --應作Dutch basic verbs,請補充翻譯
end
add_pref(data, pref)
add_sep(data, sep)
data.aux = args["aux"] or "hebben"
if args["aux"] then
if args["aux"] == "zijn" then
require("Module:debug").track("nl-verb/zijn")
else
require("Module:debug").track("nl-verb/aux")
end
end
if args["aux2"] then
require("Module:debug").track("nl-verb/aux2")
end
data.trans = args["trans"]
if data.trans then
if data.trans == "acc" then
table.insert(data.categories, "荷蘭語賓格動詞")
elseif data.trans == "erg" then
table.insert(data.categories, "荷蘭語作格動詞")
elseif data.trans == "unerg" then
table.insert(data.categories, "荷蘭語非作格動詞")
elseif data.trans == "unacc" then
table.insert(data.categories, "荷蘭語非賓格動詞")
else
error("The \"trans=\" parameter has an invalid value. Valid are: acc, erg, unerg, unacc.")
end
end
data.trans2 = args["trans2"]
if data.trans2 then
if data.trans2 == "acc" then
table.insert(data.categories, "荷蘭語賓格動詞")
elseif data.trans2 == "erg" then
table.insert(data.categories, "荷蘭語作格動詞")
elseif data.trans2 == "unerg" then
table.insert(data.categories, "荷蘭語非作格動詞")
elseif data.trans2 == "unacc" then
table.insert(data.categories, "荷蘭語非賓格動詞")
else
error("The \"trans2=\" parameter has an invalid value. Valid are: acc, erg, unerg, unacc.")
end
end
if not args["trans"] then
require("Module:debug").track("nl-verb/no trans")
end
end
-- Add the unstressed prefix to all the verb forms, or
-- if there is no prefix, add ge- to the past participle
function add_pref(data, pref)
if pref then
-- Add prefix before every form
for key, form in pairs(data.forms) do
for i, subform in ipairs(form) do
data.forms[key][i] = pref .. subform
end
end
else
-- Add ge- prefix before past participle
if data.forms["past_ptc"] then
for i, subform in ipairs(data.forms["past_ptc"]) do
-- Place a diaeresis on the initial vowel of the stem if necessary
if mw.ustring.find(subform, "^[eiu]") and not mw.ustring.find(subform, "^ij") then
subform = subform:gsub("^e", "ë"):gsub("^i", "ï"):gsub("^u", "ü")
end
data.forms["past_ptc"][i] = "ge" .. subform
end
end
end
end
-- Add the separable part to all the verb forms
function add_sep(data, sep)
if not sep then
return
end
-- Add main clause forms
local mainforms = {}
for key, form in pairs(data.forms) do
if (key:find("^pres_") or key:find("^past_")) and not key:find("_ptc$") then
mainforms[key .. "_main"] = mw.clone(form)
end
end
for key, form in pairs(mainforms) do
data.forms[key] = form
end
-- Add separable part to forms
for key, form in pairs(data.forms) do
if key:find("_main$") or key:find("^impr_") then
-- For main-clause finite forms, add the separable part after the form, separated by a space
for i, subform in ipairs(data.forms[key]) do
data.forms[key][i] = data.forms[key][i] .. " " .. sep
end
else
-- For all other forms, add the separable part before the form, with no space
for i, subform in ipairs(data.forms[key]) do
-- Add a hyphen after the separable part if it ends with the same vowel
-- that the main/prefixed verb begins with, for verbs like na-apen
if mw.ustring.find("aeiou", sep:sub(-1)) and sep:sub(-1) == subform:sub(1, 1) then
subform = sep .. "-" .. subform
else
subform = sep .. subform
end
data.forms[key][i] = subform
end
end
end
end
--[=[
*** REGULAR VERBS (mostly) ***
]=]--
local function addform(data, key, form, index)
if data.forms[key] then
if index then
table.insert(data.forms[key], index, form)
else
table.insert(data.forms[key], form)
end
else
data.forms[key] = {form}
end
end
-- Conjugate a weak verb
inflections["weak"] = {
params = {
[1] = {required = true},
[2] = {},
[3] = {},
["dt"] = {},
},
func = function(args, data)
table.insert(data.categories, "荷蘭語弱變化動詞")
data.title = "弱變化"
local stems = {}
stems["pres"] = args[1] or "{{{1}}}"
stems["pres_e"] = args[2]
present(data, stems)
local dt = args["dt"] or (stems["pres_e"]:find("[cfhkpqstx]e$") and "t" or "d")
stems["past"] = stems["pres"]:gsub("([^aeiou])i$", "%1ie") .. dt .. "e"
past(data, stems)
stems["past_ptc"] = args[3] or make_long(stems["pres"]) .. (stems["pres"]:find("[dt]$") and "" or dt)
data.forms["past_ptc"] = {stems["past_ptc"]}
if stems["past_ptc"]:find("n$") then
table.insert(data.categories, "有強變化過去分詞的荷蘭語弱變化動詞")
data.title = data.title .. ",過去分詞為強變化"
end
-- "zeggen" has an irregular past tense alongside the regular one
if stems["pres"] == "zeg" then
past(data, {["past"] = "zei", ["past_e"] = "zeide", ["past_t"] = "zeidt"}, 1)
table.insert(data.categories, "荷蘭語不規則弱變化動詞")
data.title = data.title .. ",不規則"
end
end
}
-- Conjugate a weak verb with a past tense in -cht
inflections["weak-cht"] = {
params = {
[1] = {required = true},
[2] = {},
[3] = {required = true},
},
func = function(args, data)
table.insert(data.categories, "Dutch weak verbs (-cht)")
data.title = "弱變化,過去式以 ''-cht'' 為詞尾"
local stems = {}
stems["pres"] = args[1] or "{{{1}}}"
stems["pres_e"] = args[2]
present(data, stems)
stems["past"] = (args[3] or "{{{3}}}") .. "cht"
past(data, stems)
stems["past_ptc"] = stems["past"]
data.forms["past_ptc"] = {stems["past_ptc"]}
end
}
-- Conjugate a strong verb
inflections["strong"] = {
params = {
[1] = {required = true},
[2] = {required = true},
[3] = {required = true},
[4] = {},
[5] = {},
[6] = {},
["class"] = {},
},
func = function(args, data)
local class = args["class"]
table.insert(data.categories, "荷蘭語" .. (class and "" .. class .. "類" or "") .. "強變化動詞")
data.title = "強變化" .. (class and ",第" .. class .. "類" or "")
local stems = {}
stems["pres"] = args[1] or "{{{1}}}"
stems["pres_e"] = args[4]
present(data, stems)
stems["past"] = args[2] or "{{{2}}}"
stems["past_e"] = args[5]
stems["past_t"] = args[6]
past(data, stems)
stems["past_ptc"] = args[3] or "{{{3}}}"
data.forms["past_ptc"] = {stems["past_ptc"]}
-- If the final consonant of the past participle is not n, then it is a weak past participle.
if not stems["past_ptc"]:find("n$") then
table.insert(data.categories, "有弱變化過去分詞的荷蘭語強變化動詞")
data.title = data.title .. ",過去分詞為弱變化"
end
-- "houden" and "snijden" have alternative forms without the final -d
if stems["pres"] == "houd" then
addform(data, "pres_indc_1sg", "hou", 1)
addform(data, "impr_sg", "hou", 1)
table.insert(data.categories, "荷蘭語不規則強變化動詞")
data.title = data.title .. ", slightly irregular"
elseif stems["pres"] == "snijd" then
addform(data, "pres_indc_1sg", "snij", 1)
addform(data, "impr_sg", "snij", 1)
table.insert(data.categories, "荷蘭語不規則強變化動詞")
data.title = data.title .. ", slightly irregular"
-- If the initial or final consonants of the present stem don't match, then this verb is irregular.
elseif stems["pres"]:match("^([^aeiouyj]*)") ~= stems["past"]:match("^([^aeiouyj]*)") or stems["pres"]:match("([^aeiouyj]*)$") ~= stems["past"]:match("([^aeiouyj]*)$") then
table.insert(data.categories, "荷蘭語不規則強變化動詞")
data.title = data.title .. ",不規則"
end
end
}
-- Conjugate an irregular verb
inflections["irregular"] = {
params = {
[1] = {required = true},
},
func = function(args, data)
local base = args[1] or "zijn"
if irregular[base] then
irregular[base](data)
else
error("Unknown irregular verb '" .. base .. "'.")
end
end
}
--[=[
*** IRREGULAR VERB TYPES ***
]=]--
irregular["doen"] = function(data)
table.insert(data.categories, "荷蘭語不規則動詞")
data.title = "不規則"
present(data, {["pres"] = "doe", ["pres_e"] = "doe"})
past(data, {["past"] = "deed", ["past_e"] = "dede"})
data.forms["past_ptc"] = {"daan"}
data.forms["pres_indc_u"] = {"doet"}
data.forms["pres_indc_3sg"] = {"doet"}
end
irregular["hebben"] = function(data)
table.insert(data.categories, "荷蘭語不規則弱變化動詞")
data.title = "弱變化,不規則"
present(data, {["pres"] = "heb", ["pres_e"] = "hebbe"})
past(data, {["past"] = "had", ["past_e"] = "hadde"})
data.forms["past_ptc"] = {"had"}
data.forms["pres_indc_u"] = {"hebt", "heeft"}
data.forms["pres_indc_3sg"] = {"heeft"}
end
irregular["kunnen"] = function(data)
table.insert(data.categories, "Dutch preterite-present verbs")
data.title = "preterite-present"
present(data, {["pres"] = "kan", ["pres_e"] = "kunne", ["pres_t"] = "kunt"})
past(data, {["past"] = "kon", ["past_e"] = "konde", ["past_t"] = "kondt"})
addform(data, "pres_indc_jij", "kan")
addform(data, "pres_indc_u", "kan")
data.forms["pres_indc_3sg"] = {"kan"}
data.forms["past_ptc"] = {"kund"}
end
irregular["moeten"] = function(data)
table.insert(data.categories, "Dutch preterite-present verbs")
data.title = "preterite-present"
present(data, {["pres"] = "moet"})
past(data, {["past"] = "moest"})
data.forms["past_ptc"] = {"moeten"}
end
irregular["mogen"] = function(data)
table.insert(data.categories, "Dutch preterite-present verbs")
data.title = "preterite-present"
present(data, {["pres"] = "mag", ["pres_e"] = "moge", ["pres_t"] = "moogt"})
past(data, {["past"] = "mocht"})
data.forms["pres_indc_jij"] = {"mag"}
data.forms["pres_indc_u"] = {"mag"}
data.forms["pres_indc_3sg"] = {"mag"}
data.forms["past_ptc"] = {"mogen"}
end
irregular["weten"] = function(data)
table.insert(data.categories, "Dutch preterite-present verbs")
data.title = "preterite-present"
present(data, {["pres"] = "weet", ["pres_e"] = "wete"})
past(data, {["past"] = "wist"})
data.forms["past_ptc"] = {"weten"}
end
irregular["willen"] = function(data)
table.insert(data.categories, "荷蘭語不規則動詞")
data.title = "不規則"
present(data, {["pres"] = "wil", ["pres_e"] = "wille"})
past(data, {["past"] = "wilde"})
past(data, {["past"] = "wou", ["past_e"] = "woude", ["past_t"] = "woudt"})
data.forms["past_ptc"] = {"wild"}
addform(data, "pres_indc_jij", "wil")
addform(data, "pres_indc_u", "wil")
data.forms["pres_indc_3sg"] = {"wil"}
end
irregular["zijn"] = function(data)
table.insert(data.categories, "荷蘭語不規則動詞")
table.insert(data.categories, "荷蘭語異幹動詞")
data.title = "不規則,異幹"
present(data, {["pres"] = "ben", ["pres_e"] = "zij", ["pres_t"] = "zijt"})
past(data, {["past"] = "was", ["past_e"] = "ware", ["past_t"] = "waart"})
data.forms["past_ptc"] = {"weest"}
data.forms["pres_indc_jij"] = {"bent"}
data.forms["pres_indc_u"] = {"bent", "is"}
data.forms["pres_indc_3sg"] = {"is"}
addform(data, "impr_sg", "wees", 1)
addform(data, "impr_pl", "weest", 1)
end
irregular["zullen"] = function(data)
table.insert(data.categories, "Dutch preterite-present verbs")
data.title = "preterite-present"
present(data, {["pres"] = "zal", ["pres_e"] = "zulle", ["pres_t"] = "zult"})
past(data, {["past"] = "zou", ["past_e"] = "zoude", ["past_t"] = "zoudt"})
data.forms["pres_indc_jij"] = {"zult", "zal"}
data.forms["pres_indc_u"] = {"zult", "zal"}
data.forms["pres_indc_3sg"] = {"zal"}
end
--[=[
*** HELPER FUNCTIONS ***
]=]--
-- Create regular present-tense forms
function present(data, stems, index)
if not stems["pres_e"] then
stems["pres_e"] = stems["pres"] .. "e"
end
if not stems["pres_t"] then
if mw.ustring.find(stems["pres"], "t$") then
stems["pres_t"] = make_long(stems["pres"])
else
stems["pres_t"] = make_long(stems["pres"]) .. "t"
end
end
addform(data, "pres_indc_1sg", stems["pres"], index)
addform(data, "pres_indc_jij", stems["pres_t"], index)
addform(data, "pres_indc_gij", stems["pres_t"], index)
addform(data, "pres_indc_u" , stems["pres_t"], index)
addform(data, "pres_indc_3sg", stems["pres_t"], index)
addform(data, "pres_indc_pl" , make_long(stems["pres_e"]) .. "n", index)
addform(data, "pres_subj_sg" , stems["pres_e"], index)
addform(data, "impr_sg", stems["pres"], index)
addform(data, "impr_pl", stems["pres_t"], index)
addform(data, "infinitive", make_long(stems["pres_e"]) .. "n", index)
addform(data, "pres_ptc", make_long(stems["pres_e"]) .. "nd", index)
end
-- Create regular past-tense forms
function past(data, stems, index)
if not stems["past_e"] then
if mw.ustring.find(stems["past"], "e$") then
stems["past_e"] = stems["past"]
else
stems["past_e"] = stems["past"] .. "e"
end
end
if not stems["past_t"] then
if mw.ustring.find(stems["past"], "[^eio]e$") or mw.ustring.find(stems["past"], "[të]$") then
stems["past_t"] = stems["past"]
else
stems["past_t"] = stems["past"] .. "t"
end
end
addform(data, "past_indc_sg" , stems["past"], index)
addform(data, "past_indc_gij" , stems["past_t"], index)
addform(data, "past_indc_pl" , stems["past_e"] .. "n", index)
addform(data, "past_subj_sg" , stems["past_e"], index)
end
-- A small helper function for those few verbs that have a stem ending in a
-- vowel (like gaan, staan, skiën, echoën). This lengthens the stem-final vowel.
function make_long(form)
return (form:gsub("([^aeiou])([ao])$", "%1%2%2"):gsub("([^aeiou])i$", "%1ie"))
end
-- Make the table
function make_table(data, hasSep)
local function show_form(form)
if not form then
return "—"
elseif type(form) ~= "table" then
error("a non-table value was given in the list of inflected forms.")
end
local ret = {}
for key, subform in ipairs(form) do
table.insert(ret, m_links.full_link({lang = lang, term = subform}))
end
return table.concat(ret, ", ")
end
local function repl(param)
if param == "lemma" then
return m_links.full_link({lang = lang, alt = mw.title.getCurrentTitle().text}, "term")
elseif param == "info" then
return data.title and " (" .. data.title .. ")" or ""
else
return show_form(data.forms[param])
end
end
local wikicode = [=[
{| style="border:1px solid var(--wikt-palette-indigo-3,#CCCCFF); text-align:center; line-height:125%" class="inflection-table vsSwitcher" data-toggle-category="inflection" cellspacing="1" cellpadding="3"
|- style="background:var(--wikt-palette-indigo-3,#CCCCFF); color:inherit"
! colspan="5" class="vsToggleElement" style="text-align: left" | {{{lemma}}}的變位形式{{{info}}}
|- class="vsShow" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[不定式]]
| style="min-width: 12em;" | {{{infinitive}}}
|- class="vsShow" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[過去式]][[單數]]
| ]=] .. (hasSep and "{{{past_indc_sg_main}}}" or "{{{past_indc_sg}}}") .. [=[
|- class="vsShow" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[過去]][[分詞]]
| {{{past_ptc}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[不定式]]
| colspan="4" | {{{infinitive}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[動名詞]]
| colspan="4" | {{{infinitive}}} ]=] .. require("Module:gender and number").format_genders({"n"}) .. (hasSep and [=[
|- class="vsHide" style="background:var(--wikt-palette-indigo-1,#E6E6FF); color:inherit"
|
| colspan="2" style="font-weight: bold;" | [[主句]]
| colspan="2" style="font-weight: bold;" | [[從句]]
|- class="vsHide" style="background:var(--wikt-palette-indigo-1,#E6E6FF); color:inherit"
|
| style="min-width: 12em; font-weight: bold" | [[現在式]]
| style="min-width: 12em; font-weight: bold" | [[過去式]]
| style="min-width: 12em; font-weight: bold" | [[現在式]]
| style="min-width: 12em; font-weight: bold" | [[過去式]]]=] or [=[
|- class="vsHide" style="background:var(--wikt-palette-indigo-1,#E6E6FF); color:inherit"
|
| style="min-width: 12em; font-weight: bold" | [[現在式]]
| style="min-width: 12em; font-weight: bold" | [[過去式]]
|-]=]) .. [=[
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[第一人稱]][[單數]]
| ]=] .. (hasSep and "{{{pres_indc_1sg_main}}} || {{{past_indc_sg_main}}} || " or "") .. [=[{{{pres_indc_1sg}}} || {{{past_indc_sg}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[第二人稱]][[單數]](<span lang="nl">[[jij#荷蘭語|jij]]</span>)
| ]=] .. (hasSep and "{{{pres_indc_jij_main}}} || {{{past_indc_sg_main}}} || " or "") .. [=[{{{pres_indc_jij}}} || {{{past_indc_sg}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[第二人稱]][[單數]](<span lang="nl">[[u#荷蘭語|u]]</span>)
| ]=] .. (hasSep and "{{{pres_indc_u_main}}} || {{{past_indc_sg_main}}} || " or "") .. [=[{{{pres_indc_u}}} || {{{past_indc_sg}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[第二人稱]][[單數]](<span lang="nl">[[gij#荷蘭語|gij]]</span>)
| ]=] .. (hasSep and "{{{pres_indc_gij_main}}} || {{{past_indc_gij_main}}} || " or "") .. [=[{{{pres_indc_gij}}} || {{{past_indc_gij}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[第三人稱]][[單數]]
| ]=] .. (hasSep and "{{{pres_indc_3sg_main}}} || {{{past_indc_sg_main}}} || " or "") .. [=[{{{pres_indc_3sg}}} || {{{past_indc_sg}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[複數]]
| ]=] .. (hasSep and "{{{pres_indc_pl_main}}} || {{{past_indc_pl_main}}} || " or "") .. [=[{{{pres_indc_pl}}} || {{{past_indc_pl}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-1,#E6E6FF); height: 0.5em"
|
| colspan="2" |]=] .. (hasSep and [=[ || colspan="2" |]=] or "") .. [=[
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[虛擬式]][[單數]]<sup>1</sup>
| ]=] .. (hasSep and "{{{pres_subj_sg_main}}} || {{{past_subj_sg_main}}} || " or "") .. [=[{{{pres_subj_sg}}} || {{{past_subj_sg}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[虛擬式]][[複數]]<sup>1</sup>
| ]=] .. (hasSep and "{{{pres_indc_pl_main}}} || {{{past_indc_pl_main}}} || " or "") .. [=[{{{pres_indc_pl}}} || {{{past_indc_pl}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-1,#E6E6FF); height: 0.5em"
|
| colspan="2" |]=] .. (hasSep and [=[ || rowspan="5" colspan="2" |]=] or "") .. [=[
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[命令式]][[單數]]
| {{{impr_sg}}}
| rowspan="2" style="background:var(--wikt-palette-indigo-1,#E6E6FF); color:inherit" |
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[命令式]][[複數]]<sup>1</sup>
| {{{impr_pl}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-1,#E6E6FF); height: 0.5em"
|
| colspan="2" |
|- class="vsHide" style="background:var(--wikt-palette-indigo-0,#F2F2FF); color:inherit"
! style="background:var(--wikt-palette-indigo-2,#CCCCFF); color:inherit" | [[分詞]]
| {{{pres_ptc}}} || {{{past_ptc}}}
|- class="vsHide" style="background:var(--wikt-palette-indigo-1,#E6E6FF); color:inherit"
| colspan="5" style="text-align:left; vertical-align:top; font-size: smaller; line-height: 1em" | <sup>1)</sup>古體。
|}]=]
return mw.ustring.gsub(wikicode, "{{{([a-z0-9_]+)}}}", repl)
end
function make_bot_list(data, hasSep)
local ret = ""
if hasSep then
ret = ret .. "* sep=1\n"
end
for key, form in pairs(data.forms) do
if type(form) == "table" then
for key2, subform in ipairs(form) do
ret = ret .. "* " .. key .. "_" .. key2 .. "=" .. subform .. "\n"
end
else
ret = ret .. "* " .. key .. "=" .. form .. "\n"
end
end
return ret
end
return export
c770rke4inxju16ptqjw3g408wsl8pr
Module:Rw-headword
828
1452856
9909177
5879382
2026-09-29T09:28:54Z
TongcyDai
53191
同步 enwikt
9909177
Scribunto
text/plain
local export = {}
local lang = require("Module:languages").getByCode("rw")
local MACRON = mw.ustring.char(0x0304)
local ACUTE = mw.ustring.char(0x0301)
local CIRCUMFLEX = mw.ustring.char(0x0302)
local plural_classes = {
["1"] = "c2", ["1a"] = "c2a", ["3"] = "c4", ["7"] = "c8", ["9"] = "c10", ["11"] = "c10", ["11/6"] = "c6", ["11/14"] = "c14",
["12"] = "c13", ["5"] = "c6", ["5/10"] = "c10", ["9/6"] = "c6", ["9/za"] = "c10", ["14"] = "c6", ["15"] = "c6"}
local plural_rules = {
["1"] = {[1] = {"umw", "ab", 4}, [2] = {"umu", "aba", 4}, [3] = {"mu", "ba", 3}, [4] = {"mw", "b", 3}},
["1a"] = {[1] = { "", "bā", 1}},
["3"] = {[1] = {"umw", "imy", 4}, [2] = {"umu", "imi", 4}, [3] = {"mu", "mi", 3}, [3] = {"mw", "my", 3}},
["5"] = {[1] = {"i", "ama", 2}, [2] = {"iry", "am", 4}, [3] = {"ry", "m", 3}, [4] = {"", "ma", 1}},
["5/10"] = {[1] = { "", "", 1}},
["7"] = {[1] = {"iki", "ibi", 4}, [2] = {"igi", "ibi", 4}, [3] = {"icy", "iby", 4}, [4] = {"ic", "ivy", 3}, [5] = {"ki", "bi", 3}, [6] = {"gi", "bi", 3}, [7] = {"cy", "by", 3}, [8] = {"c", "vy", 2}},
["9"] = {[1] = { "", "", 1}},
["9/6"] = {[1] = { "i", "ama", 2}, [2] = {"","ama",1}},
["9/za"] = {[1] = {"", "[[zā]] ",1}},
["11"] = {[1] = {"uruh", "imp", 5}, [2] = {"urub", "imb", 5}, [3] = {"urup", "imp", 5}, [4] = {"urum", "im", 5}, [5] = {"uruf", "imf", 5}, [6] = {"uruv", "imv", 5}, [7] = {"urw", "inz", 4}, [8] = {"uruy", "inz", 5}, [9] = {"uruny", "iny", 6}, [10] = {"urur", "ind", 5}, [11] = {"urun", "in", 5}, [12] = {"uru", "in", 4}},--fails for urū > inzū
["11/6"] = {[1] = {"uru","ama", 4}},
["11/14"] = {[1] = {"uru","ubu", 4}, [2] = {"urw", "ubw", 4}},
["12"] = {[1] = {"aka", "utu", 4}, [2] = {"aga", "udu", 4}, [3] = {"ak", "utw", 3}, [4] = {"ka", "tu", 3}, [5] = {"ga", "du", 3}, [6] = {"k", "tw", 2}},
["14"] = {[1] = { "ubu", "ama", 4}, [2] = {"ubw", "am", 4}, [3] = {"ub", "am", 3}, [4] = {"bu", "ma", 3}, [5] = {"bw", "m", 3}, [6] = {"b","m", 2}},
["15"] = {[1] = {"uku", "ama", 4}, [2] = {"ugu", "ama", 4}, [3] = {"ukw", "am", 4}, [5] = {"ugw", "am", 4}, [6] = {"ku", "ma", 3}, [7] = {"gu", "ma", 3}, [8] = {"kw", "m", 3}, [9] = {"gw", "m", 3}}
}
function export.noun(frame)
local params = {
[1] = {},
[2] = {},
[3] = {}
}
local args = require("Module:parameters").process(frame:getParent().args, params)
local categories = {}
local head
if not args[1] then
args[1] = mw.loadData("Module:headword/data").pagename
-- l10n: 提示文字與請求分類中文化
head = mw.loadData("Module:headword/data").pagename:gsub("^-", "") .. "<sup title=\"缺少音調\">?</sup>"
table.insert(categories, "缺少音調的" .. lang:getCanonicalName() .. "名詞")
end
local class
if args[2] then
class = mw.text.split(args[2], "%/")[1]
else
class = "?"
end
local data = {lang = lang, pos_category = "名詞", categories = categories, heads = {head or args[1]}, genders = {"c" .. class}, inflections = {}} -- l10n
if class ~= "?" then
table.insert(data.categories, lang:getCanonicalName() .. "第" .. class .. "類名詞") -- l10n
end
if args[3] ~= "-" and plural_classes[args[2]] then
-- l10n: 屈折標籤中文化
local infl_plural = {label = "複數", accel = {form = "p", gender = plural_classes[args[2]]}, request = true}
-- If no plural was provided, generate one
if not args[3] then
local singular = args[1] and require("Module:links").remove_links(args[1]) or mw.loadData("Module:headword/data").pagename
args[3] = export.generate_plural(singular, args[2])
end
table.insert(infl_plural, {term = args[3], genders = {plural_classes[args[2]]}})
table.insert(data.inflections, infl_plural)
end
return require("Module:headword").full_headword(data)
end
function export.verb(frame)
local params = {
[1] = {},
[2] = {list = true},
["inf"] = {}
}
local args = require("Module:parameters").process(frame:getParent().args, params)
local categories = {}
local inflections = {}
local inf_forms = {label = "不定式"} -- l10n
local head
if not args[1] then
args[1] = mw.loadData("Module:headword/data").pagename
-- l10n: 提示文字與請求分類中文化
head = "-" .. mw.loadData("Module:headword/data").pagename:gsub("^-", "") .. "<sup title=\"缺少音調\">?</sup>"
table.insert(categories, "缺少音調的" .. lang:getCanonicalName() .. "動詞")
else
head = "-" .. args[1]
end
-- Generate infinitive form
local inf_prefix = "ku"
local root = mw.ustring.toNFD(args[1])
local infinitive
if args["inf"] then
table.insert(inf_forms, args["inf"])
elseif mw.ustring.find(root, "^[cfhkpstCFHKPST]") then
inf_prefix = "gu"
infinitive = mw.ustring.toNFC(inf_prefix .. root)
table.insert(inf_forms, infinitive)
elseif mw.ustring.find(root, "^[aeiouAEIOU]") then
inf_prefix = "kw"
local vowel = mw.ustring.sub(root, 1, 1)
if mw.ustring.sub(root, 2, 2) == ACUTE then
infinitive = mw.ustring.toNFC(inf_prefix .. mw.ustring.gsub(root, ACUTE, CIRCUMFLEX, 1))
elseif mw.ustring.sub(root, 2, 2) == CIRCUMFLEX or mw.ustring.sub(root, 2, 2) == MACRON then
infinitive = mw.ustring.toNFC(inf_prefix .. root)
else
infinitive = mw.ustring.toNFC(inf_prefix .. vowel .. MACRON .. mw.ustring.sub(root, 2))
end
if mw.ustring.lower(vowel) == "o" or mw.ustring.lower(vowel) == "u" then
-- l10n: 變體限定詞中文化
table.insert(inf_forms, {term = mw.ustring.gsub(infinitive, "^kw", "k"), q = {"盧旺達"}})
table.insert(inf_forms, {term = infinitive, q = {"基隆迪"}})
else
table.insert(inf_forms, infinitive)
end
else
infinitive = mw.ustring.toNFC(inf_prefix .. root)
table.insert(inf_forms, infinitive)
end
table.insert(inflections, inf_forms)
-- perfective stem
local perf_stem = nil
if args[2][1] then
local perf_forms = {label = "完成體"} -- l10n
for _, form in ipairs(args[2]) do
perf_stem = "-" .. form
table.insert(perf_forms, {term = perf_stem, nolink = true})
end
table.insert(inflections, perf_forms)
else -- generate perfective stem if possible
local perf_forms = {label = "完成體"} -- l10n
root = mw.ustring.gsub(root, "a$", "") -- remove final vowel -a
local is_generated = false
if mw.ustring.find(root, "[aeiou]") then -- exclude -CV- roots
is_generated = true
-- regular changes
if mw.ustring.find(root, "c$") then
perf_stem = "-" .. mw.ustring.gsub(root, "c$", "she")
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "d$") then
perf_stem = "-" .. mw.ustring.gsub(root, "d$", "ze")
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "g$") then
perf_stem = "-" .. mw.ustring.gsub(root, "g$", "ze")
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "nk$") then
perf_stem = "-" .. mw.ustring.gsub(root, "nk$", "nse")
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "k$") then
perf_stem = "-" .. mw.ustring.gsub(root, "k$", "tse")
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif root == "fat" then -- one irregular case (gufata)
perf_stem = "-fashe"
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "t$") then
perf_stem = "-" .. mw.ustring.gsub(root, "t$", "se")
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "[^n]y$") then
perf_stem = "-" .. root .. "e"
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "m$") then
perf_stem = "-" .. root .. "ye"
table.insert(perf_forms, {term = perf_stem, nolink = true})
elseif mw.ustring.find(root, "b$") then
local perf_stem_rw = "-" .. root .. "ye"
table.insert(perf_forms, {term = perf_stem_rw, q = {"盧旺達"}, nolink = true}) -- l10n
local perf_stem_rn = "-" .. mw.ustring.gsub(root, "b$", "vye")
table.insert(perf_forms, {term = perf_stem_rn, q = {"基隆迪"}, nolink = true}) -- l10n
elseif mw.ustring.find(root, "[^s]h$") then
local perf_stem_rw = "-" .. mw.ustring.gsub(root, "h$", "shye")
table.insert(perf_forms, {term = perf_stem_rw, q = {"盧旺達"}, nolink = true}) -- l10n
local perf_stem_rn = "-" .. mw.ustring.gsub(root, "h$", "she")
table.insert(perf_forms, {term = perf_stem_rn, q = {"基隆迪"}, nolink = true}) -- l10n
else
is_generated = false
end
end
if not is_generated then
table.insert(perf_forms, {term = nil, nolink = true})
end
table.insert(inflections, perf_forms)
end
local data = {lang = lang, pos_category = "動詞", categories = categories, heads = {head}, inflections = inflections} -- l10n
return require("Module:headword").full_headword(data)
end
function match_case(string1, string2)
local c1 = mw.ustring.sub(string1, 1, 1)
local c2 = mw.ustring.sub(string2, 1, 1)
if (mw.ustring.lower(c1) == c1) then
return mw.ustring.lower(c2) .. mw.ustring.sub(string2, 2)
else
return mw.ustring.upper(c2) .. mw.ustring.sub(string2, 2)
end
end
function export.generate_plural(singular, class)
if plural_rules[class] then
singular = mw.ustring.toNFD(singular)
for k, v in ipairs(plural_rules[class]) do
if mw.ustring.find(mw.ustring.lower(singular), "^" .. v[1]) then
local plural = match_case(singular, v[2] .. mw.ustring.sub(singular, v[3]))
return mw.ustring.toNFC(plural)
end
end
end
end
return export
hdt5tsvuuwoh6zwb9af6ia6ocxkwrmt
Module:Nl-headword
828
1455258
9909089
9883377
2026-09-29T07:27:21Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909089
Scribunto
text/plain
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require_when_needed = require("Module:require when needed")
local m_table = require("Module:table")
local lang = require("Module:languages").getByCode("nl")
local langname = lang:getCanonicalName()
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
-- local romut_module = "Module:romance utilities"
local nl_common_module = "Module:nl-common"
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local m_string_utilities = require_when_needed("Module:string utilities")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local boolean_param = {type = "boolean"}
local list_param = {list = true, disallow_holes = true}
local insert = table.insert
local function track(page)
require("Module:debug").track("nl-headword/" .. page)
return true
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local params = {
["head"] = list_param,
["id"] = true,
-- ["splithyph"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {alias_of = "nolink"},
["json"] = boolean_param,
["pagename"] = true, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolink then
if not heads[1] then
heads = {pagename}
end
else
--local romut = require(romut_module)
--local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph, no_split_apostrophe_words)
--if not heads[1] then
-- heads = {auto_linked_head}
--else
for i, head in ipairs(heads) do
--if head:find("^~") then
-- head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
-- heads[i] = head
--end
if head == auto_linked_head then
track("redundant-head")
end
end
--end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = not user_specified_heads[1],
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
checkredlinks = pos_functions[poscat] and pos_functions[poscat].redlink_pos or true,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = true
local singular_poscat = poscat
insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
----------------------------------------------- Utilities --------------------------------------------
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture
-- replace expression.
lemma = m_string_utilities.replacement_escape(lemma)
return (term:gsub("#", lemma)) -- discard second retval
end
local function frob_term_with_hash(term, lemma)
if term:find("#") then
term = replace_hash_with_lemma(term, lemma)
end
return term
end
local function parse_term_list_with_modifiers(data, paramname, list)
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = paramname,
forms = list,
splitchar = ",",
include_mods = {"g"},
frob = function(term)
return frob_term_with_hash(term, data.pagename)
end,
}
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 = ",",
include_mods = {"g"},
frob = function(term)
return frob_term_with_hash(term, data.pagename)
end,
label = label,
accel = accel and {form = accel} or nil,
}
end
-- Insert the parsed inflections in `infls` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
----------------------------------------------- Adjectives, Adverbs --------------------------------------------
-- Display additional inflection information for an adjective
pos_functions["形容詞"] = {
params = {
inv = boolean_param,
pred = boolean_param,
[1] = {list = "comp", disallow_holes = true},
[2] = {list = "sup", disallow_holes = true},
},
func = function(args, data)
local pagename = data.pagename
local mode
if args.inv then
mode = "inv"
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, "荷蘭語無屈折形容詞")
elseif args.pred then
mode = "pred"
insert(data.inflections, {label = "僅作[[表語]]"})
insert(data.categories, "僅作表語的荷蘭語形容詞")
end
if args[1][1] == "-" then
insert(data.inflections, {label = "無" .. glossary_link("比較級")})
else
-- Gather parameters
local comparatives = parse_term_list_with_modifiers(data, {"1", "comp"}, args[1])
local superlatives = parse_term_list_with_modifiers(data, {"2", "sup"}, args[2])
-- Generate forms if none were given
if not comparatives[1] then
if mode == "inv" or mode == "pred" then
comparatives = {{term = "peri"}}
else
comparatives = {{term = require("Module:nl-adjectives").make_comparative(pagename)}}
end
end
if not superlatives[1] then
if mode == "inv" or mode == "pred" then
superlatives = {{term = "peri"}}
else
-- Add preferred periphrastic superlative, if necessary
if
pagename:find("[iï]de$") or pagename:find("[^eio]e$") or
pagename:find("s$") or pagename:find("sch$") or pagename:find("x$") or
pagename:find("sd$") or pagename:find("st$") or pagename:find("scht$") or pagename:find("sk$") then
superlatives = {{term = "peri"}}
end
insert(superlatives, {term = require("Module:nl-adjectives").make_superlative(pagename)})
end
end
-- Replace "peri" with phrase
for _, val in ipairs(comparatives) do
if val.term == "peri" then val.term = "[[meer]] " .. pagename end
end
for _, val in ipairs(superlatives) do
if val.term == "peri" then val.term = "[[meest]] " .. pagename end
end
insert_inflection(data, comparatives, "<<比較級>>")
insert_inflection(data, superlatives, "<<最高級>>")
end
end
}
-- Display additional inflection information for an adverb
pos_functions["副詞"] = {
params = {
[1] = {list = "comp", disallow_holes = true},
[2] = {list = "sup", disallow_holes = true},
},
func = function(args, data)
local pagename = data.pagename
if args[1][1] then
-- Gather parameters
local comparatives = parse_term_list_with_modifiers(data, {"1", "comp"}, args[1])
local superlatives = parse_term_list_with_modifiers(data, {"2", "sup"}, args[2])
if not superlatives[1] then
superlatives = {{term = pagename .. "st"}}
end
insert_inflection(data, comparatives, "<<比較級>>")
insert_inflection(data, superlatives, "<<最高級>>")
end
end
}
----------------------------------------------- Nouns --------------------------------------------
local allowed_genders = m_table.listToSet { "c", "p", "m", "f", "n", "?", "mf", "mfequiv", "mfbysense" }
-- Display information for a noun's gender
-- This is separate so that it can also be used for proper nouns
local function noun_gender(args, data)
-- Validate genders.
local saw_f, saw_m, saw_f_without_m, saw_m_without_f, saw_p, saw_non_p
for _, gspec in ipairs(args[1]) do
local g = gspec.spec
if not allowed_genders[g] then
error("Unrecognized " .. langname .. " gender: " .. g)
end
if g == "f" then
saw_f = true
if not saw_m then
saw_f_without_m = true
end
end
if g == "m" then
saw_m = true
if not saw_f then
saw_m_without_f = true
end
end
if g == "p" then
saw_p = true
elseif g ~= "?" then
saw_non_p = true
end
end
data.genders = args[1]
-- Most nouns that are listed as f+m should really have only f.
if saw_f_without_m and saw_m then
insert(data.categories, "有f+m性別的" .. langname .. "名詞")
end
-- Some of these nouns may be like m+f nouns but some are legitimately either masculine or feminine.
if saw_m_without_f and saw_f then
insert(data.categories, "有m+f性別的" .. langname .. "名詞")
end
return saw_p, saw_non_p
end
local function generate_plurals(pagename)
local m_common = require(nl_common_module)
local generated = {}
generated["-s"] = pagename .. "s"
generated["-'s"] = pagename .. "'s"
generated["-'en"] = pagename .. "'en"
local stem_FF = m_common.add_e(pagename, false, false)
local stem_TF = m_common.add_e(pagename, true, false)
local stem_FT = m_common.add_e(pagename, false, true)
generated["-es"] = stem_FF .. "s"
generated["-@es"] = stem_TF .. "s"
generated["-:es"] = stem_FT .. "s"
generated["-en"] = stem_FF .. "n"
generated["-@en"] = stem_TF .. "n"
generated["-:en"] = stem_FT .. "n"
generated["-eren"] = m_common.add_e(pagename .. (pagename:find("n$") and "d" or ""), false, false) .. "ren"
generated["-:eren"] = stem_FT .. "ren"
if pagename:find("f$") then
local stem = pagename:gsub("f$", "v")
local stem_FF = m_common.add_e(stem, false, false)
local stem_TF = m_common.add_e(stem, true, false)
local stem_FT = m_common.add_e(stem, false, true)
generated["-ves"] = stem_FF .. "s"
generated["-@ves"] = stem_TF .. "s"
generated["-:ves"] = stem_FT .. "s"
generated["-ven"] = stem_FF .. "n"
generated["-@ven"] = stem_TF .. "n"
generated["-:ven"] = stem_FT .. "n"
generated["-veren"] = stem_FF .. "ren"
generated["-:veren"] = stem_FT .. "ren"
elseif pagename:find("s$") then
local stem = pagename:gsub("s$", "z")
local stem_FF = m_common.add_e(stem, false, false)
local stem_TF = m_common.add_e(stem, true, false)
local stem_FT = m_common.add_e(stem, false, true)
generated["-zes"] = stem_FF .. "s"
generated["-@zes"] = stem_TF .. "s"
generated["-:zes"] = stem_FT .. "s"
generated["-zen"] = stem_FF .. "n"
generated["-@zen"] = stem_TF .. "n"
generated["-:zen"] = stem_FT .. "n"
generated["-zeren"] = stem_FF .. "ren"
generated["-:zeren"] = stem_FT .. "ren"
elseif pagename:find("heid$") then
generated["-heden"] = pagename:gsub("heid$", "heden")
end
return generated
end
local function generate_diminutive(pagename, dim)
local m_common = require(nl_common_module)
if dim == "+" then
dim = m_common.default_dim(pagename)
elseif dim == "++" then
dim = m_common.default_dim(pagename, "final multisyllable stress")
elseif dim == "++/+" then
dim = m_common.default_dim(pagename, false, "modifier final multisyllable stress")
elseif dim == "++/++" then
dim = m_common.default_dim(pagename, "final multisyllable stress", "modifier final multisyllable stress")
elseif dim == "+first" then
dim = m_common.default_dim(pagename, false, false, "first only")
elseif dim == "++first" then
dim = m_common.default_dim(pagename, "final multisyllable stress", false, "first only")
elseif dim:sub(1, 1) == "-" then
dim = pagename .. dim:sub(2)
end
return dim
end
pos_functions["專有名詞"] = {
params = {
[1] = {list = "g", type = "genders", flatten = true, default = "?", disallow_holes = true},
[2] = {list = "pl", disallow_holes = true},
adj = list_param,
mdem = list_param,
fdem = list_param,
},
func = function(args, data)
local saw_p, saw_non_p = noun_gender(args, data)
local plurals = parse_term_list_with_modifiers(data, {"2", "pl"}, args[2])
local adjectives = parse_term_list_with_modifiers(data, "adj", args["adj"])
local mdems = parse_term_list_with_modifiers(data, "mdem", args["mdem"])
local fdems = parse_term_list_with_modifiers(data, "fdem", args["fdem"])
local nm = #mdems
local nf = #fdems
local demonyms = {label = "居民稱謂詞"}
-- plural for certain words like [[Amerika]]
insert_inflection(data, plurals, "複數", "p")
--adjective for toponyms
insert_inflection(data, adjectives, "形容詞")
--demonyms for toponyms
if nm + nf > 0 then
for i, m in ipairs(mdems) do
if not m.genders then
m.genders = {"m"}
end
demonyms[i] = m
end
for i, f in ipairs(fdems) do
if not f.genders then
f.genders = {"f"}
end
demonyms[i + nm] = f
end
insert(data.inflections, demonyms)
end
end
}
local function process_plurals(data, plurals, plural_only)
local pagename = data.pagename
if plural_only then
if plurals[1] then
error("Can't specify plurals of plurale tantum noun")
end
insert(data.inflections, {label = glossary_link("唯複")})
elseif plurals[1] and plurals[1].term == "-" then
insert(data.inflections, {label = glossary_link("不可數")})
insert(data.categories, langname .. "不可數名詞")
else
local generated = generate_plurals(pagename)
-- Process the plural forms
for i, pobj in ipairs(plurals) do
local p = pobj.term
-- Is this a shortcut form?
if p:sub(1,1) == "-" then
if not generated[p] then
error("The shortcut plural " .. p .. " could not be generated.")
end
if p:sub(-2) == "es" then
insert(data.categories, "複數以-es結尾的荷蘭語名詞")
elseif p:sub(-1) == "s" then
insert(data.categories, "複數以-s結尾的荷蘭語名詞")
elseif p:sub(-4) == "eren" then
insert(data.categories, "複數以-eren結尾的荷蘭語名詞")
else
insert(data.categories, "複數以-en結尾的荷蘭語名詞")
end
if p:sub(2,2) == ":" then
insert(data.categories, "複數有加長元音的荷蘭語名詞")
end
p = generated[p]
-- Not a shortcut form, but the plural form specified directly.
else
for _, g in pairs(generated) do
if g == p then
track("plural-matches-generated-form")
break
end
end
if not pagename:find("[ -]") then
if p == pagename then
insert(data.categories, "荷蘭語無屈折名詞")
elseif
p == pagename .. "den" or p == pagename:gsub("ee$", "eden") or
p == pagename .. "des" or p == pagename:gsub("ee$", "edes") then
insert(data.categories, "複數以-den結尾的荷蘭語名詞")
elseif p == pagename:gsub("([ao])$", "%1%1ien") or p == pagename:gsub("oe$", "oeien") then
insert(data.categories, "複數有滑音元音的荷蘭語名詞")
elseif p == pagename:gsub("y$", "ies") then
insert(data.categories, "有英語複數的荷蘭語名詞")
elseif
p == pagename:gsub("a$", "ae") or
p == pagename:gsub("[ei]x$", "ices") or
p == pagename:gsub("is$", "es") or
p == pagename:gsub("men$", "mina") or
p == pagename:gsub("ns$", "ntia") or
p == pagename:gsub("o$", "ones") or
p == pagename:gsub("o$", "onen") or
p == pagename:gsub("s$", "tes") or
p == pagename:gsub("us$", "era") or
p == mw.ustring.gsub(pagename, "[uü]s$", "i") or
p == mw.ustring.gsub(pagename, "[uü]m$", "a") or
p == pagename:gsub("x$", "ges") then
insert(data.categories, "有拉丁語複數的荷蘭語名詞")
elseif
p == pagename:gsub("os$", "oi") or
p == mw.ustring.gsub(pagename, "[oö]n$", "a") or
p == pagename:gsub("a$", "ata") then
insert(data.categories, "有希臘語複數的荷蘭語名詞")
else
insert(data.categories, "荷蘭語不規則名詞")
end
end
end
pobj.term = p
end
-- Add the plural forms
m_headword_utilities.insert_inflection {
headdata = data,
terms = plurals,
label = "複數",
accel = {form = "p"},
request = true,
}
end
end
local function do_noun_ancillary_inflections(data, args)
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
parse_and_insert_noun_inflection("f", "陰性")
parse_and_insert_noun_inflection("m", "陽性")
end
-- Display additional inflection information for a noun
pos_functions["名詞"] = {
params = {
[1] = {list = "g", type = "genders", flatten = true, default = "?", disallow_holes = true},
[2] = {list = "pl", disallow_holes = true},
[3] = {list = "dim", disallow_holes = true},
["f"] = list_param,
["m"] = list_param,
},
func = function(args, data)
local pagename = data.pagename
local saw_p, saw_non_p = noun_gender(args, data)
local plurals = parse_term_list_with_modifiers(data, {"2", "pl"}, args[2])
local diminutives = parse_term_list_with_modifiers(data, {"3", "dim"}, args[3])
process_plurals(data, plurals, saw_p and not saw_non_p)
if diminutives[1] and diminutives[1].term == "-" then
-- do nothing
else
-- Process the diminutive forms
for _, dimobj in ipairs(diminutives) do
dimobj.term = generate_diminutive(pagename, dimobj.term)
if not dimobj.genders then
dimobj.genders = {"n"}
end
end
end
-- Add the diminutive forms
m_headword_utilities.insert_inflection {
headdata = data,
terms = diminutives,
label = "<<指小>>",
accel = {form = "指小"},
request = true,
}
do_noun_ancillary_inflections(data, args)
end
}
-- Display additional inflection information for a diminutive noun
pos_functions["指小名詞"] = {
params = {
[1] = {list = "pl", disallow_holes = true},
},
func = function(args, data)
local plurals = parse_term_list_with_modifiers(data, {"1", "pl"}, args[1])
if plurals[1] and plurals[1].term == "p" then
if m_headword_utilities.termobj_has_decorations(plurals[1]) then
error("Can't specify decorations with 'p' for plural-only diminutive noun")
elseif plurals[2] then
error("Can't specify plurals of plurale tantum noun")
end
data.genders = {"p"}
process_plurals(data, {}, "plural only")
else
data.genders = {"n"}
if not plurals[1] then
plurals = {{term = "-s"}}
end
process_plurals(data, plurals)
end
end,
redlink_pos = "名詞",
}
-- Display additional inflection information for diminutiva tantum nouns ({{nl-noun-dim-tant}}).
pos_functions["唯小名詞"] = {
params = {
[1] = {list = "pl", disallow_holes = true},
["f"] = {list = true},
["m"] = {list = true},
},
func = function(args, data)
insert(data.categories, "荷蘭語唯小名詞")
data.genders = {"n"}
local plurals = parse_term_list_with_modifiers(data, {"1", "pl"}, args[1])
if not plurals[1] then
plurals = {{term = "-s"}}
end
process_plurals(data, plurals)
do_noun_ancillary_inflections(data, args)
end,
pos_category = "名詞",
}
pos_functions["過去分詞"] = {
params = {
[1] = {},
},
func = function(args, data)
if args[1] == "-" then
insert(data.inflections, {label = "不用作形容詞"})
insert(data.categories, "不用作形容詞的荷蘭語過去分詞")
end
end,
redlink_pos = "分詞",
}
----------------------------------------------- Verbs --------------------------------------------
pos_functions["動詞"] = {
params = {
[1] = {},
},
func = function(args, data)
if args[1] == "-" then
insert(data.inflections, {label = "無屈折"})
insert(data.categories, "荷蘭語無屈折動詞")
end
end
}
return export
4okovqs9b04b6pi42eudho1nsv4dspj
Module:Ca-headword
828
1480791
9909049
9882746
2026-09-29T07:24:47Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909049
Scribunto
text/plain
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local com = require("Module:ca-common")
local ca_IPA_module = "Module:ca-IPA"
local ca_verb_module = "Module:ca-verb"
local decorations_module = "Module:decorations"
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local romut_module = "Module:romance utilities"
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local m_string_utilities = require_when_needed("Module:string utilities")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local lang = require("Module:languages").getByCode("ca")
local langname = lang:getCanonicalName()
local list_to_text = mw.text.listToText
local insert = table.insert
local concat = table.concat
local rfind = m_string_utilities.find
local rmatch = m_string_utilities.match
local rsplit = m_string_utilities.split
local usub = m_string_utilities.sub
local rsub = com.rsub
local function track(page)
require("Module:debug/track")("ca-headword/" .. page)
return true
end
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
-----------------------------------------------------------------------------------------
-- Main entry point --
-----------------------------------------------------------------------------------------
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local params = {
["head"] = list_param,
["id"] = true,
["splithyph"] = boolean_param,
["nolinkhead"] = boolean_param,
["json"] = boolean_param,
["pagename"] = true, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(frame:getParent().args, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolinkhead then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
if head == auto_linked_head then
track("redundant-head")
end
end
end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
checkredlinks = pos_functions[poscat] and pos_functions[poscat].redlink_pos or true,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = true
local singular_poscat = poscat
insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if args.json then
return require("Module:JSON").toJSON(data)
end
local post_note = data.post_note and ";" .. data.post_note or ""
return require("Module:headword").full_headword(data) .. post_note
end
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture replace
-- expression.
lemma = lemma:gsub("%%", "%%%%")
-- Assign to a variable to discard second return value.
term = term:gsub("#", lemma)
return term
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
-- Insert default plurals generated when a given plural had the value of + and default plurals were fetched as a result.
-- `plobj` is the parsed object whose `term` field is "+". `defpls` is the list of default plurals. `dest` is the list
-- into which the plurals are inserted (which inherit their decorations from `plobj`).
local function insert_defpls(defpls, plobj, dest)
if not defpls then
-- Happens e.g. with [[S.A.]] where the default plural algorithm returns nothing.
return
end
if #defpls == 1 then
plobj.term = defpls[1]
insert(dest, plobj)
else
for _, defpl in ipairs(defpls) do
local newplobj = m_table.shallowCopy(plobj)
newplobj.term = defpl
insert(dest, newplobj)
end
end
end
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
local function do_adjective(args, data, is_superlative)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
-- Use "participle" not "past participle" for categories such as 'invariable paticiples'
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = m_en_utilities.singularize(category_plpos)
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
insert(indicators, "'" .. indic .. "'")
end
table.sort(indicators)
error("Special inflection indicator beginning can only be " ..
list_to_text(indicators) .. ": " .. args.sp)
end
end
local lemma = data.pagename
local function fetch_inflections(field)
local retval = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
}
if not retval[1] then
return {{term = "+"}}
end
return retval
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if args.f[1] == "ind" or args.f[1] == "inv" then
-- invariable adjective
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
if args.sp or args.f[2] or args.pl[1] or args.mpl[1] or args.fpl[1] then
error("Can't specify inflections with an invariable " .. category_pos)
end
elseif args.fonly then
-- feminine-only
if args.f[1] then
error("Can't specify explicit feminines with feminine-only " .. category_pos)
end
if args.pl[1] then
error("Can't specify explicit plurals with feminine-only " .. category_pos .. ", use fpl=")
end
if args.mpl[1] then
error("Can't specify explicit masculine plurals with feminine-only " .. category_pos)
end
local argsfpl = fetch_inflections("fpl")
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
-- Generate default feminine plural.
local defpls = com.make_plural(lemma, "f", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
insert_defpls(defpls, fpl, feminine_plurals)
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
insert(feminine_plurals, fpl)
end
end
insert(data.inflections, {label = "僅陰性"})
insert_inflection(feminine_plurals, "陰性複數", "f|p")
else
-- Gather feminines.
for _, f in ipairs(fetch_inflections("f")) do
if f.term == "mf" then
f.term = lemma
elseif f.term == "+" then
-- Generate default feminine.
f.term = com.make_feminine(lemma, args.sp)
else
f.term = replace_hash_with_lemma(f.term, lemma)
end
insert(feminines, f)
end
local fem_like_lemma = #feminines == 1 and feminines[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(feminines[1])
if fem_like_lemma then
insert(data.categories, langname .. "兩性" .. category_plpos)
end
local mpl_field = "mpl"
local fpl_field = "fpl"
if args.pl[1] then
if args.mpl[1] or args.fpl[1] then
error("Can't specify both pl= and mpl=/fpl=")
end
mpl_field = "pl"
fpl_field = "pl"
end
local argsmpl = fetch_inflections(mpl_field)
local argsfpl = fetch_inflections(fpl_field)
for _, mpl in ipairs(argsmpl) do
if mpl.term == "+" then
-- Generate default masculine plural.
local defpls
-- First, some special hacks based on the feminine singular.
if not fem_like_lemma and not args.sp and not lemma:find(" ") then
for _, f in ipairs(feminines) do
if f.term:find("ssa$") then
-- If the feminine ends in -ssa, assume that the -ss- is also in the
-- masculine plural form
defpls = {rsub(f.term, "a$", "os")}
break
elseif f.term == lemma .. "na" then
defpls = {lemma .. "ns"}
break
elseif lemma:find("ig$") and f.term:find("ja$") then
-- Adjectives in -ig have two masculine plural forms, one derived from
-- the m.sg. and the other derived from the f.sg.
defpls = {lemma .. "s", rsub(f.term, "ja$", "jos")}
break
end
end
end
defpls = defpls or com.make_plural(lemma, "m", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
insert_defpls(defpls, mpl, masculine_plurals)
else
mpl.term = replace_hash_with_lemma(mpl.term, lemma)
insert(masculine_plurals, mpl)
end
end
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
-- First, some special hacks based on the feminine singular.
if fem_like_lemma and not args.sp and not lemma:find(" ") and lemma:find("[çx]$") then
-- Adjectives ending in -ç or -x behave as mf-type in the singular, but
-- regular type in the plural.
local defpls = com.make_plural(lemma .. "a", "f")
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "a'")
end
insert_defpls(defpls, fpl, feminine_plurals)
else
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local defpls = com.make_plural(f.term, "f", args.sp)
if not defpls then
error("Unable to generate default plural of '" .. f.term .. "'")
end
for _, defpl in ipairs(defpls) do
local fplobj = m_table.shallowCopy(fpl)
fplobj.term = defpl
m_headword_utilities.combine_termobj_decorations(fplobj, f)
insert(feminine_plurals, fplobj)
end
end
end
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
insert(feminine_plurals, fpl)
end
end
local fem_pl_like_masc_pl = masculine_plurals[1] and feminine_plurals[1] and
m_table.deepEquals(masculine_plurals, feminine_plurals)
local masc_pl_like_lemma = #masculine_plurals == 1 and masculine_plurals[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(masculine_plurals[1])
if fem_like_lemma and fem_pl_like_masc_pl and masc_pl_like_lemma then
-- actually invariable
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
-- Make sure there are feminines given and not same as lemma.
if not fem_like_lemma then
insert_inflection(feminines, "陰性", "f|s")
elseif args.gneut then
data.genders = {"gneut"}
else
data.genders = {"mf"}
end
if fem_pl_like_masc_pl then
if args.gneut then
insert_inflection(masculine_plurals, "複數", "p")
else
insert_inflection(masculine_plurals, "陽性和陰性複數", "p")
end
else
insert_inflection(masculine_plurals, "陽性複數", "m|p")
insert_inflection(feminine_plurals, "陰性複數", "f|p")
end
end
end
parse_and_insert_inflection(data, args, "comp", "比較級")
parse_and_insert_inflection(data, args, "sup", "最高級")
parse_and_insert_inflection(data, args, "dim", "指小")
parse_and_insert_inflection(data, args, "aug", "指大")
if args.irreg and is_superlative then
insert(data.categories, langname .. "不規則" .. category_plpos .. "最高級")
end
end
local function get_adjective_params(adjtype)
local params = {
["sp"] = true, -- special indicator: "first", "first-last", etc.
["f"] = list_param, --feminine form(s)
[1] = {alias_of = "f", list = false},
["pl"] = list_param, --plural override(s)
["mpl"] = list_param, --masculine plural override(s)
["fpl"] = list_param, --feminine plural override(s)
}
if adjtype == "base" then
params["comp"] = list_param --comparative(s)
params["sup"] = list_param --superlative(s)
params["dim"] = list_param --diminutive(s)
params["aug"] = list_param --augmentative(s)
params["fonly"] = boolean_param -- feminine only
params["hascomp"] = {} -- has comparative
end
if adjtype == "sup" then
params["irreg"] = boolean_param
end
return params
end
-- Display additional inflection information for an adjective
pos_functions["形容詞"] = {
params = get_adjective_params("base"),
func = do_adjective,
}
pos_functions["過去分詞"] = {
params = get_adjective_params("part"),
func = do_adjective,
redlink_pos = "分詞",
}
pos_functions["限定詞"] = {
params = get_adjective_params("det"),
func = do_adjective,
}
pos_functions["代詞"] = {
params = get_adjective_params("pron"),
func = do_adjective,
}
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet(
{"m", "f", "mf", "mfbysense", "mfequiv", "gneut", "n", "m-p", "f-p", "mf-p", "mfbysense-p", "mfequiv-p", "gneut-p", "n-p", "?", "?-p"}
)
local function validate_genders(genders)
for _, g in ipairs(genders) do
if type(g) == "table" then
g = g.spec
end
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
end
end
local function do_noun(args, data, is_proper)
local is_plurale_tantum = false
local has_singular = false
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = m_en_utilities.singularize(category_plpos)
validate_genders(args[1])
data.genders = args[1]
local saw_m = false
local saw_f = false
local saw_gneut = false
local gender_for_irreg_ending, gender_for_default_plural
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if type(g) == "table" then
g = g.spec
end
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
if g == "gneut" then
saw_gneut = true
end
end
end
if saw_m and saw_f then
gender_for_irreg_ending = "mf"
elseif saw_f then
gender_for_irreg_ending = "f"
else
gender_for_irreg_ending = "m"
end
gender_for_default_plural =
saw_gneut and "gneut" or gender_for_irreg_ending == "mf" and "m" or gender_for_irreg_ending
local lemma = data.pagename
-- Plural
local plurals = {}
local function insert_noun_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if is_plurale_tantum and not has_singular then
if args[2][1] then
error("Can't specify plurals of plurale tantum " .. category_pos)
end
insert(data.inflections, {label = glossary_link("唯複")})
else
plurals = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "pl"},
forms = args[2],
splitchar = ",",
}
-- Check for special plural signals
local mode = nil
local pl1 = plurals[1]
if pl1 and #pl1.term == 1 then
mode = pl1.term
if mode == "?" or mode == "!" or mode == "-" or mode == "~" then
pl1.term = nil
if next(pl1) then
error(("Can't specify inline modifiers with plural code '%s'"):format(mode))
end
table.remove(plurals, 1) -- Remove the mode parameter
elseif mode ~= "+" and mode ~= "#" then
error(("Unexpected plural code '%s'"):format(mode))
end
end
if is_plurale_tantum then
-- both singular and plural
insert(data.inflections, {label = "在變體中有時" .. glossary_link("唯複")})
end
if mode == "?" then
-- Plural is unknown
insert(data.categories, "有未知或不確定複數的" .. langname .. category_plpos)
elseif mode == "!" then
-- Plural is not attested
insert(data.inflections, {label = "複數未紀錄"})
insert(data.categories, "有無紀錄複數的" .. langname .. category_plpos)
if plurals[1] then
error("Can't specify any plurals along with unattested plural code '!'")
end
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
insert(data.categories, langname .. "不可數" .. category_plpos)
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
insert(data.categories, langname .. "可數" .. category_plpos)
else
insert(data.inflections, {label = glossary_link("不可數")})
end
else
-- Countable or mixed countable/uncountable
if not plurals[1] and not is_proper then
plurals[1] = {term = "+"}
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
insert(data.inflections, {label = glossary_link("可數") .. "和" .. glossary_link("不可數")})
insert(data.categories, langname .. "不可數" .. category_plpos)
insert(data.categories, langname .. "可數" .. category_plpos)
elseif plurals[1] then
-- Countable nouns
insert(data.categories, langname .. "可數" .. category_plpos)
else
-- Uncountable nouns
insert(data.categories, langname .. "不可數" .. category_plpos)
end
end
-- Gather plurals, handling requests for default plurals.
local has_default_or_hash = false
for _, pl in ipairs(plurals) do
if pl.term:find("^%+") or pl.term:find("#") then
has_default_or_hash = true
break
end
end
if has_default_or_hash then
local newpls = {}
for _, pl in ipairs(plurals) do
if pl.term == "+" then
local default_pls = com.make_plural(lemma, gender_for_default_plural)
insert_defpls(default_pls, pl, newpls)
elseif pl.term:find("^%+") then
pl.term = require(romut_module).get_special_indicator(pl.term)
local default_pls = com.make_plural(lemma, gender_for_default_plural, pl.term)
insert_defpls(default_pls, pl, newpls)
else
pl.term = replace_hash_with_lemma(pl.term, lemma)
insert(newpls, pl)
end
end
plurals = newpls
end
local pl1 = plurals[1]
if pl1 and not plurals[2] and pl1.term == lemma then
insert(data.inflections, {label = glossary_link("無屈折"),
q = pl1.q, qq = pl1.qq, l = pl1.l, ll = pl1.ll, refs = pl1.refs
})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
insert_noun_inflection(plurals, "複數", "p")
end
if plurals[2] then
insert(data.categories, "有多個複數的" .. langname .. category_plpos)
end
end
-- Gather masculines/feminines. For each one, generate the corresponding plural. `field` is the name of the field
-- containing the masculine or feminine forms (normally "m" or "f"); `inflect` is a function of one or two arguments
-- to generate the default masculine or feminine from the lemma (the arguments are the lemma and optionally a
-- "special" flag to indicate how to handle multiword lemmas, and the function is normally make_feminine or
-- make_masculine from [[Module:ca-common]]); and `default_plurals` is a list into which the corresponding default
-- plurals of the gathered or generated masculine or feminine forms are stored.
local function handle_mf(field, inflect, default_plurals)
local function call_inflect(special)
if inflect then
-- Generate default feminine.
return inflect(lemma, special)
else
-- FIXME
error("Can't generate default masculine currently")
end
end
local mfs = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
frob = function(term)
if term == "+" then
-- Generate default masculine/feminine.
term = call_inflect()
else
term = replace_hash_with_lemma(term, lemma)
end
local special = require(romut_module).get_special_indicator(term)
if special then
term = call_inflect(special)
end
return term
end
}
for _, mf in ipairs(mfs) do
local mfpls = com.make_plural(mf.term, gender, special)
if mfpls then
for _, mfpl in ipairs(mfpls) do
local plobj = m_table.shallowCopy(mf)
plobj.term = mfpl
-- Add an accelerator for each masculine/feminine plural whose lemma
-- is the corresponding singular, so that the accelerated entry
-- that is generated has a definition that looks like
-- # {{plural of|ca|MFSING}}
plobj.accel = {form = "p", lemma = mf.term}
table.insert(default_plurals, plobj)
end
end
end
return mfs
end
local feminine_plurals = {}
local feminines = handle_mf("f", com.make_feminine, feminine_plurals)
local masculine_plurals = {}
local masculines = handle_mf("m", com.make_masculine, masculine_plurals)
local function handle_mf_plural(mfplfield, default_plurals, singulars)
local mfpl = m_headword_utilities.parse_term_list_with_modifiers {
paramname = mfplfield,
forms = args[mfplfield],
splitchar = ",",
}
local new_mfpls = {}
local saw_plus
for i, mfpl in ipairs(mfpl) do
local accel
if #mfpl == #singulars then
-- If same number of overriding masculine/feminine plurals as singulars, assume each plural goes with
-- the corresponding singular and use each corresponding singular as the lemma in the accelerator. The
-- generated entry will have
-- # {{plural of|ca|SINGULAR}}
-- as the definition.
accel = {form = "p", lemma = singulars[i].term}
else
accel = nil
end
if mfpl.term == "+" then
-- We should never see + twice. If we do, it will lead to problems since we overwrite the values of
-- default_plurals the first time around.
if saw_plus then
error(("Saw + twice when handling %s="):format(mfplfield))
end
saw_plus = true
if not default_plurals[1] then
-- FIXME: Can this happen? Not in corresponding Spanish code and the old Portuguese code tried to
-- handle this condition by generating the default plural from the lemma.
error("Internal error: Something wrong, no generated default m/f plurals at this stage")
end
for _, defpl in ipairs(default_plurals) do
-- defpl is already a table and has an accel field
m_headword_utilities.combine_termobj_decorations(defpl, mfpl)
insert(new_mfpls, defpl)
end
elseif mfpl.term:find("^%+") then
mfpl.term = require(romut_module).get_special_indicator(mfpl.term)
for _, mf in ipairs(singulars) do
local default_mfpls = com.make_plural(mf.term, gender, mfpl.term)
for _, defp in ipairs(default_mfpls) do
local mfplobj = m_table.shallowCopy(mfpl)
mfplobj.term = defp
mfplobj.accel = accel
m_headword_utilities.combine_termobj_decorations(mfplobj, mf)
insert(new_mfpls, mfplobj)
end
end
else
mfpl.accel = accel
mfpl.term = replace_hash_with_lemma(mfpl.term, lemma)
insert(new_mfpls, mfpl)
end
end
return new_mfpls
end
if args.fpl[1] then
-- Override any existing feminine plurals.
feminine_plurals = handle_mf_plural("fpl", feminine_plurals, feminines)
end
if args.mpl[1] then
-- Override any existing masculine plurals.
masculine_plurals = handle_mf_plural("mpl", masculine_plurals, masculines)
end
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
insert_noun_inflection(feminines, "陰性", "f")
insert_noun_inflection(feminine_plurals, "陰性複數")
insert_noun_inflection(masculines, "陽性")
insert_noun_inflection(masculine_plurals, "陽性複數")
parse_and_insert_noun_inflection("dim", "指小")
parse_and_insert_noun_inflection("aug", "指大")
parse_and_insert_noun_inflection("pej", "貶義")
parse_and_insert_noun_inflection("dem", "居民稱謂詞")
parse_and_insert_noun_inflection("fdem", "女性居民稱謂詞")
-- Is this a noun with an unexpected ending (for its gender)?
-- Only check if the term is one word (there are no spaces in the term).
local irreg_gender_lemma = rsub(lemma, " .*", "") -- only look at first word
if (gender_for_irreg_ending == "m" or gender_for_irreg_ending == "mf") and irreg_gender_lemma:find("a$") then
insert(data.categories, "以-a結尾的" .. langname .. "陽性" .. category_plpos)
elseif (gender_for_irreg_ending == "f" or gender_for_irreg_ending == "mf") and not (
irreg_gender_lemma:find("a$") or irreg_gender_lemma:find("ió$") or irreg_gender_lemma:find("tat$") or
irreg_gender_lemma:find("tud$") or irreg_gender_lemma:find("[dt]riu$")) then
insert(data.categories, "沒有陰性詞尾的" .. langname .. "陰性" .. category_plpos)
end
end
local function get_noun_params(is_proper)
return {
[1] = {list = "g", disallow_holes = true, required = not is_proper, default = "?", type = "genders",
flatten = true}, -- gender(s)
[2] = {list = "pl", disallow_holes = true}, --plural override(s)
["f"] = list_param, --feminine form(s)
["m"] = list_param, --masculine form(s)
["fpl"] = list_param, --feminine plural override(s)
["mpl"] = list_param, --masculine plural override(s)
["dim"] = list_param, --diminutive(s)
["aug"] = list_param, --diminutive(s)
["pej"] = list_param, --pejorative(s)
["dem"] = list_param, --demonym(s)
["fdem"] = list_param, --female demonym(s)
}
end
pos_functions["名詞"] = {
params = get_noun_params(),
func = do_noun,
}
pos_functions["專有名詞"] = {
params = get_noun_params("is proper"),
func = function(args, data)
do_noun(args, data, "is proper")
end,
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["動詞"] = {
params = {
[1] = true,
["pres"] = list_param, --present
["pres_qual"] = {list = "pres\1_qual", allow_holes = true},
["pres3s"] = list_param, --third-singular present
["pres3s_qual"] = {list = "pres3s\1_qual", allow_holes = true},
["pret"] = list_param, --preterite
["pret_qual"] = {list = "pret\1_qual", allow_holes = true},
["part"] = list_param, --participle
["part_qual"] = {list = "part\1_qual", allow_holes = true},
["short_part"] = list_param, --short participle
["short_part_qual"] = {list = "short_part\1_qual", allow_holes = true},
["noautolinktext"] = boolean_param,
["noautolinkverb"] = boolean_param,
["attn"] = boolean_param,
["pres_1_sg"] = true, -- accept any ignore old-style param
["past_part"] = true, -- accept any ignore old-style param
["root"] = true, -- FIXME: Implement root-stressed vowel quality
},
func = function(args, data, tracking_categories, frame)
local preses, preses_3s, prets, parts, short_parts
if args.attn then
insert(tracking_categories, "Requests for attention concerning " .. langname)
return
end
local ca_verb = require(ca_verb_module)
local alternant_multiword_spec = ca_verb.do_generate_forms(args, "ca-verb", data.heads[1])
local specforms = alternant_multiword_spec.forms
local function slot_exists(slot)
return specforms[slot] and #specforms[slot] > 0
end
local function do_finite(slot_tense, label_tense)
-- Use pres_3s if it exists and pres_1s doesn't exist (e.g. impersonal verbs); similarly for pres_3p (only3p verbs);
-- but fall back to pres_1s if neither pres_1s nor pres_3s nor pres_3p exist (e.g. [[empedernir]]).
local has_1s = slot_exists(slot_tense .. "_1s")
local has_3s = slot_exists(slot_tense .. "_3s")
local has_3p = slot_exists(slot_tense .. "_3p")
if has_1s or (not has_3s and not has_3p) then
return {
slot = slot_tense .. "_1s",
label = ("第一人稱單數%s"):format(label_tense),
}, true
elseif has_3s then
return {
slot = slot_tense .. "_3s",
label = ("第三人稱單數%s"):format(label_tense),
}, false
else
return {
slot = slot_tense .. "_3p",
label = ("第三人稱複數%s"):format(label_tense),
}, false
end
end
local did_pres_1s
preses, did_pres_1s = do_finite("pres", "現在時")
preses_3s = {
slot = "pres_3s",
label = "第三人稱單數現在時",
}
prets = do_finite("pret", "過去時")
parts = {
slot = "pp_ms",
label = "過去分詞",
}
short_parts = {
slot = "short_pp_ms",
label = "短過去分詞",
}
if args.pres[1] or args.pres3s[1] or args.pret[1] or args.part[1] or args.short_part[1] then
track("verb-old-multiarg")
end
local function strip_brackets(qualifiers)
if not qualifiers then
return nil
end
local stripped_qualifiers = {}
for _, qualifier in ipairs(qualifiers) do
local stripped_qualifier = qualifier:match("^%[(.*)%]$")
if not stripped_qualifier then
error("Internal error: Qualifier should be surrounded by brackets at this stage: " .. qualifier)
end
insert(stripped_qualifiers, stripped_qualifier)
end
return stripped_qualifiers
end
local function do_verb_form(args, qualifiers, slot_desc, skip_if_empty)
local forms
local to_insert
if #args == 0 then
forms = specforms[slot_desc.slot]
if not forms or #forms == 0 then
if skip_if_empty then
return
end
forms = {{form = "-"}}
end
elseif #args == 1 and args[1] == "-" then
forms = {{form = "-"}}
else
forms = {}
for i, arg in ipairs(args) do
local qual = qualifiers[i]
if qual then
-- FIXME: It's annoying we have to add brackets and strip them out later. The inflection
-- code adds all footnotes with brackets around them; we should change this.
qual = {"[" .. qual .. "]"}
end
local form = arg
if not args.noautolinkverb then
-- [[Module:inflection utilities]] already loaded by [[Module:ca-verb]]
form = require(inflection_utilities_module).add_links(form)
end
insert(forms, {form = form, footnotes = qual})
end
end
if forms[1].form == "-" then
to_insert = {label = "無" .. slot_desc.label}
else
local into_table = {label = slot_desc.label}
for _, form in ipairs(forms) do
local qualifiers = strip_brackets(form.footnotes)
-- Strip redundant brackets surrounding entire form. These may get generated e.g.
-- if we use the angle bracket notation with a single word.
local stripped_form = rmatch(form.form, "^%[%[([^%[%]]*)%]%]$") or form.form
-- Don't include accelerators if brackets remain in form, as the result will be wrong.
-- FIXME: For now, don't include accelerators. We should use the new {{ca-verb form of}}.
-- local this_accel = not stripped_form:find("%[%[") and accel or nil
local this_accel = nil
insert(into_table, {term = stripped_form, q = qualifiers, accel = this_accel})
end
to_insert = into_table
end
insert(data.inflections, to_insert)
end
local skip_pres_if_empty
if alternant_multiword_spec.no_pres1_and_sub then
insert(data.inflections, {label = "無第一人稱單數現在時"})
insert(data.inflections, {label = "無現在時虛擬語氣"})
end
if alternant_multiword_spec.no_pres_stressed then
insert(data.inflections, {label = "無帶重音現在時直陳式或虛擬式"})
skip_pres_if_empty = true
end
if alternant_multiword_spec.only3s then
insert(data.inflections, {label = glossary_link("無人稱")})
elseif alternant_multiword_spec.only3sp then
insert(data.inflections, {label = "僅第三人稱"})
elseif alternant_multiword_spec.only3p then
insert(data.inflections, {label = "僅第三人稱複數"})
end
local has_vowel_alt
if alternant_multiword_spec.vowel_alt then
for _, vowel_alt in ipairs(alternant_multiword_spec.vowel_alt) do
if vowel_alt ~= "+" and vowel_alt ~= "í" and vowel_alt ~= "ú" then
has_vowel_alt = true
break
end
end
end
do_verb_form(args.pres, args.pres_qual, preses, skip_pres_if_empty)
-- We want to include both the pres_1s and pres_3s if there is a vowel alternation in the present singular. But we
-- don't want to redundantly include the pres_3s if we already included it.
if did_pres_1s and has_vowel_alt then
do_verb_form(args.pres3s, args.pres3s_qual, preses_3s, skip_pres_if_empty)
end
do_verb_form(args.pret, args.pret_qual, prets)
do_verb_form(args.part, args.part_qual, parts)
do_verb_form(args.short_part, args.short_part_qual, short_parts, "skip if empty")
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:ca-verb]] in place of the user-specified or auto-generated head. This was copied from
-- [[Module:it-headword]], where doing this gets accents marked on the verb(s). We don't have accents marked on
-- the verb but by doing this we do get any footnotes on the infinitive propagated here. Don't do this if the
-- user gave multiple heads or gave a head with a multiword-linked verbal expression such as Italian
-- '[[dare esca]] [[al]] [[fuoco]]' (FIXME: give Catalan equivalent).
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
if args.root then
local m_ca_IPA = require(ca_IPA_module)
local parsed_respellings = {}
local function set_parsed_respelling(dialect, parsed)
-- Validate the individual root vowel specs.
for _, termobj in ipairs(parsed.terms) do
if not rfind(termobj.words[1].term, "^" .. m_ca_IPA.mid_vowel_hint_c .. "$") then
error(("Root vowel spec '%s' should be one of the vowels %s"):format(
termobj.words[1].term, m_ca_IPA.mid_vowel_hints))
end
end
if not dialect then
for _, dial in ipairs(m_ca_IPA.dialects) do
-- Need to clone as we destructively modify each one later with the pronun.
parsed_respellings[dial] = m_table.deepCopy(parsed)
end
elseif m_ca_IPA.dialect_groups[dialect] then
for _, dial in ipairs(m_ca_IPA.dialect_groups[dialect]) do
-- Need to clone as we destructively modify each one later with the pronun.
parsed_respellings[dial] = m_table.deepCopy(parsed)
end
else
parsed_respellings[dialect] = parsed
end
end
local function check_dialect_or_dialect_group(dialect)
if not m_table.contains(m_ca_IPA.dialects, dialect) and not
m_ca_IPA.dialect_groups[dialect] then
local dialect_list = {}
for _, dial in ipairs(m_ca_IPA.dialects) do
insert(dialect_list, "'" .. dial .. "'")
end
dialect_list = list_to_text(dialect_list, nil, " or ")
local dialect_group_list = {}
for dialect_group, _ in pairs(m_ca_IPA.dialect_groups) do
insert(dialect_group_list, "'" .. dialect_group .. "'")
end
dialect_group_list = list_to_text(dialect_group_list, nil, " or ")
error(("Unrecognized dialect '%s': Should be a dialect %s or a dialect group %s"):format(
dialect, dialect_list, dialect_group_list))
end
end
-- Parse the root vowel specs.
if args.root:find("[<%[]") then
local put = require(parse_utilities_module)
-- Parse balanced segment runs involving either [...] (substitution notation) or <...> (inline
-- modifiers). We do this because we don't want commas or semicolons inside of square or angle brackets
-- to count as respelling delimiters. However, we need to rejoin square-bracketed segments with nearby
-- ones after splitting alternating runs on comma and semicolon.
local segments = put.parse_multi_delimiter_balanced_segment_run(args.root, {{"<", ">"}, {"[", "]"}})
local semicolon_separated_groups = put.split_alternating_runs(segments, "%s*;%s*")
for _, group in ipairs(semicolon_separated_groups) do
local first_element = group[1]
local dialect
if first_element:find("^[a-z]+:") then
-- a dialect-specific spec
local rest
dialect, rest = first_element:match("^([a-z]+):(.*)$")
check_dialect_or_dialect_group(dialect)
group[1] = rest
end
local comma_separated_groups = put.split_alternating_runs_on_comma(group)
-- Process each value.
local outer_container = m_ca_IPA.parse_comma_separated_groups(comma_separated_groups, true, args.root,
"root")
set_parsed_respelling(dialect, outer_container)
end
else
for _, dialect_spec in ipairs(rsplit(args.root, "%s*;%s*")) do
local dialect
if dialect_spec:find("^[a-z]+:") then
-- a dialect-specific spec
local rest
dialect, rest = dialect_spec:match("^([a-z]+):(.*)$")
check_dialect_or_dialect_group(dialect)
dialect_spec = rest
end
local termobjs = {}
for _, word in ipairs(rsplit(dialect_spec, ",")) do
insert(termobjs, {words = {{term = word}}})
end
set_parsed_respelling(dialect, {
terms = termobjs,
})
end
end
-- Convert each canonicalized respelling to phonemic/phonetic IPA.
m_ca_IPA.generate_phonemic_phonetic(parsed_respellings)
-- Group the results.
local grouped_pronuns = m_ca_IPA.group_pronuns_by_dialect(parsed_respellings)
-- Format for display.
for _, grouped_pronun_spec in pairs(grouped_pronuns) do
local pronunciations = {}
local function ins(text)
insert(pronunciations, text)
end
-- Loop through each pronunciation. For each one, format the phonetic version "raw".
for j, pronun in ipairs(grouped_pronun_spec.pronuns) do
-- Add dialect tags to left accent qualifiers if first one
local as = pronun.a
if j == 1 then
if as then
as = m_table.deepCopy(as)
else
as = {}
end
for _, dialect in ipairs(grouped_pronun_spec.dialects) do
insert(as, m_ca_IPA.dialects_to_names[dialect])
end
else
ins(", ")
end
local slash_pron = "/" .. pronun.phonetic:gsub("ˈ", "") .. "/"
if as or pronun.q or pronun.qq or pronun.aa then
ins(require(decorations_module).format_decorations {
lang = lang,
text = slash_pron,
q = pronun.q,
a = as,
qq = pronun.qq,
aa = pronun.aa
})
else
ins(slash_pron)
end
if pronun.refs then
-- FIXME: Copied from [[Module:IPA]]. Should be in a module.
local refs = {}
if #pronun.refs > 0 then
for _, refspec in ipairs(pronun.refs) 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
insert(refs, mw.getCurrentFrame():extensionTag("ref", refspec.text, refargs))
end
ins(concat(refs))
end
end
end
grouped_pronun_spec.formatted = concat(pronunciations)
end
-- Concatenate formatted results.
local formatted = {}
for _, grouped_pronun_spec in ipairs(grouped_pronuns) do
insert(formatted, grouped_pronun_spec.formatted)
end
data.post_note = "詞根重音:" .. concat(formatted, ";")
end
end
}
-----------------------------------------------------------------------------------------
-- Numerals --
-----------------------------------------------------------------------------------------
-- Display additional inflection information for a numeral
pos_functions["數詞"] = {
params = {
[1] = true,
[2] = true,
},
func = function(args, data)
if args[1] then
insert(data.genders, "m")
parse_and_insert_inflection(data, args, 1, "陰性")
parse_and_insert_inflection(data, args, 2, "名詞變格形")
else
insert(data.genders, "m")
insert(data.genders, "f")
end
end
}
-----------------------------------------------------------------------------------------
-- Phrases --
-----------------------------------------------------------------------------------------
pos_functions["短語"] = {
params = {
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
["m"] = list_param,
["f"] = list_param,
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
parse_and_insert_inflection(data, args, "m", "陽性")
parse_and_insert_inflection(data, args, "f", "陰性")
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, "構成" .. typ .. "的後綴")
end
insert(data.inflections, {label = m_table.serialCommaJoin(suffix_type, {conj = "或"}) .. "的非詞元形式"})
end,
}
return export
igzpjegx1rjfwdmj8nxcuauabl3sbhw
Module:Cop-headword
828
1481931
9909178
5915965
2026-09-29T09:28:57Z
TongcyDai
53191
同步 enwikt
9909178
Scribunto
text/plain
local export = {}
local lang = require("Module:languages").getByCode("cop")
local m_scripts = require("Module:scripts")
-- key = what is entered into the template
-- first value = displayed siglum
-- second value = tooltip
local dialects = {
["A"] = { "A", "Akhmimic" },
["A2"] = { "L", "Lycopolitan" },
["B"] = { "B", "Bohairic" },
["F"] = { "F", "Fayyumic" },
["G"] = { "G", "Dialect G" },
["H"] = { "H", "Hermopolitan" },
["I"] = { "I", "Proto-Lycopolitan" },
["J"] = { "J", "Dialect J" },
["K"] = { "K", "Dialect K" },
["L"] = { "L", "Lycopolitan" },
["M"] = { "O", "Oxyrhynchite" },
["O"] = { "O", "Oxyrhynchite" },
["OC"] = { "OC", "Old Coptic" },
["P"] = { "P", "Dialect P" },
["S"] = { "S", "Sahidic" },
}
local rdialects = {}
-- allow displayed text forms to be entered too
for key, value in pairs(dialects) do
if key == value[1] then
rdialects[value[2]] = value
end
end
for key, value in pairs(rdialects) do
dialects[key] = value
end
-- expand list of comma-separated dialects (tooltips, etc.)
local function parse_dialects(qual)
local list = mw.text.split(qual, ',%s*')
local expanded = { }
for i, dialect in ipairs(list) do
if dialects[dialect] then
table.insert(expanded, '<abbr title="' .. dialects[dialect][2] .. '">' .. dialects[dialect][1] .. "</abbr>")
else
table.insert(expanded, dialect)
end
end
return expanded
end
local function generate_head_forms(inflections, name, forms, qualifiers)
if #forms < 1 then return end
local infl = { label = name }
for i, form in ipairs(forms) do
local term = { term = form }
if qualifiers[i] then
term.q = parse_dialects(qualifiers[i])
end
table.insert(infl, term)
end
table.insert(inflections, infl)
end
function export.show_noun(frame)
local title = mw.loadData("Module:headword/data").pagename
local categories = { }
local params = {
[1] = { default = "?" },
[2] = { list = "pl", default = title },
[3] = { default = nil },
["f"] = { list = true },
["nom"] = { list = true },
["pro"] = { list = true },
["q"] = { list = true, require_index = true, allow_holes = true },
["fq"] = { list = true, require_index = true, allow_holes = true },
["nomq"] = { list = true, require_index = true, allow_holes = true },
["proq"] = { list = true, require_index = true, allow_holes = true },
["head"] = { default = nil },
["title"] = { default = nil }
}
local args = require("Module:parameters").process(frame:getParent().args, params)
local plurals = args[2]
local head = args["head"]
local gender = args[1]
local inflections = { ["enable_auto_translit"] = true }
if args[3] and plurals[1] ~= "~" then
error("Argument 3 is only allowed if argument 2 is ~ (noun is both countable and uncountable)")
elseif plurals[1] == "-" then
if #plurals > 1 then error("Cannot provide more plurals for uncountable nouns") end
-- l10n: 標籤與分類中文化
table.insert(inflections, { label = "不可數" } )
plurals = { }
table.insert(categories, "科普特語不可數名詞")
elseif plurals[1] == "!" then
if #plurals > 1 then error("Cannot provide more plurals if plural unattested") end
-- l10n: 標籤中文化;zhwikt 未設此分類
table.insert(inflections, { label = "複數未紀錄" } )
plurals = { }
-- table.insert(categories, "Coptic nouns with unattested plurals")
elseif plurals[1] == "~" then
-- l10n: 標籤與分類中文化
table.insert(inflections, { label = "可數、不可數" } )
plurals[1] = args[3] or head or title
table.insert(categories, "科普特語不可數名詞")
-- countable added later
end
if plurals[1] == "?" or (plurals[1] == "~" and args[3] == "?") then
if #plurals > 1 then error("Cannot provide more plurals if the plural is uncertain") end
plurals = { }
-- l10n: zhwikt 未設此分類
-- table.insert(categories, "Coptic noun entries missing plural forms")
elseif #plurals > 0 then
table.insert(categories, "科普特語可數名詞") -- l10n
end
if head then
for i, pl in ipairs(plurals) do
if pl == title then
plurals[i] = head
end
end
end
-- l10n: 屈折標籤中文化
generate_head_forms(inflections, "陰性", args["f"], args["fq"])
generate_head_forms(inflections, "複數", plurals, args["q"])
generate_head_forms(inflections, "名詞性", args["nom"], args["nomq"])
generate_head_forms(inflections, "代詞形", args["pro"], args["proq"])
return require("Module:headword").full_headword{
lang = lang,
heads = { head or title },
genders = { gender },
inflections = inflections,
pos_category = "名詞", -- l10n
categories = categories
}
end
return export
q8ss0bzllg64vovti8zb25xnzkylbew
Module:Affixusex
828
1486362
9909037
9137740
2026-09-29T07:24:00Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909037
Scribunto
text/plain
local export = {}
local decorations_module = "Module:decorations"
local etymology_module = "Module:etymology"
local links_module = "Module:links"
-- main function
--[==[
Format the affix usexes in `data`. We more or less simply call full_link() on each item, along with the
associated params, to format the link, but need some special-casing for affixes. On input, the `data`
object contains the following fields:
* `lang` ('''required'''): Overall language object; default for items not specifying their own language.
* `sc`: Overall script object; default for items not specifying their own script.
* `items`: List of items. Each is an object with the following fields:
** `term`: The term (affix or resulting term).
** `gloss`, `tr`, `ts`, `genders`, `alt`, `id`, `lit`, `pos`: The same as for `full_links()` in [[Module:links]].
** `lang`: Language of the term. Should only be set when the term has its own language, and will cause the
language to be displayed before the term. Defaults to the overall `lang`.
** `sc`: Script of the term. Defaults to the overall `sc`.
** `fulljoiner`: Text of the separator appearing before the item, including spaces. Takes precedence over `joiner`
and `arrow`.
** `joiner`: Text of the separator appearing before the item, not including spaces. Takes precedence over `arrow`.
** `arrow`: If specified, the separator is a right arrow. If none of `fulljoiner`, `joiner` and `arrow` are given,
the separator is a right arrow if it's the last item, otherwise a plus sign if it's not the first item, otherwise
there's no displayed separator.
** `q`: Left regular qualifier(s) for the term.
** `qq`: Right regular qualifier(s) for the term.
** `l`: Left labels for the term.
** `ll`: Right labels for the term.
** `refs`: References for the term, in the structure expected by [[Module:references]].
* `lit`: Overall literal meaning.
* `q`: Overall left regular qualifier(s).
* `qq`: Overall right regular qualifier(s).
* `l`: Overall left labels.
* `ll`: Overall right labels.
'''WARNING:''' This destructively modifies the `items` objects (specifically by adding default values for `lang` and
`sc`).
]==]
function export.format_affixusex(data)
local result = {}
for index, item in ipairs(data.items) do
local term = item.term
local alt = item.alt
if item.fulljoiner then
table.insert(result, item.fulljoiner)
elseif item.joiner then
table.insert(result, " " .. item.joiner .. " ")
elseif index == #data.items or item.arrow then
table.insert(result, " → ")
elseif index > 1 then
table.insert(result, " + ")
end
table.insert(result, "‎")
local text
local item_lang_specific = item.lang
item.lang = item.lang or data.lang
item.sc = item.sc or data.sc
if item_lang_specific then
text = require(etymology_module).format_derived {
sources = {item.lang},
terms = {item},
template_name = "affixusex",
decorations_on_outside = true,
}
else
-- l10n: zhwikt 的 [[Module:parameter utilities]] 不會自動設定 show_decorations,需在此設定
item.show_decorations = true
text = require(links_module).full_link(item, "term")
end
table.insert(result, text)
end
result = table.concat(result) .. (data.lit and ", literally " ..
require(links_module).mark(data.lit, "gloss") or "")
if data.q and data.q[1] or data.qq and data.qq[1] or data.l and data.l[1] or data.ll and data.ll[1] then
result = require(decorations_module).format_decorations {
lang = data.lang,
text = result,
q = data.q,
qq = data.qq,
l = data.l,
ll = data.ll,
}
end
return result
end
return export
be1u5g83gpnhaccldnaaulk8e24y25x
9909183
9909037
2026-09-29T09:47:36Z
TongcyBot
83009
移除手動 show_decorations
9909183
Scribunto
text/plain
local export = {}
local decorations_module = "Module:decorations"
local etymology_module = "Module:etymology"
local links_module = "Module:links"
-- main function
--[==[
Format the affix usexes in `data`. We more or less simply call full_link() on each item, along with the
associated params, to format the link, but need some special-casing for affixes. On input, the `data`
object contains the following fields:
* `lang` ('''required'''): Overall language object; default for items not specifying their own language.
* `sc`: Overall script object; default for items not specifying their own script.
* `items`: List of items. Each is an object with the following fields:
** `term`: The term (affix or resulting term).
** `gloss`, `tr`, `ts`, `genders`, `alt`, `id`, `lit`, `pos`: The same as for `full_links()` in [[Module:links]].
** `lang`: Language of the term. Should only be set when the term has its own language, and will cause the
language to be displayed before the term. Defaults to the overall `lang`.
** `sc`: Script of the term. Defaults to the overall `sc`.
** `fulljoiner`: Text of the separator appearing before the item, including spaces. Takes precedence over `joiner`
and `arrow`.
** `joiner`: Text of the separator appearing before the item, not including spaces. Takes precedence over `arrow`.
** `arrow`: If specified, the separator is a right arrow. If none of `fulljoiner`, `joiner` and `arrow` are given,
the separator is a right arrow if it's the last item, otherwise a plus sign if it's not the first item, otherwise
there's no displayed separator.
** `q`: Left regular qualifier(s) for the term.
** `qq`: Right regular qualifier(s) for the term.
** `l`: Left labels for the term.
** `ll`: Right labels for the term.
** `refs`: References for the term, in the structure expected by [[Module:references]].
* `lit`: Overall literal meaning.
* `q`: Overall left regular qualifier(s).
* `qq`: Overall right regular qualifier(s).
* `l`: Overall left labels.
* `ll`: Overall right labels.
'''WARNING:''' This destructively modifies the `items` objects (specifically by adding default values for `lang` and
`sc`).
]==]
function export.format_affixusex(data)
local result = {}
for index, item in ipairs(data.items) do
local term = item.term
local alt = item.alt
if item.fulljoiner then
table.insert(result, item.fulljoiner)
elseif item.joiner then
table.insert(result, " " .. item.joiner .. " ")
elseif index == #data.items or item.arrow then
table.insert(result, " → ")
elseif index > 1 then
table.insert(result, " + ")
end
table.insert(result, "‎")
local text
local item_lang_specific = item.lang
item.lang = item.lang or data.lang
item.sc = item.sc or data.sc
if item_lang_specific then
text = require(etymology_module).format_derived {
sources = {item.lang},
terms = {item},
template_name = "affixusex",
decorations_on_outside = true,
}
else
text = require(links_module).full_link(item, "term")
end
table.insert(result, text)
end
result = table.concat(result) .. (data.lit and ", literally " ..
require(links_module).mark(data.lit, "gloss") or "")
if data.q and data.q[1] or data.qq and data.qq[1] or data.l and data.l[1] or data.ll and data.ll[1] then
result = require(decorations_module).format_decorations {
lang = data.lang,
text = result,
q = data.q,
qq = data.qq,
l = data.l,
ll = data.ll,
}
end
return result
end
return export
7lqfzf45j98mnyqcsi4aglxcn2wo764
Module:En-headword
828
1488538
9909055
9847777
2026-09-29T07:25:09Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909055
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,
-- If we want check_missing support, we need to supply the following:
-- check_missing = true,
-- lang = lang,
-- plpos = plpos,
}
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, cat:find("的") and cat:gsub("的", "的" .. langname) or langname .. cat) -- L10N
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "後綴變格形" then
is_suffix = true
data.pos_category = "後綴"
local singular_poscat = poscat
inscat("構成" .. singular_poscat .. "的後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
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("拼字中沒有元音的詞")
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 .. "長單詞")
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", "縮寫")
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("比較級") .. "形式"})
insert(data.categories, langname .. "僅比較級" .. 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("最高級") .. "形式"})
insert(data.categories, langname .. "僅最高級" .. 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 with '?'")
end
if sups[1] then
error("Can't specify superlatives along with '?' 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, "不總是有<<比較級>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "無<<比較級>>", hyphencomp)
insert(data.categories, langname .. "無比較級" .. 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 with '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 with '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")
insert_inflection(data, outsups, "<<最高級>>", "superlative")
end
pos_functions["形容詞"] = {
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, "形容詞")
end,
}
pos_functions["副詞"] = {
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, "副詞")
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 with '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 "名詞"
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, "通常複數", originating_label)
parse_and_insert_inflection(data, args, "sg", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "唯複", 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("With plurale tantum noun, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("唯複", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, cat:find("的") and cat:gsub("的", "的" .. langname) or langname .. cat) -- L10N
end
local need_default_plural = pos == "名詞"
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("可視作單數或複數的名詞")
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
-- Uncountable noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("不可數名詞")
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
insert_fixed_inflection(data, "通常<<不可數>>", hyphpl)
else
insert_fixed_inflection(data, "<<不可數>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- Usually countable (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "通常<<可數>>", hashpl)
inscat("不可數名詞")
inscat("可數名詞")
-- 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 countable/uncountable noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<可數>>和<<不可數>>", tildepl)
inscat("不可數名詞")
inscat("可數名詞")
-- 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, "plural unknown or uncertain", questionpl)
inscat("有未知或不確定複數的名詞")
if plurals[1] then
error("Can't specify explicit plurals along with '?' for unknown/uncertain plural")
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, "複數未紀錄", exclampl)
inscat("有未紀錄複數的名詞")
if plurals[1] then
error("Can't specify explicit plurals along with '!' for unattested plural")
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("不可數名詞")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("可數名詞")
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("有不規則複數的名詞")
end
if indeclinable then
inscat("無屈折名詞")
end
insert_inflection(data, plurals, "複數", "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["名詞"] = {
params = noun_params,
func = do_nouns,
}
pos_functions["專有名詞"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "專有名詞")
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("Verb '" .. verb .. "' must have a space in it to use **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("First word '" .. first .. "' must end in a consonant to use **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Verb '" .. pagename .. "' must have a space in it to use *, **, *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["動詞"] = {
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 = {"美國"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"英國"}}}
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 = {"美國"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"英國"}}}
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 = {"美國"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"英國"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"美國"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"英國"}}}
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 = {"美國"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"英國"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"美國"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"英國"}}}
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 with 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, "第三人稱單數簡單現在時", "s-verb-form")
insert_inflection(data, pres_ptcs, "現在分詞", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "簡單過去時和過去分詞", "ed-form",
"無簡單過去時或過去分詞")
else
insert_inflection(data, pasts, "簡單過去時", "spast")
insert_inflection(data, past_ptcs, "過去分詞", "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, "帶位標「it」的" .. langname .. "詞")
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 .. "動詞短語")
for i = #seen_adverbs, 1, -1 do
insert(data.categories, "由" .. seen_adverbs[i] .. "構成的" .. langname .. "動詞短語")
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
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 .. "的後綴")
end
insert(data.inflections, {label = m_table.serialCommaJoin(suffix_type, {conj = "或"}) .. "的非詞元形式"})
end,
}
return export
44tdlwcs9rzqm4d4f22fcv44vrqgvxp
Module:Hyphenation
828
1489724
9909032
9844372
2026-09-29T07:22:14Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909032
Scribunto
text/plain
local export = {}
local decorations_module = "Module:decorations"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local function track(page)
require("Module:debug/track")("hyphenation/" .. page)
return true
end
--[==[
Meant to be called from a module. `data` is a table containing the following fields:
* `lang`: language object for the hyphenations or syllabifications;
* `hyphs`: a list of hyphenations/syllabifications, each described by an object which can contain the following fields:
** `hyph`: list of syllables comprising the hyphenation or syllabification, each a string;
** `q`: {nil} or a list of left regular qualifier strings, displayed directly before the hyphenation in question;
** `qq`: {nil} or a list of right regular qualifier strings, displayed directly after the hyphenation in question;
** `qualifiers`: {nil} or a list of qualifier strings; also displayed on the left; for compatibility purposes only, do
not use in new code;
** `a`: {nil} or a list of left accent qualifier strings (see [[Module:accent qualifier]]), displayed directly before
the hyphenation in question;
** `aa`: {nil} or a list of right accent qualifier strings, displayed directly after the hyphenation in question;
** `refs`: {nil} or a list of references or reference specs to add directly after the hyphenation; the value of a list
item 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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}} or {{cd|<nowiki><ref name="foo" /></nowiki>}})
and/or `group` (the group of the reference, as in {{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section;
** `sc`: {nil} or script object for this particular hyphenation/syllabification;
* `q`: {nil} or a list of overall left regular qualifier strings, displayed before the initial caption;
* `qq`: {nil} or a list of overall right regular qualifier strings, displayed after all hyphenations;
* `qualifiers`: {nil} or a list of overall left regular qualifier strings; for compatibility purposes only, do not use
in new code;
* `a`: {nil} or a list of overall left accent qualifier strings (see [[Module:accent qualifier]]), displayed before the
initial caption;
* `aa`: {nil} or a list of right accent qualifier strings, displayed after all hyphenations;
* `sc`: {nil} or script object for the hyphenations/syllabifications;
* `caption`, {nil} or a string specifying the caption to use, in place of {"Hyphenation"}; e.g. use {"Syllabification"}
if what is passed in is actually a syllabification, as is common; a colon and space is automatically added after the
caption;
* `nocaption`: if true, suppress the caption display.
]==]
function export.format_hyphenations(data)
local hyphtexts = {}
for _, hyph in ipairs(data.hyphs) do
if #hyph.hyph == 0 then
error("Saw empty hyphenation; use || to separate hyphenations")
end
if hyph.qualifiers then
track("old-qualifiers")
end
local text = require(links_module).full_link {
lang = data.lang,
sc = hyph.sc or data.sc,
alt = table.concat(hyph.hyph, "‧"),
tr = "-",
q = hyph.q or hyph.qualifiers,
qq = hyph.qq,
a = hyph.a,
aa = hyph.aa,
refs = hyph.refs,
show_decorations = true,
}
table.insert(hyphtexts, text)
end
local text = ((data.nocaption and "") or ((data.caption or "斷字") .. ":")) .. table.concat(hyphtexts, ", ")
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
text = require(decorations_module).format_decorations {
lang = data.lang,
text = text,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
return text
end
--[==[
Entry point for {{tl|hyphenation}} template (also written {{tl|hyph}}).
]==]
function export.hyphenation(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local lang_param = compat and "lang" or 1
local offset = compat and 0 or 1
local params = {
[lang_param] = {required = true, type = "language", default = "und"},
[1 + offset] = {list = true, required = true, allow_holes = true, default = "{{{2}}}"},
-- FIXME: For compatibility, q= and qq= refer to the first hyphenation rather than overall. Consider tracking and
-- changing this.
["q"] = {list = true, allow_holes = true, type = "qualifier"},
["qq"] = {list = true, allow_holes = true, type = "qualifier"},
["a"] = {list = true, allow_holes = true, separate_no_index = true, type = "labels"},
["aa"] = {list = true, allow_holes = true, separate_no_index = true, type = "labels"},
["ref"] = {list = true, allow_holes = true, type = "references"},
["caption"] = {},
["nocaption"] = {type = "boolean"},
["sc"] = {list = true, allow_holes = true, separate_no_index = true, type = "script"},
}
local args = require(parameters_module).process(parent_args, params)
local lang = args[lang_param]
local sc = args.sc.default
local data = {
lang = lang,
sc = sc,
hyphs = {},
caption = args.caption,
nocaption = args.nocaption,
a = args.a.default,
aa = args.aa.default,
}
local this_hyph = {hyph = {}}
local maxindex = args[1 + offset].maxindex
local function insert_hyph()
local hyphnum = #data.hyphs + 1
this_hyph.q = args.q[hyphnum]
this_hyph.qq = args.qq[hyphnum]
this_hyph.a = args.a[hyphnum]
this_hyph.aa = args.aa[hyphnum]
this_hyph.refs = args.ref[hyphnum]
table.insert(data.hyphs, this_hyph)
end
for i=1, maxindex do
local syl = args[1 + offset][i]
if not syl then
insert_hyph()
this_hyph = {hyph = {}}
else
table.insert(this_hyph.hyph, syl)
end
end
insert_hyph()
return export.format_hyphenations(data)
end
return export
exuedig3z5ka4oajm8sm4eu57tgq1x0
9909121
9909032
2026-09-29T07:41:47Z
TongcyBot
83009
同步 enwikt:舊介面改為報錯
9909121
Scribunto
text/plain
local export = {}
local decorations_module = "Module:decorations"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local function track(page)
require("Module:debug/track")("hyphenation/" .. page)
return true
end
--[==[
Meant to be called from a module. `data` is a table containing the following fields:
* `lang`: language object for the hyphenations or syllabifications;
* `hyphs`: a list of hyphenations/syllabifications, each described by an object which can contain the following fields:
** `hyph`: list of syllables comprising the hyphenation or syllabification, each a string;
** `q`: {nil} or a list of left regular qualifier strings, displayed directly before the hyphenation in question;
** `qq`: {nil} or a list of right regular qualifier strings, displayed directly after the hyphenation in question;
** `a`: {nil} or a list of left accent qualifier strings (see [[Module:accent qualifier]]), displayed directly before
the hyphenation in question;
** `aa`: {nil} or a list of right accent qualifier strings, displayed directly after the hyphenation in question;
** `refs`: {nil} or a list of references or reference specs to add directly after the hyphenation; the value of a list
item 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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}} or {{cd|<nowiki><ref name="foo" /></nowiki>}})
and/or `group` (the group of the reference, as in {{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section;
** `sc`: {nil} or script object for this particular hyphenation/syllabification;
* `q`: {nil} or a list of overall left regular qualifier strings, displayed before the initial caption;
* `qq`: {nil} or a list of overall right regular qualifier strings, displayed after all hyphenations;
* `a`: {nil} or a list of overall left accent qualifier strings (see [[Module:accent qualifier]]), displayed before the
initial caption;
* `aa`: {nil} or a list of right accent qualifier strings, displayed after all hyphenations;
* `sc`: {nil} or script object for the hyphenations/syllabifications;
* `caption`, {nil} or a string specifying the caption to use, in place of {"Hyphenation"}; e.g. use {"Syllabification"}
if what is passed in is actually a syllabification, as is common; a colon and space is automatically added after the
caption;
* `nocaption`: if true, suppress the caption display.
]==]
function export.format_hyphenations(data)
local hyphtexts = {}
for _, hyph in ipairs(data.hyphs) do
if #hyph.hyph == 0 then
error("Saw empty hyphenation; use || to separate hyphenations")
end
if hyph.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("`.qualifiers` is no longer supported; change the code to use `.qq` or `.q`")
end
local text = require(links_module).full_link {
lang = data.lang,
sc = hyph.sc or data.sc,
alt = table.concat(hyph.hyph, "‧"),
tr = "-",
q = hyph.q,
qq = hyph.qq,
a = hyph.a,
aa = hyph.aa,
refs = hyph.refs,
show_decorations = true,
}
table.insert(hyphtexts, text)
end
local text = ((data.nocaption and "") or ((data.caption or "斷字") .. ":")) .. table.concat(hyphtexts, ", ")
if data.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("overall `.qualifiers` is no longer supported; change the code to use `.qq` or `.q`")
end
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
text = require(decorations_module).format_decorations {
lang = data.lang,
text = text,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
return text
end
--[==[
Entry point for {{tl|hyphenation}} template (also written {{tl|hyph}}).
]==]
function export.hyphenation(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local lang_param = compat and "lang" or 1
local offset = compat and 0 or 1
local params = {
[lang_param] = {required = true, type = "language", default = "und"},
[1 + offset] = {list = true, required = true, allow_holes = true, default = "{{{2}}}"},
-- FIXME: For compatibility, q= and qq= refer to the first hyphenation rather than overall. Consider tracking and
-- changing this.
["q"] = {list = true, allow_holes = true, type = "qualifier"},
["qq"] = {list = true, allow_holes = true, type = "qualifier"},
["a"] = {list = true, allow_holes = true, separate_no_index = true, type = "labels"},
["aa"] = {list = true, allow_holes = true, separate_no_index = true, type = "labels"},
["ref"] = {list = true, allow_holes = true, type = "references"},
["caption"] = {},
["nocaption"] = {type = "boolean"},
["sc"] = {list = true, allow_holes = true, separate_no_index = true, type = "script"},
}
local args = require(parameters_module).process(parent_args, params)
local lang = args[lang_param]
local sc = args.sc.default
local data = {
lang = lang,
sc = sc,
hyphs = {},
caption = args.caption,
nocaption = args.nocaption,
a = args.a.default,
aa = args.aa.default,
}
local this_hyph = {hyph = {}}
local maxindex = args[1 + offset].maxindex
local function insert_hyph()
local hyphnum = #data.hyphs + 1
this_hyph.q = args.q[hyphnum]
this_hyph.qq = args.qq[hyphnum]
this_hyph.a = args.a[hyphnum]
this_hyph.aa = args.aa[hyphnum]
this_hyph.refs = args.ref[hyphnum]
table.insert(data.hyphs, this_hyph)
end
for i=1, maxindex do
local syl = args[1 + offset][i]
if not syl then
insert_hyph()
this_hyph = {hyph = {}}
else
table.insert(this_hyph.hyph, syl)
end
end
insert_hyph()
return export.format_hyphenations(data)
end
return export
m389whl45wep6g59yzmfnr816yqbeig
Module:Homophones
828
1492345
9909033
9137775
2026-09-29T07:22:17Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909033
Scribunto
text/plain
local export = {}
local decorations_module = "Module:decorations"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
--[==[
Meant to be called from a module. `data` is a table containing the following fields:
* `lang`: language object for the homophones;
* `homophones`: a list of homophones, each described by an object which can contain all the fields in the object
passed to {full_link()} in [[Module:links]] except for `lang` and `sc` (which are copied from the outer level), and in
addition can contain left and right regular and accent qualifier fields:
** `term`: the homophone itself;
** `separator`: {nil} or the string used to separate this homophone from the preceding one when displayed; defaults to
the top-level `separator`;
** `alt`: display text for the homophone, as in {{tl|l}};
** `gloss`: gloss for the homophone, as in {{tl|l}};
** `tr`: transliteration for the homophone, as in {{tl|l}};
** `ts`: transcription for the homophone, as in {{tl|l}};
** `genders`: list of genders for the homophone, as in {{tl|l}};
** `pos`: part of speech of the homophone, as in {{tl|l}};
** `lit`: literal meaning of the homophone, as in {{tl|l}};
** `id`: sense ID for the homophone, as in {{tl|l}};
** `lang`: optional lang code, overriding the lang code in the top-level `lang` field;
** `sc`: optional script code, overriding the script code in the top-level `sc` field;
** `q`: {nil} or a list of left regular qualifier strings, formatted using {format_qualifier()} in [[Module:qualifier]]
and displayed directly before the homophone in question;
** `qq`: {nil} or a list of right regular qualifier strings, displayed directly after the homophone in question;
** `qualifiers`: {nil} or a list of qualifier strings; currently displayed on the right but that may change; for
compatibility purposes only, do not use in new code;
** `a`: {nil} or a list of left accent qualifier strings, formatted using {format_qualifiers()} in
[[Module:accent qualifier]] and displayed directly before the homophone in question;
** `aa`: {nil} or a list of right accent qualifier strings, displayed directly after the homophone in question;
** `refs`: {nil} or a list of references or reference specs to add after the pronunciation and any posttext and
qualifiers; the value of a list item 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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}}
or {{cd|<nowiki><ref name="foo" /></nowiki>}}) and/or `group` (the group of the reference, as in
{{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section;
* `separator`: {nil} or a string, specifying the separator displayed before all homophones but the first; by default,
{", "}; overridable at the individual homophone level;
* `q`: {nil} or a list of left regular qualifier strings, formatted using {format_qualifier()} in [[Module:qualifier]]
and displayed before the initial caption;
* `qq`: {nil} or a list of right regular qualifier strings, displayed after all homophones;
* `a`: {nil} or a list of left accent qualifier strings, formatted using {format_qualifiers()} in
[[Module:accent qualifier]] and dispalyed before the initial caption;
* `aa`: {nil} or a list of right accent qualifier strings, displayed after all homophones;
* `sc`: {nil} or script object for the homophones;
* `sort`: {nil} or sort key;
* `caption`: {nil} or string specifying the caption to use, in place of {"Homophone"} (if there is a single homophone),
or {"Homophones"} (otherwise); a colon and space is automatically added after the caption;
* `nocaption`: If true, suppress the caption display.
* `nocat`: If true, suppress categorization.
If both regular and accent qualifiers on the same side and at the same level are specified, the accent qualifiers precede
the regular qualifiers on both left and right.
'''WARNING''': Destructively modifies the objects inside the `homophones` field.
]==]
function export.format_homophones(data)
local hmptexts = {}
local hmpcats = {}
local m_links = require(links_module)
local overall_sep = data.separator or ", "
for i, hmp in ipairs(data.homophones) do
hmp.lang = hmp.lang or data.lang
hmp.sc = hmp.sc or data.sc
local text = m_links.full_link(hmp)
if hmp.q and hmp.q[1] or hmp.qq and hmp.qq[1] or hmp.qualifiers and hmp.qualifiers[1]
or hmp.a and hmp.a[1] or hmp.aa and hmp.aa[1] or hmp.refs and hmp.refs[1] then
-- FIXME, change handling of `qualifiers`
text = require(decorations_module).format_decorations {
lang = hmp.lang,
text = text,
q = hmp.q,
qq = hmp.qq,
qualifiers = hmp.qualifiers,
qualifiers_right = true,
a = hmp.a,
aa = hmp.aa,
refs = hmp.refs,
}
end
table.insert(hmptexts, hmp.separator or i > 1 and overall_sep or "")
table.insert(hmptexts, text)
end
table.insert(hmpcats, "有同音詞的" .. data.lang:getFullName() .. "詞")
local text = table.concat(hmptexts)
local caption = data.nocaption and "" or (
data.caption or "[[Appendix:術語表#同音詞|同音詞]]"
) .. ":"
text = caption .. text
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
text = require(decorations_module).format_decorations {
lang = data.lang,
text = text,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
text = "<span class=\"homophones\">" .. text .. "</span>"
if not data.nocat then
local categories = require("Module:utilities").format_categories(hmpcats, data.lang, data.sort)
text = text .. categories
end
return text
end
--[==[
Entry point for {{tl|homophones}} template (also written {{tl|homophone}} and {{tl|hmp}}).
]==]
function export.show(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
local lang_arg = compat and "lang" or 1
local params = {
[lang_arg] = {required = true, type = "language", default = "en"},
[1 + offset] = {list = true, required = true, allow_holes = true, default = "term"},
["caption"] = {},
["nocaption"] = {type = "boolean"},
["nocat"] = {type = "boolean"},
["sort"] = {},
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "a", "q"}},
}
local homophones, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
process_args_before_parsing = function(args)
-- FIXME: temporary.
if args.q.default then
error("Use of q= in [[Template:homophones]] no longer permitted; use qq1=; in a month or two, q= will return as an overall left qualifier")
end
if args.q.maxindex > 0 then
error("Use of qN= in [[Template:homophones]] no longer permitted; use qqN=; in a month or two, qN= will return as left qualifiers")
end
end,
termarg = 1 + offset,
parse_lang_prefix = true,
track_module = "homophones",
lang = lang_arg,
sc = "sc.default",
}
local data = {
lang = args[lang_arg],
homophones = homophones,
caption = args.caption,
nocaption = args.nocaption,
nocat = args.nocat,
sc = args.sc.default,
sort = args.sort,
q = args.q.default,
qq = args.qq.default,
a = args.a.default,
aa = args.aa.default,
}
return export.format_homophones(data)
end
return export
m5fhxb7z6s4c9t0yf3rzzd9crcc1pf1
9909122
9909033
2026-09-29T07:41:50Z
TongcyBot
83009
同步 enwikt:舊介面改為報錯
9909122
Scribunto
text/plain
local export = {}
local decorations_module = "Module:decorations"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
--[==[
Meant to be called from a module. `data` is a table containing the following fields:
* `lang`: language object for the homophones;
* `homophones`: a list of homophones, each described by an object which can contain all the fields in the object
passed to {full_link()} in [[Module:links]] except for `lang` and `sc` (which are copied from the outer level),
including decoration fields;
** `term`: the homophone itself;
** `separator`: {nil} or the string used to separate this homophone from the preceding one when displayed; defaults to
the top-level `separator`;
** `alt`: display text for the homophone, as in {{tl|l}};
** `gloss`: gloss for the homophone, as in {{tl|l}};
** `tr`: transliteration for the homophone, as in {{tl|l}};
** `ts`: transcription for the homophone, as in {{tl|l}};
** `genders`: list of genders for the homophone, as in {{tl|l}};
** `pos`: part of speech of the homophone, as in {{tl|l}};
** `ng`: non-gloss text for the homophone, as in {{tl|l}};
** `lit`: literal meaning of the homophone, as in {{tl|l}};
** `id`: sense ID for the homophone, as in {{tl|l}};
** `lang`: optional lang code, overriding the lang code in the top-level `lang` field;
** `sc`: optional script code, overriding the script code in the top-level `sc` field;
** `q`: {nil} or a list of left regular qualifier strings, displayed directly before the homophone in question;
** `qq`: {nil} or a list of right regular qualifier strings, displayed directly after the homophone in question;
** `a`: {nil} or a list of left accent qualifier strings (see [[Module:accent qualifier]]) and displayed directly
before the homophone in question;
** `aa`: {nil} or a list of right accent qualifier strings, displayed directly after the homophone in question;
** `refs`: {nil} or a list of references or reference specs to add after the pronunciation and any posttext and
qualifiers; the value of a list item 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>`);
this uses a parser function to format the reference appropriately and insert a footnote number that hyperlinks to the
actual reference, located in the `<nowiki><references /></nowiki>` section;
* `separator`: {nil} or a string, specifying the separator displayed before all homophones but the first; by default,
{", "}; overridable at the individual homophone level;
* `q`: {nil} or a list of left regular qualifier strings, displayed before the initial caption;
* `qq`: {nil} or a list of right regular qualifier strings, displayed after all homophones;
* `a`: {nil} or a list of left accent qualifier strings (see [[Module:accent qualifier]]), displayed before the initial
caption;
* `aa`: {nil} or a list of right accent qualifier strings, displayed after all homophones;
* `sc`: {nil} or script object for the homophones;
* `sort`: {nil} or sort key;
* `caption`: {nil} or string specifying the caption to use, in place of {"Homophone"} (if there is a single homophone),
or {"Homophones"} (otherwise); a colon and space is automatically added after the caption;
* `nocaption`: If true, suppress the caption display.
* `nocat`: If true, suppress categorization.
If both regular and accent qualifiers on the same side and at the same level are specified, the accent qualifiers
precede the regular qualifiers on both left and right.
'''WARNING''': Destructively modifies the objects inside the `homophones` field.
]==]
function export.format_homophones(data)
local hmptexts = {}
local hmpcats = {}
local m_links = require(links_module)
local overall_sep = data.separator or ", "
for i, hmp in ipairs(data.homophones) do
hmp.lang = hmp.lang or data.lang
hmp.sc = hmp.sc or data.sc
hmp.show_decorations = true -- make full_link() display decorations (`.q`, `.qq`, `.a`, `.aa` and `.refs`)
if hmp.qualifiers then
error("`.qualifiers` is no longer supported; change the code to use `.qq` or `.q`")
end
local text = m_links.full_link(hmp)
table.insert(hmptexts, hmp.separator or i > 1 and overall_sep or "")
table.insert(hmptexts, text)
end
table.insert(hmpcats, "有同音詞的" .. data.lang:getFullName() .. "詞")
local text = table.concat(hmptexts)
local caption = data.nocaption and "" or (
data.caption or "[[Appendix:術語表#同音詞|同音詞]]"
) .. ":"
text = caption .. text
if data.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("overall `.qualifiers` is no longer supported; change the code to use `.qq` or `.q`")
end
if data.q and data.q[1] or data.qq and data.qq[1] or data.a and data.a[1] or data.aa and data.aa[1] then
text = require(decorations_module).format_decorations {
lang = data.lang,
text = text,
q = data.q,
qq = data.qq,
a = data.a,
aa = data.aa,
}
end
text = "<span class=\"homophones\">" .. text .. "</span>"
if not data.nocat then
local categories = require("Module:utilities").format_categories(hmpcats, data.lang, data.sort)
text = text .. categories
end
return text
end
--[==[
Entry point for {{tl|homophones}} template (also written {{tl|homophone}} and {{tl|hmp}}).
]==]
function export.show(frame)
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1
local lang_arg = compat and "lang" or 1
local params = {
[lang_arg] = {required = true, type = "language", default = "en"},
[1 + offset] = {list = true, required = true, allow_holes = true, default = "term"},
["caption"] = {},
["nocaption"] = {type = "boolean"},
["nocat"] = {type = "boolean"},
["sort"] = {},
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "a", "q"}},
}
local homophones, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
process_args_before_parsing = function(args)
-- FIXME: temporary.
if args.q.default then
error("Use of q= in [[Template:homophones]] no longer permitted; use qq1=; in a month or two, q= will return as an overall left qualifier")
end
if args.q.maxindex > 0 then
error("Use of qN= in [[Template:homophones]] no longer permitted; use qqN=; in a month or two, qN= will return as left qualifiers")
end
end,
termarg = 1 + offset,
parse_lang_prefix = true,
track_module = "homophones",
lang = lang_arg,
sc = "sc.default",
}
local data = {
lang = args[lang_arg],
homophones = homophones,
caption = args.caption,
nocaption = args.nocaption,
nocat = args.nocat,
sc = args.sc.default,
sort = args.sort,
q = args.q.default,
qq = args.qq.default,
a = args.a.default,
aa = args.aa.default,
}
return export.format_homophones(data)
end
return export
4uo0cpwmlekh2b8mgdu1rhrxcnsj0d7
Module:Dialect synonyms
828
1498066
9909179
9612449
2026-09-29T09:47:15Z
TongcyBot
83009
不讓 full_link 重複顯示限定詞
9909179
Scribunto
text/plain
-- Module:dialect synonyms
-- Generates dialectal synonym tables for various languages.
local export = {}
local m_links = require("Module:links")
local m_languages = require("Module:languages")
local m_table = require("Module:table")
local m_param_utils = require("Module:parameter utilities")
local PARAM_MODS = {
group = { type = "string" },
note = { type = "string" },
alt = {},
t = { item_dest = "gloss" },
gloss = {},
tr = {},
ts = {},
g = { item_dest = "genders", sublist = true },
pos = {},
lit = {},
id = {},
sc = { type = "script" },
q = { type = "qualifier" },
qq = { type = "qualifier" },
l = { type = "labels" },
ll = { type = "labels" },
}
local TERM_NA = "—"
local langs = {}
local handler_cache = {}
-- TODO: 3 collapsing modes? collapsed, uncollapsed (all locations matching the current entry title), uncollapsed (all)
-- Create the main table
local function __create_table()
return mw.html.create("table")
:addClass("dial-syn")
:addClass("wikitable")
:addClass("mw-collapsible")
:addClass("mw-collapsed")
:done()
end
-- Create the title row of the table
local function __create_title_row(columns, title, colour)
return mw.html.create("tr")
:tag("th")
:attr("colspan", #columns)
:css("background-color", colour)
:wikitext(title)
:done()
:done()
end
-- Create the header row with column names
local function __create_columns_row(columns, colour)
local tr = mw.html.create("tr"):done()
for _, col in ipairs(columns) do
tr:tag("th")
:css("background-color", colour)
:wikitext(col)
:done()
end
return tr
end
-- Create the row with "view map" and "edit data" links
local function __create_view_map_row(columns, colour, view_map, edit_link)
return mw.html.create("tr")
:tag("td")
:attr("colspan", #columns)
:css("text-align", "right")
:css("background-color", colour)
:wikitext(("[[%s|檢視地圖]];[[%s|編輯資料]]"):format(view_map, edit_link))
:done()
:done()
end
-- Helper to generate a link to create a missing data module
local function __prompt_create_data(module_path, preload_path)
local url = mw.uri.fullUrl(module_path, { action = 'edit', preload = preload_path })
return ("→Create [%s %s]?"):format(tostring(url), module_path)
end
-- Retrieve or load a language-specific handler module
local function __get_handler(code_main)
if not code_main then return nil end
if handler_cache[code_main] == nil then
local path = "Module:dialect synonyms/handlers/" .. code_main
handler_cache[code_main] = mw.title.new(path).exists and require(path) or false
end
return handler_cache[code_main] or nil
end
export.get_handler = __get_handler
function export.collect_aliases(varieties)
local alias_map = {}
local function __collect_recursive(node)
if node.name then
alias_map[node.name] = node.name
end
if node.aliases then
if type(node.aliases) == "table" then
for _, alias in ipairs(node.aliases) do
alias_map[alias] = node.name
end
elseif type(node.aliases) == "string" then
alias_map[node.aliases] = node.name
end
end
for _, child in ipairs(node) do
__collect_recursive(child)
end
end
for _, variety in ipairs(varieties) do
__collect_recursive(variety)
end
return alias_map
end
-- Process a single term object using the handler and standard normalization techniques.
-- This handles legacy syntax parsing (e.g. term:note) and applies language-specific logic.
function export.process_term(data_variety, data, invalid_langs)
if type(data.term) == "string" and (data_variety.code_main == 'ain' or data_variety.code_main == 'niv') and mw.ustring.match(data.term, '[:/]') then
-- XXX: LEGACY SYNTAX (Ainu/Nivkh only)
-- `word_n/tr:qual`
local temp_a, temp_b = mw.ustring.match(data.term, '^(.+):(.+)$')
if temp_a then data.term, data.q = temp_a, { temp_b } end
local temp_a, temp_b = mw.ustring.match(data.term, '^(.+)/(.+)$')
if temp_a then data.term, data.tr = temp_a, temp_b end
local temp_a, temp_b = mw.ustring.match(data.term, '^(.+)_(%d+)$')
if temp_a then data.term, data.ety_n = temp_a, temp_b end
end
-- Re-use language objects if possible to avoid overhead
if not langs[data_variety.code] then
local lang = m_languages.getByCode(data_variety.code, nil, true)
if not lang then
if invalid_langs then
table.insert(invalid_langs, data_variety.code)
end
lang = m_languages.getByCode(data_variety.code_main)
elseif lang:hasType("etymology-only") then
lang = lang:getFull()
end
langs[data_variety.code] = lang
end
data.lang = data.lang or langs[data_variety.code]
data.alt = data.alt or data.term
if data_variety.nolink then data.term = nil end
-- Language-specific processing via handlers (e.g. stripping Etymology N numbers)
local handler = __get_handler(data_variety.code_main)
if handler and handler.process then
data = handler.process(data_variety, data)
end
return data
end
-- Format a processed term object into a final string for display (link + qualifiers).
-- This uses Module:links to generate the standard Wiktionary link.
function export.format_term(data_variety, data)
if type(data) == "string" then return data end -- Already formatted or special string (e.g. TERM_NA)
local word
local handler = __get_handler(data_variety.code_main)
if handler and handler.format_term then
word = handler.format_term(data_variety, data)
elseif handler and handler.make_link then
word = handler.make_link(data)
else
word = m_links.full_link(data)
end
if (not handler or not handler.format_term) and data.q then
word = word .. " " .. require("Module:qualifier").format_qualifier(data.q)
end
return word
end
-- Format the display text for a variety or location (e.g. making parens small).
local function __format_text_display(data_variety, is_leaf)
if data_variety.text_display then
return data_variety
end
data_variety.text_display = data_variety.english or data_variety.name
if not data_variety.text_display then
return data_variety
end
data_variety.text_display = mw.ustring.gsub(data_variety.text_display, '(%(.+%))', '<small>%1</small>')
-- Language-specific display formatting via handlers (leaf nodes only)
if is_leaf then
local handler = __get_handler(data_variety.code_main)
if handler and handler.format_display then
data_variety = handler.format_display(data_variety)
end
end
-- Fallback: simple Wikipedia link
if (not data_variety.text_display_formatted) and data_variety.link then
data_variety.text_display = ('[[w:%s|%s]]'):format(data_variety.link, data_variety.text_display)
end
return data_variety
end
-- Main data fetching function.
function export.get_data(lang_code, term, id, demo_mode, skip_validation)
local module_path = "Module:dialect synonyms"
local synonym_data_path
local handler = __get_handler(lang_code)
if handler and handler.get_synonym_data_path then
synonym_data_path = handler.get_synonym_data_path(lang_code, term, id)
end
local language_data_path = module_path .. "/" .. lang_code
local term_path = term .. (id and id ~= "" and "/" .. id or "")
if not synonym_data_path then
synonym_data_path = language_data_path .. "/" .. term_path
end
-- Set defaults
local language_data_req = mw.title.new(language_data_path).exists and require(language_data_path) or nil
local synonym_data_req = mw.title.new(synonym_data_path).exists and require(synonym_data_path) or nil
if demo_mode then
synonym_data_path = language_data_path
synonym_data_req = {}
synonym_data_req.syns = {}
end
if (not language_data_req) or (not synonym_data_req) then
return nil, language_data_path, synonym_data_path -- Return paths for create prompts
end
if handler and handler.get_map_params then
local params = handler.get_map_params(language_data_req, synonym_data_req)
for k, v in pairs(params) do
language_data_req[k] = v
end
end
language_data_req.title = language_data_req.title or "Dialectal synonyms of %s"
language_data_req.columns = language_data_req.columns or { "Variety", "Location", "Words" }
language_data_req.notes = language_data_req.notes or {}
language_data_req.sources = language_data_req.sources or {}
language_data_req.note_aliases = language_data_req.note_aliases or {}
local collected_notes = {}
local collected_notes_set = {}
if synonym_data_req.note then
local notes = type(synonym_data_req.note) == "string" and { synonym_data_req.note } or synonym_data_req.note
for _, note_key in ipairs(notes) do
if note_key ~= "" and not collected_notes_set[note_key] then
collected_notes_set[note_key] = true
table.insert(collected_notes, note_key)
end
end
end
if synonym_data_req.notes then
for _, note_key in ipairs(synonym_data_req.notes) do
if note_key ~= "" and not collected_notes_set[note_key] then
collected_notes_set[note_key] = true
table.insert(collected_notes, note_key)
end
end
end
local expanded_sources = {}
if synonym_data_req.source then
for _, src_key in ipairs(synonym_data_req.source) do
local expanded = language_data_req.sources[src_key]
if expanded then
table.insert(expanded_sources, expanded)
elseif src_key ~= "" then
table.insert(expanded_sources, src_key)
end
end
end
local combined_sources = #expanded_sources > 0 and ("'''Sources:''' " .. table.concat(expanded_sources, "; ")) or nil
local allowed_keys = {
["syns"] = true,
["gloss"] = true,
["meaning"] = true,
["title"] = true,
["note"] = true,
["notes"] = true,
["source"] = true,
}
-- Validation tracking
local used_keys = {}
local duplicate_usage = {}
local duplicate_terms = {}
local invalid_langs = {}
local is_module_ns = mw.title.getCurrentTitle().nsText == "Module"
if skip_validation then is_module_ns = false end
local alias_map = export.collect_aliases(language_data_req.varieties)
if synonym_data_req.syns then
for alias, canonical in pairs(alias_map) do
if alias ~= canonical and synonym_data_req.syns[alias] then
synonym_data_req.syns[canonical] = synonym_data_req.syns[canonical] or {}
for _, term in ipairs(synonym_data_req.syns[alias]) do
table.insert(synonym_data_req.syns[canonical], term)
end
used_keys[alias] = true
end
end
end
local function __populate_tree(data_variety)
local new_node = m_table.shallowCopy(data_variety)
if #new_node == 0 then
-- Leaf node
if (synonym_data_req.syns[new_node.name]) then
if is_module_ns then
if used_keys[new_node.name] then
table.insert(duplicate_usage, new_node.name)
end
used_keys[new_node.name] = true
-- Check for duplicate terms within the same list
local seen_terms = {}
for _, t in ipairs(synonym_data_req.syns[new_node.name]) do
if seen_terms[t] then
if not duplicate_terms[new_node.name] then
duplicate_terms[new_node.name] = {}
end
local already_added = false
for _, dt in ipairs(duplicate_terms[new_node.name]) do
if dt == t then already_added = true break end
end
if not already_added then
table.insert(duplicate_terms[new_node.name], t)
end
end
seen_terms[t] = true
end
end
if (synonym_data_req.syns[new_node.name][1] ~= '') then
new_node.syns = synonym_data_req.syns[new_node.name]
end
end
if demo_mode then
new_node.syns = { '-' }
end
if (new_node.default) and (not new_node.syns) then
if new_node.default == 'module name' then
new_node.syns = { term } -- default fallback
end
end
if (new_node.syns) and (new_node.syns[1] == '-') then
new_node.nolink = true
new_node.syns[1] = TERM_NA
end
if (not new_node.syns) then
return nil
end
-- Process synonyms
local terms = new_node.syns
local terms_processed = {}
-- Special case for TERM_NA
if terms[1] == TERM_NA and #terms == 1 then
terms_processed = terms
else
local simple_processing = true
for _, term_str in ipairs(terms) do
if type(term_str) == "string" and term_str:find("[<,;]") then
simple_processing = false
break
end
end
if simple_processing then
for _, term_entry in ipairs(terms) do
local term_obj
if type(term_entry) == "table" then
term_obj = term_entry
else
term_obj = { term = term_entry }
end
table.insert(terms_processed, export.process_term(new_node, term_obj, invalid_langs))
end
else
local masked_terms = {}
for _, term_str in ipairs(terms) do
local masked_term = term_str:gsub("<(%/?sup[^>]*)>", "\1%1\2")
table.insert(masked_terms, masked_term)
end
local parsed_terms = m_param_utils.parse_list_with_inline_modifiers_and_separate_params({
processed_args = { masked_terms },
param_mods = PARAM_MODS,
termarg = 1,
track_module = "dialect synonyms",
-- l10n: 本模組在 format_term() 自行顯示限定詞,不讓 full_link() 再顯示一次
no_show_decorations = true,
})
for _, term_obj in ipairs(parsed_terms) do
if term_obj.term then
term_obj.term = term_obj.term:gsub("\1", "<"):gsub("\2", ">")
end
if term_obj.note and not collected_notes_set[term_obj.note] then
collected_notes_set[term_obj.note] = true
table.insert(collected_notes, term_obj.note)
end
if term_obj.q then
for i, qual in ipairs(term_obj.q) do
local alias = qual:match("^%[(.+)%]$")
if alias then
term_obj.q[i] = alias
if not collected_notes_set[alias] then
collected_notes_set[alias] = true
table.insert(collected_notes, alias)
end
end
end
end
table.insert(terms_processed, export.process_term(new_node, term_obj, invalid_langs))
end
end
end
new_node.syns = terms_processed
new_node = __format_text_display(new_node, true)
return new_node
else
-- Branch node
new_node = __format_text_display(new_node, false)
if not new_node.code then
new_node.code = (new_node.parent and new_node.parent.code or lang_code)
end
local valid_children = {}
for i, child_raw in ipairs(data_variety) do
local child_clone = m_table.shallowCopy(child_raw)
child_clone.parent = new_node -- Point to the new parent
child_clone.code_main = lang_code
child_clone.code = (child_clone.code or new_node.code)
child_clone.colour = (child_clone.colour or new_node.colour)
local populated_child = __populate_tree(child_clone)
if populated_child then
if populated_child.leaf_count then
new_node.leaf_count = (new_node.leaf_count or 0) + populated_child.leaf_count
else
new_node.leaf_count = (new_node.leaf_count or 0) + 1
end
table.insert(valid_children, populated_child)
end
end
for i = #new_node, 1, -1 do
new_node[i] = nil
end
for i, child in ipairs(valid_children) do
new_node[i] = child
end
if #valid_children == 0 then
return nil
end
return new_node
end
end
local populated_varieties = __populate_tree(language_data_req.varieties)
local expanded_notes = {}
for _, note_key in ipairs(collected_notes) do
if note_key == "" then
-- Skip empty
elseif note_key:match("^%[(.+)%]$") then
local alias = note_key:match("^%[(.+)%]$")
local expanded = language_data_req.note_aliases[alias] or (language_data_req.notes and language_data_req.notes[alias])
if expanded then
table.insert(expanded_notes, expanded)
else
table.insert(expanded_notes, alias)
end
else
local expanded = language_data_req.note_aliases[note_key] or (language_data_req.notes and language_data_req.notes[note_key])
if expanded then
table.insert(expanded_notes, expanded)
else
table.insert(expanded_notes, note_key)
end
end
end
local combined_notes = #expanded_notes > 0 and ("'''注:''' " .. table.concat(expanded_notes, " ")) or nil
-- Post-processing validation results
local validation = {
unused_keys = {},
invalid_keys = {},
invalid_langs = invalid_langs,
duplicate_usage = duplicate_usage,
duplicate_terms = duplicate_terms
}
if is_module_ns then
for loc, _ in pairs(synonym_data_req.syns) do
if not used_keys[loc] then
table.insert(validation.unused_keys, loc)
end
end
for key, _ in pairs(synonym_data_req) do
if not allowed_keys[key] then
table.insert(validation.invalid_keys, key)
end
end
end
local root_path = mw.title.getCurrentTitle().prefixedText == language_data_path
return {
varieties = populated_varieties,
properties = {
title = (root_path and "Dialectal varieties") or language_data_req.title,
columns = language_data_req.columns,
notes = language_data_req.notes,
combined_notes = combined_notes,
combined_sources = combined_sources,
gloss = (synonym_data_req.gloss ~= "" and synonym_data_req.gloss) or (synonym_data_req.meaning ~= "" and synonym_data_req.meaning) or nil,
map_template_path = "Template:dialect map/" .. lang_code .. "/" .. (root_path and ".all" or term_path),
synonym_data_path = synonym_data_path,
},
validation = validation
}
end
function export.show(frame)
local params = {
[1] = { required = true, default = "und" },
[2] = { default = mw.loadData("Module:headword/data").pagename },
['id'] = {},
['dpath syns'] = {}, -- Keeping param name for compatibility
['demo mode'] = { type = "boolean" },
}
local args = require("Module:parameters").process(frame:getParent().args, params, nil, "dialect synonyms", "show")
local lang_code = args[1]
local term = args[2]
local id = args['id']
local dataset, language_data_path, synonym_data_path = export.get_data(lang_code, term, id, args['demo mode'])
if not dataset then
if language_data_path and synonym_data_path then
local has_lang = mw.title.new(language_data_path).exists
if not has_lang then
return __prompt_create_data(language_data_path, 'Module:dialect synonyms/und')
else
return __prompt_create_data(synonym_data_path, 'Module:dialect synonyms/' .. lang_code .. '/')
end
else
return "Error loading data."
end
end
local varieties = dataset.varieties
local props = dataset.properties
local validation = dataset.validation
if not varieties then
return "No varieties found."
end
local dialect_synonyms_table
local handler = __get_handler(lang_code)
if handler and handler.create_table then
dialect_synonyms_table = handler.create_table()
else
dialect_synonyms_table = __create_table()
end
dialect_synonyms_table = dialect_synonyms_table:done()
local lang, lang_qualifier = string.match(args[1], '^(.+):(.+)$')
lang = lang or args[1]
local main_word_link = m_links.full_link({
lang = m_languages.getByCode(lang_qualifier and "en" or lang),
term = term,
gloss = props.gloss,
id = id,
}, "term")
local header_colour = 'var(--wikt-palette-green-0)'
local title_row
if handler and handler.create_title_row then
title_row = handler.create_title_row(props.columns, (props.title):format(main_word_link), header_colour)
else
title_row = __create_title_row(props.columns, (props.title):format(main_word_link), header_colour)
end
dialect_synonyms_table:node(title_row)
local map_row
if handler and handler.create_map_row then
map_row = handler.create_map_row(props.columns, header_colour, props.map_template_path, props.synonym_data_path)
else
map_row = __create_view_map_row(props.columns, header_colour, props.map_template_path, props.synonym_data_path)
end
dialect_synonyms_table:node(map_row)
local columns_row
if handler and handler.create_columns_row then
columns_row = handler.create_columns_row(props.columns, header_colour)
else
columns_row = __create_columns_row(props.columns, header_colour)
end
dialect_synonyms_table:node(columns_row)
dialect_synonyms_table:done()
-- Recursive function to render the tree of varieties into HTML rows
local function __render_tree(data_variety, tr)
if not data_variety then return end
if #m_table.numKeys(data_variety) == 0 then
-- Leaf node
if not tr then
tr = mw.html.create('tr'):done()
end
local syns_text_list = {}
for _, item in ipairs(data_variety.syns) do
table.insert(syns_text_list, export.format_term(data_variety, item))
end
local syns_str = table.concat(syns_text_list, ', ')
tr
:tag('th')
:attr('colspan', data_variety.colspan)
:attr('data-lat', data_variety.lat)
:attr('data-lon', data_variety.long)
:attr('data-term', data_variety.text_display)
:css("background-color", data_variety.colour)
:tag('span')
:wikitext(data_variety.text_display)
:done()
:done()
-- TODO: fully commit to [[WT:Palette]] and output `-0` colors for normal table cells?
:tag('td')
:css("background-color", data_variety.colour)
:wikitext(syns_str)
:done()
:done()
dialect_synonyms_table:node(tr):done()
else
-- Branch node
if (not tr) and (data_variety.parent) then
tr = mw.html.create('tr'):done()
end
if tr then
tr
:tag('th')
:attr('rowspan', data_variety.leaf_count)
:attr('colspan', data_variety.colspan)
:css("background-color", data_variety.colour)
:tag('span')
:wikitext(data_variety.text_display)
:done()
:done()
:done()
end
local first = true
for i, _ in m_table.sparseIpairs(data_variety) do
if not first then tr = nil end
__render_tree(data_variety[i], tr)
first = false
end
end
end
if handler and handler.render_table_content then
handler.render_table_content(varieties, dialect_synonyms_table)
else
__render_tree(varieties)
end
-- Output validation warnings (unused keys, duplicates)
local validation_errors = {}
if #validation.unused_keys > 0 then
table.sort(validation.unused_keys)
table.insert(validation_errors, {
track = "dialect synonyms/unused key",
warning = "Warning: The following locations are not present in the varieties data:",
items = validation.unused_keys
})
end
if #validation.invalid_keys > 0 then
table.sort(validation.invalid_keys)
table.insert(validation_errors, {
track = "dialect synonyms/invalid key",
warning = "Warning: The following keys are not allowed in the dataset:",
items = validation.invalid_keys
})
end
if #validation.invalid_langs > 0 then
table.sort(validation.invalid_langs)
table.insert(validation_errors, {
track = "dialect synonyms/invalid lang code",
warning = "Warning: The following language codes are invalid (falling back to main language):",
items = validation.invalid_langs
})
end
if #validation.duplicate_usage > 0 then
table.sort(validation.duplicate_usage)
table.insert(validation_errors, {
track = "dialect synonyms/duplicate key usage",
warning = "Warning: The following locations are used multiple times in the varieties data:",
items = validation.duplicate_usage
})
end
if next(validation.duplicate_terms) then
local items = {}
for loc, terms in pairs(validation.duplicate_terms) do
table.insert(items, loc .. ": " .. table.concat(terms, ", "))
end
table.sort(items)
table.insert(validation_errors, {
track = "dialect synonyms/duplicate terms",
warning = "Warning: The following locations contain duplicate terms:",
items = items
})
end
if #validation_errors > 0 then
local track = require("Module:debug/track")
local warning_div = mw.html.create("div")
:css("background-color", "var(--wikt-palette-red-1)")
:css("border", "1px solid var(--wikt-palette-red-5)")
:css("padding", "0.5em")
:css("margin", "0.5em 0")
for _, err in ipairs(validation_errors) do
track(err.track)
warning_div:tag("strong"):wikitext(err.warning):done()
local ul = warning_div:tag("ul"):css("margin-left", "1.5em")
for _, item in ipairs(err.items) do
ul:tag("li"):wikitext(item):done()
end
end
dialect_synonyms_table:node(warning_div:done())
end
-- Append combined notes row
local all_notes = {}
for _, note in ipairs(props.notes) do
table.insert(all_notes, note)
end
if props.combined_notes then
table.insert(all_notes, props.combined_notes)
end
if #all_notes > 0 then
local notes_text = table.concat(all_notes, "<br>")
dialect_synonyms_table:tag('tr'):tag('td'):attr("colspan", #props.columns):wikitext(notes_text):done():done():done()
end
if props.combined_sources then
dialect_synonyms_table:tag('tr'):tag('td'):attr("colspan", #props.columns):css("font-size", "90%"):wikitext(props.combined_sources):done():done():done()
end
return tostring(dialect_synonyms_table) .. require("Module:TemplateStyles")("Template:dialect synonyms/styles.css")
end
return export
t7nyccmsgafl97r4zaz9kqiim82hen2
Module:Object usage
828
1584342
9909092
9836778
2026-09-29T07:27:32Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909092
Scribunto
text/plain
local export = {}
local dump = mw.dumpObject
local m_links = require("Module:links")
local decorations_module = "Module:decorations"
local form_of_module = "Module:form of"
local headword_module = "Module:headword"
local labels_module = "Module:labels"
local parse_utilities_module = "Module:parse utilities"
local references_module = "Module:references"
local utilities_module = "Module:utilities"
local insert = table.insert
local concat = table.concat
local rsubn = mw.ustring.gsub
local rfind = mw.ustring.find
local force_cat = false
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
local qualifier_label_mod = {"q", "qq", "l", "ll"}
local qualifier_label_mod_with_starred_set = {}
for _, mod in ipairs(qualifier_label_mod) do
qualifier_label_mod_with_starred_set[mod] = true
qualifier_label_mod_with_starred_set[mod .. "*"] = true
end
--[==[
Parse one of the arguments of {{tl|+obj}} into a structure suitable for passing to `format_parsed_object`.
`object` is a string, the raw argument. `paramno` is a number or string indicating the parameter name or
number which the string in `object` came from, for error message purposes.
The format of the return value is an object with a field `arguments` containing a list of argument objects, one per
argument separated by `+` or `&`. Each argument object contains the following fields:
* `alternants`: a list of alternant objects, specifying the alternants separated by `/` or `+/`;
* `suppress_with`: a boolean, which is true for the arguments preceded by `&`;
* `q` (optional): a string, specifying an overall left qualifier given with <q*:...>, or an individual qualifier specified with <q:...> and promoted to overall-qualifier status;
* `qq` (optional): a string, specifying an overall right qualifier given with <qq*:...>, or an individual qualifier specified with <qq:...> and promoted to overall-qualifier status;
* `l` (optional): a list of strings, specifying overall left labels given with <l*:...>, or individual labels specified with <l:...> and promoted to overall-label status;
* `ll` (optional): a list of strings, specifying overall right labels given with <ll*:...>, or individual labels specified with <ll:...> and promoted to overall-label status.
Each alternant object contains the following fields:
* `form`: the alternant text itself;
* `is_term`: a boolean, which is true for literal terms (preceded by `:` or `::`);
* `is_postposition`: a boolean, which is true for postpositions (preceded by `::`);
* `case` (optional): the case of a literal term, either a string or a structure of the same sort as returned by `parse_object`;
* `t` (optional): a string, specifying the gloss (given using <...>);
* `separator (optional): the separator preceding the alternant; defaults to {" ''or'' "}, but will be set to {" ''and/or'' "} if '+/' is used;
* `tr` (optional): a string, specifying the transliteration (given using <tr:...>).
For example:
1. The raw string `:na rzecz(gen)<on behalf of someone> + :z(gen)/:od(gen)<l:in <<Middle Polish>>><from what>` maps to { {
["arguments"] = {
{
["alternants"] = {
{
["case"] = "gen",
["form"] = "na rzecz",
["is_postposition"] = false,
["is_term"] = true,
["t"] = "on behalf of someone",
},
},
["suppress_with"] = false,
},
{
["alternants"] = {
{
["case"] = "gen",
["form"] = "z",
["is_postposition"] = false,
["is_term"] = true,
},
{
["case"] = "gen",
["form"] = "od",
["is_postposition"] = false,
["is_term"] = true,
["l"] = table#10 {
"in <<Middle Polish>>",
},
["t"] = "from what",
},
},
["suppress_with"] = false,
},
},
}}
2. The raw string `:para(subject&pronoun<qq:optional> + pers&inf)<for someone to do something>` maps to { {
["arguments"] = {
{
["alternants"] = {
{
["case"] = {
["arguments"] = {
{
["alternants"] = {
{
["form"] = "subject",
["is_postposition"] = false,
["is_term"] = false,
},
},
["suppress_with"] = false,
},
{
["alternants"] = {
{
["form"] = "pronoun",
["is_postposition"] = false,
["is_term"] = false,
},
},
["qq"] = "optional",
["suppress_with"] = true,
},
{
["alternants"] = {
{
["form"] = "pers",
["is_postposition"] = false,
["is_term"] = false,
},
},
["suppress_with"] = false,
},
{
["alternants"] = {
{
["form"] = "inf",
["is_postposition"] = false,
["is_term"] = false,
},
},
["suppress_with"] = true,
},
},
},
["form"] = "para",
["is_postposition"] = false,
["is_term"] = true,
["t"] = "for someone to do something",
},
},
["suppress_with"] = false,
},
},
}}
]==]
function export.parse_object(object, paramno)
local function parse_one_form(run)
local function parse_err(msg)
error(msg .. ": '" .. concat(run) .. "'")
end
if #run == 1 and run[1] == "" then
error("Blank form not allowed")
end
local retval = {}
retval.form = run[1]
retval.form, retval.is_postposition = rsubb(retval.form, "^::", "")
if retval.is_postposition then
retval.is_term = true
else
retval.form, retval.is_term = rsubb(retval.form, "^:", "")
end
for i = 2, #run - 1, 2 do
if run[i + 1] ~= "" then
parse_err("Extraneous text '" .. run[i + 1] .. "' after modifier")
end
if run[i]:find("^%(") then
if not retval.is_term then
parse_err("Can't attach case '" .. run[i] .. "' to non-term")
end
local raw_case = run[i]:gsub("^%((.*)%)$", "%1")
if raw_case:find("[+/&<>()%[%]]") then
retval.case = export.parse_object(raw_case, ("%s:%s(...)"):format(retval.form, paramno))
else
retval.case = raw_case
end
else
local modtext = run[i]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. modtext .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+%*?):(.*)$")
if prefix then
if qualifier_label_mod_with_starred_set[prefix] or prefix == "t" or prefix == "id" or
prefix == "tr" or prefix == "ts" or prefix == "alt" or prefix == "ref" or
prefix == "pos" or prefix == "ng" then
if not retval.is_term and not qualifier_label_mod_with_starred_set[prefix] and
prefix ~= "ref" and prefix ~= "t" and prefix ~= "ng" then
parse_err("Can't attach prefix '" .. prefix .. "' to non-term")
end
local item_dest = prefix == "ref" and "refs" or prefix
if retval[item_dest] then
parse_err("Can't set two values for prefix '" .. prefix .. "'")
end
if prefix == "l" or prefix == "ll" or prefix == "l*" or prefix == "ll*" then
arg = require(labels_module).split_labels_on_comma(arg)
elseif prefix == "ref" then
arg = require(references_module).parse_references(arg, parse_err)
end
retval[item_dest] = arg
else
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. run[i])
end
else
retval.t = modtext
end
end
end
return retval
end
local m_parse_utilities = require(parse_utilities_module)
local parsed_object = {arguments = {}}
local orig_segments =
m_parse_utilities.parse_multi_delimiter_balanced_segment_run(object, {{"[", "]"}, {"(", ")"}, {"<", ">"}})
-- rejoin bracketed segments with nearby ones; we only parse them to ensure that we leave alone parens and
-- angle brackets inside of square brackets.
local joined_segments = {}
local i = 1
while i <= #orig_segments do
local segment = orig_segments[i]
if i % 2 == 0 and segment:find("^%[") then
joined_segments[#joined_segments] = joined_segments[#joined_segments] .. segment .. orig_segments[i + 1]
i = i + 2
else
insert(joined_segments, segment)
i = i + 1
end
end
local split_runs =
m_parse_utilities.split_alternating_runs(joined_segments, "%s*[+/&]+%s*", "preserve splitchar")
-- Now parse the forms.
i = 1
while i <= #split_runs do
if i == 1 and #split_runs[1] == 1 and split_runs[1][1] == "" and #split_runs > 1 and
rfind(split_runs[2][1], "^%s*&%s*$") then
-- Blank argument at beginning followed by & to suppress the +. Ignore it.
else
local form = parse_one_form(split_runs[i])
local prev_joiner = i > 1 and rsub(split_runs[i - 1][1], "^%s*(.-)%s*$", "%1")
if prev_joiner == "/" or prev_joiner == "+/" then
local this_alternants = parsed_object.arguments[#parsed_object.arguments].alternants
-- Join to the previous alternant.
insert(this_alternants, form)
if prev_joiner == "+/" then
this_alternants[#this_alternants].separator = "和/或"
elseif not form.is_term and form.form == "etc." then
for j = 2, #this_alternants do
this_alternants[j].separator = ","
end
end
else
local suppress_with = prev_joiner == "&"
-- Create a new argument.
insert(parsed_object.arguments, {alternants = {form}, suppress_with = suppress_with})
end
end
i = i + 2
end
-- Now move qualifiers up as necessary.
local function parse_err(msg)
error(("%s: %s=%s"):format(msg, paramno, object))
end
for _, argument in ipairs(parsed_object.arguments) do
for j, alternant in ipairs(argument.alternants) do
if #argument.alternants == 1 then
-- If there's only one alternant, convert regular qualifiers to starred versions if there's not
-- already a starred version.
for _, mod in ipairs(qualifier_label_mod) do
if alternant[mod] and not alternant[mod .. "*"] then
alternant[mod .. "*"] = alternant[mod]
alternant[mod] = nil
end
end
end
if j < #argument.alternants then
-- Starred versions cannot be attached to non-final alternants.
for _, mod in ipairs(qualifier_label_mod) do
if alternant[mod .. "*"] then
parse_err(("Starred version '%s' of label or qualifier must be attached to last alternant"):
format(mod .. "*"))
end
end
else
-- Starred versions attached to final alternants should be moved up to argument level.
for _, mod in ipairs(qualifier_label_mod) do
if alternant[mod .. "*"] then
argument[mod] = alternant[mod .. "*"]
alternant[mod .. "*"] = nil
end
end
end
end
end
return parsed_object
end
--[==[
Format a parsed object (the return value of `parse_object`) into a string. `recursive_suppress_with`, if
specified, suppresses the initial word `with` normally displayed before the parsed object if the user
didn't explicitly suppress it by preceding the object with `&`.
]==]
function export.format_parsed_object(lang, parsed_object, recursive_suppress_with)
local argument_parts = {}
local multiple_alternants = false
for _, argument in ipairs(parsed_object.arguments) do
if #argument.alternants > 1 then
multiple_alternants = true
break
end
end
local used_with_in_prefix = false
for i, argument in ipairs(parsed_object.arguments) do
local alternant_parts = {}
local prefix, separator
local suppress_with = argument.suppress_with or i == 1 and recursive_suppress_with
if not suppress_with then
if not used_with_in_prefix then
separator = i > 1 and " " or ""
prefix = "接 "
used_with_in_prefix = true
elseif multiple_alternants then
separator = ","
prefix = "接 "
else
separator = " "
prefix = "和 "
end
else
separator = i > 1 and " " or ""
prefix = ""
end
-- If there are multiple alternants and a non-final alternant has a gloss, assume that each alternant has
-- its own gloss, or at least that the gloss on the final alternant doesn't apply to all alternants.
-- Otherwise, we assume the gloss on the final alternant applies to all alternants. This affects the
-- placement of right labels and qualifiers vis-à-vis the gloss: if there's a single gloss applying to
-- multiple alternants, we put the right labels and qualifiers before gloss, otherwise after.
local gloss_with_non_final_alternant = false
for j, alternant in ipairs(argument.alternants) do
if j < #argument.alternants and alternant.t then
gloss_with_non_final_alternant = true
break
end
end
-- Process each alternant.
for j, alternant in ipairs(argument.alternants) do
-- Construct the "case text" for the alternant (what goes in parens). We always assume that a given case
-- text goes only with its associated alternant, unlike for the gloss (see above).
local case_text
--local text_classes = "object-usage-form-of-tag"
local text_classes = "object-usage-tag"
if alternant.case then
if type(alternant.case) == "string" then
case_text = require(form_of_module).tagged_inflections {
lang = lang, tags = {alternant.case}, text_classes = text_classes, nocat = true
}
else
case_text = export.format_parsed_object(lang, alternant.case, "suppress with")
end
if alternant.is_postposition then
case_text = "(" .. case_text .. " +)"
else
case_text = "+ " .. case_text
end
end
-- Construct the argument itself (inflection tag or literal word), and add any case text.
local form
if alternant.is_term then
local term = alternant.form
if term == "" then
term = nil
end
form = m_links.full_link({lang = lang, term = term, alt = alternant.alt, id = alternant.id,
tr = alternant.tr, ts = alternant.ts, pos = not alternant.is_postposition and case_text or nil},
"bold")
if alternant.is_postposition and case_text then
form = case_text .. " " .. form
end
else
form = require(form_of_module).tagged_inflections {
lang = lang, tags = {alternant.form}, text_classes = text_classes, nocat = true
}
form = mw.text.trim(form:gsub("<span[^>]*>", ""):gsub("</span>", ""):gsub("''", "")) -- l10n: 移除斜體標記和首尾空格
if case_text then
if alternant.is_postposition then
form = case_text .. " " .. form
else
form = form .. " (" .. case_text .. ")"
end
end
end
local part = form
local function add_decorations_to_alternant(refs)
if alternant.q or alternant.qq or alternant.l or alternant.ll or refs then
part = require(decorations_module).format_decorations {
text = part,
lang = lang,
q = alternant.q and {alternant.q} or nil,
qq = alternant.qq and {alternant.qq} or nil,
l = alternant.l,
ll = alternant.ll,
refs = refs,
}
end
end
local meaning_text = ""
if alternant.t then
meaning_text = " <small>「" .. alternant.t .. "」</small>"
end
if alternant.ng then
meaning_text = meaning_text .. " <small>(" .. alternant.ng .. ")</small>"
end
if gloss_with_non_final_alternant or #argument.alternants == 1 then
-- See above. If there is only one alternant, or multiple alternants where each gloss goes with an
-- individual alternant, right labels and qualifiers go after the gloss, otherwise before. The
-- reference always goes directly after the form (before the gloss), so if the right labels and
-- qualifiers go after the gloss, we need to split up their handling.
if alternant.refs then
part = require(decorations_module).format_decorations {
text = part,
lang = lang,
refs = alternant.refs,
}
end
part = part .. meaning_text
add_decorations_to_alternant()
else
add_decorations_to_alternant(alternant.refs)
part = part .. meaning_text
end
if j > 1 and not used_with_in_prefix and not recursive_suppress_with then
-- If we used e.g. {{+obj|ca|&transitive/:en}} to suppress the initial ''with'', we want it
-- to appear after the ''or'' so we get ''transitive or with [[en]]'' rather than just
-- ''transitive or [[en]]''.
part = "接" .. part
used_with_in_prefix = true
end
if j > 1 then
insert(alternant_parts, alternant.separator or "或")
end
insert(alternant_parts, part)
end
local part = prefix .. concat(alternant_parts)
if argument.q or argument.qq or argument.l or argument.ll then
part = require(decorations_module).format_decorations {
text = part,
lang = lang,
q = argument.q and {argument.q} or nil,
qq = argument.qq and {argument.qq} or nil,
l = argument.l,
ll = argument.ll,
}
end
insert(argument_parts, separator .. part)
end
return concat(argument_parts)
end
function export.show_obj(frame)
local parent_args = frame:getParent().args
local params = {
[1] = {required = true, type = "language", template_default = "und"},
[2] = {list = true},
catpos = true,
sort = true,
}
local args = require("Module:parameters").process(parent_args, params)
local lang = args[1]
local categories = {}
local catpos = args.catpos and require(headword_module).canonicalize_pos(args.catpos) or nil
local parsed_objects = {}
for argno, object in ipairs(args[2]) do
if object == ";" then
-- bare semicolon separator, to create a higher-level separation between parameters than
-- the normal "; or ..." separator.
if not parsed_objects[1] then
error("Can't have bare semicolon separator parameter as first parameter")
end
else
-- argno + 1 because object arguments begin at 2=
local parsed_object = export.parse_object(object, argno + 1)
if argno > 1 and args[2][argno - 1] == ";" then
parsed_object.separator = ";"
end
if catpos then
for _, argument in ipairs(parsed_object.arguments) do
for _, alternant in ipairs(argument.alternants) do
if not alternant.is_term then
local expansion = require(form_of_module).lookup_shortcut(alternant.form, lang)
insert(categories, ("%s %s governing the %s"):format(lang:getCanonicalName(), catpos, expansion))
end
end
end
end
insert(parsed_objects, parsed_object)
end
end
-- Now generate the text.
local object_parts = {}
local function ins(txt)
insert(object_parts, txt)
end
ins(require("Module:TemplateStyles")("Module:object usage/style.css"))
ins("[")
for i, parsed_object in ipairs(parsed_objects) do
if i > 1 then
if parsed_object.separator == ";" then
ins(";此外,")
else
ins(";或")
end
end
ins(export.format_parsed_object(lang, parsed_object, false))
end
ins("]")
if categories[1] then
ins(require(utilities_module).format_categories(categories, lang, args.sort, nil, force_cat))
end
return concat(object_parts)
end
function export.show_aux(frame)
local parent_args = frame:getParent().args
local params = {
[1] = {required = true, template_default = "und"},
[2] = {list = true, allow_holes = true, template_default = "助動詞"},
["alt"] = {list = true, allow_holes = true},
["q"] = {list = true, allow_holes = true},
["id"] = {list = true, allow_holes = true},
["senseid"] = {list = true, alias_of = "id"},
["means"] = {list = true, allow_holes = true, template_default = "含義"},
}
local args = require("Module:parameters").process(parent_args, params)
local lang = require("Module:languages").getByCode(args[1], 1)
-- Find the maximum index among any of the list parameters.
local maxmaxindex = 0
for k, v in pairs(args) do
if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then
maxmaxindex = v.maxindex
end
end
local parts = {}
for i = 1, maxmaxindex do
local term = m_links.full_link({lang = lang, term = args[2][i], alt = args.alt[i], id = args.id[i]}, "term")
if args.means[i] then
term = term .. " = " .. args.means[i]
end
if args.q[i] then
term = require("Module:qualifier").format_qualifier(args.q[i]) .. " " .. term
end
insert(parts, term)
end
return "[助動詞 " .. require("Module:table").serialCommaJoin(parts, {conj = "或"}) .. "]"
end
return export
f7mnv6gi67kaw6doa0cz4f9edam0ecs
Module:Place
828
1629826
9909096
9906081
2026-09-29T07:27:46Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909096
Scribunto
text/plain
local export = {}
local force_cat = false -- set to true for testing
local m_placetypes = require("Module:place/placetypes")
local m_links = require("Module:links")
local memoize = require("Module:memoize")
local m_strutils = require("Module:string utilities")
local m_table = require("Module:table")
-- Chinese localization data (zh_name, zh_suffix, qualifiers, extra-info labels, etc.)
local m_zh_data = require("Module:place/data")
local debug_track_module = "Module:debug/track"
local en_utilities_module = "Module:en-utilities"
local form_of_module = "Module:form of"
local languages_module = "Module:languages"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local parameter_utilities_module = "Module:parameter utilities"
local utilities_module = "Module:utilities"
local zhlang = require(languages_module).getByCode("zh")
local rmatch = m_strutils.match
local rfind = m_strutils.find
local ulen = m_strutils.len
local split = m_strutils.split
local dump = mw.dumpObject
local insert = table.insert
local concat = table.concat
local pluralize = require(en_utilities_module).pluralize
local extend = m_table.extend
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local internal_error = m_placetypes.internal_error
local process_error = m_placetypes.process_error
local placetype_data = m_placetypes.placetype_data
-- Shorthand for the ZH localization string table.
local zh_strings = m_zh_data.zh_strings
--[==[
ZH fork of [[Module:place]] (enwikt). Architecture and data structures are identical to the English version;
only the display-formatting functions are changed to produce Chinese-language definitions.
Key structural differences from enwikt:
* Old-style holonyms are reversed (big→small containment order) and joined with 、/和 rather than "in X, Y".
* Sentence pattern: 位於[holonyms]的[placetype] (or [holonyms]的[placetype] for "of"-type placetypes).
* No articles (a/an/the) in generated text.
* Pref/suf holonym affixes append the Chinese zh_suffix instead of English "placetype of X".
* Translations are joined with "," (full-width comma); parenthetical glosses use ().
* Extra-info labels (capital, largest city, etc.) come from Module:place/data zh_extra_info_labels.
* sentence_style is always true for ZH (extra info uses "。" separator, not ";").
]==]
--[==[ var:
List specifying the allowed form-of directives, used for former names, official names, abbreviations, etc. of places.
The key is the form-of directive and the value is an object with the following properties:
* `text`: The actual text displayed before the terms. If the value is `+`, the key is used as the text. If the value is
a function, it is passed a single argument, the overall place spec (see comment at top of file) and should return
the text to be displayed.
* `type_prefix`: The prefix used to generate the placetype for looking up the appropriate category or categories in the
placetype data structure. Can be omitted if there are no categories associated with the directive.
* `conjunction`: The conjunction used to join multiple terms, defaulting to `and`.
* `cat`: Additional category or categories to add the term to, whenever this particular directive is used.
* `default_foreign`: If specified, the default language of terms given along with this directive is the language in
{{para|1}}; otherwise it is English.
]==]
export.all_form_of_directives = {
["former name of"] = {text = "+", type_prefix = "FORMER_NAME_OF"},
["fmr of"] = {alias_of = "former name of"},
["ancient name of"] = {text = "+", type_prefix = "FORMER_NAME_OF"},
["official name of"] = {text = "+", type_prefix = "OFFICIAL_NAME_OF"},
["former official name of"] = {text = "+", type_prefix = "FORMER_OFFICIAL_NAME_OF"},
["long form of"] = {text = "+", type_prefix = "LONG_FORM_OF"},
["former long form of"] = {text = "+", type_prefix = "FORMER_LONG_FORM_OF"},
["nickname for"] = {text = "+", type_prefix = "NICKNAME_FOR"},
["official nickname for"] = {text = "+", type_prefix = "OFFICIAL_NICKNAME_FOR"},
["former nickname for"] = {text = "+", type_prefix = "FORMER_NICKNAME_FOR"},
["derogatory name for"] = {text = "[[Appendix:Glossary#derogatory|derogatory]] name for", type_prefix = "DEROGATORY_NAME_FOR"},
["synonym of"] = {text = "+"},
["syn of"] = {alias_of = "synonym of"},
["abbreviation of"] = {text = "[[Appendix:Glossary#abbreviation|abbreviation]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:abbreviations",
default_foreign = true},
["abbr of"] = {alias_of = "abbreviation of"},
["abbrev of"] = {alias_of = "abbreviation of"},
["initialism of"] = {text = "[[Appendix:Glossary#initialism|initialism]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:initialisms",
default_foreign = true},
["init of"] = {alias_of = "initialism of"},
["acronym of"] = {text = "[[Appendix:Glossary#acronym|acronym]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:acronyms",
default_foreign = true},
["syllabic abbreviation of"] = {text = "[[Appendix:Glossary#syllabic abbreviation|syllabic abbreviation]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:syllabic abbreviations",
default_foreign = true},
["sylabbr of"] = {alias_of = "syllabic abbreviation of"},
["sylabbrev of"] = {alias_of = "syllabic abbreviation of"},
["ellipsis of"] = {text = "[[Appendix:Glossary#ellipsis|ellipsis]] of", type_prefix = "ELLIPSIS_OF", cat = "cln:ellipses",
default_foreign = true},
["ellip of"] = {alias_of = "ellipsis of"},
["clipping of"] = {text = "[[Appendix:Glossary#clipping|clipping]] of", type_prefix = "CLIPPING_OF", cat = "cln:clippings",
default_foreign = true},
["clip of"] = {alias_of = "clipping of"},
["alternative form of"] = {text = "+", default_foreign = true},
["alt form"] = {alias_of = "alternative form of"},
["alternative spelling of"] = {text = "+", default_foreign = true},
["alt spell"] = {alias_of = "alternative spelling of"},
["alt sp"] = {alias_of = "alternative spelling of"},
["dated form of"] = {text = "[[Appendix:Glossary#dated|dated]] form of", type_prefix = "DATED_FORM_OF", cat = "cln:dated forms",
default_foreign = true},
["dated form"] = {alias_of = "dated form of"},
["dated spelling of"] = {text = "[[Appendix:Glossary#dated|dated]] spelling of", type_prefix = "DATED_FORM_OF", cat = "cln:dated forms",
default_foreign = true},
["dated spell"] = {alias_of = "dated spelling of"},
["dated sp"] = {alias_of = "dated spelling of"},
["archaic form of"] = {text = "[[Appendix:Glossary#archaic|archaic]] form of", type_prefix = "ARCHAIC_FORM_OF", cat = "cln:archaic forms",
default_foreign = true},
["arch form"] = {alias_of = "archaic form of"},
["archaic spelling of"] = {text = "[[Appendix:Glossary#archaic|archaic]] spelling of", type_prefix = "ARCHAIC_FORM_OF", cat = "cln:archaic forms",
default_foreign = true},
["arch spell"] = {alias_of = "archaic spelling of"},
["arch sp"] = {alias_of = "archaic spelling of"},
["obsolete form of"] = {text = "[[Appendix:Glossary#obsolete|obsolete]] form of", type_prefix = "OBSOLETE_FORM_OF", cat = "cln:obsolete forms",
default_foreign = true},
["obs form"] = {alias_of = "obsolete form of"},
["obsolete spelling of"] = {text = "[[Appendix:Glossary#obsolete|obsolete]] spelling of", type_prefix = "OBSOLETE_FORM_OF", cat = "cln:obsolete forms",
default_foreign = true},
["obs spell"] = {alias_of = "obsolete spelling of"},
["obs sp"] = {alias_of = "obsolete spelling of"},
}
local function get_seat_text(overall_place_spec)
local placetype = overall_place_spec.descs[1].placetypes[1]
if placetype == "county" or placetype == "counties" then
return "county seat"
elseif placetype == "parish" or placetype == "parishes" then
return "parish seat"
elseif placetype == "borough" or placetype == "boroughs" then
return "borough seat"
else
return "seat"
end
end
--[==[ var:
List specifying the allowed arguments containing extra information that is sometimes added to a definition, such as the
capital, largest city, modern name, official name, etc., along with associated properties; displayed in the order given.
See enwikt [[Module:place]] for full documentation of each field.
]==]
export.extra_info_args = {
{arg = "modern", text = "+", conjunction = "or", display_even_when_dropped = true},
{arg = "now", text = "now,", conjunction = "or", display_even_when_dropped = true},
{arg = "full", text = "in full,", conjunction = "or", display_even_when_dropped = true},
{arg = "short", text = "short form", conjunction = "or"},
{arg = "abbr", text = "abbreviation", conjunction = "or"},
{arg = "former", text = "formerly,"},
{arg = "official", text = "official name", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "capital", text = "+", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "largest city", text = "+", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "caplc", text = "capital and largest city", match_sentence_style = true, auto_plural = false,
with_colon = true},
{arg = "seat", text = get_seat_text, match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "shire town", text = "+", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "headquarters", text = "+", match_sentence_style = true, auto_plural = false, with_colon = true},
{arg = "center", text = "administrative center", match_sentence_style = true, auto_plural = false, with_colon = true},
{arg = "centre", text = "administrative centre", match_sentence_style = true, auto_plural = false, with_colon = true},
}
export.extra_info_arg_map = {}
for _, spec in ipairs(export.extra_info_args) do
export.extra_info_arg_map[spec.arg] = spec
end
----------- Wikicode utility functions
-- Return a wikilink link {{l|language|text}}
local function link(text, langcode, id)
if not langcode then
return text
end
return m_links.language_link( -- l10n, avoid simp/trad or applying special fonts
{
term = text,
alt = text,
lang = require(languages_module).getByCode(langcode, true, "allow etym"),
id = id
}
)
end
---------- Basic utility functions
-- Add the page to a tracking "category". To see the pages in the "category",
-- go to [[Wiktionary:Tracking/place/PAGE]] and click on "What links here".
local function track(page)
require(debug_track_module)("place/" .. page)
return true
end
local function ucfirst_all(text)
if text:find(" ") then
local parts = split(text, " ", true)
for i, part in ipairs(parts) do
parts[i] = m_strutils.ucfirst(part)
end
return concat(parts, " ")
else
return m_strutils.ucfirst(text)
end
end
local function lc(text)
return mw.getContentLanguage():lc(text)
end
---------- Argument parsing functions and utilities
-- Split an argument on comma, but not comma followed by whitespace.
local function split_on_comma(val)
if val:find(",") then
return require(parse_interface_module).split_on_comma(val)
else
return {val}
end
end
-- Split an argument on slash, but not slash occurring inside of HTML tags like </span> or <br />.
local function split_on_slash(arg)
if arg:find("<") then
local m_parse_utilities = require(parse_utilities_module)
-- We implement this by parsing balanced segment runs involving <...>, and splitting on slash in the remainder.
-- The result is a list of lists, so we have to rejoin the inner lists by concatenating.
local segments = m_parse_utilities.parse_balanced_segment_run(arg, "<", ">")
local slash_separated_groups = m_parse_utilities.split_alternating_runs(segments, "/")
for i, group in ipairs(slash_separated_groups) do
slash_separated_groups[i] = concat(group)
end
return slash_separated_groups
else
return split(arg, "/", true)
end
end
-- Implement "implications", i.e. where the presence of a given holonym causes additional holonym(s) to be added.
-- Implications apply only to categorization.
local function handle_category_implications(place_descriptions, implication_data)
for i, desc in ipairs(place_descriptions) do
if desc.holonyms then
local new_holonyms = {}
for _, holonym in ipairs(desc.holonyms) do
insert(new_holonyms, holonym)
local imp_data = m_placetypes.get_equiv_placetype_prop(holonym.placetype, function(pt)
local implication = implication_data[pt] and implication_data[pt][holonym.unlinked_placename]
if implication then
return implication
end
end)
if imp_data then
for _, holonym_to_add in ipairs(imp_data) do
local split_holonym = split_on_slash(holonym_to_add)
if #split_holonym ~= 2 then
internal_error("Invalid holonym in implications: %s", holonym_to_add)
end
local holonym_placetype, holonym_placename = unpack(split_holonym, 1, 2)
local new_holonym = {
placetype = holonym_placetype, unlinked_placename = holonym_placename
}
insert(new_holonyms, new_holonym)
m_placetypes.key_holonym_into_place_desc(desc, new_holonym)
end
end
end
desc.holonyms = new_holonyms
end
end
end
-- Split a holonym (e.g. "continent/Europe" or "country/en:Italy" or "in southern" or "r:suf/O'Higgins" or
-- "c/Austria,Germany,Czech Republic") into its components. Return a list of holonym objects (see top of file). Note
-- that if there isn't a slash in the holonym (e.g. "in southern"), the `placetype` field of the holonym will be nil.
-- Placetype aliases (e.g. "r" for "region") and placename aliases (e.g. "US" or "USA" for "United States") will be
-- expanded.
local function split_holonym(raw)
local no_display, combined_holonym = raw:match("^(!)(.*)$")
no_display = not not no_display
combined_holonym = combined_holonym or raw
local suppress_comma, combined_holonym_without_comma = combined_holonym:match("^(%*)(.*)$")
suppress_comma = not not suppress_comma
combined_holonym = combined_holonym_without_comma or combined_holonym
local holonym_parts = split_on_slash(combined_holonym)
if #holonym_parts == 1 then
-- `unlinked_placename` should not be used.
return {{display_placename = combined_holonym, no_display = no_display, suppress_comma = suppress_comma}}
end
-- Rejoin further slashes in case of slash in holonym placename, e.g. Admaston/Bromley.
local placetype = holonym_parts[1]
local placename = concat(holonym_parts, "/", 2)
-- Check for modifiers after the holonym placetype.
local split_holonym_placetype = split(placetype, ":", true)
placetype = split_holonym_placetype[1]
local affix_type
local saw_also
local saw_the
for i = 2, #split_holonym_placetype do
local modifier = split_holonym_placetype[i]
if modifier == "also" then
if saw_also then
error(("Modifier ':also' occurs twice in holonym '%s'"):format(combined_holonym))
end
saw_also = true
elseif modifier == "the" then
if saw_the then
error(("Modifier ':the' occurs twice in holonym '%s'"):format(combined_holonym))
end
saw_the = true
elseif modifier == "pref" or modifier == "Pref" or modifier == "suf" or modifier == "Suf" or
modifier == "noaff" then
if affix_type then
error(("Affix-type modifier ':%s' occurs twice in holonym '%s'"):format(modifier, combined_holonym))
end
affix_type = modifier
else
error(("Unrecognized holonym placetype modifier '%s', should be one of " ..
"'pref', 'Pref', 'suf', 'Suf', 'noaff', 'also' or 'the'"):format(modifier))
end
end
placetype = m_placetypes.resolve_placetype_aliases(placetype)
local holonyms = split_on_comma(placename)
local pluralize_affix = #holonyms > 1
local affix_holonym_index = (affix_type == "pref" or affix_type == "Pref") and 1 or affix_type == "noaff" and 0 or
#holonyms
for i, placename in ipairs(holonyms) do
-- Check for langcode before the holonym placename, but don't get tripped up by Wikipedia links, which begin
-- "[[w:...]]" or "[[wikipedia:]]".
local langcode, placename_without_langcode = rmatch(placename, "^([^%[%]]-):(.*)$")
if langcode then
placename = placename_without_langcode
end
placename = m_placetypes.resolve_placename_display_aliases(placetype, placename)
holonyms[i] = {
placetype = placetype,
display_placename = placename,
unlinked_placename = m_placetypes.remove_links_and_html(placename),
langcode = langcode,
affix_type = i == affix_holonym_index and affix_type or nil,
pluralize_affix = i == affix_holonym_index and pluralize_affix,
suppress_affix = i ~= affix_holonym_index,
no_display = no_display,
suppress_comma = suppress_comma,
continue_cat_loop = saw_also,
force_the = i == 1 and saw_the,
}
end
return holonyms
end
local get_param_mods = memoize(function()
local m_param_utils = require(parameter_utilities_module)
return m_param_utils.construct_param_mods {
{group = {"link", "q", "l", "ref"}},
{param = "eq"},
{param = "conj", set = {["and"] = true, ["or"] = true, ["and/or"] = true}, overall = true},
}
end)
local function parse_term_with_inline_modifiers(term, paramname, default_lang)
local function generate_obj(raw_term, parse_err)
local obj = require(parameter_utilities_module).generate_obj_maybe_parsing_lang_prefix {
term = raw_term,
parse_err = parse_err,
parse_lang_prefix = true,
}
obj.lang = obj.lang or default_lang
obj.show_decorations = true
return obj
end
return require(parse_interface_module).parse_inline_modifiers(term, {
paramname = paramname,
param_mods = get_param_mods(),
generate_obj = generate_obj,
splitchar = ",",
outer_container = {},
})
end
local function parse_form_of_directive(arg, lang, form_of_overridden_args)
local form_of_directive, raw_terms = arg:match("^@([a-z -]+):(.*)$")
if not form_of_directive then
error("Misformatted @-directive: " .. dump(arg) .. " (directive name must be lowercase English letters and spaces, e.g. @official name of:比利時)")
end
if not export.all_form_of_directives[form_of_directive] then
local known_directives = {}
for k, _ in pairs(export.all_form_of_directives) do
insert(known_directives, '"' .. k .. '"')
end
table.sort(known_directives)
error(("Unrecognized form-of directive %s in @-directive %s; recognized directives are %s"):format(
dump(form_of_directive), dump(arg), concat(known_directives, ", ")))
end
local spec = export.all_form_of_directives[form_of_directive]
local canonical_directive = form_of_directive
if spec.alias_of then
canonical_directive = spec.alias_of
spec = export.all_form_of_directives[canonical_directive]
if not spec then
internal_error("Form-of directive alias %s points to %s, which is not a directive",
"@" .. form_of_directive, canonical_directive)
elseif spec.alias_of then
internal_error("Form-of directive alias %s points to %s, which is also an alias",
"@" .. form_of_directive, canonical_directive)
end
end
local default_foreign = spec.default_foreign
local directive_param = "@" .. form_of_directive
if form_of_overridden_args and form_of_overridden_args[canonical_directive] then
raw_terms = form_of_overridden_args[canonical_directive].new_value
local new_directive = form_of_overridden_args[canonical_directive].new_directive
local new_spec = export.all_form_of_directives[new_directive]
if not new_spec then
error(("Internal error: [[Module:transclude]] passed in unrecognized replacement directive '@%s'"):
format(new_directive))
end
if new_spec.alias_of then
error(("Internal error: [[Module:transclude]] passed in replacement directive alias '@%s', " ..
"should be canonical"):format(new_directive))
end
if new_directive ~= canonical_directive then
directive_param = directive_param .. (" (replaced with @%s)"):format(new_directive)
canonical_directive = new_directive
spec = new_spec
end
default_foreign = true
end
local terms = parse_term_with_inline_modifiers(raw_terms, directive_param,
default_foreign and lang or zhlang)
return {
directive = canonical_directive,
terms = terms.terms,
conj = terms.conj,
spec = spec,
}
end
-- Parse an argument containing extra information that is sometimes added to a definition, such as the capital, largest
-- city, modern name, official name, etc. `args` is the value from the parsed argument structure and can be either nil,
-- a string or a list. `spec` is the extra info spec corresponding to the type of extra info. Each value in `args` can
-- be a comma-separated list of terms with inline modifiers attached.
local function parse_extra_info_arg(args, spec, default_lang)
if not args then
return nil
end
if type(args) ~= "table" then
args = {args}
end
if not args[1] then
return nil
end
local terms = nil
local conj
for i, arg in ipairs(args) do
local this_terms = parse_term_with_inline_modifiers(arg, spec.arg .. (i == 1 and "" or i), default_lang)
local thisconj = this_terms.conj
if not conj then
conj = thisconj
elseif thisconj and conj ~= thisconj then
error(("Two different conjunctions '%s' and '%s' specified for |%s=; you only need to specify the " ..
"conjunction once"):format(conj, thisconj))
end
if not terms then
terms = this_terms.terms
else
m_table.extend(terms, this_terms.terms)
end
end
return {
spec = spec,
terms = terms,
conj = conj,
}
end
--[==[
Parse a "new-style" place description, with placetypes and holonyms surrounded by `<<...>>` amid otherwise raw text.
Return value is a place description object as documented at the top of the file. Exported for use by
[[Module:demonyms]].
]==]
function export.parse_new_style_place_desc(text, lang, form_of_directives, form_of_overridden_args)
local placetypes = {}
local segments = split(text, "<<(.-)>>")
local retval = {holonyms = {}, order = {}}
local form_of_directives_already_present = form_of_directives and not not form_of_directives[1]
for i, segment in ipairs(segments) do
if i % 2 == 1 then
insert(retval.order, {type = "raw", value = segment})
elseif segment:find("@") then
if not form_of_directives then
error(("Form-of directive '%s' not allowed in this context"):format(segment))
elseif form_of_directives_already_present then
error(("Saw form-of directive '%s' in new-style place desc followed by direct (separate-parameter) form-of directives; not allowed"):format(
segment))
elseif placetypes[1] or retval.holonyms[1] then
error(("Form-of directive '%s' must come first, before placetypes and holonyms"):format(segment))
else
local form_of_directive = parse_form_of_directive(segment, lang, form_of_overridden_args)
if not retval.order[1] or retval.order[1].type ~= "raw" or retval.order[2] then
internal_error("`retval.order` should have a single raw element: %s", retval.order)
end
form_of_directive.pretext = retval.order[1].value
retval.order[1] = nil
insert(form_of_directives, form_of_directive)
end
elseif segment:find("/") then
local holonyms = split_holonym(segment)
for j, holonym in ipairs(holonyms) do
if j > 1 then
if not holonym.no_display then
if j == #holonyms then
insert(retval.order, {type = "raw", value = zh_strings.and_conj})
else
insert(retval.order, {type = "raw", value = zh_strings.enum_sep})
end
end
holonym.needs_article = true
end
insert(retval.holonyms, holonym)
if not holonym.no_display then
insert(retval.order, {type = "holonym", value = #retval.holonyms})
end
m_placetypes.key_holonym_into_place_desc(retval, holonym)
end
else
local treat_as, display = segment:match("^(..-):(.+)$")
if treat_as then
segment = treat_as
else
display = segment
end
-- see if the placetype segment is just qualifiers
local only_qualifiers = true
local split_segments = split(segment, " ", true)
for _, split_segment in ipairs(split_segments) do
if m_placetypes.placetype_qualifiers[split_segment] == nil then
only_qualifiers = false
break
end
end
insert(placetypes, {placetype = segment, only_qualifiers = only_qualifiers})
if only_qualifiers then
insert(retval.order, {type = "qualifier", value = display})
else
insert(retval.order, {type = "placetype", value = display})
end
end
end
if not form_of_directives_already_present and form_of_directives and form_of_directives[1] then
form_of_directives[#form_of_directives].posttext = ""
end
local final_placetypes = {}
for i, placetype in ipairs(placetypes) do
if i > 1 and placetypes[i - 1].only_qualifiers then
final_placetypes[#final_placetypes] = final_placetypes[#final_placetypes] .. " " .. placetypes[i].placetype
else
insert(final_placetypes, placetypes[i].placetype)
end
end
retval.placetypes = final_placetypes
return retval
end
--[==[
Parse one or more "new-style" place descriptions, with placetypes and holonyms surrounded by `<<...>>` amid otherwise
raw text. Multiple descriptions are separated by two semicolons in a row. Return value is a list of place description
objects as documented at the top of the file.
]==]
local function parse_conjoined_new_style_place_desc(text, lang, form_of_directives, form_of_overridden_args)
local separate_specs = split(text, ";(;[^ ]*)")
local descs = {}
for i = 1, #separate_specs do
if i % 2 == 1 then
insert(descs, export.parse_new_style_place_desc(separate_specs[i], lang, form_of_directives,
form_of_overridden_args))
form_of_directives = nil
else
descs[#descs].separator = separate_specs[i]
end
end
return descs
end
--[=[
Process numeric and "extra info" arguments into an overall place spec, as described at the top of the file.
]=]
local function parse_overall_place_spec(data)
local args, from_tcl, extra_info_overridden_set, form_of_overridden_args =
data.args, data.from_tcl, data.extra_info_overridden_set, data.form_of_overridden_args
local descs = {}
local this_desc
-- Index of separate (semicolon-separated) place descriptions within `descs`.
local desc_index = 1
-- Index of separate holonyms within a place description. 0 means we've seen no holonyms and have yet to process
-- the placetypes that precede the holonyms. 1 means we've seen no holonyms but have already processed the
-- placetypes.
local holonym_index = 0
local in_place_desc = false
local form_of_directives = {}
-- ZH: use Chinese semicolon and space characters for multi-description joiners.
local function set_desc_joiner(desc, separator)
if separator == ";" then
this_desc.joiner = zh_strings.semicolon -- ";" instead of "; "
this_desc.include_following_article = true
elseif separator == ";;" then
this_desc.joiner = " "
else
local joiner = separator:sub(2)
if rfind(joiner, "^%a") then
this_desc.joiner = " " .. joiner .. " "
elseif joiner == "," then
-- ZH: ASCII comma → full-width ,(無空格)
this_desc.joiner = zh_strings.trans_sep
else
this_desc.joiner = joiner .. " "
end
end
end
for _, arg in ipairs(args[2]) do
if arg:find("^@") then
if not (desc_index == 1 and holonym_index == 0) then
error("@-directives cannot follow place descriptions")
end
local form_of_directive = parse_form_of_directive(arg, args[1], form_of_overridden_args)
if form_of_directives[1] then
form_of_directive.pretext = ", "
else
form_of_directive.pretext = ""
end
insert(form_of_directives, form_of_directive)
elseif arg == ";" or arg:find("^;[^ ]") then
if not this_desc then
error("Saw semicolon joiner without preceding place description")
end
set_desc_joiner(this_desc, arg)
desc_index = desc_index + 1
holonym_index = 0
in_place_desc = false
else
if arg:find("<<") then
if in_place_desc then
error("New-style place description must come first or following a separator (semicolon or similar), not directly following another description")
end
in_place_desc = true
local this_descs = parse_conjoined_new_style_place_desc(arg, args[1], form_of_directives,
form_of_overridden_args)
for j, desc in ipairs(this_descs) do
this_desc = desc
if holonym_index > 0 then
desc_index = desc_index + 1
holonym_index = 0
end
if j < #this_descs then
set_desc_joiner(this_desc, this_desc.separator)
end
descs[desc_index] = this_desc
last_was_new_style = true
holonym_index = #this_desc.holonyms + 1
end
else
-- Old-style arguments can directly follow a new-style argument; they become additional holonyms
-- tacked onto the end of the holonym list, and are displayed old-style except that there is no
-- prefix before the first one following the new-style argument.
in_place_desc = true
if holonym_index == 0 then
local entry_placetypes = split_on_slash(arg)
this_desc = {placetypes = entry_placetypes, holonyms = {}}
descs[desc_index] = this_desc
holonym_index = holonym_index + 1
else
local holonyms = split_holonym(arg)
for j, holonym in ipairs(holonyms) do
if j > 1 then
-- All but the first in a multi-holonym need an article. Not for the first one because e.g.
-- {{place|en|city|s/Arizona|c/United States}} should not display as "a city in Arizona, the
-- United States". The overall first holonym in the place description gets an article if
-- needed regardless of our setting here.
holonym.needs_article = true
-- Insert "and" before the last holonym.
if j == #holonyms then
this_desc.holonyms[holonym_index] = {
-- Use the no_display value from the first holonym; it should be the same for all
-- holonyms. `unlinked_placename` should not be used.
display_placename = "and", no_display = holonyms[1].no_display
}
holonym_index = holonym_index + 1
end
end
this_desc.holonyms[holonym_index] = holonym
m_placetypes.key_holonym_into_place_desc(this_desc, this_desc.holonyms[holonym_index])
holonym_index = holonym_index + 1
end
end
end
end
end
if form_of_directives[1] and not form_of_directives[#form_of_directives].posttext then
form_of_directives[#form_of_directives].posttext =
(args.def and args.def ~= "-" or not args.def and descs[1]) and ":" or ""
end
-- Tracking code (unchanged from enwikt).
for _, desc in ipairs(descs) do
for _, entry_placetype in ipairs(desc.placetypes) do
local splits = m_placetypes.split_qualifiers_from_placetype(entry_placetype, "no canon qualifiers")
for _, split in ipairs(splits) do
local prev_qualifier, this_qualifier, bare_placetype = unpack(split, 1, 3)
track("entry-placetype/" .. bare_placetype)
if this_qualifier then
track("entry-qualifier/" .. this_qualifier)
end
end
end
for _, holonym in ipairs(desc.holonyms) do
if holonym.placetype then
track("holonym-placetype/" .. holonym.placetype)
end
end
end
local extra_info = {}
for _, extra_info_spec in ipairs(export.extra_info_args) do
local extra_info_terms = parse_extra_info_arg(args[extra_info_spec.arg], extra_info_spec,
from_tcl and extra_info_overridden_set and extra_info_overridden_set[extra_info_spec.arg] and args[1] or
zhlang)
if extra_info_terms then
insert(extra_info, extra_info_terms)
end
end
return {
lang = args[1],
args = args,
directives = form_of_directives,
descs = descs,
extra_info = extra_info,
}
end
-------- Definition-generating functions
-- Return a string with the wikilinks to the ZH translations of the word.
-- ZH: joins multiple translations with "," (full-width comma) instead of ", ".
local function get_translations(transl, ids)
local ret = {}
for i, t in ipairs(transl) do
local arg_transls = split_on_comma(t)
local arg_ids = ids[i]
if arg_ids then
arg_ids = split_on_comma(arg_ids)
if #arg_transls ~= #arg_ids then
error(("Saw %s translation%s in t%s=%s but %s ID%s in tid%s=%s"):format(
#arg_transls, #arg_transls > 1 and "s" or "", i == 1 and "" or i, t, #arg_ids,
#arg_ids > 1 and "'s" or "", i == 1 and "" or i, ids[i]))
end
end
for j, arg_transl in ipairs(arg_transls) do
insert(ret, link(arg_transl, "zh", arg_ids and arg_ids[j] or nil))
end
end
return concat(ret, zh_strings.trans_sep) -- "," instead of ", "
end
-- Chinese has no articles; these stubs keep the call-sites intact.
local function get_placename_article(decorated_placename, placetypes, placename, suppress_holonym_use_the_check)
return nil
end
local function get_holonym_article(decorated_placename, place_desc, holonym_index)
return nil
end
-- Convert a holonym into display format. For ZH:
-- * No article prepended (Chinese has no articles).
-- * Both prefix and suffix affix types append the zh_suffix (e.g. "州", "省") directly to the linked name,
-- instead of the English "placetype of placename" / "placename placetype" construction.
-- * Display handlers are applied unchanged (they may add text based on the holonym's surrounding context).
local function format_holonym(place_desc, holonym_index, needs_article)
local holonym = place_desc.holonyms[holonym_index]
if holonym.no_display then
return ""
end
-- needs_article and force_the are not used in Chinese.
local output = holonym.display_placename
local placetype = holonym.placetype
local affix_type_pt_data, affix_type, affix_is_prefix, affix, prefix, suffix, no_affix_strings
local pt_equiv_for_affix_type, already_seen_affix, need_affix
local affix_pt_data, pt_equiv_for_affix
-- Implement display handlers (unchanged from enwikt).
local display_handler = m_placetypes.get_equiv_placetype_prop(placetype,
function(pt) return placetype_data[pt] and placetype_data[pt].display_handler end)
if display_handler then
output = display_handler(placetype, output)
end
if not holonym.suppress_affix then
-- Affix detection logic is unchanged from enwikt; only the rendering differs below.
affix_type_pt_data, pt_equiv_for_affix_type = m_placetypes.get_equiv_placetype_prop(placetype,
function(pt)
local cdpt = placetype_data[pt]
return cdpt and cdpt.affix_type and cdpt or nil
end
)
affix_pt_data, pt_equiv_for_affix = m_placetypes.get_equiv_placetype_prop(placetype,
function(pt)
local cdpt = placetype_data[pt]
return cdpt and (cdpt.affix_type or cdpt.affix or cdpt.prefix or cdpt.suffix) and cdpt or nil
end
)
if affix_type_pt_data then
affix_type = affix_type_pt_data.affix_type
need_affix = true
end
if affix_pt_data then
prefix = affix_pt_data.prefix or affix_pt_data.affix
suffix = affix_pt_data.suffix or affix_pt_data.affix
need_affix = true
end
no_affix_strings = affix_pt_data and affix_pt_data.no_affix_strings or
affix_type_pt_data and affix_type_pt_data.no_affix_strings
if holonym.affix_type and placetype then
affix_type = holonym.affix_type
prefix = prefix or placetype
suffix = suffix or placetype
need_affix = true
end
if need_affix then
affix_is_prefix = affix_type == "pref" or affix_type == "Pref"
if affix_is_prefix then
affix = prefix
else
affix = suffix
end
if not affix then
if not pt_equiv_for_affix_type then
internal_error("Something wrong, `pt_equiv_for_affix_type` not set processing holonym: %s",
holonym)
end
affix = pt_equiv_for_affix_type.placetype
if not affix then
internal_error("Something wrong, no affix could be located in `pt_equiv_for_affix_type` for " ..
"holonym %s: %s", holonym, pt_equiv_for_affix_type)
end
end
no_affix_strings = no_affix_strings or lc(affix)
-- ZH: skip pluralize_affix (Chinese doesn't pluralize)
end
end
-- ZH: resolve zh_suffix for the affix placetype before linking.
local zh_sfx_to_append = nil
if need_affix then
local _, zh_sfx = m_zh_data.get_zh_placetype_props(affix, m_placetypes)
if zh_sfx then
local zh_sfx_str = m_zh_data.format_zh_name(zh_sfx)
-- Check if zh_suffix already present in the unlinked output.
local unlinked = m_placetypes.remove_links_and_html(output)
if not unlinked:find(zh_sfx_str, 1, true) then
zh_sfx_to_append = zh_sfx_str
end
end
end
output = link(output, holonym.langcode or placetype and "zh" or nil)
-- ZH: for both pref and suf, append zh_suffix directly after the linked name.
-- (EN prefix construction "placetype of placename" is not used in Chinese.)
if zh_sfx_to_append then
output = output .. zh_sfx_to_append
end
-- No article for Chinese.
return output
end
-- Format holonyms for a ZH old-style place description.
-- Collects all holonyms, filters "and"/"or" connectors, reverses for big→small containment order,
-- groups consecutive same-type holonyms (peer locations), then joins with ZH enumeration (、/和).
-- Returns the concatenated holonym string, e.g. "美國賓夕法尼亞州", "波蘭、白俄羅斯和烏克蘭".
local function format_holonyms_zh(place_desc)
local holonyms = place_desc.holonyms
if not holonyms or #holonyms == 0 then
return ""
end
-- Collect non-"and"/"or"/"的" holonym items with their original indices.
-- A raw "的" holonym (from |的| in the template, like enwikt's |of|) signals possessive preposition;
-- filter it from display since format_old_style_place_desc_for_display already appends de_particle.
local items = {}
local has_de_joiner = false
for i, h in ipairs(holonyms) do
if not h.no_display then
local dname = h.display_placename
if dname ~= "and" and dname ~= "or" then
if not h.placetype and dname == zh_strings.de_particle then
has_de_joiner = true
else
insert(items, {holonym_index = i, placetype = h.placetype})
end
end
end
end
-- Separate trailing raw text (items after the last structural holonym) from non-trailing.
-- Trailing raw text should NOT be reversed; it is appended after the full description with ,.
local last_structural_idx = 0
for i = #items, 1, -1 do
if items[i].placetype then
last_structural_idx = i
break
end
end
local non_trailing = {}
local trailing_texts = {}
for i = 1, #items do
if i <= last_structural_idx then
insert(non_trailing, items[i])
else
insert(trailing_texts, holonyms[items[i].holonym_index].display_placename)
end
end
-- Reverse non-trailing for big→small order.
local reversed = {}
for i = #non_trailing, 1, -1 do
insert(reversed, non_trailing[i])
end
-- Group consecutive items with the same placetype (peers like multiple countries).
-- Raw-text holonyms (no placetype) break any current structural group.
local groups = {}
local i = 1
while i <= #reversed do
local item = reversed[i]
if not item.placetype then
-- Raw text (e.g. "南部", "東北部") forms its own singleton group.
local h = holonyms[item.holonym_index]
insert(groups, {type = "raw", text = h.display_placename})
i = i + 1
else
-- Structural holonym: collect consecutive ones of the same placetype.
local group = {type = "structural", items = {item}}
local pt = item.placetype
i = i + 1
while i <= #reversed do
local next_item = reversed[i]
if next_item.placetype == pt then
insert(group.items, next_item)
i = i + 1
else
break
end
end
insert(groups, group)
end
end
-- Format each group and assemble.
local parts = {}
for _, group in ipairs(groups) do
if group.type == "raw" then
insert(parts, group.text)
else
local formatted = {}
for _, item in ipairs(group.items) do
insert(formatted, format_holonym(place_desc, item.holonym_index, false))
end
if #formatted == 1 then
insert(parts, formatted[1])
elseif #formatted == 2 then
insert(parts, formatted[1] .. zh_strings.and_conj .. formatted[2])
else
local last = table.remove(formatted)
insert(parts, concat(formatted, zh_strings.enum_sep) .. zh_strings.and_conj .. last)
end
end
end
return concat(parts), has_de_joiner, trailing_texts
end
-- Get the Chinese display name for a single (possibly qualified) placetype.
-- Strips qualifiers, looks up zh_name via m_zh_data, and prepends translated qualifier text.
-- `first_holonym_placetype` enables context-sensitive names (e.g. capital city → 首都/省會/首府).
local function get_zh_placetype_name(placetype, first_holonym_placetype)
local resolved_pt = m_placetypes.resolve_placetype_aliases(placetype)
local zh_name, _, zh_by_holonym = m_zh_data.get_zh_placetype_props(resolved_pt, m_placetypes)
if zh_name then
local resolved = m_zh_data.resolve_zh_name(zh_name, zh_by_holonym, first_holonym_placetype)
return m_zh_data.format_zh_name(resolved)
end
-- Fallback to English name; add tracking for missing zh_data entries.
track("missing-zh-placetype/" .. tostring(placetype))
return placetype
end
-- Translate one qualifier word. `split_qualifiers_from_placetype` returns the canonicalized
-- qualifier, which for most qualifiers is wrapped in a wikilink ("[[autonomous]]"), so the markup
-- has to come off before the word can be looked up. Qualifiers with no Chinese equivalent are
-- returned as they came.
local function zh_qualifier(qualifier)
if not qualifier then
return ""
end
return m_zh_data.zh_qualifiers[m_placetypes.remove_links_and_html(qualifier)] or qualifier
end
-- Format a single entry placetype (possibly with qualifiers) for ZH display.
-- Strips qualifiers from the placetype, translates each qualifier via zh_qualifiers,
-- and prepends them to the Chinese placetype name (with wikilink markup applied).
local function format_one_zh_placetype(pt, first_holonym_placetype)
-- Try progressive qualifier splits; use the first that has a zh_name entry.
local splits = m_placetypes.split_qualifiers_from_placetype(pt)
local qualifier_prefix, reduced_pt = "", pt
for _, sp in ipairs(splits) do
local _, this_qualifier, bare_pt = unpack(sp, 1, 3)
-- Each split peels off one more qualifier, so accumulate them as we go.
qualifier_prefix = qualifier_prefix .. zh_qualifier(this_qualifier)
local zh_n = m_placetypes.get_zh_placetype_display_form(bare_pt, first_holonym_placetype)
if zh_n then
-- Found a known ZH entry; return qualifier + linked display form.
return qualifier_prefix .. zh_n
end
reduced_pt = bare_pt
end
-- Fallback: the qualifiers are already in `qualifier_prefix`, so name only the placetype left
-- after peeling them off, untranslated if need be.
return qualifier_prefix .. get_zh_placetype_name(reduced_pt, first_holonym_placetype)
end
-- Build the ZH entry placetype description from the `placetypes` list.
-- Handles "and"/"or" splitting (e.g. "城市/and/市鎮" → "城市和市鎮"),
-- multiple placetypes without and/or (joined with "、...和"),
-- and context-sensitive names via first_holonym_placetype.
local function get_zh_entry_placetype_desc(placetypes, first_holonym_placetype)
local is_ignorable = m_placetypes.placetype_is_ignorable
-- Find last and/or position.
local and_or_pos
for i, pt in ipairs(placetypes) do
if pt == "and" or pt == "or" then
and_or_pos = i
end
end
if and_or_pos then
-- Split into group before and group after the conjunction.
local group1, group2 = {}, {}
for i = 1, and_or_pos - 1 do
if not is_ignorable(placetypes[i]) then
insert(group1, format_one_zh_placetype(placetypes[i], first_holonym_placetype))
end
end
for i = and_or_pos + 1, #placetypes do
if not is_ignorable(placetypes[i]) then
insert(group2, format_one_zh_placetype(placetypes[i], first_holonym_placetype))
end
end
local conj_str = placetypes[and_or_pos] == "or" and "或" or zh_strings.and_conj
local all = {}
for _, v in ipairs(group1) do insert(all, v) end
for _, v in ipairs(group2) do insert(all, v) end
if #all == 0 then return "" end
if #all == 1 then return all[1] end
local last = table.remove(all)
return concat(all, zh_strings.enum_sep) .. conj_str .. last
else
-- No explicit and/or: collect all non-ignorable placetypes.
local formatted = {}
for _, pt in ipairs(placetypes) do
if not is_ignorable(pt) then
insert(formatted, format_one_zh_placetype(pt, first_holonym_placetype))
end
end
if #formatted == 0 then return "" end
if #formatted == 1 then return formatted[1] end
local last = table.remove(formatted)
return concat(formatted, zh_strings.enum_sep) .. zh_strings.and_conj .. last
end
end
-- Return the ZH description of a placetype for use in new-style <<placetype>> segments.
local function get_placetype_description(placetype, first_holonym_placetype)
return format_one_zh_placetype(placetype, first_holonym_placetype)
end
-- Return the ZH description of a qualifier for use in new-style <<qualifier>> segments.
local function get_qualifier_description(qualifier)
-- Trim to just the qualifier word (split_qualifiers_from_placetype requires a trailing non-qualifier word).
local splits = m_placetypes.split_qualifiers_from_placetype(qualifier .. " foo")
local translated = ""
for _, sp in ipairs(splits) do
translated = translated .. zh_qualifier(sp[2])
end
return translated ~= "" and translated or qualifier
end
-- Format a set of form-of directive terms (unchanged from enwikt, only conjunction uses zh_strings).
local function format_form_of_directive(overall_place_spec, directive_terms, ucfirst, from_tcl)
local formatted_terms = {}
local placetypes
if not overall_place_spec.descs[2] then
placetypes = overall_place_spec.descs[1].placetypes
else
placetypes = {}
for _, desc in ipairs(overall_place_spec.descs) do
m_table.extend(placetypes, desc.placetypes)
end
end
for _, termobj in ipairs(directive_terms.terms) do
local placename_article
if not termobj.alt and termobj.term and not termobj.term:find("%[%[") then
placename_article = get_placename_article(termobj.term, placetypes)
end
local linked_term = m_links.full_link(termobj, "term")
linked_term = "<span class='form-of-definition-link'>" .. linked_term .. "</span>"
if termobj.eq then
linked_term = linked_term .. " (= " .. m_links.full_link {term = termobj.eq, lang = zhlang} .. ")"
end
if placename_article then
linked_term = placename_article .. " " .. linked_term
end
insert(formatted_terms, linked_term)
end
local spec = directive_terms.spec
local text = spec.text
if type(text) == "function" then
text = text(overall_place_spec)
end
if text == "+" then
text = directive_terms.directive
end
-- Look up Chinese translation; all non-"+" directives also get translated.
local zh_text = m_zh_data.zh_form_of_directive_labels[directive_terms.directive]
if zh_text then
text = zh_text
end
if ucfirst then
text = m_strutils.ucfirst(text)
end
if not from_tcl then
local tracking_prefix = "form-of/" .. directive_terms.directive
track(tracking_prefix)
local langcode = overall_place_spec.lang:getCode()
local full_langcode = overall_place_spec.lang:getFullCode()
track(tracking_prefix .. "/" .. langcode)
if full_langcode ~= langcode then
track(tracking_prefix .. "/" .. full_langcode)
end
if full_langcode ~= "zh" then
track(tracking_prefix .. "/non-chinese")
end
end
return (require(form_of_module).format_form_of {
text = text,
lemmas = m_table.serialCommaJoin(formatted_terms, {conj = directive_terms.conj or spec.conjunction or zh_strings.and_conj}),
lemma_classes = false,
})
end
-- Format extra-info terms (capital, largest city, modern name, etc.) for ZH.
-- * Labels come from m_zh_data.zh_extra_info_labels; capital/caplc use zh_capital_label_by_holonym_type,
-- resolved first by entry placetype (國家→首都, 省→省會…), then by holonym placetype as fallback.
-- * Separator: "。" + label in sentence style; ";" + label otherwise.
-- * Multiple terms joined with 、/和 instead of serial comma.
-- * auto_plural is skipped (Chinese doesn't pluralize).
local function format_extra_info(overall_place_spec, extra_info_terms, sentence_style)
local formatted_terms = {}
for _, termobj in ipairs(extra_info_terms.terms) do
insert(formatted_terms, m_links.full_link(termobj))
end
local spec = extra_info_terms.spec
-- Resolve ZH label: look up by spec.arg, then spec.text as fallback.
local zh_label = m_zh_data.zh_extra_info_labels[spec.arg]
if not zh_label then
-- Try spec.text as the lookup key (e.g. "capital and largest city").
local spec_text = spec.text
if type(spec_text) == "function" then
spec_text = spec_text(overall_place_spec)
end
zh_label = m_zh_data.zh_extra_info_labels[spec_text] or spec_text or spec.arg
end
-- Context-sensitive capital label (首都/省會/首府/縣治…) for `capital=` and `caplc=`.
-- Priority: entry placetype (alias-resolved) → holonym placetype → keep existing zh_label.
-- For caplc, appends "和最大城市" to the resolved capital label.
if spec.arg == "capital" or spec.arg == "caplc" then
local cap_label = nil
local desc1 = overall_place_spec.descs and overall_place_spec.descs[1]
-- 1. Entry placetype (e.g. 國家→country→首都, 省→province→省會)
if desc1 then
for _, raw_pt in ipairs(desc1.placetypes) do
if not m_placetypes.placetype_is_ignorable(raw_pt) and raw_pt ~= "and" and raw_pt ~= "or" then
cap_label = m_placetypes.get_equiv_placetype_prop(raw_pt, function(pt)
return m_zh_data.zh_capital_label_by_holonym_type[pt]
end)
if cap_label then break end
end
end
end
-- 2. Holonym placetype fallback (previous behaviour)
if not cap_label and desc1 and desc1.holonyms then
for _, h in ipairs(desc1.holonyms) do
if h.placetype then
cap_label = m_zh_data.zh_capital_label_by_holonym_type[h.placetype]
if cap_label then break end
end
end
end
if cap_label then
zh_label = spec.arg == "caplc" and (cap_label .. "和最大城市") or cap_label
end
end
local text = zh_label
if spec.with_colon then
text = text .. ":" -- full-width colon
end
if sentence_style and spec.match_sentence_style then
text = "。" .. text
else
text = zh_strings.semicolon .. text
end
-- Join terms with ZH enumeration.
local conj = extra_info_terms.conj or zh_strings.and_conj
local joined
if #formatted_terms == 0 then
joined = ""
elseif #formatted_terms == 1 then
joined = formatted_terms[1]
elseif #formatted_terms == 2 then
joined = formatted_terms[1] .. conj .. formatted_terms[2]
else
local last = table.remove(formatted_terms)
joined = concat(formatted_terms, zh_strings.enum_sep) .. conj .. last
end
return text .. joined
end
-- Format an old-style place description for ZH display.
-- Chinese pattern: [位於|""][holonym_string][的][zh_placetype_name]
-- * Holonyms are reversed (big→small), grouped, and joined with 、/和.
-- * "位於" is prepended for "in"-type placetypes; omitted for "of"-type (e.g. capitals).
-- * args.a (article override) is used as a prefix to the placetype name.
local function format_old_style_place_desc_for_display(args, place_desc, desc_index, with_article, ucfirst_flag)
local placetypes = place_desc.placetypes
-- Determine the entry placetype (last non-ignorable, non-conjunction item) for preposition lookup.
local entry_placetype_for_prep = nil
for i = #placetypes, 1, -1 do
local pt = placetypes[i]
if not m_placetypes.placetype_is_ignorable(pt) and pt ~= "and" and pt ~= "or" then
entry_placetype_for_prep = pt
break
end
end
-- Determine first structured holonym placetype for context-sensitive zh_name lookups.
local first_holonym_placetype = nil
if place_desc.holonyms then
for _, h in ipairs(place_desc.holonyms) do
if h.placetype then
first_holonym_placetype = h.placetype
break
end
end
end
-- Build the Chinese placetype description (possibly with qualifiers and multiple types).
local zh_pt_name = get_zh_entry_placetype_desc(placetypes, first_holonym_placetype)
-- Handle args.a (article override): used as a direct prefix in Chinese, e.g. a=盎格魯撒克遜 → "盎格魯撒克遜王國".
if with_article and desc_index == 1 and args.a then
zh_pt_name = args.a .. zh_pt_name
end
-- Build holonym string (reversed, grouped, ZH-joined).
-- has_de_joiner is true when a bare |的| token was in the holonym list (like enwikt's |of|).
local holonym_str, has_de_joiner, trailing_texts = "", false, {}
if place_desc.holonyms and #place_desc.holonyms > 0 then
holonym_str, has_de_joiner, trailing_texts = format_holonyms_zh(place_desc)
end
-- Determine preposition style ("in" → 位於…的; "of" → …的 with no 位於).
-- An explicit |的| joiner in the template overrides to possessive (no 位於).
local use_wei_yu = true
if has_de_joiner then
use_wei_yu = false
elseif entry_placetype_for_prep then
local prep = m_placetypes.get_placetype_entry_preposition(entry_placetype_for_prep)
if prep == "of" then
use_wei_yu = false
end
end
-- Assemble final string.
local result
if holonym_str ~= "" then
if use_wei_yu then
result = zh_strings.in_preposition .. holonym_str .. zh_strings.de_particle .. zh_pt_name
else
result = holonym_str .. zh_strings.de_particle .. zh_pt_name
end
else
result = zh_pt_name
end
if #trailing_texts > 0 then
result = result .. zh_strings.trans_sep .. concat(trailing_texts, zh_strings.trans_sep)
end
return result
end
--[==[
Get the full gloss (Chinese description) of a new-style place description. New-style place descriptions are
specified with a single string containing raw text interspersed with placetypes and holonyms surrounded by `<<...>>`.
Exported for use by [[Module:demonyms]].
]==]
function export.format_new_style_place_desc_for_display(args, place_desc, with_article)
local parts = {}
local function ins(txt)
insert(parts, txt)
end
-- args.a not used in Chinese (no article system).
local first_holonym_placetype = nil
if place_desc.holonyms then
for _, h in ipairs(place_desc.holonyms) do
if h.placetype then
first_holonym_placetype = h.placetype
break
end
end
end
local max_holonym = 0
for _, order in ipairs(place_desc.order) do
local segment_type, segment = order.type, order.value
if segment_type == "raw" then
ins(segment)
elseif segment_type == "placetype" then
ins(get_placetype_description(segment, first_holonym_placetype))
elseif segment_type == "qualifier" then
ins(get_qualifier_description(segment))
elseif segment_type == "holonym" then
ins(format_holonym(place_desc, segment, false))
if segment > max_holonym then
max_holonym = segment
end
else
internal_error("Unrecognized segment type %s", segment_type)
end
end
if place_desc.holonyms and max_holonym < #place_desc.holonyms then
local holonym_no_prefix = true
for holonym_index = max_holonym + 1, #place_desc.holonyms do
-- Append remaining holonyms with a space prefix (no "in"/"of" for new-style).
ins(" " .. format_holonym(place_desc, holonym_index, false))
holonym_no_prefix = false
end
end
return concat(parts)
end
-- Return a string with the gloss (the description of the place itself, as opposed to translations).
-- `sentence_style` controls whether extra-info uses "。" (sentence) or ";" (semicolon) separation.
local function get_display_form(data)
local overall_place_spec, ucfirst, sentence_style, drop_extra_info, extra_info_overridden_set, from_tcl =
data.overall_place_spec, data.ucfirst, data.sentence_style, data.drop_extra_info,
data.extra_info_overridden_set, data.from_tcl
local args = overall_place_spec.args
local parts = {}
local function ins(txt)
table.insert(parts, txt)
end
if overall_place_spec.directives and overall_place_spec.directives[1] then
for i, directive_terms in ipairs(overall_place_spec.directives) do
ins(directive_terms.pretext)
if directive_terms.pretext ~= "" then
ucfirst = false
end
if not args.def or args.def == "-" then
ins(format_form_of_directive(overall_place_spec, directive_terms, ucfirst, from_tcl))
ucfirst = false
if i == #overall_place_spec.directives and directive_terms.posttext then
ins(directive_terms.posttext)
end
end
end
end
if args.def == "-" then
return concat(parts)
end
if args.def then
if args.def:find("<<") then
local def_desc = export.parse_new_style_place_desc(args.def, args[1])
ins(export.format_new_style_place_desc_for_display({}, def_desc, false))
else
ins(args.def)
end
else
local include_article = true
for n, desc in ipairs(overall_place_spec.descs) do
if desc.order then
ins(export.format_new_style_place_desc_for_display(args, desc, n == 1))
else
ins(format_old_style_place_desc_for_display(args, desc, n, include_article, ucfirst))
end
if desc.joiner then
ins(desc.joiner)
end
include_article = desc.include_following_article
ucfirst = false
end
end
local addl = args.addl
if addl then
if addl:find("^[;:]") then
ins(addl)
elseif addl:find("^_") then
ins(" " .. addl:sub(2))
else
ins("," .. addl)
end
end
for _, extra_info_terms in ipairs(overall_place_spec.extra_info) do
if not drop_extra_info or extra_info_terms.spec.display_even_when_dropped or
extra_info_overridden_set and extra_info_overridden_set[extra_info_terms.spec.arg] then
ins(format_extra_info(overall_place_spec, extra_info_terms, sentence_style))
end
end
return concat(parts)
end
-- Return the definition line.
-- ZH: sentence_style is always true; ucfirst is always false (no case in Chinese);
-- parentheses around glosses are full-width ().
local function get_def(data)
local overall_place_spec, from_tcl, drop_extra_info, extra_info_overridden_set, translation_follows =
data.overall_place_spec, data.from_tcl, data.drop_extra_info, data.extra_info_overridden_set,
data.translation_follows
local args = overall_place_spec.args
local sentence_style = true -- always sentence style in Chinese
local ucfirst = false -- Chinese has no case
if #args.t > 0 then
local gloss = get_display_form {
overall_place_spec = overall_place_spec,
ucfirst = false,
sentence_style = false,
drop_extra_info = drop_extra_info,
extra_info_overridden_set = extra_info_overridden_set,
from_tcl = from_tcl,
}
if from_tcl and not args.tcl_nolc then
gloss = m_strutils.lcfirst(gloss)
end
if translation_follows then
return (gloss == "" and "" or gloss .. ":") .. get_translations(args.t, args.tid)
else
return get_translations(args.t, args.tid) ..
(gloss == "" and "" or zh_strings.paren_open .. gloss .. zh_strings.paren_close)
end
else
return get_display_form {
overall_place_spec = overall_place_spec,
ucfirst = ucfirst,
sentence_style = sentence_style,
drop_extra_info = drop_extra_info,
extra_info_overridden_set = extra_info_overridden_set,
from_tcl = from_tcl,
}
end
end
---------- Functions for the category wikicode
-- The code in this section finds the categories to which a given place belongs. See enwikt [[Module:place]]
-- for detailed documentation of the algorithm.
--[=[
Find the appropriate category specs for a given place description and placetype.
See enwikt [[Module:place]] for detailed documentation.
]=]
local function find_placetype_cat_specs(data)
local entry_placetype, place_desc, first_holonym_index, overriding_holonym, from_demonym =
data.entry_placetype, data.place_desc, data.first_holonym_index, data.overriding_holonym, data.from_demonym
local form_of_directive = data.form_of_directive
local function fetch_cat_specs(holonym_to_match, index, no_fallback)
local holonym_placetype = holonym_to_match.placetype
if not holonym_placetype then
return nil
end
local holonym_placename = holonym_to_match.unlinked_placename
if not holonym_placename then
internal_error("Missing unlinked_placename in holonym (index %s): %s", index, holonym_to_match)
end
local cat_specs, equiv_entry_placetype_and_qualifier = m_placetypes.get_equiv_placetype_prop(entry_placetype,
function(equiv_entry_pt)
return m_placetypes.get_equiv_placetype_prop(holonym_placetype,
function(equiv_holonym_pt) return m_placetypes.political_division_cat_handler {
entry_placetype = equiv_entry_pt,
holonym_placetype = equiv_holonym_pt,
holonym_placename = holonym_placename,
holonym_index = index,
place_desc = place_desc,
from_demonym = from_demonym,
} end)
end,
{no_fallback = no_fallback, form_of_directive = form_of_directive}
)
if cat_specs and cat_specs[1] then
return cat_specs, equiv_entry_placetype_and_qualifier.placetype
end
local cat_handler, equiv_entry_placetype_and_qualifier = m_placetypes.get_equiv_placetype_prop(entry_placetype,
function(equiv_entry_pt)
local entry_placetype_data = m_placetypes.placetype_data[equiv_entry_pt]
if entry_placetype_data and entry_placetype_data.cat_handler then
return entry_placetype_data.cat_handler
end
end,
{no_fallback = no_fallback, form_of_directive = form_of_directive}
)
if cat_handler then
local cat_specs = m_placetypes.get_equiv_placetype_prop(holonym_placetype,
function(equiv_holonym_pt) return cat_handler {
entry_placetype = equiv_entry_placetype_and_qualifier.placetype,
holonym_placetype = equiv_holonym_pt,
holonym_placename = holonym_placename,
holonym_index = index,
place_desc = place_desc,
from_demonym = from_demonym,
} end)
if cat_specs and cat_specs[1] then
return cat_specs, equiv_entry_placetype_and_qualifier.placetype
end
end
if not no_fallback then
local cat_specs, equiv_entry_placetype_and_qualifier = m_placetypes.get_equiv_placetype_prop(entry_placetype,
function(equiv_entry_pt)
local entry_placetype_data = m_placetypes.placetype_data[equiv_entry_pt]
if entry_placetype_data then
return m_placetypes.get_equiv_placetype_prop(holonym_placetype,
function(equiv_holonym_pt)
return entry_placetype_data[equiv_holonym_pt .. "/*"]
end)
end
end,
{form_of_directive = form_of_directive}
)
if cat_specs and cat_specs[1] then
return cat_specs, equiv_entry_placetype_and_qualifier.placetype
end
end
return nil
end
if overriding_holonym then
local cat_specs, fetched_entry_placetype = fetch_cat_specs(overriding_holonym, nil)
if cat_specs and cat_specs[1] then
return {
entry_placetype = fetched_entry_placetype,
cat_specs = cat_specs,
triggering_holonym = overriding_holonym,
}
end
else
-- First pass: no fallback (exact entry placetype match only).
for i, holonym in ipairs(place_desc.holonyms) do
if first_holonym_index and i < first_holonym_index then
-- continue
else
local cat_specs, fetched_entry_placetype = fetch_cat_specs(holonym, i, "no_fallback")
if cat_specs and cat_specs[1] then
return {
entry_placetype = fetched_entry_placetype,
cat_specs = cat_specs,
triggering_holonym = holonym,
triggering_holonym_index = i,
}
end
end
end
-- Second pass: with fallback.
for i, holonym in ipairs(place_desc.holonyms) do
if first_holonym_index and i < first_holonym_index then
-- continue
else
local cat_specs, fetched_entry_placetype = fetch_cat_specs(holonym, i)
if cat_specs and cat_specs[1] then
return {
entry_placetype = fetched_entry_placetype,
cat_specs = cat_specs,
triggering_holonym = holonym,
triggering_holonym_index = i,
}
end
end
end
end
return nil
end
-- Turn a list of category specs (see comment at section top) into the corresponding categories
-- (minus the language code prefix).
local function cat_specs_to_categories(place_desc, cat_data)
local all_cats = {}
local cat_specs, entry_placetype, triggering_holonym, triggering_holonym_index =
cat_data.cat_specs, cat_data.entry_placetype, cat_data.triggering_holonym, cat_data.triggering_holonym_index
if triggering_holonym then
for _, cat_spec in ipairs(cat_specs) do
local cat
if cat_spec == true then
-- ZH: 1-char zh_name → "的" + name (e.g. "的省"); 2+ chars → no "的" (e.g. "城市").
local zh_raw = m_placetypes.get_zh_placetype_name(entry_placetype)
local de = zh_raw and m_placetypes.zh_cat_de(zh_raw) or zh_strings.de_particle
local zh_str = zh_raw and m_zh_data.format_zh_name(zh_raw) or entry_placetype
cat = "+++" .. de .. zh_str
else
cat = cat_spec
end
if cat:find("%+%+%+") then
local group, key, spec, container_trail = m_placetypes.find_matching_holonym_location {
holonym_placetype = triggering_holonym.placetype,
holonym_placename = triggering_holonym.unlinked_placename,
holonym_index = triggering_holonym_index,
place_desc = place_desc,
}
if group then
cat = cat:gsub("%+%+%+", m_strutils.replacement_escape(m_placetypes.get_prefixed_key(key, spec)))
insert(all_cats, cat)
else
mw.log(("Unable to insert category for cat spec '%s' because holonym '%s/%s' did not match a " ..
"known location"):format(cat, triggering_holonym.placetype, triggering_holonym.unlinked_placename))
track("cant-match-holonym-for-category-spec")
end
else
insert(all_cats, cat)
end
end
else
for _, cat_spec in ipairs(cat_specs) do
local cat
if cat_spec == true then
-- ZH: bare placetype category (e.g. "城市")
cat = get_zh_placetype_name(entry_placetype)
else
cat = cat_spec
if cat:find("%+%+%+") then
internal_error("Category %s contains +++ but there is no holonym to substitute", cat)
end
end
insert(all_cats, cat)
end
end
return all_cats
end
-- Return the categories (without initial lang code) that should be added to the entry, given the place description
-- and a particular entry placetype.
local function get_placetype_cats(place_desc, entry_placetype, from_demonym, form_of_directive)
local cats = {}
local first_holonym_index = 1
while first_holonym_index <= #place_desc.holonyms do
local cat_data = find_placetype_cat_specs {
entry_placetype = entry_placetype,
place_desc = place_desc,
first_holonym_index = first_holonym_index,
from_demonym = from_demonym,
form_of_directive = form_of_directive,
}
if not cat_data then
break
end
local triggering_holonym = cat_data.triggering_holonym
if not triggering_holonym then
internal_error("find_placetype_cat_specs should have returned a triggering holonym: %s", cat_data)
end
extend(cats, cat_specs_to_categories(place_desc, cat_data))
first_holonym_index = cat_data.triggering_holonym_index
local equiv_triggering_placetypes = m_placetypes.get_placetype_equivs(triggering_holonym.placetype,
{no_fallback = true})
for _, equiv in ipairs(equiv_triggering_placetypes) do
local other_holonyms_of_same_type = place_desc.holonyms_by_placetype[equiv.placetype]
if other_holonyms_of_same_type then
for _, other_placename_of_same_type in ipairs(other_holonyms_of_same_type) do
if other_placename_of_same_type ~= triggering_holonym.unlinked_placename then
local overriding_holonym = {
placetype = triggering_holonym.placetype,
unlinked_placename = other_placename_of_same_type,
}
local other_cat_data = find_placetype_cat_specs {
entry_placetype = entry_placetype,
place_desc = place_desc,
overriding_holonym = overriding_holonym,
from_demonym = from_demonym,
form_of_directive = form_of_directive,
}
if other_cat_data then
extend(cats, cat_specs_to_categories(place_desc, other_cat_data))
end
end
end
end
end
first_holonym_index = first_holonym_index + 1
while first_holonym_index <= #place_desc.holonyms do
if place_desc.holonyms[first_holonym_index].continue_cat_loop then
break
end
first_holonym_index = first_holonym_index + 1
end
end
if cats[1] then
return cats
end
local entry_pt_default, equiv_entry_placetype_and_qualifier =
m_placetypes.get_equiv_placetype_prop(entry_placetype, function(pt)
return m_placetypes.placetype_data[pt] and m_placetypes.placetype_data[pt].default
end,
{form_of_directive = form_of_directive})
if entry_pt_default then
return cat_specs_to_categories(place_desc, {
cat_specs = entry_pt_default,
entry_placetype = equiv_entry_placetype_and_qualifier.placetype,
})
end
return {}
end
--[==[
Iterate through each type of place and return a list of the categories that need to be added to the entry.
See enwikt [[Module:place]] for detailed documentation.
]==]
function export.get_cats(args, overall_place_spec, from_demonym)
local cats = {}
local place_descriptions = overall_place_spec.descs
handle_category_implications(place_descriptions, m_placetypes.cat_implications)
m_placetypes.augment_holonyms_with_container(place_descriptions)
if overall_place_spec.directives then
for _, directive_terms in ipairs(overall_place_spec.directives) do
local spec_cats = directive_terms.spec.cat
if spec_cats then
if type(spec_cats) == "string" then
spec_cats = {spec_cats}
end
for _, spec_cat in ipairs(spec_cats) do
insert(cats, spec_cat)
end
end
if directive_terms.spec.type_prefix then
for _, place_desc in ipairs(place_descriptions) do
for _, placetype in ipairs(place_desc.placetypes) do
if not m_placetypes.placetype_is_ignorable(placetype) then
extend(cats, get_placetype_cats(place_desc, placetype, from_demonym,
directive_terms.spec.type_prefix))
end
end
end
end
end
end
if not from_demonym then
local bare_categories = m_placetypes.get_bare_categories(args, overall_place_spec)
extend(cats, bare_categories)
end
for _, place_desc in ipairs(place_descriptions) do
if not from_demonym then
for _, placetype in ipairs(place_desc.placetypes) do
if not m_placetypes.placetype_is_ignorable(placetype) then
extend(cats, get_placetype_cats(place_desc, placetype))
end
end
end
extend(cats, get_placetype_cats(place_desc, "*", from_demonym))
end
if args.cat then
for _, cat in ipairs(args.cat) do
local split_cats = split_on_comma(cat)
extend(cats, split_cats)
end
end
return cats
end
-- Return the category wikicode for a list of categories.
-- ZH: uses `lang:getFullName() .. " " .. cat` format for regular categories (same as cln: format in enwikt),
-- matching zhwikt's convention of "漢語 XX" category names instead of "zh:XX".
local function format_cats(lang, cats, sort_key)
local full_cats = {}
local langname = lang:getFullName()
for _, cat in ipairs(cats) do
local cln_cat = cat:match("^cln:(.*)$")
if cln_cat then
insert(full_cats, langname .. " " .. cln_cat)
else
-- ZH: use langname prefix instead of langcode:cat
insert(full_cats, langname .. " " .. cat)
end
end
return require(utilities_module).format_categories(full_cats, lang, sort_key, nil,
force_cat or m_placetypes.get_force_cat())
end
----------- Main entry point
--[==[
Implementation of {{tl|place}}. Meant to be callable from another module.
See enwikt [[Module:place]] for full documentation of the `data` argument fields.
]==]
function export.format(data)
local template_args = data.template_args
local list_param = {list = true}
local boolean_param = {type = "boolean"}
local params = {
[1] = {required = true, type = "language", default = "und"},
[2] = {required = true, list = true},
["t"] = list_param,
["tid"] = {list = true, allow_holes = true},
["cat"] = list_param,
["nocat"] = boolean_param,
["nocap"] = boolean_param,
["sort"] = true,
["pagename"] = true, -- for testing or documentation purposes
["a"] = true,
["addl"] = true,
["def"] = true,
-- params that are only used when transcluding using {{tcl}}/{{transclude}}, to transmit information to {{tcl}}.
["tcl"] = true,
["tcl_t"] = list_param,
["tcl_tid"] = list_param,
["tcl_nolb"] = true,
["tcl_nolc"] = boolean_param,
["tcl_noextratext"] = boolean_param,
}
-- add "extra info" parameters
for _, extra_arg_spec in ipairs(export.extra_info_args) do
params[extra_arg_spec.arg] = list_param
end
if template_args.def == "" then
error("Cannot currently pass def= as an empty parameter; use def=- if you want to suppress the definition display")
end
local args = require("Module:parameters").process(template_args, params)
if args.a then
track("a")
end
data.args = args
local overall_place_spec = parse_overall_place_spec(data)
data.overall_place_spec = overall_place_spec
return get_def(data) .. (
args.nocat and "" or format_cats(args[1], export.get_cats(args, overall_place_spec), args.sort))
end
--[==[
Actual entry point of {{tl|place}}.
]==]
function export.show(frame)
return export.format {
template_args = frame:getParent().args,
}
end
return export
m9cesgtxm1xd0gv5ru0cggvfidhh0af
9909176
9909096
2026-09-29T09:13:16Z
TongcyBot
83009
更新至最新修正版
9909176
Scribunto
text/plain
local export = {}
local force_cat = false -- set to true for testing
local m_placetypes = require("Module:place/placetypes")
local m_links = require("Module:links")
local memoize = require("Module:memoize")
local m_strutils = require("Module:string utilities")
local m_table = require("Module:table")
-- Chinese localization data (zh_name, zh_suffix, qualifiers, extra-info labels, etc.)
local m_zh_data = require("Module:place/data")
local debug_track_module = "Module:debug/track"
local en_utilities_module = "Module:en-utilities"
local form_of_module = "Module:form of"
local languages_module = "Module:languages"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local parameter_utilities_module = "Module:parameter utilities"
local utilities_module = "Module:utilities"
local zhlang = require(languages_module).getByCode("zh")
local rmatch = m_strutils.match
local rfind = m_strutils.find
local ulen = m_strutils.len
local split = m_strutils.split
local dump = mw.dumpObject
local insert = table.insert
local concat = table.concat
local pluralize = require(en_utilities_module).pluralize
local extend = m_table.extend
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local internal_error = m_placetypes.internal_error
local process_error = m_placetypes.process_error
local placetype_data = m_placetypes.placetype_data
-- Shorthand for the ZH localization string table.
local zh_strings = m_zh_data.zh_strings
--[==[
ZH fork of [[Module:place]] (enwikt). Architecture and data structures are identical to the English version;
only the display-formatting functions are changed to produce Chinese-language definitions.
Key structural differences from enwikt:
* Old-style holonyms are reversed (big→small containment order) and joined with 、/和 rather than "in X, Y".
* Sentence pattern: 位於[holonyms]的[placetype] (or [holonyms]的[placetype] for "of"-type placetypes).
* No articles (a/an/the) in generated text.
* Pref/suf holonym affixes append the Chinese zh_suffix instead of English "placetype of X".
* Translations are joined with "," (full-width comma); parenthetical glosses use ().
* Extra-info labels (capital, largest city, etc.) come from Module:place/data zh_extra_info_labels.
* sentence_style is always true for ZH (extra info uses "。" separator, not ";").
]==]
--[==[ var:
List specifying the allowed form-of directives, used for former names, official names, abbreviations, etc. of places.
The key is the form-of directive and the value is an object with the following properties:
* `text`: The actual text displayed before the terms. If the value is `+`, the key is used as the text. If the value is
a function, it is passed a single argument, the overall place spec (see comment at top of file) and should return
the text to be displayed.
* `type_prefix`: The prefix used to generate the placetype for looking up the appropriate category or categories in the
placetype data structure. Can be omitted if there are no categories associated with the directive.
* `conjunction`: The conjunction used to join multiple terms, defaulting to `and`.
* `cat`: Additional category or categories to add the term to, whenever this particular directive is used.
* `default_foreign`: If specified, the default language of terms given along with this directive is the language in
{{para|1}}; otherwise it is English.
]==]
export.all_form_of_directives = {
["former name of"] = {text = "+", type_prefix = "FORMER_NAME_OF"},
["fmr of"] = {alias_of = "former name of"},
["ancient name of"] = {text = "+", type_prefix = "FORMER_NAME_OF"},
["official name of"] = {text = "+", type_prefix = "OFFICIAL_NAME_OF"},
["former official name of"] = {text = "+", type_prefix = "FORMER_OFFICIAL_NAME_OF"},
["long form of"] = {text = "+", type_prefix = "LONG_FORM_OF"},
["former long form of"] = {text = "+", type_prefix = "FORMER_LONG_FORM_OF"},
["nickname for"] = {text = "+", type_prefix = "NICKNAME_FOR"},
["official nickname for"] = {text = "+", type_prefix = "OFFICIAL_NICKNAME_FOR"},
["former nickname for"] = {text = "+", type_prefix = "FORMER_NICKNAME_FOR"},
["derogatory name for"] = {text = "[[Appendix:Glossary#derogatory|derogatory]] name for", type_prefix = "DEROGATORY_NAME_FOR"},
["synonym of"] = {text = "+"},
["syn of"] = {alias_of = "synonym of"},
["abbreviation of"] = {text = "[[Appendix:Glossary#abbreviation|abbreviation]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:abbreviations",
default_foreign = true},
["abbr of"] = {alias_of = "abbreviation of"},
["abbrev of"] = {alias_of = "abbreviation of"},
["initialism of"] = {text = "[[Appendix:Glossary#initialism|initialism]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:initialisms",
default_foreign = true},
["init of"] = {alias_of = "initialism of"},
["acronym of"] = {text = "[[Appendix:Glossary#acronym|acronym]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:acronyms",
default_foreign = true},
["syllabic abbreviation of"] = {text = "[[Appendix:Glossary#syllabic abbreviation|syllabic abbreviation]] of", type_prefix = "ABBREVIATION_OF", cat = "cln:syllabic abbreviations",
default_foreign = true},
["sylabbr of"] = {alias_of = "syllabic abbreviation of"},
["sylabbrev of"] = {alias_of = "syllabic abbreviation of"},
["ellipsis of"] = {text = "[[Appendix:Glossary#ellipsis|ellipsis]] of", type_prefix = "ELLIPSIS_OF", cat = "cln:ellipses",
default_foreign = true},
["ellip of"] = {alias_of = "ellipsis of"},
["clipping of"] = {text = "[[Appendix:Glossary#clipping|clipping]] of", type_prefix = "CLIPPING_OF", cat = "cln:clippings",
default_foreign = true},
["clip of"] = {alias_of = "clipping of"},
["alternative form of"] = {text = "+", default_foreign = true},
["alt form"] = {alias_of = "alternative form of"},
["alternative spelling of"] = {text = "+", default_foreign = true},
["alt spell"] = {alias_of = "alternative spelling of"},
["alt sp"] = {alias_of = "alternative spelling of"},
["dated form of"] = {text = "[[Appendix:Glossary#dated|dated]] form of", type_prefix = "DATED_FORM_OF", cat = "cln:dated forms",
default_foreign = true},
["dated form"] = {alias_of = "dated form of"},
["dated spelling of"] = {text = "[[Appendix:Glossary#dated|dated]] spelling of", type_prefix = "DATED_FORM_OF", cat = "cln:dated forms",
default_foreign = true},
["dated spell"] = {alias_of = "dated spelling of"},
["dated sp"] = {alias_of = "dated spelling of"},
["archaic form of"] = {text = "[[Appendix:Glossary#archaic|archaic]] form of", type_prefix = "ARCHAIC_FORM_OF", cat = "cln:archaic forms",
default_foreign = true},
["arch form"] = {alias_of = "archaic form of"},
["archaic spelling of"] = {text = "[[Appendix:Glossary#archaic|archaic]] spelling of", type_prefix = "ARCHAIC_FORM_OF", cat = "cln:archaic forms",
default_foreign = true},
["arch spell"] = {alias_of = "archaic spelling of"},
["arch sp"] = {alias_of = "archaic spelling of"},
["obsolete form of"] = {text = "[[Appendix:Glossary#obsolete|obsolete]] form of", type_prefix = "OBSOLETE_FORM_OF", cat = "cln:obsolete forms",
default_foreign = true},
["obs form"] = {alias_of = "obsolete form of"},
["obsolete spelling of"] = {text = "[[Appendix:Glossary#obsolete|obsolete]] spelling of", type_prefix = "OBSOLETE_FORM_OF", cat = "cln:obsolete forms",
default_foreign = true},
["obs spell"] = {alias_of = "obsolete spelling of"},
["obs sp"] = {alias_of = "obsolete spelling of"},
}
local function get_seat_text(overall_place_spec)
local placetype = overall_place_spec.descs[1].placetypes[1]
if placetype == "county" or placetype == "counties" then
return "county seat"
elseif placetype == "parish" or placetype == "parishes" then
return "parish seat"
elseif placetype == "borough" or placetype == "boroughs" then
return "borough seat"
else
return "seat"
end
end
--[==[ var:
List specifying the allowed arguments containing extra information that is sometimes added to a definition, such as the
capital, largest city, modern name, official name, etc., along with associated properties; displayed in the order given.
See enwikt [[Module:place]] for full documentation of each field.
]==]
export.extra_info_args = {
{arg = "modern", text = "+", conjunction = "or", display_even_when_dropped = true},
{arg = "now", text = "now,", conjunction = "or", display_even_when_dropped = true},
{arg = "full", text = "in full,", conjunction = "or", display_even_when_dropped = true},
{arg = "short", text = "short form", conjunction = "or"},
{arg = "abbr", text = "abbreviation", conjunction = "or"},
{arg = "former", text = "formerly,"},
{arg = "official", text = "official name", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "capital", text = "+", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "largest city", text = "+", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "caplc", text = "capital and largest city", match_sentence_style = true, auto_plural = false,
with_colon = true},
{arg = "seat", text = get_seat_text, match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "shire town", text = "+", match_sentence_style = true, auto_plural = true, with_colon = true},
{arg = "headquarters", text = "+", match_sentence_style = true, auto_plural = false, with_colon = true},
{arg = "center", text = "administrative center", match_sentence_style = true, auto_plural = false, with_colon = true},
{arg = "centre", text = "administrative centre", match_sentence_style = true, auto_plural = false, with_colon = true},
}
export.extra_info_arg_map = {}
for _, spec in ipairs(export.extra_info_args) do
export.extra_info_arg_map[spec.arg] = spec
end
----------- Wikicode utility functions
-- Return a wikilink link {{l|language|text}}
local function link(text, langcode, id)
if not langcode then
return text
end
return m_links.language_link( -- l10n, avoid simp/trad or applying special fonts
{
term = text,
alt = text,
lang = require(languages_module).getByCode(langcode, true, "allow etym"),
id = id
}
)
end
---------- Basic utility functions
-- Add the page to a tracking "category". To see the pages in the "category",
-- go to [[Wiktionary:Tracking/place/PAGE]] and click on "What links here".
local function track(page)
require(debug_track_module)("place/" .. page)
return true
end
local function ucfirst_all(text)
if text:find(" ") then
local parts = split(text, " ", true)
for i, part in ipairs(parts) do
parts[i] = m_strutils.ucfirst(part)
end
return concat(parts, " ")
else
return m_strutils.ucfirst(text)
end
end
local function lc(text)
return mw.getContentLanguage():lc(text)
end
---------- Argument parsing functions and utilities
-- Split an argument on comma, but not comma followed by whitespace.
local function split_on_comma(val)
if val:find(",") then
return require(parse_interface_module).split_on_comma(val)
else
return {val}
end
end
-- Split an argument on slash, but not slash occurring inside of HTML tags like </span> or <br />.
local function split_on_slash(arg)
if arg:find("<") then
local m_parse_utilities = require(parse_utilities_module)
-- We implement this by parsing balanced segment runs involving <...>, and splitting on slash in the remainder.
-- The result is a list of lists, so we have to rejoin the inner lists by concatenating.
local segments = m_parse_utilities.parse_balanced_segment_run(arg, "<", ">")
local slash_separated_groups = m_parse_utilities.split_alternating_runs(segments, "/")
for i, group in ipairs(slash_separated_groups) do
slash_separated_groups[i] = concat(group)
end
return slash_separated_groups
else
return split(arg, "/", true)
end
end
-- Implement "implications", i.e. where the presence of a given holonym causes additional holonym(s) to be added.
-- Implications apply only to categorization.
local function handle_category_implications(place_descriptions, implication_data)
for i, desc in ipairs(place_descriptions) do
if desc.holonyms then
local new_holonyms = {}
for _, holonym in ipairs(desc.holonyms) do
insert(new_holonyms, holonym)
local imp_data = m_placetypes.get_equiv_placetype_prop(holonym.placetype, function(pt)
local implication = implication_data[pt] and implication_data[pt][holonym.unlinked_placename]
if implication then
return implication
end
end)
if imp_data then
for _, holonym_to_add in ipairs(imp_data) do
local split_holonym = split_on_slash(holonym_to_add)
if #split_holonym ~= 2 then
internal_error("Invalid holonym in implications: %s", holonym_to_add)
end
local holonym_placetype, holonym_placename = unpack(split_holonym, 1, 2)
local new_holonym = {
placetype = holonym_placetype, unlinked_placename = holonym_placename
}
insert(new_holonyms, new_holonym)
m_placetypes.key_holonym_into_place_desc(desc, new_holonym)
end
end
end
desc.holonyms = new_holonyms
end
end
end
-- Split a holonym (e.g. "continent/Europe" or "country/en:Italy" or "in southern" or "r:suf/O'Higgins" or
-- "c/Austria,Germany,Czech Republic") into its components. Return a list of holonym objects (see top of file). Note
-- that if there isn't a slash in the holonym (e.g. "in southern"), the `placetype` field of the holonym will be nil.
-- Placetype aliases (e.g. "r" for "region") and placename aliases (e.g. "US" or "USA" for "United States") will be
-- expanded.
local function split_holonym(raw)
local no_display, combined_holonym = raw:match("^(!)(.*)$")
no_display = not not no_display
combined_holonym = combined_holonym or raw
local suppress_comma, combined_holonym_without_comma = combined_holonym:match("^(%*)(.*)$")
suppress_comma = not not suppress_comma
combined_holonym = combined_holonym_without_comma or combined_holonym
local holonym_parts = split_on_slash(combined_holonym)
if #holonym_parts == 1 then
-- `unlinked_placename` should not be used.
return {{display_placename = combined_holonym, no_display = no_display, suppress_comma = suppress_comma}}
end
-- Rejoin further slashes in case of slash in holonym placename, e.g. Admaston/Bromley.
local placetype = holonym_parts[1]
local placename = concat(holonym_parts, "/", 2)
-- Check for modifiers after the holonym placetype.
local split_holonym_placetype = split(placetype, ":", true)
placetype = split_holonym_placetype[1]
local affix_type
local saw_also
local saw_the
for i = 2, #split_holonym_placetype do
local modifier = split_holonym_placetype[i]
if modifier == "also" then
if saw_also then
error(("Modifier ':also' occurs twice in holonym '%s'"):format(combined_holonym))
end
saw_also = true
elseif modifier == "the" then
if saw_the then
error(("Modifier ':the' occurs twice in holonym '%s'"):format(combined_holonym))
end
saw_the = true
elseif modifier == "pref" or modifier == "Pref" or modifier == "suf" or modifier == "Suf" or
modifier == "noaff" then
if affix_type then
error(("Affix-type modifier ':%s' occurs twice in holonym '%s'"):format(modifier, combined_holonym))
end
affix_type = modifier
else
error(("Unrecognized holonym placetype modifier '%s', should be one of " ..
"'pref', 'Pref', 'suf', 'Suf', 'noaff', 'also' or 'the'"):format(modifier))
end
end
placetype = m_placetypes.resolve_placetype_aliases(placetype)
local holonyms = split_on_comma(placename)
local pluralize_affix = #holonyms > 1
local affix_holonym_index = (affix_type == "pref" or affix_type == "Pref") and 1 or affix_type == "noaff" and 0 or
#holonyms
for i, placename in ipairs(holonyms) do
-- Check for langcode before the holonym placename, but don't get tripped up by Wikipedia links, which begin
-- "[[w:...]]" or "[[wikipedia:]]".
local langcode, placename_without_langcode = rmatch(placename, "^([^%[%]]-):(.*)$")
if langcode then
placename = placename_without_langcode
end
placename = m_placetypes.resolve_placename_display_aliases(placetype, placename)
holonyms[i] = {
placetype = placetype,
display_placename = placename,
unlinked_placename = m_placetypes.remove_links_and_html(placename),
langcode = langcode,
affix_type = i == affix_holonym_index and affix_type or nil,
pluralize_affix = i == affix_holonym_index and pluralize_affix,
suppress_affix = i ~= affix_holonym_index,
no_display = no_display,
suppress_comma = suppress_comma,
continue_cat_loop = saw_also,
force_the = i == 1 and saw_the,
}
end
return holonyms
end
local get_param_mods = memoize(function()
local m_param_utils = require(parameter_utilities_module)
return m_param_utils.construct_param_mods {
{group = {"link", "q", "l", "ref"}},
{param = "eq"},
{param = "conj", set = {["and"] = true, ["or"] = true, ["and/or"] = true}, overall = true},
}
end)
local function parse_term_with_inline_modifiers(term, paramname, default_lang)
local function generate_obj(raw_term, parse_err)
local obj = require(parameter_utilities_module).generate_obj_maybe_parsing_lang_prefix {
term = raw_term,
parse_err = parse_err,
parse_lang_prefix = true,
}
obj.lang = obj.lang or default_lang
obj.show_decorations = true
return obj
end
return require(parse_interface_module).parse_inline_modifiers(term, {
paramname = paramname,
param_mods = get_param_mods(),
generate_obj = generate_obj,
splitchar = ",",
outer_container = {},
})
end
local function parse_form_of_directive(arg, lang, form_of_overridden_args)
local form_of_directive, raw_terms = arg:match("^@([a-z -]+):(.*)$")
if not form_of_directive then
error("Misformatted @-directive: " .. dump(arg) .. " (directive name must be lowercase English letters and spaces, e.g. @official name of:比利時)")
end
if not export.all_form_of_directives[form_of_directive] then
local known_directives = {}
for k, _ in pairs(export.all_form_of_directives) do
insert(known_directives, '"' .. k .. '"')
end
table.sort(known_directives)
error(("Unrecognized form-of directive %s in @-directive %s; recognized directives are %s"):format(
dump(form_of_directive), dump(arg), concat(known_directives, ", ")))
end
local spec = export.all_form_of_directives[form_of_directive]
local canonical_directive = form_of_directive
if spec.alias_of then
canonical_directive = spec.alias_of
spec = export.all_form_of_directives[canonical_directive]
if not spec then
internal_error("Form-of directive alias %s points to %s, which is not a directive",
"@" .. form_of_directive, canonical_directive)
elseif spec.alias_of then
internal_error("Form-of directive alias %s points to %s, which is also an alias",
"@" .. form_of_directive, canonical_directive)
end
end
local default_foreign = spec.default_foreign
local directive_param = "@" .. form_of_directive
if form_of_overridden_args and form_of_overridden_args[canonical_directive] then
raw_terms = form_of_overridden_args[canonical_directive].new_value
local new_directive = form_of_overridden_args[canonical_directive].new_directive
local new_spec = export.all_form_of_directives[new_directive]
if not new_spec then
error(("Internal error: [[Module:transclude]] passed in unrecognized replacement directive '@%s'"):
format(new_directive))
end
if new_spec.alias_of then
error(("Internal error: [[Module:transclude]] passed in replacement directive alias '@%s', " ..
"should be canonical"):format(new_directive))
end
if new_directive ~= canonical_directive then
directive_param = directive_param .. (" (replaced with @%s)"):format(new_directive)
canonical_directive = new_directive
spec = new_spec
end
default_foreign = true
end
local terms = parse_term_with_inline_modifiers(raw_terms, directive_param,
default_foreign and lang or zhlang)
return {
directive = canonical_directive,
terms = terms.terms,
conj = terms.conj,
spec = spec,
}
end
-- Parse an argument containing extra information that is sometimes added to a definition, such as the capital, largest
-- city, modern name, official name, etc. `args` is the value from the parsed argument structure and can be either nil,
-- a string or a list. `spec` is the extra info spec corresponding to the type of extra info. Each value in `args` can
-- be a comma-separated list of terms with inline modifiers attached.
local function parse_extra_info_arg(args, spec, default_lang)
if not args then
return nil
end
if type(args) ~= "table" then
args = {args}
end
if not args[1] then
return nil
end
local terms = nil
local conj
for i, arg in ipairs(args) do
local this_terms = parse_term_with_inline_modifiers(arg, spec.arg .. (i == 1 and "" or i), default_lang)
local thisconj = this_terms.conj
if not conj then
conj = thisconj
elseif thisconj and conj ~= thisconj then
error(("Two different conjunctions '%s' and '%s' specified for |%s=; you only need to specify the " ..
"conjunction once"):format(conj, thisconj))
end
if not terms then
terms = this_terms.terms
else
m_table.extend(terms, this_terms.terms)
end
end
return {
spec = spec,
terms = terms,
conj = conj,
}
end
--[==[
Parse a "new-style" place description, with placetypes and holonyms surrounded by `<<...>>` amid otherwise raw text.
Return value is a place description object as documented at the top of the file. Exported for use by
[[Module:demonyms]].
]==]
function export.parse_new_style_place_desc(text, lang, form_of_directives, form_of_overridden_args)
local placetypes = {}
local segments = split(text, "<<(.-)>>")
local retval = {holonyms = {}, order = {}}
local form_of_directives_already_present = form_of_directives and not not form_of_directives[1]
for i, segment in ipairs(segments) do
if i % 2 == 1 then
insert(retval.order, {type = "raw", value = segment})
elseif segment:find("@") then
if not form_of_directives then
error(("Form-of directive '%s' not allowed in this context"):format(segment))
elseif form_of_directives_already_present then
error(("Saw form-of directive '%s' in new-style place desc followed by direct (separate-parameter) form-of directives; not allowed"):format(
segment))
elseif placetypes[1] or retval.holonyms[1] then
error(("Form-of directive '%s' must come first, before placetypes and holonyms"):format(segment))
else
local form_of_directive = parse_form_of_directive(segment, lang, form_of_overridden_args)
if not retval.order[1] or retval.order[1].type ~= "raw" or retval.order[2] then
internal_error("`retval.order` should have a single raw element: %s", retval.order)
end
form_of_directive.pretext = retval.order[1].value
retval.order[1] = nil
insert(form_of_directives, form_of_directive)
end
elseif segment:find("/") then
local holonyms = split_holonym(segment)
for j, holonym in ipairs(holonyms) do
if j > 1 then
if not holonym.no_display then
if j == #holonyms then
insert(retval.order, {type = "raw", value = zh_strings.and_conj})
else
insert(retval.order, {type = "raw", value = zh_strings.enum_sep})
end
end
holonym.needs_article = true
end
insert(retval.holonyms, holonym)
if not holonym.no_display then
insert(retval.order, {type = "holonym", value = #retval.holonyms})
end
m_placetypes.key_holonym_into_place_desc(retval, holonym)
end
else
local treat_as, display = segment:match("^(..-):(.+)$")
if treat_as then
segment = treat_as
else
display = segment
end
-- see if the placetype segment is just qualifiers
local only_qualifiers = true
local split_segments = split(segment, " ", true)
for _, split_segment in ipairs(split_segments) do
if m_placetypes.placetype_qualifiers[split_segment] == nil then
only_qualifiers = false
break
end
end
insert(placetypes, {placetype = segment, only_qualifiers = only_qualifiers})
if only_qualifiers then
insert(retval.order, {type = "qualifier", value = display})
else
insert(retval.order, {type = "placetype", value = display})
end
end
end
if not form_of_directives_already_present and form_of_directives and form_of_directives[1] then
form_of_directives[#form_of_directives].posttext = ""
end
local final_placetypes = {}
for i, placetype in ipairs(placetypes) do
if i > 1 and placetypes[i - 1].only_qualifiers then
final_placetypes[#final_placetypes] = final_placetypes[#final_placetypes] .. " " .. placetypes[i].placetype
else
insert(final_placetypes, placetypes[i].placetype)
end
end
retval.placetypes = final_placetypes
return retval
end
--[==[
Parse one or more "new-style" place descriptions, with placetypes and holonyms surrounded by `<<...>>` amid otherwise
raw text. Multiple descriptions are separated by two semicolons in a row. Return value is a list of place description
objects as documented at the top of the file.
]==]
local function parse_conjoined_new_style_place_desc(text, lang, form_of_directives, form_of_overridden_args)
local separate_specs = split(text, ";(;[^ ]*)")
local descs = {}
for i = 1, #separate_specs do
if i % 2 == 1 then
insert(descs, export.parse_new_style_place_desc(separate_specs[i], lang, form_of_directives,
form_of_overridden_args))
form_of_directives = nil
else
descs[#descs].separator = separate_specs[i]
end
end
return descs
end
--[=[
Process numeric and "extra info" arguments into an overall place spec, as described at the top of the file.
]=]
local function parse_overall_place_spec(data)
local args, from_tcl, extra_info_overridden_set, form_of_overridden_args =
data.args, data.from_tcl, data.extra_info_overridden_set, data.form_of_overridden_args
local descs = {}
local this_desc
-- Index of separate (semicolon-separated) place descriptions within `descs`.
local desc_index = 1
-- Index of separate holonyms within a place description. 0 means we've seen no holonyms and have yet to process
-- the placetypes that precede the holonyms. 1 means we've seen no holonyms but have already processed the
-- placetypes.
local holonym_index = 0
local in_place_desc = false
local form_of_directives = {}
-- ZH: use Chinese semicolon and space characters for multi-description joiners.
local function set_desc_joiner(desc, separator)
if separator == ";" then
this_desc.joiner = zh_strings.semicolon -- ";" instead of "; "
this_desc.include_following_article = true
elseif separator == ";;" then
this_desc.joiner = " "
else
local joiner = separator:sub(2)
if rfind(joiner, "^%a") then
this_desc.joiner = " " .. joiner .. " "
elseif joiner == "," then
-- ZH: ASCII comma → full-width ,(無空格)
this_desc.joiner = zh_strings.trans_sep
else
this_desc.joiner = joiner .. " "
end
end
end
for _, arg in ipairs(args[2]) do
if arg:find("^@") then
if not (desc_index == 1 and holonym_index == 0) then
error("@-directives cannot follow place descriptions")
end
local form_of_directive = parse_form_of_directive(arg, args[1], form_of_overridden_args)
if form_of_directives[1] then
form_of_directive.pretext = ", "
else
form_of_directive.pretext = ""
end
insert(form_of_directives, form_of_directive)
elseif arg == ";" or arg:find("^;[^ ]") then
if not this_desc then
error("Saw semicolon joiner without preceding place description")
end
set_desc_joiner(this_desc, arg)
desc_index = desc_index + 1
holonym_index = 0
in_place_desc = false
else
if arg:find("<<") then
if in_place_desc then
error("New-style place description must come first or following a separator (semicolon or similar), not directly following another description")
end
in_place_desc = true
local this_descs = parse_conjoined_new_style_place_desc(arg, args[1], form_of_directives,
form_of_overridden_args)
for j, desc in ipairs(this_descs) do
this_desc = desc
if holonym_index > 0 then
desc_index = desc_index + 1
holonym_index = 0
end
if j < #this_descs then
set_desc_joiner(this_desc, this_desc.separator)
end
descs[desc_index] = this_desc
last_was_new_style = true
holonym_index = #this_desc.holonyms + 1
end
else
-- Old-style arguments can directly follow a new-style argument; they become additional holonyms
-- tacked onto the end of the holonym list, and are displayed old-style except that there is no
-- prefix before the first one following the new-style argument.
in_place_desc = true
if holonym_index == 0 then
local entry_placetypes = split_on_slash(arg)
this_desc = {placetypes = entry_placetypes, holonyms = {}}
descs[desc_index] = this_desc
holonym_index = holonym_index + 1
else
local holonyms = split_holonym(arg)
for j, holonym in ipairs(holonyms) do
if j > 1 then
-- All but the first in a multi-holonym need an article. Not for the first one because e.g.
-- {{place|en|city|s/Arizona|c/United States}} should not display as "a city in Arizona, the
-- United States". The overall first holonym in the place description gets an article if
-- needed regardless of our setting here.
holonym.needs_article = true
-- Insert "and" before the last holonym.
if j == #holonyms then
this_desc.holonyms[holonym_index] = {
-- Use the no_display value from the first holonym; it should be the same for all
-- holonyms. `unlinked_placename` should not be used.
display_placename = "and", no_display = holonyms[1].no_display
}
holonym_index = holonym_index + 1
end
end
this_desc.holonyms[holonym_index] = holonym
m_placetypes.key_holonym_into_place_desc(this_desc, this_desc.holonyms[holonym_index])
holonym_index = holonym_index + 1
end
end
end
end
end
if form_of_directives[1] and not form_of_directives[#form_of_directives].posttext then
form_of_directives[#form_of_directives].posttext =
(args.def and args.def ~= "-" or not args.def and descs[1]) and ":" or ""
end
-- Tracking code (unchanged from enwikt).
for _, desc in ipairs(descs) do
for _, entry_placetype in ipairs(desc.placetypes) do
local splits = m_placetypes.split_qualifiers_from_placetype(entry_placetype, "no canon qualifiers")
for _, split in ipairs(splits) do
local prev_qualifier, this_qualifier, bare_placetype = unpack(split, 1, 3)
track("entry-placetype/" .. bare_placetype)
if this_qualifier then
track("entry-qualifier/" .. this_qualifier)
end
end
end
for _, holonym in ipairs(desc.holonyms) do
if holonym.placetype then
track("holonym-placetype/" .. holonym.placetype)
end
end
end
local extra_info = {}
for _, extra_info_spec in ipairs(export.extra_info_args) do
local extra_info_terms = parse_extra_info_arg(args[extra_info_spec.arg], extra_info_spec,
from_tcl and extra_info_overridden_set and extra_info_overridden_set[extra_info_spec.arg] and args[1] or
zhlang)
if extra_info_terms then
insert(extra_info, extra_info_terms)
end
end
return {
lang = args[1],
args = args,
directives = form_of_directives,
descs = descs,
extra_info = extra_info,
}
end
-------- Definition-generating functions
-- Return a string with the wikilinks to the ZH translations of the word.
-- ZH: joins multiple translations with "," (full-width comma) instead of ", ".
local function get_translations(transl, ids)
local ret = {}
for i, t in ipairs(transl) do
local arg_transls = split_on_comma(t)
local arg_ids = ids[i]
if arg_ids then
arg_ids = split_on_comma(arg_ids)
if #arg_transls ~= #arg_ids then
error(("Saw %s translation%s in t%s=%s but %s ID%s in tid%s=%s"):format(
#arg_transls, #arg_transls > 1 and "s" or "", i == 1 and "" or i, t, #arg_ids,
#arg_ids > 1 and "'s" or "", i == 1 and "" or i, ids[i]))
end
end
for j, arg_transl in ipairs(arg_transls) do
insert(ret, link(arg_transl, "zh", arg_ids and arg_ids[j] or nil))
end
end
return concat(ret, zh_strings.trans_sep) -- "," instead of ", "
end
-- Chinese has no articles; these stubs keep the call-sites intact.
local function get_placename_article(decorated_placename, placetypes, placename, suppress_holonym_use_the_check)
return nil
end
local function get_holonym_article(decorated_placename, place_desc, holonym_index)
return nil
end
-- Convert a holonym into display format. For ZH:
-- * No article prepended (Chinese has no articles).
-- * Both prefix and suffix affix types append the zh_suffix (e.g. "州", "省") directly to the linked name,
-- instead of the English "placetype of placename" / "placename placetype" construction.
-- * Display handlers are applied unchanged (they may add text based on the holonym's surrounding context).
local function format_holonym(place_desc, holonym_index, needs_article)
local holonym = place_desc.holonyms[holonym_index]
if holonym.no_display then
return ""
end
-- needs_article and force_the are not used in Chinese.
local output = holonym.display_placename
local placetype = holonym.placetype
local affix_type_pt_data, affix_type, affix_is_prefix, affix, prefix, suffix, no_affix_strings
local pt_equiv_for_affix_type, already_seen_affix, need_affix
local affix_pt_data, pt_equiv_for_affix
-- Implement display handlers (unchanged from enwikt).
local display_handler = m_placetypes.get_equiv_placetype_prop(placetype,
function(pt) return placetype_data[pt] and placetype_data[pt].display_handler end)
if display_handler then
output = display_handler(placetype, output)
end
if not holonym.suppress_affix then
-- Affix detection logic is unchanged from enwikt; only the rendering differs below.
affix_type_pt_data, pt_equiv_for_affix_type = m_placetypes.get_equiv_placetype_prop(placetype,
function(pt)
local cdpt = placetype_data[pt]
return cdpt and cdpt.affix_type and cdpt or nil
end
)
affix_pt_data, pt_equiv_for_affix = m_placetypes.get_equiv_placetype_prop(placetype,
function(pt)
local cdpt = placetype_data[pt]
return cdpt and (cdpt.affix_type or cdpt.affix or cdpt.prefix or cdpt.suffix) and cdpt or nil
end
)
if affix_type_pt_data then
affix_type = affix_type_pt_data.affix_type
need_affix = true
end
if affix_pt_data then
prefix = affix_pt_data.prefix or affix_pt_data.affix
suffix = affix_pt_data.suffix or affix_pt_data.affix
need_affix = true
end
no_affix_strings = affix_pt_data and affix_pt_data.no_affix_strings or
affix_type_pt_data and affix_type_pt_data.no_affix_strings
if holonym.affix_type and placetype then
affix_type = holonym.affix_type
prefix = prefix or placetype
suffix = suffix or placetype
need_affix = true
end
if need_affix then
affix_is_prefix = affix_type == "pref" or affix_type == "Pref"
if affix_is_prefix then
affix = prefix
else
affix = suffix
end
if not affix then
if not pt_equiv_for_affix_type then
internal_error("Something wrong, `pt_equiv_for_affix_type` not set processing holonym: %s",
holonym)
end
affix = pt_equiv_for_affix_type.placetype
if not affix then
internal_error("Something wrong, no affix could be located in `pt_equiv_for_affix_type` for " ..
"holonym %s: %s", holonym, pt_equiv_for_affix_type)
end
end
no_affix_strings = no_affix_strings or lc(affix)
-- ZH: skip pluralize_affix (Chinese doesn't pluralize)
end
end
-- ZH: resolve zh_suffix for the affix placetype before linking.
local zh_sfx_to_append = nil
if need_affix then
local _, zh_sfx = m_zh_data.get_zh_placetype_props(affix, m_placetypes)
if zh_sfx then
local zh_sfx_str = m_zh_data.format_zh_name(zh_sfx)
-- Check if zh_suffix already present in the unlinked output.
local unlinked = m_placetypes.remove_links_and_html(output)
if not unlinked:find(zh_sfx_str, 1, true) then
zh_sfx_to_append = zh_sfx_str
end
end
end
output = link(output, holonym.langcode or placetype and "zh" or nil)
-- ZH: for both pref and suf, append zh_suffix directly after the linked name.
-- (EN prefix construction "placetype of placename" is not used in Chinese.)
if zh_sfx_to_append then
output = output .. zh_sfx_to_append
end
-- No article for Chinese.
return output
end
-- Format holonyms for a ZH old-style place description.
-- Collects all holonyms, filters "and"/"or" connectors, reverses for big→small containment order,
-- groups consecutive same-type holonyms (peer locations), then joins with ZH enumeration (、/和).
-- Returns the concatenated holonym string, e.g. "美國賓夕法尼亞州", "波蘭、白俄羅斯和烏克蘭".
local function format_holonyms_zh(place_desc)
local holonyms = place_desc.holonyms
if not holonyms or #holonyms == 0 then
return ""
end
-- Collect non-"and"/"or"/"的" holonym items with their original indices.
-- A raw "的" holonym (from |的| in the template, like enwikt's |of|) signals possessive preposition;
-- filter it from display since format_old_style_place_desc_for_display already appends de_particle.
local items = {}
local has_de_joiner = false
for i, h in ipairs(holonyms) do
if not h.no_display then
local dname = h.display_placename
if dname ~= "and" and dname ~= "or" then
if not h.placetype and dname == zh_strings.de_particle then
has_de_joiner = true
else
insert(items, {holonym_index = i, placetype = h.placetype})
end
end
end
end
-- Separate trailing raw text (items after the last structural holonym) from non-trailing.
-- Trailing raw text should NOT be reversed; it is appended after the full description with ,.
local last_structural_idx = 0
for i = #items, 1, -1 do
if items[i].placetype then
last_structural_idx = i
break
end
end
local non_trailing = {}
local trailing_texts = {}
for i = 1, #items do
if i <= last_structural_idx then
insert(non_trailing, items[i])
else
insert(trailing_texts, holonyms[items[i].holonym_index].display_placename)
end
end
-- Reverse non-trailing for big→small order.
local reversed = {}
for i = #non_trailing, 1, -1 do
insert(reversed, non_trailing[i])
end
-- Group consecutive items with the same placetype (peers like multiple countries).
-- Raw-text holonyms (no placetype) break any current structural group.
local groups = {}
local i = 1
while i <= #reversed do
local item = reversed[i]
if not item.placetype then
-- Raw text (e.g. "南部", "東北部") forms its own singleton group.
local h = holonyms[item.holonym_index]
insert(groups, {type = "raw", text = h.display_placename})
i = i + 1
else
-- Structural holonym: collect consecutive ones of the same placetype.
local group = {type = "structural", items = {item}}
local pt = item.placetype
i = i + 1
while i <= #reversed do
local next_item = reversed[i]
if next_item.placetype == pt then
insert(group.items, next_item)
i = i + 1
else
break
end
end
insert(groups, group)
end
end
-- Format each group and assemble.
local parts = {}
for _, group in ipairs(groups) do
if group.type == "raw" then
insert(parts, group.text)
else
local formatted = {}
for _, item in ipairs(group.items) do
insert(formatted, format_holonym(place_desc, item.holonym_index, false))
end
if #formatted == 1 then
insert(parts, formatted[1])
elseif #formatted == 2 then
insert(parts, formatted[1] .. zh_strings.and_conj .. formatted[2])
else
local last = table.remove(formatted)
insert(parts, concat(formatted, zh_strings.enum_sep) .. zh_strings.and_conj .. last)
end
end
end
return concat(parts), has_de_joiner, trailing_texts
end
-- Get the Chinese display name for a single (possibly qualified) placetype.
-- Strips qualifiers, looks up zh_name via m_zh_data, and prepends translated qualifier text.
-- `first_holonym_placetype` enables context-sensitive names (e.g. capital city → 首都/省會/首府).
local function get_zh_placetype_name(placetype, first_holonym_placetype)
local resolved_pt = m_placetypes.resolve_placetype_aliases(placetype)
local zh_name, _, zh_by_holonym = m_zh_data.get_zh_placetype_props(resolved_pt, m_placetypes)
if zh_name then
local resolved = m_zh_data.resolve_zh_name(zh_name, zh_by_holonym, first_holonym_placetype)
return m_zh_data.format_zh_name(resolved)
end
-- Fallback to English name; add tracking for missing zh_data entries.
track("missing-zh-placetype/" .. tostring(placetype))
return placetype
end
-- Translate one qualifier word. `split_qualifiers_from_placetype` returns the canonicalized
-- qualifier, which for most qualifiers is wrapped in a wikilink ("[[autonomous]]"), so the markup
-- has to come off before the word can be looked up. Qualifiers with no Chinese equivalent are
-- returned as they came.
local function zh_qualifier(qualifier)
if not qualifier then
return ""
end
return m_zh_data.zh_qualifiers[m_placetypes.remove_links_and_html(qualifier)] or qualifier
end
-- Format a single entry placetype (possibly with qualifiers) for ZH display.
-- Strips qualifiers from the placetype, translates each qualifier via zh_qualifiers,
-- and prepends them to the Chinese placetype name (with wikilink markup applied).
local function format_one_zh_placetype(pt, first_holonym_placetype)
-- Try progressive qualifier splits; use the first that has a zh_name entry.
local splits = m_placetypes.split_qualifiers_from_placetype(pt)
local qualifier_prefix, reduced_pt = "", pt
for _, sp in ipairs(splits) do
local _, this_qualifier, bare_pt = unpack(sp, 1, 3)
-- Each split peels off one more qualifier, so accumulate them as we go.
qualifier_prefix = qualifier_prefix .. zh_qualifier(this_qualifier)
local zh_n = m_placetypes.get_zh_placetype_display_form(bare_pt, first_holonym_placetype)
if zh_n then
-- Found a known ZH entry; return qualifier + linked display form.
return qualifier_prefix .. zh_n
end
reduced_pt = bare_pt
end
-- Fallback: the qualifiers are already in `qualifier_prefix`, so name only the placetype left
-- after peeling them off, untranslated if need be.
return qualifier_prefix .. get_zh_placetype_name(reduced_pt, first_holonym_placetype)
end
-- Build the ZH entry placetype description from the `placetypes` list.
-- Handles "and"/"or" splitting (e.g. "城市/and/市鎮" → "城市和市鎮"),
-- multiple placetypes without and/or (joined with "、...和"),
-- and context-sensitive names via first_holonym_placetype.
local function get_zh_entry_placetype_desc(placetypes, first_holonym_placetype)
local is_ignorable = m_placetypes.placetype_is_ignorable
-- Find last and/or position.
local and_or_pos
for i, pt in ipairs(placetypes) do
if pt == "and" or pt == "or" then
and_or_pos = i
end
end
if and_or_pos then
-- Split into group before and group after the conjunction.
local group1, group2 = {}, {}
for i = 1, and_or_pos - 1 do
if not is_ignorable(placetypes[i]) then
insert(group1, format_one_zh_placetype(placetypes[i], first_holonym_placetype))
end
end
for i = and_or_pos + 1, #placetypes do
if not is_ignorable(placetypes[i]) then
insert(group2, format_one_zh_placetype(placetypes[i], first_holonym_placetype))
end
end
local conj_str = placetypes[and_or_pos] == "or" and "或" or zh_strings.and_conj
local all = {}
for _, v in ipairs(group1) do insert(all, v) end
for _, v in ipairs(group2) do insert(all, v) end
if #all == 0 then return "" end
if #all == 1 then return all[1] end
local last = table.remove(all)
return concat(all, zh_strings.enum_sep) .. conj_str .. last
else
-- No explicit and/or: collect all non-ignorable placetypes.
local formatted = {}
for _, pt in ipairs(placetypes) do
if not is_ignorable(pt) then
insert(formatted, format_one_zh_placetype(pt, first_holonym_placetype))
end
end
if #formatted == 0 then return "" end
if #formatted == 1 then return formatted[1] end
local last = table.remove(formatted)
return concat(formatted, zh_strings.enum_sep) .. zh_strings.and_conj .. last
end
end
-- Return the ZH description of a placetype for use in new-style <<placetype>> segments.
local function get_placetype_description(placetype, first_holonym_placetype)
return format_one_zh_placetype(placetype, first_holonym_placetype)
end
-- Return the ZH description of a qualifier for use in new-style <<qualifier>> segments.
local function get_qualifier_description(qualifier)
-- Trim to just the qualifier word (split_qualifiers_from_placetype requires a trailing non-qualifier word).
local splits = m_placetypes.split_qualifiers_from_placetype(qualifier .. " foo")
local translated = ""
for _, sp in ipairs(splits) do
translated = translated .. zh_qualifier(sp[2])
end
return translated ~= "" and translated or qualifier
end
-- Format a set of form-of directive terms (unchanged from enwikt, only conjunction uses zh_strings).
local function format_form_of_directive(overall_place_spec, directive_terms, ucfirst, from_tcl)
local formatted_terms = {}
local placetypes
if not overall_place_spec.descs[2] then
placetypes = overall_place_spec.descs[1].placetypes
else
placetypes = {}
for _, desc in ipairs(overall_place_spec.descs) do
m_table.extend(placetypes, desc.placetypes)
end
end
for _, termobj in ipairs(directive_terms.terms) do
local placename_article
if not termobj.alt and termobj.term and not termobj.term:find("%[%[") then
placename_article = get_placename_article(termobj.term, placetypes)
end
local linked_term = m_links.full_link(termobj, "term")
linked_term = "<span class='form-of-definition-link'>" .. linked_term .. "</span>"
if termobj.eq then
linked_term = linked_term .. " (= " .. m_links.full_link {term = termobj.eq, lang = zhlang} .. ")"
end
if placename_article then
linked_term = placename_article .. " " .. linked_term
end
insert(formatted_terms, linked_term)
end
local spec = directive_terms.spec
local text = spec.text
if type(text) == "function" then
text = text(overall_place_spec)
end
if text == "+" then
text = directive_terms.directive
end
-- Look up Chinese translation; all non-"+" directives also get translated.
local zh_text = m_zh_data.zh_form_of_directive_labels[directive_terms.directive]
if zh_text then
text = zh_text
end
if ucfirst then
text = m_strutils.ucfirst(text)
end
if not from_tcl then
local tracking_prefix = "form-of/" .. directive_terms.directive
track(tracking_prefix)
local langcode = overall_place_spec.lang:getCode()
local full_langcode = overall_place_spec.lang:getFullCode()
track(tracking_prefix .. "/" .. langcode)
if full_langcode ~= langcode then
track(tracking_prefix .. "/" .. full_langcode)
end
if full_langcode ~= "zh" then
track(tracking_prefix .. "/non-chinese")
end
end
return (require(form_of_module).format_form_of {
text = text,
lemmas = m_table.serialCommaJoin(formatted_terms, {conj = directive_terms.conj or spec.conjunction or zh_strings.and_conj}),
lemma_classes = false,
})
end
-- Format extra-info terms (capital, largest city, modern name, etc.) for ZH.
-- * Labels come from m_zh_data.zh_extra_info_labels; capital/caplc use zh_capital_label_by_holonym_type,
-- resolved first by entry placetype (國家→首都, 省→省會…), then by holonym placetype as fallback.
-- * Separator: "。" + label in sentence style; ";" + label otherwise.
-- * Multiple terms joined with 、/和 instead of serial comma.
-- * auto_plural is skipped (Chinese doesn't pluralize).
local function format_extra_info(overall_place_spec, extra_info_terms, sentence_style)
local formatted_terms = {}
for _, termobj in ipairs(extra_info_terms.terms) do
insert(formatted_terms, m_links.full_link(termobj))
end
local spec = extra_info_terms.spec
-- Resolve ZH label: look up by spec.arg, then spec.text as fallback.
local zh_label = m_zh_data.zh_extra_info_labels[spec.arg]
if not zh_label then
-- Try spec.text as the lookup key (e.g. "capital and largest city").
local spec_text = spec.text
if type(spec_text) == "function" then
spec_text = spec_text(overall_place_spec)
end
zh_label = m_zh_data.zh_extra_info_labels[spec_text] or spec_text or spec.arg
end
-- Context-sensitive capital label (首都/省會/首府/縣治…) for `capital=` and `caplc=`.
-- Priority: entry placetype (alias-resolved) → holonym placetype → keep existing zh_label.
-- For caplc, appends "和最大城市" to the resolved capital label.
if spec.arg == "capital" or spec.arg == "caplc" then
local cap_label = nil
local desc1 = overall_place_spec.descs and overall_place_spec.descs[1]
-- 1. Entry placetype (e.g. 國家→country→首都, 省→province→省會)
if desc1 then
for _, raw_pt in ipairs(desc1.placetypes) do
if not m_placetypes.placetype_is_ignorable(raw_pt) and raw_pt ~= "and" and raw_pt ~= "or" then
cap_label = m_placetypes.get_equiv_placetype_prop(raw_pt, function(pt)
return m_zh_data.zh_capital_label_by_holonym_type[pt]
end)
if cap_label then break end
end
end
end
-- 2. Holonym placetype fallback (previous behaviour)
if not cap_label and desc1 and desc1.holonyms then
for _, h in ipairs(desc1.holonyms) do
if h.placetype then
cap_label = m_zh_data.zh_capital_label_by_holonym_type[h.placetype]
if cap_label then break end
end
end
end
if cap_label then
zh_label = spec.arg == "caplc" and (cap_label .. "和最大城市") or cap_label
end
end
local text = zh_label
if spec.with_colon then
text = text .. ":" -- full-width colon
end
if sentence_style and spec.match_sentence_style then
text = "。" .. text
else
text = zh_strings.semicolon .. text
end
-- Join terms with ZH enumeration.
local conj = extra_info_terms.conj or zh_strings.and_conj
local joined
if #formatted_terms == 0 then
joined = ""
elseif #formatted_terms == 1 then
joined = formatted_terms[1]
elseif #formatted_terms == 2 then
joined = formatted_terms[1] .. conj .. formatted_terms[2]
else
local last = table.remove(formatted_terms)
joined = concat(formatted_terms, zh_strings.enum_sep) .. conj .. last
end
return text .. joined
end
-- Peel a `former` qualifier off every entry placetype, for hoisting it out of the noun phrase.
-- Chinese does not stack it in front of the placetype (前國家); it marks the whole phrase instead
-- (曾位於中歐的國家), so the caller adds 曾/曾經的 and displays the bare placetype. Returns nil
-- unless every placetype carries one — otherwise 曾 would also cover a placetype that is current
-- ("city/and/former capital" → 城市和前首府).
local function peel_former_qualifier(placetypes)
local peeled, saw_any = {}, false
for _, pt in ipairs(placetypes) do
if m_placetypes.placetype_is_ignorable(pt) then
insert(peeled, pt)
else
-- The deepest split holds every recognized qualifier of `pt` plus the bare placetype.
local splits = m_placetypes.split_qualifiers_from_placetype(pt, "no canon qualifiers")
local prev_qualifier, this_qualifier, bare_pt = unpack(splits[#splits], 1, 3)
local qualifiers = {}
if prev_qualifier then
for word in prev_qualifier:gmatch("%S+") do insert(qualifiers, word) end
end
if this_qualifier then
insert(qualifiers, this_qualifier)
end
local kept, found = {}, false
for _, qualifier in ipairs(qualifiers) do
if qualifier == "former" and not found then
found = true
else
insert(kept, qualifier)
end
end
if not found then
return nil
end
saw_any = true
insert(kept, bare_pt)
insert(peeled, concat(kept, " "))
end
end
return saw_any and peeled or nil
end
-- Format an old-style place description for ZH display.
-- Chinese pattern: [位於|""][holonym_string][的][zh_placetype_name]
-- * Holonyms are reversed (big→small), grouped, and joined with 、/和.
-- * "位於" is prepended for "in"-type placetypes; omitted for "of"-type (e.g. capitals).
-- * args.a (article override) is used as a prefix to the placetype name.
local function format_old_style_place_desc_for_display(args, place_desc, desc_index, with_article, ucfirst_flag)
local placetypes = place_desc.placetypes
-- Determine the entry placetype (last non-ignorable, non-conjunction item) for preposition lookup.
local entry_placetype_for_prep = nil
for i = #placetypes, 1, -1 do
local pt = placetypes[i]
if not m_placetypes.placetype_is_ignorable(pt) and pt ~= "and" and pt ~= "or" then
entry_placetype_for_prep = pt
break
end
end
-- Determine first structured holonym placetype for context-sensitive zh_name lookups.
local first_holonym_placetype = nil
if place_desc.holonyms then
for _, h in ipairs(place_desc.holonyms) do
if h.placetype then
first_holonym_placetype = h.placetype
break
end
end
end
-- Build the Chinese placetype description (possibly with qualifiers and multiple types).
-- A `former` qualifier is hoisted out of the noun phrase; the preposition is still looked up
-- from the original placetype, so `former capital` keeps its possessive "of".
local hoist_former = peel_former_qualifier(placetypes)
local zh_pt_name = get_zh_entry_placetype_desc(hoist_former or placetypes, first_holonym_placetype)
-- Handle args.a (article override): used as a direct prefix in Chinese, e.g. a=盎格魯撒克遜 → "盎格魯撒克遜王國".
if with_article and desc_index == 1 and args.a then
zh_pt_name = args.a .. zh_pt_name
end
-- Build holonym string (reversed, grouped, ZH-joined).
-- has_de_joiner is true when a bare |的| token was in the holonym list (like enwikt's |of|).
local holonym_str, has_de_joiner, trailing_texts = "", false, {}
if place_desc.holonyms and #place_desc.holonyms > 0 then
holonym_str, has_de_joiner, trailing_texts = format_holonyms_zh(place_desc)
end
-- Determine preposition style ("in" → 位於…的; "of" → …的 with no 位於).
-- An explicit |的| joiner in the template overrides to possessive (no 位於).
local use_wei_yu = true
if has_de_joiner then
use_wei_yu = false
elseif entry_placetype_for_prep then
local prep = m_placetypes.get_placetype_entry_preposition(entry_placetype_for_prep)
if prep == "of" then
use_wei_yu = false
end
end
-- Assemble final string.
local result
if holonym_str ~= "" then
if use_wei_yu then
result = (hoist_former and zh_strings.former_adverb or "") .. zh_strings.in_preposition ..
holonym_str .. zh_strings.de_particle .. zh_pt_name
else
result = holonym_str ..
(hoist_former and zh_strings.former_de or zh_strings.de_particle) .. zh_pt_name
end
else
result = (hoist_former and zh_strings.former_de or "") .. zh_pt_name
end
if #trailing_texts > 0 then
result = result .. zh_strings.trans_sep .. concat(trailing_texts, zh_strings.trans_sep)
end
return result
end
--[==[
Get the full gloss (Chinese description) of a new-style place description. New-style place descriptions are
specified with a single string containing raw text interspersed with placetypes and holonyms surrounded by `<<...>>`.
Exported for use by [[Module:demonyms]].
]==]
function export.format_new_style_place_desc_for_display(args, place_desc, with_article)
local parts = {}
local function ins(txt)
insert(parts, txt)
end
-- args.a not used in Chinese (no article system).
local first_holonym_placetype = nil
if place_desc.holonyms then
for _, h in ipairs(place_desc.holonyms) do
if h.placetype then
first_holonym_placetype = h.placetype
break
end
end
end
local max_holonym = 0
for _, order in ipairs(place_desc.order) do
local segment_type, segment = order.type, order.value
if segment_type == "raw" then
ins(segment)
elseif segment_type == "placetype" then
ins(get_placetype_description(segment, first_holonym_placetype))
elseif segment_type == "qualifier" then
ins(get_qualifier_description(segment))
elseif segment_type == "holonym" then
ins(format_holonym(place_desc, segment, false))
if segment > max_holonym then
max_holonym = segment
end
else
internal_error("Unrecognized segment type %s", segment_type)
end
end
if place_desc.holonyms and max_holonym < #place_desc.holonyms then
local holonym_no_prefix = true
for holonym_index = max_holonym + 1, #place_desc.holonyms do
-- Append remaining holonyms with a space prefix (no "in"/"of" for new-style).
ins(" " .. format_holonym(place_desc, holonym_index, false))
holonym_no_prefix = false
end
end
return concat(parts)
end
-- Return a string with the gloss (the description of the place itself, as opposed to translations).
-- `sentence_style` controls whether extra-info uses "。" (sentence) or ";" (semicolon) separation.
local function get_display_form(data)
local overall_place_spec, ucfirst, sentence_style, drop_extra_info, extra_info_overridden_set, from_tcl =
data.overall_place_spec, data.ucfirst, data.sentence_style, data.drop_extra_info,
data.extra_info_overridden_set, data.from_tcl
local args = overall_place_spec.args
local parts = {}
local function ins(txt)
table.insert(parts, txt)
end
if overall_place_spec.directives and overall_place_spec.directives[1] then
for i, directive_terms in ipairs(overall_place_spec.directives) do
ins(directive_terms.pretext)
if directive_terms.pretext ~= "" then
ucfirst = false
end
if not args.def or args.def == "-" then
ins(format_form_of_directive(overall_place_spec, directive_terms, ucfirst, from_tcl))
ucfirst = false
if i == #overall_place_spec.directives and directive_terms.posttext then
ins(directive_terms.posttext)
end
end
end
end
if args.def == "-" then
return concat(parts)
end
if args.def then
if args.def:find("<<") then
local def_desc = export.parse_new_style_place_desc(args.def, args[1])
ins(export.format_new_style_place_desc_for_display({}, def_desc, false))
else
ins(args.def)
end
else
local include_article = true
for n, desc in ipairs(overall_place_spec.descs) do
if desc.order then
ins(export.format_new_style_place_desc_for_display(args, desc, n == 1))
else
ins(format_old_style_place_desc_for_display(args, desc, n, include_article, ucfirst))
end
if desc.joiner then
ins(desc.joiner)
end
include_article = desc.include_following_article
ucfirst = false
end
end
local addl = args.addl
if addl then
if addl:find("^[;:]") then
ins(addl)
elseif addl:find("^_") then
ins(" " .. addl:sub(2))
else
ins("," .. addl)
end
end
for _, extra_info_terms in ipairs(overall_place_spec.extra_info) do
if not drop_extra_info or extra_info_terms.spec.display_even_when_dropped or
extra_info_overridden_set and extra_info_overridden_set[extra_info_terms.spec.arg] then
ins(format_extra_info(overall_place_spec, extra_info_terms, sentence_style))
end
end
return concat(parts)
end
-- Return the definition line.
-- ZH: sentence_style is always true; ucfirst is always false (no case in Chinese);
-- parentheses around glosses are full-width ().
local function get_def(data)
local overall_place_spec, from_tcl, drop_extra_info, extra_info_overridden_set, translation_follows =
data.overall_place_spec, data.from_tcl, data.drop_extra_info, data.extra_info_overridden_set,
data.translation_follows
local args = overall_place_spec.args
local sentence_style = true -- always sentence style in Chinese
local ucfirst = false -- Chinese has no case
if #args.t > 0 then
local gloss = get_display_form {
overall_place_spec = overall_place_spec,
ucfirst = false,
sentence_style = false,
drop_extra_info = drop_extra_info,
extra_info_overridden_set = extra_info_overridden_set,
from_tcl = from_tcl,
}
if from_tcl and not args.tcl_nolc then
gloss = m_strutils.lcfirst(gloss)
end
if translation_follows then
return (gloss == "" and "" or gloss .. ":") .. get_translations(args.t, args.tid)
else
return get_translations(args.t, args.tid) ..
(gloss == "" and "" or zh_strings.paren_open .. gloss .. zh_strings.paren_close)
end
else
return get_display_form {
overall_place_spec = overall_place_spec,
ucfirst = ucfirst,
sentence_style = sentence_style,
drop_extra_info = drop_extra_info,
extra_info_overridden_set = extra_info_overridden_set,
from_tcl = from_tcl,
}
end
end
---------- Functions for the category wikicode
-- The code in this section finds the categories to which a given place belongs. See enwikt [[Module:place]]
-- for detailed documentation of the algorithm.
--[=[
Find the appropriate category specs for a given place description and placetype.
See enwikt [[Module:place]] for detailed documentation.
]=]
local function find_placetype_cat_specs(data)
local entry_placetype, place_desc, first_holonym_index, overriding_holonym, from_demonym =
data.entry_placetype, data.place_desc, data.first_holonym_index, data.overriding_holonym, data.from_demonym
local form_of_directive = data.form_of_directive
local function fetch_cat_specs(holonym_to_match, index, no_fallback)
local holonym_placetype = holonym_to_match.placetype
if not holonym_placetype then
return nil
end
local holonym_placename = holonym_to_match.unlinked_placename
if not holonym_placename then
internal_error("Missing unlinked_placename in holonym (index %s): %s", index, holonym_to_match)
end
local cat_specs, equiv_entry_placetype_and_qualifier = m_placetypes.get_equiv_placetype_prop(entry_placetype,
function(equiv_entry_pt)
return m_placetypes.get_equiv_placetype_prop(holonym_placetype,
function(equiv_holonym_pt) return m_placetypes.political_division_cat_handler {
entry_placetype = equiv_entry_pt,
holonym_placetype = equiv_holonym_pt,
holonym_placename = holonym_placename,
holonym_index = index,
place_desc = place_desc,
from_demonym = from_demonym,
} end)
end,
{no_fallback = no_fallback, form_of_directive = form_of_directive}
)
if cat_specs and cat_specs[1] then
return cat_specs, equiv_entry_placetype_and_qualifier.placetype
end
local cat_handler, equiv_entry_placetype_and_qualifier = m_placetypes.get_equiv_placetype_prop(entry_placetype,
function(equiv_entry_pt)
local entry_placetype_data = m_placetypes.placetype_data[equiv_entry_pt]
if entry_placetype_data and entry_placetype_data.cat_handler then
return entry_placetype_data.cat_handler
end
end,
{no_fallback = no_fallback, form_of_directive = form_of_directive}
)
if cat_handler then
local cat_specs = m_placetypes.get_equiv_placetype_prop(holonym_placetype,
function(equiv_holonym_pt) return cat_handler {
entry_placetype = equiv_entry_placetype_and_qualifier.placetype,
holonym_placetype = equiv_holonym_pt,
holonym_placename = holonym_placename,
holonym_index = index,
place_desc = place_desc,
from_demonym = from_demonym,
} end)
if cat_specs and cat_specs[1] then
return cat_specs, equiv_entry_placetype_and_qualifier.placetype
end
end
if not no_fallback then
local cat_specs, equiv_entry_placetype_and_qualifier = m_placetypes.get_equiv_placetype_prop(entry_placetype,
function(equiv_entry_pt)
local entry_placetype_data = m_placetypes.placetype_data[equiv_entry_pt]
if entry_placetype_data then
return m_placetypes.get_equiv_placetype_prop(holonym_placetype,
function(equiv_holonym_pt)
return entry_placetype_data[equiv_holonym_pt .. "/*"]
end)
end
end,
{form_of_directive = form_of_directive}
)
if cat_specs and cat_specs[1] then
return cat_specs, equiv_entry_placetype_and_qualifier.placetype
end
end
return nil
end
if overriding_holonym then
local cat_specs, fetched_entry_placetype = fetch_cat_specs(overriding_holonym, nil)
if cat_specs and cat_specs[1] then
return {
entry_placetype = fetched_entry_placetype,
cat_specs = cat_specs,
triggering_holonym = overriding_holonym,
}
end
else
-- First pass: no fallback (exact entry placetype match only).
for i, holonym in ipairs(place_desc.holonyms) do
if first_holonym_index and i < first_holonym_index then
-- continue
else
local cat_specs, fetched_entry_placetype = fetch_cat_specs(holonym, i, "no_fallback")
if cat_specs and cat_specs[1] then
return {
entry_placetype = fetched_entry_placetype,
cat_specs = cat_specs,
triggering_holonym = holonym,
triggering_holonym_index = i,
}
end
end
end
-- Second pass: with fallback.
for i, holonym in ipairs(place_desc.holonyms) do
if first_holonym_index and i < first_holonym_index then
-- continue
else
local cat_specs, fetched_entry_placetype = fetch_cat_specs(holonym, i)
if cat_specs and cat_specs[1] then
return {
entry_placetype = fetched_entry_placetype,
cat_specs = cat_specs,
triggering_holonym = holonym,
triggering_holonym_index = i,
}
end
end
end
end
return nil
end
-- Turn a list of category specs (see comment at section top) into the corresponding categories
-- (minus the language code prefix).
local function cat_specs_to_categories(place_desc, cat_data)
local all_cats = {}
local cat_specs, entry_placetype, triggering_holonym, triggering_holonym_index =
cat_data.cat_specs, cat_data.entry_placetype, cat_data.triggering_holonym, cat_data.triggering_holonym_index
if triggering_holonym then
for _, cat_spec in ipairs(cat_specs) do
local cat
if cat_spec == true then
-- ZH: 1-char zh_name → "的" + name (e.g. "的省"); 2+ chars → no "的" (e.g. "城市").
local zh_raw = m_placetypes.get_zh_placetype_name(entry_placetype)
local de = zh_raw and m_placetypes.zh_cat_de(zh_raw) or zh_strings.de_particle
local zh_str = zh_raw and m_zh_data.format_zh_name(zh_raw) or entry_placetype
cat = "+++" .. de .. zh_str
else
cat = cat_spec
end
if cat:find("%+%+%+") then
local group, key, spec, container_trail = m_placetypes.find_matching_holonym_location {
holonym_placetype = triggering_holonym.placetype,
holonym_placename = triggering_holonym.unlinked_placename,
holonym_index = triggering_holonym_index,
place_desc = place_desc,
}
if group then
cat = cat:gsub("%+%+%+", m_strutils.replacement_escape(m_placetypes.get_prefixed_key(key, spec)))
insert(all_cats, cat)
else
mw.log(("Unable to insert category for cat spec '%s' because holonym '%s/%s' did not match a " ..
"known location"):format(cat, triggering_holonym.placetype, triggering_holonym.unlinked_placename))
track("cant-match-holonym-for-category-spec")
end
else
insert(all_cats, cat)
end
end
else
for _, cat_spec in ipairs(cat_specs) do
local cat
if cat_spec == true then
-- ZH: bare placetype category (e.g. "城市")
cat = get_zh_placetype_name(entry_placetype)
else
cat = cat_spec
if cat:find("%+%+%+") then
internal_error("Category %s contains +++ but there is no holonym to substitute", cat)
end
end
insert(all_cats, cat)
end
end
return all_cats
end
-- Return the categories (without initial lang code) that should be added to the entry, given the place description
-- and a particular entry placetype.
local function get_placetype_cats(place_desc, entry_placetype, from_demonym, form_of_directive)
local cats = {}
local first_holonym_index = 1
while first_holonym_index <= #place_desc.holonyms do
local cat_data = find_placetype_cat_specs {
entry_placetype = entry_placetype,
place_desc = place_desc,
first_holonym_index = first_holonym_index,
from_demonym = from_demonym,
form_of_directive = form_of_directive,
}
if not cat_data then
break
end
local triggering_holonym = cat_data.triggering_holonym
if not triggering_holonym then
internal_error("find_placetype_cat_specs should have returned a triggering holonym: %s", cat_data)
end
extend(cats, cat_specs_to_categories(place_desc, cat_data))
first_holonym_index = cat_data.triggering_holonym_index
local equiv_triggering_placetypes = m_placetypes.get_placetype_equivs(triggering_holonym.placetype,
{no_fallback = true})
for _, equiv in ipairs(equiv_triggering_placetypes) do
local other_holonyms_of_same_type = place_desc.holonyms_by_placetype[equiv.placetype]
if other_holonyms_of_same_type then
for _, other_placename_of_same_type in ipairs(other_holonyms_of_same_type) do
if other_placename_of_same_type ~= triggering_holonym.unlinked_placename then
local overriding_holonym = {
placetype = triggering_holonym.placetype,
unlinked_placename = other_placename_of_same_type,
}
local other_cat_data = find_placetype_cat_specs {
entry_placetype = entry_placetype,
place_desc = place_desc,
overriding_holonym = overriding_holonym,
from_demonym = from_demonym,
form_of_directive = form_of_directive,
}
if other_cat_data then
extend(cats, cat_specs_to_categories(place_desc, other_cat_data))
end
end
end
end
end
first_holonym_index = first_holonym_index + 1
while first_holonym_index <= #place_desc.holonyms do
if place_desc.holonyms[first_holonym_index].continue_cat_loop then
break
end
first_holonym_index = first_holonym_index + 1
end
end
if cats[1] then
return cats
end
local entry_pt_default, equiv_entry_placetype_and_qualifier =
m_placetypes.get_equiv_placetype_prop(entry_placetype, function(pt)
return m_placetypes.placetype_data[pt] and m_placetypes.placetype_data[pt].default
end,
{form_of_directive = form_of_directive})
if entry_pt_default then
return cat_specs_to_categories(place_desc, {
cat_specs = entry_pt_default,
entry_placetype = equiv_entry_placetype_and_qualifier.placetype,
})
end
return {}
end
--[==[
Iterate through each type of place and return a list of the categories that need to be added to the entry.
See enwikt [[Module:place]] for detailed documentation.
]==]
function export.get_cats(args, overall_place_spec, from_demonym)
local cats = {}
local place_descriptions = overall_place_spec.descs
handle_category_implications(place_descriptions, m_placetypes.cat_implications)
m_placetypes.augment_holonyms_with_container(place_descriptions)
if overall_place_spec.directives then
for _, directive_terms in ipairs(overall_place_spec.directives) do
local spec_cats = directive_terms.spec.cat
if spec_cats then
if type(spec_cats) == "string" then
spec_cats = {spec_cats}
end
for _, spec_cat in ipairs(spec_cats) do
insert(cats, spec_cat)
end
end
if directive_terms.spec.type_prefix then
for _, place_desc in ipairs(place_descriptions) do
for _, placetype in ipairs(place_desc.placetypes) do
if not m_placetypes.placetype_is_ignorable(placetype) then
extend(cats, get_placetype_cats(place_desc, placetype, from_demonym,
directive_terms.spec.type_prefix))
end
end
end
end
end
end
if not from_demonym then
local bare_categories = m_placetypes.get_bare_categories(args, overall_place_spec)
extend(cats, bare_categories)
end
for _, place_desc in ipairs(place_descriptions) do
if not from_demonym then
for _, placetype in ipairs(place_desc.placetypes) do
if not m_placetypes.placetype_is_ignorable(placetype) then
extend(cats, get_placetype_cats(place_desc, placetype))
end
end
end
extend(cats, get_placetype_cats(place_desc, "*", from_demonym))
end
if args.cat then
for _, cat in ipairs(args.cat) do
local split_cats = split_on_comma(cat)
extend(cats, split_cats)
end
end
return cats
end
-- Return the category wikicode for a list of categories.
-- ZH: topical categories use `lang:getFullName() .. " " .. cat` ("漢語 XX" instead of "zh:XX");
-- cln: categories use the language name with no separator ("漢語縮寫"), per zhwikt convention.
local function format_cats(lang, cats, sort_key)
local full_cats = {}
local langname = lang:getFullName()
for _, cat in ipairs(cats) do
local cln_cat = cat:match("^cln:(.*)$")
if cln_cat then
-- ZH: CLN categories take no separator after the language name (漢語縮寫).
local zh_cln = m_zh_data.zh_cln_categories[cln_cat]
insert(full_cats, zh_cln and (langname .. zh_cln) or (langname .. " " .. cln_cat))
else
-- ZH: use langname prefix instead of langcode:cat
insert(full_cats, langname .. " " .. cat)
end
end
return require(utilities_module).format_categories(full_cats, lang, sort_key, nil,
force_cat or m_placetypes.get_force_cat())
end
----------- Main entry point
--[==[
Implementation of {{tl|place}}. Meant to be callable from another module.
See enwikt [[Module:place]] for full documentation of the `data` argument fields.
]==]
function export.format(data)
local template_args = data.template_args
local list_param = {list = true}
local boolean_param = {type = "boolean"}
local params = {
[1] = {required = true, type = "language", default = "und"},
[2] = {required = true, list = true},
["t"] = list_param,
["tid"] = {list = true, allow_holes = true},
["cat"] = list_param,
["nocat"] = boolean_param,
["nocap"] = boolean_param,
["sort"] = true,
["pagename"] = true, -- for testing or documentation purposes
["a"] = true,
["addl"] = true,
["def"] = true,
-- params that are only used when transcluding using {{tcl}}/{{transclude}}, to transmit information to {{tcl}}.
["tcl"] = true,
["tcl_t"] = list_param,
["tcl_tid"] = list_param,
["tcl_nolb"] = true,
["tcl_nolc"] = boolean_param,
["tcl_noextratext"] = boolean_param,
}
-- add "extra info" parameters
for _, extra_arg_spec in ipairs(export.extra_info_args) do
params[extra_arg_spec.arg] = list_param
end
if template_args.def == "" then
error("Cannot currently pass def= as an empty parameter; use def=- if you want to suppress the definition display")
end
local args = require("Module:parameters").process(template_args, params)
if args.a then
track("a")
end
data.args = args
local overall_place_spec = parse_overall_place_spec(data)
data.overall_place_spec = overall_place_spec
return get_def(data) .. (
args.nocat and "" or format_cats(args[1], export.get_cats(args, overall_place_spec), args.sort))
end
--[==[
Actual entry point of {{tl|place}}.
]==]
function export.show(frame)
return export.format {
template_args = frame:getParent().args,
}
end
return export
86c71d88h5autz6vl54w3krntjb65md
Module:Place/data
828
1629828
9909172
9906082
2026-09-29T09:12:58Z
TongcyBot
83009
更新至最新修正版
9909172
Scribunto
text/plain
local export = {}
--[==[
Chinese localization data for [[Module:place]].
Fields per placetype entry:
zh_name -- Chinese display name in definition sentences (string or {string,...})
zh_suffix -- Chinese suffix for :pref/:suf holonym modifiers; if nil, falls back to zh_name
zh_name_by_holonym_type
-- context-sensitive Chinese name keyed by the first holonym's placetype;
-- used when the correct term depends on what the place is capital/seat of
zh_preposition -- "in" (→ 位於…的) or "of" (→ …的); nil means inherit from enwikt `preposition` field
-- Only needed when the Chinese preposition differs from the English one.
Multiple-regional-variant names are stored as arrays {"primary", "alt1", "alt2",...}.
- The first element is the canonical/primary form used in categories.
- All elements are displayed, joined with "/" in holonym context.
For zh_suffix with regional variants, use the same array convention.
]==]
-- ---------------------------------------------------------------------------
-- Placetype qualifier translations
-- ---------------------------------------------------------------------------
-- Maps English qualifier words (as canonicalized in placetype_qualifiers) to
-- their Chinese equivalents. Used when displaying qualified placetypes such as
-- "前城市"、"古代王國" etc.
export.zh_qualifiers = {
-- Historicity
["former"] = "前",
["ancient"] = "古代",
["medieval"] = "中世紀",
["historical"] = "歷史上的",
["extinct"] = "已消亡的",
["abandoned"] = "廢棄的",
["deserted"] = "廢棄的",
["ruined"] = "廢墟",
-- Hypothetical
["fictional"] = "虛構的",
["mythological"]= "神話中的",
-- Size
["small"] = "小",
["large"] = "大",
["major"] = "主要的",
["minor"] = "次要的",
["tiny"] = "微小的",
["huge"] = "龐大的",
-- Coastal/water
["coastal"] = "沿海",
["inland"] = "內陸",
["maritime"] = "濱海",
["riverine"] = "沿河",
-- Direction (adjectival use before holonym)
["northern"] = "北部",
["southern"] = "南部",
["eastern"] = "東部",
["western"] = "西部",
["central"] = "中部",
["northeastern"]= "東北部",
["northwestern"]= "西北部",
["southeastern"]= "東南部",
["southwestern"]= "西南部",
-- Status
["autonomous"] = "自治",
["independent"] = "獨立的",
["unincorporated"] = "非建制",
["incorporated"] = "建制",
["rural"] = "農村",
["urban"] = "城市",
["suburban"] = "郊區",
["industrial"] = "工業",
-- Terrain
["mountainous"] = "山地",
["volcanic"] = "火山",
["peninsular"] = "半島",
["insular"] = "島嶼",
-- Habitation / access
["landlocked"] = "內陸",
["uninhabited"] = "無人",
["overseas"] = "海外",
-- Historical register
["traditional"] = "傳統",
-- Legal/political status
["planned"] = "計劃性",
["de facto"] = "事實上的",
["de jure"] = "法理上的",
["de-facto"] = "事實上的",
["de-jure"] = "法理上的",
["official"] = "官方",
["unofficial"] = "非官方",
["chartered"] = "特許",
["claimed"] = "聲稱的",
["coterminous"] = "同域",
["special"] = "特別",
-- More size/extent
["big"] = "大",
["mid-size"] = "中型",
["mid-sized"] = "中型",
["sizable"] = "頗大的",
["important"] = "重要的",
["long"] = "長",
["short"] = "短",
["high"] = "高",
["tall"] = "高",
["low"] = "低",
["left"] = "左",
["right"] = "右",
-- More historicity
["modern"] = "現代",
["historic"] = "歷史上的",
["mediaeval"] = "中世紀",
["legendary"] = "傳說中的",
["mythical"] = "神話中的",
-- More water
["seaside"] = "海濱",
["beachfront"] = "海灘旁",
["beachside"] = "海灘旁",
["riverside"] = "河畔",
["freshwater"] = "淡水",
["saltwater"] = "鹹水",
["endorheic"] = "內流",
["oxbow"] = "牛軛",
["ox-bow"] = "牛軛",
["tidal"] = "潮汐",
-- More terrain
["hilltop"] = "山頂",
["hilly"] = "丘陵",
["mountaintop"] = "山頂",
["alpine"] = "高山",
["chalk"] = "白堊",
["karst"] = "喀斯特",
["limestone"] = "石灰岩",
-- Wealth
["fashionable"] = "時尚的",
["wealthy"] = "富裕的",
["affluent"] = "富裕的",
["declining"] = "衰落中的",
-- More city vs. rural
["exurban"] = "遠郊",
["outlying"] = "外圍",
["remote"] = "偏遠",
["outback"] = "內陸荒野",
["inner"] = "內",
["inner-city"] = "市中心",
["outer"] = "外",
-- Land use
["residential"] = "住宅",
["agricultural"]= "農業",
["business"] = "商務",
["commercial"] = "商業",
["railroad"] = "鐵路",
["railway"] = "鐵路",
["farming"] = "農業",
["fishing"] = "漁業",
["mining"] = "礦業",
["logging"] = "伐木",
["cattle"] = "畜牧",
-- Tourism
["resort"] = "度假",
["spa"] = "溫泉",
["ski"] = "滑雪",
-- Religion
["holy"] = "聖",
["sacred"] = "神聖的",
["religious"] = "宗教",
["secular"] = "世俗",
-- Bare compass directions (the "-ern" forms are above)
["north"] = "北",
["south"] = "南",
["east"] = "東",
["west"] = "西",
["northeast"] = "東北",
["southeast"] = "東南",
["northwest"] = "西北",
["southwest"] = "西南",
-- Season
["summer"] = "夏季",
["winter"] = "冬季",
-- Ordinals
["first"] = "第一",
["second"] = "第二",
["third"] = "第三",
["fourth"] = "第四",
["last"] = "最後一個",
["only"] = "唯一",
["sole"] = "唯一",
["main"] = "主要",
-- Superlatives
["largest"] = "最大",
["biggest"] = "最大",
["smallest"] = "最小",
["longest"] = "最長",
["shortest"] = "最短",
["tallest"] = "最高",
["highest"] = "最高",
["lowest"] = "最低",
["leftmost"] = "最左",
["rightmost"] = "最右",
["innermost"] = "最內",
["outermost"] = "最外",
["northernmost"]= "最北",
["southernmost"]= "最南",
["easternmost"] = "最東",
["westernmost"] = "最西",
["northeasternmost"] = "最東北",
["southeasternmost"] = "最東南",
["northwesternmost"] = "最西北",
["southwesternmost"] = "最西南",
-- Quantity
["several"] = "數個",
["various"] = "各個",
["numerous"] = "眾多",
["multiple"] = "多個",
["many"] = "許多",
["other"] = "其他",
}
-- ---------------------------------------------------------------------------
-- Extra-info label translations
-- ---------------------------------------------------------------------------
-- Maps the enwikt extra-info parameter names to Chinese display labels.
-- Used when appending "首都:X"、"最大城市:X" etc. to definitions.
export.zh_extra_info_labels = {
["capital"] = "首都", -- overridden for non-country holonyms; see zh_capital_label_by_holonym
["largest city"] = "最大城市",
["capital and largest city"] = "首都和最大城市",
["official"] = "官方名稱",
["modern"] = "現代名稱",
["full"] = "全稱",
["seat"] = "治所",
["county seat"] = "縣治",
["borough seat"] = "區治",
["parish seat"] = "教區治所",
["shire town"] = "郡鎮",
}
-- ---------------------------------------------------------------------------
-- Form-of directive label translations
-- ---------------------------------------------------------------------------
-- Maps English @-directive canonical keys to Chinese display text.
-- Used in format_form_of_directive; text is appended to the lemma link:
-- [[比利時]]的官方名稱:...
-- Every value begins with 的 (or 之) because zhwikt's Module:form of puts lemmas before text.
-- The shortening and superseded-form terms follow zhwikt's own templates ({{acronym of}} etc.)
-- so that one page does not show two words for the same thing.
export.zh_form_of_directive_labels = {
["official name of"] = "的官方名稱",
["former name of"] = "的舊名",
["ancient name of"] = "的古名",
["former official name of"] = "的前官方名稱",
["long form of"] = "的全稱",
["former long form of"] = "的前全稱",
["nickname for"] = "的別稱",
["official nickname for"] = "的官方別稱",
["former nickname for"] = "的前別稱",
["derogatory name for"] = "的貶稱",
["synonym of"] = "的同義詞",
["abbreviation of"] = "的縮寫",
["initialism of"] = "的首字母縮略詞",
["acronym of"] = "之首字母縮拼詞",
["syllabic abbreviation of"] = "的音節縮寫",
["ellipsis of"] = "的縮略形式",
["clipping of"] = "的截斷形式",
["alternative form of"] = "的替代形式",
["alternative spelling of"] = "的替代拼寫",
["dated form of"] = "的過時形式",
["dated spelling of"] = "的過時拼寫",
["archaic form of"] = "的古舊形式",
["archaic spelling of"] = "的古舊拼寫",
["obsolete form of"] = "的棄用形式",
["obsolete spelling of"] = "的棄用拼寫",
}
-- ---------------------------------------------------------------------------
-- CLN (per-language, non-topical) categories
-- ---------------------------------------------------------------------------
-- The `cat` field of all_form_of_directives names enwikt CLN categories ("English
-- abbreviations"). zhwikt's equivalents carry no separator after the language name
-- (漢語縮寫, not 漢語 縮寫) and are defined in Module:category tree/shortenings and
-- Module:category tree/pragmatic properties; the value here is appended directly.
export.zh_cln_categories = {
["abbreviations"] = "縮寫",
["initialisms"] = "首字母縮略詞",
["acronyms"] = "首字母縮拼詞",
["syllabic abbreviations"] = "音節縮寫",
["ellipses"] = "縮略形式",
["clippings"] = "截斷形式",
["dated forms"] = "過時形式",
["archaic forms"] = "古舊形式",
["obsolete forms"] = "棄用形式",
}
-- Context-sensitive label for `capital=` parameter, keyed by entry placetype.
-- When a capital-bearing placetype is identified, we look up the holonym type
-- to determine the correct Chinese term for the capital. Falls back to "首都".
export.zh_capital_label_by_holonym_type = {
["country"] = "首都",
["constituent country"]= "首都",
["state"] = "首府",
["province"] = "省會",
["territory"] = "首府",
["region"] = "首府",
["administrative region"] = "首府",
["autonomous region"] = "首府",
["county"] = "縣治",
["district"] = "區治",
["department"] = "省治",
["prefecture"] = "縣治",
}
-- ---------------------------------------------------------------------------
-- Special-syntax string translations
-- ---------------------------------------------------------------------------
-- Miscellaneous fixed strings used in building definition sentences.
export.zh_strings = {
in_preposition = "位於", -- prepended for "in" placetypes
-- "of" placetypes use no prepositional prefix; holonym directly precedes 的
de_particle = "的", -- possessive/locative particle
and_conj = "和", -- final conjunction in enumerations
enum_sep = "、", -- enumeration separator (replaces serial comma)
multi_name_sep = "/", -- separator for regional name variants (e.g. 格魯吉亞/喬治亞)
trans_sep = ",", -- separator for multiple t1/t2 translations
paren_open = "(",
paren_close = ")",
semicolon = ";", -- multiple-description join (replaces "; ")
former_name_of = "的舊稱", -- @former name of:X → "X的舊稱"
-- Chinese marks "former" adverbially ahead of the whole phrase (曾位於中歐的國家), not
-- attributively in front of the placetype; former_de replaces de_particle in possessive
-- descriptions (日本曾經的首都).
former_adverb = "曾",
former_de = "曾經的",
}
-- ---------------------------------------------------------------------------
-- Placetype data (zh_ fields)
-- ---------------------------------------------------------------------------
-- Keys match the canonical English placetype keys in enwikt's placetype_data.
-- Entries without zh_name will be looked up recursively through enwikt's
-- `fallback` chain until a zh_name is found; if none found, the English name
-- is displayed and a tracking category is added.
export.zh_placetype_data = {
-- -----------------------------------------------------------------------
-- Top-level polities
-- -----------------------------------------------------------------------
["country"] = {
zh_name = "國家",
zh_suffix = "國",
},
["city-state"] = {
zh_name = "城邦",
},
["polity"] = {
zh_name = "政治實體",
},
["empire"] = {
zh_name = "帝國",
zh_suffix = "帝國",
},
["kingdom"] = {
zh_name = "王國",
zh_suffix = "王國",
},
["republic"] = {
zh_name = "共和國",
zh_suffix = "共和國",
},
["federation"] = {
zh_name = "聯邦",
zh_suffix = "聯邦",
},
["confederation"] = {
zh_name = "邦聯",
zh_suffix = "邦聯",
},
["monarchy"] = {
zh_name = "君主國",
},
["principality"] = {
zh_name = "公國",
zh_suffix = "公國",
},
["duchy"] = {
zh_name = "公國",
zh_suffix = "公國",
},
["caliphate"] = {
zh_name = "哈里發國",
},
["khanate"] = {
zh_name = "汗國",
zh_suffix = "汗國",
},
["emirate"] = {
zh_name = "酋長國",
zh_suffix = "酋長國",
},
["bailiwick"] = {
zh_name = "行政管轄區",
},
["captaincy"] = {
zh_name = "都督區",
},
["microstate"] = {
zh_name = "微型國家",
},
["unrecognized country"] = {
zh_name = "未獲承認的國家",
},
-- -----------------------------------------------------------------------
-- Constituent / pseudo-countries
-- -----------------------------------------------------------------------
["constituent country"] = {
-- England, Scotland, Wales, Northern Ireland within the UK
zh_name = "構成國",
zh_suffix = "構成國",
},
["autonomous community"] = {
-- Spain
zh_name = "自治區",
zh_suffix = "自治區",
},
["autonomous republic"] = {
zh_name = "自治共和國",
zh_suffix = "自治共和國",
},
["special administrative region"] = {
-- China: Hong Kong, Macau
zh_name = "特別行政區",
zh_suffix = "特別行政區",
},
["dependent territory"] = {
zh_name = "屬地",
},
["overseas territory"] = {
zh_name = "海外屬地",
},
["Crown dependency"] = {
zh_name = "王室屬地",
},
["crown dependency"] = {
zh_name = "王室屬地",
},
-- -----------------------------------------------------------------------
-- First-level administrative divisions
-- -----------------------------------------------------------------------
["state"] = {
zh_name = "州",
zh_suffix = "州",
},
["province"] = {
zh_name = "省",
zh_suffix = "省",
zh_name_by_holonym_type = {
["empire"] = "行省",
},
},
["provinces and autonomous regions"] = {
-- Combined cat_as used for China's provinces + ARs (mirrors enwikt cat_as grouping).
zh_name = "省和自治區",
},
["counties and county-level cities"] = {
zh_name = "縣和縣級市",
},
["states and union territories"] = {
zh_name = "邦和聯邦直轄區",
},
["states and FCT"] = {
zh_name = "州和聯邦首都轄區",
},
["provinces and territories"] = {
zh_name = "省和地區",
},
["states and territories"] = {
zh_name = "州和地區",
},
["metropolitan regions"] = {
zh_name = "大區",
},
["constituent countries"] = {
zh_name = "構成國",
},
["territory"] = {
zh_name = "地區",
zh_suffix = "地區",
},
["region"] = {
zh_name = "地區", -- fallback; specific regions use location data (e.g. 法國大區)
zh_suffix = "地區",
},
["administrative region"] = {
zh_name = "行政區",
zh_suffix = "行政區",
},
["autonomous region"] = {
zh_name = "自治區",
zh_suffix = "自治區",
},
["autonomous oblast"] = {
zh_name = "自治州",
zh_suffix = "自治州",
},
["autonomous okrug"] = {
zh_name = "自治區",
zh_suffix = "自治區",
},
["autonomous province"] = {
zh_name = "自治省",
zh_suffix = "自治省",
},
["autonomous prefecture"] = {
zh_name = "自治州",
zh_suffix = "自治州",
},
["autonomous territory"] = {
zh_name = "自治領地",
},
["autonomous territorial unit"] = {
-- Moldova (Gagauzia, Transnistria)
zh_name = "自治地域單位",
},
["greater administrative region"] = {
-- China, 1949-1954
zh_name = "大行政區",
zh_suffix = "大區",
},
["aimag"] = {
-- A league in Inner Mongolia; a province in Mongolia itself.
zh_name = "盟",
zh_suffix = "盟",
zh_name_by_holonym_type = {
["country"] = "省",
},
},
["commandery"] = {
-- Historical China, Korea, Vietnam
zh_name = "郡",
zh_suffix = "郡",
},
["Roman province"] = {
zh_name = "羅馬行省",
zh_suffix = "行省",
},
["federal subject"] = {
-- Russia
zh_name = "聯邦主體",
},
["voivodeship"] = {
-- Poland
zh_name = "省",
zh_suffix = "省",
},
["oblast"] = {
-- Russia/Ukraine
zh_name = "州",
zh_suffix = "州",
},
["okrug"] = {
zh_name = "區",
zh_suffix = "區",
},
["krai"] = {
-- Russia
zh_name = "邊疆區",
zh_suffix = "邊疆區",
},
["raion"] = {
-- Ukraine/Russia
zh_name = "區",
zh_suffix = "區",
},
["department"] = {
-- France and other countries
zh_name = "省",
zh_suffix = "省",
},
["governorate"] = {
zh_name = "省",
zh_suffix = "省",
},
["canton"] = {
-- Switzerland, Luxembourg etc.
zh_name = "州",
zh_suffix = "州",
},
["division"] = {
zh_name = "省",
zh_suffix = "省",
},
["periphery"] = {
-- Greece
zh_name = "大區",
zh_suffix = "大區",
},
["collectivity"] = {
-- France overseas
zh_name = "集體",
},
["commonwealth"] = {
-- Puerto Rico
zh_name = "自由邦",
},
["constituent republic"] = {
-- Yugoslavia/Russia
zh_name = "加盟共和國",
zh_suffix = "共和國",
},
["colony"] = {
zh_name = "殖民地",
},
["viceroyalty"] = {
zh_name = "副王轄地",
},
["regency"] = {
zh_name = "縣",
zh_suffix = "縣",
},
-- -----------------------------------------------------------------------
-- Second-level and lower administrative divisions
-- -----------------------------------------------------------------------
["county"] = {
zh_name = "縣",
zh_suffix = "縣",
},
["district"] = {
zh_name = "區",
zh_suffix = "區",
},
["municipality"] = {
zh_name = "市鎮",
zh_suffix = "市鎮",
},
["commune"] = {
zh_name = "市鎮",
},
["comune"] = {
-- Italy/Switzerland
zh_name = "市鎮",
},
["borough"] = {
zh_name = "區",
zh_suffix = "區",
},
["metropolitan borough"] = {
zh_name = "都會區",
zh_suffix = "都會區",
},
["London borough"] = {
zh_name = "倫敦區",
zh_suffix = "區",
},
["royal borough"] = {
zh_name = "皇家自治市",
},
["county borough"] = {
zh_name = "郡市",
zh_suffix = "郡市",
},
["parish"] = {
zh_name = "教區",
zh_suffix = "教區",
},
["civil parish"] = {
zh_name = "教區",
zh_suffix = "教區",
},
["council area"] = {
-- Scotland
zh_name = "議會地區",
zh_suffix = "議會地區",
},
["prefecture"] = {
-- Japan: 縣(ken); France: 省治城市(handled by capital city logic)
zh_name = "都道府縣",
zh_suffix = "縣",
},
["prefecture-level city"] = {
-- China
zh_name = "地級市",
zh_suffix = "市",
},
["county-level city"] = {
-- China
zh_name = "縣級市",
zh_suffix = "市",
},
["subprovincial city"] = {
-- China
zh_name = "副省級市",
zh_suffix = "市",
},
["sub-prefectural city"] = {
-- China
zh_name = "副地級市",
zh_suffix = "市",
},
["subprovincial district"] = {
-- China (Pudong, Binhai)
zh_name = "副省級市轄區",
zh_suffix = "區",
},
["new area"] = {
-- China
zh_name = "新區",
zh_suffix = "新區",
},
["state-level new area"] = {
-- China
zh_name = "國家級新區",
zh_suffix = "新區",
},
["sum"] = {
-- A township in Inner Mongolia; a county in Mongolia itself.
zh_name = "蘇木",
zh_suffix = "蘇木",
},
["metropolitan city"] = {
-- South Korea; also Italy, where it is closer to a province
zh_name = "廣域市",
zh_suffix = "廣域市",
},
["direct-administered municipality"] = {
-- China (Beijing, Shanghai, etc.)
zh_name = "直轄市",
zh_suffix = "市",
},
["special municipality"] = {
-- Taiwan (Taipei, New Taipei etc.)
zh_name = "直轄市",
zh_suffix = "市",
},
["county-administered city"] = {
-- Taiwan
zh_name = "縣轄市",
zh_suffix = "市",
},
["special ward"] = {
-- Tokyo
zh_name = "特別區",
zh_suffix = "區",
},
["municipal district"] = {
zh_name = "市轄區",
zh_suffix = "區",
},
["subdistrict"] = {
zh_name = "街道",
zh_suffix = "街道",
},
["township"] = {
zh_name = "鄉",
zh_suffix = "鄉",
},
["rural township"] = {
-- Taiwan
zh_name = "鄉",
zh_suffix = "鄉",
},
["urban township"] = {
zh_name = "鎮",
zh_suffix = "鎮",
},
["mountain indigenous township"] = {
-- Taiwan
zh_name = "山地原住民鄉",
zh_suffix = "鄉",
},
["mountain indigenous district"] = {
-- Taiwan
zh_name = "山地原住民區",
zh_suffix = "區",
},
["Indian reservation"] = {
-- United States
zh_name = "印第安保留地",
},
["Indian reserve"] = {
-- Canada
zh_name = "印第安保留地",
},
["community development block"] = {
-- India
zh_name = "社區發展區",
},
["union territory"] = {
-- India
zh_name = "聯合地區",
},
["hromada"] = {
-- Ukraine
zh_name = "社區",
zh_suffix = "社區",
},
["urban hromada"] = {
zh_name = "城市社區",
zh_suffix = "社區",
},
["rural hromada"] = {
zh_name = "農村社區",
zh_suffix = "社區",
},
["settlement hromada"] = {
zh_name = "聚落社區",
zh_suffix = "社區",
},
["regional unit"] = {
-- Greece
zh_name = "地區單位",
zh_suffix = "地區",
},
["regional county municipality"] = {
-- Quebec
zh_name = "地區縣市",
},
["regional district"] = {
-- Canada
zh_name = "地區區",
},
["regional municipality"] = {
zh_name = "地區市",
},
["local government area"] = {
-- Australia
zh_name = "地方政府區",
},
["local government district"] = {
zh_name = "地方政府區",
},
["arrondissement"] = {
zh_name = "區",
zh_suffix = "區",
},
["cercle"] = {
-- Mali
zh_name = "縣",
},
["geopolitical zone"] = {
-- Nigeria
zh_name = "地緣政治區",
},
["enclave"] = {
zh_name = "飛地",
},
["exclave"] = {
zh_name = "外飛地",
},
-- -----------------------------------------------------------------------
-- Capitals and seats
-- -----------------------------------------------------------------------
["capital city"] = {
-- Default for most contexts; see zh_name_by_holonym_type for overrides
zh_name = "首府",
zh_name_by_holonym_type = {
["country"] = "首都",
["constituent country"] = "首都",
["microstate"] = "首都",
["city-state"] = "首都",
["state"] = "首府",
["territory"] = "首府",
["region"] = "首府",
["administrative region"] = "首府",
["autonomous region"] = "首府",
["province"] = "省會",
["autonomous province"] = "省會",
["county"] = "縣治",
["district"] = "區治",
["department"] = "省治",
["prefecture"] = "縣治",
["municipality"] = "市治",
},
},
["capital"] = {
-- Alias for capital city; same logic
zh_name = "首府",
zh_name_by_holonym_type = {
["country"] = "首都",
["constituent country"] = "首都",
["state"] = "首府",
["territory"] = "首府",
["region"] = "首府",
["province"] = "省會",
["county"] = "縣治",
["district"] = "區治",
["department"] = "省治",
},
},
["state capital"] = {
zh_name = "首府",
},
["provincial capital"] = {
zh_name = "省會",
},
["regional capital"] = {
zh_name = "首府",
},
["district capital"] = {
zh_name = "區治",
},
["departmental capital"] = {
zh_name = "省治",
},
["national capital"] = {
zh_name = "首都",
},
["administrative capital"] = {
zh_name = "行政首都",
},
["legislative capital"] = {
zh_name = "立法首都",
},
["judicial capital"] = {
zh_name = "司法首都",
},
["royal capital"] = {
zh_name = "王都",
},
["co-capital"] = {
zh_name = "共同首都",
},
["largest city"] = {
zh_name = "最大城市",
},
["caplc"] = {
zh_name = "首都和最大城市",
},
["non-city capital"] = {
zh_name = "首府",
},
["county seat"] = {
zh_name = "縣治",
},
["county town"] = {
zh_name = "縣城",
},
["borough seat"] = {
zh_name = "區治",
},
["parish seat"] = {
zh_name = "教區治所",
},
["shire town"] = {
zh_name = "郡鎮",
},
["administrative center"] = {
zh_name = "行政中心",
},
["administrative centre"] = {
zh_name = "行政中心",
},
["administrative headquarters"] = {
zh_name = "行政總部",
},
["French prefecture"] = {
-- A city that is the administrative capital of a French department
zh_name = "省府",
zh_name_by_holonym_type = {
["department"] = "省府",
},
},
-- -----------------------------------------------------------------------
-- Cities and urban settlements
-- -----------------------------------------------------------------------
["city"] = {
zh_name = "城市",
zh_suffix = "市",
},
["town"] = {
zh_name = "鎮",
zh_suffix = "鎮",
},
["village"] = {
zh_name = "村",
zh_suffix = "村",
},
["hamlet"] = {
zh_name = "小村",
},
["locality"] = {
zh_name = "村落",
},
["settlement"] = {
zh_name = "聚落",
},
["populated place"] = {
zh_name = "居民點",
},
["neighborhood"] = {
zh_name = "街區",
},
["neighbourhood"] = {
zh_name = "街區",
},
["suburb"] = {
zh_name = "郊區",
},
["community"] = {
zh_name = "社區",
},
["urban area"] = {
zh_name = "城市地區",
},
["residential area"] = {
zh_name = "住宅區",
},
["housing estate"] = {
zh_name = "住宅區",
},
["planned community"] = {
zh_name = "計劃社區",
},
["administrative village"] = {
zh_name = "行政村",
zh_suffix = "村",
},
["barangay"] = {
-- Philippines
zh_name = "里",
},
["barrio"] = {
zh_name = "里",
},
["kibbutz"] = {
zh_name = "集體農場",
},
["ghost town"] = {
zh_name = "鬼城",
},
["unincorporated community"] = {
zh_name = "非建制社區",
},
["census-designated place"] = {
zh_name = "人口普查指定地區",
},
["military base"] = {
zh_name = "軍事基地",
},
["research station"] = {
zh_name = "研究站",
},
["home rule city"] = {
zh_name = "自治市",
},
["federal district"] = {
zh_name = "聯邦特區",
zh_suffix = "特區",
},
["independent city"] = {
zh_name = "獨立市",
},
["autonomous city"] = {
zh_name = "自治市",
},
["port city"] = {
zh_name = "港市",
},
["port town"] = {
zh_name = "港鎮",
},
["market town"] = {
zh_name = "市集鎮",
},
["resort town"] = {
zh_name = "度假鎮",
},
["new town"] = {
zh_name = "新市鎮",
},
-- -----------------------------------------------------------------------
-- Geographic / natural features
-- -----------------------------------------------------------------------
["continent"] = {
zh_name = "大洲",
zh_suffix = "大洲",
},
["大陸"] = {
zh_name = "大陸",
zh_suffix = "大陸",
},
["continental region"] = {
zh_name = "大洲地區",
},
["supercontinent"] = {
zh_name = "超大陸",
},
["continents and continental regions!"] = {
zh_name = "大洲和大洲地區",
},
["ocean"] = {
zh_name = "大洋",
zh_suffix = "大洋",
},
["sea"] = {
zh_name = "海",
zh_suffix = "海",
},
["inland sea"] = {
zh_name = "內陸海",
},
["marginal sea"] = {
zh_name = "邊緣海",
},
["river"] = {
zh_name = "河",
zh_suffix = "河",
},
["河流"] = {
zh_name = "河流",
zh_suffix = "河",
},
["lake"] = {
zh_name = "湖",
zh_suffix = "湖",
},
["reservoir"] = {
zh_name = "水庫",
zh_suffix = "水庫",
},
["stream"] = {
zh_name = "溪",
zh_suffix = "溪",
},
["creek"] = {
zh_name = "溪",
zh_suffix = "溪",
},
["bay"] = {
zh_name = "灣",
zh_suffix = "灣",
},
["gulf"] = {
zh_name = "海灣",
zh_suffix = "海灣",
},
["fjord"] = {
zh_name = "峽灣",
},
["strait"] = {
zh_name = "海峽",
zh_suffix = "海峽",
},
["channel"] = {
zh_name = "水道",
zh_suffix = "水道",
},
["inlet"] = {
zh_name = "海灣",
zh_suffix = "灣",
},
["arm"] = {
zh_name = "海灣",
},
["island"] = {
zh_name = "島嶼",
zh_suffix = "島",
},
["islet"] = {
zh_name = "小島",
zh_suffix = "島",
},
["island group"] = {
zh_name = "群島",
},
["archipelago"] = {
zh_name = "群島",
zh_suffix = "群島",
},
["列島"] = {
zh_name = "列島",
zh_suffix = "列島",
},
["atoll"] = {
zh_name = "環礁",
zh_suffix = "環礁",
},
["peninsula"] = {
zh_name = "半島",
zh_suffix = "半島",
},
["spit"] = {
zh_name = "沙嘴",
},
["cape"] = {
zh_name = "角",
zh_suffix = "角",
},
["headland"] = {
zh_name = "岬角",
},
["mountain"] = {
zh_name = "山",
zh_suffix = "山",
},
["hill"] = {
zh_name = "丘",
zh_suffix = "丘",
},
["mountain range"] = {
zh_name = "山脈",
zh_suffix = "山脈",
},
["mountain pass"] = {
zh_name = "山口",
},
["plateau"] = {
zh_name = "高原",
zh_suffix = "高原",
},
["valley"] = {
zh_name = "谷",
zh_suffix = "谷",
},
["glen"] = {
zh_name = "峽谷",
},
["volcano"] = {
zh_name = "火山",
zh_suffix = "火山",
},
["forest"] = {
zh_name = "林地",
zh_suffix = "林",
},
["moor"] = {
zh_name = "荒原",
},
["desert"] = {
zh_name = "沙漠",
zh_suffix = "沙漠",
},
["beach"] = {
zh_name = "海灘",
zh_suffix = "海灘",
},
["bodies of water!"] = {
zh_name = "水體",
},
["spring"] = {
zh_name = "泉",
zh_suffix = "泉",
},
["hot spring"] = {
zh_name = "溫泉",
zh_suffix = "溫泉",
},
["tributary"] = {
zh_name = "支流",
},
["distributary"] = {
zh_name = "分流",
},
-- -----------------------------------------------------------------------
-- Geographic / cultural regions (non-admin)
-- -----------------------------------------------------------------------
["geographic and cultural area"] = {
zh_name = "地理文化區域",
},
["geographic region"] = {
zh_name = "地理區域",
},
["historical region"] = {
zh_name = "歷史地區",
},
["historic region"] = {
zh_name = "歷史地區",
},
["cultural region"] = {
zh_name = "文化地區",
},
["subregion"] = {
zh_name = "次地區",
},
["macroregion"] = {
zh_name = "大區域",
},
-- -----------------------------------------------------------------------
-- Man-made structures
-- -----------------------------------------------------------------------
["airport"] = {
zh_name = "機場",
},
["port"] = {
zh_name = "港口",
},
["bridge"] = {
zh_name = "橋",
},
["road"] = {
zh_name = "道路",
},
["highway"] = {
zh_name = "公路",
},
["motorway"] = {
zh_name = "高速公路",
},
["street"] = {
zh_name = "街道",
},
["building"] = {
zh_name = "建築物",
},
["university"] = {
zh_name = "大學",
},
["park"] = {
zh_name = "公園",
},
["national park"] = {
zh_name = "國家公園",
},
["metro station"] = {
zh_name = "地鐵站",
},
["temple"] = {
zh_name = "廟宇",
},
["castle"] = {
zh_name = "城堡",
},
-- -----------------------------------------------------------------------
-- Misc / hypothetical
-- -----------------------------------------------------------------------
["fictional location"] = {
zh_name = "虛構地點",
},
["mythological location"] = {
zh_name = "神話地點",
},
["star"] = {
zh_name = "恆星",
},
["space station"] = {
zh_name = "太空站",
},
-- Former-place grouping categories (mirrors enwikt's bare_category_parent chain).
-- 前X reads as a title (前總統、前妻), so a defunct place is 已不存在的X instead. A former
-- capital normally still exists as a town, so that one stays 歷史首府.
["former places"] = {
zh_name = "已不存在的地點",
},
["former polities"] = {
zh_name = "已不存在的政治實體",
},
["former countries and country-like entities"] = {
zh_name = "已不存在的國家和類國家實體",
},
["former political divisions"] = {
zh_name = "已不存在的政治區劃",
},
["former dependent territories"] = {
zh_name = "已不存在的屬地",
},
["former settlements"] = {
zh_name = "已不存在的聚居地",
},
["former natural features"] = {
zh_name = "已不存在的自然地物",
},
["former man-made structures"] = {
zh_name = "已不存在的人造構築物",
},
["former capitals"] = {
zh_name = "歷史首府",
},
["ancient settlements"] = {
zh_name = "古代定居點",
},
}
-- ---------------------------------------------------------------------------
-- Helper: look up zh_name for a placetype via fallback chain
-- ---------------------------------------------------------------------------
-- Looks up zh_name (and optionally zh_name_by_holonym_type) for a given
-- placetype, recursively following the enwikt fallback chain if needed.
-- Returns zh_name (string or table), zh_suffix (string or table or nil),
-- zh_name_by_holonym_type (table or nil).
-- `m_pt` should be the enwikt place-placetypes module (for fallback data).
function export.get_zh_placetype_props(placetype, m_pt)
local visited = {}
local current = placetype
while current and not visited[current] do
visited[current] = true
local zh = export.zh_placetype_data[current]
if zh and zh.zh_name then
return zh.zh_name, zh.zh_suffix, zh.zh_name_by_holonym_type
end
-- Follow enwikt fallback
if m_pt then
local ptdata = m_pt.placetype_data[current]
if ptdata and ptdata.fallback then
current = ptdata.fallback
-- Strip FORMER/ANCIENT prefixes from synthetic fallbacks like "FORMER settlement"
current = current:match("^FORMER (.+)$") or current:match("^ANCIENT (.+)$") or current
else
break
end
else
break
end
end
return nil, nil, nil
end
-- ---------------------------------------------------------------------------
-- Helper: resolve zh_name given holonym context
-- ---------------------------------------------------------------------------
-- Given the zh_name (possibly a table of variants) and zh_name_by_holonym_type,
-- and the holonym's placetype, return the appropriate Chinese name string.
-- If multiple variants, returns the array (caller joins with zh_strings.multi_name_sep).
function export.resolve_zh_name(zh_name, zh_name_by_holonym_type, holonym_placetype)
if zh_name_by_holonym_type and holonym_placetype then
local override = zh_name_by_holonym_type[holonym_placetype]
if override then
return override
end
end
return zh_name
end
-- ---------------------------------------------------------------------------
-- Helper: format zh_name for display (join variants with "/")
-- ---------------------------------------------------------------------------
function export.format_zh_name(zh_name_or_array)
if type(zh_name_or_array) == "table" then
return table.concat(zh_name_or_array, export.zh_strings.multi_name_sep)
end
return zh_name_or_array or ""
end
return export
54ivdeh26b2atltv2oqtx882pf63fq6
Module:Zh/data/glyph-data
828
1633971
9909188
8789283
2026-09-29T09:54:42Z
TongcyBot
83009
同步英語維基詞典
9909188
Scribunto
text/plain
local data = {}
data.eras = {
{ "[[w:商朝|商]]", { "bronze-shang", "oracle" } },
{ "[[w:西周|西周]]", { "bronze", "oracle-western" } },
{ "[[w:春秋时期|春秋時期]]", { "bronze-spring" } },
{ "[[w:战国时期|戰國時期]]", { "bronze-warring", "silk", "slip" } },
{ "《[[w:说文解字|說文解字]]》<br>(漢·[[w:許慎|許慎]])", { "zhou", "ancient", "odd", "seal", "vulgar" } },
{ "《汗簡》<br>(宋·[[w:郭忠恕|郭忠恕]])", { "hanjian" } },
{ "《古文四聲韻》<br>(宋·[[w:夏竦|夏竦]])", { "GWSSY" } },
{ "《集篆古文韻海》<br>(宋·杜從古)", { "JZGWYH" } },
{ "《六書通》<br>(明·[[w:閔齊伋|閔齊伋]])", { "bigseal" } },
{ "《[[w:en:Libian|隸辨]]》<br>(清·顧藹吉)", { "clerical" } },
{ "[[w:開成石經|開成石經]]", { "kai-Kaicheng" } },
{ "《[[w:康熙字典|康熙字典]]》<br>(於[[w:清朝|清朝]]編纂)", { "mingti-Kangxi" } },
}
data.scripts = {
["bronze-shang"] = "[[w:金文|金文]]",
["oracle"] = "[[w:甲骨文|甲骨文]]",
["oracle-western"] = "[[w:甲骨文|甲骨文]]",
["bronze"] = "[[w:金文|金文]]",
["bronze-spring"] = "[[w:金文|金文]]",
["bronze-warring"] = "[[w:金文|金文]]",
["silk"] = "[[w:简牍|楚系簡帛文字]]",
["slip"] = "[[w:简牍|秦系簡牘文字]]",
["zhou"] = "[[w:籀文|籀文(大篆)]]",
["ancient"] = "古文字",
["odd"] = "[[w:生僻字|生僻字]]",
["seal"] = "[[w:小篆|小篆]]",
["vulgar"] = "[[w:俗字|俗字]]",
["bigseal"] = "傳抄古文字",
["hanjian"] = "傳抄古文字",
["GWSSY"] = "傳抄古文字",
["JZGWYH"] = "傳抄古文字",
["clerical"] = "[[w:隶书|隸書]]",
["kai-Kaicheng"] = "[[w:楷书|楷書]]",
["mingti-Kangxi"] = "[[w:宋体|明體]]",
}
data.hide_scripts = { "oracle", "bronze", "seal", "bigseal" }
data.script_abbrev = {
["B"] = "bronze",
["b"] = "bronze",
["31"] = "bronze",
["32"] = "bronze",
["33"] = "bronze",
["34"] = "bronze",
["j"] = "oracle",
["41"] = "oracle",
["42"] = "oracle",
["43"] = "oracle",
["51"] = "silk",
["52"] = "silk",
["53"] = "silk",
["71"] = "slip",
["Q"] = "slip",
["S"] = "silk",
["s"] = "seal",
["27"] = "seal",
["L"] = "bigseal",
}
return data
on630raphp9ykldxxaaqc89kqrb1y7p
Module:Pt-pronunc
828
1634160
9909100
9023250
2026-09-29T07:27:59Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909100
Scribunto
text/plain
--[=[
This module implements the template {{pt-IPA}}.
Author: Benwing
-- FIXME:
1. Implement i^ not before vowel = epenthetic i or deleted epenthetic i in Brazil (in that order), and i^^ not before
vowel = opposite order. Epenthetic i should not affect stress but should otherwise be treated like a normal vowel.
Deleted epenthetic i should trigger palatalization of t/d but have no other effects. [DONE]
2. Implement i^ before vowel = i.V or yV (in that order), and i^^ before vowel = opposite order. [DONE]
3. Implement i* = mandatory epenthetic i in Brazil. [DONE]
4. Implement u^ = u. or w (in that order), and u^^ = opposite order. [DONE]
5. Implement e^ = e or i in Brazil (in that order), and e^^ = opposite order. [DONE]
6. Implement o^ = o or u in Brazil (in that order), and o^^ = opposite order. [DONE]
7. Implement ê* = ê in Brazil, é in Portugal (useful especially before nasal consonants). [DONE]
8. Implement ô* = ô in Brazil, ó in Portugal (useful especially before nasal consonants). [DONE]
9. Implement é* = é in Brazil, ê in Portugal (useful especially in -éi-). [DONE]
10. Implement ó* = ó in Brazil, ô in Portugal (useful especially in -ói-). [DONE]
11. Implement des^ at beginning of word = 'dis++' or 'des++' in Brazil (in that order), and des^^ = opposite order. [DONE]
12. In Portugal, before [ɫ], unstressed 'a' should be /a/; unstressed 'e' should be /ɛ/; and unstressed 'o' should be
either /o/ or /ɔ/ (in that order). [DONE]
13. Support qualifiers using <q:...>. [DONE]
14. Support references using <ref:...>. Syntax is the same as for IPA ref=. [DONE]
15. In Portugal, unstressed o in hiatus should be /w/, and unstressed e in hiatus should be /j/. [DONE]
16. Support - (hyphen) = left and right parts should be treated as distinct phonological words but written joined
together, and non-final primary stresses turn into secondary stresses. Word-initial and word-final behavior should
happen, e.g. Brazil epenthesis of (j) before word-final /s/ followed a stressed vowel, Brazil raising of esC- and
Portugal rendering of o- as ò-. [DONE]
17. Support : (colon), similar to hyphen but in non-final parts, final vowels aren't rendered as closed. [DONE]
18. Support + (plus sign), similar to colon but non-final primary stresses aren't displayed and syllable-division
ignores the plus sign. [DONE]
19. In Brazil, word-initial enC-, emC- should display as (逐個音節發音) ẽ-, (自然發音) ĩ-. [DONE]
20. In Portugal, -sç- and -sc(e/i)- should show as (逐個音節發音) /ʃs/, (自然發音) /ʃ/. Same for
-sz- ([[as]] [[zonas]]). [DONE] [FIXME: Verify this reduction in [[as]] [[zonas]].]
21. In Portugal, grave accent indicates unstressed open a/e/o and macron indicates unstressed closed a/e/o; both are
ignored in Brazil. [DONE]
22. In Portugal, iCi where the first i is before the stress should (maybe) show as iCi, (traditional pronunciation) ɨCi.
In iCiCi, both of the first two i's show as ɨ in the traditional pronunciation (FIXME: verify this). C should be
only a single consonant, hence not in [[piscina]] or [[distrito]] (FIXME: verify this). Does not apply if the first
i is stressed (e.g. [[mínimo]], [[tília]], [[pírico]], [[tísica]]) or if the stressed i is word-final ([[Mimi]],
[[Lili]], [[chichizinho]], [[piripiri]]), or in certain other words ([[felicíssimo]], [[filhinho]], [[estilista]],
[[pirite]]). Possibly this means it doesn't apply when the stressed i is in a suffix (-íssimo, -inho, -ista). We
can always disable the eCi spelling by adding an h in 'ihCi' to make it look like a cluster between the i's. NOTE:
It appears that iCi -> eCi should apply in [[dicionário]], meaning if we apply it at the end, we have to distinguish
between glides from original i and glides from e or y.
23. In Portugal and Brazil, stressed o in hiatus should automatically be ô (voo, Samoa, Alagoas, perdoe, abençoe). [DONE]
24. In Northern Portugal, stressed closed ô in hiatus (whether written explicitly as e.g. vôo, Côa or generated automatically)
should show as e.g. /ˈbo.(w)ɐ/. [DONE] [FIXME: Verify syllable division.]
25. Recognize -zinha like -zinho, -mente. Just use hyphen (-) to handle these. We don't recognize -zão, -zona, -zito,
-zita because of too many false positives; you can just write the hyphen explicitly before the suffix as needed.
Cf. among our current vocabulary we have 10 -zão augmentatives (animalzão, aviãozão, cipozão, cuzão, homenzão,
leãozão, paizão, pãozão, pezão, tatuzão), 2 -ão augmentatives after a word ending in -z (codornizão, felizão), and
7 non-augmentatives (alazão, coalizão, razão, rezão, sazão, sezão, vazão). Similarly for -zona: we have 5 -zona
augmentatives (boazona, cuzona, maçãzona, mãezona, mãozona) against 8 non-augmentatives (amazona, aminofenazona,
arilidrazona, Arizona, cronozona, ecozona, Eurozona, fenazona) and no -ona augmentatives after words ending in -z.
For -zito, we have 1 -ito diminutive after a word ending in -z (Queluzito), one non-diminutive (quartzito), and no
-zito diminutives. For -zita we have 1 -zita diminutive (maçãzita) and 4 non-diminutives (andaluzita, monazita,
pedzita, stolzita). [DONE]
26. Final 'r' isn't optional before -zinho, -zinha, -mente. [DONE]
27. Consider making secondary stress optional in cases like traduçãozinha where the stress is directly before the
primary stress.
28. In Brazil, unstressed final-syllable /a/ should be reduced before -r, cf. [[açúcar]]. [DONE]
29. Support + = pagename, and pagename= argument. [DONE]
30. Deduplicate final pronunciations without distinct qualifiers. [DONE]
31. Implement support for dot-under without accompanying quality diacritic. When attached to a/e/o, it defaults to acute
= open pronun, except in the following circumstances, where it defaults to circumflex: (1) in the diphthongs
ei/eu/oi/ou; (2) in a nasal vowel. [DONE]
32. Portugal final -e should show as optional (ɨ) unless there is a vowel-initial word following, in which case it
should not be displayed at all. [NOT DONE]
33. Syllabification: "Improper" clusters of non-sibiliant-obstruent + obstruent (pt, bt, bd, dk, kt; ps, bs, bv, bʒ, tz,
dv, ks; ft), non-sibiliant-obstruent + nasal (pn, bn, tm, tn, dm, dn, gm, gn), nasal + nasal (mn) are syllabified in
Portugal as .pt, .bv, .mn, etc. Note ʃ.t, ʃ.p, ʃ.k, etc. But in Brazil, all of these divide between the consonants
(p.t, b.v, ʃ.t, s.p, etc.). Particular case: [[ab-rogação]] divides as a.brr in Portugal but ab.rr in Brazil. [DONE]
34. -ão, -ãe, -õe should be recognized as nasal diphthongs with a circumflex added to force stress. [DONE]
35. Recognize obsolete -aõ, -aẽ, -oẽ as equivalent to -ão, -ãe, -õe.
36. In CluV, CruV, CliV, CriV, the 'u' and 'i' are vowels not glides in both Portugal and Brazil. [DONE]
37. Epenthesis of (j) before final stressed s in Brazil should not happen after i. [DONE]
38. Dialect markers such as "Brazil", "Portugal" should go at the beginning. [DONE]
39. Portugal exC, êxC should be rendered like eiʃC (FIXME: Does this apply to "Central Portugal" as well?). exs- needs
handling like eiʃs-/(i)ʃs- not like eiss-. [DONE]
40. Unstressed word-initial exC- should maybe have two pronunciations, one with eiʃC- and the other with (i)ʃC-.
(FIXME: Verify.)
41. -sj- (e.g. [[transgénico]]) should reduce to a single /ʒ/. [DONE]
42. [[transgredir]] should have /z/ (Brazil), /ʒ/ (Portugal) instead of /s/, /ʃ/. [DONE]
43. Unstressed -ie- in hiatus should automatically be -iè- in Portugal or maybe -iè-/-ié-? [DONE] (FIXME: Verify.)
44. Initial esC- in Brazil should be either isC- or esC-. [DONE]
45. Initial sC- in Portugal and maybe Brazil should be /s/ not /ʃ/. [DONE]
46. Deleted epenthetic /i/ should block conversion of syllable-final m/n into nasalization, cf. [[amnésia]] respelled
'ami^nési^a'. [DONE]
47. Portugal 'o', 'os' should be unstressed with /u/, not have /ɔ/. [DONE]
48. /s/ after nasal vowel before glide should not become voiced. [DONE]
49. [[arrozinho]] (which uses the + component divider) should have the IPA stress mark before the 'z' not after. [DONE]
50. Portugal final -ɨ should be suppressed before a vowel (with a tie sign), and made optional word-finally. [DONE]
51. Final -dor/-tor/-sor/-ssor + feminine and plural should have closed /o/. [DONE]
52. Final -oso should have closed /o/, but feminine and plural should have open /ɔ/. [DONE]
53. Hiatuses in Brazil involving 'i' should have two possibilities (full vowel or glide); likewise for 'u'. [DONE]
54. In Brazil phonetic representation, hiatuses involving 'i' should be [ɪ], and those involving 'u' should be [ʊ]. [DONE]
55. ui^ should convert to 'ui' in Portugal = /wi/, but to 'u.i' or 'uy' in Brazil. [DONE]
56. des^ should be 'des++' or 'dis++' (++ in both cases). [DONE]
57. Word-boundary special handling (e.g. des^, x-, -x, etc.) should also respect component boundaries e.g. in
[[aerodeslizador]], [[criptoxantina]]. [DONE]
58. Convert apostrophe to tie, and make tie transparent to syllabification. [DONE]
59. 'x' in -nx- should default to /ʃ/ ([[enxame]], [[enxugar]]). [DONE]
60. Final i^ should not be stressed. [DONE]
61. There should not be a comma between phonemic and phonetic representations. [DONE]
62. Final stressed -io in Brazil should be either i.u or iw. [DONE]
63. Unstressed final '-ax' has open /a/ including in Portugal. [DONE]
64. Clean up handling of qualifiers and fix bugs. [DONE]
65. Support i#, u# for i./y or u./w in both Brazil and Portugal. [DONE]
66. In Portugal, final unstressed -ar/-or should be pronounced open, but in Brazil, closed. [DONE]
67. Suppress initial e- in esC- in Portugal after /i/. [DONE]
68. In C[lr][iu], [iu] should be either full vowel or glide in Portugal. [DONE]
69. Support substitution notation. [DONE]
70. [[em]] by itself should be /ẽj̃/ or /ĩ/. [DONE]
71. Suffixes beginning with a vowel should act as if a pseudo-consonant precedes. [DONE]
72. Prefixes should change primary stress to secondary. [DONE]
73. -ing should be like -im in Brazil but -ingh in Portugal. [DONE]
74. Single 's' after colon should be /z/ not /s/, in keeping with normal spelling practices;
likewise for single 'r'. [NOT YET, FIRST NEED TO PUSH APPROPRIATE RESPELLING CHANGES]
75. Intertonic 'o' after stressed i/e/ɛ ([[período]], [[rubéola]]) in Brazil should be .u or w.
76. Don't display phonetic IPA if identical to phonemic IPA. [DONE]
77. Add "South Brazil" pronunciation. [DONE; SEE ISSUES BELOW]
78. -êm should be like -ém in Brazil but -ẽiem in Portugal. [DONE]
79. -õem should be like -õe in Brazil but -õeem in Portugal. [DONE]
80. -le in Portugal should split into two pronuns, one with vowel for -e, one without. [DONE]
81. Initial 'e' in Portugal after des^ should be /i/. [DONE]
]=]
--[=[
Issues concerning South Brazil pronunciation:
(Rather than an unified South Brazil pronunciation, it's better to transform it in Rio Grande do Sul/Gaúcho pronunciation)
1. Should all 'ẽ' (not just word-final) be rendered as [ẽj̃]? We have several existing examples, e.g.
/de.zẽ.ba.ˈla.do/|/de.zẽj̃.ba.ˈla.do/ for [[desembalado]], /ˌde.zẽj̃.has.ˈkɐ̃.so/ for [[desenrascanço]],
/ẽj̃.baw.sa.ˈma(ɻ)/ for [[embalsamar]], [ẽj̃.pũˈj̃aɾ] for [[empunhar]], /ẽ.ʁus.tiɾ/|[ẽj̃.ʁuʃˈ(t)͡ʃiɾ] for
[[enrustir]].
2. Coda 'r': Mostly /ɻ/ but sometimes given as both /ɾ/ and /ɻ/ or occasionally /r/.
3. Raising of unstressed 'e' and 'o', and 'des-': Mostly not present but sometimes yes, e.g. [[clarear]]
/kla.ɾe.ˈa(ɾ)/|/kla.ɾe.ˈa(ɻ)/|/kla.ɾi.ˈa(ɾ)/|/kla.ɾi.ˈa(ɻ)/|/kla.ˈɾja(ɾ)/|/kla.ˈɾja(ɻ)/, [[consentâneo]]
[kõ(w̃).senˈtə̃.ni̯u], [[cozido]] /ko.ˈzi.dʊ/|/ku.ˈzi.dʊ/, [[desbloquear]] /dez.blo.ke.ˈa(ɾ)/|/dez.blo.ˈkja(ɾ)/|/d͡ʒiz-/|/-(ɻ)/,
[[desferir]] /des.fe.ˈɾi(ɾ)/|/des.fe.ˈɾi(ɻ)/|/des.fi.ˈɾi(ɾ)/|/des.fi.ˈɾi(ɻ)/, [[desrespeitar]] /dez.hes.pej.ˈta(ɾ)/|/d͡ʒiz-/|/-(ɻ)/,
[[destruído]] /des.tɾu.ˈi.do/|/d͡ʒis.tɾu.ˈi.do/.
4. Epenthetic 'i': Less common? E.g. [[dignidade]] given as just /ˌd͡ʒiɡ.ni.ˈda.de/, but [[digno]] as /ˈd͡ʒiɡ.no/|/ˈd͡ʒi.ɡi.no/.
5. Secondary stress: Often given two syllables before the stress. I have left it out unless it seems stable and in a prefix, e.g.
[[eleitorado]] /e.ˌlej.to.ˈɾa.do/, [[eletrizante]] /e.ˌle.tɾi.ˈzɐ̃.te/, [[elevado]] /ˌe.le.ˈva.do/, [[elevador]] /e.ˌle.va.ˈdoɻ/,
[[engarrafamento]] /ˌẽ.ɡa.ˌha.fa.ˈmẽ.to/.
6. Initial em-: Mostly given as /ẽ-/ or /ẽj̃-/, but sometimes /ĩ-/ as in [[emparedar]] /ĩ.pa.ɾe.ˈda(ɾ)/|/ĩ.pa.ɾe.ˈda(ɻ)/
or both as in [[encaixar]] /ẽ.kaj.ˈʃa(ɾ)/|/ĩ.kaj.ˈʃa(ɻ)/|/ẽ.kaj.ˈʃa(ɾ)/|/ĩ.kaj.ˈʃa(ɻ)/.
7. -nh-: [[empunhar]] given as [ẽj̃.pũˈj̃aɾ], but is /j̃/ actually characteristic of this accent or is it /ɲ/?
8. Other inconsistencies: e.g. [[enrustir]] /ẽ.ʁus.tiɾ/|[ẽj̃.ʁuʃˈ(t)͡ʃiɾ], with coda /ʃ/ and strong [ʁ] (usually [h]).
[[ab-rogar]] /ab.ʁoˈɡa(ɻ)/|/ab.hoˈɡa(ɻ)/|/ab.χoˈɡa(ɻ)/|/ab.ɦoˈɡa(ɻ)/ with all possible strong r's.
9. Nasal vowels: Almost always as elsewhere, but occasionally e.g. [[entender]] /ĩn.tẽnˈde(r)/, [[a gente]] /a.ˈʒen.te/|/a.ˈʒẽ.te/.
10. Nasal diphthongs: -ão sometimes claims to be /ɐ̃õ/, sometimes /ɐ̃w̃/. [[-ção]] listed both.
]=]
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local m_strutils = require("Module:string utilities")
local m_qual = require("Module:qualifier")
local lang = require("Module:languages").getByCode("pt")
local u = require("Module:string/char")
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀ = open vowel quality without stress in Portugal only
local MACRON = u(0x0304) -- macron = ̄ = closed vowel quality without stress in Portugal only
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local CEDILLA = u(0x0327) -- cedilla = ̧
local DOTOVER = u(0x0307) -- dot over = ̇
-- DOTUNDER indicates an explicitly unstressed syllable; useful when accompanied by a quality marker (acute or
-- circumflex), or by itself with a/e/o, where it defaults to acute (except in the following circumstances, where it
-- defaults to circumflex: (1) in the diphthongs ei/eu/oi/ou; (2) in a nasal vowel).
local DOTUNDER = u(0x0323) -- dot under = ̣
-- LINEUNDER indicates an explicit secondary stress; normally not necessary as primary stress is converted to secondary
-- stress if another primary stress follows, but can be used e.g. after a primary stress; can be accompanied by a
-- quality marker (acute or circumflex) with a/e/o; if not, defaults to acute (except in the same circumstances where
-- dot under defaults to circumflex).
local LINEUNDER = u(0x0331) -- line under = ̱
-- Serves to temorarily mark where a syllable division should not happen; temporarily substitutes for comma+space;
-- temporarily substitutes for #.
local TEMP1 = u(0xFFF0)
local SYLDIV = u(0xFFF1) -- used to represent a user-specific syllable divider (.) so we won't change it
local PSEUDOCONS = u(0xFFF2) -- pseudo-consonant at the edge of prefixes ending in a vowel and suffixes beginning with a vowel
local PREFIX_MARKER = u(0xFFF3) -- marker indicating a prefix so we can convert primary to secondary accents
-- Since we convert all symbols at the beginning and decompose accented characters (except for ç and ü), we can later
-- use capital and/or accented letters to represent additional distinctions, typically in cases where we want to
-- remember the source of a given phoneme. By convention we use capital letters, optionally with accents.
-- Specifically:
-- * A/E/O represent written a/e/o where we don't yet know the vowel quality. Towards the beginning, we convert all
-- written a/e/o to A/E/O and later convert them to their final qualities (which might include /a/ /e/ /o/, so we
-- can't use those symbols directly for this purpose).
-- * Ẽ stands for a word-initial Brazilian sound that can be pronounced either /ẽ/ (in careful speech) or /ĩ/ (in
-- natural speech) and originates from en- or em- before a consonant. We distinguish this from written in-/im-,
-- which can be only /ĩ/, and written ehn-/ehm- (or similar), which can be only /ẽ/.
-- * I is used to represent epenthetic i in Brazilian variants (which should not affect stress assignment but is
-- otherwise treated as a normal sound), and Ɨ represents deleted epenthetic i (which still palatalizes /t/ and /d/).
-- I is also used to represent Brazil e or i from initial esC-, and Portugal (i) from initial esC-.
-- * Ì is used to represent either i. in hiatus or /j/ in Brazil; likewise for Ù representing u. in hiatus or /w/.
-- * Ɔ (capital version of ɔ) stands for a Portugal sound that can be pronounced either /o/ or /ɔ/ (depending on the
-- speaker), before syllable-final /l/.
-- * Ú is used word-finally after i to represent either .u in hiatus or /w/ in Brazil.
-- * L is used word-finally in Portugal to split words ending in -le into two pronuns due to the differing pronunciation
-- of /l/ in the two cases (coda or not).
local vowel = "aɐeɛiɨoɔuüAEẼIƗÌOƆÙÚ"
local V = "[" .. vowel .. "]"
local NV_NOT_SPACING_CFLEX = "[^" .. vowel .. "%^]"
local high_front_vocalic = "iIƗÌy"
local front_vocalic = "eɛɨẼ" .. high_front_vocalic
local FRONTV = "[" .. front_vocalic .. "]"
local glide = "yw"
local W = "[" .. glide .. "]" -- glide
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local primary_quality = AC .. CFLEX
local primary_quality_c = "[" .. primary_quality .. "]"
local quality = AC .. CFLEX .. GR .. MACRON
local quality_c = "[" .. quality .. "]"
local stress = LINEUNDER .. DOTOVER .. DOTUNDER .. ipa_stress
local stress_c = "[" .. stress .. "]"
local non_primary_stress = LINEUNDER .. DOTUNDER .. "ˌ"
local non_primary_stress_c = "[" .. non_primary_stress .. "]"
local accent = quality .. stress .. TILDE
local accent_c = "[" .. accent .. "]"
-- Any component separator that should be "transparent" (i.e. ignored) during syllabification processes. This should
-- include a subset of the component_sep characters, currently + and * (which ++ is converted into).
local syl_transp_component_sep = "+*"
local syl_transp_component_sep_c = "[" .. syl_transp_component_sep .. "]"
-- Any character that should be "transparent" (i.e. ignored) during syllabification processes. This includes the
-- syllable-transparent component separators + and ++ (converted into *) as well as the tie character, which originates
-- from an apostrophe (e.g. [[barriga d'água]]).
local syl_transp = syl_transp_component_sep .. "‿"
local syl_transp_c = "[" .. syl_transp .. "]"
-- Zero or more syllable-transparent characters; used during syllabification.
local STC = syl_transp_c .. "*"
-- Component separators that are not transparent to syllabification. Includes colon (:), hyphen (-) and double hyphen
-- (--), which is converted internally to @.
local non_syl_transp_component_sep = ":@%-"
local non_syl_transp_component_sep_c = "[" .. non_syl_transp_component_sep .. "]"
-- "component_sep" means any symbol that may separate word components (not including #, which is added at a certain
-- point next to certain word components so that the adjacent characters are treated as if they are at word bounaries).
local component_sep = syl_transp_component_sep .. non_syl_transp_component_sep
local component_sep_c = "[" .. component_sep .. "]"
local word_or_component_sep_c = "[#" .. component_sep .. "]"
-- Syllable divider (auto-inserted or user-specified).
local syldiv = "." .. SYLDIV
local syldiv_c = "[" .. syldiv .. "]"
-- "charsep" means any symbol that may separate the individual characters that make up a word, and which should be
-- ignored for e.g. consonant-consonant assimilation processes. This currently includes accents and syllable dividers.
local charsep = accent .. syldiv
local charsep_c = "[" .. charsep .. "]"
-- Characters that may divide words, other than the tie (‿), which is transparent to syllabification.
local non_syl_transp_word_divider = " #"
-- All characters that may divide words.
local word_divider = non_syl_transp_word_divider .. "‿"
-- "wordsep_not_syl_transp" means the same as "wordsep" below but excludes syllable-transparent characters. It is used
-- in other collections of symbols (particularly when negated, so as to include syllable-transparent characters but
-- otherwise exclude word separators) rather than by itself.
local wordsep_not_syl_transp = charsep .. non_syl_transp_word_divider .. non_syl_transp_component_sep
-- "wordsep" means any symbol that may separate the individual characters that make up a word or may separate words or
-- components, and which should be ignored for e.g. consonant-consonant assimilation processes that operate across
-- words. This currently includes everything in "charsep" and "component_sep" plus symbols that may divide words.
local wordsep = wordsep_not_syl_transp .. syl_transp
local wordsep_c = "[" .. wordsep .. "]"
local C = "[^" .. vowel .. wordsep .. "_]" -- consonant
-- consonant or syllable-transparent component separator
local C_OR_SYL_TRANSP = "[^" .. vowel .. wordsep_not_syl_transp .. "_]"
local H_OR_SYL_TRANSP = "[h" .. syl_transp .. "]"
local H_GLIDE_OR_SYL_TRANSP = "[h" .. glide .. syl_transp .. "]"
local C_NOT_H_OR_GLIDE = "[^h" .. glide .. vowel .. wordsep .. "_]" -- consonant other than h, w or y
local C_OR_WORD_BOUNDARY = "[^" .. vowel .. charsep .. "_]" -- consonant or word boundary
local voiced_cons = "bdglʎmnɲŋrɾʁvzjʒʤ" -- voiced sound
-- Unstressed words with vowel reduction in Brazil and Portugal.
local unstressed_words = require("Module:table").listToSet({
"o", "os", -- definite articles
"me", "te", "se", "lhe", "lhes", "nos", "vos", -- unstressed object pronouns
-- See https://en.wikipedia.org/wiki/Personal_pronouns_in_Portuguese#Contractions_between_clitic_pronouns
"mo", "mos", "to", "tos", "lho", "lhos", -- object pronouns combined with articles
-- Allomorphs of articles after certain consonants
"lo", "los", "no", -- [[nos]] above as object pronoun
-- Allomorphs of object pronouns before other pronouns
"vo", -- [[no]] above as allomorph of article
"que", -- subordinating conjunctions
"e", -- coordinating conjunctions
"de", "do", "dos", "por", -- basic prepositions + combinations with articles; [[no]], [[nos]] above already
-- FIXME: Portugal pronun for pelos, pela, pelas given as if spelled pêlos, etc. with stress, but differently for pelo.
-- I am assuming the Portugal pronuns with stress are wrong.
"pelo", "pelos", "pela", "pelas" -- preposition + article combinations
})
-- Unstressed words with vowel reduction in Portugal only.
local unstressed_full_vowel_words_brazil = require("Module:table").listToSet({
"a", "as", -- definite articles
-- See https://en.wikipedia.org/wiki/Personal_pronouns_in_Portuguese#Contractions_between_clitic_pronouns
"ma", "mas", "ta", "tas", "lha", "lhas", -- object pronouns combined with articles
-- Allomorphs of articles after certain consonants
"la", "las", "na", "nas",
"da", "das", -- basic prepositions + combinations with articles; [[na]], [[nas]] above already
-- coordinating conjunctions; [[mas]] above already
"para", "pra", -- preposition; for the verb form use "pára" instead
})
-- Unstressed words without vowel reduction.
local unstressed_full_vowel_words = require("Module:table").listToSet({
"um", "uns", -- single-syllable indefinite articles
"meu", "teu", "seu", "meus", "teus", "seus", -- single-syllable possessives
"ou", -- coordinating conjunctions
-- Note that in order to match à and às we have to write them as below because at the point we are trying to
-- match them, all text has been converted to canonical decomposed Unicode form. Writing "à" and "às" directly
-- won't work even if you type in the text using decomposed Unicode characters because all page contents are
-- automatically converted to canonical composed form when saved.
"ao", "aos", "a" .. GR, "a" .. GR .. "s", -- basic prepositions + combinations with articles
"em", "com", -- other prepositions
})
-- Special-case pronunciations for certain unstressed words with irregular pronunciations. The left side is the
-- original spelling after DOTUNDER or DOTOVER has been added; which diacritic gets added depends on whether the word
-- has vowel reduction (DOTOVER) or no vowel reduction (DOTUNDER). The right side is the respelling. See comment just
-- above for why we write "a" .. GR instead of "à".
local unstressed_pronunciation_substitution = {
["a" .. DOTUNDER .. "o"] = "a" .. DOTUNDER .. "u",
["a" .. DOTUNDER .. "os"] = "a" .. DOTUNDER .. "us",
["a" .. GR .. DOTUNDER] = "a" .. DOTUNDER,
["a" .. GR .. DOTUNDER .. "s"] = "a" .. DOTUNDER .. "s",
["po" .. DOTOVER .. "r"] = "pu" .. DOTOVER .. "r",
}
-- Dialects and subdialects:
export.all_styles = {"gbr", "rio", "sp", "sbr", "gpt", "cpt", "spt", "npt"}
export.all_style_groups = {
all = export.all_styles,
br = {"gbr", "rio", "sp", "sbr"},
pt = {"gpt", "cpt", "spt", "npt"},
}
local style_to_style_group = {}
for group, styles in pairs(export.all_style_groups) do
if group ~= "all" then
for _, style in ipairs(styles) do
style_to_style_group[style] = group
end
end
end
export.all_style_descs = {
-- style groups
br = "[[w:巴西葡萄牙语|巴西]]",
pt = "[[w:歐洲葡萄牙語|葡萄牙]]",
-- styles
gbr = "[[w:巴西葡萄牙语|巴西]]", -- "general" Brazil
rio = "[[:en:w:Carioca#Sociolect|里約熱內盧]]", -- Carioca accent
sp = "[[:en:w:Paulistano_dialect|聖保羅]]", -- Paulistano accent
sbr = "巴西南部",
gpt = "[[w:歐洲葡萄牙語|葡萄牙]]", -- "general" Portugal
-- lisbon = "Lisbon", -- (not added yet)
cpt = "葡萄牙中部", -- Central Portugal outside of Lisbon
spt = "葡萄牙南部", -- Dialects of Alentejo and Algarve (West Algarve is a distinct subdialect)
npt = "葡萄牙北部" -- Northern Portugal dialects (Porto/Minho and Transmontano are subdialects)
}
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- Flat-map a function `fun` over `items`. This is like `map` over a sequence followed by `flatten`, i.e. the function
-- must itself return a sequence and all of the returned sequences are flattened into a single sequence.
local function flatmap(items, fun)
local new = {}
for _, item in ipairs(items) do
local results = fun(item)
for _, result in ipairs(results) do
table.insert(new, result)
end
end
return new
end
-- Combine two sets of qualifiers, either of which may be nil or a list of qualifiers. Remove duplicate qualifiers.
-- Return value is nil or a list of qualifiers.
local function combine_qualifiers(qual1, qual2)
if not qual1 then
return qual2
end
if not qual2 then
return qual1
end
local qualifiers = m_table.deepCopy(qual1)
for _, qual in ipairs(qual2) do
m_table.insertIfNot(qualifiers, qual)
end
return qualifiers
end
-- Reorder the diacritics (accent marks) in `text` according to a canonical order. Specifically, there can conceivably
-- be up to three accents on a vowel: a quality mark (acute/circumflex/grave/macron); a mark indicating secondary stress
-- (lineunder), tertiary stress (dotunder; i.e. no stress but no vowel reduction) or forced vowel reduction (dotover);
-- and a nasalization mark (tilde). Order them as follows: quality - stress - nasalization. `err` is a function of one
-- argument (an error string) and should throw an error if called.
local function reorder_accents(text, err)
local function reorder_accent_string(accentstr)
local accents = rsplit(accentstr, "")
local accent_order = {
[AC] = 1,
[CFLEX] = 1,
[GR] = 1,
[MACRON] = 1,
[LINEUNDER] = 2,
[DOTUNDER] = 2,
[DOTOVER] = 2,
[TILDE] = 3,
}
table.sort(accents, function(ac1, ac2)
return accent_order[ac1] < accent_order[ac2]
end)
return table.concat(accents)
end
text = rsub(text, "(" .. accent_c .. "+)", reorder_accent_string)
-- Remove duplicate accents.
text = rsub_repeatedly(text, "(" .. accent_c .. ")%1", "%1")
-- Make sure we don't have more than one of a given class.
if rfind(text, quality_c .. quality_c) then
err("Two different quality diacritics cannot occur together")
end
if rfind(text, stress_c .. stress_c) then
err("Two different stress diacritics cannot occur together")
end
-- Only a/e/o can receive a circumflex, grave or macron.
if rfind(text, "[^aeo][" .. CFLEX .. GR .. MACRON .. "]") then
err("Only a/e/o can be followed by circumflex, grave or macron")
end
return text
end
-- Generate partial IPA for a single preprocessed term respelling `text` in the specified `style` ('gbr', 'rio', etc.;
-- see all_style_descs above). If `phonetic` is given, generate phonetic output, otherwise phonemic output. `err` is a
-- function of one argument (an error string) and should throw an error if called. This function is a subfunction of
-- `IPA` and cannot really be used by itself, because it generates output containing special symbols that need to be
-- postprocessed into multiple outputs (and in addition some other final postprocessing needs to happen, e.g. to get
-- stress marks in the right place). The function `IPA` is available be called externally.
local function one_term_ipa(text, style, phonetic, err)
local brazil = m_table.contains(export.all_style_groups.br, style)
local portugal = m_table.contains(export.all_style_groups.pt, style)
-- Initial x -> /ʃ/: [[xérox]], [[xilofone]], [[xadrez]], etc.
text = rsub(text, "(" .. word_or_component_sep_c .. ")x", "%1ʃ")
-- Final x -> /ks/ ([[clímax]], [[xérox]], [[córtex]], [[hélix]], [[durex]], [[lux]], etc.), but for now we map to
-- X because later on we open unstressed vowels before final x.
text = rsub(text, "x(" .. word_or_component_sep_c .. ")", "X%1")
-- x after certain dipthongs (ai, ei, oi, ou) and after -en- should be /ʃ/. Other diphthongs before x are rare
-- and mostly learned and we need to force explicit respelling.
text = rsub(text, "(([aeo])" .. charsep_c .. "*([iun])" .. charsep_c .. "*)x",
function(all, a, b)
local ab = a .. b
-- [[baixo]], [[peixe]], [[troixa]], [[frouxo]], [[enxame]], etc.
if ab == "ai" or ab == "ei" or ab == "oi" or ab == "ou" or ab == "en" then
return all .. "ʃ"
else
return all .. "x"
end
end)
-- -exC- should be pronounced like -esC- in Brazil but -eisC- in Portugal. Cf. excelente, experiência, têxtil,
-- êxtase. Not with other vowels (cf. [[Felixlândia]], [[Laxmi]], [[Oxford]]).
-- FIXME: Maybe this applies only to Lisbon and environs?
text = rsub(text, "(e" .. accent_c .. "*)x(" .. C .. ")", function(v, c)
if brazil then
return v .. "s" .. c
elseif c == "s" then
return v .. "isç"
else
return v .. "is" .. c
end
end)
if rfind(text, "x") then
err("x must be respelled z, sh, cs, ss or similar")
end
-- combinations with h; needs to precede handling of c and s, and needs to precede syllabification so that
-- the consonant isn't divided from the following h.
if style == "npt" then
-- In Northern Portugal the affricate tch is kept
text = rsub(text, "ch", "tʃ")
end
-- Else
text = rsub(text, "([scln])h", {["s"]="ʃ", ["c"]="ʃ", ["n"]="ɲ", ["l"]="ʎ" })
-- remove initial <h>
text = rsub(text, "(" .. word_or_component_sep_c .. ")h([^" .. accent .. "])", "%1%2")
-- Betacism
if style == "npt" then
-- In Northern Portugal, <v> is realized as <b>
text = rsub(text, "v", "b")
end
-- c, g, q
-- This should precede syllabification especially so that the latter isn't confused by gu, qu, gü, qü
-- also, c -> ç before front vowel ensures that cc e.g. in [[cóccix]], [[occitano]] isn't reduced to single c.
text = rsub(text, "c(" .. FRONTV .. ")", "ç%1")
text = rsub(text, "g(" .. FRONTV .. ")", "j%1")
text = rsub(text, "gu(" .. FRONTV .. ")", "g%1")
-- [[camping]], [[doping]], [[jogging]], [[Bangkok]], [[angstrom]], [[tungstênio]]
text = rsub(text, "ng([^" .. vowel .. glide .. "hlr])", brazil and "n%1" or "ngh%1")
text = rsub(text, "qu(" .. FRONTV .. ")", "k%1")
text = rsub(text, "ü", "u") -- [[agüentar]], [[freqüentemente]], [[Bündchen]], [[hübnerita]], etc.
text = rsub(text, "([gq])u(" .. V .. ")", "%1w%2") -- [[quando]], [[guarda]], etc.
text = rsub(text, "[cq]", "k") -- [[Qatar]], [[burqa]], [[Iraq]], etc.
-- y -> i between non-vowels, cf. [[Itamaraty]] /i.ta.ma.ɾa.ˈt(ʃ)i/, [[Sydney]] respelled 'Sýdjney' or similar
-- /ˈsid͡ʒ.nej/ (Brazilian). Most words with y need respelling in any case, but this may help.
text = rsub(text, "(" .. C_OR_WORD_BOUNDARY .. ")y(" .. accent_c .. "*" .. C_OR_WORD_BOUNDARY .. ")", "%1i%2")
-- Reduce double letters to single, except for rr, mm, nn and ss, which map to special single sounds. Do this
-- before syllabification so double letters don't get divided across syllables. The case of cci, cce is handled
-- above. nn always maps to /n/ and mm to /m/ and can be used to force a coda /n/ or /m/. As a result,
-- [[connosco]] will need respelling 'comnôsco', 'cõnôsco' or 'con.nôsco', and [[comummente]] will similarly
-- need respelling e.g. as 'comum.mente' or 'comũmente'. Examples of words with double letters (Brazilian
-- pronunciation):
-- * [[Accra]] no respelling needed /ˈa.kɾɐ/;
-- * [[Aleppo]] respelled 'Aléppo' /aˈlɛ.pu/;
-- * [[buffer]] respelled 'bâfferh' /ˈbɐ.feʁ/;
-- * [[cheddar]] respelled 'chéddarh' /ˈʃɛ.daʁ/;
-- * [[Hanna]] respelled 'Ranna' /ˈʁɐ̃.nɐ/;
-- * [[jazz]] respelled 'djézz' /ˈd͡ʒɛs/;
-- * [[Minnesota]] respelled 'Minnessôta' /mi.neˈso.tɐ/;
-- * [[nutella]] respelled 'nutélla' /nuˈtɛ.lɐ/;
-- * [[shopping]] respeled 'shópping' /ˈʃɔ.pĩ/ or 'shóppem' /ˈʃɔ.pẽj̃/;
-- * [[Stonehenge]] respelled 'Sto̱wnn.rrendj' /ˌstownˈʁẽd͡ʒ/;
-- * [[Yunnan]] no respelling needed /juˈnɐ̃/.
--
-- Note that further processing of r and s happens after syllabification and stress assignment, because we need
-- e.g. to know the distinction between final -s and -z to assign the stress properly.
text = rsub(text, "rr", "ʁ")
text = rsub(text, "nn", "N")
text = rsub(text, "mm", "M")
-- Deleted epenthetic /i/ should prevent preceding /m/, /n/ from being converted into nasalization.
text = rsub(text, "mƗ", "MƗ")
text = rsub(text, "nƗ", "NƗ")
-- Will map later to /s/; need to special case to support spellings like 'nóss' (= nós, plural of nó).
text = rsub(text, "ss", "S")
text = rsub(text, "(" .. C .. ")%1", "%1")
-- muit- is special and contains nasalization. Do before palatalization of t/d so [[muitíssimo]] works.
text = rsub(text, "(" .. word_or_component_sep_c .. "mu" .. stress_c .. "*)(it)", "%1" .. TILDE .. "%2")
-- Palatalize t/d + Ɨ -> affricates in Brazil. Use special unitary symbols, which we later convert to regular affricate
-- symbols, so we can distinguish palatalized d from written dj. We only do Ɨ now so we can delete it; we do another
-- palatalization round towards the end after raising e -> i.
local palatalize_td = {["t"] = "ʧ", ["d"] = "ʤ"}
if brazil then
text = rsub(text, "([td])(" .. word_or_component_sep_c .. "*Ɨ)",
function(td, high_vocalic) return palatalize_td[td] .. high_vocalic end)
-- Now delete the symbol for deleted epenthetic /i/; it still triggers palatalization of t and d.
text = rsub(text, "Ɨ", "")
end
-- Divide words into syllables.
-- First, change user-specified . into a special character so we won't move it around. We need to keep this
-- going forward until after we place the stress, so we can correctly handle initial i- + vowel, as in [[ia]],
-- [[iate]] and [[Iaundé]]. We need to divide [[ia]] as 'i.a' but [[iate]] as 'ia.te' and [[Iaundé]] as 'Ia.un.dé'.
-- In the former case, the stress goes on i but in the latter cases not; so we always divide <ia> as 'i.a',
-- and then after stress assignment remove the syllable divider if the <i> isn't stressed. The tricky thing is
-- that we want to allow the user to override this by explicitly adding a . between the <i> and <a>. So we need
-- to keep the distinction between user-specified . and auto-determined . until after stress assignment.
text = rsub(text, "%.", SYLDIV)
-- We have various characters indicating divisions between word components where we want to treat the components
-- more or less like separate words (e.g. -mente, -zinho/-zinha). Some such "characters" are digraphs, which we
-- convert internally to single characters to simplify the code. Here, -- separates off -mente/-zinho/-zinha and
-- ++ separates off prefixes. We want to ignore at least + and ++ (converted to *) for syllabification purposes.
text = rsub(text, "%-%-", "@")
text = rsub(text, "%+%+", "*")
-- Respell [[homenzinho]] as 'homemzinho' so it is stressed correctly.
text = rsub(text, "n(" .. SYLDIV .. "?ziɲos?" .. word_or_component_sep_c .. ")", "m%1")
-- Divide before the last consonant (possibly followed by a glide). We then move the syllable division marker
-- leftwards over clusters that can form onsets. Note that syllable-transparent component separators will always
-- be (and will continue to be) to the left of syllable dividers rather than to the right, so we don't need to
-- check for the latter situation.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C_OR_SYL_TRANSP .. "-)(" .. C .. H_GLIDE_OR_SYL_TRANSP .. "*" .. V .. ")", "%1.%2")
text = rsub(text, "([pbtdkgfv]" .. H_OR_SYL_TRANSP .. "*)%.([lr])", ".%1%2")
if portugal then
-- "Improper" clusters of non-sibiliant-obstruent + obstruent (pt, bt, bd, dk, kt; ps, bs, bv, bʒ, tz, dv, ks;
-- ft), non-sibiliant-obstruent + nasal (pn, bn, tm, tn, dm, dn, gm, gn), nasal + nasal (mn) are syllabified in
-- Portugal as .pt, .bv, .mn, etc. Note ʃ.t, ʃ.p, ʃ.k, etc. But in Brazil, all of these divide between the
-- consonants (p.t, b.v, ʃ.t, s.p, etc.). Particular case: [[ab-rogação]] divides as a.brr in Portugal but ab.rr
-- in Brazil.
text = rsub(text, "([pbtdkgfv]" .. H_OR_SYL_TRANSP .. "*)%.([pbtdkgfvsSçzʃʒjmMnNɲʎʁ])", ".%1%2")
text = rsub(text, "([mM]" .. H_OR_SYL_TRANSP .. "*)%.([nN])", ".%1%2")
else
-- /tʃ/, /dʒ/ are normally single sounds, but adj- in [[adjetivo]], [[adjunto]] etc. should be 'ad.j'
text = rsub(text, "(t" .. STC .. ")%.(ʃ)", ".%1%2")
text = rsub(text, "(d" .. STC .. ")%.(j)", ".%1%2")
text = rsub(text, "(" .. word_or_component_sep_c .. "a" .. STC .. ")%.(d" .. STC .. ")(j)", "%1%2.%3")
end
-- All vowels should be separated from adjacent vowels by a syllable division except
-- (1) aeo + unstressed i/u, ([[saiba]], [[peixe]], [[noite]], [[Paulo]], [[deusa]], [[ouro]]), except when
-- followed by nh or m/n/r/l + (non-vowel or word end), e.g. Bom.ba.im, ra.i.nha, Co.im.bra, sa.ir, but Jai.me,
-- a.mai.nar, bai.le, ai.ro.so, quei.mar, bei.ra;
-- (2) iu(s), ui(s) at end of word, e.g. fui, Rui, a.zuis, pa.riu, viu, sa.iu;
-- (3) ão, ãe, õe.
--
-- The easiest way to handle this is to put a special symbol between vowels that should not have a syllable
-- division between them.
--
-- First, put a syllable divider between [aeo].[iu][mnlr], as in [[Bombaim]], [[Coimbra]], [[saindo]], [[sair]],
-- [[Iaundé]], [[Raul]]. Note that in cases like [[Jaime]], [[queimar]], [[fauna]], [[baile]], [[Paulo]], [[beira]],
-- where a vowel follows the m/n/l/r, there will already be a syllable division between i.m, u.n, etc., which will
-- block the following substitution.
text = rsub(text, "([aeo]" .. accent_c .. "*" .. STC .. ")([iu]" .. STC .. "[mnlr])", "%1.%2")
-- Also put a syllable divider between [aeo].[iu].ɲ coming from 'nh' ([[rainha]], [[moinho]]).
text = rsub(text, "([aeo]" .. accent_c .. "*" .. STC .. ")([iu]" .. STC .. "%.ɲ)", "%1.%2")
-- Prevent syllable division between final -ui(s), -iu(s). This should precede the following rule that prevents
-- syllable division between ai etc., so that [[saiu]] "he left" gets divided as sa.iu.
-- It doesn't make sense to have STC in the middle of a diphthong here.
text = rsub(text, "(u" .. accent_c .. "*)(is?" .. word_or_component_sep_c .. ")", "%1" .. TEMP1 .. "%2")
text = rsub(text, "(i" .. accent_c .. "*)(us?" .. word_or_component_sep_c .. ")", "%1" .. TEMP1 .. "%2")
-- Prevent syllable division between ai, ou, etc. unless either the second vowel is accented [[saído]]) or there's
-- a TEMP1 marker already after the second vowel (which will occur e.g. in [[saiu]] divided as 'sa.iu').
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)([iu][^" .. accent .. TEMP1 .. "])", "%1" .. TEMP1 .. "%2")
text = rsub_repeatedly(text, "(u" .. accent_c .. "*)(i[^" .. accent .. TEMP1 .. "])", "%1" .. TEMP1 .. "%2")
text = rsub_repeatedly(text, "(i" .. accent_c .. "*)(u[^" .. accent .. TEMP1 .. "])", "%1" .. TEMP1 .. "%2")
-- Prevent syllable division between nasal diphthongs unless somehow the second vowel is accented.
text = rsub_repeatedly(text, "(a" .. accent_c .. "*" .. TILDE .. ")([eo][^" .. accent .. "])", "%1" .. TEMP1 .. "%2")
text = rsub_repeatedly(text, "(o" .. accent_c .. "*" .. TILDE .. ")(e[^" .. accent .. "])", "%1" .. TEMP1 .. "%2")
text = rsub_repeatedly(text, "(u" .. accent_c .. "*" .. TILDE .. ")(i[^" .. accent .. "])", "%1" .. TEMP1 .. "%2")
-- All other sequences of vowels get divided.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. STC .. ")(" .. V .. ")", "%1.%2")
-- Remove the marker preventing syllable division.
text = rsub(text, TEMP1, "")
-- An acute or circumflex not followed by a stress marker has primary stress, so indicate it.
text = rsub_repeatedly(text, "(" .. V .. quality_c .. ")([^" .. stress .. "])", "%1ˈ%2")
-- Line-under indicates secondary stress.
text = rsub(text, LINEUNDER, "ˌ")
-- Add primary stress to the word if not already present.
local function accent_word(word)
-- Check if stress already marked. We check first for primary stress before checking for tilde in case both
-- primary stress and tilde occur, e.g. [[bênção]], [[órgão]], [[hétmã]], [[connosco]] respelled 'cõnôsco'.
if rfind(word, "ˈ") then
return word
end
-- Preserve the syllable divider, which may be auto-added or user-specified.
local syllables = m_strutils.capturing_split(word, "([." .. SYLDIV .. "])")
-- Check for nasal vowel marked with tilde and without non-primary stress; assign stress to the last such
-- syllable in case there's more than one tilde, e.g. [[pãozão]]. Note, this can happen in the part before
-- -mente, cf. [[anticristãmente]], and before -zinho, cf. [[coraçãozinho]].
for i = #syllables, 1, -2 do -- -2 because of the syllable dividers; see above.
local changed
syllables[i], changed = rsubb(syllables[i], "(" .. V .. quality_c .. "*)" .. TILDE, "%1ˈ" .. TILDE)
if changed then
return table.concat(syllables)
end
end
-- Apply the default stress rule.
local sylno
-- Prefixes ending in a vowel such as pseudo- have a PSEUDOCONS after the final vowel, but we don't want that to
-- interfere in the stress-assignment algorithm.
if #syllables > 1 and (rfind(word, "[aeo][s" .. PSEUDOCONS .. "]?$") or rfind(word, "[ae]m$") or rfind(word, "[ae]ns$")) then
-- Stress the last syllable but one. The -2 is because of the syllable dividers; see above.
sylno = #syllables - 2
else
sylno = #syllables
end
-- Don't put stress on epenthetic i; instead, we stress the preceding syllable, as if epenthetic i weren't
-- there.
while sylno > 1 and rfind(syllables[sylno], "I") do
sylno = sylno - 2
end
-- It is (vaguely) possible that we have a one-syllable word beginning with a complex cluster such as gn-
-- followed by a normally unstressed ending such as -em. In this case, we want the ending to be stressed.
while sylno < #syllables and rfind(syllables[sylno], "I") do
sylno = syno + 2
end
-- If we are on a syllable without a vowel (can happen if it's the last syllable in a non-final component of a
-- word, when using a component separator that is transparent to stress, such as in [[rapazinho]] respelled
-- 'rapaz+inho'), stress the syllable to the left.
while sylno > 1 and not rfind(syllables[sylno], V) do
sylno = sylno - 2
end
if rfind(syllables[sylno], stress_c) then
-- Don't do anything if stress mark already present. (Since we check for primary stress above, this check
-- specifically affects non-primary stress.)
return word
end
-- Add stress mark after first vowel (and any quality mark).
syllables[sylno] = rsub(syllables[sylno], "^(.-" .. V .. quality_c .. "*)", "%1ˈ")
return table.concat(syllables)
end
-- Split the text into words and the words into components so we can correctly add stress to components without it.
local words = rsplit(text, " ")
for j, word_with_boundary_markers in ipairs(words) do
-- Prefixes have a PREFIX_MARKER after the # at the end of the prefix; split it off.
local begin_marker, word, end_marker = rmatch(word_with_boundary_markers, "^(#*)(.-)([#" .. PREFIX_MARKER .. "]*)$")
-- Words ends in -mente, -zinho(s) or -zinha(s); add primary stress to the preceding portion as if stressed
-- (e.g. [[agitadamente]] -> 'agitádamente') unless already stressed (e.g. [[rapidamente]] respelled
-- 'rápidamente'). The primary stress will be converted to secondary stress further below. Essentially, we
-- rip the word apart into two words ('mente'/'zinho' and the preceding portion) and
-- stress each one independently. Note that the effect of adding a primary stress will also be to cause
-- an error if stressed 'e' or 'o' is not properly marked as é/ê or ó/ô; cf. [[certamente]], which must
-- be respelled 'cértamente', and [[posteriormente]], which must be respelled 'posteriôrmente', just as
-- with [[certa]] and [[posterior]]. To prevent this happening, you can add an accent to -mente or
-- -zinho, e.g. [[dormente]] respelled 'dormênte', [[vizinho]] respelled 'vizínho'.
if rfind(word, syldiv_c .. "men%.te$") then
word = rsub(word, syldiv_c .. "(men%.te)$", "@%1")
else
word = rsub(word, syldiv_c .. "(zi%.ɲ[oa]s?)$", "@%1")
end
-- Split on components; preserve the component divider.
local components = m_strutils.capturing_split(word, "(" .. component_sep_c .. syldiv_c .. "*)")
for k = 1, #components, 2 do -- 2 because of the component dividers.
-- Don't add stress to components followed by ++ (converted to *).
if k == #components or not rfind(components[k + 1], "%*") then
components[k] = accent_word(components[k])
end
end
-- Reconstruct the word.
words[j] = begin_marker .. table.concat(components, "") .. end_marker
end
-- Reconstruct the text from the words.
text = table.concat(words, " ")
-- Add word boundaries around component separators. We add them on both sides of - and -- (converted to @), which
-- behave mostly like a true word separator, but only on the right side of other component separators (which
-- corresponds to the beginning of the word following the separator). Note that some component separators (+ and ++
-- [converted to *]) are transparent to syllable boundaries, meaning that there may be a syllable divider directly
-- to the right of the component separator. To simplify the code below, we put the word boundary marker on the outside
-- of the syllable boundary marker.
text = rsub(text, "([%-@]" .. syldiv_c .. "?)", "#%1#")
text = rsub(text, "([+:*]" .. syldiv_c .. "?)", "%1#")
-- I has served its purpose (not considered when accenting).
text = rsub(text, "I", "i")
-- Remove hiatus between initial <i> and following vowel ([[Iasmim]]) unless the <i> is stressed ([[ia]]) or the
-- user explicitly added a . (converted to SYLDIV above).
text = rsub(text, "#i%.(" .. V .. ")", "#y%1")
if brazil then
-- In Brazil, hiatuses involving i. or u. have two possibilities (full vowel or glide); represent using Ì. and Ù.,
-- which we later convert appropriately. Do this before eliminating SYLDIV so the user can force a hiatus using a
-- period.
local hiatus_to_optional_glide = {["i"] = "Ì", ["u"] = "Ù"}
text = rsub(text, "(" .. C_OR_WORD_BOUNDARY .. ")([iu])(%." .. V .. ")",
function(before, hiatus, after) return before .. hiatus_to_optional_glide[hiatus] .. after end)
-- In Brazil, hiatuses of the form í.o (e.g. [[rio]] "river", [[vazio]]; but not [[rio]] "I laugh") have two
-- possibilities (i.u or iw); represent using Ú, which we later convert appropriately. Do this before eliminating
-- SYLDIV so the user can force a hiatus using a period, as in [[rio]] "I laugh" respelled 'ri.o'.
text = rsub(text, "(i" .. ipa_stress_c .. "%.)o(s?#)", "%1Ú%2")
else
-- Outside of Brazil, e.i -> a.i, e.g. [[ateísta]], [[proteína]], [[proteinúrio]] respelled 'prote.inúrio'. But seems
-- not to happen in rei- ([[reincidente]], [[reiniciar]], [[reidratar]], etc.). Note, it does occur in [[reídeo]],
-- which needs respelling.
text = rsub(text, "(#re" .. syldiv_c .. ")(i)", "%1" .. TEMP1 .. "%2")
text = rsub(text, "e(" .. syldiv_c .. "i)", "a%1")
text = rsub(text, TEMP1, "")
-- Outside of Brazil, hiatuses involving 'e./i.' or 'o./u.' after obstruent + l/r preceding a vowel have two
-- possibilities (full vowel or glide), as in [[criança]], [[altruista]], etc. Represent using Ì. and Ù., which
-- we later convert appropriately. Do this before eliminating SYLDIV so the user can force a hiatus using a
-- period.
local hiatus_to_optional_glide = {["e"] = "Ì", ["i"] = "Ì", ["o"] = "Ù", ["u"] = "Ù"}
text = rsub(text, "([pbtdkgfv]" .. H_OR_SYL_TRANSP .. "*[lr])([eiou])(%." .. V .. ")",
function(before, hiatus, after) return before .. hiatus_to_optional_glide[hiatus] .. after end)
-- Outside of Brazil, remove hiatus more generally whenever 'e./i.' or 'o./u.' precedes a vowel. Do this before
-- eliminating SYLDIV so the user can force hiatus using a period.
local hiatus_to_glide = {["e."] = "y", ["i."] = "y", ["o."] = "w", ["u."] = "w"}
text = rsub(text, "(" .. C_OR_WORD_BOUNDARY .. ")([eiou]%.)(" .. V .. ")",
function(before, hiatus, after) return before .. hiatus_to_glide[hiatus] .. after end)
end
-- Convert user-specified syllable division back to period. See comment above when we add SYLDIV.
text = rsub(text, SYLDIV, ".")
-- Vowel quality handling. First convert all a -> A, e -> E, o -> O. We will then convert A -> a/ɐ, E -> e/ɛ/ɨ,
-- O -> o/ɔ/u depending on accent marks and context. Ultimately all vowels will be one of the nine qualities
-- aɐeɛiɨoɔu and following each vowel will either be nothing (no stress), an IPA primary stress mark (ˈ) or an
-- IPA secondary stress mark (ˌ), in turn possibly followed by a tilde (nasalization). After doing everything
-- that depends on the position of stress, we will move the IPA stress marks to the beginning of the syllable.
text = rsub(text, "[aeo]", {["a"] = "A", ["e"] = "E", ["o"] = "O"})
text = rsub(text, DOTOVER, "") -- eliminate DOTOVER; it served its purpose of preventing stress
-- Nasal vowel handling.
-- Final unstressed -am (in third-person plural verbs) pronounced like unstressed -ão.
text = rsub(text, "Am#", "A" .. TILDE .. "O#")
if portugal then
-- In Portugal, final -n is really /n/, and preceding unstressed e/o are open ([[cólon]], [[crípton]], [[éon]];
-- [[glúten]], [[hífen]], [[pólen]]).
text = rsub(text, "n#", "N#")
text = rsub(text, "([EO])(N#)", "%1" .. AC .. "%2")
end
if brazil then
-- In Brazil, [[põem]] is pronounced like 'põe'.
text = rsub(text, "(Oˈ" .. TILDE .. ")(Em#)", "%1E#")
else
-- In Portugal, circumflex accent on final -em ([[vêm]], [[mantêm]], etc.) indicates a special double nasal diphthong
-- pronunciation.
text = rsub(text, "E" .. CFLEX .. "ˈm#", "E" .. CFLEX .. "ˈ" .. TILDE .. "y" .. TILDE .. ".E" .. CFLEX .. "m#")
-- In Portugal, [[põem]] is pronounced like 'põeem'.
text = rsub(text, "(Oˈ" .. TILDE .. ")(Em#)", "%1E.%2")
end
-- Acute accent on final -em ([[além]], [[também]]) and final -ens ([[parabéns]]) does not indicate an open
-- pronunciation.
text = rsub(text, "E" .. AC .. "(ˈ[mn]s?#)", "E" .. CFLEX .. "%1")
-- Vowel + m/n within a syllable gets converted to tilde.
text = rsub(text, "(" .. V .. quality_c .. "*" .. stress_c .. "*)[mn]", "%1" .. TILDE)
-- Non-high vowel without quality mark + tilde needs to get the circumflex (possibly fed by the previous change).
text = rsub(text, "([AEO])(" .. stress_c .. "*)" .. TILDE, "%1" .. CFLEX .. "%2" .. TILDE)
-- Primary-stressed vowel without quality mark + m/n/nh across syllable boundary gets a circumflex, cf. [[cama]],
-- [[ano]], [[banho]] (excluding Northern Portugal)
if style == "npt" then
-- Northern Portugal keeps the open "a" vowels
text = rsub(text, "(A)(ˈ%.[mnɲMN])", "%1" .. AC .. "%2")
end
text = rsub(text, "(" .. V .. ")(ˈ%.[mnɲMN])", "%1" .. CFLEX .. "%2")
if brazil then
if style ~= "sbr" then -- Seems this happens less or not at all in South Brazil.
-- Primary-stressed vowel + m/n across syllable boundary gets nasalized in Brazil, cf. [[cama]], [[ano]].
text = rsub(text, "(" .. V .. quality_c .. "*)(ˈ%.[mnMN])", "%1" .. TILDE .. "%2")
end
-- All vowels before nh (always across syllable boundary) get circumflexed and nasalized in Brazil,
-- cf. [[ganhar]]. I *think* the circumflexing but not nasalizing happens in South Brazil.
text = rsub(text, "(" .. V .. stress_c .. "*)(%.ɲ)", "%1" .. CFLEX .. "%2")
if style ~= "sbr" then -- I *think* this doesn't apply to South Brazil; need to verify.
text = rsub(text, "(" .. V .. quality_c .. "*" .. stress_c .. "*)(%.ɲ)", "%1" .. TILDE .. "%2")
end
-- Convert initial unstressed em-/en- before consonant to special symbol /Ẽ/, which later on is converted
-- to /e/ (逐個音節發音) or /i/ (自然發音).
text = rsub(text, "(#E" .. CFLEX .. TILDE ..")(%." .. C ..")", "#Ẽ" .. TILDE .. "%2")
-- Same in [[em]] standing alone (which will have a DOTUNDER in it), and in [[em-]].
text = rsub(text, "(#E" .. CFLEX .. DOTUNDER .. "?" .. TILDE ..")(#)", "#Ẽ" .. TILDE .. "%2")
end
-- Nasal diphthongs.
local nasal_termination_to_glide = {["E"] = "y", ["O"] = "w"}
-- In ãe, ão, the second letter represents a glide.
text = rsub(text, "(A" .. CFLEX .. stress_c .. "*" .. TILDE .. ")([EO])",
function(v1, v2) return v1 .. nasal_termination_to_glide[v2] .. TILDE end)
-- Likewise for õe.
text = rsub(text, "(O" .. CFLEX .. stress_c .. "*" .. TILDE .. ")E", "%1y" .. TILDE)
-- Likewise for ũi (generated above from muit-).
text = rsub(text, "(u" .. stress_c .. "*" .. TILDE .. ")i", "%1y" .. TILDE)
-- Final -em and -ens (stressed or not) pronounced /ẽj̃(s)/. (Later converted to /ɐ̃j̃(s)/ in Portugal.)
text = rsub(text, "(E" .. CFLEX .. stress_c .. "*" .. TILDE .. ")(s?#)", "%1y" .. TILDE .. "%2")
-- Oral diphthongs.
-- ei, eu, oi, ou -> êi, êu, ôi, ôu
text = rsub(text, "([EO])(" .. stress_c .. "*[iuywY])", "%1" .. CFLEX .. "%2")
-- ai, au -> ái, áu
text = rsub(text, "(A)(" .. stress_c .. "*[iuywY])", "%1" .. AC .. "%2")
-- Convert A/E/O as appropriate when followed by a secondary or tertiary stress marker. If a quality is given,
-- it takes precedence; otherwise, act as if an acute accent were given.
text = rsub(text, "([AEO])(" .. non_primary_stress_c .. ")", "%1" .. AC .. "%2")
-- Stressed o in -dor, -dor, -sor ([[ganhador]], [[autor]], [[invasor]], [[agressor]], etc.) and feminines and plurals
-- is closed /o/.
text = rsub(text, "([dtsS])O(ˈr#)", "%1o%2")
text = rsub(text, "([dtsS])O(ˈ%.r[EA]s?#)", "%1o%2")
-- Stressed o in -oso is closed /o/.
text = rsub(text, "O(ˈ%.sO#)", "o%1")
-- Stressed o in -osa, -osos, -osas is open /ɔ/.
text = rsub(text, "O(ˈ%.s[OA]s?#)", "ɔ%1")
-- Unstressed syllables.
-- Before final <x>, unstressed a/e/o are open, e.g. [[clímax]], [[córtex]], [[xérox]].
text = rsub(text, "([AEO])(X)", "%1" .. AC .. "%2")
-- Capital X has served its purpose, so replace it.
text = rsub(text, "X", "kç")
if brazil then
if style ~= "sbr" then
-- Final unstressed -e(s), -o(s) -> /i/ /u/ (including before -mente)
local brazil_final_vowel = {["E"] = "i", ["O"] = "u"}
text = rsub(text, "([EO])(s?#)", function(v, after) return brazil_final_vowel[v] .. after end)
-- Word-final unstressed -a(s) -> /ɐ/ (not before -mente)
text = rsub(text, "A(s?#[^@])", function(after) return "ɐ" .. after end)
-- Word-final unstressed -ar -> /ɐr/ (e.g. [[açúcar]])
text = rsub(text, "A(r#)", function(after) return "ɐ" .. after end)
end
-- Initial unmarked unstressed non-nasal e- + -sC- -> /i/ or /e/ ([[estar]], [[esmeralda]]). To defeat this,
-- explicitly mark the <e> e.g. as <ệ> or <eh>. We reuse the special symbol /I/ for this purpose, which later
-- on is converted to /i/ or /e/. In South Brazil, however, the raised /i/ variant doesn't seem to exist.
if not rfind(text, "#Es.ç") then
text = rsub(text, "#E(s" .. C .. "*%.)", style == "sbr" and "#e%1" or "#I%1")
end
-- Remaining unstressed a, e, o without quality mark -> /a/ /e/ /o/.
local brazil_unstressed_vowel = {["A"] = "a", ["E"] = "e", ["O"] = "o"}
text = rsub(text, "([AEO])([^" .. accent .. "])",
function(v, after) return brazil_unstressed_vowel[v] .. after end)
end
if portugal then
-- In Portugal, final unstressed -r opens preceding a/e/o ([[dólar]], [[líder]], [[júnior]], [[inter-]]
-- respelled 'ínter:...').
text = rsub(text, "([AEO])(r" .. word_or_component_sep_c .. ")", "%1" .. AC .. "%2")
-- In Portugal, unstressed a/e/o before coda l takes on an open quality. Note that any /l/ directly after a
-- vowel must be a coda /l/ because otherwise there would be a syllable boundary marker.
text = rsub(text, "([AEO])l", function(v)
-- The symbol Ɔ is later converted to /o/ or /ɔ/.
local vowel_to_before_l = {["A"] = "a", ["E"] = "ɛ", ["O"] = "Ɔ"}
return vowel_to_before_l[v] .. "l"
end)
-- Unstressed 'ie' -> /jɛ/
text = rsub(text, "yE([^" .. accent .. "])", "yɛ%1")
-- Initial unmarked unstressed non-nasal e- + -sC- (seemingly also after des-, see Infopédia [[desestagnar]]) ->
-- temporary symbol I (later changed to /(i)/, except after a vowel, in which case it is deleted). Note that /s/
-- directly after a vowel must be a coda /s/ because otherwise there would be a syllable boundary marker.
text = rsub(text, "#Es", "#Is")
-- Initial unmarked unstressed non-nasal e- -> /i/, including after des-.
text = rsub(text, "#E([^" .. accent .. "])", "#i%1")
-- Initial unmarked unstressed non-nasal o- -> /ɔ/ if another vowel follows (not 'o', 'os' by themselves).
text = rsub(text, "(#O)(.-#)", function(o, rest)
if rfind(rest, "^[^" .. accent .. "]") and rfind(rest, V) then
return "#ɔ" .. rest
else
return o .. rest
end
end)
-- All other unmarked unstressed non-nasal e, o, a -> /ɨ/ /u/ /ɐ/
local portugal_unstressed_vowel = {["A"] = "ɐ", ["E"] = "ɨ", ["O"] = "u"}
text = rsub(text, "([AEO])([^" .. accent .. "])",
function(v, after) return portugal_unstressed_vowel[v] .. after end)
end
-- Remaining vowels.
-- All remaining a -> /a/ (should always be stressed).
text = rsub(text, "A([^" .. quality .. "])", "a%1")
-- Ignore quality markers on i, u; only one quality.
text = rsub(text, "([iu])" .. quality_c, "%1")
-- Convert a/e/o + quality marker appropriately.
local vowel_quality = {
["A" .. AC] = "a", ["A" .. CFLEX] = "ɐ",
["E" .. AC] = "ɛ", ["E" .. CFLEX] = "e",
["O" .. AC] = "ɔ", ["O" .. CFLEX] = "o",
}
text = rsub(text, "([AEO]" .. quality_c .. ")", vowel_quality)
-- Stressed o in hiatus ([[voo]], [[boa]], [[perdoe]], etc.) is closed /o/.
text = rsub(text, "O(ˈ%." .. V .. ")", "o%1")
-- Stressed closed /o/ in Northern Portugal has a following optional /w/.
if style=="npt" then
text = rsub(text, "o(ˈ%." .. V .. ")", "o(w)%1")
end
-- Any remaining E or O (always without quality marker) is an error.
if rfind(text, "[EO]") then
err("Stressed e or o not occurring nasalized or in a diphthong must be marked for quality using é/ê or ó/ô")
end
-- Finally, eliminate DOTUNDER, now that we have done all vowel reductions.
text = rsub(text, DOTUNDER, "")
if brazil then
-- Epenthesize /(j)/ in [[faz]], [[mas]], [[três]], [[dez]], [[feroz]], [[luz]], [[Jesus]], etc. Note, this only
-- triggers at actual word boundaries (not before -mente), and not on nasal vowels or diphthongs. To defeat this
-- (e.g. in plurals), respell using 'ss' or 'hs'.
text = rsub(text, "(" .. V .. "ˈ)([sz]#[^@])", "%1Y%2")
-- Also should happen at least before + (cf. [[rapazinho]] respelled 'rapaz+inho', [[vozinha]] respelled
-- 'vóz+inha').
text = rsub(text, "(" .. V .. "ˈ)(%.?[sz]%+)", "%1Y%2")
-- But should not happen after /i/.
text = rsub(text, "iˈY", "iˈ")
end
-- 'S' here represents earlier ss. Word-finally it is used to prevent epenthesis of (j) and should behave
-- like 's'. Elsewhere (between vowels) it should behave like 'ç'.
text = rsub(text, "S#", "s#")
text = rsub(text, "S", "ç")
-- s, z
-- s in trans + V -> z: [[transação]], [[intransigência]]
text = rsub(text, "(trɐ" .. stress_c .. "*" .. TILDE .. ".)s(" .. V .. ")", "%1z%2")
-- word final z -> s
text = rsub(text, "z#", "s#")
-- s is voiced between vowels (not nasalized) or between vowel and voiced consonant, including across word
-- boundaries; may be fed by previous rule. We have to split this into two rules before /s/ should not be voiced
-- between nasal vowel and another vowel ([[cansar]]) but should be voiced between nasal vowel and a voiced
-- consonant ([[transgredir]]). Note that almost all occurrences of nasal vowel + s + voiced consonant are in
-- trans- which potentially could be handled above, but there may be others, e.g. [[Flensburg]].
text = rsub(text, "(" .. V .. stress_c .. "*Y?%.?)s(" .. wordsep_c .. "*h?[" .. vowel .. glide .. "])", "%1z%2")
text = rsub(text, "(" .. V .. accent_c .. "*Y?%.?)s(" .. wordsep_c .. "*h?[" .. voiced_cons .. "])", "%1z%2")
-- z before voiceless consonant, e.g. [[Nazca]]; c and q already removed
text = rsub(text, "z(" .. wordsep_c .. "*[çfkpsʃt])", "s%1")
-- Coda /s,z/
if portugal or style == "rio" then
-- In General Portugal and Rio de Janeiro; s/z before consonant (including across word boundaries) or end of utterance -> ʃ/ʒ;
-- but not word-initially (e.g. [[stressado]]).
local sibilant = {["s"] = "ʃ", ["z"] = "j"}
text = rsub(text, "([sz])(##)", function(sz, after) return sibilant[sz] .. after end)
-- s/z are maintained word-initially. To implement this, insert TEMP1 directly before the s/z we want to preserve, then check for this
-- TEMP1 not being present when converting to shibilant, then remove TEMP1.
text = rsub(text, "([# %-]#)([sz])", "%1" .. TEMP1 .. "%2")
text = rsub_repeatedly(text, "([^" .. TEMP1 .. "])([sz])(" .. wordsep_c .. "*" .. C_NOT_H_OR_GLIDE .. ")",
function(before, sz, after) return before .. sibilant[sz] .. after end)
text = rsub(text, TEMP1, "")
end
text = rsub(text, "ç", "s")
text = rsub(text, "j", "ʒ")
-- Reduce identical sibilants, including across word boundaries.
text = rsub(text, "([szʃʒ])(" .. wordsep_c .. "*)(%1)", "%2%1")
if style == "rio" then
-- Also reduce sibilant ([[descer]], [[as]] [[zonas]]); not in Portugal, but in Portugal we later
-- generate two outputs in this case, either /ʃs/ and /ʒz/ (逐個音節發音) or /ʃ/ and /ʒ/ (natural
-- pronunciation). Note that the reduction of /ʃs/ to /ʃ/ in Portugal is different from the reduction of the
-- same to /s/ in Brazil (though it shouldn't be)
text = rsub(text, "ʃ(" .. wordsep_c .. "*s)", "%1")
text = rsub(text, "ʒ(" .. wordsep_c .. "*z)", "%1")
end
-- N/M from double n/m
text = rsub(text, "[NM]", {["N"] = "n", ["M"] = "m"})
if portugal then
--Drop unneeded 'h' in 'rh' respellings for European Portuguese
text = rsub(text, "rh","r")
if style=="spt" then
--Paragoge of i in word-final r/l in Southern Portugal
text = rsub(text, "ˈr#", "ˈ.ri#")
text = rsub(text, "ˈl#", "ˈ.li#")
end
end
-- r
-- Double rr -> ʁ already handled above.
-- Initial r or l/n/s/z + r -> strong r (ʁ).
text = rsub(text, "([#" .. TILDE .. "lszʃʒ]%.?)r", "%1ʁ")
if brazil then
-- Word-final r before vowel in verbs is /(ɾ)/.
text = rsub(text, "([aɛei]ˈ)r(#" .. wordsep_c .. "*h?" .. V .. ")", "%1(ɾ)%2")
-- Coda r before vowel is /ɾ/.
text = rsub(text, "r([.#]" .. wordsep_c .. "*h?" .. V .. ")", "ɾ%1")
end
-- Word-final r in Brazil in verbs (not [[pôr]]) is usually dropped. Use a spelling like 'marh' for [[mar]]
-- to prevent this. Make sure not to do this before -mente/-zinho ([[polegarzinha]], [[popularmente]]).
if brazil then
text = rsub(text, "([aɛei]ˈ)r(#[^@])",
"%1(" .. (style == "sp" and "ɾ" or style == "sbr" and "ɻ" or "ʁ") .. ")%2")
if style ~= "sp" then
-- Coda r in Southern Brazil is [ɻ], otherwise outside of São Paulo is /ʁ/.
text = rsub(text, "r(" .. C .. "*[.#])", (style == "sbr" and "ɻ" or "ʁ") .. "%1")
end
end
-- All other r -> /ɾ/.
text = rsub(text, "r", "ɾ")
if brazil and phonetic then
-- "Strong" ʁ before voiced consonant is [ɦ] in much of Brazil, [ʁ] in Rio. Use R as a temporary symbol.
text = rsub(text, "ʁ(" .. wordsep_c .. "*[" .. voiced_cons .. "])", style == "rio" and "R%1" or "ɦ%1")
-- Other "strong" ʁ is [h] in much of Brazil, [χ] in Rio. Use H because later we remove all <h>.
text = rsub(text, "ʁ", style == "rio" and "χ" or "H")
text = rsub(text, "R", "ʁ")
end
-- Nasal Diphthong <ẽi> and <ɛi>
if portugal then
-- In Portugal, always lower e -> ɐ before j when nasalized.
text = rsub(text, "e(" .. accent_c .. TILDE .. "*%.?y)", "ɐ%1")
-- In Portugal, lower ɛ -> e before i in <ɛi> (the open pronunciation is dated)
text = rsub(text, "ɛ(" .. accent_c .. "*i)", "e%1")
end
-- Diphthong <ei> (if unchanged, it shall yield /ej/; this should be the case for African pronunciations)
if brazil then
-- In Brazil, add optional /j/ in <eir>, <eij>, <eig> and <eix> (as in [[cadeira]], [[beijo]], [[manteiga]] and
-- [[peixe]]).
text = rsub(text, "(e" .. accent_c .. "*)i(%.[ɾʒgʃ])", "%1(j)%2")
-- [In Brazil, add optional /j/ in <aix> (as in [[caixa]] and [[baixo]]).] -- This was added by an IP, see
-- [[Special:Contributions/186.212.6.138]]; this seems non-standard to me. If we are to include it, it should
-- not be done this way, but as two separate outputs with the one lacking the /j/ marked with a qualifier such
-- as "non-standard"; compare the way the initial enC- is handled (near the end of export.IPA()), where there
-- are two outputs, with /ẽC-/ marked as "逐個音節發音" and /ĩC-/ marked as "自然發音".
-- (Benwing2)
-- text = rsub(text, "(a" .. accent_c .. "*)i(%.ʃ)", "%1(j)%2")
elseif style == "spt" then
-- In Southern Portugal, <ei> monophthongizes to <e>
text = rsub(text, "(e" .. accent_c .. "*)i", "%1")
elseif style == "gpt" then
-- In general Portugal, lower e -> ɐ before i in <ei>.
text = rsub(text, "e(" .. accent_c .. "*i)", "ɐ%1")
-- In general Portugal, lower e -> ɐ before j
text = rsub(text, "e(" .. accent_c .. "*%.?y)", "ɐ%1")
-- In general Portugal, lower e -> ɐ(j) before other palatals.
text = rsub(text, "e(" .. stress_c .. "*)(%.?[ʒʃɲʎ](" .. V .. "))", "ɐ%1(j)%2")
end
-- Diphthong <ou> (if unchanged, it shall yield /o(w)/)
if style == "spt" then
-- In Southern Portugal, <ou> always monophthongizes to <o>
text = rsub(text, "(o" .. accent_c .. "*)u", "%1")
elseif style == "npt" then
-- In Northern Portugal (and Galicia), <ou> is kept
text = rsub(text, "(o" .. accent_c .. "*)u", "%1w")
end
-- Else optional /w/ in <ou>
text = rsub(text, "(o" .. accent_c .. "*)u", "%1(w)")
-- Stop consonants.
if brazil then
-- Palatalize t/d + i/y -> affricates in Brazil.
text = rsub(text, "([td])(" .. word_or_component_sep_c .. "*[" .. high_front_vocalic .. "])",
function(td, high_vocalic) return palatalize_td[td] .. high_vocalic end)
elseif portugal and phonetic then
-- Fricativize voiced stops in Portugal when not utterance-initial or after a nasal; also not in /ld/.
-- Easiest way to do this is to convert all voiced stops to fricative and then back to stop in the
-- appropriate contexts.
local fricativize_stop = { ["b"] = "β", ["d"] = "ð", ["g"] = "ɣ" }
local occlude_fricative = { ["β"] = "b", ["ð"] = "d", ["ɣ"] = "g" }
text = rsub(text, "[bdg]", fricativize_stop)
text = rsub(text, "##([βðɣ])", function(bdg) return "##" .. occlude_fricative[bdg] end)
text = rsub(text, "(" .. TILDE .. wordsep_c .. "*)([βðɣ])", function(before, bdg) return before .. occlude_fricative[bdg] end)
text = rsub(text, "(l" .. wordsep_c .. "*)ð", "%1d")
end
-- Glides and l. ou -> o(w) must precede coda l -> w in Brazil, because <ol> /ow/ cannot be reduced to /o/.
-- ou -> o(w) before conversion of remaining diphthongs to vowel-glide combinations so <ow> can be used to
-- indicate a non-reducible glide.
-- Handle coda /l/.
if brazil then
-- Coda l -> /w/ in Brazil.
text = rsub(text, "l(" .. C .. "*[.#])", "w%1")
elseif phonetic then
-- Coda l -> [ɫ] in Portugal (and Rio Grande do Sul, according to Cunha-Cintra)
text = rsub(text, "l(" .. C .. "*[.#])", "ɫ%1")
end
text = rsub(text, "y", "j")
if brazil then
text = rsub(text, "Y", "(j)") -- epenthesized in [[faz]], [[três]], etc.
else
-- 'I' in Portugal represents word-initial (i) before sC, except after /i/ (e.g. [[antiestático]]), in which
-- case it is elided. In the latter case, we need to elide the word/component separators, otherwise we end up
-- with an extra syllable divider: /ˌɐ̃.ti.ʃˈta.ti.ku/ instead of correct /ˌɐ̃.tiʃˈta.ti.ku/.
text = rsub(text, "(i" .. accent_c .. "*)" .. word_or_component_sep_c .. "*#I", "%1")
text = rsub(text, "I", "(i)")
end
local vowel_termination_to_glide = brazil and phonetic and
{["i"] = "ɪ̯", ["j"] = "ɪ̯", ["u"] = "ʊ̯", ["w"] = "ʊ̯"} or
{["i"] = "j", ["j"] = "j", ["u"] = "w", ["w"] = "w"}
-- i/u as second part of diphthong becomes glide.
text = rsub(text, "(" .. V .. accent_c .. "*" .. "%(?)([ijuw])",
function(v1, v2) return v1 .. vowel_termination_to_glide[v2] end)
-- nh
if brazil and phonetic and style ~= "sbr" then
-- [[unha]] pronounced [ˈũ.j̃ɐ]; nasalization of previous vowel handled above. But initial nh- e.g. [[nhaca]],
-- [[nheengatu]], [[nhoque]] is [ɲ]. I *think* this doesn't happen in South Brazil. We do have the phonetic
-- representation [ẽj̃.pũˈj̃aɾ] given for [[empunhar]], but this is the only such case and may be a mistake.
text = rsub(text, "([^#])ɲ", "%1j" .. TILDE)
end
if portugal then
-- Suppress final -ɨ before a vowel
text = rsub(text, "ɨ#[ %-]#(" .. V .. ")", "‿%1")
--Make optional utterance-finally
-- text = rsub(text, "ɨ##", "(ɨ)##")
-- (ɨ) after l when suppressed should convert to coda ɫ, so split it later into two pronuns.
-- text = rsub(text, "l%(ɨ%)##", "L##")
-- (ɨ) after r when suppressed should also convert to coda
-- text = rsub(text, "ɾ%(ɨ%)##", "R##")
end
text = rsub(text, "g", "ɡ") -- U+0261 LATIN SMALL LETTER SCRIPT G
text = rsub(text, "[ʧʤ]", {["ʧ"] = "t͡ʃ", ["ʤ"] = "d͡ʒ"})
text = rsub(text, "tʃ", "t͡ʃ")
text = rsub(text, "dʒ", "d͡ʒ")
text = rsub(text, "h", "")
text = rsub(text, "H", "h")
return text
end
-- Generate the IPA for a single term respelling `text` in the specified `style` ('gbr', 'rio', etc.; see
-- all_style_descs above). Return value is a list of objects of the following form:
-- { phonemic = STRING, phonetic = STRING, q = {STRING, ...} }
-- Note that the returned qualifiers are only those generated automatically as a result of certain characteristics of
-- the respelling, e.g. in Brazil initial em-/en- + consonant has two outputs, one labeled "逐個音節發音" and
-- the other "自然發音". User-specified qualifiers are added at the end by the caller of IPA(), and
-- prepended to the auto-generated qualifiers.
function export.IPA(text, style)
local brazil = m_table.contains(export.all_style_groups.br, style)
local portugal = m_table.contains(export.all_style_groups.pt, style)
local origtext = text
local function err(msg)
error(msg .. ": " .. origtext)
end
text = ulower(text)
-- decompose everything but ç and ü
text = mw.ustring.toNFD(text)
text = rsub(text, ".[" .. CEDILLA .. DIA .. "]", {
["c" .. CEDILLA] = "ç",
["u" .. DIA] = "ü",
})
text = reorder_accents(text, err)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
local words = m_strutils.capturing_split(text, "([ %-]+)")
local function word_is_prefix(i)
-- Check for prefixes, either a final prefix (followed by "-" separator, then a blank word, then no more
-- words) or a non-final prefix (followed by "- " separator).
return i == #words - 2 and words[i + 1] == "-" and words[i + 2] == "" or i < #words and words[i + 1] == "- "
end
for i = 1, #words, 2 do
local word = words[i]
-- Make prefixes unstressed with vowel reduction unless they have an explicit stress marker;
-- likewise for certain monosyllabic words (e.g. [[o]], [[se]], [[de]], etc.; also [[a]], [[das]], etc.
-- in Portugal) without stress marks.
if word_is_prefix(i) and not rfind(words[i], accent_c) or unstressed_words[word] or
portugal and unstressed_full_vowel_words_brazil[word] then
-- add DOTOVER to the last vowel not the first one, or we will mess up 'que' by
-- adding the DOTOVER after the 'u'
word = rsub(word, "^(.*" .. V .. quality_c .. "*)", "%1" .. DOTOVER)
end
-- Make certain monosyllabic words (e.g. [[meu]], [[com]]; also [[a]], [[das]], etc. in Brazil)
-- without stress marks be unstressed without vowel reduction.
if unstressed_full_vowel_words[word] or brazil and unstressed_full_vowel_words_brazil[word] then
-- add DOTUNDER to the first vowel not the last one, or we will mess up 'meu' by
-- adding the DOTUNDER after the 'u'; add after a quality marker for à, às
word = rsub(word, "^(.-" .. V .. quality_c .. "*)", "%1" .. DOTUNDER)
end
-- Some unstressed words need special pronunciation.
word = unstressed_pronunciation_substitution[word] or word
words[i] = word
end
text = table.concat(words)
-- Now eliminate word-final question mark and exclamation point (converted to foot boundary above when word-medial).
text = rsub(text, "[!?]", "")
-- Apostrophe becomes tie (e.g. in [[barriga d'agua]]).
text = rsub(text, "'", "‿")
-- User-specified # as in i# (= i. or y) and u# (= u. or w) becomes TEMP1 so we can add # for word boundaries.
text = rsub(text, "#", TEMP1)
-- Put # at word beginning and end and double ## at text/foot boundary beginning/end.
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
-- Eliminate hyphens indicating prefixes/suffixes; but preserve a marker indicating prefixes, so we can later
-- convert primary to secondary stress.
text = rsub(text, "(" .. V .. charsep_c .. "*)(%-#)", "%1" .. PSEUDOCONS .. "%2")
text = rsub(text, "%-#", "#" .. PREFIX_MARKER)
text = rsub(text, "#%-(" .. V .. ")", "#" .. PSEUDOCONS .. "%1")
text = rsub(text, "#%-", "#")
local variants
-- Map over each element in `variants`. If `from` is found in the element, replace the element with two elements, one
-- obtained by replacing `from` with `to1` and the other by replacing `from` with `to2`. If `to2` is nil, only one
-- element replaces the original element.
local function flatmap_and_sub_pre(from, to1, qual1, to2, qual2)
variants = flatmap(variants, function(item)
if rfind(item.respelling, from) then
local retval = {
{
respelling = rsub(item.respelling, from, to1),
q = combine_qualifiers(item.q, qual1),
}
}
if to2 then
table.insert(retval,
{
respelling = rsub(item.respelling, from, to2),
q = combine_qualifiers(item.q, qual2),
}
)
end
return retval
else
return {item}
end
end)
end
if brazil then
-- Remove grave accents and macrons, which have special meaning only for Portugal. Do this before handling o^
-- and similar so we can write áutò^:... and have it correctly give 'autò-' in Portugal but 'áutu-,áuto-' in
-- Brazil.
text = rsub(text, "[" .. GR .. MACRON .. "]", "")
elseif portugal then
-- Convert grave accents and macrons to explicit dot-under + quality marker.
local grave_macron_to_quality = {
[GR] = AC,
[MACRON] = CFLEX,
}
text = rsub(text, "[" .. GR .. MACRON .. "]", function(acc) return grave_macron_to_quality[acc] .. DOTUNDER end)
-- ê*/ô* -> é/ó and é*/ó* -> ê/ô (reverse accents)
text = rsub(text, "([eo])([" .. AC .. CFLEX .. "])%*", function(eo, acc)
return eo .. (acc == CFLEX and AC or CFLEX) end)
-- Treat vowel after des- as word-initial, as in Brazil.
text = rsub(text, "(" .. word_or_component_sep_c .. ")des%^+", "%1des++")
-- Remove i*, i^ and i^^ not followed by a vowel (i.e. Brazilian epenthetic i), but not i^ and i^^ followed or
-- preceded by a vowel (which has a totally different meaning, i.e. i or y in Brazil).
-- Also remove all remaining ^.
text = rsub(text, "i%^+(" .. V .. ")", "i%1")
text = rsub(text, "(" .. V .. ")i%^+", "%1i")
text = rsub(text, "i?[*%^]+", "")
end
variants = {{respelling = text}}
if brazil then
-- Handle i^ and i^^ before a vowel = /i/ or /j/.
flatmap_and_sub_pre("i%^%^(" .. V .. ")", "y%1", nil, "i.%1", nil)
flatmap_and_sub_pre("i%^(" .. V .. ")", "i.%1", nil, "y%1", nil)
-- Handle i^ and i^^ after a vowel = /i/ or /j/; mostly useful for ui^
flatmap_and_sub_pre("(" .. V .. ")i%^%^", "%1y", nil, "%1.i", nil)
flatmap_and_sub_pre("(" .. V .. ")i%^", "%1.i", nil, "%1y", nil)
-- Handle i^ and i^^ not before a vowel = optional epenthetic /i/.
if style == "sbr" then
-- Epenthetic /i/ seems less common in South Brazil. Make i^^ not epenthesize (but still palatalize /t/ and /d/),
-- and i^ epenthesize but not as the first option.
flatmap_and_sub_pre("i%^%^(" .. NV_NOT_SPACING_CFLEX .. ")", "Ɨ%1", nil)
flatmap_and_sub_pre("i%^(" .. NV_NOT_SPACING_CFLEX .. ")", "Ɨ%1", nil, "I%1", nil)
else
flatmap_and_sub_pre("i%^%^(" .. NV_NOT_SPACING_CFLEX .. ")", "Ɨ%1", nil, "I%1", nil)
flatmap_and_sub_pre("i%^(" .. NV_NOT_SPACING_CFLEX .. ")", "I%1", nil, "Ɨ%1", nil)
end
-- Handle i* = epenthetic /i/.
flatmap_and_sub_pre("i%*", "I", nil)
-- Handle u^ and u^^ = /u/ or /w/.
flatmap_and_sub_pre("u%^%^", "w", nil, "u.", nil)
flatmap_and_sub_pre("u%^", "u.", nil, "w", nil)
if style == "sbr" then
-- The raised variant apparently does not occur in South Brazil.
flatmap_and_sub_pre("([eo])%^+", "%1", nil)
else
-- Handle e^ and e^^ = /e/ or /i/; handle o^ and o^^ = /o/ or /u/.
-- Do e^ and o^ together so we get only two outputs, not four, if they cooccur.
-- Likewise for e^^ and o^^.
flatmap_and_sub_pre("([eo])%^%^",
function(eo) return eo == "e" and "i" or "u" end, nil,
"%1", nil)
flatmap_and_sub_pre("([eo])%^",
"%1", nil,
function(eo) return eo == "e" and "i" or "u" end, nil)
end
-- Handle ê*/ô*/é*/ó* = same as without asterisk.
flatmap_and_sub_pre("([eo][" .. AC .. CFLEX .. "])%*", "%1", nil)
-- Handle des^ at beginning of word or component = des++ or dis++, and des^^ = opposite order. But apparently
-- not in South Brazil, where the raised variant doesn't occur.
if style == "sbr" then
flatmap_and_sub_pre("(" .. word_or_component_sep_c .. ")des%^+", "%1des++", nil)
else
flatmap_and_sub_pre("(" .. word_or_component_sep_c .. ")des%^%^", "%1dis++", nil, "%1des++", nil)
flatmap_and_sub_pre("(" .. word_or_component_sep_c .. ")des%^", "%1des++", nil, "%1dis++", nil)
end
for _, variant in ipairs(variants) do
if rfind(variant.respelling, "[*%^]") then
err(("* or ^ remains after applying all known replacements involving these characters (result is '%s')")
:format(variant.respelling))
end
end
end
-- Replace i# and u# sequences (above we replaced # with TEMP1).
flatmap_and_sub_pre("i" .. TEMP1, "i.", nil, "y", {"快速發音"})
flatmap_and_sub_pre("u" .. TEMP1, "u.", nil, "w", {"快速發音"})
-- Given a single variant element representing a preprocessed respelling along with any qualifiers resulting from the
-- preprocessing, generate the phonemic and phonetic representations using one_term_ipa() and postprocess to get the
-- final IPA. The postprocessing is there in general to handle cases where a single respelling produces multiple
-- outputs, such as Brazil -io producing either /i.u/ or /iw/. Note that user-specified qualifiers are not yet present
-- at any stage of this IPA generation; they are added at the end by the caller of IPA().
local function call_one_term_ipa(variant)
local result = {{
phonemic = one_term_ipa(variant.respelling, style, false, err),
phonetic = one_term_ipa(variant.respelling, style, true, err),
q = variant.q,
}}
local function unpack_if_list(obj)
if type(obj) == "table" and #obj == 2 and obj[1] then
return unpack(obj)
else
return obj, obj
end
end
-- Map over each element in `result`. If `from` is found in the element, replace the element with two elements, one
-- obtained by replacing `from` with `to1` in both the phonemic and phonetic representations of the existing element,
-- and the other similarly by replacing `from` with `to2` in both the phonemic and phonetic representations. If `to2`
-- is nil, only one element replaces the original element. `qual1`, if non-nil, is a list of qualifiers to be added to
-- the new element associated with `to1` (appended to any existing qualifiers). Similarly, `qual2` is a list of
-- qualifiers to be added to the new element associated with `to2`. Normally, `to1` and `to2` can be anything that can
-- be used as the replacement argument to the Lua gsub() function, i.e. a string, a function or a table. However, if
-- `to1` or `to2` is a two-element list, it is unpacked into two separate substitution objects, respectively for the
-- phonemic and phonetic representations of the element being substituted. This is used, for example, when handling
-- Ú resulting from stressed final '-io(s)', so different phonemic vs. phonetic replacements can be used (/w/ vs [ʊ̯]).
local function flatmap_and_sub_post(from, to1, qual1, to2, qual2)
result = flatmap(result, function(item)
if rfind(item.phonemic, from) or rfind(item.phonetic, from) then
local to1_phonemic, to1_phonetic = unpack_if_list(to1)
local retval = {
{
phonemic = rsub(item.phonemic, from, to1_phonemic),
phonetic = rsub(item.phonetic, from, to1_phonetic),
q = combine_qualifiers(item.q, qual1),
}
}
if to2 then
local to2_phonemic, to2_phonetic = unpack_if_list(to2)
table.insert(retval,
{
phonemic = rsub(item.phonemic, from, to2_phonemic),
phonetic = rsub(item.phonetic, from, to2_phonetic),
q = combine_qualifiers(item.q, qual2),
}
)
end
return retval
else
return {item}
end
end)
end
if brazil then
-- Convert Ẽ from initial [[em]] as a word by itself to either /ẽj̃/ and /ĩ/.
flatmap_and_sub_post("Ẽ" .. TILDE .. "#", "e" .. TILDE .. "j" .. TILDE .. "#", {"逐個音節發音"},
"i" .. TILDE .. "#", {"自然發音"})
-- Convert Ẽ from initial em-/en- + consonant to either /ẽ/ and /ĩ/.
flatmap_and_sub_post("Ẽ", "e", {"逐個音節發音"}, "i", {"自然發音"})
flatmap_and_sub_post("I", "i", nil, "e", nil)
-- Convert Ú resulting from stressed final '-io(s)'.
flatmap_and_sub_post("%.Ú", ".u", nil, {"w", "ʊ̯"}, nil)
elseif portugal then
flatmap_and_sub_post("ʃ(" .. wordsep_c .. "*)s",
"ʃ%1s", {"逐個音節發音"}, "%1s" , nil , "%1ʃ", nil)
flatmap_and_sub_post("ʒ(" .. wordsep_c .. "*)z",
"ʒ%1z", {"逐個音節發音"}, "%1z" , nil, "%1ʒ", nil)
flatmap_and_sub_post("Ɔ", "o", nil, "ɔ", nil)
-- Split (ɨ) after l or r into two pronuns, one with ɨ and the other without it (with one fewer syllables and coda).
--flatmap_and_sub_post("%.([^.# ]-)L#", ".%1lɨ#", nil, {"%1l#", "%1ɫ#"}, nil)
--flatmap_and_sub_post("%.([^.# ]-)R#", ".%1ɾɨ#", nil, {"%1ɾ#", "%1ɾ#"}, nil)
end
flatmap_and_sub_post("([ÌÙ])%.",
function(iu) return iu == "Ì" and "i." or "u." end, nil,
function(iu) return iu == "Ì" and "j" or "w" end, {"快速發音"})
return result
end
-- Final changes to the generated IPA to produce what's shown to the user. We used to do this at the end of
-- one_term_ipa() but the stuff below needs to happen after the expansion of Ì. and Ù. in Brazil to either i./u.
-- or j/w, because the latter transformation involves removing a syllable boundary, which will cause a stress mark
-- on the following syllable to retract to the beginning of the newly combined syllable. To avoid lots of hassle,
-- we postpone this stress mark movement till now.
local function finalize_ipa(text, phonetic)
-- Convert Brazil i/u in hiatus to ɪ/ʊ in the phonetic representation. This needs to happen after handling of
-- Ì. and Ù., which feeds this change.
if brazil and phonetic then
local phonetic_hiatus_iu_to_actual = {["i"] = "ɪ", ["u"] = "ʊ"}
text = rsub(text, "([iu])(%." .. V .. ")", function(iu, after) return phonetic_hiatus_iu_to_actual[iu] .. after end)
end
-- Stress marks and syllable dividers.
-- Component separators that aren't transparent to syllabification need to be made into syllable dividers.
text = rsub(text, non_syl_transp_component_sep_c, ".")
-- IPA stress marks in components followed by + should be removed.
text = rsub(text, ipa_stress_c .. "([^" .. word_divider .. component_sep .. "]*%+)", "%1")
-- Component separators that are transparent to syllabification need to be removed now, before moving IPA stress marks
-- to the beginning of the syllable, so they don't interfere in this process.
text = rsub(text, syl_transp_component_sep_c .. "#?", "")
-- Move IPA stress marks to the beginning of the syllable.
text = rsub_repeatedly(text, "([#.])([^#.]*)(" .. ipa_stress_c .. ")", "%1%3%2")
-- Suppress syllable divider before IPA stress indicator.
text = rsub(text, "%.(#?" .. ipa_stress_c .. ")", "%1")
-- Make all primary stresses but the last one in a given word be secondary. May be fed by the first rule above.
text = rsub_repeatedly(text, "ˈ([^ ]+)ˈ", "ˌ%1ˈ")
-- Make primary stresses in prefixes become secondary.
text = rsub_repeatedly(text, "ˈ([^#]*#" .. PREFIX_MARKER .. ")", "ˌ%1")
-- Remove # symbols at word/text boundaries, as well as _ (which forces separate interpretation), pseudo-consonant
-- markers (at edges of some prefixes/suffixes), and prefix markers, and recompose.
text = rsub(text, "[#_" .. PSEUDOCONS .. PREFIX_MARKER .. "]", "")
text = mw.ustring.toNFC(text)
return text
end
variants = flatmap(variants, call_one_term_ipa)
for i, variant in ipairs(variants) do
variants[i].phonemic = finalize_ipa(variants[i].phonemic, false)
variants[i].phonetic = finalize_ipa(variants[i].phonetic, true)
end
return variants
end
-- For bot usage; {{#invoke:pt-pronunc|IPA_json|SPELLING|style=STYLE}}
-- where
--
-- 1. SPELLING is the word or respelling to generate pronunciation for;
-- 2. required parameter style= indicates the pronunciation style to generate
-- (e.g. "rio" for Rio/Carioca pronunciation, "lisbon" for Lisbon pronunciation;
-- see the comment above export.IPA() above for the full list);
-- 3. phonetic=1 specifies to generate the phonetic rather than phonemic pronunciation;
function export.IPA_json(frame)
local iparams = {
[1] = {},
["style"] = {required = true},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local pronuns = export.IPA(iargs[1], iargs.style)
return require("Module:JSON").toJSON(pronuns)
end
-- "Canonicalize" a single respelling (after splitting multiple respellings on comma and parsing off inline
-- modifiers). This currently handles '+' and substitution notation.
function export.canonicalize_respelling(text, pagename)
if not text or text == "+" then
text = pagename
end
-- No such substitutions currently.
-- text = rsub(text, "%[([hHxXjJ])%]", function(sound)
-- return explicit_sound_to_substitution[ulower(sound)]
-- end)
-- Implement substitution notation.
if rfind(text, "^%[.*%]$") then
local subs = rsplit(rmatch(text, "^%[(.*)%]$"), ";")
text = pagename
for _, sub in ipairs(subs) do
local fromto = rsplit(sub, ":")
if #fromto < 2 then
error("Bad substitution spec " .. sub .. " in {{pt-IPA}}, should have a colon in it")
end
local from, to
if #fromto == 2 then
from, to = fromto[1], fromto[2]
else
from = fromto[1]
table.remove(fromto, 1)
to = table.concat(fromto, ":")
end
local newtext = text
if rfind(from, "^%^") then
-- whole-word match
from = rmatch(from, "^%^(.*)$")
newtext = rsub(text, "%f[%a]" .. require("Module:string utilities").pattern_escape(from) .. "%f[%A]", to)
else
newtext = rsub(text, require("Module:string utilities").pattern_escape(from), to)
end
if from ~= to and newtext == text then
error("Substitution spec " .. sub .. " didn't match respelling '" .. text .. "'")
end
text = newtext
end
end
return text
end
function export.express_styles(inputs, args_style, pagename)
local pronuns_by_style = {}
local expressed_styles = {}
local function dostyle(style)
pronuns_by_style[style] = {}
for _, val in ipairs(inputs[style].terms) do
local respelling = val.term
respelling = export.canonicalize_respelling(respelling, pagename)
local refs
if #val.ref == 0 then
refs = nil
else
refs = {}
for _, refspec in ipairs(val.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
local pronuns = export.IPA(respelling, style)
for _, pronun in ipairs(pronuns) do
local qualifiers = m_table.deepCopy(val.q)
if pronun.q then
for _, qual in ipairs(pronun.q) do
m_table.insertIfNot(qualifiers, qual)
end
end
pronun.q = #qualifiers > 0 and qualifiers or nil
pronun.refs = refs
m_table.insertIfNot(pronuns_by_style[style], pronun)
end
end
end
local function all_available(styles)
local available_styles = {}
for _, style in ipairs(styles) do
if pronuns_by_style[style] then
table.insert(available_styles, style)
end
end
return available_styles
end
local function express_style(styles, indent)
local hidden_tag, tag
indent = indent or 1
if type(styles) == "string" then
styles = {styles}
tag = export.all_style_descs[styles[1]]
hidden_tag = export.all_style_descs[style_to_style_group[styles[1]]]
else
tag = false
hidden_tag = false
end
styles = all_available(styles)
if #styles == 0 then
return
end
local style = styles[1]
-- If style specified, make sure it matches the requested style.
local style_matches
if not args_style then
style_matches = true
else
local or_styles = rsplit(args_style, "%s*,%s*")
for _, or_style in ipairs(or_styles) do
local and_styles = rsplit(or_style, "%s*%+%s*")
local and_matches = true
for _, and_style in ipairs(and_styles) do
local negate
if and_style:find("^%-") then
and_style = and_style:gsub("^%-", "")
negate = true
end
local this_style_matches = false
for _, part in ipairs(styles) do
if part == and_style then
this_style_matches = true
break
end
end
if negate then
this_style_matches = not this_style_matches
end
if not this_style_matches then
and_matches = false
end
end
if and_matches then
style_matches = true
break
end
end
end
if not style_matches then
return
end
local new_style = {
tag = tag,
represented_styles = styles,
pronuns = pronuns_by_style[style],
indent = indent,
bullets = inputs[style].bullets,
pre = inputs[style].pre,
post = inputs[style].post,
}
for _, hidden_tag_style in ipairs(expressed_styles) do
if hidden_tag_style.tag == hidden_tag then
table.insert(hidden_tag_style.styles, new_style)
return
end
end
table.insert(expressed_styles, {
tag = hidden_tag,
styles = {new_style},
})
end
for style, _ in pairs(inputs) do
dostyle(style)
end
local function diff(style1, style2)
if not pronuns_by_style[style1] or not pronuns_by_style[style2] then
return true
end
return not m_table.deepEquals(pronuns_by_style[style1], pronuns_by_style[style2])
end
local gbr_sp_different = diff("gbr", "sp")
local gbr_rio_different = diff("gbr", "rio")
local gbr_sbr_different = diff("gbr", "sbr")
local gpt_cpt_different = diff("gpt", "cpt")
local gpt_spt_different = diff("gpt", "spt")
local gpt_npt_different = diff("gpt", "npt")
local gbr_gpt_different = diff("gbr", "gpt") -- general differences between BP and EP
if not gbr_sp_different and not gbr_rio_different and gbr_sbr_different and
not gpt_cpt_different and not gpt_spt_different and not gpt_npt_different and
not gbr_gpt_different then
-- All the same
express_style(export.all_styles)
else
-- Within Brazil
express_style("gbr")
if gbr_sp_different then
express_style("sp", 2)
end
if gbr_rio_different then
express_style("rio", 2)
end
if gbr_sbr_different then
express_style("sbr", 2)
end
-- Within Portugal
express_style("gpt")
if gpt_npt_different then
express_style("npt", 2)
end
if gpt_cpt_different then
express_style("cpt", 2)
end
if gpt_spt_different then
express_style("spt", 2)
end
end
return expressed_styles
end
function export.show(frame)
-- Create parameter specs
local params = {
[1] = {}, -- this replaces style group 'all'
["style"] = {},
["pagename"] = {},
}
for group, _ in pairs(export.all_style_groups) do
if group ~= "all" then
params[group] = {}
end
end
for _, style in ipairs(export.all_styles) do
params[style] = {}
end
-- Parse arguments
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Set inputs
local inputs = {}
-- If 1= specified, do all styles.
if args[1] then
for _, style in ipairs(export.all_styles) do
inputs[style] = args[1]
end
end
-- Then do remaining style groups other than 'all', overriding 1= if given.
for group, styles in pairs(export.all_style_groups) do
if group ~= "all" and args[group] then
for _, style in ipairs(styles) do
inputs[style] = args[group]
end
end
end
-- Then do individual style settings.
for _, style in ipairs(export.all_styles) do
if args[style] then
inputs[style] = args[style]
end
end
-- If no inputs given, set all styles based on current pagename.
if not next(inputs) then
for _, style in ipairs(export.all_styles) do
inputs[style] = "+"
end
end
-- Parse the arguments.
local put
for style, input in pairs(inputs) do
if input:find("[<%[]") then
local function parse_err(msg)
error(msg .. ": " .. style .. "=" .. input)
end
if not put then
put = require("Module:parse utilities")
end
-- We don't want to split off a comma followed by a space, as in [[rei morto, rei posto]], so replace
-- comma+space with a special character that we later undo.
input = rsub(input, ", ", TEMP1)
-- Parse balanced segment runs involving either [...] (substitution notation) or <...> (inline modifiers). We do this
-- because we don't want commas inside of square or angle brackets to count as respelling delimiters. However, we
-- need to rejoin square-bracketed segments with nearby ones after splitting alternating runs on comma. For example,
-- if we are given "a[x]a<q:learned>,[vol:vôl;ei:éi,ei]<q:nonstandard>", after calling
-- parse_multi_delimiter_balanced_segment_run() we get the following output:
--
-- {"a", "[x]", "a", "<q:learned>", ",", "[vol:vôl;ei:éi,ei]", "", "<q:nonstandard>", ""}
--
-- After calling split_alternating_runs(), we get the following:
--
-- {{"a", "[x]", "a", "<q:learned>", ""}, {"", "[vol:vôl;ei:éi,ei]", "", "<q:nonstandard>", ""}}
--
-- We need to rejoin stuff on either side of the square-bracketed portions.
local segments = put.parse_multi_delimiter_balanced_segment_run(input, {{"<", ">"}, {"[", "]"}})
-- Not with spaces around the comma; see above for why we don't want to split off comma followed by space.
local comma_separated_groups = put.split_alternating_runs(segments, ",")
local parsed = {terms = {}}
for i, group in ipairs(comma_separated_groups) do
-- Rejoin bracketed segments with nearby ones, as described above.
local j = 2
while j <= #group do
if group[j]:find("^%[") then
group[j - 1] = group[j - 1] .. group[j] .. group[j + 1]
table.remove(group, j)
table.remove(group, j)
else
j = j + 2
end
end
for j, segment in ipairs(group) do
group[j] = rsub(segment, TEMP1, ", ")
end
local term = {term = group[1], ref = {}, q = {}}
for j = 2, #group - 1, 2 do
if group[j + 1] ~= "" then
parse_err("Extraneous text '" .. group[j + 1] .. "' after modifier")
end
local modtext = group[j]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. group[j] .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+):(.*)$")
if not prefix then
parse_err("Modifier " .. group[j] .. " lacks a prefix, should begin with one of " ..
"'pre:', 'post:', 'ref:', 'bullets:' or 'q:'")
end
if prefix == "ref" or prefix == "q" then
table.insert(term[prefix], arg)
elseif prefix == "pre" or prefix == "post" or prefix == "bullets" then
if i < #comma_separated_groups then
parse_err("Modifier '" .. prefix .. "' should occur after the last comma-separated term")
end
if parsed[prefix] then
parse_err("Modifier '" .. prefix .. "' occurs twice, second occurrence " .. group[j])
end
if prefix == "bullets" then
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument")
end
parsed.bullets = tonumber(arg)
else
parsed[prefix] = arg
end
else
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[j]
.. ", should be one of 'pre', 'post', 'ref', 'bullets' or 'q'")
end
end
table.insert(parsed.terms, term)
end
if not parsed.bullets then
parsed.bullets = 1
end
inputs[style] = parsed
else
local terms = {}
-- We don't want to split on comma+space, which should become a foot boundary as in
-- [[rei morto, rei posto]].
local subbed_input = rsub(input, ", ", TEMP1)
for _, term in ipairs(rsplit(subbed_input, ",")) do
term = rsub(term, TEMP1, ", ")
table.insert(terms, {term = term, ref = {}, q = {}})
end
inputs[style] = {
terms = terms,
bullets = 1,
}
end
end
local expressed_styles = export.express_styles(inputs, args.style, pagename)
local lines = {}
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
-- Loop through each pronunciation. For each one, add the phonemic and (if different) phonetic versions to
-- `pronunciations`, for formatting by [[Module:IPA]], and also create an approximation of the formatted
-- version so that we can compute the appropriate width of the HTML switcher div box that holds the different
-- per-dialect variants.
for i, pronun in ipairs(expressed_style.pronuns) do
table.insert(pronunciations, {
pron = "/" .. pronun.phonemic .. "/",
q = pronun.q,
separator = i > 1 and ", " or nil,
})
local formatted_phonemic = "/" .. pronun.phonemic .. "/"
if pronun.q then
formatted_phonemic = "(" .. table.concat(pronun.q, ", ") .. ") " .. formatted_phonemic
end
if i > 1 then
formatted_phonemic = ", " .. formatted_phonemic
end
table.insert(formatted_pronuns, formatted_phonemic)
-- Check if phonetic and phonemic are the same. If so, we skip displaying the phonetic version; but in this
-- case, we need to attach any references to the phonemic version.
if pronun.phonetic == pronun.phonemic then
pronunciations[#pronunciations].refs = pronun.refs
else
table.insert(pronunciations, {
pron = "[" .. pronun.phonetic .. "]",
refs = pronun.refs,
separator = " ",
})
local reftext = ""
if pronun.refs then
reftext = string.rep("[1]", #pronun.refs)
end
table.insert(formatted_pronuns, " [" .. pronun.phonetic .. "]" .. reftext)
end
end
-- Number of bullets: When indent = 1, we want the number of bullets given by `expressed_style.bullets`,
-- and when indent = 2, we want `expressed_style.bullets + 1`, hence we subtract 1.
local bullet = string.rep("*", expressed_style.bullets + expressed_style.indent - 1) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and expressed_style.pre and expressed_style.pre .. " " or ""
pre = pre .. (tag and m_qual.format_qualifier(tag) .. " " or "")
local post = is_first and (expressed_style.post and " " .. expressed_style.post or "") or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, formatted_for_len
end
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
style_group.formatted, style_group.formatted_for_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
else
style_group.formatted, style_group.formatted_for_len =
format_style(style_group.tag, style_group.styles[1], i == 1)
for j, style in ipairs(style_group.styles) do
style.formatted, style.formatted_for_len =
format_style(style.tag, style, i == 1 and j == 1)
end
end
end
-- Remove any HTML from the formatted text, since it doesn't contribute to the textual length, and return the
-- resulting length in characters.
local function textual_len(text)
text = rsub(text, "<.->", "")
return ulen(text)
end
local maxlen = 0
for i, style_group in ipairs(expressed_styles) do
local this_len = textual_len(style_group.formatted_for_len)
if #style_group.styles > 1 then
for _, style in ipairs(style_group.styles) do
this_len = math.max(this_len, textual_len(style.formatted_for_len))
end
end
maxlen = math.max(maxlen, this_len)
end
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
table.insert(lines, "<div>\n" .. style_group.formatted .. "</div>")
else
local inline = '\n<div class="vsShow" style="display:none">\n' .. style_group.formatted .. "</div>"
local full_prons = {}
for _, style in ipairs(style_group.styles) do
table.insert(full_prons, style.formatted)
end
local full = '\n<div class="vsHide">\n' .. table.concat(full_prons, "\n") .. "</div>"
local em_length = math.floor(maxlen * 0.68) -- from [[Module:grc-pronunciation]]
table.insert(lines, '<div class="vsSwitcher" data-toggle-category="pronunciations" style="width: ' .. em_length .. 'em; max-width:100%;"><span class="vsToggleElement" style="float: right;"> </span>' .. inline .. full .. "</div>")
end
end
-- major hack to get bullets working on the next line
return table.concat(lines, "\n") .. "\n<span></span>"
end
return export
nbjhblqkqx3kmdimuwsritehowubcq0
Module:Zh/data/och-pron-ZS/嫢
828
1658520
9909235
6267710
2026-09-29T11:37:29Z
TongcyBot
83009
同步英語維基詞典
9909235
Scribunto
text/plain
return {
{ "4602", "規", "元", "2", "峴", "ɡeːnʔ", "" },
{ "4603", "規", "歌", "0", "", "skʷel", "見方言。規由宜校改" },
{ "4612", "規", "脂", "1", "揆", "ɡʷilʔ", "" },
}
mx66q8e41r92n5j77monlreoetg3c2b
Module:Zh/data/och-pron-ZS/屑
828
1658766
9909236
6267961
2026-09-29T11:37:31Z
TongcyBot
83009
同步英語維基詞典
9909236
Scribunto
text/plain
return {
{ "15063", "佾", "質", "1", "屑", "slɯːd", "" },
{ "15066", "佾", "物", "1", "窣", "sluːd", "原文⺌作八,見方言" },
}
rncgro3rk55espj34vw69jhr3cxci68
Module:Zh/data/och-pron-ZS/蘊
828
1665850
9909237
6275147
2026-09-29T11:37:33Z
TongcyBot
83009
同步英語維基詞典
9909237
Scribunto
text/plain
return {
{ "12986", "昷", "文", "2", "煴", "qun", "" },
{ "12992", "昷", "文", "2", "惲", "qunʔ", "俗薀字" },
{ "12998", "昷", "文", "2", "醖", "quns", "" },
}
e5ovlaep60ibbkwbvtnbxwkbipqizww
Module:Zh/data/och-pron-ZS/鑑
828
1668304
9909238
6277731
2026-09-29T11:37:34Z
TongcyBot
83009
同步英語維基詞典
9909238
Scribunto
text/plain
return require [[Module:zh/data/och-pron-ZS/鑒]]
t0e3jsuoclbgqxqnk9xatwwla6yvblu
Module:Zh/data/och-pron-ZS/陂
828
1668434
9909239
6277861
2026-09-29T11:37:36Z
TongcyBot
83009
同步英語維基詞典
9909239
Scribunto
text/plain
return {
{ "9688", "皮", "歌", "1", "陂", "pral", "" },
{ "9696", "皮", "歌", "1", "賁", "prals", "" },
{ "9728", "皮", "歌", "1", "頗", "pʰaːl", "" },
}
12lft1os9hrc2q2sxqwre7rensl1o4d
Module:Zh/data/och-pron-ZS/鱯
828
1669439
9909240
6278866
2026-09-29T11:37:38Z
TongcyBot
83009
同步英語維基詞典
9909240
Scribunto
text/plain
return {
{ "5575", "蒦", "暮", "0", "摦", "ɢʷraːɡs", "" },
{ "5580", "蒦", "暮", "0", "護", "ɢʷraːɡs", "" },
{ "5603", "蒦", "鐸", "0", "獲", "ɢʷraːɡ", "" },
}
fc0jg1kktsy84hiw6xl7r8j9va3rbj8
Module:Zh/data/och-pron-BS/偽
828
1676719
9909252
6288691
2026-09-29T11:38:31Z
TongcyBot
83009
同步英語維基詞典
9909252
Scribunto
text/plain
return {
{ "wěi", "ngjweH", "*N-ɢʷ(r)aj-s", "false" },
{ "wěi", "ngjweH", "*m-ɢʷ(r)aj-s", "falsify; deceive" },
}
b2mrn5csrjs0xdlvxymckkuqtuyrv8m
Module:Zh/data/och-pron-BS/冷
828
1676809
9909253
6288781
2026-09-29T11:38:33Z
TongcyBot
83009
同步英語維基詞典
9909253
Scribunto
text/plain
return {
{ "lěng", "lengX", "*[r]ˤeŋʔ", "cold (Sagart & Ma, 2020)" },
}
cfgegvwmz8elb515kfk17sdkii44tfb
Module:Zh/data/och-pron-BS/堅
828
1677159
9909254
6289131
2026-09-29T11:38:35Z
TongcyBot
83009
同步英語維基詞典
9909254
Scribunto
text/plain
return {
{ "jiān", "ken", "*kˤi[ŋ]", "solid, strong" },
}
d4bs690z0nhwgx4rv9skv8d42uoy5wg
Module:Zh/data/och-pron-BS/夤
828
1677224
9909255
6289196
2026-09-29T11:38:36Z
TongcyBot
83009
同步英語維基詞典
9909255
Scribunto
text/plain
return {
{ "yín", "yin", "*[ɢ](r)ə[r]", "small of the back, region of the reins" },
}
or0ryfi4z1ykq9abmjta7oghswew4yl
Module:Zh/data/och-pron-BS/幹
828
1677503
9909256
6289475
2026-09-29T11:38:38Z
TongcyBot
83009
同步英語維基詞典
9909256
Scribunto
text/plain
return {
{ "gàn", "kanH", "*[k]ˤar-s", "stem; framework" },
{ "hán", "han", "*[ɡ]ˤar", "wall of a well" },
}
rxbg7ui1tdy2xcf5ownfsbf9ji75872
Module:Zh/data/och-pron-BS/廿
828
1677544
9909257
6289516
2026-09-29T11:38:40Z
TongcyBot
83009
同步英語維基詞典
9909257
Scribunto
text/plain
return {
{ "niàn", "nyip", "*n[ə]p", "twenty (fused variant of 二十) (Sagart & Ma, 2020)" },
}
k8m6f0cj2ckdnltea6nn7o2uorjgbaz
Module:Zh/data/och-pron-BS/抑
828
1677786
9909258
6289758
2026-09-29T11:38:42Z
TongcyBot
83009
同步英語維基詞典
9909258
Scribunto
text/plain
return {
{ "yì", "ʔik", "*[ʔ](r)ik", "and, but, or" },
{ "yì", "ʔik", "*[ʔ](r)ik", "rub, repress" },
}
6356mmkhbibh0nkuitbxt43jk1f7a49
Module:Zh/data/och-pron-BS/杖
828
1678071
9909259
6290043
2026-09-29T11:38:44Z
TongcyBot
83009
同步英語維基詞典
9909259
Scribunto
text/plain
return {
{ "zhàng", "drjangX", "*m-[t]raŋʔ", "stick (Sagart & Ma, 2020)" },
}
5qaf94omvb3mvno5py2svmueq7tznc6
Module:Zh/data/och-pron-BS/窄
828
1678908
9909260
6290880
2026-09-29T11:38:45Z
TongcyBot
83009
同步英語維基詞典
9909260
Scribunto
text/plain
return {
{ "zhǎi", "tsraek", "*[ts]ˤrak", "narrow (Sagart & Ma, 2020)" },
}
qu1wplddoq6811bmdfqafieq451479s
Module:Zh/data/och-pron-BS/纓
828
1679123
9909261
6291095
2026-09-29T11:38:47Z
TongcyBot
83009
同步英語維基詞典
9909261
Scribunto
text/plain
return {
{ "yīng", "ʔjieng", "*ʔeŋ", "ribbon" },
}
dh7s908p9xt2d07akiciid2hyd3xrkc
Module:Zh/data/och-pron-BS/蚼
828
1679513
9909262
6291485
2026-09-29T11:38:49Z
TongcyBot
83009
同步英語維基詞典
9909262
Scribunto
text/plain
return {
{ "", "xuwX", "*qʰˤ(r)oʔ", "ant (Sagart & Ma, 2020)" },
}
ntaflaj3dejoxnkzli6u58wprkf85zx
Module:Zh/data/och-pron-BS/鈶
828
1680084
9909263
6292056
2026-09-29T11:38:51Z
TongcyBot
83009
同步英語維基詞典
9909263
Scribunto
text/plain
return {
{ "yí", "ziX", "*sə-ləʔ", "handle of plough or sickle" },
}
k3n6mju9sv36ssgeuui8ysergps6z7k
Module:Ast-IPA
828
1684480
9909046
9162136
2026-09-29T07:24:35Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909046
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local decorations_module = "Module:decorations"
local put_module = "Module:parse utilities"
local parameters_module = "Module:parameters"
local force_cat = false -- for testing
local lang = require("Module:languages").getByCode("ast")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local DOT = u(0x0323) --underdot
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeiouüAEIOUÜ" -- vowel; include y so we get single-word y correct and for syllabifying from spelling
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR .. CFLEX
local accent_c = "[" .. accent .. "]"
local stress = AC .. GR
local stress_c = "[" .. AC .. GR .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class including h
local C_NOT_H = "[^" .. vowel .. separator .. "h]" -- consonant class not including h
local C_OR_WORDSEP = "[^" .. vowel .. separator_not_wordsep .. "]" -- consonant class including h, or word separator
local T = "[^" .. vowel .. "lrɾjw" .. separator .. "]" -- obstruent or nasal
local unstressed_words = require("Module:table").listToSet({ --feel free to add more unstressed words
"el", "la", "les", "lo", "los", -- definite articles
"que", "quien", "onde", "au", "le", "nos", "os", -- relative pronouns
"mos", "nos", "so", "te", -- unstressed pronouns
"y", "ya", "o", -- conjunctions
"con", "a", "de", "en", "pa", "per", "por", "sin", "tres" --prepositions
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ḷ, , ñ and ü
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. DIA .. DOT .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
["l" .. DOT] = "ḷ",
["L" .. DOT] = "Ḷ",
["h" .. DOT] = "ḥ",
["H" .. DOT] = "Ḥ"
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text, is_spelling)
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels (but don't divide a VhV
-- sequence; [[prohibir]] should be prohi.bir). Then move the syllable division marker leftwards over clusters that
-- can form onsets.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C_NOT_H .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
-- NOTE: When run on pronun, we have already eliminated c and v, but not when run on spelling.
-- When run on pronun, don't include r, which at this point represents the trill.
local cluster_r = is_spelling and "rɾ" or "ɾ"
-- Don't divide Cl or Cr where C is a stop or fricative, except for dl.
text = rsub(text, "([pbfvkctg])%.([l" .. cluster_r .. "])", ".%1%2")
text = text:gsub("d%.([" .. cluster_r .. "])", ".d%1")
-- Don't divide ch, sh, ph, th, dh, fh, kh or gh. Do allow bh to be divided ([[subhumano]], [[subhúmedo]], etc.).
text = rsub(text, "([csptdfkg])%.h", ".%1h")
-- Don't divide rr.
text = rsub(text, "r.r", ".rr")
-- Part 2: Divide hiatuses.
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?[aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub(text, "([iuüIUÜ]" .. stress_c .. ")(h?[aeoiu])", "%1.%2")
text = rsub_repeatedly(text, "([iuüIUÜ]" .. stress_c .. ")(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "([iI]" .. accent_c .. "*)(h?i)", "%1.%2")
text = rsub_repeatedly(text, "([uU]" .. accent_c .. "*)(h?u)", "%1.%2")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants.
local TEMP_QU = u(0xFFF4)
local TEMP_QU_CAPS = u(0xFFF5)
local TEMP_GU = u(0xFFF6)
local TEMP_GU_CAPS = u(0xFFF7)
local TEMP_H = u(0xFFF8)
local TEMP_CV = u(0x0400)
local TEMP_CV_CAPS = u(0x0401)
local TEMP_TS = u(0x0402)
local TEMP_TS_CAPS = u(0x0403)
local TEMP_YY = u(0x0404)
local TEMP_YY_CAPS = u(0x0405)
local TEMP_LL = u(0xFFFB)
local TEMP_LL_CAPS = u(0xFFFC)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = rsub(text, "ḷḷ", TEMP_CV)
text = rsub(text, "Ḷḷ", TEMP_CV_CAPS)
text = rsub(text, "ts", TEMP_TS)
text = rsub(text, "Ts", TEMP_TS_CAPS)
text = rsub(text, "yy", TEMP_YY)
text = rsub(text, "Yy", TEMP_YY_CAPS)
text = rsub(text, "ll", TEMP_LL)
text = rsub(text, "Ll", TEMP_LL_CAPS)
text = text:gsub("%.", SYLDIV)
-- We don't want to break -sh- except in desh-, e.g. [[deshuesar]], [[deshonra]], [[deshecho]]. Normally, -sh- is
-- automatically preserved, so we replace the h with a temporary symbol to avoid this.
text = text:gsub("^([Dd]es)h", "%1" .. TEMP_H)
text = text:gsub("([ %-][Dd]es)h", "%1" .. TEMP_H)
-- qu mostly handled correctly automatically, but not in quietud
text = rsub(text, "qu(" .. V .. ")", TEMP_QU .. "%1")
text = rsub(text, "Qu(" .. V .. ")", TEMP_QU_CAPS .. "%1")
text = rsub(text, "gu(" .. V .. ")", TEMP_GU .. "%1")
text = rsub(text, "Gu(" .. V .. ")", TEMP_GU_CAPS .. "%1")
local vowel_to_glide = { ["i"] = TEMP_I, ["u"] = TEMP_U }
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*)(h?)([iu])(" .. V .. ")",
function (v1, h, iu, v2) return v1 .. "." .. h .. vowel_to_glide[iu] .. v2 end
)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
text = text:gsub(SYLDIV, ".")
text = text:gsub(TEMP_QU, "qu")
text = text:gsub(TEMP_QU_CAPS, "Qu")
text = text:gsub(TEMP_GU, "gu")
text = text:gsub(TEMP_GU_CAPS, "Gu")
text = text:gsub(TEMP_H, "h")
text = text:gsub(TEMP_CV, "ḷḷ")
text = text:gsub(TEMP_CV_CAPS, "Ḷḷ")
text = text:gsub(TEMP_TS, "ts")
text = text:gsub(TEMP_TS_CAPS, "Ts")
text = text:gsub(TEMP_YY, "yy")
text = text:gsub(TEMP_YY_CAPS, "Yy")
text = text:gsub(TEMP_LL, "ll")
text = text:gsub(TEMP_LL_CAPS, "Ll")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text, phonetic)
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ñ and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain monosyllabic words unstressed
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") and not rfind(word, accent_c) or unstressed_words[word] then
-- add CFLEX to the last vowel not the first one
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CFLEX)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
text = rsub(text, "@", "-")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--determining whether "y" is a consonant or a vowel
text = rsub(text, "yy", "ķ")
text = rsub(text, "y(" .. V .. ")", "ɟ%1") -- not the real sound
text = rsub(text, "y", "i")
--x
text = rsub(text, "x", "ʃ")
--c, g, q
text = rsub(text, "c([ie])", "θ%1")
text = rsub(text, "g([ie])", "x%1") -- handled after <x> = /ʃ/
text = rsub(text, "gu([ie])", "g%1")
text = rsub(text, "gü([ie])", "gu%1") --it's actually /w/, but we will use <u> to keep the syllabification function simpler
text = rsub(text, "ng([^aeiouüwhlr])", "n%1") -- [[Bangkok]], [[ángstrom]], [[branding]]
text = rsub(text, "qu([ie])", "k%1") --fixme: should other q-s and ü-s be handled here?
text = rsub(text, "z", "θ")
--digraphs
text = rsub(text, "ḷḷ", "đ")
text = rsub(text, "ts", "ç") --not the real sound
text = rsub(text, "ch", "č") --not the real symbol
text = rsubb(text, "ll", "ʎ")
text = rsub(text, "#p([st])", "#%1") -- [[psicología]]
text = rsub(text, "[cjñrvy]",
{ ["c"] = "k", ["j"] = "x", ["ñ"] = "ɲ", ["r"] = "ɾ", ["v"] = "b", ["y"] = "j"})
-- trill in #r, lr, nr, sr, rr, zr too?
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "([#lnsz])ɾ", "%1r")
--other double consonants
text = rsub(text, "(" .. C .. ")%1", "%1")
--assimilation of nasals and sibilants
text = rsub(text, "n([# .]*[bpm])", "m%1")
if phonetic then
text = rsub(text, "n([# .]*[kg])", "ŋ%1")
end
--double uu and ii
text = rsub(text, "uu" .. AC, "u.u" .. AC)
text = rsub(text, "ii" .. AC, "i.i" .. AC)
text = rsubb(text, "uu", "wu")
text = rsubb(text, "ii", "ji")
-- convert i/u between vowels to glide
local vowel_to_glide = { ["i"] = "j", ["u"] = "w" }
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*h?)([iu])(" .. V .. ")",
function (v1, iu, v2) return v1 .. vowel_to_glide[iu] .. v2 end
)
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--diphthongs
text = rsub(text, "i([aeou])", "j%1")
text = rsub(text, "u([aeio])", "w%1")
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = "ˌ", [CFLEX] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including ^ indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and (rfind(word, "[^" .. vowel .. "ns#]#") or rfind(word, C .. "[ns]#")) or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 and rfind(word, "[aeo][iu][ns#]") then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
if rfind(word, "men%.te#") then
local mente_syllables
-- Words ends in -mente (converted above to ménte); add a stress to the preceding portion
mente_syllables = {}
mente_syllables[2] = table.remove(syllables)
mente_syllables[1] = table.remove(syllables)
accent_word(table.concat(syllables, "."), syllables)
accent_word(table.concat(mente_syllables, "."), mente_syllables)
table.insert(syllables, mente_syllables[1])
table.insert(syllables, mente_syllables[2])
else
accent_word(word, syllables)
end
-- Vowels are nasalized if followed by nasal in same syllable.
if phonetic then
for i = 1, #syllables do
-- first check for two vowels (veinte)
syllables[i] = rsub(syllables[i], "(" .. V .. ")(" .. V .. ")([mnɲ])",
"%1" .. TILDE .. "%2" .. TILDE .. "%3")
-- then for one vowel
syllables[i] = rsub(syllables[i], "(" .. V .. ")([mnɲ])", "%1" .. TILDE .. "%2")
end
end
-- Reconstruct the word and make all primary stresses but the last one be secondary
words[j] = table.concat(syllables, ".")
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˌ%1ˈ")
end
text = table.concat(words, " ")
text = rsub(text, "%-", "")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--phonetic transcription
if phonetic then
-- θ, s, f before voiced consonants
local voiced = "mnɲbdɟgʎl"
local r = "ɾr"
local tovoiced = {
["θ"] = "θ̬",
["s"] = "z",
["ʃ"] = "ʒ",
}
local function voice(sound, following)
return tovoiced[sound] .. following
end
text = rsub(text, "([θsʃ])(" .. separator_c .. "*[" .. voiced .. r .. "])", voice)
-- fricative vs. stop allophones; first convert stops to fricatives, then back to stops
-- after nasals and sometimes after l
local stop_to_fricative = { ["b"] = "β", ["d"] = "ð", ["ɟ"] = "ʝ", ["g"] = "ɣ" }
local fricative_to_stop = { ["β"] = "b", ["ð"] = "d", ["ʝ"] = "ɟ", ["ɣ"] = "g" }
text = rsub(text, "[bdɟg]", stop_to_fricative)
text = rsub(text, "([mnŋɲ]" .. separator_c .. "*)([βɣ])",
function(nasal, fricative)
return nasal .. fricative_to_stop[fricative]
end
)
text = rsub(text, "([lʎmnɲ]" .. separator_c .. "*)([ð])",
function(nasal_l, fricative)
return nasal_l .. fricative_to_stop[fricative]
end
)
text = rsub(text, "([mnɲ]" .. separator_c .. "*)([ʝ])",
function(nasal_l, fricative)
return nasal_l .. fricative_to_stop[fricative]
end
)
text = rsub(text, "(##" .. ipa_stress_c .. "*)([βɣðʝ])",
function(stress, fricative)
return stress .. fricative_to_stop[fricative]
end
)
text = rsub(text, "[td]", { ["t"] = "t̪", ["d"] = "d̪" })
--dental s/z
local dental = "dtθð"
local todental = {
["s"] = "s̪",
["z"] = "z̪",
}
local function dent(sound, following)
return todental[sound] .. following
end
text = rsub(text, "([sz])(" .. separator_c .. "*[" .. dental .. "])", dent)
-- nasal assimilation before consonants
local labiodental, dentialveolar, dental, palatal, velar, alveolar = "ɱ", "n̪", "n̟", "ɲ", "ŋ", "n"
local nasal_assimilation = {
["f"] = labiodental,
["t"] = dentialveolar, ["d"] = dentialveolar,
["θ"] = dental,
["ĉ"] = palatal,
["ɲ"] = palatal,
["ʃ"] = palatal,
["ʒ"] = palatal,
["ɟ"] = palatal, ["ʎ"] = palatal,
["k"] = velar, ["x"] = velar, ["g"] = velar,
["s"] = alveolar, ["z"] = alveolar, ["l"] = alveolar, ["r"] = alveolar, ["ɾ"] = alveolar, ["n"] = alveolar,
}
text = rsub(text, "n(" .. separator_c .. "*)(.)",
function(stress, following)
return (nasal_assimilation[following] or "n") .. stress .. following
end
)
--velar ŋ in final position before vowels
text = rsub(text, "n#", "ŋ#")
text = rsub(text, "ŋ(" .. separator_c .. "*)(.)",
function(stress, following)
return (nasal_assimilation[following] or "ŋ") .. stress .. following
end
)
-- lateral assimilation before consonants
text = rsub(text, "l(" .. separator_c .. "*)(.)",
function(stress, following)
local l = "l"
if following == "t" or following == "d" then
-- dentialveolar
l = "l̪"
elseif following == "θ" then
-- dental
l = "l̟"
elseif following == "ĉ" or following == "ɲ" or following == "ɟ" or following == "ʎ" or following == "ʃ" then
-- palatal
l = "ʎ"
end
return l .. stress .. following
end)
--vowels
text = rsub(text, "eu", "øu")
-- voiced fricatives are actually approximants
text = rsub(text, "([βðɣ])", "%1̞")
end
--final conversions
text = rsub(text, "([aeouãẽõũ][iĩ])", "%1̯")
text = rsub(text, "([aeioãẽĩõø][uũ])", "%1̯")
local final_conversions = {
["h"] = "",
["č"] = "t͡ʃ",
["g"] = "ɡ",
["ķ"] = "kʲ",
["ç"] = "t͡s",
["đ"] = "t͡ʂ",
["ḥ"] = "h",
["ɟ"] = phonetic and "ɟ͡ʝ" or "ʝ",
}
text = rsub(text, "[hčđgķçđḥɟ]", final_conversions)
text = rsub(text, "#", "")
text = unfc(text)
return {text = text}
end
local function express_all_styles(dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
dodialect(ret)
table.insert(ret.expressed_styles, {
styles = {{pronun = ret.pronun,}},
})
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
end
local lines = {}
for i, style_group in ipairs(expressed_styles) do
table.insert(lines, style_group.formatted)
end
return table.concat(lines, "\n")
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic, phonetic
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
else
phonemic = export.IPA(term.term, false)
phonetic = export.IPA(term.term, true)
phonemic = phonemic.text
phonetic = phonetic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = express_all_styles(this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
ins(bracket_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡ʃ", "tʃ"):gsub("d͡ʒ", "dʒ")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CFLEX then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. [[Austria-Hungría]])
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for _, pronun in pairs(parsed.pronun.pronun) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(put_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{ast-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = require(put_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = express_all_styles(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = express_all_styles(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
-- External entry point for {{ast-IPA}}.
function export.show(frame)
local params = {
[1] = {},
["pre"] = {},
["post"] = {},
["ref"] = {},
["style"] = {},
["bullets"] = {type = "number", default = 1},
}
local parargs = frame:getParent().args
local args = require(parameters_module).process(parargs, params)
local text = args[1] or mw.title.getCurrentTitle().text
args.terms = {{term = text}}
local ret = generate_pronun(args)
return ret.text
end
return export
5bcqfs78txgpfpvj8ryeh25nspb50uq
Template:R:sq:Topalli:2017
10
1685284
9908799
6299667
2026-09-29T03:28:13Z
Hiyuune
119685
9908799
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|sq
|first=Kolec|last=Topalli|authorlink=sq:Kolec Topalli
|entry={{lang|sq|{{{head|{{{1|{{pagename}}}}}}}}}}
|title=Fjalor Etimologjik i Gjuhës Shqipe
|trans-title=阿爾巴尼亞語詞源詞典
|location=Durrës, Albania
|publisher=Jozef
|year=2017
|pageparam=page
|allowparams=head,1
}}<noinclude>{{refcat}}</noinclude>
dnvt3ak25zwfz2ihkrniofmymbby7i9
9908800
9908799
2026-09-29T03:28:42Z
Hiyuune
119685
Hiyuune移动页面[[Template:R:sq:Topalli]]至[[Template:R:sq:Topalli:2017]]
9908799
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|sq
|first=Kolec|last=Topalli|authorlink=sq:Kolec Topalli
|entry={{lang|sq|{{{head|{{{1|{{pagename}}}}}}}}}}
|title=Fjalor Etimologjik i Gjuhës Shqipe
|trans-title=阿爾巴尼亞語詞源詞典
|location=Durrës, Albania
|publisher=Jozef
|year=2017
|pageparam=page
|allowparams=head,1
}}<noinclude>{{refcat}}</noinclude>
dnvt3ak25zwfz2ihkrniofmymbby7i9
Module:Cuneiform spellings
828
1699964
9909051
9511508
2026-09-29T07:24:54Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909051
Scribunto
text/plain
local export = {}
local lang = require("Module:languages").getByCode("akk")
local links_module = "Module:links"
local string_utilities_module = "Module:string utilities"
local parse_interface_module = "Module:parse interface"
local parameter_utilities_module = "Module:parameter utilities"
local qualifier_module = "Module:qualifier"
local function format_list_items(terms, linked)
local result = {}
for _, term in ipairs(terms) do
if not linked then
term["alt"] = term["term"]
term["term"] = nil
end
term.show_decorations = true
local link = require(links_module).full_link(term)
table.insert(result, '\n* ' .. link)
end
return table.concat(result)
end
function export.create_list(args, paramname)
if type(args) ~= "table" then error("expected table, got " .. type(args)) end
local output = {}
table.insert(output, [[<div class="term-list ul-column-count" data-column-count="1" >]])
if args.alphabetize then
require("Module:collation").sort(args, lang)
end
table.insert(output, format_list_items(args, paramname == "sum"))
table.insert(output, '</div>')
return table.concat(output)
end
function export.parse_params(args, paramname)
local terms = args[paramname]
local param_mods = require("Module:parameter utilities").construct_param_mods {
{ default = true, require_index = true },
{ group = { "link", "q" } },
}
local function generate_obj(term, parse_err)
local termlang
if allow_explicit_lang then
local actual_term
actual_term, termlang = require(parse_interface_module).parse_term_with_lang {
term = term,
parse_err = parse_err,
paramname = paramname,
}
term = actual_term or term
end
return {
term = term,
lang = termlang or lang,
}
end
for i, term in ipairs(terms) do
terms[i] = require(parse_interface_module).parse_inline_modifiers(term, {
param_mods = param_mods,
paramname = paramname,
generate_obj = generate_obj,
})
end
return export.create_list(terms, paramname)
end
function export.display(frame)
local parent_args = frame:getParent().args
local params = {
["sum"] = { list = true },
["phon"] = { list = true },
["mix"] = { list = true }
}
local args = require("Module:parameters").process(parent_args, params)
local top = mw.getCurrentFrame():expandTemplate {
title = 'inflection-table-top',
args = {
title = '楔形文字拼寫',
class = "tr-alongside"
}
}
local headers = ""
local data = ""
if next(args["sum"]) ~= nil then
headers = headers .. [===[
! 語素文字
]===]
data = data .. [===[
| style="text-align: start;" | {sum}
]===]
end
if next(args["phon"]) ~= nil then
headers = headers .. [===[
! 音位文字
]===]
data = data .. [===[
| style="text-align: start;" | {phon}
]===]
end
if next(args["mix"]) ~= nil then
headers = headers .. [===[
! Mixed
]===]
data = data .. [===[
| style="text-align: start;" | {mix}
]===]
end
local template = top .. headers .. [===[
|- valign="top"
]===] .. data .. mw.getCurrentFrame():expandTemplate {
title = 'inflection-table-bottom',
}
local forms = {}
forms["sum"] = export.parse_params(args, "sum")
forms["phon"] = export.parse_params(args, "phon")
forms["mix"] = export.parse_params(args, "mix")
return require(string_utilities_module).format(template, forms)
end
return export
oqnb0pq2sn0jg6ze04t2pydqgsrsouk
Module:Pl-pronunciation
828
1717018
9909095
8448096
2026-09-29T07:27:42Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909095
Scribunto
text/plain
local export = {}
local langcode = "pl"
local lang = require("Module:languages").getByCode(langcode)
local m_IPA = require("Module:IPA")
local m_pl_IPA = require("Module:pl-IPA")
local vowels = "aeiouyąęó"
local vowel = "[" .. vowels .. "]"
local consonants = "bcćdfghjklłmnńpqrsśtuvwxyzźż"
local consonant = "[" .. consonants .. "]"
-- vowel digraphs, not necessarily actual phonetic diphthongs
local diphthong_i_v2 = "[aąoeęuói]"
local diphthongs = {
["a"] = "u",
["e"] = "u",
["i"] = diphthong_i_v2
}
-- consonant digraphs (key = first letter, value = possible second letters)
local digraphs = {
["c"] = "[hz]",
["d"] = "[zźż]",
["q"] = "u",
["r"] = "z",
["s"] = "z",
}
local past_tense_suffixes = {
"liśmy", "liście", "łyśmy", "łyście",
}
local latin_borrowing_suffixes = {
"ika", "yka",
"iki", "yki",
"ika", "yka",
"ice", "yce",
"ikom", "ykom",
"ikę", "ykę",
"iką", "yką",
"ice", "yce",
"ikach", "ykach",
"iko", "yko",
}
-- if this is changed, the next two functions also need to be
local function is_respelling_close_enough(respelling, word)
word = mw.ustring.gsub(word, "j(" .. diphthong_i_v2 .. ")", "i%1")
respelling = mw.ustring.gsub(respelling, "['.]", "")
respelling = mw.ustring.gsub(respelling, "j(" .. diphthong_i_v2 .. ")", "i%1")
return word == respelling
end
local function partition(word, oword)
local parts = {}
local lenword = mw.ustring.len(word)
local pos = 1
local offset = 0
word = mw.ustring.gsub(word, "['-]", ".")
while pos <= lenword do
if mw.ustring.find(mw.ustring.lower(word), "^" .. vowel, pos) then
local initial = mw.ustring.sub(mw.ustring.lower(word), pos, pos)
local seq = 1
if diphthongs[initial] and mw.ustring.find(mw.ustring.lower(word), "^" .. initial .. diphthongs[initial], pos) then
seq = 2
end
table.insert(parts, { "v", mw.ustring.sub(oword, pos - offset, pos - offset + seq - 1) })
pos = pos + seq
elseif mw.ustring.find(mw.ustring.lower(word), "^" .. consonant, pos) then
local initial = mw.ustring.sub(mw.ustring.lower(word), pos, pos)
local seq = 1
if digraphs[initial] and mw.ustring.find(mw.ustring.lower(word), "^" .. initial .. digraphs[initial], pos) then
seq = 2
end
table.insert(parts, { "c", mw.ustring.sub(oword, pos - offset, pos - offset + seq - 1) })
pos = pos + seq
elseif mw.ustring.find(word, "^% ", pos) then
-- multiword, do not hyphenate
return nil
elseif mw.ustring.find(word, "^%.", pos) then
-- syllable break
if not mw.ustring.find(oword, "^['-]", pos - offset) then
offset = offset + 1
end
table.insert(parts, { "b", nil })
pos = pos + 1
else
-- unrecognized symbol
return nil
end
end
return parts
end
local function get_word_suffix(word)
word = word:gsub("([ˈ'.,ˌ])", "")
local word_suffix = 0
for i,v in ipairs(past_tense_suffixes) do
if word:sub(-string.len(v)) == v
then
word_suffix = 1
end
end
for i,v in ipairs(latin_borrowing_suffixes) do
if word:sub(-string.len(v)) == v
then
word_suffix = 2
end
end
return word_suffix
end
function export.generate_hyphenation(word, otitle)
local syllables = {}
local cursyl = ""
local nucleus = false
local coda = nil
local pos = 1
local parts = partition(word, otitle)
if not parts then return nil end
for pos, p in ipairs(parts) do
local kind, part = unpack(p)
if kind == "v" then
if coda then
cursyl = cursyl .. mw.ustring.sub(syllables[#syllables], -coda)
syllables[#syllables] = mw.ustring.sub(syllables[#syllables], 1, -coda - 1)
coda = nil
end
if nucleus then
table.insert(syllables, cursyl)
cursyl = ""
end
nucleus = true
coda = nil
cursyl = cursyl .. part
elseif kind == "c" then
cursyl = cursyl .. part
if nucleus then
table.insert(syllables, cursyl)
cursyl = ""
nucleus = false
coda = mw.ustring.len(part)
else
coda = nil
end
elseif kind == "b" then
-- implicit syllable break
if #cursyl > 0 then
if nucleus or #syllables < 1 then
table.insert(syllables, cursyl)
else
syllables[#syllables] = syllables[#syllables] .. cursyl
end
end
cursyl = ""
nucleus = false
coda = nil
else
-- unrecognized kind
return nil
end
end
if #cursyl > 0 then
if nucleus or #syllables < 1 then
table.insert(syllables, cursyl)
else
syllables[#syllables] = syllables[#syllables] .. cursyl
end
end
return syllables
end
local ipavowel = "[aɛiɨɔu]"
function export.generate_rhyme(ipa)
local vowels_at = { }
local pos = 1
while true do
local posnext = mw.ustring.find(ipa, ipavowel, pos)
if not posnext then break end
table.insert(vowels_at, posnext)
pos = posnext + 1
end
local vend
if #vowels_at < 1 then return nil end
if #vowels_at > 1 then
vend = vowels_at[#vowels_at - 1]
else
vend = vowels_at[#vowels_at]
end
local snippet = mw.ustring.sub(ipa, vend)
snippet = mw.ustring.gsub(snippet, "[ˈˌ.]", "")
if mw.ustring.find(snippet, " ") then
return nil -- copout, something must be wrong
end
return snippet
end
function export.show(frame)
local args = require("Module:parameters").process(frame:getParent().args, {
[1] = { list = true },
["ipa"] = { list = true, default = nil, allow_holes = true },
["qual"] = { list = true, allow_holes = true },
["n"] = { list = true, allow_holes = true },
["h"] = { list = true, allow_holes = true }, ["hyphen"] = {},
["r"] = { list = true, allow_holes = true }, ["rhymes"] = {},
["a"] = { list = true, default = nil }, ["audio"] = {},
["ac"] = { list = true, default = nil }, ["caption"] = {},
["hh"] = { default = "" }, ["homophones"] = {},
["mp"] = { list = true, allow_holes = true },
["q"] = { list = true, default = nil, allow_holes = true },
["hp"] = { list = true, default = nil, allow_holes = true },
["rp"] = { list = true, default = nil, allow_holes = true },
["hhp"] = { list = true, default = nil, allow_holes = true },
["nohyphen"] = { type = "boolean", default = false },
["norhymes"] = { type = "boolean", default = false },
["fs"] = { type = "boolean" }, ["fixstress"] = {},
["title"] = { default = nil }, -- for debugging or demonstration only
})
local words, transcriptions, transcriptions_raw
local lines = {}
local categories = {}
local actual = args["title"] or mw.title.getCurrentTitle().text
if next(args[1]) ~= nil then
words = args[1]
else
words = { actual }
end
local multiword = mw.ustring.find(words[1], " ")
local hyphenations = args["h"]
local rhymes = args["r"]
local ipa = args["ipa"]
if #ipa < 1 then ipa = nil end
local qualifiers = args["q"]
if not qualifiers or qualifiers.maxindex < 1 then qualifiers = args["qual"] end
local mp = args["mp"]
local hyphlabels = args["hp"]
local rhymlabels = args["rp"]
local nohyphen = args["nohyphen"]
local norhymes = args["norhymes"]
local fixstress = args["fs"]
if args["fixstress"] then fixstress = args["fixstress"] end
local homophones = mw.text.split(args["hh"], ",")
local homophonelabels = args["hhp"]
if #homophones == 1 and homophones[1] == "" then homophones = {} end
local audio = {}
local audios = args["a"]
local captions = args["ac"]
local word_suffix = 0
if not ipa and #words == 1 then
-- 0 - normal word
-- 1 - past tense verb stressed antepenultimately
-- 2 - Latin borrowing stressed antepenultimately
word_suffix = get_word_suffix(words[1])
end
if not (fixstress or (fixstress == nil and word_suffix == 1)) then
word_suffix = 0
end
if args["hyphen"] then hyphenation[1] = args["hyphen"] end
if args["rhymes"] then rhymes[1] = args["rhymes"] end
if args["homophones"] then homophones = mw.text.split(args["homophones"], ",") end
if args["audio"] then audios[1] = args["audio"] end
if args["captions"] then captions[1] = args["caption"] end
local respelling_ok = true
for i, w in ipairs(words) do
if not is_respelling_close_enough(w, actual) then
respelling_ok = false
break
end
end
for i, audiofile in ipairs(audios) do
if audiofile then
table.insert(audio, {file = audiofile, caption = captions[i]})
end
end
if #hyphenations == 1 and hyphenations[1] == "-" then
nohyphen = true
end
if #rhymes == 1 and rhymes[1] == "-" then
norhymes = true
end
if word_suffix == 0 then
if ipa then
transcriptions = {}
transcriptions_raw = {}
for i = 1, #ipa do
local qual = qualifiers[i]
table.insert(transcriptions, {
pron = ipa[i],
q = qual and { qual } or nil,
note = args.n[i]
})
end
else
transcriptions = {}
transcriptions_raw = {}
for i = 1, #words do
local qual = qualifiers[i]
local ipaconv = m_pl_IPA.convert_to_IPA(words[i])
table.insert(transcriptions_raw, ipaconv)
table.insert(transcriptions, {
pron = "/" .. ipaconv .. "/",
q = qual and { qual } or nil,
note = args.n[i]
})
end
end
table.insert(lines, "* " .. m_IPA.format_IPA_full { lang = lang, items = transcriptions })
else
transcriptions = {}
transcriptions2 = {}
transcriptions_raw = {}
if word_suffix == 1 then
qualifier1 = {"規範性標準;罕用"}
qualifier2 = {"口語;總體比較常用"}
elseif word_suffix == 2 then
qualifier1 = {"標準"}
qualifier2 = {"口語;常用於閒談"}
end
local ipaconv = m_pl_IPA.convert_to_IPA(words[1])
table.insert(transcriptions_raw, ipaconv)
ipaconv_syllables = mw.text.split(ipaconv, "([ˈ.])")
for j, syl in ipairs(ipaconv_syllables) do
if j == (#ipaconv_syllables - 2) then
ipaconv_syllables[j] = "ˈ" .. syl
elseif j ~= 1 then
ipaconv_syllables[j] = "." .. syl
end
end
ipacov_fixed_stress = table.concat(ipaconv_syllables)
table.insert(transcriptions, {
pron = "/" .. ipacov_fixed_stress .. "/",
q = qualifier1,
})
table.insert(lines, "* " .. m_IPA.format_IPA_full { lang = lang, items = transcriptions })
table.insert(transcriptions2, {
pron = "/" .. ipaconv .. "/",
q = qualifier2,
})
table.insert(lines, "* " .. m_IPA.format_IPA_full { lang = lang, items = transcriptions2 })
end
if #mp > 0 then
transcriptions = {}
for i = 1, #mp do
if mp[i] == "+" then
mp[i] = actual
end
local ipaconv = require("Module:zlw-mpl-IPA").convert_to_IPA_tables({mp[i]});
for _, v in ipairs(ipaconv) do
table.insert(transcriptions, v);
end
end
table.insert(lines, "* " .. require("Module:accent qualifier").format_qualifiers(lang, {"中古波蘭語"}) .. " " .. m_IPA.format_IPA_full { lang = lang, items = transcriptions })
end
for i, a in ipairs(audio) do
table.insert(lines, "* " .. require("Module:audio").format_audio { lang = lang, file = a["file"], caption = a["caption"] })
end
if not ipa and #hyphenations < 1 and respelling_ok and not multiword then
local autohyph = export.generate_hyphenation(words[1], actual)
if autohyph then
table.insert(hyphenations, autohyph)
end
elseif #hyphenations >= 1 then
local newhyphenations = {}
for i, h in ipairs(hyphenations) do
local t = {}
for x in mw.text.gsplit(h, "[.]") do
table.insert(t, x)
end
newhyphenations[i] = t
end
hyphenations = newhyphenations
end
if not norhymes then
if not ipa and #rhymes < 1 and #transcriptions_raw > 0 then
local autorhyme = export.generate_rhyme(transcriptions_raw[1])
if autorhyme then
table.insert(rhymes, autorhyme)
end
end
if #rhymes > 0 then
-- merge rhymes if they have identical labels
local last_label = false
local new_rhymes = {}
local new_labels = {}
local current_list = {}
for i, r in ipairs(rhymes) do
local label = rhymlabels[i]
if last_label == label then
table.insert(current_list, r)
else
if #current_list > 0 then
table.insert(new_rhymes, current_list)
end
if last_label ~= false then
table.insert(new_labels, last_label)
end
current_list = { r }
last_label = label
end
end
table.insert(new_rhymes, current_list)
table.insert(new_labels, last_label)
rhymes = new_rhymes
rhymlabels = new_labels
end
for i, r in ipairs(rhymes) do
local label = ""
if rhymlabels[i] then
label = " " .. require("Module:qualifier").format_qualifier(rhymlabels[i])
end
if #r >= 1 then
local sylkeys = {}
local sylcounts = {}
-- get all possible syllable counts from syllabifications
for i, h in ipairs(hyphenations) do
local hl = #h
if hl > 0 and not sylkeys[hl] then
table.insert(sylcounts, hl)
sylkeys[hl] = true
end
end
local rhymeobjs = {}
for _, rhyme in ipairs(r) do
table.insert(rhymeobjs, {rhyme = rhyme})
end
table.insert(lines, "* " .. require("Module:rhymes").format_rhymes(
{ lang = lang, rhymes = rhymeobjs, num_syl = sylcounts }) .. label)
end
end
end
if not nohyphen then
if #transcriptions > 0 and #hyphenations > 0 then
local syl_IPA = require("Module:syllables").getVowels(transcriptions[1].pron, lang)
local syl_hyph = #hyphenations[1]
if syl_IPA and syl_IPA ~= syl_hyph then
table.insert(categories, "pl-pronunciation syllable count mismatch")
end
end
if not actual:find("[ %.]") and #hyphenations < 1 then
table.insert(categories, "pl-pronunciation without hyphenation")
end
for i, h in ipairs(hyphenations) do
local label = ""
if hyphlabels[i] then
label = " " .. require("Module:qualifier").format_qualifier(hyphlabels[i])
end
table.insert(lines, "* 音節化:" .. require("Module:links").full_link({lang = lang, alt = table.concat(h, "‧"), tr = "-"}) .. label)
end
end
if #homophones > 0 then
local homophone_objs = {}
for i, h in ipairs(homophones) do
table.insert(homophone_objs, {term = h, qq = homophonelabels[i] and {homophonelabels[i]} or nil})
end
table.insert(lines, "* " .. require("Module:homophones").format_homophones { lang = lang, homophones = homophone_objs })
end
return table.concat(lines, "\n") .. require("Module:utilities").format_categories(categories, lang)
end
return export
b043l01bnoqetktccuhmu7iw2godwfo
Module:Ajp-IPA
828
1717628
9909038
8797897
2026-09-29T07:24:03Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909038
Scribunto
text/plain
local export = {}
local lang = require("Module:languages").getByCode("ajp")
local m_IPA = require("Module:IPA")
local gsub = mw.ustring.gsub
local correspondences = {
["g"] = "ɡ",
["ḵ"] = "x",
["ḥ"] = "ħ",
["ḡ"] = "ɣ",
["ṣ"] = "sˤ",
["ḍ"] = "dˤ",
["ẓ"] = "zˤ",
["ṭ"] = "tˤ",
["š"] = "ʃ",
["j"] = "ʒ",
["ā"] = "aː",
["ū"] = "uː",
["ō"] = "oː",
["ī"] = "iː",
["ē"] = "eː",
["y"] = "j",
["ṯ"] = "θ",
["ḏ"] = "ð",
["ḅ"] = "bˤ",
["ḷ"] = "lˤ",
["ṃ"] = "mˤ",
}
local vowels = "aeiuoāēīūō"
local vowel = "[" .. vowels .. "]"
local long_vowels = "āēīūō"
local consonant = "[^" .. vowels .. ". -]"
local tie = "‿"
local syllabify_pattern = "(" .. vowel .. ")(" .. consonant .. "?)(" ..
consonant .. "?)(" .. vowel .. ")"
local function parse_params(item)
local term = item
local phonetic = {}
local qualifiers = {}
while true do
local new_term, angle_bracketed = term:match("^(.-)(%b<>)$")
if not new_term then
break
end
local prefix, content = angle_bracketed:match "^<(%w+):(.+)>$"
if not prefix then
break
end
if prefix == "q" then
table.insert(qualifiers, 1, content)
elseif prefix == "p" then
table.insert(phonetic, 1, content)
else
error("Unrecognized prefix '" .. prefix .. "'")
break
end
term = new_term
end
return term, phonetic, qualifiers
end
local function syllabify(text)
text = gsub(text, "%-(" .. consonant .. ")%-(" .. consonant .. ")", "%1.%2")
text = gsub(text, "%-", ".")
-- Add syllable breaks.
for _ = 1, 2 do
text = gsub(text, syllabify_pattern, function(a, b, c, d)
if c == "" and b ~= "" then
c, b = b, ""
end
return a .. b .. "." .. c .. d
end)
end
-- Add ties between word-final vowels and word-initial consonant clusters.
text = gsub(text, "(" .. vowel .. ") (" .. consonant .. ")%.?(" ..
consonant .. ")", "%1" .. tie .. "%2.%3")
return text
end
local function pronunciation_phonemic(word)
word = syllabify(word)
word = gsub(word, '.', correspondences)
return word
end
function export.show(frame)
local args = require("Module:parameters").process(frame:getParent().args, {
[1] = { list = true }
})
local words = args[1]
local pronunciation = ""
for i, item in ipairs(words) do
local transcriptions = {}
local term, phonetic, qualifiers = parse_params(item)
table.insert(transcriptions, { pron = "/" .. pronunciation_phonemic(term) .. "/", q = qualifiers })
if phonetic then
for _, p_item in ipairs(phonetic) do
table.insert(transcriptions, { pron = "[" .. p_item .. "]" })
end
end
if i > 1 then
pronunciation = pronunciation .. "\n* "
end
pronunciation = pronunciation .. m_IPA.format_IPA_full { lang = lang, items = transcriptions }
end
return pronunciation
end
return export
74ixduo4kg6ozcqrxe4n9qdh4ls0sru
wrzemię
0
1753328
9909149
7057993
2026-09-29T08:47:48Z
TongcyDai
53191
9909149
wikitext
text/x-wiki
==波蘭語==
===詞源===
源自{{inh|pl|sla-pro|*vermę}},源自{{etyl|ine-bsl-pro|pl}} {{m|ine-bsl-pro|*wertmen}} / {{m|ine-bsl-pro|*wertmin}},源自{{etyl|ine-pro|pl}} {{m|ine-pro|*wértmn̥}},源自詞根{{m|ine-pro|*wert-||旋轉}}。
與{{cog|sa|वर्त्मन्||軌跡,路徑}}同源。
===發音===
{{pl-pr}}
===名詞===
{{pl-noun|n}}
# {{label|pl|obsolete}} [[時間]]
====變格====
{{pl-decl-noun-n}}
====派生詞====
* {{qualifier|形容詞}} {{l|pl|wrzemienny}}
====近義詞====
* {{l|pl|czas}}
===拓展閱讀===
* {{R:PWN}}
[[Category:波蘭語 時間]]
5mrgy1noq13r46jn9a5jt83xaqegxl1
9909151
9909149
2026-09-29T08:49:21Z
TongcyDai
53191
9909151
wikitext
text/x-wiki
==古波蘭語==
===其他形式===
* {{alt|zlw-opl|brzemię}}
===詞源===
{{dercat|zlw-opl|ine-bsl-pro|ine-pro|inh=2}}
{{inh+|zlw-opl|sla-pro|*vermę}}。{{displaced|zlw-opl|sl=1|by=1|czas}}。
===發音===
* {{zlw-opl-IPA}}
===名詞===
{{zlw-opl-noun|n}}
# {{lb|zlw-opl|attested in|Lesser Poland}} [[時間]]
#* {{RQ:zlw-opl:Kśw|Fl 20, 9|vkake '''uremø''' ſg͑ſıl|w kakie '''wrzemię''' zgrzeszył|-}}
====派生詞彙====
{{col|zlw-opl|title=形容詞|wrzemienny}}
===延伸閱讀===
* {{R:zlw-opl:SPJSP|wrzemię, brzemię|19456}}
* {{R:pl:SJP1900|Wrzemię, Brzemię|730|7}}
{{C|zlw-opl|時間}}
fck8idqm0o1qmx8yb93kjo95luqpa7y
9909152
9909151
2026-09-29T08:49:38Z
TongcyDai
53191
9909152
wikitext
text/x-wiki
==古波蘭語==
===其他形式===
* {{alt|zlw-opl|brzemię}}
===詞源===
{{dercat|zlw-opl|ine-bsl-pro|ine-pro|inh=2}}
{{inh+|zlw-opl|sla-pro|*vermę}}。{{displaced|zlw-opl|sl=1|by=1|czas}}。
===發音===
* {{zlw-opl-IPA}}
===名詞===
{{zlw-opl-noun|n}}
# {{lb|zlw-opl|記錄於|_|Lesser Poland}} [[時間]]
#* {{RQ:zlw-opl:Kśw|Fl 20, 9|vkake '''uremø''' ſg͑ſıl|w kakie '''wrzemię''' zgrzeszył|-}}
====派生詞彙====
{{col|zlw-opl|title=形容詞|wrzemienny}}
===延伸閱讀===
* {{R:zlw-opl:SPJSP|wrzemię, brzemię|19456}}
* {{R:pl:SJP1900|Wrzemię, Brzemię|730|7}}
{{C|zlw-opl|時間}}
4wfx48seknpzc1htlf220vwmwuwj05a
Module:De-noun
828
1783205
9909052
9511346
2026-09-29T07:24:58Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909052
Scribunto
text/plain
local export = {}
--[=[
Authorship: <benwing2>
]=]
--[=[
TERMINOLOGY:
-- "slot" = A particular combination of case/number.
Example slot names for nouns are "voc_s" (vocative singular) and
"gen_p" (genitive plural). Each slot is filled with zero or more forms.
-- "form" = The declined German form representing the value of a given slot.
-- "lemma" = The dictionary form of a given German term. Generally the nominative
masculine singular, but may occasionally be another form if the nominative
masculine singular is missing.
]=]
--[=[
FIXME:
1. Qualifiers in genders should appear as footnotes on the articles.
2. Support notation like <g:f> on feminine/diminutive/masculine, e.g. used for [[Gespons]] (neuter with the meaning
"wife", masculine with the meaning "husband").
3. Fix CSS gender-specific class in table.
4. Support adjectival nouns and adjective-noun combinations. (DONE)
5. Allow period and comma in forms e.g. for [[Eigent.-Whg.]], [[Eigt.-Whg.]] (using a backslash). (DONE)
6. Allow embedded links in genitive/plural/feminine/diminutive/masculine specs, e.g. 'f=![[weiblich]]er Geschäftspartner'.
7. Add 'prop' indicator to indicate proper nouns and suppress the indefinite article.
8. Add 'surname' indicator to indicate surnames, decline appropriately and include both masc and fem variants in the table. (DONE)
9. Add 'langname' indicator to indicate langnames and decline appropriately with its own table with two alternatives. (DONE)
]=]
local lang = require("Module:languages").getByCode("de")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local m_links = require("Module:links")
local m_string_utilities = require("Module:string utilities")
local iut = require("Module:inflection utilities")
local put = require("Module:parse utilities")
local com = require("Module:de-common")
local pretend_from_headword = false -- may be set during debugging
local force_cat = false -- may be set during debugging
local u = m_str_utils.char
local rfind = m_str_utils.find
local rmatch = m_str_utils.match
local rsubn = m_str_utils.gsub
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local usub = m_str_utils.sub
local SUB_ESCAPED_PERIOD = u(0xFFF0)
local SUB_ESCAPED_COMMA = u(0xFFF1)
local archaic_dative_note = "[現已不常用,[[:en:Wiktionary:About German#Dative_singular_-e_in_noun_declension|參見註解]]]"
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
local function track(page)
require("Module:debug").track("de-noun/" .. page)
return true
end
local states = { "str", "wk", "mix" }
local definitenesses = { "ind", "def" }
local cases_with_abl_voc = { "nom", "gen", "dat", "acc", "abl", "voc" }
local basic_cases = { "nom", "gen", "dat", "acc" }
local numbers = { "s", "p" }
local gender_spec_to_full_gender = {
m = "陽性",
f = "陰性",
n = "中性",
}
local case_set_with_abl_voc = m_table.listToSet(cases_with_abl_voc)
local function add_equiv(slot_list)
table.insert(slot_list, {"m_equiv", "-"}) -- masculine equivalent of a feminine or neuter noun
table.insert(slot_list, {"f_equiv", "-"}) -- feminine equivalent of a masculine or neuter noun
table.insert(slot_list, {"n_equiv", "-"}) -- neuter equivalent of a masculine or feminine noun
end
-- Construct noun slots.
local noun_slot_list = {}
add_equiv(noun_slot_list)
local noun_slot_set = {}
for _, number in ipairs(numbers) do
for _, case in ipairs(number == "s" and cases_with_abl_voc or basic_cases) do
local slot = case .. "_" .. number
local accel = case .. "|" .. number
table.insert(noun_slot_list, {slot, accel})
noun_slot_set[slot] = true
end
end
-- Construct noun surname slots.
local surname_slot_list = {
}
local surname_slot_set = {}
local surname_endings = {
{"m_s", "m|s"},
{"f_s", "f|s"},
{"p", "p"},
}
for _, case in ipairs(basic_cases) do
for _, ending_and_accel in ipairs(surname_endings) do
local ending, ending_accel = unpack(ending_and_accel)
local slot = case .. "_" .. ending
local accel = case .. "|" .. ending_accel
table.insert(surname_slot_list, {slot, accel})
surname_slot_set[slot] = true
end
end
-- Construct noun langname slots.
local langname_slot_list = {
}
local langname_slot_set = {}
for _, case in ipairs(basic_cases) do
for _, number in ipairs(numbers) do
for _, is_alt in ipairs { false, true } do
local slot = case .. "_" .. number .. (is_alt and "_alt" or "")
-- FIXME: We should add accelerators for the alternative forms, but this requires hacking the accelerator
-- code in [[Module:inflection utilities]] to specify the alternative lemma; e.g. genitive singular
-- ''Deutschen'' needs to have lemma [[Deutsche]] not [[Deutsch]].
local accel = is_alt and "-" or case .. "|" .. number
table.insert(langname_slot_list, {slot, accel})
langname_slot_set[slot] = true
end
end
end
-- Construct adjectival slots.
local adjectival_slot_list = {}
add_equiv(adjectival_slot_list)
local adjectival_slot_set = {}
for _, state in ipairs(states) do
for _, case in ipairs(basic_cases) do
for _, number in ipairs(numbers) do
local slot = state .. "_" .. case .. "_" .. number
local accel = state .. "|" .. case .. "|" .. number
table.insert(adjectival_slot_list, {slot, accel})
adjectival_slot_set[slot] = true
end
end
end
-- Construct expanded slot lists including linked variants.
local noun_slot_list_with_linked = m_table.shallowCopy(noun_slot_list)
table.insert(noun_slot_list_with_linked, {"nom_s_linked", "nom|s"})
table.insert(noun_slot_list_with_linked, {"nom_p_linked", "nom|p"})
local surname_slot_list_with_linked = m_table.shallowCopy(surname_slot_list)
table.insert(surname_slot_list_with_linked, {"nom_m_s_linked", "nom|m|s"})
local langname_slot_list_with_linked = m_table.shallowCopy(langname_slot_list)
table.insert(langname_slot_list_with_linked, {"nom_s_linked", "nom|s"})
local adjectival_slot_list_with_linked = m_table.shallowCopy(adjectival_slot_list)
table.insert(adjectival_slot_list_with_linked, {"str_nom_s_linked", "str|nom|s"})
table.insert(adjectival_slot_list_with_linked, {"str_nom_p_linked", "str|nom|p"})
-- Construct expanded slot lists including linked variants and articles.
local function add_slot_articles(slot_list, cases, numbers)
for _, case in ipairs(cases) do
for _, number in ipairs(numbers) do
for _, def in ipairs(definitenesses) do
local slotaccel = {"art_" .. def .. "_" .. case .. "_" .. number, "-"}
table.insert(slot_list, slotaccel)
end
end
end
end
local noun_slot_list_with_linked_and_articles = m_table.shallowCopy(noun_slot_list_with_linked)
add_slot_articles(noun_slot_list_with_linked_and_articles, cases_with_abl_voc, numbers)
local surname_slot_list_with_linked_and_articles = m_table.shallowCopy(surname_slot_list_with_linked)
add_slot_articles(surname_slot_list_with_linked_and_articles, basic_cases, {"m_s", "f_s", "p"})
local langname_slot_list_with_linked_and_articles = m_table.shallowCopy(langname_slot_list_with_linked)
add_slot_articles(langname_slot_list_with_linked_and_articles, basic_cases, {"s"})
local adjectival_slot_list_with_linked_and_articles = m_table.shallowCopy(adjectival_slot_list_with_linked)
add_slot_articles(adjectival_slot_list_with_linked_and_articles, basic_cases, numbers)
-- Return true if `prop` is a recognized indicator that can be specified on adjectives in [[Module:de-adjective]].
local function is_adjectival_decl_indicator(prop)
return prop == "ss" or prop == "sync_n" or prop == "sync_mn" or prop == "sync_mns"
end
local function skip_slot(number, slot)
return number == "sg" and rfind(slot, "_p$") or
number == "pl" and rfind(slot, "_s$")
end
local function combine_stem_ending(props, stem, ending)
if ending:find("^%^") then
-- Umlaut requested
ending = rsub(ending, "^%^", "")
stem = com.apply_umlaut(stem)
end
if props.ss and stem:find("ß$") and rfind(ending, "^" .. com.V) then
stem = rsub(stem, "ß$", "ss")
end
return stem .. ending
end
-- Add a form (a combination of `stem` and `ending`, where either may be a single string, a list of strings, or a
-- list of objects of the form {form=FORM, footnotes=FOOTNOTES}, where FOOTNOTES can be nil or a list of strings)
-- to the given slot `slot`. `gender` specifies the gender of the resulting form ("m", "f" or "n") or nil. (This is
-- used to ensure that the correct article is attached to the form when there are multiple forms with differing
-- genders. If `gender` is nil, articles of all relevant genders will be included. `gender` should only be nil
-- when the slot is plural or when the gender cannot be determined, e.g. in overrides.) `footnotes` specifies
-- any extra footnotes to add to the resulting form, and should be either nil or a list of strings.
-- `process_combined_stem_ending` is a function to process the resulting form before it is inserted. (This is used
-- currently to add an -n to the dative plural.)
local function add(base, slot, stem, ending, gender, footnotes, process_combined_stem_ending)
if not ending or skip_slot(base.number, slot) then
return
end
local function do_combine_stem_ending(stem, ending)
local retval = combine_stem_ending(base.props, stem, ending)
if process_combined_stem_ending then
retval = process_combined_stem_ending(retval)
end
-- For now, don't do this.
-- If gender specified, add a special character to the beginning of the value to indicate the
-- gender. This gets propagated to the end and used in [[Module:de-headword]].
-- if gender then
-- retval = gender_to_gender_char[gender] .. retval
-- end
return retval
end
footnotes = iut.combine_footnotes(base.footnotes, footnotes)
local ending_obj = iut.combine_form_and_footnotes(ending, footnotes)
-- If we're declining an adjectival noun or adjective-noun combination, and the slot is a noun slot, convert it to
-- the equivalent adjective slots (e.g. gen_s -> str_gen_s/wk_gen_s/mix_gen_s). But don't do that for "m_equiv",
-- "f_equiv", "n_equiv", which are the same in nouns and adjectives.
if base.props.overall_adj and noun_slot_set[slot] and not rfind(slot, "equiv$") then
for _, state in ipairs(states) do
iut.add_forms(base.forms, state .. "_" .. slot, stem or base.lemma, ending_obj, do_combine_stem_ending)
end
else
iut.add_forms(base.forms, slot, stem or base.lemma, ending_obj, do_combine_stem_ending)
end
end
-- Process an ending spec such as "s", "(e)s", "^er", "^lein", "!Pizzen", etc. as might be found in the genitive,
-- plural, an override, the value of dim=/m=/f=/n=, etc. `endings` is a list of such specs, where each entry of the
-- list is of the form {form=FORM, footnotes=FOOTNOTES} where FOOTNOTES is either nil or {FOOTNOTE, FOOTNOTE, ...}. If
-- `literal_endings` is given, the FORM values should be interpreted literally (i.e. as full forms) rather than as
-- ending specs. `default` is what to substitute if an ending spec is "+", and should be either in the same format as
-- `endings` or something that can be converted to that format, e.g. a string. `literal_default`, if given, indicates
-- that the FORM values in `default` should be interpreted literally, similar to `literal_endings`. `desc` is an
-- English description of what kind of spec is being processed, for error messages. `process` is called for each
-- generated form and is a function of two arguments, STEM and ENDING. If the spec is a full form, STEM will be that
-- form (in the form of an object {form=FORM, footnotes=FOOTNOTES}) and ENDING will be an empty string; otherwise, STEM
-- will be nil and ENDING will be the the ending to process in the form {form=FORM, footnotes=FOOTNOTES}. Note that
-- umlauts are not handled in process_spec(); if the spec passed in specifies an umlaut, e.g. "^chen", process()
-- will be called with a FORM beginning with "^", and must handle the umlaut itself. (Umlauts are properly handled
-- inside of add().)
local function process_spec(endings, literal_endings, default, literal_default, desc, process)
for _, ending in ipairs(endings) do
local function sub_form(form)
return {form = form, footnotes = ending.footnotes}
end
if ending.form == "--" then
-- do nothing
elseif ending.form == "+" then
if not default then
-- Could happen if e.g. gen is given as -- and then a gen_s override with + is specified, or with n= for neuter,
-- where no default is available.
error("Form '+' found for " .. desc .. " but no default is available")
end
process_spec(iut.convert_to_general_list_form(default, ending.footnotes), literal_default, nil, nil, desc, process)
else
local full_eform
if literal_endings or rfind(ending.form, "^" .. com.CAP) then
full_eform = true
elseif rfind(ending.form, "^!") then
full_eform = true
ending = sub_form(rsub(ending.form, "^!", ""))
end
if full_eform then
process(ending, "")
else
local expanded_endings
local umlaut = rmatch(ending.form, "^(%^?)%(e%)s$" )
if umlaut then
expanded_endings = {"es", "s"}
end
if not umlaut then
umlaut = rmatch(ending.form, "^(%^?)%(s%)$")
if umlaut then
expanded_endings = {"s", ""}
end
end
if not umlaut then
umlaut = rmatch(ending.form, "^(%^?)%(es%)$")
if umlaut then
expanded_endings = {"es", ""}
end
end
if expanded_endings then
local new_endings = {}
for _, expanded_ending in ipairs(expanded_endings) do
table.insert(new_endings, sub_form(umlaut .. expanded_ending))
end
process(nil, new_endings)
else
if ending.form == "-" then
ending = sub_form("")
end
process(nil, ending)
end
end
end
end
end
-- Add an ending spec such as "s", "(e)s", "^er", "^lein", "!Pizzen", etc. as might be found in the genitive, plural,
-- an override, the value of dim=/m=/f=/n=, etc., to the slot `slot` (e.g. "gen_s"). `endings` is a list of such specs,
-- where each entry of the list is of the form {form=FORM, footnotes=FOOTNOTES} where FOOTNOTES is either nil or
-- {FOOTNOTE, FOOTNOTE, ...}. For the meaning of `gender`, `footnotes` and `process_combined_stem_ending`, see add().
-- For the meaning of `default` and `literal_default`, see process_spec().
local function add_spec(base, slot, endings, gender, default, literal_default, footnotes, process_combined_stem_ending)
local function do_add(stem, ending)
add(base, slot, stem, ending, gender, footnotes, process_combined_stem_ending)
end
process_spec(endings, nil, default, literal_default, "slot '" .. slot .. "'", do_add)
end
local function process_slot_overrides(base)
for slot, overrides in pairs(base.overrides) do
if skip_slot(base.number, slot) then
error("Override specified for invalid slot '" .. slot .. "' due to '" .. base.number .. "' number restriction")
end
local origforms = base.forms[slot]
base.forms[slot] = nil
-- Gender is not given by the user.
add_spec(base, slot, overrides, nil, origforms, "literal default")
end
end
local function add_archaic_dative_singular(base, gender, def_gen)
for _, ending in ipairs(base.gens) do
local dat_ending
local ending_form = ending.form
if ending_form == "+" then
ending_form = def_gen
end
if ending_form == "es" or ending_form == "(e)s" then
dat_ending = "e"
elseif ending_form == "ses" then
dat_ending = "se"
elseif base.props.dat_with_e then
dat_ending = "e"
end
if dat_ending then
add(base, "dat_s", nil, dat_ending, gender, iut.combine_footnotes(ending.footnotes, {archaic_dative_note}))
end
end
end
local function get_n_ending(base, stem, is_sg)
if rfind(stem, "e$") then
-- typical feminine or weak masculine in -e
return "n"
elseif rfind(stem, "e[lr]$") and not rfind(stem, com.NV .. "[ei]e[lr]$") then
-- [[Kammer]], [[Feier]], [[Leier]], but not [[Spur]], [[Beer]], [[Manier]], [[Schmier]] or [[Vier]]
-- similarly, [[Achsel]], [[Gabel]], [[Tafel]], etc. but not [[Ziel]]
return "n"
elseif base.props.weak_n then
-- ''des Nachbarn'', ''des Herrn'', ''des Satyrn'', etc.
return "n"
elseif rfind(stem, "[^aeAE]in$") then
-- [[Chinesin]], [[Doktorin]], etc.; but not words in -ein or -ain such as [[Pein]]
return "nen"
else
return "en"
end
end
local function get_default_gen(base, gender)
if gender == "f" then
return ""
elseif base.props.weak then
return get_n_ending(base, base.lemma, "is singular")
elseif rfind(base.lemma, "nis$") then
-- neuter like [[Erlebnis]], [[Geheimnis]] or occasional masculine like [[Firnis]], [[Penis]]
return "ses"
elseif rfind(base.lemma, com.NV .. "us$") then
-- [[Euphemismus]], [[Exitus]], [[Exodus]], etc.
return ""
elseif rfind(base.lemma, "[sßxz]$") then
return "es"
else
return "s"
end
end
local function get_default_pl(base, gender)
if rfind(base.lemma, "nis$") then
-- neuter like [[Erlebnis]], [[Geheimnis]] or feminine like [[Kenntnis]], [[Wildnis]],
-- or occasional masculine like [[Firnis]], [[Penis]]
return "se"
elseif gender == "f" or base.props.weak then
return get_n_ending(base, base.lemma)
elseif rfind(base.lemma, "e$") then
track("default-pl-e-not-f-or-weak")
-- FIXME: This should return "s"
return get_n_ending(base, base.lemma)
elseif gender == "n" and rfind(base.lemma, "lein$") then
-- Diminutives in -lein (those in -chen will automatically get a null ending from -en below)
return ""
elseif gender == "n" and rfind(base.lemma, "um$") then
-- [[Museum]] -> [[Museen]], [[Vakuum]] -> [[Vakuen]]; not masculine [[Baum]] (plural [[Bäume]])
-- or [[Reichtum]] (plural [[Reichtümer]])
return "!" .. rsub(base.lemma, "um$", "en")
elseif rfind(base.lemma, "mus$") then
-- Algorithmus -> Algorithmen, Aphorismus -> Aphorismen
return "!" .. rsub(base.lemma, "us$", "en")
elseif rfind(base.lemma, com.NV .. "us$") then
-- [[Abakus]] -> [[Abakusse]], [[Zirkus]] -> [[Zirkusse]], [[Autobus]] -> [[Autobusse]];
-- not [[Applaus]] (plural [[Applause]])
return "se"
elseif rfind(base.lemma, "e[lmnr]$") and not rfind(base.lemma, com.NV .. "[ei]e[lnmr]$") then
-- check for weak ending -el, -em, -en, -er, e.g. [[Adler]], [[Meier]], [[Riedel]]; but exclude [[Heer]],
-- [[Bier]], [[Ziel]], which take -e by default
return ""
else
return "e"
end
end
local function decline_singular(base, gender, def_gen)
add(base, "nom_s", nil, "", gender)
add_spec(base, "gen_s", base.gens, gender, def_gen)
if base.props.weak then
local ending = get_n_ending(base, base.lemma, "is singular")
add(base, "dat_s", nil, ending, gender)
add(base, "acc_s", nil, gender == "m" and ending or "", gender)
else
add(base, "dat_s", nil, "", gender)
add_archaic_dative_singular(base, gender, def_gen)
add(base, "acc_s", nil, "", gender)
end
end
local function decline_plural(base, def_pl)
local function process_nom_pl_for_decl_type(stem_ending)
if base.props.saw_mn and base.number ~= "pl" then
if base.props.weak then
m_table.insertIfNot(base.decl_type, "弱變化")
elseif stem_ending == base.lemma .. "n" or stem_ending == base.lemma .. "en" then
m_table.insertIfNot(base.decl_type, "混合變化")
else
m_table.insertIfNot(base.decl_type, "強變化")
end
end
return stem_ending
end
local function process_dat_pl_to_add_n(stem_ending)
if base.props.nodatpln then
return stem_ending
elseif rfind(stem_ending, "e[lr]?$") or rfind(stem_ending, "erl$") then
return stem_ending .. "n"
else
return stem_ending
end
end
add_spec(base, "nom_p", base.pls, nil, def_pl, nil, nil, process_nom_pl_for_decl_type)
add_spec(base, "gen_p", base.pls, nil, def_pl)
add_spec(base, "dat_p", base.pls, nil, def_pl, nil, nil, process_dat_pl_to_add_n)
add_spec(base, "acc_p", base.pls, nil, def_pl)
end
local function decline_noun(base)
if base.number == "pl" then
decline_plural(base, "")
if rfind(base.lemma, "innen$") then
--- Ends in -innen, likely feminine. Chop off, and convert e.g. Chinesinnen -> Chinesen.
local masc = rsub(base.lemma, "innen$", "")
if rfind(masc, "es$") then
masc = masc .. "en"
end
-- No need to specify gender for *_equiv; will be handled correctly in [[Module:de-headword]].
add(base, "m_equiv", masc, "")
else
-- Likely masculine. Try to convert Chinesen -> Chinesinnen, and -er -> -erinnen.
local femstem = rsub(base.lemma, "en$", "")
add(base, "f_equiv", femstem, "innen")
end
else
base.decl_type = {}
for _, genderspec in ipairs(base.genders) do
local gender = genderspec.form
decline_singular(base, gender, get_default_gen(base, gender))
decline_plural(base, get_default_pl(base, gender))
if gender == "m" then
add(base, "f_equiv", rsub(base.lemma, "e$", ""), "in") -- feminine
elseif gender == "f" then
-- Try (sort of) to get the masculine. Remove final -in, and if the result ends in -es, convert to -ese
-- (e.g. Chinesin -> Chinese).
local masc = rsub(base.lemma, "in$", "")
if rfind(masc, "es$") then
masc = masc .. "e"
end
add(base, "m_equiv", masc, "")
end -- do nothing for neuter
end
end
end
local function decline_surname(base)
-- We don't specify gender here. There are always two genders, m and f, which will be handled correctly in
-- [[Module:de-headword]].
add(base, "nom_m_s", nil, "")
add(base, "nom_f_s", nil, "")
local gen_m_s
if rfind(base.lemma, "[sxzß]$") or rfind(base.lemma, "ce$") then
-- [[Marx]], [[Engels]], [[Weiß]], [[Schulz]]
-- also names with silent -s or -x like [[Delacroix]]
gen_m_s = "'"
else
gen_m_s = "s"
end
add_spec(base, "gen_m_s", base.gens, nil, gen_m_s)
add(base, "gen_m_s", nil, "", nil, {"[帶冠詞]"})
add(base, "gen_f_s", nil, "")
add(base, "dat_m_s", nil, "")
add(base, "dat_f_s", nil, "")
add(base, "acc_m_s", nil, "")
add(base, "acc_f_s", nil, "")
local pl_ending
if rfind(base.lemma, "[sxß]$") then
-- [[Marx]], [[Engels]], [[Weiß]]
pl_ending = {"", "ens"}
elseif rfind(base.lemma, "z$") then
-- [[Schulz]], [[Schmitz]]
pl_ending = {"", "es", "ens"}
elseif rfind(base.lemma, "ce$") then
pl_ending = {"", "ns"}
elseif rfind(base.lemma, "e[nlr]?$") then
-- [[Müller]], [[Goethe]], [[Dürer]], [[Schlegel]], [[Münchhausen]]
pl_ending = {"s", ""}
else
-- [[Schmidt]], [[Bergmann]], [[Brentano]]
pl_ending = {"s"}
end
add_spec(base, "nom_p", base.pls, nil, pl_ending)
add_spec(base, "gen_p", base.pls, nil, pl_ending)
add_spec(base, "dat_p", base.pls, nil, pl_ending)
add_spec(base, "acc_p", base.pls, nil, pl_ending)
end
local function decline_toponym(base)
-- We don't specify gender here, which is always neuter.
add(base, "nom_s", nil, "")
local gen_s
local null_footnote
if rfind(base.lemma, "[sxzß]$") then
gen_s = "'"
null_footnote = "[帶冠詞]"
else
gen_s = "s"
null_footnote = "[可帶可不帶冠詞]"
end
add_spec(base, "gen_s", base.gens, nil, gen_s)
add(base, "gen_s", nil, "", nil, {null_footnote})
add(base, "dat_s", nil, "")
add(base, "acc_s", nil, "")
if base.number == "both" then
-- only with explicitly given plural
add_spec(base, "nom_p", base.pls)
add_spec(base, "gen_p", base.pls)
add_spec(base, "dat_p", base.pls)
add_spec(base, "acc_p", base.pls)
end
end
local function decline_langname(base)
-- We don't specify gender here, which is always neuter.
add(base, "nom_s", nil, "")
add(base, "gen_s", nil, "")
-- If explicit genitive singular given, add it (in addition to the null genitive singular), otherwise default to -s.
add_spec(base, "gen_s", base.gens, nil, "s")
add(base, "dat_s", nil, "")
add(base, "acc_s", nil, "")
add(base, "nom_s_alt", nil, "e")
add(base, "gen_s_alt", nil, "en")
add(base, "dat_s_alt", nil, "en")
add(base, "acc_s_alt", nil, "e")
end
local function decline_adjective(base)
-- Construct an equivalent call to {{de-adecl}} based on the adjective indicators we fetched.
local adj_spec_parts = {}
local function ins(val)
table.insert(adj_spec_parts, val)
end
local function ins_dot()
if #adj_spec_parts > 0 then
ins(".")
end
end
local function insert_footnotes(footnotes)
if footnotes then
for _, footnote in ipairs(footnotes) do
ins(footnote)
end
end
end
if base.adj_stem then
ins("stem")
for _, stem in ipairs(base.adj_stem) do
ins(":")
ins(stem.form)
insert_footnotes(stem.footnotes)
end
end
if base.adj_suppress then
ins_dot()
ins("suppress:")
ins(base.adj_suppress)
end
if base.footnotes then
ins_dot()
insert_footnotes(base.footnotes)
end
for prop, _ in pairs(base.props) do
if is_adjectival_decl_indicator(prop) then
ins_dot()
ins(prop)
end
end
local adj_alternant_multiword_spec = require("Module:de-adjective").do_generate_forms(
{base.lemma .. "<" .. table.concat(adj_spec_parts) .. ">"}
)
local function copy(from_slot, to_slot)
base.forms[to_slot] = adj_alternant_multiword_spec.forms[from_slot]
end
local function copy_gender_forms(gender)
local number = gender == "p" and "p" or "s"
for _, state in ipairs(states) do
for _, case in ipairs(basic_cases) do
copy(state .. "_" .. case .. "_" .. gender, state .. "_" .. case .. "_" .. number)
end
end
end
if base.number == "pl" then
copy_gender_forms("p")
-- No need to specify gender for *_equiv; will be handled correctly in [[Module:de-headword]].
add(base, "m_equiv", base.lemma, "e")
add(base, "f_equiv", base.lemma, "e")
add(base, "n_equiv", base.lemma, "e")
else
-- Normally there should be only one gender.
for _, genderspec in ipairs(base.genders) do
local gender = genderspec.form
copy_gender_forms(gender)
-- No need to specify gender for *_equiv; will be handled correctly in [[Module:de-headword]].
add(base, "m_equiv", base.lemma, "er") -- masculine
add(base, "f_equiv", base.lemma, "e") -- feminine
add(base, "n_equiv", base.lemma, "es") -- neuter
end
if base.number ~= "sg" then
copy_gender_forms("p")
end
end
end
-- Return the slots that may contain a lemma, in the order they should be checked. `props` is a property table,
-- coming either from `base` or `alternant_multiword_spec`.
local function get_lemma_slots(props)
if props.surname then
return {"nom_m_s"}
elseif props.overall_adj then
return {"str_nom_s", "str_nom_p"}
else
return {"nom_s", "nom_p"}
end
end
-- Return the lemmas for this term. The return value is a list of {form = FORM, footnotes = FOOTNOTES}.
-- If `linked_variant` is given, return the linked variants (with embedded links if specified that way by the user),
-- otherwies return variants with any embedded links removed. If `remove_footnotes` is given, remove any
-- footnotes attached to the lemmas.
function export.get_lemmas(alternant_multiword_spec, linked_variant, remove_footnotes)
local slots_to_fetch = get_lemma_slots(alternant_multiword_spec.props)
local linked_suf = linked_variant and "_linked" or ""
for _, slot in ipairs(slots_to_fetch) do
if alternant_multiword_spec.forms[slot .. linked_suf] then
local lemmas = alternant_multiword_spec.forms[slot .. linked_suf]
if remove_footnotes then
local lemmas_no_footnotes = {}
for _, lemma in ipairs(lemmas) do
table.insert(lemmas_no_footnotes, {form = lemma.form})
end
return lemmas_no_footnotes
else
return lemmas
end
end
end
return {}
end
local function handle_derived_slots_and_overrides(base)
process_slot_overrides(base)
-- Compute linked versions of potential lemma slots, for use in {{de-noun}}.
-- We substitute the original lemma (before removing links) for forms that
-- are the same as the lemma, if the original lemma has links.
for _, slot in ipairs(get_lemma_slots(base.props)) do
iut.insert_forms(base.forms, slot .. "_linked", iut.map_forms(base.forms[slot], function(form)
if form == base.orig_lemma_no_links and rfind(base.orig_lemma, "%[%[") then
return base.orig_lemma
else
return form
end
end))
end
end
-- Like put.split_alternating_runs_and_strip_spaces(), but ensure that backslash-escaped commas and periods are not
-- treated as separators.
local function split_alternating_runs_with_escapes(segments, splitchar)
for i, segment in ipairs(segments) do
segments[i] = rsub(segment, "\\,", SUB_ESCAPED_COMMA)
segments[i] = rsub(segment, "\\%.", SUB_ESCAPED_PERIOD)
end
local separated_groups = put.split_alternating_runs_and_strip_spaces(segments, splitchar)
for _, separated_group in ipairs(separated_groups) do
for i, segment in ipairs(separated_group) do
separated_group[i] = rsub(segment, SUB_ESCAPED_COMMA, ",")
separated_group[i] = rsub(segment, SUB_ESCAPED_PERIOD, ".")
end
end
return separated_groups
end
--[=[
Parse an indicator spec (text consisting of angle brackets and zero or more dot-separated indicators within them).
Return value is an object of the form
{
overrides = {
SLOT = {OVERRIDE, OVERRIDE, ...},
...
}, -- where OVERRIDE is {form = FORM, footnotes = FOOTNOTES}; same as `forms` table; FORM can be a full form (only if
beginning with a capital letter or !), otherwise an ending; "-" for an ending means a null ending, while
"--" suppresses the slot entirely, i.e. it is defective
gens = {GEN_SG_SPEC, GEN_SG_SPEC, ...}, same form as OVERRIDE above
pls = {PL_SPEC, PL_SPEC, ...}, same form as OVERRIDE above
forms = {}, -- forms for a single spec alternant; see `forms` below
props = {
PROP = true,
PROP = true,
...
}, -- misc Boolean properties: "weak" (weak noun); "adj" (adjectival noun; set using "+");
"ss" (lemma in -ß changes to -ss- before endings beginning with a vowel; pre-1996 spelling);
"nodatpln" (suppress automatic addition of 'n' in the dative plural after '-e', '-er', '-el')
number = "NUMBER", -- "sg", "pl", "both"; may be missing
adj = true, -- may be missing
-- The following additional fields are added by other functions:
orig_lemma = "ORIGINAL-LEMMA", -- as given by the user or taken from pagename
orig_lemma_no_links = "ORIGINAL-LEMMA-NO-LINKS", -- links removed
lemma = "LEMMA", -- `orig_lemma_no_links`,
forms = {
SLOT = {
{
form = "FORM",
footnotes = {"FOOTNOTE", "FOOTNOTE", ...} -- may be missing
},
...
},
...
},
}
]=]
local function parse_indicator_spec(angle_bracket_spec, lemma, pagename, proper_noun)
if lemma == "" then
lemma = pagename
end
local base = {forms = {}, overrides = {}, props = {prop = proper_noun}}
base.orig_lemma = lemma
base.orig_lemma_no_links = m_links.remove_links(lemma)
base.lemma = base.orig_lemma_no_links
local inside = rmatch(angle_bracket_spec, "^<(.*)>$")
assert(inside)
local function parse_err(msg)
error(msg .. ": <" .. inside .. ">")
end
--[=[
Parse a single override spec and return three values: the slot the override applies to, the original indicator
spec used to specify the slot, and the override specs. The input is a list where the footnotes have been separated
out. For example, given the spec 'dat:-[referring to a card suit, as a term of endearment, and generally in speech]:en[in most cases in writing]',
the input will be a list {"dat:-", "[referring to a card suit, as a term of endearment, and generally in speech]", ":en",
"[in most cases in writing]", ""}
]=]
local function parse_override(segments)
local part = segments[1]
local offset = 4
local case = usub(part, 1, 3)
if not case_set_with_abl_voc[case] then
parse_err("Internal error: unrecognized case in override: '" .. table.concat(segments) .. "'")
end
local indicator = case
local rest = usub(part, offset)
local slot
if rfind(rest, "^pl") then
rest = rsub(rest, "^pl", "")
slot = case .. "_p"
indicator = indicator .. "pl"
else
slot = case .. "_s"
end
if rfind(rest, "^:") then
rest = rsub(rest, "^:", "")
else
parse_err("Slot indicator '" .. indicator .. "' must be followed by a colon: '" .. table.concat(segments) .. "'")
end
if not noun_slot_set[slot] then
parse_err("Unrecognized slot indicator '" .. indicator .. "': '" .. table.concat(segments) .. "'")
end
segments[1] = rest
return slot, indicator, com.fetch_specs(segments, ":", "override", nil, parse_err)
end
if inside ~= "" then
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local dot_separated_groups = split_alternating_runs_with_escapes(segments, "%.")
for i, dot_separated_group in ipairs(dot_separated_groups) do
local part = dot_separated_group[1]
if i == 1 then
local comma_separated_groups = split_alternating_runs_with_escapes(dot_separated_group, ",")
base.genders = com.fetch_specs(comma_separated_groups[1], ":", "gender", nil, parse_err)
local saw_sg = false
local saw_pl = false
local saw_gendered_pl = false
local saw_non_gendered_pl = false
local saw_adj = false
local special_variant = nil
for _, genderspec in ipairs(base.genders) do
local g = genderspec.form
if g == "m" or g == "n" then
-- Set this on `base.props` as it's used in various other places.
base.props.saw_mn = true
saw_sg = true
elseif g == "f" then
saw_sg = true
elseif g == "p" then
saw_pl = true
saw_non_gendered_pl = true
elseif rfind(g, "^[mfn]p$") then
saw_pl = true
saw_gendered_pl = true
elseif g == "+" or g == "p+" or g == "+p" then
if #base.genders > 1 then
parse_err("Can't specify multiple genders with adjectival declension")
end
saw_adj = true
if g ~= "+" then
saw_pl = true
end
elseif g == "surname" or g == "toponym" or g == "langname" then
if #base.genders > 1 then
parse_err("Can't specify multiple genders with " .. g .. " declension")
end
special_variant = g
else
parse_err("Unrecognized gender spec '" .. g .. "'")
end
end
if saw_sg and saw_pl then
parse_err("Can't specify both singular and plural gender specs")
end
if saw_gendered_pl and saw_non_gendered_pl then
parse_err("Can't specify both 'p' and gendered plural specs")
end
local gen_index = (base.props.saw_mn or special_variant) and 2 or 1
local pl_index =
(saw_adj or saw_pl) and 1 or
(base.props.saw_mn or special_variant == "surname" or special_variant == "toponym") and 3 or
2
if #comma_separated_groups > pl_index then
if saw_adj then
parse_err("Can't specify plurals or genitives with adjectival declension")
elseif saw_pl then
parse_err("Can't specify plurals or genitives with plural-only nouns")
elseif base.props.saw_mn then
parse_err("Can specify at most three comma-separated specs when the gender is masculine or "
.. "neuter (gender, genitive, plural)")
elseif special_variant == "surname" or special_variant == "toponym" then
parse_err("Can specify at most three comma-separated specs with '" .. special_variant .. "' "
.. "nouns ('" .. special_variant .. "', genitive, plural)")
elseif special_variant == "langname" then
parse_err("Can specify at most two comma-separated specs with 'langname' "
.. " ('langname', genitive)")
else
parse_err("Can specify at most two comma-separated specs when the gender is feminine "
.. "(gender, plural)")
end
end
if #comma_separated_groups >= gen_index and gen_index > 1 then
base.gens = com.fetch_specs(comma_separated_groups[gen_index], ":", "genitive", "allow blank", parse_err)
end
if #comma_separated_groups >= pl_index and pl_index > gen_index then
base.pls = com.fetch_specs(comma_separated_groups[pl_index], ":", "plural", "allow blank", parse_err)
end
if special_variant then
if #base.genders > 1 then
parse_err("Internal error: More than one gender spec for '" .. special_variant .. "'")
else
base.props[special_variant] = true
if special_variant == "surname" then
-- FIXME, does it make sense to put the footnotes on the feminine gender (they appear after the gender)?
base.genders = {{form = "m"}, {form = "f", footnotes = base.genders[1].footnotes}}
else
base.genders = {{form = "n", footnotes = base.genders[1].footnotes}}
end
end
elseif saw_adj then
if #base.genders > 1 then
parse_err("Internal error: More than one gender spec for adjectival declension")
else
base.props.adj = true
if saw_pl then
base.number = "pl"
base.genders = {{form = "p", footnotes = base.genders[1].footnotes}}
else
-- Stash the footnotes into `adj_footnotes`; we will put them onto the autodetected gender
-- in determine_adjectival_genders(), which will set base.genders appropriately.
base.adj_footnotes = base.genders[1].footnotes
base.genders = {}
end
end
elseif saw_pl then
-- Convert 'mp' to 'm-p', 'fp' to 'f-p', etc. as that's what [[Module:gender and number]] expects.
for _, genderspec in ipairs(base.genders) do
local gender = rmatch(genderspec.form, "^([mfn])p$")
if gender then
genderspec.form = gender .. "-p"
end
end
base.number = "pl"
end
elseif base.props.adj and part:find("^stem:") then
dot_separated_group[1] = rsub(part, "^stem:", "")
base.adj_stem = com.fetch_specs(dot_separated_group, ":", "adjectival stem", nil, parse_err)
elseif base.props.adj and part:find("^suppress:") then
if #dot_separated_group > 1 then
parse_err("Can't specify footnotes with suppress: '" .. table.concat(dot_separated_group) .. "'")
end
-- No need to parse or validate more. Will happen in [[Module:de-adjective]].
base.adj_suppress = rsub(part, "suppress:", "")
elseif part == "" then
if #dot_separated_group == 1 then
parse_err("Blank indicator")
end
base.footnotes = com.fetch_footnotes(dot_separated_group, parse_err)
elseif part:find(":") then
-- override
-- FIXME: Handle adjectival overrides
local case_prefix = usub(part, 1, 3)
if case_set_with_abl_voc[case_prefix] then
local slot, slot_indicator, override = parse_override(dot_separated_group)
if base.overrides[slot] then
parse_err("Can't specify override twice for slot '" .. slot_indicator .. "'")
else
base.overrides[slot] = override
end
else
parse_err("Unrecognized indicator '" .. part .. "'")
end
elseif #dot_separated_group > 1 then
local errmsg
if base.props.adj then
errmsg = "Footnotes only allowed with slot overrides, 'stem:' or by themselves"
else
errmsg = "Footnotes only allowed with genitive, plural, slot overrides or by themselves"
end
parse_err(errmsg .. ": '" .. table.concat(dot_separated_group) .. "'")
elseif part == "sg" or part == "both" then
if base.number then
if base.number ~= part then
parse_err("Can't specify '" .. part .. "' along with '" .. base.number .. "'")
else
parse_err("Can't specify '" .. part .. "' twice")
end
end
base.number = part
elseif not base.props.adj and (part == "weak" or part == "weak_n" or part == "ss" or part == "nodatpln" or part == "article" or part == "dat_with_e") then
if base.props[part] then
parse_err("Can't specify '" .. part .. "' twice")
end
base.props[part] = true
if part == "weak_n" then
-- weak_n implies weak
base.props.weak = true
end
elseif base.props.adj and (part == "article" or is_adjectival_decl_indicator(part)) then
if base.props[part] then
parse_err("Can't specify '" .. part .. "' twice")
end
base.props[part] = true
else
parse_err("Unrecognized indicator '" .. part .. "'")
end
end
end
return base
end
-- For an adjectival lemma, synthesize the predicative (lemma) form. It doesn't have to be perfect in that the
-- predicative form itself isn't used, so we don't have to try to convert -abler -> -abel or anything like that.
local function synthesize_adj_lemma(base)
local stem, ending = rmatch(base.lemma, "^(.*)(e[rs]?)$")
if not stem then
error("Unrecognized adjectival lemma, should end in '-er', '-e' or '-es': '" .. base.lemma .. "'")
end
base.lemma = stem
-- Will be ignored if number == "pl"
if ending == "er" then
base.autodetected_gender = "m"
elseif ending == "e" then
base.autodetected_gender = "f"
else
base.autodetected_gender = "n"
end
end
local function detect_indicator_spec(alternant_multiword_spec, base)
if base.props.article then
alternant_multiword_spec.props.article = true
end
for _, prop in ipairs {"surname", "toponym", "langname"} do
if alternant_multiword_spec.props[prop] == nil then
alternant_multiword_spec.props[prop] = base.props[prop]
elseif alternant_multiword_spec.props[prop] ~= base.props[prop] then
-- We do this because we have a special table with its own slots for each of these special variants.
-- FIXME: Consider supporting adjectives with these variants. That requires that we copy the adjectival
-- declensions to the appropriate per-variant slots.
error("If some alternants set '" .. prop .. "', all must do so")
end
end
if base.props.adj then
alternant_multiword_spec.props.overall_adj = true
synthesize_adj_lemma(base)
else
-- Set default values.
base.number =
base.number or
base.props.surname and "both" or
base.pls and "both" or
(alternant_multiword_spec.props.is_proper or base.props.toponym or base.props.langname) and "sg" or
"both"
if not base.props.surname then
if base.number == "pl" then
if base.gens then
error("Internal error: With plural-only noun, no genitive singular specs should be allowed")
end
if base.pls then
error("Internal error: With plural-only noun, no plural specs should be allowed")
end
end
if base.pls and base.number == "sg" then
error("Can't specify explicit plural specs along with explicit '.sg'")
end
end
base.gens = base.gens or {{form = "+"}}
base.pls = base.pls or {{form = "+"}}
end
end
local function detect_all_indicator_specs(alternant_multiword_spec)
iut.map_word_specs(alternant_multiword_spec, function(base)
detect_indicator_spec(alternant_multiword_spec, base)
end)
-- Now propagate some properties downwards.
iut.map_word_specs(alternant_multiword_spec, function(base)
base.props.overall_adj = alternant_multiword_spec.props.overall_adj
end)
end
local propagate_multiword_properties
local function propagate_alternant_properties(alternant_spec, property, mixed_value, nouns_only)
local seen_property
for _, multiword_spec in ipairs(alternant_spec.alternants) do
propagate_multiword_properties(multiword_spec, property, mixed_value, nouns_only)
if seen_property == nil then
seen_property = multiword_spec[property]
elseif multiword_spec[property] and seen_property ~= multiword_spec[property] then
seen_property = mixed_value
end
end
alternant_spec[property] = seen_property
end
propagate_multiword_properties = function(multiword_spec, property, mixed_value, nouns_only)
local seen_property = nil
local last_seen_nounal_pos = 0
local word_specs = multiword_spec.alternant_or_word_specs or multiword_spec.word_specs
for i = 1, #word_specs do
local is_nounal
if word_specs[i].alternants then
propagate_alternant_properties(word_specs[i], property, mixed_value)
is_nounal = not not word_specs[i][property]
elseif nouns_only then
is_nounal = not word_specs[i].props.adj
else
is_nounal = not not word_specs[i][property]
end
if is_nounal then
if not word_specs[i][property] then
error("Internal error: noun-type word spec without " .. property .. " set")
end
for j = last_seen_nounal_pos + 1, i - 1 do
word_specs[j][property] = word_specs[j][property] or word_specs[i][property]
end
last_seen_nounal_pos = i
if seen_property == nil then
seen_property = word_specs[i][property]
elseif seen_property ~= word_specs[i][property] then
seen_property = mixed_value
end
end
end
if last_seen_nounal_pos > 0 then
for i = last_seen_nounal_pos + 1, #word_specs do
word_specs[i][property] = word_specs[i][property] or word_specs[last_seen_nounal_pos][property]
end
end
multiword_spec[property] = seen_property
end
local function propagate_properties_downward(alternant_multiword_spec, property, default_propval)
local propval1 = alternant_multiword_spec[property] or default_propval
alternant_multiword_spec[property] = propval1
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
local propval2 = alternant_or_word_spec[property] or propval1
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
local propval3 = multiword_spec[property] or propval2
for _, word_spec in ipairs(multiword_spec.word_specs) do
local propval4 = word_spec[property] or propval3
if propval4 == "mixed" then
error("Attempt to assign mixed " .. property .. " to word")
end
word_spec[property] = propval4
end
end
else
if propval2 == "mixed" then
error("Attempt to assign mixed " .. property .. " to word")
end
alternant_or_word_spec[property] = propval2
end
end
end
--[=[
Propagate `property` ("genders" or "number") from nouns to adjacent adjectives. We proceed as follows:
1. We assume the properties in question are already set on all nouns. This should happen in parse_indicator_spec().
2. We first propagate properties upwards and sideways. We recurse downwards from the top. When we encounter a
multiword spec, we proceed left to right looking for a noun. When we find a noun, we fetch its property
(recursing if the noun is an alternant), and propagate it to any adjectives to its left, up to the next noun
to the left. When we have processed the last noun, we also propagate its property value to any adjectives to the
right. Finally, we set the property value for the multiword spec itself by combining all the non-nil properties of
the individual elements. If all non-nil properties have the same value, the result is that value, otherwise it is
`mixed_value` (which is "mixed" gender, but "both" for number).
3. When we encounter an alternant spec in this process, we recursively process each alternant (which is a multiword
spec) using the previous step, and combine any non-nil properties we encounter the same way as for multiword specs.
4. The effect of steps 2 and 3 is to set the property of each alternant and multiword spec based on its children or its
neighbors.
]=]
local function propagate_properties(alternant_multiword_spec, property, default_propval, mixed_value)
propagate_multiword_properties(alternant_multiword_spec, property, mixed_value, "nouns only")
propagate_multiword_properties(alternant_multiword_spec, property, mixed_value, false)
propagate_properties_downward(alternant_multiword_spec, property, default_propval)
end
-- Set the gender of adjectives and adjectival nouns to the gender autodetected during synthesize_adj_lemma(),
-- unless the form is plural. We don't just set the gender directly in synthesize_adj_lemma() because we don't know
-- until later (i.e. when propagate_properties() is called) whether an adjectival form in -e is feminine or plural.
-- We set the footnotes (i.e. qualifiers) of the gender to the footnotes (if any) specified directly after '+'.
local function determine_adjectival_genders(alternant_multiword_spec)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.props.adj and #base.genders == 0 then
base.genders = {{form = base.number == "pl" and "p" or base.autodetected_gender, footnotes = base.adj_footnotes}}
end
end)
end
-- Find the first noun in a multiword expression and set alternant_multiword_spec.first_noun
-- to the index of that noun. Also find the first adjective and set alternant_multiword_spec.first_adj
-- similarly. If there is a first noun, we use its properties to determine the overall expression's
-- properties; otherwise we use the first adjective's properties, otherwise the first word's properties.
-- If the "word" located this way is not an alternant spec, we just use its properties directly, otherwise
-- we use the properties of the first noun (or failing that the first adjective, or failing that the
-- first word) in each alternative alternant in the alternant spec. For this reason, we need to set the
-- the .first_noun of and .first_adj of each multiword expression embedded in the first noun alternant spec,
-- and the .first_adj of each multiword expression in each adjective alternant spec leading up to the
-- first noun alternant spec.
local function determine_noun_status(alternant_multiword_spec)
for i, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
if alternant_or_word_spec.alternants then
local alternant_type
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for j, word_spec in ipairs(multiword_spec.word_specs) do
if not word_spec.props.adj then
multiword_spec.first_noun = j
alternant_type = "noun"
break
elseif not multiword_spec.first_adj then
multiword_spec.first_adj = j
if not alternant_type then
alternant_type = "adj"
end
end
end
end
if alternant_type == "noun" then
alternant_multiword_spec.first_noun = i
return
elseif alternant_type == "adj" and not alternant_multiword_spec.first_adj then
alternant_multiword_spec.first_adj = i
end
else
if not alternant_or_word_spec.props.adj then
alternant_multiword_spec.first_noun = i
return
elseif not alternant_multiword_spec.first_adj then
alternant_multiword_spec.first_adj = i
end
end
end
end
local function decline_noun_or_adjective(base)
if base.props.surname then
decline_surname(base)
elseif base.props.toponym then
decline_toponym(base)
elseif base.props.langname then
decline_langname(base)
elseif base.props.adj then
decline_adjective(base)
else
decline_noun(base)
end
handle_derived_slots_and_overrides(base)
end
-- Set the overall articles. We can't do this using the normal inflection code as it will produce e.g.
-- '[[der]] [[und]] [[der]]' for conjoined nouns.
local function compute_non_surname_articles(alternant_multiword_spec)
if alternant_multiword_spec.number ~= "pl" then
iut.map_word_specs(alternant_multiword_spec, function(base)
for _, genderspec in ipairs(base.genders) do
for _, case in ipairs(cases_with_abl_voc) do
for _, def in ipairs(definitenesses) do
iut.insert_form(alternant_multiword_spec.forms, "art_" .. def .. "_" .. case .. "_s",
{form = com.articles[genderspec.form][def .. "_" .. case]})
end
end
end
end)
end
for _, case in ipairs(basic_cases) do
for _, def in ipairs(definitenesses) do
iut.insert_form(alternant_multiword_spec.forms, "art_" .. def .. "_" .. case .. "_p",
{form = com.articles.p[def .. "_" .. case]})
end
end
end
-- Set the overall surname articles. We can't do this using the normal inflection code as it will produce e.g.
-- '[[der]] [[und]] [[der]]' for conjoined nouns.
local function compute_surname_articles(alternant_multiword_spec)
for _, gender in ipairs {"m", "f"} do
for _, case in ipairs(basic_cases) do
for _, def in ipairs(definitenesses) do
iut.insert_form(alternant_multiword_spec.forms, "art_" .. def .. "_" .. case .. "_" .. gender .. "_s",
{form = "([[" .. com.articles[gender][def .. "_" .. case] .. "]])"})
end
end
end
for _, case in ipairs(basic_cases) do
iut.insert_form(alternant_multiword_spec.forms, "art_def_" .. case .. "_p",
{form = "([[" .. com.articles.p["def_" .. case] .. "]])"})
end
end
local function compute_articles(alternant_multiword_spec)
if alternant_multiword_spec.props.surname then
compute_surname_articles(alternant_multiword_spec)
else
compute_non_surname_articles(alternant_multiword_spec)
end
end
-- Call a function `fun` over the first noun in the `alternant_multiword_spec`, or over the first noun in each
-- alternant if there is more than one alternant. If there are no nouns, use the first adjective (in the case of an
-- adjectival noun).
local function map_first_noun(alternant_multiword_spec, fun)
local key_entry = alternant_multiword_spec.first_noun or alternant_multiword_spec.first_adj or 1
if #alternant_multiword_spec.alternant_or_word_specs >= key_entry then
local alternant_or_word_spec = alternant_multiword_spec.alternant_or_word_specs[key_entry]
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
key_entry = multiword_spec.first_noun or multiword_spec.first_adj or 1
if #multiword_spec.word_specs >= key_entry then
fun(multiword_spec.word_specs[key_entry])
end
end
else
fun(alternant_or_word_spec)
end
end
end
-- Compute the categories to add the noun to, as well as the annotation to display in the
-- declension title bar. We combine the code to do these functions as both categories and
-- title bar contain similar information.
local function compute_categories_and_annotation(alternant_multiword_spec)
alternant_multiword_spec.categories = {}
alternant_multiword_spec.decl_type = {}
local function insert(cattype)
cattype = rsub(cattype, "~", alternant_multiword_spec.pos)
m_table.insertIfNot(alternant_multiword_spec.categories, cattype:find("的") and cattype:gsub("的", "的德語") or "德語" .. cattype) -- L10N
end
if not alternant_multiword_spec.props.is_proper and alternant_multiword_spec.number == "sg" then
insert("不可數~")
elseif alternant_multiword_spec.number == "pl" then
insert("唯複名詞")
end
local annotation
local annparts = {}
local genderdescs = {}
local decldescs = {}
if alternant_multiword_spec.number == "sg" then
table.insert(annparts, "僅單數")
elseif alternant_multiword_spec.number == "pl" and alternant_multiword_spec.genders[1].spec ~= "p" then
-- If the gender is just 'p', we use "pl-only" below as a substitute for the gender and hook any qualifiers
-- onto it. Note that when 'p' is the gender, there can be only one gender.
table.insert(annparts, "僅複數")
end
for i, genderspec in ipairs(alternant_multiword_spec.genders) do
local genderdesc_parts = {}
local gender = genderspec.spec
if gender == "p" then
table.insert(genderdesc_parts, "僅複數")
else
gender = rsub(gender, "%-p$", "")
table.insert(genderdesc_parts, gender_spec_to_full_gender[gender])
end
if genderspec.q then
table.insert(genderdesc_parts, " ''(")
table.insert(genderdesc_parts, table.concat(genderspec.q, ", "))
table.insert(genderdesc_parts, ")''")
end
table.insert(genderdescs, table.concat(genderdesc_parts))
end
local function insert_decl_type(decl_type)
m_table.insertIfNot(decldescs, decl_type)
m_table.insertIfNot(alternant_multiword_spec.decl_type, decl_type)
end
local function do_word_spec(base)
if base.props.surname then
m_table.insertIfNot(decldescs, "姓氏")
elseif base.props.toponym then
m_table.insertIfNot(decldescs, "地名")
elseif base.props.langname then
m_table.insertIfNot(decldescs, "語言名")
elseif base.decl_type and #base.decl_type > 0 then
-- strong/weak/mixed declension type; should only be present on masculine or neuter nouns with a plural
for _, decl_type in ipairs(base.decl_type) do
if decl_type == "弱變化" then
insert("弱變化~")
elseif decl_type == "混合變化" then
insert("混合變化~")
end
insert_decl_type(decl_type)
end
elseif base.props.saw_mn then
-- For singular-only masculine or neuter nouns, we can still classify as strong or weak.
-- We don't try to classify plural-only nouns. Even for nouns in -n or -en, we have no idea if they are
-- strong (-en is part of the stem), mixed or weak.
if base.props.weak then
insert("弱變化~")
insert_decl_type("弱變化")
else
insert_decl_type("強變化")
end
end
end
-- Use the surname/toponym/langname/weak/strong properties of the noun(s).
map_first_noun(alternant_multiword_spec, do_word_spec)
if #genderdescs > 0 then
table.insert(annparts, table.concat(genderdescs, " // "))
end
if #decldescs > 0 then
table.insert(annparts, table.concat(decldescs, " // "))
end
if not alternant_multiword_spec.first_noun and alternant_multiword_spec.first_adj then
insert("形容詞性~")
table.insert(annparts, "形容詞性")
end
if alternant_multiword_spec.props.langname then
insert("特殊變化語言名稱")
end
alternant_multiword_spec.annotation = table.concat(annparts, ", ")
end
local function compute_headword_genders(alternant_multiword_spec)
alternant_multiword_spec.genders = {}
-- Compute the genders based on the nouns. We don't want to use the adjectives in adjective-noun combinations
-- because that will cause issues in plural-only expressions like [[Kanarische Inseln]], where ''Inseln'' may be
-- 'f-p' but ''Kanarische'' will be just 'p', and we'd end up with both genders.
map_first_noun(alternant_multiword_spec, function(base)
for _, genderspec in ipairs(base.genders) do
-- Create the new spec to insert.
local spec = {spec = genderspec.form, q = genderspec.footnotes}
-- See if the gender of the spec is already present; if so, combine qualifiers.
local saw_existing = false
for _, existing_spec in ipairs(alternant_multiword_spec.genders) do
if existing_spec.spec == spec.spec then
existing_spec.q = iut.combine_footnotes(existing_spec.q, spec.q)
saw_existing = true
break
end
end
-- If not, add gender.
if not saw_existing then
table.insert(alternant_multiword_spec.genders, spec)
end
end
end)
-- Now convert the footnotes in the gender specs to qualifiers. This involves removing brackets and expanding any
-- footnote abbreviations.
for _, genderspec in ipairs(alternant_multiword_spec.genders) do
if genderspec.q then
local processed_q = {}
for _, q in ipairs(genderspec.q) do
m_table.insertIfNot(processed_q,
iut.expand_footnote_or_references(q, "return raw", "no parse refs"))
end
genderspec.q = processed_q
end
end
end
-- Process the specs in `arg_specs` given for dim=, m=, f=, n= or sg= and store the results in `slot` in
-- `alternant_multiword_spec.forms`. `arg_specs` is a list of specs, each of which is a comma-separated or
-- colon-separated string of specs, where each spec may be a suffix like "in", or a suffix with umlaut like
-- "^chen", or a full form beginning with a capital letter or exclamation point. Suffixes are added onto the lemma
-- with -e removed if present. `default` is the default value to use if "+" is given as a spec, and `literal_default`,
-- if given, indicates that `default` is always a literal (full) form; otherwise, it `default` begins with a
-- lowercase letter, it is taken as a suffix. (This is used in cases like the feminine of [[ordenlicher Professor]],
-- which is generated as "ordentiche Professorin"; we don't want this interpreted as a suffix.) `desc` is an English
-- description of the form whose specs are being processed, for display in error messages.
local function process_dim_m_f_n(alternant_multiword_spec, arg_specs, default, literal_default, slot, desc)
-- We don't want footnotes attached to a lemma to end up in the output. These footnotes typically get there if the
-- syntax `.[footnote]` is used, which attaches a footnote to every form.
local lemmas = export.get_lemmas(alternant_multiword_spec, nil, "remove footnotes")
lemmas = iut.map_forms(lemmas, function(form)
return rsub(form, "e$", "")
end)
for _, spec in ipairs(arg_specs) do
local function parse_err(msg)
error(msg .. ": " .. spec)
end
local segments = put.parse_balanced_segment_run(spec, "[", "]")
-- Allow comma (preferred) or colon as separator.
local ending_specs = com.fetch_specs(segments, "[,:]", desc, nil, parse_err)
-- FIXME, this should propagate the 'ss' property upwards
local props = {}
local function do_combine_stem_ending(stem, ending)
return combine_stem_ending(props, stem, ending)
end
local function process(stem, ending)
iut.add_forms(alternant_multiword_spec.forms, slot, stem or lemmas, ending, do_combine_stem_ending)
end
process_spec(ending_specs, nil, default, literal_default, desc, process)
end
end
local function show_forms(alternant_multiword_spec)
local lemmas = export.get_lemmas(alternant_multiword_spec)
local props = {
lang = lang,
lemmas = lemmas,
slot_list = alternant_multiword_spec.props.surname and surname_slot_list_with_linked_and_articles
or alternant_multiword_spec.props.langname and langname_slot_list_with_linked_and_articles
or alternant_multiword_spec.props.overall_adj and adjectival_slot_list_with_linked_and_articles
or noun_slot_list_with_linked_and_articles,
}
iut.show_forms(alternant_multiword_spec.forms, props)
end
local noun_template_both = [=[
<div class="NavFrame">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table inflection-table-de inflection-table-de-{decl_type}"
! style="{BG2};width:15%" |
! colspan="3" style="{BG2};width:46%" | 单数
! colspan="2" style="{BG2};width:39%" | 复数
|-
! style="{BG3}" |
! style="{BG3};width:7%" | [[不定冠词]]
! style="{BG3};width:7%" | [[定冠词]]
! style="{BG3};width:32%" | 名词
! style="{BG3};width:7%" | [[定冠词]]
! style="{BG3};width:32%" | 名词
|-
! style="{BG3}" | 主格
| style="{BG4}" | {art_ind_nom_s}
| style="{BG4}" | {art_def_nom_s}
| {nom_s}
| style="{BG4}" | {art_def_nom_p}
| {nom_p}
|-
! style="{BG3}" | 屬格
| style="{BG4}" | {art_ind_gen_s}
| style="{BG4}" | {art_def_gen_s}
| {gen_s}
| style="{BG4}" | {art_def_gen_p}
| {gen_p}
|-
! style="{BG3}" | 与格
| style="{BG4}" | {art_ind_dat_s}
| style="{BG4}" | {art_def_dat_s}
| {dat_s}
| style="{BG4}" | {art_def_dat_p}
| {dat_p}
|-
! style="{BG3}" | 宾格
| style="{BG4}" | {art_ind_acc_s}
| style="{BG4}" | {art_def_acc_s}
| {acc_s}
| style="{BG4}" | {art_def_acc_p}
| {acc_p}
|{\cl}{notes_clause}</div></div>]=]
local noun_template_both_no_indef = [=[
<div class="NavFrame" style="width:93%">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table inflection-table-de inflection-table-de-{decl_type}"
! style="{BG2};width:15%" |
! colspan="2" style="{BG2};width:39%" | 單數
! colspan="2" style="{BG2};width:39%" | 複數
|-
! style="{BG3}" |
! style="{BG3};width:7%" | [[定冠詞]]
! style="{BG3};width:32%" | 名詞
! style="{BG3};width:7%" | [[定冠詞]]
! style="{BG3};width:32%" | 名詞
|-
! style="{BG3}" | 主格
| style="{BG4}" | {art_def_nom_s}
| {nom_s}
| style="{BG4}" | {art_def_nom_p}
| {nom_p}
|-
! style="{BG3}" | 屬格
| style="{BG4}" | {art_def_gen_s}
| {gen_s}
| style="{BG4}" | {art_def_gen_p}
| {gen_p}
|-
! style="{BG3}" | 與格
| style="{BG4}" | {art_def_dat_s}
| {dat_s}
| style="{BG4}" | {art_def_dat_p}
| {dat_p}
|-
! style="{BG3}" | 賓格
| style="{BG4}" | {art_def_acc_s}
| {acc_s}
| style="{BG4}" | {art_def_acc_p}
| {acc_p}
|{\cl}{notes_clause}</div></div>]=]
local noun_template_abl_voc = [=[
|-
! style="{BG3}" | 離格
| style="{BG4}" | {art_ind_abl_s}
| style="{BG4}" | {art_def_abl_s}
| {abl_s}
|-
! style="{BG3}" | 呼格
| style="{BG4}" | {art_ind_voc_s}
| style="{BG4}" | {art_def_voc_s}
| {voc_s}]=]
local noun_template_sg = [=[
<div class="NavFrame" style="width:61%">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table inflection-table-de inflection-table-de-{decl_type}"
! style="{BG2};width:24.6%" |
! colspan="3" style="{BG2};" | 單數
|-
! style="{BG3}" |
! style="{BG3};width:11.5%" | [[不定冠詞]]
! style="{BG3};width:11.5%" | [[定冠詞]]
! style="{BG3};width:52.5%" | 名詞
|-
! style="{BG3}" | 主格
| style="{BG4}" | {art_ind_nom_s}
| style="{BG4}" | {art_def_nom_s}
| {nom_s}
|-
! style="{BG3}" | 屬格
| style="{BG4}" | {art_ind_gen_s}
| style="{BG4}" | {art_def_gen_s}
| {gen_s}
|-
! style="{BG3}" | 與格
| style="{BG4}" | {art_ind_dat_s}
| style="{BG4}" | {art_def_dat_s}
| {dat_s}
|-
! style="{BG3}" | 賓格
| style="{BG4}" | {art_ind_acc_s}
| style="{BG4}" | {art_def_acc_s}
| {acc_s}{abl_voc_clause}
|{\cl}{notes_clause}</div></div>]=]
local noun_template_sg_no_indef = [=[
<div class="NavFrame" style="width:50%">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table inflection-table-de inflection-table-de-{decl_type}"
! style="{BG2};width:24.6%" |
! colspan="2" style="{BG2};" | 單數
|-
! style="{BG3}" |
! style="{BG3};width:11.5%" | [[定冠詞]]
! style="{BG3};width:52.5%" | 名詞
|-
! style="{BG3}" | 主格
| style="{BG4}" | {art_def_nom_s}
| {nom_s}
|-
! style="{BG3}" | 屬格
| style="{BG4}" | {art_def_gen_s}
| {gen_s}
|-
! style="{BG3}" | 與格
| style="{BG4}" | {art_def_dat_s}
| {dat_s}
|-
! style="{BG3}" | 賓格
| style="{BG4}" | {art_def_acc_s}
| {acc_s}{abl_voc_clause}
|{\cl}{notes_clause}</div></div>]=]
local noun_template_pl = [=[
<div class="NavFrame" style="width:61%">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table inflection-table-de inflection-table-de-{decl_type}"
! style="{BG2};width:24.6%" |
! colspan="2" style="{BG2};" | 複數
|-
! style="{BG3}" |
! style="{BG3};width:11.5%" | [[定冠詞]]
! style="{BG3};width:52.5%" | 名詞
|-
! style="{BG3}" | 主格
| style="{BG4}" | {art_def_nom_p}
| {nom_p}
|-
! style="{BG3}" | 屬格
| style="{BG4}" | {art_def_gen_p}
| {gen_p}
|-
! style="{BG3}" | 與格
| style="{BG4}" | {art_def_dat_p}
| {dat_p}
|-
! style="{BG3}" | 賓格
| style="{BG4}" | {art_def_acc_p}
| {acc_p}
|{\cl}{notes_clause}</div></div>]=]
local noun_template_surname = [=[
<div class="NavFrame">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table inflection-table-de inflection-table-de-{decl_type}"
! rowspan="2" style="{BG2};width:11%" |
! colspan="6" style="{BG2}" | 單數
! colspan="2" rowspan="2" style="{BG2}" | 複數
|-
! colspan="3" style="{BG2}" | 陽性
! colspan="3" style="{BG2}" | 陰性
|-
! style="{BG3}" |
! style="{BG3};width:4%" | [[不定冠詞]]
! style="{BG3};width:4%" | [[定冠詞]]
! style="{BG3};width:23%" | 名詞
! style="{BG3};width:4%" | [[不定冠詞]]
! style="{BG3};width:4%" | [[定冠詞]]
! style="{BG3};width:23%" | 名詞
! style="{BG3};width:4%" | [[定冠詞]]
! style="{BG3};width:23%" | 名詞
|-
! style="{BG3}" | 主格
| style="{BG4}" | {art_ind_nom_m_s}
| style="{BG4}" | {art_def_nom_m_s}
| {nom_m_s}
| style="{BG4}" | {art_ind_nom_f_s}
| style="{BG4}" | {art_def_nom_f_s}
| {nom_f_s}
| style="{BG4}" | {art_def_nom_p}
| {nom_p}
|-
! style="{BG3}" | 屬格
| style="{BG4}" | {art_ind_gen_m_s}
| style="{BG4}" | {art_def_gen_m_s}
| {gen_m_s}
| style="{BG4}" | {art_ind_gen_f_s}
| style="{BG4}" | {art_def_gen_f_s}
| {gen_f_s}
| style="{BG4}" | {art_def_gen_p}
| {gen_p}
|-
! style="{BG3}" | 與格
| style="{BG4}" | {art_ind_dat_m_s}
| style="{BG4}" | {art_def_dat_m_s}
| {dat_m_s}
| style="{BG4}" | {art_ind_dat_f_s}
| style="{BG4}" | {art_def_dat_f_s}
| {dat_f_s}
| style="{BG4}" | {art_def_dat_p}
| {dat_p}
|-
! style="{BG3}" | 賓格
| style="{BG4}" | {art_ind_acc_m_s}
| style="{BG4}" | {art_def_acc_m_s}
| {acc_m_s}
| style="{BG4}" | {art_ind_acc_f_s}
| style="{BG4}" | {art_def_acc_f_s}
| {acc_f_s}
| style="{BG4}" | {art_def_acc_p}
| {acc_p}
|{\cl}{notes_clause}</div></div>]=]
local noun_template_langname = [=[
<div class="NavFrame" style="width:100%">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table inflection-table-de inflection-table-de-langname"
! style="{BG2};width:15%" |
! colspan="5" style="{BG2};width:85%" | 單數([[Wiktionary:關於德語#語言名稱的變格|表格的用途和意義的解釋]])
|-
! style="{BG3}" |
! style="{BG3};width:14%" | (通常無冠詞)
! style="{BG3};width:32%" | 名詞
! style="{BG3};width:7%" | [[定冠詞]]
! style="{BG3};width:32%" | 名詞
|-
! style="{BG3}" | 主格
| style="{BG4}" | ({art_def_nom_s})
| {nom_s}
| style="{BG4}" | {art_def_nom_s}
| {nom_s_alt}
|-
! style="{BG3}" | 屬格
| style="{BG4}" | ({art_def_gen_s})
| {gen_s}
| style="{BG4}" | {art_def_gen_s}
| {gen_s_alt}
|-
! style="{BG3}" | 與格
| style="{BG4}" | ({art_def_dat_s})
| {dat_s}
| style="{BG4}" | {art_def_dat_s}
| {dat_s_alt}
|-
! style="{BG3}" | 賓格
| style="{BG4}" | ({art_def_acc_s})
| {acc_s}
| style="{BG4}" | {art_def_acc_s}
| {acc_s_alt}
|{\cl}{notes_clause}</div></div>]=]
local adjectival_template_both = [=[
<div class="NavFrame">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{\op}| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table"
! style="{BG3};width:15%" |
! colspan="2" style="{BG3}" | 單數
! colspan="2" style="{BG3}" | 複數
|-
! style="{BG2}" | {gender}
! colspan="4" style="{BG2}" | 強變化
|-
! style="{BG3}" | 主格
| colspan="2" | {str_nom_s}
| colspan="2" | {str_nom_p}
|-
! style="{BG3}" | 屬格
| colspan="2" | {str_gen_s}
| colspan="2" | {str_gen_p}
|-
! style="{BG3}" | 與格
| colspan="2" | {str_dat_s}
| colspan="2" | {str_dat_p}
|-
! style="{BG3}" | 賓格
| colspan="2" | {str_acc_s}
| colspan="2" | {str_acc_p}
|-
! style="{BG2}" |
! colspan="4" style="{BG2}" | 弱變化
|-
! style="{BG3}" | 主格
| style="{BG4};width:5em" | {art_def_nom_s}
| {wk_nom_s}
| style="{BG4};width:5em" | {art_def_nom_p}
| {wk_nom_p}
|-
! style="{BG3}" | 屬格
| style="{BG4};width:5em" | {art_def_gen_s}
| {wk_gen_s}
| style="{BG4};width:5em" | {art_def_gen_p}
| {wk_gen_p}
|-
! style="{BG3}" | 與格
| style="{BG4};width:5em" | {art_def_dat_s}
| {wk_dat_s}
| style="{BG4};width:5em" | {art_def_dat_p}
| {wk_dat_p}
|-
! style="{BG3}" | 賓格
| style="{BG4};width:5em" | {art_def_acc_s}
| {wk_acc_s}
| style="{BG4};width:5em" | {art_def_acc_p}
| {wk_acc_p}
|-
! style="{BG2}" |
! colspan="4" style="{BG2}" | 混合變化
|-
! style="{BG3}" | 主格
| style="{BG4};width:5em" | {art_ind_nom_s}
| {mix_nom_s}
| style="{BG4};width:5em" | {art_ind_nom_p}
| {mix_nom_p}
|-
! style="{BG3}" | 屬格
| style="{BG4};width:5em" | {art_ind_gen_s}
| {mix_gen_s}
| style="{BG4};width:5em" | {art_ind_gen_p}
| {mix_gen_p}
|-
! style="{BG3}" | 與格
| style="{BG4};width:5em" | {art_ind_dat_s}
| {mix_dat_s}
| style="{BG4};width:5em" | {art_ind_dat_p}
| {mix_dat_p}
|-
! style="{BG3}" | 賓格
| style="{BG4};width:5em" | {art_ind_acc_s}
| {mix_acc_s}
| style="{BG4};width:5em" | {art_ind_acc_p}
| {mix_acc_p}
|{\cl}{notes_clause}</div></div>]=]
local adjectival_template_sg = [=[
<div class="NavFrame" style="width:500px">
<div class="NavHead">{title}{annotation}</div>
<div class="NavContent">
{| style="border: 1px solid var(--wikt-palette-darkgrey,#505050); border-collapse:collapse; {BG1}; text-align:center; width:100%" class="inflection-table"
! style="{BG3};width:15%" |
! colspan="2" style="{BG3}" | 單數
|-
! style="{BG2}" | {gender}
! colspan="2" style="{BG2}" | 強變化
|-
! style="{BG3}" | 主格
| colspan="2" | {str_nom_s}
|-
! style="{BG3}" | 屬格
| colspan="2" | {str_gen_s}
|-
! style="{BG3}" | 與格
| colspan="2" | {str_dat_s}
|-
! style="{BG3}" | 賓格
| colspan="2" | {str_acc_s}
|-
! style="{BG2}" |
! colspan="2" style="{BG2}" | 弱變化
|-
! style="{BG3}" | 主格
| style="{BG4};width:5em" | {art_def_nom_s}
| {wk_nom_s}
|-
! style="{BG3}" | 屬格
| style="{BG4};width:5em" | {art_def_gen_s}
| {wk_gen_s}
|-
! style="{BG3}" | 與格
| style="{BG4};width:5em" | {art_def_dat_s}
| {wk_dat_s}
|-
! style="{BG3}" | 賓格
| style="{BG4};width:5em" | {art_def_acc_s}
| {wk_acc_s}
|-
! style="{BG2}" |
! colspan="2" style="{BG2}" | 混合變化
|-
! style="{BG3}" | 主格
| style="{BG4};width:5em" | {art_ind_nom_s}
| {mix_nom_s}
|-
! style="{BG3}" | 屬格
| style="{BG4};width:5em" | {art_ind_gen_s}
| {mix_gen_s}
|-
! style="{BG3}" | 與格
| style="{BG4};width:5em" | {art_ind_dat_s}
| {mix_dat_s}
|-
! style="{BG3}" | 賓格
| style="{BG4};width:5em" | {art_ind_acc_s}
| {mix_acc_s}
|{\cl}{notes_clause}</div></div>]=]
local notes_template = [===[
<div style="width:100%;text-align:left;background:var(--wikt-palette-lightblue,#d9ebff);color:inherit">
<div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em">
{footnote}
</div></div>
]===]
local function make_table(alternant_multiword_spec)
local forms = alternant_multiword_spec.forms
-- dark mode support
forms.BG1 = "background:var(--wikt-palette-white,#ffffff);color:inherit"
forms.BG2 = "background:var(--wikt-palette-grey,#9e9e9e);color:inherit"
forms.BG3 = "background:var(--wikt-palette-lightgrey,#cccccc);color:inherit"
forms.BG4 = "background:var(--wikt-palette-lightergrey,#eeeeee);color:inherit"
if alternant_multiword_spec.title then
forms.title = alternant_multiword_spec.title
else
forms.title = '<i lang="de" class="Latn">' .. forms.lemma .. '</i> 的變格'
end
local annotation = alternant_multiword_spec.annotation
if annotation == "" then
forms.annotation = ""
else
forms.annotation = " [<span style=\"font-size: smaller;\">" .. annotation .. "</span>]"
end
local table_spec
if alternant_multiword_spec.props.surname then
table_spec = noun_template_surname
elseif alternant_multiword_spec.props.langname then
table_spec = noun_template_langname
elseif alternant_multiword_spec.props.overall_adj then
table_spec =
alternant_multiword_spec.number == "sg" and adjectival_template_sg or
alternant_multiword_spec.number == "pl" and rsub(rsub(adjectival_template_sg, "singular", "plural"), "_s}", "_p}") or
adjectival_template_both
if alternant_multiword_spec.number == "pl" then
forms.gender = ""
else
local genderdesc_parts = {}
for _, gender in ipairs(alternant_multiword_spec.genders) do
table.insert(genderdesc_parts, gender_spec_to_full_gender[gender.spec])
end
forms.gender = "''" .. table.concat(genderdesc_parts, "<small>或</small>")
end
else
local no_indef = alternant_multiword_spec.props.toponym or alternant_multiword_spec.props.article
table_spec =
alternant_multiword_spec.number == "sg" and (no_indef and noun_template_sg_no_indef or noun_template_sg) or
alternant_multiword_spec.number == "pl" and noun_template_pl or
(no_indef and noun_template_both_no_indef or noun_template_both)
if forms.abl_s ~= "—" or forms.voc_s ~= "—" then
forms.abl_voc_clause = m_string_utilities.format(noun_template_abl_voc, forms)
else
forms.abl_voc_clause = ""
end
end
forms.notes_clause = forms.footnote ~= "" and
m_string_utilities.format(notes_template, forms) or ""
return m_string_utilities.format(table_spec, forms)
end
-- Externally callable function to parse and decline a noun given user-specified arguments. Return value is
-- ALTERNANT_MULTIWORD_SPEC, an object where the declined forms are in `ALTERNANT_MULTIWORD_SPEC.forms` for each slot.
-- If there are no values for a slot, the slot key will be missing. The value for a given slot is a list of objects
-- {form=FORM, footnotes=FOOTNOTES}.
function export.do_generate_forms(parent_args, pos, from_headword, is_proper, def)
local params = {
[1] = {required = true, default = "Haus<n,es,^er>"},
pagename = true,
json = {type = "boolean"}, -- for bot use
}
if from_headword or pretend_from_headword then
params["head"] = {list = true}
params["f"] = {list = true}
params["m"] = {list = true}
params["n"] = {list = true}
params["dim"] = {list = true}
params["sg"] = {list = true}
params["id"] = {}
params["sort"] = {}
params["splithyph"] = {type = "boolean"}
params["nolinkhead"] = {type = "boolean"}
end
local args = require("Module:parameters").process(parent_args, params)
local arg1 = args[1]
local need_surrounding_angle_brackets = true
-- Check whether we need to add <...> around the argument. If the
-- argument has no < in it, we definitely do. Otherwise, we need to
-- parse the balanced [...] and <...> and add <...> only if there isn't
-- a top-level <...>. We check for [...] because there might be angle
-- brackets inside of them (HTML tags in qualifiers or <<name:...>> and
-- such in references).
if arg1:find("<") then
local segments = put.parse_multi_delimiter_balanced_segment_run(arg1, {{"<", ">"}, {"[", "]"}})
for i = 2, #segments, 2 do
if segments[i]:find("^<.*>$") then
need_surrounding_angle_brackets = false
break
end
end
end
if need_surrounding_angle_brackets then
arg1 = "<" .. arg1 .. ">"
end
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local function do_parse_indicator_spec(angle_bracket_spec, lemma)
return parse_indicator_spec(angle_bracket_spec, lemma, pagename)
end
local parse_props = {
parse_indicator_spec = do_parse_indicator_spec,
allow_default_indicator = true,
allow_blank_lemma = true,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg1, parse_props)
alternant_multiword_spec.args = args
alternant_multiword_spec.props = {}
alternant_multiword_spec.props.is_proper = is_proper
detect_all_indicator_specs(alternant_multiword_spec)
local default_number =
(alternant_multiword_spec.props.is_proper or alternant_multiword_spec.props.toponym) and "sg" or "both"
propagate_properties(alternant_multiword_spec, "number", default_number, "both")
-- FIXME, maybe should check that noun genders match adjective genders
determine_adjectival_genders(alternant_multiword_spec)
determine_noun_status(alternant_multiword_spec)
local inflect_props = {
skip_slot = function(slot)
return skip_slot(alternant_multiword_spec.number, slot)
end,
slot_list = alternant_multiword_spec.props.surname and surname_slot_list_with_linked
or alternant_multiword_spec.props.langname and langname_slot_list_with_linked
or alternant_multiword_spec.props.overall_adj and adjectival_slot_list_with_linked
or noun_slot_list_with_linked,
inflect_word_spec = decline_noun_or_adjective,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
compute_articles(alternant_multiword_spec)
compute_headword_genders(alternant_multiword_spec)
if not pos then
-- Compute part of speech for categories. Fetch the first lemma, or failing that (which would only happen
-- if the user overrides the nom_sg and nom_p to be missing) the pagename. If it begins with a hyphen,
-- it's a suffix, else a noun (proper nouns get categorized like nouns).
local lemmas = export.get_lemmas(alternant_multiword_spec)
local first_lemma = #lemmas > 0 and lemmas[1].form or pagename
pos = rfind(first_lemma, "^%-") and "後綴" or "名詞"
end
alternant_multiword_spec.pos = pos
compute_categories_and_annotation(alternant_multiword_spec)
if from_headword or pretend_from_headword then
process_dim_m_f_n(alternant_multiword_spec, args.dim, "^chen", nil, "dim", "diminutive")
process_dim_m_f_n(alternant_multiword_spec, args.f, alternant_multiword_spec.forms.f_equiv,
"literal default", "f", "feminine equivalent")
process_dim_m_f_n(alternant_multiword_spec, args.m, alternant_multiword_spec.forms.m_equiv,
"literal default", "m", "masculine equivalent")
process_dim_m_f_n(alternant_multiword_spec, args.n, alternant_multiword_spec.forms.n_equiv,
"literal default", "n", "neuter equivalent")
process_dim_m_f_n(alternant_multiword_spec, args.sg, nil, nil, "sg", "singular")
end
if args.json then
return require("Module:JSON").toJSON(alternant_multiword_spec)
end
return alternant_multiword_spec
end
-- Entry point for {{de-ndecl}}. Template-callable function to parse and decline a noun given
-- user-specified arguments and generate a displayable table of the declined forms.
function export.show(frame)
local parent_args = frame:getParent().args
local alternant_multiword_spec = export.do_generate_forms(parent_args)
if type(alternant_multiword_spec) == "string" then -- json=1 specified
return alternant_multiword_spec
end
show_forms(alternant_multiword_spec)
-- FIXME!
alternant_multiword_spec.forms.decl_type = "foo"
return make_table(alternant_multiword_spec) .. require("Module:utilities").format_categories(
alternant_multiword_spec.categories, lang, nil, nil, force_cat)
end
return export
008ikftjn0htmwnz8qi6mmf4wver9dr
Module:Headword utilities
828
1824706
9909028
9882755
2026-09-29T07:22:00Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909028
Scribunto
text/plain
local export = {}
local fun_is_callable_module = "Module:fun/isCallable"
local languages_module = "Module:languages"
local links_module = "Module:links"
local parse_utilities_module = "Module:parse utilities"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local dump = mw.dumpObject
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local insert = table.insert
local concat = table.concat
local remove = table.remove
local sort = table.sort
local function deepEquals(...)
deepEquals = require(table_module).deepEquals
return deepEquals(...)
end
local function escape_wikicode(...)
escape_wikicode = require(parse_utilities_module).escape_wikicode
return escape_wikicode(...)
end
local function extend(...)
extend = require(table_module).extend
return extend(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function insert_if_not(...)
insert_if_not = require(table_module).insertIfNot
return insert_if_not(...)
end
local function is_callable(...)
is_callable = require(fun_is_callable_module)
return is_callable(...)
end
local function parse_inline_modifiers(...)
parse_inline_modifiers = require(parse_utilities_module).parse_inline_modifiers
return parse_inline_modifiers(...)
end
local function pattern_escape(...)
pattern_escape = require(string_pattern_escape_module)
return pattern_escape(...)
end
local function replacement_escape(...)
replacement_escape = require(string_replacement_escape_module)
return replacement_escape(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function term_contains_top_level_html(...)
term_contains_top_level_html = require(parse_utilities_module).term_contains_top_level_html
return term_contains_top_level_html(...)
end
local function ugsub(...)
ugsub = require(string_utilities_module).gsub
return ugsub(...)
end
local function umatch(...)
umatch = require(string_utilities_module).match
return umatch(...)
end
local param_mods = {
id = {}, -- disabled when `is_head = true`
q = {type = "qualifier"},
qq = {type = "qualifier"},
l = {type = "labels"},
ll = {type = "labels"},
-- [[Module:headword]] expects part references in `.refs`.
ref = {item_dest = "refs", type = "references", store = "insert-flattened"},
}
local optional_param_mods = {
g = {item_dest = "genders", type = "genders"},
alt = {},
lang = {type = "language"},
sc = {type = "script"},
t = {item_dest = "gloss"},
gloss = {},
pos = {},
lit = {},
tr = {},
ts = {},
face = {},
nolinkinfl = {type = "boolean"},
}
local optional_headword_param_mods = {
sc = {type = "script"},
tr = {},
ts = {},
}
--[==[
Parse a single inflection or headword form or list of such forms. In either case, inline modifiers may be attached.
`data` is an object with the following fields:
* `val`: The raw value to parse. Required.
* `paramname`: The name of the parameter from which the value was taken; used in error messages. Required.
* `is_head`: We are parsing a headword parameter (a value which goes into the `heads` field of `data`). This changes
the allowed modifiers, disabling the `id` modifier and only allowing a subset of optional modifiers.
* `frob`: An optional function of one value to apply to the form after inline modifiers have been removed (i.e. to
apply to the `.term` field of the returned object).
* `include_mods`: List of extra inline modifiers to include, besides the default ones (see below). Each list item is
either a string specifying a recognized extra inline modifier (see `optional_param_mods` in the code), or a two-item
list of modifier name and modifier spec, where the spec should follow the syntax for modifier specs in
`parse_inline_modifiers` in [[Module:parse utilities]].
* `exclude_mods`: List of default inline modifiers to not include.
* `splitchar`: If specified, the value in `val` can be a list of forms to parse, separated by the value of `splitchar`
(which is a Lua pattern, as in `parse_inline_modifiers` in [[Module:parse utilities]]). Most commonly, `splitchar` is
a single comma and the values are comma-separated (in this case, splitting will not happen if a space follows the
comma).
* `parse_lang_prefix`: If specified, allow a language prefix to precede a form, and if found, store into the `.lang`
field of the returned object.
* `preserve_splitchar`, `delimiter_key`, `escape_fun`, `unescape_fun`, `pre_normalize_modifiers`: As in
`parse_inline_modifiers` in [[Module:parse utilities]].
Returns an object suitable for storing as one element of one of the lists in `headdata.inflections`, where `headdata`
is the structure passed to [[Module:headword]]. If `splitchar` is specified, howeve, the return value is a list of such
objects.
The following default inline modifiers are currently recognized:
* `q`: Left qualifier.
* `qq`: Right qualifier.
* `l`: Comma-separated list of left labels. No space should follow the comma.
* `ll`: Comma-separated list of right labels. No space should follow the comma.
* `ref`: Reference or references. See {{tl|IPA}} for the syntax.
* `id`: Sense ID, in case there are multiple senses. See {{tl|l}}.
The following are the recognized additional inline modifiers:
* `g`: Comma-separated list of genders.
* `alt`: Display text.
* `lang`: Language code of language of the form, if different from the language of the headword.
* `sc`: Script code of script of the form. Almost never needed.
* `t`: Gloss for the form.
* `gloss`: Gloss for the form (alias for `t`).
* `pos`: Part of speech of the form.
* `lit`: Literal meaning of the form.
* `tr`: Manual transliteration of the form.
* `ts`: Transcription of the form, for languages where the transliteration differs markedly from the pronunciation.
* `face`: Face to display the form in, e.g. {"hypothetical"} for a hypothetical form (unlinkable and displayed in italics).
* `nolinkinfl`: Make the form unlinkable.
]==]
function export.parse_term_with_modifiers(data)
local paramname, val, frob = data.paramname, data.val, data.frob
local function generate_obj(term, parse_err)
if frob then
term = frob(term, parse_err)
end
if data.parse_lang_prefix and term:find(":") then
return require(parse_utilities_module).generate_obj_maybe_parsing_lang_prefix {
term = term,
paramname = paramname,
parse_lang_prefix = true,
parse_err = parse_err,
}
else
return {term = term}
end
end
-- Check for inline modifier, e.g. מרים<tr:Miryem>. But exclude top-level HTML entry with <span ...>,
-- <sup> or similar in it.
if (val:find("<", nil, true) or data.splitchar) and not term_contains_top_level_html(val) and
-- don't parse inline modifiers if is_head and the value begins with a ~ (link modifier syntax)
(not data.is_head or not val:find("^~")) then
local param_mods = param_mods
if data.is_head then
param_mods = shallow_copy(param_mods)
param_mods.id = nil
end
if data.include_mods or data.exclude_mods then
if not data.is_head then
-- already copied when data.is_head
param_mods = shallow_copy(param_mods)
end
if data.include_mods then
local optional_mods = data.is_head and optional_headword_param_mods or optional_param_mods
for _, mod in ipairs(data.include_mods) do
if type(mod) == "table" then
if #mod ~= 2 then
error(("Internal error: Modifier spec %s in `include_mods` should be of length 2"):format(
dump(mod)))
end
local modkey, modvalue = unpack(mod)
param_mods[modkey] = modvalue
elseif not optional_mods[mod] then
error(("Internal error: Unrecognized modifier spec %s in `include_mods`"):format(
dump(mod)))
else
param_mods[mod] = optional_mods[mod]
end
end
end
if data.exclude_mods then
for _, mod in ipairs(data.exclude_mods) do
if not param_mods[mod] then
error(("Internal error: Modifier spec %s in `exclude_mods` not found among existing modifiers"
):format(dump(mod)))
else
param_mods[mod] = nil
end
end
end
end
return parse_inline_modifiers(val, {
paramname = paramname,
param_mods = param_mods,
generate_obj = generate_obj,
splitchar = data.splitchar,
preserve_splitchar = data.preserve_splitchar,
delimiter_key = data.delimiter_key,
escape_fun = data.escape_fun,
unescape_fun = data.unescape_fun,
pre_normalize_modifiers = data.pre_normalize_modifiers,
})
else
local retval = generate_obj(val)
if data.splitchar then
retval = {retval}
end
return retval
end
end
--[==[
Parse a list of inflection forms that may have inline modifiers attached. `data` is an object with the following fields:
* `forms`: The list of raw values to parse. Required.
* `paramname`: The name of the first parameter from which the value was taken; used in error messages. If this is a
two-element list, the first element is the first parameter and the second element is the prefix of the remaining
parameters. Parameter names that are numbers are handled correctly, as are those with \1 in it marking where the
parameter index goes. Required.
* `qualifiers`: If specified, a possibly gappy list of left qualifiers to add to the parsed terms (for compatibility
purposes).
* `splitchar`: As in `parse_term_with_modifiers()`. The resulting per-term lists will be flattened.
* `frob`, `include_mods`, `exclude_mods`, `is_head`, `preserve_splitchar`, `parse_lang_prefix`, `delimiter_key`,
`escape_fun`, `unescape_fun`, `pre_normalize_modifiers`: As in `parse_term_with_modifiers()`.
Returns a list of objects, suitable for storing as one of the lists in `headdata.inflections` (once a label is added),
where `headdata` is the structure passed to [[Module:headword]].
]==]
function export.parse_term_list_with_modifiers(data)
local paramname, forms = data.paramname, data.forms
local qualifiers = data.qualifiers
local first, restpref
if type(paramname) == "table" then
first = paramname[1]
restpref = paramname[2]
else
first = paramname
restpref = paramname
end
local terms = {}
data = shallow_copy(data)
for i, val in ipairs(forms) do
data.paramname = i == 1 and first or type(restpref) == "number" and restpref + i - 1 or
restpref:find("\1", nil, true) and restpref:gsub("\1", tostring(i)) or restpref .. i
data.val = val
local parsed = export.parse_term_with_modifiers(data)
if qualifiers and qualifiers[i] then
if data.splitchar then
for _, term in ipairs(parsed) do
term.q = {qualifiers[i]}
end
else
parsed.q = {qualifiers[i]}
end
end
if data.splitchar then
extend(terms, parsed)
else
terms[i] = parsed
end
end
return terms
end
--[==[
Construct a link to [[Appendix:Glossary]] for `entry`. If `text` is specified, it is the display text; otherwise,
`entry` is used.
]==]
function export.glossary_link(entry, text)
text = text or entry
return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end
function export.replace_glossary_links_in_label(label)
if label:find("<<", nil, true) then
label = label:gsub("<<(.-)|(.-)>>", export.glossary_link):gsub("<<(.-)>>", export.glossary_link)
end
return label
end
function export.insert_fixed_inflection(data)
local headdata, origterm, label = data.headdata, data.originating_term, data.label
local inflobj = data.inflobj or headdata
inflobj.inflections = inflobj.inflections or {}
if not origterm then
insert(inflobj.inflections, {
label = export.replace_glossary_links_in_label(label)
})
else
if origterm.id then
error(("It doesn't make sense to pass in an ID '%s' for label '%s' in conjunction with a term value '%s'"
):format(origterm.id, label, origterm.term))
end
-- Preserve decorations
origterm.term = nil
origterm.label = export.replace_glossary_links_in_label(label)
insert(inflobj.inflections, origterm)
end
end
--[==[
Insert previously-parsed terms into an `inflections` field. The `inflections` field will be initialized if needed.
`data` is an object with the following fields:
* `headdata`: The headword structure passed to [[Module:headword]]. Required.
* `inflobj`: The object whose `inflections` field the terms are inserted into. Defaults to `headdata`. Only needs
to be set for nested inflections, which are specified for an inflection object rather than the headword data
structure as a whole.
* `terms`: The list of parsed terms. If {nil} or omitted, nothing happens unless `request` is set.
* `label`: The label that the inflections are given; any parts of the label surrounded in <<...>> are linked to the
glossary. (If the contents of <<...> contain a | in them, they are a two-part link.) Required.
* `no_label`: If the term is {"-"} and there are no other terms, insert a fixed label with this value. Defaults to
{"no "} plus the label.
* `usually_no_label`: If the term is {"-"} and there are other terms, insert a fixed label with this value. Defaults to
{"usually no "} plus the label.
* `accel`: If specified, a full accelerator object to add to the inflections.
* `request`: If specified and no terms are given, insert a label with a request for inflections to be given.
* `enable_auto_translit`: If specified and terms are given, display automatic transliteration of the terms.
The return value indicates whether the inflection exists and how many terms are in it. It is an object with the
following fields:
* `exists`: {"yes"} if one or more terms were specified; {"no"} if the value was given as {"-"}; {"usually no"} if
the first value was given as {"-"} but additional terms were supplied; otherwise {nil}, indicating that the status
is unspecified.
* `numterms`: Number of terms in the inflection. Will be 0 unless `exists` has the value {"yes"} or {"usually no"}.
* `request`: True if no terms were specified but a term request was inserted into the inflection (because
`data.request` was specified). Otherwise {nil}.
]==]
function export.insert_inflection(data)
local headdata, terms, label = data.headdata, data.terms, data.label
local inflobj = data.inflobj or headdata
local retval = {}
if terms and terms[1] then
if terms[1].term == "-" then
if terms[2] then
export.insert_fixed_inflection {
headdata = headdata,
inflobj = inflobj,
originating_term = terms[1],
label = data.usually_no_label or "通常無" .. label,
}
remove(terms, 1)
retval.numterms = #terms
retval.exists = "usually no"
else
export.insert_fixed_inflection {
headdata = headdata,
inflobj = inflobj,
originating_term = terms[1],
label = data.no_label or "無" .. label,
}
retval.numterms = 0
retval.exists = "no"
return retval
end
else
retval.numterms = #terms
retval.exists = "yes"
end
if data.check_missing then
error("check_missing support removed; use checkredlinks=true in [[Module:headword]]")
end
terms.label = export.replace_glossary_links_in_label(label)
if data.accel then
terms.accel = data.accel
end
terms.enable_auto_translit = data.enable_auto_translit
inflobj.inflections = inflobj.inflections or {}
insert(inflobj.inflections, terms)
elseif data.request then
inflobj.inflections = inflobj.inflections or {}
insert(inflobj.inflections, {
label = export.replace_glossary_links_in_label(label),
request = true,
})
retval.numterms = 0
-- retval.exists == nil
retval.request = true
else
retval.numterms = 0
-- retval.exists == nil
end
return retval
end
--[==[
Parse raw arguments from `forms` for inline modifiers, and insert the resulting terms (which should not require
significant additional processing) into `headdata.inflections`. `data` is an object with the following fields:
* `forms`: The list of raw values to parse. If {nil} or omitted, nothing happens.
* `headdata`: The headword structure passed to [[Module:headword]]. Required.
* `paramname`: As in `parse_term_list_with_modifiers()`. Required.
* `label`: As in `insert_inflection()`. Required.
* `qualifiers`, `frob`, `include_mods`, `exclude_mods`, `is_head`, `splitchar`, `preserve_splitchar`, `delimiter_key`,
`escape_fun`, `unescape_fun`, `pre_normalize_modifiers`: As in `parse_term_list_with_modifiers()`.
* `accel`: As in `insert_inflection()`.
Return value is as in `insert_inflection()`.
]==]
function export.parse_and_insert_inflection(data)
local forms = data.forms
if forms and forms[1] then
data = shallow_copy(data)
data.forms = forms
data.terms = export.parse_term_list_with_modifiers(data)
return export.insert_inflection(data)
end
return {
numterms = 0
}
end
--[==[
Combine two sets of decorations. If either is {nil}, just return the other, and if both are {nil}, return {nil}.
]==]
function export.combine_decorations(decs1, decs2)
if not decs1 and not decs2 then
return nil
end
if not decs1 then
return decs2
end
if not decs2 then
return decs1
end
local combined = shallow_copy(decs1)
for _, dec in ipairs(decs2) do
insert_if_not(combined, dec)
end
return combined
end
function export.combine_qualifiers_or_labels(quals1, quals2)
return export.combine_decorations(quals1, quals2)
end
--[==[
Combine the decorations (qualifiers, labels, references) and ID's of two term objects. `destobj` is the "destination
term object" into which the combined properties are written, and `srcobj` is the "source object" into which the
properties are merged. `destobj` is side-effected (but the lists inside of `destobj` are not); if this is undesirable,
make sure to shallow-copy `destobj` first. If both objects have values for a given decoration, the values of `destobj`
come first. If both objects have a value for `id`, the values must match or an error is thrown; otherwise, the resulting
value of `id` comes from whichever one is defined.
'''NOTE:''' This may not be the correct behavior when deduplicating a list of term objects. See
`insert_termobj_combining_duplicates` for a different approach.
]==]
function export.combine_termobj_decorations(destobj, srcobj)
destobj.q = export.combine_decorations(destobj.q, srcobj.q)
destobj.qq = export.combine_decorations(destobj.qq, srcobj.qq)
destobj.l = export.combine_decorations(destobj.l, srcobj.l)
destobj.ll = export.combine_decorations(destobj.ll, srcobj.ll)
destobj.refs = export.combine_decorations(destobj.refs, srcobj.refs)
if destobj.id and srcobj.id and destobj.id ~= srcobj.id then
-- FIXME: We probably want to pass in an error function
error(("Can't specify two different ID's %s and %s when combining objects"):format(srcobj.id, destobj.id))
end
destobj.id = destobj.id or srcobj.id
return destobj
end
function export.combine_termobj_qualifiers_labels(destobj, srcobj)
return export.combine_termobj_decorations(destobj, srcobj)
end
function export.termobj_has_decorations(obj)
return obj.q and obj.q[1] or obj.qq and obj.qq[1] or obj.l and obj.l[1] or obj.ll and obj.ll[1] or
obj.refs and obj.refs[1]
end
function export.termobj_has_qualifiers_or_labels(obj)
return export.termobj_has_decorations(obj)
end
local function one_decoration_equal(prop1, prop2)
local prop1_is_nil = not prop1 or not prop1[1]
local prop2_is_nil = not prop2 or not prop2[1]
if prop1_is_nil and prop2_is_nil then
return true
end
if prop1_is_nil or prop2_is_nil then
return false
end
return deepEquals(prop1, prop2)
end
function export.termobj_decorations_equal(obj1, obj2)
return one_decoration_equal(obj1.q, obj2.q) and
one_decoration_equal(obj1.qq, obj2.qq) and
one_decoration_equal(obj1.l, obj2.l) and
one_decoration_equal(obj1.ll, obj2.ll) and
one_decoration_equal(obj1.refs, obj2.refs) and
obj1.id == obj2.id
end
function export.termobj_ancillary_properties_equal(obj1, obj2)
return export.termobj_decorations_equal(obj1, obj2)
end
function export.convert_termobj_to_formobj(termobj)
local formobj = {
form = termobj.term,
translit = termobj.tr,
}
local footnotes
local function mods_to_footnote(mod_prefix, mod_vals)
if mod_vals and mod_vals[1] then
footnotes = footnotes or {}
for _, val in ipairs(mod_vals) do
insert(footnotes, "[" .. mod_prefix .. ":" .. val .. "]")
end
end
end
mods_to_footnote("q", termobj.q)
mods_to_footnote("qq", termobj.qq)
mods_to_footnote("l", termobj.l)
mods_to_footnote("ll", termobj.ll)
mods_to_footnote("ref", termobj.refs)
mods_to_footnote("id", termobj.id and {termobj.id} or nil)
formobj.footnotes = footnotes
return formobj
end
local recognized_multi_mods = {
q = "q",
qq = "qq",
l = "l",
ll = "ll",
ref = "refs",
}
local recognized_single_mods = {
id = "id",
}
function export.add_footnote_to_termobj(termobj, footnote)
local stripped_footnote = footnote:match("^%[(.*)%]$")
if not stripped_footnote then
error("Internal error: Footnote should be surrounded by brackets at this stage: " .. footnote)
end
local prefix, rest = stripped_footnote:match("^([a-z]+):(.+)$")
local field, is_multi
if prefix then
if recognized_multi_mods[prefix] then
field = recognized_multi_mods[prefix]
is_multi = true
elseif recognized_single_mods[prefix] then
field = recognized_single_mods[prefix]
is_multi = false
end
end
if not field then
rest = stripped_footnote
field = "l"
is_multi = true
end
if is_multi then
if not termobj[field] then
termobj[field] = {}
end
insert(termobj[field], rest)
else
if termobj[field] and termobj[field] ~= rest then
error(("Can't set two values for '%s': '%s' and '%s'"):format(field, termobj[field], rest))
end
termobj[field] = rest
end
end
function export.convert_formobj_to_termobj(formobj)
local termobj = {
term = formobj.form,
tr = formobj.translit,
}
if formobj.footnotes then
for _, footnote in ipairs(formobj.footnotes) do
export.add_footnote_to_termobj(termobj, footnote)
end
end
return termobj
end
local function extract_termobj_field_modifiers(fieldval)
return fieldval:match("^([*+]?)(.*)$")
end
function export.remove_termobj_field_modifiers(termobj)
local function remove_field_modifiers(field)
if termobj[field] and termobj[field][1] then
local any_field_modifiers = false
for _, val in ipairs(termobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if field_mods ~= "" then
any_field_modifiers = true
break
end
end
local new_field = {}
if any_field_modifiers then
for _, val in ipairs(termobj[field]) do
local _, field_without_mods = extract_termobj_field_modifiers(val)
insert_if_not(new_field, field_without_mods)
end
termobj[field] = new_field
end
end
end
remove_field_modifiers("q")
remove_field_modifiers("qq")
remove_field_modifiers("l")
remove_field_modifiers("ll")
remove_field_modifiers("refs")
end
function export.insert_termobj_combining_duplicates(destobjs, termobj)
for _, destobj in ipairs(destobjs) do
if destobj.term == termobj.term and destobj.tr == termobj.tr then
-- Form already present; maybe combine footnotes.
local function combine_field_values(field)
if termobj[field] and termobj[field][1] then
-- Check to see if there are existing values with *; if so, remove them.
if destobj[field] and destobj[field][1] then
local any_values_with_asterisk = false
for _, val in ipairs(destobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if field_mods:find("%*") then
any_values_with_asterisk = true
break
end
end
if any_values_with_asterisk then
local filtered_values = {}
for _, val in ipairs(destobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if not field_mods:find("%*") then
insert(filtered_values, val)
end
end
if filtered_values[1] then
destobj[field] = filtered_values
else
destobj[field] = nil
end
end
end
local any_footnotes_with_plus = false
for _, val in ipairs(termobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if field_mods:find("%+") then
any_footnotes_with_plus = true
break
end
end
if any_footnotes_with_plus then
if not destobj[field] then
destobj[field] = {}
else
destobj[field] = shallow_copy(destobj[field])
end
for _, val in ipairs(termobj[field]) do
local already_seen = false
local field_mods, field_without_mods = extract_termobj_field_modifiers(val)
if field_mods:find("%+") then
for _, existing_val in ipairs(destobj[field]) do
local _, existing_field_without_mods =
extract_termobj_field_modifiers(existing_val)
if existing_field_without_mods == field_without_mods then
already_seen = true
break
end
end
if not already_seen then
insert(destobj[field], val)
end
end
end
end
end
end
combine_field_values("q")
combine_field_values("qq")
combine_field_values("l")
combine_field_values("ll")
combine_field_values("refs")
if destobj.id and termobj.id and destobj.id ~= termobj.id then
-- FIXME: We probably want to pass in an error function
error(("Can't specify two different ID's %s and %s when combining objects"):format(termobj.id, destobj.id))
end
destobj.id = destobj.id or termobj.id
return
end
end
insert(destobjs, termobj)
end
export.allowed_special_indicators = {
["first"] = true,
["first-second"] = true,
["first-last"] = true,
["second"] = true,
["last"] = true,
["each"] = true,
["+"] = true, -- requests the default behavior with preposition handling
}
--[==[
Check for special indicators (values such as {"+first"} or {"+first-last"} that are used in a `pl`, `f`, etc. argument
and indicate how to inflect a multiword term). If `form` is such an indicator, the return value is `form` minus
the initial `+` sign; otherwise, if form begins with a `+` sign, an error is thrown; otherwise the return value is nil.
]==]
function export.get_special_indicator(form, noerror)
if form:find("^%+") then
form = form:gsub("^%+", "")
if not export.allowed_special_indicators[form] then
if noerror then
return nil
end
local indicators = {}
for indic, _ in pairs(export.allowed_special_indicators) do
insert(indicators, "+" .. indic)
end
sort(indicators)
error("Special inflection indicator beginning with '+' can only be " ..
mw.text.listToText(indicators) .. ": +" .. form)
end
return form
end
return nil
end
local function add_endings(bases, endings)
local retval = {}
if type(bases) ~= "table" then
bases = {bases}
end
if type(endings) ~= "table" then
endings = {endings}
end
for _, base in ipairs(bases) do
for _, ending in ipairs(endings) do
insert(retval, base .. ending)
end
end
return retval
end
--[==[
Inflect a possibly multiword or hyphenated term `form` using the function `inflect`, which is a function of one argument
that is called on a single word to inflect and should return either the inflected word or a list of inflected words.
`special` indicates how to inflect the multiword term and should be e.g. {"first"} to inflect only the first word,
{"first-last"} to inflect the first and last words, {"each"} to inflect each word, etc. See `allowed_special_indicators`
above for the possibilities. If `special` is `+`, or is omitted and the term is multiword (i.e. containing a space
character), and `prepositions` is supplied, the function checks for multiword or hyphenated terms containing the
prepositions in `prepositions`, e.g. Italian [[senso di marcia]] or [[medaglia d'oro]] or Portuguese
[[tartaruga-do-mar]]. If such a term is found, only the first word is inflected. Otherwise, the default is
{"first-last"}. `prepositions` is a list of Lua patterns matching prepositions. The patterns will automatically have the
separator character (space or hyphen) added to the left side but not the right side, so they should contain a space
character (which will automatically be converted to the appropriate separator) on the right side unless the preposition
is joined on the right side with an apostrophe. Examples of preposition patterns for Italian are {"di "}, {"sull'"} and
{"d?all[oae] "} (which matches {"dallo "}, {"dalle "}, {"alla "}, etc.).
The return value is always either a list of inflected multiword or hyphenated terms, or nil if `special` is omitted
and `form` is not multiword. (If `special` is specified and `form` is not multiword or hyphenated, an error results.)
]==]
function export.handle_multiword(form, special, inflect, prepositions, sep)
sep = sep or form:find(" ") and " " or "%-"
local raw_sep = sep == " " and " " or "-"
-- Used to add regex version of separator in the replacement portion of ugsub() or :gsub()
local sep_replacement = sep == " " and " " or "%%-"
-- Given a Lua pattern, replace space with the appropriate separator.
local function hack_re(re)
if sep == " " then
return re
end
return (re:gsub(" ", sep_replacement))
end
if special == "first" then
local first, rest = form:match(hack_re("^(.-)( .*)$"))
if not first then
error("Special indicator 'first' can only be used with a multiword term: " .. form)
end
return add_endings(inflect(first), rest)
elseif special == "second" then
local first, second, rest = form:match(hack_re("^([^ ]+ )([^ ]+)( .*)$"))
if not first then
error("Special indicator 'second' can only be used with a term with three or more words: " .. form)
end
return add_endings(add_endings({first}, inflect(second)), rest)
elseif special == "first-second" then
local first, space, second, rest = form:match(hack_re("^([^ ]+)( )([^ ]+)( .*)$"))
if not first then
error("Special indicator 'first-second' can only be used with a term with three or more words: " .. form)
end
return add_endings(add_endings(add_endings(inflect(first), space), inflect(second)), rest)
elseif special == "each" then
local terms = split(form, sep)
if #terms < 2 then
error("Special indicator 'each' can only be used with a multiword term: " .. form)
end
for i, term in ipairs(terms) do
terms[i] = inflect(term)
if i > 1 then
terms[i] = add_endings(raw_sep, terms[i])
end
end
local result = ""
for _, term in ipairs(terms) do
result = add_endings(result, term)
end
return result
elseif special == "first-last" then
local first, middle, last = form:match(hack_re("^(.-)( .* )(.-)$"))
if not first then
first, middle, last = form:match(hack_re("^(.-)( )(.*)$"))
end
if not first then
error("Special indicator 'first-last' can only be used with a multiword term: " .. form)
end
return add_endings(add_endings(inflect(first), middle), inflect(last))
elseif special == "last" then
local rest, last = form:match(hack_re("^(.* )(.-)$"))
if not rest then
error("Special indicator 'last' can only be used with a multiword term: " .. form)
end
return add_endings(rest, inflect(last))
elseif special and special ~= "+" then
error("Unrecognized special=" .. special)
end
-- Only do default behavior if special indicator '+' explicitly given or separator is space; otherwise we will
-- break existing behavior with hyphenated words.
if (special == "+" or sep == " ") and form:find(sep) then
if prepositions then
-- check for prepositions in the middle of the word; do it this way so we can handle
-- more than one word before the preposition (and usually inflect each word)
for _, prep in ipairs(prepositions) do
local first, space_prep_rest = umatch(form, hack_re("^(.-)( " .. prep .. ".*)$"))
if first then
return add_endings(inflect(first), space_prep_rest)
end
end
end
-- multiword or hyphenated expressions default to first-last; we need to pass in the separator to avoid
-- problems with multiword terms containing hyphens in the individual words
return export.handle_multiword(form, "first-last", inflect, prepositions, sep)
end
return nil
end
local function link_hyphen_split_component(word, data)
if data.link_hyphen_split_component then
return data.link_hyphen_split_component(word)
else
return "[[" .. word .. "]]"
end
end
-- Default function to split a word on apostrophes. Don't split apostrophes at the beginning or end of a word (e.g.
-- [['ndrangheta]] or [[po']]). Handle multiple apostrophes correctly, e.g. [[l'altr'ieri]] -> [[l']][altr']][[ieri]].
function export.default_split_apostrophe(word, data)
local apostrophe_parts = split(word, "'", true, true)
local linked_apostrophe_parts = {}
local apostrophes_at_beginning = ""
local i = 1
-- Apostrophes at beginning get attached to the first word after (which will always exist but may
-- be blank if the word consists only of apostrophes).
while i < #apostrophe_parts do -- <, not <=, in case the word consists only of apostrophes
local apostrophe_part = apostrophe_parts[i]
i = i + 1
if apostrophe_part == "" then
apostrophes_at_beginning = apostrophes_at_beginning .. "'"
else
break
end
end
apostrophe_parts[i] = apostrophes_at_beginning .. apostrophe_parts[i]
-- Now, do the remaining parts. A blank part indicates more than one apostrophe in a row; we join
-- all of them to the preceding word.
while i <= #apostrophe_parts do
local apostrophe_part = apostrophe_parts[i]
if apostrophe_part == "" then
linked_apostrophe_parts[#linked_apostrophe_parts] =
linked_apostrophe_parts[#linked_apostrophe_parts] .. "'"
elseif i == #apostrophe_parts then
insert(linked_apostrophe_parts, apostrophe_part)
else
insert(linked_apostrophe_parts, apostrophe_part .. "'")
end
i = i + 1
end
for j, tolink in ipairs(linked_apostrophe_parts) do
linked_apostrophe_parts[j] = link_hyphen_split_component(tolink, data)
end
return concat(linked_apostrophe_parts)
end
--[=[
Auto-add links to a word that should not have spaces but may have hyphens and/or apostrophes. We split off final
punctuation, then split on hyphens if `data.split_hyphen` is given, and also split on apostrophes if
`data.split_apostrophe` is given. We only split on hyphens if they are in the middle of the word, not at the beginning
or end (hyphens at the beginning or end indicate suffixes or prefixes, respectively). `include_hyphen_prefixes`, if
given, is a set of prefixes (not including the final hyphen) where we should include the final hyphen in the prefix.
Hence, e.g. if "anti" is in the set, a Portuguese word like [[anti-herói]] "anti-hero" will be split [[anti-]][[herói]]
(whereas a word like [[código-fonte]] "source code" will be split as [[código]]-[[fonte]]).
If `data.split_apostrophe` is specified, we split on apostrophes unless `data.no_split_apostrophe_words` is given and
the word is in the specified set, such as French [[c'est]] and [[quelqu'un]]. If `data.split_apostrophe` is true, the
default algorithm applies, which splits on all apostrophes except those at the beginning and end of a word (as in
Italian [['ndrangheta]] or [[po']]), and includes the apostrophe in the link to its left (so we auto-split French
[[l'eau]] as [[l']][[eau]] and [[l'altr'ieri]] as [[l']][altr']][[ieri]]). If `data.split_apostrophe` is specified
but not `true`, it should be a function of one argument that does custom apostrophe-splitting. The argument is the word
to split, and the return value should be the split and linked word.
]=]
local function add_single_word_links(space_word, data, term_has_spaces)
local space_word_no_punct, punct
local punct_pattern = data.punctuation
if punct_pattern and is_callable(punct_pattern) then
space_word_no_punct, punct = punct_pattern(space_word)
else
if punct_pattern == nil then
punct_pattern = "[,;:?!]"
end
space_word_no_punct, punct = umatch(space_word, "^(.*)(" .. punct_pattern .. ")$")
end
space_word_no_punct = space_word_no_punct or space_word
punct = punct or ""
local words
if space_word_no_punct:sub(1, 1) == "-" or space_word_no_punct:sub(-1) == "-" then
-- don't split prefixes and suffixes
words = {space_word_no_punct}
else
local splitter
if term_has_spaces then
splitter = data.split_hyphen_when_space
else
splitter = data.split_hyphen_when_no_space
end
if is_callable(splitter) then
words = splitter(space_word_no_punct)
if type(words) == "string" then
return words .. punct
end
end
end
if not words then
local split_hyphen
if term_has_spaces then
split_hyphen = data.split_hyphen_when_space
else
split_hyphen = data.split_hyphen_when_no_space
if split_hyphen == nil then -- default to true; use `false` to avoid this
split_hyphen = true
end
end
if split_hyphen then
words = split(space_word_no_punct, "-", true, true)
else
words = {space_word_no_punct}
end
end
local linked_words = {}
for j, word in ipairs(words) do
if j < #words and data.include_hyphen_prefixes and data.include_hyphen_prefixes[word] then
word = "[[" .. word .. "-]]"
elseif j > 1 and data.include_hyphen_suffixes and data.include_hyphen_suffixes[word] then
word = "[[-" .. word .. "]]"
else
-- Don't split on apostrophes if the word is in `no_split_apostrophe_words`.
if (not data.no_split_apostrophe_words or not data.no_split_apostrophe_words[word]) and
data.split_apostrophe and word:find("'", nil, true) then
if data.split_apostrophe == true then
word = export.default_split_apostrophe(word, data)
else -- custom apostrophe splitter/linker
word = data.split_apostrophe(word)
end
elseif word ~= "" then -- avoid -[[]]- (e.g. f--k)
word = link_hyphen_split_component(word, data)
end
if j < #words then
word = word .. "-"
end
end
insert(linked_words, word)
end
return concat(linked_words) .. punct
end
--[=[
Auto-add links to a multiword term. `data` contains fields customizing how to do this. By default we proceed as follows:
(1) If the term already has embedded links in it, they are left unchanged.
(2) Otherwise, if there are spaces present, we split on spaces and link each word separately.
(3) If a given space-separated component ends in punctuation (defaulting to [,;:?!]), it is separated off, the remainder
of the algorithm run, and the punctuation pasted back on.
(4) If there are hyphens in a given space-separated component, we may link each hyphenated term separately depending
on the settings in `data`. Normally the hyphens are not included in the linked terms, but this can be overridden
for specific prefixes and/or suffixes. By default, if there are spaces in the multiword term, we do not link
hyphenated components (because of cases like "boire du petit-lait" where "petit-lait" should be linked as a whole),
but do so otherwise (e.g. for "avant-avant-hier"); this can overridden for cases like "croyez-le ou non".
Cases where only some of the hyphens should be split can always be handled by explicitly specifying the head (e.g.
"Nord-Pas-de-Calais" given as head=[[Nord]]-[[Pas-de-Calais]]).
(5) If there are apostrophes in a given component, we may link each apostrophe-separated term separately depending
on the settings in `data`, including the apostrophe in the link to its left (so we split "de l'eau" as
"[[de]] [[l']][[eau]]").
The settings in `data` are as follows:
`split_hyphen_when_no_space`: Whether to split on hyphens when the term has no spaces. Defaults to true if set to `nil`.
This can be a function of one argument, to implement a custom splitting algorithm for hyphen-separated terms. If
this returns [FIXME: FINISH ME ...]
If `data.split_apostrophe` is specified, we split on apostrophes unless `data.no_split_apostrophe_words` is given and
the word is in the specified set, such as French [[c'est]] and [[quelqu'un]]. If `data.split_apostrophe` is true, the
default algorithm applies, which splits on all apostrophes except those at the beginning and end of a word (as in
Italian [['ndrangheta]] or [[po']]), and includes the apostrophe in the link to its left (so we auto-split French
[[l'eau]] as [[l']][[eau]] and [[l'altr'ieri]] as [[l']][altr']][[ieri]]). If `data.split_apostrophe` is specified
but not `true`, it should be a function of one argument that does custom apostrophe-splitting. The argument is the word
to split, and the return value should be the split and linked word.
We don't always split on hyphens because of cases like "boire du petit-lait" where "petit-lait" should be linked as a
whole, but provide the option to do it for cases like "croyez-le ou non". If there's no space, however, then it makes
sense to split on hyphens by `no_split_apostrophe_words` and `include_hyphen_prefixes` allow for special-case handling
of particular words and are as described in the comment above add_single_word_links().
]=]
function export.add_links_to_multiword_term(term, data)
if term:match("[%[%]]") then
return term
end
local words = split(term, " ", true, true)
local term_has_spaces = #words > 1
local linked_words = {}
for _, word in ipairs(words) do
insert(linked_words, add_single_word_links(word, data, term_has_spaces))
end
local retval = concat(linked_words, " ")
-- If we ended up with a single link consisting of the entire term,
-- remove the link.
return retval:match("^%[%[([^%[%]]*)%]%]$") or retval
end
local function canonicalize_begin_end_spec(spec)
local from, to = spec:match("^(.-):(.*)$")
if not from then
from = spec
to = ""
end
return from, to
end
--[==[
Given a `linked_term` that is the output of add_links_to_multiword_term(), apply modifications as given in
`modifier_spec` to change the link destination of subterms (normally single-word non-lemma forms; sometimes
collections of adjacent words). This is usually used to link non-lemma forms to their corresponding lemma, but can
also be used to replace a span of adjacent separately-linked words to a single multiword lemma. The format of
`modifier_spec` is one or more semicolon-separated subterm specs, where each such spec is of the form
SUBTERM:DEST, where SUBTERM is one or more words in the `linked_term` but without brackets in them, and DEST is the
corresponding link destination to link the subterm to. Any occurrence of ~ in DEST is replaced with SUBTERM.
Alternatively, a single modifier spec can be of the form BEGIN[FROM:TO], which is equivalent to writing
BEGINFROM:BEGINTO (see example below).
For example, given the source phrase [[il bue che dice cornuto all'asino]] "the pot calling the kettle black"
(literally "the ox that calls the donkey horned/cuckolded"), the result of calling add_links_to_multiword_term()
is [[il]] [[bue]] [[che]] [[dice]] [[cornuto]] [[all']][[asino]]. With a modifier_spec of 'dice:dire', the result
is [[il]] [[bue]] [[che]] [[dire|dice]] [[cornuto]] [[all']][[asino]]. Here, based on the modifier spec, the
non-lemma form [[dice]] is replaced with the two-part link [[dire|dice]].
Another example: given the source phrase [[chi semina vento raccoglie tempesta]] "sow the wind, reap the whirlwind"
(literally (he) who sows wind gathers [the] tempest"). The result of calling add_links_to_multiword_term() is
[[chi]] [[semina]] [[vento]] [[raccoglie]] [[tempesta]], and with a modifier_spec of 'semina:~re; raccoglie:~re',
the result is [[chi]] [[seminare|semina]] [[vento]] [[raccogliere|raccoglie]] [[tempesta]]. Here we use the ~
notation to stand for the non-lemma form in the destination link.
A more complex example is [[se non hai altri moccoli puoi andare a letto al buio]], which becomes
[[se]] [[non]] [[hai]] [[altri]] [[moccoli]] [[puoi]] [[andare]] [[a]] [[letto]] [[al]] [[buio]] after calling
add_links_to_multiword_term(). With the following modifier_spec:
'hai:avere; altr[i:o]; moccol[i:o]; puoi: potere; andare a letto:~; al buio:~', the result of applying the spec is
[[se]] [[non]] [[avere|hai]] [[altro|altri]] [[moccolo|moccoli]] [[potere|puoi]] [[andare a letto]] [[al buio]].
Here, we rely on the alternative notation mentioned above for e.g. 'altr[i:o]', which is equivalent to 'altri:altro',
and link multiword subterms using e.g. 'andare a letto:~'. (The code knows how to handle multiword subexpressions
properly, and if the link text and destination are the same, only a single-part link is formed.)
]==]
function export.apply_link_modifiers(linked_term, modifier_spec, lang)
local split_modspecs = split(modifier_spec, "%s*;%s*")
for j, modspec in ipairs(split_modspecs) do
local id
if modspec:find("<") then
local rest
rest, id = modspec:match("^(.*)<id:(.-)>$")
if rest then
modspec = rest
end
end
local subterm, dest, otherlang
local begin_spec, rest, end_spec = modspec:match("^%[(.-)%]([^:]*)%[(.-)%]$")
if begin_spec then
local begin_from, begin_to = canonicalize_begin_end_spec(begin_spec)
local end_from, end_to = canonicalize_begin_end_spec(end_spec)
subterm = begin_from .. rest .. end_from
dest = begin_to .. rest .. end_to
end
if not subterm then
rest, end_spec = modspec:match("^([^:]*)%[(.-)%]$")
if rest then
local end_from, end_to = canonicalize_begin_end_spec(end_spec)
subterm = rest .. end_from
dest = rest .. end_to
end
end
if not subterm then
begin_spec, rest = modspec:match("^%[(.-)%]([^:]*)$")
if begin_spec then
local begin_from, begin_to = canonicalize_begin_end_spec(begin_spec)
subterm = begin_from .. rest
dest = begin_to .. rest
end
end
if not subterm then
subterm, dest = modspec:match("^(.-)%s*:%s*(.*)$")
if subterm and subterm ~= "^" and subterm ~= "$" then
local langdest
-- Parse off an initial language code (e.g. 'en:Higgs', 'la:minūtia' or 'grc:σκατός'). Also handle
-- Wikipedia prefixes ('w:Abatemarco' or 'w:it:Colle Val d'Elsa').
otherlang, langdest = dest:match("^([A-Za-z0-9._-]+):([^ ].*)$")
if otherlang == "w" then
local foreign_wikipedia, foreign_term = langdest:match("^([A-Za-z0-9._-]+):([^ ].*)$")
if foreign_wikipedia then
otherlang = otherlang .. ":" .. foreign_wikipedia
langdest = foreign_term
end
dest = ("%s:%s"):format(otherlang, langdest)
otherlang = nil
elseif otherlang then
otherlang = get_lang(otherlang, true, "allow etym")
dest = langdest
end
end
end
if not subterm then
if modspec == "?" or modspec == "!" then
subterm = "$"
dest = modspec
elseif modspec == "..." or modspec == "...?" then
subterm = "$"
dest = " " .. modspec
elseif modspec:find("^[A-Z]$") then
-- X, Y, etc. by themselves are unlinked, to help with snowclones
subterm = modspec
dest = "_"
else
subterm = modspec
dest = "~"
end
end
if subterm == "^" then
linked_term = dest:gsub("_", " ") .. linked_term
elseif subterm == "$" then
linked_term = linked_term .. dest:gsub("_", " ")
else
if subterm:find("[", nil, true) then
error(("Subterm '%s' in modifier spec '%s' cannot have brackets in it"):format(
escape_wikicode(subterm), escape_wikicode(modspec)))
end
local escaped_subterm = pattern_escape(subterm)
local subterm_re = "%[%[" .. escaped_subterm:gsub("(%%?[ ',%-])", "%%]*%1%%[*") .. "%]%]"
local expanded_dest
if dest:find("~", nil, true) then
expanded_dest = dest:gsub("~", replacement_escape(subterm))
else
expanded_dest = dest
end
if otherlang then
expanded_dest = expanded_dest .. "#" .. otherlang:getCanonicalName()
end
local subterm_replacement
if expanded_dest == "_" then
subterm_replacement = subterm
if id then
error("Can't supply <id:...> with an unlinked subterm")
end
if otherlang then
error("Can't supply prefixed language with an unlinked subterm")
end
elseif id or otherlang then
if id and expanded_dest:find("[", nil, true) then
error("Can't supply <id:...> with destination with embedded brackets")
end
subterm_replacement = require(links_module).language_link {
lang = otherlang or lang,
term = expanded_dest,
alt = subterm,
id = id,
}
elseif expanded_dest:find("[", nil, true) then
-- Use the destination directly if it has brackets in it (e.g. to put brackets around parts of a word).
subterm_replacement = expanded_dest
elseif expanded_dest == subterm then
subterm_replacement = "[[" .. subterm .. "]]"
else
subterm_replacement = "[[" .. expanded_dest .. "|" .. subterm .. "]]"
end
local escaped_subterm_replacement = replacement_escape(subterm_replacement)
local replaced_linked_term = ugsub(linked_term, subterm_re, escaped_subterm_replacement)
if replaced_linked_term == linked_term then
mw.log(("Attempted to replace %s with %s in %s"):format(subterm_re, escaped_subterm_replacement, linked_term))
error(("Subterm '%s' could not be located in %slinked expression %s, or replacement same as subterm"):format(
subterm, j > 1 and "intermediate " or "", escape_wikicode(linked_term)))
else
linked_term = replaced_linked_term
end
end
end
return linked_term
end
return export
hz2bx7nbq2lpxvi7quc2gv6tsz1utgj
9909124
9909028
2026-09-29T07:41:59Z
TongcyBot
83009
同步 enwikt:舊介面改為報錯
9909124
Scribunto
text/plain
local export = {}
local fun_is_callable_module = "Module:fun/isCallable"
local languages_module = "Module:languages"
local links_module = "Module:links"
local parse_utilities_module = "Module:parse utilities"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local dump = mw.dumpObject
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local insert = table.insert
local concat = table.concat
local remove = table.remove
local sort = table.sort
local function deepEquals(...)
deepEquals = require(table_module).deepEquals
return deepEquals(...)
end
local function escape_wikicode(...)
escape_wikicode = require(parse_utilities_module).escape_wikicode
return escape_wikicode(...)
end
local function extend(...)
extend = require(table_module).extend
return extend(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function insert_if_not(...)
insert_if_not = require(table_module).insertIfNot
return insert_if_not(...)
end
local function is_callable(...)
is_callable = require(fun_is_callable_module)
return is_callable(...)
end
local function parse_inline_modifiers(...)
parse_inline_modifiers = require(parse_utilities_module).parse_inline_modifiers
return parse_inline_modifiers(...)
end
local function pattern_escape(...)
pattern_escape = require(string_pattern_escape_module)
return pattern_escape(...)
end
local function replacement_escape(...)
replacement_escape = require(string_replacement_escape_module)
return replacement_escape(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function term_contains_top_level_html(...)
term_contains_top_level_html = require(parse_utilities_module).term_contains_top_level_html
return term_contains_top_level_html(...)
end
local function ugsub(...)
ugsub = require(string_utilities_module).gsub
return ugsub(...)
end
local function umatch(...)
umatch = require(string_utilities_module).match
return umatch(...)
end
local param_mods = {
id = {}, -- disabled when `is_head = true`
q = {type = "qualifier"},
qq = {type = "qualifier"},
l = {type = "labels"},
ll = {type = "labels"},
-- [[Module:headword]] expects part references in `.refs`.
ref = {item_dest = "refs", type = "references", store = "insert-flattened"},
}
local optional_param_mods = {
g = {item_dest = "genders", type = "genders"},
alt = {},
lang = {type = "language"},
sc = {type = "script"},
t = {item_dest = "gloss"},
gloss = {},
pos = {},
lit = {},
tr = {},
ts = {},
face = {},
nolinkinfl = {type = "boolean"},
}
local optional_headword_param_mods = {
sc = {type = "script"},
tr = {},
ts = {},
}
--[==[
Parse a single inflection or headword form or list of such forms. In either case, inline modifiers may be attached.
`data` is an object with the following fields:
* `val`: The raw value to parse. Required.
* `paramname`: The name of the parameter from which the value was taken; used in error messages. Required.
* `is_head`: We are parsing a headword parameter (a value which goes into the `heads` field of `data`). This changes
the allowed modifiers, disabling the `id` modifier and only allowing a subset of optional modifiers.
* `frob`: An optional function of one value to apply to the form after inline modifiers have been removed (i.e. to
apply to the `.term` field of the returned object).
* `include_mods`: List of extra inline modifiers to include, besides the default ones (see below). Each list item is
either a string specifying a recognized extra inline modifier (see `optional_param_mods` in the code), or a two-item
list of modifier name and modifier spec, where the spec should follow the syntax for modifier specs in
`parse_inline_modifiers` in [[Module:parse utilities]].
* `exclude_mods`: List of default inline modifiers to not include.
* `splitchar`: If specified, the value in `val` can be a list of forms to parse, separated by the value of `splitchar`
(which is a Lua pattern, as in `parse_inline_modifiers` in [[Module:parse utilities]]). Most commonly, `splitchar` is
a single comma and the values are comma-separated (in this case, splitting will not happen if a space follows the
comma).
* `parse_lang_prefix`: If specified, allow a language prefix to precede a form, and if found, store into the `.lang`
field of the returned object.
* `preserve_splitchar`, `delimiter_key`, `escape_fun`, `unescape_fun`, `pre_normalize_modifiers`: As in
`parse_inline_modifiers` in [[Module:parse utilities]].
Returns an object suitable for storing as one element of one of the lists in `headdata.inflections`, where `headdata`
is the structure passed to [[Module:headword]]. If `splitchar` is specified, howeve, the return value is a list of such
objects.
The following default inline modifiers are currently recognized:
* `q`: Left qualifier.
* `qq`: Right qualifier.
* `l`: Comma-separated list of left labels. No space should follow the comma.
* `ll`: Comma-separated list of right labels. No space should follow the comma.
* `ref`: Reference or references. See {{tl|IPA}} for the syntax.
* `id`: Sense ID, in case there are multiple senses. See {{tl|l}}.
The following are the recognized additional inline modifiers:
* `g`: Comma-separated list of genders.
* `alt`: Display text.
* `lang`: Language code of language of the form, if different from the language of the headword.
* `sc`: Script code of script of the form. Almost never needed.
* `t`: Gloss for the form.
* `gloss`: Gloss for the form (alias for `t`).
* `pos`: Part of speech of the form.
* `lit`: Literal meaning of the form.
* `tr`: Manual transliteration of the form.
* `ts`: Transcription of the form, for languages where the transliteration differs markedly from the pronunciation.
* `face`: Face to display the form in, e.g. {"hypothetical"} for a hypothetical form (unlinkable and displayed in italics).
* `nolinkinfl`: Make the form unlinkable.
]==]
function export.parse_term_with_modifiers(data)
local paramname, val, frob = data.paramname, data.val, data.frob
local function generate_obj(term, parse_err)
if frob then
term = frob(term, parse_err)
end
if data.parse_lang_prefix and term:find(":") then
return require(parse_utilities_module).generate_obj_maybe_parsing_lang_prefix {
term = term,
paramname = paramname,
parse_lang_prefix = true,
parse_err = parse_err,
}
else
return {term = term}
end
end
-- Check for inline modifier, e.g. מרים<tr:Miryem>. But exclude top-level HTML entry with <span ...>,
-- <sup> or similar in it.
if (val:find("<", nil, true) or data.splitchar) and not term_contains_top_level_html(val) and
-- don't parse inline modifiers if is_head and the value begins with a ~ (link modifier syntax)
(not data.is_head or not val:find("^~")) then
local param_mods = param_mods
if data.is_head then
param_mods = shallow_copy(param_mods)
param_mods.id = nil
end
if data.include_mods or data.exclude_mods then
if not data.is_head then
-- already copied when data.is_head
param_mods = shallow_copy(param_mods)
end
if data.include_mods then
local optional_mods = data.is_head and optional_headword_param_mods or optional_param_mods
for _, mod in ipairs(data.include_mods) do
if type(mod) == "table" then
if #mod ~= 2 then
error(("Internal error: Modifier spec %s in `include_mods` should be of length 2"):format(
dump(mod)))
end
local modkey, modvalue = unpack(mod)
param_mods[modkey] = modvalue
elseif not optional_mods[mod] then
error(("Internal error: Unrecognized modifier spec %s in `include_mods`"):format(
dump(mod)))
else
param_mods[mod] = optional_mods[mod]
end
end
end
if data.exclude_mods then
for _, mod in ipairs(data.exclude_mods) do
if not param_mods[mod] then
error(("Internal error: Modifier spec %s in `exclude_mods` not found among existing modifiers"
):format(dump(mod)))
else
param_mods[mod] = nil
end
end
end
end
return parse_inline_modifiers(val, {
paramname = paramname,
param_mods = param_mods,
generate_obj = generate_obj,
splitchar = data.splitchar,
preserve_splitchar = data.preserve_splitchar,
delimiter_key = data.delimiter_key,
escape_fun = data.escape_fun,
unescape_fun = data.unescape_fun,
pre_normalize_modifiers = data.pre_normalize_modifiers,
})
else
local retval = generate_obj(val)
if data.splitchar then
retval = {retval}
end
return retval
end
end
--[==[
Parse a list of inflection forms that may have inline modifiers attached. `data` is an object with the following fields:
* `forms`: The list of raw values to parse. Required.
* `paramname`: The name of the first parameter from which the value was taken; used in error messages. If this is a
two-element list, the first element is the first parameter and the second element is the prefix of the remaining
parameters. Parameter names that are numbers are handled correctly, as are those with \1 in it marking where the
parameter index goes. Required.
* `qualifiers`: If specified, a possibly gappy list of left qualifiers to add to the parsed terms (for compatibility
purposes).
* `splitchar`: As in `parse_term_with_modifiers()`. The resulting per-term lists will be flattened.
* `frob`, `include_mods`, `exclude_mods`, `is_head`, `preserve_splitchar`, `parse_lang_prefix`, `delimiter_key`,
`escape_fun`, `unescape_fun`, `pre_normalize_modifiers`: As in `parse_term_with_modifiers()`.
Returns a list of objects, suitable for storing as one of the lists in `headdata.inflections` (once a label is added),
where `headdata` is the structure passed to [[Module:headword]].
]==]
function export.parse_term_list_with_modifiers(data)
local paramname, forms = data.paramname, data.forms
local qualifiers = data.qualifiers
local first, restpref
if type(paramname) == "table" then
first = paramname[1]
restpref = paramname[2]
else
first = paramname
restpref = paramname
end
local terms = {}
data = shallow_copy(data)
for i, val in ipairs(forms) do
data.paramname = i == 1 and first or type(restpref) == "number" and restpref + i - 1 or
restpref:find("\1", nil, true) and restpref:gsub("\1", tostring(i)) or restpref .. i
data.val = val
local parsed = export.parse_term_with_modifiers(data)
if qualifiers and qualifiers[i] then
if data.splitchar then
for _, term in ipairs(parsed) do
term.q = {qualifiers[i]}
end
else
parsed.q = {qualifiers[i]}
end
end
if data.splitchar then
extend(terms, parsed)
else
terms[i] = parsed
end
end
return terms
end
--[==[
Construct a link to [[Appendix:Glossary]] for `entry`. If `text` is specified, it is the display text; otherwise,
`entry` is used.
]==]
function export.glossary_link(entry, text)
text = text or entry
return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end
function export.replace_glossary_links_in_label(label)
if label:find("<<", nil, true) then
label = label:gsub("<<(.-)|(.-)>>", export.glossary_link):gsub("<<(.-)>>", export.glossary_link)
end
return label
end
function export.insert_fixed_inflection(data)
local headdata, origterm, label = data.headdata, data.originating_term, data.label
local inflobj = data.inflobj or headdata
inflobj.inflections = inflobj.inflections or {}
if not origterm then
insert(inflobj.inflections, {
label = export.replace_glossary_links_in_label(label)
})
else
if origterm.id then
error(("It doesn't make sense to pass in an ID '%s' for label '%s' in conjunction with a term value '%s'"
):format(origterm.id, label, origterm.term))
end
-- Preserve decorations
origterm.term = nil
origterm.label = export.replace_glossary_links_in_label(label)
insert(inflobj.inflections, origterm)
end
end
--[==[
Insert previously-parsed terms into an `inflections` field. The `inflections` field will be initialized if needed.
`data` is an object with the following fields:
* `headdata`: The headword structure passed to [[Module:headword]]. Required.
* `inflobj`: The object whose `inflections` field the terms are inserted into. Defaults to `headdata`. Only needs
to be set for nested inflections, which are specified for an inflection object rather than the headword data
structure as a whole.
* `terms`: The list of parsed terms. If {nil} or omitted, nothing happens unless `request` is set.
* `label`: The label that the inflections are given; any parts of the label surrounded in <<...>> are linked to the
glossary. (If the contents of <<...> contain a | in them, they are a two-part link.) Required.
* `no_label`: If the term is {"-"} and there are no other terms, insert a fixed label with this value. Defaults to
{"no "} plus the label.
* `usually_no_label`: If the term is {"-"} and there are other terms, insert a fixed label with this value. Defaults to
{"usually no "} plus the label.
* `accel`: If specified, a full accelerator object to add to the inflections.
* `request`: If specified and no terms are given, insert a label with a request for inflections to be given.
* `enable_auto_translit`: If specified and terms are given, display automatic transliteration of the terms.
The return value indicates whether the inflection exists and how many terms are in it. It is an object with the
following fields:
* `exists`: {"yes"} if one or more terms were specified; {"no"} if the value was given as {"-"}; {"usually no"} if
the first value was given as {"-"} but additional terms were supplied; otherwise {nil}, indicating that the status
is unspecified.
* `numterms`: Number of terms in the inflection. Will be 0 unless `exists` has the value {"yes"} or {"usually no"}.
* `request`: True if no terms were specified but a term request was inserted into the inflection (because
`data.request` was specified). Otherwise {nil}.
]==]
function export.insert_inflection(data)
local headdata, terms, label = data.headdata, data.terms, data.label
local inflobj = data.inflobj or headdata
local retval = {}
if terms and terms[1] then
if terms[1].term == "-" then
if terms[2] then
export.insert_fixed_inflection {
headdata = headdata,
inflobj = inflobj,
originating_term = terms[1],
label = data.usually_no_label or "通常無" .. label,
}
remove(terms, 1)
retval.numterms = #terms
retval.exists = "usually no"
else
export.insert_fixed_inflection {
headdata = headdata,
inflobj = inflobj,
originating_term = terms[1],
label = data.no_label or "無" .. label,
}
retval.numterms = 0
retval.exists = "no"
return retval
end
else
retval.numterms = #terms
retval.exists = "yes"
end
if data.check_missing then
error("check_missing support removed; use checkredlinks=true in [[Module:headword]]")
end
terms.label = export.replace_glossary_links_in_label(label)
if data.accel then
terms.accel = data.accel
end
terms.enable_auto_translit = data.enable_auto_translit
inflobj.inflections = inflobj.inflections or {}
insert(inflobj.inflections, terms)
elseif data.request then
inflobj.inflections = inflobj.inflections or {}
insert(inflobj.inflections, {
label = export.replace_glossary_links_in_label(label),
request = true,
})
retval.numterms = 0
-- retval.exists == nil
retval.request = true
else
retval.numterms = 0
-- retval.exists == nil
end
return retval
end
--[==[
Parse raw arguments from `forms` for inline modifiers, and insert the resulting terms (which should not require
significant additional processing) into `headdata.inflections`. `data` is an object with the following fields:
* `forms`: The list of raw values to parse. If {nil} or omitted, nothing happens.
* `headdata`: The headword structure passed to [[Module:headword]]. Required.
* `paramname`: As in `parse_term_list_with_modifiers()`. Required.
* `label`: As in `insert_inflection()`. Required.
* `qualifiers`, `frob`, `include_mods`, `exclude_mods`, `is_head`, `splitchar`, `preserve_splitchar`, `delimiter_key`,
`escape_fun`, `unescape_fun`, `pre_normalize_modifiers`: As in `parse_term_list_with_modifiers()`.
* `accel`: As in `insert_inflection()`.
Return value is as in `insert_inflection()`.
]==]
function export.parse_and_insert_inflection(data)
local forms = data.forms
if forms and forms[1] then
data = shallow_copy(data)
data.forms = forms
data.terms = export.parse_term_list_with_modifiers(data)
return export.insert_inflection(data)
end
return {
numterms = 0
}
end
--[==[
Combine two sets of decorations. If either is {nil}, just return the other, and if both are {nil}, return {nil}.
]==]
function export.combine_decorations(decs1, decs2)
if not decs1 and not decs2 then
return nil
end
if not decs1 then
return decs2
end
if not decs2 then
return decs1
end
local combined = shallow_copy(decs1)
for _, dec in ipairs(decs2) do
insert_if_not(combined, dec)
end
return combined
end
function export.combine_qualifiers_or_labels(...)
-- FIXME: Added 2026-09-17. Remove after a month.
error("Use combine_decorations instead")
end
--[==[
Combine the decorations (qualifiers, labels, references) and ID's of two term objects. `destobj` is the "destination
term object" into which the combined properties are written, and `srcobj` is the "source object" into which the
properties are merged. `destobj` is side-effected (but the lists inside of `destobj` are not); if this is undesirable,
make sure to shallow-copy `destobj` first. If both objects have values for a given decoration, the values of `destobj`
come first. If both objects have a value for `id`, the values must match or an error is thrown; otherwise, the resulting
value of `id` comes from whichever one is defined.
'''NOTE:''' This may not be the correct behavior when deduplicating a list of term objects. See
`insert_termobj_combining_duplicates` for a different approach.
]==]
function export.combine_termobj_decorations(destobj, srcobj)
destobj.q = export.combine_decorations(destobj.q, srcobj.q)
destobj.qq = export.combine_decorations(destobj.qq, srcobj.qq)
destobj.l = export.combine_decorations(destobj.l, srcobj.l)
destobj.ll = export.combine_decorations(destobj.ll, srcobj.ll)
destobj.refs = export.combine_decorations(destobj.refs, srcobj.refs)
if destobj.id and srcobj.id and destobj.id ~= srcobj.id then
-- FIXME: We probably want to pass in an error function
error(("Can't specify two different ID's %s and %s when combining objects"):format(srcobj.id, destobj.id))
end
destobj.id = destobj.id or srcobj.id
return destobj
end
function export.combine_termobj_qualifiers_labels(...)
-- FIXME: Added 2026-09-17. Remove after a month.
error("Use combine_termobj_decorations instead")
end
function export.termobj_has_decorations(obj)
return obj.q and obj.q[1] or obj.qq and obj.qq[1] or obj.l and obj.l[1] or obj.ll and obj.ll[1] or
obj.refs and obj.refs[1]
end
function export.termobj_has_qualifiers_or_labels(...)
-- FIXME: Added 2026-09-17. Remove after a month.
error("Use termobj_has_decorations instead")
end
local function one_decoration_equal(prop1, prop2)
local prop1_is_nil = not prop1 or not prop1[1]
local prop2_is_nil = not prop2 or not prop2[1]
if prop1_is_nil and prop2_is_nil then
return true
end
if prop1_is_nil or prop2_is_nil then
return false
end
return deepEquals(prop1, prop2)
end
function export.termobj_decorations_equal(obj1, obj2)
return one_decoration_equal(obj1.q, obj2.q) and
one_decoration_equal(obj1.qq, obj2.qq) and
one_decoration_equal(obj1.l, obj2.l) and
one_decoration_equal(obj1.ll, obj2.ll) and
one_decoration_equal(obj1.refs, obj2.refs) and
obj1.id == obj2.id
end
function export.termobj_ancillary_properties_equal(...)
-- FIXME: Added 2026-09-17. Remove after a month.
error("Use termobj_decorations_equal instead")
end
function export.convert_termobj_to_formobj(termobj)
local formobj = {
form = termobj.term,
translit = termobj.tr,
}
local footnotes
local function mods_to_footnote(mod_prefix, mod_vals)
if mod_vals and mod_vals[1] then
footnotes = footnotes or {}
for _, val in ipairs(mod_vals) do
insert(footnotes, "[" .. mod_prefix .. ":" .. val .. "]")
end
end
end
mods_to_footnote("q", termobj.q)
mods_to_footnote("qq", termobj.qq)
mods_to_footnote("l", termobj.l)
mods_to_footnote("ll", termobj.ll)
mods_to_footnote("ref", termobj.refs)
mods_to_footnote("id", termobj.id and {termobj.id} or nil)
formobj.footnotes = footnotes
return formobj
end
local recognized_multi_mods = {
q = "q",
qq = "qq",
l = "l",
ll = "ll",
ref = "refs",
}
local recognized_single_mods = {
id = "id",
}
function export.add_footnote_to_termobj(termobj, footnote)
local stripped_footnote = footnote:match("^%[(.*)%]$")
if not stripped_footnote then
error("Internal error: Footnote should be surrounded by brackets at this stage: " .. footnote)
end
local prefix, rest = stripped_footnote:match("^([a-z]+):(.+)$")
local field, is_multi
if prefix then
if recognized_multi_mods[prefix] then
field = recognized_multi_mods[prefix]
is_multi = true
elseif recognized_single_mods[prefix] then
field = recognized_single_mods[prefix]
is_multi = false
end
end
if not field then
rest = stripped_footnote
field = "l"
is_multi = true
end
if is_multi then
if not termobj[field] then
termobj[field] = {}
end
insert(termobj[field], rest)
else
if termobj[field] and termobj[field] ~= rest then
error(("Can't set two values for '%s': '%s' and '%s'"):format(field, termobj[field], rest))
end
termobj[field] = rest
end
end
function export.convert_formobj_to_termobj(formobj)
local termobj = {
term = formobj.form,
tr = formobj.translit,
}
if formobj.footnotes then
for _, footnote in ipairs(formobj.footnotes) do
export.add_footnote_to_termobj(termobj, footnote)
end
end
return termobj
end
local function extract_termobj_field_modifiers(fieldval)
return fieldval:match("^([*+]?)(.*)$")
end
function export.remove_termobj_field_modifiers(termobj)
local function remove_field_modifiers(field)
if termobj[field] and termobj[field][1] then
local any_field_modifiers = false
for _, val in ipairs(termobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if field_mods ~= "" then
any_field_modifiers = true
break
end
end
local new_field = {}
if any_field_modifiers then
for _, val in ipairs(termobj[field]) do
local _, field_without_mods = extract_termobj_field_modifiers(val)
insert_if_not(new_field, field_without_mods)
end
termobj[field] = new_field
end
end
end
remove_field_modifiers("q")
remove_field_modifiers("qq")
remove_field_modifiers("l")
remove_field_modifiers("ll")
remove_field_modifiers("refs")
end
function export.insert_termobj_combining_duplicates(destobjs, termobj)
for _, destobj in ipairs(destobjs) do
if destobj.term == termobj.term and destobj.tr == termobj.tr then
-- Form already present; maybe combine footnotes.
local function combine_field_values(field)
if termobj[field] and termobj[field][1] then
-- Check to see if there are existing values with *; if so, remove them.
if destobj[field] and destobj[field][1] then
local any_values_with_asterisk = false
for _, val in ipairs(destobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if field_mods:find("%*") then
any_values_with_asterisk = true
break
end
end
if any_values_with_asterisk then
local filtered_values = {}
for _, val in ipairs(destobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if not field_mods:find("%*") then
insert(filtered_values, val)
end
end
if filtered_values[1] then
destobj[field] = filtered_values
else
destobj[field] = nil
end
end
end
local any_footnotes_with_plus = false
for _, val in ipairs(termobj[field]) do
local field_mods, _ = extract_termobj_field_modifiers(val)
if field_mods:find("%+") then
any_footnotes_with_plus = true
break
end
end
if any_footnotes_with_plus then
if not destobj[field] then
destobj[field] = {}
else
destobj[field] = shallow_copy(destobj[field])
end
for _, val in ipairs(termobj[field]) do
local already_seen = false
local field_mods, field_without_mods = extract_termobj_field_modifiers(val)
if field_mods:find("%+") then
for _, existing_val in ipairs(destobj[field]) do
local _, existing_field_without_mods =
extract_termobj_field_modifiers(existing_val)
if existing_field_without_mods == field_without_mods then
already_seen = true
break
end
end
if not already_seen then
insert(destobj[field], val)
end
end
end
end
end
end
combine_field_values("q")
combine_field_values("qq")
combine_field_values("l")
combine_field_values("ll")
combine_field_values("refs")
if destobj.id and termobj.id and destobj.id ~= termobj.id then
-- FIXME: We probably want to pass in an error function
error(("Can't specify two different ID's %s and %s when combining objects"):format(termobj.id, destobj.id))
end
destobj.id = destobj.id or termobj.id
return
end
end
insert(destobjs, termobj)
end
export.allowed_special_indicators = {
["first"] = true,
["first-second"] = true,
["first-last"] = true,
["second"] = true,
["last"] = true,
["each"] = true,
["+"] = true, -- requests the default behavior with preposition handling
}
--[==[
Check for special indicators (values such as {"+first"} or {"+first-last"} that are used in a `pl`, `f`, etc. argument
and indicate how to inflect a multiword term). If `form` is such an indicator, the return value is `form` minus
the initial `+` sign; otherwise, if form begins with a `+` sign, an error is thrown; otherwise the return value is nil.
]==]
function export.get_special_indicator(form, noerror)
if form:find("^%+") then
form = form:gsub("^%+", "")
if not export.allowed_special_indicators[form] then
if noerror then
return nil
end
local indicators = {}
for indic, _ in pairs(export.allowed_special_indicators) do
insert(indicators, "+" .. indic)
end
sort(indicators)
error("Special inflection indicator beginning with '+' can only be " ..
mw.text.listToText(indicators) .. ": +" .. form)
end
return form
end
return nil
end
local function add_endings(bases, endings)
local retval = {}
if type(bases) ~= "table" then
bases = {bases}
end
if type(endings) ~= "table" then
endings = {endings}
end
for _, base in ipairs(bases) do
for _, ending in ipairs(endings) do
insert(retval, base .. ending)
end
end
return retval
end
--[==[
Inflect a possibly multiword or hyphenated term `form` using the function `inflect`, which is a function of one argument
that is called on a single word to inflect and should return either the inflected word or a list of inflected words.
`special` indicates how to inflect the multiword term and should be e.g. {"first"} to inflect only the first word,
{"first-last"} to inflect the first and last words, {"each"} to inflect each word, etc. See `allowed_special_indicators`
above for the possibilities. If `special` is `+`, or is omitted and the term is multiword (i.e. containing a space
character), and `prepositions` is supplied, the function checks for multiword or hyphenated terms containing the
prepositions in `prepositions`, e.g. Italian [[senso di marcia]] or [[medaglia d'oro]] or Portuguese
[[tartaruga-do-mar]]. If such a term is found, only the first word is inflected. Otherwise, the default is
{"first-last"}. `prepositions` is a list of Lua patterns matching prepositions. The patterns will automatically have the
separator character (space or hyphen) added to the left side but not the right side, so they should contain a space
character (which will automatically be converted to the appropriate separator) on the right side unless the preposition
is joined on the right side with an apostrophe. Examples of preposition patterns for Italian are {"di "}, {"sull'"} and
{"d?all[oae] "} (which matches {"dallo "}, {"dalle "}, {"alla "}, etc.).
The return value is always either a list of inflected multiword or hyphenated terms, or nil if `special` is omitted
and `form` is not multiword. (If `special` is specified and `form` is not multiword or hyphenated, an error results.)
]==]
function export.handle_multiword(form, special, inflect, prepositions, sep)
sep = sep or form:find(" ") and " " or "%-"
local raw_sep = sep == " " and " " or "-"
-- Used to add regex version of separator in the replacement portion of ugsub() or :gsub()
local sep_replacement = sep == " " and " " or "%%-"
-- Given a Lua pattern, replace space with the appropriate separator.
local function hack_re(re)
if sep == " " then
return re
end
return (re:gsub(" ", sep_replacement))
end
if special == "first" then
local first, rest = form:match(hack_re("^(.-)( .*)$"))
if not first then
error("Special indicator 'first' can only be used with a multiword term: " .. form)
end
return add_endings(inflect(first), rest)
elseif special == "second" then
local first, second, rest = form:match(hack_re("^([^ ]+ )([^ ]+)( .*)$"))
if not first then
error("Special indicator 'second' can only be used with a term with three or more words: " .. form)
end
return add_endings(add_endings({first}, inflect(second)), rest)
elseif special == "first-second" then
local first, space, second, rest = form:match(hack_re("^([^ ]+)( )([^ ]+)( .*)$"))
if not first then
error("Special indicator 'first-second' can only be used with a term with three or more words: " .. form)
end
return add_endings(add_endings(add_endings(inflect(first), space), inflect(second)), rest)
elseif special == "each" then
local terms = split(form, sep)
if #terms < 2 then
error("Special indicator 'each' can only be used with a multiword term: " .. form)
end
for i, term in ipairs(terms) do
terms[i] = inflect(term)
if i > 1 then
terms[i] = add_endings(raw_sep, terms[i])
end
end
local result = ""
for _, term in ipairs(terms) do
result = add_endings(result, term)
end
return result
elseif special == "first-last" then
local first, middle, last = form:match(hack_re("^(.-)( .* )(.-)$"))
if not first then
first, middle, last = form:match(hack_re("^(.-)( )(.*)$"))
end
if not first then
error("Special indicator 'first-last' can only be used with a multiword term: " .. form)
end
return add_endings(add_endings(inflect(first), middle), inflect(last))
elseif special == "last" then
local rest, last = form:match(hack_re("^(.* )(.-)$"))
if not rest then
error("Special indicator 'last' can only be used with a multiword term: " .. form)
end
return add_endings(rest, inflect(last))
elseif special and special ~= "+" then
error("Unrecognized special=" .. special)
end
-- Only do default behavior if special indicator '+' explicitly given or separator is space; otherwise we will
-- break existing behavior with hyphenated words.
if (special == "+" or sep == " ") and form:find(sep) then
if prepositions then
-- check for prepositions in the middle of the word; do it this way so we can handle
-- more than one word before the preposition (and usually inflect each word)
for _, prep in ipairs(prepositions) do
local first, space_prep_rest = umatch(form, hack_re("^(.-)( " .. prep .. ".*)$"))
if first then
return add_endings(inflect(first), space_prep_rest)
end
end
end
-- multiword or hyphenated expressions default to first-last; we need to pass in the separator to avoid
-- problems with multiword terms containing hyphens in the individual words
return export.handle_multiword(form, "first-last", inflect, prepositions, sep)
end
return nil
end
local function link_hyphen_split_component(word, data)
if data.link_hyphen_split_component then
return data.link_hyphen_split_component(word)
else
return "[[" .. word .. "]]"
end
end
-- Default function to split a word on apostrophes. Don't split apostrophes at the beginning or end of a word (e.g.
-- [['ndrangheta]] or [[po']]). Handle multiple apostrophes correctly, e.g. [[l'altr'ieri]] -> [[l']][altr']][[ieri]].
function export.default_split_apostrophe(word, data)
local apostrophe_parts = split(word, "'", true, true)
local linked_apostrophe_parts = {}
local apostrophes_at_beginning = ""
local i = 1
-- Apostrophes at beginning get attached to the first word after (which will always exist but may
-- be blank if the word consists only of apostrophes).
while i < #apostrophe_parts do -- <, not <=, in case the word consists only of apostrophes
local apostrophe_part = apostrophe_parts[i]
i = i + 1
if apostrophe_part == "" then
apostrophes_at_beginning = apostrophes_at_beginning .. "'"
else
break
end
end
apostrophe_parts[i] = apostrophes_at_beginning .. apostrophe_parts[i]
-- Now, do the remaining parts. A blank part indicates more than one apostrophe in a row; we join
-- all of them to the preceding word.
while i <= #apostrophe_parts do
local apostrophe_part = apostrophe_parts[i]
if apostrophe_part == "" then
linked_apostrophe_parts[#linked_apostrophe_parts] =
linked_apostrophe_parts[#linked_apostrophe_parts] .. "'"
elseif i == #apostrophe_parts then
insert(linked_apostrophe_parts, apostrophe_part)
else
insert(linked_apostrophe_parts, apostrophe_part .. "'")
end
i = i + 1
end
for j, tolink in ipairs(linked_apostrophe_parts) do
linked_apostrophe_parts[j] = link_hyphen_split_component(tolink, data)
end
return concat(linked_apostrophe_parts)
end
--[=[
Auto-add links to a word that should not have spaces but may have hyphens and/or apostrophes. We split off final
punctuation, then split on hyphens if `data.split_hyphen` is given, and also split on apostrophes if
`data.split_apostrophe` is given. We only split on hyphens if they are in the middle of the word, not at the beginning
or end (hyphens at the beginning or end indicate suffixes or prefixes, respectively). `include_hyphen_prefixes`, if
given, is a set of prefixes (not including the final hyphen) where we should include the final hyphen in the prefix.
Hence, e.g. if "anti" is in the set, a Portuguese word like [[anti-herói]] "anti-hero" will be split [[anti-]][[herói]]
(whereas a word like [[código-fonte]] "source code" will be split as [[código]]-[[fonte]]).
If `data.split_apostrophe` is specified, we split on apostrophes unless `data.no_split_apostrophe_words` is given and
the word is in the specified set, such as French [[c'est]] and [[quelqu'un]]. If `data.split_apostrophe` is true, the
default algorithm applies, which splits on all apostrophes except those at the beginning and end of a word (as in
Italian [['ndrangheta]] or [[po']]), and includes the apostrophe in the link to its left (so we auto-split French
[[l'eau]] as [[l']][[eau]] and [[l'altr'ieri]] as [[l']][altr']][[ieri]]). If `data.split_apostrophe` is specified
but not `true`, it should be a function of one argument that does custom apostrophe-splitting. The argument is the word
to split, and the return value should be the split and linked word.
]=]
local function add_single_word_links(space_word, data, term_has_spaces)
local space_word_no_punct, punct
local punct_pattern = data.punctuation
if punct_pattern and is_callable(punct_pattern) then
space_word_no_punct, punct = punct_pattern(space_word)
else
if punct_pattern == nil then
punct_pattern = "[,;:?!]"
end
space_word_no_punct, punct = umatch(space_word, "^(.*)(" .. punct_pattern .. ")$")
end
space_word_no_punct = space_word_no_punct or space_word
punct = punct or ""
local words
if space_word_no_punct:sub(1, 1) == "-" or space_word_no_punct:sub(-1) == "-" then
-- don't split prefixes and suffixes
words = {space_word_no_punct}
else
local splitter
if term_has_spaces then
splitter = data.split_hyphen_when_space
else
splitter = data.split_hyphen_when_no_space
end
if is_callable(splitter) then
words = splitter(space_word_no_punct)
if type(words) == "string" then
return words .. punct
end
end
end
if not words then
local split_hyphen
if term_has_spaces then
split_hyphen = data.split_hyphen_when_space
else
split_hyphen = data.split_hyphen_when_no_space
if split_hyphen == nil then -- default to true; use `false` to avoid this
split_hyphen = true
end
end
if split_hyphen then
words = split(space_word_no_punct, "-", true, true)
else
words = {space_word_no_punct}
end
end
local linked_words = {}
for j, word in ipairs(words) do
if j < #words and data.include_hyphen_prefixes and data.include_hyphen_prefixes[word] then
word = "[[" .. word .. "-]]"
elseif j > 1 and data.include_hyphen_suffixes and data.include_hyphen_suffixes[word] then
word = "[[-" .. word .. "]]"
else
-- Don't split on apostrophes if the word is in `no_split_apostrophe_words`.
if (not data.no_split_apostrophe_words or not data.no_split_apostrophe_words[word]) and
data.split_apostrophe and word:find("'", nil, true) then
if data.split_apostrophe == true then
word = export.default_split_apostrophe(word, data)
else -- custom apostrophe splitter/linker
word = data.split_apostrophe(word)
end
elseif word ~= "" then -- avoid -[[]]- (e.g. f--k)
word = link_hyphen_split_component(word, data)
end
if j < #words then
word = word .. "-"
end
end
insert(linked_words, word)
end
return concat(linked_words) .. punct
end
--[=[
Auto-add links to a multiword term. `data` contains fields customizing how to do this. By default we proceed as follows:
(1) If the term already has embedded links in it, they are left unchanged.
(2) Otherwise, if there are spaces present, we split on spaces and link each word separately.
(3) If a given space-separated component ends in punctuation (defaulting to [,;:?!]), it is separated off, the remainder
of the algorithm run, and the punctuation pasted back on.
(4) If there are hyphens in a given space-separated component, we may link each hyphenated term separately depending
on the settings in `data`. Normally the hyphens are not included in the linked terms, but this can be overridden
for specific prefixes and/or suffixes. By default, if there are spaces in the multiword term, we do not link
hyphenated components (because of cases like "boire du petit-lait" where "petit-lait" should be linked as a whole),
but do so otherwise (e.g. for "avant-avant-hier"); this can overridden for cases like "croyez-le ou non".
Cases where only some of the hyphens should be split can always be handled by explicitly specifying the head (e.g.
"Nord-Pas-de-Calais" given as head=[[Nord]]-[[Pas-de-Calais]]).
(5) If there are apostrophes in a given component, we may link each apostrophe-separated term separately depending
on the settings in `data`, including the apostrophe in the link to its left (so we split "de l'eau" as
"[[de]] [[l']][[eau]]").
The settings in `data` are as follows:
`split_hyphen_when_no_space`: Whether to split on hyphens when the term has no spaces. Defaults to true if set to `nil`.
This can be a function of one argument, to implement a custom splitting algorithm for hyphen-separated terms. If
this returns [FIXME: FINISH ME ...]
If `data.split_apostrophe` is specified, we split on apostrophes unless `data.no_split_apostrophe_words` is given and
the word is in the specified set, such as French [[c'est]] and [[quelqu'un]]. If `data.split_apostrophe` is true, the
default algorithm applies, which splits on all apostrophes except those at the beginning and end of a word (as in
Italian [['ndrangheta]] or [[po']]), and includes the apostrophe in the link to its left (so we auto-split French
[[l'eau]] as [[l']][[eau]] and [[l'altr'ieri]] as [[l']][altr']][[ieri]]). If `data.split_apostrophe` is specified
but not `true`, it should be a function of one argument that does custom apostrophe-splitting. The argument is the word
to split, and the return value should be the split and linked word.
We don't always split on hyphens because of cases like "boire du petit-lait" where "petit-lait" should be linked as a
whole, but provide the option to do it for cases like "croyez-le ou non". If there's no space, however, then it makes
sense to split on hyphens by `no_split_apostrophe_words` and `include_hyphen_prefixes` allow for special-case handling
of particular words and are as described in the comment above add_single_word_links().
]=]
function export.add_links_to_multiword_term(term, data)
if term:match("[%[%]]") then
return term
end
local words = split(term, " ", true, true)
local term_has_spaces = #words > 1
local linked_words = {}
for _, word in ipairs(words) do
insert(linked_words, add_single_word_links(word, data, term_has_spaces))
end
local retval = concat(linked_words, " ")
-- If we ended up with a single link consisting of the entire term,
-- remove the link.
return retval:match("^%[%[([^%[%]]*)%]%]$") or retval
end
local function canonicalize_begin_end_spec(spec)
local from, to = spec:match("^(.-):(.*)$")
if not from then
from = spec
to = ""
end
return from, to
end
--[==[
Given a `linked_term` that is the output of add_links_to_multiword_term(), apply modifications as given in
`modifier_spec` to change the link destination of subterms (normally single-word non-lemma forms; sometimes
collections of adjacent words). This is usually used to link non-lemma forms to their corresponding lemma, but can
also be used to replace a span of adjacent separately-linked words to a single multiword lemma. The format of
`modifier_spec` is one or more semicolon-separated subterm specs, where each such spec is of the form
SUBTERM:DEST, where SUBTERM is one or more words in the `linked_term` but without brackets in them, and DEST is the
corresponding link destination to link the subterm to. Any occurrence of ~ in DEST is replaced with SUBTERM.
Alternatively, a single modifier spec can be of the form BEGIN[FROM:TO], which is equivalent to writing
BEGINFROM:BEGINTO (see example below).
For example, given the source phrase [[il bue che dice cornuto all'asino]] "the pot calling the kettle black"
(literally "the ox that calls the donkey horned/cuckolded"), the result of calling add_links_to_multiword_term()
is [[il]] [[bue]] [[che]] [[dice]] [[cornuto]] [[all']][[asino]]. With a modifier_spec of 'dice:dire', the result
is [[il]] [[bue]] [[che]] [[dire|dice]] [[cornuto]] [[all']][[asino]]. Here, based on the modifier spec, the
non-lemma form [[dice]] is replaced with the two-part link [[dire|dice]].
Another example: given the source phrase [[chi semina vento raccoglie tempesta]] "sow the wind, reap the whirlwind"
(literally (he) who sows wind gathers [the] tempest"). The result of calling add_links_to_multiword_term() is
[[chi]] [[semina]] [[vento]] [[raccoglie]] [[tempesta]], and with a modifier_spec of 'semina:~re; raccoglie:~re',
the result is [[chi]] [[seminare|semina]] [[vento]] [[raccogliere|raccoglie]] [[tempesta]]. Here we use the ~
notation to stand for the non-lemma form in the destination link.
A more complex example is [[se non hai altri moccoli puoi andare a letto al buio]], which becomes
[[se]] [[non]] [[hai]] [[altri]] [[moccoli]] [[puoi]] [[andare]] [[a]] [[letto]] [[al]] [[buio]] after calling
add_links_to_multiword_term(). With the following modifier_spec:
'hai:avere; altr[i:o]; moccol[i:o]; puoi: potere; andare a letto:~; al buio:~', the result of applying the spec is
[[se]] [[non]] [[avere|hai]] [[altro|altri]] [[moccolo|moccoli]] [[potere|puoi]] [[andare a letto]] [[al buio]].
Here, we rely on the alternative notation mentioned above for e.g. 'altr[i:o]', which is equivalent to 'altri:altro',
and link multiword subterms using e.g. 'andare a letto:~'. (The code knows how to handle multiword subexpressions
properly, and if the link text and destination are the same, only a single-part link is formed.)
]==]
function export.apply_link_modifiers(linked_term, modifier_spec, lang)
local split_modspecs = split(modifier_spec, "%s*;%s*")
for j, modspec in ipairs(split_modspecs) do
local id
if modspec:find("<") then
local rest
rest, id = modspec:match("^(.*)<id:(.-)>$")
if rest then
modspec = rest
end
end
local subterm, dest, otherlang
local begin_spec, rest, end_spec = modspec:match("^%[(.-)%]([^:]*)%[(.-)%]$")
if begin_spec then
local begin_from, begin_to = canonicalize_begin_end_spec(begin_spec)
local end_from, end_to = canonicalize_begin_end_spec(end_spec)
subterm = begin_from .. rest .. end_from
dest = begin_to .. rest .. end_to
end
if not subterm then
rest, end_spec = modspec:match("^([^:]*)%[(.-)%]$")
if rest then
local end_from, end_to = canonicalize_begin_end_spec(end_spec)
subterm = rest .. end_from
dest = rest .. end_to
end
end
if not subterm then
begin_spec, rest = modspec:match("^%[(.-)%]([^:]*)$")
if begin_spec then
local begin_from, begin_to = canonicalize_begin_end_spec(begin_spec)
subterm = begin_from .. rest
dest = begin_to .. rest
end
end
if not subterm then
subterm, dest = modspec:match("^(.-)%s*:%s*(.*)$")
if subterm and subterm ~= "^" and subterm ~= "$" then
local langdest
-- Parse off an initial language code (e.g. 'en:Higgs', 'la:minūtia' or 'grc:σκατός'). Also handle
-- Wikipedia prefixes ('w:Abatemarco' or 'w:it:Colle Val d'Elsa').
otherlang, langdest = dest:match("^([A-Za-z0-9._-]+):([^ ].*)$")
if otherlang == "w" then
local foreign_wikipedia, foreign_term = langdest:match("^([A-Za-z0-9._-]+):([^ ].*)$")
if foreign_wikipedia then
otherlang = otherlang .. ":" .. foreign_wikipedia
langdest = foreign_term
end
dest = ("%s:%s"):format(otherlang, langdest)
otherlang = nil
elseif otherlang then
otherlang = get_lang(otherlang, true, "allow etym")
dest = langdest
end
end
end
if not subterm then
if modspec == "?" or modspec == "!" then
subterm = "$"
dest = modspec
elseif modspec == "..." or modspec == "...?" then
subterm = "$"
dest = " " .. modspec
elseif modspec:find("^[A-Z]$") then
-- X, Y, etc. by themselves are unlinked, to help with snowclones
subterm = modspec
dest = "_"
else
subterm = modspec
dest = "~"
end
end
if subterm == "^" then
linked_term = dest:gsub("_", " ") .. linked_term
elseif subterm == "$" then
linked_term = linked_term .. dest:gsub("_", " ")
else
if subterm:find("[", nil, true) then
error(("Subterm '%s' in modifier spec '%s' cannot have brackets in it"):format(
escape_wikicode(subterm), escape_wikicode(modspec)))
end
local escaped_subterm = pattern_escape(subterm)
local subterm_re = "%[%[" .. escaped_subterm:gsub("(%%?[ ',%-])", "%%]*%1%%[*") .. "%]%]"
local expanded_dest
if dest:find("~", nil, true) then
expanded_dest = dest:gsub("~", replacement_escape(subterm))
else
expanded_dest = dest
end
if otherlang then
expanded_dest = expanded_dest .. "#" .. otherlang:getCanonicalName()
end
local subterm_replacement
if expanded_dest == "_" then
subterm_replacement = subterm
if id then
error("Can't supply <id:...> with an unlinked subterm")
end
if otherlang then
error("Can't supply prefixed language with an unlinked subterm")
end
elseif id or otherlang then
if id and expanded_dest:find("[", nil, true) then
error("Can't supply <id:...> with destination with embedded brackets")
end
subterm_replacement = require(links_module).language_link {
lang = otherlang or lang,
term = expanded_dest,
alt = subterm,
id = id,
}
elseif expanded_dest:find("[", nil, true) then
-- Use the destination directly if it has brackets in it (e.g. to put brackets around parts of a word).
subterm_replacement = expanded_dest
elseif expanded_dest == subterm then
subterm_replacement = "[[" .. subterm .. "]]"
else
subterm_replacement = "[[" .. expanded_dest .. "|" .. subterm .. "]]"
end
local escaped_subterm_replacement = replacement_escape(subterm_replacement)
local replaced_linked_term = ugsub(linked_term, subterm_re, escaped_subterm_replacement)
if replaced_linked_term == linked_term then
mw.log(("Attempted to replace %s with %s in %s"):format(subterm_re, escaped_subterm_replacement, linked_term))
error(("Subterm '%s' could not be located in %slinked expression %s, or replacement same as subterm"):format(
subterm, j > 1 and "intermediate " or "", escape_wikicode(linked_term)))
else
linked_term = replaced_linked_term
end
end
end
return linked_term
end
return export
shzqh4uhsj2wk8mi3myseh108pwldkk
Morze Martwe
0
1828430
9909133
7148813
2026-09-29T08:39:23Z
TongcyBot
83009
Bot: 從英語維基詞典更新參數
9909133
wikitext
text/x-wiki
==波蘭語==
{{wiki|lang=pl}}
===發音===
{{pl-pr|a=Pl-Morze Martwe.ogg}}
===專有名詞===
{{pl-proper noun|n|head=[[morze|Morze]] [[martwy|Martwe]]}}
# [[死海]] {{c|pl|湖}}
====變格====
{{pl-decl-noun-sing
|Morze Martwe
|Morza Martwego
|Morzu Martwemu
|Morze Martwe
|Morzem Martwym
|Morzu Martwym
|Morze Martwe
}}
o8wxb0hrdypx3r50u0cqcj08aih43ko
zapadni
0
1851174
9909144
9351132
2026-09-29T08:44:23Z
TongcyDai
53191
9909144
wikitext
text/x-wiki
{{also|západní}}
==波蘭語==
===詞源===
{{suf|pl|zapad|ni}}
===發音===
{{pl-pr}}
===形容詞===
{{pl-adj}}
# {{lb|pl|舊}} [[西方]]的
#: {{syn|pl|zachodni}}
====變格====
{{Pl-decl-adj-auto}}
==塞爾維亞-克羅地亞語==
===發音===
* {{IPA|sh|/zâːpadniː/}}
* {{hyphenation|sh|za|pa|dni}}
===形容詞===
{{sh-adj|zȃpadnī|comp=zapadniji}}
# [[西方]]的
====變格====
{{sh-adj-def-full|zapadn|o|zapadnij|e}}
t9tt69v082v2wkw0pyhj4ryvd8qb4nf
9909146
9909144
2026-09-29T08:45:45Z
TongcyDai
53191
9909146
wikitext
text/x-wiki
{{also|západní}}
==波蘭語==
===詞源===
{{suf|pl|zapad|ni}}
===發音===
{{pl-pr}}
===形容詞===
{{pl-adj}}
# {{lb|pl|obsolete}} [[西方]]的
#: {{syn|pl|zachodni}}
====變格====
{{Pl-decl-adj-auto}}
==塞爾維亞-克羅地亞語==
===詞源===
{{inh+|sh|sla-pro|*zapadьnъ}}。
===發音===
* {{IPA|sh|/zâːpadniː/}}
* {{hyphenation|sh|za|pa|dni}}
===形容詞===
{{sh-adj|zȃpadnī|comp=zapadniji}}
# [[西方]]的
====變格====
{{sh-adj-def-full|zapadn|o|zapadnij|e}}
tgbroev68re0pam0l7pu9v93pul90gp
Module:Etymology/templates/descendant
828
1859226
9909058
9888910
2026-09-29T07:25:21Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909058
Scribunto
text/plain
local export = {}
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local descendants_tree_module = "Module:descendants tree"
local etymology_style_css = "Module:etymology/style.css"
local labels_module = "Module:labels"
local languages_module = "Module:languages"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
local scripts_module = "Module:scripts"
local table_module = "Module:table"
local table_module_list_to_set = "Module:table/listToSet"
local template_styles_module = "Module:TemplateStyles"
local concat = table.concat
local insert = table.insert
local list_to_set = require(table_module_list_to_set)
local error_on_no_descendants = false
local function track(page)
return require(debug_track_module)("descendant/" .. page)
end
local function ine(arg)
if arg == "" then
return nil
else
return arg
end
end
local function add_tooltip(text, tooltip)
return '<span class="desc-arr" title="' .. tooltip .. '">' .. text .. '</span>'
end
-- Boolean params indicating whether a descendant term (or all terms) are particular sorts of borrowings.
local bortypes = {"inh", "bor", "lbor", "slb", "obor", "translit", "der", "clq", "pclq", "sml", "unc"}
local bortype_set = list_to_set(bortypes)
-- Aliases of clq=.
local calque_aliases = {"cal", "calq", "calque"}
local calque_alias_set = list_to_set(calque_aliases)
-- Aliases of pclq=.
local partial_calque_aliases = {"pcal", "pcalq", "pcalque"}
local partial_calque_alias_set = list_to_set(partial_calque_aliases)
local semi_learned_borrowing_aliases = {"slbor"}
local semi_learned_borrowing_alias_set = list_to_set(semi_learned_borrowing_aliases)
--- Return a function of one argument `field` (a param name), which fetches `args`[`field`].default if index == 0, else
--- `container`[`field`].
local function get_val(container, args, index)
return function(field)
if index == 0 then
return args[field].default
else
return container[field]
end
end
end
local function get_arrow(container, args, index)
local val = get_val(container, args, index)
local arrow
if val("bor") then
arrow = add_tooltip("→", "借詞")
elseif val("lbor") then
arrow = add_tooltip("→", "學術借詞")
elseif val("slb") then
arrow = add_tooltip("→", "半學術借詞")
elseif val("obor") then
arrow = add_tooltip("→", "形譯詞")
elseif val("translit") then
arrow = add_tooltip("→", "轉寫")
elseif val("clq") then
arrow = add_tooltip("→", "仿譯詞")
elseif val("pclq") then
arrow = add_tooltip("→", "部分仿譯")
elseif val("sml") then
arrow = add_tooltip("→", "語義借用")
elseif val("inh") or (val("unc") and not val("der")) then
arrow = add_tooltip(">", "繼承")
else
arrow = ""
end
-- allow der=1 in conjunction with bor=1 to indicate e.g. English "pars recta"
-- derived and borrowed from Latin "pars".
if val("der") then
arrow = arrow .. add_tooltip("⇒", "reshaped by analogy or addition of morphemes")
end
if val("unc") then
arrow = arrow .. add_tooltip("?", "uncertain")
end
if arrow ~= "" then
arrow = arrow .. " "
end
return arrow
end
-- Return the pre-decoration text for the `index`th term, or the overall pre-decoration text if index == 0.
local function get_pre_decorations(container, args, index)
if index > 0 then
-- per term decorations are handled at the subitem level, by full_link().
return nil, nil
end
local val = get_val(container, args, index)
return val("l"), val("q")
end
-- Return the post-decoration text for the `index`th term, or the overall post-decoration text if index == 0.
local function get_post_decorations(container, args, index, lang)
local val = get_val(container, args, index)
local boolean_labels = {}
if val("inh") then
insert(boolean_labels, "繼承")
end
if val("lbor") then
insert(boolean_labels, "學術")
end
if val("slb") then
insert(boolean_labels, "半學術")
end
if val("translit") then
insert(boolean_labels, "轉寫")
end
if val("clq") then
insert(boolean_labels, "仿譯")
end
if val("pclq") then
insert(boolean_labels, "部分仿譯")
end
if val("sml") then
insert(boolean_labels, "語義借用")
end
if index > 0 then
-- per term decorations are handled at the subitem level, by full_link().
return boolean_labels
else
local quals, dash_labels
quals = val("qq")
if val("ll") then
local labels = require(labels_module).show_labels {
lang = lang,
labels = val("ll"),
nocat = true,
open = false,
close = false,
no_track_already_seen = true,
ok_to_destructively_modify = true, -- doesn't apply to `labels`
}
if labels ~= "" then
dash_labels = " — " .. labels
end
end
return boolean_labels, quals, dash_labels
end
end
local function desc_or_desc_tree(frame, desc_tree)
local params
local boolean = {type = "boolean"}
if desc_tree then
params = {
[1] = {required = true, type = "language", family = true, default = "gem-pro"},
[2] = {required = true, list = true, allow_holes = true, default = "*fuhsaz"},
notext = boolean,
noalts = boolean,
noparent = boolean,
}
else
params = {
[1] = {required = true, type = "language", family = true, default = "en"},
[2] = {list = true, allow_holes = true, template_default = "word"},
alts = boolean,
}
end
-- Add other single params.
params.sclang = boolean
params.sclb = {replaced_by = "sclang", reason = "to avoid confusion with 'labels' as in [[Template:lb]]"}
params.nolang = boolean
params.nolb = {replaced_by = "nolang", reason = "to avoid confusion with 'labels' as in [[Template:lb]]"}
local parent_args
if frame.args[1] then
parent_args = frame.args
else
parent_args = frame:getParent().args
end
-- Error to catch most uses of old-style parameters.
if ine(parent_args[4]) and not ine(parent_args[3]) and not ine(parent_args.tr2) and not ine(parent_args.ts2)
and not ine(parent_args.t2) and not ine(parent_args.gloss2) and not ine(parent_args.g2)
and not ine(parent_args.alt2) then
error("You specified a term in 4= and not one in 3=. You probably meant to use t= to specify a gloss instead. "
.. "If you intended to specify two terms, put the second term in 3=.")
end
if not ine(parent_args[3]) and not ine(parent_args.alt2) and not ine(parent_args.tr2) and not ine(parent_args.ts2)
and ine(parent_args.g2) then
error("You specified a gender in g2= but no term in 3=. You were probably trying to specify two genders for "
.. "a single term. To do that, put both genders in g=, comma-separated.")
end
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "l", "q"}},
{param = "lb", replaced_by = false, instead = "use 'l' for left labels or 'll' for right labels"},
{param = bortypes, type = "boolean", overall = true, separate_no_index = true},
{param = calque_aliases, alias_of = "clq"},
{param = partial_calque_aliases, alias_of = "pclq"},
{param = semi_learned_borrowing_aliases, alias_of = "slb"},
}
local groups, args, globalprops = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
-- Need some work to support this.
-- parse_lang_prefix = true,
track_module = "descendant",
-- Due to allowing families as langs and substituting 'und', it's easier to do this later.
-- lang = function() ... end
sc = "sc.default",
splitchar = "[,~]",
subitem_separator_map = {[","] = " / ", ["~"] = " ~ "},
pre_normalize_modifiers = function(data)
local modtext = data.modtext
modtext = modtext:match("^<(.*)>$")
if not modtext then
error(("Internal error: Passed-in modifier isn't surrounded by angle brackets: %s"):format(
data.modtext))
end
if bortype_set[modtext] or calque_alias_set[modtext] or partial_calque_alias_set[modtext] or semi_learned_borrowing_alias_set[modtext] then
modtext = modtext .. ":1"
end
return "<" .. modtext .. ">"
end,
}
local lang = args[1]
local namespace = mw.title.getCurrentTitle().nsText
if (namespace == "" or namespace == "Reconstruction") and (
lang:hasType("appendix-constructed") and not lang:hasType("regular")) then
error("Terms in appendix-only constructed languages may not be given as descendants.")
end
local fetch_alt_forms = desc_tree and not args.noalts or not desc_tree and args.alts
local m_desctree
if desc_tree or fetch_alt_forms then
m_desctree = require(descendants_tree_module)
end
if lang:getCode() ~= lang:getFullCode() then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/etymological]]
track("etymological")
track("etymological/" .. lang:getCode())
end
local is_family = lang:hasType("family")
local proxy_lang
if is_family then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/family]]
track("family")
track("family/" .. lang:getCode())
proxy_lang = require(languages_module).getByCode("und")
else
proxy_lang = lang
end
local langname
if is_family then
-- The display form for families includes the word "languages", which we probably don't want to
-- display.
langname = lang:getCanonicalName()
else
langname = lang:getDisplayForm()
end
local langtag
if args.sclang then
local sc_to_use = args.sc.default
if not sc_to_use then
local first_termobj = groups[1] and groups[1].terms[1]
if not first_termobj then
error("sclang= given but no term exists to display the script name of")
end
sc_to_use = first_termobj.sc
if not sc_to_use then
local first_term = first_termobj.term or first_termobj.alt
if not first_term then
error("sclang= given but first specified item no term or display form to display the script name of")
end
if first_termobj.lang then
sc_to_use = first_termobj.lang:findBestScript(first_term)
elseif is_family then
sc_to_use = require(scripts_module).findBestScriptWithoutLang(first_term, "none is last resort")
else
sc_to_use = lang:findBestScript(first_term)
end
end
end
langtag = sc_to_use:getDisplayForm()
else
langtag = langname
end
local terms_for_descendant_trees = {}
-- Keep track of descendants whose descendant tree we fetch. Don't fetch the same descendant tree twice (which
-- can happen especially with Arabic-script terms with the same unvocalized spelling but differing vocalization).
-- This happens e.g. with Ottoman Turkish [[پورتقال]], which has {{desctree|fa-cls|پُرْتُقَال|پُرْتِقَال|bor=1}}, with
-- two terms that have the same unvocalized spelling.
local terms_and_ids_fetched = {}
local descendant_terms_seen = {}
local parts = {}
for i, group in ipairs(groups) do
local group_parts = {}
local terms_for_alt_forms = {}
for _, item in ipairs(group.terms) do
local link = ""
item.lang = item.lang or proxy_lang
item.track_sc = true
-- Construct a link out of `item`. Also add the term to the list of descendant trees and/or alternative
-- forms to fetch, if the page+ID combination hasn't already been seen.
if item.term ~= "-" then -- including term == nil
item.show_decorations = true
link = require(links_module).full_link(item, nil, true)
if item.term and (desc_tree or fetch_alt_forms) then
local m_links = require(links_module)
-- Fetches information under entry. If term is of type A//B, it checks A.
local entry_name = m_links.get_link_page(m_links.remove_links(mw.ustring.gsub(item.term, "//.+$", "")), lang, item.sc)
-- NOTE: We use the term and ID as the key, but not the language. This is OK currently because
-- all terms have the same language; but if we ever add support for a term-specific language,
-- we need to fix this.
local term_and_id = item.id and entry_name .. "!!!" .. item.id or entry_name
if not terms_and_ids_fetched[term_and_id] then
terms_and_ids_fetched[term_and_id] = true
local term_for_fetching = {
lang = lang, entry_name = entry_name, id = item.id
}
if desc_tree then
if is_family then
error("No support currently (and probably ever) for fetching a descendant tree when a family code instead of language code is given")
end
if error_on_no_descendants then
require(table_module).insertIfNot(descendant_terms_seen,
{ term = item.term, id = item.id })
end
table.insert(terms_for_descendant_trees, term_for_fetching)
end
if fetch_alt_forms then
if is_family then
error("No support currently (and probably ever) for fetching alternative forms when a family code instead of language code is given")
end
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/alts]]
track("alts")
table.insert(terms_for_alt_forms, term_for_fetching)
end
end
end
elseif item.tr or item.ts or item.gloss or item.genders then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/no term]]
track("no term")
item.term = nil
item.show_decorations = true
link = require(links_module).full_link(item, nil, true)
link = link
:gsub("<small>%[Term%?%]</small> ", "")
:gsub("<small>%[Term%?%]</small> ", "")
:gsub("%[%[Category:[^%[%]]+ term requests%]%]", "")
else -- display no link at all
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/no term or annotations]]
track("no term or annotations")
end
if link ~= "" then
insert(group_parts, item.separator)
insert(group_parts, link)
end
end
if group_parts[1] then
for _, altterm in ipairs(terms_for_alt_forms) do
local altform = m_desctree.get_alternative_forms(altterm.lang, altterm.entry_name, altterm.id,
globalprops.use_semicolon and "; " or ", ")
if altform ~= "" then
insert(group_parts, globalprops.use_semicolon and "; " or ", ")
insert(group_parts, altform)
end
end
local group_link = concat(group_parts)
insert(parts, group.separator)
if not args.notext then
insert(parts, get_arrow(group, args, i))
end
-- no pre-qualifiers/labels and no post-qualifiers/dash-labels
local post_boolean_labels = get_post_decorations(group, args, i, proxy_lang)
if post_boolean_labels and post_boolean_labels[1] then
group_link = require(decorations_module).format_decorations {
lang = proxy_lang,
text = group_link,
ll = post_boolean_labels,
}
end
insert(parts, group_link)
end
end
local descendant_trees = {}
for _, descterm in ipairs(terms_for_descendant_trees) do
-- When I ([[User:Benwing2]]) first implemented this in Nov 2020, I had `maxmaxindex > 1` as the last argument.
-- Since then, [[User:Fytcha]] changed the last param to `true`.
local descendant_tree = m_desctree.get_descendants(descterm.lang, descterm.entry_name, descterm.id, true)
if descendant_tree and descendant_tree ~= "" then
insert(descendant_trees, descendant_tree)
end
end
if error_on_no_descendants and desc_tree and not descendant_trees[1] then
local function format_term_seen(term_seen)
if term_seen.id then
return ("[[%s]] with ID '%s'"):format(term_seen.term, term_seen.id)
else
return ("[[%s]]"):format(term_seen.term)
end
end
if #descendant_terms_seen == 0 then
error("[[Template:desctree]] invoked but no terms to retrieve descendants from")
elseif #descendant_terms_seen == 1 then
error(("No Descendants section was found in the entry %s under the header for %s"):format(
format_term_seen(descendant_terms_seen[1]), lang:getFullName()))
else
for i, term_seen in ipairs(descendant_terms_seen) do
descendant_terms_seen[i] = format_term_seen(term_seen)
end
error(("No Descendants section was found in any of the entries %s under the header for %s"):format(
concat(descendant_terms_seen, ", "), lang:getFullName()))
end
end
local descendants = concat(descendant_trees)
if args.noparent then
return descendants
end
local initial_labels, initial_quals = get_pre_decorations(nil, args, 0)
local final_boolean_labels, final_quals, final_dash_labels = get_post_decorations(nil, args, 0, proxy_lang)
local all_linktext = concat(parts)
if initial_labels and initial_labels[1] or initial_quals and initial_quals[1] or
final_boolean_labels and final_boolean_labels[1] or final_quals and final_quals[1] then
all_linktext = require(decorations_module).format_decorations {
lang = proxy_lang,
text = all_linktext,
l = initial_labels,
q = initial_quals,
ll = final_boolean_labels,
qq = final_quals,
}
end
if final_dash_labels then
all_linktext = all_linktext .. final_dash_labels
end
all_linktext = all_linktext .. descendants
if args.notext then
return all_linktext
end
local initial_arrow = get_arrow(nil, args, 0)
if args.nolang then
return initial_arrow .. all_linktext
else
return concat { initial_arrow, langtag, ":", all_linktext } -- L10N
end
end
function export.descendant(frame)
return desc_or_desc_tree(frame, false) .. require(template_styles_module)(etymology_style_css)
end
function export.descendants_tree(frame)
return desc_or_desc_tree(frame, true)
end
return export
ggh0ymp4bo2nvc101q47zbmc3xgiqiy
9909187
9909058
2026-09-29T09:47:54Z
TongcyBot
83009
移除手動 show_decorations
9909187
Scribunto
text/plain
local export = {}
local debug_track_module = "Module:debug/track"
local decorations_module = "Module:decorations"
local descendants_tree_module = "Module:descendants tree"
local etymology_style_css = "Module:etymology/style.css"
local labels_module = "Module:labels"
local languages_module = "Module:languages"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
local scripts_module = "Module:scripts"
local table_module = "Module:table"
local table_module_list_to_set = "Module:table/listToSet"
local template_styles_module = "Module:TemplateStyles"
local concat = table.concat
local insert = table.insert
local list_to_set = require(table_module_list_to_set)
local error_on_no_descendants = false
local function track(page)
return require(debug_track_module)("descendant/" .. page)
end
local function ine(arg)
if arg == "" then
return nil
else
return arg
end
end
local function add_tooltip(text, tooltip)
return '<span class="desc-arr" title="' .. tooltip .. '">' .. text .. '</span>'
end
-- Boolean params indicating whether a descendant term (or all terms) are particular sorts of borrowings.
local bortypes = {"inh", "bor", "lbor", "slb", "obor", "translit", "der", "clq", "pclq", "sml", "unc"}
local bortype_set = list_to_set(bortypes)
-- Aliases of clq=.
local calque_aliases = {"cal", "calq", "calque"}
local calque_alias_set = list_to_set(calque_aliases)
-- Aliases of pclq=.
local partial_calque_aliases = {"pcal", "pcalq", "pcalque"}
local partial_calque_alias_set = list_to_set(partial_calque_aliases)
local semi_learned_borrowing_aliases = {"slbor"}
local semi_learned_borrowing_alias_set = list_to_set(semi_learned_borrowing_aliases)
--- Return a function of one argument `field` (a param name), which fetches `args`[`field`].default if index == 0, else
--- `container`[`field`].
local function get_val(container, args, index)
return function(field)
if index == 0 then
return args[field].default
else
return container[field]
end
end
end
local function get_arrow(container, args, index)
local val = get_val(container, args, index)
local arrow
if val("bor") then
arrow = add_tooltip("→", "借詞")
elseif val("lbor") then
arrow = add_tooltip("→", "學術借詞")
elseif val("slb") then
arrow = add_tooltip("→", "半學術借詞")
elseif val("obor") then
arrow = add_tooltip("→", "形譯詞")
elseif val("translit") then
arrow = add_tooltip("→", "轉寫")
elseif val("clq") then
arrow = add_tooltip("→", "仿譯詞")
elseif val("pclq") then
arrow = add_tooltip("→", "部分仿譯")
elseif val("sml") then
arrow = add_tooltip("→", "語義借用")
elseif val("inh") or (val("unc") and not val("der")) then
arrow = add_tooltip(">", "繼承")
else
arrow = ""
end
-- allow der=1 in conjunction with bor=1 to indicate e.g. English "pars recta"
-- derived and borrowed from Latin "pars".
if val("der") then
arrow = arrow .. add_tooltip("⇒", "reshaped by analogy or addition of morphemes")
end
if val("unc") then
arrow = arrow .. add_tooltip("?", "uncertain")
end
if arrow ~= "" then
arrow = arrow .. " "
end
return arrow
end
-- Return the pre-decoration text for the `index`th term, or the overall pre-decoration text if index == 0.
local function get_pre_decorations(container, args, index)
if index > 0 then
-- per term decorations are handled at the subitem level, by full_link().
return nil, nil
end
local val = get_val(container, args, index)
return val("l"), val("q")
end
-- Return the post-decoration text for the `index`th term, or the overall post-decoration text if index == 0.
local function get_post_decorations(container, args, index, lang)
local val = get_val(container, args, index)
local boolean_labels = {}
if val("inh") then
insert(boolean_labels, "繼承")
end
if val("lbor") then
insert(boolean_labels, "學術")
end
if val("slb") then
insert(boolean_labels, "半學術")
end
if val("translit") then
insert(boolean_labels, "轉寫")
end
if val("clq") then
insert(boolean_labels, "仿譯")
end
if val("pclq") then
insert(boolean_labels, "部分仿譯")
end
if val("sml") then
insert(boolean_labels, "語義借用")
end
if index > 0 then
-- per term decorations are handled at the subitem level, by full_link().
return boolean_labels
else
local quals, dash_labels
quals = val("qq")
if val("ll") then
local labels = require(labels_module).show_labels {
lang = lang,
labels = val("ll"),
nocat = true,
open = false,
close = false,
no_track_already_seen = true,
ok_to_destructively_modify = true, -- doesn't apply to `labels`
}
if labels ~= "" then
dash_labels = " — " .. labels
end
end
return boolean_labels, quals, dash_labels
end
end
local function desc_or_desc_tree(frame, desc_tree)
local params
local boolean = {type = "boolean"}
if desc_tree then
params = {
[1] = {required = true, type = "language", family = true, default = "gem-pro"},
[2] = {required = true, list = true, allow_holes = true, default = "*fuhsaz"},
notext = boolean,
noalts = boolean,
noparent = boolean,
}
else
params = {
[1] = {required = true, type = "language", family = true, default = "en"},
[2] = {list = true, allow_holes = true, template_default = "word"},
alts = boolean,
}
end
-- Add other single params.
params.sclang = boolean
params.sclb = {replaced_by = "sclang", reason = "to avoid confusion with 'labels' as in [[Template:lb]]"}
params.nolang = boolean
params.nolb = {replaced_by = "nolang", reason = "to avoid confusion with 'labels' as in [[Template:lb]]"}
local parent_args
if frame.args[1] then
parent_args = frame.args
else
parent_args = frame:getParent().args
end
-- Error to catch most uses of old-style parameters.
if ine(parent_args[4]) and not ine(parent_args[3]) and not ine(parent_args.tr2) and not ine(parent_args.ts2)
and not ine(parent_args.t2) and not ine(parent_args.gloss2) and not ine(parent_args.g2)
and not ine(parent_args.alt2) then
error("You specified a term in 4= and not one in 3=. You probably meant to use t= to specify a gloss instead. "
.. "If you intended to specify two terms, put the second term in 3=.")
end
if not ine(parent_args[3]) and not ine(parent_args.alt2) and not ine(parent_args.tr2) and not ine(parent_args.ts2)
and ine(parent_args.g2) then
error("You specified a gender in g2= but no term in 3=. You were probably trying to specify two genders for "
.. "a single term. To do that, put both genders in g=, comma-separated.")
end
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "l", "q"}},
{param = "lb", replaced_by = false, instead = "use 'l' for left labels or 'll' for right labels"},
{param = bortypes, type = "boolean", overall = true, separate_no_index = true},
{param = calque_aliases, alias_of = "clq"},
{param = partial_calque_aliases, alias_of = "pclq"},
{param = semi_learned_borrowing_aliases, alias_of = "slb"},
}
local groups, args, globalprops = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
-- Need some work to support this.
-- parse_lang_prefix = true,
track_module = "descendant",
-- Due to allowing families as langs and substituting 'und', it's easier to do this later.
-- lang = function() ... end
sc = "sc.default",
splitchar = "[,~]",
subitem_separator_map = {[","] = " / ", ["~"] = " ~ "},
pre_normalize_modifiers = function(data)
local modtext = data.modtext
modtext = modtext:match("^<(.*)>$")
if not modtext then
error(("Internal error: Passed-in modifier isn't surrounded by angle brackets: %s"):format(
data.modtext))
end
if bortype_set[modtext] or calque_alias_set[modtext] or partial_calque_alias_set[modtext] or semi_learned_borrowing_alias_set[modtext] then
modtext = modtext .. ":1"
end
return "<" .. modtext .. ">"
end,
}
local lang = args[1]
local namespace = mw.title.getCurrentTitle().nsText
if (namespace == "" or namespace == "Reconstruction") and (
lang:hasType("appendix-constructed") and not lang:hasType("regular")) then
error("Terms in appendix-only constructed languages may not be given as descendants.")
end
local fetch_alt_forms = desc_tree and not args.noalts or not desc_tree and args.alts
local m_desctree
if desc_tree or fetch_alt_forms then
m_desctree = require(descendants_tree_module)
end
if lang:getCode() ~= lang:getFullCode() then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/etymological]]
track("etymological")
track("etymological/" .. lang:getCode())
end
local is_family = lang:hasType("family")
local proxy_lang
if is_family then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/family]]
track("family")
track("family/" .. lang:getCode())
proxy_lang = require(languages_module).getByCode("und")
else
proxy_lang = lang
end
local langname
if is_family then
-- The display form for families includes the word "languages", which we probably don't want to
-- display.
langname = lang:getCanonicalName()
else
langname = lang:getDisplayForm()
end
local langtag
if args.sclang then
local sc_to_use = args.sc.default
if not sc_to_use then
local first_termobj = groups[1] and groups[1].terms[1]
if not first_termobj then
error("sclang= given but no term exists to display the script name of")
end
sc_to_use = first_termobj.sc
if not sc_to_use then
local first_term = first_termobj.term or first_termobj.alt
if not first_term then
error("sclang= given but first specified item no term or display form to display the script name of")
end
if first_termobj.lang then
sc_to_use = first_termobj.lang:findBestScript(first_term)
elseif is_family then
sc_to_use = require(scripts_module).findBestScriptWithoutLang(first_term, "none is last resort")
else
sc_to_use = lang:findBestScript(first_term)
end
end
end
langtag = sc_to_use:getDisplayForm()
else
langtag = langname
end
local terms_for_descendant_trees = {}
-- Keep track of descendants whose descendant tree we fetch. Don't fetch the same descendant tree twice (which
-- can happen especially with Arabic-script terms with the same unvocalized spelling but differing vocalization).
-- This happens e.g. with Ottoman Turkish [[پورتقال]], which has {{desctree|fa-cls|پُرْتُقَال|پُرْتِقَال|bor=1}}, with
-- two terms that have the same unvocalized spelling.
local terms_and_ids_fetched = {}
local descendant_terms_seen = {}
local parts = {}
for i, group in ipairs(groups) do
local group_parts = {}
local terms_for_alt_forms = {}
for _, item in ipairs(group.terms) do
local link = ""
item.lang = item.lang or proxy_lang
item.track_sc = true
-- Construct a link out of `item`. Also add the term to the list of descendant trees and/or alternative
-- forms to fetch, if the page+ID combination hasn't already been seen.
if item.term ~= "-" then -- including term == nil
link = require(links_module).full_link(item, nil, true)
if item.term and (desc_tree or fetch_alt_forms) then
local m_links = require(links_module)
-- Fetches information under entry. If term is of type A//B, it checks A.
local entry_name = m_links.get_link_page(m_links.remove_links(mw.ustring.gsub(item.term, "//.+$", "")), lang, item.sc)
-- NOTE: We use the term and ID as the key, but not the language. This is OK currently because
-- all terms have the same language; but if we ever add support for a term-specific language,
-- we need to fix this.
local term_and_id = item.id and entry_name .. "!!!" .. item.id or entry_name
if not terms_and_ids_fetched[term_and_id] then
terms_and_ids_fetched[term_and_id] = true
local term_for_fetching = {
lang = lang, entry_name = entry_name, id = item.id
}
if desc_tree then
if is_family then
error("No support currently (and probably ever) for fetching a descendant tree when a family code instead of language code is given")
end
if error_on_no_descendants then
require(table_module).insertIfNot(descendant_terms_seen,
{ term = item.term, id = item.id })
end
table.insert(terms_for_descendant_trees, term_for_fetching)
end
if fetch_alt_forms then
if is_family then
error("No support currently (and probably ever) for fetching alternative forms when a family code instead of language code is given")
end
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/alts]]
track("alts")
table.insert(terms_for_alt_forms, term_for_fetching)
end
end
end
elseif item.tr or item.ts or item.gloss or item.genders then
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/no term]]
track("no term")
item.term = nil
item.show_decorations = true
link = require(links_module).full_link(item, nil, true)
link = link
:gsub("<small>%[Term%?%]</small> ", "")
:gsub("<small>%[Term%?%]</small> ", "")
:gsub("%[%[Category:[^%[%]]+ term requests%]%]", "")
else -- display no link at all
-- [[Special:WhatLinksHere/Wiktionary:Tracking/descendant/no term or annotations]]
track("no term or annotations")
end
if link ~= "" then
insert(group_parts, item.separator)
insert(group_parts, link)
end
end
if group_parts[1] then
for _, altterm in ipairs(terms_for_alt_forms) do
local altform = m_desctree.get_alternative_forms(altterm.lang, altterm.entry_name, altterm.id,
globalprops.use_semicolon and "; " or ", ")
if altform ~= "" then
insert(group_parts, globalprops.use_semicolon and "; " or ", ")
insert(group_parts, altform)
end
end
local group_link = concat(group_parts)
insert(parts, group.separator)
if not args.notext then
insert(parts, get_arrow(group, args, i))
end
-- no pre-qualifiers/labels and no post-qualifiers/dash-labels
local post_boolean_labels = get_post_decorations(group, args, i, proxy_lang)
if post_boolean_labels and post_boolean_labels[1] then
group_link = require(decorations_module).format_decorations {
lang = proxy_lang,
text = group_link,
ll = post_boolean_labels,
}
end
insert(parts, group_link)
end
end
local descendant_trees = {}
for _, descterm in ipairs(terms_for_descendant_trees) do
-- When I ([[User:Benwing2]]) first implemented this in Nov 2020, I had `maxmaxindex > 1` as the last argument.
-- Since then, [[User:Fytcha]] changed the last param to `true`.
local descendant_tree = m_desctree.get_descendants(descterm.lang, descterm.entry_name, descterm.id, true)
if descendant_tree and descendant_tree ~= "" then
insert(descendant_trees, descendant_tree)
end
end
if error_on_no_descendants and desc_tree and not descendant_trees[1] then
local function format_term_seen(term_seen)
if term_seen.id then
return ("[[%s]] with ID '%s'"):format(term_seen.term, term_seen.id)
else
return ("[[%s]]"):format(term_seen.term)
end
end
if #descendant_terms_seen == 0 then
error("[[Template:desctree]] invoked but no terms to retrieve descendants from")
elseif #descendant_terms_seen == 1 then
error(("No Descendants section was found in the entry %s under the header for %s"):format(
format_term_seen(descendant_terms_seen[1]), lang:getFullName()))
else
for i, term_seen in ipairs(descendant_terms_seen) do
descendant_terms_seen[i] = format_term_seen(term_seen)
end
error(("No Descendants section was found in any of the entries %s under the header for %s"):format(
concat(descendant_terms_seen, ", "), lang:getFullName()))
end
end
local descendants = concat(descendant_trees)
if args.noparent then
return descendants
end
local initial_labels, initial_quals = get_pre_decorations(nil, args, 0)
local final_boolean_labels, final_quals, final_dash_labels = get_post_decorations(nil, args, 0, proxy_lang)
local all_linktext = concat(parts)
if initial_labels and initial_labels[1] or initial_quals and initial_quals[1] or
final_boolean_labels and final_boolean_labels[1] or final_quals and final_quals[1] then
all_linktext = require(decorations_module).format_decorations {
lang = proxy_lang,
text = all_linktext,
l = initial_labels,
q = initial_quals,
ll = final_boolean_labels,
qq = final_quals,
}
end
if final_dash_labels then
all_linktext = all_linktext .. final_dash_labels
end
all_linktext = all_linktext .. descendants
if args.notext then
return all_linktext
end
local initial_arrow = get_arrow(nil, args, 0)
if args.nolang then
return initial_arrow .. all_linktext
else
return concat { initial_arrow, langtag, ":", all_linktext } -- L10N
end
end
function export.descendant(frame)
return desc_or_desc_tree(frame, false) .. require(template_styles_module)(etymology_style_css)
end
function export.descendants_tree(frame)
return desc_or_desc_tree(frame, true)
end
return export
0glol0y1o91uryc5a0szspdvrrqu191
psi
0
1864544
9909143
9717643
2026-09-29T08:44:09Z
TongcyDai
53191
9909143
wikitext
text/x-wiki
{{also|Psi|PSI|psí|pśi}}
==跨語言==
===符號===
{{mul-symbol}}
# {{ISO 639|3}}
===參見===
* {{langcat}}
==英語==
{{letter disp2|chi|[[image:Greek uc psi.svg|36px]] [[image:Greek lc psi.svg|27px]]|omega|ψεῖ|Ψ|ψ}}
===詞源1===
源自{{etyl|grc|en}} {{m|grc|ψῖ}},第二十三個古希臘字母(Ψ/ψ)的名稱。
====發音====
'''希臘字母'''
* {{enPR|psī|sī}}, {{IPA|en|/psaɪ/|/saɪ/}}
* {{audio|en|en-us-psi.ogg|音頻 (美國)}}
* {{homophones|en|xi|sigh|scye|sai|Si}}
* {{rhymes|en|aɪ|s=1}}
'''心理能'''
* {{enPR|sī}}, {{IPA|en|/saɪ/}}
* {{audio|en|en-us-psi-mental.ogg|音頻 (美國)}}
* {{homophones|en|xi|sigh|scye|sai|Si}}
* {{rhymes|en|aɪ|s=1}}
====名詞====
{{en-noun|~}}
# {{lb|en|countable}} [[普西]],希臘字母[[Ψ]]/[[ψ]]的名稱
# {{lb|en|uncountable|超心理學}} [[心理能]]的一種形式
#* '''1993''', Will Self, ''My Idea of Fun'':
#*: ‘Come, lad,’ he said. ‘We will take tea together and speak of the noumenon, the '''psi''' and other more heterogenous phenomena.’
===詞源2===
首字母縮略詞。
====其他寫法====
* {{l|en|P.S.I.}}
* {{l|en|PSI}}
====符號====
{{head|en|符號}}
# {{initialism of|en|[[pound|Pounds]] per [[square inch]]|t=磅每平方英寸}} {{qualifier|英制壓強單位}}
===異序詞===
* {{anagrams|en|a=ips|IPS|IPs|ISP|Isp|PIs|SIP|SPI|iPS|isp|pis|sip}}
{{c|en|希臘字母名稱}}
==加泰羅尼亞語==
===發音===
* {{ca-IPA}}
===名詞===
{{ca-noun|f}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
{{c|ca|希臘字母名稱}}
==捷克語==
===發音===
* {{cs-IPA}}
===名詞===
{{head|cs|名詞變格形}}
# {{inflection of|cs|pes||nom//voc|p}}
==法語==
===發音===
* {{fr-IPA}}
* {{audio|fr|LL-Q150 (fra)-WikiLucas00-psi.wav|Audio}}
* {{homophones|fr|psy|psys}}
===名詞===
{{fr-noun|m|psi}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===拓展閱讀===
* {{R:TLFi}}
===異序詞===
* {{l|fr|pis}}, {{l|fr|spi}}
{{c|fr|希臘字母名稱}}
==意大利語==
{{wikipedia|lang=it}}
===發音===
{{it-pr|psì*}}
===名詞===
{{it-noun|m|g2=f|#}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===異序詞===
* {{anagrams|it|a=ips|spi}}
{{C|it|希臘字母名稱}}
==波蘭語==
{{wiki|lang=pl}}
===詞源1===
{{inh+|pl|sla-pro|*pьsьjь}},等同於{{af|pl|pies|-i}}。
====發音====
{{pl-pr|a=LL-Q809 (pol)-Poemat-psi.wav}}
====形容詞====
{{pl-adj|-|adv=-}}
# {{lb|pl|relational}} [[狗]]的
#: {{syn|pl|pieski|sobaczy|q2=古舊}}
=====變格=====
{{pl-adecl}}
====相關詞彙====
{{col3|pl|title=動詞|
[[psocić]] {{g|impf}}
}}
{{col3|pl|title=名詞|psota|psotnica|psotnik}}
===詞源2===
{{bor+|pl|grc|ψῖ}}。
====發音====
{{pl-p|ps-i|h=psi}}
====名詞====
{{pl-noun|n|indecl=yes}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===拓展閱讀===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
{{C|pl|希臘字母名稱}}
==葡萄牙語==
===發音===
* {{rhymes|pt|i|s=1}}
===名詞===
{{pt-noun|m}}
# {{lb|pt|countable}} 希臘字母[[Ψ]]/[[ψ]]的名稱
{{c|pt|希臘字母名稱}}
==羅馬尼亞語==
===詞源===
源自{{bor|ro|el|ψι}}。
===名詞===
{{ro-noun|m|psi}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
====變格====
{{ro-noun-m|pl=psi}}
{{c|ro|希臘字母名稱}}
==斯洛伐克語==
===發音===
* {{sk-IPA}}
===名詞===
{{head|sk|名詞變格形|g=m-an}}
# {{inflection of|sk|pes||nom|p}}
==西班牙語==
===發音===
{{es-pr}}
* {{rhymes|es|i|s=1}}
===名詞===
{{es-noun|f|psíes}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===拓展閱讀===
* {{R:DRAE}}
{{C|es|希臘字母名稱}}
fjauflv8b88en7ytk4z1u5ocmz8yo5o
9909148
9909143
2026-09-29T08:47:32Z
TongcyDai
53191
9909148
wikitext
text/x-wiki
{{also|Psi|PSI|psí|pśi}}
==跨語言==
===符號===
{{mul-symbol}}
# {{ISO 639|3}}
===參見===
* {{langcat}}
==英語==
{{letter disp2|chi|[[image:Greek uc psi.svg|36px]] [[image:Greek lc psi.svg|27px]]|omega|ψεῖ|Ψ|ψ}}
===詞源1===
源自{{etyl|grc|en}} {{m|grc|ψῖ}},第二十三個古希臘字母(Ψ/ψ)的名稱。
====發音====
'''希臘字母'''
* {{enPR|psī|sī}}, {{IPA|en|/psaɪ/|/saɪ/}}
* {{audio|en|en-us-psi.ogg|音頻 (美國)}}
* {{homophones|en|xi|sigh|scye|sai|Si}}
* {{rhymes|en|aɪ|s=1}}
'''心理能'''
* {{enPR|sī}}, {{IPA|en|/saɪ/}}
* {{audio|en|en-us-psi-mental.ogg|音頻 (美國)}}
* {{homophones|en|xi|sigh|scye|sai|Si}}
* {{rhymes|en|aɪ|s=1}}
====名詞====
{{en-noun|~}}
# {{lb|en|countable}} [[普西]],希臘字母[[Ψ]]/[[ψ]]的名稱
# {{lb|en|uncountable|超心理學}} [[心理能]]的一種形式
#* '''1993''', Will Self, ''My Idea of Fun'':
#*: ‘Come, lad,’ he said. ‘We will take tea together and speak of the noumenon, the '''psi''' and other more heterogenous phenomena.’
===詞源2===
首字母縮略詞。
====其他寫法====
* {{l|en|P.S.I.}}
* {{l|en|PSI}}
====符號====
{{head|en|符號}}
# {{initialism of|en|[[pound|Pounds]] per [[square inch]]|t=磅每平方英寸}} {{qualifier|英制壓強單位}}
===異序詞===
* {{anagrams|en|a=ips|IPS|IPs|ISP|Isp|PIs|SIP|SPI|iPS|isp|pis|sip}}
{{c|en|希臘字母名稱}}
==加泰羅尼亞語==
===發音===
* {{ca-IPA}}
===名詞===
{{ca-noun|f}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
{{c|ca|希臘字母名稱}}
==捷克語==
===發音===
* {{cs-IPA}}
===名詞===
{{head|cs|名詞變格形}}
# {{inflection of|cs|pes||nom//voc|p}}
==法語==
===發音===
* {{fr-IPA}}
* {{audio|fr|LL-Q150 (fra)-WikiLucas00-psi.wav|Audio}}
* {{homophones|fr|psy|psys}}
===名詞===
{{fr-noun|m|psi}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===拓展閱讀===
* {{R:TLFi}}
===異序詞===
* {{l|fr|pis}}, {{l|fr|spi}}
{{c|fr|希臘字母名稱}}
==意大利語==
{{wikipedia|lang=it}}
===發音===
{{it-pr|psì*}}
===名詞===
{{it-noun|m|g2=f|#}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===異序詞===
* {{anagrams|it|a=ips|spi}}
{{C|it|希臘字母名稱}}
==波蘭語==
{{wiki|lang=pl}}
===詞源1===
{{inh+|pl|sla-pro|*pьsьjь}},等同於{{af|pl|pies|-i}}。
====發音====
{{pl-pr|a=LL-Q809 (pol)-Poemat-psi.wav}}
====形容詞====
{{pl-adj|-|adv=-}}
# {{lb|pl|relational}} [[狗]]的
#: {{syn|pl|pieski|sobaczy|q2=古舊}}
=====變格=====
{{pl-adecl}}
====相關詞彙====
{{col3|pl|title=動詞|
[[psocić]] {{g|impf}}
}}
{{col3|pl|title=名詞|psota|psotnica|psotnik}}
===詞源2===
{{bor+|pl|grc|ψῖ}}。
====發音====
{{pl-pr|ps-i|h=psi}}
====名詞====
{{pl-noun|n|indecl=yes}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===拓展閱讀===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
{{C|pl|希臘字母名稱}}
==葡萄牙語==
===發音===
* {{rhymes|pt|i|s=1}}
===名詞===
{{pt-noun|m}}
# {{lb|pt|countable}} 希臘字母[[Ψ]]/[[ψ]]的名稱
{{c|pt|希臘字母名稱}}
==羅馬尼亞語==
===詞源===
源自{{bor|ro|el|ψι}}。
===名詞===
{{ro-noun|m|psi}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
====變格====
{{ro-noun-m|pl=psi}}
{{c|ro|希臘字母名稱}}
==斯洛伐克語==
===發音===
* {{sk-IPA}}
===名詞===
{{head|sk|名詞變格形|g=m-an}}
# {{inflection of|sk|pes||nom|p}}
==西班牙語==
===發音===
{{es-pr}}
* {{rhymes|es|i|s=1}}
===名詞===
{{es-noun|f|psíes}}
# 希臘字母[[Ψ]]/[[ψ]]的名稱
===拓展閱讀===
* {{R:DRAE}}
{{C|es|希臘字母名稱}}
l801kwenrd114xry0d19ik8i8gb6qu7
jedno-
0
1871121
9909155
8804170
2026-09-29T08:50:32Z
TongcyDai
53191
9909155
wikitext
text/x-wiki
{{also|jedno}}
==波蘭語==
===發音===
{{pl-pr}}
===前綴===
{{head|pl|前綴}}
# [[單個]],[[單獨]]
#: {{prefixusex|pl|dniowy|jednodniowy}}
====派生詞====
{{prefixsee|pl|jedno-}}
===拓展閱讀===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
gbyvl5iacjwunbid5hxaub1xn75emd2
anarchoindywidualistyczny
0
1878062
9909142
7212265
2026-09-29T08:43:52Z
TongcyDai
53191
9909142
wikitext
text/x-wiki
==波蘭語==
===詞源===
源自 {{af|pl|anarchoindywidualizm|-yczny}}。
===發音===
{{pl-pr}}
===形容詞===
{{pl-adj|-|adv=-}}
# {{lb|pl|relational}} [[個人無政府主義]]的
====變格====
{{pl-decl-adj-auto}}
====相關詞彙====
{{col3|pl|title=名詞|anarch|anarchia|anarchista|anarchistka|anarchizacja|anarchizm|anarchokolektywizm|anarchokomunizm|anarchosyndykalista|anarchosyndykalizm}}
{{col3|pl|title=形容詞|anarchiczny|anarchistowski|anarchistyczny}}
{{col3|pl|title=副詞|anarchicznie|anarchistycznie}}
{{col3|pl|title=動詞|anarchizować|zanarchizować}}
===延伸閱讀===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
{{cln|pl|含有前綴anarcho-的詞}}
{{C|pl|無政府主義|政治}}
m9ouxw8l3urm862rqln0nosue4iuy8w
Module:Bg-verb
828
1879706
9909048
9509427
2026-09-29T07:24:43Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909048
Scribunto
text/plain
local export = {}
--[=[
Authorship: Ben Wing <benwing2>
]=]
--[=[
TERMINOLOGY:
-- "slot" = A particular combination of tense/aspect/person/number, or for participles
a particular combination of tense/aspect/voice/case/gender/number/definiteness.
Example slot names for nouns are "pres_1sg" (present first singular) and
"paap_def_obj_m_sg" (definite objective masculine singular past active aorist participle).
Each slot is filled with zero or more forms.
-- "form" = The conjugated Bulgarian form representing the value of a given slot.
For example, реши́лия is a form, representing the value of the paap_def_obj_m_sg
slot of the lemma реша́.
-- "lemma" = The dictionary form of a given Bulgarian term. Generally the first singular
present, but may occasionally be another form if the first singular present is missing.
]=]
local lang = require("Module:languages").getByCode("bg")
local m_links = require("Module:links")
local m_table = require("Module:table")
local m_string_utilities = require("Module:string utilities")
local iut = require("Module:inflection utilities")
local put = require("Module:parse utilities")
local m_para = require("Module:parameters")
local m_bg_translit = require("Module:bg-translit")
local com = require("Module:bg-common")
local current_title = mw.title.getCurrentTitle()
local NAMESPACE = current_title.nsText
local PAGENAME = current_title.text
local u = require("Module:string/char")
local rsplit = mw.text.split
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local rgmatch = mw.ustring.gmatch
local rsubn = mw.ustring.gsub
local ulen = mw.ustring.len
local uupper = mw.ustring.upper
local AC = u(0x0301) -- acute = ́
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
local function link(term)
return m_links.full_link({lang = lang, term = term, tr = "-"})
end
local verb_slots = {
-- present tense
"pres_1sg", "pres_2sg", "pres_3sg", "pres_1pl", "pres_2pl", "pres_3pl",
-- imperfect
"impf_1sg", "impf_2sg", "impf_3sg", "impf_1pl", "impf_2pl", "impf_3pl",
-- aorist
"aor_1sg", "aor_2sg", "aor_3sg", "aor_1pl", "aor_2pl", "aor_3pl",
-- imperative
"impv_sg", "impv_pl",
-- present active participle
"prap_ind_m_sg", "prap_def_sub_m_sg", "prap_def_obj_m_sg",
"prap_ind_f_sg", "prap_def_f_sg", "prap_ind_n_sg", "prap_def_n_sg",
"prap_ind_pl", "prap_def_pl",
-- past active aorist participle
"paap_ind_m_sg", "paap_def_sub_m_sg", "paap_def_obj_m_sg",
"paap_ind_f_sg", "paap_def_f_sg", "paap_ind_n_sg", "paap_def_n_sg",
"paap_ind_pl", "paap_def_pl",
-- past active imperfect participle
"paip_m_sg", "paip_f_sg", "paip_n_sg", "paip_pl",
-- past passive participle
"ppp_ind_m_sg", "ppp_def_sub_m_sg", "ppp_def_obj_m_sg",
"ppp_ind_f_sg", "ppp_def_f_sg", "ppp_ind_n_sg", "ppp_def_n_sg",
"ppp_ind_pl", "ppp_def_pl",
-- verbal noun
"vn_ind_sg", "vn_def_sg", "vn_ind_pl", "vn_def_pl",
-- adverbial participle
"advp",
}
local impers_verb_slots = m_table.listToSet({
"pres_3sg",
"impf_3sg",
"aor_3sg",
"prap_ind_n_sg", "prap_def_n_sg",
"paap_ind_n_sg", "paap_def_n_sg",
"paip_n_sg",
"ppp_ind_n_sg", "ppp_def_n_sg",
"vn_ind_sg", "vn_def_sg", "vn_ind_pl", "vn_def_pl",
"advp",
})
local suppressed_acceleration_slots = m_table.listToSet({
"vn_def_sg", "vn_ind_pl", "vn_def_pl",
})
local prefix_to_accel_form = {
["pres"] = "pres|ind",
["impf"] = "impf|ind",
["aor"] = "aor|ind",
["impv"] = "imp",
["prap"] = "pres|act|part",
["paap"] = "past|act|aor|part",
["paip"] = "past|act|impf|part",
["ppp"] = "past|pass|part",
["vn"] = "vnoun",
["advp"] = "adv|part",
}
local suffix_to_accel_form = {
-- suffixes for finite forms
["1sg"] = "1|s",
["2sg"] = "2|s",
["3sg"] = "3|s",
["1pl"] = "1|p",
["2pl"] = "2|p",
["3pl"] = "3|p",
-- additional suffixes for imperatives
["sg"] = "s",
["pl"] = "p",
-- suffixes for participles that can have definite forms
["ind_m_sg"] = "indef|m|s",
["def_sub_m_sg"] = "def|sbj|m|s",
["def_obj_m_sg"] = "def|obj|m|s",
["ind_f_sg"] = "indef|f|s",
["def_f_sg"] = "def|f|s",
["ind_n_sg"] = "indef|n|s",
["def_n_sg"] = "def|n|s",
["ind_pl"] = "indef|p",
["def_pl"] = "def|p",
-- suffixes for participles that only have indefinite forms (i.e. imperfect participle)
-- ("pl" already handled above)
["m_sg"] = "m|s",
["f_sg"] = "f|s",
["n_sg"] = "n|s",
-- additional verbal noun suffixes
["ind_sg"] = "indef|s",
["def_sg"] = "def|s",
}
local function slot_to_accel_form(slot)
if suppressed_acceleration_slots[slot] then return nil end
local prefix, suffix = rmatch(slot, "^([a-z]+)_(.*)$")
if not prefix then
return prefix_to_accel_form[slot]
end
return suffix_to_accel_form[suffix] .. "|" .. prefix_to_accel_form[prefix]
end
local verb_notr_slots = {
"paap_ind_m_sg", "paap_ind_f_sg", "paap_ind_n_sg", "paap_ind_pl",
"paip_m_sg", "paip_f_sg", "paip_n_sg", "paip_pl",
}
local base_slots = {
"prap", -- masculine indefinite singular present active participle
"paap", -- masculine indefinite singular past active aorist participle
"paap_pl", -- masculine indefinite plural past active aorist participle
"paip", -- masculine indefinite singular past active imperfect participle
"advp", -- adverbial participle
"pres", -- present 1sg
"pres2", -- present 2sg
"impf", -- imperfect 1sg
"impf2", -- imperfect 2sg
"aor", -- aorist 1sg
"aor2", -- aorist 2sg
"impv", -- imperative singular
"vn", -- verbal noun
}
-- Used to determine if a perfective verb is prefixed; such verbs can't have
-- a stress-shifted aorist. This will have false positives; such verbs should
-- be indicated using (-pref).
local prefixes = {
"^[дп]о", -- also catches под-
"^[зн]а", -- also catches над-
"^из",
"^о" .. com.non_vowel_c, -- also catches об-, от-
"^пр[еио]", -- also catches пред-
"^раз",
"^[вс][^аеиоуяю]", -- also catches въ-, съ–
"^у",
}
local function verb_may_be_prefixed(lemma)
for _, prefix in ipairs(prefixes) do
if rfind(lemma, prefix) then
return true
end
end
return false
end
local function map_append(forms, suffix)
return iut.map_form_or_forms(forms, function(form) return form .. suffix end)
end
local function map_rsub(forms, from, to)
return iut.map_form_or_forms(forms, function(form) return rsub(form, from, to) end)
end
local function conjugate_all(base)
local function combine_stem_ending(stem, ending)
if stem == "?" then
return "?"
else
return stem .. ending
end
end
local function add(slot, stems, ending)
if stems == nil then
return
end
if type(stems) == "string" then
iut.insert_form(base.forms, slot, {form = combine_stem_ending(stems, ending)})
else
if stems.form then
stems = {stems}
end
for _, stem in ipairs(stems) do
if type(stem) == "string" then
stem = {form = stem}
end
iut.insert_form(base.forms, slot, {form = combine_stem_ending(stem.form, ending), footnotes = stem.footnotes})
end
end
end
local function conjugate_participle(baseslot, msg, fsg, nsg, pl)
add(baseslot .. "_ind_m_sg", msg, "")
add(baseslot .. "_def_sub_m_sg", pl, "ят")
add(baseslot .. "_def_obj_m_sg", pl, "я")
add(baseslot .. "_ind_f_sg", fsg, "")
add(baseslot .. "_def_f_sg", fsg, "та")
add(baseslot .. "_ind_n_sg", nsg, "")
add(baseslot .. "_def_n_sg", nsg, "то")
add(baseslot .. "_ind_pl", pl, "")
add(baseslot .. "_def_pl", pl, "те")
end
local function conjugate_aor_impf(baseslot, stem, stem23)
add(baseslot .. "_1sg", stem, "")
add(baseslot .. "_2sg", stem23, "")
add(baseslot .. "_3sg", stem23, "")
add(baseslot .. "_1pl", stem, "ме")
add(baseslot .. "_2pl", stem, "те")
add(baseslot .. "_3pl", stem, "а")
end
if base.aspect ~= "pf" then
conjugate_participle("prap", base.prap, map_append(base.prap, "а"),
map_append(base.prap, "о"), map_append(base.prap, "и"))
end
conjugate_participle("paap", base.paap, base.paapf, base.paapn, base.paappl)
if base.trans == "tr" then
conjugate_participle("ppp", base.ppp, map_append(base.ppp, "а"), map_append(base.ppp, "о"),
base.ppppl)
end
add("paip_m_sg", base.paip, "")
add("paip_f_sg", base.paipf, "")
add("paip_n_sg", base.paipn, "")
add("paip_pl", base.paippl, "")
if base.aspect ~= "pf" then
add("vn_ind_sg", base.vn, "")
add("vn_def_sg", base.vn, "то")
-- Some verbal nouns are ending-stressed, but the plural in -ия pushes
-- the stress onto the stem.
add("vn_ind_pl", iut.map_form_or_forms(base.vn, function(form)
return com.maybe_stress_final_syllable(rsub(form, "е́?$", ""))
end), "ия")
add("vn_def_pl", iut.map_form_or_forms(base.vn, function(form)
return com.maybe_stress_final_syllable(rsub(form, "е́?$", ""))
end), "ията")
add("vn_ind_pl", base.vn, "та")
add("vn_def_pl", base.vn, "тата")
end
add("pres_1sg", base.pres1sg, "")
add("pres_2sg", base.pres2sg, "")
add("pres_3sg", base.pres3sg, "")
add("pres_1pl", base.pres1pl, "")
add("pres_2pl", base.pres2pl, "")
add("pres_3pl", base.pres3pl, "")
conjugate_aor_impf("aor", base.aor, base.aor23)
conjugate_aor_impf("impf", base.impf, base.impf23)
add("impv_sg", base.impv, "")
add("impv_pl", base.impvpl, "")
if base.aspect ~= "pf" then
add("advp", base.advp, "")
end
end
local function add_reflexive_suffix(base)
for _, slot in ipairs(verb_slots) do
if not rfind(slot, "^vn_") then
base.forms[slot] = iut.map_forms(base.forms[slot], function(form)
return form .. " " .. base.refl
end)
end
end
end
local function remove_non_impersonal_forms(base)
for _, slot in ipairs(verb_slots) do
if not impers_verb_slots[slot] then
base.forms[slot] = nil
end
end
end
local function add_categories(base)
base.categories = {}
if base.aspect == "impf" then
table.insert(base.categories, "保加利亞語非完整體動詞")
elseif base.aspect == "pf" then
table.insert(base.categories, "保加利亞語完整體動詞")
else
assert(base.aspect == "both")
table.insert(base.categories, "保加利亞語非完整體動詞")
table.insert(base.categories, "保加利亞語完整體動詞")
table.insert(base.categories, "保加利亞語雙體動詞")
end
if base.trans == "tr" then
table.insert(base.categories, "保加利亞語及物動詞")
elseif base.trans == "intr" then
table.insert(base.categories, "保加利亞語不及物動詞")
end
if base.refl then
table.insert(base.categories, "保加利亞語反身動詞")
end
if base.irreg or base.conj == "irreg" then
table.insert(base.categories, "保加利亞語不規則動詞")
end
if base.conj ~= "irreg" then
table.insert(base.categories, "保加利亞語第" .. base.conj .. "類變位動詞")
end
end
local function pres_advp_1conj(base, lemma)
local stem, last_letter, accent = rmatch(lemma, "^(.*)(.)[ая](́?)$")
if not stem then
error("Unrecognized lemma for conjugation 1: '" .. lemma .. "'")
end
local last_letter_pal = com.first_palatalization[last_letter] or last_letter
base.pres3sg = stem .. last_letter_pal .. "е" .. accent
-- defaults to 1sg + т, but set it explicitly in case we're called
-- from ям, дам, знам, with irregular 1sg
base.pres3pl = lemma .. "т"
base.advp = base.pres3sg .. "йки"
end
local function pres_advp_2conj(base, lemma)
local stem, accent = rmatch(lemma, "^(.*)[ая](́?)$")
if not stem then
error("Unrecognized lemma for conjugation 2: '" .. lemma .. "'")
end
base.pres3sg = stem .. "и" .. accent
base.advp = stem .. "е" .. accent .. "йки"
end
local function impf_12conj(base, lemma)
local stem, last_letter, accent = rmatch(lemma, "^(.*)(.)[ая](́?)$")
if not stem then
error("Unrecognized lemma for conjugation 1 or 2: '" .. lemma .. "'")
end
local last_letter_pal = com.first_palatalization[last_letter] or last_letter
local full_stem = stem .. last_letter_pal
base.impf23 = full_stem .. "е" .. accent .. "ше"
if accent == AC then
if rfind(last_letter_pal, "[жчш]") then
base.impf = {full_stem .. "а́х", {form = full_stem .. "е́х", footnotes = {"[largely fallen into disuse]"}}}
else
base.impf = full_stem .. "я́х"
-- This is the only case where the imperfect participle's vowel
-- disagrees with the imperfect 1sg vowel, since it's a yat vowel
-- followed by a front vowel. In other cases, postprocess_base()
-- will automatically generate the imperfect participle bases based
-- on the imperfect 1sg.
base.paippl = full_stem .. "е́ли"
end
else
base.impf = full_stem .. "ех"
end
end
local function generate_maybe_shifted_aorist(base, aor23, shifted_aor23)
if base.aspect ~= "pf" or not base.prefixed then
shifted_aor23 = shifted_aor23 or rsub(aor23, AC, "") .. AC
if aor23 ~= shifted_aor23 then
base.aor23 = {aor23, {form = shifted_aor23, footnotes = {"[方言標記]"}}}
else
base.aor23 = aor23
end
else
base.aor23 = aor23
end
end
local function impv_12conj(base, lemma)
local stem, last_letter = rmatch(lemma, "^(.-)(.)́?[ая]́?$")
if not stem then
error("Unrecognized lemma for conjugation 1 or 2: '" .. lemma .. "'")
end
last_letter = com.first_palatalization[last_letter] or last_letter
local full_stem = stem .. last_letter
if rfind(last_letter, com.vowel_c) then
base.impv = com.maybe_stress_final_syllable(full_stem) .. "й"
else
full_stem = rsub(full_stem, AC, "")
base.impv = full_stem .. "и́"
base.impvpl = full_stem .. "е́те"
end
end
local function impf_impv_12conj(base, lemma)
impf_12conj(base, lemma)
impv_12conj(base, lemma)
end
local conjs = {}
conjs["1.1"] = function(base, lemma)
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
local stem, last_letter = rmatch(lemma, "^(.*)(.)а́?$")
if not stem then
error("Unrecognized lemma for class 1.1: '" .. lemma .. "'")
end
stem = com.maybe_stress_final_syllable(stem)
-- Generate aorist stems (and various other stems for вля́за).
local last_letter_pal = com.first_palatalization[last_letter] or last_letter
if rfind(lemma, "ля́за$") then
local full_stem = stem .. last_letter
local unyat_stem = rsub(full_stem, "ля́з$", "ле́з")
base.aor = full_stem .. "ох"
base.aor23 = unyat_stem .. "е"
base.impf = unyat_stem .. "ех"
base.impf23 = unyat_stem .. "еше"
base.pres3sg = unyat_stem .. "е"
base.impv = unyat_stem
base.impvpl = unyat_stem .. "те"
base.irreg = true
elseif rfind(lemma, "[бв]лека́$") or rfind(lemma, "сека́$") then
base.aor = rsub(stem, "^(.*)е", "%1я") .. last_letter .. "ох"
base.aor23 = stem .. last_letter_pal .. "е"
else
base.aor = stem .. last_letter .. "ох"
base.aor23 = stem .. last_letter_pal .. "е"
end
-- Generate aorist participle stems.
if rfind(lemma, "раста́$") then
base.paap = stem .. "ъл"
base.paapf = stem .. "ла"
base.irreg = true
elseif last_letter == "д" or last_letter == "т" then
base.paap = stem .. "л"
else
local full_stem = rsub(base.aor, "ох$", "")
base.paap = full_stem .. "ъл"
base.paapf = full_stem .. "ла"
base.paappl = rsub(full_stem, "я", "е") .. "ли"
end
-- Generate past passive participle stems.
base.ppp = stem .. last_letter_pal .. "ен"
-- Generate verbal noun stems.
base.vna = base.aor23 .. "не"
end
conjs["1.2"] = function(base, lemma)
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
if not rfind(lemma, "а́?$") then
error("Unrecognized lemma for class 1.2: '" .. lemma .. "'")
end
-- Generate aorist stems.
local aor23
if rfind(lemma, "ера́$") then
-- бера́, дера́, пера́ and derivatives
aor23 = rsub(lemma, "ера́$", "ра́")
elseif rfind(lemma, "греба́$") then
aor23 = rsub(lemma, "греба́$", "гре́ба")
base.irreg = true
elseif rfind(lemma, "гриза́$") then
aor23 = rsub(lemma, "гриза́$", "гри́за")
base.irreg = true
elseif rfind(lemma, "я́") then
-- дя́на (Chitanka type 153tt)
-- бя́лна се, дя́лна, избя́гна, мля́сна, мя́рна, отбя́гна, пробя́гна, ря́зна (Chitanka type 152att)
-- забя́гна, кря́кна, кря́сна, побя́гна, прибя́гна, убя́гна (Chitanka type 152ait)
local unyat_stem = rsub(lemma, "я́(.*)а$", "е́%1")
local unstressed_unyat_stem = rsub(unyat_stem, "е́", "е")
local shifted_aor23 = unstressed_unyat_stem .. "а́"
generate_maybe_shifted_aorist(base, lemma, shifted_aor23)
base.impf = unyat_stem .. "ех"
base.impf23 = unyat_stem .. "еше"
base.pres3sg = unyat_stem .. "е"
base.impv = unstressed_unyat_stem .. "и́"
base.impvpl = unstressed_unyat_stem .. "е́те"
base.irreg = true
else
generate_maybe_shifted_aorist(base, lemma)
end
if aor23 then
base.aor23 = aor23
end
-- Generate past passive participle stems.
base.ppp = aor23 and aor23 .. "н" or rfind(lemma, "на́?$") and lemma .. "т" or lemma .. "н"
-- Generate verbal noun stems.
if rfind(lemma, "на́?$") then
base.vna = false
elseif rfind(lemma, "ера́$") then
base.vna = rsub(lemma, "ера́$", "ране́")
base.vni = false
end
end
conjs["1.3"] = function(base, lemma)
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
if not rfind(lemma, "я́?$") then
error("Unrecognized lemma for class 1.3: '" .. lemma .. "'")
end
-- Generate aorist stems.
local aor23
if rfind(lemma, "дре́мя$") then
aor23 = rsub(lemma, "дре́мя$", "дря́ма")
local shifted_aor23 = rsub(lemma, "дре́мя$", "дрема́")
generate_maybe_shifted_aorist(base, aor23, shifted_aor23)
base.irreg = true
else
aor23 = rsub(lemma, "^(.*)я", "%1а")
generate_maybe_shifted_aorist(base, rsub(lemma, "^(.*)я", "%1а"))
end
-- Generate past passive participle stems.
base.ppp = aor23 .. "н"
end
conjs["1.4"] = function(base, lemma)
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
-- Generate aorist stems.
local aor23
local skip_generate_aorist = false
if rfind(lemma, "лъ́жа$") or rfind(lemma, "стри́жа$") or rfind(lemma, "стъ́ржа$") then
aor23 = rsub(lemma, "жа$", "га")
elseif rfind(lemma, "ре́жа$") then
aor23 = rsub(lemma, "ре́жа$", "ря́за")
local shifted_aor23 = rsub(lemma, "ре́жа", "реза́")
generate_maybe_shifted_aorist(base, aor23, shifted_aor23)
skip_generate_aorist = true
base.irreg = true
elseif rfind(lemma, "жа$") then
aor23 = rsub(lemma, "жа$", "за")
elseif rfind(lemma, "ча$") then
aor23 = rsub(lemma, "ча$", "ка")
elseif rfind(lemma, "ша$") then
aor23 = rsub(lemma, "ша$", "са")
elseif rfind(lemma, "ща$") then -- тра́ща
aor23 = rsub(lemma, "ща$", "та")
else
error("Unrecognized lemma for class 1.4: '" .. lemma .. "'")
end
if not skip_generate_aorist then
generate_maybe_shifted_aorist(base, aor23)
end
-- Generate past passive participle stems.
base.ppp = aor23 .. "н"
end
conjs["1.5"] = function(base, lemma)
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
if not rfind(lemma, "[рщ]а́$") then
error("Unrecognized lemma for class 1.5: '" .. lemma .. "'")
end
-- Generate aorist stems.
base.aor23 = rsub(lemma, "а́$", "я́")
base.paappl = rsub(lemma, "а́$", "е́ли")
-- Generate past passive participle stems.
base.ppp = base.aor23 .. "н"
base.ppppl = rsub(base.aor23, "я́$", "е́") .. "ни"
end
conjs["1.6"] = function(base, lemma)
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
if not rfind(lemma, "[аяе]́я$") then
error("Unrecognized lemma for class 1.6: '" .. lemma .. "'")
end
-- Generate aorist stems.
generate_maybe_shifted_aorist(base, lemma)
-- Generate past passive participle stems.
if rfind(lemma, "зна́я$") then
base.ppp = rsub(lemma, "я$", "ен")
base.irreg = true
else
base.ppp = lemma .. "н"
end
end
conjs["1.7"] = function(base, lemma)
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
local ppp_endings
if base.ppp_ending == "т" then
ppp_endings = "т"
elseif base.ppp_ending == "тн" then
ppp_endings = {"т", "н"}
elseif base.ppp_ending == "нт" then
ppp_endings = {"н", "т"}
else
ppp_endings = "н"
end
-- Generate aorist and past passive participle stems.
if rfind(lemma, "ма$") then
base.aor23 = rsub(lemma, "ма$", "")
base.ppp = base.aor23 .. "т"
elseif rfind(lemma, "[жчш]е́я$") then
base.aor23 = rsub(lemma, "е́я$", "а́")
base.ppp = iut.map_form_or_forms(ppp_endings, function(ending) return base.aor23 .. ending end)
elseif rfind(lemma, "е́я$") then
base.aor23 = rsub(lemma, "е́я$", "я́")
local yat_plural_stem = rsub(lemma, "е́я$", "е́")
base.paappl = yat_plural_stem .. "ли"
base.ppp = iut.map_form_or_forms(ppp_endings, function(ending) return base.aor23 .. ending end)
base.ppppl = iut.map_form_or_forms(ppp_endings, function(ending) return yat_plural_stem .. ending .. "и" end)
elseif rfind(lemma, "[аяиую]́я$") then
-- For verbs in -я́я (влия́я, сия́я), this results in an aorist participle
-- in -я́л, but per rechnik.chitanka.info it stays as -я́ли in the plural,
-- not **-е́ли.
base.aor23 = rsub(lemma, "я$", "")
if rfind(lemma, "[иую]́я$") then
base.ppp = base.aor23 .. "т"
else
base.ppp = iut.map_form_or_forms(ppp_endings, function(ending) return base.aor23 .. ending end)
end
else
error("Unrecognized lemma for class 1.7: '" .. lemma .. "'")
end
-- Generate verbal noun stems.
if rfind(lemma, "[еиую]́я$") then
base.vna = false
end
end
conjs["2.1"] = function(base, lemma)
pres_advp_2conj(base, lemma)
impf_impv_12conj(base, lemma)
-- Generate aorist stems.
generate_maybe_shifted_aorist(base, rsub(lemma, "^(.*)[ая]", "%1и"))
-- Generate past passive participle stems.
base.ppp = rsub(lemma, "^(.*)[ая](́?)$", "%1е%2н")
-- Generate verbal noun stems.
base.vna = base.ppp .. "е"
base.vni = false
end
conjs["2.2"] = function(base, lemma)
pres_advp_2conj(base, lemma)
impf_impv_12conj(base, lemma)
if not rfind(lemma, "я́$") then
error("Unrecognized lemma for class 2.2: '" .. lemma .. "'")
end
-- Generate aorist stems.
base.aor23 = lemma
local oya = rfind(lemma, "оя́$") -- стоя́, боя́ се
local yat_plural_stem = oya and lemma or rsub(lemma, "я́$", "е́")
base.paappl = yat_plural_stem .. "ли"
if oya then
-- стоя has both стоя́не and стое́не as verbal nouns
base.vna = lemma .. "не"
base.paippl = lemma .. "ли"
base.irreg = true
end
-- Generate past passive participle stems.
base.ppp = base.aor23 .. "н"
base.ppppl = yat_plural_stem .. "ни"
end
conjs["2.3"] = function(base, lemma)
pres_advp_2conj(base, lemma)
impf_impv_12conj(base, lemma)
if not rfind(lemma, "[жчш]а́$") then
error("Unrecognized lemma for class 2.3: '" .. lemma .. "'")
end
-- Generate aorist stems.
base.aor23 = lemma
-- Generate past passive participle stems.
base.ppp = base.aor23 .. "н"
-- Handle irregularities
if rfind(lemma, "държа́$") then
base.impv = rsub(lemma, "държа́$", "дръ́ж")
base.impvpl = rsub(lemma, "държа́$", "дръ́жте")
base.irreg = true
end
end
conjs["3"] = function(base, lemma)
local stem = rmatch(lemma, "^(.*[ая])м$")
if not stem then
error("Unrecognized lemma for conjugation 3: '" .. lemma .. "'")
end
-- Generate present stems.
base.pres3sg = stem
base.pres1pl = stem .. "ме"
base.pres3pl = stem .. "т"
-- Generate imperfect stems.
base.impf = stem .. "х"
base.impf23 = stem .. "ше"
-- Generate aorist stems.
generate_maybe_shifted_aorist(base, stem)
-- Generate imperative stems.
base.impv = stem .. "й"
-- Generate past passive participle stems.
base.ppp = stem .. "н"
-- Generate adverbial participle.
base.advp = stem .. "йки"
end
conjs["irreg"] = function(base, lemma)
base.irreg = true
if rfind(lemma, "я́м$") then
conjs["1.1"](base, rsub(lemma, "я́м$", "яда́"))
base.vni = false
base.impv = rsub(lemma, "м$", "ж")
base.impvpl = base.impv .. "те"
base.conj = "1.1"
elseif rfind(lemma, "съ́м$") then
local stem = rmatch(lemma, "^(.*)съ́м$")
base.pres2sg = stem .. "си́"
base.pres3sg = stem .. "е́"
base.pres1pl = stem .. "сме́"
base.pres2pl = stem .. "сте́"
base.pres3pl = stem .. "са́"
base.aor = stem .. "бя́х"
base.aor23 = {stem .. "бе́", stem .. "бе́ше"}
base.impf = stem .. "бя́х"
base.impf23 = stem .. "бе́ше"
base.prap = false
base.paap = stem .. "би́л"
base.paapf = stem .. "била́"
base.paip = stem .. "би́л"
base.paipf = stem .. "била́"
base.advp = {stem .. "бъ́дейки", stem .. "би́дейки"}
base.impv = "бъди́"
base.impvpl = "бъде́те"
base.vna = false
base.vni = false
elseif rfind(lemma, "бъ́да$") then
local stem = rmatch(lemma, "^(.*)бъ́да$")
base.pres3sg = stem .. "бъ́де"
base.aor = {stem .. "би́х", stem .. "би́дох"}
base.aor23 = {stem .. "би́", stem .. "би́де"}
base.impf = {stem .. "бъ́дех", stem .. "бя́х"}
base.impf23 = {stem .. "бъ́деше", stem .. "бе́ше"}
base.prap = stem .. "бъ́дещ"
base.paap = stem .. "би́л"
base.paapf = stem .. "била́"
base.paip = stem .. "бъ́дел"
base.advp = {stem .. "бъ́дейки", stem .. "би́дейки"}
base.impv = "бъди́"
base.impvpl = "бъде́те"
base.vna = false
base.vni = false
elseif rfind(lemma, "ща́") then
local stem = rmatch(lemma, "^(.*)ща́$")
base.pres3sg = stem .. "ще́"
base.aor = stem .. "щя́х"
base.aor23 = {stem .. "щя́", stem .. "ще́ше"}
base.impf = stem .. "щя́х"
base.impf23 = stem .. "ще́ше"
base.prap = false
base.paap = stem .. "щя́л"
base.paappl = stem .. "ще́ли"
base.paip = stem .. "щя́л"
base.paippl = stem .. "ще́ли"
base.vna = false
base.vni = false
elseif rfind(lemma, "зна́м$") then
conjs["1.6"](base, rsub(lemma, "а́м$", "а́я"))
base.conj = "1.6"
elseif rfind(lemma, "да́м$") then
conjs["1.1"](base, rsub(lemma, "да́м$", "дада́"))
base.impv = rsub(lemma, "м$", "й")
base.conj = "1.1"
elseif rfind(lemma, "йда$") then -- до́йда, за́йда, подо́йда, придо́йда
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
base.aor = rsub(lemma, AC .. "йда$", "йдо́х")
base.aor23 = rsub(lemma, AC .. "йда$", "йде́")
base.paap = rsub(lemma, AC .. "йда$", "шъ́л")
base.paapf = rsub(lemma, AC .. "йда$", "шла́")
if lemma == "до́йда" then
base.impv = "ела́"
base.impvpl = base.impv .. "те"
end
-- no past passive participle
base.vna = false
base.conj = "1.1"
elseif rfind(lemma, "и́да$") then -- и́да, оти́да, пооти́да, разоти́да
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
if lemma ~= "и́да" then
-- base verb и́да doesn't have aorist or aorist participle forms
base.aor = rsub(lemma, "да$", "дох")
base.aor23 = rsub(lemma, "да$", "де")
base.paap = {rsub(lemma, "да$", "шъл"), rsub(lemma, "и́да$", "ишъ́л"),
{form=rsub(lemma, "да$", "шел"), footnotes={"[dialectal]"}}}
base.paapf = {rsub(lemma, "да$", "шла"), rsub(lemma, "и́да$", "ишла́")}
end
-- no past passive participle, no verbal noun
base.vna = false
base.vni = false
base.conj = "1.1"
elseif rfind(lemma, "мо́га$") then
pres_advp_1conj(base, lemma)
impf_12conj(base, lemma)
-- no imperative
base.aor23 = rsub(lemma, "мо́га$", "можа́")
local reg_aor23 = rsub(lemma, "мо́га$", "можа́л")
base.paap = {rsub(lemma, "мо́га$", "могъ́л"), reg_aor23}
base.paapf = {rsub(lemma, "мо́га$", "могла́"), reg_aor23 .. "а"}
-- no past passive participle
base.vna = false
elseif rfind(lemma, "спя́$") then
pres_advp_2conj(base, lemma)
impf_impv_12conj(base, lemma)
base.aor23 = rsub(lemma, "я́$", "а́")
-- no past passive participle
base.vna = rsub(lemma, "я́$", "ане́")
base.vni = false
elseif rfind(lemma, "ме́ля$") then
local stem = rmatch(lemma, "^(.*)я$")
base.pres3sg = {stem .. "е", stem .. "и"}
base.advp = stem .. "ейки"
impf_impv_12conj(base, lemma)
base.aor23 = rsub(lemma, "е́ля$", "ля́")
local yat_plural_stem = rsub(lemma, "е́ля$", "ле́")
base.paappl = yat_plural_stem .. "ли"
base.ppp = base.aor23 .. "н"
base.ppppl = yat_plural_stem .. "ни"
base.vna = false
-- prefixed variants are perfective and don't have verbal nouns
base.vni = {"ме́лене", {form="мле́не", footnotes={"[colloquial]"}}, {form="млене́", footnotes={"[colloquial]"}}}
elseif rfind(lemma, "ко́ля$") then
pres_advp_2conj(base, lemma)
impf_impv_12conj(base, lemma)
base.aor23 = {rsub(lemma, "о́ля$", "ла́"), rsub(lemma, "я$", "и")}
base.ppp = {rsub(lemma, "о́ля$", "ла́н"), rsub(lemma, "я$", "ен")}
base.vna = false
-- prefixed variants are perfective and don't have verbal nouns
base.vni = {"ко́лене", "клане́"}
elseif rfind(lemma, "ви́дя$") then
pres_advp_2conj(base, lemma)
impf_impv_12conj(base, lemma)
base.aor23 = rsub(lemma, "ви́дя$", "видя́")
local yat_plural_stem = rsub(base.aor23, "я́$", "е́")
base.paappl = yat_plural_stem .. "ли"
-- Generate past passive participle stems.
base.ppp = base.aor23 .. "н"
base.ppppl = yat_plural_stem .. "ни"
-- Generate imperative forms.
base.impv = rsub(lemma, "ви́дя$", "ви́ж")
base.impvpl = base.impv .. "те"
-- perfective; no verbal noun
base.vna = false
base.vni = false
base.conj = "2.2"
elseif rfind(lemma, "кълна́$") then
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
base.aor23 = {rsub(lemma, "кълна́$", "кле́"), lemma}
base.ppp = {rsub(lemma, "кълна́$", "кле́т"), lemma .. "т"}
base.vna = false
base.conj = "1.2"
elseif rfind(lemma, "сте́ля$") then
pres_advp_1conj(base, lemma)
impf_impv_12conj(base, lemma)
base.aor23 = rsub(lemma, "сте́ля$", "стла́")
base.ppp = base.aor23 .. "н"
base.vna = false
base.conj = "1.3"
elseif rfind(lemma, "беле́жа$") then
pres_advp_2conj(base, lemma)
impf_impv_12conj(base, lemma)
base.aor23 = rsub(lemma, "ле́жа$", "ля́за")
base.ppp = rsub(lemma, "ле́жа$", "ля́зан")
base.vna = false
else
error("Irregular verb '" .. lemma .. "' not yet supported")
end
end
local function postprocess_base(base, lemma)
if not base.pres1sg then
base.pres1sg = lemma
end
if not base.pres2sg then
base.pres2sg = map_append(base.pres3sg, "ш")
end
if not base.pres1pl then
base.pres1pl = map_append(base.pres3sg, "м")
end
if not base.pres2pl then
base.pres2pl = map_append(base.pres3sg, "те")
end
if not base.pres3pl then
base.pres3pl = map_append(base.pres1sg, "т")
end
if not base.aor then
base.aor = map_append(base.aor23, "х")
end
if base.prap == false then
base.prap = nil
elseif not base.prap then
base.prap = map_rsub(base.impf, "х$", "щ")
end
if not base.paap then
base.paap = map_rsub(base.aor, "х$", "л")
end
if not base.paapf then
base.paapf = map_append(base.paap, "а")
end
if not base.paapn then
base.paapn = map_rsub(base.paapf, "а(́?)$", "о%1")
end
if not base.paappl then
base.paappl = map_rsub(base.paapf, "а(́?)$", "и%1")
end
if not base.paip then
base.paip = map_rsub(base.impf, "х$", "л")
end
if not base.paipf then
base.paipf = map_append(base.paip, "а")
end
if not base.paipn then
base.paipn = map_rsub(base.paipf, "а(́?)$", "о%1")
end
if not base.paippl then
base.paippl = map_rsub(base.paipf, "а(́?)$", "и%1")
end
if not base.ppppl then
base.ppppl = map_append(base.ppp, "и")
end
if not base.impvpl then
base.impvpl = map_append(base.impv, "те")
end
local function aor_to_vn(form)
form = rsub(form, "я́х$", "е́не")
form = rsub(form, "х$", "не")
return form
end
local function impf_to_vn(form)
form = rsub(form, "[ая]́х$", "е́не")
form = rsub(form, "х$", "не")
return form
end
if base.vna == nil then
base.vna = iut.map_form_or_forms(base.aor, aor_to_vn, "first only")
end
if base.vni == nil then
base.vni = iut.map_form_or_forms(base.impf, impf_to_vn, "first only")
end
if base.vna == false and base.vni == false then
base.vn = nil
elseif base.vna == false then
base.vn = base.vni
elseif base.vni == false then
base.vn = base.vna
elseif base.vna == base.vni then
base.vn = base.vna
elseif base.aspect == "pf" then
-- No verbal noun.
base.vn = nil
elseif base.vnspec == "a" then
base.vn = base.vna
elseif base.vnspec == "i" then
base.vn = base.vni
elseif base.vnspec == "ai" then
base.vn = {base.vna, base.vni}
elseif base.vnspec == "ia" then
base.vn = {base.vni, base.vna}
elseif base.vnspec == "?" then
base.vn = "?"
else
error("Imperfective/biaspectual verb '" .. base.full_lemma .. "' has two possible verbal nouns, don't know which one to choose; specify 'vna' for " .. base.vna .. ", 'vni' for " .. base.vni .. ", or 'vnai' or 'vnia' for both")
end
end
local function parse_indicator_and_form_spec(angle_bracket_spec)
local inside = rmatch(angle_bracket_spec, "^<(.*)>$")
assert(inside)
local base = {}
local parts = rsplit(inside, ".", true)
local conj
local start = 1
if parts[1] == "irreg" then
conj = "irreg"
start = 2
elseif rfind(parts[1], "^[123]$") then
conj = parts[1]
start = 2
if parts[2] and rfind(parts[2], "^[1-7]$") then
conj = conj .. "." .. parts[2]
start = 3
end
end
base.conj = conj
for i=start,#parts do
local part = parts[i]
if part == "impf" or part == "pf" or part == "both" then
if base.aspect then
error("Can't specify aspect twice: '" .. inside .. "'")
end
base.aspect = part
elseif part == "tr" or part == "intr" then
if base.trans then
error("Can't specify transitivity twice: " .. inside .. "'")
end
base.trans = part
elseif part == "т" or part == "тн" or part == "нт" then
if base.ppp_ending then
error("Can't specify past passive participle ending twice: " .. inside .. "'")
end
base.ppp_ending = part
elseif part == "-pref" then
if base.no_pref then
error("Can't specify '-pref' twice: " .. inside .. "'")
end
base.no_pref = true
elseif part == "vna" or part == "vni" or part == "vnai" or part == "vnia" or part == "vn?" then
if base.vnspec then
error("Can't specify verbal noun spec twice: " .. inside .. "'")
end
base.vnspec = rsub(part, "^vn", "")
elseif part == "impers" then
if base.impers then
error("Can't specify 'impers' twice: " .. inside .. "'")
end
base.impers = true
else
error("Unrecognized indicator '" .. part .. "': '" .. inside .. "'")
end
end
return base
end
-- Separate out reflexive suffix, check that multisyllabic lemmas have stress, and add stress
-- to monosyllabic lemmas if needed.
local function check_lemma_stress(base, lemma)
local active_verb, refl = rmatch(lemma, "^(.*) (с[еи])$")
if active_verb then
base.refl = refl
lemma = active_verb
end
lemma = com.add_monosyllabic_stress(lemma)
if not rfind(lemma, AC) then
error("Multisyllabic lemma '" .. lemma .. "' needs an accent")
end
if base.refl then
base.full_lemma = lemma .. " " .. base.refl
else
base.full_lemma = lemma
end
return lemma
end
local function impersonal_to_personal_lemma(lemma, conj)
local undo_first_palatalization = {
["ч"] = "к",
["ж"] = "г",
["ш"] = "х",
}
local function mustsub(from, to)
local newlemma = rsub(lemma, from, to)
if newlemma == lemma then
error("Unrecognized impersonal lemma for conjugation " .. conj .. ": " .. lemma)
end
return newlemma
end
local conj_is_2 = rfind(conj, "^2%.")
if conj == "1.1" then
return mustsub("(.)е(́?)$",
function(last_cons, accent)
return (undo_first_palatalization[last_cons] or last_cons) .. "а" .. accent
end)
elseif conj == "1.2" or conj == "1.4" or conj == "1.5" or (conj == "1.7" and rfind(lemma, "ме$")) then
return mustsub("е(́?)$", "а%1")
elseif conj == "1.3" or conj == "1.6" or conj == "1.7" then
return mustsub("е(́?)$", "я%1")
elseif (conj_is_2 and rfind(lemma, "[чжш]е$")) then
return mustsub("и(́?)$", "а%1")
elseif conj_is_2 then
return mustsub("и(́?)$", "я%1")
elseif rfind(conj, "^3") then
return lemma .. "м"
else
error("Can't handle irregular impersonal verbs yet")
end
end
local function detect_indicator_and_form_spec(base, lemma)
if not base.aspect then
error("Aspect of 'pf', 'impf' or 'both' must be specified")
end
if base.refl then
if base.trans then
error("Can't specify transitivity with reflexive verb, they're always intransitive: '" .. base.full_lemma .. "'")
end
elseif not base.trans then
error("Transitivity of 'tr' or 'intr' must be specified")
end
local pers_ending = base.impers and "" or "м"
if not base.conj then
if rfind(lemma, "[ая]" .. pers_ending .. "$") then
base.conj = "3"
elseif base.impers then
error("For lemma ending in -е or -и, conjugation must be specified: '" .. lemma .. "'")
else
error("For lemma ending in -а or -я, conjugation must be specified: '" .. lemma .. "'")
end
elseif base.conj == "3.1" then
if not rfind(lemma, "а" .. pers_ending .. "$") then
error("Conjugation 3.1 lemma must end in -а" .. pers_ending .. ": '" .. lemma .. "'")
end
base.conj = "3"
elseif base.conj == "3.2" then
if not rfind(lemma, "я" .. pers_ending .. "$") then
error("Conjugation 3.2 lemma must end in -я" .. pers_ending .. ": '" .. lemma .. "'")
end
base.conj = "3"
elseif not conjs[base.conj] then
error("Unrecognized conjugation '" .. base.conj .. "' for lemma '" .. lemma .. "'")
end
base.orig_lemma = lemma
if base.impers then
lemma = impersonal_to_personal_lemma(lemma, base.conj)
end
base.prefixed = not base.no_pref and verb_may_be_prefixed(lemma)
return lemma
end
local function parse_word_spec(text)
local segments = put.parse_balanced_segment_run(text, "<", ">")
if #segments ~= 3 or segments[3] ~= "" then
error("Verb spec must be of the form 'LEMMA<CONJ.SPECS>': '" .. text .. "'")
end
local lemma = segments[1]
local base = parse_indicator_and_form_spec(segments[2])
return base, lemma
end
local function format_gender(g)
return require("Module:gender and number").format_genders({g})
end
local function show_forms(base, fullmod)
local forms = base.forms
local lemmas = {}
for _, lemma in ipairs(forms.lemma) do
table.insert(lemmas, com.remove_monosyllabic_stress(lemma))
end
local accel_lemma = lemmas[1]
forms.lemma = table.concat(lemmas, ", ")
local footnote_obj = com.init_footnote_obj()
for _, notr_slot in ipairs(verb_notr_slots) do
forms[notr_slot .. "_notr"] = com.display_one_form(footnote_obj, forms, notr_slot,
nil, nil, false, "slash join")
end
com.display_forms(footnote_obj, forms, forms, verb_slots, "is list", accel_lemma, slot_to_accel_form)
if fullmod then
com.display_forms(footnote_obj, forms, forms, fullmod.verb_compound_slots, "is list", nil, nil)
end
forms.refl = base.refl or ""
-- forms.along_with_refl = base.refl and "along with [[" .. base.refl .. "]]" or ""
forms.along_with_refl = ""
forms.gm = format_gender("m")
forms.gf = format_gender("f")
forms.gn = format_gender("n")
forms.gp = format_gender("p")
if base.footnote then
table.insert(footnote_obj.notes, base.footnote)
end
forms.footnote = table.concat(footnote_obj.notes, "<br />")
end
local function make_table(base, fullmod)
local forms = base.forms
local ann_parts = {}
if base.conj == "irreg" then
table.insert(ann_parts, "不規則")
else
table.insert(ann_parts, "第" .. base.conj .. "類變位")
if base.irreg then
table.insert(ann_parts, "不規則")
end
end
table.insert(ann_parts,
base.aspect == "impf" and "非完整體" or
base.aspect == "pf" and "完整體" or
"雙體")
table.insert(ann_parts,
base.trans == "tr" and "及物" or
base.trans == "intr" and "不及物" or
"反身")
if base.impers then
table.insert(ann_parts, "非人稱")
end
forms.annotation = " (" .. table.concat(ann_parts, ",") .. ")"
-- auxiliaries used in the table
forms.nyama = link("ня́ма")
forms.nyamashe = link("ня́маше")
forms.nyamalo = link("ня́мало")
forms.shte = link("ще")
forms.shta = link("ща")
forms.da = link("да")
forms.bilo = link("било́")
forms.sam = link("съм")
forms.e = link("е")
forms.bada = link("бъ́да")
local aor_part_list_pers = [=[
{paap_ind_m_sg_notr} {gm}, {paap_ind_f_sg_notr} {gf},{paap_ind_n_sg_notr} {gn}或{paap_ind_pl_notr} {gp}]=]
local aor_part_list_impers = [=[
{paap_ind_n_sg_notr}]=]
local impf_part_list_pers = [=[
{paip_m_sg_notr} {gm}, {paip_f_sg_notr} {gf},{paip_n_sg_notr} {gn}或{paip_pl_notr} {gp}]=]
local impf_part_list_impers = [=[
{paip_n_sg_notr}]=]
local table_spec_non_compound = [=[
<div class="NavFrame">
<div class="NavHead" align=left> {lemma} 的變位{annotation}</div>
<div class="NavContent" align="center">
{\op}| style="background:var(--wikt-palette-grey-0,#F0F0F0); font-size: 90%; width:100%; margin: 0 auto 0 auto; text-align:center;" class="inflection-table"
! colspan="2" style="width:10%; background:var(--wikt-palette-lime-2,#e2e4c0);" | 分詞
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 現在主動分詞
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 過去主動不定過去分詞
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 過去主動不定過去分詞
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 過去被動分詞
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 動詞性名詞
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 副詞性分詞
|-
! rowspan="3" style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 陽性
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 不定
|{prap_ind_m_sg}
|{paap_ind_m_sg}
|{paip_m_sg}
|{ppp_ind_m_sg}
| rowspan="5" |
| rowspan="9" |{advp}
|-
! style="background:var(--wikt-palette-lime-2,#e2e4c0); white-space: nowrap;" | 定指主語形式
|{prap_def_sub_m_sg}
|{paap_def_sub_m_sg}
|—
|{ppp_def_sub_m_sg}
|-
! style="background:var(--wikt-palette-lime-2,#e2e4c0); white-space: nowrap;" | 定指賓語形式
|{prap_def_obj_m_sg}
|{paap_def_obj_m_sg}
|—
|{ppp_def_obj_m_sg}
|-
! rowspan="2" style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 陰性
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 不定
|{prap_ind_f_sg}
|{paap_ind_f_sg}
|{paip_f_sg}
|{ppp_ind_f_sg}
|-
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 定指
|{prap_def_f_sg}
|{paap_def_f_sg}
|—
|{ppp_def_f_sg}
|-
! rowspan="2" style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 中性
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 不定
|{prap_ind_n_sg}
|{paap_ind_n_sg}
|{paip_n_sg}
|{ppp_ind_n_sg}
|{vn_ind_sg}
|-
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 定指
|{prap_def_n_sg}
|{paap_def_n_sg}
|—
|{ppp_def_n_sg}
|{vn_def_sg}
|-
! rowspan="2" style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 複數
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 不定
|{prap_ind_pl}
|{paap_ind_pl}
|{paip_pl}
|{ppp_ind_pl}
|{vn_ind_pl}
|-
! style="background:var(--wikt-palette-lime-2,#e2e4c0)" | 定指
|{prap_def_pl}
|{paap_def_pl}
|—
|{ppp_def_pl}
|{vn_def_pl}
|{\cl}
{\op}| style="background:var(--wikt-palette-grey-0,#F0F0F0); font-size: 90%; width: 100%" class="inflection-table"
! colspan="{ncol}" rowspan="2" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 人稱
! colspan="3" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 單數
! colspan="3" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 複數
|-
! style="background:var(--wikt-palette-grey-2,#C0C0C0);width:12.5%" | 第一
! style="background:var(--wikt-palette-grey-2,#C0C0C0);width:12.5%" | 第二
! style="background:var(--wikt-palette-grey-2,#C0C0C0);width:12.5%" | 第三
! style="background:var(--wikt-palette-grey-2,#C0C0C0);width:12.5%" | 第一
! style="background:var(--wikt-palette-grey-2,#C0C0C0);width:12.5%" | 第二
! style="background:var(--wikt-palette-grey-2,#C0C0C0);width:12.5%" | 第三
|-
! colspan="{ncol}" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 直陳
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | аз
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | ти
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | той/тя/то
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | ние
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | вие
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | те
|-
! colspan="{ncol}" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 現在
| {pres_1sg}
| {pres_2sg}
| {pres_3sg}
| {pres_1pl}
| {pres_2pl}
| {pres_3pl}
|-
! colspan="{ncol}" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 未完成
| {impf_1sg}
| {impf_2sg}
| {impf_3sg}
| {impf_1pl}
| {impf_2pl}
| {impf_3pl}
|-
! colspan="{ncol}" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 不定過去
| {aor_1sg}
| {aor_2sg}
| {aor_3sg}
| {aor_1pl}
| {aor_2pl}
| {aor_3pl}
|-
]=]
local table_spec_compound_short = [=[
! rowspan="2" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 將來
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 肯定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{shte} {refl}後跟現在直陳式
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 否定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{nyama} {da} {refl}後跟現在直陳式
|-
! rowspan= "2" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 過去將來
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 肯定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{shta}的未完成直陳式後跟{da} {refl}和現在直陳式
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 否定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{nyamashe} {da} {refl} 後跟現在直陳式
|-
! colspan="2" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 現在完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的現在直陳式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 過去完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的未完成直陳式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 將來完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的將來直陳式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 過去將來完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的過去將來直陳式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-green-2,#c0e4c0)" | 推理
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | аз
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | ти
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | той/тя/то
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | ние
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | вие
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | те
|-
! colspan="2" style="background:var(--wikt-palette-green-2,#c0e4c0)" | 現在未完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0); white-space: nowrap;" | 用{sam} {along_with_refl}的現在直陳式(忽略第三人稱)和{impf_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-green-2,#c0e4c0)" | 不定過去
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的現在直陳式(忽略第三人稱)和{aor_part_list}
|-
! rowspan="2" style="background:var(--wikt-palette-green-2,#c0e4c0)" | 將來和過去將來
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | 肯定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{shta}的現在/未完成推理式後跟{da} {refl}和現在直陳式
|-
! style="background:var(--wikt-palette-green-2,#c0e4c0)" | 否定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{nyamalo} {da} {refl}和現在直陳式
|-
! colspan="2" style="background:var(--wikt-palette-green-2,#c0e4c0)" | 現在和過去完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的現在/未完成推理式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-green-2,#c0e4c0)" | 將來完成和過去將來完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的將來/過去將來推理式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 懷疑
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | аз
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | ти
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | той/тя/то
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | ние
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | вие
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | те
|-
! colspan="2" style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 現在和未完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的現在/未完成推理式和{impf_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 不定過去
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的不定過去推理式和{aor_part_list}
|-
! rowspan="2" style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 將來和過去將來
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 肯定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{shta}的現在/未完成懷疑式後跟{da} {refl}和現在直陳式
|-
! style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 否定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{nyamalo} {bilo} {da} {refl}和現在直陳式
|-
! colspan="2" style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 現在和過去完成
| colspan="6" |<div class="center">>無</div>
|-
! colspan="2" style="background:var(--wikt-palette-yellow-2,#f0e68c)" | 將來完成和過去將來完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0); white-space: nowrap;" | 用{sam} {along_with_refl}的將來/過去將來懷疑式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 結論
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | аз
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | ти
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | той/тя/то
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | ние
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | вие
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | те
|-
! colspan="2" style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 現在和未完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的現在直陳式和{impf_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 不定過去
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的現在直陳式和{aor_part_list}
|-
! rowspan="2" style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 將來和過去將來
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 肯定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{shta}的現在/未完成結論式後跟{da} {refl}和現在直陳式
|-
! style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 否定
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{nyamalo} {e} {da} {refl}和現在直陳式
|-
! colspan="2" style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 現在和過去完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{sam} {along_with_refl}的現在/未完成結論式和{aor_part_list}
|-
! colspan="2" style="background:var(--wikt-palette-cyan-2,#9be1ff)" | 將來完成和過去將來完成
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0); white-space: nowrap;" | 用{sam} {along_with_refl}的將來/過去將來結論式和{aor_part_list}
|-
! rowspan="2" colspan="2" style="background:var(--wikt-palette-grey-red-2,#f2b6c3)" | 條件
! style="background:var(--wikt-palette-grey-red-2,#f2b6c3)" | аз
! style="background:var(--wikt-palette-grey-red-2,#f2b6c3)" | ти
! style="background:var(--wikt-palette-grey-red-2,#f2b6c3)" | той/тя/то
! style="background:var(--wikt-palette-grey-red-2,#f2b6c3)" | ние
! style="background:var(--wikt-palette-grey-red-2,#f2b6c3)" | вие
! style="background:var(--wikt-palette-grey-red-2,#f2b6c3)" | те
|-
! colspan="6" style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 用{bada} {along_with_refl}的第一不定過去直陳式和{aor_part_list}
|-
]=]
local table_spec_end = [=[
! rowspan="2" colspan="{ncol}" style="background:var(--wikt-palette-grey-orange-2,#e4d4c0)" | 祈使
! style="background:var(--wikt-palette-grey-orange-2,#e4d4c0)" | -
! style="background:var(--wikt-palette-grey-orange-2,#e4d4c0)" | ти
! style="background:var(--wikt-palette-grey-orange-2,#e4d4c0)" | -
! style="background:var(--wikt-palette-grey-orange-2,#e4d4c0)" | -
! style="background:var(--wikt-palette-grey-orange-2,#e4d4c0)" | вие
! style="background:var(--wikt-palette-grey-orange-2,#e4d4c0)" | -
|-
|
| {impv_sg}
|
|
| {impv_pl}
|
|{\cl}{notes_clause}</div></div>]=]
local notes_template = [===[
<div style="width:100%;text-align:left;background:var(--wikt-palette-blue-2,#d9ebff)">
<div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em">
{footnote}
</div></div>
]===]
forms.aor_part_list = m_string_utilities.format(
base.impers and aor_part_list_impers or aor_part_list_pers, forms)
forms.impf_part_list = m_string_utilities.format(
base.impers and impf_part_list_impers or impf_part_list_pers, forms)
forms.notes_clause = forms.footnote ~= "" and
m_string_utilities.format(notes_template, forms) or ""
forms.ncol = fullmod and "3" or "2"
local table_spec_compound = fullmod and fullmod.table_spec_compound_full or table_spec_compound_short
local table_spec = table_spec_non_compound .. table_spec_compound .. table_spec_end
return m_string_utilities.format(table_spec, forms)
end
-- Externally callable function to parse and decline a verb given user-specified arguments.
-- Return value is WORD_SPEC, an object where the declined forms are in `WORD_SPEC.forms`
-- for each slot. If there are no values for a slot, the slot key will be missing. The value
-- for a given slot is a list of objects {form=FORM, footnotes=FOOTNOTES}.
function export.do_generate_forms(parent_args, pos, from_headword, def)
local params = {
[1] = {required = true, default = def or "пра́вя<2.1.impf.tr>"},
footnote = true,
title = true,
full = {type = "boolean"},
json = {type = "boolean"}, -- for bot use
}
if from_headword then
params.lemma = {list = true}
params.id = true
params.pos = {default = pos}
params.cat = {list = true}
end
local args = m_para.process(parent_args, params)
if args.title then
track("overriding-title")
end
pos = args.pos or pos -- args.pos only set when from_headword
local base, lemma = parse_word_spec(args[1])
lemma = check_lemma_stress(base, lemma)
lemma = detect_indicator_and_form_spec(base, lemma)
conjs[base.conj](base, lemma)
postprocess_base(base, lemma)
base.forms = {}
base.footnote = footnote
conjugate_all(base)
if base.refl then
add_reflexive_suffix(base)
end
if base.impers then
remove_non_impersonal_forms(base)
end
add_categories(base)
local fullmod
if args.full then
fullmod = require("Module:bg-verb/full")
fullmod.conjugate_all_compound(base)
end
base.forms.lemma = args.lemma and #args.lemma > 0 and args.lemma or
{base.orig_lemma .. (base.refl and " " .. base.refl or "")}
if args.json then
return require("Module:JSON").toJSON(base)
end
return base, fullmod
end
-- Main entry point. Template-callable function to parse and conjugate a verb given
-- user-specified arguments and generate a displayable table of the conjugated forms.
function export.show(frame)
local parent_args = frame:getParent().args
local base, fullmod = export.do_generate_forms(parent_args)
if type(base) == "string" then -- json=1 specified
return base
end
show_forms(base, fullmod)
return make_table(base, fullmod) .. require("Module:utilities").format_categories(base.categories, lang)
end
return export
fx3r6o0gsxqqr43veq1ldise0cz09yu
Module:Ast-conj
828
1884225
9909047
7222717
2026-09-29T07:24:39Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909047
Scribunto
text/plain
local m_gen_num = require("Module:gender and number")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_infl = require("Module:ast-conj/data")
local lang = require("Module:languages").getByCode("ast")
local export = {}
-- Shows forms with links, or a dash if empty
local function show_form(subforms)
if not subforms then
return "—"
elseif type(subforms) ~= "table" then
error("a non-table value was given in the list of inflected forms.")
elseif #subforms == 0 then
return "—"
end
local ret = {}
-- Go over each subform and insert links
for key, subform in ipairs(subforms) do
table.insert(ret, m_links.full_link({lang = lang, term = subform}))
end
return table.concat(ret, ", ")
end
-- Shows the table with the given forms
local function make_table(data)
local ret = {[=[<div class="NavFrame">
<div class="NavHead" style="text-align: left">變位:]=]}
table.insert(ret, m_links.full_link({lang = lang, alt = data.forms.infinitive[1]}, "term") .. "</div>\n")
table.insert(ret, [=[<div class="NavContent">
{| style="background:#F0F0F0;width:100%" class="inflection-table"
|-
! colspan="2" style="background:#e2e4c0" | 不定式
| colspan="6" align="center" | ]=])
table.insert(ret, show_form(data.forms.infinitive) .. "\n")
table.insert(ret, [=[|-
! colspan="2" style="background:#e2e4c0" | 動名詞
| colspan="6" align="center" | ]=])
table.insert(ret, show_form(data.forms.gerund) .. "\n")
table.insert(ret, [=[|-
! colspan="2" style="background:#e2e4c0" | 過去分詞
| colspan="6" align="center" | ]=])
if not data.forms.part_all then
table.insert(ret, m_gen_num.format_genders({"m"}) .. " " .. show_form(data.forms.part_m) .. ", ")
table.insert(ret, m_gen_num.format_genders({"f"}) .. " " .. show_form(data.forms.part_f) .. ", ")
table.insert(ret, m_gen_num.format_genders({"n"}) .. " " .. show_form(data.forms.part_n) .. ", ")
table.insert(ret, m_gen_num.format_genders({"m-p"}) .. " " .. show_form(data.forms["part_m-p"]) .. ", ")
table.insert(ret, m_gen_num.format_genders({"f-p"}) .. " " .. show_form(data.forms["part_f-p"]) .. "\n")
else
table.insert(ret, show_form(data.forms.part_all) .. "\n")
end
table.insert(ret, [=[
|- style="background:#C0C0C0"
! colspan="2" | 人稱
! style="width:12.5%" | 第一人稱單數<br/>yo
! style="width:12.5%" | 第二人稱單數<br/>tu
! style="width:12.5%" | 第三人稱單數<br/>él/elli
! style="width:12.5%" | 第一人稱複數<br/>nosotros/nós
! style="width:12.5%" | 第二人稱複數<br/>vosotros/vós
! style="width:12.5%" | 第三人稱複數<br/>ellos
|-
! style="background:#c0cfe4" rowspan="6" | 直陳式
! style="height:3em;background:#c0cfe4" | 現在
]=])
table.insert(ret, "| " .. show_form(data.forms.pres_ind_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_ind_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_ind_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_ind_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_ind_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_ind_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="height:3em;background:#c0cfe4" | 過去未完成
]=])
table.insert(ret, "| " .. show_form(data.forms.impf_ind_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_ind_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_ind_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_ind_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_ind_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_ind_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="height:3em;background:#c0cfe4" | 過去
]=])
table.insert(ret, "| " .. show_form(data.forms.pret_ind_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pret_ind_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pret_ind_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pret_ind_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pret_ind_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pret_ind_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="height:3em;background:#c0cfe4" | 過去完成
]=])
table.insert(ret, "| " .. show_form(data.forms.plup_ind_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.plup_ind_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.plup_ind_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.plup_ind_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.plup_ind_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.plup_ind_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="height:3em;background:#c0cfe4" | 將來
]=])
table.insert(ret, "| " .. show_form(data.forms.futr_ind_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.futr_ind_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.futr_ind_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.futr_ind_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.futr_ind_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.futr_ind_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="height:3em;background:#c0cfe4" | 條件式
]=])
table.insert(ret, "| " .. show_form(data.forms.cond_ind_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.cond_ind_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.cond_ind_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.cond_ind_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.cond_ind_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.cond_ind_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="background:#c0e4c0" rowspan="2" | 虛擬式
! style="height:3em;background:#c0e4c0" | 現在
]=])
table.insert(ret, "| " .. show_form(data.forms.pres_sub_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_sub_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_sub_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_sub_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_sub_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.pres_sub_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="height:3em;background:#c0e4c0" | 過去未完成
]=])
table.insert(ret, "| " .. show_form(data.forms.impf_sub_1sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_sub_2sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_sub_3sg) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_sub_1pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_sub_2pl) .. "\n")
table.insert(ret, "| " .. show_form(data.forms.impf_sub_3pl) .. "\n")
table.insert(ret, [=[
|-
! style="height:3em;background:#e4d4c0" colspan="2" | 命令式
| —
| ]=])
table.insert(ret, show_form(data.forms.impr_2sg) .. "\n")
table.insert(ret, [=[
| —
| —
| ]=])
table.insert(ret, show_form(data.forms.impr_2pl) .. "\n")
table.insert(ret, [=[
| —
|}
</div></div>]=])
return table.concat(ret)
end
-- Main entry point
function export.show(frame)
local args = frame:getParent().args
-- Create the forms
local data = {forms = {}, categories = {}, refl = false}
-- Find what type of verb is it (hard-coded in the template).
-- m_gen_numerate standard conjugated forms for each type of verb,
local infl_type = frame.args["conj"]
if not args[1] then args[1] = "{{{1}}}" end
--[[ Provisional code to prevent arg 2 from being blank, to avoid
the following forms from ending up as blank stems with their
inflectional endings in cases where this is invoked with only
args 1 and 3:
* 1st sg. ind. (would result in "o")
* 3rd sg. ind. (would result in "a")
* 2nd. sg. imp. (would result in "a")
]]--
if (args[2] == "" or not args[2]) then args[2] = args[1] end
if not infl_type then
data.forms.infinitive = {args["inf"]}; if data.forms.infinitive[1] == "" then data.forms.infinitive = nil end
elseif m_infl[infl_type] then
m_infl[infl_type](args, data)
else
error("Verb type " .. infl_type .. " not supported.")
end
return make_table(data) .. m_utilities.format_categories(data.categories, lang)
end
return export
mgllu20wwja6hamg1j6h3jiq53qz5z9
-tygodniowy
0
1887944
9909154
8114342
2026-09-29T08:50:24Z
TongcyDai
53191
9909154
wikitext
text/x-wiki
{{also|tygodniowy}}
==波蘭語==
===發音===
{{pl-pr}}
===後綴===
{{head|pl|後綴}}
# …[[星期]],…星期長
#: {{suffixusex|pl|dwa|dwutygodniowy}}
# …星期大(年齡)
#: {{suffixusex|pl|dwa|dwutygodniowy}}
====變格====
{{pl-adecl}}
====派生詞彙====
{{suffixsee|pl}}
===延伸閱讀===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
b44yviqrqssx8tzntz3ux1e1y8n4du0
powstanie
0
1909133
9909156
8653144
2026-09-29T08:54:13Z
TongcyDai
53191
9909156
wikitext
text/x-wiki
==古波蘭語==
===詞源===
{{ety|zlw-opl|:af|powstać|-anie|etydate=15世紀|tree=+|text=+}}
===發音===
* {{zlw-opl-IPA|powstanie:}}
===名詞===
{{zlw-opl-noun|n}}
# {{lb|zlw-opl|記錄於|_|Masovia}} {{verbal noun of|zlw-opl|powstać|t=站起來,起身}}
#* {{RQ:zlw-opl:EwZam|295|Otho poloszon yest then na vpad y na '''powsthanye''' (in resurrectionem Luc 2, 34) vyelyą w Ysraelyv|Oto położon jest ten na upad i na '''powstanie''' (in resurrectionem Luc 2, 34) wiela w Israelu|-}}
====相關詞彙====
{{zlw-opl-derived verbs|powstać/powstajać,powstawać}}
====派生語彙====
* {{desc|pl|powstanie}}
* {{desc|szl|powstanie}}
===參考資料===
* {{R:pl:Bańkowski}}
* {{R:zlw-opl:SPJSP}}
==波蘭語==
{{wp|pl:}}
===詞源1===
{{ety|pl|:inh|zlw-opl:powstanie|tree=+|text=+}} {{surf|pl|powstać|-anie}}。
====發音====
{{pl-pr|po.|a=Pl-powstanie.ogg|mp=[po.,ié]}}
====名詞====
{{pl-noun|n|adj=powstaniowy}}
# {{lb|pl|可數|政府|政治}} [[起義]],[[叛亂]] {{+obj|pl|:przeciw/:przeciwko(dat)<對抗某人/某事>}}
#: {{syn|pl|insurekcja|rebelia|rewolta}}
#: {{hyper|pl|bunt}}
=====變格=====
{{pl-decl-noun-n}}
=====相關詞彙=====
{{col|pl|title=形容詞|powstańczy}}
{{col|pl|title=名詞|powstaniec}}
{{col|pl|title=動詞|powstać|powstawać}}
====名詞====
{{pl-noun|n|gerund=1}}
# {{lb|pl|不可數}} {{verbal noun of|pl|powstać}}
=====變格=====
{{pl-decl-noun-n|tantum=s}}
====附記====
{{pl-freq 1990|32|25|10|4|3|74|872|422|1}}
===詞源2===
{{ety|pl|nl=1}}
====發音====
{{pl-pr|po.|a=Pl-powstanie.ogg|mp=po.}}
====動詞====
{{head|pl|動詞變位形式}}
# {{inflection of|pl|powstać||3|s|futr}}
===參考資料===
{{reflist}}
===延伸閱讀===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
* {{R:pl:SXVI|+|91096}}
* {{R:pl:SXVII|POWSTANIE|46800|PK|07.05.2020}}
* {{R:pl:SJP1807}}
* {{R:pl:SJP1861}}
* {{R:pl:SJP1900||866|4}}
* {{R:pl:NFJP}}
{{C|pl|集體|暴力}}
==西里西亞語==
===詞源===
{{ety|szl|:inh|zlw-opl:powstanie|tree=+|text=+}} {{surf|szl|powstać|-anie}}。
===發音===
{{szl-pr|a=LL-Q30319 (szl)-Vxern-#.wav}}
===名詞===
{{szl-noun|n}}
# {{lb|szl|不可數}} {{verbal noun of|szl|powstać}}
# {{lb|szl|可數|政府|政治}} [[起義]],[[暴動]],[[叛亂]] {{+obj|szl|:przeciw/:przeciwko(dat)<對抗某人/某事>}}
#: {{syn|szl|aufsztand|insurekcyjŏ|rebelijŏ|sryw}}
====相關詞彙====
{{col|szl|title=名詞|powstaniec}}
{{szl-derived verbs|powstać/powstŏwać}}
===延伸閱讀===
* {{R:szl:silling}}
* {{R:szl:ZPJŚ|powstani[e]|118}}
{{C|szl|集體|暴力}}
7l9xggjt4hoks317crx8edoqz4vhl8w
protaktyn
0
1992061
9909147
7446531
2026-09-29T08:46:46Z
TongcyDai
53191
9909147
wikitext
text/x-wiki
==波兰语==
{{chemical element box|pl}}
{{wikipedia|lang=pl}}
===词源===
{{bor+|pl|la|prōtactīnium}}。
===发音===
{{pl-pr|a=LL-Q809 (pol)-Poemat-protaktyn.wav}}
===名词===
{{pl-noun|m-in}}
# [[鏷]]
====变格====
{{pl-decl-noun-m-in|tantum=s}}
===延伸阅读===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
j2fodd64wtzflneiwh5xk5kyimzyj3f
smyrek
0
2030231
9909153
9875244
2026-09-29T08:50:00Z
TongcyDai
53191
9909153
wikitext
text/x-wiki
==捷克語==
===發音===
* {{cs-IPA}}
* {{hyphenation|cs|smy|rek}}
===名詞===
{{cs-noun|m-in}}
# {{lb|cs|古舊}} {{alternative form of|cs|smirek}}
====變格====
{{cs-ndecl|m}}
===延伸閱讀===
* {{R:cs:PSJC}}
* {{R:cs:SSJC}}
==西里西亞語==
===發音===
{{szl-pr|a=LL-Q30319 (szl)-Vxern-#.wav}}
===名詞===
{{szl-noun|m-in}}
# {{alt form|szl|smrek}}
rhm87a9ydadzde0ijt61dzx1k24bwgv
Module:Sla-derived terms
828
2064165
9909106
9819371
2026-09-29T07:28:21Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909106
Scribunto
text/plain
--[=[
This module implements {{*-derived terms}} for various Slavic languages, which creates a list or table of
verbs derived from a base verb.
Potentially this supports all Slavic languages, but in practice additional work is needed for Ukrainian and
Belarusian to properly handle stressed prefixes (particularly Belarusian вы́-, Ukrainian ви́-). To add this
support, you need to provide the appropriate language-specific version of paste_prefix_suffix(); see
ru_paste_prefix_suffix() for the Russian version, which provides a starting point. (Handling Ukrainian and
Belarusian should be easier because these languages don't normally support or need manual transliteration.)
Author: Benwing2; rewritten from initial version by Erutuon.
FIXME:
1. Brackets. [DONE]
2. Period as prefix value (needed?). [DONE]
3. Properly propagate all modifiers. [DONE]
4. Consider adding default aspect to table if term occurs as both perfective and imperfective.
]=]
local export = {}
local m_table = require("Module:table")
local m_links = require("Module:links")
local require_when_needed = require("Module:require when needed")
local decorations_module = "Module:decorations"
local parse_utilities_module = "Module:parse utilities"
local parameter_utilities_module = "Module:parameter utilities"
local m_decorations = require_when_needed(decorations_module)
local put = require_when_needed(parse_utilities_module)
local m_parameter_utilities = require_when_needed(parameter_utilities_module)
local dump = mw.dumpObject
local rsplit = mw.text.split
local rsubn = mw.ustring.gsub
local insert = table.insert
local concat = table.concat
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
local function transliterate(lang, term)
return (lang:transliterate(m_links.remove_links(term)))
end
local function default_paste_prefix_suffix(lang, prefix, prefix_tr, suffix, suffix_tr, aspect)
if not prefix_tr and not suffix_tr then
return prefix .. suffix, nil
end
prefix_tr = prefix_tr or transliterate(lang, prefix)
suffix_tr = suffix_tr or transliterate(lang, suffix)
return prefix .. suffix, prefix_tr .. suffix_tr
end
local function ru_paste_prefix_suffix(lang, prefix, prefix_tr, suffix, suffix_tr, aspect)
local com = require("Module:ru-common")
prefix_tr = prefix_tr and com.decompose(prefix_tr) or nil
suffix_tr = suffix_tr and com.decompose(suffix_tr) or nil
if aspect == "impf" then
prefix, prefix_tr = com.make_unstressed(prefix, prefix_tr)
end
if com.is_stressed(prefix) then
suffix, suffix_tr = com.make_unstressed(suffix, suffix_tr)
end
local verb, verb_tr = com.concat_russian_tr(prefix, prefix_tr, suffix, suffix_tr)
verb_tr = verb_tr and com.recompose(verb_tr) or nil
return com.remove_monosyllabic_accents(verb, verb_tr)
end
-- Combine two sets of decorations. If either is {nil}, just return the other, and if both are {nil}, return {nil}.
local function combine_decorations(quals1, quals2)
if not quals1 and not quals2 then
return nil
end
if not quals1 then
return quals2
end
if not quals2 then
return quals1
end
local combined = m_table.shallowCopy(quals1)
for _, note in ipairs(quals2) do
m_table.insertIfNot(combined, note)
end
return combined
end
local function get_aspects(args)
local first_aspect, second_aspect
if args.impf_first then
first_aspect = "impf"
second_aspect = "pf"
else
first_aspect = "pf"
second_aspect = "impf"
end
return first_aspect, second_aspect
end
local param_mods
local function parse_aspect_pair(arg, arg_index, state, lang_module, args)
local pair = {}
local suffixes = false
if arg == "-" then
return false
end
local origarg = arg
if arg:find("^%*") then
suffixes = true
arg = rsub(arg, "^%*", "")
end
local function parse_err(msg)
error(msg .. ": " .. arg_index .. "=" .. origarg)
end
local function generate_obj(term)
local obj
local within_brackets = term:match("^%[(.*)%]$")
if within_brackets then
obj = {term = within_brackets, brackets = true}
else
obj = {term = term}
end
if obj.term == "." then
obj.term = ""
end
return obj
end
local function parse_term_with_modifiers(run)
param_mods = param_mods or m_parameter_utilities.construct_param_mods {
{group = {"link", "ref", "l", "q"}}
}
return put.parse_inline_modifiers_from_segments {
group = run,
props = {
generate_obj = generate_obj,
parse_err = parse_err,
param_mods = param_mods,
}
}
end
if arg:find("<") and not put.term_contains_top_level_html(arg) then
local segments = put.parse_balanced_segment_run(arg, "<", ">")
local slash_separated_groups =
put.split_alternating_runs_and_frob_raw_text(segments, "/", put.strip_spaces)
if #slash_separated_groups == 1 then
pair.prefix = parse_term_with_modifiers(slash_separated_groups[1])
elseif #slash_separated_groups > 2 then
parse_err("Saw more than two slashes")
else
local function process_terms(segments)
local retval = {}
local comma_separated_groups =
put.split_alternating_runs_and_frob_raw_text(segments, ",", put.strip_spaces)
for _, comma_separated_group in ipairs(comma_separated_groups) do
insert(retval, parse_term_with_modifiers(comma_separated_group))
end
return retval
end
local firsts, seconds = unpack(slash_separated_groups)
pair.firsts = process_terms(firsts)
pair.seconds = process_terms(seconds)
end
else
local split_on_slash = rsplit(arg, "%s*/%s*")
if #split_on_slash == 1 then
pair.prefix = generate_obj(arg)
elseif #split_on_slash > 2 then
parse_err("Saw more than two slashes")
else
local function process_terms(terms)
local retval = {}
terms = rsplit(terms, "%s*,%s*")
for _, term in ipairs(terms) do
insert(retval, generate_obj(term))
end
return retval
end
local firsts, seconds = unpack(split_on_slash)
pair.firsts = process_terms(firsts)
pair.seconds = process_terms(seconds)
end
end
if pair.prefix and pair.prefix.term:find(",") then
parse_err(("Commas not allowed in single prefix spec '%s'"):format(pair.prefix.term))
end
if suffixes then
if pair.prefix then
parse_err("Can't specify a single prefix with leading *")
end
local function remove_bare_hyphens(terms)
local retval = {}
for _, term in ipairs(terms) do
if term.term ~= "-" then
insert(retval, term)
end
end
return retval
end
state.first_suffixes = remove_bare_hyphens(pair.firsts)
state.second_suffixes = remove_bare_hyphens(pair.seconds)
if #state.first_suffixes == 0 and #state.second_suffixes == 0 then
parse_err("Need at least one perfective or imperfective suffix")
end
return nil
end
local first_aspect, second_aspect = get_aspects(args)
if pair.prefix then
-- A single prefix; combine with all template suffixes.
if not state.first_suffixes then
parse_err(
("Saw prefix '%s' with no preceding template suffixes (line beginning with *)"):format(pair.prefix.term)
)
end
pair.prefix.term = rsub(pair.prefix.term, "%-$", "")
if pair.prefix.tr then
pair.prefix.tr = rsub(pair.prefix.tr, "%-$", "")
end
-- Error on prefix properties we don't know how to handle.
for _, prop in ipairs {"ts", "alt", "genders", "id", "pos", "lit"} do
if pair.prefix[prop] then
parse_err(
("Can't handle property '%s' in prefix '%s'"):format(prop, pair.prefix.term))
end
end
local function prefix_template_suffixes(prefix, terms, aspect)
local retval = {}
for _, term in ipairs(terms) do
term = m_table.shallowCopy(term)
for _, prop in ipairs {"ts", "alt"} do
if term[prop] then
parse_err(
("For aspect=%s, can't handle property '%s' in suffix '%s' when combining with prefix"):
format(aspect, prop, term.term))
end
end
term.term, term.tr = lang_module.paste_prefix_suffix(lang_module.lang, prefix.term, prefix.tr,
term.term, term.tr, aspect)
insert(retval, term)
end
return retval
end
-- Do the prefixing.
pair.firsts = prefix_template_suffixes(pair.prefix, state.first_suffixes, first_aspect)
pair.seconds = prefix_template_suffixes(pair.prefix, state.second_suffixes, second_aspect)
-- Now propagate t= (goes into 'gloss') and qq=/ll=/ref= to the last resulting term, and q=/l=/ref= to the
-- first resulting term.
local last_term
if #pair.seconds > 0 then
last_term = pair.seconds[#pair.seconds]
else
last_term = pair.firsts[#pair.firsts]
end
last_term.qq = combine_decorations(last_term.qq, pair.prefix.qq)
last_term.ll = combine_decorations(last_term.ll, pair.prefix.ll)
last_term.refs = combine_decorations(last_term.refs, pair.prefix.refs)
if last_term.gloss and pair.prefix.gloss then
parse_err(("Can't override gloss '%s' of term '%s' with gloss '%s' of prefix '%s'"):
format(last_term.gloss, last_term.term, pair.prefix.gloss, pair.prefix.term))
elseif pair.prefix.gloss then
last_term.gloss = pair.prefix.gloss
end
local first_term
if #pair.firsts > 0 then
first_term = pair.firsts[1]
else
first_term = pair.seconds[1]
end
first_term.q = combine_decorations(first_term.q, pair.prefix.q)
first_term.l = combine_decorations(first_term.l, pair.prefix.l)
-- FIXME: Is this correct?
first_term.refs = combine_decorations(first_term.refs, pair.prefix.refs)
else
local function handle_aspect_terms(terms, template_suffixes, aspect)
local retval = {}
for i, term in ipairs(terms) do
if term.term ~= "-" then
if term.term:find("%-$") or term.term == "" then
-- prefix to add to corresponding template suffix
if #template_suffixes < i then
local numsuf = #template_suffixes
parse_err(
("For aspect=%s, term #%s=%s is a prefix but there %s only %s corresponding template suffix%s"):
format(aspect, i, term.term, numsuf == 1 and "is" or "are", numsuf,
numsuf == 1 and "" or "es"))
end
-- Fetch suffix; clone because we are modifying it destructively and may reuse it later for
-- another prefix.
local newterm = m_table.shallowCopy(template_suffixes[i])
-- Don't know how to combine ts= or alt= values.
for _, prop in ipairs {"ts", "alt"} do
if newterm[prop] or term[prop] then
parse_err(
("For aspect=%s, can't handle property '%s' in prefix '%s' or suffix '%s' when combining them"):
format(aspect, prop, term.term, newterm.term))
end
end
-- Combine term and translit, along with decorations (qualifiers, labels, and references) and
-- brackets.
newterm.term, newterm.tr =
lang_module.paste_prefix_suffix(lang_module.lang, rsub(term.term, "%-$", ""),
term.tr and rsub(term.tr, "%-$", "") or nil, newterm.term, newterm.tr, aspect)
newterm.q = combine_decorations(newterm.q, term.q)
newterm.qq = combine_decorations(newterm.qq, term.qq)
newterm.l = combine_decorations(newterm.l, term.l)
newterm.ll = combine_decorations(newterm.ll, term.ll)
newterm.refs = combine_decorations(newterm.refs, term.refs)
newterm.brackets = newterm.brackets or term.brackets
-- Remaining properties are copied from prefix to suffix if not already in suffix.
for _, prop in ipairs {"gloss", "genders", "id", "pos", "lit"} do
if newterm[prop] and term[prop] then
parse_err(
("For aspect=%s, can't handle property '%s' occurring along with both prefix '%s' and suffix '%s' when combining them"):
format(aspect, prop, term.term, newterm.term))
end
if term[prop] then
newterm[prop] = term[prop]
end
end
insert(retval, newterm)
else
insert(retval, term)
end
end
end
return retval
end
pair.firsts = handle_aspect_terms(pair.firsts, state.first_suffixes, first_aspect)
pair.seconds = handle_aspect_terms(pair.seconds, state.second_suffixes, second_aspect)
end
if #pair.firsts == 0 and #pair.seconds == 0 then
parse_err("Need at least one perfective or imperfective term")
end
return pair
end
local function parse_args(lang, args)
local lang_module = {lang = lang}
if lang:getCode() == "ru" then
lang_module.paste_prefix_suffix = ru_paste_prefix_suffix
else
lang_module.paste_prefix_suffix = default_paste_prefix_suffix
end
local state = {}
local groups = {}
local group = {}
for i, arg in ipairs(args[1]) do
local pair = parse_aspect_pair(arg, i, state, lang_module, args)
if pair == false then
if #group == 0 then
error("No items in group terminated by single hyphen in arg #" .. i)
end
insert(groups, group)
group = {}
elseif pair then
insert(group, pair)
end
end
if #group > 0 then
insert(groups, group)
end
return groups
end
local function format_aspect_terms(lang, args, term_groups, include_default_aspect)
local all_formatted_items = {}
for _, group in ipairs(term_groups) do
local group_formatted_items = {}
for _, items in ipairs(group) do
local sort_key = nil
local this_include_default_aspect = include_default_aspect
local function handle_aspect_terms(terms, aspect)
local term_parts = {}
for _, term in ipairs(terms) do
sort_key = sort_key or (lang:makeSortKey(lang:stripDiacritics(term.term)))
if not term.genders and this_include_default_aspect then
term.genders = {aspect}
end
term.lang = lang
-- We could use show_decorations except for the brackets that may need to be added.
term.show_decorations = false
local linked_term = m_links.full_link(term)
if term.brackets then
linked_term = "[" .. linked_term .. "]"
end
if term.q and term.q[1] or term.qq and term.qq[1] or term.l and term.l[1] or
term.ll and term.ll[1] or term.refs and term.refs[1] then
linked_term = m_decorations.format_decorations {
lang = lang,
text = linked_term,
q = term.q,
qq = term.qq,
l = term.l,
ll = term.ll,
refs = term.refs,
}
end
insert(term_parts, linked_term)
end
return concat(term_parts, ", ")
end
local first_aspect, second_aspect = get_aspects(args)
local switch_aspects
for _, term in ipairs(items.firsts) do
if term.genders and #term.genders == 1 and term.genders[1] == second_aspect then
switch_aspects = true
break
end
end
if switch_aspects then
this_include_default_aspect = true
local temp = first_aspect
first_aspect = second_aspect
second_aspect = temp
end
local firsts = handle_aspect_terms(items.firsts, first_aspect)
local seconds = handle_aspect_terms(items.seconds, second_aspect)
insert(group_formatted_items, {
firsts = firsts,
seconds = seconds,
sort_key = sort_key
})
end
table.sort(group_formatted_items, function(a, b) return a.sort_key < b.sort_key end)
for _, formatted_item in ipairs(group_formatted_items) do
insert(all_formatted_items, formatted_item)
end
end
return all_formatted_items
end
local function format_terms_as_list(lang, args, formatted_items)
for i, formatted_item in ipairs(formatted_items) do
if formatted_item.firsts == "" then
formatted_items[i] = formatted_item.seconds
elseif formatted_item.seconds == "" then
formatted_items[i] = formatted_item.firsts
else
formatted_items[i] = formatted_item.firsts .. ", " .. formatted_item.seconds
end
end
return require("Module:columns").create_list {
header = "動詞",
title_new_style = true,
format_header = true,
content = formatted_items,
lang = lang,
class = "columns-bg",
column_count = args.ncol,
collapse = true,
}
end
local function format_terms_as_table(lang, args, formatted_items)
local lines = {}
local first_aspect_header, second_aspect_header
if args.impf_first then
first_aspect_header = "非完整體"
second_aspect_header = "完整體"
else
first_aspect_header = "完整體"
second_aspect_header = "非完整體"
end
insert(lines, '{| class="wikitable vsSwitcher" data-toggle-category="derived terms"\n! ' ..
first_aspect_header .. ' !! class="vsToggleElement" | ' .. second_aspect_header)
for _, formatted_item in ipairs(formatted_items) do
insert(lines, '|- class="vsHide"\n| ' .. formatted_item.firsts .. " || " ..
formatted_item.seconds)
end
insert(lines, "|}")
return concat(lines, "\n")
end
function export.imperfectives_and_perfectives(frame)
local process = require("Module:parameters").process
local iargs = process(frame.args, {
["lang"] = {required = true, type = "language"},
["format"] = {default = "list"},
})
local args = process(frame:getParent().args, {
[1] = {list = true},
["format"] = true,
["ncol"] = {default = 2, type = "number"},
["impf_first"] = {type = "boolean"},
})
local format = args.format or iargs.format
local lang = iargs.lang
if format ~= "list" and format ~= "table" then
error(("Unrecognized format '%s'; possible values are 'list', 'table'"):format(format))
end
local groups = parse_args(lang, args)
local formatted_items = format_aspect_terms(lang, args, groups, format == "list")
return format == "list" and format_terms_as_list(lang, args, formatted_items) or
format_terms_as_table(lang, args, formatted_items)
end
return export
r4hjjpihe6ji55s6tjbvyankp5zgncp
Module:Fa-IPA
828
2076662
9909062
9308963
2026-09-29T07:25:37Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909062
Scribunto
text/plain
--[=[
FIXME:
1. (ir) q and ğ should both be ɣ intervocally (āqā should give ɒːɣɒː)
2. (prs) disable auto lowering of long vowels before /h/ & /ʔ/, (causes too many issues).
3. (tg, prs) change /q/ to /ɢ/ before a voiced consonant
4. (cls) prevent the appearance of β after a final consonant (e.g -atb should not give β)
-- see [[Template talk:fa-IPA#ð in Classical Persian]]
5. (cls) fix geminated β and ð should be a normal b and d
6. FIXED // (ir) [[محوطه]] gives [mo.ɦav.væ.t̪ʰé] instead of [mo.ɦæv.væ.t̪ʰé]
7. FIXED (all except cls) final geminates such as [[خط]] should transcribe as [xat(ː)]
8. (ir) add support for Shirazi dialect
9. (kbl, haz) [[متأسف]] should be /mutāssif/
10. (ir) [[مقید]] should be /mo.ʁæj.jǽd̪/ (without a second parameter)
11. support disabling a dialect with '-'. e.g. [[نشریه]] should not generate classical
]=]
local export = {}
local m_str_utils = require("Module:string utilities")
local U = m_str_utils.char
local lang = require("Module:languages").getByCode("fa")
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local m_qual = require("Module:qualifier")
local all_consonants = "bptTjčhxdDðrzžsšʔʾğGfqkglmnŋhɦwvy'" --needed for syllables
local stop_cons = "bptTjčdDðqkg"
local non_stopc = "hxrzžsšʔğGflmhɦwvy'"
local rsplit = m_str_utils.split
local rsubn = m_str_utils.gsub
local toNFC = mw.ustring.toNFC
local ulen = m_str_utils.len
local usub = m_str_utils.sub
local pitchaccent = U(0x301)
local devoice = U(0x325)
local dtack = U(0x31E)
local gstop = U(0x027)
local dental = U(0x32A)
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
export.all_styles = {"cls", "prs", "kbl", "haz", "fa", "teh", "tg"}
export.all_style_groups = {
all = export.all_styles,
cls = {"cls"},
dari = {"prs", "kbl", "haz"},
ir = {"fa", "teh"},
tg = {"tg"}
}
export.all_style_descs = {
cls = "Classical Persian",
prs = "Dari Persian",
kabul = "Kabuli",
haz = "Hazaragi",
fa = "Iranian Persian",
teh = "Tehrani",
tg = "Tajik"
}
local function flatmap(items, fun)
local new = {}
for _, item in ipairs(items) do
local results = fun(item)
for _, result in ipairs(results) do
table.insert(new, result)
end
end
return new
end
local common_consonants = {
["j"] = "d͡ʒ",
["'"] = "ʔ",
["ḍ"] = "z",
["D"] = "d",
--these are here for Hazaragi
["T"] = "t",
--they are retroflexes in haz
["ğ"] = "ɣ",
["ġ"] = "ɣ",
["ḥ"] = "h",
["r"] = "ɾ",
["ṣ"] = "s",
["š"] = "ʃ",
["ṯ"] = "s",
["ṭ"] = "t",
["y"] = "j",
["ž"] = "ʒ",
["ẓ"] = "z",
["č"] = "t͡ʃ",
["g"] = "ɡ",
["`"] = "ˈ"
}
local iranian_persian_short_vowels = {["a"] = "æ", ["i"] = "e", ["u"] = "o"}
local iranian_persian_long_vowels = {
["ā"] = "ɒː",
["ī"] = "iː",
["ū"] = "uː",
["ō"] = "uː",
["ē"] = "iː"
}
local iranian_persian_consonants = {["ḏ"] = "z", ["q"] = "ɢ", ["ğ"] = "ɢ", ["ġ"] = "ɢ", ["k"] = "c", ["g"] = "ɟ"}
local dari_persian_short_vowels = {["a"] = "ä", ["i"] = "ɪ", ["u"] = "ʊ"}
local dari_persian_long_vowels = {
["ā"] = "ɑː",
["ī"] = "iː",
["ū"] = "uː",
["ō"] = "oː",
["ē"] = "eː"
}
local dari_persian_consonants = {["ḏ"] = "z", ["v"] = "w"}
local tajik_short_vowels = {["a"] = "ä", ["i"] = "i", ["u"] = "u"}
local tajik_long_vowels = {
["ā"] = "ɔ",
["ī"] = "i",
["ū"] = "u",
["ō"] = "ɵ",
["ē"] = "e"
}
local tajik_vowels = "aieuɵɔ"
local tajik_consonants = {["ḏ"] = "z", ["ɣ"] = "ʁ", ["x"] = "χ"}
local classical_persian_short_vowels = {["a"] = "a", ["i"] = "i", ["u"] = "u"}
local classical_persian_long_vowels = {
["ā"] = "aː",
["ī"] = "iː",
["ū"] = "uː",
["ō"] = "oː",
["ē"] = "eː"
}
local classical_persian_consonants = {["ḏ"] = "ð", ["v"] = "w"}
local vowels_minus_a = "iuāīūüēōːʷ"
local vowels = "aiuāīūüēōːʷ"
--ʷ and ː are counted as vowels to prevent them from being put in the next syllable
local consonant = "[^" .. vowels .. ". -]"
local vowel = "[" .. vowels .. "]"
local syllabify_pattern = "(" .. vowel .. ")(" .. consonant .. ")(" .. consonant .. "?)(" .. vowel .. ")"
local function syllabify(text)
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "%-(" .. consonant .. ")%-(" .. consonant .. ")", "%1.%2")
text = rsubn(text, "([" .. all_consonants .. vowels .. "])`", "%1.`")
-- Add syllable breaks.
for _ = 1, 2 do
text =
rsubn(
text,
syllabify_pattern,
function(a, b, c, d)
if c == "" and b ~= "" then
c, b = b, ""
end
return a .. b .. "." .. c .. d
end
)
end
-- syllable boundry consonants
text =
rsubn(
text,
"([" .. non_stopc .. "])([" .. stop_cons .. "])([" .. non_stopc .. "]+ʷ?)([" .. all_consonants .. vowels .. "])",
"%1%2.%3%4"
)
text = rsubn(text, "([" .. all_consonants .. "])([" .. all_consonants .. "])([" .. all_consonants .. "])", "%1%2.%3")
-- ALL syllables are CV- so vowels NEED an intial consonant (ʔ)
text = rsubn(text, "([" .. all_consonants .. "])([.])i#", "%1%2i#") --exclude izafa/ezafe
text = rsubn(text, "([.])([" .. vowels .. "])", "%1ʔ%2")
text = rsubn(text, "#([" .. vowels .. "])", "#ʔ%1")
return text
end
local function remove_glottal_c(text) --only for regional dialects
-- remove glottal consonants with appropriate glide
text = rsubn(text, "([aā](%.?))([hɦ'])([uū])", "%1w%4")
text = rsubn(text, "([iī])(%.?)([hɦ'])([auāēōū])", "i%2y%4")
text = rsubn(text, "([auāēōū](%.?))([hɦ'])([iīē])", "%1y%4")
text = rsubn(text, "([ē](%.?))([hɦ'])([auāēōū])", "%1y%4")
text = rsubn(text, "([uū])(%.?)([hɦ'])([aāiīēō])", "u%2w%4")
text = rsubn(text, "([ō](%.?))([hɦ'])([aāiīēō])", "%1w%4")
--completely delete GC if both vowels are the either the same or similar
text = rsubn(text, "([" .. all_consonants .. "])([uū])`([h'])([uū])", "`%1ū")
text = rsubn(text, "([" .. all_consonants .. "])([iī])`([h'])([iī])", "`%1ī")
text = rsubn(text, "([" .. all_consonants .. "])([aā])`([h'])([aā])", "`%1ā")
--Else, turn GC into majhul long vowels
text = rsubn(text, "([aā])((%.?)[h'])", "ā")
text = rsubn(text, "([iī])((%.?)['])", "ē")
text = rsubn(text, "([uū])((%.?)['])", "ō")
text = rsubn(text, "(['h])", "")
-- lastly, remove all remaning GC
return text
end
local function con_assimilation(text) --DONT USE THIS ON CLASSICAL
-- assimilation/placement of certain consonants
text = rsubn(text, "l((%.?)[ʈɖ])", "ɭ%1") --retroflexes are only in hazaragi
text = rsubn(text, "([nl])((%.?)[td])", "%1" .. dental .. "%2")
text = rsubn(text, "n((%.?)[ʈɖ])", "ɳ%1")
text = rsubn(text, "([td])", "%1" .. dental .. "")
text = rsubn(text, "n((%.?)[kg])", "ŋ%1")
text = rsubn(text, "n((%.?)[cɟy])", "ɲ%1")
text = rsubn(text, "n((%.?)[bp])", "m%1")
text = rsubn(text, "n((%.?)[qɢ])", "ɴ%1")
text = rsubn(text, "([nm])((%.?)[fv])", "ɱ%2")
text = rsubn(text, "([āʌɑɒäæeēīioɔōuūʊɪ](%" .. dtack .. "?)(%" .. pitchaccent .. "?)(%ː?)(%.?))([h])", "%1ɦ")
text = rsubn(text, "r([tdszšlž])", "ɹ%1")
-- formally, f only assimiates in the same syllable
text = rsubn(text, "f([bjdžğġ])", "v%1")
text = rsubn(text, "ɾ(%.?)ɾ", "#r%1r")
text = rsubn(text, "ä(" .. pitchaccent .. ")", "æ%1")
text = rsubn(text, "([ɦ])#", "ʱ#")
text = rsubn(text, "([h])#", "ʰ#")
text = rsubn(text, "([%a]" .. dental .. "?" .. devoice .. "?)%1(" .. devoice .. "?)#", "%1%2(ː)#")
return text
end
function export.fa_IPA(text)
text = rsubn(text, "a[-]([" .. all_consonants .. "])[-]", "a%1")
text = rsubn(text, "a%-", "e-")
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "^%-", "‿")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "%-i#", "i#")
text = rsubn(text, "[-]([" .. vowels .. all_consonants .. "])[-]", "%1")
text = rsubn(text, "[-]", ".")
text = rsubn(text, "v", "w")
-- Replace xwa with xu
text = rsubn(text, "([#%-])xwa", "%1xu")
-- Replace xwā with xā
text = rsubn(text, "([#%-])xwā", "%1xā")
-- Replace xwē with xē
text = rsubn(text, "([#%-])xwē", "%1xē")
text = rsubn(text, "w(" .. vowel .. ")", "v%1")
text = rsubn(text, "w(" .. consonant .. ")", "w%1")
text = rsubn(text, "([" .. vowels_minus_a .. "])w", "%1v")
text = rsubn(text, "(" .. consonant .. ")w#", "%1v#")
text = rsubn(text, "([iī]y(%`?))(%.?)y", "i%2y")
text = rsubn(text, "v%(w", "v(v")
-- Replace diphthong
text =
rsubn(
text,
"a([wy])()",
function(semivowel, position)
local consonant = usub(text, position, position)
if consonant == "" or consonant:find(consonant) then
if semivowel == "w" then
return "uw"
else
return "ey"
end
end
end
)
--automatically denote syllables
text = syllabify(text)
text = rsubn(text, "%%", "#")
-- then do pitch accent mark
text = rsubn(text, "`([" .. all_consonants .. "])([" .. vowels .. "])", "%1%2" .. pitchaccent .. "")
text = rsubn(text, "`([" .. vowels .. "])", "%1" .. pitchaccent .. "")
text = rsubn(text, "([iī])(" .. pitchaccent .. "?)(%.?)y", "E%2%3y")
text = rsubn(text, "([ptkč](%" .. dental .. "?))([" .. vowels .. "])", "%1ʰ%3")
text = rsubn(text, "([" .. vowels .. "](%.?))q", "%1ʁ")
-- Replace final a with e (can be overwritten by entering æ)
text = rsubn(text, "([a])(%" .. pitchaccent .. "?)#", "e%2#")
-- Replace short vowels
text = rsubn(text, ".", iranian_persian_short_vowels)
-- Replace long vowels
text = rsubn(text, ".", iranian_persian_long_vowels)
text = rsubn(text, "E(" .. pitchaccent .. "?)(%.?)y", "i%1%2y")
-- Replace jj with dj
text = rsubn(text, "jj", "dj")
-- Replace čč with tč
text = rsubn(text, "čč", "tč")
-- Replace owv- with avv-
text = rsubn(text, "owv", "ævv")
text = rsubn(text, "ow.v", "æv.v")
-- Allephones
text = rsubn(text, "([gbdjl](%" .. dental .. "?))#", "%1" .. devoice .. "#")
-- Replace consonants
text = rsubn(text, ".", iranian_persian_consonants)
text = rsubn(text, "cʰ([ɒuo])", "kʰ%1")
text = rsubn(text, "ɟ(%" .. devoice .. "?)([ɒuo])", "g%1%2")
text = con_assimilation(text)
text = rsubn(text, "#(g)", "%1" .. devoice .. "")
text = rsubn(text, ".", common_consonants)
text = rsubn(text, "ɾ", "ɹ")
--fix the pitch accent on long vowels
text = rsubn(text, "([ɒiu])ː" .. pitchaccent .. "", "%1" .. pitchaccent .. "ː")
text = rsubn(text, "([ɢʁ])(%.?)t", "x%2t")
text = rsubn(text, "([ʁɢ])#", "ɢ" .. devoice .. "#")
text = rsubn(text, "#([ʁɢ])", "#q")
text = rsubn(text, "ʁɢ", "ɢɢ")
text = rsubn(text, "#g", "#k")
text = rsubn(text, "c", "kʲ")
text = rsubn(text, "ɟ", "ɡʲ")
text = rsubn(text, "ʲʰ", "ʰʲ")
text = rsubn(text, "#", "")
text = toNFC(text)
return text
end
function export.prs_IPA(text) --based on formal speech URBAN kabul
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "^%-", "‿")
text = rsubn(text, "%%", "")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "%-i#", "i#")
text = rsubn(text, "[-]([" .. vowels .. all_consonants .."])[-]", "%1")
text = rsubn(text, "[-]", ".")
text = rsubn(text, "v", "w")
-- Replace xwa with xu
text = rsubn(text, "xwa", "xu")
-- Replace xwā with xā
text = rsubn(text, "xwā", "xā")
-- Replace xwē with xē
text = rsubn(text, "xwē", "xē")
text = rsubn(text, "xwē", "xē")
text = rsubn(text, "([iī](%`?))(%.?)y", "i%2y")
-- Iranian and Classical dictionaries list -iyy,
-- But Tajik and Dari ones dont
text = rsubn(text, "([iī]y(%`?))(%.?)y", "i%2y")
--automatically denote syllables
text = syllabify(text)
--pitch accent mark
text = rsubn(text, "`([" .. all_consonants .. "])([ʷ]?)([" .. vowels .. "])", "%1%2%3" .. pitchaccent .. "")
text = rsubn(text, "`([" .. vowels .. "])", "%1" .. pitchaccent .. "")
text = rsubn(text, "([ptkč])([" .. vowels .. "])", "%1ʰ%2")
-- Replace ih, īh, i\', ī\' by ēh, ē\'
text = rsubn(text, "i((%.?)[ɦh'])", "e" .. dtack .. "%1")
-- false positives text = rsubn(text, "ī((%.?)[ɦh'])", "ē%1")
-- Replace uh, ūh, u\', ū\' by ɵh, ɵ\'
text = rsubn(text, "u((%.?)[hɦ'])", "o" .. dtack .. "%1")
-- false positives text = rsubn(text, "ū((%.?)[hɦ'])", "ō%1")
-- Replace short vowels
text = rsubn(text, ".", dari_persian_short_vowels)
-- Replace long vowels
text = rsubn(text, ".", dari_persian_long_vowels)
-- Replace jj with dj
text = rsubn(text, "jj", "dj")
-- Replace čč with tč
text = rsubn(text, "čč", "tč")
--fix the pitch accent on long vowels
text = rsubn(text, "([ʌɑeiou])ː" .. pitchaccent .. "", "%1" .. pitchaccent .. "ː")
-- Allephones
text = con_assimilation(text)
-- Replace consonants
text = rsubn(text, ".", common_consonants)
text = rsubn(text, "f([bjdžğġ])", "v%1")
text = rsubn(text, ".", dari_persian_consonants)
text = rsubn(text, "ɾ(%.?)ɾ", "r%1r")
text = rsubn(text, "ɪ(" .. pitchaccent .. "?)(%.?)j", "i%1%2j")
text = rsubn(text, "#ɾ", "#r")
text = rsubn(text, "#", "")
text = toNFC(text)
return text
end
function export.prs_kbl_IPA(text) --Colloquial dialect of Kabul
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "^%-", "‿")
text = rsubn(text, "%%", "")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "%-i#", "i#")
text = rsubn(text, "[-]([" .. vowels .. all_consonants .."])[-]", "%1")
text = rsubn(text, "[-]", ".")
-- Verb stem changes
text = rsubn(text, "#kar(`?)dan#", "#ka%1dan#")
text = rsubn(text, "v", "w")
-- Replace xwa with xu
text = rsubn(text, "xwa", "xu")
-- Replace xwā with xā
text = rsubn(text, "xwā", "xā")
-- Replace xwē with xē
text = rsubn(text, "xwē", "xē")
text = rsubn(text, "xwē", "xē")
text = rsubn(text, "([iī](%`?))(%.?)y", "i%2y")
-- Iranian and Classical dictionaries list -iyy,
-- But Tajik and Dari ones dont
text = rsubn(text, "([iī]y(%`?))(%.?)y", "i%2y")
--pitch accent mark
text = rsubn(text, "`([" .. all_consonants .. "])([" .. vowels .. "])", "%1%2" .. pitchaccent .. "")
text = rsubn(text, "`([" .. vowels .. "])", "%1" .. pitchaccent .. "")
-- remove glottal consonants for some dialects
text = remove_glottal_c(text)
--automatically denote syllables
text = syllabify(text)
-- dipthong <ōy> /oːj/ is lost in closed syllables
text = rsubn(text, "ōy", "ūy")
-- universal aspiration
text = rsubn(text, "([ptkč])([" .. vowels .. "])", "%1ʰ%2")
-- Replace short vowels
text = rsubn(text, ".", dari_persian_short_vowels)
-- Replace long vowels
text = rsubn(text, ".", dari_persian_long_vowels)
-- Replace jj with dj
text = rsubn(text, "jj", "dj")
text = rsubn(text, "āw", "aw") -- lost colloquially
-- Replace čč with tč
text = rsubn(text, "čč", "tč")
-- Allephones
text = con_assimilation(text)
-- Replace consonants
text = rsubn(text, ".", dari_persian_consonants)
text = rsubn(text, "f((%.?)[bjdžğġ])", "v%1")
text = rsubn(text, ".", common_consonants)
--fix the pitch accent on long vowels
text = rsubn(text, "([ʌɑeiou])ː" .. pitchaccent .. "", "%1" .. pitchaccent .. "ː")
text = rsubn(text, "ɾ(%.?)ɾ", "#r%1r")
text = rsubn(text, "ɪ(" .. pitchaccent .. "?)(%.?)j", "i%1%2j")
text = rsubn(text, "#ɾ", "#r")
text = rsubn(text, "#", "")
text = toNFC(text)
return text
end
function export.prs_haz_IPA(text) --Hazaragi
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "^%-", "‿")
text = rsubn(text, "%%", "")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "([iī](%`?))(%.?)y", "i%2y")
-- Iranian and Classical dictionaries list -iyy,
-- But Tajik and Dari ones dont
text = rsubn(text, "([iī]y(%`?))(%.?)y", "i%2y")
text = rsubn(text, "%-i#", "i#")
text = rsubn(text, "[-]([" .. vowels .. all_consonants .."])[-]", "%1")
text = rsubn(text, "[-]", ".")
-- Verb stem changes
text = rsubn(text, "#kar(`?)dan#", "#ka%1dā#")
--these conversions need to happen BEFORE EVERYTHING ELSE
--pitch accent mark
text = rsubn(text, "`([" .. all_consonants .. "])([" .. vowels .. "])", "%1%2" .. pitchaccent .. "")
text = rsubn(text, "`([" .. vowels .. "])", "%1" .. pitchaccent .. "")
--Vowel Harmony
text = rsubn(text, "ē(" .. pitchaccent .. "?)([" .. all_consonants .. "])([ūiī])", "%3%1%2%3")
text =
rsubn(text, "ē(" .. pitchaccent .. "?)([" .. all_consonants .. "])([" .. all_consonants .. "])([ī])", "%4%1%2%3%4")
text = rsubn(text, "i(" .. pitchaccent .. "?)([" .. all_consonants .. "])([ouū])", "%3%1%2%3")
text = rsubn(text, "ī(" .. pitchaccent .. "?)([" .. all_consonants .. "])([ēōuūiī])", "%3%1%2%3")
text = rsubn(text, "ō(" .. pitchaccent .. "?)([" .. all_consonants .. "])([uū])", "%3%1%2%3")
text = rsubn(text, "ō(" .. pitchaccent .. "?)([" .. all_consonants .. "])([i])", "u%1%2%3")
text = rsubn(text, "ō(" .. pitchaccent .. "?)([" .. all_consonants .. "])([ī])", "ū%1%2%3")
-- Replace xwa with xu
text = rsubn(text, "xwa", "xu")
-- Replace xwā with xā
text = rsubn(text, "xwā", "xā")
-- Replace xwē with xē
text = rsubn(text, "xwē", "xē")
text = rsubn(text, "xwē", "xē")
text = rsubn(text, "v", "w")
-- remove glottal consonants for some dialects
text = remove_glottal_c(text)
--automatically denote syllables
text = syllabify(text)
-- universal aspiration
text = rsubn(text, "([ptkč])([" .. vowels .. "])", "%1ʰ%2")
--delete certain consonant clusters and dipthongs
text = rsubn(text, "āy", "ay")
text = rsubn(text, "āw", "aw")
--retroflex consonants
text = rsubn(text, "D", "ɖ")
text = rsubn(text, "T", "ʈ")
-- Replace short vowels
-- no consistent vowel length for i or u
text = rsubn(text, "ī", "i")
text = rsubn(text, "ū", "u")
text = rsubn(text, ".", classical_persian_short_vowels)
--approximate vowels
text = rsubn(text, "ā", "ɔː")
text = rsubn(text, "ō", "ʊː")
text = rsubn(text, "a", "ä")
-- Replace long vowels
text = rsubn(text, ".", dari_persian_long_vowels)
--fix the pitch accent on long vowels
text = rsubn(text, "([ɔeiʊu])ː" .. pitchaccent .. "", "%1" .. pitchaccent .. "ː")
-- Replace jj with dj
text = rsubn(text, "jj", "dj")
-- Replace čč with tč
text = rsubn(text, "čč", "tč")
--allophones
text = con_assimilation(text)
text = rsubn(text, "([gbdjl](%" .. dental .. "?))#", "%1" .. devoice .. "#")
--Terminal voicing in not phonetic
text = rsubn(text, "([gbdj])#", "%1" .. devoice .. "#")
-- Replace consonants
text = rsubn(text, ".", dari_persian_consonants)
text = rsubn(text, "f((%.?)[bjdžğġ])", "v%1")
text = rsubn(text, ".", common_consonants)
text = rsubn(text, "ɾ(%.?)ɾ", "#r%1r")
text = rsubn(text, "#ɾ", "#r")
text = rsubn(text, "#", "")
text = toNFC(text)
return text
end
function export.tg_IPA(text)
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "^%-", "‿")
text = rsubn(text, "%%", "")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "%-i#", "i#")
-- Iranian and Classical dictionaries list -iyy,
-- But Tajik and Dari ones dont
text = rsubn(text, "([iī]y(%`?))(%.?)y", "i%2y")
text = rsubn(text, "[-]([" .. vowels .. all_consonants .."])[-]", "%1")
text = rsubn(text, "[-]", ".")
text = rsubn(text, "v", "w")
-- Replace xwa with xu
text = rsubn(text, "xwa", "xu")
-- Replace xwā with xā
text = rsubn(text, "xwā", "xā")
-- Replace xwē with xē
text = rsubn(text, "xwē", "xē")
text = rsubn(text, "xwē", "xē")
--automatically denote syllables
text = syllabify(text)
--pitch accent mark
text = rsubn(text, "`([" .. all_consonants .. "])([" .. vowels .. "])", "%1%2" .. pitchaccent .. "")
text = rsubn(text, "`([" .. vowels .. "])", "%1" .. pitchaccent .. "")
-- Replace jj with dj
text = rsubn(text, "jj", "dj")
-- Replace čč with tč
text = rsubn(text, "čč", "tč")
text = rsubn(text, "w([" .. vowels .. "])", "v%1")
-- universal aspiration
text = rsubn(text, "([ptkč])([" .. vowels .. "])", "%1ʰ%2")
-- lower short vowels before glottals
-- dont do this for long vowels as it causes false positives
text = rsubn(text, "([i])(%" .. pitchaccent .. "?)([h'ʔɦ])([^" .. tajik_vowels .. "])", "e%2%3%4")
-- Replace uh, ūh, u\', ū\' by ɵh, ɵ\'
text = rsubn(text, "([u])(%" .. pitchaccent .. "?)([hʔ'ɦ])([^" .. tajik_vowels .. "])", "ɵ%2%3%4")
--aspiration
text = rsubn(text, "([ptkč](%" .. dental .. "?))([" .. vowels .. "])", "%1ʰ%3")
-- Replace short vowels
text = rsubn(text, ".", tajik_short_vowels)
-- Replace long vowels
text = rsubn(text, ".", tajik_long_vowels)
--allophones
text = con_assimilation(text)
text = rsubn(text, "c", "t͡s")
-- Replace consonants
text = rsubn(text, ".", common_consonants)
text = rsubn(text, ".", tajik_consonants)
text = rsubn(text, "([aä])(" .. pitchaccent .. ")", "æ%2")
text = rsubn(text, "#", "")
text = toNFC(text)
return text
end
function export.fa_cls_IPA(text)
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "^%-", "‿")
text = rsubn(text, "%%", "")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "([iī](%`?))(%.?)y", "i%2y")
text = rsubn(text, "%-i#", "i#")
text = rsubn(text, "[-]([" .. vowels .. all_consonants .."])[-]", "%1")
text = rsubn(text, "[-]", ".")
text = rsubn(text, "v", "w")
-- Replace xwa with xʷa
text = rsubn(text, "xwa", "xʷa")
-- Replace xwā with xʷā
text = rsubn(text, "xwā", "xʷā")
-- Replace xwē with xʷē
text = rsubn(text, "xwē", "xʷē")
--automatically denote syllables
text = syllabify(text)
-- Replace short vowels
text = rsubn(text, ".", classical_persian_short_vowels)
-- Replace d with ḏ after vowels
text = rsubn(text, "([" .. vowels .. "]+.?)(%`?)([d])", "%1%2ḏ")
text = rsubn(text, "([" .. vowels .. "]+.?)(%`?)([b])", "%1%2β")
-- Replace long vowels
text = rsubn(text, ".", classical_persian_long_vowels)
-- Replace jj with dj
text = rsubn(text, "jj", "dj")
-- Replace čč with tč
text = rsubn(text, "čč", "tč")
-- Replace consonants
text = rsubn(text, ".", common_consonants)
text = rsubn(text, ".", classical_persian_consonants)
text = rsubn(text, "#", "")
text = toNFC(text)
return text
end
-- ROMANIZATIONS
function export.romanize_fa_cls(text, script, options)
if type(text) == "table" then
options = {}
text, script = text.args[1], text.args[2]
end
text = rsubn(text, "%%", "")
text = rsubn(text, "^" .. "[-]", "\1")
text = rsubn(text, "`", "")
text = rsubn(text, "ˈ", "")
text = rsubn(text, "[,]", ", ")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "([iī])(%.?)y", "iy")
--kill incorrect characters
text = rsubn(text, "([" .. dental .. pitchaccent .. devoice .. dtack .. "ʰ])", "")
text = rsubn(text, "([ɴŋ])", "n")
--remove v
text = rsubn(text, "v", "w")
--ensure vowels are paired to a consonant
text = rsubn(text, "([.])([" .. vowels .. "])", "%1'%2")
text = rsubn(text, "([.])", "")
text = rsubn(text, "([" .. vowels .. "])([dḍ])", "%1ḏ")
text = rsubn(text, "([" .. vowels .. "](%-?))b", "%1ḇ")
text = rsubn(text, "ḏ", "ḏ")
text = rsubn(text, "ḏd", "dd")
text = rsubn(text, "ḇb", "bb")
text = rsubn(text, "ḍ", "z")
text = rsubn(text, "ṭ", "t")
text = rsubn(text, "ṯ", "s")
text = rsubn(text, "ṣ", "s")
text = rsubn(text, "ḥ", "h")
-- remove Hazaragi retroflexes
text = rsubn(text, "D", "d")
text = rsubn(text, "T", "t")
text = rsubn(text, "ɖ", "d")
text = rsubn(text, "ʈ", "t")
text = rsubn(text, "#'", "#")
text = rsubn(text, "#", "")
text = rsubn(text, "\1", "-")
return text
end
function export.romanize_prs(text, script, options)
if type(text) == "table" then
options = {}
text, script = text.args[1], text.args[2]
end
text = rsubn(text, "^" .. "[-]", "\1")
text = rsubn(text, "%%", "")
text = rsubn(text, "`", "")
text = rsubn(text, "ˈ", "")
text = rsubn(text, "[,]", ", ")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "([iī](%.?))y", "i%2y")
text = rsubn(text, "([iī]y(%.?))y", "i%2y")
text = rsubn(text, "i(['h])", "e%1")
text = rsubn(text, "u(['h])", "o%1")
--kill incorrect characters
text = rsubn(text, "([" .. dental .. pitchaccent .. devoice .. dtack .. "ʰ])", "")
text = rsubn(text, "([ɴŋ])", "n")
text = rsubn(text, "v", "w")
-- Replace xw clusters
text = rsubn(text, "xw([āē])", "x%1")
text = rsubn(text, "xwa", "xu")
-- for rare exceptions
text = rsubn(text, "ʷ", "w")
--ensure vowels are paired to a consonant
text = rsubn(text, "([.])([" .. vowels .. "])", "%1'%2")
text = rsubn(text, "([.])", "")
text = rsubn(text, "ḍ", "z")
text = rsubn(text, "ḏ", "z")
text = rsubn(text, "ṯ", "s")
text = rsubn(text, "ṭ", "t")
text = rsubn(text, "ṣ", "s")
text = rsubn(text, "ḥ", "h")
-- THIS SHOULD ONLY BE DONE FOR HAZARAGI RETROFLEX ENTRIES
-- THEY SHOULD NEVER APPEAR IN A MAIN ENTRY
text = rsubn(text, "D", "ḍ")
text = rsubn(text, "T", "ṭ")
text = rsubn(text, "ɖ", "ḍ")
text = rsubn(text, "ʈ", "ṭ")
text = rsubn(text, "#'", "#")
-- remove unnecessary marks
text = rsubn(text, "#", "")
text = rsubn(text, "\1", "-")
return text
end
function export.romanize_ira(text, script, options)
if type(text) == "table" then
options = {}
text, script = text.args[1], text.args[2]
end
text = rsubn(text, "^" .. "[-]", "\1")
text = rsubn(text, "`", "")
text = rsubn(text, "ˈ", "")
text = rsubn(text, "%%", "#")
text = rsubn(text, "[,]", ", ")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "([iī]y(%.?))y", "i%2y")
--kill incorrect characters
text = rsubn(text, "([" .. dental .. pitchaccent .. devoice .. dtack .. "ʰ])", "")
text = rsubn(text, "([ɴŋ])", "n")
text = rsubn(text, "v", "w")
-- Replace xw clusters
text = rsubn(text, "([#%-])xw([āē])", "%1x%2")
text = rsubn(text, "([#%-])xwa", "%1xu")
text = rsubn(text, "ʷ", "")
text = rsubn(text, "w(" .. vowel .. ")", "v%1")
text = rsubn(text, "w(" .. consonant .. ")", "w%1")
text = rsubn(text, "([" .. vowels_minus_a .. "])w", "%1v")
text = rsubn(text, "v%(w", "v(v")
text = rsubn(text, "(" .. consonant .. ")w#", "%1v#")
text = rsubn(text, "wv", "vv")
text = rsubn(text, "wæ", "væ")
--ensure vowels are paired to a consonant
text = rsubn(text, "([.])([" .. vowels .. "])", "%1'%2")
text = rsubn(text, "([.])", "")
text = rsubn(text, "iy", "īy")
text = rsubn(text, "ay", "ey")
text = rsubn(text, "aw", "ow")
text = rsubn(text, "ḍ", "z")
text = rsubn(text, "ḏ", "z")
text = rsubn(text, "ṭ", "t")
text = rsubn(text, "q", "ġ")
text = rsubn(text, "ṯ", "s")
text = rsubn(text, "ṣ", "s")
text = rsubn(text, "ḥ", "h")
text = rsubn(text, "ā", "â")
text = rsubn(text, "u", "o")
text = rsubn(text, "i", "e")
-- remove Hazaragi retroflexes
text = rsubn(text, "D", "d")
text = rsubn(text, "T", "t")
text = rsubn(text, "ɖ", "d")
text = rsubn(text, "ʈ", "t")
-- Tajik does not have vowel length
text = rsubn(text, "([ēī])", "i")
text = rsubn(text, "([ūō])", "u")
-- terminal w is only possible in a dipthong
text = rsubn(text, "([o]0)w#", "v#")
text = rsubn(text, "a#", "e#")
text = rsubn(text, "#ve#", "#va#")
text = rsubn(text, "a%-", "e-")
text = rsubn(text, "æ", "a")
text = rsubn(text, "#'", "#")
text = rsubn(text, "#", "")
text = rsubn(text, "\1", "-")
return text
end
function export.romanize_tg(text, script, options)
if type(text) == "table" then
options = {}
text, script = text.args[1], text.args[2]
end
text = rsubn(text, "^" .. "[-]", "\1")
text = rsubn(text, "[,]", ", ")
text = rsubn(text, "%%", "")
text = rsubn(text, " | ", "# | #")
text = "##" .. rsubn(text, " ", "# #") .. "##"
text = rsubn(text, "i(['h])", "ē%1")
text = rsubn(text, "u(['h])", "ō%1")
text = rsubn(text, "([iī]y(%.?))y", "i%2y")
text = rsubn(text, "ˈ", "`")
text = rsubn(text, "([iī]y`y)", "i`y")
--kill incorrect characters
text = rsubn(text, "([" .. dental .. pitchaccent .. devoice .. dtack .. "ʰ])", "")
text = rsubn(text, "([ɴŋ])", "n")
-- Tajik needs some Capital letters for headword spellings
-- But Capital letters in the headword can only be initial
text = rsubn(text, "w", "v")
text = rsubn(text, "W", "V")
-- Replace xw clusters
text = rsubn(text, "xv([āē])", "x%1")
text = rsubn(text, "xva", "xu")
text = rsubn(text, "Xv([āē])", "X%1")
text = rsubn(text, "Xva", "Xu")
text = rsubn(text, "ʷ", "")
--ensure vowels are paired to a consonant
text = rsubn(text, "([.])([" .. vowels .. "])", "%1'%2")
text = rsubn(text, "([.])", "")
text = rsubn(text, "ḍ", "z")
text = rsubn(text, "ḏ", "z")
text = rsubn(text, "ṯ", "s")
text = rsubn(text, "ṭ", "t")
text = rsubn(text, "ṣ", "s")
text = rsubn(text, "ḥ", "h")
text = rsubn(text, "ē", "e")
text = rsubn(text, "ō", "ü")
text = rsubn(text, "ā", "o")
text = rsubn(text, "Ē", "E")
text = rsubn(text, "Ō", "Ü")
text = rsubn(text, "Ā", "O")
-- remove Hazaragi retroflexes
text = rsubn(text, "ɖ", "d")
text = rsubn(text, "ʈ", "t")
-- Tajik does not have vowel length
text = rsubn(text, "([iī])", "i")
text = rsubn(text, "([ūu])", "u")
text = rsubn(text, "([IĪ])", "I")
text = rsubn(text, "([UŪ])", "U")
text = rsubn(text, "`", "")
text = rsubn(text, "#([ʾ])", "")
text = rsubn(text, "#", "")
text = rsubn(text, "\1", "-")
return text
end
local function one_term_ipa(text, style)
if style == "cls" then
text = export.fa_cls_IPA(text)
elseif style == "prs" then
text = export.prs_IPA(text)
elseif style == "kbl" then
text = export.prs_kbl_IPA(text) or export.prs_IPA(text) --should ignore conversion if specified
elseif style == "haz" then
text = export.prs_haz_IPA(text)
elseif style == "fa" then
text = export.fa_IPA(text)
elseif style == "teh" then
text = export.fa_IPA(text)
elseif style == "tg" then
text = export.tg_IPA(text)
end
return text
end
-- style == one of the following:
-- "cls": Classical Persian
-- "prs": Dari Persian
-- "kbl": Kabuli
-- "haz": Hazaragi
-- "fa": Iranian Persian
-- "teh": Tehrani
-- "tg": Tajik
function export.IPA(text, style)
local variants = {text}
local function apply_sub(from, to1, to2)
return function(item)
if rfind(item, from) then
if to2 then
return {rsub(item, from, to1), rsub(item, from, to2)}
else
return {rsub(item, from, to1)}
end
else
return {item}
end
end
end
local function call_one_term_ipa(variant)
local result = {
{
phonemic = one_term_ipa(variant, style, false, err)
}
}
local function apply_sub(item, from, to1, qual1, to2, qual2)
if rfind(item.phonemic, from) or rfind(item.phonetic, from) then
return {
{
phonemic = rsub(item.phonemic, from, to1),
q = qual1
},
{
phonemic = rsub(item.phonemic, from, to2),
q = qual2
}
}
else
return {item}
end
end
return result
end
return flatmap(variants, call_one_term_ipa)
end
function export.express_styles(inputs, args_style)
local pronuns_by_style = {}
local expressed_styles = {}
local function dostyle(style)
pronuns_by_style[style] = {}
for _, val in ipairs(inputs[style]) do
local pronuns = export.IPA(val, style)
for _, pronun in ipairs(pronuns) do
table.insert(pronuns_by_style[style], pronun)
end
end
end
local function all_available(styles)
local available_styles = {}
for _, style in ipairs(styles) do
if pronuns_by_style[style] then
table.insert(available_styles, style)
end
end
return available_styles
end
local function express_style(hidden_tag, tag, styles, indent)
indent = indent or 1
if hidden_tag == true then
hidden_tag = tag
end
if type(styles) == "string" then
styles = {styles}
end
styles = all_available(styles)
if #styles == 0 then
return
end
local style = styles[1]
-- If style specified, make sure it matches the requested style.
local style_matches
if not args_style then
style_matches = true
else
local or_styles = rsplit(args_style, "%s*,%s*")
for _, or_style in ipairs(or_styles) do
local and_styles = rsplit(or_style, "%s*%+%s*")
local and_matches = true
for _, and_style in ipairs(and_styles) do
local negate
if and_style:find("^%-") then
and_style = and_style:gsub("^%-", "")
negate = true
end
local this_style_matches = false
for _, part in ipairs(styles) do
if part == and_style then
this_style_matches = true
break
end
end
if negate then
this_style_matches = not this_style_matches
end
if not this_style_matches then
and_matches = false
end
end
if and_matches then
style_matches = true
break
end
end
end
if not style_matches then
return
end
local new_style = {
tag = tag,
represented_styles = styles,
pronuns = pronuns_by_style[style],
indent = indent
}
for _, hidden_tag_style in ipairs(expressed_styles) do
if hidden_tag_style.tag == hidden_tag then
table.insert(hidden_tag_style.styles, new_style)
return
end
end
table.insert(
expressed_styles,
{
tag = hidden_tag,
styles = {new_style}
}
)
end
for style, _ in pairs(inputs) do
dostyle(style)
end
local function diff(style1, style2)
if not pronuns_by_style[style1] or not pronuns_by_style[style2] then
return true
end
return not m_table.deepEquals(pronuns_by_style[style1], pronuns_by_style[style2])
end
local fa_teh_different = diff("fa", "teh")
local prs_kbl_different = diff("prs", "kbl")
local prs_haz_different = diff("prs", "haz")
-- Classical Persian
express_style("[[w:波斯語|古典波斯語]]", "[[w:波斯語|古典波斯語]]", "cls")
-- Dari Persian
express_style(
"[[w:達利語|達利語,正式]]",
"[[w:達利語|達利語,正式]]",
"prs"
)
express_style("[[w:達利語|達利語,正式]]", "[[:en:w:Dari#South-Eastern|喀布爾]]", "kbl", 2)
express_style("[[w:達利語|達利語,正式]]", "[[w:哈扎拉吉語|哈扎拉吉語]]", "haz", 2)
-- Iranian Persian
express_style(
"[[w:伊朗波斯語|伊朗,正式]]",
"[[w:伊朗波斯語|伊朗,正式]]",
"fa"
)
if fa_teh_different then
express_style("[[w:伊朗波斯語|伊朗,正式]]", "[[w:德黑蘭口音|德黑蘭]]", "teh", 2)
end
-- Tajik
express_style(
"[[w:塔吉克語|塔吉克,正式]]",
"[[w:塔吉克語|塔吉克,正式]]",
"tg"
)
return expressed_styles
end
function export.show(frame)
-- Create parameter specs
local params = {
[1] = {}, -- this replaces style group 'all'
["pre"] = {},
["post"] = {},
["ref"] = {},
["style"] = {},
["bullets"] = {type = "number", default = 1}
}
for group, _ in pairs(export.all_style_groups) do
if group ~= "all" then
params[group] = {}
end
end
for _, style in ipairs(export.all_styles) do
params[style] = {}
end
-- Parse arguments
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
-- Set inputs
local inputs = {}
-- If 1= specified, do all styles.
if args[1] then
for _, style in ipairs(export.all_styles) do
inputs[style] = args[1]
end
end
-- Then do remaining style groups other than 'all', overriding 1= if given.
for group, styles in pairs(export.all_style_groups) do
if group ~= "all" and args[group] then
for _, style in ipairs(styles) do
inputs[style] = args[group]
end
end
end
-- Then do individual style settings.
for _, style in ipairs(export.all_styles) do
if args[style] then
inputs[style] = args[style]
end
end
-- If no inputs given, set all styles based on current pagename.
if not next(inputs) then
local text = mw.title.getCurrentTitle().text
for _, style in ipairs(export.all_styles) do
inputs[style] = text
end
end
for style, input in pairs(inputs) do
inputs[style] = rsplit(input, ",")
end
local expressed_styles = export.express_styles(inputs, args.style)
local lines = {}
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
for _, pronun in ipairs(expressed_style.pronuns) do
table.insert(
pronunciations,
{
pron = "[" .. pronun.phonemic .. "]",
q = pronun.q
}
)
local formatted_phonemic = "[" .. pronun.phonemic .. "]"
if pronun.q then
formatted_phonemic = "(" .. table.concat(pronun.q, ", ") .. ") " .. formatted_phonemic
end
table.insert(formatted_pronuns, formatted_phonemic)
end
-- Number of bullets: When indent = 1, we want the number of bullets given by `args.bullets`,
-- and when indent = 2, we want `args.bullets + 1`, hence we subtract 1.
local bullet = string.rep("*", args.bullets + expressed_style.indent - 1) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
pre = pre .. (tag and m_qual.format_qualifier(tag) .. " " or "")
local post = is_first and (args.ref or "") .. (args.post and " " .. args.post or "") or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns, ", ") .. post
return formatted, formatted_for_len
end
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
style_group.formatted, style_group.formatted_for_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
else
style_group.formatted, style_group.formatted_for_len = format_style(style_group.tag, style_group.styles[1], i == 1)
for j, style in ipairs(style_group.styles) do
style.formatted, style.formatted_for_len = format_style(style.tag, style, i == 1 and j == 1)
end
end
end
local function textual_len(text)
text = rsub(text, "<.->", "")
return ulen(text)
end
local maxlen = 0
for i, style_group in ipairs(expressed_styles) do
local this_len = textual_len(style_group.formatted_for_len)
if #style_group.styles > 1 then
for _, style in ipairs(style_group.styles) do
this_len = math.max(this_len, textual_len(style.formatted_for_len))
end
end
maxlen = math.max(maxlen, this_len)
end
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
table.insert(lines, "<div>\n" .. style_group.formatted .. "</div>")
else
local inline = '\n<div class="vsShow" style="display:none">\n' .. style_group.formatted .. "</div>"
local full_prons = {}
for _, style in ipairs(style_group.styles) do
table.insert(full_prons, style.formatted)
end
local full = '\n<div class="vsHide">\n' .. table.concat(full_prons, "\n") .. "</div>"
local em_length = math.floor(maxlen * 0.68) -- from [[Module:grc-pronunciation]]
table.insert(
lines,
'<div class="vsSwitcher" data-toggle-category="pronunciations" style="width: ' ..
em_length ..
'em; max-width:100%;"><span class="vsToggleElement" style="float: right;"> </span>' ..
inline .. full .. "</div>"
)
end
end
-- major hack to get bullets working on the next line
return table.concat(lines, "\n") .. "\n<span></span>"
end
--Tajik from Cyrillic
local t_consonants = {
['g'] = 'ɡ',
['c'] = 't͡s',
['j'] = 'd͡ʒ',
['ġ'] = 'ɣ',
['y'] = 'j',
['š'] = 'ʃ',
['č'] = 't͡ʃ',
['x'] = 'x',
['ž'] = 'ʒ',
['\''] = 'ʔ'
}
local t_vowels = {
['a'] = 'a',
['ü'] = 'ɵ',
['o'] = 'o',
}
function export.tg_phonemic(text)
text = rsubn(text, "`", "ˈ")
-- not using yodated syllable implies syllable break
text = require('Module:languages').getByCode('tg'):transliterate(text)
text = m_str_utils.lower(text)
text = rsubn(text, "%-", "")
text = rsubn(text, "[-,]", " ")
text = rsubn(text, "%.", "")
-- Replace vowels
text = rsubn(text, ".", t_vowels)
-- Replace consonants
text = rsubn(text, ".", t_consonants)
return text
end
function export.tg_phonetic(text)
text = rsubn(text, "`", "ˈ")
text = require('Module:languages').getByCode('tg'):transliterate(text)
text = m_str_utils.lower(text)
text = rsubn(text, "^%-", "‿")
text = rsubn(text, "(i)", "ī")
text = rsubn(text, "(u)", "ū")
text = rsubn(text, "(e)", "ē")
text = rsubn(text, "(ü)", "ō")
text = rsubn(text, "(o)", "ā")
text = rsubn(text, "ʾ", "'")
text = rsubn(text, "%-", "")
text = syllabify(text)
text = export.tg_IPA(text)
text = mw.ustring.toNFC(text)
return text
end
function export.show_tg(frame)
local tg = require("Module:languages").getByCode("tg")
local args = frame:getParent().args
local p, results = {}, {}
if args[1] then
for index, item in ipairs(args) do
table.insert(p, (item ~= "") and item or nil)
end
else
if mw.title.getCurrentTitle().nsText == "Template" then
p = {"чаҳоршанбе"}
else
p = {mw.title.getCurrentTitle().text}
end
end
for _, word in ipairs(p) do
table.insert(results, {pron = "/" .. export.tg_phonemic(word) .. "/"})
table.insert(results, {pron = "[" .. export.tg_phonetic(word) .. "]"})
end
return "* " .. m_IPA.format_IPA_full { lang = tg, items = results }
end
return export
lyp9mha2r41qcuaki5215fdeo8ry8q4
bazylika
0
2093174
9909150
8229531
2026-09-29T08:48:35Z
TongcyDai
53191
9909150
wikitext
text/x-wiki
{{also|bazyliką}}
==波兰语==
{{wikipedia|lang=pl}}
===词源===
{{inh+|pl|zlw-opl|bazylika}},源自{{der|pl|la|basilica}},源自{{der|pl|grc|βασιλικός}}。{{doublet|pl|bazylia|bazyliszek}}。
===发音===
{{pl-pr|a=Pl-bazylika.ogg|mp=#}}
===名词===
{{pl-noun|f}}
# {{lb|pl|architecture}} [[巴西利卡]]
# {{lb|pl|Roman Catholicism}} [[宗座]][[聖殿]] {{gl|部分地位特殊教堂的頭銜}}
====变格====
{{pl-decl-noun-f}}
====衍生词汇====
{{col3|pl|title=形容詞|bazylikowy}}
===延伸阅读===
* {{R:pl:WSJP}}
* {{R:pl:PWN}}
{{C|pl|宗教場所}}
1xx0haujt2n0uivwydn3qq4i5qsrczk
chemica
0
2153300
9909141
7580141
2026-09-29T08:43:11Z
TongcyDai
53191
9909141
wikitext
text/x-wiki
==拉丁語==
===形容詞===
{{la-adj-form|chēmica}}
# {{inflection of|la|chēmicus||nom//voc|f|s|;|nom//voc//acc|n|p}}
===形容詞===
{{la-adj-form|chēmicā}}
# {{inflection of|la|chēmicus||abl|f|s}}
==波蘭語==
===發音===
{{pl-pr|a=LL-Q809 (pol)-Olaf-chemica.wav}}
===名詞===
{{pl-noun|f}}
# {{Augmentative of|pl|chemiczka|t=女化學家}}
====變格====
{{pl-decl-noun-f}}
almhwzmskyqqv9dqe2hdku1xpghqffm
Module:Fax-IPA
828
2159007
9909063
9023380
2026-09-29T07:25:41Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909063
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
--TODO
--This code is adapted from [[Module:es-pronunc]], which can output multiple dialectal pronounciations from a single spelling. This module will
--return a single pronunciation per spelling in all cases, so there are parts of the code which can be simplified.
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local decorations_module = "Module:decorations"
local put_module = "Module:parse utilities"
local force_cat = false -- for testing
local lang = require("Module:languages").getByCode("fax")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeiouüAEIOUÜ" -- vowel; include y so we get single-word y correct and for syllabifying from spelling
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR .. CFLEX
local accent_c = "[" .. accent .. "]"
local stress = AC .. GR
local stress_c = "[" .. AC .. GR .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class including h
local C_NOT_H = "[^" .. vowel .. separator .. "h]" -- consonant class not including h
local C_OR_WORDSEP = "[^" .. vowel .. separator_not_wordsep .. "]" -- consonant class including h, or word separator
local T = "[^" .. vowel .. "lrɾjw" .. separator .. "]" -- obstruent or nasal
local unstressed_words = require("Module:table").listToSet({ --feel free to add more unstressed words
"o", "u", "us", "os", "a", "as", "da", "das", "do", "dos", "du", "dus", "no", "nos", "nu", "nus", "na", "nas", "un", -- articles and preposition+article contractions
"me", "mus", "nus", "te", "vus", "le", "se", "mo", "mu", "to", "tu",-- unstressed pronouns and contraction
"e", "i", "mais", "si", "que", -- conjunctions
"con", "cun", "de", "in", "en", "pa", --prepositions
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ñ and ü
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text, is_spelling)
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels (but don't divide a VhV
-- sequence; [[prohibir]] should be prohi.bir). Then move the syllable division marker leftwards over clusters that
-- can form onsets.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C_NOT_H .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
-- NOTE: When run on pronun, we have already eliminated c and v, but not when run on spelling.
-- When run on pronun, don't include r, which at this point represents the trill.
local cluster_r = is_spelling and "rɾ" or "ɾ"
-- Don't divide Cl or Cr where C is a stop or fricative, except for dl.
text = rsub(text, "([pbfvkctgɡ])%.([l" .. cluster_r .. "])", ".%1%2")
text = text:gsub("d%.([" .. cluster_r .. "])", ".d%1")
-- Don't divide ch, sh, ph, th, dh, fh, kh or gh. Do allow bh to be divided ([[subhumano]], [[subhúmedo]], etc.).
text = rsub(text, "([csptdfkg])%.h", ".%1h")
-- Don't divide rr.
text = rsub(text, "r.r", ".rr")
-- Part 2: Divide hiatuses.
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?[aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub(text, "([iuüIUÜ]" .. stress_c .. ")(h?[aeoiu])", "%1.%2")
text = rsub_repeatedly(text, "([iuüIUÜ]" .. stress_c .. ")(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "([iI]" .. accent_c .. "*)(h?i)", "%1.%2")
text = rsub_repeatedly(text, "([uU]" .. accent_c .. "*)(h?u)", "%1.%2")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants.
local TEMP_I = u(0xFFF1)
local TEMP_U = u(0xFFF2)
local TEMP_QU = u(0xFFF4)
local TEMP_QU_CAPS = u(0xFFF5)
local TEMP_GU = u(0xFFF6)
local TEMP_GU_CAPS = u(0xFFF7)
local TEMP_H = u(0xFFF8)
local TEMP_DX = u(0xFFF9)
local TEMP_DX_CAPS = u(0xFFFA)
local TEMP_LL = u(0xFFFB)
local TEMP_LL_CAPS = u(0xFFFC)
local TEMP_NH = u(0x0400)
local TEMP_NH_CAPS = u(0x0401)
local TEMP_SH = u(0x0402)
local TEMP_SH_CAPS = u(0x0403)
local TEMP_XH = u(0x0404)
local TEMP_XH_CAPS = u(0x0405)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = rsub(text, "ii", "i" .. TEMP_I)
text = rsub(text, "uu", "u" .. TEMP_U)
text = rsub(text, "dx", TEMP_DX)
text = rsub(text, "Dx", TEMP_DX_CAPS)
text = rsub(text, "nh", TEMP_NH)
text = rsub(text, "Nh", TEMP_NH_CAPS)
text = rsub(text, "sh", TEMP_SH)
text = rsub(text, "Sh", TEMP_SH_CAPS)
text = rsub(text, "xh", TEMP_XH)
text = rsub(text, "Xh", TEMP_XH_CAPS)
text = rsub(text, "ll", TEMP_LL)
text = rsub(text, "Ll", TEMP_LL_CAPS)
text = text:gsub("%.", SYLDIV)
-- We don't want to break -sh- except in desh-, e.g. [[deshuesar]], [[deshonra]], [[deshecho]]. Normally, -sh- is
-- automatically preserved, so we replace the h with a temporary symbol to avoid this.
text = text:gsub("^([Dd]es)h", "%1" .. TEMP_H)
text = text:gsub("([ %-][Dd]es)h", "%1" .. TEMP_H)
-- qu mostly handled correctly automatically, but not in quietud
text = rsub(text, "qu(" .. V .. ")", TEMP_QU .. "%1")
text = rsub(text, "Qu(" .. V .. ")", TEMP_QU_CAPS .. "%1")
text = rsub(text, "gu(" .. V .. ")", TEMP_GU .. "%1")
text = rsub(text, "Gu(" .. V .. ")", TEMP_GU_CAPS .. "%1")
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*)(h?)([iu])(" .. V .. ")",
function (v1, h, iu, v2) return v1 .. "." .. h .. vowel_to_glide[iu] .. v2 end
)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
text = text:gsub(SYLDIV, ".")
text = text:gsub(TEMP_I, "i")
text = text:gsub(TEMP_U, "u")
text = text:gsub(TEMP_QU, "qu")
text = text:gsub(TEMP_QU_CAPS, "Qu")
text = text:gsub(TEMP_GU, "gu")
text = text:gsub(TEMP_GU_CAPS, "Gu")
text = text:gsub(TEMP_H, "h")
text = text:gsub(TEMP_DX, "dx")
text = text:gsub(TEMP_DX_CAPS, "Dx")
text = text:gsub(TEMP_SH, "sh")
text = text:gsub(TEMP_SH_CAPS, "Sh")
text = text:gsub(TEMP_XH, "xh")
text = text:gsub(TEMP_XH_CAPS, "Xh")
text = text:gsub(TEMP_NH, "nh")
text = text:gsub(TEMP_NH_CAPS, "Nh")
text = text:gsub(TEMP_LL, "ll")
text = text:gsub(TEMP_LL_CAPS, "Ll")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text)
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ñ and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain monosyllabic words unstressed
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") and not rfind(word, accent_c) or unstressed_words[word] then
-- add CFLEX to the last vowel not the first one
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CFLEX)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
text = rsub(text, "@", "-")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--c, g, q
text = rsub(text, "c([ie])", "θ%1")
text = rsub(text, "gü([ie])", "ɡw%1")
text = rsub(text, "gu([ie])", "ɡ%1") -- special IPA g, not normal g
text = rsub(text, "g([ie])", "j%1") -- <ge,gi> is pronounced as <je,ji>, to be handled later
text = rsub(text, "ng([^aeiouüwhlr])", "n%1") -- [[Bangkok]], [[ángstrom]], [[branding]]
text = rsub(text, "qu([ie])", "k%1")
text = rsub(text, "z", "θ")
--digraphs
text = rsub(text, "ch", "č") --not the real symbol
text = rsub(text, "dx", "đ") --not the real symbol
text = rsubb(text, "ll", "ʎ")
text = rsubb(text, "nh", "ŋ")
text = rsubb(text, "xh", "ʒ")
text = rsubb(text, "sh", "z")
text = rsub(text, "#ps", "#s") -- [[psicología]]
text = rsub(text, "[cgjñrvxy]",
{ ["c"] = "k", ["g"] = "ɡ", ["j"] = "x", ["ñ"] = "ɲ", ["r"] = "ɾ", ["v"] = "b", ["x"] = "ʃ", ["y"] = "j"})
-- trill in #r, lr, nr, sr, rr
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "([#lns])ɾ", "%1r")
--assimilation of nasals and sibilants
text = rsub(text, "n([# .]*[bpm])", "m%1")
text = rsub(text, "n([# .]*[kɡ])", "ŋ%1")
text = rsub(text, "s([# .]*[nml])", "z%1")
--double uu and ii
text = rsub(text, "uu" .. AC, "u.u" .. AC)
text = rsub(text, "ii" .. AC, "i.i" .. AC)
text = rsubb(text, "uu", "wu")
text = rsubb(text, "ii", "ji")
-- convert i/u between vowels to glide
local vowel_to_glide = { ["i"] = "j", ["u"] = "w" }
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*h?)([iu])(" .. V .. ")",
function (v1, iu, v2) return v1 .. vowel_to_glide[iu] .. v2 end
)
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--diphthongs
text = rsub(text, "i([aeou])", "j%1")
text = rsub(text, "u([aeio])", "w%1")
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = "ˌ", [CFLEX] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including ^ indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and (rfind(word, "[^" .. vowel .. "ns#]#") or rfind(word, C .. "[ns]#")) or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 and rfind(word, "[aeo][iu][ns#]") then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
if rfind(word, "men%.ti#") then
local mente_syllables
-- Words ends in -menti (converted above to ménti); add a stress to the preceding portion
mente_syllables = {}
mente_syllables[2] = table.remove(syllables)
mente_syllables[1] = table.remove(syllables)
accent_word(table.concat(syllables, "."), syllables)
accent_word(table.concat(mente_syllables, "."), mente_syllables)
table.insert(syllables, mente_syllables[1])
table.insert(syllables, mente_syllables[2])
else
accent_word(word, syllables)
end
-- Reconstruct the word and make all primary stresses but the last one be secondary
words[j] = table.concat(syllables, ".")
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˌ%1ˈ")
end
text = table.concat(words, " ")
text = rsub(text, "%-", "")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--final conversions
text = rsub(text, "([aeou][i])", "%1̯")
text = rsub(text, "([aeio][u])", "%1̯")
local final_conversions = {
["h"] = "",
["č"] = "t͡ʃ",
["đ"] = "d͡ʒ",
}
text = rsub(text, "[hčđ]", final_conversions)
text = rsub(text, "#", "")
text = unfc(text)
return {text = text}
end
local function express_all_styles(dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
dodialect(ret)
table.insert(ret.expressed_styles, {
styles = {{pronun = ret.pronun,}},
})
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
end
local lines = {}
for i, style_group in ipairs(expressed_styles) do
table.insert(lines, style_group.formatted)
end
return table.concat(lines, "\n")
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic
if term.raw then
phonemic = term.raw_phonemic
else
phonemic = export.IPA(term.term)
phonemic = phonemic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = express_all_styles(this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡ʃ", "tʃ"):gsub("d͡ʒ", "dʒ")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CFLEX then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. [[Austria-Hungría]])
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(put_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorators
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorators
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{es-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = require(put_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = express_all_styles(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = express_all_styles(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
-- External entry point for {{fax-IPA}}.
function export.show(frame)
local params = {
[1] = {},
["pre"] = {},
["post"] = {},
["ref"] = {},
["style"] = {},
["bullets"] = {type = "number", default = 1},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local text = args[1] or mw.title.getCurrentTitle().text
args.terms = {{term = text}}
local ret = generate_pronun(args)
return ret.text
end
return export
l4gbe7muc98leeoytvoof1hi962jh2c
Module:Eu-pron/new
828
2189208
9909061
9108432
2026-09-29T07:25:33Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909061
Scribunto
text/plain
--Based on Module:es-pronunc by Benwing2.
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local decorations_module = "Module:decorations"
local put_module = "Module:parse utilities"
local force_cat = false -- for testing
local lang = require("Module:languages").getByCode("eu")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local TILDE = u(0x0303) -- tilde = ̃
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local BREVE = u(0x032F)
local vowel = "aeiouAEIOU" -- vowel; include y so we get single-word y correct and for syllabifying from spelling
local V = "[" .. vowel .. "]" -- vowel class
local W = "[wʝ]" -- glides, used in a few loanwords
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator = sylsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class including h
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ñ and ü
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
local function construct_default_differences(dialect)
if dialect == "gipuzkoan" then
return {
northern_different = false,
j_different = false,
need_bisc = false,
}
end
return nil
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text)
text = rsub_repeatedly(text, "(" .. V .. ")(" .. C .. W .. "?" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. C .. ")(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. C .. C .. ")(" .. C .. C .. V .. ")", "%1.%2")
text = rsub(text, "([pfbktdɡg])%.([lrɾ])", ".%1%2") --the first "ɡ" is the IPA one; the second "g" is the regular one
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
text = rsub_repeatedly(text, "([ae]" .. ")ui", "%1u.i")
text = rsub_repeatedly(text, "([aeiou]" .. ")([aeo])", "%1.%2")
text = rsub_repeatedly(text, "ii", "i.i")
text = rsub_repeatedly(text, "([iou]" .. ")u", "%1.u")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants.
local TEMP_tz = u(0xFFF1)
local TEMP_ts = u(0xFFF2)
local TEMP_tx = u(0xFFF3)
local TEMP_tx_CAPS = u(0xFFF4)
local TEMP_ll = u(0xFFF5)
local TEMP_ll_CAPS = u(0xFFF6)
local TEMP_dd = u(0xFFF7)
local TEMP_dd_CAPS = u(0xFFF8)
local TEMP_tt = u(0xFFF9)
local TEMP_tt_CAPS = u(0xFFFA)
local TEMP_rr = u(0xFFB)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = text:gsub("%.", SYLDIV)
-- handle digraphs
text = rsub(text, "tz", TEMP_tz)
text = rsub(text, "ts", TEMP_ts)
text = rsub(text, "tx", TEMP_tx)
text = rsub(text, "Tx", TEMP_tx_CAPS)
text = rsub(text, "ll", TEMP_ll)
text = rsub(text, "Ll", TEMP_ll_CAPS)
text = rsub(text, "tt", TEMP_tt)
text = rsub(text, "Tt", TEMP_tt_CAPS)
text = rsub(text, "dd", TEMP_dd)
text = rsub(text, "Dd", TEMP_dd_CAPS)
text = rsub(text, "rr", TEMP_rr)
text = syllabify_from_spelling_or_pronun(text)
text = text:gsub(SYLDIV, ".")
text = text:gsub(TEMP_tz, "tz")
text = text:gsub(TEMP_ts, "ts")
text = text:gsub(TEMP_tx, "tx")
text = text:gsub(TEMP_tx_CAPS, "Tx")
text = text:gsub(TEMP_ll, "ll")
text = text:gsub(TEMP_ll_CAPS, "Ll")
text = text:gsub(TEMP_tt, "tt")
text = text:gsub(TEMP_tt_CAPS, "Tt")
text = text:gsub(TEMP_dd, "dd")
text = text:gsub(TEMP_dd_CAPS, "Dd")
text = text:gsub(TEMP_rr, "rr")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling, where a respelling is the representation of the pronunciation of a given
function export.IPA(text, dialect, phonetic)
local northern = dialect == "northern"
local southern = dialect == "gipuzkoan" or dialect == "navarrese" or dialect == "biscayan"
local nav = dialect == "navarrese"
local bisc = dialect == "biscayan"
local northern_different = false
local j_different = false
local need_bisc = false
local J = ""
local N = ""
-- start at FFF1 because FFF0 is used for SYLDIV
local TEMP_Y = u(0xFFF1)
local TEMP_W = u(0xFFF2)
--function that gets the last two syllables (used for generating the correct rhymes)
local function extract_last_syllables(text)
local words = rsplit(text, " ")
if #words > 1 then --no rhymes are generated when there are multiple words
return ""
end
text = rsub(text, "([aeou])i", "%1")
text = rsub(text, "([ae])u", "%1")
local length = string.len(text)
local rhyme = ""
local count = -1
local syl = 0
while count > -length do
if string.find("aeiou", string.sub(text,count,count)) then
rhyme = string.sub(text,count,-1)
syl = syl + 1
if syl == 2 then
return rhyme
end
end
count = count - 1
end
return rhyme
end
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ñ and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Convert hyphens to spaces, to handle [[Austria-Hungría]], [[franco-italiano]], etc.
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[!?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--determining whether "h" denotes a phoneme, or the lack of palatalization, palatalization when needed
text = rsub(text, "nh", "N")
text = rsub(text, "lh", "L")
text = rsub(text, "([aeo])il([# .h]*[aeiou])", (northern and "%1ił%2" or "%1ĺ%2"))
text = rsub(text, "([aeo])in([# .h]*[aeiou])", (northern and "%1iň%2" or "%1ń%2"))
text = rsub(text, "uin([# .h]*[aeiou])", (northern and "uiň%1" or "uiń%1"))
text = rsub(text, "uil([# .h]*[aeiou])", (northern and "uił%1" or "uiĺ%1"))
text = rsub(text, "il([# .h]*[aeiou])", (northern and "ił%1" or "iĺ%1"))
text = rsub(text, "in([# .h]*[aeiou])", (northern and "iň%1" or "iń%1"))
if text:find("[łňńĺh]") then
northern_different = true
end
if extract_last_syllables(text):find("[łňńĺh]") then
N = "N"
end
text = rsub(text, "ĺ", "ʎ")
text = rsub(text, "ń", "ɲ")
text = rsub(text, "N", "n")
text = rsub(text, "L", "l")
text = rsub(text, "ň", "n")
text = rsub(text, "ł", "l")
--c, g, q
text = rsub(text, "ng([^aeiouüwhlr])", "n%1") -- [[Bangkok]], [[angstrom]], etc.
text = rsub(text, "q", "k") -- [[quark]], [[Qatar]], [[burqa]], [[Iraq]], etc.
text = rsub(text, "c", "k") -- [[campus]], etc.
--alphabet-to-phoneme
text = rsub(text, "aje#", "aʒe#") -- final -aje is pronounced /aʒe/ in the North and /axe/ in the South, regardless of how native j is pronounced
text = rsub(text, "ihoa", "iχoa") -- the <h> in conjugated forms of joan is pronounced in the South
text = rsub(text, "zh", "ź")
text = rsub(text, "tx", "C") --not the real sound
text = rsub(text, "tz", "Ś") --not the real sound
text = rsub(text, "ts", "S") --not the real sound
text = rsubb(text, "ll", "ʎ")
text = rsubb(text, "dd", "ɟ")
text = rsubb(text, "tt", "c")
text = rsub(text, "x", "ʃ")
text = rsub(text, "#p([st])", "#%1") -- [[psicologia]], [[pterodaktilo]]
text = rsub(text, "[gñrvz]",
{ ["g"] = "ɡ", ["ñ"] = "ɲ", ["v"] = "b", ["z"] = "ś" })
if text:find("ś") or text:find("S") then
need_bisc = true
end
--/x/~/j/~/ʒ/ sounds
if text:find("[yj]") then
northern_different = true
j_different = true
end
if extract_last_syllables(text):find("[yj]") then
J = "J"
end
if text:find("ʒ") or text:find("χ") then
northern_different = true
end
if extract_last_syllables(text):find("ʒ") then
N = "N"
end
if text:find("kh") then
northern_different = true
end
if extract_last_syllables(text):find("kh") then
N = "N"
end
if text:find("ź") then
northern_different = true
end
if extract_last_syllables(text):find("ź") then
N = "N"
end
text = rsubb(text, "j", (northern and "ɟ" or "x"))
text = rsubb(text, "y", (northern and "ɟ" or "j")) -- <y> is used for words which always have a palatal
text = rsubb(text, "kh", (northern and "k" or "χ"))
text = rsubb(text, "ź", (northern and "ʒ" or "ʃ"))
-- trilled r (in all cases except between vowels)
text = rsub(text, "([aeiou])r([aeiou])", "%1ɾ%2")
text = rsub(text, "([aeiou])r([aeiou])", "%1ɾ%2") -- it has to be applied twice in order to handle VrVrV sequences
text = rsub(text, "rr", "r")
text = rsub(text, "n([# .]*[bpm])", "m%1")
-- lack of /h/ in Southern dialects
text = rsub(text, "aha", (northern and "aha" or "a"))
text = rsub(text, "ehe", (northern and "ehe" or "e"))
text = rsub(text, "ihi", (northern and "ihi" or "i"))
text = rsub(text, "oho", (northern and "oho" or "o"))
text = rsub(text, "uhu", (northern and "uhu" or "u"))
text = rsub(text, "h", (northern and "h" or ""))
text = rsub(text, "n([# .]*[bpm])", "m%1")
-- handle i/u between vowels
text = rsub_repeatedly(text, "ui", "U") --this dipthong requires special treatment
local vowel_to_glide = { ["i"] = "i.", ["u"] = "u." }
text = rsub_repeatedly(text, "(" .. V .. "*)([iu])(" .. V .. ")",
function(v1, iu, v2)
return v1 .. vowel_to_glide[iu] .. v2
end
)
text = rsub_repeatedly(text, "U", "ui")
--syllable division
text = syllabify_from_spelling_or_pronun(text)
--dialectal differences
if nav then
text = rsub(text, "x", "j")
elseif bisc then --fake symbols, to be replaced later
text = rsub(text, "x", "đ")
text = rsub(text, "j", "đ")
text = rsub(text, "ś", "s")
text = rsub(text, "S", "Ś")
end
text = rsubb(text, "ʒ", (southern and "x" or "ʒ"))
text = rsubb(text, "χ", (southern and "x" or "h"))
--phonetic transcription
if phonetic then
-- θ, s, f before voiced consonants
local voiced = "mnɲbdɟɡʎljđ"
local r = "ɾr"
local tovoiced = {
["s"] = "z̺",
["ś"] = "z̻",
}
local function voice(sound, following)
return tovoiced[sound] .. following
end
text = rsub(text, "([sś])(" .. separator_c .. "*[" .. voiced .. r .. "])", voice)
-- fricative vs. stop allophones; first convert stops to fricatives, then back to stops
-- after nasals and sometimes after l
local stop_to_fricative = {["b"] = "β", ["d"] = "ð", ["ɡ"] = "ɣ"}
local fricative_to_stop = {["β"] = "b", ["ð"] = "d", ["ɣ"] = "ɡ"}
text = rsub(text, "[bdɡ]", stop_to_fricative)
text = rsub(text, "([mnɲ]" .. separator_c .. "*)([βɣ])",
function(nasal, fricative) return nasal .. fricative_to_stop[fricative] end
)
text = rsub(text, "([lʎmnɲ]" .. separator_c .. "*)([ð])",
function(nasal_l, fricative) return nasal_l .. fricative_to_stop[fricative] end
)
text = rsub(text, "##β", "##b")
text = rsub(text, "##ð", "##d")
text = rsub(text, "##ɣ", "##ɡ")
text = rsub(text, "[td]", {["t"] = "t̪", ["d"] = "d̪"})
-- nasal assimilation before consonants
local labiodental, dentialveolar, alveolopalatal, palatal, velar = "ɱ", "n̪", "nʲ", "ɲ", "ŋ"
local nasal_assimilation = {
["f"] = labiodental,
["t"] = dentialveolar, ["d"] = dentialveolar,
["C"] = alveolopalatal,
["ʃ"] = alveolopalatal,
["đ"] = alveolopalatal,
["ɟ"] = palatal, ["c"] = palatal, ["ʎ"] = palatal, ["j"] = palatal,
["k"] = velar, ["x"] = velar, ["ɡ"] = velar,
}
text = rsub(text, "n(" .. separator_c .. "*)(.)",
function(stress, following)
return (nasal_assimilation[following] or "n") .. stress .. following
end
)
-- lateral assimilation before consonants
text = rsub(text, "l(" .. separator_c .. "*)(.)",
function(stress, following)
local l = "l"
if following == "t" or following == "d" then
-- dentialveolar
l = "l̪"
elseif following == "C" or following == "ʃ" or following == "đ" then
-- alveolopalatal
l = "lʲ"
elseif following == "ɟ" or following == "c" or following == "j" then
-- alveolopalatal
l = "ʎ"
end
return l .. stress .. following
end)
-- voiced fricatives are actually approximants
text = rsub(text, "([βðɣ])", "%1̞")
--nasalization of vowels
text = rsub(text, "([aeou])i([mnɲ])", "%1" .. TILDE .. "i" .. TILDE .. "%2")
text = rsub(text, "([ae])u([mnɲ])", "%1" .. TILDE .. "u" .. TILDE .. "%2")
text = rsub(text, "([aeiou])([mnɲ])", "%1" .. TILDE .. "%2")
end
--semivowels
text = rsub(text, "([aeou])" .. TILDE .. "([i])", "%1" .. TILDE .. "%2̯")
text = rsub(text, "([ae])" .. TILDE .. "([u])", "%1" .. TILDE .. "%2̯")
text = rsub(text, "([aeou][i])", "%1̯")
text = rsub(text, "([ae][u])", "%1̯")
text = rsub(text, "u̯i̯", "u̯i") --correct the mistake appeareing in words with aui, eui
-- convert fake symbols to real ones
local final_conversions = {
["S"] = "t͡s̺",
["Ś"] = "t͡s̻",
["s"] = "s̺",
["ś"] = "s̻",
["C"] = "t͡ʃ",
["X"] = "x",
["đ"] = "d͡ʒ",
["ʝ"] = "j"
}
text = rsub(text, "[SŚsśCXđʝ]", final_conversions)
-- remove # symbols at word and text boundaries
text = rsub(text, "#", "")
text = mw.ustring.toNFC(text)
--add technical characters to the end of the rhyme in order to parse the rhymes correctly
text = text .. J .. N
local differences = nil
if dialect == "gipuzkoan" then
differences = {
northern_different = northern_different,
need_bisc = need_bisc,
j_different = j_different,
}
end
local ret = {
text = text,
differences = differences,
}
return ret
end
function export.IPA_string(frame)
local iparams = {
[1] = {},
["style"] = {required = true},
["phonetic"] = {type = "boolean"},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local retval = export.IPA(iargs[1], iargs.style, iargs.phonetic)
return retval.text
end
-- Generate all relevant dialect pronunciations and group into styles.
local function express_all_styles(style_spec, dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
local need_bisc
local function express_style(hidden_tag, tag, representative_dialect, matching_styles)
matching_styles = matching_styles or representative_dialect
-- If style specified, make sure it matches the requested style.
local style_matches
if not style_spec then
style_matches = true
else
local style_parts = rsplit(matching_styles, "%-")
local or_styles = rsplit(style_spec, "%s*,%s*")
for _, or_style in ipairs(or_styles) do
local and_styles = rsplit(or_style, "%s*%+%s*")
local and_matches = true
for _, and_style in ipairs(and_styles) do
local negate
if and_style:find("^%-") then
and_style = and_style:gsub("^%-", "")
negate = true
end
local this_style_matches = false
for _, part in ipairs(style_parts) do
if part == and_style then
this_style_matches = true
break
end
end
if negate then
this_style_matches = not this_style_matches
end
if not this_style_matches then
and_matches = false
end
end
if and_matches then
style_matches = true
break
end
end
end
if not style_matches then
return
end
-- Fetch the representative dialect's pronunciation if not already present.
if not ret.pronun[representative_dialect] then
dodialect(ret, representative_dialect)
end
-- Insert the new style into the style group, creating the group if necessary.
local new_style = {
tag = tag,
pronun = ret.pronun[representative_dialect],
}
for _, hidden_tag_style in ipairs(ret.expressed_styles) do
if hidden_tag_style.tag == hidden_tag then
table.insert(hidden_tag_style.styles, new_style)
return
end
end
table.insert(ret.expressed_styles, {
tag = hidden_tag,
styles = {new_style},
})
end
-- For each type of difference, figure out if the difference exists in any of the given respellings. We do this by
-- generating the pronunciation for the dialect "gipuzkoan", for each respelling. In the process of
-- generating the pronunciation for a given respelling, it computes how the other dialects for that respelling
-- differ. Then we take the union of these differences across the respellings.
dodialect(ret, "gipuzkoan")
local differences = {}
for _, difftype in ipairs { "northern_different", "j_different", "need_bisc"} do
for _, pronun in ipairs(ret.pronun["gipuzkoan"]) do
if pronun.differences[difftype] then
differences[difftype] = true
end
end
end
local northern_different = differences.northern_different
local j_different = differences.j_different
need_bisc = differences.need_bisc
-- Now, based on the observed differences, figure out how to combine the individual dialects into styles and style groups.
if northern_different then
express_style("Navarro-Lapurdian","納瓦拉-拉布爾","northern")
if j_different then
express_style("Southern","吉普斯夸","gipuzkoan")
express_style("Southern","比斯開","biscayan")
express_style("Southern","納瓦拉","navarrese")
else
if need_bisc then
express_style("Southern","吉普斯夸、納瓦拉","gipuzkoan")
express_style("Southern","比斯開","biscayan")
else
express_style("Southern","南部","gipuzkoan")
end
end
else
if j_different then
express_style("Navarro-Lapurdian","納瓦拉-拉布爾","northern")
express_style("Southern","吉普斯夸","gipuzkoan")
express_style("Southern","比斯開","biscayan")
express_style("Southern","納瓦拉","navarrese")
elseif need_bisc then
express_style(false,"多數方言","gipuzkoan")
express_style(false,"比斯開","biscayan")
else
express_style(false,false,"gipuzkoan")
end
end
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
else
style_group.formatted, style_group.formatted_len =
format_style(style_group.tag, style_group.styles[1], i == 1)
for j, style in ipairs(style_group.styles) do
style.formatted, style.formatted_len =
format_style(style.tag, style, i == 1 and j == 1)
end
end
end
local maxlen = 0
for i, style_group in ipairs(expressed_styles) do
local this_len = style_group.formatted_len
if #style_group.styles > 1 then
for _, style in ipairs(style_group.styles) do
this_len = math.max(this_len, style.formatted_len)
end
end
maxlen = math.max(maxlen, this_len)
end
local lines = {}
local need_major_hack = false
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
table.insert(lines, style_group.formatted)
need_major_hack = false
else
local inline = '\n<div class="vsShow" style="display:none">\n' .. style_group.formatted .. "</div>"
local full_prons = {}
for _, style in ipairs(style_group.styles) do
table.insert(full_prons, style.formatted)
end
local full = '\n<div class="vsHide">\n' .. table.concat(full_prons, "\n") .. "</div>"
local em_length = math.floor(maxlen * 0.68) -- from [[Module:grc-pronunciation]]
table.insert(lines, '<div class="vsSwitcher" data-toggle-category="pronunciations" style="width: ' .. em_length .. 'em; max-width:100%;"><span class="vsToggleElement" style="float: right;"> </span>' .. inline .. full .. "</div>")
need_major_hack = true
end
end
-- major hack to get bullets working on the next line after a div box
return table.concat(lines, "\n") .. (need_major_hack and "\n<span></span>" or "")
end
local function dodialect_pronun(args, ret, dialect)
ret.pronun[dialect] = {}
for i, term in ipairs(args.terms) do
local phonemic, phonetic, differences
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
differences = construct_default_differences(dialect)
else
phonemic = export.IPA(term.term, dialect, false)
phonetic = export.IPA(term.term, dialect, true)
differences = phonemic.differences
phonemic = phonemic.text
phonetic = phonetic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[dialect][i] = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
differences = differences,
}
end
end
local function generate_pronun(args)
local function remove_technical_chars(text)
return rsub(text, "([JN])", "")
end
local function this_dodialect_pronun(ret, dialect)
dodialect_pronun(args, ret, dialect)
end
local ret = express_all_styles(args.style, this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]], and also create an approximation of the formatted version so that we can
-- compute the appropriate width of the HTML switcher div box that holds the different per-dialect variants.
-- NOTE: The code below constructs the formatted approximation out-of-order in some cases but that doesn't
-- currently matter because we assume all characters have the same width. If we change the width computation
-- in a way that requires the correct order, we need changes to the code below.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. remove_technical_chars(pronun.phonemic:gsub("%.", "")) .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. remove_technical_chars(pronun.phonetic) .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
ins(bracket_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no
-- hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Take the last two syllables for the ryhme, unless the word has a single syllable
local function convert_phonemic_to_rhyme(phonemic, short)
local syllables = rsplit(phonemic, "%.")
local rh = ""
if #syllables > 1 then
rh = short and syllables[#syllables] or table.concat(syllables, ".", #syllables - 1, #syllables)
else
rh = phonemic
end
return rsub(rh, "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("͡", "")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CFLEX then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. [[Austria-Hungría]])
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun[dialect]) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
differences = construct_default_differences(dialect),
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local put = require(put_module)
local function get_valid_prefixes()
local valid_prefixes = {}
for param_mod, _ in pairs(param_mods) do
table.insert(valid_prefixes, param_mod)
end
table.insert(valid_prefixes, "q")
table.insert(valid_prefixes, "qq")
table.insert(valid_prefixes, "a")
table.insert(valid_prefixes, "aa")
table.sort(valid_prefixes)
return valid_prefixes
end
local segments = put.parse_balanced_segment_run(arg, "<", ">")
local comma_separated_groups =
no_split_on_comma and {segments} or put.split_alternating_runs_on_comma(segments)
for _, group in ipairs(comma_separated_groups) do
local obj = generate_obj(group[1])
for j = 2, #group - 1, 2 do
if group[j + 1] ~= "" then
parse_err("Extraneous text '" .. group[j + 1] .. "' after modifier")
end
local modtext = group[j]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. group[j] .. "' isn't surrounded by angle brackets")
end
local prefix, val = modtext:match("^([a-z]+):(.*)$")
if not prefix then
local valid_prefixes = get_valid_prefixes()
for i, valid_prefix in ipairs(valid_prefixes) do
valid_prefixes[i] = "'" .. valid_prefix .. ":'"
end
parse_err("Modifier " .. group[j] .. " lacks a prefix, should begin with one of " ..
m_table.serialCommaJoin(valid_prefixes))
end
if prefix == "q" or prefix == "qq" or prefix == "a" or prefix == "aa" then
if not obj[prefix] then
obj[prefix] = {}
end
table.insert(obj[prefix], val)
elseif param_mods[prefix] then
local key = param_mods[prefix].item_dest or prefix
if obj[key] then
parse_err("Modifier '" .. prefix .. "' specified more than once")
end
local convert = param_mods[prefix].convert
if convert then
obj[key] = convert(val)
else
obj[key] = val
end
else
local valid_prefixes = get_valid_prefixes()
for i, valid_prefix in ipairs(valid_prefixes) do
valid_prefixes[i] = "'" .. valid_prefix .. "'"
end
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[j]
.. ", should be " .. m_table.serialCommaJoin(valid_prefixes))
end
end
table.insert(retval, obj)
end
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
file = file or arg
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{es-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local function remove_technical_chars(text)
return rsub(text, "([JN])", "")
end
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, nil, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, nil, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, nil, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, nil, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
local put
for i, respelling in ipairs(respellings) do
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
if respelling:find("<") then
if not put then
put = require(put_module)
end
local param_mods = {
pre = {},
post = {},
style = {},
bullets = {
convert = function(arg)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
insert = true,
flatten = true,
convert = function(arg) return parse_rhyme(arg, parse_err) end,
},
hyph = {
insert = true,
flatten = true,
convert = function(arg) return parse_hyph(arg, parse_err) end,
},
hmp = {
insert = true,
flatten = true,
convert = function(arg) return parse_homophone(arg, parse_err) end,
},
audio = {
insert = true,
flatten = true,
convert = function(arg) return parse_audio(arg, parse_err) end,
},
}
local function get_valid_prefixes()
local valid_prefixes = {}
for param_mod, _ in pairs(param_mods) do
table.insert(valid_prefixes, param_mod)
end
table.insert(valid_prefixes, "ref")
table.insert(valid_prefixes, "q")
table.insert(valid_prefixes, "qq")
table.insert(valid_prefixes, "a")
table.insert(valid_prefixes, "aa")
table.sort(valid_prefixes)
return valid_prefixes
end
local segments = put.parse_balanced_segment_run(respelling, "<", ">")
local comma_separated_groups = put.split_alternating_runs_on_comma(segments, ",")
local parsed = {terms = {}, audio = {}, rhyme = {}, hyph = {}, hmp = {}}
for j, group in ipairs(comma_separated_groups) do
local termobj = parse_respelling(group[1], pagename, parse_err)
for k = 2, #group - 1, 2 do
if group[k + 1] ~= "" then
parse_err("Extraneous text '" .. group[k + 1] .. "' after modifier")
end
local modtext = group[k]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. group[k] .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+):(.*)$")
if not prefix then
local valid_prefixes = get_valid_prefixes()
for i, valid_prefix in ipairs(valid_prefixes) do
valid_prefixes[i] = "'" .. valid_prefix .. ":'"
end
parse_err("Modifier " .. group[k] .. " lacks a prefix, should begin with one of " ..
m_table.serialCommaJoin(valid_prefixes))
end
if prefix == "ref" or prefix == "q" or prefix == "qq" or prefix == "a" or prefix == "aa" then
if not termobj[prefix] then
termobj[prefix] = {}
end
table.insert(termobj[prefix], arg)
elseif param_mods[prefix] then
if j < #comma_separated_groups then
parse_err("Modifier '" .. prefix .. "' should occur after the last comma-separated term")
end
if not param_mods[prefix].insert and parsed[prefix] then
parse_err("Modifier '" .. prefix .. "' occurs twice, second occurrence " .. group[k])
end
local converted
if param_mods[prefix].convert then
converted = param_mods[prefix].convert(arg)
else
converted = arg
end
if param_mods[prefix].insert then
if param_mods[prefix].flatten then
for _, obj in ipairs(converted) do
table.insert(parsed[prefix], obj)
end
else
table.insert(parsed[prefix], converted)
end
else
parsed[prefix] = converted
end
else
local valid_prefixes = get_valid_prefixes()
for i, valid_prefix in ipairs(valid_prefixes) do
valid_prefixes[i] = "'" .. valid_prefix .. "'"
end
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. group[k]
.. ", should be " .. m_table.serialCommaJoin(valid_prefixes))
end
end
table.insert(parsed.terms, termobj)
end
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic or term.raw_phonetic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
-- It's possible the pronunciation for a passed-in dialect was never generated. This happens e.g. with
-- {{es-pr|cebolla<style:seseo>}}. The initial call to generate_pronun() fails to generate a pronunciation
-- for the dialect 'distinction-yeismo' because the pronunciation of 'cebolla' differs between distincion
-- and seseo and so the seseo style restriction rules out generation of pronunciation for distincion
-- dialects (other than 'gipuzkoan', which always gets generated so as to determine on which axes
-- the dialects differ). However, when generating the rhyme, it is based only on -olla, whose pronunciation
-- does not differ between distincion and seseo, but does differ between lleismo and yeismo, so it needs to
-- generate a yeismo-specific rhyme, and 'distincion-yeismo' is the representative dialect for yeismo in the
-- situation where distincion and seseo do not have distinct results (based on the following line in
-- express_all_styles()):
-- express_style(false, "most of Spain and Latin America", "distincion-yeismo", "distincion-seseo-yeismo")
-- In this case we need to generate the missing overall pronunciation ourselves since we need it to generate
-- the dialect-specific rhyme pronunciation.
if not parsed.pronun.pronun[dialect] then
dodialect_pronun(parsed, parsed.pronun, dialect)
end
for _, pronun in ipairs(parsed.pronun.pronun[dialect]) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic, false)
local short_rhyme = convert_phonemic_to_rhyme(pronun.phonemic, true)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun[dialect]) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
local rh2 = rsub(rhyme, "ts̻", "")
local rhyme_diffs = nil
if dialect == "gipuzkoan" then
rhyme_diffs = {}
if rfind(rh2,"s̻") then
rhyme_diffs.need_bisc = true
end
if rfind(rhyme,"ts̺") then
rhyme_diffs.need_bisc = true
end
if rfind(rhyme,"J") then
rhyme_diffs.j_different = true
end
if rfind(rhyme,"N") then
rhyme_diffs.northern_different = true
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = remove_technical_chars(rhyme),
num_syl = {num_syl},
differences = rhyme_diffs,
})
if rhyme ~= short_rhyme then
table.insert(rhyme_ret.pronun[dialect], {
rhyme = remove_technical_chars(short_rhyme),
num_syl = {num_syl},
differences = rhyme_diffs, --we'll keep the rhyme in all dialects even if it's repeated (for clarity, see [[zinen]])
})
end
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(parsed.style, dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret, dialect)
end
parsed.rhyme = express_all_styles(parsed.style, this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret, dialect)
end
overall_rhyme = express_all_styles(parsed.style, dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs}
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require("Module:audio").format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
tcwtzle62qkht0q6ug0t0advvv2e9am
Module:Zlw-opl-IPA
828
2199351
9909113
8797860
2026-09-29T07:28:47Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909113
Scribunto
text/plain
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local lang = require("Module:languages").getByCode("zlw-opl")
local rsub = mw.ustring.gsub
local rlower = mw.ustring.lower
local rfind = mw.ustring.find
local consonants = "bdfɡxjkɫlmnprstvzʒʃɲɕʑʲ"
local C = "[" .. consonants .. "]"
local vowels = "aiɨuɛɔæ̃ɑ̃"
local V = "[" .. vowels .. "]"
local voiceless_consonant = "ptsʃɕkx"
local voiced_consonant = "bdzʒʑɡ"
local function track(page)
require("Module:debug").track("zlw-opl-IPA/" .. page)
return true
end
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local digraphs = {
["ch"] = "x",
["cz"] = "tʃ",
["b́"] = "bʲ",
["rz"] = "R",
["sz"] = "ʃ"
}
local devoicing = {
["b"] = "p",
["d"] = "t",
["z"] = "s",
["ʒ"] = "ʃ",
["ʑ"] = "ɕ",
["ɡ"] = "k",
["v"] = "f"
}
local voicing = {
["p"] = "b",
["t"] = "d",
["s"] = "z",
["ʃ"] = "ʒ",
["ɕ"] = "ʑ",
["k"] = "ɡ",
["f"] = "v"
}
local function flatmap(items, fun)
local new = {}
for _, item in ipairs(items) do
local results = fun(item)
for _, result in ipairs(results) do
table.insert(new, result)
end
end
return new
end
local phon = {
["a"] = "a",
["ą"] = "ɑ̃",
["b"] = "b",
["c"] = "ts",
["ć"] = "tɕ",
["d"] = "d",
["e"] = "ɛ",
["ę"] = "æ̃",
["f"] = "f",
["g"] = "ɡ",
["h"] = "x",
["i"] = "i",
["j"] = "j",
["k"] = "k",
["l"] = "lʲ",
["ł"] = "ɫ",
["m"] = "m",
["ḿ"] = "mʲ",
["n"] = "n",
["ń"] = "ɲ",
["o"] = "ɔ",
["ó"] = "ɔː",
["p"] = "p",
["ṕ"] = "pʲ",
["r"] = "r",
["s"] = "s",
["ś"] = "ɕ",
["t"] = "t",
["u"] = "u",
["w"] = "v",
["ẃ"] = "vʲ",
["y"] = "ɨ",
["z"] = "z",
["ż"] = "ʒ",
["ź"] = "ʑ",
[":"] = "ː",
["?"] = "(ː)"
}
function export.phonemic(text, post)
text = rlower(text)
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
-- basic phonology
text = rsub(text, "ts", "tss")
for digraph, replacement in pairs(digraphs) do
text = rsub(text, digraph, replacement)
end
text = rsub(text, "r̝", "R")
text = rsub(text, ".", phon)
text = rsub(text, "si(" .. V .. ")", "ɕ%1")
text = rsub(text, "zi(" .. V .. ")", "ʑ%1")
text = rsub(text, "si", "ɕi")
text = rsub(text, "zi", "ʑi")
-- palatalisation
text = rsub(text, "(" .. C .. ")i(" .. V .. ")", "%1j%2")
text = rsub(text, "Ri(" .. V .. ")", "Rj%1")
text = rsub(text, "ni(" .. C .. ")", "ɲi%1")
text = rsub(text, "nʲ", "ɲ")
text = rsub(text, "nj", "ɲ")
text = rsub(text, "sʲ", "ɕ")
text = rsub(text, "zʲ", "ʑ")
text = rsub(text, "jʲ", "j")
local function voice_backward(sound, following)
return voicing[sound] .. following
end
local function devoice_backward(sound, following)
return devoicing[sound] .. following
end
local function voice_forward(preceding, sound)
return preceding .. voicing[sound]
end
local function devoice_forward(preceding, sound)
return preceding .. devoicing[sound]
end
local function final_devoicing(sound, following)
return devoicing[sound] .. following .. "#"
end
text = rsub_repeatedly(text, "([" .. voiced_consonant .. "v])([Rʲ]?)#", final_devoicing)
text = rsub_repeatedly(text, "v([" .. voiceless_consonant .. "])", "f%1")
text = rsub_repeatedly(text, "f([" .. voiced_consonant .. "])", "v%1")
text = rsub_repeatedly(text, "([" .. voiced_consonant .. "])([f])", voice_forward)
text = rsub_repeatedly(text, "([" .. voiceless_consonant .. "])([v])", devoice_forward)
text = rsub_repeatedly(text, "([" .. voiced_consonant .. "])([" .. voiceless_consonant .. "])", devoice_backward)
text = rsub_repeatedly(text, "([" .. voiceless_consonant .. "])([" .. voiced_consonant .. "])", voice_backward)
-- rzy
text = rsub(text, "Rɨ", "Ri")
text = rsub(text, "(" .. V .. ")'", "'%1")
text = rsub(text, "#(" .. C .. "+ʲ?)'", "#'%1")
text = rsub(text, " (" .. C .. "+ʲ?)'", " '%1")
text = rsub(text, "(" .. C .. "ʲ?)'", "'%1")
text = rsub(text, "t's", "'ts")
text = rsub(text, "'", "ˈ")
text = rsub(text, "-", "");
-- affricates
text = rsub(text, "[ʃʒ]", "%1ʲ")
-- affricates
text = rsub(text, "t([sɕʃ])", "t͡%1")
text = rsub(text, "d([zʑʒ])", "d͡%1")
-- suffixes
text = rsub(text, "^ˈ%-", "-")
-- resolution
text = rsub(text, "jʲ", "j")
text = rsub(text, "ʃʲ#", "ʃ#")
text = rsub(text, "R", "r̝")
local variants = {text}
local function flatmap_and_sub_pre(from, to1, to2)
variants =
flatmap(
variants,
function(item)
if rfind(item, from) then
local retval = {rsub_repeatedly(item, from, to1)}
if to2 then
m_table.insertIfNot(retval, rsub_repeatedly(item, from, to2))
end
return retval
else
return {item}
end
end
)
end
if post ~= nil then
flatmap_and_sub_pre("iː", "i")
flatmap_and_sub_pre("ɨː", "ɨ")
flatmap_and_sub_pre("uː", "u")
flatmap_and_sub_pre("ɔː", "o")
flatmap_and_sub_pre("ɛː", "e")
flatmap_and_sub_pre("aː", "ɒ")
flatmap_and_sub_pre("i%(ː%)", "i")
flatmap_and_sub_pre("ɨ%(ː%)", "ɨ")
flatmap_and_sub_pre("u%(ː%)", "u")
flatmap_and_sub_pre("a%(ː%)", "a", "ɒ")
flatmap_and_sub_pre("ɔ%(ː%)", "ɔ", "o")
flatmap_and_sub_pre("ɛ%(ː%)", "ɛ", "e")
end
flatmap_and_sub_pre("#", "")
return variants
end
function export.IPA(frame)
local terms = {}
local args = frame:getParent().args
local currentTitle = mw.title.getCurrentTitle().text
for _, term in ipairs(args) do
if term == currentTitle then track("redundant") end
table.insert(terms, term)
end
if #terms == 0 then
terms = {currentTitle}
end
local results1, results2 = {}, {}
for _, term in ipairs(terms) do
local variantsPre = export.phonemic(term)
for _, variant in ipairs(variantsPre) do
table.insert(results1, {pron = "/" .. variant .. "/"})
end
local variantsPost = export.phonemic(term, true)
for _, variant in ipairs(variantsPost) do
table.insert(results2, {pron = "/" .. variant .. "/"})
end
end
results1[1].q = {"10世紀—15世紀"}
results2[1].q = {"15世紀"}
return "*" .. m_IPA.format_IPA_full { lang = lang, items = results1 }
.. "\n*" .. m_IPA.format_IPA_full { lang = lang, items = results2 }
end
return export
3kroncpk3l9q7lg1qp5vag62rf06weh
Module:Decorations
828
2218731
9909024
9137938
2026-09-29T07:21:46Z
TongcyBot
83009
TongcyBot移动页面[[Module:Pron qualifier]]至[[Module:Decorations]]:同步 enwikt:pron qualifier → decorations
9137938
Scribunto
text/plain
local export = {}
local labels_module = "Module:labels"
local qualifier_module = "Module:qualifier"
local references_module = "Module:references"
local function track(page)
require("Module:debug/track")("pron qualifier/" .. page)
return true
end
--[==[
This function is used by any module that wants to add support for (some subset of) left and right regular and accent
qualifiers, labels and references to a template that specifies a pronunciation or related property. It is currently used
by [[Module:IPA]], [[Module:rhymes]], [[Module:hyphenation]], [[Module:homophones]] and various lang-specific modules
such as [[Module:es-pronunc]] (for specifying pronunciation, rhymes, hyphenation, homophones and audio in {{tl|es-pr}}).It should potentially also be used in {{tl|audio}}. To reduce memory usage, the caller should check that any qualifiers
exist before loading the module.
`data` is a structure containing the following fields:
* `q`: List of left regular qualifiers, each a string.
* `qq`: List of right regular qualifiers, each a string.
* `qualifiers`: List of qualifiers, each a string, for compatibility. If `qualifiers_right` is given, these are
right qualifiers, otherwise left qualifiers. If both `qualifiers` and `q`/`qq` (depending on the value of
`qualifiers_right`) are non-{nil}, `qualifiers` is ignored.
* `qualifiers_right`: If specified, qualifiers in `qualifiers` are placed to the right, otherwise the left. See above.
* `a`: List of left accent qualifiers, each a string.
* `aa`: List of right accent qualifiers, each a string.
* `l`: List of left labels, each a string.
* `ll`: List of right labels, each a string.
* `refs`: {nil} or a list of references or reference specs to add directly after the text; the value of a list item
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 {{cd|<nowiki><ref name="foo">...</ref></nowiki>}} or {{cd|<nowiki><ref name="foo" /></nowiki>}})
and/or `group` (the group of the reference, as in {{cd|<nowiki><ref name="foo" group="bar">...</ref></nowiki>}} or
{{cd|<nowiki><ref name="foo" group="bar"/></nowiki>}}); this uses a parser function to format the reference
appropriately and insert a footnote number that hyperlinks to the actual reference, located in the
{{cd|<nowiki><references /></nowiki>}} section.
* `lang`: Language object for accent qualifiers.
* `text`: The text to wrap with qualifiers.
*` raw`: Don't do any CSS wrapping of the formatted text.
The order of qualifiers and labels, on both the left and right, is (1) labels, (2) accent qualifiers, (3) regular
qualifiers. This goes in order of relative importance.
]==]
function export.format_qualifiers(data)
if not data.text then
error("Missing `data.text`; did you try to pass `text` or `qualifiers_right` as separate params?")
end
if not data.lang then
track("nolang")
end
local text = data.text
-- Format the qualifiers and labels that go either before or after the main text. They are ordered as follows, on
-- both the left and the right: (1) labels, (2) accent qualifiers, (3) regular qualifiers. This puts the different
-- types of qualifiers/labels in order of relative importance. Return nil if no qualifiers or labels, otherwise
-- a string containing all formatted qualifiers and labels surrounded by parens.
local function format_qualifier_like(labels, accent_qualifiers, qualifiers)
local has_qualifiers = qualifiers and qualifiers[1]
local has_accent_qualifiers = accent_qualifiers and accent_qualifiers[1]
local has_labels = labels and labels[1]
if not has_qualifiers and not has_accent_qualifiers and not has_labels then
return nil
end
local qualifier_like_parts = {}
local function ins(part)
table.insert(qualifier_like_parts, part)
end
local function format_label_like(labels, mode)
return require(labels_module).show_labels {
lang = data.lang,
labels = labels,
nocat = true,
mode = mode,
open = false,
close = false,
no_ib_content = true,
no_track_already_seen = true,
ok_to_destructively_modify = true, -- doesn't apply to `labels`
raw = data.raw,
}
end
local m_qualifier = require(qualifier_module)
if has_labels then
ins(format_label_like(labels))
end
if has_accent_qualifiers then
ins(format_label_like(accent_qualifiers, "accent"))
end
if has_qualifiers then
ins(m_qualifier.format_qualifiers {
qualifiers = qualifiers,
open = false,
close = false,
no_ib_content = true,
raw = data.raw,
})
end
local qualifier_inside
local function wrap_qualifier_css(txt, suffix)
if data.raw then
return txt
else
return m_qualifier.wrap_qualifier_css(txt, suffix)
end
end
if qualifier_like_parts[2] then
qualifier_inside = table.concat(qualifier_like_parts, wrap_qualifier_css(",", "comma"))
else
qualifier_inside = qualifier_like_parts[1]
end
qualifier_like_parts = {}
ins(wrap_qualifier_css("(", "brac"))
ins(wrap_qualifier_css(qualifier_inside, "content"))
ins(wrap_qualifier_css(")", "brac"))
return table.concat(qualifier_like_parts)
end
if data.refs then
text = text .. require(references_module).format_references(data.refs)
end
local leftq = format_qualifier_like(data.l, data.a, data.q or not data.qualifiers_right and data.qualifiers)
local rightq = format_qualifier_like(data.ll, data.aa, data.qq or data.qualifiers_right and data.qualifiers)
if leftq then
text = leftq .. " " .. text
end
if rightq then
text = text .. " " .. rightq
end
return text
end
return export
mof7br90g3philb0xxng6ecpc8r9gti
9909026
9909024
2026-09-29T07:21:49Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909026
Scribunto
text/plain
local export = {}
local gender_and_number_module = "Module:gender and number"
local labels_module = "Module:labels"
local qualifier_module = "Module:qualifier"
local references_module = "Module:references"
local function track(page)
require("Module:debug/track")("decorations/" .. page)
return true
end
--[==[
This function is used by any module that wants to add support for adding decorations (i.e. left and right regular and
accent qualifiers, labels and references, and potentially other types of decorations in the future) to an item, where
an "item" is any string of text that might want to be decorated. It is currently used for:
# pronunciations and other pronunciation-related items, such as rhymes, hyphenations and homophones, as implemented in
[[Module:IPA]], [[Module:rhymes]], [[Module:hyphenation]], [[Module:homophones]] and various language-specific modules
such as [[Module:es-pronunc]];
# arbitrary links, as implemented in [[Module:links]];
# headwords, as implemented in [[Module:headword]];
# gender/number specs, as implemented in [[Module:gender and number]];
# *nyms of all sorts (e.g. synonyms, antonyms, hypernyms, hyponyms, coordinate terms, etc.) as implemented in
[[Module:nyms]];
# affixes of all sorts, as implemented in [[Module:affix]];
# affix usage examples, as implemented in [[Module:affixusex]];
# column templates as such as {{tl|col}} and {{tl|col3}}, as implemented in [[Module:columns]];
# names (surnames, given names, patronymics, etc.) as implemented in [[Module:names]];
# object-usage qualifiers such as {{tl|+obj}}, as implemented in [[Module:object usage]];
# various inflection templates such as {{tl|ar-conj}} in [[Module:ar-verb]];
# demonyms, as implemented in [[Module:demonym]];
and several other places.
Modules that implement templates that occur frequently on a page, such as [[Module:headword]] and [[Module:links]],
should consider checking that any decorations exist before loading the module. This is less necessary for other,
less-used templates, as this module is not very heavy and does appropriate checks itself to make sure that any
decorations are present before taking action.
`data` is a structure containing the following fields:
* `q`: Optional list of left regular qualifiers, each a string.
* `qq`: Optional list of right regular qualifiers, each a string.
* `qualifiers`: Optional list of qualifiers, each a string, for compatibility. If `qualifiers_right` is given, these are
right qualifiers, otherwise left qualifiers. If both `qualifiers` and `q`/`qq` (depending on the value of
`qualifiers_right`) are non-{nil}, `qualifiers` is ignored.
* `qualifiers_right`: If specified, qualifiers in `qualifiers` are placed to the right, otherwise the left. See above.
* `a`: Optional list of left accent qualifiers, each a string.
* `aa`: Optional list of right accent qualifiers, each a string.
* `l`: Optional list of left labels, each a string.
* `ll`: Optional list of right labels, each a string.
* `refs`: Optional list of references or reference specs to add directly after the text; the value of a list item 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>`); this uses a parser function to format the reference appropriately
and insert a footnote number that hyperlinks to the actual reference, located in the `<nowiki><references /></nowiki>`
section.
* `genders`: Optional list of gender/number specs as accepted by [[Module:gender and number]].
* `lang`: Language object for accent qualifiers.
* `text`: The text to wrap with qualifiers.
*` raw`: Don't do any CSS wrapping of the formatted text.
The order of qualifiers and labels, on both the left and right, is (1) labels, (2) accent qualifiers, (3) regular
qualifiers. This goes in order of relative importance. References and genders go on the right, inside of labels and
qualifiers, with references before genders.
]==]
function export.format_decorations(data)
if not data.text then
error("Missing `data.text`; did you try to pass `text` or `qualifiers_right` as separate params?")
end
if not data.lang then
track("nolang")
end
local text = data.text
-- Format the qualifiers and labels that go either before or after the main text. They are ordered as follows, on
-- both the left and the right: (1) labels, (2) accent qualifiers, (3) regular qualifiers. This puts the different
-- types of qualifiers/labels in order of relative importance. Return nil if no qualifiers or labels, otherwise
-- a string containing all formatted qualifiers and labels surrounded by parens.
local function format_qualifier_like(labels, accent_qualifiers, qualifiers)
local has_qualifiers = qualifiers and qualifiers[1]
local has_accent_qualifiers = accent_qualifiers and accent_qualifiers[1]
local has_labels = labels and labels[1]
if not has_qualifiers and not has_accent_qualifiers and not has_labels then
return nil
end
local qualifier_like_parts = {}
local function ins(part)
table.insert(qualifier_like_parts, part)
end
local function format_label_like(labels, mode)
return require(labels_module).show_labels {
lang = data.lang,
labels = labels,
nocat = true,
mode = mode,
open = false,
close = false,
no_ib_content = true,
no_track_already_seen = true,
ok_to_destructively_modify = true, -- doesn't apply to `labels`
raw = data.raw,
}
end
local m_qualifier = require(qualifier_module)
if has_labels then
ins(format_label_like(labels))
end
if has_accent_qualifiers then
ins(format_label_like(accent_qualifiers, "accent"))
end
if has_qualifiers then
ins(m_qualifier.format_qualifiers {
qualifiers = qualifiers,
open = false,
close = false,
no_ib_content = true,
raw = data.raw,
})
end
local qualifier_inside
local function wrap_qualifier_css(txt, suffix)
if data.raw then
return txt
else
return m_qualifier.wrap_qualifier_css(txt, suffix)
end
end
if qualifier_like_parts[2] then
-- l10n: 全形逗號,不另加空格
qualifier_inside = table.concat(qualifier_like_parts, wrap_qualifier_css(",", "comma"))
else
qualifier_inside = qualifier_like_parts[1]
end
qualifier_like_parts = {}
ins(wrap_qualifier_css("(", "brac"))
ins(wrap_qualifier_css(qualifier_inside, "content"))
ins(wrap_qualifier_css(")", "brac"))
return table.concat(qualifier_like_parts)
end
if data.refs then
text = text .. require(references_module).format_references(data.refs)
end
if data.genders and data.genders[1] then
-- NOTE, format_genders() returns a second value (categories) but we ignore it.
text = text .. " " .. require(gender_and_number_module).format_genders(data.genders, data.lang)
end
if data.qualifiers then
track("old-qualifiers")
end
local leftq = format_qualifier_like(data.l, data.a, data.q or not data.qualifiers_right and data.qualifiers)
local rightq = format_qualifier_like(data.ll, data.aa, data.qq or data.qualifiers_right and data.qualifiers)
if leftq then
text = leftq .. " " .. text
end
if rightq then
text = text .. " " .. rightq
end
return text
end
function export.format_qualifiers(data)
track("format_qualifiers")
return export.format_decorations(data)
end
return export
5nnnaz5slgy48jyhknvnb5tkq538sow
9909126
9909026
2026-09-29T07:42:06Z
TongcyBot
83009
同步 enwikt:舊介面改為報錯
9909126
Scribunto
text/plain
local export = {}
local gender_and_number_module = "Module:gender and number"
local labels_module = "Module:labels"
local qualifier_module = "Module:qualifier"
local references_module = "Module:references"
local function track(page)
require("Module:debug/track")("decorations/" .. page)
return true
end
--[==[
This function is used by any module that wants to add support for adding decorations (i.e. left and right regular and
accent qualifiers, labels and references, and potentially other types of decorations in the future) to an item, where
an "item" is any string of text that might want to be decorated. It is currently used for:
# pronunciations and other pronunciation-related items, such as rhymes, hyphenations and homophones, as implemented in
[[Module:IPA]], [[Module:rhymes]], [[Module:hyphenation]], [[Module:homophones]] and various language-specific modules
such as [[Module:es-pronunc]];
# arbitrary links, as implemented in [[Module:links]];
# headwords, as implemented in [[Module:headword]];
# gender/number specs, as implemented in [[Module:gender and number]];
# *nyms of all sorts (e.g. synonyms, antonyms, hypernyms, hyponyms, coordinate terms, etc.) as implemented in
[[Module:nyms]];
# affixes of all sorts, as implemented in [[Module:affix]];
# affix usage examples, as implemented in [[Module:affixusex]];
# column templates as such as {{tl|col}} and {{tl|col3}}, as implemented in [[Module:columns]];
# names (surnames, given names, patronymics, etc.) as implemented in [[Module:names]];
# object-usage qualifiers such as {{tl|+obj}}, as implemented in [[Module:object usage]];
# various inflection templates such as {{tl|ar-conj}} in [[Module:ar-verb]];
# demonyms, as implemented in [[Module:demonym]];
and several other places.
Modules that implement templates that occur frequently on a page, such as [[Module:headword]] and [[Module:links]],
should consider checking that any decorations exist before loading the module. This is less necessary for other,
less-used templates, as this module is not very heavy and does appropriate checks itself to make sure that any
decorations are present before taking action.
`data` is a structure containing the following fields:
* `q`: Optional list of left regular qualifiers, each a string.
* `qq`: Optional list of right regular qualifiers, each a string.
* `a`: Optional list of left accent qualifiers, each a string.
* `aa`: Optional list of right accent qualifiers, each a string.
* `l`: Optional list of left labels, each a string.
* `ll`: Optional list of right labels, each a string.
* `refs`: Optional list of references or reference specs to add directly after the text; the value of a list item 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>`); this uses a parser function to format the reference appropriately
and insert a footnote number that hyperlinks to the actual reference, located in the `<nowiki><references /></nowiki>`
section.
* `genders`: Optional list of gender/number specs as accepted by [[Module:gender and number]].
* `lang`: Language object for accent qualifiers.
* `text`: The text to wrap with qualifiers.
*` raw`: Don't do any CSS wrapping of the formatted text.
The order of qualifiers and labels, on both the left and right, is (1) labels, (2) accent qualifiers, (3) regular
qualifiers. This goes in order of relative importance. References and genders go on the right, inside of labels and
qualifiers, with references before genders.
]==]
function export.format_decorations(data)
if not data.text then
error("Missing `data.text`; did you try to pass `text` as a separate param?")
end
if not data.lang then
track("nolang")
end
local text = data.text
-- Format the qualifiers and labels that go either before or after the main text. They are ordered as follows, on
-- both the left and the right: (1) labels, (2) accent qualifiers, (3) regular qualifiers. This puts the different
-- types of qualifiers/labels in order of relative importance. Return nil if no qualifiers or labels, otherwise
-- a string containing all formatted qualifiers and labels surrounded by parens.
local function format_qualifier_like(labels, accent_qualifiers, qualifiers)
local has_qualifiers = qualifiers and qualifiers[1]
local has_accent_qualifiers = accent_qualifiers and accent_qualifiers[1]
local has_labels = labels and labels[1]
if not has_qualifiers and not has_accent_qualifiers and not has_labels then
return nil
end
local qualifier_like_parts = {}
local function ins(part)
table.insert(qualifier_like_parts, part)
end
local function format_label_like(labels, mode)
return require(labels_module).show_labels {
lang = data.lang,
labels = labels,
nocat = true,
mode = mode,
open = false,
close = false,
no_ib_content = true,
no_track_already_seen = true,
ok_to_destructively_modify = true, -- doesn't apply to `labels`
raw = data.raw,
}
end
local m_qualifier = require(qualifier_module)
if has_labels then
ins(format_label_like(labels))
end
if has_accent_qualifiers then
ins(format_label_like(accent_qualifiers, "accent"))
end
if has_qualifiers then
ins(m_qualifier.format_qualifiers {
qualifiers = qualifiers,
open = false,
close = false,
no_ib_content = true,
raw = data.raw,
})
end
local qualifier_inside
local function wrap_qualifier_css(txt, suffix)
if data.raw then
return txt
else
return m_qualifier.wrap_qualifier_css(txt, suffix)
end
end
if qualifier_like_parts[2] then
-- l10n: 全形逗號,不另加空格
qualifier_inside = table.concat(qualifier_like_parts, wrap_qualifier_css(",", "comma"))
else
qualifier_inside = qualifier_like_parts[1]
end
qualifier_like_parts = {}
ins(wrap_qualifier_css("(", "brac"))
ins(wrap_qualifier_css(qualifier_inside, "content"))
ins(wrap_qualifier_css(")", "brac"))
return table.concat(qualifier_like_parts)
end
if data.refs then
text = text .. require(references_module).format_references(data.refs)
end
if data.genders and data.genders[1] then
-- NOTE, format_genders() returns a second value (categories) but we ignore it.
text = text .. " " .. require(gender_and_number_module).format_genders(data.genders, data.lang)
end
if data.qualifiers then
-- FIXME: added 2026-09-18; consider removing eventually.
error("`.qualifiers` is no longer supported; change the code to use `.q` or `.qq`")
end
local leftq = format_qualifier_like(data.l, data.a, data.q)
local rightq = format_qualifier_like(data.ll, data.aa, data.qq)
if leftq then
text = leftq .. " " .. text
end
if rightq then
text = text .. " " .. rightq
end
return text
end
function export.format_qualifiers(...)
-- FIXME: Added 2026-09-17. Remove after a month or less.
error("use format_decorations instead")
end
return export
q3uh8979ujbnnlf1ohu0hkz863jm1hm
Module:An-pron
828
2246850
9909041
9023381
2026-09-29T07:24:15Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909041
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
--TODO
--This code is adapted from [[Module:pronunc]], which can output multiple dialectal pronounciations from a single spelling. This module will
--return a single pronunciation per spelling in all cases, so there are parts of the code which can be simplified.
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local decorations_module = "Module:decorations"
local put_module = "Module:parse utilities"
local force_cat = false -- for testing
local lang = require("Module:languages").getByCode("an")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeiouüyAEIOUÜY" -- vowel; include y so we get single-word y correct and for syllabifying from spelling
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR .. CFLEX
local accent_c = "[" .. accent .. "]"
local stress = AC .. GR
local stress_c = "[" .. AC .. GR .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class including h
local C_NOT_H = "[^" .. vowel .. separator .. "h]" -- consonant class not including h
local C_OR_WORDSEP = "[^" .. vowel .. separator_not_wordsep .. "]" -- consonant class including h, or word separator
local T = "[^" .. vowel .. "lrɾjw" .. separator .. "]" -- obstruent or nasal
local unstressed_words = m_table.listToSet({ -- TODO add more
"o", "ro",-- definite articles
"y", --conjunction
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ñ and ü
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
-- Main syllable-division algorithm. Can be called either directly on spelling (when hyphenating) or after
-- non-trivial processing of respelling in the direction of pronunciation (when generating pronunciation).
local function syllabify_from_spelling_or_pronun(text, is_spelling)
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels (but don't divide a VhV
-- sequence; [[prohibir]] should be prohi.bir). Then move the syllable division marker leftwards over clusters that
-- can form onsets.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C_NOT_H .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
-- NOTE: When run on pronun, we have already eliminated c and v, but not when run on spelling.
-- When run on pronun, don't include r, which at this point represents the trill.
local cluster_r = is_spelling and "rɾ" or "ɾ"
-- Don't divide Cl or Cr where C is a stop or fricative, except for dl.
text = rsub(text, "([pbfvkctg])%.([l" .. cluster_r .. "])", ".%1%2")
text = text:gsub("d%.([" .. cluster_r .. "])", ".d%1")
-- Don't divide ch, sh, ph, th, dh, fh, kh or gh. Do allow bh to be divided ([[subhumano]], [[subhúmedo]], etc.).
text = rsub(text, "([csptdfkg])%.h", ".%1h")
-- Don't divide rr.
text = rsub(text, "r.r", ".rr")
-- Don't divide tz
text = rsub(text, "t%.z", ".tz")
-- Part 2: Divide hiatuses. Any aeo, or stressed iuüy, should be syllabically divided from a following aeo or
-- stressed iuüy. Also divide ii and uu sequences ([[antiincendios]], [[shiita]], [[vacuum]]). Note that words with
-- ii or uu next to a vowel (e.g. [[hawaiiano]]) will not make it to this point unchanged; the i or u adjacent to
-- a vowel (or the second one if both are adjacent to vowels) will get converted to a consonant symbol (temporarily
-- when syllabifying spelling).
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?[aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeoAEO]" .. accent_c .. "*)(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub(text, "([iuüyIUÜY]" .. stress_c .. ")(h?[aeo])", "%1.%2")
text = rsub_repeatedly(text, "([iuüyIUÜY]" .. stress_c .. ")(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "([iI]" .. accent_c .. "*)(h?i)", "%1.%2")
text = rsub_repeatedly(text, "([uU]" .. accent_c .. "*)(h?u)", "%1.%2")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants.
local TEMP_I = u(0xFFF1)
local TEMP_U = u(0xFFF2)
local TEMP_Y_CONS = u(0xFFF3)
local TEMP_QU = u(0xFFF4)
local TEMP_QU_CAPS = u(0xFFF5)
local TEMP_GU = u(0xFFF6)
local TEMP_GU_CAPS = u(0xFFF7)
local TEMP_H = u(0xFFF8)
local TEMP_NY = u(0xFFF9)
local TEMP_NY_CAPS = u(0xFFFA)
local TEMP_LL = u(0xFFFB)
local TEMP_LL_CAPS = u(0xFFFC)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables. TODO fix hyphenation of words like paral·lel
text = rsub(text, "ny", TEMP_NY)
text = rsub(text, "Ny", TEMP_NY_CAPS)
text = rsub(text, "ll", TEMP_LL)
text = rsub(text, "Ll", TEMP_LL_CAPS)
text = text:gsub("%.", SYLDIV)
text = rsub(text, "y(" .. V .. ")", TEMP_Y_CONS .. "%1")
-- We don't want to break -sh- except in desh-, e.g. [[deshuesar]], [[deshonra]], [[deshecho]]. Normally, -sh- is
-- automatically preserved, so we replace the h with a temporary symbol to avoid this.
text = text:gsub("^([Dd]es)h", "%1" .. TEMP_H)
text = text:gsub("([ %-][Dd]es)h", "%1" .. TEMP_H)
-- qu mostly handled correctly automatically, but not in quietud
text = rsub(text, "qu(" .. V .. ")", TEMP_QU .. "%1")
text = rsub(text, "Qu(" .. V .. ")", TEMP_QU_CAPS .. "%1")
text = rsub(text, "gu(" .. V .. ")", TEMP_GU .. "%1")
text = rsub(text, "Gu(" .. V .. ")", TEMP_GU_CAPS .. "%1")
local vowel_to_glide = { ["i"] = TEMP_I, ["u"] = TEMP_U }
-- i and u between vowels -> consonant-like substitutions: [[paranoia]], [[baiano]], [[abreuense]], [[alauita]],
-- [[Malaui]], etc.; also with h, as in [[marihuana]], [[parihuela]], [[antihielo]], [[pelluhuano]], [[náhuatl]],
-- etc. When we do this we need to help the syllabification particularly of words with -hiV- and -huV- in them,
-- otherwise we get e.g. 'an.tih.ie.lo' because we converted the i following the h to a consonant. Add .* at the
-- beginning so we go right-to-left, in the case of [[hawaiiano]] -> ha.wai.iano.
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*)(h?)([iu])(" .. V .. ")",
function (v1, h, iu, v2) return v1 .. "." .. h .. vowel_to_glide[iu] .. v2 end
)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
text = text:gsub(SYLDIV, ".")
text = text:gsub(TEMP_I, "i")
text = text:gsub(TEMP_U, "u")
text = text:gsub(TEMP_Y_CONS, "y")
text = text:gsub(TEMP_QU, "qu")
text = text:gsub(TEMP_QU_CAPS, "Qu")
text = text:gsub(TEMP_GU, "gu")
text = text:gsub(TEMP_GU_CAPS, "Gu")
text = text:gsub(TEMP_H, "h")
text = text:gsub(TEMP_NY, "ny")
text = text:gsub(TEMP_NY_CAPS, "Ny")
text = text:gsub(TEMP_LL, "ll")
text = text:gsub(TEMP_LL_CAPS, "Ll")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text)
-- start at FFF1 because FFF0 is used for SYLDIV
local TEMP_Y = u(0xFFF1)
local TEMP_W = u(0xFFF2)
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ñ and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain
-- monosyllabic words (e.g. [[el]], [[la]], [[de]], [[en]], etc.) without stress marks be
-- unstressed.
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") and not rfind(word, accent_c) or unstressed_words[word] then
-- add CFLEX to the last vowel not the first one, or we will mess up 'que' by
-- adding the CFLEX after the 'u'
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CFLEX)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces, to handle [[Austria-Hungría]], [[franco-italiano]], etc; with the exception of verbal clitics (with the temporary symbol @)
text = rsub(text, "-se", "@se")
text = rsub(text, "-te", "@te")
text = rsub(text, "-me", "@me")
text = rsub(text, "-tos", "@tos")
text = rsub(text, "-vos", "@vos")
text = rsub(text, "-nos", "@nos")
text = rsub(text, "-mos", "@mos")
text = rsub(text, "-lo", "@lo")
text = rsub(text, "-el", "@el")
text = rsub(text, "-los", "@los")
text = rsub(text, "-es", "@es")
text = rsub(text, "-la", "@la")
text = rsub(text, "-las", "@las")
text = rsub(text, "-le", "@le")
text = rsub(text, "-li", "@li")
text = rsub(text, "-les", "@les")
text = rsub(text, "-lis", "@lis")
text = rsub(text, "-en", "@en")
text = rsub(text, "-ne", "@ne")
text = rsub(text, "-i", "@i")
text = rsub(text, "-bi", "@bi")
text = rsub(text, "%-", " ")
text = rsub(text, "@", "-")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--glides
text = rsub(text, "#y" .. CFLEX, "#i" .. CFLEX) -- conjunction
text = rsub(text, "ny", "ɲ")
text = rsub(text, "y", "ʝ")
text = rsub(text, "hi([aeou])", "ʝ%1")
-- handle certain combinations; sh handling needs to go before x handling to avoid issues with [[exhausto]]
text = rsub(text, "ch", "ĉ") --not the real sound
text = rsub(text, "ll", "ʎ")
text = rsub(text, "#ps", "#s") -- [[psicolochía]]
text = rsub(text, "x", "ʃ")
--c, g, q
text = rsub(text, "c([ie])", "θ%1")
text = rsub(text, "z", "θ")
text = rsub(text, "g([ie])", "x%1")
text = rsub(text, "gu([ie])", "g%1")
text = rsub(text, "gü([ie])", "gu%1")
text = rsub(text, "qü([ie])", "ku%1")
text = rsub(text, "ng([^aeiouüwhlr])", "n%1") -- TODO is this really needed?
text = rsub(text, "qu([ie])", "k%1")
text = rsub(text, "q", "k") -- [[quan]]
text = rsub(text, "([aeo])iʃ", "%1Iʃ")
-- map various consonants to their phoneme equivalent
text = rsub(text, "[cjñrv]", {["c"]="k", ["j"]="x", ["ñ"]="ɲ", ["r"]="ɾ", ["v"]="b"})
--pronunciation of <r>/<rr>
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "([#lnsθ])ɾ", "%1r")
text = rsub(text, "ɾ#", "R#")
text = rsub(text, "ɾ%-", "R-")
-- voiceless stop to voiced before obstruent or nasal (see Basic Grammar of Aragonese)
local voice_stop = { ["p"] = "b", ["t"] = "d", ["k"] = "g" }
text = rsub(text, "([ptk])(" .. separator_c .. "*" .. T .. ")",
function(stop, after) return voice_stop[stop] .. after end)
text = rsub(text, "!", "t")
text = rsub(text, "n([# .]*[bpm])", "m%1")
-- remove silent h and · before syllable division
text = rsub(text, "h", "")
text = rsub(text, "·", ".")
-- convert i/u between vowels to glide
local vowel_to_glide = { ["i"] = "j", ["u"] = "w" }
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*h?)([iu])(" .. V .. ")",
function (v1, iu, v2) return v1 .. vowel_to_glide[iu] .. v2 end
)
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--diphthongs; do not include TEMP_Y here
text = rsub(text, "i([aeou])", "j%1")
text = rsub(text, "u([aeio])", "w%1")
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = "ˌ", [CFLEX] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including ^ indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and (rfind(word, "[^" .. vowel .. "ns#]#") or rfind(word, C .. "[ns]#")) or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 and rfind(word, "[aeo][iu][ns#]") then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
if rfind(word, "ment#") then
local mente_syllables
-- Words ends in -ment (converted above to mént); add a stress to the preceding portion
mente_syllables = {}
mente_syllables[2] = table.remove(syllables)
mente_syllables[1] = table.remove(syllables)
accent_word(table.concat(syllables, "."), syllables)
accent_word(table.concat(mente_syllables, "."), mente_syllables)
table.insert(syllables, mente_syllables[1])
table.insert(syllables, mente_syllables[2])
else
accent_word(word, syllables)
end
-- Reconstruct the word.
words[j] = table.concat(syllables, ".")
end
text = table.concat(words, " ")
text = rsub(text, "%-", "")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ")
--other conversions, silent letters
text = rsub(text, "R", "(ɾ)")
text = rsub(text, "tθ#", "θ#")
text = rsub(text, "([nsɾ])t#", "%1#")
text = rsub(text, "nd#", "n#")
text = rsub(text, "ɾs#", "s#")
--semivowel symbols
text = rsub(text, "([aeou][i])", "%1̯")
text = rsub(text, "([aeio][u])", "%1̯")
--value of conjunction <y>
text = rsub(text, "([aeiou][# .]*)#i#([ˈ# .]*[aeiou])", "%1#ʝ#‿%2")
text = rsub(text, "i̯([# .]*)#i#([ˈ# .]*[aeiou])", "i̯%1#ʝ#‿%2")
text = rsub(text, "u̯([# .]*)#i#([ˈ# .]*[aeiou])", "u̯%1#ʝ#‿%2")
text = rsub(text, "#i#([ˈ# .]*[aeiou])", "#j#‿%1")
text = rsub(text, "([aeio][# .]*)#i#", "%1‿#i̯#")
-- convert fake symbols to real ones
local final_conversions = {
["ĉ"] = "t͡ʃ", -- fake "ch" to real "ch"
["g"] = "ɡ", -- U+0067 LATIN SMALL LETTER G → U+0261 LATIN SMALL LETTER SCRIPT G
["I"] = "(i̯)",
}
text = rsub(text, "[ĉgI]", final_conversions)
-- remove # symbols at word and text boundaries
text = rsub(text, "#", "")
text = rsub(text, "‿ ", "‿")
text = rsub(text, " ‿", "‿")
text = rsub(text, "([jʝ])‿ˈ", "ˈ%1‿")
text = rsub(text, "([jʝ])‿ˌ", "ˌ%1‿")
text = unfc(text)
local ret = {
text = text,
}
return ret
end
local function express_all_styles(dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
dodialect(ret)
table.insert(ret.expressed_styles, {
styles = {{pronun = ret.pronun,}},
})
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
end
local lines = {}
for i, style_group in ipairs(expressed_styles) do
table.insert(lines, style_group.formatted)
end
return table.concat(lines, "\n")
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic
if term.raw then
phonemic = term.raw_phonemic
else
phonemic = export.IPA(term.term)
phonemic = phonemic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = express_all_styles(this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡ʃ", "tʃ")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CFLEX then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. [[Austria-Hungría]])
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(put_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{es-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = require(put_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = express_all_styles(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = express_all_styles(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
3pd95b8k7vyl7tsmnseiyc1f3ryguns
Module:Zlw-ocs-pronunciation
828
2341284
9909112
9700737
2026-09-29T07:28:43Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909112
Scribunto
text/plain
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local lang = require("Module:languages").getByCode("zlw-ocs")
local U = mw.ustring.char
local rsub = mw.ustring.gsub
local rlower = mw.ustring.lower
local rfind = mw.ustring.find
local raised = U(0x031D)
local syllabic = U(0x0329)
local consonants = "bdcɟfɡɣxjkɫlmnɲprstʋzʒʃɲʲ"
local sconsonants = "bdcɟfɡɣxjkɫmnɲpstʋzʒʃɲʲ"
local C = "[" .. consonants .. "]"
local sC = "[" .. sconsonants .. "]"
local vowels = "aɛiɨou"
local voiceless_consonant = "ptsʃckxf"
local voiced_consonant = "bdzʒɟɡɣʋ"
local function track(page)
require("Module:debug").track("zlw-ocs-IPA/" .. page)
return true
end
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local digraphs = {
["ch"] = "χ",
["dž"] = "d͡ʒ",
["ie"] = "i̯ɛː",
["b́"] = "bʲ",
["f́"] = "fʲ",
}
local devoicing = {
["d͡z"] = "t͡s",
["d͡ʒ"] = "t͡ʃ",
["b"] = "p",
["d"] = "t",
["z"] = "s",
["ʒ"] = "ʃ",
["ɣ"] = "x",
["g"] = "k",
["ʋ"] = "f",
["ɟ"] = "c",
}
local voicing = {
["t͡s"] = "d͡z",
["t͡ʃ"] = "d͡ʒ",
["p"] = "b",
["t"] = "d",
["s"] = "z",
["ʃ"] = "ʒ",
["x"] = "ɣ",
["k"] = "ɡ",
["f"] = "ʋ",
["c"] = "ɟ",
}
local function voice_backward(sound, following)
return voicing[sound] .. following
end
local function devoice_backward(sound, following)
return devoicing[sound] .. following
end
local function voice_forward(preceding, sound)
return preceding .. voicing[sound]
end
local function devoice_forward(preceding, sound)
return preceding .. devoicing[sound]
end
local function final_devoicing(sound, following)
return devoicing[sound] .. following .. "#"
end
local function flatmap(items, fun)
local new = {}
for _, item in ipairs(items) do
local results = fun(item)
for _, result in ipairs(results) do
table.insert(new, result)
end
end
return new
end
local phon = {
["a"] = "a",
["á"] = "aː",
["b"] = "b",
["c"] = "t͡s",
["č"] = "t͡ʃ",
["d"] = "d",
["ď"] = "ɟ",
["e"] = "ɛ",
["é"] = "ɛː",
["ě"] = "i̯ɛ",
["f"] = "f",
["g"] = "ɡ",
["h"] = "ɣ",
["i"] = "i",
["í"] = "iː",
["j"] = "j",
["k"] = "k",
["l"] = "l",
["ľ"] = "ʎ",
["ł"] = "ɫ",
["m"] = "m",
["ḿ"] = "mʲ",
["n"] = "n",
["ň"] = "ɲ",
["o"] = "o",
["ó"] = "oː",
["p"] = "p",
["ṕ"] = "pʲ",
["r"] = "r",
["ř"] = "r"..raised,
["s"] = "s",
["š"] = "ʃ",
["ś"] = "sʲ",
["t"] = "t",
["ť"] = "c",
["u"] = "u",
["ú"] = "uː",
["v"] = "ʋ",
["v́"] = "ʋʲ",
["y"] = "ɨ",
["ý"] = "ɨː",
["z"] = "z",
["ž"] = "ʒ",
["ź"] = "zʲ",
["ȥ"] = "ȥ",
[":"] = "ː",
["?"] = "ʔ"
}
function export.phonemic(text, post)
text = rlower(text)
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
-- basic phonology
for digraph, replacement in pairs(digraphs) do
text = rsub(text, digraph, replacement)
end
text = rsub(text, ".", phon)
text = rsub(text, "ȥ", "(d)z")
text = rsub(text, "x́", "ksʲ")
text = rsub(text, "x", "ks")
text = rsub(text, "χ", "x")
text = rsub(text, "l([aɨou])", "ɫ%1")
text = rsub(text, "l#", "ɫ#")
text = rsub(text, "(l)(" .. C .. ")", "ɫ%2")
text = rsub(text, "ʎ", "l")
-- palatalisation
text = rsub(text, "ni", "ɲi")
text = rsub(text, "ti", "ci")
text = rsub(text, "di", "ɟi")
text = rsub(text, "(" .. C .. ")([rlɫ])(" .. C .. ")", "%1%2"..syllabic.."%3")
text = rsub(text, "(" .. sC .. ")([mn])(" .. sC .. ")", "%1%2"..syllabic.."%3")
text = rsub(text, "(" .. C .. ")([rlɫ])(#)", "%1%2"..syllabic.."%3")
text = rsub(text, "(" .. sC .. ")([mn])(#)", "%1%2"..syllabic.."%3")
text = rsub(text, "([" .. voiceless_consonant .. "])r̝", "%1r̝̊")
text = rsub(text, "'", "ˈ")
text = rsub(text, "-", "");
-- suffixes
text = rsub(text, "^ˈ%-", "-")
if not rfind(text, " ") then
text = rsub(text, text, "ˈ" .. text)
end
local variants = {text}
local function flatmap_and_sub_pre(from, to1, to2)
variants =
flatmap(
variants,
function(item)
if rfind(item, from) then
local retval = {rsub_repeatedly(item, from, to1)}
if to2 then
m_table.insertIfNot(retval, rsub_repeatedly(item, from, to2))
end
return retval
else
return {item}
end
end
)
end
if post ~= nil then
if args.desc == "no" then
else
if args.desc ~= nil then
local desc = args.desc
desc = rlower(desc)
desc = rsub(desc, " | ", "# | #")
desc = "##" .. rsub(desc, " ", "# #") .. "##"
-- basic phonology
for digraph, replacement in pairs(digraphs) do
desc = rsub(desc, digraph, replacement)
end
desc = rsub(desc, ".", phon)
text = rsub(text, "χ", "x")
desc = rsub(desc, "l([aɨou])", "ɫ%1")
text = rsub(text, "l#", "ɫ#")
text = rsub(text, "(l)(" .. C .. ")", "ɫ%2")
text = rsub(text, "ʎ", "l")
-- palatalisation
desc = rsub(desc, "ni", "ɲi")
desc = rsub(desc, "ti", "ci")
desc = rsub(desc, "di", "ɟi")
desc = rsub(desc, "'", "ˈ")
desc = rsub(desc, "-", "")
-- suffixes
desc = rsub(desc, "^ˈ%-", "-")
if not rfind(desc, " ") then
desc = rsub(desc, desc, "ˈ" .. desc)
end
variants = {desc}
end
text = flatmap_and_sub_pre("(" .. C .. ")([rlɫ])(" .. C .. ")", "%1%2"..syllabic.."%3")
text = flatmap_and_sub_pre("(" .. sC .. ")([mn])(" .. sC .. ")", "%1%2"..syllabic.."%3")
text = flatmap_and_sub_pre("(" .. C .. ")([rlɫ])(#)", "%1%2"..syllabic.."%3")
text = flatmap_and_sub_pre("(" .. sC .. ")([mn])(#)", "%1%2"..syllabic.."%3")
flatmap_and_sub_pre("r̝#", "r̝̊")
text = flatmap_and_sub_pre("([" .. voiced_consonant .. "])(r̝̊)", devoice_backward)
text = flatmap_and_sub_pre("([" .. voiced_consonant .. "])(#)", final_devoicing)
text = flatmap_and_sub_pre("v([" .. voiceless_consonant .. "])", "f%1")
text = flatmap_and_sub_pre("f([" .. voiced_consonant .. "])", "v%1")
text = flatmap_and_sub_pre("([" .. voiced_consonant .. "])([f])", voice_forward)
text = flatmap_and_sub_pre("([" .. voiceless_consonant .. "])([v])", devoice_forward)
text = flatmap_and_sub_pre("d͡z([" .. voiceless_consonant .. "])", "t͡s%1")
text = flatmap_and_sub_pre("t͡s([bdzʒɟɡɣ])", "d͡z%1")
text = flatmap_and_sub_pre("d͡ʒ([" .. voiceless_consonant .. "])", "t͡ʃ%1")
text = flatmap_and_sub_pre("t͡ʃ([bdzʒɟɡɣ])", "d͡ʒ%1")
text = flatmap_and_sub_pre("([" .. voiced_consonant .. "])([" .. voiceless_consonant .. "])", devoice_backward)
text = flatmap_and_sub_pre("([" .. voiceless_consonant .. "])([bdzʒɟɡɣ])", voice_backward)
flatmap_and_sub_pre("-", "")
flatmap_and_sub_pre("ɨː", "iː", "aj")
flatmap_and_sub_pre("ɨ", "i")
flatmap_and_sub_pre("ʲu", "i")
flatmap_and_sub_pre("([sz])ʲ", "%1")
flatmap_and_sub_pre("ʲ", "")
flatmap_and_sub_pre("([ʒʃjɟcɲl])u", "%1i")
flatmap_and_sub_pre("t͡su", "t͡si")
flatmap_and_sub_pre("ȥu", "zi")
flatmap_and_sub_pre("ȥa", "ze")
flatmap_and_sub_pre("ȥ", "z")
flatmap_and_sub_pre("(r"..raised..")u", "%1i")
flatmap_and_sub_pre("oː", "u̯o")
flatmap_and_sub_pre("uː", "au̯")
flatmap_and_sub_pre("ie", "iː")
flatmap_and_sub_pre("ʋ", "v")
flatmap_and_sub_pre("i̯ɛː", "iː")
flatmap_and_sub_pre("([bpfvm])i̯", "%1j")
flatmap_and_sub_pre("([ʒʃjɟcɲl])ɛː", "%1iː")
flatmap_and_sub_pre("(r"..raised..")ɛː", "%1iː")
flatmap_and_sub_pre("ɫ", "l")
flatmap_and_sub_pre("ɣ", "ɦ")
flatmap_and_sub_pre("i̯", "")
flatmap_and_sub_pre("r̝([" .. voiceless_consonant .. "])", "r̝̊%1")
end
end
flatmap_and_sub_pre("#", "")
return variants
end
function export.IPA(frame)
local terms = {}
args = frame:getParent().args
local currentTitle = mw.loadData("Module:headword/data").pagename
for _, term in ipairs(args) do
if term == currentTitle then track("redundant") end
table.insert(terms, term)
end
if #terms == 0 then
terms = {currentTitle}
end
local results1, results2 = {}, {}
for _, term in ipairs(terms) do
local variantsPre = export.phonemic(term)
for _, variant in ipairs(variantsPre) do
table.insert(results1, {pron = "/" .. variant .. "/"})
end
local variantsPost = export.phonemic(term, true)
for _, variant in ipairs(variantsPost) do
table.insert(results2, {pron = "/" .. variant .. "/"})
end
end
if args.desc == "no" then
results1[1].q = {"13世紀"}
return "*" .. m_IPA.format_IPA_full { lang = lang, items = results1 }
else
results1[1].q = {"13世紀"}
results2[1].q = {"15世紀"}
return "*" .. m_IPA.format_IPA_full { lang = lang, items = results1 }
.. "\n*" .. m_IPA.format_IPA_full { lang = lang, items = results2 }
end
end
return export
opjle854mxprcf080d9ei4wijo6dcl4
Module:Zlw-ocs-headword
828
2341285
9909111
9847578
2026-09-29T07:28:40Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909111
Scribunto
text/plain
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local lang = require("Module:languages").getByCode("zlw-ocs")
local langname = "古捷克語"
-- Table of all valid genders.
local valid_genders = {
["mfbysense"] = true,
["mfbysense-p"] = true,
["m-pr"] = true,
["m-anml"] = true,
["m-in"] = true,
["m-d"] = true,
["m-p"] = true,
["f"] = true,
["f-d"] = true,
["f-p"] = true,
["n"] = true,
["n-d"] = true,
["n-p"] = true,
["?"] = true,
["?-d"] = true,
["?-p"] = true,
}
-- Table of all valid aspects.
local valid_aspects = {
["impf"] = true,
["pf"] = true,
["both"] = true,
["biasp"] = true,
["?"] = true,
}
local rfind = mw.ustring.find
local function track(track_id)
require("Module:debug/track")("zlw-ocs-headword/" .. track_id)
return true
end
local function glossary_link(anchor, text)
text = text or anchor
return "[[Appendix:Glossary#" .. anchor .. "|" .. text .. "]]"
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = {required = true},
["def"] = {},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local args = frame:getParent().args
local poscat = iargs[1]
local def = iargs.def
local parargs = frame:getParent().args
local params = {
["head"] = {list = true},
["id"] = {},
["sort"] = {},
["splithyph"] = {type = "boolean"},
["nolinkhead"] = {type = "boolean"},
["json"] = {type = "boolean"},
["pagename"] = {}, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().text
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = args.head,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
sort_key = args.sort,
force_cat_output = force_cat,
}
local is_suffix = false
if pagename:find("^%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "後綴"
local singular_poscat = require("Module:string utilities").singularize(poscat)
table.insert(data.categories, "構成" .. singular_poscat .. "的" ..langname .. "後綴")
table.insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(def, args, data, is_suffix)
end
-- mw.ustring.toNFD performs decomposition, so letters that decompose
-- to an ASCII vowel and a diacritic, such as é, are counted as vowels and
-- do not need to be included in the pattern.
if not pagename:find("[ %-]") and not rfind(mw.ustring.lower(mw.ustring.toNFD(pagename)), "[aeiouyæœø]") then
table.insert(data.categories, "拼字中沒有元音的" .. langname .. "詞")
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
local function get_noun_params(is_proper)
params = {
[1] = {alias_of = "g"},
["indecl"] = {type = "boolean"},
}
local function insert_list_param(arg)
params[arg] = {list = true}
params[arg .. "_qual"] = {list = arg .. "=_qual", allow_holes = true}
end
insert_list_param("g")
insert_list_param("m")
insert_list_param("f")
insert_list_param("adj")
insert_list_param("dim")
insert_list_param("aug")
insert_list_param("pej")
insert_list_param("dem")
insert_list_param("fdem")
return params
end
local function do_nouns(is_proper, def, args, data, is_suffix)
for i, g in ipairs(args.g) do
if not valid_genders[g] then
error("Unrecognized gender: '" .. g .. "'")
end
-- mfbysense should always be animate so add that
if g == "mfbysense" then
g = "mfbysense-an"
elseif g == "mfbysense-p" then
g = "mfbysense-an-p"
end
track("gender-" .. g)
if args.g_qual[i] then
table.insert(data.genders, {spec = g, q = {args.g_qual[i]}})
else
table.insert(data.genders, g)
end
end
if args.indecl then
table.insert(data.inflections, {label = glossary_link("indeclinable")})
table.insert(data.categories, langname .. "無屈折名詞")
end
local function handle_infl(arg, label)
local vals = args[arg]
local quals = args[arg .. "_qual"]
if #vals > 0 then
local inflections = {}
for i, val in ipairs(vals) do
table.insert(inflections, {term = val, q = quals and quals[i] and {quals[i]} or nil})
end
inflections.label = label
table.insert(data.inflections, inflections)
end
end
handle_infl("m", "陽性")
handle_infl("f", "陰性")
handle_infl("adj", "關係形容詞")
handle_infl("dim", "指小詞")
handle_infl("aug", "指大詞")
handle_infl("pej", "貶稱")
handle_infl("dem", "居民稱謂詞")
handle_infl("fdem", "女性居民稱謂詞")
end
pos_functions["名詞"] = {
params = get_noun_params(false),
func = function(def, args, data, is_suffix)
return do_nouns(false, def, args, data, is_suffix)
end,
}
pos_functions["專有名詞"] = {
params = get_noun_params("proper noun"),
func = function(def, args, data, is_suffix)
return do_nouns("proper noun", def, args, data, is_suffix)
end,
}
pos_functions["動詞"] = {
params = {
["a"] = {default = "?"},
["pf"] = {list = true},
["impf"] = {list = true},
},
func = function(def, args, data, is_suffix)
if not valid_aspects[args.a] then
error("Unrecognized aspect: '" .. args.a .. "'")
end
data.genders = args.a == "both" and {"biasp"} or {args.a}
local pf = args.pf
if #pf > 0 then
pf.label = "完整體"
table.insert(data.inflections, pf)
end
local impf = args.impf
if #impf > 0 then
impf.label = "非完整體"
table.insert(data.inflections, impf)
end
end,
}
pos_functions["形容詞"] = {
params = {
[1] = {list = "comp"},
[2] = {list = "sup"},
["s"] = {list = true},
["sc"] = {list = true},
["ss"] = {list = "ss"},
["adv"] = {list = true},
["indecl"] = {type = "boolean"},
},
func = function(def, args, data, is_suffix)
if args.indecl then
table.insert(data.inflections, {label = glossary_link("indeclinable")})
table.insert(data.categories, langname .. "無屈折形容詞")
end
if #args.s > 0 then
args.s.label = "短形式"
table.insert(data.inflections, args.s)
end
if args[1][1] == "-" then
table.insert(data.inflections, {label = "無比較級"})
table.insert(data.categories, langname .. "不可比形容詞")
elseif #args[1] > 0 then
local comp = args[1]
local sup = args[2]
if #sup == 0 then
for _, c in ipairs(comp) do
table.insert(sup, "naj" .. c)
end
end
if #args.ss == 0 then
for _, c in ipairs(args.sc) do
table.insert(args.ss, "naj" .. c)
end
end
comp.label = "比較級"
comp.accel = {form = "comparative"}
sup.label = "最高級"
sup.accel = {form = "superlative"}
table.insert(data.inflections, comp)
if #args.sc > 0 then
args.sc.label = "短比較級"
table.insert(data.inflections, args.sc)
end
table.insert(data.inflections, sup)
if #args.sc > 0 then
args.ss.label = "短最高級"
table.insert(data.inflections, args.ss)
end
table.insert(data.categories, langname .. "可比形容詞")
end
if #args.adv > 0 then
args.adv.label = "副詞"
table.insert(data.inflections, args.adv)
end
end,
}
return export
1w3134une5fbfctq9bjwocbc3yqf678
Module:Gl-headword
828
2345220
9909067
9883365
2026-09-29T07:25:59Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909067
Scribunto
text/plain
-- This module contains code for Galician headword templates.
-- Templates covered are:
-- * {{gl-noun}}, {{gl-reinteg-noun}}, {{gl-proper noun}};
-- * {{gl-verb}}, {{gl-reinteg-verb}};
-- * {{gl-adj}}, {{gl-reinteg-adj}}, {{gl-adj-comp}}, {{gl-adj-sup}};
-- * {{gl-det}};
-- * {{gl-pron-adj}};
-- * {{gl-contr-adj}};
-- * {{gl-pp}};
-- * {{gl-cardinal}};
-- * {{gl-adv}}.
-- See [[Module:gl-verb]] and [[Module:gl-reinteg-verb]] for Galician conjugation templates.
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local m_strutils = require("Module:string utilities")
local rfind = m_strutils.find
local rmatch = m_strutils.match
local rsplit = m_strutils.split
local usub = m_strutils.sub
local require_when_needed = require("Module:utilities/require when needed")
local insert = table.insert
local m_table = require("Module:table")
local com_module = "Module:gl-common"
local reinteg_com_module = "Module:gl-reinteg-common"
local en_utilities_module = "Module:en-utilities"
local gl_verb_module = "Module:gl-verb"
local gl_reinteg_verb_module = "Module:gl-reinteg-verb"
local headword_utilities_module = "Module:headword utilities"
local inflection_utilities_module = "Module:inflection utilities"
local romut_module = "Module:romance utilities"
local lang = require("Module:languages").getByCode("gl")
local langname = lang:getCanonicalName()
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
-- When followed by a hyphen in a hyphenated compound, the hyphen will be included with the prefix when linked.
local include_hyphen_prefixes = m_table.listToSet {
"ab",
"afro",
"anarco",
"anglo",
"ântero",
"anti",
"auto",
"contra",
"ex",
"franco",
"hiper",
"infra",
"inter",
"intra",
"macro",
"micro",
"neo",
"pan",
"pós",
"pré",
"pró",
"proto",
"sobre",
"sub",
"super",
"vice",
}
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function track(page)
require("Module:debug/track")("gl-headword/" .. page)
return true
end
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
-- 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, frob)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
frob = frob,
}
end
local function do_headword(parargs, poscat, is_reinteg)
local params = {
["head"] = list_param,
["id"] = {},
["splithyph"] = boolean_param,
["nolinkhead"] = boolean_param,
["json"] = boolean_param,
["pagename"] = {}, -- for testing
["reinteg"] = {}, -- processed in show()
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolinkhead then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph, nil,
include_hyphen_prefixes)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
end
end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
checkredlinks = pos_functions[poscat] and pos_functions[poscat].redlink_pos or true,
is_reinteg = is_reinteg,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = true
local singular_poscat = poscat
table.insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
table.insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if is_reinteg then
table.insert(data.inflections, {label = "[[Appendix:重新融入主義|重新融入主義]]標準"})
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local poscat = frame.args[1] or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local is_reinteg = frame.args.reinteg and require("Module:yesno")(frame.args.reinteg, false) or false
local parargs = frame:getParent().args
local also_reinteg = parargs.reinteg and require("Module:yesno")(parargs.reinteg, false) or false
if is_reinteg and also_reinteg then
error("Can't specify reinteg= when using a reintegrationist-specific template")
end
local parts = {}
if also_reinteg or not is_reinteg then
table.insert(parts, do_headword(parargs, poscat, false))
end
if also_reinteg or is_reinteg then
table.insert(parts, do_headword(parargs, poscat, true))
end
return table.concat(parts, "<br />")
end
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture
-- replace expression.
lemma = lemma:gsub("%%", "%%%%")
return (term:gsub("#", lemma))
end
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet(
{"m", "f", "mf", "mfbysense", "mfequiv", "gneut", "n", "m-p", "f-p", "mf-p", "mfbysense-p", "mfequiv-p", "gneut-p", "n-p", "?", "?-p"}
)
local function validate_genders(genders)
for _, g in ipairs(genders) do
if type(g) == "table" then
g = g.spec
end
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
end
end
local function do_noun(args, data, is_proper)
local com = require(data.is_reinteg and reinteg_com_module or com_module)
local is_plurale_tantum = false
local has_singular = false
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
validate_genders(args[1])
data.genders = args[1]
local saw_m = false
local saw_f = false
local saw_gneut = false
local gender_for_irreg_ending
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if type(g) == "table" then
g = g.spec
end
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
if g == "gneut" then
saw_gneut = true
end
end
end
if saw_m and saw_f then
gender_for_irreg_ending = "mf"
elseif saw_f then
gender_for_irreg_ending = "f"
else
gender_for_irreg_ending = "m"
end
local lemma = data.pagename
local plurals = {}
local function insert_noun_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if is_plurale_tantum and not has_singular then
if args[2][1] then
error("Can't specify plurals of plurale tantum " .. category_pos)
end
insert(data.inflections, {label = glossary_link("唯複")})
else
plurals = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "pl"},
forms = args[2],
splitchar = ",",
}
-- Check for special plural signals
local mode = nil
local pl1 = plurals[1]
if pl1 and #pl1.term == 1 then
mode = pl1.term
if mode == "?" or mode == "!" or mode == "-" or mode == "~" then
pl1.term = nil
if next(pl1) then
error(("Can't specify inline modifiers with plural code '%s'"):format(mode))
end
table.remove(plurals, 1) -- Remove the mode parameter
elseif mode ~= "+" and mode ~= "#" then
error(("Unexpected plural code '%s'"):format(mode))
end
end
if is_plurale_tantum then
-- both singular and plural
insert(data.inflections, {label = "有時" .. glossary_link("唯複") .. ",有變體"})
end
if mode == "?" then
-- Plural is unknown
insert(data.categories, "有未知或不確定複數的" .. langname .. category_plpos)
elseif mode == "!" then
-- Plural is not attested
insert(data.inflections, {label = "複數無紀錄"})
insert(data.categories, "有無紀錄複數的" .. langname .. category_plpos)
if plurals[1] then
error("Can't specify any plurals along with unattested plural code '!'")
end
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
insert(data.categories, langname .. "不可數" .. category_plpos)
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
insert(data.categories, langname .. "可數" .. category_plpos)
else
insert(data.inflections, {label = glossary_link("不可數")})
end
else
-- Countable or mixed countable/uncountable
if not plurals[1] and not is_proper then
plurals[1] = {term = "+"}
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
insert(data.inflections, {label = glossary_link("可數") .. "和" .. glossary_link("不可數")})
insert(data.categories, langname .. "不可數" .. category_plpos)
insert(data.categories, langname .. "可數" .. category_plpos)
elseif plurals[1] then
-- Countable nouns
insert(data.categories, langname .. "可數" .. category_plpos)
else
-- Uncountable nouns
insert(data.categories, langname .. "不可數" .. category_plpos)
end
end
-- Gather plurals, handling requests for default plurals.
local has_default_or_hash = false
for _, pl in ipairs(plurals) do
if pl.term:find("^%+") or pl.term:find("#") then
has_default_or_hash = true
break
end
end
if has_default_or_hash then
for _, pl in ipairs(plurals) do
if pl.term == "+" then
-- make_plural should never return nil
pl.term = com.make_plural(lemma)
elseif pl.term:find("^%+") then
pl.term = require(romut_module).get_special_indicator(pl.term)
-- make_plural should never return nil
pl.term = com.make_plural(lemma, pl.term)
else
pl.term = replace_hash_with_lemma(pl.term, lemma)
end
end
end
local pl1 = plurals[1]
if pl1 and not plurals[2] and pl1.term == lemma then
insert(data.inflections, {label = glossary_link("無屈折"),
q = pl1.q, qq = pl1.qq, l = pl1.l, ll = pl1.ll, refs = pl1.refs
})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
insert_noun_inflection(plurals, "複數", "p")
end
if plurals[2] then
insert(data.categories, "有多個複數的" .. langname .. category_plpos)
end
end
-- Gather masculines/feminines. For each one, generate the corresponding plural. `field` is the name of the field
-- containing the masculine or feminine forms (normally "m" or "f"); `inflect` is a function of one or two arguments
-- to generate the default masculine or feminine from the lemma (the arguments are the lemma and optionally a
-- "special" flag to indicate how to handle multiword lemmas, and the function is normally make_feminine or
-- make_masculine from [[Module:gl-common]]); and `default_plurals` is a list into which the corresponding default
-- plurals of the gathered or generated masculine or feminine forms are stored.
local function handle_mf(field, inflect, default_plurals)
local mfs = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
frob = function(term)
if term == "+" then
-- Generate default masculine/feminine.
term = inflect(lemma)
else
term = replace_hash_with_lemma(term, lemma)
end
local special = require(romut_module).get_special_indicator(term)
if special then
term = inflect(lemma, special)
end
return term
end
}
for _, mf in ipairs(mfs) do
local plobj = m_table.shallowCopy(mf)
-- make_plural should never return nil
plobj.term = com.make_plural(mf.term, special)
-- Add an accelerator for each masculine/feminine plural whose lemma is the corresponding singular, so that
-- the accelerated entry that is generated has a definition that looks like
-- # {{plural of|pt|MFSING}}
plobj.accel = {form = "p", lemma = mf.term}
insert(default_plurals, plobj)
end
return mfs
end
local feminine_plurals = {}
local feminines = handle_mf("f", function(term, special)
return com.make_feminine(term, "is noun", special) end, feminine_plurals)
local masculine_plurals = {}
local masculines = handle_mf("m", com.make_masculine, masculine_plurals)
local function handle_mf_plural(mfplfield, default_plurals, singulars)
local mfpl = m_headword_utilities.parse_term_list_with_modifiers {
paramname = mfplfield,
forms = args[mfplfield],
splitchar = ",",
}
if is_plurale_tantum then
return mfpl
end
local new_mfpls = {}
local saw_plus
for i, mfpl in ipairs(mfpl) do
local accel
if #mfpl == #singulars then
-- If same number of overriding masculine/feminine plurals as singulars, assume each plural goes with
-- the corresponding singular and use each corresponding singular as the lemma in the accelerator. The
-- generated entry will have
-- # {{plural of|pt|SINGULAR}}
-- as the definition.
accel = {form = "p", lemma = singulars[i].term}
else
accel = nil
end
if mfpl.term == "+" then
-- We should never see + twice. If we do, it will lead to problems since we overwrite the values of
-- default_plurals the first time around.
if saw_plus then
error(("Saw + twice when handling %s="):format(mfplfield))
end
saw_plus = true
if not default_plurals[1] then
-- FIXME: Can this happen? Not in corresponding Spanish code and the old Portuguese code tried to
-- handle this condition by generating the default plural from the lemma.
error("Internal error: Something wrong, no generated default m/f plurals at this stage")
end
for _, defpl in ipairs(default_plurals) do
-- defpl is already a table and has an accel field
m_headword_utilities.combine_termobj_decorations(defpl, mfpl)
insert(new_mfpls, defpl)
end
elseif mfpl.term:find("^%+") then
mfpl.term = require(romut_module).get_special_indicator(mfpl.term)
for _, mf in ipairs(singulars) do
local mfplobj = m_table.shallowCopy(mfpl)
-- make_plural should never return nil
mfplobj.term = com.make_plural(mf.term, mfpl.term)
mfplobj.accel = accel
m_headword_utilities.combine_termobj_decorations(mfplobj, mf)
insert(new_mfpls, mfplobj)
end
else
mfpl.accel = accel
mfpl.term = replace_hash_with_lemma(mfpl.term, lemma)
insert(new_mfpls, mfpl)
end
end
return new_mfpls
end
if args.fpl[1] then
-- Override any existing feminine plurals.
feminine_plurals = handle_mf_plural("fpl", feminine_plurals, feminines)
end
if args.mpl[1] then
-- Override any existing masculine plurals.
masculine_plurals = handle_mf_plural("mpl", masculine_plurals, masculines)
end
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
insert_noun_inflection(feminines, "陰性", "f")
insert_noun_inflection(feminine_plurals, "陰性複數")
insert_noun_inflection(masculines, "陽性")
insert_noun_inflection(masculine_plurals, "陽性複數")
parse_and_insert_noun_inflection("dim", "指小")
parse_and_insert_noun_inflection("aug", "指大")
parse_and_insert_noun_inflection("pej", "貶義")
parse_and_insert_noun_inflection("dem", "居民稱謂詞")
parse_and_insert_noun_inflection("fdem", "女性居民稱謂詞")
-- Maybe add category 'Galician nouns with irregular gender' (or similar)
local irreg_gender_lemma = com.rsub(lemma, " .*", "") -- only look at first word
if (rfind(irreg_gender_lemma, "[^ã]o$") and (gender_for_irreg_ending == "f" or gender_for_irreg_ending == "mf")) or
(irreg_gender_lemma:find("a$") and (gender_for_irreg_ending == "m" or gender_for_irreg_ending == "mf")) then
insert(data.categories, "有不規則性別的" .. langname .. category_plpos)
end
end
local function get_noun_params(is_proper)
return {
[1] = {list = "g", disallow_holes = true, required = not is_proper, default = "?", type = "genders",
flatten = true},
[2] = {list = "pl", disallow_holes = true},
["m"] = list_param,
["f"] = list_param,
["mpl"] = list_param,
["fpl"] = list_param,
["dim"] = list_param, --diminutive(s)
["aug"] = list_param, --diminutive(s)
["pej"] = list_param, --pejorative(s)
["dem"] = list_param, --demonym(s)
["fdem"] = list_param, --female demonym(s)
}
end
pos_functions["名詞"] = {
params = get_noun_params(),
func = do_noun,
}
pos_functions["專有名詞"] = {
params = get_noun_params("is proper"),
func = function(args, data)
do_noun(args, data, "is proper noun")
end,
}
-----------------------------------------------------------------------------------------
-- Pronouns --
-----------------------------------------------------------------------------------------
local function do_pronoun(args, data)
local lemma = data.pagename
validate_genders(args[1])
data.genders = args[1]
local function do_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
do_inflection("m", "陽性")
do_inflection("f", "陰性")
do_inflection("sg", "單數")
do_inflection("pl", "複數")
do_inflection("mpl", "陽性複數")
do_inflection("fpl", "陰性複數")
do_inflection("n", "中性")
end
local function get_pronoun_params()
local params = {
[1] = {list = "g", disallow_holes = true, type = "genders", flatten = true}, --gender(s)
["m"] = list_param, --masculine form(s)
["f"] = list_param, --feminine form(s)
["sg"] = list_param, --singular form(s)
["pl"] = list_param, --plural form(s)
["mpl"] = list_param, --masculine plural form(s)
["fpl"] = list_param, --feminine plural form(s)
["n"] = list_param, --neuter form(s)
}
return params
end
pos_functions["代詞"] = {
params = get_pronoun_params(),
func = do_pronoun,
}
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
-- Handle comparatives and superlatives for adjectives and adverbs, including user-specified comparatives and
-- superlatives, default-requested comparatives/superlatives using '+', autogenerated comparatives/superlatives,
-- and hascomp=. Code is the same for adjectives and adverbs.
local function handle_adj_adv_comp(args, data, plpos, is_adv)
local com = require(data.is_reinteg and reinteg_com_module or com_module)
local lemma = data.pagename
local stem
if is_adv then
stem = com.rsub(lemma, "mente$", "")
else
stem = lemma
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
local function make_absolute_superlative(special)
if is_adv then
return com.make_adverbial_absolute_superlative(stem, special)
else
return com.make_absolute_superlative(stem, special)
end
end
-- Maybe autogenerate default comparative/superlative.
local comp, sup
if args.comp and args.sup then-- comp= and sup= were given as options to the user
-- If no comp, but a non-default sup given, then add the default comparative/superlative.
-- This is useful when an absolute superlative is given.
comp = m_headword_utilities.parse_term_list_with_modifiers {
paramname = "comp",
forms = args.comp,
splitchar = ",",
}
sup = m_headword_utilities.parse_term_list_with_modifiers {
paramname = "sup",
forms = args.sup,
splitchar = ",",
}
local saw_sup_plus = false
if not comp[1] and sup[1] then
for _, supval in ipairs(sup) do
if supval.term == "+" then
saw_sup_plus = true
end
end
if not saw_sup_plus then
comp = {{term = "+"}}
insert(sup, 1, {term = "+"})
end
end
-- If comp=+, use default comparative 'mais ...', and set a default superlative if unspecified.
local saw_comp_plus = false
for _, compval in ipairs(comp) do
if compval.term == "+" then
saw_comp_plus = true
compval.term = "[[mais]] [[" .. lemma .. "]]"
end
end
if saw_comp_plus and not sup[1] then
sup = {{term = "+"}}
end
-- If sup=+ (possibly from comp=+), use default superlative 'o mais ...'. Also handle absolute superlatives.
for _, supval in ipairs(sup) do
if supval.term == "+" then
supval.term = "[[o]] [[mais]] [[" .. lemma .. "]]"
elseif supval.term == "+abs" then
supval.term = make_absolute_superlative()
elseif rfind(supval.term, "^%+abs:") then
local sp = rmatch(supval.term, "^%+abs:(.*)$")
supval.term = make_absolute_superlative(sp)
end
end
end
if args.hascomp then
if args.hascomp == "both" then
insert(data.inflections, {label = "有時" .. glossary_link("有比較級")})
insert(data.categories, langname .. "有比較級" .. plpos)
insert(data.categories, langname .. "無比較級" .. plpos)
else
local hascomp = require("Module:yesno")(args.hascomp)
if hascomp == true then
insert(data.inflections, {label = glossary_link("比較級")})
insert(data.categories, langname .. "有比較級" .. plpos)
elseif hascomp == false then
insert(data.inflections, {label = "無" .. glossary_link("比較級")})
insert(data.categories, langname .. "無比較級" .. plpos)
else
error("Unrecognized value for hascomp=: " .. args.hascomp)
end
end
elseif comp and comp[1] or sup and sup[1] then
insert(data.inflections, {label = glossary_link("比較級")})
insert(data.categories, langname .. "有比較級" .. plpos)
end
insert_inflection(comp, "比較級")
insert_inflection(sup, "最高級")
end
local function do_adjective(args, data, is_superlative)
local com = require(data.is_reinteg and reinteg_com_module or com_module)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
-- Use "participle" not "past participle" for categories such as 'invariable participles'
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
insert(indicators, "'" .. indic .. "'")
end
table.sort(indicators)
error("Special inflection indicator beginning can only be " ..
mw.text.listToText(indicators) .. ": " .. args.sp)
end
end
local lemma = data.pagename
local function fetch_inflections(field)
local retval = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
}
if not retval[1] then
return {{term = "+"}}
end
return retval
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if args.short then
insert(data.inflections, {label = "[[Appendix:加利西亞語動詞#分詞|短分詞]]"})
end
if args.inv then
-- invariable adjective
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
if args.sp or args.f[1] or args.pl[1] or args.mpl[1] or args.fpl[1] then
error("Can't specify inflections with an invariable " .. category_pos)
end
elseif args.fonly then
-- feminine-only
if args.f[1] then
error("Can't specify explicit feminines with feminine-only " .. category_pos)
end
if args.pl[1] then
error("Can't specify explicit plurals with feminine-only " .. category_pos .. ", use fpl=")
end
if args.mpl[1] then
error("Can't specify explicit masculine plurals with feminine-only " .. category_pos)
end
local argsfpl = fetch_inflections("fpl")
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
-- make_plural should never return nil
fpl.term = com.make_plural(lemma, args.sp)
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
end
insert(feminine_plurals, fpl)
end
insert(data.inflections, {label = "僅陰性"})
insert_inflection(feminine_plurals, "陰性複數", "f|p")
else
-- Gather feminines.
for _, f in ipairs(fetch_inflections("f")) do
if f.term == "+" then
-- Generate default feminine.
f.term = com.make_feminine(lemma, false, args.sp)
else
f.term = replace_hash_with_lemma(f.term, lemma)
end
insert(feminines, f)
end
local fem_like_lemma = #feminines == 1 and feminines[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(feminines[1])
if fem_like_lemma then
insert(data.categories, langname .. "兩性" .. category_plpos)
end
local mpl_field = "mpl"
local fpl_field = "fpl"
if args.pl[1] then
if args.mpl[1] or args.fpl[1] then
error("Can't specify both pl= and mpl=/fpl=")
end
mpl_field = "pl"
fpl_field = "pl"
end
local argsmpl = fetch_inflections(mpl_field)
local argsfpl = fetch_inflections(fpl_field)
for _, mpl in ipairs(argsmpl) do
if mpl.term == "+" then
-- Generate default masculine plural. make_plural should never return nil.
mpl.term = com.make_plural(lemma, args.sp)
else
mpl.term = replace_hash_with_lemma(mpl.term, lemma)
end
insert(masculine_plurals, mpl)
end
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local fplobj = m_table.shallowCopy(fpl)
-- make_plural should never return nil.
fplobj.term = com.make_plural(f.term, args.sp)
m_headword_utilities.combine_termobj_decorations(fplobj, f)
insert(feminine_plurals, fplobj)
end
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
insert(feminine_plurals, fpl)
end
end
local fem_pl_like_masc_pl = masculine_plurals[1] and feminine_plurals[1] and
m_table.deepEquals(masculine_plurals, feminine_plurals)
local masc_pl_like_lemma = #masculine_plurals == 1 and masculine_plurals[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(masculine_plurals[1])
if fem_like_lemma and fem_pl_like_masc_pl and masc_pl_like_lemma then
-- actually invariable
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, langname .. "無屈折" .. category_plpos)
else
-- Make sure there are feminines given and not same as lemma.
if not fem_like_lemma then
insert_inflection(feminines, "陰性", "f|s")
elseif args.gneut then
data.genders = {"gneut"}
else
data.genders = {"mf"}
end
if fem_pl_like_masc_pl then
if args.gneut then
insert_inflection(masculine_plurals, "複數", "p")
else
-- This is how the Spanish module works.
-- insert_inflection(masculine_plurals, "陽性和陰性複數", "p")
insert_inflection(masculine_plurals, "複數", "p")
end
else
insert_inflection(masculine_plurals, "陽性複數", "m|p")
insert_inflection(feminine_plurals, "陰性複數", "f|p")
end
end
end
local function parse_and_insert_adj_inflection(field, label, accel, frob)
parse_and_insert_inflection(data, args, field, label, accel, frob)
end
parse_and_insert_adj_inflection("n", "中性")
handle_adj_adv_comp(args, data, category_plpos, false)
parse_and_insert_adj_inflection("dim", "指小", nil, function(term)
if term == "+" then
term = com.make_diminutive(lemma)
elseif term:find("^%+") then
term = require(romut_module).get_special_indicator(term)
term = com.make_diminutive(lemma, term)
end
return term
end)
parse_and_insert_adj_inflection("aug", "指大", nil, function(term)
if term == "+" then
term = com.make_augmentative(lemma)
elseif term:find("^%+") then
term = require(romut_module).get_special_indicator(term)
term = com.make_augmentative(lemma, term)
end
return term
end)
if args.irreg and is_superlative then
insert(data.categories, langname .. "不規則" .. category_plpos .. "最高級")
end
end
local function get_adjective_params(adjtype)
local params = {
["inv"] = boolean_param, --invariable
["sp"] = true, -- special indicator: "first", "first-last", etc.
["f"] = list_param, --feminine form(s)
["pl"] = list_param, --plural override(s)
["mpl"] = list_param, --masculine plural override(s)
["fpl"] = list_param, --feminine plural override(s)
}
if adjtype == "base" then
params["comp"] = list_param --comparative(s)
params["sup"] = list_param --superlative(s)
params["dim"] = list_param --diminutive(s)
params["aug"] = list_param --augmentative(s)
params["fonly"] = boolean_param -- feminine only
params["gneut"] = boolean_param -- gender-neutral adjective e.g. [[latine]] (FIXME: do these exist in Galician?)
params["hascomp"] = true -- has comparative
end
if adjtype == "part" then
params["short"] = boolean_param -- short participle
end
if adjtype == "sup" then
params["irreg"] = boolean_param
end
if adjtype == "pron" or adjtype == "contr" then
params["n"] = list_param --neuter form(s)
end
return params
end
pos_functions["形容詞"] = {
params = get_adjective_params("base"),
func = do_adjective,
}
pos_functions["形容詞比較級"] = {
params = get_adjective_params("comp"),
func = do_adjective,
pos_category = "形容詞",
}
pos_functions["形容詞最高級"] = {
params = get_adjective_params("sup"),
func = function(args, data)
do_adjective(args, data, "is superlative")
end,
pos_category = "形容詞",
}
pos_functions["過去分詞"] = {
params = get_adjective_params("part"),
func = do_adjective,
redlink_pos = "分詞",
}
pos_functions["限定詞"] = {
params = get_adjective_params("det"),
func = do_adjective,
}
pos_functions["類似形容詞的代詞"] = {
params = get_adjective_params("pron"),
func = do_adjective,
pos_category = "代詞",
}
pos_functions["類似形容詞的縮約形"] = {
params = get_adjective_params("contr"),
func = do_adjective,
pos_category = "縮約形",
}
-----------------------------------------------------------------------------------------
-- Adverbs --
-----------------------------------------------------------------------------------------
local function do_adverb(args, data)
handle_adj_adv_comp(args, data, "adverbs", "is adv")
end
local function get_adverb_params(advtype)
local params = {}
if advtype == "base" then
params["comp"] = list_param --comparative(s)
params["sup"] = list_param --superlative(s)
params["hascomp"] = true -- has comparative
end
return params
end
pos_functions["副詞"] = {
params = get_adverb_params("base"),
func = do_adverb,
}
pos_functions["副詞比較級"] = {
params = get_adverb_params("comp"),
func = do_adverb,
pos_category = "副詞",
}
pos_functions["副詞最高級"] = {
params = get_adverb_params("sup"),
func = do_adverb,
pos_category = "副詞",
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["動詞"] = {
params = {
[1] = {},
["pres"] = list_param, --present
["pres_qual"] = {list = "pres\1_qual", allow_holes = true},
["pres3s"] = list_param, --third-singular present
["pres3s_qual"] = {list = "pres3s\1_qual", allow_holes = true},
["pret"] = list_param, --preterite
["pret_qual"] = {list = "pret\1_qual", allow_holes = true},
["part"] = list_param, --participle
["part_qual"] = {list = "part\1_qual", allow_holes = true},
["short_part"] = list_param, --short participle
["short_part_qual"] = {list = "short_part\1_qual", allow_holes = true},
["noautolinktext"] = boolean_param,
["noautolinkverb"] = boolean_param,
["attn"] = boolean_param,
},
func = function(args, data)
local preses, preses_3s, prets, parts, short_parts
if args.attn then
table.insert(data.categories, "Requests for attention concerning " .. langname)
return
end
local gl_verb = require(data.is_reinteg and gl_reinteg_verb_module or gl_verb_module)
local alternant_multiword_spec = gl_verb.do_generate_forms(args,
data.is_reinteg and "gl-reinteg-verb" or "gl-verb", data.heads[1])
local specforms = alternant_multiword_spec.forms
local function slot_exists(slot)
return specforms[slot] and #specforms[slot] > 0
end
local function do_finite(slot_tense, label_tense)
-- Use pres_3s if it exists and pres_1s doesn't exist (e.g. impersonal verbs); similarly for pres_3p (only3p verbs);
-- but fall back to pres_1s if neither pres_1s nor pres_3s nor pres_3p exist (e.g. [[empedernir]]).
local has_1s = slot_exists(slot_tense .. "_1s")
local has_3s = slot_exists(slot_tense .. "_3s")
local has_3p = slot_exists(slot_tense .. "_3p")
if has_1s or (not has_3s and not has_3p) then
return {
slot = slot_tense .. "_1s",
label = ("第一人稱單數%s"):format(label_tense),
}, true
elseif has_3s then
return {
slot = slot_tense .. "_3s",
label = ("第三人稱單數%s"):format(label_tense),
}, false
else
return {
slot = slot_tense .. "_3p",
label = ("第三人稱複數%s"):format(label_tense),
}, false
end
end
local did_pres_1s
preses, did_pres_1s = do_finite("pres", "現在時")
preses_3s = {
slot = "pres_3s",
label = "第三人稱單數現在時",
}
prets = do_finite("pret", "過去時")
parts = {
slot = "pp_ms",
label = "過去分詞",
}
short_parts = {
slot = "short_pp_ms",
label = "短過去分詞",
}
if #args.pres > 0 or #args.pres3s > 0 or #args.pret > 0 or #args.part > 0 or #args.short_part > 0 then
track("verb-old-multiarg")
end
local function strip_brackets(qualifiers)
if not qualifiers then
return nil
end
local stripped_qualifiers = {}
for _, qualifier in ipairs(qualifiers) do
local stripped_qualifier = qualifier:match("^%[(.*)%]$")
if not stripped_qualifier then
error("Internal error: Qualifier should be surrounded by brackets at this stage: " .. qualifier)
end
table.insert(stripped_qualifiers, stripped_qualifier)
end
return stripped_qualifiers
end
local function do_verb_form(args, qualifiers, slot_desc, skip_if_empty)
local forms
local to_insert
if #args == 0 then
forms = specforms[slot_desc.slot]
if not forms or #forms == 0 then
if skip_if_empty then
return
end
forms = {{form = "-"}}
end
elseif #args == 1 and args[1] == "-" then
forms = {{form = "-"}}
else
forms = {}
for i, arg in ipairs(args) do
local qual = qualifiers[i]
if qual then
-- FIXME: It's annoying we have to add brackets and strip them out later. The inflection
-- code adds all footnotes with brackets around them; we should change this.
qual = {"[" .. qual .. "]"}
end
local form = arg
if not args.noautolinkverb then
-- [[Module:inflection utilities]] already loaded by [[Module:gl-verb]] or
-- [[Module:gl-reinteg-verb]]
form = require(inflection_utilities_module).add_links(form)
end
table.insert(forms, {form = form, footnotes = qual})
end
end
if forms[1].form == "-" then
to_insert = {label = "無" .. slot_desc.label}
else
local into_table = {label = slot_desc.label}
for _, form in ipairs(forms) do
local qualifiers = strip_brackets(form.footnotes)
-- Strip redundant brackets surrounding entire form. These may get generated e.g.
-- if we use the angle bracket notation with a single word.
local stripped_form = rmatch(form.form, "^%[%[([^%[%]]*)%]%]$") or form.form
-- The reintegrationist code uses variant codes to track "Portuguese-ending" vs. "Galician-ending"
-- forms and "less common" forms. Remove them now.
if data.is_reinteg then
stripped_form = gl_verb.remove_variant_codes(stripped_form)
end
-- Don't include accelerators if brackets remain in form, as the result will be wrong.
-- FIXME: For now, don't include accelerators. We should use the new {{gl-verb form of}} once implemented.
-- local this_accel = not stripped_form:find("%[%[") and accel or nil
local this_accel = nil
table.insert(into_table, {term = stripped_form, q = qualifiers, accel = this_accel})
end
to_insert = into_table
end
table.insert(data.inflections, to_insert)
end
local skip_pres_if_empty
if alternant_multiword_spec.no_pres1_and_sub then
table.insert(data.inflections, {label = "無第一人稱單數現在時"})
table.insert(data.inflections, {label = "無現在時虛擬式"})
end
if alternant_multiword_spec.no_pres_stressed then
table.insert(data.inflections, {label = "無帶重音的現在時直陳式或虛擬式"})
skip_pres_if_empty = true
end
if alternant_multiword_spec.only3s then
table.insert(data.inflections, {label = glossary_link("非人稱")})
elseif alternant_multiword_spec.only3sp then
table.insert(data.inflections, {label = "僅第三人稱"})
elseif alternant_multiword_spec.only3p then
table.insert(data.inflections, {label = "僅第三人稱複數"})
end
local has_vowel_alt
if alternant_multiword_spec.vowel_alt then
for _, vowel_alt in ipairs(alternant_multiword_spec.vowel_alt) do
if vowel_alt ~= "+" and vowel_alt ~= "í" and vowel_alt ~= "ú" then
has_vowel_alt = true
break
end
end
end
do_verb_form(args.pres, args.pres_qual, preses, skip_pres_if_empty)
-- We want to include both the pres_1s and pres_3s if there is a vowel alternation in the present singular. But we
-- don't want to redundantly include the pres_3s if we already included it.
if did_pres_1s and has_vowel_alt then
do_verb_form(args.pres3s, args.pres3s_qual, preses_3s, skip_pres_if_empty)
end
do_verb_form(args.pret, args.pret_qual, prets)
do_verb_form(args.part, args.part_qual, parts)
do_verb_form(args.short_part, args.short_part_qual, short_parts, "skip if empty")
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
table.insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:gl-verb]]/[[Module:gl-reinteg-verb]] in place of the user-specified or auto-generated head. This was
-- copied from [[Module:it-headword]], where doing this gets accents marked on the verb(s). We don't have
-- accents marked on the verb but by doing this we do get any footnotes on the infinitive propagated here. Don't
-- do this if the user gave multiple heads or gave a head with a multiword-linked verbal expression such as
-- Italian '[[dare esca]] [[al]] [[fuoco]]' (FIXME: give Galician equivalent).
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
table.insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
end
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
["g"] = {list = true, disallow_holes = true, type = "genders", flatten = true},
},
func = function(args, data)
validate_genders(args.g)
data.genders = args.g
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, "構成" .. typ .. "的後綴")
end
insert(data.inflections, {label = m_table.serialCommaJoin(suffix_type, {conj = "或"}) .. "的非詞元形式"})
end,
}
return export
lgj1xkkdk0ztgdcjirmy7ab6y5t6dn3
Module:Mdh-pronunciation
828
2360352
9909085
9023363
2026-09-29T07:27:06Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909085
Scribunto
text/plain
-- Based on [[Module:es-pronunc]] by Benwing2.
-- Adaptation by TagaSanPedroAko, Improved by Ysrael214.
-- Partly rewritten by Benwing2, merging code from [[Module:es-pronunc]] back into this module; {{tl-pr}} restructured
-- to take inline modifiers, like {{es-pr}}
-- Modified for {{mdh-pr}}.
local export = {}
local force_cat = false -- enable for testing
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local accent_qualifier_module = "Module:accent qualifier"
local audio_module = "Module:audio"
local headword_data_module = "Module:headword/data"
local hyphenation_module = "Module:hyphenation"
local labels_module = "Module:labels"
local parse_utilities_module = "Module:parse utilities"
local rhymes_module = "Module:rhymes"
local set_utilities_module = "Module:set utilities"
-- local jawi_decode_module = "Module:mdh-translit"
-- local jawi_encode_module = "Module:mdh-jawi sc"
local lang = require("Module:languages").getByCode("mdh")
local maxn = table.maxn
local rfind = m_str_utils.find
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local toNFC = mw.ustring.toNFC
local toNFD = mw.ustring.toNFD
local trim = mw.text.trim
local u = m_str_utils.char
local ulen = m_str_utils.len
local ulower = m_str_utils.lower
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local MACRON = u(0x0304) -- macron = ̄
local DOTOVER = u(0x0307) -- dot over = ̇
local NO_RELEASE = u(0x031A) -- no release = ̚
local vowel = "aeëəiou" -- vowel
local V = "[" .. vowel .. "]"
local W = "[jw]" -- glide
local NV = "[^" .. vowel .. "]"
local accent = AC .. GR .. CFLEX .. MACRON
local accent_c = "[" .. accent .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local separator = accent .. ipa_stress .. "# .-"
local C = "[^" .. vowel .. separator .. "]" -- consonant
local unstressed_words = m_table.listToSet {
-- TODO
}
local unstressed_affixes = m_table.listToSet {
-- TODO
}
local special_words = {
-- TODO
}
local function track(page)
require("Module:debug/track")("mdh-pronunciation/" .. page)
return true
end
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- Combine two sets of qualifiers, either of which may be nil or a list of qualifiers. Remove duplicate qualifiers.
-- Return value is nil or a list of qualifiers.
local function combine_qualifiers(qual1, qual2)
if not qual1 then
return qual2
end
if not qual2 then
return qual1
end
local qualifiers = m_table.deepCopy(qual1)
for _, qual in ipairs(qual2) do
m_table.insertIfNot(qualifiers, qual)
end
return qualifiers
end
local function decompose(text, recompose_e_dia)
-- decompose everything but ñ and ü
text = toNFD(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
if recompose_e_dia then
text = rsub(text, ".[" .. DIA .. "]", {
["e" .. DIA] = "ë",
["E" .. DIA] = "Ë",
})
end
return text
end
local function remove_accents(str)
str = decompose(str, "recompose e-dia")
str = rsub(str, "(.)" .. accent_c, "%1")
return str
end
local function split_on_comma(term)
if term:find(",%s") then
return require(parse_utilities_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
--Cleanup Jawi inputs--
-- local function decode_jawi(text)
-- -- TODO
-- end
-- ĵ, ɟ and ĉ are used internally to represent [d͡ʒ], [j] and [t͡ʃ]
--
function export.IPA(text, include_phonemic_syllable_boundaries)
local debug = {}
text = ulower(text)
text = decompose(text, "recompose e-dia")
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker
local words = rsplit(text, "([ %-])")
local function make_unstressed(word)
-- add macron to the last vowel not the first one, in case of affixes with qui/que/gui/gue (which don't
-- currently exist)
return rsub(word, "^(.*" .. V .. ")", "%1" .. MACRON)
end
local function signal_no_initial_glottal_stop(word)
return rsub(word, "^(" .. V .. ")", "◌%1")
end
for i=1, #words do
if i % 2 == 1 then -- a word, not a hyphen or space
if words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") and
words[i + 1] ~= "-" then
-- a suffix
if unstressed_affixes["-" .. words[i]] then
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] ~= "-" then
-- a prefix
if not rfind(words[i], accent_c) then
-- an unstressed prefix
words[i] = make_unstressed(words[i])
end
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") then
-- an interfix or infix
if not rfind(words[i], accent_c) then
-- an unstressed interfix or infix
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
else
-- a space-delimited word or a word in a hyphen-delimited compound
words[i] = special_words[words[i]] or words[i]
if unstressed_words[words[i]] then
words[i] = make_unstressed(words[i])
elseif words[i + 1] == "-" and (not words[i - 1] or words[i - 1] == " ") and
-- e.g. 'mag-' in [[mag-post]]
unstressed_affixes[words[i] .. "-"] then
words[i] = make_unstressed(words[i])
end
end
end
-- old code that I didn't port because I don't understand why it's being done; the purpose is to make suffixes
-- and infixes with explicit initial glottal stop be unstressed, which seems a weird exception
-- words[i] = rsub(words[i], "^%-([7ʔ])(" .. V .. ")", "-%1%2" .. MACRON) -- affix that requires glottal stop
end
text = table.concat(words, "")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[!?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
table.insert(debug, text)
-- handle certain combinations; ch ng and sh handling needs to go first
text = rsub(text, "([t]?)ch", "ĉ") --not the real sound
text = rsub(text, "([n]?)g̃", "ng") -- Spanish spelling support
text = rsub(text, "ng", "ŋ")
text = rsub(text, "sh", "ʃ") -- primarily for unassimilated forms of loanwords and names from Arabic (e.g. [[sharif]], [[sheriat]])
--x
text = rsub(text, "([#])x(" .. V .. ")", "%1s%2")
text = rsub(text, "x", "ks")
--c, g, q (for compatibility with Spanish-based forms, usually proper nouns, e.g. [[Guiaman]])
text = rsub(text, "c([iey])", "s%1")
text = rsub(text, "(" .. V .. ")gü([ie])", "%1ɡw%2")
text = rsub(text, "gü([ie])", "ɡuw%1")
text = rsub(text, "gui([aeëo])", "ɡy%1")
text = rsub(text, "gu([ie])", "ɡ%1")
text = rsub(text, "qu([ie])", "k%1")
text = rsub(text, "ü", "u")
--alphabet-to-phoneme
text = rsub(text, "[cëfgjñqrvz7]",
--["g"]="ɡ": U+0067 LATIN SMALL LETTER G → U+0261 LATIN SMALL LETTER SCRIPT G
{ ["c"] = "k", ["ë"] = "ə", ["g"] = "ɡ", ["j"] = "ĵ", ["ñ"] = "ny", ["q"] = "k", ["r"] = "ɾ", ["7"] = "ʔ"})
-- reduce any remaining double consonants (e.g. [[Kammis]], [[Sawwal]]);
text = rsub(text, "(" .. C .. ")%1", "%1")
-- ts
text = rsub(text, "t_s", "ć") --not the real sound
-- text = rsub(text, "ts", "ĉ") --not the real sound
--syllable division
local vowel_to_glide = { ["i"] = "j", ["o"] = "w", ["u"] = "w" }
-- i, o and u between vowels -> j and w. Usually in proper nouns
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)([iou])(" .. V .. ")",
function(v1, iou, v2)
return v1 .. vowel_to_glide[iou] .. v2
end
)
table.insert(debug, text)
-- text = rsub_repeatedly(text, "(" .. NV .. ")([u])([" .. AC .. MACRON .. "]?)([aeio])(" .. accent_c .. "?)","%1%2%3.w%4%5")
-- text = rsub_repeatedly(text, "(" .. V .. ")([u])([aeio])(" .. accent_c .. "?)","%1.w%3%4")
-- text = rsub_repeatedly(text, "(" .. V .. ")([o])([aei])(" .. accent_c .. "?)","%1.w%3%4")
-- text = rsub(text, "([i])([" .. AC .. MACRON .. "])([aeou])(" .. accent_c .. "?)","%1%2.y%3%4")
-- text = rsub(text, "([i])([aeou])(" .. accent_c .. "?)","y%2%3")
-- text = rsub(text, "a([".. AC .."]*)o([#.ʔ])","a%1w%2")
-- eu rules
text = rsub_repeatedly(text, "([#])([e])([u])(" .. accent_c .. "?)","%1y%3%4")
text = rsub_repeatedly(text, "(" .. NV .. ")([e])([" .. AC .. MACRON .. "]?)([u])(" .. accent_c .. "?)","%1%2%3.%4%5")
--determining whether "y" is a consonant or a vowel
text = rsub(text, "y(" .. accent_c .. ")", "i%1")
text = rsub(text, "y(" .. V .. ")", "ɟ%1") -- not the real sound
text = rsub(text,"(" .. NV .. ")y([ˈˌ.]*)([bćĉdfɡhjĵklmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","%1i%2%3")
text = rsub(text,"(" .. V .. ")y([ˈˌ.]*)([bćĉdfɡhjĵklmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","%1j%2%3")
text = rsub(text, "w(" .. V .. ")","w%1")
text = rsub(text,"(" .. NV .. ")w([ˈˌ]?)([bćĉdfɡjĵklmnɲŋpɾrsʃtvwɟzʔ#])","%1u%2%3")
-- Convert CV(wy)C pattern into diphthong
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)w(" .. NV .. ")([^lɾr" .. vowel .. separator .."])" ,"%1%2u%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)w([ĉdjĵlmnɲŋɾrstvwɟzʔ])([l" .. vowel .. separator .."])" ,"%1%2u%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)[jyɟ](" .. NV .. ")([^lɾr" .. vowel .. separator .."])" ,"%1%2i%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)[jyɟ]([ĉdjĵlmnɲŋɾrstvwɟzʔ])([l" .. vowel .. separator .."])" ,"%1%2i%3%4")
--vowels with grave/circumflex to vowel+glottal stop
text = rsub(text, CFLEX, AC .. GR)
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR .. "([#" .. vowel .. "])", "%1%2ʔ%3")
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR, "%1%2")
-- Add glottal stop for words starting with vowel
text = rsub(text, "([#])(" .. V .. ")", "%1ʔ%2")
text = rsub(text, "◌", "")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^hlɾrɟ" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dkt])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfɡklmnpsʃvz])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^hlɾrɟsʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. W .. "?" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeo, or stressed iu, should be syllabically divided from a following aeo or stressed iu.
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)(" .. V .. AC .. ")", "%1.%2")
text = rsub(text, "([iuə]" .. AC .. ")([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([iuə]" .. AC .. ")(" .. V .. AC .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
table.insert(debug, text)
local accent_to_stress_mark = { [AC] = "ˈ", [MACRON] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including macron indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post)
return accent_to_stress_mark[accent] .. pre .. post
end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and rfind(word, "[^aeiouəʔbcćĉdfɡghjɟĵklmnñɲŋpqrɾsʃtvwxz#]#") or #syllables == 1 and rfind(word, "[aeiou]") then
syllables[#syllables] = "ˈ" ..syllables[#syllables] -- Monosyllabic word schwa don't get stress
elseif #syllables >= 2 then
local vowel_find = false
local stress_find = false
for i=0, #syllables-1 do
if rfind(syllables[#syllables - i], V) then
if vowel_find then
syllables[#syllables - i] = "ˈ" .. syllables[#syllables - i]
stress_find = true
break
end
vowel_find = true
end
end
if vowel_find and not stress_find then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
accent_word(word, syllables)
-- Reconstruct the word.
words[j] = table.concat(syllables, ".")
end
text = table.concat(words, " ")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ")
table.insert(debug,text)
--correct final glottal stop placement
text = rsub(text,"([ˈˌ])ʔ([#]*)([ʔbĉćdfɡhĵɟklmnŋɲpɾrsʃtvwz])(" .. V .. ")","%1%2%3%4ʔ")
table.insert(debug,text)
--add temporary macron for /a/, /i/ and /u/ in stressed syllables so they don't get replaced by unstressed form
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([a])([ʔbdfɡiklmnŋpɾstu]?)([bdɡklmnpɾst]?)","%1%2%3%4ā%6%7")
--text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([i])([ʔbdfɡklmnŋpɾstu]?)([bdɡklmnpɾst]?)","%1%2%3%4ī%6%7")
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([u])([ʔbdfɡiklmnŋpɾst]?)([bdɡklmnpɾst]?)","%1%2%3%4ū%6%7")
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([ə])([ʔbdfɡiklmnŋpɾst]?)([bdɡklmnpɾst]?)","%1%2%3%4Ə%6%7")
table.insert(debug, text)
--remove "ɟ" and "w" inserted on vowel pair starting with "i" and "u"
text = rsub(text,"([i])([ˈˌ]?)ɟ([aāeəƏouū])","%1%2%3")
text = rsub(text,"([u])([ˈˌ]?)w([aāeəƏiī])","%1%2%3")
table.insert(debug,text)
text = rsub_repeatedly(text, "^([#]*)([ˈˌ])([#]*)", "%1%3%2") -- Move stress inside word boundary fix at start
text = rsub_repeatedly(text, "([ ])([#]*)([ˈˌ])([#]*)", "%1%2%4%3") -- Move stress inside word boundary fix at start
local mdh_IPA_table = {
["phonetic"] = text,
["phonemic"] = text,
["raw"] = text
}
for key, value in pairs(mdh_IPA_table) do
text = mdh_IPA_table[key]
--phonetic transcription
if key == "phonetic" then
table.insert(debug, text)
--i or u, and /e/ and /o/
text = rsub(text,"([e])","i")
text = rsub(text,"([o])","u")
--Corrections for diphthongs
text = rsub(text,"([aāeəƏiīouū])i","%1j") --ay
text = rsub(text,"([aāeəƏiīouū])u","%1w") --aw
--Turn phonemic diphthongs to phonetic diphthongs
text = rsub(text, "([iī])j", "%1i̯")
text = rsub(text, "([aāeəƏouū])j", "%1i̯")
text = rsub(text, "([aāeəƏiīouū])w", "%1u̯")
--Correction for diphthong
text = rsub(text, "([i])u̯", "i̯u")
-- text = rsub(text, "([u])i̯", "u̯i")
table.insert(debug, text)
text = rsub(text,"n([ˈˌ.]?)[ɟj]","%1ɲ") -- /n/ before /j/
text = rsub(text,"n[ɟji]([aāeəƏiɪ̯īouu̯ū])", "ɲ%1") -- /n/ before /j/
--Combine consonants (except H) followed by I/U and certain stressed vowels
text = rsub(text,"([bćĉdfɡhĵklmnɲŋpɾrstvz])([lnɾst]?)i([ˈˌ.])ɟ?([āaeƏəoūu])","%3%1%2i̯%4")
text = rsub(text,"([bćĉdfɡhĵklmnɲŋpɾrstvz])([lnɾst]?)u([ˈˌ.])w?([āaeƏəīio])","%3%1%2u̯%4")
text = rsub_repeatedly(text, "([.]+)", ".")
table.insert(debug, text)
-- /i/ on closed syllables regardless of stress
text = rsub(text,"([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[i]([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz])([bdɡklmnpɾst]?)","%1%2ɪ%3%4")
-- /u/ on closed syllables regardless of stress
text = rsub(text,"([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[ū]([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz])([bdɡklmnpɾst]?)","%1%2u%3%4")
table.insert(debug, text)
text = rsub(text,"([nŋ])([ˈˌ# .]*[bfpv])","m%2")
text = rsub(text,"([ŋ])([ˈˌ# .]*[dĵlstćĉz])","n%2")
text = rsub(text,"d([ˈˌ.])ɟ","%1ĵ") --/d/ before /j/
text = rsub(text,"d[ɟji]([aāeəƏɪii̯īouu̯ūʊ])","ĵ%1") --/d/ before /j/
text = rsub(text,"s[ɟji]([aāeəƏɪii̯īouu̯ūʊ])","ʃ%1") --/s/ before /j/
text = rsub(text,"([n])([ˈ ˌ# .]*[ɡk])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
text = rsub(text,"s([ˈˌ.])ɟ","%1ʃ") -- /s/ before /j/
text = rsub(text,"z([ˈˌ.])ɟ","%1ʒ") -- /z/ before /j/
text = rsub(text,"t([ˈˌ.])ɟ","%1ĉ") -- /t/ before /j/
text = rsub(text,"([ˈˌ.])d([ɟj])([aāeəƏɪiīouūʊ])","%1ĵ%3") -- /dj/ before any vowel following stress
text = rsub(text,"([ˈˌ.])n([ɟj])([aāeəƏɪiīouūʊ])","%1ɲ%3") -- /nj/ before any vowel following stress
text = rsub(text,"([ˈˌ.])s([ɟj])([aāeəƏɪiīouūʊ])","%1ʃ%3") -- /sj/ before any vowel following stress
text = rsub(text,"([ˈˌ.])t([ɟj])([aāeəƏɪiīouūʊ])","%1ĉ%3") -- /tj/ before any vowel following stress
-- Remove medial glottal stop in a phrase
text = rsub_repeatedly(text,"([ ])([#]+)([ˌˈ]?)[ʔ]([aāeəƏɪiīouūʊ])","%1%2%3%4")
-- Magindawn intervocalic /d/ and /l/
text = rsub_repeatedly(text,"([aāeəƏɪiīouū])([.ˈˌ #]+)[d]([aāeəƏɪiīouūʊ])","%1%2ɾ%3")
text = rsub_repeatedly(text,"([aāeəƏɪiīouū])([.ˈˌ #]+)[l]([aāeəƏɪiīouūʊ])","%1%2ɺ%3")
text = rsub_repeatedly(text,"([aāeəƏɪiīouū])[d]([.ˈˌ #]+)([aāeəƏɪiīouūʊ])","%1ɾ%2%3")
text = rsub_repeatedly(text,"([aāeəƏɪiīouū])[l]([.ˈˌ #]+)([aāeəƏɪiīouūʊ])","%1ɺ%2%3")
table.insert(debug, text)
-- /s/ to /z/ before voiced consonants
text = rsub(text, "([aāeəƏɪiīouūʊ])[s]([.]?)([bdɖɡ ])", "%1z%2%3")
text = rsub(text,"[d]([ˈˌ.]?)s","%1ĵ") --/d/ before /s/
-- Schwa changes
text = rsub(text,"[Ə]([ˈˌ.])l","a%1l")
text = rsub(text,"(#[ʔ])[ə]","%1a")
-- Bilabial affecting unstressed schwa
text = rsub(text, "([#][bpm])[ə]([ʔbćĉdfɡhĵɟklmnɲŋpɾrsʃtvwzʒ]?[#])", "%1ʊ%2")
-- Retroalveolars affecting unstressed schwa
text = rsub(text, "([#][ʔbćĉdfɡhĵɟklmnɲŋpɾrsʃtvwzʒ]?)[ə]([tdrlszn][#])", "%1ɪ%2")
-- Some consonants are retroflex in Magindanawn
text = rsub(text, "[tdɾlɺsn]", {
["t"] = "ʈ",
["d"] = "ɖ",
["ɾ"] = "ɽ",
["l"] = "ɭ",
["ɺ"] = "ɭ̆",
["s"] = "ʂ",
["z"] = "ʐ",
["n"] = "ɳ"
})
-- Voiceless stops are unreleased before another stop
text = rsub(text, "([aāeəƏɪiīouūʊ])([ptʈk])([.ˈˌ ]?)([#ptʈkbdɖɡ ])", "%1%2" .. NO_RELEASE .. "%3%4")
--change a to unstressed equivalents (certain forms to restore)
text = rsub(text,"a","ʌ")
text = rsub(text,"Ə","ə")
text = rsub(text, "ə" ,"ɨ")
elseif key == "phonemic" then
text = rsub(text,"([n])([ˈˌ#.]?[ɡk])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
--Corrections for diphthongs
text = rsub(text,"([aāeəƏiīouū])j","%1i") --ay
text = rsub(text,"([aāeəƏiīouū])w","%1u") --aw
if not include_phonemic_syllable_boundaries then
text = rsub(text,"%.","")
end
text = rsub(text,"‿", " ")
end
table.insert(debug, text)
--delete temporary macron in /a/, /i/ and /u/
text = rsub(text,"ā","a")
text = rsub(text,"ī","i")
text = rsub(text,"ū","u")
text = rsub(text,"Ə","ə")
-- Final fix for "iy" and "uw" combination
text = rsub(text,"([iɪ])([ˈˌ.]*)ɟ([aɐeɛəouʊ])","%1%2%3")
text = rsub(text,"([uʊ])([ˈˌ.]*)w([aɐeɛəiɪo])","%1%2%3")
text = rsub(text,"([ɪ])([ˈˌ.]*)ɟ([i])","%1%2%3")
text = rsub(text,"([i])([.]*)ɟ([ɪ])","%1%2%3")
text = rsub(text,"([ʊ])([ˈˌ.]*)w([u])","%1%2%3")
text = rsub(text,"([u])([.]*)w([ʊ])","%1%2%3")
--remove "ɟ" and "w" inserted on vowel pair starting with "e" and "o"
text = rsub(text,"([ɛe])([ː]?)([ˈˌ.]*)[ɟj]([aɐo])","%1%2%3%4")
text = rsub(text,"([o])([ː]?)([ˈˌ.]*)w([aɐeɛə])","%1%2%3%4")
-- convert fake symbols to real ones
local final_conversions = {
["ĉ"] = "t͡ʃ", -- fake "ch" to real "ch"
["ć"] = "t͡s", -- fake "ts" to real "ts"
["ɟ"] = "j", -- fake "y" to real "y"
["ĵ"] = "d͡ʒ" -- fake "j" to real "j"
}
text = rsub(text, "[ć]([" .. separator .. "])", "ts%1")
text = rsub(text, "[ĉćɟĵ]", final_conversions)
-- Do not have multiple syllable break consecutively
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub_repeatedly(text, "([.]?)(‿)([.]?)", "%2")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
-- resuppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
text = rsub_repeatedly(text, "([.]?)(" .. ipa_stress_c .. ")([.]?)", "%2")
mdh_IPA_table[key] = canon_spaces(toNFC(text))
end
return mdh_IPA_table
end
function export.show(frame)
local params = {
[1] = {},
["pre"] = {},
["bullets"] = {type = "number", default = 1},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local results = {}
local text = args[1] or mw.title.getCurrentTitle().text
local IPA_result = export.IPA(text)
table.insert(results, { pron = "/" .. IPA_result["phonemic"] .. "/" })
table.insert(results, { pron = "[" .. IPA_result["phonetic"] .. "]" })
local pre = args.pre and args.pre .. " " or ""
local bullet = (args.bullets ~= 0) and "* " or ""
return bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = results }
end
local function parse_gloss(arg)
local poses, gloss
if arg:find("%^") then
poses, gloss = arg:match("^(.-)%^(.*)$")
if gloss == "" then
gloss = nil
end
else
gloss = arg
end
if poses then
poses = split_on_comma(poses)
local m_headword_data = mw.loadData(headword_data_module)
for i, pos in ipairs(poses) do
poses[i] = m_headword_data.pos_aliases[pos] or pos
end
end
return {
poses = poses,
gloss = gloss,
}
end
-- Parse a raw accent spec, which is one or more comma-separated accent qualifiers.
local function parse_accents(arg)
return require(labels_module).split_labels_on_comma(arg)
end
-- Return the number of syllables of a phonemic or phonetic representation, which should have syllable dividers in it
-- but no hyphens.
local function get_num_syl_from_ipa(pron)
-- Maybe we should just count vowels instead of the below code.
pron = rsub(pron, "|", " ") -- remove IPA foot boundaries
local words = rsplit(pron, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
pron = table.concat(words, ".")
return ulen(rsub(pron, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
phonemic = rsplit(phonemic, " ")
phonemic = phonemic[#phonemic]
phonemic = rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^" .. NV .. "*", ""):gsub("%.", "")
phonemic = rsub_repeatedly(phonemic, "^[iu](" .. V .. ")", "%1" )
return phonemic
end
local function split_syllabified_spelling(spelling)
spelling = "#" .. spelling .. "#"
spelling = rsub_repeatedly(spelling, "%.([ #])", "·%1")
spelling = rsub_repeatedly(spelling, "#", "")
spelling = rsplit(spelling, "%.")
for key, value in ipairs(spelling) do
spelling[key] = rsub_repeatedly(value, "·", ".")
end
return spelling
end
-- "Align" syllabified respelling `syllab` to original spelling `spelling` by matching character-by-character, allowing
-- for extra syllable and accent markers in the syllabification and certain mismatches in the consonants. The goal is to
-- produce the appropriately syllabified version of the original spelling (the pagename) by matching characters in the
-- syllabified respelling to the original spelling, putting the syllable boundaries in the appropriate places in the
-- original spelling. As an example, given syllabified respelling 'a.ma.7ín' and original spelling 'amain', we would
-- like to produce 'a.ma.in'.
--
-- If we encounter an extra syllable marker (.), we allow and keep it. If we encounter an extra accent marker in thes
-- syllabification, we drop it. We allow for mismatches in capitalization and for certain other mismatches, e.g. extra
-- glottal stops (written 7), h in respelling vs. g or j in the original, etc. If we can't match, we return nil
-- indicating the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local function concat_result()
-- Postprocess to remove dots (syllable boundaries) next to hyphens.
return (toNFC(table.concat(result)):gsub("%.%-", "-"):gsub("%-%.", "-"))
end
-- Remove glottal stop (7) from respelling to simplify the code below, because it's never found in the original
-- spelling. (FIXME: We should do the same for diacritics, but they're currently removed earlier, in
-- syllabify_from_spelling(). We should probably get rid of the removal there and put it here.)
syllab = decompose(syllab:gsub("ː", ""), "recompose e-dia"):gsub("7", "")
spelling = decompose(spelling, "recompose e-dia")
local syll_chars = rsplit(ulower(syllab), "")
local spelling_chars = rsplit(spelling, "")
local i = 1
local j = 1
local function matches(uci, ucj)
-- Return true if a syllabified respelling character (uci) matches the corresponding spelling char (ucj).
-- Both uci and ucj should be lowercase.
-- Sound is at the key, values are the letters sound can match
local matching_chars = {
["e"] = {"i"},
["ë"] = {"a", "e", "o", "u"},
["h"] = {"g", "j", "x"},
["i"] = {"e"},
["j"] = {"g"},
["k"] = {"j"},
["o"] = {"u"},
["s"] = {"j", "c", "x"},
["u"] = {"o"},
["w"] = {"u", "o"},
["y"] = {"i"}
}
return uci == ucj or (matching_chars[uci] and m_table.contains(matching_chars[uci], ucj))
end
local function silent_spelling_letter(ucj)
return ucj == "h" or ucj == "'" or ucj == "-"
end
local function syll_at(pos)
return syll_chars[pos] or ""
end
local function spell_at(pos)
return spelling_chars[pos] or ""
end
local function uspell_at(pos)
local c = spelling_chars[pos]
return c and ulower(c) or ""
end
while i <= #syll_chars or j <= #spelling_chars do
local uci = syll_at(i)
local cj = spell_at(j)
local ucj = uspell_at(j)
if uci == "g" and syll_at(i - 1) == "n" and syll_at(i + 1) == "." and matches(syll_at(i + 2), ucj) and
not matches(syll_at(i + 2), uspell_at(j + 1)) then
-- As a special case, before checking whether the corresponding characters match, we have to skip an extra
-- g in an -ng- sequence in the syllabified respelling if the corresponding spelling character matches the
-- next respelling character (taking into account the syllable boundary). This is so that e.g.
-- syll='ba.rang.gay' matches spelling='barangay'. Otherwise we will match the first respelling g against
-- the spelling g and the second respelling g won't match. A similar case occurs with
-- syll='E.vang.he.lis.ta' and spelling='Evangelista'. But we need an extra condition to not do this hack
-- when syll='ba.rang.gay' matches spelling='baranggay'.
i = i + 1
elseif uci == "g" and ucj == "g" and uspell_at(j + 1) == TILDE then
table.insert(result, cj)
table.insert(result, uspell_at(j + 1))
i = i + 1
j = j + 2
elseif matches(uci, ucj) then
table.insert(result, cj)
i = i + 1
j = j + 1
elseif ucj == uspell_at(j - 1) and uci == "." and ucj ~= syll_at(i + 1) then
-- See below. We want to allow for a doubled letter in spelling that is pronounced single, and preserve the
-- doubled letter. But it's tricky in the presence of syllable boundaries on both sides of the doubled
-- letter as well as doubled letters pronounced double. Specifically, there are three possibilities,
-- exemplified by:
-- (1) syll='Mal.lig', spelling='Mallig' -> 'Mal.lig';
-- (2) syll='Ma.lig', spelling='Mallig' -> 'Ma.llig';
-- (3) syll='Wil.iam', spelling='William' -> 'Will.iam'.
-- If we copy the dot first, we get (1) and (2) right but not (3).
-- If we copy the double letter first, we get (2) and (3) right but not (1).
-- We choose to copy the dot first except in the situation exemplified by (3), where we copy the doubled
-- letter first. The condition above handles (3) (the doubled letter matches against a dot) while not
-- interfering with (1) (where the doubled letter also matches against a dot but the next letter in the
-- syllabification is the same as the doubled letter, because the doubled letter is pronounced double).
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) and uci == "." and ucj ~= syll_at(i + 1) and
not rfind(uspell_at(j + 1), V) then
-- See below for silent h or apostrophe in spelling. This condition is parallel to the one directly above
-- for silent doubled letters in spelling and handles the case of syllab='Abduramán', spelling='Abdurahman',
-- which should be syllabified 'Ab.du.rah.man'. But we need a check to see that the next spelling character
-- isn't a vowel, because in that case we want the silent letter to go after the period, e.g.
-- syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah' (the 7 is removed above).
table.insert(result, cj)
j = j + 1
elseif uci == "." then
table.insert(result, uci)
i = i + 1
elseif ucj == uspell_at(j - 1) then
-- A doubled letter in spelling that is pronounced single. Examples:
-- * syllab='Ma.líg', spelling='Mallig' -> 'Ma.llig' (with l)
-- * syllab='Lu.il.yér', spelling='Lhuillier' -> 'Lhu.ill.ier' (with l; a more complex example)
-- * syllab='a.sa.la.mu a.lai.kum', spelling='assalamu alaikum' -> 'as.sa.la.mu a.lai.kum' (with s)
-- * syllab='Jé.fer.son', spelling='Jefferson' -> 'Je.ffer.son' (with f)
-- * syllab='Je.ma', spelling='Gemma' -> 'Ge.mma' (with m)
-- * syllab='Ha.na', spelling='Hannah' -> 'Ha.nnah' (with n)
-- * syllab='A.by', spelling='Abby' -> 'A.bby' (with b)
-- * syllab='Ka.ba', spelling='Kaaba' -> 'Kaa.ba' (with a)
-- * syllab='Fu.ji', spelling='Fujii' -> 'Fu.jii' (with i)
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) then
-- A silent h, apostrophe or hyphen in spelling. Examples:
-- * syllab='adán', spelling='adhan' -> 'a.dhan'
-- * syllab='Atanasya', spelling='Athanasia' -> 'A.tha.nas.ia'
-- * syllab='Cýntiya', spelling='Cynthia' -> 'Cyn.thi.a'
-- * syllab='Ermóhenes', spelling='Hermogenes' -> 'Her.mo.ge.nes'
-- * syllab='Abduramán', spelling='Abdurahman' -> 'Ab.du.rah.man'
-- * syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah'
-- * syllab='pag7ibig', spelling='pag-ibig' -> 'pag-i.big'
table.insert(result, cj)
j = j + 1
elseif uci == AC or uci == GR or uci == CFLEX or uci == DIA or uci == TILDE or uci == MACRON or
uci == "y" or uci == "w" then
-- skip character
i = i + 1
else
-- non-matching character
mw.log(("Syllabification alignment mismatch for pagename '%s' (position %s, character %s), syllabified respelling '%s' (position %s, character %s), aligned result so far '%s'"
):format(spelling, j, ucj, syllab, i, uci, concat_result()))
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
mw.log(("Syllabification alignment mismatch for pagename '%s' (%s), syllabified respelling '%s' (%s), aligned result so far '%s'"
):format(
spelling, j > #spelling_chars and "end of string" or ("position %s, character %s"):format(j, uspell_at(j)),
syllab, i > #syll_chars and "end of string" or ("position %s, character %s"):format(i, syll_at(i)),
concat_result()))
return nil
end
return concat_result()
end
local function generate_syll_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
word = ulower(word)
return rfind(word, V) or word:find("y")
end
local function any_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word_has_vowels(word) then
return true
end
end
return false
end
-- local function has_jawi(text)
-- -- TO DO
-- end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
local last_word = words[#words]
local should_generate_cat = #words == 1
local should_generate_rhyme =
not last_word:find("%-$") and -- no if word is a prefix
not (last_word:find("^%-") and last_word:find(MACRON)) and -- no if word is an unstressed suffix
word_has_vowels(last_word) -- no if word has no vowels (e.g. a single letter)
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_ipa(ipa)
local should_generate_cat = not ipa:find("%s")
local should_generate_rhyme = word_has_vowels(decompose(ipa))
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_termobj(termobj)
if termobj.raw then
return should_generate_rhyme_from_ipa(termobj.raw_phonemic or termobj.raw_phonetic)
else
return should_generate_rhyme_from_respelling(termobj.term)
end
end
local function process_specified_rhymes(rhymes, sylls, parsed_respellings)
local rhyme_ret = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the syllabification.
if not num_syl then
num_syl = {}
for _, syll in ipairs(sylls) do
if should_generate_rhyme_from_respelling(syll.syllabification) then
local this_num_syl = 1 + ulen(rsub(syll.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for _, pronun in ipairs(parsed.pronuns) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given), and rhyme
-- isn't suppressed (which may happen if the term has a qualifier "colloquial", "obsolete" or the
-- like or is an auto-generated "glottal stop elision" pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_ipa(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
local rhymeobj = m_table.shallowCopy(rhyme)
rhymeobj.num_syl = num_syl
table.insert(rhyme_ret, rhymeobj)
end
end
-- Parse a pronunciation modifier in `arg`, the argument portion in an inline modifier (after the prefix), which
-- specifies a pronunciation property such as rhyme, syllabification, homophones or audio. The argument can itself have
-- inline modifiers, e.g. <audio:Foo.ogg<a:Colombia>>. The allowed inline modifiers are specified by `param_mods` (of
-- the format expected by `parse_inline_modifiers()`); in addition to any modifiers specified there, the modifiers
-- <q:...>, <qq:...>, <a:...> and <aa:...> are always accepted (and can be repeated). `generate_obj` and `parse_err` are
-- like in `parse_inline_modifiers()` and specify respectively a function to generate the object into which modifier
-- properties are stored given the non-modifier part of the argument, and a function to generate an error message (given
-- the message). Normally, a comma-separated list of pronunciation properties is accepted and parsed, where each element
-- in the list can have its own inline modifiers and where no spaces are allowed next to the commas in order for them to
-- be recognized as separators. If `no_split_on_comma` is given, only a single pronunciation property is accepted. If
-- `has_outer_container` is given, the list of pronunciation properties is embedded in the `terms` property of an outer
-- container, into which other list-level modifiers can also be stored (by setting `overall = "true"` in the respective
-- spec in `param_mods`). The return value is a list if neither `no_split_on_comma` nor `has_outer_container` are given,
-- otherwise a container object (which, in the case of `has_outer_container`, will contain a list inside of it, in the
-- `terms` property).
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma, has_outer_container)
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(parse_utilities_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
outer_container = has_outer_container and {} or nil,
})
elseif no_split_on_comma then
return generate_obj(arg)
else
local retval = {}
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
if has_outer_container then
retval = {
terms = retval,
}
end
return retval
end
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
type = "number",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_syll(arg, parse_err)
local param_mods = {
cap = { overall = true},
}
-- We need to pass in has_outer_container because we have an overall property <cap:...> (the caption, defaulting
-- to "Syllabification") applying to the whole set of syllabifications.
return parse_pron_modifier(arg, parse_err, generate_syll_obj, param_mods, nil, "has outer container")
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the genders in "genders".
item_dest = "genders",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space (typically followed by
-- an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
local function syllabify_from_spelling(text, pagename)
-- Auto syllabifications start --
local vowel = vowel .. "ẃý" -- vowel
local V = "[" .. vowel .. "]"
local NV = "[^" .. vowel .. "]"
local C = "[^" .. vowel .. separator .."]" -- consonant
text = rsub(text, "ng", "ŋ")
-- reduce any remaining double consonants by default (e.g. [[Kammis]], [[Sawwal]]);
text = rsub(text, "(" .. C .. ")%1", "%1")
text = rsub(text, "ŋ", "ng")
text = decompose(text, "recompose e-dia")
local origtext = remove_accents(text)
text = string.lower(text)
text = rsub(text, "[.] ", "․ ")
text = rsub(text, "[.]$", "․")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
text = rsub(text, "ng̃", "ŋ")
text = rsub(text, "ng", "ŋ")
text = rsub(text, "g̃", "ġ")
text = rsub(text, "ch", "ĉ")
text = rsub(text, "t_s", "ć")
text = rsub(text, "sh", "ʃ")
text = rsub(text, "gu([eëiy])", "ǵ%1")
text = rsub(text, "qu([eëiy])", "ḱ%1")
text = rsub(text, "r", "ɾ")
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "ʔ", "7")
-- text = rsub_repeatedly(text, "#(" .. C .. "+)u([aeio])","#%1u.%2")
-- text = rsub_repeatedly(text, "#(" .. C .. "+)i([aeou])","#%1i.%2")
text = rsub(text, "([^#])(" .. C .. ")u([aeio])","%1.%2u%3")
text = rsub(text, "([^#])(" .. C .. ")i([aeou])","%1.%2i%3")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)u([aeio])","%1.u%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)o([aei])","%1.ó%2")
text = rsub(text, "a(" .. accent_c .. "*)o([#.7])","a%1ó%2")
-- eu rules
text = rsub_repeatedly(text, "([^" .. vowel .. "#])([e])(" .. accent_c .. "?)([u])(" .. accent_c .. "?)","%1%2%3.%4%5")
text = rsub(text, "y([ˈˌ." .. accent .. "]*)([bćĉdfgǵhjĵkḱlmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","ý%1%2")
text = rsub(text, "ý(" .. V .. ")", "y%1")
text = rsub(text, "w([ˈˌ]?)([bćĉdfgǵjĵkḱlmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","ẃ%1%2")
text = rsub(text, "ẃ(" .. V .. ")","w%1")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ẃ([bdfgǵkḱpt])([ɾr" .. vowel .. separator .."])" ,"%1%2w%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ẃ([bfgǵkḱp])([l" .. vowel .. separator .."])" ,"%1%2w%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ý([bdfgǵkḱpt])([ɾr" .. vowel .. separator .."])" ,"%1%2y%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ý([bfgǵkḱp])([l" .. vowel .. separator .."])" ,"%1%2y%3%4")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^lɾrɟy" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dk])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([t])([^lɾrɟys" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfgǵkḱlmnpsʃvz])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^lɾrɟysʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. W .. "?" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeëo, or stressed iu, should be syllabically divided from a following aeëo or stressed iu.
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)(" .. V .. accent_c .. ")", "%1.%2")
text = rsub(text, "([iu]" .. accent_c .. ")([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([iu]" .. accent_c .. ")(" .. V .. accent_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
text = rsub_repeatedly(text, "i[.]([aeëou])", "i%1")
text = rsub_repeatedly(text, "u[.]([aeëio])", "u%1")
text = rsub(text, "ĉ", "ch")
text = rsub(text, "ć", "ts")
text = rsub(text, "ŋ", "ng")
text = rsub(text, "ʃ", "sh")
text = rsub(text, "ǵ", "gu")
text = rsub(text, "ġ", "g̃")
text = rsub(text, "ḱ", "qu")
text = rsub(text, "r", "rr")
text = rsub(text, "ɾ", "r")
text = remove_accents(text)
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub(text, "[.]?-[.]?", "-")
text = rsub(text, "[‿]([^ ])", "|%1")
text = rsub(text, "[.]([^ ])", "|%1")
text = rsub(text, "([|])+", "%1")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
text = rsub(text, "․", ".")
-- Fix Capitalization --
local syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" and origtext:sub(i-syllbreak, i-syllbreak) ~= "." and origtext:sub(i-syllbreak, i-syllbreak) ~= "7" then
syllbreak = syllbreak + 1
elseif origtext:sub(i-syllbreak, i-syllbreak) == text:sub(i,i):upper() then
text = table.concat({text:sub(1, i-1), text:sub(i,i):upper(), text:sub(i+1)})
end
end
-- Fix hyphens --
-- FIXME!!! Why are we relying on looking at the pagename here? This should not be happening.
origtext = pagename
if (table.concat(rsplit(origtext, "-")) == table.concat(rsplit(table.concat(rsplit(text, "|")), "-"))) then
syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" then
if origtext:sub(i-syllbreak, i-syllbreak) == "-" then
text = table.concat({text:sub(1, i-1), "-", text:sub(i+1)})
else
syllbreak = syllbreak + 1
end
end
end
end
-- FIXME! Hack -- up above we changed periods to vertical bars. The rest of the code expects periods so change
-- them back. We should clean up the code above to leave the periods alone.
return (text:gsub("|", "%."))
end
function export.syllabify_and_align(respelling, pagename)
local syllabification = syllabify_from_spelling(respelling, pagename)
return align_syllabification_to_spelling(syllabification, pagename)
end
local function css_wrap(text, classes)
return ('<span class="%s">%s</span>'):format(classes, text)
end
local function format_glosses(glosses)
if not glosses then
return ""
end
local formatted_glosses = {}
for _, glossobj in ipairs(glosses) do
local gloss_parts = {}
if glossobj.gloss then
table.insert(gloss_parts, css_wrap("“", "mention-gloss-double-quote") ..
css_wrap(glossobj.gloss, "mention-gloss") .. css_wrap("”", "mention-gloss-double-quote"))
end
if glossobj.poses then
for _, pos in ipairs(glossobj.poses) do
table.insert(gloss_parts, css_wrap(pos, "ann-pos"))
end
end
table.insert(formatted_glosses, table.concat(gloss_parts, css_wrap(",", "mention-gloss-comma") .. " "))
end
return " " .. css_wrap("(", "mention-gloss-paren annotation-paren") ..
table.concat(formatted_glosses, css_wrap(";", "mention-gloss-semicolon") .. " ") ..
css_wrap(")", "mention-gloss-paren annotation-paren")
end
local function format_pronuns(pronuns)
local pronunciations = {}
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]].
for j, pronun in ipairs(pronuns) do
local qs = pronun.q
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
end
local last_pronun = #pronunciations
if pronun.q then
pronunciations[first_pronun].q = pronun.q
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
end
end
return m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" }
end
local function format_pronun_line(parsed)
local formatted_pronuns = format_pronuns(parsed.pronuns)
local pre = is_first and parsed.pre and parsed.pre .. " " or ""
local post = is_first and parsed.post and " " .. parsed.post or ""
return pre .. formatted_pronuns .. format_glosses(parsed.t) .. post
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- External entry point for {{mdh-pr}}.
function export.show_full(frame)
--------------------------------- 1. Parse the arguments. ------------------------------------
local params = {
[1] = {list = true},
["rhyme"] = {},
["syll"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "=" .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_syll = args.syll and
parse_syll(args.syll, function(msg) overall_parse_err(msg, "syll", args.syll) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if #args.audio > 0 then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
table.insert(overall_audio, parsed_audio)
end
end
-- Parse each respelling. Individual arguments in 1=, 2=, etc. can consist of one or more comma-separated
-- respellings, each of which can have inline modifiers <q:...>, <qq:...>, <a:...>, <aa:...> or <ref:...>.
-- In addition, the respellings as a whole of a given argument can be followed by various inline modifiers,
-- such as <t:...>, <rhyme:...>, <syll:...>, etc. The result of parsing goes into `parsed_respellings`, which
-- is a list of objects (one per numbered argument), each of which is a table of the form
--
-- {
-- terms = {TERM, TERM, ...},
-- audio = {AUDIO, AUDIO, ...},
-- rhyme = {RHYME, RHYME, ...},
-- syll = {SYLL, SYLL, ...},
-- hmp = {HMP, HMP, ...},
-- t = {GLOSS, GLOSS, ...},
-- pre = "PRE-TEXT" or nil,
-- post = "POST-TEXT" or nil,
-- bullets = NUM_BULLETS,
-- accents = {"ACCENT", "ACCENT", ...},
-- }
--
-- In this structure, TERM is an object that usually has the form
--
-- {
-- term = "RESPELLING",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- Note that in this structure, "REF-SPEC" of the form parsable by parse_references() in [[Module:references]].
--
-- Alternatively, if phonemic or phonetic IPA is given in place of a respelling, TERM will have the form
--
-- {
-- raw = true,
-- phonemic = "PHONEMIC",
-- phonetic = "PHONETIC",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- AUDIO is a table of the form
--
-- {
-- file = "FILE",
-- gloss = "GLOSS",
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- a = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- aa = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- }
--
-- RHYME is a table of the form
--
-- {
-- rhyme = "RHYME",
-- num_syl = {NUM_SYL, NUM_SYL, ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- SYLL is a table of the form (where `hyph` is required to be named this way for [[Module:hyphenation]])
--
-- {
-- syllabification = "SYL.LAB.LES",
-- hyph = {"SYL", "LAB", "LES"},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- HMP is a table of the form
--
-- {
-- term = "HOMOPHONE",
-- gloss = "GLOSS" or nil,
-- pos = "POS" or nil,
-- alt = "ALT" or nil,
-- lit = "LIT" or nil,
-- id = "ID" or nil,
-- g = {"G", "G", ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- GLOSS is a table of the form
--
-- {
-- poses = {"POS", "POS", ...} or nil,
-- gloss = "GLOSS" or nil,
-- }
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
type = "number",
},
t = {
overall = true,
store = "insert",
convert = parse_gloss,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
syll = {
overall = true,
-- Not `store = "insert-flattened"`. parse_syll() does not generates a list but a structure where
-- the syllabifications are in `terms` and there's an additional overall property `cap` for the
-- caption (defaulting to "Syllabification"). FIXME: Rethink whether we even want "insert-flattened"
-- or just "insert" for the remaining pronunciation properties.
convert = parse_syll,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert", -- not "insert-flattened" because parse_audio returns a single object
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = {
item_dest = "accents",
overall = true,
convert = parse_accents,
},
}
local parsed = require(parse_utilities_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
pre_normalize_modifiers = function(data)
local modtext = data.modtext
modtext = modtext:match("^<(.*)>$")
if not modtext then
error(("Internal error: Passed-in modifier isn't surrounded by angle brackets: %s"):format(
data.modtext))
end
if modtext:find("%^") and not modtext:find("^t:") then
modtext = "t:" .. modtext
end
return "<" .. modtext .. ">"
end,
splitchar = ",",
outer_container = {},
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "=" .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
bullets = 1,
})
end
end
--------------------------------- 2. Generate IPA, rhymes and syllabification. ------------------------------------
-- Used for categorization below.
local syllabification_alignment_failed = false
-- Canonicalize syllabifications in `sylls` by convering '+' to the default syllabification of the pagename, '#' to
-- the pagename itself, and '-' to no syllabification (return `null_syll`). If '-' not seen, return `sylls`.
local function canonicalize_syllabification(sylls, null_syll)
for _, syll in ipairs(sylls.terms) do
if syll.syllabification == "+" then
syll.syllabification = syllabify_from_spelling(pagename, pagename)
syll.hyph = split_syllabified_spelling(syll.syllabification)
elseif syll.syllabification == "#" then
syll.syllabification = pagename
syll.hyph = {syll.syllabification}
elseif syll.syllabification == "-" then
return null_syll
end
end
return sylls
end
if overall_syll then
overall_syll = canonicalize_syllabification(overall_syll, {})
end
local function doesnt_count_for_rhyme(list)
if not list then
return false
end
local accent_no_count = {"colloquial", "obsolete", "relaxed"}
for _, item in ipairs(list) do
for _, word_no_count in ipairs(accent_no_count) do
if item:find("%f[%w]" .. word_no_count .. "%f[%W]") then
return true
end
end
end
return false
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
-- First, sort the specified accents and default to "Magindanawn".
local default_accent = "標準馬京達瑙語"
if not parsed.accents then
parsed.accents = {default_accent}
end
-- If more than one respelling given, then if any accent or qualifier has the words 'colloquial', 'obsolete' or
-- 'relaxed' in them, don't generate a rhyme or a '#-syllable word' category.
local more_than_one_respelling = #parsed.terms > 1 or #parsed_respellings > 1
local is_standard = m_table.contains(parsed.accents, default_accent)
local all_terms_no_rhyme = more_than_one_respelling and doesnt_count_for_rhyme(parsed.accents)
parsed.pronuns = {}
for i, term in ipairs(parsed.terms) do
local phonemic, phonetic
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
else
local ret = export.IPA(term.term, "include phonemic syllable boundaries")
phonemic = ret.phonemic
phonetic = ret.phonetic
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
local no_rhyme, rhyme_with_cat
-- Same check as above for colloquial/obsolete/relaxed but check the qualifiers, which are attached to
-- individual respellings rather than a single-line set of respellings.
no_rhyme = all_terms_no_rhyme or more_than_one_respelling and (
doesnt_count_for_rhyme(term.q) or doesnt_count_for_rhyme(term.qq)
)
if not no_rhyme then
local should_generate_rhyme, should_generate_cat = should_generate_rhyme_from_termobj(term)
no_rhyme = not should_generate_rhyme
rhyme_with_cat = should_generate_cat
end
local pronobj = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
no_rhyme = no_rhyme,
rhyme_with_cat = rhyme_with_cat,
}
table.insert(parsed.pronuns, pronobj)
end
if not parsed.syll then
if not overall_syll and any_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term, pagename)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
if not parsed.syll then
parsed.syll = {terms = {}}
end
m_table.insertIfNot(parsed.syll.terms, generate_syll_obj(aligned_syll))
else
syllabification_alignment_failed = true
end
end
end
end
else
parsed.syll = canonicalize_syllabification(parsed.syll, nil)
end
if not parsed.rhyme then
if overall_rhyme then
parsed.rhyme = nil
else
-- Generate the rhymes.
for _, pronun in ipairs(parsed.pronuns) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation
-- (see `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if
-- raw phonetic-only pronun is given), and rhyme isn't suppressed (which may happen if the term has
-- a qualifier "colloquial", "obsolete" or the like or is an auto-generated "glottal stop elision"
-- pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
-- Copying qualifiers to rhymes:
-- (1) If there's only one pronunciation, displaying any associated qualifier on the rhyme is
-- is redundant, so don't do it.
-- (2) If there are multiple pronunciations, then we generally do want to copy the qualifier(s)
-- from pronunciation to rhyme, but only if a given rhyme either derives from a single
-- pronunciation, or derives from multiple pronunciations all of which share the same
-- qualifier(s). We do NOT want to combine two different qualifiers from two different
-- pronunciations.
-- (3) If there are multiple pronunciations that map to a single rhyme, and all pronunciations
-- share qualifiers, then we might consider omitting the qualifiers as redundant; but this
-- case will rarely happen so it might not be worth worrying about.
-- (4) Similarly, if there are multiple pronunciations where some have the rhyme suppressed (see
-- above), and all pronunciations share qualifiers, then we might consider omitting the
-- qualifiers as redundant; but again, this case will rarely happen (especially since in
-- almost all cases the suppressed-rhyme pronunciation will have distinctive qualifiers) so
-- it probably isn't worth worrying about. Note that in the common case where the qualifiers
-- of the rhyme-suppressed pronunciation differ from those of the rhyme-included
-- pronunciation, we do want to include the qualifiers of the rhyme-included pronunciation
-- (imagine e.g. there are two pronunciations marked "standard" and "colloquial"; we want to
-- mark the rhyme as "standard").
-- (4) There are two different types of qualifiers (left and right); when comparing qualifiers,
-- we need to compare the entire set of both qualifiers and make sure they both match
-- (although it will be rare to have both left and right qualifiers on a single
-- pronunciation).
local saw_already = false
if not parsed.rhyme then
parsed.rhyme = {}
end
for _, existing in ipairs(parsed.rhyme) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
if not m_table.deepEquals(existing.q, pronun.q) or not
m_table.deepEquals(existing.qq, pronun.qq) then
existing.q = nil
existing.qq = nil
end
break
end
end
if not saw_already then
table.insert(parsed.rhyme, {
rhyme = rhyme,
num_syl = {num_syl},
q = #parsed.pronuns > 1 and pronun.q or nil,
qq = #parsed.pronuns > 1 and pronun.qq or nil,
nocat = not pronun.rhyme_with_cat,
})
end
end
end
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = process_specified_rhymes(parsed.rhyme, parsed.syll and parsed.syll.terms or {}, {parsed})
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_sylls
if overall_syll then
all_sylls = overall_syll
else
all_sylls = {}
for _, parsed in ipairs(parsed_respellings) do
if parsed.syll then
for _, syll in ipairs(parsed.syll.terms) do
m_table.insertIfNot(all_sylls, syll)
end
end
end
end
overall_rhyme = process_specified_rhymes(overall_rhyme, all_sylls, parsed_respellings)
end
end
-- Determine whether all sets of pronunciations have the same value for a pronunciation property (rhymes,
-- syllabifications or homophones). If so, we display them them only once at the bottom, otherwise beneath each set,
-- indented. This function takes one argument, the name of a slot specifying the pronunciation property, and
-- returns two values, a boolean indicating whether all values are the same and the first value seen (which will
-- be the only value seen if all values are the same).
local function all_sets_equal(parsed_slot)
local first_set
local all_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_set = parsed[parsed_slot]
elseif not m_table.deepEquals(first_set, parsed[parsed_slot]) then
all_sets_eq = false
break
end
end
return all_sets_eq, first_set
end
local all_rhyme_sets_eq, first_rhyme_ret = all_sets_equal("rhyme")
local all_syll_sets_eq, first_sylls = all_sets_equal("syll")
local all_hmp_sets_eq, first_hmps = all_sets_equal("hmp")
------------------------------ 3. Insert categories as appropriate. ---------------------------------
local categories = {}
local function get_rhymes_categories(rhymes)
if not rhymes then
return
end
-- for _, rhyme in ipairs(rhymes) do
-- local num_vowels_in_rhyme = #rsub(rhyme.rhyme, NV, "")
-- local penult = num_vowels_in_rhyme == 2
-- local glottal = rhyme.rhyme:find("ʔ$")
-- local pron_cat
-- if penult and glottal then
-- pron_cat = "malumi"
-- elseif penult then
-- pron_cat = "malumay"
-- elseif glottal then
-- pron_cat = "maragsa"
-- else
-- pron_cat = "mabilis"
-- end
-- m_table.insertIfNot(categories,
-- ("%s terms with %s pronunciation"):format(lang:getCanonicalName(), pron_cat))
-- end
end
get_rhymes_categories(overall_rhyme)
for _, parsed in ipairs(parsed_respellings) do
get_rhymes_categories(parsed.rhyme)
end
local function get_syll_categories(sylls)
if not sylls then
return
end
for _, syll in ipairs(sylls.terms) do
local syll_no_dot = "#" .. syll.syllabification .. "#"
syll_no_dot = syll.syllabification:gsub("%.([^ #])", "%1"):gsub("#", "")
if syll_no_dot ~= pagename then
mw.log(("For page '%s', saw syllabification '%s' not matching pagename"):format(
pagename, syll.syllabification))
m_table.insertIfNot(categories, ("%s terms with syllabification not matching pagename"):format(
lang:getCanonicalName()))
end
end
end
get_syll_categories(overall_syll)
for _, parsed in ipairs(parsed_respellings) do
get_syll_categories(parsed.syll)
end
if syllabification_alignment_failed then
table.insert(categories, ("%s terms where syllabification alignment failed"):format(lang:getCanonicalName()))
end
---------------------------- 4. Format IPA, rhymes and syllabification for display. -------------------------------
local function bullet_prefix(num_bullets)
return string.rep("*", num_bullets) .. " "
end
local function format_rhyme(rhymes)
return require(rhymes_module).format_rhymes {
lang = lang,
rhymes = rhymes,
force_cat = force_cat,
}
end
local function format_syllabifications(syllobj)
return require(hyphenation_module).format_hyphenations {
lang = lang,
hyphs = syllobj.terms,
caption = syllobj.cap or "音節化"
}
end
local function format_homophones(hmps)
return require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, bullet_prefix(num_bullets) .. text)
end
return table.concat(ret, "\n")
end
-- Implement grouping by accent. If there is a run of more than one consecutive set of pronunciations with the
-- same accent, the accent goes on its own line and the pronunciations with this accent go below with an extra
-- bullet.
local prev_accents
local num_seen_with_these_accents
for j, parsed in ipairs(parsed_respellings) do
if m_table.deepEquals(prev_accents, parsed.accents) then
parsed.of_several_accents = "continuation"
num_seen_with_these_accents = num_seen_with_these_accents + 1
if num_seen_with_these_accents == 2 then
parsed_respellings[j - 1].of_several_accents = "first"
end
else
prev_accents = parsed.accents
num_seen_with_these_accents = 1
end
end
-- Pull out autogenerated pronunciations and move to the next line, indented.
for _, parsed in ipairs(parsed_respellings) do
local saw_next_line_pronuns = false
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
saw_next_line_pronuns = true
break
end
end
if saw_next_line_pronuns then
local this_line_pronuns = {}
local next_line_pronuns = {}
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
table.insert(next_line_pronuns, pronun)
else
table.insert(this_line_pronuns, pronun)
end
end
-- Now see if there are qualifiers shared among all elements of the next-line pronuns and deduplicate if so.
local function deduplicate_qualifiers(field, keepfirst)
local saw_nil = false
for _, pronun in ipairs(next_line_pronuns) do
if not pronun[field] then
saw_nil = true
break
end
end
if not saw_nil then
local m_setutil = require(set_utilities_module)
local qualifiers = {}
for _, pronun in ipairs(next_line_pronuns) do
table.insert(qualifiers, m_setutil.list_to_set(pronun[field]))
end
local all_shared = m_setutil.intersect(unpack(qualifiers))
if next(all_shared) then
local first_index, last_index
if keepfirst then
first_index = 2
last_index = #pronun
else
first_index = 1
last_index = #pronun - 1
end
for i = first_index, last_index do
local pronun = next_line_pronuns[i]
local new_qualifiers = {}
for _, q in ipairs(pronun[field]) do
if not all_shared[q] then
table.insert(new_qualifiers, q)
end
end
pronun[field] = new_qualifiers
end
end
end
end
parsed.pronuns = this_line_pronuns
parsed.next_line_pronuns = next_line_pronuns
end
end
-- Now actually format the pronunciations.
local textparts = {}
local first_line = true
local function ins_line(linetext, num_bullets)
if not first_line then
table.insert(textparts, "\n")
end
first_line = false
table.insert(textparts, bullet_prefix(num_bullets) .. linetext)
end
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
local accent_grouping_offset = 0
if parsed.of_several_accents == "first" then
ins_line(require(accent_qualifier_module).format_qualifiers(lang, parsed.accents), parsed.bullets)
end
local pronuns = format_pronun_line(parsed)
local accent_prefix
if not parsed.of_several_accents then
accent_prefix = require(accent_qualifier_module).format_qualifiers(lang, parsed.accents) .. " "
else
accent_prefix = ""
accent_grouping_offset = 1
end
ins_line(accent_prefix .. pronuns, parsed.bullets + accent_grouping_offset)
if parsed.next_line_pronuns then
ins_line(format_pronuns(parsed.next_line_pronuns), parsed.bullets + accent_grouping_offset + 1)
end
if parsed.audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise indent audio by
-- one more bullet.
table.insert(textparts, format_audio(parsed.audio,
(#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1) + accent_grouping_offset))
end
if not all_rhyme_sets_eq and parsed.rhyme then
ins_line(format_rhyme(parsed.rhyme), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_syll_sets_eq and parsed.syll then
ins_line(format_syllabifications(parsed.syll), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_hmp_sets_eq and parsed.hmp then
ins_line(format_homophones(parsed.hmp), parsed.bullets + 1 + accent_grouping_offset)
end
end
if overall_audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
ins_line(format_rhyme(first_rhyme_ret), min_num_bullets)
end
if overall_rhyme then
ins_line(format_rhyme(overall_rhyme), min_num_bullets)
end
if all_syll_sets_eq and first_sylls then
ins_line(format_syllabifications(first_sylls), min_num_bullets)
end
if overall_syll then
ins_line(format_syllabifications(overall_syll), min_num_bullets)
end
if all_hmp_sets_eq and first_hmps then
ins_line(format_homophones(first_hmps), min_num_bullets)
end
if overall_hmp then
ins_line(format_homophones(overall_hmp), min_num_bullets)
end
return table.concat(textparts) ..
require("Module:utilities").format_categories(categories, lang, nil, nil, force_cat)
end
-- Meant to be called from a bot.
function export.pron_json(frame)
local iparams = {
[1] = {list = true, required = true},
["pagename"] = {required = true},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local data = {}
local syllabification_from_pagename = syllabify_from_spelling(iargs.pagename, iargs.pagename)
for _, respelling in ipairs(iargs[1]) do
local pronun = export.IPA(respelling, "include phonemic syllable boundaries")
local syllabification = export.syllabify_and_align(respelling, iargs.pagename)
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
table.insert(data, {
respelling = respelling,
phonemic = pronun.phonemic,
phonetic = pronun.phonetic,
syllabification = syllabification,
num_syl = num_syl,
rhyme = rhyme,
})
end
local retval = {
pagename = iargs.pagename,
syllabification_from_pagename = syllabification_from_pagename,
data = data,
}
return require("Module:JSON").toJSON(retval)
end
return export
2bzm76gw1xuo63ydlq43zl348b2jjze
Module:Pl-mid-noun
828
2364434
9909093
8033666
2026-09-29T07:27:35Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909093
Scribunto
text/plain
local export = {}
local m_links = require('Module:links')
local m_adj = require('Module:pl-adj')
local m_g = require('Module:gender and number')
local lang = require("Module:languages").getByCode("pl")
-- local consonants = "[bcćdfghjklłmnńpqrsśtvwxzżź]";
-- case information
local cases = {
{ key = "nom"; en = "主格"; pl = "mianownik (kto? co?)" },
{ key = "gen"; en = "屬格"; pl = "dopełniacz (kogo? czego?)" },
{ key = "dat"; en = "與格"; pl = "celownik (komu? czemu?)" },
{ key = "acc"; en = "賓格"; pl = "biernik (kogo? co?)" },
{ key = "ins"; en = "工具格"; pl = "narzędnik (kim? czym?)" },
{ key = "loc"; en = "方位格"; pl = "miejscownik (o kim? o czym?)" },
{ key = "voc"; en = "呼格"; pl = "wołacz (o!)" },
}
-- columns for normal nouns
local noun_cols = {
{ key = "s"; title = "單數" },
{ key = "p"; title = "複數" },
}
-- columns for Old Polish nouns
local noun_dual_cols = {
{ key = "s"; title = "單數" },
{ key = "d"; title = "雙數" },
{ key = "p"; title = "複數" },
}
-- columns for pronouns
local pronoun_cols = {
{ key = "sm"; title = m_g.format_genders({"m"}) },
{ key = "sf"; title = m_g.format_genders({"f"}) },
{ key = "sn"; title = m_g.format_genders({"n"}) },
{ key = "pm"; title = m_g.format_genders({"vr-p"}) },
{ key = "po"; title = m_g.format_genders({"nv-p"}) },
}
local altsep = "/"
function empty_item(text)
return (not text) or text == "" or text == "-" or text == "–" or text == "—"
end
-- add link markers, with links separated by any of splitchars
-- exported for use in testcases
-- normally the separator is altsep
function export.make_links(text, splitchars, title)
if not title then
title = mw.title.getCurrentTitle().fullText
end
if empty_item(text) then
return "—"
elseif not splitchars or splitchars == "" then
return ("[[%s#波蘭語|%s]]"):format(text, text)
else
items = {}
for word in mw.ustring.gmatch(text, "[^" .. splitchars .. "]+") do
add_archaic = ""
if word:sub(-string.len(" (archaic)")) == " (archaic)" then
word = (mw.text.split(word, "% %(archaic%)"))[1]
add_archaic = " (archaic)"
end
if word == title then
table.insert(items, ("[[%s]]"):format(word) .. add_archaic)
else
table.insert(items, ("[[%s#波蘭語|%s]]"):format(word, word) .. add_archaic)
end
end
if #items > 1 then
require("Module:debug").track("pl-noun/splitchars")
end
return table.concat(items, "/")
end
end
local function linkify_info(declinfo, splitchars, nolinks)
if nolinks then
require("Module:debug").track("pl-noun/nolinks")
end
local linked = {}
local title = mw.title.getCurrentTitle().fullText
for k, v in pairs(declinfo) do
if v == title then
linked[k] = "[[" .. v .. "]]"
elseif nolinks then
linked[k] = v
else
linked[k] = export.make_links(v, splitchars, title)
end
end
return linked
end
local function nowiki_info(declinfo)
require("Module:debug").track("pl-noun/nowiki")
local nowikied = {}
for k, v in pairs(declinfo) do
nowikied[k] = mw.text.nowiki(v)
end
return nowikied
end
local function override_col_titles(heads, cols)
if not heads then
return cols or {}
end
local new_cols = {}
local index = 1
for word in mw.ustring.gmatch(heads, "[^,]+") do
if cols[index] then
table.insert(new_cols, { key = cols[index].key; title = word } )
else
table.insert(new_cols, { key = tostring(index); title = word } )
end
index = index + 1
end
for ci, col in ipairs(cols) do
if ci >= index then
table.insert(new_cols, cols[index])
end
end
return new_cols
end
local function guess_width(declinfo, cols)
local maxl = 0
for k, v in pairs(declinfo) do
local l = mw.ustring.len(mw.text.trim(v))
if maxl < l then
maxl = l
end
end
local width = math.floor(maxl * 0.78) -- number obtained by anecdotal evidence
width = (width < 10) and 10 or width
width = 9 + (width * #cols)
return width
end
-- generate the HTML code of an inflection table
-- each entry in heads must have "key" and "title"
local function make_table(declinfo, cols, preproc, width, title, nolinks, rnoms)
local result = {}
if not cols or not cols[1] then
error("make_table: invalid cols parameter")
end
local lemma_key = cases[1].key .. cols[1].key
local lemma = m_links.remove_links(declinfo[lemma_key])
if rnoms then
lemma = '*' .. m_links.remove_links(rnoms)
end
title = title or ('<span class="Latn mention" lang="pl" xml:lang="pl">%s</span> 有紀錄的形式'):format(lemma)
local emwidth = width or guess_width(declinfo, cols)
if preproc == "nowiki" then
declinfo = nowiki_info(declinfo)
elseif preproc == "linkify" then
declinfo = linkify_info(declinfo, altsep, nolinks)
end
if cols and (#cols > 0) then
table.insert(result, '|-\n! style="background:#d9ebff; width: 8em;" |\n')
for i, col in ipairs(cols) do
table.insert(result, ('! style="background:#d9ebff;" scope="col" | %s\n'):format(col.title))
end
end
local maxl = 0
for i, case in ipairs(cases) do
table.insert(result, ('|-\n! title="%s" style="background:#eff7ff;" scope="row" | %s\n'):format(case.pl, case.en))
for _, col in ipairs(cols) do
local declkey = case.key .. col.key
local item = mw.text.trim(declinfo[declkey])
if empty_item(item) then
table.insert(result, '| —\n')
else
table.insert(result, ('| <span class="Latn" lang="pl" xml:lang="pl">%s</span>\n'):format(item))
end
end
end
local outtext = ([=[<div class="NavFrame" style="display: block;width: %uem;">
<div class="NavHead" style="background:#eff7ff" >%s</div>
<div class="NavContent">
{| style="background:#F9F9F9; text-align:center; width: %uem; margin: 0;" class="inflection-table"
]=]):format(emwidth, title, emwidth) .. table.concat(result, "") .. "|}</div></div>"
return outtext
end
local function get_mode()
local frame = mw.getCurrentFrame()
if mw.isSubsting() then
return 'subst'
elseif frame:getParent():getTitle() == mw.title.getCurrentTitle().fullText then
return 'demo'
else
return 'xclude'
end
end
local function make_table_from_pargs(pargs, cols)
local width = pargs.width and tonumber(pargs.width)
local declinfo = {}
cols = override_col_titles(pargs.heads, cols or {})
for i = 1, 7 do
for j, col in ipairs(cols) do
local case_key = cases[i].key .. col.key
local argn = ((i-1) * #cols) + j
declinfo[case_key] = m_links.remove_links(mw.text.trim(pargs[case_key] or pargs[argn] or "-"))
end
end
return make_table(declinfo, cols, "linkify", pargs.width, pargs.title, pargs.nolinks, pargs['rnoms'])
end
-- Generate declension table for a regular noun with all forms passed explicitly as parameters
function export.template_decl_noun(frame)
local pargs = frame:getParent().args
return make_table_from_pargs(pargs, noun_cols)
end
return export
jbpiy8jhywk0tw6j1kym47r8i61xs0s
Cienciała
0
2369355
9909137
8039355
2026-09-29T08:41:46Z
TongcyDai
53191
TongcyDai移动页面[[Cieńciała]]至[[Cienciała]]
8039355
wikitext
text/x-wiki
==波蘭語==
===發音===
{{pl-p}}
===專有名詞===
{{pl-proper noun|m-pr}}
# {{surname|pl|g=m}}
====變格====
{{pl-decl-noun
|Cieńciała|Cieńciałowie
|Cieńciały|Cieńciałów
|Cieńciale|Cieńciałom
|Cieńciałę|Cieńciałów
|Cieńciałą|Cieńciałami
|Cieńciale|Cieńciałach
|Cieńciało|Cieńciałowie
}}
===專有名詞===
{{pl-proper noun|f}}
# {{surname|pl|g=f}}
====變格====
{{pl-decl-noun-f}}
py763l2rw0t6x7c3sjb72peoriqkcia
9909139
9909137
2026-09-29T08:42:02Z
TongcyDai
53191
9909139
wikitext
text/x-wiki
==波蘭語==
===其他形式===
* {{alt|pl|Cięciała}}
===發音===
{{pl-pr}}
===專有名詞===
{{pl-proper noun|m-pr}}
# {{surname|pl|g=m}}
====變格====
{{pl-decl-noun-f|nomp=Cienciałowie|genp=Cienciałów|accp=Cienciałów}}
===專有名詞===
{{pl-proper noun|f|marr=Ciencialina|unmarr=Ciencialanka}}
# {{surname|pl|g=f}}
====變格====
{{pl-decl-noun-f}}
===延伸閱讀===
* {{R:pl:ISNP}}
omocs66jhygv129h4m3b089h8nbi592
Module:Hsb-dsb-headword
828
2372111
9909076
9706232
2026-09-29T07:26:31Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909076
Scribunto
text/plain
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local rfind = mw.ustring.find
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local m_headword_utilities = require_when_needed(headword_utilities_module)
local m_string_utilities = require_when_needed("Module:string utilities")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local insert = table.insert
local concat = table.concat
local langs_supported = {
["hsb"] = {
peri_comp = "bóle",
sup = "naj",
abs_sup_prefix = "na",
excessive = "pře",
},
["dsb"] = {
peri_comp = "wěcej",
sup = "nej",
},
}
-- Table of all valid aspects.
local valid_aspects = m_table.listToSet {
"impf", "pf", "biasp", "?",
}
local valid_verb_types = {
semelfactive = true,
durative = true,
frequentative = true,
continuative = true,
unidirectional = true,
multidirectional = true,
concrete = "unidirectional",
abstract = "multidirectional",
}
local function track(page)
require("Module:debug").track("hsb-dsb-headword/" .. page)
return true
end
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture
-- replace expression.
lemma = m_string_utilities.replacement_escape(lemma)
return (term:gsub("#", lemma)) -- discard second retval
end
local function frob_term_with_hash(term, lemma)
if term:find("#") then
term = replace_hash_with_lemma(term, lemma)
end
return term
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;
-- sections enclosed in <<...>> are linked to the glossary. `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel, frob)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
include_mods = {"t"},
accel = accel and {form = accel} or nil,
frob = function(term)
term = frob_term_with_hash(term, data.pagename)
if frob then
term = frob(term)
end
return term
end,
}
end
-- 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(data, paramname, forms)
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = paramname,
forms = forms,
splitchar = ",",
include_mods = {"t"},
frob = function(term)
return frob_term_with_hash(term, data.pagename)
end,
}
end
-- Insert the parsed inflections in `infls` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = {required = true},
["lang"] = {required = true},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local poscat = iargs[1]
langcode = iargs.lang
if langcode ~= "hsb" and langcode ~= "dsb" then
error("This module currently only works for lang=hsb and lang=dsb")
end
local lang = require("Module:languages").getByCode(langcode)
local langname = lang:getCanonicalName()
local params = {
["head"] = {list = true},
["nolink"] = {type = "boolean"},
["nolinkhead"] = {type = "boolean", alias_of = "nolink"},
["id"] = {},
["sort"] = {},
["suffix"] = {type = "boolean"},
["nosuffix"] = {type = "boolean"},
["json"] = {type = "boolean"},
["pagename"] = {}, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolink then
if #heads == 0 then
heads = {pagename}
end
end
local data = {
lang = lang,
langname = langname,
langcode = langcode,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
categories = {},
pagename = pagename,
id = args.id,
sort_key = args.sort,
force_cat_output = force_cat,
}
data.is_suffix = false
if args.suffix or (
not args.nosuffix and pagename:find("^%-") and poscat ~= "suffixes" and poscat ~= "suffix forms"
) then
data.is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = require(en_utilities_module).singularize(poscat)
insert(data.categories, langname .. " " .. singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
local function get_noun_pos(is_proper)
local noun_inflection_specs = {
{"gen", "屬格單數"},
{"du", "主格雙數"},
{"pl", "主格複數"},
{"genpl", "屬格複數"},
{"m", "陽性等價詞"},
{"f", "陰性等價詞"},
{"aug", "指大"},
{"adj", "相關形容詞"},
{"poss", "<<所有格>><<形容詞>>"},
{"dim", "指小"},
{"pej", "貶義"},
{"dem", "<<居民稱謂詞>>"},
{"fdem", "女性<<居民稱謂詞>>"},
}
local params = {
[1] = {type = "genders", required = true, template_default = "?"},
["g"] = {list = true, disallow_holes = true, replaced_by = false,
instead = "use multiple comma-separated genders in |1="},
["indecl"] = {type = "boolean"},
}
for _, spec in ipairs(noun_inflection_specs) do
local param, desc = unpack(spec)
params[param] = list_param
end
return {
params = params,
func = function(args, data)
-- Compute allowed genders, and map incomplete genders to specs with a "?" in them.
local masc_genders = {"m", "mf", "mfbysense"}
local non_masc_genders = {false, "f", "n"}
local hsb_masc_animacies = {false, "in", "anml", "pr"}
local dsb_masc_animacies = {false, "in", "an"}
local numbers = {false, "d", "p"}
local allowed_genders = {}
local function insert_genders(genders, animacies, numbers, is_masc)
for _, g in ipairs(genders) do
for _, an in ipairs(animacies) do
for _, num in ipairs(numbers) do
local source_gender_parts = {}
local dest_gender_parts = {}
local function ins_part(part, partname)
if part then
insert(source_gender_parts, part)
insert(dest_gender_parts, part)
elseif partname == "g" and num == false or partname == "an" and is_masc then
-- allow incomplete gender duale tantum and plurale tantum nouns; also allow
-- incomplete animacy for fem/neut; otherwise insert a ? to indicate incomplete
-- gender spec
insert(dest_gender_parts, "?")
end
end
ins_part(g, "g")
ins_part(an, "an")
ins_part(num, "num")
if #source_gender_parts == 0 then
allowed_genders["?"] = "?"
else
allowed_genders[concat(source_gender_parts, "-")] =
concat(dest_gender_parts, "-")
end
end
end
end
end
if data.lang:getCode() == "hsb" then
insert_genders(masc_genders, hsb_masc_animacies, numbers, true)
else
insert_genders(masc_genders, dsb_masc_animacies, numbers, true)
end
insert_genders(non_masc_genders, {false}, numbers, false)
-- allow inanimate dual/plural; no gender distinction here
allowed_genders["in-d"] = "in-d"
allowed_genders["in-p"] = "in-p"
-- Validate and canonicalize genders.
for _, gspec in ipairs(args[1]) do
local g = gspec.spec
local canon_g = allowed_genders[g]
if canon_g then
g = canon_g
else
error("Unrecognized " .. data.langname .. " gender: " .. g)
end
gspec.spec = g
end
data.genders = args[1]
local function process_inflection(label, infls, qs, frob)
infls.label = label
for i, infl in ipairs(infls) do
if frob then
infl = frob(infl)
end
if qs[i] then
infls[i] = {term = infl, q = {qs[i]}}
end
end
if #infls > 0 then
insert(data.inflections, infls)
end
end
if args.indecl then
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, data.langname .. "無屈折名詞")
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, which is linked to the glossary if preceded by * (which is removed).
local function handle_infl(field, label)
parse_and_insert_inflection(data, args, field, label)
end
-- Process all inflections.
for _, spec in ipairs(noun_inflection_specs) do
local param, desc = unpack(spec)
handle_infl(param, desc)
end
end
}
end
pos_functions["名詞"] = get_noun_pos(false)
pos_functions["專有名詞"] = get_noun_pos(true)
pos_functions["動詞"] = {
params = {
[1] = {type = "genders", default = "?"},
["type"] = {set = valid_verb_types},
["pf"] = list_param,
["impf"] = list_param,
},
func = function(args, data)
-- Validate genders.
for _, gspec in ipairs(args[1]) do
local g = gspec.spec
if not valid_aspects[g] then
error("Unrecognized " .. data.langname .. " aspect: " .. g)
end
end
data.genders = args[1]
if args.type then
m_headword_utilities.insert_fixed_inflection {
headdata = data,
label = ("<<%s>>"):format(args.type),
}
insert(data.categories, ("%s %s verbs"):format(data.langname, args.type))
end
parse_and_insert_inflection(data, args, "pf", "完整體")
parse_and_insert_inflection(data, args, "impf", "非完整體")
end,
}
local function get_adj_adv_pos(pos)
local params = {
[1] = list_param,
["dim"] = list_param,
["sup"] = list_param,
["nodefsup"] = boolean_param,
}
if pos == "形容詞" then
params["adv"] = list_param
params["indecl"] = boolean_param
end
return {
params = params,
func = function(args, data)
if pos == "形容詞" and args.indecl then
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, data.langname .. "無屈折形容詞")
end
local default_sups = {}
local default_abs_sups = {}
local comps = parse_inflection(data, 1, args[1])
local is_comparable = false
local lang_data = langs_supported[data.langcode]
if comps[1] then
if comps[1].term == "-" then
if not comps[2] then
m_headword_utilities.insert_fixed_inflection {
headdata = data,
label = "無<<比較級>>",
originating_term = comps[1],
}
insert(data.categories, data.langname .. "無比較級" .. data.pos_category)
else
m_headword_utilities.insert_fixed_inflection {
headdata = data,
label = "通常無<<比較級>>",
originating_term = comps[1],
}
end
table.remove(comps, 1)
end
for i, comp in ipairs(comps) do
if not is_comparable then
is_comparable = true
insert(data.categories, data.langname .. "有比較級" .. data.pos_category)
end
if comp.term == "peri" then
if not lang_data.peri_comp then
error("Don't know how to form periphrastic comparatives for " .. data.langname)
end
comp.term = ("[[%s]] [[%s]]"):format(lang_data.peri_comp, data.pagename)
if lang_data.sup then
local default_sup = m_table.shallowCopy(comp)
default_sup.term = ("[[%s%s]] [[%s]]"):format(lang_data.sup, lang_data.peri_comp,
data.pagename)
insert(default_sups, default_sup)
if lang_data.abs_sup_prefix then
local default_abs_sup = m_table.shallowCopy(comp)
default_abs_sup.term = ("[[%s%s%s]] [[%s]]"):format(lang_data.abs_sup_prefix,
lang_data.sup, lang_data.peri_comp, data.pagename)
insert(default_abs_sups, default_abs_sup)
end
end
else
if lang_data.sup then
local default_sup = m_table.shallowCopy(comp)
default_sup.term = ("%s%s"):format(lang_data.sup, comp.term)
insert(default_sups, default_sup)
if lang_data.abs_sup_prefix then
local default_abs_sup = m_table.shallowCopy(comp)
default_abs_sup.term = ("%s%s%s"):format(lang_data.abs_sup_prefix, lang_data.sup,
comp.term)
insert(default_abs_sups, default_abs_sup)
end
end
end
end
end
insert_inflection(data, comps, "比較級", "比較級")
local sups = parse_inflection(data, "sup", args.sup)
if not sups[1] then
sups = args.nodefsup and {} or {{term = "+"}}
end
local combined_sups = {}
for _, sup in ipairs(sups) do
if sup.term == "+" then
for _, def_sup in ipairs(default_sups) do
def_sup = m_table.shallowCopy(def_sup)
m_headword_utilities.combine_termobj_decorations(def_sup, sup)
insert(combined_sups, def_sup)
end
else
insert(combined_sups, sup)
end
end
-- Compute the absolute superlative before inserting any inflections because the process of inserting
-- inflections may modify the superlatives that we base the absolute superlatives off of.
local combined_abs_sups = {}
if lang_data.abs_sup_prefix then
for _, sup in ipairs(sups) do
if sup.term == "+" then
for _, def_abs_sup in ipairs(default_abs_sups) do
def_abs_sup = m_table.shallowCopy(def_abs_sup)
m_headword_utilities.combine_termobj_decorations(def_abs_sup, sup)
insert(combined_abs_sups, def_abs_sup)
end
else
sup = m_table.shallowCopy(sup)
if sup.term:find("%[%[") then
sup.term = "[[" .. lang_data.abs_sup_prefix .. sup.term:sub(3)
else
sup.term = lang_data.abs_sup_prefix .. sup.term
end
insert(combined_abs_sups, sup)
end
end
end
insert_inflection(data, combined_sups, "最高級", "最高級")
insert_inflection(data, combined_abs_sups, "<<絕對最高級>>", "絕對最高級")
if is_comparable and lang_data.excessive then
insert_inflection(data, {{term = lang_data.excessive .. data.pagename}}, "<<excessive>>", "excessive")
end
if pos == "形容詞" then
parse_and_insert_inflection(data, args, "adv", "派生副詞")
end
parse_and_insert_inflection(data, args, "dim", "指小")
end,
}
end
pos_functions["形容詞"] = get_adj_adv_pos("形容詞")
pos_functions["副詞"] = get_adj_adv_pos("副詞")
return export
9byzzj5wc81pqs6g02lncbdudletz39
borneański
0
2377923
9909140
8049625
2026-09-29T08:42:39Z
TongcyDai
53191
9909140
wikitext
text/x-wiki
==波蘭語==
===詞源===
{{af|pl|Borneo|-ski}}
===發音===
{{pl-pr|a=LL-Q809 (pol)-Poemat-borneański.wav}}
===形容詞===
{{pl-adj|-|adv=-}}
# {{lb|pl|relational}} [[婆羅洲]]的
====變格====
{{pl-decl-adj-auto}}
====相關詞彙====
{{col-auto|pl|title=名詞|Borneańczyk|Borneanka}}
===拓展閱讀===
* {{R:pl:PWN}}
i8auhq2aarjdimqx6n0duwnilyfjr4s
Bugdoll
0
2380365
9909135
8052549
2026-09-29T08:41:04Z
TongcyDai
53191
9909135
wikitext
text/x-wiki
==德語==
{{wp|de:}}
===其他形式===
* {{alter|de|Bogdal|Bogdol|Bogdoll|Bugdahl|Bugdal|Bugdol}}
===詞源===
源自{{der|de|szl|-}} or {{der|de|pl|Bugdoł}}、{{m|pl|Bógdoł}}。
===專有名詞===
{{de-proper noun|surname}}
# {{surname|de|from=波蘭語,西里西亞語}}
67qzpgj5ydddjfkt6xkbcyn000w1n4f
9909136
9909135
2026-09-29T08:41:14Z
TongcyDai
53191
9909136
wikitext
text/x-wiki
==德語==
{{wp|de:}}
===其他形式===
* {{alter|de|Bogdal|Bogdol|Bogdoll|Bugdahl|Bugdal|Bugdol}}
===詞源===
源自{{der|de|szl|-}} 或{{der|de|pl|Bugdoł}}、{{m|pl|Bógdoł}}。
===專有名詞===
{{de-proper noun|surname}}
# {{surname|de|from=波蘭語,西里西亞語}}
itpik5p59omdxn9ulrbiwwdxm8bbmo5
Module:Zlw-lch-headword
828
2431548
9909110
9847189
2026-09-29T07:28:37Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909110
Scribunto
text/plain
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local m_string_utilities = require_when_needed("Module:string utilities")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local boolean_param = {type = "boolean"}
local list_param = {list = true, disallow_holes = true}
local concat = table.concat
local insert = table.insert
local remove = table.remove
local rfind = mw.ustring.find
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local langs_supported = {
["pl"] = {
peri_comp = "bardziej",
sup = "naj",
-- participle endings
act = {"ąc[yae]$"}, -- biegnący
pass = {"[ntł][yae]$"}, -- otwarty, uwielbiany, legły
cont_adv = {"ąc$"},
ant_adv = {"szy$"},
},
["csb"] = {
peri_comp = "barżi",
sup = "nô",
-- participle endings
act = {"ący$"},
pass = {"[ao]ny$", "ty$", "łi$"},
cont_adv = {"ōnc$"},
ant_adv = {"[wł]szë$"},
},
["szl"] = {
peri_comp = "bardzij",
sup = "noj",
-- participle endings
act = {"ōncy$"},
pass = {"[aō]ny$", "[tł]y$"},
cont_adv = {"ąc$"},
ant_adv = false,
},
["zlw-opl"] = {
peri_comp = "barziej",
sup = false,
-- participle endings
act = {"ąc[yae]$"}, -- biegnący
pass = {"[ntł][yae]$"}, -- otwarty, uwielbiany, legły
cont_adv = {"ąc$"},
ant_adv = {"szy$"},
},
["pox"] = {
peri_comp = false,
sup = false,
-- participle endings
act = false,
pass = false,
cont_adv = false,
ant_adv = false,
has_dual = true,
},
["zlw-slv"] = {
peri_comp = "barżé",
sup = "no",
-- participle endings
act = false,
pass = false,
cont_adv = false,
ant_adv = false,
has_dual = true,
},
}
----------------------------------------------- Utilities --------------------------------------------
local function track(page)
require("Module:debug").track("zlw-lch-headword/" .. page)
return true
end
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture
-- replace expression.
lemma = m_string_utilities.replacement_escape(lemma)
return (term:gsub("#", lemma)) -- discard second retval
end
local function frob_term_with_hash(term, lemma)
if term:find("#") then
term = replace_hash_with_lemma(term, lemma)
end
return term
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 = ",",
include_mods = {"g"},
frob = function(term)
return frob_term_with_hash(term, data.pagename)
end,
label = label,
accel = accel and {form = accel} or nil,
}
end
-- 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(data, args, field)
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
splitchar = ",",
include_mods = {"g"},
frob = function(term)
return frob_term_with_hash(term, data.pagename)
end,
}
end
-- Insert the parsed inflections in `infls` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
----------------------------------------------- Main entry point --------------------------------------------
function export.show(frame)
local iparams = {
[1] = {required = true},
["lang"] = {required = true},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local poscat = iargs[1]
langcode = iargs.lang
if not langs_supported[langcode] then
local langcodes_supported = {}
for lang, _ in pairs(langs_supported) do
insert(langcodes_supported, lang)
end
error("This module currently only works for lang=" .. concat(langcodes_supported, "/"))
end
local lang = require("Module:languages").getByCode(langcode)
local langname = lang:getCanonicalName()
local params = {
["head"] = {list = true},
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["json"] = boolean_param,
["abbr"] = {list = true},
["pagename"] = {}, -- for testing
}
if pos_functions[poscat] then
local posparams = pos_functions[poscat].params
if type(posparams) == "function" then
posparams = posparams(langcode)
end
for key, val in pairs(posparams) do
params[key] = val
end
end
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolink then
if #heads == 0 then
heads = {pagename}
end
end
local data = {
lang = lang,
langcode = langcode,
langname = langname,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = not user_specified_heads[1],
genders = {},
inflections = {},
categories = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
data.is_suffix = false
if args.suffix or (
not args.nosuffix and pagename:find("^%-") and poscat ~= "後綴" and poscat ~= "後綴變格形"
) then
data.is_suffix = true
data.pos_category = "後綴"
local singular_poscat = poscat
insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
parse_and_insert_inflection(data, args, "abbr", "縮寫")
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
----------------------------------------------- Nouns --------------------------------------------
local function get_noun_inflection_specs(langcode)
local noun_inflection_specs = {
{"gen", "屬格單數"},
}
if langs_supported[langcode].has_dual then
insert(noun_inflection_specs, {"du", "主格雙數"})
end
for _, spec in ipairs {
{"pl", "主格複數"},
{"genpl", "屬格複數"},
{"f", "陰性等價詞"},
{"m", "陽性等價詞"},
{"n", "中性等價詞"},
{"marr", "傳統已婚形式"},
{"unmarr", "傳統未婚形式"},
{"dim", "指小詞"},
{"pej", "貶義詞"},
{"aug", "指大詞"},
{"adj", "相關形容詞"},
{"poss", "所有格形容詞"},
{"dem", "居民稱謂詞"},
{"fdem", "女性居民稱謂詞"},
} do
insert(noun_inflection_specs, spec)
end
return noun_inflection_specs
end
local function get_noun_pos(is_proper)
return {
params = function(langcode)
local params = {
["indecl"] = boolean_param,
[1] = {list = "g", disallow_holes = true, type = "genders", flatten = true}, -- gender(s)
["gerund"] = boolean_param,
}
for _, spec in ipairs(get_noun_inflection_specs(langcode)) do
local param, desc = unpack(spec)
params[param] = list_param
end
params["rel"] = {list = true, alias_of = "adj"}
return params
end,
func = function(args, data)
-- Compute allowed genders, and map incomplete genders to specs with a "?" in them.
local genders = {false, "m", "mf", "mfbysense", "f", "n", "g!"}
local animacies = {false, "in", "anml", "pr", "an!"}
local numbers = {false, "p", "num!"}
local allowed_genders = {}
for _, g in ipairs(genders) do
for _, an in ipairs(animacies) do
for _, num in ipairs(numbers) do
local source_gender_parts = {}
local dest_gender_parts = {}
local function ins_part(part, partname)
if part then
insert(source_gender_parts, part)
insert(dest_gender_parts, part)
elseif partname == "g" and num == false or
partname == "an" and g ~= "f" and g ~= "n" then
-- allow incomplete gender plurale tantum nouns; also allow incomplete
-- animacy for fem/neut, where it makes no difference for agreement
-- purposes; otherwise insert a ? to indicate incomplete gender spec
insert(dest_gender_parts, "?")
end
end
ins_part(g, "g")
ins_part(an, "an")
ins_part(num, "num")
if #source_gender_parts == 0 then
allowed_genders["?"] = "?"
else
allowed_genders[concat(source_gender_parts, "-")] =
concat(dest_gender_parts, "-")
end
-- "Virile" = masculine personal, allow in the plural and convert appropriately;
-- "Nonvirile" = anything but masculine personal, allow in the plural;
-- "Nonpersonal" = anything but personal, i.e. animal or inanimate; allow in the plural.
allowed_genders["vr-p"] = "m-pr-p"
allowed_genders["nv-p"] = "nv-p"
allowed_genders["np-p"] = "np-p"
end
end
end
-- Validate and canonicalize genders.
for _, g in ipairs(args[1]) do
if not allowed_genders[g.spec] then
error("Unrecognized " .. data.langname .. " gender: " .. g.spec)
else
g.spec = allowed_genders[g.spec]
end
end
data.genders = args[1]
if args.indecl then
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, data.langname .. "無屈折名詞")
end
if args.gerund then
data.pos_category = "動名詞"
end
-- Process all inflections.
for _, spec in ipairs(get_noun_inflection_specs(data.langcode)) do
local param, desc = unpack(spec)
parse_and_insert_inflection(data, args, param, desc)
end
end
}
end
pos_functions["名詞"] = get_noun_pos(false)
pos_functions["專有名詞"] = get_noun_pos(true)
----------------------------------------------- Verbs --------------------------------------------
local function get_verb_pos()
local verb_inflection_specs = {
-- order per old [[Module:pl-headword]]
{"det", "非完整體定向"},
{"pf", "完整體"},
{"impf", "非完整體"},
{"indet", "不定向"},
{"freq", "反覆體"},
}
local params = {
[1] = {default = "?"},
["def"] = boolean_param,
}
for _, spec in ipairs(verb_inflection_specs) do
local param, desc = unpack(spec)
params[param] = list_param
end
return {
params = params,
func = function(args, data)
local allowed_aspects = m_table.listToSet {
"pf", "impf", "biasp", "both", "impf-det", "impf-indet", "impf-freq", "?"
}
local impf_allowed = true
local pf_allowed = true
local indet_allowed = true
local det_allowed = true
local freq_allowed = true
local function insert_label_and_cat(aspect, label)
-- Preserve decorations.
aspect = aspect and m_table.shallowCopy(aspect) or {}
aspect.term = nil
aspect.label = label
insert(data.inflections, aspect)
insert(data.categories, data.langname .. label .. "動詞")
end
local aspects = m_headword_utilities.parse_term_with_modifiers {
paramname = 1,
val = args[1],
splitchar = ",",
exclude_mods = {"id"}, -- doesn't make sense for gender specs
}
-- Validate and canonicalize aspects.
for i, aspect in ipairs(aspects) do
local a = aspect.term
if not allowed_aspects[a] then
error("Unrecognized " .. data.langname .. " aspect: " .. a)
elseif a == "both" then
a = "biasp"
elseif a == "impf-det" then
a = "impf"
insert_label_and_cat(aspect, "定向")
det_allowed = false
elseif a == "impf-indet" then
a = "impf"
insert_label_and_cat(aspect, "不定向")
indet_allowed = false
elseif a == "impf-freq" then
a = "impf"
insert_label_and_cat(aspect, "不定向")
insert_label_and_cat(aspect, "反覆體")
indet_allowed = false
freq_allowed = false
elseif a == "pf" then
pf_allowed = false
elseif a == "impf" then
impf_allowed = false
end
aspect.spec = a
aspect.term = nil
end
data.genders = aspects
if args.def then
insert_label_and_cat(nil, "殘缺")
end
-- Process all inflections.
for _, spec in ipairs(verb_inflection_specs) do
local param, desc = unpack(spec)
local infls = parse_inflection(data, args, param)
if infls[1] then
if param == "pf" and not pf_allowed then
error("Aspectual-pair perfectives not allowed with perfective-only verb")
end
if param == "impf" and not impf_allowed then
error("Aspectual-pair imperfectives not allowed with imperfective-only verb")
end
if param == "det" and not det_allowed then
error("Aspectual-pair determinates not allowed with imperfective-only determinate verb")
end
if param == "indet" and not indet_allowed then
error("Aspectual-pair indeterminates not allowed with imperfective-only indeterminate or frequentative verb")
end
if param == "freq" and not freq_allowed then
error("Aspectual-pair frequentatives not allowed with imperfective-only frequentative verb")
end
insert_inflection(data, infls, desc)
end
end
end
}
end
pos_functions["動詞"] = get_verb_pos()
----------------------------------------------- Adjectives, Adverbs --------------------------------------------
local function get_adj_adv_pos(pos)
return {
params = function(langcode)
local params = {
[1] = list_param,
["dim"] = list_param,
["sup"] = list_param,
["nodefsup"] = boolean_param,
}
if pos == "形容詞" then
params["adv"] = list_param
params["indecl"] = boolean_param
end
if langcode == "pl" then
params["mpcomp"] = list_param
params["mpsup"] = list_param
end
return params
end,
func = function(args, data)
local default_sups = {}
local comps = parse_inflection(data, args, 1)
if comps[1] then
local lang_data = langs_supported[data.langcode]
if comps[1].term == "-" then
if not comps[2] then
-- Preserve any decorations.
comps[1].label = "無" .. glossary_link("比較級")
comps[1].term = nil
insert(data.inflections, comps[1])
insert(data.categories, data.langname .. "無比較級" .. data.pos_category)
else
-- Preserve any decorations.
comps[1].label = "不總是有" .. glossary_link("比較級")
comps[1].term = nil
insert(data.inflections, comps[1])
end
remove(comps, 1)
end
for i, comp in ipairs(comps) do
if comp.term == "peri" then
if not lang_data.peri_comp then
error("Don't know how to form periphrastic comparatives for " .. data.langname)
end
comp.term = ("[[%s]] [[%s]]"):format(lang_data.peri_comp, data.pagename)
if lang_data.sup then
local default_sup = m_table.shallowCopy(comp)
default_sup.term = ("[[%s%s]] [[%s]]"):format(lang_data.sup, lang_data.peri_comp,
data.pagename)
insert(default_sups, default_sup)
end
elseif lang_data.sup then
local default_sup = m_table.shallowCopy(comp)
default_sup.term = ("%s%s"):format(lang_data.sup, comp.term)
insert(default_sups, default_sup)
end
end
end
insert_inflection(data, comps, "比較級", "比較級")
local sups = parse_inflection(data, args, "sup")
if not sups[1] then
sups = args.nodefsup and {} or {{term = "+"}}
end
local combined_sups = {}
for _, sup in ipairs(sups) do
if sup.term == "+" then
for _, def_sup in ipairs(default_sups) do
def_sup = m_table.shallowCopy(def_sup)
m_headword_utilities.combine_termobj_decorations(def_sup, sup)
insert(combined_sups, def_sup)
end
else
insert(combined_sups, sup)
end
end
insert_inflection(data, combined_sups, "最高級", "最高級")
if data.langcode == "pl" then
parse_and_insert_inflection(data, args, "mpcomp", "中古波蘭語比較級")
parse_and_insert_inflection(data, args, "mpsup", "中古波蘭語最高級")
end
if pos == "形容詞" then
if args.indecl then
insert(data.inflections, {label = glossary_link("無屈折")})
insert(data.categories, data.langname .. "無屈折形容詞")
end
parse_and_insert_inflection(data, args, "adv", "派生副詞")
end
parse_and_insert_inflection(data, args, "dim", "指小")
end,
}
end
pos_functions["形容詞"] = get_adj_adv_pos("形容詞")
pos_functions["副詞"] = get_adj_adv_pos("副詞")
----------------------------------------------- Participles --------------------------------------------
local function get_part_pos()
local params = {
[1] = {},
["a"] = list_param,
}
return {
params = params,
func = function(args, data)
if data.langcode ~= "pl" then
error("Internal error: Unable to handle languages other than Polish for participles: " .. data.langname)
end
-- Compute allowed aspects, and map incomplete aspects to specs with a "?" in them.
local allowed_aspects = m_table.listToSet {
"pf", "impf", "biasp", "both", "pf-it", "pf-sem", "impf-it", "impf-dur", "?"
}
local allowed_types = m_table.listToSet {
"pass", "act", "ant-adv", "cont-adv", "?"
}
local function insert_label_and_cat(aspect, label, nolink)
local cat_label = label
if not nolink and label then
label = glossary_link(label)
end
-- Preserve decorations.
aspect = aspect and m_table.shallowCopy(aspect) or {}
aspect.term = nil
aspect.label = label
insert(data.inflections, aspect)
if cat_label then
insert(data.categories, data.langname .. cat_label .. "分詞")
end
end
local aspects = m_headword_utilities.parse_term_list_with_modifiers {
paramname = "a",
forms = args.a,
splitchar = ",",
exclude_mods = {"id"}, -- doesn't make sense for gender specs
}
-- Validate and canonicalize aspects.
for i, aspect in ipairs(aspects) do
local a = aspect.term
if not allowed_aspects[a] then
error("Unrecognized " .. data.langname .. " participle aspect: " .. a)
elseif a == "both" then
a = "biasp"
elseif a == "impf-it" then
a = "impf"
insert_label_and_cat(aspect, "反覆體")
elseif a == "impf-dur" then
a = "impf"
insert_label_and_cat(aspect, "持續體")
elseif a == "pf-it" then
a = "pf"
insert_label_and_cat(aspect, "反覆體")
elseif a == "pf-sem" then
a = "pf"
insert_label_and_cat(aspect, "瞬息體")
end
aspect.spec = a
aspect.term = nil
end
data.genders = aspects
-- Validate or autodetect participle type.
local function matches_parttype(typ)
local endings = langs_supported[data.langcode][typ]
if not endings then
return false
end
for _, ending in ipairs(endings) do
if rfind(data.pagename, ending) then
return true
end
end
return false
end
local ptype = args[1]
if ptype then
if not allowed_types[ptype] then
error("Unrecognized " .. data.langname .. " participle type: " .. ptype)
end
elseif matches_parttype("act") then -- biegnący
ptype = "act"
elseif matches_parttype("pass") then -- otwarty, uwielbiany, legły
ptype = "pass"
elseif matches_parttype("cont_adv") then
ptype = "cont-adv"
elseif matches_parttype("ant_adv") then
ptype = "ant-adv"
elseif (data.pagename:find("%-participle$") or data.pagename:find("%-part$")) and
mw.title.getCurrentTitle().nsText == "Template" then
ptype = "pass"
else
error(("Missing %s participle type and can't infer from pagename '%s'"):format(data.langname,
data.pagename))
end
if ptype == "act" then
insert_label_and_cat(nil, "主動形容詞性", true)
elseif ptype == "pass" then
insert_label_and_cat(nil, "被動形容詞性", true)
elseif ptype == "cont-adv" then
insert_label_and_cat(nil, "同時副詞性", true)
elseif ptype == "ant-adv" then
insert_label_and_cat(nil, "先時副詞性", true)
end
end
}
end
pos_functions["分詞"] = get_part_pos()
return export
npu9m1qfg7zhhte0phb1yjvexe2eh9p
Module:Gl-pron
828
2465353
9909068
9023382
2026-09-29T07:26:02Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909068
Scribunto
text/plain
--Based on [[Module:es-pronunc]] by Benwing2
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local decorations_module = "Module:decorations"
local put_module = "Module:parse utilities"
local force_cat = false
local lang = require("Module:languages").getByCode("gl")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ^
local CARO = u(0x030C) -- caron
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local DOTA = u(0x0307) -- dot above
local DOTB = u(0x0323) --dot below
local BREVE = u(0x0306) --breve
local vowel = "aeiouüɛɔɐɪʊɑAEIOUÜ" -- vowel
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR .. CARO
local accent_c = "[" .. accent .. "]"
local stress = AC
local stress_c = "[" .. AC .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class
local unstressed_words = m_table.listToSet({ --all words in this list containing non-final <e,o> should be listed later (look for "handle vowels"). FIXME add more unstressed words
"a", "as", "o", "os", -- definite articles
"con", "de", "en", "por", "sen", "so", --prepositions
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ñ and ü
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
local function construct_default_differences(dialect)
if dialect == "Standard" then
return {
need_seseo = false,
gheada_different = false,
}
end
return nil
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text)
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels.
-- Then move the syllable division marker leftwards over clusters that can form onsets.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
local cluster_r = is_spelling and "rɾ" or "ɾ"
-- Don't divide Cl or Cr where C is a stop or fricative, except for dl.
text = rsub(text, "([pbfvkctg])%.([l" .. cluster_r .. "])", ".%1%2")
text = text:gsub("d%.([" .. cluster_r .. "])", ".d%1")
-- Don't divide ll or rr.
text = rsub(text, "([lr])%.%1", ".%1%1")
-- Part 2: Divide hiatuses. Any aeo, or stressed iuüy, should be syllabically divided from a following aeo or
-- stressed iuüy. Also divide ii and uu sequences
text = rsub_repeatedly(text, "([aeoɛɔAEO]" .. accent_c .. "*)(h?[aeoɛɔ])", "%1.%2")
text = rsub_repeatedly(text, "([aeoɛɔAEO]" .. accent_c .. "*)(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub(text, "([iuüyIUÜY]" .. stress_c .. ")(h?[aeoɛɔ])", "%1.%2")
text = rsub_repeatedly(text, "([iuüyIUÜY]" .. stress_c .. ")(h?" .. V .. stress_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "([iI]" .. accent_c .. "*)(h?i)", "%1.%2")
text = rsub_repeatedly(text, "([uU]" .. accent_c .. "*)(h?u)", "%1.%2")
return text
end
-- Generate the IPA of a given respelling.
function export.IPA(text, dialect, phonetic)
local distincion = dialect == "Standard" or dialect == "Gheada"
local gheismo = dialect == "Gheada" or dialect == "Gheada-seseo"
local gheada_different = false
local need_seseo = false
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ñ and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain monosyllabic words unstressed.
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") and not rfind(word, accent_c) or unstressed_words[word] then
-- add CARO to the last vowel not the first one, or we will mess up 'que' by
-- adding the CARO after the 'u'
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CARO)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--handle vowels (internally, we use an accute accent to denote stress)
text = rsub(text, "a" .. BREVE, "ɐ")
text = rsub(text, "e" .. BREVE, "ɪ")
text = rsub(text, "o" .. BREVE, "ʊ")
text = rsub(text, "e" .. GR, "ɛ" .. AC)
text = rsub(text, "o" .. GR, "ɔ" .. AC)
text = rsub(text, "e" .. CFLEX, "E" .. AC) --temporary symbol
text = rsub(text, "o" .. CFLEX, "O" .. AC) --temporary symbol
text = rsub(text, "o".. DOTB, "ɔ")
text = rsub(text, "e" .. DOTB, "ɛ")
text = rsub(text, "e" .. DOTA, "E") --temporary symbols. we will use <e o> for undetermined mid vowels. The template will return an error
text = rsub(text, "o" .. DOTA, "O") --if they remain in pre-tonic or accented syllables
--handle reintegrationist norm forms, if any
-- "nh" and word-final M stay unchanged so they won't affect standard forms.
text = rsub(text, "mh", "nh")
text = rsub(text, "lh", "ll")
text = rsub(text, "ç", "z")
text = rsub(text, "ss", "s")
text = rsub(text, "g([ieɛE])", "x%1")
text = rsub(text, "j", "x")
--determine the values of <e,o> automatically (in some cases only)
text = rsub(text, "ol#", "ɔl#")
text = rsub(text, "ols#", "ɔls#")
text = rsub(text, "el#", "ɛl#")
text = rsub(text, "els#", "ɛls#")
text = rsub(text, "ou", "Ou")
text = rsub(text, "edo#", "Edo#")
text = rsub(text, "edos#", "Edos#")
text = rsub(text, "ello#", "Ello#")
text = rsub(text, "ellos#", "Ellos#")
text = rsub(text, "eza#", "Eza#")
text = rsub(text, "ezas#", "Ezas#")
text = rsub(text, "o" .. AC .. "n#", "O" .. AC .. "n#")
text = rsub(text, "o" .. AC .. "ns#", "O" .. AC .. "ns#")
text = rsub(text, "mente#", "mEnte#") --the -mente ending in adverbs will be handled automatically
--some prepositions/function words will be handled automatically (no need to add "de")
text = rsub(text, "#en#", "#En")
text = rsub(text, "#co" .. CARO .. "n#", "#cʊ" .. CARO .. "n#")
text = rsub(text, "#po" .. CARO .. "r#", "#pʊ" .. CARO .. "r#")
text = rsub(text, "#si" .. CARO .. "n#", "#sɪn#")
--FIXME add automatic suport for prefixes such as termo-, ...
-- handle certain combinations
text = rsub(text, "ch", "ĉ") --not the real sound
text = rsub(text, "nh", "ŋ")
text = rsub(text, "ll", "ɟ")
text = rsub(text, "#p([st])", "#%1") -- [[psicoloxía]], [[pterodáctilo]]
--c, g, q
text = rsub(text, "c([ieɛE])", (distincion and "θ" or "z") .. "%1") -- not the real seseo sound
text = rsub(text, "gu([ieɛE])", "g%1")
text = rsub(text, "gü([ieɛE])", "gu%1")
text = rsub(text, "qu([ieɛE])", "k%1")
text = rsub(text, "z", distincion and "θ" or "z") -- not the real seseo sound
if rfind(text, "[θz]") then
need_seseo = true
end
-- map various consonants to their phoneme equivalent (we will switch to the correct <ɡ> later)
text = rsub(text, "[vgñrxc]", {["v"]="b", ["ñ"]="ɲ", ["r"]="ɾ", ["x"]="ʃ", ["c"] = "k" })
-- trill/tap
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "([#lnszθ])ɾ", "%1r")
--assimilation of nasals
text = rsub(text, "n([# .]*[bp])", "m%1")
text = rsub(text, "n([# .]*[kg])", "ŋ%1")
text = rsub(text, "ns#", "ŋs#")
text = rsub(text, "mn", "ŋn")
text = rsub(text, "nm", "ŋm")
text = rsub(text, "(" .. V .. ")gn(" .. V .. ")", "%1ŋn%2")
text = rsub(text, "n##", "ŋ##")
text = rsub(text, "n# #(" .. V .. ")", "ŋ# #%1")
-- remove silent h before syllable division
text = rsub(text, "h", "")
-- convert i/u between vowels to glide
local vowel_to_glide = { ["i"] = "j", ["u"] = "w" }
-- i and u between vowels -> consonant-like substitutions: [[paranoia]]...
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*h?)([iu])(" .. V .. ")",
function (v1, iu, v2) return v1 .. vowel_to_glide[iu] .. v2 end
)
--some special cases involving the sequences <iu, ui>
text = rsub(text, "iu#", "iw#")
text = rsub(text, "iu" .. AC, "i.u" .. AC)
--syllable division
text = syllabify_from_spelling_or_pronun(text)
--diphthongs
text = rsub(text, "i([aeɛoɔuEO])", "j%1")
text = rsub(text, "u([aeɛoɔuiEO])", "w%1")
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = "ˌ", [CARO] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including ^ indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and (rfind(word, "[^" .. vowel .. "nŋs#]#") or rfind(word, C .. "[nŋs]#")) or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
if rfind(word, "mEn%.te#") then
local mente_syllables
-- Words ends in -mente (converted above to -mênte); add a stress to the preceding portion
mente_syllables = {}
mente_syllables[2] = table.remove(syllables)
mente_syllables[1] = table.remove(syllables)
accent_word(table.concat(syllables, "."), syllables)
accent_word(table.concat(mente_syllables, "."), mente_syllables)
table.insert(syllables, mente_syllables[1])
table.insert(syllables, mente_syllables[2])
else
accent_word(word, syllables)
end
--if the aperture of the stressed vowel is not given, throw an error
for i = 1, #syllables do
if string.find(syllables[i], "ˈ") and (string.find(syllables[i], "e") or string.find(syllables[i], "o")) then
error("Please specify whether the stressed vowels are close-mid or open-mid (see documentation for details).")
end
end
--Reduction of word-final vowels
if not string.find(syllables[#syllables], "ˈ") then
syllables[#syllables] = rsub(syllables[#syllables], "a#", "ɐ#")
syllables[#syllables] = rsub(syllables[#syllables], "ɛ#", "ɪ#")
syllables[#syllables] = rsub(syllables[#syllables], "e#", "ɪ#")
syllables[#syllables] = rsub(syllables[#syllables], "ɔ#", "ʊ#")
syllables[#syllables] = rsub(syllables[#syllables], "o#", "ʊ#")
end
-- Reconstruct the word.
words[j] = table.concat(syllables, ".")
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˌ%1ˈ")--make all primary stresses but the last one be secondary
words[j] = rsub(words[j], "%.(" .. ipa_stress_c .. ")", "%1") -- suppress syllable mark before IPA stress indicator
--determine whether there's an undetermined vowel in the pre-tonic part (exclude monosyllabic unaccented words)
if string.find(words[j], "ˈ") then
local extracted = string.match(words[j], "(.*)ˈ") -- take everything before the stressed syllable
if string.find(extracted, "e") or string.find(extracted, "o") then
error("Please specify whether the pre-tonic vowels are close-mid or open-mid (see documentation for details).")
end
end
end
text = table.concat(words, " ")
text = rsub(text, "E", "e") --no need for the fake symbols anymore
text = rsub(text, "O", "o")
--diphthongs
text = rsub(text, "([aeɛoɔ])i", "%1j")
text = rsub(text, "([aeɛo])u", "%1w")
--we will use /wi/ after velar sounds and /uj/ elsewhere
text = rsub(text, "wi", "uj")
text = rsub(text, "([kgħ])uj", "%1wi")
text = rsub(text, "ju", "iw")
--real sound of seseo Z
text = rsub(text, "z", "s")
--phonetic transcription
if phonetic then
--allophones of /a/
text = rsub(text, "al", "ɑl") --syllable ending in /l/
text = rsub(text, "aŋ", "ɑŋ") --preceded or followed by /ŋ/ in the same syllable --
text = rsub(text, "a" .. TILDE .. "ŋ", "ɑ" .. TILDE .. "ŋ")
text = rsub(text, "ŋa", "ŋɑ")
text = rsub(text, "aw", "ɑw") --in the falling dipthong /aw/
text = rsub(text, "aˈu", "ɑˈu") --hiatus with /aˈu/
text = rsub(text, "a%.([oʊ])", "ɑ.%1") --followed by /o/~/ʊ/
text = rsub(text, "([gk])a", "%1ɑ") --in contact with /k/ or /g/
text = rsub(text, "a([gk])", "ɑ%1")
text = rsub(text, "a%.([gk])", "ɑ.%1")
text = rsub(text, "aˈ([gk])", "ɑˈ%1")
text = rsub(text, "a([ɟʃĉj])", "a̠%1") --before palatal consonants/in a the falling dipthong /aj/
text = rsub(text, "a%.([ɟʃĉj])", "a̠.%1")
--nasalization between nasal consonants
text = rsub(text, "([ŋmnɲ])(" .. V .. ")%.([ŋmnɲ])", "%1%2" .. TILDE .. ".%3")
text = rsub(text, "([ŋmnɲ])(" .. V .. ")ˈ([ŋmnɲ])", "%1%2" .. TILDE .. "ˈ%3")
text = rsub(text, "([ŋmnɲ])(" .. V .. ")([ŋmnɲ])", "%1%2" .. TILDE .. "%3")
-- θ, s before voiced consonants
local voiced = "mnɲbdɟgʎ"
local r = "ɾr"
local tovoiced = {
["θ"] = "θ̬",
["s"] = "z",
}
local function voice(sound, following)
return tovoiced[sound] .. following
end
text = rsub(text, "([θs])(" .. separator_c .. "*[" .. voiced .. r .. "])", voice)
--allophones of /s/
text = rsub(text, "s([ĉʃ])", "S%1") --fake symbol
text = rsub(text, "s%.([ĉʃ])", "S.%1")
text = rsub(text, "z([ɲɟ])", "Z%1") --fake symbol
text = rsub(text, "z%.([ɲɟ])", "Z.%1")
-- fricative vs. stop allophones; first convert stops to fricatives, then back to stops after nasals and sometimes after l
local stop_to_fricative = {["b"] = "β", ["d"] = "ð", ["g"] = "ɣ"}
local fricative_to_stop = {["β"] = "b", ["ð"] = "d", ["ɣ"] = "g"}
text = rsub(text, "[bdg]", stop_to_fricative)
text = rsub(text, "([mnɲŋ]" .. separator_c .. "*)([βɣ])",
function(nasal, fricative) return nasal .. fricative_to_stop[fricative] end
)
text = rsub(text, "([lʎmnɲŋ]" .. separator_c .. "*)ð",
function(nasal_l, fricative) return nasal_l .. "d" end
)
text = rsub(text, "(##" .. ipa_stress_c .. "*)([βɣð])",
function(stress, fricative) return stress .. fricative_to_stop[fricative] end
)
--plosives before consonants
text = rsub(text, "([βɣð])%.([smtʃ])", function(fricative, consonant) return fricative_to_stop[fricative] .. "." .. consonant end )
--/t,d/ are dental
text = rsub(text, "[td]", {["t"] = "t̪", ["d"] = "d̪"})
--/s/ and its voiced allophone [z] are usually apico-alveolar in non-seseo dialects
if distincion then
text = rsub(text, "[sz]", {["s"] = "s̺", ["z"] = "z̺"})
end
-- nasal assimilation before consonants
local labiodental, dental, palatalized =
"ɱ", "n̪", "nʲ"
local nasal_assimilation = {
["f"] = labiodental,
["t"] = dental, ["d"] = dental,
["θ"] = dental,
["ĉ"] = palatalized,
["ʃ"] = palatalized,
}
text = rsub(text, "n(" .. separator_c .. "*)(.)",
function(stress, following) return (nasal_assimilation[following] or "n") .. stress .. following end
)
-- lateral assimilation before consonants
text = rsub(text, "l(" .. separator_c .. "*)(.)",
function(stress, following)
local l = "l"
if following == "t" or following == "d" or following == "θ" then -- dentialveolar
l = "l̪"
elseif following == "ĉ" or following == "ʃ" then -- alveolopalatal
l = "lʲ"
elseif following == "k" or following == "ɣ" then -- velarized
l = "ɫ"
end
return l .. stress .. following
end)
text = rsub(text, "l#", "ɫ#")
end
--dialectal gheada
text = rsub(text, "g", (gheismo and "ħ" or "g"))
text = rsub(text, "ɣ", (gheismo and "ħ" or "ɣ"))
if rfind(text, "[gɣħ]") then
gheada_different = true
end
-- convert fake symbols to real ones
local final_conversions = {
["ĉ"] = "t͡ʃ", -- fake "ch" to real "ch"
["S"] = "sʲ",
["Z"] = "zʲ",
["g"] = "ɡ", --non-IPA to IPA
}
text = rsub(text, "[ĉSZg]", final_conversions)
text = rsub(text, "([βðɣ])", "%1̞") -- voiced fricatives are actually approximants
--don't show vowel reduction in phonemic transcriptions
if not phonetic then
text = rsub(text, "ʊ", "o")
text = rsub(text, "ɪ", "e")
text = rsub(text, "ɐ", "a")
end
-- remove # symbols at word and text boundaries
text = rsub(text, "#", "")
text = unfc(text)
local differences = nil
if dialect == "Standard" then
differences = {
gheada_different = gheada_different,
need_seseo = need_seseo,
}
end
local ret = {
text = text,
differences = differences,
}
return ret
end
function export.IPA_string(frame)
local iparams = {
[1] = {},
["style"] = {required = true},
["phonetic"] = {type = "boolean"},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local retval = export.IPA(iargs[1], iargs.style, iargs.phonetic)
return retval.text
end
-- Generate all relevant dialect pronunciations and group into styles. See [[Module:es-pronunc]] for details.
local function express_all_styles(style_spec, dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
local function express_style(hidden_tag, tag, representative_dialect, matching_styles)
matching_styles = matching_styles or representative_dialect
-- If style specified, make sure it matches the requested style.
local style_matches
if not style_spec then
style_matches = true
else
local style_parts = rsplit(matching_styles, "%-")
local or_styles = rsplit(style_spec, "%s*,%s*")
for _, or_style in ipairs(or_styles) do
local and_styles = rsplit(or_style, "%s*%+%s*")
local and_matches = true
for _, and_style in ipairs(and_styles) do
local negate
if and_style:find("^%-") then
and_style = and_style:gsub("^%-", "")
negate = true
end
local this_style_matches = false
for _, part in ipairs(style_parts) do
if part == and_style then
this_style_matches = true
break
end
end
if negate then
this_style_matches = not this_style_matches
end
if not this_style_matches then
and_matches = false
end
end
if and_matches then
style_matches = true
break
end
end
end
if not style_matches then
return
end
-- Fetch the representative dialect's pronunciation if not already present.
if not ret.pronun[representative_dialect] then
dodialect(ret, representative_dialect)
end
-- Insert the new style into the style group, creating the group if necessary.
local new_style = {
tag = tag,
pronun = ret.pronun[representative_dialect],
}
for _, hidden_tag_style in ipairs(ret.expressed_styles) do
if hidden_tag_style.tag == hidden_tag then
table.insert(hidden_tag_style.styles, new_style)
return
end
end
table.insert(ret.expressed_styles, {
tag = hidden_tag,
styles = {new_style},
})
end
-- For each type of difference, figure out if the difference exists in any of the given respellings.
dodialect(ret, "Standard")
local differences = {}
for _, difftype in ipairs { "need_seseo", "gheada_different" } do
for _, pronun in ipairs(ret.pronun["Standard"]) do
if pronun.differences[difftype] then
differences[difftype] = true
end
end
end
local need_seseo = differences.need_seseo
local gheada_different = differences.gheada_different
-- Now, based on the observed differences, figure out how to combine the individual dialects into styles and
-- style groups.
if not gheada_different then
if not need_seseo then
express_style(false, false, "Standard")
else
express_style(false, "standard", "Standard")
express_style(false, "seseo", "Seseo")
end
else
if not need_seseo then
express_style(false, "standard", "Standard")
express_style(false, "gheada", "Gheada")
else
express_style(false, "standard", "Standard")
express_style(false, "gheada", "Gheada")
express_style(false, "gheada and seseo", "Gheada-seseo")
end
end
return ret
end
local function format_all_styles(expressed_styles, format_style, width)
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
else
style_group.formatted, style_group.formatted_len =
format_style(style_group.tag, style_group.styles[1], i == 1)
for j, style in ipairs(style_group.styles) do
style.formatted, style.formatted_len =
format_style(style.tag, style, i == 1 and j == 1)
end
end
end
local maxlen = 0
for i, style_group in ipairs(expressed_styles) do
local this_len = style_group.formatted_len
if #style_group.styles > 1 then
for _, style in ipairs(style_group.styles) do
this_len = math.max(this_len, style.formatted_len)
end
end
maxlen = math.max(maxlen, this_len)
end
local lines = {}
local need_major_hack = false
for i, style_group in ipairs(expressed_styles) do
if #style_group.styles == 1 then
table.insert(lines, style_group.formatted)
need_major_hack = false
else
local inline = '\n<div class="vsShow" style="display:none">\n' .. style_group.formatted .. "</div>"
local full_prons = {}
for _, style in ipairs(style_group.styles) do
table.insert(full_prons, style.formatted)
end
local full = '\n<div class="vsHide">\n' .. table.concat(full_prons, "\n") .. "</div>"
local em_length = math.floor(maxlen * width) --this allows us to have a different coefficient in IPA transcriptions and rhymes
table.insert(lines, '<div class="vsSwitcher" data-toggle-category="pronunciations" style="width: ' .. em_length .. 'em; max-width:100%;"><span class="vsToggleElement" style="float: right;"> </span>' .. inline .. full .. "</div>")
need_major_hack = true
end
end
-- major hack to get bullets working on the next line after a div box
return table.concat(lines, "\n") .. (need_major_hack and "\n<span></span>" or "")
end
local function dodialect_pronun(args, ret, dialect)
ret.pronun[dialect] = {}
for i, term in ipairs(args.terms) do
local phonemic, phonetic, differences
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
differences = construct_default_differences(dialect)
else
phonemic = export.IPA(term.term, dialect, false)
phonetic = export.IPA(term.term, dialect, true)
differences = phonemic.differences
phonemic = phonemic.text
phonetic = phonetic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[dialect][i] = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
differences = differences,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret, dialect)
dodialect_pronun(args, ret, dialect)
end
local ret = express_all_styles(args.style, this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]], and also create an approximation of the formatted version so that we can
-- compute the appropriate width of the HTML switcher div box that holds the different per-dialect variants.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
ins(bracket_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style, 0.55)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡ʃ", "tʃ")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CARO then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. [[Austria-Hungría]])
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CARO)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, parsed_respellings, rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables and term is single-word, try to take the number of syllables from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun[dialect]) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
differences = construct_default_differences(dialect),
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(put_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{es-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "=" .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
style = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = require(put_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "=" .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hmp = {},
bullets = 1,
})
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic or term.raw_phonetic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
if not parsed.pronun.pronun[dialect] then
dodialect_pronun(parsed, parsed.pronun, dialect)
end
for _, pronun in ipairs(parsed.pronun.pronun[dialect]) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun[dialect]) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
local rhyme_diffs = nil
if dialect == "Standard" then
rhyme_diffs = {}
if rhyme:find("θ") then
rhyme_diffs.need_seseo = true
end
if rhyme:find("ɡ") then
rhyme_diffs.gheada_different = true
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = rhyme,
num_syl = {num_syl},
differences = rhyme_diffs,
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(parsed.style, dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, {parsed}, rhyme_ret, dialect)
end
parsed.rhyme = express_all_styles(parsed.style, this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, parsed_respellings, rhyme_ret, dialect)
end
overall_rhyme = express_all_styles(parsed.style, dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style, 0.75)
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
byrwpdct7i2tmdhxa9cjc0fkxxwaxq9
Module:Affix
828
2499225
9909035
9636747
2026-09-29T07:23:54Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909035
Scribunto
text/plain
local export = {}
local debug_force_cat = false -- if set to true, always display categories even on userspace pages
local m_links = require("Module:links")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local decorations_module = "Module:decorations"
local en_utilities_module = "Module:en-utilities"
local etymology_module = "Module:etymology"
local scripts_module = "Module:scripts"
local utilities_module = "Module:utilities"
-- Export this so the category code in [[Module:category tree/etymology]] can access it.
export.affix_lang_data_module_prefix = "Module:affix/lang-data/"
local rsub = m_str_utils.gsub
local usub = m_str_utils.sub
local ulen = m_str_utils.len
local rfind = m_str_utils.find
local rmatch = m_str_utils.match
local pluralize = require(en_utilities_module).pluralize
local u = m_str_utils.char
local ucfirst = m_str_utils.ucfirst
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local chinese = {
prefix = "前綴",
suffix = "後綴",
infix = "中綴",
circumfix = "環綴",
interfix = "間綴",
transfix = "插綴",
}
function export.affix_variants(canonical, variants)
local mappings = {}
for _, variant in ipairs(variants) do
mappings[variant] = canonical
end
return mappings
end
function export.id_mapping(default, ids)
local mapping = { default = default }
if ids then
for id, target in pairs(ids) do
mapping[id] = target
end
end
return mapping
end
function export.id_mapping_with_affix_variants(base, id_variants)
local mappings = {}
for id, variants in pairs(id_variants) do
for _, variant in ipairs(variants) do
mappings[variant] = export.id_mapping(base, {[id] = base})
end
end
return mappings
end
function export.merge_tables(...)
local result = {}
for i = 1, select('#', ...) do
local t = select(i, ...)
if t then
for k, v in pairs(t) do
result[k] = v
end
end
end
return result
end
-- Export this so the category code in [[Module:category tree/etymology]] can access it.
export.langs_with_lang_specific_data = {
["az"] = true,
["fi"] = true,
["fr"] = true,
["izh"] = true,
["la"] = true,
["sah"] = true,
["tr"] = true,
["trk-pro"] = true,
}
local default_pos = "詞"
--[==[ intro:
===About different types of hyphens ("template", "display" and "lookup"):===
* The "template hyphen" is the per-script hyphen character that is used in template calls to indicate that a term is an
affix. This is always a single Unicode char, but there may be multiple possible hyphens for a given script. Normally
this is just the regular hyphen character "-", but for some non-Latin-script languages (currently only right-to-left
languages), it is different.
* The "display hyphen" is the string (which might be an empty string) that is added onto a term as displayed and linked,
to indicate that a term is an affix. Currently this is always either the same as the template hyphen or an empty
string, but the code below is written generally enough to handle arbitrary display hyphens. Specifically:
*# For East Asian languages, the display hyphen is always blank.
*# For Arabic-script languages, either tatweel (ـ) or ZWNJ (zero-width non-joiner) are allowed as template hyphens,
where ZWNJ is supported primarily for Farsi, because some suffixes have non-joining behavior. The display hyphen
corresponding to tatweel is also tatweel, but the display hyphen corresponding to ZWNJ is blank (tatweel is also
the default display hyphen, for calls to {{tl|prefix}}/{{tl|suffix}}/etc. that don't include an explicit hyphen).
* The "lookup hyphen" is the hyphen that is used when looking up language-specific affix mappings. (These mappings are
discussed in more detail below when discussing link affixes.) It depends only on the script of the affix in question.
Most scripts (including East Asian scripts) use a regular hyphen "-" as the lookup hyphen, but Hebrew and Arabic
have their own lookup hyphens (respectively maqqef and tatweel). Note that for Arabic in particular, there are
three possible template hyphens that are recognized (tatweel, ZWNJ and regular hyphen), but mappings must use tatweel.
===About different types of affixes ("template", "display", "link", "lookup" and "category"):===
* A "template affix" is an affix in its source form as it appears in a template call. Generally, a template affix has an
attached template hyphen (see above) to indicate that it is an affix and indicate what type of affix it is (prefix,
suffix, interfix or circumfix), but some of the older-style templates such as {{tl|suffix}}, {{tl|prefix}},
{{tl|confix}}, etc. have "positional" affixes where the presence of the affix in a certain position (e.g. the second
or third parameter) indicates that it is a certain type of affix, whether or not it has an attached template hyphen.
* A "display affix" is the corresponding affix as it is actually displayed to the user. The display affix may differ
from the template affix for various reasons:
*# The display affix may be specified explicitly using the {{para|alt<var>N</var>}} parameter, the `<alt:...>` inline
modifier or a piped link of the form e.g. `<nowiki>[[-kas|-käs]]</nowiki>` (here indicating that the affix should
display as `-käs` but be linked as `-kas`). Here, the template affix is arguably the entire piped link, while the
display affix is `-käs`.
*# Even in the absence of {{para|alt<var>N</var>}} parameters, `<alt:...>` inline modifiers and piped links, certain
languages have differences between the "template hyphen" specified in the template (which always needs to be
specified somehow or other in templates like {{tl|affix}}, to indicate that the term is an affix and what type of
affix it is) and the display hyphen (see above), with corresponding differences between template and display
affixes.
* A (regular) "link affix" is the affix that is linked to when the affix is shown to the user. The link affix is usually
the same as the display affix, but will differ in one of three circumstances:
*# The display and link affixes are explicitly made different using {{para|alt<var>N</var>}} parameters, `<alt:...>`
inline modifiers or piped links, as described above under "display affix".
*# For certain languages, certain affixes are mapped to canonical form using language-specific mappings. For example,
in Finnish, the adjective-forming suffix {{m|fi|-kas}} appears as {{m|fi|-käs}} after front vowels, but logically
both forms are the same suffix and should be linked and categorized the same. Similarly, in Latin, the negative and
intensive prefixes spelled {{m|la|in-}} (etymologically two distinct prefixes) appear variously as {{m|la|il-}},
{{m|la|im-}} or {{m|la|ir-}} before certain consonants. Mappings are supplied in [[Module:affix/lang-data/LANGCODE]]
to convert Finnish {{m|fi|-käs}} to {{m|fi|-kas}} for linking and categorization purposes. Note that the affixes in
the mappings use "lookup hyphens" to indicate the different types of affixes, which is usually the same as the
template hyphen but differs for Arabic scripts, because there are multiple possible template hyphens recognized but
only one lookup hyphen (tatweel). The form of the affix as used to look up in the mapping tables is called the
"lookup affix"; see below.
* A "stripped link affix" is a link affix that has been passed through the language's `stripDiacritics()` function, which
may strip certain diacritics: e.g. macrons in Latin and Old English (indicating length); acute and grave accents in
Russian and various other Slavic languages (indicating stress); vowel diacritics in most Arabic-script languages; and
also tatweel in some Arabic-script languages (currently, for example, Persian, Arabic and Urdu strip tatweel, but
Ottoman Turkish does not). Stripped link affixes are currently what are used in category names.
* A "lookup affix" is the form of the affix as it is looked up in the language-specific lookup mappings described above
under link affixes. There are actually two lookup stages:
*# First, the affix is looked up in a modified display form (specifically, the same as the display affix but using
lookup hyphens). Note that this lookup does not occur if an explicit display form is given using
{{para|alt<var>N</var>}} or an `<alt:...>` inline modifier, or if the template affix contains a piped or embedded
link.
*# If no entry is found, the affix is then looked up in a modified link form (specifically, the modified display
form passed through the language's `stripDiacritics()` function, which strips out certain diacritics, but with the
lookup hyphen re-added if it was stripped out, as in the case of tatweel in many Arabic-script languages).
The reason for this double lookup procedure is to allow for mappings that are sensitive to the extra diacritics, but
also allow for mappings that are not sensitive in this fashion (e.g. Russian {{m|ru|-ливый}} occurs both stressed and
unstressed, but is the same prefix either way).
* A "category affix" is the affix as it appears in categories such as [[:Category:Finnish terms suffixed with -kas|
Category:Finnish terms suffixed with ''-kas'']]. The category affix is currently always the same as the stripped link
affix. This means that for Arabic-script languages, it may or may not have a tatweel, even if the correponding display
affix and regular link affix have a tatweel. As mentioned above, stripDiacritics() strips tatweel for Arabic, Persian
and Urdu, but not for Ottoman Turkish. Hence affix categories for Arabic, Persian and Urdu will be missing the
tatweel, but affix categories for Ottoman Turkish will have it. An additional complication is that if the template
affix contains a ZWNJ, the display (and hence the link and category affixes) will have no hyphen attached in any case.
]==]
-----------------------------------------------------------------------------------------
-- Template and display hyphens --
-----------------------------------------------------------------------------------------
--[=[
Per-script template hyphens. The template hyphen is what appears in the {{affix}}/{{prefix}}/{{suffix}}/etc. template
(in the wikicode). See above.
They key below is a script code, after removing a hyphen and anything preceding. Hence, script codes like 'fa-Arab'
and 'ur-Arab' will match 'Arab'.
The value below is a string consisting of one or more hyphen characters. If there is more than one character, the
default hyphen must come last and a non-default function must be specified for the script in display_hyphens[] so
the correct display hyphen will be specified when no template hyphen is given (in {{suffix}}/{{prefix}}/etc.).
Script detection is normally done when linking, but we need to do it earlier. However, under most circumstances we
don't need to do script detection. Specifically, we only need to do script detection for a given language if
(a) the language has multiple scripts; and
(b) at least one of those scripts is listed below or in display_hyphens.
]=]
local ZWNJ = u(0x200C) -- zero-width non-joiner
local template_hyphens = {
-- This covers all Arabic scripts. See above.
["Arab"] = "ـ" .. ZWNJ .. "-", -- tatweel + zero-width non-joiner + regular hyphen
["Hebr"] = "־", -- Hebrew-specific hyphen termed "maqqef"
["Mong"] = "᠊",
-- FIXME! What about the following right-to-left scripts?
-- Adlm (Adlam)
-- Armi (Imperial Aramaic)
-- Avst (Avestan)
-- Cprt (Cypriot)
-- Khar (Kharoshthi)
-- Mand (Mandaic/Mandaean)
-- Mani (Manichaean)
-- Mend (Mende/Mende Kikakui)
-- Narb (Old North Arabian)
-- Nbat (Nabataean/Nabatean)
-- Nkoo (N'Ko)
-- Orkh (Orkhon runes)
-- Phli (Inscriptional Pahlavi)
-- Phlp (Psalter Pahlavi)
-- Phlv (Book Pahlavi)
-- Phnx (Phoenician)
-- Prti (Inscriptional Parthian)
-- Rohg (Hanifi Rohingya)
-- Samr (Samaritan)
-- Sarb (Old South Arabian)
-- Sogd (Sogdian)
-- Sogo (Old Sogdian)
-- Syrc (Syriac)
-- Thaa (Thaana)
}
-- Hyphens used when looking up an affix in a lang-specific affix mapping. Defaults to regular hyphen (-). The keys
-- are script codes, after removing a hyphen and anything preceding. Hence, script codes like 'fa-Arab' and 'ur-Arab'
-- will match 'Arab'. The value should be a single character.
local lookup_hyphens = {
["Hebr"] = "־",
-- This covers all Arabic scripts. See above.
["Arab"] = "ـ",
}
-- Default display-hyphen function.
local function default_display_hyphen(script, hyph)
if not hyph then
return template_hyphens[script] or "-"
end
return hyph
end
local function arab_get_display_hyphen(script, hyph)
if not hyph then
return "ـ" -- tatweel
elseif hyph == ZWNJ then
return ""
else
return hyph
end
end
local function no_display_hyphen(script, hyph)
return ""
end
-- Per-script function to return the correct display hyphen given the script and template hyphen. The function should
-- also handle the case where the passed-in template hyphen is nil, corresponding to the situation in
-- {{prefix}}/{{suffix}}/etc. where no template hyphen is specified. The key is the script code after removing a hyphen
-- and anything preceding, so 'fa-Arab', 'ur-Arab' etc. will match 'Arab'.
local display_hyphens = {
-- This covers all Arabic scripts. See above.
["Arab"] = arab_get_display_hyphen,
["Bopo"] = no_display_hyphen,
["Hani"] = no_display_hyphen,
["Hans"] = no_display_hyphen,
["Hant"] = no_display_hyphen,
-- The following is a mixture of several scripts. Hopefully the specs here are correct!
["Jpan"] = no_display_hyphen,
["Jurc"] = no_display_hyphen,
["Kitl"] = no_display_hyphen,
["Kits"] = no_display_hyphen,
["Laoo"] = no_display_hyphen,
["Nshu"] = no_display_hyphen,
["Shui"] = no_display_hyphen,
["Tang"] = no_display_hyphen,
["Thaa"] = no_display_hyphen,
["Thai"] = no_display_hyphen,
["Tibt"] = no_display_hyphen,
}
-----------------------------------------------------------------------------------------
-- Basic Utility functions --
-----------------------------------------------------------------------------------------
local function glossary_link(entry, text)
text = text or entry
return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end
local function track(page)
if type(page) == "table" then
for i, pg in ipairs(page) do
page[i] = "affix/" .. pg
end
else
page = "affix/" .. page
end
require("Module:debug/track")(page)
end
local function ine(val)
return val ~= "" and val or nil
end
-----------------------------------------------------------------------------------------
-- Compound types --
-----------------------------------------------------------------------------------------
local function make_compound_type(typ, alttext)
return {
text = glossary_link(typ, alttext) .. "複合詞",
cat = typ .. "複合詞",
}
end
-- Make a compound type entry with a simple rather than glossary link.
-- These should be replaced with a glossary link when the entry in the glossary
-- is created.
local function make_non_glossary_compound_type(typ, alttext)
local link = alttext and "[[" .. typ .. "|" .. alttext .. "]]" or "[[" .. typ .. "]]"
return {
text = link .. "複合詞",
cat = typ .. "複合詞",
}
end
local function make_raw_compound_type(typ, alttext)
return {
text = glossary_link(typ, alttext),
cat = typ,
}
end
local function make_borrowing_type(typ, alttext)
return {
text = glossary_link(typ, alttext),
borrowing_type = typ
}
end
export.etymology_types = {
["adapted borrowing"] = make_borrowing_type("同化借詞"),
["adap"] = "adapted borrowing",
["abor"] = "adapted borrowing",
["alliterative"] = make_non_glossary_compound_type("alliterative"),
["allit"] = "alliterative",
["antonymous"] = make_non_glossary_compound_type("反義詞"),
["ant"] = "antonymous",
["bahuvrihi"] = make_compound_type("bahuvrihi", "bahuvrīhi"),
["bahu"] = "bahuvrihi",
["bv"] = "bahuvrihi",
["coordinative"] = make_compound_type("同類詞"),
["coord"] = "coordinative",
["descriptive"] = make_compound_type("descriptive"),
["desc"] = "descriptive",
["determinative"] = make_compound_type("限定詞"),
["det"] = "determinative",
["dvandva"] = make_compound_type("dvandva"),
["dva"] = "dvandva",
["dvigu"] = make_compound_type("dvigu"),
["dvi"] = "dvigu",
["endocentric"] = make_compound_type("向心詞"),
["endo"] = "endocentric",
["exocentric"] = make_compound_type("離心詞"),
["exo"] = "exocentric",
["izafet I"] = make_compound_type("izafet I"),
["iz1"] = "izafet I",
["izafet II"] = make_compound_type("izafet II"),
["iz2"] = "izafet II",
["izafet III"] = make_compound_type("izafet III"),
["iz3"] = "izafet III",
["karmadharaya"] = make_compound_type("karmadharaya", "karmadhāraya"),
["karma"] = "karmadharaya",
["kd"] = "karmadharaya",
["kenning"] = make_raw_compound_type("kenning"),
["ken"] = "kenning",
["rhyming"] = make_non_glossary_compound_type("押韻"),
["rhy"] = "rhyming",
["synonymous"] = make_non_glossary_compound_type("近義詞"),
["syn"] = "synonymous",
["tatpurusa"] = make_compound_type("tatpurusa", "tatpuruṣa"),
["tat"] = "tatpurusa",
["tp"] = "tatpurusa",
}
local function process_etymology_type(typ, nocap, notext, has_parts)
local text_sections = {}
local categories = {}
local borrowing_type
if typ then
local typdata = export.etymology_types[typ]
if type(typdata) == "string" then
typdata = export.etymology_types[typdata]
end
if not typdata then
error("Internal error: Unrecognized type '" .. typ .. "'")
end
local text = typdata.text
if not nocap then
text = ucfirst(text)
end
local cat = typdata.cat
borrowing_type = typdata.borrowing_type
local oftext = typdata.oftext or "的"
if borrowing_type and usub(borrowing_type, -1) == "詞" then -- L10N, see categories on [[absorbovať]]
borrowing_type = usub(borrowing_type, 1, -2)
end
if not notext then -- L10N 注意語序
if has_parts then
table.insert(text_sections, oftext)
end
table.insert(text_sections, text)
end
if cat then
table.insert(categories, cat)
end
end
return text_sections, categories, borrowing_type
end
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
-- Iterate an array up to the greatest integer index found.
local function ipairs_with_gaps(t)
local indices = m_table.numKeys(t)
local max_index = #indices > 0 and math.max(unpack(indices)) or 0
local i = 0
return function()
while i < max_index do
i = i + 1
return i, t[i]
end
end
end
export.ipairs_with_gaps = ipairs_with_gaps
-- l10n
function format_category_name(lang_name, category)
if category:find("的") then
local before, after = category:match("(.*)的(.*)$")
return before .. "的" .. lang_name .. after
else
return lang_name .. category
end
end
export.format_category_name = format_category_name
--[==[
Join formatted parts (in `parts_formatted`) together with any overall {{para|lit}} spec (in `lit`) plus categories,
which are formatted by prepending the language name as found in `lang`. The value of an entry in `categories` can be
either a string (which is formatted using `sort_key`) or a table of the form `{ {cat=<var>category</var>,
sort_key=<var>sort_key</var>, sort_base=<var>sort_base</var>}`, specifying the sort key and sort base to use when
formatting the category. If `nocat` is given, no categories are added; otherwise, `force_cat` causes categories to be
added even on userspace pages.
]==]
function export.join_formatted_parts(data)
local cattext
local lang = data.data.lang
local force_cat = data.data.force_cat or debug_force_cat
if data.data.nocat then
cattext = ""
else
for i, cat in ipairs(data.categories) do
if type(cat) == "table" then
local category_name = format_category_name(lang:getFullName(), cat.cat)
data.categories[i] = require(utilities_module).format_categories(category_name,
lang, cat.sort_key, cat.sort_base, force_cat)
else
local category_name = format_category_name(lang:getFullName(), cat)
data.categories[i] = require(utilities_module).format_categories(category_name, lang,
data.data.sort_key, nil, force_cat)
end
end
cattext = table.concat(data.categories)
end
local result = table.concat(data.parts_formatted, not data.separator_already_added and " +‎ " or nil) ..
(data.data.lit and ",字面意思為" .. m_links.mark(data.data.lit, "gloss") or "")
local q = data.data.q
local qq = data.data.qq
local l = data.data.l
local ll = data.data.ll
if q and q[1] or qq and qq[1] or l and l[1] or ll and ll[1] then
result = require(decorations_module).format_decorations {
lang = lang,
text = result,
q = q,
qq = qq,
l = l,
ll = ll,
}
end
return result .. cattext
end
local function pluralize(pos)
if pos ~= "nouns" and usub(pos, -5) ~= "verbs" and usub(pos, -4) ~= "ives" then
if pos:find("[sx]$") then
pos = pos .. "es"
else
pos = pos .. "s"
end
end
return pos
end
-- Remove links and call lang:stripDiacritics(term).
local function strip_diacritics_no_links(lang, term)
return lang:stripDiacritics(m_links.remove_links(term))
end
--[=[
Convert a raw part as passed into an entry point into a part ready for linking. `lang` and `sc` are the overall
language and script objects. This uses the overall language and script objects as defaults for the part and parses off
any fragment from the term. We need to do the latter so that fragments don't end up in categories and so that we
correctly do affix mapping even in the presence of fragments.
]=]
local function canonicalize_part(part, lang, sc)
if not part then
return
end
-- Save the original (user-specified, part-specific) value of `lang`. If such a value is specified, we don't insert
-- a '*fixed with' category, and we format the part using format_derived() in [[Module:etymology]] rather than
-- full_link() in [[Module:links]].
part.part_lang = part.lang
part.lang = part.lang or lang
part.sc = part.sc or sc
local term = part.term
if not term then
return
elseif not part.fragment then
part.term, part.fragment = m_links.get_fragment(term)
else
part.term = m_links.get_fragment(term)
end
end
--[==[
Construct a single linked part based on the information in `part`, for use by `show_affix()` and other entry points.
This should be called after `canonicalize_part()` is called on the part. This is a thin wrapper around `full_link()` in
[[Module:links]] unless `part.part_lang` is specified (indicating that a part-specific language was given), in which
case `format_derived()` in [[Module:etymology]] is called to display a term in a language other than the language of
the overall term (specified in `data.lang`). `data` contains the entire object passed into the entry point and is used
to access information for constructing the categories added by `format_derived()`.
]==]
function export.link_term(part, data, include_separator)
local result
if part.part_lang then
result = require(etymology_module).format_derived {
lang = data.lang,
terms = {part},
sources = {part.lang},
sort_key = data.sort_key,
nocat = data.nocat,
template_name = "affix",
decorations_on_outside = true,
borrowing_type = data.borrowing_type,
force_cat = data.force_cat or debug_force_cat,
}
else
-- l10n: zhwikt 的 [[Module:parameter utilities]] 不會自動設定 show_decorations,需在此設定
part.show_decorations = true
result = m_links.full_link(part, "term")
end
if include_separator and part.separator then
return part.separator .. result
else
return result
end
end
local function canonicalize_script_code(scode)
-- Convert fa-Arab, ur-Arab etc. to Arab.
return (scode:gsub("^.*%-", ""))
end
-----------------------------------------------------------------------------------------
-- Affix-handling functions --
-----------------------------------------------------------------------------------------
-- Figure out the appropriate script for the given affix and language (unless the script is explicitly passed in), and
-- return the values of template_hyphens[], display_hyphens[] and lookup_hyphens[] for that script, substituting
-- default values as appropriate. Four values are returned:
-- DETECTED_SCRIPT, TEMPLATE_HYPHEN, DISPLAY_HYPHEN, LOOKUP_HYPHEN
local function detect_script_and_hyphens(text, lang, sc)
local scode
-- 1. If the script is explicitly passed in, use it.
if sc then
scode = sc:getCode()
else
local possible_script_codes = lang:getScriptCodes()
-- YUCK! `possible_script_codes` comes from loadData() so #possible_scripts doesn't work (always returns 0).
local num_possible_script_codes = m_table.length(possible_script_codes)
if num_possible_script_codes == 0 then
-- This shouldn't happen; if the language has no script codes,
-- the list {"None"} should be returned.
error("Something is majorly wrong! Language " .. lang:getCanonicalName() .. " has no script codes.")
end
if num_possible_script_codes == 1 then
-- 2. If the language has only one possible script, use it.
scode = possible_script_codes[1]
else
-- 3. Check if any of the possible scripts for the language have non-default values for template_hyphens[]
-- or display_hyphens[]. If so, we need to do script detection on the text. If not, just use "Latn",
-- which may not be technically correct but produces the right results because Latn has all default
-- values for template_hyphens[] and display_hyphens[].
local may_have_nondefault_hyphen = false
for _, script_code in ipairs(possible_script_codes) do
script_code = canonicalize_script_code(script_code)
if template_hyphens[script_code] or display_hyphens[script_code] then
may_have_nondefault_hyphen = true
break
end
end
if not may_have_nondefault_hyphen then
scode = "Latn"
else
scode = lang:findBestScript(text):getCode()
end
end
end
scode = canonicalize_script_code(scode)
local template_hyphen = template_hyphens[scode] or "-"
local lookup_hyphen = lookup_hyphens[scode] or "-"
local display_hyphen = display_hyphens[scode] or default_display_hyphen
return scode, template_hyphen, display_hyphen, lookup_hyphen
end
--[=[
Given a template affix `term` and an affix type `affix_type`, change the relevant template hyphen(s) in the affix to
the display or lookup hyphen specified in `new_hyphen`, or add them if they are missing. `new_hyphen` can be a string,
specifying a fixed hyphen, or a function of two arguments (the script code `scode` and the discovered template hyphen,
or nil of no relevant template hyphen is present). `thyph_re` is a Lua pattern (which must be enclosed in parens) that
matches the possible template hyphens. Note that not all template hyphens present in the affix are changed, but only
the "relevant" ones (e.g. for a prefix, a relevant template hyphen is one coming at the end of the affix).
]=]
local function reconstruct_term_per_hyphens(term, affix_type, scode, thyph_re, new_hyphen)
local function get_hyphen(hyph)
if type(new_hyphen) == "string" then
return new_hyphen
end
return new_hyphen(scode, hyph)
end
if affix_type == "non-affix" then
return term
elseif affix_type == "circumfix" then
local before, before_hyphen, after_hyphen, after = rmatch(term, "^(.*)" .. thyph_re .. " " .. thyph_re
.. "(.*)$")
if not before or ulen(term) <= 3 then
-- Unlike with other types of affixes, don't try to add hyphens in the middle of the term to convert it to
-- a circumfix. Also, if the term is just hyphen + space + hyphen, return it.
return term
end
return before .. get_hyphen(before_hyphen) .. " " .. get_hyphen(after_hyphen) .. after
elseif affix_type == "infix" or affix_type == "interfix" then
local before_hyphen, middle, after_hyphen = rmatch(term, "^" .. thyph_re .. "(.*)" .. thyph_re .. "$")
if before_hyphen and ulen(term) <= 1 then
-- If the term is just a hyphen, return it.
return term
end
return get_hyphen(before_hyphen) .. (middle or term) .. get_hyphen(after_hyphen)
elseif affix_type == "prefix" then
local middle, after_hyphen = rmatch(term, "^(.*)" .. thyph_re .. "$")
if middle and ulen(term) <= 1 then
-- If the term is just a hyphen, return it.
return term
end
return (middle or term) .. get_hyphen(after_hyphen)
elseif affix_type == "suffix" then
local before_hyphen, middle = rmatch(term, "^" .. thyph_re .. "(.*)$")
if before_hyphen and ulen(term) <= 1 then
-- If the term is just a hyphen, return it.
return term
end
return get_hyphen(before_hyphen) .. (middle or term)
else
error(("Internal error: Unrecognized affix type '%s'"):format(affix_type))
end
end
--[=[
Look up a mapping from a given affix variant to the canonical form used in categories and links. The lookup tables are
language-specific according to `lang`, and may be ID-specific according to `affix_id`. The affixes as they appear in the
lookup tables (both the variant and the canonical form) are in "lookup affix" format (approximately speaking, they use a
regular hyphen for most scripts, but a tatweel for Arabic-script entries and a maqqef for Hebrew-script entries), but
the passed-in `affix` param is in "template affix" format (which differs from the lookup affix for Arabic-script
entries, because more types of hyphens are allowed in template affixes; see the comments at the top of the file). The
remaining parameters to this function are used to convert from template affixes to lookup affixes; see the
reconstruct_term_per_hyphens() function above.
If the affix contains brackets, no lookup is done. Otherwise, a two-stage process is used, first looking up the affix
directly and then stripping diacritics and looking it up again. The reason for this is documented above in the comments
at the top of the file (specifically, the comments describing lookup affixes).
The value of a mapping can either be a string (do the mapping regardless of affix ID) or a table indexed by affix ID
(where the special value `false` indicates no affix ID). The values of entries in this table can also be strings, or
tables with keys `affix` and `id` (again, use `false` to indicate no ID). This allows an affix mapping to map from one
ID to another (for example, this is used in English to map the [[an-]] prefix with no ID to the [[a-]] prefix with the
ID 'not').
The Given a template affix `term` and an affix type `affix_type`, change the relevant template hyphen(s) in the affix to
the display or lookup hyphen specified in `new_hyphen`, or add them if they are missing. `new_hyphen` can be a string,
specifying a fixed hyphen, or a function of two arguments (the script code `scode` and the discovered template hyphen,
or nil of no relevant template hyphen is present). `thyph_re` is a Lua pattern (which must be enclosed in parens) that
matches the possible template hyphens. Note that not all template hyphens present in the affix are changed, but only
the "relevant" ones (e.g. for a prefix, a relevant template hyphen is one coming at the end of the affix).
]=]
local function lookup_affix_mapping(affix, affix_type, lang, scode, thyph_re, lookup_hyph, affix_id)
local function do_lookup(affix)
-- Ensure that the affix uses lookup hyphens regardless of whether it used a different type of hyphens before
-- or no hyphens.
local lookup_affix = reconstruct_term_per_hyphens(affix, affix_type, scode, thyph_re, lookup_hyph)
local function do_lookup_for_langcode(langcode)
if export.langs_with_lang_specific_data[langcode] then
local langdata = mw.loadData(export.affix_lang_data_module_prefix .. langcode)
if langdata.affix_mappings then
local mapping = langdata.affix_mappings[lookup_affix]
if mapping then
if type(mapping) == "table" then
mapping = mapping[affix_id] or mapping.default or mapping[affix_id or false]
if mapping then
return mapping
end
else
return mapping
end
end
end
end
end
-- If `lang` is an etymology-only language, look for a mapping both for it and its full parent.
local langcode = lang:getCode()
local mapping = do_lookup_for_langcode(langcode)
if mapping then
return mapping
end
local full_langcode = lang:getFullCode()
if full_langcode ~= langcode then
mapping = do_lookup_for_langcode(full_langcode)
if mapping then
return mapping
end
end
return nil
end
if affix:find("%[%[") then
return nil
end
return do_lookup(affix) or do_lookup(lang:stripDiacritics(affix)) or nil
end
--[==[
For a given template term in a given language (see the definition of "template affix" near the top of the file),
possibly in an explicitly specified script `sc` (but usually nil), return the term's affix type ({"prefix"},
{"interfix"}, {"suffix"}, {"circumfix"} or {"non-affix"}) along with the corresponding link and display affixes
(see definitions near the top of the file); also the corresponding lookup affix (if `return_lookup_affix` is specified).
The term passed in should already have any fragment (after the # sign) parsed off of it. Four values are returned:
`affix_type`, `link_term`, `display_term` and `lookup_term`. The affix type can be passed in instead of autodetected; in
this case, the template term need not have any attached hyphens, and the appropriate hyphens will be added in the
appropriate places. If `do_affix_mapping` is specified, look up the affix in the lang-specific affix mappings, as
described in the comment at the top of the file; otherwise, the link and display terms will always be the same. (They
will be the same in any case if the template term has a bracketed link in it or is not an affix.) If
`return_lookup_affix` is given, the fourth return value contains the term with appropriate lookup hyphens in the
appropriate places; otherwise, it is the same as the display term. (This functionality is used in
[[Module:category tree/affixes and compounds]] to convert link affixes into lookup affixes so that they can be looked up
in the affix mapping tables.)
]==]
local function parse_term_for_affixes(term, lang, sc, affix_type, do_affix_mapping, return_lookup_affix, affix_id)
if not term then
return "non-affix", nil, nil, nil
end
if term == "^" then
-- Indicates a null term to emulate the behavior of {{suffix|foo||bar}}.
term = ""
return "non-affix", term, term, term
end
if term:find("^%^") then
-- HACK! ^ at the beginning of Korean languages has a special meaning, triggering capitalization of the
-- transliteration. Don't interpret it as "force non-affix" for those languages.
local langcode = lang:getCode()
if langcode ~= "ko" and langcode ~= "okm" and langcode ~= "jje" then
-- Formerly we allowed ^ to force non-affix type; this is now handled using an inline modifier
-- <naf>, <root>, etc. Throw an error for the moment when the old way is encountered.
error("Use of ^ to force non-affix status is no longer supported; use an inline modifier <naf> or <root> " ..
"after the component")
end
end
-- Remove an asterisk if the morpheme is reconstructed and add it back at the end.
local reconstructed = ""
if term:find("^%*") then
reconstructed = "*"
term = term:gsub("^%*", "")
end
local scode, thyph, dhyph, lhyph = detect_script_and_hyphens(term, lang, sc)
thyph = "([" .. thyph .. "])"
if not affix_type then
if rfind(term, thyph .. " " .. thyph) then
affix_type = "circumfix"
else
local has_beginning_hyphen = rfind(term, "^" .. thyph)
local has_ending_hyphen = rfind(term, thyph .. "$")
if has_beginning_hyphen and has_ending_hyphen then
affix_type = "interfix"
elseif has_ending_hyphen then
affix_type = "prefix"
elseif has_beginning_hyphen then
affix_type = "suffix"
else
affix_type = "non-affix"
end
end
end
local link_term, display_term, lookup_term
if affix_type == "non-affix" then
link_term = term
display_term = term
lookup_term = term
else
display_term = reconstruct_term_per_hyphens(term, affix_type, scode, thyph, dhyph)
if do_affix_mapping then
link_term = lookup_affix_mapping(term, affix_type, lang, scode, thyph, lhyph, affix_id)
-- The return value of lookup_affix_mapping() may be an affix mapping with lookup hyphens if a mapping
-- was found, otherwise nil if a mapping was not found. We need to convert to display hyphens in
-- either case, but in the latter case we can reuse the display term, which has already been converted.
if link_term then
link_term = reconstruct_term_per_hyphens(link_term, affix_type, scode, thyph, dhyph)
else
link_term = display_term
end
else
link_term = display_term
end
if return_lookup_affix then
lookup_term = reconstruct_term_per_hyphens(term, affix_type, scode, thyph, lhyph)
else
lookup_term = display_term
end
end
link_term = reconstructed .. link_term
display_term = reconstructed .. display_term
lookup_term = reconstructed .. lookup_term
return affix_type, link_term, display_term, lookup_term
end
--[==[
Add a hyphen to a term in the appropriate place, based on the specified affix type, stripping off any existing hyphens
in that place. For example, if `affix_type` == {"prefix"}, we'll add a hyphen onto the end if it's not already there (or
is of the wrong type). Three values are returned: the link term, display term and lookup term. This function is a thin
wrapper around `parse_term_for_affixes`; see the comments above that function for more information. Note that this
function is exposed externally because it is called by [[Module:category tree/affixes and compounds]]; see the comment
in `parse_term_for_affixes` for more information.
]==]
function export.make_affix(term, lang, sc, affix_type, do_affix_mapping, return_lookup_affix, affix_id)
if not (affix_type == "prefix" or affix_type == "suffix" or affix_type == "circumfix" or affix_type == "infix" or
affix_type == "interfix" or affix_type == "non-affix") then
error("Internal error: Invalid affix type " .. (affix_type or "(nil)"))
end
local _, link_term, display_term, lookup_term = parse_term_for_affixes(term, lang, sc, affix_type,
do_affix_mapping, return_lookup_affix, affix_id)
return link_term, display_term, lookup_term
end
-----------------------------------------------------------------------------------------
-- Main entry points --
-----------------------------------------------------------------------------------------
--[==[
Core categorization logic for affixes. This is shared between show_affix(), show_compound_like() and
get_affix_categories_only(). Returns the categories array and other metadata needed for formatting.
]==]
local function generate_affix_categories(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
local text_sections, categories, borrowing_type =
process_etymology_type(data.type, data.surface_analysis or data.nocap, data.notext, #data.parts > 0)
data.borrowing_type = borrowing_type
-- Process each part
local whole_words = 0
local is_affix_or_compound = false
-- Canonicalize and generate links for all the parts first; then do categorization in a separate step, because when
-- processing the first part for categorization, we may access the second part and need it already canonicalized.
for i, part in ipairs_with_gaps(data.parts) do
part = part or {}
data.parts[i] = part
canonicalize_part(part, data.lang, data.sc)
-- Determine affix type and get link and display terms (see text at top of file). Store them in the part
-- (in fields that won't clash with fields used by full_link() in [[Module:links]] or link_term()), so they
-- can be used in the loop below when categorizing.
part.affix_type, part.affix_link_term, part.affix_display_term = parse_term_for_affixes(part.term,
part.lang, part.sc, part.type, not part.alt, nil, part.id)
-- If link_term is an empty string, either a bare ^ was specified or an empty term was used along with inline
-- modifiers. The intention in either case is not to link the term.
part.term = ine(part.affix_link_term)
-- If part.alt would be the same as part.term, make it nil, so that it isn't erroneously tracked as being
-- redundant alt text.
part.alt = part.alt or (part.affix_display_term ~= part.affix_link_term and part.affix_display_term) or nil
end
-- Now do categorization.
for i, part in ipairs_with_gaps(data.parts) do
local affix_type = part.affix_type
if affix_type ~= "non-affix" then
is_affix_or_compound = true
-- Make a sort key. For the first part, use the second part as the sort key; the intention is that if the
-- term has a prefix, sorting by the prefix won't be very useful so we sort by what follows, which is
-- presumably the root.
local part_sort_base = nil
local part_sort = part.sort or data.sort_key
if i == 1 and data.parts[2] and data.parts[2].term then
local part2 = data.parts[2]
-- If the second-part link term is empty, the user requested an unlinked term; avoid a wikitext error
-- by using the alt value if available.
part_sort_base = ine(part2.affix_link_term) or ine(part2.alt)
if part_sort_base then
part_sort_base = strip_diacritics_no_links(part2.lang, part_sort_base)
end
end
if part.pos and rfind(part.pos, "父名") then
table.insert(categories, {cat = "父名", sort_key = part_sort, sort_base = part_sort_base})
end
if data.pos ~= "詞" and part.pos and rfind(part.pos, "指小") then
table.insert(categories, {cat = "指小" .. data.pos, sort_key = part_sort,
sort_base = part_sort_base})
end
-- Don't add a '*fixed with' category if the link term is empty or is in a different language.
if ine(part.affix_link_term) and not part.part_lang then
table.insert(categories, {cat = "含有" .. chinese[affix_type] ..
strip_diacritics_no_links(part.lang, part.affix_link_term) ..
(part.id and " (" .. part.id .. ")" or "") .. "的" .. data.pos,
sort_key = part_sort, sort_base = part_sort_base})
end
else
whole_words = whole_words + 1
if whole_words == 2 then
is_affix_or_compound = true
table.insert(categories, "複合" .. data.pos)
end
end
end
-- Make sure there was either an affix or a compound (two or more non-affix terms).
if not is_affix_or_compound and not data.allow_no_affixes_or_compounds then
error("The parameters did not include any affixes, and the term is not a compound. Please provide at least one affix.")
end
return text_sections, categories, borrowing_type
end
--[==[
Implementation of {{tl|affix}} and {{tl|surface analysis}}. `data` contains all the information describing the affixes to
be displayed, and contains the following:
* `.lang` ('''required'''): Overall language object. Different from term-specific language objects (see `.parts` below).
* `.sc`: Overall script object (usually omitted). Different from term-specific script objects.
* `.parts` ('''required'''): List of objects describing the affixes to show. The general format of each object is as would
be passed to `full_link()`, except that the `.lang` field should be missing unless the term is of a language
different from the overall `.lang` value (in such a case, the language name is shown along with the term and
an additional "derived from" category is added). '''WARNING''': The data in `.parts` will be destructively
modified.
* `.pos`: Overall part of speech (used in categories, defaults to {"terms"}). Different from term-specific part of speech.
* `.sort_key`: Overall sort key. Normally omitted except e.g. in Japanese.
* `.type`: Type of compound, if the parts in `.parts` describe a compound. Strictly optional, and if supplied, the
compound type is displayed before the parts (normally capitalized, unless `.nocap` is given).
* `.nocap`: Don't capitalize the first letter of text displayed before the parts (relevant only if `.type` or
`.surface_analysis` is given).
* `.notext`: Don't display any text before the parts (relevant only if `.type` or `.surface_analysis` is given).
* `.nocat`: Disable all categorization.
* `.lit`: Overall literal definition. Different from term-specific literal definitions.
* `.force_cat`: Always display categories, even on userspace pages.
* `.surface_analysis`: Implement {{surface analysis}}; adds `By surface analysis, ` before the parts.
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.show_affix(data)
local text_sections, categories, borrowing_type = generate_affix_categories(data)
-- Process each part for display
local parts_formatted = {}
for i, part in ipairs_with_gaps(data.parts) do
-- Make a link for the part
table.insert(parts_formatted, export.link_term(part, data, "include_separator"))
end
if data.surface_analysis then
local text = glossary_link("字面分析") .. "等同於"
if not data.nocap then
text = ucfirst(text)
end
table.insert(text_sections, 1, text)
end
-- L10N:調整語序
local formatted_parts = export.join_formatted_parts { data = data, parts_formatted = parts_formatted,
categories = categories, separator_already_added = true }
if data.surface_analysis then
-- 字面分析:[文字][詞彙] (同英語版邏輯)
table.insert(text_sections, formatted_parts)
else
-- 一般詞源(如同化借詞):[詞彙][文字] (即:Parts + 的 + 同化借詞)
table.insert(text_sections, 1, formatted_parts)
end
return table.concat(text_sections)
end
--[==[
Get only the categories that would be generated by show_affix(), without any text output or formatting.
This is used by Module:etymon to get affix categorization.
Returns an array of category objects, where
each entry is either a string (simple category name) or a table with keys `cat`, `sort_key`,
and `sort_base` for more complex categorization.
`data` should have the same structure as passed to show_affix():
* `.lang` (required): Overall language object
* `.parts` (required): Array of affix part objects with `.term`, `.lang`, `.id`, etc.
* `.pos`: Part of speech (defaults to "terms")
* `.sort_key`: Overall sort key for categories
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.get_affix_categories_only(data)
local text_sections, categories, borrowing_type = generate_affix_categories(data)
return categories
end
function export.show_surface_analysis(data)
data.surface_analysis = true
return export.show_affix(data)
end
--[==[
Implementation of {{tl|compound}}.
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.show_compound(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
local text_sections, categories, borrowing_type =
process_etymology_type(data.type, data.nocap, data.notext, #data.parts > 0)
data.borrowing_type = borrowing_type
-- Process each part for display
local parts_formatted = {}
table.insert(categories, "複合" .. data.pos)
-- Make links out of all the parts
local whole_words = 0
for i, part in ipairs(data.parts) do
canonicalize_part(part, data.lang, data.sc)
-- Determine affix type and get link and display terms (see text at top of file).
local affix_type, link_term, display_term = parse_term_for_affixes(part.term, part.lang, part.sc,
part.type, not part.alt, nil, part.id)
-- If the term is an interfix or the type was explicitly given, recognize it as such (which means e.g. that we
-- will display the term without hyphens for East Asian languages). Otherwise, ignore the fact that it looks
-- like an affix and display as specified in the template (but pay attention to the detected affix type for
-- certain tracking purposes).
if affix_type == "interfix" or (part.type and part.type ~= "non-affix") then
-- If link_term is an empty string, either a bare ^ was specified or an empty term was used along with
-- inline modifiers. The intention in either case is not to link the term. Don't add a '*fixed with'
-- category in this case, or if the term is in a different language.
-- If part.alt would be the same as part.term, make it nil, so that it isn't erroneously tracked as being
-- redundant alt text.
if link_term and link_term ~= "" and not part.part_lang then
table.insert(categories, {cat = "含有間綴" .. strip_diacritics_no_links(part.lang,
link_term) .. "的" .. data.pos, sort_key = part.sort or data.sort_key})
end
part.term = link_term ~= "" and link_term or nil
part.alt = part.alt or (display_term ~= link_term and display_term) or nil
else
if affix_type ~= "non-affix" then
local langcode = data.lang:getCode()
-- If `data.lang` is an etymology-only language, track both using its code and its full parent's code.
track { affix_type, affix_type .. "/lang/" .. langcode }
local full_langcode = data.lang:getFullCode()
if langcode ~= full_langcode then
track(affix_type .. "/lang/" .. full_langcode)
end
else
whole_words = whole_words + 1
end
end
table.insert(parts_formatted, export.link_term(part, data, "include_separator"))
end
if whole_words == 1 then
track("one whole word")
elseif whole_words == 0 then
track("looks like confix")
end
table.insert(text_sections, export.join_formatted_parts { data = data, parts_formatted = parts_formatted,
categories = categories, separator_already_added = true })
return table.concat(text_sections)
end
--[==[
Implementation of {{tl|blend}}, {{tl|univerbation}} and similar "compound-like" templates.
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.show_compound_like(data)
data.allow_no_affixes_or_compounds = true
local text_sections, categories, borrowing_type = generate_affix_categories(data)
if data.cat then
table.insert(categories, data.cat)
end
-- Process each part for display
local parts_formatted = {}
for i, part in ipairs_with_gaps(data.parts) do
-- Make a link for the part
table.insert(parts_formatted, export.link_term(part, data, "include_separator"))
end
-- l10n: 注意語序,應為 parts + oftext (組成的) + text (混成詞)
table.insert(text_sections, export.join_formatted_parts { data = data, parts_formatted = parts_formatted,
categories = categories, separator_already_added = true })
if #data.parts > 0 and data.oftext then
table.insert(text_sections, " " .. data.oftext)
end
if data.text then
table.insert(text_sections, data.text)
end
return table.concat(text_sections)
end
--[==[
Make `part` (a structure holding information on an affix part) into an affix of type `affix_type`, and apply any
relevant affix mappings. For example, if the desired affix type is "suffix", this will (in general) add a hyphen onto
the beginning of the term, alt, tr and ts components of the part if not already present. The hyphen that's added is the
"display hyphen" (see above) and may be script-specific. (In the case of East Asian scripts, the display hyphen is an
empty string whereas the template hyphen is the regular hyphen, meaning that any regular hyphen at the beginning of the
part will be effectively removed.) `lang` and `sc` hold overall language and script objects.
Note that this also applies any language-specific affix mappings, so that e.g. if the language is Finnish and the user
specified [[-käs]] in the affix and didn't specify an `.alt` value, `part.term` will contain [[-kas]] and `part.alt` will
contain [[-käs]].
This function is used by the "legacy" templates ({{tl|prefix}}, {{tl|suffix}}, {{tl|confix}}, etc.) where the nature of
the affix is specified by the template itself rather than auto-determined from the affix, as is the case with
{{tl|affix}}.
'''WARNING''': This destructively modifies `part`.
]==]
local function make_part_into_affix(part, lang, sc, affix_type)
canonicalize_part(part, lang, sc)
local link_term, display_term = export.make_affix(part.term, part.lang, part.sc, affix_type, not part.alt, nil, part.id)
part.term = link_term
-- When we don't specify `do_affix_mapping` to make_affix(), link and display terms (first and second retvals of
-- make_affix()) are the same.
-- If part.alt would be the same as part.term, make it nil, so that it isn't erroneously tracked as being
-- redundant alt text.
part.alt = part.alt and export.make_affix(part.alt, part.lang, part.sc, affix_type) or (display_term ~= link_term and display_term) or nil
local Latn = require(scripts_module).getByCode("Latn")
part.tr = export.make_affix(part.tr, part.lang, Latn, affix_type)
part.ts = export.make_affix(part.ts, part.lang, Latn, affix_type)
end
local function track_wrong_affix_type(template, part, expected_affix_type)
if part and not part.type then
local affix_type = parse_term_for_affixes(part.term, part.lang, part.sc)
if affix_type ~= expected_affix_type then
local part_name = expected_affix_type or "base"
local langcode = part.lang:getCode()
local full_langcode = part.lang:getFullCode()
require("Module:debug/track") {
template,
template .. "/" .. part_name,
template .. "/" .. part_name .. "/" .. (affix_type or "none"),
template .. "/" .. part_name .. "/" .. (affix_type or "none") .. "/lang/" .. langcode
}
-- If `part.lang` is an etymology-only language, track both using its code and its full parent's code.
if full_langcode ~= langcode then
require("Module:debug/track")(
template .. "/" .. part_name .. "/" .. (affix_type or "none") .. "/lang/" .. full_langcode
)
end
end
end
end
local function insert_affix_category(categories, pos, affix_type, part, sort_key, sort_base)
-- Don't add a '*fixed with' category if the link term is empty or is in a different language.
if part.term and not part.part_lang then
local cat = "含有" .. chinese[affix_type] .. strip_diacritics_no_links(part.lang, part.term) ..
(part.id and " (" .. part.id .. ")" or "") .. "的" .. pos
if sort_key or sort_base then
table.insert(categories, {cat = cat, sort_key = sort_key, sort_base = sort_base})
else
table.insert(categories, cat)
end
end
end
--[==[
Implementation of {{tl|circumfix}}.
'''WARNING''': This destructively modifies both `data` and `.prefix`, `.base` and `.suffix`.
]==]
function export.show_circumfix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
make_part_into_affix(data.prefix, data.lang, data.sc, "prefix")
make_part_into_affix(data.suffix, data.lang, data.sc, "suffix")
track_wrong_affix_type("circumfix", data.prefix, "prefix")
track_wrong_affix_type("circumfix", data.base, nil)
track_wrong_affix_type("circumfix", data.suffix, "suffix")
-- Create circumfix term.
local circumfix = nil
if data.prefix.term and data.suffix.term then
circumfix = data.prefix.term .. " " .. data.suffix.term
data.prefix.alt = data.prefix.alt or data.prefix.term
data.suffix.alt = data.suffix.alt or data.suffix.term
data.prefix.term = circumfix
data.suffix.term = circumfix
end
-- Make links out of all the parts.
local parts_formatted = {}
local categories = {}
local sort_base
if data.base.term then
sort_base = strip_diacritics_no_links(data.base.lang, data.base.term)
end
table.insert(parts_formatted, export.link_term(data.prefix, data))
table.insert(parts_formatted, export.link_term(data.base, data))
table.insert(parts_formatted, export.link_term(data.suffix, data))
-- Insert the categories, but don't add a '*fixed with' category if the link term is in a different language.
if not data.prefix.part_lang then
table.insert(categories, {cat= "含有環綴" .. circumfix .. "的" .. strip_diacritics_no_links(data.prefix.lang,
data.pos), sort_key=data.sort_key, sort_base=sort_base})
end
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|confix}}.
'''WARNING''': This destructively modifies both `data` and `.prefix`, `.base` and `.suffix`.
]==]
function export.show_confix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
make_part_into_affix(data.prefix, data.lang, data.sc, "prefix")
make_part_into_affix(data.suffix, data.lang, data.sc, "suffix")
track_wrong_affix_type("confix", data.prefix, "prefix")
track_wrong_affix_type("confix", data.base, nil)
track_wrong_affix_type("confix", data.suffix, "suffix")
-- Make links out of all the parts.
local parts_formatted = {}
local prefix_sort_base
if data.base and data.base.term then
prefix_sort_base = strip_diacritics_no_links(data.base.lang, data.base.term)
elseif data.suffix.term then
prefix_sort_base = strip_diacritics_no_links(data.suffix.lang, data.suffix.term)
end
-- Insert the categories and parts.
local categories = {}
table.insert(parts_formatted, export.link_term(data.prefix, data))
insert_affix_category(categories, data.pos, "prefix", data.prefix, data.sort_key, prefix_sort_base)
if data.base then
table.insert(parts_formatted, export.link_term(data.base, data))
end
table.insert(parts_formatted, export.link_term(data.suffix, data))
-- FIXME, should we be specifying a sort base here?
insert_affix_category(categories, data.pos, "suffix", data.suffix)
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|infix}}.
'''WARNING''': This destructively modifies both `data` and `.base` and `.infix`.
]==]
function export.show_infix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
make_part_into_affix(data.infix, data.lang, data.sc, "infix")
track_wrong_affix_type("infix", data.base, nil)
track_wrong_affix_type("infix", data.infix, "infix")
-- Make links out of all the parts.
local parts_formatted = {}
local categories = {}
table.insert(parts_formatted, export.link_term(data.base, data))
table.insert(parts_formatted, export.link_term(data.infix, data))
-- Insert the categories.
-- FIXME, should we be specifying a sort base here?
insert_affix_category(categories, data.pos, "infix", data.infix)
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|prefix}}.
'''WARNING''': This destructively modifies both `data` and the structures within `.prefixes`, as well as `.base`.
]==]
function export.show_prefix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
for i, prefix in ipairs(data.prefixes) do
make_part_into_affix(prefix, data.lang, data.sc, "prefix")
end
for i, prefix in ipairs(data.prefixes) do
track_wrong_affix_type("prefix", prefix, "prefix")
end
track_wrong_affix_type("prefix", data.base, nil)
-- Make links out of all the parts.
local parts_formatted = {}
local first_sort_base = nil
local categories = {}
if data.prefixes[2] then
first_sort_base = ine(data.prefixes[2].term) or ine(data.prefixes[2].alt)
if first_sort_base then
first_sort_base = strip_diacritics_no_links(data.prefixes[2].lang, first_sort_base)
end
elseif data.base then
first_sort_base = ine(data.base.term) or ine(data.base.alt)
if first_sort_base then
first_sort_base = strip_diacritics_no_links(data.base.lang, first_sort_base)
end
end
for i, prefix in ipairs(data.prefixes) do
table.insert(parts_formatted, export.link_term(prefix, data))
insert_affix_category(categories, data.pos, "prefix", prefix, data.sort_key, i == 1 and first_sort_base or nil)
end
if data.base then
table.insert(parts_formatted, export.link_term(data.base, data))
else
table.insert(parts_formatted, "")
end
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|suffix}}.
'''WARNING''': This destructively modifies both `data` and the structures within `.suffixes`, as well as `.base`.
]==]
function export.show_suffix(data)
local categories = {}
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
for i, suffix in ipairs(data.suffixes) do
make_part_into_affix(suffix, data.lang, data.sc, "suffix")
end
track_wrong_affix_type("suffix", data.base, nil)
for i, suffix in ipairs(data.suffixes) do
track_wrong_affix_type("suffix", suffix, "suffix")
end
-- Make links out of all the parts.
local parts_formatted = {}
if data.base then
table.insert(parts_formatted, export.link_term(data.base, data))
else
table.insert(parts_formatted, "")
end
for i, suffix in ipairs(data.suffixes) do
table.insert(parts_formatted, export.link_term(suffix, data))
end
-- Insert the categories.
for i, suffix in ipairs(data.suffixes) do
-- FIXME, should we be specifying a sort base here?
insert_affix_category(categories, data.pos, "suffix", suffix)
if suffix.pos and rfind(suffix.pos, "patronym") then
table.insert(categories, "patronymics")
end
end
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
return export
b1tkdv3e3rt81l76oqeanobaghosc8p
9909182
9909035
2026-09-29T09:47:32Z
TongcyBot
83009
移除手動 show_decorations
9909182
Scribunto
text/plain
local export = {}
local debug_force_cat = false -- if set to true, always display categories even on userspace pages
local m_links = require("Module:links")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local decorations_module = "Module:decorations"
local en_utilities_module = "Module:en-utilities"
local etymology_module = "Module:etymology"
local scripts_module = "Module:scripts"
local utilities_module = "Module:utilities"
-- Export this so the category code in [[Module:category tree/etymology]] can access it.
export.affix_lang_data_module_prefix = "Module:affix/lang-data/"
local rsub = m_str_utils.gsub
local usub = m_str_utils.sub
local ulen = m_str_utils.len
local rfind = m_str_utils.find
local rmatch = m_str_utils.match
local pluralize = require(en_utilities_module).pluralize
local u = m_str_utils.char
local ucfirst = m_str_utils.ucfirst
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local chinese = {
prefix = "前綴",
suffix = "後綴",
infix = "中綴",
circumfix = "環綴",
interfix = "間綴",
transfix = "插綴",
}
function export.affix_variants(canonical, variants)
local mappings = {}
for _, variant in ipairs(variants) do
mappings[variant] = canonical
end
return mappings
end
function export.id_mapping(default, ids)
local mapping = { default = default }
if ids then
for id, target in pairs(ids) do
mapping[id] = target
end
end
return mapping
end
function export.id_mapping_with_affix_variants(base, id_variants)
local mappings = {}
for id, variants in pairs(id_variants) do
for _, variant in ipairs(variants) do
mappings[variant] = export.id_mapping(base, {[id] = base})
end
end
return mappings
end
function export.merge_tables(...)
local result = {}
for i = 1, select('#', ...) do
local t = select(i, ...)
if t then
for k, v in pairs(t) do
result[k] = v
end
end
end
return result
end
-- Export this so the category code in [[Module:category tree/etymology]] can access it.
export.langs_with_lang_specific_data = {
["az"] = true,
["fi"] = true,
["fr"] = true,
["izh"] = true,
["la"] = true,
["sah"] = true,
["tr"] = true,
["trk-pro"] = true,
}
local default_pos = "詞"
--[==[ intro:
===About different types of hyphens ("template", "display" and "lookup"):===
* The "template hyphen" is the per-script hyphen character that is used in template calls to indicate that a term is an
affix. This is always a single Unicode char, but there may be multiple possible hyphens for a given script. Normally
this is just the regular hyphen character "-", but for some non-Latin-script languages (currently only right-to-left
languages), it is different.
* The "display hyphen" is the string (which might be an empty string) that is added onto a term as displayed and linked,
to indicate that a term is an affix. Currently this is always either the same as the template hyphen or an empty
string, but the code below is written generally enough to handle arbitrary display hyphens. Specifically:
*# For East Asian languages, the display hyphen is always blank.
*# For Arabic-script languages, either tatweel (ـ) or ZWNJ (zero-width non-joiner) are allowed as template hyphens,
where ZWNJ is supported primarily for Farsi, because some suffixes have non-joining behavior. The display hyphen
corresponding to tatweel is also tatweel, but the display hyphen corresponding to ZWNJ is blank (tatweel is also
the default display hyphen, for calls to {{tl|prefix}}/{{tl|suffix}}/etc. that don't include an explicit hyphen).
* The "lookup hyphen" is the hyphen that is used when looking up language-specific affix mappings. (These mappings are
discussed in more detail below when discussing link affixes.) It depends only on the script of the affix in question.
Most scripts (including East Asian scripts) use a regular hyphen "-" as the lookup hyphen, but Hebrew and Arabic
have their own lookup hyphens (respectively maqqef and tatweel). Note that for Arabic in particular, there are
three possible template hyphens that are recognized (tatweel, ZWNJ and regular hyphen), but mappings must use tatweel.
===About different types of affixes ("template", "display", "link", "lookup" and "category"):===
* A "template affix" is an affix in its source form as it appears in a template call. Generally, a template affix has an
attached template hyphen (see above) to indicate that it is an affix and indicate what type of affix it is (prefix,
suffix, interfix or circumfix), but some of the older-style templates such as {{tl|suffix}}, {{tl|prefix}},
{{tl|confix}}, etc. have "positional" affixes where the presence of the affix in a certain position (e.g. the second
or third parameter) indicates that it is a certain type of affix, whether or not it has an attached template hyphen.
* A "display affix" is the corresponding affix as it is actually displayed to the user. The display affix may differ
from the template affix for various reasons:
*# The display affix may be specified explicitly using the {{para|alt<var>N</var>}} parameter, the `<alt:...>` inline
modifier or a piped link of the form e.g. `<nowiki>[[-kas|-käs]]</nowiki>` (here indicating that the affix should
display as `-käs` but be linked as `-kas`). Here, the template affix is arguably the entire piped link, while the
display affix is `-käs`.
*# Even in the absence of {{para|alt<var>N</var>}} parameters, `<alt:...>` inline modifiers and piped links, certain
languages have differences between the "template hyphen" specified in the template (which always needs to be
specified somehow or other in templates like {{tl|affix}}, to indicate that the term is an affix and what type of
affix it is) and the display hyphen (see above), with corresponding differences between template and display
affixes.
* A (regular) "link affix" is the affix that is linked to when the affix is shown to the user. The link affix is usually
the same as the display affix, but will differ in one of three circumstances:
*# The display and link affixes are explicitly made different using {{para|alt<var>N</var>}} parameters, `<alt:...>`
inline modifiers or piped links, as described above under "display affix".
*# For certain languages, certain affixes are mapped to canonical form using language-specific mappings. For example,
in Finnish, the adjective-forming suffix {{m|fi|-kas}} appears as {{m|fi|-käs}} after front vowels, but logically
both forms are the same suffix and should be linked and categorized the same. Similarly, in Latin, the negative and
intensive prefixes spelled {{m|la|in-}} (etymologically two distinct prefixes) appear variously as {{m|la|il-}},
{{m|la|im-}} or {{m|la|ir-}} before certain consonants. Mappings are supplied in [[Module:affix/lang-data/LANGCODE]]
to convert Finnish {{m|fi|-käs}} to {{m|fi|-kas}} for linking and categorization purposes. Note that the affixes in
the mappings use "lookup hyphens" to indicate the different types of affixes, which is usually the same as the
template hyphen but differs for Arabic scripts, because there are multiple possible template hyphens recognized but
only one lookup hyphen (tatweel). The form of the affix as used to look up in the mapping tables is called the
"lookup affix"; see below.
* A "stripped link affix" is a link affix that has been passed through the language's `stripDiacritics()` function, which
may strip certain diacritics: e.g. macrons in Latin and Old English (indicating length); acute and grave accents in
Russian and various other Slavic languages (indicating stress); vowel diacritics in most Arabic-script languages; and
also tatweel in some Arabic-script languages (currently, for example, Persian, Arabic and Urdu strip tatweel, but
Ottoman Turkish does not). Stripped link affixes are currently what are used in category names.
* A "lookup affix" is the form of the affix as it is looked up in the language-specific lookup mappings described above
under link affixes. There are actually two lookup stages:
*# First, the affix is looked up in a modified display form (specifically, the same as the display affix but using
lookup hyphens). Note that this lookup does not occur if an explicit display form is given using
{{para|alt<var>N</var>}} or an `<alt:...>` inline modifier, or if the template affix contains a piped or embedded
link.
*# If no entry is found, the affix is then looked up in a modified link form (specifically, the modified display
form passed through the language's `stripDiacritics()` function, which strips out certain diacritics, but with the
lookup hyphen re-added if it was stripped out, as in the case of tatweel in many Arabic-script languages).
The reason for this double lookup procedure is to allow for mappings that are sensitive to the extra diacritics, but
also allow for mappings that are not sensitive in this fashion (e.g. Russian {{m|ru|-ливый}} occurs both stressed and
unstressed, but is the same prefix either way).
* A "category affix" is the affix as it appears in categories such as [[:Category:Finnish terms suffixed with -kas|
Category:Finnish terms suffixed with ''-kas'']]. The category affix is currently always the same as the stripped link
affix. This means that for Arabic-script languages, it may or may not have a tatweel, even if the correponding display
affix and regular link affix have a tatweel. As mentioned above, stripDiacritics() strips tatweel for Arabic, Persian
and Urdu, but not for Ottoman Turkish. Hence affix categories for Arabic, Persian and Urdu will be missing the
tatweel, but affix categories for Ottoman Turkish will have it. An additional complication is that if the template
affix contains a ZWNJ, the display (and hence the link and category affixes) will have no hyphen attached in any case.
]==]
-----------------------------------------------------------------------------------------
-- Template and display hyphens --
-----------------------------------------------------------------------------------------
--[=[
Per-script template hyphens. The template hyphen is what appears in the {{affix}}/{{prefix}}/{{suffix}}/etc. template
(in the wikicode). See above.
They key below is a script code, after removing a hyphen and anything preceding. Hence, script codes like 'fa-Arab'
and 'ur-Arab' will match 'Arab'.
The value below is a string consisting of one or more hyphen characters. If there is more than one character, the
default hyphen must come last and a non-default function must be specified for the script in display_hyphens[] so
the correct display hyphen will be specified when no template hyphen is given (in {{suffix}}/{{prefix}}/etc.).
Script detection is normally done when linking, but we need to do it earlier. However, under most circumstances we
don't need to do script detection. Specifically, we only need to do script detection for a given language if
(a) the language has multiple scripts; and
(b) at least one of those scripts is listed below or in display_hyphens.
]=]
local ZWNJ = u(0x200C) -- zero-width non-joiner
local template_hyphens = {
-- This covers all Arabic scripts. See above.
["Arab"] = "ـ" .. ZWNJ .. "-", -- tatweel + zero-width non-joiner + regular hyphen
["Hebr"] = "־", -- Hebrew-specific hyphen termed "maqqef"
["Mong"] = "᠊",
-- FIXME! What about the following right-to-left scripts?
-- Adlm (Adlam)
-- Armi (Imperial Aramaic)
-- Avst (Avestan)
-- Cprt (Cypriot)
-- Khar (Kharoshthi)
-- Mand (Mandaic/Mandaean)
-- Mani (Manichaean)
-- Mend (Mende/Mende Kikakui)
-- Narb (Old North Arabian)
-- Nbat (Nabataean/Nabatean)
-- Nkoo (N'Ko)
-- Orkh (Orkhon runes)
-- Phli (Inscriptional Pahlavi)
-- Phlp (Psalter Pahlavi)
-- Phlv (Book Pahlavi)
-- Phnx (Phoenician)
-- Prti (Inscriptional Parthian)
-- Rohg (Hanifi Rohingya)
-- Samr (Samaritan)
-- Sarb (Old South Arabian)
-- Sogd (Sogdian)
-- Sogo (Old Sogdian)
-- Syrc (Syriac)
-- Thaa (Thaana)
}
-- Hyphens used when looking up an affix in a lang-specific affix mapping. Defaults to regular hyphen (-). The keys
-- are script codes, after removing a hyphen and anything preceding. Hence, script codes like 'fa-Arab' and 'ur-Arab'
-- will match 'Arab'. The value should be a single character.
local lookup_hyphens = {
["Hebr"] = "־",
-- This covers all Arabic scripts. See above.
["Arab"] = "ـ",
}
-- Default display-hyphen function.
local function default_display_hyphen(script, hyph)
if not hyph then
return template_hyphens[script] or "-"
end
return hyph
end
local function arab_get_display_hyphen(script, hyph)
if not hyph then
return "ـ" -- tatweel
elseif hyph == ZWNJ then
return ""
else
return hyph
end
end
local function no_display_hyphen(script, hyph)
return ""
end
-- Per-script function to return the correct display hyphen given the script and template hyphen. The function should
-- also handle the case where the passed-in template hyphen is nil, corresponding to the situation in
-- {{prefix}}/{{suffix}}/etc. where no template hyphen is specified. The key is the script code after removing a hyphen
-- and anything preceding, so 'fa-Arab', 'ur-Arab' etc. will match 'Arab'.
local display_hyphens = {
-- This covers all Arabic scripts. See above.
["Arab"] = arab_get_display_hyphen,
["Bopo"] = no_display_hyphen,
["Hani"] = no_display_hyphen,
["Hans"] = no_display_hyphen,
["Hant"] = no_display_hyphen,
-- The following is a mixture of several scripts. Hopefully the specs here are correct!
["Jpan"] = no_display_hyphen,
["Jurc"] = no_display_hyphen,
["Kitl"] = no_display_hyphen,
["Kits"] = no_display_hyphen,
["Laoo"] = no_display_hyphen,
["Nshu"] = no_display_hyphen,
["Shui"] = no_display_hyphen,
["Tang"] = no_display_hyphen,
["Thaa"] = no_display_hyphen,
["Thai"] = no_display_hyphen,
["Tibt"] = no_display_hyphen,
}
-----------------------------------------------------------------------------------------
-- Basic Utility functions --
-----------------------------------------------------------------------------------------
local function glossary_link(entry, text)
text = text or entry
return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end
local function track(page)
if type(page) == "table" then
for i, pg in ipairs(page) do
page[i] = "affix/" .. pg
end
else
page = "affix/" .. page
end
require("Module:debug/track")(page)
end
local function ine(val)
return val ~= "" and val or nil
end
-----------------------------------------------------------------------------------------
-- Compound types --
-----------------------------------------------------------------------------------------
local function make_compound_type(typ, alttext)
return {
text = glossary_link(typ, alttext) .. "複合詞",
cat = typ .. "複合詞",
}
end
-- Make a compound type entry with a simple rather than glossary link.
-- These should be replaced with a glossary link when the entry in the glossary
-- is created.
local function make_non_glossary_compound_type(typ, alttext)
local link = alttext and "[[" .. typ .. "|" .. alttext .. "]]" or "[[" .. typ .. "]]"
return {
text = link .. "複合詞",
cat = typ .. "複合詞",
}
end
local function make_raw_compound_type(typ, alttext)
return {
text = glossary_link(typ, alttext),
cat = typ,
}
end
local function make_borrowing_type(typ, alttext)
return {
text = glossary_link(typ, alttext),
borrowing_type = typ
}
end
export.etymology_types = {
["adapted borrowing"] = make_borrowing_type("同化借詞"),
["adap"] = "adapted borrowing",
["abor"] = "adapted borrowing",
["alliterative"] = make_non_glossary_compound_type("alliterative"),
["allit"] = "alliterative",
["antonymous"] = make_non_glossary_compound_type("反義詞"),
["ant"] = "antonymous",
["bahuvrihi"] = make_compound_type("bahuvrihi", "bahuvrīhi"),
["bahu"] = "bahuvrihi",
["bv"] = "bahuvrihi",
["coordinative"] = make_compound_type("同類詞"),
["coord"] = "coordinative",
["descriptive"] = make_compound_type("descriptive"),
["desc"] = "descriptive",
["determinative"] = make_compound_type("限定詞"),
["det"] = "determinative",
["dvandva"] = make_compound_type("dvandva"),
["dva"] = "dvandva",
["dvigu"] = make_compound_type("dvigu"),
["dvi"] = "dvigu",
["endocentric"] = make_compound_type("向心詞"),
["endo"] = "endocentric",
["exocentric"] = make_compound_type("離心詞"),
["exo"] = "exocentric",
["izafet I"] = make_compound_type("izafet I"),
["iz1"] = "izafet I",
["izafet II"] = make_compound_type("izafet II"),
["iz2"] = "izafet II",
["izafet III"] = make_compound_type("izafet III"),
["iz3"] = "izafet III",
["karmadharaya"] = make_compound_type("karmadharaya", "karmadhāraya"),
["karma"] = "karmadharaya",
["kd"] = "karmadharaya",
["kenning"] = make_raw_compound_type("kenning"),
["ken"] = "kenning",
["rhyming"] = make_non_glossary_compound_type("押韻"),
["rhy"] = "rhyming",
["synonymous"] = make_non_glossary_compound_type("近義詞"),
["syn"] = "synonymous",
["tatpurusa"] = make_compound_type("tatpurusa", "tatpuruṣa"),
["tat"] = "tatpurusa",
["tp"] = "tatpurusa",
}
local function process_etymology_type(typ, nocap, notext, has_parts)
local text_sections = {}
local categories = {}
local borrowing_type
if typ then
local typdata = export.etymology_types[typ]
if type(typdata) == "string" then
typdata = export.etymology_types[typdata]
end
if not typdata then
error("Internal error: Unrecognized type '" .. typ .. "'")
end
local text = typdata.text
if not nocap then
text = ucfirst(text)
end
local cat = typdata.cat
borrowing_type = typdata.borrowing_type
local oftext = typdata.oftext or "的"
if borrowing_type and usub(borrowing_type, -1) == "詞" then -- L10N, see categories on [[absorbovať]]
borrowing_type = usub(borrowing_type, 1, -2)
end
if not notext then -- L10N 注意語序
if has_parts then
table.insert(text_sections, oftext)
end
table.insert(text_sections, text)
end
if cat then
table.insert(categories, cat)
end
end
return text_sections, categories, borrowing_type
end
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
-- Iterate an array up to the greatest integer index found.
local function ipairs_with_gaps(t)
local indices = m_table.numKeys(t)
local max_index = #indices > 0 and math.max(unpack(indices)) or 0
local i = 0
return function()
while i < max_index do
i = i + 1
return i, t[i]
end
end
end
export.ipairs_with_gaps = ipairs_with_gaps
-- l10n
function format_category_name(lang_name, category)
if category:find("的") then
local before, after = category:match("(.*)的(.*)$")
return before .. "的" .. lang_name .. after
else
return lang_name .. category
end
end
export.format_category_name = format_category_name
--[==[
Join formatted parts (in `parts_formatted`) together with any overall {{para|lit}} spec (in `lit`) plus categories,
which are formatted by prepending the language name as found in `lang`. The value of an entry in `categories` can be
either a string (which is formatted using `sort_key`) or a table of the form `{ {cat=<var>category</var>,
sort_key=<var>sort_key</var>, sort_base=<var>sort_base</var>}`, specifying the sort key and sort base to use when
formatting the category. If `nocat` is given, no categories are added; otherwise, `force_cat` causes categories to be
added even on userspace pages.
]==]
function export.join_formatted_parts(data)
local cattext
local lang = data.data.lang
local force_cat = data.data.force_cat or debug_force_cat
if data.data.nocat then
cattext = ""
else
for i, cat in ipairs(data.categories) do
if type(cat) == "table" then
local category_name = format_category_name(lang:getFullName(), cat.cat)
data.categories[i] = require(utilities_module).format_categories(category_name,
lang, cat.sort_key, cat.sort_base, force_cat)
else
local category_name = format_category_name(lang:getFullName(), cat)
data.categories[i] = require(utilities_module).format_categories(category_name, lang,
data.data.sort_key, nil, force_cat)
end
end
cattext = table.concat(data.categories)
end
local result = table.concat(data.parts_formatted, not data.separator_already_added and " +‎ " or nil) ..
(data.data.lit and ",字面意思為" .. m_links.mark(data.data.lit, "gloss") or "")
local q = data.data.q
local qq = data.data.qq
local l = data.data.l
local ll = data.data.ll
if q and q[1] or qq and qq[1] or l and l[1] or ll and ll[1] then
result = require(decorations_module).format_decorations {
lang = lang,
text = result,
q = q,
qq = qq,
l = l,
ll = ll,
}
end
return result .. cattext
end
local function pluralize(pos)
if pos ~= "nouns" and usub(pos, -5) ~= "verbs" and usub(pos, -4) ~= "ives" then
if pos:find("[sx]$") then
pos = pos .. "es"
else
pos = pos .. "s"
end
end
return pos
end
-- Remove links and call lang:stripDiacritics(term).
local function strip_diacritics_no_links(lang, term)
return lang:stripDiacritics(m_links.remove_links(term))
end
--[=[
Convert a raw part as passed into an entry point into a part ready for linking. `lang` and `sc` are the overall
language and script objects. This uses the overall language and script objects as defaults for the part and parses off
any fragment from the term. We need to do the latter so that fragments don't end up in categories and so that we
correctly do affix mapping even in the presence of fragments.
]=]
local function canonicalize_part(part, lang, sc)
if not part then
return
end
-- Save the original (user-specified, part-specific) value of `lang`. If such a value is specified, we don't insert
-- a '*fixed with' category, and we format the part using format_derived() in [[Module:etymology]] rather than
-- full_link() in [[Module:links]].
part.part_lang = part.lang
part.lang = part.lang or lang
part.sc = part.sc or sc
local term = part.term
if not term then
return
elseif not part.fragment then
part.term, part.fragment = m_links.get_fragment(term)
else
part.term = m_links.get_fragment(term)
end
end
--[==[
Construct a single linked part based on the information in `part`, for use by `show_affix()` and other entry points.
This should be called after `canonicalize_part()` is called on the part. This is a thin wrapper around `full_link()` in
[[Module:links]] unless `part.part_lang` is specified (indicating that a part-specific language was given), in which
case `format_derived()` in [[Module:etymology]] is called to display a term in a language other than the language of
the overall term (specified in `data.lang`). `data` contains the entire object passed into the entry point and is used
to access information for constructing the categories added by `format_derived()`.
]==]
function export.link_term(part, data, include_separator)
local result
if part.part_lang then
result = require(etymology_module).format_derived {
lang = data.lang,
terms = {part},
sources = {part.lang},
sort_key = data.sort_key,
nocat = data.nocat,
template_name = "affix",
decorations_on_outside = true,
borrowing_type = data.borrowing_type,
force_cat = data.force_cat or debug_force_cat,
}
else
result = m_links.full_link(part, "term")
end
if include_separator and part.separator then
return part.separator .. result
else
return result
end
end
local function canonicalize_script_code(scode)
-- Convert fa-Arab, ur-Arab etc. to Arab.
return (scode:gsub("^.*%-", ""))
end
-----------------------------------------------------------------------------------------
-- Affix-handling functions --
-----------------------------------------------------------------------------------------
-- Figure out the appropriate script for the given affix and language (unless the script is explicitly passed in), and
-- return the values of template_hyphens[], display_hyphens[] and lookup_hyphens[] for that script, substituting
-- default values as appropriate. Four values are returned:
-- DETECTED_SCRIPT, TEMPLATE_HYPHEN, DISPLAY_HYPHEN, LOOKUP_HYPHEN
local function detect_script_and_hyphens(text, lang, sc)
local scode
-- 1. If the script is explicitly passed in, use it.
if sc then
scode = sc:getCode()
else
local possible_script_codes = lang:getScriptCodes()
-- YUCK! `possible_script_codes` comes from loadData() so #possible_scripts doesn't work (always returns 0).
local num_possible_script_codes = m_table.length(possible_script_codes)
if num_possible_script_codes == 0 then
-- This shouldn't happen; if the language has no script codes,
-- the list {"None"} should be returned.
error("Something is majorly wrong! Language " .. lang:getCanonicalName() .. " has no script codes.")
end
if num_possible_script_codes == 1 then
-- 2. If the language has only one possible script, use it.
scode = possible_script_codes[1]
else
-- 3. Check if any of the possible scripts for the language have non-default values for template_hyphens[]
-- or display_hyphens[]. If so, we need to do script detection on the text. If not, just use "Latn",
-- which may not be technically correct but produces the right results because Latn has all default
-- values for template_hyphens[] and display_hyphens[].
local may_have_nondefault_hyphen = false
for _, script_code in ipairs(possible_script_codes) do
script_code = canonicalize_script_code(script_code)
if template_hyphens[script_code] or display_hyphens[script_code] then
may_have_nondefault_hyphen = true
break
end
end
if not may_have_nondefault_hyphen then
scode = "Latn"
else
scode = lang:findBestScript(text):getCode()
end
end
end
scode = canonicalize_script_code(scode)
local template_hyphen = template_hyphens[scode] or "-"
local lookup_hyphen = lookup_hyphens[scode] or "-"
local display_hyphen = display_hyphens[scode] or default_display_hyphen
return scode, template_hyphen, display_hyphen, lookup_hyphen
end
--[=[
Given a template affix `term` and an affix type `affix_type`, change the relevant template hyphen(s) in the affix to
the display or lookup hyphen specified in `new_hyphen`, or add them if they are missing. `new_hyphen` can be a string,
specifying a fixed hyphen, or a function of two arguments (the script code `scode` and the discovered template hyphen,
or nil of no relevant template hyphen is present). `thyph_re` is a Lua pattern (which must be enclosed in parens) that
matches the possible template hyphens. Note that not all template hyphens present in the affix are changed, but only
the "relevant" ones (e.g. for a prefix, a relevant template hyphen is one coming at the end of the affix).
]=]
local function reconstruct_term_per_hyphens(term, affix_type, scode, thyph_re, new_hyphen)
local function get_hyphen(hyph)
if type(new_hyphen) == "string" then
return new_hyphen
end
return new_hyphen(scode, hyph)
end
if affix_type == "non-affix" then
return term
elseif affix_type == "circumfix" then
local before, before_hyphen, after_hyphen, after = rmatch(term, "^(.*)" .. thyph_re .. " " .. thyph_re
.. "(.*)$")
if not before or ulen(term) <= 3 then
-- Unlike with other types of affixes, don't try to add hyphens in the middle of the term to convert it to
-- a circumfix. Also, if the term is just hyphen + space + hyphen, return it.
return term
end
return before .. get_hyphen(before_hyphen) .. " " .. get_hyphen(after_hyphen) .. after
elseif affix_type == "infix" or affix_type == "interfix" then
local before_hyphen, middle, after_hyphen = rmatch(term, "^" .. thyph_re .. "(.*)" .. thyph_re .. "$")
if before_hyphen and ulen(term) <= 1 then
-- If the term is just a hyphen, return it.
return term
end
return get_hyphen(before_hyphen) .. (middle or term) .. get_hyphen(after_hyphen)
elseif affix_type == "prefix" then
local middle, after_hyphen = rmatch(term, "^(.*)" .. thyph_re .. "$")
if middle and ulen(term) <= 1 then
-- If the term is just a hyphen, return it.
return term
end
return (middle or term) .. get_hyphen(after_hyphen)
elseif affix_type == "suffix" then
local before_hyphen, middle = rmatch(term, "^" .. thyph_re .. "(.*)$")
if before_hyphen and ulen(term) <= 1 then
-- If the term is just a hyphen, return it.
return term
end
return get_hyphen(before_hyphen) .. (middle or term)
else
error(("Internal error: Unrecognized affix type '%s'"):format(affix_type))
end
end
--[=[
Look up a mapping from a given affix variant to the canonical form used in categories and links. The lookup tables are
language-specific according to `lang`, and may be ID-specific according to `affix_id`. The affixes as they appear in the
lookup tables (both the variant and the canonical form) are in "lookup affix" format (approximately speaking, they use a
regular hyphen for most scripts, but a tatweel for Arabic-script entries and a maqqef for Hebrew-script entries), but
the passed-in `affix` param is in "template affix" format (which differs from the lookup affix for Arabic-script
entries, because more types of hyphens are allowed in template affixes; see the comments at the top of the file). The
remaining parameters to this function are used to convert from template affixes to lookup affixes; see the
reconstruct_term_per_hyphens() function above.
If the affix contains brackets, no lookup is done. Otherwise, a two-stage process is used, first looking up the affix
directly and then stripping diacritics and looking it up again. The reason for this is documented above in the comments
at the top of the file (specifically, the comments describing lookup affixes).
The value of a mapping can either be a string (do the mapping regardless of affix ID) or a table indexed by affix ID
(where the special value `false` indicates no affix ID). The values of entries in this table can also be strings, or
tables with keys `affix` and `id` (again, use `false` to indicate no ID). This allows an affix mapping to map from one
ID to another (for example, this is used in English to map the [[an-]] prefix with no ID to the [[a-]] prefix with the
ID 'not').
The Given a template affix `term` and an affix type `affix_type`, change the relevant template hyphen(s) in the affix to
the display or lookup hyphen specified in `new_hyphen`, or add them if they are missing. `new_hyphen` can be a string,
specifying a fixed hyphen, or a function of two arguments (the script code `scode` and the discovered template hyphen,
or nil of no relevant template hyphen is present). `thyph_re` is a Lua pattern (which must be enclosed in parens) that
matches the possible template hyphens. Note that not all template hyphens present in the affix are changed, but only
the "relevant" ones (e.g. for a prefix, a relevant template hyphen is one coming at the end of the affix).
]=]
local function lookup_affix_mapping(affix, affix_type, lang, scode, thyph_re, lookup_hyph, affix_id)
local function do_lookup(affix)
-- Ensure that the affix uses lookup hyphens regardless of whether it used a different type of hyphens before
-- or no hyphens.
local lookup_affix = reconstruct_term_per_hyphens(affix, affix_type, scode, thyph_re, lookup_hyph)
local function do_lookup_for_langcode(langcode)
if export.langs_with_lang_specific_data[langcode] then
local langdata = mw.loadData(export.affix_lang_data_module_prefix .. langcode)
if langdata.affix_mappings then
local mapping = langdata.affix_mappings[lookup_affix]
if mapping then
if type(mapping) == "table" then
mapping = mapping[affix_id] or mapping.default or mapping[affix_id or false]
if mapping then
return mapping
end
else
return mapping
end
end
end
end
end
-- If `lang` is an etymology-only language, look for a mapping both for it and its full parent.
local langcode = lang:getCode()
local mapping = do_lookup_for_langcode(langcode)
if mapping then
return mapping
end
local full_langcode = lang:getFullCode()
if full_langcode ~= langcode then
mapping = do_lookup_for_langcode(full_langcode)
if mapping then
return mapping
end
end
return nil
end
if affix:find("%[%[") then
return nil
end
return do_lookup(affix) or do_lookup(lang:stripDiacritics(affix)) or nil
end
--[==[
For a given template term in a given language (see the definition of "template affix" near the top of the file),
possibly in an explicitly specified script `sc` (but usually nil), return the term's affix type ({"prefix"},
{"interfix"}, {"suffix"}, {"circumfix"} or {"non-affix"}) along with the corresponding link and display affixes
(see definitions near the top of the file); also the corresponding lookup affix (if `return_lookup_affix` is specified).
The term passed in should already have any fragment (after the # sign) parsed off of it. Four values are returned:
`affix_type`, `link_term`, `display_term` and `lookup_term`. The affix type can be passed in instead of autodetected; in
this case, the template term need not have any attached hyphens, and the appropriate hyphens will be added in the
appropriate places. If `do_affix_mapping` is specified, look up the affix in the lang-specific affix mappings, as
described in the comment at the top of the file; otherwise, the link and display terms will always be the same. (They
will be the same in any case if the template term has a bracketed link in it or is not an affix.) If
`return_lookup_affix` is given, the fourth return value contains the term with appropriate lookup hyphens in the
appropriate places; otherwise, it is the same as the display term. (This functionality is used in
[[Module:category tree/affixes and compounds]] to convert link affixes into lookup affixes so that they can be looked up
in the affix mapping tables.)
]==]
local function parse_term_for_affixes(term, lang, sc, affix_type, do_affix_mapping, return_lookup_affix, affix_id)
if not term then
return "non-affix", nil, nil, nil
end
if term == "^" then
-- Indicates a null term to emulate the behavior of {{suffix|foo||bar}}.
term = ""
return "non-affix", term, term, term
end
if term:find("^%^") then
-- HACK! ^ at the beginning of Korean languages has a special meaning, triggering capitalization of the
-- transliteration. Don't interpret it as "force non-affix" for those languages.
local langcode = lang:getCode()
if langcode ~= "ko" and langcode ~= "okm" and langcode ~= "jje" then
-- Formerly we allowed ^ to force non-affix type; this is now handled using an inline modifier
-- <naf>, <root>, etc. Throw an error for the moment when the old way is encountered.
error("Use of ^ to force non-affix status is no longer supported; use an inline modifier <naf> or <root> " ..
"after the component")
end
end
-- Remove an asterisk if the morpheme is reconstructed and add it back at the end.
local reconstructed = ""
if term:find("^%*") then
reconstructed = "*"
term = term:gsub("^%*", "")
end
local scode, thyph, dhyph, lhyph = detect_script_and_hyphens(term, lang, sc)
thyph = "([" .. thyph .. "])"
if not affix_type then
if rfind(term, thyph .. " " .. thyph) then
affix_type = "circumfix"
else
local has_beginning_hyphen = rfind(term, "^" .. thyph)
local has_ending_hyphen = rfind(term, thyph .. "$")
if has_beginning_hyphen and has_ending_hyphen then
affix_type = "interfix"
elseif has_ending_hyphen then
affix_type = "prefix"
elseif has_beginning_hyphen then
affix_type = "suffix"
else
affix_type = "non-affix"
end
end
end
local link_term, display_term, lookup_term
if affix_type == "non-affix" then
link_term = term
display_term = term
lookup_term = term
else
display_term = reconstruct_term_per_hyphens(term, affix_type, scode, thyph, dhyph)
if do_affix_mapping then
link_term = lookup_affix_mapping(term, affix_type, lang, scode, thyph, lhyph, affix_id)
-- The return value of lookup_affix_mapping() may be an affix mapping with lookup hyphens if a mapping
-- was found, otherwise nil if a mapping was not found. We need to convert to display hyphens in
-- either case, but in the latter case we can reuse the display term, which has already been converted.
if link_term then
link_term = reconstruct_term_per_hyphens(link_term, affix_type, scode, thyph, dhyph)
else
link_term = display_term
end
else
link_term = display_term
end
if return_lookup_affix then
lookup_term = reconstruct_term_per_hyphens(term, affix_type, scode, thyph, lhyph)
else
lookup_term = display_term
end
end
link_term = reconstructed .. link_term
display_term = reconstructed .. display_term
lookup_term = reconstructed .. lookup_term
return affix_type, link_term, display_term, lookup_term
end
--[==[
Add a hyphen to a term in the appropriate place, based on the specified affix type, stripping off any existing hyphens
in that place. For example, if `affix_type` == {"prefix"}, we'll add a hyphen onto the end if it's not already there (or
is of the wrong type). Three values are returned: the link term, display term and lookup term. This function is a thin
wrapper around `parse_term_for_affixes`; see the comments above that function for more information. Note that this
function is exposed externally because it is called by [[Module:category tree/affixes and compounds]]; see the comment
in `parse_term_for_affixes` for more information.
]==]
function export.make_affix(term, lang, sc, affix_type, do_affix_mapping, return_lookup_affix, affix_id)
if not (affix_type == "prefix" or affix_type == "suffix" or affix_type == "circumfix" or affix_type == "infix" or
affix_type == "interfix" or affix_type == "non-affix") then
error("Internal error: Invalid affix type " .. (affix_type or "(nil)"))
end
local _, link_term, display_term, lookup_term = parse_term_for_affixes(term, lang, sc, affix_type,
do_affix_mapping, return_lookup_affix, affix_id)
return link_term, display_term, lookup_term
end
-----------------------------------------------------------------------------------------
-- Main entry points --
-----------------------------------------------------------------------------------------
--[==[
Core categorization logic for affixes. This is shared between show_affix(), show_compound_like() and
get_affix_categories_only(). Returns the categories array and other metadata needed for formatting.
]==]
local function generate_affix_categories(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
local text_sections, categories, borrowing_type =
process_etymology_type(data.type, data.surface_analysis or data.nocap, data.notext, #data.parts > 0)
data.borrowing_type = borrowing_type
-- Process each part
local whole_words = 0
local is_affix_or_compound = false
-- Canonicalize and generate links for all the parts first; then do categorization in a separate step, because when
-- processing the first part for categorization, we may access the second part and need it already canonicalized.
for i, part in ipairs_with_gaps(data.parts) do
part = part or {}
data.parts[i] = part
canonicalize_part(part, data.lang, data.sc)
-- Determine affix type and get link and display terms (see text at top of file). Store them in the part
-- (in fields that won't clash with fields used by full_link() in [[Module:links]] or link_term()), so they
-- can be used in the loop below when categorizing.
part.affix_type, part.affix_link_term, part.affix_display_term = parse_term_for_affixes(part.term,
part.lang, part.sc, part.type, not part.alt, nil, part.id)
-- If link_term is an empty string, either a bare ^ was specified or an empty term was used along with inline
-- modifiers. The intention in either case is not to link the term.
part.term = ine(part.affix_link_term)
-- If part.alt would be the same as part.term, make it nil, so that it isn't erroneously tracked as being
-- redundant alt text.
part.alt = part.alt or (part.affix_display_term ~= part.affix_link_term and part.affix_display_term) or nil
end
-- Now do categorization.
for i, part in ipairs_with_gaps(data.parts) do
local affix_type = part.affix_type
if affix_type ~= "non-affix" then
is_affix_or_compound = true
-- Make a sort key. For the first part, use the second part as the sort key; the intention is that if the
-- term has a prefix, sorting by the prefix won't be very useful so we sort by what follows, which is
-- presumably the root.
local part_sort_base = nil
local part_sort = part.sort or data.sort_key
if i == 1 and data.parts[2] and data.parts[2].term then
local part2 = data.parts[2]
-- If the second-part link term is empty, the user requested an unlinked term; avoid a wikitext error
-- by using the alt value if available.
part_sort_base = ine(part2.affix_link_term) or ine(part2.alt)
if part_sort_base then
part_sort_base = strip_diacritics_no_links(part2.lang, part_sort_base)
end
end
if part.pos and rfind(part.pos, "父名") then
table.insert(categories, {cat = "父名", sort_key = part_sort, sort_base = part_sort_base})
end
if data.pos ~= "詞" and part.pos and rfind(part.pos, "指小") then
table.insert(categories, {cat = "指小" .. data.pos, sort_key = part_sort,
sort_base = part_sort_base})
end
-- Don't add a '*fixed with' category if the link term is empty or is in a different language.
if ine(part.affix_link_term) and not part.part_lang then
table.insert(categories, {cat = "含有" .. chinese[affix_type] ..
strip_diacritics_no_links(part.lang, part.affix_link_term) ..
(part.id and " (" .. part.id .. ")" or "") .. "的" .. data.pos,
sort_key = part_sort, sort_base = part_sort_base})
end
else
whole_words = whole_words + 1
if whole_words == 2 then
is_affix_or_compound = true
table.insert(categories, "複合" .. data.pos)
end
end
end
-- Make sure there was either an affix or a compound (two or more non-affix terms).
if not is_affix_or_compound and not data.allow_no_affixes_or_compounds then
error("The parameters did not include any affixes, and the term is not a compound. Please provide at least one affix.")
end
return text_sections, categories, borrowing_type
end
--[==[
Implementation of {{tl|affix}} and {{tl|surface analysis}}. `data` contains all the information describing the affixes to
be displayed, and contains the following:
* `.lang` ('''required'''): Overall language object. Different from term-specific language objects (see `.parts` below).
* `.sc`: Overall script object (usually omitted). Different from term-specific script objects.
* `.parts` ('''required'''): List of objects describing the affixes to show. The general format of each object is as would
be passed to `full_link()`, except that the `.lang` field should be missing unless the term is of a language
different from the overall `.lang` value (in such a case, the language name is shown along with the term and
an additional "derived from" category is added). '''WARNING''': The data in `.parts` will be destructively
modified.
* `.pos`: Overall part of speech (used in categories, defaults to {"terms"}). Different from term-specific part of speech.
* `.sort_key`: Overall sort key. Normally omitted except e.g. in Japanese.
* `.type`: Type of compound, if the parts in `.parts` describe a compound. Strictly optional, and if supplied, the
compound type is displayed before the parts (normally capitalized, unless `.nocap` is given).
* `.nocap`: Don't capitalize the first letter of text displayed before the parts (relevant only if `.type` or
`.surface_analysis` is given).
* `.notext`: Don't display any text before the parts (relevant only if `.type` or `.surface_analysis` is given).
* `.nocat`: Disable all categorization.
* `.lit`: Overall literal definition. Different from term-specific literal definitions.
* `.force_cat`: Always display categories, even on userspace pages.
* `.surface_analysis`: Implement {{surface analysis}}; adds `By surface analysis, ` before the parts.
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.show_affix(data)
local text_sections, categories, borrowing_type = generate_affix_categories(data)
-- Process each part for display
local parts_formatted = {}
for i, part in ipairs_with_gaps(data.parts) do
-- Make a link for the part
table.insert(parts_formatted, export.link_term(part, data, "include_separator"))
end
if data.surface_analysis then
local text = glossary_link("字面分析") .. "等同於"
if not data.nocap then
text = ucfirst(text)
end
table.insert(text_sections, 1, text)
end
-- L10N:調整語序
local formatted_parts = export.join_formatted_parts { data = data, parts_formatted = parts_formatted,
categories = categories, separator_already_added = true }
if data.surface_analysis then
-- 字面分析:[文字][詞彙] (同英語版邏輯)
table.insert(text_sections, formatted_parts)
else
-- 一般詞源(如同化借詞):[詞彙][文字] (即:Parts + 的 + 同化借詞)
table.insert(text_sections, 1, formatted_parts)
end
return table.concat(text_sections)
end
--[==[
Get only the categories that would be generated by show_affix(), without any text output or formatting.
This is used by Module:etymon to get affix categorization.
Returns an array of category objects, where
each entry is either a string (simple category name) or a table with keys `cat`, `sort_key`,
and `sort_base` for more complex categorization.
`data` should have the same structure as passed to show_affix():
* `.lang` (required): Overall language object
* `.parts` (required): Array of affix part objects with `.term`, `.lang`, `.id`, etc.
* `.pos`: Part of speech (defaults to "terms")
* `.sort_key`: Overall sort key for categories
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.get_affix_categories_only(data)
local text_sections, categories, borrowing_type = generate_affix_categories(data)
return categories
end
function export.show_surface_analysis(data)
data.surface_analysis = true
return export.show_affix(data)
end
--[==[
Implementation of {{tl|compound}}.
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.show_compound(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
local text_sections, categories, borrowing_type =
process_etymology_type(data.type, data.nocap, data.notext, #data.parts > 0)
data.borrowing_type = borrowing_type
-- Process each part for display
local parts_formatted = {}
table.insert(categories, "複合" .. data.pos)
-- Make links out of all the parts
local whole_words = 0
for i, part in ipairs(data.parts) do
canonicalize_part(part, data.lang, data.sc)
-- Determine affix type and get link and display terms (see text at top of file).
local affix_type, link_term, display_term = parse_term_for_affixes(part.term, part.lang, part.sc,
part.type, not part.alt, nil, part.id)
-- If the term is an interfix or the type was explicitly given, recognize it as such (which means e.g. that we
-- will display the term without hyphens for East Asian languages). Otherwise, ignore the fact that it looks
-- like an affix and display as specified in the template (but pay attention to the detected affix type for
-- certain tracking purposes).
if affix_type == "interfix" or (part.type and part.type ~= "non-affix") then
-- If link_term is an empty string, either a bare ^ was specified or an empty term was used along with
-- inline modifiers. The intention in either case is not to link the term. Don't add a '*fixed with'
-- category in this case, or if the term is in a different language.
-- If part.alt would be the same as part.term, make it nil, so that it isn't erroneously tracked as being
-- redundant alt text.
if link_term and link_term ~= "" and not part.part_lang then
table.insert(categories, {cat = "含有間綴" .. strip_diacritics_no_links(part.lang,
link_term) .. "的" .. data.pos, sort_key = part.sort or data.sort_key})
end
part.term = link_term ~= "" and link_term or nil
part.alt = part.alt or (display_term ~= link_term and display_term) or nil
else
if affix_type ~= "non-affix" then
local langcode = data.lang:getCode()
-- If `data.lang` is an etymology-only language, track both using its code and its full parent's code.
track { affix_type, affix_type .. "/lang/" .. langcode }
local full_langcode = data.lang:getFullCode()
if langcode ~= full_langcode then
track(affix_type .. "/lang/" .. full_langcode)
end
else
whole_words = whole_words + 1
end
end
table.insert(parts_formatted, export.link_term(part, data, "include_separator"))
end
if whole_words == 1 then
track("one whole word")
elseif whole_words == 0 then
track("looks like confix")
end
table.insert(text_sections, export.join_formatted_parts { data = data, parts_formatted = parts_formatted,
categories = categories, separator_already_added = true })
return table.concat(text_sections)
end
--[==[
Implementation of {{tl|blend}}, {{tl|univerbation}} and similar "compound-like" templates.
'''WARNING''': This destructively modifies both `data` and the individual structures within `.parts`.
]==]
function export.show_compound_like(data)
data.allow_no_affixes_or_compounds = true
local text_sections, categories, borrowing_type = generate_affix_categories(data)
if data.cat then
table.insert(categories, data.cat)
end
-- Process each part for display
local parts_formatted = {}
for i, part in ipairs_with_gaps(data.parts) do
-- Make a link for the part
table.insert(parts_formatted, export.link_term(part, data, "include_separator"))
end
-- l10n: 注意語序,應為 parts + oftext (組成的) + text (混成詞)
table.insert(text_sections, export.join_formatted_parts { data = data, parts_formatted = parts_formatted,
categories = categories, separator_already_added = true })
if #data.parts > 0 and data.oftext then
table.insert(text_sections, " " .. data.oftext)
end
if data.text then
table.insert(text_sections, data.text)
end
return table.concat(text_sections)
end
--[==[
Make `part` (a structure holding information on an affix part) into an affix of type `affix_type`, and apply any
relevant affix mappings. For example, if the desired affix type is "suffix", this will (in general) add a hyphen onto
the beginning of the term, alt, tr and ts components of the part if not already present. The hyphen that's added is the
"display hyphen" (see above) and may be script-specific. (In the case of East Asian scripts, the display hyphen is an
empty string whereas the template hyphen is the regular hyphen, meaning that any regular hyphen at the beginning of the
part will be effectively removed.) `lang` and `sc` hold overall language and script objects.
Note that this also applies any language-specific affix mappings, so that e.g. if the language is Finnish and the user
specified [[-käs]] in the affix and didn't specify an `.alt` value, `part.term` will contain [[-kas]] and `part.alt` will
contain [[-käs]].
This function is used by the "legacy" templates ({{tl|prefix}}, {{tl|suffix}}, {{tl|confix}}, etc.) where the nature of
the affix is specified by the template itself rather than auto-determined from the affix, as is the case with
{{tl|affix}}.
'''WARNING''': This destructively modifies `part`.
]==]
local function make_part_into_affix(part, lang, sc, affix_type)
canonicalize_part(part, lang, sc)
local link_term, display_term = export.make_affix(part.term, part.lang, part.sc, affix_type, not part.alt, nil, part.id)
part.term = link_term
-- When we don't specify `do_affix_mapping` to make_affix(), link and display terms (first and second retvals of
-- make_affix()) are the same.
-- If part.alt would be the same as part.term, make it nil, so that it isn't erroneously tracked as being
-- redundant alt text.
part.alt = part.alt and export.make_affix(part.alt, part.lang, part.sc, affix_type) or (display_term ~= link_term and display_term) or nil
local Latn = require(scripts_module).getByCode("Latn")
part.tr = export.make_affix(part.tr, part.lang, Latn, affix_type)
part.ts = export.make_affix(part.ts, part.lang, Latn, affix_type)
end
local function track_wrong_affix_type(template, part, expected_affix_type)
if part and not part.type then
local affix_type = parse_term_for_affixes(part.term, part.lang, part.sc)
if affix_type ~= expected_affix_type then
local part_name = expected_affix_type or "base"
local langcode = part.lang:getCode()
local full_langcode = part.lang:getFullCode()
require("Module:debug/track") {
template,
template .. "/" .. part_name,
template .. "/" .. part_name .. "/" .. (affix_type or "none"),
template .. "/" .. part_name .. "/" .. (affix_type or "none") .. "/lang/" .. langcode
}
-- If `part.lang` is an etymology-only language, track both using its code and its full parent's code.
if full_langcode ~= langcode then
require("Module:debug/track")(
template .. "/" .. part_name .. "/" .. (affix_type or "none") .. "/lang/" .. full_langcode
)
end
end
end
end
local function insert_affix_category(categories, pos, affix_type, part, sort_key, sort_base)
-- Don't add a '*fixed with' category if the link term is empty or is in a different language.
if part.term and not part.part_lang then
local cat = "含有" .. chinese[affix_type] .. strip_diacritics_no_links(part.lang, part.term) ..
(part.id and " (" .. part.id .. ")" or "") .. "的" .. pos
if sort_key or sort_base then
table.insert(categories, {cat = cat, sort_key = sort_key, sort_base = sort_base})
else
table.insert(categories, cat)
end
end
end
--[==[
Implementation of {{tl|circumfix}}.
'''WARNING''': This destructively modifies both `data` and `.prefix`, `.base` and `.suffix`.
]==]
function export.show_circumfix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
make_part_into_affix(data.prefix, data.lang, data.sc, "prefix")
make_part_into_affix(data.suffix, data.lang, data.sc, "suffix")
track_wrong_affix_type("circumfix", data.prefix, "prefix")
track_wrong_affix_type("circumfix", data.base, nil)
track_wrong_affix_type("circumfix", data.suffix, "suffix")
-- Create circumfix term.
local circumfix = nil
if data.prefix.term and data.suffix.term then
circumfix = data.prefix.term .. " " .. data.suffix.term
data.prefix.alt = data.prefix.alt or data.prefix.term
data.suffix.alt = data.suffix.alt or data.suffix.term
data.prefix.term = circumfix
data.suffix.term = circumfix
end
-- Make links out of all the parts.
local parts_formatted = {}
local categories = {}
local sort_base
if data.base.term then
sort_base = strip_diacritics_no_links(data.base.lang, data.base.term)
end
table.insert(parts_formatted, export.link_term(data.prefix, data))
table.insert(parts_formatted, export.link_term(data.base, data))
table.insert(parts_formatted, export.link_term(data.suffix, data))
-- Insert the categories, but don't add a '*fixed with' category if the link term is in a different language.
if not data.prefix.part_lang then
table.insert(categories, {cat= "含有環綴" .. circumfix .. "的" .. strip_diacritics_no_links(data.prefix.lang,
data.pos), sort_key=data.sort_key, sort_base=sort_base})
end
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|confix}}.
'''WARNING''': This destructively modifies both `data` and `.prefix`, `.base` and `.suffix`.
]==]
function export.show_confix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
make_part_into_affix(data.prefix, data.lang, data.sc, "prefix")
make_part_into_affix(data.suffix, data.lang, data.sc, "suffix")
track_wrong_affix_type("confix", data.prefix, "prefix")
track_wrong_affix_type("confix", data.base, nil)
track_wrong_affix_type("confix", data.suffix, "suffix")
-- Make links out of all the parts.
local parts_formatted = {}
local prefix_sort_base
if data.base and data.base.term then
prefix_sort_base = strip_diacritics_no_links(data.base.lang, data.base.term)
elseif data.suffix.term then
prefix_sort_base = strip_diacritics_no_links(data.suffix.lang, data.suffix.term)
end
-- Insert the categories and parts.
local categories = {}
table.insert(parts_formatted, export.link_term(data.prefix, data))
insert_affix_category(categories, data.pos, "prefix", data.prefix, data.sort_key, prefix_sort_base)
if data.base then
table.insert(parts_formatted, export.link_term(data.base, data))
end
table.insert(parts_formatted, export.link_term(data.suffix, data))
-- FIXME, should we be specifying a sort base here?
insert_affix_category(categories, data.pos, "suffix", data.suffix)
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|infix}}.
'''WARNING''': This destructively modifies both `data` and `.base` and `.infix`.
]==]
function export.show_infix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
make_part_into_affix(data.infix, data.lang, data.sc, "infix")
track_wrong_affix_type("infix", data.base, nil)
track_wrong_affix_type("infix", data.infix, "infix")
-- Make links out of all the parts.
local parts_formatted = {}
local categories = {}
table.insert(parts_formatted, export.link_term(data.base, data))
table.insert(parts_formatted, export.link_term(data.infix, data))
-- Insert the categories.
-- FIXME, should we be specifying a sort base here?
insert_affix_category(categories, data.pos, "infix", data.infix)
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|prefix}}.
'''WARNING''': This destructively modifies both `data` and the structures within `.prefixes`, as well as `.base`.
]==]
function export.show_prefix(data)
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
for i, prefix in ipairs(data.prefixes) do
make_part_into_affix(prefix, data.lang, data.sc, "prefix")
end
for i, prefix in ipairs(data.prefixes) do
track_wrong_affix_type("prefix", prefix, "prefix")
end
track_wrong_affix_type("prefix", data.base, nil)
-- Make links out of all the parts.
local parts_formatted = {}
local first_sort_base = nil
local categories = {}
if data.prefixes[2] then
first_sort_base = ine(data.prefixes[2].term) or ine(data.prefixes[2].alt)
if first_sort_base then
first_sort_base = strip_diacritics_no_links(data.prefixes[2].lang, first_sort_base)
end
elseif data.base then
first_sort_base = ine(data.base.term) or ine(data.base.alt)
if first_sort_base then
first_sort_base = strip_diacritics_no_links(data.base.lang, first_sort_base)
end
end
for i, prefix in ipairs(data.prefixes) do
table.insert(parts_formatted, export.link_term(prefix, data))
insert_affix_category(categories, data.pos, "prefix", prefix, data.sort_key, i == 1 and first_sort_base or nil)
end
if data.base then
table.insert(parts_formatted, export.link_term(data.base, data))
else
table.insert(parts_formatted, "")
end
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
--[==[
Implementation of {{tl|suffix}}.
'''WARNING''': This destructively modifies both `data` and the structures within `.suffixes`, as well as `.base`.
]==]
function export.show_suffix(data)
local categories = {}
data.pos = data.pos or default_pos
-- data.pos = pluralize(data.pos)
canonicalize_part(data.base, data.lang, data.sc)
-- Hyphenate the affixes and apply any affix mappings.
for i, suffix in ipairs(data.suffixes) do
make_part_into_affix(suffix, data.lang, data.sc, "suffix")
end
track_wrong_affix_type("suffix", data.base, nil)
for i, suffix in ipairs(data.suffixes) do
track_wrong_affix_type("suffix", suffix, "suffix")
end
-- Make links out of all the parts.
local parts_formatted = {}
if data.base then
table.insert(parts_formatted, export.link_term(data.base, data))
else
table.insert(parts_formatted, "")
end
for i, suffix in ipairs(data.suffixes) do
table.insert(parts_formatted, export.link_term(suffix, data))
end
-- Insert the categories.
for i, suffix in ipairs(data.suffixes) do
-- FIXME, should we be specifying a sort base here?
insert_affix_category(categories, data.pos, "suffix", suffix)
if suffix.pos and rfind(suffix.pos, "patronym") then
table.insert(categories, "patronymics")
end
end
return export.join_formatted_parts { data = data, parts_formatted = parts_formatted, categories = categories }
end
return export
8b6qo0zwt9emgn0uk31es0563n619ac
Module:Etymon
828
2560114
9909059
9884955
2026-09-29T07:25:25Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909059
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",
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",
parse_utilities = "Module:parse utilities",
references = "Module:references",
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 = {
nocat = { type = "boolean" },
sort = {},
y = {},
m = {},
fragment = {},
section = {},
box = { type = "boolean" },
noes = { type = "boolean" },
},
etystub = {
nocat = { type = "boolean" },
sort = {},
nocap = { type = "boolean" },
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.t
end
if term.g and term.g ~= "" then
local genders = M.string_utilities.split(term.g, ",")
for i = 1, #genders do
genders[i] = M.string_utilities.trim(genders[i])
end
link_params.genders = genders
end
if opts.pos ~= "suppress" then
link_params.pos = term.pos
link_params.ng = term.ng
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 = "[[Appendix:Glossary#字面分析|字面分析]]為"
local surf_phrase = "[[Appendix:Glossary#字面分析|字面分析]]為"
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 .. "[[Appendix:Glossary#名詞化|名詞化]],來自"
local phrase = adj .. "[[Appendix:Glossary#名詞化|名詞化]],來自"
return { text = text, phrase = phrase }
end
local EtymonParser = {}
-- Keyword modifier definitions
EtymonParser.keyword_param_mods = {
unc = { type = "boolean" },
ref = {},
text = { restrict = { keywords = { "from", "derived" } } },
lit = { restrict = { affix_group = true } },
conj = {}, -- conjunction for alternatives: "and", "or", "and/or", etc.
g = { restrict = { keywords = { "nominalization" } } },
surf = { type = "boolean" },
senseid = { restrict = { keywords = { "semantic loan" } } },
}
-- Term modifier definitions
EtymonParser.etymon_param_mods = {
id = {},
t = {},
tr = {},
ts = {},
q = {},
qq = {},
l = {},
ll = {},
pos = {},
ng = {},
alt = {},
g = {},
ety = {},
lit = {},
unc = { type = "boolean" },
ref = {},
aftype = { restrict = { affix_group = true } },
postype = {},
bor = { type = "boolean", restrict = { affix_group = true } },
slbor = { type = "boolean", restrict = { affix_group = true } },
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,
ref = parsed.ref,
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) < 1 then -- L10N:中文單字 ID 有意義,enwikt 原值為 2
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 pos_aliases = { ["前綴"] = "prefix", ["後綴"] = "suffix", ["間綴"] = "interfix", ["中綴"] = "infix", ["詞根"] = "root", ["詞"] = "word", }
if pos and pos_aliases[pos] then pos = pos_aliases[pos] end -- L10N
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_forms = 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("^詞源") or node:get_name():find("^词源") 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 = mw.ustring.lower(node:get_name()) -- L10N lower all template titles
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 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 = {}
local function parse_etymon_references(refs_text)
if not refs_text or refs_text == "" then
return ""
end
return M.references.parse_references(refs_text)
end
local function parse_tree_references(node)
if node.ref then
node.parsed_ref = parse_etymon_references(node.ref)
end
if node.children then
for _, container in ipairs(node.children) do
if container.terms then
for _, term in ipairs(container.terms) do
parse_tree_references(term)
end
end
end
end
if node.supplements then
for _, supplement in ipairs(node.supplements) do
if supplement.terms then
for _, term in ipairs(supplement.terms) do
parse_tree_references(term)
end
end
end
end
end
-- 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.t = etymon_data.t
term.tr = etymon_data.tr
term.ts = etymon_data.ts
term.alt = etymon_data.alt
term.g = etymon_data.g
term.pos = etymon_data.pos
term.ng = etymon_data.ng
term.ref = etymon_data.ref
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 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.t,
transliteration = node.tr,
transcription = node.ts,
alt = node.alt,
g = node.g,
pos = node.pos,
ng = node.ng,
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)
parse_tree_references(ety_data_tree)
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 and require("Module:zh").st(current_L2) ~= norm_name then -- L10N
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 = {
ref = { list = true, type = "references", allow_holes = true },
refn = { list = true, allow_holes = true },
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.ref and #parsed_etydate.ref > 0) and parsed_etydate.ref or nil,
})
end
TreeBuilder.append_term_supplement(ety_data_tree, lang, "doublet", doublet)
if ety_data_tree.supplements then
parse_tree_references(ety_data_tree)
end
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
i1zindu5xq16db1rz9gsw45xdegz605
Module:Zh/data/glyph-data/箙
828
2565775
9909132
8452356
2026-09-29T08:37:03Z
TongcyDai
53191
9909132
Scribunto
text/plain
return {
{ "s03405", "L19648", "L19649", "L37484", "L37485", "b04958", "b04959", "b04960", "b04961", "b06615", "b06616", "b06617", "b06618", "b06619", "b06620", "b06621", "b06622", "b06623", "b06624", "b06625", "b06626", "b06627", "b06628", "j10787", "j07789", "j07790", "j07791", "j07792", "j07793", "j07794", "j07795", "j07796", "j07797", "j07798", "j07799", "j07800", "j07801", "j07802", "j07803", "j07804", "j07805", "j07806", "j07807", "j07808", "j07809", "j07810", "j07811", "j07812", "j07813", "j07814", "j07815", "j07816" },
}
joy1x2ng3oo0paojtcuhv92fr2dl977
9909192
9909132
2026-09-29T09:55:59Z
TongcyBot
83009
同步英語維基詞典
9909192
Scribunto
text/plain
return {
{ "s03405", "L19648", "L19649", "L37484", "L37485", "b04958", "b04959", "b04960", "b04961", "b06615", "b06616", "b06617", "b06618", "b06619", "b06620", "b06621", "b06622", "b06623", "b06624", "b06625", "b06626", "b06627", "b06628", "j10787", "j07789", "j07790", "j07791", "j07792", "j07793", "j07794", "j07795", "j07796", "j07797", "j07798", "j07799", "j07800", "j07801", "j07802", "j07803", "j07804", "j07805", "j07806", "j07807", "j07808", "j07809", "j07810", "j07811", "j07812", "j07813", "j07814", "j07815", "j07816" },
}
7ll97jnr8je3ay837es844a26j87jn8
Module:Parameter utilities
828
2576089
9909180
9755992
2026-09-29T09:47:21Z
TongcyBot
83009
同步 enwikt
9909180
Scribunto
text/plain
local export = {}
local debug_track_module = "Module:debug/track"
local functions_module = "Module:fun"
local parameters_module = "Module:parameters"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local table_module = "Module:table"
local dump = mw.dumpObject
local error = error
local insert = table.insert
local ipairs = ipairs
local next = next
local pairs = pairs
local require = require
local tonumber = tonumber
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 debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function is_callable(...)
is_callable = require(functions_module).is_callable
return is_callable(...)
end
local function list_to_set(...)
list_to_set = require(table_module).listToSet
return list_to_set(...)
end
local function parse_inline_modifiers(...)
parse_inline_modifiers = require(parse_interface_module).parse_inline_modifiers
return parse_inline_modifiers(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function shallow_copy(...)
shallow_copy = require(table_module).shallowCopy
return shallow_copy(...)
end
local function table_len(...)
table_len = require(table_module).length
return table_len(...)
end
----------------- end loaders ----------------
local function track(page, track_module)
return debug_track((track_module or "parameter utilities") .. "/" .. page)
end
-- Throw an error prefixed with the words "Internal error" (and suffixed with a dumped version of `spec`, if provided).
-- This is for logic errors in the code itself rather than template user errors.
local function internal_error(msg, spec)
if spec then
msg = ("%s: %s"):format(msg, dump(spec))
end
error(("Internal error: %s"):format(msg))
end
-- Table listing the default recognized special separator arguments and how they display.
export.default_special_separators = {
[";"] = "; ",
["_"] = " ",
["~"] = " ~ ",
["→"] = " → ",
}
-- Table listing how subitem delimiters display. Unlike for `default_special_separators`, the presence of an item in
-- this table does not mean that the delimiter is recognized; only those specified by `data.splitchar` are recognized.
export.default_subitem_separator_map = {
[";"] = "; ",
[","] = ", ",
["/"] = "/",
["_"] = " ",
["~"] = " ~ ",
["→"] = " → ",
}
--[==[ intro:
The purpose of this module is to facilitate implementation of templates that can have arguments specified either through
inline modifiers or separate parameters. There are two types of templates supported: those that take a list of items
with associated properties, which can be specified either through indexed separate parameters (e.g. {{para|t2}},
{{para|pos3}}) or inline modifiers (`<t:...>`, `<pos:...>`, etc.); and those that take a single term, whose properties
can be specified through non-indexed separate parameters (e.g. {{para|t}} or {{para|pos}}) or inline modifiers. Both
types of templates can optionally have subitems in the term parameter(s), where the subitems are typically (but not
necessarily) separated with commas and each subitem can have its own inline modifiers.
Some examples of templates that take a list of items are {{tl|alter}}/{{tl|alt}}; {{tl|synonyms}}/{{tl|syn}},
{{tl|antonyms}}/{{tl|ant}}, and other "nyms" templates; {{tl|col}}, {{tl|col2}}, {{tl|col3}}, {{tl|col4}} and other
column templates; {{tl|descendant}}/{{tl|desc}}; {{tl|affix}}/{{tl|af}}, {{tl|prefix}}/{{tl|pre}} and related *fix
templates; {{tl|affixusex}}/{{tl|afex}} and related templates; {{tl|IPA}}; {{tl|homophones}}; {{tl|rhymes}}; and several
others.
Examples of templates that take a single item are form-of templates ({{tl|inflection of}}/{{tl|infl of}},
{{tl|form of}}, and specific templates such as {{tl|alt form}}/{{tl|alternative form of}},
{{tl|abbr of}}/{{tl|abbreviation of}}, {{tl|clipping of}}, and many others); for etymology templates
({{tl|bor}}/{{tl|borrowed}}, {{tl|der}}/{{tl|derived}}, etc. as well as `misc_variant` templates like {{tl|ellipsis}},
{{tl|abbrev}}, {{tl|clipping}}, {{tl|reduplication}} and the like); and other templates that take an argument structure
similar to {{tl|l}} or {{tl|m}}.
This module can be thought of as a combination of [[Module:parameters]] (which parses template parameters, and in
particular handles the separate parameter versions of the properties) and `parse_inline_modifiers()` in
[[Module:parse utilities]] (which parses inline modifiers).
The two main entry points are `parse_list_with_inline_modifiers_and_separate_params()` (for templates that take a list
of items) and `parse_term_with_inline_modifiers_and_separate_params()` (for templates that take a single item). However,
there are other functions provided, e.g. to initialize the `param_mods` structure that is passed to the two entry
points.
The typical workflow for using `parse_list_with_inline_modifiers_and_separate_params()` looks as follows (a slightly
simplified version of the code in [[Module:nyms]]):
{
local export = {}
local parameter_utilities_module = "Module:parameter utilities"
...
-- Entry point to be invoked from a template.
function export.show(frame)
local parent_args = frame:getParent().args
-- Parameters that don't have corresponding inline modifiers. Note in particular that the parameter corresponding to
-- the items themselves must be specified this way, and must specify either `allow_holes = true` (if the user can
-- omit terms, typically by specifying the term using |altN= or <alt:...> so that they remain unlinked) or
-- `disallow_holes = true` (if omitting terms is not allowed). (If neither `allow_holes` nor `disallow_holes` is
-- specified, an error is thrown in parse_list_with_inline_modifiers_and_separate_params().)
local params = {
[1] = {required = true, type = "language", default = "und"},
[2] = {list = true, allow_holes = true, required = true, default = "term"},
}
local m_param_utils = require(parameter_utilities_module)
-- This constructs the `param_mods` structure by adding well-known groups of parameters (such as all the parameters
-- associated with based on full_link() in [[Module:links]], with default properties that can be overridden. This is
-- easier and less error-prone than manually specifying the `param_mods` structure (see below for how this would
-- look). Here, we specify the group "link" (consisting of all the link parameters for use with full_link()), group
-- "ref" (which adds the "ref" parameter for specifying references), group "l" (which adds the "l" and "ll"
-- parameters for specifying labels) and group "q" (which adds the "q" and "qq" parameters for specifying regular
-- qualifiers). By default, labels and qualifiers have `separate_no_index` set so that e.g. |q1= is distinct from
-- |q=, the former specifying the left qualifier for the first item and the latter specifying the overall left
-- qualifier. For compatibility, we override the `separate_no_index` setting for the group "q", which causes |q= and
-- |q1= to be the same, and likewise for |qq= and |qq1=. Finally, also for compatibility, we add an "lb" parameter
-- that is an alias of "ll" (in all respects; |lb= is the same as |ll=, |lb1= is the same as |ll1=, <lb:...> is the
-- same as <ll:...>, etc.).
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "l"}},
{group = "q", separate_no_index = false},
{param = "lb", alias_of = "ll"},
}
-- This processes the raw arguments in `parent_args`, parses inline modifiers and creates corresponding objects
-- containing the property values specified either through inline modifiers or separate parameters.
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
parse_lang_prefix = true,
track_module = "nyms",
lang = 1,
sc = "sc.default",
}
local lang = args[1]
-- Now do the actual implementation of the template. Generally this should be split into a separate function, often
-- in a separate module (if the implementation goes in [[Module:foo]], the template interface code goes in
-- [[Module:foo/templates]]).
...
}
The `param_mods` structure controls the properties that can be specified by the user for a given item, and is
conceptually very similar to the `param_mods` structure used by `parse_inline_modifiers()`. The key is the name of the
parameter (e.g. {"t"}, {"pos"}) and the value is a table with optional elements as follows:
* `item_dest`, `store`: Same as the corresponding fields in the `param_mods` structure passed to
`parse_inline_modifiers()`.
* `type`, `set`, `sublist`, `convert` and associated fields such as `family` and `method`: These control parsing and
conversion of the raw values specified by the user and have the same meaning as in [[Module:parameters]] and also in
`parse_inline_modifiers()` (which delegates the actual conversion to [[Module:parameters]]). These fields — and for
that matter, all fields other than `item_dest`, `store` and `overall` — are forwarded to the `process()` function in
[[Module:parameters]].
* `alias_of`: This parameter is an alias of some other parameter. This spec is recognized only by `process()` in
[[Module:parameters]], and not by `parse_inline_modifiers()`; to set up an alias in `parse_inline_modifiers()`, you
need to make sure (using `item_dest`) that both the alias and aliasee modifiers store their values in the same
location, and you need to copy the remaining properties from the aliasee's spec to the aliasing modifier's spec. All
of this happens automatically if you generate the `param_mods` structure using `construct_param_mods()`.
* `require_index`: This means that the non-indexed parameter version of the property is not recognized. E.g. in the
case of the {"sc"} property, use of the {{para|sc}} parameter would result in an error, while {{para|sc1}} is
recognized and specifies the {"sc"} property for the first item. The default, if neither `require_index` nor
`separate_no_index` is given, is for {{para|sc}} and {{para|sc1}} to mean the same thing (both would specify the
{"sc"} property of the first item). Note that `require_index` and `separate_no_index` are mutually exclusive, and if
either one is specified during processing by `construct_param_mods()`, the other one is automaticallly turned off.
* `separate_no_index`: This means that e.g. the {{para|sc}} parameter is distinct from the {{para|sc1}} parameter
(and thus from the `<sc:...>` inline modifier on the first item). This is typically used to distinguish an overall
version of a property from the corresponding item-specific property on the first item. (In this case, for example,
{{para|sc}} overrides the script code for all items, while {{para|sc1}} overrides the script code only for the
first item.) If not given, and if `require_index` is not given, {{para|sc}} and {{para|sc1}} would have the same
meaning and refer to the item-specific property on the first item. When this is given, the overall value can be
accessed using the `.default` field of the property value in `args`, e.g. in this case `args.sc.default`. Note that
(as mentioned above) `require_index` and `separate_no_index` are mutually exclusive, and if either one is specified
during processing by `construct_param_mods()`, the other one is automaticallly turned off.
* `list`, `allow_holes`, `disallow_holes`: These should '''not''' be given. `list` and `allow_holes` are automatically
set for all parameter specs added to the `params` structure used by `process()` in [[Module:parameters]], and
`disallow_holes` clashes with `allow_holes`.
For the above workflow example, the call to `construct_param_mods()` generates the following `param_mods` structure:
{
local param_mods = {
-- the parameters generated by group "link"
alt = {},
t = {
-- [[Module:links]] expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {
alias_of = "t",
},
tr = {},
ts = {},
g = {
-- [[Module:links]] expects the genders in "genders".
item_dest = "genders",
type = "genders",
},
pos = {},
ng = {},
lit = {},
id = {},
sc = {
separate_no_index = true,
type = "script",
},
-- the parameters generated by group "ref"
ref = {
item_dest = "refs",
type = "references",
},
-- the parameters generated by group "l"
l = {
type = "labels",
separate_no_index = true,
},
ll = {
type = "labels",
separate_no_index = true,
},
-- the parameters generated by group "q"; note that `separate_no_index = true` would be set, but is overridden
-- (specifying `separate_no_index = false` in the `param_mods` structure is equivalent to not specifying it at all)
q = {
type = "qualifier",
separate_no_index = false,
},
qq = {
type = "qualifier",
separate_no_index = false,
},
infl = {
type = "form of tags",
separate_no_index = true,
},
-- the parameter generated by the individual "lb" parameter spec; note that only `alias_of` was explicitly given,
-- while `item_dest` is automatically set so that inline modifier <lb:...> stores into the same place as <ll:...>,
-- and the other specs are copied from the `ll` spec so `lb` works like `ll` in all regards
lb = {
alias_of = "ll",
item_dest = "ll",
type = "labels",
separate_no_index = true,
},
}
}
]==]
local qualifier_spec = {
type = "qualifier",
separate_no_index = true,
}
local label_spec = {
type = "labels",
separate_no_index = true,
}
local form_of_spec = {
type = "form of tags",
separate_no_index = true,
}
local recognized_param_mod_groups = {
link = {
alt = {},
t = {
-- [[Module:links]] expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {
alias_of = "t",
},
tr = {},
ts = {},
g = {
-- [[Module:links]] expects the genders in "genders".
item_dest = "genders",
type = "genders",
},
pos = {},
ng = {},
lit = {},
id = {},
sc = {
separate_no_index = true,
type = "script",
},
},
lang = {
lang = {
require_index = true,
type = "language",
},
},
q = {
q = qualifier_spec,
qq = qualifier_spec,
},
a = {
a = label_spec,
aa = label_spec,
},
l = {
l = label_spec,
ll = label_spec,
},
infl = {
infl = form_of_spec,
},
ref = {
ref = {
item_dest = "refs",
type = "references",
},
},
}
local function merge_param_mod_settings(orig, additions)
local merged = shallow_copy(orig)
for k, v in pairs(additions) do
merged[k] = v
if k == "require_index" then
merged.separate_no_index = nil
elseif k == "separate_no_index" then
merged.require_index = nil
end
end
merged.default = nil
merged.group = nil
merged.param = nil
merged.exclude = nil
merged.include = nil
return merged
end
local function verify_type(spec, param, typ1, typ2)
if not spec[param] then
return
end
local val = spec[param]
if type(val) ~= typ1 and (not typ2 or type(val) ~= typ2) then
internal_error(("Parameter `%s` must be a %s%s but saw a %s"):format(param, typ1, typ2 and " or " .. typ2 or "",
type(val)), spec)
end
end
local function verify_well_constructed_spec(spec)
local num_control = (spec.default and 1 or 0) + (spec.group and 1 or 0) + (spec.param and 1 or 0)
if num_control == 0 then
internal_error(
"Spec passed to construct_param_mods() must have either the `default`, `group` or `param` keys set", spec)
end
if num_control > 1 then
internal_error(
"Exactly one of `default`, `group` or `param` must be set in construct_param_mods() spec", spec)
end
if spec.list or spec.allow_holes then
-- FIXME: We need to support list = "foo" for list parameters that are stored in e.g. 2=, foo2=, foo3=, etc.
internal_error("`list` and `allow_holes` may not be set; they are automatically set when constructing the " ..
"corresponding spec in the `params` object passed to [[Module:parameters]]", spec)
end
if spec.disallow_holes then
internal_error("`disallow_holes` may not be set; it conflicts with `allow_holes`, which is automatically " ..
"set when constructing the corresponding spec in the `params` object passed to [[Module:parameters]]", spec)
end
if spec.include and spec.exclude then
internal_error("Saw both `include` and `exclude` in the same spec", spec)
end
if (spec.include or spec.exclude) and not spec.group then
internal_error(
"`include` and `exclude` can only be specified along with `group`, not with `default` or `param`", spec)
end
verify_type(spec, "group", "string", "table")
verify_type(spec, "param", "string", "table")
verify_type(spec, "include", "table")
verify_type(spec, "exclude", "table")
end
--[==[
Construct the `param_mods` structure used in parsing arguments and inline modifiers from a list of specifications.
A sample invocation (a slightly simplified version of the actual invocation associated with {{tl|affix}} and related
templates) looks like this:
{
local param_mods = require("Module:parameter utilities").construct_param_mods {
-- We want to require an index for all params (or use separate_no_index, which also requires an index for the
-- param corresponding to the first item).
{default = true, require_index = true},
{group = {"link", "ref", "lang", "q", "l"}},
-- Override these two to have separate_no_index.
{param = {"lit", "pos"}, separate_no_index = true},
}
}
Each specification either sets the default value for further parameter specs or adds one or more parameters. Parameters
can be added directly using `param`, or groups of predefined parameters can be added using `group`. Specifications are
one of three types:
# Those that set the default properties for future-added parameters. These contain {default = true} as one of the
properties of the spec. Specs are processed in order and you can change the defaults mid-way through.
# Those that add the parameters associated with one or more pre-defined groups. These contain {group = "group"} or
{group = {"group1", "group2", ...}}. The pre-defined parameter groups and their associated properties are listed
below. The pre-defined properties of parameters in a group override properties associated with a {default = true}
spec, and are in turn overridden by any properties given directly in the spec itself. Note as well that setting the
`separate_no_index` property will automatically cause the `require_index` property to be unset and vice-versa, as the
two are mutually exclusive. (This happens in the example above, where the {separate_no_index = true} setting
associated with the params {"lit"} and {"pos"} cancels out the {require_index = true} default setting, as well as less
obviously with the pre-defined {"sc"} property of the {"link"} group, the {"q"} and {"qq"} properties of the {"q"}
group, and the {"l"} and {"ll"} properties of the {"l"} group, all of which have an associated pre-defined property
{separate_no_index = true}, which overrides and cancels out the {require_index = true} default setting. Finally, when
adding the parameters of a group, you can request the only a subset of the parameters be added using either the
`include` or `exclude` properties, each of whose values is a list of parameters that specify (respectively) the
parameters to include (all other parameters of the group are excluded) or to exclude (all other parameters of the
group are included). This is used, for example, in [[Module:romance etymology]] and [[Module:it-etymology]], which
specify {group = "link", exclude = {"tr", "ts", "sc"}} to exclude link parameters that aren't relevant to Latin-script
languages such as the Romance languages, and conversely in [[Module:IPA/templates]], which specifies
{group = "link", include = {"t", "gloss", "pos"}} to include only the specified parameters for use with {{tl|IPA}}.
# Those that add individual parameters. These contain {param = "param"} or {param = {"param1", "param2", ...}}, the
latter syntax used to control a set of parameters together. The resulting spec is formed by initializing the
parameter's settings with any previously-specified default properties (using a spec containing {default = true}) if
the parameter hasn't already been initialized, and then overriding the resulting settings with any settings given
directly in the specification. In the above example, the {"lit"} and {"pos"} parameters were previously initialized
through the {"link"} group (specified in the second of the three specifications) but ended up with
{require_index = true} due to the {default = true} spec (the first of the three specifications). We override these
two parameters to have {separate_no_index = true} (which, as mentioned above, cancels out {require_index = true}).
This is done so that {{tl|affix}} and related templates have {{para|pos}} and {{para|lit}} parameters distinct from
{{para|pos1}} and {{para|lit1}}, which are used to specify an overall part of speech (which applies to all parts of
the affix, as opposed to applying to just one element of the expression) or a literal definition for the entire
expression (instead of just for one element of the expression).
The built-in parameter groups are as follows:
{|class="wikitable"
! Group !! Group meaning !! Parameter !! Parameter meaning !! Default properties
|-
| rowspan=11| `link`
| rowspan=11| link parameters; same as those available on {{tl|l}}, {{tl|m}} and other linking templates
| `alt` || display text, overriding the term's display form || —
|-
| `t` || gloss (translation) of a non-English term || {item_dest = "gloss"}
|-
| `gloss` || gloss (translation); same as `t` || {alias_of = "t"}
|-
| `tr` || transliteration of a non-Latin-script term; only needed if the automatic transliteration is incorrect or unavailable (e.g. in Hebrew, which doesn't have automatic transliteration) || —
|-
| `ts` || transcription of a non-Latin-script term, if the transliteration is markedly different from the actual pronunciation; should not be used for IPA pronunciations || —
|-
| `g` || comma-separated list of genders; whitespace may surround the comma and will be ignored || {item_dest = "genders", type = "genders"}
|-
| `pos` || part of speech for the term || —
|-
| `ng` || arbitrary non-gloss descriptive text for the term || —
|-
| `lit` || literal meaning (translation) of the term || —
|-
| `id` || a sense ID for the term, which links to anchors on the page set by the {{tl|senseid}} template || —
|-
| `sc` || the script code (see [[Wiktionary:Scripts]]) for the script that the term is written in; rarely necessary, as the script is autodetected (in most cases, correctly) || {separate_no_index = true, type = "script"}
|-
| rowspan=2| `q`
| rowspan=2| left and right normal qualifiers (as displayed using {{tl|q}})
| `q` || left normal qualifier || {separate_no_index = true, type = "qualifier"}
|-
| `qq` || right normal qualifier || {separate_no_index = true, type = "qualifier"}
|-
| rowspan=2| `a`
| rowspan=2| left and right accent qualifiers (as displayed using {{tl|a}})
| `a` || comma-separated list of left accent qualifiers; whitespace must not surround the comma || {separate_no_index = true, type = "labels"}
|-
| `aa` || comma-separated list of right accent qualifiers; whitespace must not surround the comma || {separate_no_index = true, type = "labels"}
|-
| rowspan=2| `l`
| rowspan=2| left and right labels (as displayed using {{tl|lb}}, but without categorizing)
| `l` || comma-separated list of left labels; whitespace must not surround the comma || {separate_no_index = true, type = "labels"}
|-
| `ll` || comma-separated list of right labels; whitespace must not surround the comma || {separate_no_index = true, type = "labels"}
|-
| `ref`
| reference(s) (in the format accepted by [[Module:references]]; see also the documentation for the {{para|ref}} parameter to {{tl|IPA}})
| `ref` || one or more references, in the format accepted by [[Module:references]] || {item_dest = "refs", type = "references"}
|-
| `lang`
| language for an individual term (provided for compatibility; it is preferred to specify languages for individual terms using language prefixes instead)
| `lang` || language code (see [[Wiktionary:Languages]]) for the term || {require_index = true, type = "language"}
|}
]==]
function export.construct_param_mods(specs)
local param_mods = {}
local default_specs = {}
for _, spec in ipairs(specs) do
verify_well_constructed_spec(spec)
if spec.default then
-- This will have an extra `default` field in it, but it will be erased by merge_param_mod_settings()
default_specs = spec
else
if spec.group then
local groups = spec.group
if type(groups) ~= "table" then
groups = {groups}
end
local include_set
if spec.include then
include_set = list_to_set(spec.include)
end
local exclude_set
if spec.exclude then
exclude_set = list_to_set(spec.exclude)
end
for _, group in ipairs(groups) do
local group_specs = recognized_param_mod_groups[group]
if not group_specs then
internal_error(("Unrecognized built-in param mod group '%s'"):format(group), spec)
end
for group_param, group_param_settings in pairs(group_specs) do
local include_param
if include_set then
include_param = include_set[group_param]
elseif exclude_set then
include_param = not exclude_set[group_param]
else
include_param = true
end
if include_param then
local merged_settings = merge_param_mod_settings(merge_param_mod_settings(
param_mods[group_param] or default_specs, group_param_settings), spec)
param_mods[group_param] = merged_settings
end
end
end
end
if spec.param then
local params = spec.param
if type(params) ~= "table" then
params = {params}
end
for _, param in ipairs(params) do
local settings = merge_param_mod_settings(param_mods[param] or default_specs, spec)
-- If this parameter is an alias of another parameter, we need to copy the specs from the other
-- parameter, since parse_inline_modifiers() doesn't know about `alias_of` and having the specs
-- duplicated won't cause problems for [[Module:parameters]]. We also need to set `item_dest` to
-- point to the `item_dest` of the aliasee (defaulting to the aliasee's value itself), so that
-- both modifiers write to the same location. Note that this works correctly in the common case of
-- <t:...> with `item_dest = "gloss"` and <gloss:...> with `alias_of = "t"`, because both will end
-- up with `item_dest = "gloss"`.
local aliasee = settings.alias_of
if aliasee then
local aliasee_settings = param_mods[aliasee]
if not aliasee_settings then
internal_error(("Undefined aliasee '%s'"):format(aliasee), spec)
end
for k, v in pairs(aliasee_settings) do
if settings[k] == nil then
settings[k] = v
end
end
if settings.item_dest == nil then
settings.item_dest = aliasee
end
end
param_mods[param] = settings
end
end
end
end
return param_mods
end
-- Return true if `k` is a "built-in" (specially recognized) key in a `param_mod` specification. All other keys
-- are forwarded to the structure passed to [[Module:parameters]].
local function param_mod_spec_key_is_builtin(k)
return k == "item_dest" or k == "overall" or k == "store"
end
--[==[
Convert the properties in `param_mods` into the appropriate structures for use by `process()` in [[Module:parameters]]
and store them in `params`. If `overall_only` is given, only store the properties in `param_mods` that correspond to
overall (non-item-specific) parameters. Currently this only happens when `separate_no_index` is specified.
]==]
function export.augment_params_with_modifiers(params, param_mods, overall_only)
if overall_only then
for param_mod, param_mod_spec in pairs(param_mods) do
if overall_only == "always" or param_mod_spec.separate_no_index then
local param_spec = {}
for k, v in pairs(param_mod_spec) do
if k ~= "separate_no_index" and k ~= "require_index" and not param_mod_spec_key_is_builtin(k) then
param_spec[k] = v
end
end
params[param_mod] = param_spec
end
end
else
local list_with_holes
-- Add parameters for each term modifier.
for param_mod, param_mod_spec in pairs(param_mods) do
local param_spec
for k, v in pairs(param_mod_spec) do
if not param_mod_spec_key_is_builtin(k) then
if param_spec == nil then
param_spec = {list = true}
end
param_spec[k] = v
end
end
if param_spec == nil then
if list_with_holes == nil then
list_with_holes = {list = true, allow_holes = true}
end
param_spec = list_with_holes
elseif param_spec.alias_of == nil then
param_spec.allow_holes = true
end
params[param_mod] = param_spec
end
end
end
--[==[
Return true if `k`, a key in an item, refers to a property of the item (is not one of the specially stored values).
Note that `lang` and `sc` are considered properties of the item, although `lang` is set when there's a language
prefix and both `lang` and `sc` may be set from default values specified in the `data` structure passed into
`parse_list_with_inline_modifiers_and_separate_params()` and `parse_term_with_inline_modifiers_and_separate_params()`.
If you don't want these treated as property keys, you need to check for them yourself.
]==]
function export.item_key_is_property(k)
return k ~= "term" and k ~= "termlang" and k ~= "termlangs" and k ~= "itemno" and k ~= "orig_index" and
k ~= "separator"
end
-- Fetch the argument in `args` corresponding to `index_or_value`, which may be a string of the form "foo.default"
-- (requesting the value of `args["foo"].default`); a string or number (requesting the value at that key); a function of
-- one argument (`args`), which returns the argument value; or the value itself. Return the resulting value and the
-- parameter in `args` that the value came from, or nil if unknown (i.e. a function or direct value was specified).
local function fetch_argument(args, index_or_value)
if not index_or_value then
return index_or_value, nil
end
local index_or_value_type = type(index_or_value)
if index_or_value_type == "string" then
if index_or_value:sub(-8) == ".default" then
local index_without_default = index_or_value:sub(1, -9)
local arg_obj = fetch_argument(args, index_without_default)
if type(arg_obj) ~= "table" then
internal_error(("Requested that the '.default' key of argument `%s` be fetched, but argument value is undefined or not a table"):
format(index_without_default), arg_obj)
end
return arg_obj.default, index_without_default
end
if index_or_value:match("^%d+$") then
index_or_value = tonumber(index_or_value)
end
return args[index_or_value], index_or_value
elseif index_or_value_type == "number" then
return args[index_or_value], index_or_value
elseif is_callable(index_or_value) then
return index_or_value(args), nil
end
return index_or_value, nil
end
function export.generate_obj_maybe_parsing_lang_prefix(data)
return require(parse_utilities_module).generate_obj_maybe_parsing_lang_prefix(data)
end
-- Subfunction of parse_list_with_inline_modifiers_and_separate_params() and
-- parse_term_with_inline_modifiers_and_separate_params(), validating certain argument-related fields that are shared
-- among the two functions.
local function validate_argument_related_fields(data)
if not data.termarg then
internal_error("`data.termarg` must be given, indicating which argument contains the terms to be parsed", data)
end
if not data.param_mods then
internal_error("`data.param_mods` must be given, indicating the allowed inline modifiers and separate " ..
"parameters to copy", data)
end
local subitem_param_handling = data.subitem_param_handling or "only"
if subitem_param_handling ~= "only" and subitem_param_handling ~= "first" and subitem_param_handling ~= "last" then
internal_error("Unrecognized value for `data.subitem_param_handling`, should be 'first', 'last' or 'only'",
subitem_param_handling)
end
if data.raw_args then
if data.processed_args then
internal_error("Only one of `data.raw_args` and `data.processed_args` can be specified", data)
end
if not data.params then
internal_error("When `data.raw_args` is specified, so must `data.params`, so that the raw arguments " ..
"can be parsed", data)
end
if data.params[data.termarg] == nil then
internal_error("There must be a spec in `data.params` corresponding to `data.termarg`", data)
end
else
if not data.processed_args then
internal_error("Either `data.raw_args` or `data.processed_args` must be specified", data)
end
if data.params then
internal_error("When `data.processed_args` is specified, `data.params` should not be specified", data)
end
end
end
local function argval_missing(val)
return val == nil or type(val) == "table" and next(val) == nil
end
-- Subfunction of parse_list_with_inline_modifiers_and_separate_params() and
-- parse_term_with_inline_modifiers_and_separate_params(). After parsing inline modifiers, copy the separate parameters
-- to the generated object (or to the appropriate subobject if there are multiple). `data` contains the following
-- fields:
--
-- `args`: The separate-parameter argument structure.
-- `param_mods`: The structure describing the inline modifiers.
-- `itemno`: The logical item number of the term being processed, or nil if there's only a single term.
-- `termobj`: The object to store the inline modifiers into. If there are subitems, they are in the `terms` field;
-- otherwise the properties are stored directly into `termobj`.
-- `has_subitems`: True if there are subitems.
-- `subitem_separator_map`: If `has_subitems` and this is specified, controls the assignment of the `separator` field
-- in subitems. If not specified or a delimiter is not in the map, it is copied unchanged.
-- `lang`: Language object to store into all items.
-- `sc`: Script object to store into all items, or nil.
-- `subitem_param_handling`: "only", "first" or "last", indicating what to do if there are multiple subitems.
-- `allow_conflicting_inline_mods_and_separate_params`: If true, specifying a value for both an inline modifier and
-- corresponding separate parameter is allowed, and the inline modifier takes precedence. Otherwise, an error
-- occurs.
-- `postprocess_termobj`: Optional function called on all items at the end, to do any postprocessing. Called with one
-- argument, the object to postprocess.
-- `no_show_decorations`: If true, don't automatically set {show_decorations = true} on the object or subobject if there
-- are decorations (i.e. qualifiers, labels or references) specified for the object.
local function copy_separate_params_to_termobj_and_postprocess(data)
local args, param_mods, itemno, termobj = data.args, data.param_mods, data.itemno, data.termobj
local function set_lang_and_sc(termobj)
-- Set these after parsing inline modifiers, not in generate_obj(), otherwise we'll get an error in
-- parse_inline_modifiers() if we try to use <lang:...> or <sc:...> as inline modifiers.
termobj.lang = termobj.lang or data.lang
termobj.sc = termobj.sc or data.sc
end
local function set_show_decorations(termobj)
-- Need to set after parsing inline modifiers.
if not data.no_show_decorations and (termobj.q or termobj.qq or termobj.a or termobj.aa or termobj.l or termobj.ll or
termobj.refs) then
termobj.show_decorations = true
end
end
local function fetch_separate_param(args, paramkey, itemno)
local argval = args[paramkey]
-- Careful with argument values that may be `false`.
if argval and itemno then
argval = argval[itemno]
end
return argval
end
-- Copy separate parameters to a given object.
local function copy_separate_params_to_termobj(fetch_destobj)
for param_mod, param_mod_spec in pairs(param_mods) do
local dest = param_mod_spec.item_dest or param_mod
-- Don't do anything with the `sc` param, which will get overwritten below; we don't
-- want it to cause an error if there are multiple subitems.
if dest ~= "sc" then
local argval = fetch_separate_param(args, param_mod, itemno)
if not argval_missing(argval) then
local destobj = fetch_destobj(param_mod, param_mod_spec, dest)
-- Don't overwrite a value already set by an inline modifier.
if argval_missing(destobj[dest]) then
destobj[dest] = argval
elseif not data.allow_conflicting_inline_mods_and_separate_params then
error(("Can't specify a value for separate parameter %s%s= because there is " ..
"already an inline modifier <%s:...> specifying a value for the term"):format(
param_mod, itemno or "", param_mod))
end
end
end
end
end
if data.has_subitems then
-- If there are any separate indexed parameters, we need to copy them to the first, last or only
-- subitem, depending on the value of `data.subitem_param_handling` (which defaults to 'only',
-- meaning it's an error if there are multiple subitems). Do this before calling
-- postprocess_termobj() because the latter sets .lang and .sc and we want the user to be able to
-- set separate langN= and scN= parameters.
-- If there was no term, `termobj.terms` will not exist; make it exist to make the callers' lives easier.
if not termobj.terms then
termobj.terms = {}
end
-- Compute whether any of the separate indexed params exist for this index.
local any_param_at_index
for param_mod in pairs(param_mods) do
local argval = fetch_separate_param(args, param_mod, itemno)
if not argval_missing(argval) then
any_param_at_index = true
break
end
end
-- If there was no term, but there's a separate parameter, we need to create an empty subitem.
if any_param_at_index and not termobj.terms[1] then
termobj.terms[1] = {}
end
local function fetch_destobj(param_mod, param_mod_spec, dest)
if param_mod_spec.overall then
return termobj
end
if data.subitem_param_handling == "only" and termobj.terms[2] then
error(("Can't specify a value for separate parameter %s%s= because there are " ..
"multiple subitems (%s) in the term; use an inline modifier"):format(
param_mod, itemno or "", #termobj.terms))
end
local termind
-- q/a/l need to go at the beginning and qq/aa/ll/refs at the end, regardless; otherwise, respect
-- `data.subitem_param_handling`.
if dest == "q" or dest == "a" or dest == "l" then
termind = 1
elseif dest == "qq" or dest == "aa" or dest == "ll" or dest == "refs" then
termind = #termobj.terms
elseif data.subitem_param_handling == "only" or data.subitem_param_handling == "first" then
termind = 1
else
termind = #termobj.terms
end
return termobj.terms[termind]
end
copy_separate_params_to_termobj(fetch_destobj)
for i, subitem in ipairs(termobj.terms) do
set_lang_and_sc(subitem)
set_show_decorations(subitem)
if subitem.delimiter then
subitem.separator = i == 1 and "" or
data.subitem_separator_map and data.subitem_separator_map[subitem.delimiter] or
subitem.delimiter
end
if data.postprocess_termobj then
data.postprocess_termobj(subitem, data)
end
end
else
-- Copy all the parsed term-specific parameters into `termobj`.
copy_separate_params_to_termobj(function(param_mod, dest) return termobj end)
set_lang_and_sc(termobj)
set_show_decorations(termobj)
if data.postprocess_termobj then
data.postprocess_termobj(termobj, data)
end
end
end
local function postprocess_termobj(item, data)
if not (data.disallow_custom_separators or data.use_semicolon) then
if data.has_subitems and item.separator and item.separator:find(",", nil, true) then
data.use_semicolon = true
else
-- If the displayed term (from .term/etc. or .alt) has an embedded comma, use a semicolon to
-- join the terms.
local term_text = item[data.term_dest] or item.alt
if term_text and term_text:find(",", nil, true) then
data.use_semicolon = true
end
end
end
end
--[==[
Parse a list of terms, each of which may have properties specified using inline modifiers or separate parameters. This
function is intended for parsing the arguments of templates like {{tl|syn}}, {{tl|ant}} and related ''*nym'' templates;
alternative-form templates {{tl|alt}}/{{tl|alter}}; affix templates like {{tl|af}}/{{tl|affix}},
{{tl|com}}/{{tl|compound}}, etc.; affix usex templates like {{tl|afex}}/{{tl|affixusex}}; name templates like
{{tl|name translit}}; column templates like {{tl|col}}; pronunciation templates like {{tl|rhyme}}/{{tl|rhymes}} and
{{tl|hmp}}/{{tl|homophones}}; etc. In these templates there are one or more terms specified using numeric parameters, and
associated separate parameters specifying per-term properties such as {{para|t1}}, {{para|t2}}, {{para|t3}}, ... for the
gloss of the first, second, third, ... term respectively. All such properties can also be specified through inline
modifiers attached directly to each term (`<t:...>`, `<pos:...>`, etc.). Normally it is an error if both an inline
modifier and separate parameter for the same value are given, but this can be overridden (in which case inline modifiers
take precedence over separate parameters when both occur).
For an example of a typical workflow involving this function, see the comment at the top of this file.
Some notable properties of this function:
# Processing of the raw frame parent args using `process()` in [[Module:parameters]] can occur either inside of this
function (the usual workflow) or outside of this function (for more complex cases). In the former case the raw parent
args are passed in along with a partially built `params` structure of the sort required by [[Module:parameters]],
containing only the term list itself along with any other parameters that are '''not''' term properties (such as
a language code in {{para|1}} and boolean flags like {{para|nocat}}, {{para|nocap}}, etc.). This structure is
''augmented'' with list parameters, one for each per-term property, and [[Module:parameters]] is invoked. In the
latter case where raw argument processing is done by the caller, they must build the partial `params` structure;
augment it themselves using `augment_params_with_modifiers()`; call [[Module:parameters]] themselves; and pass in the
processed arguments. In both cases, the return value of this function contains three values: a list of objects, one
per term, specifying the term and all properties; the processed arguments structure, so that the non-term-property
arguments can be processed as appropriate; and an object containing miscellaneous global computed properties
(currently only `use_semicolon`; see below).
# Optionally, each term can consist of a number of ''subitems'' separated by delimiters (usually a comma, but the
possible delimiter or delimiters are controllable). Each subitem can have its own inline modifiers. This functionality
is used, for example, by {{tl|col}} and variants, which allow each row to have comma-separated or tilde-separated
subitems. When this feature is invoked, the format of the per-term object changes; instead of directly being an object
describing the term and its properties, it is an object with a `terms` field containing a list of per-subitem objects
along with other top-level fields describing per-term properties. By default, if there are separate parameters
specified along with multiple subitems, an error occurs, but this is controllable; currently, you can request that the
parameters be assigned to the first or last subitem.
# By default, special ''separator'' arguments may be present, mixed in among regular term arguments. Examples of such
separator arguments are (by default; this can be overridden) a bare semicolon, specifying that the terms on either
side should be separated by a semicolon instead of a comma (indicating a higher-level grouping); a bare tilde,
replacing the comma separator with a tilde (indicating that the terms on either side are alternants); and a bare
underscore, replacing the comma separator with a space. Separator arguments are ignored when numbering the separate
parameters. You disable the separator argument handling entirely if it doesn't make sense to have this (e.g. in
{{tl|af}}/{{tl|affix}}, where the separator is always a {{cd|+}} sign).
`data` is an object containing several possible fields.
1. Fields that are required or recommended (usually related to argument processing):
* `raw_args` ('''required''' unless `processed_args` is specified): The raw arguments, normally fetched from
{frame:getParent().args}. They are parsed using `process()` in [[Module:parameters]]. Most callers pass in raw
arguments.
* `processed_args`: The object of parsed arguments returned by `process()` in [[Module:parameters]]. One (but not both)
of `raw_args` and `processed_args` must be set.
* `param_mods` ('''required'''): A structure describing the possible inline modifiers and their properties. See the
introductory comment above. Most often, this is generated using `construct_param_mods()` rather than specified
manually.
* `params` ('''required''' unless `processed_args` is specified): A structure describing the possible parameters,
'''other than''' the ones that are separate-parameter equivalents of inline modifiers. This is automatically
"augmented" with the separate-parameter equivalents of the inline modifiers described in `param_mods` prior to parsing
the raw arguments with [[Module:parameters]]. '''WARNING:''' This structure is destructively modified, both by the
"augmentation" process of adding separate-parameter equivalents of inline modifiers, and by the processing done by
[[Module:parameters]] itself. (Nonetheless, substructures can safely be shared in this structure, and will be
correctly handled.)
* `termarg` ('''required'''): The argument containing the first item with attached inline modifiers to be parsed.
Usually a numeric value such as {1} or {2}.
* `track_module` ('''recommended'''): The name of the calling module, for use in adding tracking pages that are used
internally to track pages containing template invocations with certain properties. Example properties tracked are
missing items with corresponding properties as well as missing items without corresponding properties (which are
skipped entirely). To find out the exact properties tracked and the name of the tracking pages, read the code.
* `lang` ('''recommended'''): The language object for the language of the items, or the name of the argument to fetch
the object from. It is not strictly necessary to specify this, as this function only initializes items based on inline
modifiers and separate arguments and doesn't actually format the resulting items. However, if specified, it is used
for certain purposes:
*# It specifies the default for the `lang` property of returned objects if not otherwise set (e.g. by a language
prefix).
*# It is used to initialize an internal cache for speeding up language-code parsing (primarily useful if the same
language code may appear in several items, such as with {{tl|col}} and related templates).
The value of `lang` can be any of the following:
* If a string of the form "foo.default", it is assumed to be requesting the value of `args["foo"].default`.
* Otherwise, if a string or number, it is assumed to be requesting the value of `args` at that key. Note that if the
string is in the form of a number (e.g. "3"), it is normalized to a number prior to fetching (this also happens with
a spec like "2.default").
* Otherwise, if a function, it is assumed to be a function to return the argument value given `args`, which is passed
to the function as its only argument.
* Otherwise, it is used directly.
* `sc` ('''recommended'''): The script object for the items, or the name of the argument to fetch the object from. The
possible values and their handling are the same as with `lang`. In general, as with `lang`, it is not strictly
necessary to specify this. However, if specified, it is used to supply the default for the `sc` property of returned
items if not otherwise set (e.g. by the {{para|sc<var>N</var>}} parameter or `<sc:...>` inline modifier). The most
common value is {"sc.default"}.
2. Other argument-related fields:
* `process_args_before_parsing`: An optional function to apply further processing to the processed `args` structure
returned by [[Module:parameters]], before parsing inline modifiers. This is passed one argument, the processed
arguments. It should make modifications in-place.
* `term_dest`: The field to store the value of the item itself into, after inline modifiers and (if allowed) language
prefixes are stripped off. Defaults to {"term"}.
* `pre_normalize_modifiers`: As in `parse_inline_modifiers()`.
* `allow_conflicting_inline_mods_and_separate_params`: If specified, don't throw an error if a value is specified for
a given property using both an inline modifier and separate param; in this case, the inline modifier takes precedence.
3. Fields related to language prefixes:
* `parse_lang_prefix`: If true, allow and parse off a language code prefix attached to items followed by a colon, such
as {la:minūtia} or {grc:[[σκῶρ|σκατός]]}. Etymology-only languages are allowed. Inline modifiers can be attached to
such items. The exact syntax allowed is as specified in the `parse_term_with_lang()` function in
[[Module:parse utilities]]. If `allow_multiple_lang_prefixes` is given, a {{cd|+}}-sign-separated list of language
prefixes can be attached to an item. The resulting language object is stored into the `termlang` field, and also into
the `lang` field (or in the case of `allow_multiple_lang_prefixes`, the list of language objects is stored into the
`termlangs` field, and the first specified object is stored in the `lang` field).
* `allow_multiple_lang_prefixes`: If given in conjunction with `parse_lang_prefix`, multiple language code prefixes can
be given, separated by a {{cd|+}} sign. See `parse_lang_prefix` above.
* `allow_bad_lang_prefix`: If given in conjunction with `parse_lang_prefix`, unrecognized language prefixes do not
trigger an error, but are simply ignored (and not stripped off the item). Note that, regardless of whether this is
given, prefixes before a colon do not trigger an error if they do not have the form of a language prefix or if a space
follows the colon. It is not recommended that this be given because typos in language prefixes will not trigger an
error and will tend to remain unfixed.
4. Fields related to custom/special separators:
* `disallow_custom_separators`: If specified, disallow specifying custom separators (semicolon, underscore, tilde; see
the internal `default_special_separators` table, or the `special_separators` field) as an item value to override the
default separator. By default, the previous separator of each item is considered to be an empty string (for the first
item) and otherwise the value of the field `default_separator` (normally a comma + space), unless either the preceding
item is one of the values listed in `special_separators`, such as a bare semicolon (which causes the following item's
previous separator to be a semicolon + space) or an item has an embedded comma in it (which causes ''all'' items other
than the first to have their previous separator be a semicolon + space). The previous separator of each item is set on
the item's `separator` property. Bare semicolons and other separator arguments do not count when indexing items using
separate parameters.
For example, the following is correct:
** {{tl|template|lang|item 1|q1=qualifier 1|;|item 2|q2=qualifier 2}}
If `disallow_custom_separators` is specified, however, the `separator` property is not set and separator arguments are
not recognized.
* `default_separator`: Override the default separator (normally {", "}).
* `special_separators`: Table giving the special/custom separators that can be given, and how they should display. If
not specified, the default in `default_special_separators` is used. This is a table mapping separator values (such as
{"~"}) to the corresponding display string (such as {" ~ "}).
5. Fields related to multiple subitems in a given term:
* `splitchar`: A Lua pattern. If specified, each user-specified argument can consist of multiple delimiter-separated
subitems, each of which may be followed by inline modifiers. In this case, each element in the returned list of items
is no longer an object describing an item, but instead an object with a `terms` field, whose value is a list
describing the subitems (whose format is the same as the normal format of an item in the top-level list when
`splitchar` is not specified). Each subitem object will have a `delimiter` field holding the actual delimiter
occurring before the subitem, which is useful in the case where `splitchar` matches multiple possible characters. In
this case, it is possible to specify that a given modifier can only occur after the last subitem and effectively
modifies the whole collection of subitems by setting {overall = true} on the modifier. In this case, the modifier's
value will be stored in the top-level object (the object with the `terms` field specifying the subitems). Note that
splitting on delimiters will not happen in certain protected sequences (by default comma+whitespace; see below). In
addition, the algorithm to split on delimiters is sensitive to inline modifier syntax and will not be confused by
delimiters inside of inline modifiers or inside of square brackets, which do not trigger splitting (whether or not
contained within protected sequences).
* `escape_fun` and `unescape_fun`: As in `split_escaping()` and `split_alternating_runs_escaping()` in
[[Module:parse utilities]]. They control the protected sequences that won't be split when `splitchar` is specified
(see previous item). By default, `escape_comma_whitespace` and `unescape_comma_whitespace` are used, so that
comma+whitespace sequences won't be split.
* `subitem_param_handling`: How to handle separate parameters that are specified in the presence of multiple subitems.
The possible values are {"only"} (only allow separate parameters if there aren't any subitems, otherwise throw an
error), {"first"} (store the separate parameters in the first subitem) and {"last"} (store the separate parameters
in the last subitem). The default is {"only"}. As a special case, an {{para|scN}} separate parameter will be stored
into all subitems.
* `subitem_separator_map`: Table mapping user-specified delimiters to displayed separators, stored in the `separator`
field of the subitem. If not specified, it defaults to `default_subitem_separator_map`. Note that the presence of an
item in this table does not mean that it can be used as a delimiter; only the delimiters specified using `splitchar`
are recognized. Delimiters not in this map display as-is.
6. Other fields:
* `dont_skip_items`: Normally, items that are completely unspecified (have no term and no properties) are skipped and
not inserted into the returned list of items. (Such items cannot occur if {disallow_holes = true} is set on the term
specification in the `params` structure passed to `process()` in [[Module:parameters]]. It is generally recommended
to do so unless a specific meaning is associated the term value being missing.) If `dont_skip_items` is set, however,
items are never skipped, and completely unspecified items will be returned along with others. (They will not have
the term or any properties set, but will have the normal non-property fields set; see below.)
* `stop_when`: If specified, a function to determine when to prematurely stop processing items. It is passed a single
argument, an object containing the following fields:
** `term`: The raw term, prior to parsing off language prefixes and inline modifiers (since the processing of
`stop_when` happens before parsing the term).
** `any_param_at_index`: True if any separate property parameters exist for this item.
** `orig_index`: Same as `orig_index` below.
** `itemno`: Same as `itemno` below.
** `stored_itemno`: The index where this item will be stored into the returned items table. This may differ from
`itemno` due to skipped items (it will never be different if `dont_skip_items` is set).
The function should return true to stop processing items and return the ones processed so far (not including the item
currently being processed). This is used, for example, in [[Module:alternative forms]], where an unspecified item
signal the end of items and the start of labels.
* `no_show_decorations`: If set, don't automatically set {show_decorations = true} on items or subitems that have
decorations (i.e. qualifiers, labels or references) attached to them. Normally, {show_decorations = true} is set,
causing `full_link()` in [[Module:links]] to appropriately display the decorations when showing the item or subitem.
If you handle this display yourself, set {no_show_decorations = true} to prevent double display of decorations.
Three values are returned: the list of items; the processed `args` structure; and an object of miscellaneous computed
global values (currently only `use_semicolon`, indicating that commas were found in individual arguments and so the
default separator should be a semicolon). In each returned item, there will be one field set for each specified property
(either through inline modifiers or separate parameters). If subitems are not allowed, each item directly has fields set
on it for the specified properties. If subitems ''are'' allowed, each item contains a `terms` field, which is a list of
subitem objects, each of which has fields set on it for the specified properties of that subitem. In addition, the
following fields may be set on each item or subitem:
* `term`: The term portion of the item (minus inline modifiers and language prefixes). {nil} if no term was given.
* `orig_index`: The original index into the item in the items table returned by `process()` in [[Module:parameters]].
This may differ from `itemno` if there are raw semiclons and `disallow_custom_separators` is not given.
* `itemno`: The logical index of the item. The index of separate parameters corresponds to this index. This may be
different from `orig_index` in the presence of raw semicolons; see above.
* `termlang`: If there is a language prefix, the corresponding language object is stored here (only if
`parse_lang_prefix` is set and `allow_multiple_lang_prefixes` is not set).
* `termlangs`: If there is are language prefixes and both `parse_lang_prefix` and `allow_multiple_lang_prefixes` are
set, the list of corresponding language objects is stored here.
* `lang`: The language object of the item. This is set when either (a) there is a language prefix parsed off (if
multiple prefixes are allowed, this corresponds to the first one); (b) the `lang` property is allowed and specified;
(c) neither (a) nor (b) apply and the `lang` field of the overall `data` object is set, providing a default value.
* `sc`: The script object of the item. This is set when either (a) the `sc` property is allowed and specified; (b)
`sc` isn't otherwise set and the `sc` field of the overall `data` object is set, providing a default value.
* `delimiter`: If subitems are allowed, this is set on subitems and specifies the delimiter used prior to the given
subitem (e.g. {","}).
* `separator`: The separator to display before the item. Always set on subitems, and set on top-level items if
`disallow_custom_separators` is not given. Controlled by `special_separators` (for top-level items) and
`subitem_separator_map` (for subitems).
* `show_decorations`: If the item or subitem has any decorations (i.e. qualifiers, labels or references) specified,
{show_decorations = true} is normally set on the item, so that these decorations are displayed when `full_link()` is
called. Use {no_show_decorations = true} to prevent this.
]==]
function export.parse_list_with_inline_modifiers_and_separate_params(data)
validate_argument_related_fields(data)
local raw_args, termarg, param_mods, args = data.raw_args, data.termarg, data.param_mods
if raw_args then
local params = data.params
local termarg_spec = params[termarg]
if termarg_spec == true or not termarg_spec.list then
internal_error("Term spec in `data.params` must have `list` set", termarg_spec)
end
if termarg_spec == true or not (termarg_spec.allow_holes or termarg_spec.disallow_holes) then
internal_error("Term spec in `data.params` must have either `allow_holes` or `disallow_holes` set",
termarg_spec)
end
export.augment_params_with_modifiers(params, param_mods)
args = process_params(raw_args, params)
else
args = data.processed_args
end
local process_args_before_parsing = data.process_args_before_parsing
if process_args_before_parsing then
process_args_before_parsing(args)
end
-- Find the maximum index among any of the list parameters.
local term_args = args[termarg]
-- As a special case, the term args might not have a `maxindex` field because they might have
-- been declared with `disallow_holes = true`, so fall back to the actual length of the list
-- using the table_len function, since # can be unpredictable with arbitrary tables.
local maxmaxindex = term_args.maxindex or table_len(term_args)
for _, v in pairs(args) do
if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then
maxmaxindex = v.maxindex
end
end
local special_separators = data.special_separators or export.default_special_separators
local items, lang_cache, use_semicolon = {}, data.lang_cache or {}
local lang = fetch_argument(args, data.lang)
if lang then
lang_cache[lang:getCode()] = lang
end
local sc = fetch_argument(args, data.sc)
local term_dest = data.term_dest or "term"
-- FIXME: this is vulnerable to abusive inputs like 1000000=.
local itemno = 0
for i = 1, maxmaxindex do
local term = term_args[i]
if data.disallow_custom_separators or not special_separators[term] then
itemno = itemno + 1
-- Compute whether any of the separate indexed params exist for this index.
local any_param_at_index
for param_mod in pairs(param_mods) do
local argval = args[param_mod]
-- Careful with argument values that may be `false`.
if argval then
argval = argval[itemno]
end
if not argval_missing(argval) then
any_param_at_index = true
break
end
end
if data.stop_when and data.stop_when{
term = term,
-- FIXME, we should just pass in `any_param_at_index` directly.
any_param_at_index = term ~= nil or any_param_at_index,
orig_index = i,
itemno = itemno,
stored_itemno = #items + 1,
} then
break
end
-- If any of the params used for formatting this term is present, create a term and add it to the list.
if not data.dont_skip_items and term == nil and not any_param_at_index then
track("skipped-term", data.track_module)
else
if not term then
track("missing-term", data.track_module)
end
local termobj = {
itemno = itemno,
orig_index = i,
}
if not data.disallow_custom_separators then
termobj.separator = i == 1 and "" or special_separators[term_args[i - 1]]
end
-- Add 1 because first term index starts at 2.
local paramname = termarg + i - 1
if term then
local function generate_obj(term, parse_err)
return export.generate_obj_maybe_parsing_lang_prefix {
term = term,
termobj = data.splitchar and {} or termobj,
term_dest = term_dest,
paramname = paramname,
parse_lang_prefix = data.parse_lang_prefix,
parse_err = parse_err,
allow_bad_lang_prefix = data.allow_bad_lang_prefix,
allow_multiple_lang_prefixes = data.allow_multiple_lang_prefixes,
lang_cache = lang_cache,
}
end
parse_inline_modifiers(term, {
paramname = paramname,
param_mods = param_mods,
generate_obj = generate_obj,
splitchar = data.splitchar,
preserve_splitchar = true,
escape_fun = data.escape_fun,
unescape_fun = data.unescape_fun,
outer_container = data.splitchar and termobj or nil,
pre_normalize_modifiers = data.pre_normalize_modifiers,
})
end
-- FIXME: Make into an error, then remove after a month.
if data.no_show_qualifiers then
track("no_show_qualifiers")
end
local term_data = {
args = args,
param_mods = param_mods,
itemno = itemno,
termobj = termobj,
term_dest = term_dest,
has_subitems = not not data.splitchar,
lang = lang,
-- As a special case, if the caller defined a scN= separate param, set it on all subitems if there
-- are multiple, falling back to the overall sc= param.
sc = args.sc and args.sc[itemno] or sc,
subitem_param_handling = data.subitem_param_handling,
subitem_separator_map = data.subitem_separator_map or export.default_subitem_separator_map,
allow_conflicting_inline_mods_and_separate_params =
data.allow_conflicting_inline_mods_and_separate_params,
postprocess_termobj = postprocess_termobj,
disallow_custom_separators = data.disallow_custom_separators,
use_semicolon = use_semicolon,
no_show_decorations = data.no_show_decorations or data.no_show_qualifiers,
}
copy_separate_params_to_termobj_and_postprocess(term_data)
use_semicolon = term_data.use_semicolon
insert(items, termobj)
end
end
end
if not data.disallow_custom_separators then
-- Set the default separator of all those items for which a separator wasn't explicitly given to the default
-- separator, defaulting to comma + space; but if any items have embedded commas, set the separator to
-- semicolon + space.
for _, item in ipairs(items) do
if not item.separator then
item.separator = use_semicolon and "; " or data.default_separator or ", "
end
end
end
return items, args, {use_semicolon = use_semicolon}
end
--[==[
Parse a single term that may have properties specified through inline modifiers or separate parameters. This differs
from `parse_list_with_inline_modifiers_and_separate_params()` in that the latter is for parsing a list of terms, each of
which may have properties specified through inline modifiers or separate parameters. Both functions optionally support
having multiple subitems in a single term. This function is used e.g. for form-of templates
({{tl|inflection of}}/{{tl|infl of}}, {{tl|form of}}, and specific templates such as
{{tl|alt form}}/{{tl|alternative form of}}, {{tl|abbr of}}/{{tl|abbreviation of}}, {{tl|clipping of}}, and many others);
for etymology templates ({{tl|bor}}/{{tl|borrowed}}, {{tl|der}}/{{tl|derived}}, etc. as well as `misc_variant` templates
like {{tl|ellipsis}}, {{tl|abbrev}}, {{tl|clipping}}, {{tl|reduplication}} and the like); and for other templates with
an argument structure similar to {{tl|l}} or {{tl|m}}. In these templates there is a term specified using a numeric
parameter and associated separate parameters specifying term properties such as {{para|t}} for the gloss or {{para|tr}}
for manual transliteration. All such properties can also be specified through inline modifiers attached directly to each
term (`<t:...>`, `<tr:...>`, etc.). Normally it is an error if both an inline modifier and separate parameter for the
same value are given, but this can be overridden (in which case inline modifiers take precedence over separate
parameters when both occur).
Some notable properties of this function:
# Processing of the raw frame parent args using `process()` in [[Module:parameters]] can occur either inside of this
function (the usual workflow) or outside of this function (for more complex cases). In the former case the raw parent
args are passed in along with a partially built `params` structure of the sort required by [[Module:parameters]],
containing only the term list itself along with any other parameters that are '''not''' term properties (such as
a language code in {{para|1}} and boolean flags like {{para|nocat}}, {{para|nocap}}, etc.). This structure is
''augmented'' with parameters, one for each per-term property, and [[Module:parameters]] is invoked. In the latter
case where raw argument processing is done by the caller, they must build the partial `params` structure; augment it
themselves using `augment_params_with_modifiers()`; call [[Module:parameters]] themselves; and pass in the processed
arguments. In both cases, the return value of this function contains two values, an object specifying the term and all
properties; and the processed arguments structure, so that the non-term-property arguments can be processed as
appropriate.
# Optionally, the term can consist of a number of ''subitems'' separated by delimiters (usually a comma, but the
possible delimiter or delimiters are controllable). Each subitem can have its own inline modifiers. This functionality
is used, for example, by form-of templates. When this feature is invoked, the format of the term object changes;
instead of directly being an object describing the term and its properties, it is an object with a `terms` field
containing a list of per-subitem objects along with other top-level fields describing per-term properties. By default,
if there are separate parameters specified along with multiple subitems, an error occurs, but this is controllable;
currently, you can request that the parameters be assigned to the first or last subitem.
`data` is an object containing several possible fields.
1. Fields that are required or recommended (usually related to argument processing):
* `raw_args` ('''required''' unless `processed_args` is specified): The raw arguments, normally fetched from
{frame:getParent().args}. They are parsed using `process()` in [[Module:parameters]]. Most callers pass in raw
arguments.
* `processed_args`: The object of parsed arguments returned by `process()` in [[Module:parameters]]. One (but not both)
of `raw_args` and `processed_args` must be set.
* `param_mods` ('''required'''): A structure describing the possible inline modifiers and their properties. See the
introductory comment above. Most often, this is generated using `construct_param_mods()` rather than specified
manually.
* `params` ('''required''' unless `processed_args` is specified): A structure describing the possible parameters,
'''other than''' the ones that are separate-parameter equivalents of inline modifiers. This is automatically
"augmented" with the separate-parameter equivalents of the inline modifiers described in `param_mods` prior to parsing
the raw arguments with [[Module:parameters]]. '''WARNING:''' This structure is destructively modified, both by the
"augmentation" process of adding separate-parameter equivalents of inline modifiers, and by the processing done by
[[Module:parameters]] itself. (Nonetheless, substructures can safely be shared in this structure, and will be
correctly handled.)
* `termarg` ('''required'''): The argument containing the item with attached inline modifiers to be parsed. Usually a
numeric value such as {1} or {2}.
* `track_module` ('''recommended'''): The name of the calling module, for use in adding tracking pages that are used
internally to track pages containing template invocations with certain properties.
* `lang` ('''recommended'''): The language object for the language of the item or subitems, or the name of the argument
to fetch the object from. It is not strictly necessary to specify this, as this function only initializes items based
on inline modifiers and separate arguments and doesn't actually format the resulting items. However, if specified, it
is used for certain purposes:
*# It specifies the default for the `lang` property of returned objects if not otherwise set (e.g. by a language
prefix).
*# It is used to initialize an internal cache for speeding up language-code parsing (primarily useful if the same
language code may appear in several subitems).
The value of `lang` can be any of the following:
* If a string or number, it is assumed to be requesting the value of `args` at that key. Note that if the string is in
the form of a number (e.g. "3"), it is normalized to a number prior to fetching.
* Otherwise, if a function, it is assumed to be a function to return the argument value given `args`, which is passed
to the function as its only argument.
* Otherwise, it is used directly.
* `sc` ('''recommended'''): The script object for the item or subitems, or the name of the argument to fetch the object
from. The possible values and their handling are the same as with `lang`. In general, as with `lang`, it is not
strictly necessary to specify this. However, if specified, it is used to supply the default for the `sc` property of
returned items if not otherwise set (e.g. by the {{para|sc}} parameter or `<sc:...>` inline modifier). The most common
value is {"sc"}.
* `make_separate_g_into_list`: Set this to {true} if separate gender parameters exist are are specified using
{{para|g}}, {{para|g2}}, etc. instead of using a single comma-separated {{para|g}} field.
2. Other argument-related fields:
* `adjust_params_before_arg_processing`: An optional function to further adjust the `params` structure prior to
calling `process()` in [[Module:parameters]]. This should be used when there are mismatches between the format of a
given property as an inline modifier and the corresponding property as a separate parameter (as with the {{para|g}}
parameter and {{cd|<g:...>}} modifier, but this particular case is handled by the `make_separate_g_into_list` field).
* `process_args_before_parsing`: An optional function to apply further processing to the processed `args` structure
returned by [[Module:parameters]], before parsing inline modifiers. This is passed one argument, the processed
arguments. It should make modifications in-place.
* `term_dest`: The field to store the value of the item itself into, after inline modifiers and (if allowed) language
prefixes are stripped off. Defaults to {"term"}.
* `pre_normalize_modifiers`: As in `parse_inline_modifiers()`.
* `allow_conflicting_inline_mods_and_separate_params`: If specified, don't throw an error if a value is specified for
a given property using both an inline modifier and separate param; in this case, the inline modifier takes precedence.
* `no_show_decorations`: If set, don't automatically set {show_decorations = true} on items or subitems that have
decorations (i.e. qualifiers, labels or references) attached to them. Normally, {show_decorations = true} is set,
causing `full_link()` in [[Module:links]] to appropriately display the decorations when showing the item or subitem.
If you handle this display yourself, set {no_show_decorations = true} to prevent double display of decorations.
3. Fields related to language prefixes:
* `parse_lang_prefix`: If true, allow and parse off a language code prefix attached to items followed by a colon, such
as {la:minūtia} or {grc:[[σκῶρ|σκατός]]}. Etymology-only languages are allowed. Inline modifiers can be attached to
such items. The exact syntax allowed is as specified in the `parse_term_with_lang()` function in
[[Module:parse utilities]]. If `allow_multiple_lang_prefixes` is given, a {{cd|+}}-sign-separated list of language
prefixes can be attached to an item. The resulting language object is stored into the `termlang` field, and also into
the `lang` field (or in the case of `allow_multiple_lang_prefixes`, the list of language objects is stored into the
`termlangs` field, and the first specified object is stored in the `lang` field).
* `allow_multiple_lang_prefixes`: If given in conjunction with `parse_lang_prefix`, multiple language code prefixes can
be given, separated by a {{cd|+}} sign. See `parse_lang_prefix` above.
* `allow_bad_lang_prefix`: If given in conjunction with `parse_lang_prefix`, unrecognized language prefixes do not
trigger an error, but are simply ignored (and not stripped off the item). Note that, regardless of whether this is
given, prefixes before a colon do not trigger an error if they do not have the form of a language prefix or if a space
follows the colon. It is not recommended that this be given because typos in language prefixes will not trigger an
error and will tend to remain unfixed.
4. Fields related to multiple subitems in the term:
* `splitchar`: A Lua pattern. If specified, the user-specified argument can consist of multiple delimiter-separated
subitems, each of which may be followed by inline modifiers. In this case, the first returned value is no longer an
object describing the item, but instead an object with a `terms` field, whose value is a list describing the subitems
(whose format is the same as the normal format of the item when `splitchar` is not specified). Each subitem object
will have a `delimiter` field holding the actual delimiter occurring before the subitem, which is useful in the case
where `splitchar` matches multiple possible characters. In this case, it is possible to specify that a given modifier
can only occur after the last subitem and effectively modifies the whole collection of subitems by setting
`overall = true` on the modifier. In this case, the modifier's value will be stored in the top-level object (the
object with the `terms` field specifying the subitems). Note that splitting on delimiters will not happen in certain
protected sequences (by default comma+whitespace; see below). In addition, the algorithm to split on delimiters is
sensitive to inline modifier syntax and will not be confused by delimiters inside of inline modifiers or inside of
square brackets, which do not trigger splitting (whether or not contained within protected sequences).
* `escape_fun` and `unescape_fun`: As in `split_escaping()` and `split_alternating_runs_escaping()` in
[[Module:parse utilities]]. They control the protected sequences that won't be split when `splitchar` is specified
(see previous item). By default, `escape_comma_whitespace` and `unescape_comma_whitespace` are used, so that
comma+whitespace sequences won't be split.
* `subitem_param_handling`: How to handle separate parameters that are specified in the presence of multiple subitems.
The possible values are {"only"} (only allow separate parameters if there aren't any subitems, otherwise throw an
error), {"first"} (store the separate parameters in the first subitem) and {"last"} (store the separate parameters
in the last subitem). The default is {"only"}. As a special case, an {{para|scN}} separate parameter will be stored
into all subitems.
* `subitem_separator_map`: Table mapping user-specified delimiters to displayed separators, stored in the `separator`
field of the subitem. If not specified, it defaults to `default_subitem_separator_map`. Note that the presence of an
item in this table does not mean that it can be used as a delimiter; only the delimiters specified using `splitchar`
are recognized. Delimiters not in this map display as-is.
Two values are returned, an object describing the item (or subitems) and the processed `args` structure. In the returned
item, there will be one field set for each specified property (either through inline modifiers or separate parameters).
If subitems are not allowed, the item directly has fields set on it for the specified properties. If subitems ''are''
allowed, the item contains a `terms` field, which is a list of subitem objects, each of which has fields set on it for
the specified properties of that subitem. In addition, the following fields may be set on the item or each subitem:
* `term`: The term portion of the item (minus inline modifiers and language prefixes). {nil} if no term was given.
* `termlang`: If there is a language prefix, the corresponding language object is stored here (only if
`parse_lang_prefix` is set and `allow_multiple_lang_prefixes` is not set).
* `termlangs`: If there is are language prefixes and both `parse_lang_prefix` and `allow_multiple_lang_prefixes` are
set, the list of corresponding language objects is stored here.
* `lang`: The language object of the item. This is set when either (a) there is a language prefix parsed off (if
multiple prefixes are allowed, this corresponds to the first one); (b) the `lang` property is allowed and specified;
(c) neither (a) nor (b) apply and the `lang` field of the overall `data` object is set, providing a default value.
* `sc`: The script object of the item. This is set when either (a) the `sc` property is allowed and specified; (b)
`sc` isn't otherwise set and the `sc` field of the overall `data` object is set, providing a default value.
* `delimiter`: If subitems are allowed, this specifies the delimiter used prior to the given subitem (e.g. {","}).
* `separator`: If subitems are allowed, this specifies the displayed form of the delimiter to be shown before a given
subitem. The mapping from user-specified delimiters to displayed separators is handled by `subitem_separator_map`;
see above. The first subitem always has a blank string in the `separator` field.
* `show_decorations`: If the item or subitem has any decorations (i.e. qualifiers, labels or references) specified,
{show_decorations = true} is normally set on the item, so that these decorations are displayed when `full_link()` is
called. Use {no_show_decorations = true} to prevent this.
]==]
function export.parse_term_with_inline_modifiers_and_separate_params(data)
validate_argument_related_fields(data)
local raw_args, termarg, param_mods, args = data.raw_args, data.termarg, data.param_mods
if raw_args then
local params = data.params
local termarg_spec = params[termarg]
if type(termarg_spec) == "table" and termarg_spec.list then
internal_error("Term spec in `data.params` must not have `list` set", termarg_spec)
end
export.augment_params_with_modifiers(params, param_mods, "always")
if data.make_separate_g_into_list then
-- HACK: g= is a list for compatibility, but sublist as an inline parameter.
params.g = {list = true, item_dest = "genders", type = "genders", flatten = true}
end
local adjust_params_before_arg_processing = data.adjust_params_before_arg_processing
if adjust_params_before_arg_processing then
adjust_params_before_arg_processing(params)
end
args = process_params(raw_args, params)
else
args = data.processed_args
end
local process_args_before_parsing = data.process_args_before_parsing
if process_args_before_parsing then
process_args_before_parsing(args)
end
local term, lang_cache = args[termarg], data.lang_cache
local lang = fetch_argument(args, data.lang)
if lang and lang_cache then
lang_cache[lang:getCode()] = lang
end
local sc = fetch_argument(args, data.sc)
local term_dest = data.term_dest or "term"
if not term then
track("missing-term", data.track_module)
end
local termobj, splitchar = {}, data.splitchar
if term then
local function generate_obj(term, parse_err)
return export.generate_obj_maybe_parsing_lang_prefix {
term = term,
termobj = splitchar and {} or termobj,
term_dest = term_dest,
paramname = termarg,
parse_lang_prefix = data.parse_lang_prefix,
parse_err = parse_err,
allow_bad_lang_prefix = data.allow_bad_lang_prefix,
allow_multiple_lang_prefixes = data.allow_multiple_lang_prefixes,
lang_cache = lang_cache,
}
end
parse_inline_modifiers(term, {
paramname = termarg,
param_mods = param_mods,
generate_obj = generate_obj,
splitchar = splitchar,
preserve_splitchar = true,
escape_fun = data.escape_fun,
unescape_fun = data.unescape_fun,
outer_container = splitchar and termobj or nil,
pre_normalize_modifiers = data.pre_normalize_modifiers,
})
end
-- FIXME: Make into an error, then remove after a month.
if data.no_show_qualifiers then
track("no_show_qualifiers")
end
copy_separate_params_to_termobj_and_postprocess {
args = args,
param_mods = param_mods,
termobj = termobj,
has_subitems = not not splitchar,
lang = lang,
sc = sc,
subitem_param_handling = data.subitem_param_handling,
subitem_separator_map = data.subitem_separator_map or export.default_subitem_separator_map,
allow_conflicting_inline_mods_and_separate_params = data.allow_conflicting_inline_mods_and_separate_params,
no_show_decorations = data.no_show_decorations or data.no_show_qualifiers,
}
if splitchar and termobj.terms[2] then
track("parse-term-multiple-subitems", data.track_module)
track("parse-term-multiple-subitems")
end
return termobj, args
end
return export
1kp3k2x2ejn7z1nhgqsp5b8fxr2qs7w
Module:Zh-glyph/display-phonetic
828
2651592
9909190
8566127
2026-09-29T09:54:50Z
TongcyBot
83009
同步英語維基詞典
9909190
Scribunto
text/plain
local export = {}
local m_phonetic = mw.loadData("Module:zh-glyph/phonetic")
local m_och = require("Module:och-pron").retrieve_pron
function export.show(frame)
local phonekeys = {}
for k in pairs(m_phonetic) do table.insert(phonekeys, k) end
table.sort(phonekeys)
-- l10n: Chinese introduction and project page
local result = { "下表列出了[[Wiktionary:關於漢語/參考文獻|鄭張尚芳(2003)]]所列舉的所有[[上古漢語]]諧聲系。\n==列表==" }
if mw.ustring.match(mw.loadData("Module:headword/data").pagename, "2$") then
i, j = 529, 1056
-- l10n: Chinese navigation link and project page
other = "← [[Wiktionary:關於漢語/諧聲系|上一頁]]"
else
i, j = 1, 528
-- l10n: Chinese navigation link and project page
other = "→ [[Wiktionary:關於漢語/諧聲系2|下一頁]]"
end
table.insert(result, "\n'''''<center>" .. other .. "</center>'''''")
table.insert(result, "\n{| class=\"Hani\" style=\"font-size:150%; border:1px solid var(--border-color-base,#aaa)\" cellpadding=\"10\"")
k = 1
for _, base in ipairs(phonekeys) do
if k >= i and k <= j then
local och = mw.text.split(m_och(base, nil, true) or "", ",")[1]
table.insert(result, "\n|-\n!width=17% style=\"background-color:var(--wikt-palette-cyan-2,#D1E8F0);\"|[[" .. base .. "]]" .. (och ~= "" and "<span class=\"IPA\" style=\"font-size:60%\"> (" .. och .. ")</span>" or "") .. "\n|style=\"background-color:var(--wikt-palette-yellow-1,#FAF9E8);\"|")
for derivation in mw.text.gsplit(m_phonetic[base], "") do
table.insert(result, " [[" .. derivation .. "]]")
end
end
k = k + 1
end
table.insert(result, "\n|}\n'''''<center>" .. other .. "</center>'''''")
return table.concat(result)
end
return export
ry71of2xkvm8hdwqouch08tn5a1zj4x
Module:Pron utilities
828
2655110
9909098
9137774
2026-09-29T07:27:49Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909098
Scribunto
text/plain
local export = {}
local dump = mw.dumpObject
local decorations_module = "Module:decorations"
local IPA_module = "Module:IPA"
local parameter_utilities_module = "Module:parameter utilities"
--[==[ intro:
This module provides utilities to simplify and standardize the creation of pronunciation modules, particularly when it
comes to argument parsing. Using this module, it should only be necessary to provide the function that actually converts
respelling to IPA.
]==]
local function combine_decorations(dec1, dec2)
if type(dec1) == "string" then
dec1 = {dec1}
end
if type(dec2) == "string" then
dec2 = {dec2}
end
if not dec1 then
return dec2
end
if not dec2 then
return dec1
end
local decs = mw.clone(dec1)
for _, dec in ipairs(dec2) do
table.insert(decs, dec)
end
return decs
end
--[==[
Function to parse arguments and format one or more pronunciations. Meant to be called from a module. `data` is a table
containing the following fields:<ul>
<li> `lang` ('''required'''): Language object for the respellings.
<li> `raw_args` ('''required'''): Unparsed arguments, generally taken from {frame:getParent().args}.
<li> `respelling_to_IPA` ('''required'''): Function to generate one or more pronunciations given respelling. On input, the
function is passed a single object `data` with the following fields:<ul>
<li> `respelling`: The respelling.
<li> `orig_respelling`: The original respelling; may be {nil} or `+` (which is substituted with the pagename).
<li> `item`: The item parsed from the argument containing the respelling, along with inline modifiers and separate
parameters.
<li> `args`: The parsed `args` table generated from `raw_args`.
<li> `pagename`: The pagename, either taken from `args.pagename` or from the actual pagename.</ul>
The function should return either a string (the respelling); a table with the respelling in the `pron` field and
optional left and right regular and/or accent qualifiers in (respectively) `q`, `qq`, `a` and `aa`; a list of
respelling strings; or a list of respelling+qualifier tables. If regular or accent qualifiers are returned, they will
be combined with any user-specified qualifiers (which go first).
<li> `track_module` ('''recommended'''): Name of the invoking module, for debug-tracking purposes.
<li> `include_bullet`: If specified, a bullet (`*`) will be added before the generated pronunciation. When this is given,
extra parameters {{para|bullets}} (to override the number of added bullets) and {{para|pre}} (to specify text to
insert between the bullets and the generated pronunciation) will be available. '''Use of this field is not
recommended; it is provided for compatibility only.'''
<li> `augment_params`: If specified, a key-value table of arguments to add to the `params` table, prior to parsing the
raw arguments using `process` in [[Module:parameters]]. See [[Module:fr-pron]] for an example of specifying this
field.
<li> `augment_param_mod_spec`: If specified, a list of specs of the form accepted by `construct_param_mods()` in
[[Module:parameter utilities]] to add to the default specs (which specify the following inline modifiers and separate
parameters: {{para|q}} and {{cd|<q:...>}}; {{para|qq}} and {{cd|<qq:...>}}; {{para|a}} and {{cd|<a:...>}};
{{para|aa}} and {{cd|<aa:...>}}; and {{para|ref}} and {{cd|<ref:...>}}. See [[Module:fr-pron]] for an example of
specifying this field.
</ul>
]==]
function export.format_prons(data)
local params = {
[1] = {list = true, allow_holes = true, default = "+", template_default = data.template_default},
["pagename"] = {},
["notext"] = {},
}
if data.include_bullet then
params["pre"] = {}
params["bullets"] = {type = "number", default = 1}
end
if data.augment_params then
for k, v in pairs(data.augment_params) do
params[k] = v
end
end
local m_param_utils = require(parameter_utilities_module)
local construct_param_mod_spec = {
{group = {"q", "a", "ref"}},
{group = "link", include = {"t", "gloss", "pos"}},
}
if data.augment_param_mod_spec then
for _, spec in ipairs(data.augment_param_mod_spec) do
table.insert(construct_param_mod_spec, spec)
end
end
local param_mods = m_param_utils.construct_param_mods(construct_param_mod_spec)
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = data.raw_args,
termarg = 1,
term_dest = "pron",
track_module = data.track_module,
}
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local saw_multiple_prons = false
for i, item in ipairs(items) do
local respelling = item.pron or "+"
if respelling == "+" then
respelling = pagename
end
local pron = data.respelling_to_IPA {
orig_respelling = item.pron,
respelling = respelling,
item = item,
args = args,
pagename = pagename,
}
if type(pron) == "table" and not pron.pron then
-- assume a list
if pron[1] and not pron[2] then
-- a single-element list
pron = pron[1]
else
saw_multiple_prons = true
end
end
item.pron = pron
end
if saw_multiple_prons then
-- we need to flatten out the pronuns
local pron_items = {}
for _, item in ipairs(items) do
local prons = item.pron
if type(prons) ~= "table" then
prons = {prons}
end
if prons.pron then
-- a single item with decorations, not a list
prons = {prons}
end
for i, pron in ipairs(prons) do
if type(pron) ~= "table" then
-- a string; convert to item table with decorations
pron = {pron = pron}
end
if not pron.pron then
error(("Internal error: Pronunciation table doesn't have a pronunciation in '.pron': %s"):format(
dump(pron)))
end
pron.separator = i == 1 and item.separator or " ~ "
if i == 1 then
pron.q = combine_decorations(item.q, pron.q)
pron.a = combine_decorations(item.a, pron.a)
pron.pos = item.pos
pron.gloss = item.gloss
end
if i == #prons then
pron.refs = item.refs
pron.qq = combine_decorations(item.qq, pron.qq)
pron.aa = combine_decorations(item.aa, pron.aa)
end
if not pron_items then
pron_items = {pron}
else
table.insert(pron_items, pron)
end
end
end
items = pron_items
else
for _, item in ipairs(items) do
if type(item.pron) == "table" then
if not item.pron.pron then
error(("Internal error: Pronunciation table doesn't have a pronunciation in '.pron': %s"):format(
dump(item.pron)))
end
item.q = combine_decorations(item.q, item.pron.q)
item.a = combine_decorations(item.a, item.pron.a)
item.qq = combine_decorations(item.qq, item.pron.qq)
item.aa = combine_decorations(item.aa, item.pron.aa)
item.pron = item.pron.pron
end
end
end
local prontext
local q = args.q.default
local qq = args.qq.default
local a = args.a.default
local aa = args.aa.default
if args.notext then
prontext = require(IPA_module).format_IPA_multiple(data.lang, items)
if q and q[1] or qq and qq[1] or a and a[1] or aa and aa[1] then
prontext = require(decorations_module).format_decorations {
lang = data.lang,
text = prontext,
q = q,
qq = qq,
a = a,
aa = aa,
}
end
else
prontext = require(IPA_module).format_IPA_full {
lang = data.lang,
items = items,
q = q,
qq = qq,
a = a,
aa = aa,
}
end
if args.pre then
prontext = args.pre .. " " .. prontext
end
if args.bullets and args.bullets ~= 0 then
prontext = ("*"):rep(args.bullets) .. " " .. prontext
end
return prontext
end
return export
51l8qx9wj1q31unlbzmsgwwj7vttw6m
Module:Kne-pronunciation
828
2765851
9909080
9318932
2026-09-29T07:26:47Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909080
Scribunto
text/plain
-- Forked by Yivan000 from [[Module:tl-pronunciation]].
-- This is still a complete fork, without any Kankanaey-specific changes.
-- TODO: Check if stresses and other particularities are different in Kankanaey
-- This module is also used for Northern Kankanay (xnn) pronunciations.
-- Based on [[Module:es-pronunc]] by Benwing2.
-- Adaptation by TagaSanPedroAko, Improved by Ysrael214.
-- Partly rewritten by Benwing2, merging code from [[Module:es-pronunc]] back into this module; {{tl-pr}} restructured
-- to take inline modifiers, like {{es-pr}}.
local export = {}
local force_cat = false -- enable for testing
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local accent_qualifier_module = "Module:accent qualifier"
local audio_module = "Module:audio"
local headword_data_module = "Module:headword/data"
local hyphenation_module = "Module:hyphenation"
local labels_module = "Module:labels"
local parse_utilities_module = "Module:parse utilities"
local pron_utilities_module = "Module:pron utilities"
local rhymes_module = "Module:rhymes"
local set_utilities_module = "Module:set utilities"
local langcode -- to be filled up by the langcode parameter, either kne or xnn
local lang -- require("Module:languages").getByCode("kne")
local rfind = m_str_utils.find
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local toNFC = mw.ustring.toNFC
local toNFD = mw.ustring.toNFD
local trim = mw.text.trim
local u = m_str_utils.char
local ulen = m_str_utils.len
local ulower = m_str_utils.lower
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local MACRON = u(0x0304) -- macron = ̄
local DOTOVER = u(0x0307) -- dot over = ̇
local vowel = "aeëəiou" -- vowel
local V = "[" .. vowel .. "]"
local NV = "[^" .. vowel .. "]"
local accent = AC .. GR .. CFLEX .. MACRON
local accent_c = "[" .. accent .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local separator = accent .. ipa_stress .. "# ."
local C = "[^" .. vowel .. separator .. "]" -- consonant
local unstressed_words = m_table.listToSet {
-- case markers; "nang" here is for written "ng", but can also work with nang as in the contraction na'ng and the
-- conjunction "nang"
"ang", "sa", "nang", "si", "ni", "kay",
-- letter names (abakada and modern Filipino)
"a", "ar", "ay", "ba", "bi", "da", "di", "e", "ef", "eks", "dyi", "i", "jey", "key", "em", "ma", "en", "pi", "ra",
"es", "ta", "ti", "u", "vi", "wa", "way", "ya", "yu", "zey", "zi",
"ko", "mo", "ka",--single-syllable personal pronouns
-- in some Spanish-derived terms and names; also de- prefix in compound words
"de", "del", "el", "la", "las", "los", "y"
}
local unstressed_affixes = m_table.listToSet {
-- NOTE: prefixes here aren't currently used with prefixes themselves because they are all assumed unstressed
-- in the absence of an explicit accent marker.
"mang-", "nang-", "man-", "nan-", "-om-", "-inom-", "-inm-", "-ën", "-en", "-in-",
"-an", "in-", "ma-", "na-", "-in", "ini-", "mai-", "nai-"
}
-- local nang_macron = "na" .. MACRON .. "ng"
-- local manga_acute = "manga" .. AC
local special_words = {
-- ["ng"] = nang_macron, ["ng̃"] = nang_macron, ["ñ̃g"] = nang_macron,
-- ["mga"] = manga_acute, ["mg̃a"] = manga_acute,
["y"] = "i" .. MACRON -- Spanish [[y]]
}
local function track(page)
require("Module:debug/track")("kne-pronunciation/" .. page)
return true
end
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- Combine two sets of qualifiers, either of which may be nil or a list of qualifiers. Remove duplicate qualifiers.
-- Return value is nil or a list of qualifiers.
local function combine_qualifiers(qual1, qual2)
if not qual1 then
return qual2
end
if not qual2 then
return qual1
end
local qualifiers = m_table.deepCopy(qual1)
for _, qual in ipairs(qual2) do
m_table.insertIfNot(qualifiers, qual)
end
return qualifiers
end
local function decompose(text, recompose_e_dia)
-- decompose everything but ñ and ü
text = toNFD(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
if recompose_e_dia then
text = rsub(text, ".[" .. DIA .. "]", {
["e" .. DIA] = "ë",
["E" .. DIA] = "Ë",
})
end
return text
end
local function remove_accents(str)
str = decompose(str, "recompose e-dia")
str = rsub(str, "(.)" .. accent_c, "%1")
return str
end
local function split_on_comma(term)
if term:find(",%s") then
return require(parse_utilities_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- ĵ, ɟ and ĉ are used internally to represent [d͡ʒ], [j] and [t͡ʃ]
--
function export.IPA(text, include_phonemic_syllable_boundaries)
local debug = {}
text = ulower(text)
text = decompose(text, "recompose e-dia")
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain monosyllabic words (e.g.
-- [[ang]], [[ng]], [[si]], [[na]], etc.) without stress marks be unstressed. We want to do this in most cases as
-- well with hyphenated compounds, e.g. [[bato-sa-rinyon]] and [[kalahatian-ng-buwan]]. To do this, we use a
-- capturing split on space or hyphen; in this situation, the actual words are at odd positions, and the separators
-- (always a single space or hyphen) are at even positions.
local words = rsplit(text, "([ %-])")
local function make_unstressed(word)
-- add macron to the last vowel not the first one, in case of affixes with qui/que/gui/gue (which don't
-- currently exist)
return rsub(word, "^(.*" .. V .. ")", "%1" .. MACRON)
end
local function signal_no_initial_glottal_stop(word)
return rsub(word, "^(" .. V .. ")", "◌%1")
end
for i=1, #words do
if i % 2 == 1 then -- a word, not a hyphen or space
if words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") and
words[i + 1] ~= "-" then
-- a suffix
if unstressed_affixes["-" .. words[i]] then
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] ~= "-" then
-- a prefix
if not rfind(words[i], accent_c) then
-- an unstressed prefix
words[i] = make_unstressed(words[i])
end
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") then
-- an interfix or infix
if not rfind(words[i], accent_c) then
-- an unstressed interfix or infix
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
else
-- a space-delimited word or a word in a hyphen-delimited compound
words[i] = special_words[words[i]] or words[i]
if unstressed_words[words[i]] then
words[i] = make_unstressed(words[i])
elseif words[i + 1] == "-" and (not words[i - 1] or words[i - 1] == " ") and
-- e.g. 'mag-' in [[mag-post]]
unstressed_affixes[words[i] .. "-"] then
words[i] = make_unstressed(words[i])
end
end
end
-- old code that I didn't port because I don't understand why it's being done; the purpose is to make suffixes
-- and infixes with explicit initial glottal stop be unstressed, which seems a weird exception
-- words[i] = rsub(words[i], "^%-([7ʔ])(" .. V .. ")", "-%1%2" .. MACRON) -- affix that requires glottal stop
end
text = table.concat(words, "")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[!?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
table.insert(debug, text)
-- handle certain combinations; ch ng and sh handling needs to go first
text = rsub(text, "([t]?)ch", "ts") --not the real sound
text = rsub(text, "([n]?)g̃", "ng") -- Spanish spelling support
text = rsub(text, "ng", "ŋ")
text = rsub(text, "sh", "ʃ")
--x
text = rsub(text, "([#])x(" .. V .. ")", "%1s%2")
text = rsub(text, "x", "ks")
--ll
text = rsub(text, "ll([i]?)(".. V.. ")", "ly%2")
--c, gü/gu+e or i, q
text = rsub(text, "c([iey])", "s%1")
text = rsub(text, "(" .. V .. ")gü([ie])", "%1ɡw%2")
text = rsub(text, "gü([ie])", "ɡuw%1")
text = rsub(text, "gui([aeëo])", "ɡy%1")
text = rsub(text, "gu([ie])", "ɡ%1")
text = rsub(text, "qu([ie])", "k%1")
text = rsub(text, "ü", "u")
text = rsub(text, "ë", "ə")
--alphabet-to-phoneme
text = rsub(text, "[cfgjñqrvz7]",
--["g"]="ɡ": U+0067 LATIN SMALL LETTER G → U+0261 LATIN SMALL LETTER SCRIPT G
{ ["c"] = "k", ["g"] = "ɡ", ["j"] = "ĵ", ["ñ"] = "ny", ["q"] = "k", ["r"] = "ɾ", ["7"] = "ʔ"})
-- trill in rr
text = rsub(text, "[ɾ]+", "ɾ")
text = rsub(text, "ɾ[.]ɾ", "r")
-- ts
text = rsub(text, "t_s", "ć") --not the real sound
text = rsub(text, "ts", "ĉ") --not the real sound
table.insert(debug, text)
text = rsub_repeatedly(text, "(" .. NV .. ")([u])([" .. AC .. MACRON .. "]?)([aeio])(" .. accent_c .. "?)","%1%2%3.w%4%5")
text = rsub_repeatedly(text, "(" .. V .. ")([u])([aeio])(" .. accent_c .. "?)","%1.w%3%4")
text = rsub_repeatedly(text, "(" .. V .. ")([o])([aei])(" .. accent_c .. "?)","%1.w%3%4")
text = rsub(text, "([i])([" .. AC .. MACRON .. "])([aeou])(" .. accent_c .. "?)","%1%2.y%3%4")
text = rsub(text, "([i])([aeou])(" .. accent_c .. "?)","y%2%3")
text = rsub(text, "a([".. AC .."]*)o([#.ʔ])","a%1w%2")
-- eu rules
text = rsub_repeatedly(text, "([#])([e])([u])(" .. accent_c .. "?)","%1y%3%4")
text = rsub_repeatedly(text, "(" .. NV .. ")([e])([" .. AC .. MACRON .. "]?)([u])(" .. accent_c .. "?)","%1%2%3.%4%5")
--determining whether "y" is a consonant or a vowel
text = rsub(text, "y(" .. accent_c .. ")", "i%1")
text = rsub(text, "y(" .. V .. ")", "ɟ%1") -- not the real sound
text = rsub(text,"(" .. NV .. ")y([ˈˌ.]*)([bćĉdfɡhjĵklmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","%1i%2%3")
text = rsub(text,"(" .. V .. ")y([ˈˌ.]*)([bćĉdfɡhjĵklmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","%1j%2%3")
text = rsub(text, "w(" .. V .. ")","w%1")
text = rsub(text,"(" .. NV .. ")w([ˈˌ]?)([bćĉdfɡjĵklmnɲŋpɾrsʃtvwɟzʔ#])","%1u%2%3")
table.insert(debug, text)
--vowels with grave/circumflex to vowel+glottal stop
text = rsub(text, CFLEX, AC .. GR)
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR .. "([#" .. vowel .. "])", "%1%2ʔ%3")
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR, "%1%2")
-- Add glottal stop for words starting with vowel
text = rsub(text, "([#])(" .. V .. ")", "%1ʔ%2")
text = rsub(text, "◌", "")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^hlɾrɟ" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dkt])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfɡklmnpsʃvz])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^hlɾrɟsʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeo, or stressed iu, should be syllabically divided from a following aeo or stressed iu.
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)(" .. V .. AC .. ")", "%1.%2")
text = rsub(text, "([iuə]" .. AC .. ")([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([iuə]" .. AC .. ")(" .. V .. AC .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
table.insert(debug, text)
local accent_to_stress_mark = { [AC] = "ˈ", [MACRON] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including macron indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post)
return accent_to_stress_mark[accent] .. pre .. post
end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and rfind(word, "[^aeiouəʔbcćĉdfɡghjɟĵklmnñɲŋpqrɾsʃtvwxz#]#") or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables >= 2 then
local vowel_find = false
local stress_find = false
for i=0, #syllables-1 do
if rfind(syllables[#syllables - i], V) then
if vowel_find then
syllables[#syllables - i] = "ˈ" .. syllables[#syllables - i]
stress_find = true
break
end
vowel_find = true
end
end
if vowel_find and not stress_find then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
accent_word(word, syllables)
-- Reconstruct the word.
words[j] = table.concat(syllables, ".")
end
text = table.concat(words, " ")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ")
table.insert(debug,text)
--correct final glottal stop placement
text = rsub(text,"([ˈˌ])ʔ([#]*)([ʔbĉćdfɡhĵɟklmnŋɲpɾrsʃtvwz])(" .. V .. ")","%1%2%3%4ʔ")
table.insert(debug,text)
--add temporary macron for /a/, /i/ and /u/
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([a])([.# ˈˌ])","%1%2%3%4ā%6") -- /a/ on open stressed syllables
text = rsub(text,"([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[i]([ʔbdfɡklmnŋpɾstu#])([bdɡklmnpɾst]?)","%1%2ī%3%4") -- /i/ on closed syllables regardless of stress
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([u])([ʔbdfɡiklmnŋpɾst]?)([bdɡklmnpɾst]?)","%1%2%3%4ū%6%7") -- /u/ stress checker
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([o])([ʔbdfɡiklmnŋpɾst]?)([bdɡklmnpɾst]?)","%1%2%3%4ō%6%7") -- /o/ stress checker
table.insert(debug, text)
--Corrections for diphthongs
text = rsub(text,"([aāeəiīoōuū])[iī]","%1j") --ay
text = rsub(text,"([aāeəiīoōuū])[u]","%1w") --aw
table.insert(debug, text)
--remove "ɟ" and "w" inserted on vowel pair starting with "i" and "u"
text = rsub(text,"([i])([ˈˌ]?)ɟ([aāeəouū])","%1%2%3")
text = rsub(text,"([oōuū])([ˈˌ]?)w([aāeəiī])","%1%2%3")
table.insert(debug,text)
--/z/ changes
text = rsub(text,"([aāeəoiīuū])z([ˈˌ.#])([^bdfɡĵjɟŋɾrvz])","%1s%2%3") -- /z/ turn to /s/ before some unvoiced sounds
text = rsub(text,"([^#bdfɡĵjɟnŋɾrvzaāeəoiīuū])([ˈˌ.#])z","%1%2s") -- /z/ turn to /s/ after some unvoiced sounds
text = rsub(text,"([bćĉdfɡhĵjɟklmnŋptvwz])([ˈˌ.]?)([ɟlɾst])([aāeəoōiīuū])([.]?)([z])","%1%2%3%4%5s") -- consonant cluster before /z/ turn to /s/
text = rsub_repeatedly(text, "([^z]*)z([^z]*)([^#bdfɡĵjɟnŋɾrvzˈˌ.#][ˈˌ.#]?)z", "%1z%2%3s") -- /z/ turn to /s/ if /z/ already said earlier
text = rsub_repeatedly(text, "^([#]*)([ˈˌ])([#]*)", "%1%3%2") -- Move stress inside word boundary fix at start
text = rsub_repeatedly(text, "([ ])([#]*)([ˈˌ])([#]*)", "%1%2%4%3") -- Move stress inside word boundary fix at start
local kne_IPA_table = {
["phonetic"] = text,
["phonemic"] = text
}
for key, value in pairs(kne_IPA_table) do
text = kne_IPA_table[key]
--phonetic transcription
if key == "phonetic" then
table.insert(debug, text)
-- Kankanaey /o/ and /u/
text = rsub(text,"([ou])([bdfɡlmnŋpɾstu])([bdɡklmnpɾst]?)([^#])","ʊ%2%3%4")
text = rsub(text,"([bćĉdfɡhĵɟklmnŋpɾrstvwz])([ɟlnɾstw]?)([ou])([j])[#]","%1%2ʊ%4#")
text = rsub(text,"([uū])([kʔ])","o%2")
text = rsub(text,"([uū])([bćĉdfɡhĵɟklmnŋpɾrstvwz])#","o%2#")
--Turn phonemic diphthongs to phonetic diphthongs
text = rsub(text, "([aāeəiī])j", "%1i̯")
text = rsub(text, "([ōoʊuū])j", "%1y̯")
text = rsub(text, "([aāeəiīōoʊuū])w", "%1ʊ̯")
table.insert(debug, text)
--Combine consonants (except H) followed by I/U and certain stressed vowels
text = rsub(text,"([bćĉdfɡĵklmnɲŋpɾrstvz])([lnɾst]?)i([ˈˌ.])ɟ?([āaeəoūʊ])","%3%1%2ɟ%4")
text = rsub(text,"([bćĉdfɡĵklmnɲŋpɾrstvz])([lnɾst]?)u([ˈˌ.])w?([āaeəīio])","%3%1%2w%4")
text = rsub(text,"([h])ʊ([ˈˌ.])w?([āaeəīi])","%2%1w%3") -- only for hu with (ei) combination
text = rsub_repeatedly(text, "([.]+)", ".")
table.insert(debug, text)
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdfɡhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)([u])([ʔbdfɡiklmnŋpɾst]?)([bdɡklmnpɾst]?)","%1%2%3%4ū%6%7")
-- foreign s consonant clusters
text = rsub(text,"([ˈˌ.]?)([#]*)([.]?)([s])([ʔbćĉdfɡhĵklmnŋpɾrt])([ɟlnɾstw]?)([aāeəii̯īoʊʊ̯ū])",
function(stress, boundary, syllable, s, cons1, cons2, vowel)
if stress == "" then stress = "." end
return boundary .. "ʔi" .. s .. stress .. cons1 .. cons2 .. vowel
end
)
-- text = rsub(text,"([ˈˌ])([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz]?)([ɟlnɾstw]?)([a])","%1%2%3ā")
-- text = rsub(text,"([ˈˌ])([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz]?)([ɟlnɾstw]?)([i])","%1%2%3ī")
-- text = rsub(text,"([ˈˌ])([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz]?)([ɟlnɾstw]?)([u])","%1%2%3ū")
table.insert(debug, text)
text = rsub(text,"([nŋ])([ˈˌ# .]*[bfpv])","m%2")
text = rsub(text,"([ŋ])([ˈˌ# .]*[dlmnstwz])","n%2")
--text = rsub_repeatedly(text,"([aāeəii̯īoʊʊ̯ū])([#]?)([ ]?)([ˈˌ#.])([k])([aāeəiīoʊū])","%1%2%3%4x%6") -- /k/ between vowels
--text = rsub_repeatedly(text,"([aāeəii̯īoʊʊ̯ū])([#]?)([ ]?)([ˈˌ#.])([ɡ])([aāeəiīoʊū])", "%1%2%3%4ɣ%6") -- /ɡ/ between vowels
text = rsub(text,"d([ˈˌ.])ɟ","%1ĵ") --/d/ before /j/
text = rsub(text,"d[ɟj]([aāeəii̯īoʊʊ̯ū])","ĵ%1") --/d/ before /j/
-- text = rsub(text,"s[ɟj]([aāeəii̯īoʊʊ̯ū])","ʃ%1") --/s/ before /j/
text = rsub(text,"([n])([ˈˌ# .]*[ɡk])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
--text = rsub(text,"n([ˈˌ.])ɟ","%1ɲ") -- /n/ before /j/
--text = rsub(text,"s([ˈˌ.])ɟ","%1ʃ") -- /s/ before /j/
--text = rsub(text,"z([ˈˌ.])ɟ","%1ʒ") -- /z/ before /j/
--text = rsub(text,"t([ˈˌ.])ɟ","%1ĉ") -- /t/ before /j/
text = rsub(text,"([ˈˌ.])d([ɟj])([aāeəiīoʊū])","%1ĵ%3") -- /dj/ before any vowel following stress
--text = rsub(text,"([ˈˌ.])n([ɟj])([aāeəiīoʊū])","%1ɲ%3") -- /nj/ before any vowel following stress
--text = rsub(text,"([ˈˌ.])s([ɟj])([aāeəiīoʊū])","%1ʃ%3") -- /sj/ before any vowel following stress
--text = rsub(text,"([ˈˌ.])t([ɟj])([aāeəiīoʊū])","%1ĉ%3") -- /tj/ before any vowel following stress
-- text = rsub(text,"([oʊ])([m])([.]?)([ˈ]?)([pb])","u%2%3%4%5") -- /o/ and /ʊ/ before /mb/ or /mp/
text = rsub(text,"([aāeəiīoʊū])(ɾ)([bćĉdfɡĵklmnŋpstvz])([s]?)([#.])","%1ɹ%3%4%5") -- /ɾ/ becoming /ɹ/ before consonants not part of another syllable
--final fix for phonetic diphthongs
text = rsub(text,"([a])i̯","āi̯") --ay
text = rsub(text,"([a])ʊ̯","āʊ̯") --aw
text = rsub(text,"([i])ʊ̯","īʊ̯") --iw
-- Kankanaey /i/ lowers before nasal
text = rsub(text,"([i])([# .ˈˌ]*[ŋ])","ī%2")
table.insert(debug, text)
--Change stresses before penultimate to have final syllable stress
text = rsub_repeatedly(text,"[ˈˌ]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrsʃtwvzʒ]?[ɟlnɾstw]?[āeəīoū])([^# ˈˌ]*)" ..
"([.][ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrsʃtwvzʒ]?[ɟlnɾstw]?[āeīoūaəiʊ])([^# ˈˌ]*)" ..
"[.]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrsʃtwvzʒ]?[ɟlnɾstw]?[āeīoūaəiʊ])([^# ˈˌ.]*)([# ])","ˌ%1%2%3%4ˈ%5%6%7")
-- text = rsub(text,"([ˈˌ])([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz]?)([ɟlnɾstw]?)([a])","%1%2%3ā")
-- text = rsub(text,"([ˈˌ])([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz]?)([ɟlnɾstw]?)([i])","%1%2%3ī")
-- text = rsub(text,"([ˈˌ])([ʔbćĉdfɡhĵɟklmnŋpɾrstwvz]?)([ɟlnɾstw]?)([ʊ])","%1%2%3ū")
--If final syllable is stressed but so is penultimate, mark penultimate only
-- text = rsub_repeatedly(text,"[ˌ]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrsʃtwvzʒ]?[ɟlnɾstw]?[āeəīoū])([^# .ˈˌ]*)" ..
-- "([.]?[ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrsʃtwvzʒ]?[ɟlnɾstw]?)([āaeoəīōū])([^# ˈˌ.]*)([# ])","ˈ%1%2.%4%5%6%7")
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ") -- Reset primary to secondary stresses if not on last word
-- Add vowel length to open stressed vowels
text = rsub_repeatedly(text,"([ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrsʃtwvzʒ]?)([ɟlnɾstw]?)([aāeəīioōūʊ])([ˈˌ.])","%1%2%3%4ː%5")
-- Stops are unreleased on syllable-final position
text = rsub(text,"([pbtdkɡ])([.# ˈˌ])","%1̚%2")
-- /k/ is farther back except with high front vowel /i/
text = rsub(text,"k","k̠")
text = rsub(text,"(k̠)([īi])","k%2")
text = rsub(text,"([īi])(k̠)","%1k")
--Change /e/ closer to native pronunciation.
--text = rsub(text, "e", "ɛ")
--Change /ə/ closer to native pronunciation.
text = rsub(text, "ə", "ɨ")
--change a, u to unstressed equivalents (certain forms to restore)
text = rsub(text,"a","ʌ")
--text = rsub(text,"[ou]","ʊ")
text = rsub(text,"ī","i̞")
else
text = rsub(text,"([n])([ˈˌ#.]?[ɡk])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
if not include_phonemic_syllable_boundaries then
text = rsub(text,"%.","")
end
text = rsub(text,"‿", " ")
text = rsub(text,"ʰ", "") -- Remove aspiration
text = rsub(text,"ː", "") -- Remove varying vowel lengths
end
table.insert(debug, text)
--delete temporary macron in /a/, /o/ and /u/
text = rsub(text,"ā","a")
text = rsub(text,"ī","i")
text = rsub(text,"ō","o")
text = rsub(text,"ū","u")
-- Final fix for "iy" and "uw" combination
text = rsub(text,"([iɪ])([ː]?)([ˈˌ.]*)ɟ([aɐeɛəouʊ])","%1%2%3%4")
text = rsub(text,"([uʊ])([ː]?)([ˈˌ.]*)w([aɐeɛəiɪo])","%1%2%3%4")
text = rsub(text,"([ɪ])([ː]?)([ˈˌ.]*)ɟ([i])","%1%2%3%4")
text = rsub(text,"([i])([ː]?)([.]*)ɟ([ɪ])","%1%2%3%4")
text = rsub(text,"([ʊ])([ː]?)([ˈˌ.]*)w([u])","%1%2%3%4")
text = rsub(text,"([u])([ː]?)([.]*)w([ʊ])","%1%2%3%4")
--remove "ɟ" and "w" inserted on vowel pair starting with "e" and "o"
text = rsub(text,"([ɛe])([ː]?)([ˈˌ.]*)[ɟj]([aɐo])","%1%2%3%4")
text = rsub(text,"([o])([ː]?)([ˈˌ.]*)w([aɐeɛə])","%1%2%3%4")
-- convert fake symbols to real ones
local final_conversions = {
["ĉ"] = "t͡ʃ", -- fake "ch" to real "ch"
["ć"] = "t͡s", -- fake "ts" to real "ts"
["ɟ"] = "j", -- fake "y" to real "y"
["ĵ"] = "d͡ʒ" -- fake "j" to real "j"
}
text = rsub(text, "[ć]([" .. separator .. "])", "ts%1")
text = rsub(text, "[ĉćɟĵ]", final_conversions)
-- Do not have multiple syllable break consecutively
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub_repeatedly(text, "([.]?)(‿)([.]?)", "%2")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
-- resuppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
text = rsub_repeatedly(text, "([.]?)(" .. ipa_stress_c .. ")([.]?)", "%2")
kne_IPA_table[key] = toNFC(text)
end
return kne_IPA_table
end
local function respelling_to_IPA(data)
return ("/%s/ [%s]"):format(export.IPA(data.respelling, false).phonemic, export.IPA(data.respelling, true).phonetic)
end
-- External entry point for {{kne-IPA}}.
function export.show(frame)
-- import the correct "lang" depending on if `kne` or `xnn`:
local lang = require("Module:languages").getByCode(frame.args.lang or "kne")
local parent_args = frame:getParent().args
return require(pron_utilities_module).format_prons {
lang = lang,
respelling_to_IPA = respelling_to_IPA,
raw_args = parent_args,
track_module = "kne-pronunciation",
}
end
local function parse_gloss(arg)
local poses, gloss
if arg:find("%^") then
poses, gloss = arg:match("^(.-)%^(.*)$")
if gloss == "" then
gloss = nil
end
else
gloss = arg
end
if poses then
poses = split_on_comma(poses)
local m_headword_data = mw.loadData(headword_data_module)
for i, pos in ipairs(poses) do
poses[i] = m_headword_data.pos_aliases[pos] or pos
end
end
return {
poses = poses,
gloss = gloss,
}
end
-- Parse a raw accent spec, which is one or more comma-separated accent qualifiers.
local function parse_accents(arg)
return require(labels_module).split_labels_on_comma(arg)
end
-- Return the number of syllables of a phonemic or phonetic representation, which should have syllable dividers in it
-- but no hyphens.
local function get_num_syl_from_ipa(pron)
-- Maybe we should just count vowels instead of the below code.
pron = rsub(pron, "|", " ") -- remove IPA foot boundaries
local words = rsplit(pron, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
pron = table.concat(words, ".")
return ulen(rsub(pron, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
phonemic = rsplit(phonemic, " ")
phonemic = phonemic[#phonemic]
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^" .. NV .. "*", ""):gsub("%.", "")
end
local function split_syllabified_spelling(spelling)
spelling = "#" .. spelling .. "#"
spelling = rsub_repeatedly(spelling, "%.([ #])", "·%1")
spelling = rsub_repeatedly(spelling, "#", "")
spelling = rsplit(spelling, "%.")
for key, value in ipairs(spelling) do
spelling[key] = rsub_repeatedly(value, "·", ".")
end
return spelling
end
-- "Align" syllabified respelling `syllab` to original spelling `spelling` by matching character-by-character, allowing
-- for extra syllable and accent markers in the syllabification and certain mismatches in the consonants. The goal is to
-- produce the appropriately syllabified version of the original spelling (the pagename) by matching characters in the
-- syllabified respelling to the original spelling, putting the syllable boundaries in the appropriate places in the
-- original spelling. As an example, given syllabified respelling 'a.ma.7ín' and original spelling 'amain', we would
-- like to produce 'a.ma.in'.
--
-- If we encounter an extra syllable marker (.), we allow and keep it. If we encounter an extra accent marker in the
-- syllabification, we drop it. We allow for mismatches in capitalization and for certain other mismatches, e.g. extra
-- glottal stops (written 7), h in respelling vs. g or j in the original, etc. If we can't match, we return nil
-- indicating the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local function concat_result()
-- Postprocess to remove dots (syllable boundaries) next to hyphens.
return (toNFC(table.concat(result)):gsub("%.%-", "-"):gsub("%-%.", "-"))
end
-- Remove glottal stop (7) from respelling to simplify the code below, because it's never found in the original
-- spelling. (FIXME: We should do the same for diacritics, but they're currently removed earlier, in
-- syllabify_from_spelling(). We should probably get rid of the removal there and put it here.)
syllab = decompose(syllab:gsub("ː", ""), "recompose e-dia"):gsub("7", "")
spelling = decompose(spelling, "recompose e-dia")
local syll_chars = rsplit(ulower(syllab), "")
local spelling_chars = rsplit(spelling, "")
local i = 1
local j = 1
local function matches(uci, ucj)
-- Return true if a syllabified respelling character (uci) matches the corresponding spelling char (ucj).
-- Both uci and ucj should be lowercase.
-- Sound is at the key, values are the letters sound can match
local matching_chars = {
["e"] = {"i"},
["ë"] = {"e", "a"},
["h"] = {"g", "j", "x"},
["i"] = {"e"},
["j"] = {"g"},
["k"] = {"j"},
["o"] = {"u"},
["s"] = {"j", "c"},
["w"] = {"u", "o"},
["y"] = {"i"}
}
return uci == ucj or (matching_chars[uci] and m_table.contains(matching_chars[uci], ucj) and true) or false
end
local function silent_spelling_letter(ucj)
return ucj == "h" or ucj == "'" or ucj == "-"
end
local function syll_at(pos)
return syll_chars[pos] or ""
end
local function spell_at(pos)
return spelling_chars[pos] or ""
end
local function uspell_at(pos)
local c = spelling_chars[pos]
return c and ulower(c) or ""
end
while i <= #syll_chars or j <= #spelling_chars do
local uci = syll_at(i)
local cj = spell_at(j)
local ucj = uspell_at(j)
if uci == "g" and syll_at(i - 1) == "n" and syll_at(i + 1) == "." and matches(syll_at(i + 2), ucj) and
not matches(syll_at(i + 2), uspell_at(j + 1)) then
-- As a special case, before checking whether the corresponding characters match, we have to skip an extra
-- g in an -ng- sequence in the syllabified respelling if the corresponding spelling character matches the
-- next respelling character (taking into account the syllable boundary). This is so that e.g.
-- syll='ba.rang.gay' matches spelling='barangay'. Otherwise we will match the first respelling g against
-- the spelling g and the second respelling g won't match. A similar case occurs with
-- syll='E.vang.he.lis.ta' and spelling='Evangelista'. But we need an extra condition to not do this hack
-- when syll='ba.rang.gay' matches spelling='baranggay'.
i = i + 1
elseif matches(uci, ucj) then
table.insert(result, cj)
i = i + 1
j = j + 1
elseif ucj == uspell_at(j - 1) and uci == "." and ucj ~= syll_at(i + 1) then
-- See below. We want to allow for a doubled letter in spelling that is pronounced single, and preserve the
-- doubled letter. But it's tricky in the presence of syllable boundaries on both sides of the doubled
-- letter as well as doubled letters pronounced double. Specifically, there are three possibilities,
-- exemplified by:
-- (1) syll='Mal.lig', spelling='Mallig' -> 'Mal.lig';
-- (2) syll='Ma.lig', spelling='Mallig' -> 'Ma.llig';
-- (3) syll='Wil.iam', spelling='William' -> 'Will.iam'.
-- If we copy the dot first, we get (1) and (2) right but not (3).
-- If we copy the double letter first, we get (2) and (3) right but not (1).
-- We choose to copy the dot first except in the situation exemplified by (3), where we copy the doubled
-- letter first. The condition above handles (3) (the doubled letter matches against a dot) while not
-- interfering with (1) (where the doubled letter also matches against a dot but the next letter in the
-- syllabification is the same as the doubled letter, because the doubled letter is pronounced double).
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) and uci == "." and ucj ~= syll_at(i + 1) and
not rfind(uspell_at(j + 1), V) then
-- See below for silent h or apostrophe in spelling. This condition is parallel to the one directly above
-- for silent doubled letters in spelling and handles the case of syllab='Abduramán', spelling='Abdurahman',
-- which should be syllabified 'Ab.du.rah.man'. But we need a check to see that the next spelling character
-- isn't a vowel, because in that case we want the silent letter to go after the period, e.g.
-- syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah' (the 7 is removed above).
table.insert(result, cj)
j = j + 1
elseif uci == "." then
table.insert(result, uci)
i = i + 1
elseif ucj == uspell_at(j - 1) then
-- A doubled letter in spelling that is pronounced single. Examples:
-- * syllab='Ma.líg', spelling='Mallig' -> 'Ma.llig' (with l)
-- * syllab='Lu.il.yér', spelling='Lhuillier' -> 'Lhu.ill.ier' (with l; a more complex example)
-- * syllab='a.sa.la.mu a.lai.kum', spelling='assalamu alaikum' -> 'as.sa.la.mu a.lai.kum' (with s)
-- * syllab='Jé.fer.son', spelling='Jefferson' -> 'Je.ffer.son' (with f)
-- * syllab='Je.ma', spelling='Gemma' -> 'Ge.mma' (with m)
-- * syllab='Ha.na', spelling='Hannah' -> 'Ha.nnah' (with n)
-- * syllab='A.by', spelling='Abby' -> 'A.bby' (with b)
-- * syllab='Ka.ba', spelling='Kaaba' -> 'Kaa.ba' (with a)
-- * syllab='Fu.ji', spelling='Fujii' -> 'Fu.jii' (with i)
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) then
-- A silent h, apostrophe or hyphen in spelling. Examples:
-- * syllab='adán', spelling='adhan' -> 'a.dhan'
-- * syllab='Atanasya', spelling='Athanasia' -> 'A.tha.nas.ia'
-- * syllab='Cýntiya', spelling='Cynthia' -> 'Cyn.thi.a'
-- * syllab='Ermóhenes', spelling='Hermogenes' -> 'Her.mo.ge.nes'
-- * syllab='Abduramán', spelling='Abdurahman' -> 'Ab.du.rah.man'
-- * syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah'
-- * syllab='pag7ibig', spelling='pag-ibig' -> 'pag-i.big'
table.insert(result, cj)
j = j + 1
elseif uci == AC or uci == GR or uci == CFLEX or uci == DIA or uci == TILDE or uci == MACRON or
uci == "y" or uci == "w" then
-- skip character
i = i + 1
else
-- non-matching character
mw.log(("Syllabification alignment mismatch for pagename '%s' (position %s, character %s), syllabified respelling '%s' (position %s, character %s), aligned result so far '%s'"
):format(spelling, j, ucj, syllab, i, uci, concat_result()))
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
mw.log(("Syllabification alignment mismatch for pagename '%s' (%s), syllabified respelling '%s' (%s), aligned result so far '%s'"
):format(
spelling, j > #spelling_chars and "end of string" or ("position %s, character %s"):format(j, uspell_at(j)),
syllab, i > #syll_chars and "end of string" or ("position %s, character %s"):format(i, syll_at(i)),
concat_result()))
return nil
end
return concat_result()
end
local function generate_syll_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
word = ulower(word)
return rfind(word, V) or word:find("y")
end
local function any_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word_has_vowels(word) then
return true
end
end
return false
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
local last_word = words[#words]
local should_generate_cat = #words == 1
local should_generate_rhyme =
not last_word:find("%-$") and -- no if word is a prefix
not (last_word:find("^%-") and last_word:find(MACRON)) and -- no if word is an unstressed suffix
word_has_vowels(last_word) -- no if word has no vowels (e.g. a single letter)
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_ipa(ipa)
local should_generate_cat = not ipa:find("%s")
local should_generate_rhyme = word_has_vowels(decompose(ipa))
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_termobj(termobj)
if termobj.raw then
return should_generate_rhyme_from_ipa(termobj.raw_phonemic or termobj.raw_phonetic)
else
return should_generate_rhyme_from_respelling(termobj.term)
end
end
local function process_specified_rhymes(rhymes, sylls, parsed_respellings)
local rhyme_ret = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the syllabification.
if not num_syl then
num_syl = {}
for _, syll in ipairs(sylls) do
if should_generate_rhyme_from_respelling(syll.syllabification) then
local this_num_syl = 1 + ulen(rsub(syll.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for _, pronun in ipairs(parsed.pronuns) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given), and rhyme
-- isn't suppressed (which may happen if the term has a qualifier "colloquial", "obsolete" or the
-- like or is an auto-generated "glottal stop elision" pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_ipa(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
local rhymeobj = m_table.shallowCopy(rhyme)
rhymeobj.num_syl = num_syl
table.insert(rhyme_ret, rhymeobj)
end
end
-- Parse a pronunciation modifier in `arg`, the argument portion in an inline modifier (after the prefix), which
-- specifies a pronunciation property such as rhyme, syllabification, homophones or audio. The argument can itself have
-- inline modifiers, e.g. <audio:Foo.ogg<a:Colombia>>. The allowed inline modifiers are specified by `param_mods` (of
-- the format expected by `parse_inline_modifiers()`); in addition to any modifiers specified there, the modifiers
-- <q:...>, <qq:...>, <a:...> and <aa:...> are always accepted (and can be repeated). `generate_obj` and `parse_err` are
-- like in `parse_inline_modifiers()` and specify respectively a function to generate the object into which modifier
-- properties are stored given the non-modifier part of the argument, and a function to generate an error message (given
-- the message). Normally, a comma-separated list of pronunciation properties is accepted and parsed, where each element
-- in the list can have its own inline modifiers and where no spaces are allowed next to the commas in order for them to
-- be recognized as separators. If `no_split_on_comma` is given, only a single pronunciation property is accepted. If
-- `has_outer_container` is given, the list of pronunciation properties is embedded in the `terms` property of an outer
-- container, into which other list-level modifiers can also be stored (by setting `overall = "true"` in the respective
-- spec in `param_mods`). The return value is a list if neither `no_split_on_comma` nor `has_outer_container` are given,
-- otherwise a container object (which, in the case of `has_outer_container`, will contain a list inside of it, in the
-- `terms` property).
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma, has_outer_container)
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(parse_utilities_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
outer_container = has_outer_container and {} or nil,
})
elseif no_split_on_comma then
return generate_obj(arg)
else
local retval = {}
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
if has_outer_container then
retval = {
terms = retval,
}
end
return retval
end
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
type = "number",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_syll(arg, parse_err)
local param_mods = {
cap = { overall = true},
}
-- We need to pass in has_outer_container because we have an overall property <cap:...> (the caption, defaulting
-- to "Syllabification") applying to the whole set of syllabifications.
return parse_pron_modifier(arg, parse_err, generate_syll_obj, param_mods, nil, "has outer container")
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the genders in "genders".
item_dest = "genders",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space (typically followed by
-- an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
local function syllabify_from_spelling(text, pagename)
-- Auto syllabifications start --
local vowel = vowel .. "ẃý" -- vowel
local V = "[" .. vowel .. "]"
local NV = "[^" .. vowel .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant
text = decompose(text, "recompose e-dia")
local origtext = remove_accents(text)
text = string.lower(text)
text = rsub(text, "[.] ", "․ ")
text = rsub(text, "[.]$", "․")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
text = rsub(text, "ng", "ŋ")
text = rsub(text, "ch", "ĉ")
text = rsub(text, "t_s", "ć")
text = rsub(text, "sh", "ʃ")
text = rsub(text, "gu([eëiy])", "ǵ%1")
text = rsub(text, "qu([eëiy])", "ḱ%1")
text = rsub(text, "r", "ɾ")
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "ʔ", "7")
text = rsub_repeatedly(text, "#(" .. C .. "+)u([aeio])","#%1u.%2")
text = rsub_repeatedly(text, "#(" .. C .. "+)i([aeou])","#%1i.%2")
text = rsub_repeatedly(text, "(" .. C .. ")u([aeio])","#%1.u%2")
text = rsub_repeatedly(text, "(" .. C .. ")i([aeou])","#%1.i%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)u([aeio])","%1.u%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)o([aei])","%1.ó%2")
text = rsub(text, "a(" .. accent_c .. "*)o([#.7])","a%1ó%2")
-- eu rules
text = rsub_repeatedly(text, "([^" .. vowel .. "#])([e])(" .. accent_c .. "?)([u])(" .. accent_c .. "?)","%1%2%3.%4%5")
text = rsub(text, "y([ˈˌ." .. accent .. "]*)([bćĉdfgǵhjĵkḱlmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","ý%1%2")
text = rsub(text, "ý(" .. V .. ")", "y%1")
text = rsub(text, "w([ˈˌ]?)([bćĉdfgǵjĵkḱlmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","ẃ%1%2")
text = rsub(text, "ẃ(" .. V .. ")","w%1")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^lɾrɟy" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dk])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([t])([^lɾrɟys" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfgǵkḱlmnpsʃvz])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^lɾrɟysʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeëo, or stressed iu, should be syllabically divided from a following aeëo or stressed iu.
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)(" .. V .. accent_c .. ")", "%1.%2")
text = rsub(text, "([iu]" .. accent_c .. ")([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([iu]" .. accent_c .. ")(" .. V .. accent_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
text = rsub(text, "ĉ", "ch")
text = rsub(text, "ć", "ts")
text = rsub(text, "ŋ", "ng")
text = rsub(text, "ʃ", "sh")
text = rsub(text, "ǵ", "gu")
text = rsub(text, "ḱ", "qu")
text = rsub(text, "r", "rr")
text = rsub(text, "ɾ", "r")
text = remove_accents(text)
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub(text, "[.]?-[.]?", "-")
text = rsub(text, "[‿]([^ ])", "|%1")
text = rsub(text, "[.]([^ ])", "|%1")
text = rsub(text, "([|])+", "%1")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
text = rsub(text, "․", ".")
-- Fix Capitalization --
local syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" and origtext:sub(i-syllbreak, i-syllbreak) ~= "." and origtext:sub(i-syllbreak, i-syllbreak) ~= "7" then
syllbreak = syllbreak + 1
elseif origtext:sub(i-syllbreak, i-syllbreak) == text:sub(i,i):upper() then
text = table.concat({text:sub(1, i-1), text:sub(i,i):upper(), text:sub(i+1)})
end
end
-- Fix hyphens --
-- FIXME!!! Why are we relying on looking at the pagename here? This should not be happening.
origtext = pagename
if (table.concat(rsplit(origtext, "-")) == table.concat(rsplit(table.concat(rsplit(text, "|")), "-"))) then
syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" then
if origtext:sub(i-syllbreak, i-syllbreak) == "-" then
text = table.concat({text:sub(1, i-1), "-", text:sub(i+1)})
else
syllbreak = syllbreak + 1
end
end
end
end
-- FIXME! Hack -- up above we changed periods to vertical bars. The rest of the code expects periods so change
-- them back. We should clean up the code above to leave the periods alone.
return (text:gsub("|", "%."))
end
function export.syllabify_and_align(respelling, pagename)
local syllabification = syllabify_from_spelling(respelling, pagename)
return align_syllabification_to_spelling(syllabification, pagename)
end
local function css_wrap(text, classes)
return ('<span class="%s">%s</span>'):format(classes, text)
end
local function format_glosses(glosses)
if not glosses then
return ""
end
local formatted_glosses = {}
for _, glossobj in ipairs(glosses) do
local gloss_parts = {}
if glossobj.gloss then
table.insert(gloss_parts, css_wrap("“", "mention-gloss-double-quote") ..
css_wrap(glossobj.gloss, "mention-gloss") .. css_wrap("”", "mention-gloss-double-quote"))
end
if glossobj.poses then
for _, pos in ipairs(glossobj.poses) do
table.insert(gloss_parts, css_wrap(pos, "ann-pos"))
end
end
table.insert(formatted_glosses, table.concat(gloss_parts, css_wrap(",", "mention-gloss-comma") .. " "))
end
return " " .. css_wrap("(", "mention-gloss-paren annotation-paren") ..
table.concat(formatted_glosses, css_wrap(";", "mention-gloss-semicolon") .. " ") ..
css_wrap(")", "mention-gloss-paren annotation-paren")
end
local function format_pronuns(pronuns)
local pronunciations = {}
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]].
for j, pronun in ipairs(pronuns) do
local qs = pronun.q
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
end
local last_pronun = #pronunciations
if pronun.q then
pronunciations[first_pronun].q = pronun.q
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
end
end
return m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" }
end
local function format_pronun_line(parsed)
local formatted_pronuns = format_pronuns(parsed.pronuns)
local pre = is_first and parsed.pre and parsed.pre .. " " or ""
local post = is_first and parsed.post and " " .. parsed.post or ""
return pre .. formatted_pronuns .. format_glosses(parsed.t) .. post
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- External entry point for {{kne-pr}}.
function export.show_full(frame)
--------------------------------- 1. Parse the arguments. ------------------------------------
local params = {
[1] = {list = true},
["rhyme"] = {},
["syll"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
if frame.args.lang == nil then
frame.args.lang = "kne"
end
-- import the correct "lang" depending on if `kne` or `xnn`:
lang = require("Module:languages").getByCode(frame.args.lang)
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "=" .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_syll = args.syll and
parse_syll(args.syll, function(msg) overall_parse_err(msg, "syll", args.syll) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if #args.audio > 0 then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
table.insert(overall_audio, parsed_audio)
end
end
-- Parse each respelling. Individual arguments in 1=, 2=, etc. can consist of one or more comma-separated
-- respellings, each of which can have inline modifiers <q:...>, <qq:...>, <a:...>, <aa:...> or <ref:...>.
-- In addition, the respellings as a whole of a given argument can be followed by various inline modifiers,
-- such as <t:...>, <rhyme:...>, <syll:...>, etc. The result of parsing goes into `parsed_respellings`, which
-- is a list of objects (one per numbered argument), each of which is a table of the form
--
-- {
-- terms = {TERM, TERM, ...},
-- audio = {AUDIO, AUDIO, ...},
-- rhyme = {RHYME, RHYME, ...},
-- syll = {SYLL, SYLL, ...},
-- hmp = {HMP, HMP, ...},
-- t = {GLOSS, GLOSS, ...},
-- pre = "PRE-TEXT" or nil,
-- post = "POST-TEXT" or nil,
-- bullets = NUM_BULLETS,
-- accents = {"ACCENT", "ACCENT", ...},
-- }
--
-- In this structure, TERM is an object that usually has the form
--
-- {
-- term = "RESPELLING",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- Note that in this structure, "REF-SPEC" of the form parsable by parse_references() in [[Module:references]].
--
-- Alternatively, if phonemic or phonetic IPA is given in place of a respelling, TERM will have the form
--
-- {
-- raw = true,
-- phonemic = "PHONEMIC",
-- phonetic = "PHONETIC",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- AUDIO is a table of the form
--
-- {
-- file = "FILE",
-- gloss = "GLOSS",
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- a = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- aa = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- }
--
-- RHYME is a table of the form
--
-- {
-- rhyme = "RHYME",
-- num_syl = {NUM_SYL, NUM_SYL, ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- SYLL is a table of the form (where `hyph` is required to be named this way for [[Module:hyphenation]])
--
-- {
-- syllabification = "SYL.LAB.LES",
-- hyph = {"SYL", "LAB", "LES"},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- HMP is a table of the form
--
-- {
-- term = "HOMOPHONE",
-- gloss = "GLOSS" or nil,
-- pos = "POS" or nil,
-- alt = "ALT" or nil,
-- lit = "LIT" or nil,
-- id = "ID" or nil,
-- g = {"G", "G", ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- GLOSS is a table of the form
--
-- {
-- poses = {"POS", "POS", ...} or nil,
-- gloss = "GLOSS" or nil,
-- }
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
type = "number",
},
t = {
overall = true,
store = "insert",
convert = parse_gloss,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
syll = {
overall = true,
-- Not `store = "insert-flattened"`. parse_syll() does not generates a list but a structure where
-- the syllabifications are in `terms` and there's an additional overall property `cap` for the
-- caption (defaulting to "Syllabification"). FIXME: Rethink whether we even want "insert-flattened"
-- or just "insert" for the remaining pronunciation properties.
convert = parse_syll,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert", -- not "insert-flattened" because parse_audio returns a single object
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = {
item_dest = "accents",
overall = true,
convert = parse_accents,
},
}
local parsed = require(parse_utilities_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
pre_normalize_modifiers = function(data)
local modtext = data.modtext
modtext = modtext:match("^<(.*)>$")
if not modtext then
error(("Internal error: Passed-in modifier isn't surrounded by angle brackets: %s"):format(
data.modtext))
end
if modtext:find("%^") and not modtext:find("^t:") then
modtext = "t:" .. modtext
end
return "<" .. modtext .. ">"
end,
splitchar = ",",
outer_container = {},
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "=" .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
bullets = 1,
})
end
end
--------------------------------- 2. Generate IPA, rhymes and syllabification. ------------------------------------
-- Used for categorization below.
local syllabification_alignment_failed = false
-- Canonicalize syllabifications in `sylls` by convering '+' to the default syllabification of the pagename, '#' to
-- the pagename itself, and '-' to no syllabification (return `null_syll`). If '-' not seen, return `sylls`.
local function canonicalize_syllabification(sylls, null_syll)
for _, syll in ipairs(sylls.terms) do
if syll.syllabification == "+" then
syll.syllabification = syllabify_from_spelling(pagename, pagename)
syll.hyph = split_syllabified_spelling(syll.syllabification)
elseif syll.syllabification == "#" then
syll.syllabification = pagename
syll.hyph = {syll.syllabification}
elseif syll.syllabification == "-" then
return null_syll
end
end
return sylls
end
if overall_syll then
overall_syll = canonicalize_syllabification(overall_syll, {})
end
local function doesnt_count_for_rhyme(list)
if not list then
return false
end
local accent_no_count = {"colloquial", "obsolete", "relaxed"}
for _, item in ipairs(list) do
for _, word_no_count in ipairs(accent_no_count) do
if item:find("%f[%w]" .. word_no_count .. "%f[%W]") then
return true
end
end
end
return false
end
-- Loop over individual respellings, processing each.
local respell_ct = 1
while respell_ct <= #parsed_respellings do
local parsed = parsed_respellings[respell_ct]
-- First, sort the specified accents and default to "Kankanaey".
if not parsed.accents then
parsed.accents = {"標準坎卡奈語"}
end
-- If more than one respelling given, then if any accent or qualifier has the words 'colloquial', 'obsolete' or
-- 'relaxed' in them, don't generate a rhyme or a '#-syllable word' category.
local more_than_one_respelling = #parsed.terms > 1 or #parsed_respellings > 1
local is_standard_kne = m_table.contains(parsed.accents, "標準坎卡奈語") and true or false
local all_terms_no_rhyme = more_than_one_respelling and doesnt_count_for_rhyme(parsed.accents)
if not parsed.pronuns then
parsed.pronuns = {}
for i, term in ipairs(parsed.terms) do
local phonemic, phonetic
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
else
local ret = export.IPA(term.term, "include phonemic syllable boundaries")
phonemic = ret.phonemic
phonetic = ret.phonetic
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
local no_rhyme, rhyme_with_cat
-- Same check as above for colloquial/obsolete/relaxed but check the qualifiers, which are attached to
-- individual respellings rather than a single-line set of respellings.
no_rhyme = all_terms_no_rhyme or more_than_one_respelling and (
doesnt_count_for_rhyme(term.q) or doesnt_count_for_rhyme(term.qq)
)
if not no_rhyme then
local should_generate_rhyme, should_generate_cat = should_generate_rhyme_from_termobj(term)
no_rhyme = not should_generate_rhyme
rhyme_with_cat = should_generate_cat
end
local pronobj = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
no_rhyme = no_rhyme,
rhyme_with_cat = rhyme_with_cat,
}
table.insert(parsed.pronuns, pronobj)
local _pronobj --temporary variable
-- If [fvz] present in phonemic pronunciation, generate a "more native-sounding" variant with [pbs] in
-- place.
if pronobj.phonemic:find("[fvz]") then
local fvz_charmap = { ["f"] = "p", ["v"] = "b", ["z"] = "s"}
_pronobj = {
raw = pronobj.raw,
phonemic = pronobj.phonemic:gsub("[fvz]", fvz_charmap),
phonetic = pronobj.phonetic:gsub("[fvz]", fvz_charmap),
refs = pronobj.refs,
q = combine_qualifiers(pronobj.q, {"more native-sounding"}),
qq = pronobj.qq,
no_rhyme = pronobj.no_rhyme,
rhyme_with_cat = pronobj.rhyme_with_cat,
move_to_next_line = true,
}
table.insert(parsed.pronuns, _pronobj)
end
-- ====== Pronunciation variants: ======
local function generate_pron_variant(accents, phonemic, phonetic)
local new_pron_obj_base = m_table.deepCopy(parsed)
new_pron_obj_base.accents = accents
new_pron_obj_base.pronuns = {}
local _pronobj = {
raw = pronobj.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = pronobj.refs,
q = pronobj.q,
qq = pronobj.qq,
no_rhyme = pronobj.no_rhyme,
rhyme_with_cat = pronobj.rhyme_with_cat,
}
table.insert(new_pron_obj_base.pronuns, _pronobj)
return new_pron_obj_base
end
if is_standard_kne then
local charmap = {}
-- In some areas in Mt. Province, /s/ is pronounced as /h/
if pronobj.phonemic:find("[s]") then
charmap = { ["s"] = "h"}
table.insert(parsed_respellings, generate_pron_variant(
{"parts of", "_","Bauko", "Sabangan", "_", "and", "_", "Tadian"},
pronobj.phonemic:gsub("[s]", charmap),
pronobj.phonetic:gsub("[s]", charmap)
))
end
-- In Tadian, /nm/ is pronounced as /mm/
if pronobj.phonemic:find("n[ˈm]m?") then
table.insert(parsed_respellings, generate_pron_variant(
{"Tadian"},
pronobj.phonemic:gsub("nˈ?m", { ["nm"] = "mm", ["nˈm"] = "mˈm"}),
pronobj.phonetic:gsub("n[ˈ%.]m", { ["n%.m"] = "m.m", ["nˈm"] = "mˈm"})
))
end
-- In Tadian & Bauko, /man/ and /mën/ are the same
if pronobj.phonemic:find("^man") then
table.insert(parsed_respellings, generate_pron_variant(
{"Bauko", "Tadian"},
pronobj.phonemic:gsub("^man", { ["man"] = "mən",}),
pronobj.phonetic:gsub("^mʌ[nm]", { ["mʌn"] = "mɨn", ["mʌm"] = "mɨm",})
))
end
-- For some people in Sabangan, /r/ and /l/ are allophonic
if pronobj.phonemic:find("[rl]") then
charmap = { ["r"] = "l", ["l"] = "r"}
table.insert(parsed_respellings, generate_pron_variant(
m_table.extend(langcode ~= "kne" and {"Sagada"} or {}, {"parts of","_", "Sabangan"}),
pronobj.phonemic:gsub("[rl]", charmap),
pronobj.phonetic:gsub("[rl]", charmap)
))
end
if langcode == "xnn" then
-- In Tanulong, Sagada, /b/ and /gh/ are allophonic if on the first t͡ʃ
if pronobj.phonemic:find("^b") then
charmap = { ["b"] = "gh"}
table.insert(parsed_respellings, generate_pron_variant(
m_table.extend({"Tanulong","Sagada"}),
pronobj.phonemic:gsub("^b", charmap),
pronobj.phonetic:gsub("^b", charmap)
))
end
-- In Pide, Sagada, /d/ and /ts/ are allophonic
if pronobj.phonemic:find("[d]") then
charmap = { ["d"] = "t͡ʃ"}
table.insert(parsed_respellings, generate_pron_variant(
m_table.extend({"Pide","Sagada"}),
pronobj.phonemic:gsub("[d]", charmap),
pronobj.phonetic:gsub("[d]", charmap)
))
end
end
end
end
end
if not parsed.syll then
if not overall_syll and any_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term, pagename)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
if not parsed.syll then
parsed.syll = {terms = {}}
end
m_table.insertIfNot(parsed.syll.terms, generate_syll_obj(aligned_syll))
else
syllabification_alignment_failed = true
end
end
end
end
else
parsed.syll = canonicalize_syllabification(parsed.syll, nil)
end
if not parsed.rhyme then
if overall_rhyme then
parsed.rhyme = nil
else
-- Generate the rhymes.
for _, pronun in ipairs(parsed.pronuns) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation
-- (see `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if
-- raw phonetic-only pronun is given), and rhyme isn't suppressed (which may happen if the term has
-- a qualifier "colloquial", "obsolete" or the like or is an auto-generated "glottal stop elision"
-- pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
-- Copying qualifiers to rhymes:
-- (1) If there's only one pronunciation, displaying any associated qualifier on the rhyme is
-- is redundant, so don't do it.
-- (2) If there are multiple pronunciations, then we generally do want to copy the qualifier(s)
-- from pronunciation to rhyme, but only if a given rhyme either derives from a single
-- pronunciation, or derives from multiple pronunciations all of which share the same
-- qualifier(s). We do NOT want to combine two different qualifiers from two different
-- pronunciations.
-- (3) If there are multiple pronunciations that map to a single rhyme, and all pronunciations
-- share qualifiers, then we might consider omitting the qualifiers as redundant; but this
-- case will rarely happen so it might not be worth worrying about.
-- (4) Similarly, if there are multiple pronunciations where some have the rhyme suppressed (see
-- above), and all pronunciations share qualifiers, then we might consider omitting the
-- qualifiers as redundant; but again, this case will rarely happen (especially since in
-- almost all cases the suppressed-rhyme pronunciation will have distinctive qualifiers) so
-- it probably isn't worth worrying about. Note that in the common case where the qualifiers
-- of the rhyme-suppressed pronunciation differ from those of the rhyme-included
-- pronunciation, we do want to include the qualifiers of the rhyme-included pronunciation
-- (imagine e.g. there are two pronunciations marked "standard" and "colloquial"; we want to
-- mark the rhyme as "standard").
-- (4) There are two different types of qualifiers (left and right); when comparing qualifiers,
-- we need to compare the entire set of both qualifiers and make sure they both match
-- (although it will be rare to have both left and right qualifiers on a single
-- pronunciation).
local saw_already = false
if not parsed.rhyme then
parsed.rhyme = {}
end
for _, existing in ipairs(parsed.rhyme) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
if not m_table.deepEquals(existing.q, pronun.q) or not
m_table.deepEquals(existing.qq, pronun.qq) then
existing.q = nil
existing.qq = nil
end
break
end
end
if not saw_already then
table.insert(parsed.rhyme, {
rhyme = rhyme,
num_syl = {num_syl},
q = #parsed.pronuns > 1 and pronun.q or nil,
qq = #parsed.pronuns > 1 and pronun.qq or nil,
nocat = not pronun.rhyme_with_cat,
})
end
end
end
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = process_specified_rhymes(parsed.rhyme, parsed.syll and parsed.syll.terms or {}, {parsed})
end
end
respell_ct = respell_ct + 1
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_sylls
if overall_syll then
all_sylls = overall_syll
else
all_sylls = {}
for _, parsed in ipairs(parsed_respellings) do
if parsed.syll then
for _, syll in ipairs(parsed.syll.terms) do
m_table.insertIfNot(all_sylls, syll)
end
end
end
end
overall_rhyme = process_specified_rhymes(overall_rhyme, all_sylls, parsed_respellings)
end
end
-- Determine whether all sets of pronunciations have the same value for a pronunciation property (rhymes,
-- syllabifications or homophones). If so, we display them them only once at the bottom, otherwise beneath each set,
-- indented. This function takes one argument, the name of a slot specifying the pronunciation property, and
-- returns two values, a boolean indicating whether all values are the same and the first value seen (which will
-- be the only value seen if all values are the same).
local function all_sets_equal(parsed_slot)
local first_set
local all_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_set = parsed[parsed_slot]
elseif not m_table.deepEquals(first_set, parsed[parsed_slot]) then
all_sets_eq = false
break
end
end
return all_sets_eq, first_set
end
local all_rhyme_sets_eq, first_rhyme_ret = all_sets_equal("rhyme")
local all_syll_sets_eq, first_sylls = all_sets_equal("syll")
local all_hmp_sets_eq, first_hmps = all_sets_equal("hmp")
------------------------------ 3. Insert categories as appropriate. ---------------------------------
local categories = {}
local function get_rhymes_categories(rhymes)
if not rhymes then
return
end
for _, rhyme in ipairs(rhymes) do
-- local num_vowels_in_rhyme = #rsub(rhyme.rhyme, NV, "")
-- local penult = num_vowels_in_rhyme == 2
-- local glottal = rhyme.rhyme:find("ʔ$")
-- local pron_cat
-- if penult and glottal then
-- pron_cat = "malumi"
-- elseif penult then
-- pron_cat = "malumay"
-- elseif glottal then
-- pron_cat = "maragsa"
-- else
-- pron_cat = "mabilis"
-- end
-- m_table.insertIfNot(categories,
-- ("%s terms with %s pronunciation"):format(lang:getCanonicalName(), pron_cat))
end
end
get_rhymes_categories(overall_rhyme)
for _, parsed in ipairs(parsed_respellings) do
get_rhymes_categories(parsed.rhyme)
end
local function get_syll_categories(sylls)
if not sylls then
return
end
for _, syll in ipairs(sylls.terms) do
local syll_no_dot = "#" .. syll.syllabification .. "#"
syll_no_dot = syll.syllabification:gsub("%.([^ #])", "%1"):gsub("#", "")
if syll_no_dot ~= pagename then
mw.log(("For page '%s', saw syllabification '%s' not matching pagename"):format(
pagename, syll.syllabification))
m_table.insertIfNot(categories, ("%s terms with syllabification not matching pagename"):format(
lang:getCanonicalName()))
end
end
end
get_syll_categories(overall_syll)
for _, parsed in ipairs(parsed_respellings) do
get_syll_categories(parsed.syll)
end
if syllabification_alignment_failed then
table.insert(categories, ("%s terms where syllabification alignment failed"):format(lang:getCanonicalName()))
end
---------------------------- 4. Format IPA, rhymes and syllabification for display. -------------------------------
local function bullet_prefix(num_bullets)
return string.rep("*", num_bullets) .. " "
end
local function format_rhyme(rhymes)
return require(rhymes_module).format_rhymes {
lang = lang,
rhymes = rhymes,
force_cat = force_cat,
}
end
local function format_syllabifications(syllobj)
return require(hyphenation_module).format_hyphenations {
lang = lang,
hyphs = syllobj.terms,
caption = syllobj.cap or "音節化"
}
end
local function format_homophones(hmps)
return require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, bullet_prefix(num_bullets) .. text)
end
return table.concat(ret, "\n")
end
-- Implement grouping by accent. If there is a run of more than one consecutive set of pronunciations with the
-- same accent, the accent goes on its own line and the pronunciations with this accent go below with an extra
-- bullet.
local prev_accents
local num_seen_with_these_accents
for j, parsed in ipairs(parsed_respellings) do
if m_table.deepEquals(prev_accents, parsed.accents) then
parsed.of_several_accents = "continuation"
num_seen_with_these_accents = num_seen_with_these_accents + 1
if num_seen_with_these_accents == 2 then
parsed_respellings[j - 1].of_several_accents = "first"
end
else
prev_accents = parsed.accents
num_seen_with_these_accents = 1
end
end
-- Pull out autogenerated pronunciations and move to the next line, indented.
for _, parsed in ipairs(parsed_respellings) do
local saw_next_line_pronuns = false
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
saw_next_line_pronuns = true
break
end
end
if saw_next_line_pronuns then
local this_line_pronuns = {}
local next_line_pronuns = {}
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
table.insert(next_line_pronuns, pronun)
else
table.insert(this_line_pronuns, pronun)
end
end
-- Now see if there are qualifiers shared among all elements of the next-line pronuns and deduplicate if so.
local function deduplicate_qualifiers(field, keepfirst)
local saw_nil = false
for _, pronun in ipairs(next_line_pronuns) do
if not pronun[field] then
saw_nil = true
break
end
end
if not saw_nil then
local m_setutil = require(set_utilities_module)
local qualifiers = {}
for _, pronun in ipairs(next_line_pronuns) do
table.insert(qualifiers, m_setutil.list_to_set(pronun[field]))
end
local all_shared = m_setutil.intersect(unpack(qualifiers))
if next(all_shared) then
local first_index, last_index
if keepfirst then
first_index = 2
last_index = #pronun
else
first_index = 1
last_index = #pronun - 1
end
for i = first_index, last_index do
local pronun = next_line_pronuns[i]
local new_qualifiers = {}
for _, q in ipairs(pronun[field]) do
if not all_shared[q] then
table.insert(new_qualifiers, q)
end
end
pronun[field] = new_qualifiers
end
end
end
end
parsed.pronuns = this_line_pronuns
parsed.next_line_pronuns = next_line_pronuns
end
end
-- Now actually format the pronunciations.
local textparts = {}
local first_line = true
local function ins_line(linetext, num_bullets)
if not first_line then
table.insert(textparts, "\n")
end
first_line = false
table.insert(textparts, bullet_prefix(num_bullets) .. linetext)
end
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
local accent_grouping_offset = 0
local no_accent = #parsed.accents == 1 and parsed.accents[1] == "標準坎卡奈語"
local pronuns = format_pronun_line(parsed)
local accent_prefix
if not no_accent then
if parsed.of_several_accents == "first" then
ins_line(require(accent_qualifier_module).format_qualifiers(lang, parsed.accents), parsed.bullets)
end
if not parsed.of_several_accents then
accent_prefix = require(accent_qualifier_module).format_qualifiers(lang, parsed.accents) .. " "
else
accent_prefix = ""
accent_grouping_offset = 1
end
else
accent_prefix = ""
end
ins_line(accent_prefix .. pronuns, parsed.bullets + accent_grouping_offset)
if parsed.next_line_pronuns then
ins_line(format_pronuns(parsed.next_line_pronuns), parsed.bullets + accent_grouping_offset + 1)
end
if parsed.audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise indent audio by
-- one more bullet.
table.insert(textparts, format_audio(parsed.audio,
(#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1) + accent_grouping_offset))
end
if not all_rhyme_sets_eq and parsed.rhyme then
ins_line(format_rhyme(parsed.rhyme), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_syll_sets_eq and parsed.syll then
ins_line(format_syllabifications(parsed.syll), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_hmp_sets_eq and parsed.hmp then
ins_line(format_homophones(parsed.hmp), parsed.bullets + 1 + accent_grouping_offset)
end
end
if overall_audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
ins_line(format_rhyme(first_rhyme_ret), min_num_bullets)
end
if overall_rhyme then
ins_line(format_rhyme(overall_rhyme), min_num_bullets)
end
if all_syll_sets_eq and first_sylls then
ins_line(format_syllabifications(first_sylls), min_num_bullets)
end
if overall_syll then
ins_line(format_syllabifications(overall_syll), min_num_bullets)
end
if all_hmp_sets_eq and first_hmps then
ins_line(format_homophones(first_hmps), min_num_bullets)
end
if overall_hmp then
ins_line(format_homophones(overall_hmp), min_num_bullets)
end
return table.concat(textparts) ..
require("Module:utilities").format_categories(categories, lang, nil, nil, force_cat)
end
-- Meant to be called from a bot.
function export.pron_json(frame)
local iparams = {
[1] = {list = true, required = true},
["pagename"] = {required = true},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local data = {}
local syllabification_from_pagename = syllabify_from_spelling(iargs.pagename, iargs.pagename)
for _, respelling in ipairs(iargs[1]) do
local pronun = export.IPA(respelling, "include phonemic syllable boundaries")
local syllabification = export.syllabify_and_align(respelling, iargs.pagename)
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
table.insert(data, {
respelling = respelling,
phonemic = pronun.phonemic,
phonetic = pronun.phonetic,
syllabification = syllabification,
num_syl = num_syl,
rhyme = rhyme,
})
end
local retval = {
pagename = iargs.pagename,
syllabification_from_pagename = syllabification_from_pagename,
data = data,
}
return require("Module:JSON").toJSON(retval)
end
return export
lnizpdljrpy0l34ysdz9ehrp8mh3qh1
Module:Demonym
828
2785988
9909053
9885447
2026-09-29T07:25:02Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909053
Scribunto
text/plain
local export = {}
local force_cat = false -- set to true for testing
local decorations_module = "Module:decorations"
local form_of_module = "Module:form of"
local place_module = "Module:place"
local qualifier_module = "Module:qualifier"
local utilities_module = "Module:utilities"
local table_module = "Module:table"
local m_links = require("Module:links")
local m_languages = require("Module:languages")
local lang_en = m_languages.getByCode("en")
local dump = mw.dumpObject
local insert = table.insert
--[=[
Add decorations (qualifiers, labels and references) to a formatted part. `part` is the object describing the formatted
part, which should optionally contain:
* left qualifiers in `q`, an array of strings;
* right qualifiers in `qq`, an array of strings;
* left labels in `l`, an array of strings;
* right labels in `ll`, an array of strings;
* references in `refs`, an array either of strings (formatted reference text) or objects containing fields `text`
(formatted reference text) and optionally `name` and/or `group`;
`formatted` is the formatted version of the term itself, and `lang` is the optional language object passed into
format_decorations().
]=]
local function add_decorations(formatted, part)
local function field_non_empty(field)
local list = part[field]
if not list then
return nil
end
if type(list) ~= "table" then
error(("Internal error: Wrong type for `spec.%s`=%s, should be \"table\""):format(
field, mw.dumpObject(list)))
end
return list[1]
end
if field_non_empty("q") or field_non_empty("qq") or field_non_empty("l") or field_non_empty("ll") or
field_non_empty("refs") then
formatted = require(decorations_module).format_decorations {
lang = part.lang,
text = formatted,
q = part.q,
qq = part.qq,
l = part.l,
ll = part.ll,
refs = part.refs,
}
end
return formatted
end
local function link_with_decorations(part, face, pretext)
local retval = m_links.full_link(part, face, "allow self link")
if pretext then
retval = pretext .. retval
end
return add_decorations(retval, part)
end
local function format_glosses(data, gloss_pretext)
local has_gloss = data.gloss and data.gloss[1]
if data.lang:getCode() == "en" then
if has_gloss then
local glosstext
if not data.gloss[2] then
glosstext = ("an English gloss '%s'"):format(data.gloss[1].term)
else
for i, term in ipairs(data.gloss) do
data.gloss[i] = "'" .. term.term .. "'"
end
glosstext = ("English glosses %s"):format(table.concat(data.gloss, ", "))
end
error(("Can't specify %s for the term when the language is already English"):format(glosstext))
end
return ""
elseif has_gloss then
if data.notext then
error("Can't specify gloss along with notext=")
end
for i, gloss in ipairs(data.gloss) do
gloss.lang = gloss.lang or lang_en
data.gloss[i] = link_with_decorations(gloss, nil, gloss_pretext)
end
return table.concat(data.gloss, ", ")
else
return ""
end
end
local function format_parts(data)
local comma_in_part = false
local cats = {}
-- l10n: 分類名以語言名開頭(enwikt 用「語言代碼:」)
local full_langname = data.lang:getFullName()
for i, part in ipairs(data.parts) do
part.lang = part.lang or lang_en
local display = part.alt or part.term
if display then
display = m_links.remove_links(display)
if display:find(",") then
comma_in_part = true
end
end
if part.gloss then
-- move gloss to 'pos' so it doesn't have quotes around it
if part.pos then
part.pos = part.gloss .. ", " .. part.pos
else
part.pos = part.gloss
end
part.gloss = nil
end
local function has_comma_or_multiple(field)
return part[field] and (part[field][2] or part[field][1] and part[field][1]:find(","))
end
comma_in_part = comma_in_part or (part.pos and part.pos:find(",")) or
has_comma_or_multiple("q") or has_comma_or_multiple("qq") or
has_comma_or_multiple("l") or has_comma_or_multiple("ll")
local formatted_desc
if part.term and part.term:find("<<") then
local m_place = require(place_module)
local place_desc = m_place.parse_new_style_place_desc(part.term, data.lang)
formatted_desc = m_place.format_new_style_place_desc_for_display({}, place_desc, false)
if not data.nocat then
local this_cats = m_place.get_cats({}, {lang = data.lang, descs = {place_desc}}, "from demonym")
for _, cat in ipairs(this_cats) do
insert(cats, full_langname .. " " .. cat)
end
end
formatted_desc = add_decorations(formatted_desc, part)
else
formatted_desc = link_with_decorations(part)
end
data.parts[i] = formatted_desc
end
local nparts = #data.parts
local rettext
-- FIXME: There should be a generalization of serialCommaJoin in [[Module:table]] for this use case.
if nparts == 1 then
rettext = data.parts[1]
elseif nparts == 2 then
rettext = data.parts[1] .. "或" .. data.parts[2]
elseif comma_in_part then
rettext = table.concat(data.parts, ";", 1, nparts - 1) .. ";或" .. data.parts[nparts]
else
-- l10n: conj 給中文字時 [[Module:table]] 改用頓號分隔且不加空格
rettext = require(table_module).serialCommaJoin(data.parts, {conj = "或"})
end
return rettext, cats
end
-- WARNING: This overwrites objects in `data`.
function export.format_demonym_adj(data)
local result = {}
local cats = {}
local function ins(text)
insert(result, text)
end
local full_langcode = data.lang:getFullCode()
-- l10n: 分類名以語言名開頭(enwikt 用「語言代碼:」)
local full_langname = data.lang:getFullName()
local lang_is_en = full_langcode == "en"
if not data.nocat then
insert(cats, full_langname .. " 居民稱謂詞")
end
local glosstext = format_glosses(data)
ins(glosstext)
if glosstext ~= "" then
ins("(")
end
if not data.notext then
-- l10n: 漢語無大小寫之別,兩個分支輸出相同,保留分支以便與 enwikt 逐行對照
if glosstext == "" and lang_is_en and not data.nocap then
ins("屬於")
else
ins("屬於")
end
ins("、來自或關於")
end
local parttext, partcats = format_parts(data)
ins(parttext)
if not data.notext then
-- l10n: 漢語語序,「屬於、來自或關於……的」的收尾
ins("的")
end
if partcats[1] then
require(table_module).extend(cats, partcats)
end
if glosstext ~= "" then
ins(")")
end
if not data.nocat then
ins(require(utilities_module).format_categories(cats, data.lang, data.sort, nil, force_cat))
end
return table.concat(result)
end
-- WARNING: This overwrites objects in `data`.
function export.format_demonym_noun(data)
local result = {}
local cats = {}
local function ins(text)
insert(result, text)
end
local full_langcode = data.lang:getFullCode()
-- l10n: 分類名以語言名開頭(enwikt 用「語言代碼:」)
local full_langname = data.lang:getFullName()
local lang_is_en = full_langcode == "en"
local has_femeq = data.m and data.m[1]
data.g = data.g or has_femeq and "f" or nil
if not data.nocat then
insert(cats, full_langname .. " 居民稱謂詞")
if data.g == "m" then
insert(cats, full_langname .. " 男人")
elseif data.g == "f" then
insert(cats, full_langname .. " 女人")
end
if has_femeq then
insert(cats, full_langname .. "陰性等價名詞")
end
end
if has_femeq then
if data.notext then
error("Can't specify m= along with notext=")
end
for i, m in ipairs(data.m) do
data.m[i].lang = data.m[i].lang or data.lang
end
local femeq_data = {
-- l10n: zhwikt 的 [[Module:form of]] 先輸出詞元再接 text;漢語無大小寫之別
text = "的" .. (lang_is_en and not data.nocap and "陰性" or "陰性") .. "等價詞",
lemmas = data.m,
lemma_face = "term",
}
ins((require(form_of_module).format_form_of(femeq_data)))
ins(";")
end
local glosstext = format_glosses(data, data.g == "f" and not data.gloss_is_gendered and "[[女性]]" or nil)
ins(glosstext)
if lang_is_en and not data.notext then
-- l10n: 漢語不用不定冠詞,兩個分支都不輸出東西
if not has_femeq and not data.nocap then
ins("")
else
ins("")
end
elseif glosstext ~= "" then
ins("(")
end
-- l10n: 漢語語序,地名在前,「的(女性)本地人或居民」在後
local parttext, partcats = format_parts(data)
ins(parttext)
if not data.notext then
ins("的")
if data.g == "f" then
ins("[[女性]]")
end
ins("[[本地人]]或[[居民]]")
end
if partcats[1] then
require(table_module).extend(cats, partcats)
end
if glosstext ~= "" then
ins(")")
end
if data.g == "m" and not data.notext then
ins(" ")
ins(require(qualifier_module).format_qualifier("通常為男性"))
end
if not data.nocat then
ins(require(utilities_module).format_categories(cats, data.lang, data.sort, nil, force_cat))
end
return table.concat(result)
end
return export
2tc5eb5h3mllvwmqiyhze2ahb3skkqq
Module:Hrx-pron
828
2830064
9909075
9339825
2026-09-29T07:26:28Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909075
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
--[==[
FIXME:
1. Add unstressed words.
2. Clean up the code.
3. <aai> = /ɔːɪ̯/
4. Intervocalic <d> = /ɾ/?
8. Assimilation of nasals before stops
]==]
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local put_module = "Module:parse utilities"
local lang = require("Module:languages").getByCode("hrx")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local DIA = u(0x0308) -- diaeresis = ̈
local BREVE = u(0x0306) --breve
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeəiouàáéíóőúɔɛäëïÄËÏAIOU" -- vowels
local V = "[" .. vowel .. "]" -- vowel class
local Vo = "[" .. vowel .. u(0xFFF1) .. u(0xFFF2) .. u(0xFFF3) .. u(0xFFF4) .. u(0xFFF5) .. u(0xFFF6) ..
u(0xFFF9) .. u(0xFFFA) .. u(0xFFFB) .. u(0xFFFC) .. u(0x0400) .. u(0x0401) .. u(0x0402) .. u(0x0403) .. "]" --the extra characters stand for the long vowels in the hyphenation function
local accent = AC .. GR .. CFLEX
local accent_c = "[" .. accent .. "]"
local stress = AC .. GR
local stress_c = "[" .. AC .. GR .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class
local C_OR_WORDSEP = "[^" .. vowel .. separator_not_wordsep .. "]" -- consonant class, or word separator
local unstressed_words = require("Module:table").listToSet({
"ab", "de",
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text, remove_gr)
-- decompose everything but ä, ë, ï
text = unfd(text)
text = rsub(text, ".[" .. DIA .. BREVE .. "]", {
["a" .. DIA] = "ä",
["A" .. DIA] = "Ä",
["e" .. DIA] = "ë",
["E" .. DIA] = "Ë",
["i" .. DIA] = "ï",
["I" .. DIA] = "Ï",
["u" .. BREVE] = "ŭ",
["U" .. BREVE] = "Ŭ",
["i" .. BREVE] = "ĭ",
["I" .. BREVE] = "Ĭ",
})
if remove_gr then
text = rsub(text, "a" .. GR, "ä")
text = rsub(text, "ĭ", "i")
text = rsub(text, "ŭ", "u")
end
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text, is_spelling)
text = rsub(text, "##", "@@")
text = rsub(text, "# #", "@ @")
text = rsub(text, "#", ".")
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels
text = rsub_repeatedly(text, "(" .. Vo .. accent_c .. "*)(" .. C .. Vo .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. Vo .. accent_c .. "*" .. C .. "+)(" .. C .. Vo .. ")", "%1.%2")
-- Don't divide Cl or Cr where C is a stop or fricative, except for dl.
text = rsub(text, "([pbfvkctgɡ])%.([lr])", ".%1%2")
-- Don't divide ch
text = rsub(text, "c%.h", ".ch")
-- Part 2: Divide hiatuses (handling ai, ei, au, eu)
text = rsub(text,"([aeAE])([ui])", "%1&%2")
text = rsub_repeatedly(text, "(" .. Vo .. accent_c .. "*)(" .. Vo .. ")", "%1.%2")
text = rsub(text, "&", "")
--other fixes
text = rsub(text, "@@", "##")
text = rsub(text, "@ @", "# #")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants (vowels have been added manually to Vo)
local TEMP_aa = u(0xFFF1)
local TEMP_Aa = u(0xFFF2)
local TEMP_ee = u(0xFFF4)
local TEMP_Ee = u(0xFFF5)
local TEMP_ie = u(0xFFF3)
local TEMP_Ie = u(0xFFF6)
local TEMP_Sch = u(0xFFF7)
local TEMP_sch = u(0xFFF8)
local TEMP_Tsch = u(0x0404)
local TEMP_tsch = u(0x0405)
local TEMP_AEae = u(0xFFF9)
local TEMP_aeae = u(0xFFFA)
local TEMP_Ii = u(0xFFFB)
local TEMP_ii = u(0xFFFC)
local TEMP_Oo = u(0x0400)
local TEMP_oo = u(0x0401)
local TEMP_Uu = u(0x0402)
local TEMP_uu = u(0x0403)
--vowel+h (by default, VhV will be interpreted as long vowel+vowel)
text = rsub(text, "([aeiouäAEIOUÄ])h([aeiouä])", "%1h.%2")
--substitutions
text = rsub(text, "aa", TEMP_aa)
text = rsub(text, "Aa", TEMP_Aa)
text = rsub(text, "ee", TEMP_ee)
text = rsub(text, "Ee", TEMP_Ee)
text = rsub(text, "ii", TEMP_ii)
text = rsub(text, "Ii", TEMP_Ii)
text = rsub(text, "oo", TEMP_oo)
text = rsub(text, "Oo", TEMP_Oo)
text = rsub(text, "uu", TEMP_uu)
text = rsub(text, "Uu", TEMP_Uu)
text = rsub(text, "Ää", TEMP_AEae)
text = rsub(text, "ää", TEMP_aeae)
text = rsub(text, "tsch", TEMP_tsch)
text = rsub(text, "Tsch", TEMP_Tsch)
text = rsub(text, "sch", TEMP_sch)
text = rsub(text, "Sch", TEMP_Sch)
--diphthongs (ai, ei, au, eu + falling dipthongs respelled with <ĭŭ>)
text = rsub(text, "([ĬĭŬŭ])(" .. Vo .. ")", "%1.%2")
text = rsub(text,"([aeAE])([ui])(" .. Vo .. ")", "%1%2.%3")
text = rsub(text, "ie", TEMP_ie)
text = rsub(text, "Ie", TEMP_Ie)
--the u in qu isn't a vowel
text = rsub(text, "([Qq])u", "%1w")
text = text:gsub("%.", SYLDIV)
text = text:gsub("#", SYLDIV)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = text:gsub(SYLDIV, ".")
text = text:gsub("qw", "qu")
text = text:gsub("Qw", "Qu")
text = text:gsub(TEMP_aa, "aa")
text = text:gsub(TEMP_Aa, "Aa")
text = text:gsub(TEMP_ee, "ee")
text = text:gsub(TEMP_Ee, "Ee")
text = text:gsub(TEMP_ie, "ie")
text = text:gsub(TEMP_Ie, "Ie")
text = text:gsub(TEMP_ii, "ii")
text = text:gsub(TEMP_Ii, "Ii")
text = text:gsub(TEMP_oo, "oo")
text = text:gsub(TEMP_Oo, "Oo")
text = text:gsub(TEMP_uu, "uu")
text = text:gsub(TEMP_Uu, "Uu")
text = text:gsub(TEMP_AEae, "Ää")
text = text:gsub(TEMP_aeae, "ää")
text = text:gsub(TEMP_sch, "sch")
text = text:gsub(TEMP_Sch, "Sch")
text = text:gsub(TEMP_tsch, "tsch")
text = text:gsub(TEMP_Tsch, "Tsch")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text)
local debug = {}
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ä, ë, ï
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain
-- monosyllabic words without stress marks be unstressed.
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") and not rfind(word, accent_c) or unstressed_words[word] then
-- add CFLEX to the last vowel not the first one, or we will mess up 'que' by
-- adding the CFLEX after the 'u'
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CFLEX)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--x/ç
text = rsub(text, "x", "X") --the character <x>
text = rsub(text, "auch", "auxx")
text = rsub(text, "([aou])(" .. accent_c .. "?)ch", "%1%2xx")
text = rsub(text, "eich", "eiçç")
text = rsub(text, "euch", "euçç")
text = rsub(text, "äich", "eiçç")
text = rsub(text, "([äei])(" .. accent_c .. "?)ch", "%1%2çç")
text = rsub(text, "augh", "aux")
text = rsub(text, "([aou])(" .. accent_c .. "?)gh", "%1%2x")
text = rsub(text, "eigh", "eiç")
text = rsub(text, "eugh", "euç")
text = rsub(text, "äigh", "eiç")
text = rsub(text, "([äëei])(" .. accent_c .. "?)gh", "%1%2ç")
--c, ng, qu
text = rsub(text, "tsch", "čč")
text = rsub(text, "sch", "ʃʃ")
text = rsub(text, "#ch", "ʃ")
text = rsub(text, "ck", "kk")
text = rsub(text, "c([ei])", "s%1")
text = rsub(text, "c", "k")
text = rsub(text, "ng", "ŋŋ")
text = rsub(text, "qu", "kW") --fake symbol used to distinguish this sound from the usual /v/ written as <w>
--diphthongs/n-phthongs
text = rsub(text, "ieh", "í" .. AC)
text = rsub(text, "ih", "í" .. AC)
text = rsub(text, "eu", "ɔ" .. AC .. "ɪ̯")
text = rsub(text, "ai", "a" .. AC .. "ɪ̯")
text = rsub(text, "ei", "a" .. AC .. "ɪ̯")
text = rsub(text, "aai", "ő" .. AC .. "ɪ̯")
text = rsub(text, "au", "a" .. AC .. "ʊ̯")
text = rsub(text, "oi", "o" .. AC .. "ɪ̯")
text = rsub(text, "ĭ", "ɪ̯")
text = rsub(text, "ŭ", "ʊ̯")
-- double vowels (we will handle them with a single character)
text = rsub(text, "aa", "ő" .. AC)
text = rsub(text, "ää", "à" .. AC)
text = rsub(text, "äh", "à" .. AC)
text = rsub(text, "a" .. GR, "ä" .. AC) --note that <à> is used internally for long ä=/ɛː/
text = rsub(text, "e", "ə")
text = rsub(text, "əh", "é" .. AC)
text = rsub(text, "əər#", "é" .. AC .. "a#")
text = rsub(text, "əə", "é" .. AC)
text = rsub(text, "ëə", "é" .. AC .. "ə")
text = rsub(text, "ër", "ɚ")
text = rsub(text, "ë", "e")
text = rsub(text, "iə", "í" .. AC)
text = rsub(text, "ïə", "í" .. AC .. "ə")
text = rsub(text, "ə" .. AC, "e" .. AC)
text = rsub(text, "ii", "í" .. AC) --not really used, probably unnecesary
text = rsub(text, "oo", "ó" .. AC)
text = rsub(text, "oh", "ó" .. AC)
text = rsub(text, "uu", "ú" .. AC)
text = rsub(text, "uh", "ú" .. AC)
text = rsub(text, "ï", "i")
--consonants
text = rsub(text, "w", "vv")
text = rsub(text, "W", "w")
text = rsub(text, "tz", "zz")
text = rsub(text, "khə#", "çə#") --the suffix -che (note that c has been changed to k in a previous step)
text = rsub(text, "s([ptk])", "ʃ%1") --palatalize s before (orthographic) fortis stops (this doesn't affect borrowings with <x>)
text = rsub(text, "X", "ks")
--<ptk> represent plain consonants word-initially only when followed by a vowel
text = rsub(text, "#p(" .. V .. ")", "#P%1")
text = rsub(text, "#t(" .. V .. ")", "#T%1")
text = rsub(text, "#k(" .. V .. ")", "#K%1")
fortis_lenis_conv = {["P"] = "p", ["T"] = "t", ["K"] = "k", ["p"] = "b", ["t"] = "d", ["k"] = "g"}
text = rsub(text, "[PTKptk]", fortis_lenis_conv)
--word finally, <v> devoices to /f/
text = rsub(text, "v#", "f#")
--Sequence äer
text = rsub(text, "äər", "à" .. AC .. "a")
-- accent all words (if no accent is found, it is added after the first vowel)
local words = rsplit(text, " ")
for j, word in ipairs(words) do
if not rfind(word, accent_c) then
words[j] = rsub(word, "^(.-" .. V .. ")", "%1" .. AC)
end
end
text = table.concat(words, " ")
--Ending -er when pronounced /-ˈɛa/ and -for when pronounced /ˈfɔa/
text = rsub(text, "[eə]" .. AC .. "r#", "ɛ" .. AC .. "a#")
text = rsub(text, "#fo" .. AC .. "r#", "#fɔ" .. AC .. "a#")
--Orthographically short and phonologically long vowels, convert stressed /ə/ into /e/
local short_long_v = {{"a", "á"}, {"ä", "à"}, {"e", "é"}, {"ə", "é"}, {"i", "í"}, {"o", "ó"}, {"u", "ú"}}
for _, pair in ipairs(short_long_v) do
text = rsub(text, pair[1] .. AC .. "(" .. C .. "?)" .. "#", pair[2] .. AC .. "%1#")
text = rsub(text, pair[1] .. AC .. "(" .. C .. "?)" .. "(" .. V .. ")", pair[2] .. AC .. "%1%2")
end
text = rsub(text, "ə" .. AC, "e" .. AC)
--Rhotics
text = rsub(text, "o" .. AC .. "ər#", "ó" .. AC .. "a#")
text = rsub(text, "([eə])" .. AC .. "([mnŋr])" .. "(" .. C .. ")", "ä" .. AC .. "%2%3")
text = rsub(text, "o" .. AC .. "([mnŋr])" .. "(" .. C .. ")", "ɔ" .. AC .. "%1%2")
text = rsub(text, "ər#", "a#")
text = rsub(text, "ő" .. AC .. "r#", "ő" .. AC)
text = rsub(text, "rr#", "r#")
text = rsub(text, "r([dlnsʃčtz])", "%1%1")
text = rsub(text, "ɚ", "er")
--n -> ŋ before velar stops
text = rsub(text, "n(%.*[gk])", "ŋ%1")
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--stress
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = "ˌ", [CFLEX] = "" }
local words = rsplit(text, " ")
for j, word in ipairs(words) do
local syllables = rsplit(word, "%.")
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
words[j] = table.concat(syllables, ".")
-- suppress syllable mark before IPA stress indicator
words[j] = rsub(words[j], "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˈ%1ˌ")
end
text = table.concat(words, " ")
--final conversions
final_conversions={
["g"] = "k", ["k"] = "kʰ",
["b"] = "p", ["p"] = "pʰ",
["d"] = "t", ["t"] = "tʰ",
["ő"] = "ɔː", ["á"] = "aː", ["ä"] = "ɛ", ["à"] = "ɛː", ["é"] = "eː", ["í"] = "iː", ["ó"]= "oː", ["ú"] = "uː"}
text = rsub(text, "[gkbpdtőáäàéíóú]",final_conversions)
text = rsub(text, "#", "")
text = rsub_repeatedly(text, "(" .. C .. ")%1", "%1") --remove double consonants
text = rsub_repeatedly(text, "(" .. C .. ")([%.ˈˌ])%1", "%2%1") --remove double consonants
text = rsub(text, "z", "t͡s")
text = rsub(text, "č", "t͡ʃ")
text = rsub(text, "ˌ" .. u(0x032F), u(0x032F) .. "ˌ")
text = unfc(text)
return {text = text}
end
local function make_ret(dodialect)
local ret = {
pronun = {},
tag = {},
}
dodialect(ret)
ret.tag = tag
return ret
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic
if term.raw then
phonemic = term.raw_phonemic
else
phonemic = export.IPA(term.term)
phonemic = phonemic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = make_ret(this_dodialect_pronun)
local pronunciations = {}
-- Loop through each pronunciation.
for j, pronun in ipairs(ret.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and ret.tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, ret.tag)
else
qs = {ret.tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
end
end
ret.text = string.rep("*", args.bullets) .. " " .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" }
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡s", "ts")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.#), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab, true), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." or ci == "#" then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == CFLEX then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if parsed.pronun.pronun[1].phonemic then
if not should_generate_rhyme_from_ipa(parsed.pronun.pronun[1].phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(parsed.pronun.pronun[1].phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(put_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorators
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorators
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{fax-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = require(put_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = make_ret(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = make_ret(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = make_ret(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local rhymes = {}
for _, pron in ipairs(rhyme_ret.pronun) do
table.insert(rhymes, pron)
end
local data = {
lang = lang,
rhymes = rhymes,
q = rhyme_ret.tag and {rhyme_ret.tag} or nil,
}
return string.rep("*", num_bullets) .. " " .. require("Module:rhymes").format_rhymes(data)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
tgjr108sfn9cs51aj2sgt3ymvo6s3of
Template:Patronymic
10
2874116
9909021
8921826
2026-09-29T06:14:04Z
TongcyDai
53191
9909021
wikitext
text/x-wiki
{{#invoke:names|surname|type=patronymic}}<!--
--><noinclude>{{documentation}}[[Category:定義模板]]</noinclude>
6znwtjo8pvon4urzd3lelhvsnsftfuo
Module:Gmq-headword
828
2908811
9909071
9847580
2026-09-29T07:26:13Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909071
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[=[
Author: Benwing2
This module is eventually intended for all the headword templates of all North Germanic languages, especially the ones
that still maintain complex inflection systems (Icelandic, Faroese, Old Norse, Old Swedish, Elfdalian, etc.), but could
definitely be extended to support other North Germanic languages (Swedish, Danish, Norwegian *). Currently it only
supports Icelandic.
]=]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local rfind = mw.ustring.find
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local headword_utilities_module = "Module:headword utilities"
local m_headword_utilities = require_when_needed(headword_utilities_module)
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local inflection_utilities_module = "Module:inflection utilities"
local en_utilities_module = "Module:en-utilities"
local singularize = require_when_needed(en_utilities_module, "singularize")
local pluralize = require_when_needed(en_utilities_module, "pluralize")
local m_inflection_utilities = require_when_needed(inflection_utilities_module)
local is_adjective_module = "Module:is-adjective"
local is_common_module = "Module:is-common"
local is_noun_module = "Module:is-noun"
local dump = mw.dumpObject
local list_param = {list = true, disallow_holes = true}
-- Table of all valid genders by language, mapping user-specified gender specs to canonicalized versions.
local valid_number_suffixes = {"", "-p"}
local valid_genders = {"m", "f", "n"}
local valid_gender_specs = {}
for _, gender in ipairs(valid_genders) do
for _, number in ipairs(valid_number_suffixes) do
local spec = gender .. number
valid_gender_specs[spec] = spec
end
end
local function track(track_id)
require("Module:debug/track")("gmq-headword/" .. track_id)
return true
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = {required = true},
["lang"] = {required = true},
["def"] = {},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local args = frame:getParent().args
local poscat = iargs[1]
local langcode = iargs.lang
if langcode ~= "is" then
error("This module currently only works for lang=is")
end
local lang = require("Module:languages").getByCode(langcode, true)
local langname = lang:getCanonicalName()
local def = iargs.def
local parargs = frame:getParent().args
local params = {
["head"] = list_param,
["id"] = {},
["sort"] = {},
["nolinkhead"] = {type = "boolean"},
["json"] = {type = "boolean"},
["pagename"] = {}, -- for testing
}
if pos_functions[poscat] then
local posparams = pos_functions[poscat].params
if type(posparams) == "function" then
posparams = posparams(lang)
end
for key, val in pairs(posparams) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().text
local data = {
lang = lang,
langname = langname,
pos_category = poscat,
categories = {},
heads = args.head,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
sort_key = args.sort,
force_cat_output = force_cat,
def = def,
is_suffix = false,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
local singular_poscat = poscat
table.insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
table.insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
local multiple_data = nil
if pos_functions[poscat] then
multiple_data = pos_functions[poscat].func(args, data)
end
-- mw.ustring.toNFD performs decomposition, so letters that decompose
-- to an ASCII vowel and a diacritic, such as é, are counted as vowels and
-- do not need to be included in the pattern.
if not pagename:find("[ %-]") and not rfind(mw.ustring.lower(mw.ustring.toNFD(pagename)), "[aeiouyæœø]")
and not pagename:find("%.$") then
local cat = "拼字中沒有元音的" .. langname .. "詞"
if multiple_data then
table.insert(multiple_data[1].categories, cat)
else
table.insert(data.categories, cat)
end
end
if multiple_data then
if args.json then
return require("Module:JSON").toJSON(multiple_data)
end
return pos_functions[poscat].process_multiple_headword_data(multiple_data)
else
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
end
-- Parse user-specified arguments in 1=, 2=, etc. for @@ scraping specs. If found, look up the appropriate
-- inflection(s). Returns three values. If there is no scraping error, the first value is a list of inflection
-- specs, either directly specified by the user or scraped if an @... spec was given), the second value is the
-- singular part of speeh (taken from passed-in `args_pos` if given, else from the pos= argument of the scraped
-- inflection template if found, else nil), and the third value is nil. Otherwise, the first and second values
-- are nil and the third value is a scraping-error data structure, which the calling function should directly
-- return upstream.
local function parse_noun_adj_scraping_specs(data, declspecs, args_pos, infl_template, allow_empty_infl)
if not declspecs[2] and declspecs[1]:find("^@@") then
local declid
if declspecs[1] ~= "@@" then
declid = declspecs[1]:match("^@@%s*:%s*(.+)$")
if not declid then
error(("Syntax error in self-scraping spec '%s'"):format(declspecs[1]))
end
end
local decls = require(is_common_module).find_inflection(data.pagename, infl_template, false, false, declid)
if type(decls) == "string" then
data.alternant_multiword_spec = {scrape_errors = {decls}}
return nil, nil, {data}
end
for i, declobj in ipairs(decls.infls) do
decls.infls[i] = declobj.infl
end
args_pos = args_pos or decls.pos
declspecs = decls.infls
end
return declspecs, args_pos, nil
end
local function do_noun_adj_form(this_data, alternant_multiword_spec, slot, label, label_for_not_present, accel_form)
local forms = alternant_multiword_spec.forms[slot]
local retval
if not forms then
if not label_for_not_present then
return
end
retval = {label = "無" .. label_for_not_present}
else
retval = {label = label, accel = accel_form and {form = accel_form} or nil}
local prev_footnotes
for _, form in ipairs(forms) do
local footnotes = form.footnotes
if footnotes and prev_footnotes and m_table.deepEquals(footnotes, prev_footnotes) then
footnotes = nil
end
prev_footnotes = form.footnotes
local quals, refs
if footnotes then
quals, refs = m_inflection_utilities.convert_footnotes_to_qualifiers_and_references(footnotes)
end
local term = form.form
table.insert(retval, {term = term, q = quals, refs = refs})
end
end
table.insert(this_data.inflections, retval)
end
local function insert_noun_adj_linked_lemma_if_needed(this_data, alternant_multiword_spec, infl_module_name)
-- Use the "linked" form of the lemma as the head if no head= explicitly given.
if #this_data.heads == 0 then
this_data.heads = {}
local lemmas = require(infl_module_name).get_lemmas(alternant_multiword_spec, "linked variant")
for _, lemma_obj in ipairs(lemmas) do
local head = lemma_obj.form
--local head = alternant_multiword_spec.args.nolinkhead and lemma_obj.form or
-- require(headword_utilities_module).add_links_to_multiword_term(lemma_obj.form,
-- {split_hyphen_when_space = alternant_multiword_spec.args.splithyph})
local quals, refs
if lemma_obj.footnotes then
quals, refs = m_inflection_utilities.convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
end
table.insert(this_data.heads, {term = head, q = quals, refs = refs})
end
end
end
local function get_process_noun_adj_multiple_data(infl_template)
return function(multiple_data)
local parts = {}
local function ins(txt)
table.insert(parts, txt)
end
for i, data in ipairs(multiple_data) do
local alternant_multiword_spec = data.alternant_multiword_spec
if alternant_multiword_spec.header then
ins(("'''%s:'''<br />"):format(alternant_multiword_spec.header))
elseif alternant_multiword_spec.q then
ins((" ''%s''<br />"):format(alternant_multiword_spec.q))
elseif i > 1 then
ins(" ''or''<br />")
end
if alternant_multiword_spec.scrape_errors then
local errmsgs = {}
for _, scrape_error in ipairs(alternant_multiword_spec.scrape_errors) do
table.insert(errmsgs, '<span style="font-weight: bold; color: var(--wikt-palette-red,#CC2200);">' .. scrape_error .. "</span>")
end
ins(table.concat(errmsgs, "<br />"))
ins(require("Module:utilities").format_categories(
{("Template:%s援引錯誤的冰島語詞"):format(infl_template)}, data.lang, nil, nil,
force_cat))
else
ins(require("Module:headword").full_headword(data))
end
end
return table.concat(parts)
end
end
local function get_manual_noun_params(lang, is_proper)
return {
[1] = {alias_of = "g"},
["g"] = list_param,
["g_qual"] = {list = "g\1_qual", allow_holes = true},
["indecl"] = {type = "boolean"},
["m"] = list_param,
["f"] = list_param,
["dim"] = list_param,
["aug"] = list_param,
["pej"] = list_param,
["dem"] = list_param,
["fdem"] = list_param,
["gen"] = list_param,
["pl"] = list_param,
}
end
local function do_manual_nouns(is_proper, args, data)
local specs = valid_gender_specs[data.lang:getCode()]
for i, g in ipairs(args.g) do
local canon_g = specs[g]
if canon_g then
g = canon_g
else
error("Unrecognized gender: '" .. g .. "'")
end
track("gender-" .. g)
if args.g_qual[i] then
table.insert(data.genders, {spec = g, q = {args.g_qual[i]}})
else
table.insert(data.genders, g)
end
end
if #data.genders == 0 then
table.insert(data.genders, "?")
end
if args.indecl then
table.insert(data.inflections, {label = glossary_link("無屈折")})
table.insert(data.categories, data.langname .. "無屈折名詞")
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. If there is a corresponding qualifier field
-- `FIELD_qual`, qualifiers may additionally come from there. `label` is the label that the inflections are given,
-- which is linked to the glossary if surrounded by <<..>> (which is removed). `plpos` is the plural part of speech,
-- used in [[Category:LANGNAME PLPOS with red links in their headword lines]]. `accel` is the accelerator form, or
-- nil.
local function handle_infl(field, label, frob)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
label = label,
frob = frob,
}
end
handle_infl("gen", "屬格單數")
handle_infl("pl", "主格複數")
handle_infl("m", "陽性等價詞")
handle_infl("f", "陰性等價詞")
handle_infl("dim", "<<指小詞>>")
handle_infl("aug", "<<指大詞>>")
handle_infl("pej", "<<貶義>>")
handle_infl("dem", "<<居民稱謂詞>>")
handle_infl("fdem", "女性<<居民稱謂詞>>")
end
local function get_auto_noun_params(lang, is_proper)
return {
[1] = {required = true, list = true, default = "akur<m.#>"},
["pos"] = {},
["m"] = list_param,
["f"] = list_param,
["dim"] = list_param,
["aug"] = list_param,
["pej"] = list_param,
["dem"] = list_param,
["fdem"] = list_param,
}
end
local function do_auto_nouns(is_proper, args, data)
if data.lang:getCode() ~= "is" then
error("Internal error: Only Icelandic supported at the moment")
end
local m_is_noun = require(is_noun_module)
local alternant_multiword_specs = {}
local multiple_data = {}
local all_genders = {}
local declspecs, pos, scrape_error_data = parse_noun_adj_scraping_specs(data, args[1], args.pos, "is-ndecl", false)
if scrape_error_data then
return scrape_error_data
end
if pos then
data.pos_category = pos
args.pos = pos
end
for i, declspec in ipairs(declspecs) do
local alternant_multiword_spec =
m_is_noun.do_generate_forms(args, declspec, is_proper and "is-proper noun" or "is-noun")
alternant_multiword_specs[i] = alternant_multiword_spec
local this_data
if i < #declspecs then
this_data = m_table.shallowCopy(data)
this_data.categories = {}
this_data.inflections = {}
-- genders gets overwritten just below
else
-- for the last (or usually only) spec, save memory by not cloning
this_data = data
end
multiple_data[i] = this_data
this_data.alternant_multiword_spec = alternant_multiword_spec
this_data.heads = args.head
if not alternant_multiword_spec.scrape_errors then
this_data.genders = alternant_multiword_spec.genders
for _, gender in ipairs(this_data.genders) do
m_table.insertIfNot(all_genders, gender)
end
local function do_noun_form(slot, label, label_for_not_present, accel_form)
do_noun_adj_form(this_data, alternant_multiword_spec, slot, label, label_for_not_present, accel_form)
end
local function insert_label(label)
table.insert(this_data.inflections, {label = label})
end
if is_proper then
insert_label(glossary_link("專有名詞"))
end
if not alternant_multiword_spec.first_noun and alternant_multiword_spec.first_adj then
insert_label("形容詞性")
end
local def_prefix
if alternant_multiword_spec.definiteness == "def" then
def_prefix = "def_"
insert_label("僅" .. glossary_link("定指"))
else
def_prefix = "ind_"
end
if alternant_multiword_spec.number == "pl" then
insert_label(glossary_link("唯複"))
end
if alternant_multiword_spec.saw_indecl and not alternant_multiword_spec.saw_non_indecl then
insert_label(glossary_link("無屈折"))
elseif alternant_multiword_spec.saw_unknown_decl and not alternant_multiword_spec.saw_non_unknown_decl then
insert_label("未知變格")
elseif alternant_multiword_spec.number == "pl" then
do_noun_form(def_prefix .. "gen_p", "屬格複數", "屬格複數")
else
do_noun_form(def_prefix .. "gen_s", "屬格單數", "屬格單數")
do_noun_form(def_prefix .. "nom_p", "主格複數", not is_proper and "複數" or nil)
end
if i == 1 then
-- Parse and insert an inflection not requiring additional processing into `this_data.inflections`. The raw
-- arguments come from `args[field]`, which is parsed for inline modifiers. If there is a corresponding
-- qualifier field `FIELD_qual`, qualifiers may additionally come from there. `label` is the label that the
-- inflections are given, which is linked to the glossary if surrounded by <<..>> (which is removed).
-- `plpos` is the plural part of speech, used in [[Category:LANGNAME PLPOS with red links in their headword
-- lines]]. `accel` is the accelerator form, or nil.
local function handle_infl(field, label, frob)
m_headword_utilities.parse_and_insert_inflection {
headdata = this_data,
forms = args[field],
paramname = field,
label = label,
frob = frob,
}
end
handle_infl("m", "陽性等價詞")
handle_infl("f", "陰性等價詞")
handle_infl("dim", "<<指小詞>>")
handle_infl("aug", "<<指大詞>>")
handle_infl("pej", "<<貶義>>")
handle_infl("dem", "<<居民稱謂詞>>")
handle_infl("fdem", "<<居民稱謂詞>>")
end
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
table.insert(this_data.categories, cat)
end
insert_noun_adj_linked_lemma_if_needed(this_data, alternant_multiword_spec, is_noun_module)
end
end
if #all_genders > 1 then
table.insert(multiple_data[1].categories, "有多種性別的" .. data.langname .. (pos or "名詞"))
end
return multiple_data
end
local function get_noun_pos_functions(is_proper)
return {
params = function(lang)
if lang:getCode() == "is" then
return get_auto_noun_params(lang, is_proper)
else
return get_manual_noun_params(lang, is_proper)
end
end,
func = function(args, data)
if data.lang:getCode() == "is" then
return do_auto_nouns(is_proper, args, data)
else
return do_manual_nouns(is_proper, args, data)
end
end,
process_multiple_headword_data = get_process_noun_adj_multiple_data("is-ndecl"),
}
end
pos_functions["名詞"] = get_noun_pos_functions(false)
pos_functions["專有名詞"] = get_noun_pos_functions("專有名詞")
local function do_comparative_superlative(args, data, plpos)
if args[1][1] == "-" then
table.insert(data.inflections, {label = "無" .. glossary_link("比較級")})
table.insert(data.categories, data.langname .. "不可數" .. plpos)
elseif args[1][1] or args[2][1] then
local comp = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {1, "comp"},
forms = args[1],
}
local sup = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "sup"},
forms = args[2],
}
if comp[1] then
comp.label = glossary_link("比較級")
-- comp.accel = {form = "comparative"}
table.insert(data.inflections, comp)
end
if sup[1] then
sup.label = glossary_link("最高級")
-- sup.accel = {form = "superlative"}
table.insert(data.inflections, sup)
end
table.insert(data.categories, data.langname .. "有比較級" .. plpos)
end
end
local function get_manual_adjective_params(lang)
return {
[1] = {list = "comp", disallow_holes = true},
[2] = {list = "sup", disallow_holes = true},
["adv"] = list_param,
["indecl"] = {type = "boolean"},
}
end
local function do_manual_adjectives(args, data)
if args.indecl then
table.insert(data.inflections, {label = glossary_link("indeclinable")})
table.insert(data.categories, data.langname .. " indeclinable adjectives")
end
do_comparative_superlative(args, data, "adjectives")
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args.adv,
paramname = "adv",
label = "adverb",
}
end
local function get_auto_adjective_params(lang)
return {
[1] = {required = true, list = true, default = "djúpur<comp:^>"},
["pos"] = {},
["adv"] = list_param,
}
end
local function do_auto_adjectives(args, data)
if data.lang:getCode() ~= "is" then
error("Internal error: Only Icelandic supported at the moment")
end
local m_is_adjective = require(is_adjective_module)
local m_is_common = require(is_common_module)
local alternant_multiword_specs = {}
local multiple_data = {}
local declspecs, pos, scrape_error_data = parse_noun_adj_scraping_specs(data, args[1], args.pos, "is-adecl",
"allow empty infl")
if scrape_error_data then
return scrape_error_data
end
if pos then
data.pos_category = pos
args.pos = pos
end
for i, declspec in ipairs(declspecs) do
local alternant_multiword_spec = m_is_adjective.do_generate_forms(args, declspec, "is-adj")
alternant_multiword_specs[i] = alternant_multiword_spec
local this_data
if i < #declspecs then
this_data = m_table.shallowCopy(data)
this_data.categories = {}
this_data.inflections = {}
else
-- for the last (or usually only) spec, save memory by not cloning
this_data = data
end
multiple_data[i] = this_data
this_data.alternant_multiword_spec = alternant_multiword_spec
this_data.heads = args.head
if not alternant_multiword_spec.scrape_errors then
local function do_adjective_form(slot, label, label_for_not_present, accel_form)
do_noun_adj_form(this_data, alternant_multiword_spec, slot, label, label_for_not_present, accel_form)
end
local function insert_label(label)
table.insert(this_data.inflections, {label = label})
end
if alternant_multiword_spec.saw_indecl and not alternant_multiword_spec.saw_non_indecl then
insert_label(glossary_link("無屈折"))
elseif alternant_multiword_spec.saw_unknown_decl and not alternant_multiword_spec.saw_non_unknown_decl then
insert_label("未知變格")
end
if alternant_multiword_spec.saw_pred and not alternant_multiword_spec.saw_non_pred then
insert_label("predicate-only")
end
local function do_superlative()
do_adjective_form("sup_str_nom_m", glossary_link("最高級"), nil, "superlative")
end
if alternant_multiword_spec.haspos == "has" then
if alternant_multiword_spec.number.pos == "sg" then
insert_label(glossary_link("唯單"))
elseif alternant_multiword_spec.number.pos == "pl" then
insert_label(glossary_link("唯複"))
end
if alternant_multiword_spec.state.pos == "strong" and alternant_multiword_spec.hascomp ~= "has" and
alternant_multiword_spec.hassup ~= "has" then
local pl = alternant_multiword_spec.number.pos == "pl" and "p" or ""
do_adjective_form("str_nom_f" .. pl, glossary_link("陰性"))
do_adjective_form("str_nom_n" .. pl, glossary_link("中性"))
else
if alternant_multiword_spec.state.pos == "strong" or alternant_multiword_spec.state.pos == "weak" then
-- not "both" or "none"
insert_label("僅" .. alternant_multiword_spec.state.po)
end
if alternant_multiword_spec.hascomp == "has" then
do_adjective_form("comp_wk_nom_m", glossary_link("比較級"), nil, "comparative")
if alternant_multiword_spec.hassup == "has" then
do_superlative()
elseif alternant_multiword_spec.hassup == "hasnot" then
insert_label("無" .. glossary_link("最高級"))
end
elseif alternant_multiword_spec.hascomp == "hasnot" then
if alternant_multiword_spec.hassup == "has" then
insert_label("無" .. glossary_link("比較級"))
do_superlative()
else
insert_label(glossary_link("不可比"))
end
end
end
elseif alternant_multiword_spec.hascomp == "has" then
insert_label("僅" .. glossary_link("比較級"))
if alternant_multiword_spec.hassup == "has" then
do_superlative()
end
else
insert_label("僅" .. glossary_link("最高級"))
end
if i == 1 then
-- Parse and insert an inflection not requiring additional processing into `this_data.inflections`. The raw
-- arguments come from `args[field]`, which is parsed for inline modifiers. If there is a corresponding
-- qualifier field `FIELD_qual`, qualifiers may additionally come from there. `label` is the label that the
-- inflections are given, which is linked to the glossary if surrounded by <<..>> (which is removed).
-- `plpos` is the plural part of speech, used in [[Category:LANGNAME PLPOS with red links in their headword
-- lines]]. `accel` is the accelerator form, or nil.
local function handle_infl(field, label, frob)
m_headword_utilities.parse_and_insert_inflection {
headdata = this_data,
forms = args[field],
paramname = field,
label = label,
frob = frob,
}
end
handle_infl("adv", "adverb")
end
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
table.insert(this_data.categories, cat)
end
insert_noun_adj_linked_lemma_if_needed(this_data, alternant_multiword_spec, is_adjective_module)
end
end
return multiple_data
end
local function get_adjective_pos_functions()
return {
params = function(lang)
if lang:getCode() == "is" then
return get_auto_adjective_params(lang)
else
return get_manual_adjective_params(lang)
end
end,
func = function(args, data)
if data.lang:getCode() == "is" then
return do_auto_adjectives(args, data)
else
return do_manual_adjectives(args, data)
end
end,
process_multiple_headword_data = get_process_noun_adj_multiple_data("is-adecl"),
}
end
pos_functions["形容詞"] = get_adjective_pos_functions()
pos_functions["副詞"] = {
params = {
[1] = {list = "comp", disallow_holes = true},
[2] = {list = "sup", disallow_holes = true},
},
func = function(args, data)
do_comparative_superlative(args, data, "副詞")
end,
}
return export
st664io71zytzfkpw6gonldpdkeindw
Module:Tuf-pron
828
2999875
9909109
9102970
2026-09-29T07:28:34Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909109
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = require("Module:audio")
local decorations_module = "Module:decorations"
local put_module = require("Module:parse utilities")
local force_cat = false -- for testing
local lang = require("Module:languages").getByCode("tuf")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local MACRON = u(0x0304) -- macron
local NON_SYLL = u(0x032F) --inverted breve
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeiouAEIOUɨ" -- vowel; include ɨ for allophonic variation
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR .. CFLEX
local accent_c = "[" .. accent .. "]"
local stress = AC .. CFLEX
local stress_c = "[" .. AC .. CFLEX .. "]"
local ipa_stress = "ˈ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class including h
local unstressed_words = require("Module:table").listToSet({ --FIXME should this be here?
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but w̃, which internally will be ŵ for simplicity
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. "]", {
["w" .. TILDE] = "ŵ",
["W" .. TILDE] = "Ŵ",
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return put_module.split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text, is_spelling)
--syllabify
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
--don't divide the dipthong au
text = rsub_repeatedly(text, "(a" .. accent_c .. "*)%.u", "%1u" .. NON_SYLL)
--fix spelling syllabification
if is_spelling then
text = rsub(text, NON_SYLL, "")
text = rsub(text, "cu%.(" .. V .. ")", "cu%1")
text = rsub(text, SYLDIV, ".")
end
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants.
local TEMP_QU = u(0xFFF1)
local TEMP_QU_CAPS = u(0xFFF2)
local TEMP_CH = u(0xFFF4)
local TEMP_CH_CAPS = u(0xFFF5)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = rsub(text, "ch", TEMP_CH)
text = rsub(text, "Ch", TEMP_CH_CAPS)
text = rsub(text, "qu", TEMP_QU)
text = rsub(text, "Qu", TEMP_QU_CAPS)
text = text:gsub("%.", SYLDIV)
--Syllabify
text = syllabify_from_spelling_or_pronun(text, true)
--Restore digraphs
text = text:gsub(TEMP_CH, "ch")
text = text:gsub(TEMP_CH_CAPS, "Ch")
text = text:gsub(TEMP_QU, "qu")
text = text:gsub(TEMP_QU_CAPS, "Qu")
text = text:gsub("ŵ", "w̃")
text = text:gsub("Ŵ", "W̃")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text, phonetic)
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but w̃
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes/suffixes unstressed unless they have an explicit stress marker; also make certain monosyllabic words unstressed
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if (rfind(word, "%-$") or rfind(word, "^%-")) and not rfind(word, accent_c) or unstressed_words[word] then
-- add GR to the last vowel not the first one
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. GR)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?]", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--long vowels
text = rsub(text, "([aeiou])" .. MACRON, function(v) return v:upper() end)
--transcription (with some fake symbols for convenience)
text = rsub(text, "qu", "k")
text = rsub(text, "cu([aeiou])", "q%1")
text = rsub(text, "ch", "ç")
text = rsub(text, "[yjhc]", { ["y"] = "j", ["j"] = "h", ["h"] = "ʔ", ["c"] = "k"})
--allophones
if phonetic then
text = rsub(text, "([#n])r", "%1ɖ")
text = rsub(text, "sr", "ʂr")
text = rsub(text, "tr", "ʈr")
text = rsub(text, "k([br])", "ɡ%1") --FIXME does this happen before other consonants too?
text = rsub(text, "(" .. V .. ")b(" .. V .. ")", "%1β%2")
text = rsub(text, "wi", "wɨ")
end
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = "", [CFLEX] = "ˈ" }
local function add_tone(l, dia)
if dia ~= CFLEX then return l end
return rsub(l, "[aeiou]", {a = "Â", e = "Ê", i = "Î", o = "Ô", u = "Û"})
end
local function accent_word(word, syllables)
-- Words are accented in the penultimate syllable unless explicitly marked
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. add_tone(pre, accent) .. post end
)
end
else
if #syllables > 1 then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
else
syllables[1] = "ˈ" .. syllables[1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
accent_word(word, syllables)
-- Reconstruct the word and make all primary stresses but the last one be secondary
words[j] = table.concat(syllables, ".")
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˌ%1ˈ")
end
text = table.concat(words, " ")
text = rsub(text, "%-", "")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--allophonic nasalization of vowels adjacent to w̃
if phonetic then
text = rsub(text, "(ŵ" .. separator_c .. "*" .. V .. ")", "%1" .. TILDE)
text = rsub(text, "(" .. V .. ")(" .. separator_c .. "*ŵ)", "%1" .. TILDE .. "%2")
text = rsub(text, "m(" .. separator_c .. "*r)", "mᵇ%1")
end
-- convert fake symbols to the real ones
local final_conversions = {
["ŵ"] = "w̃", ["q"] = "kʷ", ["r"] = "ɾ",
["A"] = "aː", ["E"] = "eː", ["I"] = "iː", ["O"] = "oː", ["U"] = "uː",
["Â"] = "âː", ["Ê"] = "êː", ["Î"] = "îː", ["Ô"] = "ôː", ["Û"] = "ûː"
}
text = rsub(text, "[ŵqrAEIOUÂÊÎÔÛ]", final_conversions)
text = rsub(text, "#", "")
text = unfc(text)
return {text = text}
end
local function express_all_styles(dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
dodialect(ret)
table.insert(ret.expressed_styles, {
styles = {{pronun = ret.pronun,}},
})
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
end
local lines = {}
for i, style_group in ipairs(expressed_styles) do
table.insert(lines, style_group.formatted)
end
return table.concat(lines, "\n")
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic
local phonetic
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
else
phonemic = export.IPA(term.term, false)
phonemic = phonemic.text
phonetic = export.IPA(term.term, true)
phonetic = phonetic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = express_all_styles(this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
ins(bracket_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
phonemic = rsub(phonemic, "[âêîôû]", {["â"] = "a", ["ê"] = "e", ["î"] = "i", ["ô"] = "ô", ["û"] = "u"}) --first remove tone diacritics
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", "")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CFLEX or ci == MACRON then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result):gsub("ŵ", "w̃"):gsub("Ŵ", "W̃")) --internally, the precomposed characters ŵ and Ŵ are used for convenience
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(GR)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return put_module.parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{tuf-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = put_module.parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables FIXME is this really needed?
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = express_all_styles(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = express_all_styles(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = audio_module.format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
7p226x7vffzhur2phbnh6ty9gtolgne
Module:Guc-pbg-pron
828
3024826
9909072
9140973
2026-09-29T07:26:16Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909072
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
--TODO
--This code is adapted from [[Module:es-pronunc]], which can output multiple dialectal pronounciations from a single spelling. This module will
--return a single pronunciation per spelling in all cases, so there are parts of the code which can be simplified.
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = require("Module:audio")
local decorations_module = "Module:decorations"
local put_module = require("Module:parse utilities")
local force_cat = false -- for testing
--we declare them now but the (meaningful) assignment will take place later
local lang
local langcod = ""
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local SYLLABIC = u(0x0329)
local NON_SYLL = u(0x032F)
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeiouüAEIOUÜɨ" -- vowel; include ɨ for rhymes
local V = "[" .. vowel .. "]" -- vowel class
local accent = CFLEX
local accent_c = "[" .. accent .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class
local unstressed_words_guc = require("Module:table").listToSet({ --feel free to add more unstressed words
})
local unstressed_words_pbg = require("Module:table").listToSet({ --feel free to add more unstressed words
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ñ and ü
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return put_module.split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text, is_spelling)
text = rsub_repeatedly(text, "(" .. V ..")(" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "([mn])%1", "%1" .. SYLLABIC .. ".%1") --there can be syllabic nasals in initial position
--don't divide dipthongs
text = rsub_repeatedly(text, "([aeoAEO])%.([iuü])", "%1%2" .. NON_SYLL)
text = rsub_repeatedly(text, "([uüUÜ])%.i", "%1i" .. NON_SYLL)
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants.
local TEMP_SH = u(0xFFF1)
local TEMP_SH_CAPS = u(0xFFF2)
local TEMP_CH = u(0xFFF4)
local TEMP_CH_CAPS = u(0xFFF5)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = rsub(text, "ch", TEMP_CH)
text = rsub(text, "Ch", TEMP_CH_CAPS)
text = rsub(text, "sh", TEMP_SH)
text = rsub(text, "Sh", TEMP_SH_CAPS)
text = text:gsub("%.", SYLDIV)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
text = text:gsub(SYLDIV, ".")
text = text:gsub(TEMP_CH, "ch")
text = text:gsub(TEMP_CH_CAPS, "Ch")
text = text:gsub(TEMP_SH, "sh")
text = text:gsub(TEMP_SH_CAPS, "Sh")
--remove the symbols used for syllabic nasals and non-syllabic vowels
text = text:gsub(SYLLABIC, "")
text = text:gsub(NON_SYLL, "")
--don't divide long vowels
text = rsub(text, "([aeiouü])%.%1", "%1%1")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text)
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ñ and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make suffixes unstressed; also make certain monosyllabic words unstressed
local words = rsplit(text, " ")
local unstressed_words = (langcod == "guc") and unstressed_words_pbg or unstressed_words_guc
for i, word in ipairs(words) do
if rfind(word, "^%-") or unstressed_words[word] then
-- add CFLEX to the last vowel not the first one
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CFLEX)
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?]", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--long vowels
text = rsub(text, "([aeiou])%1", function(v) return v:upper() end)
text = rsub(text, "üü", "Ü")
--consonants
text = rsub(text, "ch", "c") --fake symbol
text = rsub(text, "sh", "x") --fake symbol
text = rsub(text, "j", "h")
text = rsub(text, "y", "j")
text = rsub(text, "'", "q") --fake symbol
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--determine whether a syllable is heavy or not. Syllables are heavy if they contain long vowels, dipthongs or end in a consonant.
local function is_heavy(syllable)
if rfind(syllable, "[AEIOUÜ" .. NON_SYLL .."]") or rfind(syllable, "[mnptckqsxhlrwy]$") then return true end
return false
end
local function extrametric(first, second)
if (not rfind(first, "[AEIOUÜ" .. NON_SYLL .."]")) and (rfind(first, "q$") or (rfind(first, "[aeiouü$]") and rfind(second, "^q"))) then return true end
return false
end
local function accent_word(word, syllables)
-- If the first syllable is heavy, then the first syllable is stressed. Otherwise the second syllable is stressed.
-- In words beginning with (C)V' (where V is a short vowel) the first syllable is not counted when determining the position of stress (only in Wayuu, there's no glottal stop in Paraujano)
if not rfind(word, CFLEX) then
if #syllables > 1 then
local extra = 0
if extrametric(syllables[1], syllables[2]) then
if is_heavy(syllables[2]) or #syllables < 3 then
syllables[2] = "ˈ" .. syllables[2]
else
syllables[3] = "ˈ" .. syllables[3]
end
else
if is_heavy(syllables[1]) then
syllables[1] = "ˈ" .. syllables[1]
else
syllables[2] = "ˈ" .. syllables[2]
end
end
else
syllables[1] = "ˈ" .. syllables[1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
accent_word(word, syllables)
-- Reconstruct the word and make all primary stresses but the last one be secondary
words[j] = table.concat(syllables, ".")
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˌ%1ˈ")
words[j] = rsub(words[j], CFLEX, "")
end
text = table.concat(words, " ")
text = rsub(text, "%-", "")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--final conversions
local final_conversions = {
["c"] = "t͡ʃ", ["x"] = "ʃ", ["q"] = "ʔ", ["v"] = "β", ["ñ"] = "ɲ", --the phonemic status of β and ɲ is dubious in Wayuu. In Parajuano /ɲ/ an undisputed phoneme.
["A"] = "aː", ["E"] = "eː", ["I"] = "iː", ["O"] = "oː", ["U"] = "uː", ["Ü"] = "ɨː", ["ü"] = "ɨ"
}
text = rsub(text, "[cxqvñAEIOUÜü]", final_conversions)
if langcod == "guc" then
text = rsub(text, "l", "ɺ")
else
text = rsub(text, "r", "ɺ")
end
text = rsub(text, "#", "")
text = unfc(text)
return {text = text}
end
local function express_all_styles(dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
dodialect(ret)
table.insert(ret.expressed_styles, {
styles = {{pronun = ret.pronun,}},
})
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
end
local lines = {}
for i, style_group in ipairs(expressed_styles) do
table.insert(lines, style_group.formatted)
end
return table.concat(lines, "\n")
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic
if term.raw then
phonemic = term.raw_phonemic
else
phonemic = export.IPA(term.term)
phonemic = phonemic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = express_all_styles(this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡ʃ", "tʃ"):gsub("d͡ʒ", "dʒ")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == CFLEX then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts
not words[1]:find("%-$") and -- no if word is a prefix
not words[1]:find("^%-") and -- no if word is a suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return put_module.parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{guc-pr}}.
function export.show_pr(frame)
langcod = frame.args.lang
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
--Determine the language
if langcod == "guc" then
lang = require("Module:languages").getByCode("guc")
elseif langcod == "pbg" then
lang = require("Module:languages").getByCode("pbg")
else
error("Invalid or missing language code. Supported languages are Wayuu (guc) and Paraujano (pbg).")
end
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = put_module.parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = express_all_styles(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = express_all_styles(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = audio_module.format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
c5wmfmfd7ihn2kwgendqilyjc2e1g2m
Module:Tsg-pronunciation
828
3033582
9909108
9151196
2026-09-29T07:28:29Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909108
Scribunto
text/plain
-- Based on [[Module:es-pronunc]] by Benwing2.
-- Adaptation by TagaSanPedroAko, Improved by Ysrael214.
-- Partly rewritten by Benwing2, merging code from [[Module:es-pronunc]] back into this module; {{tl-pr}} restructured
-- to take inline modifiers, like {{es-pr}}.
-- Modified for {{tsg-pr}}.
local export = {}
local force_cat = false -- enable for testing
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local accent_qualifier_module = "Module:accent qualifier"
local audio_module = "Module:audio"
local headword_data_module = "Module:headword/data"
local hyphenation_module = "Module:hyphenation"
local labels_module = "Module:labels"
local parse_utilities_module = "Module:parse utilities"
local rhymes_module = "Module:rhymes"
local set_utilities_module = "Module:set utilities"
-- local jawi_decode_module = "Module:tsg-translit"
-- local jawi_encode_module = "Module:tsg-jaw sc"
local lang = require("Module:languages").getByCode("tsg")
local rfind = m_str_utils.find
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local toNFC = mw.ustring.toNFC
local toNFD = mw.ustring.toNFD
local trim = mw.text.trim
local u = m_str_utils.char
local ulen = m_str_utils.len
local ulower = m_str_utils.lower
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local MACRON = u(0x0304) -- macron = ̄
local DOTOVER = u(0x0307) -- dot over = ̇
local vowel = "aeëəiouüɜ" -- vowel
local V = "[" .. vowel .. "]"
local NV = "[^" .. vowel .. "]"
local accent = AC .. GR .. CFLEX .. MACRON .. DOTOVER
local accent_c = "[" .. accent .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local separator = accent .. ipa_stress .. "# .-"
local C = "[^" .. vowel .. separator .. "]" -- consonant
local unstressed_words = m_table.listToSet {
--TO DO
}
local unstressed_affixes = m_table.listToSet {
--TO DO
}
local special_words = {
-- TO DO
}
local function track(page)
require("Module:debug/track")("tsg-pronunciation/" .. page)
return true
end
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- Combine two sets of qualifiers, either of which may be nil or a list of qualifiers. Remove duplicate qualifiers.
-- Return value is nil or a list of qualifiers.
local function combine_qualifiers(qual1, qual2)
if not qual1 then
return qual2
end
if not qual2 then
return qual1
end
local qualifiers = m_table.deepCopy(qual1)
for _, qual in ipairs(qual2) do
m_table.insertIfNot(qualifiers, qual)
end
return qualifiers
end
local function decompose(text, recompose_e_dia)
-- decompose everything but ñ and ü
text = toNFD(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
if recompose_e_dia then
text = rsub(text, ".[" .. DIA .. "]", {
["e" .. DIA] = "ë",
["E" .. DIA] = "Ë",
})
end
return text
end
local function remove_accents(str)
str = decompose(str, "recompose e-dia")
str = rsub(str, "(.)" .. accent_c, "%1")
return str
end
local function split_on_comma(term)
if term:find(",%s") then
return require(parse_utilities_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
--Cleanup Jawi inputs--
local function decode_jawi(text)
-- TO DO
return text
end
-- ĵ, ɟ and ĉ are used internally to represent [d͡ʒ], [j] and [t͡ʃ]
--
function export.IPA(text, include_phonemic_syllable_boundaries)
local debug = {}
-- text = decode_jawi(text)
text = ulower(text)
text = decompose(text, "recompose e-dia")
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain monosyllabic words (e.g.
-- [[ang]], [[ng]], [[si]], [[na]], etc.) without stress marks be unstressed. We want to do this in most cases as
-- well with hyphenated compounds, e.g. [[bato-sa-rinyon]] and [[kalahatian-ng-buwan]]. To do this, we use a
-- capturing split on space or hyphen; in this situation, the actual words are at odd positions, and the separators
-- (always a single space or hyphen) are at even positions.
local words = rsplit(text, "([ %-])")
local function make_unstressed(word)
-- add dotover to the last vowel not the first one, in case of affixes with qui/que/gui/gue (which don't
-- currently exist)
return rsub(word, "^(.*" .. V .. ")", "%1" .. DOTOVER)
end
local function signal_no_initial_glottal_stop(word)
return rsub(word, "^(" .. V .. ")", "◌%1")
end
for i=1, #words do
if i % 2 == 1 then -- a word, not a hyphen or space
if words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") and
words[i + 1] ~= "-" then
-- a suffix
if unstressed_affixes["-" .. words[i]] then
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] ~= "-" then
-- a prefix
if not rfind(words[i], accent_c) then
-- an unstressed prefix
words[i] = make_unstressed(words[i])
end
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") then
-- an interfix or infix
if not rfind(words[i], accent_c) then
-- an unstressed interfix or infix
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
else
-- a space-delimited word or a word in a hyphen-delimited compound
words[i] = special_words[words[i]] or words[i]
if unstressed_words[words[i]] then
words[i] = make_unstressed(words[i])
elseif words[i + 1] == "-" and (not words[i - 1] or words[i - 1] == " ") and
-- e.g. 'mag-' in [[mag-post]]
unstressed_affixes[words[i] .. "-"] then
words[i] = make_unstressed(words[i])
end
end
end
-- old code that I didn't port because I don't understand why it's being done; the purpose is to make suffixes
-- and infixes with explicit initial glottal stop be unstressed, which seems a weird exception
-- words[i] = rsub(words[i], "^%-([7ʔ])(" .. V .. ")", "-%1%2" .. DOTOVER) -- affix that requires glottal stop
end
text = table.concat(words, "")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[!?]", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
table.insert(debug, text)
-- handle certain combinations; ch ng and sh handling needs to go first
text = rsub(text, "([t]?)ch", "ĉ") --not the real sound
text = rsub(text, "([n]?)g̃", "ng") -- Spanish spelling support
text = rsub(text, "ng", "ŋ")
text = rsub(text, "sh", "ʃ")
--x
text = rsub(text, "([#])x(" .. V .. ")", "%1s%2")
text = rsub(text, "x", "ks")
--Arabic loan sounds
text = rsub(text, "dh", "ð")
text = rsub(text, "kh", "x")
text = rsub(text, "[gɡ]h", "ɣ")
--c, gü/gu+e or i, q
text = rsub(text, "c([iey])", "s%1")
text = rsub(text, "(" .. V .. ")gü([ie])", "%1ɡw%2")
text = rsub(text, "gü([ie])", "ɡuw%1")
text = rsub(text, "gui([aeëo])", "ɡy%1")
text = rsub(text, "gu([ie])", "ɡ%1")
text = rsub(text, "qu([ie])", "k%1")
-- Correction for vowels with in-between glottal stop, now default
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. V .. ")", "%1'%2")
text = rsub(text, "(i)" .. MACRON, "iy")
text = rsub(text, "(u)" .. MACRON, "uw")
text = rsub(text, "(".. V .. ")" .. MACRON, "%1%1")
--glottal stop
text = rsub(text, "[ꞌ']", "7")
--alphabet-to-phoneme
text = rsub(text, "[cfgjñqrvz7]",
--["g"]="ɡ": U+0067 LATIN SMALL LETTER G → U+0261 LATIN SMALL LETTER SCRIPT G
{ ["c"] = "k", ["g"] = "ɡ", ["j"] = "ĵ", ["ñ"] = "ny", ["r"] = "ɾ", ["7"] = "ʔ"})
-- ts
text = rsub(text, "t_s", "ć") --not the real sound
text = rsub(text, "ts", "ĉ") --not the real sound
table.insert(debug, text)
--determining whether "y" is a consonant or a vowel
text = rsub(text, "y(" .. accent_c .. ")", "i%1")
text = rsub(text, "y(" .. V .. ")", "ɟ%1") -- not the real sound
text = rsub(text,"(" .. NV .. ")y([ˈˌ.]*)([bćĉdðfɡɣhjĵklmnɲŋpqɾrsʃtvwxɟzʔ#" .. vowel .. "])","%1i%2%3")
text = rsub(text,"(" .. V .. ")y([ˈˌ.]*)([bćĉdðfɡɣhjĵklmnɲŋpqɾrsʃtvwxɟzʔ#" .. vowel .. "])","%1j%2%3")
text = rsub(text, "w(" .. V .. ")","w%1")
text = rsub(text,"(" .. NV .. ")w([ˈˌ]?)([bćĉdðfɡɣjĵklmnɲŋpqɾrsʃtvxwɟzʔ#])","%1u%2%3")
-- Convert CV(wy)C pattern into diphthong
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)w(" .. NV .. ")([^lɾr" .. vowel .. separator .."])" ,"%1%2u%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)w([ĉdðjĵlmnɲŋɾrstvwɟzʔ])([l" .. vowel .. separator .."])" ,"%1%2u%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)[jyɟ](" .. NV .. ")([^lɾr" .. vowel .. separator .."])" ,"%1%2i%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)[jyɟ]([ĉdðjĵlmnɲŋɾrstvwɟzʔ])([l" .. vowel .. separator .."])" ,"%1%2i%3%4")
--vowels with grave/circumflex to vowel+glottal stop
text = rsub(text, CFLEX, AC .. GR)
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR .. "([#" .. vowel .. "])", "%1%2ʔ%3")
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR, "%1%2")
-- Add glottal stop for words starting with vowel
text = rsub(text, "([#])(" .. V .. ")", "%1ʔ%2")
text = rsub(text, "◌", "")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^hlɾrɟ" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dkt])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfɡɣklmnpqsʃvxz])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^hlɾrɟsʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeo, or stressed iu, should be syllabically divided from a following aeo or stressed iu.
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)(" .. V .. AC .. ")", "%1.%2")
text = rsub(text, "([iuə]" .. AC .. ")([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([iuə]" .. AC .. ")(" .. V .. AC .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
table.insert(debug, text)
local accent_to_stress_mark = { [AC] = "ˈ", [DOTOVER] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including macron indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post)
return accent_to_stress_mark[accent] .. pre .. post
end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and rfind(word, "[^aeiouəʔbcćĉdðfɡgɣhjɟĵklmnñɲŋpqrɾsʃtvwxz#]#") or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables >= 2 then
-- Default stress on the last syllable in Tausug
local vowel_find = false
local stress_find = false
for i=0, #syllables-1 do
if rfind(syllables[#syllables - i], V) then
vowel_find = true
if vowel_find then
syllables[#syllables - i] = "ˈ" .. syllables[#syllables - i]
stress_find = true
break
end
end
end
if vowel_find and not stress_find then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
end
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
accent_word(word, syllables)
-- Reconstruct the word.
words[j] = table.concat(syllables, ".")
end
text = table.concat(words, " ")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ")
table.insert(debug,text)
--correct final glottal stop placement
text = rsub(text,"([ˈˌ])ʔ([#]*)([ʔbĉćdðfɡɣhĵɟklmnŋɲpqɾrsʃtvwz])(" .. V .. ")","%1%2%3%4ʔ")
table.insert(debug,text)
-- Long vowels
text = rsub(text,"(i)[jɟ]","%1ː")
text = rsub(text,"(u)w","%1ː")
text = rsub(text,"([aeiouëü])[.]%1","%1ː")
text = rsub(text,"([ʔbćĉdðfɡɣhjĵklmnɲŋpqɾrsʃtvwxɟz])([lnɾst]?)([aeiouëü])([ˈˌ.])([aeiouëü])","%4%1%2%3ː")
--add temporary macron for /a/, /i/ and /u/ in stressed syllables so they don't get replaced by unstressed form
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdðfɡɣhĵɟklmnŋpɾrstvxwz]?)([ɟlnɾstw]?)([a])([ː]?)([ʔbdðfɡɣiklmnŋpɾstu]?)([bdɡklmnpɾst]?)","%1%2%3%4ā%6%7%8")
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdðfɡɣhĵɟklmnŋpɾrstvxwz]?)([ɟlnɾstw]?)([i])([ː]?)([ʔbdðfɡɣklmnŋpɾstu]?)([bdɡklmnpɾst]?)","%1%2%3%4ī%6%7%8")
text = rsub(text,"([ˈˌ])([#]*)([ʔbćĉdðfɡɣhĵɟklmnŋpɾrstvxwz]?)([ɟlnɾstw]?)([u])([ː]?)([ʔbdðfɡɣiklmnŋpɾst]?)([bdɡklmnpɾst]?)","%1%2%3%4ū%6%7%8")
table.insert(debug, text)
--Corrections for diphthongs
text = rsub(text,"([aāeəëiīouūü])([ː]?)i","%1%2j") --ay
text = rsub(text,"([aāeəëiīouūü])([ː]?)u","%1%2w") --aw
text = rsub(text,"([i])j","%1ː") --ay
table.insert(debug, text)
--remove "ɟ" and "w" inserted on vowel pair starting with "i" and "u"
text = rsub(text,"([i])([ˈˌ]?)ɟ([aāeəouū])","%1%2%3")
text = rsub(text,"([u])([ˈˌ]?)w([aāeəiī])","%1%2%3")
table.insert(debug,text)
--/z/ changes
-- text = rsub(text,"([aāeəoiīuū])z([ˈˌ.#])([^bdfɡĵjɟŋɾrvz])","%1s%2%3") -- /z/ turn to /s/ before some unvoiced sounds
-- text = rsub(text,"([^#bdfɡĵjɟnŋɾrvzaāeəoiīuū])([ˈˌ.#])z","%1%2s") -- /z/ turn to /s/ after some unvoiced sounds
-- text = rsub(text,"([bćĉdfɡhĵjɟklmnŋptvwz])([ˈˌ.]?)([ɟlɾst])([aāeəoiīuū])([.]?)([z])","%1%2%3%4%5s") -- consonant cluster before /z/ turn to /s/
-- text = rsub_repeatedly(text, "([^z]*)z([^z]*)([^#bdfɡĵjɟnŋɾrvzˈˌ.#][ˈˌ.#]?)z", "%1z%2%3s") -- /z/ turn to /s/ if /z/ already said earlier
text = rsub_repeatedly(text, "^([#]*)([ˈˌ])([#]*)", "%1%3%2") -- Move stress inside word boundary fix at start
text = rsub_repeatedly(text, "([ ])([#]*)([ˈˌ])([#]*)", "%1%2%4%3") -- Move stress inside word boundary fix at start
local tsg_IPA_table = {
["phonetic"] = text,
["phonemic"] = text,
["raw"] = text
}
for key, value in pairs(tsg_IPA_table) do
text = tsg_IPA_table[key]
--phonetic transcription
if key == "phonetic" then
table.insert(debug, text)
text = rsub(text,"([e])","i")
text = rsub(text,"([o])","u")
--Turn phonemic diphthongs to phonetic diphthongs
-- text = rsub(text, "([aāeëəouūüɜ])([ː]?)j", "%1%2ɪ̯")
-- text = rsub(text, "([aāeëəiīouūɜ])([ː]?)w", "%1%2ʊ̯")
table.insert(debug, text)
text = rsub(text,"n([ˈˌ.])ɟ","%1ɲ") -- /n/ before /j/
text = rsub(text,"n[ɟj]([aāeëəiɪ̯īouʊ̯ūüɜ])", "ɲ%1") -- /n/ before /j/
--Combine consonants (except H) followed by I/U and certain stressed vowels
text = rsub(text,"([bćĉdðfɡɣhĵklmnɲŋpɾrstvz])([lnɾst]?)i([ˈˌ.ː])ɟ?([āaeëəoūuüɜ])","%3%1%2ɟ%4")
text = rsub(text,"([bćĉdðfɡɣhĵklmnɲŋpɾrstvz])([lnɾst]?)u([ˈˌ.ː])w?([āaeëəioüɜ])","%3%1%2w%4")
text = rsub(text,"([bćĉdðfɡɣhĵklmnɲŋpɾrstvz])([lnɾst]?)u([ˈˌ.ː])w?([ī])","%3%1%2wi")
text = rsub_repeatedly(text, "([.]+)", ".")
table.insert(debug, text)
table.insert(debug, text)
text = rsub(text,"([nŋ])([ˈˌ# .]*[bfpv])","m%2")
text = rsub(text,"([ŋ])([ˈˌ# .]*[dst])","n%2")
text = rsub_repeatedly(text,"([aāeëəiɪ̯īouʊ̯ūüɜ])([#]?)([ ]?)([ˈˌ#.ː])([ɡ])([aāeəiīouūüɜ])", "%1%2%3%4ɣ%6") -- /ɡ/ between vowels
text = rsub(text,"([n])([ˈ ˌ# .]*[ɡkhw])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
text = rsub(text,"([ˈˌ.])n([ɟj])([aāeəiīouūüɜ])","%1ɲ%3") -- /nj/ before any vowel following stress
text = rsub(text,"([ɾ])([ˈˌ# .]*[mnɡk])","ɹ%2")
--final fix for phonetic diphthongs
text = rsub(text,"([a])ɪ̯","āɪ̯") --ay
text = rsub(text,"([a])ʊ̯","āʊ̯") --aw
text = rsub(text,"([i])ʊ̯","īʊ̯") --iw
table.insert(debug, text)
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ") -- Reset primary to secondary stresses if not on last word
-- Tausug intervocalic consonants
text = rsub_repeatedly(text,"([aāeəiīouɜūʊü])([.ˈˌ #ː]*)[b]([aāeəiīouɜūʊ])","%1%2β%3")
text = rsub_repeatedly(text,"([aāeəiīouɜūʊü])([.ˈˌ #ː]*)[d]([aāeəiīouɜūʊ])","%1%2ɾ%3")
text = rsub_repeatedly(text,"([aāeəiīouɜūʊü])([.ˈˌ #ː]*)[ɡ]([aāeəiīouɜūʊ])","%1%2ɣ%3")
text = rsub_repeatedly(text,"([aāeəiīouɜūʊü])([.ˈˌ #ː]*)[h]([aāeəiīouɜūʊ])","%1%2ɦ%3")
-- Nasal vowels
text = rsub_repeatedly(text,"([mnɲŋ])([aāeëəiīouɜūʊ])([.ˈˌ #ː]*)([mnɲŋ])","%1%2" .. TILDE .. "%3%4")
-- Add vowel length to stressed open vowels
text = rsub_repeatedly(text,"([ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?)([ɟlnɾstw]?)([āeëəīoūü])([ˈˌ.])","%1%2%3%4ː%5")
-- Tausug /a/
text = rsub_repeatedly(text,"([kxgɡɣqŋʔhɦw])[āa]","%1ɑ") -- after back consonants
text = rsub(text,"[ā]","a")
-- /k/ fronting and retracting
text = rsub_repeatedly(text,"([k])([iī])","%1̟%2")
text = rsub_repeatedly(text,"([k])([uūɑ])","%1̠%2")
--change a, i, u to unstressed equivalents (certain forms to restore)
-- text = rsub(text,"a","ɐ")
-- text = rsub(text,"a","ä")
text = rsub(text,"i","ɪ")
text = rsub(text,"u","ʊ")
-- Some alveolars are actually dental
text = rsub(text, "([tdn])", "%1̪")
-- Unreleased stops
text = rsub(text,"([pbtdkɡq])([̪]?)([.ˈˌ])([pbtdkɡqĵʔmnɲŋ])","%1̚%2%3%4")
text = rsub(text,"([pbtdkɡq])([̪]?)([# ])","%1̚%2%3")
elseif key == "phonemic" then
--Turn semivowel to different symbol
text = rsub(text, "([aāeëəouūüɜ])j([^.ˈˌ#])", "%1i%2")
text = rsub(text, "([aāeëəiīouūüɜ])w([^.ˈˌ#])", "%1u%2")
text = rsub(text,"([n])([ˈˌ#.]?[ɡk])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
if not include_phonemic_syllable_boundaries then
text = rsub(text,"%.","")
end
text = rsub(text,"‿", " ")
text = rsub_repeatedly(text,"[ˈˌ]", "") -- Remove stresses
end
table.insert(debug, text)
--delete temporary macron in /a/, /i/ and /u/
text = rsub(text,"ā","a")
text = rsub(text,"ī","i")
text = rsub(text,"ū","u")
-- Final fix for "iy" and "uw" combination
text = rsub(text,"([iɪ])([ː]?)([ˈˌ.]*)ɟ([aɐeɛəouʊ])","%1%2%3%4")
text = rsub(text,"([uʊ])([ː]?)([ˈˌ.]*)w([aɐeɛəiɪo])","%1%2%3%4")
text = rsub(text,"([ɪ])([ː]?)([ˈˌ.]*)ɟ([i])","%1%2%3%4")
text = rsub(text,"([i])([ː]?)([.]*)ɟ([ɪ])","%1%2%3%4")
text = rsub(text,"([ʊ])([ː]?)([ˈˌ.]*)w([u])","%1%2%3%4")
text = rsub(text,"([u])([ː]?)([.]*)w([ʊ])","%1%2%3%4")
--remove "ɟ" and "w" inserted on vowel pair starting with "e" and "o"
text = rsub(text,"([ɛe])([ː]?)([ˈˌ.]*)[ɟj]([aɐo])","%1%2%3%4")
text = rsub(text,"([o])([ː]?)([ˈˌ.]*)w([aɐeɛə])","%1%2%3%4")
-- convert fake symbols to real ones
local final_conversions = {
["ĉ"] = "t͡ʃ", -- fake "ch" to real "ch"
["ć"] = "t͡s", -- fake "ts" to real "ts"
["ɟ"] = "j", -- fake "y" to real "y"
["ĵ"] = "d͡ʒ", -- fake "j" to real "j"
["ë"] = "ə", -- schwa
["ü"] = "ɜ" -- obscure u
}
text = rsub(text, "[ć]([" .. separator .. "])", "ts%1")
text = rsub(text, "[ĉćɟĵëü]", final_conversions)
-- Do not have multiple syllable break consecutively
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub_repeatedly(text, "([.]?)(‿)([.]?)", "%2")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
-- resuppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
text = rsub_repeatedly(text, "([.]?)(" .. ipa_stress_c .. ")([.]?)", "%2")
tsg_IPA_table[key] = canon_spaces(toNFC(text))
end
return tsg_IPA_table
end
function export.show(frame)
local params = {
[1] = {},
["pre"] = {},
["bullets"] = {type = "number", default = 1},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local results = {}
local text = args[1] or mw.title.getCurrentTitle().text
local IPA_result = export.IPA(text)
table.insert(results, { pron = "/" .. IPA_result["phonemic"] .. "/" })
table.insert(results, { pron = "[" .. IPA_result["phonetic"] .. "]" })
local pre = args.pre and args.pre .. " " or ""
local bullet = (args.bullets ~= 0) and "* " or ""
return bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = results }
end
local function parse_gloss(arg)
local poses, gloss
if arg:find("%^") then
poses, gloss = arg:match("^(.-)%^(.*)$")
if gloss == "" then
gloss = nil
end
else
gloss = arg
end
if poses then
poses = split_on_comma(poses)
local m_headword_data = mw.loadData(headword_data_module)
for i, pos in ipairs(poses) do
poses[i] = m_headword_data.pos_aliases[pos] or pos
end
end
return {
poses = poses,
gloss = gloss,
}
end
-- Parse a raw accent spec, which is one or more comma-separated accent qualifiers.
local function parse_accents(arg)
return require(labels_module).split_labels_on_comma(arg)
end
-- Return the number of syllables of a phonemic or phonetic representation, which should have syllable dividers in it
-- but no hyphens.
local function get_num_syl_from_ipa(pron)
-- Maybe we should just count vowels instead of the below code.
pron = rsub(pron, "|", " ") -- remove IPA foot boundaries
local words = rsplit(pron, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
pron = table.concat(words, ".")
pron = rsub(pron,"([i])([au])", "%1y%2")
pron = rsub(pron,"([u])([ai])", "%1w%2")
local vowel = 0
for check in pron:gmatch('([aeiou]+)') do
vowel = vowel + 1
end
return vowel
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
phonemic = rsplit(phonemic, " ")
phonemic = phonemic[#phonemic]
phonemic = rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^" .. NV .. "*", ""):gsub("%.", "")
phonemic = rsub(phonemic,"([i])([au])", "%1y%2")
phonemic = rsub(phonemic,"([u])([ai])", "%1w%2")
phonemic = rsub_repeatedly(phonemic,"(" .. V .. "+)(" .. NV .. "+)(" .. V .. "+)", "%3")
return phonemic
end
local function split_syllabified_spelling(spelling)
spelling = "#" .. spelling .. "#"
spelling = rsub_repeatedly(spelling, "%.([ #])", "·%1")
spelling = rsub_repeatedly(spelling, "#", "")
spelling = rsplit(spelling, "%.")
for key, value in ipairs(spelling) do
spelling[key] = rsub_repeatedly(value, "·", ".")
end
return spelling
end
-- "Align" syllabified respelling `syllab` to original spelling `spelling` by matching character-by-character, allowing
-- for extra syllable and accent markers in the syllabification and certain mismatches in the consonants. The goal is to
-- produce the appropriately syllabified version of the original spelling (the pagename) by matching characters in the
-- syllabified respelling to the original spelling, putting the syllable boundaries in the appropriate places in the
-- original spelling. As an example, given syllabified respelling 'a.ma.7ín' and original spelling 'amain', we would
-- like to produce 'a.ma.in'.
--
-- If we encounter an extra syllable marker (.), we allow and keep it. If we encounter an extra accent marker in thes
-- syllabification, we drop it. We allow for mismatches in capitalization and for certain other mismatches, e.g. extra
-- glottal stops (written 7), h in respelling vs. g or j in the original, etc. If we can't match, we return nil
-- indicating the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local function concat_result()
-- Postprocess to remove dots (syllable boundaries) next to hyphens.
return (toNFC(table.concat(result)):gsub("%.%-", "-"):gsub("%-%.", "-"))
end
-- Remove glottal stop (7) from respelling to simplify the code below, because it's never found in the original
-- spelling. (FIXME: We should do the same for diacritics, but they're currently removed earlier, in
-- syllabify_from_spelling(). We should probably get rid of the removal there and put it here.)
syllab = decompose(syllab:gsub("ː", ""), "recompose e-dia"):gsub("7", "")
spelling = decompose(spelling, "recompose e-dia")
local syll_chars = rsplit(ulower(syllab), "")
local spelling_chars = rsplit(spelling, "")
local i = 1
local j = 1
local function matches(uci, ucj)
-- Return true if a syllabified respelling character (uci) matches the corresponding spelling char (ucj).
-- Both uci and ucj should be lowercase.
-- Sound is at the key, values are the letters sound can match
local matching_chars = {
["e"] = {"i"},
["ë"] = {"a", "e", "o", "u"},
["h"] = {"g", "j", "x"},
["i"] = {"e"},
["j"] = {"g"},
["k"] = {"j"},
["o"] = {"u"},
["s"] = {"j", "c", "x"},
["u"] = {"o"},
["w"] = {"u", "o"},
["y"] = {"i"}
}
return uci == ucj or (matching_chars[uci] and m_table.contains(matching_chars[uci], ucj) and true) or false
end
local function silent_spelling_letter(ucj)
return ucj == "h" or ucj == "'" or ucj == "-"
end
local function syll_at(pos)
return syll_chars[pos] or ""
end
local function spell_at(pos)
return spelling_chars[pos] or ""
end
local function uspell_at(pos)
local c = spelling_chars[pos]
return c and ulower(c) or ""
end
while i <= #syll_chars or j <= #spelling_chars do
local uci = syll_at(i)
local cj = spell_at(j)
local ucj = uspell_at(j)
if uci == "g" and syll_at(i - 1) == "n" and syll_at(i + 1) == "." and matches(syll_at(i + 2), ucj) and
not matches(syll_at(i + 2), uspell_at(j + 1)) then
-- As a special case, before checking whether the corresponding characters match, we have to skip an extra
-- g in an -ng- sequence in the syllabified respelling if the corresponding spelling character matches the
-- next respelling character (taking into account the syllable boundary). This is so that e.g.
-- syll='ba.rang.gay' matches spelling='barangay'. Otherwise we will match the first respelling g against
-- the spelling g and the second respelling g won't match. A similar case occurs with
-- syll='E.vang.he.lis.ta' and spelling='Evangelista'. But we need an extra condition to not do this hack
-- when syll='ba.rang.gay' matches spelling='baranggay'.
i = i + 1
elseif uci == "g" and ucj == "g" and uspell_at(j + 1) == TILDE then
table.insert(result, cj)
table.insert(result, uspell_at(j + 1))
i = i + 1
j = j + 2
elseif matches(uci, ucj) then
table.insert(result, cj)
i = i + 1
j = j + 1
elseif ucj == uspell_at(j - 1) and uci == "." and ucj ~= syll_at(i + 1) then
-- See below. We want to allow for a doubled letter in spelling that is pronounced single, and preserve the
-- doubled letter. But it's tricky in the presence of syllable boundaries on both sides of the doubled
-- letter as well as doubled letters pronounced double. Specifically, there are three possibilities,
-- exemplified by:
-- (1) syll='Mal.lig', spelling='Mallig' -> 'Mal.lig';
-- (2) syll='Ma.lig', spelling='Mallig' -> 'Ma.llig';
-- (3) syll='Wil.iam', spelling='William' -> 'Will.iam'.
-- If we copy the dot first, we get (1) and (2) right but not (3).
-- If we copy the double letter first, we get (2) and (3) right but not (1).
-- We choose to copy the dot first except in the situation exemplified by (3), where we copy the doubled
-- letter first. The condition above handles (3) (the doubled letter matches against a dot) while not
-- interfering with (1) (where the doubled letter also matches against a dot but the next letter in the
-- syllabification is the same as the doubled letter, because the doubled letter is pronounced double).
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) and uci == "." and ucj ~= syll_at(i + 1) and
not rfind(uspell_at(j + 1), V) then
-- See below for silent h or apostrophe in spelling. This condition is parallel to the one directly above
-- for silent doubled letters in spelling and handles the case of syllab='Abduramán', spelling='Abdurahman',
-- which should be syllabified 'Ab.du.rah.man'. But we need a check to see that the next spelling character
-- isn't a vowel, because in that case we want the silent letter to go after the period, e.g.
-- syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah' (the 7 is removed above).
table.insert(result, cj)
j = j + 1
elseif uci == "." then
table.insert(result, uci)
i = i + 1
elseif ucj == uspell_at(j - 1) then
-- A doubled letter in spelling that is pronounced single. Examples:
-- * syllab='Ma.líg', spelling='Mallig' -> 'Ma.llig' (with l)
-- * syllab='Lu.il.yér', spelling='Lhuillier' -> 'Lhu.ill.ier' (with l; a more complex example)
-- * syllab='a.sa.la.mu a.lai.kum', spelling='assalamu alaikum' -> 'as.sa.la.mu a.lai.kum' (with s)
-- * syllab='Jé.fer.son', spelling='Jefferson' -> 'Je.ffer.son' (with f)
-- * syllab='Je.ma', spelling='Gemma' -> 'Ge.mma' (with m)
-- * syllab='Ha.na', spelling='Hannah' -> 'Ha.nnah' (with n)
-- * syllab='A.by', spelling='Abby' -> 'A.bby' (with b)
-- * syllab='Ka.ba', spelling='Kaaba' -> 'Kaa.ba' (with a)
-- * syllab='Fu.ji', spelling='Fujii' -> 'Fu.jii' (with i)
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) then
-- A silent h, apostrophe or hyphen in spelling. Examples:
-- * syllab='adán', spelling='adhan' -> 'a.dhan'
-- * syllab='Atanasya', spelling='Athanasia' -> 'A.tha.nas.ia'
-- * syllab='Cýntiya', spelling='Cynthia' -> 'Cyn.thi.a'
-- * syllab='Ermóhenes', spelling='Hermogenes' -> 'Her.mo.ge.nes'
-- * syllab='Abduramán', spelling='Abdurahman' -> 'Ab.du.rah.man'
-- * syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah'
-- * syllab='pag7ibig', spelling='pag-ibig' -> 'pag-i.big'
table.insert(result, cj)
j = j + 1
elseif uci == AC or uci == GR or uci == CFLEX or uci == DIA or uci == TILDE or uci == MACRON or uci == DOTOVER or
uci == "y" or uci == "w" then
-- skip character
i = i + 1
else
-- non-matching character
mw.log(("Syllabification alignment mismatch for pagename '%s' (position %s, character %s), syllabified respelling '%s' (position %s, character %s), aligned result so far '%s'"
):format(spelling, j, ucj, syllab, i, uci, concat_result()))
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
mw.log(("Syllabification alignment mismatch for pagename '%s' (%s), syllabified respelling '%s' (%s), aligned result so far '%s'"
):format(
spelling, j > #spelling_chars and "end of string" or ("position %s, character %s"):format(j, uspell_at(j)),
syllab, i > #syll_chars and "end of string" or ("position %s, character %s"):format(i, syll_at(i)),
concat_result()))
return nil
end
return concat_result()
end
local function generate_syll_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
word = ulower(word)
return rfind(word, V) or word:find("y")
end
local function any_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word_has_vowels(word) then
return true
end
end
return false
end
local function has_jawi(text)
return text:match("[ᜀ-ᜟ]")
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
local last_word = words[#words]
local should_generate_cat = #words == 1
local should_generate_rhyme =
not last_word:find("%-$") and -- no if word is a prefix
not (last_word:find("^%-") and last_word:find(DOTOVER)) and -- no if word is an unstressed suffix
word_has_vowels(last_word) -- no if word has no vowels (e.g. a single letter)
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_ipa(ipa)
local should_generate_cat = not ipa:find("%s")
local should_generate_rhyme = word_has_vowels(decompose(ipa))
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_termobj(termobj)
if termobj.raw then
return should_generate_rhyme_from_ipa(termobj.raw_phonemic or termobj.raw_phonetic)
else
return should_generate_rhyme_from_respelling(termobj.term)
end
end
local function process_specified_rhymes(rhymes, sylls, parsed_respellings)
local rhyme_ret = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the syllabification.
if not num_syl then
num_syl = {}
for _, syll in ipairs(sylls) do
if should_generate_rhyme_from_respelling(syll.syllabification) then
local this_num_syl = 1 + ulen(rsub(syll.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for _, pronun in ipairs(parsed.pronuns) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given), and rhyme
-- isn't suppressed (which may happen if the term has a qualifier "colloquial", "obsolete" or the
-- like or is an auto-generated "glottal stop elision" pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_ipa(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
local rhymeobj = m_table.shallowCopy(rhyme)
rhymeobj.num_syl = num_syl
table.insert(rhyme_ret, rhymeobj)
end
end
-- Parse a pronunciation modifier in `arg`, the argument portion in an inline modifier (after the prefix), which
-- specifies a pronunciation property such as rhyme, syllabification, homophones or audio. The argument can itself have
-- inline modifiers, e.g. <audio:Foo.ogg<a:Colombia>>. The allowed inline modifiers are specified by `param_mods` (of
-- the format expected by `parse_inline_modifiers()`); in addition to any modifiers specified there, the modifiers
-- <q:...>, <qq:...>, <a:...> and <aa:...> are always accepted (and can be repeated). `generate_obj` and `parse_err` are
-- like in `parse_inline_modifiers()` and specify respectively a function to generate the object into which modifier
-- properties are stored given the non-modifier part of the argument, and a function to generate an error message (given
-- the message). Normally, a comma-separated list of pronunciation properties is accepted and parsed, where each element
-- in the list can have its own inline modifiers and where no spaces are allowed next to the commas in order for them to
-- be recognized as separators. If `no_split_on_comma` is given, only a single pronunciation property is accepted. If
-- `has_outer_container` is given, the list of pronunciation properties is embedded in the `terms` property of an outer
-- container, into which other list-level modifiers can also be stored (by setting `overall = "true"` in the respective
-- spec in `param_mods`). The return value is a list if neither `no_split_on_comma` nor `has_outer_container` are given,
-- otherwise a container object (which, in the case of `has_outer_container`, will contain a list inside of it, in the
-- `terms` property).
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma, has_outer_container)
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(parse_utilities_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
outer_container = has_outer_container and {} or nil,
})
elseif no_split_on_comma then
return generate_obj(arg)
else
local retval = {}
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
if has_outer_container then
retval = {
terms = retval,
}
end
return retval
end
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
type = "number",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_syll(arg, parse_err)
local param_mods = {
cap = { overall = true},
}
-- We need to pass in has_outer_container because we have an overall property <cap:...> (the caption, defaulting
-- to "Syllabification") applying to the whole set of syllabifications.
return parse_pron_modifier(arg, parse_err, generate_syll_obj, param_mods, nil, "has outer container")
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the genders in "genders".
item_dest = "genders",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space (typically followed by
-- an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
local function syllabify_from_spelling(text, pagename)
-- Auto syllabifications start --
local vowel = vowel .. "ẃýāēīōū" -- vowel
local V = "[" .. vowel .. "]"
local NV = "[^" .. vowel .. "]"
local C = "[^" .. vowel .. separator .."]" -- consonant
text = decompose(text, "recompose e-dia")
local origtext = text
text = string.lower(text)
text = rsub(text, "[.] ", "․ ")
text = rsub(text, "[.]$", "․")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
text = rsub(text, "ng̃", "ŋ")
text = rsub(text, "ng", "ŋ")
text = rsub(text, "g̃", "ġ")
text = rsub(text, "ch", "ĉ")
text = rsub(text, "t_s", "ć")
text = rsub(text, "sh", "ʃ")
text = rsub(text, "gu([eëiy])", "ǵ%1")
text = rsub(text, "qu([eëiy])", "ḱ%1")
text = rsub(text, "r", "ɾ")
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "ʔ", "7")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "#(" .. C .. "+)u([aeio])","#%1u.%2")
text = rsub_repeatedly(text, "#(" .. C .. "+)i([aeou])","#%1i.%2")
text = rsub_repeatedly(text, "(" .. C .. ")u([aeio])","#%1.u%2")
text = rsub_repeatedly(text, "(" .. C .. ")i([aeou])","#%1.i%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)u([aeio])","%1.u%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)o([aei])","%1.ó%2")
text = rsub(text, "a(" .. accent_c .. "*)o([#.7])","a%1ó%2")
-- eu rules
text = rsub_repeatedly(text, "([^" .. vowel .. "#])([e])(" .. accent_c .. "?)([u])(" .. accent_c .. "?)","%1%2%3.%4%5")
text = rsub(text, "y([ˈˌ." .. accent .. "]*)([bćĉdfgǵhjĵkḱlmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","ý%1%2")
text = rsub(text, "ý(" .. V .. ")", "y%1")
text = rsub(text, "w([ˈˌ]?)([bćĉdfgǵjĵkḱlmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","ẃ%1%2")
text = rsub(text, "ẃ(" .. V .. ")","w%1")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ẃ([bdfgǵkḱpt])([ɾr" .. vowel .. separator .."])" ,"%1%2w%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ẃ([bfgǵkḱp])([l" .. vowel .. separator .."])" ,"%1%2w%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ý([bdfgǵkḱpt])([ɾr" .. vowel .. separator .."])" ,"%1%2y%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ý([bfgǵkḱp])([l" .. vowel .. separator .."])" ,"%1%2y%3%4")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^lɾrɟy" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dk])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([t])([^lɾrɟys" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfgǵkḱlmnpsʃvz])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^lɾrɟysʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeëo, or stressed iu, should be syllabically divided from a following aeëo or stressed iu.
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)(" .. V .. accent_c .. ")", "%1.%2")
text = rsub(text, "([iu]" .. accent_c .. ")([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([iu]" .. accent_c .. ")(" .. V .. accent_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
text = rsub(text, "ĉ", "ch")
text = rsub(text, "ć", "ts")
text = rsub(text, "ŋ", "ng")
text = rsub(text, "ʃ", "sh")
text = rsub(text, "ǵ", "gu")
text = rsub(text, "ġ", "g̃")
text = rsub(text, "ḱ", "qu")
text = rsub(text, "r", "rr")
text = rsub(text, "ɾ", "r")
-- text = remove_accents(text)
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub(text, "[.]?-[.]?", "-")
text = rsub(text, "[‿]([^ ])", "|%1")
text = rsub(text, "[.]([^ ])", "|%1")
text = rsub(text, "([|])+", "%1")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
text = rsub(text, "․", ".")
-- Fix Capitalization --
local syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" and origtext:sub(i-syllbreak, i-syllbreak) ~= "." and origtext:sub(i-syllbreak, i-syllbreak) ~= "7" then
syllbreak = syllbreak + 1
elseif origtext:sub(i-syllbreak, i-syllbreak) == text:sub(i,i):upper() then
text = table.concat({text:sub(1, i-1), text:sub(i,i):upper(), text:sub(i+1)})
end
end
-- Fix hyphens --
-- FIXME!!! Why are we relying on looking at the pagename here? This should not be happening.
origtext = pagename
if (table.concat(rsplit(origtext, "-")) == table.concat(rsplit(table.concat(rsplit(text, "|")), "-"))) then
syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" then
if origtext:sub(i-syllbreak, i-syllbreak) == "-" then
text = table.concat({text:sub(1, i-1), "-", text:sub(i+1)})
else
syllbreak = syllbreak + 1
end
end
end
end
-- FIXME! Hack -- up above we changed periods to vertical bars. The rest of the code expects periods so change
-- them back. We should clean up the code above to leave the periods alone.
return (text:gsub("|", "%."))
end
function export.syllabify_and_align(respelling, pagename)
local syllabification = syllabify_from_spelling(respelling, pagename)
return align_syllabification_to_spelling(syllabification, pagename)
end
local function css_wrap(text, classes)
return ('<span class="%s">%s</span>'):format(classes, text)
end
local function format_glosses(glosses)
if not glosses then
return ""
end
local formatted_glosses = {}
for _, glossobj in ipairs(glosses) do
local gloss_parts = {}
if glossobj.gloss then
table.insert(gloss_parts, css_wrap("“", "mention-gloss-double-quote") ..
css_wrap(glossobj.gloss, "mention-gloss") .. css_wrap("”", "mention-gloss-double-quote"))
end
if glossobj.poses then
for _, pos in ipairs(glossobj.poses) do
table.insert(gloss_parts, css_wrap(pos, "ann-pos"))
end
end
table.insert(formatted_glosses, table.concat(gloss_parts, css_wrap(",", "mention-gloss-comma") .. " "))
end
return " " .. css_wrap("(", "mention-gloss-paren annotation-paren") ..
table.concat(formatted_glosses, css_wrap(";", "mention-gloss-semicolon") .. " ") ..
css_wrap(")", "mention-gloss-paren annotation-paren")
end
local function format_pronuns(pronuns)
local pronunciations = {}
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]].
for j, pronun in ipairs(pronuns) do
local qs = pronun.q
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
end
local last_pronun = #pronunciations
if pronun.q then
pronunciations[first_pronun].q = pronun.q
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
end
end
return m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" }
end
local function format_pronun_line(parsed)
local formatted_pronuns = format_pronuns(parsed.pronuns)
local pre = is_first and parsed.pre and parsed.pre .. " " or ""
local post = is_first and parsed.post and " " .. parsed.post or ""
return pre .. formatted_pronuns .. format_glosses(parsed.t) .. post
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- External entry point for {{tsg-pr}}.
function export.show_full(frame)
--------------------------------- 1. Parse the arguments. ------------------------------------
local params = {
[1] = {list = true},
["rhyme"] = {},
["syll"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "=" .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_syll = args.syll and
parse_syll(args.syll, function(msg) overall_parse_err(msg, "syll", args.syll) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if #args.audio > 0 then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
table.insert(overall_audio, parsed_audio)
end
end
-- Parse each respelling. Individual arguments in 1=, 2=, etc. can consist of one or more comma-separated
-- respellings, each of which can have inline modifiers <q:...>, <qq:...>, <a:...>, <aa:...> or <ref:...>.
-- In addition, the respellings as a whole of a given argument can be followed by various inline modifiers,
-- such as <t:...>, <rhyme:...>, <syll:...>, etc. The result of parsing goes into `parsed_respellings`, which
-- is a list of objects (one per numbered argument), each of which is a table of the form
--
-- {
-- terms = {TERM, TERM, ...},
-- audio = {AUDIO, AUDIO, ...},
-- rhyme = {RHYME, RHYME, ...},
-- syll = {SYLL, SYLL, ...},
-- hmp = {HMP, HMP, ...},
-- t = {GLOSS, GLOSS, ...},
-- pre = "PRE-TEXT" or nil,
-- post = "POST-TEXT" or nil,
-- bullets = NUM_BULLETS,
-- accents = {"ACCENT", "ACCENT", ...},
-- }
--
-- In this structure, TERM is an object that usually has the form
--
-- {
-- term = "RESPELLING",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- Note that in this structure, "REF-SPEC" of the form parsable by parse_references() in [[Module:references]].
--
-- Alternatively, if phonemic or phonetic IPA is given in place of a respelling, TERM will have the form
--
-- {
-- raw = true,
-- phonemic = "PHONEMIC",
-- phonetic = "PHONETIC",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- AUDIO is a table of the form
--
-- {
-- file = "FILE",
-- gloss = "GLOSS",
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- a = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- aa = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- }
--
-- RHYME is a table of the form
--
-- {
-- rhyme = "RHYME",
-- num_syl = {NUM_SYL, NUM_SYL, ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- SYLL is a table of the form (where `hyph` is required to be named this way for [[Module:hyphenation]])
--
-- {
-- syllabification = "SYL.LAB.LES",
-- hyph = {"SYL", "LAB", "LES"},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- HMP is a table of the form
--
-- {
-- term = "HOMOPHONE",
-- gloss = "GLOSS" or nil,
-- pos = "POS" or nil,
-- alt = "ALT" or nil,
-- lit = "LIT" or nil,
-- id = "ID" or nil,
-- g = {"G", "G", ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- GLOSS is a table of the form
--
-- {
-- poses = {"POS", "POS", ...} or nil,
-- gloss = "GLOSS" or nil,
-- }
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
type = "number",
},
t = {
overall = true,
store = "insert",
convert = parse_gloss,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
syll = {
overall = true,
-- Not `store = "insert-flattened"`. parse_syll() does not generates a list but a structure where
-- the syllabifications are in `terms` and there's an additional overall property `cap` for the
-- caption (defaulting to "Syllabification"). FIXME: Rethink whether we even want "insert-flattened"
-- or just "insert" for the remaining pronunciation properties.
convert = parse_syll,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert", -- not "insert-flattened" because parse_audio returns a single object
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = {
item_dest = "accents",
overall = true,
convert = parse_accents,
},
}
local parsed = require(parse_utilities_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
pre_normalize_modifiers = function(data)
local modtext = data.modtext
modtext = modtext:match("^<(.*)>$")
if not modtext then
error(("Internal error: Passed-in modifier isn't surrounded by angle brackets: %s"):format(
data.modtext))
end
if modtext:find("%^") and not modtext:find("^t:") then
modtext = "t:" .. modtext
end
return "<" .. modtext .. ">"
end,
splitchar = ",",
outer_container = {},
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "=" .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
bullets = 1,
})
end
end
--------------------------------- 2. Generate IPA, rhymes and syllabification. ------------------------------------
-- Used for categorization below.
local syllabification_alignment_failed = false
-- Canonicalize syllabifications in `sylls` by convering '+' to the default syllabification of the pagename, '#' to
-- the pagename itself, and '-' to no syllabification (return `null_syll`). If '-' not seen, return `sylls`.
local function canonicalize_syllabification(sylls, null_syll)
for _, syll in ipairs(sylls.terms) do
if syll.syllabification == "+" then
syll.syllabification = syllabify_from_spelling(pagename, pagename)
syll.hyph = split_syllabified_spelling(syll.syllabification)
elseif syll.syllabification == "#" then
syll.syllabification = pagename
syll.hyph = {syll.syllabification}
elseif syll.syllabification == "-" then
return null_syll
end
end
return sylls
end
if overall_syll then
overall_syll = canonicalize_syllabification(overall_syll, {})
end
local function doesnt_count_for_rhyme(list)
if not list then
return false
end
local accent_no_count = {"colloquial", "obsolete", "relaxed"}
for _, item in ipairs(list) do
for _, word_no_count in ipairs(accent_no_count) do
if item:find("%f[%w]" .. word_no_count .. "%f[%W]") then
return true
end
end
end
return false
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
-- First, sort the specified accents and default to "Sinūgan Parianun".
local default_accent = "Sinūgan Parianun"
if not parsed.accents then
parsed.accents = {default_accent}
end
-- If more than one respelling given, then if any accent or qualifier has the words 'colloquial', 'obsolete' or
-- 'relaxed' in them, don't generate a rhyme or a '#-syllable word' category.
local more_than_one_respelling = #parsed.terms > 1 or #parsed_respellings > 1
local is_standard = m_table.contains(parsed.accents, default_accent) and true or false
local all_terms_no_rhyme = more_than_one_respelling and doesnt_count_for_rhyme(parsed.accents)
parsed.pronuns = {}
for i, term in ipairs(parsed.terms) do
local phonemic, phonetic
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
else
local ret = export.IPA(term.term, "include phonemic syllable boundaries")
phonemic = ret.phonemic
phonetic = ret.phonetic
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
local no_rhyme, rhyme_with_cat
-- Same check as above for colloquial/obsolete/relaxed but check the qualifiers, which are attached to
-- individual respellings rather than a single-line set of respellings.
no_rhyme = all_terms_no_rhyme or more_than_one_respelling and (
doesnt_count_for_rhyme(term.q) or doesnt_count_for_rhyme(term.qq)
)
if not no_rhyme then
local should_generate_rhyme, should_generate_cat = should_generate_rhyme_from_termobj(term)
no_rhyme = not should_generate_rhyme
rhyme_with_cat = should_generate_cat
end
local pronobj = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
no_rhyme = no_rhyme,
rhyme_with_cat = rhyme_with_cat,
}
table.insert(parsed.pronuns, pronobj)
-- Gimbahanun variant
if pronobj.phonemic:find("[iuɪʊ]") then
local gimb_charmap = { ["i"] = "ɨ", ["ɪ"] = "ɨ", ["u"] = "ɯ", ["ʊ"] = "ɯ"}
local gimb_pronobj = {
raw = pronobj.raw,
phonemic = pronobj.phonemic:gsub("[iuɪʊ]", gimb_charmap),
phonetic = pronobj.phonetic:gsub("[iuɪʊ]", gimb_charmap),
refs = pronobj.refs,
q = combine_qualifiers(pronobj.q, {"Sinūgan Gimbahanun"}),
qq = pronobj.qq,
no_rhyme = pronobj.no_rhyme,
rhyme_with_cat = pronobj.rhyme_with_cat,
move_to_next_line = true,
}
--table.insert(parsed.pronuns, gimb_pronobj)
end
-- If [fvz] present in phonemic pronunciation, generate a "more native-sounding" variant with [pbs] in
-- place.
-- local fvz_pronobj
-- if pronobj.phonemic:find("[fvz]") then
-- local fvz_charmap = { ["f"] = "p", ["v"] = "b", ["z"] = "s"}
-- fvz_pronobj = {
-- raw = pronobj.raw,
-- phonemic = pronobj.phonemic:gsub("[fvz]", fvz_charmap),
-- phonetic = pronobj.phonetic:gsub("[fvz]", fvz_charmap),
-- refs = pronobj.refs,
-- q = combine_qualifiers(pronobj.q, {"more native-sounding"}),
-- qq = pronobj.qq,
-- no_rhyme = pronobj.no_rhyme,
-- rhyme_with_cat = pronobj.rhyme_with_cat,
-- move_to_next_line = true,
-- }
-- -- "bv"/ "pf", "sz" correction
-- -- fvz_pronobj.phonetic = fvz_pronobj.phonetic:gsub("[b]([.ˈˌ]*)[b]", "%1b")
-- -- fvz_pronobj.phonetic = fvz_pronobj.phonetic:gsub("[p]([.ˈˌ]*)[p]", "%1p")
-- -- fvz_pronobj.phonetic = fvz_pronobj.phonetic:gsub("[s]([.ˈˌ]*)[s]", "%1s")
-- -- Phrase-Final unreleased stops correction
-- fvz_pronobj.phonetic = fvz_pronobj.phonetic:gsub("([pbtdkɡ])$", "%1̚")
-- table.insert(parsed.pronuns, fvz_pronobj)
-- end
end
if not parsed.syll then
if not overall_syll and any_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term, pagename)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
if not parsed.syll then
parsed.syll = {terms = {}}
end
m_table.insertIfNot(parsed.syll.terms, generate_syll_obj(aligned_syll))
else
if lang:findBestScript(pagename):getCode() == "Latn" or
lang:findBestScript(syllabification):getCode() == lang:findBestScript(pagename) then
syllabification_alignment_failed = true
end
end
end
end
end
else
parsed.syll = canonicalize_syllabification(parsed.syll, nil)
end
if not parsed.rhyme then
if overall_rhyme then
parsed.rhyme = nil
else
-- Generate the rhymes.
for _, pronun in ipairs(parsed.pronuns) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation
-- (see `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if
-- raw phonetic-only pronun is given), and rhyme isn't suppressed (which may happen if the term has
-- a qualifier "colloquial", "obsolete" or the like or is an auto-generated "glottal stop elision"
-- pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
-- Copying qualifiers to rhymes:
-- (1) If there's only one pronunciation, displaying any associated qualifier on the rhyme is
-- is redundant, so don't do it.
-- (2) If there are multiple pronunciations, then we generally do want to copy the qualifier(s)
-- from pronunciation to rhyme, but only if a given rhyme either derives from a single
-- pronunciation, or derives from multiple pronunciations all of which share the same
-- qualifier(s). We do NOT want to combine two different qualifiers from two different
-- pronunciations.
-- (3) If there are multiple pronunciations that map to a single rhyme, and all pronunciations
-- share qualifiers, then we might consider omitting the qualifiers as redundant; but this
-- case will rarely happen so it might not be worth worrying about.
-- (4) Similarly, if there are multiple pronunciations where some have the rhyme suppressed (see
-- above), and all pronunciations share qualifiers, then we might consider omitting the
-- qualifiers as redundant; but again, this case will rarely happen (especially since in
-- almost all cases the suppressed-rhyme pronunciation will have distinctive qualifiers) so
-- it probably isn't worth worrying about. Note that in the common case where the qualifiers
-- of the rhyme-suppressed pronunciation differ from those of the rhyme-included
-- pronunciation, we do want to include the qualifiers of the rhyme-included pronunciation
-- (imagine e.g. there are two pronunciations marked "standard" and "colloquial"; we want to
-- mark the rhyme as "standard").
-- (4) There are two different types of qualifiers (left and right); when comparing qualifiers,
-- we need to compare the entire set of both qualifiers and make sure they both match
-- (although it will be rare to have both left and right qualifiers on a single
-- pronunciation).
local saw_already = false
if not parsed.rhyme then
parsed.rhyme = {}
end
for _, existing in ipairs(parsed.rhyme) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
if not m_table.deepEquals(existing.q, pronun.q) or not
m_table.deepEquals(existing.qq, pronun.qq) then
existing.q = nil
existing.qq = nil
end
break
end
end
if not saw_already then
table.insert(parsed.rhyme, {
rhyme = rhyme,
num_syl = {num_syl},
q = #parsed.pronuns > 1 and pronun.q or nil,
qq = #parsed.pronuns > 1 and pronun.qq or nil,
nocat = not pronun.rhyme_with_cat,
})
end
end
end
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = process_specified_rhymes(parsed.rhyme, parsed.syll and parsed.syll.terms or {}, {parsed})
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_sylls
if overall_syll then
all_sylls = overall_syll
else
all_sylls = {}
for _, parsed in ipairs(parsed_respellings) do
if parsed.syll then
for _, syll in ipairs(parsed.syll.terms) do
m_table.insertIfNot(all_sylls, syll)
end
end
end
end
overall_rhyme = process_specified_rhymes(overall_rhyme, all_sylls, parsed_respellings)
end
end
-- Determine whether all sets of pronunciations have the same value for a pronunciation property (rhymes,
-- syllabifications or homophones). If so, we display them them only once at the bottom, otherwise beneath each set,
-- indented. This function takes one argument, the name of a slot specifying the pronunciation property, and
-- returns two values, a boolean indicating whether all values are the same and the first value seen (which will
-- be the only value seen if all values are the same).
local function all_sets_equal(parsed_slot)
local first_set
local all_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_set = parsed[parsed_slot]
elseif not m_table.deepEquals(first_set, parsed[parsed_slot]) then
all_sets_eq = false
break
end
end
return all_sets_eq, first_set
end
local all_rhyme_sets_eq, first_rhyme_ret = all_sets_equal("rhyme")
local all_syll_sets_eq, first_sylls = all_sets_equal("syll")
local all_hmp_sets_eq, first_hmps = all_sets_equal("hmp")
------------------------------ 3. Insert categories as appropriate. ---------------------------------
local categories = {}
-- local function get_rhymes_categories(rhymes)
-- if not rhymes then
-- return
-- end
-- for _, rhyme in ipairs(rhymes) do
-- local num_vowels_in_rhyme = #rsub(rhyme.rhyme, NV, "")
-- local penult = num_vowels_in_rhyme == 2
-- local glottal = rhyme.rhyme:find("ʔ$")
-- local pron_cat
-- if penult and glottal then
-- pron_cat = "malumi"
-- elseif penult then
-- pron_cat = "malumay"
-- elseif glottal then
-- pron_cat = "maragsa"
-- else
-- pron_cat = "mabilis"
-- end
-- m_table.insertIfNot(categories,
-- ("%s terms with %s pronunciation"):format(lang:getCanonicalName(), pron_cat))
-- end
-- end
-- get_rhymes_categories(overall_rhyme)
-- for _, parsed in ipairs(parsed_respellings) do
-- get_rhymes_categories(parsed.rhyme)
-- end
local function get_syll_categories(sylls)
if not sylls then
return
end
for _, syll in ipairs(sylls.terms) do
local syll_no_dot = "#" .. syll.syllabification .. "#"
syll_no_dot = syll.syllabification:gsub("%.([^ #])", "%1"):gsub("#", "")
if syll_no_dot ~= pagename then
mw.log(("For page '%s', saw syllabification '%s' not matching pagename"):format(
pagename, syll.syllabification))
m_table.insertIfNot(categories, ("%s terms with syllabification not matching pagename"):format(
lang:getCanonicalName()))
end
end
end
get_syll_categories(overall_syll)
for _, parsed in ipairs(parsed_respellings) do
get_syll_categories(parsed.syll)
end
if syllabification_alignment_failed then
table.insert(categories, ("%s terms where syllabification alignment failed"):format(lang:getCanonicalName()))
end
---------------------------- 4. Format IPA, rhymes and syllabification for display. -------------------------------
local function bullet_prefix(num_bullets)
return string.rep("*", num_bullets) .. " "
end
local function format_rhyme(rhymes)
return require(rhymes_module).format_rhymes {
lang = lang,
rhymes = rhymes,
force_cat = force_cat,
}
end
local function format_syllabifications(syllobj)
return require(hyphenation_module).format_hyphenations {
lang = lang,
hyphs = syllobj.terms,
caption = syllobj.cap or "斷字"
}
end
local function format_homophones(hmps)
return require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, bullet_prefix(num_bullets) .. text)
end
return table.concat(ret, "\n")
end
-- Implement grouping by accent. If there is a run of more than one consecutive set of pronunciations with the
-- same accent, the accent goes on its own line and the pronunciations with this accent go below with an extra
-- bullet.
local prev_accents
local num_seen_with_these_accents
for j, parsed in ipairs(parsed_respellings) do
if m_table.deepEquals(prev_accents, parsed.accents) then
parsed.of_several_accents = "continuation"
num_seen_with_these_accents = num_seen_with_these_accents + 1
if num_seen_with_these_accents == 2 then
parsed_respellings[j - 1].of_several_accents = "first"
end
else
prev_accents = parsed.accents
num_seen_with_these_accents = 1
end
end
-- Pull out autogenerated pronunciations and move to the next line, indented.
for _, parsed in ipairs(parsed_respellings) do
local saw_next_line_pronuns = false
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
saw_next_line_pronuns = true
break
end
end
if saw_next_line_pronuns then
local this_line_pronuns = {}
local next_line_pronuns = {}
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
table.insert(next_line_pronuns, pronun)
else
table.insert(this_line_pronuns, pronun)
end
end
-- Now see if there are qualifiers shared among all elements of the next-line pronuns and deduplicate if so.
local function deduplicate_qualifiers(field, keepfirst)
local saw_nil = false
for _, pronun in ipairs(next_line_pronuns) do
if not pronun[field] then
saw_nil = true
break
end
end
if not saw_nil then
local m_setutil = require(set_utilities_module)
local qualifiers = {}
for _, pronun in ipairs(next_line_pronuns) do
table.insert(qualifiers, m_setutil.list_to_set(pronun[field]))
end
local all_shared = m_setutil.intersect(unpack(qualifiers))
if next(all_shared) then
local first_index, last_index
if keepfirst then
first_index = 2
last_index = #pronun
else
first_index = 1
last_index = #pronun - 1
end
for i = first_index, last_index do
local pronun = next_line_pronuns[i]
local new_qualifiers = {}
for _, q in ipairs(pronun[field]) do
if not all_shared[q] then
table.insert(new_qualifiers, q)
end
end
pronun[field] = new_qualifiers
end
end
end
end
parsed.pronuns = this_line_pronuns
parsed.next_line_pronuns = next_line_pronuns
end
end
-- Now actually format the pronunciations.
local textparts = {}
local first_line = true
local function ins_line(linetext, num_bullets)
if not first_line then
table.insert(textparts, "\n")
end
first_line = false
table.insert(textparts, bullet_prefix(num_bullets) .. linetext)
end
local min_num_bullets = 9999
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
local accent_grouping_offset = 0
if parsed.of_several_accents == "first" then
ins_line(require(accent_qualifier_module).format_qualifiers(lang, parsed.accents), parsed.bullets)
end
local pronuns = format_pronun_line(parsed)
local accent_prefix
if not parsed.of_several_accents then
accent_prefix = require(accent_qualifier_module).format_qualifiers(lang, parsed.accents) .. " "
else
accent_prefix = ""
accent_grouping_offset = 1
end
ins_line(accent_prefix .. pronuns, parsed.bullets + accent_grouping_offset)
if parsed.next_line_pronuns then
ins_line(format_pronuns(parsed.next_line_pronuns), parsed.bullets + accent_grouping_offset + 1)
end
if parsed.audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise indent audio by
-- one more bullet.
table.insert(textparts, format_audio(parsed.audio,
(#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1) + accent_grouping_offset))
end
if not all_rhyme_sets_eq and parsed.rhyme then
ins_line(format_rhyme(parsed.rhyme), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_syll_sets_eq and parsed.syll then
ins_line(format_syllabifications(parsed.syll), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_hmp_sets_eq and parsed.hmp then
ins_line(format_homophones(parsed.hmp), parsed.bullets + 1 + accent_grouping_offset)
end
end
if overall_audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
ins_line(format_rhyme(first_rhyme_ret), min_num_bullets)
end
if overall_rhyme then
ins_line(format_rhyme(overall_rhyme), min_num_bullets)
end
if all_syll_sets_eq and first_sylls then
ins_line(format_syllabifications(first_sylls), min_num_bullets)
end
if overall_syll then
ins_line(format_syllabifications(overall_syll), min_num_bullets)
end
if all_hmp_sets_eq and first_hmps then
ins_line(format_homophones(first_hmps), min_num_bullets)
end
if overall_hmp then
ins_line(format_homophones(overall_hmp), min_num_bullets)
end
return table.concat(textparts) ..
require("Module:utilities").format_categories(categories, lang, nil, nil, force_cat)
end
-- Meant to be called from a bot.
function export.pron_json(frame)
local iparams = {
[1] = {list = true, required = true},
["pagename"] = {required = true},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local data = {}
local syllabification_from_pagename = syllabify_from_spelling(iargs.pagename, iargs.pagename)
for _, respelling in ipairs(iargs[1]) do
local pronun = export.IPA(respelling, "include phonemic syllable boundaries")
local syllabification = export.syllabify_and_align(respelling, iargs.pagename)
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
table.insert(data, {
respelling = respelling,
phonemic = pronun.phonemic,
phonetic = pronun.phonetic,
syllabification = syllabification,
num_syl = num_syl,
rhyme = rhyme,
})
end
local retval = {
pagename = iargs.pagename,
syllabification_from_pagename = syllabification_from_pagename,
data = data,
}
return require("Module:JSON").toJSON(retval)
end
return export
ejenwymf50z0dcdwx4n5byrmt9acl3t
bhh
0
3079855
9908798
9713239
2026-09-29T02:46:34Z
Chihunglu83
87715
9908798
wikitext
text/x-wiki
{{also|bḥḥ}}
==跨語言==
===符號===
{{mul-symbol}}
# {{ISO 639|3}}
===參見===
* {{langcat}}
6xyu0zgt1cs1apq9blezn3t2nl9x8dt
Module:Romance etymology
828
3101486
9909102
9235359
2026-09-29T07:28:06Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909102
Scribunto
text/plain
local export = {}
local m_links = require("Module:links")
local affix_module = "Module:affix"
local parameter_utilities_module = "Module:parameter utilities"
local force_cat = false -- set to true for testing
local function verb_obj_or_verb_verb(data, frame, obj_or_verb)
local parent_args = frame:getParent().args
local object_or_verb = obj_or_verb == "obj" and "受詞" or "動詞"
local params = {
[1] = {list = true, required = true, disallow_holes = true},
["sort"] = {},
["nocat"] = {type = "boolean"},
["nocap"] = {type = "boolean"},
["notext"] = {type = "boolean"},
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{default = true, require_index = true},
{group = "link", exclude = {"tr", "ts", "sc"}},
{group = {"q", "l", "ref"}},
-- Override to have separate_no_index set so we have an overall lit=.
{param = "lit", separate_no_index = true},
{param = {"pl", "imp"}},
{param = "type", set = {"動詞", "受詞", "連接詞"}},
}
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
process_args_before_parsing = function(args)
if #args[1] < 2 then
local NAMESPACE = mw.title.getCurrentTitle().nsText
if NAMESPACE == "Template" then
for _, defarg in ipairs(data.default_args) do
table.insert(args[1], defarg)
end
else
error(("Need at least two numbered arguments to [[Template:%s-%s]]"):format(
data.lang:getCode(), template_name))
end
end
end,
termarg = 1,
track_module = "romance-etymology",
parse_lang_prefix = true,
-- Don't include `lang` because the code below expects it to be set only when explicitly given
}
for i, item in ipairs(items) do
if not item.term:find("%[") then
local parttype = item.type
if not parttype then
if i == 1 then
parttype = "動詞"
elseif i == #items then
parttype = object_or_verb
else
if data.looks_like_infinitive(item.term) then
parttype = "動詞"
else
parttype = "連接詞"
end
end
end
if parttype ~= "受詞" and item.pl then
parse_err(("Can't specify <pl:...> with an argument that is not an object (argument type is '%s')"):
format(parttype))
end
if parttype ~= "動詞" and item.imp then
parse_err(("Can't specify <imp:...> with an argument that is not a verb (argument type is '%s')"):
format(parttype))
end
if parttype == "動詞" then
local imp
item.imp = item.imp or "+"
if item.imp:find("^%+") then
if item.lang then
parse_err(("Can't form default imperative given with explicit language code prefix '%s'"):
format(item.lang:getCode()))
end
imp = data.make_imperative(item.imp, item.term, parse_err)
if not imp then
parse_err("Default imperative algorithm was unable to form the imperative")
end
elseif item.imp then
imp = item.imp
end
if imp then
item.term = ("[[%s|%s]]"):format(item.term, imp)
end
elseif parttype == "受詞" then
local pl
if item.pl == "1" or item.pl and item.pl:find("^%+") then
if item.lang then
parse_err(("Can't form default plural of term '%s' given with explicit language code prefix '%s'"):
format(item.term, item.lang:getCode()))
end
pl = data.make_plural(item.pl, item.term, parse_err)
if not pl then
parse_err(("Default plural algorithm was unable to form the plural of term '%s'"):format(item.term))
end
elseif item.pl then
pl = item.pl
end
if pl then
item.term = ("[[%s|%s]]"):format(item.term, pl)
end
end
end
item.lang = item.lang or data.lang
-- l10n: zhwikt 的 [[Module:parameter utilities]] 不會自動設定 show_decorations,需在此設定
item.show_decorations = true
items[i] = m_links.full_link(item, "term", "allow self link")
end
local result = {}
local function ins(text)
table.insert(result, text)
end
if not args.notext then
if args.nocap then
ins("動詞-" .. object_or_verb)
else
ins("動詞-" .. object_or_verb)
end
ins("複合詞,由 ")
end
ins(require(affix_module).join_formatted_parts {
data = {
lang = data.lang,
nocat = args.nocat,
sort_key = args.sort,
lit = args.lit.default,
force_cat = force_cat,
q = args.q.default,
qq = args.qq.default,
l = args.l.default,
ll = args.ll.default,
},
parts_formatted = items,
categories = {("動詞-%s複合詞"):format(object_or_verb)},
})
ins(" 組成")
return table.concat(result)
end
function export.verb_obj(data, frame)
return verb_obj_or_verb_verb(data, frame, "obj")
end
function export.verb_verb(data, frame)
return verb_obj_or_verb_verb(data, frame, "動詞")
end
return export
aop9pv9o3ath4xce64k1kozlmh6698c
9909186
9909102
2026-09-29T09:47:49Z
TongcyBot
83009
移除手動 show_decorations
9909186
Scribunto
text/plain
local export = {}
local m_links = require("Module:links")
local affix_module = "Module:affix"
local parameter_utilities_module = "Module:parameter utilities"
local force_cat = false -- set to true for testing
local function verb_obj_or_verb_verb(data, frame, obj_or_verb)
local parent_args = frame:getParent().args
local object_or_verb = obj_or_verb == "obj" and "受詞" or "動詞"
local params = {
[1] = {list = true, required = true, disallow_holes = true},
["sort"] = {},
["nocat"] = {type = "boolean"},
["nocap"] = {type = "boolean"},
["notext"] = {type = "boolean"},
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{default = true, require_index = true},
{group = "link", exclude = {"tr", "ts", "sc"}},
{group = {"q", "l", "ref"}},
-- Override to have separate_no_index set so we have an overall lit=.
{param = "lit", separate_no_index = true},
{param = {"pl", "imp"}},
{param = "type", set = {"動詞", "受詞", "連接詞"}},
}
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
process_args_before_parsing = function(args)
if #args[1] < 2 then
local NAMESPACE = mw.title.getCurrentTitle().nsText
if NAMESPACE == "Template" then
for _, defarg in ipairs(data.default_args) do
table.insert(args[1], defarg)
end
else
error(("Need at least two numbered arguments to [[Template:%s-%s]]"):format(
data.lang:getCode(), template_name))
end
end
end,
termarg = 1,
track_module = "romance-etymology",
parse_lang_prefix = true,
-- Don't include `lang` because the code below expects it to be set only when explicitly given
}
for i, item in ipairs(items) do
if not item.term:find("%[") then
local parttype = item.type
if not parttype then
if i == 1 then
parttype = "動詞"
elseif i == #items then
parttype = object_or_verb
else
if data.looks_like_infinitive(item.term) then
parttype = "動詞"
else
parttype = "連接詞"
end
end
end
if parttype ~= "受詞" and item.pl then
parse_err(("Can't specify <pl:...> with an argument that is not an object (argument type is '%s')"):
format(parttype))
end
if parttype ~= "動詞" and item.imp then
parse_err(("Can't specify <imp:...> with an argument that is not a verb (argument type is '%s')"):
format(parttype))
end
if parttype == "動詞" then
local imp
item.imp = item.imp or "+"
if item.imp:find("^%+") then
if item.lang then
parse_err(("Can't form default imperative given with explicit language code prefix '%s'"):
format(item.lang:getCode()))
end
imp = data.make_imperative(item.imp, item.term, parse_err)
if not imp then
parse_err("Default imperative algorithm was unable to form the imperative")
end
elseif item.imp then
imp = item.imp
end
if imp then
item.term = ("[[%s|%s]]"):format(item.term, imp)
end
elseif parttype == "受詞" then
local pl
if item.pl == "1" or item.pl and item.pl:find("^%+") then
if item.lang then
parse_err(("Can't form default plural of term '%s' given with explicit language code prefix '%s'"):
format(item.term, item.lang:getCode()))
end
pl = data.make_plural(item.pl, item.term, parse_err)
if not pl then
parse_err(("Default plural algorithm was unable to form the plural of term '%s'"):format(item.term))
end
elseif item.pl then
pl = item.pl
end
if pl then
item.term = ("[[%s|%s]]"):format(item.term, pl)
end
end
end
item.lang = item.lang or data.lang
items[i] = m_links.full_link(item, "term", "allow self link")
end
local result = {}
local function ins(text)
table.insert(result, text)
end
if not args.notext then
if args.nocap then
ins("動詞-" .. object_or_verb)
else
ins("動詞-" .. object_or_verb)
end
ins("複合詞,由 ")
end
ins(require(affix_module).join_formatted_parts {
data = {
lang = data.lang,
nocat = args.nocat,
sort_key = args.sort,
lit = args.lit.default,
force_cat = force_cat,
q = args.q.default,
qq = args.qq.default,
l = args.l.default,
ll = args.ll.default,
},
parts_formatted = items,
categories = {("動詞-%s複合詞"):format(object_or_verb)},
})
ins(" 組成")
return table.concat(result)
end
function export.verb_obj(data, frame)
return verb_obj_or_verb_verb(data, frame, "obj")
end
function export.verb_verb(data, frame)
return verb_obj_or_verb_verb(data, frame, "動詞")
end
return export
o9gmohv6n3yb91y2jik4i81dfgzeg84
Module:Rue-pron
828
3130998
9909105
9274654
2026-09-29T07:28:18Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909105
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local decorations_module = "Module:decorations"
local put_module = "Module:parse utilities"
local force_cat = false -- for testing
local lang = require("Module:languages").getByCode("rue")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local DIA = u(0x0308) -- diaeresis = ̈
local BR = u(0x0306) -- breve
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeɛioɔuɪɤаеіиыоуяєїёюАЕІЫОУЯЄЇЁЮ" -- vowels
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR
local accent_c = "[" .. accent .. "]"
local stress = AC .. GR
local stress_c = "[" .. AC .. GR .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class
local C_OR_WORDSEP = "[^" .. vowel .. separator_not_wordsep .. "]" -- consonant class, or word separator
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ї, ё, й
text = unfd(text)
text = rsub(text, ".[" .. DIA .. BR .. "]", {
["і" .. DIA] = "ї",
["І" .. DIA] = "Ї",
["е" .. DIA] = "ё",
["Е" .. DIA] = "Ё",
["и" .. BR] = "й",
["И" .. BR] = "Й",
})
return text
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Remove any HTML from the formatted text and resolve links, since the extra characters don't contribute to the
-- displayed length.
local function convert_to_raw_text(text)
text = rsub(text, "<.->", "")
if text:find("%[%[") then
text = require("Module:links").remove_links(text)
end
return text
end
-- Return the approximate displayed length in characters.
local function textual_len(text)
return ulen(convert_to_raw_text(text))
end
-- Main syllable-division algorithm
local function syllabify_from_spelling_or_pronun(text, is_spelling)
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels, hiatuses too
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. V .. ")", "%1.%2")
-- Part 2: Group soft/hard signs with the previous consonant
text = rsub(text, "%.([ьъ])", "%1.")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- Temporary replacements for characters we want treated as default consonants.
text = rsub(text, "дз", "s")
text = rsub(text, "Дз", "S")
text = rsub(text, "дж", "j")
text = rsub(text, "Дж", "J")
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = text:gsub("%.", SYLDIV)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
text = text:gsub(SYLDIV, ".")
text = text:gsub("s", "дз")
text = text:gsub("S", "Дз")
text = text:gsub("j", "дж")
text = text:gsub("J", "Дж")
text = unfc(text)
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text)
local debug = {}
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ä, ë, ï
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Convert hyphens to spaces
text = rsub(text, "%-$", "@") --we will use this symbol to allow generating the pronunciation of unstressed prefixes
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--vowels (we will change <eo> to <ɛɔ> later)
vowel_conv = {["а"] = "a", ["е"] = "e", ["і"] = "i", ["и"] = "ɪ", ["ы"] = "ɤ", ["о"] = "ɔ", ["у"] = "u", ["я"] = "ja", ["є"] = "je", ["ї"] = "ji", ["ё"] = "jo", ["ю"] = "ju"}
text = rsub(text, "[аеіиыоуяєїёю]", vowel_conv)
--consonants (we will switch to the IPA <ɡ> later)
cons_conv = {
["б"] = "b", ["п"] = "p",
["к"] = "k", ["ґ"] = "g",
["т"] = "t", ["д"] = "d",
["м"] = "m", ["н"] = "n",
["ц"] = "c", ["ч"] = "č",
["ш"] = "ʃ", ["с"] = "s",
["ф"] = "f", ["г"] = "h",
["в"] = "v", ["з"] = "z",
["х"] = "x", ["ж"] = "ʒ",
["л"] = "l", ["р"] = "r"
}
text = rsub(text, "дз", "y")
text = rsub(text, "дж", "ђ")
text = rsub(text, "[бпкґтдмнцчшсфгвзхжлр]", cons_conv)
--palatalization
-- Since 2005, the soft and hard signs are used as in Russian: C + ъ + jV -> CjV and C + ь + jV -> CʲjV. In older spellings C + ь + jV might stand for CjV
palat_pairs = {["t"] = "ť", ["d"] = "ď", ["n"] = "ń", ["s"] = "ś", ["z"] = "ź", ["r"] = "ŕ", ["l"] = "ĺ", ["c"] = "ć", ["y"] = "ý"}
text = rsub(text, "([tdnszrlcy])([ьj])", function(letter, pal) return palat_pairs[letter] end)
text = rsub(text, "ъ", "")
text = rsub(text, "й", "j")
--assimilation of voicelessness
voice_pairs = {["b"] = "p", ["d"] = "t", ["ď"] = "ť", ["g"] = "k", ["z"] = "s", ["ź"] = "ś", ["ʒ"] = "ʃ", ["h"] = "x", ["ђ"] = "č", ["y"] = "c", ["ý"] = "ć"}
voiceless_pairs = {["p"] = "b", ["t"] = "d", ["ť"] = "ď", ["k"] = "g", ["f"] = "v", ["s"] = "z", ["ś"] = "ź", ["ʃ"] = "ʒ", ["x"] = "h", ["c"] = "y", ["ć"] = "ý", ["č"] = "ђ"}
text = rsub_repeatedly(text, "([bdďgzźʒhђyý])([#%s]*)([ptťkcćčfsśʃx])", function(prev_c, sep, next_c) return voice_pairs[prev_c] .. sep .. next_c end)
text = rsub(text, "([bdďgzźʒhђyý])#", function(prev_c) return voice_pairs[prev_c] .. "#" end)
--/v~w/
text = rsub(text, "(" .. V .. ")(" .. accent_c .."*)v(" .. C .. ")", "%1%2w%3")
text = rsub(text, "(" .. V .. ")(" .. accent_c .."*)v#", "%1%2w#")
text = rsub(text, "wj", "vj")
--assimilation of voicedness
text = rsub_repeatedly(text, "([ptťkfsśʃxcćč])([#%s]*)([bdďgvzźʒhђyý])", function(prev_c, sep, next_c) return voiceless_pairs[prev_c] .. sep .. next_c end)
--assimilation of palatalization
text = rsub_repeatedly(text, "([tdnszrlcy])([ťďńśźŕĺćýђčщ])", function(prev_c, next_c) return palat_pairs[prev_c] .. next_c end)
--assimilation of place of articulation
text = rsub(text, "s([#%s]*)([ʃč])", "ʃ%1%2")
text = rsub(text, "z([#%s]*)([ʒђ])", "ʒ%1%2")
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--stress
local accent_to_stress_mark = {[AC] = "ˈ", [GR] = "ˌ"}
local words = rsplit(text, " ")
for j, word in ipairs(words) do
local syllables = rsplit(word, "%.")
if #syllables > 1 and not (rfind(word, AC) or rfind(word, "@")) then
error("Multisyllabic words that are not prefixes or suffixes must have an acute accent marking the stress.")
end
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
words[j] = table.concat(syllables, ".")
-- suppress syllable mark before IPA stress indicator
words[j] = rsub(words[j], "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˈ%1ˌ")
end
text = table.concat(words, " ")
text = rsub(text, "@", "")
--final conversions
final_conversions={
["g"] = "ɡ", ["ť"] = "tʲ",
["ń"] = "nʲ", ["ď"] = "dʲ",
["ś"] = "sʲ", ["ź"] = "zʲ",
["ŕ"] = "rʲ", ["ĺ"] = "lʲ",
["ć"] = "t͡sʲ", ["c"] = "t͡s",
["č"] = "t͡ʃ", ["y"] = "d͡z",
["ý"] = "d͡zʲ", ["ђ"] = "d͡ʒ",
["e"] = "ɛ", ["o"] = "ɔ"}
text = rsub(text, "[geońťďśźŕĺcćčyýђ]",final_conversions)
text = rsub(text, "ʃ([%.]*)ʃ", "%1ʃː")
text = rsub(text, "ʒ([%.]*)ʒ", "%1ʒː")
text = rsub(text, "щ", "ʃt͡ʃ")
text = rsub(text, "#", "")
text = unfc(text)
return {text = text}
end
local function express_all_styles(dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
dodialect(ret)
table.insert(ret.expressed_styles, {
styles = {{pronun = ret.pronun,}},
})
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style_group in ipairs(expressed_styles) do
style_group.formatted, style_group.formatted_len =
format_style(style_group.styles[1].tag, style_group.styles[1], i == 1)
end
local lines = {}
for i, style_group in ipairs(expressed_styles) do
table.insert(lines, style_group.formatted)
end
return table.concat(lines, "\n")
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic
local phonetic
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
else
phonetic = export.IPA(term.term)
phonetic = phonetic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = express_all_styles(this_dodialect_pronun)
local function format_style(tag, expressed_style, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation.
for j, pronun in ipairs(expressed_style.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, tag)
else
qs = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonetic and not pronun.phonemic then
error("Internal error: Saw no phonetic or phonemic pronunciation")
end
if pronun.phonetic then
local brack_pron = "[" .. pronun.phonetic:gsub("%.", "") .. "]"
table.insert(pronunciations, {
pron = brack_pron,
})
ins(brack_pron)
end
if pronun.phonemic then --this is needed if we use parameters of the form raw:/.../, but in most cases this code won't be used
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if qs or pronun.qq or pronun.a or pronun.aa then
-- Note: This inserts the actual formatted decoration text, including HTML and such, but the later call
-- to textual_len() removes all HTML and reduces links.
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = qs,
qq = pronun.qq,
a = pronun.a,
aa = pronun.aa,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`, and also try to construct the equivalent without HTML
-- and wiki markup in `formatted_for_len`, so we can compute the approximate textual length for use in sizing
-- the toggle box with the "more" button on the right.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
local formatted_for_len = bullet .. pre .. "IPA(key): " .. table.concat(formatted_pronuns) .. post
return formatted, textual_len(formatted_for_len)
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", "")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.#), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." or ci == "#" then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
--FIXME Add all words consisting of a single consonant
if word == "з" then
return true
end
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts
not words[1]:find("%-$") and -- no if word is a prefix
not words[1]:find("^%-") and words[1]:find(AC) and -- no if word is unstressed (ie has no explicit stress)
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(put_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{rue-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().subpageText
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = require(put_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonetic or (pronun.phonemic and not pronun.phonetic) then
local num_syl
local rhyme
if pronun.phonetic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
num_syl = get_num_syl_from_phonemic(pronun.phonetic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
rhyme = convert_phonemic_to_rhyme(pronun.phonetic)
else
--Same as above, when using raw:/.../
num_syl = get_num_syl_from_phonemic(pronun.phonemic)
rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
end
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = express_all_styles(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = express_all_styles(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, expressed_style, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(expressed_style.pronun) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
q = tag and {tag} or nil,
force_cat = force_cat,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require("Module:rhymes").format_rhymes(data)
local formatted_for_len_parts = {}
table.insert(formatted_for_len_parts, bullet .. "Rhymes: " .. (tag and "(" .. tag .. ") " or ""))
for j, pronun in ipairs(expressed_style.pronun) do
if j > 1 then
table.insert(formatted_for_len_parts, ", ")
end
if pronun.q then
table.insert(formatted_for_len_parts, "(" .. table.concat(pronun.q, ", ") .. ") ")
end
table.insert(formatted_for_len_parts, "-" .. pronun.rhyme)
end
return formatted, textual_len(table.concat(formatted_for_len_parts))
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs, caption = "Syllabification" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
7e27ehdndray0ji1nzasbslxpflpptw
Module:Message box/doc
828
3168672
9908782
9323649
2026-09-28T21:25:27Z
Cewbot
61744
[[User:Cewbot/log/20211203/configuration|同步通用頁面]]: [[w:zh:Module:Message box/doc]] [[w:zh:Special:PermanentLink/94481201|2026-09-21]] (輔助理解用的模板說明文件)
9908782
wikitext
text/x-wiki
<noinclude><!-- 本頁面複製自[[w:zh:Module:Message box/doc]],由機器人定期更新。請直接編輯原維基項目頁面,或自設定頁面[[User:Cewbot/log/20211203/configuration]]去除本頁面之後再編輯。 --></noinclude>{{High-use|1341719|all-pages = yes}}
{{Used in system}}
{{PPL}}
{{Uses TemplateStyles
|Template:Ambox/style.css
|Template:Imbox/style.css
|Template:Tmbox/style.css
|Template:Ombox/style.css
|Template:Cmbox/style.css
|Template:Fmbox/style.css
|Module:Message box/mbox.css
}}
本元模块({{lang|en|meta-module}})实现了消息框模板{{tl|mbox}}、{{tl|ambox}}, {{tl|cmbox}}、{{tl|fmbox}}、{{tl|imbox}}、{{tl|ombox}}及{{tl|tmbox}}。本模块只应由Lua模块使用,不应由wiki页面直接调用。如要从wiki页面使用本模块的功能,应使用对应的消息框模板。
== 用法 ==
To use this module from another Lua module, first you need to load it.
<syntaxhighlight lang="lua">
local messageBox = require('Module:Message box')
</syntaxhighlight>
To create a message box, use the <code>main</code> function. It takes two parameters: the first is the box type (as a string), and the second is a table containing the message box parameters.
<syntaxhighlight lang="lua">
local box = messageBox.main( boxType, {
param1 = param1,
param2 = param2,
-- More parameters...
})
</syntaxhighlight>
There are seven available box types:
{| class="wikitable"
! Box type !! Template !! Purpose
|-
| <code>mbox</code> || {{tl|mbox}} || For message boxes to be used in multiple namespaces
|-
| <code>ambox</code> || {{tl|ambox}} || For article message boxes
|-
| <code>cmbox</code> || {{tl|cmbox}} || For category message boxes
|-
| <code>fmbox</code> || {{tl|fmbox}} || For interface message boxes
|-
| <code>imbox</code> || {{tl|imbox}} || For file namespace message boxes
|-
| <code>tmbox</code> || {{tl|tmbox}} || For talk page message boxes
|-
| <code>ombox</code> || {{tl|ombox}} || For message boxes in other namespaces
|}
See the template page of each box type for the available parameters.
== #invoke用法 ==
As well as the <code>main</code> function, this module has separate functions for each box type. They are accessed using the code <code><nowiki>{{#invoke:Message box|mbox|...}}</nowiki></code>, <code><nowiki>{{#invoke:Message box|ambox|...}}</nowiki></code>, etc. These will work when called from other modules, but they access code used to process arguments passed from #invoke, and so calling them will be less efficient than calling <code>main</code>.
== 技术细节 ==
The module uses the same basic code for each of the templates listed above; the differences between each of them are configured using the data at [[Module:Message box/configuration]]. Here are the various configuration options and what they mean:
* <code>types</code> - a table containing data used by the type parameter of the message box. The table keys are the values that can be passed to the type parameter, and the table values are tables containing the class and the image used by that type.
* <code>default</code> - the type to use if no value was passed to the type parameter, or if an invalid value was specified.
* <code>showInvalidTypeError</code> - whether to show an error if the value passed to the type parameter was invalid.
* <code>allowBlankParams</code> - usually blank values are stripped from parameters passed to the module. However, whitespace is preserved for the parameters included in the allowBlankParams table.
* <code>allowId</code> - whether it is possible to set an "id" attribute to the html table tag.
* <code>allowSmall</code> - whether a small version of the message box can be produced with "small=yes".
* <code>smallParam</code> - a custom name for the small parameter. For example, if set to "left" you can produce a small message box using "small=left".
* <code>smallClass</code> - the class to use for small message boxes.
* <code>substCheck</code> - whether to perform a subst check or not.
* <code>classes</code> - an array of classes to use with the message box.
* <code>usePlainlinksParam</code> - whether to allow "plainlinks=no" to turn off the plainlinks class.
* <code>imageEmptyCell</code> - whether to use an empty {{tag|td}} cell if there is no image set. This is used to preserve spacing for message boxes with a width of less than 100% of the screen.
* <code>imageEmptyCellStyle</code> - whether empty image cells should be styled.
* <code>imageCheckBlank</code> - whether "image=blank" results in no image being displayed.
* <code>imageSmallSize</code> - usually, images used in small message boxes are set to 30x30px. This sets a custom size.
* <code>imageCellDiv</code> - whether to enclose the image in a div enforcing a maximum image size.
* <code>useCollapsibleTextFields</code> - whether to use text fields that can be collapsed, i.e. "issue", "fix", "talk", etc. Currently only used in ambox.
* <code>imageRightNone</code> - whether imageright=none results in no image being displayed on the right-hand side of the message box.
* <code>sectionDefault</code> - the default name for the "section" parameter. Depends on <code>useCollapsibleTextFields</code>.
* <code>allowMainspaceCategories</code> - allow categorisation in the main namespace.
* <code>templateCategory</code> - the name of a category to be placed on the template page.
* <code>templateCategoryRequireName</code> - whether the <code>name</code> parameter is required to display the template category.
* <code>templateErrorCategory</code> - the name of the error category to be used on the template page.
* <code>templateErrorParamsToCheck</code> - an array of parameter names to check. If any are absent, the <code>templateErrorCategory</code> is applied to the template page.
7jz344ooqjg5lnec48fjq0vjpdwsvjg
Module:Gmh-pronunciation
828
3169859
9909070
9324983
2026-09-29T07:26:09Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909070
Scribunto
text/plain
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local lang = require("Module:languages").getByCode("gmh")
local U = mw.ustring.char
local rsub = mw.ustring.gsub
local rlower = mw.ustring.lower
local rfind = mw.ustring.find
local raised = U(0x031D)
local consonants = "bpmwfdtznɲrlkɡhvxjsʋʃs̄s̠ŋ"
local C = "[" .. consonants .. "]"
local vowels = "aeɛiouæøyə"
local function track(page)
require("Module:debug").track("gmh-IPA/" .. page)
return true
end
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local trigraphs = {
["sch"] = "ʃ",
}
local digraphs = {
["iu"] = "yː",
["ei"] = "eɪ̯",
["ie"] = "ɪe̯",
["ou"] = "ɔʊ̯",
["uo"] = "ʊɔ̯",
["öu"] = "œʏ̯",
["eu"] = "ɛu̯",
["üe"] = "ʏe̯",
["pf"] = "p͡f",
["ph"] = "p͡f",
["ng"] = "ŋg",
["ch"] = "x",
["qu"] = "kw",
["ht"] = "xt",
["hs"] = "xs",
["lh"] = "lx",
["rh"] = "rx",
["ss"] = "s̠ː",
["pp"] = "pː",
["bb"] = "bː",
["tt"] = "tː",
["dd"] = "dː",
["ck"] = "kː",
["gg"] = "gː",
["ff"] = "fː",
["mm"] = "mː",
["nn"] = "nː",
["ll"] = "lː",
["rr"] = "rː",
["tz"] = "zː",
["ȥȥ"] = "ȥː",
}
local function flatmap(items, fun)
local new = {}
for _, item in ipairs(items) do
local results = fun(item)
mw.logObject(results, "results")
for _, result in ipairs(results) do
table.insert(new, result)
end
mw.logObject(new, "new")
end
return new
end
local phon = {
["a"] = "a",
["ā"] = "aː",
["â"] = "aː",
["ä"] = "æ",
["æ"] = "æː",
["e"] = "e",
["ë"] = "ɛ",
["ē"] = "ɛː",
["ê"] = "ɛː",
["i"] = "ɪ",
["ī"] = "iː",
["î"] = "iː",
["o"] = "ɔ",
["ō"] = "oː",
["ô"] = "oː",
["u"] = "ʊ",
["ū"] = "uː",
["û"] = "uː",
["ö"] = "œ",
["œ"] = "øː",
["ü"] = "ʏ",
["b"] = "b",
["p"] = "p",
["m"] = "m",
["w"] = "w",
["v"] = "v",
["d"] = "d",
["t"] = "t",
["s"] = "s̠",
["ȥ"] = "s",
["z"] = "t͡s",
["n"] = "n",
["r"] = "r",
["l"] = "l",
["c"] = "k",
["k"] = "k",
["g"] = "ɡ",
["h"] = "h",
[":"] = "ː",
["?"] = "ʔ"
}
function export.phonemic(text, post)
text = rlower(text)
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
-- basic phonology
for digraph, replacement in pairs(digraphs) do
text = rsub(text, digraph, replacement)
end
text = rsub(text, ".", phon)
text = rsub(text, "^ˈ%-", "-")
if not rfind(text, "'") then
text = rsub(text, text, "ˈ" .. text)
end
text = rsub(text, "'", "ˈ")
text = rsub(text, "-", "");
text = rsub(text, "(e)(" .. C .. ")", "ə%2")
text = rsub(text, "(e)ˈ", "əˈ")
text = rsub(text, "(e)#", "ə#")
text = rsub(text, "(ˈ" .. C .. "*)(ə)", "%1e")
text = rsub(text, "(ˈ##" .. C .. "*)(ə)", "%1e")
-- suffixes
local variants = {text}
local function flatmap_and_sub_pre(from, to1, to2)
variants =
flatmap(
variants,
function(item)
if rfind(item, from) then
local retval = {rsub_repeatedly(item, from, to1)}
if to2 then
m_table.insertIfNot(retval, rsub_repeatedly(item, from, to2))
end
return retval
else
return {item}
end
end
)
end
flatmap_and_sub_pre("v", "v", "f")
flatmap_and_sub_pre("#", "")
return variants
end
function export.IPA(frame)
local terms = {}
args = frame:getParent().args
local currentTitle = mw.title.getCurrentTitle().text
for _, term in ipairs(args) do
if term == currentTitle then track("redundant") end
table.insert(terms, term)
end
if #terms == 0 then
terms = {currentTitle}
end
local results = {}
for _, term in ipairs(terms) do
local phonemic = export.phonemic(term)
for _, variant in ipairs(phonemic) do
table.insert(results, {pron = "/" .. variant .. "/"})
end
end
results[1].q = {"13世紀以前"}
return "*" .. m_IPA.format_IPA_full { lang = lang, items = results }
end
return export
nrqvfz88nrbg121yrnf8cstdhnaumoo
Module:Li-verb
828
3262572
9909082
9444446
2026-09-29T07:26:55Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909082
Scribunto
text/plain
local m_gender = require("Module:gender and number")
local m_links = require("Module:links")
local m_utils = require("Module:utilities")
local lang = require("Module:languages").getByCode("li")
local export = {}
-- Verbs that take "zeen" in the past participle.
local AUX_ZEEN = {
"baere",
"gaon"
}
-- Functions that do the actual inflecting by creating the forms of a basic term.
local inflections = {}
local irregular = {}
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local parent_args = frame:getParent().args
local infl_type = parent_args[1] == nil and guess_conjugation(frame) or parent_args[1]
if not inflections[infl_type] then
error("Unknown inflection type '" .. infl_type .. "'")
end
local data = {forms = {}, categories = {}}
data.info = ""
local args = require("Module:parameters").process(parent_args, inflections[infl_type].params)
local word = (mw.title.getCurrentTitle().nsText == "Template" and "blósse" or mw.title.getCurrentTitle().text)
data.aux = AUX_ZEEN.word and "zeen" or "höbbe"
-- Generate default pronouns
-- Generate the forms
inflections[infl_type].func(args, data)
return make_table(data)
end
function guess_conjugation(frame)
-- Strip final -e
local stem = (mw.title.getCurrentTitle().nsText == "Template" and "blóss" or mw.title.getCurrentTitle().text):sub(1, -2)
if irregular[stem .. "e"] then
return "irregular"
end
if frame:getParent().args["umlaut"] then
local final_vowel = stem:sub(-4, -2)
-- second, third, fifth, sixth
if final_vowel == "ae" then
return "second"
elseif final_vowel == "ei" then
return "third"
elseif final_vowel == "ou" then
return "fifth"
elseif final_vowel:sub(-2, -1) == "a" then
return "sixth"
else
return "irregular"
end
else
local final = stem:sub(-2, -1)
-- first, fourth, seventh
if final:find("[jlnrtw]") then
return "first"
elseif final:find("[bdfgkps]") or final == "ch" then
return "fourth"
elseif final == "mm" then
return "seventh"
else
return "irregular"
end
end
end
inflections["first"] = {
params = {
[1] = {},
[2] = {},
},
func = function(args, data)
local base = mw.title.getCurrentTitle().nsText == "Template" and "-" or mw.title.getCurrentTitle().text
-- If the final consonant is doubled, simplify it for certain forms
local single = (base:sub(-3, -3) == base:sub(-2, -2)) and base:sub(1, -3) or base
data.info = " (第一變位)"
table.insert(data.categories, "林堡語第一變位動詞")
data.forms["inf"] = {base}
data.forms["ger"] = {base}
data.forms["pres_p"] = {base .. "ndj"}
data.forms["past_p"] = {"ge" .. single}
data.forms["adj"] = {"ge" .. single .. "dje", "ge" .. single .. "djer", "ge" .. single .. "djes"}
data.forms["adv"] = {"ge" .. single .. "dj", "ge" .. single .. "djelik"}
data.forms["pres_1s_sv"] = {single}
data.forms["past_1s_sv"] = {single .. "dje"}
data.forms["sub_1s_sv"] = {base}
data.forms["pres_1s_vs"] = {single}
data.forms["past_1s_vs"] = {single .. "dje-n"}
data.forms["sub_1s_vs"] = {base .. "-n"}
data.forms["pres_2s_sv"] = {single .. "s"}
data.forms["past_2s_sv"] = {single .. "djes"}
data.forms["sub_2s_sv"] = {base}
data.forms["pres_2s_vs"] = {single .. "s"}
data.forms["past_2s_vs"] = {single .. "djes"}
data.forms["sub_2s_vs"] = {base .. "r"}
data.forms["pres_3s_sv"] = {single .. "tj"}
data.forms["past_3s_sv"] = {single .. "dje"}
data.forms["sub_3s_sv"] = {base}
data.forms["pres_3s_vs"] = {single .. "tj'r"}
data.forms["past_3s_vs"] = {single .. "dje"}
data.forms["sub_3s_vs"] = {base .. "r"}
data.forms["pres_1p_sv"] = {base}
data.forms["past_1p_sv"] = {single .. "dje"}
data.forms["sub_1p_sv"] = {base}
data.forms["pres_1p_vs"] = {single .. "tj"}
data.forms["past_1p_vs"] = {single .. "dje"}
data.forms["sub_1p_vs"] = {base}
data.forms["pres_2p_sv"] = {single .. "tj"}
data.forms["past_2p_sv"] = {single .. "dje"}
data.forms["sub_2p_sv"] = {base}
data.forms["pres_2p_vs"] = {single .. "tj"}
data.forms["past_2p_vs"] = {single .. "dje"}
data.forms["sub_2p_vs"] = {base .. "tj"}
data.forms["pres_3p_sv"] = {base}
data.forms["past_3p_sv"] = {single .. "dje"}
data.forms["sub_3p_sv"] = {base}
data.forms["pres_3p_vs"] = {base}
data.forms["past_3p_vs"] = {single .. "dje"}
data.forms["sub_3p_vs"] = {base .. "r"}
data.forms["noun"] = {"ge" .. single}
data.forms["imp_sg_imp"] = {single}
data.forms["imp_sg_pol"] = {single .. "tj"}
data.forms["imp_du"] = {single .. "tj", base .. "tj"}
data.forms["imp_pl"] = {single .. "tj"}
data.forms["incl"] = {base .. "m"}
end
}
inflections["fourth"] = {
params = {
[1] = {},
[2] = {},
},
func = function(args, data)
local base = mw.title.getCurrentTitle().nsText == "Template" and "-" or mw.title.getCurrentTitle().text
-- If the final consonant is doubled, simplify it for certain forms
local single = (base:sub(-3, -3) == base:sub(-2, -2)) and base:sub(1, -3) or base
data.info = " (第四變位)"
table.insert(data.categories, "林堡語第四變位動詞")
data.forms["inf"] = {base}
data.forms["ger"] = {base}
data.forms["pres_p"] = {base .. "ndj"}
data.forms["past_p"] = {"ge" .. single}
data.forms["adj"] = {"ge" .. single .. "dje", "ge" .. single .. "djer", "ge" .. single .. "djes"}
data.forms["adv"] = {"ge" .. single, "ge" .. single .. "djelik"}
data.forms["pres_1s_sv"] = {single}
data.forms["past_1s_sv"] = {single .. "dje"}
data.forms["sub_1s_sv"] = {base}
data.forms["pres_1s_vs"] = {single}
data.forms["past_1s_vs"] = {single .. "dje-n"}
data.forms["sub_1s_vs"] = {base .. "-n"}
data.forms["pres_2s_sv"] = {single:sub(-1, -1) == "s" and single or single .. "s"}
data.forms["past_2s_sv"] = {single .. "djes"}
data.forms["sub_2s_sv"] = {base}
data.forms["pres_2s_vs"] = {single:sub(-1, -1) == "s" and single or single .. "s"}
data.forms["past_2s_vs"] = {single .. "djes"}
data.forms["sub_2s_vs"] = {base .. "r"}
data.forms["pres_3s_sv"] = {single}
data.forms["past_3s_sv"] = {single .. "dje"}
data.forms["sub_3s_sv"] = {base}
data.forms["pres_3s_vs"] = {single}
data.forms["past_3s_vs"] = {single .. "dje"}
data.forms["sub_3s_vs"] = {base .. "r"}
data.forms["pres_1p_sv"] = {base}
data.forms["past_1p_sv"] = {single .. "dje"}
data.forms["sub_1p_sv"] = {base}
data.forms["pres_1p_vs"] = {single .. "tj"}
data.forms["past_1p_vs"] = {single .. "dje"}
data.forms["sub_1p_vs"] = {base}
data.forms["pres_2p_sv"] = {single, single .. "tj"}
data.forms["past_2p_sv"] = {single .. "dje"}
data.forms["sub_2p_sv"] = {base}
data.forms["pres_2p_vs"] = {single, single .. "tj"}
data.forms["past_2p_vs"] = {single .. "dje"}
data.forms["sub_2p_vs"] = {base .. "tj"}
data.forms["pres_3p_sv"] = {base}
data.forms["past_3p_sv"] = {single .. "dje"}
data.forms["sub_3p_sv"] = {base}
data.forms["pres_3p_vs"] = {base}
data.forms["past_3p_vs"] = {single .. "dje"}
data.forms["sub_3p_vs"] = {base .. "r"}
data.forms["noun"] = {"ge" .. single}
data.forms["imp_sg_imp"] = {single}
data.forms["imp_sg_pol"] = {single, single .. "tj"}
data.forms["imp_du"] = {base .. "tj"}
data.forms["imp_pl"] = {single .. "tj"}
data.forms["incl"] = {base .. "m"}
end
}
inflections["seventh"] = {
params = {
[1] = {},
[2] = {},
},
func = function(args, data)
local base = mw.title.getCurrentTitle().nsText == "Template" and "-" or mw.title.getCurrentTitle().text
-- If the final consonant is doubled, simplify it for certain forms
local single = (base:sub(-3, -3) == base:sub(-2, -2)) and base:sub(1, -3) or base
data.info = " (第七變位)"
table.insert(data.categories, "林堡語第七變位動詞")
data.forms["inf"] = {base}
data.forms["ger"] = {base}
data.forms["pres_p"] = {base .. "ndj"}
data.forms["past_p"] = {"ge" .. single .. "p"}
data.forms["adj"] = {"ge" .. single .. "pdje", "ge" .. single .. "pdjer", "ge" .. single .. "pdjes"}
data.forms["adv"] = {"ge" .. single .. "p", "ge" .. single .. "pdjelik"}
data.forms["pres_1s_sv"] = {single}
data.forms["past_1s_sv"] = {single .. "dje"}
data.forms["sub_1s_sv"] = {base}
data.forms["pres_1s_vs"] = {single}
data.forms["past_1s_vs"] = {single .. "dje-n"}
data.forms["sub_1s_vs"] = {base .. "-n"}
data.forms["pres_2s_sv"] = {single .. "s"}
data.forms["past_2s_sv"] = {single .. "djes"}
data.forms["sub_2s_sv"] = {base}
data.forms["pres_2s_vs"] = {single .. "s"}
data.forms["past_2s_vs"] = {single .. "djes"}
data.forms["sub_2s_vs"] = {base .. "r"}
data.forms["pres_3s_sv"] = {single .. "p"}
data.forms["past_3s_sv"] = {single .. "dje"}
data.forms["sub_3s_sv"] = {base}
data.forms["pres_3s_vs"] = {single .. "p"}
data.forms["past_3s_vs"] = {single .. "dje"}
data.forms["sub_3s_vs"] = {base .. "r"}
data.forms["pres_1p_sv"] = {base}
data.forms["past_1p_sv"] = {single .. "dje"}
data.forms["sub_1p_sv"] = {base}
data.forms["pres_1p_vs"] = {single .. "p"}
data.forms["past_1p_vs"] = {single .. "dje"}
data.forms["sub_1p_vs"] = {base}
data.forms["pres_2p_sv"] = {single .. "p"}
data.forms["past_2p_sv"] = {single .. "dje"}
data.forms["sub_2p_sv"] = {base}
data.forms["pres_2p_vs"] = {single .. "p"}
data.forms["past_2p_vs"] = {single .. "dje"}
data.forms["sub_2p_vs"] = {base .. "tj"}
data.forms["pres_3p_sv"] = {base}
data.forms["past_3p_sv"] = {single .. "dje"}
data.forms["sub_3p_sv"] = {base}
data.forms["pres_3p_vs"] = {base}
data.forms["past_3p_vs"] = {single .. "dje"}
data.forms["sub_3p_vs"] = {base .. "r"}
data.forms["noun"] = {"ge" .. single}
data.forms["imp_sg_imp"] = {single}
data.forms["imp_sg_pol"] = {single, single .. "p"}
data.forms["imp_du"] = {single .. "tj", base .. "tj"}
data.forms["imp_pl"] = {single .. "p"}
data.forms["incl"] = {base .. "m"}
end
}
inflections["irregular"] = {
params = {
[1] = {},
[2] = {}
},
func = function(args, data)
local base = args[2] or (mw.title.getCurrentTitle().nsText == "Template" and "zeen" or mw.title.getCurrentTitle().text)
if irregular[base] then
irregular[base](data)
else
error("Unknown irregular verb '" .. base .. "'.")
end
end
}
irregular["baere"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
data.info = " (不規則)"
data.forms["inf"] = {"baere"}
data.forms["ger"] = {"baere"}
data.forms["pres_p"] = {"baerendj"}
data.forms["past_p"] = {"gebaore"}
data.forms["adj"] = {"gebaore", "gebaorer", "gebaores"}
data.forms["adv"] = {"gebaorelik"}
data.forms["pres_1s_sv"] = {"baer"}
data.forms["past_1s_sv"] = {"boor"}
data.forms["sub_1s_sv"] = {"baere"}
data.forms["pres_1s_vs"] = {"bèr"}
data.forms["past_1s_vs"] = {"boor"}
data.forms["sub_1s_vs"] = {"baere-n"}
data.forms["pres_2s_sv"] = {"bèrs"}
data.forms["past_2s_sv"] = {"boors"}
data.forms["sub_2s_sv"] = {"baere"}
data.forms["pres_2s_vs"] = {"bèrs"}
data.forms["past_2s_vs"] = {"boors"}
data.forms["sub_2s_vs"] = {"bèrrer"}
data.forms["pres_3s_sv"] = {"bèrtj"}
data.forms["past_3s_sv"] = {"boor"}
data.forms["sub_3s_sv"] = {"baere"}
data.forms["pres_3s_vs"] = {"bèrtj'r"}
data.forms["pres_3s_vs_pron"] = ""
data.forms["past_3s_vs"] = {"boor"}
data.forms["sub_3s_vs"] = {"bèrrer"}
data.forms["pres_1p_sv"] = {"baere"}
data.forms["past_1p_sv"] = {"bore"}
data.forms["sub_1p_sv"] = {"baere"}
data.forms["pres_1p_vs"] = {"bèrtj"}
data.forms["past_1p_vs"] = {"boortj"}
data.forms["sub_1p_vs"] = {"bèrre"}
data.forms["pres_2p_sv"] = {"baertj"}
data.forms["past_2p_sv"] = {"boortj"}
data.forms["sub_2p_sv"] = {"baere"}
data.forms["pres_2p_vs"] = {"baertj"}
data.forms["past_2p_vs"] = {"boortj"}
data.forms["sub_2p_vs"] = {"bèrretj"}
data.forms["pres_3p_sv"] = {"baere"}
data.forms["past_3p_sv"] = {"bore"}
data.forms["sub_3p_sv"] = {"baere"}
data.forms["pres_3p_vs"] = {"baere"}
data.forms["past_3p_vs"] = {"bore"}
data.forms["sub_3p_vs"] = {"bèrrer"}
data.forms["noun"] = {"gebèr"}
data.forms["imp_sg_imp"] = {"baer"}
data.forms["imp_sg_pol"] = {"bèrtj"}
data.forms["imp_du"] = {"bèr"}
data.forms["imp_pl"] = {"baertj"}
data.forms["incl"] = {"baerem"}
end
irregular["bèdzje"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
data.info = " (不規則)"
data.forms["inf"] = {"bèdzje"}
data.forms["ger"] = {"bèdzje"}
data.forms["pres_p"] = {"bèdzjendje"}
data.forms["past_p"] = {"gebótsj"}
data.forms["adj"] = {"gebótsj", "gebódzje", "gebódzjer", "gebódzjes"}
data.forms["adv"] = {"gebódzjelik"}
data.forms["pres_1s_sv"] = {"bètsj"}
data.forms["past_1s_sv"] = {"batsj"}
data.forms["sub_1s_sv"] = {"bèdzje"}
data.forms["pres_1s_vs"] = {"bètsj"}
data.forms["past_1s_vs"] = {"batsj"}
data.forms["sub_1s_vs"] = {"bèdzje-n"}
data.forms["pres_2s_sv"] = {"bèdzjes"}
data.forms["past_2s_sv"] = {"badzjes"}
data.forms["sub_2s_sv"] = {"bèdzje"}
data.forms["pres_2s_vs"] = {"bèdzjes"}
data.forms["past_2s_vs"] = {"badzjes"}
data.forms["sub_2s_vs"] = {"bèdzjer"}
data.forms["pres_3s_sv"] = {"bètsj"}
data.forms["past_3s_sv"] = {"batsj"}
data.forms["sub_3s_sv"] = {"bèdzje"}
data.forms["pres_3s_vs"] = {"bètsj"}
data.forms["past_3s_vs"] = {"batsj"}
data.forms["sub_3s_vs"] = {"bèdzjer"}
data.forms["pres_1p_sv"] = {"bèdzje"}
data.forms["past_1p_sv"] = {"badzje"}
data.forms["sub_1p_sv"] = {"bèdzje"}
data.forms["pres_1p_vs"] = {"bètsj"}
data.forms["past_1p_vs"] = {"batsj"}
data.forms["sub_1p_vs"] = {"bèdzje"}
data.forms["pres_2p_sv"] = {"bètsj"}
data.forms["past_2p_sv"] = {"batsj"}
data.forms["sub_2p_sv"] = {"bèdzje"}
data.forms["pres_2p_vs"] = {"bètsj"}
data.forms["past_2p_vs"] = {"batsj"}
data.forms["sub_2p_vs"] = {"bèdzjetj"}
data.forms["pres_3p_sv"] = {"bèdzje"}
data.forms["past_3p_sv"] = {"badzje"}
data.forms["sub_3p_sv"] = {"bèdzje"}
data.forms["pres_3p_vs"] = {"bèdzje"}
data.forms["past_3p_vs"] = {"badzje"}
data.forms["sub_3p_vs"] = {"bèdzjer"}
data.forms["noun"] = {"gebótsj"}
data.forms["imp_sg_imp"] = {"bètsj"}
data.forms["imp_sg_pol"] = {"bötsj"}
data.forms["imp_du"] = {"buuetsj"}
data.forms["imp_pl"] = {"boeatsj"}
data.forms["incl"] = {"bötsjem"}
end
irregular["doon"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
data.info = " (不規則)"
data.forms["inf"] = {"doon"}
data.forms["ger"] = {"doon"}
data.forms["pres_p"] = {"donendje"}
data.forms["past_p"] = {"gedaon"}
data.forms["adj"] = {"gedaon", "gedaone", "gedaoner", "gedaones"}
data.forms["adv"] = {"gedäöndjelik"}
data.forms["pres_1s_sv"] = {"doon"}
data.forms["past_1s_sv"] = {"di-j"}
data.forms["sub_1s_sv"] = {"daon"}
data.forms["pres_1s_vs"] = {"doon"}
data.forms["past_1s_vs"] = {"di-j"}
data.forms["sub_1s_vs"] = {"daon"}
data.forms["pres_2s_sv"] = {"deis"}
data.forms["past_2s_sv"] = {"di-js"}
data.forms["sub_2s_sv"] = {"daons"}
data.forms["pres_2s_vs"] = {"deis"}
data.forms["past_2s_vs"] = {"di-js"}
data.forms["sub_2s_vs"] = {"daons"}
data.forms["pres_3s_sv"] = {"deitj"}
data.forms["past_3s_sv"] = {"di-j"}
data.forms["sub_3s_sv"] = {"daontj"}
data.forms["pres_3s_vs"] = {"deitj'r"}
data.forms["pres_3s_vs_pron"] = ""
data.forms["past_3s_vs"] = {"di-j"}
data.forms["sub_3s_vs"] = {"daontj'r"}
data.forms["sub_3s_vs_pron"] = ""
data.forms["pres_1p_sv"] = {"doon"}
data.forms["past_1p_sv"] = {"di-jje"}
data.forms["sub_1p_sv"] = {"daone"}
data.forms["pres_1p_vs"] = {"doontj"}
data.forms["past_1p_vs"] = {"di-jtj"}
data.forms["sub_1p_vs"] = {"daontj"}
data.forms["pres_2p_sv"] = {"doontj"}
data.forms["past_2p_sv"] = {"di-j"}
data.forms["sub_2p_sv"] = {"daontj"}
data.forms["pres_2p_vs"] = {"doontj"}
data.forms["past_2p_vs"] = {"di-j", "di-jtj"}
data.forms["sub_2p_vs"] = {"daontj"}
data.forms["pres_3p_sv"] = {"doon"}
data.forms["past_3p_sv"] = {"di-jje"}
data.forms["sub_3p_sv"] = {"daone"}
data.forms["pres_3p_vs"] = {"doon"}
data.forms["past_3p_vs"] = {"di-jje"}
data.forms["sub_3p_vs"] = {"daone"}
data.forms["noun"] = {"gedoons"}
data.forms["imp_sg_imp"] = {"dooch"}
data.forms["imp_sg_pol"] = {"dootj"}
data.forms["imp_du"] = {"doon"}
data.forms["imp_pl"] = {"doontj"}
data.forms["incl"] = {"daonem"}
end
irregular["dönke"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
data.info = " (不規則)"
data.forms["inf"] = {"dönke"}
data.forms["ger"] = {"dönke"}
data.forms["pres_p"] = {"dönkendje"}
data.forms["past_p"] = {"gedóch"}
data.forms["adj"] = {"gedóch", "gedóchtje", "gedóchtjer", "gedóchtjes"}
data.forms["adv"] = {"gedóch", "gedóchtjelik"}
data.forms["pres_1s_sv"] = {"dönk"}
data.forms["past_1s_sv"] = {"dóch"}
data.forms["sub_1s_sv"] = {"tönke"}
data.forms["pres_1s_vs"] = {"dönk"}
data.forms["past_1s_vs"] = {"dóch"}
data.forms["sub_1s_vs"] = {"tönke-n"}
data.forms["pres_2s_sv"] = {"dönks"}
data.forms["past_2s_sv"] = {"dóchs"}
data.forms["sub_2s_sv"] = {"tönks"}
data.forms["pres_2s_vs"] = {"dönks"}
data.forms["past_2s_vs"] = {"dóchs"}
data.forms["sub_2s_vs"] = {"tönks"}
data.forms["pres_3s_sv"] = {"dönk"}
data.forms["past_3s_sv"] = {"dóch"}
data.forms["sub_3s_sv"] = {"tönk"}
data.forms["pres_3s_vs"] = {"dönk"}
data.forms["past_3s_vs"] = {"dóch"}
data.forms["sub_3s_vs"] = {"tönk"}
data.forms["pres_1p_sv"] = {"dönke"}
data.forms["past_1p_sv"] = {"dóchte"}
data.forms["sub_1p_sv"] = {"tönke"}
data.forms["pres_1p_vs"] = {"dönke"}
data.forms["past_1p_vs"] = {"dóchte"}
data.forms["sub_1p_vs"] = {"tönktj"}
data.forms["pres_2p_sv"] = {"tönk"}
data.forms["past_2p_sv"] = {"dóch"}
data.forms["sub_2p_sv"] = {"tönk"}
data.forms["pres_2p_vs"] = {"tönk"}
data.forms["past_2p_vs"] = {"dóch"}
data.forms["sub_2p_vs"] = {"tönketj"}
data.forms["pres_3p_sv"] = {"dönke"}
data.forms["past_3p_sv"] = {"dóchte"}
data.forms["sub_3p_sv"] = {"tönke"}
data.forms["pres_3p_vs"] = {"dönke"}
data.forms["past_3p_vs"] = {"dóchte"}
data.forms["sub_3p_vs"] = {"tönke"}
data.forms["noun"] = {"gedönk"}
data.forms["imp_sg_imp"] = {"duuch"}
data.forms["imp_sg_pol"] = {"duutj"}
data.forms["imp_du"] = {"duuetj"}
data.forms["imp_pl"] = {"duutj"}
data.forms["incl"] = {"dönkem"}
end
irregular["gaon"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
table.insert(data.categories, "林堡語異幹動詞")
data.info = " (不規則,異幹互補)"
data.forms["inf"] = {"gaon"}
data.forms["ger"] = {"gaon"}
data.forms["pres_p"] = {"gäöndje"}
data.forms["past_p"] = {"gegange"}
data.forms["adj"] = {"gegange", "geganger", "geganges"}
data.forms["adv"] = {"gegäöndjelik"}
data.forms["pres_1s_sv"] = {"gaon"}
data.forms["past_1s_sv"] = {"góng"}
data.forms["sub_1s_sv"] = {"gank"}
data.forms["pres_1s_vs"] = {"gaon"}
data.forms["past_1s_vs"] = {"góng"}
data.forms["sub_1s_vs"] = {"gank"}
data.forms["pres_2s_sv"] = {"geis"}
data.forms["past_2s_sv"] = {"gónks"}
data.forms["sub_2s_sv"] = {"ganks"}
data.forms["pres_2s_vs"] = {"geis"}
data.forms["past_2s_vs"] = {"gónks"}
data.forms["sub_2s_vs"] = {"ganks"}
data.forms["pres_3s_sv"] = {"geitj"}
data.forms["past_3s_sv"] = {"góng"}
data.forms["sub_3s_sv"] = {"gank"}
data.forms["pres_3s_vs"] = {"geitj'r"}
data.forms["past_3s_vs"] = {"góng"}
data.forms["sub_3s_vs"] = {"gank"}
data.forms["pres_1p_sv"] = {"gaon"}
data.forms["past_1p_sv"] = {"gónge"}
data.forms["sub_1p_sv"] = {"gange"}
data.forms["pres_1p_vs"] = {"gaontj"}
data.forms["past_1p_vs"] = {"gónk"}
data.forms["sub_1p_vs"] = {"gank"}
data.forms["pres_2p_sv"] = {"gaontj"}
data.forms["past_2p_sv"] = {"góng"}
data.forms["sub_2p_sv"] = {"gank"}
data.forms["pres_2p_vs"] = {"gaontj"}
data.forms["past_2p_vs"] = {"gónk"}
data.forms["sub_2p_vs"] = {"gank"}
data.forms["pres_3p_sv"] = {"gaon"}
data.forms["past_3p_sv"] = {"gónge"}
data.forms["sub_3p_sv"] = {"gange"}
data.forms["pres_3p_vs"] = {"gaon"}
data.forms["past_3p_vs"] = {"gónge"}
data.forms["sub_3p_vs"] = {"gange"}
data.forms["noun"] = {"gegaon"}
data.forms["imp_sg_imp"] = {"gank"}
data.forms["imp_sg_pol"] = {"gank"}
data.forms["imp_du"] = {"gènk"}
data.forms["imp_pl"] = {"gank"}
data.forms["incl"] = {"gaonem"}
end
irregular["lieje"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
data.info = " (不規則)"
data.forms["inf"] = {"lieje"}
data.forms["ger"] = {"lieje"}
data.forms["pres_p"] = {"liejendj"}
data.forms["past_p"] = {"geleeje"}
data.forms["adj"] = {"geleeje", "geleejer", "geleejes"}
data.forms["adv"] = {"geleejelik"}
data.forms["pres_1s_sv"] = {"liej"}
data.forms["past_1s_sv"] = {"leej"}
data.forms["sub_1s_sv"] = {"lieje"}
data.forms["pres_1s_vs"] = {"liej"}
data.forms["past_1s_vs"] = {"leej"}
data.forms["sub_1s_vs"] = {"lieje-n"}
data.forms["pres_2s_sv"] = {"lietsj"}
data.forms["past_2s_sv"] = {"leejs"}
data.forms["sub_2s_sv"] = {"lieje"}
data.forms["pres_2s_vs"] = {"lietsj"}
data.forms["past_2s_vs"] = {"leejs"}
data.forms["sub_2s_vs"] = {"liejer"}
data.forms["pres_3s_sv"] = {"lietj"}
data.forms["past_3s_sv"] = {"leej"}
data.forms["sub_3s_sv"] = {"lieje"}
data.forms["pres_3s_vs"] = {"lietj'r"}
data.forms["past_3s_vs"] = {"leej"}
data.forms["sub_3s_vs"] = {"liejer"}
data.forms["pres_1p_sv"] = {"lieje"}
data.forms["past_1p_sv"] = {"leeje"}
data.forms["sub_1p_sv"] = {"lieje"}
data.forms["pres_1p_vs"] = {"lietj"}
data.forms["past_1p_vs"] = {"leetj"}
data.forms["sub_1p_vs"] = {"lieje"}
data.forms["pres_2p_sv"] = {"lietj"}
data.forms["past_2p_sv"] = {"leej"}
data.forms["sub_2p_sv"] = {"lieje"}
data.forms["pres_2p_vs"] = {"lietj"}
data.forms["past_2p_vs"] = {"leetj"}
data.forms["sub_2p_vs"] = {"lieëtj"}
data.forms["pres_3p_sv"] = {"lieje"}
data.forms["past_3p_sv"] = {"leeje"}
data.forms["sub_3p_sv"] = {"lieje"}
data.forms["pres_3p_vs"] = {"lieje"}
data.forms["past_3p_vs"] = {"leeje"}
data.forms["sub_3p_vs"] = {"liejer"}
data.forms["noun"] = {"geliej"}
data.forms["imp_sg_imp"] = {"liej"}
data.forms["imp_sg_pol"] = {"lietj"}
data.forms["imp_du"] = {"lieëtj"}
data.forms["imp_pl"] = {"lietj"}
data.forms["incl"] = {"lieëm"}
end
irregular["zeen"] = function(data)
error("Ambiguous conjugation: please specify zeen-1 (to see) or zeen-2 (to be)")
end
irregular["zeen-1"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
data.info = " (irregular)"
data.forms["inf"] = {"zeen"}
data.forms["ger"] = {"zeen"}
data.forms["pres_p"] = {"zeendje"}
data.forms["past_p"] = {"gezeen"}
data.forms["adj"] = {"gezeen", "gezene", "gezener", "gezenes"}
data.forms["adv"] = {"gezenelik"}
data.forms["pres_1s_sv"] = {"zeen"}
data.forms["past_1s_sv"] = {"zoog", "zaag"}
data.forms["sub_1s_sv"] = {"zene"}
data.forms["pres_1s_vs"] = {"zeen"}
data.forms["past_1s_vs"] = {"zoog", "zaag"}
data.forms["sub_1s_vs"] = {"zene-n"}
data.forms["pres_2s_sv"] = {"zuus"}
data.forms["past_2s_sv"] = {"zoogs", "zaags"}
data.forms["sub_2s_sv"] = {"zene"}
data.forms["pres_2s_vs"] = {"zuus"}
data.forms["past_2s_vs"] = {"zoogs", "zaags"}
data.forms["sub_2s_vs"] = {"zener"}
data.forms["pres_3s_sv"] = {"zuutj"}
data.forms["past_3s_sv"] = {"zoog", "zaag"}
data.forms["sub_3s_sv"] = {"zene"}
data.forms["pres_3s_vs"] = {"zuutj'r"}
data.forms["past_3s_vs"] = {"zoog", "zaag"}
data.forms["sub_3s_vs"] = {"zener"}
data.forms["pres_1p_sv"] = {"zeen"}
data.forms["past_1p_sv"] = {"zoge", "zage"}
data.forms["sub_1p_sv"] = {"zene"}
data.forms["pres_1p_vs"] = {"zeentj"}
data.forms["past_1p_vs"] = {"zoge", "zage"}
data.forms["sub_1p_vs"] = {"zene"}
data.forms["pres_2p_sv"] = {"zuutj"}
data.forms["past_2p_sv"] = {"zoog", "zaag"}
data.forms["sub_2p_sv"] = {"zene"}
data.forms["pres_2p_vs"] = {"zuutj"}
data.forms["past_2p_vs"] = {"zoog", "zaag"}
data.forms["sub_2p_vs"] = {"zenetj"}
data.forms["pres_3p_sv"] = {"zeen"}
data.forms["past_3p_sv"] = {"zoge", "zage"}
data.forms["sub_3p_sv"] = {"zene"}
data.forms["pres_3p_vs"] = {"zeen"}
data.forms["past_3p_vs"] = {"leeje"}
data.forms["sub_3p_vs"] = {"zener"}
data.forms["noun"] = {"gezeen"}
data.forms["imp_sg_imp"] = {"zuug"}
data.forms["imp_sg_pol"] = {"zuutj"}
data.forms["imp_du"] = {"zuuetj"}
data.forms["imp_pl"] = {"zuutj"}
data.forms["incl"] = {"zenem"}
end
irregular["zeen-2"] = function(data)
table.insert(data.categories, "林堡語不規則動詞")
table.insert(data.categories, "林堡語異幹動詞")
data.info = " (高度不規則,異幹互補)"
data.aux = "zeen"
data.forms["inf"] = {"zeen", "waeze"}
data.forms["ger"] = {"zeen"}
data.forms["pres_p"] = {"?"}
data.forms["past_p"] = {"gewaes"}
data.forms["adj"] = {"gewaes", "gewaeze", "gewaezer", "gewaezes"}
data.forms["adv"] = {"gewaezelik"}
data.forms["pres_1s_sv"] = {"bön"}
data.forms["past_1s_sv"] = {"woor", "waas", "waar"}
data.forms["sub_1s_sv"] = {"bönne"}
data.forms["pres_1s_vs"] = {"bön"}
data.forms["past_1s_vs"] = {"woor", "waas", "waar"}
data.forms["sub_1s_vs"] = {"bönne-n"}
data.forms["pres_2s_sv"] = {"bös"}
data.forms["past_2s_sv"] = {"woors", "waas", "waars"}
data.forms["sub_2s_sv"] = {"bönne"}
data.forms["pres_2s_vs"] = {"bös"}
data.forms["past_2s_vs"] = {"woors", "waas", "waars"}
data.forms["sub_2s_vs"] = {"bonner"}
data.forms["pres_3s_sv"] = {"is"}
data.forms["past_3s_sv"] = {"woor", "waas", "waar"}
data.forms["sub_3s_sv"] = {"bönne"}
data.forms["pres_3s_vs"] = {"is"}
data.forms["past_3s_vs"] = {"woor", "waas", "waar"}
data.forms["sub_3s_vs"] = {"bönner"}
data.forms["pres_1p_sv"] = {"zeen"}
data.forms["past_1p_sv"] = {"wore", "ware"}
data.forms["sub_1p_sv"] = {"bönne"}
data.forms["pres_1p_vs"] = {"zeetj"}
data.forms["past_1p_vs"] = {"woortj"}
data.forms["sub_1p_vs"] = {"bönne"}
data.forms["pres_2p_sv"] = {"zeetj", "zeentj"}
data.forms["past_2p_sv"] = {"woortj", "waartj"}
data.forms["sub_2p_sv"] = {"bönne"}
data.forms["pres_2p_vs"] = {"zeetj", "zeentj"}
data.forms["past_2p_vs"] = {"woortj", "waartj"}
data.forms["sub_2p_vs"] = {"bönneetj"}
data.forms["pres_3p_sv"] = {"zeen", "zeent"}
data.forms["past_3p_sv"] = {"wore", "ware"}
data.forms["sub_3p_sv"] = {"bönne"}
data.forms["pres_3p_vs"] = {"zeen"}
data.forms["past_3p_vs"] = {"wore", "ware"}
data.forms["sub_3p_vs"] = {"bönner"}
data.forms["noun"] = {"gezeen"}
data.forms["imp_sg_imp"] = {"bön"}
data.forms["imp_sg_pol"] = {"zeetj", "zeentj"}
data.forms["imp_du"] = {"eertj"}
data.forms["imp_pl"] = {"zeetj", "zeentj"}
data.forms["incl"] = {"bönnem"}
end
-- Make the table
function make_table(data)
local function show_form(form)
if not form then
return "—"
end
local ret = {}
for key, subform in ipairs(form) do
table.insert(ret, m_links.full_link({lang = lang, term = subform}))
end
return table.concat(ret, ", ")
end
local function repl(param)
if param == "lemma" then
return m_links.full_link({lang = lang, alt = mw.title.getCurrentTitle().text}, "term")
elseif param == "info" or param == "aux" then
return data[param]
elseif param == "n" then
return m_gender.format_genders({"n"})
else
return show_form(data.forms[param])
end
end
local wikicode = [=[
<div class="NavFrame">
<div class="NavHead" style="text-align: left">{{{lemma}}} 的蒙福特方言變位{{{info}}}</div>
<div class="NavContent">
{| style="border: 1px solid var(--wikt-palette-lightgrey,#cdcdcd); border-collapse:collapse;background:var(--wikt-palette-grey-0,#FEFEFE);width:100%;text-align:center;" class="inflection-table"
|-
! rowspan=2 style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 非限定形式
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 不定式
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 動名詞
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 現在分詞
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 過去分詞
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 形容詞
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 副詞
|-
| (tö) {{{inf}}}
| 't {{{ger}}} {{{n}}}
| {{{pres_p}}}
| {{{aux}}} {{{past_p}}}
| {{{adj}}}
| {{{adv}}}
|-
! rowspan=2 style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 數和時態
! colspan=3 style="background:var(--wikt-palette-grey-2,#C0C0C0)" | [[w:動詞第二順位|動詞第二順位]]
! colspan=3 style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 動詞第一順位
|-
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 現在時
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 過去時
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 假設語氣
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 現在時
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 過去時
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 假設語氣
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 第一人稱單數
| {{{pres_1s_sv}}}
| {{{past_1s_sv}}}
| {{{sub_1s_sv}}}
| {{{pres_1s_vs}}}
| {{{past_1s_vs}}}
| {{{sub_1s_vs}}}
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 第二人稱單數
| {{{pres_2s_sv}}}
| {{{past_2s_sv}}}
| {{{sub_2s_sv}}}
| {{{pres_2s_vs}}}
| {{{past_2s_vs}}}
| {{{sub_2s_vs}}}
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 第三人稱單數
| {{{pres_3s_sv}}}
| {{{past_3s_sv}}}
| {{{sub_3s_sv}}}
| {{{pres_3s_vs}}}
| {{{past_3s_vs}}}
| {{{sub_3s_vs}}}
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 第一人稱複數
| {{{pres_1p_sv}}}
| {{{past_1p_sv}}}
| {{{sub_1p_sv}}}
| {{{pres_1p_vs}}}
| {{{past_1p_vs}}}
| {{{sub_1p_vs}}}
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 第二人稱複數
| {{{pres_2p_sv}}}
| {{{past_2p_sv}}}
| {{{sub_2p_sv}}}
| {{{pres_2p_vs}}}
| {{{past_2p_vs}}}
| {{{sub_2p_vs}}}
|-
! style="background:var(--wikt-palette-grey-blue-2,#c0cfe4)" | 第三人稱複數
| {{{pres_3p_sv}}}
| {{{past_3p_sv}}}
| {{{sub_3p_sv}}}
| {{{pres_3p_vs}}}
| {{{past_3p_vs}}}
| {{{sub_3p_vs}}}
|-
! rowspan=2 style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 其他形式
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 名詞
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 祈使語氣單數不禮貌
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 祈使語氣單數禮貌
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 祈使語氣雙數
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 祈使語氣複數
! style="background:var(--wikt-palette-grey-2,#C0C0C0)" | 包含式
|-
| 't {{{noun}}} {{{n}}}
| {{{imp_sg_imp}}}!
| {{{imp_sg_pol}}}!
| {{{imp_du}}}!
| {{{imp_pl}}}!
| {{{incl}}}
|}</div></div>]=]
return (mw.ustring.gsub(wikicode, "{{{([a-z0-9_]+)}}}", repl)) .. m_utils.format_categories(data.categories, lang)
end
return export
q8i8gzw0ar25m5y90i51ngcp9mg9a1o
Module:Mwl-headword
828
3298766
9909086
9883367
2026-09-29T07:27:09Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909086
Scribunto
text/plain
--[=[
Based on [[Module:es-headword]]
]=]
local export = {}
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local rfind = mw.ustring.find
local rmatch = mw.ustring.match
local require_when_needed = require("Module:utilities/require when needed")
local m_table = require("Module:table")
local com = require("Module:mwl-common")
local en_utilities_module = "Module:en-utilities"
local mwl_verb_module = "Module:mwl-verb"
local headword_module = "Module:headword"
local headword_utilities_module = "Module:headword utilities"
local inflection_utilities_module = "Module:inflection utilities"
local romut_module = "Module:romance utilities"
local m_en_utilities = require_when_needed(en_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local m_string_utilities = require_when_needed("Module:string utilities")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local lang = require("Module:languages").getByCode("mwl")
local langname = lang:getCanonicalName()
local insert = table.insert
local rsub = com.rsub
local usub = mw.ustring.sub
local function track(page)
require("Module:debug").track("mwl-headword/" .. page)
return true
end
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local poscat = frame.args[1]
or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local parargs = frame:getParent().args
local params = {
["head"] = list_param,
["id"] = {},
["splithyph"] = boolean_param,
["nolinkhead"] = boolean_param,
["json"] = boolean_param,
["pagename"] = {}, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolinkhead then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
end
end
end
local data = {
lang = lang,
pos_category = pos_functions[poscat] and pos_functions[poscat].pos_category or poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
no_redundant_head_cat = #user_specified_heads == 0,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
checkredlinks = pos_functions[poscat] and pos_functions[poscat].redlink_pos or true,
}
if pagename:find("^%-") and poscat ~= "後綴變格形" then
data.is_suffix = true
data.pos_category = "後綴"
data.checkredlinks = true
local singular_poscat = poscat
table.insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
table.insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(args, data)
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require(headword_module).full_headword(data)
end
-----------------------------------------------------------------------------------------
-- Utility functions --
-----------------------------------------------------------------------------------------
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture
-- replace expression.
lemma = m_string_utilities.replacement_escape(lemma)
return (term:gsub("#", lemma)) -- discard second retval
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,
label = label,
accel = accel and {form = accel} or nil,
}
end
-- Insert default plurals generated when a given plural had the value of + and default plurals were fetched as a result.
-- `plobj` is the parsed object whose `term` field is "+". `defpls` is the list of default plurals. `dest` is the list
-- into which the plurals are inserted (which inherit their decorations from `plobj`).
local function insert_defpls(defpls, plobj, dest)
if not defpls then
-- Happens e.g. with [[S.A.]] where the default plural algorithm returns nothing.
return
end
if #defpls == 1 then
plobj.term = defpls[1]
table.insert(dest, plobj)
else
for _, defpl in ipairs(defpls) do
local newplobj = m_table.shallowCopy(plobj)
newplobj.term = defpl
table.insert(dest, newplobj)
end
end
end
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
local function do_adjective(args, data, is_superlative)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
-- Use "participle" not "past participle" for categories such as 'invariable participles'
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
table.insert(indicators, "'" .. indic .. "'")
end
table.sort(indicators)
error("Special inflection indicator beginning can only be " ..
mw.text.listToText(indicators) .. ": " .. args.sp)
end
end
local lemma = data.pagename
local function fetch_inflections(field)
local retval = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
}
if not retval[1] then
return {{term = "+"}}
end
return retval
end
local function insert_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
if args.inv then
-- invariable adjective
table.insert(data.inflections, {label = glossary_link("無屈折")})
table.insert(data.categories, langname .. "無屈折" .. category_plpos)
if args.sp or args.f[1] or args.pl[1] or args.mpl[1] or args.fpl[1] then
error("Can't specify inflections with an invariable " .. category_pos)
end
elseif args.fonly then
-- feminine-only
if args.f[1] then
error("Can't specify explicit feminines with feminine-only " .. category_pos)
end
if args.pl[1] then
error("Can't specify explicit plurals with feminine-only " .. category_pos .. ", use fpl=")
end
if args.mpl[1] then
error("Can't specify explicit masculine plurals with feminine-only " .. category_pos)
end
local argsfpl = fetch_inflections("fpl")
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
-- Generate default feminine plural.
local defpls = com.make_plural(lemma, args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
insert_defpls(defpls, fpl, feminine_plurals)
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
table.insert(feminine_plurals, fpl)
end
end
table.insert(data.inflections, {label = "僅陰性"})
insert_inflection(feminine_plurals, "陰性複數", "f|p")
else
-- Gather feminines.
for _, f in ipairs(fetch_inflections("f")) do
if f.term == "+" then
-- Generate default feminine.
f.term = com.make_feminine(lemma, args.sp)
else
f.term = replace_hash_with_lemma(f.term, lemma)
end
table.insert(feminines, f)
end
local fem_like_lemma = #feminines == 1 and feminines[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(feminines[1])
if fem_like_lemma then
table.insert(data.categories, langname .. "兩性" .. category_plpos)
end
local mpl_field = "mpl"
local fpl_field = "fpl"
if args.pl[1] then
if args.mpl[1] or args.fpl[1] then
error("Can't specify both pl= and mpl=/fpl=")
end
mpl_field = "pl"
fpl_field = "pl"
end
local argsmpl = fetch_inflections(mpl_field)
local argsfpl = fetch_inflections(fpl_field)
for _, mpl in ipairs(argsmpl) do
if mpl.term == "+" then
-- Generate default masculine plural.
local defpls = com.make_plural(lemma, args.sp)
if not defpls then
error("Unable to generate default plural of '" .. lemma .. "'")
end
insert_defpls(defpls, mpl, masculine_plurals)
else
mpl.term = replace_hash_with_lemma(mpl.term, lemma)
table.insert(masculine_plurals, mpl)
end
end
for _, fpl in ipairs(argsfpl) do
if fpl.term == "+" then
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local defpls = com.make_plural(f.term, args.sp)
if not defpls then
error("Unable to generate default plural of '" .. f.term .. "'")
end
for _, defpl in ipairs(defpls) do
local fplobj = m_table.shallowCopy(fpl)
fplobj.term = defpl
m_headword_utilities.combine_termobj_decorations(fplobj, f)
table.insert(feminine_plurals, fplobj)
end
end
else
fpl.term = replace_hash_with_lemma(fpl.term, lemma)
table.insert(feminine_plurals, fpl)
end
end
parse_and_insert_inflection(data, args, "mapoc", "名詞前為陽性單數")
local fem_pl_like_masc_pl = masculine_plurals[1] and feminine_plurals[1] and
m_table.deepEquals(masculine_plurals, feminine_plurals)
local masc_pl_like_lemma = #masculine_plurals == 1 and masculine_plurals[1].term == lemma and
not m_headword_utilities.termobj_has_decorations(masculine_plurals[1])
if fem_like_lemma and fem_pl_like_masc_pl and masc_pl_like_lemma then
-- actually invariable
table.insert(data.inflections, {label = glossary_link("無屈折")})
table.insert(data.categories, langname .. "無屈折" .. category_plpos)
else
-- Make sure there are feminines given and not same as lemma.
if not fem_like_lemma then
insert_inflection(feminines, "陰性", "f|s")
else
data.genders = {"mf"}
end
if fem_pl_like_masc_pl then
insert_inflection(masculine_plurals, "陽性和陰性複數", "p")
else
insert_inflection(masculine_plurals, "陽性複數", "m|p")
insert_inflection(feminine_plurals, "陰性複數", "f|p")
end
end
end
parse_and_insert_inflection(data, args, "comp", "比較級")
parse_and_insert_inflection(data, args, "sup", "最高級")
parse_and_insert_inflection(data, args, "dim", "指小")
parse_and_insert_inflection(data, args, "aug", "指大")
if args.irreg and is_superlative then
table.insert(data.categories, langname .. "不規則" .. category_plpos .. "最高級")
end
end
local function get_adjective_params(adjtype)
local params = {
["inv"] = boolean_param, --invariable
["sp"] = {}, -- special indicator: "first", "first-last", etc.
}
local function ins_infl(field)
params[field] = list_param --feminine form(s)
end
ins_infl("f") -- feminine form(s)
ins_infl("pl") -- plural override(s)
ins_infl("mpl") -- masculine plural override(s)
ins_infl("fpl") -- feminine plural override(s)
if adjtype == "base" then
ins_infl("comp") --comparative(s)
ins_infl("sup") --superlative(s)
ins_infl("dim") --diminutive(s)
ins_infl("aug") --augmentative(s)
params["fonly"] = boolean_param -- feminine only
params["hascomp"] = {} -- has comparative
end
if adjtype == "sup" then
params["irreg"] = boolean_param
end
return params
end
pos_functions["形容詞"] = {
params = get_adjective_params("base"),
func = do_adjective,
}
pos_functions["過去分詞"] = {
params = get_adjective_params("part"),
func = do_adjective,
redlink_pos = "分詞",
}
pos_functions["限定詞"] = {
params = get_adjective_params("det"),
func = do_adjective,
}
pos_functions["代詞"] = {
params = get_adjective_params("pron"),
func = do_adjective,
}
pos_functions["形容詞比較級"] = {
params = get_adjective_params("comp"),
func = do_adjective,
pos_category = "形容詞",
}
pos_functions["形容詞最高級"] = {
params = get_adjective_params("sup"),
func = function(args, data)
do_adjective(args, data, true)
end,
pos_category = "形容詞",
}
-----------------------------------------------------------------------------------------
-- Adverbs --
-----------------------------------------------------------------------------------------
pos_functions["副詞"] = {
params = {
["sup"] = list_param, --superlative(s)
},
func = function(args, data)
parse_and_insert_inflection(data, args, "sup", "最高級")
end,
}
-----------------------------------------------------------------------------------------
-- Numerals --
-----------------------------------------------------------------------------------------
pos_functions["基數詞"] = {
params = {
["f"] = list_param, --feminine(s)
},
func = function(args, data)
table.insert(data.categories, 1, langname .. "基數詞")
if args.f[1] then
table.insert(data.genders, "m")
parse_and_insert_inflection(data, args, "f", "陰性")
end
end,
pos_category = "數詞",
}
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = require("Module:table").listToSet(
{"m", "f", "mf", "mfbysense", "mfequiv", "n", "m-p", "f-p", "mf-p", "mfbysense-p", "mfequiv-p", "n-p", "?", "?-p"}
)
local function process_genders(data, genders, g_qual)
for i, g in ipairs(genders) do
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
if g_qual[i] then
table.insert(data.genders, {spec = g, q = {g_qual[i]}})
else
table.insert(data.genders, g)
end
end
end
-- Display additional inflection information for a noun
local function do_noun(args, data, is_proper)
local is_plurale_tantum = false
local has_singular = false
local category_plpos = data.checkredlinks
if category_plpos == true then
category_plpos = data.pos_category
end
local category_pos = category_plpos
data.genders = {}
local saw_m = false
local saw_f = false
local gender_for_irreg_ending, gender_for_make_plural
process_genders(data, args[1], args.g_qual)
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
end
end
if saw_m and saw_f then
gender_for_irreg_ending = "mf"
elseif saw_f then
gender_for_irreg_ending = "f"
else
gender_for_irreg_ending = "m"
end
gender_for_make_plural = gender_for_irreg_ending
local lemma = data.pagename
local plurals = {}
if is_plurale_tantum and not has_singular then
if args[2][1] then
error("Can't specify plurals of plurale tantum " .. category_pos)
end
table.insert(data.inflections, {label = glossary_link("唯複")})
else
plurals = m_headword_utilities.parse_term_list_with_modifiers {
paramname = {2, "pl"},
forms = args[2],
}
-- Check for special plural signals
local mode = nil
local pl1 = plurals[1]
if pl1 and #pl1.term == 1 then
mode = pl1.term
if mode == "?" or mode == "!" or mode == "-" or mode == "~" then
pl1.term = nil
if next(pl1) then
error(("Can't specify inline modifiers with plural code '%s'"):format(mode))
end
table.remove(plurals, 1) -- Remove the mode parameter
elseif mode ~= "+" and mode ~= "#" then
error(("Unexpected plural code '%s'"):format(mode))
end
end
if mode == "?" then
-- Plural is unknown
table.insert(data.categories, "有未知或不確定複數的" .. langname .. category_plpos)
elseif mode == "!" then
-- Plural is not attested
table.insert(data.inflections, {label = "複數無紀錄"})
table.insert(data.categories, "有無紀錄複數的" .. langname .. category_plpos)
if plurals[1] then
error("Can't specify any plurals along with unattested plural code '!'")
end
elseif mode == "-" then
-- Uncountable noun; may occasionally have a plural
table.insert(data.categories, langname .. "不可數" .. category_plpos)
-- If plural forms were given explicitly, then show "usually"
if plurals[1] then
table.insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
table.insert(data.categories, langname .. "可數" .. category_plpos)
else
table.insert(data.inflections, {label = glossary_link("不可數")})
end
else
-- Countable or mixed countable/uncountable
if not plurals[1] and not is_proper then
plurals[1] = {term = "+"}
end
if mode == "~" then
-- Mixed countable/uncountable noun, always has a plural
table.insert(data.inflections, {label = glossary_link("可數") .. "和" .. glossary_link("不可數")})
table.insert(data.categories, langname .. "不可數" .. category_plpos)
table.insert(data.categories, langname .. "可數" .. category_plpos)
else
-- Countable nouns
table.insert(data.categories, langname .. "可數" .. category_plpos)
end
end
-- Gather plurals, handling requests for default plurals.
local has_default_or_hash = false
for _, pl in ipairs(plurals) do
if pl.term:find("^%+") or pl.term:find("#") then
has_default_or_hash = true
break
end
end
if has_default_or_hash then
local newpls = {}
for _, pl in ipairs(plurals) do
if pl.term == "+" then
local default_pls = com.make_plural(lemma)
insert_defpls(default_pls, pl, newpls)
elseif pl.term:find("^%+") then
pl.term = require(romut_module).get_special_indicator(pl.term)
local default_pls = com.make_plural(lemma, pl.term)
insert_defpls(default_pls, pl, newpls)
else
pl.term = replace_hash_with_lemma(pl.term, lemma)
table.insert(newpls, pl)
end
end
plurals = newpls
end
end
if #plurals > 1 then
table.insert(data.categories, "有多個複數的" .. langname .. category_plpos)
end
-- Gather masculines/feminines. For each one, generate the corresponding plural(s). `field` is the name of the
-- field containing the masculine or feminine forms (normally "m" or "f"); `gender` is "m" or "f" for the gender
-- of the forms; `inflect` is a function of one or two arguments to generate the default masculine or feminine from
-- the lemma (the arguments are the lemma and optionally a "special" flag to indicate how to handle multiword
-- lemmas, and the function is normally make_feminine or make_masculine from [[Module:es-common]]); and
-- `default_plurals` is a list into which the corresponding default plurals of the gathered or generated masculine
-- or feminine forms are stored. Note that there may be more default plurals than masculines or feminines, because
-- some terms have multiple possible plurals.
local function handle_mf(field, gender, inflect, default_plurals)
local mfs = m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[field],
frob = function(term)
if term == "+" then
-- Generate default masculine/feminine.
term = inflect(lemma)
else
term = replace_hash_with_lemma(term, lemma)
end
local special = require(romut_module).get_special_indicator(term)
if special then
term = inflect(lemma, special)
end
return term
end
}
for _, mf in ipairs(mfs) do
local mfpls = com.make_plural(mf.term, special)
if mfpls then
for _, mfpl in ipairs(mfpls) do
local plobj = m_table.shallowCopy(mf)
plobj.term = mfpl
-- Add an accelerator for each masculine/feminine plural whose lemma
-- is the corresponding singular, so that the accelerated entry
-- that is generated has a definition that looks like
-- # {{plural of|es|MFSING}}
plobj.accel = {form = "p", lemma = mf.term}
table.insert(default_plurals, plobj)
end
end
end
return mfs
end
local feminine_plurals = {}
local feminines = handle_mf("f", "f", com.make_feminine, feminine_plurals)
local masculine_plurals = {}
local masculines = handle_mf("m", "m", com.make_masculine, masculine_plurals)
local function handle_mf_plural(mfplfield, gender, default_plurals, singulars)
local mfpl = m_headword_utilities.parse_term_list_with_modifiers {
paramname = mfplfield,
forms = args[mfplfield],
}
local new_mfpls = {}
local saw_plus
for i, mfpl in ipairs(mfpl) do
local accel
if #mfpl == #singulars then
-- If same number of overriding masculine/feminine plurals as singulars,
-- assume each plural goes with the corresponding singular
-- and use each corresponding singular as the lemma in the accelerator.
-- The generated entry will have # {{plural of|es|SINGULAR}} as the
-- definition.
accel = {form = "p", lemma = singulars[i].term}
else
accel = nil
end
if mfpl.term == "+" then
-- We should never see + twice. If we do, it will lead to problems since we overwrite the values of
-- default_plurals the first time around.
if saw_plus then
error(("Saw + twice when handling %s="):format(mfplfield))
end
saw_plus = true
for _, defpl in ipairs(default_plurals) do
-- defpl is already a table and has an accel field
m_headword_utilities.combine_termobj_decorations(defpl, mfpl)
table.insert(new_mfpls, defpl)
end
elseif mfpl.term:find("^%+") then
mfpl.term = require(romut_module).get_special_indicator(mfpl.term)
for _, mf in ipairs(singulars) do
local default_mfpls = com.make_plural(mf.term, mfpl.term)
for _, defp in ipairs(default_mfpls) do
local mfplobj = m_table.shallowCopy(mfpl)
mfplobj.term = defp
mfplobj.accel = accel
m_headword_utilities.combine_termobj_decorations(mfplobj, mf)
table.insert(new_mfpls, mfplobj)
end
end
else
mfpl.accel = accel
mfpl.term = replace_hash_with_lemma(mfpl.term, lemma)
table.insert(new_mfpls, mfpl)
end
end
return new_mfpls
end
if args.fpl[1] then
-- Override any existing feminine plurals.
feminine_plurals = handle_mf_plural("fpl", "f", feminine_plurals, feminines)
end
if args.mpl[1] then
-- Override any existing masculine plurals.
masculine_plurals = handle_mf_plural("mpl", "m", masculine_plurals, masculines)
end
local function parse_and_insert_noun_inflection(field, label, accel)
parse_and_insert_inflection(data, args, field, label, accel)
end
local function insert_noun_inflection(terms, label, accel)
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
accel = accel and {form = accel} or nil,
}
end
insert_noun_inflection(plurals, "複數", "p")
insert_noun_inflection(feminines, "陰性", "f")
insert_noun_inflection(feminine_plurals, "陰性複數")
insert_noun_inflection(masculines, "陽性")
insert_noun_inflection(masculine_plurals, "陽性複數")
parse_and_insert_noun_inflection("dim", "指小")
parse_and_insert_noun_inflection("aug", "指大")
parse_and_insert_noun_inflection("pej", "貶義")
--[=[
-- Maybe add category 'Mirandese nouns with irregular gender'
local irreg_gender_lemma = rsub(lemma, " .*", "") -- only look at first word
if (rfind(irreg_gender_lemma, "o$") and (gender_for_irreg_ending == "f" or gender_for_irreg_ending == "mf")) or
(irreg_gender_lemma:find("a$") and (gender_for_irreg_ending == "m" or gender_for_irreg_ending == "mf")) then
table.insert(data.categories, "有不規則性別的" .. langname .. "名詞")
end
]=]
end
local function get_noun_params(is_proper)
return {
[1] = {list = "g", required = not is_proper, default = "?"}, --gender
["g_qual"] = {list = "g\1_qual", allow_holes = true},
[2] = {list = "pl"}, --plural override(s)
["f"] = list_param, --feminine form(s)
["m"] = list_param, --masculine form(s)
["fpl"] = list_param, --feminine plural override(s)
["mpl"] = list_param, --masculine plural override(s)
["dim"] = list_param, --diminutive(s)
["aug"] = list_param, --diminutive(s)
["pej"] = list_param, --pejorative(s)
}
end
pos_functions["名詞"] = {
params = get_noun_params(),
func = do_noun,
}
pos_functions["專有名詞"] = {
params = get_noun_params("is proper"),
func = function(args, data)
do_noun(args, data, "is proper")
end,
}
-----------------------------------------------------------------------------------------
-- Phrases --
-----------------------------------------------------------------------------------------
pos_functions["短語"] = {
params = {
["g"] = list_param,
["g_qual"] = {list = "g\1_qual", allow_holes = true},
["m"] = list_param,
["f"] = list_param,
},
func = function(args, data)
data.genders = {}
process_genders(data, args.g, args.g_qual)
local plpos = "phrases"
parse_and_insert_inflection(data, args, "m", "陽性")
parse_and_insert_inflection(data, args, "f", "陰性")
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
["g"] = list_param,
["g_qual"] = {list = "g\1_qual", allow_holes = true},
},
func = function(args, data)
data.genders = {}
process_genders(data, args.g, args.g_qual)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
table.insert(suffix_type, "構成" .. typ .. "的後綴")
end
table.insert(data.inflections, {label = require("Module:table").serialCommaJoin(suffix_type, {conj = "或"}) .. "的非詞元形式"})
end,
}
pos_functions["動詞"] = {
params = {
[1] = {},
["pres"] = list_param, --present
["pres_qual"] = {list = "pres\1_qual", allow_holes = true},
["pret"] = list_param, --preterite
["pret_qual"] = {list = "pret\1_qual", allow_holes = true},
["part"] = list_param, --participle
["part_qual"] = {list = "part\1_qual", allow_holes = true},
["pagename"] = {}, -- for testing
["noautolinktext"] = boolean_param,
["noautolinkverb"] = boolean_param,
["attn"] = boolean_param,
},
func = function(args, data)
local preses, prets, parts
if args.attn then
insert(data.categories, "需要注意的" .. langname .. "詞")
return
end
local mwl_verb = require(mwl_verb_module)
local alternant_multiword_spec = mwl_verb.do_generate_forms(args, "mwl-verb", data.heads[1])
local specforms = alternant_multiword_spec.forms
local function slot_exists(slot)
return specforms[slot] and specforms[slot][1]
end
local function do_finite(slot_tense, label_tense)
-- Use pres_3s if it exists and pres_1s doesn't exist (e.g. impersonal verbs); similarly for pres_3p (only3p verbs);
-- but fall back to pres_1s if neither pres_1s nor pres_3s nor pres_3p exist (e.g. [[empedernir]]).
local has_1s = slot_exists(slot_tense .. "_1s")
local has_3s = slot_exists(slot_tense .. "_3s")
local has_3p = slot_exists(slot_tense .. "_3p")
if has_1s or (not has_3s and not has_3p) then
return {
slot = slot_tense .. "_1s",
label = ("第一人稱單數%s"):format(label_tense),
}
elseif has_3s then
return {
slot = slot_tense .. "_3s",
label = ("第三人稱單數%s"):format(label_tense),
}
else
return {
slot = slot_tense .. "_3p",
label = ("第三人稱複數%s"):format(label_tense),
}
end
end
preses = do_finite("pres", "現在時")
prets = do_finite("pret", "簡單過去時")
parts = {
slot = "pp_ms",
label = "過去分詞",
}
if args.pres[1] or args.pret[1] or args.part[1] then
track("verb-old-multiarg")
end
local function strip_brackets(qualifiers)
if not qualifiers then
return nil
end
local stripped_qualifiers = {}
for _, qualifier in ipairs(qualifiers) do
local stripped_qualifier = qualifier:match("^%[(.*)%]$")
if not stripped_qualifier then
error("Internal error: Qualifier should be surrounded by brackets at this stage: " .. qualifier)
end
insert(stripped_qualifiers, stripped_qualifier)
end
return stripped_qualifiers
end
local function do_verb_form(args, qualifiers, slot_desc, skip_if_empty)
local forms
local to_insert
if #args == 0 then
forms = specforms[slot_desc.slot]
if not forms or #forms == 0 then
if skip_if_empty then
return
end
forms = {{form = "-"}}
end
elseif #args == 1 and args[1] == "-" then
forms = {{form = "-"}}
else
forms = {}
for i, arg in ipairs(args) do
local qual = qualifiers[i]
if qual then
-- FIXME: It's annoying we have to add brackets and strip them out later. The inflection
-- code adds all footnotes with brackets around them; we should change this.
qual = {"[" .. qual .. "]"}
end
local form = arg
if not args.noautolinkverb then
-- [[Module:inflection utilities]] already loaded by [[Module:es-verb]]
form = require(inflection_utilities_module).add_links(form)
end
insert(forms, {form = form, footnotes = qual})
end
end
if forms[1].form == "-" then
to_insert = {label = "無" .. slot_desc.label}
else
local into_table = {label = slot_desc.label}
for _, form in ipairs(forms) do
local qualifiers = strip_brackets(form.footnotes)
-- Strip redundant brackets surrounding entire form. These may get generated e.g.
-- if we use the angle bracket notation with a single word.
local stripped_form = rmatch(form.form, "^%[%[([^%[%]]*)%]%]$") or form.form
-- Don't include accelerators if brackets remain in form, as the result will be wrong.
-- FIXME: For now, don't include accelerators. We should use {{es-verb form of}} instead.
-- local this_accel = not stripped_form:find("%[%[") and accel or nil
local this_accel = nil
insert(into_table, {term = stripped_form, q = qualifiers, accel = this_accel})
end
to_insert = into_table
end
insert(data.inflections, to_insert)
end
local skip_pres_if_empty
if alternant_multiword_spec.no_pres1_and_sub then
insert(data.inflections, {label = "無第一人稱單數現在時"})
insert(data.inflections, {label = "無現在虛擬式"})
end
if alternant_multiword_spec.no_pres_stressed then
insert(data.inflections, {label = "沒有重讀的現在直陳式或虛擬式"})
skip_pres_if_empty = true
end
if alternant_multiword_spec.only3s then
insert(data.inflections, {label = glossary_link("非人稱")})
elseif alternant_multiword_spec.only3sp then
insert(data.inflections, {label = "僅第三人稱"})
elseif alternant_multiword_spec.only3p then
insert(data.inflections, {label = "僅第三人稱複數"})
end
do_verb_form(args.pres, args.pres_qual, preses, skip_pres_if_empty)
do_verb_form(args.pret, args.pret_qual, prets)
do_verb_form(args.part, args.part_qual, parts)
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:es-verb]] in place of the user-specified or auto-generated head. This was copied from
-- [[Module:it-headword]], where doing this gets accents marked on the verb(s). We don't have accents marked on
-- the verb but by doing this we do get any footnotes on the infinitive propagated here. Don't do this if the
-- user gave multiple heads or gave a head with a multiword-linked verbal expression such as Italian
-- '[[dare esca]] [[al]] [[fuoco]]' (FIXME: give Mirandese equivalent).
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.infinitive_linked) do
local quals, refs = require(inflection_utilities_module).
convert_footnotes_to_qualifiers_and_references(lemma_obj.footnotes)
insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
end
}
return export
ssc0p36b5u6og5e1fe0cfc5zi5svm0x
Module:Mwl-pron
828
3298768
9909087
9499503
2026-09-29T07:27:13Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909087
Scribunto
text/plain
--[=[
This module implements the template {{mwl-pr}}。
Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
]=]
local export = {}
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local decorations_module = "Module:decorations"
local headword_data_module = "Module:headword/data"
local homophones_module = "Module:homophones"
local hyphenation_module = "Module:hyphenation"
local parameters_module = "Module:parameters"
local parse_utilities_module = "Module:parse utilities"
local rhymes_module = "Module:rhymes"
--[=[
About styles, dialects and isoglosses:
In this module only two dialects are recognized, each corresponding to an isogloss.
The recognized isoglosses/dialects are:
-- central
-- sendinese
A "style" here is a set of dialects (two at most) that pronounce a given word in a given fashion.
A "style spec" restricts the output to certain styles (e.g. {{mwl-pr|+<style:sendinese>}} in a Sendinese-only word)
]=]
local lang = require("Module:languages").getByCode("mwl")
local u = m_str_utils.char
local rfind = m_str_utils.find
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local ulower = m_str_utils.lower
local ulen = m_str_utils.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local CED = u(0x0327) --cedilla
local DENT = u(0x032A) --dental symbol
local NONSYL = u(0x032F) --non-syllabic vowel
local SYLDIV = u(0xFFF0) -- used to represent a user-specific syllable divider (.) so we won't change it
local vowel = "aeiouAEIOUɐãẽĩõũẼÕ" -- vowels
local V = "[" .. vowel .. "]" -- vowel class
local accent = AC .. GR .. CFLEX
local accent_c = "[" .. accent .. "]"
local stress = AC .. CFLEX
local stress_c = "[" .. AC .. CFLEX .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local sylsep = "%-." .. SYLDIV -- hyphen included for syllabifying from spelling
local sylsep_c = "[" .. sylsep .. "]"
local wordsep = "# "
local separator_not_wordsep = accent .. ipa_stress .. sylsep
local separator = separator_not_wordsep .. wordsep
local separator_c = "[" .. separator .. "]"
local C = "[^" .. vowel .. separator .. "]" -- consonant class
local T = "[^" .. vowel .. "lrɾjw" .. separator .. "]" -- obstruent or nasal
local unstressed_words = m_table.listToSet({
--FIXM誒話:add more unstressed words
"a", "an", "en", "ne", "cun", "de", "des", "pra", "por", "sin", --prepositions
"la", "las", --articles
"pul", "cul", "nel",--article+preposition contractions
"i"
})
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
local function decompose(text)
-- decompose everything but ũ, ç and ü
text = unfd(text)
text = rsub(text, ".[" .. TILDE .. DIA .. CED .. "]", {
["u" .. TILDE] = "ũ",
["U" .. TILDE] = "Ũ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
["C" .. CED] = "Ç",
["c" .. CED] = "ç"
})
return text
end
local function split_on_comma(term)
if not term then
return nil
end
if term:find(",%s") then
return require(parse_utilities_module).split_on_comma(term)
elseif term:find(",") then
return rsplit(term, ",")
else
return {term}
end
end
local function construct_default_differences(dialect)
if dialect == "central" then
return false
end
return nil
end
-- Main syllable-division algorithm. Can be called either directly on spelling (when hyphenating) or after
-- non-trivial processing of respelling in the direction of pronunciation (when generating pronunciation).
local function syllabify_from_spelling_or_pronun(text, is_spelling)
-- Part 1: Divide before the last consonant in a cluster of consonants between vowels.
-- Then move the syllable division marker leftwards over clusters that
-- can form onsets.
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. V .. ")", "%1.%2")
-- NOT誒話:When run on pronun, we have already eliminated c and v, but not when run on spelling.
-- When run on pronun, don't include r, which at this point represents the trill.
local cluster_r = is_spelling and "rɾ" or "ɾ"
-- Don't divide Cl or Cr where C is a stop or fricative, except for dl. --FIXME
text = rsub(text, "([pbfvkctgɡ])%.([l" .. cluster_r .. "])", ".%1%2")
text = text:gsub("d%.([" .. cluster_r .. "])", ".d%1")
-- Part 2: Divide hiatuses.
text = rsub_repeatedly(text, "([uU]" .. accent_c .. "*)([eɛEu])", "%1.%2")
text = rsub_repeatedly(text, "([iI]" .. accent_c .. "*)([ioɔO])", "%1.%2")
text = rsub_repeatedly(text, "(i" .. accent_c .. "*)u", "%1.u")
text = rsub(text, "ku%.En%.ta", "kuEn.ta") --but don't divide cin.quen.ta
--Fix V.[jw]V -> V[jw].V (note that we're using the symbols <jw> already)
text = rsub_repeatedly(text, "(" .. V ..accent_c .. "*)%.([jw])(" .. V .. ")", "%1%2.%3")
return text
end
local function syllabify_from_spelling(text)
text = decompose(text)
-- start at FFF1 because FFF0 is used for SYLDIV
-- Temporary replacements for characters we want treated as default consonants. The C and related consonant regexes
-- treat all unknown characters as consonants.
local TEMP_I = u(0xFFF1)
local TEMP_U = u(0xFFF2)
local TEMP_GH = u(0xFFF3)
local TEMP_GH_CAPS = u(0xFFF4)
local TEMP_NH = u(0x0400)
local TEMP_NH_CAPS = u(0x0401)
local TEMP_LH = u(0xFFFB)
local TEMP_LH_CAPS = u(0xFFFC)
local TEMP_QU = u(0xFFF9)
local TEMP_QU_CAPS = u(0xFFF5)
local TEMP_GU = u(0xFFF6)
local TEMP_GU_CAPS = u(0xFFF7)
local TEMP_H = u(0xFFF8)
-- Change user-specified . into SYLDIV so we don't shuffle it around when dividing into syllables.
text = rsub(text, "gh", TEMP_GH)
text = rsub(text, "Gh", TEMP_GH_CAPS)
text = rsub(text, "nh", TEMP_NH)
text = rsub(text, "Nh", TEMP_NH_CAPS)
text = rsub(text, "lh", TEMP_LH)
text = rsub(text, "Lh", TEMP_LH_CAPS)
text = rsub(text, "qu(" .. V .. ")", TEMP_QU .. "%1")
text = rsub(text, "Qu(" .. V .. ")", TEMP_QU_CAPS .. "%1")
text = rsub(text, "gu(" .. V .. ")", TEMP_GU .. "%1")
text = rsub(text, "Gu(" .. V .. ")", TEMP_GU_CAPS .. "%1")
text = text:gsub("%.", SYLDIV)
local vowel_to_glide = { ["i"] = TEMP_I, ["u"] = TEMP_U }
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*)([iu])(" .. V .. ")",
function (v1, iu, v2) return v1 .. "." .. vowel_to_glide[iu] .. v2 end
)
text = syllabify_from_spelling_or_pronun(text, "is spelling")
--restore everything back
text = text:gsub(SYLDIV, ".")
text = text:gsub("%." .. TEMP_I, TEMP_I .. ".")
text = text:gsub("%." .. TEMP_U, TEMP_U .. ".")
text = text:gsub(TEMP_I, "i")
text = text:gsub(TEMP_U, "u")
text = text:gsub(TEMP_GH, "gh")
text = text:gsub(TEMP_GH_CAPS, "Gh")
text = text:gsub(TEMP_NH, "nh")
text = text:gsub(TEMP_NH_CAPS, "Nh")
text = text:gsub(TEMP_LH, "lh")
text = text:gsub(TEMP_LH_CAPS, "Lh")
text = text:gsub(TEMP_QU, "qu")
text = text:gsub(TEMP_QU_CAPS, "Qu")
text = text:gsub(TEMP_GU, "gu")
text = text:gsub(TEMP_GU_CAPS, "Gu")
text = unfc(text)
return text
end
export.syllabify_from_spelling = syllabify_from_spelling --Used elsewhere, maybe this should be moved to [[Module:mwl-common]]?
-- Generate the IPA of a given respelling, where a respelling is the representation of the pronunciation of a given
-- Mirandese term using Mirandese spelling conventions.
function export.IPA(text, dialect, phonetic)
local central = dialect == "central"
local sendinese_different = false
text = ulower(text or mw.title.getCurrentTitle().text)
-- decompose everything but ũ, ç and ü
text = decompose(text)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain
-- monosyllabic words unstressed.
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") and not rfind(word, accent_c) or unstressed_words[word] then
-- add GR to the last vowel not the first one
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. GR)
--words ending in -ie
elseif rfind(word, "ie$") and not rfind(word, accent_c) then
words[i] = rsub(word, "ie$", "i" .. AC .. "e")
end
end
text = table.concat(words, " ")
-- Convert hyphens to spaces, to handle [[Austria-Hungría]], [[franco-italiano]], etc.
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
-- handling of irregular spellings
text = rsub(text, "cinquenta", "cincuenta")
--handling of ũ (this is a rare character that appears when nasal u is in a hiatus)
text = rsub(text, "ũ", "uñ")
--handling of <e> and <o>
text = rsub(text, "e" .. CFLEX, "e" .. AC)
text = rsub(text, "o" .. CFLEX, "o" .. AC)
--c, ç, s, z, ch. Internally we'll use S, Z, ć for s̺, z̺, t͡ʃ
text = rsub(text, "s", "S")
text = rsub(text, "(" .. V .. ")S(" .. V .. ")", "%1Z%2")
text = rsub(text, "SS", "S")
text = rsub(text, "ç", "s")
text = rsub(text, "c([ie])", "s%1")
text = rsub(text, "ch", "ć")
--g, q
text = rsub(text, "g([ie])", "ʒ%1")
text = rsub(text, "gu([ie])", "g%1")
text = rsub(text, "gü([ie])", "gu%1") --it's actually /w/, but we will use <u> to keep the syllabification function simpler
text = rsub(text, "qu([ie])", "k%1")
text = rsub(text, "qua", "kua")
--do we need Sendinese?
if rfind(text, "uo") or rfind(text, "ie") or rfind(text, "#ye#") then
sendinese_different = true
end
if not central then
text = rsub(text, "#ye#", "#yi#")
end
--digraphs
text = rsub(text, "nh", "ɲ")
text = rsubb(text, "lh", "ʎ")
text = rsub(text, "#p([st])", "#%1")
text = rsub(text, "x", "ʃ")
text = rsub(text, "gh", "x") --used in some loanwords from Spanish
text = rsub(text, "[cjrvy]",
{ ["c"] = "k", ["j"] = "ʒ", ["r"] = "ɾ", ["v"] = "b", ["y"] = "j"})
--treat the articles l/ls as a special case
text = rsub(text, "#l([S]?)#", "#L%1#")
-- trill or flap ɾ
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "#ɾ", "#r")
text = rsub(text, "(" .. C .. ")ɾ", "%1r")
text = rsub(text, "([bkfgpt])r", "%1ɾ")
--other double consonants
text = rsub(text, "(" .. C .. ")%1", "%1")
--assimilation of sibilants
-- θ, s, S before voiced consonants
local voiced = "mnɲbdgʎl"
local r = "ɾr"
local tovoiced = {
["S"] = "Z",
["s"] = "z",
["ʃ"] = "ʒ",
}
local function voice(sound, following)
return tovoiced[sound] .. following
end
text = rsub(text, "([Ssʃ])(" .. separator_c .. "*[" .. voiced .. r .. "])", voice)
-- convert i/u between vowels to glide
local vowel_to_glide = { ["i"] = "j", ["u"] = "w" }
text = rsub_repeatedly(text, "(.*" .. V .. accent_c .. "*)([iu])(" .. V .. ")",
function (v1, iu, v2) return v1 .. vowel_to_glide[iu] .. v2 end
)
--syllable division
text = syllabify_from_spelling_or_pronun(text, false)
--vowel reduction
local function sendinese_vowels(syl, stressed)
if stressed then
if rfind(syl, "[^#aeou][iu]") then
return true
end
else
if rfind(syl, "e[mn]") then
return true
end
end
return false
end
local function reduce_vowels(syllables)
for i = 1, #syllables do
local is_syllable_stressed = rfind(syllables[i], "ˈ")
if sendinese_vowels(syllables[i], is_syllable_stressed) then
sendinese_different = true
end
--Changes that apply to all syllables
syllables[i] = rsub(syllables[i], "a([mn])", "ɐ%1")
--Changes that apply to unstressed syllables
if not is_syllable_stressed then
syllables[i] = rsub(syllables[i], "ou", "û")
syllables[i] = rsub(syllables[i], "o", "u")
if phonetic then
syllables[i] = rsub(syllables[i], "a", "ɐ")
syllables[i] = rsub(syllables[i], "ɐl", "al") --there's no reduction if followed by /l/ in the same syllable
syllables[i] = rsub(syllables[i], "ei", "ê")
syllables[i] = rsub(syllables[i], "e", "ɨ")
syllables[i] = central and rsub(syllables[i], "ɨ([mn])", "ɐ%1") or syllables[i]
end
syllables[i] = rsub(syllables[i], "û", "ou")
syllables[i] = rsub(syllables[i], "ê", "ei")
--Changes that apply to stressed syllables
else
if not central and phonetic then --FIXM誒話:this should probably happen in the phonemic transcription too
syllables[i] = rsub(syllables[i], "([^#aeou])([iu])", "%1ɨ%2")
end
end
end
end
--diphthongs
text = rsub(text, "i([ae])", "j%1")
text = rsub(text, "u([aeo])", "w%1") --e only in cinquenta
-- add accents
local accent_to_stress_mark = { [AC] = "ˈ", [GR] = ""}
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including ` indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post) return accent_to_stress_mark[accent] .. pre .. post end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
-- Final-stressed words ending in -ç, -n, -l, -r, -i, -u, -is, -us don't need an explicit stress marker
if #syllables > 1 and (rfind(word, "[snlɾiu]#*$") or rfind(word, "[iu]S#*$")) or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables > 1 then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
if rfind(word, "men%.te#") then
local mente_syllables
-- Words ends in -mente (converted above to ménti); add a stress to the preceding portion
mente_syllables = {}
mente_syllables[2] = table.remove(syllables)
mente_syllables[1] = table.remove(syllables)
accent_word(table.concat(syllables, "."), syllables)
accent_word(table.concat(mente_syllables, "."), mente_syllables)
table.insert(syllables, mente_syllables[1])
table.insert(syllables, mente_syllables[2])
else
if rfind(word, "i" .. AC .. "e#") then
local ie = central and "i" .. AC .. ".(e)#" or "je" .. AC .. "#"
syllables[#syllables] = rsub(syllables[#syllables], "i" .. AC .. "e#", ie)
end
accent_word(word, syllables)
end
reduce_vowels(syllables)
-- Reconstruct the word and make all primary stresses but the last one be secondary
words[j] = table.concat(syllables, ".")
words[j] = rsub_repeatedly(words[j], "ˈ(.+)ˈ", "ˌ%1ˈ")
end
text = table.concat(words, " ")
text = rsub(text, "%-", "")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
-- apply dialectal variations
if not central then
text = rsub(text, "je", "i")
text = rsub(text, "wo", "u")
end
--phonetic transcription
if phonetic then
--definite article
text = rsub(text, "L", "(ɐ)l")
-- velarization of syllable-final /l/
text = rsub(text, "l([%.#ˈˌ])", "ɫ%1")
-- fricative vs. stop allophones; first convert stops to fricatives, then back to stops
-- after nasals and sometimes after l
local stop_to_fricative = { ["b"] = "β", ["d"] = "ð", ["g"] = "ɣ" }
local fricative_to_stop = { ["β"] = "b", ["ð"] = "d", ["ɣ"] = "g" }
text = rsub(text, "[bdg]", stop_to_fricative)
text = rsub(text, "([mnɲ]" .. separator_c .. "*)([βɣ])",
function(nasal, fricative)
return nasal .. fricative_to_stop[fricative]
end
)
text = rsub(text, "([lɫʎmnɲ]" .. separator_c .. "*)([ð])",
function(nasal_l, fricative)
return nasal_l .. fricative_to_stop[fricative]
end
)
text = rsub(text, "(##" .. ipa_stress_c .. "*)([βɣð])",
function(stress, fricative)
return stress .. fricative_to_stop[fricative]
end
)
text = rsub(text, "[td]", { ["t"] = "t̪", ["d"] = "d̪" })
-- lateral assimilation before consonants
text = rsub(text, "([lɫ])(" .. separator_c .. "*[td])", "%1" .. DENT .. "%2")
-- voiced fricatives are actually approximants
text = rsub(text, "([βðɣ])", "%1̞")
-- value of the dipthong <ei> = /ej/
text = rsub(text, "ei", "ɐi")
--palatalization of -ingo/a -ico/a in Sendinese
if rfind(text, "ɨin%.g[ɐu]#") or rfind(text, "ɨi%.k[ɐu]#") then
sendinese_different = true
end
if not central then
text = rsub(text, "ɨin%.g([ɐu])#", "ɨin.gʲ%1#")
text = rsub(text, "ɨi%.k([ɐu])#", "ɨi.kʲ%1#")
end
end
--final conversions
local final_conversions = {
["h"] = "",
["ć"] = "t͡ʃ",
["g"] = "ɡ",
["s"] = "s̻",
["z"] = "z̻",
["S"] = "s̺",
["Z"] = "z̺",
}
text = rsub(text, "[hćgszSZ]", final_conversions)
--nasalization and semivowels
text = rsub(text, "([aɐɨeou])i[mn]", "%1" .. TILDE .. "j" .. TILDE)
text = rsub(text, "([aeio])u[mn]", "%1" .. TILDE .. "w" .. TILDE)
text = rsub(text, "([ɐaeoɨiu])[mn]", "%1" .. TILDE)
text = rsub(text, "([aɐɨeou])i", "%1j") --"%1i" .. NONSYL) if we prefer the non-syllabic vowel notation
text = rsub(text, "([aeiɨo])u", "%1w") --"%1w" .. NONSYL)
if rfind(text, "o" .. TILDE .. "#") then sendinese_different = true end
if phonetic then
text = rsub(text, TILDE .. "#", TILDE .. "ŋ#")
text = rsub(text, "u%.ñ", "u" .. TILDE .. ".ŋ")
if not central then --Sendinese final /õ/ is realized as [ɐ̃w̃]
text = rsub(text, "o" .. TILDE .. "ŋ#", "ɐ" .. TILDE .. "w" .. TILDE .. "#")
end
else
text = rsub(text, "L", "(a)l")
text = rsub(text, "u%.ñ", "u" .. TILDE .. ".")
end
text = rsub(text, "#", "")
text = unfc(text)
-- mark /wo/ and /je/ explicitly (this is for the rhymes)
text = rsub(text, "wo", "wO")
text = rsub(text, "je", "jE")
text = rsub(text, "wõ", "wÕ")
text = rsub(text, "jẽ", "jẼ")
local ret = {
text = text,
differences = (dialect == "central") and sendinese_different or nil,
}
return ret
end
-- Remove the special characters E and O (+ their nasal counterparts)
local function cleanup_pron(text)
text = rsub(text, "E", "e")
text = rsub(text, "O", "o")
text = rsub(text, "Ẽ", "ẽ")
text = rsub(text, "Õ", "õ")
return text
end
-- For bot usage; {{#invoke:es-pronunc|IPA_string|SPELLING|style=STYLE|phonetic=PHONETIC}}
-- where
--
-- 1. SPELLING is the word or respelling to generate pronunciation for;
-- 2. required parameter style= indicates the pronunciation style to generate
-- (e.g. "distincion-yeismo" for distinción+yeísmo, as is common in Spain;
-- see the comment above export.IPA() above for the full list);
-- 3. phonetic=1 specifies to generate the phonetic rather than phonemic pronunciation;
function export.IPA_string(frame)
local iparams = {
[1] = {},
["style"] = {required = true},
["phonetic"] = {type = "boolean"},
}
local iargs = require(parameters_module).process(frame.args, iparams)
local retval = export.IPA(iargs[1], iargs.style, iargs.phonetic)
return retval.text
end
-- Generate all relevant dialect pronunciations and group into styles. See the comment above about dialects and styles.
-- A "pronunciation" here could be for example the IPA phonemic/phonetic representation of the term or the IPA form of
-- the rhyme that the term belongs to. If `style_spec` is nil, this generates all styles for all dialects, but
-- `style_spec` can also be a style spec such as "sendinese" or "central" (see comment above) to restrict the
-- output. `dodialect` is a function of two arguments, `ret` and `dialect`, where `ret` is the return-value table (see
-- below), and `dialect` is a string naming a particular dialect, such as "central" or "sendinese".
-- `dodialect` should side-effect the `ret` table by adding an entry to `ret.pronun` for the dialect in question.
--
-- The return value is a table of the form
--
-- {
-- pronun = {DIALECT = {PRONUN, PRONUN, ...}, DIALECT = {PRONUN, PRONUN, ...}, ...},
-- expressed_styles = {STYLE, STYLE, ...},
-- }
--
-- where:
-- 1. DIALECT is a string such as "central" naming a specific dialect.
-- 2. PRONUN is a table describing a particular pronunciation. If the dialect is "central", there should be
-- a field in this table named `differences`, but where other fields may vary depending on the type of pronunciation
-- (e.g. phonemic/phonetic or rhyme). See below for the form of the PRONUN table for phonemic/phonetic pronunciation
-- vs. rhyme and the form of the `differences` field.
-- 3. STYLE is a table of the form {tag = "TAG", pronun = {PRONUN, PRONUN, ...}}。The tag of the style group is the text
-- displayed before the pronunciation, such as "Central" or "Sendinese". It can have the special value of `false` to indicate
-- that no tag text is to be displayed.
--
-- The PRONUN table has the following form for the full phonemic/phonetic pronunciation:
--
-- {
-- phonemic = "PHONEMIC",
-- phonetic = "PHONETIC",
-- differences = {FLAG = BOOLEAN, FLAG = BOOLEAN, ...},
-- }
--
-- Here, `phonemic` is the phonemic pronunciation (displayed as /.../) and `phonetic` is the phonetic pronunciation
-- (displayed as [...]).
--
-- The PRONUN table has the following form for the rhyme pronunciation:
--
-- {
-- rhyme = "RHYME_PRONUN",
-- num_syl = {NUM, NUM, ...},
-- q = nil or {QUALIFIER, QUALIFIER, ...},
-- differences = {FLAG = BOOLEAN, FLAG = BOOLEAN, ...},
-- }
--
-- Here, `rhyme` is a phonemic pronunciation such as "as̻" for [[paçpalhaç]],
-- and `num_syl` is a list of the possible numbers of syllables for the term(s) that have this rhyme
-- (e.g. {3} for [[paçpalhaç]]. `num_syl` is used to generate syllable-count categories such as
-- {{C|韻部|Mirandese/el/3 syllables}} in addition to {{C|韻部|Mirandese/el}}。`num_syl` may be nil to
-- suppress the generation of syllable-count categories; this is typically the case with multiword terms.
-- `q`, if non-nil, comes from the user using the syntax.
--
-- The value of the `differences` field in the PRONUN table (which, as noted above, only needs to be present for the
-- "central" dialect, and otherwise should be nil) is a table containing flags indicating whether and how
-- the per-dialect pronunciations differ. This is an optimization to avoid having to generate all six dialectal
-- pronunciations and compare them. It has the following form:
--
local function express_all_styles(style_spec, dodialect)
local ret = {
pronun = {},
expressed_styles = {},
}
-- Add a style object that represents a particular style to `ret.expressed_styles`.
-- `tag` is the tag text to be used.
-- `dialect_name` is the dialect that this style represents, and whose pronunciation is stored in
-- the style object.
local function express_style(tag, dialect_name)
-- If style specified, make sure it matches the requested style.
local style_matches
if not style_spec then
style_matches = true
else
if dialect_name == style_spec then
style_matches = true
end
if negate then
style_matches = not style_matches
end
end
if not style_matches then
return
end
-- Fetch the representative dialect's pronunciation if not already present.
if not ret.pronun[dialect_name] then
dodialect(ret, dialect_name)
end
-- Insert the new style.
table.insert(ret.expressed_styles, {
tag = tag,
pronun = ret.pronun[dialect_name],
})
end
-- For each type of difference, figure out if the difference exists in any of the given respellings. We do this by
-- generating the pronunciation for the dialect "central", for each respelling. In the process of
-- generating the pronunciation for a given respelling, it computes how the other dialects for that respelling
-- differ. Then we take the union of these differences across the respellings.
dodialect(ret, "central")
local differences = false
for _, pronun in ipairs(ret.pronun["central"]) do
if pronun.differences == true then
differences = true
end
end
-- Now, based on the observed differences, figure out how to combine the individual dialects into styles and
-- style groups.
if not differences then
express_style(false, "central")
else
express_style("中部", "central")
express_style("森迪姆", "sendinese")
end
return ret
end
local function format_all_styles(expressed_styles, format_style)
for i, style in ipairs(expressed_styles) do
style.formatted = format_style(style.tag, style.pronun, i == 1)
end
local lines = {}
for i, style in ipairs(expressed_styles) do
table.insert(lines, style.formatted)
end
return table.concat(lines, "\
")
end
local function dodialect_pronun(args, ret, dialect)
ret.pronun[dialect] = {}
for i, term in ipairs(args.terms) do
local phonemic, phonetic, differences
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
differences = construct_default_differences(dialect)
else
phonemic = export.IPA(term.term, dialect, false)
phonetic = export.IPA(term.term, dialect, true)
differences = phonemic.differences
phonemic = phonemic.text
phonetic = phonetic.text
end
ret.pronun[dialect][i] = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = term.refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
differences = differences,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret, dialect)
dodialect_pronun(args, ret, dialect)
end
local ret = express_all_styles(args.style, this_dodialect_pronun)
local function format_style(tag, pronuns, is_first)
local pronunciations = {}
local formatted_pronuns = {}
local function ins(formatted_part)
table.insert(formatted_pronuns, formatted_part)
end
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]], and also create an approximation of the formatted version so that we can
-- compute the appropriate width of the HTML switcher div box that holds the different per-dialect variants.
-- NOT誒話:The code below constructs the formatted approximation out-of-order in some cases but that doesn't
-- currently matter because we assume all characters have the same width. If we change the width computation
-- in a way that requires the correct order, we need changes to the code below.
for j, pronun in ipairs(pronuns) do
-- Add tag to left qualifiers if first one
-- FIXM誒話:Consider using accent qualifier for the tag instead.
local aas = pronun.aa
if j == #pronuns and tag then
if aas then
aas = m_table.deepCopy(aas)
table.insert(aas, tag)
else
aas = {tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. cleanup_pron(pronun.phonemic:gsub("%.", "")) .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
ins(slash_pron)
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. cleanup_pron(pronun.phonetic) .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
ins(bracket_pron)
end
local last_pronun = #pronunciations
if pronun.q then
pronunciations[first_pronun].q = pronun.q
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
ins(", ")
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if aas then
pronunciations[last_pronun].aa = aas
end
if pronun.q or pronun.qq or pronun.a or aas then
ins(require(decorations_module).format_decorations {
lang = lang,
text = "",
q = pronun.q,
qq = pronun.qq,
a = pronun.a,
aa = aas,
})
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
-- Approximate the reference using a footnote notation. This will be slightly inaccurate if there are
-- more than nine references but that is rare.
ins(string.rep("[1]", #pronun.refs))
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
ins(" ")
end
end
local bullet = string.rep("*", args.bullets) .. " "
-- Here we construct the formatted line in `formatted`.
local pre = is_first and args.pre and args.pre .. " " or ""
local post = is_first and args.post and " " .. args.post or ""
local formatted = bullet .. pre ..
m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" } .. post
return formatted
end
ret.text = format_all_styles(ret.expressed_styles, format_style)
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Return the number of syllables of a phonemic representation, which should have syllable dividers in it but no
-- hyphens.
local function get_num_syl_from_phonemic(phonemic)
-- Maybe we should just count vowels instead of the below code.
phonemic = rsub(phonemic, "|", " ") -- remove IPA foot boundaries
local words = rsplit(phonemic, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
phonemic = table.concat(words, ".")
return ulen(rsub(phonemic, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOT誒話:This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", ""):gsub("t͡ʃ", "tʃ")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
-- "Align" syllabification to original spelling by matching character-by-character, allowing for extra syllable and
-- accent markers in the syllabification. If we encounter an extra syllable marker (.), we allow and keep it. If we
-- encounter an extra accent marker in the syllabification, we drop it. In any other case, we return nil indicating
-- the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local syll_chars = rsplit(decompose(syllab), "")
local spelling_chars = rsplit(decompose(spelling), "")
local i = 1
local j = 1
while i <= #syll_chars or j <= #spelling_chars do
local ci = syll_chars[i]
local cj = spelling_chars[j]
if ci == cj then
table.insert(result, ci)
i = i + 1
j = j + 1
elseif ci == "." then
table.insert(result, ci)
i = i + 1
elseif ci == AC or ci == GR or ci == CFLEX or ci == DIA then
-- skip character
i = i + 1
else
-- non-matching character
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
return nil
end
return unfc(table.concat(result))
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
if word == "l" or word == "ls" then
return true
end
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
return #words == 1 and -- no if multiple words
not words[1]:find(".%-.") and -- no if word is composed of hyphenated parts (e.g. [[Austria-Hungría]])
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and words[1]:find(CFLEX)) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(decompose(ipa))
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the hyphenation.
if not num_syl then
num_syl = {}
for _, hyph in ipairs(hyphs) do
if should_generate_rhyme_from_respelling(hyph.syllabification) then
local this_num_syl = 1 + ulen(rsub(hyph.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for dialect, pronun in pairs(parsed.pronun.pronun[dialect]) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if pronun.phonemic then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_phonemic(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
differences = construct_default_differences(dialect),
})
end
end
local q_qq_inline_modifier_spec = {
store = "insert-flattened",
type = "qualifier",
}
local a_aa_inline_modifier_spec = {
store = "insert-flattened",
type = "labels",
}
local ref_inline_modifier_spec = {
store = "insert-flattened",
item_dest = "refs",
type = "references",
}
-- Parse a pronunciation modifier in `arg`, the argument portion in an inline modifier (after the prefix), which
-- specifies a pronunciation property such as rhyme, hyphenation/syllabification, homophones or audio. The argument
-- can itself have inline modifiers, e.g. <audio:Foo.ogg<a:Central>>. The allowed inline modifiers are specified
-- by `param_mods` (of the format expected by `parse_inline_modifiers()`); in addition to any modifiers specified
-- there, the modifiers <q:...>, <qq:...>, <a:...>, <aa:...> and <ref:...> are always accepted (and can be repeated).
-- `generate_obj` and `parse_err` are like in `parse_inline_modifiers()` and specify respectively a function to
-- generate the object into which modifier properties are stored given the non-modifier part of the argument, and
-- a function to generate an error message (given the message). Normally, a comma-separated list of pronunciation
-- properties is accepted and parsed, where each element in the list can have its own inline modifiers and where
-- no spaces are allowed next to the commas in order for them to be recognized as separators. If `no_split_on_comma`
-- is given, only a single pronunciation property is accepted. In all cases, however, the return value is a list
-- of property objects (when `no_split_on_comma` is given, the return value is a one-element list).
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
if arg:find("<") then
param_mods.q = q_qq_inline_modifier_spec
param_mods.qq = q_qq_inline_modifier_spec
param_mods.a = a_aa_inline_modifier_spec
param_mods.aa = a_aa_inline_modifier_spec
param_mods.ref = ref_inline_modifier_spec
local retval = require(parse_utilities_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
if no_split_on_comma then
retval = {retval}
end
return retval
elseif no_split_on_comma then
return {generate_obj(arg)}
else
local retval = {}
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
return retval
end
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
type = "number",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- [[Module:links]] expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
-- No tr=, ts=, or sc=; doesn't make sense for Spanish.
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- [[Module:links]] expects the genders in "genders".
item_dest = "genders",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, caption = arg:match("^(.-)%s*#%s*(.*)$")
file = file or arg
return {file = file, caption = caption}
end
local function parse_audio(arg, parse_err)
local param_mods = {
IPA = {
sublist = true,
},
text = {},
t = {
item_dest = "gloss",
},
-- No tr=, ts=, or sc=; doesn't make sense for Spanish.
gloss = {},
pos = {},
-- No alt=; text= already goes in alt=.
lit = {},
-- No id=; text= already goes in alt= and isn't normally linked.
g = {
item_dest = "genders",
sublist = true,
},
bad = {},
}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
local retvals = parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
local retval = retvals[1]
retval.lang = lang
local textobj = require(audio_module).construct_audio_textobj(retval)
retval.text = textobj
retval.gloss = nil
retval.pos = nil
retval.lit = nil
retval.genders = nil
return retval
end
-- External entry point for {{es-pr}}。
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {convert = parse_rhyme},
["hyph"] = {convert = parse_hyph},
["hmp"] = {convert = parse_homophone},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require(parameters_module).process(parargs, params)
local pagename = args.pagename or mw.loadData(headword_data_module).pagename
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local overall_rhyme = args.rhyme
local overall_hyph = args.hyph
local overall_hmp = args.hmp
local overall_audio
if args.audio then
-- We can't specify parse_audio() as a `convert` function because it needs access to `pagename` (i.e. another
-- parameter).
overall_audio = {}
for i, audio in ipairs(args.audio) do
local function parse_err(msg)
error(("%s: parameter audio%s=%s"):format(msg, i == 1 and "" or i, audio))
end
local parsed_audio = parse_audio(audio, parse_err, pagename)
table.insert(overall_audio, parsed_audio)
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
style = { overall = true },
bullets = {
overall = true,
type = "number",
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert",
convert = function(arg, parse_err)
return parse_audio(arg, parse_err, pagename)
end,
},
ref = ref_inline_modifier_spec,
q = q_qq_inline_modifier_spec,
qq = q_qq_inline_modifier_spec,
a = a_aa_inline_modifier_spec,
aa = a_aa_inline_modifier_spec,
}
local parsed = require(parse_utilities_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "=" .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic or term.raw_phonetic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(aligned_syll))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify_from_spelling(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret, dialect)
rhyme_ret.pronun[dialect] = {}
-- It's possible the pronunciation for a passed-in dialect was never generated.
-- In this case we need to generate the missing overall pronunciation ourselves
-- since we need it to generate the dialect-specific rhyme pronunciation.
if not parsed.pronun.pronun[dialect] then
dodialect_pronun(parsed, parsed.pronun, dialect)
end
for _, pronun in ipairs(parsed.pronun.pronun[dialect]) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_phonemic(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun[dialect]) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables.
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
local rhyme_diffs = false
if dialect == "central" then
if rfind(rhyme, "[EẼOÕ]") then
rhyme_diffs = true
end
end
table.insert(rhyme_ret.pronun[dialect], {
rhyme = cleanup_pron(rhyme),
num_syl = {num_syl},
differences = rhyme_diffs,
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = express_all_styles(parsed.style, dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret, dialect)
end
parsed.rhyme = express_all_styles(parsed.style, this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret, dialect)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret, dialect)
end
overall_rhyme = express_all_styles(parsed.style, dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local function format_rhyme_style(tag, pronuns, is_first)
local pronunciations = {}
local rhymes = {}
for _, pronun in ipairs(pronuns) do
table.insert(rhymes, pronun)
end
local data = {
lang = lang,
rhymes = rhymes,
aa = tag and {tag} or nil,
}
local bullet = string.rep("*", num_bullets) .. " "
local formatted = bullet .. require(rhymes_module).format_rhymes(data)
return formatted
end
return format_all_styles(rhyme_ret.expressed_styles, format_rhyme_style)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require(hyphenation_module).format_hyphenations { lang = lang, hyphs = hyphs, caption = "音節化" }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require(homophones_module).format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio(audio)
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\
")
end
local textparts = {}
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\
")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\
")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\
")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\
")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\
")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\
")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\
")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\
")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\
")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\
")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\
")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\
")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
g4owsdhiiuoag4pyunjubmkyxv57k1m
Module:Gmh-headword
828
3313314
9909069
9522616
2026-09-29T07:26:06Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909069
Scribunto
text/plain
local export = {}
local pos_functions = {}
local en_utilities_module = "Module:en-utilities"
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local lang = require("Module:languages").getByCode("gmh")
local langname = "中古高地德語"
-- Table of all valid genders.
local valid_genders = {
["m"] = true,
["f"] = true,
["n"] = true,
["m-p"] = true,
["f-p"] = true,
["n-p"] = true,
["?"] = true,
["?-p"] = true,
}
local rfind = mw.ustring.find
local function track(track_id)
require("Module:debug/track")("gmh-headword/" .. track_id)
return true
end
local function glossary_link(anchor, text)
text = text or anchor
return "[[Appendix:Glossary#" .. anchor .. "|" .. text .. "]]"
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = {required = true},
["def"] = {},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local args = frame:getParent().args
local poscat = iargs[1]
local def = iargs.def
local parargs = frame:getParent().args
local params = {
["head"] = {list = true},
["id"] = {},
["sort"] = {},
["splithyph"] = {type = "boolean"},
["nolinkhead"] = {type = "boolean"},
["json"] = {type = "boolean"},
["pagename"] = {}, -- for testing
}
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.title.getCurrentTitle().text
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = args.head,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
sort_key = args.sort,
force_cat_output = force_cat,
}
local is_suffix = false
if pagename:find("^%-") and poscat ~= "後綴變格形" then
is_suffix = true
data.pos_category = "後綴"
local singular_poscat = poscat
table.insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
table.insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
if pos_functions[poscat] then
pos_functions[poscat].func(def, args, data, is_suffix)
end
-- mw.ustring.toNFD performs decomposition, so letters that decompose
-- to an ASCII vowel and a diacritic, such as é, are counted as vowels and
-- do not need to be included in the pattern.
if not pagename:find("[ %-]") and not rfind(mw.ustring.lower(mw.ustring.toNFD(pagename)), "[aeiouyæœø]") then
table.insert(data.categories, langname .. " words without vowels")
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
end
local function get_noun_params(is_proper)
params = {
[1] = {alias_of = "g", list = false},
["indecl"] = {type = "boolean"},
}
local list = {list = true}
local function insert_list_param(arg)
params[arg] = list
params[arg .. "_qual"] = {list = arg .. "\1_qual", allow_holes = true}
end
insert_list_param("g")
insert_list_param("m")
insert_list_param("f")
insert_list_param("adj")
insert_list_param("dim")
insert_list_param("pl")
insert_list_param("aug")
insert_list_param("pej")
insert_list_param("dem")
insert_list_param("fdem")
return params
end
local function do_nouns(is_proper, def, args, data, is_suffix)
for i, g in ipairs(args.g) do
if not valid_genders[g] then
error("Unrecognized gender: '" .. g .. "'")
end
track("gender-" .. g)
if args.g_qual[i] then
table.insert(data.genders, {spec = g, q = {args.g_qual[i]}})
else
table.insert(data.genders, g)
end
end
if args.indecl then
table.insert(data.inflections, {label = glossary_link("無屈折")})
table.insert(data.categories, langname .. "無屈折名詞")
end
local function handle_infl(arg, label)
local vals = args[arg]
local quals = args[arg .. "_qual"]
if #vals > 0 then
local inflections = {}
for i, val in ipairs(vals) do
table.insert(inflections, {term = val, q = quals and quals[i] and {quals[i]} or nil})
end
inflections.label = label
table.insert(data.inflections, inflections)
end
end
handle_infl("m", "陽性")
handle_infl("f", "陰性")
handle_infl("adj", "相關形容詞")
handle_infl("dim", "指小")
handle_infl("pl", "複數")
handle_infl("aug", "指大")
handle_infl("pej", "貶義")
handle_infl("dem", "居民稱謂詞")
handle_infl("fdem", "女性居民稱謂詞")
end
pos_functions["名詞"] = {
params = get_noun_params(false),
func = function(def, args, data, is_suffix)
return do_nouns(false, def, args, data, is_suffix)
end,
}
pos_functions["形容詞"] = {
params = {
[1] = {list = "comp"},
[2] = {list = "sup"},
["adv"] = {list = true},
["indecl"] = {type = "boolean"},
},
func = function(def, args, data, is_suffix)
if args.indecl then
table.insert(data.inflections, {label = glossary_link("無屈折")})
table.insert(data.categories, langname .. "無屈折形容詞")
end
if args[1][1] == "-" then
table.insert(data.inflections, {label = "無比較級"})
table.insert(data.categories, langname .. "無比較級形容詞")
elseif #args[1] > 0 then
local comp = args[1]
comp.label = "比較級"
comp.accel = {form = "comparative"}
table.insert(data.inflections, comp)
table.insert(data.categories, langname .. "有比較級形容詞")
end
if #args[2] > 0 then
local sup = args[2]
sup.label = "最高級"
sup.accel = {form = "superlative"}
table.insert(data.inflections, sup)
end
if #args.adv > 0 then
args.adv.label = "副詞"
table.insert(data.inflections, args.adv)
end
end,
}
return export
px7w9f2g326vc6r4ws39lwwepvhtyal
Module:Lld-headword
828
3354005
9909084
9633653
2026-09-29T07:27:02Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909084
Scribunto
text/plain
local export = {}
-- This module contains code for Ladin headword templates.
-- Templates covered are:
-- * {{lld-noun}}, {{lld-proper noun}};
-- * {{lld-verb}};
-- * {{lld-adj}}, {{lld-adj-comp}}, {{lld-adj-sup}};
-- * {{lld-det}};
-- * {{lld-art}};
-- * {{lld-pron-adj}};
-- * {{lld-pp}};
-- * {{lld-presp}};
-- * {{lld-card-noun}}, {{lld-card-adj}}, {{lld-card-inv}};
-- * {{lld-adv}};
-- * {{lld-pos}};
-- * {{lld-suffix form}}.
local pos_functions = {}
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local m_links = require("Module:links")
local m_table = require("Module:table")
local en_utilities_module = "Module:en-utilities"
local romut_module = "Module:romance utilities"
local lld_verb_module = "Module:lld-verb"
local com = require("Module:lld-common")
local lang = require("Module:languages").getByCode("lld")
local langname = lang:getCanonicalName()
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local uupper = mw.ustring.upper
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local unfd = mw.ustring.toNFD
local unfc = mw.ustring.toNFC
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local GR = u(0x0300)
local V = "[aeiou]"
local NV = "[^aeiou]"
local AV = "[àèéìòóù]"
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
local function track(page)
require("Module:debug/track")("lld-headword/" .. page)
return true
end
local function glossary_link(entry, text)
text = text or entry
return "[[Appendix:Glossary#" .. entry .. "|" .. text .. "]]"
end
local function check_all_missing(forms, plpos, tracking_categories)
for _, form in ipairs(forms) do
if type(form) == "table" then
form = form.term
end
if form then
local title = mw.title.new(form)
if title and not title.exists then
table.insert(tracking_categories, langname .. " " .. plpos .. " with red links in their headword lines")
end
end
end
end
local no_spllld_apostrophe_words = {
["c'è"] = true,
["c'era"] = true,
["c'erano"] = true,
}
-- The main entry point.
function export.show(frame)
local poscat = frame.args[1]
or error("Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local parargs = frame:getParent().args
local params = {
["head"] = {list = true},
["id"] = {},
["sort"] = {},
["apoc"] = {type = "boolean"},
["splithyph"] = {type = "boolean"},
["nolinkhead"] = {type = "boolean"},
["json"] = {type = "boolean"},
["pagename"] = {}, -- for testing
}
if poscat == "verbs-old" then
local m_headword_old = require("Module:lld-headword/old")
pos_functions = m_headword_old.pos_functions
poscat = "verbs"
end
if pos_functions[poscat] then
for key, val in pairs(pos_functions[poscat].params) do
params[key] = val
end
end
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = args.head
local heads = user_specified_heads
if args.nolinkhead then
if #heads == 0 then
heads = {pagename}
end
else
local romut = require(romut_module)
local auto_linked_head = romut.add_links_to_multiword_term(pagename, args.splithyph,
no_spllld_apostrophe_words)
if #heads == 0 then
heads = {auto_linked_head}
else
for i, head in ipairs(heads) do
if head:find("^~") then
head = romut.apply_link_modifiers(auto_linked_head, usub(head, 2))
heads[i] = head
end
if head == auto_linked_head then
track("redundant-head")
end
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
genders = {},
inflections = {},
pagename = pagename,
id = args.id,
sort_key = args.sort,
force_cat_output = force_cat,
}
local is_suffix = false
if pagename:find("^%-") and poscat ~= "後綴變格形" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = poscat
table.insert(data.categories, "構成" .. singular_poscat .. "的" .. langname .. "後綴")
table.insert(data.inflections, {label = "構成" .. singular_poscat .. "的後綴"})
end
local tracking_categories = {}
if pos_functions[poscat] then
pos_functions[poscat].func(args, data, tracking_categories, frame, is_suffix)
end
if args.apoc then
-- Apocopated form of a term; do this after calling pos_functions[], because the function might modify
-- data.pos_category.
local pos = data.pos_category
if not pos:find("變格形") then
-- Apocopated forms are non-lemma forms.
local singular_poscat = pos
data.pos_category = singular_poscat .. "變格形"
end
-- If this is a suffix, insert label 'apocopated' after 'FOO-forming suffix', otherwise insert at the beginning.
table.insert(data.inflections, is_suffix and 2 or 1, {label = glossary_link("apocopated")})
end
if args.json then
return require("Module:JSON").toJSON(data)
end
return require("Module:headword").full_headword(data)
.. (#tracking_categories > 0 and require("Module:utilities").format_categories(tracking_categories, lang, args.sort, nil, force_cat) or "")
end
local function fetch_qualifiers(qual, existing)
if not qual then
return existing
end
if not existing then
return {qual}
end
local retval = {}
for _, e in ipairs(existing) do
table.insert(retval, e)
end
table.insert(retval, qual)
return retval
end
local function process_terms_with_qualifiers(terms, quals)
local infls = {}
for i, term in ipairs(terms) do
table.insert(infls, {term = term, q = fetch_qualifiers(quals[i])})
end
return infls
end
local function replace_hash_with_lemma(term, lemma)
-- If there is a % sign in the lemma, we have to replace it with %% so it doesn't get interpreted as a capture replace
-- expression.
lemma = lemma:gsub("%%", "%%%%")
-- Assign to a variable to discard second return value.
term = term:gsub("#", lemma)
return term
end
-----------------------------------------------------------------------------------------
-- Nouns --
-----------------------------------------------------------------------------------------
local allowed_genders = m_table.listToSet(
{"m", "f", "mf", "mfbysense", "m-p", "f-p", "mf-p", "mfbysense-p", "?", "?-p"}
)
local function process_genders(data, genders, g_qual)
for i, g in ipairs(genders) do
if not allowed_genders[g] then
error("Unrecognized gender: " .. g)
end
if g_qual[i] then
table.insert(data.genders, {spec = g, q = {g_qual[i]}})
else
table.insert(data.genders, g)
end
end
end
local function do_noun(args, data, tracking_categories, pos, is_suffix, is_proper)
local is_plurale_tantum = false
local has_singular = false
if is_suffix then
pos = "suffix"
end
local plpos = require(en_utilities_module).pluralize(pos)
data.genders = {}
local saw_m = false
local saw_f = false
local gender_for_default_plural = args[1][1]
process_genders(data, args[1], args.g_qual)
-- Check for specific genders and pluralia tantum.
for _, g in ipairs(args[1]) do
if g:find("-p$") then
is_plurale_tantum = true
else
has_singular = true
if g == "m" or g == "mf" or g == "mfbysense" then
saw_m = true
end
if g == "f" or g == "mf" or g == "mfbysense" then
saw_f = true
end
end
end
if saw_m and saw_f then
gender_for_default_plural = "mf"
end
local lemma = m_links.remove_links(data.heads[1]) -- should always be specified
local function insert_inflection(list, term, accel, qualifiers, genders, no_inv)
if genders then
for _, g in ipairs(genders) do
if g == "m" and not saw_m or g == "f" and not saw_f then
table.insert(data.categories, langname .. " " .. plpos .. " that change gender in the plural")
end
end
end
local infl = {q = qualifiers, accel = accel, genders = genders}
if term == lemma and not no_inv then
infl.label = glossary_link("無屈折")
else
infl.term = term
end
infl.term_for_further_inflection = term
table.insert(list, infl)
end
-- Plural
local plurals = {}
local args_mpl = args.mpl
local args_fpl = args.fpl
local args_pl = args[2]
if is_plurale_tantum and not has_singular then
if #args_pl > 0 then
error("Can't specify plurals of plurale tantum " .. pos)
end
table.insert(data.inflections, {label = glossary_link("唯複")})
elseif args.apoc then
-- apocopated noun
if #args_pl > 0 then
error("Can't specify plurals of apocopated " .. pos)
end
else
if is_plurale_tantum then
-- both singular and plural
table.insert(data.inflections, {label = "有時" .. glossary_link("唯複") .. ",有變化"})
end
-- If no plurals, use the default plural unless mpl= or fpl= explicitly given.
if #args_pl == 0 and #args_mpl == 0 and #args_fpl == 0 and not is_proper then
args_pl = {"+"}
end
-- If only ~ given (countable and uncountable), add the default plural after it.
if #args_pl == 1 and args_pl[1] == "~" then
args_pl = {"~", "+"}
end
-- Gather plurals, handling requests for default plurals
for i, pl in ipairs(args_pl) do
local function insert_pl(term)
if term == lemma and i == 1 then
-- Invariable
-- If plural forms were given explicitly, then show "usually"
if #args_pl > 1 then
table.insert(data.inflections, {label = "通常" .. glossary_link("無屈折")})
else
table.insert(data.inflections, {label = glossary_link("無屈折")})
end
table.insert(data.categories, langname .. "無屈折" .. plpos)
else
insert_inflection(plurals, term, nil, fetch_qualifiers(args.pl_qual[i]),
args.pl_g[i] and rsplit(args.pl_g[i], "%s*,%s*") or nil)
end
table.insert(data.categories, langname .. "可數" .. plpos)
end
local function make_gendered_plural(form, gender, special)
if gender == "mf" then
local default_mpl = com.make_plural(lemma, "m", special)
local default_fpl = com.make_plural(lemma, "f", special)
if default_mpl then
if default_mpl == default_fpl then
insert_pl(default_mpl)
else
if #args_mpl > 0 or #args_fpl > 0 then
error("Can't specify gendered plural spec '" .. (special or "+") .. "' along with gender=" .. gender
.. " and also specify mpl= or fpl=")
end
args_mpl = {default_mpl}
args_fpl = {default_fpl}
end
end
else
local pl = com.make_plural(lemma, gender, special)
if pl then
insert_pl(pl)
end
end
end
if pl == "cap*" or pl == "cap*+" then
make_gendered_plural(lemma, gender_for_default_plural, pl)
elseif pl == "+" then
make_gendered_plural(lemma, gender_for_default_plural)
elseif pl:find("^%+") then
pl = require(romut_module).get_special_indicator(pl)
make_gendered_plural(lemma, gender_for_default_plural, pl)
elseif pl == "?" or pl == "!" then
if i > 1 or #args_pl > 1 then
error("Can't specify ? or ! with other plurals")
end
if pl == "?" then
-- Plural is unknown
table.insert(data.inflections, {label = "複數未知或不確定"})
table.insert(data.categories, "有未知或不確定複數的" .. langname .. plpos)
else
-- Plural is not attested
table.insert(data.inflections, {label = "複數無紀錄"})
table.insert(data.categories, "有無紀錄複數的" .. langname .. plpo)
end
elseif pl == "-" then
if i > 1 then
error("Plural specifier - must be first")
end
-- Uncountable noun; may occasionally have a plural
table.insert(data.categories, langname .. "不可數" .. plpos)
-- If plural forms were given explicitly, then show "usually"
if #args_pl > 1 then
table.insert(data.inflections, {label = "通常" .. glossary_link("不可數")})
table.insert(data.categories, langname .. "可數" .. plpos)
else
table.insert(data.inflections, {label = glossary_link("不可數")})
end
elseif pl == "~" then
if i > 1 then
error("Plural specifier ~ must be first")
end
-- Countable and uncountable noun; will have a plural
table.insert(data.categories, langname .. " countable " .. plpos)
table.insert(data.categories, langname .. " uncountable " .. plpos)
table.insert(data.inflections, {label = glossary_link("countable") .. " and " .. glossary_link("uncountable")})
else
insert_pl(replace_hash_with_lemma(pl, lemma))
end
end
end
if #plurals > 1 then
table.insert(data.categories, langname .. " " .. plpos .. " with multiple plurals")
end
-- Gather masculines/feminines. For each one, generate the corresponding plural(s).
local function handle_mf(mfs, qualifiers, gender, inflect, default_plurals)
local retval = {}
for i, mf in ipairs(mfs) do
local function insert_infl(list, term, accel, existing_qualifiers)
insert_inflection(list, term, accel, fetch_qualifiers(qualifiers[i], existing_qualifiers), nil, "no inv")
end
if mf == "+" then
-- Generate default feminine.
mf = inflect(lemma)
else
mf = replace_hash_with_lemma(mf, lemma)
end
local special = require(romut_module).get_special_indicator(mf)
if special then
mf = inflect(lemma, special)
end
insert_infl(retval, mf)
local mfpl = com.make_plural(mf, gender, special)
if mfpl then
-- Add an accelerator for each masculine/feminine plural whose lemma
-- is the corresponding singular, so that the accelerated entry
-- that is generated has a definition that looks like
-- # {{plural of|es|MFSING}}
insert_infl(default_plurals, mfpl, {form = "p", lemma = mf})
end
end
return retval
end
local default_feminine_plurals = {}
local feminine_plurals = {}
local feminines = handle_mf(args.f, args.f_qual, "f", com.make_feminine, default_feminine_plurals)
local default_masculine_plurals = {}
local masculine_plurals = {}
local masculines = handle_mf(args.m, args.m_qual, "m", com.make_masculine, default_masculine_plurals)
local function handle_mf_plural(mfpl, qualifiers, gender, default_plurals, singulars)
local new_mfpls = {}
for i, mfpl in ipairs(mfpl) do
local function insert_infl(term, accel, existing_qualifiers, no_inv)
insert_inflection(new_mfpls, term, accel, fetch_qualifiers(qualifiers[i], existing_qualifiers), nil, no_inv)
end
local accel
if #mfpl == #singulars then
-- If same number of overriding masculine/feminine plurals as singulars,
-- assume each plural goes with the corresponding singular
-- and use each corresponding singular as the lemma in the accelerator.
-- The generated entry will have # {{plural of|it|SINGULAR}} as the
-- definition.
accel = {form = "p", lemma = singulars[i].term}
else
accel = nil
end
if mfpl == "+" then
if #default_plurals > 0 then
for _, defpl in ipairs(default_plurals) do
-- defpl is a table
-- don't use "invariable" because the plural is not with respect to the lemma but
-- with respect to the masc/fem singular
insert_infl(defpl.term_for_further_inflection, defpl.accel, defpl.q, "no inv")
end
else
-- mf is a table
local default_mfpl = com.make_plural(lemma, gender)
if default_mfpl then
insert_infl(default_mfpl, accel)
end
end
elseif mfpl == "cap*" or mfpl == "cap*+" or mfpl:find("^%+") then
if mfpl:find("^%+") then
mfpl = require(romut_module).get_special_indicator(mfpl)
end
if #singulars > 0 then
for _, mf in ipairs(singulars) do
-- mf is a table
local default_mfpl = com.make_plural(mf.term_for_further_inflection, gender, mfpl)
if default_mfpl then
-- don't use "invariable" because the plural is not with respect to the lemma but
-- with respect to the masc/fem singular
insert_infl(default_mfpl, accel, mf.q, "no inv")
end
end
else
local default_mfpl = com.make_plural(lemma, gender, mfpl)
if default_mfpl then
insert_infl(default_mfpl, accel)
end
end
else
mfpl = replace_hash_with_lemma(mfpl, lemma)
-- don't use "invariable" if masc/fem singular present because the plural is not with respect to
-- the lemma but with respect to the masc/fem singular
insert_infl(mfpl, accel, nil, #singulars > 0)
end
end
return new_mfpls
end
-- FIXME: We should generate feminine plurals by default from feminine singulars given, and vice-versa.
-- To do that, eliminate the distinction between `default_feminine_plurals` and `feminine_plurals`,
-- as in [[Module:es-headword]].
if #args_fpl > 0 then
-- Set feminine plurals.
feminine_plurals = handle_mf_plural(args_fpl, args.fpl_qual, "f", default_feminine_plurals, feminines)
end
if #args_mpl > 0 then
-- Set masculine plurals.
masculine_plurals = handle_mf_plural(args_mpl, args.mpl_qual, "m", default_masculine_plurals, masculines)
end
check_all_missing(plurals, plpos, tracking_categories)
check_all_missing(feminines, plpos, tracking_categories)
check_all_missing(feminine_plurals, plpos, tracking_categories)
check_all_missing(masculines, plpos, tracking_categories)
check_all_missing(masculine_plurals, plpos, tracking_categories)
local function redundant_plural(pl)
for _, p in ipairs(plurals) do
if p.term_for_further_inflection == pl.term_for_further_inflection then
return true
end
end
return false
end
for _, mpl in ipairs(masculine_plurals) do
if redundant_plural(mpl) then
track("noun-redundant-mpl")
end
end
for _, fpl in ipairs(feminine_plurals) do
if redundant_plural(fpl) then
track("noun-redundant-fpl")
end
end
if #plurals > 0 then
plurals.label = "複數"
plurals.accel = {form = "p"}
table.insert(data.inflections, plurals)
end
if #masculines > 0 then
masculines.label = "陽性"
table.insert(data.inflections, masculines)
end
if #masculine_plurals > 0 then
masculine_plurals.label = "陽性複數"
table.insert(data.inflections, masculine_plurals)
end
if #feminines > 0 then
feminines.label = "陰性"
feminines.accel = {form = "f"}
table.insert(data.inflections, feminines)
end
if #feminine_plurals > 0 then
feminine_plurals.label = "陰性複數"
table.insert(data.inflections, feminine_plurals)
end
-- Maybe add category 'Ladin nouns with irregular gender' (or similar)
local irreg_gender_lemma = rsub(lemma, " .*", "") -- only look at first word
if (irreg_gender_lemma:find("o$") and (gender_for_default_plural == "f" or gender_for_default_plural == "mf"
or gender_for_default_plural == "mfbysense")) or
(irreg_gender_lemma:find("a$") and (gender_for_default_plural == "m" or gender_for_default_plural == "mf"
or gender_for_default_plural == "mfbysense")) then
table.insert(data.categories, "有不規則性別的" .. langname .. plpos)
end
end
local function get_noun_params(nountype)
return {
[1] = {list = "g", required = nountype ~= "proper", default = "?"},
[2] = {list = "pl"},
["g_qual"] = {list = "g\1_qual", allow_holes = true},
["pl_qual"] = {list = "pl\1_qual", allow_holes = true},
["pl_g"] = {list = "pl\1_g", allow_holes = true},
["m"] = {list = true},
["m_qual"] = {list = "m\1_qual", allow_holes = true},
["f"] = {list = true},
["f_qual"] = {list = "f\1_qual", allow_holes = true},
["mpl"] = {list = true},
["mpl_qual"] = {list = "mpl\1_qual", allow_holes = true},
["fpl"] = {list = true},
["fpl_qual"] = {list = "fpl\1_qual", allow_holes = true},
}
end
pos_functions["名詞"] = {
params = get_noun_params("base"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_noun(args, data, tracking_categories, "名詞", is_suffix)
end,
}
pos_functions["專有名詞"] = {
params = get_noun_params("proper"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_noun(args, data, tracking_categories, "專有名詞", is_suffix, "is proper noun")
end,
}
pos_functions["cardinal nouns"] = {
params = get_noun_params("base"),
func = function(args, data, tracking_categories)
do_noun(args, data, tracking_categories, "數詞")
data.pos_category = "numerals"
table.insert(data.categories, 1, langname .. "基數詞")
end,
}
-----------------------------------------------------------------------------------------
-- Adjectives --
-----------------------------------------------------------------------------------------
local function do_adjective(args, data, tracking_categories, pos, is_suffix, is_superlative)
local feminines = {}
local masculine_plurals = {}
local feminine_plurals = {}
if is_suffix then
pos = "suffix"
end
local plpos = require(en_utilities_module).pluralize(pos)
if not is_suffix then
data.pos_category = plpos
end
if args.sp then
local romut = require(romut_module)
if not romut.allowed_special_indicators[args.sp] then
local indicators = {}
for indic, _ in pairs(romut.allowed_special_indicators) do
table.insert(indicators, "'" .. indic .. "'")
end
table.sort(indicators)
error("Special inflection indicator beginning can only be " ..
mw.text.listToText(indicators) .. ": " .. args.sp)
end
end
local lemma = m_links.remove_links(data.heads[1]) -- should always be specified
local function insert_inflection(forms, label, accel)
if #forms > 0 then
if forms[1].term == "-" then
table.insert(data.inflections, {label = "無" .. label})
else
forms.label = label
forms.accel = {form = accel}
table.insert(data.inflections, forms)
end
end
end
if args.inv then
-- invariable adjective
table.insert(data.inflections, {label = glossary_link("無屈折")})
table.insert(data.categories, langname .. "無屈折" .. plpos)
if args.sp or #args.f > 0 or #args.pl > 0 or #args.mpl > 0 or #args.fpl > 0 then
error("Can't specify inflections with an invariable adjective")
end
end
if args.apoc then
-- apocopated adjective
if args.sp or #args.f > 0 or #args.pl > 0 or #args.mpl > 0 or #args.fpl > 0 then
error("Can't specify inflections with an apocopated adjective")
end
end
if args.inv or args.apoc then
--
elseif args.fonly then
-- feminine-only
if #args.f > 0 then
error("Can't specify explicit feminines with feminine-only adjective")
end
if #args.pl > 0 then
error("Can't specify explicit plurals with feminine-only adjective, use fpl=")
end
if #args.mpl > 0 then
error("Can't specify explicit masculine plurals with feminine-only adjective")
end
local argsfpl = args.fpl
if #argsfpl == 0 then
argsfpl = {"+"}
end
for i, fpl in ipairs(argsfpl) do
if fpl == "+" then
local defpl = com.make_plural(lemma, "f", args.sp)
if not defpl then
error("Unable to generate default plural of '" .. lemma .. "'")
end
fpl = defpl
else
fpl = replace_hash_with_lemma(fpl, lemma)
end
table.insert(feminine_plurals, {term = fpl, q = fetch_qualifiers(args.fpl_qual[i])})
end
check_all_missing(feminine_plurals, plpos, tracking_categories)
table.insert(data.inflections, {label = "僅陰性"})
insert_inflection(feminine_plurals, "陰性複數", "f|p")
else
-- Gather feminines.
local argsf = args.f
if #argsf == 0 then
argsf = {"+"}
end
for i, f in ipairs(argsf) do
if f == "+" then
-- Generate default feminine.
f = com.make_feminine(lemma, args.sp)
else
f = replace_hash_with_lemma(f, lemma)
end
table.insert(feminines, {term = f, q = fetch_qualifiers(args.f_qual[i])})
end
local argsmpl = args.mpl
local argsfpl = args.fpl
if #args.pl > 0 then
if #argsmpl > 0 or #argsfpl > 0 then
error("Can't specify both pl= and mpl=/fpl=")
end
argsmpl = args.pl
argsfpl = args.pl
end
if #argsmpl == 0 then
argsmpl = {"+"}
end
if #argsfpl == 0 then
argsfpl = {"+"}
end
for i, mpl in ipairs(argsmpl) do
if mpl == "+" then
-- Generate default masculine plural.
local defpl = com.make_plural(lemma, "m", args.sp)
if not defpl then
error("Unable to generate default plural of '" .. lemma .. "'")
end
mpl = defpl
else
replace_hash_with_lemma(mpl, lemma)
end
table.insert(masculine_plurals, {term = mpl, q = fetch_qualifiers(args.mpl_qual[i])})
end
for i, fpl in ipairs(argsfpl) do
if fpl == "+" then
for _, f in ipairs(feminines) do
-- Generate default feminine plural; f is a table.
local defpl = com.make_plural(f.term, "f", args.sp)
if not defpl then
error("Unable to generate default plural of '" .. f.term .. "'")
end
table.insert(feminine_plurals, {term = defpl, q = fetch_qualifiers(args.fpl_qual[i], f.q)})
end
else
fpl = replace_hash_with_lemma(fpl, lemma)
table.insert(feminine_plurals, {term = fpl, q = fetch_qualifiers(args.fpl_qual[i])})
end
end
check_all_missing(feminines, plpos, tracking_categories)
check_all_missing(masculine_plurals, plpos, tracking_categories)
check_all_missing(feminine_plurals, plpos, tracking_categories)
-- Make sure there are feminines given and not same as lemma.
if not (#feminines == 1 and feminines[1].term == lemma and not feminines[1].q) then
insert_inflection(feminines, "陰性", "f|s")
end
if #masculine_plurals > 0 and #feminine_plurals > 0 and
m_table.deepEquals(masculine_plurals, feminine_plurals) then
insert_inflection(masculine_plurals, "複數", "p")
else
insert_inflection(masculine_plurals, "陽性複數", "m|p")
insert_inflection(feminine_plurals, "陰性複數", "f|p")
end
end
if args.comp and #args.comp > 0 then
local comps = process_terms_with_qualifiers(args.comp, args.comp_qual)
check_all_missing(comps, plpos, tracking_categories)
comps.label = "比較級"
table.insert(data.inflections, comps)
end
if args.sup and #args.sup > 0 then
local sups = process_terms_with_qualifiers(args.sup, args.sup_qual)
check_all_missing(sups, plpos, tracking_categories)
sups.label = "最高級"
table.insert(data.inflections, sups)
end
if args.irreg and is_superlative then
table.insert(data.categories, langname .. "不規則形容詞最高級")
end
end
local function get_adjective_params(adjtype)
local params = {
["inv"] = {type = "boolean"}, --invariable
["sp"] = {}, -- special indicator: "first", "first-last", etc.
["f"] = {list = true}, --feminine form(s)
["f_qual"] = {list = "f\1_qual", allow_holes = true},
["pl"] = {list = true}, --plural override(s)
["pl_qual"] = {list = "pl\1_qual", allow_holes = true},
["fpl"] = {list = true}, --feminine plural override(s)
["fpl_qual"] = {list = "fpl\1_qual", allow_holes = true},
["mpl"] = {list = true}, --masculine plural override(s)
["mpl_qual"] = {list = "mpl\1_qual", allow_holes = true},
}
if adjtype == "base" or adjtype == "part" or adjtype == "det" then
params["comp"] = {list = true} --comparative(s)
params["comp_qual"] = {list = "comp\1_qual", allow_holes = true}
params["sup"] = {list = true} --superlative(s)
params["sup_qual"] = {list = "sup\1_qual", allow_holes = true}
params["fonly"] = {type = "boolean"} -- feminine only
end
if adjtype == "sup" then
params["irreg"] = {type = "boolean"}
end
return params
end
pos_functions["形容詞"] = {
params = get_adjective_params("base"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "形容詞", is_suffix)
end,
}
pos_functions["形容詞比較級"] = {
params = get_adjective_params("comp"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "形容詞", is_suffix)
end,
}
pos_functions["形容詞最高級"] = {
params = get_adjective_params("sup"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "形容詞", is_suffix, "is superlative")
end,
}
pos_functions["cardinal adjectives"] = {
params = get_adjective_params("card"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "numeral", is_suffix)
table.insert(data.categories, 1, langname .. " cardinal numbers")
end,
}
pos_functions["過去分詞"] = {
params = get_adjective_params("part"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "participle", is_suffix)
data.pos_category = "過去分詞"
end,
}
pos_functions["現在分詞"] = {
params = get_adjective_params("part"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "分詞", is_suffix)
data.pos_category = "現在分詞"
end,
}
pos_functions["限定詞"] = {
params = get_adjective_params("det"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "限定詞", is_suffix)
end,
}
pos_functions["冠詞"] = {
params = get_adjective_params("det"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "冠詞", is_suffix)
end,
}
pos_functions["adjective-like pronouns"] = {
params = get_adjective_params("pron"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adjective(args, data, tracking_categories, "代詞", is_suffix)
end,
}
pos_functions["cardinal invariable"] = {
params = {},
func = function(args, data, tracking_categories)
data.pos_category = "numerals"
table.insert(data.categories, langname .. "基數詞")
table.insert(data.categories, langname .. "無屈折數詞")
table.insert(data.inflections, {label = glossary_link("無屈折")})
end,
}
-----------------------------------------------------------------------------------------
-- Adverbs --
-----------------------------------------------------------------------------------------
local function do_adverb(args, data, tracking_categories, pos, is_suffix)
if is_suffix then
pos = "後綴"
end
local plpos = require(en_utilities_module).pluralize(pos)
if not is_suffix then
data.pos_category = plpos
end
if args.comp and #args.comp > 0 then
local comps = process_terms_with_qualifiers(args.comp, args.comp_qual)
check_all_missing(comps, "副詞", tracking_categories)
comps.label = "比較級"
table.insert(data.inflections, comps)
end
if args.sup and #args.sup > 0 then
local sups = process_terms_with_qualifiers(args.sup, args.sup_qual)
check_all_missing(sups, "副詞", tracking_categories)
sups.label = "最高級"
table.insert(data.inflections, sups)
end
end
local function get_adverb_params(advtype)
local params = {}
if advtype == "base" then
params["comp"] = {list = true} --comparative(s)
params["comp_qual"] = {list = "comp\1_qual", allow_holes = true}
params["sup"] = {list = true} --superlative(s)
params["sup_qual"] = {list = "sup\1_qual", allow_holes = true}
end
return params
end
pos_functions["副詞"] = {
params = get_adverb_params("base"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adverb(args, data, tracking_categories, "副詞", is_suffix)
end,
}
pos_functions["副詞比較級"] = {
params = get_adverb_params("comp"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adverb(args, data, tracking_categories, "副詞", is_suffix)
end,
}
pos_functions["副詞最高級"] = {
params = get_adverb_params("sup"),
func = function(args, data, tracking_categories, frame, is_suffix)
do_adverb(args, data, tracking_categories, "副詞", is_suffix)
end,
}
-----------------------------------------------------------------------------------------
-- Verbs --
-----------------------------------------------------------------------------------------
pos_functions["動詞"] = {
params = {
[1] = {},
["noautolinktext"] = {type = "boolean"},
["noautolinkverb"] = {type = "boolean"},
},
func = function(args, data, tracking_categories, frame)
if args[1] then
local preses, prets, parts
local def_forms
local alternant_multiword_spec = require(lld_verb_module).do_generate_forms(args, "from headword", data.heads[1])
local function expand_footnotes_and_references(footnotes)
if not footnotes then
return nil
end
local quals, refs
for _, qualifier in ipairs(footnotes) do
local this_footnote, this_refs =
require("Module:inflection utilities").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
table.insert(refs, ref)
end
end
else
if not quals then
quals = {this_footnote}
else
table.insert(quals, this_footnote)
end
end
end
return quals, refs
end
local function do_verb_form(slot, label, rowslot, rowlabel)
local forms = alternant_multiword_spec.forms[slot]
local retval
if alternant_multiword_spec.rowprops.all_defective[rowslot] then
if not alternant_multiword_spec.rowprops.defective[rowslot] then
-- No forms, but none expected; don't display anything
return
end
retval = {label = "no " .. rowlabel}
elseif not forms then
retval = {label = "no " .. label}
elseif alternant_multiword_spec.rowprops.all_unknown[rowslot] then
retval = {label = "unknown " .. rowlabel}
elseif forms[1].form == "?" then
retval = {label = "unknown " .. label}
else
-- Disable accelerators for now because we don't want the added accents going into the headwords.
-- FIXME: We now have support in [[Module:accel]] to specify the target explicitly; we can use this
-- so we can add the accelerators back with a param to avoid the accents.
local accel_form = nil -- all_verb_slots[slot]
retval = {label = label, accel = accel_form and {form = accel_form} or nil}
local prev_footnotes = nil
-- If the footnotes for this form are the same as the footnotes for the preceding form or
-- contain the preceding footnotes, replace the footnotes that are the same with "ditto".
-- This avoids repetition on pages like [[succedere]] where the form ''succedétti'' has a long
-- footnote which gets repeated in the traditional form ''succedètti'' (which also has the
-- footnote "[traditional]").
for _, form in ipairs(forms) do
local quals, refs = expand_footnotes_and_references(form.footnotes)
local quals_with_ditto = quals
if quals and prev_footnotes then
local quals_contains_previous = true
for _, qual in ipairs(prev_footnotes) do
if not m_table.contains(quals, qual) then
quals_contains_previous = false
break
end
end
if quals_contains_previous then
local inserted_ditto = false
quals_with_ditto = {}
for _, qual in ipairs(quals) do
if m_table.contains(prev_footnotes, qual) then
if not inserted_ditto then
table.insert(quals_with_ditto, "ditto")
inserted_ditto = true
end
else
table.insert(quals_with_ditto, qual)
end
end
end
end
prev_footnotes = quals
table.insert(retval, {term = form.form, q = quals_with_ditto, refs = refs})
end
end
table.insert(data.inflections, retval)
end
if alternant_multiword_spec.props.is_pronominal then
table.insert(data.inflections, {label = glossary_link("代詞性")})
end
if alternant_multiword_spec.props.impers then
table.insert(data.inflections, {label = glossary_link("無人稱")})
end
if alternant_multiword_spec.props.thirdonly then
table.insert(data.inflections, {label = "僅第三人稱"})
end
local thirdonly = alternant_multiword_spec.props.impers or alternant_multiword_spec.props.thirdonly
local sing_label = thirdonly and "third-person singular" or "first-person singular"
for _, rowspec in ipairs {
{"pres", "present", true},
{"phis", "past historic", true},
{"pp", "past participle", true},
{"imperf", "imperfect"},
{"fut", "future"},
{"sub", "subjunctive"},
{"impsub", "imperfect subjunctive"},
} do
local rowslot, desc, always_show = unpack(rowspec)
local slot = rowslot .. (thirdonly and "3s" or "1s")
local must_show = alternant_multiword_spec.is_irreg[slot]
if always_show then
must_show = true
elseif rowslot == "imperf" and alternant_multiword_spec.props.has_expliclld_stem_spec then
-- If there is an explicit stem spec, make sure it gets displayed; the imperfect is a good way of
-- showing this.
must_show = true
elseif not alternant_multiword_spec.forms[slot] then
-- If the principal part is unexpectedly missing, make sure we show this.
must_show = true
elseif alternant_multiword_spec.forms[slot][1].form == "?" then
-- If the principal part is unknown, make sure we show this.
must_show = true
end
if must_show then
if rowslot == "pp" then
do_verb_form(rowslot, desc, rowslot, desc)
else
do_verb_form(slot, sing_label .. " " .. desc, rowslot, desc)
end
end
end
-- Also do the imperative, but not for third-only verbs, which are always missing the imperative.
if not thirdonly and (alternant_multiword_spec.is_irreg.imp2s
or not alternant_multiword_spec.forms.imp2s) then
do_verb_form("imp2s", "second-person singular imperative", "imp", "祈使語氣")
end
-- If there is a past participle but no auxiliary (e.g. [[malfare]]), explicitly add "no auxiliary". In
-- cases where there's no past participle and no auxiliary (e.g. [[irrompere]]), we don't do this as we
-- already get "no past participle" displayed. Don't display an auxiliary in any case if the lemma
-- consists entirely of reflexive verbs (for which the auxiliary is always [[essere]]).
if alternant_multiword_spec.props.is_non_reflexive and (
alternant_multiword_spec.forms.aux or alternant_multiword_spec.forms.pp
) then
do_verb_form("aux", "auxiliary", "aux", "auxiliary")
end
-- Add categories.
for _, cat in ipairs(alternant_multiword_spec.categories) do
table.insert(data.categories, cat)
end
-- If the user didn't explicitly specify head=, or specified exactly one head (not 2+) and we were able to
-- incorporate any links in that head into the 1= specification, use the infinitive generated by
-- [[Module:lld-verb]] it in place of the user-specified or auto-generated head so that we get accents marked
-- on the verb(s). Don't do this if the user gave multiple heads or gave a head with a multiword-linked
-- verbal expression such as '[[dare esca]] [[al]] [[fuoco]]'.
if #data.user_specified_heads == 0 or (
#data.user_specified_heads == 1 and alternant_multiword_spec.incorporated_headword_head_into_lemma
) then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.inf) do
-- FIXME, can't yet specify qualifiers or references for heads
table.insert(data.heads, lemma_obj.form)
-- local quals, refs = expand_footnotes_and_references(lemma_obj.footnotes)
-- table.insert(data.heads, {term = lemma_obj.form, q = quals, refs = refs})
end
end
end
end
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["後綴變格形"] = {
params = {
[1] = {required = true, list = true},
["g"] = {list = true},
["g_qual"] = {list = "g\1_qual", allow_holes = true},
},
func = function(args, data, tracking_categories, frame)
data.genders = {}
process_genders(data, args.g, args.g_qual)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
table.insert(suffix_type, "構成" .. typ .. "的後綴")
end
table.insert(data.inflections, {label = m_table.serialCommaJoin(suffix_type, {conj = "or"})} .. "的非詞元形式")
end,
}
-----------------------------------------------------------------------------------------
-- Arbitrary parts of speech --
-----------------------------------------------------------------------------------------
pos_functions["arbitrary part of speech"] = {
params = {
[1] = {required = true},
["g"] = {list = true},
["g_qual"] = {list = "g\1_qual", allow_holes = true},
},
func = function(args, data, tracking_categories, frame, is_suffix)
if is_suffix then
error("Can't use [[Template:lld-pos]] with suffixes")
end
data.genders = {}
process_genders(data, args.g, args.g_qual)
local plpos = require(en_utilities_module).pluralize(args[1])
data.pos_category = plpos
end,
}
return export
2qxq9kdrj1b0xpce91qbijt067g7qvs
Module:Akl-pronunciation
828
3363592
9909039
9645897
2026-09-29T07:24:08Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909039
Scribunto
text/plain
-- Based on [[Module:es-pronunc]] by Benwing2.
-- Adaptation by TagaSanPedroAko, Improved by Ysrael214.
-- Partly rewritten by Benwing2, merging code from [[Module:es-pronunc]] back into this module; {{tl-pr}} restructured
-- to take inline modifiers, like {{es-pr}}
-- Modified for {{akl-pr}}.
local export = {}
local force_cat = false -- enable for testing
local m_IPA = require("Module:IPA")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local accent_qualifier_module = "Module:accent qualifier"
local audio_module = "Module:audio"
local headword_data_module = "Module:headword/data"
local hyphenation_module = "Module:hyphenation"
local labels_module = "Module:labels"
local parse_utilities_module = "Module:parse utilities"
local pron_utilities_module = "Module:pron utilities"
local rhymes_module = "Module:rhymes"
local set_utilities_module = "Module:set utilities"
local lang = require("Module:languages").getByCode("akl")
local rfind = m_str_utils.find
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local toNFC = mw.ustring.toNFC
local toNFD = mw.ustring.toNFD
local trim = mw.text.trim
local u = m_str_utils.char
local ulen = m_str_utils.len
local ulower = m_str_utils.lower
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local AC = u(0x0301) -- acute = ́
local GR = u(0x0300) -- grave = ̀
local CFLEX = u(0x0302) -- circumflex = ̂
local TILDE = u(0x0303) -- tilde = ̃
local DIA = u(0x0308) -- diaeresis = ̈
local MACRON = u(0x0304) -- macron = ̄
local DOTOVER = u(0x0307) -- dot over = ̇
local NO_RELEASE = u(0x031A) -- no release = ̚
local DENTAL = u(0x032A) -- dental = ̪
local vowel = "aeëəiou" -- vowel
local V = "[" .. vowel .. "]"
local NV = "[^" .. vowel .. "]"
local accent = AC .. GR .. CFLEX .. MACRON
local accent_c = "[" .. accent .. "]"
local ipa_stress = "ˈˌ"
local ipa_stress_c = "[" .. ipa_stress .. "]"
local separator = accent .. ipa_stress .. "# .-"
local C = "[^" .. vowel .. separator .. "]" -- consonant
local unstressed_words = m_table.listToSet {}
local unstressed_affixes = m_table.listToSet {}
local special_words = {}
local function track(page)
require("Module:debug/track")("akl-pronunciation/" .. page)
return true
end
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- Combine two sets of qualifiers, either of which may be nil or a list of qualifiers. Remove duplicate qualifiers.
-- Return value is nil or a list of qualifiers.
local function combine_qualifiers(qual1, qual2)
if not qual1 then
return qual2
end
if not qual2 then
return qual1
end
local qualifiers = m_table.deepCopy(qual1)
for _, qual in ipairs(qual2) do
m_table.insertIfNot(qualifiers, qual)
end
return qualifiers
end
local function decompose(text, recompose_e_dia)
-- decompose everything but ñ and ü
text = toNFD(text)
text = rsub(text, ".[" .. TILDE .. DIA .. "]", {
["n" .. TILDE] = "ñ",
["N" .. TILDE] = "Ñ",
["u" .. DIA] = "ü",
["U" .. DIA] = "Ü",
})
if recompose_e_dia then
text = rsub(text, ".[" .. DIA .. "]", {
["e" .. DIA] = "ë",
["E" .. DIA] = "Ë",
})
end
return text
end
local function remove_accents(str)
str = decompose(str, "recompose e-dia")
str = rsub(str, "(.)" .. accent_c, "%1")
return str
end
local function split_on_comma(term)
if term:find(",%s") then
return require(parse_utilities_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- ĵ, ɟ and ĉ are used internally to represent [d͡ʒ], [j] and [t͡ʃ]
--
function export.IPA(text, include_phonemic_syllable_boundaries)
local debug = {}
text = ulower(text)
text = decompose(text, "recompose e-dia")
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[!?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes unstressed unless they have an explicit stress marker; also make certain monosyllabic words (e.g.
-- [[ang]], [[ng]], [[si]], [[na]], etc.) without stress marks be unstressed. We want to do this in most cases as
-- well with hyphenated compounds, e.g. [[bato-sa-rinyon]] and [[kalahatian-ng-buwan]]. To do this, we use a
-- capturing split on space or hyphen; in this situation, the actual words are at odd positions, and the separators
-- (always a single space or hyphen) are at even positions.
local words = rsplit(text, "([ %-])")
local function make_unstressed(word)
-- add macron to the last vowel not the first one, in case of affixes with qui/que/gui/gue (which don't
-- currently exist)
return rsub(word, "^(.*" .. V .. ")", "%1" .. MACRON)
end
local function signal_no_initial_glottal_stop(word)
return rsub(word, "^(" .. V .. ")", "◌%1")
end
for i=1, #words do
if i % 2 == 1 then -- a word, not a hyphen or space
if words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") and
words[i + 1] ~= "-" then
-- a suffix
if unstressed_affixes["-" .. words[i]] then
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] ~= "-" then
-- a prefix
if not rfind(words[i], accent_c) then
-- an unstressed prefix
words[i] = make_unstressed(words[i])
end
elseif words[i + 1] == "-" and (not words[i + 2] or words[i + 2] == "" and words[i + 3] ~= "-") and
words[i - 1] == "-" and (not words[i - 2] or words[i - 2] == "" and words[i - 3] ~= "-") then
-- an interfix or infix
if not rfind(words[i], accent_c) then
-- an unstressed interfix or infix
words[i] = make_unstressed(words[i])
end
words[i] = signal_no_initial_glottal_stop(words[i])
else
-- a space-delimited word or a word in a hyphen-delimited compound
words[i] = special_words[words[i]] or words[i]
if unstressed_words[words[i]] then
words[i] = make_unstressed(words[i])
elseif words[i + 1] == "-" and (not words[i - 1] or words[i - 1] == " ") and
-- e.g. 'mag-' in [[mag-post]]
unstressed_affixes[words[i] .. "-"] then
words[i] = make_unstressed(words[i])
end
end
end
-- old code that I didn't port because I don't understand why it's being done; the purpose is to make suffixes
-- and infixes with explicit initial glottal stop be unstressed, which seems a weird exception
-- words[i] = rsub(words[i], "^%-([7ʔ])(" .. V .. ")", "-%1%2" .. MACRON) -- affix that requires glottal stop
end
text = table.concat(words, "")
-- Convert hyphens to spaces
text = rsub(text, "%-", " ")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- convert ending apostrophe to glottal stop
text = rsub(text, "['] ", "7 ")
text = rsub(text, "[']$", "7")
-- now eliminate punctuation
text = rsub(text, "[!?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
table.insert(debug, text)
-- handle certain combinations; ch ng and sh handling needs to go first
text = rsub(text, "([t]?)ch", "ĉ") --not the real sound
text = rsub(text, "d([jĵ])", "j")
text = rsub(text, "([n]?)g̃", "ng") -- Spanish spelling support
text = rsub(text, "ng", "ŋ")
text = rsub(text, "sh", "sy")
--x
text = rsub(text, "([#])x(" .. V .. ")", "%1s%2")
text = rsub(text, "x", "ks")
--ll
text = rsub(text, "ll([i]?)(".. V.. ")", "ly%2")
--c, gü/gu+e or i, q
text = rsub(text, "c([iey])", "s%1")
text = rsub(text, "(" .. V .. ")gü([ie])", "%1ɡw%2")
text = rsub(text, "gü([ie])", "ɡuw%1")
text = rsub(text, "gui([aeëo])", "ɡy%1")
text = rsub(text, "gu([ie])", "ɡ%1")
text = rsub(text, "qu([ie])", "k%1")
text = rsub(text, "ü", "u")
text = rsub(text, "ë", "ə")
--alphabet-to-phoneme
text = rsub(text, "[cfgjñqrvz7]",
--["g"]="ɡ": U+0067 LATIN SMALL LETTER G → U+0261 LATIN SMALL LETTER SCRIPT G
{ ["c"] = "k", ["g"] = "ɡ", ["j"] = "ĵ", ["ñ"] = "ny", ["q"] = "k", ["r"] = "ɾ", ["z"] = "s", ["7"] = "ʔ"})
-- trill in rr
text = rsub(text, "[ɾ]+", "ɾ")
text = rsub(text, "ɾ[.]ɾ", "r")
-- ts
text = rsub(text, "t_s", "ć") --not the real sound
text = rsub(text, "ts", "ĉ") --not the real sound
table.insert(debug, text)
text = rsub(text, "e([^" .. accent .. "])","ɣ%1")
text = rsub(text,"(" .. C .. ")ɣ([ˈˌ.]*)([bćĉdfɡɣhjĵklmnɲŋpɾrsʃtvwyɟzʔ#" .. vowel .. "])","%1e%2%3")
text = rsub(text,"(#)ɣ([ˈˌ.]*)([bćĉdfɡɣhjĵklmnɲŋpɾrsʃtvwyɟzʔ#" .. vowel .. "])","%1e%2%3")
text = rsub(text, "e(".. V ..")", "ɣ%1")
text = rsub_repeatedly(text, "(" .. NV .. ")([u])([" .. AC .. MACRON .. "]?)([aeio])(" .. accent_c .. "?)","%1%2%3.w%4%5")
text = rsub_repeatedly(text, "(" .. V .. ")([u])([aeio])(" .. accent_c .. "?)","%1.w%3%4")
text = rsub_repeatedly(text, "(" .. V .. ")([o])([aei])(" .. accent_c .. "?)","%1.w%3%4")
text = rsub(text, "([i])([" .. AC .. MACRON .. "])([aeou])(" .. accent_c .. "?)","%1%2.y%3%4")
text = rsub(text, "([i])([aeou])(" .. accent_c .. "?)","y%2%3")
text = rsub(text, "a([".. AC .."]*)o([#.ʔ])","a%1w%2")
--determining whether "y" is a consonant or a vowel
text = rsub(text, "y(" .. accent_c .. ")", "i%1")
text = rsub(text, "y(" .. V .. ")", "ɟ%1") -- not the real sound
text = rsub(text,"(" .. NV .. ")y([ˈˌ.]*)([bćĉdfɡɣhjĵklmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","%1i%2%3")
text = rsub(text,"(" .. V .. ")y([ˈˌ.]*)([bćĉdfɡɣhjĵklmnɲŋpɾrsʃtvwɟzʔ#" .. vowel .. "])","%1j%2%3")
text = rsub(text, "w(" .. V .. ")","w%1")
text = rsub(text,"(" .. NV .. ")w([ˈˌ]?)([bćĉdfɡɣjĵklmnɲŋpɾrsʃtvwɟzʔ#])","%1u%2%3")
-- eu rules
text = rsub_repeatedly(text, "([#])([e])([u])(" .. accent_c .. "?)","%1y%3%4")
text = rsub_repeatedly(text, "(" .. NV .. ")([e])([" .. AC .. MACRON .. "]?)([u])(" .. accent_c .. "?)","%1%2%3.%4%5")
-- Convert CV(wy)C pattern into diphthong
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)w(" .. NV .. ")([^lɾr" .. vowel .. separator .."])" ,"%1%2u%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)w([ĉdjĵlmnɲŋɾrstvwɟzʔ])([l" .. vowel .. separator .."])" ,"%1%2u%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)[jyɟ](" .. NV .. ")([^lɾr" .. vowel .. separator .."])" ,"%1%2i%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)[jyɟ]([ĉdjĵlmnɲŋɾrstvwɟzʔ])([l" .. vowel .. separator .."])" ,"%1%2i%3%4")
--vowels with grave/circumflex to vowel+glottal stop
text = rsub(text, CFLEX, AC .. GR)
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR .. "([#" .. vowel .. "])", "%1%2ʔ%3")
text = rsub(text, "(" .. V .. ")([" .. AC .. "]?)" .. GR, "%1%2")
-- Add glottal stop for words starting with vowel
text = rsub(text, "([#])(" .. V .. ")", "%1ʔ%2")
text = rsub(text, "◌", "")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^hlɾrɟ" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dkt])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfɡklmnpsʃvz])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^hlɾrɟsʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(s)([ktp])([^hlɾrɟ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeo, or stressed iu, should be syllabically divided from a following aeo or stressed iu.
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([aeo]" .. accent_c .. "*)(" .. V .. AC .. ")", "%1.%2")
text = rsub(text, "([iuə]" .. AC .. ")([aeo])", "%1.%2")
text = rsub_repeatedly(text, "([iuə]" .. AC .. ")(" .. V .. AC .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
table.insert(debug, text)
local accent_to_stress_mark = { [AC] = "ˈ", [MACRON] = "" }
local function accent_word(word, syllables)
-- Now stress the word. If any accent exists in the word (including macron indicating an unaccented word),
-- put the stress mark(s) at the beginning of the indicated syllable(s). Otherwise, apply the default
-- stress rule.
if rfind(word, accent_c) then
for i = 1, #syllables do
syllables[i] = rsub(syllables[i], "^(.*)(" .. accent_c .. ")(.*)$",
function(pre, accent, post)
return accent_to_stress_mark[accent] .. pre .. post
end
)
end
else
-- Default stress rule. Words without vowels (e.g. IPA foot boundaries) don't get stress.
if #syllables > 1 and rfind(word, "[^aeiouəʔbcćĉdfɡɣghjɟĵklmnñɲŋpqrɾsʃtvwxz#]#") or #syllables == 1 and rfind(word, V) then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
elseif #syllables >= 2 then
local vowel_find = false
local stress_find = false
for i=0, #syllables-1 do
if rfind(syllables[#syllables - i], V) then
if vowel_find then
syllables[#syllables - i] = "ˈ" .. syllables[#syllables - i]
stress_find = true
break
end
vowel_find = true
end
end
if vowel_find and not stress_find then
syllables[#syllables - 1] = "ˈ" .. syllables[#syllables - 1]
end
end
end
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
local syllables = rsplit(word, "%.")
accent_word(word, syllables)
-- Reconstruct the word.
words[j] = table.concat(syllables, ".")
end
text = table.concat(words, " ")
-- suppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
--make all primary stresses but the last one be secondary
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ")
table.insert(debug,text)
--correct final glottal stop placement
text = rsub(text,"([ˈˌ])ʔ([#]*)([ʔbĉćdfɡɣhĵɟklmnŋɲpɾrsʃtvwz])(" .. V .. ")","%1%2%3%4ʔ")
table.insert(debug, text)
--Corrections for diphthongs
text = rsub(text,"([aāeəiīouū])i","%1j") --ay
text = rsub(text,"([aāeəiīouū])u","%1w") --aw
table.insert(debug, text)
text = rsub_repeatedly(text, "^([#]*)([ˈˌ])([#]*)", "%1%3%2") -- Move stress inside word boundary fix at start
text = rsub_repeatedly(text, "([ ])([#]*)([ˈˌ])([#]*)", "%1%2%4%3") -- Move stress inside word boundary fix at start
local akl_IPA_table = {
["phonetic"] = text,
["phonemic"] = text,
["raw"] = text
}
for key, value in pairs(akl_IPA_table) do
text = akl_IPA_table[key]
--phonetic transcription
if key == "phonetic" then
table.insert(debug, text)
--remove "ɟ" and "w" inserted on vowel pair starting with "i" and "u"
text = rsub(text,"([i])([ˈˌ]?)ɟ([aāeəouū])","%1%2%3")
text = rsub(text,"([u])([ˈˌ]?)w([aāeəiī])","%1%2%3")
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub(text,"([k])([ˈˌ# .]*[ɣ])","x%2")
text = rsub(text,"([nŋ])([ˈˌ# .]*[bfpv])","m%2")
text = rsub(text,"([ŋ])([ˈˌ# .]*[dĵlstćĉz])","n%2")
text = rsub(text,"([n])([ˈ ˌ# .]*[ɡk])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
text = rsub(text,"d([ˈˌ.])ɟ","d%1ĵ") --/d/ before /j/
text = rsub(text,"d[ɟj]([aāeəiɪ̯īouʊ̯ū])","ĵ%1") --/d/ before /j/
text = rsub(text,"s([ˈˌ.])ɟ","%1ʃ") -- /s/ before /j/
text = rsub(text,"s[ɟj]([aāeəiɪ̯īouʊ̯ū])","ʃ%1") --/s/ before /j/
-- resuppress syllable mark before IPA stress indicator
text = rsub(text, "([.]?)(" .. ipa_stress_c .. ")([.]?)", "%2")
table.insert(debug, text)
--Change stresses before penultimate to have final syllable stress
text = rsub_repeatedly(text,"[ˈˌ]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?[āeəīoū])([^# ˈˌ]*)" ..
"([.][ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?[āeīoūaəiu])([^# ˈˌ]*)" ..
"[.]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?[āeīoūaəiu])([^# ˈˌ.]*)([# ])","ˌ%1%2%3%4ˈ%5%6%7")
text = rsub(text,"([ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?)([ɟlnɾstw]?)([a])","%1%2%3ā")
text = rsub(text,"([ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?)([ɟlnɾstw]?)([i])","%1%2%3ī")
text = rsub(text,"([ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?)([ɟlnɾstw]?)([u])","%1%2%3ū")
--If final syllable is stressed but so is penultimate, mark penultimate only
text = rsub_repeatedly(text,"[ˌ]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?[āeəīoū])([^# .ˈˌ]*)" ..
"([.]?[ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?)([ā])([^# ˈˌ.]*)([# ])","ˈ%1%2.%4a%6%7")
text = rsub_repeatedly(text,"[ˌ]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?[āeəīoū])([^# .ˈˌ]*)" ..
"([.]?[ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?)([eoə])([^# ˈˌ.]*)([# ])","ˈ%1%2.%4%5%6%7")
text = rsub_repeatedly(text,"[ˌ]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?[āeəīoū])([^# .ˈˌ]*)" ..
"([.]?[ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?)([ī])([^# ˈˌ.]*)([# ])","ˈ%1%2.%4i%6%7")
text = rsub_repeatedly(text,"[ˌ]([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?[āeəīoū])([^# .ˈˌ]*)" ..
"([.]?[ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?[ɟlnɾstw]?)([ū])([^# ˈˌ.]*)([# ])","ˈ%1%2.%4u%6%7")
text = rsub_repeatedly(text, "ˈ(.+)ˈ", "ˌ%1ˈ") -- Reset primary to secondary stresses if not on last word
-- Add vowel length to stressed open vowels
text = rsub_repeatedly(text,"([ˈˌ])([ʔbćĉdfɡɣhĵɟkxlmnɲŋpɾrɹsʃtwvzʒ]?)([ɟlnɾstw]?)([āeəīoū])([ˈˌ.])","%1%2%3%4ː%5")
--change a, i, u to unstressed equivalents (certain forms to restore)
text = rsub(text,"ā","a")
text = rsub(text,"ī","i")
text = rsub(text,"ū","u")
-- Aklanon vowels
text = rsub(text,"([ʔbćĉdfɡɣhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[e]([ʔbdfɡklmnŋpɾrstɟjw])([bdɡklmnpɾrst]?)([^#])","%1%2ɛ%3%4%5")
text = rsub(text,"([ʔbćĉdfɡɣhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[i]([ʔbdfɡklmnŋpɾrstɟjw])([bdɡklmnpɾrst]?)","%1%2ɪ%3%4")
text = rsub(text,"([ʔbćĉdfɡɣhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[o]([ʔbdfɡklmnŋpɾrstɟjw])([bdɡklmnpɾrst]?)([^#])","%1%2ɔ%3%4%5")
text = rsub(text,"([ʔbćĉdfɡɣhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[u]([ʔbdfɡklmnŋpɾrstɟjw])([bdɡklmnpɾrst]?)([^#])","%1%2ʊ%3%4%5")
-- text = rsub(text,"([ʔbćĉdfɡɣhĵɟklmnŋpɾrstvwz]?)([ɟlnɾstw]?)[o]([ˈˌ.])","%1%2u%3")
text = rsub(text,"([ʔćĉdfɡɣhĵɟklnŋpɾrstvwz])([ɟlnɾstw]?)[u]([ʔbdfɡklmnŋpɾrstɟjw]*)([#])","%1%2o%3%4")
text = rsub(text,"wo","ʊo")
-- Some alveolars are dental in Aklanon
-- text = rsub(text, "([td])([# ˈˌ.]*)([l])", "%1%2%3" .. DENTAL)
text = rsub(text, "([tdn])", "%1" .. DENTAL)
text = rsub(text, "([n])(" .. DENTAL .. ")([# ˈˌ.]*)([ɲ])", "%1%3%4")
text = rsub(text, "([t])(" .. DENTAL .. ")([# ˈˌ.]*)([ĉ])", "%1%3%4")
text = rsub(text, "([d])(" .. DENTAL .. ")([# ˈˌ.]*)([ĵ])", "%1%3%4")
elseif key == "phonemic" then
text = rsub(text,"([n])([ˈˌ#.]?[ɡk])","ŋ%2") -- /n/ before /k/ and /g/ (some proper nouns and loanwords)
if not include_phonemic_syllable_boundaries then
text = rsub(text,"%.","")
end
text = rsub(text,"‿", " ")
end
table.insert(debug, text)
-- convert fake symbols to real ones
local final_conversions = {
["ĉ"] = "t͡ʃ", -- fake "ch" to real "ch"
["ć"] = "t͡s", -- fake "ts" to real "ts"
["ɟ"] = "j", -- fake "y" to real "y"
["ĵ"] = "d͡ʒ" -- fake "j" to real "j"
}
text = rsub(text, "[ć]([" .. separator .. "])", "ts%1")
text = rsub(text, "[ĉćɟĵ]", final_conversions)
-- Do not have multiple syllable break consecutively
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub_repeatedly(text, "([.]?)(‿)([.]?)", "%2")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
-- resuppress syllable mark before IPA stress indicator
text = rsub(text, "%.(" .. ipa_stress_c .. ")", "%1")
text = rsub_repeatedly(text, "([.]?)(" .. ipa_stress_c .. ")([.]?)", "%2")
akl_IPA_table[key] = canon_spaces(toNFC(text))
end
return akl_IPA_table
end
local function respelling_to_IPA(data)
return ("/%s/ [%s]"):format(export.IPA(data.respelling, false).phonemic, export.IPA(data.respelling, true).phonetic)
end
function export.show(frame)
local parent_args = frame:getParent().args
return require(pron_utilities_module).format_prons {
lang = lang,
respelling_to_IPA = respelling_to_IPA,
raw_args = parent_args,
track_module = "akl-pronunciation",
}
end
local function parse_gloss(arg)
local poses, gloss
if arg:find("%^") then
poses, gloss = arg:match("^(.-)%^(.*)$")
if gloss == "" then
gloss = nil
end
else
gloss = arg
end
if poses then
poses = split_on_comma(poses)
local m_headword_data = mw.loadData(headword_data_module)
for i, pos in ipairs(poses) do
poses[i] = m_headword_data.pos_aliases[pos] or pos
end
end
return {
poses = poses,
gloss = gloss,
}
end
-- Parse a raw accent spec, which is one or more comma-separated accent qualifiers.
local function parse_accents(arg)
retval = require(labels_module).split_labels_on_comma(arg)
return retval
end
-- Return the number of syllables of a phonemic or phonetic representation, which should have syllable dividers in it
-- but no hyphens.
local function get_num_syl_from_ipa(pron)
-- Maybe we should just count vowels instead of the below code.
pron = rsub(pron, "|", " ") -- remove IPA foot boundaries
local words = rsplit(pron, " +")
for i, word in ipairs(words) do
-- IPA stress marks are syllable divisions if between characters; otherwise just remove.
word = rsub(word, "(.)[ˌˈ](.)", "%1.%2")
word = rsub(word, "[ˌˈ]", "")
words[i] = word
end
-- There should be a syllable boundary between words.
pron = table.concat(words, ".")
return ulen(rsub(pron, "[^.]", "")) + 1
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
-- NOTE: This works because the phonemic vowels are just [aeiou] possibly with diacritics that are separate
-- Unicode chars. If we want to handle things like ɛ or ɔ we need to add them to `vowel`.
phonemic = rsplit(phonemic, " ")
phonemic = phonemic[#phonemic]
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^" .. NV .. "*", ""):gsub("%.", "")
end
local function split_syllabified_spelling(spelling)
spelling = "#" .. spelling .. "#"
spelling = rsub_repeatedly(spelling, "%.([ #])", "·%1")
spelling = rsub_repeatedly(spelling, "#", "")
spelling = rsplit(spelling, "%.")
for key, value in ipairs(spelling) do
spelling[key] = rsub_repeatedly(value, "·", ".")
end
return spelling
end
-- "Align" syllabified respelling `syllab` to original spelling `spelling` by matching character-by-character, allowing
-- for extra syllable and accent markers in the syllabification and certain mismatches in the consonants. The goal is to
-- produce the appropriately syllabified version of the original spelling (the pagename) by matching characters in the
-- syllabified respelling to the original spelling, putting the syllable boundaries in the appropriate places in the
-- original spelling. As an example, given syllabified respelling 'a.ma.7ín' and original spelling 'amain', we would
-- like to produce 'a.ma.in'.
--
-- If we encounter an extra syllable marker (.), we allow and keep it. If we encounter an extra accent marker in thes
-- syllabification, we drop it. We allow for mismatches in capitalization and for certain other mismatches, e.g. extra
-- glottal stops (written 7), h in respelling vs. g or j in the original, etc. If we can't match, we return nil
-- indicating the alignment failed.
local function align_syllabification_to_spelling(syllab, spelling)
local result = {}
local function concat_result()
-- Postprocess to remove dots (syllable boundaries) next to hyphens.
return (toNFC(table.concat(result)):gsub("%.%-", "-"):gsub("%-%.", "-"))
end
-- Remove glottal stop (7) from respelling to simplify the code below, because it's never found in the original
-- spelling. (FIXME: We should do the same for diacritics, but they're currently removed earlier, in
-- syllabify_from_spelling(). We should probably get rid of the removal there and put it here.)
syllab = decompose(syllab:gsub("ː", ""), "recompose e-dia"):gsub("7", "")
spelling = decompose(spelling, "recompose e-dia")
local syll_chars = rsplit(ulower(syllab), "")
local spelling_chars = rsplit(spelling, "")
local i = 1
local j = 1
local function matches(uci, ucj)
-- Return true if a syllabified respelling character (uci) matches the corresponding spelling char (ucj).
-- Both uci and ucj should be lowercase.
-- Sound is at the key, values are the letters sound can match
local matching_chars = {
["e"] = {"i"},
["ë"] = {"a", "e", "o", "u"},
["ɣ"] = {"e", "l"},
["h"] = {"g", "j", "x"},
["i"] = {"e"},
["j"] = {"g"},
["k"] = {"j"},
["o"] = {"u"},
["s"] = {"j", "c", "x"},
["u"] = {"o"},
["w"] = {"u", "o"},
["y"] = {"i"}
}
return uci == ucj or (matching_chars[uci] and m_table.contains(matching_chars[uci], ucj) and true) or false
end
local function silent_spelling_letter(ucj)
return ucj == "h" or ucj == "'" or ucj == "-"
end
local function syll_at(pos)
return syll_chars[pos] or ""
end
local function spell_at(pos)
return spelling_chars[pos] or ""
end
local function uspell_at(pos)
local c = spelling_chars[pos]
return c and ulower(c) or ""
end
while i <= #syll_chars or j <= #spelling_chars do
local uci = syll_at(i)
local cj = spell_at(j)
local ucj = uspell_at(j)
if uci == "g" and syll_at(i - 1) == "n" and syll_at(i + 1) == "." and matches(syll_at(i + 2), ucj) and
not matches(syll_at(i + 2), uspell_at(j + 1)) then
-- As a special case, before checking whether the corresponding characters match, we have to skip an extra
-- g in an -ng- sequence in the syllabified respelling if the corresponding spelling character matches the
-- next respelling character (taking into account the syllable boundary). This is so that e.g.
-- syll='ba.rang.gay' matches spelling='barangay'. Otherwise we will match the first respelling g against
-- the spelling g and the second respelling g won't match. A similar case occurs with
-- syll='E.vang.he.lis.ta' and spelling='Evangelista'. But we need an extra condition to not do this hack
-- when syll='ba.rang.gay' matches spelling='baranggay'.
i = i + 1
elseif uci == "g" and ucj == "g" and uspell_at(j + 1) == TILDE then
table.insert(result, cj)
table.insert(result, uspell_at(j + 1))
i = i + 1
j = j + 2
elseif matches(uci, ucj) then
table.insert(result, cj)
i = i + 1
j = j + 1
elseif ucj == uspell_at(j - 1) and uci == "." and ucj ~= syll_at(i + 1) then
-- See below. We want to allow for a doubled letter in spelling that is pronounced single, and preserve the
-- doubled letter. But it's tricky in the presence of syllable boundaries on both sides of the doubled
-- letter as well as doubled letters pronounced double. Specifically, there are three possibilities,
-- exemplified by:
-- (1) syll='Mal.lig', spelling='Mallig' -> 'Mal.lig';
-- (2) syll='Ma.lig', spelling='Mallig' -> 'Ma.llig';
-- (3) syll='Wil.iam', spelling='William' -> 'Will.iam'.
-- If we copy the dot first, we get (1) and (2) right but not (3).
-- If we copy the double letter first, we get (2) and (3) right but not (1).
-- We choose to copy the dot first except in the situation exemplified by (3), where we copy the doubled
-- letter first. The condition above handles (3) (the doubled letter matches against a dot) while not
-- interfering with (1) (where the doubled letter also matches against a dot but the next letter in the
-- syllabification is the same as the doubled letter, because the doubled letter is pronounced double).
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) and uci == "." and ucj ~= syll_at(i + 1) and
not rfind(uspell_at(j + 1), V) then
-- See below for silent h or apostrophe in spelling. This condition is parallel to the one directly above
-- for silent doubled letters in spelling and handles the case of syllab='Abduramán', spelling='Abdurahman',
-- which should be syllabified 'Ab.du.rah.man'. But we need a check to see that the next spelling character
-- isn't a vowel, because in that case we want the silent letter to go after the period, e.g.
-- syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah' (the 7 is removed above).
table.insert(result, cj)
j = j + 1
elseif uci == "." then
table.insert(result, uci)
i = i + 1
elseif ucj == uspell_at(j - 1) then
-- A doubled letter in spelling that is pronounced single. Examples:
-- * syllab='Ma.líg', spelling='Mallig' -> 'Ma.llig' (with l)
-- * syllab='Lu.il.yér', spelling='Lhuillier' -> 'Lhu.ill.ier' (with l; a more complex example)
-- * syllab='a.sa.la.mu a.lai.kum', spelling='assalamu alaikum' -> 'as.sa.la.mu a.lai.kum' (with s)
-- * syllab='Jé.fer.son', spelling='Jefferson' -> 'Je.ffer.son' (with f)
-- * syllab='Je.ma', spelling='Gemma' -> 'Ge.mma' (with m)
-- * syllab='Ha.na', spelling='Hannah' -> 'Ha.nnah' (with n)
-- * syllab='A.by', spelling='Abby' -> 'A.bby' (with b)
-- * syllab='Ka.ba', spelling='Kaaba' -> 'Kaa.ba' (with a)
-- * syllab='Fu.ji', spelling='Fujii' -> 'Fu.jii' (with i)
table.insert(result, cj)
j = j + 1
elseif silent_spelling_letter(ucj) then
-- A silent h, apostrophe or hyphen in spelling. Examples:
-- * syllab='adán', spelling='adhan' -> 'a.dhan'
-- * syllab='Atanasya', spelling='Athanasia' -> 'A.tha.nas.ia'
-- * syllab='Cýntiya', spelling='Cynthia' -> 'Cyn.thi.a'
-- * syllab='Ermóhenes', spelling='Hermogenes' -> 'Her.mo.ge.nes'
-- * syllab='Abduramán', spelling='Abdurahman' -> 'Ab.du.rah.man'
-- * syllab='Jumu7á', spelling='Jumu'ah' -> 'Ju.mu.'ah'
-- * syllab='pag7ibig', spelling='pag-ibig' -> 'pag-i.big'
table.insert(result, cj)
j = j + 1
elseif uci == AC or uci == GR or uci == CFLEX or uci == DIA or uci == TILDE or uci == MACRON or
uci == "y" or uci == "w" then
-- skip character
i = i + 1
else
-- non-matching character
mw.log(("Syllabification alignment mismatch for pagename '%s' (position %s, character %s), syllabified respelling '%s' (position %s, character %s), aligned result so far '%s'"
):format(spelling, j, ucj, syllab, i, uci, concat_result()))
return nil
end
end
if i <= #syll_chars or j <= #spelling_chars then
-- left-over characters on one side or the other
mw.log(("Syllabification alignment mismatch for pagename '%s' (%s), syllabified respelling '%s' (%s), aligned result so far '%s'"
):format(
spelling, j > #spelling_chars and "end of string" or ("position %s, character %s"):format(j, uspell_at(j)),
syllab, i > #syll_chars and "end of string" or ("position %s, character %s"):format(i, syll_at(i)),
concat_result()))
return nil
end
return concat_result()
end
local function generate_syll_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
word = ulower(word)
return rfind(word, V) or word:find("y")
end
local function any_words_have_vowels(term)
local words = rsplit(decompose(term), "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word_has_vowels(word) then
return true
end
end
return false
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(decompose(term), " +")
local last_word = words[#words]
local should_generate_cat = #words == 1
local should_generate_rhyme =
not last_word:find("%-$") and -- no if word is a prefix
not (last_word:find("^%-") and last_word:find(MACRON)) and -- no if word is an unstressed suffix
word_has_vowels(last_word) -- no if word has no vowels (e.g. a single letter)
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_ipa(ipa)
local should_generate_cat = not ipa:find("%s")
local should_generate_rhyme = word_has_vowels(decompose(ipa))
return should_generate_rhyme, should_generate_cat
end
local function should_generate_rhyme_from_termobj(termobj)
if termobj.raw then
return should_generate_rhyme_from_ipa(termobj.raw_phonemic or termobj.raw_phonetic)
else
return should_generate_rhyme_from_respelling(termobj.term)
end
end
local function process_specified_rhymes(rhymes, sylls, parsed_respellings)
local rhyme_ret = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the syllabification.
if not num_syl then
num_syl = {}
for _, syll in ipairs(sylls) do
if should_generate_rhyme_from_respelling(syll.syllabification) then
local this_num_syl = 1 + ulen(rsub(syll.syllabification, "[^.]", ""))
m_table.insertIfNot(num_syl, this_num_syl)
else
no_num_syl = true
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
-- If that fails and term is single-word, try to take it from the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
for _, pronun in ipairs(parsed.pronuns) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given), and rhyme
-- isn't suppressed (which may happen if the term has a qualifier "colloquial", "obsolete" or the
-- like or is an auto-generated "glottal stop elision" pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
if not should_generate_rhyme_from_ipa(pronun.phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl_from_ipa(pronun.phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl then
break
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
local rhymeobj = m_table.shallowCopy(rhyme)
rhymeobj.num_syl = num_syl
table.insert(rhyme_ret, rhymeobj)
end
end
-- Parse a pronunciation modifier in `arg`, the argument portion in an inline modifier (after the prefix), which
-- specifies a pronunciation property such as rhyme, syllabification, homophones or audio. The argument can itself have
-- inline modifiers, e.g. <audio:Foo.ogg<a:Colombia>>. The allowed inline modifiers are specified by `param_mods` (of
-- the format expected by `parse_inline_modifiers()`); in addition to any modifiers specified there, the modifiers
-- <q:...>, <qq:...>, <a:...> and <aa:...> are always accepted (and can be repeated). `generate_obj` and `parse_err` are
-- like in `parse_inline_modifiers()` and specify respectively a function to generate the object into which modifier
-- properties are stored given the non-modifier part of the argument, and a function to generate an error message (given
-- the message). Normally, a comma-separated list of pronunciation properties is accepted and parsed, where each element
-- in the list can have its own inline modifiers and where no spaces are allowed next to the commas in order for them to
-- be recognized as separators. If `no_split_on_comma` is given, only a single pronunciation property is accepted. If
-- `has_outer_container` is given, the list of pronunciation properties is embedded in the `terms` property of an outer
-- container, into which other list-level modifiers can also be stored (by setting `overall = "true"` in the respective
-- spec in `param_mods`). The return value is a list if neither `no_split_on_comma` nor `has_outer_container` are given,
-- otherwise a container object (which, in the case of `has_outer_container`, will contain a list inside of it, in the
-- `terms` property).
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma, has_outer_container)
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(parse_utilities_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
outer_container = has_outer_container and {} or nil,
})
elseif no_split_on_comma then
return generate_obj(arg)
else
local retval = {}
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
if has_outer_container then
retval = {
terms = retval,
}
end
return retval
end
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
type = "number",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_syll(arg, parse_err)
local param_mods = {
cap = { overall = true},
}
-- We need to pass in has_outer_container because we have an overall property <cap:...> (the caption, defaulting
-- to "Syllabification") applying to the whole set of syllabifications.
return parse_pron_modifier(arg, parse_err, generate_syll_obj, param_mods, nil, "has outer container")
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the gloss in "gloss".
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- [[Module:links]] (called from [[Module:homophones]]) expects the genders in "genders".
item_dest = "genders",
sublist = true,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space (typically followed by
-- an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
local function syllabify_from_spelling(text, pagename)
-- Auto syllabifications start --
local vowel = vowel .. "ẃý" -- vowel
local V = "[" .. vowel .. "]"
local NV = "[^" .. vowel .. "]"
local C = "[^" .. vowel .. separator .."]" -- consonant
text = decompose(text, "recompose e-dia")
local origtext = remove_accents(text)
text = string.lower(text)
text = rsub(text, "[.] ", "․ ")
text = rsub(text, "[.]$", "․")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
text = rsub_repeatedly(text, "([.]?)#([.]?)", "#")
text = rsub(text, "ng̃", "ŋ")
text = rsub(text, "ng", "ŋ")
text = rsub(text, "g̃", "ġ")
text = rsub(text, "ch", "ĉ")
text = rsub(text, "t_s", "ć")
text = rsub(text, "sh", "ʃ")
text = rsub(text, "gu([eëiy])", "ǵ%1")
text = rsub(text, "qu([eëiy])", "ḱ%1")
text = rsub(text, "r", "ɾ")
text = rsub(text, "ɾɾ", "r")
text = rsub(text, "ʔ", "7")
text = rsub_repeatedly(text, "#(" .. C .. "+)u([aeio])","#%1u.%2")
text = rsub_repeatedly(text, "#(" .. C .. "+)i([aeou])","#%1i.%2")
text = rsub_repeatedly(text, "(" .. C .. ")u([aeio])","#%1.u%2")
text = rsub_repeatedly(text, "(" .. C .. ")i([aeou])","#%1.i%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)u([aeio])","%1.u%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)o([aei])","%1.ó%2")
text = rsub(text, "a(" .. accent_c .. "*)o([#.7])","a%1ó%2")
text = rsub(text, "e([^" .. accent .. "])","ɣ%1")
text = rsub(text, "(" .. C .. ")ɣ([ˈˌ." .. accent .. "]*)([bćĉdfgǵɣhjĵkḱlmnɲŋpɾrsʃtvwyɟzʔ#" .. vowel .. "])","%1e%2%3")
text = rsub(text, "(#)ɣ([ˈˌ." .. accent .. "]*)([bćĉdfgǵɣhjĵkḱlmnɲŋpɾrsʃtvwyɟzʔ#" .. vowel .. "])","%1e%2%3")
text = rsub(text, "e(" .. V .. ")", "ɣ%1")
text = rsub(text, "y([ˈˌ." .. accent .. "]*)([bćĉdfgǵɣhjĵkḱlmnɲŋpɾrsʃtvwyɟzʔ#" .. vowel .. "])","ý%1%2")
text = rsub(text, "ý(" .. V .. ")", "y%1")
text = rsub(text, "w([ˈˌ]?)([bćĉdfgǵɣjĵkḱlmnɲŋpɾrsʃtvwyɟzʔ#" .. vowel .. "])","ẃ%1%2")
text = rsub(text, "ẃ(" .. V .. ")","w%1")
-- -- eu rules
-- text = rsub_repeatedly(text, "([^" .. vowel .. "])([e])(" .. accent_c .. "?)([u])(" .. accent_c .. "?)","%1%2%3.%4%5")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ẃ([bdfgǵkḱpt])([ɾr" .. vowel .. separator .."])" ,"%1%2w%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ẃ([bfgǵkḱp])([l" .. vowel .. separator .."])" ,"%1%2w%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ý([bdfgǵkḱpt])([ɾr" .. vowel .. separator .."])" ,"%1%2y%3%4")
text = rsub(text, "(" .. V .. ")(" .. accent_c .. "?)ý([bfgǵkḱp])([l" .. vowel .. separator .."])" ,"%1%2y%3%4")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*)(" .. C .. V .. ")", "%1.%2")
-- "mb", "mp", "nd", "nk", "nt" combinations
text = rsub_repeatedly(text, "(m)([bp])([^lɾrɟy" .. vowel .. separator .."])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([dk])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([s])([^ɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(n)([t])([^lɾrɟys" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(ŋ)([k])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([bćĉdfgǵkḱlmnpsʃvz])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "([ɾr])([t])([sz]?)([^lɾrɟysʃ" .. vowel .. separator .. "])(" .. V .. ")", "%1%2%3.%4%5")
text = rsub_repeatedly(text, "(s)([ktp])([^lɾrɟy" .. vowel .. separator .. "])(" .. V .. ")", "%1%2.%3%4")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. ")(" .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. V .. accent_c .. "*" .. C .. "+)(" .. C .. C .. V .. ")", "%1.%2")
text = rsub_repeatedly(text, "(" .. C .. ")%.s(" .. C .. ")", "%1s.%2")
-- Any aeëo, or stressed iu, should be syllabically divided from a following aeëo or stressed iu.
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([aeëo]" .. accent_c .. "*)(" .. V .. accent_c .. ")", "%1.%2")
text = rsub(text, "([iu]" .. accent_c .. ")([aeëo])", "%1.%2")
text = rsub_repeatedly(text, "([iu]" .. accent_c .. ")(" .. V .. accent_c .. ")", "%1.%2")
text = rsub_repeatedly(text, "i(" .. accent_c .. "*)i", "i%1.i")
text = rsub_repeatedly(text, "u(" .. accent_c .. "*)u", "u%1.u")
text = rsub(text, "ĉ", "ch")
text = rsub(text, "ć", "ts")
text = rsub(text, "ŋ", "ng")
text = rsub(text, "ʃ", "sh")
text = rsub(text, "ǵ", "gu")
text = rsub(text, "ġ", "g̃")
text = rsub(text, "ḱ", "qu")
text = rsub(text, "r", "rr")
text = rsub(text, "ɾ", "r")
text = remove_accents(text)
text = rsub_repeatedly(text, "([.]+)", ".")
text = rsub(text, "[.]?-[.]?", "-")
text = rsub(text, "[‿]([^ ])", "|%1")
text = rsub(text, "[.]([^ ])", "|%1")
text = rsub(text, "([|])+", "%1")
-- remove # symbols at word and text boundaries
text = rsub_repeatedly(text, "([.]?)#([.]?)", "")
text = rsub(text, "․", ".")
-- Fix Capitalization --
local syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" and origtext:sub(i-syllbreak, i-syllbreak) ~= "." and origtext:sub(i-syllbreak, i-syllbreak) ~= "7" then
syllbreak = syllbreak + 1
elseif origtext:sub(i-syllbreak, i-syllbreak) == text:sub(i,i):upper() then
text = table.concat({text:sub(1, i-1), text:sub(i,i):upper(), text:sub(i+1)})
end
end
-- Fix hyphens --
-- FIXME!!! Why are we relying on looking at the pagename here? This should not be happening.
origtext = pagename
if (table.concat(rsplit(origtext, "-")) == table.concat(rsplit(table.concat(rsplit(text, "|")), "-"))) then
syllbreak = 0
for i=1, #text do
if text:sub(i,i) == "|" then
if origtext:sub(i-syllbreak, i-syllbreak) == "-" then
text = table.concat({text:sub(1, i-1), "-", text:sub(i+1)})
else
syllbreak = syllbreak + 1
end
end
end
end
-- FIXME! Hack -- up above we changed periods to vertical bars. The rest of the code expects periods so change
-- them back. We should clean up the code above to leave the periods alone.
return (text:gsub("|", "%."))
end
function export.syllabify_and_align(respelling, pagename)
local syllabification = syllabify_from_spelling(respelling, pagename)
return align_syllabification_to_spelling(syllabification, pagename)
end
local function css_wrap(text, classes)
return ('<span class="%s">%s</span>'):format(classes, text)
end
local function format_glosses(glosses)
if not glosses then
return ""
end
local formatted_glosses = {}
for _, glossobj in ipairs(glosses) do
local gloss_parts = {}
if glossobj.gloss then
table.insert(gloss_parts, css_wrap("“", "mention-gloss-double-quote") ..
css_wrap(glossobj.gloss, "mention-gloss") .. css_wrap("”", "mention-gloss-double-quote"))
end
if glossobj.poses then
for _, pos in ipairs(glossobj.poses) do
table.insert(gloss_parts, css_wrap(pos, "ann-pos"))
end
end
table.insert(formatted_glosses, table.concat(gloss_parts, css_wrap(",", "mention-gloss-comma") .. " "))
end
return " " .. css_wrap("(", "mention-gloss-paren annotation-paren") ..
table.concat(formatted_glosses, css_wrap(";", "mention-gloss-semicolon") .. " ") ..
css_wrap(")", "mention-gloss-paren annotation-paren")
end
local function format_pronuns(pronuns)
local pronunciations = {}
-- Loop through each pronunciation. For each one, add the phonemic and phonetic versions to `pronunciations`,
-- for formatting by [[Module:IPA]].
for j, pronun in ipairs(pronuns) do
local qs = pronun.q
local first_pronun = #pronunciations + 1
if not pronun.phonemic and not pronun.phonetic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then -- missing if 'raw:[...]' given
-- don't display syllable division markers in phonemic
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
end
if pronun.phonetic then -- missing if 'raw:/.../' given
local bracket_pron = "[" .. pronun.phonetic .. "]"
table.insert(pronunciations, {
pron = bracket_pron,
})
end
local last_pronun = #pronunciations
if pronun.q then
pronunciations[first_pronun].q = pronun.q
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
end
end
return m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" }
end
local function format_pronun_line(parsed)
local formatted_pronuns = format_pronuns(parsed.pronuns)
local pre = is_first and parsed.pre and parsed.pre .. " " or ""
local post = is_first and parsed.post and " " .. parsed.post or ""
return pre .. formatted_pronuns .. format_glosses(parsed.t) .. post
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic, raw_phonetic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
raw_phonetic = raw_respelling:match("^%[(.*)%]$")
end
if not raw_phonemic and not raw_phonetic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
raw_phonetic = raw_phonetic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- External entry point for {{akl-pr}}.
function export.show_full(frame)
--------------------------------- 1. Parse the arguments. ------------------------------------
local params = {
[1] = {list = true},
["rhyme"] = {},
["syll"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "=" .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_syll = args.syll and
parse_syll(args.syll, function(msg) overall_parse_err(msg, "syll", args.syll) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if #args.audio > 0 then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
table.insert(overall_audio, parsed_audio)
end
end
-- Parse each respelling. Individual arguments in 1=, 2=, etc. can consist of one or more comma-separated
-- respellings, each of which can have inline modifiers <q:...>, <qq:...>, <a:...>, <aa:...> or <ref:...>.
-- In addition, the respellings as a whole of a given argument can be followed by various inline modifiers,
-- such as <t:...>, <rhyme:...>, <syll:...>, etc. The result of parsing goes into `parsed_respellings`, which
-- is a list of objects (one per numbered argument), each of which is a table of the form
--
-- {
-- terms = {TERM, TERM, ...},
-- audio = {AUDIO, AUDIO, ...},
-- rhyme = {RHYME, RHYME, ...},
-- syll = {SYLL, SYLL, ...},
-- hmp = {HMP, HMP, ...},
-- t = {GLOSS, GLOSS, ...},
-- pre = "PRE-TEXT" or nil,
-- post = "POST-TEXT" or nil,
-- bullets = NUM_BULLETS,
-- accents = {"ACCENT", "ACCENT", ...},
-- }
--
-- In this structure, TERM is an object that usually has the form
--
-- {
-- term = "RESPELLING",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- Note that in this structure, "REF-SPEC" of the form parsable by parse_references() in [[Module:references]].
--
-- Alternatively, if phonemic or phonetic IPA is given in place of a respelling, TERM will have the form
--
-- {
-- raw = true,
-- phonemic = "PHONEMIC",
-- phonetic = "PHONETIC",
-- ref = {"REF-SPEC", "REF-SPEC", ...},
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- }
--
-- AUDIO is a table of the form
--
-- {
-- file = "FILE",
-- gloss = "GLOSS",
-- q = {"QUALIFIER", "QUALIFIER", ...},
-- qq = {"QUALIFIER", "QUALIFIER", ...},
-- a = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- aa = {"ACCENT-QUALIFIER", "ACCENT-QUALIFIER", ...},
-- }
--
-- RHYME is a table of the form
--
-- {
-- rhyme = "RHYME",
-- num_syl = {NUM_SYL, NUM_SYL, ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- SYLL is a table of the form (where `hyph` is required to be named this way for [[Module:hyphenation]])
--
-- {
-- syllabification = "SYL.LAB.LES",
-- hyph = {"SYL", "LAB", "LES"},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- HMP is a table of the form
--
-- {
-- term = "HOMOPHONE",
-- gloss = "GLOSS" or nil,
-- pos = "POS" or nil,
-- alt = "ALT" or nil,
-- lit = "LIT" or nil,
-- id = "ID" or nil,
-- g = {"G", "G", ...},
-- q, qq, a, aa = (as for AUDIO),
-- }
--
-- GLOSS is a table of the form
--
-- {
-- poses = {"POS", "POS", ...} or nil,
-- gloss = "GLOSS" or nil,
-- }
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
type = "number",
},
t = {
overall = true,
store = "insert",
convert = parse_gloss,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
syll = {
overall = true,
-- Not `store = "insert-flattened"`. parse_syll() does not generates a list but a structure where
-- the syllabifications are in `terms` and there's an additional overall property `cap` for the
-- caption (defaulting to "Syllabification"). FIXME: Rethink whether we even want "insert-flattened"
-- or just "insert" for the remaining pronunciation properties.
convert = parse_syll,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert", -- not "insert-flattened" because parse_audio returns a single object
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = {
item_dest = "accents",
overall = true,
convert = parse_accents,
},
}
local parsed = require(parse_utilities_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
pre_normalize_modifiers = function(data)
local modtext = data.modtext
modtext = modtext:match("^<(.*)>$")
if not modtext then
error(("Internal error: Passed-in modifier isn't surrounded by angle brackets: %s"):format(
data.modtext))
end
if modtext:find("%^") and not modtext:find("^t:") then
modtext = "t:" .. modtext
end
return "<" .. modtext .. ">"
end,
splitchar = ",",
outer_container = {},
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "=" .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
bullets = 1,
})
end
end
--------------------------------- 2. Generate IPA, rhymes and syllabification. ------------------------------------
-- Used for categorization below.
local syllabification_alignment_failed = false
-- Canonicalize syllabifications in `sylls` by convering '+' to the default syllabification of the pagename, '#' to
-- the pagename itself, and '-' to no syllabification (return `null_syll`). If '-' not seen, return `sylls`.
local function canonicalize_syllabification(sylls, null_syll)
for _, syll in ipairs(sylls.terms) do
if syll.syllabification == "+" then
syll.syllabification = syllabify_from_spelling(pagename, pagename)
syll.hyph = split_syllabified_spelling(syll.syllabification)
elseif syll.syllabification == "#" then
syll.syllabification = pagename
syll.hyph = {syll.syllabification}
elseif syll.syllabification == "-" then
return null_syll
end
end
return sylls
end
if overall_syll then
overall_syll = canonicalize_syllabification(overall_syll, {})
end
local function doesnt_count_for_rhyme(list)
if not list then
return false
end
local accent_no_count = {"colloquial", "obsolete", "relaxed"}
for _, item in ipairs(list) do
for _, word_no_count in ipairs(accent_no_count) do
if item:find("%f[%w]" .. word_no_count .. "%f[%W]") then
return true
end
end
end
return false
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
-- First, sort the specified accents and default to "Standard Aklanon".
default_accent = "Standard Aklanon"
if not parsed.accents then
-- parsed.accents = {default_accent}
parsed.accents = {""}
end
-- If more than one respelling given, then if any accent or qualifier has the words 'colloquial', 'obsolete' or
-- 'relaxed' in them, don't generate a rhyme or a '#-syllable word' category.
local more_than_one_respelling = #parsed.terms > 1 or #parsed_respellings > 1
local is_standard = m_table.contains(parsed.accents, "Standard Aklanon") and true or false
local all_terms_no_rhyme = more_than_one_respelling and doesnt_count_for_rhyme(parsed.accents)
parsed.pronuns = {}
for i, term in ipairs(parsed.terms) do
local phonemic, phonetic
if term.raw then
phonemic = term.raw_phonemic
phonetic = term.raw_phonetic
else
local ret = export.IPA(term.term, "include phonemic syllable boundaries")
phonemic = ret.phonemic
phonetic = ret.phonetic
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
local no_rhyme, rhyme_with_cat
-- Same check as above for colloquial/obsolete/relaxed but check the qualifiers, which are attached to
-- individual respellings rather than a single-line set of respellings.
no_rhyme = all_terms_no_rhyme or more_than_one_respelling and (
doesnt_count_for_rhyme(term.q) or doesnt_count_for_rhyme(term.qq)
)
if not no_rhyme then
local should_generate_rhyme, should_generate_cat = should_generate_rhyme_from_termobj(term)
no_rhyme = not should_generate_rhyme
rhyme_with_cat = should_generate_cat
end
local pronobj = {
raw = term.raw,
phonemic = phonemic,
phonetic = phonetic,
refs = refs,
q = term.q,
qq = term.qq,
no_rhyme = no_rhyme,
rhyme_with_cat = rhyme_with_cat,
}
table.insert(parsed.pronuns, pronobj)
local function generate_pron_variant(options)
local ref_pron_obj = options["ref_pron_obj"] or pronobj
local new_pronobj = {
raw = ref_pron_obj.raw,
phonemic = options["phonemic"] or ref_pron_obj.phonemic,
phonetic = options["phonetic"] or ref_pron_obj.phonetic,
raw_pron = options["raw_pron"] or ref_pron_obj.raw_pron,
refs = ref_pron_obj.refs,
q = combine_qualifiers(ref_pron_obj.q, options["q"]),
qq = combine_qualifiers(ref_pron_obj.qq, options["qq"]),
no_rhyme = options["no_rhyme"] or ref_pron_obj.no_rhyme,
rhyme_with_cat = ref_pron_obj.rhyme_with_cat,
move_to_next_line = options["move_to_next_line"] or false
}
if options["accents"] then
local new_pron_obj_base = m_table.deepCopy(parsed)
new_pron_obj_base.accents = options["accents"]
new_pron_obj_base.pronuns = {}
table.insert(new_pron_obj_base.pronuns, new_pronobj)
return new_pron_obj_base
else
return new_pronobj
end
end
-- If [fvz] present in phonemic pronunciation, generate a "more native-sounding" variant with [pbs] in
-- place.
local fvz_pronobj
if tonumber(term.no_nativepron) ~= 1 and pronobj.phonemic:find("[fvz]") then
local fvz_charmap = { ["f"] = "p", ["v"] = "b", ["z"] = "s"}
fvz_pronobj = generate_pron_variant({
["phonemic"] = pronobj.phonemic:gsub("[fvz]", fvz_charmap),
["phonetic"] = pronobj.phonetic:gsub("[fvz]", fvz_charmap):gsub("([ɐaeɛəɪioʊu])([pbtdkɡ])$", "%1%2" .. NO_RELEASE):gsub("([ɐaeɛəɪioʊu])([pbtdkɡ]) ([|])", "%1%2" .. NO_RELEASE .. " %3"),
["raw_pron"] = pronobj.raw_pron:gsub("[fvz]", fvz_charmap),
["q"] = {"with nativization"},
["move_to_next_line"] = true
})
table.insert(parsed.pronuns, fvz_pronobj)
end
end
if not parsed.syll then
if not overall_syll and (any_words_have_vowels(pagename)) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify_from_spelling(term.term, pagename)
local aligned_syll = align_syllabification_to_spelling(syllabification, pagename)
if aligned_syll then
if not parsed.syll then
parsed.syll = {terms = {}}
end
m_table.insertIfNot(parsed.syll.terms, generate_syll_obj(aligned_syll))
else
if lang:findBestScript(pagename):getCode() == "Latn" or
lang:findBestScript(syllabification):getCode() == lang:findBestScript(pagename) then
syllabification_alignment_failed = true
end
end
end
end
end
else
parsed.syll = canonicalize_syllabification(parsed.syll, nil)
end
if not parsed.rhyme then
if overall_rhyme then
parsed.rhyme = nil
else
-- Generate the rhymes.
for _, pronun in ipairs(parsed.pronuns) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation
-- (see `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if
-- raw phonetic-only pronun is given), and rhyme isn't suppressed (which may happen if the term has
-- a qualifier "colloquial", "obsolete" or the like or is an auto-generated "glottal stop elision"
-- pronunciation).
if pronun.phonemic and not pronun.no_rhyme then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
-- Copying qualifiers to rhymes:
-- (1) If there's only one pronunciation, displaying any associated qualifier on the rhyme is
-- is redundant, so don't do it.
-- (2) If there are multiple pronunciations, then we generally do want to copy the qualifier(s)
-- from pronunciation to rhyme, but only if a given rhyme either derives from a single
-- pronunciation, or derives from multiple pronunciations all of which share the same
-- qualifier(s). We do NOT want to combine two different qualifiers from two different
-- pronunciations.
-- (3) If there are multiple pronunciations that map to a single rhyme, and all pronunciations
-- share qualifiers, then we might consider omitting the qualifiers as redundant; but this
-- case will rarely happen so it might not be worth worrying about.
-- (4) Similarly, if there are multiple pronunciations where some have the rhyme suppressed (see
-- above), and all pronunciations share qualifiers, then we might consider omitting the
-- qualifiers as redundant; but again, this case will rarely happen (especially since in
-- almost all cases the suppressed-rhyme pronunciation will have distinctive qualifiers) so
-- it probably isn't worth worrying about. Note that in the common case where the qualifiers
-- of the rhyme-suppressed pronunciation differ from those of the rhyme-included
-- pronunciation, we do want to include the qualifiers of the rhyme-included pronunciation
-- (imagine e.g. there are two pronunciations marked "standard" and "colloquial"; we want to
-- mark the rhyme as "standard").
-- (4) There are two different types of qualifiers (left and right); when comparing qualifiers,
-- we need to compare the entire set of both qualifiers and make sure they both match
-- (although it will be rare to have both left and right qualifiers on a single
-- pronunciation).
local saw_already = false
if not parsed.rhyme then
parsed.rhyme = {}
end
for _, existing in ipairs(parsed.rhyme) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables,
-- e.g. if the user specified two pronunciations 'biología' (4 syllables) and
-- 'bi.ología' (5 syllables), both of which have the same rhyme /ia/.
m_table.insertIfNot(existing.num_syl, num_syl)
if not m_table.deepEquals(existing.q, pronun.q) or not
m_table.deepEquals(existing.qq, pronun.qq) then
existing.q = nil
existing.qq = nil
end
break
end
end
if not saw_already then
table.insert(parsed.rhyme, {
rhyme = rhyme,
num_syl = {num_syl},
q = #parsed.pronuns > 1 and pronun.q or nil,
qq = #parsed.pronuns > 1 and pronun.qq or nil,
nocat = not pronun.rhyme_with_cat,
})
end
end
end
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = process_specified_rhymes(parsed.rhyme, parsed.syll and parsed.syll.terms or {}, {parsed})
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_sylls
if overall_syll then
all_sylls = overall_syll
else
all_sylls = {}
for _, parsed in ipairs(parsed_respellings) do
if parsed.syll then
for _, syll in ipairs(parsed.syll.terms) do
m_table.insertIfNot(all_sylls, syll)
end
end
end
end
overall_rhyme = process_specified_rhymes(overall_rhyme, all_sylls, parsed_respellings)
end
end
-- Determine whether all sets of pronunciations have the same value for a pronunciation property (rhymes,
-- syllabifications or homophones). If so, we display them them only once at the bottom, otherwise beneath each set,
-- indented. This function takes one argument, the name of a slot specifying the pronunciation property, and
-- returns two values, a boolean indicating whether all values are the same and the first value seen (which will
-- be the only value seen if all values are the same).
local function all_sets_equal(parsed_slot)
local first_set
local all_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_set = parsed[parsed_slot]
elseif not m_table.deepEquals(first_set, parsed[parsed_slot]) then
all_sets_eq = false
break
end
end
return all_sets_eq, first_set
end
local all_rhyme_sets_eq, first_rhyme_ret = all_sets_equal("rhyme")
local all_syll_sets_eq, first_sylls = all_sets_equal("syll")
local all_hmp_sets_eq, first_hmps = all_sets_equal("hmp")
------------------------------ 3. Insert categories as appropriate. ---------------------------------
local categories = {}
local function get_rhymes_categories(rhymes)
if not rhymes then
return
end
end
get_rhymes_categories(overall_rhyme)
for _, parsed in ipairs(parsed_respellings) do
get_rhymes_categories(parsed.rhyme)
end
local function get_syll_categories(sylls)
if not sylls then
return
end
for _, syll in ipairs(sylls.terms) do
local syll_no_dot = "#" .. syll.syllabification .. "#"
syll_no_dot = syll.syllabification:gsub("%.([^ #])", "%1"):gsub("#", "")
if syll_no_dot ~= pagename then
mw.log(("For page '%s', saw syllabification '%s' not matching pagename"):format(
pagename, syll.syllabification))
m_table.insertIfNot(categories, ("%s terms with syllabification not matching pagename"):format(
lang:getCanonicalName()))
end
end
end
get_syll_categories(overall_syll)
for _, parsed in ipairs(parsed_respellings) do
get_syll_categories(parsed.syll)
end
if syllabification_alignment_failed then
table.insert(categories, ("%s terms where syllabification alignment failed"):format(lang:getCanonicalName()))
end
---------------------------- 4. Format IPA, rhymes and syllabification for display. -------------------------------
local function bullet_prefix(num_bullets)
return string.rep("*", num_bullets) .. " "
end
local function format_rhyme(rhymes)
return require(rhymes_module).format_rhymes {
lang = lang,
rhymes = rhymes,
force_cat = force_cat,
}
end
local function format_syllabifications(syllobj)
return require(hyphenation_module).format_hyphenations {
lang = lang,
hyphs = syllobj.terms,
caption = syllobj.cap or "Syllabification"
}
end
local function format_homophones(hmps)
return require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, bullet_prefix(num_bullets) .. text)
end
return table.concat(ret, "\n")
end
-- Implement grouping by accent. If there is a run of more than one consecutive set of pronunciations with the
-- same accent, the accent goes on its own line and the pronunciations with this accent go below with an extra
-- bullet.
local prev_accents
local num_seen_with_these_accents
for j, parsed in ipairs(parsed_respellings) do
if m_table.deepEquals(prev_accents, parsed.accents) then
parsed.of_several_accents = "continuation"
num_seen_with_these_accents = num_seen_with_these_accents + 1
if num_seen_with_these_accents == 2 then
parsed_respellings[j - 1].of_several_accents = "first"
end
else
prev_accents = parsed.accents
num_seen_with_these_accents = 1
end
end
-- Pull out autogenerated pronunciations and move to the next line, indented.
for _, parsed in ipairs(parsed_respellings) do
local saw_next_line_pronuns = false
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
saw_next_line_pronuns = true
break
end
end
if saw_next_line_pronuns then
local this_line_pronuns = {}
local next_line_pronuns = {}
for _, pronun in ipairs(parsed.pronuns) do
if pronun.move_to_next_line then
table.insert(next_line_pronuns, pronun)
else
table.insert(this_line_pronuns, pronun)
end
end
-- Now see if there are qualifiers shared among all elements of the next-line pronuns and deduplicate if so.
local function deduplicate_qualifiers(field, keepfirst)
local saw_nil = false
for _, pronun in ipairs(next_line_pronuns) do
if not pronun[field] then
saw_nil = true
break
end
end
if not saw_nil then
local m_setutil = require(set_utilities_module)
local qualifiers = {}
for _, pronun in ipairs(next_line_pronuns) do
table.insert(qualifiers, m_setutil.list_to_set(pronun[field]))
end
local all_shared = m_setutil.intersect(unpack(qualifiers))
if next(all_shared) then
local first_index, last_index
if keepfirst then
first_index = 2
last_index = #pronun
else
first_index = 1
last_index = #pronun - 1
end
for i = first_index, last_index do
local pronun = next_line_pronuns[i]
local new_qualifiers = {}
for _, q in ipairs(pronun[field]) do
if not all_shared[q] then
table.insert(new_qualifiers, q)
end
end
pronun[field] = new_qualifiers
end
end
end
end
parsed.pronuns = this_line_pronuns
parsed.next_line_pronuns = next_line_pronuns
end
end
-- Now actually format the pronunciations.
local textparts = {}
local first_line = true
local function ins_line(linetext, num_bullets)
if not first_line then
table.insert(textparts, "\n")
end
first_line = false
table.insert(textparts, bullet_prefix(num_bullets) .. linetext)
end
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
local accent_grouping_offset = 0
local no_accent = #parsed.accents == 1 and parsed.accents[1] == ""
local pronuns = format_pronun_line(parsed)
local accent_prefix
if not no_accent then
if parsed.of_several_accents == "first" then
ins_line(require(accent_qualifier_module).format_qualifiers(lang, parsed.accents), parsed.bullets)
end
if not parsed.of_several_accents then
accent_prefix = require(accent_qualifier_module).format_qualifiers(lang, parsed.accents) .. " "
else
accent_prefix = ""
accent_grouping_offset = 1
end
else
accent_prefix = ""
end
ins_line(accent_prefix .. pronuns, parsed.bullets + accent_grouping_offset)
if parsed.next_line_pronuns then
ins_line(format_pronuns(parsed.next_line_pronuns), parsed.bullets + accent_grouping_offset + 1)
end
if parsed.audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise indent audio by
-- one more bullet.
table.insert(textparts, format_audio(parsed.audio,
(#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1) + accent_grouping_offset))
end
if not all_rhyme_sets_eq and parsed.rhyme then
ins_line(format_rhyme(parsed.rhyme), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_syll_sets_eq and parsed.syll then
ins_line(format_syllabifications(parsed.syll), parsed.bullets + 1 + accent_grouping_offset)
end
if not all_hmp_sets_eq and parsed.hmp then
ins_line(format_homophones(parsed.hmp), parsed.bullets + 1 + accent_grouping_offset)
end
end
if overall_audio then
-- format_audio() inserts multiple lines and handles bullets by itself.
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
ins_line(format_rhyme(first_rhyme_ret), min_num_bullets)
end
if overall_rhyme then
ins_line(format_rhyme(overall_rhyme), min_num_bullets)
end
if all_syll_sets_eq and first_sylls then
ins_line(format_syllabifications(first_sylls), min_num_bullets)
end
if overall_syll then
ins_line(format_syllabifications(overall_syll), min_num_bullets)
end
if all_hmp_sets_eq and first_hmps then
ins_line(format_homophones(first_hmps), min_num_bullets)
end
if overall_hmp then
ins_line(format_homophones(overall_hmp), min_num_bullets)
end
return table.concat(textparts) ..
require("Module:utilities").format_categories(categories, lang, nil, nil, force_cat)
end
-- Meant to be called from a bot.
function export.pron_json(frame)
local iparams = {
[1] = {list = true, required = true},
["pagename"] = {required = true},
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local data = {}
local syllabification_from_pagename = syllabify_from_spelling(iargs.pagename, iargs.pagename)
for _, respelling in ipairs(iargs[1]) do
local pronun = export.IPA(respelling, "include phonemic syllable boundaries")
local syllabification = export.syllabify_and_align(respelling, iargs.pagename)
local num_syl = get_num_syl_from_ipa(pronun.phonemic)
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
table.insert(data, {
respelling = respelling,
phonemic = pronun.phonemic,
phonetic = pronun.phonetic,
syllabification = syllabification,
num_syl = num_syl,
rhyme = rhyme,
})
end
local retval = {
pagename = iargs.pagename,
syllabification_from_pagename = syllabification_from_pagename,
data = data,
}
return require("Module:JSON").toJSON(retval)
end
return export
do3epjopz4l532x5v8v6re0qqfhcmpv
Module:Letter headword
828
3387421
9909081
9804036
2026-09-29T07:26:51Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909081
Scribunto
text/plain
local export = {}
local force_cat = false
local require_when_needed = require("Module:utilities/require when needed")
local headword_module = "Module:headword"
local headword_utilities_module = "Module:headword utilities"
local JSON_module = "Module:JSON"
local languages_module = "Module:languages"
local parameters_module = "Module:parameters"
local scripts_module = "Module:scripts"
local table_module = "Module:table"
local m_string_utilities = require("Module:string utilities")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local deep_equals = require_when_needed(table_module, "deepEquals")
local shallow_copy = require_when_needed(table_module, "shallowCopy")
local uupper = m_string_utilities.upper
local ucfirst = m_string_utilities.ucfirst
local ulower = m_string_utilities.lower
local ulen = m_string_utilities.len
local insert = table.insert
local per_language_defaults = {
de = {g = "n"},
en = {pl_ending = "s,'s"},
it = {g = "f,m", pl_ending = "_"},
pt = {g = "m"},
}
local function ine(val)
if not val then
return val
end
val = mw.text.trim(val)
if val == "" then return nil else return val end
end
local function add_initial_colon_to_term(term)
if term ~= "-" and term ~= "+" and not term:find("^:") then
-- Make sure we link to the specified term even if it has a diacritic that would normally be stripped off.
term = ":" .. term
end
return term
end
local function resolve_plus(termobjs, default, paramname)
local saw_plus = false
for _, termobj in ipairs(termobjs) do
if termobj.term == "+" then
saw_plus = true
break
end
end
if not saw_plus then
return termobjs
end
if not default then
error(("Saw '+' for param '%s' but no default available"):format(paramname))
end
if type(default) == "string" then
for _, termobj in ipairs(termobjs) do
if termobj.term == "+" then
termobj.term = default
end
end
return termobjs
end
if type(default) ~= "table" then
error("Internal error: `default` should be nil, string or list of strings")
end
local resolved_termobjs = {}
for _, termobj in ipairs(termobjs) do
if termobj.term == "+" then
for _, defval in ipairs(default) do
defval = shallow_copy(defval)
require(headword_utilities_module).combine_termobj_decorations(defval, termobj)
insert(resolved_termobjs, defval)
end
else
insert(resolved_termobjs, termobj)
end
end
return resolved_termobjs
end
local function parse_equivalent(value, default, paramname, no_prefix_colon)
if not value then
return nil
end
local termobjs
if value == "+" then
-- optimization to avoid loading [[Module:headword utilities]]
if not default then
error(("Saw '+' for param '%s' but no default available"):format(paramname))
end
if type(default) == "string" then
termobjs = {{term = default}}
else
if type(default) ~= "table" then
error("Internal error: `default` should be nil, string or list of term objects")
end
termobjs = default
end
elseif value:find("[,<]") then
termobjs = require(headword_utilities_module).parse_term_with_modifiers {
val = value,
paramname = paramname,
splitchar = ",",
include_mods = {"tr", "ts", "t", "sc"},
}
else
termobjs = {{ term = value }}
end
termobjs = resolve_plus(termobjs, default, paramname)
for _, termobj in ipairs(termobjs) do
if not no_prefix_colon then
termobj.term = add_initial_colon_to_term(termobj.term)
end
termobj.tr = "-"
end
return termobjs
end
--[==[
Implementation of the letter headword template for a given language (e.g. {{tl|en-letter}}, {{tl|it-letter}} or
{{tl|sh-letter}}). Supports the following invocation parameters:
; {{para|pos}}
: The plural part of speech to use; defaults to {{cd|letters}}. Other possibilities are e.g. {{cd|numeral symbols}} for
numeral symbols (letters used for list items).
; {{para|lang}}
: The language code of the language of the headword template. Omit for language-agnostic {{tl|letter}}.
; {{para|sc}}
: Specify the default script code. Rarely needs to be given.
; {{para|g}}
: Specify the default gender(s) of the letter. Multiple comma-separated values are allowed, along with qualifier, label
and reference inline modifiers. See [[Module:gender and number]] for more information, including the allowed values.
The default(s) can be overridden using the {{para|g}} template parameter.
; {{para|pl_ending}} ...
: Specify the default ending(s) of the plural form(s) of the letter. Multiple items should be comma-separated, and
qualifier, label, reference, transliteration and gloss inline modifiers are allowed. Use the value {{cd|_}} to
indicate a null ending. The default(s) can be overridden using the {{para|pl}} template parameter.
; {{para|allow_tr|1}}
: Specify that the template allows the {{para|tr}} parameter to be given for specifying transliteration.
]==]
function export.show(frame)
local list_param = {list = true, disallow_holes = true}
local boolean_param = {type = "boolean"}
local frame_args = frame.args
local parent_args = frame:getParent().args
-- Extract language and any per-language defaults. If they exist, clone the frame args and set the defaults into the
-- frame args before parsing. If there is no language specified at either the invocation or template level, we'll
-- get an error later.
local lang = ine(frame_args.lang) or ine(parent_args[1])
if lang and per_language_defaults[lang] then
local cloned_frame_args = {}
for k, v in pairs(frame_args) do
cloned_frame_args[k] = v
end
local defaults = per_language_defaults[lang]
for k, v in pairs(defaults) do
if cloned_frame_args[k] == nil then
cloned_frame_args[k] = v
end
end
frame_args = cloned_frame_args
end
local iargs = require(parameters_module).process(frame_args, {
pos = {default = "字母"},
lang = {type = "language", template_default = "und"},
sc = {type = "script"},
g = {type = "genders"},
pl_ending = true,
allow_tr = boolean_param,
})
local allowed_types = {"upper", "lower", "mixed", "allcaps", "nocase"}
local params = {
g = {type = "genders"},
sc = {type = "script"},
type = {set = allowed_types},
head = list_param,
upper = true,
lower = true,
mixed = true,
allcaps = true,
pl = true,
nopl = boolean_param,
id = true,
sort = true,
pagename = true,
modern = true,
}
local langparam, otherparam
if not iargs.lang then
langparam = 1
otherparam = 2
params[langparam] = {type = "language", required = true, template_default = "und"}
else
otherparam = 1
end
params[otherparam] = list_param
if iargs.g and iargs.g[1] then
params.nog = boolean_param
end
if iargs.allow_tr or not iargs.lang then
params.tr = list_param
end
if not iargs.lang then
params.ts = list_param
end
local args = require(parameters_module).process(parent_args, params)
local others = {}
for i, otherspec in ipairs(args[otherparam]) do
local lang_sc, rest = otherspec:match("^([a-zA-Z0-9-]+):([^ ].*)$")
if not lang_sc then
error(("Expected other-lang or other-script param %s=%s to begin with a language code or script code followed by a colon and no space"):format(i + otherparam - 1, otherspec))
end
local obj = require(scripts_module).getByCode(lang_sc)
local objtype
if obj then
objtype = "script"
else
obj = require(languages_module).getByCode(lang_sc, nil, "allow etym")
if obj then
objtype = "language"
else
error(("Unrecognized language or script '%s' in %s=%s"):format(lang_sc, i + otherparam - 1, otherspec))
end
end
insert(others, {
obj = obj,
objtype = objtype,
value = rest,
})
end
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
if args.type then
if args.type ~= "upper" and args.type ~= "lower" and args.type ~= "mixed" and args.type ~= "nocase" then
error(("Unrecognized value for type '%s'; should be one of 'upper', 'lower', 'mixed' or 'nocase'"):format(
args.type))
end
end
local lang = langparam and args[langparam] or iargs.lang
local sc = args.sc or iargs.sc or lang:findBestScript(pagename)
local data = {
lang = lang,
sc = sc,
pos_category = iargs.pos,
categories = {},
pagename = pagename,
inflections = {},
id = args.id,
sort_key = args.sort,
heads = args.head,
translits = args.tr,
transcriptions = args.ts,
force_cat_output = force_cat,
genders = not args.nog and (args.g and args.g[1] and args.g or iargs.g) or nil,
categories = {},
-- Disable "terms with redundant script codes" and "terms with non-redundant manual script codes"
-- categories. We always specify the script and the categories simply aren't useful in this case; having
-- them just clutters the categories with letter entries.
no_script_code_cat = true,
}
-- All letters can also be used as nouns ("There are two f's in that word").
insert(data.categories, lang:getFullName() .. "名詞")
if sc:getCode() ~= "None" then
insert(data.categories, sc:getCategoryName() .. "字元")
end
local uppage = uupper(pagename)
local lopage = ulower(pagename)
local ucfirstpage = ucfirst(lopage)
local function insert_inflection(termobjs, label)
if not termobjs or not termobjs[1] then
return
end
if termobjs[1].term == "-" then
require(headword_utilities_module).insert_inflection {
headdata = data,
terms = termobjs,
label = label,
}
else
termobjs.label = label
insert(data.inflections, termobjs)
end
end
local typ = args.type
if not typ then
if uppage == lopage then
typ = "nocase"
elseif data.pagename == ucfirstpage then
typ = "upper"
elseif data.pagename == uppage then
typ = "allcaps"
elseif data.pagename == lopage then
typ = "lower"
else
typ = "mixed"
end
end
if typ == "nocase" then
if args.upper or args.lower or args.mixed or args.allcaps then
error("Can't specify upper=, lower=, mixed= or allcaps= when letter has no case")
end
insert(data.inflections, {label = "不分大小寫"})
else
local upper = parse_equivalent(args.upper or "+", ucfirstpage, "upper")
local lower = parse_equivalent(args.lower or "+", lopage, "lower")
local allcaps = parse_equivalent(args.allcaps or ulen(pagename) == 1 and args.upper or "+", uppage, "allcaps")
local mixed = parse_equivalent(args.mixed, nil, "mixed")
local pagenameobj = {{term = ":" .. pagename, tr = "-"}}
if typ == "upper" then
if args.upper then
error("Already uppercase; can't specify upper=")
end
insert(data.inflections, {label = "[[Appendix:大寫字母|大寫]]"})
insert_inflection(lower, "小寫")
if not deep_equals(pagenameobj, allcaps) then
insert_inflection(allcaps, "[[Appendix:大寫字母|全大寫]]")
end
insert_inflection(mixed, "混合大小寫")
elseif typ == "lower" then
if args.lower then
error("Already lowercase; can't specify lower=")
end
insert(data.inflections, {label = "小寫"})
if deep_equals(upper, allcaps) then
if ulen(pagename) == 1 then
insert_inflection(upper, "[[Appendix:大寫字母|大寫]]")
else
insert_inflection(upper, "[[Appendix:大寫字母|大寫]]和全大寫")
end
else
insert_inflection(upper, "[[Appendix:大寫字母|大寫]]")
insert_inflection(allcaps, "[[Appendix:大寫字母|全大寫]]")
end
insert_inflection(mixed, "混合大小寫")
elseif typ == "allcaps" then
if args.allcaps then
error("Already all-caps; can't specify allcaps=")
end
insert(data.inflections, {label = "[[Appendix:大寫字母|全大寫]]"})
if not deep_equals(pagenameobj, upper) then
insert_inflection(upper, "[[Appendix:大寫字母|大寫]]")
end
insert_inflection(lower, "小寫")
insert_inflection(mixed, "混合大小寫")
else
if args.mixed then
error("Already mixed-case; can't specify mixed=")
end
insert(data.inflections, {label = "混合大小寫"})
insert_inflection(lower, "小寫")
if deep_equals(upper, allcaps) then
insert_inflection(upper, "[[Appendix:大寫字母|大寫]]和全大寫")
else
insert_inflection(upper, "[[Appendix:大寫字母|大寫]]")
insert_inflection(allcaps, "[[Appendix:大寫字母|全大寫]]")
end
end
end
if args.nopl then
insert(data.inflections, {label = "無複數"})
elseif args.pl or iargs.pl_ending then
local default_pls
if iargs.pl_ending then
default_pls = parse_equivalent(iargs.pl_ending, nil, "pl_ending", "no_prefix_colon")
for _, pl_ending in ipairs(default_pls) do
if pl_ending.term == "_" then
pl_ending.term = pagename
else
pl_ending.term = pagename .. pl_ending.term
end
end
end
local pls = parse_equivalent(args.pl or "+", default_pls, "pl")
if not pls[2] and pls[1].term == ":" .. pagename then
require(headword_utilities_module).insert_fixed_inflection {
headdata = data,
originating_term = pls[1],
label = glossary_link("無屈折"),
}
else
insert_inflection(pls, "複數")
end
end
if args.modern then
local termobjs = parse_equivalent(args.modern, nil, "modern")
insert_inflection(termobjs, "現代等價形式")
end
if others[1] then
for _, other in ipairs(others) do
local termobjs = parse_equivalent(other.value, nil, other.obj:getCode())
for _, termobj in ipairs(termobjs) do
if other.objtype == "language" then
termobj.lang = other.obj
else
termobj.sc = other.obj
end
end
insert_inflection(termobjs, other.obj:getCanonicalName() .. "等價形式")
end
end
if args.json then
return require(JSON_module).toJSON(data)
end
return require(headword_module).full_headword(data)
end
return export
h7y4u4phjpplcgm4zcwr44b091tx34f
Module:Egy-pron
828
3402745
9909054
9711538
2026-09-29T07:25:05Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909054
Scribunto
text/plain
local export = {}
local M = require("Module:module loader").init({
require = {
string_utilities = "Module:string utilities",
parse_utilities = "Module:parse utilities",
IPA = "Module:IPA",
accent_qualifier = "Module:accent qualifier",
decorations = "Module:decorations",
languages = "Module:languages",
parameters = "Module:parameters",
references = "Module:references",
utilities = "Module:utilities",
},
loadData = {
headword_data = "Module:headword/data",
},
})
local rsplit = M.string_utilities.split
local rsubn = M.string_utilities.gsub
local ulower = M.string_utilities.lower
local concat = table.concat
local insert = table.insert
local remove = table.remove
local lang = M.languages.getByCode("egy")
local phonetic_chars_map = {
["ꜣ"] = "ɑ",
["j"] = "i",
["y"] = "iː",
["ꜥ"] = "ɑː",
["w"] = "w",
["u"] = "uː",
["b"] = "b",
["p"] = "p",
["f"] = "f",
["m"] = "m",
["n"] = "n",
["r"] = "r",
["h"] = "h",
["ḥ"] = "h",
["ḫ"] = "x",
["ẖ"] = "ç",
["z"] = "z",
["s"] = "s",
["š"] = "ʃ",
["q"] = "k",
["k"] = "k",
["g"] = "ɡ",
["t"] = "t",
["ṯ"] = "t͡ʃ",
["d"] = "d",
["ḏ"] = "d͡ʒ",
["e"] = "ɛ"
}
local ang_chars_map = {
["ꜣ"] = "a",
["j"] = "i",
["y"] = "y",
["ꜥ"] = "a",
["w"] = "w",
["u"] = "u",
["b"] = "b",
["p"] = "p",
["f"] = "f",
["m"] = "m",
["n"] = "n",
["r"] = "r",
["h"] = "h",
["ḥ"] = "h",
["ḫ"] = "kh",
["ẖ"] = "kh",
["z"] = "z",
["s"] = "s",
["š"] = "sh",
["q"] = "q",
["k"] = "k",
["g"] = "g",
["t"] = "t",
["ṯ"] = "tj",
["d"] = "d",
["ḏ"] = "dj",
["e"] = "e"
}
local function generate_obj(respelling)
return { respelling = respelling }
end
local function generate_pron_obj(pron)
return { pron = pron }
end
local function parse_reconstructed_item(item, paramname)
if not item or item == "" then
return {}
end
if item:find("[<%[]") then
local put = M.parse_utilities
local segments = put.parse_multi_delimiter_balanced_segment_run(item, { { "<", ">" }, { "[", "]" } })
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
local retval = {}
for _, group in ipairs(comma_separated_groups) do
local j = 2
while j <= #group do
if not group[j]:find("^<.*>$") then
group[j - 1] = group[j - 1] .. group[j] .. group[j + 1]
remove(group, j)
remove(group, j)
else
j = j + 2
end
end
local param_mods = {
q = { type = "qualifier" },
qq = { type = "qualifier" },
a = { type = "labels" },
aa = { type = "labels" },
ref = { item_dest = "refs", type = "references" },
note = { item_dest = "note", type = "qualifier" },
}
insert(retval, put.parse_inline_modifiers_from_segments {
group = group,
arg = item,
props = {
paramname = paramname,
param_mods = param_mods,
generate_obj = generate_pron_obj,
},
})
end
return retval
end
return { { pron = item } }
end
local function split_on_comma(term)
if not term then
return nil
end
if term:find(",%s") or term:find("") then
return M.parse_utilities.split_on_comma(term)
else
return rsplit(term, ",")
end
end
local function parse_respellings_with_modifiers(respelling, paramname)
if respelling and respelling:find("[<%[]") then
local put = M.parse_utilities
local segments = put.parse_multi_delimiter_balanced_segment_run(respelling, { { "<", ">" }, { "[", "]" } })
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
local retval = {}
for _, group in ipairs(comma_separated_groups) do
local j = 2
while j <= #group do
if not group[j]:find("^<.*>$") then
group[j - 1] = group[j - 1] .. group[j] .. group[j + 1]
remove(group, j)
remove(group, j)
else
j = j + 2
end
end
local param_mods = {
q = { type = "qualifier" },
qq = { type = "qualifier" },
a = { type = "labels" },
aa = { type = "labels" },
ref = { item_dest = "refs", type = "references" },
}
insert(retval, put.parse_inline_modifiers_from_segments {
group = group,
arg = respelling,
props = {
paramname = paramname,
param_mods = param_mods,
generate_obj = generate_obj,
},
})
end
return retval
else
local retval = {}
local items = respelling and split_on_comma(respelling) or {}
for _, item in ipairs(items) do
insert(retval, generate_obj(item))
end
return retval
end
end
local function normalize_and_phonology(word)
word = ulower(word or "")
local s = word
s = rsubn(s, "^%-", "")
s = rsubn(s, "%-$", "")
s = rsubn(s, "%-", " ")
s = rsubn(s, "_", " _ʔ")
s = rsubn(s, "%f[%a]j%.", "j")
s = rsubn(s, "%.", " ʔ")
s = rsubn(s, "w", "u")
s = rsubn(s, "%f[%a]u", "w")
s = rsubn(s, "s _ʔu", "s _ʔw")
s = rsubn(s, "%f[%a]([mnr])%f[%A]", "e%1")
s = rsubn(s, "ʔ([bpfmnrhḥḫẖzsšqkgtṯdḏ])%f[%A]", "ʔe%1")
s = rsubn(s, "%f[%a]([wbpfmnrhḥḫẖzsšqkgtṯdḏ])%f[%A]", "%1e")
s = rsubn(s, "([ꜣjyꜥue])([bpfmnrhḥḫẖzsšqkgtṯdḏ])([bpfmnrhḥḫẖzsšqkgtṯdḏ])([ꜣjyꜥue])", "%1%2v%3%4")
s = rsubn(s, "([wbpfmnrhḥḫẖzsšqkgtṯdḏ])([wbpfmnrhḥḫẖzsšqkgtṯdḏ])", "%1e%2")
s = rsubn(s, "([wbpfmnrhḥḫẖzsšqkgtṯdḏ])([wbpfmnrhḥḫẖzsšqkgtṯdḏ])%f[%A]", "%1e%2")
s = rsubn(s, "v", "")
s = rsubn(s, "([ꜣjyꜥue])([bpfmnrhḥḫẖzsšqkgtṯdḏ])e([bpfmnrhḥḫẖzsšqkgtṯdḏ])e", "%1%2%3e")
s = rsubn(s, "([wbpfmnrhḥḫẖzsšqkgtṯdḏ])%1", "%1e%1")
s = rsubn(s, " _ʔ", "")
s = rsubn(s, " ʔ", "ʔ")
return s
end
local function to_IPA_internal(word)
local s = normalize_and_phonology(word)
s = rsubn(s, "ꜥneḫ", "ꜥnḫ")
s = rsubn(s, "([ꜣꜥ])([ꜣꜥ])", "%1ʔ%2")
s = rsubn(s, "([ꜣꜥ])([ꜣꜥ])%f[%A]", "%1ʔ%2")
s = rsubn(s, "([jy])([jy])", "%1ʔ%2")
s = rsubn(s, "([jy])([jy])%f[%A]", "%1ʔ%2")
s = rsubn(s, "uu", "uʔu")
s = rsubn(s, ".", phonetic_chars_map)
return "/" .. s .. "/"
end
local function to_anglicization_internal(word)
word = ulower(word or "")
local ang = word
ang = rsubn(ang, "%-", " - ")
ang = rsubn(ang, "%f[%a]j%.", "j")
ang = rsubn(ang, "%.", " ʔ")
ang = rsubn(ang, "_", " _ʔ")
ang = rsubn(ang, "w", "u")
ang = rsubn(ang, "%f[%a]u", "w")
ang = rsubn(ang, "s _ʔu", "s _ʔw")
ang = rsubn(ang, "uj%f[%A]", "wj")
ang = rsubn(ang, "%f[%a]([mnr])%f[%A]", "e%1")
ang = rsubn(ang, "ʔ([wbpfmnrhḥḫẖzsšqkgtṯdḏ])%f[%A]", "ʔe%1")
ang = rsubn(ang, "%f[%a]([wbpfmnrhḥḫẖzsšqkgtṯdḏ])%f[%A]", "%1e")
ang = rsubn(ang, "([ꜣjyꜥue])([wbpfmnrhḥḫẖzsšqkgtṯdḏ])([wbpfmnrhḥḫẖzsšqkgtṯdḏ])([ꜣjyꜥue])", "%1%2v%3%4")
ang = rsubn(ang, "([wbpfmnrhḥḫẖzsšqkgtṯdḏ])([wbpfmnrhḥḫẖzsšqkgtṯdḏ])", "%1e%2")
ang = rsubn(ang, "([wbpfmnrhḥḫẖzsšqkgtṯdḏ])([wbpfmnrhḥḫẖzsšqkgtṯdḏ])%f[%A]", "%1e%2")
ang = rsubn(ang, "v", "")
ang = rsubn(ang, "([ꜣjyꜥue])([wbpfmnrhḥḫẖzsšqkgtṯdḏ])e([wbpfmnrhḥḫẖzsšqkgtṯdḏ])e", "%1%2%3e")
ang = rsubn(ang, "([wbpfmnrhḥḫẖzsšqkgtṯdḏ])%1", "%1e%1")
ang = rsubn(ang, " _ʔ", "")
ang = rsubn(ang, " ʔ", ".")
ang = rsubn(ang, "ꜥneḫ", "ꜥnḫ")
ang = rsubn(ang, "neḫet", "naḫt")
ang = rsubn(ang, "jmen", "ꜣmen")
ang = rsubn(ang, "jten", "ꜣten")
ang = rsubn(ang, "sebek", "sobek")
ang = rsubn(ang, "peteḥ", "ptaḥ")
ang = rsubn(ang, " %- ", "-")
ang = rsubn(ang, ".", ang_chars_map)
return ang
end
local function multiword(term, pagename)
if term and term:find("^raw:%[.+%]$") then
return { { phonetic = term:gsub("^raw:", "") } }
end
local respelling = term
if not respelling or respelling == "" or respelling == "#" then
respelling = pagename or ""
end
local words = split_on_comma(respelling)
if not words or #words == 0 then
words = { respelling }
end
local prons = {}
for _, word in ipairs(words) do
local w = (word and word ~= "") and word or pagename
if w and w ~= "" then
insert(prons, { pron = to_IPA_internal(w) })
end
end
if #prons == 0 and respelling ~= "" then
insert(prons, { pron = to_IPA_internal(respelling) })
end
return prons
end
local function ensure_slashes(pron)
if not pron or pron == "" then return "" end
if not pron:find("^/") then pron = "/" .. pron .. "/" end
return pron
end
local function refs_list(val)
if not val then return nil end
if type(val) == "table" and #val > 0 then return val end
if type(val) == "string" and val ~= "" then
local ok, parsed = pcall(M.references.parse_references, val)
return (ok and parsed and #parsed > 0) and parsed or nil
end
return nil
end
local function build_reconstructed_block(lang_obj, stages, args, indent, qualifier_override, row_ref)
local q_val = qualifier_override
local block_refs = refs_list(row_ref)
local ulen = M.string_utilities.len
local function total_len()
local t = 0
for _, stage_key in ipairs({ "o", "m", "ml", "l" }) do
local list = stages[stage_key] or {}
for _, item in ipairs(list) do
t = t + ulen(item.pron or "")
end
end
local q_str = (type(q_val) == "string") and q_val or ""
return t + ulen(q_str)
end
local w = 28 + math.floor(2 * total_len() / 3)
local collapsed_parts = {}
local function add_collapsed(s) insert(collapsed_parts, s) end
local stage_order = { { key = "o", label = "Old Egyptian", date = "c. 2500 {{BCE}}", link = "[[w:埃及語|上古]]" }, { key = "m", label = "Middle Egyptian", date = "c. 1700 {{BCE}}", link = "[[w:埃及語|中古]]" }, { key = "ml", label = "Amarna-period Late Egyptian", date = "c. 1350 {{BCE}}", link = "[[w:埃及語|阿瑪納時期]]" }, { key = "l", label = "Late Egyptian", date = "c. 800 {{BCE}}", link = "[[w:埃及語|新埃及語]]" } }
local collapsed_items = {}
for si, stage in ipairs(stage_order) do
local list = stages[stage.key] or {}
for ii, item in ipairs(list) do
local pron = ensure_slashes(item.pron)
if pron ~= "" then
local entry = { pron = pron }
if #collapsed_items > 0 then
entry.separator = (ii == 1) and " → " or ", "
end
insert(collapsed_items, entry)
end
end
end
local collapsed_line = ""
if #collapsed_items > 0 then
local q_list = (q_val and q_val ~= "") and { q_val } or nil
local qualifier_pretext = (q_list and q_list[1]) and (M.decorations.format_decorations { lang = lang_obj, text = "", q = q_list }) or ""
if collapsed_items[1] and qualifier_pretext ~= "" then
collapsed_items[1].pretext = qualifier_pretext
end
local a_list = { "[[w:埃及語|構擬]]" }
collapsed_line = M.IPA.format_IPA_full {
lang = lang_obj,
items = collapsed_items,
separator = ", ",
no_count = true,
a = a_list,
split_output = true,
}
if block_refs then
collapsed_line = collapsed_line .. M.references.format_references(block_refs)
end
end
add_collapsed(collapsed_line)
local expanded_list_items = {}
for _, stage in ipairs(stage_order) do
local list = stages[stage.key] or {}
if #list > 0 then
local stage_items = {}
local q_list = (q_val and q_val ~= "") and { q_val } or nil
local qualifier_pretext = (q_list and q_list[1]) and (M.decorations.format_decorations { lang = lang_obj, text = "", q = q_list }) or ""
local is_first_stage = (stage.key == "o")
for i, item in ipairs(list) do
local pron = ensure_slashes(item.pron)
if pron ~= "" then
local q = type(item.q) == "table" and item.q or (item.q and item.q ~= "" and { item.q } or nil)
local refs = refs_list(item.refs)
local entry = { pron = pron, q = q, refs = refs }
if i == 1 and qualifier_pretext ~= "" then
entry.pretext = qualifier_pretext
end
insert(stage_items, entry)
end
end
local expanded_line = ""
if #stage_items > 0 then
local a_list = { stage.link .. ", " .. stage.date }
expanded_line = M.IPA.format_IPA_full {
lang = lang_obj,
items = stage_items,
no_count = true,
a = a_list,
split_output = true,
}
end
local note_str
for _, item in ipairs(list) do
if item.note then
local n = (type(item.note) == "table" and item.note[1]) or item.note
if n and n ~= "" then
note_str = (type(n) == "string") and n or tostring(n)
break
end
end
end
local li = mw.html.create("li")
li:wikitext(expanded_line)
if note_str and note_str ~= "" then
li:wikitext(" ")
li:tag("span"):addClass("qualifier-content"):wikitext(note_str)
end
local stage_has_V = false
for _, item in ipairs(list) do
if item.pron and item.pron:find("V") then stage_has_V = true; break end
end
if stage_has_V then
li:tag("dl"):tag("dd"):tag("span"):css("font-size", "x-small"):wikitext("where V represents an unknown short vowel.")
end
insert(expanded_list_items, li)
end
end
local ul_collapsed = mw.html.create("ul")
ul_collapsed:tag("li"):wikitext(concat(collapsed_parts))
local ul_expanded = mw.html.create("ul")
for _, li in ipairs(expanded_list_items) do
ul_expanded:node(li)
end
local div_collapsed = mw.html.create("div")
:addClass("vsShow")
:css("display", "block")
:node(ul_collapsed)
local div_expanded = mw.html.create("div")
:addClass("vsHide")
:css("display", "none")
:node(ul_expanded)
local span_toggle = mw.html.create("span")
:addClass("vsToggleElement")
:css("float", "right")
:css("cursor", "pointer")
:wikitext(" ")
return tostring(mw.html.create("div")
:addClass("vsSwitcher")
:attr("data-toggle-category", "pronunciations")
:css("width", w .. "em")
:css("max-width", "100%")
:node(span_toggle)
:node(div_collapsed)
:node(div_expanded))
end
function export.get_pron_info(terms, pagename)
if #terms == 1 and terms[1].respelling == "-" then
return { pron_list = nil }
end
local pron_list = {}
for _, term in ipairs(terms) do
local respelling = term.respelling
if not respelling or respelling == "" or respelling == "#" then
respelling = pagename
end
if not respelling or respelling == "" then
respelling = pagename or ""
end
local prons = multiword(respelling, pagename)
for i, pron in ipairs(prons) do
if pron.phonetic then
insert(pron_list, {
pron = pron.phonetic,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
refs = i == 1 and term.refs or nil,
})
else
local pron_str = pron.pron
local bracketed_pron = pron_str:find("^/") and pron_str or ("/" .. pron_str .. "/")
insert(pron_list, {
pron = bracketed_pron,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
refs = i == 1 and term.refs or nil,
})
end
end
end
return { pron_list = pron_list }
end
function export.to_IPA(word)
return to_IPA_internal(word)
end
function export.to_anglicization(word)
return to_anglicization_internal(word)
end
function export.pronunciation(words)
local items = {}
for _, word in ipairs(words) do
insert(items, { pron = to_IPA_internal(word) })
end
return M.IPA.format_IPA_full { lang = lang, items = items }
end
function export.anglicization(words)
local items = {}
for _, word in ipairs(words) do
insert(items, to_anglicization_internal(word))
end
return concat(items, ", ")
end
function export.show(frame)
local parent_args = frame:getParent().args
local lang_obj = lang
local params = {
[1] = { list = true, default = M.headword_data.pagename },
ang = { list = true, allow_holes = true },
pagename = true,
indent = true,
o = { list = true, allow_holes = true, sublist = "comma without whitespace" },
m = { list = true, allow_holes = true, sublist = "comma without whitespace" },
ml = { list = true, allow_holes = true, sublist = "comma without whitespace" },
l = { list = true, allow_holes = true, sublist = "comma without whitespace" },
q = { list = true, allow_holes = true },
ref = { list = true, type = "references" },
}
local args = M.parameters.process(parent_args, params)
local termspec = args[1]
local terms
if type(termspec) == "table" then
terms = {}
for _, w in ipairs(termspec) do
insert(terms, { respelling = w })
end
else
terms = parse_respellings_with_modifiers(termspec or "#", 1)
end
local pagename = args.pagename or M.headword_data.pagename
local indent = args.indent or "*"
local pronobj = export.get_pron_info(terms, pagename)
local parts = {}
local function ins(text)
insert(parts, text)
end
local function to_list(v)
if type(v) == "table" then return v end
return (v and v ~= "") and { v } or {}
end
local stage_keys = { "o", "m", "ml", "l" }
local max_row = 0
for _, key in ipairs(stage_keys) do
local t = args[key]
if t then
local n = t.maxindex or #t
if n > max_row then max_row = n end
end
end
local reconstructed_rows = {}
local all_stages_union = { o = {}, m = {}, ml = {}, l = {} }
for i = 1, max_row do
local stages_row = { o = {}, m = {}, ml = {}, l = {} }
for _, stage_key in ipairs(stage_keys) do
local raw_list = to_list(args[stage_key] and args[stage_key][i])
for _, raw in ipairs(raw_list) do
if raw and raw ~= "" then
for _, obj in ipairs(parse_reconstructed_item(raw, stage_key)) do
insert(stages_row[stage_key], obj)
end
end
end
end
local has_any = false
for _, key in ipairs(stage_keys) do
if #(stages_row[key]) > 0 then has_any = true; break end
end
if has_any then
local qualifier = args.q and args.q[i]
local row_ref = args.ref and args.ref[i]
insert(reconstructed_rows, { stages = stages_row, qualifier = qualifier, ref = row_ref })
for _, key in ipairs(stage_keys) do
for _, obj in ipairs(stages_row[key]) do
insert(all_stages_union[key], obj)
end
end
end
end
for i, row in ipairs(reconstructed_rows) do
if i > 1 then ins("\
") end
ins(build_reconstructed_block(lang_obj, row.stages, args, indent, row.qualifier, row.ref))
end
if #reconstructed_rows > 0 then
local namespace = mw.title.getCurrentTitle().nsText
local is_content = (namespace == "" or namespace == "Reconstruction" or
(lang_obj and lang_obj.hasType and lang_obj:hasType("appendix-constructed") and namespace == "Appendix"))
if is_content then
local cat_list = {}
insert(cat_list, { cat = "有構擬國際音標發音的埃及語詞" })
local period_cats = {
o = "有構擬上古埃及語國際音標發音的埃及語詞",
m = "有構擬中古埃及語國際音標發音的埃及語詞",
ml = "有構擬阿瑪納時期新埃及語國際音標發音的埃及語詞",
l = "有構擬新埃及語國際音標發音的埃及語詞",
}
for _, key in ipairs(stage_keys) do
if #(all_stages_union[key]) > 0 then
insert(cat_list, { cat = period_cats[key] })
end
end
ins(M.utilities.format_categories(cat_list, lang_obj))
end
end
if pronobj.pron_list and #pronobj.pron_list > 0 then
if #reconstructed_rows > 0 then ins("\
") end
ins(indent .. " " .. M.accent_qualifier.format_qualifiers(lang_obj, { "埃及學" }) .. " ")
ins(M.IPA.format_IPA_full { lang = lang_obj, items = pronobj.pron_list })
end
if args.ang and args.ang[1] ~= "0" then
local ang_list = {}
for i, t in ipairs(terms) do
ang_list[i] = args.ang[i] or t.respelling
end
if #ang_list > 0 then
local ang_str = export.anglicization(ang_list)
ins("\
** 傳統英語化:" .. tostring(mw.html.create("span"):addClass("use-with-mention"):wikitext(ang_str)))
end
end
return concat(parts)
end
return export
7jqusj0z75z4ba4ohz4irvpq3cabj2d
Module:Rap-pron
828
3414677
9909101
9730708
2026-09-29T07:28:03Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909101
Scribunto
text/plain
--Adapted from [[Module:es-pronunc]] by [[User:Benwing2]]
local export = {}
local m_IPA = require("Module:IPA")
local m_table = require("Module:table")
local audio_module = "Module:audio"
local put_module = "Module:parse utilities"
local lang = require("Module:languages").getByCode("rap")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rsplit = mw.text.split
local ulower = mw.ustring.lower
local usub = mw.ustring.sub
local unfc = mw.ustring.toNFC
local CFLEX = u(0x0302) -- circumflex = ̂
local vowel = "aeiouāēīōū" -- vowel
local consonant = "hkmnŋprtvꞌfs" --consonants
local V = "[" .. vowel .. "]" -- vowel class
local P = "[" .. vowel .. consonant .. "]" -- phoneme class
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- version of rsubn() that returns a 2nd argument boolean indicating whether
-- a substitution was made.
local function rsubb(term, foo, bar)
local retval, nsubs = rsubn(term, foo, bar)
return retval, nsubs > 0
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- Return the number of syllables of a phonemic/orthographic representation
local function get_num_syl(phonemic)
return #rsub(phonemic, "[^" .. vowel .. "]", "")
end
local function split_on_comma(term)
if term:find(",%s") then
return require(put_module).split_on_comma(term)
else
return rsplit(term, ",")
end
end
-- Main syllable-division algorithm
local function syllabify(text)
text = rsub_repeatedly(text, "(" .. V .. ")(" .. P .. ")", "%1.%2")
return text
end
-- Generate the IPA of a given respelling
function export.IPA(text)
text = ulower(text or mw.loadData("Module:headword/data").pagename)
-- convert commas and en/en dashes to IPA foot boundaries
text = rsub(text, "%s*[,–—]%s*", " | ")
-- question mark or exclamation point in the middle of a sentence -> IPA foot boundary
text = rsub(text, "([^%s])%s*[¡!¿?]%s*([^%s])", "%1 | %2")
-- canonicalize multiple spaces and remove leading and trailing spaces
local function canon_spaces(text)
text = rsub(text, "%s+", " ")
text = rsub(text, "^ ", "")
text = rsub(text, " $", "")
return text
end
text = canon_spaces(text)
-- Make prefixes and certain suffixes unstressed by adding CFLEX
local words = rsplit(text, " ")
for i, word in ipairs(words) do
if rfind(word, "%-$") or (rfind(word, "^%-") and (not rfind(word, "[āēīōū]") or get_num_syl(word) < 2)) then
words[i] = rsub(word, "^(.*" .. V .. ")", "%1" .. CFLEX)
end
end
text = table.concat(words, " ")
-- Remove hyphens
text = rsub(text, "%-", "")
-- canonicalize multiple spaces again, which may have been introduced by hyphens
text = canon_spaces(text)
-- now eliminate punctuation
text = rsub(text, "[¡!¿?']", "")
-- put # at word beginning and end and double ## at text/foot boundary beginning/end
text = rsub(text, " | ", "# | #")
text = "##" .. rsub(text, " ", "# #") .. "##"
--syllable division
text = syllabify(text)
local function accent_word(word)
if rfind(word, CFLEX) then return rsub(word, CFLEX, "") end
local syllables = rsplit(word, "%.")
local i = #syllables
local no_stress = true
while i > 1 do
if rfind(syllables[i], "[āēīōū]") then
syllables[i] = "ˈ" .. syllables[i]
i = i - 1
no_stress = false
else
syllables[i-1] = "ˈ" .. syllables[i-1]
i = i - 2
no_stress = false
end
end
if (rfind(syllables[1], "[āēīōū]") and not rfind(syllables[1], "ˈ")) or no_stress then
syllables[1] = "ˈ" .. syllables[1]
end
word = table.concat(syllables, ".")
word = rsub_repeatedly(word, "ˈ(.+)ˈ", "ˌ%1ˈ")
return word
end
local words = rsplit(text, " ")
for j, word in ipairs(words) do
-- accentuation
words[j] = accent_word(word)
end
text = table.concat(words, " ")
text = rsub(text, "%-", "")
--final conversions
local final_conversions = {
["ā"] = "aː",
["ē"] = "eː",
["ī"] = "iː",
["ō"] = "oː",
["ū"] = "uː",
["r"] = "ɾ",
["ꞌ"] = "ʔ"
}
text = rsub(text, "[āēīōūrꞌ]", final_conversions)
text = rsub(text, "#", "")
text = unfc(text)
return {text = text}
end
local function make_ret(dodialect)
local ret = {
pronun = {},
tag = {},
}
dodialect(ret)
ret.tag = tag
return ret
end
local function dodialect_pronun(args, ret)
ret.pronun = {}
for i, term in ipairs(args.terms) do
local phonemic
if term.raw then
phonemic = term.raw_phonemic
else
phonemic = export.IPA(term.term)
phonemic = phonemic.text
end
local refs
if not term.ref then
refs = nil
else
refs = {}
for _, refspec in ipairs(term.ref) do
local this_refs = require("Module:references").parse_references(refspec)
for _, this_ref in ipairs(this_refs) do
table.insert(refs, this_ref)
end
end
end
ret.pronun[i] = {
raw = term.raw,
phonemic = phonemic,
refs = refs,
q = term.q,
qq = term.qq,
a = term.a,
aa = term.aa,
}
end
end
local function generate_pronun(args)
local function this_dodialect_pronun(ret)
dodialect_pronun(args, ret)
end
local ret = make_ret(this_dodialect_pronun)
local pronunciations = {}
-- Loop through each pronunciation.
for j, pronun in ipairs(ret.pronun) do
-- Add tag to left qualifiers if first one
-- FIXME: Consider using accent qualifier for the tag instead.
local qs = pronun.q
if j == 1 and ret.tag then
if qs then
qs = m_table.deepCopy(qs)
table.insert(qs, ret.tag)
else
qs = {ret.tag}
end
end
local first_pronun = #pronunciations + 1
if not pronun.phonemic then
error("Internal error: Saw neither phonemic nor phonetic pronunciation")
end
if pronun.phonemic then
local slash_pron = "/" .. pronun.phonemic:gsub("%.", "") .. "/"
table.insert(pronunciations, {
pron = slash_pron,
})
end
local last_pronun = #pronunciations
if qs then
pronunciations[first_pronun].q = qs
end
if pronun.a then
pronunciations[first_pronun].a = pronun.a
end
if j > 1 then
pronunciations[first_pronun].separator = ", "
end
if pronun.qq then
pronunciations[last_pronun].qq = pronun.qq
end
if pronun.aa then
pronunciations[last_pronun].aa = pronun.aa
end
if pronun.refs then
pronunciations[last_pronun].refs = pronun.refs
end
if first_pronun ~= last_pronun then
pronunciations[last_pronun].separator = " "
end
end
ret.text = string.rep("*", args.bullets) .. " " .. m_IPA.format_IPA_full { lang = lang, items = pronunciations, separator = "" }
return ret
end
local function parse_respelling(respelling, pagename, parse_err)
local raw_respelling = respelling:match("^raw:(.*)$")
if raw_respelling then
local raw_phonemic = raw_respelling:match("^/(.*)/ %[(.*)%]$")
if not raw_phonemic then
raw_phonemic = raw_respelling:match("^/(.*)/$")
end
if not raw_phonemic then
parse_err(("Unable to parse raw respelling '%s', should be one of /.../, [...] or /.../ [...]")
:format(raw_respelling))
end
return {
raw = true,
raw_phonemic = raw_phonemic,
}
end
if respelling == "+" then
respelling = pagename
end
return {term = respelling}
end
-- Get the rhyme by truncating everything up through the last stress mark + any following consonants, and remove
-- syllable boundary markers.
local function convert_phonemic_to_rhyme(phonemic)
return rsub(rsub(phonemic, ".*[ˌˈ]", ""), "^[^" .. vowel .. "]*", ""):gsub("%.", "")
end
local function split_syllabified_spelling(spelling)
return rsplit(spelling, "%.")
end
local function generate_hyph_obj(term)
return {syllabification = term, hyph = split_syllabified_spelling(term)}
end
-- Word should already be decomposed.
local function word_has_vowels(word)
return rfind(word, V)
end
local function all_words_have_vowels(term)
local words = rsplit(term, "[ %-]")
for i, word in ipairs(words) do
-- Allow empty word; this occurs with prefixes and suffixes.
if word ~= "" and not word_has_vowels(word) then
return false
end
end
return true
end
local function should_generate_rhyme_from_respelling(term)
local words = rsplit(term, " +")
return #words == 1 and -- no if multiple words
not words[1]:find("%-$") and -- no if word is a prefix
not (words[1]:find("^%-") and not rfind(words[1], "[āēīōū]") and get_num_syl(words[1]) < 2) and -- no if word is an unstressed suffix
word_has_vowels(words[1]) -- no if word has no vowels (e.g. a single letter)
end
local function should_generate_rhyme_from_ipa(ipa)
return not ipa:find("%s") and word_has_vowels(ipa)
end
local function dodialect_specified_rhymes(rhymes, hyphs, parsed_respellings, rhyme_ret)
rhyme_ret.pronun = {}
for _, rhyme in ipairs(rhymes) do
local num_syl = rhyme.num_syl
local no_num_syl = false
-- If user explicitly gave the rhyme but didn't explicitly specify the number of syllables, try to take it from
-- the phonemic.
if not no_num_syl and not num_syl then
for _, parsed in ipairs(parsed_respellings) do
-- Check that pronun.phonemic exists (it may not if raw phonetic-only pronun is given).
if parsed.pronun.pronun[1].phonemic then
if not should_generate_rhyme_from_ipa(parsed.pronun.pronun[1].phonemic) then
no_num_syl = true
break
end
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local this_num_syl = get_num_syl(parsed.pronun.pronun[1].phonemic)
m_table.insertIfNot(num_syl, this_num_syl)
end
end
if no_num_syl or #num_syl == 0 then
num_syl = nil
end
end
table.insert(rhyme_ret.pronun, {
rhyme = rhyme.rhyme,
num_syl = num_syl,
q = rhyme.q,
})
end
end
local function parse_pron_modifier(arg, parse_err, generate_obj, param_mods, no_split_on_comma)
local retval = {}
if arg:find("<") then
local insert = { store = "insert" }
param_mods.q = insert
param_mods.qq = insert
param_mods.a = insert
param_mods.aa = insert
return require(put_module).parse_inline_modifiers(arg, {
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = parse_err,
splitchar = not no_split_on_comma and "," or nil,
})
elseif no_split_on_comma then
table.insert(retval, generate_obj(arg))
else
for _, term in ipairs(split_on_comma(arg)) do
table.insert(retval, generate_obj(term))
end
end
return retval
end
local function parse_rhyme(arg, parse_err)
local function generate_obj(term)
return {rhyme = term}
end
local param_mods = {
s = {
item_dest = "num_syl",
convert = function(arg, parse_err)
local nsyls = rsplit(arg, ",")
for i, nsyl in ipairs(nsyls) do
if not nsyl:find("^[0-9]+$") then
parse_err("Number of syllables '" .. nsyl .. "' should be numeric")
end
nsyls[i] = tonumber(nsyl)
end
return nsyls
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function parse_hyph(arg, parse_err)
-- None other than decorations
local param_mods = {}
return parse_pron_modifier(arg, parse_err, generate_hyph_obj, param_mods)
end
local function parse_homophone(arg, parse_err)
local function generate_obj(term)
return {term = term}
end
local param_mods = {
t = {
-- We need to store the <t:...> inline modifier into the "gloss" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "gloss",
},
gloss = {},
pos = {},
alt = {},
lit = {},
id = {},
g = {
-- We need to store the <g:...> inline modifier into the "genders" key of the parsed term,
-- because that is what [[Module:links]] (called from [[Module:homophones]]) expects.
item_dest = "genders",
convert = function(arg)
return rsplit(arg, ",")
end,
},
}
return parse_pron_modifier(arg, parse_err, generate_obj, param_mods)
end
local function generate_audio_obj(arg)
local file, gloss
if arg:find("#") then
file, gloss = arg:match("^(.-)%s*#%s*(.*)$")
else
file, gloss = arg:match("^(.-)%s*;%s*(.*)$")
end
if not file then
file = arg
gloss = "Audio"
end
return {file = file, gloss = gloss}
end
local function parse_audio(arg, parse_err)
-- None other than decorations
local param_mods = {}
-- Don't split on comma because some filenames have embedded commas not followed by a space
-- (typically followed by an underscore).
return parse_pron_modifier(arg, parse_err, generate_audio_obj, param_mods, "no split on comma")
end
-- External entry point for {{fax-pr}}.
function export.show_pr(frame)
local params = {
[1] = {list = true},
["rhyme"] = {},
["hyph"] = {},
["hmp"] = {},
["audio"] = {list = true},
["pagename"] = {},
}
local parargs = frame:getParent().args
local args = require("Module:parameters").process(parargs, params)
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
-- Parse the arguments.
local respellings = #args[1] > 0 and args[1] or {"+"}
local parsed_respellings = {}
local function overall_parse_err(msg, arg, val)
error(msg .. ": " .. arg .. "= " .. val)
end
local overall_rhyme = args.rhyme and
parse_rhyme(args.rhyme, function(msg) overall_parse_err(msg, "rhyme", args.rhyme) end) or nil
local overall_hyph = args.hyph and
parse_hyph(args.hyph, function(msg) overall_parse_err(msg, "hyph", args.hyph) end) or nil
local overall_hmp = args.hmp and
parse_homophone(args.hmp, function(msg) overall_parse_err(msg, "hmp", args.hmp) end) or nil
local overall_audio
if args.audio then
overall_audio = {}
for _, audio in ipairs(args.audio) do
local parsed_audio = parse_audio(audio, function(msg) overall_parse_err(msg, "audio", audio) end)
if #parsed_audio > 1 then
error("Internal error: Saw more than one object returned from parse_audio")
end
table.insert(overall_audio, parsed_audio[1])
end
end
for i, respelling in ipairs(respellings) do
if respelling:find("<") then
local param_mods = {
pre = { overall = true },
post = { overall = true },
bullets = {
overall = true,
convert = function(arg, parse_err)
if not arg:find("^[0-9]+$") then
parse_err("Modifier 'bullets' should have a number as argument, but saw '" .. arg .. "'")
end
return tonumber(arg)
end,
},
rhyme = {
overall = true,
store = "insert-flattened",
convert = parse_rhyme,
},
hyph = {
overall = true,
store = "insert-flattened",
convert = parse_hyph,
},
hmp = {
overall = true,
store = "insert-flattened",
convert = parse_homophone,
},
audio = {
overall = true,
store = "insert-flattened",
convert = parse_audio,
},
ref = { store = "insert" },
q = { store = "insert" },
qq = { store = "insert" },
a = { store = "insert" },
aa = { store = "insert" },
}
local parsed = require(put_module).parse_inline_modifiers(respelling, {
paramname = i,
param_mods = param_mods,
generate_obj = function(term, parse_err)
return parse_respelling(term, pagename, parse_err)
end,
splitchar = ",",
outer_container = {
audio = {}, rhyme = {}, hyph = {}, hmp = {}
}
})
if not parsed.bullets then
parsed.bullets = 1
end
table.insert(parsed_respellings, parsed)
else
local termobjs = {}
local function parse_err(msg)
error(msg .. ": " .. i .. "= " .. respelling)
end
for _, term in ipairs(split_on_comma(respelling)) do
table.insert(termobjs, parse_respelling(term, pagename, parse_err))
end
table.insert(parsed_respellings, {
terms = termobjs,
audio = {},
rhyme = {},
hyph = {},
hmp = {},
bullets = 1,
})
end
end
if overall_hyph then
local hyphs = {}
for _, hyph in ipairs(overall_hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
overall_hyph = {}
break
end
end
end
-- Loop over individual respellings, processing each.
for _, parsed in ipairs(parsed_respellings) do
parsed.pronun = generate_pronun(parsed)
local no_auto_rhyme = false
for _, term in ipairs(parsed.terms) do
if term.raw then
if not should_generate_rhyme_from_ipa(term.raw_phonemic) then
no_auto_rhyme = true
break
end
elseif not should_generate_rhyme_from_respelling(term.term) then
no_auto_rhyme = true
break
end
end
if #parsed.hyph == 0 then
if not overall_hyph and all_words_have_vowels(pagename) then
for _, term in ipairs(parsed.terms) do
if not term.raw then
local syllabification = syllabify(term.term)
if term.term == pagename then
m_table.insertIfNot(parsed.hyph, generate_hyph_obj(syllabification))
end
end
end
end
else
for _, hyph in ipairs(parsed.hyph) do
if hyph.syllabification == "+" then
hyph.syllabification = syllabify(pagename)
hyph.hyph = split_syllabified_spelling(hyph.syllabification)
elseif hyph.syllabification == "-" then
parsed.hyph = {}
break
end
end
end
-- Generate the rhymes.
local function dodialect_rhymes_from_pronun(rhyme_ret)
rhyme_ret.pronun = {}
for _, pronun in ipairs(parsed.pronun.pronun) do
-- We should have already excluded multiword terms and terms without vowels from rhyme generation (see
-- `no_auto_rhyme` below). But make sure to check that pronun.phonemic exists (it may not if raw
-- phonetic-only pronun is given).
if pronun.phonemic then
-- Count number of syllables by looking at syllable boundaries (including stress marks).
local num_syl = get_num_syl(pronun.phonemic)
-- Get the rhyme by truncating everything up through the last stress mark + any following
-- consonants, and remove syllable boundary markers.
local rhyme = convert_phonemic_to_rhyme(pronun.phonemic)
local saw_already = false
for _, existing in ipairs(rhyme_ret.pronun) do
if existing.rhyme == rhyme then
saw_already = true
-- We already saw this rhyme but possibly with a different number of syllables
m_table.insertIfNot(existing.num_syl, num_syl)
break
end
end
if not saw_already then
table.insert(rhyme_ret.pronun, {
rhyme = rhyme,
num_syl = {num_syl},
})
end
end
end
end
if #parsed.rhyme == 0 then
if overall_rhyme or no_auto_rhyme then
parsed.rhyme = nil
else
parsed.rhyme = make_ret(dodialect_rhymes_from_pronun)
end
else
local no_rhyme = false
for _, rhyme in ipairs(parsed.rhyme) do
if rhyme.rhyme == "-" then
no_rhyme = true
break
end
end
if no_rhyme then
parsed.rhyme = nil
else
local function this_dodialect(rhyme_ret, dialect)
return dodialect_specified_rhymes(parsed.rhyme, parsed.hyph, {parsed}, rhyme_ret)
end
parsed.rhyme = make_ret(this_dodialect)
end
end
end
if overall_rhyme then
local no_overall_rhyme = false
for _, orhyme in ipairs(overall_rhyme) do
if orhyme.rhyme == "-" then
no_overall_rhyme = true
break
end
end
if no_overall_rhyme then
overall_rhyme = nil
else
local all_hyphs
if overall_hyph then
all_hyphs = overall_hyph
else
all_hyphs = {}
for _, parsed in ipairs(parsed_respellings) do
for _, hyph in ipairs(parsed.hyph) do
m_table.insertIfNot(all_hyphs, hyph)
end
end
end
local function dodialect_overall_rhyme(rhyme_ret)
return dodialect_specified_rhymes(overall_rhyme, all_hyphs, parsed_respellings, rhyme_ret)
end
overall_rhyme = make_ret(dodialect_overall_rhyme)
end
end
-- If all sets of pronunciations have the same rhymes, display them only once at the bottom.
-- Otherwise, display rhymes beneath each set, indented.
local first_rhyme_ret
local all_rhyme_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_rhyme_ret = parsed.rhyme
elseif not m_table.deepEquals(first_rhyme_ret, parsed.rhyme) then
all_rhyme_sets_eq = false
break
end
end
local function format_rhyme(rhyme_ret, num_bullets)
local rhymes = {}
for _, pron in ipairs(rhyme_ret.pronun) do
table.insert(rhymes, pron)
end
local data = {
lang = lang,
rhymes = rhymes,
q = rhyme_ret.tag and {rhyme_ret.tag} or nil,
}
return string.rep("*", num_bullets) .. " " .. require("Module:rhymes").format_rhymes(data)
end
-- If all sets of pronunciations have the same hyphenations, display them only once at the bottom.
-- Otherwise, display hyphenations beneath each set, indented.
local first_hyphs
local all_hyph_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hyphs = parsed.hyph
elseif not m_table.deepEquals(first_hyphs, parsed.hyph) then
all_hyph_sets_eq = false
break
end
end
local function format_hyphenations(hyphs, num_bullets)
local hyphtext = require("Module:hyphenation").format_hyphenations { lang = lang, hyphs = hyphs }
return string.rep("*", num_bullets) .. " " .. hyphtext
end
-- If all sets of pronunciations have the same homophones, display them only once at the bottom.
-- Otherwise, display homophones beneath each set, indented.
local first_hmps
local all_hmp_sets_eq = true
for j, parsed in ipairs(parsed_respellings) do
if j == 1 then
first_hmps = parsed.hmp
elseif not m_table.deepEquals(first_hmps, parsed.hmp) then
all_hmp_sets_eq = false
break
end
end
local function format_homophones(hmps, num_bullets)
local hmptext = require("Module:homophones").format_homophones { lang = lang, homophones = hmps }
return string.rep("*", num_bullets) .. " " .. hmptext
end
local function format_audio(audios, num_bullets)
local ret = {}
for i, audio in ipairs(audios) do
local text = require(audio_module).format_audio {
lang = lang,
file = audio.file,
caption = audio.gloss,
q = audio.q,
qq = audio.qq,
a = audio.a,
aa = audio.aa,
}
table.insert(ret, string.rep("*", num_bullets) .. " " .. text)
end
return table.concat(ret, "\n")
end
local textparts = {}
local min_num_bullets = math.huge
for j, parsed in ipairs(parsed_respellings) do
if parsed.bullets < min_num_bullets then
min_num_bullets = parsed.bullets
end
if j > 1 then
table.insert(textparts, "\n")
end
table.insert(textparts, parsed.pronun.text)
if #parsed.audio > 0 then
table.insert(textparts, "\n")
-- If only one pronunciation set, add the audio with the same number of bullets, otherwise
-- indent audio by one more bullet.
table.insert(textparts, format_audio(parsed.audio,
#parsed_respellings == 1 and parsed.bullets or parsed.bullets + 1))
end
if not all_rhyme_sets_eq and parsed.rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(parsed.rhyme, parsed.bullets + 1))
end
if not all_hyph_sets_eq and #parsed.hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(parsed.hyph, parsed.bullets + 1))
end
if not all_hmp_sets_eq and #parsed.hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(parsed.hmp, parsed.bullets + 1))
end
end
if overall_audio and #overall_audio > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_audio(overall_audio, min_num_bullets))
end
if all_rhyme_sets_eq and first_rhyme_ret then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(first_rhyme_ret, min_num_bullets))
end
if overall_rhyme then
table.insert(textparts, "\n")
table.insert(textparts, format_rhyme(overall_rhyme, min_num_bullets))
end
if all_hyph_sets_eq and #first_hyphs > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(first_hyphs, min_num_bullets))
end
if overall_hyph and #overall_hyph > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_hyphenations(overall_hyph, min_num_bullets))
end
if all_hmp_sets_eq and #first_hmps > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(first_hmps, min_num_bullets))
end
if overall_hmp and #overall_hmp > 0 then
table.insert(textparts, "\n")
table.insert(textparts, format_homophones(overall_hmp, min_num_bullets))
end
return table.concat(textparts)
end
return export
mjkquk4boh1h4ul1d62azpon6qgk35y
Module:Place/locations
828
3437749
9909173
9906083
2026-09-29T09:13:03Z
TongcyBot
83009
更新至最新修正版
9909173
Scribunto
text/plain
local export = {}
export.force_cat = false
local m_table = require("Module:table")
local insert = table.insert
local concat = table.concat
local dump = mw.dumpObject
local unpack = unpack or table.unpack
--[==[
Chinese Wiktionary localization of [[Module:place/locations]].
All canonical location keys are in Chinese, matching what editors write in holonym parameters
(e.g. {{place|zh|城市|s/中央邦|c/印度}}). English Wikipedia article names are stored in the
`wp` field for cross-reference. The `the`, `british_spelling` and English-suffix-detection
fields from enwikt are omitted as they are not applicable in Chinese.
The helper functions (process_error through construct_linked_placename) are kept intact from
enwikt's Module:place/locations to maintain API compatibility with Module:place/placetypes.
]==]
-----------------------------------------------------------------------------------
-- Helper functions --
-----------------------------------------------------------------------------------
function export.process_error(fmt, ...)
local args = {...}
for i = 1, select("#", ...) do
args[i] = dump(args[i])
end
return error(string.format(fmt, unpack(args)))
end
function export.internal_error(fmt, ...)
export.process_error("Internal error: " .. fmt, ...)
end
local internal_error = export.internal_error
local function list_or_element_contains(list_or_element, item)
if type(list_or_element) == "table" then
return m_table.contains(list_or_element, item) and true or false
end
return list_or_element == item
end
function export.key_to_placename(group, key)
if group.key_to_placename == false then
return key, key
end
if group.key_to_placename then
local full_placename, elliptical_placename = group.key_to_placename(key)
if type(full_placename) ~= "string" then
internal_error("Key %s returned a non-string full placename: %s", key, full_placename)
end
if type(elliptical_placename) ~= "string" then
internal_error("Key %s returned a non-string elliptical placename: %s", key, elliptical_placename)
end
return full_placename, elliptical_placename
end
key = key:gsub(",.*", "")
return key, key
end
function export.placename_to_key(group, placename)
if group.placename_to_key == false then
return placename
elseif group.placename_to_key then
local key = group.placename_to_key(placename)
if type(key) ~= "string" then
internal_error("Placename %s returned a non-string key: %s", placename, key)
end
return key
elseif group.default_placetype == "city" then
return placename
else
local defcon = group.default_container
if not defcon then
return placename
elseif type(defcon) == "string" then
return placename .. ", " .. defcon
elseif type(defcon) == "table" and (defcon.placetype == "country" or
defcon.placetype == "constituent country") then
return placename .. ", " .. defcon.key
else
return placename
end
end
end
function export.initialize_spec(group, key, spec)
if spec.initialized then
return
end
local container = spec.container
local containers
local container_from_default
if not container then
container = group.default_container
container_from_default = true
end
if container then
if type(container) == "string" or container.key then
container = {container}
end
containers = {}
for _, cont in ipairs(container) do
if type(cont) == "string" then
if group.canonicalize_key_container and not container_from_default then
cont = group.canonicalize_key_container(cont)
else
cont = {key = cont, placetype = "country"}
end
end
insert(containers, cont)
end
end
spec.containers = containers
spec.container = nil
local function value_with_default(val, default_val)
if val == nil then
return default_val
else
return val
end
end
local function set_or_default(prop)
spec[prop] = value_with_default(spec[prop], group["default_" .. prop])
end
set_or_default("placetype")
if not spec.placetype then
internal_error("No placetype found in key %s for spec %s or in group `default_placetype`", key, spec)
end
set_or_default("divs")
spec.addl_divs = group.addl_divs
for _, prop in ipairs {
"keydesc", "fulldesc", "addl_parents", "overriding_bare_label_parents",
"bare_category_parent_type", "wp", "wpcat", "commonscat", "the",
"no_container_cat", "no_container_parent", "no_generic_place_cat",
"no_check_holonym_mismatch", "no_auto_augment_container",
"no_include_container_in_desc", "is_city", "is_former_place",
} do
set_or_default(prop)
end
spec.is_city = value_with_default(spec.is_city, group.default_placetype == "city")
spec.initialized = true
end
local function find_matching_key_in_group(group, placetypes, key, alias_resolution)
if alias_resolution ~= nil and alias_resolution ~= "none" and alias_resolution ~= "display" and
alias_resolution ~= "all" then
internal_error("Bad value for 'alias_resolution': %s", alias_resolution)
end
local spec = group.data[key]
if not spec then
return nil
end
local function check_correct_placetype(placetype)
if type(placetype) == "table" then
for _, pt in ipairs(placetype) do
if list_or_element_contains(placetypes, pt) then
return true
end
end
return false
else
return list_or_element_contains(placetypes, placetype)
end
end
if spec.alias_of then
local resolved_key = spec.alias_of
local resolved_spec = group.data[resolved_key]
if not resolved_spec then
internal_error("Key %s is an alias of %s, which doesn't exist", key, resolved_key)
elseif resolved_spec.alias_of then
internal_error("Key %s is an alias of %s, which is itself an alias; indirect aliasing not allowed",
key, resolved_key)
end
if alias_resolution == "none" or alias_resolution == "display" then
local placetype = spec.placetype or resolved_spec.placetype or group.default_placetype
if not placetype then
internal_error("No placetype found for key %s", key)
end
if not check_correct_placetype(placetype) then
return nil
end
if alias_resolution == "display" then
if spec.display == true then
key = resolved_key
elseif spec.display then
key = spec.display
end
end
return key, spec
end
key = resolved_key
spec = resolved_spec
end
local placetype = spec.placetype or group.default_placetype
if not placetype then
internal_error("No placetype found for key %s in spec %s or group `default_placetype`", key, spec)
end
if not check_correct_placetype(placetype) then
return nil
end
export.initialize_spec(group, key, spec)
return key, spec
end
local function find_matching_placename_in_group(group, placetypes, placename, alias_resolution)
local key = export.placename_to_key(group, placename)
return find_matching_key_in_group(group, placetypes, key, alias_resolution)
end
function export.find_canonical_key(key)
local found_locations = {}
for _, group in ipairs(export.locations) do
local spec = group.data[key]
if not spec then
-- do nothing
elseif spec.alias_of then
mw.log(("Skipping alias '%s' of canonical '%s'"):format(key, spec.alias_of))
else
insert(found_locations, {group, spec})
end
end
if not found_locations[1] then
return nil
elseif found_locations[2] then
internal_error("Found multiple matching locations for canonical key %s: %s", key, found_locations)
else
local group, spec = unpack(found_locations[1])
export.initialize_spec(group, key, spec)
return group, spec
end
end
function export.iterate_matching_location(data)
local i = 0
local n = #export.locations
return function()
while true do
i = i + 1
if i > n then
break
end
local group = export.locations[i]
local key, spec
if data.placename then
key, spec = find_matching_placename_in_group(group, data.placetypes, data.placename,
data.alias_resolution)
else
if not data.key then
internal_error("'.placename' or '.key' must be defined: %s", data)
end
key, spec = find_matching_key_in_group(group, data.placetypes, data.key, data.alias_resolution)
end
if key then
return group, key, spec
end
end
end
end
function export.get_matching_location(data)
local all_found = {}
for group, key, spec in export.iterate_matching_location(data) do
insert(all_found, {group, key, spec})
end
if not all_found[1] then
internal_error("Couldn't find matching location for data %s", data)
elseif all_found[2] then
internal_error("Found multiple matching locations for data %s: %s", data, all_found)
else
return unpack(all_found[1])
end
end
function export.iterate_containers(group, key, spec)
local keys_seen = {}
keys_seen[key] = true
local iterations = 0
local last_iteration_containers = {{group = group, key = key, spec = spec}}
return function()
iterations = iterations + 1
if iterations > 10 then
internal_error("Probable loop in containers when processing key %s", key)
end
local next_iteration_containers = {}
for _, location in ipairs(last_iteration_containers) do
local containers = location.spec.containers
if containers then
for _, container in ipairs(containers) do
local container_group, container_key, container_spec = export.get_matching_location {
placetypes = container.placetype,
key = container.key,
}
if not keys_seen[container_key] then
insert(next_iteration_containers, {
group = container_group, key = container_key, spec = container_spec
})
keys_seen[container_key] = true
end
end
end
end
if not next_iteration_containers[1] then
return nil
end
last_iteration_containers = next_iteration_containers
return next_iteration_containers
end
end
function export.construct_linked_placename(spec, placename, display_form)
local linked_placename = display_form and placename ~= display_form and ("[[%s|%s]]"):format(placename,
display_form) or ("[[%s]]"):format(placename)
-- Chinese has no articles; `the` field is unused but kept for API compatibility.
return linked_placename
end
-----------------------------------------------------------------------------------
-- Local data helper factories --
-----------------------------------------------------------------------------------
local function make_key_to_placename(container_patterns, divtype_patterns)
if type(container_patterns) == "string" then
container_patterns = {container_patterns}
end
if type(divtype_patterns) == "string" then
divtype_patterns = {divtype_patterns}
end
return function(key)
local full_placename = key
if container_patterns then
for _, pat in ipairs(container_patterns) do
local nsubs
full_placename, nsubs = full_placename:gsub(pat, "")
if nsubs > 0 then break end
end
end
local elliptical_placename = full_placename
if divtype_patterns then
for _, pat in ipairs(divtype_patterns) do
local nsubs
elliptical_placename, nsubs = elliptical_placename:gsub(pat, "")
if nsubs > 0 then break end
end
end
return full_placename, elliptical_placename
end
end
local function make_placename_to_key(container_suffix, divtype_suffix)
return function(placename)
local key = placename
if divtype_suffix then
if not key:find(divtype_suffix .. "$") then
key = key .. divtype_suffix
end
end
if container_suffix then
key = key .. container_suffix
end
return key
end
end
local function make_canonicalize_key_container(suffix, placetype)
return function(container)
if type(container) == "string" then
return {key = container .. (suffix or ""), placetype = placetype}
else
return container
end
end
end
-- Canonicalize a continent container key (string) to canonical container form.
local function canonicalize_continent_container(key)
if type(key) ~= "string" then
return key
end
if export.continents[key] then
return {key = key, placetype = export.continents[key].placetype}
end
internal_error("Unrecognized key %s in `canonicalize_continent_container`", key)
end
-----------------------------------------------------------------------------------
-- Top-level tables --
-----------------------------------------------------------------------------------
export.continents = {
["地球"] = {placetype = "planet", addl_parents = {"自然"},
fulldesc = "=the planet [[Earth]] and the features found on it", wp = "地球"},
["非洲"] = {placetype = "continent", container = {key = "地球", placetype = "planet"}, wp = "非洲"},
["美洲"] = {placetype = {"supercontinent", "continent"}, container = {key = "地球", placetype = "planet"},
wp = "美洲"},
["北美洲"] = {placetype = "continent", container = {key = "美洲", placetype = "supercontinent"}, wp = "北美洲"},
["加勒比地區"] = {placetype = {"continental region", "region"}, container = {key = "北美洲", placetype = "continent"}, wp = "加勒比地區"},
["中美洲"] = {placetype = {"continental region", "region"}, container = {key = "北美洲", placetype = "continent"}, wp = "中美洲"},
["南美洲"] = {placetype = "continent", container = {key = "美洲", placetype = "supercontinent"}, wp = "南美洲"},
["南極洲"] = {placetype = "continent", container = {key = "地球", placetype = "planet"},
fulldesc = "=the territory of [[Antarctica]]", wp = "南極洲"},
["歐亞大陸"] = {placetype = {"supercontinent", "continent"}, container = {key = "地球", placetype = "planet"}, wp = "歐亞大陸"},
["亞洲"] = {placetype = "continent", container = {key = "歐亞大陸", placetype = "supercontinent"}, wp = "亞洲"},
["歐洲"] = {placetype = "continent", container = {key = "歐亞大陸", placetype = "supercontinent"}, wp = "歐洲"},
["大洋洲"] = {placetype = "continent", container = {key = "地球", placetype = "planet"}, wp = "大洋洲"},
["美拉尼西亞"] = {placetype = {"continental region", "region"}, container = {key = "大洋洲", placetype = "continent"}, wp = "美拉尼西亞"},
["密克羅尼西亞"] = {placetype = {"continental region", "region"}, container = {key = "大洋洲", placetype = "continent"}, wp = "密克羅尼西亞"},
["玻里尼西亞"] = {placetype = {"continental region", "region"}, container = {key = "大洋洲", placetype = "continent"}, wp = "玻里尼西亞"},
}
export.continents_group = {
default_overriding_bare_label_parents = {}, -- needed, don't move
default_divs = {{type = "countries", prep = "in"}},
default_no_include_container_in_desc = true,
default_no_container_cat = true,
default_no_container_parent = true,
default_no_auto_augment_container = true,
default_no_generic_place_cat = true,
default_no_check_holonym_mismatch = true,
data = export.continents,
}
export.countries = {
-- Africa
["阿爾及利亞"] = {container = "非洲", divs = {"provinces", "communes", "districts"}, wp = "阿爾及利亞"},
["安哥拉"] = {container = "非洲", divs = {"provinces", "municipalities"}, wp = "安哥拉"},
["貝寧"] = {container = "非洲", wp = "貝寧"},
["貝南"] = {alias_of = "貝寧"},
["博茨瓦納"] = {container = "非洲", wp = "博茨瓦納"},
["波札那"] = {alias_of = "博茨瓦納"},
["布基納法索"] = {container = "非洲", wp = "布基納法索"},
["布吉納法索"] = {alias_of = "布基納法索"},
["布隆迪"] = {container = "非洲", wp = "布隆迪"},
["蒲隆地"] = {alias_of = "布隆迪"},
["佛得角"] = {container = "非洲", wp = "佛得角"},
["維德角"] = {alias_of = "佛得角"},
["中非共和國"] = {container = "非洲", wp = "中非共和國"},
["乍得"] = {container = "非洲", wp = "乍得"},
["查德"] = {alias_of = "乍得"},
["科摩羅"] = {container = "非洲", wp = "科摩羅"},
["葛摩"] = {alias_of = "科摩羅"},
["剛果共和國"] = {container = "非洲", wp = "剛果共和國"},
["剛果(布)"] = {alias_of = "剛果共和國"},
["剛果民主共和國"] = {container = "非洲", divs = {"provinces"}, wp = "剛果民主共和國"},
["剛果(金)"] = {alias_of = "剛果民主共和國"},
["吉布提"] = {container = "非洲", wp = "吉布提"},
["吉布地"] = {alias_of = "吉布提"},
["埃及"] = {container = "非洲", divs = {"governorates"}, wp = "埃及"},
["赤道幾內亞"] = {container = "非洲", wp = "赤道幾內亞"},
["厄立特里亞"] = {container = "非洲", wp = "厄立特里亞"},
["厄利垂亞"] = {alias_of = "厄立特里亞"},
["斯威士蘭"] = {container = "非洲", wp = "斯威士蘭"},
["史瓦帝尼"] = {alias_of = "斯威士蘭"},
["埃塞俄比亞"] = {container = "非洲", divs = {"regions"}, wp = "埃塞俄比亞"},
["衣索比亞"] = {alias_of = "埃塞俄比亞"},
["加蓬"] = {container = "非洲", wp = "加蓬"},
["加彭"] = {alias_of = "加蓬"},
["岡比亞"] = {container = "非洲", wp = "岡比亞"},
["甘比亞"] = {alias_of = "岡比亞"},
["加納"] = {container = "非洲", wp = "加納"},
["迦納"] = {alias_of = "加納"},
["幾內亞"] = {container = "非洲", wp = "幾內亞"},
["幾內亞比紹"] = {container = "非洲", wp = "幾內亞比紹"},
["幾內亞比索"] = {alias_of = "幾內亞比紹"},
["科特迪瓦"] = {container = "非洲", wp = "科特迪瓦"},
["象牙海岸"] = {alias_of = "科特迪瓦"},
["肯尼亞"] = {container = "非洲", divs = {"counties"}, wp = "肯尼亞"},
["肯亞"] = {alias_of = "肯尼亞"},
["萊索托"] = {container = "非洲", wp = "萊索托"},
["賴索托"] = {alias_of = "萊索托"},
["利比里亞"] = {container = "非洲", wp = "利比里亞"},
["賴比瑞亞"] = {alias_of = "利比里亞"},
["利比亞"] = {container = "非洲", wp = "利比亞"},
["馬達加斯加"] = {container = "非洲", wp = "馬達加斯加"},
["馬拉維"] = {container = "非洲", wp = "馬拉維"},
["馬拉威"] = {alias_of = "馬拉維"},
["馬里"] = {container = "非洲", wp = "馬里"},
["馬利"] = {alias_of = "馬里"},
["毛里塔尼亞"] = {container = "非洲", wp = "毛里塔尼亞"},
["茅利塔尼亞"] = {alias_of = "毛里塔尼亞"},
["毛里求斯"] = {container = "非洲", wp = "毛里求斯"},
["模里西斯"] = {alias_of = "毛里求斯"},
["摩洛哥"] = {container = "非洲", wp = "摩洛哥"},
["莫桑比克"] = {container = "非洲", wp = "莫桑比克"},
["莫三比克"] = {alias_of = "莫桑比克"},
["納米比亞"] = {container = "非洲", wp = "納米比亞"},
["尼日爾"] = {container = "非洲", wp = "尼日爾"},
["尼日"] = {alias_of = "尼日爾"},
["尼日利亞"] = {container = "非洲", divs = {
{type = "states", cat_as = "states and FCT"},
"local government areas",
}, wp = "尼日利亞"},
["奈及利亞"] = {alias_of = "尼日利亞"},
["盧旺達"] = {container = "非洲", wp = "盧旺達"},
["盧安達"] = {alias_of = "盧旺達"},
["聖多美和普林西比"] = {container = "非洲", wp = "聖多美和普林西比"},
["塞內加爾"] = {container = "非洲", wp = "塞內加爾"},
["塞舌爾"] = {container = "非洲", wp = "塞舌爾"},
["塞席爾"] = {alias_of = "塞舌爾"},
["塞拉利昂"] = {container = "非洲", wp = "塞拉利昂"},
["獅子山"] = {alias_of = "塞拉利昂"},
["索馬里"] = {container = "非洲", wp = "索馬里"},
["索馬利亞"] = {alias_of = "索馬里"},
["南非"] = {container = "非洲", divs = {"provinces"}, wp = "南非"},
["南蘇丹"] = {container = "非洲", wp = "南蘇丹"},
["蘇丹"] = {container = "非洲", wp = "蘇丹"},
["坦桑尼亞"] = {container = "非洲", wp = "坦桑尼亞"},
["坦尚尼亞"] = {alias_of = "坦桑尼亞"},
["多哥"] = {container = "非洲", wp = "多哥"},
["突尼斯"] = {container = "非洲", wp = "突尼斯"},
["突尼西亞"] = {alias_of = "突尼斯"},
["烏干達"] = {container = "非洲", wp = "烏干達"},
["贊比亞"] = {container = "非洲", wp = "贊比亞"},
["尚比亞"] = {alias_of = "贊比亞"},
["津巴布韋"] = {container = "非洲", wp = "津巴布韋"},
["辛巴威"] = {alias_of = "津巴布韋"},
-- Asia
["阿富汗"] = {container = "亞洲", wp = "阿富汗"},
["亞美尼亞"] = {container = {"歐洲", "亞洲"}, wp = "亞美尼亞"},
["阿塞拜疆"] = {container = {"歐洲", "亞洲"}, wp = "阿塞拜疆"},
["亞塞拜然"] = {alias_of = "阿塞拜疆"},
["巴林"] = {container = "亞洲", wp = "巴林"},
["孟加拉國"] = {container = "亞洲", divs = {"divisions", "districts"}, wp = "孟加拉國"},
["孟加拉"] = {alias_of = "孟加拉國"},
["不丹"] = {container = "亞洲", wp = "不丹"},
["文萊"] = {container = "亞洲", wp = "文萊"},
["汶萊"] = {alias_of = "文萊"},
["柬埔寨"] = {container = "亞洲", wp = "柬埔寨"},
["中國"] = {container = "亞洲", divs = {
{type = "provinces", cat_as = "provinces and autonomous regions"},
{type = "autonomous regions", cat_as = "provinces and autonomous regions"},
"direct-administered municipalities",
"special administrative regions",
"prefecture-level cities",
"districts",
"subdistricts",
"townships",
{type = "counties", cat_as = "counties and county-level cities"},
{type = "county-level cities", cat_as = "counties and county-level cities"},
}, wp = "中國"},
["東帝汶"] = {container = "亞洲", wp = "東帝汶"},
["帝汶島"] = {alias_of = "東帝汶"},
["印度"] = {container = "亞洲", divs = {
{type = "states", cat_as = "states and union territories"},
{type = "union territories", cat_as = "states and union territories"},
}, wp = "印度"},
["印度尼西亞"] = {container = "亞洲", divs = {"provinces"}, wp = "印度尼西亞"},
["印尼"] = {alias_of = "印度尼西亞"},
["伊朗"] = {container = "亞洲", wp = "伊朗"},
["伊拉克"] = {container = "亞洲", wp = "伊拉克"},
["以色列"] = {container = "亞洲", wp = "以色列"},
["日本"] = {container = "亞洲", divs = {"prefectures"}, wp = "日本"},
["約旦"] = {container = "亞洲", wp = "約旦"},
["哈薩克斯坦"] = {container = {"歐洲", "亞洲"}, wp = "哈薩克斯坦"},
["哈薩克"] = {alias_of = "哈薩克斯坦"},
["朝鮮"] = {container = "亞洲", wp = "朝鮮民主主義人民共和國"},
["北韓"] = {alias_of = "朝鮮"},
["韓國"] = {container = "亞洲", divs = {"provinces", "metropolitan cities", "counties", "districts"}, wp = "韓國"},
["南韓"] = {alias_of = "韓國"},
["科威特"] = {container = "亞洲", wp = "科威特"},
["吉爾吉斯斯坦"] = {container = "亞洲", wp = "吉爾吉斯斯坦"},
["吉爾吉斯"] = {alias_of = "吉爾吉斯斯坦"},
["老撾"] = {container = "亞洲", wp = "老撾"},
["寮國"] = {alias_of = "老撾"},
["黎巴嫩"] = {container = "亞洲", wp = "黎巴嫩"},
["馬來西亞"] = {container = "亞洲", divs = {"states"}, wp = "馬來西亞"},
["馬爾代夫"] = {container = "亞洲", wp = "馬爾代夫"},
["馬爾地夫"] = {alias_of = "馬爾代夫"},
["蒙古"] = {container = "亞洲", wp = "蒙古"},
["緬甸"] = {container = "亞洲", wp = "緬甸"},
["尼泊爾"] = {container = "亞洲", wp = "尼泊爾"},
["阿曼"] = {container = "亞洲", wp = "阿曼"},
["巴基斯坦"] = {container = "亞洲", divs = {"provinces"}, wp = "巴基斯坦"},
["菲律賓"] = {container = "亞洲", divs = {"regions"}, wp = "菲律賓"},
["卡塔爾"] = {container = "亞洲", wp = "卡塔爾"},
["卡達"] = {alias_of = "卡塔爾"},
["俄羅斯"] = {container = {"歐洲", "亞洲"}, divs = {"federal subjects"}, wp = "俄羅斯"},
["俄國"] = {alias_of = "俄羅斯"},
["沙特阿拉伯"] = {container = "亞洲", wp = "沙特阿拉伯"},
["沙烏地阿拉伯"] = {alias_of = "沙特阿拉伯"},
["新加坡"] = {container = "亞洲", is_city = true, wp = "新加坡"},
["斯里蘭卡"] = {container = "亞洲", wp = "斯里蘭卡"},
["敘利亞"] = {container = "亞洲", wp = "敘利亞"},
["臺灣"] = {container = "亞洲", divs = {
"special municipalities",
"counties",
}, wp = "臺灣"},
["台灣"] = {alias_of = "臺灣"},
["塔吉克斯坦"] = {container = "亞洲", wp = "塔吉克斯坦"},
["塔吉克"] = {alias_of = "塔吉克斯坦"},
["泰國"] = {container = "亞洲", divs = {"provinces"}, wp = "泰國"},
["土耳其"] = {container = {"歐洲", "亞洲"}, wp = "土耳其"},
["土庫曼斯坦"] = {container = "亞洲", wp = "土庫曼斯坦"},
["土庫曼"] = {alias_of = "土庫曼斯坦"},
["阿拉伯聯合酋長國"] = {container = "亞洲", wp = "阿拉伯聯合酋長國"},
["阿拉伯聯合大公國"] = {alias_of = "阿拉伯聯合酋長國"},
["阿聯酋"] = {alias_of = "阿拉伯聯合酋長國"},
["烏茲別克斯坦"] = {container = "亞洲", wp = "烏茲別克斯坦"},
["烏茲別克"] = {alias_of = "烏茲別克斯坦"},
["越南"] = {container = "亞洲", divs = {"provinces", "municipalities"}, wp = "越南"},
["也門"] = {container = "亞洲", wp = "也門"},
["葉門"] = {alias_of = "也門"},
-- Europe
["阿爾巴尼亞"] = {container = "歐洲", wp = "阿爾巴尼亞"},
["安道爾"] = {container = "歐洲", wp = "安道爾"},
["奧地利"] = {container = "歐洲", divs = {"states"}, wp = "奧地利"},
["白俄羅斯"] = {container = "歐洲", divs = {"regions"}, wp = "白俄羅斯"},
["比利時"] = {container = "歐洲", divs = {"regions", "provinces"}, wp = "比利時"},
["波斯尼亞和黑塞哥維那"] = {container = "歐洲", wp = "波斯尼亞和黑塞哥維那"},
["波士尼亞與赫塞哥維納"] = {alias_of = "波斯尼亞和黑塞哥維那"},
["保加利亞"] = {container = "歐洲", wp = "保加利亞"},
["克羅地亞"] = {container = "歐洲", wp = "克羅地亞"},
["克羅埃西亞"] = {alias_of = "克羅地亞"},
["塞浦路斯"] = {container = "亞洲", addl_parents = {"歐洲"}, wp = "塞浦路斯"},
["賽普勒斯"] = {alias_of = "塞浦路斯"},
["捷克"] = {container = "歐洲", wp = "捷克"},
["丹麥"] = {container = "歐洲", divs = {"regions", "municipalities", "dependent territories"}, wp = "丹麥"},
["愛沙尼亞"] = {container = "歐洲", wp = "愛沙尼亞"},
["芬蘭"] = {container = "歐洲", divs = {"regions"}, wp = "芬蘭"},
["法國"] = {container = "歐洲", divs = {
{type = "regions", cat_as = "metropolitan regions"},
"departments",
"communes",
}, wp = "法國"},
["格魯吉亞"] = {container = {"歐洲", "亞洲"}, wp = "格魯吉亞"},
["喬治亞"] = {alias_of = "格魯吉亞"},
["德國"] = {container = "歐洲", divs = {"states"}, wp = "德國"},
["希臘"] = {container = "歐洲", divs = {"regions"}, wp = "希臘"},
["匈牙利"] = {container = "歐洲", wp = "匈牙利"},
["冰島"] = {container = "歐洲", wp = "冰島"},
["愛爾蘭"] = {container = "歐洲", divs = {"provinces", "counties"}, wp = "愛爾蘭"},
["意大利"] = {container = "歐洲", divs = {"regions"}, wp = "意大利"},
["義大利"] = {alias_of = "意大利"},
["科索沃"] = {container = "歐洲", wp = "科索沃"},
["拉脫維亞"] = {container = "歐洲", wp = "拉脫維亞"},
["拉托維亞"] = {alias_of = "拉脫維亞"},
["列支敦士登"] = {container = "歐洲", wp = "列支敦士登"},
["列支敦斯登"] = {alias_of = "列支敦士登"},
["立陶宛"] = {container = "歐洲", wp = "立陶宛"},
["盧森堡"] = {container = "歐洲", wp = "盧森堡"},
["北馬其頓"] = {container = "歐洲", wp = "北馬其頓"},
["馬耳他"] = {container = "歐洲", wp = "馬耳他"},
["馬爾他"] = {alias_of = "馬耳他"},
["摩爾多瓦"] = {container = "歐洲", wp = "摩爾多瓦"},
["摩納哥"] = {container = "歐洲", is_city = true, wp = "摩納哥"},
["黑山"] = {container = "歐洲", wp = "黑山"},
["蒙特內哥羅"] = {alias_of = "黑山"},
["荷蘭"] = {container = "歐洲", divs = {"provinces", {type = "constituent countries", cat_as = "constituent countries"}}, wp = "荷蘭"},
["尼德蘭"] = {alias_of = "荷蘭"},
["挪威"] = {container = "歐洲", wp = "挪威"},
["波蘭"] = {container = "歐洲", divs = {"voivodeships"}, wp = "波蘭"},
["葡萄牙"] = {container = "歐洲", divs = {"districts"}, wp = "葡萄牙"},
["羅馬尼亞"] = {container = "歐洲", wp = "羅馬尼亞"},
["聖馬力諾"] = {container = "歐洲", wp = "聖馬力諾"},
["聖馬利諾"] = {alias_of = "聖馬力諾"},
["塞爾維亞"] = {container = "歐洲", wp = "塞爾維亞"},
["斯洛伐克"] = {container = "歐洲", wp = "斯洛伐克"},
["斯洛文尼亞"] = {container = "歐洲", wp = "斯洛文尼亞"},
["斯洛維尼亞"] = {alias_of = "斯洛文尼亞"},
["西班牙"] = {container = "歐洲", divs = {"autonomous communities"}, wp = "西班牙"},
["瑞典"] = {container = "歐洲", wp = "瑞典"},
["瑞士"] = {container = "歐洲", divs = {"cantons"}, wp = "瑞士"},
["烏克蘭"] = {container = "歐洲", divs = {"oblasts"}, wp = "烏克蘭"},
["英國"] = {container = "歐洲", divs = {
{type = "constituent countries", cat_as = "constituent countries"},
}, wp = "英國"},
-- North America
["加拿大"] = {container = "北美洲", divs = {
{type = "provinces", cat_as = "provinces and territories"},
{type = "territories", cat_as = "provinces and territories"},
}, wp = "加拿大"},
["墨西哥"] = {container = "北美洲", divs = {"states"}, wp = "墨西哥"},
["美國"] = {container = "北美洲", divs = {"states"}, wp = "美國"},
-- Central America
["伯利茲"] = {container = "中美洲", wp = "伯利茲"},
["貝里斯"] = {alias_of = "伯利茲"},
["哥斯達黎加"] = {container = "中美洲", wp = "哥斯達黎加"},
["哥斯大黎加"] = {alias_of = "哥斯達黎加"},
["薩爾瓦多"] = {container = "中美洲", wp = "薩爾瓦多"},
["危地馬拉"] = {container = "中美洲", wp = "危地馬拉"},
["瓜地馬拉"] = {alias_of = "危地馬拉"},
["洪都拉斯"] = {container = "中美洲", wp = "洪都拉斯"},
["宏都拉斯"] = {alias_of = "洪都拉斯"},
["尼加拉瓜"] = {container = "中美洲", wp = "尼加拉瓜"},
["巴拿馬"] = {container = "中美洲", wp = "巴拿馬"},
-- Caribbean
["安提瓜和巴布達"] = {container = "加勒比地區", wp = "安提瓜和巴布達"},
["安地卡及巴布達"] = {alias_of = "安提瓜和巴布達"},
["巴哈馬"] = {container = "加勒比地區", wp = "巴哈馬"},
["巴巴多斯"] = {container = "加勒比地區", wp = "巴巴多斯"},
["巴貝多"] = {alias_of = "巴巴多斯"},
["古巴"] = {container = "加勒比地區", wp = "古巴"},
["多米尼克"] = {container = "加勒比地區", wp = "多米尼克"},
["多米尼加共和國"] = {container = "加勒比地區", wp = "多米尼加"},
["多明尼加共和國"] = {alias_of = "多米尼加共和國"},
["格林納達"] = {container = "加勒比地區", wp = "格林納達"},
["格瑞那達"] = {alias_of = "格林納達"},
["海地"] = {container = "加勒比地區", wp = "海地"},
["牙買加"] = {container = "加勒比地區", wp = "牙買加"},
["聖基茨和尼維斯"] = {container = "加勒比地區", wp = "聖基茨和尼維斯"},
["聖克里斯多福及尼維斯"] = {alias_of = "聖基茨和尼維斯"},
["聖盧西亞"] = {container = "加勒比地區", wp = "聖盧西亞"},
["聖露西亞"] = {alias_of = "聖盧西亞"},
["聖文森特和格林納丁斯"] = {container = "加勒比地區", wp = "聖文森特和格林納丁斯"},
["聖文森及格瑞那丁"] = {alias_of = "聖文森特和格林納丁斯"},
["特立尼達和多巴哥"] = {container = "加勒比地區", wp = "特立尼達和多巴哥"},
["千里達及托巴哥"] = {alias_of = "特立尼達和多巴哥"},
-- South America
["阿根廷"] = {container = "南美洲", divs = {"provinces"}, wp = "阿根廷"},
["玻利維亞"] = {container = "南美洲", wp = "玻利維亞"},
["巴西"] = {container = "南美洲", divs = {"states"}, wp = "巴西"},
["智利"] = {container = "南美洲", wp = "智利"},
["哥倫比亞"] = {container = "南美洲", wp = "哥倫比亞"},
["厄瓜多爾"] = {container = "南美洲", wp = "厄瓜多爾"},
["厄瓜多"] = {alias_of = "厄瓜多爾"},
["圭亞那"] = {container = "南美洲", wp = "圭亞那"},
["蓋亞那"] = {alias_of = "圭亞那"},
["巴拉圭"] = {container = "南美洲", wp = "巴拉圭"},
["秘魯"] = {container = "南美洲", wp = "秘魯"},
["蘇里南"] = {container = "南美洲", wp = "蘇里南"},
["蘇利南"] = {alias_of = "蘇里南"},
["烏拉圭"] = {container = "南美洲", wp = "烏拉圭"},
["委內瑞拉"] = {container = "南美洲", wp = "委內瑞拉"},
-- Oceania
["澳大利亞"] = {container = "大洋洲", divs = {
{type = "states", cat_as = "states and territories"},
{type = "territories", cat_as = "states and territories"},
}, wp = "澳大利亞"},
["澳洲"] = {alias_of = "澳大利亞"},
["斐濟"] = {container = "大洋洲", wp = "斐濟"},
["基里巴斯"] = {container = "大洋洲", wp = "基里巴斯"},
["吉里巴斯"] = {alias_of = "基里巴斯"},
["馬紹爾群島"] = {container = "大洋洲", wp = "馬紹爾群島"},
["密克羅尼西亞聯邦"] = {container = "大洋洲", wp = "密克羅尼西亞聯邦"},
["瑙魯"] = {container = "大洋洲", wp = "瑙魯"},
["諾魯"] = {alias_of = "瑙魯"},
["新西蘭"] = {container = "大洋洲", wp = "新西蘭"},
["紐西蘭"] = {alias_of = "新西蘭"},
["帕勞"] = {container = "大洋洲", wp = "帕勞"},
["帛琉"] = {alias_of = "帕勞"},
["巴布亞新幾內亞"] = {container = "大洋洲", wp = "巴布亞新幾內亞"},
["巴布亞紐幾內亞"] = {alias_of = "巴布亞新幾內亞"},
["薩摩亞"] = {container = "大洋洲", wp = "薩摩亞"},
["所羅門群島"] = {container = "大洋洲", wp = "所羅門群島"},
["湯加"] = {container = "大洋洲", wp = "湯加"},
["東加"] = {alias_of = "湯加"},
["圖瓦盧"] = {container = "大洋洲", wp = "圖瓦盧"},
["吐瓦魯"] = {alias_of = "圖瓦盧"},
["瓦努阿圖"] = {container = "大洋洲", wp = "瓦努阿圖"},
["萬那杜"] = {alias_of = "瓦努阿圖"},
}
export.countries_group = {
canonicalize_key_container = canonicalize_continent_container,
default_overriding_bare_label_parents = {"+++", "國家"},
default_placetype = "country",
default_no_container_cat = true,
default_no_container_parent = true,
default_no_auto_augment_container = true,
placename_to_key = false,
data = export.countries,
}
export.country_like_entities = {
-- British Overseas Territory
["阿克羅蒂里和澤凱利亞"] = {
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"塞浦路斯", "歐洲", "亞洲"},
},
-- Åland: Listed as a region of Finland. Wikipedia lists this under "dependent territories" in
-- [[w:List of sovereign states and dependent territories by continent]].
-- unincorporated territory of the United States
["美屬薩摩亞"] = {
placetype = {"unincorporated territory", "overseas territory", "territory"},
container = "美國",
addl_parents = {"玻里尼西亞"},
},
-- British Overseas Territory
["安圭拉"] = {
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"加勒比地區"},
},
-- de-facto independent state, internationally recognized as part of Georgia
["阿布哈茲"] = {
placetype = {"unrecognized country", "country"},
addl_parents = {"格魯吉亞", "歐洲", "亞洲"},
divs = {"districts"},
keydesc = "事實上獨立的[[阿布哈茲]],國際上被承認為[[格魯吉亞]]的一部分",
},
-- Australian external territory
["阿什莫爾和卡捷群島"] = {
the = true,
placetype = {"external territory", "territory"},
container = "澳大利亞",
addl_parents = {"亞洲"},
},
-- constituent country of the Netherlands
["阿魯巴"] = {
placetype = {"constituent country", "country"},
container = "荷蘭",
addl_parents = {"加勒比地區"},
},
-- British Overseas Territory
["百慕大"] = {
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"北美洲"},
},
-- special municipality of the Netherlands
["博奈爾"] = {
placetype = {"special municipality", "municipality", "overseas territory", "territory"},
container = "荷蘭",
addl_parents = {"加勒比地區"},
is_city = true,
},
-- British Overseas Territory
["英屬印度洋領地"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"亞洲"},
},
-- British Overseas Territory
["英屬維爾京群島"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"加勒比地區"},
},
-- Norwegian dependent territory
["布韋島"] = {
placetype = {"dependent territory", "territory"},
container = "挪威",
addl_parents = {"非洲"},
},
-- British Overseas Territory
["開曼群島"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"加勒比地區"},
},
-- Australian external territory
["聖誕島"] = {
placetype = {"external territory", "territory"},
container = "澳大利亞",
addl_parents = {"亞洲"},
},
-- Sui generis French "state private property" per Wikipedia; classify as overseas territory like the
-- French Southern and Antarctic Lands.
["克利珀頓島"] = {
placetype = {"overseas territory", "territory"},
container = "法國",
addl_parents = {"北美洲"},
},
-- Australian external territory; also called the Keeling Islands or (officially) the Cocos (Keeling) Islands
["科科斯群島"] = {
the = true,
placetype = {"external territory", "territory"},
container = "澳大利亞",
addl_parents = {"亞洲"},
wp = "科科斯(基林)群島",
},
["科科斯(基林)群島"] = {alias_of = "科科斯群島", display = true, the = true},
["基林群島"] = {alias_of = "科科斯群島", display = true, the = true},
-- self-governing but in free association with New Zealand
["庫克群島"] = {
the = true,
placetype = {"country"},
container = "新西蘭",
addl_parents = {"玻里尼西亞"},
},
-- constituent country of the Netherlands
["庫拉索"] = {
placetype = {"constituent country", "country"},
container = "荷蘭",
addl_parents = {"加勒比地區"},
},
-- special territory of Chile
["復活節島"] = {
placetype = {"special territory", "territory"},
container = "智利",
addl_parents = {"玻里尼西亞"},
},
-- British Overseas Territory
["福克蘭群島"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"南美洲"},
},
-- autonomous territory of Denmark
["法羅群島"] = {
the = true,
placetype = {"autonomous territory", "territory"},
container = "丹麥",
addl_parents = {"歐洲"},
},
-- overseas department and region of France
["法屬圭亞那"] = {
placetype = {"overseas department", "department", "administrative region", "region"},
container = "法國",
divs = {"communes"},
addl_parents = {"南美洲"},
},
-- overseas collectivity of France
["法屬玻里尼西亞"] = {
placetype = {"overseas collectivity", "collectivity"},
container = "法國",
addl_parents = {"玻里尼西亞"},
},
-- French overseas territory
["法屬南部和南極領地"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "法國",
addl_parents = {"非洲"},
},
-- British Overseas Territory
["直布羅陀"] = {
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"歐洲"},
is_city = true,
},
-- autonomous territory of Denmark
["格陵蘭"] = {
placetype = {"autonomous territory", "territory"},
container = "丹麥",
addl_parents = {"北美洲"},
divs = {"municipalities"},
},
-- overseas department and region of France
["瓜德羅普"] = {
placetype = {"overseas department", "department", "administrative region", "region"},
container = "法國",
addl_parents = {"加勒比地區"},
divs = {"communes"},
},
-- unincorporated territory of the United States
["關島"] = {
placetype = {"unincorporated territory", "overseas territory", "territory"},
container = "美國",
addl_parents = {"密克羅尼西亞"},
},
-- self-governing British Crown dependency; technically called the Bailiwick of Guernsey
["根西"] = {
placetype = {"crown dependency", "dependency", "dependent territory", "bailiwick", "territory"},
container = "英國",
addl_parents = {"不列顛群島", "歐洲"},
wp = "%l行政區",
},
["根西行政區"] = {alias_of = "根西", the = true},
-- Australian external territory
["赫德島和麥克唐納群島"] = {
the = true,
placetype = {"external territory", "territory"},
container = "澳大利亞",
addl_parents = {"非洲"},
},
-- special administrative region of China
["香港"] = {
placetype = {"special administrative region", "city"},
container = "中國",
is_city = true,
},
-- self-governing British Crown dependency
["馬恩島"] = {
the = true,
placetype = {"crown dependency", "dependency", "dependent territory", "territory"},
container = "英國",
addl_parents = {"不列顛群島", "歐洲"},
},
-- Norwegian unincorporated area
["揚馬延"] = {
placetype = {"unincorporated area", "dependent territory", "territory", "island"},
container = "挪威",
addl_parents = {"歐洲"},
},
-- self-governing British Crown dependency; technically called the Bailiwick of Jersey
["澤西"] = {
placetype = {"crown dependency", "dependency", "dependent territory", "bailiwick", "territory"},
container = "英國",
addl_parents = {"不列顛群島", "歐洲"},
},
["澤西行政區"] = {alias_of = "澤西", the = true},
-- special administrative region of China
["澳門"] = {
placetype = {"special administrative region", "city"},
container = "中國",
is_city = true,
},
-- overseas department and region of France
["馬提尼克"] = {
placetype = {"overseas department", "department", "administrative region", "region"},
container = "法國",
divs = {"communes"},
addl_parents = {"加勒比地區"},
},
-- overseas department and region of France
["馬約特"] = {
placetype = {"overseas department", "department", "administrative region", "region"},
container = "法國",
divs = {"communes"},
addl_parents = {"非洲"},
},
-- British Overseas Territory
["蒙特塞拉特"] = {
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"加勒比地區"},
},
-- special collectivity of France
["新喀里多尼亞"] = {
placetype = {"special collectivity", "collectivity"},
container = "法國",
addl_parents = {"美拉尼西亞"},
},
-- dependent territory of New Zealand
["新西蘭亞南極群島"] = {
the = true,
placetype = {"dependent territory", "territory"},
container = "新西蘭",
addl_parents = {"南極洲"},
},
-- self-governing but in free association with New Zealand
["紐埃"] = {
placetype = {"country"},
container = "新西蘭",
addl_parents = {"玻里尼西亞"},
},
-- Australian external territory
["諾福克島"] = {
placetype = {"external territory", "territory"},
container = "澳大利亞",
addl_parents = {"玻里尼西亞"},
},
-- de-facto independent state, internationally recognized as part of Cyprus
["北塞浦路斯"] = {
placetype = {"unrecognized country", "country"},
addl_parents = {"塞浦路斯", "土耳其", "歐洲", "亞洲"},
divs = {"districts"},
keydesc = "事實上獨立的[[北塞浦路斯]],國際上被承認為[[塞浦路斯]]的一部分",
},
-- commonwealth, unincorporated territory of the United States
["北馬里亞納群島"] = {
the = true,
placetype = {"commonwealth", "unincorporated territory", "overseas territory", "territory"},
container = "美國",
addl_parents = {"密克羅尼西亞"},
},
-- British Overseas Territory
["皮特凱恩群島"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"玻里尼西亞"},
},
-- commonwealth of the United States
["波多黎各"] = {
placetype = {"commonwealth", "overseas territory", "territory"},
container = "美國",
addl_parents = {"加勒比地區"},
divs = {"municipalities"},
},
-- overseas department and region of France
["留尼汪"] = {
placetype = {"overseas department", "department", "administrative region", "region"},
container = "法國",
divs = {"communes"},
addl_parents = {"非洲"},
},
-- special municipality of the Netherlands
["薩巴"] = {
placetype = {"special municipality", "municipality", "overseas territory", "territory"},
container = "荷蘭",
addl_parents = {"加勒比地區"},
is_city = true,
},
-- overseas collectivity of France
["聖巴泰勒米"] = {
placetype = {"overseas collectivity", "collectivity"},
container = "法國",
addl_parents = {"加勒比地區"},
},
-- British Overseas Territory
["聖赫勒拿、阿森松和特里斯坦-達庫尼亞"] = {
placetype = {"overseas territory", "territory"},
container = "英國",
divs = {{type = "constituent parts", container_parent_type = false}},
addl_parents = {"大西洋", "非洲"},
},
-- constituent parts of the combined oveseas territory
["阿森松島"] = {
placetype = {"constituent part", "territory", "island"},
container = {key = "聖赫勒拿、阿森松和特里斯坦-達庫尼亞", placetype = "overseas territory"},
addl_parents = {"大西洋"},
overriding_bare_label_parents = {},
no_container_cat = false,
no_container_parent = false,
no_auto_augment_container = false,
},
["聖赫勒拿"] = {
placetype = {"constituent part", "territory", "island"},
container = {key = "聖赫勒拿、阿森松和特里斯坦-達庫尼亞", placetype = "overseas territory"},
addl_parents = {"大西洋"},
overriding_bare_label_parents = {},
no_container_cat = false,
no_container_parent = false,
no_auto_augment_container = false,
},
["特里斯坦-達庫尼亞"] = {
placetype = {"constituent part", "territory", "archipelago"},
container = {key = "聖赫勒拿、阿森松和特里斯坦-達庫尼亞", placetype = "overseas territory"},
addl_parents = {"大西洋"},
overriding_bare_label_parents = {},
no_container_cat = false,
no_container_parent = false,
no_auto_augment_container = false,
},
-- overseas collectivity of France
["聖馬丁"] = {
placetype = {"overseas collectivity", "collectivity"},
container = "法國",
addl_parents = {"加勒比地區"},
},
-- overseas collectivity of France
["聖皮埃爾和密克隆"] = {
placetype = {"overseas collectivity", "collectivity"},
container = "法國",
divs = {"communes"},
addl_parents = {"北美洲"},
},
-- special municipality of the Netherlands
["聖尤斯特歇斯"] = {
placetype = {"special municipality", "municipality", "overseas territory", "territory"},
container = "荷蘭",
addl_parents = {"加勒比地區"},
is_city = true,
},
-- constituent country of the Netherlands
["荷屬聖馬丁"] = {
placetype = {"constituent country", "country"},
container = "荷蘭",
addl_parents = {"加勒比地區"},
},
-- de-facto independent state, internationally recognized as part of Somalia
["索馬里蘭"] = {
placetype = {"unrecognized country", "country"},
addl_parents = {"索馬里", "非洲"},
keydesc = "事實上獨立的[[索馬里蘭]],國際上被承認為[[索馬里]]的一部分",
},
-- British Overseas Territory
-- FIXME: We should form the group "South Georgia and the South Sandwich Islands" like we did for
-- "Saint Helena, Ascension and Tristan da Cunha".
["南喬治亞"] = {
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"大西洋"},
},
-- de-facto independent state, internationally recognized as part of Georgia
["南奧塞梯"] = {
placetype = {"unrecognized country", "country"},
addl_parents = {"格魯吉亞", "歐洲", "亞洲"},
keydesc = "事實上獨立的[[南奧塞梯]],國際上被承認為[[格魯吉亞]]的一部分",
},
-- British Overseas Territory
["南桑威奇群島"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"大西洋"},
wp = true,
wpcat = "南喬治亞和南桑威奇群島",
},
-- Norwegian unincorporated area
["斯瓦爾巴"] = {
placetype = {"unincorporated area", "dependent territory", "territory", "archipelago"},
container = "挪威",
addl_parents = {"歐洲"},
},
-- dependent territory of New Zealand
["托克勞"] = {
placetype = {"dependent territory", "territory"},
container = "新西蘭",
addl_parents = {"玻里尼西亞"},
},
-- de-facto independent state, internationally recognized as part of Moldova
["德涅斯特河沿岸"] = {
placetype = {"unrecognized country", "country"},
addl_parents = {"摩爾多瓦", "歐洲"},
keydesc = "事實上獨立的[[德涅斯特河沿岸]],國際上被承認為[[摩爾多瓦]]的一部分",
},
-- British Overseas Territory
["特克斯和凱科斯群島"] = {
the = true,
placetype = {"overseas territory", "territory"},
container = "英國",
addl_parents = {"加勒比地區"},
},
-- unincorporated territory of the United States
["美國本土外小島嶼"] = {
the = true,
placetype = {"unincorporated territory", "overseas territory", "territory"},
container = "美國",
addl_parents = {"島嶼", "密克羅尼西亞", "玻里尼西亞", "加勒比地區"},
},
-- FIXME: We should add entries for the other minor outlying islands.
-- Baker Island (Oceania)
-- Howland Island (Oceania)
-- Jarvis Island (Oceania)
-- Johnston Atoll (Oceania)
-- Kingman Reef (Oceania)
-- Midway Atoll (Oceania)
-- Navassa Island (Caribbean)
-- Palmyra Atoll (Oceania)
-- Wake Island (Oceania)
["威克島"] = {
placetype = {"unincorporated territory", "overseas territory", "territory"},
container = "美國",
addl_parents = {"密克羅尼西亞"},
},
-- unincorporated territory of the United States
["美屬維爾京群島"] = {
the = true,
placetype = {"unincorporated territory", "overseas territory", "territory"},
container = "美國",
addl_parents = {"加勒比地區"},
},
["美屬維京群島"] = {alias_of = "美屬維爾京群島", display = true, the = true},
["美屬處女群島"] = {alias_of = "美屬維爾京群島", display = true, the = true},
-- overseas collectivity of France
["瓦利斯和富圖納"] = {
placetype = {"overseas collectivity", "collectivity"},
container = "法國",
addl_parents = {"玻里尼西亞"},
},
}
export.country_like_entities_group = {
canonicalize_key_container = make_canonicalize_key_container(nil, "country"),
default_placetype = "territory",
placename_to_key = false,
default_overriding_bare_label_parents = {"類國家實體"},
default_no_container_parent = true,
default_no_auto_augment_container = true,
data = export.country_like_entities,
}
export.former_countries = {
["蘇聯"] = {
placetype = {"country"},
container = {"歐洲", "亞洲"},
is_former_place = true,
wp = "Soviet Union",
},
["南斯拉夫"] = {
placetype = {"country"},
container = "歐洲",
is_former_place = true,
wp = "Yugoslavia",
},
["捷克斯洛伐克"] = {
placetype = {"country"},
container = "歐洲",
is_former_place = true,
wp = "Czechoslovakia",
},
["東德"] = {
placetype = {"country"},
container = "歐洲",
is_former_place = true,
wp = "East Germany",
},
["西德"] = {
placetype = {"country"},
container = "歐洲",
is_former_place = true,
wp = "West Germany",
},
["奧匈帝國"] = {
placetype = {"empire"},
container = "歐洲",
is_former_place = true,
wp = "Austria-Hungary",
},
["鄂圖曼帝國"] = {
placetype = {"empire"},
container = {"歐洲", "亞洲"},
is_former_place = true,
wp = "Ottoman Empire",
},
["奧斯曼帝國"] = {alias_of = "鄂圖曼帝國"},
["羅馬帝國"] = {
placetype = {"empire"},
container = {"歐洲", "亞洲", "非洲"},
is_former_place = true,
wp = "Roman Empire",
divs = {
"provinces",
{type = "FORMER provinces", cat_as = "provinces"},
},
},
["大英帝國"] = {
placetype = {"empire"},
is_former_place = true,
wp = "British Empire",
},
["普魯士"] = {
placetype = {"country"},
container = "歐洲",
is_former_place = true,
wp = "Prussia",
},
}
export.former_countries_group = {
canonicalize_key_container = canonicalize_continent_container,
default_overriding_bare_label_parents = {"已不存在的國家和類國家實體"},
default_placetype = "country",
default_is_former_place = true,
default_no_container_parent = true,
placename_to_key = false,
data = export.former_countries,
}
-----------------------------------------------------------------------------------
-- Subpolity tables --
-----------------------------------------------------------------------------------
export.australia_states_and_territories = {
["新南威爾士"] = {wp = "New South Wales"},
["維多利亞州"] = {wp = "Victoria (Australia)"},
["昆士蘭"] = {wp = "Queensland"},
["南澳大利亞"] = {wp = "South Australia"},
["西澳大利亞"] = {wp = "Western Australia"},
["塔斯馬尼亞"] = {wp = "Tasmania"},
["澳大利亞首都領地"] = {placetype = "territory", wp = "Australian Capital Territory"},
["北領地"] = {placetype = "territory", wp = "Northern Territory"},
}
-- states and territories of Australia
export.australia_group = {
placename_to_key = false,
default_container = "澳大利亞",
default_placetype = "state",
data = export.australia_states_and_territories,
}
export.austria_states = {
["維也納州"] = {wp = "Vienna (state)"},
["下奧地利州"] = {wp = "Lower Austria"},
["上奧地利州"] = {wp = "Upper Austria"},
["施泰爾馬克州"] = {wp = "Styria"},
["蒂羅爾州"] = {wp = "Tyrol (state)"},
["克恩頓州"] = {wp = "Carinthia"},
["薩爾茨堡州"] = {wp = "Salzburg (state)"},
["福拉爾貝格州"] = {wp = "Vorarlberg"},
["布根蘭州"] = {wp = "Burgenland"},
}
-- states of Austria
export.austria_group = {
placename_to_key = false,
default_container = "奧地利",
default_placetype = "state",
data = export.austria_states,
}
export.bangladesh_divisions = {
["巴里薩爾專區"] = {wp = "Barisal Division"},
["吉大港專區"] = {wp = "Chittagong Division"},
["達卡專區"] = {wp = "Dhaka Division"},
["庫爾納專區"] = {wp = "Khulna Division"},
["邁門辛專區"] = {wp = "Mymensingh Division"},
["拉吉沙希專區"] = {wp = "Rajshahi Division"},
["朗普爾專區"] = {wp = "Rangpur Division"},
["錫爾赫特專區"] = {wp = "Sylhet Division"},
}
-- divisions of Bangladesh
export.bangladesh_group = {
placename_to_key = false,
default_container = "孟加拉國",
default_placetype = "division",
data = export.bangladesh_divisions,
}
export.brazil_states = {
["里約熱內盧州"] = {wp = "Rio de Janeiro (state)"},
["里約熱內盧"] = {alias_of = "里約熱內盧州"},
["聖保羅州"] = {wp = "São Paulo (state)"},
["米納斯吉拉斯"] = {wp = "Minas Gerais"},
["巴伊亞"] = {wp = "Bahia"},
["亞馬遜州"] = {wp = "Amazonas (Brazilian state)"},
["帕拉州"] = {wp = "Pará"},
["伯南布哥州"] = {wp = "Pernambuco"},
["南里奧格蘭德州"] = {wp = "Rio Grande do Sul"},
["巴西聯邦區"] = {wp = "Federal District (Brazil)"},
["塞阿拉州"] = {wp = "Ceará"},
["巴拉那州"] = {wp = "Paraná (state)"},
["戈亞斯州"] = {wp = "Goiás"},
["聖埃斯皮里圖州"] = {wp = "Espírito Santo"},
["馬拉尼昂州"] = {wp = "Maranhão"},
["北里奧格蘭德州"] = {wp = "Rio Grande do Norte"},
["聖卡塔琳娜州"] = {wp = "Santa Catarina (state)"},
["阿拉戈亞斯州"] = {wp = "Alagoas"},
["帕拉伊巴州"] = {wp = "Paraíba"},
["皮奧伊州"] = {wp = "Piauí"},
["阿克里州"] = {wp = "Acre (state)"},
["阿馬帕州"] = {wp = "Amapá"},
["馬托格羅索州"] = {wp = "Mato Grosso"},
["南馬托格羅索州"] = {wp = "Mato Grosso do Sul"},
["朗多尼亞州"] = {wp = "Rondônia"},
["羅賴馬州"] = {wp = "Roraima"},
["塞爾希培州"] = {wp = "Sergipe"},
["托坎廷斯州"] = {wp = "Tocantins"},
}
-- states of Brazil
export.brazil_group = {
placename_to_key = false,
default_container = "巴西",
default_placetype = "state",
data = export.brazil_states,
}
-- provinces (a.k.a. oblasts) of Bulgaria
export.bulgaria_provinces = {
["布拉戈耶夫格勒省"] = {wp = "Blagoevgrad Province"},
["布爾加斯省"] = {wp = "Burgas Province"},
["多布里奇省"] = {wp = "Dobrich Province"},
["加布羅沃省"] = {wp = "Gabrovo Province"},
["哈斯科沃省"] = {wp = "Haskovo Province"},
["卡爾扎利省"] = {wp = "Kardzhali Province"},
["屈斯滕迪爾省"] = {wp = "Kyustendil Province"},
["洛維奇省"] = {wp = "Lovech Province"},
["蒙塔納省"] = {wp = "Montana Province"},
["帕扎爾吉克省"] = {wp = "Pazardzhik Province"},
["佩爾尼克省"] = {wp = "Pernik Province"},
["普萊文省"] = {wp = "Pleven Province"},
["普羅夫迪夫省"] = {wp = "Plovdiv Province"},
["拉茲格勒省"] = {wp = "Razgrad Province"},
["魯塞省"] = {wp = "Ruse Province"},
["舒門省"] = {wp = "Shumen Province"},
["錫利斯特拉省"] = {wp = "Silistra Province"},
["斯利文省"] = {wp = "Sliven Province"},
["斯莫梁省"] = {wp = "Smolyan Province"},
["索非亞市省"] = {wp = "Sofia City Province"},
["索非亞省"] = {wp = "Sofia Province"},
["斯塔拉扎戈拉省"] = {wp = "Stara Zagora Province"},
["特爾戈維什泰省"] = {wp = "Targovishte Province"},
["瓦爾納省"] = {wp = "Varna Province"},
["大特爾諾沃省"] = {wp = "Veliko Tarnovo Province"},
["維丁省"] = {wp = "Vidin Province"},
["弗拉察省"] = {wp = "Vratsa Province"},
["揚博爾省"] = {wp = "Yambol Province"},
}
export.bulgaria_group = {
placename_to_key = false,
default_container = "保加利亞",
divs = {"regions", "planning regions", "provinces", "municipalities", "settlements"},
default_placetype = "province",
data = export.bulgaria_provinces,
}
export.canada_provinces_and_territories = {
["亞伯達"] = {wp = "Alberta"},
["艾伯塔"] = {alias_of = "亞伯達"},
["英屬哥倫比亞"] = {wp = "British Columbia"},
["卑詩省"] = {alias_of = "英屬哥倫比亞"},
["馬尼托巴"] = {wp = "Manitoba"},
["新不倫瑞克"] = {wp = "New Brunswick"},
["紐芬蘭與拉布拉多"] = {wp = "Newfoundland and Labrador"},
["西北地區"] = {placetype = "territory", wp = "Northwest Territories"},
["新斯科舍"] = {wp = "Nova Scotia"},
["努納武特"] = {placetype = "territory", wp = "Nunavut"},
["安大略"] = {divs = {"counties", "regional municipalities"}, wp = "Ontario"},
["愛德華王子島"] = {wp = "Prince Edward Island"},
["魁北克"] = {wp = "Quebec"},
["薩斯喀徹溫"] = {wp = "Saskatchewan"},
["育空"] = {placetype = "territory", wp = "Yukon"},
}
export.canada_group = {
placename_to_key = false,
default_container = "加拿大",
default_placetype = "province",
data = export.canada_provinces_and_territories,
}
-- Subdivisions of a direct-administered municipality are those of a prefecture-level city,
-- not those of a province, so the four municipalities need an explicit `divs` list.
local municipality_divs = {
"districts", "subdistricts", "townships",
{type = "counties", cat_as = "counties and county-level cities"},
{type = "county-level cities", cat_as = "counties and county-level cities"},
}
export.china_provinces_and_autonomous_regions = {
-- Provinces
["安徽"] = {wp = "安徽省"},
["福建"] = {wp = "福建省"},
["甘肅"] = {wp = "甘肅省"},
["廣東"] = {wp = "廣東省"},
["貴州"] = {wp = "貴州省"},
["海南"] = {wp = "海南省"},
["河北"] = {wp = "河北省"},
["黑龍江"] = {wp = "黑龍江省"},
["河南"] = {wp = "河南省"},
["湖北"] = {wp = "湖北省"},
["湖南"] = {wp = "湖南省"},
["江蘇"] = {wp = "江蘇省"},
["江西"] = {wp = "江西省"},
["吉林"] = {wp = "吉林省"},
["遼寧"] = {wp = "遼寧省"},
["青海"] = {wp = "青海省"},
["陝西"] = {wp = "陝西省"},
["山東"] = {wp = "山東省"},
["山西"] = {wp = "山西省"},
["四川"] = {wp = "四川省"},
["雲南"] = {wp = "雲南省"},
["浙江"] = {wp = "浙江省"},
-- Autonomous regions
["廣西"] = {placetype = "autonomous region", wp = "廣西壯族自治區"},
["廣西壯族自治區"] = {alias_of = "廣西"},
["內蒙古"] = {placetype = "autonomous region", wp = "內蒙古自治區"},
["內蒙古自治區"] = {alias_of = "內蒙"},
["寧夏"] = {placetype = "autonomous region", wp = "寧夏回族自治區"},
["寧夏回族自治區"] = {alias_of = "寧夏"},
["西藏"] = {placetype = "autonomous region", wp = "西藏自治區"},
["西藏自治區"] = {alias_of = "西藏"},
["新疆"] = {placetype = "autonomous region", wp = "新疆維吾爾自治區"},
["新疆維吾爾自治區"] = {alias_of = "新疆"},
-- Direct-administered municipalities (treated as province-level for holonym purposes)
["北京"] = {placetype = {"direct-administered municipality", "municipality", "city"}, divs = municipality_divs, wp = "北京市"},
["北京市"] = {alias_of = "北京"},
["上海"] = {placetype = {"direct-administered municipality", "municipality", "city"}, divs = municipality_divs, wp = "上海市"},
["上海市"] = {alias_of = "上海"},
["天津"] = {placetype = {"direct-administered municipality", "municipality", "city"}, divs = municipality_divs, wp = "天津市"},
["天津市"] = {alias_of = "天津"},
["重慶"] = {placetype = {"direct-administered municipality", "municipality", "city"}, divs = municipality_divs, wp = "重慶市"},
["重慶市"] = {alias_of = "重慶"},
}
export.china_group = {
placename_to_key = false,
default_container = "中國",
default_placetype = "province",
default_divs = {
"prefecture-level cities",
"districts",
{type = "counties", cat_as = "counties and county-level cities"},
{type = "county-level cities", cat_as = "counties and county-level cities"},
},
data = export.china_provinces_and_autonomous_regions,
}
export.china_prefecture_level_cities = {
-- Guangdong
["廣州"] = {container = "廣東", wp = "Guangzhou"},
["東莞"] = {container = "廣東", wp = "Dongguan"},
["佛山"] = {container = "廣東", wp = "Foshan"},
["惠州"] = {container = "廣東", wp = "Huizhou"},
["江門"] = {container = "廣東", wp = "Jiangmen"},
["深圳"] = {container = "廣東", wp = "Shenzhen"},
["中山"] = {container = "廣東", wp = "Zhongshan"},
["汕頭"] = {container = "廣東", wp = "Shantou"},
["潮州"] = {container = "廣東", wp = "Chaozhou"},
["揭陽"] = {container = "廣東", wp = "Jieyang"},
["珠海"] = {container = "廣東", wp = "Zhuhai"},
["湛江"] = {container = "廣東", wp = "Zhanjiang"},
["茂名"] = {container = "廣東", wp = "Maoming"},
["清遠"] = {container = "廣東", wp = "Qingyuan"},
["肇慶"] = {container = "廣東", wp = "Zhaoqing"},
-- Jiangsu
["常熟"] = {container = "江蘇", wp = "Changshu"},
["常州"] = {container = "江蘇", wp = "Changzhou"},
["蘇州"] = {container = "江蘇", wp = "Suzhou"},
["無錫"] = {container = "江蘇", wp = "Wuxi"},
["南京"] = {container = "江蘇", wp = "Nanjing"},
["南通"] = {container = "江蘇", wp = "Nantong"},
["江陰"] = {container = "江蘇", wp = "Jiangyin"},
["張家港"] = {container = "江蘇", wp = "Zhangjiagang"},
["徐州"] = {container = "江蘇", wp = "Xuzhou"},
["揚州"] = {container = "江蘇", wp = "Yangzhou"},
["鹽城"] = {container = "江蘇", wp = "Yancheng"},
["淮安"] = {container = "江蘇", wp = "Huai'an"},
["連雲港"] = {container = "江蘇", wp = "Lianyungang"},
["宿遷"] = {container = "江蘇", wp = "Suqian"},
["鎮江"] = {container = "江蘇", wp = "Zhenjiang"},
["崑山"] = {container = "江蘇", wp = "Kunshan"},
["泰州"] = {container = "江蘇", wp = "Taizhou, Jiangsu"}, -- distinct from 台州 (Zhejiang)
-- Zhejiang
["杭州"] = {container = "浙江", wp = "Hangzhou"},
["紹興"] = {container = "浙江", wp = "Shaoxing"},
["寧波"] = {container = "浙江", wp = "Ningbo"},
["慈溪"] = {container = "浙江", wp = "Cixi"},
["余姚"] = {container = "浙江", wp = "Yuyao"},
["溫州"] = {container = "浙江", wp = "Wenzhou"},
["瑞安"] = {placetype = "county-level city", container = {key = "溫州", placetype = "prefecture-level city"}, divs = {"subdistricts", "townships"}, wp = "Rui'an"},
["台州"] = {container = "浙江", wp = "Taizhou, Zhejiang"},
["義烏"] = {container = "浙江", wp = "Yiwu"},
["嘉興"] = {container = "浙江", wp = "Jiaxing"},
["金華"] = {container = "浙江", wp = "Jinhua"},
["湖州"] = {container = "浙江", wp = "Huzhou"},
["永康"] = {placetype = "county-level city", container = {key = "金華", placetype = "prefecture-level city"}, divs = {"subdistricts", "townships"}, wp = "Yongkang, Zhejiang"},
-- Fujian
["廈門"] = {container = "福建", wp = "Xiamen"},
["晉江"] = {container = "福建", wp = "Jinjiang"},
["泉州"] = {container = "福建", wp = "Quanzhou"},
["莆田"] = {container = "福建", wp = "Putian"},
["福州"] = {container = "福建", wp = "Fuzhou"},
-- Shandong
["青島"] = {container = "山東", wp = "Qingdao"},
["臨沂"] = {container = "山東", wp = "Linyi"},
["淄博"] = {container = "山東", wp = "Zibo"},
["濟南"] = {container = "山東", wp = "Jinan"},
["濰坊"] = {container = "山東", wp = "Weifang"},
["煙台"] = {container = "山東", wp = "Yantai"},
["濟寧"] = {container = "山東", wp = "Jining"},
["泰安"] = {container = "山東", wp = "Tai'an"},
["威海"] = {container = "山東", wp = "Weihai"},
["菏澤"] = {container = "山東", wp = "Heze"},
["德州"] = {container = "山東", wp = "Dezhou"},
["日照"] = {container = "山東", wp = "Rizhao"},
["聊城"] = {container = "山東", wp = "Liaocheng"},
["棗莊"] = {container = "山東", wp = "Zaozhuang"},
["滕州"] = {placetype = "county-level city", container = {key = "棗莊", placetype = "prefecture-level city"}, divs = {"subdistricts", "townships"}, wp = "Tengzhou"},
["東營"] = {container = "山東", wp = "Dongying"},
-- Henan
["鄭州"] = {container = "河南", wp = "Zhengzhou"},
["洛陽"] = {container = "河南", wp = "Luoyang"},
["新鄉"] = {container = "河南", wp = "Xinxiang"},
["信陽"] = {container = "河南", wp = "Xinyang"},
["安陽"] = {container = "河南", wp = "Anyang"},
["開封"] = {container = "河南", wp = "Kaifeng"},
["許昌"] = {container = "河南", wp = "Xuchang"},
["濮陽"] = {container = "河南", wp = "Puyang"},
["商丘"] = {container = "河南", wp = "Shangqiu"},
["南陽"] = {container = "河南", wp = "Nanyang, Henan"},
["焦作"] = {container = "河南", wp = "Jiaozuo"},
["平頂山"] = {container = "河南", wp = "Pingdingshan"},
["周口"] = {container = "河南", wp = "Zhoukou"},
["駐馬店"] = {container = "河南", wp = "Zhumadian"},
-- Hubei
["武漢"] = {container = "湖北", wp = "Wuhan"},
["襄陽"] = {container = "湖北", wp = "Xiangyang"},
["宜昌"] = {container = "湖北", wp = "Yichang"},
["鄂州"] = {container = "湖北", wp = "Ezhou"},
["十堰"] = {container = "湖北", wp = "Shiyan"},
-- Hunan
["長沙"] = {container = "湖南", wp = "Changsha"},
["株洲"] = {container = "湖南", wp = "Zhuzhou"},
["衡陽"] = {container = "湖南", wp = "Hengyang"},
["邵陽"] = {container = "湖南", wp = "Shaoyang"},
["岳陽"] = {container = "湖南", wp = "Yueyang"},
["常德"] = {container = "湖南", wp = "Changde"},
["瀏陽"] = {placetype = "county-level city", container = {key = "長沙", placetype = "prefecture-level city"}, divs = {"subdistricts", "townships"}, wp = "Liuyang"},
-- Sichuan
["成都"] = {container = "四川", wp = "Chengdu"},
["綿陽"] = {container = "四川", wp = "Mianyang"},
["南充"] = {container = "四川", wp = "Nanchong"},
["達州"] = {container = "四川", wp = "Dazhou"},
["瀘州"] = {container = "四川", wp = "Luzhou"},
["宜賓"] = {container = "四川", wp = "Yibin"},
-- Yunnan
["昆明"] = {container = "雲南", wp = "Kunming"},
["曲靖"] = {container = "雲南", wp = "Qujing"},
-- Guizhou
["貴陽"] = {container = "貴州", wp = "Guiyang"},
["遵義"] = {container = "貴州", wp = "Zunyi"},
["六盤水"] = {container = "貴州", wp = "Liupanshui"},
["畢節"] = {container = "貴州", wp = "Bijie"},
-- Shaanxi
["西安"] = {container = "陝西", wp = "Xi'an"},
["咸陽"] = {container = "陝西", wp = "Xianyang"},
["寶雞"] = {container = "陝西", wp = "Baoji"},
-- Heilongjiang
["哈爾濱"] = {container = "黑龍江", wp = "Harbin"},
["大慶"] = {container = "黑龍江", wp = "Daqing"},
["齊齊哈爾"] = {container = "黑龍江", wp = "Qiqihar"},
-- Liaoning
["瀋陽"] = {container = "遼寧", wp = "Shenyang"},
["撫順"] = {container = "遼寧", wp = "Fushun"},
["大連"] = {container = "遼寧", wp = "Dalian"},
["鞍山"] = {container = "遼寧", wp = "Anshan"},
["錦州"] = {container = "遼寧", wp = "Jinzhou"},
["葫蘆島"] = {container = "遼寧", wp = "Huludao"},
["盤錦"] = {container = "遼寧", wp = "Panjin"},
-- Jilin
["長春"] = {container = "吉林", wp = "Changchun"},
["吉林市"] = {container = "吉林", wp = "Jilin City"}, -- distinct from 吉林 (province)
-- Anhui
["合肥"] = {container = "安徽", wp = "Hefei"},
["蚌埠"] = {container = "安徽", wp = "Bengbu"},
["淮南"] = {container = "安徽", wp = "Huainan"},
["蕪湖"] = {container = "安徽", wp = "Wuhu"},
["淮北"] = {container = "安徽", wp = "Huaibei"},
["滁州"] = {container = "安徽", wp = "Chuzhou"},
["六安"] = {container = "安徽", wp = "Lu'an"},
["阜陽"] = {container = "安徽", wp = "Fuyang, Anhui"},
["宿州"] = {container = "安徽", wp = "Suzhou, Anhui"}, -- distinct from 蘇州 (Jiangsu)
-- Hebei
["石家莊"] = {container = "河北", wp = "Shijiazhuang"},
["邯鄲"] = {container = "河北", wp = "Handan"},
["唐山"] = {container = "河北", wp = "Tangshan"},
["秦皇島"] = {container = "河北", wp = "Qinhuangdao"},
["邢台"] = {container = "河北", wp = "Xingtai"},
["保定"] = {container = "河北", wp = "Baoding"},
["張家口"] = {container = "河北", wp = "Zhangjiakou"},
["衡水"] = {container = "河北", wp = "Hengshui"},
["滄州"] = {container = "河北", wp = "Cangzhou"},
-- Shanxi
["太原"] = {container = "山西", wp = "Taiyuan"},
["大同"] = {container = "山西", wp = "Datong"},
["長治"] = {container = "山西", wp = "Changzhi"},
["臨汾"] = {container = "山西", wp = "Linfen"},
-- Jiangxi
["南昌"] = {container = "江西", wp = "Nanchang"},
["贛州"] = {container = "江西", wp = "Ganzhou"},
["上饒"] = {container = "江西", wp = "Shangrao"},
["九江"] = {container = "江西", wp = "Jiujiang"},
-- Guangxi (autonomous region)
["南寧"] = {container = {key = "廣西", placetype = "autonomous region"}, wp = "Nanning"},
["柳州"] = {container = {key = "廣西", placetype = "autonomous region"}, wp = "Liuzhou"},
["桂林"] = {container = {key = "廣西", placetype = "autonomous region"}, wp = "Guilin"},
["玉林"] = {container = {key = "廣西", placetype = "autonomous region"}, wp = "Yulin, Guangxi"},
["北海"] = {container = {key = "廣西", placetype = "autonomous region"}, wp = "Beihai"},
["貴港"] = {container = {key = "廣西", placetype = "autonomous region"}, wp = "Guigang"},
-- Inner Mongolia (autonomous region)
["呼和浩特"] = {container = {key = "內蒙古", placetype = "autonomous region"}, wp = "Hohhot"},
["包頭"] = {container = {key = "內蒙古", placetype = "autonomous region"}, wp = "Baotou"},
["赤峰"] = {container = {key = "內蒙古", placetype = "autonomous region"}, wp = "Chifeng"},
-- Xinjiang (autonomous region)
["烏魯木齊"] = {container = {key = "新疆", placetype = "autonomous region"}, wp = "Ürümqi"},
-- Ningxia (autonomous region)
["銀川"] = {container = {key = "寧夏", placetype = "autonomous region"}, wp = "Yinchuan"},
-- Hainan
["海口"] = {container = "海南", wp = "Haikou"},
-- Qinghai
["西寧"] = {container = "青海", wp = "Xining"},
-- Gansu
["蘭州"] = {container = "甘肅", wp = "Lanzhou"},
-- Chongqing subdivision (district 142 miles from Chongqing proper)
["萬州"] = {placetype = "district", container = {key = "重慶", placetype = "direct-administered municipality"}, divs = {"subdistricts", "townships"}, wp = "Wanzhou"},
-- County within Suqian prefecture-level city
["沭陽"] = {placetype = "county", container = {key = "宿遷", placetype = "prefecture-level city"}, divs = {"subdistricts", "townships"}, wp = "Shuyang County"},
}
export.china_prefecture_level_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_placetype = {"prefecture-level city", "city"},
default_divs = {
"districts", "subdistricts", "townships",
{type = "counties", cat_as = "counties and county-level cities"},
{type = "county-level cities", cat_as = "counties and county-level cities"},
},
data = export.china_prefecture_level_cities,
}
export.finland_regions = {
["拉普蘭省"] = {wp = "Lapland (Finland)"},
["北奧斯特博滕省"] = {wp = "North Ostrobothnia"},
["卡亞尼省"] = {wp = "Kainuu"},
["北卡累利亞省"] = {wp = "North Karelia"},
["北薩沃省"] = {wp = "Northern Savonia"},
["南薩沃省"] = {wp = "Southern Savonia"},
["南卡累利亞省"] = {wp = "South Karelia"},
["中芬蘭省"] = {wp = "Central Finland"},
["南奧斯特博滕省"] = {wp = "South Ostrobothnia"},
["奧斯特博滕省"] = {wp = "Ostrobothnia (region)"},
["中奧斯特博滕省"] = {wp = "Central Ostrobothnia"},
["皮爾卡馬省"] = {wp = "Pirkanmaa"},
["薩塔昆塔省"] = {wp = "Satakunta"},
["派亞特哈米省"] = {wp = "Päijät-Häme"},
["坎塔哈米省"] = {wp = "Tavastia Proper"},
["屈米河谷省"] = {wp = "Kymenlaakso"},
["烏西馬省"] = {wp = "Uusimaa"},
["西南芬蘭省"] = {wp = "Southwest Finland"},
["奧蘭群島"] = {wp = "Åland", placetype = {"autonomous region", "region"}},
}
export.finland_group = {
placename_to_key = false,
default_container = "芬蘭",
default_placetype = "region",
data = export.finland_regions,
}
export.france_administrative_regions = {
["奧弗涅-隆河-阿爾卑斯大區"] = {wp = "Auvergne-Rhône-Alpes"},
["勃艮第-弗朗什-孔泰大區"] = {wp = "Bourgogne-Franche-Comté"},
["布列塔尼大區"] = {wp = "Brittany (administrative region)"},
["中央-羅亞爾河谷大區"] = {wp = "Centre-Val de Loire"},
["科西嘉大區"] = {wp = "Corsica"},
["大東部大區"] = {wp = "Grand Est"},
["上法蘭西大區"] = {wp = "Hauts-de-France"},
["法蘭西島大區"] = {wp = "Île-de-France"},
["諾曼底大區"] = {wp = "Normandy"},
["新阿基坦大區"] = {wp = "Nouvelle-Aquitaine"},
["奧克西塔尼大區"] = {wp = "Occitanie"},
["盧瓦爾河地區大區"] = {wp = "Pays de la Loire"},
["普羅旺斯-阿爾卑斯-蔚藍海岸大區"] = {wp = "Provence-Alpes-Côte d'Azur"},
-- Old region names (pre-2016) as aliases
["北部-加萊海峽大區"] = {wp = "Nord-Pas-de-Calais"},
["加來海峽大區"] = {alias_of = "北部-加萊海峽大區"},
}
export.france_group = {
placename_to_key = false,
default_container = "法國",
default_placetype = "region",
data = export.france_administrative_regions,
}
-- France departments (abbreviated list)
export.france_departments = {
["北部省"] = {wp = "Nord (department)", container = "上法蘭西大區"},
["加來海峽省"] = {wp = "Pas-de-Calais", container = "上法蘭西大區"},
["巴黎市"] = {wp = "Paris", container = "法蘭西島大區"},
["上塞納省"] = {wp = "Hauts-de-Seine", container = "法蘭西島大區"},
}
export.france_departments_group = {
placename_to_key = false,
default_container = "法國",
default_placetype = "department",
data = export.france_departments,
}
export.germany_states = {
["巴登-符騰堡"] = {wp = "Baden-Württemberg"},
["巴伐利亞"] = {wp = "Bavaria"},
-- 柏林、不來梅、漢堡為城邦,僅列於 germany_cities,此處不重複(仿 enwikt)
["布蘭登堡"] = {wp = "Brandenburg"},
["黑森"] = {wp = "Hesse"},
["梅克倫堡-前波美拉尼亞"] = {wp = "Mecklenburg-Vorpommern"},
["下薩克森"] = {wp = "Lower Saxony"},
["北萊茵-威斯特法倫"] = {wp = "North Rhine-Westphalia"},
["萊茵蘭-普法爾茨"] = {wp = "Rhineland-Palatinate"},
["薩爾"] = {wp = "Saarland"},
["薩克森"] = {wp = "Saxony"},
["薩克森-安哈爾特"] = {wp = "Saxony-Anhalt"},
["石勒蘇益格-荷爾斯泰因"] = {wp = "Schleswig-Holstein"},
["圖林根"] = {wp = "Thuringia"},
}
export.germany_group = {
placename_to_key = false,
default_container = "德國",
default_placetype = "state",
data = export.germany_states,
}
export.greece_regions = {
["阿提卡大區"] = {wp = "Attica (region)"},
["中希臘大區"] = {wp = "Central Greece (administrative region)"},
["中馬其頓大區"] = {wp = "Central Macedonia"},
["克里特大區"] = {wp = "Crete"},
["東馬其頓和色雷斯大區"] = {wp = "Eastern Macedonia and Thrace"},
["伊庇魯斯大區"] = {wp = "Epirus (region)"},
["愛奧尼亞群島大區"] = {wp = "Ionian Islands (region)"},
["北愛琴大區"] = {wp = "North Aegean"},
["伯羅奔尼撒大區"] = {wp = "Peloponnese (region)"},
["南愛琴大區"] = {wp = "South Aegean"},
["塞薩利大區"] = {wp = "Thessaly"},
["西希臘大區"] = {wp = "Western Greece"},
["西馬其頓大區"] = {wp = "Western Macedonia"},
["阿托斯山"] = {wp = "Monastic community of Mount Athos", placetype = {"autonomous region", "region"}},
}
export.greece_group = {
placename_to_key = false,
default_container = "希臘",
default_placetype = "region",
data = export.greece_regions,
}
export.india_states_and_union_territories = {
["安達曼-尼科巴群島"] = {placetype = "union territory", wp = "Andaman and Nicobar Islands"},
["安得拉邦"] = {wp = "Andhra Pradesh"},
["阿魯納恰爾邦"] = {wp = "Arunachal Pradesh"},
["阿薩姆"] = {wp = "Assam"},
["比哈爾邦"] = {wp = "Bihar"},
["昌迪加爾"] = {placetype = "union territory", wp = "Chandigarh"},
["恰蒂斯加爾邦"] = {wp = "Chhattisgarh"},
["達德拉-納加爾哈維利和達曼-第烏"] = {placetype = "union territory", wp = "Dadra and Nagar Haveli and Daman and Diu"},
["德里"] = {placetype = "union territory", wp = "Delhi"},
["果阿"] = {wp = "Goa"},
["古吉拉特邦"] = {wp = "Gujarat"},
["哈里亞納邦"] = {wp = "Haryana"},
["喜馬偕爾邦"] = {wp = "Himachal Pradesh"},
["查謨和克什米爾"] = {placetype = "union territory", wp = "Jammu and Kashmir (union territory)"},
["賈坎德邦"] = {wp = "Jharkhand"},
["卡納塔克邦"] = {wp = "Karnataka"},
["喀拉拉邦"] = {wp = "Kerala"},
["拉達克"] = {placetype = "union territory", wp = "Ladakh"},
["拉克沙群島"] = {placetype = "union territory", wp = "Lakshadweep"},
["中央邦"] = {wp = "Madhya Pradesh"},
["馬哈拉施特拉邦"] = {wp = "Maharashtra"},
["曼尼普爾邦"] = {wp = "Manipur"},
["梅加拉亞邦"] = {wp = "Meghalaya"},
["米佐拉姆邦"] = {wp = "Mizoram"},
["那加蘭邦"] = {wp = "Nagaland"},
["奧里薩邦"] = {wp = "Odisha"},
["本地治里"] = {placetype = "union territory", wp = "Puducherry"},
["旁遮普邦"] = {wp = "Punjab, India"},
["拉賈斯坦邦"] = {wp = "Rajasthan"},
["錫金"] = {wp = "Sikkim"},
["泰米爾納德邦"] = {wp = "Tamil Nadu"},
["特倫甘納邦"] = {wp = "Telangana"},
["特里普拉邦"] = {wp = "Tripura"},
["北方邦"] = {wp = "Uttar Pradesh"},
["北阿坎德邦"] = {wp = "Uttarakhand"},
["西孟加拉邦"] = {wp = "West Bengal"},
}
export.india_group = {
placename_to_key = false,
default_container = "印度",
default_placetype = "state",
default_divs = {"districts"},
data = export.india_states_and_union_territories,
}
export.indonesia_provinces = {
["亞齊省"] = {wp = "Aceh"},
["峇里省"] = {wp = "Bali"},
["邦加勿里洞省"] = {wp = "Bangka Belitung Islands"},
["萬丹省"] = {wp = "Banten"},
["明古魯省"] = {wp = "Bengkulu"},
["中爪哇省"] = {wp = "Central Java"},
["中加里曼丹省"] = {wp = "Central Kalimantan"},
["中巴布亞省"] = {wp = "Central Papua"},
["中蘇拉威西省"] = {wp = "Central Sulawesi"},
["東爪哇省"] = {wp = "East Java"},
["東加里曼丹省"] = {wp = "East Kalimantan"},
["東努沙登加拉省"] = {wp = "East Nusa Tenggara"},
["哥倫打洛省"] = {wp = "Gorontalo"},
["巴布亞高地省"] = {wp = "Highland Papua"},
["雅加達首都特區"] = {wp = "Jakarta"},
["占碑省"] = {wp = "Jambi"},
["楠榜省"] = {wp = "Lampung"},
["馬魯古省"] = {wp = "Maluku"},
["北加里曼丹省"] = {wp = "North Kalimantan"},
["北馬魯古省"] = {wp = "North Maluku"},
["北蘇拉威西省"] = {wp = "North Sulawesi"},
["北巴布亞省"] = {wp = "North Papua"},
["北蘇門答臘省"] = {wp = "North Sumatra"},
["巴布亞省"] = {wp = "Papua (province)"},
["廖內省"] = {wp = "Riau"},
["廖內群島省"] = {wp = "Riau Islands"},
["東南蘇拉威西省"] = {wp = "Southeast Sulawesi"},
["南加里曼丹省"] = {wp = "South Kalimantan"},
["南巴布亞省"] = {wp = "South Papua"},
["南蘇拉威西省"] = {wp = "South Sulawesi"},
["南蘇門答臘省"] = {wp = "South Sumatra"},
["西南巴布亞省"] = {wp = "Southwest Papua"},
["西爪哇省"] = {wp = "West Java"},
["西加里曼丹省"] = {wp = "West Kalimantan"},
["西努沙登加拉省"] = {wp = "West Nusa Tenggara"},
["西巴布亞省"] = {wp = "West Papua (province)"},
["西蘇拉威西省"] = {wp = "West Sulawesi"},
["西蘇門答臘省"] = {wp = "West Sumatra"},
["日惹特區"] = {wp = "Special Region of Yogyakarta"},
}
export.indonesia_group = {
placename_to_key = false,
default_container = "印度尼西亞",
default_placetype = "province",
data = export.indonesia_provinces,
}
export.iran_provinces = {
["阿爾博爾茲省"] = {wp = "Alborz Province"},
["阿爾達比勒省"] = {wp = "Ardabil Province"},
["布什爾省"] = {wp = "Bushehr Province"},
["恰哈爾馬哈勒-巴赫蒂亞里省"] = {wp = "Chaharmahal and Bakhtiari Province"},
["東阿塞拜疆省"] = {wp = "East Azerbaijan Province"},
["法爾斯省"] = {wp = "Fars Province"},
["帕爾斯省"] = {alias_of = "法爾斯省"},
["吉蘭省"] = {wp = "Gilan Province"},
["戈勒斯坦省"] = {wp = "Golestan Province"},
["哈馬丹省"] = {wp = "Hamadan Province"},
["霍爾木茲甘省"] = {wp = "Hormozgan Province"},
["伊拉姆省"] = {wp = "Ilam Province"},
["伊斯法罕省"] = {wp = "Isfahan Province"},
["克爾曼省"] = {wp = "Kerman Province"},
["克爾曼沙赫省"] = {wp = "Kermanshah Province"},
["胡齊斯坦省"] = {wp = "Khuzestan Province"},
["科吉盧耶-博亞赫邁德省"] = {wp = "Kohgiluyeh and Boyer-Ahmad Province"},
["庫爾德斯坦省"] = {wp = "Kurdistan Province"},
["洛雷斯坦省"] = {wp = "Lorestan Province"},
["馬爾卡齊省"] = {wp = "Markazi Province"},
["馬贊達蘭省"] = {wp = "Mazandaran Province"},
["北呼羅珊省"] = {wp = "North Khorasan Province"},
["加茲溫省"] = {wp = "Qazvin Province"},
["庫姆省"] = {wp = "Qom Province"},
["拉扎維呼羅珊省"] = {wp = "Razavi Khorasan Province"},
["森南省"] = {wp = "Semnan Province"},
["錫斯坦-俾路支斯坦省"] = {wp = "Sistan and Baluchestan Province"},
["南呼羅珊省"] = {wp = "South Khorasan Province"},
["德黑蘭省"] = {wp = "Tehran Province"},
["西阿塞拜疆省"] = {wp = "West Azerbaijan Province"},
["亞茲德省"] = {wp = "Yazd Province"},
["贊詹省"] = {wp = "Zanjan Province"},
}
export.iran_group = {
placename_to_key = false,
default_container = "伊朗",
default_placetype = "province",
data = export.iran_provinces,
}
export.ireland_counties = {
["卡洛郡"] = {wp = "County Carlow"},
["卡文郡"] = {wp = "County Cavan"},
["克萊爾郡"] = {wp = "County Clare"},
["科克郡"] = {wp = "County Cork"},
["多尼戈爾郡"] = {wp = "County Donegal"},
["都柏林郡"] = {wp = "County Dublin"},
["戈爾韋郡"] = {wp = "County Galway"},
["克里郡"] = {wp = "County Kerry"},
["基爾代爾郡"] = {wp = "County Kildare"},
["基爾肯尼郡"] = {wp = "County Kilkenny"},
["萊什郡"] = {wp = "County Laois"},
["利特里姆郡"] = {wp = "County Leitrim"},
["利默里克郡"] = {wp = "County Limerick"},
["朗福德郡"] = {wp = "County Longford"},
["勞斯郡"] = {wp = "County Louth"},
["梅奧郡"] = {wp = "County Mayo"},
["米斯郡"] = {wp = "County Meath"},
["莫納亨郡"] = {wp = "County Monaghan"},
["奧法利郡"] = {wp = "County Offaly"},
["羅斯康芒郡"] = {wp = "County Roscommon"},
["斯萊戈郡"] = {wp = "County Sligo"},
["蒂珀雷里郡"] = {wp = "County Tipperary"},
["沃特福德郡"] = {wp = "County Waterford"},
["西米斯郡"] = {wp = "County Westmeath"},
["韋克斯福德郡"] = {wp = "County Wexford"},
["威克洛郡"] = {wp = "County Wicklow"},
}
-- local function make_irish_type_key_to_placename(container_pattern)
-- return function(key)
-- key = key:gsub(container_pattern, "")
-- local elliptical_key = key:gsub("^County ", "")
-- return key, elliptical_key
-- end
-- end
-- local function make_irish_type_placename_to_key(container_suffix)
-- return function(placename)
-- if not placename:find("^County ") and not placename:find("^City ") then
-- placename = "County " .. placename
-- end
-- return placename .. container_suffix
-- end
-- end
-- counties of Ireland
export.ireland_group = {
placename_to_key = false,
default_container = "愛爾蘭",
default_placetype = "county",
data = export.ireland_counties,
}
export.italy_administrative_regions = {
["阿布魯佐"] = {wp = "Abruzzo"},
["巴西利卡塔"] = {wp = "Basilicata"},
["卡拉布里亞"] = {wp = "Calabria"},
["坎帕尼亞"] = {wp = "Campania"},
["艾米利亞-羅馬涅"] = {wp = "Emilia-Romagna"},
["弗留利-威尼斯朱利亞"] = {wp = "Friuli-Venezia Giulia"},
["拉齊奧"] = {wp = "Lazio"},
["利古里亞"] = {wp = "Liguria"},
["倫巴第"] = {wp = "Lombardy"},
["倫巴底"] = {alias_of = "倫巴第"},
["馬爾凱"] = {wp = "Marche"},
["莫利塞"] = {wp = "Molise"},
["皮埃蒙特"] = {wp = "Piedmont"},
["普利亞"] = {wp = "Apulia"},
["阿普利亞"] = {alias_of = "普利亞"},
["薩丁尼亞"] = {wp = "Sardinia"},
["西西里"] = {wp = "Sicily"},
["托斯卡納"] = {wp = "Tuscany"},
["特倫蒂諾-上阿迪傑"] = {wp = "Trentino-Alto Adige/Südtirol"},
["翁布里亞"] = {wp = "Umbria"},
["奧斯塔谷"] = {wp = "Aosta Valley"},
["威尼托"] = {wp = "Veneto"},
}
export.italy_group = {
placename_to_key = false,
default_container = "意大利",
default_placetype = "region",
data = export.italy_administrative_regions,
}
-- table of Japanese prefectures; interpolated into the main 'places' table, but also needed separately
export.japan_prefectures = {
["青森縣"] = {wp = "青森縣"},
["岩手縣"] = {wp = "岩手縣"},
["宮城縣"] = {wp = "宮城縣"},
["秋田縣"] = {wp = "秋田縣"},
["山形縣"] = {wp = "山形縣"},
["福島縣"] = {wp = "福島縣"},
["茨城縣"] = {wp = "茨城縣"},
["栃木縣"] = {wp = "栃木縣"},
["群馬縣"] = {wp = "群馬縣"},
["埼玉縣"] = {wp = "埼玉縣"},
["千葉縣"] = {wp = "千葉縣"},
["神奈川縣"] = {wp = "神奈川縣"},
["新潟縣"] = {wp = "新潟縣"},
["富山縣"] = {wp = "富山縣"},
["石川縣"] = {wp = "石川縣"},
["福井縣"] = {wp = "福井縣"},
["山梨縣"] = {wp = "山梨縣"},
["長野縣"] = {wp = "長野縣"},
["岐阜縣"] = {wp = "岐阜縣"},
["靜岡縣"] = {wp = "靜岡縣"},
["愛知縣"] = {wp = "愛知縣"},
["三重縣"] = {wp = "三重縣"},
["滋賀縣"] = {wp = "滋賀縣"},
["兵庫縣"] = {wp = "兵庫縣"},
["奈良縣"] = {wp = "奈良縣"},
["和歌山縣"] = {wp = "和歌山縣"},
["鳥取縣"] = {wp = "鳥取縣"},
["島根縣"] = {wp = "島根縣"},
["岡山縣"] = {wp = "岡山縣"},
["廣島縣"] = {wp = "廣島縣"},
["山口縣"] = {wp = "山口縣"},
["德島縣"] = {wp = "德島縣"},
["香川縣"] = {wp = "香川縣"},
["愛媛縣"] = {wp = "愛媛縣"},
["高知縣"] = {wp = "高知縣"},
["福岡縣"] = {wp = "福岡縣"},
["佐賀縣"] = {wp = "佐賀縣"},
["長崎縣"] = {wp = "長崎縣"},
["熊本縣"] = {wp = "熊本縣"},
["大分縣"] = {wp = "大分縣"},
["宮崎縣"] = {wp = "宮崎縣"},
["鹿兒島縣"] = {wp = "鹿兒島縣"},
["沖繩縣"] = {wp = "沖繩縣"},
["北海道"] = {divs = {"subprefectures"}, wp = "北海道"},
["京都府"] = {wp = "京都府"},
["大阪府"] = {wp = "大阪府"},
}
local function japan_placename_to_key(placename)
local japan_placename_to_key_map = {
-- ["東京"] = "東京都",
["大阪"] = "大阪府",
["京都"] = "京都府",
}
if placename:find("[都道府縣]$") then
return placename -- 已帶後綴,不轉換
end
return japan_placename_to_key_map[placename] or (placename .. "縣")
end
-- prefectures of Japan
export.japan_group = {
key_to_placename = make_key_to_placename(nil, "[都道府縣]$"),
placename_to_key = japan_placename_to_key,
default_container = "日本",
default_placetype = "prefecture",
default_divs = {"cities", "districts"},
data = export.japan_prefectures,
}
export.laos_provinces = {
["阿塔坡省"] = {wp = "Attapeu Province"},
["博膠省"] = {wp = "Bokeo Province"},
["波里坎賽省"] = {wp = "Bolikhamxai Province"},
["占巴塞省"] = {wp = "Champasak Province"},
["華潘省"] = {wp = "Houaphanh Province"},
["甘蒙省"] = {wp = "Khammouane Province"},
["南塔省"] = {wp = "Luang Namtha Province"},
["琅勃拉邦省"] = {wp = "Luang Prabang Province"},
["烏多姆賽省"] = {wp = "Oudomxay Province"},
["豐沙里省"] = {wp = "Phongsaly Province"},
["沙拉灣省"] = {wp = "Salavan Province"},
["沙灣拿吉省"] = {wp = "Savannakhet Province"},
["萬象省"] = {wp = "Vientiane Province"},
["萬象直轄市"] = {placetype = "prefecture", wp = "Vientiane Prefecture"},
["賽亞武里省"] = {wp = "Sainyabuli Province"},
["塞公省"] = {wp = "Sekong Province"},
["賽頌本省"] = {wp = "Xaisomboun Province"},
["川壙省"] = {wp = "Xiangkhouang Province"},
}
export.laos_group = {
placename_to_key = false,
default_container = "老撾",
default_placetype = "province",
data = export.laos_provinces,
}
export.lebanon_governorates = {
["阿卡爾省"] = {wp = "Akkar Governorate"},
["巴勒貝克-赫爾梅勒省"] = {wp = "Baalbek-Hermel Governorate"},
["貝魯特省"] = {wp = "Beirut Governorate"},
["貝卡省"] = {wp = "Beqaa Governorate"},
["克塞旺-裘拜勒省"] = {wp = "Keserwan-Jbeil Governorate"},
["黎巴嫩山省"] = {wp = "Mount Lebanon Governorate"},
["納巴蒂耶省"] = {wp = "Nabatieh Governorate"},
["北部省(黎巴嫩)"] = {wp = "North Governorate (Lebanon)", no_auto_augment_container = true},
["南部省(黎巴嫩)"] = {wp = "South Governorate (Lebanon)", no_auto_augment_container = true},
}
export.lebanon_group = {
placename_to_key = false,
default_container = "黎巴嫩",
default_placetype = "governorate",
data = export.lebanon_governorates,
}
export.malaysia_states = {
["柔佛州"] = {wp = "Johor"},
["吉打州"] = {wp = "Kedah"},
["吉蘭丹州"] = {wp = "Kelantan"},
["馬六甲州"] = {wp = "Malacca"},
["森美蘭州"] = {wp = "Negeri Sembilan"},
["彭亨州"] = {wp = "Pahang"},
["檳城州"] = {wp = "Penang"},
["霹靂州"] = {wp = "Perak"},
["玻璃市州"] = {wp = "Perlis"},
["沙巴州"] = {wp = "Sabah"},
["砂拉越州"] = {wp = "Sarawak"},
["雪蘭莪州"] = {wp = "Selangor"},
["登嘉樓州"] = {wp = "Terengganu"},
}
export.malaysia_group = {
placename_to_key = false,
default_container = "馬來西亞",
default_placetype = "state",
data = export.malaysia_states,
}
export.malta_regions = {
["東部區(馬爾他)"] = {wp = "Eastern Region, Malta", no_auto_augment_container = true},
["戈佐區"] = {wp = "Gozo Region"},
["北部區(馬爾他)"] = {wp = "Northern Region, Malta", no_auto_augment_container = true},
["港口區"] = {wp = "Port Region, Malta"},
["南部區(馬爾他)"] = {wp = "Southern Region, Malta", no_auto_augment_container = true},
["西部區(馬爾他)"] = {wp = "Western Region, Malta", no_auto_augment_container = true},
}
export.malta_group = {
placename_to_key = false,
default_container = "馬爾他",
default_placetype = "region",
data = export.malta_regions,
}
export.mexico_states = {
["阿瓜斯卡連特斯州"] = {wp = "Aguascalientes"},
["下加利福尼亞州"] = {wp = "Baja California"},
["南下加利福尼亞州"] = {wp = "Baja California Sur"},
["坎佩切州"] = {wp = "Campeche"},
["恰帕斯州"] = {wp = "Chiapas"},
["奇瓦瓦州"] = {wp = "Chihuahua (state)"},
["科阿韋拉州"] = {wp = "Coahuila"},
["科利馬州"] = {wp = "Colima"},
["杜蘭戈州"] = {wp = "Durango"},
["瓜納華托州"] = {wp = "Guanajuato"},
["格雷羅州"] = {wp = "Guerrero"},
["伊達爾戈州"] = {wp = "Hidalgo (state)"},
["哈利斯科州"] = {wp = "Jalisco"},
["墨西哥州"] = {wp = "State of Mexico"},
["米卻肯州"] = {wp = "Michoacán"},
["莫雷洛斯州"] = {wp = "Morelos"},
["納亞里特州"] = {wp = "Nayarit"},
["新萊昂州"] = {wp = "Nuevo León"},
["瓦哈卡州"] = {wp = "Oaxaca"},
["普埃布拉州"] = {wp = "Puebla"},
["克雷塔羅州"] = {wp = "Querétaro"},
["金塔納羅奧州"] = {wp = "Quintana Roo"},
["聖路易斯波托西州"] = {wp = "San Luis Potosí"},
["錫那羅亞州"] = {wp = "Sinaloa"},
["索諾拉州"] = {wp = "Sonora"},
["塔巴斯科州"] = {wp = "Tabasco (state)"},
["塔毛利帕斯州"] = {wp = "Tamaulipas"},
["特拉斯卡拉州"] = {wp = "Tlaxcala"},
["韋拉克魯斯州"] = {wp = "Veracruz"},
["尤卡坦州"] = {wp = "Yucatán"},
["薩卡特卡斯州"] = {wp = "Zacatecas"},
}
export.mexico_group = {
placename_to_key = false,
default_container = "墨西哥",
default_placetype = "state",
data = export.mexico_states,
}
export.moldova_districts_and_autonomous_territorial_units = {
["阿內尼諾伊區"] = {wp = "Anenii Noi District"},
["巴薩拉比亞斯卡區"] = {wp = "Basarabeasca District"},
["布里切尼區"] = {wp = "Briceni District"},
["卡胡爾區"] = {wp = "Cahul District"},
["坎特米爾區"] = {wp = "Cantemir District"},
["克勒拉希區"] = {wp = "Călărași District"},
["考沙尼區"] = {wp = "Căușeni District"},
["奇米什利亞區"] = {wp = "Cimișlia District"},
["克里烏列尼區"] = {wp = "Criuleni District"},
["東杜沙尼區"] = {wp = "Dondușeni District"},
["德羅基亞區"] = {wp = "Drochia District"},
["杜巴薩里區"] = {wp = "Dubăsari District"},
["埃迪涅茨區"] = {wp = "Edineț District"},
["法勒什蒂區"] = {wp = "Fălești District"},
["弗洛雷什蒂區"] = {wp = "Florești District"},
["格洛德尼區"] = {wp = "Glodeni District"},
["欣切什蒂區"] = {wp = "Hîncești District"},
["亞洛韋尼區"] = {wp = "Ialoveni District"},
["萊奧瓦區"] = {wp = "Leova District"},
["尼斯波雷尼區"] = {wp = "Nisporeni District"},
["奧克尼察區"] = {wp = "Ocnița District"},
["奧爾海伊區"] = {wp = "Orhei District"},
["雷齊納區"] = {wp = "Rezina District"},
["雷什卡尼區"] = {wp = "Rîșcani District"},
["辛格雷伊區"] = {wp = "Sîngerei District"},
["索羅卡區"] = {wp = "Soroca District"},
["斯特拉沙尼區"] = {wp = "Strășeni District"},
["紹爾達涅什蒂區"] = {wp = "Șoldănești District"},
["斯蒂凡沃達區"] = {wp = "Ștefan Vodă District"},
["塔拉克利亞區"] = {wp = "Taraclia District"},
["泰萊涅什蒂區"] = {wp = "Telenești District"},
["翁格尼區"] = {wp = "Ungheni District"},
["基希訥烏市"] = {wp = "Chișinău", placetype = "municipality"},
["巴爾茨市"] = {wp = "Bălți", placetype = "municipality"},
["加告茲自治領土"] = {wp = "Gagauzia", placetype = {"autonomous territorial unit", "autonomous region", "region"}},
["本德爾市"] = {wp = "Bender, Moldova", placetype = "municipality"},
["德涅斯特河沿岸"] = {wp = "Transnistria", placetype = {"autonomous territorial unit", "autonomous region", "region"}},
}
export.moldova_group = {
placename_to_key = false,
default_container = "摩爾多瓦",
default_placetype = {"district", "raion"},
data = export.moldova_districts_and_autonomous_territorial_units,
}
export.morocco_regions = {
["丹吉爾-得土安-霍賽馬大區"] = {wp = "Tangier-Tetouan-Al Hoceima"},
["東方大區(摩洛哥)"] = {wp = "Oriental, Morocco"},
["非斯-梅克內斯大區"] = {wp = "Fez-Meknes"},
["拉巴特-薩累-肯尼特拉大區"] = {wp = "Rabat-Salé-Kénitra"},
["貝尼梅拉勒-赫尼夫拉大區"] = {wp = "Béni Mellal-Khénifra"},
["卡薩布蘭卡-塞塔特大區"] = {wp = "Casablanca-Settat"},
["馬拉喀什-薩菲大區"] = {wp = "Marrakesh-Safi"},
["德拉阿-塔菲拉勒特大區"] = {wp = "Drâa-Tafilalet"},
["蘇斯-馬薩大區"] = {wp = "Souss-Massa"},
["蓋勒明-烏德農大區"] = {wp = "Guelmim-Oued Noun"},
["拉阿永-薩基亞-哈姆拉大區"] = {wp = "Laâyoune-Sakia El Hamra"},
["達赫拉-奧以德達赫卜大區"] = {wp = "Dakhla-Oued Ed-Dahab"},
}
export.morocco_group = {
placename_to_key = false,
default_container = "摩洛哥",
default_placetype = "region",
data = export.morocco_regions,
}
export.egypt_governorates = {
["開羅省"] = {wp = "Cairo Governorate"},
["吉薩省"] = {wp = "Giza Governorate"},
["夏爾基亞省"] = {wp = "Sharqia Governorate"},
["達卡利亞省"] = {wp = "Dakahlia Governorate"},
["貝海拉省"] = {wp = "Beheira Governorate"},
["明亞省"] = {wp = "Minya Governorate"},
["蓋柳比亞省"] = {wp = "Qalyubia Governorate"},
["索哈傑省"] = {wp = "Sohag Governorate"},
["亞歷山大省"] = {wp = "Alexandria Governorate"},
["加爾比亞省"] = {wp = "Gharbia Governorate"},
["艾斯尤特省"] = {wp = "Asyut Governorate"},
["莫諾菲亞省"] = {wp = "Monufia Governorate"},
["法尤姆省"] = {wp = "Faiyum Governorate"},
["卡夫爾謝赫省"] = {wp = "Kafr El Sheikh Governorate"},
["基納省"] = {wp = "Qena Governorate"},
["本尼蘇威夫省"] = {wp = "Beni Suef Governorate"},
["達米埃塔省"] = {wp = "Damietta Governorate"},
["阿斯旺省"] = {wp = "Aswan Governorate"},
["伊斯梅利亞省"] = {wp = "Ismailia Governorate"},
["盧克索省"] = {wp = "Luxor Governorate"},
["蘇伊士省"] = {wp = "Suez Governorate"},
["塞德港省"] = {wp = "Port Said Governorate"},
["馬特魯省"] = {wp = "Matrouh Governorate"},
["北西奈省"] = {wp = "North Sinai Governorate"},
["紅海省"] = {wp = "Red Sea Governorate"},
["新河谷省"] = {wp = "New Valley Governorate"},
["南西奈省"] = {wp = "South Sinai Governorate"},
}
export.egypt_group = {
placename_to_key = false,
default_container = "埃及",
default_placetype = "governorate",
data = export.egypt_governorates,
}
export.spain_autonomous_communities = {
["安達盧西亞"] = {wp = "Andalusia"},
["安達魯西亞"] = {alias_of = "安達盧西亞"},
["阿拉貢"] = {wp = "Aragon"},
["阿斯圖里亞斯"] = {wp = "Asturias"},
["巴利阿里群島"] = {wp = "Balearic Islands"},
["巴斯克"] = {wp = "Basque Country (autonomous community)"},
["加那利群島"] = {wp = "Canary Islands"},
["坎塔布里亞"] = {wp = "Cantabria"},
["卡斯蒂利亞-拉曼恰"] = {wp = "Castilla-La Mancha"},
["卡斯蒂利亞-萊昂"] = {wp = "Castilla y León"},
["加泰羅尼亞"] = {wp = "Catalonia"},
["加泰隆尼亞"] = {alias_of = "加泰羅尼亞"},
["埃斯特雷馬杜拉"] = {wp = "Extremadura"},
["加利西亞"] = {wp = "Galicia (Spain)"},
["拉里奧哈"] = {wp = "La Rioja (autonomous community)"},
["馬德里自治區"] = {wp = "Community of Madrid"},
["穆爾西亞"] = {wp = "Region of Murcia"},
["納瓦拉"] = {wp = "Navarre"},
["巴倫西亞"] = {wp = "Valencian Community"},
["巴倫西亞自治區"] = {alias_of = "巴倫西亞"},
}
export.spain_group = {
placename_to_key = false,
default_container = "西班牙",
default_placetype = "autonomous community",
data = export.spain_autonomous_communities,
}
export.taiwan_counties = {
["彰化縣"] = {wp = "彰化縣"},
["嘉義縣"] = {wp = "嘉義縣"},
["新竹縣"] = {wp = "新竹縣"},
["花蓮縣"] = {wp = "花蓮縣"},
["金門縣"] = {wp = "金門縣"},
["連江縣"] = {wp = "連江縣"},
["苗栗縣"] = {wp = "苗栗縣"},
["南投縣"] = {wp = "南投縣"},
["屏東縣"] = {wp = "屏東縣"},
["澎湖縣"] = {wp = "澎湖縣"},
["宜蘭縣"] = {wp = "宜蘭縣"},
["雲林縣"] = {wp = "雲林縣"},
}
export.taiwan_group = {
key_to_placename = make_key_to_placename(nil, "縣$"),
placename_to_key = make_placename_to_key("縣"),
default_container = "臺灣",
default_placetype = "county",
default_divs = {"districts", "townships"},
data = export.taiwan_counties,
}
export.new_york_boroughs = {
["布朗克斯"] = {the = true, wp = "The Bronx"},
["布魯克林"] = {wp = "Brooklyn"},
["曼哈頓"] = {wp = "Manhattan"},
["皇后區"] = {wp = "Queens"},
["史泰登島"] = {wp = "Staten Island"},
}
export.new_york_boroughs_group = {
placename_to_key = false,
default_container = {key = "紐約市", placetype = "city"},
default_placetype = "borough",
default_is_city = true,
data = export.new_york_boroughs,
}
export.romania_counties = {
["阿爾巴縣"] = {wp = "Alba County"},
["阿拉德縣"] = {wp = "Arad County"},
["阿爾傑什縣"] = {wp = "Argeș County"},
["巴考縣"] = {wp = "Bacău County"},
["比霍爾縣"] = {wp = "Bihor County"},
["比斯特里察-訥瑟烏德縣"] = {wp = "Bistrița-Năsăud County"},
["博托沙尼縣"] = {wp = "Botoșani County"},
["布拉索夫縣"] = {wp = "Brașov County"},
["布勒伊拉縣"] = {wp = "Brăila County"},
["布澤烏縣"] = {wp = "Buzău County"},
["卡拉什-塞韋林縣"] = {wp = "Caraș-Severin County"},
["克盧日縣"] = {wp = "Cluj County"},
["康斯坦察縣"] = {wp = "Constanța County"},
["科瓦斯納縣"] = {wp = "Covasna County"},
["克勒拉希縣"] = {wp = "Călărași County"},
["多爾日縣"] = {wp = "Dolj County"},
["登博維察縣"] = {wp = "Dâmbovița County"},
["加拉茨縣"] = {wp = "Galați County"},
["久爾久縣"] = {wp = "Giurgiu County"},
["戈爾日縣"] = {wp = "Gorj County"},
["哈爾吉塔縣"] = {wp = "Harghita County"},
["胡內多阿拉縣"] = {wp = "Hunedoara County"},
["雅洛米察縣"] = {wp = "Ialomița County"},
["雅西縣"] = {wp = "Iași County"},
["伊爾福夫縣"] = {wp = "Ilfov County"},
["馬拉穆列什縣"] = {wp = "Maramureș County"},
["梅赫丁茨縣"] = {wp = "Mehedinți County"},
["穆列什縣"] = {wp = "Mureș County"},
["尼亞姆茨縣"] = {wp = "Neamț County"},
["奧爾特縣"] = {wp = "Olt County"},
["普拉霍瓦縣"] = {wp = "Prahova County"},
["薩圖馬雷縣"] = {wp = "Satu Mare County"},
["錫比烏縣"] = {wp = "Sibiu County"},
["蘇恰瓦縣"] = {wp = "Suceava County"},
["瑟拉日縣"] = {wp = "Sălaj County"},
["特列奧爾曼縣"] = {wp = "Teleorman County"},
["蒂米什縣"] = {wp = "Timiș County"},
["圖爾恰縣"] = {wp = "Tulcea County"},
["瓦斯盧伊縣"] = {wp = "Vaslui County"},
["弗蘭恰縣"] = {wp = "Vrancea County"},
["沃爾恰縣"] = {wp = "Vâlcea County"},
}
export.romania_group = {
placename_to_key = false,
default_container = "羅馬尼亞",
default_placetype = "county",
data = export.romania_counties,
}
export.north_korea_provinces = {
["慈江道"] = {wp = "慈江道"},
["咸鏡北道"] = {wp = "咸鏡北道"},
["咸鏡南道"] = {wp = "咸鏡南道"},
["黃海北道"] = {wp = "黃海北道"},
["黃海南道"] = {wp = "黃海南道"},
["江原道(北韓)"] = {wp = "江原道 (朝鮮民主主義人民共和國)"},
["平安北道"] = {wp = "平安北道"},
["平安南道"] = {wp = "平安南道"},
["兩江道"] = {wp = "兩江道"},
}
export.north_korea_group = {
placename_to_key = false,
default_container = "朝鮮",
default_placetype = "province",
data = export.north_korea_provinces,
}
export.south_korea_provinces = {
["忠清北道"] = {wp = "忠清北道"},
["忠清南道"] = {wp = "忠清南道"},
["江原道(南韓)"] = {wp = "江原特別自治道"},
["京畿道"] = {wp = "京畿道"},
["慶尚北道"] = {wp = "慶尚北道"},
["慶尚南道"] = {wp = "慶尚南道"},
["全羅北道"] = {wp = "全羅北道"},
["全羅南道"] = {wp = "全羅南道"},
["濟州道"] = {wp = "濟州道"},
}
export.south_korea_group = {
placename_to_key = false,
default_container = "韓國",
default_placetype = "province",
data = export.south_korea_provinces,
}
export.thailand_provinces = {
["阿姆納特坤府"] = {wp = "Amnat Charoen Province"},
["紅統府"] = {wp = "Ang Thong Province"},
["彭侃府"] = {wp = "Bueng Kan Province"},
["武里南府"] = {wp = "Buriram Province"},
["北柳府"] = {wp = "Chachoengsao Province"},
["猜納府"] = {wp = "Chai Nat Province"},
["猜耶奔府"] = {wp = "Chaiyaphum Province"},
["尖竹汶府"] = {wp = "Chanthaburi Province"},
["清邁府"] = {wp = "Chiang Mai Province"},
["清萊府"] = {wp = "Chiang Rai Province"},
["春武里府"] = {wp = "Chonburi Province"},
["春蓬府"] = {wp = "Chumphon Province"},
["加拉信府"] = {wp = "Kalasin Province"},
["甘烹碧府"] = {wp = "Kamphaeng Phet Province"},
["北碧府"] = {wp = "Kanchanaburi Province"},
["孔敬府"] = {wp = "Khon Kaen Province"},
["甲米府"] = {wp = "Krabi Province"},
["南邦府"] = {wp = "Lampang Province"},
["南奔府"] = {wp = "Lamphun Province"},
["黎府"] = {wp = "Loei Province"},
["華富里府"] = {wp = "Lopburi Province"},
["夜豐頌府"] = {wp = "Mae Hong Son Province"},
["瑪哈沙拉堪府"] = {wp = "Maha Sarakham Province"},
["莫達罕府"] = {wp = "Mukdahan Province"},
["那空那育府"] = {wp = "Nakhon Nayok Province"},
["佛統府"] = {wp = "Nakhon Pathom Province"},
["那空帕農府"] = {wp = "Nakhon Phanom Province"},
["呵叻府"] = {wp = "Nakhon Ratchasima Province"},
["那空沙旺府"] = {wp = "Nakhon Sawan Province"},
["洛坤府"] = {wp = "Nakhon Si Thammarat Province"},
["難府"] = {wp = "Nan Province"},
["那拉提瓦府"] = {wp = "Narathiwat Province"},
["農布瓦蘭普府"] = {wp = "Nong Bua Lamphu Province"},
["廊開府"] = {wp = "Nong Khai Province"},
["暖武里府"] = {wp = "Nonthaburi Province"},
["巴吞他尼府"] = {wp = "Pathum Thani Province"},
["北大年府"] = {wp = "Pattani Province"},
["攀牙府"] = {wp = "Phang Nga Province"},
["博他侖府"] = {wp = "Phatthalung Province"},
["帕堯府"] = {wp = "Phayao Province"},
["碧差汶府"] = {wp = "Phetchabun Province"},
["碧武里府"] = {wp = "Phetchaburi Province"},
["披集府"] = {wp = "Phichit Province"},
["彭世洛府"] = {wp = "Phitsanulok Province"},
["大城府"] = {wp = "Phra Nakhon Si Ayutthaya Province"},
["帕府"] = {wp = "Phrae Province"},
["普吉府"] = {wp = "Phuket Province"},
["巴真府"] = {wp = "Prachinburi Province"},
["巴蜀府"] = {wp = "Prachuap Khiri Khan Province"},
["拉廊府"] = {wp = "Ranong Province"},
["叻丕府"] = {wp = "Ratchaburi Province"},
["羅勇府"] = {wp = "Rayong Province"},
["黎逸府"] = {wp = "Roi Et Province"},
["沙繳府"] = {wp = "Sa Kaeo Province"},
["色軍府"] = {wp = "Sakon Nakhon Province"},
["北欖府"] = {wp = "Samut Prakan Province"},
["沙沒沙空府"] = {wp = "Samut Sakhon Province"},
["沙沒頌堪府"] = {wp = "Samut Songkhram Province"},
["沙拉武里府"] = {wp = "Saraburi Province"},
["沙敦府"] = {wp = "Satun Province"},
["信武里府"] = {wp = "Sing Buri Province"},
["四色菊府"] = {wp = "Sisaket Province"},
["宋卡府"] = {wp = "Songkhla Province"},
["素可泰府"] = {wp = "Sukhothai Province"},
["素攀武里府"] = {wp = "Suphan Buri Province"},
["素叻他尼府"] = {wp = "Surat Thani Province"},
["素林府"] = {wp = "Surin Province"},
["達府"] = {wp = "Tak Province"},
["董里府"] = {wp = "Trang Province"},
["達叻府"] = {wp = "Trat Province"},
["烏汶府"] = {wp = "Ubon Ratchathani Province"},
["烏隆府"] = {wp = "Udon Thani Province"},
["烏泰他尼府"] = {wp = "Uthai Thani Province"},
["烏達拉勐府"] = {wp = "Uttaradit Province"},
["惹拉府"] = {wp = "Yala Province"},
["耶梭通府"] = {wp = "Yasothon Province"},
}
export.thailand_group = {
placename_to_key = false,
default_container = "泰國",
default_placetype = "province",
data = export.thailand_provinces,
}
export.turkey_provinces = {
["阿達納省"] = {wp = "Adana Province"},
["阿德亞曼省"] = {wp = "Adıyaman Province"},
["阿菲永卡拉希薩爾省"] = {wp = "Afyonkarahisar Province"},
["阿里省"] = {wp = "Ağrı Province"},
["阿馬西亞省"] = {wp = "Amasya Province"},
["安卡拉省"] = {wp = "Ankara Province"},
["安塔利亞省"] = {wp = "Antalya Province"},
["阿爾特溫省"] = {wp = "Artvin Province"},
["艾登省"] = {wp = "Aydın Province"},
["巴勒克埃西爾省"] = {wp = "Balıkesir Province"},
["比萊吉克省"] = {wp = "Bilecik Province"},
["賓格爾省"] = {wp = "Bingöl Province"},
["比特利斯省"] = {wp = "Bitlis Province"},
["博盧省"] = {wp = "Bolu Province"},
["布爾杜爾省"] = {wp = "Burdur Province"},
["布爾薩省"] = {wp = "Bursa Province"},
["恰納卡萊省"] = {wp = "Çanakkale Province"},
["昌克勒省"] = {wp = "Çankırı Province"},
["喬魯姆省"] = {wp = "Çorum Province"},
["代尼茲利省"] = {wp = "Denizli Province"},
["迪亞巴克爾省"] = {wp = "Diyarbakır Province"},
["埃迪爾內省"] = {wp = "Edirne Province"},
["埃拉澤省"] = {wp = "Elazığ Province"},
["埃爾津詹省"] = {wp = "Erzincan Province"},
["埃爾祖魯姆省"] = {wp = "Erzurum Province"},
["埃斯基謝希爾省"] = {wp = "Eskişehir Province"},
["加濟安泰普省"] = {wp = "Gaziantep Province"},
["吉雷松省"] = {wp = "Giresun Province"},
["居米什哈內省"] = {wp = "Gümüşhane Province"},
["哈卡里省"] = {wp = "Hakkâri Province"},
["哈塔伊省"] = {wp = "Hatay Province"},
["伊斯帕爾塔省"] = {wp = "Isparta Province"},
["梅爾辛省"] = {wp = "Mersin Province"},
["伊茲密爾省"] = {wp = "İzmir Province"},
["卡爾斯省"] = {wp = "Kars Province"},
["卡斯塔莫努省"] = {wp = "Kastamonu Province"},
["開塞利省"] = {wp = "Kayseri Province"},
["柯克拉雷利省"] = {wp = "Kırklareli Province"},
["克爾謝希爾省"] = {wp = "Kırşehir Province"},
["科賈埃利省"] = {wp = "Kocaeli Province"},
["科尼亞省"] = {wp = "Konya Province"},
["屈塔希亞省"] = {wp = "Kütahya Province"},
["馬拉蒂亞省"] = {wp = "Malatya Province"},
["馬尼薩省"] = {wp = "Manisa Province"},
["卡赫拉曼馬拉什省"] = {wp = "Kahramanmaraş Province"},
["馬爾丁省"] = {wp = "Mardin Province"},
["穆拉省"] = {wp = "Muğla Province"},
["穆什省"] = {wp = "Muş Province"},
["內夫謝希爾省"] = {wp = "Nevşehir Province"},
["尼代省"] = {wp = "Niğde Province"},
["奧爾杜省"] = {wp = "Ordu Province"},
["里澤省"] = {wp = "Rize Province"},
["薩卡里亞省"] = {wp = "Sakarya Province"},
["薩姆松省"] = {wp = "Samsun Province"},
["錫爾特省"] = {wp = "Siirt Province"},
["錫諾普省"] = {wp = "Sinop Province"},
["錫瓦斯省"] = {wp = "Sivas Province"},
["泰基爾達省"] = {wp = "Tekirdağ Province"},
["托卡特省"] = {wp = "Tokat Province"},
["特拉布宗省"] = {wp = "Trabzon Province"},
["通傑利省"] = {wp = "Tunceli Province"},
["尚勒烏爾法省"] = {wp = "Şanlıurfa Province"},
["烏沙克省"] = {wp = "Uşak Province"},
["凡省"] = {wp = "Van Province"},
["約茲加特省"] = {wp = "Yozgat Province"},
["宗古爾達克省"] = {wp = "Zonguldak Province"},
["阿克薩賴省"] = {wp = "Aksaray Province"},
["拜布爾特省"] = {wp = "Bayburt Province"},
["卡拉曼省"] = {wp = "Karaman Province"},
["克勒克卡萊省"] = {wp = "Kırıkkale Province"},
["巴特曼省"] = {wp = "Batman Province"},
["蘇爾納克省"] = {wp = "Şırnak Province"},
["巴爾騰省"] = {wp = "Bartın Province"},
["阿爾達罕省"] = {wp = "Ardahan Province"},
["伊迪爾省"] = {wp = "Iğdır Province"},
["亞洛瓦省"] = {wp = "Yalova Province"},
["卡拉比克省"] = {wp = "Karabük Province"},
["基利斯省"] = {wp = "Kilis Province"},
["奧斯曼尼耶省"] = {wp = "Osmaniye Province"},
["迪茲傑省"] = {wp = "Düzce Province"},
}
export.turkey_group = {
placename_to_key = false,
default_container = "土耳其",
default_placetype = "province",
data = export.turkey_provinces,
}
export.vietnam_provinces = {
-- Northeast
["北江省"] = {wp = "Bắc Giang Province"},
["北件省"] = {wp = "Bắc Kạn Province"},
["高平省"] = {wp = "Cao Bằng Province"},
["河江省"] = {wp = "Hà Giang Province"},
["諒山省"] = {wp = "Lạng Sơn Province"},
["富壽省"] = {wp = "Phú Thọ Province"},
["廣寧省"] = {wp = "Quảng Ninh Province"},
["太原省"] = {wp = "Thái Nguyên Province"},
["宣光省"] = {wp = "Tuyên Quang Province"},
-- Northwest
["老街省"] = {wp = "Lào Cai Province"},
["安沛省"] = {wp = "Yên Bái Province"},
["奠邊省"] = {wp = "Điện Biên Province"},
["和平省"] = {wp = "Hoà Bình Province"},
["萊州省"] = {wp = "Lai Châu Province"},
["山羅省"] = {wp = "Sơn La Province"},
-- Red River Delta
["北寧省"] = {wp = "Bắc Ninh Province"},
["河南省"] = {wp = "Hà Nam Province"},
["海陽省"] = {wp = "Hải Dương Province"},
["興安省"] = {wp = "Hưng Yên Province"},
["南定省"] = {wp = "Nam Định Province"},
["寧平省"] = {wp = "Ninh Bình Province"},
["太平省"] = {wp = "Thái Bình Province"},
["永福省"] = {wp = "Vĩnh Phúc Province"},
-- North Central Coast
["河靜省"] = {wp = "Hà Tĩnh Province"},
["乂安省"] = {wp = "Nghệ An Province"},
["廣平省"] = {wp = "Quảng Bình Province"},
["廣治省"] = {wp = "Quảng Trị Province"},
["清化省"] = {wp = "Thanh Hoá Province"},
-- Central Highlands
["得樂省"] = {wp = "Đắk Lắk Province"},
["得農省"] = {wp = "Đăk Nông Province"},
["嘉萊省"] = {wp = "Gia Lai Province"},
["崑嵩省"] = {wp = "Kon Tum Province"},
["林同省"] = {wp = "Lâm Đồng Province"},
-- South Central Coast
["平定省"] = {wp = "Bình Định Province"},
["平順省"] = {wp = "Bình Thuận Province"},
["慶和省"] = {wp = "Khánh Hoà Province"},
["寧順省"] = {wp = "Ninh Thuận Province"},
["富安省"] = {wp = "Phú Yên Province"},
["廣南省"] = {wp = "Quảng Nam Province"},
["廣義省"] = {wp = "Quảng Ngãi Province"},
-- Southeast
["巴地-頭頓省"] = {wp = "Bà Rịa–Vũng Tàu Province"},
["平陽省"] = {wp = "Bình Dương Province"},
["平福省"] = {wp = "Bình Phước Province"},
["同奈省"] = {wp = "Đồng Nai Province"},
["西寧省"] = {wp = "Tây Ninh Province"},
-- Mekong Delta
["安江省"] = {wp = "An Giang Province"},
["薄寮省"] = {wp = "Bạc Liêu Province"},
["檳椥省"] = {wp = "Bến Tre Province"},
["金甌省"] = {wp = "Cà Mau Province"},
["同塔省"] = {wp = "Đồng Tháp Province"},
["後江省"] = {wp = "Hậu Giang Province"},
["堅江省"] = {wp = "Kiên Giang Province"},
["龍安省"] = {wp = "Long An Province"},
["朔莊省"] = {wp = "Sóc Trăng Province"},
["前江省"] = {wp = "Tiền Giang Province"},
["茶榮省"] = {wp = "Trà Vinh Province"},
["永隆省"] = {wp = "Vĩnh Long Province"},
}
export.vietnam_group = {
placename_to_key = false,
default_container = "越南",
default_placetype = "province",
data = export.vietnam_provinces,
}
export.united_states_states = {
["阿拉巴馬"] = {wp = "Alabama"},
["阿拉斯加"] = {wp = "Alaska"},
["亞利桑那"] = {wp = "Arizona"},
["阿肯色"] = {wp = "Arkansas"},
["加利福尼亞"] = {wp = "California"},
["加州"] = {alias_of = "加利福尼亞"},
["科羅拉多"] = {wp = "Colorado"},
["康乃狄克"] = {wp = "Connecticut"},
["康涅狄格"] = {alias_of = "康乃狄克"},
["德拉瓦"] = {wp = "Delaware"},
["佛羅里達"] = {wp = "Florida"},
["佛罗里达"] = {alias_of = "佛羅里達"},
["佐治亞"] = {wp = "Georgia (U.S. state)"},
["夏威夷"] = {addl_parents = {"玻里尼西亞"}, wp = "Hawaii"},
["愛達荷"] = {wp = "Idaho"},
["伊利諾伊"] = {wp = "Illinois"},
["伊利诺伊"] = {alias_of = "伊利諾伊"},
["印地安那"] = {wp = "Indiana"},
["印第安納"] = {alias_of = "印地安那"},
["愛荷華"] = {wp = "Iowa"},
["堪薩斯"] = {wp = "Kansas"},
["肯塔基"] = {wp = "Kentucky"},
["路易斯安那"] = {wp = "Louisiana"},
["緬因"] = {wp = "Maine"},
["馬里蘭"] = {wp = "Maryland"},
["麻薩諸塞"] = {wp = "Massachusetts"},
["麻薩诸塞"] = {alias_of = "麻薩諸塞"},
["密西根"] = {wp = "Michigan"},
["密歇根"] = {alias_of = "密西根"},
["明尼蘇達"] = {wp = "Minnesota"},
["明尼苏达"] = {alias_of = "明尼蘇達"},
["密西西比"] = {wp = "Mississippi"},
["密蘇里"] = {wp = "Missouri"},
["蒙大拿"] = {wp = "Montana"},
["內布拉斯加"] = {wp = "Nebraska"},
["內華達"] = {wp = "Nevada"},
["新罕布什爾"] = {wp = "New Hampshire"},
["新澤西"] = {wp = "New Jersey"},
["新泽西"] = {alias_of = "新澤西"},
["新墨西哥"] = {wp = "New Mexico"},
["紐約"] = {wp = "New York (state)"},
["纽约"] = {alias_of = "紐約"},
["北卡羅來納"] = {wp = "North Carolina"},
["北达科他"] = {alias_of = "北達科他"},
["北達科他"] = {wp = "North Dakota"},
["俄亥俄"] = {wp = "Ohio"},
["奧克拉荷馬"] = {wp = "Oklahoma"},
["奧勒岡"] = {wp = "Oregon"},
["俄勒冈"] = {alias_of = "奧勒岡"},
["賓夕法尼亞"] = {wp = "Pennsylvania"},
["宾夕法尼亚"] = {alias_of = "賓夕法尼亞"},
["羅德島"] = {wp = "Rhode Island"},
["南卡羅來納"] = {wp = "South Carolina"},
["南達科他"] = {wp = "South Dakota"},
["田納西"] = {wp = "Tennessee"},
["田纳西"] = {alias_of = "田納西"},
["德克薩斯"] = {wp = "Texas"},
["德州"] = {alias_of = "德克薩斯"},
["猶他"] = {wp = "Utah"},
["佛蒙特"] = {wp = "Vermont"},
["弗吉尼亞"] = {wp = "Virginia"},
["弗吉尼亚"] = {alias_of = "弗吉尼亞"},
["維吉尼亞"] = {alias_of = "弗吉尼亞"},
["華盛頓州"] = {wp = "Washington (state)"},
["华盛顿州"] = {alias_of = "華盛頓州"},
["西維吉尼亞"] = {wp = "West Virginia"},
["西弗吉尼亞"] = {alias_of = "西維吉尼亞"},
["威斯康辛"] = {wp = "Wisconsin"},
["懷俄明"] = {wp = "Wyoming"},
-- Washington D.C. (federal district, treated like a state in holonym context)
["華盛頓特區"] = {placetype = {"federal district", "district"}, wp = "Washington, D.C."},
}
export.united_states_group = {
placename_to_key = false,
default_container = "美國",
default_placetype = "state",
default_divs = {"counties", "county seats"},
addl_divs = {
{type = "census-designated places", prep = "in"},
{type = "unincorporated communities", prep = "in"},
},
data = export.united_states_states,
}
export.united_kingdom_constituent_countries = {
["英格蘭"] = {divs = {"counties", "districts"}, wp = "England"},
["北愛爾蘭"] = {
placetype = {"constituent country", "province", "country"},
divs = {"counties", "districts"},
wp = "Northern Ireland",
},
["蘇格蘭"] = {divs = {{type = "council areas", container_parent_type = false}}, wp = "Scotland"},
["威爾士"] = {divs = {"counties"}, wp = "Wales"},
["威爾斯"] = {alias_of = "威爾士"},
}
export.united_kingdom_group = {
placename_to_key = false,
default_container = "英國",
default_placetype = {"constituent country", "country"},
default_no_container_cat = true,
data = export.united_kingdom_constituent_countries,
}
-- Scotland council areas
export.scotland_council_areas = {
["Aberdeen市"] = {wp = "Aberdeen"},
["Aberdeenshire"] = {wp = "Aberdeenshire"},
["Angus"] = {wp = "Angus, Scotland"},
["Argyll and Bute"] = {wp = "Argyll and Bute"},
["Clackmannanshire"] = {wp = "Clackmannanshire"},
["Dumfries and Galloway"] = {wp = "Dumfries and Galloway"},
["Dundee市"] = {wp = "Dundee"},
["East Ayrshire"] = {wp = "East Ayrshire"},
["East Dunbartonshire"] = {wp = "East Dunbartonshire"},
["East Lothian"] = {wp = "East Lothian"},
["East Renfrewshire"] = {wp = "East Renfrewshire"},
["Edinburgh市"] = {wp = "Edinburgh"},
["Eilean Siar"] = {wp = "Outer Hebrides"},
["Falkirk"] = {wp = "Falkirk"},
["Fife"] = {wp = "Fife"},
["Glasgow市"] = {wp = "Glasgow"},
["高地"] = {wp = "Highland council area"},
["Highland"] = {alias_of = "高地", display = true},
["Inverclyde"] = {wp = "Inverclyde"},
["Midlothian"] = {wp = "Midlothian"},
["Moray"] = {wp = "Moray"},
["North Ayrshire"] = {wp = "North Ayrshire"},
["North Lanarkshire"] = {wp = "North Lanarkshire"},
["Orkney群島"] = {wp = "Orkney"},
["Perth and Kinross"] = {wp = "Perth and Kinross"},
["Renfrewshire"] = {wp = "Renfrewshire"},
["Scottish Borders"] = {wp = "Scottish Borders"},
["Shetland群島"] = {wp = "Shetland"},
["South Ayrshire"] = {wp = "South Ayrshire"},
["South Lanarkshire"] = {wp = "South Lanarkshire"},
["Stirling"] = {wp = "Stirling (council area)"},
["West Dunbartonshire"] = {wp = "West Dunbartonshire"},
["West Lothian"] = {wp = "West Lothian"},
}
export.scotland_group = {
placename_to_key = false,
default_container = "蘇格蘭",
default_placetype = "council area",
data = export.scotland_council_areas,
}
-- England counties (ceremonial)
export.england_counties = {
-- Metropolitan counties
["大倫敦"] = {wp = "Greater London"},
["大曼徹斯特"] = {wp = "Greater Manchester"},
["默西賽德郡"] = {wp = "Merseyside"},
["西密德蘭"] = {wp = "West Midlands (county)"},
["南約克郡"] = {wp = "South Yorkshire"},
["西約克郡"] = {wp = "West Yorkshire"},
["泰恩-威爾郡"] = {wp = "Tyne and Wear"},
-- Non-metropolitan ceremonial counties
["貝德福德郡"] = {wp = "Bedfordshire"},
["伯克郡"] = {wp = "Berkshire"},
["白金漢郡"] = {wp = "Buckinghamshire"},
["劍橋郡"] = {wp = "Cambridgeshire"},
["柴郡"] = {wp = "Cheshire"},
["康沃爾郡"] = {wp = "Cornwall"},
["坎布里亞郡"] = {wp = "Cumbria"},
["德比郡"] = {wp = "Derbyshire"},
["德文郡"] = {wp = "Devon"},
["多塞特郡"] = {wp = "Dorset"},
["達勒姆郡"] = {wp = "County Durham"},
["約克郡東區"] = {wp = "East Riding of Yorkshire"},
["東薩塞克斯郡"] = {wp = "East Sussex"},
["埃塞克斯郡"] = {wp = "Essex"},
["格洛斯特郡"] = {wp = "Gloucestershire"},
["漢普郡"] = {wp = "Hampshire"},
["赫里福德郡"] = {wp = "Herefordshire"},
["赫特福德郡"] = {wp = "Hertfordshire"},
["懷特島"] = {wp = "Isle of Wight"},
["肯特郡"] = {wp = "Kent"},
["蘭開夏郡"] = {wp = "Lancashire"},
["萊斯特郡"] = {wp = "Leicestershire"},
["林肯郡"] = {wp = "Lincolnshire"},
["諾福克郡"] = {wp = "Norfolk"},
["北安普敦郡"] = {wp = "Northamptonshire"},
["諾森伯蘭郡"] = {wp = "Northumberland"},
["北約克郡"] = {wp = "North Yorkshire"},
["諾丁漢郡"] = {wp = "Nottinghamshire"},
["牛津郡"] = {wp = "Oxfordshire"},
["拉特蘭郡"] = {wp = "Rutland"},
["什羅普郡"] = {wp = "Shropshire"},
["薩默塞特郡"] = {wp = "Somerset"},
["南亨伯賽德郡"] = {wp = "South Humberside"},
["斯塔福德郡"] = {wp = "Staffordshire"},
["薩福克郡"] = {wp = "Suffolk"},
["薩里郡"] = {wp = "Surrey"},
["沃里克郡"] = {wp = "Warwickshire"},
["西薩塞克斯郡"] = {wp = "West Sussex"},
["威爾特郡"] = {wp = "Wiltshire"},
["伍斯特郡"] = {wp = "Worcestershire"},
}
export.england_group = {
placename_to_key = false,
default_container = {key = "英格蘭", placetype = "constituent country"},
default_placetype = "county",
data = export.england_counties,
}
-- Counties of Northern Ireland
export.northern_ireland_counties = {
["安特里姆郡"] = {wp = "County Antrim"},
["阿爾馬郡"] = {wp = "County Armagh"},
["貝爾法斯特市"] = {is_city = true, wp = "Belfast"},
["唐郡"] = {wp = "County Down"},
["弗馬納郡"] = {wp = "County Fermanagh"},
["倫敦德里郡"] = {wp = "County Londonderry"},
["德里市"] = {is_city = true, wp = "Derry"},
["泰龍郡"] = {wp = "County Tyrone"},
}
export.northern_ireland_group = {
placename_to_key = false,
default_container = {key = "北愛爾蘭", placetype = "constituent country"},
default_placetype = "county",
data = export.northern_ireland_counties,
}
export.nigeria_states = {
["阿比亞州"] = {wp = "Abia State"},
["阿達馬瓦州"] = {wp = "Adamawa State"},
["阿誇伊博姆州"] = {wp = "Akwa Ibom State"},
["阿南布拉州"] = {wp = "Anambra State"},
["包奇州"] = {wp = "Bauchi State"},
["貝耶薩州"] = {wp = "Bayelsa State"},
["貝努埃州"] = {wp = "Benue State"},
["博爾諾州"] = {wp = "Borno State"},
["跨河州"] = {wp = "Cross River State"},
["三角洲州"] = {wp = "Delta State"},
["埃博尼州"] = {wp = "Ebonyi State"},
["埃多州"] = {wp = "Edo State"},
["埃基蒂州"] = {wp = "Ekiti State"},
["埃努古州"] = {wp = "Enugu State"},
["阿布賈聯邦首都轄區"] = {placetype = {"federal territory", "territory", "state"}, the = true, wp = "Federal Capital Territory, Nigeria"},
["貢貝州"] = {wp = "Gombe State"},
["伊莫州"] = {wp = "Imo State"},
["吉加瓦州"] = {wp = "Jigawa State"},
["卡杜納州"] = {wp = "Kaduna State"},
["卡諾州"] = {wp = "Kano State"},
["卡齊納州"] = {wp = "Katsina State"},
["克比州"] = {wp = "Kebbi State"},
["科吉州"] = {wp = "Kogi State"},
["誇拉州"] = {wp = "Kwara State"},
["拉各斯州"] = {wp = "Lagos State"},
["納薩拉瓦州"] = {wp = "Nasarawa State"},
["尼日爾州"] = {wp = "Niger State"},
["奧貢州"] = {wp = "Ogun State"},
["翁多州"] = {wp = "Ondo State"},
["奧順州"] = {wp = "Osun State"},
["奧約州"] = {wp = "Oyo State"},
["高原州"] = {wp = "Plateau State"},
["河流州"] = {wp = "Rivers State"},
["索科托州"] = {wp = "Sokoto State"},
["塔拉巴州"] = {wp = "Taraba State"},
["約貝州"] = {wp = "Yobe State"},
["贊法拉州"] = {wp = "Zamfara State"},
}
export.nigeria_group = {
placename_to_key = false,
default_container = "奈及利亞",
default_placetype = "state",
data = export.nigeria_states,
}
-----------------------------------------------------------------------------------
-- Selected city groups --
-----------------------------------------------------------------------------------
-- Key world cities frequently used as holonyms (entries covered by dedicated city groups removed)
export.misc_cities = {
-- Americas
["費爾法克斯"] = {container = {key = "弗吉尼亞", placetype = "state"}, placetype = "city", is_city = true, wp = "Fairfax, Virginia"},
["斯克蘭頓"] = {container = {key = "賓夕法尼亞", placetype = "state"}, placetype = "city", is_city = true, wp = "Scranton, Pennsylvania"},
["里約熱內盧市"] = {container = "巴西", placetype = "city", is_city = true, wp = "Rio de Janeiro"},
["布宜諾斯艾利斯"] = {container = "阿根廷", placetype = "city", is_city = true, wp = "Buenos Aires"},
["聖地牙哥"] = {container = "智利", placetype = "city", is_city = true, wp = "Santiago"},
["波哥大"] = {container = "哥倫比亞", placetype = "city", is_city = true, wp = "Bogotá"},
["利馬"] = {container = "秘魯", placetype = "city", is_city = true, wp = "Lima"},
-- Europe
["阿姆斯特丹"] = {container = "荷蘭", placetype = "city", is_city = true, wp = "Amsterdam"},
["布魯塞爾"] = {container = "比利時", placetype = "city", is_city = true, wp = "Brussels"},
["維也納"] = {container = "奧地利", placetype = "city", is_city = true, wp = "Vienna"},
["伊斯坦布爾"] = {container = "土耳其", placetype = "city", is_city = true, wp = "Istanbul"},
["基輔"] = {container = "烏克蘭", placetype = "city", is_city = true, wp = "Kyiv"},
["華沙"] = {container = "波蘭", placetype = "city", is_city = true, wp = "Warsaw"},
["布拉格"] = {container = "捷克", placetype = "city", is_city = true, wp = "Prague"},
["布達佩斯"] = {container = "匈牙利", placetype = "city", is_city = true, wp = "Budapest"},
["斯德哥爾摩"] = {container = "瑞典", placetype = "city", is_city = true, wp = "Stockholm"},
["赫爾辛基"] = {container = "芬蘭", placetype = "city", is_city = true, wp = "Helsinki"},
["奧斯陸"] = {container = "挪威", placetype = "city", is_city = true, wp = "Oslo"},
["哥本哈根"] = {container = "丹麥", placetype = "city", is_city = true, wp = "Copenhagen"},
["里斯本"] = {container = "葡萄牙", placetype = "city", is_city = true, wp = "Lisbon"},
["雅典"] = {container = "希臘", placetype = "city", is_city = true, wp = "Athens"},
["蘇黎世"] = {container = "瑞士", placetype = "city", is_city = true, wp = "Zurich"},
["日內瓦"] = {container = "瑞士", placetype = "city", is_city = true, wp = "Geneva"},
["第比利斯"] = {container = "格魯吉亞", placetype = "city", is_city = true, wp = "Tbilisi"},
["貝爾格勒"] = {container = "塞爾維亞", placetype = "city", is_city = true, wp = "Belgrade"},
["薩格勒布"] = {container = "克羅埃西亞", placetype = "city", is_city = true, wp = "Zagreb"},
["都柏林"] = {container = "愛爾蘭", placetype = "city", is_city = true, wp = "Dublin"},
-- Asia
["德里市"] = {container = {key = "德里", placetype = "union territory"}, placetype = "city", is_city = true, wp = "Delhi"},
["曼谷"] = {container = "泰國", placetype = "city", is_city = true, wp = "Bangkok"},
["吉隆坡"] = {container = "馬來西亞", placetype = "city", is_city = true, wp = "Kuala Lumpur"},
["開羅"] = {container = "埃及", placetype = "city", is_city = true, wp = "Cairo"},
["德黑蘭"] = {container = "伊朗", placetype = "city", is_city = true, wp = "Tehran"},
["安卡拉"] = {container = "土耳其", placetype = "city", is_city = true, wp = "Ankara"},
-- Oceania
["奧克蘭"] = {container = "紐西蘭", placetype = "city", is_city = true, wp = "Auckland"},
-- Africa
["開普敦"] = {container = "南非", placetype = "city", is_city = true, wp = "Cape Town"},
["約翰尼斯堡"] = {container = "南非", placetype = "city", is_city = true, wp = "Johannesburg"},
["奈洛比"] = {container = "肯亞", placetype = "city", is_city = true, wp = "Nairobi"},
}
export.misc_cities_group = {
canonicalize_key_container = make_canonicalize_key_container(nil, "country"),
placename_to_key = false,
default_placetype = "city",
default_is_city = true,
data = export.misc_cities,
}
export.netherlands_provinces = {
["德倫特省"] = {wp = "Drenthe"},
["弗萊福蘭省"] = {wp = "Flevoland"},
["弗里斯蘭省"] = {wp = "Friesland"},
["格爾德蘭省"] = {wp = "Gelderland"},
["格羅寧根省"] = {wp = "Groningen (province)"},
["林堡省(荷蘭)"] = {wp = "Limburg (Netherlands)"},
["北布拉班特省"] = {wp = "North Brabant"},
["北荷蘭省"] = {wp = "North Holland"},
["上艾瑟爾省"] = {wp = "Overijssel"},
["南荷蘭省"] = {wp = "South Holland"},
["烏特勒支省"] = {wp = "Utrecht (province)"},
["澤蘭省"] = {wp = "Zeeland"},
}
export.netherlands_group = {
placename_to_key = false,
default_container = "荷蘭",
default_placetype = "province",
data = export.netherlands_provinces,
}
export.new_zealand_regions = {
["北地大區"] = {wp = "Northland Region"},
["奧克蘭大區"] = {wp = "Auckland Region"},
["懷卡托大區"] = {wp = "Waikato Region"},
["豐盛灣大區"] = {wp = "Bay of Plenty Region"},
["吉斯伯恩大區"] = {wp = "Gisborne District", placetype = {"region", "district"}},
["霍克斯灣大區"] = {wp = "Hawke's Bay Region"},
["塔拉納基大區"] = {wp = "Taranaki Region"},
["馬納瓦圖-旺阿努伊大區"] = {wp = "Manawatū-Whanganui Region"},
["惠靈頓大區"] = {wp = "Wellington Region"},
["塔斯曼大區"] = {wp = "Tasman District", placetype = {"region", "district"}},
["納爾遜大區"] = {wp = "Nelson, New Zealand", placetype = {"region", "city"}, is_city = true},
["馬爾堡大區"] = {wp = "Marlborough District", placetype = {"region", "district"}},
["西海岸大區"] = {wp = "West Coast Region"},
["坎特伯雷大區"] = {wp = "Canterbury Region"},
["奧塔哥大區"] = {wp = "Otago Region"},
["南地大區"] = {wp = "Southland Region"},
}
export.new_zealand_group = {
placename_to_key = false,
default_container = "紐西蘭",
default_placetype = "region",
data = export.new_zealand_regions,
}
export.norwegian_counties = {
["奧斯陸縣"] = {wp = "Oslo"},
["羅加蘭縣"] = {wp = "Rogaland"},
["莫雷-羅姆斯達爾縣"] = {wp = "Møre og Romsdal"},
["諾爾蘭縣"] = {wp = "Nordland"},
["東福爾縣"] = {wp = "Østfold"},
["阿克斯胡斯縣"] = {wp = "Akershus"},
["布斯克呂縣"] = {wp = "Buskerud"},
["因蘭德縣"] = {wp = "Innlandet"},
["西福爾縣"] = {wp = "Vestfold"},
["泰勒馬克縣"] = {wp = "Telemark"},
["阿格德爾縣"] = {wp = "Agder"},
["西地縣"] = {wp = "Vestland"},
["特倫德拉格縣"] = {wp = "Trøndelag"},
["特羅姆斯縣"] = {wp = "Troms"},
["芬馬克縣"] = {wp = "Finnmark"},
}
export.norway_group = {
placename_to_key = false,
default_container = "挪威",
default_placetype = "county",
data = export.norwegian_counties,
}
export.pakistan_provinces_and_territories = {
["自由克什米爾"] = {wp = "Azad Kashmir", placetype = {"administrative territory", "autonomous territory", "territory"}},
["俾路支省"] = {wp = "Balochistan, Pakistan"},
["吉爾吉特-巴爾蒂斯坦"] = {wp = "Gilgit-Baltistan", placetype = {"administrative territory", "territory"}},
["伊斯蘭馬巴德首都直轄區"] = {wp = "Islamabad Capital Territory", placetype = {"federal territory", "administrative territory", "territory"}},
["開伯爾-普赫圖赫瓦省"] = {wp = "Khyber Pakhtunkhwa"},
["旁遮普省(巴基斯坦)"] = {wp = "Punjab, Pakistan"},
["信德省"] = {wp = "Sindh"},
}
export.pakistan_group = {
placename_to_key = false,
default_container = "巴基斯坦",
default_placetype = "province",
data = export.pakistan_provinces_and_territories,
}
export.philippines_provinces = {
["阿布拉省"] = {wp = "Abra (province)"},
["北阿古桑省"] = {wp = "Agusan del Norte"},
["南阿古桑省"] = {wp = "Agusan del Sur"},
["阿克蘭省"] = {wp = "Aklan"},
["阿爾拜省"] = {wp = "Albay"},
["安蒂克省"] = {wp = "Antique (province)"},
["阿帕亞沃省"] = {wp = "Apayao"},
["奧羅拉省"] = {wp = "Aurora (province)"},
["巴西蘭省"] = {wp = "Basilan"},
["巴丹省"] = {wp = "Bataan"},
["巴達內斯省"] = {wp = "Batanes"},
["八打雁省"] = {wp = "Batangas"},
["本格特省"] = {wp = "Benguet"},
["比利蘭省"] = {wp = "Biliran"},
["保和省"] = {wp = "Bohol"},
["布基農省"] = {wp = "Bukidnon"},
["布拉干省"] = {wp = "Bulacan"},
["卡加延省"] = {wp = "Cagayan"},
["北甲馬仁省"] = {wp = "Camarines Norte"},
["南甲馬仁省"] = {wp = "Camarines Sur"},
["卡米金省"] = {wp = "Camiguin"},
["卡皮斯省"] = {wp = "Capiz"},
["卡坦端內斯省"] = {wp = "Catanduanes"},
["甲米地省"] = {wp = "Cavite"},
["宿霧省"] = {wp = "Cebu"},
["哥打巴托省"] = {wp = "Cotabato"},
["達沃德奧羅省"] = {wp = "Davao de Oro"},
["北達沃省"] = {wp = "Davao del Norte"},
["南達沃省"] = {wp = "Davao del Sur"},
["西達沃省"] = {wp = "Davao Occidental"},
["東達沃省"] = {wp = "Davao Oriental"},
["迪納加特群島省"] = {wp = "Dinagat Islands"},
["東薩馬省"] = {wp = "Eastern Samar"},
["吉馬拉斯省"] = {wp = "Guimaras"},
["伊富高省"] = {wp = "Ifugao"},
["北伊羅科斯省"] = {wp = "Ilocos Norte"},
["南伊羅科斯省"] = {wp = "Ilocos Sur"},
["伊洛伊洛省"] = {wp = "Iloilo"},
["伊莎貝拉省"] = {wp = "Isabela (province)"},
["卡林加省"] = {wp = "Kalinga (province)"},
["拉烏尼翁省"] = {wp = "La Union"},
["拉古納省"] = {wp = "Laguna (province)"},
["北拉瑙省"] = {wp = "Lanao del Norte"},
["南拉瑙省"] = {wp = "Lanao del Sur"},
["萊特省"] = {wp = "Leyte (province)"},
["北馬京達瑙省"] = {wp = "Maguindanao del Norte"},
["南馬京達瑙省"] = {wp = "Maguindanao del Sur"},
["馬林杜克省"] = {wp = "Marinduque"},
["馬斯巴特省"] = {wp = "Masbate"},
["西米薩米斯省"] = {wp = "Misamis Occidental"},
["東米薩米斯省"] = {wp = "Misamis Oriental"},
["山岳省"] = {wp = "Mountain Province"},
["西內格羅斯省"] = {wp = "Negros Occidental"},
["東內格羅斯省"] = {wp = "Negros Oriental"},
["北薩馬省"] = {wp = "Northern Samar"},
["新怡詩夏省"] = {wp = "Nueva Ecija"},
["新比斯開省"] = {wp = "Nueva Vizcaya"},
["西民都洛省"] = {wp = "Occidental Mindoro"},
["東民都洛省"] = {wp = "Oriental Mindoro"},
["巴拉望省"] = {wp = "Palawan"},
["邦板牙省"] = {wp = "Pampanga"},
["邦阿西楠省"] = {wp = "Pangasinan"},
["奎松省"] = {wp = "Quezon"},
["基里諾省"] = {wp = "Quirino"},
["黎剎省"] = {wp = "Rizal (province)"},
["朗布隆省"] = {wp = "Romblon"},
["薩馬省"] = {wp = "Samar (province)"},
["薩蘭加尼省"] = {wp = "Sarangani"},
["錫基霍爾省"] = {wp = "Siquijor"},
["索索貢省"] = {wp = "Sorsogon"},
["南哥打巴托省"] = {wp = "South Cotabato"},
["南萊特省"] = {wp = "Southern Leyte"},
["蘇丹庫達拉省"] = {wp = "Sultan Kudarat"},
["蘇祿省"] = {wp = "Sulu"},
["北蘇里高省"] = {wp = "Surigao del Norte"},
["南蘇里高省"] = {wp = "Surigao del Sur"},
["塔拉克省"] = {wp = "Tarlac"},
["塔威塔威省"] = {wp = "Tawi-Tawi"},
["三描禮士省"] = {wp = "Zambales"},
["北三寶顏省"] = {wp = "Zamboanga del Norte"},
["南三寶顏省"] = {wp = "Zamboanga del Sur"},
["三寶顏錫布蓋省"] = {wp = "Zamboanga Sibugay"},
["馬尼拉都會區"] = {wp = "Metro Manila", placetype = {"region", "province"}},
}
export.philippines_group = {
placename_to_key = false,
default_container = "菲律賓",
default_placetype = "province",
data = export.philippines_provinces,
}
export.poland_voivodeships = {
["下西里西亞省"] = {wp = "Lower Silesian Voivodeship"},
["庫亞維-波美拉尼亞省"] = {wp = "Kuyavian-Pomeranian Voivodeship"},
["盧布林省"] = {wp = "Lublin Voivodeship"},
["盧布斯省"] = {wp = "Lubusz Voivodeship"},
["羅茲省"] = {wp = "Łódź Voivodeship"},
["小波蘭省"] = {wp = "Lesser Poland Voivodeship"},
["馬佐夫舍省"] = {wp = "Masovian Voivodeship"},
["奧波萊省"] = {wp = "Opole Voivodeship"},
["喀爾巴阡山省"] = {wp = "Subcarpathian Voivodeship"},
["波德拉謝省"] = {wp = "Podlaskie Voivodeship"},
["波美拉尼亞省"] = {wp = "Pomeranian Voivodeship"},
["西里西亞省"] = {wp = "Silesian Voivodeship"},
["神聖十字省"] = {wp = "Świętokrzyskie Voivodeship"},
["瓦爾米亞-馬祖里省"] = {wp = "Warmian-Masurian Voivodeship"},
["大波蘭省"] = {wp = "Greater Poland Voivodeship"},
["西波美拉尼亞省"] = {wp = "West Pomeranian Voivodeship"},
}
export.poland_group = {
placename_to_key = false,
default_container = "波蘭",
default_placetype = "voivodeship",
data = export.poland_voivodeships,
}
export.portugal_districts_and_autonomous_regions = {
["亞速爾群島"] = {wp = "Azores", placetype = {"autonomous region", "region"}},
["阿維羅區"] = {wp = "Aveiro District"},
["貝雅區"] = {wp = "Beja District"},
["布拉加區"] = {wp = "Braga District"},
["布拉干薩區"] = {wp = "Bragança District"},
["卡斯特洛布蘭科區"] = {wp = "Castelo Branco District"},
["科英布拉區"] = {wp = "Coimbra District"},
["埃武拉區"] = {wp = "Évora District"},
["法魯區"] = {wp = "Faro District"},
["瓜達區"] = {wp = "Guarda District"},
["萊里亞區"] = {wp = "Leiria District"},
["里斯本區"] = {wp = "Lisbon District"},
["馬德拉島"] = {wp = "Madeira", placetype = {"autonomous region", "region"}},
["波塔萊格里區"] = {wp = "Portalegre District"},
["波爾圖區"] = {wp = "Porto District"},
["聖塔倫區"] = {wp = "Santarém District"},
["塞圖巴爾區"] = {wp = "Setúbal District"},
["維亞納堡區"] = {wp = "Viana do Castelo District"},
["維拉雷亞爾區"] = {wp = "Vila Real District"},
["維塞烏區"] = {wp = "Viseu District"},
}
export.portugal_group = {
placename_to_key = false,
default_container = "葡萄牙",
default_placetype = "district",
data = export.portugal_districts_and_autonomous_regions,
}
local russia_oblast = {placetype = "oblast"}
local russia_krai = {placetype = "krai"}
local russia_republic = {placetype = "republic"}
local russia_autonomous_okrug = {placetype = {"autonomous okrug", "okrug"}}
export.russia_federal_subjects = {
-- autonomous oblast
["猶太自治州"] = {placetype = {"autonomous oblast", "oblast"}, wp = "Jewish Autonomous Oblast"},
-- autonomous okrugs
["楚科奇自治區"] = {wp = "Chukotka Autonomous Okrug", placetype = {"autonomous okrug", "okrug"}},
["漢特-曼西自治區"] = {wp = "Khanty-Mansi Autonomous Okrug", placetype = {"autonomous okrug", "okrug"}},
["漢特-曼西亞"] = {alias_of = "漢特-曼西自治區"},
["涅涅茨自治區"] = {wp = "Nenets Autonomous Okrug", placetype = {"autonomous okrug", "okrug"}},
["亞馬洛-涅涅茨自治區"] = {wp = "Yamalo-Nenets Autonomous Okrug", placetype = {"autonomous okrug", "okrug"}},
-- krais
["阿爾泰邊疆區"] = {wp = "Altai Krai", placetype = "krai"},
["堪察加邊疆區"] = {wp = "Kamchatka Krai", placetype = "krai"},
["哈巴羅夫斯克邊疆區"] = {wp = "Khabarovsk Krai", placetype = "krai"},
["克拉斯諾達爾邊疆區"] = {wp = "Krasnodar Krai", placetype = "krai"},
["克拉斯諾亞爾斯克邊疆區"] = {wp = "Krasnoyarsk Krai", placetype = "krai"},
["彼爾姆邊疆區"] = {wp = "Perm Krai", placetype = "krai"},
["濱海邊疆區"] = {wp = "Primorsky Krai", placetype = "krai"},
["斯塔夫羅波爾邊疆區"] = {wp = "Stavropol Krai", placetype = "krai"},
["外貝加爾邊疆區"] = {wp = "Zabaykalsky Krai", placetype = "krai"},
-- oblasts
["阿穆爾州"] = russia_oblast,
["阿爾漢格爾斯克州"] = russia_oblast,
["阿斯特拉罕州"] = russia_oblast,
["別爾哥羅德州"] = russia_oblast,
["布良斯克州"] = russia_oblast,
["車裡雅賓斯克州"] = russia_oblast,
["伊爾庫茨克州"] = russia_oblast,
["伊萬諾沃州"] = russia_oblast,
["加里寧格勒州"] = russia_oblast,
["卡盧加州"] = russia_oblast,
["克麥羅沃州"] = russia_oblast,
["基洛夫州"] = russia_oblast,
["科斯特羅馬州"] = russia_oblast,
["庫爾干州"] = russia_oblast,
["庫爾斯克州"] = russia_oblast,
["列寧格勒州"] = russia_oblast,
["利佩茨克州"] = russia_oblast,
["馬加丹州"] = russia_oblast,
["莫斯科州"] = russia_oblast,
["摩爾曼斯克州"] = russia_oblast,
["下諾夫哥羅德州"] = russia_oblast,
["諾夫哥羅德州"] = russia_oblast,
["新西伯利亞州"] = russia_oblast,
["鄂木斯克州"] = russia_oblast,
["奧倫堡州"] = russia_oblast,
["奧廖爾州"] = russia_oblast,
["奔薩州"] = russia_oblast,
["普斯科夫州"] = russia_oblast,
["羅斯托夫州"] = russia_oblast,
["梁贊州"] = russia_oblast,
["薩哈林州"] = {wp = "Sakhalin Oblast", placetype = "oblast"},
["薩馬拉州"] = russia_oblast,
["薩拉托夫州"] = russia_oblast,
["斯摩棱斯克州"] = russia_oblast,
["斯維爾德洛夫斯克州"] = russia_oblast,
["坦波夫州"] = russia_oblast,
["托木斯克州"] = russia_oblast,
["圖拉州"] = russia_oblast,
["特維爾州"] = russia_oblast,
["秋明州"] = russia_oblast,
["烏里揚諾夫斯克州"] = russia_oblast,
["弗拉基米爾州"] = russia_oblast,
["伏爾加格勒州"] = russia_oblast,
["沃洛格達州"] = russia_oblast,
["沃羅涅日州"] = russia_oblast,
["雅羅斯拉夫爾州"] = russia_oblast,
-- republics
["阿迪格共和國"] = russia_republic,
["巴什科爾托斯坦共和國"] = russia_republic,
["布里亞特共和國"] = russia_republic,
["達吉斯坦共和國"] = russia_republic,
["印古什共和國"] = russia_republic,
["卡爾梅克共和國"] = russia_republic,
["卡累利亞共和國"] = {wp = "Republic of Karelia", placetype = "republic"},
["哈卡斯共和國"] = russia_republic,
["莫爾多瓦共和國"] = russia_republic,
["北奧塞梯-阿蘭尼亞共和國"] = {wp = "North Ossetia–Alania", placetype = "republic"},
["北奧塞梯"] = {alias_of = "北奧塞梯-阿蘭尼亞共和國"},
["韃靼斯坦共和國"] = russia_republic,
["阿爾泰共和國"] = {wp = "Altai Republic", placetype = "republic"},
["車臣共和國"] = russia_republic,
["楚瓦什共和國"] = russia_republic,
["卡巴爾達-巴爾卡爾共和國"] = russia_republic,
["卡拉恰伊-切爾克斯共和國"] = russia_republic,
["科米共和國"] = {wp = "Komi Republic", placetype = "republic"},
["馬里埃爾共和國"] = russia_republic,
["薩哈共和國"] = {wp = "Sakha Republic", placetype = "republic"},
["雅庫特"] = {alias_of = "薩哈共和國"},
["圖瓦共和國"] = russia_republic,
["烏德穆爾特共和國"] = russia_republic,
}
export.russia_group = {
placename_to_key = false,
default_container = "俄羅斯",
default_placetype = "oblast",
data = export.russia_federal_subjects,
}
export.saudi_arabia_provinces = {
["利雅得省"] = {wp = "Riyadh Province"},
["麥加省"] = {wp = "Mecca Province"},
["東部省(沙特)"] = {wp = "Eastern Province, Saudi Arabia", no_auto_augment_container = true},
["麥地那省"] = {wp = "Medina Province"},
["阿西爾省"] = {wp = "Asir"},
["吉贊省"] = {wp = "Jazan Province"},
["卡西姆省"] = {wp = "Al-Qassim Province"},
["塔布克省"] = {wp = "Tabuk Province"},
["哈伊爾省"] = {wp = "Ha'il Province"},
["焦夫省"] = {wp = "Al-Jawf Province"},
["納季蘭省"] = {wp = "Najran Province"},
["北部邊界省"] = {wp = "Northern Borders Province"},
["巴哈省"] = {wp = "Al-Bahah Province"},
}
export.saudi_arabia_group = {
placename_to_key = false,
default_container = "沙烏地阿拉伯",
default_placetype = "province",
data = export.saudi_arabia_provinces,
}
export.south_africa_provinces = {
["東開普省"] = {wp = "Eastern Cape"},
["自由邦省"] = {wp = "Free State (South African province)"},
["豪登省"] = {wp = "Gauteng"},
["夸祖魯-納塔爾省"] = {wp = "KwaZulu-Natal"},
["林波波省"] = {wp = "Limpopo"},
["姆普馬蘭加省"] = {wp = "Mpumalanga"},
["西北省(南非)"] = {wp = "North West (South African province)"},
["北開普省"] = {wp = "Northern Cape"},
["西開普省"] = {wp = "Western Cape"},
}
export.south_africa_group = {
placename_to_key = false,
default_container = "南非",
default_placetype = "province",
data = export.south_africa_provinces,
}
export.ukraine_oblasts = {
["切爾卡瑟州"] = {wp = "Cherkasy Oblast"},
["切爾尼戈夫州"] = {wp = "Chernihiv Oblast"},
["切爾諾夫策州"] = {wp = "Chernivtsi Oblast"},
["第聶伯羅彼得羅夫斯克州"] = {wp = "Dnipropetrovsk Oblast"},
["頓涅茨克州"] = {wp = "Donetsk Oblast"},
["伊萬諾-弗蘭科夫斯克州"] = {wp = "Ivano-Frankivsk Oblast"},
["哈爾科夫州"] = {wp = "Kharkiv Oblast"},
["赫爾松州"] = {wp = "Kherson Oblast"},
["赫梅利尼茨基州"] = {wp = "Khmelnytskyi Oblast"},
["基洛夫格勒州"] = {wp = "Kirovohrad Oblast"},
["基輔州"] = {wp = "Kyiv Oblast"},
["盧甘斯克州"] = {wp = "Luhansk Oblast"},
["利沃夫州"] = {wp = "Lviv Oblast"},
["尼古拉耶夫州"] = {wp = "Mykolaiv Oblast"},
["敖德薩州"] = {wp = "Odesa Oblast"},
["波爾塔瓦州"] = {wp = "Poltava Oblast"},
["羅夫諾州"] = {wp = "Rivne Oblast"},
["蘇梅州"] = {wp = "Sumy Oblast"},
["捷爾諾波爾州"] = {wp = "Ternopil Oblast"},
["文尼察州"] = {wp = "Vinnytsia Oblast"},
["沃里尼亞州"] = {wp = "Volyn Oblast"},
["外喀爾巴阡州"] = {wp = "Zakarpattia Oblast"},
["扎波羅熱州"] = {wp = "Zaporizhzhia Oblast"},
["日托米爾州"] = {wp = "Zhytomyr Oblast"},
}
export.ukraine_group = {
placename_to_key = false,
default_container = "烏克蘭",
default_placetype = "oblast",
data = export.ukraine_oblasts,
}
export.wales_principal_areas = {
["布拉艾諾格溫特"] = {wp = "Blaenau Gwent"},
["布里真德"] = {wp = "Bridgend County Borough"},
["卡菲利"] = {wp = "Caerphilly County Borough"},
["卡馬森郡"] = {wp = "Carmarthenshire", placetype = "county"},
["克雷地基昂"] = {wp = "Ceredigion", placetype = "county"},
["科溫"] = {wp = "Conwy County Borough"},
["登比郡"] = {wp = "Denbighshire", placetype = "county"},
["弗林特郡"] = {wp = "Flintshire", placetype = "county"},
["格溫內德"] = {wp = "Gwynedd", placetype = "county"},
["安格爾西島"] = {wp = "Isle of Anglesey", placetype = "county"},
["默瑟蒂德菲爾"] = {wp = "Merthyr Tydfil County Borough"},
["蒙茅斯郡"] = {wp = "Monmouthshire", placetype = "county"},
["尼斯-波特塔爾博特"] = {wp = "Neath Port Talbot"},
["彭布羅克郡"] = {wp = "Pembrokeshire", placetype = "county"},
["波伊斯"] = {wp = "Powys", placetype = "county"},
["朗達坎農塔夫"] = {wp = "Rhondda Cynon Taf"},
["托爾范"] = {wp = "Torfaen"},
["格拉摩根谷"] = {wp = "Vale of Glamorgan"},
["雷克瑟姆"] = {wp = "Wrexham County Borough"},
}
export.wales_group = {
placename_to_key = false,
default_container = {key = "威爾士", placetype = "constituent country"},
default_placetype = "county borough",
data = export.wales_principal_areas,
}
-----------------------------------------------------------------------------------
-- City groups --
-----------------------------------------------------------------------------------
export.australia_cities = {
["阿德萊德"] = {container = "南澳大利亞", wp = "Adelaide"},
["布里斯班"] = {container = "昆士蘭", wp = "Brisbane"},
["坎培拉"] = {container = "澳大利亞首都領地", wp = "Canberra"},
["墨爾本"] = {container = "維多利亞州", wp = "Melbourne"},
["紐卡素"] = {container = "新南威爾士", wp = "Newcastle, New South Wales"},
["伯斯"] = {container = "西澳大利亞", wp = "Perth"},
["悉尼"] = {container = "新南威爾士", wp = "Sydney"},
}
export.australia_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "state"),
default_container = "澳大利亞",
default_placetype = "city",
default_is_city = true,
data = export.australia_cities,
}
export.brazil_cities = {
["聖保羅"] = {container = "聖保羅州", wp = "São Paulo"},
["里約熱內盧"] = {container = "里約熱內盧州", wp = "Rio de Janeiro"},
["貝洛奧里藏特"] = {container = "米納斯吉拉斯", wp = "Belo Horizonte"},
["累西腓"] = {container = "伯南布哥州", wp = "Recife"},
["阿雷格里港"] = {container = "南里奧格蘭德州", wp = "Porto Alegre"},
["巴西利亞"] = {container = "巴西聯邦區", wp = "Brasília"},
["福塔萊薩"] = {container = "塞阿拉州", wp = "Fortaleza"},
["薩爾瓦多"] = {container = "巴伊亞", wp = "Salvador, Bahia"},
["庫里蒂巴"] = {container = "巴拉那州", wp = "Curitiba"},
["坎皮納斯"] = {container = "聖保羅州", wp = "Campinas"},
["戈亞尼亞"] = {container = "戈亞斯州", wp = "Goiânia"},
["瑪瑙斯"] = {container = "亞馬遜州", wp = "Manaus"},
["伯利恆"] = {container = "帕拉州", wp = "Belém, Pará"},
["維多利亞"] = {container = "聖埃斯皮里圖州", wp = "Vitória, Espírito Santo"},
["聖路易斯"] = {container = "馬拉尼昂州", wp = "São Luís"},
["納塔爾"] = {container = "北里奧格蘭德州", wp = "Natal, Rio Grande do Norte"},
["弗洛里亞諾波利斯"] = {container = "聖卡塔琳娜州", wp = "Florianópolis"},
["馬塞約"] = {container = "阿拉戈亞斯州", wp = "Maceió"},
["若昂佩索阿"] = {container = "帕拉伊巴州", wp = "João Pessoa"},
["特雷齊納"] = {container = "皮奧伊州", wp = "Teresina"},
["隆德里納"] = {container = "巴拉那州", wp = "Londrina"},
}
export.brazil_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "state"),
default_container = "巴西",
default_placetype = "city",
default_is_city = true,
data = export.brazil_cities,
}
export.canada_cities = {
["多倫多"] = {container = "安大略", wp = "Toronto"},
["蒙特婁"] = {container = "魁北克", wp = "Montreal"},
["溫哥華"] = {container = "英屬哥倫比亞", wp = "Vancouver"},
["卡加里"] = {container = "亞伯達", wp = "Calgary"},
["愛民頓"] = {container = "亞伯達", wp = "Edmonton"},
["渥太華"] = {container = "安大略", wp = "Ottawa"},
["魁北克城"] = {container = "魁北克", wp = "Quebec City"},
["溫尼伯"] = {container = "馬尼托巴", wp = "Winnipeg"},
["漢密爾頓"] = {container = "安大略", wp = "Hamilton, Ontario"},
}
export.canada_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_container = "加拿大",
default_placetype = "city",
default_is_city = true,
data = export.canada_cities,
}
export.france_cities = {
["巴黎"] = {container = "法蘭西島大區", wp = "Paris"},
["里昂"] = {container = "奧弗涅-隆河-阿爾卑斯大區", wp = "Lyon"},
["馬賽"] = {container = "普羅旺斯-阿爾卑斯-蔚藍海岸大區", wp = "Marseille"},
["里爾"] = {container = "上法蘭西大區", wp = "Lille"},
["波爾多"] = {container = "新阿基坦大區", wp = "Bordeaux"},
["圖盧茲"] = {container = "奧克西塔尼大區", wp = "Toulouse"},
["尼斯"] = {container = "普羅旺斯-阿爾卑斯-蔚藍海岸大區", wp = "Nice"},
["南特"] = {container = "盧瓦爾河地區大區", wp = "Nantes"},
["史特拉斯堡"] = {container = "大東部大區", wp = "Strasbourg"},
["雷恩"] = {container = "布列塔尼大區", wp = "Rennes"},
}
export.france_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "region"),
default_container = "法國",
default_placetype = "city",
default_is_city = true,
data = export.france_cities,
}
export.germany_cities = {
["科隆"] = {container = "北萊茵-威斯特法倫", wp = "Cologne"},
["杜塞爾多夫"] = {container = "北萊茵-威斯特法倫", wp = "Düsseldorf"},
["多特蒙德"] = {container = "北萊茵-威斯特法倫", wp = "Dortmund"},
["埃森"] = {container = "北萊茵-威斯特法倫", wp = "Essen"},
["柏林"] = {wp = "Berlin"},
["法蘭克福"] = {container = "黑森", wp = "Frankfurt"},
["漢堡"] = {wp = "Hamburg"},
["慕尼黑"] = {container = "巴伐利亞", wp = "Munich"},
["斯圖加特"] = {container = "巴登-符騰堡", wp = "Stuttgart"},
["曼海姆"] = {container = "巴登-符騰堡", wp = "Mannheim"},
["紐倫堡"] = {container = "巴伐利亞", wp = "Nuremberg"},
["漢諾威"] = {container = "下薩克森", wp = "Hanover"},
["比勒費爾德"] = {container = "北萊茵-威斯特法倫", wp = "Bielefeld"},
["萊比錫"] = {container = "薩克森", wp = "Leipzig"},
["亞琛"] = {container = "北萊茵-威斯特法倫", wp = "Aachen"},
["不來梅"] = {wp = "Bremen"},
}
export.germany_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "state"),
default_container = "德國",
default_placetype = "city",
default_is_city = true,
data = export.germany_cities,
}
export.india_cities = {
["德里"] = {container = {key = "德里", placetype = "union territory"}, wp = "Delhi"},
["孟買"] = {container = "馬哈拉施特拉邦", wp = "Mumbai"},
["加爾各答"] = {container = "西孟加拉邦", wp = "Kolkata"},
["班加羅爾"] = {container = "卡納塔克邦", wp = "Bengaluru"},
["金奈"] = {container = "泰米爾納德邦", wp = "Chennai"},
["海德拉巴"] = {container = "特倫甘納邦", wp = "Hyderabad"},
["艾哈邁達巴德"] = {container = "古吉拉特邦", wp = "Ahmedabad"},
["浦那"] = {container = "馬哈拉施特拉邦", wp = "Pune"},
["蘇拉特"] = {container = "古吉拉特邦", wp = "Surat"},
["勒克瑙"] = {container = "北方邦", wp = "Lucknow"},
["齋浦爾"] = {container = "拉賈斯坦邦", wp = "Jaipur"},
["坎普爾"] = {container = "北方邦", wp = "Kanpur"},
["那格浦爾"] = {container = "馬哈拉施特拉邦", wp = "Nagpur"},
["巴特那"] = {container = "比哈爾邦", wp = "Patna"},
["瓦拉納西"] = {container = "北方邦", wp = "Varanasi"},
["印多爾"] = {container = "中央邦", wp = "Indore"},
["博帕爾"] = {container = "中央邦", wp = "Bhopal"},
["阿格拉"] = {container = "北方邦", wp = "Agra"},
["科欽"] = {container = "喀拉拉邦", wp = "Kochi"},
["維沙卡帕特南"] = {container = "安得拉邦", wp = "Visakhapatnam"},
["齋普爾"] = {alias_of = "齋浦爾"},
}
export.india_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "state"),
default_container = "印度",
default_placetype = "city",
default_is_city = true,
data = export.india_cities,
}
export.indonesia_cities = {
["雅加達"] = {container = "雅加達首都特區", wp = "Jakarta"},
["泗水"] = {container = "東爪哇省", wp = "Surabaya"},
["勿加泗"] = {container = "西爪哇省", wp = "Bekasi"},
["萬隆"] = {container = "西爪哇省", wp = "Bandung"},
["棉蘭"] = {container = "北蘇門答臘省", wp = "Medan"},
["德波"] = {container = "西爪哇省", wp = "Depok"},
["萬丹市"] = {container = "萬丹省", wp = "Tangerang"},
["巨港"] = {container = "南蘇門答臘省", wp = "Palembang"},
["三寶壟"] = {container = "中爪哇省", wp = "Semarang"},
["望加錫"] = {container = "南蘇拉威西省", wp = "Makassar"},
["武端"] = {container = "廖內群島省", wp = "Batam"},
["茂物"] = {container = "西爪哇省", wp = "Bogor"},
["北干巴魯"] = {container = "廖內省", wp = "Pekanbaru"},
["班達爾楠榜"] = {container = "楠榜省", wp = "Bandar Lampung"},
["巴東"] = {container = "西蘇門答臘省", wp = "Padang"},
["薩馬林達"] = {container = "東加里曼丹省", wp = "Samarinda"},
["瑪琅"] = {container = "東爪哇省", wp = "Malang"},
["日惹"] = {container = "日惹特區", wp = "Yogyakarta"},
["登巴薩"] = {container = "峇里省", wp = "Denpasar"},
["哲勒貢"] = {container = "西爪哇省", wp = "Cirebon"},
["梭羅"] = {container = "中爪哇省", wp = "Surakarta"},
["馬辰"] = {container = "南加里曼丹省", wp = "Banjarmasin"},
["打橫"] = {container = "西爪哇省", wp = "Tasikmalaya"},
}
export.indonesia_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_container = "印度尼西亞",
default_placetype = "city",
default_is_city = true,
data = export.indonesia_cities,
}
export.italy_cities = {
["米蘭"] = {container = "倫巴第", wp = "Milan"},
["那不勒斯"] = {container = "坎帕尼亞", wp = "Naples"},
["羅馬"] = {container = "拉齊奧", wp = "Rome"},
["都靈"] = {container = "皮埃蒙特", wp = "Turin"},
["威尼斯"] = {container = "威尼托", wp = "Venice"},
["佛羅倫斯"] = {container = "托斯卡納", wp = "Florence"},
["巴里"] = {container = "普利亞", wp = "Bari"},
["巴勒莫"] = {container = "西西里", wp = "Palermo"},
["卡塔尼亞"] = {container = "西西里", wp = "Catania"},
["布雷西亞"] = {container = "倫巴第", wp = "Brescia"},
["熱那亞"] = {container = "利古里亞", wp = "Genoa"},
}
export.italy_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "region"),
default_container = "意大利",
default_placetype = "city",
default_is_city = true,
data = export.italy_cities,
}
export.japan_cities = {
["東京"] = {keydesc = "[[東京]]都會區,[[日本]]的[[首都]]和[[都]]級行政區",
placetype = {"city", "prefecture"},
divs = {
{type = "special wards", container_parent_type = false},
{type = "cities", prep = "in"},
},
},
["橫濱"] = {container = "神奈川縣", wp = "Yokohama"},
["大阪"] = {container = "大阪府", wp = "Osaka"},
["名古屋"] = {container = "愛知縣", wp = "Nagoya"},
["札幌"] = {container = "北海道", wp = "Sapporo"},
["福岡"] = {container = "福岡縣", wp = "Fukuoka"},
["神戶"] = {container = "兵庫縣", wp = "Kobe"},
["京都"] = {container = "京都府", wp = "Kyoto"},
["川崎"] = {container = "神奈川縣", wp = "Kawasaki, Kanagawa"},
["廣島"] = {container = "廣島縣", wp = "Hiroshima"},
["仙台"] = {container = "宮城縣", wp = "Sendai"},
["北九州"] = {container = "福岡縣", wp = "Kitakyushu"},
["新潟"] = {container = "新潟縣", wp = "Niigata (city)"},
["靜岡"] = {container = "靜岡縣", wp = "Shizuoka (city)"},
["岡山"] = {container = "岡山縣", wp = "Okayama"},
["熊本"] = {container = "熊本縣", wp = "Kumamoto"},
["鹿兒島"] = {container = "鹿兒島縣", wp = "Kagoshima"},
}
export.japan_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "prefecture"),
default_container = "日本",
default_placetype = "city",
default_is_city = true,
data = export.japan_cities,
}
export.mexico_cities = {
["墨西哥城"] = {wp = "墨西哥城"},
["蒙特雷"] = {container = "新萊昂州", wp = "Monterrey"},
["瓜達拉哈拉"] = {container = "哈利斯科州", wp = "Guadalajara"},
["普埃布拉市"] = {container = "普埃布拉州", wp = "Puebla (city)"},
["托盧卡"] = {container = "墨西哥州", wp = "Toluca"},
["蒂華納"] = {container = "下加利福尼亞州", wp = "Tijuana"},
["萊昂"] = {container = "瓜納華托州", wp = "León, Guanajuato"},
["克雷塔羅市"] = {container = "克雷塔羅州", wp = "Querétaro (city)"},
["華雷斯城"] = {container = "奇瓦瓦州", wp = "Ciudad Juárez"},
["托雷翁"] = {container = "科阿韋拉州", wp = "Torreón"},
["梅里達"] = {container = "尤卡坦州", wp = "Mérida, Yucatán"},
["聖路易斯波托西市"] = {container = "聖路易斯波托西州", wp = "San Luis Potosí (city)"},
["阿瓜斯卡連特斯市"] = {container = "阿瓜斯卡連特斯州", wp = "Aguascalientes (city)"},
["墨西卡利"] = {container = "下加利福尼亞州", wp = "Mexicali"},
}
export.mexico_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "state"),
default_container = "墨西哥",
default_placetype = "city",
default_is_city = true,
data = export.mexico_cities,
}
export.nigeria_cities = {
["拉各斯"] = {container = "拉各斯州", wp = "Lagos"},
["卡諾市"] = {container = "卡諾州", wp = "Kano (city)"},
["伊巴丹"] = {container = "奧約州", wp = "Ibadan"},
["阿布賈"] = {container = {key = "阿布賈聯邦首都轄區", placetype = "federal territory"}, wp = "Abuja"},
["哈科特港"] = {container = "河流州", wp = "Port Harcourt"},
["卡杜納市"] = {container = "卡杜納州", wp = "Kaduna (city)"},
["貝寧城"] = {container = "埃多州", wp = "Benin City"},
["阿巴"] = {container = "阿比亞州", wp = "Aba, Nigeria"},
["奧尼查"] = {container = "阿南布拉州", wp = "Onitsha"},
["邁杜古里"] = {container = "博爾諾州", wp = "Maiduguri"},
["伊洛林"] = {container = "誇拉州", wp = "Ilorin"},
["索科托市"] = {container = "索科托州", wp = "Sokoto (city)"},
["喬斯"] = {container = "高原州", wp = "Jos"},
["扎里亞"] = {container = "卡杜納州", wp = "Zaria"},
["埃努古市"] = {container = "埃努古州", wp = "Enugu (city)"},
}
export.nigeria_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "state"),
default_container = "奈及利亞",
default_placetype = "city",
default_is_city = true,
data = export.nigeria_cities,
}
export.pakistan_cities = {
["卡拉奇"] = {container = "信德省", wp = "Karachi"},
["拉合爾"] = {container = "旁遮普省(巴基斯坦)", wp = "Lahore"},
["拉瓦爾品第"] = {container = "旁遮普省(巴基斯坦)", wp = "Rawalpindi"},
["伊斯蘭馬巴德"] = {container = {key = "伊斯蘭馬巴德首都直轄區", placetype = "federal territory"}, wp = "Islamabad"},
["費薩拉巴德"] = {container = "旁遮普省(巴基斯坦)", wp = "Faisalabad"},
["古傑蘭瓦拉"] = {container = "旁遮普省(巴基斯坦)", wp = "Gujranwala"},
["海德拉巴(巴基斯坦)"] = {container = "信德省", wp = "Hyderabad, Pakistan"},
["木爾坦"] = {container = "旁遮普省(巴基斯坦)", wp = "Multan"},
["白沙瓦"] = {container = "開伯爾-普赫圖赫瓦省", wp = "Peshawar"},
["奎達"] = {container = "俾路支省", wp = "Quetta"},
["薩戈達"] = {container = "旁遮普省(巴基斯坦)", wp = "Sargodha"},
["錫亞爾科特"] = {container = "旁遮普省(巴基斯坦)", wp = "Sialkot"},
}
export.pakistan_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_container = "巴基斯坦",
default_placetype = "city",
default_is_city = true,
data = export.pakistan_cities,
}
export.philippines_cities = {
["奎松市"] = {container = {key = "馬尼拉都會區", placetype = "region"}, wp = "Quezon City"},
["馬尼拉"] = {container = {key = "馬尼拉都會區", placetype = "region"}, wp = "Manila"},
["達沃市"] = {container = "南達沃省", wp = "Davao City"},
["卡洛坎"] = {container = {key = "馬尼拉都會區", placetype = "region"}, wp = "Caloocan"},
["三寶顏市"] = {container = "南三寶顏省", wp = "Zamboanga City"},
["宿霧市"] = {container = "宿霧省", wp = "Cebu City"},
["安蒂波洛"] = {container = "黎剎省", wp = "Antipolo"},
["卡加延德奧羅"] = {container = "東米薩米斯省", wp = "Cagayan de Oro"},
["達斯馬里尼亞斯"] = {container = "甲米地省", wp = "Dasmariñas"},
["棉蘭老將軍城"] = {container = "南哥打巴托省", wp = "General Santos"},
["聖何塞德爾蒙特"] = {container = "布拉干省", wp = "San Jose del Monte"},
["巴科洛德"] = {container = "西內格羅斯省", wp = "Bacolod"},
["卡蘭巴"] = {container = "拉古納省", wp = "Calamba, Laguna"},
["天使城"] = {container = "邦板牙省", wp = "Angeles City"},
["伊洛伊洛市"] = {container = "伊洛伊洛省", wp = "Iloilo City"},
}
export.philippines_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_container = "菲律賓",
default_placetype = "city",
default_is_city = true,
data = export.philippines_cities,
}
export.russia_cities = {
["莫斯科"] = {wp = "Moscow"},
["聖彼得堡"] = {wp = "Saint Petersburg"},
["新西伯利亞"] = {container = "新西伯利亞州", wp = "Novosibirsk"},
["葉卡捷琳堡"] = {container = "斯維爾德洛夫斯克州", wp = "Yekaterinburg"},
["下諾夫哥羅德"] = {container = "下諾夫哥羅德州", wp = "Nizhny Novgorod"},
["喀山"] = {container = {key = "韃靼斯坦共和國", placetype = "republic"}, wp = "Kazan"},
["車裡雅賓斯克市"] = {container = "車裡雅賓斯克州", wp = "Chelyabinsk"},
["頓河畔羅斯托夫"] = {container = "羅斯托夫州", wp = "Rostov-on-Don"},
["克拉斯諾達爾市"] = {container = {key = "克拉斯諾達爾邊疆區", placetype = "krai"}, wp = "Krasnodar"},
["薩馬拉市"] = {container = "薩馬拉州", wp = "Samara"},
["克拉斯諾亞爾斯克市"] = {container = {key = "克拉斯諾亞爾斯克邊疆區", placetype = "krai"}, wp = "Krasnoyarsk"},
["烏法"] = {container = {key = "巴什科爾托斯坦共和國", placetype = "republic"}, wp = "Ufa"},
["薩拉托夫市"] = {container = "薩拉托夫州", wp = "Saratov"},
["鄂木斯克市"] = {container = "鄂木斯克州", wp = "Omsk"},
["沃羅涅日市"] = {container = "沃羅涅日州", wp = "Voronezh"},
["伏爾加格勒市"] = {container = "伏爾加格勒州", wp = "Volgograd"},
["彼爾姆市"] = {container = {key = "彼爾姆邊疆區", placetype = "krai"}, wp = "Perm, Russia"},
}
export.russia_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "oblast"),
default_container = "俄羅斯",
default_placetype = "city",
default_is_city = true,
data = export.russia_cities,
}
export.saudi_arabia_cities = {
["利雅得"] = {container = "利雅得省", wp = "Riyadh"},
["吉達"] = {container = "麥加省", wp = "Jeddah"},
["達曼"] = {container = "東部省(沙特)", wp = "Dammam"},
["麥加"] = {container = "麥加省", wp = "Mecca"},
["麥地那"] = {container = "麥地那省", wp = "Medina"},
["霍夫夫"] = {container = "東部省(沙特)", wp = "Hofuf"},
["哈米斯穆沙伊特"] = {container = "阿西爾省", wp = "Khamis Mushait"},
}
export.saudi_arabia_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_container = "沙烏地阿拉伯",
default_placetype = "city",
default_is_city = true,
data = export.saudi_arabia_cities,
}
export.south_korea_cities = {
["首爾"] = {wp = "Seoul"},
["釜山"] = {wp = "Busan"},
["仁川"] = {wp = "Incheon"},
["大邱"] = {wp = "Daegu"},
["大田"] = {wp = "Daejeon"},
["光州"] = {wp = "Gwangju"},
["蔚山"] = {wp = "Ulsan"},
}
export.south_korea_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_container = "南韓",
default_placetype = "city",
default_is_city = true,
data = export.south_korea_cities,
}
export.spain_cities = {
["馬德里"] = {container = "馬德里自治區", wp = "Madrid"},
["巴塞羅那"] = {container = "加泰羅尼亞", wp = "Barcelona"},
["瓦倫西亞"] = {container = "巴倫西亞", wp = "Valencia"},
["塞維利亞"] = {container = "安達盧西亞", wp = "Seville"},
["畢爾包"] = {container = "巴斯克", wp = "Bilbao"},
}
export.spain_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "autonomous community"),
default_container = "西班牙",
default_placetype = "city",
default_is_city = true,
data = export.spain_cities,
}
export.taiwan_cities = {
-- Special municipalities (province-level)
["臺北市"] = {placetype = "special municipality", wp = "臺北市"},
["新北市"] = {placetype = "special municipality", wp = "新北市"},
["桃園市"] = {placetype = "special municipality", wp = "桃園市"},
["臺中市"] = {placetype = "special municipality", wp = "臺中市"},
["臺南市"] = {placetype = "special municipality", wp = "臺南市"},
["高雄市"] = {placetype = "special municipality", wp = "高雄市"},
-- these last three are not special municipalities
["嘉義市"] = {placetype = "city", wp = "嘉義市"},
["新竹市"] = {placetype = "city", wp = "新竹市"},
["基隆市"] = {placetype = "city", wp = "基隆市"},
}
export.taiwan_cities_group = {
key_to_placename = make_key_to_placename(nil, "市$"),
placename_to_key = make_placename_to_key("市"),
default_container = "臺灣",
default_placetype = {"special municipality", "city"},
default_is_city = true,
data = export.taiwan_cities,
}
export.united_kingdom_cities = {
["倫敦"] = {container = {key = "大倫敦", placetype = "county"}, wp = "London"},
["曼徹斯特"] = {container = {key = "大曼徹斯特", placetype = "county"}, wp = "Manchester"},
["伯明翰"] = {container = {key = "西密德蘭", placetype = "county"}, wp = "Birmingham"},
["利物浦"] = {container = {key = "默西賽德郡", placetype = "county"}, wp = "Liverpool"},
["格拉斯哥"] = {container = {key = "Glasgow市", placetype = "council area"}, wp = "Glasgow"},
["利茲"] = {container = {key = "西約克郡", placetype = "county"}, wp = "Leeds"},
["布里斯托"] = {container = {key = "英格蘭", placetype = "constituent country"}, wp = "Bristol"},
["卡地夫"] = {container = {key = "威爾士", placetype = "constituent country"}, wp = "Cardiff"},
["愛丁堡"] = {container = {key = "Edinburgh市", placetype = "council area"}, wp = "Edinburgh"},
["斯旺西"] = {container = {key = "威爾士", placetype = "constituent country"}, wp = "Swansea"},
["紐波特"] = {container = {key = "威爾士", placetype = "constituent country"}, wp = "Newport, Wales"},
}
export.united_kingdom_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "county"),
default_container = "英國",
default_placetype = "city",
default_is_city = true,
data = export.united_kingdom_cities,
}
export.united_states_cities = {
["紐約市"] = {container = "紐約", wp = "New York City"},
["洛杉磯"] = {container = "加利福尼亞", wp = "Los Angeles"},
["芝加哥"] = {container = "伊利諾伊", wp = "Chicago"},
["華盛頓特區"] = {wp = "Washington, D.C."},
["巴爾的摩"] = {container = "馬里蘭", wp = "Baltimore"},
["聖荷西"] = {container = "加利福尼亞", wp = "San Jose, California"},
["舊金山"] = {container = "加利福尼亞", wp = "San Francisco"},
["波士頓"] = {container = "麻薩諸塞", wp = "Boston"},
["達拉斯"] = {container = "德克薩斯", wp = "Dallas"},
["費城"] = {container = "賓夕法尼亞", wp = "Philadelphia"},
["休士頓"] = {container = "德克薩斯", wp = "Houston"},
["邁阿密"] = {container = "佛羅里達", wp = "Miami"},
["亞特蘭大"] = {container = "喬治亞", wp = "Atlanta"},
["底特律"] = {container = "密西根", wp = "Detroit"},
["鳳凰城"] = {container = "亞利桑那", wp = "Phoenix"},
["西雅圖"] = {container = "華盛頓州", wp = "Seattle"},
["明尼阿波利斯"] = {container = "明尼蘇達", wp = "Minneapolis"},
["克里夫蘭"] = {container = "俄亥俄", wp = "Cleveland"},
["丹佛"] = {container = "科羅拉多", wp = "Denver"},
["聖地牙哥(美國)"] = {container = "加利福尼亞", wp = "San Diego"},
["波特蘭(俄勒岡)"] = {container = "俄勒岡", wp = "Portland"},
["聖路易斯"] = {container = "密蘇里", wp = "St. Louis"},
["夏洛特"] = {container = "北卡羅來納", wp = "Charlotte"},
["薩克拉門托"] = {container = "加利福尼亞", wp = "Sacramento"},
["匹茲堡"] = {container = "賓夕法尼亞", wp = "Pittsburgh"},
["鹽湖城"] = {container = "猶他", wp = "Salt Lake City"},
["聖安東尼奧"] = {container = "德克薩斯", wp = "San Antonio"},
["哥倫布"] = {container = "俄亥俄", wp = "Columbus"},
["堪薩斯城"] = {container = "密蘇里", wp = "Kansas City metropolitan area"},
["印第安納波利斯"] = {container = "印第安納", wp = "Indianapolis"},
["拉斯維加斯"] = {container = "內華達", wp = "Las Vegas"},
["辛辛那提"] = {container = "俄亥俄", wp = "Cincinnati"},
["奧斯汀"] = {container = "德克薩斯", wp = "Austin"},
["密爾沃基"] = {container = "威斯康辛", wp = "Milwaukee"},
["羅利"] = {container = "北卡羅來納", wp = "Raleigh"},
["納什維爾"] = {container = "田納西", wp = "Nashville"},
["傑克遜維爾"] = {container = "佛羅里達", wp = "Jacksonville"},
["新奧爾良"] = {container = "路易斯安那", wp = "New Orleans"},
["路易斯維爾"] = {container = "肯塔基", wp = "Louisville"},
["奧克拉荷馬城"] = {container = "奧克拉荷馬", wp = "Oklahoma City"},
["孟菲斯"] = {container = "田納西", wp = "Memphis"},
["伯明翰(美國)"] = {container = "阿拉巴馬", wp = "Birmingham, Alabama"},
["弗雷斯諾"] = {container = "加利福尼亞", wp = "Fresno"},
["里奇蒙"] = {container = "弗吉尼亞", wp = "Richmond"},
["水牛城"] = {container = "紐約", wp = "Buffalo"},
["艾爾帕索"] = {container = "德克薩斯", wp = "El Paso"},
["阿爾伯克基"] = {container = "新墨西哥", wp = "Albuquerque"},
["塔森"] = {container = "亞利桑那", wp = "Tucson"},
["科羅拉多斯普林斯"] = {container = "科羅拉多", wp = "Colorado Springs"},
["奧馬哈"] = {container = "內布拉斯加", wp = "Omaha"},
["塔爾薩"] = {container = "奧克拉荷馬", wp = "Tulsa"},
}
export.united_states_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "state"),
default_container = "美國",
default_placetype = "city",
default_is_city = true,
data = export.united_states_cities,
}
export.vietnam_cities = {
["胡志明市"] = {wp = "Ho Chi Minh City"},
["西貢"] = {alias_of = "胡志明市"},
["河內"] = {wp = "Hanoi"},
["峴港"] = {wp = "Da Nang"},
["海防"] = {wp = "Haiphong"},
["邊和"] = {container = "同奈省", wp = "Biên Hòa", placetype = "city"},
["芹苴"] = {wp = "Cần Thơ"},
["順化"] = {wp = "Huế"},
}
export.vietnam_cities_group = {
placename_to_key = false,
canonicalize_key_container = make_canonicalize_key_container(nil, "province"),
default_container = "越南",
default_placetype = {"municipality", "city"},
default_is_city = true,
data = export.vietnam_cities,
}
-----------------------------------------------------------------------------------
-- locations list --
-----------------------------------------------------------------------------------
export.locations = {
export.continents_group,
export.countries_group,
export.country_like_entities_group,
export.former_countries_group,
export.united_states_group,
export.united_kingdom_group,
export.scotland_group,
export.england_group,
export.northern_ireland_group,
export.ireland_group,
export.wales_group,
export.china_group,
export.china_prefecture_level_cities_group,
export.taiwan_group,
export.canada_group,
export.australia_group,
export.india_group,
export.japan_group,
export.brazil_group,
export.france_group,
export.france_departments_group,
export.germany_group,
export.italy_group,
export.spain_group,
export.austria_group,
export.bangladesh_group,
export.egypt_group,
export.finland_group,
export.greece_group,
export.indonesia_group,
export.lebanon_group,
export.malaysia_group,
export.malta_group,
export.mexico_group,
export.moldova_group,
export.morocco_group,
export.netherlands_group,
export.new_zealand_group,
export.norway_group,
export.pakistan_group,
export.philippines_group,
export.poland_group,
export.portugal_group,
export.romania_group,
export.bulgaria_group,
export.iran_group,
export.russia_group,
export.saudi_arabia_group,
export.south_africa_group,
export.laos_group,
export.north_korea_group,
export.south_korea_group,
export.thailand_group,
export.turkey_group,
export.ukraine_group,
export.vietnam_group,
export.nigeria_group,
export.new_york_boroughs_group,
export.australia_cities_group,
export.brazil_cities_group,
export.canada_cities_group,
export.france_cities_group,
export.germany_cities_group,
export.india_cities_group,
export.indonesia_cities_group,
export.italy_cities_group,
export.japan_cities_group,
export.mexico_cities_group,
export.nigeria_cities_group,
export.pakistan_cities_group,
export.philippines_cities_group,
export.russia_cities_group,
export.saudi_arabia_cities_group,
export.south_korea_cities_group,
export.spain_cities_group,
export.taiwan_cities_group,
export.united_kingdom_cities_group,
export.united_states_cities_group,
export.vietnam_cities_group,
export.misc_cities_group,
}
return export
1pyip2jxns5i7v7tozmzj5xi7ihkukw
Module:Place/placetypes
828
3437750
9909174
9906084
2026-09-29T09:13:08Z
TongcyBot
83009
更新至最新修正版
9909174
Scribunto
text/plain
local export = {}
export.force_cat = false -- set to true for testing
local m_locations = require("Module:place/locations")
local m_links = require("Module:links")
local m_table = require("Module:table")
local m_strutils = require("Module:string utilities")
local debug_track_module = "Module:debug/track"
local en_utilities_module = "Module:en-utilities"
-- Chinese localization data (zh_name, zh_suffix, qualifiers, extra-info labels, etc.)
local m_zh_data = require("Module:place/data")
local dump = mw.dumpObject
local insert = table.insert
local concat = table.concat
local internal_error = m_locations.internal_error
export.internal_error = internal_error
local process_error = m_locations.process_error
export.process_error = process_error
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local ucfirst = m_strutils.ucfirst
local ulower = m_strutils.lower
local rmatch = m_strutils.match
local split = m_strutils.split
--[==[ intro:
This module contains placetype data used by [[Module:place]] and {{tl|place}}, along with a significant amount of code
to work with both placetypes and locations, as well as some placename-related info (FIXME: Consider moving it to
[[Module:place/locations]]). See also [[Module:place/locations]], which has definitions of all known locations. You must
currently load this module using {{cd|require()}}, not using {{cd|mw.loadData()}}.
In particular, it contains two fundamental and tricky functions:
# `get_placetype_equivs`, which finds the equivalent placetypes to look under in order to find a given property, and in
the process correctly handles placetypes with qualifiers (including qualifiers that act similar to "type-raising"
operators in that they do something non-trivial to the placetype to their right) as well as form-of directives and
fallbacks.
# `find_matching_holonym_location`, which looks up a holonym to find a matching known location, but in the process
checks holonyms to the right to make sure there isn't a clash between the user-specified containing holonyms and the
containers of the known location being considered. This is done to prevent overcategorizing when either there are two
known locations with the same name (e.g. Birmingham in England and Birmingham, Alabama in the US), or more generally
two locations with the same name, one of which is a known location but where the other is not (e.g. we're processing
non-known-location Mérida, Spain and don't want it categorized like known location Mérida, Yucatán, Mexico).
Both of these functions are invoked repeatedly, and probably are invoked several times on the same inputs and as a
result are candidates for memoization to speed up the operation of {{tl|place}}.
]==]
------------------------------------------------------------------------------------------
-- Basic utilities --
------------------------------------------------------------------------------------------
--[==[
Return true if `force_cat` is set either in this module or in [[Module:place/locations]].
]==]
function export.get_force_cat()
return export.force_cat or m_locations.force_cat
end
-- Add the page to a tracking "category". To see the pages in the "category",
-- go to [[Wiktionary:Tracking/place/PAGE]] and click on "What links here".
local function track(page)
require(debug_track_module)("place/" .. page)
return true
end
function export.remove_links_and_html(text)
text = m_links.remove_links(text)
return text:gsub("<.->", "")
end
--[==[
Return the singular version of a maybe-plural placetype, or nil if not plural. This correctly handles placetypes with
irregular plurals such as `kibbutzim` plural of `kibbutz` by looking up in a table constructed from the `plural` values
specified in `placetype_data`. If a special plural value is not found, the regular singularization algorithm in
[[Module:en-utilities]] is invoked, which reverses the y -> ies change after vowels and the 'es' addition after sh/ch/x,
and otherwise just subtracts a final 's' (which will incorrectly generate 'passe' for plural 'passes'; FIXME: consider
changing this for words ending in '-sses'). If the generated singular is the same as the passed-in value, nil is
returned.
]==]
function export.maybe_singularize_placetype(placetype)
if not placetype then
return nil
end
if export.plural_placetype_to_singular[placetype] then
return export.plural_placetype_to_singular[placetype]
end
local retval = require(en_utilities_module).singularize(placetype)
if retval == placetype then
return nil
end
return retval
end
-- Return the correct plural of a placetype, and (if `do_ucfirst` is given) make the first letter uppercase. We first
-- look up the plural in `placetype_data`, falling back to pluralize() in [[Module:en-utilities]], which is almost
-- always correct.
function export.pluralize_placetype(placetype, do_ucfirst)
local ptdata = export.placetype_data[placetype]
if ptdata and ptdata.plural then
placetype = ptdata.plural
else
placetype = require(en_utilities_module).pluralize(placetype)
end
if do_ucfirst then
return ucfirst(placetype)
else
return placetype
end
end
--[==[
Get the data associated with a placetype, which may be in its singular or plural form. If `from_category` is specified,
we also look for category-only placetypes (generally plural) followed by `!`. Return three values: (a) the placetype
under which the data can be looked up (i.e. in its singular form if the passed-in `placetype` is plural and did not
match a category-only placetype followed by `!`); (b) the placetype data structure; (c) the type of `placetype` match
that occurred, one of `"direct"` if the canonical placetype is the same as the passed-in `placetype` and also the same
as the key under which `ptdata` was looked up, or `"direct-category"` if the `ptdata` was looked up under a key formed
from the passed-in `placetype` by adding `!`, or `"plural"` if the `ptdata` was looked up under the singularized version
of the plural passed-in `placetype`.
]==]
function export.get_placetype_data(placetype, from_category)
local ptdata = export.placetype_data[placetype]
if ptdata then
return placetype, ptdata, "direct"
end
if from_category then
ptdata = export.placetype_data[placetype .. "!"]
if ptdata then
return placetype .. "!", ptdata, "direct-category"
end
end
local sg_placetype = export.maybe_singularize_placetype(placetype)
if sg_placetype then
ptdata = export.placetype_data[sg_placetype]
if ptdata then
return sg_placetype, ptdata, "plural"
end
end
return nil
end
--[==[
Check for special pseudo-placetypes that should be ignored for categorization purposes.
]==]
function export.placetype_is_ignorable(placetype)
return placetype == "and" or placetype == "or" or placetype:find("^%(")
end
function export.resolve_placetype_aliases(placetype)
return export.placetype_aliases[placetype] or placetype
end
--[==[
Return a property from `placetype_data` for a given placetype. If the placetype isn't found in `placetype_data`, or the
key isn't found in the placetype's entry in `placetype_data`, return nil.
]==]
function export.get_placetype_prop(placetype, key)
-- Usually we are called on equivalent placetypes returned from `get_placetype_equivs`, in which case placetype
-- aliases have been resolved, but sometimes not, e.g. when fetching the indefinite article in
-- get_placetype_article(). `resolve_placetype_aliases` is just a simple lookup and it doesn't hurt to do it twice.
placetype = export.resolve_placetype_aliases(placetype)
if export.placetype_data[placetype] then
return export.placetype_data[placetype][key]
else
return nil
end
end
--[==[
Given a placetype, split the placetype into one or more potential ''splits'', each consisting of a three-element list
{ {``prev_qualifiers``, ``this_qualifier``, ``reduced_placetype``}}, i.e.
# the concatenation of zero or more previously-recognized qualifiers on the left, normally canonicalized (if there are
zero such qualifiers, the value will be nil);
# a single recognized qualifier, normally canonicalized (if there is no qualifier, the value will be nil);
# the "reduced placetype" on the right.
Splitting between the qualifier in (2) and the reduced placetype in (3) happens at each space character, proceeding from
left to right, and stops if a qualifier isn't recognized. All placetypes are canonicalized by checking for aliases
in `placetype_aliases`, but no other checks are made as to whether the reduced placetype is recognized. Canonicalization
of qualifiers does not happen if `no_canon_qualifiers` is specified.
For example, given the placetype `"small beachside unincorporated community"`, the return value will be
{ {
{nil, nil, "small beachside unincorporated community"},
{nil, "small", "beachside unincorporated community"},
{"small", "[[beachfront]]", "unincorporated community"},
{"small [[beachfront]]", "[[unincorporated]]", "community"},
}}
Here, `"beachside"` is canonicalized to `"[[beachfront]]"` and `"unincorporated"` is canonicalized to
`"[[unincorporated]]"`, in both cases according to the entry in `placetype_qualifiers`.
On the other hand, if given `"small former haunted community"`, the return value will be
{ {
{nil, nil, "small former haunted community"},
{nil, "small", "former haunted community"},
{"small", "former", "haunted community"},
}}
because `"small"` and `"former"` but not `"haunted"` are recognized as qualifiers.
Finally, if given `"former adr"`, the return value will be
{ {
{nil, nil, "former adr"},
{nil, "former", "administrative region"},
}}
because `"adr"` is a recognized placetype alias for `"administrative region"`.
]==]
function export.split_qualifiers_from_placetype(placetype, no_canon_qualifiers)
local splits = {{nil, nil, export.resolve_placetype_aliases(placetype)}}
local prev_qualifier = nil
while true do
local qualifier, reduced_placetype = placetype:match("^(.-) (.*)$")
if qualifier then
local canon = export.placetype_qualifiers[qualifier]
if canon == nil then
break
end
local new_qualifier = qualifier
if type(canon) == "table" then
canon = canon.link
end
if not no_canon_qualifiers and canon ~= false then
if canon == true then
new_qualifier = "[[" .. qualifier .. "]]"
else
new_qualifier = canon
end
end
insert(splits, {prev_qualifier, new_qualifier, export.resolve_placetype_aliases(reduced_placetype)})
prev_qualifier = prev_qualifier and prev_qualifier .. " " .. new_qualifier or new_qualifier
placetype = reduced_placetype
else
break
end
end
return splits
end
--[==[
Given a `placetype` (which may be pluralized), return an ordered list of equivalent placetypes to look under to find the
placetype's properties (such as the category or categories to be inserted). The return value is actually an ordered list
of objects of the form `{qualifier=``qualifier``, placetype=``equiv_placetype``}` where ``equiv_placetype`` is a
placetype whose properties to look up, derived from the passed-in placetype or from a contiguous subsequence of the
words in the passed-in placetype (always including the rightmost word in the placetype, i.e. we successively chop off
qualifier words from the left and use the remainder to find equivalent placetypes). ``qualifier`` is the remaining words
not part of the subsequence used to find ``equiv_placetype``; or nil if all words in the passed-in placetype were used
to find ``equiv_placetype``. (FIXME: This qualifier is not currently used anywhere.) Only placetypes for which there is
an entry in `placetype_data` are included. The placetype passed in is always checked first, and will form the first
entry if it exists in `placetype_data`.
'''NOTE:''' This is a tricky function as it implements handling of (a) qualifiers, (b) fallback logic, (c)
"type-raising" qualifiers such as `former`/`ancient`/etc. as well as `fictional` and `mythological`, and (d) form-of
directives, which act somewhat similarly to `former`, and allows interaction between more than one of these
simultaneously (e.g. official names of former places, which have their own categorization).
If {{tl|place}} gets too slow, one potential speedup is to memoize the results of this function, as it appears to be
getting called more than once on the same inputs. Another similar potential speedup is to memoize the results of
`iterate_matching_holonym_location()`.
For example, given the placetype `left tributary`, the following placetype/qualifier combinations are checked in turn:
```
{qualifier = nil, placetype="left tributary"}
{qualifier = "left", placetype="tributary"}
{qualifier = "left", placetype="river"}
```
and the return value will be
{ {
{qualifier = "left", placetype="tributary"},
{qualifier = "left", placetype="river"},
}}
The algorithm first enters the placetype itself into the list, then checks for `left tributary` as a recognized
placetype in `placetype_data` and doesn't find it, so it doesn't enter it into the returned list (if it found it, it
would add it as well as any fallbacks directly after it). It then splits off the recognized qualifier `left` to form the
''reduced placetype'' `tributary`, which is entered into the list because it is found in `placetype_data`. Then, because
it has a fallback `river`, which exists in `placetype_data`, the fallback is entered next.
Another example is `small rural fraziones` (where a ''frazione'' is type of subdivision of a ''comune'' or municipality,
often specifically an outlying hamlet). the placetype/qualifier combinations checked are:
```
{qualifier = nil, placetype="small rural fraziones"}
{qualifier = nil, placetype="small rural frazione"}
{qualifier = "small", placetype="rural fraziones"}
{qualifier = "small", placetype="rural frazione"}
{qualifier = "small [[rural]]", placetype="fraziones"}
{qualifier = "small [[rural]]", placetype="frazione"}
{qualifier = "small [[rural]]", placetype="hamlet"}
{qualifier = "small [[rural]]", placetype="village"}
```
The return value ends up as
{qualifier = "small [[rural]]", placetype="frazione"},
{qualifier = "small [[rural]]", placetype="hamlet"},
{qualifier = "small [[rural]]", placetype="village"},
}}
Here, because the result of singularizing `fraziones` returns a different value from the placetype itself, that
singularized value is checked after the original plural value. Also, in the process of splitting off qualifiers,
they are canonicalized if the entry in `placetype_qualifiers` says to do so; in this case, links are placed around
`rural`. Finally, `frazione` has `hamlet` as its fallback, which in turn has `village` as its fallback, so both
fallbacks end up being returned.
`no_fallback`, if set, disables returning equivalent placetypes based on the `fallback` setting for a placetype. This is
used in the first of two loops in find_placetype_cat_specs() in [[Module:place]] to prefer exact matches for placetypes
such as barangays with later holonyms to matches based on a fallback such as `neighborhood` with an earlier holonym.
See the comment in that function in [[Module:place]] for a more detailed explanation of why this is needed. Only the
placetype itself, and any reduced placetypes created by chopping off recognized qualifiers at the beginning, are
returned; but we do not return reduced placetypes if a containing placetype exists in `placetype_data`. (For example,
`"overseas territory"` has a fallback `"dependent territory"`, and `"overseas"` is also a recognized qualifier. When
`no_fallback` is in place, without the above proviso, we would return `"overseas territory"` followed by `"territory"`
with the incorrect effect of classifying an `"overseas territory"` of the United Kingdom such as `"Gibraltar"` under
[[:Category:Territories of the United Kingdom]] instead of [[:Category:Dependent territories of the United Kingdom]].)
As an exception, if `historical`, `ancient`, `former` or the like are found, they proceed ignoring `no_fallback`,
because it seems tricky to handle them correctly in the presence of `no_fallback`, and historical/former placetypes
rarely occur with exact match category specs anyway.
`no_split_qualifiers` prevents splitting off recognized qualifiers and returning the remainder of the placetype as an
equivalent placetype. Only the passed-in placetype, and any fallbacks, will be returned. This is used in
[[Module:category tree/topic cat/data/Places]] when looking up placetypes found in categories. Such placetypes won't
have qualifiers and so it doesn't make sense to try and look for them.
`from_category`, if set, causes category-only placetypes (those ending in `!`) to also be checked.
`form_of_directive`, if set, causes the specified form-of directive (e.g. `FORMER_NAME_OF`) to be prepended to checked
placetypes, their directive-specific type (e.g. `FORMER_NAME_OF_type`), and their classes (`class`) to get the
appropriate placetypes to check for form-of-directive categories. It falls back to the prepended generic `place` as a
placetype, e.g. `FORMER_NAME_OF place`, if nothing else matches.
`no_check_for_inherently_former` is used internally to prevent an infinite loop when checking for `inherently_former`.
`register_former_as_non_former` is a major hack used in `get_bare_categories` to deal with the mismatch between e.g.
known location `Yugoslavia` declaring itself a `country` but definitions of it declaring it a `former country`. It
causes the non-former version of the specified placetype to be included in the returned equivalents along with the
former placetypes. [FIXME: This should apply only to the entries in `former_countries` but it's tricky to do that now;
fix this in the known-location refactor. -- The known-location refactor is already done but we haven't yet fixed this.]
]==]
function export.get_placetype_equivs(placetype, props)
local no_fallback, no_split_qualifiers, no_check_for_inherently_former, from_category, register_former_as_non_former
local form_of_directive
if props then
no_fallback, no_split_qualifiers, no_check_for_inherently_former, from_category, register_former_as_non_former =
props.no_fallback, props.no_split_qualifiers, props.no_check_for_inherently_former, props.from_category,
props.register_former_as_non_former
form_of_directive = props.form_of_directive
end
local equivs = {}
-- Insert `placetype` into `equivs`, along with any fallback placetypes listed in `placetype_data`. `qualifier` is
-- the preceding qualifier to insert into `equivs` along with the placetype (see comment at top of function). If
-- `from_category` is given, we also check for a category-specific entry consisting of the placetype followed by
-- `!`, and in all cases we also check to see if `placetype` is plural, and if so, insert the singularized version
-- along with its fallbacks (if any) in `placetype_data`. `form_of_prefix` is a form-of prefix such as
-- `OFFICIAL_NAME_OF`. If specified, we check the fallbacks of `placetype` without the prefix but then insert into
-- `equivs` the prefixed placetype. This way, if the user says e.g. {{tl|place|pt|@official name of:Cuba|island country|r/Caribbean}},
-- we will correctly categorize into [[:Category:Official names of countries]], rather than only trying to look up
-- `OFFICIAL_NAME_OF island country` and failing, falling back ultimately to [[:Category:Official names of places]].
local function insert_placetype_and_fallbacks(qualifier, placetype, form_of_prefix)
local function insert_equiv(pt)
if form_of_prefix then
-- Let's say the user says {{tl|place|pt|@official name of:Cuba|island country|r/Caribbean}} and we have
-- no entry for `OFFICIAL_NAME_OF island country` but we do for `OFFICIAL_NAME_OF country` (which we end
-- up processing because `island country` falls back to `country`), and that entry in turn is defined
-- using a fallback. We have to insert that fallback-of-fallback, and the easiest/cleanest way of
-- handling this is by calling ourselves recursively.
insert_placetype_and_fallbacks(qualifier, form_of_prefix .. " " .. pt)
else
insert(equivs, {qualifier=qualifier, placetype=pt})
end
end
-- Insert the placetype, along with any fallbacks.
local canon_placetype, ptdata, ptmatch = export.get_placetype_data(placetype, from_category)
if ptdata then
insert_equiv(canon_placetype)
if no_fallback then
return
end
local first_placetype = #equivs + 1
local prev_placetype = nil
while true do
local pt_value = export.placetype_data[canon_placetype]
if not pt_value then
internal_error("Fallback value %s specified for placetype %s but is not in `placetype_data`",
canon_placetype, prev_placetype)
end
if pt_value.fallback then
insert_equiv(pt_value.fallback)
local last_placetype = #equivs
if last_placetype - first_placetype >= 10 then
local fallback_loop = {}
for i = first_placetype, last_placetype do
insert(fallback_loop, equivs[i].placetype)
end
internal_error("Apparent loop in fallback chain: %s", table.concat(fallback_loop, " -> "))
end
prev_placetype = canon_placetype
canon_placetype = pt_value.fallback
else
break
end
end
end
end
-- Insert `placetype` into `equivs`, along with any fallback placetypes listed in `placetype_data`. This is a
-- wrapper around the more basic `insert_placetype_and_fallbacks()` which handles form-of directives. If there is no
-- form-of directive, this function directly calls `insert_placetype_and_fallbacks()`. We do things this way so that
-- form-of directives correctly combine with `former`-type qualifiers. Note that we also have special backups for
-- form-of directives that check `DIRECTIVE place` (and before that, `DIRECTIVE FORMER/ANCIENT place` is there's a
-- `former`-type directive); these backups live outside this function because we want them done once, late, rather
-- than in each invocation of `process_and_insert_placetype()`.
local function process_and_insert_placetype(qualifier, reduced_placetype)
if form_of_directive then
-- First check for e.g. `OFFICIAL_NAME_OF island country` and its fallbacks; then we look for fallbacks of
-- `island country` and check e.g. `OFFICIAL_NAME_OF country` and its fallbacks. All of this is handled by
-- `insert_placetype_and_fallbacks()` with appropriate parameters. After that, check the general class of
-- the directive, e.g. `subpolity` if something like `district` is given. (Eventually, we check for
-- `OFFICIAL_NAME_OF place` as a backup, but this happens at the end outside the loop over qualifiers.)
insert_placetype_and_fallbacks(qualifier, reduced_placetype, form_of_directive)
if not no_fallback then
local reduced_placetype_equivs = export.get_placetype_equivs(reduced_placetype)
local directive_type = export.get_equiv_placetype_prop_from_equivs(reduced_placetype_equivs,
function(pt) return export.get_placetype_prop(pt, form_of_directive .. "_type") or
export.get_placetype_prop(pt, "class") end
)
if not directive_type then
local pt_data = export.get_equiv_placetype_prop_from_equivs(reduced_placetype_equivs,
function(pt) return export.placetype_data[pt] end
)
if pt_data then
internal_error("For placetype %s in conjunction with form-of directive %s, placetype data " ..
'located but directive-specific type property %s missing, and so is "class"; ' ..
"placetypes searched are %s", reduced_placetype, form_of_directive,
form_of_directive .. "_type", reduced_placetype_equivs)
else
-- This should be allowed, as we allow unrecognized placetypes in general.
end
elseif directive_type ~= "!" then
insert_placetype_and_fallbacks(qualifier, directive_type, form_of_directive)
end
end
else
insert_placetype_and_fallbacks(qualifier, reduced_placetype)
end
end
-- Successively split off recognized qualifiers and loop over successively greater sets of qualifiers from the left
-- (unless `no_split_qualifiers` is specified, in which case we don't check for qualifiers).
local splits
if no_split_qualifiers then
splits = {{nil, nil, export.resolve_placetype_aliases(placetype)}}
else
splits = export.split_qualifiers_from_placetype(placetype)
end
for _, split in ipairs(splits) do
local prev_qualifier, this_qualifier, reduced_placetype = unpack(split, 1, 3)
-- If a special "former" qualifier like `former` or `historical` isn't present, and
-- `no_check_for_inherently_former` is not given (this flag is used to avoid infinite loops), check for
-- "inherently former" placetypes like `satrapy` and `treaty port` that always refer to no-longer-existing
-- placetypes, and handle accordingly.
local unlinked_this_qualifier
if this_qualifier and this_qualifier:find("%[") then
unlinked_this_qualifier = export.remove_links_and_html(this_qualifier)
else
unlinked_this_qualifier = this_qualifier
end
local former_qualifiers = this_qualifier and export.former_qualifiers[unlinked_this_qualifier] or nil
if not former_qualifiers and not no_check_for_inherently_former then
former_qualifiers = export.get_equiv_placetype_prop(reduced_placetype,
function(pt) return export.get_placetype_prop(pt, "inherently_former") end,
{no_check_for_inherently_former = true})
end
-- If a special "former" qualifier like `former` or `historical` is present, map it to the appropriate internal
-- qualifiers (`ANCIENT` and/or `FORMER`, which are written in all-caps to distinguish them from user-specified
-- qualifiers), fetch the `former_type` property, and treat the placetype as if a concatenation of the mapped
-- qualifier(s) and the value of `former_type`. For example, if `medieval village` is given, we map `medieval`
-- to `ANCIENT` and `FORMER`, and `village` to its `former_type` of `settlement`, and enter the placetypes
-- `ANCIENT settlement` and `FORMER settlement` (in that order) into `equivs`. If the placetype following the
-- "former" qualifier is recognized in `placetype_data` but has no `former_type` and no fallback with a
-- `former_type` specified, it is an internal error; but if the placetype isn't recognized (e.g. something like
-- `former greenhouse` is specified and we don't have an entry for `greenhouse`), just track the occurrence and
-- don't enter anything into `equivs`.
if former_qualifiers then
-- FIXME: Should we respect `no_fallback` here? My instinct says no.
local reduced_placetype_equivs = export.get_placetype_equivs(reduced_placetype, {
no_check_for_inherently_former = true
})
local former_type = export.get_equiv_placetype_prop_from_equivs(reduced_placetype_equivs,
function(pt) return export.get_placetype_prop(pt, "former_type") or
export.get_placetype_prop(pt, "class") end
)
if not former_type then
local pt_data = export.get_equiv_placetype_prop_from_equivs(reduced_placetype_equivs,
function(pt) return export.placetype_data[pt] end
)
if pt_data then
internal_error("For placetype %s, placetype data located but `former_type` missing; " ..
"placetypes searched are %s", reduced_placetype, reduced_placetype_equivs)
else
-- Enable error when we've verified there aren't any examples.
track("bad-former-placetype")
track("bad-former-placetype/" .. reduced_placetype)
--process_error("For placetype '%s', unrecognized placetype following 'former'-type " ..
-- "qualifier; searched placetype(s) %s", reduced_placetype, dump(reduced_placetype_equivs))
end
elseif former_type ~= "!" then
-- First check directly for `ANCIENT/FORMER` + the original following placetype. This makes it possible
-- for (e.g.) former provinces of the Roman empire to be categorized specially.
for _, former_qualifier in ipairs(former_qualifiers) do
process_and_insert_placetype(prev_qualifier, former_qualifier .. " " .. reduced_placetype)
end
for _, former_qualifier in ipairs(former_qualifiers) do
process_and_insert_placetype(prev_qualifier, former_qualifier .. " " .. former_type)
end
-- HACK! See explanation above for `register_former_as_non_former`.
if register_former_as_non_former then
process_and_insert_placetype(prev_qualifier, reduced_placetype)
end
-- If we're processing a form-of directive, after doing everything else we do
-- `DIRECTIVE ANCIENT/FORMER place` e.g. `OFFICIAL_NAME_OF FORMER place` as a backup.
if form_of_directive and not no_fallback then
for _, former_qualifier in ipairs(former_qualifiers) do
insert_placetype_and_fallbacks(prev_qualifier, form_of_directive .. " " .. former_qualifier ..
" place")
end
end
-- Don't continue processing equivs. The reason is probably the same as the `break` below for
-- qualifier_to_placetype_equivs[]; categories for `former BLAH` are set using `default`, and
-- non-former equivs will otherwise take precedence.
break
end
end
-- Then see if the rightmost split-off qualifier is in qualifier_to_placetype_equivs
-- (e.g. 'fictional *' -> 'fictional location'). If so, add the mapping.
if this_qualifier and export.qualifier_to_placetype_equivs[unlinked_this_qualifier] then
insert(equivs, {
qualifier=prev_qualifier,
placetype=export.qualifier_to_placetype_equivs[unlinked_this_qualifier]
})
-- Don't continue processing equivs; otherwise, if we specify 'mythological city', even though the
-- equivalent entry for 'mythological location' gets inserted ahead of the entry for 'city', the
-- latter ends up generating the category because the category for 'mythological location' is set as
-- the default value, which is used only when no non-default category can be found.
break
end
-- Finally, join the rightmost split-off qualifier to the previously split-off qualifiers to form a combined
-- qualifier, and add it along with reduced_placetype and any mapping in placetype_data for reduced_placetype.
-- NOTE: The first time through this loop, both `prev_qualifier` and `this_qualifier` are nil, and this inserts
-- the full placetype into `equivs`.
local qualifier = prev_qualifier and prev_qualifier .. " " .. this_qualifier or this_qualifier
process_and_insert_placetype(qualifier, reduced_placetype)
-- If `no_fallback` and there's an entry in `placetype_data` for this placetype, don't include any reduced
-- placetypes to avoid the "overseas territory treated as a territory" issue describe above.
if no_fallback then
local canon_placetype, ptdata, ptmatch = export.get_placetype_data(reduced_placetype, from_category)
if canon_placetype then
break
end
end
end
-- If we're processing a form-of directive, after doing everything else we do `DIRECTIVE place` e.g.
-- `OFFICIAL_NAME_OF place` as a backup; but only if either the placetype as a whole is recognized or the placetype
-- begins with a recognized qualifier. This latter check is to avoid categorizing into e.g.
-- [[Category:en:Former names of places]] in an invocation like
-- {{place|en|@former name of:Democratic Republic of the Congo|country|r/Central Africa|;|used from 1971–1997}};
-- the `used from 1971–1997` gets treated as a placetype and we're called on it.
if form_of_directive and not no_fallback and (splits[2] or export.get_placetype_data(placetype, from_category)) then
insert_placetype_and_fallbacks(nil, form_of_directive .. " place")
end
return equivs
end
function export.get_equiv_placetype_prop_from_equivs(equivs, fun, continue_on_nil_only)
for _, equiv in ipairs(equivs) do
local retval = fun(equiv.placetype)
if continue_on_nil_only and retval ~= nil or not continue_on_nil_only and retval then
return retval, equiv
end
end
return nil, nil
end
--[==[
Given a placetype `placetype` and a function `fun` of one argument, iteratively call the function on equivalent
placetypes fetched from `get_placetype_equivs` until the function returns a non-falsy value (i.e. not {nil} or {false});
but if `continue_on_nil_only` is specified, the iterations continue until the function returns non non-{nil} value.
FIXME: We should make `continue_on_nil_only` the default; but this requires changing some callers.) When `fun` returns a
non-falsy or non-{nil} value, `get_equiv_placetype_prop` returns two values: the value returned by `fun` and the
equivalent placetype that triggered the non-falsy (or non-{nil}) return value. If `fun` never returns a non-falsy (or
non-{nil}) value, `get_equiv_placetype_prop` returns {nil} for both return values. If `placetype` is passed in as {nil},
the return value is the result of calling `fun` on {nil} (whatever it is) with {nil} for the second return value.
]==]
function export.get_equiv_placetype_prop(placetype, fun, props)
if not placetype then
return fun(nil), nil
end
return export.get_equiv_placetype_prop_from_equivs(export.get_placetype_equivs(placetype, props), fun,
props and props.continue_on_nil_only)
end
--[==[
Return the article that is used with an entry placetype. We proceed as follows:
# See if there is a recognized qualifier at the beginning that specifies an article (including `false` for no article).
This takes precedence over anything else, so that e.g. `various capitals` gets no article rather than "`the"`.
# Then check the placetype or any equivalent placetype for the `entry_placetype_use_the` property, indicating that
`"the"` should be used.
# Otherwise we look to see if the placetype itself (not any equivalents, even those involving deleting a qualifier from
the beginning) has an entry in `placetype_data` that specifies the indefinite article using `entry_placetype_use_the`
(principally for use with placetypes like `union territory`).
# Otherwise, we use [[Module:en-utilities]] to apply the standard algorithm to generate `"an"` for words beginning with
a vowel and `"a"` otherwise.
If `ucfirst` is true, the first letter of the article is made upper-case.
]==]
function export.get_placetype_article(placetype, ucfirst)
local art
local qualifier, reduced_placetype = placetype:match("^(.-) (.*)$")
if qualifier then
local canon = export.placetype_qualifiers[qualifier]
if type(canon) == "table" then
art = canon.article
end
end
if art == false then
return art
end
if art == nil then
local placetype_use_the = export.get_equiv_placetype_prop(placetype,
function(pt) return export.get_placetype_prop(pt, "entry_placetype_use_the") end)
if placetype_use_the then
art = "the"
else
art = export.get_placetype_prop(placetype, "entry_placetype_indefinite_article")
if not art then
art = require(en_utilities_module).get_indefinite_article(placetype)
end
end
end
if ucfirst then
art = m_strutils.ucfirst(art)
end
return art
end
--[==[
Return the preposition that should be used after `placetype` when occurring as an entry placetype or in categories
(e.g. `city >in< France` but `country >of< South America`). The preposition defaults to `"in"` if not specified.
]==]
function export.get_placetype_entry_preposition(placetype)
local pt_prep = export.get_equiv_placetype_prop(placetype,
function(pt) return export.get_placetype_prop(pt, "preposition") end
)
return pt_prep or "in"
end
--[==[
Given a place desc (see top of file) and a holonym object (see top of file), add a key/value into the place desc's
`holonyms_by_placetype` field corresponding to the placetype and placename of the holonym. For example, corresponding
to the holonym "c/Italy", a key "country" with the list value {"Italy"} will be added to the place desc's
`holonyms_by_placetype` field. If there is already a key with that place type, the new placename will be added to the
end of the value's list.
]==]
function export.key_holonym_into_place_desc(place_desc, holonym)
if not holonym.placetype then
return
end
-- Key in equivalent placetypes, so that e.g. `cities/San Francisco` gets keyed under `city`; but don't do
-- fallbacks, as it doesn't seem correct for the "do other holonyms of the same placetype" algorithm to do holonyms
-- of different types just because they have the same fallback.
local equiv_placetypes = export.get_placetype_equivs(holonym.placetype, {no_fallback = true})
local unlinked_placename = holonym.unlinked_placename
for _, equiv in ipairs(equiv_placetypes) do
local placetype = equiv.placetype
if not place_desc.holonyms_by_placetype then
place_desc.holonyms_by_placetype = {}
end
if not place_desc.holonyms_by_placetype[placetype] then
place_desc.holonyms_by_placetype[placetype] = {unlinked_placename}
else
insert(place_desc.holonyms_by_placetype[placetype], unlinked_placename)
end
end
end
--[=[
Construct a formatted link from the raw link spec `link` given the canonical singular placetype `sg_placetype`. If the
placetype was originally plural, `orig_placetype` should contain this plural value; otherwise it should be nil. This
will construct the appropriate type of link that displays as `orig_placetype` (or otherwise `sg_placetype`) but links to
whatever the `link` spec specifies (which may be `sg_placetype`, a Wikipedia article, etc.). `ptdata` is the placetype
data structure for the placetype, and `from_category` indicates that we are generating the description of a category
(otherwise we are generating the display form of an entry placetype).
]=]
local function make_placetype_link(link, sg_placetype, orig_placetype, ptdata, from_category, noerror)
if not from_category and ptdata.disallow_in_entries then
if noerror then
return "[not meant to be specified directly, with warning: " .. ptdata.disallow_in_entries .. "]"
else
process_error("Placetype %s is not meant to be specified directly: " .. ptdata.disallow_in_entries, sg_placetype)
end
end
if link == nil then
internal_error("Placetype data present for placetype %s but no link= setting given", sg_placetype)
elseif link == true then
if orig_placetype then
return ("[[%s|%s]]"):format(sg_placetype, orig_placetype)
else
return ("[[%s]]"):format(sg_placetype)
end
elseif link == false then
process_error("Placetype %s is not meant to be specified directly, but is only for internal use", sg_placetype)
elseif link == "w" then
return ("[[w:%s|%s]]"):format(sg_placetype, orig_placetype or sg_placetype)
elseif link == "separately" then
if orig_placetype then
local sg_words = split(sg_placetype, " ")
local orig_words = split(orig_placetype, " ")
if #sg_words ~= #orig_words then
internal_error("Can't construct 'separately' link for plural placetype %s as original placetype %s " ..
"has different number of words", orig_placetype, sg_placetype)
else
for i = 1, #sg_words do
if sg_words[i] == orig_words[i] then
sg_words[i] = ("[[%s]]"):format(sg_words[i])
else
sg_words[i] = ("[[%s|%s]]"):format(sg_words[i], orig_words[i])
end
end
return concat(sg_words, " ")
end
else
return (sg_placetype:gsub("([^ ]+)", "[[%1]]"))
end
elseif link:find("^%+") then
link = link:sub(2) -- discard initial +
return ("[[%s|%s]]"):format(link, orig_placetype or sg_placetype)
elseif not orig_placetype then
return link
else
return require(en_utilities_module).pluralize(link)
end
end
--[==[
Get the display form of a placetype by looking it up in `placetype_data`. If the placetype is recognized, or is the
plural of a recognized placetype, the corresponding linked display form is returned (with plural placetypes displaying
as plural but linked to the singular form of the placetype). Otherwise, return nil. If we're generating the description
of a category, `category_type` should be set to one of `"top-level"` (for top-level categories like
[[:Category:Neighborhoods]]), `"noncity"` (for non-city categories like [[:Category:Neighborhoods in Illinois, USA]]) or
`"city"` (for city categories like [[:Category:Neighborhoods of Chicago]]). Otherwise, we're generating the description
for use in formatting a {{tl|place}} call, and category-only placetypes ending in `!` will be ignored, along with
special `category_link*` settings. `return_full` is used along with `category_type` and will preferably return the
"full" variant of category link settings, i.e. `full_category_link*`; if they don't exist, the `category_link*` value is
prepended with `"names of"`. `noerror` says to not throw an error when encountering entry placetypes that would be
disallowed.
]==]
function export.get_placetype_display_form(placetype, category_type, return_full, noerror)
local from_category = not not category_type
local canon_placetype, ptdata, ptmatch = export.get_placetype_data(placetype, from_category)
if canon_placetype then
local raw_link
local function is_linked_string(str)
return type(str) == "string" and str:find("%[%[")
end
if category_type then
local fetched_full
local function fetch_maybe_full(prop)
local retval = ptdata["full_" .. prop]
if retval ~= nil then
if return_full then
return retval, true
else
internal_error("Saw full_" .. prop .. "=%s but `return_full` not set, can't handle", retval)
end
end
return ptdata[prop], false
end
local function maybe_prefix(str)
if return_full and not fetched_full then
return "names of " .. str
else
return str
end
end
-- Careful with `false` as possible value.
if category_type == "top-level" then
raw_link, fetched_full = fetch_maybe_full("category_link_top_level")
elseif category_type == "noncity" then
raw_link, fetched_full = fetch_maybe_full("category_link_before_noncity")
elseif category_type == "city" then
raw_link, fetched_full = fetch_maybe_full("category_link_before_city")
else
internal_error('Unrecognized value for `category_type` %s, should be "top-level", "noncity" or "city"',
category_type)
end
if type(raw_link) == "string" then
return maybe_prefix(raw_link), ptdata
elseif raw_link ~= nil then
return raw_link, ptdata
end
raw_link, fetched_full = fetch_maybe_full("category_link")
if raw_link == false then
return raw_link, ptdata
end
if is_linked_string(raw_link) then
return maybe_prefix(raw_link), ptdata
end
if ptmatch == "plural" then
raw_link, fetched_full = fetch_maybe_full("plural_link")
if raw_link == false then
return raw_link, ptdata
end
if is_linked_string(raw_link) then
return maybe_prefix(raw_link), ptdata
end
end
if raw_link == nil then
raw_link, fetched_full = fetch_maybe_full("link")
end
if raw_link == false then
return raw_link, ptdata
end
return maybe_prefix(make_placetype_link(raw_link, canon_placetype,
placetype ~= canon_placetype and placetype or nil, ptdata, from_category, noerror)), ptdata
else
if ptmatch == "plural" then
raw_link = ptdata.plural_link
if raw_link == false then
process_error("Placetype %s cannot appear plural", placetype)
end
if is_linked_string(raw_link) then
return raw_link, ptdata
end
end
if raw_link == nil then
raw_link = ptdata.link
end
return make_placetype_link(raw_link, canon_placetype,
placetype ~= canon_placetype and placetype or nil, ptdata, from_category, noerror), ptdata
end
end
return nil
end
local function resolve_unlinked_placename_display_aliases(placetype, placename)
local equiv_placetypes = export.get_placetype_equivs(placetype)
for i, equiv in ipairs(equiv_placetypes) do
equiv_placetypes[i] = equiv.placetype
end
local all_display_aliases_found = {}
local all_others_found = {}
for group, key, spec in m_locations.iterate_matching_location {
placetypes = equiv_placetypes,
placename = placename,
alias_resolution = "display",
} do
if spec.alias_of and spec.display then
insert(all_display_aliases_found, {group, key, spec, spec.display_as_full})
else
insert(all_others_found, {group, key, spec})
end
end
if not all_display_aliases_found[1] then
return placename
elseif all_display_aliases_found[2] then
internal_error("Found multiple matching display aliases for placename %s, placetype %s: " ..
"all_display_aliases_found=%s, all_others_found=%s", placename, placetype, all_display_aliases_found,
all_others_found)
elseif all_others_found[1] then
internal_error("Found a display alias along with other possible meanings for placename %s, placetype %s: " ..
"all_display_aliases_found=%s, all_others_found=%s", placename, placetype, all_display_aliases_found,
all_others_found)
else
local group, key, spec, as_full = unpack(all_display_aliases_found[1])
local full, elliptical = m_locations.key_to_placename(group, key)
return as_full and full or elliptical
end
end
--[==[
If `placename` of type `placetype` is a display alias, convert it to its canonical form; otherwise, return unchanged.
Display aliases transform certain placenames into canonical displayed forms. For example, if any of `country/US`,
`country/USA` or `country/United States of America` (or `c/US`, etc.) are given, the result will be displayed as
`United States`.
'''NOTE''': Display aliases change what is displayed from what the editor wrote in the Wikitext. As a result, they
should (a) be non-political in nature, and (b) not involve a change where the word `the` needs to be added or removed.
For example, normalizing `US` and `USA` to `United States` for display purposes is OK but normalizing `Burma` to
`Myanmar` is not (instead a cat alias should be used) because the terms `Burma` and `Myanmar` have clear political
connotations. Similarly, we have a display alias that maps the old name of `Macedonia` as a country (but not a region!)
to `North Macedonia`, but `Republic of Macedonia` is mapped to `North Macedonia` only as a cat alias because the two
terms differ in their use of `the`. (For example, if we had a display alias mapping `Republic of Macedonia` to
`North Macedonia`, the call {{tl|place|en|the <<capital city>> of the <<c/Republic of Macedonia>>}} would wrongly
display as `the [[capital city]] of the [[North Macedonia]]`.) Generally, display normalizations tend to involve
alternative forms (e.g. abbreviations, ellipses, foreign spellings) where the normalization improves clarity and
consistency.
]==]
function export.resolve_placename_display_aliases(placetype, placename)
-- If the placename is a link, apply the alias inside the link.
-- This pattern matches both piped and unpiped links. If the link is not piped, the second capture (linktext) will
-- be empty.
local link, linktext = rmatch(placename, "^%[%[([^|%[%]]+)|?([^|%[%]]-)%]%]$")
if link then
if linktext ~= "" then
local alias = resolve_unlinked_placename_display_aliases(placetype, linktext)
return "[[" .. link .. "|" .. alias .. "]]"
else
local alias = resolve_unlinked_placename_display_aliases(placetype, link)
return "[[" .. alias .. "]]"
end
else
return resolve_unlinked_placename_display_aliases(placetype, placename)
end
end
--[==[
Generate the "prefixed" version of a bare key, i.e. prefix it with `the` if correct for this key.
]==]
function export.get_prefixed_key(key, spec)
if spec.the then
return "the " .. key
else
return key
end
end
-- Necessary for use by [[Module:place]]. FIXME: Reorganize the modules so this isn't necessary.
export.iterate_matching_location = m_locations.iterate_matching_location
--[=[
Iterator that iterates over holonyms in `place_desc`. If `first_holonym_index` is given, start iterating at the
specified holonym and stop either when there are no more holonyms or a holonym with modifier `:also` is found. If
`first_holonym_index` is nil or omitted, iterate over all holonyms regardless. If `include_raw_text_holonyms` is
specified, raw text holonyms (those not of the form `placetype/placename`) are returned as well; they can be identified
by the fact that the `placetype` field in the holonym structure is nil. Two values are returned at each iteration, the
holonym index and holonym structure, similar to `ipairs()`.
]=]
function export.get_holonyms_to_check(place_desc, first_holonym_index, include_raw_text_holonyms)
local stop_at_also = not not first_holonym_index
return function(place_desc, index)
while true do
index = index + 1
local this_holonym = place_desc.holonyms[index]
-- If we were passed in a starting holonym index, go up to but not including a holonym marked with `:also`
-- (continue_cat_loop); the categorization code will then restart the loop at that holonym. That holonym
-- will have `:also` marked on it, so make sure not to stop immediately if the first holonym is marked with
-- `:also`.
if not this_holonym or stop_at_also and index > first_holonym_index and this_holonym.continue_cat_loop then
return nil
end
-- If not placetype, we're processing raw text, which we normally want to skip.
if include_raw_text_holonyms or this_holonym.placetype then
return index, this_holonym
end
end
end, place_desc, first_holonym_index and first_holonym_index - 1 or 0
end
--[==[
If the holonym in `data` (in the format as passed to a category handler) refers to a known location, iterate over all
such known locations, returning for each location the corresponding key, spec and group as well as the trail of
ancestral containers. Unlike `iterate_matching_location()`, this specifically checks that there is no mismatch between
the location's containers at any level and any of the following holonyms in the {{tl|place}} spec. The fields in `data`
are:
* `holonym_placetype`: The placetype of the holonym. It can actually be a list of possible placetypes, as with
`iterate_matching_location()`.
* `holonym_placename`: The placename of the holonym.
* `holonym_index`: The index of the holonym among the holonyms in `place_desc`, or nil if the holonym is not among the
holonyms in `place_desc`. (If a holonym index is given, we check for container mismatches among the holonyms
following the specified index, stopping either when encountering a holonym marked with modifier `:also` or, if none
exist, when we run out of holonyms. If no holonym index is given, we check all holonyms for container mismatches.)
* `place_desc`: Description of the place; used for the holonyms, to check for container mismatches.
Returns four values: the location group, the canonical key by which the location is known, the spec object describing
the location and the trail of ancestral containers for the location. The first three values are the same as for
`iterate_matching_location`.
]==]
function export.iterate_matching_holonym_location(data)
local holonym_placetype, holonym_placename, holonym_index, place_desc =
data.holonym_placetype, data.holonym_placename, data.holonym_index, data.place_desc
local matching_location_iterator = m_locations.iterate_matching_location {
placetypes = holonym_placetype,
placename = holonym_placename,
}
return function()
while true do
local group, key, spec = matching_location_iterator()
if not group then
return nil
end
local container_trail = {}
-- For each level of container, check that there are no mismatches (i.e. other location of the same
-- placetype) mentioned. We allow a mismatch at a given level if there's also a match with the container
-- at that level. For example, in the case of Kansas City, defined in [[Module:place/locations]] as a city
-- in Missouri, if we define it as {{tl|place|city|s/Missouri,Kansas}}, we ignore the mismatching state of
-- Kansas because the correct state of Missouri was also mentioned. But imagine we are defining Newark,
-- Delaware as {{tl|place|city|s/Delaware|c/US}} and (as is the case) we have an entry for Newark, New
-- Jersey in [[Module:place/locations]]. Just because the containing location `US` matches isn't enough,
-- because Newark, NJ also has New Jersey as a containing location and there's a mismatch at that level. If
-- there are no mismatches at any level we assume we're dealing with the right known location.
--
-- If at a given level there are multiple containing locations, we count a match if any holonym matches any
-- containing location, and a mismatch only if a holonym exists of the same placetype that doesn't match any
-- containing location.
local containers_mismatch = false
for containers in m_locations.iterate_containers(group, key, spec) do
insert(container_trail, containers)
local match_at_level = false
local mismatch_at_level = false
for other_holonym_index, other_holonym in export.get_holonyms_to_check(place_desc,
holonym_index and holonym_index + 1 or nil) do
local other_source_holonym = other_holonym.augmented_from_holonym
if other_source_holonym and other_source_holonym.placetype == holonym_placetype and
other_source_holonym.unlinked_placename ~= holonym_placename then
-- Ignore holonyms added during the augmentation process for other holonyms of the same
-- placetype as the placetype of the holonym we're considering. See comment in
-- augment_holonyms_with_container() for why we do this.
-- continue; grrr, no 'continue' in Lua
else
local holonym_matches_at_level = false
local holonym_exists_with_same_placetype = false
for _, container in ipairs(containers) do
if not container.spec.no_check_holonym_mismatch then
local full_container_placename, elliptical_container_placename =
m_locations.key_to_placename(container.group, container.key)
local placetypes = container.spec.placetype
if type(placetypes) ~= "table" then
placetypes = {placetypes}
end
local placetype_equivs = {}
for _, pt in ipairs(placetypes) do
m_table.extend(placetype_equivs, export.get_placetype_equivs(pt))
end
local this_holonym_matches = export.get_equiv_placetype_prop_from_equivs(
placetype_equivs, function(placetype)
return other_holonym.placetype == placetype and
(other_holonym.unlinked_placename == full_container_placename or
other_holonym.unlinked_placename == elliptical_container_placename)
end
)
if this_holonym_matches then
holonym_matches_at_level = true
break
end
local this_holonym_exists_with_same_placetype = export.get_equiv_placetype_prop_from_equivs(
placetype_equivs, function(placetype)
return other_holonym.placetype == placetype
end
)
if this_holonym_exists_with_same_placetype then
-- We seem to have a mismatch at this level. But before we decide conclusively that this
-- is the case, check to see whether the putative mismatch is an alias and matches when
-- we resolve the alias.
for oh_group, oh_key, oh_spec, oh_container_trail in
export.iterate_matching_holonym_location {
holonym_placetype = other_holonym.placetype,
holonym_placename = other_holonym.unlinked_placename,
holonym_index = other_holonym_index,
place_desc = place_desc,
} do
local oh_full_placename, oh_elliptical_placename =
m_locations.key_to_placename(oh_group, oh_key)
if oh_full_placename == full_container_placename or
oh_elliptical_placename == elliptical_container_placename then
-- Alias matched when resolved.
this_holonym_matches = true
break
end
end
if this_holonym_matches then
-- Alias matched above when resolved.
holonym_matches_at_level = true
break
else
-- Not an alias, or doesn't match when resolved. We have a true mismatch.
holonym_exists_with_same_placetype = true
end
end
end
end
if holonym_matches_at_level then
match_at_level = true
break
end
if holonym_exists_with_same_placetype then
mismatch_at_level = true
end
end
end
if not match_at_level and mismatch_at_level then
containers_mismatch = true
break
end
end
if not containers_mismatch then
return group, key, spec, container_trail
end
end
end
end
--[==[
If the holonym in `data` (in the format as passed to a category handler) refers to a known location, find and return the
corresponding key, spec and group as well as the trail of ancestral containers. This is like
`iterate_matching_holonym_location()` but throws an error if more than one location matches. (An example where this
would happen is {{tl|place|en|neighborhood|city/Newcastle}}, because there are two known locations named Newcastle. To
fix this, specify additional following disambiguating holonyms, e.g.
{{tl|place|en|neighborhood|city/Newcastle|s/New South Wales}}.
]==]
function export.find_matching_holonym_location(data)
local all_found = {}
for group, key, spec, container_trail in export.iterate_matching_holonym_location(data) do
insert(all_found, {group, key, spec, container_trail})
end
if not all_found[1] then
return nil
elseif all_found[2] then
local holonym_placetype = data.holonym_placetype
if type(holonym_placetype) == "table" then
holonym_placetype = concat(holonym_placetype, ",")
end
local found_keys = {}
for _, found in ipairs(all_found) do
local _, key, _, _ = unpack(found)
insert(found_keys, key)
end
error(("Found multiple matching locations for holonym '%s/%s'; specify disambiguating context in the " ..
"containing holonyms: %s"):format(holonym_placetype, data.holonym_placename, dump(found_keys)))
else
return unpack(all_found[1])
end
end
------------------------------------------------------------------------------------------
-- Placename and placetype data --
------------------------------------------------------------------------------------------
--[==[ var:
This is a map from aliases to their canonical forms. Any placetypes appearing as keys here will be mapped to their
canonical forms in all respects, including the display form. Contrast entries in 'placetype_data' with a fallback, which
applies to categorization and other processes but not to display.
The most important aliases are for holonym placetypes, particularly those that occur often such as "country", "state",
"province" and the like. Particularly long placetypes that mostly occur as entry placetypes (e.g.
"census-designated place") can be given abbreviations, but it is generally preferred to spell out the entry placetype.
Note also that we purposely avoid certain abbreviations that would be ambiguous (e.g. "d", which could variously be
interpreted as "department", "district" or "division").
]==]
export.placetype_aliases = {
["acomm"] = "autonomous community",
["adr"] = "administrative region",
["adterr"] = "administrative territory", -- Pakistan
["aobl"] = "autonomous oblast",
["aokr"] = "autonomous okrug",
["ap"] = "autonomous province",
["apref"] = "autonomous prefecture",
["aprov"] = "autonomous province",
["ar"] = "autonomous region",
["arch"] = "archipelago",
["arep"] = "autonomous republic",
["aterr"] = "autonomous territory",
["atu"] = "autonomous territorial unit",
["bor"] = "borough",
["c"] = "country",
["can"] = "canton",
["carea"] = "council area",
["cc"] = "constituent country",
["cdblock"] = "community development block",
["cdep"] = "Crown dependency",
["CDP"] = "census-designated place",
["cdp"] = "census-designated place",
["clcity"] = "county-level city",
["co"] = "county",
["cobor"] = "county borough",
["colcity"] = "county-level city",
["coll"] = "collectivity",
["comm"] = "community",
["cont"] = "continent",
["contr"] = "continental region",
["contregion"] = "continental region",
["cpar"] = "civil parish",
["damun"] = "direct-administered municipality",
["dep"] = "dependency",
["department capital"] = "departmental capital",
["dept"] = "department",
["depterr"] = "dependent territory",
["dist"] = "district",
["distmun"] = "district municipality",
["div"] = "division",
["emp"] = "empire",
["fpref"] = "French prefecture",
["gov"] = "governorate",
["govnat"] = "governorate",
["home-rule city"] = "home rule city",
["home-rule municipality"] = "home rule municipality",
["inner-city area"] = "inner city area",
["ires"] = "Indian reservation",
["isl"] = "island",
["lbor"] = "London borough",
["lga"] = "local government area",
["lgarea"] = "local government area",
["lgd"] = "local government district",
["lgdist"] = "local government district",
["metbor"] = "metropolitan borough",
["metcity"] = "metropolitan city",
["metmun"] = "metropolitan municipality",
["mtn"] = "mountain",
["mun"] = "municipality",
["mundist"] = "municipal district",
["nonmetropolitan county"] = "non-metropolitan county",
["obl"] = "oblast",
["okr"] = "okrug",
["p"] = "province",
["par"] = "parish",
["parmun"] = "parish municipality",
["pen"] = "peninsula",
["plcity"] = "prefecture-level city",
["plcolony"] = "Polish colony",
["pref"] = "prefecture",
["prefcity"] = "prefecture-level city",
["preflcity"] = "prefecture-level city",
["prov"] = "province",
["r"] = "region",
["range"] = "mountain range",
["rcm"] = "regional county municipality",
["rcomun"] = "regional county municipality",
["rdist"] = "regional district",
["rep"] = "republic",
["rhrom"] = "rural hromada",
["riv"] = "river",
["rmun"] = "regional municipality",
["robor"] = "royal borough",
["romp"] = "Roman province",
["runit"] = "regional unit",
["rurmun"] = "rural municipality",
["s"] = "state",
["sar"] = "special administrative region",
["shrom"] = "settlement hromada",
["spref"] = "subprefecture",
["sprefcity"] = "sub-prefectural city",
["sprovcity"] = "subprovincial city",
["submet city"] = "sub-metropolitan city",
["submetropolitan city"] = "sub-metropolitan city",
["sub-prefecture-level city"] = "sub-prefectural city",
["sub-provincial city"] = "subprovincial city",
["sub-provincial district"] = "subprovincial district",
["terr"] = "territory",
["terrauth"] = "territorial authority",
["twp"] = "township",
["twpmun"] = "township municipality",
["uauth"] = "unitary authority",
["ucomm"] = "unincorporated community",
["udist"] = "unitary district",
["uhrom"] = "urban hromada",
["uterr"] = "union territory",
["utwpmun"] = "united township municipality",
["val"] = "valley",
["vdc"] = "village development committee",
["vil"] = "village",
["voi"] = "voivodeship",
["wcomm"] = "Welsh community",
-- Chinese capital/seat types
["首都"] = "capital",
["省會"] = "provincial capital",
["首府"] = "regional capital",
["縣治"] = "county seat",
-- Chinese placetype name aliases (map to English equivalents so they inherit preposition, display handler, etc.)
["國家"] = "country",
["構成國"] = "constituent country",
["大洲"] = "continent",
["大洲地區"] = "continental region",
["洲"] = "continent",
["帝國"] = "empire",
["共和國"] = "republic",
["邦"] = "state",
["州"] = "state",
["省"] = "province",
["郡"] = "county",
["縣"] = "county",
["城市"] = "city",
["市"] = "city",
["區"] = "district",
["鄉"] = "township",
["鎮"] = "town",
["村"] = "village",
["社區"] = "community",
["島"] = "island",
["島嶼"] = "island",
["群島"] = "archipelago",
["半島"] = "peninsula",
["山"] = "mountain",
["山脈"] = "mountain range",
["河"] = "river",
["谷"] = "valley",
["山谷"] = "valley",
["特別行政區"] = "special administrative region",
["直轄市"] = "direct-administered municipality",
["地級市"] = "prefecture-level city",
["縣級市"] = "county-level city",
["副省級市"] = "subprovincial city",
["副地級市"] = "sub-prefectural city",
["市轄區"] = "municipal district",
["自治區"] = "autonomous region",
["盟"] = "aimag",
["蘇木"] = "sum",
["新區"] = "new area",
["國家級新區"] = "state-level new area",
["副省級市轄區"] = "subprovincial district",
["大行政區"] = "greater administrative region",
["行政村"] = "administrative village",
["縣轄市"] = "county-administered city",
["山地原住民鄉"] = "mountain indigenous township",
["山地原住民區"] = "mountain indigenous district",
["特別區"] = "special ward",
["自治省"] = "autonomous province",
["自治州"] = "autonomous prefecture",
["自治共和國"] = "autonomous republic",
["自治市"] = "borough",
["屬地"] = "dependent territory",
["海外領地"] = "dependent territory",
["領地"] = "territory",
["聯邦屬地"] = "union territory",
["皇家屬地"] = "Crown dependency",
["教區"] = "parish",
["堂區"] = "parish",
["民政教區"] = "civil parish",
["廣域市"] = "metropolitan city",
["非建制地區"] = "unincorporated community",
["人口普查指定地區"] = "census-designated place",
["印第安保留地"] = "Indian reservation",
["羅馬行省"] = "Roman province",
["行省"] = "province",
["單一管理區"] = "unitary authority",
["地方行政區"] = "local government area",
-- Additional polities
["城邦"] = "city-state",
["王國"] = "kingdom",
["聯邦"] = "federation",
["邦聯"] = "confederation",
["君主國"] = "monarchy",
["公國"] = "principality",
["汗國"] = "khanate",
["酋長國"] = "emirate",
["微型國家"] = "microstate",
["殖民地"] = "colony",
["聯邦主體"] = "federal subject",
["自由邦"] = "commonwealth",
["加盟共和國"] = "constituent republic",
["聯邦特區"] = "federal district",
["獨立市"] = "independent city",
-- Admin divisions
["都道府縣"] = "prefecture",
["議會地區"] = "council area",
["都會區"] = "metropolitan borough",
["郡市"] = "county borough",
-- Settlements
["聚落"] = "settlement",
["村落"] = "locality",
["居民點"] = "populated place",
["街區"] = "neighborhood",
["郊區"] = "suburb",
["鬼城"] = "ghost town",
-- Geographic features
["大洋"] = "ocean",
["海"] = "sea",
["湖"] = "lake",
["灣"] = "bay",
["海灣"] = "gulf",
["峽灣"] = "fjord",
["海峽"] = "strait",
["水道"] = "channel",
["水庫"] = "reservoir",
["溪"] = "stream",
["泉"] = "spring",
["溫泉"] = "hot spring",
["支流"] = "tributary",
["火山"] = "volcano",
["高原"] = "plateau",
["沙漠"] = "desert",
["海灘"] = "beach",
["丘"] = "hill",
["山口"] = "mountain pass",
["岬角"] = "headland",
["角"] = "cape",
["沙嘴"] = "spit",
["環礁"] = "atoll",
["小島"] = "islet",
-- Geographic/cultural regions
["地理文化區域"] = "geographic and cultural area",
["地理區域"] = "geographic region",
["歷史地區"] = "historical region",
["文化地區"] = "cultural region",
-- Man-made structures
["機場"] = "airport",
["港口"] = "port",
["公路"] = "highway",
["高速公路"] = "motorway",
["公園"] = "park",
["國家公園"] = "national park",
["大學"] = "university",
["城堡"] = "castle",
}
local no_link_def_article = {link = false, article = "the"}
local no_link_no_article = {link = false, article = false}
--[==[ var:
These qualifiers can be prepended onto any placetype and will be handled correctly. For example, the placetype
`large city` will be displayed as `large <nowiki>[[city]]</nowiki>` and categorized as if `city` were specified. If the
value in the following table is a string, the qualifier will display according to the string. If the value is `true`,
the qualifier will be linked to its corresponding Wiktionary entry. If the value is `false`, the qualifier will not be
linked but will appear as-is. Note that these qualifiers do not override placetypes with entries elsewhere that contain
those same qualifiers. For example, the entry for `inland sea` in `placetype_data` will apply in preference to treating
`inland sea` as equivalent to `sea`.
]==]
export.placetype_qualifiers = {
-- generic qualifiers
["huge"] = false,
["tiny"] = false,
["large"] = false,
["big"] = false,
["mid-size"] = false,
["mid-sized"] = false,
["small"] = false,
["sizable"] = false,
["important"] = false,
["long"] = false,
["short"] = false,
["major"] = false,
["minor"] = false,
["high"] = false,
["tall"] = false,
["low"] = false,
["left"] = false, -- left tributary
["right"] = false, -- right tributary
["modern"] = false, -- for use in opposition to "ancient" in another definition
-- "former" qualifiers
["abandoned"] = true,
["ancient"] = true,
["deserted"] = true,
["extinct"] = true,
["former"] = false,
["historic"] = "historical",
["historical"] = true,
["medieval"] = true,
["mediaeval"] = true,
["ruined"] = true,
["traditional"] = true,
-- sea qualifiers
["coastal"] = true,
["inland"] = true, -- note, we also have an entry in placetype_data for 'inland sea' to get a link to [[inland sea]]
["maritime"] = true,
["overseas"] = true,
["seaside"] = true,
["beachfront"] = true,
["beachside"] = true,
["riverside"] = true,
-- lake qualifiers
["freshwater"] = true,
["saltwater"] = true,
["endorheic"] = true,
["oxbow"] = true,
["ox-bow"] = "[[oxbow]]", -- [[ox-bow]] is a red link
["tidal"] = true,
-- land qualifiers
["hilltop"] = true,
["hilly"] = true,
["insular"] = true,
["peninsular"] = true,
["chalk"] = true,
["karst"] = true,
["limestone"] = true,
["mountainous"] = true,
["mountaintop"] = true,
["alpine"] = true,
["volcanic"] = true, -- for an island
-- political status qualifiers
["autonomous"] = true,
["incorporated"] = true,
["special"] = true,
["unincorporated"] = true,
["coterminous"] = true,
-- monetary status/etc. qualifiers
["fashionable"] = true,
["wealthy"] = true,
["affluent"] = true,
["declining"] = true,
-- city vs. rural qualifiers
["urban"] = true,
["suburban"] = true,
["exurban"] = true,
["outlying"] = true,
["remote"] = true,
["rural"] = true,
["outback"] = true,
["inner"] = false,
["inner-city"] = true,
["central"] = false,
["outer"] = false,
-- land use qualifiers
["residential"] = true,
["agricultural"] = true,
["business"] = true,
["commercial"] = true,
["industrial"] = true,
-- business use qualifiers
["railroad"] = true,
["railway"] = true,
["farming"] = true,
["fishing"] = true,
["mining"] = true,
["logging"] = true,
["cattle"] = true,
-- tourism use qualifiers
["resort"] = true, -- note, we also have 'resort city' and 'resort town', that take precedecne
["spa"] = true, -- note, we also have 'spa city' and 'spa town', that take precedecne
["ski"] = true, -- note, we also have 'ski resort city' and 'ski resort town', that take precedecne
-- religious qualifiers
["holy"] = true,
["sacred"] = true,
["religious"] = true,
["secular"] = true,
-- qualifiers for nonexistent places
["claimed"] = false,
["fictional"] = true,
["legendary"] = true,
["mythical"] = true,
["mythological"] = true,
-- directional qualifiers
["northern"] = false,
["southern"] = false,
["eastern"] = false,
["western"] = false,
["north"] = false,
["south"] = false,
["east"] = false,
["west"] = false,
["northeastern"] = false,
["southeastern"] = false,
["northwestern"] = false,
["southwestern"] = false,
["northeast"] = false,
["southeast"] = false,
["northwest"] = false,
["southwest"] = false,
-- seasonal qualifiers
["summer"] = true, -- e.g. for 'summer capital'
["winter"] = true,
-- legal status qualifiers
-- FIXME: Two-word qualifiers don't work yet. But you can enter "de-facto" and it's canonicalized to [[de facto]].
["official"] = true,
["unofficial"] = true,
["de facto"] = true, -- 'de facto capital'
["de-facto"] = "[[de facto]]", -- [[de-facto]] is a red link
["de jure"] = true, -- 'de jure capital'
["de-jure"] = "[[de jure]]", -- [[de-jure]] is a red link
-- NOTE: 'unrecognized/unrecognised' are handled as placetypes 'unrecognized country', 'unrecognized state'
-- misc. qualifiers
["planned"] = true,
["chartered"] = true,
["landlocked"] = true,
["uninhabited"] = true,
-- superlative qualifiers
["first"] = no_link_def_article,
["second"] = no_link_def_article, -- for "second largest" etc.
["third"] = no_link_def_article,
["fourth"] = no_link_def_article,
["last"] = no_link_def_article,
["only"] = no_link_def_article,
["sole"] = no_link_def_article,
["main"] = no_link_def_article,
["largest"] = no_link_def_article,
["biggest"] = no_link_def_article,
["smallest"] = no_link_def_article,
["shortest"] = no_link_def_article,
["longest"] = no_link_def_article,
["tallest"] = no_link_def_article,
["highest"] = no_link_def_article,
["lowest"] = no_link_def_article,
["leftmost"] = no_link_def_article,
["rightmost"] = no_link_def_article,
["innermost"] = no_link_def_article,
["outermost"] = no_link_def_article,
["northernmost"] = no_link_def_article,
["southernmost"] = no_link_def_article,
["westernmost"] = no_link_def_article,
["easternmost"] = no_link_def_article,
["northwesternmost"] = no_link_def_article,
["southwesternmost"] = no_link_def_article,
["northeasternmost"] = no_link_def_article,
["southeasternmost"] = no_link_def_article,
-- several/various
["several"] = no_link_no_article,
["various"] = no_link_no_article,
["numerous"] = no_link_no_article,
["multiple"] = no_link_no_article,
["many"] = no_link_no_article,
["other"] = no_link_no_article,
}
--[==[ var:
In this table, the key qualifiers should be treated the same as the value qualifiers for categorization purposes. This
is overridden by `placetype_data` and `qualifier_to_placetype_equivs`.
]==]
export.former_qualifiers = {
["abandoned"] = {"FORMER"},
["ancient"] = {"ANCIENT", "FORMER"},
["former"] = {"FORMER"},
["extinct"] = {"FORMER"},
["historic"] = {"FORMER"},
["historical"] = {"FORMER"},
["medieval"] = {"ANCIENT", "FORMER"},
["mediaeval"] = {"ANCIENT", "FORMER"},
["ruined"] = {"ANCIENT", "FORMER"},
["traditional"] = {"FORMER"},
}
--[==[ var:
In this table, any placetypes containing these qualifiers that do not occur in `placetype_data` should be mapped to the
specified placetypes for categorization purposes. Entries here are overridden by `placetype_data`.
]==]
export.qualifier_to_placetype_equivs = {
["fictional"] = "fictional location",
["legendary"] = "mythological location",
["mythical"] = "mythological location",
["mythological"] = "mythological location",
-- For e.g. Taiwan as a "claimed province" of China; parts of Belize as claimed by Guatemala; various islands
-- claimed by various parties in East Asia. FIXME: We should conditionalize on what is being claimed since there are
-- also claimed capitals, e.g. Israel and Palestine claim Jerusalem as their capital.
["claimed"] = "claimed political division",
}
--[==[ var:
Mapping from placetypes to the corresponding plural category-only placetype for a capital of that placetype. The reverse
mapping also exists.
]==]
export.placetype_to_capital_cat = {
["autonomous community"] = "autonomous community capitals",
["canton"] = "cantonal capitals",
["comarca"] = "comarca capitals",
["country"] = "national capitals",
-- The following are not obviously different from 'county seats' but the latte terminology is used in the US.
["county"] = "county capitals",
["department"] = "departmental capitals",
["district"] = "district capitals",
["division"] = "division capitals",
["emirate"] = "emirate capitals",
["governorate"] = "governorate capitals",
["hromada"] = "hromada capitals",
["krai"] = "krai capitals",
["metropolitan city"] = "metropolitan city capitals",
["municipality"] = "municipal capitals",
["oblast"] = "oblast capitals",
["okrug"] = "okrug capitals",
["prefecture"] = "prefectural capitals",
["province"] = "provincial capitals",
["raion"] = "raion capitals",
["regency"] = "regency capitals",
["region"] = "regional capitals",
["regional unit"] = "regional unit capitals",
["republic"] = "republic capitals",
["state"] = "state capitals",
["territory"] = "territorial capitals",
["voivodeship"] = "voivodeship capitals",
}
--[==[ var:
This contains placenames that should be preceded by an article (almost always "the"). '''NOTE''': There are multiple
ways that placenames can come to be preceded by "the":
# Listed here.
# Given in [[Module:place/locations]] with an initial "the". All such placenames are added to this map by the code
just below the map.
# The placetype of the placename has `holonym_use_the = true` in its placetype_data.
# A regex in placename_the_re matches the placename.
Note that "the" is added only before the first holonym in a place description.
]==]
export.placename_article = {
-- This should only contain info that can't be inferred from [[Module:place/locations]].
["archipelago"] = {
["Cyclades"] = "the",
["Dodecanese"] = "the",
},
["country"] = {
["Holy Roman Empire"] = "the",
},
["empire"] = {
["Holy Roman Empire"] = "the",
},
["island"] = {
["North Island"] = "the",
["South Island"] = "the",
},
["region"] = {
["Balkans"] = "the",
["Russian Far East"] = "the",
["Caribbean"] = "the",
["Caucasus"] = "the",
["Middle East"] = "the",
["New Territories"] = "the",
["North Caucasus"] = "the",
["South Caucasus"] = "the",
["West Bank"] = "the",
["Gaza Strip"] = "the",
},
["valley"] = {
["San Fernando Valley"] = "the",
},
}
--[==[ var:
Regular expressions to apply to determine whether we need to put 'the' before a holonym. The key "*" applies to all
holonyms, otherwise only the regexes for the holonym's placetype apply.
]==]
export.placename_the_re = {
-- We don't need entries for peninsulas, seas, oceans, gulfs or rivers
-- because they have holonym_use_the = true.
["*"] = {"^Isle of ", " Islands$", " Mountains$", " Empire$", " Country$", " Region$", " District$", "^City of "},
["bay"] = {"^Bay of "},
["lake"] = {"^Lake of "},
["country"] = {"^Republic of ", " Republic$"},
["republic"] = {"^Republic of ", " Republic$"},
["region"] = {" [Rr]egion$"},
["river"] = {" River$"},
["local government area"] = {"^Shire of "},
["county"] = {"^Shire of "},
["Indian reservation"] = {" Reservation", " Nation"},
["tribal jurisdictional area"] = {" Reservation", " Nation"},
}
--[==[ var:
If any of the following holonyms are present, the associated holonyms are automatically added to the end of the list of
holonyms for categorization (but not display) purposes.
]==]
export.cat_implications = {
["region"] = {
["東歐"] = {"continent/歐洲"},
["中歐"] = {"continent/歐洲"},
["西歐"] = {"continent/歐洲"},
["南歐"] = {"continent/歐洲"},
["北歐"] = {"continent/歐洲"},
["東北歐"] = {"continent/歐洲"},
["東南歐"] = {"continent/歐洲"},
["北高加索"] = {"continent/歐洲"},
["南高加索"] = {"continent/亞洲"},
["南亞"] = {"continent/亞洲"},
["東亞"] = {"continent/亞洲"},
["中亞"] = {"continent/亞洲"},
["西亞"] = {"continent/亞洲"},
["東南亞"] = {"continent/亞洲"},
["北亞"] = {"continent/亞洲"},
["安納托利亞"] = {"continent/亞洲"},
["小亞細亞"] = {"continent/亞洲"},
["美索不達米亞"] = {"continent/亞洲"},
["北非"] = {"continent/非洲"},
["中非"] = {"continent/非洲"},
["西非"] = {"continent/非洲"},
["東非"] = {"continent/非洲"},
["南非"] = {"continent/非洲"},
["中美洲"] = {"continent/中美洲"},
["加勒比地區"] = {"continent/北美洲"},
["玻里尼西亞"] = {"continent/大洋洲"},
["密克羅尼西亞"] = {"continent/大洋洲"},
["美拉尼西亞"] = {"continent/大洋洲"},
["西伯利亞"] = {"country/俄羅斯", "continent/亞洲"},
["俄羅斯遠東地區"] = {"country/俄羅斯", "continent/亞洲"},
["南威爾士"] = {"constituent country/威爾士", "continent/歐洲"},
["巴爾幹半島"] = {"continent/歐洲"},
["約旦河西岸"] = {"country/巴勒斯坦", "continent/亞洲"},
["加薩"] = {"country/巴勒斯坦", "continent/亞洲"},
["加薩走廊"] = {"country/巴勒斯坦", "continent/亞洲"},
}
}
------------------------------------------------------------------------------------------
-- Category and display handlers --
------------------------------------------------------------------------------------------
function export.zh_cat_de(zh_name_raw)
local canonical = type(zh_name_raw) == "table" and zh_name_raw[1] or zh_name_raw
if not canonical then return "" end
if mw.ustring.len(canonical) == 1 then
return export.zh_strings.de_particle
end
-- "X和Y" compound names (e.g. 縣和縣級市) need 的 when X is single char
if mw.ustring.match(canonical, "^.和") then
return export.zh_strings.de_particle
end
return ""
end
local zh_cat_de = export.zh_cat_de
local function city_type_cat_handler(data)
local entry_placetype = data.entry_placetype
local generic_before_non_cities = export.get_placetype_prop(entry_placetype, "generic_before_non_cities")
if not generic_before_non_cities then
internal_error("city_type_cat_handler called on placetype %s that doesn't have a `generic_before_non_cities`" ..
" setting", entry_placetype)
end
local plural_entry_placetype = export.pluralize_placetype(entry_placetype)
local group, key, spec, container_trail = export.find_matching_holonym_location(data)
if group and not spec.is_former_place and not spec.is_city then
local zh_name_raw = export.get_zh_placetype_name(entry_placetype)
local zh_entry_name = zh_name_raw and export.format_zh_name(zh_name_raw) or plural_entry_placetype
local de = zh_cat_de(zh_name_raw)
local retcats = {export.get_prefixed_key(key, spec) .. de .. zh_entry_name}
if container_trail[1] and not spec.no_container_cat then
for _, container in ipairs(container_trail[1]) do
insert(retcats, export.get_prefixed_key(container.key, container.spec) .. de .. zh_entry_name)
end
end
return retcats
end
end
local function capital_city_cat_handler(data, non_city)
local holonym_placetype, holonym_placename, holonym_index, place_desc =
data.holonym_placetype, data.holonym_placename, data.holonym_index, data.place_desc
-- The first time we're called we want to return something; otherwise we will be called for later-mentioned
-- holonyms, which can result in wrongly classifying into e.g. `National capitals`. Simulate the loop in
-- find_placetype_cat_specs() over holonyms so we get the proper `Cities in ...` categories as well as the capital
-- category/categories we add below.
local retcats
if not non_city and place_desc.holonyms then
for h_index, holonym in export.get_holonyms_to_check(place_desc, holonym_index) do
local h_placetype, h_placename = holonym.placetype, holonym.unlinked_placename
retcats = city_type_cat_handler {
entry_placetype = "city",
holonym_placetype = h_placetype,
holonym_placename = h_placename,
holonym_index = h_index,
place_desc = place_desc,
}
if retcats then
break
end
end
end
if not retcats then
retcats = {}
end
-- Now find the appropriate capital-type category for the placetype of the holonym, e.g. 'State capitals'. If we
-- recognize the holonym among the known holonyms in [[Module:place/locations]], also add a category like 'State
-- capitals of the United States'. Truncate e.g. 'autonomous region' to 'region', 'union territory' to 'territory'
-- when looking up the type of capital category, if we can't find an entry for the holonym placetype itself (there's
-- an entry for 'autonomous community').
-- ZH: look up Chinese capital label by holonym placetype ("首都", "省會", "首府", etc.)
local zh_capital_name = export.zh_capital_label_by_holonym_type[holonym_placetype]
if not zh_capital_name then
zh_capital_name = export.zh_capital_label_by_holonym_type[holonym_placetype:gsub("^.* ", "")]
end
local de = zh_capital_name and (mw.ustring.len(zh_capital_name) == 1 and export.zh_strings.de_particle or "") or export.zh_strings.de_particle
if zh_capital_name then
local inserted_specific_variant_cat = false
if holonym_index then
-- Now find the first recognized holonym location. We don't stop when :also is seen because of the common pattern
-- where we use :also to specify that a given city is the capital at multiple surrounding levels.
local matching_group, matching_key, matching_spec, matching_container_trail, matching_holonym_index
for h_index = holonym_index, #place_desc.holonyms do
if place_desc.holonyms[h_index].placetype then
matching_group, matching_key, matching_spec, matching_container_trail = export.find_matching_holonym_location {
holonym_placetype = place_desc.holonyms[h_index].placetype,
holonym_placename = place_desc.holonyms[h_index].unlinked_placename,
holonym_index = h_index,
place_desc = place_desc,
}
if matching_group then
matching_holonym_index = h_index
break
end
end
end
if matching_holonym_index == holonym_index then
if matching_container_trail[1] and not matching_spec.no_container_cat then
for _, container in ipairs(matching_container_trail[1]) do
-- ZH: "美國的首都" (location_key + 的 + zh_capital_name)
insert(retcats, export.get_prefixed_key(container.key, container.spec) .. de .. zh_capital_name)
inserted_specific_variant_cat = true
end
end
elseif matching_holonym_index then
-- Check to make sure that the holonym placetype we were called on is listed among the
-- divtypes of the location we found.
local function insert_specific_variant_if_possible(key, spec)
return export.get_equiv_placetype_prop(holonym_placetype, function(pt)
local plural_holonym_placetype = export.pluralize_placetype(pt)
local saw_matching_div
if spec.divs then
local divs = spec.divs
if type(divs) ~= "table" then
divs = {divs}
end
for _, div in ipairs(divs) do
if type(div) ~= "table" then
div = {type = div}
end
if plural_holonym_placetype == div.type then
saw_matching_div = true
break
end
end
end
if saw_matching_div then
insert(retcats, export.get_prefixed_key(key, spec) .. de .. zh_capital_name)
return true
end
return false
end)
end
if insert_specific_variant_if_possible(matching_key, matching_spec) then
inserted_specific_variant_cat = true
elseif not matching_spec.no_container_cat then
for _, containers in ipairs(matching_container_trail) do
local saw_no_container_cat = false
for _, container in ipairs(containers) do
if insert_specific_variant_if_possible(container.key, container.spec) then
inserted_specific_variant_cat = true
break
end
saw_no_container_cat = saw_no_container_cat or container.spec.no_container_cat
end
if inserted_specific_variant_cat or saw_no_container_cat then
break
end
end
end
end
else
-- This happens when in an invocation like {{place|en|capital city|s/Haryana,Punjab}} for
-- [[Chandigarh]]. We fall back to older code that doesn't depend on the holonym index existing.
-- FIXME: This may not be necessary. In the example just given, when processing Haryana we add to
-- [[:Category:en:State capitals of India]], and nothing extra gets added when processing Punjab.
-- Possibly we can just skip this case entirely.
local group, key, spec, container_trail = export.find_matching_holonym_location(data)
if group and container_trail[1] and not spec.no_container_cat then
for _, container in ipairs(container_trail[1]) do
insert(retcats, export.get_prefixed_key(container.key, container.spec) .. de .. zh_capital_name)
inserted_specific_variant_cat = true
end
end
end
if not inserted_specific_variant_cat then
insert(retcats, zh_capital_name)
end
else
-- We didn't recognize the holonym placetype; fall back to generic capital label.
insert(retcats, "首都")
end
return retcats
end
--[=[
This is invoked specially for all placetypes (see the `*` placetype key at the bottom of `placetype_data`). This is used
in two ways:
# To add pages to generic holonym categories like [[:Category:en:Places in Merseyside, England]] (and
[[:Category:en:Places in England]]) for any pages that have `co/Merseyside` as their holonym.
# To categorize demonyms in bare placename categories like [[:Category:en:Merseyside, England]] if the demonym
description mentions `co/Merseyside` and doesn't mention a more specific placename that also has a category. (In this
case there are none, but we can have demonyms at multiple levels, e.g. in France for individual villages, departments,
administrative regions, and for the entire country, and for example we only want to categorize a demonym into
[[:Category:France]] if no more specific category applies.) Unlike when invoked from {{tl|place}}, a demonym
invocation only adds the most specific holonym category and not the category of any containing polity (hence if we
add [[:Category:en:Merseyside, England]] we won't also add [[:Category:England]]).
This code also handles cities; e.g. for the first use case above, it would be used to add a page that has `city/Boston`
as a holonym to [[:Category:en:Places in Boston]], along with [[:Category:en:Places in Massachusetts, USA]] and
[[:Category:en:Places in the United States]]. The city handler tries to deal with the possibility of multiple cities
having the same name. For example, the code in [[Module:place/locations]] knows about the city of [[Columbus]],
[[Ohio]], which has containing polities `Ohio` (a state) and `the United States` (a country). If either containing
polity is mentioned, the handler proceeds to return the key `Columbus` (along with `Ohio, USA` and `the United States`).
Otherwise, if any other state or country is mentioned, the handler returns nothing, and otherwise it assumes the
mentioned city is the one we're considering and returns `Columbus` etc. This works correctly if the place only mentions
Ohio and a holonym for a Columbus in a different country is encountered, because of the function
`augment_holonyms_with_container`, which adds the US as a holonym when Ohio is encountered.
The single parameter `data` is as in category handlers. The return value is a list of categories (without the preceding
language code).
]=]
local function generic_place_cat_handler(data)
local from_demonym = data.from_demonym
local retcats = {}
local function insert_retkey(key, spec)
if from_demonym then
insert(retcats, key)
else
-- ZH: "法國地點" (location_key + 地點)
insert(retcats, export.get_prefixed_key(key, spec) .. "地點")
end
end
local group, key, spec, container_trail = export.find_matching_holonym_location(data)
if group then
if not spec.no_generic_place_cat then
-- This applies to continents and continental regions.
insert_retkey(key, spec)
end
-- Categorize both in key, and in the larger location(s) that the key is part of, e.g. [[Hirakata]] goes in
-- both [[Category:Places in Osaka Prefecture, Japan]] and [[Category:Places in Japan]]. But not when
-- no_container_cat is set (e.g. for 'United Kingdom').
if not spec.no_container_cat then
for _, container_set in ipairs(container_trail) do
local stop_adding_containers = false
for _, container in ipairs(container_set) do
if not container.spec.no_generic_place_cat then
insert_retkey(container.key, container.spec)
end
if container.spec.no_container_cat then
stop_adding_containers = true
end
end
if stop_adding_containers then
break
end
end
end
return retcats
end
end
--[==[
Special category handler run for all placetypes that checks for specified division placetypes of known locations and
categorizes appropriately.
]==]
function export.political_division_cat_handler(data)
if data.from_demonym then
return
end
local group, key, spec, container_trail = export.find_matching_holonym_location(data)
if group then
local divlists = {}
if spec.divs then
insert(divlists, spec.divs)
end
if spec.addl_divs then
insert(divlists, spec.addl_divs)
end
for _, divlist in ipairs(divlists) do
if type(divlist) ~= "table" then
divlist = {divlist}
end
for _, div in ipairs(divlist) do
if type(div) == "string" then
div = {type = div}
end
local sgdiv = export.maybe_singularize_placetype(div.type) or div.type
local prep = div.prep or "of"
local cat_as = div.cat_as or div.type
if type(cat_as) ~= "table" then
cat_as = {cat_as}
end
if not export.placetype_data[sgdiv] then
internal_error("Placetype %s associated with known location key %s and data %s not found in " ..
"`placetype_data`", sgdiv, key, spec)
end
if sgdiv == data.entry_placetype then
local retcats = {}
for _, pt_cat in ipairs(cat_as) do
if type(pt_cat) == "string" then pt_cat = {type = pt_cat} end
local cat_key = pt_cat.type
local zh_n_raw = export.get_zh_placetype_name(cat_key, data.holonym_placetype)
if not zh_n_raw then
local sg = export.maybe_singularize_placetype(cat_key)
if sg then zh_n_raw = export.get_zh_placetype_name(sg, data.holonym_placetype) end
end
local zh_n = zh_n_raw and export.format_zh_name(zh_n_raw) or cat_key
insert(retcats, export.get_prefixed_key(key, spec) .. zh_cat_de(zh_n_raw or zh_n) .. zh_n)
end
return retcats
end
end
end
end
end
--[==[
This is used to add pages to "bare" categories like [[:Category:en:Georgia, USA]] for `[[Georgia]]` and any
foreign-language terms that are translations of the state of Georgia. We look at the page title (or its overridden value
in {{para|pagename}}) as well as the glosses in {{para|t}}/{{para|t2}} etc., various extra-info values such as the
modern names in {{para|modern}}, and any values specified using a form-of directive. We need to pay attention to the
entry placetypes specified so we don't overcategorize; e.g. the US state of Georgia is `[[Джорджия]]` in Russian but the
country of Georgia is `[[Грузия]]`, and if we just looked for matching names, we'd get both Russian terms categorized
into both [[:Category:ru:Georgia, USA]] and [[:Category:ru:Georgia]]. We also need to check the containing holonyms to
make sure there isn't a mismatch (so we don't e.g. categorize Newark, Delaware in [[:Category:en:Newark]], which is
intended for Newark, New Jersey).
]==]
function export.get_bare_categories(args, overall_place_spec)
local bare_cats = {}
local place_descs = overall_place_spec.descs
local possible_placetypes_by_place_desc = {}
for i, place_desc in ipairs(place_descs) do
possible_placetypes_by_place_desc[i] = {}
for _, placetype in ipairs(place_desc.placetypes) do
if not export.placetype_is_ignorable(placetype) then
local equivs = export.get_placetype_equivs(placetype, {register_former_as_non_former = true})
for _, equiv in ipairs(equivs) do
insert(possible_placetypes_by_place_desc[i], equiv.placetype)
end
end
end
end
local function check_term(term)
-- Treat Wikipedia links like local ones.
term = term:gsub("%[%[w:", "[["):gsub("%[%[wikipedia:", "[[")
term = export.remove_links_and_html(term)
term = term:gsub("^the ", "")
for i, place_desc in ipairs(place_descs) do
-- Iterate over all matching locations in case there are multiple, as with Delhi defined as
-- {{place|en|megacity/and/union territory|c/India|containing the national capital [[New Delhi]]}}.
for group, key, spec, container_trail in export.iterate_matching_holonym_location {
holonym_placetype = possible_placetypes_by_place_desc[i],
holonym_placename = term,
place_desc = place_desc,
} do
insert(bare_cats, key)
end
end
end
-- FIXME: Should we only do the following if the language is English (requires that the lang is passed in)?
-- We should always do it if `pagename` is given (as it is with {{tcl}}) but maybe not otherwise unless 1=en. There
-- are cases like [[Ankara]] = English name for capital of Turkey, but also the name in various languages for the
-- capital of Ghana (= English [[Accra]]). But this should get caught by mismatching the containing country. The
-- advantage of checking when the language isn't English is we catch those places that fail to give an English
-- translation but where the translation happens to be the same as the other-language spelling. However, I don't
-- know how often this situation occurs.
check_term(args.pagename or mw.loadData("Module:headword/data").pagename)
for _, t in ipairs(args.t) do
check_term(t)
end
local function check_termobj_list(terms)
for _, term in ipairs(terms) do
if term.eq then
check_term(term.eq)
end
if term.alt or term.term then
check_term(term.alt or term.term)
end
end
end
for _, extra_info_terms in ipairs(overall_place_spec.extra_info) do
local arg = extra_info_terms.arg
if arg == "modern" or arg == "now" or arg == "full" or arg == "short" then
check_termobj_list(extra_info_terms.terms)
end
end
for _, directive in ipairs(overall_place_spec.directives) do
check_termobj_list(directive.terms)
end
return bare_cats
end
--[==[
This is used to augment the holonyms associated with a place description with the containing polities. For example,
given the following:
`# {{tl|place|en|subprefecture|pref/Hokkaido}}.`
We auto-add Japan as another holonym so that the term gets categorized into [[:Category:Subprefectures of Japan]].
To avoid over-categorizing we need to check to make sure no other countries are specified as holonyms.
]==]
function export.augment_holonyms_with_container(place_descs)
for _, place_desc in ipairs(place_descs) do
if place_desc.holonyms then
-- This ends up containing a copy of the original holonyms, with the augmented holonyms inserted in their
-- appropriate position. We don't just put them at the end because some holonyms have use the `:also`
-- modifier, which causes category processing to restart at that point after generating categories for a
-- preceding holonym, and we don't want the preceding holonym's augmented holonyms interfering with
-- categorization of a later holonym. We proceed from right to left, and each time we augment, we copy
-- the holonyms with the augmented holonym(s) inserted appropriately and replace the place description's
-- holonyms with the augmented ones before the next iteration. The reason for this is so that e.g.
-- {{place|neighborhood|city/Birmingham|co/West Midlands|cc/England}} doesn't throw an error during the
-- augmentation process due to 'Birmingham' referring to two known locations (in England and Alabama). If
-- we go left to right, we will throw an ambiguity error on `city/Birmingham` because code to exclude
-- Birmingham, Alabama needs `c/United Kingdom` present (to cause a mismatch with `c/United States`),
-- which isn't yet present as the augmentation code hasn't gotten to `cc/England` yet. For similar
-- reasons, we need to include the augmented holonyms in the holonyms considered in the next iteration
-- rather than modifying the place description once at athe end.
for i = #place_desc.holonyms, 1, -1 do
local holonym = place_desc.holonyms[i]
if holonym.placetype and not export.placetype_is_ignorable(holonym.placetype) then
local group, key, spec, container_trail = export.find_matching_holonym_location {
holonym_placetype = holonym.placetype,
holonym_placename = holonym.unlinked_placename,
holonym_index = i,
place_desc = place_desc,
}
if group and container_trail[1] and not spec.no_auto_augment_container then
local augmented_holonyms = {}
for j = 1, i do
insert(augmented_holonyms, place_desc.holonyms[j])
end
for _, containers in ipairs(container_trail) do
local any_no_auto_augment_container = false
for _, container in ipairs(containers) do
any_no_auto_augment_container = any_no_auto_augment_container or
container.spec.no_auto_augment_container
local containing_type = container.spec.placetype
if type(containing_type) == "table" then
-- If the containing type is a list, use the first element as the canonical variant.
containing_type = containing_type[1]
end
local full_container_placename, elliptical_container_placename =
m_locations.key_to_placename(container.group, container.key)
-- Don't side-effect holonyms while processing them.
local new_holonym = {
-- By the time we run, the display has already been generated so we don't need to
-- set display_placename.
placetype = containing_type,
-- placename_to_key() for the group should correctly handle both full and elliptical
-- placenames, but the full placename seems less likely to be ambiguous. FIXME: We
-- should just store the key directly and use it when available to avoid having to
-- convert key to placename and back to key.
unlinked_placename = full_container_placename,
-- Indicate that this is an augmented holonym, and was derived from the specified
-- holonym. In iterate_matching_holonym_location(), we ignore augmented holonyms
-- derived from holonyms that are different from the holonym we're searching for but
-- of the same placetype. This is to correctly handle a situation like
-- {{place|river|dept/Ardèche,Gard,Vaucluse,Bouches-du-Rhône|c/France}}. Here,
-- `Ardèche` is in `r/Auvergne-Rhône-Alpes`, while `Gard` is in `r/Occitania` and
-- the other two are in `r/Provence-Alpes-Côte d'Azur`. Augmenting proceeds from
-- right to left, so after it adds `r/Provence-Alpes-Côte d'Azur` to
-- `Bouches-du-Rhône`, Vaucluse gets augmented correctly but `Gard` fails to match
-- in find_matching_holonym_location() because of the mismatch between augmented
-- `r/Provence-Alpes-Côte d'Azur` and actual `r/Occitania`. Similarly, all later
-- calls to find_matching_holonym_location() fail to match `Gard` (and likewise
-- `Ardèche`) against any known location. To deal with this, we mark augmented
-- holoynms as being augmented due to a source holonym, and when processing a given
-- holonym, ignore augmented holonyms from other holonyms of the same placetype.
-- The restriction to the same placetype is so that `Birmingham` still gets
-- correctly disambiguated to Birmingham, England in the example given above near
-- the top of this function, using the augmented holonym `c/United Kingdom` added by
-- the specified `cc/England` (whose placetype `constituent country` differs from
-- the placetype `city` of Birmingham).
augmented_from_holonym = holonym,
}
insert(augmented_holonyms, new_holonym)
-- But it is safe to modify other parts of the place_desc.
export.key_holonym_into_place_desc(place_desc, new_holonym)
end
if any_no_auto_augment_container then
break
end
end
for j = i + 1, #place_desc.holonyms do
insert(augmented_holonyms, place_desc.holonyms[j])
end
place_desc.holonyms = augmented_holonyms
end
end
end
end
end
end
-- Cat handler for district, areas, neighborhoods and suburbs. Districts are tricky because they can either be political
-- divisions or city neighborhoods. Areas similarly can be political divisions (rarely; specifically, in Kuwait), city
-- neighborhoods or larger geographical areas/regions. We handle this as follows:
-- (1) `placetype_data` cat entries for specific countries or country divisions take precedence over cat_handlers, so if
-- the user says {{tl|place|district|s/Maharashtra|c/India}}, we won't even be called because there is an entry that
-- categorizes into [[:Category|Districts of Maharashtra, India]].
-- (2) If we're called, we check the holonym we're called on to see if it is a recognized city, e.g. if we're called
-- using {{tl|place|district|city/Mumbai|s/Maharashtra|c/India}}. If so, we categorize under e.g.
-- [[:Category:Neighbourhoods of Mumbai]]. (Choosing the spelling "neighbourhoods" because we're in India.)
-- (3) If we're called and the holonym is not a recognized city, we check if the placetype has has_neighborhoods set.
-- If so, it's "city-like" and we categorize under the first containing polity that we recognize. For example, if
-- we're called using {{tl|place|district|town/Northampton|co/Hampshire|s/Massachusetts|c/US}}, we should recognize
-- town as "city-like" and categorize under [[:Category:Neighborhoods in Massachusetts]]. (Note "in" not "of", and
-- note the spelling "neighborhoods" because we're in the US.)
-- (4) If the holonym is not city-like, we do nothing. If there's a city or city-like placetype farther up (e.g. we're
-- called as {{tl|place|district|ward/Foo|mun/Bar|...}}), we will handle the city-like entity according to (2) or
-- (3) when called on that holonym. Otherwise either the categorization in (1) takes place or there's no
-- categorization.
local function district_neighborhood_cat_handler(data)
local function get_plural_entry_placetype(location_spec, container_trail)
if data.entry_placetype == "suburb" then
return "Suburbs"
else
-- Check for `british_spelling` setting on the spec itself or any container.
local uses_british_spelling = location_spec.british_spelling
if uses_british_spelling == nil and container_trail then
for _, container_set in ipairs(container_trail) do
local must_outer_break = false
for _, container in ipairs(container_set) do
if container.spec.british_spelling ~= nil then
uses_british_spelling = container.spec.british_spelling
must_outer_break = true
break
end
end
if must_outer_break then
break
end
end
end
return uses_british_spelling and "Neighbourhoods" or "Neighborhoods"
end
end
-- First check the immediate holonym to see if it's a city or a city-like top-level entity (Hong Kong, Bonaire,
-- etc.)
local group, key, spec, container_trail = export.find_matching_holonym_location(data)
if group and not spec.is_former_place and spec.is_city then
return {get_plural_entry_placetype(spec, container_trail) .. " of " .. export.get_prefixed_key(key, spec)}
end
-- If the entry placetype is neighbo(u)rhood, assume it is a neighborhood even if there isn't a city-like
-- entity father up the chain. (E.g. due to a mistaken use of m/ instead of mun/ for municipality.)
local has_neighborhoods
local entry_placetype = data.entry_placetype
if entry_placetype == "neighborhood" or entry_placetype == "neighbourhood" or entry_placetype == "suburb" then
has_neighborhoods = true
else
-- Otherwise, make sure the current holonym is city-like.
has_neighborhoods = export.get_equiv_placetype_prop(data.holonym_placetype, function(pt)
return export.get_placetype_prop(pt, "has_neighborhoods")
end, {continue_on_nil_only = true})
end
if has_neighborhoods then
-- Loop up the holonyms, looking for city and city-like entities in case of e.g. [[Sepulveda]] written
-- {{place|en|neighborhood|valley/San Fernando Valley|city/Los Angeles|s/California|c/USA}}
-- but also look for a recognizable poldiv, and if so categorize as "Neighborhoods in POLDIV". We need
-- to start with the current holonym, which is especially important for neighborhoods and suburbs that
-- may have the first holonym be a recognizable province, etc. but can't hurt otherwise. (Previously
-- we skipped the first/current holonym.)
for other_holonym_index, other_holonym in export.get_holonyms_to_check(data.place_desc,
data.holonym_index) do
local other_holonym_data = {
holonym_placetype = other_holonym.placetype,
holonym_placename = other_holonym.unlinked_placename,
holonym_index = other_holonym_index,
place_desc = data.place_desc,
}
local group, key, spec, container_trail = export.find_matching_holonym_location(other_holonym_data)
if group and not spec.is_former_place then
return {get_plural_entry_placetype(spec, container_trail) .. (spec.is_city and " of " or " in ") ..
export.get_prefixed_key(key, spec)}
end
end
end
end
function export.check_already_seen_string(holonym_placename, already_seen_strings)
local canon_placename = ulower(m_links.remove_links(holonym_placename))
if type(already_seen_strings) ~= "table" then
already_seen_strings = {already_seen_strings}
end
for _, already_seen_string in ipairs(already_seen_strings) do
if canon_placename:find(already_seen_string) then
return true
end
end
return false
end
-- Prefix display handler that adds a prefix such as "Metropolitan Borough of " to the display
-- form of holonyms. We make sure the holonym doesn't contain the prefix or some variant already.
-- We do this by checking if any of the strings in ALREADY_SEEN_STRINGS, either a single string or
-- a list of strings, or the prefix if ALREADY_SEEN_STRINGS is omitted, are found in the holonym
-- placename, ignoring case and links. If the prefix isn't already present, we create a link that
-- uses the raw form as the link destination but the prefixed form as the display form, unless the
-- holonym already has a link in it, in which case we just add the prefix.
local function prefix_display_handler(prefix, holonym_placename, already_seen_strings)
if export.check_already_seen_string(holonym_placename, already_seen_strings or ulower(prefix)) then
return holonym_placename
end
if holonym_placename:find("%[%[") then
return prefix .. " " .. holonym_placename
end
return prefix .. " [[" .. holonym_placename .. "]]"
end
-- Suffix display handler that adds a suffix such as " parish" to the display form of holonyms.
-- Works identically to prefix_display_handler but for suffixes instead of prefixes.
local function suffix_display_handler(suffix, holonym_placename, already_seen_strings, include_suffix_in_link)
if export.check_already_seen_string(holonym_placename, already_seen_strings or ulower(suffix)) then
return holonym_placename
end
if holonym_placename:find("%[%[") then
return holonym_placename .. " " .. suffix
end
if include_suffix_in_link then
return "[[" .. holonym_placename .. " " .. suffix .. "]]"
else
return "[[" .. holonym_placename .. "]] " .. suffix
end
end
-- ZH: write the Chinese term for the holonym's placetype directly after its name, which is how
-- Chinese names such places (伊犁自治州, 福島縣). The term is the placetype's `zh_suffix`; nothing is
-- added when the name already carries it, which is the normal case for the Chinese location keys.
-- This replaces the English " prefecture"/" borough" suffixes the enwikt handlers append.
local function zh_suffix_display_handler(holonym_placetype, holonym_placename)
local zh_suffix = m_zh_data.format_zh_name(export.get_zh_placetype_suffix(holonym_placetype))
if zh_suffix == "" then
return holonym_placename
end
if m_links.remove_links(holonym_placename):find(zh_suffix, 1, true) then
return holonym_placename
end
if holonym_placename:find("%[%[") then
return holonym_placename .. zh_suffix
end
return "[[" .. holonym_placename .. "]]" .. zh_suffix
end
-- Display handler for boroughs. New York City boroughs are displayed as-is; others take the
-- Chinese term for their placetype.
local function borough_display_handler(holonym_placetype, holonym_placename)
local unlinked_placename = m_links.remove_links(holonym_placename)
if m_locations.new_york_boroughs[unlinked_placename] then
-- Hack: don't display "borough" after the names of NYC boroughs
return holonym_placename
end
return zh_suffix_display_handler(holonym_placetype, holonym_placename)
end
local function county_display_handler(holonym_placetype, holonym_placename)
local unlinked_placename = m_links.remove_links(holonym_placename)
-- Display handler for Irish counties. Irish counties are displayed as e.g. "County [[Cork]]".
if m_locations.ireland_counties["County " .. unlinked_placename .. ", Ireland"] or
m_locations.northern_ireland_counties["County " .. unlinked_placename .. ", Northern Ireland"] then
return prefix_display_handler("County", holonym_placename)
end
-- Display handler for Taiwanese counties. Taiwanese counties are displayed as e.g. "[[Chiayi]] County".
if m_locations.taiwan_counties[unlinked_placename .. " County, Taiwan"] then
return suffix_display_handler("County", holonym_placename)
end
-- Display handler for Romanian counties. Romanian counties are displayed as e.g. "[[Cluj]] County".
if m_locations.romania_counties[unlinked_placename .. " County, Romania"] then
return suffix_display_handler("County", holonym_placename)
end
-- FIXME, we need the same for US counties but need to key off the country, not the specific county.
-- Others are displayed as-is.
return holonym_placename
end
-- Display handler for prefectures. Bare "prefecture" stands for several unrelated Chinese terms
-- (日本的縣、中國的地區、希臘的州), so the name is left as the editor wrote it; a more specific
-- placetype names one term and supplies it (autonomous prefecture → 伊犁自治州).
local function prefecture_display_handler(holonym_placetype, holonym_placename)
if export.resolve_placetype_aliases(holonym_placetype) == "prefecture" then
return holonym_placename
end
return zh_suffix_display_handler(holonym_placetype, holonym_placename)
end
-- Display handler for provinces of Bulgaria, Iran, Laos, North and South Korea, Thailand, Turkey and Vietnam. Recognized
-- provinces are displayed as e.g. "[[Gyeonggi]] Province" or "[[Antalya]] Province". Others are displayed as-is.
local function province_display_handler(holonym_placetype, holonym_placename)
local unlinked_placename = m_links.remove_links(holonym_placename)
if
m_locations.bulgaria_provinces[unlinked_placename .. "Province, Bulgaria"] or
m_locations.iran_provinces[unlinked_placename .. " Province, Iran"] or
m_locations.laos_provinces[unlinked_placename .. " Province, Laos"] or
m_locations.north_korea_provinces[unlinked_placename .. " Province, North Korea"] or
m_locations.south_korea_provinces[unlinked_placename .. " Province, South Korea"] or
m_locations.thailand_provinces[unlinked_placename .. " Province, Thailand"] or
m_locations.turkey_provinces[unlinked_placename .. " Province, Turkey"] or
m_locations.vietnam_provinces[unlinked_placename .. " Province, Vietnam"] then
return suffix_display_handler("Province", holonym_placename)
end
return holonym_placename
end
-- Display handler for Nigerian states. Nigerian states are display as "[[Kano]] State". Others are displayed as-is.
local function state_display_handler(holonym_placetype, holonym_placename)
local unlinked_placename = m_links.remove_links(holonym_placename)
if m_locations.nigeria_states[unlinked_placename .. " State, Nigeria"] then
return suffix_display_handler("State", holonym_placename)
end
return holonym_placename
end
-- Display handler for voivodeships. Display as e.g. [[下喀爾巴阡]]省.
local function voivodesip_display_handler(holonym_placetype, holonym_placename)
return zh_suffix_display_handler(holonym_placetype, holonym_placename)
end
------------------------------------------------------------------------------------------
-- Placetype data --
------------------------------------------------------------------------------------------
--[==[ var:
Main placetype data structure. This specifies, for each canonicalized placetype, various properties. The keys are
placetypes (in the singular, except for category-only placetypes, which are plural and followed by `!`), and the value
is a table of properties. The `"*"` key is special and is used for adding "generic" categories of the form
`Places in ``location`` `; it runs for all entry placetypes. Keys in the form of plural placetypes followed by `!` are
used only in [[Module:category tree/topic cat/data/Places]] for specifying the properties of categories containing the
specified placetype, esp. bare categories like [[:Category:States and territories]] (rather than qualified categories
like [[:Category:States and territories of Australia]]).
Keys under the value table for a given placetype of are two types: ''property keys'' (which specify the value of
specific properties) and ''categorization keys'' (which tell how to categorize certain sorts of holonyms if the
placetype in question occurs as an entry placetype). Categorization keys are either the special value `default` or are
wildcard strings with a slash in them, such as `"country/*"`. Note that only wildcard strings are currently allowed
directly in the placetype data; everything else is handled through category handlers, either per-placetype or special
(such as `political_division_cat_handler`). The algorithm for how category keys and handlers are used to generate
categories is described at the top of [[Module:place]].
There are several recognized property keys, of various types:
1. The following link-related property keys are recognized:
* `link`: '''Required''' except in category-only placetypes ending in `!`. Describes how to link and display the
placetype in the formatted description when occurring as an entry placetype. Also used for formatting pluralized
placetypes (which may occur in entry placetypes, esp. new-format ones, such as `two <<islands>>`) and may occur in
categories). The possible values are:
*# `true`: Link to the same-named Wiktionary entry. This creates a raw link, e.g. `<nowiki>[[city]]</nowiki>`, which is
converted to an English-specific link by JavaScript postprocessing. If the placetype is plural, this creates a
two-part raw link e.g. `<nowiki>[[city|cities]]</nowiki>`.
*# `"w"`: Link to the same-named Wikipedia entry. This creates a two-part link, e.g.
`<nowiki>[[w:census town|census town]]</nowiki>`, or `<nowiki>[[w:census town|census towns]]</nowiki>` if the
placetype is given plural.
*# `"+..."`: Create a two-part link to the entry following the `+` sign. For example, if `cercle` specifies
`"+w:cercles of Mali"`, a two-part link `<nowiki>[[w:cercles of Mali|cercle]]</nowiki>` will be generated, or
`<nowiki>[[w:cercles of Mali|cercles]]</nowiki>` if plural `cercles` is specified.
*# `"separately"`: Link each word separately. For example, if `administrative territory` specifies `"separately"`, it
will be linked as `<nowiki>[[administrative]] [[territory]]</nowiki>`, or as
`<nowiki>[[administrative]] [[territory|territories]]</nowiki>` if plural `administrative territories` is given.
*# another string: Use that string directly. If the placetype is plural, `pluralize()` in [[Module:en-utilities]] is
called on the string, which will correctly pluralize most strings, including those with links in them. (If there
are multiple links, the display form of the last link is pluralized.)
*# `false`: This placetype is not allowed as an entry placetype. An error will be thrown if this placetype is given as
an entry placetype. This is specified for internal-use placetypes, especially placetypes used in conjunction with
the qualifiers `former`, `ancient`, `historical` and such.
* `plural_link`: If specified and the placetype is plural, use the value in place of generating a pluralized version of
the link spec in `link`. Most commonly, this is either a string with links in it (which is used directly) or the
value `false`, indicating that the placetype cannot occur plural. (This is used for example by `caplc`, which displays
as `<nowiki>[[capital]] and [[large]]st [[city]]</nowiki>`, where a plural version doesn't make sense.) Generally if
this is specified, `plural` also needs to be specified to give a special placetype plural; this situation occurs
especially with multiword placetypes where something other than the last word is pluralized. An example is
`town with bystatus`, whose plural is `towns with bystatus`, which needs to be explicitly given. This example uses
`link = <nowiki>"[[town]] with [[bystatus#Norwegian Bokmål|bystatus]]"</nowiki>` ({{m|nb|bystatus}}) is a Norwegian
Bokmål word, and template calls aren't currently permitted in link strings), along with
`plural_link = <nowiki>"[[town]]s with [[bystatus#Norwegian Bokmål|bystatus]]"</nowiki>`.
* `category_link`: Spec indicating how to display the placetype when occurring in category descriptions. Defaults to
the value of `link`, and in turn is overridden by more specific `category_link_*` keys; see below. Category-only
placetypes (which are plural and end in `!`) usually use `category_link` in preference to `link`. The value of
`category_link` can be any of the types of specs given above, but most commonly is a plural string with links in it,
spelling out the description; in this case it is used directly. When both `category_link` and `link` are given, the
value in `category_link` is typically longer and more descriptive. For example, `polity` uses `link = true`, which
just generates a link `<nowiki>[[polity]]</nowiki>` or plural `<nowiki>[[polity|polities]]</nowiki>`, but specifies a
separate `category_link = <nowiki>"[[independent]] or [[semi-]][[independent]] [[polity|polities]]"</nowiki>`, which
clarifies in the category description what a polity is.
* `category_link_top_level`: Spec indicating how to display top-level (bare/unqualified) categories, i.e. categories
where the placetype is not followed by `in ``location`` ` or `of ``location`` `. If given, this overrides
`category_link` for this type of category.
* `category_link_before_noncity`: Spec indicating how to display qualified categories of the form
` ``placetypes`` in/of ``location`` ` where ``location`` does not refer to a city. If given, this overrides
`category_link` for this type of category.
* `category_link_before_city`: Spec indicating how to display qualified categories of the form
` ``placetypes`` in/of ``location`` ` where ``location`` refer to a city. If given, this overrides `category_link` for
this type of category. An example where this is given is `neighborhood`, which uses the following specs:<ol>
<li>`link = true`</li>
<li>`category_link = <nowiki>"[[neighborhood]]s, [[district]]s and other subportions of [[city|cities]]"</nowiki>`</li>
<li>`category_link_before_city = <nowiki>"[[neighborhood]]s, [[district]]s and other subportions"</nowiki>`</li>
</ol> This has the effect of making the entry placetype `neighborhood` display as just
`<nowiki>[[neighborhood]]</nowiki>`, while e.g. a category like `Neighborhoods of Chicago` displays as
`<nowiki>[[neighborhood]]s, [[district]]s and other subportions of [[Chicago]], ...</nowiki>` and a category like
`Neighborhoods in Illinois, USA` displays as
`<nowiki>[[neighborhood]]s, [[district]]s and other subportions of [[city|cities]] in [[Illinois]], ...</nowiki>`.
* `disallow_in_entries`: If specified, this placetype cannot occur as an entry placetype, and the specified value
(a message indicating what to use instead) is displayed in the error message.
* `disallow_in_holonyms`: If specified, this placetype cannot occur as a holonym placetype, and the specified value
(a message indicating what to use instead) is displayed in the error message.
2. There is currently one fallback-related property key recognized:
* `fallback`: If specified, its value is a placetype which will be used for categorization purposes if no categories
get added using the placetype itself. As an example, `branch` sets a fallback of `river` but also sets
`preposition = "of"`, meaning that {{tl|place|en|branch|riv/Mississippi}} displays as `a branch of the Mississippi`
(whereas `river` itself uses the preposition `in`), but otherwise categorizes the same as `river`. A more complex
example is `area`, which sets a fallback of `geographic and cultural area` and also sets a category handler that
checks for cities or city-like entities (e.g. boroughs) occurring as holonyms and categorizes the toponym under
[[:Category:Neighborhoods of CITY]] (for recognized cities) or otherwise [[:Category:Neighborhoods of POLDIV]] (for
the nearest containing recognized location). In addition, `area` is set as a political division of Kuwait, meaning if
`c/Kuwait` occurs as holonym, the toponym is categorized under [[:Category:Areas of Kuwait]]. If none of these
categories trigger, the fallback of `geographic and cultural area` will take effect, and the toponym will be
categorized as e.g. [[:Category:Geographic and cultural areas of England]].
3. There is currently one property to control irregular plurals of placetypes:
* `plural`: If specified, its value is the plural of the placetype. Otherwise, the default pluralization algorithm in
[[Module:en-utilities]] applies (which correctly pluralizes most words, including those ending in `-y`, `-ch`, `-sh`,
`-x`, etc.). The value of `plural` is also used when converting a pluralized placetype into its singular equivalent;
for example, since the placetype `kibbutz` has `plural = "kibbutzim"`, the placetype `kibbutzim` will be recognized
as a plural and singularized to `kibbutz`. For this reason, it's occasionally necessary to specify a `plural` value
even when the default pluralization algorithm works correctly, if the default singularization algorithm won't
correctly reverse the pluralization (as with `pass` and other terms ending in `-ss`).
4. The following property keys relate to generating categories for entry placetypes and specifying the parents of those
categories:
* `class`: The general class of placetype. This is used for various purposes: (a) to categorize placetypes preceded by
a qualifier such as `former`, `ancient`, `medieval` or `historical` (note that these placetypes are not all treated
alike); (b) to determine the parent category of bare placetype categories (e.g. [[:Category:Villages]] for placetype
`village`); (c) to determine whether to add a parent category `political divisions of specific countries` to
qualified placetype categories (e.g. [[:Category:Villages in Mali]]). The possible values are:
*# `polity`: a more-or-less sovereign/independent polity, such as a country, kingdom or empire.
*# `subpolity`: a non-sovereign division of a polity, above the level of an individual settlement.
*# `settlement`: a city or smaller equivalent, such as a village. This also includes administrative divisions of a
settlement, such as wards and barangays.
*# `non-admin settlement`: similar to a settlement but without administrative or political significance, such as an
unincorporated community, farm or neighborhood.
*# `capital`: a settlement that is a capital. A former capital is generally still in existence, just not the capital
any more.
*# `natural feature`: any non-man-made feature, such as a lake, mountain, island, ocean, etc.
*# `man-made structure`: a man-made feature below the level of a neighborhood, such as a house, airport, university,
metro station, park or the like.
*# `geographic region`: a geographic or cultural region or area that has no administrative significance. These may vary
greatly in size but typically have some sort of cultural significance (possibly historical). The `former`, `ancient`,
etc. qualifier has no effect on the category of these placetypes.
*# `generic place`: a place that isn't further qualified into any specific subtype.
* `former_type`: The class of placetype used for categorizing placetypes preceded by a qualifier such as `former`,
`ancient`, `medieval` or `historical`. The possible values are the same as for `class` but with the addition of
`dependent territory` (for colonies, protectorates and the like) and `!` (ignore the historical/former/ancient/etc.
qualifier; used e.g. with `fictional location` and `mythological location`). If not specified, the value of `class`
is used. When a qualifier such as `former`, `ancient`, `medieval` or `historical` is encountered (specifically, those
in `former_qualifiers`), it is mapped using `former_qualifiers` to the appropriate internal qualifier or qualifiers
(one or both of `ANCIENT` and/or `FORMER`, which are written in all-caps to distinguish them from user-specified
qualifiers), which is prepended to the value of `former_type` or `class` to form a placetype whose properties are
looked up to determine how to categorize the toponym in question. For example, if `medieval village` is given, we map
`medieval` to `ANCIENT` and `FORMER`, and `village` to its `class` of `settlement`, and enter the placetypes
`ANCIENT settlement` and `FORMER settlement` (in that order) into the list of equivalent placetypes returned by
`get_placetype_equivs`. In this case, there is an entry in `placetype_data` for `ANCIENT settlement`, so its default
category spec `Ancient settlements` is used as the category. If on the other hand `medieval kingdom` is given, where
`kingdom` has a `class` value `polity`, we first look up `ANCIENT polity`, see there is no entry in `placetype_data`
for it, and then look up `FORMER polity`, which exists and has a default category spec `Former polities`, which is
used as the category. Note that if the placetype following the "former" qualifier is recognized in `placetype_data`
but has no `former_type` or `class` and no fallback with a `former_type` or `class` specified, it is an internal
error; but if the placetype isn't recognized (e.g. something like `former greenhouse` is specified and we don't have
an entry for `greenhouse`), we just track the occurrence and end up not categorizing.
* `bare_category_parent`: This specifies the first parent category of a bare placetype category named according to the
placetype in question (e.g. [[:Category:Atolls]] for placetype `atoll`, or [[:Category:Named buildings]] for
placetype `named buildings!`). If not specified, the first parent category is determined by the value of `class`,
using the mapping `class_to_bare_category_parent` in [[Module:category tree/topic cat/data/Places]].
* `addl_bare_category_parents`: Extra parent categories to add a bare placetype category to (see `bare_category_parent`
just above).
* `bare_category_breadcrumb`: Breadcrumb for bare placetype categories. Also used as the sort key of
`bare_category_parent` if it is a string.
* `inherently_former`: If specified and the given placetype is used as an entry placetype, act as if `former` or
`ancient` (depending on the value of `inherently_former`) were prefixed to the placetype. This is for placetypes that
always refer to no-longer-existing entities, such as `satrapy` and `treaty port`. The value of `inherently_former` is
a list of internal qualifiers (one or more of `ANCIENT` and/or `FORMER`), just as for `former_qualifiers`, and the
implementation is the same.
* `cat_handler`: Handler used to generate the categories to add a given toponym to, if its entry placetype is the
placetype in question. Generally the `cat_handler` function checks the holonyms specified in order to determine which
category or categories to generate. For example, `district_neighborhood_cat_handler` handles placetypes `district`,
`neighborhood`, `subdivision`, `suburb` and the like, and either adds the toponym to a category like
`Neighborhoods of ``city`` ` (if a recognized city is given as a holonym), or otherwise a category like
`Neighborhoods in ``location`` ` (for the first recognized non-city location given as a holonym, if an unrecognized
city or city-like entity is given before the recognized non-city). The algorithm that runs the category handlers
iterates over holonyms from left to right, running the `cat_handler` function on each holonym in turn until one or
more categories are returned; see below for more specifics. (Note that countries for which e.g. a `district` is a
political division do not get the corresponding category added by the `district_neighborhood_cat_handler` function but
by `political_division_cat_handler`.) `cat_handler` functions are called with one argument, `data`, describing the
resolved entry placetype (i.e. after resolving placetype aliases and fallbacks) and the holonym being processed. The
return value should be a list of category specs (categories minus the langcode prefix, with `+++` standing for the
holonym key, or the value `true`, which stands for ` ``Placetypes`` in/of ``Holonym`` `, i.e. the pluralized placetype
with the appropriate preposition as specified in `placetype_data`). `data` contains the following fields:
** `entry_placetype`: the resolved entry placetype for the entry placetype being processed (i.e. it will always have an
entry in `placetype_data` but may not be the original placetype given by the user);
** `holonym_placetype` and `holonym_placename`: the holonym placetype and placename being processed;
** `holonym_index`: the index of the holonym being processed, or {nil} if we're handling an overriding holonym (FIXME:
we will change the overriding holonym algorithm so there will be an index even when processing overriding holonyms);
** `place_desc`: a full description of the {{tl|place}} call, as specified at the top of [[Module:place]];
** `from_demonym`: If set, we are called from [[Module:demonym]], triggered by {{tl|demonym-adj}} or
{{tl|demonym-noun}}, instead of being triggered by {{tl|place}}.
* `has_neighborhoods`: If `true`, the specified placetype is city-like. This is used in the
`district_neighborhood_cat_handler` to determine whether to add a category such as `Neighborhoods in ``location`` `;
see the section just above on `cat_handler`.
5. The following preposition-related property keys are recognized:
* `preposition`: The preposition used after this placetype when it occurs as an entry placetype. Defaults to `"in"`.
* `generic_before_non_cities`: If specified, the appropriate category description handler in
[[Module:category tree/topic cat/data/Places]] will recognize categories of the form
` ``Placetype`` in/of ``location`` ` for the specified placetype and preposition, if ``location`` is a non-city. This
is used to generate descriptions for categories added by category handlers and by explicit category specs in the
placetype data. All placetypes that specify `generic_before_non_cities` or `generic_before_cities` *MUST* also specify
a value for `class` so that the category tree code can determine whether it's a political or non-political division.
* `generic_before_cities`: Like `generic_before_non_cities` but for locations referring to cities.
6. The following property keys control the auto-addition of affixes when formatting holonyms of a particular placetype:
* `affix_type`: If specified, add the placetype as an affix before or after holonyms of this placetype. Possible values
are:
*# `"pref"` (the holonym will display as `(the) placetype of Holonym`, where `the` appears when the holonym directly
follows an entry placetype);
*# `"Pref"` (same as `"pref"` but the placetype is capitalized; each word is capitalized if there are multiple);
*# `"suf"` (the holonym will display as `Holonym placetype`);
*# `"Suf"` (the holonym will display as `Holonym Placetype`, i.e. same as `"suf"` but the placetype is capitalized).
* `suffix`: String to use in place of the placetype itself when the placetype is displayed as a suffix after a holonym.
Note that `suffix` can be used independently of `affix_type` because the user can also request a suffix explicitly
using a syntax like `adr:suf/Occitania`, which will display as `Occitania region` because the placetype
`administrative region` specifies `suffix = "region"`.
* `prefix`: Like `suffix` but for use when the placetype is displayed as a prefix before the holonym.
* `affix`: Like `suffix` and `prefix` but for use when the placetype is displayed as an affix either before or after the
holonym. If both `suffix` or `prefix` and `affix` are given for a single placetype, `suffix` or `prefix` take
precedence.
* `no_affix_strings`: String or list of strings that, if they occur in the holonym, suppress the addition of any affix
requested using `affix_type`. Defaults to the placetype itself. For example, `autonomous okrug` specifies
`affix_type = "Suf"` so that `aokr/Nenets` displays as `Nenets Autonomous Okrug`, but also specifies
`no_affix_strings = "okrug"` so that `aokr/Nenets Okrug` or `aokr/Nenets Autonomous Okrug` displays as specified,
without a redundant `Autonomous Okrug` added. Matching is case-insensitive but whole-word.
* `display_handler`: A function of two arguments, `holonym_placetype` and `holonym_placename` (specifying a holonym).
Its return value is a string specifying the display form of the holonym.
7. The following property keys control the indefinite and definite articles used before entry placetypes and/or holonyms
of the specified placetype.
* `entry_placetype_use_the`: Use `"the"` before this placetype when it occurs as an entry placetype.
* `entry_placetype_indefinite_article`: Indefinite article used before this placetype when it occurs as an entry
placetype (usually `"a"`, specifically for placetypes beginning with u- that don't take the indefinite article
`"an"`). Defaults to the appropriate indefinite article (`"a"` or `"an"` depending on whether the placetype begins
with a vowel). Overridden by `entry_placetype_use_the`, and unlike for most properties, does not apply to equivalent
placetypes (i.e. fallbacks or those formed by removing a qualifier from the beginning); only to the exact placetype
specified.
* `holonym_use_the`: Use `"the"` before holonyms of this placetype.
'''NOTE:'''
# The `link` property must be specified on all placetypes, except those ending in `!` (category-only placetypes), which
must have either `link` or `category_link` specified.
# Either the `class` or `former_type` property must be specified on all placetypes not ending in `!` that do not have a
fallback (if a placetype has a fallback and omits the `class` and `former_type` properties, they are taken from the
fallback). An internal error will result if a placetype has no `class` or `former_type` property derivable either
directly or through a fallback, if an attempt is made to categorize a former/ancient/historical/etc. entity of this
placetype.
# It is possible to have multiple levels of fallback (e.g. `frazione` falls back to `hamlet`, which falls back
to `village`). Fallback loops will cause an internal error. All placetypes specified as fallbacks must exist in
`placetype_data` or an internal error occurs.
]==]
export.placetype_data = {
--[=[
If you need to sort the following, do this (using Vim):
1. Make sure all full-line comments are within the { ... } table, or are moved after and on the same line as single-line
entries.
2. Make sure the table uses tabs everywhere for indent, and not spaces.
3. Mark the top of the table with `ma`, go to the bottom and execute the following two lines in sequence:
:'a,.s/\n/\\n/g
:s/\\n\(\t\[\)/\r\1/g
The first command converts every newline to a literal `\n` sequence, so the whole thing becomes a single line, while
the second command restores the newlines before the beginning of each entry. The effect is to convert all entries to
a single line while not losing any information. (Potentially a negative lookahead could be used to do it all in one
command.)
4. Execute the following to sort:
:'a,.!perl -pe 's/^(\t\[")(.*?)(".*)$/$2 @@@ $1$2$3/' | sort -f | perl -pe 's/.*? @@@ //'
Note that a simple `sort -f` (where `-f` means case-insensitive) would almost work, but it would sort "hill station"
before "hill" and "county borough" before "county" because the space after e.g. "hill station" sorts before the
quotation mark after e.g. "hill". The above command deals with this by extracting the key, prepending it followed by
` @@@ `, sorting, and then removing key (the classic decorate-sort-undecorate pattern).
5. Put the table back to multi-line format by marking the top of the table with `ma`, going to the bottom and executing
:'a,.s/\\n/\r/g
Note that for some reason, in order to get a match a newline in the left side of a replacement, you must use \n, but
to insert a newline in the right sode of a replacement you must use \r.
]=]
["*"] = {
link = false,
cat_handler = generic_place_cat_handler,
},
["administrative atoll"] = {
-- Maldives
link = "+w:administrative divisions of the Maldives",
preposition = "of",
class = "subpolity",
},
["administrative capital"] = {
link = "w",
fallback = "capital city",
},
["administrative center"] = {
link = "w",
fallback = "non-city capital",
},
["administrative centre"] = {
link = "w",
fallback = "administrative center",
},
["administrative county"] = {
link = "w",
fallback = "county",
},
["administrative district"] = {
link = "w",
fallback = "district",
},
["administrative headquarters"] = {
link = "separately",
fallback = "administrative centre",
},
["administrative region"] = {
link = true,
preposition = "of",
suffix = "region", -- but prefix is still "administrative region (of)"
fallback = "region",
class = "subpolity",
},
["administrative seat"] = {
link = "w",
fallback = "administrative centre",
},
["administrative territory"] = {
link = "separately",
preposition = "of",
suffix = "territory", -- but prefix is still "administrative territory (of)"
fallback = "territory",
class = "subpolity",
},
["administrative unit"] = {
-- Grrr, it's difficult to generalize about "administrative units". In Albania, "administrative unit" is an
-- official term for a city-level division of municipalities; Wikipedia renders it using the more practical term
-- "commune". In Pakistan, "administrative unit" is a collective term used to refer to all the different types
-- of first-level divisions (four provinces, one federal territory, and two "disputed territories", i.e. Azad
-- Kashmir and Gilgit-Balistan, that are variously described). For this reason, we set no fallback, but we need
-- to include this so that it can be used as a placetype for Albania, categorizing as communes.
link = "w",
class = "subpolity",
},
["administrative village"] = {
link = "w",
preposition = "of",
has_neighborhoods = true,
class = "settlement",
},
["aimag"] = {
-- used in Mongolia, Russia and China (Inner Mongolia); in Mongolia, equivalent to a province;
-- in China, equivalent to a prefecture (below a province); in Russia, equivalent to a municipal district.
link = "w",
fallback = "prefecture",
},
["airport"] = {
link = true,
class = "man-made structure",
default = {true},
},
["alliance"] = {
link = true,
fallback = "confederation",
},
["archipelago"] = {
link = true,
fallback = "island",
},
["列島"] = {
link = true,
fallback = "archipelago",
},
["area"] = {
link = true,
preposition = "of",
fallback = "geographic and cultural area",
-- Areas can either be administrative divisions (specifically of Kuwait) or geographic areas. Assume the former
-- when categorizing 'Areas' but the latter when handling e.g. 'historical area'.
class = "subpolity",
former_type = "geographic region",
cat_handler = district_neighborhood_cat_handler,
},
["arm"] = {
link = true,
preposition = "of",
class = "natural feature",
default = {"海"},
},
["arrondissement"] = {
link = true,
preposition = "of",
-- FIXME!!! Grrrrr!!! In some countries, arrondissements are divisions of cities; in others, they are divisions
-- of departments or provinces. Need to conditionalize on the country for both of the following.
class = "subpolity",
has_neighborhoods = true,
},
["associated province"] = {
link = "separately",
fallback = "province",
},
["atoll"] = {
-- FIXME! Atolls are administrative divisions of the Maldives but natural features elsewhere. Need to
-- conditionalize `class` on the country. See also `administrative atoll`.
link = true,
class = "natural feature",
bare_category_parent = "islands",
default = {true},
},
["autonomous city"] = {
link = "w",
preposition = "of",
fallback = "city",
has_neighborhoods = true,
},
["autonomous community"] = {
-- Spain; refers to regional entities, not village-like entities, as might be expected from "community"
link = true,
preposition = "of",
class = "subpolity",
},
["autonomous island"] = {
-- Comoros; seems like an administrative atoll of the Maldives.
link = "+w:autonomous islands of Comoros",
preposition = "of",
class = "subpolity",
},
["autonomous oblast"] = {
link = true,
preposition = "of",
affix_type = "Suf",
no_affix_strings = "oblast",
class = "subpolity",
},
["autonomous okrug"] = {
link = true,
preposition = "of",
affix_type = "Suf",
no_affix_strings = "okrug",
class = "subpolity",
},
["autonomous prefecture"] = {
link = true,
fallback = "prefecture",
},
["autonomous province"] = {
link = "w",
fallback = "province",
},
["autonomous region"] = {
link = "w",
preposition = "of",
fallback = "administrative region",
-- "administrative region" sets an affix of "region" but we want to display as "Tibet Autonomous Region"
-- if the user writes 'ar:Suf/Tibet'.
affix = "autonomous region",
},
["autonomous republic"] = {
link = "w",
preposition = "of",
class = "subpolity",
},
["autonomous territorial unit"] = {
-- Moldova; only two of them, one for Gagauzia and one for Transnistria.
link = "w",
preposition = "of",
class = "subpolity",
},
["autonomous territory"] = {
link = "w",
fallback = "dependent territory",
},
["bailiwick"] = {
-- Jersey, etc.
link = true,
fallback = "polity",
},
["barangay"] = {
-- Philippines
link = true,
class = "settlement",
-- Barangays are formal administrative divisions of a city rather than informal neighborhoods, but can use
-- some of the properties of a neighborhood.
fallback = "neighborhood",
},
["barrio"] = {
-- Spanish-speaking countries; Philippines
link = true,
-- FIXME: Not completely correct, in some countries barrios are formal administrative divisions of a city.
-- `class` will need to conditionalize on the country to be completely correct.
fallback = "neighborhood",
},
["basin"] = {
link = true,
fallback = "lake",
},
["bay"] = {
link = true,
preposition = "of",
class = "natural feature",
addl_bare_category_parents = {"bodies of water"},
default = {true},
},
["beach"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"water"},
default = {true},
},
["beach resort"] = {
link = "w",
fallback = "resort town",
},
["bishopric"] = {
link = true,
fallback = "polity",
},
["bodies of water!"] = {
-- FIXME: This is (maybe?) a type category not a name category. There should be an option for this. We need to
-- straighten out the type vs. name vs. related-to issue.
category_link = "[[body of water|bodies of water]]",
class = "natural feature",
addl_bare_category_parents = {"landforms", "ecosystems", "water"},
},
["borough"] = {
link = true,
preposition = "of",
display_handler = borough_display_handler,
has_neighborhoods = true,
-- "former borough" could be a former settlement or a former part of a city but seems more likely to
-- be a former subpolity, particularly in England. FIXME, we really need a handler to take care of this
-- properly.
class = "subpolity",
-- Grr, some boroughs are city-like but some (e.g. in Britain) may be larger.
},
["borough seat"] = {
link = true,
entry_placetype_use_the = true,
preposition = "of",
has_neighborhoods = true,
class = "capital",
},
["branch"] = {
link = true,
preposition = "of",
fallback = "river",
},
["bridge"] = {
link = true,
class = "man-made structure",
default = {"具名橋樑"},
},
["building"] = {
link = true,
class = "man-made structure",
default = {"具名建築物"},
},
["built-up area"] = {
link = "w",
fallback = "area",
},
["burgh"] = {
link = true,
fallback = "borough",
},
["business park"] = {
link = true,
fallback = "park",
},
["caliphate"] = {
link = true,
fallback = "polity",
},
["canton"] = {
link = true,
preposition = "of",
affix_type = "suf",
class = "subpolity",
},
["cape"] = {
link = true,
fallback = "headland",
},
["capital"] = {
link = true,
fallback = "capital city",
},
["capital city"] = {
link = true,
category_link = "[[capital city|capital cities]]: the [[seat of government|seats of government]] for a country or [[political]] [[division]] of a country",
entry_placetype_use_the = true,
preposition = "of",
has_neighborhoods = true,
class = "capital",
bare_category_parent = "cities",
cat_handler = capital_city_cat_handler,
default = {true},
-- The following is necessary so that e.g. [[Melbourne]] defined as {{place|en|capital city|s/Victoria|c/Australia}}
-- gets categorized in the bare category [[Category:en:Melbourne]]; otherwise placetype 'capital city' wouldn't
-- match against the placetype 'city' of Melbourne.
fallback = "city",
},
["caplc"] = {
link = "[[首都]]和[[最大城市]]",
plural_link = false,
fallback = "capital city",
},
["captaincy"] = {
link = true,
preposition = "of",
class = "subpolity",
inherently_former = {"FORMER"},
},
["caravan city"] = {
link = "w",
fallback = "city",
class = "settlement",
inherently_former = {"ANCIENT", "FORMER"},
},
["castle"] = {
link = true,
fallback = "building",
},
["cathedral city"] = {
link = true,
fallback = "city",
},
["cattle station"] = {
-- Australia
link = true,
fallback = "farm",
},
["census area"] = {
link = true,
affix_type = "Suf",
has_neighborhoods = true,
class = "non-admin settlement",
},
["census-designated place"] = {
-- United States
link = true,
class = "non-admin settlement",
},
["census division"] = {
-- Canada
link = "w",
preposition = "of",
class = "subpolity",
},
["census town"] = {
link = "w",
fallback = "town",
},
["central business district"] = {
link = true,
fallback = "neighborhood",
},
["cercle"] = {
-- Mali
link = "+w:cercles of Mali",
preposition = "of",
class = "subpolity",
},
["ceremonial county"] = {
link = true,
fallback = "county",
},
["chain of islands"] = {
link = true,
plural = "chains of islands",
plural_link = "[[chain]]s of [[island]]s",
fallback = "island",
},
["channel"] = {
link = true,
fallback = "strait",
},
["charter community"] = {
-- Northwest Territories, Canada
link = "w",
fallback = "village",
},
["city"] = {
link = true,
generic_before_non_cities = "in",
has_neighborhoods = true,
class = "settlement",
cat_handler = city_type_cat_handler,
default = {true},
},
["city-state"] = {
link = true,
category_link = "[[sovereign]] [[microstate]]s consisting of a single [[city]] and [[w:dependent territory|dependent territories]]",
has_neighborhoods = true,
class = "settlement",
["continent/*"] = {"城邦", "+++城市", "+++國家", "首都"},
default = {"城邦", "城市", "國家", "首都"},
},
["civil parish"] = {
-- Mostly England; similar to municipalities
link = true,
preposition = "of",
affix_type = "suf",
has_neighborhoods = true,
class = "subpolity",
},
["claimed political division"] = {
link = true,
class = "subpolity",
default = {true},
},
["co-capital"] = {
link = true,
fallback = "capital city",
},
["coal city"] = {
link = "+w:coal town",
fallback = "city",
},
["coal town"] = {
link = "w",
fallback = "town",
},
["collectivity"] = {
link = "w",
preposition = "of",
-- No default; these are weird one-off governmental divisions in France (esp. for overseas collectivities)
class = "subpolity",
},
["colony"] = {
link = true,
fallback = "dependent territory",
},
["comarca"] = {
-- per Wikipedia: traditional region or local administrative division found in Portugal, Spain, and some of
-- their former colonies, like Brazil, Nicaragua, and Panama. In the Valencian Community, for example, it
-- sits between municipalities and provinces, something like a county or district.
link = true,
preposition = "of",
class = "subpolity",
},
["commandery"] = {
link = true,
preposition = "of",
class = "subpolity",
inherently_former = {"ANCIENT", "FORMER"},
},
["commonwealth"] = {
link = true,
preposition = "of",
-- No default; applies specifically to Puerto Rico
class = "subpolity",
},
["commune"] = {
link = true,
fallback = "municipality",
},
["community"] = {
link = true,
category_link = "[[community|communities]] of all sizes",
fallback = "village",
},
["community development block"] = {
-- in India; appears to be similar to a rural municipality; groups several villages, unclear if there will be
-- neighborhoods so I'm not setting `has_neighborhoods` for now
link = "w",
affix_type = "suf",
no_affix_strings = "block",
class = "subpolity",
},
["comune"] = {
-- Italy, Switzerland
link = true,
fallback = "municipality",
},
["condominium"] = {
link = true,
fallback = "polity",
},
["confederacy"] = {
link = true,
fallback = "confederation",
},
["confederation"] = {
link = true,
fallback = "polity",
},
["constituency"] = {
-- currently we have them as political divisions of Namibia but many countries have them
link = true,
preposition = "of",
class = "subpolity",
},
["constituent country"] = {
link = true,
preposition = "of",
class = "subpolity",
},
["constituent part"] = {
link = "separately",
preposition = "of",
class = "subpolity",
},
["constituent republic"] = {
-- Of Russia, Yugoslavia, etc.
link = "separately",
preposition = "of",
class = "subpolity",
},
["counties and county-level cities!"] = {
-- This is used when grouping counties and county-level cities under prefecture-level cities in China.
category_link = "[[county|counties]] and [[county-level city|county-level cities]]",
class = "subpolity",
},
["continent"] = {
link = true,
category_link = false, -- can't occur as a bare category
class = "natural feature",
default = {"大洲和大洲地區"},
},
["大陸"] = {
fallback = "continent",
},
["continental region"] = {
link = "separately",
category_link = false, -- can't occur as a bare category
class = "geographic region",
fallback = "continent",
},
["continents and continental regions!"] = {
category_link = "[[大洲]]以及大洲等級的[[地區]](如[[玻里尼西亞]])",
class = "geographic region",
},
["council area"] = {
link = true,
-- in Scotland; similar to a county
preposition = "of",
affix_type = "suf",
class = "subpolity",
},
["country"] = {
link = true,
class = "polity",
["continent/*"] = {"+++國家", "國家"},
default = {true},
},
["country-like entities!"] = {
category_link = "[[polity|polities]] not normally considered [[country|countries]] but treated similarly for categorization purposes; typically, [[unrecognized]] [[de-facto]] countries or [[w:dependent territory|dependent territories]]",
class = "polity",
},
["county"] = {
link = true,
preposition = "of",
display_handler = county_display_handler,
class = "subpolity",
},
["county borough"] = {
link = true,
-- in Wales; similar to a county
preposition = "of",
affix_type = "suf",
fallback = "borough",
class = "subpolity",
},
["county seat"] = {
link = true,
entry_placetype_use_the = true,
preposition = "of",
has_neighborhoods = true,
class = "capital",
},
["county town"] = {
link = true,
entry_placetype_use_the = true,
preposition = "of",
fallback = "town",
has_neighborhoods = true,
class = "capital",
},
["county-administered city"] = {
-- In Taiwan, per Wikipedia similar to a Taiwanese township or district, which is a small city.
-- NOT anything like a "county-level city" in PR China, which is a county masquerading as a city.
link = "w",
fallback = "city",
has_neighborhoods = true,
class = "settlement",
},
["county-controlled city"] = {
-- Taiwan
link = "w",
fallback = "county-administered city",
},
["county-level city"] = {
-- PR China
link = "w",
fallback = "prefecture-level city",
},
["crater lake"] = {
link = true,
fallback = "lake",
},
["creek"] = {
link = true,
fallback = "stream",
},
["Crown colony"] = {
link = "+crown colony",
fallback = "crown colony",
},
["crown colony"] = {
link = true,
fallback = "colony",
},
["Crown dependency"] = {
link = true,
fallback = "dependent territory",
},
["crown dependency"] = {
link = true,
fallback = "dependent territory",
},
["cultural area"] = {
link = "w",
fallback = "geographic and cultural area",
},
["cultural region"] = {
link = "w",
fallback = "geographic and cultural area",
},
["delegation"] = {
-- Tunisia
link = "+w:delegations of Tunisia",
preposition = "of",
class = "subpolity",
},
["department"] = {
link = true,
preposition = "of",
affix_type = "suf",
class = "subpolity",
},
["departmental capital"] = {
link = "separately",
fallback = "capital city",
},
["dependency"] = {
link = true,
fallback = "dependent territory",
},
["dependent territory"] = {
link = "w",
preposition = "of",
class = "subpolity",
former_type = "dependent territory",
bare_category_parent = "political divisions",
["country/*"] = {true},
default = {true},
},
["desert"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"ecosystems"},
default = {true},
},
["deserted mediaeval village"] = {
link = "w",
fallback = "deserted medieval village",
},
["deserted medieval village"] = {
link = "w",
fallback = "ANCIENT settlement",
},
["direct-administered municipality"] = {
-- China
link = "+w:direct-administered municipalities of China",
fallback = "municipality",
},
["direct-controlled municipality"] = {
-- several countries
link = "w",
fallback = "municipality",
},
["distributary"] = {
link = true,
preposition = "of",
fallback = "river",
},
["district"] = {
link = true,
preposition = "of",
affix_type = "suf",
-- Grrr! FIXME! Here is where we need handlers for `class`. Using similar logic to
-- district_neighborhood_cat_handler, we need to check if we're below or above a city to determine if the class
-- is "settlement" or "subpolity".
class = "subpolity",
cat_handler = district_neighborhood_cat_handler,
-- No default. Countries for which districts are political divisions will get entries.
},
["districts and autonomous regions!"] = {
-- This and other similar "combined placetypes" are for use in the plural when grouping first-level
-- administrative regions of certain countries, in this case Portugal.
category_link = "[[district]]s and [[autonomous region]]s",
class = "subpolity",
},
["districts and autonomous territorial units!"] = {
-- This and other similar "combined placetypes" are for use in the plural when grouping first-level
-- administrative regions of certain countries, in this case Moldova.
category_link = "[[district]]s and [[w:autonomous territorial unit|autonomous territorial unit]]s",
class = "subpolity",
},
["district capital"] = {
link = "separately",
fallback = "capital city",
},
["district headquarters"] = {
link = "separately",
fallback = "administrative centre",
},
["district municipality"] = {
-- In Canada, a district municipality is equivalent to a rural municipality and won't have neighborhoods; in
-- South Africa, district municipalities group local municipalities and hence won't have neighborhoods.
link = "w",
preposition = "of",
affix_type = "suf",
no_affix_strings = {"district", "municipality"},
fallback = "municipality",
class = "subpolity",
},
["division"] = {
link = true,
preposition = "of",
class = "subpolity",
},
["division capital"] = {
link = "separately",
fallback = "capital city",
},
["dome"] = {
link = true,
fallback = "mountain",
},
["dormant volcano"] = {
link = true,
fallback = "volcano",
},
["duchy"] = {
link = true,
fallback = "polity",
},
["emirate"] = {
link = true,
preposition = "of",
-- FIXME: Can be subpolities (of the United Arab Emirates).
fallback = "polity",
},
["empire"] = {
link = true,
fallback = "polity",
},
["enclave"] = {
link = true,
preposition = "of",
-- Enclaves can theoretically be any size but assume a subpolity.
class = "subpolity",
},
["entity"] = {
-- Bosnia and Herzegovina
link = "+w:entities of Bosnia and Herzegovina",
preposition = "of",
class = "subpolity",
},
["escarpment"] = {
link = true,
fallback = "mountain",
},
["ethnographic region"] = {
-- used in Lithuania
link = "+w:ethnographic regions of Lithuania",
fallback = "geographic and cultural area",
},
["exclave"] = {
link = true,
preposition = "of",
-- exclaves can theoretically be any size but assume a subpolity.
class = "subpolity",
},
["external territory"] = {
link = "separately",
fallback = "dependent territory",
},
["farm"] = {
link = true,
class = "non-admin settlement",
default = {"農牧場"},
},
["farms and ranches!"] = {
category_link = "[[farm]]s and [[ranch]]es",
class = "non-admin settlement",
},
["federal city"] = {
link = "w",
preposition = "of",
fallback = "city",
},
["federal district"] = {
link = true,
preposition = "of",
-- Might have neighborhoods as federal districts are often cities (e.g. Mexico City)
has_neighborhoods = true,
class = "settlement",
},
["federal subject"] = {
-- In Russia; a generic term for first-level administrative divisions (republics, oblasts, okrugs, krais,
-- autonomous okrugs and autonomous oblasts).
link = "w",
preposition = "of",
class = "subpolity",
},
["federal territory"] = {
link = "w",
fallback = "territory",
},
["fictional location"] = {
link = "separately",
former_type = "!",
class = "hypothetical location",
bare_category_parent = "places",
default = {true},
},
["First Nations reserve"] = {
-- Canada
link = "w",
-- Wikipedia uses "Indian reserve"; presumably that is the legal term
fallback = "Indian reserve",
class = "subpolity",
},
["fjord"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"bodies of water"},
default = {true},
},
["footpath"] = {
link = true,
fallback = "road",
},
["forest"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"ecosystems", "forestry"},
default = {true},
},
["fort"] = {
link = true,
fallback = "building",
},
["fortress"] = {
link = true,
-- The default plural algorithm gets this right but the singularization algorithm incorrectly converts
-- fortresses -> fortresse, so put an entry here to ensure we singularize correctly.
plural = "fortresses",
fallback = "building",
},
["frazione"] = {
link = "w",
fallback = "hamlet",
},
["freeway"] = {
link = true,
fallback = "road",
},
["French prefecture"] = {
link = "w",
entry_placetype_use_the = true,
preposition = "of",
has_neighborhoods = true,
class = "capital",
},
["geographic and cultural area"] = {
link = "+w:cultural area",
-- `generic_before_non_cities` is used when generating the category description of categories of the format
-- `Geographic and cultural areas of PLACE`. `preposition` is used when generating {{place}} description and
-- categories for any placetype that falls back to `geographic and cultural area`.
generic_before_non_cities = "of",
preposition = "of",
class = "geographic region",
bare_category_parent = "places",
["country/*"] = {true},
["constituent country/*"] = {true},
["continent/*"] = {true},
default = {true},
},
["geographic area"] = {
link = "+w:geographic region",
fallback = "geographic and cultural area",
},
["geographic region"] = {
link = "w",
fallback = "geographic and cultural area",
},
["geographical area"] = {
link = "w",
fallback = "geographic and cultural area",
},
["geographical region"] = {
link = "w",
fallback = "geographic and cultural area",
},
["geopolitical zone"] = {
-- Nigeria
link = true,
preposition = "of",
class = "subpolity",
},
["gewog"] = {
-- Bhutan
link = true,
preposition = "of",
class = "subpolity",
},
["ghost town"] = {
link = true,
generic_before_non_cities = "in",
class = "non-admin settlement",
bare_category_parent = "former settlements",
cat_handler = city_type_cat_handler,
default = {true},
},
["glen"] = {
link = true,
fallback = "valley",
},
["governorate"] = {
link = true,
preposition = "of",
affix_type = "suf",
class = "subpolity",
},
["greater administrative region"] = {
-- China (former division)
link = "w",
preposition = "of",
class = "subpolity",
inherently_former = {"FORMER"},
},
["gromada"] = {
-- Poland (former division)
link = "w",
preposition = "of",
affix_type = "Pref",
class = "subpolity",
inherently_former = {"FORMER"},
},
["group of islands"] = {
link = true,
plural = "groups of islands",
plural_link = "[[group]]s of [[island]]s",
fallback = "island group",
},
["gulf"] = {
link = true,
preposition = "of",
holonym_use_the = true,
class = "natural feature",
addl_bare_category_parents = {"bodies of water"},
default = {true},
},
["hamlet"] = {
link = true,
fallback = "village",
},
["harbor city"] = {
link = "separately",
fallback = "city",
},
["harbor town"] = {
link = "separately",
fallback = "town",
},
["harbour city"] = {
link = "separately",
fallback = "city",
},
["harbour town"] = {
link = "separately",
fallback = "town",
},
["headland"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"landforms"},
default = {true},
},
["headquarters"] = {
link = "w",
fallback = "administrative centre",
},
["heath"] = {
link = true,
fallback = "moor",
},
["hemisphere"] = {
link = true,
entry_placetype_use_the = true,
fallback = "continental region",
},
["highway"] = {
link = true,
fallback = "road",
},
["hill"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"landforms"},
default = {true},
},
["hill station"] = {
link = "w",
fallback = "town",
},
["hill town"] = {
link = "w",
fallback = "town",
},
["historic region"] = {
-- provided only for the link
link = "+w:historical region",
fallback = "FORMER geographic region",
},
["historical county"] = {
-- needed for historical counties of England/etc.
link = "+w:historic county",
fallback = "FORMER subpolity",
},
["historical region"] = {
-- provided only for the link
link = "w",
fallback = "FORMER geographic region",
},
["home rule city"] = {
link = "w",
fallback = "city",
},
["home rule municipality"] = {
link = "w",
fallback = "municipality",
},
["hot spring"] = {
link = true,
fallback = "spring",
},
["house"] = {
link = true,
fallback = "building",
},
["housing estate"] = {
-- not the same as a housing project (i.e. public housing)
link = true,
-- not exactly the case but approximately
fallback = "neighborhood",
},
["hromada"] = {
-- Ukraine
link = "w",
disallow_in_entries = "Use placetype 'urban hromada', 'rural hromada' or 'settlement hromada' in place of bare 'hromada'",
disallow_in_holonyms = "Use placetype 'urban hromada'/'uhrom', 'rural hromada'/'rhrom' or 'settlement hromada'/'shrom' in place of bare 'hromada'",
preposition = "of",
affix_type = "suf",
class = "subpolity",
},
["inactive volcano"] = {
link = "w",
fallback = "dormant volcano",
},
["independent city"] = {
link = true,
fallback = "city",
},
["independent town"] = {
link = "+independent city",
fallback = "town",
},
["Indian reservation"] = {
link = "w",
-- In the US. Also known as "Native American reservation" or "domestic dependent nation", and the reservations
-- themselves often use the term "nation" in their official name (e.g. the "Navajo Nation"). But Wikipedia puts
-- the article at [[w:Indian reservation]] and uses that term when describing e.g. what the Navajo Nation is,
-- so this must still be the legal term.
preposition = "of",
class = "subpolity",
default = {true},
},
["Indian reserve"] = {
link = "w",
-- In Canada. "First Nations reserve" sounds more modern/PC but Wikipedia uses "Indian reserve"; presumably that
-- is still the legal term.
preposition = "of",
class = "subpolity",
default = {true},
},
["inland sea"] = {
-- note, we also have 'inland' as a qualifier
link = true,
fallback = "sea",
},
["inner city area"] = {
link = true,
fallback = "neighborhood",
},
["island"] = {
link = true,
preposition = "of",
class = "natural feature",
addl_bare_category_parents = {"landforms"},
default = {true},
},
["island country"] = {
-- FIXME: The following should map to both 'island' and 'country'.
link = "w",
fallback = "country",
},
["island group"] = {
link = "separately",
fallback = "island",
},
["island municipality"] = {
link = "w",
fallback = "municipality",
},
["islet"] = {
link = "w",
fallback = "island",
},
["Israeli settlement"] = {
link = "w",
class = "settlement",
default = {true},
},
["judicial capital"] = {
link = "w",
fallback = "capital city",
},
["khanate"] = {
link = true,
fallback = "polity",
},
["kibbutz"] = {
link = true,
plural = "kibbutzim",
class = "non-admin settlement",
default = {true},
},
["kingdom"] = {
link = true,
fallback = "monarchy",
},
["krai"] = {
link = true,
preposition = "of",
affix_type = "Suf",
class = "subpolity",
},
["lake"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"bodies of water"},
default = {true},
},
["landforms!"] = {
category_link = "[[landform]]s",
bare_category_parent = "places",
addl_bare_category_parents = {"地球"},
},
["largest city"] = {
link = true,
entry_placetype_use_the = true,
fallback = "city",
has_neighborhoods = true,
},
["league"] = {
link = true,
fallback = "confederation",
},
["legislative capital"] = {
link = "separately",
fallback = "capital city",
},
["library"] = {
link = true,
fallback = "building",
},
["lieutenancy area"] = {
-- used in the United Kingdom; per Wikipedia:
-- In England, lieutenancy areas are colloquially known as the ceremonial counties, although this phrase does
-- not appear in any legislation referring to them. The lieutenancy areas of Scotland are subdivisions of
-- Scotland that are more or less based on the counties of Scotland, making use of the major cities as separate
-- entities.[2] In Wales, the lieutenancy areas are known as the preserved counties of Wales and are based on
-- those used for lieutenancy and local government between 1974 and 1996. The lieutenancy areas of Northern
-- Ireland correspond to the six counties and two former county boroughs.[3]
link = "w",
fallback = "ceremonial county",
},
["local authority district"] = {
link = "w",
fallback = "local government district",
},
["local government area"] = {
-- Australia
link = "w",
preposition = "of",
class = "subpolity",
},
["local council"] = {
-- Malta; similar to municipalities
link = "+w:local councils of Malta",
preposition = "of",
fallback = "municipality",
},
["local government district"] = {
link = "w",
preposition = "of",
affix_type = "suf",
affix = "district",
class = "subpolity",
},
["local government district with borough status"] = {
link = "w",
plural = "local government districts with borough status",
plural_link = "[[w:local government district|local government districts]] with [[w:borough status|borough status]]",
preposition = "of",
affix_type = "suf",
affix = "district",
class = "subpolity",
},
["local urban district"] = {
link = "w",
fallback = "unincorporated community",
},
["locality"] = {
link = "+w:locality (settlement)",
-- not necessarily true, but usually is the case
fallback = "village",
},
["London borough"] = {
link = "w",
preposition = "of",
affix_type = "pref",
affix = "borough",
fallback = "local government district with borough status",
has_neighborhoods = true,
},
["macroregion"] = {
link = true,
fallback = "region",
},
["man-made structures!"] = {
category_link = "[[w:geographical feature#Engineered constructs|man-made structures]] such as [[airport]]s, [[university|universities]] and [[metro station]]s",
bare_category_parent = "places",
},
["manor"] = {
-- FIXME: or is this more like a farm?
link = true,
fallback = "building",
},
["marginal sea"] = {
link = true,
preposition = "of",
fallback = "sea",
},
["market city"] = {
link = "+market town",
fallback = "city",
},
["market town"] = {
link = true,
fallback = "town",
},
["massif"] = {
link = true,
fallback = "mountain",
},
["megacity"] = {
link = true,
fallback = "city",
},
["metro station"] = {
link = true,
class = "man-made structure",
},
["metropolitan borough"] = {
link = true,
preposition = "of",
affix_type = "Pref",
no_affix_strings = {"borough", "city"},
fallback = "local government district",
has_neighborhoods = true,
},
["metropolitan city"] = {
-- These exist e.g. in Italy and are more like municipalities or even provinces than cities.
link = true,
preposition = "of",
affix_type = "Pref",
no_affix_strings = {"metropolitan", "city"},
class = "subpolity",
},
["metropolitan county"] = {
link = true,
fallback = "county",
},
["metropolitan municipality"] = {
-- In South Africa, metropolitan municipalities group local municipalities and are like districts, between
-- provinces and municipalities.
-- In Turkey, metropolitan municipalities are provinces-level.
link = "w",
preposition = "of",
affix_type = "Suf",
no_affix_strings = {"metropolitan", "municipality"},
fallback = "municipality",
class = "subpolity",
},
["microdistrict"] = {
-- residential complex in post-Soviet states
link = true,
fallback = "neighborhood",
},
["micronations!"] = {
-- FIXME, merge with microstate
category_link = "[[micronation]]s",
bare_category_parent = "countries",
},
["microstate"] = {
link = true,
fallback = "country",
},
["military base"] = {
link = "w",
class = "settlement", -- or "man-made structure"?
default = {true},
},
["minster town"] = {
-- England
link = "separately",
fallback = "town",
},
["monarchy"] = {
link = true,
fallback = "polity",
},
["moor"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"landforms", "ecosystems"},
default = {true},
},
["moorland"] = {
link = true,
fallback = "moor",
},
["motorway"] = {
link = true,
fallback = "road",
},
["mountain"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"landforms"},
default = {true},
},
["mountain indigenous district"] = {
-- Taiwan
link = "+w:district (Taiwan)",
fallback = "district",
},
["mountain indigenous township"] = {
-- Taiwan
link = "+w:township (Taiwan)",
fallback = "township",
},
["mountain pass"] = {
link = true,
-- The default plural algorithm gets this right but the singularization algorithm incorrectly converts
-- passes -> passe, so put an entry here to ensure we singularize correctly.
plural = "mountain passes",
class = "natural feature",
addl_bare_category_parents = {"mountains"},
default = {true},
},
["mountain range"] = {
link = true,
fallback = "mountain",
},
["mountainous region"] = {
link = "separately",
fallback = "region",
},
["mukim"] = {
-- Malaysia, Brunei, Indonesia, Singapore
link = true,
preposition = "of",
class = "subpolity",
},
["municipal district"] = {
link = "w",
-- meaning varies depending on the country; for now, assume no neighborhoods.
-- FIXME: has_neighborhoods might have to be a function that looks at the containing holonyms.
preposition = "of",
affix_type = "Pref",
no_affix_strings = "district",
fallback = "municipality",
},
["municipality"] = {
link = true,
preposition = "of",
has_neighborhoods = true,
class = "subpolity",
},
["municipality with city status"] = {
link = "w",
plural = "municipalities with city status",
plural_link = "[[municipality|municipalities]] with [[w:city status|city status]]",
fallback = "municipality",
},
["museum"] = {
link = true,
fallback = "building",
},
["mythological location"] = {
link = "separately",
former_type = "!",
class = "hypothetical location",
bare_category_parent = "places",
default = {true},
},
["named bridges!"] = {
category_link = "notable [[bridge]]s",
bare_category_parent = "man-made structures",
addl_bare_category_parents = {"bridges"},
},
["named buildings!"] = {
category_link = "notable [[house]]s, [[library|libraries]] and other [[building]]s",
bare_category_parent = "man-made structures",
addl_bare_category_parents = {"buildings"},
},
["named roads!"] = {
category_link = "notable [[road]]s, [[highway]]s, [[trail]]s and similar linear structures",
bare_category_parent = "man-made structures",
addl_bare_category_parents = {"roads"},
},
["national capital"] = {
link = "w",
fallback = "capital city",
},
["national park"] = {
link = true,
fallback = "park",
},
["natural features!"] = {
category_link = "[[w:geographical feature#Natural features|natural features]] such as [[lake]]s, [[mountain]]s, [[island]]s and [[ocean]]s",
bare_category_parent = "places",
},
["neighborhood"] = {
-- The majority of the properties here apply to both `neighborhoods` and `neighbourhoods`; the choice of which
-- one to use is made by district_neighborhood_cat_handler() based on the value of `british_spelling` for the
-- location (city, political division, etc.) of the holonym that follows the word "neighbo(u)hoods" in the
-- category name. It does *NOT* depend on whether the {{place}} call uses "neighborhoods" or "neighbourhoods".
-- (In general it can't, because other things like "urban areas", "districts", "subdivisions" and the like also
-- categorize as neighbo(u)rhoods.)
link = true,
-- See below. These are used by category handlers in [[Module:category tree/topic cat/data/Places]].
generic_before_non_cities = "in",
generic_before_cities = "of",
-- The following text is suitable for the top-level description of a neighborhood as well as categories of the
-- form `Neighborhoods in POLDIV` e.g. `Neighborhoods in Illinois, USA` but not for categories of the form
-- `Neighborhoods of Chicago`, where we'd get "... and other subportions of [[city|cities]] of [[Chicago]]".
category_link = "[[neighborhood]]s, [[district]]s and other subportions of [[city|cities]]",
category_link_before_city = "[[neighborhood]]s, [[district]]s and other subportions",
-- NOTE: This setting is needed for administrative divisions like barangays that fall back to `neighborhood`,
-- when set in [[Module:place/locations]] for a specific country (e.g. the Philippines). The above settings
-- for `generic_before_non_cities` and `generic_before_cities` are used by category handlers in
-- [[Module:category tree/topic cat/data/Places]] for `Neighborhoods in POLDIV` and `Neighborhoods of CITY`
-- categories. In fact, district_neighborhood_cat_handler() does not currently pay attention to them, but
-- generates "of" before cities and "in" before non-cities regardless. (FIXME: We should change that.)
preposition = "of",
class = "non-admin settlement",
cat_handler = district_neighborhood_cat_handler,
},
["neighbourhood"] = {
link = true,
category_link = "[[neighbourhood]]s, [[district]]s and other subportions of [[city|cities]]",
category_link_before_city = "[[neighbourhood]]s, [[district]]s and other subportions",
fallback = "neighborhood",
},
["new area"] = {
-- China (type of economic development zone, varying greatly in size)
link = "w",
preposition = "in",
class = "subpolity", --?
},
["new town"] = {
link = true,
fallback = "town",
},
["non-city capital"] = {
link = true,
entry_placetype_use_the = true,
preposition = "of",
has_neighborhoods = true,
class = "capital",
cat_handler = function(data)
return capital_city_cat_handler(data, "non-city")
end,
-- FIXME, do we need the following?
default = {true},
},
["non-metropolitan county"] = {
link = "w",
fallback = "county",
},
["non-metropolitan district"] = {
link = "w",
fallback = "local government district",
},
["non-sovereign kingdom"] = {
-- especially in Africa and Asia
link = "+w:non-sovereign monarchy",
generic_before_non_cities = "in",
class = "subpolity",
["country/*"] = {true},
["continent/*"] = {true},
default = {true},
},
["non-sovereign monarchy"] = {
link = "w",
fallback = "non-sovereign kingdom",
},
["oblast"] = {
link = true,
preposition = "of",
affix_type = "Suf",
class = "subpolity",
},
["oblasts and autonomous republics!"] = {
-- This and other similar "combined placetypes" are for use in the plural when grouping first-level
-- administrative regions of certain countries, in this case Ukraine.
category_link = "[[oblast]]s and [[w:autonomous republic|autonomous republic]]s",
class = "subpolity",
},
["ocean"] = {
link = true,
holonym_use_the = true,
class = "natural feature",
addl_bare_category_parents = {"seas", "bodies of water"},
default = {true},
},
["okrug"] = {
link = true,
preposition = "of",
affix_type = "Suf",
class = "subpolity",
},
["overseas collectivity"] = {
link = "w",
fallback = "collectivity",
},
["overseas department"] = {
link = "w",
fallback = "department",
},
["overseas territory"] = {
link = "w",
fallback = "dependent territory",
},
["parish"] = {
link = true,
preposition = "of",
affix_type = "suf",
class = "subpolity",
},
["parish municipality"] = {
-- in Quebec, often similar to a rural village; the famous [[Saint-Louis-du-Ha! Ha!]] is one of them.
link = "+w:parish municipality (Quebec)",
preposition = "of",
fallback = "municipality",
has_neighborhoods = true,
},
["parish seat"] = {
link = true,
entry_placetype_use_the = true,
preposition = "of",
class = "capital",
has_neighborhoods = true,
},
["park"] = {
link = true,
class = "man-made structure",
default = {true},
},
["pass"] = {
link = "+mountain pass",
-- The default plural algorithm gets this right but the singularization algorithm incorrectly converts
-- passes -> passe, so put an entry here to ensure we singularize correctly.
plural = "passes",
fallback = "mountain pass",
},
["path"] = {
link = true,
fallback = "road",
},
["peak"] = {
link = true,
fallback = "mountain",
},
["peninsula"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"landforms"},
default = {true},
},
["periphery"] = {
link = true,
preposition = "of",
class = "subpolity",
},
["places!"] = {
generic_before_non_cities = "in",
generic_before_cities = "in",
class = "generic place",
category_link = "[[place]]s of all sorts",
-- `category_link_top_level` control the description used in the top-level [[Category:Places]] and
-- language-specific variants such as [[Category:en:Places]]. The actual text for a language-spefic variant is
-- "{{{langname}}} names of [[geographical]] [[place]]s of all sorts; [[toponym]]s." where the "names of"
-- portion is automatically generated by the appropriate handler in
-- [[Module:category tree/topic cat/data/Places]].
category_link_top_level = "[[geographical]] [[place]]s of all sorts; [[toponym]]s",
bare_category_parent = "names",
},
["planned community"] = {
-- Include this so we don't categorize 'planned community' into villages, as 'community' does.
link = true,
class = "settlement",
has_neighborhoods = true,
},
["plateau"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"landforms"},
default = {true},
-- FIXME: Should generate both "Plateaus" and the appropriate 'geographic and cultural area' category
},
["Polish colony"] = {
link = "w",
affix_type = "suf",
affix = "colony",
fallback = "village",
has_neighborhoods = true,
},
["political divisions!"] = {
category_link = "[[political]] [[division]]s and [[subdivision]]s, such as [[state]]s, [[province]]s, [[county|counties]] or [[district]]s",
bare_category_parent = "places",
},
["polity"] = {
link = true,
category_link = "[[independent]] or [[semi-]][[independent]] [[polity|polities]]",
class = "polity",
bare_category_parent = "places",
default = {true},
},
["populated place"] = {
link = "+w:populated place",
-- not necessarily true, but usually is the case
fallback = "village",
},
["port"] = {
link = true,
class = "man-made structure",
default = {true},
},
["port city"] = {
-- FIXME: should categorize into "Ports" as well as "Cities"
link = true,
fallback = "city",
},
["port town"] = {
-- FIXME: should categorize into "Ports" as well as "Towns"
link = "w",
fallback = "town",
},
["prefecture"] = {
-- FIXME! `prefecture` is like a county in Japan and elsewhere but a department capital city in France.
-- May need `has_neighborhoods` to be a function.
link = true,
preposition = "of",
display_handler = prefecture_display_handler,
class = "subpolity",
},
["prefecture-level city"] = {
-- China; they are huge entities with a central city; not cities themselves.
link = "w",
preposition = "of",
class = "subpolity",
},
["preserved county"] = {
-- In Wales; they are former counties enshrined in law; there are 8 of them and each consists of one or more
-- "principal areas" (styled as "counties" or "county boroughs"), of which there are 22.
link = "w",
preposition = "of",
class = "subpolity",
inherently_former = {"FORMER"},
},
["primary area"] = {
-- a grouping of "districts" (neighborhoods) in Gothenburg, Sweden
link = "+w:sv:primärområde",
fallback = "neighborhood",
},
["principality"] = {
link = true,
fallback = "monarchy",
},
["promontory"] = {
link = true,
fallback = "headland",
},
["protectorate"] = {
link = true,
fallback = "dependent territory",
},
["province"] = {
link = true,
preposition = "of",
display_handler = province_display_handler,
class = "subpolity",
},
["provinces and autonomous regions!"] = {
-- This and other similar "combined placetypes" are for use in the plural when grouping first-level
-- administrative regions of certain countries, in this case China.
category_link = "[[province]]s and [[autonomous region]]s",
class = "subpolity",
},
["provinces and territories!"] = {
-- This and other similar "combined placetypes" are for use in the plural when grouping first-level
-- administrative regions of certain countries, in this case Canada and Pakistan.
category_link = "[[province]]s and [[territory|territories]]",
class = "subpolity",
},
["provincial capital"] = {
link = "separately",
fallback = "capital city",
},
["raion"] = {
link = true,
preposition = "of",
affix_type = "Suf",
class = "subpolity",
},
["ranch"] = {
link = true,
fallback = "farm",
},
["range"] = {
-- FIXME: Where is this used? Is it a mountain range?
link = true,
holonym_use_the = true,
class = "natural feature",
},
["regency"] = {
link = true,
preposition = "of",
class = "subpolity",
},
["region"] = {
link = true,
preposition = "of",
-- If 'region' isn't a specific administrative division, fall back to 'geographic and cultural area'
fallback = "geographic and cultural area",
-- "former region" is a subpolity but traditional/historic(al)/ancient/medieval/etc. is a geographic region
class = "geographic region",
},
["regional capital"] = {
link = "separately",
fallback = "capital city",
},
["regional county municipality"] = {
-- Quebec
link = "w",
preposition = "of",
affix_type = "Suf",
no_affix_strings = {"municipality", "county"},
fallback = "municipality",
},
["regional district"] = {
link = "w",
preposition = "of",
affix_type = "Pref",
no_affix_strings = "district",
fallback = "district",
},
["regional municipality"] = {
link = "w",
preposition = "of",
affix_type = "Pref",
no_affix_strings = "municipality",
fallback = "municipality",
},
["regional unit"] = {
link = "w",
preposition = "of",
affix_type = "suf",
class = "subpolity",
},
["registration county"] = {
-- Used in Scotland for land registration purposes; formerly used in England, Wales and Ireland for statistical
-- purposes (registration of births, deaths and marriages, and for the output of census information).
link = "w",
fallback = "county",
},
["republic"] = {
-- Of Russia, Yugoslavia, etc. "Republics" in general are sovereign but we use "country" in that case.
link = true,
fallback = "constituent republic",
},
["research base"] = {
link = "+w:research station",
fallback = "research station",
},
["research station"] = {
link = "w",
class = "non-admin settlement", -- or "man-made structure"?
default = {true},
},
["reservoir"] = {
link = true,
fallback = "lake",
},
["residential area"] = {
link = "separately",
fallback = "neighborhood",
},
["resort city"] = {
link = "w",
fallback = "city",
},
["resort town"] = {
link = "w",
fallback = "town",
},
["river"] = {
link = true,
generic_before_non_cities = "in",
holonym_use_the = true,
class = "natural feature",
addl_bare_category_parents = {"bodies of water"},
cat_handler = city_type_cat_handler,
["continent/*"] = {true},
default = {true},
},
["river island"] = {
link = "w",
fallback = "island",
},
["河流"] = {
link = true,
fallback = "river",
},
["road"] = {
link = true,
class = "man-made structure",
default = {"具名道路"},
},
["Roman province"] = {
-- FIXME! Eliminate this in favor of 'former province|emp/Roman Empire'
link = "w",
default = {"羅馬帝國行省"},
class = "subpolity",
},
["royal borough"] = {
link = "w",
preposition = "of",
affix_type = "Pref",
no_affix_strings = {"royal", "borough"},
fallback = "local government district with borough status",
has_neighborhoods = true,
},
["royal burgh"] = {
link = true,
fallback = "borough",
},
["royal capital"] = {
link = "w",
fallback = "capital city",
},
["rural committee"] = {
-- Hong Kong; a group of villages
link = "w",
affix_type = "Suf",
has_neighborhoods = true,
class = "settlement",
},
["rural community"] = {
-- New Brunswick
link = "+w:list of municipalities in New_Brunswick#Rural communities",
fallback = "municipality",
},
["rural hromada"] = {
link = "w",
affix_type = "suf",
fallback = "hromada",
},
["rural municipality"] = {
link = "w",
preposition = "of",
affix_type = "Pref",
no_affix_strings = "municipality",
fallback = "municipality",
has_neighborhoods = true, --?
},
["rural township"] = {
-- Taiwan
link = "+w:rural township (Taiwan)",
fallback = "township",
},
["sanctuary"] = {
link = true,
fallback = "temple",
},
["satrapy"] = {
link = true,
preposition = "of",
class = "subpolity",
inherently_former = {"ANCIENT", "FORMER"},
},
["sea"] = {
link = true,
holonym_use_the = true,
class = "natural feature",
addl_bare_category_parents = {"bodies of water"},
default = {true},
},
["seaport"] = {
link = true,
fallback = "port",
},
["seat"] = {
link = true,
fallback = "administrative centre",
},
["self-administered area"] = {
-- Myanmar (groups self-administered divisions and zones)
link = "+w:self-administered zone",
preposition = "of",
class = "subpolity",
},
["self-administered division"] = {
-- Myanmar (only one of them: Wa Self-Administered Division)
link = "w",
fallback = "self-administered area",
},
["self-administered zone"] = {
-- Myanmar (five of them)
link = "w",
fallback = "self-administered area",
},
["separatist state"] = {
link = "separately",
fallback = "unrecognized country",
},
["settlement"] = {
link = true,
category_link = "[[settlement]]s such as [[city|cities]], [[village]]s and [[farm]]s",
bare_category_parent = "places",
-- not necessarily true, but usually is the case
fallback = "village",
},
["settlement hromada"] = {
link = "w",
affix_type = "suf",
fallback = "hromada",
},
["sheading"] = {
-- Isle of Man
link = true,
fallback = "district",
},
["sheep station"] = {
-- Australia
link = true,
fallback = "farm",
},
["shire"] = {
link = true,
fallback = "county",
},
["shire county"] = {
link = "w",
fallback = "county",
},
["shire town"] = {
link = true,
fallback = "county seat",
},
["ski resort city"] = {
link = true,
fallback = "city",
},
["ski resort town"] = {
link = true,
fallback = "town",
},
["spa city"] = {
link = "+w:spa town",
fallback = "city",
},
["spa town"] = {
link = "w",
fallback = "town",
},
["space station"] = {
link = true,
fallback = "research station",
},
["special administrative region"] = {
-- in China; in practice they are city-like (Hong Kong, Macau); also [[Oecusse]] in East Timor is formally a
-- "special administrative region"; North Korea had one such region planned (Sinuiju) but abandoned; Indonesia
-- has similar "special regions" of Jakarta, Yogyakarta and Aceh; and South Sudan has three "special
-- administrative areas"
link = "+w:special administrative regions of China",
preposition = "of",
class = "subpolity",
has_neighborhoods = true, --?
-- no suffix since places in Hong Kong or Macau are listed without China, except Hong Kong and Macau themselves
-- they also contain regions (or areas), e.g. [[Kowloon]], so it would be confusing
suffix = "",
},
["special collectivity"] = {
link = "w",
fallback = "collectivity",
},
["special municipality"] = {
-- formerly linked to the Taiwan article but there are also special municipalities of the Netherlands
link = "w",
fallback = "municipality",
},
["special ward"] = {
-- Tokyo
link = true,
fallback = "municipality",
},
["spit"] = {
link = true,
fallback = "peninsula",
},
["spring"] = {
link = true,
class = "natural feature",
default = {true},
},
["star"] = {
link = true,
class = "natural feature",
default = {true},
},
["state"] = {
link = true,
preposition = "of",
class = "subpolity",
-- 'former/historical state' could refer either to a state of a country (a division) or a state = sovereign
-- entity. The latter appears more common (e.g. in various "ancient states" of East Asia).
former_type = "polity",
},
["states and territories!"] = {
-- This and other similar "combined placetypes" are for use in the plural when grouping first-level
-- administrative regions of certain countries, in this case Australia.
category_link = "[[state]]s and [[territory|territories]]",
class = "subpolity",
},
["states and union territories!"] = {
-- This and other similar "combined placetypes" are for use in the plural when grouping first-level
-- administrative regions of certain countries, in this case India.
category_link = "[[state]]s and [[union territory|union territories]]",
class = "subpolity",
},
["state capital"] = {
link = true,
fallback = "capital city",
},
["state park"] = {
link = true,
fallback = "park",
},
["state-level new area"] = {
-- China (type of economic development zone, varying greatly in size)
link = "w",
fallback = "new area",
},
["statistical region"] = {
-- Slovenia
link = true,
fallback = "administrative region",
},
["statutory city"] = {
link = "w",
fallback = "city",
},
["statutory town"] = {
link = "w",
fallback = "town",
},
["strait"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"bodies of water"},
default = {true},
},
["stream"] = {
link = true,
fallback = "river",
},
["street"] = {
link = true,
fallback = "road",
},
["strip"] = {
link = true,
fallback = "geographic region",
},
["strip of land"] = {
link = true,
plural = "strips of land",
plural_link = "[[strip]]s of [[land]]",
fallback = "geographic region",
},
["sub-metropolitan city"] = {
link = "+w:List of cities in Nepal#Sub-metropolitan cities",
fallback = "city",
},
["sub-prefectural city"] = {
link = "w",
fallback = "subprovincial city",
},
["subdistrict"] = {
link = true,
preposition = "of",
has_neighborhoods = true, --?
-- FIXME: subdistricts can be neighborhood-like (of Jakarta) or larger (in China); need a handler
class = "subpolity",
default = {true},
},
["subdivision"] = {
link = true,
preposition = "of",
affix_type = "suf",
-- FIXME: subdivisions can be neighborhood-like or larger; need a handler
class = "subpolity",
cat_handler = district_neighborhood_cat_handler,
},
["submerged ghost town"] = {
-- FIXME: Consider just having "submerged" as a qualifier.
link = true,
fallback = "ghost town",
},
["subnational kingdom"] = {
link = "+w:subnational monarchy",
fallback = "non-sovereign kingdom",
},
["subnational monarchy"] = {
link = "w",
fallback = "non-sovereign kingdom",
},
["subprefecture"] = {
link = true,
affix_type = "suf",
preposition = "of",
class = "subpolity",
},
["subprovince"] = {
link = true,
preposition = "of",
class = "subpolity",
},
["subprovincial city"] = {
link = "w",
-- China; special status given to certain prefecture-level cities
fallback = "prefecture-level city",
},
["subprovincial district"] = {
link = "w",
-- China; special status given to Binhai New Area and Pudong New Area, which are county-level districts
preposition = "of",
class = "subpolity",
},
["subregion"] = {
link = true,
fallback = "geographic region",
},
["suburb"] = {
link = true,
-- The following text is suitable for the top-level description of a suburb as well as categories of the form
-- 'Suburbs in POLDIV' e.g. 'Suburbs in Illinois, USA' but not for categories of the form 'Suburbs of Chicago',
-- where we'd get "[[suburb]]s of [[city|cities]] of [[Chicago]]".
category_link = "[[suburb]]s of [[city|cities]]",
category_link_before_city = "[[suburb]]s",
-- See comments under "neighborhood" for the following three settings. They are used by
-- [[Module:category tree/topic cat/data/Places]] for generating the text of 'Suburbs in/of PLACE' categories
-- but currently ignored by district_neighborhood_cat_handler (which actually generates the categories for a
-- given page), which hardcodes "in" for non-cities and "of" for cities. (FIXME: Change this.)
generic_before_non_cities = "in",
generic_before_cities = "of",
preposition = "of",
has_neighborhoods = true, --?
class = "non-admin settlement", --?
cat_handler = district_neighborhood_cat_handler,
},
["suburban area"] = {
link = "w",
fallback = "suburb",
},
["subway station"] = {
link = "w",
fallback = "metro station",
},
["sum"] = {
-- In China, Mongolia, Russia; something like a county in Mongolia but a township in China (Inner Mongolia),
-- and equivalent to a [[selsoviet]] in the parts of Russia where it's in use (a rural council, below a raion).
link = "+w:sum (administrative division)",
-- This fallback is somewha arbitrary. We could use "county" but that has a display handler
-- which we don't want to be active (FIXME: If the display handler would be active, that's a bug).
fallback = "division",
},
["supercontinent"] = {
link = true,
fallback = "continent",
},
["tehsil"] = {
link = true,
affix_type = "suf",
no_affix_strings = {"tehsil", "tahsil"},
class = "subpolity",
},
["temple"] = {
link = true,
fallback = "building",
},
["territorial authority"] = {
link = "w",
fallback = "district",
},
["territory"] = {
link = true,
preposition = "of",
class = "subpolity",
},
["theme"] = {
link = "+w:theme (Byzantine district)",
preposition = "of",
class = "subpolity",
},
["town"] = {
link = true,
generic_before_non_cities = "in",
has_neighborhoods = true,
class = "settlement",
cat_handler = city_type_cat_handler,
default = {true},
},
["town with bystatus"] = {
-- can't use templates in links currently
link = "w",
plural = "towns with bystatus",
plural_link = "[[town]]s with [[bystatus#Norwegian Bokmål|bystatus]]",
fallback = "town",
},
["township"] = {
link = true,
has_neighborhoods = true,
class = "settlement", --?
default = {true},
},
["township municipality"] = {
-- Quebec
link = "+w:township municipality (Quebec)",
preposition = "of",
fallback = "municipality",
has_neighborhoods = true, --?
},
["traditional county"] = {
link = true,
fallback = "county",
},
["traditional region"] = {
-- FIXME: Verify this works. Same for 'historic(al) region'.
-- provided only for the link
link = "w",
fallback = "FORMER geographic region",
},
["trail"] = {
link = true,
fallback = "road",
},
["treaty port"] = {
link = "w",
fallback = "city",
class = "settlement",
inherently_former = {"FORMER"},
},
["tributary"] = {
link = true,
preposition = "of",
fallback = "river",
},
["underground station"] = {
link = "w",
fallback = "metro station",
},
["unincorporated area"] = {
link = "w",
-- I don't know if this fallback makes sense everywhere.
fallback = "unincorporated community",
},
["unincorporated community"] = {
link = true,
generic_before_non_cities = "in",
class = "non-admin settlement",
},
["unincorporated territory"] = {
link = "w",
fallback = "territory",
},
["union territory"] = {
-- India
link = true,
preposition = "of",
entry_placetype_indefinite_article = "a",
class = "subpolity",
},
["unitary authority"] = {
-- UK, New Zealand
link = true,
entry_placetype_indefinite_article = "a",
fallback = "local government district",
},
["unitary district"] = {
link = "w",
entry_placetype_indefinite_article = "a",
fallback = "local government district",
},
["united township municipality"] = {
-- Quebec
link = "+w:united township municipality (Quebec)",
entry_placetype_indefinite_article = "a",
fallback = "township municipality",
has_neighborhoods = true, --?
},
["university"] = {
link = true,
entry_placetype_indefinite_article = "a",
class = "man-made structure",
default = {true},
},
["unrecognised country"] = {
link = "w",
fallback = "unrecognized country",
},
["unrecognized and nearly unrecognized countries!"] = {
category_link = "[[de facto]] [[independent]] [[state]]s with little or no {{w|international recognition}}",
bare_category_parent = "country-like entities",
},
["unrecognized country"] = {
link = "w",
class = "polity",
default = {"未受國際普遍承認國家"},
},
["unrecognised state"] = {
link = "w",
fallback = "unrecognized country",
},
["unrecognized state"] = {
link = "w",
fallback = "unrecognized country",
},
["urban area"] = {
link = "separately",
fallback = "neighborhood",
},
["urban hromada"] = {
link = "w",
affix_type = "suf",
fallback = "hromada",
},
["urban service area"] = {
-- A strange beast existing in Alberta; technically a type of hamlet but in practice used for much larger
-- cities and treated equivalent to a city. (There are only two of them, [[Fort McMurray]] and [[Sherwood Park]]).
link = "w",
fallback = "city",
},
["urban township"] = {
link = "w",
fallback = "township",
},
["urban-type settlement"] = {
-- appears to be a particular type of small urban settlement in post-Soviet states,
-- had an administrative function.
link = "w",
fallback = "town",
},
["valley"] = {
link = true,
class = "natural feature",
addl_bare_category_parents = {"landforms", "water"},
default = {true},
},
["viceroyalty"] = {
-- in essence, a type of colony
link = true,
fallback = "dependent territory",
},
["village"] = {
link = true,
generic_before_non_cities = "in",
category_link = "[[village]]s, [[hamlet]]s, and other small [[community|communities]] and [[settlement]]s",
class = "settlement",
cat_handler = city_type_cat_handler,
default = {true},
},
["village development committee"] = {
-- former administrative structure in Nepal; also exists in India but not as a formal unit
link = "+w:village development committee (Nepal)",
inherently_former = {"FORMER"},
fallback = "village",
},
["village municipality"] = {
-- Quebec
link = "+w:village municipality (Quebec)",
preposition = "of",
fallback = "municipality",
has_neighborhoods = true, --?
},
["voivodeship"] = {
-- Poland
link = true,
display_handler = voivodeship_display_handler,
preposition = "of",
class = "subpolity",
},
["volcano"] = {
link = true,
plural = "volcanoes",
class = "natural feature",
addl_bare_category_parents = {"landforms"},
default = {true, "山"},
},
["ward"] = {
link = true,
class = "settlement",
-- Wards are formal administrative divisions of a city but have some properties of neighborhoods.
fallback = "neighborhood",
},
["watercourse"] = {
link = true,
fallback = "channel",
},
["Welsh community"] = {
-- Wales
link = "w",
preposition = "of",
affix_type = "suf",
affix = "community",
has_neighborhoods = true,
class = "settlement",
},
["zone"] = {
-- administrative division of Ethiopia, Qatar, Nepal, India
link = "+w:zone#Place names",
preposition = "of",
class = "subpolity",
},
----------------------------------------------------------------------------------------------
-- Categories for former places --
----------------------------------------------------------------------------------------------
["ANCIENT capital"] = {
link = false,
entry_placetype_use_the = true,
preposition = "of",
has_neighborhoods = true,
class = "capital",
-- FIXME: Consider removing 'ancient settlements' here. Ancient capitals, like former capitals, often still
-- exist but just aren't the capital any more. Maybe we should have an 'Ancient capitals' category.
default = {"古代定居點", "歷史首府"},
},
["ANCIENT non-admin settlement"] = {
link = false,
class = "non-admin settlement",
fallback = "ANCIENT settlement",
},
["ANCIENT settlement"] = {
link = false,
has_neighborhoods = true,
class = "settlement",
default = {"古代定居點"},
},
["ancient settlements!"] = {
category_link = "former [[city|cities]], [[town]]s and [[village]]s that existed in [[antiquity]]",
bare_category_parent = "former settlements",
},
["FORMER capital"] = {
link = false,
entry_placetype_use_the = true,
preposition = "of",
has_neighborhoods = true,
class = "capital",
default = {"歷史首府"},
},
["former capitals!"] = {
category_link = "former [[capital]] [[city|cities]] and [[town]]s",
bare_category_parent = "settlements",
},
["former counties and county-level cities!"] = {
-- For categorizing former counties and county-level cities of China
category_link = "no-longer existing [[county|counties]] and [[county-level city|county-level cities]]",
bare_category_breadcrumb = "counties and county-level cities",
bare_category_parent = "former political divisions",
},
["FORMER county"] = {
-- For categorizing former counties and county-level cities of China
link = false,
fallback = "FORMER subpolity",
},
["FORMER county-level city"] = {
-- For categorizing former counties and county-level cities of China
link = false,
fallback = "FORMER subpolity",
},
["former countries and country-like entities!"] = {
category_link = "已不存在的[[國家]]和類似國家的[[政治實體]]",
bare_category_breadcrumb = "國家和類國家實體",
bare_category_parent = "former polities",
},
["FORMER country"] = {
link = false,
class = "polity",
default = {"已不存在的國家和類國家實體"},
},
["former dependent territories!"] = {
category_link = "[[w:dependent territory|dependent territories]] (colonies, dependencies, protectorates, etc.) that no longer exist",
bare_category_breadcrumb = "dependent territories",
bare_category_parent = "former political divisions",
},
["FORMER dependent territory"] = {
link = false,
preposition = "of",
class = "subpolity",
default = {"已不存在的屬地"},
},
["former districts!"] = {
-- For categorizing former districts of China
category_link = "no-longer-existing [[district]]s",
bare_category_breadcrumb = "districts",
bare_category_parent = "former political divisions",
},
["FORMER district"] = {
-- For categorizing former districts of China
link = false,
fallback = "FORMER subpolity",
},
["FORMER geographic region"] = {
link = false,
fallback = "geographic and cultural area",
},
["FORMER man-made structure"] = {
link = false,
class = "man-made structure",
default = {"已不存在的人造構築物"},
},
["former man-made structures!"] = {
category_link = "man-made structures such as [[airport]]s and [[park]]s that no longer exist",
bare_category_breadcrumb = "man-made structures",
bare_category_parent = "former places",
},
["former municipalities!"] = {
-- For categorizing former municipalities of the Netherlands
category_link = "no-longer-existing [[municipality|municipalities]]",
bare_category_breadcrumb = "municipalities",
bare_category_parent = "former political divisions",
},
["FORMER municipality"] = {
-- For categorizing former municipalities of the Netherlands
link = false,
fallback = "FORMER subpolity",
},
["FORMER natural feature"] = {
link = false,
class = "natural feature",
default = {"已不存在的自然地物"},
},
["former natural features!"] = {
category_link = "natural features such as [[lake]]s, [[river]]s and [[island]]s that no longer exist",
bare_category_breadcrumb = "natural features",
bare_category_parent = "former places",
},
["FORMER non-admin settlement"] = {
link = false,
class = "non-admin settlement",
fallback = "FORMER settlement",
},
["former places!"] = {
category_link = "[[place]]s of all sorts that no longer exist",
bare_category_breadcrumb = "former",
bare_category_parent = "places",
},
["former political divisions!"] = {
category_link = "[[political]] [[division]]s (states, provinces, counties, etc.) that no longer exist",
bare_category_breadcrumb = "political divisions",
bare_category_parent = "former places",
},
["former polities!"] = {
category_link = "[[polity|polities]] (countries, kingdoms, empires, etc.) that no longer exist",
bare_category_breadcrumb = "polities",
bare_category_parent = "former places",
},
["FORMER polity"] = {
link = false,
class = "polity",
default = {"已不存在的政治實體"},
},
["former prefectures!"] = {
-- For categorizing former prefectures of China
category_link = "no-longer-existing [[prefecture]]s",
bare_category_breadcrumb = "prefectures",
bare_category_parent = "former political divisions",
},
["FORMER prefecture"] = {
-- For categorizing former prefectures of China
link = false,
fallback = "FORMER subpolity",
},
["former provinces!"] = {
-- For categorizing former provinces of China, etc.
category_link = "no-longer-existing [[province]]s",
bare_category_breadcrumb = "provinces",
bare_category_parent = "former political divisions",
},
["FORMER province"] = {
-- For categorizing ancient/historical/former provinces of the Roman Empire
link = false,
fallback = "FORMER subpolity",
},
["former region"] = {
-- A former region is considered a former political division, but not a 'historical/traditional/etc.' region.
link = "separately",
preposition = "of",
inherently_former = {"FORMER"},
class = "subpolity",
},
["FORMER settlement"] = {
link = false,
has_neighborhoods = true,
class = "settlement",
default = {"已不存在的聚居地"},
},
["former settlements!"] = {
category_link = "[[city|cities]], [[town]]s and [[village]]s that no longer exist or have been merged or reclassified",
bare_category_breadcrumb = "settlements",
bare_category_parent = "former political divisions",
},
["FORMER subpolity"] = {
link = false,
preposition = "of",
class = "subpolity",
default = {"已不存在的政治區劃"},
},
----------------------------------------------------------------------------------------------
-- form-of categories --
----------------------------------------------------------------------------------------------
---------- Abbreviations ----------
["abbreviations of counties!"] = {
-- For categorizing abbreviations of counties of e.g. England
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[county|counties]]",
bare_category_breadcrumb = "counties",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of countries!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[country|countries]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "abbreviations of places",
},
["abbreviations of departments!"] = {
-- For categorizing abbreviations of departments of e.g. France
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[department]]s",
bare_category_breadcrumb = "departments",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of districts!"] = {
-- For categorizing abbreviations of districts of e.g. ???
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[district]]s",
bare_category_breadcrumb = "districts",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of divisions!"] = {
-- For categorizing abbreviations of divisions of e.g. Bangladesh
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[division]]s",
bare_category_breadcrumb = "divisions",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of former countries!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[country|countries]] that no longer [[exist]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "abbreviations of former places",
},
["abbreviations of former places!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[place]]s that no longer [[exist]]",
bare_category_breadcrumb = "abbreviations",
bare_category_parent = "former places",
addl_bare_category_parents = {{name = "abbreviations of places", sort = "former"}},
},
["abbreviations of places!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[place]]s",
bare_category_breadcrumb = "abbreviations",
bare_category_parent = "places",
},
["abbreviations of political divisions!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[political]] [[division]]s",
bare_category_breadcrumb = "political divisions",
bare_category_parent = "abbreviations of places",
},
["abbreviations of prefectures!"] = {
-- For categorizing abbreviations of prefectures of e.g. Japan
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[prefecture]]s",
bare_category_breadcrumb = "prefectures",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of provinces!"] = {
-- For categorizing abbreviations of provinces of e.g. Canada
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[province]]s",
bare_category_breadcrumb = "provinces",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of provinces and territories!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[province]]s and [[territory|territories]]",
bare_category_breadcrumb = "provinces and territories",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of regions!"] = {
-- For categorizing abbreviations of regions of e.g. Italy
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[administrative region]]s",
bare_category_breadcrumb = "regions",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of states!"] = {
-- For categorizing abbreviations of states of e.g. the United States
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[state]]s",
bare_category_breadcrumb = "states",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of states and territories!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[state]]s and [[territory|territories]]",
bare_category_breadcrumb = "states and territories",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of states and union territories!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[state]]s and [[union territory|union territories]]",
bare_category_breadcrumb = "states and union territories",
bare_category_parent = "abbreviations of political divisions",
},
["abbreviations of territories!"] = {
full_category_link = "{{glossary|abbreviation}}s of [[name]]s of [[territory|territories]]",
bare_category_breadcrumb = "territories",
bare_category_parent = "abbreviations of political divisions",
},
["ABBREVIATION_OF country"] = {
link = false,
default = {"國家縮寫"},
},
["ABBREVIATION_OF county"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF department"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF district"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF division"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF FORMER country"] = {
link = false,
default = {"已不存在國家的縮寫"},
},
["ABBREVIATION_OF FORMER place"] = {
link = false,
default = {"已不存在地點的縮寫"},
},
["ABBREVIATION_OF place"] = {
link = false,
default = {"地名縮寫"},
},
["ABBREVIATION_OF prefecture"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF province"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF region"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF state"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF subpolity"] = {
link = false,
default = {"政治區劃縮寫"},
},
["ABBREVIATION_OF territory"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
["ABBREVIATION_OF union territory"] = {
link = false,
fallback = "ABBREVIATION_OF subpolity",
},
---------- Archaic forms ----------
["archaic forms of places!"] = {
full_category_link = "{{glossary|archaic}} [[form]]s of [[name]]s of [[place]]s",
bare_category_breadcrumb = "archaic forms",
bare_category_parent = "places",
},
["ARCHAIC_FORM_OF place"] = {
link = false,
default = {"地名古舊形式"},
},
---------- Clippings ----------
["clippings of places!"] = {
full_category_link = "{{glossary|clipping}}s of [[name]]s of [[place]]s",
bare_category_breadcrumb = "clippings",
bare_category_parent = "places",
},
["CLIPPING_OF place"] = {
link = false,
default = {"地名截斷形式"},
},
---------- Dated forms ----------
["dated forms of places!"] = {
full_category_link = "{{glossary|dated}} [[form]]s of [[name]]s of [[place]]s",
bare_category_breadcrumb = "dated forms",
bare_category_parent = "places",
},
["DATED_FORM_OF place"] = {
link = false,
default = {"地名過時形式"},
},
---------- Derogatory names ----------
["derogatory names for cities!"] = {
full_category_link = "{{glossary|derogatory}} [[name]]s for [[city|cities]]",
bare_category_breadcrumb = "cities",
bare_category_parent = "derogatory names for places",
addl_bare_category_parents = {"nicknames for cities"},
},
["derogatory names for continents!"] = {
full_category_link = "{{glossary|derogatory}} [[name]]s for [[continent]]s",
bare_category_breadcrumb = "continents",
bare_category_parent = "derogatory names for places",
addl_bare_category_parents = {"nicknames for continents"},
},
["derogatory names for countries!"] = {
full_category_link = "{{glossary|derogatory}} [[name]]s for [[country|countries]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "derogatory names for places",
addl_bare_category_parents = {"nicknames for countries"},
},
["derogatory names for places!"] = {
full_category_link = "{{glossary|derogatory}} [[name]]s for [[place]]s",
bare_category_breadcrumb = "derogatory names",
bare_category_parent = "nicknames for places",
},
["derogatory names for states!"] = {
full_category_link = "{{glossary|derogatory}} [[name]]s for [[state]]s",
bare_category_breadcrumb = "states",
bare_category_parent = "derogatory names for places",
addl_bare_category_parents = {"nicknames for states"},
},
["DEROGATORY_NAME_FOR capital"] = {
link = false,
default = {"城市貶稱"},
},
["DEROGATORY_NAME_FOR city"] = {
link = false,
default = {"城市貶稱"},
},
["DEROGATORY_NAME_FOR continent"] = {
link = false,
default = {"大洲貶稱"},
},
["DEROGATORY_NAME_FOR country"] = {
link = false,
default = {"國家貶稱"},
},
["DEROGATORY_NAME_FOR metropolitan city"] = {
-- "metropolitan city" doesn't fall back to "city"
link = false,
default = {"城市貶稱"},
},
["DEROGATORY_NAME_FOR place"] = {
link = false,
default = {"地名貶稱"},
},
["DEROGATORY_NAME_FOR prefecture-level city"] = {
-- "prefecture-level city" doesn't fall back to "city" but things like "county-level city" and
-- "subprovincial city" fall back to "prefecture-level city"
link = false,
default = {"城市貶稱"},
},
["DEROGATORY_NAME_FOR state"] = {
link = false,
default = {"州貶稱"},
},
["DEROGATORY_NAME_FOR town"] = {
link = false,
default = {"城市貶稱"},
},
---------- Ellipses ----------
["ellipses of places!"] = {
full_category_link = "{{glossary|ellipsis|ellipses}} of [[name]]s of [[place]]s",
bare_category_breadcrumb = "ellipses",
bare_category_parent = "places",
},
["ELLIPSIS_OF place"] = {
link = false,
default = {"地名縮略形式"},
},
---------- Former long-form names ----------
["former long-form names of countries!"] = {
full_category_link = "no-longer-[[use]]d [[long]]-[[form]] (but typically [[unofficial]]) [[name]]s of [[country|countries]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "former long-form names of places",
addl_bare_category_parents = {{name = "former names of countries", sort = "long-form"}},
},
["former long-form names of places!"] = {
full_category_link = "no-longer-[[use]]d [[long]]-[[form]] (but typically [[unofficial]]) [[name]]s of [[place]]s",
bare_category_breadcrumb = "long-form",
bare_category_parent = "former names of places",
},
["FORMER_LONG_FORM_OF country"] = {
link = false,
default = {"國家前全稱"},
},
["FORMER_LONG_FORM_OF place"] = {
link = false,
default = {"地名前全稱"},
},
---------- Former names ----------
["former names of capitals!"] = {
full_category_link = "[[former]] [[name]]s of [[capital city|capital cities]] that generally still exist but under a different name",
bare_category_breadcrumb = "capitals",
bare_category_parent = "former names of settlements",
},
["former names of countries!"] = {
full_category_link = "[[former]] [[name]]s of [[country|countries]] that generally still exist but under a different name",
bare_category_breadcrumb = "countries",
bare_category_parent = "former names of places",
},
["former names of places!"] = {
full_category_link = "[[former]] [[name]]s of [[place]]s that generally still exist but under a different name",
bare_category_breadcrumb = "former names",
bare_category_parent = "places",
},
["former names of political divisions!"] = {
full_category_link = "[[former]] [[name]]s of [[political]] [[division]]s (states, provinces, counties, etc.) that generally still exist but under a different name",
bare_category_breadcrumb = "political divisions",
bare_category_parent = "former names of places",
},
["former names of polities!"] = {
full_category_link = "[[former]] [[name]]s of [[polity|polities]] (e.g. [[country|countries]]) that generally still exist but under a different name",
bare_category_breadcrumb = "polities",
bare_category_parent = "former names of places",
},
["former names of settlements!"] = {
full_category_link = "[[former]] [[name]]s of [[city|cities]], [[town]]s, [[village]]s, etc. that generally still exist but under a different name",
bare_category_breadcrumb = "settlements",
bare_category_parent = "former names of political divisions",
},
["FORMER_NAME_OF capital"] = {
link = false,
default = {"首都舊名"},
},
["FORMER_NAME_OF country"] = {
link = false,
default = {"國家舊名"},
},
["FORMER_NAME_OF place"] = {
link = false,
default = {"地名舊名"},
},
["FORMER_NAME_OF polity"] = {
link = false,
default = {"政治實體舊名"},
},
["FORMER_NAME_OF region"] = {
link = false,
fallback = "FORMER_NAME_OF subpolity",
},
["FORMER_NAME_OF settlement"] = {
link = false,
default = {"聚落舊名"},
},
["FORMER_NAME_OF subpolity"] = {
link = false,
default = {"政治區劃舊名"},
},
---------- Former nicknames ----------
["former nicknames for cities!"] = {
full_category_link = "no-longer-used [[nickname]]s for [[city|cities]], e.g. the [[Eternal City]] for [[Kyoto]] during the {{w|Heian period}} ({{circa2|800–1100|short=yes}} {{AD}})",
bare_category_breadcrumb = "cities",
bare_category_parent = "former nicknames for places",
addl_bare_category_parents = {"nicknames for cities"},
},
["former nicknames for places!"] = {
full_category_link = "no-longer-used [[nickname]]s for [[place]]s",
bare_category_breadcrumb = "former",
bare_category_parent = "nicknames for places",
addl_bare_category_parents = {{name = "former names of places", sort = "nicknames"}},
},
["FORMER_NICKNAME_FOR capital"] = {
link = false,
default = {"城市前別稱"},
},
["FORMER_NICKNAME_FOR city"] = {
link = false,
default = {"城市前別稱"},
},
["FORMER_NICKNAME_FOR metropolitan city"] = {
-- "metropolitan city" doesn't fall back to "city"
link = false,
default = {"城市前別稱"},
},
["FORMER_NICKNAME_FOR place"] = {
link = false,
default = {"地名前別稱"},
},
["FORMER_NICKNAME_FOR prefecture-level city"] = {
-- "prefecture-level city" doesn't fall back to "city" but things like "county-level city" and
-- "subprovincial city" fall back to "prefecture-level city"
link = false,
default = {"城市前別稱"},
},
["FORMER_NICKNAME_FOR town"] = {
link = false,
default = {"城市前別稱"},
},
---------- Former official names ----------
["former official names of countries!"] = {
full_category_link = "no-longer-[[use]]d [[official]] [[name]]s of [[country|countries]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "former official names of places",
addl_bare_category_parents = {{name = "former names of countries", sort = "official"}},
},
["former official names of places!"] = {
full_category_link = "no-longer-[[use]]d [[official]] [[name]]s of [[place]]s",
bare_category_breadcrumb = "official",
bare_category_parent = "former names of places",
},
["FORMER_OFFICIAL_NAME_OF country"] = {
link = false,
default = {"國家前官方名稱"},
},
["FORMER_OFFICIAL_NAME_OF place"] = {
link = false,
default = {"地名前官方名稱"},
},
---------- Long-form names ----------
["long-form names of countries!"] = {
full_category_link = "[[long]]-[[form]] (but typically [[unofficial]]) [[name]]s of [[country|countries]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "long-form names of places",
},
["long-form names of places!"] = {
full_category_link = "[[long]]-[[form]] (but typically [[unofficial]]) [[name]]s of [[place]]s",
bare_category_breadcrumb = "long-form names",
bare_category_parent = "places",
},
["LONG_FORM_OF country"] = {
link = false,
default = {"國家全稱"},
},
["LONG_FORM_OF place"] = {
link = false,
default = {"地名全稱"},
},
---------- Nicknames ----------
["nicknames for cities!"] = {
full_category_link = "[[nickname]]s for [[city|cities]], e.g. the [[Big Apple]] for [[New York City]]",
bare_category_breadcrumb = "cities",
bare_category_parent = "nicknames for places",
addl_bare_category_parents = {"cities"},
},
["nicknames for continents!"] = {
full_category_link = "[[nickname]]s for [[continent]]s",
bare_category_breadcrumb = "continents",
bare_category_parent = "nicknames for places",
addl_bare_category_parents = {"continents"},
},
["nicknames for countries!"] = {
full_category_link = "[[nickname]]s for [[country|countries]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "nicknames for places",
addl_bare_category_parents = {"countries"},
},
["nicknames for places!"] = {
full_category_link = "[[nickname]]s for [[place]]s",
bare_category_breadcrumb = "places",
bare_category_parent = "nicknames",
addl_bare_category_parents = {"places"},
},
["nicknames for states!"] = {
-- For categorizing nicknames for states of e.g. the United States
full_category_link = "[[nicknames]] for [[state]]s",
bare_category_breadcrumb = "states",
bare_category_parent = "nicknames for places",
addl_bare_category_parents = {"states"},
},
["NICKNAME_FOR capital"] = {
link = false,
default = {"城市別稱"},
},
["NICKNAME_FOR city"] = {
link = false,
default = {"城市別稱"},
},
["NICKNAME_FOR continent"] = {
link = false,
default = {"大洲別稱"},
},
["NICKNAME_FOR country"] = {
link = false,
default = {"國家別稱"},
},
["NICKNAME_FOR metropolitan city"] = {
-- "metropolitan city" doesn't fall back to "city"
link = false,
default = {"城市別稱"},
},
["NICKNAME_FOR place"] = {
link = false,
default = {"地名別稱"},
},
["NICKNAME_FOR prefecture-level city"] = {
-- "prefecture-level city" doesn't fall back to "city" but things like "county-level city" and
-- "subprovincial city" fall back to "prefecture-level city"
link = false,
default = {"城市別稱"},
},
["NICKNAME_FOR state"] = {
link = false,
default = {"州別稱"},
},
["NICKNAME_FOR town"] = {
link = false,
default = {"城市別稱"},
},
---------- Obsolete forms ----------
["obsolete forms of places!"] = {
full_category_link = "{{glossary|obsolete}} [[form]]s of [[name]]s of [[place]]s",
bare_category_breadcrumb = "obsolete forms",
bare_category_parent = "places",
},
["OBSOLETE_FORM_OF place"] = {
link = false,
default = {"地名棄用形式"},
},
---------- Official names ----------
["official names of countries!"] = {
full_category_link = "[[official]] [[name]]s of [[country|countries]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "official names of places",
},
["official names of former countries!"] = {
full_category_link = "[[official]] [[name]]s of [[country|countries]] that no longer [[exist]]",
bare_category_breadcrumb = "countries",
bare_category_parent = "official names of former places",
},
["official names of former places!"] = {
full_category_link = "[[official]] [[name]]s of [[place]]s that no longer [[exist]]",
bare_category_breadcrumb = "official names",
bare_category_parent = "former places",
addl_bare_category_parents = {{name = "official names of places", sort = "former"}},
},
["official names of places!"] = {
full_category_link = "[[official]] [[name]]s of [[place]]s",
bare_category_breadcrumb = "official names",
bare_category_parent = "places",
},
["OFFICIAL_NAME_OF country"] = {
link = false,
default = {"國家官方名稱"},
},
["OFFICIAL_NAME_OF FORMER country"] = {
link = false,
default = {"已不存在國家的官方名稱"},
},
["OFFICIAL_NAME_OF FORMER place"] = {
link = false,
default = {"已不存在地點的官方名稱"},
},
["OFFICIAL_NAME_OF place"] = {
link = false,
default = {"地名官方名稱"},
},
---------- Official nicknames ----------
["official nicknames for places!"] = {
full_category_link = "[[official]] [[nickname]]s for [[place]]s",
bare_category_breadcrumb = "official",
bare_category_parent = "nicknames for places",
},
["official nicknames for states!"] = {
-- For categorizing official nicknames for states of e.g. the United States
full_category_link = "[[official]] [[nicknames]] for [[state]]s",
bare_category_breadcrumb = "official",
bare_category_parent = "nicknames for states",
addl_bare_category_parents = {"states"},
},
["OFFICIAL_NICKNAME_FOR place"] = {
link = false,
default = {"地名官方別稱"},
},
["OFFICIAL_NICKNAME_FOR state"] = {
link = false,
default = {"州官方別稱"},
},
}
export.plural_placetype_to_singular = {}
for sg_placetype, spec in pairs(export.placetype_data) do
if spec.plural then
export.plural_placetype_to_singular[spec.plural] = sg_placetype
end
end
------------------------------------------------------------------------------------------
-- ZH: Chinese localization functions --
------------------------------------------------------------------------------------------
--[==[
Get the Chinese display name for a placetype, resolving through the enwikt fallback chain.
`holonym_placetype` (optional string) provides context for placetypes like `capital city`
whose Chinese term depends on the containing administrative level (country → 首都,
province → 省會, state → 首府, etc.).
Returns two values: zh_name (string or array of regional variants), zh_suffix (string,
array, or nil). Returns nil, nil if no Chinese data is available for the placetype or
any of its fallbacks.
]==]
-- Maps Chinese alias input → override display name. When a user enters a zh alias
-- that differs from the canonical zh_name, the alias itself is shown (categories
-- and logic still follow the canonical placetype).
export.zh_placetype_display_overrides = {
}
function export.get_zh_placetype_name(placetype, holonym_placetype)
local display_override = export.zh_placetype_display_overrides[placetype]
placetype = export.resolve_placetype_aliases(placetype)
local zh_name, zh_suffix, zh_by_holonym = m_zh_data.get_zh_placetype_props(placetype, export)
if zh_name then
local resolved = display_override or m_zh_data.resolve_zh_name(zh_name, zh_by_holonym, holonym_placetype)
return resolved, zh_suffix
end
return nil, nil
end
--[==[
Get only the Chinese suffix for a placetype.
Used when rendering holonym `:pref`/`:suf` modifiers in Chinese: the suffix is appended
directly to the holonym name (e.g. `s:suf/New York` → 紐約州; `c:pref/Georgia` → 格魯吉亞國).
Returns a string, an array of regional variants, or nil if no suffix is defined.
]==]
function export.get_zh_placetype_suffix(placetype)
placetype = export.resolve_placetype_aliases(placetype)
local _, zh_suffix = m_zh_data.get_zh_placetype_props(placetype, export)
return zh_suffix
end
--[==[
Chinese placetype "pluralization" — a no-op, since Chinese has no grammatical plural.
Accepts and returns a string or an array (for multi-variant names) unchanged.
Provided so that call sites shared with the English path can call a uniform function.
]==]
function export.pluralize_zh_placetype(zh_name)
return zh_name
end
--[==[
Get the entry preposition for Chinese display: "in" or "of".
Identical to `get_placetype_entry_preposition` in semantics; the Chinese rendering
maps these to different constructions:
"in" → 位於 [holonyms] 的 [placetype]
"of" → [holonyms] 的 [placetype] (no 位於 prefix)
]==]
export.get_zh_entry_preposition = export.get_placetype_entry_preposition
-- Re-export Chinese data tables so that [[Module:place]] only needs to require
-- this module rather than both this module and [[Module:place/zh-data]] separately.
export.zh_strings = m_zh_data.zh_strings
export.zh_qualifiers = m_zh_data.zh_qualifiers
export.zh_extra_info_labels = m_zh_data.zh_extra_info_labels
export.zh_capital_label_by_holonym_type = m_zh_data.zh_capital_label_by_holonym_type
export.format_zh_name = m_zh_data.format_zh_name
--[==[
Build wikilink markup for a Chinese placetype display name.
`zh_name`: formatted string (output of format_zh_name).
`link`: the `link` field value from placetype_data for the canonical placetype.
Link semantics:
true → [[zh_name]] (local zhwikt entry)
"w" → [[w:zh_name|zh_name]] (Chinese Wikipedia)
"+w:..." → [[w:zh_name|zh_name]] (Chinese Wikipedia; English target discarded)
"separately" → [[zh_name]] (no zh_name_parts; whole name linked)
"[[...]]..." → returned as-is (pre-formed Chinese link string)
false/nil → zh_name (no link)
]==]
local function make_zh_placetype_link(zh_name, link)
if not link or link == false then
return zh_name
elseif link == true then
return ("[[%s]]"):format(zh_name)
elseif link == "w" then
return ("[[w:%s|%s]]"):format(zh_name, zh_name)
elseif link == "separately" then
return ("[[%s]]"):format(zh_name)
elseif type(link) == "string" and link:find("^%+") then
-- "+w:SOMETHING" — English article name irrelevant for Chinese; link to Chinese Wikipedia
return ("[[w:%s|%s]]"):format(zh_name, zh_name)
elseif type(link) == "string" and link:find("[[", 1, true) then
return link
end
end
--[==[
Get the Chinese display form for a placetype, with wikilink markup applied.
Like `get_zh_placetype_name` but wraps the result in the appropriate wikilink based on
the placetype's `link` field. Returns nil if no Chinese data is available.
]==]
function export.get_zh_placetype_display_form(placetype, holonym_placetype)
local zh_name, _ = export.get_zh_placetype_name(placetype, holonym_placetype)
if not zh_name then
return nil
end
local formatted = m_zh_data.format_zh_name(zh_name)
local sg_pt = export.resolve_placetype_aliases(placetype)
local ptdata = export.placetype_data[sg_pt]
local link = ptdata and ptdata.link
return make_zh_placetype_link(formatted, link)
end
return export
psphwzi1xgu8qzu3x1amdsefi8qdp6p
Module:See
828
3489877
9909036
9825903
2026-09-29T07:23:57Z
TongcyBot
83009
同步 enwikt:pron qualifier → decorations
9909036
Scribunto
text/plain
local export = {}
local etymology_module = "Module:etymology"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
local parse_utilities_module = "Module:parse utilities"
local table_module = "Module:table"
local function term_already_linked(term)
-- optimization to avoid unnecessarily loading [[Module:parse utilities]]
return term:find("[<{]") and require(parse_utilities_module).term_already_linked(term)
end
function export.see(frame)
local parent_args = frame:getParent().args
local boolean = {type = "boolean"}
local params = {
[1] = {required = true, type = "language", default = "und"},
[2] = {list = true, allow_holes = true},
["noast"] = boolean,
["and"] = boolean,
["compare"] = boolean,
["der"] = boolean,
["notes"] = true,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "l"}},
-- For compatibility, we don't distinguish q= from q1= and qq= from q1=. FIXME: Maybe we should change this.
{group = "q", separate_no_index = false},
}
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
parse_lang_prefix = true,
track_module = "see",
sc = "sc.default",
}
local lang = args[1]
if not next(items) and mw.title.getCurrentTitle().nsText == "Template" then
items = { {term = "term"} }
end
local textparts = {}
local function ins(text)
table.insert(textparts, text)
end
if not args.noast then
ins("* ")
end
ins("")
if args["and"] then
ins("並")
end
if args.compare then
ins("對比:")
elseif args.der then
ins("參見衍生詞:")
else
ins("參見:")
end
ins("")
local termparts = {}
-- Make links out of all the parts.
for _, item in ipairs(items) do
local result
if item.lang then
result = require(etymology_module).format_derived {
terms = {item},
sources = {item.lang},
-- Don't need to specify `nocat = true` because we don't pass in `lang`.
template_name = "see",
decorations_on_outside = true,
}
else
local raw_term = item.alt or item.term
if raw_term and term_already_linked(raw_term) then
result = raw_term
else
item.lang = lang
-- l10n: zhwikt 的 [[Module:parameter utilities]] 不會自動設定 show_decorations,需在此設定
item.show_decorations = true
result = require(links_module).full_link(item, nil, false)
end
end
table.insert(termparts, result)
end
if #termparts == 1 then
ins(termparts[1])
else
ins(require(table_module).serialCommaJoin(termparts, {conj = "和"}))
end
if args.notes then
ins(" ")
ins(args.notes)
ins("")
end
return table.concat(textparts)
end
return export
driobmesdv4y7nl5wvg72tbtfdss79v
9909184
9909036
2026-09-29T09:47:41Z
TongcyBot
83009
移除手動 show_decorations
9909184
Scribunto
text/plain
local export = {}
local etymology_module = "Module:etymology"
local links_module = "Module:links"
local parameter_utilities_module = "Module:parameter utilities"
local parse_utilities_module = "Module:parse utilities"
local table_module = "Module:table"
local function term_already_linked(term)
-- optimization to avoid unnecessarily loading [[Module:parse utilities]]
return term:find("[<{]") and require(parse_utilities_module).term_already_linked(term)
end
function export.see(frame)
local parent_args = frame:getParent().args
local boolean = {type = "boolean"}
local params = {
[1] = {required = true, type = "language", default = "und"},
[2] = {list = true, allow_holes = true},
["noast"] = boolean,
["and"] = boolean,
["compare"] = boolean,
["der"] = boolean,
["notes"] = true,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "ref", "l"}},
-- For compatibility, we don't distinguish q= from q1= and qq= from q1=. FIXME: Maybe we should change this.
{group = "q", separate_no_index = false},
}
local items, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
parse_lang_prefix = true,
track_module = "see",
sc = "sc.default",
}
local lang = args[1]
if not next(items) and mw.title.getCurrentTitle().nsText == "Template" then
items = { {term = "term"} }
end
local textparts = {}
local function ins(text)
table.insert(textparts, text)
end
if not args.noast then
ins("* ")
end
ins("")
if args["and"] then
ins("並")
end
if args.compare then
ins("對比:")
elseif args.der then
ins("參見衍生詞:")
else
ins("參見:")
end
ins("")
local termparts = {}
-- Make links out of all the parts.
for _, item in ipairs(items) do
local result
if item.lang then
result = require(etymology_module).format_derived {
terms = {item},
sources = {item.lang},
-- Don't need to specify `nocat = true` because we don't pass in `lang`.
template_name = "see",
decorations_on_outside = true,
}
else
local raw_term = item.alt or item.term
if raw_term and term_already_linked(raw_term) then
result = raw_term
else
item.lang = lang
result = require(links_module).full_link(item, nil, false)
end
end
table.insert(termparts, result)
end
if #termparts == 1 then
ins(termparts[1])
else
ins(require(table_module).serialCommaJoin(termparts, {conj = "和"}))
end
if args.notes then
ins(" ")
ins(args.notes)
ins("")
end
return table.concat(textparts)
end
return export
jt2gefh9cbnf0sb61x8rtu8ollu6l4o
猿鶴羞
0
3538194
9908772
9906087
2026-09-28T15:11:54Z
徐元昊
122104
修正筆誤
9908772
wikitext
text/x-wiki
==漢語==
{{was wotd|2026|10月|7日}}
{{zh-forms|s=猿鹤羞}}
===發音===
{{zh-pron
|m=yuánhèxiū
|c=jyun4 hok6 sau1
|mc=y
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|書面}} [[隱士]]重新[[出仕]],對於違背了最初[[歸隱]]的志向而感到[[慚愧]]
#* {{zh-x|丈夫 已 忍 猿鶴羞,欲 去 且 復 斯須 留。||TCP|ref=宋·{{w|陳與義}}〈[[s:簡齋詩集/26#遙碧軒作呈使君少隱時欲赴召|遙碧軒作時欲赴召]]〉}}
#* {{zh-x|^衡陽 小隱 雖 深幽,去 天 尺五 君 無 留。貂蟬 兜鍪 何足道,君 必 不 為{wéi} 猿鶴羞。||TCP|ref=宋·{{w|張孝祥}}〈[[s:兩宋名賢小集_(四庫全書本)/卷144#贈張欽州|贈張欽州]]〉}}
#* {{zh-x|蹇 予{yú} 五 載 滯 邊州,去 住 空 遺 猿鶴羞。君 過 ^辰 ^沅 定 相{xiāng}憶,扁{piān}舟 予{yú} 亦 下 ^湘 流。||TCP|ref=明·{{w|程旦}}〈[[s:粤西詩載_(四庫全書本)/卷08#送洪方伯玉方之任廣西|送洪方伯玉方之任廣西]]〉}}
===參考資料===
* {{R:zh:HYDCD}}
94ar2267nw0z5yoeu3uhkasgnpwgjb5
武射
0
3539771
9908739
9908150
2026-09-28T12:07:15Z
徐元昊
122104
修正筆誤
9908739
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|m=wǔshè
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|historical}} 古代[[弓箭手]]的[[職名]]
#* {{zh-x|w:張次公\^次公 父 w:張隆\^隆,輕車 武射 也。以 善 射,w:漢景帝\^景帝 幸 近 之 也。||CL|ref=漢·{{w|司馬遷}}《[[s:史記/卷111|史記·衛將軍驃騎列傳]]》}}
# {{lb|zh|historical}} 相對於[[禮射]],指以[[實戰]][[殺敵]]為[[目的]]之[[射術]]
#* {{zh-x|夫子 亦 非 是 惡{wù} 貫革 之 射。但是 當時 皆 習 於 此,故 言 古人 之 道 耳。如 古人 亦 只是 禮射 不 主 皮;若 武射,依舊 要 貫革。若 不 貫革,何 益?||CL|ref=《[[s:朱子語類/025#射不主皮章|朱子語類·卷二十五·論語七·射不主皮章]]》}}
#* {{zh-x|武射 法:披挂 戎衣,側身 斜立,左肩 對 把,右肩 直列,眼 看 把 中線 直 截至 前 腳跟 底。||CL|ref=明·{{w|顧煜}}等《射書·卷二·武射法》}}
#* {{zh-x|^澤宮 兼 習 武射。《w:尚書大傳\^尚書大傳》謂 蒐狩 陳 餘獲 于 ^澤宮,卿大夫 射 而 取 之,所謂 主皮 之 射 也。《w:周官\^周官·^司弓矢》澤{shì} 其 射 椹{zhēn}質 之 弓矢,則 武射 在 ^澤宮 明 矣。||CL|ref=清·{{w|金鶚}}《求古錄禮說·卷二》}}
#* {{zh-x|禮射、武射 在 盛世 是 並行 的,在 夫子 時 亦 不是 廢 禮射 不 用,只是 雖 禮射 亦 主皮。||CL|ref=清·{{w|陸隴其}}《[[s:四書講義困勉録 (四庫全書本)/卷06|四書講義困勉錄·卷六]]》}}
l56v7ek6wk720mkpgpga28hi1tjed1t
9908740
9908739
2026-09-28T12:08:01Z
徐元昊
122104
維基化
9908740
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|m=wǔshè
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|historical}} 古代[[弓箭手]]的[[職名]]
#* {{zh-x|w:張次公\^次公 父 w:張隆\^隆,輕車 武射 也。以 善 射,w:漢景帝\^景帝 幸 近 之 也。||CL|ref=漢·{{w|司馬遷}}《[[s:史記/卷111|史記·衛將軍驃騎列傳]]》}}
# {{lb|zh|historical}} 相對於[[禮射]],指以[[實戰]][[殺敵]]為[[目的]]之[[射術]]
#* {{zh-x|夫子 亦 非 是 惡{wù} 貫革 之 射。但是 當時 皆 習 於 此,故 言 古人 之 道 耳。如 古人 亦 只是 禮射 不 主 皮;若 武射,依舊 要 貫革。若 不 貫革,何 益?||CL|ref=《[[s:朱子語類/025#射不主皮章|朱子語類·卷二十五·論語七·射不主皮章]]》}}
#* {{zh-x|武射 法:披挂 戎衣,側身 斜立,左肩 對 把,右肩 直列,眼 看 把 中線 直 截至 前 腳跟 底。||CL|ref=明·{{w|顧煜}}等《射書·卷二·武射法》}}
#* {{zh-x|^澤宮 兼 習 武射。《w:尚書大傳\^尚書大傳》謂 蒐狩 陳 餘獲 于 ^澤宮,卿大夫 射 而 取 之,所謂 主皮 之 射 也。《w:周官\^周官·^司弓矢》澤{shì} 其 射 椹{zhēn}質 之 弓矢,則 武射 在 ^澤宮 明 矣。||CL|ref=清·{{w|金鶚}}《求古錄禮說·卷二》}}
#* {{zh-x|禮射、武射 在 盛世 是 並行 的,在 夫子 時 亦 不是 廢 禮射 不 用,只是 雖 禮射 亦 主皮。||CL|ref=清·{{w|陸隴其}}《[[s:四書講義困勉録 (四庫全書本)/卷06|四書講義困勉錄·卷六]]》}}
===參考資料===
* {{R:zh:HYDCD}}
9kct02se5qc95k6qusm8mphnzi34ej6
world record
0
3540285
9908748
2026-09-28T13:05:26Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==英語== {{wp|en:}} ===發音=== * {{audio|en|LL-Q1860 (eng)-Vealhurl-world record.wav|a=Southern England}} ===名詞=== {{en-noun}} # {{lb|en|運動}} [[世界紀錄]] # {{lb|en|idiomatic}} [[超越]]所有其他[[同类]][[特征]]或[[成就]]者 ====近義詞==== * {{l|en|world's record}} * {{l|en|WR}}」的新頁面
9908748
wikitext
text/x-wiki
==英語==
{{wp|en:}}
===發音===
* {{audio|en|LL-Q1860 (eng)-Vealhurl-world record.wav|a=Southern England}}
===名詞===
{{en-noun}}
# {{lb|en|運動}} [[世界紀錄]]
# {{lb|en|idiomatic}} [[超越]]所有其他[[同类]][[特征]]或[[成就]]者
====近義詞====
* {{l|en|world's record}}
* {{l|en|WR}}
8y2wmey5uwfgwoi5tmnfoywkuq55q1n
world records
0
3540286
9908749
2026-09-28T13:05:30Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==英語== ===名詞=== {{head|en|名詞變格形}} # {{plural of|en|world record}}」的新頁面
9908749
wikitext
text/x-wiki
==英語==
===名詞===
{{head|en|名詞變格形}}
# {{plural of|en|world record}}
5f2zkxzl3cwa4zuretvtr49uy7g53gd
sci-fi
0
3540287
9908750
2026-09-28T13:12:32Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「{{also|scifi|sci fi}} ==英語== ===其他形式=== * {{alt|en|sci fi|scifi}} ===詞源=== {{clipping|en|science fiction}}。 ===發音=== * {{IPA|en|/saɪ.faɪ/|a=UK}} * {{audio|en|LL-Q1860 (eng)-Vealhurl-sci-fi.wav|a=Southern England}} * {{rhymes|en|aɪfaɪ|s=2}} ===名詞=== {{en-noun|-|nolink=1}} # [[科幻]](電影題材) #* {{quote-journal|en|year=1970|author=National Board of Review of Motion Pictures (contributor)|title=|jour…」的新頁面
9908750
wikitext
text/x-wiki
{{also|scifi|sci fi}}
==英語==
===其他形式===
* {{alt|en|sci fi|scifi}}
===詞源===
{{clipping|en|science fiction}}。
===發音===
* {{IPA|en|/saɪ.faɪ/|a=UK}}
* {{audio|en|LL-Q1860 (eng)-Vealhurl-sci-fi.wav|a=Southern England}}
* {{rhymes|en|aɪfaɪ|s=2}}
===名詞===
{{en-noun|-|nolink=1}}
# [[科幻]](電影題材)
#* {{quote-journal|en|year=1970|author=National Board of Review of Motion Pictures (contributor)|title=|journal=Films in Review|url=https://www.google.com/books/edition/Films_in_Review/wRELAQAAMAAJ?hl=en&gbpv=1&bsq=%22I%27m%20the%20kind%20of%20sci-fi%20addict%22|volume=21|issue=|page=571|text=I'm the kind of '''sci-fi''' addict who can stand any amount of improbability, bad acting, uninterested direction and poverty-row budgets, but can't take ''The Mind of Mr. Soames''.|t=我是一个'''科幻'''迷,可以忍受任何程度的不合情理、糟糕的演技、漫不经心的导演和低成本制作,但我无法接受《索姆斯先生的思维》。}}
#* {{quote-journal|en|author=Emmanuel Tsekleves|title=Science fiction as fact: how desires drive discoveries|magazine=w:The Guardian|url=https://www.theguardian.com/media-network/2015/aug/13/science-fiction-reality-predicts-future-technology|date=2015-08-13|passage=Several '''sci-fi''' films and novels have very accurately predicted and paved the way for many of the pieces of technology we enjoy today.|t=许多'''科幻'''电影和小说都非常准确地预测并铺平了我们今天所享受的许多技术的发展道路。|archiveurl=https://web.archive.org/web/20160618073242/https://www.theguardian.com/media-network/2015/aug/13/science-fiction-reality-predicts-future-technology}}
# {{lb|en|口語}} {{abbr of|en|science fiction}}。
===形容詞===
{{en-adj|?}}
# [[科幻]]的
====用法說明====
* 科幻小说爱好者有时会贬低 ''sci-fi'' 这个词,如果一定要用缩写形式,他们可能更喜欢用 [[SF]]。
====近義詞====
* [[science fiction]]、[[science-fiction]]、[[sci-fix]]、[[sci-fic]]
* [[SF]]、[[S.F.]]、[[S. F.]]、[[sf]]、[[s.f.]]
====派生詞彙====
{{col|en|skiffy}}
===異序詞===
* {{anagrams|en|a=cfiis|fisci}}
{{cln|en|押韻短語}}
{{C|en|類型|科幻}}
==捷克語==
{{wikipedia|lang=cs}}
===發音===
* {{cs-IPA}}
===名詞===
{{cs-noun|f,n|indecl=1}}
# [[科幻]](電影題材)
===延伸閱讀===
* {{R:cs:IJP}}
{{c|cs|類型}}
==匈牙利語==
===詞源===
{{obor|hu|en|sci-fi}}。
===發音===
* {{hu-IPA|szkifi|szcifi|szifi}}<ref>[https://e-nyelv.hu/2010-04-01/sci-fi-kiejtese/ ''Sci-fi'' kiejtése] (e-nyelv.hu advice), recommending either of the first two, based on {{R:OsirisHelyes 2006}}</ref><ref>[https://www.gyakorikerdesek.hu/kultura-es-kozosseg__muveszetek__8480336-miert-lett-a-tudomanyos-fantasztikus-szki-fi-es-nem-szci-fi Miért lett a ''tudományos fantasztikus'' ''"SZKI_FI"?'' És nem ''szci-fi?''] (GyakoriKérdések.hu, “Common Questions”, open discussion forum)</ref><ref>[https://www.gyakorikerdesek.hu/kultura-es-kozosseg__nyelvek__2488337-hogyan-kellene-magyarul-kiejteni-a-sci-fi-szot Hogyan kellene magyarul kiejteni a ''sci-fi'' szót?] (GyakoriKérdések.hu, “Common Questions”, open discussion forum)</ref>
* {{hyphenation|hu|sci-|fi}}
===名詞===
{{hu-noun|~|k}}
# [[科幻]](電影題材)
#: {{syn|hu|[[tudományos-fantasztikus]] [[irodalom]]/[[műfaj]]/[[mű]]|[[tudományos]] [[fantasztikum]]|q2=罕用}}
====變格====
{{hu-infl-nom|sci-fi|e|-}}{{hu-pos-tek|sci-fi|je}}
===參考資料===
{{reflist}}
{{c|hu|類型}}
9bq3plzira0qjw9ih3uiwl5u9v1zph4
禮射
0
3540288
9908751
2026-09-28T13:19:53Z
徐元昊
122104
新詞條
9908751
wikitext
text/x-wiki
==漢語==
{{zh-forms|s=礼射}}
===發音===
{{zh-pron
|m=lǐshè
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|historical|Confucianism}} [[古代]][[相對]]於[[武射]],[[注重]][[禮樂]][[規範]]與[[身心]][[德行]][[修養]]的[[射箭]][[典禮]]或[[射藝]]
===參考資料===
* {{R:zh:HYDCD}}
qp28r81ue1w3r6qwfk2socf1sdg5utc
9908752
9908751
2026-09-28T13:22:19Z
徐元昊
122104
內容擴充
9908752
wikitext
text/x-wiki
==漢語==
{{zh-forms|s=礼射}}
===發音===
{{zh-pron
|m=lǐshè
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|historical|Confucianism}} [[古代]][[相對]]於[[武射]],[[注重]][[禮樂]][[規範]]與[[身心]][[德行]][[修養]]的[[射箭]][[典禮]]或[[射藝]]
===參考資料===
* {{R:zh:HYDCD}}
{{C|zh|儒教}}
ivjfv39bahphkz9h9utrpfwb3du3f51
9908753
9908752
2026-09-28T13:25:03Z
徐元昊
122104
內容擴充
9908753
wikitext
text/x-wiki
==漢語==
{{zh-forms|s=礼射}}
===發音===
{{zh-pron
|m=lǐshè
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|historical|Confucianism}} [[古代]][[相對]]於[[武射]],[[注重]][[禮樂]][[規範]]與[[身心]][[德行]][[修養]]的[[射箭]][[典禮]]或[[射藝]]
===參考資料===
* {{R:zh:HYDCD}}
{{C|zh|儒教}}
==朝鮮語==
{{ko-hanjatab}}
===名詞===
{{ko-noun|hj|hangeul=예사}}
# {{hanja form of|예사}}
52kptnbxo54zd7fjivrdpxglpyyqplu
9908760
9908753
2026-09-28T13:54:40Z
TongcyDai
53191
overlink
9908760
wikitext
text/x-wiki
==漢語==
{{zh-forms|s=礼射}}
===發音===
{{zh-pron
|m=lǐshè
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|historical|Confucianism}} 古代相對於[[武射]],注重[[禮樂]][[規範]]與[[身心]][[德行]][[修養]]的[[射箭]][[典禮]]或[[射藝]]
===參考資料===
* {{R:zh:HYDCD}}
{{C|zh|儒教}}
==朝鮮語==
{{ko-hanjatab}}
===名詞===
{{ko-noun|hj|hangeul=예사}}
# {{hanja form of|예사}}
a14uhu4rfnkfu4pclwmtybrgnbecmr0
9908761
9908760
2026-09-28T13:57:06Z
TongcyDai
53191
9908761
wikitext
text/x-wiki
==漢語==
{{zh-forms|s=礼射}}
===發音===
{{zh-pron
|m=lǐshè
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|historical|Confucianism}} 古代相對於[[武射]],注重[[禮樂]][[規範]]與[[身心]][[德行]][[修養]]的[[射箭]][[典禮]]或[[射藝]]
===參考資料===
* {{R:zh:HYDCD}}
{{C|zh|儒教}}
==日語==
{{ja-kanjitab|れい|しゃ|yomi=on}}
===名詞===
{{ja-noun|れいしゃ|hhira=れいしや}}
# {{ja-kyujitai spelling of|礼射}}
==朝鮮語==
{{ko-hanjatab}}
===名詞===
{{ko-noun|hj|hangeul=예사}}
# {{hanja form of|예사}}
f9sea5p5u14np3eeyvhsbewmdtk8lpp
흰개미
0
3540289
9908754
2026-09-28T13:27:20Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==朝鮮語== {{wp|ko:}} ===詞源=== 源自 {{com|ko|흰|개미|t1=白色|t2=螞蟻}}。對照{{cog|en|white ant}}、{{cog|zh|白蟻}}、{{cog|ja|白蟻|tr=shiroari}}。 ===發音=== {{ko-ipa}} ===名詞=== {{ko-noun}} # [[白蟻]] {{c|ko|等翅下目}}」的新頁面
9908754
wikitext
text/x-wiki
==朝鮮語==
{{wp|ko:}}
===詞源===
源自 {{com|ko|흰|개미|t1=白色|t2=螞蟻}}。對照{{cog|en|white ant}}、{{cog|zh|白蟻}}、{{cog|ja|白蟻|tr=shiroari}}。
===發音===
{{ko-ipa}}
===名詞===
{{ko-noun}}
# [[白蟻]]
{{c|ko|等翅下目}}
o1c5vfxwt6i9mr1qwyr410sd54o375k
흰
0
3540290
9908755
2026-09-28T13:27:59Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「{{character info}} {{ko-symbol-nav}} ==朝鮮語== ===詞源=== {{ko-etym-native}} ===發音=== {{ko-IPA}} ===形容詞=== {{ko-adj-form}} # {{attributive form of|ko|희다||[[白色]]}} ====近義詞==== * {{ko-l|하얀}} ====反義詞==== * {{ko-l|검은|gloss=黑色}} {{C|ko|白色}}」的新頁面
9908755
wikitext
text/x-wiki
{{character info}}
{{ko-symbol-nav}}
==朝鮮語==
===詞源===
{{ko-etym-native}}
===發音===
{{ko-IPA}}
===形容詞===
{{ko-adj-form}}
# {{attributive form of|ko|희다||[[白色]]}}
====近義詞====
* {{ko-l|하얀}}
====反義詞====
* {{ko-l|검은|gloss=黑色}}
{{C|ko|白色}}
bthi6nkoxqttbf5n540pilgqthutt69
쥐며느리
0
3540291
9908756
2026-09-28T13:29:33Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==朝鮮語== ===詞源=== {{af|ko|쥐|며느리|t1=老鼠|t2=兒媳,媳婦}} ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[鼠婦]],[[潮蟲]] {{C|ko|等足目}}」的新頁面
9908756
wikitext
text/x-wiki
==朝鮮語==
===詞源===
{{af|ko|쥐|며느리|t1=老鼠|t2=兒媳,媳婦}}
===發音===
{{ko-IPA}}
===名詞===
{{ko-noun}}
# [[鼠婦]],[[潮蟲]]
{{C|ko|等足目}}
2i0qr60lx777rwxty9cmevehmouxgpl
투구벌레
0
3540292
9908757
2026-09-28T13:33:28Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==朝鲜语== ===词源=== {{af|ko|투구|벌레|t1=头盔|t2=虫子}} ===发音=== {{ko-IPA}} ===名词=== {{ko-noun}} # [[獨角仙]]({{taxlink|Trypoxylus dichotomus|speices}}) #: {{syn|ko|장수풍뎅이}} {{c|ko|甲蟲}}」的新頁面
9908757
wikitext
text/x-wiki
==朝鲜语==
===词源===
{{af|ko|투구|벌레|t1=头盔|t2=虫子}}
===发音===
{{ko-IPA}}
===名词===
{{ko-noun}}
# [[獨角仙]]({{taxlink|Trypoxylus dichotomus|speices}})
#: {{syn|ko|장수풍뎅이}}
{{c|ko|甲蟲}}
3n99jucz03zb3c25ijc58fp2pi4sqcj
장수풍뎅이
0
3540293
9908758
2026-09-28T13:34:09Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==朝鲜语== {{wp|ko:}} ===词源=== {{af|ko|장수(將帥)|풍뎅이|t2=金龜子}} ===发音=== {{ko-IPA|l=y}} ===名词=== {{ko-noun}} # [[獨角仙]]({{taxlink|Trypoxylus dichotomus|speices}}) #: {{syn|ko|투구벌레}} {{c|ko|甲蟲}}」的新頁面
9908758
wikitext
text/x-wiki
==朝鲜语==
{{wp|ko:}}
===词源===
{{af|ko|장수(將帥)|풍뎅이|t2=金龜子}}
===发音===
{{ko-IPA|l=y}}
===名词===
{{ko-noun}}
# [[獨角仙]]({{taxlink|Trypoxylus dichotomus|speices}})
#: {{syn|ko|투구벌레}}
{{c|ko|甲蟲}}
1hnc43x1ug6xu01d809o9ujl0q11ncw
Category:帶「」的漢語詞
14
3540294
9908762
2026-09-28T13:59:09Z
TongcyBot
83009
養貓
9908762
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:帶「」的詞
14
3540295
9908763
2026-09-28T13:59:25Z
TongcyBot
83009
養貓
9908763
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:帶「𧡹」的漢語詞
14
3540296
9908766
2026-09-28T14:03:07Z
TongcyBot
83009
養貓
9908766
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:帶「𧡹」的詞
14
3540297
9908767
2026-09-28T14:03:13Z
TongcyBot
83009
養貓
9908767
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
屋舎
0
3540298
9908770
2026-09-28T14:54:02Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==日語== {{ja-kanjitab|おく|しゃ|yomi=o}} ===發音=== {{ja-pron|おくしゃ|acc=1|acc_ref=DJR,SMK7}} ===名詞=== {{ja-noun|おくしゃ|hhira=をくしや}} # [[屋舍]],[[房屋]] ===參考資料=== <references/>」的新頁面
9908770
wikitext
text/x-wiki
==日語==
{{ja-kanjitab|おく|しゃ|yomi=o}}
===發音===
{{ja-pron|おくしゃ|acc=1|acc_ref=DJR,SMK7}}
===名詞===
{{ja-noun|おくしゃ|hhira=をくしや}}
# [[屋舍]],[[房屋]]
===參考資料===
<references/>
4is3l0btxjcaiu2sx656ualrz8iu2rc
成都人
0
3540299
9908773
2026-09-28T15:32:57Z
徐元昊
122104
新詞條 可能還須改進
9908773
wikitext
text/x-wiki
==漢語==
{{zh-forms|type=21}}
===發音===
{{zh-pron
|m=Chéngdūrén
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# [[成都市]]的[[居民]],或[[祖籍]]、[[出生]]於[[成都]]的人
# {{lb|zh|historical}} 古代[[成都]][[地區]]的[[居民]]
ifo2nsnx0q9kfyitjiiuofacdjntnf1
武抃
0
3540300
9908774
2026-09-28T16:10:49Z
徐元昊
122104
新詞條 宋祁自造的奇詞,其他文獻罕見。 如果之後還有發現新的,可以再增加。唐代許景先《奉和御制春台望》詩中有「長歌東武抃千春」一句,實際是「長歌《東武》,抃千春」,沒納入引文。
9908774
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|m=wǔbiàn
|cat=n
}}
===名詞===
{{head|zh|名詞}}
# {{lb|zh|Classical|historical}} 古代的一種[[手搏]]、[[角力]][[競技]]
#* {{zh-x|w:唐武宗\^武宗 喜 畋遊,角{jué} 武抃,擇 五坊.小兒 得 出入 禁中。||CL|ref=宋·{{w|歐陽脩}}、{{w|宋祁}}《[[s:新唐書/卷077#憲宗懿安郭皇后|新唐書·卷七十七·列傳第二·后妃下·憲宗懿安郭皇后]]》}}
# {{lb|zh|Classical|historical|metonymic}} 古代的[[角觝]]士、[[摔跤]][[藝人]]
#* {{zh-x|帝 再拜,還{huán} 索 諫章 閱 之,往往 道 游獵 事,自是 畋幸 稀,小兒 武抃 等 不復 橫賜 矣。||CL|ref=宋·{{w|歐陽脩}}、{{w|宋祁}}《[[s:新唐書/卷077#憲宗懿安郭皇后|新唐書·卷七十七·列傳第二·后妃下·憲宗懿安郭皇后]]》}}
===參考資料===
* {{R:zh:HYDCD}}
895lvca2yvepe5exr8vzmf8agp2y2jd
wǔbiàn
0
3540301
9908776
2026-09-28T16:28:08Z
徐元昊
122104
新詞條
9908776
wikitext
text/x-wiki
==官話==
===羅馬化===
{{cmn-pinyin}}
# {{cmn-pinyin of|武弁}}
# {{cmn-pinyin of|武抃}}
# {{cmn-pinyin of|五遍}}
# {{cmn-pinyin of|舞抃}}
# {{cmn-pinyin of|舞忭}}
# {{cmn-pinyin of|五辯}}
# {{cmn-pinyin of|五變}}
# {{cmn-pinyin of|舞遍}}
# {{cmn-pinyin of|武辯}}
lslklsuxhqsunk92gavmwvrbl5c7935
cap圖
0
3540302
9908777
2026-09-28T17:16:41Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|s=cap图|type=31|alt=Cap圖}} ===發音=== {{zh-pron |c=kep1 tou4 |cat=v }} ===動詞=== {{zh-verb|type=vo3}} # {{lb|zh|Hong Kong Cantonese}} [[截圖]] ===名詞=== {{head|zh|名詞}} # {{lb|zh|Hong Kong Cantonese}} [[截圖]]」的新頁面
9908777
wikitext
text/x-wiki
==漢語==
{{zh-forms|s=cap图|type=31|alt=Cap圖}}
===發音===
{{zh-pron
|c=kep1 tou4
|cat=v
}}
===動詞===
{{zh-verb|type=vo3}}
# {{lb|zh|Hong Kong Cantonese}} [[截圖]]
===名詞===
{{head|zh|名詞}}
# {{lb|zh|Hong Kong Cantonese}} [[截圖]]
isq6uz6wujd72eyhnmsy5hmbirqou59
埋口
0
3540303
9908780
2026-09-28T17:43:24Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms}} ===發音=== {{zh-pron |c=maai4 hau2 |cat=v }} ===動詞=== {{zh-verb|type=vo}} # {{lb|zh|粵語}} [[傷口]][[癒合]] ====近義詞==== {{syn-saurus|zh|癒合}}」的新頁面
9908780
wikitext
text/x-wiki
==漢語==
{{zh-forms}}
===發音===
{{zh-pron
|c=maai4 hau2
|cat=v
}}
===動詞===
{{zh-verb|type=vo}}
# {{lb|zh|粵語}} [[傷口]][[癒合]]
====近義詞====
{{syn-saurus|zh|癒合}}
94t8hudpw5fa7ty8rnqnir2ifceya47
Template:R:sq:Topalli
10
3540305
9908801
2026-09-29T03:28:43Z
Hiyuune
119685
Hiyuune移动页面[[Template:R:sq:Topalli]]至[[Template:R:sq:Topalli:2017]]
9908801
wikitext
text/x-wiki
#REDIRECT [[Template:R:sq:Topalli:2017]]
awo9mmx8bsg80su442fwdbvu05sdfaz
att.
0
3540306
9908803
2026-09-29T04:39:28Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==英語== ===名詞=== {{en-noun|?}} # {{abbr of|en|attendance}}。 ===異序詞=== * {{anagrams|en|a=att|Tat|tat|TAT}} ==德語== ===形容詞=== {{de-adj|indecl}} # {{abbr of|de|attisch|t=阿提卡的}}」的新頁面
9908803
wikitext
text/x-wiki
==英語==
===名詞===
{{en-noun|?}}
# {{abbr of|en|attendance}}。
===異序詞===
* {{anagrams|en|a=att|Tat|tat|TAT}}
==德語==
===形容詞===
{{de-adj|indecl}}
# {{abbr of|de|attisch|t=阿提卡的}}
2gytjowz7fk4l19pfucw73a63i42qfe
attischer
0
3540307
9908805
2026-09-29T04:40:32Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈatɪʃɐ/}} * {{audio|de|De-attischer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|attisch}}」的新頁面
9908805
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈatɪʃɐ/}}
* {{audio|de|De-attischer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|attisch}}
n8r19fbbs3b3lwro1x6gs8g6kh9iwzw
attischen
0
3540308
9908806
2026-09-29T04:40:33Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈatɪʃn̩/}} * {{audio|de|De-attischen.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|attisch}}」的新頁面
9908806
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈatɪʃn̩/}}
* {{audio|de|De-attischen.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|attisch}}
fcjfk95u0ccidv08olln019g85uxt2x
attischem
0
3540309
9908807
2026-09-29T04:40:35Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈatɪʃm̩/}} * {{audio|de|De-attischem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|attisch}}」的新頁面
9908807
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈatɪʃm̩/}}
* {{audio|de|De-attischem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|attisch}}
i8592uuc0b9snqjiq1ox3s9dzsvv88i
attische
0
3540310
9908808
2026-09-29T04:40:36Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈatɪʃə/}} * {{audio|de|De-attische.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|attisch}}」的新頁面
9908808
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈatɪʃə/}}
* {{audio|de|De-attische.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|attisch}}
a18fh5kk8uokz922tg2dwnj816rpzk1
attisches
0
3540311
9908809
2026-09-29T04:40:38Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈatɪʃəs/}} * {{audio|de|De-attisches.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|attisch}}」的新頁面
9908809
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈatɪʃəs/}}
* {{audio|de|De-attisches.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|attisch}}
4q9sq008d8vvd7voz8ywcojkkdpw5w7
Disco
0
3540312
9908810
2026-09-29T04:43:52Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「{{also|disco|discó}} ==德語== {{wp|de:}} ===其他形式=== * {{alt|de|Disko}} ===詞源=== {{ety|de|:bor|en:disco}} {{bor+|de|en|disco}}。 ===發音=== * {{IPA|de|/ˈdɪsko/}} * {{audio|de|De-Disco.ogg|a=柏林}} * {{hyph|de|Dis|co}} * {{homophones|de|Disko}} ===名詞=== {{de-noun|f,s}} # [[迪廳]],[[夜總會]] #: {{syn|de|Diskothek|Tanzbar<q:過時>|Tanzlokal<q:過時>}} ====變格==== {{de-ndecl|f,s}} ===名詞=== {{de-noun…」的新頁面
9908810
wikitext
text/x-wiki
{{also|disco|discó}}
==德語==
{{wp|de:}}
===其他形式===
* {{alt|de|Disko}}
===詞源===
{{ety|de|:bor|en:disco}}
{{bor+|de|en|disco}}。
===發音===
* {{IPA|de|/ˈdɪsko/}}
* {{audio|de|De-Disco.ogg|a=柏林}}
* {{hyph|de|Dis|co}}
* {{homophones|de|Disko}}
===名詞===
{{de-noun|f,s}}
# [[迪廳]],[[夜總會]]
#: {{syn|de|Diskothek|Tanzbar<q:過時>|Tanzlokal<q:過時>}}
====變格====
{{de-ndecl|f,s}}
===名詞===
{{de-noun|m,-.sg}}
# {{lb|de|music genre}} [[迪斯科]]
#: {{syn|de|Discomusik}}
====變格====
{{de-ndecl|m,-.sg}}
===延伸閱讀===
* {{R:de:DWDS}}
* {{R:de:Duden}}
* {{R:de:OWID|174343}}
* {{R:de:UniLeipzig}}
{{C|de|建築}}
rdbifhcjt3a75a8ja48q3j0oehwxs9d
Discos
0
3540313
9908811
2026-09-29T04:43:56Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈdɪskos/}} * {{audio|de|De-Discos.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{plural of|de|Disco}}」的新頁面
9908811
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈdɪskos/}}
* {{audio|de|De-Discos.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{plural of|de|Disco}}
jgoux4c6dy8waq4jg6lvft3al743nzi
Diskos
0
3540314
9908813
2026-09-29T04:45:14Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈdɪskos/}} * {{audio|de|De-Diskos.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{plural of|de|Disko}}」的新頁面
9908813
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈdɪskos/}}
* {{audio|de|De-Diskos.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{plural of|de|Disko}}
qwn4chf788y4t71t81x1057xf3owvpk
Diskotheken
0
3540315
9908815
2026-09-29T04:46:08Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/dɪskoˈteːkn̩/}} * {{audio|de|De-Diskotheken.ogg|a=柏林}} * {{audio|de|De-Diskotheken2.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形|g=f}} # {{plural of|de|Diskothek}}」的新頁面
9908815
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/dɪskoˈteːkn̩/}}
* {{audio|de|De-Diskotheken.ogg|a=柏林}}
* {{audio|de|De-Diskotheken2.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形|g=f}}
# {{plural of|de|Diskothek}}
hyqhes0jijrku3za1qtpsp3ls20cigh
Tanzbars
0
3540316
9908817
2026-09-29T04:46:53Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈtant͡sbaːɐ̯s/}} * {{audio|de|De-Tanzbars.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{plural of|de|Tanzbar}}」的新頁面
9908817
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈtant͡sbaːɐ̯s/}}
* {{audio|de|De-Tanzbars.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{plural of|de|Tanzbar}}
o5puwpewun0nrwu2iay38axrkujed0b
Tanzlokales
0
3540317
9908819
2026-09-29T04:47:29Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈtant͡sloˌkaːləs/}} * {{audio|de|De-Tanzlokales.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{inflection of|de|Tanzlokal||gen|s}}」的新頁面
9908819
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈtant͡sloˌkaːləs/}}
* {{audio|de|De-Tanzlokales.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{inflection of|de|Tanzlokal||gen|s}}
mxekm3gxqfrbswu2lcqfxp0yroawga2
Tanzlokals
0
3540318
9908820
2026-09-29T04:47:31Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈtant͡sloˌkaːls/}} * {{audio|de|De-Tanzlokals.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{inflection of|de|Tanzlokal||gen|s}}」的新頁面
9908820
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈtant͡sloˌkaːls/}}
* {{audio|de|De-Tanzlokals.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{inflection of|de|Tanzlokal||gen|s}}
pm6nrsn7gusrr24o5olncxpyrdlf0a5
Tanzlokale
0
3540319
9908821
2026-09-29T04:47:38Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈtant͡sloˌkaːlə/}} * {{audio|de|De-Tanzlokale.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{inflection of|de|Tanzlokal||nom//gen//acc|p|;|dat|s}}」的新頁面
9908821
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈtant͡sloˌkaːlə/}}
* {{audio|de|De-Tanzlokale.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{inflection of|de|Tanzlokal||nom//gen//acc|p|;|dat|s}}
6d4dl8dysbzhsec4papfwda4x01tvlh
Tanzlokalen
0
3540320
9908822
2026-09-29T04:47:40Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈtant͡sloˌkaːlən/}} * {{audio|de|De-Tanzlokalen.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{inflection of|de|Tanzlokal||dat|p}}」的新頁面
9908822
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈtant͡sloˌkaːlən/}}
* {{audio|de|De-Tanzlokalen.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{inflection of|de|Tanzlokal||dat|p}}
7xiwclrzj3ecqx290sk6ey5y5qb0vzs
Nachtclub
0
3540321
9908823
2026-09-29T04:48:39Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== {{wp|de:}} ===其他形式=== * {{alter|de|Nachtklub}} ===詞源=== {{ety|de|:af|Nacht|Club}} {{compound|de|Nacht|Club}} ===發音=== * {{IPA|de|/ˈnaxtˌklʊp/}} * {{audio|de|De-Nachtclub.ogg|a=柏林}} ===名詞=== {{de-noun|m,,s}} # [[夜總會]] #: {{syn|de|Nachtlokal|Nachtbar}} ====變格==== {{de-ndecl|m,,s}} {{C|de|酒吧|商業實體}}」的新頁面
9908823
wikitext
text/x-wiki
==德語==
{{wp|de:}}
===其他形式===
* {{alter|de|Nachtklub}}
===詞源===
{{ety|de|:af|Nacht|Club}}
{{compound|de|Nacht|Club}}
===發音===
* {{IPA|de|/ˈnaxtˌklʊp/}}
* {{audio|de|De-Nachtclub.ogg|a=柏林}}
===名詞===
{{de-noun|m,,s}}
# [[夜總會]]
#: {{syn|de|Nachtlokal|Nachtbar}}
====變格====
{{de-ndecl|m,,s}}
{{C|de|酒吧|商業實體}}
meko8jxsfqlwy4hv5vwoipftoucfpr5
Nachtklubs
0
3540322
9908825
2026-09-29T04:49:04Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈnaxtklʊps/}} * {{audio|de|De-Nachtklubs.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{infl of|de|Nachtklub||p|;|gen|s}}」的新頁面
9908825
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈnaxtklʊps/}}
* {{audio|de|De-Nachtklubs.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{infl of|de|Nachtklub||p|;|gen|s}}
hizzyu46v3hl2btdlgvnt9sqdkgskue
Nachtclubs
0
3540323
9908826
2026-09-29T04:49:08Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈnaxtklʊps/}} * {{audio|de|De-Nachtclubs.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{infl of|de|Nachtclub||p|;|gen|s}}」的新頁面
9908826
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈnaxtklʊps/}}
* {{audio|de|De-Nachtclubs.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{infl of|de|Nachtclub||p|;|gen|s}}
mxh7o6g7105udm3yraiifbdao5aur2l
Nachtlokals
0
3540324
9908828
2026-09-29T04:50:30Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈnaxtlokaːls/}} * {{audio|de|De-Nachtlokals.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{infl of|de|Nachtlokal||gen|s}}」的新頁面
9908828
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈnaxtlokaːls/}}
* {{audio|de|De-Nachtlokals.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{infl of|de|Nachtlokal||gen|s}}
7b3j42icuk3yke4thcio05z04zb4t23
Nachtlokale
0
3540325
9908829
2026-09-29T04:50:32Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈnaxtlokaːlə/}} * {{audio|de|De-Nachtlokale.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{infl of|de|Nachtlokal||nom//gen//acc|p}}」的新頁面
9908829
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈnaxtlokaːlə/}}
* {{audio|de|De-Nachtlokale.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{infl of|de|Nachtlokal||nom//gen//acc|p}}
mn3das8qeo2ul4z8qia3u7sg2strbfl
Nachtlokalen
0
3540326
9908830
2026-09-29T04:50:34Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈnaxtlokaːlən/}} * {{audio|de|De-Nachtlokalen.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{infl of|de|Nachtlokal||dat|p}}」的新頁面
9908830
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈnaxtlokaːlən/}}
* {{audio|de|De-Nachtlokalen.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{infl of|de|Nachtlokal||dat|p}}
6n0g5oajtk7zhhn77m4f92poe9wemsv
Nationalhymnen
0
3540327
9908832
2026-09-29T04:56:30Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌnat͡si̯oˈnaːlˌhʏmnən/}} * {{audio|de|De-Nationalhymnen.ogg|a=柏林}} ===名詞=== {{head|de|名詞變格形}} # {{plural of|de|Nationalhymne}}」的新頁面
9908832
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌnat͡si̯oˈnaːlˌhʏmnən/}}
* {{audio|de|De-Nationalhymnen.ogg|a=柏林}}
===名詞===
{{head|de|名詞變格形}}
# {{plural of|de|Nationalhymne}}
a0hv275lvsp4u6gpssns3b22wm8exz5
Marseillaises
0
3540328
9908835
2026-09-29T04:59:09Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「{{also|marseillaises}} ==法語== ===名詞=== {{head|fr|名詞變格形|g=f}} # {{plural of|fr|Marseillaise}}」的新頁面
9908835
wikitext
text/x-wiki
{{also|marseillaises}}
==法語==
===名詞===
{{head|fr|名詞變格形|g=f}}
# {{plural of|fr|Marseillaise}}
q8ulc7f33mxll8juxf4xc3sbzid4eez
offizieller
0
3540329
9908839
2026-09-29T05:08:55Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛlɐ/}} * {{audio|de|De-offizieller.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級}} # {{comparative of|de|offiziell}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908839
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlɐ/}}
* {{audio|de|De-offizieller.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級}}
# {{comparative of|de|offiziell}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offiziell}}
di8x2q4tdg41s1noh6pm701hnha57cy
offiziellem
0
3540330
9908840
2026-09-29T05:09:01Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛləm/}} * {{audio|de|De-offiziellem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908840
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛləm/}}
* {{audio|de|De-offiziellem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offiziell}}
nyupbci7o6pzp076vvuh18n54764kik
offizielles
0
3540331
9908841
2026-09-29T05:09:04Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛləs/}} * {{audio|de|De-offizielles.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908841
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛləs/}}
* {{audio|de|De-offizielles.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offiziell}}
6ue7344z6fvlg5f7w9qeo4j2v1uyo9k
offiziellerer
0
3540332
9908842
2026-09-29T05:10:08Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛləʁɐ/}} * {{audio|de|De-offiziellerer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908842
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛləʁɐ/}}
* {{audio|de|De-offiziellerer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offiziell}}
agymrwigaxrzyl0zfp8u7s11vz0y88a
offizielleren
0
3540333
9908843
2026-09-29T05:10:09Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛləʁən/}} * {{audio|de|De-offizielleren.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908843
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛləʁən/}}
* {{audio|de|De-offizielleren.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offiziell}}
k5h50e6s991840rgke4d9iednmku10k
offiziellerem
0
3540334
9908844
2026-09-29T05:10:11Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛləʁəm/}} * {{audio|de|De-offiziellerem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908844
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛləʁəm/}}
* {{audio|de|De-offiziellerem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offiziell}}
ne5auh03rydu4lq3w7mnfvnjxotwlx4
offiziellere
0
3540335
9908845
2026-09-29T05:10:14Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛləʁə/}} * {{audio|de|De-offiziellere.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908845
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛləʁə/}}
* {{audio|de|De-offiziellere.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offiziell}}
2oac5xadpym8qj69xh6749jmye7uqgg
offizielleres
0
3540336
9908846
2026-09-29T05:10:16Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛləʁəs/}} * {{audio|de|De-offizielleres.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908846
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛləʁəs/}}
* {{audio|de|De-offizielleres.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offiziell}}
3iikqa1abz4tbm9j2009tj55wo8wb2n
offiziellster
0
3540337
9908847
2026-09-29T05:10:32Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛlstɐ/}} * {{audio|de|De-offiziellster.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908847
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlstɐ/}}
* {{audio|de|De-offiziellster.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offiziell}}
nsc6x9yzzezctn5odcm0snarz9abl62
offiziellsten
0
3540338
9908848
2026-09-29T05:10:33Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛlstn̩/}} * {{audio|de|De-offiziellsten.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級}} # {{superlative of|de|offiziell}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908848
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlstn̩/}}
* {{audio|de|De-offiziellsten.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級}}
# {{superlative of|de|offiziell}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offiziell}}
tpz10aioo9ezgfipkr9x1slm3tuhqjp
offiziellstem
0
3540339
9908849
2026-09-29T05:10:35Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛlstəm/}} * {{audio|de|De-offiziellstem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908849
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlstəm/}}
* {{audio|de|De-offiziellstem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offiziell}}
582we14azr1jjzwwxywgjs2xocnna3o
offiziellste
0
3540340
9908850
2026-09-29T05:10:37Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛlstə/}} * {{audio|de|De-offiziellste.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908850
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlstə/}}
* {{audio|de|De-offiziellste.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offiziell}}
r7uxwrlb8jpazms6gs74yp1xj6uzsrc
offiziellstes
0
3540341
9908851
2026-09-29T05:10:40Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɔfiˈt͡si̯ɛlstəs/}} * {{audio|de|De-offiziellstes.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offiziell}}」的新頁面
9908851
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɔfiˈt͡si̯ɛlstəs/}}
* {{audio|de|De-offiziellstes.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offiziell}}
lf0agpt4hqgy3ro2c5mszy6gbnyxwx5
inoffizieller
0
3540342
9908853
2026-09-29T05:12:03Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlɐ/}} * {{audio|de|De-inoffizieller.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級}} # {{comparative of|de|inoffiziell}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908853
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlɐ/}}
* {{audio|de|De-inoffizieller.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級}}
# {{comparative of|de|inoffiziell}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|inoffiziell}}
aoheec6x8xv95248073sha2e66hzjo6
inoffiziellen
0
3540343
9908854
2026-09-29T05:12:05Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlən/}} * {{audio|de|De-inoffiziellen.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908854
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlən/}}
* {{audio|de|De-inoffiziellen.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|inoffiziell}}
71a2au134arycxfnlg8h6ahvw3kft1x
inoffizielle
0
3540344
9908855
2026-09-29T05:12:07Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlə/}} * {{audio|de|De-inoffizielle.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908855
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlə/}}
* {{audio|de|De-inoffizielle.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|inoffiziell}}
6x8k89x1zerdv86vjtk4x1vzueijlsp
inoffiziellem
0
3540345
9908856
2026-09-29T05:12:12Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləm/}} * {{audio|de|De-inoffiziellem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908856
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləm/}}
* {{audio|de|De-inoffiziellem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|inoffiziell}}
0kcfda077eypl1oi7q30t59dodusurw
inoffizielles
0
3540346
9908857
2026-09-29T05:12:14Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləs/}} * {{audio|de|De-inoffizielles.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908857
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləs/}}
* {{audio|de|De-inoffizielles.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|inoffiziell}}
581j47qmwmkqnyv9lqn98zipsf2zuu5
inoffiziellerer
0
3540347
9908858
2026-09-29T05:12:37Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁɐ/}} * {{audio|de|De-inoffiziellerer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908858
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁɐ/}}
* {{audio|de|De-inoffiziellerer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|inoffiziell}}
gwbl4cicett6fvktbhzho0jo14bkq0h
inoffizielleren
0
3540348
9908859
2026-09-29T05:12:40Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁən/}} * {{audio|de|De-inoffizielleren.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908859
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁən/}}
* {{audio|de|De-inoffizielleren.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|inoffiziell}}
5feoksopb99ym7n6vc716baf2ahpin0
inoffiziellerem
0
3540349
9908860
2026-09-29T05:12:45Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁəm/}} * {{audio|de|De-inoffiziellerem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908860
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁəm/}}
* {{audio|de|De-inoffiziellerem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|inoffiziell}}
8i2iitabt5yh5nzri4k9irblrkqph62
inoffiziellere
0
3540350
9908861
2026-09-29T05:12:47Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁə/}} * {{audio|de|De-inoffiziellere.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908861
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁə/}}
* {{audio|de|De-inoffiziellere.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|inoffiziell}}
8f0prfcrraocfzxkicfo7igtzxtncag
inoffizielleres
0
3540351
9908862
2026-09-29T05:12:49Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁəs/}} * {{audio|de|De-inoffizielleres.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908862
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛləʁəs/}}
* {{audio|de|De-inoffizielleres.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|inoffiziell}}
gu4fiyju6eat3p4btlckwf09fevu01x
inoffiziellster
0
3540352
9908863
2026-09-29T05:13:11Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstɐ/}} * {{audio|de|De-inoffiziellster.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908863
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstɐ/}}
* {{audio|de|De-inoffiziellster.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|inoffiziell}}
nko7541130c8h1kvve25ldrd2bdpevj
inoffiziellsten
0
3540353
9908864
2026-09-29T05:13:14Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstn̩/}} * {{audio|de|De-inoffiziellsten.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級}} # {{superlative of|de|inoffiziell}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908864
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstn̩/}}
* {{audio|de|De-inoffiziellsten.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級}}
# {{superlative of|de|inoffiziell}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|inoffiziell}}
sgpvv3ufatr9hza9l5i96bs1h7ez795
inoffiziellstem
0
3540354
9908865
2026-09-29T05:13:19Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstəm/}} * {{audio|de|De-inoffiziellstem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908865
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstəm/}}
* {{audio|de|De-inoffiziellstem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|inoffiziell}}
6os41dop4643g85hnk1m52ckthfdfy6
inoffiziellste
0
3540355
9908866
2026-09-29T05:13:22Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstə/}} * {{audio|de|De-inoffiziellste.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908866
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstə/}}
* {{audio|de|De-inoffiziellste.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|inoffiziell}}
7bqyzughrw7cyh87d5irfbuz0rt1jn0
inoffiziellstes
0
3540356
9908867
2026-09-29T05:13:25Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstəs/}} * {{audio|de|De-inoffiziellstes.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|inoffiziell}}」的新頁面
9908867
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɪnʔɔfiˌt͡si̯ɛlstəs/}}
* {{audio|de|De-inoffiziellstes.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|inoffiziell}}
nfrqiuzjnq7r535l5aa29rp6v8jfy2g
offenkundiger
0
3540357
9908868
2026-09-29T05:17:23Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡɐ/}} * {{audio|de|De-offenkundiger.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級}} # {{comparative of|de|offenkundig}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908868
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡɐ/}}
* {{audio|de|De-offenkundiger.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級}}
# {{comparative of|de|offenkundig}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offenkundig}}
e0nukd4ct0uxd0fjwgllabmq1w8rptu
offenkundigen
0
3540358
9908869
2026-09-29T05:17:26Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡn̩/}} * {{audio|de|De-offenkundigen.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908869
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡn̩/}}
* {{audio|de|De-offenkundigen.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offenkundig}}
9my0m9qa8k9kyzqfxxo7msa3iannpl9
offenkundigem
0
3540359
9908870
2026-09-29T05:17:47Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəm/}} * {{audio|de|De-offenkundigem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908870
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəm/}}
* {{audio|de|De-offenkundigem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offenkundig}}
9zylvhwyob843sze7v0zaxfbqs6mpxu
offenkundige
0
3540360
9908871
2026-09-29T05:17:50Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡə/}} * {{audio|de|De-offenkundige.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908871
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡə/}}
* {{audio|de|De-offenkundige.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offenkundig}}
f8g4outn9st5wqoif21mzcjix8bxus5
offenkundiges
0
3540361
9908872
2026-09-29T05:17:53Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəs/}} * {{audio|de|De-offenkundiges.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908872
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəs/}}
* {{audio|de|De-offenkundiges.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|offenkundig}}
p1oo50tks6tgplb8zfist2of27k64gw
offenkundigerer
0
3540362
9908873
2026-09-29T05:18:27Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁɐ/}} * {{audio|de|De-offenkundigerer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908873
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁɐ/}}
* {{audio|de|De-offenkundigerer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offenkundig}}
tvfq5igkrw4cltuto86h9u3of7j7x2m
offenkundigeren
0
3540363
9908874
2026-09-29T05:18:29Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁən/}} * {{audio|de|De-offenkundigeren.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908874
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁən/}}
* {{audio|de|De-offenkundigeren.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offenkundig}}
qjr9wrr99wl6ncdw7bisxdc4bdqqhe5
offenkundigerem
0
3540364
9908875
2026-09-29T05:18:33Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁəm/}} * {{audio|de|De-offenkundigerem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908875
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁəm/}}
* {{audio|de|De-offenkundigerem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offenkundig}}
bjrxjcfc170c06bt1sbtqtslukaaeyd
offenkundigere
0
3540365
9908876
2026-09-29T05:18:36Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁə/}} * {{audio|de|De-offenkundigere.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908876
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁə/}}
* {{audio|de|De-offenkundigere.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offenkundig}}
cto6991dg2z0kjj75vnzwjxx5xzpncn
offenkundigeres
0
3540366
9908877
2026-09-29T05:18:40Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁəs/}} * {{audio|de|De-offenkundigeres.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908877
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪɡəʁəs/}}
* {{audio|de|De-offenkundigeres.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|offenkundig}}
8y5f7fcwyo0xswy345jt965e8vp1vhz
offenkundigster
0
3540367
9908878
2026-09-29T05:19:03Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪçstɐ/|/ˈɔfn̩ˌkʊndɪkstɐ/}} * {{audio|de|De-offenkundigster.ogg|a=柏林}} * {{audio|de|De-offenkundigster2.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908878
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪçstɐ/|/ˈɔfn̩ˌkʊndɪkstɐ/}}
* {{audio|de|De-offenkundigster.ogg|a=柏林}}
* {{audio|de|De-offenkundigster2.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offenkundig}}
2ddm0ksffzz4b5gsnf1nzkw79x491yf
offenkundigsten
0
3540368
9908879
2026-09-29T05:19:06Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪçstn̩/|/ˈɔfn̩ˌkʊndɪkstn̩/}} * {{audio|de|De-offenkundigsten.ogg|a=柏林}} * {{audio|de|De-offenkundigsten2.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級}} # {{superlative of|de|offenkundig}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908879
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪçstn̩/|/ˈɔfn̩ˌkʊndɪkstn̩/}}
* {{audio|de|De-offenkundigsten.ogg|a=柏林}}
* {{audio|de|De-offenkundigsten2.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級}}
# {{superlative of|de|offenkundig}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offenkundig}}
8hb6dk3942qq8psdw9uj1j84ocs8d3r
offenkundigstem
0
3540369
9908880
2026-09-29T05:19:10Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪçstəm/|/ˈɔfn̩ˌkʊndɪkstəm/}} * {{audio|de|De-offenkundigstem.ogg|a=柏林}} * {{audio|de|De-offenkundigstem2.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908880
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪçstəm/|/ˈɔfn̩ˌkʊndɪkstəm/}}
* {{audio|de|De-offenkundigstem.ogg|a=柏林}}
* {{audio|de|De-offenkundigstem2.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offenkundig}}
22e1o6vwl3ur7zi5wk88n07gngfk5ak
offenkundigste
0
3540370
9908881
2026-09-29T05:19:11Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪçstə/|/ˈɔfn̩ˌkʊndɪkstə/}} * {{audio|de|De-offenkundigste.ogg|a=柏林}} * {{audio|de|De-offenkundigste2.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908881
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪçstə/|/ˈɔfn̩ˌkʊndɪkstə/}}
* {{audio|de|De-offenkundigste.ogg|a=柏林}}
* {{audio|de|De-offenkundigste2.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offenkundig}}
sou02dauaydtg2ir5pzt2brthx7y01t
offenkundigstes
0
3540371
9908882
2026-09-29T05:19:13Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɔfn̩ˌkʊndɪçstəs/|/ˈɔfn̩ˌkʊndɪkstəs/}} * {{audio|de|De-offenkundigstes.ogg|a=柏林}} * {{audio|de|De-offenkundigstes2.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|offenkundig}}」的新頁面
9908882
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɔfn̩ˌkʊndɪçstəs/|/ˈɔfn̩ˌkʊndɪkstəs/}}
* {{audio|de|De-offenkundigstes.ogg|a=柏林}}
* {{audio|de|De-offenkundigstes2.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|offenkundig}}
34qvzo5fp1t1wnx5njfdx79h3g0ldw1
geographischer
0
3540372
9908884
2026-09-29T05:21:24Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡeoˈɡʁaːfɪʃɐ/}} * {{audio|de|De-geographischer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|geographisch}}」的新頁面
9908884
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡeoˈɡʁaːfɪʃɐ/}}
* {{audio|de|De-geographischer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|geographisch}}
6fytwlmpcjuxue3d1oivpogf238i6kl
geographischen
0
3540373
9908885
2026-09-29T05:21:26Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡeoˈɡʁaːfɪʃn̩/}} * {{audio|de|De-geographischen.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|geographisch}}」的新頁面
9908885
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡeoˈɡʁaːfɪʃn̩/}}
* {{audio|de|De-geographischen.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|geographisch}}
1rblmdqhqdkerxudo95tpkh1qwl3o8i
geographischem
0
3540374
9908886
2026-09-29T05:21:30Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡeoˈɡʁaːfɪʃm̩/}} * {{audio|de|De-geographischem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|geographisch}}」的新頁面
9908886
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡeoˈɡʁaːfɪʃm̩/}}
* {{audio|de|De-geographischem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|geographisch}}
btt1xyyro4k632qqyps2snoptc8hizj
geographische
0
3540375
9908887
2026-09-29T05:21:32Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡeoˈɡʁaːfɪʃə/}} * {{audio|de|De-geographische.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|geographisch}}」的新頁面
9908887
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡeoˈɡʁaːfɪʃə/}}
* {{audio|de|De-geographische.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|geographisch}}
645d9n4ebkiah1zb8e8dpsk0pnc8ian
geographisches
0
3540376
9908888
2026-09-29T05:21:35Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡeoˈɡʁaːfɪʃəs/}} * {{audio|de|De-geographisches.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|geographisch}}」的新頁面
9908888
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡeoˈɡʁaːfɪʃəs/}}
* {{audio|de|De-geographisches.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|geographisch}}
psui5gb3ovlyvsp6i82b9ani5ttcqty
endgültiger
0
3540377
9908889
2026-09-29T05:23:20Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɛntɡʏltɪɡɐ/}} * {{audio|de|De-endgültiger.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|endgültig}}」的新頁面
9908889
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɛntɡʏltɪɡɐ/}}
* {{audio|de|De-endgültiger.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|endgültig}}
awxgyjbmern6nhekkq6uuszyfedbkff
endgültigen
0
3540378
9908890
2026-09-29T05:23:22Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɛntɡʏltɪɡn̩/}} * {{audio|de|De-endgültigen.ogg|a=柏林}} * {{audio|de|De-endgültigen2.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|endgültig}}」的新頁面
9908890
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɛntɡʏltɪɡn̩/}}
* {{audio|de|De-endgültigen.ogg|a=柏林}}
* {{audio|de|De-endgültigen2.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|endgültig}}
suwkv39ibz30326smlfjmazeukqqska
endgültigem
0
3540379
9908891
2026-09-29T05:23:26Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɛntɡʏltɪɡəm/}} * {{audio|de|De-endgültigem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|endgültig}}」的新頁面
9908891
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɛntɡʏltɪɡəm/}}
* {{audio|de|De-endgültigem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|endgültig}}
cjaq8hjaoubblieux97tvnymii0z2he
endgültige
0
3540380
9908892
2026-09-29T05:23:29Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɛntɡʏltɪɡə/}} * {{audio|de|De-endgültige.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|endgültig}}」的新頁面
9908892
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɛntɡʏltɪɡə/}}
* {{audio|de|De-endgültige.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|endgültig}}
c96j2itup3mqtesp47nz3bvv2ur640g
endgültiges
0
3540381
9908893
2026-09-29T05:23:32Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈɛntɡʏltɪɡəs/}} * {{audio|de|De-endgültiges.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|endgültig}}」的新頁面
9908893
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈɛntɡʏltɪɡəs/}}
* {{audio|de|De-endgültiges.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|endgültig}}
ewhpl8nhi0usocgehyamkhab2zluzfa
aktualisierter
0
3540382
9908896
2026-09-29T05:29:13Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯tɐ/}} * {{audio|de|De-aktualisierter.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisiert}}」的新頁面
9908896
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯tɐ/}}
* {{audio|de|De-aktualisierter.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisiert}}
2c10p69rduh24pdiasamdytxgtux0hy
aktualisiertem
0
3540383
9908897
2026-09-29T05:29:15Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯təm/}} * {{audio|de|De-aktualisiertem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisiert}}」的新頁面
9908897
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯təm/}}
* {{audio|de|De-aktualisiertem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisiert}}
sazx0573xkuq1rcert53hr7m7lcxdjp
aktualisiertes
0
3540384
9908898
2026-09-29T05:29:19Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯təs/}} * {{audio|de|De-aktualisiertes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisiert}}」的新頁面
9908898
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯təs/}}
* {{audio|de|De-aktualisiertes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisiert}}
gbm5irbnbh18aex3dk9p09x9pv1jf10
aktualisierte
0
3540385
9908899
2026-09-29T05:30:18Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯tə/}} * {{audio|de|De-aktualisierte.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||1//3|s|pret|;|1//3|s|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisiert}}」的新頁面
9908899
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯tə/}}
* {{audio|de|De-aktualisierte.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||1//3|s|pret|;|1//3|s|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisiert}}
kffvvmczashn8h0gcr653y6bit1vdr0
aktualisierten
0
3540386
9908900
2026-09-29T05:30:21Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯tn̩/}} * {{audio|de|De-aktualisierten.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||1//3|p|pret|;|1//3|p|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisiert}}」的新頁面
9908900
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯tn̩/}}
* {{audio|de|De-aktualisierten.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||1//3|p|pret|;|1//3|p|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisiert}}
530xn3ur9wt8wa90fe1clfq044lhcbh
aktualisierend
0
3540387
9908901
2026-09-29T05:30:31Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁənt/}} * {{audio|de|De-aktualisierend.ogg|a=柏林}} ===分詞=== {{head|de|現在分詞}} # {{present participle of|de|aktualisieren}} ====變格==== {{de-adecl}}」的新頁面
9908901
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁənt/}}
* {{audio|de|De-aktualisierend.ogg|a=柏林}}
===分詞===
{{head|de|現在分詞}}
# {{present participle of|de|aktualisieren}}
====變格====
{{de-adecl}}
7tdhrnz2s64l7m5cvd314w1qdxdzt8t
aktualisierender
0
3540388
9908902
2026-09-29T05:30:46Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁəndɐ/}} * {{audio|de|De-aktualisierender.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisierend}}」的新頁面
9908902
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁəndɐ/}}
* {{audio|de|De-aktualisierender.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisierend}}
qw0pkn3b4hjnnyw2wbrh1yr1mg02iso
aktualisierenden
0
3540389
9908903
2026-09-29T05:30:47Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁəndn̩/}} * {{audio|de|De-aktualisierenden.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisierend}}」的新頁面
9908903
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁəndn̩/}}
* {{audio|de|De-aktualisierenden.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisierend}}
81qevpirlzysxe7l5t1r11vefzisedn
aktualisierendem
0
3540390
9908904
2026-09-29T05:30:49Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁəndəm/}} * {{audio|de|De-aktualisierendem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisierend}}」的新頁面
9908904
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁəndəm/}}
* {{audio|de|De-aktualisierendem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisierend}}
kgxb3pmsn7dc47njinzjxwblo790la1
aktualisierende
0
3540391
9908905
2026-09-29T05:30:54Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁəndə/}} * {{audio|de|De-aktualisierende.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisierend}}」的新頁面
9908905
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁəndə/}}
* {{audio|de|De-aktualisierende.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisierend}}
gpix0n5zjmqtbomqzx6meqf8fqnkajj
aktualisierendes
0
3540392
9908906
2026-09-29T05:30:56Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁəndəs/}} * {{audio|de|De-aktualisierendes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|aktualisierend}}」的新頁面
9908906
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁəndəs/}}
* {{audio|de|De-aktualisierendes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|aktualisierend}}
mhmd8h9qfrj1aksnukk8q4d77paqxqn
aktualisiere
0
3540393
9908907
2026-09-29T05:31:53Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁə/}} * {{audio|de|De-aktualisiere.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||1|s|pres|;|s|imp|;|1//3|s|sub|I}}」的新頁面
9908907
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁə/}}
* {{audio|de|De-aktualisiere.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||1|s|pres|;|s|imp|;|1//3|s|sub|I}}
pbjzqarp0k5s0oqb02v3zx4x59mga8k
aktualisierst
0
3540394
9908908
2026-09-29T05:31:56Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯st/}} * {{audio|de|De-aktualisierst.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||2|s|pres}}」的新頁面
9908908
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯st/}}
* {{audio|de|De-aktualisierst.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||2|s|pres}}
mvzun5dqqba597yzh3kj1864dgk9vz6
aktualisiertest
0
3540395
9908909
2026-09-29T05:32:00Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯təst/}} * {{audio|de|De-aktualisiertest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||2|s|pret|;|2|s|sub|II}}」的新頁面
9908909
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯təst/}}
* {{audio|de|De-aktualisiertest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||2|s|pret|;|2|s|sub|II}}
qhqunatxm7p1gzgg4cu21gnws6bwrz9
aktualisier
0
3540396
9908910
2026-09-29T05:32:08Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯/}} * {{audio|de|De-aktualisier.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||s|imp}} # {{lb|de|口語}} {{verb form of|de|aktualisieren||1|s|pres}}」的新頁面
9908910
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯/}}
* {{audio|de|De-aktualisier.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||s|imp}}
# {{lb|de|口語}} {{verb form of|de|aktualisieren||1|s|pres}}
e58egz5uush3jes38wa20rrcc79i1ok
aktualisiertet
0
3540397
9908911
2026-09-29T05:32:10Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːɐ̯tət/}} * {{audio|de|De-aktualisiertet.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||2|p|pret|;|2|p|sub|II}}」的新頁面
9908911
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːɐ̯tət/}}
* {{audio|de|De-aktualisiertet.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||2|p|pret|;|2|p|sub|II}}
1y2ke5p0iwb9vm35h84qdakd0v8ewtu
aktualisierest
0
3540398
9908912
2026-09-29T05:32:16Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁəst/}} * {{audio|de|De-aktualisierest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||2|s|sub|I}}」的新頁面
9908912
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁəst/}}
* {{audio|de|De-aktualisierest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||2|s|sub|I}}
ik500ok1roolf3xl4olli5lr7ora4ov
aktualisieret
0
3540399
9908913
2026-09-29T05:32:18Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˌaktualiˈziːʁət/}} * {{audio|de|De-aktualisieret.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|aktualisieren||2|p|sub|I}}」的新頁面
9908913
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˌaktualiˈziːʁət/}}
* {{audio|de|De-aktualisieret.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|aktualisieren||2|p|sub|I}}
1wincu0uf7ig17h15amizp10idr01tp
identischer
0
3540400
9908914
2026-09-29T05:33:57Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/iˈdɛntɪʃɐ/}} * {{audio|de|De-identischer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|identisch}}」的新頁面
9908914
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/iˈdɛntɪʃɐ/}}
* {{audio|de|De-identischer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|identisch}}
tgk77ka2wn2dknagsfibvvufzbzouqu
identischen
0
3540401
9908915
2026-09-29T05:34:00Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/iˈdɛntɪʃn̩/}} * {{audio|de|De-identischen.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|identisch}}」的新頁面
9908915
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/iˈdɛntɪʃn̩/}}
* {{audio|de|De-identischen.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|identisch}}
flsvirb8r3wh8g75xwj76hmvg209vqo
identischem
0
3540402
9908916
2026-09-29T05:34:03Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/iˈdɛntɪʃm̩/}} * {{audio|de|De-identischem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|identisch}}」的新頁面
9908916
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/iˈdɛntɪʃm̩/}}
* {{audio|de|De-identischem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|identisch}}
a5g11t4ae1l7or8iztn9s1bowov0q34
identische
0
3540403
9908917
2026-09-29T05:34:09Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/iˈdɛntɪʃə/}} * {{audio|de|De-identische.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|identisch}}」的新頁面
9908917
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/iˈdɛntɪʃə/}}
* {{audio|de|De-identische.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|identisch}}
j9xav4yke1jnuye81puka51vtzsnl4x
identisches
0
3540404
9908918
2026-09-29T05:34:22Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/iˈdɛntɪʃəs/}} * {{audio|de|De-identisches.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|identisch}}」的新頁面
9908918
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/iˈdɛntɪʃəs/}}
* {{audio|de|De-identisches.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|identisch}}
dj77yv4qoza3ctzlim0y8m8cczsqitc
inspirierender
0
3540405
9908922
2026-09-29T05:36:06Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁəndɐ/}} * {{audio|de|De-inspirierender.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspirierend}}」的新頁面
9908922
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁəndɐ/}}
* {{audio|de|De-inspirierender.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspirierend}}
4105b6twy2z0y83zg68och6s8rfxyjh
inspirierenden
0
3540406
9908923
2026-09-29T05:36:09Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁəndn̩/}} * {{audio|de|De-inspirierenden.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspirierend}}」的新頁面
9908923
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁəndn̩/}}
* {{audio|de|De-inspirierenden.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspirierend}}
lwbeh62sbbh9qx78f95n773fxhgxsha
inspirierendem
0
3540407
9908924
2026-09-29T05:36:12Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁəndəm/}} * {{audio|de|De-inspirierendem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspirierend}}」的新頁面
9908924
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁəndəm/}}
* {{audio|de|De-inspirierendem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspirierend}}
ccs0dldcovefbp2e1pivn4d0a0hdxma
inspirierende
0
3540408
9908925
2026-09-29T05:36:15Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁəndə/}} * {{audio|de|De-inspirierende.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspirierend}}」的新頁面
9908925
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁəndə/}}
* {{audio|de|De-inspirierende.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspirierend}}
3g14mrynzkstlyhjqdvkera7rdm19mr
inspirierendes
0
3540409
9908926
2026-09-29T05:36:18Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁəndəs/}} * {{audio|de|De-inspirierendes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspirierend}}」的新頁面
9908926
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁəndəs/}}
* {{audio|de|De-inspirierendes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspirierend}}
sgo8dihf2lrgnvj97g568o0xjjphgy1
inspirierter
0
3540410
9908927
2026-09-29T05:36:50Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯tɐ/}} * {{audio|de|De-inspirierter.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspiriert}}」的新頁面
9908927
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯tɐ/}}
* {{audio|de|De-inspirierter.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspiriert}}
ddnuxg1riqv5swgweyd5a78uxbhcr6o
inspiriertem
0
3540411
9908928
2026-09-29T05:36:52Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯təm/}} * {{audio|de|De-inspiriertem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspiriert}}」的新頁面
9908928
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯təm/}}
* {{audio|de|De-inspiriertem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspiriert}}
4w05e118frh1etuv10jt3938k4c8xax
inspiriertes
0
3540412
9908929
2026-09-29T05:36:53Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯təs/}} * {{audio|de|De-inspiriertes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspiriert}}」的新頁面
9908929
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯təs/}}
* {{audio|de|De-inspiriertes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspiriert}}
ie2pzzuzmiswqahayom32amkni1tled
inspirierte
0
3540413
9908930
2026-09-29T05:37:08Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯tə/}} * {{audio|de|De-inspirierte.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||1//3|s|pret|;|1//3|s|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspiriert}}」的新頁面
9908930
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯tə/}}
* {{audio|de|De-inspirierte.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||1//3|s|pret|;|1//3|s|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspiriert}}
h8lyy83mlo9kmz4ua2yylqreuarojf7
inspirierten
0
3540414
9908931
2026-09-29T05:37:10Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯tn̩/}} * {{audio|de|De-inspirierten.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||1//3|p|pret|;|1//3|p|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|inspiriert}}」的新頁面
9908931
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯tn̩/}}
* {{audio|de|De-inspirierten.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||1//3|p|pret|;|1//3|p|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|inspiriert}}
asw6u3d1ia1eonyysitahr3k00bm2to
inspiriere
0
3540415
9908932
2026-09-29T05:37:26Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁə/}} * {{audio|de|De-inspiriere.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||1|s|pres|;|1//3|s|sub|I|;|s|imp}}」的新頁面
9908932
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁə/}}
* {{audio|de|De-inspiriere.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||1|s|pres|;|1//3|s|sub|I|;|s|imp}}
9vupq2xujn0azbjwlyx37c8ufz3qoh2
inspirierst
0
3540416
9908933
2026-09-29T05:37:29Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯st/}} * {{audio|de|De-inspirierst.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||2|s|pres}}」的新頁面
9908933
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯st/}}
* {{audio|de|De-inspirierst.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||2|s|pres}}
5r9h0slcezswo16fzu834jp7o92vwjq
inspiriertest
0
3540417
9908934
2026-09-29T05:37:34Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯təst/}} * {{audio|de|De-inspiriertest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||2|s|pret|;|2|s|sub|II}}」的新頁面
9908934
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯təst/}}
* {{audio|de|De-inspiriertest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||2|s|pret|;|2|s|sub|II}}
thrduceg7a5dgszdb54mbxw64t2f24b
inspirier
0
3540418
9908935
2026-09-29T05:37:36Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯/}} * {{audio|de|De-inspirier.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{infl of|de|inspirieren||s|imp}}」的新頁面
9908935
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯/}}
* {{audio|de|De-inspirier.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{infl of|de|inspirieren||s|imp}}
clnbk6d1edzylyhiywfwtk8c648u08w
inspiriertet
0
3540419
9908936
2026-09-29T05:37:40Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːɐ̯tət/}} * {{audio|de|De-inspiriertet.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||2|p|pret|;|2|p|sub|II}}」的新頁面
9908936
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːɐ̯tət/}}
* {{audio|de|De-inspiriertet.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||2|p|pret|;|2|p|sub|II}}
ap6ku33bicoglpkjs2ztztmx1t8tnqo
inspirierest
0
3540420
9908937
2026-09-29T05:37:43Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁəst/}} * {{audio|de|De-inspirierest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||2|s|sub|I}}」的新頁面
9908937
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁəst/}}
* {{audio|de|De-inspirierest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||2|s|sub|I}}
tgb3op7gskilx7txcd7wf0p5mvhragv
inspirieret
0
3540421
9908938
2026-09-29T05:37:47Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪnspiˈʁiːʁət/}} * {{audio|de|De-inspirieret.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|inspirieren||2|p|sub|I}}」的新頁面
9908938
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪnspiˈʁiːʁət/}}
* {{audio|de|De-inspirieret.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|inspirieren||2|p|sub|I}}
saovxsbgfe53sfr6ybfj9sl8nkldn6f
geeinter
0
3540422
9908939
2026-09-29T05:39:08Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡəˈʔaɪ̯ntɐ/}} * {{audio|de|De-geeinter.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|geeint}}」的新頁面
9908939
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡəˈʔaɪ̯ntɐ/}}
* {{audio|de|De-geeinter.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|geeint}}
hb2b4bdf5noayl30jogace1jjhka7hk
geeinten
0
3540423
9908940
2026-09-29T05:39:11Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡəˈʔaɪ̯ntn̩/}} * {{audio|de|De-geeinten.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|geeint}}」的新頁面
9908940
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡəˈʔaɪ̯ntn̩/}}
* {{audio|de|De-geeinten.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|geeint}}
806zf0huw5ns15oocweznlnxrurhrw4
geeintem
0
3540424
9908941
2026-09-29T05:39:14Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡəˈʔaɪ̯ntəm/}} * {{audio|de|De-geeintem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|geeint}}」的新頁面
9908941
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡəˈʔaɪ̯ntəm/}}
* {{audio|de|De-geeintem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|geeint}}
j23i0dk906ffoxlj1bkho74c980xa49
geeinte
0
3540425
9908942
2026-09-29T05:39:18Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡəˈʔaɪ̯ntə/}} * {{audio|de|De-geeinte.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|geeint}}」的新頁面
9908942
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡəˈʔaɪ̯ntə/}}
* {{audio|de|De-geeinte.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|geeint}}
jqetnd1g2ay3otprayrgg53sxjvkjav
geeintes
0
3540426
9908943
2026-09-29T05:39:21Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɡəˈʔaɪ̯ntəs/}} * {{audio|de|De-geeintes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|geeint}}」的新頁面
9908943
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɡəˈʔaɪ̯ntəs/}}
* {{audio|de|De-geeintes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|geeint}}
g5ziezxzppoj0deejmzd4gr4e0ebnf0
sterbender
0
3540427
9908945
2026-09-29T05:40:03Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʃtɛʁbn̩dɐ/}} * {{audio|de|De-sterbender.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|sterbend}}」的新頁面
9908945
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʃtɛʁbn̩dɐ/}}
* {{audio|de|De-sterbender.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|sterbend}}
8aahhyjwdbcmig3v9gzj9wi928arfbg
sterbenden
0
3540428
9908946
2026-09-29T05:40:06Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʃtɛʁbn̩dən/}} * {{audio|de|De-sterbenden.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|sterbend}}」的新頁面
9908946
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʃtɛʁbn̩dən/}}
* {{audio|de|De-sterbenden.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|sterbend}}
1mc8r0584f8qwu4lxe5a5vp6rrl6979
sterbendem
0
3540429
9908947
2026-09-29T05:40:09Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʃtɛʁbn̩dəm/}} * {{audio|de|De-sterbendem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|sterbend}}」的新頁面
9908947
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʃtɛʁbn̩dəm/}}
* {{audio|de|De-sterbendem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|sterbend}}
h8i1yw73xyutirfnkqnndpvpnqk3fp1
sterbende
0
3540430
9908948
2026-09-29T05:40:13Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʃtɛʁbn̩də/}} * {{audio|de|De-sterbende.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|sterbend}}」的新頁面
9908948
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʃtɛʁbn̩də/}}
* {{audio|de|De-sterbende.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|sterbend}}
6j2p4f9fa3hehodif4treu6um03kt0o
sterbendes
0
3540431
9908949
2026-09-29T05:40:16Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʃtɛʁbn̩dəs/}} * {{audio|de|De-sterbendes.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|sterbend}}」的新頁面
9908949
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʃtɛʁbn̩dəs/}}
* {{audio|de|De-sterbendes.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|sterbend}}
ewi967qvqeb7a62mldveo8gs13jobze
unbeeinflusst
0
3540432
9908950
2026-09-29T05:41:28Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===詞源=== {{ety|de|:af|un-|beeinflusst}} {{prefix|de|un|beeinflusst}} ===發音=== * {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊst/}} * {{audio|de|De-unbeeinflusst.ogg|a=柏林}} ===形容詞=== {{de-adj}} # 未受[[影響]]的 ====變格==== {{de-adecl}}」的新頁面
9908950
wikitext
text/x-wiki
==德語==
===詞源===
{{ety|de|:af|un-|beeinflusst}}
{{prefix|de|un|beeinflusst}}
===發音===
* {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊst/}}
* {{audio|de|De-unbeeinflusst.ogg|a=柏林}}
===形容詞===
{{de-adj}}
# 未受[[影響]]的
====變格====
{{de-adecl}}
dlw96c1h00evg2vi275ouloqcsd9ynh
unbeeinflusster
0
3540433
9908951
2026-09-29T05:41:41Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstɐ/}} * {{audio|de|De-unbeeinflusster.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|unbeeinflusst}}」的新頁面
9908951
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstɐ/}}
* {{audio|de|De-unbeeinflusster.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|unbeeinflusst}}
hn9d7rd6yzwbklsv2r05jk66u89hhv1
unbeeinflussten
0
3540434
9908952
2026-09-29T05:41:44Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstn̩/}} * {{audio|de|De-unbeeinflussten.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|unbeeinflusst}}」的新頁面
9908952
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstn̩/}}
* {{audio|de|De-unbeeinflussten.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|unbeeinflusst}}
g380j00vrcqi946fl6xno3v6rdkttyn
unbeeinflusstem
0
3540435
9908953
2026-09-29T05:41:57Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstəm/}} * {{audio|de|De-unbeeinflusstem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|unbeeinflusst}}」的新頁面
9908953
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstəm/}}
* {{audio|de|De-unbeeinflusstem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|unbeeinflusst}}
i026oz58hxcv43thr89db9evs4ldvw3
unbeeinflusste
0
3540436
9908954
2026-09-29T05:42:04Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstə/}} * {{audio|de|De-unbeeinflusste.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|unbeeinflusst}}」的新頁面
9908954
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstə/}}
* {{audio|de|De-unbeeinflusste.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|unbeeinflusst}}
cqkzbnmmayyi9m2d3jhbq64dnf7y09u
unbeeinflusstes
0
3540437
9908955
2026-09-29T05:42:06Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstəs/}} * {{audio|de|De-unbeeinflusstes.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|unbeeinflusst}}」的新頁面
9908955
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈʊnbəˌʔaɪ̯nflʊstəs/}}
* {{audio|de|De-unbeeinflusstes.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|unbeeinflusst}}
ewtcs6w0mjgxpzhl3l6wdzsr1tp5q9x
symbolisierend
0
3540438
9908958
2026-09-29T05:43:51Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁənt/}} * {{audio|de|De-symbolisierend.ogg|a=柏林}} ===分詞=== {{head|de|現在分詞}} # {{present participle of|de|symbolisieren}} ====變格==== {{de-adecl}}」的新頁面
9908958
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁənt/}}
* {{audio|de|De-symbolisierend.ogg|a=柏林}}
===分詞===
{{head|de|現在分詞}}
# {{present participle of|de|symbolisieren}}
====變格====
{{de-adecl}}
8jsdds7gzh648qxphrcwhxezcq27fiy
symbolisierender
0
3540439
9908959
2026-09-29T05:44:06Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁəndɐ/}} * {{audio|de|De-symbolisierender.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisierend}}」的新頁面
9908959
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁəndɐ/}}
* {{audio|de|De-symbolisierender.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisierend}}
q8b864rmw571xel8qj45mnnst08sdom
symbolisierenden
0
3540440
9908960
2026-09-29T05:44:08Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁəndn̩/}} * {{audio|de|De-symbolisierenden.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisierend}}」的新頁面
9908960
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁəndn̩/}}
* {{audio|de|De-symbolisierenden.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisierend}}
8x6xlwom5wuje722q6demgqo3hcjdxl
symbolisierendem
0
3540441
9908961
2026-09-29T05:44:39Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁəndəm/}} * {{audio|de|De-symbolisierendem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisierend}}」的新頁面
9908961
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁəndəm/}}
* {{audio|de|De-symbolisierendem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisierend}}
mppsbd6icxy6uqauluccfuog5p3mllc
symbolisierende
0
3540442
9908962
2026-09-29T05:44:40Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁəndə/}} * {{audio|de|De-symbolisierende.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisierend}}」的新頁面
9908962
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁəndə/}}
* {{audio|de|De-symbolisierende.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisierend}}
e8am5gt2tatec4m5tvdzoq60sg61xyl
symbolisierendes
0
3540443
9908963
2026-09-29T05:44:42Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁəndəs/}} * {{audio|de|De-symbolisierendes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisierend}}」的新頁面
9908963
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁəndəs/}}
* {{audio|de|De-symbolisierendes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisierend}}
iijkj53tah8fxhe5nl5y9d1b7ow9bj9
symbolisiert
0
3540444
9908964
2026-09-29T05:44:59Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯t/}} * {{audio|de|De-symbolisiert.ogg|a=柏林}} ===分詞=== {{head|de|過去分詞}} # {{past participle of|de|symbolisieren}} ====變格==== {{de-adecl}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||3|s|pres|;|2|p|pres|;|p|imp}}」的新頁面
9908964
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯t/}}
* {{audio|de|De-symbolisiert.ogg|a=柏林}}
===分詞===
{{head|de|過去分詞}}
# {{past participle of|de|symbolisieren}}
====變格====
{{de-adecl}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||3|s|pres|;|2|p|pres|;|p|imp}}
f4piqnk4gbrc3y1d6qrfk7tofjvjf1z
symbolisierter
0
3540445
9908965
2026-09-29T05:45:13Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯tɐ/}} * {{audio|de|De-symbolisierter.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisiert}}」的新頁面
9908965
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯tɐ/}}
* {{audio|de|De-symbolisierter.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisiert}}
1ao9esohq2h05tf7vglav8jhgcy02hf
symbolisiertem
0
3540446
9908966
2026-09-29T05:45:14Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯təm/}} * {{audio|de|De-symbolisiertem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisiert}}」的新頁面
9908966
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯təm/}}
* {{audio|de|De-symbolisiertem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisiert}}
14yf8mii5wwgll48nrv1v1n8e10vh03
symbolisiertes
0
3540447
9908967
2026-09-29T05:45:17Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯təs/}} * {{audio|de|De-symbolisiertes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisiert}}」的新頁面
9908967
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯təs/}}
* {{audio|de|De-symbolisiertes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisiert}}
t1z2mf9qeuutzxv9icyjg33jtjfszva
symbolisierte
0
3540448
9908968
2026-09-29T05:45:56Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯tə/}} * {{audio|de|De-symbolisierte.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||1//3|s|pret|;|1//3|s|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisiert}}」的新頁面
9908968
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯tə/}}
* {{audio|de|De-symbolisierte.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||1//3|s|pret|;|1//3|s|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisiert}}
dy0p5u60qg5ivhyi37h5g5kpj064e4c
symbolisierten
0
3540449
9908969
2026-09-29T05:45:58Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯tn̩/}} * {{audio|de|De-symbolisierten.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||1//3|p|pret|;|1//3|p|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|symbolisiert}}」的新頁面
9908969
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯tn̩/}}
* {{audio|de|De-symbolisierten.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||1//3|p|pret|;|1//3|p|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|symbolisiert}}
pstgnuymw6i2l4zrigqq44mpf7pppts
symbolisiere
0
3540450
9908970
2026-09-29T05:46:22Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁə/}} * {{audio|de|De-symbolisiere.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||1|s|pres|;|s|imp|;|1//3|s|sub|I}}」的新頁面
9908970
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁə/}}
* {{audio|de|De-symbolisiere.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||1|s|pres|;|s|imp|;|1//3|s|sub|I}}
534zj71hk6jmqt038b166zy1x3797ue
symbolisiertest
0
3540451
9908971
2026-09-29T05:46:27Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯təst/}} * {{audio|de|De-symbolisiertest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||2|s|pret|;|2|s|sub|II}}」的新頁面
9908971
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯təst/}}
* {{audio|de|De-symbolisiertest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||2|s|pret|;|2|s|sub|II}}
o4b5s8re4omi0621d2pvjk2fbk8mbl6
symbolisierst
0
3540452
9908972
2026-09-29T05:46:31Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯st/}} * {{audio|de|De-symbolisierst.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||2|s|pres}}」的新頁面
9908972
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯st/}}
* {{audio|de|De-symbolisierst.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||2|s|pres}}
a39gwxhc3dvq0ghxq64457ofjeo6sy1
symbolisiertet
0
3540453
9908973
2026-09-29T05:46:35Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯tət/}} * {{audio|de|De-symbolisiertet.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||2|p|pret|;|2|p|sub|II}}」的新頁面
9908973
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯tət/}}
* {{audio|de|De-symbolisiertet.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||2|p|pret|;|2|p|sub|II}}
ddgenq2t9wnpwbe105nxmu2r2rdpf0a
symbolisierest
0
3540454
9908974
2026-09-29T05:47:46Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁəst/}} * {{audio|de|De-symbolisierest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||2|s|sub|I}}」的新頁面
9908974
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁəst/}}
* {{audio|de|De-symbolisierest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||2|s|sub|I}}
85usi84rg8ag2uk8ww60yruuwjgm1ee
symbolisieret
0
3540455
9908975
2026-09-29T05:47:50Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːʁət/}} * {{audio|de|De-symbolisieret.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|symbolisieren||2|p|sub|I}}」的新頁面
9908975
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːʁət/}}
* {{audio|de|De-symbolisieret.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|symbolisieren||2|p|sub|I}}
r9a2qvpwuwn309ef6drzlcwox7pzjxm
symbolisier
0
3540456
9908976
2026-09-29T05:47:59Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/zʏmboliˈziːɐ̯/}} * {{audio|de|De-symbolisier.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{infl of|de|symbolisieren||s|imp}}」的新頁面
9908976
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/zʏmboliˈziːɐ̯/}}
* {{audio|de|De-symbolisier.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{infl of|de|symbolisieren||s|imp}}
bpyttubqmqow65z7ld3khmea1py094l
linientreuer
0
3540457
9908978
2026-09-29T05:50:56Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯ɐ/}} * {{audio|de|De-linientreuer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級}} # {{comparative of|de|linientreu}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908978
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯ɐ/}}
* {{audio|de|De-linientreuer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級}}
# {{comparative of|de|linientreu}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|linientreu}}
ccjv02zf900nly6ledubj1bsmhb0jlq
linientreuen
0
3540458
9908979
2026-09-29T05:50:58Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯ən/}} * {{audio|de|De-linientreuen.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908979
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯ən/}}
* {{audio|de|De-linientreuen.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|linientreu}}
acbrxp7mr3lbud72jkrk9d0cl1glzkg
linientreuem
0
3540459
9908980
2026-09-29T05:50:59Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əm/}} * {{audio|de|De-linientreuem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908980
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əm/}}
* {{audio|de|De-linientreuem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|linientreu}}
jc9gnxi2aer27aqxdb00k4k365w7exe
linientreue
0
3540460
9908981
2026-09-29T05:51:01Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯ə/}} * {{audio|de|De-linientreue.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908981
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯ə/}}
* {{audio|de|De-linientreue.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|linientreu}}
4lsq9jvbfwejm5ad5x8l9xe1pnd6977
linientreues
0
3540461
9908982
2026-09-29T05:51:04Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əs/}} * {{audio|de|De-linientreues.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908982
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əs/}}
* {{audio|de|De-linientreues.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞變格形}}
# {{de-adj form of|linientreu}}
fws86zm1a7lwnczybw4w75haxhdji2h
linientreuerer
0
3540462
9908983
2026-09-29T05:51:38Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁɐ/}} * {{audio|de|De-linientreuerer.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908983
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁɐ/}}
* {{audio|de|De-linientreuerer.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|linientreu}}
aipwwe1vmdd2uo19aonihdy5frgxdts
linientreueren
0
3540463
9908984
2026-09-29T05:51:40Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁən/}} * {{audio|de|De-linientreueren.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908984
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁən/}}
* {{audio|de|De-linientreueren.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|linientreu}}
qpji2gicq2dciplvkfvzspiil1rfm2x
linientreuerem
0
3540464
9908985
2026-09-29T05:51:42Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁəm/}} * {{audio|de|De-linientreuerem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908985
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁəm/}}
* {{audio|de|De-linientreuerem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|linientreu}}
4587oh6k9mak5dvb2vm2aj104ec7ftb
linientreuere
0
3540465
9908986
2026-09-29T05:51:47Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁə/}} * {{audio|de|De-linientreuere.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908986
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁə/}}
* {{audio|de|De-linientreuere.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|linientreu}}
52c9xrtmwr2x96r0pmt1mhq2ci2y70l
linientreueres
0
3540466
9908987
2026-09-29T05:51:49Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁəs/}} * {{audio|de|De-linientreueres.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞比較級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908987
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əʁəs/}}
* {{audio|de|De-linientreueres.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞比較級變格形}}
# {{de-adj form of|linientreu}}
fsewf860wg4c5l265mr2uqyxop5lp3v
linientreuester
0
3540467
9908988
2026-09-29T05:52:12Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstɐ/}} * {{audio|de|De-linientreuester.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu|ster}}」的新頁面
9908988
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstɐ/}}
* {{audio|de|De-linientreuester.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu|ster}}
l6m1alpqlz96zegr1ngt7ml3ci5iij0
linientreuster
0
3540468
9908989
2026-09-29T05:52:16Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stɐ/}} * {{audio|de|De-linientreuster.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908989
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stɐ/}}
* {{audio|de|De-linientreuster.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu}}
a1m3h1ubk7vz9437klj7iqlhwwmxi61
linientreuesten
0
3540469
9908990
2026-09-29T05:52:22Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstn̩/}} * {{audio|de|De-linientreuesten.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級}} # {{superlative of|de|linientreu}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu|sten}}」的新頁面
9908990
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstn̩/}}
* {{audio|de|De-linientreuesten.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級}}
# {{superlative of|de|linientreu}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu|sten}}
ekg4yogket3d0kycja77k2493t0zizm
linientreusten
0
3540470
9908991
2026-09-29T05:52:24Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stn̩/}} * {{audio|de|De-linientreusten.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級}} # {{superlative of|de|linientreu}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908991
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stn̩/}}
* {{audio|de|De-linientreusten.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級}}
# {{superlative of|de|linientreu}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu}}
t9x0jy63h0rzezhcvz1isfyl9ytgdfp
linientreueste
0
3540471
9908992
2026-09-29T05:52:31Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstə/}} * {{audio|de|De-linientreueste.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu|ste}}」的新頁面
9908992
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstə/}}
* {{audio|de|De-linientreueste.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu|ste}}
ot6m8kbov5p743f5buxzdd9fhugfqos
linientreuste
0
3540472
9908993
2026-09-29T05:52:32Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stə/}} * {{audio|de|De-linientreuste.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908993
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stə/}}
* {{audio|de|De-linientreuste.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu}}
b8gy2ildckaxsfgwrpogw281o9jaer8
linientreuestem
0
3540473
9908994
2026-09-29T05:53:09Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstəm/}} * {{audio|de|De-linientreuestem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu|stem}}」的新頁面
9908994
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstəm/}}
* {{audio|de|De-linientreuestem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu|stem}}
kzee6zn8wxbpj59t5jmcgwb93bv9dj0
linientreuestes
0
3540474
9908995
2026-09-29T05:53:12Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstəs/}} * {{audio|de|De-linientreuestes.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu|stes}}」的新頁面
9908995
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯əstəs/}}
* {{audio|de|De-linientreuestes.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu|stes}}
ka2sadon9k8x8sxjvxv58qpdbovnkur
linientreustem
0
3540475
9908996
2026-09-29T05:53:15Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stəm/}} * {{audio|de|De-linientreustem.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908996
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stəm/}}
* {{audio|de|De-linientreustem.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu}}
02gcpsivw3f3wpafz6bqp8mfmsov72q
linientreustes
0
3540476
9908997
2026-09-29T05:53:17Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stəs/}} * {{audio|de|De-linientreustes.ogg|a=柏林}} ===形容詞=== {{head|de|形容詞最高級變格形}} # {{de-adj form of|linientreu}}」的新頁面
9908997
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ˈliːni̯ənˌtʁɔɪ̯stəs/}}
* {{audio|de|De-linientreustes.ogg|a=柏林}}
===形容詞===
{{head|de|形容詞最高級變格形}}
# {{de-adj form of|linientreu}}
hdvg7bhel3vuiq2gymopnhxwzc3iqlk
interpretierender
0
3540477
9909001
2026-09-29T06:07:14Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁəndɐ/}} * {{audio|de|De-interpretierender.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretierend}}」的新頁面
9909001
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁəndɐ/}}
* {{audio|de|De-interpretierender.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretierend}}
bv3zr82lzqamjbba1wlsryap4apolvj
interpretierenden
0
3540478
9909002
2026-09-29T06:07:17Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁəndn̩/}} * {{audio|de|De-interpretierenden.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretierend}}」的新頁面
9909002
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁəndn̩/}}
* {{audio|de|De-interpretierenden.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretierend}}
nir8vu2qydge7w9ru4ge7wmk29vdvav
interpretierendem
0
3540479
9909003
2026-09-29T06:07:21Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁəndəm/}} * {{audio|de|De-interpretierendem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretierend}}」的新頁面
9909003
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁəndəm/}}
* {{audio|de|De-interpretierendem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretierend}}
69x5ouhpxye614x9gyqjpp20x2o7z4m
interpretierende
0
3540480
9909004
2026-09-29T06:07:24Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁəndə/}} * {{audio|de|De-interpretierende.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretierend}}」的新頁面
9909004
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁəndə/}}
* {{audio|de|De-interpretierende.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretierend}}
cnmvzxzni07iwqk3vnd15t9y01atfoe
interpretierendes
0
3540481
9909005
2026-09-29T06:07:27Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁəndəs/}} * {{audio|de|De-interpretierendes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretierend}}」的新頁面
9909005
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁəndəs/}}
* {{audio|de|De-interpretierendes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretierend}}
k42c34s51tg8imrdurno6g7apxmdsjk
interpretierter
0
3540482
9909006
2026-09-29T06:07:39Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tɐ/}} * {{audio|de|De-interpretierter.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretiert}}」的新頁面
9909006
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tɐ/}}
* {{audio|de|De-interpretierter.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretiert}}
08jwv6ddah7sqrj7gcd2i6ljdy6f8uh
interpretiertem
0
3540483
9909007
2026-09-29T06:07:42Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯təm/}} * {{audio|de|De-interpretiertem.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretiert}}」的新頁面
9909007
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯təm/}}
* {{audio|de|De-interpretiertem.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretiert}}
t2x9vnj9chhul0twirdj7b9pnnpa72r
interpretiertes
0
3540484
9909008
2026-09-29T06:07:43Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯təs/}} * {{audio|de|De-interpretiertes.ogg|a=柏林}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretiert}}」的新頁面
9909008
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯təs/}}
* {{audio|de|De-interpretiertes.ogg|a=柏林}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretiert}}
lhkxo8vcqzx1qhyswv9n1lc0mb9hp2q
interpretierten
0
3540485
9909009
2026-09-29T06:07:57Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tn̩/}} * {{audio|de|De-interpretierten.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||1//3|p|pret|;|1//3|p|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretiert}}」的新頁面
9909009
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tn̩/}}
* {{audio|de|De-interpretierten.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||1//3|p|pret|;|1//3|p|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretiert}}
n3sg2198liiasmzblz8lx9hcdc0w326
interpretierte
0
3540486
9909010
2026-09-29T06:08:00Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tə/}} * {{audio|de|De-interpretierte.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||1//3|s|pret|;|1//3|s|sub|II}} ===分詞=== {{head|de|分詞變格形}} # {{de-adj form of|interpretiert}}」的新頁面
9909010
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tə/}}
* {{audio|de|De-interpretierte.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||1//3|s|pret|;|1//3|s|sub|II}}
===分詞===
{{head|de|分詞變格形}}
# {{de-adj form of|interpretiert}}
h801bfyf9rpwbr5i7z4h1sq8tlvk5jz
interpretiere
0
3540487
9909012
2026-09-29T06:08:29Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁə/}} * {{audio|de|De-interpretiere.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||1|s}}」的新頁面
9909012
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁə/}}
* {{audio|de|De-interpretiere.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||1|s}}
0mas82cxvctwv3358djbwnzz8zbp2p8
interpretierst
0
3540488
9909013
2026-09-29T06:08:31Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯st/}} * {{audio|de|De-interpretierst.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||2|s|pres}}」的新頁面
9909013
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯st/}}
* {{audio|de|De-interpretierst.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||2|s|pres}}
t9hcmbfppp5xpvsxaetdo0ypan6j9dl
interpretiertest
0
3540489
9909014
2026-09-29T06:08:33Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯təst/}} * {{audio|de|De-interpretiertest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||2|s|pret|;|2|s|sub|II}}」的新頁面
9909014
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯təst/}}
* {{audio|de|De-interpretiertest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||2|s|pret|;|2|s|sub|II}}
kj4h9lfu0naz1ftohuzcgmwu2t4dtfi
interpretier
0
3540490
9909015
2026-09-29T06:08:36Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯/}} * {{audio|de|De-interpretier.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{infl of|de|interpretieren||s|imp}}」的新頁面
9909015
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯/}}
* {{audio|de|De-interpretier.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{infl of|de|interpretieren||s|imp}}
97udtuywaw03nlgxng245wc5wk13qpz
interpretierest
0
3540491
9909016
2026-09-29T06:09:17Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁəst/}} * {{audio|de|De-interpretierest.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||2|s|sub|I}}」的新頁面
9909016
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁəst/}}
* {{audio|de|De-interpretierest.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||2|s|sub|I}}
0w0g4b1viyxvrf2vl05yr23f6p2i7ru
interpretieret
0
3540492
9909017
2026-09-29T06:09:27Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːʁət/}} * {{audio|de|De-interpretieret.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||2|p|sub|I}}」的新頁面
9909017
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːʁət/}}
* {{audio|de|De-interpretieret.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||2|p|sub|I}}
45n4iewox0cfp8zofdih5lizbancxnx
interpretiertet
0
3540493
9909019
2026-09-29T06:09:29Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==德語== ===發音=== * {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tət/}} * {{audio|de|De-interpretiertet.ogg|a=柏林}} ===動詞=== {{head|de|動詞變位形式}} # {{verb form of|de|interpretieren||2|p|pret|;|2|p|sub|II}}」的新頁面
9909019
wikitext
text/x-wiki
==德語==
===發音===
* {{IPA|de|/ɪntɐpʁeˈtiːɐ̯tət/}}
* {{audio|de|De-interpretiertet.ogg|a=柏林}}
===動詞===
{{head|de|動詞變位形式}}
# {{verb form of|de|interpretieren||2|p|pret|;|2|p|sub|II}}
f48z0mpgf1acpgmgvdn80y8qkorr7cn
Template:Name respelling
10
3540494
9909022
2026-09-29T06:15:52Z
TongcyDai
53191
[[w:WP:AES|←]]建立內容為「{{#invoke:names|name_translit|desctext=重新拼寫}}<!-- --><noinclude>{{documentation}}[[Category:定義模板]]</noinclude>」的新頁面
9909022
wikitext
text/x-wiki
{{#invoke:names|name_translit|desctext=重新拼寫}}<!--
--><noinclude>{{documentation}}[[Category:定義模板]]</noinclude>
lchxruwjqnoo3p2t3inme2fffdhv1aa
물푸레나무
0
3540495
9909023
2026-09-29T07:07:39Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==朝鮮語== ===其他形式=== * {{ko-l|물푸레}} * {{ko-l|수청목|水靑木}} * {{ko-l|수창목|水蒼木}} ===詞源=== 最早的間接引文是漢文{{m|zh|水靑木}},見於13世紀中葉所作的《{{lang|ko|향약구급방 / 鄕藥救急方}}》。 其韓文形式{{ko-etym-native|hm|므프레|mùphùlèy|dot=,}}源自{{m|okm|믈|tr=mul|t=水}} + {{m|okm|[[프르다|플—]]|tr=phul-|t=綠色,藍色}} + {{m|okm|-개|게|t…」的新頁面
9909023
wikitext
text/x-wiki
==朝鮮語==
===其他形式===
* {{ko-l|물푸레}}
* {{ko-l|수청목|水靑木}}
* {{ko-l|수창목|水蒼木}}
===詞源===
最早的間接引文是漢文{{m|zh|水靑木}},見於13世紀中葉所作的《{{lang|ko|향약구급방 / 鄕藥救急方}}》。
其韓文形式{{ko-etym-native|hm|므프레|mùphùlèy|dot=,}}源自{{m|okm|믈|tr=mul|t=水}} + {{m|okm|[[프르다|플—]]|tr=phul-|t=綠色,藍色}} + {{m|okm|-개|게|tr=-key|pos=施動名詞後綴}}。{{lit|使水變藍者}}。梣樹的樹皮可以產出藍色染料。
現代通用詞形加上了{{ko-l|나무|gloss=樹}},最早見於1617年。
[[File:Fraxinus chinensis kz01.jpg|thumb|{{ko-l|물푸레나무}}]]
===發音===
{{ko-IPA}}
===名詞===
{{ko-noun}}
# [[梣樹]]
# 特指[[白蠟樹]]({{taxlink|Fraxinus chinensis|species}})
====派生詞彙====
* {{ko-l|구주물푸레나무|歐洲—|gloss=歐洲白蠟樹}}
* {{ko-l|미국물푸레나무|美國—|gloss=北美白梣}}
* {{ko-l|붉은물푸레나무|gloss=美國紅梣}}
* {{ko-l|쇠물푸레나무||gloss=小蠟樹}}
{{C|ko|木樨科植物}}
siosxphjq9swzjqr3w5kjvy231d48tp
Module:Pron qualifier
828
3540496
9909025
2026-09-29T07:21:46Z
TongcyBot
83009
TongcyBot移动页面[[Module:Pron qualifier]]至[[Module:Decorations]]:同步 enwikt:pron qualifier → decorations
9909025
Scribunto
text/plain
return require [[Module:Decorations]]
ojmmq0nyfawz9snajjutnrgmbn8wrmk
술고래
0
3540497
9909097
2026-09-29T07:27:49Z
Sayonzei
40728
[[w:WP:AES|←]]建立內容為「==朝鮮語== ===詞源=== {{ko-etym-native}} 源自 {{com|ko|술|고래|t1=酒|t2=鲸;酒鬼,嗜酒的人}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[酒鬼]]」的新頁面
9909097
wikitext
text/x-wiki
==朝鮮語==
===詞源===
{{ko-etym-native}} 源自 {{com|ko|술|고래|t1=酒|t2=鲸;酒鬼,嗜酒的人}}。
===發音===
{{ko-IPA}}
===名詞===
{{ko-noun}}
# [[酒鬼]]
9detq9grx5du5gf1eceagkrpmog87ud
Cieńciała
0
3540498
9909138
2026-09-29T08:41:46Z
TongcyDai
53191
TongcyDai移动页面[[Cieńciała]]至[[Cienciała]]
9909138
wikitext
text/x-wiki
#REDIRECT [[Cienciała]]
rmlwr61ckzefhsx3v6ghk947x43v2ed
Category:Rhymes:西里西亞語/aɲɛ
14
3540499
9909157
2026-09-29T08:54:46Z
TongcyBot
83009
養貓
9909157
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:Rhymes:西里西亞語/aɲɛ/3音節
14
3540500
9909158
2026-09-29T08:54:54Z
TongcyBot
83009
養貓
9909158
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:古波蘭語動詞性名詞
14
3540501
9909159
2026-09-29T08:55:00Z
TongcyBot
83009
養貓
9909159
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:含有後綴-anie的古波蘭語詞
14
3540502
9909160
2026-09-29T08:55:06Z
TongcyBot
83009
養貓
9909160
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:含有後綴-anie的西里西亞語詞
14
3540503
9909161
2026-09-29T08:55:13Z
TongcyBot
83009
養貓
9909161
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:波蘭語動名詞
14
3540504
9909162
2026-09-29T08:55:19Z
TongcyBot
83009
養貓
9909162
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:西里西亞語 暴力
14
3540505
9909163
2026-09-29T08:55:24Z
TongcyBot
83009
養貓
9909163
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:西里西亞語 集體
14
3540506
9909164
2026-09-29T08:55:32Z
TongcyBot
83009
養貓
9909164
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:西里西亞語 雜項
14
3540507
9909165
2026-09-29T08:55:38Z
TongcyBot
83009
養貓
9909165
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:西里西亞語動詞性名詞
14
3540508
9909166
2026-09-29T08:55:46Z
TongcyBot
83009
養貓
9909166
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Category:西里西亞語可數名詞
14
3540509
9909167
2026-09-29T08:55:53Z
TongcyBot
83009
養貓
9909167
wikitext
text/x-wiki
{{auto cat}}
eomzlm5v4j7ond1phrju7cnue91g5qx
Module:Zh/data/och-pron-ZS/㓨
828
3540510
9909204
2026-09-29T11:36:33Z
TongcyBot
83009
同步英語維基詞典
9909204
Scribunto
text/plain
return {
{ "1748", "朿", "賜", "0", "刺", "sʰeɡs", "俗刺字,見秦簡、論衡" },
}
21noudkyc13bhhi4ucpeuyf46ej61sb
Module:Zh/data/och-pron-ZS/俁
828
3540511
9909205
2026-09-29T11:36:35Z
TongcyBot
83009
同步英語維基詞典
9909205
Scribunto
text/plain
return {
{ "13107", "吴", "魚", "0", "麌", "ŋʷaʔ", "" },
}
0mtozxsexnh5xn4qs7m3kfadso5j1w0
Module:Zh/data/och-pron-ZS/儹
828
3540512
9909206
2026-09-29T11:36:37Z
TongcyBot
83009
同步英語維基詞典
9909206
Scribunto
text/plain
return {
{ "16531", "贊", "元", "3", "纂", "ʔsoːnʔ", "" },
}
njnepjafw7xkr04jdmj54tcsxmlvox6
Module:Zh/data/och-pron-ZS/嗥
828
3540513
9909207
2026-09-29T11:36:39Z
TongcyBot
83009
同步英語維基詞典
9909207
Scribunto
text/plain
return {
{ "3790", "皐", "幽", "1", "豪", "ɡuː", "" },
}
ocqx3lqbk47jgt8fzw5tk0mz86uu1uw
Module:Zh/data/och-pron-ZS/姍
828
3540514
9909208
2026-09-29T11:36:41Z
TongcyBot
83009
同步英語維基詞典
9909208
Scribunto
text/plain
return {
{ "11066", "删", "元", "1", "𦙽", "slaːn", "" },
}
jvqo9a17fv15xoucq296adwolsl5w82
Module:Zh/data/och-pron-ZS/屭
828
3540515
9909209
2026-09-29T11:36:43Z
TongcyBot
83009
同步英語維基詞典
9909209
Scribunto
text/plain
return {
{ "13428", "屓", "至", "1", "齂", "qʰrids", "見張衡賦" },
}
60x23pw3i4q78o3oc8adwrx7rzi5rc9
Module:Zh/data/och-pron-ZS/敿
828
3540516
9909210
2026-09-29T11:36:45Z
TongcyBot
83009
同步英語維基詞典
9909210
Scribunto
text/plain
return {
{ "10337", "喬", "宵", "2", "矯", "krewʔ", "" },
}
4y6kx2eheeszi481orlj7uqu6cuokd2
Module:Zh/data/och-pron-ZS/柵
828
3540517
9909211
2026-09-29T11:36:47Z
TongcyBot
83009
同步英語維基詞典
9909211
Scribunto
text/plain
return {
{ "1166", "册", "鐸", "0", "柵", "sʰraːɡ", "" },
{ "1169", "册", "錫", "0", "策", "sʰreːɡ", "" },
{ "11068", "删", "元", "1", "訕", "sraːns", "訓讀" },
}
jojoo9umiiv843x9hmj1xc5q9ip3o4n
Module:Zh/data/och-pron-ZS/栜
828
3540518
9909212
2026-09-29T11:36:48Z
TongcyBot
83009
同步英語維基詞典
9909212
Scribunto
text/plain
return {
{ "1753", "朿", "錫", "0", "栜", "sreːɡ", "" },
}
8a1hupselk9ynhip8tvhideo4i5cchs
Module:Zh/data/och-pron-ZS/榲
828
3540519
9909213
2026-09-29T11:36:50Z
TongcyBot
83009
同步英語維基詞典
9909213
Scribunto
text/plain
return {
{ "13003", "昷", "物", "2", "𩑦", "quːd", "" },
}
ngn9zlw4461pby0elxmqawvk89h7nxc
Module:Zh/data/och-pron-ZS/槔
828
3540520
9909214
2026-09-29T11:36:52Z
TongcyBot
83009
同步英語維基詞典
9909214
Scribunto
text/plain
return {
{ "3786", "皐", "幽", "1", "髙", "kuː", "" },
}
994s0ejh1rep1i0xn9pfbfzwz3tb9mi
Module:Zh/data/och-pron-ZS/氳
828
3540521
9909215
2026-09-29T11:36:54Z
TongcyBot
83009
同步英語維基詞典
9909215
Scribunto
text/plain
return {
{ "12980", "昷", "文", "2", "煴", "qun", "" },
}
6w1s6ynoslw9d5mpokkiukiu9ar9p1q
Module:Zh/data/och-pron-ZS/滾
828
3540522
9909216
2026-09-29T11:36:56Z
TongcyBot
83009
同步英語維基詞典
9909216
Scribunto
text/plain
return {
{ "4088", "公", "文", "2", "𩩌", "kluːnʔ", "與涫同源" },
}
olba0znpacdrck415be28h742uvtymf
Module:Zh/data/och-pron-ZS/灒
828
3540523
9909217
2026-09-29T11:36:58Z
TongcyBot
83009
同步英語維基詞典
9909217
Scribunto
text/plain
return {
{ "16519", "贊", "元", "1", "賛", "ʔsaːns", "" },
}
ml9nstbjasb8pi3udjug03f9zpn792t
Module:Zh/data/och-pron-ZS/熅
828
3540524
9909218
2026-09-29T11:37:00Z
TongcyBot
83009
同步英語維基詞典
9909218
Scribunto
text/plain
return {
{ "12981", "昷", "文", "2", "煴", "qun", "" },
{ "12994", "昷", "文", "2", "", "quns", "原列焮韻,尉分化字" },
}
aick4mcm0da2nt210pzh45x3lpe4746
Module:Zh/data/och-pron-ZS/疃
828
3540525
9909219
2026-09-29T11:37:01Z
TongcyBot
83009
同步英語維基詞典
9909219
Scribunto
text/plain
return {
{ "17430", "重", "元", "3", "疃", "tʰoːnʔ", "" },
{ "17431", "重", "文", "2", "", "tʰuːnʔ", "" },
}
29351hvi4gk79spvdw5yq4wxbkke29i
Module:Zh/data/och-pron-ZS/癒
828
3540526
9909220
2026-09-29T11:37:03Z
TongcyBot
83009
同步英語維基詞典
9909220
Scribunto
text/plain
return {
{ "15874", "俞", "侯", "0", "逾", "lo", "" },
{ "15878", "俞", "侯", "0", "庾", "loʔ", "" },
}
svoejeojll0c0lju55l5if6zskuv91n
Module:Zh/data/och-pron-ZS/皞
828
3540527
9909221
2026-09-29T11:37:05Z
TongcyBot
83009
同步英語維基詞典
9909221
Scribunto
text/plain
return {
{ "3792", "皐", "幽", "1", "晧", "ɡuːʔ", "" },
}
fm2xru8i37d0p9zsbbyhe8i8w59pwfz
Module:Zh/data/och-pron-ZS/縕
828
3540528
9909222
2026-09-29T11:37:07Z
TongcyBot
83009
同步英語維基詞典
9909222
Scribunto
text/plain
return {
{ "12975", "昷", "文", "2", "𥁕", "quːn", "" },
{ "12982", "昷", "文", "2", "煴", "qun", "" },
{ "12988", "昷", "文", "2", "惲", "qunʔ", "" },
{ "12997", "昷", "文", "2", "醖", "quns", "" },
}
clr2tpurv1ody13eyve1b0qsasazlgk
Module:Zh/data/och-pron-ZS/胣
828
3540529
9909223
2026-09-29T11:37:08Z
TongcyBot
83009
同步英語維基詞典
9909223
Scribunto
text/plain
return {
{ "14778", "也", "歌", "2", "褫", "l̥ʰalʔ", "移爾切注又切,見莊子" },
{ "14804", "也", "歌", "1", "酏", "lalʔ", "見莊子" },
}
tb1lwbjejrg0w3u78r0mw1u5qdsyqlb
Module:Zh/data/och-pron-ZS/膃
828
3540530
9909224
2026-09-29T11:37:10Z
TongcyBot
83009
同步英語維基詞典
9909224
Scribunto
text/plain
return {
{ "13000", "昷", "物", "2", "𩑦", "quːd", "" },
}
3o20h86m6vcz5zma4cbwqgjnjsi8szb
Module:Zh/data/och-pron-ZS/蒕
828
3540531
9909225
2026-09-29T11:37:12Z
TongcyBot
83009
同步英語維基詞典
9909225
Scribunto
text/plain
return {
{ "12984", "昷", "文", "2", "煴", "qun", "" },
}
7zphynancbf3ops120igxkmt7e8ukqa
Module:Zh/data/och-pron-ZS/薀
828
3540532
9909226
2026-09-29T11:37:14Z
TongcyBot
83009
同步英語維基詞典
9909226
Scribunto
text/plain
return {
{ "12972", "昷", "文", "2", "𥁕", "quːn", "見左傳" },
{ "12993", "昷", "文", "2", "惲", "qunʔ", "" },
{ "12999", "昷", "文", "2", "醖", "quns", "" },
}
ls263suek03stfadt1pf81fibp92yf9
Module:Zh/data/och-pron-ZS/褒
828
3540533
9909227
2026-09-29T11:37:15Z
TongcyBot
83009
同步英語維基詞典
9909227
Scribunto
text/plain
return {
{ "316", "保", "幽", "1", "襃", "puː", "" },
{ "317", "保", "幽", "1", "襃", "puː", "" },
}
hoiwaams9msdy815owatxa68k9h51bx
Module:Zh/data/och-pron-ZS/豜
828
3540534
9909228
2026-09-29T11:37:17Z
TongcyBot
83009
同步英語維基詞典
9909228
Scribunto
text/plain
return {
{ "6163", "幵", "元", "2", "堅", "kŋeːn", "" },
{ "6177", "幵", "元", "2", "硯", "ŋeːns", "" },
}
plt7s7u9p8vbmncsjrfhilh715kb6ux
Module:Zh/data/och-pron-ZS/鑒
828
3540535
9909229
2026-09-29T11:37:19Z
TongcyBot
83009
同步英語維基詞典
9909229
Scribunto
text/plain
return {
{ "6105", "監", "談", "1", "", "kraːm", "監轉註字" },
{ "6107", "監", "談", "1", "鑑", "kraːms", "同鑑見詩" },
{ "6108", "監", "談", "1", "鑑", "kraːms", "" },
}
4wirhcjx4td1y7i57dw38ko52ooifci
Module:Zh/data/och-pron-ZS/鮓
828
3540536
9909230
2026-09-29T11:37:21Z
TongcyBot
83009
同步英語維基詞典
9909230
Scribunto
text/plain
return {
{ "16642", "乍", "魚", "0", "鮓", "ʔsraːʔ", "見釋名" },
}
5g7ee7o055j58bhlhh8fq72ok4p648k
Module:Zh/data/och-pron-ZS/鶒
828
3540537
9909231
2026-09-29T11:37:22Z
TongcyBot
83009
同步英語維基詞典
9909231
Scribunto
text/plain
return {
{ "1487", "敕", "職", "0", "敕", "tʰɯɡ", "" },
}
ic81jrzl4voeef31ukzkb0tozxh8mah
Module:Zh/data/och-pron-ZS/鷉
828
3540538
9909232
2026-09-29T11:37:24Z
TongcyBot
83009
同步英語維基詞典
9909232
Scribunto
text/plain
return {
{ "11968", "虒", "支", "0", "梯", "l̥ʰeː", "" },
}
pw64eoz9dlfutrc79mln0bgu3mne0n8
Module:Zh/data/och-pron-ZS/鸊
828
3540539
9909233
2026-09-29T11:37:26Z
TongcyBot
83009
同步英語維基詞典
9909233
Scribunto
text/plain
return {
{ "699", "辟", "錫", "0", "甓", "beːɡ", "" },
}
1ydvakkfmsuwwlh2kegokkaggivygph
Module:Zh/data/och-pron-ZS/𩨭
828
3540540
9909234
2026-09-29T11:37:28Z
TongcyBot
83009
同步英語維基詞典
9909234
Scribunto
text/plain
return {
{ "16315", "𦚢", "歌", "1", "𦚢", "qhʷal", "" },
}
er8uf16nd3vtgbdnqpn46a5k9rs6ce8
Module:Zh/data/och-pron-BS/唾
828
3540541
9909241
2026-09-29T11:38:12Z
TongcyBot
83009
同步英語維基詞典
9909241
Scribunto
text/plain
return {
{ "tuò", "thwaH", "*tʰˤoj-s", "to spit (Sagart & Ma, 2020)" },
}
794j05te445ouckqlji4ny537as5go8
Module:Zh/data/och-pron-BS/坅
828
3540542
9909242
2026-09-29T11:38:14Z
TongcyBot
83009
同步英語維基詞典
9909242
Scribunto
text/plain
-- Appears in "A Hypothesis on the Origin of Old Chinese Pharyngealization"
return {
{"qǐn", "khjimX", "*[k]ʰ(r)[ə]mʔ", "pit"}
}
8xqn84oowz4rs3l1f8y27ncm9kyx7tt
Module:Zh/data/och-pron-BS/戇
828
3540543
9909243
2026-09-29T11:38:16Z
TongcyBot
83009
同步英語維基詞典
9909243
Scribunto
text/plain
return {
{ "zhuàng", "xuwngH", "*qʰˤoŋ-s", "stupid (Beaudouin, Sagart & Baxter, 2021)" },
}
3fbclvdgey80kp3wri6k548hti420ew
Module:Zh/data/och-pron-BS/樺
828
3540544
9909244
2026-09-29T11:38:17Z
TongcyBot
83009
同步英語維基詞典
9909244
Scribunto
text/plain
return {
{"huà", "hwae, hwaeH", "*N-qʷʰˤra(-s)", "birch"}
}
26smily2xcql0hnnrhzsxv0pv8hdgq9
Module:Zh/data/och-pron-BS/涎
828
3540545
9909245
2026-09-29T11:38:19Z
TongcyBot
83009
同步英語維基詞典
9909245
Scribunto
text/plain
return {
{ "xián", "zjen", "*s-N-qa[r]", "saliva (from envy?)" },
{ "xián", "zjen", "*s-N-qa[r]", "spittle" },
}
deeqbits4de7vaeb8gd3gpupaqq8ine
Module:Zh/data/och-pron-BS/秝
828
3540546
9909246
2026-09-29T11:38:21Z
TongcyBot
83009
同步英語維基詞典
9909246
Scribunto
text/plain
return {
{ "lì", "lek", "*[r]ˤek", "" },
}
-- From https://stan.hypotheses.org/413
ta0e36b7oi1ftt3cwd518p2c4r575bx
Module:Zh/data/och-pron-BS/絯
828
3540547
9909247
2026-09-29T11:38:23Z
TongcyBot
83009
同步英語維基詞典
9909247
Scribunto
text/plain
return {
{ "hài", "heajX", "*[ɡ]ˤrəʔ", "to bind (Sagart, 2017)" },
}
fqwwpshgkid213tmk92xeinmy7ptjzd
Module:Zh/data/och-pron-BS/腓
828
3540548
9909248
2026-09-29T11:38:24Z
TongcyBot
83009
同步英語維基詞典
9909248
Scribunto
text/plain
-- Appears in "A Hypothesis on the Origin of Old Chinese Pharyngealization"
return {
{"féi", "bj+j", "*[b]əj", "calf"}
}
4ns6p7kgtdwiucej4g96lxgqrxzhuc4
Module:Zh/data/och-pron-BS/蘊
828
3540549
9909249
2026-09-29T11:38:26Z
TongcyBot
83009
同步英語維基詞典
9909249
Scribunto
text/plain
return {
{ "yǔn", "ʔjunX", "*ʔunʔ", "accumulate, block up" },
{ "yùn", "ʔjunH", "*ʔunʔ-s", "accumulate, block up" },
}
8tkpa4cb2k9na4016h5uvikm1247c5v
Module:Zh/data/och-pron-BS/蝠
828
3540550
9909250
2026-09-29T11:38:28Z
TongcyBot
83009
同步英語維基詞典
9909250
Scribunto
text/plain
return {
{ "fú", "pjuwk", "*[p]ək", "bat (Sagart, 2017)" },
}
7cj2lheqbsagbp38r8yzlj9gt6gt35e
Module:Zh/data/och-pron-BS/踴
828
3540551
9909251
2026-09-29T11:38:29Z
TongcyBot
83009
同步英語維基詞典
9909251
Scribunto
text/plain
return {
{ "yǒng", "yowngX", "*loŋʔ", "leap" },
}
0i2zhoodovuoxiw32fvfezzkpar6o7s