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> * 尖角括號 "&lt;>" 表示中綴;<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], "&nbsp; ") .. '</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 (&#39; → '), -- as they contain #, which causes the numeric character reference to be -- misparsed (wa'a → wa&#39;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 = "&#x5D;" .. link.display .. "&#x5B;" 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, "&nbsp;" .. 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 &lt; -- and &gt; 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), ["<"] = "&lt;", [">"] = "&gt;", } 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> &rarr; <code><nowiki>displayed text</nowiki></code> * <code><nowiki>[[page and displayed text]]</nowiki></code> &rarr; <code><nowiki>page and displayed text</nowiki></code> * <code><nowiki>[[Category:English lemmas|WORD]]</nowiki></code> &rarr; ''(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 .. " §&nbsp;" .. 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 (&#39; → '), -- as they contain #, which causes the numeric character reference to be -- misparsed (wa'a → wa&#39;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 = "&#x5D;" .. link.display .. "&#x5B;" 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, "&nbsp;" .. 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 &lt; -- and &gt; 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), ["<"] = "&lt;", [">"] = "&gt;", } 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> &rarr; <code><nowiki>displayed text</nowiki></code> * <code><nowiki>[[page and displayed text]]</nowiki></code> &rarr; <code><nowiki>page and displayed text</nowiki></code> * <code><nowiki>[[Category:English lemmas|WORD]]</nowiki></code> &rarr; ''(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 .. " §&nbsp;" .. 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 .. "&nbsp;" .. 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>&nbsp; ' .. '</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 = "&nbsp;" 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 .. " &mdash; " .. 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>:&#32;" 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;>&#32;' .. 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>:&#32;" 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;>&#32;' .. 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, "&nbsp;") 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, "&nbsp;") 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("&nbsp;" .. 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 .. "&zwnj;: \"" .. 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, ",&lrm; ")) 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;">&nbsp;</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, "", "") .. ":&#32;" 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, "", "") .. ":&#32;" 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(" &mdash; " .. 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(" &mdash; " .. 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|&nbsp;的{{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;">&nbsp;</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 "&mdash;" 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, "&lrm;") 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, "&lrm;") 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 = "&mdash;" 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 `&amp;`. ]==] 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("&#91;") 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("&#93;") 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;">&nbsp;</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 = " &mdash; " .. 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>&nbsp;", "") :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 = " &mdash; " .. 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>&nbsp;", "") :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>&nbsp; &nbsp; {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 "&mdash;" elseif type(subforms) ~= "table" then error("a non-table value was given in the list of inflected forms.") elseif #subforms == 0 then return "&mdash;" 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", "&#45;") 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", "&#45;") 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", "&#45;") 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", "&#45;") 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;">&nbsp;</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;">&nbsp;</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 .. "&nbsp;" .. 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 .. "&nbsp;" .. 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;">&nbsp;</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 " +&lrm; " 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 " +&lrm; " 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 "&mdash;" 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("&#160;") 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