Wiktionary zhwiktionary https://zh.wiktionary.org/wiki/Wiktionary:%E9%A6%96%E9%A1%B5 MediaWiki 1.47.0-wmf.23 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 9943646 9942804 2026-10-13T03:43:33Z TongcyBot 83009 機器人:更新每日統計數據(2026-10-13) 9943646 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-10-01 | fr | mg | zh | th | el | 7019522 | 6071464 | 2392934 | 2207248 | 1677499 | 7019915 | 6074591 | 2393234 | 2207248 | 1677514 | 7020418 | 6075819 | 2393498 | 2207269 | 1677536 | 7020745 | 6076647 | 2393734 | 2207269 | 1678556 | 7021263 | 6078363 | 2394334 | 2207269 | 1694705 | 7021761 | 6079669 | 2394797 | 2207270 | 1694715 | 7022135 | 6081374 | 2395131 | 2207285 | 1694735 | 7022421 | 6082822 | 2395395 | 2207286 | 1694742 | 7022749 | 6084244 | 2395809 | 2207307 | 1694754 | 7023029 | 6085469 | 2396125 | 2207307 | 1694767 | 7023176 | 6085681 | 2396558 | 2207307 | 1694773 | 7023317 | 6085877 | 2397255 | 2207308 | 1699333 | 7023900 | 6086291 | 2397602 | 2207309 | 1699342 | 7024328 | 6086724 | 2398044 | 2207321 | 1702334 }} * 来源:[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:維基詞典統計]] 1bg91rhuakkfm2vj5nlxbq087z8cdbl 一隅 0 116795 9943158 388467 2026-10-13T00:01:23Z Fglffer 55252 9943158 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=一yú |cat=n,a }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|literal|figurative}} [[角落]];[[區域]] ===形容詞=== {{head|zh|形容詞}} # 偏於某一[[方面]]或某一[[部分]] ==日語== {{ja-kanjitab|いち|ぐう|y=goon,kanyoon}} ===發音=== {{ja-pron|いちぐう|acc=0|acc_ref=SMK5,NHK,DJR}} ===名詞=== {{ja-noun|いちぐう}} # [[角落]] #* {{RQ:ja:Aozora Bunko|001569/files/53205_49828}} #*: {{ja-usex|家 の 中 は 真暗 で ある が、真中 に 長い 廊下 が あって、その '''一隅''' に 燈火 管制 を した 電燈 が ついている。|いえ の なか は まっくら で ある が、まんなか に ながい ろうか が あって、その '''いちぐう''' に とうか かんせい を した でんとう が ついている。|房間裡一片漆黑,但正中央有一條長長走廊,走廊的'''轉角處'''有一盞燈因停電而熄滅了。}} ===來源=== {{reflist}} ==朝鮮語== {{ko-hanjatab}} ===名詞=== {{ko-noun|hangeul=일우}} # {{hanja form of|일우}} gz59qkuzpzenoylnqiy27479lg0zcsa europäischen 0 299243 9943027 6055415 2026-10-12T14:01:56Z Jeuwre 48455 發音 9943027 wikitext text/x-wiki ==德语== ===發音=== * {{IPA|de|[ɔɪ̯ʁoˈpɛːɪʃn̩]}} * {{audio|de|De-europäischen2.ogg|a=柏林}} * {{hyph|de|eu|ro|pä|i|schen}} ===形容词=== #[[europäisch]]的变格形式 [[Category:德语形容词变格形]] 2190ewu4xixziifquoh3npr65a9d95k 懽 0 307695 9943152 9659666 2026-10-12T23:51:23Z Fglffer 55252 9943152 wikitext text/x-wiki {{character info}} ==跨語言== ===漢字=== {{Han char|rn=61|rad=心|as=18|sn=21|four=94014|canj=PTRG|ids=⿰忄雚}} ====來源==== {{Han ref|kx=0409.010|dkj=11497|dj=0751.060|hdz=42370.100|ch=5490006.01|uh=61FD}} ==漢語== {{zh-see|歡|v}} ==日語== ===漢字=== {{ja-kanji|grade=|rs=心18}} # {{rfdef|ja|sort=心18}} ====讀法==== {{ja-readings|goon=かん<くわん,がん<ぐわん|kanon=かん<くわん|kun=よろこ-ぶ}} ==朝鮮語== ===漢字=== {{ko-hanja|환}} # {{rfdef|ko|sort=心18}} ==越南語== ===漢字=== {{vi-readings|reading=hoàn, hoan|rs=心18}} # {{rfdef|vi|sort=心18}} phgz26enjumqi98efvl4iu3j8yij56a courrier électronique 0 330929 9943028 8783030 2026-10-12T15:17:46Z TongcyDai 53191 9943028 wikitext text/x-wiki ==法語== {{wp|fr:}} ===發音=== * {{fr-IPA}} * {{audio|fr|LL-Q150 (fra)-Pamputt-courrier électronique.wav|a=奧爾日河畔布雷蒂尼}} ===名詞=== {{fr-noun|m}} # {{lb|fr|網際網路}} [[電子郵件]] #: {{syn|fr|courriel|email}} ====派生詞彙==== * {{l|fr|courriel}} * {{l|fr|adresse de courrier électronique}} {{C|fr|電子郵件}} mhiqnihfxcum5lu1m9wg246bxi8k3wu 가변 0 347247 9943168 6732581 2026-10-13T01:22:21Z 列维劳德 79702 /* 朝鮮語 */ 9943168 wikitext text/x-wiki ==朝鮮語== ===詞源=== {{ko-etym-sino|可變}}。 ===發音=== {{ko-IPA|l=y}} ===名詞=== {{ko-noun|hanja=可變}} # {{lb|ko|attributive}} [[可變]] #: {{ant|ko|불변(不變)}} ====派生詞==== * {{l|ko|가변적(可變的)}} 1hj0cxkji89wipk7nebwjsxuzymapeq 9943644 9943168 2026-10-13T02:15:42Z Sayonzei 40728 /* 朝鮮語 */ 9943644 wikitext text/x-wiki ==朝鮮語== ===詞源1=== {{ko-etym-sino|可變}}。 ====發音==== {{ko-IPA|l=y}} ====名詞==== {{ko-noun|hanja=可變}} # {{lb|ko|attributive}} [[可變]] #: {{ant|ko|불변(不變)}} =====派生詞===== * {{l|ko|가변적(可變的)}} ===詞源2=== {{ko-etym-sino|家變}}。 ====發音==== {{ko-IPA}} ====名詞==== {{ko-noun|hanja=家變}} # [[家庭]][[變故]] 2cf3i2aj4h666226x8og48x4edgo13i 감미료 0 350353 9943066 6734741 2026-10-12T17:39:52Z Sayonzei 40728 9943066 wikitext text/x-wiki ==朝鮮語== ===詞源=== {{ko-etym-sino|甘味|甜味|料|材料,物質}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun|hanja=甘味料}} # [[甜味劑]] ====參見==== * {{ko-l|감미|甘味|甜味}} iz4b6ltngoos2i3dzivb53hl4md6j1l 대리석 0 387642 9943068 5240168 2026-10-12T17:43:18Z Sayonzei 40728 9943068 wikitext text/x-wiki ==朝鮮語== ===詞源=== {{ko-etym-sino|大理石}}。 ===發音=== {{ko-IPA|l=y}} ===名詞=== {{ko-noun|hanja=大理石}} # [[大理石]] {{C|ko|岩石}} 7ubt8krth9a6bsfe7mxwzr8gyymyo71 문법 0 408627 9943061 7028384 2026-10-12T17:28:22Z Sayonzei 40728 使用[[WP:HOTCAT|HotCat]]已移除[[Category:朝鮮語 文法]]; 已添加[[Category:朝鮮語 語法]] 9943061 wikitext text/x-wiki ==朝鮮語== ===發音=== {{ko-IPA|com=1}} ===詞源1=== {{ko-etym-sino|文|書寫|法|法則|genitive=y}}。 ====名詞==== {{ko-noun|hanja=文法}} # [[文法]] ===詞源2=== ====名詞==== {{ko-noun|hanja=聞法}} # [[聽聞]][[佛法]] [[Category:朝鮮語 語法]] kmnxjq8tg7pjjjpfym4wahw33ibl3kh 이제 0 493675 9943167 8347255 2026-10-13T01:19:42Z 列维劳德 79702 /* 朝鮮語 */ 9943167 wikitext text/x-wiki ==朝鮮語== ===詞源=== {{compound|ko|이|t1=這|제|t2=時候}}。 ===發音=== {{ko-IPA|a=y}} ===名詞=== {{ko-noun}} # [[現在]],[[如今]],[[此刻]],[[此時]] #: {{uxi|ko|'''이제''' 점심 시간이야.|'''現在'''是午餐時間。}} ===參見=== * {{l|ko|지금}} {{C|ko|現在}} f8jyzu4njtnc3k5opqjqytvkadu22ty 9943645 9943167 2026-10-13T02:17:56Z Sayonzei 40728 /* 朝鮮語 */ 9943645 wikitext text/x-wiki ==朝鮮語== ===詞源1=== {{compound|ko|이|t1=這|제|t2=時候}}。 ====發音==== {{ko-IPA|a=y}} ====名詞==== {{ko-noun}} # [[現在]],[[如今]],[[此刻]],[[此時]] #: {{uxi|ko|'''이제''' 점심 시간이야.|'''現在'''是午餐時間。}} ====參見==== * {{l|ko|지금}} ===詞源2=== {{ko-etym-sino|二諦}}。 ====發音==== {{Ko-IPA|l=y}} ====名詞==== {{ko-noun|hanja=二諦}} # {{lb|ko|佛教}} {{l|ko|진제(眞諦)}}和{{l|ko|속제(俗諦)}}的合稱 ===詞源3=== {{ko-etym-sino|裏題}}。 ====發音==== {{Ko-IPA|l=y}} ====名詞==== {{ko-noun|hanja=裏題}} # 写在第一页或内封上的[[书名]] {{C|ko|現在}} bo4cpkhudie42un6k1b8is99wqudpl8 穿堂 0 590689 9943669 8553615 2026-10-13T08:47:23Z Fglffer 55252 9943669 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=chuāntáng,er=y |c=cyun1 tong4 |w=sh:1tshoe daon |cat=n }} ===名詞=== {{head|zh|名詞}} # 連接[[前院]]和[[後院]]的[[走廊]]、[[通道]]{{gl|古老中式庭園常見部件}} ====衍生詞==== {{col3|zh|穿堂門}} {{C|zh|建築學}} sswdmwquygvahno99ohhucfdwesd3da 璀璨 0 595604 9943670 9051688 2026-10-13T08:51:29Z Fglffer 55252 /* 形容詞 */ 9943670 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=cuǐcàn |c=ceoi1 caan3 |mc=y,y |cat=a }} ===形容詞=== {{head|zh|形容詞}} # {{lb|zh|字面|figuratively}} [[光輝]][[燦爛]];[[光亮]];[[光明]];[[明亮]];[[明朗]] #: {{zh-x|璀璨 人生}} # [[衣服]][[摩擦]]聲 ====同義詞==== * {{qualifier|書面}} {{zh-l|炳}} ====反義詞==== * {{zh-l|晦暗}} ====衍生詞==== {{col3|zh|璀璨奪目}} ==朝鮮語== {{ko-hanjatab}} ===名詞=== {{ko-noun|hangeul=최찬}} # {{hanja form of|최찬|}} b3swofbas69nsc053pvttdaquji5imf 敞車 0 596517 9943148 8159004 2026-10-12T23:49:14Z Fglffer 55252 9943148 wikitext text/x-wiki ==漢語== {{zh-forms|s=敞车}} {{wp}} ===發音=== {{zh-pron |m=chǎngchē |c=cong2 ce1 |cat=n }} ===名詞=== {{head|zh|名詞}} # [[敞篷車]] # [[鐵路]][[貨車]]的車種之一,無蓋,有四邊[[側板]],其中兩邊側板有可[[翻開]]之[[側門]] {{C|zh|交通工具}} 01mnnf24gz0czpyqjsgu6moydz2cxqc 敞篷车 0 596533 9943146 8159063 2026-10-12T23:46:07Z Fglffer 55252 Fglffer移动页面[[敞蓬車]]至[[敞篷车]],不留重定向 6789027 wikitext text/x-wiki ==漢語== ===寫法=== {{漢語寫法|简=[[敞]][[蓬]][[车]]|正=[[]]}} ===讀音=== {{汉语读音|國=|漢語拼音=chǎngpéngchē|粵=|滬=|白話字=|臺羅拼音=}} ===釋義=== # ===翻譯=== {{翻译-顶}} *俄语:[[открытая]] [[машина]]; [[экипаж]] [[с]] [[открытым]] [[верхом]] {{翻译-底}} [[Category:待分類的漢語詞]] [[Category:漢語|攴08艸11车00]] 714zl6z65g4go8m29e799b23cueh8np 9943147 9943146 2026-10-12T23:46:18Z Fglffer 55252 重定向页面至[[敞篷車]] 9943147 wikitext text/x-wiki #REDIRECT [[敞篷車]] mv2let6amdamfu5kqw0x4ieakh3gpdt 奪朱 0 613181 9943648 9232232 2026-10-13T07:41:32Z Fglffer 55252 9943648 wikitext text/x-wiki ==漢語== {{zh-forms|s=夺朱}} ===發音=== {{zh-pron |m=duózhū |cat=v }} ===動詞=== {{zh-verb}} # {{lb|zh|figurative}} 以[[邪道]]取代[[正道]] #* {{zh-x|惡 紫 之 奪朱 也,惡 ^鄭 聲 之 亂 雅樂 也。||CL|ref=Lunyu}} ====參見==== * {{zh-l|惡紫奪朱}} * {{zh-l|紅紫亂朱}} qshhppl9vi02w5evpjgzgwmzlkqb7x6 9943649 9943648 2026-10-13T07:47:37Z Fglffer 55252 9943649 wikitext text/x-wiki ==漢語== {{zh-forms|s=夺朱}} ===發音=== {{zh-pron |m=duózhū |cat=v }} ===動詞=== {{zh-verb}} # {{lb|zh|figurative}} 以[[邪道]]取代[[正道]] #* {{zh-x|惡 紫 之 奪朱 也,惡 ^鄭 聲 之 亂 雅樂 也。||CL|ref=Lunyu}} ====同義詞==== * {{zh-l|惡紫奪朱}} * {{zh-l|紅紫亂朱}} ktixdrd5961yd53bl5peuacj7rys4l5 9943661 9943649 2026-10-13T08:16:47Z Fglffer 55252 9943661 wikitext text/x-wiki ==漢語== {{zh-forms|s=夺朱}} ===詞源=== 出自《[[s:論語/陽貨第十七|論語·陽貨]]》: {{zh-x|惡 '''紫''' 之 奪'''朱''' 也,惡 鄭聲 之 亂 雅樂 也,惡 利口 之 覆 邦家 者。||CL|collapsed=y}} ===發音=== {{zh-pron |m=duózhū |cat=v }} ===動詞=== {{zh-verb}} # {{lb|zh|figurative}} 以[[邪道]]取代[[正道]] ====同義詞==== * {{zh-l|惡紫奪朱}} * {{zh-l|紅紫亂朱}} {{cln|zh|來自《論語》的詞}} n6xnphqdg6gbp7xmvb14753ufyl2fd4 9943662 9943661 2026-10-13T08:17:23Z Fglffer 55252 9943662 wikitext text/x-wiki ==漢語== {{zh-forms|s=夺朱}} ===詞源=== 出自《[[s:論語/陽貨第十七|論語·陽貨]]》: {{zh-x|惡 紫 之 奪朱 也,惡 鄭聲 之 亂 雅樂 也,惡 利口 之 覆 邦家 者。||CL|collapsed=y}} ===發音=== {{zh-pron |m=duózhū |cat=v }} ===動詞=== {{zh-verb}} # {{lb|zh|figurative}} 以[[邪道]]取代[[正道]] ====同義詞==== * {{zh-l|惡紫奪朱}} * {{zh-l|紅紫亂朱}} {{cln|zh|來自《論語》的詞}} jxt2llv8o98t6bunksd2nios2ic7jrl 9943664 9943662 2026-10-13T08:19:31Z Fglffer 55252 9943664 wikitext text/x-wiki ==漢語== {{zh-forms|s=夺朱}} ===詞源=== 參見{{zh-l|紫朱}}。 ===發音=== {{zh-pron |m=duózhū |cat=v }} ===動詞=== {{zh-verb}} # {{lb|zh|figurative}} 以[[邪道]]取代[[正道]] ====同義詞==== * {{zh-l|惡紫奪朱}} * {{zh-l|紅紫亂朱}} {{cln|zh|來自《論語》的詞}} 3h83enjspw315x5zxyg1qc3y705qezs 滾瓜爛熟 0 615724 9943129 8170419 2026-10-12T23:25:37Z Fglffer 55252 9943129 wikitext text/x-wiki ==漢語== {{zh-forms|s=滚瓜烂熟|lit=掉落地上的瓜,熟透了}} ===發音=== {{zh-pron |m=gǔnguālàn熟 |c=gwan2 gwaa1 laan6 suk6 |cat=cy }} ===成語=== {{head|zh|成語}} # 形容[[讀書]]、[[背誦]]或[[講述]]非常[[純熟]]、[[流利]] 6ucs93kye0ht0nlc5ypewus9a44r955 紅紫 0 628933 9943654 8181344 2026-10-13T07:56:20Z Fglffer 55252 9943654 wikitext text/x-wiki ==漢語== {{zh-forms|s=红紫}} ===發音=== {{zh-pron |m=hóngzǐ |cat=n }} ===名詞=== {{head|zh|noun}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ==日語== ===詞源1=== {{ja-kanjitab|こう|し|y=o}} ====發音==== {{ja-pron|こうし|acc=1|acc_ref=DJR,SMK8}} ====名詞==== {{ja-noun|こうし}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ===詞源2=== {{ja-kanjitab|べに|むらさき|y=k}} {{ja-com|紅|べに|t1=crimson|紫|むらさき|t2=purple|sort=へにむらさき'}} ====發音==== {{ja-pron|べにむらさき}} ====名詞==== {{ja-noun|べにむらさき}} # 帶點紅的[[紫色]] #: {{color panel|べにむらさき|B44C97}} ===來源=== {{reflist}} {{C|ja|顏色|sort=こうし}} {{C|ja|紫色|sort=へにむらさき'}} hsbjopbn0z6j2j97z7uc5a60sfk524s 9943655 9943654 2026-10-13T07:56:50Z Fglffer 55252 /* 日語 */ 9943655 wikitext text/x-wiki ==漢語== {{zh-forms|s=红紫}} ===發音=== {{zh-pron |m=hóngzǐ |cat=n }} ===名詞=== {{head|zh|noun}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ==日語== ===詞源1=== {{ja-kanjitab|こう|し|y=o}} ====發音==== {{ja-pron|こうし|acc=1|acc_ref=DJR,SMK8}} ====名詞==== {{ja-noun|こうし}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ===詞源2=== {{ja-kanjitab|べに|むらさき|y=k}} {{ja-com|紅|べに|t1=赤紅|紫|むらさき|sort=へにむらさき'}} ====發音==== {{ja-pron|べにむらさき}} ====名詞==== {{ja-noun|べにむらさき}} # 帶點紅的[[紫色]] #: {{color panel|べにむらさき|B44C97}} ===來源=== {{reflist}} {{C|ja|顏色|sort=こうし}} {{C|ja|紫色|sort=へにむらさき'}} mbd5pqi11xd4eklzt8cd98y4aq4tetg 9943656 9943655 2026-10-13T07:57:25Z Fglffer 55252 /* 日語 */ 9943656 wikitext text/x-wiki ==漢語== {{zh-forms|s=红紫}} ===發音=== {{zh-pron |m=hóngzǐ |cat=n }} ===名詞=== {{head|zh|noun}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ==日語== ===詞源1=== {{ja-kanjitab|こう|し|y=o}} ====發音==== {{ja-pron|こうし|acc=1|acc_ref=DJR,SMK8}} ====名詞==== {{ja-noun|こうし}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ===詞源2=== {{ja-kanjitab|べに|むらさき|y=k}} {{ja-com|紅|べに|t1=赤紅|紫|むらさき|sort=へにむらさき'}}。 ====發音==== {{ja-pron|べにむらさき}} ====名詞==== {{ja-noun|べにむらさき}} # 帶點紅的[[紫色]] #: {{color panel|べにむらさき|B44C97}} ===來源=== {{reflist}} {{C|ja|顏色|sort=こうし}} {{C|ja|紫色|sort=へにむらさき'}} s6bvr6ao5z7pvlt0d7326je78qo3hql 9943657 9943656 2026-10-13T07:59:19Z Fglffer 55252 9943657 wikitext text/x-wiki ==漢語== {{zh-forms|s=红紫}} ===發音=== {{zh-pron |m=hóngzǐ |cat=n }} ===名詞=== {{head|zh|noun}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ====衍生詞==== {{col3|zh|紅紫芬芳}} ==日語== ===詞源1=== {{ja-kanjitab|こう|し|y=o}} ====發音==== {{ja-pron|こうし|acc=1|acc_ref=DJR,SMK8}} ====名詞==== {{ja-noun|こうし}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ===詞源2=== {{ja-kanjitab|べに|むらさき|y=k}} {{ja-com|紅|べに|t1=赤紅|紫|むらさき|sort=へにむらさき'}}。 ====發音==== {{ja-pron|べにむらさき}} ====名詞==== {{ja-noun|べにむらさき}} # 帶點紅的[[紫色]] #: {{color panel|べにむらさき|B44C97}} ===來源=== {{reflist}} {{C|ja|顏色|sort=こうし}} {{C|ja|紫色|sort=へにむらさき'}} r00cg8kpzajkb01t6uyr46f5wchgk7y 9943659 9943657 2026-10-13T08:07:56Z Fglffer 55252 /* 漢語 */ 9943659 wikitext text/x-wiki ==漢語== {{zh-forms|s=红紫}} ===發音=== {{zh-pron |m=hóngzǐ |cat=n }} ===名詞=== {{head|zh|名詞}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ====衍生詞==== {{col3|zh|紅紫芬芳}} ==日語== ===詞源1=== {{ja-kanjitab|こう|し|y=o}} ====發音==== {{ja-pron|こうし|acc=1|acc_ref=DJR,SMK8}} ====名詞==== {{ja-noun|こうし}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ===詞源2=== {{ja-kanjitab|べに|むらさき|y=k}} {{ja-com|紅|べに|t1=赤紅|紫|むらさき|sort=へにむらさき'}}。 ====發音==== {{ja-pron|べにむらさき}} ====名詞==== {{ja-noun|べにむらさき}} # 帶點紅的[[紫色]] #: {{color panel|べにむらさき|B44C97}} ===來源=== {{reflist}} {{C|ja|顏色|sort=こうし}} {{C|ja|紫色|sort=へにむらさき'}} 3b93lifl4b85eegw8rvv8ynem8uwwf8 9943677 9943659 2026-10-13T11:39:30Z Sayonzei 40728 /* 日語 */ 9943677 wikitext text/x-wiki ==漢語== {{zh-forms|s=红紫}} ===發音=== {{zh-pron |m=hóngzǐ |cat=n }} ===名詞=== {{head|zh|名詞}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ====衍生詞==== {{col3|zh|紅紫芬芳}} ==日語== ===詞源1=== {{ja-kanjitab|こう|し|y=o}} ====發音==== {{ja-pron|こうし|acc=1|acc_ref=DJR,SMK8}} ====名詞==== {{ja-noun|こうし}} # [[紅色]]和[[紫色]] # [[五顏六色]]的[[花朵]] ===詞源2=== {{ja-kanjitab|べに|むらさき|y=k}} {{ja-com|紅|べに|t1=赤紅|紫|むらさき|sort=へにむらさき'}}。 ====發音==== {{ja-pron|べにむらさき}} ====名詞==== {{ja-noun|べにむらさき}} # 帶點紅的[[紫色]] #: {{color panel|べにむらさき|B44C97}} ===來源=== {{reflist}} {{C|ja|顏色|sort=こうし}} {{C|ja|紫色|sort=へにむらさき'}} ==朝鮮語== {{ko-hanjatab}} ===名詞=== {{ko-noun|hangeul=홍자}} # {{hanja form of|홍자}} cj5e1cm0ub3j0xzeu71cawuze8hc0im 紅紫芬芳 0 628934 9943658 8181404 2026-10-13T08:03:01Z Fglffer 55252 9943658 wikitext text/x-wiki ==漢語== {{zh-forms|s=红紫芬芳|type=22}} ===發音=== {{zh-pron |m=hóngzǐfēnfāng |cat=cy }} ===成語=== {{head|zh|成語}} # 泛指各種[[花卉]] c6atj94urg74hfqlb1ydtm2qsly44v4 遼東豕 0 663665 9943139 8198127 2026-10-12T23:37:44Z Fglffer 55252 9943139 wikitext text/x-wiki ==漢語== {{zh-forms|s=辽东豕|type=21}} ===發音=== {{zh-pron |m=liáodōngshǐ |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|自謙}} {{syn of|zh|遼東白豕}} s9dkebhcup98a6novzbg3bz3nm4fled 遼豕 0 663677 9943140 8198229 2026-10-12T23:38:35Z Fglffer 55252 9943140 wikitext text/x-wiki ==漢語== {{zh-forms|s=辽豕}} ===發音=== {{zh-pron |m=liáoshǐ |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|自謙}} {{syn of|zh|遼東白豕}} h1yfppxlojswk0bkbatxftqyjur5rg9 いちぐう 0 706378 9943159 5469079 2026-10-13T00:01:46Z Fglffer 55252 9943159 wikitext text/x-wiki ==日語== {{ja-see|一隅}} 17205dk03wikswuqhmk36twgwmchhgp なぎ 0 737479 9943674 5427179 2026-10-13T09:04:14Z Fglffer 55252 9943674 wikitext text/x-wiki {{also|なき}} ==日語== {{ja-see|凪|梛|菜葱|薙ぎ}} m0z4448yn4xc9g1vm4ro84c36xgjzr0 ナショナリズム 0 737540 9943647 9920979 2026-10-13T03:58:52Z स्वर्ण 131372 修飾語句 9943647 wikitext text/x-wiki ==日語== {{swp|lang=ja}} ===詞源=== {{bor+|ja|en|nationalism|sort=なしょなりずむ}}。 ===發音=== {{ja-pron|acc=4|acc_ref=SMK7,DJR,NHK,NKD2}} ===名詞=== {{ja-noun}} # [[国家主义]],[[民族主义]],[[国粹主义]] #: {{syn|ja|国家主義|国粋主義|民族主義}} ===參考資料=== {{reflist}} {{C|ja|意識形態}} jbtxlcqyg4ccvx02aga38wjeknlhpfc подменить 0 838994 9943650 7646085 2026-10-13T07:50:38Z ~2026-54933-42 135708 /* 俄語 */ 9943650 wikitext text/x-wiki ==俄語== ===詞源=== {{af|ru|под-|-мени́ть}}。 ===發音=== * {{ru-IPA|подмени́ть}} ===動詞=== {{ru-verb|подмени́ть|pf|impf=подменя́ть|impf2=подме́нивать}} # [[替換]],[[頂替]] #: {{uxi|ru|[[сло́вно]] '''подмени́ли''' [[кто-либо|кого́-либо]]|替換成另一個人}} # {{lb|ru|colloquial}} [[插足]],[[介入]] ====屈折==== {{ru-conj|pf|4c(7)|подмени́ть}} ====相關詞==== * {{l|ru|подме́на}}、{{l|ru|подме́н}} * {{l|ru|меня́ть|g=impf}}、{{l|ru|поменя́ть|g=pf}} * {{l|ru|меново́й}} * {{l|ru|меня́ла}} peahtg0sy0a7s5k3ddg1xew1bkhlugu 9943651 9943650 2026-10-13T07:51:18Z ~2026-54933-42 135708 /* 動詞 */ 9943651 wikitext text/x-wiki ==俄語== ===詞源=== {{af|ru|под-|-мени́ть}}。 ===發音=== * {{ru-IPA|подмени́ть}} ===動詞=== {{ru-verb|подмени́ть|pf|impf=подменя́ть|impf2=подме́нивать}} # [[替換]],[[頂替]] #: {{uxi|ru|[[сло́вно]] '''подмени́ли''' [[кто-либо|кого́-либо]]|'''替換'''成另一個人}} # {{lb|ru|colloquial}} [[插足]],[[介入]] ====屈折==== {{ru-conj|pf|4c(7)|подмени́ть}} ====相關詞==== * {{l|ru|подме́на}}、{{l|ru|подме́н}} * {{l|ru|меня́ть|g=impf}}、{{l|ru|поменя́ть|g=pf}} * {{l|ru|меново́й}} * {{l|ru|меня́ла}} enzgtjfuocmr7gnt0uk518473cpnfmj подменять 0 838998 9943652 5104150 2026-10-13T07:51:25Z ~2026-54933-42 135708 9943652 wikitext text/x-wiki ==俄語== ===詞源=== {{af|ru|подмени́ть|-я́ть}}。 ===發音=== * {{ru-IPA|подменя́ть}} ===Verb=== {{ru-verb|подменя́ть|impf|pf=подмени́ть}} # [[替換]],[[頂替]] # {{lb|ru|colloquial}} [[插足]],[[介入]] ====屈折==== {{ru-conj|impf|1a|подменя́ть}} ====相關詞==== * {{l|ru|подме́на}}、{{l|ru|подме́н}} * {{l|ru|меня́ть|g=impf}}、{{l|ru|поменя́ть|g=pf}} * {{l|ru|меново́й}} * {{l|ru|меня́ла}} 7ld2sfbnhsjkuogdggq7keo90qw9dw1 9943653 9943652 2026-10-13T07:51:38Z ~2026-54933-42 135708 /* 俄語 */ 9943653 wikitext text/x-wiki ==俄語== ===詞源=== {{af|ru|подмени́ть|-я́ть}}。 ===發音=== * {{ru-IPA|подменя́ть}} ===動詞=== {{ru-verb|подменя́ть|impf|pf=подмени́ть}} # [[替換]],[[頂替]] # {{lb|ru|colloquial}} [[插足]],[[介入]] ====屈折==== {{ru-conj|impf|1a|подменя́ть}} ====相關詞==== * {{l|ru|подме́на}}、{{l|ru|подме́н}} * {{l|ru|меня́ть|g=impf}}、{{l|ru|поменя́ть|g=pf}} * {{l|ru|меново́й}} * {{l|ru|меня́ла}} e5chfslh4w1q7zbnf6cnx1oin3lbp1e назубок 0 856859 9943128 5092071 2026-10-12T23:22:51Z ~2026-54933-42 135708 9943128 wikitext text/x-wiki ==俄語== ===詞源=== {{univerbation|ru|[[на]] [[зубо́к]]}}。 ===發音=== * {{ru-IPA|назубо́к}} ===副詞=== {{ru-adv|назубо́к}} # {{lb|ru|informal}} [[瞭如指掌]],[[熟稔]] #: {{uxi|ru|знать назубо́к|瞭如指掌;非常了解}} ====參見==== * {{l|ru|наизу́сть}} 4rwyrwlm1n0ccabmwpm9o3ba069kjzq beatificar 0 888519 9943187 7726449 2026-10-13T01:36:51Z Sayonzei 40728 9943187 wikitext text/x-wiki ==加泰羅尼亞語== ===詞源=== {{bor+|ca|la-med|beātificō|beātificāre|t=賜福}}。 ===發音=== * {{ca-IPA}} ===動詞=== {{ca-verb}} # {{lb|ca|及物}} [[賜福]],使……[[蒙福]] # {{lb|ca|及物|天主教}} 為……行[[宣福禮]] ====變位==== {{ca-conj}} ====相關詞彙==== {{col3 |ca|beatífic |beatificació |beatíficament }} ===延伸閱讀=== * {{R:ca:IEC2}} * {{R:ca:GDLC}} * {{R:ca:DNV}} * {{R:ca:DCVB}} ==加利西亞語== ===詞源=== {{ety|gl|:lbor|la-med:beātificō<alt:beātificāre><t:賜福>|:af<surf>|beato|-ificar|text=+|tree=1}} ===動詞=== {{gl-verb}} # {{lb|gl|及物}} [[賜福]],使……[[蒙福]] # {{lb|gl|及物|羅馬天主教}} 為……行[[宣福禮]] ====變位==== {{gl-conj}} ====相關詞彙==== * {{l|gl|beatificación}} ===延伸閱讀=== * {{R:gl:DRAG}} ==葡萄牙語== ===詞源=== {{ety|pt|:lbor|la-med:beātificō<alt:beātificāre><t:賜福>|:af<surf>|beato|-ificar|text=+|tree=1}} ===發音=== {{pt-IPA}} * {{hyph|pt|be|a|ti|fi|car}} ===動詞=== {{pt-verb}} # {{lb|pt|及物}} [[賜福]],使……[[蒙福]] # {{lb|pt|及物|羅馬天主教}} 為……行[[宣福禮]] ====變位==== {{pt-conj}} ====相關詞彙==== * {{l|pt|beatificação}} ===延伸閱讀=== * {{R:pt:Aulete}} * {{R:pt:DAN}} * {{R:pt:Priberam}} ==西班牙語== ===詞源=== {{ety|es|:lbor|la-med:beātificō<alt:beātificāre><t:賜福>|:af<surf>|beato|-ificar|text=+|tree=1}} ===發音=== {{es-pr}} ===動詞=== {{es-verb-auto}} # {{lb|es|及物}} [[賜福]],使……[[蒙福]] # {{lb|es|及物|天主教}} 為……行[[宣福禮]] ====變位==== {{es-conj-auto}} ====相關詞彙==== {{col|es|beatificación}} ===延伸閱讀=== * {{R:es:DRAE}} mtfwwd1uyodwd9chgzfb95q7d91s97u florilegio 0 894907 9943151 7692072 2026-10-12T23:51:05Z Apisite 85347 + 參見模板 9943151 wikitext text/x-wiki {{also|florilégio}} ==西班牙语== [[Category:西班牙语]] [[Category:西班牙语名词]] m. 文选 iqu2kww12uyipozqvowz27w4efjvz2z 合不拢嘴 0 923115 9943132 6862711 2026-10-12T23:28:43Z Fglffer 55252 Fglffer移动页面[[和尚的木鱼]]至[[合不拢嘴]],不留重定向 6862711 wikitext text/x-wiki ==漢語== [[Category:歇后语]] 和尚的木鱼 —— 合不拢嘴;咧开了嘴;不打不响;挨敲打的货;挨敲的货 [[Category:待分類的漢語詞]] [[Category:漢語|口05小05白03木00鱼00]] 6t1lb1mn0mvfs4fr4d9pvvgagupkc8e 9943133 9943132 2026-10-12T23:28:59Z Fglffer 55252 重定向页面至[[合不攏嘴]] 9943133 wikitext text/x-wiki #REDIRECT [[合不攏嘴]] 85bro1twusdaxma78cqfbfkr8j9kgy5 合不攏嘴 0 943978 9943130 6865326 2026-10-12T23:27:08Z Fglffer 55252 Fglffer移动页面[[敲开了的木鱼]]至[[合不攏嘴]],不留重定向 6865326 wikitext text/x-wiki ==漢語== [[Category:歇后语]] 敲开了的木鱼 —— 合不拢嘴;咧开了嘴 [[Category:待分類的漢語詞]] [[Category:漢語|攴10廾01亅01白03木00鱼00]] 1vkfc7xfufefgxook70oyur8yplppim 9943131 9943130 2026-10-12T23:28:21Z Fglffer 55252 9943131 wikitext text/x-wiki ==漢語== {{zh-forms|s=合不拢嘴}} ===發音=== {{zh-pron |m=hé不lǒngzuǐ |cat=id }} ===俗語=== {{head|zh|俗語}} # 指人[[高興]]或[[驚訝]]得[[嘴巴]]合不起來 byaltzac735j4drus0tv03vgzgttk8p balbettare 0 960157 9943170 4876902 2026-10-13T01:23:35Z Sayonzei 40728 9943170 wikitext text/x-wiki ==意大利語== ===詞源=== {{inh+|it|la-lat|balbitāre}}<ref>{{R:it:Sapere}}</ref>(參見 {{m|la|balbūtīre}}),派生自 {{m|la|balbāre}},{{inh+|it|la|balbus|gloss=口吃}}。對照{{cog|fr|balbutier}}。 ===發音=== {{it-pr}} ===動詞=== {{it-verb|a/é}} # {{lb|it|ambitransitive}} [[口吃]],[[說話]][[模糊]][[不清]] {{+aux|it|avere}} # {{lb|it|不及物}} 嬰兒[[咿呀學語]] {{+aux|it|avere}} # {{lb|it|不及物|比喻|指各種學科}} [[出現]];[[萌芽]] {{+aux|it|avere}} #: {{ux|it|quando fu eletto JFK, la medicina '''balbettava''' ancora|肯尼迪当选总统时,医学领域尚不'''发达'''}} # {{lb|it|不及物|航海}} [[搖晃]] {{+aux|it|avere}} # {{lb|it|及物}} 會[[說]]一點點(外語) #: {{ux|it|'''balbetto''' un po' di italiano|我會說'''粗体文本'''一點點意大利語}} ====變位==== {{it-conj|a/é}} ====相關詞彙==== * {{l|it|balbettio}} * {{l|it|balbuziente}} ===參考資料=== {{reflist}} ===異序詞=== * {{anagrams|it|a=aabbeelrtt|abbatterle|balbetterà}} hchuh49izv2u2uxx22kpqgn6bb6rbvy baleniere 0 960206 9943192 4876974 2026-10-13T01:38:51Z Sayonzei 40728 9943192 wikitext text/x-wiki ==意大利語== ===詞源1=== {{ety|it|:af|balena|-iere}} 源自 {{suffix|it|balena|iere}}。 ====名詞==== {{it-noun|m}} # [[捕鲸]]者 ===詞源2=== ====名詞==== {{head|it|名詞變格形|g=f}} # {{plural of|it|baleniera}} ===異序詞=== * {{anagrams|it|a=abeeeilnr|balenerei|balenerie}} stfmhdnep7s7fs4rdowzh5g8230k9xi banchiere 0 960384 9943195 4877273 2026-10-13T01:40:07Z Sayonzei 40728 9943195 wikitext text/x-wiki ==意大利語== ===詞源=== {{ety|it|:af|banca|-iere}} 源自 {{suffix|it|banca|iere}}。 ===發音=== {{it-pr|banchière}} ===名詞=== {{it-noun|m}} # [[银行家]] # [[賭博]][[莊家]] ===異序詞=== * {{anagrams|it|a=abceehinr|bancherei}} 5zs1u44q8nxz7q23su5re10z4b5qngi beatificare 0 961113 9943177 4878897 2026-10-13T01:31:36Z Sayonzei 40728 9943177 wikitext text/x-wiki {{also|beatificaré}} ==意大利語== ===詞源=== 源自{{uder|it|la|beātificō|beātificāre|t=使……蒙福;祝福}}。 ===動詞=== {{it-verb|a/ì-}} # {{lb|it|及物}} 使……[[蒙福]] # {{lb|it|及物|天主教}} 為……行[[宣福禮]] ====變位==== {{it-conj|a/ì-}} ====相關詞彙==== * {{l|it|beatitudine}} * {{l|it|beato}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāre}} # {{inflection of|la|beātificō||pres|actv|inf|;|2|s|pres|pasv|imp//ind}} ==羅馬尼亞語== ===詞源=== {{ety|ro|:af|beatifica|-re|text=+|tree=1}} ===名詞=== {{ro-noun|f|beatificări}} # {{lb|ro|天主教}} [[宣福]];[[宣福禮]] ====變格==== {{ro-noun-f-e|beatificar|beatificăr}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 2a7hfxxshxyuocjr14xdeoplc190c00 bloccante 0 961626 9943197 2124194 2026-10-13T01:41:16Z Sayonzei 40728 Sayonzei移动页面[[beta-bloccante]]至[[bloccante]],不留重定向 2124194 wikitext text/x-wiki {{-it-}} 测试阻滞剂 swb7qgcl434sawzyq96utt502z9tfl2 9943198 9943197 2026-10-13T01:41:50Z Sayonzei 40728 9943198 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|bloccare}} ===名詞=== {{it-noun|m}} # [[阻滯]]者 hrs0zvkfgj51zeg8zwuv4hmcuhij5bj bicicletta 0 961797 9943635 4881700 2026-10-13T02:06:31Z Sayonzei 40728 9943635 wikitext text/x-wiki ==英語== ===詞源=== {{ubor|en|it|bicicletta}}。{{doublet|en|bicyclette}}。 ===名詞=== {{en-noun}} # {{c|en|雞尾酒}} 一种用[[白葡萄酒]]、[[金巴利]]酒、[[苏打水]]和[[柑橘]]类水果调制的[[鸡尾酒]]<ref> Chicago Tribune, Joe Gray, [https://www.chicagotribune.com/dining/drink/sc-food-bicicletta-italian-summer-cocktail-recipe-0920-20190923-x4ykqhkudbc7jole4xc2jqwfca-story.html How the Bicicletta, a classic Italian drink, got a little dirty], 2019 </ref><ref>Liquor.com, [https://web.archive.org/web/20200925210912/https://www.liquor.com/recipes/bicicletta/ Bicicletta], 2018</ref><ref> Rachael Ray Magazine, Judith Pena, [https://www.rachaelraymag.com/real-life/bicicletta-brunch-cocktail-recip A Blurry Bicicletta Cocktail], 2013 </ref> ====近義詞==== * {{l|en|bicyclette}} ===參考資料=== <references/> ==意大利語== [[Image:Cykel.JPG|thumb|una bicicletta]] ===詞源=== {{ety|it|:bor|fr:bicyclette}} {{bor+|it|fr|bicyclette}}。{{surf|it|biciclo|-etta}}。 ===發音=== {{it-pr|+<audio:It-una bicicletta.ogg>}} ===名詞=== {{it-noun|f}} # [[脚踏车]],[[自行车]] #: {{syn|it|bici|biciclo|ciclo|velocipede<q:法律上稱呼>}} ====派生詞彙==== * {{l|it|bici}} * {{l|it|bicicletta da corsa}} * {{l|it|biciclettata}} * {{l|it|biciclo}} ====派生語彙==== * {{desc|sq|biçikletë|bor=1}} * {{desc|am|ብስክሌት|bor=1}} * {{desc|en|bicicletta|bor=1}} * {{desc|so|baskiil|bor=1}} * {{desc|ti|ብሽክለታ|bor=1}} ===延伸閱讀=== * {{R:it:Treccani}} * {{pedia|lang=it}} {{C|it|交通工具|自行車}} 2sxwwe31bxlvzn0llhddf631hzuxzkk bidimensionale 0 961830 9943633 4881776 2026-10-13T02:05:13Z Sayonzei 40728 9943633 wikitext text/x-wiki ==意大利語== ===形容詞=== {{it-adj}} # [[二维]]的 ====相關詞彙==== * {{l|it|dimensionale}} qfeclp355p0lzna872ldmwl66zttta1 bilaterale 0 961948 9943637 4882039 2026-10-13T02:08:15Z Sayonzei 40728 9943637 wikitext text/x-wiki {{also|bilatérale}} ==意大利語== ===發音=== {{it-pr}} ===形容詞=== {{it-adj}} # 兩側的 # [[双边]]的,[[雙方]]的 ====派生詞彙==== * {{l|it|bilateralmente}} ===延伸閱讀=== * {{R:it:Treccani}} ===異序詞=== * {{anagrams|it|a=aabeeillrt|alterabile|barelliate|liberatela}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=bilaterāle}} # {{inflection of|la|bilaterālis||nom//acc//voc|n|s}} ==書面挪威語== ===形容詞=== {{head|nb|形容詞變格形}} # {{inflection of|nb|bilateral||def:sg//p}} ==新挪威語== ===形容詞=== {{head|nn|形容詞變格形}} # {{inflection of|nn|bilateral||def:sg//p}} chmklgldwktc4emllihkn8esjhbljve biodegradabile 0 962037 9943628 4882410 2026-10-13T02:03:19Z Sayonzei 40728 9943628 wikitext text/x-wiki ==意大利語== ===詞源=== {{ety|it|:af|bio-|degradabile}} 源自 {{prefix|it|bio|degradabile}}。 ===形容詞=== {{it-adj}} # 可被[[微生物]][[降解]]的 ====相關詞彙==== * {{l|it|biodegradabilità}} * {{l|it|degradabile}} qvznrsjw9jh4pjxl2g22znk9inyqz26 borbottando 0 962617 9943203 4884603 2026-10-13T01:44:48Z Sayonzei 40728 9943203 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動名詞}} # {{gerund of|it|borbottare}} 119a5p9jwta3791qcgqt8c18fsw5ko0 borbottare 0 962622 9943200 4884604 2026-10-13T01:44:30Z Sayonzei 40728 9943200 wikitext text/x-wiki ==意大利語== ===詞源=== {{onomatopoeic|it}}。<ref>{{R:it:Trec}}</ref> ===發音=== {{it-pr}} ===動詞=== {{it-verb|a/ò+}} # {{lb|it|及物|or|不及物}} [[低聲]][[抱怨]],[[嘟囔]] {{+aux|it|avere}} # {{lb|it|不及物}} [[隆隆]][[作響]] {{+aux|it|avere}} ====變位==== {{it-conj|a/ò+}} ====相關詞彙==== * {{l|it|borbottamento}} * {{l|it|borbottio}} ===參考資料=== {{reflist}} ===異序詞=== * {{anagrams|it|a=abbeoorrtt|borbotterà}} tkye7o2tt9m3k2adarb64gu1ygj2zgk Botrytis cinerea 0 962757 9943627 5880045 2026-10-13T02:02:45Z Sayonzei 40728 9943627 wikitext text/x-wiki ==跨語言== [[File:Botryotinia fuckeliana on Capsicum annuum, grauwe schimmel op paprika.jpg|thumb|''[[Capsicum annuum]]'' 上生長的 '''''Botrytis cinerea''''']] ===專有名詞=== {{taxoninfl|head=[[Botrytis]] {{epinew|cinereus|cinerea|la}}|i=1|g=f}} # {{taxon|種|核盤菌|科|{{w|灰葡萄孢菌}}}} ====同類詞彙==== * {{sense|種}} {{taxlink|Botryotinia fuckeliana|species}}&nbsp;- 有性型 ===參考資料=== * {{pedia|i=1}} * {{specieslite|i=1}} * {{comcatlite|i=1}} * {{R:MycoBank|217312}} * {{R:EOL|189846}} * {{R:NCBI|40559}} * {{R:WoRMS|100261}} gbcpy363cneja5o9pk56tcecrur9yv9 brancolare 0 962905 9943208 4885496 2026-10-13T01:45:56Z Sayonzei 40728 9943208 wikitext text/x-wiki ==意大利語== ===詞源=== {{ety|it|:af|branca|-olare}} 源自 {{af|it|branca|-olare}}。 ===發音=== {{it-pr}} ===動詞=== {{it-verb|a/à}} # {{lb|it|不及物}} [[摸索]] {{+aux|it|avere}} # {{lb|it|不及物|比喻}} [[試探]]著做 {{+aux|it|avere}} ====變位==== {{it-conj|a/à}} ===異序詞=== * {{anagrams|it|a=aabcelnorr|brancolerà}} hvfdrzrcrit00uoewldwtoj7vjtuhx4 Brasile 0 962936 9943216 7328983 2026-10-13T01:47:31Z Sayonzei 40728 9943216 wikitext text/x-wiki {{also|brasile}} ==意大利語== {{wp|it:}} ===發音=== {{it-pr|Brasìle}} ===專有名詞=== {{it-proper noun|m}} # {{place|it|國家|cont/南美洲|t=巴西}} ====派生詞彙==== * {{l|it|brasiliano}} ==撒丁語== {{wp|sc:}} ===專有名詞=== {{sc-proper noun|m}} # {{place|sc|國家|cont/南美洲|t=巴西}} iwlvs1466zd3q8aygnpg3x272u6aitw brasiliano 0 962940 9943229 4885560 2026-10-13T01:48:00Z Sayonzei 40728 9943229 wikitext text/x-wiki ==意大利語== ===發音=== {{it-pr}} ===形容詞=== {{it-adj}} # [[巴西]]的 ===名詞=== {{it-noun|m|f=+}} # [[巴西人]] ====相關詞彙==== * {{l|it|Brasile}} {{C|it|國籍}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliānō}} # {{inflection of|la|brasiliānus||dat//abl|m//n|s}} ==葡萄牙語== ===詞源=== {{ety|pt|:lbor|la-new:brasīliānus|text=+|tree=1}} ===發音=== {{pt-IPA}} * {{rhymes|pt|ɐnu|s=4,5}} * {{hyph|pt|bra|si|li|a|no}} ===形容詞=== {{pt-adj|sup=+abs|dim=+|aug=+}} # {{lb|pt|古舊}} {{demonym-noun|pt|<<c/巴西>>}} #: {{syn|pt|Thesaurus:brasileiro}} ===名詞=== {{pt-noun|m|f=+}} # {{lb|pt|古舊}} {{demonym-adj|pt|<<c/巴西>>}} #: {{syn|pt|Thesaurus:brasileiro}} ===延伸閱讀=== * {{R:pt:Aulete}} * {{R:pt:Infopédia}} * {{R:pt:Priberam}} * {{R:pt:Michaelis}} {{C|pt|國籍|巴西}} nj8y9m0oa1mnuirdgm5zthjhnjsol0o brevettuale 0 962993 9943643 4885850 2026-10-13T02:13:27Z Sayonzei 40728 9943643 wikitext text/x-wiki ==意大利語== ===形容詞=== {{it-adj}} # {{lb|it|relational}} [[專利]]的 ====相關詞彙==== * {{l|it|brevetto}} cd9p98rgp63vq16jmt5tpb0d70f2yyf britannico 0 963071 9943639 4886098 2026-10-13T02:10:17Z Sayonzei 40728 9943639 wikitext text/x-wiki ==意大利語== ===詞源=== 源自{{der|it|la|britannicus}},源自{{der|it|grc|βρεττανικός}}。 ===發音=== {{it-pr|britànnico}} ===形容詞=== {{it-adj}} # [[英國]]的,[[大不列顛]]的 ===名詞=== {{it-noun|m|f=+}} # [[英國人]],[[大不列顛]]人 ====相關詞彙==== {{col|it |Gran Bretagna |Regno Unito }} ===延伸閱讀=== * {{R:it:Collins}} * {{R:it:Hoepli}} * {{R:it:Garzanti}} * {{R:it:Olivetti|britànnico}} * {{R:it:Trec|britànnico}} {{C|it|國籍|英國}} ==拉丁語== ===發音=== * {{la-IPA|britannicō}} ===形容詞=== {{head|la|形容詞變格形|head=britannicō}} # {{inflection of|la|britannicus||dat//abl|m//n|s}} ib1dblycnf5s6t0i36g54a3j4rjy6lb 有色 0 991209 9943107 8511026 2026-10-12T18:47:04Z Jiba1219 110530 /* 日語 */ 9943107 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=yǒusè |c=jau5 sik1 |cat=v,a }} ===動詞=== {{zh-verb}} # 擁有[[色彩]];顯現[[顏色]] ===形容詞=== {{head|zh|形容詞}} # {{lb|zh|attributive}} [[彩色]] ====反義詞==== * {{zh-l|無色}} ====衍生詞==== {{col3|zh|有色玻璃|有色金屬|有色人種|有色眼鏡}} ==日語== {{ja-kanjitab|ゆう|しょく|yomi=o}} ===發音=== {{ja-pron|ゆうしょく|acc=0|acc_ref=NHK}} ===名詞=== {{ja-noun|ゆうしょく}} # 擁有色彩 ====反義詞==== * {{ja-r|無色|むしょく}} ===參考資料=== {{reflist}} ==朝鮮語== {{ko-hanjatab}} ===名詞=== {{ko-noun|hj|hangeul=유색}} # {{hanja form of|유색}} tge5gwmp4rhtpmd34jraid7rlizwgmk 夕風 0 991488 9943671 7692497 2026-10-13T08:58:15Z Fglffer 55252 Fglffer移动页面[[夕なぎ]]至[[夕風]],不留重定向 7692497 wikitext text/x-wiki ==日语== 夕なぎ【ゆう風】 傍晚海上风平浪静。 [[Category:日语]] 97rj2fw6dgh0xqj4o17omqxdg8dzywv 阮囊羞澀 0 1052571 9943153 7067847 2026-10-12T23:51:48Z Fglffer 55252 9943153 wikitext text/x-wiki ==漢語== {{zh-forms|s=阮囊羞涩|type=112}} ===發音=== {{zh-pron |m=ruǎnnángxiūsè |cat=cy }} ===成語=== {{head|zh|成語}} # {{lb|zh|比喻}} [[手頭]]無錢,[[貧乏]]、[[窮困]] s7ug90z7jw1qwqp2jztx17tz78z10fz 尸位素餐 0 1059278 9943138 9925694 2026-10-12T23:34:47Z Fglffer 55252 /* 漢語 */ 9943138 wikitext text/x-wiki ==漢語== {{zh-forms|alt=素餐尸位,尸素|type=22}} ===字源=== 來自《[[s:漢書/卷067|漢書]]》: {{zh-x|今 朝廷 大臣,上 不.能 匡-主,下 亡{wú} 以 益-民,皆 尸位素餐。|現今的朝廷大臣,不能匡正輔佐主上,對下也無法對人民有幫助,占著職位領著薪水不做事。|CL|collapsed=y}} ===發音=== {{zh-pron |m=shīwèisùcān |c=si1 wai6 sou3 caan1 |md=sĭ-ôi-só-chuăng |mn-t=si1 ui7 su3 cang1 |oc=y |mc=y |cat=cy }} ===成語=== {{head|zh|成語}} # {{lb|zh|derogatory|或|自謙}} 占著[[職位]]享受[[俸祿]]而不[[做事]] #* {{zh-x|為什麼 ^@元 ^@好{hào}問 要 說「當官 避事 平生 恥」?^@習 總書記 為什麼 又 要 說「為{wéi} 官 避事 平生 恥」呢?因為 在 我們 中國人 看來 啊,在 其 位 就 得{děi} 謀 其 政,一個 人 要是 尸位素餐 的話,本身 就是 為{wéi} 人 不齒 的 一 件 事。|ref='''2021年'''2月21日,{{w|中國中央電視台}} 《平“语”近人——习近平喜欢的典故》第二季第四集《[https://tv.cctv.com/2021/02/22/VIDET6b0JFvhxybLDZia1TxU210222.shtml 为官避事平生耻]》 ([https://epaper.gmw.cn/gmrb/html/2021-02/22/nw.D110000gmrb_20210222_1-03.htm 文本]),主講人{{w|蒙曼}}|}} ====同義字==== * {{zh-l|尸祿素餐}} * {{zh-l|尸利素餐}} * {{zh-l|飽食終日}} ==日語== {{ja-kanjitab|し|い|そ|さん|yomi=o}} ===發音=== {{ja-pron|し.いそさん|acc=1|acc_ref=DJR}} ===名詞=== {{ja-noun|し.いそさん|hhira=しゐそさん}} # 占著[[職位]]享受[[俸祿]]而不[[做事]] ===參考資料=== {{reflist}} ==朝鮮語== {{ko-hanjatab}} ===名詞=== {{ko-noun|hangeul=시위소찬}} # {{hanja form of|시위소찬}} 4p9l9pv4drfwalqvnqrgu7vum5pss5w 望舒 0 1094333 9943154 6924762 2026-10-12T23:54:31Z Fglffer 55252 9943154 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=wàngshū |cat=n }} ===名詞=== {{head|zh|名詞}} # [[神話]][[傳說]]中為[[月神]][[駕車]]的[[御者]] # {{lb|zh|引申}} [[月亮]] ====同義詞==== * {{zh-l|纖阿}} * {{zh-l|月御}} jslt0q4v10lmrxcc7445664v0cvs2j8 望月 0 1094429 9943156 9927767 2026-10-12T23:55:39Z Fglffer 55252 9943156 wikitext text/x-wiki ==汉语== {{zh-forms}} ===发音=== {{zh-pron |m=wàngyuè |c=mong6 jyut6 |cat=n }} ===名词=== {{head|zh|名詞}} # [[满月]] {{C|zh|曆法}} ==日语== ===词源1=== {{ja-kanjitab|yomi=o|ぼう|げつ}} 源自{{der|ja|ltc|-|sort=ぼうげつ}}复合词{{m|zh|望月|tr=mjang ngjwot}},可能与满月时最易观赏有关。 ====发音==== {{ja-pron|ぼうげつ|dev=4|acc=1|acc_ref=DJR}} ====名词==== {{ja-noun|ぼうげつ|hhira=ばうげつ}} # [[满月]] # [[农历]][[十五]] ===词源2=== {{ja-kanjitab|yomi=k|もち|つき|k2=づき}} {{compound|ja|sort=もちづき|望|tr1=mochi|月|tr2=tsuki}}构成的复合词。<ref name="KDJ">{{R:Kokugo Dai Jiten}}</ref><ref name="DJR">{{R:Daijirin}}</ref>''mochi''部分派生自何词不详。可能派生自动词{{m|ja|持つ|tr=motsu||拿着,持有}}的{{m|ja|連用形|tr=ren'yōkei}},但语义上的关联不明。{{rendaku2|sort=もちづき|tsuki|zuki}} ====发音==== {{ja-pron|もちづき|dev=4|acc=2|acc_ref=DJR}} ====名词==== {{ja-noun|もちづき}} # [[满月]] # [[农历]][[十五]] # [[农历]][[八月十五]],[[中秋节]] =====近义词===== * {{sense|满月}} {{ja-r|満月|まんげつ}} ====专有名词==== {{ja-pos|proper|もちづき}} # {{surname|ja|sort=もちづき}} ===参考资料=== {{reflist}} {{cln|ja|有多種讀音的詞}} {{topics|ja|曆法|sort=もちづき}} cxny5y924v2oj5tqouwx6vh76hzbw2h 朱紫 0 1113192 9943665 6943544 2026-10-13T08:22:44Z Fglffer 55252 9943665 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=zhūzǐ |cat=n }} ===名詞=== {{head|zh|名詞}} # [[紅色]]和[[紫色]] # {{lb|zh|比喻}} [[高官]] # {{lb|zh|比喻}} [[相對]]的[[兩方]]{{gl|正邪、善惡、美醜等}} # {{lb|zh|比喻}} [[作品]]的[[文采]] keafplbr9kcjoo7ndbq50d3yd128gkj 凪 0 1133456 9943673 8689502 2026-10-13T09:04:06Z Fglffer 55252 /* 日語 */ 9943673 wikitext text/x-wiki {{character info}} ==跨語言== ===漢字=== {{Han char|rn=16|rad=几|as=04|sn=6|four=|canj=HNYLM|ids=⿵𠘨止}} ====來源==== {{Han ref|kx=0134.121|dkj=01758|dj=0299.260|hdz=10277.021|uh=51EA}} ==漢語== {{zh-forms}} ===字源=== {{obor|zh|ja|凪|tr=nagi}}。 ===詞源=== {{spelling pronunciation|zh}},取{{zh-l|止}}音。 ===發音=== {{zh-pron |m=zhǐ |c=jau1 |w=sh:5tsy |cat= }} ===釋義=== {{head|zh|漢字}} # {{n-g|用於日本名字。}} ==日語== ===字源=== {{ja-etym-kokuji}}。{{m|ja|𠘨|pos={{m|ja|風}}的外圍部件}} + {{m|ja|止|t=停止}}。 ===漢字=== {{ja-kanji|grade=|rs=几04}} ====讀法==== {{ja-readings |on= |kun=な-ぐ, なぎ- }} ====組詞==== {{col|ja |{{ja-r|朝%凪|あさ%なぎ}} |{{ja-r|油%凪|あぶら%なぎ}} |{{ja-r|磯%凪|いそ%なぎ}} |{{ja-r|浦%凪|うら%なぎ}} |{{ja-r|べた凪|べたなぎ}} |{{ja-r|御%講%凪|お%こう%なぎ}} |{{ja-r|寒%凪|かん%なぎ}} |{{ja-r|小%春%凪|こ%はる%なぎ}} |{{ja-r|風%凪|かざ%なぎ}} |{{ja-r|巣%入り凪|す%いりなぎ}} |{{ja-r|土%用%凪|ど%よう%なぎ}} |{{ja-r|初%凪|はつ%なぎ}} |{{ja-r|冬%凪|ふゆ%なぎ}} |{{ja-r|夕%凪|ゆう%なぎ}} |{{ja-r|夜%凪|よ%なぎ}} }} ===發音=== {{ja-kanjitab|なぎ|yomi=k|alt=和ぎ}} {{ja-pron|なぎ|acc=2}} ===名詞=== {{ja-noun|なぎ}} # [[海面]]無風無浪,[[風平浪靜]] ===專有名詞=== {{ja-pos|proper|なぎ}} # {{given name|ja|male,female|sort=なぎ}} ==朝鮮語== ===漢字=== {{ko-hanja|hangeul=[[지]]|rv=ji|mr=chi}} # {{rfdef|ko|sort=几04}} ==越南語== ===漢字=== {{vi-readings|reading=[[chỉ]]|rs=几04}} # {{rfdef|vi|sort=几04}} 779gom01e62yhh8z0wi1rptmxe2ugui 煃 0 1150532 9943668 6963437 2026-10-13T08:43:55Z Fglffer 55252 9943668 wikitext text/x-wiki {{character info}} ==跨語言== ===漢字=== {{Han char|rn=86|rad=火|as=09|sn=13|four=94814|canj=FKGG|ids=⿰火奎}} ====來源==== * {{Han ref|kx=0675.230|dkj=19170|hdz=32216.070|ch=8550001.02|uh=7143}} ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=kuǐ |m-s= |dg= |c=kwai2 |c-t= |g= |h=pfs= |j= |mb= |md= |mn= |mn-t= |w= |x= |mc=y |oc=y |cat=a }} ===釋義=== {{head|zh|漢字}} # {{lb|zh|hist-dict}} [[火]]的[[外貌]] # {{zh-used2|n}} i4my1ljkr7yuuch7jjj0wtrvhjxhvml 嗜痂成癖 0 1161754 9943666 6966857 2026-10-13T08:40:34Z Fglffer 55252 9943666 wikitext text/x-wiki ==漢語== {{zh-forms|lit=喜歡吃結痂的嗜好}} ===發音=== {{zh-pron |m=shìjiāchéngpǐ |cat=cy }} ===成語=== {{head|zh|成語}} # 形容人[[愛好]][[特殊]][[事物]]的[[嗜好]] ====同義詞==== * {{zh-l|嗜痂之癖}} 3eul26mtu56jk1122ma23kthzt62qhd 敞车 0 1225416 9943149 6989922 2026-10-12T23:49:33Z Fglffer 55252 Fglffer移动页面[[敞篷汽車]]至[[敞车]],不留重定向 6989922 wikitext text/x-wiki ==漢語== * 简体:<big>'''-{[[敞]]}- -{[[篷]]}- -{[[汽]]}- -{[[车]]}-'''</big> * 繁体:<big>'''-{[[敞]]}- -{[[篷]]}- -{[[汽]]}- -{[[車]]}-'''</big> ===讀音=== * 汉语拼音:chǎng péng qì chē ===翻譯=== {{翻譯-頂}} * {{en}}:convertible (car); {{翻譯-底}} {{CC-CEDICT}} [[Category:待分類的漢語詞]] [[Category:漢語|攴08竹11水04車00]] qtpl4k97l41p7d8ejjvapu645vrsvcz 9943150 9943149 2026-10-12T23:49:46Z Fglffer 55252 重定向页面至[[敞車]] 9943150 wikitext text/x-wiki #REDIRECT [[敞車]] jtk7wmx0mjevtv6mh4gykgik7qp923l 敞篷車 0 1226924 9943145 6990513 2026-10-12T23:45:10Z Fglffer 55252 9943145 wikitext text/x-wiki ==漢語== {{zh-forms|s=敞篷车|type=21}} {{wp}} ===發音=== {{zh-pron |m=chǎngpéngchē,er=y |c=cong2 pung4 ce1 |w=sh:5tshan bon tsho |cat=n }} ===名詞=== {{head|zh|名詞}} # 帶有[[摺疊]]式可[[開啟]][[車頂]]的[[轎車]] ====同義詞==== * {{zh-l|敞車}} {{C|zh|交通工具}} 8n3mp15x6rj24cvybn7ax39cb6memtb 9943163 9943145 2026-10-13T01:16:05Z Sayonzei 40728 /* 名詞 */ 9943163 wikitext text/x-wiki ==漢語== {{zh-forms|s=敞篷车|type=21}} {{wp}} ===發音=== {{zh-pron |m=chǎngpéngchē,er=y |c=cong2 pung4 ce1 |w=sh:5tshan bon tsho |cat=n }} ===名詞=== {{head|zh|名詞}} # 帶有[[摺疊]]式可[[開啟]][[車頂]]的[[轎車]] ====同義詞==== * {{zh-l|敞車}} ====翻譯==== {{trans-top|帶有摺疊式可開啟車頂的轎車}} * 阿拉伯語:{{t|ar|حَنْطُور|m}} * 保加利亞語:{{t|bg|кабрио|n}} * 加泰羅尼亞語:{{t+|ca|descapotable|m}}、{{t+|ca|convertible|m}} * 丹麥語:{{t+|da|cabriolet|c}} * 英語:{{t+|en|convertible}} * 世界語:{{t|eo|kabrioleto}} * 芬蘭語:{{t+|fi|avoauto}} * 法語:{{t+|fr|décapotable|f}}、{{t+|fr|cabriolet|m}} * 加利西亞語:{{t+|gl|convertíbel|m}}、{{t+|gl|convertible}} * 德語:{{t+|de|Cabriolet|m}}、{{t+|de|Cabrio|n}}、{{qualifier|罕用}} {{t+|de|Kabriolett|n}} * 匈牙利語:{{t+|hu|kabrió}}、{{t|hu|[[lenyitható]]/[[lehajtható]]/[[nyitható]] [[tetejű]] [[autó]]}} * 意大利語:{{t+|it|decappottabile|f}} * 日語:{{t+|ja|オープンカー|tr=ōpunkā}}、{{t+|ja|コンバーチブル|tr=konbāchiburu}}、{{t+|ja|カブリオレ|tr=kaburiore}} * 朝鮮語:{{t|ko|오픈카}}、{{t|ko|컨버터블}}、{{t|ko|카브리올레}} * 馬其頓語:{{t|mk|кабриоле́т|m}} * 納瓦霍語:{{t|nv|chidí nadlééh}}、{{t|nv|chidí bikʼi dah azbaalígíí}} * 挪威語: *: 書面挪威語:{{t|nb|kabriolet|m}} *: 新挪威語:{{t|nn|kabriolet|m}} * 波蘭語:{{t+|pl|kabriolet|m}} * 葡萄牙語:{{t+|pt|conversível|m}} {{qualifier|巴西}}、{{t+|pt|descapotável}} {{qualifier|葡萄牙}}、{{t+|pt|cabriolé|m}} * 俄語:{{t+|ru|кабриоле́т|m}} * 西班牙語:{{t+|es|convertible|m}} {{qualifier|Latin America}}、{{t+|es|descapotable|m}} {{qualifier|西班牙}}、{{t+|es|cabriolé|m}} * 瑞典語:{{t+|sv|cabriolet|c}}、{{t+|sv|cab|c}} * 土耳其語:{{t|tr|kabriyole}} * 威爾士語:{{t|cy|to gostwng|m}}、{{t|cy|cabriole|m}}、{{t|cy|cabrio|m}} {{trans-bottom}} {{C|zh|交通工具}} 24ewd1fe6mi2eslfhuxrg2dcby0xko5 紫朱 0 1339231 9943660 7615655 2026-10-13T08:16:16Z Fglffer 55252 9943660 wikitext text/x-wiki ==漢語== {{zh-forms}} ===詞源=== 出自《[[s:論語/陽貨第十七|論語·陽貨]]》: {{zh-x|惡 '''紫''' 之 奪'''朱''' 也,惡 鄭聲 之 亂 雅樂 也,惡 利口 之 覆 邦家 者。||CL|collapsed=y}} ===發音=== {{zh-pron |m=zǐzhū |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|比喻}} [[正邪]]或[[真偽]] #* {{zh-x|虛妄 顯 於 真,實誠 亂 於 偽,世人 不悟,是非 不定,紫朱 雜廁,瓦玉.集糅。||CL|ref=漢·王充《論衡‧對作》}} #* {{zh-x|道逢 一 古-叟,開-唇 論 紫朱。||CL|ref=明·袁宏道《感興》詩之三}} {{cln|zh|來自《論語》的詞}} ==朝鮮語== {{ko-hanjatab}} ===名詞=== {{ko-noun|hangeul=자주}} # {{hanja form of|자주}} 06xt0oc1qkgrj56x0kzl6chlevon3ui 9943663 9943660 2026-10-13T08:19:18Z Fglffer 55252 9943663 wikitext text/x-wiki ==漢語== {{zh-forms}} ===詞源=== 出自《[[s:論語/陽貨第十七|論語·陽貨]]》: {{zh-x|惡 '''紫''' 之 奪 '''朱''' 也,惡 鄭聲 之 亂 雅樂 也,惡 利口 之 覆 邦家 者。||CL|collapsed=y}} 後來衍生出{{zh-l|奪朱}}等詞彙。 ===發音=== {{zh-pron |m=zǐzhū |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|比喻}} [[正邪]]或[[真偽]] #* {{zh-x|虛妄 顯 於 真,實誠 亂 於 偽,世人 不悟,是非 不定,紫朱 雜廁,瓦玉.集糅。||CL|ref=漢·王充《論衡‧對作》}} #* {{zh-x|道逢 一 古-叟,開-唇 論 紫朱。||CL|ref=明·袁宏道《感興》詩之三}} {{cln|zh|來自《論語》的詞}} ==朝鮮語== {{ko-hanjatab}} ===名詞=== {{ko-noun|hangeul=자주}} # {{hanja form of|자주}} gtfpkvn2d3ozm2d9ui07ri8b812bkgj Module:Parameters 828 1363187 9943108 9734182 2026-10-12T19:23:16Z TongcyDai 53191 同步 enwikt(rev 93284592):新增 type = "boolean or string"(新版 [[Module:headword utilities]] 所需) 9943108 Scribunto text/plain --[==[TODO: * Change certain flag names, as some are misnomers: * Change `allow_holes` to `keep_holes`, because it's not the inverse of `disallow_holes`. * Change `allow_empty` to `keep_empty`, as it causes them to be kept as "" instead of deleted. * Sort out all the internal error calls. Manual error(format()) calls are used when certain parameters shouldn't be dumped, so find a way to avoid that. ]==] local export = {} local collation_module = "Module:collation" local families_module = "Module:families" local functions_module = "Module:fun" local gender_and_number_utilities_module = "Module:gender and number utilities" local labels_module = "Module:labels" local languages_module = "Module:languages" local math_module = "Module:math" local pages_module = "Module:pages" local parameters_finalize_set_module = "Module:parameters/finalizeSet" local parameters_track_module = "Module:parameters/track" local parse_utilities_module = "Module:parse utilities" local references_module = "Module:references" local scribunto_module = "Module:Scribunto" local scripts_module = "Module:scripts" local string_utilities_module = "Module:string utilities" local table_module = "Module:table" local wikimedia_languages_module = "Module:wikimedia languages" local yesno_module = "Module:yesno" local mw = mw local mw_title = mw.title local string = string local table = table local dump = mw.dumpObject local find = string.find local format = string.format local gsub = string.gsub local insert = table.insert local ipairs = ipairs local list_to_text = mw.text.listToText local make_title = mw_title.makeTitle local match = string.match local max = math.max local new_title = mw_title.new local next = next local pairs = pairs local pcall = pcall local require = require local sub = string.sub local tonumber = tonumber local type = type local unpack = unpack or table.unpack -- Lua 5.2 compatibility local current_title_text, current_namespace, sets -- Defined when needed. local namespaces = mw.site.namespaces --[==[ 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_entities(...) decode_entities = require(string_utilities_module).decode_entities return decode_entities(...) end local function extend(...) extend = require(table_module).extend return extend(...) end local function finalize_set(...) finalize_set = require(parameters_finalize_set_module) return finalize_set(...) end local function get_family_by_code(...) get_family_by_code = require(families_module).getByCode return get_family_by_code(...) end local function get_family_by_name(...) get_family_by_name = require(families_module).getByCanonicalName return get_family_by_name(...) end local function get_language_by_code(...) get_language_by_code = require(languages_module).getByCode return get_language_by_code(...) end local function get_language_by_name(...) get_language_by_name = require(languages_module).getByCanonicalName return get_language_by_name(...) end local function get_script_by_code(...) get_script_by_code = require(scripts_module).getByCode return get_script_by_code(...) end local function get_script_by_name(...) get_script_by_name = require(scripts_module).getByCanonicalName return get_script_by_name(...) end local function get_wm_lang_by_code(...) get_wm_lang_by_code = require(wikimedia_languages_module).getByCode return get_wm_lang_by_code(...) end local function get_wm_lang_by_code_with_fallback(...) get_wm_lang_by_code_with_fallback = require(wikimedia_languages_module).getByCodeWithFallback return get_wm_lang_by_code_with_fallback(...) end local function gsplit(...) gsplit = require(string_utilities_module).gsplit return gsplit(...) end local function is_callable(...) is_callable = require(functions_module).is_callable return is_callable(...) end local function is_integer(...) is_integer = require(math_module).is_integer return is_integer(...) end local function is_internal_title(...) is_internal_title = require(pages_module).is_internal_title return is_internal_title(...) end local function is_positive_integer(...) is_positive_integer = require(math_module).is_positive_integer return is_positive_integer(...) end local function iterate_list(...) iterate_list = require(table_module).iterateList return iterate_list(...) end local function num_keys(...) num_keys = require(table_module).numKeys return num_keys(...) end local function parse_gender_and_number_spec(...) parse_gender_and_number_spec = require(gender_and_number_utilities_module).parse_gender_and_number_spec return parse_gender_and_number_spec(...) end local function parse_references(...) parse_references = require(references_module).parse_references return parse_references(...) end local function pattern_escape(...) pattern_escape = require(string_utilities_module).pattern_escape return pattern_escape(...) end local function php_trim(...) php_trim = require(scribunto_module).php_trim return php_trim(...) end local function scribunto_parameter_key(...) scribunto_parameter_key = require(scribunto_module).scribunto_parameter_key return scribunto_parameter_key(...) end local function sort(...) sort = require(collation_module).sort return sort(...) end local function sorted_pairs(...) sorted_pairs = require(table_module).sortedPairs return sorted_pairs(...) end local function split(...) split = require(string_utilities_module).split return split(...) end local function split_labels_on_comma(...) split_labels_on_comma = require(labels_module).split_labels_on_comma return split_labels_on_comma(...) end local function split_on_comma(...) split_on_comma = require(parse_utilities_module).split_on_comma return split_on_comma(...) end local function tonumber_extended(...) tonumber_extended = require(math_module).tonumber_extended return tonumber_extended(...) end local function track(...) track = require(parameters_track_module) return track(...) end local function yesno(...) yesno = require(yesno_module) return yesno(...) end --[==[ intro: This module is used to standardize template argument processing and checking. A typical workflow is as follows (based on [[Module:translations]]): { ... local parent_args = frame:getParent().args local params = { [1] = {required = true, type = "language", default = "und"}, [2] = true, [3] = {list = true}, ["alt"] = true, ["id"] = true, ["sc"] = {type = "script"}, ["tr"] = true, ["ts"] = true, ["lit"] = true, } local args = require("Module:parameters").process(parent_args, params) -- Do further processing of the parsed arguments in `args`. ... } The `params` table should have the parameter names as the keys, and a (possibly empty) table of parameter tags as the value. An empty table as the value merely states that the parameter exists, but should not receive any special treatment; if desired, empty tables can be replaced with the value `true` as a perforamnce optimization. Possible parameter tags are listed below: ; {required = true} : The parameter is required; an error is shown if it is not present. The template's page itself is an exception; no error is shown there. ; {default =} : Specifies a default input value for the parameter, if it is absent or empty. This will be processed as though it were the input instead, so (for example) {default = "und"} with the type {"language"} will return a language object for [[:Category:Undetermined language|Undetermined language]] if no language code is provided. (As an exception, numbers and booleans can be specified either as-is or in string format.) When used on list parameters, this specifies a default value for the first item in the list only. Note that it is not possible to generate a default that depends on the value of other parameters. If used together with {required = true}, the default applies only to template pages (see the following entry), as a side effect of the fact that "required" parameters aren't actually required on template pages. This can be used to show an example of the template in action when the template page is visited; however, it is preferred to use `template_default` for this purpose, for clarity. ; {template_default =} : Specifies a default input value for absent or empty parameters only on the template demo invocation (the invocation of the template that is displayed when the template page that implements the template is viewed). Template pages are pages in template space that invoke (through {{tl|#invoke:}}) the module that implements the template and calls [[Module:parameters]]. For example, the page [[Template:en-noun]] implements the {{tl|en-noun}} template, which in turn invokes [[Module:en-headword]], and is a template page for [[Module:en-headword]]. When the template page [[Template:en-noun]] is visited, the {{tl|#invoke:}} of the template's module is expanded as if the template were called without arguments, and the output is inserted at that point into the processed page. This output serves as a sort of demo of the template's functionality. `template_default` can be used to supply default values for use only in this demo. Since the template page may also contain other invocations of the same template (e.g. on the template's documentation page, which is typically transcluded into the template page itself), `template_default` does not apply if there are any arguments passed to the template or if the template is invoked on any other page but its own template page (which is checked by comparing the name of the invoking template to the current pagename). Both `template_default` and `default` can be specified for the same parameter. If this is done, `template_default` applies for the argumentless template invocation on the template page, and `default` in all other circumstances As an example, {{tl|cs-IPA}} uses the equivalent of {[1] = {default = "+", template_default = "příklad"}} to supply a default of {"+"} for mainspace and documentation pages (which tells the module to use the value of the {{para|pagename}} parameter, falling back to the actual pagename), but {"příklad"} (which means "example"), on [[Template:cs-IPA]]. ; {alias_of =} : Treat the parameter as an alias of another. When arguments are specified for this parameter, they will automatically be renamed and stored under the alias name. This allows for parameters with multiple alternative names, while still treating them as if they had only one name. The conversion-related properties of an aliased parameter (e.g. `type`, `set`, `convert`, `sublist`) are taken from the aliasee, and the corrresponding properties set on the alias itself are ignored; but other properties on the alias are taken from the alias's spec and not from the aliasee's spec. This means, for example, that if you create an alias of a list parameter, the alias must also specify the `list` property or it is not a list. (In such a case, a value specified for the alias goes into the first item of the aliasee's list. You cannot make a list alias of a non-list parameter; this causes an error to be thrown.) Similarly, if you specify `separate_no_index` on an aliasee but not on the alias, uses of the unindexed aliasee parameter are stored into the `.default` key, but uses of the unindexed alias are stored into the first numbered key of the aliasee's list. Aliases cannot be required, as this prevents the other name or names of the parameter from being used. Parameters that are aliases and required at the same time cause an error to be thrown. ; {allow_empty = true} : If the argument is an empty string value, it is not converted to {nil}, but kept as-is. The use of `allow_empty` is disallowed if a type has been specified, and causes an error to be thrown. ; {no_trim = true} : Spacing characters such as spaces and newlines at the beginning and end of a positional parameter are not removed. (MediaWiki itself automatically trims spaces and newlines at the edge of named parameters.) The use of `no_trim` is disallowed if a type has been specified, and causes an error to be thrown. ; {type =} : Specifies what value type to convert the argument into. The default is to leave it as a text string. Alternatives are: :; {type = "boolean"} :: The value is treated as a boolean value, either true or false. No value, the empty string, and the strings {"0"}, {"no"}, {"n"}, {"false"}, {"f"} and {"off"} (case-insensitive) are treated as {false}, all other values are considered {true}. :; {type = "boolean or string"} :: The value is treated as either a boolean value (true or false) or some other string value. The strings {"0"}, {"no"}, {"n"}, {"false"}, {"f"} and {"off"} (case-insensitive) are treated as {false}; the string {"1"}, {"yes"}, {"y"}, {"true"}, {"t"} and {"on"} (case-insensitive) are treated as {true}; and all other values are left as-is. You can use `set` to restrict the possible strings that are allowed; the values specified using `set` should not include {true} or {false}. :; {type = "number"} :: The value is converted into a number, and throws an error if the value is not parsable as a number. Input values may be signed (`+` or `-`), and may contain decimal points and leading zeroes. If {allow_hex = true}, then hexadecimal values in the form {"0x100"} may optionally be used instead, which otherwise have the same syntax restrictions (including signs, decimal digits, and leading zeroes after {"0x"}). Hexadecimal inputs are not case-sensitive. Lua's special number values (`inf` and `nan`) are not possible inputs. :; {type = "range"} :: The value is interpreted as a hyphen-separated range of two numbers (e.g. {"2-4"} is interpreted as the range from {2} to {4}). A number input without a hyphen is interpreted as a range from that number to itself (e.g. the input {"1"} is interpreted as the range from {1} to {1}). Any optional flags which are available for numbers will also work for ranges. :; {type = "language"} :: The value is interpreted as a full or [[Wiktionary:Languages#Etymology-only languages|etymology-only language]] code language code (or name, if {method = "name"}) and converted into the corresponding object (see [[Module:languages]]). If the code or name is invalid, then an error is thrown. The additional setting {family = true} can be given to allow [[Wiktionary:Language families|language family codes]] to be considered valid and the corresponding object returned. Note that to distinguish an etymology-only language object from a full language object, use {object:hasType("language", "etymology-only")}. :; {type = "full language"} :: The value is interpreted as a full language code (or name, if {method = "name"}) and converted into the corresponding object (see [[Module:languages]]). If the code or name is invalid, then an error is thrown. Etymology-only languages are not allowed. The additional setting {family = true} can be given to allow [[Wiktionary:Language families|language family codes]] to be considered valid and the corresponding object returned. :; {type = "Wikimedia language"} :: The value is interpreted as a code and converted into a Wikimedia language object. If the code is invalid, then an error is thrown. If {fallback = true} is specified, conventional language codes which are different from their Wikimedia equivalent will also be accepted as a fallback. :; {type = "family"} :: The value is interpreted as a language family code (or name, if {method = "name"}) and converted into the corresponding object (see [[Module:families]]). If the code or name is invalid, then an error is thrown. :; {type = "script"} :: The value is interpreted as a script code (or name, if {method = "name"}) and converted into the corresponding object (see [[Module:scripts]]). If the code or name is invalid, then an error is thrown. :; {type = "title"} :: The value is interpreted as a page title and converted into the corresponding object (see the [[mw:Extension:Scribunto/Lua_reference_manual#Title_library|Title library]]). If the page title is invalid, then an error is thrown; by default, external titles (i.e. those on other wikis) are not treated as valid. Options are: ::; {namespace = n} ::: The default namespace, where {n} is a namespace number; this is treated as {0} (the mainspace) if not specified. ::; {allow_external = true} ::: External titles are treated as valid. ::; {prefix = "namespace override"} (default) ::: The default namespace prefix will be prefixed to the value is already prefixed by a namespace prefix. For instance, the input {"Foo"} with namespace {10} returns {"Template:Foo"}, {"Wiktionary:Foo"} returns {"Wiktionary:Foo"}, and {"Template:Foo"} returns {"Template:Foo"}. Interwiki prefixes cannot act as overrides, however: the input {"fr:Foo"} returns {"Template:fr:Foo"}. ::; {prefix = "force"} ::: The default namespace prefix will be prefixed unconditionally, even if the value already appears to be prefixed. This is the way that {{tl|#invoke:}} works when calling modules from the module namespace ({828}): the input {"Foo"} returns {"Module:Foo"}, {"Wiktionary:Foo"} returns {"Module:Wiktionary:Foo"}, and {"Module:Foo"} returns {"Module:Module:Foo"}. ::; {prefix = "full override"} ::: The same as {prefix = "namespace override"}, except that interwiki prefixes can also act as overrides. For instance, {"el:All topics"} with namespace {14} returns {"el:Category:All topics"}. Due to the limitations of MediaWiki, only the first prefix in the value may act as an override, so the namespace cannot be overridden if the first prefix is an interwiki prefix: e.g. {"el:Template:All topics"} with namespace {14} returns {"el:Category:Template:All topics"}. :; {type = "parameter"} :: The value is interpreted as the name of a parameter, and will be normalized using the method that Scribunto uses when constructing a {frame.args} table of arguments. This means that integers will be converted to numbers, but all other arguments will remain as strings (e.g. {"1"} will be normalized to {1}, but {"foo"} and {"1.5"} will remain unchanged). Note that Scribunto also trims parameter names, following the same trimming method that this module applies by default to all parameter types. :: This type is useful when one set of input arguments is used to construct a {params} table for use in a subsequent {export.process()} call with another set of input arguments; for instance, the set of valid parameters for a template might be defined as {{tl|#invoke:[some module]|args=}} in the template, where {args} is a sublist of valid parameters for the template. :; {type = "qualifier"} :: The value is interpreted as a qualifier and converted into the correct format for passing into `format_qualifiers()` in [[Module:qualifier]] (which currently just means converting it to a one-item list). :; {type = "labels"} :: The value is interpreted as a comma-separated list of labels and converted into the correct format for passing into `show_labels()` in [[Module:labels]] (which is currently a list of strings). Splitting is done on commas not followed by whitespace, except that commas inside of double angle brackets do not count even if not followed by whitespace. This type should be used by for normal labels (typically specified using {{para|l}} or {{para|ll}}) and accent qualifiers (typically specified using {{para|a}} and {{para|aa}}). :; {type = "references"} :: The value is interpreted as one or more references, in the format prescribed by `parse_references()` in [[Module:references]], and converted into a list of objects of the form accepted by `format_references()` in the same module. If a syntax error is found in the reference format, an error is thrown. :; {type = "genders"} :: The value is interpreted as one or more comma-separated gender/number specs, in the format prescribed by [[Module:gender and number]]. Inline modifiers (`<q:...>`, `<qq:...>`, `<l:...>`, `<ll:...>` or `<ref:...>`) may be attached to a gender/number spec. :; {type = "form of tags"} :: The value is interpreted as an ampersand-separated list of grammar tags and converted into the correct format for passing as `tags` into `tagged_inflections()` in [[Module:form of]] (which is currently a list of strings). Splitting is always done by ampersands. This type should be used by for inflection qualifiers that act as grammar tags (typically specified using {{para|infl}}). :; {type = function(val) ... end} :: `type` may be set to a function (or callable table), which must take the argument value as its sole argument, and must output one of the other recognized types. This is particularly useful for lists (see below), where certain values need to be interpreted differently to others. ; {list =} : Treat the parameter as a list of values, each having its own parameter name, rather than a single value. The parameters will have a number at the end, except optionally for the first (but see also {require_index = true}). For example, {list = true} on a parameter named "head" will include the parameters {{para|head}} (or {{para|head1}}), {{para|head2}}, {{para|head3}} and so on. If the parameter name is a number, another number doesn't get appended, but the counting simply continues, e.g. for parameter {3} the sequence is {{para|3}}, {{para|4}}, {{para|5}} etc. List parameters are returned as numbered lists, so for a template that is given the parameters `|head=a|head2=b|head3=c`, the processed value of the parameter {"head"} will be { { "a", "b", "c" }}}. : The value for {list =} can also be a string. This tells the module that parameters other than the first should have a different name, which is useful when the first parameter in a list is a number, but the remainder is named. An example would be for genders: {list = "g"} on a parameter named {1} would have parameters {{para|1}}, {{para|g2}}, {{para|g3}} etc. : If the number is not located at the end, it can be specified by putting {"\1"} at the number position. For example, parameters {{para|f1accel}}, {{para|f2accel}}, ... can be captured by using the parameter name {"f\1accel"}, as is done in [[Module:headword/templates]]. ; {set =} : Require that the value of the parameter be one of the specified values (or omitted, if {required = true} isn't given). Two formats are allowed; either a list of possible values can be supplied, or a table can be supplied where the keys are allowed values and the values are either `true` or a string naming a value found elsewhere in the table as a key. In the latter case, the key is an alias and the value is the canonical value, and if the user uses the alias, it will automatically be mapped to the canonical value. In such a case, the canonical value cannot itself be an alias. The use of `set` is disallowed if {type = "boolean"} and causes an error to be thrown. The values given should be strings (i.e. pre-conversion values) unless {type = "number"}, in which case they should be numbers. If {type = "boolean or string"}, the values specified using {set =} are those that ''cannot'' be interpreted as a boolean; boolean strings (e.g. {"1"}, {"0"}, {"true"}, {"false"}, etc.) are always allowed in addition. ; {sublist =} : The value of the parameter is a delimiter-separated list of individual raw values. The resulting field in `args` will be a Lua list (i.e. a table with numeric indices) of the converted values. If {sublist = true} is given, the values will be split on commas (possibly with whitespace on one or both sides of the comma, which is ignored). If {sublist = "comma without whitespace"} is given, the values will be split on commas which are not followed by whitespace, and which aren't preceded by an escaping backslash. Otherwise, the value of `sublist` should be either a Lua pattern specifying the delimiter(s) to split on or a function (or callable table) to do the splitting, which is passed two values (the value to split and a function to signal an error) and should return a list of the split values. ; {convert =} : If given, this specifies a function (or callable table) to convert the raw parameter value into the Lua object used during further processing. The function is passed two arguments, the raw parameter value itself and a function used to signal an error during parsing or conversion, and should return one value, the converted parameter. The error-signaling function contains the name and raw value of the parameter embedded into the message it generates, so these do not need to specified in the message passed into it. If `type` is specified in conjunction with `convert`, the processing by `type` happens first. If `sublist` is given in conjunction with `convert`, the raw parameter value will be split appropriately and `convert` called on each resulting item. ; {allow_hex = true} : When used in conjunction with {type = "number"}, allows hexadecimal numbers as inputs, in the format {"0x100"} (which is not case-sensitive). ; {family = true} : When used in conjunction with {type = "language"}, allows [[Wiktionary:Language families|language family codes]] to be returned. To check if a given object refers to a language family, use {object:hasType("family")}. ; {method = "name"} : When used in conjunction with {type = "language"}, {type = "family"} or {type = "script"}, checks for and parses a language, family or script name instead of a code. ; {allow_holes = true} : This is used in conjunction with list-type parameters. By default, the values are tightly packed in the resulting list. This means that if, for example, an entry specified `head=a|head3=c` but not {{para|head2}}, the returned list will be { {"a", "c"}}}, with the values stored at the indices {1} and {2}, not {1} and {3}. If it is desirable to keep the numbering intact, for example if the numbers of several list parameters correlate with each other (like those of {{tl|affix}}), then this tag should be specified. : If {allow_holes = true} is given, there may be {nil} values in between two real values, which makes many of Lua's table processing functions no longer work, like {#} or {ipairs()}. To remedy this, the resulting table will contain an additional named value, `maxindex`, which tells you the highest numeric index that is present in the table. In the example above, the resulting table will now be { { "a", nil, "c", maxindex = 3}}}. That way, you can iterate over the values from {1} to `maxindex`, while skipping {nil} values in between. ; {disallow_holes = true} : This is used in conjunction with list-type parameters. As mentioned above, normally if there is a hole in the source arguments, e.g. `head=a|head3=c` but not {{para|head2}}, it will be removed in the returned list. If {disallow_holes = true} is specified, however, an error is thrown in such a case. This should be used whenever there are multiple list-type parameters that need to line up (e.g. both {{para|head}} and {{para|tr}} are available and {{para|head3}} lines up with {{para|tr3}}), unless {allow_holes = true} is given and you are prepared to handle the holes in the returned lists. ; {disallow_missing = true} : This is similar to {disallow_holes = true}, but an error will not be thrown if an argument is blank, rather than completely missing. This may be used to tolerate intermediate blank numerical parameters, which sometimes occur in list templates. For instance, `head=a|head2=|head3=c` will not throw an error, but `head=a|head3=c` will. ; {require_index = true} : This is used in conjunction with list-type parameters. By default, the first parameter can have its index omitted. For example, a list parameter named `head` can have its first parameter specified as either {{para|head}} or {{para|head1}}. If {require_index = true} is specified, however, only {{para|head1}} is recognized, and {{para|head}} will be treated as an unknown parameter. {{tl|affixusex}} (and variants {{tl|suffixusex}}, {{tl|prefixusex}}) use this, for example, on all list parameters. ; {separate_no_index = true} : This is used to distinguish between {{para|head}} and {{para|head1}} as different parameters. For example, in {{tl|affixusex}}, to distinguish between {{para|sc}} (a script code for all elements in the usex's language) and {{para|sc1}} (the script code of the first element, used when the first element is prefixed with a language code to indicate that it is in a different language). When this is used, the resulting table will contain an additional named value, `default`, which contains the value for the indexless argument. ; {flatten = true} : This is used in conjunction with list-type parameters when `sublist` or a list-generating type such as {"labels"} or {"genders"} is also specified, and causes the resulting list to be flattened. Not currently compatible with {allow_holes = true}. ; {replaced_by =} : Specifies that the parameter is no longer valid, and has been replaced by some other mechanism. If the value of `replaced_by` is a string, it is the name of the new parameter to use instead. Use the `reason` tag to specify the reason why this change has been made, e.g. {reason = "for consistency with the corresponding parameter in other Romance-language headword templates"}. If the value of `replaced_by` is {false}, there is no replacement parameter. In this case, `instead` should be supplied with a description of what to do instead, e.g. {instead = "use an inline modifier on |2= such as <q:...>, <qq:...>, <l:...> or <ll:...>"}. You can also supply a justification in `reason` if you feel it is appropriate or necessary to do so. ; {reason =} : When used in conjunction with `replaced_by`, specifies the reason for the parameter replacement. ; {instead =} : When used in conjunction with {replaced_by = false}, specifies what to do instead of using the removed parameter. ; {demo = true} : This is used as a way to ensure that the parameter is only enabled on the template's own page (and its documentation page), and in the User: namespace; otherwise, it will be treated as an unknown parameter. This should only be used if special settings are required to showcase a template in its documentation (e.g. adjusting the pagename or disabling categorization). In most cases, it should be possible to do this without using demo parameters, but they may be required if a template/documentation page also contains real uses of the same template as well (e.g. {{tl|shortcut}}), as a way to distinguish them. ; {deprecated = true} : This is for tracking the use of deprecated parameters, including any aliases that are being brought out of use. See [[Wiktionary:Tracking]] for more information. ]==] -- Returns true if the current page is a template or module containing the current {{#invoke}}. -- If the include_documentation argument is given, also returns true if the current page is either page's documentation page. local own_page, own_page_or_documentation local function is_own_page(include_documentation) if own_page == nil then if current_namespace == nil then local current_title = mw_title.getCurrentTitle() current_title_text, current_namespace = current_title.prefixedText, current_title.namespace end local frame = current_namespace == 828 and mw.getCurrentFrame() or current_namespace == 10 and mw.getCurrentFrame():getParent() if frame then local frame_title_text = frame:getTitle() own_page = current_title_text == frame_title_text own_page_or_documentation = own_page or current_title_text == frame_title_text .. "/documentation" else own_page, own_page_or_documentation = false, false end end return include_documentation and own_page_or_documentation or own_page end -------------------------------------- Some helper functions ----------------------------- -- Convert a list in `list` to a string, separating the final element from the preceding one(s) by `conjunction`. If -- `dump_vals` is given, pass all values in `list` through mw.dumpObject() (WARNING: this destructively modifies -- `list`). This is similar to serialCommaJoin() in [[Module:table]] when used with the `dontTag = true` option, but -- internally uses mw.text.listToText(). local function concat_list(list, conjunction, dump_vals) if dump_vals then for k, v in pairs(list) do list[k] = dump(v) end end return list_to_text(list, nil, conjunction) end -- A helper function for use with generating error-signaling functions in the presence of raw value conversion. Format a -- message `msg`, including the processed value `processed` if it is different from the raw value `rawval`; otherwise, -- just return `msg`. local function msg_with_processed(msg, rawval, processed) if rawval == processed then return msg end local processed_type = type(processed) return format("%s (processed value %s)", msg, (processed_type == "string" or processed_type == "number") and processed or dump(processed) ) end -- Separate form of tags with ampersand (&). local function split_tags_on_ampersand(tags) return split(tags, "&") end -------------------------------------- Error handling ----------------------------- local function process_error(fmt, ...) local args = {...} for i, val in ipairs(args) do args[i] = dump(val) end if type(fmt) == "table" then -- hacky signal that we're called from internal_process_error(), and not to omit stack frames return error(format(fmt[1], unpack(args))) end return error(format(fmt, unpack(args)), 3) end local function internal_process_error(fmt, ...) process_error({"Internal error in `params` table: " .. fmt}, ...) end -- Check that a parameter or argument is in the form form Scribunto normalizes input argument keys into (e.g. 1 not "1", "foo" not " foo "). Otherwise, it won't be possible to normalize inputs in the expected way. Unless is_argument is set, also check that the name only contains one placeholder at most, and that strings don't resolve to numeric keys once the placeholder has been substituted. local function validate_name(name, desc, extra_name, is_argument) local normalized = scribunto_parameter_key(name) if name and name == normalized then if is_argument or type(name) ~= "string" then return end local placeholder = find(name, "\1", nil, true) if not placeholder then return elseif find(name, "\1", placeholder + 1, true) then error(format( "Internal error: expected %s to only contain one placeholder, but saw %s", extra_name and (desc .. dump(extra_name)) or desc, dump(name) )) end local first_name = gsub(name, "\1", "1") normalized = scribunto_parameter_key(first_name) if first_name == normalized then return end error(format( "Internal error: %s cannot resolve to numeric parameters once any placeholder has been substituted, but %s resolves to %s", extra_name and (desc .. dump(extra_name)) or desc, dump(name), dump(normalized) )) elseif normalized == nil then error(format( "Internal error: expected %s to be of type string or number, but saw %s", extra_name and (desc .. dump(extra_name)) or desc, type(name) )) end error(format( "Internal error: expected %s to be Scribunto-compatible: %s (a %s) should be %s (a %s)", extra_name and (desc .. dump(extra_name)) or desc, dump(name), type(name), dump(normalized), type(normalized) )) end local function validate_alias_options(...) local invalid = { required = true, default = true, template_default = true, allow_holes = true, disallow_holes = true, disallow_missing = true, } function validate_alias_options(param, name, main_param, alias_of) for k in pairs(param) do if invalid[k] then track("bad alias option") -- internal_process_error( -- "parameter %s cannot have the option %s, as it is an alias of parameter %s.", -- name, option, alias_of -- ) end end -- Soon, aliases will inherit options from the main parameter via __index. Track cases where this would happen. if main_param ~= true then for k in pairs(main_param) do if param[k] == nil and not invalid[k] then if k == "list" then -- these need to be changed to list = false to retain current behaviour track("mismatched list alias option") elseif not (k == "type" or k == "set" or k == "sublist") then -- rarely specified on aliases, as they're effectively inherited already track("mismatched alias option") end end end end end validate_alias_options(...) end -- TODO: give ranges instead of long lists, if possible. --[==[ func: export.params_list_error(params, msg) Given a key-value table of raw parameters `params`, display an error message about all the parameters seen in the table. The parameter names are displayed in sorted order. `msg` should be e.g. {"required"} or {"not used by this template"}. This is used internally to display error messages about required or invalid parameters, and can be used for the same purpose by code that processes its own parameters (e.g. if the `return_unknown` flag is specified to `process`). ]==] local function params_list_error(params, msg) local list, n = {}, 0 for name in sorted_pairs(params) do n = n + 1 list[n] = name end error(format( "Parameter%s %s.", format(n == 1 and " %s is" or "s %s are", concat_list(list, " and ", true)), msg ), 3) end export.params_list_error = params_list_error -- Helper function for use with convert_val_error(). Format a list of possible choices using `concat_list` and -- conjunction "or", displaying "either " before the choices if there's more than one. local function format_choice_list(valid) return (#valid > 1 and "either " or "") .. concat_list(valid, " or ") end -- Signal an error for a value `val` that is not of the right type `valid` (which is either a string specifying a type, or -- a list of possible values, in the case where `set` was used). `name` is the name of the parameter and can be a -- function to signal an error (which is assumed to automatically display the parameter's name and value). `seetext` is -- an optional additional explanatory link to display (e.g. [[WT:LOL]], the list of possible languages and codes). local function convert_val_error(val, name, valid, seetext) if is_callable(name) then if type(valid) == "table" then valid = "choice, must be " .. format_choice_list(valid) end name(format("Invalid %s; the value %s is not valid%s", valid, val, seetext and "; see " .. seetext or "")) else if type(valid) == "table" then valid = format_choice_list(valid) else valid = "a valid " .. valid end error(format("Parameter %s must be %s; the value %s is not valid.%s", dump(name), valid, dump(val), seetext and " See " .. seetext .. "." or "")) end end -- Generate the appropriate error-signaling function given parameter value `val` and name `name`. If `name` is already -- a function, it is just returned; otherwise a function is generated and returned that displays the passed-in messaeg -- along with the parameter's name and value. local function make_parse_err(val, name) if is_callable(name) then return name end return function(msg) error(format("%s: parameter %s=%s", msg, name, val)) end end -------------------------------------- Value conversion ----------------------------- -- For a list parameter `name` and corresponding value `list_name` of the `list` field (which should have the same value -- as `name` if `list = true` was given), generate a pattern to match parameters of the list and store the pattern as a -- key in `patterns`, with corresponding value set to `name`. For example, if `list_name` is "tr", the pattern will -- match "tr" as well as "tr1", "tr2", ..., "tr10", "tr11", etc. If the `list_name` contains a \1 in it, the numeric -- portion goes in place of the \1. For example, if `list_name` is "f\1accel", the pattern will match "faccel", -- "f1accel", "f2accel", etc. Any \1 in `name` is removed before storing into `patterns`. local function save_pattern(name, list_name, patterns) name = type(name) == "string" and gsub(name, "\1", "") or name if find(list_name, "\1", nil, true) then patterns["^" .. gsub(pattern_escape(list_name), "\1", "([1-9]%%d*)") .. "$"] = name else patterns["^" .. pattern_escape(list_name) .. "([1-9]%d*)$"] = name list_name = list_name .. "\1" end validate_name(list_name, "the list field of parameter ", name) return patterns end -- A helper function for use with `sublist`. It is an iterator function for use in a for-loop that returns split -- elements of `val` using `sublist` (a Lua split pattern; boolean `true` to split on commas optionally surrounded by -- whitespace; "comma without whitespace" to split only on commas not followed by whitespace which have not been escaped -- by a backslash; or a function to do the splitting, which is passed two values, the value to split and a function to -- signal an error, and should return a list of the split elements). `name` is the parameter name or error-signaling -- function passed into convert_val(). local function split_sublist(val, name, sublist) if sublist == true then return gsplit(val, "%s*,%s*") -- Split an argument on comma, but not comma followed by whitespace. elseif sublist == "comma without whitespace" then -- If difficult cases, use split_on_comma. if find(val, "\\", nil, true) or match(val, ",%s") then return iterate_list(split_on_comma(val)) end -- Otherwise, use gsplit. return gsplit(val, ",") elseif type(sublist) == "string" then return gsplit(val, sublist) elseif not is_callable(sublist) then error(format('Internal error: expected `sublist` to be of type "string" or "function" or boolean `true`, but saw %s', dump(sublist))) end return iterate_list(sublist(val, make_parse_err(val, name))) end --[=[ For parameter named `name` with value `val` and param spec `param`, if the `set` field is specified, verify that the value is one of the one specified in `set`, and throw an error otherwise. `name` is taken directly from the corresponding parameter passed into convert_val() and may be a function to signal an error. Optional `param_type` is a string specifying the conversion type of `val` and is used for special-casing: If `param_type` is "boolean", an internal error is thrown (since `set` cannot be used in conjunction with booleans); and if `param_type` is "number" or "boolean or string", and `called_from_converter` is not given, no checking happens because in these cases `set` needs to be called by the conversion function itself (respectively after converting `val` to a number or processing boolean strings). Return the canonical value of `val` (which may be different from `val` if an alias map is given). ]=] local function check_set(val, name, param, param_type, called_from_converter) -- Needs to be special cased because the check happens after conversion to numbers. if (param_type == "number" or param_type == "boolean or string") and not called_from_converter then return val end local set = param.set local map if sets == nil then map = finalize_set(set, name) sets = {[set] = map} else map = sets[set] if map == nil then map = finalize_set(set, name) sets[set] = map end end local newval = map[val] if newval == true then return val elseif newval ~= nil then return newval end local list = {} for k, v in sorted_pairs(map) do if v == true then insert(list, dump(k)) else insert(list, ("%s (alias of %s)"):format(dump(k), dump(v))) end end -- If the parameter type is "boolean or string", put "a boolean string value" because such values are allowed. if param_type == "boolean or string" then insert(list, "a boolean string value") end -- If the parameter is not required then put "or empty" at the end of the list, to avoid implying the parameter is -- actually required. if not param.required then insert(list, "empty") end convert_val_error(val, name, list) end local function convert_language(val, name, param, allow_etym) local method = param.method local func if method == nil or method == "code" then func, method = get_language_by_code, "code" elseif method == "name" then func, method = get_language_by_name, "name" else error(format('Internal error: expected `method` for type `language` to be "code", "name" or undefined, but saw %s', dump(method))) end local lang = func(val, nil, allow_etym, param.family) if lang then return lang end local list, links = {"language"}, {"[[WT:LOL]]"} if allow_etym then insert(list, "etymology language") insert(links, "[[WT:LOL/E]]") end if param.family then insert(list, "family") insert(links, "[[WT:LOF]]") end convert_val_error(val, name, concat_list(list, " or ") .. " " .. (method == "name" and "name" or "code"), concat_list(links, " and ")) end local function convert_number(val, allow_hex) -- Call tonumber_extended with the `real_finite` flag, which filters out ±infinity and NaN. -- By default, specify base 10, which prevents 0x hex inputs from being converted. -- If `allow_hex` is set, then don't give a base, which means 0x hex inputs will work. local num = tonumber_extended(val, not allow_hex and 10 or nil, "finite_real") if not num then return num end if match(val, "[eEpP.]") then -- float track("number not an integer") end if find(val, "+", nil, true) then track("number with +") end -- Track various unusual number inputs to determine if it should be restricted to positive integers by default (possibly including 0). if not is_positive_integer(num) then track("number not a positive integer") if num == 0 then track("number is 0") elseif not is_integer(num) then track("number not an integer") end end return num end -- TODO: validate parameter specs separately, as it's making the handler code really messy at the moment. local type_handlers = setmetatable({ ["boolean"] = function(val, name, param) local newval = yesno(val, val) if newval ~= true and newval ~= false then newval = true end return newval end, ["boolean or string"] = function(val, name, param) local newval = yesno(val, val) if newval ~= true and newval ~= false then if param.set then newval = check_set(newval, name, param, "boolean or string", "called_from_converter") else newval = true end end return newval end, ["family"] = function(val, name, param) local method = param.method local func if method == nil or method == "code" then func, method = get_family_by_code, "code" elseif method == "name" then func, method = get_family_by_name, "name" else error(format('Internal error: expected `method` for type `family` to be "code", "name" or undefined, but saw %s', dump(method))) end return func(val) or convert_val_error(val, name, "family " .. method, "[[WT:LOF]]") end, ["labels"] = function(val, name, param) -- FIXME: Should be able to pass in a parse_err function. return split_labels_on_comma(val) end, ["form of tags"] = function(val, name, param) return split_tags_on_ampersand(val) end, ["language"] = function(val, name, param) return convert_language(val, name, param, true) end, ["full language"] = convert_language, ["number"] = function(val, name, param) local allow_hex = param.allow_hex if allow_hex and allow_hex ~= true then error(format( 'Internal error: expected `allow_hex` for type `number` to be of type "boolean" or undefined, but saw %s', dump(allow_hex) )) end local num = convert_number(val, allow_hex) if param.set then num = check_set(num, name, param, "number", "called_from_converter") end if num then return num end convert_val_error(val, name, (allow_hex and "decimal or hexadecimal " or "") .. "number") end, ["range"] = function(val, name, param) local allow_hex = param.allow_hex if allow_hex and allow_hex ~= true then error(format( 'Internal error: expected `allow_hex` for type `range` to be of type "boolean" or undefined, but saw %s', dump(allow_hex) )) end -- Pattern ensures leading minus signs are accounted for. local m1, m2 = match(val, "^(%s*%S.-)%-(%s*%S.*)") if m1 then m1 = convert_number(m1, allow_hex) if m1 then m2 = convert_number(m2, allow_hex) if m2 then return {m1, m2} end end end -- Try `val` if it couldn't be split into a range, and return a range of `val` to `val` if possible. local num = convert_number(val, allow_hex) if num then return {num, num} end convert_val_error(val, name, (allow_hex and "decimal or hexadecimal " or "") .. "number or a hyphen-separated range of two numbers") end, ["parameter"] = function(val, name, param) -- Use the `no_trim` option, as any trimming will have already been done. return scribunto_parameter_key(val, true) end, ["qualifier"] = function(val, name, param) return {val} end, ["references"] = function(val, name, param) return parse_references(val, make_parse_err(val, name)) end, ["genders"] = function(val, name, param) if not val:find("[,<]") then return {{spec = val}} end -- NOTE: We don't pass in allow_space_around_comma. Consistent with other comma-separated types, there shouldn't -- be spaces around the comma. return parse_gender_and_number_spec { spec = val, parse_err = make_parse_err(val, name), allow_multiple = true, } end, ["script"] = function(val, name, param) local method, func = param.method if method == nil or method == "code" then func, method = get_script_by_code, "code" elseif method == "name" then func, method = get_script_by_name, "name" else error(format('Internal error: expected `method` for type `script` to be "code", "name" or undefined, but saw %s', dump(method))) end return func(val) or convert_val_error(val, name, "script " .. method, "[[WT:LOS]]") end, ["string"] = function(val, name, param) -- To be removed as unnecessary. track("string") return val end, -- TODO: add support for resolving to unsupported titles. -- TODO: split this into "page name" (i.e. internal) and "link target" (i.e. external as well), which is more intuitive. ["title"] = function(val, name, param) local namespace = param.namespace if namespace == nil then namespace = 0 else local valid_type = type(namespace) ~= "number" and 'of type "number" or undefined' or not namespaces[namespace] and "a valid namespace number" or nil if valid_type then error(format('Internal error: expected `namespace` for type `title` to be %s, but saw %s', valid_type, dump(namespace))) end end -- Decode entities. WARNING: mw.title.makeTitle must be called with `decoded` (as it doesn't decode) and mw.title.new must be called with `val` (as it does decode, so double-decoding needs to be avoided). local decoded, prefix, title = decode_entities(val), param.prefix -- If the input is a fragment, treat the title as the current title with the input fragment. if sub(decoded, 1, 1) == "#" then -- If prefix is "force", only get the current title if it's in the specified namespace. current_title includes the namespace prefix. if current_namespace == nil then local current_title = mw_title.getCurrentTitle() current_title_text, current_namespace = current_title.prefixedText, current_title.namespace end if not (prefix == "force" and namespace ~= current_namespace) then title = new_title(current_title_text .. val) end elseif prefix == "force" then -- Unconditionally add the namespace prefix (mw.title.makeTitle). title = make_title(namespace, decoded) elseif prefix == "full override" then -- The first input prefix will be used as an override (mw.title.new). This can be a namespace or interwiki prefix. title = new_title(val, namespace) elseif prefix == nil or prefix == "namespace override" then -- Only allow namespace prefixes to override. Interwiki prefixes therefore need to be treated as plaintext (e.g. "el:All topics" with namespace 14 returns "el:Category:All topics", but we want "Category:el:All topics" instead; if the former is really needed, then the input ":el:Category:All topics" will work, as the initial colon overrides the namespace). mw.title.new can take namespace names as well as numbers in the second argument, and will throw an error if the input isn't a valid namespace, so this can be used to determine if a prefix is for a namespace, since mw.title.new will return successfully only if there's either no prefix or the prefix is for a valid namespace (in which case we want the override). local success success, title = pcall(new_title, val, match(decoded, "^.-%f[:]") or namespace) -- Otherwise, get the title with mw.title.makeTitle, which unconditionally adds the namespace prefix, but behaves like mw.title.new if the namespace is 0. if not success then title = make_title(namespace, decoded) end else error(format('Internal error: expected `prefix` for type `title` to be "force", "full override", "namespace override" or undefined, but saw %s', dump(prefix))) end local allow_external = param.allow_external if allow_external == true then return title or convert_val_error(val, name, "Wiktionary or external page title") elseif not allow_external then return title and is_internal_title(title) and title or convert_val_error(val, name, "Wiktionary page title") end error(format('Internal error: expected `allow_external` for type `title` to be of type "boolean" or undefined, but saw %s', dump(allow_external))) end, ["Wikimedia language"] = function(val, name, param) local fallback = param.fallback if fallback == true then return get_wm_lang_by_code_with_fallback(val) or convert_val_error(val, name, "Wikimedia language or language code") elseif not fallback then return get_wm_lang_by_code(val) or convert_val_error(val, name, "Wikimedia language code") end error(format('Internal error: expected `fallback` for type `Wikimedia language` to be of type "boolean" or undefined, but saw %s', dump(fallback))) end, }, { -- TODO: decode HTML entities in all input values. Non-trivial to implement, because we need to avoid any downstream -- functions decoding the output from this module, which would be double-decoding. Note that "title" has this -- implemented already, and it needs to have both the raw input and the decoded input to avoid double-decoding by -- mw.title.new, so any implementation can't be as simple as decoding in __call then passing the result to the -- handler. __call = function(self, val, name, param, param_type, default) local val_type = type(val) -- TODO: check this for all possible parameter types. if val_type == param_type or val_type == "boolean" and param_type == "boolean or string" then return val elseif val_type ~= "string" then local expected = "string" if default then if param_type == "boolean or string" then expected = param_type elseif param_type == "boolean" or param_type == "number" then expected = param_type .. " or " .. expected end end error(format( "Internal error: %sargument %s has the type %s; expected a %s.", default and (default .. " for ") or "", name, dump(val_type), expected )) end local func = self[param_type] if func == nil then error(format("Internal error: %s is not a recognized parameter type.", dump(param_type))) end return func(val, name, param) end }) --[==[ func: export.convert_val(val, name, param) Convert a parameter value according to the associated specs listed in the `params` table passed to [[Module:parameters]]. `val` is the value to convert for a parameter whose name is `name` (used only in error messages). `param` is the spec (the value part of the `params` table for the parameter). In place of passing in the parameter name, `name` can be a function that throws an error, displaying the specified message along with the parameter name and value. This function processes all the conversion-related fields in `param`, including `type`, `set`, `sublist`, `convert`, etc. It returns the converted value. ]==] local function convert_val(val, name, param, default) local param_type = param.type or "string" -- If param.type is a function, resolve it to a recognized type. if is_callable(param_type) then param_type = param_type(val) end local convert, sublist = param.convert, param.sublist -- `val` might not be a string if it's the default value. if sublist and type(val) == "string" then local retlist, set = {}, param.set if convert then local thisindex = 0 local thisval, insval, parse_err if is_callable(name) then -- We assume the passed-in error function in `name` already shows the parameter name and raw value. function parse_err(msg) name(format("%s: item #%s=%s", msg_with_processed(msg, thisval, insval), thisindex, thisval) ) end else function parse_err(msg) error(format("%s: item #%s=%s of parameter %s=%s", msg_with_processed(msg, thisval, insval), thisindex, thisval, name, val) ) end end for v in split_sublist(val, name, sublist) do thisindex, thisval = thisindex + 1, v if set then v = check_set(v, name, param, param_type) end insval = v insert(retlist, convert(type_handlers(v, name, param, param_type, default), parse_err)) end else for v in split_sublist(val, name, sublist) do if set then v = check_set(v, name, param, param_type) end insert(retlist, type_handlers(v, name, param, param_type, default)) end end return retlist elseif param.set then val = check_set(val, name, param, param_type) end local retval = type_handlers(val, name, param, param_type, default) if convert then local parse_err if is_callable(name) then -- We assume the passed-in error function in `name` already shows the parameter name and raw value. if retval == val then -- This is an optimization to avoid creating a closure. The second arm works correctly even -- when retval == val. parse_err = name else function parse_err(msg) name(msg_with_processed(msg, val, retval)) end end else function parse_err(msg) error(format("%s: parameter %s=%s", msg_with_processed(msg, val, retval), name, val)) end end retval = convert(retval, parse_err) end -- If `sublist` is set but the input wasn't a string, return `retval` as a one-item list. if sublist then retval = {retval} end return retval end export.convert_val = convert_val -- used by [[Module:parameter utilities]] local function unknown_param(name, val, args_unknown) track("unknown parameters") args_unknown[name] = val return args_unknown end local function check_string_param_modifier(param_type, name, tag) if param_type and not (param_type == "string" or param_type == "parameter" or is_callable(param_type)) then internal_process_error( "%s cannot be set unless %s is set to %s (the default), %s or a function: parameter %s has the type %s.", tag, "type", "string", "parameter", name, param_type ) end end local function hole_error(params, name, listname, this, nxt, extra) -- `process_error` calls `dump` on values to be inserted into -- error messages, but with numeric lists this causes "numeric" -- to look like the name of the list rather than a description, -- as `dump` adds quote marks. Insert it early to avoid this, -- but add another %s specifier in all other cases, so that -- actual list names will be displayed properly. local offset, specifier, starting_from = 0, "%s", "" local msg = "Item %%d in the list of %s parameters must be given if item %%d is given, because %sthere shouldn't be any gaps due to missing%s parameters." local specs = {} if type(listname) == "string" then specs[2] = listname elseif type(name) == "number" then offset = name - 1 -- To get the original parameter. specifier = "numeric" -- If the list doesn't start at parameter 1, avoid implying -- there can't be any gaps in the numeric parameters if -- some parameter with a lower key is optional. for j = name - 1, 1, -1 do local _param = params[j] if not (_param and _param.required) then starting_from = format("(starting from parameter %d) ", dump(j + 1)) break end end else specs[2] = name end specs[1] = this + offset -- Absolute index for this item. insert(specs, nxt + offset) -- Absolute index for the next item. process_error(format(msg, specifier, starting_from, extra or ""), unpack(specs)) end local function check_disallow_holes(params, val, name, listname, extra) for i = 1, val.maxindex do if val[i] == nil then hole_error(params, name, listname, i, num_keys(val)[i], extra) end end end local function handle_holes(params, val, name) local param = params[name] local disallow_holes = param.disallow_holes -- Iterate up the list, and throw an error if a hole is found. if disallow_holes then check_disallow_holes(params, val, name, param.list, " or empty") end -- Iterate up the list, and throw an error if a hole is found due to a -- missing parameter, treating empty parameters as part of the list. This -- applies beyond maxindex if blank arguments are supplied beyond it, so -- isn't mutually exclusive with `disallow_holes`. local empty = val.empty if param.disallow_missing then if empty then -- Remove `empty` from `val`, so it doesn't get returned. val.empty = nil for i = 1, max(val.maxindex, empty.maxindex) do if val[i] == nil and not empty[i] then local keys = extend(num_keys(val), num_keys(empty)) sort(keys) hole_error(params, name, param.list, i, keys[i]) end end -- If there's no table of empty parameters, the check is identical to -- `disallow_holes`, except that the error message only refers to -- missing parameters, not missing or empty ones. If `disallow_holes` is -- also set, there's no point checking again. elseif not disallow_holes then check_disallow_holes(params, val, name, param.list) end end -- If `allow_holes` is set, there's nothing left to do. if param.allow_holes then -- do nothing -- Otherwise, remove any holes: `pairs` won't work, as it's unsorted, and -- iterating from 1 to `maxindex` times out with inputs like |100000000000=, -- so use num_keys to get a list of numerical keys sorted from lowest to -- highest, then iterate up the list, moving each value in `val` to the -- lowest unused positive integer key. This also avoids the need to create a -- new table. If `disallow_holes` is specified, then there can't be any -- holes in the list, so there's no reason to check again; this doesn't -- apply to `disallow_missing`, however. else if not disallow_holes then local keys, i = num_keys(val), 0 while true do i = i + 1 local key = keys[i] if key == nil then break elseif i ~= key then track("holes compressed") val[i], val[key] = val[key], nil end end end -- Some code depends on only numeric params being present when no holes are -- allowed (e.g. by checking for the presence of arguments using next()), so -- remove `maxindex`. val.maxindex = nil end end local function maybe_flatten(params, val, name) local param = params[name] if param.flatten then if param.allow_holes then process_error("For parameter %s, can't set both `allow_holes` and `flatten`", name) end if not param.sublist and param.type ~= "genders" and param.type ~= "labels" and param.type ~= "references" and param.type ~= "qualifier" and param.type ~= "form of tags" then process_error("For parameter %s, can only set `flatten` along with `sublist` or a list-generating type", name) end -- Do the flattening ourselves rather than calling flatten() in [[Module:table]], which will attempt to -- flatten non-list objects like title objects, and cause an error in the process. -- FIXME: We should do this in-place if possible. local newlist = {} for _, sublist in ipairs(val) do for _, item in ipairs(sublist) do insert(newlist, item) end end val = newlist end return val end -- If both `template_default` and `default` are given, `template_default` takes precedence, but only on the template or -- module page. This means a different default can be specified for the template or module page example. However, -- `template_default` doesn't apply if any args are set, which helps (somewhat) with examples on documentation pages -- transcluded into the template page. HACK: We still run into problems on documentation pages transcluded into the -- template page when pagename= is set. Check this on the assumption that pagename= is fairly standard. local function convert_default_val(name, param, pagename_set, any_args_set, add_empty_sublist) if not pagename_set then local val = param.template_default if val ~= nil and not any_args_set and is_own_page() then return convert_val(val, name, param, "template default") end end local val = param.default if val ~= nil then return convert_val(val, name, param, "default") -- Sublist parameters should return an empty table if not given, but only do -- this if the parameter isn't also a list (in which case it will already -- be an empty table). -- FIXME: do this once all modules that pass in a sublist parameter treat an empty sublist identically to a nil argument; some currently do things based on the fact an argument exists at all. -- elseif add_empty_sublist and param.sublist then --return {} end end --[==[ Process arguments with a given list of parameters. Return a table containing the processed arguments. The `args` parameter specifies the arguments to be processed; they are the arguments you might retrieve from {frame:getParent().args} (the template arguments) or in some cases {frame.args} (the invocation arguments). The `params` parameter specifies a list of valid parameters, and consists of a table. If an argument is encountered that is not in the parameter table, an error is thrown. The structure of the `params` table is as described above in the intro comment. '''WARNING:''' The `params` table is destructively modified to save memory. Nonetheless, different keys can share the same value objects in memory without causing problems. The `return_unknown` parameter, if set to {true}, prevents the function from triggering an error when it comes across an argument with a name that it doesn't recognise. Instead, the return value is a pair of values: the first is the processed arguments as usual, while the second contains all the unrecognised arguments that were left unprocessed. This allows you to do multi-stage processing, where the entire set of arguments that a template should accept is not known at once. For example, an inflection-table might do some generic processing on some arguments, but then defer processing of the remainder to the function that handles a specific inflectional type. ]==] function export.process(args, params, return_unknown) -- Process parameters for specific properties local args_new = {} local args_unknown, any_args_set, required, patterns, list_args, index_list, args_placeholders, placeholders_n -- TODO: memoize the processing of each unique `param` value, since it's common for the same value to be used for -- many parameter names. for name, param in pairs(params) do validate_name(name, "parameter names") if param ~= true then local spec_type = type(param) if type(param) ~= "table" then internal_process_error( "spec for parameter %s must be a table of specs or the value true, but found %s.", name, spec_type ~= "boolean" and spec_type or param ) end -- Populate required table, and make sure aliases aren't set to required. if param.required then if required == nil then required = {} end required[name] = true end local listname, alias_of = param.list, param.alias_of if alias_of then validate_name(alias_of, "the alias_of field of parameter ", name) if alias_of == name then internal_process_error( "parameter %s cannot be an alias of itself.", name ) end local main_param = params[alias_of] -- Check that the alias_of is set to a valid parameter. if not (main_param == true or type(main_param) == "table") then internal_process_error( "parameter %s is an alias of an invalid parameter.", name ) end validate_alias_options(param, name, main_param, alias_of) -- Aliases can't be lists unless the canonical parameter is also a list. if listname and (main_param == true or not main_param.list) then internal_process_error( "list parameter %s is set as an alias of %s, which is not a list parameter.", name, alias_of ) -- Can't be an alias of an alias. elseif main_param ~= true then local main_alias_of = main_param.alias_of if main_alias_of ~= nil then internal_process_error( "alias_of cannot be set to another alias: parameter %s is set as an alias of %s, which is in turn an alias of %s. Set alias_of for %s to %s.", name, alias_of, main_alias_of, name, main_alias_of ) end end end local replaced_by = param.replaced_by if replaced_by then -- replaced_by can be `false`, which is OK validate_name(replaced_by, "the replaced_by field of parameter ", name) if replaced_by == name then internal_process_error( "parameter %s cannot be replaced by itself.", name ) end local main_param = params[replaced_by] -- Check that the replaced_by is set to a valid parameter. if not (main_param == true or type(main_param) == "table") then internal_process_error( "parameter %s is set to be replaced by an invalid parameter.", name ) end -- Can't be a replaced-by of a replaced-by. if main_param ~= true then local main_replaced_by = main_param.replaced_by if main_replaced_by ~= nil then internal_process_error( "replaced_by cannot be set to another replaced-by parameter: parameter %s is set as replaced by %s, which is in turn replaced by %s. Set replaced_by for %s to %s.", name, replaced_by, main_replaced_by, name, main_replaced_by ) end end if param.instead ~= nil then internal_process_error("the `instead` tag can only be given when `replaced_by` is set to `false`.") end elseif replaced_by == false then if param.instead ~= nil and type(param.instead) ~= "string" then internal_process_error( "the `instead` tag must be a string, but saw %s.", param.instead ) end end if replaced_by ~= nil then if param.reason ~= nil and type(param.reason) ~= "string" then internal_process_error( "the `reason` tag must be a string, but saw %s.", param.reason ) end end if listname then if not alias_of then local key = name if type(name) == "string" then key = gsub(name, "\1", "") end local list_arg = {maxindex = 0} args_new[key] = list_arg if list_args == nil then list_args = {} end list_args[key] = list_arg end local list_type = type(listname) if list_type == "string" then -- If the list property is a string, then it represents the name -- to be used as the prefix for list items. This is for use with lists -- where the first item is a numbered parameter and the -- subsequent ones are named, such as 1, pl2, pl3. patterns = save_pattern(name, listname, patterns or {}) elseif listname ~= true then internal_process_error( "list field for parameter %s must be a boolean, string or undefined, but saw a %s.", name, list_type ) elseif type(name) == "number" then if index_list ~= nil then internal_process_error( "only one numeric parameter can be a list, unless the list property is a string." ) end -- If the name is a number, then all indexed parameters from -- this number onwards go in the list. index_list = name else patterns = save_pattern(name, name, patterns or {}) end if find(name, "\1", nil, true) then if args_placeholders then placeholders_n = placeholders_n + 1 args_placeholders[placeholders_n] = name else args_placeholders, placeholders_n = {name}, 1 end end end end end --Process required changes to `params`. if args_placeholders then for i = 1, placeholders_n do local name = args_placeholders[i] params[gsub(name, "\1", "")], params[name] = params[name], nil end end -- Process the arguments for name, val in pairs(args) do any_args_set = true validate_name(name, "argument names", nil, true) -- Guaranteeing that all values are strings avoids issues with type coercion being inconsistent between functions. local val_type = type(val) if val_type ~= "string" then internal_process_error( "argument %s has the type %s; all arguments must be strings.", name, val_type ) end local orig_name, raw_type = name, type(name) local index, canonical if raw_type == "number" then if index_list and name >= index_list then index = name - index_list + 1 name = index_list end elseif patterns then -- Does this argument name match a pattern? for pattern, pname in next, patterns do index = match(name, pattern) -- It matches, so store the parameter name and the -- numeric index extracted from the argument name. if index then index = tonumber(index) name = pname break end end end local param = params[name] -- If the argument is not in the list of parameters, store it in a separate list. if not param then args_unknown = unknown_param(name, val, args_unknown or {}) elseif param == true then canonical = orig_name val = php_trim(val) if val ~= "" then -- If the parameter is duplicated, throw an error. if args_new[name] ~= nil then process_error( "Parameter %s has been entered more than once. This is probably because a parameter alias has been used.", canonical ) end args_new[name] = val end else if param.replaced_by == false then process_error( ("Parameter %%s has been removed and is no longer valid%s.%s"):format( param.reason and ", " .. param.reason or "", param.instead and " Instead, " .. param.instead .. "." or ""), name ) elseif param.replaced_by then process_error( ("Parameter %%s has been replaced by %%s%s."):format( param.reason and ", " .. param.reason or ""), name, param.replaced_by ) end if param.deprecated then track("deprecated parameter", name) end if param.require_index then -- Disallow require_index for numeric parameter names, as this doesn't make sense. if raw_type == "number" then internal_process_error( "cannot set require_index for numeric parameter %s.", name ) -- If a parameter without the trailing index was found, and -- require_index is set on the param, treat it -- as if it isn't recognized. elseif not index then args_unknown = unknown_param(name, val, args_unknown or {}) end end -- Check that separate_no_index is not being used with a numeric parameter. if param.separate_no_index then if raw_type == "number" then internal_process_error( "cannot set separate_no_index for numeric parameter %s.", name ) elseif type(param.alias_of) == "number" then internal_process_error( "cannot set separate_no_index for parameter %s, as it is an alias of numeric parameter %s.", name, param.alias_of ) end end -- If no index was found, use 1 as the default index. -- This makes list parameters like g, g2, g3 put g at index 1. -- If `separate_no_index` is set, then use 0 as the default instead. if not index and param.list then index = param.separate_no_index and 0 or 1 end -- Normalize to the canonical parameter name. If it's a list, but the alias is not, then determine the index. local raw_name = param.alias_of if raw_name then raw_type = type(raw_name) if raw_type == "number" then name = raw_name local main_param = params[raw_name] if main_param ~= true and main_param.list then if not index then index = param.separate_no_index and 0 or 1 end canonical = raw_name + index - 1 else canonical = raw_name end else name = gsub(raw_name, "\1", "") local main_param = params[name] if not index and main_param ~= true and main_param.list then index = param.separate_no_index and 0 or 1 end if not index or index == 0 then canonical = name elseif name == raw_name then canonical = name .. index else canonical = gsub(raw_name, "\1", index) end end else canonical = orig_name end -- Only recognize demo parameters if this is the current template or module's -- page, or its documentation page. if param.demo and not is_own_page("include_documentation") then args_unknown = unknown_param(name, val, args_unknown or {}) end -- Remove leading and trailing whitespace unless no_trim is true. if param.no_trim then check_string_param_modifier(param.type, name, "no_trim") else val = php_trim(val) end -- Empty string is equivalent to nil unless allow_empty is true. if param.allow_empty then check_string_param_modifier(param.type, name, "allow_empty") elseif val == "" then -- If `disallow_missing` is set, keep track of empty parameters -- via the `empty` field in `arg`, which will be used by the -- `disallow_missing` check. This will be deleted before -- returning. if index and param.disallow_missing then local arg = args_new[name] local empty = arg.empty if empty == nil then empty = {maxindex = 0} arg.empty = empty end empty[index] = true if index > empty.maxindex then empty.maxindex = index end end val = nil end -- Allow boolean false. if val ~= nil then -- Convert to proper type if necessary. local main_param = params[raw_name] if main_param ~= true then val = convert_val(val, orig_name, main_param or param) end -- Mark it as no longer required, as it is present. if required then required[name] = nil end -- Store the argument value. if index then local arg = args_new[name] -- If the parameter is duplicated, throw an error. if arg[index] ~= nil then process_error( "Parameter %s has been entered more than once. This is probably because a list parameter has been entered without an index and with index 1 at the same time, or because a parameter alias has been used.", canonical ) end arg[index] = val -- Store the highest index we find. local maxindex = arg.maxindex if index > maxindex then maxindex = index end if arg[0] ~= nil then arg.default, arg[0] = arg[0], nil if maxindex < 1 then maxindex = 1 end end arg.maxindex = maxindex if not params[name].list then args_new[name] = val -- Don't store index 0, as it's a proxy for the default. elseif index > 0 then arg[index] = val end else -- If the parameter is duplicated, throw an error. if args_new[name] ~= nil then process_error( "Parameter %s has been entered more than once. This is probably because a parameter alias has been used.", canonical ) end if not raw_name then args_new[name] = val else local main_param = params[raw_name] if main_param ~= true and main_param.list then local main_arg = args_new[raw_name] main_arg[1] = val -- Store the highest index we find. if main_arg.maxindex < 1 then main_arg.maxindex = 1 end else args_new[raw_name] = val end end end end end end -- Remove holes in any list parameters if needed. This must be handled -- straight after the previous loop, as any instances of `empty` need to be -- converted to nil. if list_args then for name, val in next, list_args do handle_holes(params, val, name) end end -- If the current page is the template which invoked this Lua instance, then ignore the `require` flag, as it -- means we're viewing the template directly. Required parameters sometimes have a `template_default` key set, -- which gets used in such cases as a demo. -- Note: this won't work on other pages in the Template: namespace (including the /documentation subpage), -- or if the #invoke: is on a page in another namespace. local pagename_set = args_new.pagename -- Handle defaults. for name, param in pairs(params) do if param ~= true then local arg_new = args_new[name] if arg_new == nil then args_new[name] = convert_default_val(name, param, pagename_set, any_args_set, true) elseif param.list and arg_new[1] == nil then local default_val = convert_default_val(name, param, pagename_set, any_args_set) if default_val ~= nil then arg_new[1] = default_val if arg_new.maxindex == 0 then arg_new.maxindex = 1 end end end end end -- Flatten nested lists if called for. This must come after setting the default. if list_args then for name, val in next, list_args do args_new[name] = maybe_flatten(params, val, name) end end -- The required table should now be empty. -- If any parameters remain, throw an error, unless we're on the current template or module's page. if required and next(required) ~= nil and not is_own_page() then params_list_error(required, "required") -- Return the arguments table. -- If there are any unknown parameters, throw an error, unless return_unknown is set, in which case return args_unknown as a second return value. elseif return_unknown then return args_new, args_unknown or {} elseif args_unknown and next(args_unknown) ~= nil then params_list_error(args_unknown, "not used by this template") end return args_new end return export n22wbg166a0hooxofspixrredxb8pb5 User:Xiplus/沙盒 2 1367001 9943053 7272593 2026-10-12T15:48:08Z Xiplus 25402 请求快速删除([[Wiktionary:CSD|CSD G1]]) 9943053 wikitext text/x-wiki {{delete|g1}} {{al|安撫良心|安電燈|安靜下來|安排勞力|安排人力|安全關閉計算機|安置鎖定區域|安排好|安排膳宿|安慰之物|安全操作保護措施|安裝底座|安裝橡皮布|安裝打印機|安排里|保持離岸安全距離|保安弱點|本地安全認證子系統服務|巴爾幹半島國家|巴氏早期癌變探查試驗}} rqxov6apqj2men3vzc3rhazob8tfjtn 9943054 9943053 2026-10-12T15:48:18Z Xiplus 25402 回退到由[[Special:Contributions/Xiplus|Xiplus]]([[User talk:Xiplus|討論]])做出的修訂版本[[Special:PermaLink/7272593|7272593]] 9943054 wikitext text/x-wiki {{al|安撫良心|安電燈|安靜下來|安排勞力|安排人力|安全關閉計算機|安置鎖定區域|安排好|安排膳宿|安慰之物|安全操作保護措施|安裝底座|安裝橡皮布|安裝打印機|安排里|保持離岸安全距離|保安弱點|本地安全認證子系統服務|巴爾幹半島國家|巴氏早期癌變探查試驗}} 5rgfxo7uqxs5l1t7gvjmqrnp7pkw3q0 MediaWiki:Gadget-morebits.css 8 1368856 9943038 5917445 2026-10-12T15:40:48Z Xiplus 25402 Repo at 375b1e1: morebits: stop relying on .toccolours (not available in vector-2022) 9943038 css text/css /** * vim: set noet sts=0 sw=8: * morebits.css * =========== * Styles to support morebits.js. * * The morebits library is maintained by the maintainers of Twinkle. * For queries, suggestions, help, etc., head to [[WT:TW]]. * The latest development source is available at [https://github.com/azatoth/twinkle/blob/master/morebits.css]. */ /* Morebits.status */ .morebits_status_status { color: #4682B4; } .morebits_status_info { color: #228B22; } .morebits_status_warn { color: #FF4500; } .morebits_status_error { color: #FF4500; font-weight: bold; } /* Morebits.quickForm */ form.quickform { width: 96%; vertical-align: middle; margin: auto; padding: .5em; } form.quickform * { font-family: sans-serif; } form.quickform fieldset { margin: .4em 0 1em; } form.quickform legend { color: #31628F; font-weight: bold; } form.quickform input[type=text], form.quickform select { min-width: 15em; font-size: 110%; } form.quickform select { border: 1px solid gray; margin-left: .2em; } form.quickform input[type=checkbox], form.quickform input[type=radio] { height: 13px; margin-top: 2px; margin-right: 2px; margin-bottom: 2px; padding: 0; width: 13px; vertical-align: top; } form.quickform div { line-height: 18px; } form.quickform h5 { margin: .5em 0 0; padding: .3em .2em .2em; } /* only give the top border to headers with something above them */ form.quickform div + h5, form.quickform div + div > h5, form.quickform h5 + h5 { border-top: 1px solid #88A; } form.quickform textarea { width: 100%; height: 4em; font-size: 150%; } form.quickform input:disabled + label { color: gray; } form.quickform span.quickformDescription { font-style: italic; } form.quickform .quickformSubgroup { margin-bottom: .5em; margin-left: 3em; } form.quickform .tooltipButtonContainer { position: relative; width: 100%; } form.quickform .tooltipButton { color: blue; font-weight: bold; cursor: help; padding: .2em; } /* Scrollbox styles, for use within Morebits.simpleWindow */ div.morebits-scrollbox { background: white; border: 1px solid gray; margin-bottom: .6em; margin-top: .6em; max-height: 20em; overflow: auto; padding: 6px 6px 0; } div.morebits-scrollbox > h5:first-child { border: none; margin-top: 0; padding-top: 0; } div.morebits-scrollbox > :last-child { margin-bottom: 6px; } /* Previewbox */ div.morebits-previewbox { background: white; color: black; border: 2px inset; margin: .4em auto .2em; padding: .2em .4em; } div.morebits-previewbox *:not(img) { vertical-align: baseline; } div.morebits-previewbox .mw-editsection { display: none; } div.morebits-usertext { border: 1px solid #a2a9b1; background-color: #f8f9fa; padding: 5px; font-size: 95%; } /* Portlet */ .skin-modern #mw_contentwrapper div.portlet { overflow: hidden; height: 1.5em; margin: 0 0 0 14em; padding: 0; } .skin-modern #mw_contentwrapper div.portlet h5 { display: none; } .skin-modern #mw_contentwrapper div.portlet div.pbody { margin: 0; padding: 0; } .skin-modern #mw_contentwrapper div.portlet div.pbody ul { display: inline; margin: 0; } .skin-modern #mw_contentwrapper div.portlet div.pbody ul li { display: block; float: left; height: 1.5em; margin: 0 .5em; padding: 0 .2em; text-transform: lowercase; } .skin-modern #mw_contentwrapper div.portlet div.pbody ul li a { text-decoration: underline; } .skin-modern #mw_contentwrapper div.portlet div.pbody ul li.selected a { text-decoration: none; } /* Morebits.simpleWindow */ .morebits-dialog { border: 1px #666 solid; font: small sans-serif; background-color: #F0F8FF !important; background-image: none !important; } body.skin-monobook .morebits-dialog { font-size: 125%; } body .ui-dialog.morebits-dialog .ui-dialog-titlebar { height: 1em; background: repeat-x 50% 80% #cfd6e1 !important; /* the actual image is specified in morebits.js - ResourceLoader mangles data: URIs in CSS */ font: bold 1em sans-serif; overflow: hidden; padding: .4em .3em .5em !important; white-space: nowrap; } body.skin-monobook .morebits-dialog .ui-dialog-titlebar { line-height: 1em; } .morebits-dialog-scriptname { font-weight: normal; } .ui-dialog.morebits-dialog .ui-dialog-titlebar-close { height: 100%; right: 0; top: auto; width: 2em; margin: -.5em -.15em 0; } .ui-dialog.morebits-dialog .ui-dialog-titlebar-close span { margin: .33em; } .ui-dialog.morebits-dialog .morebits-dialog-content { font-size: 88%; /* this just seems to be traditional - I don't quite see the point, in this day and age of huge monitors, etc. */ padding: 0; } body .ui-dialog.morebits-dialog .ui-dialog-buttonpane { background-color: #BCCADF; margin: 0; min-height: .5em; padding-left: 1.2em !important; } body .ui-dialog.morebits-dialog .ui-dialog-buttonpane button { float: none; margin: .2em 0 -.1em; cursor: auto; } .morebits-dialog-footerlinks { font-size: 90%; float: right; margin: .7em .4em 0 0; } body .ui-dialog.morebits-dialog .morebits-dialog-footerlinks a { color: #3062AD; } .morebits-dialog-buttons[data-empty] + .morebits-dialog-footerlinks { margin: .1em .4em -.2em 0; } .ui-dialog.morebits-dialog a, .ui-dialog.morebits-dialog .ui-widget-content a { color: #0645AD; /* jQuery imposes a ridiculous nearly-black colour on <a> tags... I don't understand it */ } .ui-icon { vertical-align: -3px; } .ui-icon-inline { display: inline-block; } /* For styling message/warning boxes. Cannot use MediaWiki ombox class because it will be deprecated soon. */ table.morebits-ombox { margin: 4px 10%; border-collapse: collapse; border: 1px solid #a2a9b1; background: #f8f9fa; box-sizing: border-box; } table.morebits-ombox-content { border: 1px solid #f28500; } td.morebits-mbox-image { border: none; padding: 2px 0 2px 0.9em; text-align: center; } th.morebits-mbox-text, td.morebits-mbox-text { border: none; padding: 0.25em 0.9em; width: 100%; } 2qocl8qp5xee07gg00jnicg6qhktu34 MediaWiki:Gadget-morebits.js 8 1368857 9943037 6328853 2026-10-12T15:40:32Z Xiplus 25402 Repo at 375b1e1: create capitalized aliases for classes; Add hidden attr to option; stop relying on .toccolours (not available in vector-2022); fix handling of hard errors; load RL modules required for previews; Update from upstream (#270); Fix commentOutImage for multiple files with same parameters; Use wgUserLanguage for preview; Update daysShort 9943037 javascript text/javascript // <nowiki> /** * A library full of lots of goodness for user scripts on MediaWiki wikis, including Wikipedia. * * The highlights include: * - {@link Morebits.wiki.api} - make calls to the MediaWiki API * - {@link Morebits.wiki.page} - modify pages on the wiki (edit, revert, delete, etc.) * - {@link Morebits.date} - enhanced date object processing, sort of a light moment.js * - {@link Morebits.quickForm} - generate quick HTML forms on the fly * - {@link Morebits.simpleWindow} - a wrapper for jQuery UI Dialog with a custom look and extra features * - {@link Morebits.status} - a rough-and-ready status message displayer, used by the Morebits.wiki classes * - {@link Morebits.wikitext} - utilities for dealing with wikitext * - {@link Morebits.string} - utilities for manipulating strings * - {@link Morebits.array} - utilities for manipulating arrays * - {@link Morebits.ip} - utilities to help process IP addresses * * Dependencies: * - The whole thing relies on jQuery. But most wikis should provide this by default. * - {@link Morebits.quickForm}, {@link Morebits.simpleWindow}, and {@link Morebits.status} rely on the "morebits.css" file for their styling. * - {@link Morebits.simpleWindow} and {@link Morebits.quickForm} tooltips rely on jQuery UI Dialog (from ResourceLoader module name 'jquery.ui'). * - To create a gadget based on morebits.js, use this syntax in MediaWiki:Gadgets-definition: * - `*GadgetName[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,mediawiki.Title,jquery.ui]|morebits.js|morebits.css|GadgetName.js` * - Alternatively, you can configure morebits.js as a hidden gadget in MediaWiki:Gadgets-definition: * - `*morebits[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,mediawiki.Title,jquery.ui|hidden]|morebits.js|morebits.css` * and then load ext.gadget.morebits as one of the dependencies for the new gadget. * * All the stuff here works on all browsers for which MediaWiki provides JavaScript support. * * This library is maintained by the maintainers of Twinkle. * For queries, suggestions, help, etc., head to [Wikipedia talk:Twinkle on English Wikipedia](http://en.wikipedia.org/wiki/WT:TW). * The latest development source is available at {@link https://github.com/wikimedia-gadgets/twinkle/blob/master/morebits.js|GitHub}. * * @namespace Morebits */ (function (window, document, $) { // Wrap entire file with anonymous function // MediaWiki:Gadget-site-lib.js window.wgUXS = function (wg, hans, hant, cn, tw, hk, sg, zh, mo, my) { var ret = { 'zh': zh || hans || hant || cn || tw || hk || sg || mo || my, 'zh-hans': hans || cn || sg || my, 'zh-hant': hant || tw || hk || mo, 'zh-cn': cn || hans || sg || my, 'zh-sg': sg || hans || cn || my, 'zh-tw': tw || hant || hk || mo, 'zh-hk': hk || hant || mo || tw, 'zh-mo': mo || hant || hk || tw }; return ret[wg] || zh || hans || hant || cn || tw || hk || sg || mo || my; // 保證每一語言有值 }; window.wgULS = function (hans, hant, cn, tw, hk, sg, zh, mo, my) { return wgUXS(mw.config.get('wgUserLanguage'), hans, hant, cn, tw, hk, sg, zh, mo, my); // eslint-disable-line no-undef }; window.wgUVS = function (hans, hant, cn, tw, hk, sg, zh, mo, my) { return wgUXS(mw.config.get('wgUserVariant'), hans, hant, cn, tw, hk, sg, zh, mo, my); // eslint-disable-line no-undef }; /** @lends Morebits */ var Morebits = {}; window.Morebits = Morebits; // allow global access /** * Wiki-specific configurations for Morebits */ Morebits.l10n = { /** * Local aliases for "redirect" magic word. * Check using api.php?action=query&format=json&meta=siteinfo&formatversion=2&siprop=magicwords */ redirectTagAliases: ['#REDIRECT', '#重定向'], /** * Takes a string as argument and checks if it is a timestamp or not * If not, it returns null. If yes, it returns an array of integers * in the format [year, month, date, hour, minute, second] * which can be passed to Date.UTC() * @param {string} str * @returns {number[] | null} */ signatureTimestampFormat: function (str) { // YYYY年Month月DD日 (w) HH:mm (UTC) var rgx = /(\d{4})年(\d{1,2})月(\d{1,2})日 \(.\) (\d{2}):(\d{2}) \(UTC\)/; var match = rgx.exec(str); if (!match) { return null; } // ..... year .... month ... date .... hour ... minute return [match[1], match[2] - 1, match[3], match[4], match[5]]; } }; /** * Simple helper function to see what groups a user might belong. * * @param {string} group - e.g. `sysop`, `extendedconfirmed`, etc. * @returns {boolean} */ Morebits.userIsInGroup = function (group) { return mw.config.get('wgUserGroups').indexOf(group) !== -1; }; /** Hardcodes whether the user is a sysop, used a lot. * * @constant * @type {boolean} */ Morebits.userIsSysop = Morebits.userIsInGroup('sysop'); /** * Deprecated as of February 2021, use {@link Morebits.ip.sanitizeIPv6}. * * @deprecated Use {@link Morebits.ip.sanitizeIPv6}. * Converts an IPv6 address to the canonical form stored and used by MediaWiki. * JavaScript translation of the {@link https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/8eb6ac3e84ea3312d391ca96c12c49e3ad0753bb/includes/utils/IP.php#131|`IP::sanitizeIP()`} * function from the IPUtils library. Adddresses are verbose, uppercase, * normalized, and expanded to 8 words. * * @param {string} address - The IPv6 address, with or without CIDR. * @returns {string} */ Morebits.sanitizeIPv6 = function (address) { console.warn('NOTE: Morebits.sanitizeIPv6 was renamed to Morebits.ip.sanitizeIPv6 in February 2021, please use that instead'); // eslint-disable-line no-console return Morebits.ip.sanitizeIPv6(address); }; /** * Determines whether the current page is a redirect or soft redirect. Fails * to detect soft redirects on edit, history, etc. pages. Will attempt to * detect Module:RfD, with the same failure points. * * @returns {boolean} */ Morebits.isPageRedirect = function() { return !!(mw.config.get('wgIsRedirect') || document.getElementById('softredirect') || $('.box-RfD').length || $('.box-Redirect_category_shell').length); }; /** * Stores a normalized (underscores converted to spaces) version of the * `wgPageName` variable. * * @type {string} */ Morebits.pageNameNorm = mw.config.get('wgPageName').replace(/_/g, ' '); /** * Create a string for use in regex matching a page name. Accounts for * leading character's capitalization, underscores as spaces, and special * characters being escaped. See also {@link Morebits.namespaceRegex}. * * @param {string} pageName - Page name without namespace. * @returns {string} - For a page name `Foo bar`, returns the string `[Ff]oo[_ ]bar`. */ Morebits.pageNameRegex = function(pageName) { if (pageName === '') { return ''; } var firstChar = pageName[0], remainder = Morebits.string.escapeRegExp(pageName.slice(1)); if (mw.Title.phpCharToUpper(firstChar) !== firstChar.toLowerCase()) { return '[' + mw.Title.phpCharToUpper(firstChar) + firstChar.toLowerCase() + ']' + remainder; } return Morebits.string.escapeRegExp(firstChar) + remainder; }; /** * Converts string or array of DOM nodes into an HTML fragment. * Wikilink syntax (`[[...]]`) is transformed into HTML anchor. * Used in Morebits.quickForm and Morebits.status * @internal * @param {string|Node|(string|Node)[]} input * @returns {DocumentFragment} */ Morebits.createHtml = function(input) { var fragment = document.createDocumentFragment(); if (!input) { return fragment; } if (!Array.isArray(input)) { input = [ input ]; } for (var i = 0; i < input.length; ++i) { if (input[i] instanceof Node) { fragment.appendChild(input[i]); } else { $.parseHTML(Morebits.createHtml.renderWikilinks(input[i])).forEach(function(node) { if (node.nodeType === 3) { // text node fragment.appendChild(node); } else if (node.nodeType === 1) { // Element node, strip dangerous attributes Array.prototype.slice.call(node.attributes).forEach(function(attr) { // onclick, onerror, onload, etc if (attr.name.indexOf('on') === 0) { node.removeAttribute(attr.name); } }); fragment.appendChild(node); } // any other node type is suspicious }); } } return fragment; }; /** * Converts wikilinks to HTML anchor tags. * @param text * @returns {*} */ Morebits.createHtml.renderWikilinks = function (text) { var ub = new Morebits.unbinder(text); // Don't convert wikilinks within code tags as they're used for displaying wiki-code ub.unbind('<code>', '</code>'); ub.content = ub.content.replace( /\[\[:?(?:([^|\]]+?)\|)?([^\]|]+?)\]\]/g, function(_, target, text) { if (!target) { target = text; } return '<a target="_blank" href="' + mw.util.getUrl(target) + '" title="' + target.replace(/"/g, '&#34;') + '">' + text + '</a>'; }); return ub.rebind(); }; /** * Create a string for use in regex matching all namespace aliases, regardless * of the capitalization and underscores/spaces. Doesn't include the optional * leading `:`, but if there's more than one item, wraps the list in a * non-capturing group. This means you can do `Morebits.namespaceRegex([4]) + * ':' + Morebits.pageNameRegex('Twinkle')` to match a full page. Uses * {@link Morebits.pageNameRegex}. * * @param {number[]} namespaces - Array of namespace numbers. Unused/invalid * namespace numbers are silently discarded. * @example * // returns '(?:[Ff][Ii][Ll][Ee]|[Ii][Mm][Aa][Gg][Ee])' * Morebits.namespaceRegex([6]) * @returns {string} - Regex-suitable string of all namespace aliases. */ Morebits.namespaceRegex = function(namespaces) { if (!Array.isArray(namespaces)) { namespaces = [namespaces]; } var aliases = [], regex; $.each(mw.config.get('wgNamespaceIds'), function(name, number) { if (namespaces.indexOf(number) !== -1) { // Namespaces are completely agnostic as to case, // and a regex string is more useful/compatibile than a RegExp object, // so we accept any casing for any letter. aliases.push(name.split('').map(function(char) { return Morebits.pageNameRegex(char); }).join('')); } }); switch (aliases.length) { case 0: regex = ''; break; case 1: regex = aliases[0]; break; default: regex = '(?:' + aliases.join('|') + ')'; break; } return regex; }; /* **************** Morebits.quickForm **************** */ /** * Creation of simple and standard forms without much specific coding. * * @namespace Morebits.quickForm * @memberof Morebits * @class * @param {event} event - Function to execute when form is submitted. * @param {string} [eventType=submit] - Type of the event. */ Morebits.quickForm = function QuickForm(event, eventType) { this.root = new Morebits.quickForm.element({ type: 'form', event: event, eventType: eventType }); }; /** * Renders the HTML output of the quickForm. * * @memberof Morebits.quickForm * @returns {HTMLElement} */ Morebits.quickForm.prototype.render = function QuickFormRender() { var ret = this.root.render(); ret.names = {}; return ret; }; /** * Append element to the form. * * @memberof Morebits.quickForm * @param {(object|Morebits.quickForm.element)} data - A quickform element, or the object with which * a quickform element is constructed. * @returns {Morebits.quickForm.element} - Same as what is passed to the function. */ Morebits.quickForm.prototype.append = function QuickFormAppend(data) { return this.root.append(data); }; /** * Create a new element for the the form. * * Index to Morebits.quickForm.element types: * - Global attributes: id, className, style, tooltip, extra, adminonly * - `select`: A combo box (aka drop-down). * - Attributes: name, label, multiple, size, list, event, disabled * - `option`: An element for a combo box. * - Attributes: value, label, selected, disabled * - `optgroup`: A group of "option"s. * - Attributes: label, list * - `field`: A fieldset (aka group box). * - Attributes: name, label, disabled * - `checkbox`: A checkbox. Must use "list" parameter. * - Attributes: name, list, event * - Attributes (within list): name, label, value, checked, disabled, event, subgroup * - `radio`: A radio button. Must use "list" parameter. * - Attributes: name, list, event * - Attributes (within list): name, label, value, checked, disabled, event, subgroup * - `input`: A text input box. * - Attributes: name, label, value, size, placeholder, maxlength, disabled, required, readonly, event * - `number`: A number input box. * - Attributes: Everything the text `input` has, as well as: min, max, step, list * - `dyninput`: A set of text boxes with "Remove" buttons and an "Add" button. * - Attributes: name, label, min, max, sublabel, value, size, maxlength, event * - `hidden`: An invisible form field. * - Attributes: name, value * - `header`: A level 5 header. * - Attributes: label * - `div`: A generic placeholder element or label. * - Attributes: name, label * - `submit`: A submit button. Morebits.simpleWindow moves these to the footer of the dialog. * - Attributes: name, label, disabled * - `button`: A generic button. * - Attributes: name, label, disabled, event * - `textarea`: A big, multi-line text box. * - Attributes: name, label, value, cols, rows, disabled, required, readonly * - `fragment`: A DocumentFragment object. * - No attributes, and no global attributes except adminonly. * * @memberof Morebits.quickForm * @class * @param {object} data - Object representing the quickform element. Should * specify one of the available types from the index above, as well as any * relevant and available attributes. * @example new Morebits.quickForm.element({ * name: 'target', * type: 'input', * label: 'Your target:', * tooltip: 'Enter your target. Required.', * required: true * }); */ Morebits.quickForm.element = function QuickFormElement(data) { this.data = data; this.childs = []; }; /** * @memberof Morebits.quickForm.element * @type {number} */ Morebits.quickForm.element.id = 0; /** * Appends an element to current element. * * @memberof Morebits.quickForm.element * @param {Morebits.quickForm.element} data - A quickForm element or the object required to * create the quickForm element. * @returns {Morebits.quickForm.element} The same element passed in. */ Morebits.quickForm.element.prototype.append = function QuickFormElementAppend(data) { var child; if (data instanceof Morebits.quickForm.element) { child = data; } else { child = new Morebits.quickForm.element(data); } this.childs.push(child); return child; }; /** * Renders the HTML output for the quickForm element. This should be called * without parameters: `form.render()`. * * @memberof Morebits.quickForm.element * @returns {HTMLElement} */ Morebits.quickForm.element.prototype.render = function QuickFormElementRender(internal_subgroup_id) { var currentNode = this.compute(this.data, internal_subgroup_id); for (var i = 0; i < this.childs.length; ++i) { // do not pass internal_subgroup_id to recursive calls currentNode[1].appendChild(this.childs[i].render()); } return currentNode[0]; }; /** @memberof Morebits.quickForm.element */ Morebits.quickForm.element.prototype.compute = function QuickFormElementCompute(data, in_id) { var node; var childContainder = null; var label; var id = (in_id ? in_id + '_' : '') + 'node_' + Morebits.quickForm.element.id++; if (data.adminonly && !Morebits.userIsSysop) { // hell hack alpha data.type = 'hidden'; } var i, current, subnode; switch (data.type) { case 'form': node = document.createElement('form'); node.className = 'quickform'; node.setAttribute('action', 'javascript:void(0);'); if (data.event) { node.addEventListener(data.eventType || 'submit', data.event, false); } break; case 'fragment': node = document.createDocumentFragment(); // fragments can't have any attributes, so just return it straight away return [ node, node ]; case 'select': node = document.createElement('div'); node.setAttribute('id', 'div_' + id); if (data.label) { label = node.appendChild(document.createElement('label')); label.setAttribute('for', id); label.appendChild(Morebits.createHtml(data.label)); } var select = node.appendChild(document.createElement('select')); if (data.event) { select.addEventListener('change', data.event, false); } if (data.multiple) { select.setAttribute('multiple', 'multiple'); } if (data.size) { select.setAttribute('size', data.size); } if (data.disabled) { select.setAttribute('disabled', 'disabled'); } select.setAttribute('name', data.name); if (data.list) { for (i = 0; i < data.list.length; ++i) { current = data.list[i]; if (current.list) { current.type = 'optgroup'; } else { current.type = 'option'; } subnode = this.compute(current); select.appendChild(subnode[0]); } } childContainder = select; break; case 'option': node = document.createElement('option'); node.values = data.value; node.setAttribute('value', data.value); if (data.selected) { node.setAttribute('selected', 'selected'); } if (data.disabled) { node.setAttribute('disabled', 'disabled'); } // zhwiki: Add hidden attr if (data.hidden) { node.setAttribute('hidden', ''); } node.setAttribute('label', data.label); node.appendChild(document.createTextNode(data.label)); break; case 'optgroup': node = document.createElement('optgroup'); node.setAttribute('label', data.label); if (data.list) { for (i = 0; i < data.list.length; ++i) { current = data.list[i]; current.type = 'option'; // must be options here subnode = this.compute(current); node.appendChild(subnode[0]); } } break; case 'field': node = document.createElement('fieldset'); label = node.appendChild(document.createElement('legend')); label.appendChild(Morebits.createHtml(data.label)); if (data.name) { node.setAttribute('name', data.name); } if (data.disabled) { node.setAttribute('disabled', 'disabled'); } break; case 'checkbox': case 'radio': node = document.createElement('div'); if (data.list) { for (i = 0; i < data.list.length; ++i) { var cur_id = id + '_' + i; current = data.list[i]; var cur_div; if (current.type === 'header') { // inline hack cur_div = node.appendChild(document.createElement('h6')); cur_div.appendChild(document.createTextNode(current.label)); if (current.tooltip) { Morebits.quickForm.element.generateTooltip(cur_div, current); } continue; } cur_div = node.appendChild(document.createElement('div')); if (current.hidden) { cur_div.setAttribute('hidden', ''); } subnode = cur_div.appendChild(document.createElement('input')); subnode.values = current.value; subnode.setAttribute('value', current.value); subnode.setAttribute('type', data.type); subnode.setAttribute('id', cur_id); subnode.setAttribute('name', current.name || data.name); // If name is provided on the individual checkbox, add a data-single // attribute which indicates it isn't part of a list of checkboxes with // same name. Used in getInputData() if (current.name) { subnode.setAttribute('data-single', 'data-single'); } if (current.checked) { subnode.setAttribute('checked', 'checked'); } if (current.disabled) { subnode.setAttribute('disabled', 'disabled'); } label = cur_div.appendChild(document.createElement('label')); label.appendChild(Morebits.createHtml(current.label)); label.setAttribute('for', cur_id); if (current.tooltip) { Morebits.quickForm.element.generateTooltip(label, current); } // styles go on the label, doesn't make sense to style a checkbox/radio if (current.style) { label.setAttribute('style', current.style); } var event; if (current.subgroup) { var tmpgroup = current.subgroup; if (!Array.isArray(tmpgroup)) { tmpgroup = [ tmpgroup ]; } var subgroupRaw = new Morebits.quickForm.element({ type: 'div', id: id + '_' + i + '_subgroup' }); $.each(tmpgroup, function(idx, el) { var newEl = $.extend({}, el); if (!newEl.type) { newEl.type = data.type; } newEl.name = (current.name || data.name) + '.' + newEl.name; subgroupRaw.append(newEl); }); var subgroup = subgroupRaw.render(cur_id); subgroup.className = 'quickformSubgroup'; subnode.subgroup = subgroup; subnode.shown = false; event = function(e) { if (e.target.checked) { e.target.parentNode.appendChild(e.target.subgroup); if (e.target.type === 'radio') { var name = e.target.name; if (e.target.form.names[name] !== undefined) { e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup); } e.target.form.names[name] = e.target; } } else { e.target.parentNode.removeChild(e.target.subgroup); } }; subnode.addEventListener('change', event, true); if (current.checked) { subnode.parentNode.appendChild(subgroup); } } else if (data.type === 'radio') { event = function(e) { if (e.target.checked) { var name = e.target.name; if (e.target.form.names[name] !== undefined) { e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup); } delete e.target.form.names[name]; } }; subnode.addEventListener('change', event, true); } // add users' event last, so it can interact with the subgroup if (data.event) { subnode.addEventListener('change', data.event, false); } else if (current.event) { subnode.addEventListener('change', current.event, true); } } } if (data.shiftClickSupport && data.type === 'checkbox') { Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(node, data.name)); } break; // input is actually a text-type, so number here inherits the same stuff case 'number': case 'input': node = document.createElement('div'); node.setAttribute('id', 'div_' + id); if (data.hidden) { node.setAttribute('hidden', ''); } if (data.label) { label = node.appendChild(document.createElement('label')); label.appendChild(Morebits.createHtml(data.label)); label.setAttribute('for', data.id || id); } subnode = node.appendChild(document.createElement('input')); if (data.value) { subnode.setAttribute('value', data.value); } if (data.placeholder) { subnode.setAttribute('placeholder', data.placeholder); } subnode.setAttribute('name', data.name); if (data.type === 'input') { subnode.setAttribute('type', 'text'); } else { subnode.setAttribute('type', 'number'); ['min', 'max', 'step', 'list'].forEach(function(att) { if (data[att]) { subnode.setAttribute(att, data[att]); } }); } ['value', 'size', 'placeholder', 'maxlength'].forEach(function(att) { if (data[att]) { subnode.setAttribute(att, data[att]); } }); ['disabled', 'required', 'readonly'].forEach(function(att) { if (data[att]) { subnode.setAttribute(att, att); } }); if (data.event) { subnode.addEventListener('keyup', data.event, false); } childContainder = subnode; break; case 'dyninput': var min = data.min || 1; var max = data.max || Infinity; node = document.createElement('div'); label = node.appendChild(document.createElement('h5')); label.appendChild(Morebits.createHtml(data.label)); var listNode = node.appendChild(document.createElement('div')); var more = this.compute({ type: 'button', label: '更多', disabled: min >= max, event: function(e) { var new_node = new Morebits.quickForm.element(e.target.sublist); e.target.area.appendChild(new_node.render()); if (++e.target.counter >= e.target.max) { e.target.setAttribute('disabled', 'disabled'); } e.stopPropagation(); } }); node.appendChild(more[0]); var moreButton = more[1]; var sublist = { type: '_dyninput_element', label: data.sublabel || data.label, name: data.name, value: data.value, size: data.size, remove: false, maxlength: data.maxlength, event: data.event }; for (i = 0; i < min; ++i) { var elem = new Morebits.quickForm.element(sublist); listNode.appendChild(elem.render()); } sublist.remove = true; sublist.morebutton = moreButton; sublist.listnode = listNode; moreButton.sublist = sublist; moreButton.area = listNode; moreButton.max = max - min; moreButton.counter = 0; break; case '_dyninput_element': // Private, similar to normal input node = document.createElement('div'); if (data.label) { label = node.appendChild(document.createElement('label')); label.appendChild(document.createTextNode(data.label)); label.setAttribute('for', id); } subnode = node.appendChild(document.createElement('input')); if (data.value) { subnode.setAttribute('value', data.value); } subnode.setAttribute('name', data.name); subnode.setAttribute('type', 'text'); if (data.size) { subnode.setAttribute('size', data.size); } if (data.maxlength) { subnode.setAttribute('maxlength', data.maxlength); } if (data.event) { subnode.addEventListener('keyup', data.event, false); } if (data.remove) { var remove = this.compute({ type: 'button', label: '移除', event: function(e) { var list = e.target.listnode; var node = e.target.inputnode; var more = e.target.morebutton; list.removeChild(node); --more.counter; more.removeAttribute('disabled'); e.stopPropagation(); } }); node.appendChild(remove[0]); var removeButton = remove[1]; removeButton.inputnode = node; removeButton.listnode = data.listnode; removeButton.morebutton = data.morebutton; } break; case 'hidden': node = document.createElement('input'); node.setAttribute('type', 'hidden'); node.values = data.value; node.setAttribute('value', data.value); node.setAttribute('name', data.name); break; case 'header': node = document.createElement('h5'); node.appendChild(Morebits.createHtml(data.label)); break; case 'div': node = document.createElement('div'); if (data.name) { node.setAttribute('name', data.name); } if (data.label) { if (!Array.isArray(data.label)) { data.label = [ data.label ]; } var result = document.createElement('span'); result.className = 'quickformDescription'; result.appendChild(Morebits.createHtml(data.label)); node.appendChild(result); } break; case 'submit': node = document.createElement('span'); childContainder = node.appendChild(document.createElement('input')); childContainder.setAttribute('type', 'submit'); if (data.label) { childContainder.setAttribute('value', data.label); } childContainder.setAttribute('name', data.name || 'submit'); if (data.disabled) { childContainder.setAttribute('disabled', 'disabled'); } break; case 'button': node = document.createElement('span'); childContainder = node.appendChild(document.createElement('input')); childContainder.setAttribute('type', 'button'); if (data.label) { childContainder.setAttribute('value', data.label); } childContainder.setAttribute('name', data.name); if (data.disabled) { childContainder.setAttribute('disabled', 'disabled'); } if (data.event) { childContainder.addEventListener('click', data.event, false); } break; case 'textarea': node = document.createElement('div'); node.setAttribute('id', 'div_' + id); if (data.hidden) { node.setAttribute('hidden', ''); } if (data.label) { label = node.appendChild(document.createElement('h5')); var labelElement = document.createElement('label'); labelElement.appendChild(Morebits.createHtml(data.label)); labelElement.setAttribute('for', data.id || id); label.appendChild(labelElement); } subnode = node.appendChild(document.createElement('textarea')); subnode.setAttribute('name', data.name); if (data.cols) { subnode.setAttribute('cols', data.cols); } if (data.rows) { subnode.setAttribute('rows', data.rows); } if (data.disabled) { subnode.setAttribute('disabled', 'disabled'); } if (data.required) { subnode.setAttribute('required', 'required'); } if (data.readonly) { subnode.setAttribute('readonly', 'readonly'); } if (data.value) { subnode.value = data.value; } if (data.placeholder) { subnode.placeholder = data.placeholder; } childContainder = subnode; break; default: throw new Error('Morebits.quickForm: unknown element type ' + data.type.toString()); } if (!childContainder) { childContainder = node; } if (data.tooltip) { Morebits.quickForm.element.generateTooltip(label || node, data); } if (data.extra) { childContainder.extra = data.extra; } if (data.style) { childContainder.setAttribute('style', data.style); } if (data.className) { childContainder.className = childContainder.className ? childContainder.className + ' ' + data.className : data.className; } childContainder.setAttribute('id', data.id || id); return [ node, childContainder ]; }; /** * Create a jQuery UI-based tooltip. * * @memberof Morebits.quickForm.element * @requires jquery.ui * @param {HTMLElement} node - The HTML element beside which a tooltip is to be generated. * @param {object} data - Tooltip-related configuration data. */ Morebits.quickForm.element.generateTooltip = function QuickFormElementGenerateTooltip(node, data) { var tooltipButton = node.appendChild(document.createElement('span')); tooltipButton.className = 'morebits-tooltipButton'; tooltipButton.title = data.tooltip; // Provides the content for jQuery UI tooltipButton.appendChild(document.createTextNode('?')); $(tooltipButton).tooltip({ position: { my: 'left top', at: 'center bottom', collision: 'flipfit' }, // Deprecated in UI 1.12, but MW stuck on 1.9.2 indefinitely; see #398 and T71386 tooltipClass: 'morebits-ui-tooltip' }); }; // Some utility methods for manipulating quickForms after their creation: // (None of these work for "dyninput" type fields at present) /** * Returns an object containing all filled form data entered by the user, with the object * keys being the form element names. Disabled fields will be ignored, but not hidden fields. * * @memberof Morebits.quickForm * @param {HTMLFormElement} form * @returns {object} With field names as keys, input data as values. */ Morebits.quickForm.getInputData = function(form) { var result = {}; for (var i = 0; i < form.elements.length; i++) { var field = form.elements[i]; if (field.disabled || !field.name || !field.type || field.type === 'submit' || field.type === 'button') { continue; } // For elements in subgroups, quickform prepends element names with // name of the parent group followed by a period, get rid of that. var fieldNameNorm = field.name.slice(field.name.indexOf('.') + 1); switch (field.type) { case 'radio': if (field.checked) { result[fieldNameNorm] = field.value; } break; case 'checkbox': if (field.dataset.single) { result[fieldNameNorm] = field.checked; // boolean } else { result[fieldNameNorm] = result[fieldNameNorm] || []; if (field.checked) { result[fieldNameNorm].push(field.value); } } break; case 'select-multiple': result[fieldNameNorm] = $(field).val(); // field.value doesn't work break; case 'text': // falls through case 'textarea': result[fieldNameNorm] = field.value.trim(); break; default: // could be select-one, date, number, email, etc if (field.value) { result[fieldNameNorm] = field.value; } break; } } return result; }; /** * Returns all form elements with a given field name or ID. * * @memberof Morebits.quickForm * @param {HTMLFormElement} form * @param {string} fieldName - The name or id of the fields. * @returns {HTMLElement[]} - Array of matching form elements. */ Morebits.quickForm.getElements = function QuickFormGetElements(form, fieldName) { var $form = $(form); fieldName = $.escapeSelector(fieldName); // sanitize input var $elements = $form.find('[name="' + fieldName + '"]'); if ($elements.length > 0) { return $elements.toArray(); } $elements = $form.find('#' + fieldName); return $elements.toArray(); }; /** * Searches the array of elements for a checkbox or radio button with a certain * `value` attribute, and returns the first such element. Returns null if not found. * * @memberof Morebits.quickForm * @param {HTMLInputElement[]} elementArray - Array of checkbox or radio elements. * @param {string} value - Value to search for. * @returns {HTMLInputElement} */ Morebits.quickForm.getCheckboxOrRadio = function QuickFormGetCheckboxOrRadio(elementArray, value) { var found = $.grep(elementArray, function(el) { return el.value === value; }); if (found.length > 0) { return found[0]; } return null; }; /** * Returns the &lt;div> containing the form element, or the form element itself * May not work as expected on checkboxes or radios. * * @memberof Morebits.quickForm * @param {HTMLElement} element * @returns {HTMLElement} */ Morebits.quickForm.getElementContainer = function QuickFormGetElementContainer(element) { // for divs, headings and fieldsets, the container is the element itself if (element instanceof HTMLFieldSetElement || element instanceof HTMLDivElement || element instanceof HTMLHeadingElement) { return element; } // for others, just return the parent node return element.parentNode; }; /** * Gets the HTML element that contains the label of the given form element * (mainly for internal use). * * @memberof Morebits.quickForm * @param {(HTMLElement|Morebits.quickForm.element)} element * @returns {HTMLElement} */ Morebits.quickForm.getElementLabelObject = function QuickFormGetElementLabelObject(element) { // for buttons, divs and headers, the label is on the element itself if (element.type === 'button' || element.type === 'submit' || element instanceof HTMLDivElement || element instanceof HTMLHeadingElement) { return element; // for fieldsets, the label is the child <legend> element } else if (element instanceof HTMLFieldSetElement) { return element.getElementsByTagName('legend')[0]; // for textareas, the label is the sibling <h5> element } else if (element instanceof HTMLTextAreaElement) { return element.parentNode.getElementsByTagName('h5')[0]; } // for others, the label is the sibling <label> element return element.parentNode.getElementsByTagName('label')[0]; }; /** * Gets the label text of the element. * * @memberof Morebits.quickForm * @param {(HTMLElement|Morebits.quickForm.element)} element * @returns {string} */ Morebits.quickForm.getElementLabel = function QuickFormGetElementLabel(element) { var labelElement = Morebits.quickForm.getElementLabelObject(element); if (!labelElement) { return null; } return labelElement.firstChild.textContent; }; /** * Sets the label of the element to the given text. * * @memberof Morebits.quickForm * @param {(HTMLElement|Morebits.quickForm.element)} element * @param {string} labelText * @returns {boolean} True if succeeded, false if the label element is unavailable. */ Morebits.quickForm.setElementLabel = function QuickFormSetElementLabel(element, labelText) { var labelElement = Morebits.quickForm.getElementLabelObject(element); if (!labelElement) { return false; } labelElement.firstChild.textContent = labelText; return true; }; /** * Stores the element's current label, and temporarily sets the label to the given text. * * @memberof Morebits.quickForm * @param {(HTMLElement|Morebits.quickForm.element)} element * @param {string} temporaryLabelText * @returns {boolean} `true` if succeeded, `false` if the label element is unavailable. */ Morebits.quickForm.overrideElementLabel = function QuickFormOverrideElementLabel(element, temporaryLabelText) { if (!element.hasAttribute('data-oldlabel')) { element.setAttribute('data-oldlabel', Morebits.quickForm.getElementLabel(element)); } return Morebits.quickForm.setElementLabel(element, temporaryLabelText); }; /** * Restores the label stored by overrideElementLabel. * * @memberof Morebits.quickForm * @param {(HTMLElement|Morebits.quickForm.element)} element * @returns {boolean} True if succeeded, false if the label element is unavailable. */ Morebits.quickForm.resetElementLabel = function QuickFormResetElementLabel(element) { if (element.hasAttribute('data-oldlabel')) { return Morebits.quickForm.setElementLabel(element, element.getAttribute('data-oldlabel')); } return null; }; /** * Shows or hides a form element plus its label and tooltip. * * @memberof Morebits.quickForm * @param {(HTMLElement|jQuery|string)} element - HTML/jQuery element, or jQuery selector string. * @param {boolean} [visibility] - Skip this to toggle visibility. */ Morebits.quickForm.setElementVisibility = function QuickFormSetElementVisibility(element, visibility) { $(element).toggle(visibility); }; /** * Shows or hides the question mark icon (which displays the tooltip) next to a form element. * * @memberof Morebits.quickForm * @param {(HTMLElement|jQuery)} element * @param {boolean} [visibility] - Skip this to toggle visibility. */ Morebits.quickForm.setElementTooltipVisibility = function QuickFormSetElementTooltipVisibility(element, visibility) { $(Morebits.quickForm.getElementContainer(element)).find('.morebits-tooltipButton').toggle(visibility); }; /** * @external HTMLFormElement */ /** * Get checked items in the form. * * @function external:HTMLFormElement.getChecked * @param {string} name - Find checked property of elements (i.e. a checkbox * or a radiobutton) with the given name, or select options that have selected * set to true (don't try to mix selects with radio/checkboxes). * @param {string} [type] - Optionally specify either radio or checkbox (for * the event that both checkboxes and radiobuttons have the same name). * @returns {string[]} - Contains the values of elements with the given name * checked property set to true. */ HTMLFormElement.prototype.getChecked = function(name, type) { var elements = this.elements[name]; if (!elements) { return []; } var return_array = []; var i; if (elements instanceof HTMLSelectElement) { var options = elements.options; for (i = 0; i < options.length; ++i) { if (options[i].selected) { if (options[i].values) { return_array.push(options[i].values); } else { return_array.push(options[i].value); } } } } else if (elements instanceof HTMLInputElement) { if (type && elements.type !== type) { return []; } else if (elements.checked) { return [ elements.value ]; } } else { for (i = 0; i < elements.length; ++i) { if (elements[i].checked) { if (type && elements[i].type !== type) { continue; } if (elements[i].values) { return_array.push(elements[i].values); } else { return_array.push(elements[i].value); } } } } return return_array; }; /** * Does the same as {@link HTMLFormElement.getChecked|getChecked}, but with unchecked elements. * * @function external:HTMLFormElement.getUnchecked * @param {string} name - Find checked property of elements (i.e. a checkbox * or a radiobutton) with the given name, or select options that have selected * set to true (don't try to mix selects with radio/checkboxes). * @param {string} [type] - Optionally specify either radio or checkbox (for * the event that both checkboxes and radiobuttons have the same name). * @returns {string[]} - Contains the values of elements with the given name * checked property set to true. */ HTMLFormElement.prototype.getUnchecked = function(name, type) { var elements = this.elements[name]; if (!elements) { return []; } var return_array = []; var i; if (elements instanceof HTMLSelectElement) { var options = elements.options; for (i = 0; i < options.length; ++i) { if (!options[i].selected) { if (options[i].values) { return_array.push(options[i].values); } else { return_array.push(options[i].value); } } } } else if (elements instanceof HTMLInputElement) { if (type && elements.type !== type) { return []; } else if (!elements.checked) { return [ elements.value ]; } } else { for (i = 0; i < elements.length; ++i) { if (!elements[i].checked) { if (type && elements[i].type !== type) { continue; } if (elements[i].values) { return_array.push(elements[i].values); } else { return_array.push(elements[i].value); } } } } return return_array; }; /** * Utilities to help process IP addresses. * * @namespace Morebits.ip * @memberof Morebits */ Morebits.ip = { /** * Converts an IPv6 address to the canonical form stored and used by MediaWiki. * JavaScript translation of the {@link https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/core/+/8eb6ac3e84ea3312d391ca96c12c49e3ad0753bb/includes/utils/IP.php#131|`IP::sanitizeIP()`} * function from the IPUtils library. Adddresses are verbose, uppercase, * normalized, and expanded to 8 words. * * @param {string} address - The IPv6 address, with or without CIDR. * @returns {string} */ sanitizeIPv6: function (address) { address = address.trim(); if (address === '') { return null; } if (!mw.util.isIPv6Address(address, true)) { return address; // nothing else to do for IPv4 addresses or invalid ones } // Remove any whitespaces, convert to upper case address = address.toUpperCase(); // Expand zero abbreviations var abbrevPos = address.indexOf('::'); if (abbrevPos > -1) { // We know this is valid IPv6. Find the last index of the // address before any CIDR number (e.g. "a:b:c::/24"). var CIDRStart = address.indexOf('/'); var addressEnd = CIDRStart !== -1 ? CIDRStart - 1 : address.length - 1; // If the '::' is at the beginning... var repeat, extra, pad; if (abbrevPos === 0) { repeat = '0:'; extra = address === '::' ? '0' : ''; // for the address '::' pad = 9; // 7+2 (due to '::') // If the '::' is at the end... } else if (abbrevPos === (addressEnd - 1)) { repeat = ':0'; extra = ''; pad = 9; // 7+2 (due to '::') // If the '::' is in the middle... } else { repeat = ':0'; extra = ':'; pad = 8; // 6+2 (due to '::') } var replacement = repeat; pad -= address.split(':').length - 1; for (var i = 1; i < pad; i++) { replacement += repeat; } replacement += extra; address = address.replace('::', replacement); } // Remove leading zeros from each bloc as needed return address.replace(/(^|:)0+([0-9A-Fa-f]{1,4})/g, '$1$2'); }, /** * Determine if the given IP address is a range. Just conjoins * `mw.util.isIPAddress` with and without the `allowBlock` option. * * @param {string} ip * @returns {boolean} - True if given a valid IP address range, false otherwise. */ isRange: function (ip) { return mw.util.isIPAddress(ip, true) && !mw.util.isIPAddress(ip); }, /** * Check that an IP range is within the CIDR limits. Most likely to be useful * in conjunction with `wgRelevantUserName`. CIDR limits are harcoded as /16 * for IPv4 and /32 for IPv6. * * @returns {boolean} - True for valid ranges within the CIDR limits, * otherwise false (ranges outside the limit, single IPs, non-IPs). */ validCIDR: function (ip) { if (Morebits.ip.isRange(ip)) { var subnet = parseInt(ip.match(/\/(\d{1,3})$/)[1], 10); if (subnet) { // Should be redundant if (mw.util.isIPv6Address(ip, true)) { if (subnet >= 32) { return true; } } else { if (subnet >= 16) { return true; } } } } return false; }, /** * Get the /64 subnet for an IPv6 address. * * @param {string} ipv6 - The IPv6 address, with or without a subnet. * @returns {boolean|string} - False if not IPv6 or bigger than a 64, * otherwise the (sanitized) /64 address. */ get64: function (ipv6) { if (!ipv6 || !mw.util.isIPv6Address(ipv6, true)) { return false; } var subnetMatch = ipv6.match(/\/(\d{1,3})$/); if (subnetMatch && parseInt(subnetMatch[1], 10) < 64) { return false; } ipv6 = Morebits.ip.sanitizeIPv6(ipv6); var ip_re = /^((?:[0-9A-F]{1,4}:){4})(?:[0-9A-F]{1,4}:){3}[0-9A-F]{1,4}(?:\/\d{1,3})?$/; return ipv6.replace(ip_re, '$1' + '0:0:0:0/64'); } }; /** * @external RegExp */ /** * Deprecated as of September 2020, use {@link Morebits.string.escapeRegExp} * or `mw.util.escapeRegExp`. * * @function external:RegExp.escape * @deprecated Use {@link Morebits.string.escapeRegExp} or `mw.util.escapeRegExp`. * @param {string} text - String to be escaped. * @param {boolean} [space_fix=false] - Whether to replace spaces and * underscores with `[ _]` as they are often equivalent. * @returns {string} - The escaped text. */ RegExp.escape = function(text, space_fix) { if (space_fix) { console.error('NOTE: RegExp.escape from Morebits was deprecated September 2020, please replace it with Morebits.string.escapeRegExp'); // eslint-disable-line no-console return Morebits.string.escapeRegExp(text); } console.error('NOTE: RegExp.escape from Morebits was deprecated September 2020, please replace it with mw.util.escapeRegExp'); // eslint-disable-line no-console return mw.util.escapeRegExp(text); }; /** * Helper functions to manipulate strings. * * @namespace Morebits.string * @memberof Morebits */ Morebits.string = { /** * @param {string} str * @returns {string} */ toUpperCaseFirstChar: function(str) { str = str.toString(); return str.substr(0, 1).toUpperCase() + str.substr(1); }, /** * @param {string} str * @returns {string} */ toLowerCaseFirstChar: function(str) { str = str.toString(); return str.substr(0, 1).toLowerCase() + str.substr(1); }, /** * Gives an array of substrings of `str` - starting with `start` and * ending with `end` - which is not in `skiplist`. Intended for use * on wikitext with templates or links. * * @param {string} str * @param {string} start * @param {string} end * @param {(string[]|string)} [skiplist] * @returns {string[]} * @throws If the `start` and `end` strings aren't of the same length. * @throws If `skiplist` isn't an array or string */ splitWeightedByKeys: function(str, start, end, skiplist) { if (start.length !== end.length) { throw new Error('start marker and end marker must be of the same length'); } var level = 0; var initial = null; var result = []; if (!Array.isArray(skiplist)) { if (skiplist === undefined) { skiplist = []; } else if (typeof skiplist === 'string') { skiplist = [ skiplist ]; } else { throw new Error('non-applicable skiplist parameter'); } } for (var i = 0; i < str.length; ++i) { for (var j = 0; j < skiplist.length; ++j) { if (str.substr(i, skiplist[j].length) === skiplist[j]) { i += skiplist[j].length - 1; continue; } } if (str.substr(i, start.length) === start) { if (initial === null) { initial = i; } ++level; i += start.length - 1; } else if (str.substr(i, end.length) === end) { --level; i += end.length - 1; } if (!level && initial !== null) { result.push(str.substring(initial, i + 1)); initial = null; } } return result; }, /** * Formats freeform "reason" (from a textarea) for deletion/other * templates that are going to be substituted, (e.g. PROD, XFD, RPP). * Handles `|` outside a nowiki tag. * Optionally, also adds a signature if not present already. * * @param {string} str * @param {boolean} [addSig] * @returns {string} */ formatReasonText: function(str, addSig) { var reason = (str || '').toString().trim(); var unbinder = new Morebits.unbinder(reason); unbinder.unbind('<no' + 'wiki>', '</no' + 'wiki>'); unbinder.content = unbinder.content.replace(/\|/g, '{{subst:!}}'); reason = unbinder.rebind(); if (addSig) { var sig = '~~~~', sigIndex = reason.lastIndexOf(sig); if (sigIndex === -1 || sigIndex !== reason.length - sig.length) { reason += ' ' + sig; } } return reason.trim(); }, /** * Formats a "reason" (from a textarea) for inclusion in a userspace * log. Replaces newlines with {{Pb}}, and adds an extra `#` before * list items for proper formatting. * * @param {string} str * @returns {string} */ formatReasonForLog: function(str) { return str // handle line breaks, which otherwise break numbering .replace(/\n+/g, '{{pb}}') // put an extra # in front before bulleted or numbered list items .replace(/^(#+)/mg, '#$1') .replace(/^(\*+)/mg, '#$1'); }, /** * Like `String.prototype.replace()`, but escapes any dollar signs in * the replacement string. Useful when the the replacement string is * arbitrary, such as a username or freeform user input, and could * contain dollar signs. * * @param {string} string - Text in which to replace. * @param {(string|RegExp)} pattern * @param {string} replacement * @returns {string} */ safeReplace: function morebitsStringSafeReplace(string, pattern, replacement) { return string.replace(pattern, replacement.replace(/\$/g, '$$$$')); }, /** * Determine if the user-provided expiration will be considered an * infinite-length by MW. * * @see {@link https://phabricator.wikimedia.org/T68646} * * @param {string} expiry * @returns {boolean} */ isInfinity: function morebitsStringIsInfinity(expiry) { return ['indefinite', 'infinity', 'infinite', 'never'].indexOf(expiry) !== -1; }, /** * Escapes a string to be used in a RegExp, replacing spaces and * underscores with `[_ ]` as they are often equivalent. * Replaced RegExp.escape September 2020. * * @param {string} text - String to be escaped. * @returns {string} - The escaped text. */ escapeRegExp: function(text) { return mw.util.escapeRegExp(text).replace(/ |_/g, '[_ ]'); }, /** * @param {*} time The string to foramt * @returns {string} */ formatTime: function morebitsStringFormatTime(time) { var m; if ((m = time.match(/^\s*(\d+)\s*sec(ond)?s?\s*$/)) !== null) { return m[1] + '秒'; } if ((m = time.match(/^\s*(\d+)\s*min(ute)?s?\s*$/)) !== null) { return m[1] + '分'; } if ((m = time.match(/^\s*(\d+)\s*hours?\s*$/)) !== null) { return m[1] + wgULS('小时', '小時'); } if ((m = time.match(/^\s*(\d+)\s*days?\s*$/)) !== null) { return m[1] + '天'; } if ((m = time.match(/^\s*(\d+)\s*weeks?\s*$/)) !== null) { return m[1] + wgULS('周', '週'); } if ((m = time.match(/^\s*(\d+)\s*months?\s*$/)) !== null) { return m[1] + wgULS('个月', '個月'); } if ((m = time.match(/^\s*(\d+)\s*years?\s*$/)) !== null) { return m[1] + '年'; } if (Morebits.string.isInfinity(time.trim())) { return wgULS('无限期', '無限期'); } return time; }, /** * Append punctuation to a string when it's missing * @param {string} str * @param {string} punctuation * @returns {String} */ appendPunctuation: function(str, punctuation) { if (punctuation === undefined) { punctuation = '。'; } if (str.search(/[.?!;。?!;]$/) === -1) { str += punctuation; } return str; } }; /** * Helper functions to manipulate arrays. * * @namespace Morebits.array * @memberof Morebits */ Morebits.array = { /** * Remove duplicated items from an array. * * @param {Array} arr * @returns {Array} A copy of the array with duplicates removed. * @throws When provided a non-array. */ uniq: function(arr) { if (!Array.isArray(arr)) { throw 'A non-array object passed to Morebits.array.uniq'; } return arr.filter(function(item, idx) { return arr.indexOf(item) === idx; }); }, /** * Remove non-duplicated items from an array. * * @param {Array} arr * @returns {Array} A copy of the array with the first instance of each value * removed; subsequent instances of those values (duplicates) remain. * @throws When provided a non-array. */ dups: function(arr) { if (!Array.isArray(arr)) { throw 'A non-array object passed to Morebits.array.dups'; } return arr.filter(function(item, idx) { return arr.indexOf(item) !== idx; }); }, /** * Break up an array into smaller arrays. * * @param {Array} arr * @param {number} size - Size of each chunk (except the last, which could be different). * @returns {Array[]} An array containing the smaller, chunked arrays. * @throws When provided a non-array. */ chunk: function(arr, size) { if (!Array.isArray(arr)) { throw 'A non-array object passed to Morebits.array.chunk'; } if (typeof size !== 'number' || size <= 0) { // pretty impossible to do anything :) return [ arr ]; // we return an array consisting of this array. } var numChunks = Math.ceil(arr.length / size); var result = new Array(numChunks); for (var i = 0; i < numChunks; i++) { result[i] = arr.slice(i * size, (i + 1) * size); } return result; } }; /** * Utilities to enhance select2 menus. See twinklewarn, twinklexfd, * twinkleblock for sample usages. * * @see {@link https://select2.org/} * * @namespace Morebits.select2 * @memberof Morebits * @requires jquery.select2 */ Morebits.select2 = { matchers: { /** * Custom matcher in which if the optgroup name matches, all options in that * group are shown, like in jquery.chosen. */ optgroupFull: function(params, data) { var originalMatcher = $.fn.select2.defaults.defaults.matcher; var result = originalMatcher(params, data); if (result && params.term && data.text.toUpperCase().indexOf(params.term.toUpperCase()) !== -1) { result.children = data.children; } return result; }, /** Custom matcher that matches from the beginning of words only. */ wordBeginning: function(params, data) { var originalMatcher = $.fn.select2.defaults.defaults.matcher; var result = originalMatcher(params, data); if (!params.term || (result && new RegExp('\\b' + mw.util.escapeRegExp(params.term), 'i').test(result.text))) { return result; } return null; } }, /** Underline matched part of options. */ highlightSearchMatches: function(data) { var searchTerm = Morebits.select2SearchQuery; if (!searchTerm || data.loading) { return data.text; } var idx = data.text.toUpperCase().indexOf(searchTerm.toUpperCase()); if (idx < 0) { return data.text; } return $('<span>').append( data.text.slice(0, idx), $('<span>').css('text-decoration', 'underline').text(data.text.slice(idx, idx + searchTerm.length)), data.text.slice(idx + searchTerm.length) ); }, /** Intercept query as it is happening, for use in highlightSearchMatches. */ queryInterceptor: function(params) { Morebits.select2SearchQuery = params && params.term; }, /** * Open dropdown and begin search when the `.select2-selection` has * focus and a key is pressed. * * @see {@link https://github.com/select2/select2/issues/3279#issuecomment-442524147} */ autoStart: function(ev) { if (ev.which < 48) { return; } var target = $(ev.target).closest('.select2-container'); if (!target.length) { return; } target = target.prev(); target.select2('open'); var search = target.data('select2').dropdown.$search || target.data('select2').selection.$search; // Use DOM .focus() to work around a jQuery 3.6.0 regression (https://github.com/select2/select2/issues/5993) search[0].focus(); } }; /** * Temporarily hide a part of a string while processing the rest of it. * Used by {@link Morebits.wikitext.page#commentOutImage|Morebits.wikitext.page.commentOutImage}. * * @memberof Morebits * @class * @param {string} string - The initial text to process. * @example var u = new Morebits.unbinder('Hello world <!-- world --> world'); * u.unbind('<!--', '-->'); // text inside comment remains intact * u.content = u.content.replace(/world/g, 'earth'); * u.rebind(); // gives 'Hello earth <!-- world --> earth' */ Morebits.unbinder = function Unbinder(string) { if (typeof string !== 'string') { throw new Error('not a string'); } /** The text being processed. */ this.content = string; this.counter = 0; this.history = {}; this.prefix = '%UNIQ::' + Math.random() + '::'; this.postfix = '::UNIQ%'; }; Morebits.unbinder.prototype = { /** * Hide the region encapsulated by the `prefix` and `postfix` from * string processing. `prefix` and `postfix` will be used in a * RegExp, so items that need escaping should be use `\\`. * * @param {string} prefix * @param {string} postfix * @throws If either `prefix` or `postfix` is missing. */ unbind: function UnbinderUnbind(prefix, postfix) { if (!prefix || !postfix) { throw new Error('Both prefix and postfix must be provided'); } var re = new RegExp(prefix + '([\\s\\S]*?)' + postfix, 'g'); this.content = this.content.replace(re, Morebits.unbinder.getCallback(this)); }, /** * Restore the hidden portion of the `content` string. * * @returns {string} The processed output. */ rebind: function UnbinderRebind() { var content = this.content; content.self = this; for (var current in this.history) { if (Object.prototype.hasOwnProperty.call(this.history, current)) { content = content.replace(current, this.history[current]); } } return content; }, prefix: null, // %UNIQ::0.5955981644938324:: postfix: null, // ::UNIQ% content: null, // string counter: null, // 0++ history: null // {} }; /** @memberof Morebits.unbinder */ Morebits.unbinder.getCallback = function UnbinderGetCallback(self) { return function UnbinderCallback(match) { var current = self.prefix + self.counter + self.postfix; self.history[current] = match; ++self.counter; return current; }; }; /* **************** Morebits.date **************** */ /** * Create a date object with enhanced processing capabilities, a la {@link * https://momentjs.com/|moment.js}. MediaWiki timestamp format is also * acceptable, in addition to everything that JS Date() accepts. * * @memberof Morebits * @class */ Morebits.date = function() { var args = Array.prototype.slice.call(arguments); // Check MediaWiki formats // Must be first since firefox erroneously accepts the timestamp // format, sans timezone (See also: #921, #936, #1174, #1187), and the // 14-digit string will be interpreted differently. if (args.length === 1) { var param = args[0]; if (/^\d{14}$/.test(param)) { // YYYYMMDDHHmmss var digitMatch = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(param); if (digitMatch) { // ..... year ... month .. date ... hour .... minute ..... second this._d = new Date(Date.UTC.apply(null, [digitMatch[1], digitMatch[2] - 1, digitMatch[3], digitMatch[4], digitMatch[5], digitMatch[6]])); } } else if (typeof param === 'string') { // Wikitext signature timestamp var dateParts = Morebits.l10n.signatureTimestampFormat(param); if (dateParts) { this._d = new Date(Date.UTC.apply(null, dateParts)); } } } if (!this._d) { // Try standard date this._d = new (Function.prototype.bind.apply(Date, [Date].concat(args))); } // Still no? if (!this.isValid()) { mw.log.warn('Invalid Morebits.date initialisation:', args); } }; /** * Localized strings for date processing. * * @memberof Morebits.date * @type {object.<string, string>} * @property {string[]} months * @property {string[]} monthsShort * @property {string[]} days * @property {string[]} daysShort * @property {object.<string, string>} relativeTimes * @private */ Morebits.date.localeData = { months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], monthsShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], daysShort: ['日', '一', '二', '三', '四', '五', '六'], relativeTimes: { thisDay: '[今天]A hh:mm', prevDay: '[昨天]A hh:mm', nextDay: '[明天]A hh:mm', thisWeek: 'ddddA hh:mm', pastWeek: '[上]ddddA hh:mm', other: 'YYYY-MM-DD' } }; /** * Map units with getter/setter function names, for `add` and `subtract` * methods. * * @memberof Morebits.date * @type {object.<string, string>} * @property {string} seconds * @property {string} minutes * @property {string} hours * @property {string} days * @property {string} weeks * @property {string} months * @property {string} years */ Morebits.date.unitMap = { seconds: 'Seconds', minutes: 'Minutes', hours: 'Hours', days: 'Date', weeks: 'Week', // Not a function but handled in `add` through cunning use of multiplication months: 'Month', years: 'FullYear' }; Morebits.date.prototype = { /** @returns {boolean} */ isValid: function() { return !isNaN(this.getTime()); }, /** * @param {(Date|Morebits.date)} date * @returns {boolean} */ isBefore: function(date) { return this.getTime() < date.getTime(); }, /** * @param {(Date|Morebits.date)} date * @returns {boolean} */ isAfter: function(date) { return this.getTime() > date.getTime(); }, /** @returns {string} */ getUTCMonthName: function() { return Morebits.date.localeData.months[this.getUTCMonth()]; }, /** @returns {string} */ getUTCMonthNameAbbrev: function() { return Morebits.date.localeData.monthsShort[this.getUTCMonth()]; }, /** @returns {string} */ getMonthName: function() { return Morebits.date.localeData.months[this.getMonth()]; }, /** @returns {string} */ getMonthNameAbbrev: function() { return Morebits.date.localeData.monthsShort[this.getMonth()]; }, /** @returns {string} */ getUTCDayName: function() { return Morebits.date.localeData.days[this.getUTCDay()]; }, /** @returns {string} */ getUTCDayNameAbbrev: function() { return Morebits.date.localeData.daysShort[this.getUTCDay()]; }, /** @returns {string} */ getDayName: function() { return Morebits.date.localeData.days[this.getDay()]; }, /** @returns {string} */ getDayNameAbbrev: function() { return Morebits.date.localeData.daysShort[this.getDay()]; }, /** * Add a given number of minutes, hours, days, weeks, months, or years to the date. * This is done in-place. The modified date object is also returned, allowing chaining. * * @param {number} number - Should be an integer. * @param {string} unit * @throws If invalid or unsupported unit is given. * @returns {Morebits.date} */ add: function(number, unit) { var num = parseInt(number, 10); // normalize if (isNaN(num)) { throw new Error('Invalid number "' + number + '" provided.'); } unit = unit.toLowerCase(); // normalize var unitMap = Morebits.date.unitMap; var unitNorm = unitMap[unit] || unitMap[unit + 's']; // so that both singular and plural forms work if (unitNorm) { // No built-in week functions, so rather than build out ISO's getWeek/setWeek, just multiply // Probably can't be used for Julian->Gregorian changeovers, etc. if (unitNorm === 'Week') { unitNorm = 'Date', num *= 7; } this['set' + unitNorm](this['get' + unitNorm]() + num); return this; } throw new Error('Invalid unit "' + unit + '": Only ' + Object.keys(unitMap).join(', ') + ' are allowed.'); }, /** * Subtracts a given number of minutes, hours, days, weeks, months, or years to the date. * This is done in-place. The modified date object is also returned, allowing chaining. * * @param {number} number - Should be an integer. * @param {string} unit * @throws If invalid or unsupported unit is given. * @returns {Morebits.date} */ subtract: function(number, unit) { return this.add(-number, unit); }, /** * Formats the date into a string per the given format string. * Replacement syntax is a subset of that in moment.js: * * | Syntax | Output | * |--------|--------| * | H | Hours (24-hour) | * | HH | Hours (24-hour, padded) | * | h | Hours (12-hour) | * | hh | Hours (12-hour, padded) | * | A | AM or PM | * | m | Minutes | * | mm | Minutes (padded) | * | s | Seconds | * | ss | Seconds (padded) | * | SSS | Milliseconds fragment, padded | * | d | Day number of the week (Sun=0) | * | ddd | Abbreviated day name | * | dddd | Full day name | * | D | Date | * | DD | Date (padded) | * | M | Month number (0-indexed) | * | MM | Month number (0-indexed, padded) | * | MMM | Abbreviated month name | * | MMMM | Full month name | * | Y | Year | * | YY | Final two digits of year (20 for 2020, 42 for 1942) | * | YYYY | Year (same as `Y`) | * * @param {string} formatstr - Format the date into a string, using * the replacement syntax. Use `[` and `]` to escape items. If not * provided, will return the ISO-8601-formatted string. * @param {(string|number)} [zone=system] - `system` (for browser-default time zone), * `utc`, or specify a time zone as number of minutes relative to UTC. * @returns {string} */ format: function(formatstr, zone) { if (!this.isValid()) { return 'Invalid date'; // Put the truth out, preferable to "NaNNaNNan NaN:NaN" or whatever } var udate = this; // create a new date object that will contain the date to display as system time if (zone === 'utc') { udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset(), 'minutes'); } else if (typeof zone === 'number') { // convert to utc, then add the utc offset given udate = new Morebits.date(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes'); } // default to ISOString if (!formatstr) { return udate.toISOString(); } var pad = function(num, len) { len = len || 2; // Up to length of 00 + 1 return ('00' + num).toString().slice(0 - len); }; var h24 = udate.getHours(), m = udate.getMinutes(), s = udate.getSeconds(), ms = udate.getMilliseconds(); var D = udate.getDate(), M = udate.getMonth() + 1, Y = udate.getFullYear(); var h12 = h24 % 12 || 12, amOrPm = h24 >= 12 ? '下午' : '上午'; var replacementMap = { HH: pad(h24), H: h24, hh: pad(h12), h: h12, A: amOrPm, mm: pad(m), m: m, ss: pad(s), s: s, SSS: pad(ms, 3), dddd: udate.getDayName(), ddd: udate.getDayNameAbbrev(), d: udate.getDay(), DD: pad(D), D: D, MMMM: udate.getMonthName(), MMM: udate.getMonthNameAbbrev(), MM: pad(M), M: M, YYYY: Y, YY: pad(Y % 100), Y: Y }; var unbinder = new Morebits.unbinder(formatstr); // escape stuff between [...] unbinder.unbind('\\[', '\\]'); unbinder.content = unbinder.content.replace( /* Regex notes: * d(d{2,3})? matches exactly 1, 3 or 4 occurrences of 'd' ('dd' is treated as a double match of 'd') * Y{1,2}(Y{2})? matches exactly 1, 2 or 4 occurrences of 'Y' */ /H{1,2}|h{1,2}|m{1,2}|s{1,2}|SSS|d(d{2,3})?|D{1,2}|M{1,4}|Y{1,2}(Y{2})?|A/g, function(match) { return replacementMap[match]; } ); return unbinder.rebind().replace(/\[(.*?)\]/g, '$1'); }, /** * Gives a readable relative time string such as "Yesterday at 6:43 PM" or "Last Thursday at 11:45 AM". * Similar to `calendar` in moment.js, but with time zone support. * * @param {(string|number)} [zone=system] - 'system' (for browser-default time zone), * 'utc' (for UTC), or specify a time zone as number of minutes past UTC. * @returns {string} */ calendar: function(zone) { // Zero out the hours, minutes, seconds and milliseconds - keeping only the date; // find the difference. Note that setHours() returns the same thing as getTime(). var dateDiff = (new Date().setHours(0, 0, 0, 0) - new Date(this).setHours(0, 0, 0, 0)) / 8.64e7; switch (true) { case dateDiff === 0: return this.format(Morebits.date.localeData.relativeTimes.thisDay, zone); case dateDiff === 1: return this.format(Morebits.date.localeData.relativeTimes.prevDay, zone); case dateDiff > 0 && dateDiff < 7: return this.format(Morebits.date.localeData.relativeTimes.pastWeek, zone); case dateDiff === -1: return this.format(Morebits.date.localeData.relativeTimes.nextDay, zone); case dateDiff < 0 && dateDiff > -7: return this.format(Morebits.date.localeData.relativeTimes.thisWeek, zone); default: return this.format(Morebits.date.localeData.relativeTimes.other, zone); } }, /** * Get a regular expression that matches wikitext section titles, such * as `==December 2019==` or `=== Jan 2018 ===`. * * @returns {RegExp} */ monthHeaderRegex: function() { return new RegExp('^(==+)\\s*' + this.getUTCFullYear() + '年(?:' + this.getUTCMonthName() + '|' + this.getUTCMonthNameAbbrev() + ')\\s*\\1', 'mg'); }, /** * Creates a wikitext section header with the month and year. * * @param {number} [level=2] - Header level. Pass 0 for just the text * with no wikitext markers (==). * @returns {string} */ monthHeader: function(level) { // Default to 2, but allow for 0 or stringy numbers level = parseInt(level, 10); level = isNaN(level) ? 2 : level; var header = Array(level + 1).join('='); // String.prototype.repeat not supported in IE 11 var text = this.getUTCFullYear() + '年' + this.getUTCMonthName(); if (header.length) { // wikitext-formatted header return header + ' ' + text + ' ' + header; } return text; // Just the string } }; // Allow native Date.prototype methods to be used on Morebits.date objects Object.getOwnPropertyNames(Date.prototype).forEach(function(func) { // Exclude methods that collide with PageTriage's Date.js external, which clobbers native Date: [[phab:T268513]] if (['add', 'getDayName', 'getMonthName'].indexOf(func) === -1) { Morebits.date.prototype[func] = function() { return this._d[func].apply(this._d, Array.prototype.slice.call(arguments)); }; } }); /* **************** Morebits.wiki **************** */ /** * Various objects for wiki editing and API access, including {@link * Morebits.wiki.api} and {@link Morebits.wiki.page}. * * @namespace Morebits.wiki * @memberof Morebits */ Morebits.wiki = {}; /** * @deprecated in favor of Morebits.isPageRedirect as of November 2020 * @memberof Morebits.wiki * @returns {boolean} */ Morebits.wiki.isPageRedirect = function wikipediaIsPageRedirect() { console.warn('NOTE: Morebits.wiki.isPageRedirect has been deprecated, use Morebits.isPageRedirect instead.'); // eslint-disable-line no-console return Morebits.isPageRedirect(); }; /* **************** Morebits.wiki.actionCompleted **************** */ /** * @memberof Morebits.wiki * @type {number} */ Morebits.wiki.numberOfActionsLeft = 0; /** * @memberof Morebits.wiki * @type {number} */ Morebits.wiki.nbrOfCheckpointsLeft = 0; /** * Display message and/or redirect to page upon completion of tasks. * * Every call to Morebits.wiki.api.post() results in the dispatch of an * asynchronous callback. Each callback can in turn make an additional call to * Morebits.wiki.api.post() to continue a processing sequence. At the * conclusion of the final callback of a processing sequence, it is not * possible to simply return to the original caller because there is no call * stack leading back to the original context. Instead, * Morebits.wiki.actionCompleted.event() is called to display the result to * the user and to perform an optional page redirect. * * The determination of when to call Morebits.wiki.actionCompleted.event() is * managed through the globals Morebits.wiki.numberOfActionsLeft and * Morebits.wiki.nbrOfCheckpointsLeft. Morebits.wiki.numberOfActionsLeft is * incremented at the start of every Morebits.wiki.api call and decremented * after the completion of a callback function. If a callback function does * not create a new Morebits.wiki.api object before exiting, it is the final * step in the processing chain and Morebits.wiki.actionCompleted.event() will * then be called. * * Optionally, callers may use Morebits.wiki.addCheckpoint() to indicate that * processing is not complete upon the conclusion of the final callback * function. This is used for batch operations. The end of a batch is * signaled by calling Morebits.wiki.removeCheckpoint(). * * @memberof Morebits.wiki */ Morebits.wiki.actionCompleted = function(self) { if (--Morebits.wiki.numberOfActionsLeft <= 0 && Morebits.wiki.nbrOfCheckpointsLeft <= 0) { Morebits.wiki.actionCompleted.event(self); } }; // Change per action wanted /** @memberof Morebits.wiki */ Morebits.wiki.actionCompleted.event = function() { if (Morebits.wiki.actionCompleted.notice) { Morebits.status.actionCompleted(Morebits.wiki.actionCompleted.notice); } if (Morebits.wiki.actionCompleted.redirect) { // if it isn't a URL, make it one. TODO: This breaks on the articles 'http://', 'ftp://', and similar ones. if (!(/^\w+:\/\//).test(Morebits.wiki.actionCompleted.redirect)) { Morebits.wiki.actionCompleted.redirect = mw.util.getUrl(Morebits.wiki.actionCompleted.redirect); if (Morebits.wiki.actionCompleted.followRedirect === false) { Morebits.wiki.actionCompleted.redirect += '?redirect=no'; } } window.setTimeout(function() { window.location = Morebits.wiki.actionCompleted.redirect; }, Morebits.wiki.actionCompleted.timeOut); } }; /** @memberof Morebits.wiki */ Morebits.wiki.actionCompleted.timeOut = typeof window.wpActionCompletedTimeOut === 'undefined' ? 5000 : window.wpActionCompletedTimeOut; /** @memberof Morebits.wiki */ Morebits.wiki.actionCompleted.redirect = null; /** @memberof Morebits.wiki */ Morebits.wiki.actionCompleted.notice = null; /** @memberof Morebits.wiki */ Morebits.wiki.addCheckpoint = function() { ++Morebits.wiki.nbrOfCheckpointsLeft; }; /** @memberof Morebits.wiki */ Morebits.wiki.removeCheckpoint = function() { if (--Morebits.wiki.nbrOfCheckpointsLeft <= 0 && Morebits.wiki.numberOfActionsLeft <= 0) { Morebits.wiki.actionCompleted.event(); } }; /* **************** Morebits.wiki.api **************** */ /** * An easy way to talk to the MediaWiki API. Accepts either json or xml * (default) formats; if json is selected, will default to `formatversion=2` * unless otherwise specified. Similarly, enforces newer `errorformat`s, * defaulting to `html` if unspecified. `uselang` enforced to the wiki's * content language. * * In new code, the use of the last 3 parameters should be avoided, instead * use {@link Morebits.wiki.api#setStatusElement|setStatusElement()} to bind * the status element (if needed) and use `.then()` or `.catch()` on the * promise returned by `post()`, rather than specify the `onSuccess` or * `onFailure` callbacks. * * @memberof Morebits.wiki * @class * @param {string} currentAction - The current action (required). * @param {object} query - The query (required). * @param {Function} [onSuccess] - The function to call when request is successful. * @param {Morebits.status} [statusElement] - A Morebits.status object to use for status messages. * @param {Function} [onError] - The function to call if an error occurs. */ Morebits.wiki.api = function(currentAction, query, onSuccess, statusElement, onError) { this.currentAction = currentAction; this.query = query; this.query.assert = 'user'; // Enforce newer error formats, preferring html if (!query.errorformat || ['wikitext', 'plaintext'].indexOf(query.errorformat) === -1) { this.query.errorformat = 'html'; } // Explicitly use the wiki's content language to minimize confusion, // see #1179 for discussion this.query.uselang = this.query.uselang || 'content'; this.query.errorlang = 'uselang'; this.query.errorsuselocal = 1; this.onSuccess = onSuccess; this.onError = onError; if (statusElement) { this.setStatusElement(statusElement); } else { this.statelem = new Morebits.status(currentAction); } // JSON is used throughout Morebits/Twinkle, but xml remains the default for backwards compatibility if (!query.format) { this.query.format = 'xml'; } else if (query.format === 'json' && !query.formatversion) { this.query.formatversion = '2'; } else if (['xml', 'json'].indexOf(query.format) === -1) { this.statelem.error('Invalid API format: only xml and json are supported.'); } // Ignore tags for queries and most common unsupported actions, produces warnings if (query.action && ['query', 'review', 'stabilize', 'pagetriageaction', 'watch'].indexOf(query.action) !== -1) { delete query.tags; } else if (!query.tags && morebitsWikiChangeTag) { query.tags = morebitsWikiChangeTag; } }; Morebits.wiki.api.prototype = { currentAction: '', onSuccess: null, onError: null, parent: window, // use global context if there is no parent object query: null, response: null, responseXML: null, // use `response` instead; retained for backwards compatibility statelem: null, // this non-standard name kept for backwards compatibility statusText: null, // result received from the API, normally "success" or "error" errorCode: null, // short text error code, if any, as documented in the MediaWiki API errorText: null, // full error description, if any badtokenRetry: false, // set to true if this on a retry attempted after a badtoken error /** * Keep track of parent object for callbacks. * * @param {*} parent */ setParent: function(parent) { this.parent = parent; }, /** @param {Morebits.status} statusElement */ setStatusElement: function(statusElement) { this.statelem = statusElement; this.statelem.status(this.currentAction); }, /** * Carry out the request. * * @param {object} callerAjaxParameters - Do not specify a parameter unless you really * really want to give jQuery some extra parameters. * @returns {promise} - A jQuery promise object that is resolved or rejected with the api object. */ post: function(callerAjaxParameters) { ++Morebits.wiki.numberOfActionsLeft; var queryString = $.map(this.query, function(val, i) { if (Array.isArray(val)) { return encodeURIComponent(i) + '=' + val.map(encodeURIComponent).join('|'); } else if (val !== undefined) { return encodeURIComponent(i) + '=' + encodeURIComponent(val); } }).join('&').replace(/^(.*?)(\btoken=[^&]*)&(.*)/, '$1$3&$2'); // token should always be the last item in the query string (bug TW-B-0013) var ajaxparams = $.extend({}, { context: this, type: this.query.action === 'query' ? 'GET' : 'POST', url: mw.util.wikiScript('api'), data: queryString, dataType: this.query.format, headers: { 'Api-User-Agent': morebitsWikiApiUserAgent } }, callerAjaxParameters); return $.ajax(ajaxparams).then( function onAPIsuccess(response, statusText) { this.statusText = statusText; this.response = this.responseXML = response; // Limit to first error if (this.query.format === 'json') { this.errorCode = response.errors && response.errors[0].code; if (this.query.errorformat === 'html') { this.errorText = response.errors && response.errors[0].html; } else if (this.query.errorformat === 'wikitext' || this.query.errorformat === 'plaintext') { this.errorText = response.errors && response.errors[0].text; } } else { this.errorCode = $(response).find('errors error').eq(0).attr('code'); // Sufficient for html, wikitext, or plaintext errorformats this.errorText = $(response).find('errors error').eq(0).text(); } if (typeof this.errorCode === 'string') { // the API didn't like what we told it, e.g., bad edit token or an error creating a page return this.returnError(callerAjaxParameters); } // invoke success callback if one was supplied if (this.onSuccess) { // set the callback context to this.parent for new code and supply the API object // as the first argument to the callback (for legacy code) this.onSuccess.call(this.parent, this); } else { this.statelem.info('完成'); } Morebits.wiki.actionCompleted(); return $.Deferred().resolveWith(this.parent, [this]); }, // only network and server errors reach here - complaints from the API itself are caught in success() function onAPIfailure(jqXHR, statusText, errorThrown) { this.statusText = statusText; this.errorThrown = errorThrown; // frequently undefined this.errorText = statusText + wgULS('在调用API时发生了错误“', '在呼叫API時發生了錯誤「') + jqXHR.statusText + wgULS('”。', '」。'); return this.returnError(); } ); }, returnError: function(callerAjaxParameters) { if (this.errorCode === 'badtoken' && !this.badtokenRetry) { this.statelem.warn(wgULS('无效令牌,获取新的令牌并重试…', '無效權杖,取得新的權杖並重試…')); this.badtokenRetry = true; // Get a new CSRF token and retry. If the original action needs a different // type of action than CSRF, we do one pointless retry before bailing out return Morebits.wiki.api.getToken().then(function(token) { this.query.token = token; return this.post(callerAjaxParameters); }.bind(this)); } this.statelem.error(this.errorText + '(' + this.errorCode + ')'); // invoke failure callback if one was supplied if (this.onError) { // set the callback context to this.parent for new code and supply the API object // as the first argument to the callback for legacy code this.onError.call(this.parent, this); } // don't complete the action so that the error remains displayed return $.Deferred().rejectWith(this.parent, [this]); }, getStatusElement: function() { return this.statelem; }, getErrorCode: function() { return this.errorCode; }, getErrorText: function() { return this.errorText; }, getXML: function() { // retained for backwards compatibility, use getResponse() instead return this.responseXML; }, getResponse: function() { return this.response; } }; /** * Custom user agent header, used by WMF for server-side logging. Set via * {@link Morebits.wiki.api.setApiUserAgent|setApiUserAgent}. * * @see {@link https://lists.wikimedia.org/pipermail/mediawiki-api-announce/2014-November/000075.html} * for original announcement. * * @memberof Morebits.wiki.api * @type {string} */ var morebitsWikiApiUserAgent = 'morebits.js~zh ([[w:zh:WT:TW]])'; /** * Sets the custom user agent header. * * @memberof Morebits.wiki.api * @param {string} [ua] - User agent. */ Morebits.wiki.api.setApiUserAgent = function(ua) { morebitsWikiApiUserAgent = (ua ? ua + ' ' : '') + 'morebits.js~zh ([[w:zh:WT:TW]])'; }; /** * Change/revision tag applied to Morebits actions when no other tags are specified. * Defaults to unused per {@link https://en.wikipedia.org/w/index.php?oldid=970618849#Adding_tags_to_Twinkle_edits_and_actions|consensus}. * * @constant * @memberof Morebits.wiki.api * @type {string} */ var morebitsWikiChangeTag = ''; /** * Get a new CSRF token on encountering token errors. * * @memberof Morebits.wiki.api * @returns {string} MediaWiki CSRF token. */ Morebits.wiki.api.getToken = function() { var tokenApi = new Morebits.wiki.api(wgULS('获取令牌', '取得權杖'), { action: 'query', meta: 'tokens', type: 'csrf', format: 'json' }); return tokenApi.post().then(function(apiobj) { return apiobj.response.query.tokens.csrftoken; }); }; /* **************** Morebits.wiki.page **************** */ /** * Use the MediaWiki API to load a page and optionally edit it, move it, etc. * * Callers are not permitted to directly access the properties of this class! * All property access is through the appropriate get___() or set___() method. * * Callers should set {@link Morebits.wiki.actionCompleted.notice} and {@link Morebits.wiki.actionCompleted.redirect} * before the first call to {@link Morebits.wiki.page.load()}. * * Each of the callback functions takes one parameter, which is a * reference to the Morebits.wiki.page object that registered the callback. * Callback functions may invoke any Morebits.wiki.page prototype method using this reference. * * * Call sequence for common operations (optional final user callbacks not shown): * * - Edit current contents of a page (no edit conflict): * `.load(userTextEditCallback) -> ctx.loadApi.post() -> * ctx.loadApi.post.success() -> ctx.fnLoadSuccess() -> userTextEditCallback() -> * .save() -> ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()` * * - Edit current contents of a page (with edit conflict): * `.load(userTextEditCallback) -> ctx.loadApi.post() -> * ctx.loadApi.post.success() -> ctx.fnLoadSuccess() -> userTextEditCallback() -> * .save() -> ctx.saveApi.post() -> ctx.loadApi.post.success() -> * ctx.fnSaveError() -> ctx.loadApi.post() -> ctx.loadApi.post.success() -> * ctx.fnLoadSuccess() -> userTextEditCallback() -> .save() -> * ctx.saveApi.post() -> ctx.loadApi.post.success() -> ctx.fnSaveSuccess()` * * - Append to a page (similar for prepend and newSection): * `.append() -> ctx.loadApi.post() -> ctx.loadApi.post.success() -> * ctx.fnLoadSuccess() -> ctx.fnAutoSave() -> .save() -> ctx.saveApi.post() -> * ctx.loadApi.post.success() -> ctx.fnSaveSuccess()` * * Notes: * 1. All functions following Morebits.wiki.api.post() are invoked asynchronously from the jQuery AJAX library. * 2. The sequence for append/prepend/newSection could be slightly shortened, * but it would require significant duplication of code for little benefit. * * * @memberof Morebits.wiki * @class * @param {string} pageName - The name of the page, prefixed by the namespace (if any). * For the current page, use `mw.config.get('wgPageName')`. * @param {string|Morebits.status} [status] - A string describing the action about to be undertaken, * or a Morebits.status object */ Morebits.wiki.page = function(pageName, status) { if (!status) { status = wgULS('打开页面“', '打開頁面「') + pageName + wgULS('”', '」'); } /** * Private context variables. * * This context is not visible to the outside, thus all the data here * must be accessed via getter and setter functions. * * @private */ var ctx = { // backing fields for public properties pageName: pageName, pageExists: false, editSummary: null, changeTags: null, testActions: null, // array if any valid actions callbackParameters: null, statusElement: status instanceof Morebits.status ? status : new Morebits.status(status), // - edit pageText: null, editMode: 'all', // save() replaces entire contents of the page by default appendText: null, // can't reuse pageText for this because pageText is needed to follow a redirect prependText: null, // can't reuse pageText for this because pageText is needed to follow a redirect newSectionText: null, newSectionTitle: null, createOption: null, minorEdit: false, botEdit: false, pageSection: null, maxConflictRetries: 2, maxRetries: 2, followRedirect: false, followCrossNsRedirect: true, watchlistOption: 'nochange', watchlistExpiry: null, creator: null, timestamp: null, // - revert revertOldID: null, // - move moveDestination: null, moveTalkPage: false, moveSubpages: false, moveSuppressRedirect: false, // - protect protectEdit: null, protectMove: null, protectCreate: null, protectCascade: null, // - creation lookup lookupNonRedirectCreator: false, // - stabilize (FlaggedRevs) flaggedRevs: null, // internal status pageLoaded: false, csrfToken: null, loadTime: null, lastEditTime: null, pageID: null, contentModel: null, revertCurID: null, revertUser: null, watched: false, fullyProtected: false, suppressProtectWarning: false, conflictRetries: 0, retries: 0, // callbacks onLoadSuccess: null, onLoadFailure: null, onSaveSuccess: null, onSaveFailure: null, onLookupCreationSuccess: null, onLookupCreationFailure: null, onMoveSuccess: null, onMoveFailure: null, onDeleteSuccess: null, onDeleteFailure: null, onUndeleteSuccess: null, onUndeleteFailure: null, onProtectSuccess: null, onProtectFailure: null, onStabilizeSuccess: null, onStabilizeFailure: null, // internal objects loadQuery: null, loadApi: null, saveApi: null, lookupCreationApi: null, moveApi: null, moveProcessApi: null, patrolApi: null, patrolProcessApi: null, triageApi: null, triageProcessListApi: null, triageProcessApi: null, deleteApi: null, deleteProcessApi: null, undeleteApi: null, undeleteProcessApi: null, protectApi: null, protectProcessApi: null, stabilizeApi: null, stabilizeProcessApi: null }; var emptyFunction = function() { }; /** * Loads the text for the page. * * @param {Function} onSuccess - Callback function which is called when the load has succeeded. * @param {Function} [onFailure] - Callback function which is called when the load fails. */ this.load = function(onSuccess, onFailure) { ctx.onLoadSuccess = onSuccess; ctx.onLoadFailure = onFailure || emptyFunction; // Need to be able to do something after the page loads if (!onSuccess) { ctx.statusElement.error('Internal error: no onSuccess callback provided to load()!'); ctx.onLoadFailure(this); return; } ctx.loadQuery = { action: 'query', prop: 'info|revisions', inprop: 'watched', intestactions: 'edit', // can be expanded curtimestamp: '', meta: 'tokens', type: 'csrf', titles: ctx.pageName, format: 'json' // don't need rvlimit=1 because we don't need rvstartid here and only one actual rev is returned by default }; if (ctx.editMode === 'all') { ctx.loadQuery.rvprop = 'content|timestamp'; // get the page content at the same time, if needed } else if (ctx.editMode === 'revert') { ctx.loadQuery.rvprop = 'timestamp'; ctx.loadQuery.rvlimit = 1; ctx.loadQuery.rvstartid = ctx.revertOldID; } if (ctx.followRedirect) { ctx.loadQuery.redirects = ''; // follow all redirects } if (typeof ctx.pageSection === 'number') { ctx.loadQuery.rvsection = ctx.pageSection; } if (Morebits.userIsSysop) { ctx.loadQuery.inprop += '|protection'; } ctx.loadApi = new Morebits.wiki.api(wgULS('抓取页面…', '抓取頁面…'), ctx.loadQuery, fnLoadSuccess, ctx.statusElement, ctx.onLoadFailure); ctx.loadApi.setParent(this); ctx.loadApi.post(); }; /** * Saves the text for the page to Wikipedia. * Must be preceded by successfully calling `load()`. * * Warning: Calling `save()` can result in additional calls to the * previous `load()` callbacks to recover from edit conflicts! In this * case, callers must make the same edit to the new pageText and * reinvoke `save()`. This behavior can be disabled with * `setMaxConflictRetries(0)`. * * @param {Function} [onSuccess] - Callback function which is called when the save has succeeded. * @param {Function} [onFailure] - Callback function which is called when the save fails. */ this.save = function(onSuccess, onFailure) { ctx.onSaveSuccess = onSuccess; ctx.onSaveFailure = onFailure || emptyFunction; // are we getting our editing token from mw.user.tokens? var canUseMwUserToken = fnCanUseMwUserToken('edit'); if (!ctx.pageLoaded && !canUseMwUserToken) { ctx.statusElement.error('Internal error: attempt to save a page that has not been loaded!'); ctx.onSaveFailure(this); return; } if (!ctx.editSummary) { // new section mode allows (nay, encourages) using the // title as the edit summary, but the query needs // editSummary to be undefined or '', not null if (ctx.editMode === 'new' && ctx.newSectionTitle) { ctx.editSummary = ''; } else { ctx.statusElement.error('Internal error: edit summary not set before save!'); ctx.onSaveFailure(this); return; } } // shouldn't happen if canUseMwUserToken === true if (ctx.fullyProtected && !ctx.suppressProtectWarning && !confirm(wgULS('您即将编辑全保护页面“', '您即將編輯全保護頁面「') + ctx.pageName + (ctx.fullyProtected === 'infinity' ? wgULS('”(永久)', '」(永久)') : wgULS('”(到期:', '」(到期:') + new Morebits.date(ctx.fullyProtected).calendar('utc') + ' (UTC))') + wgULS('。\n\n单击确定以确定,或单击取消以取消操作。', '。\n\n點擊確定以確定,或點擊取消以取消操作。'))) { ctx.statusElement.error(wgULS('已取消对全保护页面的编辑。', '已取消對全保護頁面的編輯。')); ctx.onSaveFailure(this); return; } ctx.retries = 0; var query = { action: 'edit', title: ctx.pageName, summary: ctx.editSummary, token: canUseMwUserToken ? mw.user.tokens.get('csrfToken') : ctx.csrfToken, watchlist: ctx.watchlistOption, format: 'json' }; if (ctx.changeTags) { query.tags = ctx.changeTags; } if (fnApplyWatchlistExpiry()) { query.watchlistexpiry = ctx.watchlistExpiry; } if (typeof ctx.pageSection === 'number') { query.section = ctx.pageSection; } // Set minor edit attribute. If these parameters are present with any value, it is interpreted as true if (ctx.minorEdit) { query.minor = true; } else { query.notminor = true; // force Twinkle config to override user preference setting for "all edits are minor" } // Set bot edit attribute. If this paramter is present with any value, it is interpreted as true if (ctx.botEdit) { query.bot = true; } switch (ctx.editMode) { case 'append': if (ctx.appendText === null) { ctx.statusElement.error('Internal error: append text not set before save!'); ctx.onSaveFailure(this); return; } query.appendtext = ctx.appendText; // use mode to append to current page contents break; case 'prepend': if (ctx.prependText === null) { ctx.statusElement.error('Internal error: prepend text not set before save!'); ctx.onSaveFailure(this); return; } query.prependtext = ctx.prependText; // use mode to prepend to current page contents break; case 'new': if (!ctx.newSectionText) { // API doesn't allow empty new section text ctx.statusElement.error('Internal error: new section text not set before save!'); ctx.onSaveFailure(this); return; } query.section = 'new'; query.text = ctx.newSectionText; // add a new section to current page query.sectiontitle = ctx.newSectionTitle || ctx.editSummary; // done by the API, but non-'' values would get treated as text break; case 'revert': query.undo = ctx.revertCurID; query.undoafter = ctx.revertOldID; if (ctx.lastEditTime) { query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded } query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff) break; default: // 'all' query.text = ctx.pageText; // replace entire contents of the page if (ctx.lastEditTime) { query.basetimestamp = ctx.lastEditTime; // check that page hasn't been edited since it was loaded } query.starttimestamp = ctx.loadTime; // check that page hasn't been deleted since it was loaded (don't recreate bad stuff) break; } if (['recreate', 'createonly', 'nocreate'].indexOf(ctx.createOption) !== -1) { query[ctx.createOption] = ''; } if (canUseMwUserToken && ctx.followRedirect) { query.redirect = true; } ctx.saveApi = new Morebits.wiki.api(wgULS('保存页面…', '儲存頁面…'), query, fnSaveSuccess, ctx.statusElement, fnSaveError); ctx.saveApi.setParent(this); ctx.saveApi.post(); }; /** * Adds the text provided via `setAppendText()` to the end of the * page. Does not require calling `load()` first, unless a watchlist * expiry is used. * * @param {Function} [onSuccess] - Callback function which is called when the method has succeeded. * @param {Function} [onFailure] - Callback function which is called when the method fails. */ this.append = function(onSuccess, onFailure) { ctx.editMode = 'append'; if (fnCanUseMwUserToken('edit')) { this.save(onSuccess, onFailure); } else { ctx.onSaveSuccess = onSuccess; ctx.onSaveFailure = onFailure || emptyFunction; this.load(fnAutoSave, ctx.onSaveFailure); } }; /** * Adds the text provided via `setPrependText()` to the start of the * page. Does not require calling `load()` first, unless a watchlist * expiry is used. * * @param {Function} [onSuccess] - Callback function which is called when the method has succeeded. * @param {Function} [onFailure] - Callback function which is called when the method fails. */ this.prepend = function(onSuccess, onFailure) { ctx.editMode = 'prepend'; if (fnCanUseMwUserToken('edit')) { this.save(onSuccess, onFailure); } else { ctx.onSaveSuccess = onSuccess; ctx.onSaveFailure = onFailure || emptyFunction; this.load(fnAutoSave, ctx.onSaveFailure); } }; /** * Creates a new section with the text provided by `setNewSectionText()` * and section title from `setNewSectionTitle()`. * If `editSummary` is provided, that will be used instead of the * autogenerated "->Title (new section" edit summary. * Does not require calling `load()` first, unless a watchlist expiry * is used. * * @param {Function} [onSuccess] - Callback function which is called when the method has succeeded. * @param {Function} [onFailure] - Callback function which is called when the method fails. */ this.newSection = function(onSuccess, onFailure) { ctx.editMode = 'new'; if (fnCanUseMwUserToken('edit')) { this.save(onSuccess, onFailure); } else { ctx.onSaveSuccess = onSuccess; ctx.onSaveFailure = onFailure || emptyFunction; this.load(fnAutoSave, ctx.onSaveFailure); } }; /** @returns {string} The name of the loaded page, including the namespace */ this.getPageName = function() { return ctx.pageName; }; /** @returns {string} The text of the page after a successful load() */ this.getPageText = function() { return ctx.pageText; }; /** @param {string} pageText - Updated page text that will be saved when `save()` is called */ this.setPageText = function(pageText) { ctx.editMode = 'all'; ctx.pageText = pageText; }; /** @param {string} appendText - Text that will be appended to the page when `append()` is called */ this.setAppendText = function(appendText) { ctx.editMode = 'append'; ctx.appendText = appendText; }; /** @param {string} prependText - Text that will be prepended to the page when `prepend()` is called */ this.setPrependText = function(prependText) { ctx.editMode = 'prepend'; ctx.prependText = prependText; }; /** @param {string} newSectionText - Text that will be added in a new section on the page when `newSection()` is called */ this.setNewSectionText = function(newSectionText) { ctx.editMode = 'new'; ctx.newSectionText = newSectionText; }; /** * @param {string} newSectionTitle - Title for the new section created when `newSection()` is called * If missing, `ctx.editSummary` will be used. Issues may occur if a substituted template is used. */ this.setNewSectionTitle = function(newSectionTitle) { ctx.editMode = 'new'; ctx.newSectionTitle = newSectionTitle; }; // Edit-related setter methods: /** * Set the edit summary that will be used when `save()` is called. * Unnecessary if editMode is 'new' and newSectionTitle is provided. * * @param {string} summary */ this.setEditSummary = function(summary) { ctx.editSummary = summary; }; /** * Set any custom tag(s) to be applied to the API action. * A number of actions don't support it, most notably watch, review, * and stabilize ({@link https://phabricator.wikimedia.org/T247721|T247721}), and * pagetriageaction ({@link https://phabricator.wikimedia.org/T252980|T252980}). * * @param {string|string[]} tags - String or array of tag(s). */ this.setChangeTags = function(tags) { ctx.changeTags = tags; }; /** * @param {string} [createOption=null] - Can take the following four values: * - recreate: create the page if it does not exist, or edit it if it exists. * - createonly: create the page if it does not exist, but return an * error if it already exists. * - nocreate: don't create the page, only edit it if it already exists. * - `null`: create the page if it does not exist, unless it was deleted * in the moment between loading the page and saving the edit (default). * */ this.setCreateOption = function(createOption) { ctx.createOption = createOption; }; /** @param {boolean} minorEdit - Set true to mark the edit as a minor edit. */ this.setMinorEdit = function(minorEdit) { ctx.minorEdit = minorEdit; }; /** @param {boolean} botEdit - Set true to mark the edit as a bot edit */ this.setBotEdit = function(botEdit) { ctx.botEdit = botEdit; }; /** * @param {number} pageSection - Integer specifying the section number to load or save. * If specified as `null`, the entire page will be retrieved. */ this.setPageSection = function(pageSection) { ctx.pageSection = pageSection; }; /** * @param {number} maxConflictRetries - Number of retries for save errors involving an edit conflict or * loss of token. Default: 2. */ this.setMaxConflictRetries = function(maxConflictRetries) { ctx.maxConflictRetries = maxConflictRetries; }; /** * @param {number} maxRetries - Number of retries for save errors not involving an edit conflict or * loss of token. Default: 2. */ this.setMaxRetries = function(maxRetries) { ctx.maxRetries = maxRetries; }; /** * Set whether and how to watch the page, including setting an expiry. * * @param {boolean|string|Morebits.date|Date} [watchlistOption=false] - * Basically a mix of MW API and Twinkley options available pre-expiry: * - `true`|`'yes'`|`'watch'`: page will be added to the user's * watchlist when the action is called. Defaults to an indefinite * watch unless `watchlistExpiry` is provided. * - `false`|`'no'`|`'nochange'`: watchlist status of the page (including expiry) will not be changed. * - `'default'`|`'preferences'`: watchlist status of the page will be * set based on the user's preference settings when the action is * called. Defaults to an indefinite watch unless `watchlistExpiry` is * provided. * - `'unwatch'`: explicitly unwatch the page. * - Any other `string` or `number`, or a `Morebits.date` or `Date` * object: watch page until the specified time, deferring to * `watchlistExpiry` if provided. * @param {string|number|Morebits.date|Date} [watchlistExpiry=infinity] - * A date-like string or number, or a date object. If a string or number, * can be relative (2 weeks) or other similarly date-like (i.e. NOT "potato"): * ISO 8601: 2038-01-09T03:14:07Z * MediaWiki: 20380109031407 * UNIX: 2147483647 * SQL: 2038-01-09 03:14:07 * Can also be `infinity` or infinity-like (`infinite`, `indefinite`, and `never`). * See {@link https://phabricator.wikimedia.org/source/mediawiki-libs-Timestamp/browse/master/src/ConvertibleTimestamp.php;4e53b859a9580c55958078f46dd4f3a44d0fcaa0$57-109?as=source&blame=off} */ this.setWatchlist = function(watchlistOption, watchlistExpiry) { if (watchlistOption instanceof Morebits.date || watchlistOption instanceof Date) { watchlistOption = watchlistOption.toISOString(); } if (typeof watchlistExpiry === 'undefined') { watchlistExpiry = 'infinity'; } else if (watchlistExpiry instanceof Morebits.date || watchlistExpiry instanceof Date) { watchlistExpiry = watchlistExpiry.toISOString(); } switch (watchlistOption) { case 'nochange': case 'no': case false: case undefined: ctx.watchlistOption = 'nochange'; // The MW API allows for changing expiry with nochange (as "nochange" refers to the binary status), // but by keeping this null it will default to any existing expiry, ensure there is actually "no change." ctx.watchlistExpiry = null; break; case 'unwatch': // expiry unimportant ctx.watchlistOption = 'unwatch'; break; case 'preferences': case 'default': ctx.watchlistOption = 'preferences'; // The API allows an expiry here, but there is as of yet (T265716) // no expiry preference option, so it's a bit devoid of context. ctx.watchlistExpiry = watchlistExpiry; break; case 'watch': case 'yes': case true: ctx.watchlistOption = 'watch'; ctx.watchlistExpiry = watchlistExpiry; break; default: // Not really a "default" per se but catches "any other string" ctx.watchlistOption = 'watch'; ctx.watchlistExpiry = watchlistOption; break; } }; /** * Set a watchlist expiry. setWatchlist can mostly handle this by * itself, so this is here largely for completeness and compatibility * with the full suite of options. * * @param {string|number|Morebits.date|Date} [watchlistExpiry=infinity] - * A date-like string or number, or a date object. If a string or number, * can be relative (2 weeks) or other similarly date-like (i.e. NOT "potato"): * ISO 8601: 2038-01-09T03:14:07Z * MediaWiki: 20380109031407 * UNIX: 2147483647 * SQL: 2038-01-09 03:14:07 * Can also be `infinity` or infinity-like (`infinite`, `indefinite`, and `never`). * See {@link https://phabricator.wikimedia.org/source/mediawiki-libs-Timestamp/browse/master/src/ConvertibleTimestamp.php;4e53b859a9580c55958078f46dd4f3a44d0fcaa0$57-109?as=source&blame=off} */ this.setWatchlistExpiry = function(watchlistExpiry) { if (typeof watchlistExpiry === 'undefined') { watchlistExpiry = 'infinity'; } else if (watchlistExpiry instanceof Morebits.date || watchlistExpiry instanceof Date) { watchlistExpiry = watchlistExpiry.toISOString(); } ctx.watchlistExpiry = watchlistExpiry; }; /** * @deprecated As of December 2020, use setWatchlist. * @param {boolean} [watchlistOption=false] - * - `True`: page watchlist status will be set based on the user's * preference settings when `save()` is called. * - `False`: watchlist status of the page will not be changed. * * Watchlist notes: * 1. The MediaWiki API value of 'unwatch', which explicitly removes * the page from the user's watchlist, is not used. * 2. If both `setWatchlist()` and `setWatchlistFromPreferences()` are * called, the last call takes priority. * 3. Twinkle modules should use the appropriate preference to set the watchlist options. * 4. Most Twinkle modules use `setWatchlist()`. `setWatchlistFromPreferences()` * is only needed for the few Twinkle watchlist preferences that * accept a string value of `default`. */ this.setWatchlistFromPreferences = function(watchlistOption) { console.warn('NOTE: Morebits.wiki.page.setWatchlistFromPreferences was deprecated December 2020, please use setWatchlist'); // eslint-disable-line no-console if (watchlistOption) { ctx.watchlistOption = 'preferences'; } else { ctx.watchlistOption = 'nochange'; } }; /** * @param {boolean} [followRedirect=false] - * - `true`: a maximum of one redirect will be followed. In the event * of a redirect, a message is displayed to the user and the redirect * target can be retrieved with getPageName(). * - `false`: (default) the requested pageName will be used without regard to any redirect. * @param {boolean} [followCrossNsRedirect=true] - Not applicable if `followRedirect` is not set true. * - `true`: (default) follow redirect even if it is a cross-namespace redirect * - `false`: don't follow redirect if it is cross-namespace, edit the redirect itself. */ this.setFollowRedirect = function(followRedirect, followCrossNsRedirect) { if (ctx.pageLoaded) { ctx.statusElement.error('Internal error: cannot change redirect setting after the page has been loaded!'); return; } ctx.followRedirect = followRedirect; ctx.followCrossNsRedirect = typeof followCrossNsRedirect !== 'undefined' ? followCrossNsRedirect : ctx.followCrossNsRedirect; }; // lookup-creation setter function /** * @param {boolean} flag - If set true, the author and timestamp of * the first non-redirect version of the page is retrieved. * * Warning: * 1. If there are no revisions among the first 50 that are * non-redirects, or if there are less 50 revisions and all are * redirects, the original creation is retrived. * 2. Revisions that the user is not privileged to access * (revdeled/suppressed) will be treated as non-redirects. * 3. Must not be used when the page has a non-wikitext contentmodel * such as Modulespace Lua or user JavaScript/CSS. */ this.setLookupNonRedirectCreator = function(flag) { ctx.lookupNonRedirectCreator = flag; }; // Move-related setter functions /** @param {string} destination */ this.setMoveDestination = function(destination) { ctx.moveDestination = destination; }; /** @param {boolean} flag */ this.setMoveTalkPage = function(flag) { ctx.moveTalkPage = !!flag; }; /** @param {boolean} flag */ this.setMoveSubpages = function(flag) { ctx.moveSubpages = !!flag; }; /** @param {boolean} flag */ this.setMoveSuppressRedirect = function(flag) { ctx.moveSuppressRedirect = !!flag; }; // Protect-related setter functions /** * @param {string} level - The right required for the specific action * e.g. autoconfirmed, sysop, templateeditor, extendedconfirmed * (enWiki-only). * @param {string} [expiry=infinity] */ this.setEditProtection = function(level, expiry) { ctx.protectEdit = { level: level, expiry: expiry || 'infinity' }; }; this.setMoveProtection = function(level, expiry) { ctx.protectMove = { level: level, expiry: expiry || 'infinity' }; }; this.setCreateProtection = function(level, expiry) { ctx.protectCreate = { level: level, expiry: expiry || 'infinity' }; }; this.setCascadingProtection = function(flag) { ctx.protectCascade = !!flag; }; this.suppressProtectWarning = function() { ctx.suppressProtectWarning = true; }; // Revert-related getters/setters: this.setOldID = function(oldID) { ctx.revertOldID = oldID; }; /** @returns {string} The current revision ID of the page */ this.getCurrentID = function() { return ctx.revertCurID; }; /** @returns {string} Last editor of the page */ this.getRevisionUser = function() { return ctx.revertUser; }; /** @returns {string} ISO 8601 timestamp at which the page was last edited. */ this.getLastEditTime = function() { return ctx.lastEditTime; }; // Miscellaneous getters/setters: /** * Define an object for use in a callback function. * * `callbackParameters` is for use by the caller only. The parameters * allow a caller to pass the proper context into its callback * function. Callers must ensure that any changes to the * callbackParameters object within a `load()` callback still permit a * proper re-entry into the `load()` callback if an edit conflict is * detected upon calling `save()`. * * @param {object} callbackParameters */ this.setCallbackParameters = function(callbackParameters) { ctx.callbackParameters = callbackParameters; }; /** * @returns {object} - The object previously set by `setCallbackParameters()`. */ this.getCallbackParameters = function() { return ctx.callbackParameters; }; /** * @param {Morebits.status} statusElement */ this.setStatusElement = function(statusElement) { ctx.statusElement = statusElement; }; /** * @returns {Morebits.status} Status element created by the constructor. */ this.getStatusElement = function() { return ctx.statusElement; }; /** * @param {string} level - The right required for edits not to require * review. Possible options: none, autoconfirmed, review (not on enWiki). * @param {string} [expiry=infinity] */ this.setFlaggedRevs = function(level, expiry) { ctx.flaggedRevs = { level: level, expiry: expiry || 'infinity' }; }; /** * @returns {boolean} True if the page existed on the wiki when it was last loaded. */ this.exists = function() { return ctx.pageExists; }; /** * @returns {string} Page ID of the page loaded. 0 if the page doesn't * exist. */ this.getPageID = function() { return ctx.pageID; }; /** * @returns {string} - Content model of the page. Possible values * include (but may not be limited to): `wikitext`, `javascript`, * `css`, `json`, `Scribunto`, `sanitized-css`, `MassMessageListContent`. * Also gettable via `mw.config.get('wgPageContentModel')`. */ this.getContentModel = function() { return ctx.contentModel; }; /** * @returns {boolean|string} - Watched status of the page. Boolean * unless it's being watched temporarily, in which case returns the * expiry string. */ this.getWatched = function () { return ctx.watched; }; /** * @returns {string} ISO 8601 timestamp at which the page was last loaded. */ this.getLoadTime = function() { return ctx.loadTime; }; /** * @returns {string} The user who created the page following `lookupCreation()`. */ this.getCreator = function() { return ctx.creator; }; /** * @returns {string} The ISOString timestamp of page creation following `lookupCreation()`. */ this.getCreationTimestamp = function() { return ctx.timestamp; }; /** @returns {boolean} whether or not you can edit the page */ this.canEdit = function() { return !!ctx.testActions && ctx.testActions.indexOf('edit') !== -1; }; /** * Retrieves the username of the user who created the page as well as * the timestamp of creation. The username can be retrieved using the * `getCreator()` function; the timestamp can be retrieved using the * `getCreationTimestamp()` function. * Prior to June 2019 known as `lookupCreator()`. * * @param {Function} onSuccess - Callback function to be called when * the username and timestamp are found within the callback. * @param {Function} [onFailure] - Callback function to be called when * the lookup fails */ this.lookupCreation = function(onSuccess, onFailure) { ctx.onLookupCreationSuccess = onSuccess; ctx.onLookupCreationFailure = onFailure || emptyFunction; if (!onSuccess) { ctx.statusElement.error('Internal error: no onSuccess callback provided to lookupCreation()!'); ctx.onLookupCreationFailure(this); return; } var query = { action: 'query', prop: 'revisions', titles: ctx.pageName, rvlimit: 1, rvprop: 'user|timestamp', rvdir: 'newer', format: 'json' }; // Only the wikitext content model can reliably handle // rvsection, others return an error when paired with the // content rvprop. Relatedly, non-wikitext models don't // understand the #REDIRECT concept, so we shouldn't attempt // the redirect resolution in fnLookupCreationSuccess if (ctx.lookupNonRedirectCreator) { query.rvsection = 0; query.rvprop += '|content'; } if (ctx.followRedirect) { query.redirects = ''; // follow all redirects } ctx.lookupCreationApi = new Morebits.wiki.api(wgULS('抓取页面创建者信息', '抓取頁面建立者資訊'), query, fnLookupCreationSuccess, ctx.statusElement, ctx.onLookupCreationFailure); ctx.lookupCreationApi.setParent(this); ctx.lookupCreationApi.post(); }; /** * Reverts a page to `revertOldID` set by `setOldID`. * * @param {Function} [onSuccess] - Callback function to run on success. * @param {Function} [onFailure] - Callback function to run on failure. */ this.revert = function(onSuccess, onFailure) { ctx.onSaveSuccess = onSuccess; ctx.onSaveFailure = onFailure || emptyFunction; if (!ctx.revertOldID) { ctx.statusElement.error('Internal error: revision ID to revert to was not set before revert!'); ctx.onSaveFailure(this); return; } ctx.editMode = 'revert'; this.load(fnAutoSave, ctx.onSaveFailure); }; /** * Moves a page to another title. * * @param {Function} [onSuccess] - Callback function to run on success. * @param {Function} [onFailure] - Callback function to run on failure. */ this.move = function(onSuccess, onFailure) { ctx.onMoveSuccess = onSuccess; ctx.onMoveFailure = onFailure || emptyFunction; if (!fnPreflightChecks.call(this, 'move', ctx.onMoveFailure)) { return; // abort } if (!ctx.moveDestination) { ctx.statusElement.error('Internal error: destination page name was not set before move!'); ctx.onMoveFailure(this); return; } if (fnCanUseMwUserToken('move')) { fnProcessMove.call(this, this); } else { var query = fnNeedTokenInfoQuery('move'); ctx.moveApi = new Morebits.wiki.api(wgULS('获取令牌…', '取得權杖…'), query, fnProcessMove, ctx.statusElement, ctx.onMoveFailure); ctx.moveApi.setParent(this); ctx.moveApi.post(); } }; /** * Marks the page as patrolled, using `rcid` (if available) or `revid`. * * Patrolling as such doesn't need to rely on loading the page in * question; simply passing a revid to the API is sufficient, so in * those cases just using {@link Morebits.wiki.api} is probably preferable. * * No error handling since we don't actually care about the errors. */ this.patrol = function() { if (!Morebits.userIsSysop && !Morebits.userIsInGroup('patroller')) { return; } // If a link is present, don't need to check if it's patrolled if ($('.patrollink').length) { var patrolhref = $('.patrollink a').attr('href'); ctx.rcid = mw.util.getParamValue('rcid', patrolhref); fnProcessPatrol(this, this); } else { var patrolQuery = { action: 'query', prop: 'info', meta: 'tokens', type: 'patrol', // as long as we're querying, might as well get a token list: 'recentchanges', // check if the page is unpatrolled titles: ctx.pageName, rcprop: 'patrolled', rctitle: ctx.pageName, rclimit: 1, format: 'json' }; ctx.patrolApi = new Morebits.wiki.api(wgULS('获取令牌…', '取得權杖…'), patrolQuery, fnProcessPatrol); ctx.patrolApi.setParent(this); ctx.patrolApi.post(); } }; /** * Marks the page as reviewed by the PageTriage extension. * * Will, by it's nature, mark as patrolled as well. Falls back to * patrolling if not in an appropriate namespace. * * Doesn't inherently rely on loading the page in question; simply * passing a `pageid` to the API is sufficient, so in those cases just * using {@link Morebits.wiki.api} is probably preferable. * * Will first check if the page is queued via {@link * Morebits.wiki.page~fnProcessTriageList|fnProcessTriageList}. * * No error handling since we don't actually care about the errors. * * @see {@link https://www.mediawiki.org/wiki/Extension:PageTriage} Referred to as "review" on-wiki. */ this.triage = function() { // Fall back to patrol if not a valid triage namespace if (mw.config.get('pageTriageNamespaces').indexOf(new mw.Title(ctx.pageName).getNamespaceId()) === -1) { this.patrol(); } else { if (!Morebits.userIsSysop && !Morebits.userIsInGroup('patroller')) { return; } // If on the page in question, don't need to query for page ID if (new mw.Title(Morebits.pageNameNorm).getPrefixedText() === new mw.Title(ctx.pageName).getPrefixedText()) { ctx.pageID = mw.config.get('wgArticleId'); fnProcessTriageList(this, this); } else { var query = fnNeedTokenInfoQuery('triage'); ctx.triageApi = new Morebits.wiki.api(wgULS('获取令牌…', '取得權杖…'), query, fnProcessTriageList); ctx.triageApi.setParent(this); ctx.triageApi.post(); } } }; // |delete| is a reserved word in some flavours of JS /** * Deletes a page (for admins only). * * @param {Function} [onSuccess] - Callback function to run on success. * @param {Function} [onFailure] - Callback function to run on failure. */ this.deletePage = function(onSuccess, onFailure) { ctx.onDeleteSuccess = onSuccess; ctx.onDeleteFailure = onFailure || emptyFunction; if (!fnPreflightChecks.call(this, 'delete', ctx.onDeleteFailure)) { return; // abort } if (fnCanUseMwUserToken('delete')) { fnProcessDelete.call(this, this); } else { var query = fnNeedTokenInfoQuery('delete'); ctx.deleteApi = new Morebits.wiki.api(wgULS('获取令牌…', '取得權杖…'), query, fnProcessDelete, ctx.statusElement, ctx.onDeleteFailure); ctx.deleteApi.setParent(this); ctx.deleteApi.post(); } }; /** * Undeletes a page (for admins only). * * @param {Function} [onSuccess] - Callback function to run on success. * @param {Function} [onFailure] - Callback function to run on failure. */ this.undeletePage = function(onSuccess, onFailure) { ctx.onUndeleteSuccess = onSuccess; ctx.onUndeleteFailure = onFailure || emptyFunction; if (!fnPreflightChecks.call(this, 'undelete', ctx.onUndeleteFailure)) { return; // abort } if (fnCanUseMwUserToken('undelete')) { fnProcessUndelete.call(this, this); } else { var query = fnNeedTokenInfoQuery('undelete'); ctx.undeleteApi = new Morebits.wiki.api(wgULS('获取令牌…', '取得權杖…'), query, fnProcessUndelete, ctx.statusElement, ctx.onUndeleteFailure); ctx.undeleteApi.setParent(this); ctx.undeleteApi.post(); } }; /** * Protects a page (for admins only). * * @param {Function} [onSuccess] - Callback function to run on success. * @param {Function} [onFailure] - Callback function to run on failure. */ this.protect = function(onSuccess, onFailure) { ctx.onProtectSuccess = onSuccess; ctx.onProtectFailure = onFailure || emptyFunction; if (!fnPreflightChecks.call(this, 'protect', ctx.onProtectFailure)) { return; // abort } if (!ctx.protectEdit && !ctx.protectMove && !ctx.protectCreate) { ctx.statusElement.error('Internal error: you must set edit and/or move and/or create protection before calling protect()!'); ctx.onProtectFailure(this); return; } // because of the way MW API interprets protection levels // (absolute, not differential), we always need to request // protection levels from the server var query = fnNeedTokenInfoQuery('protect'); ctx.protectApi = new Morebits.wiki.api(wgULS('获取令牌…', '取得權杖…'), query, fnProcessProtect, ctx.statusElement, ctx.onProtectFailure); ctx.protectApi.setParent(this); ctx.protectApi.post(); }; /** * Apply FlaggedRevs protection settings. Only works on wikis where * the extension is installed (`$wgFlaggedRevsProtection = true` * i.e. where FlaggedRevs settings appear on the "protect" tab). * * @see {@link https://www.mediawiki.org/wiki/Extension:FlaggedRevs} * Referred to as "pending changes" on-wiki. * * @param {Function} [onSuccess] * @param {Function} [onFailure] */ this.stabilize = function(onSuccess, onFailure) { ctx.onStabilizeSuccess = onSuccess; ctx.onStabilizeFailure = onFailure || emptyFunction; if (!fnPreflightChecks.call(this, 'FlaggedRevs', ctx.onStabilizeFailure)) { return; // abort } if (!ctx.flaggedRevs) { ctx.statusElement.error('Internal error: you must set flaggedRevs before calling stabilize()!'); ctx.onStabilizeFailure(this); return; } if (fnCanUseMwUserToken('stabilize')) { fnProcessStabilize.call(this, this); } else { var query = fnNeedTokenInfoQuery('stabilize'); ctx.stabilizeApi = new Morebits.wiki.api(wgULS('获取令牌…', '取得權杖…'), query, fnProcessStabilize, ctx.statusElement, ctx.onStabilizeFailure); ctx.stabilizeApi.setParent(this); ctx.stabilizeApi.post(); } }; /* * Private member functions * These are not exposed outside */ /** * Determines whether we can save an API call by using the csrf token * sent with the page HTML, or whether we need to ask the server for * more info (e.g. protection or watchlist expiry). * * Currently used for `append`, `prepend`, `newSection`, `move`, * `stabilize`, `deletePage`, and `undeletePage`. Not used for * `protect` since it always needs to request protection status. * * @param {string} [action=edit] - The action being undertaken, e.g. * "edit" or "delete". In practice, only "edit" or "notedit" matters. * @returns {boolean} */ var fnCanUseMwUserToken = function(action) { action = typeof action !== 'undefined' ? action : 'edit'; // IE doesn't support default parameters // If a watchlist expiry is set, we must always load the page // to avoid overwriting indefinite protection. Of course, not // needed if setting indefinite watching! if (ctx.watchlistExpiry && !Morebits.string.isInfinity(ctx.watchlistExpiry)) { return false; } // API-based redirect resolution only works for action=query and // action=edit in append/prepend/new modes if (ctx.followRedirect) { if (!ctx.followCrossNsRedirect) { return false; // must load the page to check for cross namespace redirects } if (action !== 'edit' || (ctx.editMode === 'all' || ctx.editMode === 'revert')) { return false; } } // do we need to fetch the edit protection expiry? if (Morebits.userIsSysop && !ctx.suppressProtectWarning) { if (new mw.Title(Morebits.pageNameNorm).getPrefixedText() !== new mw.Title(ctx.pageName).getPrefixedText()) { return false; } // wgRestrictionEdit is null on non-existent pages, // so this neatly handles nonexistent pages var editRestriction = mw.config.get('wgRestrictionEdit'); if (!editRestriction || editRestriction.indexOf('sysop') !== -1) { return false; } } return !!mw.user.tokens.get('csrfToken'); }; /** * When functions can't use {@link * Morebits.wiki.page~fnCanUseMwUserToken|fnCanUseMwUserToken} or * require checking protection or watched status, maintain the query * in one place. Used for {@link Morebits.wiki.page#deletePage|delete}, * {@link Morebits.wiki.page#undeletePage|undelete}, * {@link* Morebits.wiki.page#protect|protect}, * {@link Morebits.wiki.page#stabilize|stabilize}, * and {@link Morebits.wiki.page#move|move} * (basically, just not {@link Morebits.wiki.page#load|load}). * * @param {string} action - The action being undertaken, e.g. "edit" or * "delete". * @returns {object} Appropriate query. */ var fnNeedTokenInfoQuery = function(action) { var query = { action: 'query', meta: 'tokens', type: 'csrf', titles: ctx.pageName, prop: 'info', inprop: 'watched', format: 'json' }; // Protection not checked for flagged-revs or non-sysop moves if (action !== 'stabilize' && (action !== 'move' || Morebits.userIsSysop)) { query.inprop += '|protection'; } if (ctx.followRedirect && action !== 'undelete') { query.redirects = ''; // follow all redirects } return query; }; // callback from loadSuccess() for append(), prepend(), and newSection() threads var fnAutoSave = function(pageobj) { pageobj.save(ctx.onSaveSuccess, ctx.onSaveFailure); }; // callback from loadApi.post() var fnLoadSuccess = function() { var response = ctx.loadApi.getResponse().query; if (!fnCheckPageName(response, ctx.onLoadFailure)) { return; // abort } var page = response.pages[0], rev; ctx.pageExists = !page.missing; if (ctx.pageExists) { rev = page.revisions[0]; ctx.lastEditTime = rev.timestamp; ctx.pageText = rev.content; ctx.pageID = page.pageid; } else { ctx.pageText = ''; // allow for concatenation, etc. ctx.pageID = 0; // nonexistent in response, matches wgArticleId } ctx.csrfToken = response.tokens.csrftoken; if (!ctx.csrfToken) { ctx.statusElement.error(wgULS('未能获取编辑令牌。', '未能取得編輯權杖。')); ctx.onLoadFailure(this); return; } ctx.loadTime = ctx.loadApi.getResponse().curtimestamp; if (!ctx.loadTime) { ctx.statusElement.error(wgULS('未能获取当前时间戳。', '未能取得當前時間戳。')); ctx.onLoadFailure(this); return; } ctx.contentModel = page.contentmodel; ctx.watched = page.watchlistexpiry || page.watched; // extract protection info, to alert admins when they are about to edit a protected page // Includes cascading protection if (Morebits.userIsSysop) { var editProt = page.protection.filter(function(pr) { return pr.type === 'edit' && pr.level === 'sysop'; }).pop(); if (editProt) { ctx.fullyProtected = editProt.expiry; } else { ctx.fullyProtected = false; } } ctx.revertCurID = page.lastrevid; var testactions = page.actions; ctx.testActions = []; // was null Object.keys(testactions).forEach(function(action) { if (testactions[action]) { ctx.testActions.push(action); } }); if (ctx.editMode === 'revert') { ctx.revertCurID = rev && rev.revid; if (!ctx.revertCurID) { ctx.statusElement.error(wgULS('未能获取当前修订版本ID。', '未能取得目前修訂版本ID。')); ctx.onLoadFailure(this); return; } ctx.revertUser = rev && rev.user; if (!ctx.revertUser) { if (rev && rev.userhidden) { // username was RevDel'd or oversighted ctx.revertUser = wgULS('<用户名已隐藏>', '<使用者名稱已隱藏>'); } else { ctx.statusElement.error(wgULS('未能获取此修订版本的编辑者。', '未能取得此修訂版本的編輯者。')); ctx.onLoadFailure(this); return; } } // set revert edit summary ctx.editSummary = '[[WP:UNDO|取消]]由 ' + ctx.revertUser + ' 所做出的' + wgULS('修订 ', '修訂 ') + ctx.revertOldID + ':' + ctx.editSummary; } ctx.pageLoaded = true; // alert("Generate edit conflict now"); // for testing edit conflict recovery logic ctx.onLoadSuccess(this); // invoke callback }; // helper function to parse the page name returned from the API var fnCheckPageName = function(response, onFailure) { if (!onFailure) { onFailure = emptyFunction; } var page = response.pages && response.pages[0]; if (page) { // check for invalid titles if (page.invalid) { ctx.statusElement.error(wgULS('标题不合法:', '標題不合法:' + ctx.pageName)); onFailure(this); return false; // abort } // retrieve actual title of the page after normalization and redirects var resolvedName = page.title; if (response.redirects) { // check for cross-namespace redirect: var origNs = new mw.Title(ctx.pageName).namespace; var newNs = new mw.Title(resolvedName).namespace; if (origNs !== newNs && !ctx.followCrossNsRedirect) { ctx.statusElement.error(ctx.pageName + wgULS('是跨命名空间重定向到', '是跨命名空間重新導向到') + resolvedName + wgULS(',略过', ',略過')); onFailure(this); return false; } // only notify user for redirects, not normalization new Morebits.status(wgULS('信息', '資訊'), wgULS('从 ', '從 ') + ctx.pageName + wgULS(' 重定向到 ', ' 重新導向到 ') + resolvedName); } ctx.pageName = resolvedName; // update to redirect target or normalized name } else { // could be a circular redirect or other problem ctx.statusElement.error(wgULS('不能解析页面的重定向:', '不能解析頁面的重新導向:') + ctx.pageName); onFailure(this); // force error to stay on the screen ++Morebits.wiki.numberOfActionsLeft; return false; // abort } return true; // all OK }; /** * Determine whether we should provide a watchlist expiry. Will not * do so if the page is currently permanently watched, or the current * expiry is *after* the new, provided expiry. Only handles strings * recognized by {@link Morebits.date} or relative timeframes with * unit it can process. Relies on the fact that fnCanUseMwUserToken * requires page loading if a watchlistexpiry is provided, so we are * ensured of knowing the watch status by the use of this. * * @returns {boolean} */ var fnApplyWatchlistExpiry = function() { if (ctx.watchlistExpiry) { if (!ctx.watched || Morebits.string.isInfinity(ctx.watchlistExpiry)) { return true; } else if (typeof ctx.watched === 'string') { var newExpiry; // Attempt to determine if the new expiry is a // relative (e.g. `1 month`) or absolute datetime var rel = ctx.watchlistExpiry.split(' '); try { newExpiry = new Morebits.date().add(rel[0], rel[1]); } catch (e) { newExpiry = new Morebits.date(ctx.watchlistExpiry); } // If the date is valid, only use it if it extends the current expiry if (newExpiry.isValid()) { if (newExpiry.isAfter(new Morebits.date(ctx.watched))) { return true; } } else { // If it's still not valid, hope it's a valid MW expiry format that // Morebits.date doesn't recognize, so just default to using it. // This will also include minor typos. return true; } } } return false; }; // callback from saveApi.post() var fnSaveSuccess = function() { ctx.editMode = 'all'; // cancel append/prepend/newSection/revert modes var response = ctx.saveApi.getResponse(); // see if the API thinks we were successful if (response.edit.result === 'Success') { // real success // default on success action - display link for edited page var link = document.createElement('a'); link.setAttribute('href', mw.util.getUrl(ctx.pageName)); link.appendChild(document.createTextNode(ctx.pageName)); ctx.statusElement.info(['完成(', link, ')']); if (ctx.onSaveSuccess) { ctx.onSaveSuccess(this); // invoke callback } return; } // errors here are only generated by extensions which hook APIEditBeforeSave within MediaWiki, // which as of 1.34.0-wmf.23 (Sept 2019) should only encompass captcha messages if (response.edit.captcha) { ctx.statusElement.error(wgULS('不能保存页面,因维基服务器要求您输入验证码。', '不能儲存頁面,因維基伺服器要求您輸入驗證碼。')); } else { ctx.statusElement.error(wgULS('保存页面时由API得到未知错误', '儲存頁面時由API得到未知錯誤')); } // force error to stay on the screen ++Morebits.wiki.numberOfActionsLeft; ctx.onSaveFailure(this); }; // callback from saveApi.post() var fnSaveError = function() { var errorCode = ctx.saveApi.getErrorCode(); // check for edit conflict if (errorCode === 'editconflict' && ctx.conflictRetries++ < ctx.maxConflictRetries) { // edit conflicts can occur when the page needs to be purged from the server cache var purgeQuery = { action: 'purge', titles: ctx.pageName // redirects are already resolved }; var purgeApi = new Morebits.wiki.api(wgULS('检测到编辑冲突,正在更新服务器缓存', '檢測到編輯衝突,正在更新伺服器快取'), purgeQuery, function() { --Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds ctx.statusElement.info(wgULS('检测到编辑冲突,重试修改', '檢測到編輯衝突,重試修改')); if (fnCanUseMwUserToken('edit')) { ctx.saveApi.post(); // necessarily append, prepend, or newSection, so this should work as desired } else { ctx.loadApi.post(); // reload the page and reapply the edit } }, ctx.statusElement); purgeApi.post(); // check for network or server error } else if ((errorCode === null || errorCode === undefined) && ctx.retries++ < ctx.maxRetries) { // the error might be transient, so try again ctx.statusElement.info(wgULS('保存失败,在2秒后重试…', '儲存失敗,在2秒後重試…')); --Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds // wait for sometime for client to regain connnectivity sleep(2000).then(function() { ctx.saveApi.post(); // give it another go! }); // hard error, give up } else { var response = ctx.saveApi.getResponse(); var errorData = response.error || // bc error format response.errors[0].data; // html/wikitext/plaintext error format switch (errorCode) { case 'protectedpage': // non-admin attempting to edit a protected page - this gives a friendlier message than the default ctx.statusElement.error(wgULS('不能保存修改:页面被保护', '不能儲存修改:頁面被保護')); break; case 'abusefilter-disallowed': ctx.statusElement.error(wgULS('编辑被防滥用过滤器规则“', '編輯被防濫用過濾器規則「') + errorData.abusefilter.description + wgULS('”阻止。如果您认为您的该次编辑是有意义的,请至 Wikipedia:防滥用过滤器/错误报告 提报。', '」阻止。如果您認為您的該次編輯是有意義的,請至 Wikipedia:防濫用過濾器/錯誤報告 提報。')); break; case 'abusefilter-warning': ctx.statusElement.error([ wgULS('编辑被防滥用过滤器规则“', '編輯被防濫用過濾器規則「'), errorData.abusefilter.description, wgULS('”警告,如果您仍希望做出该编辑,请尝试重新提交,根据过滤器的设置您可能可以作出此编辑。', '」警告,如果您仍希望做出該編輯,請嘗試重新提交,根據過濾器的設定您可能可以作出此編輯。') ]); // We should provide the user with a way to automatically retry the action if they so choose - // I can't see how to do this without creating a UI dependency on Morebits.wiki.page though -- TTO break; case 'spamblacklist': // If multiple items are blacklisted, we only return the first var spam = errorData.spamblacklist.matches[0]; ctx.statusElement.error(wgULS('不能保存页面,因URL ', '不能儲存頁面,因URL ') + spam + wgULS(' 在垃圾链接黑名单中。', ' 在垃圾連結黑名單中。')); break; default: ctx.statusElement.error(wgULS('不能保存修改:', '不能儲存修改:') + ctx.saveApi.getErrorText()); } ctx.editMode = 'all'; // cancel append/prepend/newSection/revert modes if (ctx.onSaveFailure) { ctx.onSaveFailure(this); // invoke callback } } }; var isTextRedirect = function(text) { if (!text) { // no text - content empty or inaccessible (revdelled or suppressed) return false; } return Morebits.l10n.redirectTagAliases.some(function(tag) { return new RegExp('^\\s*' + tag + '\\W', 'i').test(text); }); }; var fnLookupCreationSuccess = function() { var response = ctx.lookupCreationApi.getResponse().query; if (!fnCheckPageName(response, ctx.onLookupCreationFailure)) { return; // abort } var rev = response.pages[0].revisions && response.pages[0].revisions[0]; if (!rev) { ctx.statusElement.error(wgULS('无法找到', '無法找到') + ctx.pageName + wgULS('的任何修订版本', '的任何修訂版本')); ctx.onLookupCreationFailure(this); return; } if (!ctx.lookupNonRedirectCreator || !isTextRedirect(rev.content)) { ctx.creator = rev.user; if (!ctx.creator) { ctx.statusElement.error(wgULS('无法获取页面创建者的名字', '無法取得頁面建立者的名字')); ctx.onLookupCreationFailure(this); return; } ctx.timestamp = rev.timestamp; if (!ctx.timestamp) { ctx.statusElement.error(wgULS('无法获取页面创建时间', '無法取得頁面建立時間')); ctx.onLookupCreationFailure(this); return; } ctx.statusElement.info(wgULS('已获取页面创建信息', '已取得頁面建立資訊')); ctx.onLookupCreationSuccess(this); } else { ctx.lookupCreationApi.query.rvlimit = 50; // modify previous query to fetch more revisions ctx.lookupCreationApi.query.titles = ctx.pageName; // update pageName if redirect resolution took place in earlier query ctx.lookupCreationApi = new Morebits.wiki.api(wgULS('获取页面创建信息', '取得頁面建立資訊'), ctx.lookupCreationApi.query, fnLookupNonRedirectCreator, ctx.statusElement, ctx.onLookupCreationFailure); ctx.lookupCreationApi.setParent(this); ctx.lookupCreationApi.post(); } }; var fnLookupNonRedirectCreator = function() { var response = ctx.lookupCreationApi.getResponse().query; var revs = response.pages[0].revisions; for (var i = 0; i < revs.length; i++) { if (!isTextRedirect(revs[i].content)) { ctx.creator = revs[i].user; ctx.timestamp = revs[i].timestamp; break; } } if (!ctx.creator) { // fallback to give first revision author if no non-redirect version in the first 50 ctx.creator = revs[0].user; ctx.timestamp = revs[0].timestamp; if (!ctx.creator) { ctx.statusElement.error(wgULS('无法获取页面创建者的名字', '無法取得頁面建立者的名字')); ctx.onLookupCreationFailure(this); return; } } if (!ctx.timestamp) { ctx.statusElement.error(wgULS('无法获取页面创建时间', '無法取得頁面建立時間')); ctx.onLookupCreationFailure(this); return; } ctx.statusElement.info(wgULS('已获取页面创建信息', '已取得頁面建立資訊')); ctx.onLookupCreationSuccess(this); }; /** * Common checks for action methods. Used for move, undelete, delete, * protect, stabilize * * @param {string} action - The action being checked. * @param {string} onFailure - Failure callback. * @returns {boolean} */ var fnPreflightChecks = function(action, onFailure) { // if a non-admin tries to do this, don't bother if (!Morebits.userIsSysop && action !== 'move') { ctx.statusElement.error(wgULS('无法对页面进行“', '無法對頁面進行「') + action + wgULS('”操作:只有管理员可以进行此操作', '」操作:只有管理員可以進行此操作')); onFailure(this); return false; } if (!ctx.editSummary) { ctx.statusElement.error('Internal error: ' + action + ' reason not set (use setEditSummary function)!'); onFailure(this); return false; } return true; // all OK }; /** * Common checks for fnProcess functions (`fnProcessDelete`, `fnProcessMove`, etc. * Used for move, undelete, delete, protect, stabilize. * * @param {string} action - The action being checked. * @param {string} onFailure - Failure callback. * @param {string} response - The response document from the API call. * @returns {boolean} */ var fnProcessChecks = function(action, onFailure, response) { var missing = response.pages[0].missing; // No undelete as an existing page could have deleted revisions var actionMissing = missing && ['delete', 'stabilize', 'move'].indexOf(action) !== -1; var protectMissing = action === 'protect' && missing && (ctx.protectEdit || ctx.protectMove); var saltMissing = action === 'protect' && !missing && ctx.protectCreate; if (actionMissing || protectMissing || saltMissing) { ctx.statusElement.error(wgULS('无法对页面进行“', '無法對頁面進行「') + action + wgULS('”操作,因为页面', '」操作,因為頁面') + (missing ? '已不' : wgULS('已经', '已經')) + '存在'); onFailure(this); return false; } // Delete, undelete, move // extract protection info var editprot; if (action === 'undelete') { editprot = response.pages[0].protection.filter(function(pr) { return pr.type === 'create' && pr.level === 'sysop'; }).pop(); } else if (action === 'delete' || action === 'move') { editprot = response.pages[0].protection.filter(function(pr) { return pr.type === 'edit' && pr.level === 'sysop'; }).pop(); } if (editprot && !ctx.suppressProtectWarning && !confirm(wgULS('您即将对全保护页面“', '您即將對全保護頁面「') + ctx.pageName + (editprot.expiry === 'infinity' ? wgULS('”(永久)', '」(永久)') : wgULS('”(到期:', '」(到期:') + new Morebits.date(editprot.expiry).calendar('utc') + ' (UTC))') + wgULS('”进行“', '」進行「') + action + wgULS('”操作', '」操作') + wgULS('。\n\n单击确定以继续操作,或单击取消以取消操作。', '。\n\n點擊確定以繼續操作,或點擊取消以取消操作。'))) { ctx.statusElement.error(wgULS('已取消对全保护页面的操作。', '已取消對全保護頁面的操作。')); onFailure(this); return false; } if (!response.tokens.csrftoken) { ctx.statusElement.error(wgULS('无法获取令牌。', '無法取得權杖。')); onFailure(this); return false; } return true; // all OK }; var fnProcessMove = function() { var pageTitle, token; if (fnCanUseMwUserToken('move')) { token = mw.user.tokens.get('csrfToken'); pageTitle = ctx.pageName; } else { var response = ctx.moveApi.getResponse().query; if (!fnProcessChecks('move', ctx.onMoveFailure, response)) { return; // abort } token = response.tokens.csrftoken; var page = response.pages[0]; pageTitle = page.title; ctx.watched = page.watchlistexpiry || page.watched; } var query = { action: 'move', from: pageTitle, to: ctx.moveDestination, token: token, reason: ctx.editSummary, watchlist: ctx.watchlistOption, format: 'json' }; if (ctx.changeTags) { query.tags = ctx.changeTags; } if (fnApplyWatchlistExpiry()) { query.watchlistexpiry = ctx.watchlistExpiry; } if (ctx.moveTalkPage) { query.movetalk = 'true'; } if (ctx.moveSubpages) { query.movesubpages = 'true'; } if (ctx.moveSuppressRedirect) { query.noredirect = 'true'; } ctx.moveProcessApi = new Morebits.wiki.api(wgULS('移动页面…', '移動頁面…'), query, ctx.onMoveSuccess, ctx.statusElement, ctx.onMoveFailure); ctx.moveProcessApi.setParent(this); ctx.moveProcessApi.post(); }; var fnProcessPatrol = function() { var query = { action: 'patrol', format: 'json' }; // Didn't need to load the page if (ctx.rcid) { query.rcid = ctx.rcid; query.token = mw.user.tokens.get('patrolToken'); } else { var response = ctx.patrolApi.getResponse().query; // Don't patrol if not unpatrolled if (!response.recentchanges[0].unpatrolled) { return; } var lastrevid = response.pages[0].lastrevid; if (!lastrevid) { return; } query.revid = lastrevid; var token = response.tokens.csrftoken; if (!token) { return; } query.token = token; } if (ctx.changeTags) { query.tags = ctx.changeTags; } var patrolStat = new Morebits.status(wgULS('标记页面为已巡查', '標記頁面為已巡查')); ctx.patrolProcessApi = new Morebits.wiki.api(wgULS('巡查页面…', '巡查頁面…'), query, null, patrolStat); ctx.patrolProcessApi.setParent(this); ctx.patrolProcessApi.post(); }; // Ensure that the page is curatable var fnProcessTriageList = function() { if (ctx.pageID) { ctx.csrfToken = mw.user.tokens.get('csrfToken'); } else { var response = ctx.triageApi.getResponse().query; ctx.pageID = response.pages[0].pageid; if (!ctx.pageID) { return; } ctx.csrfToken = response.tokens.csrftoken; if (!ctx.csrfToken) { return; } } var query = { action: 'pagetriagelist', page_id: ctx.pageID, format: 'json' }; ctx.triageProcessListApi = new Morebits.wiki.api('checking curation status...', query, fnProcessTriage); ctx.triageProcessListApi.setParent(this); ctx.triageProcessListApi.post(); }; // callback from triageProcessListApi.post() var fnProcessTriage = function() { var responseList = ctx.triageProcessListApi.getResponse().pagetriagelist; // Exit if not in the queue if (!responseList || responseList.result !== 'success') { return; } var page = responseList.pages && responseList.pages[0]; // Do nothing if page already triaged/patrolled if (!page || !parseInt(page.patrol_status, 10)) { var query = { action: 'pagetriageaction', pageid: ctx.pageID, reviewed: 1, // tags: ctx.changeTags, // pagetriage tag support: [[phab:T252980]] // Could use an adder to modify/create note: // summaryAd, but that seems overwrought token: ctx.csrfToken, format: 'json' }; var triageStat = new Morebits.status('Marking page as curated'); ctx.triageProcessApi = new Morebits.wiki.api('curating page...', query, null, triageStat); ctx.triageProcessApi.setParent(this); ctx.triageProcessApi.post(); } }; var fnProcessDelete = function() { var pageTitle, token; if (fnCanUseMwUserToken('delete')) { token = mw.user.tokens.get('csrfToken'); pageTitle = ctx.pageName; } else { var response = ctx.deleteApi.getResponse().query; if (!fnProcessChecks('delete', ctx.onDeleteFailure, response)) { return; // abort } token = response.tokens.csrftoken; var page = response.pages[0]; pageTitle = page.title; ctx.watched = page.watchlistexpiry || page.watched; } var query = { action: 'delete', title: pageTitle, token: token, reason: ctx.editSummary, watchlist: ctx.watchlistOption, format: 'json' }; if (ctx.changeTags) { query.tags = ctx.changeTags; } if (fnApplyWatchlistExpiry()) { query.watchlistexpiry = ctx.watchlistExpiry; } ctx.deleteProcessApi = new Morebits.wiki.api(wgULS('删除页面…', '刪除頁面…'), query, ctx.onDeleteSuccess, ctx.statusElement, fnProcessDeleteError); ctx.deleteProcessApi.setParent(this); ctx.deleteProcessApi.post(); }; // callback from deleteProcessApi.post() var fnProcessDeleteError = function() { var errorCode = ctx.deleteProcessApi.getErrorCode(); // check for "Database query error" if (errorCode === 'internal_api_error_DBQueryError' && ctx.retries++ < ctx.maxRetries) { ctx.statusElement.info(wgULS('数据库查询错误,重试', '資料庫查詢錯誤,重試')); --Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds ctx.deleteProcessApi.post(); // give it another go! } else if (errorCode === 'missingtitle') { ctx.statusElement.error(wgULS('不能删除页面,因其已不存在', '不能刪除頁面,因其已不存在')); if (ctx.onDeleteFailure) { ctx.onDeleteFailure.call(this, ctx.deleteProcessApi); // invoke callback } // hard error, give up } else { ctx.statusElement.error(wgULS('无法删除页面:', '無法刪除頁面:') + ctx.deleteProcessApi.getErrorText()); if (ctx.onDeleteFailure) { ctx.onDeleteFailure.call(this, ctx.deleteProcessApi); // invoke callback } } }; var fnProcessUndelete = function() { var pageTitle, token; if (fnCanUseMwUserToken('undelete')) { token = mw.user.tokens.get('csrfToken'); pageTitle = ctx.pageName; } else { var response = ctx.undeleteApi.getResponse().query; if (!fnProcessChecks('undelete', ctx.onUndeleteFailure, response)) { return; // abort } token = response.tokens.csrftoken; var page = response.pages[0]; pageTitle = page.title; ctx.watched = page.watchlistexpiry || page.watched; } var query = { action: 'undelete', title: pageTitle, token: token, reason: ctx.editSummary, watchlist: ctx.watchlistOption, format: 'json' }; if (ctx.changeTags) { query.tags = ctx.changeTags; } if (fnApplyWatchlistExpiry()) { query.watchlistexpiry = ctx.watchlistExpiry; } ctx.undeleteProcessApi = new Morebits.wiki.api(wgULS('还原页面…', '還原頁面…'), query, ctx.onUndeleteSuccess, ctx.statusElement, fnProcessUndeleteError); ctx.undeleteProcessApi.setParent(this); ctx.undeleteProcessApi.post(); }; // callback from undeleteProcessApi.post() var fnProcessUndeleteError = function() { var errorCode = ctx.undeleteProcessApi.getErrorCode(); // check for "Database query error" if (errorCode === 'internal_api_error_DBQueryError') { if (ctx.retries++ < ctx.maxRetries) { ctx.statusElement.info(wgULS('数据库查询错误,重试', '資料庫查詢錯誤,重試')); --Morebits.wiki.numberOfActionsLeft; // allow for normal completion if retry succeeds ctx.undeleteProcessApi.post(); // give it another go! } else { ctx.statusElement.error(wgULS('持续的数据库查询错误,重新加载页面并重试', '持續的資料庫查詢錯誤,重新載入頁面並重試')); if (ctx.onUndeleteFailure) { ctx.onUndeleteFailure.call(this, ctx.undeleteProcessApi); // invoke callback } } } else if (errorCode === 'cantundelete') { ctx.statusElement.error(wgULS('无法还原删除页面,因没有版本供还原或已被还原', '無法還原刪除頁面,因沒有版本供還原或已被還原')); if (ctx.onUndeleteFailure) { ctx.onUndeleteFailure.call(this, ctx.undeleteProcessApi); // invoke callback } // hard error, give up } else { ctx.statusElement.error(wgULS('无法还原页面:', '無法還原頁面:') + ctx.undeleteProcessApi.getErrorText()); if (ctx.onUndeleteFailure) { ctx.onUndeleteFailure.call(this, ctx.undeleteProcessApi); // invoke callback } } }; var fnProcessProtect = function() { var response = ctx.protectApi.getResponse().query; if (!fnProcessChecks('protect', ctx.onProtectFailure, response)) { return; // abort } var token = response.tokens.csrftoken; var page = response.pages[0]; var pageTitle = page.title; ctx.watched = page.watchlistexpiry || page.watched; // Fetch existing protection levels var prs = response.pages[0].protection; var editprot, moveprot, createprot; prs.forEach(function(pr) { // Filter out protection from cascading if (pr.type === 'edit' && !pr.source) { editprot = pr; } else if (pr.type === 'move') { moveprot = pr; } else if (pr.type === 'create') { createprot = pr; } }); // Fall back to current levels if not explicitly set if (!ctx.protectEdit && editprot) { ctx.protectEdit = { level: editprot.level, expiry: editprot.expiry }; } if (!ctx.protectMove && moveprot) { ctx.protectMove = { level: moveprot.level, expiry: moveprot.expiry }; } if (!ctx.protectCreate && createprot) { ctx.protectCreate = { level: createprot.level, expiry: createprot.expiry }; } // Default to pre-existing cascading protection if unchanged (similar to above) if (ctx.protectCascade === null) { ctx.protectCascade = !!prs.filter(function(pr) { return pr.cascade; }).length; } // Warn if cascading protection being applied with an invalid protection level, // which for edit protection will cause cascading to be silently stripped if (ctx.protectCascade) { // On move protection, this is technically stricter than the MW API, // but seems reasonable to avoid dumb values and misleading log entries (T265626) if (((!ctx.protectEdit || ctx.protectEdit.level !== 'sysop') || (!ctx.protectMove || ctx.protectMove.level !== 'sysop')) && !confirm(wgULS('您已对“', '您已對「') + ctx.pageName + wgULS('”启用了连锁保护', '」啟用了連鎖保護') + wgULS(',但没有设置仅管理员的保护级别。\n\n', ',但沒有設定僅管理員的保護級別。\n\n') + wgULS('单击确认以自动调整并继续连锁全保护,单击取消以跳过此操作', '點擊確認以自動調整並繼續連鎖全保護,點擊取消以跳過此操作'))) { ctx.statusElement.error(wgULS('连锁保护已取消。', '連鎖保護已取消。')); ctx.onProtectFailure(this); return; } ctx.protectEdit.level = 'sysop'; ctx.protectMove.level = 'sysop'; } // Build protection levels and expirys (expiries?) for query var protections = [], expirys = []; if (ctx.protectEdit) { protections.push('edit=' + ctx.protectEdit.level); expirys.push(ctx.protectEdit.expiry); } if (ctx.protectMove) { protections.push('move=' + ctx.protectMove.level); expirys.push(ctx.protectMove.expiry); } if (ctx.protectCreate) { protections.push('create=' + ctx.protectCreate.level); expirys.push(ctx.protectCreate.expiry); } var query = { action: 'protect', title: pageTitle, token: token, protections: protections.join('|'), expiry: expirys.join('|'), reason: ctx.editSummary, watchlist: ctx.watchlistOption, format: 'json' }; // Only shows up in logs, not page history [[phab:T259983]] if (ctx.changeTags) { query.tags = ctx.changeTags; } if (fnApplyWatchlistExpiry()) { query.watchlistexpiry = ctx.watchlistExpiry; } if (ctx.protectCascade) { query.cascade = 'true'; } ctx.protectProcessApi = new Morebits.wiki.api(wgULS('保护页面…', '保護頁面…'), query, ctx.onProtectSuccess, ctx.statusElement, ctx.onProtectFailure); ctx.protectProcessApi.setParent(this); ctx.protectProcessApi.post(); }; var fnProcessStabilize = function() { var pageTitle, token; if (fnCanUseMwUserToken('stabilize')) { token = mw.user.tokens.get('csrfToken'); pageTitle = ctx.pageName; } else { var response = ctx.stabilizeApi.getResponse().query; // 'stabilize' as a verb not necessarily well understood if (!fnProcessChecks('stabilize', ctx.onStabilizeFailure, response)) { return; // abort } token = response.tokens.csrftoken; pageTitle = response.pages[0].title; // ctx.watched = response.pages[0].watched; // Doesn't support watchlist expiry [[phab:T263336]] } var query = { action: 'stabilize', title: pageTitle, token: token, protectlevel: ctx.flaggedRevs.level, expiry: ctx.flaggedRevs.expiry, // tags: ctx.changeTags, // flaggedrevs tag support: [[phab:T247721]] reason: ctx.editSummary, watchlist: ctx.watchlistOption, format: 'json' }; /* Doesn't support watchlist expiry [[phab:T263336]] if (fnApplyWatchlistExpiry()) { query.watchlistexpiry = ctx.watchlistExpiry; } */ ctx.stabilizeProcessApi = new Morebits.wiki.api('configuring stabilization settings...', query, ctx.onStabilizeSuccess, ctx.statusElement, ctx.onStabilizeFailure); ctx.stabilizeProcessApi.setParent(this); ctx.stabilizeProcessApi.post(); }; var sleep = function(milliseconds) { var deferred = $.Deferred(); setTimeout(deferred.resolve, milliseconds); return deferred; }; }; // end Morebits.wiki.page /* Morebits.wiki.page TODO: (XXX) * - Should we retry loads also? * - Need to reset current action before the save? * - Deal with action.completed stuff * - Need to reset all parameters once done (e.g. edit summary, move destination, etc.) */ /** * Get username or ip range * @param {boolean} [allowBlock=false] * @returns {string} */ Morebits.relevantUserName = function (allowBlock) { var name = mw.config.get('wgRelevantUserName'); if (name) { return name; } else if (['Contributions', 'IPContributions'].indexOf(mw.config.get('wgCanonicalSpecialPageName')) !== -1) { var $blockLog = $('#contentSub').find('a[title^="Special:日志/block"], a[title^="Special:Log/block"]'); if ($blockLog.length && allowBlock) { return mw.util.getParamValue('page', $blockLog[0].href).replace('User:', ''); } } return null; }; /* **************** Morebits.wiki.preview **************** */ /** * Use the API to parse a fragment of wikitext and render it as HTML. * * The suggested implementation pattern (in {@link Morebits.simpleWindow} and * {@link Morebits.quickForm} situations) is to construct a * `Morebits.wiki.preview` object after rendering a `Morebits.quickForm`, and * bind the object to an arbitrary property of the form (e.g. |previewer|). * For an example, see twinklewarn.js. * * @memberof Morebits.wiki * @class * @param {HTMLElement} previewbox - The element that will contain the rendered HTML, * usually a <div> element. */ Morebits.wiki.preview = function(previewbox) { this.previewbox = previewbox; $(previewbox).addClass('morebits-previewbox').hide(); /** * Displays the preview box, and begins an asynchronous attempt * to render the specified wikitext. * * @param {string} wikitext - Wikitext to render; most things should work, including `subst:` and `~~~~`. * @param {string} [pageTitle] - Optional parameter for the page this should be rendered as being on, if omitted it is taken as the current page. * @param {string} [sectionTitle] - If provided, render the text as a new section using this as the title. */ this.beginRender = function(wikitext, pageTitle, sectionTitle) { $(previewbox).show(); var statusspan = document.createElement('span'); previewbox.appendChild(statusspan); Morebits.status.init(statusspan); // 如果页面不是wikitext(例如用户js/css等),那么找一个wikitext页面来预览。 var pageName = mw.config.get('wgPageName'); if (mw.config.get('wgPageContentModel') !== 'wikitext') { pageName = 'Draft:' + pageName; } var query = { action: 'parse', prop: ['text', 'modules'], pst: true, // PST = pre-save transform; this makes substitution work properly preview: true, text: wikitext, title: pageTitle || pageName, disablelimitreport: true, disableeditsection: true, uselang: mw.config.get('wgUserLanguage'), format: 'json' }; if (sectionTitle) { query.section = 'new'; query.sectiontitle = sectionTitle; } var renderApi = new Morebits.wiki.api(wgULS('加载中…', '載入中…'), query, fnRenderSuccess, new Morebits.status(wgULS('预览', '預覽'))); renderApi.post(); }; var fnRenderSuccess = function(apiobj) { var response = apiobj.getResponse(); var html = response.parse.text; if (!html) { apiobj.statelem.error(wgULS('加载预览失败,或模板为空', '載入預覽失敗,或模板為空')); return; } previewbox.innerHTML = html; mw.loader.load(response.parse.modulestyles); mw.loader.load(response.parse.modules); // this makes links open in new tab $(previewbox).find('a').attr('target', '_blank'); }; /** Hides the preview box and clears it. */ this.closePreview = function() { $(previewbox).empty().hide(); }; }; /* **************** Morebits.wikitext **************** */ /** * Wikitext manipulation. * * @namespace Morebits.wikitext * @memberof Morebits */ Morebits.wikitext = {}; /** * Get the value of every parameter found in the wikitext of a given template. * * @memberof Morebits.wikitext * @param {string} text - Wikitext containing a template. * @param {number} [start=0] - Index noting where in the text the template begins. * @returns {object} `{name: templateName, parameters: {key: value}}`. */ Morebits.wikitext.parseTemplate = function(text, start) { start = start || 0; var level = []; // Track of how deep we are ({{, {{{, or [[) var count = -1; // Number of parameters found var unnamed = 0; // Keep track of what number an unnamed parameter should receive var equals = -1; // After finding "=" before a parameter, the index; otherwise, -1 var current = ''; var result = { name: '', parameters: {} }; var key, value; /** * Function to handle finding parameter values. * * @param {boolean} [final=false] - Whether this is the final * parameter and we need to remove the trailing `}}`. */ function findParam(final) { // Nothing found yet, this must be the template name if (count === -1) { result.name = current.substring(2).trim(); ++count; } else { // In a parameter if (equals !== -1) { // We found an equals, so save the parameter as key: value key = current.substring(0, equals).trim(); value = final ? current.substring(equals + 1, current.length - 2).trim() : current.substring(equals + 1).trim(); result.parameters[key] = value; equals = -1; } else { // No equals, so it must be unnamed; no trim since whitespace allowed var param = final ? current.substring(equals + 1, current.length - 2) : current; if (param) { result.parameters[++unnamed] = param; ++count; } } } } for (var i = start; i < text.length; ++i) { var test3 = text.substr(i, 3); if (test3 === '{{{' || (test3 === '}}}' && level[level.length - 1] === 3)) { current += test3; i += 2; if (test3 === '{{{') { level.push(3); } else { level.pop(); } continue; } var test2 = text.substr(i, 2); // Entering a template (or link) if (test2 === '{{' || test2 === '[[') { current += test2; ++i; if (test2 === '{{') { level.push(2); } else { level.push('wl'); } continue; } // Either leaving a link or template/parser function if ((test2 === '}}' && level[level.length - 1] === 2) || (test2 === ']]' && level[level.length - 1] === 'wl')) { current += test2; ++i; level.pop(); // Find the final parameter if this really is the end if (test2 === '}}' && level.length === 0) { findParam(true); break; } continue; } if (text.charAt(i) === '|' && level.length === 1) { // Another pipe found, toplevel, so parameter coming up! findParam(); current = ''; } else if (equals === -1 && text.charAt(i) === '=' && level.length === 1) { // Equals found, toplevel equals = current.length; current += text.charAt(i); } else { // Just advance the position current += text.charAt(i); } } return result; }; /** * Adjust and manipulate the wikitext of a page. * * @class * @memberof Morebits.wikitext * @param {string} text - Wikitext to be manipulated. */ Morebits.wikitext.page = function mediawikiPage(text) { this.text = text; }; Morebits.wikitext.page.prototype = { text: '', /** * Removes links to `link_target` from the page text. * * @param {string} link_target * @returns {Morebits.wikitext.page} */ removeLink: function(link_target) { // Rempve a leading colon, to be handled later if (link_target.indexOf(':') === 0) { link_target = link_target.slice(1); } var link_re_string = '', ns = '', title = link_target; var idx = link_target.indexOf(':'); if (idx > 0) { ns = link_target.slice(0, idx); title = link_target.slice(idx + 1); link_re_string = Morebits.namespaceRegex(mw.config.get('wgNamespaceIds')[ns.toLowerCase().replace(/ /g, '_')]) + ':'; } link_re_string += Morebits.pageNameRegex(title); // Allow for an optional leading colon, e.g. [[:User:Test]] // Files and Categories become links with a leading colon, e.g. [[:File:Test.png]] var colon = new RegExp(Morebits.namespaceRegex([6, 14])).test(ns) ? ':' : ':?'; var link_simple_re = new RegExp('\\[\\[' + colon + '(' + link_re_string + ')\\]\\]', 'g'); var link_named_re = new RegExp('\\[\\[' + colon + link_re_string + '\\|(.+?)\\]\\]', 'g'); this.text = this.text.replace(link_simple_re, '$1').replace(link_named_re, '$1'); return this; }, /** * Comments out images from page text; if used in a gallery, deletes the whole line. * If used as a template argument (not necessarily with `File:` prefix), the template parameter is commented out. * * @param {string} image - Image name without `File:` prefix. * @param {string} [reason] - Reason to be included in comment, alongside the commented-out image. * @returns {Morebits.wikitext.page} */ commentOutImage: function(image, reason) { var unbinder = new Morebits.unbinder(this.text); unbinder.unbind('<!--', '-->'); reason = reason ? reason + ': ' : ''; var image_re_string = Morebits.pageNameRegex(image); // Check for normal image links, i.e. [[File:Foobar.png|...]] // Will eat the whole link var links_re = new RegExp('\\[\\[' + Morebits.namespaceRegex(6) + ':\\s*' + image_re_string + '\\s*[\\|(?:\\]\\])]'); var allLinks = Morebits.string.splitWeightedByKeys(unbinder.content, '[[', ']]'); for (var i = 0; i < allLinks.length; ++i) { if (links_re.test(allLinks[i])) { var replacement = '<!-- ' + reason + allLinks[i] + ' -->'; unbinder.content = unbinder.content.replace(allLinks[i], replacement); // unbind the newly created comments unbinder.unbind('<!--', '-->'); } } // Check for gallery images, i.e. instances that must start on a new line, // eventually preceded with some space, and must include File: prefix // Will eat the whole line. var gallery_image_re = new RegExp('(^\\s*' + Morebits.namespaceRegex(6) + ':\\s*' + image_re_string + '\\s*(?:\\|.*?$|$))', 'mg'); unbinder.content = unbinder.content.replace(gallery_image_re, '<!-- ' + reason + '$1 -->'); // unbind the newly created comments unbinder.unbind('<!--', '-->'); // Check free image usages, for example as template arguments, might have the File: prefix excluded, but must be preceeded by an | // Will only eat the image name and the preceeding bar and an eventual named parameter var free_image_re = new RegExp('(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:' + Morebits.namespaceRegex(6) + ':\\s*)?' + image_re_string + ')', 'mg'); unbinder.content = unbinder.content.replace(free_image_re, '<!-- ' + reason + '$1 -->'); // Rebind the content now, we are done! this.text = unbinder.rebind(); return this; }, /** * Converts uses of [[File:`image`]] to [[File:`image`|`data`]]. * * @param {string} image - Image name without File: prefix. * @param {string} data - The display options. * @returns {Morebits.wikitext.page} */ addToImageComment: function(image, data) { var image_re_string = Morebits.pageNameRegex(image); var links_re = new RegExp('\\[\\[' + Morebits.namespaceRegex(6) + ':\\s*' + image_re_string + '\\s*[\\|(?:\\]\\])]'); var allLinks = Morebits.string.splitWeightedByKeys(this.text, '[[', ']]'); for (var i = 0; i < allLinks.length; ++i) { if (links_re.test(allLinks[i])) { var replacement = allLinks[i]; // just put it at the end? replacement = replacement.replace(/\]\]$/, '|' + data + ']]'); this.text = this.text.replace(allLinks[i], replacement); } } var gallery_re = new RegExp('^(\\s*' + image_re_string + '.*?)\\|?(.*?)$', 'mg'); var newtext = '$1|$2 ' + data; this.text = this.text.replace(gallery_re, newtext); return this; }, /** * Remove all transclusions of a template from page text. * * @param {string} template - Page name whose transclusions are to be removed, * include namespace prefix only if not in template namespace. * @returns {Morebits.wikitext.page} */ removeTemplate: function(template) { var template_re_string = Morebits.pageNameRegex(template); var links_re = new RegExp('\\{\\{(?:' + Morebits.namespaceRegex(10) + ':)?\\s*' + template_re_string + '\\s*[\\|(?:\\}\\})]'); var allTemplates = Morebits.string.splitWeightedByKeys(this.text, '{{', '}}', [ '{{{', '}}}' ]); for (var i = 0; i < allTemplates.length; ++i) { if (links_re.test(allTemplates[i])) { this.text = this.text.replace(allTemplates[i], ''); } } return this; }, /** * Smartly insert a tag atop page text but after specified templates, * such as hatnotes, short description, or deletion and protection templates. * Notably, does *not* insert a newline after the tag. * * @param {string} tag - The tag to be inserted. * @param {string|string[]} regex - Templates after which to insert tag, * given as either as a (regex-valid) string or an array to be joined by pipes. * @param {string} [flags=i] - Regex flags to apply. `''` to provide no flags; * other falsey values will default to `i`. * @param {string|string[]} [preRegex] - Optional regex string or array to match * before any template matches (i.e. before `{{`), such as html comments. * @returns {Morebits.wikitext.page} */ insertAfterTemplates: function(tag, regex, flags, preRegex) { if (typeof tag === 'undefined') { throw new Error('No tag provided'); } // .length is only a property of strings and arrays so we // shouldn't need to check type if (typeof regex === 'undefined' || !regex.length) { throw new Error('No regex provided'); } else if (Array.isArray(regex)) { regex = regex.join('|'); } if (typeof flags !== 'string') { flags = 'i'; } if (!preRegex || !preRegex.length) { preRegex = ''; } else if (Array.isArray(preRegex)) { preRegex = preRegex.join('|'); } // Regex is extra complicated to allow for templates with // parameters and to handle whitespace properly this.text = this.text.replace( new RegExp( // leading whitespace '^\\s*' + // capture template(s) '(?:((?:\\s*' + // Pre-template regex, such as leading html comments preRegex + '|' + // begin template format '\\{\\{\\s*(?:' + // Template regex regex + // end main template name, optionally with a number // Probably remove the (?:) though ')\\d*\\s*' + // template parameters '(\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?' + // end template format '\\}\\})+' + // end capture '(?:\\s*\\n)?)' + // trailing whitespace '\\s*)?', flags), '$1' + tag ); return this; }, /** * Get the manipulated wikitext. * * @returns {string} */ getText: function() { return this.text; } }; /* *********** Morebits.userspaceLogger ************ */ /** * Handles logging actions to a userspace log. * Used in CSD, PROD, and XFD. * * @memberof Morebits * @class * @param {string} logPageName - Title of the subpage of the current user's log. */ Morebits.userspaceLogger = function(logPageName) { if (!logPageName) { throw new Error('no log page name specified'); } /** * The text to prefix the log with upon creation, defaults to empty. * * @type {string} */ this.initialText = ''; /** * The header level to use for months, defaults to 3 (`===`). * * @type {number} */ this.headerLevel = 3; this.changeTags = ''; /** * Log the entry. * * @param {string} logText - Doesn't include leading `#` or `*`. * @param {string} summaryText - Edit summary. * @returns {JQuery.Promise} */ this.log = function(logText, summaryText) { var def = $.Deferred(); if (!logText) { return def.reject(); } var page = new Morebits.wiki.page('User:' + mw.config.get('wgUserName') + '/' + logPageName, wgULS('将项目加入到用户空间日志', '將項目加入到使用者空間日誌')); // make this '... to ' + logPageName ? page.load(function(pageobj) { // add blurb if log page doesn't exist or is blank var text = pageobj.getPageText() || this.initialText; // create monthly header if it doesn't exist already var date = new Morebits.date(pageobj.getLoadTime()); if (!date.monthHeaderRegex().exec(text)) { text += '\n\n' + date.monthHeader(this.headerLevel); } pageobj.setPageText(text + '\n' + logText); pageobj.setEditSummary(summaryText); pageobj.setChangeTags(this.changeTags); pageobj.setCreateOption('recreate'); pageobj.save(def.resolve, def.reject); }.bind(this)); return def; }; }; /* **************** Morebits.status **************** */ /** * Create and show status messages of varying urgency. * {@link Morebits.status.init|Morebits.status.init()} must be called before * any status object is created, otherwise those statuses won't be visible. * * @memberof Morebits * @class * @param {string} text - Text before the the colon `:`. * @param {string} stat - Text after the colon `:`. * @param {string} [type=status] - Determine the font color of the status * line, allowable values are: `status` (blue), `info` (green), `warn` (red), * or `error` (bold red). */ Morebits.status = function Status(text, stat, type) { this.textRaw = text; this.text = Morebits.createHtml(text); this.type = type || 'status'; this.generate(); if (stat) { this.update(stat, type); } }; /** * Specify an area for status message elements to be added to. * * @memberof Morebits.status * @param {HTMLElement} root - Usually a div element. * @throws If `root` is not an `HTMLElement`. */ Morebits.status.init = function(root) { if (!(root instanceof Element)) { throw new Error('object not an instance of Element'); } while (root.hasChildNodes()) { root.removeChild(root.firstChild); } Morebits.status.root = root; Morebits.status.errorEvent = null; }; Morebits.status.root = null; /** * @memberof Morebits.status * @param {Function} handler - Function to execute on error. * @throws When `handler` is not a function. */ Morebits.status.onError = function(handler) { if (typeof handler === 'function') { Morebits.status.errorEvent = handler; } else { throw 'Morebits.status.onError: handler is not a function'; } }; Morebits.status.prototype = { stat: null, statRaw: null, text: null, textRaw: null, type: 'status', target: null, node: null, linked: false, /** Add the status element node to the DOM. */ link: function() { if (!this.linked && Morebits.status.root) { Morebits.status.root.appendChild(this.node); this.linked = true; } }, /** Remove the status element node from the DOM. */ unlink: function() { if (this.linked) { Morebits.status.root.removeChild(this.node); this.linked = false; } }, /** * Update the status. * * @param {string} status - Part of status message after colon. * @param {string} type - 'status' (blue), 'info' (green), 'warn' * (red), or 'error' (bold red). */ update: function(status, type) { this.statRaw = status; this.stat = Morebits.createHtml(status); if (type) { this.type = type; if (type === 'error') { // hack to force the page not to reload when an error is output - see also Morebits.status() above Morebits.wiki.numberOfActionsLeft = 1000; // call error callback if (Morebits.status.errorEvent) { Morebits.status.errorEvent(); } // also log error messages in the browser console console.error(this.textRaw + ': ' + this.statRaw); // eslint-disable-line no-console } } this.render(); }, /** Produce the html for first part of the status message. */ generate: function() { this.node = document.createElement('div'); this.node.appendChild(document.createElement('span')).appendChild(this.text); this.node.appendChild(document.createElement('span')).appendChild(document.createTextNode(': ')); this.target = this.node.appendChild(document.createElement('span')); this.target.appendChild(document.createTextNode('')); // dummy node }, /** Complete the html, for the second part of the status message. */ render: function() { this.node.className = 'morebits_status_' + this.type; while (this.target.hasChildNodes()) { this.target.removeChild(this.target.firstChild); } this.target.appendChild(this.stat); this.link(); }, status: function(status) { this.update(status, 'status'); }, info: function(status) { this.update(status, 'info'); }, warn: function(status) { this.update(status, 'warn'); }, error: function(status) { this.update(status, 'error'); } }; /** * @memberof Morebits.status * @param {string} text - Before colon * @param {string} stats - After colon * @returns {Morebits.status} - `status`-type (blue) */ Morebits.status.status = function(text, status) { return new Morebits.status(text, status); }; /** * @memberof Morebits.status * @param {string} text - Before colon * @param {string} stats - After colon * @returns {Morebits.status} - `info`-type (green) */ Morebits.status.info = function(text, status) { return new Morebits.status(text, status, 'info'); }; /** * @memberof Morebits.status * @param {string} text - Before colon * @param {string} stats - After colon * @returns {Morebits.status} - `warn`-type (red) */ Morebits.status.warn = function(text, status) { return new Morebits.status(text, status, 'warn'); }; /** * @memberof Morebits.status * @param {string} text - Before colon * @param {string} stats - After colon * @returns {Morebits.status} - `error`-type (bold red) */ Morebits.status.error = function(text, status) { return new Morebits.status(text, status, 'error'); }; /** * For the action complete message at the end, create a status line without * a colon separator. * * @memberof Morebits.status * @param {string} text */ Morebits.status.actionCompleted = function(text) { var node = document.createElement('div'); node.appendChild(document.createElement('b')).appendChild(document.createTextNode(text)); node.className = 'morebits_status_info morebits_action_complete'; if (Morebits.status.root) { Morebits.status.root.appendChild(node); } }; /** * Display the user's rationale, comments, etc. Back to them after a failure, * so that they may re-use it. * * @memberof Morebits.status * @param {string} comments * @param {string} message */ Morebits.status.printUserText = function(comments, message) { var p = document.createElement('p'); p.innerHTML = message; var div = document.createElement('div'); div.className = 'morebits-usertext'; div.style.marginTop = '0'; div.style.whiteSpace = 'pre-wrap'; div.textContent = comments; p.appendChild(div); Morebits.status.root.appendChild(p); }; /** * Simple helper function to create a simple node. * * @param {string} type - Type of HTML element. * @param {string} content - Text content. * @param {string} [color] - Font color. * @returns {HTMLElement} */ Morebits.htmlNode = function (type, content, color) { var node = document.createElement(type); if (color) { node.style.color = color; } node.appendChild(document.createTextNode(content)); return node; }; /** * Add shift-click support for checkboxes. The wikibits version * (`window.addCheckboxClickHandlers`) has some restrictions, and doesn't work * with checkboxes inside a sortable table, so let's build our own. * * @param jQuerySelector * @param jQueryContext */ Morebits.checkboxShiftClickSupport = function (jQuerySelector, jQueryContext) { var lastCheckbox = null; function clickHandler(event) { var thisCb = this; if (event.shiftKey && lastCheckbox !== null) { var cbs = $(jQuerySelector, jQueryContext); // can't cache them, obviously, if we want to support resorting var index = -1, lastIndex = -1, i; for (i = 0; i < cbs.length; i++) { if (cbs[i] === thisCb) { index = i; if (lastIndex > -1) { break; } } if (cbs[i] === lastCheckbox) { lastIndex = i; if (index > -1) { break; } } } if (index > -1 && lastIndex > -1) { // inspired by wikibits var endState = thisCb.checked; var start, finish; if (index < lastIndex) { start = index + 1; finish = lastIndex; } else { start = lastIndex; finish = index - 1; } for (i = start; i <= finish; i++) { if (cbs[i].checked !== endState) { cbs[i].click(); } } } } lastCheckbox = thisCb; return true; } $(jQuerySelector, jQueryContext).click(clickHandler); }; /* **************** Morebits.batchOperation **************** */ /** * Iterates over a group of pages (or arbitrary objects) and executes a worker function * for each. * * `setPageList(pageList)`: Sets the list of pages to work on. It should be an * array of page names strings. * * `setOption(optionName, optionValue)`: Sets a known option: * - `chunkSize` (integer): The size of chunks to break the array into (default * 50). Setting this to a small value (<5) can cause problems. * - `preserveIndividualStatusLines` (boolean): Keep each page's status element * visible when worker is complete? See note below. * * `run(worker, postFinish)`: Runs the callback `worker` for each page in the * list. The callback must call `workerSuccess` when succeeding, or * `workerFailure` when failing. If using {@link Morebits.wiki.api} or {@link * Morebits.wiki.page}, this is easily done by passing these two functions as * parameters to the methods on those objects: for instance, * `page.save(batchOp.workerSuccess, batchOp.workerFailure)`. Make sure the * methods are called directly if special success/failure cases arise. If you * omit to call these methods, the batch operation will stall after the first * chunk! Also ensure that either workerSuccess or workerFailure is called no * more than once. The second callback `postFinish` is executed when the * entire batch has been processed. * * If using `preserveIndividualStatusLines`, you should try to ensure that the * `workerSuccess` callback has access to the page title. This is no problem for * {@link Morebits.wiki.page} objects. But when using the API, please set the * |pageName| property on the {@link Morebits.wiki.api} object. * * There are sample batchOperation implementations using Morebits.wiki.page in * twinklebatchdelete.js, twinklebatchundelete.js, and twinklebatchprotect.js. * * @memberof Morebits * @class * @param {string} [currentAction] */ Morebits.batchOperation = function(currentAction) { var ctx = { // backing fields for public properties pageList: null, options: { chunkSize: 50, preserveIndividualStatusLines: false }, // internal counters, etc. statusElement: new Morebits.status(currentAction || wgULS('执行批量操作', '執行批次操作')), worker: null, // function that executes for each item in pageList postFinish: null, // function that executes when the whole batch has been processed countStarted: 0, countFinished: 0, countFinishedSuccess: 0, currentChunkIndex: -1, pageChunks: [], running: false }; // shouldn't be needed by external users, but provided anyway for maximum flexibility this.getStatusElement = function() { return ctx.statusElement; }; /** * Sets the list of pages to work on. * * @param {Array} pageList - Array of objects over which you wish to execute the worker function * This is usually the list of page names (strings). */ this.setPageList = function(pageList) { ctx.pageList = pageList; }; /** * Sets a known option. * * @param {string} optionName - Name of the option: * - chunkSize (integer): The size of chunks to break the array into * (default 50). Setting this to a small value (<5) can cause problems. * - preserveIndividualStatusLines (boolean): Keep each page's status * element visible when worker is complete? * @param {number|boolean} optionValue - Value to which the option is * to be set. Should be an integer for chunkSize and a boolean for * preserveIndividualStatusLines. */ this.setOption = function(optionName, optionValue) { ctx.options[optionName] = optionValue; }; /** * Runs the first callback for each page in the list. * The callback must call workerSuccess when succeeding, or workerFailure when failing. * Runs the optional second callback when the whole batch has been processed. * * @param {Function} worker * @param {Function} [postFinish] */ this.run = function(worker, postFinish) { if (ctx.running) { ctx.statusElement.error(wgULS('批量操作已在运行', '批次操作已在執行')); return; } ctx.running = true; ctx.worker = worker; ctx.postFinish = postFinish; ctx.countStarted = 0; ctx.countFinished = 0; ctx.countFinishedSuccess = 0; ctx.currentChunkIndex = -1; ctx.pageChunks = []; var total = ctx.pageList.length; if (!total) { ctx.statusElement.info(wgULS('没有指定页面', '沒有指定頁面')); ctx.running = false; if (ctx.postFinish) { ctx.postFinish(); } return; } // chunk page list into more manageable units ctx.pageChunks = Morebits.array.chunk(ctx.pageList, ctx.options.chunkSize); // start the process Morebits.wiki.addCheckpoint(); ctx.statusElement.status('0%'); fnStartNewChunk(); }; /** * To be called by worker before it terminates succesfully. * * @param {(Morebits.wiki.page|Morebits.wiki.api|string)} arg - * This should be the `Morebits.wiki.page` or `Morebits.wiki.api` object used by worker * (for the adjustment of status lines emitted by them). * If no Morebits.wiki.* object is used (e.g. you're using `mw.Api()` or something else), and * `preserveIndividualStatusLines` option is on, give the page name (string) as argument. */ this.workerSuccess = function(arg) { var createPageLink = function(pageName) { var link = document.createElement('a'); link.setAttribute('href', mw.util.getUrl(pageName)); link.appendChild(document.createTextNode(pageName)); return link; }; if (arg instanceof Morebits.wiki.api || arg instanceof Morebits.wiki.page) { // update or remove status line var statelem = arg.getStatusElement(); if (ctx.options.preserveIndividualStatusLines) { if (arg.getPageName || arg.pageName || (arg.query && arg.query.title)) { // we know the page title - display a relevant message var pageName = arg.getPageName ? arg.getPageName() : arg.pageName || arg.query.title; statelem.info(['完成(', createPageLink(pageName), ')']); } else { // we don't know the page title - just display a generic message statelem.info('完成'); } } else { // remove the status line automatically produced by Morebits.wiki.* statelem.unlink(); } } else if (typeof arg === 'string' && ctx.options.preserveIndividualStatusLines) { new Morebits.status(arg, ['完成(', createPageLink(arg), ')']); } ctx.countFinishedSuccess++; fnDoneOne(); }; this.workerFailure = function() { fnDoneOne(); }; // private functions var thisProxy = this; var fnStartNewChunk = function() { var chunk = ctx.pageChunks[++ctx.currentChunkIndex]; if (!chunk) { return; // done! yay } // start workers for the current chunk ctx.countStarted += chunk.length; chunk.forEach(function(page) { ctx.worker(page, thisProxy); }); }; var fnDoneOne = function() { ctx.countFinished++; // update overall status line var total = ctx.pageList.length; if (ctx.countFinished < total) { ctx.statusElement.status(parseInt(100 * ctx.countFinished / total, 10) + '%'); // start a new chunk if we're close enough to the end of the previous chunk, and // we haven't already started the next one if (ctx.countFinished >= (ctx.countStarted - Math.max(ctx.options.chunkSize / 10, 2)) && Math.floor(ctx.countFinished / ctx.options.chunkSize) > ctx.currentChunkIndex) { fnStartNewChunk(); } } else if (ctx.countFinished === total) { var statusString = '完成(' + ctx.countFinishedSuccess + '/' + ctx.countFinished + '操作成功完成)'; if (ctx.countFinishedSuccess < ctx.countFinished) { ctx.statusElement.warn(statusString); } else { ctx.statusElement.info(statusString); } if (ctx.postFinish) { ctx.postFinish(); } Morebits.wiki.removeCheckpoint(); ctx.running = false; } else { // ctx.countFinished > total // just for giggles! (well, serious debugging, actually) ctx.statusElement.warn(wgULS('完成(多执行了', '完成(多執行了') + (ctx.countFinished - total) + '次)'); Morebits.wiki.removeCheckpoint(); ctx.running = false; } }; }; /** * Given a set of asynchronous functions to run along with their dependencies, * run them in an efficient sequence so that multiple functions * that don't depend on each other are triggered simultaneously. Where * dependencies exist, it ensures that the dependency functions finish running * before the dependent function runs. The values resolved by the dependencies * are made available to the dependant as arguments. * * @memberof Morebits * @class */ Morebits.taskManager = function(context) { this.taskDependencyMap = new Map(); this.failureCallbackMap = new Map(); this.deferreds = new Map(); this.allDeferreds = []; // Hack: IE doesn't support Map.prototype.values this.context = context || window; /** * Register a task along with its dependencies (tasks which should have finished * execution before we can begin this one). Each task is a function that must return * a promise. The function will get the values resolved by the dependency functions * as arguments. * * @param {Function} func - A task. * @param {Function[]} deps - Its dependencies. * @param {Function} [onFailure] - a failure callback that's run if the task or any one * of its dependencies fail. */ this.add = function(func, deps, onFailure) { this.taskDependencyMap.set(func, deps); this.failureCallbackMap.set(func, onFailure || function() {}); var deferred = $.Deferred(); this.deferreds.set(func, deferred); this.allDeferreds.push(deferred); }; /** * Run all the tasks. Multiple tasks may be run at once. * * @returns {jQuery.Promise} - Resolved if all tasks succeed, rejected otherwise. */ this.execute = function() { var self = this; // proxy for `this` for use inside functions where `this` is something else this.taskDependencyMap.forEach(function(deps, task) { var dependencyPromisesArray = deps.map(function(dep) { return self.deferreds.get(dep); }); $.when.apply(self.context, dependencyPromisesArray).then(function() { var result = task.apply(self.context, arguments); if (result === undefined) { // maybe the function threw, or it didn't return anything mw.log.error('Morebits.taskManager: task returned undefined'); self.deferreds.get(task).reject.apply(self.context, arguments); self.failureCallbackMap.get(task).apply(self.context, []); } result.then(function() { self.deferreds.get(task).resolve.apply(self.context, arguments); }, function() { // task failed self.deferreds.get(task).reject.apply(self.context, arguments); self.failureCallbackMap.get(task).apply(self.context, arguments); }); }, function() { // one or more of the dependencies failed self.failureCallbackMap.get(task).apply(self.context, arguments); }); }); return $.when.apply(null, this.allDeferreds); // resolved when everything is done! }; }; /** * A simple draggable window, now a wrapper for jQuery UI's dialog feature. * * @memberof Morebits * @class * @requires jquery.ui * @param {number} width * @param {number} height - The maximum allowable height for the content area. */ Morebits.simpleWindow = function SimpleWindow(width, height) { var content = document.createElement('div'); this.content = content; content.className = 'morebits-dialog-content'; content.id = 'morebits-dialog-content-' + Math.round(Math.random() * 1e15); this.height = height; $(this.content).dialog({ autoOpen: false, buttons: { 'Placeholder button': function() {} }, dialogClass: 'morebits-dialog', width: Math.min(parseInt(window.innerWidth, 10), parseInt(width ? width : 800, 10)), // give jQuery the given height value (which represents the anticipated height of the dialog) here, so // it can position the dialog appropriately // the 20 pixels represents adjustment for the extra height of the jQuery dialog "chrome", compared // to that of the old SimpleWindow height: height + 20, close: function(event) { // dialogs and their content can be destroyed once closed $(event.target).dialog('destroy').remove(); }, resizeStart: function() { this.scrollbox = $(this).find('.morebits-scrollbox')[0]; if (this.scrollbox) { this.scrollbox.style.maxHeight = 'none'; } }, resizeStop: function() { this.scrollbox = null; }, resize: function() { this.style.maxHeight = ''; if (this.scrollbox) { this.scrollbox.style.width = ''; } } }); var $widget = $(this.content).dialog('widget'); // delete the placeholder button (it's only there so the buttonpane gets created) $widget.find('button').each(function(key, value) { value.parentNode.removeChild(value); }); // add container for the buttons we add, and the footer links (if any) var buttonspan = document.createElement('span'); buttonspan.className = 'morebits-dialog-buttons'; var linksspan = document.createElement('span'); linksspan.className = 'morebits-dialog-footerlinks'; $widget.find('.ui-dialog-buttonpane').append(buttonspan, linksspan); // resize the scrollbox with the dialog, if one is present $widget.resizable('option', 'alsoResize', '#' + this.content.id + ' .morebits-scrollbox, #' + this.content.id); }; Morebits.simpleWindow.prototype = { buttons: [], height: 600, hasFooterLinks: false, scriptName: null, /** * Focuses the dialog. This might work, or on the contrary, it might not. * * @returns {Morebits.simpleWindow} */ focus: function() { $(this.content).dialog('moveToTop'); return this; }, /** * Closes the dialog. If this is set as an event handler, it will stop the event * from doing anything more. * * @param {event} [event] * @returns {Morebits.simpleWindow} */ close: function(event) { if (event) { event.preventDefault(); } $(this.content).dialog('close'); return this; }, /** * Shows the dialog. Calling display() on a dialog that has previously been closed * might work, but it is not guaranteed. * * @returns {Morebits.simpleWindow} */ display: function() { if (this.scriptName) { var $widget = $(this.content).dialog('widget'); $widget.find('.morebits-dialog-scriptname').remove(); var scriptnamespan = document.createElement('span'); scriptnamespan.className = 'morebits-dialog-scriptname'; scriptnamespan.textContent = this.scriptName + ' \u00B7 '; // U+00B7 MIDDLE DOT = &middot; $widget.find('.ui-dialog-title').prepend(scriptnamespan); } var dialog = $(this.content).dialog('open'); if (window.setupTooltips && window.pg && window.pg.re && window.pg.re.diff) { // tie in with NAVPOP dialog.parent()[0].ranSetupTooltipsAlready = false; window.setupTooltips(dialog.parent()[0]); } this.setHeight(this.height); // init height algorithm return this; }, /** * Sets the dialog title. * * @param {string} title * @returns {Morebits.simpleWindow} */ setTitle: function(title) { $(this.content).dialog('option', 'title', title); return this; }, /** * Sets the script name, appearing as a prefix to the title to help users determine which * user script is producing which dialog. For instance, Twinkle modules set this to "Twinkle". * * @param {string} name * @returns {Morebits.simpleWindow} */ setScriptName: function(name) { this.scriptName = name; return this; }, /** * Sets the dialog width. * * @param {number} width * @returns {Morebits.simpleWindow} */ setWidth: function(width) { $(this.content).dialog('option', 'width', width); return this; }, /** * Sets the dialog's maximum height. The dialog will auto-size to fit its contents, * but the content area will grow no larger than the height given here. * * @param {number} height * @returns {Morebits.simpleWindow} */ setHeight: function(height) { this.height = height; // from display time onwards, let the browser determine the optimum height, // and instead limit the height at the given value // note that the given height will exclude the approx. 20px that the jQuery UI // chrome has in height in addition to the height of an equivalent "classic" // Morebits.simpleWindow if (parseInt(getComputedStyle($(this.content).dialog('widget')[0], null).height, 10) > window.innerHeight) { $(this.content).dialog('option', 'height', window.innerHeight - 2).dialog('option', 'position', 'top'); } else { $(this.content).dialog('option', 'height', 'auto'); } $(this.content).dialog('widget').find('.morebits-dialog-content')[0].style.maxHeight = parseInt(this.height - 30, 10) + 'px'; return this; }, /** * Sets the content of the dialog to the given element node, usually from rendering * a {@link Morebits.quickForm}. * Re-enumerates the footer buttons, but leaves the footer links as they are. * Be sure to call this at least once before the dialog is displayed... * * @param {HTMLElement} content * @returns {Morebits.simpleWindow} */ setContent: function(content) { this.purgeContent(); this.addContent(content); return this; }, /** * Adds the given element node to the dialog content. * * @param {HTMLElement} content * @returns {Morebits.simpleWindow} */ addContent: function(content) { this.content.appendChild(content); // look for submit buttons in the content, hide them, and add a proxy button to the button pane var thisproxy = this; $(this.content).find('input[type="submit"], button[type="submit"]').each(function(key, value) { value.style.display = 'none'; var button = document.createElement('button'); button.textContent = value.hasAttribute('value') ? value.getAttribute('value') : value.textContent ? value.textContent : '提交'; button.className = value.className || 'submitButtonProxy'; // here is an instance of cheap coding, probably a memory-usage hit in using a closure here button.addEventListener('click', function() { value.click(); }, false); thisproxy.buttons.push(button); }); // remove all buttons from the button pane and re-add them if (this.buttons.length > 0) { $(this.content).dialog('widget').find('.morebits-dialog-buttons').empty().append(this.buttons)[0].removeAttribute('data-empty'); } else { $(this.content).dialog('widget').find('.morebits-dialog-buttons')[0].setAttribute('data-empty', 'data-empty'); // used by CSS } return this; }, /** * Removes all contents from the dialog, barring any footer links. * * @returns {Morebits.simpleWindow} */ purgeContent: function() { this.buttons = []; // delete all buttons in the buttonpane $(this.content).dialog('widget').find('.morebits-dialog-buttons').empty(); while (this.content.hasChildNodes()) { this.content.removeChild(this.content.firstChild); } return this; }, /** * Adds a link in the bottom-right corner of the dialog. * This can be used to provide help or policy links. * For example, Twinkle's CSD module adds a link to the CSD policy page, * as well as a link to Twinkle's documentation. * * @param {string} text - Display text. * @param {string} wikiPage - Link target. * @param {boolean} [prep=false] - Set true to prepend rather than append. * @returns {Morebits.simpleWindow} */ addFooterLink: function(text, wikiPage, prep) { var $footerlinks = $(this.content).dialog('widget').find('.morebits-dialog-footerlinks'); if (this.hasFooterLinks) { var bullet = document.createElement('span'); bullet.textContent = ' \u2022 '; // U+2022 BULLET if (prep) { $footerlinks.prepend(bullet); } else { $footerlinks.append(bullet); } } var link = document.createElement('a'); link.setAttribute('href', mw.util.getUrl(wikiPage)); link.setAttribute('title', wikiPage); link.setAttribute('target', '_blank'); link.textContent = text; if (prep) { $footerlinks.prepend(link); } else { $footerlinks.append(link); } this.hasFooterLinks = true; return this; }, /** * Sets whether the window should be modal or not. Modal dialogs create * an overlay below the dialog but above other page elements. This * must be used (if necessary) before calling display(). * * @param {boolean} [modal=false] - If set to true, other items on the * page will be disabled, i.e., cannot be interacted with. * @returns {Morebits.simpleWindow} */ setModality: function(modal) { $(this.content).dialog('option', 'modal', modal); return this; } }; /** * Enables or disables all footer buttons on all {@link Morebits.simpleWindow}s in the current page. * This should be called with `false` when the button(s) become irrelevant (e.g. just before * {@link Morebits.status.init} is called). * This is not an instance method so that consumers don't have to keep a reference to the * original `Morebits.simpleWindow` object sitting around somewhere. Anyway, most of the time * there will only be one `Morebits.simpleWindow` open, so this shouldn't matter. * * @memberof Morebits.simpleWindow * @param {boolean} enabled */ Morebits.simpleWindow.setButtonsEnabled = function(enabled) { $('.morebits-dialog-buttons button').prop('disabled', !enabled); }; // Create capital letter aliases for all Morebits @classes (functions that work with the `new` keyword) // to follow the coding convention that classes should start with an uppercase letter. This will let us start fixing ESLint `new-cap` errors in other files. Morebits.BatchOperation = Morebits.batchOperation; Morebits.Date = Morebits.date; Morebits.QuickForm = Morebits.quickForm; Morebits.QuickForm.Element = Morebits.quickForm.element; Morebits.SimpleWindow = Morebits.simpleWindow; Morebits.Status = Morebits.status; Morebits.TaskManager = Morebits.taskManager; Morebits.Unbinder = Morebits.unbinder; Morebits.UserspaceLogger = Morebits.userspaceLogger; Morebits.wiki.Api = Morebits.wiki.api; Morebits.wiki.Page = Morebits.wiki.page; Morebits.wiki.Preview = Morebits.wiki.preview; Morebits.wikitext.Page = Morebits.wikitext.page; }(window, document, jQuery)); // End wrap with anonymous function /** * If this script is being executed outside a ResourceLoader context, we add some * global assignments for legacy scripts, hopefully these can be removed down the line. * * IMPORTANT NOTE: * PLEASE DO NOT USE THESE ALIASES IN NEW CODE! * Thanks. */ if (typeof arguments === 'undefined') { // typeof is here for a reason... /* global Morebits */ window.SimpleWindow = Morebits.simpleWindow; window.QuickForm = Morebits.quickForm; window.Wikipedia = Morebits.wiki; window.Status = Morebits.status; } // </nowiki> iazeyn0squ6ee4t5n8fmwml8wyq31wj MediaWiki:Gadget-Twinkle.js 8 1368858 9943035 7145926 2026-10-12T15:40:27Z Xiplus 25402 Repo at 375b1e1: allow to load at Special:IPContributions (#362); fix addPortlet() in vector-2022; Add watchBlockNotices config; 2022; fix menu appearance for skin vector-2022; add support for vector-2022 9943035 javascript text/javascript /** * vim: set noet sts=0 sw=8: * +-------------------------------------------------------------------------+ * | === 警告:全局小工具文件 === | * | 对此文件的修改会影响许多用户。 | * | 修改前请联系维护者。 | * +-------------------------------------------------------------------------+ * * 从Github导入[https://github.com/Xi-Plus/twinkle] * 基於 [https://github.com/jimmyxu/twinkle] 和 [https://github.com/vjudge1/twinkle] 的修改 * * ---------- * * 这是经过亂改的Twinkle,是让維護者自己方便使用维基百科的 * 好帮手。请参见[[Wiktionary:TW]]以获取更多信息。 */ // <nowiki> /* global Morebits */ (function (window, document, $) { // Wrap with anonymous function // Check if account is experienced enough to use Twinkle if (!Morebits.userIsInGroup('autoconfirmed') && !Morebits.userIsInGroup('confirmed')) { return; } // MediaWiki:Gadget-site-lib.js window.wgUXS = function (wg, hans, hant, cn, tw, hk, sg, zh, mo, my) { var ret = { 'zh': zh || hans || hant || cn || tw || hk || sg || mo || my, 'zh-hans': hans || cn || sg || my, 'zh-hant': hant || tw || hk || mo, 'zh-cn': cn || hans || sg || my, 'zh-sg': sg || hans || cn || my, 'zh-tw': tw || hant || hk || mo, 'zh-hk': hk || hant || mo || tw, 'zh-mo': mo || hant || hk || tw }; return ret[wg] || zh || hans || hant || cn || tw || hk || sg || mo || my; // 保證每一語言有值 }; window.wgULS = function (hans, hant, cn, tw, hk, sg, zh, mo, my) { return wgUXS(mw.config.get('wgUserLanguage'), hans, hant, cn, tw, hk, sg, zh, mo, my); // eslint-disable-line no-undef }; window.wgUVS = function (hans, hant, cn, tw, hk, sg, zh, mo, my) { return wgUXS(mw.config.get('wgUserVariant'), hans, hant, cn, tw, hk, sg, zh, mo, my); // eslint-disable-line no-undef }; var Twinkle = {}; window.Twinkle = Twinkle; // allow global access /** * Twinkle-specific data shared by multiple modules * Likely customized per installation */ // Custom change tag(s) to be applied to all Twinkle actions, create at Special:Tags Twinkle.changeTags = 'Twinkle'; // Available for actions that don't (yet) support tags // currently: FlaggedRevs and PageTriage Twinkle.summaryAd = ' ([[WP:TW|TW]])'; // for use by custom modules (normally empty) Twinkle.initCallbacks = []; Twinkle.addInitCallback = function twinkleAddInitCallback(func) { Twinkle.initCallbacks.push(func); }; Twinkle.defaultConfig = {}; /** * This holds the default set of preferences used by Twinkle. * It is important that all new preferences added here, especially admin-only ones, are also added to * |Twinkle.config.sections| in twinkleconfig.js, so they are configurable via the Twinkle preferences panel. * For help on the actual preferences, see the comments in twinkleconfig.js. * * Formerly Twinkle.defaultConfig.twinkle and Twinkle.defaultConfig.friendly */ Twinkle.defaultConfig = { // General userTalkPageMode: 'tab', dialogLargeFont: false, disabledModules: [], disabledSysopModules: [], // Block blankTalkpageOnIndefBlock: false, watchBlockNotices: false, customBlockReasonList: [], // Fluff (revert and rollback) openTalkPage: [ ], openTalkPageOnAutoRevert: false, markRevertedPagesAsMinor: [ 'vand' ], watchRevertedPages: [ ], offerReasonOnNormalRevert: true, confirmOnFluff: false, showRollbackLinks: [ 'diff', 'others' ], customRevertSummary: [], // DI (twinkleimage) notifyUserOnDeli: true, deliWatchPage: 'default', deliWatchUser: 'default', // CSD speedySelectionStyle: 'buttonClick', watchSpeedyPages: [ ], markSpeedyPagesAsPatrolled: true, // these next two should probably be identical by default notifyUserOnSpeedyDeletionNomination: [ 'db', 'g1', 'g2', 'g3', 'g5', 'g11', 'g12', 'g13', 'a1', 'r2', 'r3' ], welcomeUserOnSpeedyDeletionNotification: [ 'db', 'g1', 'g2', 'g3', 'g5', 'g11', 'g12', 'g13', 'a1', 'r2', 'r3' ], promptForSpeedyDeletionSummary: [], openUserTalkPageOnSpeedyDelete: [ ], deleteTalkPageOnDelete: true, deleteRedirectsOnDelete: true, deleteSysopDefaultToTag: false, speedyWindowHeight: 500, speedyWindowWidth: 800, logSpeedyNominations: false, speedyLogPageName: 'CSD日志', noLogOnSpeedyNomination: [ 'o1' ], enlargeG11Input: false, // Unlink unlinkNamespaces: [ '0', '10', '100', '118' ], // Warn defaultWarningGroup: '1', showSharedIPNotice: true, watchWarnings: false, customWarningList: [], // XfD xfdWatchDiscussion: 'default', xfdWatchPage: 'default', xfdWatchUser: 'default', markXfdPagesAsPatrolled: true, FwdCsdToXfd: Morebits.userIsSysop, afdDefaultCategory: 'delete', XfdClose: Morebits.userIsSysop ? 'all' : 'hide', // Copyvio copyvioWatchPage: 'default', copyvioWatchUser: 'default', markCopyvioPagesAsPatrolled: true, markDraftCopyvioWithCSD: false, // Hidden preferences revertMaxRevisions: 50, batchMax: 5000, batchdeleteChunks: 50, batchProtectChunks: 50, batchundeleteChunks: 50, proddeleteChunks: 50, configPage: 'Wiktionary:Twinkle/参数设置', projectNamespaceName: mw.config.get('wgFormattedNamespaces')[4], sandboxPage: 'Wiktionary:沙盒', // Deprecated options, as a fallback for add-on scripts/modules summaryAd: '', deletionSummaryAd: '', protectionSummaryAd: '', blockSummaryAd: '', // Formerly defaultConfig.friendly: // Tag watchTaggedPages: false, watchMergeDiscussions: false, markTaggedPagesAsMinor: false, markTaggedPagesAsPatrolled: true, tagArticleSortOrder: 'cat', customTagList: [], // Talkback markTalkbackAsMinor: true, insertTalkbackSignature: true, // always sign talkback templates talkbackHeading: wgULS('回复通告', '回覆通告'), mailHeading: wgULS('您有新邮件!', '您有新郵件!'), // Shared markSharedIPAsMinor: true }; // now some skin dependent config. switch (mw.config.get('skin')) { case 'vector': case 'vector-2022': Twinkle.defaultConfig.portletArea = 'right-navigation'; Twinkle.defaultConfig.portletId = 'p-twinkle'; Twinkle.defaultConfig.portletName = 'TW'; Twinkle.defaultConfig.portletType = 'menu'; Twinkle.defaultConfig.portletNext = 'p-search'; break; case 'timeless': Twinkle.defaultConfig.portletArea = '#page-tools .sidebar-inner'; Twinkle.defaultConfig.portletId = 'p-twinkle'; Twinkle.defaultConfig.portletName = 'Twinkle'; Twinkle.defaultConfig.portletType = null; Twinkle.defaultConfig.portletNext = 'p-userpagetools'; break; default: Twinkle.defaultConfig.portletArea = null; Twinkle.defaultConfig.portletId = 'p-cactions'; Twinkle.defaultConfig.portletName = null; Twinkle.defaultConfig.portletType = null; Twinkle.defaultConfig.portletNext = null; } Twinkle.getPref = function twinkleGetPref(name) { // Old preferences format if (typeof Twinkle.prefs === 'object' && typeof Twinkle.prefs.twinkle === 'object' && Twinkle.prefs.twinkle[name]) { return Twinkle.prefs.twinkle[name]; } if (typeof Twinkle.prefs === 'object' && typeof Twinkle.prefs.friendly === 'object' && Twinkle.prefs.friendly[name]) { return Twinkle.prefs.friendly[name]; } if (typeof Twinkle.prefs === 'object' && Twinkle.prefs[name] !== undefined) { return Twinkle.prefs[name]; } // Old preferences format, used before twinkleoptions.js was a thing if (typeof window.TwinkleConfig === 'object' && window.TwinkleConfig[name] !== undefined) { return window.TwinkleConfig[name]; } if (typeof window.FriendlyConfig === 'object' && window.FriendlyConfig[name] !== undefined) { return window.FriendlyConfig[name]; } return Twinkle.defaultConfig[name]; }; /** * **************** Twinkle.addPortlet() **************** * * Adds a portlet menu to one of the navigation areas on the page. * This is necessarily quite a hack since skins, navigation areas, and * portlet menu types all work slightly different. * * Available navigation areas depend on the skin used. * Vector: * For each option, the outer nav class contains "vector-menu", the inner div class is "vector-menu-content", and the ul is "vector-menu-content-list" * "mw-panel", outer nav class contains "vector-menu-portal". Existing portlets/elements: "p-logo", "p-navigation", "p-interaction", "p-tb", "p-coll-print_export" * "left-navigation", outer nav class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-namespaces", "p-variants" (menu) * "right-navigation", outer nav class contains "vector-menu-tabs" or "vector-menu-dropdown". Existing portlets: "p-views", "p-cactions" (menu), "p-search" * Special layout of p-personal portlet (part of "head") through specialized styles. * Monobook: * "column-one", outer nav class "portlet", inner div class "pBody". Existing portlets: "p-cactions", "p-personal", "p-logo", "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export" * Special layout of p-cactions and p-personal through specialized styles. * Modern: * "mw_contentwrapper" (top nav), outer nav class "portlet", inner div class "pBody". Existing portlets or elements: "p-cactions", "mw_content" * "mw_portlets" (sidebar), outer nav class "portlet", inner div class "pBody". Existing portlets: "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export" * * @param String navigation -- id of the target navigation area (skin dependant, on vector either of "left-navigation", "right-navigation", or "mw-panel") * @param String id -- id of the portlet menu to create, preferably start with "p-". * @param String text -- name of the portlet menu to create. Visibility depends on the class used. * @param String type -- type of portlet. Currently only used for the vector non-sidebar portlets, pass "menu" to make this portlet a drop down menu. * @param Node nextnodeid -- the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end. * * @return Node -- the DOM node of the new item (a DIV element) or null */ Twinkle.addPortlet = function(navigation, id, text, type, nextnodeid) { // sanity checks, and get required DOM nodes var root = document.getElementById(navigation) || document.querySelector(navigation); if (!root) { return null; } var item = document.getElementById(id); if (item) { if (item.parentNode && item.parentNode === root) { return item; } return null; } var nextnode; if (nextnodeid) { nextnode = document.getElementById(nextnodeid); } // verify/normalize input var skin = mw.config.get('skin'); if ((skin !== 'vector' && skin !== 'vector-2022') || (navigation !== 'left-navigation' && navigation !== 'right-navigation')) { type = null; // menu supported only in vector's #left-navigation & #right-navigation } var outerNavClass, innerDivClass; switch (skin) { case 'vector': case 'vector-2022': // XXX: portal doesn't work if (navigation !== 'portal' && navigation !== 'left-navigation' && navigation !== 'right-navigation') { navigation = 'mw-panel'; } outerNavClass = 'mw-portlet vector-menu'; if (navigation === 'mw-panel') { outerNavClass += ' vector-menu-portal'; } else if (type === 'menu') { outerNavClass += ' vector-menu-dropdown vector-dropdown vector-menu-dropdown-noicon'; } else { outerNavClass += ' vector-menu-tabs'; } innerDivClass = 'vector-menu-content vector-dropdown-content'; break; case 'modern': if (navigation !== 'mw_portlets' && navigation !== 'mw_contentwrapper') { navigation = 'mw_portlets'; } outerNavClass = 'portlet'; break; case 'timeless': outerNavClass = 'mw-portlet'; innerDivClass = 'mw-portlet-body'; break; default: navigation = 'column-one'; outerNavClass = 'portlet'; break; } // Build the DOM elements. var outerNav, heading; if (skin === 'vector-2022') { outerNav = document.createElement('div'); heading = document.createElement('label'); } else { outerNav = document.createElement('nav'); heading = document.createElement('h3'); } outerNav.setAttribute('aria-labelledby', id + '-label'); // Vector getting vector-menu-empty FIXME TODO outerNav.className = outerNavClass + ' emptyPortlet'; outerNav.id = id; if (nextnode && nextnode.parentNode === root) { root.insertBefore(outerNav, nextnode); } else { root.appendChild(outerNav); } heading.id = id + '-label'; var ul = document.createElement('ul'); if (skin === 'vector' || skin === 'vector-2022') { heading.setAttribute('for', id + '-dropdown-checkbox'); ul.className = 'vector-menu-content-list'; heading.className = 'vector-menu-heading vector-dropdown-label'; // add invisible checkbox to keep menu open when clicked // similar to the p-cactions ("More") menu if (outerNavClass.indexOf('vector-menu-dropdown') !== -1) { var chkbox = document.createElement('input'); chkbox.id = id + '-dropdown-checkbox'; chkbox.className = 'vector-menu-checkbox vector-dropdown-checkbox'; chkbox.setAttribute('type', 'checkbox'); chkbox.setAttribute('aria-labelledby', id + '-label'); outerNav.appendChild(chkbox); // Vector gets its title in a span; all others except // timeless have no title, and it has no span var span = document.createElement('span'); span.appendChild(document.createTextNode(text)); heading.appendChild(span); var a = document.createElement('a'); a.href = '#'; $(a).click(function(e) { e.preventDefault(); }); heading.appendChild(a); } } else { // Basically just Timeless heading.appendChild(document.createTextNode(text)); } outerNav.appendChild(heading); if (innerDivClass) { var innerDiv = document.createElement('div'); innerDiv.className = innerDivClass; innerDiv.appendChild(ul); outerNav.appendChild(innerDiv); } else { outerNav.appendChild(ul); } return outerNav; }; /** * **************** Twinkle.addPortletLink() **************** * Builds a portlet menu if it doesn't exist yet, and add the portlet link. * @param task: Either a URL for the portlet link or a function to execute. */ Twinkle.addPortletLink = function(task, text, id, tooltip) { if (Twinkle.getPref('portletArea') !== null) { Twinkle.addPortlet(Twinkle.getPref('portletArea'), Twinkle.getPref('portletId'), Twinkle.getPref('portletName'), Twinkle.getPref('portletType'), Twinkle.getPref('portletNext')); } var link = mw.util.addPortletLink(Twinkle.getPref('portletId'), typeof task === 'string' ? task : '#', text, id, tooltip); $('.client-js .skin-vector #p-cactions').css('margin-right', 'initial'); if (typeof task === 'function') { $(link).find('a').on('click', function (ev) { task(); ev.preventDefault(); }); } if ($.collapsibleTabs) { $.collapsibleTabs.handleResize(); } return link; }; /** * **************** General initialization code **************** */ var scriptpathbefore = mw.util.wikiScript('index') + '?title=', scriptpathafter = '&action=raw&ctype=text/javascript&happy=yes'; // Retrieve the user's Twinkle preferences $.ajax({ url: scriptpathbefore + 'User:' + encodeURIComponent(mw.config.get('wgUserName')) + '/twinkleoptions.js' + scriptpathafter, dataType: 'text' }) .fail(function () { mw.util.jsMessage('未能加载twinkleoptions.js'); }) .done(function (optionsText) { // Quick pass if user has no options if (optionsText === '') { return; } // Twinkle options are basically a JSON object with some comments. Strip those: optionsText = optionsText.replace(/(?:^(?:\/\/[^\n]*\n)*\n*|(?:\/\/[^\n]*(?:\n|$))*$)/g, ''); // First version of options had some boilerplate code to make it eval-able -- strip that too. This part may become obsolete down the line. if (optionsText.lastIndexOf('window.Twinkle.prefs = ', 0) === 0) { optionsText = optionsText.replace(/(?:^window.Twinkle.prefs = |;\n*$)/g, ''); } try { var options = JSON.parse(optionsText); if (options) { if (options.twinkle || options.friendly) { // Old preferences format Twinkle.prefs = $.extend(options.twinkle, options.friendly); } else { Twinkle.prefs = options; } // v2 established after unification of Twinkle/Friendly objects Twinkle.prefs.optionsVersion = Twinkle.prefs.optionsVersion || 1; } } catch (e) { mw.util.jsMessage('未能解析twinkleoptions.js'); } }) .always(function () { $(Twinkle.load); }); // Developers: you can import custom Twinkle modules here // For example, mw.loader.load(scriptpathbefore + "User:UncleDouggie/morebits-test.js" + scriptpathafter); Twinkle.load = function () { // Don't activate on special pages other than those on the whitelist so that // they load faster, especially the watchlist. var specialPageWhitelist = [ 'Block', 'Contributions', 'IPContributions', 'AbuseLog' ]; // wgRelevantUserName defined for non-sysops on Special:Block if (Morebits.userIsSysop) { specialPageWhitelist = specialPageWhitelist.concat([ 'DeletedContributions', 'Prefixindex' ]); } if (mw.config.get('wgNamespaceNumber') === -1 && specialPageWhitelist.indexOf(mw.config.get('wgCanonicalSpecialPageName')) === -1) { return; } // Prevent clickjacking if (window.top !== window.self) { return; } // Set custom Api-User-Agent header, for server-side logging purposes Morebits.wiki.api.setApiUserAgent('Twinkle~zh~/2.0 (' + mw.config.get('wgDBname') + ')'); // Load all the modules in the order that the tabs should appear var twinkleModules = [ // User/user talk-related 'arv', 'block', // Deletion 'speedy', 'copyvio', 'xfd', // Maintenance 'tag', // Misc. ones last 'diff', 'fluff', 'batchdelete' ]; // Don't load modules users have disabled var disabledModules = Twinkle.getPref('disabledModules').concat(Twinkle.getPref('disabledSysopModules')); twinkleModules.filter(function(mod) { return disabledModules.indexOf(mod) === -1; }).forEach(function(module) { Twinkle[module](); }); if (Twinkle.getPref('XfdClose') !== 'hide') { Twinkle.close(); } Twinkle.config.init(); // Can't turn off Twinkle.addPortletLink(mw.util.wikiScript('index') + '?title=' + Twinkle.getPref('configPage'), wgULS('设置', '設定'), 'tw-config', wgULS('设置Twinkle参数', '設定Twinkle參數')); // Run the initialization callbacks for any custom modules $(Twinkle.initCallbacks).each(function (k, v) { v(); }); Twinkle.addInitCallback = function (func) { func(); }; // Increases text size in Twinkle dialogs, if so configured if (Twinkle.getPref('dialogLargeFont')) { mw.util.addCSS('.morebits-dialog-content, .morebits-dialog-footerlinks { font-size: 100% !important; } ' + '.morebits-dialog input, .morebits-dialog select, .morebits-dialog-content button { font-size: inherit !important; }'); } mw.hook('twinkle.loaded').fire(Twinkle); }; }(window, document, jQuery)); // End wrap with anonymous function // </nowiki> 3e7fsftexu4twaqj6002o0cpu4675d1 MediaWiki:Gadget-twinklearv.js 8 1368859 9943040 6328858 2026-10-12T15:40:53Z Xiplus 25402 Repo at 375b1e1: Check page param 9943040 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { // eslint-disable-line no-unused-vars /* **************************************** *** twinklearv.js: ARV module **************************************** * Mode of invocation: Tab ("ARV") * Active on: Any page with relevant user name (userspace, contribs, etc.) */ Twinkle.arv = function twinklearv() { var username = Morebits.relevantUserName(true); // disable report of IP addresses, range is ok if (!username || mw.util.isIPAddress(username)) { return; } var title = mw.util.isIPAddress(username) ? wgULS('报告IP给管理员', '報告IP給管理員') : wgULS('报告用户给管理人员', '報告使用者給管理人員'); Twinkle.addPortletLink(function() { Twinkle.arv.callback(username); }, wgULS('告状', '告狀'), 'tw-arv', title); }; Twinkle.arv.callback = function (uid) { if (uid === mw.config.get('wgUserName')) { alert(wgULS('你不想报告你自己,对吧?', '你不想報告你自己,對吧?')); return; } var Window = new Morebits.simpleWindow(600, 500); Window.setTitle(wgULS('报告用户给管理人员', '報告用戶給管理人員')); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'w:WP:TW/DOC#告狀'); var form = new Morebits.quickForm(Twinkle.arv.callback.evaluate); var categories = form.append({ type: 'select', name: 'category', label: wgULS('选择报告类型:', '選擇報告類型:'), event: Twinkle.arv.callback.changeCategory }); categories.append({ type: 'option', label: wgULS('破坏(WT:BP)', '破壞(WT:BP)'), value: 'aiv' }); form.append({ type: 'field', label: 'Work area', name: 'work_area' }); form.append({ type: 'submit', label: '提交' }); form.append({ type: 'hidden', name: 'uid', value: uid }); var result = form.render(); Window.setContent(result); Window.display(); // We must init the var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.category.dispatchEvent(evt); }; Twinkle.arv.callback.changeCategory = function (e) { var value = e.target.value; var root = e.target.form; var old_area = Morebits.quickForm.getElements(root, 'work_area')[0]; var work_area = null; switch (value) { case 'aiv': /* falls through */ default: work_area = new Morebits.quickForm.element({ type: 'field', label: wgULS('报告用户破坏', '報告用戶破壞'), name: 'work_area' }); work_area.append({ type: 'input', name: 'page', label: wgULS('相关页面:', '相關頁面:'), tooltip: wgULS('如不希望让报告链接到页面,请留空', '如不希望讓報告連結到頁面,請留空'), value: mw.util.getParamValue('vanarticle') || '', event: function(e) { var value = e.target.value; var root = e.target.form; if (value === '') { root.badid.disabled = root.goodid.disabled = true; } else { root.badid.disabled = false; root.goodid.disabled = root.badid.value === ''; } } }); work_area.append({ type: 'input', name: 'badid', label: wgULS('受到破坏的修订版本:', '受到破壞的修訂版本:'), tooltip: wgULS('留空以略过差异', '留空以略過差異'), value: mw.util.getParamValue('vanarticlerevid') || '', disabled: !mw.util.getParamValue('vanarticle'), event: function(e) { var value = e.target.value; var root = e.target.form; root.goodid.disabled = value === ''; } }); work_area.append({ type: 'input', name: 'goodid', label: wgULS('破坏前的修订版本:', '破壞前的修訂版本:'), tooltip: wgULS('留空以略过差异的较早版本', '留空以略過差異的較早版本'), value: mw.util.getParamValue('vanarticlegoodrevid') || '', disabled: !mw.util.getParamValue('vanarticle') || mw.util.getParamValue('vanarticlerevid') }); work_area.append({ type: 'checkbox', name: 'arvtype', list: [ { label: wgULS('已发出最后(层级4或4im)警告', '已發出最後(層級4或4im)警告'), value: 'final' }, { label: wgULS('封禁过期后随即破坏', '封禁過期後隨即破壞'), value: 'postblock' }, { label: wgULS('显而易见的纯破坏用户', '顯而易見的純破壞用戶'), value: 'vandalonly', disabled: mw.util.isIPAddress(root.uid.value) }, { label: wgULS('显而易见的spambot或失窃账户', '顯而易見的spambot或失竊帳戶'), value: 'spambot' }, { label: wgULS('仅用来散发广告宣传的用户', '僅用來散發廣告宣傳的用戶'), value: 'promoonly' } ] }); work_area.append({ type: 'textarea', name: 'reason', label: wgULS('评论:', '評論:') }); work_area = work_area.render(); old_area.parentNode.replaceChild(work_area, old_area); break; } }; Twinkle.arv.callback.evaluate = function(e) { var form = e.target; var reason = ''; var comment = ''; if (form.reason) { comment = form.reason.value; } var uid = form.uid.value; var checkTitle = function(title, revid) { if (/https?:\/\//.test(title)) { alert(wgULS('页面名称不能使用网址。', '頁面名稱不能使用網址。')); return false; } var page; try { page = new mw.Title(title); } catch (error) { alert(wgULS('“', '「') + title + wgULS('”不是一个有效的页面名称,如要使用差异链接请放在“评论”', '」不是一個有效的頁面名稱,如要使用差異連結請放在「評論」') + (revid ? wgULS(',或正确输入“修订版本”', ',或正確輸入「修訂版本」') : '') + '。'); return false; } if (page.namespace === -1) { alert(wgULS('“', '「') + title + wgULS('”属于特殊页面,如要使用差异链接请放在“评论”', '」屬於特殊頁面,如要使用差異連結請放在「評論」') + (revid ? wgULS(',或正确输入“修订版本”', ',或正確輸入「修訂版本」') : '') + '。'); return false; } return page; }; var types, page; switch (form.category.value) { // Report user for vandalism case 'aiv': /* falls through */ default: types = form.getChecked('arvtype'); if (!types.length && comment === '') { alert(wgULS('您必须指定理由', '您必須指定理由')); return; } types = types.map(function(v) { switch (v) { case 'final': return '已发出最后警告'; case 'postblock': return '封禁过期后随即破坏'; case 'spambot': return '显而易见的spambot或失窃账户'; case 'vandalonly': return '显而易见的纯破坏用户'; case 'promoonly': return '仅用来散发广告宣传的用户'; default: return '位置理由'; } }).join(','); if (form.page.value !== '') { page = checkTitle(form.page.value, true); if (!page) { return; } reason = '* {{pagelinks|' + (page.getPrefixedText().indexOf('=') > -1 ? '1=' : '') + page.getPrefixedText() + '}}'; if (form.badid.value !== '') { reason += '({{diff|' + page.getPrefixedText() + '|' + form.badid.value + '|' + form.goodid.value + '|diff}})'; } reason += '\n'; } if (types) { reason += '* ' + types; } if (comment !== '') { comment = comment.replace(/\r?\n/g, '\n*:'); // indent newlines reason += (types ? '。' : '* ') + comment; } reason = reason.trim(); if (reason.search(/[.?!;。?!;]$/) === -1) { reason += '。'; } reason += '\n* 发现人:~~~~\n* 处理:'; Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); Morebits.wiki.actionCompleted.redirect = 'Wiktionary:啤酒馆'; Morebits.wiki.actionCompleted.notice = '报告完成'; var aivPage = new Morebits.wiki.page('Wiktionary:啤酒馆', wgULS('处理请求', '處理請求')); aivPage.setFollowRedirect(true); aivPage.load(function() { var text = aivPage.getPageText(); var $aivLink = '<a target="_blank" href="/wiki/WT:BP">WT:BP</a>'; // check if user has already been reported if (new RegExp('==\\s*\\{\\{\\s*(?:[Vv]andal)\\s*\\|\\s*(?:1=)?\\s*' + RegExp.escape(uid, true) + '\\s*\\}\\}\\s*==').test(text)) { aivPage.getStatusElement().error(wgULS('报告已存在,将不会加入新的', '報告已存在,將不會加入新的')); Morebits.status.printUserText(reason, wgULS('您键入的评论已在下方提供,您可以将其加入到' + $aivLink + '已存在的小节中:', '您鍵入的評論已在下方提供,您可以將其加入到' + $aivLink + '已存在的小節中:')); return; } aivPage.getStatusElement().status(wgULS('加入新报告…', '加入新報告…')); aivPage.setEditSummary(wgULS('报告', '報告') + '[[Special:Contributions/' + uid + '|' + uid + ']]。'); aivPage.setChangeTags(Twinkle.changeTags); aivPage.setAppendText('\n== {{vandal|' + (/=/.test(uid) ? '1=' : '') + uid + '}} ==\n' + reason); aivPage.append(); }); break; } }; })(jQuery); // </nowiki> dx33663drzi1325dvncy2abcysb7dco MediaWiki:Gadget-twinklewarn.js 8 1368860 9943049 6328891 2026-10-12T15:41:33Z Xiplus 25402 Repo at 375b1e1: Remove support for deprecated SD (Flow) page 9943049 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { /* **************************************** *** twinklewarn.js: Warn module **************************************** * Mode of invocation: Tab ("Warn") * Active on: Any page with relevant user name (userspace, contribs, * etc.), as well as the rollback success page */ Twinkle.warn = function twinklewarn() { if (Morebits.relevantUserName()) { Twinkle.addPortletLink(Twinkle.warn.callback, '警告', 'tw-warn', wgULS('警告或提醒用户', '警告或提醒用戶')); } // Modify URL of talk page on rollback success pages. This is only used // when a user Ctrl+clicks on a rollback link. if (mw.config.get('wgAction') === 'rollback') { var $vandalTalkLink = $('#mw-rollback-success').find('.mw-usertoollinks a').first(); if ($vandalTalkLink.length) { Twinkle.warn.makeVandalTalkLink($vandalTalkLink); $vandalTalkLink.css('font-weight', 'bold'); } } if (mw.config.get('wgCanonicalSpecialPageName') === 'AbuseLog' && mw.config.get('wgAbuseFilterVariables') !== null) { var afTalkLink = $('.mw-usertoollinks-talk').first(); if (afTalkLink.length) { Twinkle.warn.makeVandalTalkLink(afTalkLink, mw.config.get('wgAbuseFilterVariables').page_prefixedtitle); afTalkLink.css('font-weight', 'bold'); } } // Override the mw.notify function to allow us to inject a link into the // rollback success popup. Only users with the 'rollback' right need this, // but we have no nice way of knowing who has that right (what with global // groups and the like) /* else if( mw.config.get('wgAction') === 'history' ) { mw.notifyOriginal = mw.notify; mw.notify = function mwNotifyTwinkleOverride(message, options) { // This is a horrible, awful hack to add a link to the rollback success // popup. All other notification popups should be left untouched. // It won't work for people whose user language is not English. // As it's a hack, it's liable to stop working or break sometimes, // particularly if the text or format of the confirmation message // (MediaWiki:Rollback-success-notify) changes. var regexMatch; if ( options && options.title && mw.msg && options.title === mw.msg('actioncomplete') && message && $.isArray(message) && message[0] instanceof HTMLParagraphElement && (regexMatch = /^(?:回退|還原|取消|撤销|撤銷)(.+)(?:编辑|編輯|做出的編輯|做出的编辑|做出的修订版本|做出的修訂版本)/.exec(message[0].innerText)) ) { // Create a nicely-styled paragraph to place the link in var $p = $('<p/>'); $p.css("margin", "0.5em -1.5em -1.5em"); $p.css("padding", "0.5em 1.5em 0.8em"); $p.css("border-top", "1px #666 solid"); $p.css("cursor", "default"); $p.click(function(e) { e.stopPropagation(); }); // Create the new talk link and append it to the end of the message var $vandalTalkLink = $('<a/>'); $vandalTalkLink.text("用Twinkle警告用户"); //$vandalTalkLink.css("display", "block"); $vandalTalkLink.attr("href", mw.util.getUrl("User talk:" + regexMatch[1])); Twinkle.warn.makeVandalTalkLink($vandalTalkLink); $p.append($vandalTalkLink); message[0].appendChild($p.get()[0]); // Don't auto-hide the notification. It only stays around for 5 seconds by // default, which might not be enough time for the user to read it and // click the link options.autoHide = false; } mw.notifyOriginal.apply(mw, arguments); }; } */ // for testing, use: // mw.notify([ $("<p>Reverted edits by foo; changed</p>")[0] ], { title: mw.msg('actioncomplete') } ); }; Twinkle.warn.makeVandalTalkLink = function($vandalTalkLink) { $vandalTalkLink.wrapInner($('<span/>').attr('title', wgULS('如果合适,您可以用Twinkle在该用户对话页上做出警告。', '如果合適,您可以用Twinkle在該用戶對話頁上做出警告。'))); var extraParam = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm); var href = $vandalTalkLink.attr('href'); if (href.indexOf('?') === -1) { $vandalTalkLink.attr('href', href + '?' + extraParam); } else { $vandalTalkLink.attr('href', href + '&' + extraParam); } }; Twinkle.warn.callback = function twinklewarnCallback() { if (Morebits.relevantUserName() === mw.config.get('wgUserName') && !confirm(wgULS('您将要警告自己!您确定要继续吗?', '您將要警告自己!您確定要繼續嗎?'))) { return; } var Window = new Morebits.simpleWindow(600, 440); Window.setTitle(wgULS('警告、通知用户', '警告、通知用戶')); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('选择警告级别', '選擇警告級別'), 'WP:WARN'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'WP:TW/DOC#warn'); var form = new Morebits.quickForm(Twinkle.warn.callback.evaluate); var main_select = form.append({ type: 'field', label: wgULS('选择要发送的警告或通知类型', '選擇要發送的警告或通知類別'), tooltip: wgULS('首先选择一组,再选择具体的警告模板。', '首先選擇一組,再選擇具體的警告模板。') }); var main_group = main_select.append({ type: 'select', name: 'main_group', event: Twinkle.warn.callback.change_category }); var defaultGroup = parseInt(Twinkle.getPref('defaultWarningGroup'), 10); main_group.append({ type: 'option', label: wgULS('层级1', '層級1'), value: 'level1', selected: defaultGroup === 1 || defaultGroup < 1 || (Morebits.userIsSysop ? defaultGroup > 8 : defaultGroup > 7) }); main_group.append({ type: 'option', label: wgULS('层级2', '層級2'), value: 'level2', selected: defaultGroup === 2 }); main_group.append({ type: 'option', label: wgULS('层级3', '層級3'), value: 'level3', selected: defaultGroup === 3 }); main_group.append({ type: 'option', label: wgULS('层级4', '層級4'), value: 'level4', selected: defaultGroup === 4 }); main_group.append({ type: 'option', label: wgULS('层级4im', '層級4im'), value: 'level4im', selected: defaultGroup === 5 }); main_group.append({ type: 'option', label: wgULS('单层级通知', '單層級通知'), value: 'singlenotice', selected: defaultGroup === 6 }); main_group.append({ type: 'option', label: wgULS('单层级警告', '單層級警告'), value: 'singlewarn', selected: defaultGroup === 7 }); if (Twinkle.getPref('customWarningList').length) { main_group.append({ type: 'option', label: wgULS('自定义警告', '自訂警告'), value: 'custom', selected: defaultGroup === 9 }); } main_select.append({ type: 'select', name: 'sub_group', event: Twinkle.warn.callback.change_subcategory }); // Will be empty to begin with. form.append({ type: 'input', name: 'article', label: wgULS('页面链接', '頁面連結'), value: mw.util.getParamValue('vanarticle') || '', size: 50, tooltip: wgULS('给模板中加入一页面链接,可留空。', '給模板中加入一頁面連結,可留空。'), placeholder: wgULS('仅限一个,勿使用网址、[[ ]],可使用Special:Diff', '僅限一個,勿使用網址、[[ ]],可使用Special:Diff') }); var more = form.append({ type: 'field', name: 'reasonGroup', label: wgULS('警告消息', '警告訊息') }); more.append({ type: 'textarea', label: wgULS('可选消息:', '可選訊息:'), name: 'reason', tooltip: wgULS('理由或是附加消息', '理由或是附加訊息') }); var previewlink = document.createElement('a'); $(previewlink).click(function() { Twinkle.warn.callbacks.preview(result); // |result| is defined below }); previewlink.style.cursor = 'pointer'; previewlink.textContent = wgULS('预览', '預覽'); more.append({ type: 'div', id: 'warningpreview', label: [ previewlink ] }); more.append({ type: 'div', id: 'twinklewarn-previewbox', style: 'display: none' }); more.append({ type: 'submit', label: '提交' }); var result = form.render(); Window.setContent(result); Window.display(); result.main_group.root = result; result.previewer = new Morebits.wiki.preview($(result).find('div#twinklewarn-previewbox').last()[0]); // We must init the first choice (General Note); var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.main_group.dispatchEvent(evt); }; // This is all the messages that might be dispatched by the code // Each of the individual templates require the following information: // label (required): A short description displayed in the dialog // summary (required): The edit summary used. If an article name is entered, the summary is postfixed with "on [[article]]" // suppressArticleInSummary (optional): Set to true to suppress showing the article name in the edit summary. Useful if the warning relates to attack pages, or some such. // 警告:警告消息有简体和繁体两个版本,请注意同时维护! Twinkle.warn.messages = wgULS({ level1: { '不同类型的非建设编辑': { 'uw-vandalism1': { label: '明显的破坏', summary: '层级1:明显破坏' }, 'uw-test1': { label: '进行编辑测试而未及时清理', summary: '层级1:进行编辑测试而未及时清理' }, 'uw-delete1': { label: '不恰当地移除页面内容、模板或资料', summary: '层级1:不恰当地页面条目内容、模板或资料' }, 'uw-redirect1': { label: '创建破坏性的重定向', summary: '层级1:创建破坏性的重定向' }, 'uw-tdel1': { label: '在问题仍未解决的情况下移除维护性模板', summary: '层级1:移除维护性模板' }, 'uw-joke1': { label: '在百科全书内容中加入玩笑', summary: '层级1:加入不当玩笑' }, 'uw-create1': { label: '创建不当页面', summary: '层级1:创建不当页面' }, 'uw-upload1': { label: '上传不当图像', summary: '层级1:上传不当图像' }, 'uw-image1': { label: '在页面中加入不当图片', summary: '层级1:在页面中加入不当图片' }, 'uw-nor1': { label: '在条目中加入原创研究', summary: '层级1:在条目中加入原创研究' }, 'uw-politicalbias1': { label: '违反两岸用语方针', summary: '层级1:违反两岸用语方针' } }, '增加商品或政治广告': { 'uw-spam1': { label: '增加不合适的外部链接', summary: '层级1:增加不合适的外部链接' }, 'uw-advert1': { label: '利用维基百科来发布广告或推广', summary: '层级1:利用维基百科来发布广告或推广' }, 'uw-npov1': { label: '不遵守中立的观点方针', summary: '层级1:不遵守中立的观点方针' } }, '加插不实及/或诽谤文字': { 'uw-unsourced1': { label: '加入没有可靠来源佐证的内容', summary: '层级1:加入没有可靠来源佐证的内容' }, 'uw-error1': { label: '故意加入不实内容', summary: '层级1:故意加入不实内容' }, 'uw-biog1': { label: '在生者传记中加入没有可靠来源佐证而且可能引发争议的内容', summary: '层级1:在生者传记中加入没有可靠来源佐证而且可能引发争议的内容' }, 'uw-defamatory1': { label: '加入诽谤内容', summary: '层级1:加入诽谤内容' } }, '翻译品质': { 'uw-roughtranslation1': { label: '您翻译的质量有待改善', summary: '您翻译的质量有待改善' } }, '非能接受且违反方针或指引的单方面行为或操作': { 'uw-notcensored1': { label: '因为“内容使人反感”而删除条目内容', summary: '层级1:审查条目内容' }, 'uw-mos1': { label: '不恰当的条目格式、日期、语言等', summary: '层级1:不恰当的条目格式、日期、语言等' }, 'uw-move1': { label: '无故移动条目/新名称不符合命名规范', summary: '层级1:不恰当地移动页面' }, 'uw-cd1': { label: '清空讨论页', summary: '层级1:清空讨论页' }, 'uw-chat1': { label: '在讨论页发表与改善条目无关的内容', summary: '层级1:在讨论页发表与改善条目无关的内容' }, 'uw-tpv1': { label: '修改他人留言', summary: '层级1:修改他人留言' }, 'uw-afd1': { label: '移除{{afd}}(页面存废讨论)模板', summary: '层级1:移除{{afd}}(页面存废讨论)模板' }, 'uw-speedy1': { label: '移除{{delete}}(快速删除)模板', summary: '层级1:移除{{delete}}(快速删除)模板' } }, '对其他用户和条目的态度': { 'uw-npa1': { label: '针对特定用户的人身攻击', summary: '层级1:针对特定用户的人身攻击' }, 'uw-agf1': { label: '没有假定善意', summary: '层级1:没有假定善意' }, 'uw-own1': { label: '主张条目所有权', summary: '层级1:主张条目所有权' }, 'uw-tempabuse1': { label: '不当使用警告或封禁模板', summary: '层级1:不当使用警告或封禁模板' } } }, level2: { '不同类型的非建设编辑': { 'uw-vandalism2': { label: '明显的破坏', summary: '层级2:明显破坏' }, 'uw-test2': { label: '进行损毁性的编辑测试', summary: '层级2:进行编辑测试' }, 'uw-delete2': { label: '不恰当地移除页面内容、模板或资料', summary: '层级2:不恰当地移除页面内容、模板或资料' }, 'uw-redirect2': { label: '创建恶意重定向', summary: '层级2:创建恶意重定向' }, 'uw-tdel2': { label: '在问题仍未解决的情况下移除维护性模板', summary: '层级2:移除维护性模板' }, 'uw-joke2': { label: '在百科全书内容中加入玩笑', summary: '层级2:加入不当玩笑' }, 'uw-create2': { label: '创建不当页面', summary: '层级2:创建不当页面' }, 'uw-upload2': { label: '上传不当图像', summary: '层级2:上传不当图像' }, 'uw-image2': { label: '在页面中加入不当图片', summary: '层级2:在页面中加入不当图片' }, 'uw-nor2': { label: '在条目中加入原创研究', summary: '层级2:在条目中加入原创研究' }, 'uw-politicalbias2': { label: '违反两岸用语方针', summary: '层级2:违反两岸用语方针' } }, '增加商品或政治广告': { 'uw-spam2': { label: '增加垃圾链接', summary: '层级2:增加垃圾链接' }, 'uw-advert2': { label: '利用维基百科来发布广告或推广', summary: '层级2:利用维基百科来发布广告或推广' }, 'uw-npov2': { label: '不遵守中立的观点方针', summary: '层级2:不遵守中立的观点方针' } }, '加插不实及/或诽谤文字': { 'uw-unsourced2': { label: '加入没有可靠来源佐证的内容', summary: '层级2:加入没有可靠来源佐证的内容' }, 'uw-error2': { label: '故意加入不实内容', summary: '层级2:故意加入不实内容' }, 'uw-biog2': { label: '在生者传记中加入没有可靠来源佐证而且可能引发争议的内容', summary: '层级2:在生者传记中加入没有可靠来源佐证而且可能引发争议的内容' }, 'uw-defamatory2': { label: '加入诽谤内容', summary: '层级2:加入诽谤内容' } }, '翻译品质': { 'uw-roughtranslation2': { label: '粗劣翻译', summary: '层级2:粗劣翻译' } }, '非能接受且违反方针或指引的单方面行为或操作': { 'uw-notcensored2': { label: '内容审查', summary: '层级2:内容审查' }, 'uw-mos2': { label: '不恰当的条目格式、日期、语言等', summary: '层级2:不恰当的条目格式、日期、语言等' }, 'uw-move2': { label: '把页面移动到不恰当、违反命名常规或违反共识的标题', summary: '层级2:不恰当地移动页面' }, 'uw-cd2': { label: '清空讨论页', summary: '层级2:清空讨论页' }, 'uw-chat2': { label: '在讨论页发表与改善条目无关的内容', summary: '层级2:在讨论页发表与改善条目无关的内容' }, 'uw-tpv2': { label: '修改他人留言', summary: '层级2:修改他人留言' }, 'uw-afd2': { label: '移除{{afd}}(页面存废讨论)模板', summary: '层级2:移除{{afd}}(页面存废讨论)模板' }, 'uw-speedy2': { label: '移除{{delete}}(快速删除)模板', summary: '层级2:移除{{delete}}(快速删除)模板' } }, '对其他用户和条目的态度': { 'uw-npa2': { label: '针对特定用户的人身攻击', summary: '层级2:针对特定用户的人身攻击' }, 'uw-agf2': { label: '没有假定善意', summary: '层级2:没有假定善意' }, 'uw-own2': { label: '主张条目的所有权', summary: '层级2:主张条目的所有权' }, 'uw-tempabuse2': { label: '不当使用警告或封禁模板', summary: '层级2:不当使用警告或封禁模板' } } }, level3: { '不同类型的非建设编辑': { 'uw-vandalism3': { label: '恶意破坏', summary: '层级3:恶意破坏' }, 'uw-test3': { label: '编辑测试', summary: '层级3:编辑测试' }, 'uw-delete3': { label: '不恰当地移除页面内容、模板或资料', summary: '层级3:不恰当地移除页面内容、模板或资料' }, 'uw-redirect3': { label: '创建恶意重定向', summary: '层级3:创建恶意重定向' }, 'uw-tdel3': { label: '移除维护性模板', summary: '层级3:移除维护性模板' }, 'uw-joke3': { label: '在百科全书内容中加入不当玩笑', summary: '层级3:在百科全书内容中加入不当玩笑' }, 'uw-create3': { label: '创建不当页面', summary: '层级3:创建不当页面' }, 'uw-upload3': { label: '上传不当图像', summary: '层级3:上传不当图像' }, 'uw-image3': { label: '在页面中加入不当图片', summary: '层级3:在页面中加入不当图片' }, 'uw-nor3': { label: '在条目中加入原创研究', summary: '层级3:在条目中加入原创研究' }, 'uw-politicalbias3': { label: '违反两岸用语方针', summary: '层级3:违反两岸用语方针' } }, '增加商品或政治广告': { 'uw-spam3': { label: '增加垃圾链接', summary: '层级3:增加垃圾链接' }, 'uw-advert3': { label: '利用维基百科来发布广告或推广', summary: '层级3:利用维基百科来发布广告或推广' }, 'uw-npov3': { label: '违反中立的观点方针', summary: '层级3:违反中立的观点方针' } }, '加插不实及/或诽谤文字': { 'uw-unsourced3': { label: '加入没有可靠来源佐证的内容', summary: '层级3:加入没有可靠来源佐证的内容' }, 'uw-error3': { label: '故意加入不实内容', summary: '层级3:故意加入不实内容' }, 'uw-biog3': { label: '在生者传记中加入没有可靠来源佐证而且带有争议的内容', summary: '层级3:在生者传记中加入没有可靠来源佐证而且可能引发争议的内容' }, 'uw-defamatory3': { label: '加入诽谤内容', summary: '层级3:加入诽谤内容' } }, '翻译品质': { 'uw-roughtranslation3': { label: '粗劣翻译', summary: '层级3:粗劣翻译' } }, '非能接受且违反方针或指引的单方面行为或操作': { 'uw-notcensored3': { label: '审查内容', summary: '层级3:审查内容' }, 'uw-mos3': { label: '违反格式、日期、语言等规定', summary: '层级3:违反格式、日期、语言等规定' }, 'uw-move3': { label: '不恰当地移动页面', summary: '层级3:不恰当地移动页面' }, 'uw-cd3': { label: '清空讨论页', summary: '层级3:清空讨论页' }, 'uw-chat3': { label: '在讨论页发表无关内容', summary: '层级3:在讨论页发表无关内容' }, 'uw-tpv3': { label: '修改他人留言', summary: '层级3:修改他人留言' }, 'uw-afd3': { label: '移除{{afd}}(页面存废讨论)模板', summary: '层级3:移除{{afd}}(页面存废讨论)模板' }, 'uw-speedy3': { label: '移除{{delete}}(快速删除)模板', summary: '层级3:移除{{delete}}(快速删除)模板' } }, '对其他用户和条目的态度': { 'uw-npa3': { label: '针对特定用户的人身攻击', summary: '层级3:针对特定用户的人身攻击' }, 'uw-agf3': { label: '没有假定善意', summary: '层级3:没有假定善意' }, 'uw-own3': { label: '主张条目的所有权', summary: '层级3:主张条目的所有权' }, 'uw-tempabuse3': { label: '不当使用警告或封禁模板', summary: '层级3:不当使用警告或封禁模板' } } }, level4: { '不同类型的非建设编辑': { 'uw-vandalism4': { label: '恶意破坏', summary: '层级4:恶意破坏' }, 'uw-test4': { label: '编辑测试', summary: '层级4:编辑测试' }, 'uw-delete4': { label: '移除页面、移除内容或模板', summary: '层级4:移除页面、移除内容或模板' }, 'uw-redirect4': { label: '创建恶意重定向', summary: '层级4:创建恶意重定向' }, 'uw-tdel4': { label: '移除维护性模板', summary: '层级4:移除维护性模板' }, 'uw-joke4': { label: '在百科全书内容中加入不当玩笑', summary: '层级4:在百科全书内容中加入不当玩笑' }, 'uw-create4': { label: '创建不当页面', summary: '层级4:创建不当页面' }, 'uw-upload4': { label: '上传不当图像', summary: '层级4:上传不当图像' }, 'uw-image4': { label: '在页面中加入不当图片', summary: '层级4:在页面中加入不当图片' }, 'uw-politicalbias4': { label: '违反两岸用语方针', summary: '层级4:违反两岸用语方针' } }, '增加商品或政治广告': { 'uw-spam4': { label: '增加垃圾链接', summary: '层级4:增加垃圾链接' }, 'uw-advert4': { label: '利用维基百科来发布广告或推广', summary: '层级4:利用维基百科来发布广告或推广' }, 'uw-npov4': { label: '违反中立的观点方针', summary: '层级4:违反中立的观点方针' } }, '加插不实及/或诽谤文字': { 'uw-biog4': { label: '加入有关在生人物而又缺乏来源的资料', summary: '层级4:加入有关在生人物而又缺乏来源的资料' }, 'uw-defamatory4': { label: '加入诽谤内容', summary: '层级4:加入诽谤内容' } }, '非能接受且违反方针或指引的单方面行为或操作': { 'uw-mos4': { label: '违反格式、日期、语言等相关规定', summary: '层级4:违反格式、日期、语言等相关规定' }, 'uw-move4': { label: '不恰当地移动页面', summary: '层级4:不恰当地移动页面' }, 'uw-chat4': { label: '在讨论页进行不当讨论', summary: '层级4:在讨论页进行不当讨论' }, 'uw-afd4': { label: '移除{{afd}}模板', summary: '层级4:移除{{afd}}模板' }, 'uw-speedy4': { label: '移除{{delete}}模板', summary: '层级4:移除{{delete}}模板' } }, '对其他用户和条目的态度': { 'uw-npa4': { label: '针对特定用户的人身攻击', summary: '层级4:针对特定用户的人身攻击' }, 'uw-tempabuse4': { label: '不当使用警告或封禁模板', summary: '层级4:不当使用警告或封禁模板' } } }, level4im: { '不同类型的非建设编辑': { 'uw-vandalism4im': { label: '恶意破坏', summary: '层级4im:恶意破坏' }, 'uw-delete4im': { label: '移除页面内容、模板或资料', summary: '层级4im:移除页面内容、模板或资料' }, 'uw-redirect4im': { label: '创建恶意重定向', summary: '层级4im:创建恶意重定向' }, 'uw-joke4im': { label: '加入不当玩笑', summary: '层级4im:加入不当玩笑' }, 'uw-create4im': { label: '创建不当页面', summary: '层级4im:创建不当页面' }, 'uw-upload4im': { label: '上传不当图像', summary: '层级4im:上传不当图像' }, 'uw-image4im': { label: '加入不恰当的图片', summary: '层级4im:加入不恰当的图片' }, 'uw-politicalbias4im': { label: '违反两岸用语方针', summary: '层级4im:违反两岸用语方针' } }, '增加商品或政治广告': { 'uw-spam4im': { label: '增加垃圾连结', summary: '层级4im:增加垃圾连结' } }, '加插不实及/或诽谤文字': { 'uw-biog4im': { label: '加入有关在生人物而又缺乏来源的资料', summary: '层级4im:加入有关在生人物而又缺乏来源的资料' }, 'uw-defamatory4im': { label: '加入诽谤内容', summary: '层级4im:加入诽谤内容' } }, '非能接受且违反方针或指引的单方面行为或操作': { 'uw-move4im': { label: '不恰当地移动页面', summary: '层级4im:不恰当地移动页面' } }, '对其他用户和条目的态度': { 'uw-npa4im': { label: '针对特定用户的人身攻击', summary: '层级4im:针对特定用户的人身攻击' }, 'uw-tempabuse4im': { label: '不当使用警告或封禁模板', summary: '层级4im:不当使用警告或封禁模板' } } }, singlenotice: { 'uw-2redirect': { label: '在移动页面后应该修复双重重定向', summary: '单层级通知:在移动页面后应该修复双重重定向' }, 'uw-aiv': { label: '举报的并不是破坏者,或者举报破坏前未进行警告', summary: '单层级通知:不恰当地举报破坏' }, 'uw-articlesig': { label: '在条目中签名', summary: '单层级通知:在条目中签名' }, 'uw-autobiography': { label: '建立自传', summary: '单层级通知:建立自传' }, 'uw-badcat': { label: '加入错误的页面分类', summary: '单层级通知:加入错误的页面分类' }, 'uw-bite': { label: '伤害新手', summary: '单层级通知:伤害新手' }, 'uw-booktitle': { label: '没有使用书名号来标示书籍、电影、音乐专辑等', summary: '单层级通知:没有使用书名号来标示书籍、电影、音乐专辑等' }, 'uw-c&pmove': { label: '剪贴移动', summary: '单层级通知:剪贴移动' }, 'uw-chinese': { label: '不是以中文进行沟通', summary: '单层级通知:不是以中文进行沟通' }, 'uw-coi': { label: '利益冲突', summary: '单层级通知:利益冲突' }, 'uw-copyright-friendly': { label: '初次加入侵犯版权的内容', summary: '单层级通知:初次加入侵犯版权的内容' }, 'uw-copyviorewrite': { label: '在侵权页面直接重写条目', summary: '单层级通知:在侵权页面直接重写条目' }, 'uw-crystal': { label: '加入臆测或未确认的讯息', summary: '单层级通知:加入臆测或未确认的讯息' }, 'uw-csd': { label: '快速删除理由不当', summary: '单层级通知:快速删除理由不当' }, 'uw-dab': { label: '消歧义页格式错误', summary: '单层级通知:消歧义页格式错误' }, 'uw-date': { label: '不必要地更换日期格式', summary: '单层级通知:不必要地更换日期格式' }, 'uw-editsummary': { label: '没有使用编辑摘要', summary: '单层级通知:没有使用编辑摘要' }, 'uw-hangon': { label: '没有在讨论页说明暂缓快速删除理由', summary: '单层级通知:没有在讨论页说明暂缓快速删除理由' }, 'uw-lang': { label: '不必要地将条目所有文字换成简体或繁体中文', summary: '单层级通知:不必要地将条目所有文字换成简体或繁体中文' }, 'uw-langmove': { label: '不必要地将条目标题换成简体或繁体中文', summary: '单层级通知:不必要地将条目标题换成简体或繁体中文' }, 'uw-linking': { label: '过度加入红字连结或重复蓝字连结', summary: '单层级通知:过度加入红字连结或重复蓝字连结' }, 'uw-minor': { label: '不适当地使用小修改选项', summary: '单层级通知:不适当地使用小修改选项' }, 'uw-notaiv': { label: '向“当前的破坏”中报告的是用户纷争而不是破坏', summary: '单层级通知:向“当前的破坏”中报告的是用户纷争而不是破坏' }, 'uw-notvote': { label: '我们以共识处事,而不仅仅是投票', summary: '单层级通知:我们以共识处事,而不仅仅是投票' }, 'uw-preview': { label: '请使用预览按钮来避免不必要的错误', summary: '单层级通知:请使用预览按钮来避免不必要的错误' }, 'uw-sandbox': { label: '移除沙盒的置顶模板{{sandbox}}', summary: '单层级通知:移除沙盒的置顶模板{{sandbox}}' }, 'uw-selfrevert': { label: '感谢您自行回退自己的测试,以后不要再这样做了', summary: '单层级通知:回退个人的测试' }, 'uw-subst': { label: '谨记要替代模板(subst)', summary: '单层级通知:谨记要替代模板' }, 'uw-talkinarticle': { label: '在条目页中留下意见', summary: '单层级通知:在条目页中留下意见' }, 'uw-tilde': { label: '没有在讨论页上签名', summary: '单层级通知:没有在讨论页上签名' }, 'uw-translated': { label: '翻译条目未标注原作者', summary: '单层级通知:翻译条目未标注原作者' }, 'uw-uaa': { label: '向需要管理员注意的用户名报告的用户名称并不违反方针', summary: '单层级通知:向需要管理员注意的用户名报告的用户名称并不违反方针' }, 'uw-warn': { label: '警告破坏用户', summary: '单层级通知:警告破坏用户' }, 'uw-mosiw': { label: '不要使用跨语言链接', summary: '单层级通知:不要使用跨语言链接' }, 'uw-badtwinkle': { label: '不恰当地使用Twinkle警告别人', summary: '单层级通知:不恰当地使用Twinkle警告别人' } }, singlewarn: { 'uw-3rr': { label: '用户潜在违反回退不过三原则的可能性', summary: '单层级警告:用户潜在违反回退不过三原则的可能性' }, 'uw-attack': { label: '建立人身攻击页面', summary: '单层级警告:建立人身攻击页面', suppressArticleInSummary: true }, 'uw-bv': { label: '公然地破坏', summary: '单层级警告:公然地破坏' }, 'uw-canvass': { label: '不恰当地拉票', summary: '单层级警告:不恰当地拉票' }, 'uw-copyright': { label: '侵犯版权', summary: '单层级警告:侵犯版权' }, 'uw-copyright-link': { label: '连结到有版权的材料', summary: '单层级警告:连结到有版权的材料' }, 'uw-fakesource': { label: '虚构资料来源或引文', summary: '单层级警告:虚构资料来源或引文' }, 'uw-hoax': { label: '建立恶作剧', summary: '单层级警告:建立恶作剧' }, 'uw-incompletecite': { label: '列出的资料来源欠缺若干详情而不易查找', summary: '单层级警告:列出的资料来源欠缺若干详情而不易查找' }, 'uw-legal': { label: '诉诸法律威胁', summary: '单层级警告:诉诸法律威胁' }, 'uw-longterm': { label: '长期的破坏', summary: '单层级警告:长期的破坏' }, 'uw-multipleIPs': { label: '使用多个IP地址', summary: '单层级警告:使用多个IP地址' }, 'uw-npov-tvd': { label: '在剧集条目中加入奸角等非中立描述', summary: '单层级警告:在剧集条目中加入奸角等非中立描述' }, 'uw-pinfo': { label: '张贴他人隐私', summary: '单层级警告:张贴他人隐私' }, 'uw-upv': { label: '破坏他人用户页', summary: '单层级警告:破坏他人用户页' }, 'uw-selfinventedname': { label: '不适当地自创新名词、新译名', summary: '单层级警告:不适当地自创新名词、新译名' }, 'uw-substub': { label: '创建小小作品', summary: '单层级警告:创建小小作品' }, 'uw-username': { label: '使用不恰当的用户名', summary: '单层级警告:使用不恰当的用户名' }, 'uw-wrongsummary': { label: '在编辑摘要制造不适当的内容', summary: '单层级警告:在编辑摘要制造不适当的内容' } } }, { level1: { '不同類別的非建設編輯': { 'uw-vandalism1': { label: '明顯的破壞', summary: '層級1:明顯破壞' }, 'uw-test1': { label: '進行編輯測試而未及時清理', summary: '層級1:進行編輯測試而未及時清理' }, 'uw-delete1': { label: '不恰當地移除頁面內容、模板或資料', summary: '層級1:不恰當地頁面條目內容、模板或資料' }, 'uw-redirect1': { label: '建立破壞性的重定向', summary: '層級1:建立破壞性的重定向' }, 'uw-tdel1': { label: '在問題仍未解決的情況下移除維護性模板', summary: '層級1:移除維護性模板' }, 'uw-joke1': { label: '在百科全書內容中加入玩笑', summary: '層級1:加入不當玩笑' }, 'uw-create1': { label: '建立不當頁面', summary: '層級1:建立不當頁面' }, 'uw-upload1': { label: '上傳不當圖像', summary: '層級1:上傳不當圖像' }, 'uw-image1': { label: '在頁面中加入不當圖片', summary: '層級1:在頁面中加入不當圖片' }, 'uw-nor1': { label: '在條目中加入原創研究', summary: '層級1:在條目中加入原創研究' }, 'uw-politicalbias1': { label: '違反兩岸用語方針', summary: '層級1:違反兩岸用語方針' } }, '增加商品或政治廣告': { 'uw-spam1': { label: '增加不合適的外部連結', summary: '層級1:增加不合適的外部連結' }, 'uw-advert1': { label: '利用維基百科來發布廣告或推廣', summary: '層級1:利用維基百科來發布廣告或推廣' }, 'uw-npov1': { label: '不遵守中立的觀點方針', summary: '層級1:不遵守中立的觀點方針' } }, '加插不實及/或誹謗文字': { 'uw-unsourced1': { label: '加入沒有可靠來源佐證的內容', summary: '層級1:加入沒有可靠來源佐證的內容' }, 'uw-error1': { label: '故意加入不實內容', summary: '層級1:故意加入不實內容' }, 'uw-biog1': { label: '在生者傳記中加入沒有可靠來源佐證而且可能引發爭議的內容', summary: '層級1:在生者傳記中加入沒有可靠來源佐證而且可能引發爭議的內容' }, 'uw-defamatory1': { label: '加入誹謗內容', summary: '層級1:加入誹謗內容' } }, '翻譯品質': { 'uw-roughtranslation1': { label: '您翻譯的質量有待改善', summary: '您翻譯的質量有待改善' } }, '非能接受且違反方針或指引的單方面行為或操作': { 'uw-notcensored1': { label: '因為「內容使人反感」而刪除條目內容', summary: '層級1:審查條目內容' }, 'uw-mos1': { label: '不恰當的條目格式、日期、語言等', summary: '層級1:不恰當的條目格式、日期、語言等' }, 'uw-move1': { label: '無故移動條目/新名稱不符合命名規範', summary: '層級1:不恰當地移動頁面' }, 'uw-cd1': { label: '清空討論頁', summary: '層級1:清空討論頁' }, 'uw-chat1': { label: '在討論頁發表與改善條目無關的內容', summary: '層級1:在討論頁發表與改善條目無關的內容' }, 'uw-tpv1': { label: '修改他人留言', summary: '層級1:修改他人留言' }, 'uw-afd1': { label: '移除{{afd}}(頁面存廢討論)模板', summary: '層級1:移除{{afd}}(頁面存廢討論)模板' }, 'uw-speedy1': { label: '移除{{delete}}(快速刪除)模板', summary: '層級1:移除{{delete}}(快速刪除)模板' } }, '對其他用戶和條目的態度': { 'uw-npa1': { label: '針對特定用戶的人身攻擊', summary: '層級1:針對特定用戶的人身攻擊' }, 'uw-agf1': { label: '沒有假定善意', summary: '層級1:沒有假定善意' }, 'uw-own1': { label: '主張條目所有權', summary: '層級1:主張條目所有權' }, 'uw-tempabuse1': { label: '不當使用警告或封禁模板', summary: '層級1:不當使用警告或封禁模板' } } }, level2: { '不同類別的非建設編輯': { 'uw-vandalism2': { label: '明顯的破壞', summary: '層級2:明顯破壞' }, 'uw-test2': { label: '進行損毀性的編輯測試', summary: '層級2:進行編輯測試' }, 'uw-delete2': { label: '不恰當地移除頁面內容、模板或資料', summary: '層級2:不恰當地移除頁面內容、模板或資料' }, 'uw-redirect2': { label: '建立惡意重定向', summary: '層級2:建立惡意重定向' }, 'uw-tdel2': { label: '在問題仍未解決的情況下移除維護性模板', summary: '層級2:移除維護性模板' }, 'uw-joke2': { label: '在百科全書內容中加入玩笑', summary: '層級2:加入不當玩笑' }, 'uw-create2': { label: '建立不當頁面', summary: '層級2:建立不當頁面' }, 'uw-upload2': { label: '上傳不當圖像', summary: '層級2:上傳不當圖像' }, 'uw-image2': { label: '在頁面中加入不當圖片', summary: '層級2:在頁面中加入不當圖片' }, 'uw-nor2': { label: '在條目中加入原創研究', summary: '層級2:在條目中加入原創研究' }, 'uw-politicalbias2': { label: '違反兩岸用語方針', summary: '層級2:違反兩岸用語方針' } }, '增加商品或政治廣告': { 'uw-spam2': { label: '增加垃圾連結', summary: '層級2:增加垃圾連結' }, 'uw-advert2': { label: '利用維基百科來發布廣告或推廣', summary: '層級2:利用維基百科來發布廣告或推廣' }, 'uw-npov2': { label: '不遵守中立的觀點方針', summary: '層級2:不遵守中立的觀點方針' } }, '加插不實及/或誹謗文字': { 'uw-unsourced2': { label: '加入沒有可靠來源佐證的內容', summary: '層級2:加入沒有可靠來源佐證的內容' }, 'uw-error2': { label: '故意加入不實內容', summary: '層級2:故意加入不實內容' }, 'uw-biog2': { label: '在生者傳記中加入沒有可靠來源佐證而且可能引發爭議的內容', summary: '層級2:在生者傳記中加入沒有可靠來源佐證而且可能引發爭議的內容' }, 'uw-defamatory2': { label: '加入誹謗內容', summary: '層級2:加入誹謗內容' } }, '翻譯品質': { 'uw-roughtranslation2': { label: '粗劣翻譯', summary: '層級2:粗劣翻譯' } }, '非能接受且違反方針或指引的單方面行為或操作': { 'uw-notcensored2': { label: '內容審查', summary: '層級2:內容審查' }, 'uw-mos2': { label: '不恰當的條目格式、日期、語言等', summary: '層級2:不恰當的條目格式、日期、語言等' }, 'uw-move2': { label: '把頁面移動到不恰當、違反命名常規或違反共識的標題', summary: '層級2:不恰當地移動頁面' }, 'uw-cd2': { label: '清空討論頁', summary: '層級2:清空討論頁' }, 'uw-chat2': { label: '在討論頁發表與改善條目無關的內容', summary: '層級2:在討論頁發表與改善條目無關的內容' }, 'uw-tpv2': { label: '修改他人留言', summary: '層級2:修改他人留言' }, 'uw-afd2': { label: '移除{{afd}}(頁面存廢討論)模板', summary: '層級2:移除{{afd}}(頁面存廢討論)模板' }, 'uw-speedy2': { label: '移除{{delete}}(快速刪除)模板', summary: '層級2:移除{{delete}}(快速刪除)模板' } }, '對其他用戶和條目的態度': { 'uw-npa2': { label: '針對特定用戶的人身攻擊', summary: '層級2:針對特定用戶的人身攻擊' }, 'uw-agf2': { label: '沒有假定善意', summary: '層級2:沒有假定善意' }, 'uw-own2': { label: '主張條目的所有權', summary: '層級2:主張條目的所有權' }, 'uw-tempabuse2': { label: '不當使用警告或封禁模板', summary: '層級2:不當使用警告或封禁模板' } } }, level3: { '不同類別的非建設編輯': { 'uw-vandalism3': { label: '惡意破壞', summary: '層級3:惡意破壞' }, 'uw-test3': { label: '編輯測試', summary: '層級3:編輯測試' }, 'uw-delete3': { label: '不恰當地移除頁面內容、模板或資料', summary: '層級3:不恰當地移除頁面內容、模板或資料' }, 'uw-redirect3': { label: '建立惡意重定向', summary: '層級3:建立惡意重定向' }, 'uw-tdel3': { label: '移除維護性模板', summary: '層級3:移除維護性模板' }, 'uw-joke3': { label: '在百科全書內容中加入不當玩笑', summary: '層級3:在百科全書內容中加入不當玩笑' }, 'uw-create3': { label: '建立不當頁面', summary: '層級3:建立不當頁面' }, 'uw-upload3': { label: '上傳不當圖像', summary: '層級3:上傳不當圖像' }, 'uw-image3': { label: '在頁面中加入不當圖片', summary: '層級3:在頁面中加入不當圖片' }, 'uw-nor3': { label: '在條目中加入原創研究', summary: '層級3:在條目中加入原創研究' }, 'uw-politicalbias3': { label: '違反兩岸用語方針', summary: '層級3:違反兩岸用語方針' } }, '增加商品或政治廣告': { 'uw-spam3': { label: '增加垃圾連結', summary: '層級3:增加垃圾連結' }, 'uw-advert3': { label: '利用維基百科來發布廣告或推廣', summary: '層級3:利用維基百科來發布廣告或推廣' }, 'uw-npov3': { label: '違反中立的觀點方針', summary: '層級3:違反中立的觀點方針' } }, '加插不實及/或誹謗文字': { 'uw-unsourced3': { label: '加入沒有可靠來源佐證的內容', summary: '層級3:加入沒有可靠來源佐證的內容' }, 'uw-error3': { label: '故意加入不實內容', summary: '層級3:故意加入不實內容' }, 'uw-biog3': { label: '在生者傳記中加入沒有可靠來源佐證而且帶有爭議的內容', summary: '層級3:在生者傳記中加入沒有可靠來源佐證而且可能引發爭議的內容' }, 'uw-defamatory3': { label: '加入誹謗內容', summary: '層級3:加入誹謗內容' } }, '翻譯品質': { 'uw-roughtranslation3': { label: '粗劣翻譯', summary: '層級3:粗劣翻譯' } }, '非能接受且違反方針或指引的單方面行為或操作': { 'uw-notcensored3': { label: '審查內容', summary: '層級3:審查內容' }, 'uw-mos3': { label: '違反格式、日期、語言等規定', summary: '層級3:違反格式、日期、語言等規定' }, 'uw-move3': { label: '不恰當地移動頁面', summary: '層級3:不恰當地移動頁面' }, 'uw-cd3': { label: '清空討論頁', summary: '層級3:清空討論頁' }, 'uw-chat3': { label: '在討論頁發表無關內容', summary: '層級3:在討論頁發表無關內容' }, 'uw-tpv3': { label: '修改他人留言', summary: '層級3:修改他人留言' }, 'uw-afd3': { label: '移除{{afd}}(頁面存廢討論)模板', summary: '層級3:移除{{afd}}(頁面存廢討論)模板' }, 'uw-speedy3': { label: '移除{{delete}}(快速刪除)模板', summary: '層級3:移除{{delete}}(快速刪除)模板' } }, '對其他用戶和條目的態度': { 'uw-npa3': { label: '針對特定用戶的人身攻擊', summary: '層級3:針對特定用戶的人身攻擊' }, 'uw-agf3': { label: '沒有假定善意', summary: '層級3:沒有假定善意' }, 'uw-own3': { label: '主張條目的所有權', summary: '層級3:主張條目的所有權' }, 'uw-tempabuse3': { label: '不當使用警告或封禁模板', summary: '層級3:不當使用警告或封禁模板' } } }, level4: { '不同類別的非建設編輯': { 'uw-vandalism4': { label: '惡意破壞', summary: '層級4:惡意破壞' }, 'uw-test4': { label: '編輯測試', summary: '層級4:編輯測試' }, 'uw-delete4': { label: '移除頁面、移除內容或模板', summary: '層級4:移除頁面、移除內容或模板' }, 'uw-redirect4': { label: '建立惡意重定向', summary: '層級4:建立惡意重定向' }, 'uw-tdel4': { label: '移除維護性模板', summary: '層級4:移除維護性模板' }, 'uw-joke4': { label: '在百科全書內容中加入不當玩笑', summary: '層級4:在百科全書內容中加入不當玩笑' }, 'uw-create4': { label: '建立不當頁面', summary: '層級4:建立不當頁面' }, 'uw-upload4': { label: '上傳不當圖像', summary: '層級4:上傳不當圖像' }, 'uw-image4': { label: '在頁面中加入不當圖片', summary: '層級4:在頁面中加入不當圖片' }, 'uw-politicalbias4': { label: '違反兩岸用語方針', summary: '層級4:違反兩岸用語方針' } }, '增加商品或政治廣告': { 'uw-spam4': { label: '增加垃圾連結', summary: '層級4:增加垃圾連結' }, 'uw-advert4': { label: '利用維基百科來發布廣告或推廣', summary: '層級4:利用維基百科來發布廣告或推廣' }, 'uw-npov4': { label: '違反中立的觀點方針', summary: '層級4:違反中立的觀點方針' } }, '加插不實及/或誹謗文字': { 'uw-biog4': { label: '加入有關在生人物而又缺乏來源的資料', summary: '層級4:加入有關在生人物而又缺乏來源的資料' }, 'uw-defamatory4': { label: '加入誹謗內容', summary: '層級4:加入誹謗內容' } }, '非能接受且違反方針或指引的單方面行為或操作': { 'uw-mos4': { label: '違反格式、日期、語言等相關規定', summary: '層級4:違反格式、日期、語言等相關規定' }, 'uw-move4': { label: '不恰當地移動頁面', summary: '層級4:不恰當地移動頁面' }, 'uw-chat4': { label: '在討論頁進行不當討論', summary: '層級4:在討論頁進行不當討論' }, 'uw-afd4': { label: '移除{{afd}}模板', summary: '層級4:移除{{afd}}模板' }, 'uw-speedy4': { label: '移除{{delete}}模板', summary: '層級4:移除{{delete}}模板' } }, '對其他用戶和條目的態度': { 'uw-npa4': { label: '針對特定用戶的人身攻擊', summary: '層級4:針對特定用戶的人身攻擊' }, 'uw-tempabuse4': { label: '不當使用警告或封禁模板', summary: '層級4:不當使用警告或封禁模板' } } }, level4im: { '不同類別的非建設編輯': { 'uw-vandalism4im': { label: '惡意破壞', summary: '層級4im:惡意破壞' }, 'uw-delete4im': { label: '移除頁面內容、模板或資料', summary: '層級4im:移除頁面內容、模板或資料' }, 'uw-redirect4im': { label: '建立惡意重定向', summary: '層級4im:建立惡意重定向' }, 'uw-joke4im': { label: '加入不當玩笑', summary: '層級4im:加入不當玩笑' }, 'uw-create4im': { label: '建立不當頁面', summary: '層級4im:建立不當頁面' }, 'uw-upload4im': { label: '上傳不當圖像', summary: '層級4im:上傳不當圖像' }, 'uw-image4im': { label: '加入不恰當的圖片', summary: '層級4im:加入不恰當的圖片' }, 'uw-politicalbias4im': { label: '違反兩岸用語方針', summary: '層級4im:違反兩岸用語方針' } }, '增加商品或政治廣告': { 'uw-spam4im': { label: '增加垃圾連結', summary: '層級4im:增加垃圾連結' } }, '加插不實及/或誹謗文字': { 'uw-biog4im': { label: '加入有關在生人物而又缺乏來源的資料', summary: '層級4im:加入有關在生人物而又缺乏來源的資料' }, 'uw-defamatory4im': { label: '加入誹謗內容', summary: '層級4im:加入誹謗內容' } }, '非能接受且違反方針或指引的單方面行為或操作': { 'uw-move4im': { label: '不恰當地移動頁面', summary: '層級4im:不恰當地移動頁面' } }, '對其他用戶和條目的態度': { 'uw-npa4im': { label: '針對特定用戶的人身攻擊', summary: '層級4im:針對特定用戶的人身攻擊' }, 'uw-tempabuse4im': { label: '不當使用警告或封禁模板', summary: '層級4im:不當使用警告或封禁模板' } } }, singlenotice: { 'uw-2redirect': { label: '在移動頁面後應該修復雙重重定向', summary: '單層級通知:在移動頁面後應該修復雙重重定向' }, 'uw-aiv': { label: '舉報的並不是破壞者,或者舉報破壞前未進行警告', summary: '單層級通知:不恰當地舉報破壞' }, 'uw-articlesig': { label: '在條目中簽名', summary: '單層級通知:在條目中簽名' }, 'uw-autobiography': { label: '建立自傳', summary: '單層級通知:建立自傳' }, 'uw-badcat': { label: '加入錯誤的頁面分類', summary: '單層級通知:加入錯誤的頁面分類' }, 'uw-bite': { label: '傷害新手', summary: '單層級通知:傷害新手' }, 'uw-booktitle': { label: '沒有使用書名號來標示書籍、電影、音樂專輯等', summary: '單層級通知:沒有使用書名號來標示書籍、電影、音樂專輯等' }, 'uw-c&pmove': { label: '剪貼移動', summary: '單層級通知:剪貼移動' }, 'uw-chinese': { label: '不是以中文進行溝通', summary: '單層級通知:不是以中文進行溝通' }, 'uw-coi': { label: '利益衝突', summary: '單層級通知:利益衝突' }, 'uw-copyright-friendly': { label: '初次加入侵犯版權的內容', summary: '單層級通知:初次加入侵犯版權的內容' }, 'uw-copyviorewrite': { label: '在侵權頁面直接重寫條目', summary: '單層級通知:在侵權頁面直接重寫條目' }, 'uw-crystal': { label: '加入臆測或未確認的訊息', summary: '單層級通知:加入臆測或未確認的訊息' }, 'uw-csd': { label: '快速刪除理由不當', summary: '單層級通知:快速刪除理由不當' }, 'uw-dab': { label: '消歧義頁格式錯誤', summary: '單層級通知:消歧義頁格式錯誤' }, 'uw-date': { label: '不必要地更換日期格式', summary: '單層級通知:不必要地更換日期格式' }, 'uw-editsummary': { label: '沒有使用編輯摘要', summary: '單層級通知:沒有使用編輯摘要' }, 'uw-hangon': { label: '沒有在討論頁說明暫緩快速刪除理由', summary: '單層級通知:沒有在討論頁說明暫緩快速刪除理由' }, 'uw-lang': { label: '不必要地將條目所有文字換成簡體或繁體中文', summary: '單層級通知:不必要地將條目所有文字換成簡體或繁體中文' }, 'uw-langmove': { label: '不必要地將條目標題換成簡體或繁體中文', summary: '單層級通知:不必要地將條目標題換成簡體或繁體中文' }, 'uw-linking': { label: '過度加入紅字連結或重複藍字連結', summary: '單層級通知:過度加入紅字連結或重複藍字連結' }, 'uw-minor': { label: '不適當地使用小修改選項', summary: '單層級通知:不適當地使用小修改選項' }, 'uw-notaiv': { label: '向「當前的破壞」中報告的是用戶紛爭而不是破壞', summary: '單層級通知:向「當前的破壞」中報告的是用戶紛爭而不是破壞' }, 'uw-notvote': { label: '我們以共識處事,而不僅僅是投票', summary: '單層級通知:我們以共識處事,而不僅僅是投票' }, 'uw-preview': { label: '請使用預覽按鈕來避免不必要的錯誤', summary: '單層級通知:請使用預覽按鈕來避免不必要的錯誤' }, 'uw-sandbox': { label: '移除沙盒的置頂模板{{sandbox}}', summary: '單層級通知:移除沙盒的置頂模板{{sandbox}}' }, 'uw-selfrevert': { label: '感謝您自行回退自己的測試,以後不要再這樣做了', summary: '單層級通知:回退個人的測試' }, 'uw-subst': { label: '謹記要替代模板(subst)', summary: '單層級通知:謹記要替代模板' }, 'uw-talkinarticle': { label: '在條目頁中留下意見', summary: '單層級通知:在條目頁中留下意見' }, 'uw-tilde': { label: '沒有在討論頁上簽名', summary: '單層級通知:沒有在討論頁上簽名' }, 'uw-translated': { label: '翻譯條目未標註原作者', summary: '單層級通知:翻譯條目未標註原作者' }, 'uw-uaa': { label: '向需要管理員注意的用戶名報告的用戶名稱並不違反方針', summary: '單層級通知:向需要管理員注意的用戶名報告的用戶名稱並不違反方針' }, 'uw-warn': { label: '警告破壞用戶', summary: '單層級通知:警告破壞用戶' }, 'uw-mosiw': { label: '不要使用跨語言連結', summary: '單層級通知:不要使用跨語言連結' }, 'uw-badtwinkle': { label: '不恰當地使用Twinkle警告別人', summary: '單層級通知:不恰當地使用Twinkle警告別人' } }, singlewarn: { 'uw-3rr': { label: '用戶潛在違反回退不過三原則的可能性', summary: '單層級警告:用戶潛在違反回退不過三原則的可能性' }, 'uw-attack': { label: '建立人身攻擊頁面', summary: '單層級警告:建立人身攻擊頁面', suppressArticleInSummary: true }, 'uw-bv': { label: '公然地破壞', summary: '單層級警告:公然地破壞' }, 'uw-canvass': { label: '不恰當地拉票', summary: '單層級警告:不恰當地拉票' }, 'uw-copyright': { label: '侵犯版權', summary: '單層級警告:侵犯版權' }, 'uw-copyright-link': { label: '連結到有版權的材料', summary: '單層級警告:連結到有版權的材料' }, 'uw-fakesource': { label: '虛構資料來源或引文', summary: '單層級警告:虛構資料來源或引文' }, 'uw-hoax': { label: '建立惡作劇', summary: '單層級警告:建立惡作劇' }, 'uw-incompletecite': { label: '列出的資料來源欠缺若干詳情而不易查找', summary: '單層級警告:列出的資料來源欠缺若干詳情而不易查找' }, 'uw-legal': { label: '訴諸法律威脅', summary: '單層級警告:訴諸法律威脅' }, 'uw-longterm': { label: '長期的破壞', summary: '單層級警告:長期的破壞' }, 'uw-multipleIPs': { label: '使用多個IP地址', summary: '單層級警告:使用多個IP地址' }, 'uw-npov-tvd': { label: '在劇集條目中加入奸角等非中立描述', summary: '單層級警告:在劇集條目中加入奸角等非中立描述' }, 'uw-pinfo': { label: '張貼他人隱私', summary: '單層級警告:張貼他人隱私' }, 'uw-upv': { label: '破壞他人用戶頁', summary: '單層級警告:破壞他人用戶頁' }, 'uw-selfinventedname': { label: '不適當地自創新名詞、新譯名', summary: '單層級警告:不適當地自創新名詞、新譯名' }, 'uw-substub': { label: '建立小小作品', summary: '單層級警告:建立小小作品' }, 'uw-username': { label: '使用不恰當的用戶名', summary: '單層級警告:使用不恰當的用戶名' }, 'uw-wrongsummary': { label: '在編輯摘要製造不適當的內容', summary: '單層級警告:在編輯摘要製造不適當的內容' } } }); Twinkle.warn.prev_article = null; Twinkle.warn.prev_reason = null; Twinkle.warn.callback.change_category = function twinklewarnCallbackChangeCategory(e) { var value = e.target.value; var sub_group = e.target.root.sub_group; sub_group.main_group = value; var old_subvalue = sub_group.value; var old_subvalue_re; if (old_subvalue) { old_subvalue = old_subvalue.replace(/\d*(im)?$/, ''); old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue) + '(\\d*(?:im)?)$'); } while (sub_group.hasChildNodes()) { sub_group.removeChild(sub_group.firstChild); } // worker function to create the combo box entries var createEntries = function(contents, container, wrapInOptgroup) { // due to an apparent iOS bug, we have to add an option-group to prevent truncation of text // (search WT:TW archives for "Problem selecting warnings on an iPhone") if (wrapInOptgroup && $.client.profile().platform === 'iphone') { var wrapperOptgroup = new Morebits.quickForm.element({ type: 'optgroup', label: '可用模板' }); wrapperOptgroup = wrapperOptgroup.render(); container.appendChild(wrapperOptgroup); container = wrapperOptgroup; } $.each(contents, function(itemKey, itemProperties) { var key = typeof itemKey === 'string' ? itemKey : itemProperties.value; var selected = false; if (old_subvalue && old_subvalue_re.test(key)) { selected = true; } var elem = new Morebits.quickForm.element({ type: 'option', label: '{{' + key + '}}: ' + itemProperties.label, value: key, selected: selected }); var elemRendered = container.appendChild(elem.render()); $(elemRendered).data('messageData', itemProperties); }); }; if (value === 'singlenotice' || value === 'singlewarn') { // no categories, just create the options right away createEntries(Twinkle.warn.messages[value], sub_group, true); } else if (value === 'custom') { createEntries(Twinkle.getPref('customWarningList'), sub_group, true); } else { // create the option-groups $.each(Twinkle.warn.messages[value], function(groupLabel, groupContents) { var optgroup = new Morebits.quickForm.element({ type: 'optgroup', label: groupLabel }); optgroup = optgroup.render(); sub_group.appendChild(optgroup); // create the options createEntries(groupContents, optgroup, false); }); } // clear overridden label on article textbox Morebits.quickForm.setElementTooltipVisibility(e.target.root.article, true); Morebits.quickForm.resetElementLabel(e.target.root.article); // hide the big red notice $('#tw-warn-red-notice').remove(); }; Twinkle.warn.callback.change_subcategory = function twinklewarnCallbackChangeSubcategory(e) { var main_group = e.target.form.main_group.value; var value = e.target.form.sub_group.value; // Tags that don't take a linked article, but something else (often a username). // The value of each tag is the label next to the input field var notLinkedArticle = { 'uw-agf-sock': 'Optional username of other account (without User:) ', 'uw-bite': "Username of 'bitten' user (without User:) ", 'uw-socksuspect': 'Username of sock master, if known (without User:) ', 'uw-username': 'Username violates policy because... ' }; if (main_group === 'singlenotice' || main_group === 'singlewarn') { if (notLinkedArticle[value]) { if (Twinkle.warn.prev_article === null) { Twinkle.warn.prev_article = e.target.form.article.value; } e.target.form.article.notArticle = true; e.target.form.article.value = ''; // change form labels according to the warning selected Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false); Morebits.quickForm.overrideElementLabel(e.target.form.article, notLinkedArticle[value]); } else if (e.target.form.article.notArticle) { if (Twinkle.warn.prev_article !== null) { e.target.form.article.value = Twinkle.warn.prev_article; Twinkle.warn.prev_article = null; } e.target.form.article.notArticle = false; Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, true); Morebits.quickForm.resetElementLabel(e.target.form.article); } } // change form labels according to the warning selected if (value === 'uw-socksuspect') { Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false); Morebits.quickForm.overrideElementLabel(e.target.form.article, wgULS('傀儡操纵者用户名,如果知道的话(不含User:) ', '傀儡操縱者用戶名,如果知道的話(不含User:) ')); } else if (value === 'uw-username') { Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false); Morebits.quickForm.overrideElementLabel(e.target.form.article, wgULS('用户名违反方针,因为… ', '用戶名違反方針,因為… ')); } else if (value === 'uw-bite') { Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false); Morebits.quickForm.overrideElementLabel(e.target.form.article, wgULS('被“咬到”的用户(不含User:) ', '被「咬到」的用戶(不含User:) ')); } else { Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, true); Morebits.quickForm.resetElementLabel(e.target.form.article); } // add big red notice, warning users about how to use {{uw-[coi-]username}} appropriately $('#tw-warn-red-notice').remove(); var $redWarning; if (value === 'uw-username') { $redWarning = $(wgULS("<div style='color: red;' id='tw-warn-red-notice'>{{uw-username}}<b>不应</b>被用于<b>明显</b>违反用户名方针的用户。" + '明显的违反方针应被报告给UAA。' + '{{uw-username}}应只被用在边界情况下需要与用户讨论时。</div>', "<div style='color: red;' id='tw-warn-red-notice'>{{uw-username}}<b>不應</b>被用於<b>明顯</b>違反用戶名方針的用戶。" + '明顯的違反方針應被報告給UAA。' + '{{uw-username}}應只被用在邊界情況下需要與用戶討論時。</div>')); $redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup)); } }; Twinkle.warn.callbacks = { getWarningWikitext: function(templateName, article, reason, isCustom) { // eslint-disable-line no-unused-vars var text = '{{subst:' + templateName; if (article) { // add linked article for user warnings text += '|1=' + article; } if (reason) { text += '|2=' + reason; } text += '|subst=subst:}}'; return text; }, preview: function(form) { var templatename = form.sub_group.value; var linkedarticle = form.article.value; var templatetext; templatetext = Twinkle.warn.callbacks.getWarningWikitext(templatename, linkedarticle, form.reason.value, form.main_group.value === 'custom'); form.previewer.beginRender(templatetext); }, main: function(pageobj) { var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); var messageData = params.messageData; var history_re = /<!-- Template:(uw-.*?) -->.*?(\d{4})年(\d{1,2})月(\d{1,2})日 \([日一二三四五六]\) (\d{1,2}):(\d{1,2}) \(UTC\)/g; var history = {}; var latest = { date: new Date(0), type: '' }; var current; while ((current = history_re.exec(text)) !== null) { var current_date = new Date(current[2] + '-' + current[3] + '-' + current[4] + ' ' + current[5] + ':' + current[6] + ' UTC'); if (!(current[1] in history) || history[current[1]] < current_date) { history[current[1]] = current_date; } if (current_date > latest.date) { latest.date = current_date; latest.type = current[1]; } } var date = new Date(pageobj.getLoadTime()); if (params.sub_group in history) { var temp_time = new Date(history[params.sub_group]); temp_time.setUTCHours(temp_time.getUTCHours() + 24); if (temp_time > date) { if (!confirm(wgULS('近24小时内一个同样的 ' + params.sub_group + ' 模板已被发出。\n是否继续?', '近24小時內一個同樣的 ' + params.sub_group + ' 模板已被發出。\n是否繼續?'))) { pageobj.statelem.info('用户取消'); return; } } } latest.date.setUTCMinutes(latest.date.getUTCMinutes() + 1); // after long debate, one minute is max if (latest.date > date) { if (!confirm(wgULS('近1分锺内一个同样的 ' + latest.type + ' 模板已被发出。\n是否继续?', '近1分鍾內一個同樣的 ' + latest.type + ' 模板已被發出。\n是否繼續?'))) { pageobj.statelem.info('用户取消'); return; } } var dateHeaderRegex = new RegExp('^==+\\s*' + date.getUTCFullYear() + '年' + (date.getUTCMonth() + 1) + '月' + '\\s*==+', 'mg'); var dateHeaderRegexLast, dateHeaderRegexResult; while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) { dateHeaderRegexResult = dateHeaderRegexLast; } // If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but // \n== is not found, then the date header must be at the very start of the page. lastIndexOf // returns -1 in this case, so lastHeaderIndex gets set to 0 as desired. var lastHeaderIndex = text.lastIndexOf('\n==') + 1; if (text.length > 0) { text += '\n\n'; } if (messageData.heading) { text += '== ' + messageData.heading + ' ==\n'; } else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) { Morebits.status.info('信息', wgULS('未找到当月标题,将创建新的', '未找到當月標題,將建立新的')); text += '== ' + date.getUTCFullYear() + '年' + (date.getUTCMonth() + 1) + '月 ' + ' ==\n'; } text += Twinkle.warn.callbacks.getWarningWikitext(params.sub_group, params.article, params.reason, params.main_group === 'custom') + '--~~~~'; if (Twinkle.getPref('showSharedIPNotice') && mw.util.isIPAddress(mw.config.get('wgTitle'))) { Morebits.status.info('信息', wgULS('加入共享IP说明', '加入共享IP說明')); text += '\n{{subst:SharedIPAdvice}}'; } // build the edit summary var summary; if (params.main_group === 'custom') { switch (params.sub_group.substr(-1)) { case '1': summary = '提醒'; break; case '2': summary = '注意'; break; case '3': summary = '警告'; break; case '4': summary = wgULS('最后警告', '最後警告'); break; case 'm': if (params.sub_group.substr(-3) === '4im') { summary = '唯一警告'; break; } summary = '提示'; break; default: summary = '提示'; break; } summary += ':' + Morebits.string.toUpperCaseFirstChar(messageData.label); } else { summary = messageData.summary; if (messageData.suppressArticleInSummary !== true && params.article) { if (params.sub_group === 'uw-socksuspect') { // this template requires a username summary += ',[[User:' + params.article + ']]的'; } else if (params.sub_group === 'uw-bite') { // this template requires a username summary += ',' + wgULS('于', '於') + '[[User talk:' + params.article + ']]'; } else { summary += wgULS(',于[[', ',於[[') + params.article + ']]'; } } } pageobj.setPageText(text); pageobj.setEditSummary(summary); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setWatchlist(Twinkle.getPref('watchWarnings')); pageobj.save(); } }; Twinkle.warn.callback.evaluate = function twinklewarnCallbackEvaluate(e) { var userTalkPage = 'User_talk:' + Morebits.relevantUserName(); // First, check to make sure a reason was filled in if uw-username was selected if (e.target.sub_group.value === 'uw-username' && e.target.article.value.trim() === '') { alert(wgULS('必须给{{uw-username}}提供理由。', '必須給{{uw-username}}提供理由。')); return; } // Find the selected <option> element so we can fetch the data structure var selectedEl = $(e.target.sub_group).find('option[value="' + $(e.target.sub_group).val() + '"]'); // Then, grab all the values provided by the form var params = { reason: e.target.reason.value, main_group: e.target.main_group.value, sub_group: e.target.sub_group.value, article: e.target.article.value, // .replace( /^(Image|Category):/i, ':$1:' ), -- apparently no longer needed... messageData: selectedEl.data('messageData') }; Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); Morebits.wiki.actionCompleted.redirect = userTalkPage; Morebits.wiki.actionCompleted.notice = wgULS('警告完成,将在几秒后刷新', '警告完成,將在幾秒後重新整理'); var wikipedia_page = new Morebits.wiki.page(userTalkPage, wgULS('用户对话页修改', '用戶對話頁修改')); wikipedia_page.setCallbackParameters(params); wikipedia_page.setFollowRedirect(true); wikipedia_page.load(Twinkle.warn.callbacks.main); }; })(jQuery); // </nowiki> o62gdv78qygrysm26gafgw2pesg42h9 MediaWiki:Gadget-friendlytag.js 8 1368862 9943051 6328892 2026-10-12T15:41:50Z Xiplus 25402 Repo at 375b1e1: remove px from form.size 9943051 javascript text/javascript // <nowiki> (function($) { /* **************************************** *** friendlytag.js: Tag module **************************************** * Mode of invocation: Tab ("Tag") * Active on: Existing articles and drafts; file pages with a corresponding file * which is local (not on Commons); all redirects */ Twinkle.tag = function friendlytag() { // redirect tagging if (Morebits.wiki.isPageRedirect()) { // Twinkle.tag.modeOriginal = 'redirect'; // Twinkle.tag.mode = '重定向'; // Twinkle.addPortletLink(Twinkle.tag.callback, wgULS('标记', '標記'), 'friendly-tag', wgULS('标记重定向', '標記重定向')); // file tagging } else if (mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById('mw-sharedupload') && document.getElementById('mw-imagepage-section-filehistory')) { // Twinkle.tag.modeOriginal = 'file'; // Twinkle.tag.mode = wgULS('文件', '檔案'); // Twinkle.addPortletLink(Twinkle.tag.callback, wgULS('标记', '標記'), 'friendly-tag', wgULS('标记文件', '標記檔案')); // article/draft tagging } else if ((mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgCurRevisionId')) || (Morebits.pageNameNorm === Twinkle.getPref('sandboxPage'))) { Twinkle.tag.modeOriginal = 'article'; Twinkle.tag.mode = wgULS('词条', '詞條'); // Can't remove tags when not viewing current version Twinkle.tag.canRemove = (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) && // Disabled on latest diff because the diff slider could be used to slide // away from the latest diff without causing the script to reload !mw.config.get('wgDiffNewId'); Twinkle.addPortletLink(Twinkle.tag.callback, wgULS('标记', '標記'), 'friendly-tag', wgULS('标记词条', '標記詞條')); } }; Twinkle.tag.checkedTags = []; Twinkle.tag.callback = function friendlytagCallback() { var Window = new Morebits.simpleWindow(630, Twinkle.tag.mode === '词条' || Twinkle.tag.mode === '詞條' ? 500 : 400); Window.setScriptName('Twinkle'); // anyone got a good policy/guideline/info page/instructional page link?? Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'WP:TW/DOC#tag'); var form = new Morebits.quickForm(Twinkle.tag.callback.evaluate); if (document.getElementsByClassName('patrollink').length) { form.append({ type: 'checkbox', list: [ { label: wgULS('标记页面为已巡查', '標記頁面為已巡查'), value: 'patrolPage', name: 'patrolPage', checked: Twinkle.getPref('markTaggedPagesAsPatrolled') } ] }); } switch (Twinkle.tag.mode) { case '詞條': case '词条': Window.setTitle(wgULS('词条维护标记', '詞條維護標記')); form.append({ type: 'select', name: 'sortorder', label: wgULS('查看列表:', '檢視列表:'), tooltip: wgULS('您可以在Twinkle参数设置(WP:TWPREFS)中更改此项。', '您可以在Twinkle偏好設定(WP:TWPREFS)中更改此項。'), event: Twinkle.tag.updateSortOrder, list: [ { type: 'option', value: 'cat', label: wgULS('按类型', '按類別'), selected: Twinkle.getPref('tagArticleSortOrder') === 'cat' }, { type: 'option', value: 'alpha', label: '按字母', selected: Twinkle.getPref('tagArticleSortOrder') === 'alpha' } ] }); Twinkle.tag.quickFilter(form); if (!Twinkle.tag.canRemove) { var divElement = document.createElement('div'); divElement.innerHTML = wgULS('要移除现有维护标记,请从当前词条版本中打开“标记”菜单', '要移除現有維護標記,請從目前詞條版本中打開「標記」選單'); form.append({ type: 'div', name: 'untagnotice', label: divElement }); } form.append({ type: 'div', id: 'tagWorkArea', className: 'morebits-scrollbox', style: 'max-height: 22em' }); form.append({ type: 'input', label: '理由:', name: 'reason', tooltip: wgULS('附加于编辑摘要的可选理由,例如指出条目内容的哪些部分有问题或移除模板的理由,但如果理由很长则应该发表在讨论页。', '附加於編輯摘要的可選理由,例如指出條目內容的哪些部分有問題或移除模板的理由,但如果理由很長則應該發表在討論頁。'), size: '80' }); break; case '重定向': Window.setTitle(wgULS('重定向标记', '重定向標記')); Twinkle.tag.quickFilter(form); form.append({ type: 'header', label: '常用模板' }); form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.frequentList }); form.append({ type: 'header', label: '偶用模板' }); form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.lessFrequentList }); form.append({ type: 'header', label: wgULS('鲜用模板', '鮮用模板') }); form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.rareList }); break; case '文件': case '檔案': Window.setTitle(wgULS('文件维护标记', '檔案維護標記')); Twinkle.tag.quickFilter(form); // TODO: perhaps add custom tags TO list of checkboxes form.append({ type: 'header', label: wgULS('著作权和来源问题标签', '版權和來源問題標籤') }); form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.licenseList }); form.append({ type: 'header', label: wgULS('维基共享资源相关标签', '維基共享資源相關標籤') }); form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.commonsList }); form.append({ type: 'header', label: wgULS('清理标签', '清理標籤') }); form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.cleanupList }); form.append({ type: 'header', label: wgULS('文件取代标签', '檔案取代標籤') }); form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.replacementList }); break; default: alert('Twinkle.tag:未知模式 ' + Twinkle.tag.mode); break; } form.append({ type: 'submit', className: 'tw-tag-submit' }); var result = form.render(); Window.setContent(result); Window.display(); // for quick filter: $allCheckboxDivs = $(result).find('[name$=Tags]').parent(); $allHeaders = $(result).find('h5'); result.quickfilter.focus(); // place cursor in the quick filter field as soon as window is opened result.quickfilter.autocomplete = 'off'; // disable browser suggestions result.quickfilter.addEventListener('keypress', function(e) { if (e.keyCode === 13) { // prevent enter key from accidentally submitting the form e.preventDefault(); return false; } }); if (Twinkle.tag.mode === '词条' || Twinkle.tag.mode === '詞條') { Twinkle.tag.alreadyPresentTags = []; if (Twinkle.tag.canRemove) { // Look for existing maintenance tags in the lead section and put them in array // All tags are HTML table elements that are direct children of .mw-parser-output, // except when they are within {{multiple issues}} $('.mw-parser-output').children().each(function parsehtml(i, e) { // break out on encountering the first heading, which means we are no // longer in the lead section if (e.tagName === 'H2') { return false; } // The ability to remove tags depends on the template's {{ambox}} |name= // parameter bearing the template's correct name (preferably) or a name that at // least redirects to the actual name // All tags have their first class name as "box-" + template name if (e.className.indexOf('box-') === 0) { if (e.classList[0] === 'box-问题词条') { $(e).find('.ambox').each(function(idx, e) { var tag = e.classList[0].slice(4).replace(/_/g, ' '); Twinkle.tag.alreadyPresentTags.push(tag); }); return true; // continue } var tag = e.classList[0].slice(4).replace(/_/g, ' '); Twinkle.tag.alreadyPresentTags.push(tag); } }); // {{Uncategorized}} and {{Improve categories}} are usually placed at the end if ($('.box-Uncategorized').length) { Twinkle.tag.alreadyPresentTags.push('Uncategorized'); } if ($('.box-Improve_categories').length) { Twinkle.tag.alreadyPresentTags.push('Improve categories'); } } // Add status text node after Submit button var statusNode = document.createElement('small'); statusNode.id = 'tw-tag-status'; Twinkle.tag.status = { // initial state; defined like this because these need to be available for reference // in the click event handler numAdded: 0, numRemoved: 0 }; $('button.tw-tag-submit').after(statusNode); // fake a change event on the sort dropdown, to initialize the tag list var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.sortorder.dispatchEvent(evt); } else { // Redirects and files: Add a link to each template's description page Morebits.quickForm.getElements(result, Twinkle.tag.mode + 'Tags').forEach(generateLinks); } }; // $allCheckboxDivs and $allHeaders are defined globally, rather than in // the event function, to avoid having to recompute them on every keydown. var $allCheckboxDivs, $allHeaders; Twinkle.tag.quickFilter = function(form) { form.append({ type: 'input', label: wgULS('快速筛选:', '快速篩選:'), name: 'quickfilter', size: '30', event: function twinkletagquickfilter() { // flush the DOM of all existing underline spans $allCheckboxDivs.find('.search-hit').each(function(i, e) { var label_element = e.parentElement; // This would convert <label>Hello <span class=search-hit>wo</span>rld</label> // to <label>Hello world</label> label_element.innerHTML = label_element.textContent; }); if (this.value) { $allCheckboxDivs.hide(); $allHeaders.hide(); var searchString = this.value; var searchRegex = new RegExp(mw.util.escapeRegExp(searchString), 'i'); $allCheckboxDivs.find('label').each(function () { var label_text = this.textContent; var searchHit = searchRegex.exec(label_text); if (searchHit) { var range = document.createRange(); var textnode = this.childNodes[0]; range.selectNodeContents(textnode); range.setStart(textnode, searchHit.index); range.setEnd(textnode, searchHit.index + searchString.length); var underline_span = $('<span>').addClass('search-hit').css('text-decoration', 'underline')[0]; range.surroundContents(underline_span); this.parentElement.style.display = 'block'; // show } }); } else { $allCheckboxDivs.show(); $allHeaders.show(); } } }); }; Twinkle.tag.updateSortOrder = function(e) { var form = e.target.form; var sortorder = e.target.value; Twinkle.tag.checkedTags = form.getChecked('articleTags') || []; var container = new Morebits.quickForm.element({ type: 'fragment' }); // function to generate a checkbox, with appropriate subgroup if needed var makeCheckbox = function(tag, description) { var checkbox = { value: tag, label: '{{' + tag + '}}: ' + description }; if (Twinkle.tag.checkedTags.indexOf(tag) !== -1) { checkbox.checked = true; } switch (tag) { case 'History merge': checkbox.subgroup = [ { name: 'histmergeOriginalPage', type: 'input', label: wgULS('来源页面名称(必填):', '來源頁面名稱(必填):') }, { name: 'histmergeReason', type: 'input', label: wgULS('合并理由:', '合併理由:') }, { name: 'histmergeSysopDetails', type: 'input', label: wgULS('附加消息:', '附加訊息:') } ]; break; default: break; } return checkbox; }; var makeCheckboxesForAlreadyPresentTags = function() { container.append({ type: 'header', id: 'tagHeader0', label: wgULS('已放置的维护标记', '已放置的維護標記') }); var subdiv = container.append({ type: 'div', id: 'tagSubdiv0' }); var checkboxes = []; var unCheckedTags = e.target.form.getUnchecked('alreadyPresentArticleTags') || []; Twinkle.tag.alreadyPresentTags.forEach(function(tag) { var description = Twinkle.tag.article.tags[tag]; var checkbox = { value: tag, label: '{{' + tag + '}}' + (description ? ': ' + description : ''), checked: unCheckedTags.indexOf(tag) === -1 }; checkboxes.push(checkbox); }); subdiv.append({ type: 'checkbox', name: 'alreadyPresentArticleTags', list: checkboxes }); }; // categorical sort order if (sortorder === 'cat') { // function to iterate through the tags and create a checkbox for each one var doCategoryCheckboxes = function(subdiv, array) { var checkboxes = []; $.each(array, function(k, tag) { var description = Twinkle.tag.article.tags[tag]; if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) { checkboxes.push(makeCheckbox(tag, description)); } }); subdiv.append({ type: 'checkbox', name: 'articleTags', list: checkboxes }); }; if (Twinkle.tag.alreadyPresentTags.length > 0) { makeCheckboxesForAlreadyPresentTags(); } var i = 1; // go through each category and sub-category and append lists of checkboxes $.each(Twinkle.tag.article.tagCategories, function(title, content) { container.append({ type: 'header', id: 'tagHeader' + i, label: title }); var subdiv = container.append({ type: 'div', id: 'tagSubdiv' + i++ }); if ($.isArray(content)) { doCategoryCheckboxes(subdiv, content); } else { $.each(content, function(subtitle, subcontent) { subdiv.append({ type: 'div', label: [ Morebits.htmlNode('b', subtitle) ] }); doCategoryCheckboxes(subdiv, subcontent); }); } }); } else { // alphabetical sort order if (Twinkle.tag.alreadyPresentTags.length > 0) { makeCheckboxesForAlreadyPresentTags(); container.append({ type: 'header', id: 'tagHeader1', label: wgULS('可用的维护标记', '可用的維護標記') }); } var checkboxes = []; $.each(Twinkle.tag.article.tags, function(tag, description) { if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) { checkboxes.push(makeCheckbox(tag, description)); } }); container.append({ type: 'checkbox', name: 'articleTags', list: checkboxes }); } // append any custom tags if (Twinkle.getPref('customTagList').length) { container.append({ type: 'header', label: wgULS('自定义模板', '自訂模板') }); var customcheckboxes = []; $.each(Twinkle.getPref('customTagList'), function(_, item) { customcheckboxes.push(makeCheckbox(item.value, item.label)); }); container.append({ type: 'checkbox', name: 'articleTags', list: customcheckboxes }); } var $workarea = $(form).find('#tagWorkArea'); var rendered = container.render(); $workarea.empty().append(rendered); // for quick filter: $allCheckboxDivs = $workarea.find('[name$=Tags]').parent(); $allHeaders = $workarea.find('h5, .quickformDescription'); form.quickfilter.value = ''; // clear search, because the search results are not preserved over mode change form.quickfilter.focus(); // style adjustments $workarea.find('h5').css({ 'font-size': '110%' }); $workarea.find('h5:not(:first-child)').css({ 'margin-top': '1em' }); $workarea.find('div').filter(':has(span.quickformDescription)').css({ 'margin-top': '0.4em' }); var alreadyPresentTags = Morebits.quickForm.getElements(form, 'alreadyPresentArticleTags'); if (alreadyPresentTags) { alreadyPresentTags.forEach(generateLinks); } // in the unlikely case that *every* tag is already on the page var notPresentTags = Morebits.quickForm.getElements(form, 'articleTags'); if (notPresentTags) { notPresentTags.forEach(generateLinks); } // tally tags added/removed, update statusNode text var statusNode = document.getElementById('tw-tag-status'); $('[name=articleTags], [name=alreadyPresentArticleTags]').click(function() { if (this.name === 'articleTags') { Twinkle.tag.status.numAdded += this.checked ? 1 : -1; } else if (this.name === 'alreadyPresentArticleTags') { Twinkle.tag.status.numRemoved += this.checked ? -1 : 1; } var firstPart = '加入' + Twinkle.tag.status.numAdded + wgULS('个标记', '個標記'); var secondPart = '移除' + Twinkle.tag.status.numRemoved + wgULS('个标记', '個標記'); statusNode.textContent = (Twinkle.tag.status.numAdded ? ' ' + firstPart : '') + (Twinkle.tag.status.numRemoved ? (Twinkle.tag.status.numAdded ? ';' : ' ') + secondPart : ''); }); }; /** * Adds a link to each template's description page * @param {Morebits.quickForm.element} checkbox associated with the template */ var generateLinks = function(checkbox) { var link = Morebits.htmlNode('a', '>'); link.setAttribute('class', 'tag-template-link'); var tagname = checkbox.values; link.setAttribute('href', mw.util.getUrl( (tagname.indexOf(':') === -1 ? 'Template:' : '') + (tagname.indexOf('|') === -1 ? tagname : tagname.slice(0, tagname.indexOf('|'))) )); link.setAttribute('target', '_blank'); $(checkbox).parent().append(['\u00A0', link]); }; // Tags for ARTICLES start here Twinkle.tag.article = {}; // A list of all article tags, in alphabetical order // To ensure tags appear in the default "categorized" view, add them to the tagCategories hash below. Twinkle.tag.article.tags = wgULS({ 'History merge': '建议将其他页面的历史并入本页' }, { 'History merge': '建議將其他頁面的歷史併入本頁' }); // A list of tags in order of category // Tags should be in alphabetical order within the categories // Add new categories with discretion - the list is long enough as is! Twinkle.tag.article.tagCategories = wgULS({ '合并': [ // these three have a subgroup with several options 'History merge' ] }, { '合併': [ // these three have a subgroup with several options 'History merge' ] }); // Tags for REDIRECTS start here Twinkle.tag.frequentList = wgULS([ ], [ ]); Twinkle.tag.lessFrequentList = wgULS([ ], [ ]); Twinkle.tag.rareList = wgULS([ ], [ ]); // maintenance tags for FILES start here Twinkle.tag.file = {}; Twinkle.tag.file.licenseList = wgULS([ ], [ ]); Twinkle.tag.file.commonsList = wgULS([ ], [ ]); Twinkle.tag.file.cleanupList = wgULS([ ], [ ]); Twinkle.tag.file.replacementList = wgULS([ ], [ ]); Twinkle.tag.callbacks = { main: function(pageobj) { // Remove tags that become superfluous with this action var pageText = pageobj.getPageText().replace(/\{\{\s*([Nn]ew unreviewed article|[Uu]nreviewed|[Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, ''); var params = pageobj.getCallbackParameters(); /** * Saves the page following the removal of tags if any. The last step. * Called from removeTags() */ var postRemoval = function() { // Build edit summary var makeSentence = function(array) { if (array.length < 3) { return array.join('和'); } var last = array.pop(); return array.join('、') + '和' + last; }; var makeTemplateLink = function(tag) { var text = '{{[['; // if it is a custom tag with a parameter if (tag.indexOf('|') !== -1) { tag = tag.slice(0, tag.indexOf('|')); } text += tag.indexOf(':') !== -1 ? tag : 'Template:' + tag + '|' + tag; return text + ']]}}'; }; var summaryText; var addedTags = params.tags.map(makeTemplateLink); var removedTags = params.tagsToRemove.map(makeTemplateLink); if (addedTags.length) { summaryText = '加入' + makeSentence(addedTags); summaryText += removedTags.length ? '並移除' + makeSentence(removedTags) : ''; } else { summaryText = '移除' + makeSentence(removedTags); } summaryText += wgULS('标记', '標記'); if (params.reason) { summaryText += ':' + params.reason; } // avoid truncated summaries if (summaryText.length > 499) { summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1'); } pageobj.setPageText(pageText); pageobj.setEditSummary(summaryText); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages')); pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor')); pageobj.setCreateOption('nocreate'); pageobj.save(function() { // special functions for merge tags if (params.mergeReason) { // post the rationale on the talk page (only operates in main namespace) var talkpageText = '\n\n== 请求与[[' + params.nonDiscussArticle + ']]合并 ==\n\n'; talkpageText += params.mergeReason.trim() + '--~~~~'; var talkpage = new Morebits.wiki.page('Talk:' + params.discussArticle, wgULS('将理由贴进讨论页', '將理由貼進討論頁')); talkpage.setAppendText(talkpageText); talkpage.setEditSummary(wgULS('请求将[[' + params.nonDiscussArticle + ']]' + '与' + '[[' + params.discussArticle + ']]合并', '請求將[[' + params.nonDiscussArticle + ']]' + '與' + '[[' + params.discussArticle + ']]合併')); talkpage.setChangeTags(Twinkle.changeTags); talkpage.setWatchlist(Twinkle.getPref('watchMergeDiscussions')); talkpage.setCreateOption('recreate'); talkpage.append(); } if (params.mergeTagOther) { // tag the target page if requested var otherTagName = 'Merge'; if (tags.indexOf('Merge from') !== -1) { otherTagName = 'Merge to'; } else if (tags.indexOf('Merge to') !== -1) { otherTagName = 'Merge from'; } var newParams = { tags: [otherTagName], tagsToRemove: [], tagsToRemain: [], mergeTarget: Morebits.pageNameNorm, discussArticle: params.discussArticle, talkDiscussionTitle: params.talkDiscussionTitle }; var otherpage = new Morebits.wiki.page(params.mergeTarget, wgULS('标记其他页面(', '標記其他頁面(') + params.mergeTarget + ')'); otherpage.setCallbackParameters(newParams); otherpage.load(Twinkle.tag.callbacks.main); } // special functions for requested move tags if (params.moveReason) { // post the rationale on the talk page (only operates in main namespace) var moveTalkpageText = '\n\n{{subst:RM|' + params.moveReason.trim(); if (params.moveTarget) { moveTalkpageText += '|' + params.moveTarget; } moveTalkpageText += '}}'; var moveTalkpage = new Morebits.wiki.page('Talk:' + params.discussArticle, wgULS('将理由贴进讨论页', '將理由貼進討論頁')); moveTalkpage.setAppendText(moveTalkpageText); moveTalkpage.setEditSummary(wgULS('请求移动' + (params.moveTarget ? '至[[' + params.moveTarget + ']]' : ''), '請求移動' + (params.moveTarget ? '至[[' + params.moveTarget + ']]' : ''))); moveTalkpage.setChangeTags(Twinkle.changeTags); moveTalkpage.setCreateOption('recreate'); moveTalkpage.append(); } }); if (params.patrol) { pageobj.patrol(); } }; /** * Removes the existing tags that were deselected (if any) * Calls postRemoval() when done */ var removeTags = function removeTags() { if (params.tagsToRemove.length === 0) { postRemoval(); return; } Morebits.status.info(wgULS('信息', '資訊'), wgULS('移除取消选择的已存在标记', '移除取消選擇的已存在標記')); var getRedirectsFor = []; // Remove the tags from the page text, if found in its proper name, // otherwise moves it to `getRedirectsFor` array earmarking it for // later removal params.tagsToRemove.forEach(function removeTag(tag) { var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?'); if (tag_re.test(pageText)) { pageText = pageText.replace(tag_re, ''); } else { getRedirectsFor.push('Template:' + tag); } }); if (!getRedirectsFor.length) { postRemoval(); return; } // Remove tags which appear in page text as redirects var api = new Morebits.wiki.api(wgULS('获取模板重定向', '取得模板重定向'), { 'action': 'query', 'prop': 'linkshere', 'titles': getRedirectsFor.join('|'), 'redirects': 1, // follow redirect if the class name turns out to be a redirect page 'lhnamespace': '10', // template namespace only 'lhshow': 'redirect', 'lhlimit': 'max' }, function removeRedirectTag(apiobj) { $(apiobj.responseXML).find('page').each(function(idx, page) { var removed = false; $(page).find('lh').each(function(idx, el) { var tag = $(el).attr('title').slice(9); var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?'); if (tag_re.test(pageText)) { pageText = pageText.replace(tag_re, ''); removed = true; return false; // break out of $.each } }); if (!removed) { Morebits.status.warn(wgULS('信息', '資訊'), wgULS('無法在页面上找到{{' + $(page).attr('title').slice(9) + '}}…跳过', '無法在頁面上找到{{' + $(page).attr('title').slice(9) + '}}…跳過')); } }); postRemoval(); }); api.post(); }; if (!params.tags.length) { removeTags(); return; } var tagRe, tagText = '', tags = [], groupableTags = []; // Executes first: addition of selected tags /** * Updates `tagText` with the syntax of `tagName` template with its parameters * @param {number} tagIndex * @param {string} tagName */ var addTag = function articleAddTag(tagIndex, tagName) { var currentTag = ''; if (tagName === 'Uncategorized' || tagName === 'Improve categories') { pageText += '\n\n{{' + tagName + '|time={{subst:#time:c}}}}'; } else { currentTag += '{{' + tagName; // fill in other parameters, based on the tag switch (tagName) { case 'History merge': currentTag += '|1=' + params.histmergeOriginalPage; if (params.histmergeReason) { currentTag += '|reason=' + params.histmergeReason; } if (params.histmergeSysopDetails) { currentTag += '|details=' + params.histmergeSysopDetails; } break; default: break; } currentTag += '|time={{subst:#time:c}}}}\n'; tagText += currentTag; } }; /** * Adds the tags which go outside {{multiple issues}}, either because * these tags aren't supported in {{multiple issues}} or because * {{multiple issues}} is not being added to the page at all */ var addUngroupedTags = function() { $.each(tags, addTag); // Smartly insert the new tags after any hatnotes or // afd, csd, or prod templates or hatnotes. Regex is // extra complicated to allow for templates with // parameters and to handle whitespace properly. pageText = pageText.replace(/^\s*(?:((?:\s*\{\{\s*(?:about|correct title|dablink|distinguish|for|other\s?(?:hurricaneuses|people|persons|places|uses(?:of)?)|redirect(?:-acronym)?|see\s?(?:also|wiktionary)|selfref|the)\d*\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\})+(?:\s*\n)?)\s*)?/i, '$1' + tagText); removeTags(); }; // Separate tags into groupable ones (`groupableTags`) and non-groupable ones (`tags`) params.tags.forEach(function(tag) { tagRe = new RegExp('\\{\\{' + tag + '(\\||\\}\\})', 'im'); // regex check for preexistence of tag can be skipped if in canRemove mode if (Twinkle.tag.canRemove || !tagRe.exec(pageText)) { tags.push(tag); } else { if (tag === 'History merge') { tags.push(tag); } else { Morebits.status.warn(wgULS('信息', '資訊'), wgULS('在页面上找到{{' + tag + '}}…跳过', '在頁面上找到{{' + tag + '}}…跳過')); // don't do anything else with merge tags if (['Merge', 'Merge to'].indexOf(tag) !== -1) { params.mergeTarget = params.mergeReason = params.mergeTagOther = null; } } } }); tags = tags.concat(groupableTags); addUngroupedTags(); }, redirect: function redirect(pageobj) { var params = pageobj.getCallbackParameters(), pageText = pageobj.getPageText(), tagRe, tagText = '', summaryText = '加入', tags = [], i; for (i = 0; i < params.tags.length; i++) { tagRe = new RegExp('(\\{\\{' + params.tags[i] + '(\\||\\}\\}))', 'im'); if (!tagRe.exec(pageText)) { tags.push(params.tags[i]); } else { Morebits.status.warn(wgULS('信息', '資訊'), wgULS('在重定向上找到{{' + params.tags[i] + '}}…跳过', '在重定向上找到{{' + params.tags[i] + '}}…跳過')); } } var addTag = function redirectAddTag(tagIndex, tagName) { tagText += '\n{{' + tagName; if (tagName === 'R from alternative language') { if (params.altLangFrom) { tagText += '|from=' + params.altLangFrom; } if (params.altLangTo) { tagText += '|to=' + params.altLangTo; } } tagText += '}}'; if (tagIndex > 0) { if (tagIndex === (tags.length - 1)) { summaryText += '和'; } else if (tagIndex < (tags.length - 1)) { summaryText += '、'; } } summaryText += '{{[[:' + (tagName.indexOf(':') !== -1 ? tagName : 'Template:' + tagName + '|' + tagName) + ']]}}'; }; tags.sort(); $.each(tags, addTag); // Check for all Rcat shell redirects (from #433) if (pageText.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) { // Regex inspired by [[User:Kephir/gadgets/sagittarius.js]] ([[Special:PermaLink/831402893]]) var oldTags = pageText.match(/(\s*{{[A-Za-z ]+\|)((?:[^|{}]*|{{[^}]*}})+)(}})\s*/i); pageText = pageText.replace(oldTags[0], oldTags[1] + tagText + oldTags[2] + oldTags[3]); } else { // Fold any pre-existing Rcats into taglist and under Rcatshell var pageTags = pageText.match(/\n{{R(?:edirect)? .*?}}/img); var oldPageTags = ''; if (pageTags) { pageTags.forEach(function(pageTag) { var pageRe = new RegExp(pageTag, 'img'); pageText = pageText.replace(pageRe, ''); oldPageTags += pageTag; }); } pageText += '\n{{Redirect category shell|' + tagText + oldPageTags + '\n}}'; } summaryText += (tags.length > 0 ? wgULS('标记', '標記') : '') + '到重定向'; // avoid truncated summaries if (summaryText.length > 499) { summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1'); } pageobj.setPageText(pageText); pageobj.setEditSummary(summaryText); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages')); pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor')); pageobj.setCreateOption('nocreate'); pageobj.save(); if (params.patrol) { pageobj.patrol(); } }, file: function friendlytagCallbacksFile(pageobj) { var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); var summary = '加入'; // Add maintenance tags if (params.tags.length) { var tagtext = '', currentTag; $.each(params.tags, function(k, tag) { // when other commons-related tags are placed, remove "move to Commons" tag if (['Keep local', 'subst:ncd', 'Do not move to Commons_reason', 'Do not move to Commons', 'Now Commons'].indexOf(tag) !== -1) { text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, ''); } if (tag === 'Vector version available') { text = text.replace(/\{\{((convert to |convertto|should be |shouldbe|to)?svg|badpng|vectorize)[^}]*\}\}/gi, ''); } currentTag = '{{' + (tag === 'Do not move to Commons_reason' ? 'Do not move to Commons' : tag); var input; switch (tag) { case 'subst:ncd': /* falls through */ case 'Keep local': input = prompt('{{' + (tag === 'subst:ncd' ? 'Now Commons' : tag) + '}} ─ ' + wgULS('输入在共享资源的图像名称(如果不同于本地名称),不包括 File: 前缀。要跳过标记,请单击取消:', '輸入在共享資源的圖像名稱(如果不同於本地名稱),不包括 File: 字首。要跳過標記,請點擊取消:'), ''); if (input === null) { return true; // continue } else if (input !== '') { currentTag += '|1=' + input; } if (tag === 'Keep local') { input = prompt('{{Keep local}} ─ ' + wgULS('输入请求在本地保留文件副本的原因(可选):', '輸入請求在本地保留檔案副本的原因(可選):'), ''); if (input !== null && input !== '') { currentTag += '|reason=' + input; } } break; case 'Rename media': input = prompt('{{Rename media}} ─ ' + wgULS('输入图像的新名称(可选):', '輸入圖像的新名稱(可選):'), ''); if (input === null) { return true; // continue } else if (input !== '') { currentTag += '|1=' + input; } input = prompt('{{Rename media}} ─ ' + wgULS('输入重命名的原因(可选):', '輸入重命名的原因(可選):'), ''); if (input === null) { return true; // continue } else if (input !== '') { currentTag += '|2=' + input; } break; case 'Vector version available': /* falls through */ case 'Obsolete': input = prompt('{{' + tag + '}} ─ ' + wgULS('输入替换此文件的文件名称(必填)。 要跳过标记,请单击取消:', '輸入替換此檔案的檔案名稱(必填)。 要跳過標記,請點擊取消:'), ''); if (input === null) { return true; // continue } else if (input !== '') { currentTag += '|1=' + input; } break; case 'Do not move to Commons_reason': input = prompt('{{Do not move to Commons}} ─ ' + wgULS('输入不应该将该图像移动到维基共享资源的原因(必填)。 要跳过标记,请单击取消:', '輸入不應該將該圖像移動到維基共享資源的原因(必填)。 要跳過標記,請點擊取消:'), ''); if (input === null) { return true; // continue } else if (input !== '') { currentTag += '|reason=' + input; } break; case 'Copy to Commons': currentTag += '|human=' + mw.config.get('wgUserName'); break; default: break; // don't care } currentTag += '}}\n'; tagtext += currentTag; summary += '{{' + tag + '}}, '; return true; // continue }); if (!tagtext) { pageobj.getStatusElement().warn(wgULS('用户取消操作,没什么要做的', '使用者取消操作,沒什麼要做的')); return; } text = tagtext + text; } pageobj.setPageText(text); pageobj.setEditSummary(summary.substring(0, summary.length - 2)); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages')); pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor')); pageobj.setCreateOption('nocreate'); pageobj.save(); if (params.patrol) { pageobj.patrol(); } } }; Twinkle.tag.callback.evaluate = function friendlytagCallbackEvaluate(e) { var form = e.target; var params = {}; if (form.patrolPage) { params.patrol = form.patrolPage.checked; } // Save values of input fields into params object. This works as quickform input // fields within subgroups of elements with name 'articleTags' (say) have their // name attribute as 'articleTags.' + name of the subgroup element var name_prefix = Twinkle.tag.modeOriginal + 'Tags.'; $(form).find("[name^='" + name_prefix + "']:not(div)").each(function(idx, el) { // el are the HTMLInputElements, el.name gives the name attribute params[el.name.slice(name_prefix.length)] = el.type === 'checkbox' ? form[el.name].checked : form[el.name].value; }); switch (Twinkle.tag.mode) { case '詞條': case '词条': // Don't return null if there aren't any available tags params.tags = form.getChecked('articleTags') || []; params.tagsToRemove = form.getUnchecked('alreadyPresentArticleTags') || []; params.tagsToRemain = form.getChecked('alreadyPresentArticleTags') || []; params.reason = form.reason.value.trim(); if (params.tags.indexOf('History merge') !== -1 && params.histmergeOriginalPage.trim() === '') { alert(wgULS('您必须指定{{History merge}}的来源页面名称', '您必須指定{{History merge}}的來源頁面名稱')); return; } break; case '重定向': // Don't return null if there aren't any available tags params.tags = form.getChecked('redirectTags') || []; break; case '文件': case '檔案': // Don't return null if there aren't any available tags params.tags = form.getChecked('imageTags') || []; break; default: alert('Twinkle.tag:未知模式 ' + Twinkle.tag.mode); break; } // form validation if (params.tags.length === 0 && (['詞條', '词条'].indexOf(Twinkle.tag.mode) === -1 || params.tagsToRemove.length === 0)) { alert(wgULS('必须选择至少一个标记!', '必須選擇至少一個標記!')); return; } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); Morebits.wiki.actionCompleted.redirect = Morebits.pageNameNorm; Morebits.wiki.actionCompleted.notice = wgULS('标记完成,在几秒内刷新页面', '標記完成,在幾秒內重新整理頁面'); if (Twinkle.tag.mode === '重定向') { Morebits.wiki.actionCompleted.followRedirect = false; } var wikipedia_page = new Morebits.wiki.page(Morebits.pageNameNorm, wgULS('正在标记', '正在標記') + Twinkle.tag.mode); wikipedia_page.setCallbackParameters(params); switch (Twinkle.tag.mode) { case '詞條': case '词条': wikipedia_page.load(Twinkle.tag.callbacks.main); return; case '重定向': wikipedia_page.load(Twinkle.tag.callbacks.redirect); return; case '文件': case '檔案': wikipedia_page.load(Twinkle.tag.callbacks.file); return; default: alert('Twinkle.tag:未知模式 ' + Twinkle.tag.mode); break; } }; })(jQuery); // </nowiki> 40so7btm1bzmsyxae37o9i66c87momc MediaWiki:Gadget-friendlytalkback.js 8 1368863 9943052 6328893 2026-10-12T15:41:59Z Xiplus 25402 Repo at 375b1e1: Remove support for deprecated SD (Flow) page 9943052 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { /* **************************************** *** friendlytalkback.js: Talkback module **************************************** * Mode of invocation: Tab ("TB") * Active on: Any page with relevant user name (userspace, contribs, etc.) * Config directives in: FriendlyConfig */ Twinkle.talkback = function() { if (!Morebits.relevantUserName()) { return; } Twinkle.addPortletLink(Twinkle.talkback.callback, '通告', 'friendly-talkback', wgULS('回复通告', '回覆通告')); }; Twinkle.talkback.callback = function() { if (Morebits.relevantUserName() === mw.config.get('wgUserName') && !confirm(wgULS('您寂寞到了要自己回复自己的程度么?', '您寂寞到了要自己回覆自己的程度麼?'))) { return; } var Window = new Morebits.simpleWindow(600, 350); Window.setTitle(wgULS('回复通告', '回覆通告')); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('关于{{talkback}}', '關於{{talkback}}'), 'Template:Talkback'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'WP:TW/DOC#talkback'); var form = new Morebits.quickForm(callback_evaluate); form.append({ type: 'radio', name: 'tbtarget', list: [ { label: wgULS('回复:我的对话页', '回覆:我的對話頁'), value: 'mytalk', checked: 'true' }, { label: wgULS('回复:其他用户的对话页', '回覆:其他用戶的對話頁'), value: 'usertalk' }, { label: wgULS('其它页面', '其它頁面'), value: 'other' }, { label: wgULS('“有新邮件”', '「有新郵件」'), value: 'mail' } ], event: callback_change_target }); form.append({ type: 'field', label: '工作区', name: 'work_area' }); form.append({ type: 'submit' }); var result = form.render(); Window.setContent(result); Window.display(); // We must init the var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.tbtarget[0].dispatchEvent(evt); // Check whether the user has opted out from talkback // TODO: wgCategories is only set on action=view (bug 45033) var wgcat = mw.config.get('wgCategories'); if (wgcat.length && wgcat.indexOf('不希望收到回复通告的用户') === -1) { Twinkle.talkback.optout = false; } else { var query = { action: 'query', prop: 'extlinks', titles: mw.config.get('wgPageName'), elquery: 'userjs.invalid/noTalkback', ellimit: '1' }; var wpapi = new Morebits.wiki.api('抓取opt-out信息', query, Twinkle.talkback.callback.optoutStatus); wpapi.post(); } }; Twinkle.talkback.optout = null; Twinkle.talkback.callback.optoutStatus = function(apiobj) { var xml = apiobj.getXML(); var $el = $(xml).find('el'); if ($el.length) { Twinkle.talkback.optout = Morebits.relevantUserName() + wgULS('不希望收到回复通告', '不希望收到回覆通告'); var url = $el.text(); if (url.indexOf('reason=') > -1) { Twinkle.talkback.optout += ':' + decodeURIComponent(url.substring(url.indexOf('reason=') + 7)) + '。'; } else { Twinkle.talkback.optout += '。'; } } else { Twinkle.talkback.optout = false; } var $status = $('#twinkle-talkback-optout-message'); if ($status.length) { $status.append(Twinkle.talkback.optout); } }; var prev_page = ''; var prev_section = ''; var prev_message = ''; var callback_change_target = function(e) { var value = e.target.values; var root = e.target.form; var old_area = Morebits.quickForm.getElements(root, 'work_area')[0]; if (root.section) { prev_section = root.section.value; } if (root.message) { prev_message = root.message.value; } if (root.page) { prev_page = root.page.value; } var work_area = new Morebits.quickForm.element({ type: 'field', label: wgULS('回复通告消息', '回覆通告訊息'), name: 'work_area' }); switch (value) { case 'mytalk': /* falls through */ default: work_area.append({ type: 'div', label: '', style: 'color: red', id: 'twinkle-talkback-optout-message' }); work_area.append({ type: 'input', name: 'section', label: wgULS('小节(可选)', '小節(可選)'), tooltip: wgULS('您留下消息的小节标题。', '您留下消息的小節標題。'), value: prev_section }); break; case 'usertalk': work_area.append({ type: 'div', label: '', style: 'color: red', id: 'twinkle-talkback-optout-message' }); work_area.append({ type: 'input', name: 'page', label: '用户', tooltip: wgULS('您留下消息的用户名。', '您留下消息的用戶名。'), value: prev_page }); work_area.append({ type: 'input', name: 'section', label: wgULS('小节(可选)', '小節(可選)'), tooltip: wgULS('您留下消息的小节标题。', '您留下消息的小節標題。'), value: prev_section }); break; case 'other': work_area.append({ type: 'div', label: '', style: 'color: red', id: 'twinkle-talkback-optout-message' }); work_area.append({ type: 'input', name: 'page', label: wgULS('完整页面名', '完整頁面名'), tooltip: wgULS('您留下消息的完整页面名,比如“' + Twinkle.getPref('projectNamespaceName') + ' talk:Twinkle”。', '您留下消息的完整頁面名,比如「' + Twinkle.getPref('projectNamespaceName') + ' talk:Twinkle」。'), value: prev_page }); work_area.append({ type: 'input', name: 'section', label: wgULS('小节(可选)', '小節(可選)'), tooltip: wgULS('您留下消息的小节标题。', '您留下消息的小節標題。'), value: prev_section }); break; case 'mail': work_area.append({ type: 'input', name: 'section', label: wgULS('电子邮件主题(可选)', '電子郵件主題(可選)'), tooltip: wgULS('您发出的电子邮件的主题。', '您發出的電子郵件的主題。') }); break; } if (value !== 'notice') { work_area.append({ type: 'textarea', label: wgULS('附加消息(可选):', '附加訊息(可選):'), name: 'message', tooltip: wgULS('会在回复通告模板下出现的消息,您的签名会被加在最后。', '會在回覆通告模板下出現的訊息,您的簽名會被加在最後。') }); } work_area = work_area.render(); root.replaceChild(work_area, old_area); if (root.message) { root.message.value = prev_message; } if (Twinkle.talkback.optout) { $('#twinkle-talkback-optout-message').append(Twinkle.talkback.optout); } }; var callback_evaluate = function(e) { var tbtarget = e.target.getChecked('tbtarget')[0]; var page = null; var section = e.target.section.value; var userName = Morebits.relevantUserName(); var fullUserTalkPageName = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user_talk] + ':' + userName; if (tbtarget === 'usertalk' || tbtarget === 'other') { page = e.target.page.value; if (tbtarget === 'usertalk') { if (!page) { alert(wgULS('您必须指定用户名。', '您必須指定用戶名。')); return; } } else { if (!page) { alert(wgULS('您必须指定页面名。', '您必須指定頁面名。')); return; } } } var message; if (e.target.message) { message = e.target.message.value; } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); Morebits.wiki.actionCompleted.redirect = fullUserTalkPageName; Morebits.wiki.actionCompleted.notice = wgULS('回复通告完成,将在几秒内刷新', '回覆通告完成,將在幾秒內重新整理'); var tbPageName = tbtarget === 'mytalk' ? mw.config.get('wgUserName') : page; var text; var title, content, editSummary; if (tbtarget === 'mail') { title = Twinkle.getPref('mailHeading'); content = "{{you've got mail|subject=" + section + '|ts=~~~~~}}'; text = '\n\n==' + title + '==\n' + content; if (message) { content += '\n' + message.trim(); text += '\n' + message.trim() + '--~~~~'; } else if (Twinkle.getPref('insertTalkbackSignature')) { text += '\n~~~~'; } editSummary = wgULS('通知:有新邮件', '通知:有新郵件'); } else { // tbtarget one of mytalk, usertalk, other // clean talkback heading: strip section header markers that were erroneously suggested in the documentation title = Twinkle.getPref('talkbackHeading').replace(/^\s*=+\s*(.*?)\s*=+$\s*/, '$1'); content = '{{talkback|' + tbPageName; if (section) { content += '|' + section; } content += '|target=' + userName + '|ts=~~~~~}}'; text = '\n\n==' + title + '==\n' + content; if (message) { content += '\n' + message.trim(); text += '\n' + message.trim() + '--~~~~'; } else if (Twinkle.getPref('insertTalkbackSignature')) { text += '\n~~~~'; } editSummary = wgULS('回复通告([[', '回覆通告([['); if (tbtarget !== 'other' && !/^\s*user talk:/i.test(tbPageName)) { editSummary += 'User talk:'; } editSummary += tbPageName + (section ? '#' + section : '') + ']])'; } var talkpage = new Morebits.wiki.page(fullUserTalkPageName, wgULS('加入回复通告', '加入回覆通告')); talkpage.setEditSummary(editSummary); talkpage.setChangeTags(Twinkle.changeTags); talkpage.setAppendText(text); talkpage.setCreateOption('recreate'); talkpage.setMinorEdit(Twinkle.getPref('markTalkbackAsMinor')); talkpage.setFollowRedirect(true); talkpage.append(); }; })(jQuery); // </nowiki> tv6u0syny0jfghpp9zhpe80gymsjox8 MediaWiki:Gadget-twinklebatchdelete.js 8 1368864 9943041 6328884 2026-10-12T15:40:55Z Xiplus 25402 Repo at 375b1e1: Accept both canonical and localized PrefixIndex names in path; Update PrefixIndex name 9943041 javascript text/javascript // <nowiki> (function($) { /* **************************************** *** twinklebatchdelete.js: Batch delete module (sysops only) **************************************** * Mode of invocation: Tab ("D-batch") * Active on: Existing non-articles, and Special:PrefixIndex */ Twinkle.batchdelete = function twinklebatchdelete() { if ( Morebits.userIsSysop && ( (mw.config.get('wgCurRevisionId') && mw.config.get('wgNamespaceNumber') > 0) || mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex' ) ) { Twinkle.addPortletLink(Twinkle.batchdelete.callback, wgULS('批删', '批刪'), 'tw-batch', wgULS('删除此分类或页面中的所有链接', '刪除此分類或頁面中的所有連結')); } }; Twinkle.batchdelete.unlinkCache = {}; // Has the subpages list been loaded? var subpagesLoaded; Twinkle.batchdelete.callback = function twinklebatchdeleteCallback() { subpagesLoaded = false; var Window = new Morebits.simpleWindow(600, 400); Window.setTitle(wgULS('批量删除', '批次刪除')); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'w:Help:Twinkle#管理员专用模块'); var form = new Morebits.quickForm(Twinkle.batchdelete.callback.evaluate); form.append({ type: 'checkbox', list: [ { label: wgULS('删除页面', '刪除頁面'), name: 'delete_page', value: 'delete', checked: true, subgroup: { type: 'checkbox', list: [ { label: wgULS('删除关联的讨论页(用户讨论页除外)', '刪除關聯的討論頁(使用者討論頁除外)'), name: 'delete_talk', value: 'delete_talk', checked: true }, { label: wgULS('删除到已删页面的重定向页', '刪除到已刪頁面的重新導向頁面'), name: 'delete_redirects', value: 'delete_redirects', checked: true }, { label: wgULS('删除已删页面的子页面', '刪除已刪頁面的子頁面'), name: 'delete_subpages', value: 'delete_subpages', checked: false, event: Twinkle.batchdelete.callback.toggleSubpages, subgroup: { type: 'checkbox', list: [ { label: wgULS('删除已删子页面的讨论页', '刪除已刪子頁面的討論頁'), name: 'delete_subpage_talks', value: 'delete_subpage_talks' }, { label: wgULS('删除到已删子页面的重定向页', '刪除到已刪子頁面的重新導向頁面'), name: 'delete_subpage_redirects', value: 'delete_subpage_redirects' }, { label: wgULS('取消所有已删页面的链入(仅处理条目及Portal命名空间)', '取消所有已刪頁面的連入(僅處理條目及Portal命名空間)'), name: 'unlink_subpages', value: 'unlink_subpages' } ] } } ] } }, { label: wgULS('取消链入(仅处理条目及Portal命名空间)', '取消連入(僅處理條目及Portal命名空間)'), name: 'unlink_page', value: 'unlink', checked: false }, { label: wgULS('移除文件使用(所有命名空间)', '移除檔案使用(所有命名空間)'), name: 'unlink_file', value: 'unlink_file', checked: true } ] }); form.append({ type: 'select', name: 'common_reason', label: '常用理由:', style: 'width: 85%;', list: Twinkle.batchdelete.deletereasonlist, event: Twinkle.batchdelete.callback.change_common_reason }); form.append({ name: 'reason', type: 'input', label: '理由:', size: 75 }); var query = { 'action': 'query', 'prop': 'revisions|info|imageinfo', 'inprop': 'protection', 'rvprop': 'size|user' }; // On categories if (mw.config.get('wgNamespaceNumber') === 14) { query.generator = 'categorymembers'; query.gcmtitle = mw.config.get('wgPageName'); query.gcmlimit = Twinkle.getPref('batchMax'); // On Special:PrefixIndex } else if (mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex') { query.generator = 'allpages'; query.gaplimit = Twinkle.getPref('batchMax'); if (mw.util.getParamValue('prefix')) { query.gapnamespace = mw.util.getParamValue('namespace'); query.gapprefix = mw.util.getParamValue('prefix'); } else { var pathSplit = decodeURIComponent(location.pathname).split('/'); if (pathSplit.length < 3 || ['Special:PrefixIndex', 'Special:前缀索引'].indexOf(pathSplit[2]) === -1) { return; } var titleSplit = pathSplit[3].split(':'); query.gapnamespace = mw.config.get('wgNamespaceIds')[titleSplit[0].toLowerCase()]; if (titleSplit.length < 2 || typeof query.gapnamespace === 'undefined') { query.gapnamespace = 0; // article namespace query.gapprefix = pathSplit.splice(3).join('/'); } else { pathSplit = pathSplit.splice(4); pathSplit.splice(0, 0, titleSplit.splice(1).join(':')); query.gapprefix = pathSplit.join('/'); } } // On normal pages } else { query.generator = 'links'; query.titles = mw.config.get('wgPageName'); query.gpllimit = Twinkle.getPref('batchMax'); } var statusdiv = document.createElement('div'); statusdiv.style.padding = '15px'; // just so it doesn't look broken Window.setContent(statusdiv); Morebits.status.init(statusdiv); Window.display(); Twinkle.batchdelete.pages = {}; var statelem = new Morebits.status(wgULS('抓取页面列表', '抓取頁面列表')); var wikipedia_api = new Morebits.wiki.api(wgULS('加载中…', '載入中…'), query, function(apiobj) { var xml = apiobj.responseXML; var $pages = $(xml).find('page').filter(':not([missing])'); // :not([imagerepository="shared"]) $pages.each(function(index, page) { var $page = $(page); var ns = $page.attr('ns'); var title = $page.attr('title'); var isRedir = $page.attr('redirect') === ''; var $editprot = $page.find('pr[type="edit"][level="sysop"]'); var isProtected = $editprot.length > 0; var size = $page.find('rev').attr('size'); var metadata = []; if (isRedir) { metadata.push(wgULS('重定向', '重新導向')); } if (isProtected) { metadata.push(wgULS('全保护,', '全保護,') + ($editprot.attr('expiry') === 'infinity' ? wgULS('无限期', '無限期') : wgULS('过期时间', '過期時間') + $editprot.attr('expiry'))); } if (ns === '6') { // mimic what delimages used to show for files metadata.push(wgULS('上传者:', '上傳者:') + $page.find('ii').attr('user')); metadata.push(wgULS('最后编辑:', '最後編輯:') + $page.find('rev').attr('user')); } else { metadata.push(size + wgULS('字节', '位元組')); } Twinkle.batchdelete.pages[title] = { label: title + (metadata.length ? '(' + metadata.join(',') + ')' : ''), value: title, checked: true, style: isProtected ? 'color:red' : '' }; }); var form = apiobj.params.form; form.append({ type: 'header', label: wgULS('待删除页面', '待刪除頁面') }); form.append({ type: 'button', label: wgULS('全选', '全選'), event: function dBatchSelectAll() { $(result).find('input[name=pages]:not(:checked)').each(function(_, e) { e.click(); // check it, and invoke click event so that subgroup can be shown }); // Check any unchecked subpages too $('input[name="pages.subpages"]').prop('checked', true); } }); form.append({ type: 'button', label: wgULS('全不选', '全不選'), event: function dBatchDeselectAll() { $(result).find('input[name=pages]:checked').each(function(_, e) { e.click(); // uncheck it, and invoke click event so that subgroup can be hidden }); } }); form.append({ type: 'checkbox', name: 'pages', id: 'tw-dbatch-pages', list: $.map(Twinkle.batchdelete.pages, function (e) { return e; }) }); form.append({ type: 'submit' }); var result = form.render(); apiobj.params.Window.setContent(result); var pageCheckboxes = Morebits.quickForm.getElements(result, 'pages') || []; pageCheckboxes.forEach(generateArrowLinks); Morebits.checkboxShiftClickSupport(pageCheckboxes); }, statelem); wikipedia_api.params = { form: form, Window: Window }; wikipedia_api.post(); }; function generateArrowLinks (checkbox) { var link = Morebits.htmlNode('a', ' >'); link.setAttribute('class', 'tw-dbatch-page-link'); link.setAttribute('href', mw.util.getUrl(checkbox.value)); link.setAttribute('target', '_blank'); checkbox.nextElementSibling.append(link); } Twinkle.batchdelete.generateNewPageList = function(form) { // Update the list of checked pages in Twinkle.batchdelete.pages object var elements = form.elements.pages; if (elements instanceof NodeList) { // if there are multiple pages for (var i = 0; i < elements.length; ++i) { Twinkle.batchdelete.pages[elements[i].value].checked = elements[i].checked; } } else if (elements instanceof HTMLInputElement) { // if there is just one page Twinkle.batchdelete.pages[elements.value].checked = elements.checked; } return new Morebits.quickForm.element({ type: 'checkbox', name: 'pages', id: 'tw-dbatch-pages', list: $.map(Twinkle.batchdelete.pages, function (e) { return e; }) }).render(); }; Twinkle.batchdelete.deletereasonlist = [ { label: wgULS('请选择', '請選擇'), value: '' }, { label: wgULS('G1: 无实际内容或非词典', 'G1: 無實際內容或非詞典'), value: wgULS('[[Wiktionary:CSD|G1]]: 无实际内容或非词典', '[[Wiktionary:CSD|G1]]: 無實際內容或非詞典') }, { label: wgULS('G10: 作者请求或原作者清空页面', 'G10: 作者請求或原作者清空頁面'), value: wgULS('[[Wiktionary:CSD|G10]]: 作者请求或原作者清空页面', '[[Wiktionary:CSD|G10]]: 作者請求或原作者清空頁面') }, { label: wgULS('A1: 标题拼写错误', 'A1: 標題拼寫錯誤'), value: wgULS('[[Wiktionary:CSD|A1]]: 标题拼写错误', '[[Wiktionary:CSD|A1]]: 標題拼寫錯誤') }, { label: wgULS('O4: 空的分类', 'O4: 空的分類'), value: wgULS('[[Wiktionary:CSD|O4]]: 空的分类', '[[Wiktionary:CSD|O4]]: 空的分類') } ]; Twinkle.batchdelete.callback.change_common_reason = function twinklebatchdeleteCallbackChangeCustomReason(e) { if (e.target.form.reason.value !== '') { e.target.form.reason.value = Morebits.string.appendPunctuation(e.target.form.reason.value); } e.target.form.reason.value += e.target.value; e.target.value = ''; }; Twinkle.batchdelete.callback.toggleSubpages = function twDbatchToggleSubpages(e) { var form = e.target.form; var newPageList, pageCheckboxes, subpageCheckboxes; if (e.target.checked) { form.delete_subpage_redirects.checked = form.delete_redirects.checked; form.delete_subpage_talks.checked = form.delete_talk.checked; form.unlink_subpages.checked = form.unlink_page.checked; // If lists of subpages were already loaded once, they are // available without use of any API calls if (subpagesLoaded) { $.each(Twinkle.batchdelete.pages, function(i, el) { // Get back the subgroup from subgroup_, where we saved it if (el.subgroup === null && el.subgroup_) { el.subgroup = el.subgroup_; } }); newPageList = Twinkle.batchdelete.generateNewPageList(form); $('#tw-dbatch-pages').replaceWith(newPageList); pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || []; pageCheckboxes.forEach(generateArrowLinks); Morebits.checkboxShiftClickSupport(pageCheckboxes); subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || []; subpageCheckboxes.forEach(generateArrowLinks); Morebits.checkboxShiftClickSupport(subpageCheckboxes); return; } // Proceed with API calls to get list of subpages var loadingText = '<strong id="dbatch-subpage-loading">' + wgULS('加载中...', '載入中...') + '</strong>'; $(e.target).after(loadingText); var pages = $(form.pages).map(function(i, el) { return el.value; }).get(); var subpageLister = new Morebits.batchOperation(); subpageLister.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks')); subpageLister.setPageList(pages); subpageLister.run(function worker (pageName) { var pageTitle = mw.Title.newFromText(pageName); // No need to look for subpages in main/file/mediawiki space if ([0, 6, 8].indexOf(pageTitle.namespace) > -1) { subpageLister.workerSuccess(); return; } var wikipedia_api = new Morebits.wiki.api(wgULS('正在获取 ', '正在取得 ') + pageName + wgULS(' 的子页面', ' 的子頁面'), { action: 'query', prop: 'revisions|info|imageinfo', generator: 'allpages', rvprop: 'size', inprop: 'protection', gapprefix: pageTitle.title + '/', gapnamespace: pageTitle.namespace, gaplimit: 'max', // 500 is max for normal users, 5000 for bots and sysops pageNameFull: pageName // Not used by API, but added for access in onSuccess() }, function onSuccess(apiobj) { var xml = apiobj.responseXML; var $pages = $(xml).find('page'); var subpageList = []; $pages.each(function(index, page) { var $page = $(page); var ns = $page.attr('ns'); var title = $page.attr('title'); var isRedir = $page.attr('redirect') === ''; var $editprot = $page.find('pr[type="edit"][level="sysop"]'); var isProtected = $editprot.length > 0; var size = $page.find('rev').attr('size'); var metadata = []; if (isRedir) { metadata.push('redirect'); } if (isProtected) { metadata.push(wgULS('全保护,', '全保護,') + ($editprot.attr('expiry') === 'infinity' ? wgULS('无限期', '無限期') : wgULS('过期时间', '過期時間') + $editprot.attr('expiry'))); } if (ns === '6') { // mimic what delimages used to show for files metadata.push(wgULS('上传者:', '上傳者:') + $page.find('ii').attr('user')); metadata.push(wgULS('最后编辑:', '最後編輯:') + $page.find('rev').attr('user')); } else { metadata.push(size + wgULS('字节', '位元組')); } subpageList.push({ label: title + (metadata.length ? ' (' + metadata.join('; ') + ')' : ''), value: title, checked: true, style: isProtected ? 'color:red' : '' }); }); if (subpageList.length) { var pageName = apiobj.query.pageNameFull; Twinkle.batchdelete.pages[pageName].subgroup = { type: 'checkbox', name: 'subpages', className: 'dbatch-subpages', list: subpageList }; } subpageLister.workerSuccess(); }, null /* statusElement */, function onFailure() { subpageLister.workerFailure(); }); wikipedia_api.post(); }, function postFinish () { // List 'em on the interface newPageList = Twinkle.batchdelete.generateNewPageList(form); $('#tw-dbatch-pages').replaceWith(newPageList); pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || []; pageCheckboxes.forEach(generateArrowLinks); Morebits.checkboxShiftClickSupport(pageCheckboxes); subpageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages.subpages') || []; subpageCheckboxes.forEach(generateArrowLinks); Morebits.checkboxShiftClickSupport(subpageCheckboxes); subpagesLoaded = true; // Remove "Loading... " text $('#dbatch-subpage-loading').remove(); }); } else if (!e.target.checked) { $.each(Twinkle.batchdelete.pages, function(i, el) { if (el.subgroup) { // Remove subgroup after saving its contents in subgroup_ // so that it can be retrieved easily if user decides to // delete the subpages again el.subgroup_ = el.subgroup; el.subgroup = null; } }); newPageList = Twinkle.batchdelete.generateNewPageList(form); $('#tw-dbatch-pages').replaceWith(newPageList); pageCheckboxes = Morebits.quickForm.getElements(newPageList, 'pages') || []; pageCheckboxes.forEach(generateArrowLinks); Morebits.checkboxShiftClickSupport(pageCheckboxes); } }; Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) { Morebits.wiki.actionCompleted.notice = wgULS('批量删除已完成', '批次刪除已完成'); var form = event.target; var numProtected = $(Morebits.quickForm.getElements(form, 'pages')).filter(function(index, element) { return element.checked && element.nextElementSibling.style.color === 'red'; }).length; if (numProtected > 0 && !confirm(wgULS('您正要删除 ', '您正要刪除 ') + numProtected + wgULS(' 个全保护页面,您确定吗?', ' 個全保護頁面,您確定嗎?'))) { return; } var pages = form.getChecked('pages'); var subpages = form.getChecked('pages.subpages'); var reason = form.reason.value; var delete_page = form.delete_page.checked; var delete_talk, delete_redirects, delete_subpages; var delete_subpage_redirects, delete_subpage_talks, unlink_subpages; if (delete_page) { delete_talk = form.delete_talk.checked; delete_redirects = form.delete_redirects.checked; delete_subpages = form.delete_subpages.checked; if (delete_subpages) { delete_subpage_redirects = form.delete_subpage_redirects.checked; delete_subpage_talks = form.delete_subpage_talks.checked; unlink_subpages = form.unlink_subpages.checked; } } var unlink_page = form.unlink_page.checked; var unlink_file = form.unlink_file.checked; if (!reason) { alert(wgULS('您需要给出一个理由', '您需要給出一個理由')); return; } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); if (!pages) { Morebits.status.error(wgULS('错误', '錯誤'), wgULS('没有要删除的内容,中止', '沒有要刪除的內容,中止')); return; } var pageDeleter = new Morebits.batchOperation(delete_page ? wgULS('正在删除页面', '正在刪除頁面') : wgULS('正在启动要求的任务', '正在啟動要求的任務')); pageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks')); // we only need the initial status lines if we're deleting the pages in the pages array pageDeleter.setOption('preserveIndividualStatusLines', delete_page); pageDeleter.setPageList(pages); pageDeleter.run(function worker(pageName) { var params = { page: pageName, delete_page: delete_page, delete_talk: delete_talk, delete_redirects: delete_redirects, unlink_page: unlink_page, unlink_file: unlink_file && /^(File|Image):/i.test(pageName), reason: reason, pageDeleter: pageDeleter }; var wikipedia_page = new Morebits.wiki.page(pageName, wgULS('正在删除页面 ', '正在刪除頁面 ') + pageName); wikipedia_page.setCallbackParameters(params); if (delete_page) { wikipedia_page.setEditSummary(reason); wikipedia_page.setChangeTags(Twinkle.changeTags); wikipedia_page.suppressProtectWarning(); wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure); } else { Twinkle.batchdelete.callbacks.doExtras(wikipedia_page); } }, function postFinish() { if (delete_subpages) { var subpageDeleter = new Morebits.batchOperation(wgULS('正在删除子页面', '正在刪除子頁面')); subpageDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks')); subpageDeleter.setOption('preserveIndividualStatusLines', true); subpageDeleter.setPageList(subpages); subpageDeleter.run(function(pageName) { var params = { page: pageName, delete_page: true, delete_talk: delete_subpage_talks, delete_redirects: delete_subpage_redirects, unlink_page: unlink_subpages, unlink_file: false, reason: reason, pageDeleter: subpageDeleter }; var wikipedia_page = new Morebits.wiki.page(pageName, wgULS('正在删除子页面 ', '正在刪除子頁面 ') + pageName); wikipedia_page.setCallbackParameters(params); wikipedia_page.setEditSummary(reason); wikipedia_page.setChangeTags(Twinkle.changeTags); wikipedia_page.suppressProtectWarning(); wikipedia_page.deletePage(Twinkle.batchdelete.callbacks.doExtras, pageDeleter.workerFailure); }); } }); }; Twinkle.batchdelete.callbacks = { // this stupid parameter name is a temporary thing until I implement an overhaul // of Morebits.wiki.* callback parameters doExtras: function(thingWithParameters) { var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters() : thingWithParameters.getCallbackParameters(); // the initial batch operation's job is to delete the page, and that has // succeeded by now params.pageDeleter.workerSuccess(thingWithParameters); var query, wikipedia_api; if (params.unlink_page) { Twinkle.batchdelete.unlinkCache = {}; query = { 'action': 'query', 'list': 'backlinks', 'blfilterredir': 'nonredirects', 'blnamespace': [0, 100], // main space and portal space only 'bltitle': params.page, 'bllimit': 'max' // 500 is max for normal users, 5000 for bots and sysops }; wikipedia_api = new Morebits.wiki.api(wgULS('正在获取链入', '正在取得連入'), query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain); wikipedia_api.params = params; wikipedia_api.post(); } if (params.unlink_file) { query = { 'action': 'query', 'list': 'imageusage', 'iutitle': params.page, 'iulimit': 'max' // 500 is max for normal users, 5000 for bots and sysops }; wikipedia_api = new Morebits.wiki.api(wgULS('正在获取文件链入', '正在取得檔案連入'), query, Twinkle.batchdelete.callbacks.unlinkImageInstancesMain); wikipedia_api.params = params; wikipedia_api.post(); } if (params.delete_page) { if (params.delete_redirects) { query = { 'action': 'query', 'titles': params.page, 'prop': 'redirects', 'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops }; wikipedia_api = new Morebits.wiki.api(wgULS('正在获取重定向', '正在取得重新導向'), query, Twinkle.batchdelete.callbacks.deleteRedirectsMain); wikipedia_api.params = params; wikipedia_api.post(); } if (params.delete_talk) { var pageTitle = mw.Title.newFromText(params.page); if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) { pageTitle.namespace++; // now pageTitle is the talk page title! query = { 'action': 'query', 'titles': pageTitle.toText() }; wikipedia_api = new Morebits.wiki.api(wgULS('正在检查讨论页面是否存在', '正在檢查討論頁面是否存在'), query, Twinkle.batchdelete.callbacks.deleteTalk); wikipedia_api.params = params; wikipedia_api.params.talkPage = pageTitle.toText(); wikipedia_api.post(); } } } }, deleteRedirectsMain: function(apiobj) { var xml = apiobj.responseXML; var pages = $(xml).find('rd').map(function() { return $(this).attr('title'); }).get(); if (!pages.length) { return; } var redirectDeleter = new Morebits.batchOperation(wgULS('正在删除到 ', '正在刪除到 ') + apiobj.params.page + wgULS(' 的重定向', ' 的重新導向')); redirectDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks')); redirectDeleter.setPageList(pages); redirectDeleter.run(function(pageName) { var wikipedia_page = new Morebits.wiki.page(pageName, wgULS('正在删除 ', '正在刪除 ') + pageName); wikipedia_page.setEditSummary('[[Wiktionary:CSD#G15|G15]]: ' + wgULS('指向已删页面“', '指向已刪頁面「') + apiobj.params.page + wgULS('”的重定向', '」的重新導向')); wikipedia_page.setChangeTags(Twinkle.changeTags); wikipedia_page.deletePage(redirectDeleter.workerSuccess, redirectDeleter.workerFailure); }); }, deleteTalk: function(apiobj) { var xml = apiobj.responseXML; var exists = $(xml).find('page:not([missing])').length > 0; if (!exists) { // no talk page; forget about it return; } var page = new Morebits.wiki.page(apiobj.params.talkPage, wgULS('正在删除页面 ', '正在刪除頁面 ') + apiobj.params.page + wgULS(' 的讨论页', ' 的討論頁')); page.setEditSummary('[[Wiktionary:CSD#G15|G15]]: ' + wgULS('已删页面“', '已刪頁面「') + apiobj.params.page + wgULS('”的[[w:Wikipedia:讨论页|讨论页]]', '」的[[w:Wikipedia:讨论页|討論頁]]')); page.setChangeTags(Twinkle.changeTags); page.deletePage(); }, unlinkBacklinksMain: function(apiobj) { var xml = apiobj.responseXML; var pages = $(xml).find('bl').map(function() { return $(this).attr('title'); }).get(); if (!pages.length) { return; } unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks')); var unlinker = new Morebits.batchOperation('正在取消到 ' + apiobj.params.page + wgULS(' 的链入', ' 的連入')); unlinker.setPageList(pages); unlinker.run(function(pageName) { var wikipedia_page = new Morebits.wiki.page(pageName, '正在取消 ' + pageName + wgULS(' 上的链入', ' 上的連入')); var params = $.extend({}, apiobj.params); params.title = pageName; params.unlinker = unlinker; wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks); }); }, unlinkBacklinks: function(pageobj) { var params = pageobj.getCallbackParameters(); if (!pageobj.exists()) { // we probably just deleted it, as a recursive backlink params.unlinker.workerSuccess(pageobj); return; } var text; if (params.title in Twinkle.batchdelete.unlinkCache) { text = Twinkle.batchdelete.unlinkCache[params.title]; } else { text = pageobj.getPageText(); } var old_text = text; var wikiPage = new Morebits.wikitext.page(text); wikiPage.removeLink(params.page); text = wikiPage.getText(); Twinkle.batchdelete.unlinkCache[params.title] = text; if (text === old_text) { // Nothing to do, return params.unlinker.workerSuccess(pageobj); return; } pageobj.setEditSummary(wgULS('取消到已删页面', '取消到已刪頁面') + params.page + wgULS('的链入', '的連入')); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setPageText(text); pageobj.setCreateOption('nocreate'); pageobj.setMaxConflictRetries(10); pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure); }, unlinkImageInstancesMain: function(apiobj) { var xml = apiobj.responseXML; var pages = $(xml).find('iu').map(function() { return $(this).attr('title'); }).get(); if (!pages.length) { return; } var unlinker = new Morebits.batchOperation('正在取消到 ' + apiobj.params.page + wgULS(' 的链入', ' 的連入')); unlinker.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks')); unlinker.setPageList(pages); unlinker.run(function(pageName) { var wikipedia_page = new Morebits.wiki.page(pageName, '取消 ' + pageName + wgULS(' 的文件使用', ' 的檔案使用')); var params = $.extend({}, apiobj.params); params.title = pageName; params.unlinker = unlinker; wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkImageInstances); }); }, unlinkImageInstances: function(pageobj) { var params = pageobj.getCallbackParameters(); if (!pageobj.exists()) { // we probably just deleted it, as a recursive backlink params.unlinker.workerSuccess(pageobj); return; } var image = params.page.replace(/^(?:Image|File):/, ''); var text; if (params.title in Twinkle.batchdelete.unlinkCache) { text = Twinkle.batchdelete.unlinkCache[params.title]; } else { text = pageobj.getPageText(); } var old_text = text; var wikiPage = new Morebits.wikitext.page(text); wikiPage.commentOutImage(image, wgULS('因文件已删,故注解', '因檔案已刪,故註解')); text = wikiPage.getText(); Twinkle.batchdelete.unlinkCache[params.title] = text; if (text === old_text) { pageobj.getStatusElement().error('在 ' + pageobj.getPageName() + ' 上取消 ' + image + wgULS(' 的文件使用失败', ' 的檔案使用失敗')); params.unlinker.workerFailure(pageobj); return; } pageobj.setEditSummary(wgULS('取消使用已被删除文件', '取消使用已被刪除檔案') + image + wgULS('使用,因为:', '使用,因為:') + params.reason); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setPageText(text); pageobj.setCreateOption('nocreate'); pageobj.setMaxConflictRetries(10); pageobj.save(params.unlinker.workerSuccess, params.unlinker.workerFailure); } }; })(jQuery); // </nowiki> 91xm9zl5l4e6a0r79gv2q0pg3m9v8nl MediaWiki:Gadget-twinkleblock.js 8 1368866 9943042 6328885 2026-10-12T15:40:58Z Xiplus 25402 Repo at 375b1e1: Add watchBlockNotices config 9943042 javascript text/javascript // <nowiki> (function($) { var api = new mw.Api(), relevantUserName; /* **************************************** *** twinkleblock.js: Block module **************************************** * Mode of invocation: Tab ("Block") * Active on: Any page with relevant user name (userspace, contribs, etc.) */ Twinkle.block = function twinkleblock() { // should show on Contributions or Block pages, anywhere there's a relevant user if (Morebits.userIsSysop && Morebits.relevantUserName(true)) { Twinkle.addPortletLink(Twinkle.block.callback, wgULS('封禁', '封鎖'), 'tw-block', wgULS('全站封禁相关用户', '全站封禁相關用戶')); } }; Twinkle.block.callback = function twinkleblockCallback() { if (Morebits.relevantUserName(true) === mw.config.get('wgUserName') && !confirm(wgULS('您即将封禁自己!确认要继续吗?', '您即將封鎖自己!確認要繼續嗎?'))) { return; } Twinkle.block.currentBlockInfo = undefined; Twinkle.block.field_block_options = {}; Twinkle.block.field_template_options = {}; var Window = new Morebits.simpleWindow(650, 530); // need to be verbose about who we're blocking Window.setTitle(wgULS('封禁或向', '封鎖或向') + Morebits.relevantUserName(true) + wgULS('发出封禁模板', '發出封鎖模板')); Window.setScriptName('Twinkle'); Window.addFooterLink('封禁模板', 'Wikipedia:模板消息/用戶討論名字空間#.E5.B0.81.E7.A6.81'); Window.addFooterLink(wgULS('封禁方针', '封鎖方針'), 'WP:BLOCK'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'WP:TW/DOC#block'); var form = new Morebits.quickForm(Twinkle.block.callback.evaluate); var actionfield = form.append({ type: 'field', label: wgULS('操作类型', '操作類別') }); actionfield.append({ type: 'checkbox', name: 'actiontype', event: Twinkle.block.callback.change_action, list: [ { label: wgULS('封禁用户', '封鎖用戶'), value: 'block', tooltip: wgULS('用选择的选项全站封禁相关用户。', '用選擇的選項全站封鎖相關用戶。'), checked: true }, { label: wgULS('加入封禁模板到用户对话页', '加入封鎖模板到用戶對話頁'), value: 'template', tooltip: wgULS('如果执行封禁的管理员忘记发出封禁模板,或你封禁了用户而没有给其发出模板,则你可以用此来发出合适的模板。', '如果執行封鎖的管理員忘記發出封鎖模板,或你封鎖了用戶而沒有給其發出模板,則你可以用此來發出合適的模板。'), checked: false, disabled: mw.util.isIPAddress(Morebits.relevantUserName(true), false), hidden: true }, { label: wgULS('标记用户页', '標記用戶頁'), value: 'tag', tooltip: wgULS('将用户页替换成{{indef}}或{{spp}},仅限永久封禁使用。', '將用戶頁替換成{{indef}}或{{spp}},僅限永久封鎖使用。'), hidden: true }, { label: wgULS('保护用户页', '保護用戶頁'), value: 'protect', tooltip: wgULS('全保护用户页,仅限永久封禁使用。', '全保護用戶頁,僅限永久封鎖使用。'), hidden: true }, { label: wgULS('解除封禁用户', '解除封鎖用戶'), value: 'unblock', tooltip: wgULS('解除封禁相关用户。', '解除封鎖相關用戶。') } ] }); form.append({ type: 'field', label: wgULS('默认', '預設'), name: 'field_preset' }); form.append({ type: 'field', label: wgULS('模板设置', '模板設定'), name: 'field_template_options' }); form.append({ type: 'field', label: wgULS('封禁设置', '封鎖設定'), name: 'field_block_options' }); form.append({ type: 'field', label: wgULS('标记用户页', '標記用戶頁'), name: 'field_tag_options' }); form.append({ type: 'field', label: wgULS('解除封禁设置', '解除封鎖設定'), name: 'field_unblock_options' }); form.append({ type: 'submit', label: '提交' }); var result = form.render(); Window.setContent(result); Window.display(); result.root = result; Twinkle.block.fetchUserInfo(function() { if (Twinkle.block.isRegistered) { var $form = $(result); Morebits.quickForm.setElementVisibility($form.find('[name=actiontype][value=tag]').parent(), true); Morebits.quickForm.setElementVisibility($form.find('[name=actiontype][value=protect]').parent(), true); } // clean up preset data (defaults, etc.), done exactly once, must be before Twinkle.block.callback.change_action is called Twinkle.block.transformBlockPresets(); // init the controls after user and block info have been fetched var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.actiontype[0].dispatchEvent(evt); }); }; Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) { var userName = Morebits.relevantUserName(true); var query = { format: 'json', action: 'query', list: 'blocks|users|logevents', letype: 'block', lelimit: 1, ususers: userName, letitle: 'User:' + userName }; if (Morebits.ip.isRange(userName)) { query.bkip = userName; } else { query.bkusers = userName; } api.get(query) .then(function(data) { var blockinfo = data.query.blocks[0], userinfo = data.query.users[0]; Twinkle.block.isRegistered = !!userinfo.userid; relevantUserName = Twinkle.block.isRegistered ? 'User:' + userName : userName; if (blockinfo) { // handle frustrating system of inverted boolean values blockinfo.disabletalk = blockinfo.allowusertalk === undefined; blockinfo.hardblock = blockinfo.anononly === undefined; Twinkle.block.currentBlockInfo = blockinfo; } Twinkle.block.hasBlockLog = !!data.query.logevents.length; if (typeof fn === 'function') { return fn(); } }, function(msg) { Morebits.status.init($('div[name="currentblock"] span').last()[0]); Morebits.status.warn(wgULS('抓取用户信息出错', '抓取用戶資訊出錯'), msg); }); }; Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) { Twinkle.block[$(fieldset).prop('name')] = {}; $(fieldset).serializeArray().forEach(function(el) { Twinkle.block[$(fieldset).prop('name')][el.name] = el.value; }); }; Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) { var field_preset, field_template_options, field_block_options, field_tag_options, field_unblock_options, $form = $(e.target.form); if (e.target.value === 'unblock') { if (!Twinkle.block.currentBlockInfo) { $form.find('[name=actiontype][value=unblock]').prop('checked', false); return alert(wgULS('用户没有被封禁', '用戶沒有被封鎖')); } $form.find('[name=actiontype][value=block]').prop('checked', false); $form.find('[name=actiontype][value=template]').prop('checked', false); $form.find('[name=actiontype][value=tag]').prop('checked', false); $form.find('[name=actiontype][value=protect]').prop('checked', false); } else { $form.find('[name=actiontype][value=unblock]').prop('checked', false); } Twinkle.block.callback.saveFieldset($('[name=field_block_options]')); Twinkle.block.callback.saveFieldset($('[name=field_template_options]')); Twinkle.block.callback.saveFieldset($('[name=field_tag_options]')); Twinkle.block.callback.saveFieldset($('[name=field_unblock_options]')); if ($form.find('[name=actiontype][value=block]').is(':checked')) { field_preset = new Morebits.quickForm.element({ type: 'field', label: wgULS('默认', '預設'), name: 'field_preset' }); field_preset.append({ type: 'select', name: 'preset', label: wgULS('选择默认:', '選擇預設:'), event: Twinkle.block.callback.change_preset, list: Twinkle.block.callback.filtered_block_groups() }); field_block_options = new Morebits.quickForm.element({ type: 'field', label: wgULS('封禁设置', '封鎖設定'), name: 'field_block_options' }); field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' }); field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' }); field_block_options.append({ type: 'select', name: 'expiry_preset', label: wgULS('过期时间:', '過期時間:'), event: Twinkle.block.callback.change_expiry, list: [ { label: wgULS('自定义', '自訂'), value: 'custom', selected: true }, { label: wgULS('无限期', '無限期'), value: 'infinity' }, { label: wgULS('3小时', '3小時'), value: '3 hours' }, { label: wgULS('12小时', '12小時'), value: '12 hours' }, { label: wgULS('24小时', '24小時'), value: '24 hours' }, { label: wgULS('31小时', '31小時'), value: '31 hours' }, { label: wgULS('36小时', '36小時'), value: '36 hours' }, { label: wgULS('48小时', '48小時'), value: '48 hours' }, { label: wgULS('60小时', '60小時'), value: '60 hours' }, { label: wgULS('72小时', '72小時'), value: '72 hours' }, { label: wgULS('1周', '1週'), value: '1 week' }, { label: wgULS('2周', '2週'), value: '2 weeks' }, { label: '1月', value: '1 month' }, { label: '3月', value: '3 months' }, { label: '6月', value: '6 months' }, { label: '1年', value: '1 year' }, { label: '2年', value: '2 years' }, { label: '3年', value: '3 years' } ] }); field_block_options.append({ type: 'input', name: 'expiry', label: wgULS('自定义过期时间', '自訂過期時間'), tooltip: wgULS('您可以使用相对时间,如“1 minute”或“19 days”;或绝对时间,“yyyymmddhhmm”(如“200602011405”是2006年2月1日14:05 UTC。)', '您可以使用相對時間,如「1 minute」或「19 days」;或絕對時間,「yyyymmddhhmm」(如「200602011405」是2006年2月1日14:05 UTC。)'), value: Twinkle.block.field_block_options.expiry || Twinkle.block.field_template_options.template_expiry }); var blockoptions = [ { checked: Twinkle.block.field_block_options.nocreate, label: wgULS('禁止创建账户', '禁止建立帳戶'), name: 'nocreate', value: '1' }, { checked: Twinkle.block.field_block_options.noemail, label: wgULS('电子邮件停用', '電子郵件停用'), name: 'noemail', value: '1' }, { checked: Twinkle.block.field_block_options.disabletalk, label: wgULS('不能编辑自己的讨论页', '不能編輯自己的討論頁'), name: 'disabletalk', value: '1' } ]; if (Twinkle.block.isRegistered) { blockoptions.push({ checked: Twinkle.block.field_block_options.autoblock, label: wgULS('自动封禁', '自動封鎖'), name: 'autoblock', value: '1' }); } else { blockoptions.push({ checked: Twinkle.block.field_block_options.hardblock, label: wgULS('阻止登录用户使用该IP地址编辑', '阻止登入用戶使用該IP位址編輯'), name: 'hardblock', value: '1' }); } blockoptions.push({ checked: Twinkle.block.field_block_options.watchuser, label: wgULS('监视该用户的用户页和讨论页', '監視該用戶的用戶頁和討論頁'), name: 'watchuser', value: '1' }); field_block_options.append({ type: 'checkbox', name: 'blockoptions', list: blockoptions }); field_block_options.append({ type: 'textarea', label: wgULS('理由(用于封禁日志):', '理由(用於封鎖日誌):'), name: 'reason', value: Twinkle.block.field_block_options.reason }); field_block_options.append({ type: 'div', name: 'filerlog_label', label: wgULS('“参见”:', '「參見」:'), style: 'display:inline-block;font-style:normal !important', tooltip: wgULS('在封禁理由中标清特殊情况以供其他管理员参考', '在封鎖理由中標清特殊情況以供其他管理員參考') }); field_block_options.append({ type: 'checkbox', name: 'filter_see_also', event: Twinkle.block.callback.toggle_see_alsos, style: 'display:inline-block; margin-right:5px', list: [ { label: wgULS('过滤器日志', '過濾器日誌'), checked: false, value: wgULS('过滤器日志', '過濾器日誌') } ] }); field_block_options.append({ type: 'checkbox', name: 'deleted_see_also', event: Twinkle.block.callback.toggle_see_alsos, style: 'display:inline-block', list: [ { label: wgULS('已删除的编辑', '已刪除的編輯'), checked: false, value: wgULS('已删除的编辑', '已刪除的編輯') } ] }); field_block_options.append({ type: 'checkbox', name: 'filter_see_also', event: Twinkle.block.callback.toggle_see_alsos, style: 'display:inline-block; margin-right:5px', list: [ { label: wgULS('用户讨论页', '用戶討論頁'), checked: false, value: wgULS('用户讨论页', '用戶討論頁') } ] }); field_block_options.append({ type: 'checkbox', name: 'filter_see_also', event: Twinkle.block.callback.toggle_see_alsos, style: 'display:inline-block; margin-right:5px', list: [ { label: wgULS('过去的封禁记录', '過去的封鎖記錄'), checked: false, value: wgULS('过去的封禁记录', '過去的封鎖記錄') } ] }); if (Twinkle.block.currentBlockInfo) { field_block_options.append({ type: 'hidden', name: 'reblock', value: '1' }); } } if ($form.find('[name=actiontype][value=template]').is(':checked')) { field_template_options = new Morebits.quickForm.element({ type: 'field', label: wgULS('模板设置', '模板設定'), name: 'field_template_options' }); field_template_options.append({ type: 'select', name: 'template', label: wgULS('选择对话页模板:', '選擇對話頁模板:'), event: Twinkle.block.callback.change_template, list: Twinkle.block.callback.filtered_block_groups(true), value: Twinkle.block.field_template_options.template }); field_template_options.append({ type: 'input', name: 'article', display: 'none', label: wgULS('条目链接', '條目連結'), value: '', tooltip: wgULS('可以随通知链接条目,比如扰乱的主目标。没有条目需要链接则请留空。', '可以隨通知連結條目,比如擾亂的主目標。沒有條目需要連結則請留空。') }); if (!$form.find('[name=actiontype][value=block]').is(':checked')) { field_template_options.append({ type: 'input', name: 'template_expiry', display: 'none', label: '封禁期限:', value: '', tooltip: wgULS('封禁时长,如24小时、2周、无限期等。', '封鎖時長,如24小時、2週、無限期等。') }); } field_template_options.append({ type: 'input', name: 'block_reason', label: wgULS('“由于…您已被封禁”', '「由於…您已被封鎖」'), display: 'none', tooltip: wgULS('可选的理由,用于替换默认理由。只在常规封禁模板中有效。', '可選的理由,用於替換預設理由。只在常規封鎖模板中有效。'), value: Twinkle.block.field_template_options.block_reason }); if ($form.find('[name=actiontype][value=block]').is(':checked')) { field_template_options.append({ type: 'checkbox', name: 'blank_duration', list: [ { label: '不在模板中包含封禁期限', checked: Twinkle.block.field_template_options.blank_duration, tooltip: wgULS('模板将会显示“一段时间”而不是具体时长', '模板將會顯示「一段時間」而不是具體時長') } ] }); } else { /* field_template_options.append( { type: 'checkbox', name: 'notalk', list: [ { label: wgULS('不能编辑自己的讨论页', '不能編輯自己的討論頁'), checked: Twinkle.block.field_template_options.notalk, tooltip: wgULS('用此在保护模板中指明该用户编辑对话页的权限已被移除', '用此在保護模板中指明該用戶編輯對話頁的權限已被移除') } ] } ); */ } var $previewlink = $('<a id="twinkleblock-preivew-link">' + wgULS('预览', '預覽') + '</a>'); $previewlink.off('click').on('click', function() { Twinkle.block.callback.preview($form[0]); }); $previewlink.css({cursor: 'pointer'}); field_template_options.append({ type: 'div', id: 'blockpreview', label: [ $previewlink[0] ] }); field_template_options.append({ type: 'div', id: 'twinkleblock-previewbox', style: 'display: none' }); } if ($form.find('[name=actiontype][value=tag]').is(':checked')) { field_tag_options = new Morebits.quickForm.element({ type: 'field', label: wgULS('标记用户页', '標記用戶頁'), name: 'field_tag_options' }); field_tag_options.append({ type: 'select', name: 'tag', label: wgULS('选择用户页模板:', '選擇用戶頁模板:'), event: Twinkle.block.callback.change_tag, list: [ { label: '{{Indef}}:一般永久封禁', value: 'indef' }, { label: '{{Spp}}:傀儡帳號', value: 'spp' }, { label: '{{Sockpuppeteer|blocked}}:傀儡主帳號', value: 'spm' } ] }); field_tag_options.append({ type: 'input', name: 'username', label: wgULS('主账户用户名:', '主帳號用戶名:'), display: 'none' }); } if ($form.find('[name=actiontype][value=unblock]').is(':checked')) { field_unblock_options = new Morebits.quickForm.element({ type: 'field', label: wgULS('解除封禁设置', '解除封鎖設定'), name: 'field_unblock_options' }); field_unblock_options.append({ type: 'textarea', label: wgULS('理由(用于封禁日志):', '理由(用於封鎖日誌):'), name: 'reason', value: Twinkle.block.field_unblock_options.reason }); } var oldfield; if (field_preset) { oldfield = $form.find('fieldset[name="field_preset"]')[0]; oldfield.parentNode.replaceChild(field_preset.render(), oldfield); } else { $form.find('fieldset[name="field_preset"]').hide(); } if (field_block_options) { oldfield = $form.find('fieldset[name="field_block_options"]')[0]; oldfield.parentNode.replaceChild(field_block_options.render(), oldfield); } else { $form.find('fieldset[name="field_block_options"]').hide(); } if (field_tag_options) { oldfield = $form.find('fieldset[name="field_tag_options"]')[0]; oldfield.parentNode.replaceChild(field_tag_options.render(), oldfield); } else { $form.find('fieldset[name="field_tag_options"]').hide(); } if (field_unblock_options) { oldfield = $form.find('fieldset[name="field_unblock_options"]')[0]; oldfield.parentNode.replaceChild(field_unblock_options.render(), oldfield); } else { $form.find('fieldset[name="field_unblock_options"]').hide(); } if (field_template_options) { oldfield = $form.find('fieldset[name="field_template_options"]')[0]; oldfield.parentNode.replaceChild(field_template_options.render(), oldfield); e.target.form.root.previewer = new Morebits.wiki.preview($(e.target.form.root).find('#twinkleblock-previewbox').last()[0]); } else { $form.find('fieldset[name="field_template_options"]').hide(); } if (Twinkle.block.hasBlockLog) { var $blockloglink = $('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: Morebits.relevantUserName(true), type: 'block'}) + '">' + wgULS('封禁日志', '封鎖日誌') + '</a>)'); Morebits.status.init($('div[name="hasblocklog"] span').last()[0]); Morebits.status.warn(wgULS('此用户曾在过去被封禁', '此用戶曾在過去被封鎖'), $blockloglink[0]); } if (Twinkle.block.currentBlockInfo) { Morebits.status.init($('div[name="currentblock"] span').last()[0]); if (Twinkle.block.currentBlockInfo.partial === '') { // Partial block Morebits.status.warn(relevantUserName + wgULS('已被部分封禁', '已被部分封鎖'), wgULS('提交请求来用给定的设置转为全站封禁', '提交請求來用給定的設定轉為全站封鎖')); } else if (Twinkle.block.currentBlockInfo.partial === undefined) { // Sitewide block Morebits.status.warn(relevantUserName + wgULS('已被封禁', '已被封鎖'), wgULS('提交请求来用给定的设置重新封禁', '提交請求來用給定的設定重新封鎖')); } Twinkle.block.callback.update_form(e, Twinkle.block.currentBlockInfo); } else if ($form.find('[name=actiontype][value=template]').is(':checked')) { // make sure all the fields are correct based on defaults if ($form.find('[name=actiontype][value=block]').is(':checked')) { Twinkle.block.callback.change_preset(e); } else { Twinkle.block.callback.change_template(e); } } else if ($form.find('[name=actiontype][value=block]').is(':checked')) { Twinkle.block.callback.change_preset(e); } if ($form.find('[name=actiontype][value=tag]').is(':checked')) { Twinkle.block.callback.change_tag(e); } }; /* * Keep alphabetized by key name, Twinkle.block.blockGroups establishes * the order they will appear in the interface * * Block preset format, all keys accept only 'true' (omit for false) except where noted: * <title of block template> : { * autoblock: <autoblock any IP addresses used (for registered users only)> * disabletalk: <disable user from editing their own talk page while blocked> * expiry: <string - expiry timestamp, can include relative times like "5 months", "2 weeks" etc, use "infinity" for indefinite> * forAnonOnly: <show block option in the interface only if the relevant user is an IP> * forRegisteredOnly: <show block option in the interface only if the relevant user is registered> * label: <string - label for the option of the dropdown in the interface (keep brief)> * noemail: prevent the user from sending email through Special:Emailuser * pageParam: <set if the associated block template accepts a page parameter> * prependReason: <string - prepends the value of 'reason' to the end of the existing reason, namely for when revoking talk page access> * nocreate: <block account creation from the user's IP (for anonymous users only)> * nonstandard: <template does not conform to stewardship of WikiProject User Warnings and may not accept standard parameters> * reason: <string - block rationale, as would appear in the block log, * and the edit summary for when adding block template, unless 'summary' is set> * reasonParam: <set if the associated block template accepts a reason parameter> * sig: <string - set to ~~~~ if block template does not accept "true" as the value, or set null to omit sig param altogether> * summary: <string - edit summary for when adding block template to user's talk page, if not set, 'reason' is used> * suppressArticleInSummary: <set to suppress showing the article name in the edit summary, as with attack pages> * templateName: <string - name of template to use (instead of key name), entry will be omitted from the Templates list. * (e.g. use another template but with different block options)> * useInitialOptions: <when preset is chosen, only change given block options, leave others as they were> * * WARNING: 'anononly' and 'allowusertalk' are enabled by default. * To disable, set 'hardblock' and 'disabletalk', respectively */ Twinkle.block.blockPresetsInfo = { 'blocked proxy': { expiry: '2 years', nocreate: true, hardblock: true, nonstandard: true, reason: '{{blocked proxy}}', sig: '~~~~' }, // Placeholder for when we add support for rangeblocks /* 'range block' : { expiry: '6 months', forAnonOnly: true, nocreate: true, nonstandard: true, reason: '{{range block}}', sig: null }, */ 'schoolblock': { forAnonOnly: true, nocreate: true, nonstandard: true, reason: '{{schoolblock}}', sig: '~~~~' }, // uw-prefixed 'uw-ablock': { autoblock: true, expiry: '24 hours', forAnonOnly: true, nocreate: true, reasonParam: true }, 'uw-block1': { autoblock: true, nocreate: true, reasonParam: true }, 'uw-block2': { autoblock: true, expiry: '1 week', nocreate: true, reasonParam: true }, 'uw-block3': { autoblock: true, expiry: 'infinity', nocreate: true, reasonParam: true }, 'uw-dblock': { autoblock: true, nocreate: true }, 'uw-vblock': { autoblock: true, expiry: '24 hours', nocreate: true } }; Twinkle.block.blockGroupsUpdated = false; Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets() { // supply sensible defaults $.each(Twinkle.block.blockPresetsInfo, function(preset, settings) { settings.summary = settings.summary || settings.reason; settings.sig = settings.sig !== undefined ? settings.sig : 'yes'; // despite this it's preferred that you use 'infinity' as the value for expiry settings.indefinite = settings.indefinite || settings.expiry === 'infinity' || settings.expiry === 'indefinite' || settings.expiry === 'never'; if (!Twinkle.block.isRegistered && settings.indefinite) { settings.expiry = '24 hours'; } else { settings.expiry = settings.expiry || '24 hours'; } Twinkle.block.blockPresetsInfo[preset] = settings; }); if (!Twinkle.block.blockGroupsUpdated) { $.each(Twinkle.block.blockGroups, function(_, blockGroup) { if (blockGroup.custom) { blockGroup.list = Twinkle.getPref('customBlockReasonList'); } $.each(blockGroup.list, function(_, blockPreset) { var value = blockPreset.value, reason = blockPreset.label, newPreset = value + ':' + reason; Twinkle.block.blockPresetsInfo[newPreset] = jQuery.extend(true, {}, Twinkle.block.blockPresetsInfo[value]); Twinkle.block.blockPresetsInfo[newPreset].template = value; if (blockGroup.meta) { // Twinkle.block.blockPresetsInfo[newPreset].forAnonOnly = false; Twinkle.block.blockPresetsInfo[newPreset].forRegisteredOnly = false; } else if (reason) { Twinkle.block.blockPresetsInfo[newPreset].reason = reason; } if (blockGroup.custom && Twinkle.block.blockPresetsInfo[blockPreset.value] === undefined) { Twinkle.block.blockPresetsInfo[newPreset].reasonParam = true; Twinkle.block.blockPresetsInfo[blockPreset.value] = Twinkle.block.blockPresetsInfo[newPreset]; } if (blockGroup.custom && Twinkle.block.blockPresetsInfo[blockPreset.value].expiry === undefined) { Twinkle.block.blockPresetsInfo[blockPreset.value].expiry = '24 hours'; } blockPreset.value = newPreset; }); }); Twinkle.block.blockGroupsUpdated = true; } }; // These are the groups of presets and defines the order in which they appear. For each list item: // label: <string, the description that will be visible in the dropdown> // value: <string, the key of a preset in blockPresetsInfo> Twinkle.block.blockGroups = [ { meta: true, label: '封禁模板', list: [ { label: wgULS('层级1封禁', '層級1封鎖'), value: 'uw-block1' }, { label: wgULS('层级2封禁', '層級2封鎖'), value: 'uw-block2' }, { label: wgULS('层级3封禁', '層級3封鎖'), value: 'uw-block3' }, { label: '匿名封禁', value: 'uw-ablock', forAnonOnly: true } ] }, { label: '一般的封禁理由', list: [ { label: wgULS('破坏', '破壞'), value: 'uw-vblock' }, { label: wgULS('繁简破坏', '繁簡破壞'), value: 'uw-block1' }, { label: wgULS('跨维基项目破坏', '跨維基項目破壞'), value: 'uw-block3', forRegisteredOnly: true }, { label: wgULS('纯粹破坏', '純粹破壞'), value: 'uw-block3' }, { label: wgULS('散发广告/宣传', '散發廣告/宣傳'), value: 'uw-block1' }, { label: wgULS('仅散发广告/宣传', '僅散發廣告/宣傳'), value: 'uw-block3', forRegisteredOnly: true }, { label: wgULS('无礼的行为、攻击别人', '無禮的行為、攻擊別人'), value: 'uw-block1' }, { label: wgULS('骚扰用户', '騷擾用戶'), value: 'uw-block1' }, { label: wgULS('扰乱', '擾亂'), value: 'uw-block1' }, { label: wgULS('确认为傀儡', '確認為傀儡'), value: 'uw-block3' }, { label: wgULS('屡次增加不实资料', '屢次增加不實資料'), value: 'uw-block1' }, { label: wgULS('在条目中增加无意义文字', '在條目中增加無意義文字'), value: 'uw-block1' }, { label: wgULS('无故删除条目内容', '無故刪除條目內容'), value: 'uw-dblock' }, { label: wgULS('多次加入侵犯著作权的内容', '多次加入侵犯版權的內容'), value: 'uw-block1' } ] }, { custom: true, label: wgULS('自定义的封禁理由', '自訂的封鎖理由') }, { label: wgULS('用户名封禁', '用戶名封鎖'), list: [ { label: wgULS('用户名不当', '用戶名不當'), value: 'uw-block3', forRegisteredOnly: true } ] }, { label: '其他模板', list: [ { label: '', value: 'blocked proxy', forAnonOnly: true } ] } ]; Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(show_template) { return $.map(Twinkle.block.blockGroups, function(blockGroup) { if (!show_template && blockGroup.meta) { return; } var list = $.map(blockGroup.list, function(blockPreset) { // only show uw-talkrevoked if reblocking if (!Twinkle.block.currentBlockInfo && blockPreset.value === 'uw-talkrevoked') { return; } var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value]; var registrationRestrict = blockPreset.forRegisteredOnly ? Twinkle.block.isRegistered : blockPreset.forAnonOnly ? !Twinkle.block.isRegistered : true; if (!(blockSettings.templateName && show_template) && registrationRestrict) { var templateName = blockSettings.templateName || blockSettings.template || blockPreset.value; return { label: (show_template ? '{{' + templateName + '}}: ' : '') + (blockPreset.label || '{{' + templateName + '}}'), value: blockPreset.value, data: [{ name: 'template-name', value: templateName }], selected: !!blockPreset.selected }; } }); if (list.length) { return { label: blockGroup.label, list: list }; } }); }; Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) { var key = e.target.form.preset.value; if (!key) { return; } // e.target.form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || key; Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]); // Twinkle.block.callback.change_template(e); }; Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) { var expiry = e.target.form.expiry; if (e.target.value === 'custom') { Morebits.quickForm.setElementVisibility(expiry.parentNode, true); } else { Morebits.quickForm.setElementVisibility(expiry.parentNode, false); expiry.value = e.target.value; } }; Twinkle.block.seeAlsos = []; Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() { var reason = this.form.reason.value.replace( new RegExp('(<!-- )(参见|參見)' + Twinkle.block.seeAlsos.join('、') + '( -->)?'), '' ); Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) { return el !== this.value; }.bind(this)); if (this.checked) { Twinkle.block.seeAlsos.push(this.value); } var seeAlsoMessage = Twinkle.block.seeAlsos.join('、'); if (!Twinkle.block.seeAlsos.length) { this.form.reason.value = reason; } else { this.form.reason.value = reason + '<!-- ' + wgULS('参见', '參見') + seeAlsoMessage + ' -->'; } }; Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) { var form = e.target.form, expiry = data.expiry; // don't override original expiry if useInitialOptions is set if (!data.useInitialOptions) { if (Date.parse(expiry)) { expiry = new Date(expiry).toGMTString(); form.expiry_preset.value = 'custom'; } else { form.expiry_preset.value = data.expiry || 'custom'; } form.expiry.value = expiry; if (form.expiry_preset.value === 'custom') { Morebits.quickForm.setElementVisibility(form.expiry.parentNode, true); } else { Morebits.quickForm.setElementVisibility(form.expiry.parentNode, false); } } // boolean-flipped options, more at [[mw:API:Block]] data.disabletalk = data.disabletalk !== undefined ? data.disabletalk : false; data.hardblock = data.hardblock !== undefined ? data.hardblock : false; // disable autoblock if blocking a bot if (Twinkle.block.isRegistered && relevantUserName.search(/bot$/i) > 0) { data.autoblock = false; } $(form.field_block_options).find(':checkbox').each(function(i, el) { // don't override original options if useInitialOptions is set if (data.useInitialOptions && data[el.name] === undefined) { return; } var check = data[el.name] === '' || !!data[el.name]; $(el).prop('checked', check); }); if (data.prependReason && data.reason) { form.reason.value = data.reason + '; ' + form.reason.value; } else { form.reason.value = data.reason || ''; } }; Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) { var form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value]; if (!$(form).find('[name=actiontype][value=block]').is(':checked')) { if (settings.indefinite || settings.nonstandard) { if (Twinkle.block.prev_template_expiry === null) { Twinkle.block.prev_template_expiry = form.template_expiry.value || ''; } form.template_expiry.parentNode.style.display = 'none'; form.template_expiry.value = 'indefinite'; } else if (form.template_expiry.parentNode.style.display === 'none') { if (Twinkle.block.prev_template_expiry !== null) { form.template_expiry.value = Twinkle.block.prev_template_expiry; Twinkle.block.prev_template_expiry = null; } form.template_expiry.parentNode.style.display = 'block'; } if (Twinkle.block.prev_template_expiry) { form.expiry.value = Twinkle.block.prev_template_expiry; } // Morebits.quickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard); } else { Morebits.quickForm.setElementVisibility( form.blank_duration.parentNode, !settings.indefinite && !settings.nonstandard ); } Morebits.quickForm.setElementVisibility(form.article.parentNode, !!settings.pageParam); Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, !!settings.reasonParam); form.block_reason.value = settings.reason || ''; form.root.previewer.closePreview(); }; Twinkle.block.prev_template_expiry = null; Twinkle.block.prev_block_reason = null; Twinkle.block.prev_article = null; Twinkle.block.prev_reason = null; Twinkle.block.callback.change_tag = function twinkleblockcallbackChangeTag(e) { var form = e.target.form, value = form.tag.value; if (value === 'spp') { form.username.parentNode.style.display = 'block'; } else { form.username.parentNode.style.display = 'none'; } }; Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) { var params = { article: form.article.value, blank_duration: form.blank_duration ? form.blank_duration.checked : false, disabletalk: form.disabletalk.checked || (form.notalk ? form.notalk.checked : false), expiry: form.template_expiry ? form.template_expiry.value : form.expiry.value, hardblock: Twinkle.block.isRegistered ? form.autoblock.checked : form.hardblock.checked, indefinite: (/indef|infinity|never|\*|max/).test(form.template_expiry ? form.template_expiry.value : form.expiry.value), reason: form.block_reason.value, template: form.template.value.split(':', 1)[0] }; var templateText = Twinkle.block.callback.getBlockNoticeWikitext(params); form.previewer.beginRender(templateText); }; Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) { var $form = $(e.target), toBlock = $form.find('[name=actiontype][value=block]').is(':checked'), toWarn = $form.find('[name=actiontype][value=template]').is(':checked'), toTag = $form.find('[name=actiontype][value=tag]').is(':checked'), toProtect = $form.find('[name=actiontype][value=protect]').is(':checked'), toUnblock = $form.find('[name=actiontype][value=unblock]').is(':checked'), blockoptions = {}, templateoptions = {}, unblockoptions = {}, tagprotectoptions = {}; Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]')); Twinkle.block.callback.saveFieldset($form.find('[name=field_template_options]')); Twinkle.block.callback.saveFieldset($form.find('[name=field_tag_options]')); Twinkle.block.callback.saveFieldset($form.find('[name=field_unblock_options]')); blockoptions = Twinkle.block.field_block_options; unblockoptions = Twinkle.block.field_unblock_options; tagprotectoptions = Twinkle.block.field_tag_options; templateoptions = Twinkle.block.field_template_options; templateoptions.disabletalk = !!(templateoptions.disabletalk || blockoptions.disabletalk); templateoptions.hardblock = !!blockoptions.hardblock; tagprotectoptions.istag = toTag; tagprotectoptions.isprotect = toProtect; // remove extraneous delete blockoptions.expiry_preset; // use block settings as warn options where not supplied templateoptions.summary = templateoptions.summary || blockoptions.reason; templateoptions.expiry = templateoptions.template_expiry || blockoptions.expiry; if (toBlock) { if (!blockoptions.expiry) { return alert(wgULS('请提供过期时间!', '請提供過期時間!')); } if (!blockoptions.reason) { return alert(wgULS('请提供封禁理由!', '請提供封鎖理由!')); } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); var statusElement = new Morebits.status(wgULS('执行封禁', '執行封鎖')); blockoptions.action = 'block'; blockoptions.tags = Twinkle.changeTags; blockoptions.user = Morebits.relevantUserName(true); // boolean-flipped options blockoptions.anononly = blockoptions.hardblock ? undefined : true; blockoptions.allowusertalk = blockoptions.disabletalk ? undefined : true; // fix for bug with block API, see [[phab:T68646]] if (blockoptions.expiry === 'infinity') { blockoptions.expiry = 'infinite'; } // execute block api.getToken('block').then(function(token) { statusElement.status(wgULS('处理中…', '處理中…')); blockoptions.token = token; var mbApi = new Morebits.wiki.api(wgULS('执行封禁', '執行封鎖'), blockoptions, function() { statusElement.info('完成'); }); mbApi.post(); }, function() { statusElement.error(wgULS('未能抓取封禁令牌', '未能抓取封鎖權杖')); }); } if (toWarn) { Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); if (Morebits.ip.isRange(Morebits.relevantUserName(true))) { new Morebits.status(wgULS('信息', '資訊'), wgULS('由于封禁目标为IP段,加入封禁模板已略过', '由於封禁目標為IP段,加入封鎖模板已略過'), 'warn'); } else if (mw.util.isIPAddress(Morebits.relevantUserName(true), false)) { new Morebits.status(wgULS('信息', '資訊'), wgULS('由于封禁目标为IP地址,加入封禁模板已略过', '由於封禁目標為IP位址,加入封鎖模板已略過'), 'warn'); } else { Twinkle.block.callback.issue_template(templateoptions); } } if (toTag || toProtect) { Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); var userPage = 'User:' + Morebits.relevantUserName(true); var wikipedia_page = new Morebits.wiki.page(userPage, wgULS('标记或保护用户页', '標記或保護用戶頁')); wikipedia_page.setCallbackParameters(tagprotectoptions); wikipedia_page.load(Twinkle.block.callback.taguserpage); } if (toUnblock) { if (!unblockoptions.reason) { return alert(wgULS('请提供解除封禁理由!', '請提供解除封鎖理由!')); } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); var unblockStatusElement = new Morebits.status(wgULS('执行解除封禁', '執行解除封鎖')); unblockoptions.action = 'unblock'; unblockoptions.tags = Twinkle.changeTags; unblockoptions.user = Morebits.relevantUserName(true); api.getToken('block').then(function(token) { unblockStatusElement.status(wgULS('处理中…', '處理中…')); unblockoptions.token = token; var mbApi = new Morebits.wiki.api(wgULS('执行解除封禁', '執行解除封鎖'), unblockoptions, function() { unblockStatusElement.info('完成'); }); mbApi.post(); }, function() { unblockStatusElement.error(wgULS('未能抓取封禁令牌', '未能抓取封鎖權杖')); }); } if (!toBlock && !toWarn && !toTag && !toProtect && !toUnblock) { return alert(wgULS('请给Twinkle点事做!', '請給Twinkle點事做!')); } }; Twinkle.block.callback.taguserpage = function twinkleblockCallbackTagUserpage(pageobj) { var params = pageobj.getCallbackParameters(); // var statelem = pageobj.getStatusElement(); if (params.istag) { var pagetext = ''; switch (params.tag) { case 'indef': pagetext = '{{indef}}'; break; case 'spp': var username = params.username.trim(); if (!username) { return alert(wgULS('请给主账户用户名!', '請給主帳號用戶名!')); } pagetext = '{{spp|' + username + '}}'; break; case 'spm': pagetext = '{{Sockpuppeteer|blocked}}'; break; default: return alert(wgULS('未知的用户页模板!', '未知的用戶頁模板!')); } pageobj.setPageText(pagetext); pageobj.setEditSummary(wgULS('标记被永久封禁的用户页', '標記被永久封鎖的用戶頁')); pageobj.setChangeTags(Twinkle.changeTags); pageobj.save(function() { Morebits.status.info(wgULS('标记用户页', '標記用戶頁'), '完成'); pageobj.load(Twinkle.block.callback.protectuserpage); }); } else { Twinkle.block.callback.protectuserpage(pageobj); } }; Twinkle.block.callback.protectuserpage = function twinkleblockCallbackProtectUserpage(pageobj) { var params = pageobj.getCallbackParameters(); // var statelem = pageobj.getStatusElement(); if (params.isprotect) { if (pageobj.exists()) { pageobj.setEditProtection('sysop', 'indefinite'); pageobj.setMoveProtection('sysop', 'indefinite'); } else { pageobj.setCreateProtection('sysop', 'indefinite'); } pageobj.setEditSummary(wgULS('被永久封禁的用户页', '被永久封鎖的用戶頁')); pageobj.setChangeTags(Twinkle.changeTags); pageobj.protect(function() { Morebits.status.info(wgULS('保护用户页', '保護用戶頁'), pageobj.exists() ? wgULS('已全保护', '已全保護') : wgULS('已白纸保护', '已白紙保護')); }); } }; Twinkle.block.callback.issue_template = function twinkleblockCallbackIssueTemplate(formData) { var userTalkPage = 'User_talk:' + Morebits.relevantUserName(true); var params = $.extend(formData, { messageData: Twinkle.block.blockPresetsInfo[formData.template], reason: Twinkle.block.field_template_options.block_reason, disabletalk: Twinkle.block.field_template_options.notalk }); params.template = params.template.split(':', 1)[0]; Morebits.wiki.actionCompleted.redirect = userTalkPage; Morebits.wiki.actionCompleted.notice = wgULS('完成,将在几秒后加载用户对话页', '完成,將在幾秒後載入用戶對話頁'); var wikipedia_page = new Morebits.wiki.page(userTalkPage, wgULS('用户对话页修改', '用戶對話頁修改')); wikipedia_page.setCallbackParameters(params); wikipedia_page.setFollowRedirect(true); wikipedia_page.load(Twinkle.block.callback.main); }; Twinkle.block.formatBlockTime = function twinkleblockFormatBlockTime(time) { var m; if ((m = time.match(/^\s*(\d+)\s*seconds?\s*$/)) !== null) { return m[1] + '秒'; } if ((m = time.match(/^\s*(\d+)\s*min(ute)?s?\s*$/)) !== null) { return m[1] + '分'; } if ((m = time.match(/^\s*(\d+)\s*hours?\s*$/)) !== null) { return m[1] + '小時'; } if ((m = time.match(/^\s*(\d+)\s*days?\s*$/)) !== null) { return m[1] + '天'; } if ((m = time.match(/^\s*(\d+)\s*weeks?\s*$/)) !== null) { return m[1] + '週'; } if ((m = time.match(/^\s*(\d+)\s*months?\s*$/)) !== null) { return m[1] + '月'; } if ((m = time.match(/^\s*(\d+)\s*years?\s*$/)) !== null) { return m[1] + '年'; } return time; }; Twinkle.block.callback.getBlockNoticeWikitext = function(params, nosign) { var text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template]; if (!settings.nonstandard) { text += 'subst:' + params.template; if (params.article && settings.pageParam) { text += '|page=' + params.article; } if (!/te?mp|^\s*$|min/.exec(params.expiry)) { if (params.indefinite) { text += '|indef=yes'; } else if (!params.blank_duration) { text += '|time=' + Twinkle.block.formatBlockTime(params.expiry); } } if (!Twinkle.block.isRegistered && !params.hardblock) { text += '|anon=yes'; } if (params.reason) { text += '|reason=' + params.reason; } if (params.disabletalk) { text += '|notalk=yes'; } text += '|subst=subst:'; } else { text += params.template; } if ((settings.sig === '~~~~' || settings.sig === undefined) && !nosign) { text += '}}--~~~~'; } else if (settings.sig && !nosign) { text += '|sig=' + settings.sig; text += '}}'; } else { text += '}}'; } return text; }; Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) { var text = pageobj.getPageText(), params = pageobj.getCallbackParameters(), messageData = params.messageData, date = new Date(pageobj.getLoadTime()); var dateHeaderRegex = new RegExp('^==+\\s*' + date.getUTCFullYear() + '年' + (date.getUTCMonth() + 1) + '月' + '\\s*==+', 'mg'); var dateHeaderRegexLast, dateHeaderRegexResult; while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) { dateHeaderRegexResult = dateHeaderRegexLast; } // If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but // \n== is not found, then the date header must be at the very start of the page. lastIndexOf // returns -1 in this case, so lastHeaderIndex gets set to 0 as desired. var lastHeaderIndex = text.lastIndexOf('\n==') + 1; if (text.length > 0) { text += '\n\n'; } params.indefinite = (/indef|infinity|never|\*|max/).test(params.expiry); if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) { Morebits.status.info('信息', wgULS('根据参数设置清空讨论页并为日期创建新2级标题', '根據偏好設定清空討論頁並為日期創建新2級標題')); text = '== ' + date.getUTCFullYear() + '年' + (date.getUTCMonth() + 1) + '月 ' + ' ==\n'; } else if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) { Morebits.status.info('信息', wgULS('未找到当月标题,将创建新的', '未找到當月標題,將建立新的')); text += '== ' + date.getUTCFullYear() + '年' + (date.getUTCMonth() + 1) + '月 ' + ' ==\n'; } params.expiry = typeof params.template_expiry !== 'undefined' ? params.template_expiry : params.expiry; text += Twinkle.block.callback.getBlockNoticeWikitext(params); // build the edit summary var templateName = messageData.templateName || messageData.template || messageData.value; var summary = '{{' + templateName + '}}: ' + params.reason; if (messageData.suppressArticleInSummary !== true && params.article) { summary += wgULS(',于[[', ',於[[') + params.article + ']]'; } pageobj.setPageText(text); pageobj.setEditSummary(summary); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setWatchlist(Twinkle.getPref('watchBlockNotices')); pageobj.save(); }; })(jQuery); // </nowiki> 6q6acotyk2qo1za2g250a6k8s47n179 MediaWiki:Gadget-twinkleclose.js 8 1368867 9943043 8649589 2026-10-12T15:41:00Z Xiplus 25402 Repo at 375b1e1: remove trailing pipe (#397); sysops from deselecting noop in the deletion options; never display the actual deletion option to non-sysops; Re-enable on vector-2022 (#317); Ignore h2 has h3; Fix disableLink 9943043 javascript text/javascript // <nowiki> (function($) { /* **************************************** *** twinkleclose.js: XFD closing module **************************************** * Mode of invocation: Links after section heading * Active on: AfD dated archive pages * Config directives in: TwinkleConfig */ Twinkle.close = function twinkleclose() { if (Twinkle.getPref('XfdClose') === 'hide' || mw.config.get('wgPageName') !== 'Wiktionary:删除请求') { return; } mw.hook('wikipage.content').add(function(item) { if (item.attr('id') === 'mw-content-text') { Twinkle.close.addLinks(); } }); }; Twinkle.close.addLinks = function twinklecloseAddLinks() { $('.mw-heading.mw-heading1, .mw-heading.mw-heading2, .mw-heading.mw-heading3, .mw-heading.mw-heading4, .mw-heading.mw-heading5, .mw-heading.mw-heading6', '#bodyContent').each(function (index, current) { current.setAttribute('data-section', index + 1); }); var selector = ':has(a:only-of-type):not(:has(+ div.NavFrame))'; var titles = $('#bodyContent').find('.mw-heading3' + selector + ':not(:has(+ p + div.mw-heading.mw-heading4)), .mw-heading4' + selector); // really needs to work on titles.each(function(key, current) { var headlinehref = $(current).find('a').attr('href'); if (headlinehref === undefined) { return; } var title = null; if (headlinehref.indexOf('redlink=1') !== -1) { title = headlinehref.slice(19, -22); } else { var m = headlinehref.match(/\/wiki\/([^?]+)/, '$1'); if (m !== null) { title = m[1]; } } if (title === null) { return; } title = decodeURIComponent(title); title = title.replace(/_/g, ' '); // Normalize for using in interface and summary var pagenotexist = $(current).find('a').hasClass('new'); var section = current.getAttribute('data-section'); var node = current.getElementsByClassName('mw-editsection')[0]; var delLink = document.createElement('a'); delLink.className = 'twinkle-close-button'; delLink.href = '#'; delLink.setAttribute('data-section', section); delLink.innerText = wgULS('关闭讨论', '關閉討論'); $(delLink).click(function() { Twinkle.close.callback(title, section, pagenotexist); return false; }); node.insertBefore(delLink, node.childNodes[1]); }); }; // Keep this synchronized with {{delh}} Twinkle.close.codes = wgULS({ '请求无效': { ir: { label: '请求无效', action: 'keep' }, rep: { label: '重复提出,无效', action: 'keep' }, commons: { label: '应在维基共享资源提请', action: 'keep' }, ne: { label: '目标页面或档案不存在,无效', action: 'keep' }, nq: { label: '提删者未取得提删资格,无效', action: 'keep' } }, '保留': { k: { label: '保留', action: 'keep', adminonly: true }, sk: { label: '快速保留', action: 'keep' }, rr: { label: '请求理由消失', action: 'keep', selected: Twinkle.getPref('XfdClose') === 'nonadminonly' }, dan: { label: '删后重建', action: 'keep', adminonly: true } }, '删除': { d: { label: '删除', action: 'del', adminonly: true, selected: Twinkle.getPref('XfdClose') === 'all' }, ic: { label: '图像因侵权被删', action: 'del', adminonly: true } }, '快速删除': { sd: { label: '快速删除', action: 'del' }, lssd: { label: '无来源或版权资讯,快速删除', action: 'del' }, svg: { label: '已改用SVG图形,快速删除', action: 'del' }, nowcommons: { label: '维基共享资源已提供,快速删除', action: 'del' }, drep: { label: '多次被删除,条目锁定', action: 'del', adminonly: true } }, '转移至其他维基计划': { twc: { label: '转移至维基共享资源', action: 'noop', adminonly: true }, twn: { label: '转移至维基新闻', action: 'noop', adminonly: true }, tws: { label: '转移至维基文库', action: 'noop', adminonly: true }, twb: { label: '转移至维基教科书', action: 'noop', adminonly: true }, twq: { label: '转移至维基语录', action: 'noop', adminonly: true }, tww: { label: '转移至维基百科', action: 'noop', adminonly: true }, twvoy: { label: '转移至维基导游', action: 'noop', adminonly: true }, two: { label: '转移至其他维基计划', action: 'noop', adminonly: true } }, '其他处理方法': { r: { label: '重定向', action: 'keep', adminonly: true }, cr: { label: '分类重定向', action: 'keep', adminonly: true }, m: { label: '移动', action: 'keep', adminonly: true }, merge: { label: '并入', action: 'keep', adminonly: true }, nc: { label: '无共识', action: 'keep' } } }, { '請求無效': { ir: { label: '請求無效', action: 'keep' }, rep: { label: '重複提出,無效', action: 'keep' }, commons: { label: '應在維基共享資源提請', action: 'keep' }, ne: { label: '目標頁面或檔案不存在,無效', action: 'keep' }, nq: { label: '提刪者未取得提刪資格,無效', action: 'keep' } }, '保留': { k: { label: '保留', action: 'keep', adminonly: true }, sk: { label: '快速保留', action: 'keep' }, rr: { label: '請求理由消失', action: 'keep', selected: Twinkle.getPref('XfdClose') === 'nonadminonly' }, dan: { label: '刪後重建', action: 'keep', adminonly: true } }, '刪除': { d: { label: '刪除', action: 'del', adminonly: true, selected: Twinkle.getPref('XfdClose') === 'all' }, ic: { label: '圖像因侵權被刪', action: 'del', adminonly: true } }, '快速刪除': { sd: { label: '快速刪除', action: 'del' }, lssd: { label: '無來源或版權資訊,快速刪除', action: 'del' }, svg: { label: '已改用SVG圖形,快速刪除', action: 'del' }, nowcommons: { label: '維基共享資源已提供,快速刪除', action: 'del' }, drep: { label: '多次被刪除,條目鎖定', action: 'del', adminonly: true } }, '轉移至其他維基計劃': { twc: { label: '轉移至維基共享資源', action: 'noop', adminonly: true }, twn: { label: '轉移至維基新聞', action: 'noop', adminonly: true }, tws: { label: '轉移至維基文庫', action: 'noop', adminonly: true }, twb: { label: '轉移至維基教科書', action: 'noop', adminonly: true }, twq: { label: '轉移至維基語錄', action: 'noop', adminonly: true }, tww: { label: '轉移至維基百科', action: 'noop', adminonly: true }, twvoy: { label: '轉移至維基導遊', action: 'noop', adminonly: true }, two: { label: '轉移至其他維基計劃', action: 'noop', adminonly: true } }, '其他處理方法': { r: { label: '重定向', action: 'keep', adminonly: true }, cr: { label: '分類重定向', action: 'keep', adminonly: true }, m: { label: '移動', action: 'keep', adminonly: true }, merge: { label: '併入', action: 'keep', adminonly: true }, nc: { label: '無共識', action: 'keep' } } }); /* eslint-enable quote-props */ Twinkle.close.callback = function twinklecloseCallback(title, section, noop) { var Window = new Morebits.simpleWindow(400, 200); Window.setTitle(wgULS('关闭删除请求', '關閉刪除請求') + ' \u00B7 ' + title); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'WP:TW/DOC#close'); var form = new Morebits.quickForm(Twinkle.close.callback.evaluate); form.append({ type: 'select', label: wgULS('处理结果:', '處理結果:'), name: 'sub_group', event: Twinkle.close.callback.change_code }); if (Morebits.userIsSysop) { form.append({ type: 'input', name: 'sdreason', label: wgULS('速删理由:', '速刪理由:'), tooltip: wgULS('用于删除日志,使用{{delete}}的参数格式,例如 A1 或 A1|G1', '用於刪除日誌,使用{{delete}}的參數格式,例如 A1 或 A1|G1'), hidden: true }); } form.append({ type: 'input', name: 'remark', label: wgULS('补充说明:', '補充說明:') }); form.append({ type: 'checkbox', list: [ { label: wgULS('只关闭讨论,不进行其他操作', '只關閉討論,不進行其他操作'), value: 'noop', name: 'noop', event: Twinkle.close.callback.change_operation, checked: noop } ] }); if (Morebits.userIsSysop) { if (new mw.Title(title).namespace % 2 === 0 && new mw.Title(title).namespace !== 2) { // hide option for user pages, to avoid accidentally deleting user talk page form.append({ type: 'checkbox', list: [ { label: wgULS('删除关联的讨论页', '刪除關聯的討論頁'), value: 'talkpage', name: 'talkpage', tooltip: wgULS('删除时附带删除此页面的讨论页。', '刪除時附帶刪除此頁面的討論頁。'), checked: true, event: function(event) { event.stopPropagation(); } } ] }); } form.append({ type: 'checkbox', list: [ { label: wgULS('删除重定向页', '刪除重新導向頁面'), value: 'redirects', name: 'redirects', tooltip: wgULS('删除到此页的重定向。', '刪除到此頁的重新導向。'), checked: true, event: function(event) { event.stopPropagation(); } } ] }); } form.append({ type: 'submit' }); var result = form.render(); Window.setContent(result); Window.display(); var sub_group = result.getElementsByTagName('select')[0]; // hack var resultData = { title: title, section: parseInt(section), noop: noop }; $(result).data('resultData', resultData); // worker function to create the combo box entries var createEntries = function(contents, container) { $.each(contents, function(itemKey, itemProperties) { var key = typeof itemKey === 'string' ? itemKey : itemProperties.value; var elem = new Morebits.quickForm.element({ type: 'option', label: key + ':' + itemProperties.label, value: key, selected: itemProperties.selected, disabled: Twinkle.getPref('XfdClose') !== 'all' && itemProperties.adminonly }); var elemRendered = container.appendChild(elem.render()); $(elemRendered).data('messageData', itemProperties); }); }; $.each(Twinkle.close.codes, function(groupLabel, groupContents) { var optgroup = new Morebits.quickForm.element({ type: 'optgroup', label: groupLabel }); optgroup = optgroup.render(); sub_group.appendChild(optgroup); // create the options createEntries(groupContents, optgroup); }); var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.sub_group.dispatchEvent(evt); }; Twinkle.close.callback.change_operation = function twinklecloseCallbackChangeOperation(e) { var noop = e.target.checked; var code = e.target.form.sub_group.value; var messageData = $(e.target.form.sub_group).find('option[value="' + code + '"]').data('messageData'); var talkpage = e.target.form.talkpage; var redirects = e.target.form.redirects; if (noop || messageData.action === 'keep') { if (talkpage) { talkpage.checked = false; talkpage.disabled = true; } if (redirects) { redirects.checked = false; redirects.disabled = true; } } else { if (talkpage) { talkpage.checked = true; talkpage.disabled = false; } if (redirects) { redirects.checked = true; redirects.disabled = false; } } }; Twinkle.close.callback.change_code = function twinklecloseCallbackChangeCode(e) { var resultData = $(e.target.form).data('resultData'); var messageData = $(e.target).find('option[value="' + e.target.value + '"]').data('messageData'); var noop = e.target.form.noop; var sdreason = e.target.form.sdreason; var talkpage = e.target.form.talkpage; var redirects = e.target.form.redirects; if (resultData.noop || messageData.action === 'noop') { noop.checked = true; noop.disabled = true; if (talkpage) { talkpage.checked = false; talkpage.disabled = true; } if (redirects) { redirects.checked = false; redirects.disabled = true; } } else { var isKeepAction = messageData.action === 'keep'; if (Morebits.userIsSysop) { noop.checked = false; noop.disabled = false; if (talkpage) { talkpage.checked = !isKeepAction; talkpage.disabled = isKeepAction; } if (redirects) { redirects.checked = !isKeepAction; redirects.disabled = isKeepAction; } } else { noop.checked = !isKeepAction; noop.disabled = !isKeepAction; } if (sdreason) { if (e.target.value === 'sd') { sdreason.parentElement.removeAttribute('hidden'); } else { sdreason.parentElement.setAttribute('hidden', ''); } } } }; Twinkle.close.callback.evaluate = function twinklecloseCallbackEvaluate(e) { var code = e.target.sub_group.value; var resultData = $(e.target).data('resultData'); var messageData = $(e.target.sub_group).find('option[value="' + code + '"]').data('messageData'); var noop = e.target.noop.checked; var sdreason = e.target.sdreason ? e.target.sdreason.value : ''; var talkpage = e.target.talkpage && e.target.talkpage.checked; var redirects = e.target.redirects && e.target.redirects.checked; var params = { title: resultData.title, code: code, remark: e.target.remark.value, sdreason: sdreason, section: resultData.section, messageData: messageData, talkpage: talkpage, redirects: redirects }; Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); Morebits.wiki.actionCompleted.notice = '操作完成'; if (noop || messageData.action === 'noop') { Twinkle.close.callbacks.talkend(params); } else { switch (messageData.action) { case 'del': Twinkle.close.callbacks.del(params); break; case 'keep': var wikipedia_page = new Morebits.wiki.page(params.title, wgULS('移除删除请求模板', '移除刪除請求模板')); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.close.callbacks.keep); break; default: alert('Twinkle.close:未定义 ' + code); } } }; Twinkle.close.callbacks = { del: function (params) { var query, wikipedia_api; Morebits.wiki.addCheckpoint(); var page = new Morebits.wiki.page(params.title, wgULS('删除页面', '刪除頁面')); if (params.code === 'sd') { Twinkle.speedy.callbacks.parseWikitext(params.title, '{{delete|' + params.sdreason + '}}', function(reason) { reason = prompt(wgULS('输入删除理由,或点击确定以接受自动生成的:', '輸入刪除理由,或點選確定以接受自動生成的:'), reason); if (reason === null) { page.getStatusElement().warn(wgULS('没有执行删除', '沒有執行刪除')); Twinkle.close.callbacks.talkend(params); } else { page.setEditSummary(reason); page.setChangeTags(Twinkle.changeTags); page.deletePage(function() { page.getStatusElement().info('完成'); Twinkle.close.callbacks.talkend(params); }); } }); } else { page.setEditSummary('[[Special:PermaLink/' + mw.config.get('wgCurRevisionId') + '|' + wgULS('删除请求通过', '刪除請求通過') + ']]'); page.setChangeTags(Twinkle.changeTags); page.deletePage(function() { page.getStatusElement().info('完成'); Twinkle.close.callbacks.talkend(params); }); } if (params.redirects) { query = { action: 'query', titles: params.title, prop: 'redirects', rdlimit: 'max' // 500 is max for normal users, 5000 for bots and sysops }; wikipedia_api = new Morebits.wiki.api(wgULS('正在获取重定向', '正在取得重新導向'), query, Twinkle.close.callbacks.deleteRedirectsMain); wikipedia_api.params = params; wikipedia_api.post(); } if (params.talkpage) { var pageTitle = mw.Title.newFromText(params.title); if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) { pageTitle.namespace++; // now pageTitle is the talk page title! query = { action: 'query', titles: pageTitle.toText() }; wikipedia_api = new Morebits.wiki.api(wgULS('正在检查讨论页面是否存在', '正在檢查討論頁面是否存在'), query, Twinkle.close.callbacks.deleteTalk); wikipedia_api.params = params; wikipedia_api.params.talkPage = pageTitle.toText(); wikipedia_api.post(); } } Morebits.wiki.removeCheckpoint(); }, deleteRedirectsMain: function(apiobj) { var xml = apiobj.responseXML; var pages = $(xml).find('rd').map(function() { return $(this).attr('title'); }).get(); if (!pages.length) { return; } var redirectDeleter = new Morebits.batchOperation(wgULS('正在删除到 ', '正在刪除到 ') + apiobj.params.title + wgULS(' 的重定向', ' 的重新導向')); redirectDeleter.setOption('chunkSize', Twinkle.getPref('batchdeleteChunks')); redirectDeleter.setPageList(pages); redirectDeleter.run(function(pageName) { var wikipedia_page = new Morebits.wiki.page(pageName, wgULS('正在删除 ', '正在刪除 ') + pageName); wikipedia_page.setEditSummary('[[WT:CSD|G15]]: ' + wgULS('指向已删页面“', '指向已刪頁面「') + apiobj.params.title + wgULS('”的重定向', '」的重新導向')); wikipedia_page.setChangeTags(Twinkle.changeTags); wikipedia_page.deletePage(redirectDeleter.workerSuccess, redirectDeleter.workerFailure); }); }, deleteTalk: function(apiobj) { var xml = apiobj.responseXML; var exists = $(xml).find('page:not([missing])').length > 0; if (!exists) { // no talk page; forget about it return; } var page = new Morebits.wiki.page(apiobj.params.talkPage, wgULS('正在删除页面 ', '正在刪除頁面 ') + apiobj.params.title + wgULS(' 的讨论页', ' 的討論頁')); page.setEditSummary('[[WT:CSD|G15]]: ' + wgULS('已删页面“', '已刪頁面「') + apiobj.params.title + wgULS('”的讨论页', '」的討論頁')); page.setChangeTags(Twinkle.changeTags); page.deletePage(); }, keep: function (pageobj) { var statelem = pageobj.getStatusElement(); if (!pageobj.exists()) { statelem.error(wgULS('页面不存在,可能已被删除', '頁面不存在,可能已被刪除')); return; } var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); var newtext = text.replace(/<noinclude>\s*\{\{([rsaiftcmv]fd)(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*<\/noinclude>\s*/gi, ''); newtext = newtext.replace(/\{\{([rsaiftcmv]fd)(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/gi, ''); if (newtext === text) { statelem.warn(wgULS('未找到删除请求模板,可能已被移除', '未找到刪除請求模板,可能已被移除')); Twinkle.close.callbacks.talkend(params); return; } var editsummary = '[[Special:PermaLink/' + mw.config.get('wgCurRevisionId') + '|' + wgULS('删除请求关闭', '刪除請求關閉') + ']]'; pageobj.setPageText(newtext); pageobj.setEditSummary(editsummary); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setCreateOption('nocreate'); pageobj.save(Twinkle.close.callbacks.keepComplete); }, keepComplete: function (pageobj) { var params = pageobj.getCallbackParameters(); Twinkle.close.callbacks.talkend(params); }, talkend: function (params) { var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), wgULS('关闭讨论', '關閉討論')); wikipedia_page.setCallbackParameters(params); wikipedia_page.setPageSection(params.section); wikipedia_page.load(Twinkle.close.callbacks.saveTalk); }, saveTalk: function (pageobj) { var statelem = pageobj.getStatusElement(); var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); if (text.indexOf('{{delh') !== -1) { statelem.error(wgULS('讨论已被关闭', '討論已被關閉')); return; } var sbegin = text.indexOf('<section begin=backlog />') !== -1; var send = text.indexOf('<section end=backlog />') !== -1; text = text.replace('\n<section begin=backlog />', ''); text = text.replace('\n<section end=backlog />', ''); var bar = text.split('\n----\n'); var split = bar[0].split('\n'); text = split[0] + '\n{{delh|' + params.code + '}}\n' + split.slice(1).join('\n'); text += '\n<hr>\n: ' + params.messageData.label; if (params.remark) { text += ':' + params.remark; } else { text += '。'; } text += '--~~~~\n{{delf}}'; if (bar[1]) { text += '\n----\n' + bar.slice(1).join('\n----\n'); } if (send) { text += '\n<section end=backlog />'; } if (sbegin) { // guaranteed to be at tne end? text += '\n<section begin=backlog />'; } pageobj.setPageText(text); pageobj.setEditSummary('/* ' + params.title + ' */ ' + params.messageData.label); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setCreateOption('nocreate'); pageobj.save(Twinkle.close.callbacks.disableLink); }, disableLink: function (pageobj) { var params = pageobj.getCallbackParameters(); $('a.twinkle-close-button[data-section=' + params.section + ']').addClass('twinkle-close-button-disabled'); } }; })(jQuery); // </nowiki> 8407wv1jwwpdgdal4ptqos5x2aef7ty MediaWiki:Gadget-twinkleconfig.js 8 1368868 9943039 6328883 2026-10-12T15:40:51Z Xiplus 25402 Repo at 375b1e1: Replace removed successbox classes; add support for vector-2022 9943039 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { /* **************************************** *** twinkleconfig.js: Preferences module **************************************** * Mode of invocation: Adds configuration form to Wikipedia:Twinkle/Preferences and user subpages named "/Twinkle preferences", and adds ad box to the top of user subpages belonging to the currently logged-in user which end in '.js' * Active on: What I just said. Yeah. I, [[User:This, that and the other]], originally wrote this. If the code is misbehaving, or you have any questions, don't hesitate to ask me. (This doesn't at all imply [[WP:OWN]]ership - it's just meant to point you in the right direction.) -- TTO */ Twinkle.config = {}; Twinkle.config.watchlistEnums = wgULS({ yes: '添加到监视列表', no: '不添加到监视列表', 'default': '遵守站点设置' }, { yes: '加入到監視清單', no: '不加入到監視清單', 'default': '遵守站點設定' }); Twinkle.config.commonSets = { csdCriteria: { db: wgULS('自定义理由', '自訂理由'), g1: 'G1', g2: 'G2', g3: 'G3', g5: 'G5', g7: 'G7', g9: 'G9', g10: 'G10', g11: 'G11', g13: 'G13', g15: 'G15', a1: 'a1', o1: 'O1', o3: 'O3', o4: 'O4', r2: 'R2', r3: 'R3' }, csdCriteriaDisplayOrder: [ 'db', 'g1', 'g2', 'g3', 'g5', 'g7', 'g9', 'g10', 'g11', 'g13', 'g15', 'a1', 'o1', 'o3', 'o4', 'r2', 'r3' ], csdCriteriaNotification: { db: wgULS('自定义理由', '自訂理由'), g1: 'G1', g2: 'G2', g3: 'G3', g5: 'G5', g7: 'G7', g9: 'G9', g10: 'G10', g11: 'G11', g13: 'G13', g15: 'G15', o1: 'O1', o3: 'O3', o4: 'O4', r2: 'R2', r3: 'R3' }, csdCriteriaNotificationDisplayOrder: [ 'db', 'g1', 'g2', 'g3', 'g5', 'g7', 'g9', 'g10', 'g11', 'g13', 'g15', 'a1', 'o1', 'o3', 'o4', 'r2', 'r3' ], csdAndDICriteria: { db: wgULS('自定义理由', '自訂理由'), g1: 'G1', g2: 'G2', g3: 'G3', g5: 'G5', g7: 'G7', g9: 'G9', g10: 'G10', g11: 'G11', g13: 'G13', g15: 'G15', o1: 'O1', o3: 'O3', o4: 'O4', r2: 'R2', r3: 'R3' }, csdAndDICriteriaDisplayOrder: [ 'db', 'g1', 'g2', 'g3', 'g5', 'g7', 'g9', 'g10', 'g11', 'g13', 'g15', 'a1', 'o1', 'o3', 'o4', 'r2', 'r3' ], namespacesNoSpecial: { '0': wgULS('(条目)', '(條目)'), '1': 'Talk', '2': 'User', '3': 'User talk', '4': 'Wikipedia', '5': 'Wikipedia talk', '6': 'File', '7': 'File talk', '8': 'MediaWiki', '9': 'MediaWiki talk', '10': 'Template', '11': 'Template talk', '12': 'Help', '13': 'Help talk', '14': 'Category', '15': 'Category talk', '100': 'Portal', '101': 'Portal talk', '118': 'Draft', '119': 'Draft talk', '828': 'Module', '829': 'Module talk' } }; /** * Section entry format: * * { * title: <human-readable section title>, * adminOnly: <true for admin-only sections>, * hidden: <true for advanced preferences that rarely need to be changed - they can still be modified by manually editing twinkleoptions.js>, * preferences: [ * { * name: <TwinkleConfig property name>, * label: <human-readable short description - used as a form label>, * helptip: <(optional) human-readable text (using valid HTML) that complements the description, like limits, warnings, etc.> * adminOnly: <true for admin-only preferences>, * type: <string|boolean|integer|enum|set|customList> (customList stores an array of JSON objects { value, label }), * enumValues: <for type = "enum": a JSON object where the keys are the internal names and the values are human-readable strings>, * setValues: <for type = "set": a JSON object where the keys are the internal names and the values are human-readable strings>, * setDisplayOrder: <(optional) for type = "set": an array containing the keys of setValues (as strings) in the order that they are displayed>, * customListValueTitle: <for type = "customList": the heading for the left "value" column in the custom list editor>, * customListLabelTitle: <for type = "customList": the heading for the right "label" column in the custom list editor> * }, * . . . * ] * }, * . . . * */ Twinkle.config.sections = [ { title: wgULS('常规', '常規'), preferences: [ // TwinkleConfig.userTalkPageMode may take arguments: // 'window': open a new window, remember the opened window // 'tab': opens in a new tab, if possible. // 'blank': force open in a new window, even if such a window exists { name: 'userTalkPageMode', label: wgULS('当要开启用户对话页时,', '當要開啟用戶對話頁時,'), type: 'enum', enumValues: wgULS( { window: '在窗口中,替换成其它用户对话页', tab: '在新标签页中', blank: '在全新的窗口中' }, { window: '在當前分頁,替換成其它用戶對話頁', tab: '在新分頁中', blank: '在新視窗中' } ) }, // TwinkleConfig.dialogLargeFont (boolean) { name: 'dialogLargeFont', label: wgULS('在Twinkle对话框中使用大号字体', '在Twinkle對話方塊中使用大號字型'), type: 'boolean' }, // Twinkle.config.disabledModules (array) { name: 'disabledModules', label: wgULS('关闭指定的Twinkle模块', '關閉指定的Twinkle模組'), helptip: wgULS('您在此选择的功能将无法使用,取消选择以重新启用功能。', '您在此選擇的功能將無法使用,取消選擇以重新啟用功能。'), type: 'set', setValues: { arv: wgULS('告状', '告狀'), speedy: wgULS('速删', '速刪'), copyvio: wgULS('侵权', '侵權'), xfd: wgULS('提删', '提刪'), tag: wgULS('标记', '標記'), diff: wgULS('差异', '差異'), 'fluff': '回退' } }, // Twinkle.config.disabledSysopModules (array) { name: 'disabledSysopModules', label: wgULS('关闭指定的Twinkle管理员模块', '關閉指定的Twinkle管理員模組'), helptip: wgULS('您在此选择的功能将无法使用,取消选择以重新启用功能。', '您在此選擇的功能將無法使用,取消選擇以重新啟用功能。'), type: 'set', setValues: { block: wgULS('封禁', '封鎖'), batchdelete: wgULS('批删', '批刪') } } ] }, /* { title: wgULS("图片删除", "圖片刪除"), preferences: [ // TwinkleConfig.notifyUserOnDeli (boolean) // If the user should be notified after placing a file deletion tag { name: "notifyUserOnDeli", label: wgULS("默认勾选“通知创建者”", "預設勾選「通知建立者」"), type: "boolean" }, // TwinkleConfig.deliWatchPage (string) // The watchlist setting of the page tagged for deletion. Either "yes", "no", or "default". Default is "default" (Duh). { name: "deliWatchPage", label: wgULS("标记图片时添加到监视列表", "標記圖片時加入到監視清單"), type: "enum", enumValues: Twinkle.config.commonEnums.watchlist }, // TwinkleConfig.deliWatchUser (string) // The watchlist setting of the user talk page if a notification is placed. Either "yes", "no", or "default". Default is "default" (Duh). { name: "deliWatchUser", label: wgULS("标记图片时添加创建者对话页到监视列表", "標記圖片時加入建立者對話頁到監視清單"), type: "enum", enumValues: Twinkle.config.commonEnums.watchlist } ] }, */ { title: '回退', // twinklefluff module preferences: [ // TwinkleConfig.openTalkPage (array) // What types of actions that should result in opening of talk page { name: 'openTalkPage', label: wgULS('在这些类型的回退后开启用户对话页', '在這些類別的回退後開啟用戶對話頁'), type: 'set', setValues: wgULS({ agf: '善意回退', norm: '常规回退', vand: '破坏回退', torev: '“恢复此版本”' }, { agf: '善意回退', norm: '常規回退', vand: '破壞回退', torev: '「恢復此版本」' }) }, // TwinkleConfig.openTalkPageOnAutoRevert (bool) // Defines if talk page should be opened when calling revert from contrib page, because from there, actions may be multiple, and opening talk page not suitable. If set to true, openTalkPage defines then if talk page will be opened. { name: 'openTalkPageOnAutoRevert', label: wgULS('在从用户贡献中发起回退时开启用户对话页', '在從用戶貢獻中發起回退時開啟用戶對話頁'), helptip: wgULS('您经常会在破坏者的用户贡献中发起许多回退,总是开启用户对话页可能不太适当,所以这个设置默认关闭。当它开启时,依赖上一个设置。', '您經常會在破壞者的用戶貢獻中發起許多回退,總是開啟用戶對話頁可能不太適當,所以這個設定預設關閉。當它開啟時,依賴上一個設定。'), type: 'boolean' }, // TwinkleConfig.markRevertedPagesAsMinor (array) // What types of actions that should result in marking edit as minor { name: 'markRevertedPagesAsMinor', label: wgULS('将这些类型的回退标记为小修改', '將這些類別的回退標記為小修改'), type: 'set', setValues: wgULS({ agf: '善意回退', norm: '常规回退', vand: '破坏回退', torev: '“恢复此版本”' }, { agf: '善意回退', norm: '常規回退', vand: '破壞回退', torev: '「恢復此版本」' }) }, // TwinkleConfig.watchRevertedPages (array) // What types of actions that should result in forced addition to watchlist { name: 'watchRevertedPages', label: wgULS('把这些类型的回退加入监视列表', '把這些類別的回退加入監視清單'), type: 'set', setValues: wgULS({ agf: '善意回退', norm: '常规回退', vand: '破坏回退', torev: '“恢复此版本”' }, { agf: '善意回退', norm: '常規回退', vand: '破壞回退', torev: '「恢復此版本」' }) }, // TwinkleConfig.offerReasonOnNormalRevert (boolean) // If to offer a prompt for extra summary reason for normal reverts, default to true { name: 'offerReasonOnNormalRevert', label: wgULS('常规回退时询问理由', '常規回退時詢問理由'), helptip: wgULS('“常规”回退是中间的那个[回退]链接。', '「常規」回退是中間的那個[回退]連結。'), type: 'boolean' }, { name: 'confirmOnFluff', label: wgULS('回退前要求确认', '回退前要求確認'), helptip: wgULS('给那些手持移动设备的用户,或者意志不坚定的。', '給那些手持行動裝置的用戶,或者意志不堅定的。'), type: 'boolean' }, // TwinkleConfig.showRollbackLinks (array) // Where Twinkle should show rollback links (diff, others, mine, contribs) // Note from TTO: |contribs| seems to be equal to |others| + |mine|, i.e. redundant, so I left it out heres { name: 'showRollbackLinks', label: wgULS('在这些页面上显示回退链接', '在這些頁面上顯示回退連結'), type: 'set', setValues: wgULS({ diff: '差异', history: '历史记录', others: '其它用户的贡献', mine: '我的贡献' }, { diff: '差異', history: '歷史記錄', others: '其它用戶的貢獻', mine: '我的貢獻' }) }, { name: 'rollbackInCurrentWindow', label: wgULS('在当前窗口内执行回退', '在目前視窗內執行回退'), helptip: wgULS('不要开新窗口或者改变当前窗口的状态。', '不要開新視窗或者改變目前視窗的狀態。'), type: 'boolean' }, { name: 'customRevertSummary', label: '回退理由', helptip: wgULS('在查看差异时可选,仅善意回退、常规回退、恢复此版本', '在檢視差異時可選,僅善意回退、常規回退、恢復此版本'), type: 'customList', customListValueTitle: '理由', customListLabelTitle: wgULS('显示的文字', '顯示的文字') } ] }, /* { title: wgULS("共享IP标记", "共享IP標記"), inFriendlyConfig: true, preferences: [ { name: "markSharedIPAsMinor", label: wgULS("将共享IP标记标记为小修改", "將共享IP標記標記為小修改"), type: "boolean" } ] }, */ { title: wgULS('快速删除', '快速刪除'), preferences: [ { name: 'speedySelectionStyle', label: wgULS('什么时候执行标记或删除', '什麼時候執行標記或刪除'), type: 'enum', enumValues: wgULS({ 'buttonClick': '当我点“提交”时', 'radioClick': '当我点一个选项时' }, { 'buttonClick': '當我點「提交」時', 'radioClick': '當我點一個設定時' }) }, // TwinkleConfig.watchSpeedyPages (array) // Whether to add speedy tagged pages to watchlist { name: 'watchSpeedyPages', label: wgULS('将以下理由加入到监视列表', '將以下理由加入到監視清單'), type: 'set', setValues: Twinkle.config.commonSets.csdCriteria, setDisplayOrder: Twinkle.config.commonSets.csdCriteriaDisplayOrder }, // TwinkleConfig.markSpeedyPagesAsPatrolled (boolean) // If, when applying speedy template to page, to mark the page as patrolled (if the page was reached from NewPages) { name: 'markSpeedyPagesAsPatrolled', label: wgULS('标记时标记页面为已巡查(如可能)', '標記時標記頁面為已巡查(如可能)'), type: 'boolean' }, // TwinkleConfig.notifyUserOnSpeedyDeletionNomination (array) // What types of actions should result that the author of the page being notified of nomination /* { name: "notifyUserOnSpeedyDeletionNomination", label: wgULS("仅在使用以下理由时通知页面创建者", "僅在使用以下理由時通知頁面建立者"), helptip: wgULS("尽管您在对话框中选择通知,通知仍只会在使用这些理由时发出。", "盡管您在對話框中選擇通知,通知仍只會在使用這些理由時發出。"), type: "set", setValues: Twinkle.config.commonSets.csdCriteriaNotification, setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder }, */ // TwinkleConfig.welcomeUserOnSpeedyDeletionNotification (array of strings) // On what types of speedy deletion notifications shall the user be welcomed // with a "firstarticle" notice if his talk page has not yet been created. /* { name: "welcomeUserOnSpeedyDeletionNotification", label: wgULS("在使用以下理由时欢迎页面创建者", "在使用以下理由時歡迎頁面建立者"), helptip: wgULS("欢迎模板仅在用户被通知时加入,使用的模板是{{firstarticle}}。", "歡迎模板僅在用戶被通知時加入,使用的模板是{{firstarticle}}。"), type: "set", setValues: Twinkle.config.commonSets.csdCriteriaNotification, setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder }, */ // TwinkleConfig.promptForSpeedyDeletionSummary (array of strings) { name: 'promptForSpeedyDeletionSummary', label: wgULS('使用以下理由删除时允许编辑删除理由', '使用以下理由刪除時允許編輯刪除理由'), adminOnly: true, type: 'set', setValues: Twinkle.config.commonSets.csdAndDICriteria, setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder }, // TwinkleConfig.openUserTalkPageOnSpeedyDelete (array of strings) // What types of actions that should result user talk page to be opened when speedily deleting (admin only) { name: 'openUserTalkPageOnSpeedyDelete', label: wgULS('使用以下理由时开启用户对话页', '使用以下理由時開啟用戶對話頁'), adminOnly: true, type: 'set', setValues: Twinkle.config.commonSets.csdAndDICriteria, setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder }, // TwinkleConfig.deleteTalkPageOnDelete (boolean) // If talk page if exists should also be deleted (CSD G8) when spedying a page (admin only) { name: 'deleteTalkPageOnDelete', label: wgULS('默认勾选“删除讨论页”', '預設勾選「刪除討論頁」'), adminOnly: true, type: 'boolean' }, { name: 'deleteRedirectsOnDelete', label: wgULS('默认勾选“删除重定向”', '預設勾選「刪除重定向」'), adminOnly: true, type: 'boolean' }, // TwinkleConfig.deleteSysopDefaultToTag (boolean) // Make the CSD screen default to "tag" instead of "delete" (admin only) { name: 'deleteSysopDefaultToTag', label: wgULS('默认为标记而不是直接删除', '預設為標記而不是直接刪除'), adminOnly: true, type: 'boolean' }, // TwinkleConfig.speedyWindowWidth (integer) // Defines the width of the Twinkle SD window in pixels { name: 'speedyWindowWidth', label: wgULS('快速删除对话框宽度(像素)', '快速刪除對話方塊寬度(像素)'), type: 'integer' }, // TwinkleConfig.speedyWindowWidth (integer) // Defines the width of the Twinkle SD window in pixels { name: 'speedyWindowHeight', label: wgULS('快速删除对话框高度(像素)', '快速刪除對話方塊高度(像素)'), helptip: wgULS('如果您有一只很大的监视器,您可以将此调高。', '如果您有一只很大的監視器,您可以將此調高。'), type: 'integer' }, { name: 'logSpeedyNominations', label: wgULS('在用户空间中记录所有快速删除提名', '在用戶空間中記錄所有快速刪除提名'), helptip: wgULS('非管理员无法访问到已删除的贡献,用户空间日志提供了一个很好的方法来记录这些历史。', '非管理員無法訪問到已刪除的貢獻,用戶空間日誌提供了一個很好的方法來記錄這些歷史。'), type: 'boolean' }, { name: 'speedyLogPageName', label: wgULS('在此页保留日志', '在此頁保留日誌'), helptip: wgULS('在此框中输入子页面名称,您将在User:<i>用户名</i>/<i>子页面</i>找到CSD日志。仅在开启日志时工作。', '在此框中輸入子頁面名稱,您將在User:<i>用戶名</i>/<i>子頁面</i>找到CSD日誌。僅在開啟日誌時工作。'), type: 'string' }, { name: 'noLogOnSpeedyNomination', label: wgULS('在使用以下理由时不做记录', '在使用以下理由時不做記錄'), type: 'set', setValues: Twinkle.config.commonSets.csdAndDICriteria, setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder }, { name: 'enlargeG11Input', label: wgULS('扩大CSD G11的按钮', '擴大CSD G11的按鈕'), helptip: wgULS('扩为默认的两倍大。', '擴為預設的兩倍大。'), type: 'boolean' } ] }, /* { title: wgULS("标记", "標記"), inFriendlyConfig: true, preferences: [ { name: "watchTaggedPages", label: wgULS("标记时添加到监视列表", "標記時加入到監視清單"), type: "boolean" }, { name: "watchMergeDiscussions", label: wgULS("添加合并讨论时监视讨论页", "加入合併討論時監視討論頁"), type: "boolean" }, { name: "markTaggedPagesAsMinor", label: wgULS("将标记标记为小修改", "將標記標記為小修改"), type: "boolean" }, { name: "markTaggedPagesAsPatrolled", label: wgULS("默认勾选“标记页面为已巡查”框", "預設勾選「標記頁面為已巡查」框"), type: "boolean" }, { name: "groupByDefault", label: wgULS("默认勾选“合并到{{multiple issues}}”复选框", "預設勾選「合併到{{multiple issues}}」核取方塊"), type: "boolean" }, { name: "tagArticleSortOrder", label: wgULS("条目标记的默认查看方式", "條目標記的預設檢視方式"), type: "enum", enumValues: { "cat": wgULS("按类别", "按類別"), "alpha": "按字母" } }, { name: "customTagList", label: wgULS("自定义条目维护标记", "自訂條目維護標記"), helptip: wgULS("这些会出现在列表的末尾。", "這些會出現在清單的末尾。"), type: "customList", customListValueTitle: wgULS("模板名(不含大括号)", "模板名(不含大括號)"), customListLabelTitle: wgULS("显示的文字", "顯示的文字") } ] }, */ /* { title: wgULS("回复", "回覆"), inFriendlyConfig: true, preferences: [ { name: "markTalkbackAsMinor", label: wgULS("将回复标记为小修改", "將回覆標記為小修改"), type: "boolean" }, { name: "insertTalkbackSignature", label: wgULS("回复时添加签名", "回覆時加入簽名"), type: "boolean" }, { name: "talkbackHeading", label: wgULS("回复所用的小节标题", "回覆所用的小節標題"), type: "string" }, { name: "mailHeading", label: wgULS("“有新邮件”所用的小节标题", "「有新郵件」所用的小節標題"), type: "string" } ] }, */ /* { title: wgULS("反链", "反連"), preferences: [ // TwinkleConfig.unlinkNamespaces (array) // In what namespaces unlink should happen, default in 0 (article) and 100 (portal) { name: "unlinkNamespaces", label: wgULS("取消以下命名空间中的反链", "取消以下名字空間中的反連"), helptip: wgULS("请避免选择讨论页,因这样会导致Twinkle试图修改讨论存档。", "請避免選擇討論頁,因這樣會導致Twinkle試圖修改討論存檔。"), type: "set", setValues: Twinkle.config.commonSets.namespacesNoSpecial } ] }, */ /* { title: wgULS("警告用户", "警告用戶"), preferences: [ // TwinkleConfig.defaultWarningGroup (int) // if true, watch the page which has been dispatched an warning or notice, if false, default applies { name: "defaultWarningGroup", label: wgULS("默认警告级别", "預設警告級別"), type: "enum", enumValues: wgULS({ "1": "层级1", "2": "层级2", "3": "层级3", "4": "层级4", "5": "层级4im", "6": "单层级通知", "7": "单层级警告", "9": "自定义警告" }, { "1": "層級1", "2": "層級2", "3": "層級3", "4": "層級4", "5": "層級4im", "6": "單層級通知", "7": "單層級警告", "9": "自訂警告" }) }, // TwinkleConfig.showSharedIPNotice may take arguments: // true: to show shared ip notice if an IP address // false: to not print the notice { name: "showSharedIPNotice", label: wgULS("在IP对话页上显示附加信息", "在IP對話頁上顯示附加資訊"), helptip: wgULS("使用的模板是{{SharedIPAdvice}}。", "使用的模板是{{SharedIPAdvice}}。"), type: "boolean" }, // TwinkleConfig.watchWarnings (boolean) // if true, watch the page which has been dispatched an warning or notice, if false, default applies { name: "watchWarnings", label: wgULS("警告时添加用户对话页到监视列表", "警告時加入用戶對話頁到監視清單"), type: "boolean" }, { name: "customWarningList", label: wgULS("自定义警告模板", "自訂警告模板"), helptip: wgULS("您可以加入模板或用户子页面。自定义警告会出现在警告对话框中“自定义警告”一节。", "您可以加入模板或用戶子頁面。自訂警告會出現在警告對話框中「自訂警告」一節。"), type: "customList", customListValueTitle: wgULS("模板名(不含大括号)", "模板名(不含大括號)"), customListLabelTitle: wgULS("显示的文字(和编辑摘要)", "顯示的文字(和編輯摘要)") }, { name: "markXfdPagesAsPatrolled", label: wgULS("在提交存废讨论时将页面标记为已巡查(如可能)", "在提交存廢討論時將頁面標記為已巡查(如可能)"), type: "boolean" } ] }, */ /* { title: wgULS("封禁", "封禁"), preferences: [ { name: "customBlockReasonList", label: wgULS("自定义封禁理由", "自訂封禁理由"), helptip: wgULS("您可以加入常用的封禁理由。自订的封禁理由会出现在一般的封禁理由下方。", "您可以加入常用的封禁理由。自訂的封禁理由會出現在一般的封禁理由下方。"), type: "customList", customListValueTitle: wgULS("使用封禁模板(预设为 uw-block1)", "使用封禁模板(預設為 uw-block1)"), customListLabelTitle: wgULS("「由于…您已被封禁」及封禁日志理由", "「由於…您已被封禁」及封禁日誌理由") } ] }, */ { title: wgULS('存废讨论', '存廢討論'), preferences: [ // TwinkleConfig.xfdWatchPage (string) // The watchlist setting of the page being nominated for XfD. Either "yes" (add to watchlist), "no" (don't // add to watchlist), or "default" (use setting from preferences). Default is "default" (duh). { name: 'xfdWatchPage', label: wgULS('加入提名的页面到监视列表', '加入提名的頁面到監視清單'), type: 'enum', enumValues: Twinkle.config.watchlistEnums }, // TwinkleConfig.xfdWatchDiscussion (string) // The watchlist setting of the newly created XfD page (for those processes that create discussion pages for each nomination), // or the list page for the other processes. // Either "yes" (add to watchlist), "no" (don't add to watchlist), or "default" (use setting from preferences). Default is "default" (duh). { name: 'xfdWatchDiscussion', label: wgULS('加入存废讨论页到监视列表', '加入存廢討論頁到監視清單'), helptip: wgULS('当日的页面。', '當日的頁面。'), type: 'enum', enumValues: Twinkle.config.watchlistEnums }, // TwinkleConfig.xfdWatchUser (string) // The watchlist setting of the user if he receives a notification. Either "yes" (add to watchlist), "no" (don't // add to watchlist), or "default" (use setting from preferences). Default is "default" (duh). { name: 'xfdWatchUser', label: wgULS('加入创建者对话页到监视列表(在通知时)', '加入建立者對話頁到監視清單(在通知時)'), type: 'enum', enumValues: Twinkle.config.watchlistEnums }, // TwinkleConfig.markXfdPagesAsPatrolled (boolean) // If, when applying xfd template to page, to mark the page as patrolled (if the page was reached from NewPages) { name: 'markXfdPagesAsPatrolled', label: wgULS('标记时标记页面为已巡查(如可能)', '標記時標記頁面為已巡查(如可能)'), helptip: wgULS('基于技术原因,页面仅会在由Special:NewPages到达时被标记为已巡查。', '基於技術原因,頁面僅會在由Special:NewPages到達時被標記為已巡查。'), type: 'boolean' }, { name: 'FwdCsdToXfd', label: wgULS('提删类型增加转交自快速删除候选', '提刪類別增加轉交自快速刪除候選'), helptip: wgULS('请确保您充分了解[[Wikipedia:快速删除方针]]才开启此功能。', '請確保您充分了解[[Wikipedia:快速刪除方針]]才開啟此功能。'), type: 'boolean' }, { name: 'afdDefaultCategory', label: wgULS('默认提删类型', '預設提刪類別'), helptip: wgULS('若选择“相同于上次选择”将使用localStorage来记忆。', '若選擇「相同於上次選擇」將使用localStorage來記憶。'), type: 'enum', enumValues: wgULS({ 'delete': '删除', 'same': '相同于上次选择' }, { 'delete': '刪除', 'same': '相同於上次選擇' }) }, { name: 'XfdClose', label: wgULS('在存废讨论显示关闭讨论按钮', '在存廢討論顯示關閉討論按鈕'), helptip: wgULS('请确保您充分了解[[Wikipedia:关闭删除讨论指南]]才开启此功能。', '請確保您充分了解[[Wikipedia:關閉刪除討論指南]]才開啟此功能。'), type: 'enum', enumValues: wgULS({ 'hide': '不显示', 'nonadminonly': '只包含非管理员可使用选项', 'all': '显示所有选项' }, { 'hide': '不顯示', 'nonadminonly': '只包含非管理員可使用選項', 'all': '顯示所有選項' }) } ] }, /* { title: wgULS("侵犯版权", "侵犯版權"), preferences: [ // TwinkleConfig.copyvioWatchPage (string) // The watchlist setting of the page being nominated for XfD. Either "yes" (add to watchlist), "no" (don't // add to watchlist), or "default" (use setting from preferences). Default is "default" (duh). { name: "copyvioWatchPage", label: wgULS("添加提报的页面到监视列表", "加入提報的頁面到監視清單"), type: "enum", enumValues: Twinkle.config.commonEnums.watchlist }, // TwinkleConfig.copyvioWatchUser (string) // The watchlist setting of the user if he receives a notification. Either "yes" (add to watchlist), "no" (don't // add to watchlist), or "default" (use setting from preferences). Default is "default" (duh). { name: "copyvioWatchUser", label: wgULS("添加创建者对话页到监视列表(在通知时)", "加入建立者對話頁到監視清單(在通知時)"), type: "enum", enumValues: Twinkle.config.commonEnums.watchlist }, // TwinkleConfig.markCopyvioPagesAsPatrolled (boolean) // If, when applying copyvio template to page, to mark the page as patrolled (if the page was reached from NewPages) { name: "markCopyvioPagesAsPatrolled", label: wgULS("标记时标记页面为已巡查(如可能)", "標記時標記頁面為已巡查(如可能)"), helptip: wgULS("基于技术原因,页面仅会在由Special:NewPages到达时被标记为已巡查。", "基於技術原因,頁面僅會在由Special:NewPages到達時被標記為已巡查。"), type: "boolean" }, // TwinkleConfig.markDraftCopyvioWithCSD (boolean) // If, when applying copyvio template to draft page, to mark the page with CSD { name: "markDraftCopyvioWithCSD", label: wgULS("在草稿名字空间默认勾选“同时标记CSD G16”", "在草稿命名空間預設勾選「同時標記CSD G16」"), type: "boolean" }, ] }, */ { title: wgULS('隐藏', '隱藏'), hidden: true, preferences: [ // twinkle.js: portlet setup { name: 'portletArea', type: 'string' }, { name: 'portletId', type: 'string' }, { name: 'portletName', type: 'string' }, { name: 'portletType', type: 'string' }, { name: 'portletNext', type: 'string' }, // twinklefluff.js: defines how many revision to query maximum, maximum possible is 50, default is 50 { name: 'revertMaxRevisions', type: 'integer' }, // twinklebatchdelete.js: How many pages should be processed maximum { name: 'batchMax', type: 'integer', adminOnly: true }, // twinklebatchdelete.js: How many pages should be processed at a time { name: 'batchdeleteChunks', type: 'integer', adminOnly: true }, // twinklebatchprotect.js: How many pages should be processed at a time { name: 'batchProtectChunks', type: 'integer', adminOnly: true }, // twinklebatchundelete.js: How many pages should be processed at a time { name: 'batchundeleteChunks', type: 'integer', adminOnly: true }, // twinkledeprod.js: How many pages should be processed at a time { name: 'proddeleteChunks', type: 'integer', adminOnly: true } ] } ]; // end of Twinkle.config.sections Twinkle.config.init = function twinkleconfigInit() { if ((mw.config.get('wgPageName') === Twinkle.getPref('configPage') || (mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').user && mw.config.get('wgTitle').lastIndexOf('/Twinkle参数') === (mw.config.get('wgTitle').length - 10))) && mw.config.get('wgAction') === 'view') { // create the config page at Twinkle.getPref('configPage'), and at user subpages (for testing purposes) if (!document.getElementById('twinkle-config')) { return; // maybe the page is misconfigured, or something - but any attempt to modify it will be pointless } // set style (the url() CSS function doesn't seem to work from wikicode - ?!) document.getElementById('twinkle-config-titlebar').style.backgroundImage = 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB%2FqqA%2BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEhQTFRFr73ZobTPusjdsMHZp7nVwtDhzNbnwM3fu8jdq7vUt8nbxtDkw9DhpbfSvMrfssPZqLvVztbno7bRrr7W1d%2Fs1N7qydXk0NjpkW7Q%2BgAAADVJREFUeNoMwgESQCAAAMGLkEIi%2FP%2BnbnbpdB59app5Vdg0sXAoMZCpGoFbK6ciuy6FX4ABAEyoAef0BXOXAAAAAElFTkSuQmCC)'; var contentdiv = document.getElementById('twinkle-config-content'); contentdiv.textContent = ''; // clear children // let user know about possible conflict with monobook.js/vector.js file // (settings in that file will still work, but they will be overwritten by twinkleoptions.js settings) var contentnotice = document.createElement('p'); // I hate innerHTML, but this is one thing it *is* good for... contentnotice.innerHTML = wgULS('<b>在这里修改您的参数设置之前,</b>确认您已移除了<a href="' + mw.util.getUrl('Special:MyPage/skin.js') + '" title="Special:MyPage/skin.js">用户JavaScript文件</a>中任何旧的<code>FriendlyConfig</code>设置。', '<b>在這裡修改您的偏好設定之前,</b>確認您已移除了<a href="' + mw.util.getUrl('Special:MyPage/skin.js') + '" title="Special:MyPage/skin.js">用戶JavaScript檔案</a>中任何舊的<code>FriendlyConfig</code>設定。'); contentdiv.appendChild(contentnotice); // look and see if the user does in fact have any old settings in their skin JS file var skinjs = new Morebits.wiki.page('User:' + mw.config.get('wgUserName') + '/' + mw.config.get('skin') + '.js'); skinjs.setCallbackParameters(contentnotice); skinjs.load(Twinkle.config.legacyPrefsNotice); // start a table of contents var toctable = document.createElement('div'); toctable.className = 'toc'; toctable.style.marginLeft = '0.4em'; // create TOC title var toctitle = document.createElement('div'); toctitle.id = 'toctitle'; var toch2 = document.createElement('h2'); toch2.textContent = wgULS('目录 ', '目錄 '); toctitle.appendChild(toch2); // add TOC show/hide link var toctoggle = document.createElement('span'); toctoggle.className = 'toctoggle'; toctoggle.appendChild(document.createTextNode('[')); var toctogglelink = document.createElement('a'); toctogglelink.className = 'internal'; toctogglelink.setAttribute('href', '#tw-tocshowhide'); toctogglelink.textContent = wgULS('隐藏', '隱藏'); toctoggle.appendChild(toctogglelink); toctoggle.appendChild(document.createTextNode(']')); toctitle.appendChild(toctoggle); toctable.appendChild(toctitle); // create item container: this is what we add stuff to var tocul = document.createElement('ul'); toctogglelink.addEventListener('click', function twinkleconfigTocToggle() { var $tocul = $(tocul); $tocul.toggle(); if ($tocul.find(':visible').length) { toctogglelink.textContent = wgULS('隐藏', '隱藏'); } else { toctogglelink.textContent = wgULS('显示', '顯示'); } }, false); toctable.appendChild(tocul); contentdiv.appendChild(toctable); var tocnumber = 1; var contentform = document.createElement('form'); contentform.setAttribute('action', 'javascript:void(0)'); // was #tw-save - changed to void(0) to work around Chrome issue contentform.addEventListener('submit', Twinkle.config.save, true); contentdiv.appendChild(contentform); var container = document.createElement('table'); container.style.width = '100%'; contentform.appendChild(container); $(Twinkle.config.sections).each(function(sectionkey, section) { if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) { return true; // i.e. "continue" in this context } // add to TOC var tocli = document.createElement('li'); tocli.className = 'toclevel-1'; var toca = document.createElement('a'); toca.setAttribute('href', '#twinkle-config-section-' + tocnumber.toString()); toca.appendChild(document.createTextNode(section.title)); tocli.appendChild(toca); tocul.appendChild(tocli); var row = document.createElement('tr'); var cell = document.createElement('td'); cell.setAttribute('colspan', '3'); var heading = document.createElement('h4'); heading.style.borderBottom = '1px solid gray'; heading.style.marginTop = '0.2em'; heading.id = 'twinkle-config-section-' + (tocnumber++).toString(); heading.appendChild(document.createTextNode(section.title)); cell.appendChild(heading); row.appendChild(cell); container.appendChild(row); var rowcount = 1; // for row banding // add each of the preferences to the form $(section.preferences).each(function(prefkey, pref) { if (pref.adminOnly && !Morebits.userIsSysop) { return true; // i.e. "continue" in this context } row = document.createElement('tr'); row.style.marginBottom = '0.2em'; // create odd row banding if (rowcount++ % 2 === 0) { row.style.backgroundColor = 'rgba(128, 128, 128, 0.1)'; } cell = document.createElement('td'); var label, input; switch (pref.type) { case 'boolean': // create a checkbox cell.setAttribute('colspan', '2'); label = document.createElement('label'); input = document.createElement('input'); input.setAttribute('type', 'checkbox'); input.setAttribute('id', pref.name); input.setAttribute('name', pref.name); if (Twinkle.getPref(pref.name) === true) { input.setAttribute('checked', 'checked'); } label.appendChild(input); label.appendChild(document.createTextNode(' ' + pref.label)); cell.appendChild(label); break; case 'string': // create an input box case 'integer': // add label to first column cell.style.textAlign = 'right'; cell.style.paddingRight = '0.5em'; label = document.createElement('label'); label.setAttribute('for', pref.name); label.appendChild(document.createTextNode(pref.label + ':')); cell.appendChild(label); row.appendChild(cell); // add input box to second column cell = document.createElement('td'); cell.style.paddingRight = '1em'; input = document.createElement('input'); input.setAttribute('type', 'text'); input.setAttribute('id', pref.name); input.setAttribute('name', pref.name); if (pref.type === 'integer') { input.setAttribute('size', 6); input.setAttribute('type', 'number'); input.setAttribute('step', '1'); // integers only } if (Twinkle.getPref(pref.name)) { input.setAttribute('value', Twinkle.getPref(pref.name)); } cell.appendChild(input); break; case 'enum': // create a combo box // add label to first column // note: duplicates the code above, under string/integer cell.style.textAlign = 'right'; cell.style.paddingRight = '0.5em'; label = document.createElement('label'); label.setAttribute('for', pref.name); label.appendChild(document.createTextNode(pref.label + ':')); cell.appendChild(label); row.appendChild(cell); // add input box to second column cell = document.createElement('td'); cell.style.paddingRight = '1em'; input = document.createElement('select'); input.setAttribute('id', pref.name); input.setAttribute('name', pref.name); $.each(pref.enumValues, function(enumvalue, enumdisplay) { var option = document.createElement('option'); option.setAttribute('value', enumvalue); if (Twinkle.getPref(pref.name) === enumvalue) { option.setAttribute('selected', 'selected'); } option.appendChild(document.createTextNode(enumdisplay)); input.appendChild(option); }); cell.appendChild(input); break; case 'set': // create a set of check boxes // add label first of all cell.setAttribute('colspan', '2'); label = document.createElement('label'); // not really necessary to use a label element here, but we do it for consistency of styling label.appendChild(document.createTextNode(pref.label + ':')); cell.appendChild(label); var checkdiv = document.createElement('div'); checkdiv.style.paddingLeft = '1em'; var worker = function(itemkey, itemvalue) { var checklabel = document.createElement('label'); checklabel.style.marginRight = '0.7em'; checklabel.style.display = 'inline-block'; var check = document.createElement('input'); check.setAttribute('type', 'checkbox'); check.setAttribute('id', pref.name + '_' + itemkey); check.setAttribute('name', pref.name + '_' + itemkey); if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(itemkey) !== -1) { check.setAttribute('checked', 'checked'); } // cater for legacy integer array values for unlinkNamespaces (this can be removed a few years down the track...) if (pref.name === 'unlinkNamespaces') { if (Twinkle.getPref(pref.name) && Twinkle.getPref(pref.name).indexOf(parseInt(itemkey, 10)) !== -1) { check.setAttribute('checked', 'checked'); } } checklabel.appendChild(check); checklabel.appendChild(document.createTextNode(itemvalue)); checkdiv.appendChild(checklabel); }; if (pref.setDisplayOrder) { // add check boxes according to the given display order $.each(pref.setDisplayOrder, function(itemkey, item) { worker(item, pref.setValues[item]); }); } else { // add check boxes according to the order it gets fed to us (probably strict alphabetical) $.each(pref.setValues, worker); } cell.appendChild(checkdiv); break; case 'customList': // add label to first column cell.style.textAlign = 'right'; cell.style.paddingRight = '0.5em'; label = document.createElement('label'); label.setAttribute('for', pref.name); label.appendChild(document.createTextNode(pref.label + ':')); cell.appendChild(label); row.appendChild(cell); // add button to second column cell = document.createElement('td'); cell.style.paddingRight = '1em'; var button = document.createElement('button'); button.setAttribute('id', pref.name); button.setAttribute('name', pref.name); button.setAttribute('type', 'button'); button.addEventListener('click', Twinkle.config.listDialog.display, false); // use jQuery data on the button to store the current config value $(button).data({ value: Twinkle.getPref(pref.name), pref: pref }); button.appendChild(document.createTextNode(wgULS('编辑项目', '編輯項目'))); cell.appendChild(button); break; default: alert('twinkleconfig: 未知类型的属性 ' + pref.name); break; } row.appendChild(cell); // add help tip cell = document.createElement('td'); cell.style.fontSize = '90%'; cell.style.color = 'gray'; if (pref.helptip) { // convert mentions of templates in the helptip to clickable links cell.innerHTML = pref.helptip.replace(/{{(.+?)}}/g, '{{<a href="' + mw.util.getUrl('Template:') + '$1" target="_blank">$1</a>}}') .replace(/\[\[(.+?)]]/g, '<a href="' + mw.util.getUrl('') + '$1" target="_blank">$1</a>'); } // add reset link (custom lists don't need this, as their config value isn't displayed on the form) if (pref.type !== 'customList') { var resetlink = document.createElement('a'); resetlink.setAttribute('href', '#tw-reset'); resetlink.setAttribute('id', 'twinkle-config-reset-' + pref.name); resetlink.addEventListener('click', Twinkle.config.resetPrefLink, false); resetlink.style.cssFloat = 'right'; resetlink.style.margin = '0 0.6em'; resetlink.appendChild(document.createTextNode(wgULS('复位', '復位'))); cell.appendChild(resetlink); } row.appendChild(cell); container.appendChild(row); return true; }); return true; }); var footerbox = document.createElement('div'); footerbox.setAttribute('id', 'twinkle-config-buttonpane'); footerbox.style.backgroundColor = '#BCCADF'; footerbox.style.padding = '0.5em'; var button = document.createElement('button'); button.setAttribute('id', 'twinkle-config-submit'); button.setAttribute('type', 'submit'); button.appendChild(document.createTextNode(wgULS('保存修改', '儲存修改'))); footerbox.appendChild(button); var footerspan = document.createElement('span'); footerspan.className = 'plainlinks'; footerspan.style.marginLeft = '2.4em'; footerspan.style.fontSize = '90%'; var footera = document.createElement('a'); footera.setAttribute('href', '#tw-reset-all'); footera.setAttribute('id', 'twinkle-config-resetall'); footera.addEventListener('click', Twinkle.config.resetAllPrefs, false); footera.appendChild(document.createTextNode(wgULS('恢复默认', '恢復預設'))); footerspan.appendChild(footera); footerbox.appendChild(footerspan); contentform.appendChild(footerbox); // since all the section headers exist now, we can try going to the requested anchor if (location.hash) { window.location.hash = location.hash; } } else if (mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').user && mw.config.get('wgTitle').indexOf(mw.config.get('wgUserName')) === 0 && mw.config.get('wgPageName').slice(-3) === '.js') { var box = document.createElement('div'); box.setAttribute('id', 'twinkle-config-headerbox'); box.style.border = '1px #f60 solid'; box.style.background = '#fed'; box.style.padding = '0.6em'; box.style.margin = '0.5em auto'; box.style.textAlign = 'center'; var link, scriptPageName = mw.config.get('wgPageName').slice(mw.config.get('wgPageName').lastIndexOf('/') + 1, mw.config.get('wgPageName').lastIndexOf('.js')); if (scriptPageName === 'twinkleoptions') { // place "why not try the preference panel" notice box.style.fontWeight = 'bold'; box.style.width = '80%'; box.style.borderWidth = '2px'; if (mw.config.get('wgArticleId') > 0) { // page exists box.appendChild(document.createTextNode(wgULS('这页包含您的Twinkle参数设置,您可使用', '這頁包含您的Twinkle偏好設定,您可使用'))); } else { // page does not exist box.appendChild(document.createTextNode(wgULS('您可配置您的Twinkle,通过使用', '您可配置您的Twinkle,通過使用'))); } link = document.createElement('a'); link.setAttribute('href', mw.util.getUrl(Twinkle.getPref('configPage'))); link.appendChild(document.createTextNode(wgULS('Twinkle参数设置面板', 'Twinkle偏好設定面板'))); box.appendChild(link); box.appendChild(document.createTextNode(wgULS(',或直接编辑本页。', ',或直接編輯本頁。'))); $(box).insertAfter($('#contentSub')); } else if (['monobook', 'vector', 'vector-2022', 'cologneblue', 'modern', 'timeless', 'minerva', 'common'].indexOf(scriptPageName) !== -1) { // place "Looking for Twinkle options?" notice box.style.width = '60%'; box.appendChild(document.createTextNode(wgULS('如果您想配置您的Twinkle,请使用', '如果您想配置您的Twinkle,請使用'))); link = document.createElement('a'); link.setAttribute('href', mw.util.getUrl(Twinkle.getPref('configPage'))); link.appendChild(document.createTextNode(wgULS('Twinkle参数设置面板', 'Twinkle偏好設定面板'))); box.appendChild(link); box.appendChild(document.createTextNode('。')); $(box).insertAfter($('#contentSub')); } } }; // Morebits.wiki.page callback from init code Twinkle.config.legacyPrefsNotice = function twinkleconfigLegacyPrefsNotice(pageobj) { var text = pageobj.getPageText(); var contentnotice = pageobj.getCallbackParameters(); if (text.indexOf('TwinkleConfig') !== -1 || text.indexOf('FriendlyConfig') !== -1) { contentnotice.innerHTML = '<table class="plainlinks ombox ombox-content"><tr><td class="mbox-image">' + '<img alt="" src="http://upload.wikimedia.org/wikipedia/en/3/38/Imbox_content.png" /></td>' + '<td class="mbox-text"><p><big><b>在这里修改您的参数设置之前,</b>您必须移除在用户JavaScript文件中任何旧的Friendly设置。</big></p>' + '<p>要这样做,您可以<a href="' + mw.config.get('wgScript') + '?title=User:' + encodeURIComponent(mw.config.get('wgUserName')) + '/' + mw.config.get('skin') + '.js&action=edit" target="_blank"><b>编辑您的个人JavaScript</b></a>。删除提到<code>FriendlyConfig</code>的代码。</p>' + '</td></tr></table>'; } else { $(contentnotice).remove(); } }; // custom list-related stuff Twinkle.config.listDialog = {}; Twinkle.config.listDialog.addRow = function twinkleconfigListDialogAddRow(dlgtable, value, label) { var contenttr = document.createElement('tr'); // "remove" button var contenttd = document.createElement('td'); var removeButton = document.createElement('button'); removeButton.setAttribute('type', 'button'); removeButton.addEventListener('click', function() { $(contenttr).remove(); }, false); removeButton.textContent = '移除'; contenttd.appendChild(removeButton); contenttr.appendChild(contenttd); // value input box contenttd = document.createElement('td'); var input = document.createElement('input'); input.setAttribute('type', 'text'); input.className = 'twinkle-config-customlist-value'; input.style.width = '97%'; if (value) { input.setAttribute('value', value); } contenttd.appendChild(input); contenttr.appendChild(contenttd); // label input box contenttd = document.createElement('td'); input = document.createElement('input'); input.setAttribute('type', 'text'); input.className = 'twinkle-config-customlist-label'; input.style.width = '98%'; if (label) { input.setAttribute('value', label); } contenttd.appendChild(input); contenttr.appendChild(contenttd); dlgtable.appendChild(contenttr); }; Twinkle.config.listDialog.display = function twinkleconfigListDialogDisplay(e) { var $prefbutton = $(e.target); var curvalue = $prefbutton.data('value'); var curpref = $prefbutton.data('pref'); var dialog = new Morebits.simpleWindow(720, 400); dialog.setTitle(curpref.label); dialog.setScriptName(wgULS('Twinkle参数设置', 'Twinkle偏好設定')); var dialogcontent = document.createElement('div'); var dlgtable = document.createElement('table'); dlgtable.className = 'wikitable'; dlgtable.style.margin = '1.4em 1em'; dlgtable.style.width = '97%'; var dlgtbody = document.createElement('tbody'); // header row var dlgtr = document.createElement('tr'); // top-left cell var dlgth = document.createElement('th'); dlgth.style.width = '5%'; dlgtr.appendChild(dlgth); // value column header dlgth = document.createElement('th'); dlgth.style.width = '35%'; dlgth.textContent = curpref.customListValueTitle ? curpref.customListValueTitle : wgULS('数值', '數值'); dlgtr.appendChild(dlgth); // label column header dlgth = document.createElement('th'); dlgth.style.width = '60%'; dlgth.textContent = curpref.customListLabelTitle ? curpref.customListLabelTitle : wgULS('标签', '標籤'); dlgtr.appendChild(dlgth); dlgtbody.appendChild(dlgtr); // content rows var gotRow = false; $.each(curvalue, function(k, v) { gotRow = true; Twinkle.config.listDialog.addRow(dlgtbody, v.value, v.label); }); // if there are no values present, add a blank row to start the user off if (!gotRow) { Twinkle.config.listDialog.addRow(dlgtbody); } // final "add" button var dlgtfoot = document.createElement('tfoot'); dlgtr = document.createElement('tr'); var dlgtd = document.createElement('td'); dlgtd.setAttribute('colspan', '3'); var addButton = document.createElement('button'); addButton.style.minWidth = '8em'; addButton.setAttribute('type', 'button'); addButton.addEventListener('click', function() { Twinkle.config.listDialog.addRow(dlgtbody); }, false); addButton.textContent = '添加'; dlgtd.appendChild(addButton); dlgtr.appendChild(dlgtd); dlgtfoot.appendChild(dlgtr); dlgtable.appendChild(dlgtbody); dlgtable.appendChild(dlgtfoot); dialogcontent.appendChild(dlgtable); // buttonpane buttons: [Save changes] [Reset] [Cancel] var button = document.createElement('button'); button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane button.addEventListener('click', function() { Twinkle.config.listDialog.save($prefbutton, dlgtbody); dialog.close(); }, false); button.textContent = wgULS('保存变更', '儲存變更'); dialogcontent.appendChild(button); button = document.createElement('button'); button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane button.addEventListener('click', function() { Twinkle.config.listDialog.reset($prefbutton, dlgtbody); }, false); button.textContent = wgULS('复位', '復位'); dialogcontent.appendChild(button); button = document.createElement('button'); button.setAttribute('type', 'submit'); // so Morebits.simpleWindow puts the button in the button pane button.addEventListener('click', function() { dialog.close(); // the event parameter on this function seems to be broken }, false); button.textContent = '取消'; dialogcontent.appendChild(button); dialog.setContent(dialogcontent); dialog.display(); }; // Resets the data value, re-populates based on the new (default) value, then saves the // old data value again (less surprising behaviour) Twinkle.config.listDialog.reset = function twinkleconfigListDialogReset(button, tbody) { // reset value on button var $button = $(button); var curpref = $button.data('pref'); var oldvalue = $button.data('value'); Twinkle.config.resetPref(curpref); // reset form var $tbody = $(tbody); $tbody.find('tr').slice(1).remove(); // all rows except the first (header) row // add the new values var curvalue = $button.data('value'); $.each(curvalue, function(k, v) { Twinkle.config.listDialog.addRow(tbody, v.value, v.label); }); // save the old value $button.data('value', oldvalue); }; Twinkle.config.listDialog.save = function twinkleconfigListDialogSave(button, tbody) { var result = []; var current = {}; $(tbody).find('input[type="text"]').each(function(inputkey, input) { if ($(input).hasClass('twinkle-config-customlist-value')) { current = { value: input.value }; } else { current.label = input.value; // exclude totally empty rows if (current.value || current.label) { result.push(current); } } }); $(button).data('value', result); }; // reset/restore defaults Twinkle.config.resetPrefLink = function twinkleconfigResetPrefLink(e) { var wantedpref = e.target.id.substring(21); // "twinkle-config-reset-" prefix is stripped // search tactics $(Twinkle.config.sections).each(function(sectionkey, section) { if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) { return true; // continue: skip impossibilities } var foundit = false; $(section.preferences).each(function(prefkey, pref) { if (pref.name !== wantedpref) { return true; // continue } Twinkle.config.resetPref(pref); foundit = true; return false; // break }); if (foundit) { return false; // break } }); return false; // stop link from scrolling page }; Twinkle.config.resetPref = function twinkleconfigResetPref(pref) { switch (pref.type) { case 'boolean': document.getElementById(pref.name).checked = Twinkle.defaultConfig[pref.name]; break; case 'string': case 'integer': case 'enum': document.getElementById(pref.name).value = Twinkle.defaultConfig[pref.name]; break; case 'set': $.each(pref.setValues, function(itemkey) { if (document.getElementById(pref.name + '_' + itemkey)) { document.getElementById(pref.name + '_' + itemkey).checked = Twinkle.defaultConfig[pref.name].indexOf(itemkey) !== -1; } }); break; case 'customList': $(document.getElementById(pref.name)).data('value', Twinkle.defaultConfig[pref.name]); break; default: alert('twinkleconfig: unknown data type for preference ' + pref.name); break; } }; Twinkle.config.resetAllPrefs = function twinkleconfigResetAllPrefs() { // no confirmation message - the user can just refresh/close the page to abort $(Twinkle.config.sections).each(function(sectionkey, section) { if (section.hidden || (section.adminOnly && !Morebits.userIsSysop)) { return true; // continue: skip impossibilities } $(section.preferences).each(function(prefkey, pref) { if (!pref.adminOnly || Morebits.userIsSysop) { Twinkle.config.resetPref(pref); } }); return true; }); return false; // stop link from scrolling page }; Twinkle.config.save = function twinkleconfigSave(e) { Morebits.status.init(document.getElementById('twinkle-config-content')); var userjs = mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceIds').user] + ':' + mw.config.get('wgUserName') + '/twinkleoptions.js'; var wikipedia_page = new Morebits.wiki.page(userjs, wgULS('保存参数设置到 ', '儲存偏好設定到 ') + userjs); wikipedia_page.setCallbackParameters(e.target); wikipedia_page.load(Twinkle.config.writePrefs); return false; }; Twinkle.config.writePrefs = function twinkleconfigWritePrefs(pageobj) { var form = pageobj.getCallbackParameters(); // this is the object which gets serialized into JSON; only // preferences that this script knows about are kept var newConfig = {optionsVersion: 2}; // a comparison function is needed later on // it is just enough for our purposes (i.e. comparing strings, numbers, booleans, // arrays of strings, and arrays of { value, label }) // and it is not very robust: e.g. compare([2], ["2"]) === true, and // compare({}, {}) === false, but it's good enough for our purposes here var compare = function(a, b) { if ($.isArray(a)) { if (a.length !== b.length) { return false; } var asort = a.sort(), bsort = b.sort(); for (var i = 0; asort[i]; ++i) { // comparison of the two properties of custom lists if ((typeof asort[i] === 'object') && (asort[i].label !== bsort[i].label || asort[i].value !== bsort[i].value)) { return false; } else if (asort[i].toString() !== bsort[i].toString()) { return false; } } return true; } return a === b; }; $(Twinkle.config.sections).each(function(sectionkey, section) { if (section.adminOnly && !Morebits.userIsSysop) { return; // i.e. "continue" in this context } // reach each of the preferences from the form $(section.preferences).each(function(prefkey, pref) { var userValue; // = undefined // only read form values for those prefs that have them if (!pref.adminOnly || Morebits.userIsSysop) { if (!section.hidden) { switch (pref.type) { case 'boolean': // read from the checkbox userValue = form[pref.name].checked; break; case 'string': // read from the input box or combo box case 'enum': userValue = form[pref.name].value; break; case 'integer': // read from the input box userValue = parseInt(form[pref.name].value, 10); if (isNaN(userValue)) { Morebits.status.warn(wgULS('保存', '儲存'), wgULS('您为 ' + pref.name + ' 指定的值(' + pref.value + ')不合法,会继续保存操作,但此值将会跳过。', '您為 ' + pref.name + ' 指定的值(' + pref.value + ')不合法,會繼續儲存操作,但此值將會跳過。')); userValue = null; } break; case 'set': // read from the set of check boxes userValue = []; if (pref.setDisplayOrder) { // read only those keys specified in the display order $.each(pref.setDisplayOrder, function(itemkey, item) { if (form[pref.name + '_' + item].checked) { userValue.push(item); } }); } else { // read all the keys in the list of values $.each(pref.setValues, function(itemkey) { if (form[pref.name + '_' + itemkey].checked) { userValue.push(itemkey); } }); } break; case 'customList': // read from the jQuery data stored on the button object userValue = $(form[pref.name]).data('value'); break; default: alert('twinkleconfig: 未知数据类型,属性 ' + pref.name); break; } } else if (Twinkle.prefs[pref.name]) { // Retain the hidden preferences that may have customised by the user from twinkleoptions.js // undefined if not set userValue = Twinkle.prefs[pref.name]; } } // only save those preferences that are *different* from the default if (userValue !== undefined && !compare(userValue, Twinkle.defaultConfig[pref.name])) { newConfig[pref.name] = userValue; } }); }); var text = wgULS( '// twinkleoptions.js:用户Twinkle参数设置文件\n' + '//\n' + '// 注:修改您的参数设置最简单的办法是使用\n' + '// Twinkle参数设置面板,在[[' + Morebits.pageNameNorm + ']]。\n' + '//\n' + '// 这个文件是自动生成的,您所做的任何修改(除了\n' + '// 以一种合法的JavaScript的方式来修改这些属性值)会\n' + '// 在下一次您点击“保存”时被覆盖。\n' + '// 修改此文件时,请记得使用合法的JavaScript。\n' + '\n' + 'window.Twinkle.prefs = ', '// twinkleoptions.js:使用者Twinkle參數設定檔案\n' + '//\n' + '// 註:修改您的參數設定最簡單的辦法是使用\n' + '// Twinkle參數設定面板,在[[' + Morebits.pageNameNorm + ']]。\n' + '//\n' + '// 這個檔案是自動產生的,您所做的任何修改(除了\n' + '// 以一種合法的JavaScript的方式來修改這些屬性值)會\n' + '// 在下一次您點擊「儲存」時被覆蓋。\n' + '// 修改此檔案時,請記得使用合法的JavaScript。\n' + '\n' + 'window.Twinkle.prefs = ' ); text += JSON.stringify(newConfig, null, 2); text += ';\n' + '\n' + wgULS('// twinkleoptions.js到此为止\n', '// twinkleoptions.js到此為止\n'); pageobj.setPageText(text); pageobj.setEditSummary(wgULS('保存Twinkle参数设置:来自[[' + Morebits.pageNameNorm + ']]的自动编辑', '儲存Twinkle偏好設定:來自[[' + Morebits.pageNameNorm + ']]的自動編輯')); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setCreateOption('recreate'); pageobj.save(Twinkle.config.saveSuccess); }; Twinkle.config.saveSuccess = function twinkleconfigSaveSuccess(pageobj) { pageobj.getStatusElement().info('成功'); var noticebox = document.createElement('div'); noticebox.className = 'mw-message-box mw-message-box-success'; noticebox.style.fontSize = '100%'; noticebox.style.marginTop = '2em'; noticebox.innerHTML = wgULS('<p><b>您的Twinkle参数设置已被保存。</b></p><p>要看到这些更改,您可能需要<a href="' + mw.util.getUrl('WP:BYPASS') + '" title="WP:BYPASS"><b>绕过浏览器缓存</b></a>。</p>', '<p><b>您的Twinkle偏好設定已被儲存。</b></p><p>要看到這些更改,您可能需要<a href="' + mw.util.getUrl('WP:BYPASS') + '" title="WP:BYPASS"><b>繞過瀏覽器快取</b></a>。</p>'); Morebits.status.root.appendChild(noticebox); var noticeclear = document.createElement('br'); noticeclear.style.clear = 'both'; Morebits.status.root.appendChild(noticeclear); }; })(jQuery); // </nowiki> trdslmjqm0ywjn4keista6jizkd2vgz MediaWiki:Gadget-twinklecopyvio.js 8 1368869 9943044 6333136 2026-10-12T15:41:06Z Xiplus 25402 Repo at 375b1e1: Do not edit legacy IP page 9943044 javascript text/javascript // <nowiki> (function($) { // eslint-disable-line no-unused-vars /* **************************************** *** twinklecopyvio.js: Copyvio module **************************************** * Mode of invocation: Tab ("Copyvio") * Active on: Existing, non-special pages, except for file pages with no local (non-Commons) file which are not redirects * Config directives in: TwinkleConfig */ Twinkle.copyvio = function twinklecopyvio() { // Disable on: // * special pages // * non-existent pages // * files on Commons, whether there is a local page or not (unneeded local pages of files on Commons are eligible for CSD F2) // * file pages without actual files (these are eligible for CSD G8) if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId') || (mw.config.get('wgNamespaceNumber') === 6 && (document.getElementById('mw-sharedupload') || (!document.getElementById('mw-imagepage-section-filehistory') && !Morebits.wiki.isPageRedirect())))) { return; } Twinkle.addPortletLink(Twinkle.copyvio.callback, wgULS('侵权', '侵權'), 'tw-copyvio', wgULS('提报侵权页面', '提報侵權頁面'), ''); }; Twinkle.copyvio.callback = function twinklecopyvioCallback() { var Window = new Morebits.simpleWindow(600, 350); Window.setTitle(wgULS('提报侵权页面', '提報侵權頁面')); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('常见错误', '常見錯誤'), 'Wikipedia:管理员错误自查表/侵权处理'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'WP:TW/DOC#copyvio'); var form = new Morebits.quickForm(Twinkle.copyvio.callback.evaluate); form.append({ type: 'textarea', label: wgULS('侵权来源:', '侵權來源:'), name: 'source' } ); form.append({ type: 'checkbox', list: [ { label: wgULS('通知页面创建者', '通知頁面建立者'), value: 'notify', name: 'notify', tooltip: wgULS('在页面创建者对话页上放置一通知模板。', '在頁面建立者對話頁上放置一通知模板。'), checked: true } ] } ); form.append({ type: 'submit' }); var result = form.render(); Window.setContent(result); Window.display(); }; Twinkle.copyvio.callbacks = { tryTagging: function (pageobj) { // 先尝试标记页面,如果发现已经标记则停止提报 var text = pageobj.getPageText(); if (text.indexOf('{{Copyvio|') === -1) { Twinkle.copyvio.callbacks.taggingArticle(pageobj); // Contributor specific edits var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName')); wikipedia_page.setCallbackParameters(pageobj.getCallbackParameters()); wikipedia_page.lookupCreation(Twinkle.copyvio.callbacks.main); } else { Morebits.status.error(wgULS('错误', '錯誤'), wgULS('页面已经标记侵权,请人工确认是否已经提报。', '頁面已經標記侵權,請人工確認是否已經提報。')); } }, main: function(pageobj) { // this is coming in from lookupCreation...! var params = pageobj.getCallbackParameters(); var initialContrib = pageobj.getCreator(); // Adding discussion var wikipedia_page = new Morebits.wiki.page(params.logpage, wgULS('加入侵权记录项', '加入侵權記錄項')); wikipedia_page.setFollowRedirect(true); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.copyvio.callbacks.copyvioList); // Notification to first contributor if (params.usertalk && mw.util.isIPAddress(initialContrib, false)) { Morebits.status.info(wgULS('通知页面创建者', '通知頁面建立者'), wgULS('IP用户创建了该页,跳过通知。', 'IP使用者建立了該頁,跳過通知。')); } else if (params.usertalk) { var usertalkpage = new Morebits.wiki.page('User talk:' + initialContrib, wgULS('通知页面创建者(' + initialContrib + ')', '通知頁面建立者(' + initialContrib + ')')); var notifytext = '\n{{subst:AFDNote|' + mw.config.get('wgPageName') + '}}--~~~~'; usertalkpage.setAppendText(notifytext); usertalkpage.setEditSummary(wgULS('通知:页面[[', '通知:頁面[[') + mw.config.get('wgPageName') + wgULS(']]疑似侵犯著作权', ']]疑似侵犯版權')); usertalkpage.setChangeTags(Twinkle.changeTags); usertalkpage.setCreateOption('recreate'); switch (Twinkle.getPref('copyvioWatchUser')) { case 'yes': usertalkpage.setWatchlist(true); break; case 'no': usertalkpage.setWatchlistFromPreferences(false); break; default: usertalkpage.setWatchlistFromPreferences(true); break; } usertalkpage.setFollowRedirect(true); usertalkpage.append(); } }, taggingArticle: function(pageobj) { var params = pageobj.getCallbackParameters(); var tag = '{{subst:Copyvio/auto|url=' + params.source.replace(/http/g, '&#104;ttp').replace(/\n+/g, '\n').replace(/^\s*([^*])/gm, '* $1').replace(/^\* $/m, '') + '|OldRevision=' + mw.config.get('wgRevisionId') + '}}'; var text = pageobj.getPageText(); var oldcsd = text.match(/\{\{\s*(Delete|Deletebecause|D|Db|Speedydelete)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}/i); if (oldcsd && confirm(wgULS('在页面上找到快速删除模板,要保留吗?\n\n当页面同时侵犯著作权又符合快速删除标准时,应该优先走快速删除程序。\n单击“确认”以保留快速删除模板,若您认为快速删除理由不合,单击“取消”以移除快速删除模板。', '在頁面上找到快速刪除模板,要保留嗎?\n\n當頁面同時侵犯版權又符合快速刪除標準時,應該優先走快速刪除程序。\n點擊「確認」以保留快速刪除模板,若您認為快速刪除理由不合,點擊「取消」以移除快速刪除模板。'))) { tag = oldcsd[0] + '\n' + tag; } pageobj.setPageText(tag); pageobj.setEditSummary(wgULS('本页面疑似侵犯著作权', '本頁面疑似侵犯版權')); pageobj.setChangeTags(Twinkle.changeTags); switch (Twinkle.getPref('copyvioWatchPage')) { case 'yes': pageobj.setWatchlist(true); break; case 'no': pageobj.setWatchlistFromPreferences(false); break; default: pageobj.setWatchlistFromPreferences(true); break; } // pageobj.setCreateOption('recreate'); pageobj.save(); if (Twinkle.getPref('markCopyvioPagesAsPatrolled')) { pageobj.patrol(); } }, copyvioList: function(pageobj) { var reason = '疑似侵權,侵權來源請見該頁面。'; pageobj.setAppendText('\n{{subst:DRItem|DRarticles=' + Morebits.pageNameNorm + '|Reason=' + Morebits.string.formatReasonText(reason) + '}}--~~~~'); pageobj.setEditSummary('加入[[' + Morebits.pageNameNorm + ']]'); pageobj.setChangeTags(Twinkle.changeTags); switch (Twinkle.getPref('xfdWatchDiscussion')) { case 'yes': pageobj.setWatchlist(true); break; case 'no': pageobj.setWatchlistFromPreferences(false); break; default: pageobj.setWatchlistFromPreferences(true); break; } pageobj.append(); } }; Twinkle.copyvio.callback.evaluate = function(e) { mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' ')); // for queen/king/whatever and country! var source = e.target.source.value; var usertalk = e.target.notify.checked; Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); if (!source.trim()) { Morebits.status.error(wgULS('错误', '錯誤'), wgULS('未指定侵权来源', '未指定侵權來源')); return; } var query, wikipedia_page, wikipedia_api, logpage, params; // eslint-disable-line no-unused-vars logpage = 'Wiktionary:删除请求'; params = { source: source, logpage: logpage, usertalk: usertalk}; Morebits.wiki.addCheckpoint(); // Updating data for the action completed event Morebits.wiki.actionCompleted.redirect = logpage; Morebits.wiki.actionCompleted.notice = wgULS('提报完成,将在几秒内刷新', '提報完成,將在幾秒內重新整理'); // Tagging file wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), wgULS('加入侵权模板到页面', '加入侵權模板到頁面')); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.copyvio.callbacks.tryTagging); Morebits.wiki.removeCheckpoint(); }; })(jQuery); // </nowiki> 07559ymd6q0adiz0cogib2lfodzdajw MediaWiki:Gadget-twinklediff.js 8 1368870 9943045 5800333 2026-10-12T15:41:12Z Xiplus 25402 Repo at 375b1e1: Remove support for deprecated SD (Flow) page 9943045 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { /* **************************************** *** twinklediff.js: Diff module **************************************** * Mode of invocation: Tab on non-diff pages ("Last"); tabs on diff pages ("Since", "Since mine", "Current") * Active on: Existing non-special pages */ Twinkle.diff = function twinklediff() { if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) { return; } Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: 'prev'}), wgULS('最后', '最後'), 'tw-lastdiff', wgULS('显示最后变更', '顯示最後變更')); // Show additional tabs only on diff pages if (mw.config.get('wgDiffNewId')) { Twinkle.addPortletLink(function() { Twinkle.diff.evaluate(false); }, '自上', 'tw-since', wgULS('显示与上一修订版本间的差异', '顯示與上一修訂版本間的差異')); Twinkle.addPortletLink(function() { Twinkle.diff.evaluate(true); }, '自我', 'tw-sincemine', wgULS('显示与我做出的修订版本的差异', '顯示與我做出的修訂版本的差異')); Twinkle.addPortletLink(mw.util.getUrl(mw.config.get('wgPageName'), {diff: 'cur', oldid: mw.config.get('wgDiffNewId')}), wgULS('当前', '目前'), 'tw-curdiff', wgULS('显示与当前版本间的差异', '顯示與目前版本間的差異')); } }; Twinkle.diff.evaluate = function twinklediffEvaluate(me) { var user; if (me) { user = mw.config.get('wgUserName'); } else { var node = document.getElementById('mw-diff-ntitle2'); if (!node) { // nothing to do? return; } user = $(node).find('a').first().text(); } var query = { 'prop': 'revisions', 'action': 'query', 'titles': mw.config.get('wgPageName'), 'rvlimit': 1, 'rvprop': [ 'ids', 'user' ], 'rvstartid': mw.config.get('wgCurRevisionId') - 1, // i.e. not the current one 'rvuser': user }; Morebits.status.init(document.getElementById('mw-content-text')); var wikipedia_api = new Morebits.wiki.api(wgULS('抓取最初贡献者信息', '抓取最初貢獻者資訊'), query, Twinkle.diff.callbacks.main); wikipedia_api.params = { user: user }; wikipedia_api.post(); }; Twinkle.diff.callbacks = { main: function(self) { var xmlDoc = self.responseXML; var revid = $(xmlDoc).find('rev').attr('revid'); if (!revid) { self.statelem.error(wgULS('未找到合适的早期版本,或 ' + self.params.user + ' 是唯一贡献者。取消。', '未找到合適的早期版本,或 ' + self.params.user + ' 是唯一貢獻者。取消。')); return; } window.location = mw.util.getUrl(mw.config.get('wgPageName'), { diff: mw.config.get('wgCurRevisionId'), oldid: revid }); } }; })(jQuery); // </nowiki> t56qfikhh2qfr5oa5oy0zc2cy60qfix MediaWiki:Gadget-twinklefluff.js 8 1368871 9943046 6333177 2026-10-12T15:41:15Z Xiplus 25402 Repo at 375b1e1: add link to PermaLink when show exact #rev (#402); Fix rollback links for top rev in history 9943046 javascript text/javascript // <nowiki> (function($) { /* **************************************** *** twinklefluff.js: Revert/rollback module **************************************** * Mode of invocation: Links on contributions, recent changes, history, and diff pages * Active on: Diff pages, history pages, Special:RecentChanges(Linked), and Special:Contributions */ /** Twinklefluff revert and antivandalism utility */ Twinkle.fluff = function twinklefluff() { // Only proceed if the user can actually edit the page in question // (see #632 for contribs issue). wgIsProbablyEditable should take // care of namespace/contentModel restrictions as well as explicit // protections; it won't take care of cascading or TitleBlacklist. if (mw.config.get('wgIsProbablyEditable')) { // wgDiffOldId included for clarity in if else loop [[phab:T214985]] if (mw.config.get('wgDiffNewId') || mw.config.get('wgDiffOldId')) { // Reload alongside the revision slider mw.hook('wikipage.diff').add(function () { Twinkle.fluff.addLinks.diff(); }); } else if (mw.config.get('wgAction') === 'view' && mw.config.get('wgRevisionId') && mw.config.get('wgCurRevisionId') !== mw.config.get('wgRevisionId')) { Twinkle.fluff.addLinks.oldid(); } else if (mw.config.get('wgAction') === 'history') { Twinkle.fluff.addLinks.history(); } } else if (mw.config.get('wgNamespaceNumber') === -1) { Twinkle.fluff.skipTalk = !Twinkle.getPref('openTalkPageOnAutoRevert'); Twinkle.fluff.rollbackInPlace = Twinkle.getPref('rollbackInPlace'); if (mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') { Twinkle.fluff.addLinks.contributions(); } else if (mw.config.get('wgCanonicalSpecialPageName') === 'Recentchanges' || mw.config.get('wgCanonicalSpecialPageName') === 'Recentchangeslinked') { // Reload with recent changes updates // structuredChangeFilters.ui.initialized is just on load mw.hook('wikipage.content').add(function(item) { if (item.is('div')) { Twinkle.fluff.addLinks.recentchanges(); } }); } } }; // A list of usernames, usually only bots, that vandalism revert is jumped // over; that is, if vandalism revert was chosen on such username, then its // target is on the revision before. This is for handling quick bots that // makes edits seconds after the original edit is made. This only affects // vandalism rollback; for good faith rollback, it will stop, indicating a bot // has no faith, and for normal rollback, it will rollback that edit. Twinkle.fluff.trustedBots = [ 'A2093064-bot' ]; Twinkle.fluff.skipTalk = null; Twinkle.fluff.rollbackInPlace = null; // String to insert when a username is hidden Twinkle.fluff.hiddenName = wgULS('已隐藏的用户', '已隱藏的使用者'); // Consolidated construction of fluff links Twinkle.fluff.linkBuilder = { spanTag: function(color, content) { var span = document.createElement('span'); span.style.color = color; span.appendChild(document.createTextNode(content)); return span; }, buildLink: function(color, text) { var link = document.createElement('a'); link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', '[')); link.appendChild(Twinkle.fluff.linkBuilder.spanTag(color, text)); link.appendChild(Twinkle.fluff.linkBuilder.spanTag('Black', ']')); link.href = '#'; return link; }, /** * @param {string} [vandal=null] - Username of the editor being reverted * Provide a falsey value if the username is hidden, defaults to null * @param {boolean} inline - True to create two links in a span, false * to create three links in a div (optional) * @param {number|string} [rev=wgCurRevisionId] - Revision ID being reverted (optional) * @param {string} [page=wgPageName] - Page being reverted (optional) */ rollbackLinks: function(vandal, inline, rev, page) { vandal = vandal || null; var elem = inline ? 'span' : 'div'; var revNode = document.createElement(elem); rev = parseInt(rev, 10); if (rev) { revNode.setAttribute('id', 'tw-revert' + rev); } else { revNode.setAttribute('id', 'tw-revert'); } var normNode = document.createElement('strong'); var vandNode = document.createElement('strong'); var normLink = Twinkle.fluff.linkBuilder.buildLink('SteelBlue', '回退'); var vandLink = Twinkle.fluff.linkBuilder.buildLink('Red', wgULS('破坏', '破壞')); $(normLink).click(function(e) { e.preventDefault(); Twinkle.fluff.revert('norm', vandal, rev, page); Twinkle.fluff.disableLinks(revNode); }); $(vandLink).click(function(e) { e.preventDefault(); Twinkle.fluff.revert('vand', vandal, rev, page); Twinkle.fluff.disableLinks(revNode); }); vandNode.appendChild(vandLink); normNode.appendChild(normLink); var separator = inline ? ' ' : ' || '; if (!inline) { var agfNode = document.createElement('strong'); var agfLink = Twinkle.fluff.linkBuilder.buildLink('DarkOliveGreen', '回退(AGF)'); $(agfLink).click(function(e) { e.preventDefault(); Twinkle.fluff.revert('agf', vandal, rev, page); // Twinkle.fluff.disableLinks(revNode); // rollbackInPlace not relevant for any inline situations }); agfNode.appendChild(agfLink); revNode.appendChild(agfNode); } revNode.appendChild(document.createTextNode(separator)); revNode.appendChild(normNode); revNode.appendChild(document.createTextNode(separator)); revNode.appendChild(vandNode); return revNode; }, // Build [restore this revision] links restoreThisRevisionLink: function(revisionRef, inline) { // If not a specific revision number, should be wgDiffNewId/wgDiffOldId/wgRevisionId revisionRef = typeof revisionRef === 'number' ? revisionRef : mw.config.get(revisionRef); var elem = inline ? 'span' : 'div'; var revertToRevisionNode = document.createElement(elem); revertToRevisionNode.setAttribute('id', 'tw-revert-to-' + revisionRef); revertToRevisionNode.style.fontWeight = 'bold'; var revertToRevisionLink = Twinkle.fluff.linkBuilder.buildLink('SaddleBrown', wgULS('恢复此版本', '恢復此版本')); $(revertToRevisionLink).click(function(e) { e.preventDefault(); Twinkle.fluff.revertToRevision(revisionRef); }); if (inline) { revertToRevisionNode.appendChild(document.createTextNode(' ')); } revertToRevisionNode.appendChild(revertToRevisionLink); return revertToRevisionNode; } }; Twinkle.fluff.addLinks = { contributions: function() { // $('sp-contributions-footer-anon-range') relies on the fmbox // id in [[MediaWiki:Sp-contributions-footer-anon-range]] and // is used to show rollback/vandalism links for IP ranges var isRange = !!$('#sp-contributions-footer-anon-range')[0]; if (mw.config.exists('wgRelevantUserName') || isRange) { // Get the username these contributions are for var username = mw.config.get('wgRelevantUserName'); if (Twinkle.getPref('showRollbackLinks').indexOf('contribs') !== -1 || (mw.config.get('wgUserName') !== username && Twinkle.getPref('showRollbackLinks').indexOf('others') !== -1) || (mw.config.get('wgUserName') === username && Twinkle.getPref('showRollbackLinks').indexOf('mine') !== -1)) { var $list = $('#mw-content-text').find('ul li:has(span.mw-uctop):has(.mw-changeslist-diff)'); $list.each(function(key, current) { // revid is also available in the href of both // .mw-changeslist-date or .mw-changeslist-diff var page = $(current).find('.mw-contributions-title').text(); // Get username for IP ranges (wgRelevantUserName is null) if (isRange) { // The :not is possibly unnecessary, as it appears that // .mw-userlink is simply not present if the username is hidden username = $(current).find('.mw-userlink:not(.history-deleted)').text(); } // It's unlikely, but we can't easily check for revdel'd usernames // since only a strong element is provided, with no easy selector [[phab:T255903]] current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(username, true, current.dataset.mwRevid, page)); }); } } }, recentchanges: function() { if ( (mw.config.get('wgCanonicalSpecialPageName') === 'Recentchanges' && Twinkle.getPref('showRollbackLinks').indexOf('recentchanges') !== -1) || (mw.config.get('wgCanonicalSpecialPageName') === 'Recentchangeslinked' && Twinkle.getPref('showRollbackLinks').indexOf('recentchangeslinked') !== -1) ) { // Latest and revertable (not page creations, logs, categorizations, etc.) var $list = $('.mw-changeslist .mw-changeslist-last.mw-changeslist-src-mw-edit'); // Exclude top-level header if "group changes" preference is used // and find only individual lines or nested lines $list = $list.not('.mw-rcfilters-ui-highlights-enhanced-toplevel').find('.mw-changeslist-line-inner, td.mw-enhanced-rc-nested'); $list.each(function(key, current) { // The :not is possibly unnecessary, as it appears that // .mw-userlink is simply not present if the username is hidden var vandal = $(current).find('.mw-userlink:not(.history-deleted)').text(); var href = $(current).find('.mw-changeslist-diff').attr('href'); var rev = mw.util.getParamValue('diff', href); var page = current.dataset.targetPage; current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true, rev, page)); }); } }, history: function() { if (Twinkle.getPref('showRollbackLinks').indexOf('history') !== -1) { // All revs var histList = $('#pagehistory li').toArray(); // On first page of results, so add revert/rollback // links to the top revision if (!$('a.mw-firstlink').length) { var first = histList.shift(); var vandal = $(first).find('.mw-userlink:not(.history-deleted)').text(); // Check for first username different than the top user, // only apply rollback links if/when found // for faster than every for (var i = 0; i < histList.length; i++) { if ($(histList[i]).find('.mw-userlink').text() !== vandal) { first.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks(vandal, true)); break; } } } // oldid histList.forEach(function(rev) { // From restoreThisRevision, non-transferable // If the text has been revdel'd, it gets wrapped in a span with .history-deleted, // and href will be undefined (and thus oldid is NaN) var href = rev.querySelector('.mw-changeslist-date').href; var oldid = parseInt(mw.util.getParamValue('oldid', href), 10); if (!isNaN(oldid)) { rev.appendChild(Twinkle.fluff.linkBuilder.restoreThisRevisionLink(oldid, true)); } }); } }, diff: function() { // Autofill user talk links on diffs with vanarticle for easy warning, but don't autowarn var warnFromTalk = function(xtitle) { var talkLink = $('#mw-diff-' + xtitle + '2 .mw-usertoollinks a').first(); if (talkLink.length) { var extraParams = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm) + '&' + 'noautowarn=true'; // diffIDs for vanarticlerevid extraParams += '&vanarticlerevid='; extraParams += xtitle === 'otitle' ? mw.config.get('wgDiffOldId') : mw.config.get('wgDiffNewId'); var href = talkLink.attr('href'); if (href.indexOf('?') === -1) { talkLink.attr('href', href + '?' + extraParams); } else { talkLink.attr('href', href + '&' + extraParams); } } }; // Older revision warnFromTalk('otitle'); // Add quick-warn link to user talk link // Don't load if there's a single revision or weird diff (cur on latest) if (mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId'))) { // Add a [restore this revision] link to the older revision var oldTitle = document.getElementById('mw-diff-otitle1').parentNode; var revertToRevision = Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffOldId'); oldTitle.insertBefore(revertToRevision, oldTitle.firstChild); if (Twinkle.getPref('customRevertSummary').length > 0) { revertToRevision.appendChild(document.createTextNode(' || ')); var revertsummary = new Morebits.quickForm.element({ type: 'select', name: 'revertsummary' }); revertsummary.append({ type: 'option', label: wgULS('选择回退理由', '選擇回退理由'), value: '' }); $(Twinkle.getPref('customRevertSummary')).each(function(_, e) { revertsummary.append({ type: 'option', label: e.label, value: e.value }); }); revertToRevision.appendChild(revertsummary.render().childNodes[0]); } } // Newer revision warnFromTalk('ntitle'); // Add quick-warn link to user talk link // Add either restore or rollback links to the newer revision // Don't show if there's a single revision or weird diff (prev on first) if (document.getElementById('differences-nextlink')) { // Not latest revision, add [restore this revision] link to newer revision var newTitle = document.getElementById('mw-diff-ntitle1').parentNode; newTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffNewId'), newTitle.firstChild); } else if (Twinkle.getPref('showRollbackLinks').indexOf('diff') !== -1 && mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId') || document.getElementById('differences-prevlink'))) { // Normally .mw-userlink is a link, but if the // username is hidden, it will be a span with // .history-deleted as well. When a sysop views the // hidden content, the span contains the username in a // link element, which will *just* have // .mw-userlink. The below thus finds the first // instance of the class, which if hidden is the span // and thus text returns undefined. Technically, this // is a place where sysops *could* have more // information available to them (as above, via // &unhide=1), since the username will be available by // checking a.mw-userlink instead, but revert() will // need reworking around userHidden var vandal = $('#mw-diff-ntitle2').find('.mw-userlink')[0].text; var ntitle = document.getElementById('mw-diff-ntitle1').parentNode; ntitle.insertBefore(Twinkle.fluff.linkBuilder.rollbackLinks(vandal), ntitle.firstChild); } }, oldid: function() { // Add a [restore this revision] link on old revisions var title = document.getElementById('mw-revision-info').parentNode; title.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgRevisionId'), title.firstChild); } }; Twinkle.fluff.disableLinks = function disablelinks(parentNode) { // Array.from not available in IE11 :( $(parentNode).children().each(function(_ix, node) { node.innerHTML = node.textContent; // Feels like cheating $(node).css('font-weight', 'normal').css('color', 'darkgray'); }); }; Twinkle.fluff.revert = function revertPage(type, vandal, rev, page) { if (mw.util.isIPv6Address(vandal)) { vandal = Morebits.sanitizeIPv6(vandal); } var pagename = page || mw.config.get('wgPageName'); var revid = rev || mw.config.get('wgCurRevisionId'); var summary = ''; if (document.getElementsByName('revertsummary')[0] !== undefined) { summary = document.getElementsByName('revertsummary')[0].value; } if (Twinkle.fluff.rollbackInPlace) { var notifyStatus = document.createElement('span'); mw.notify(notifyStatus, { autoHide: false, title: '回退' + page, tag: 'twinklefluff_' + rev // Shouldn't be necessary given disableLink }); Morebits.status.init(notifyStatus); } else { Morebits.status.init(document.getElementById('mw-content-text')); $('#catlinks').remove(); } var params = { type: type, user: vandal, userHidden: !vandal, // Keep track of whether the username was hidden pagename: pagename, revid: revid, summary: summary }; var query = { action: 'query', prop: ['info', 'revisions'], titles: pagename, intestactions: 'edit', rvlimit: Twinkle.getPref('revertMaxRevisions'), rvprop: [ 'ids', 'timestamp', 'user' ], curtimestamp: '', meta: 'tokens', type: 'csrf' }; var wikipedia_api = new Morebits.wiki.api(wgULS('抓取较早修订版本信息', '抓取較早修訂版本資訊'), query, Twinkle.fluff.callbacks.main); wikipedia_api.params = params; wikipedia_api.post(); }; Twinkle.fluff.revertToRevision = function revertToRevision(oldrev) { var summary = ''; if (document.getElementsByName('revertsummary')[0] !== undefined) { summary = document.getElementsByName('revertsummary')[0].value; } Morebits.status.init(document.getElementById('mw-content-text')); var query = { action: 'query', prop: ['info', 'revisions'], titles: mw.config.get('wgPageName'), rvlimit: 1, rvstartid: oldrev, rvprop: [ 'ids', 'user' ], format: 'xml', curtimestamp: '', meta: 'tokens', type: 'csrf' }; var wikipedia_api = new Morebits.wiki.api(wgULS('抓取较早修订版本信息', '抓取較早修訂版本資訊'), query, Twinkle.fluff.callbacks.toRevision); wikipedia_api.params = { rev: oldrev, summary: summary }; wikipedia_api.post(); }; Twinkle.fluff.callbacks = { toRevision: function(apiobj) { var xmlDoc = apiobj.responseXML; var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10); var touched = $(xmlDoc).find('page').attr('touched'); var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp'); var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken'); var revertToRevID = parseInt($(xmlDoc).find('rev').attr('revid'), 10); var revertToUser = $(xmlDoc).find('rev').attr('user'); var revertToUserHidden = typeof $(xmlDoc).find('rev').attr('userhidden') === 'string'; if (revertToRevID !== apiobj.params.rev) { apiobj.statelem.error(wgULS('抓取到的修订版本与请求的修订版本不符,取消。', '抓取到的修訂版本與請求的修訂版本不符,取消。')); return; } var optional_summary = prompt(wgULS('请输入回退理由:', '請輸入回退理由:') + ' ', apiobj.params.summary); // padded out to widen prompt in Firefox if (optional_summary === null) { apiobj.statelem.error(wgULS('由用户取消。', '由使用者取消。')); return; } var summary = Twinkle.fluff.formatSummary(wgULS('回退到由$USER做出的修订版本', '回退到由$USER做出的修訂版本') + '[[Special:PermaLink/' + revertToRevID + '|' + revertToRevID + ']]', revertToUserHidden ? null : revertToUser, optional_summary); var query = { action: 'edit', title: mw.config.get('wgPageName'), summary: summary, tags: Twinkle.changeTags, token: csrftoken, undo: lastrevid, undoafter: revertToRevID, basetimestamp: touched, starttimestamp: loadtimestamp, minor: Twinkle.getPref('markRevertedPagesAsMinor').indexOf('torev') !== -1 ? true : undefined, bot: true }; // Handle watching, possible expiry if (Twinkle.getPref('watchRevertedPages').indexOf('torev') !== -1) { var watchOrExpiry = Twinkle.getPref('watchRevertedExpiry'); if (!watchOrExpiry || watchOrExpiry === 'no') { query.watchlist = 'nochange'; } else if (watchOrExpiry === 'default' || watchOrExpiry === 'preferences') { query.watchlist = 'preferences'; } else { query.watchlist = 'watch'; // number allowed but not used in Twinkle.config.watchlistEnums if (typeof watchOrExpiry === 'string' && watchOrExpiry !== 'yes') { query.watchlistexpiry = watchOrExpiry; } } } Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName'); Morebits.wiki.actionCompleted.notice = '回退完成'; var wikipedia_api = new Morebits.wiki.api(wgULS('保存回退内容', '儲存回退內容'), query, Twinkle.fluff.callbacks.complete, apiobj.statelem); wikipedia_api.params = apiobj.params; wikipedia_api.post(); }, main: function(apiobj) { var xmlDoc = apiobj.responseXML; if (typeof $(xmlDoc).find('actions').attr('edit') === 'undefined') { apiobj.statelem.error("Unable to edit the page, it's probably protected."); return; } var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10); var touched = $(xmlDoc).find('page').attr('touched'); var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp'); var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken'); var revs = $(xmlDoc).find('rev'); var statelem = apiobj.statelem; var params = apiobj.params; if (revs.length < 1) { statelem.error(wgULS('没有其它修订版本,无法回退', '沒有其它修訂版本,無法回退')); return; } var top = revs[0]; var lastuser = top.getAttribute('user'); if (lastrevid < params.revid) { Morebits.status.error(wgULS('错误', '錯誤'), wgULS([ '从服务器获取的最新修订版本ID ', Morebits.htmlNode('strong', lastrevid), ' 小于目前所显示的修订版本ID。这可能意味着当前修订版本已被删除、服务器延迟、或抓取到了坏掉的信息。取消。' ], [ '從伺服器取得的最新修訂版本ID ', Morebits.htmlNode('strong', lastrevid), ' 小於目前所顯示的修訂版本ID。這可能意味著當前修訂版本已被刪除、伺服器延遲、或擷取到了壞掉的資訊。取消。' ])); return; } // Used for user-facing alerts, messages, etc., not edits or summaries var userNorm = params.user || Twinkle.fluff.hiddenName; var index = 1; if (params.revid !== lastrevid) { Morebits.status.warn('警告', wgULS([ '最新修订版本 ', Morebits.htmlNode('strong', lastrevid), ' 与我们的修订版本 ', Morebits.htmlNode('strong', params.revid), '不同' ], [ '最新修訂版本 ', Morebits.htmlNode('strong', lastrevid), ' 與我們的修訂版本 ', Morebits.htmlNode('strong', params.revid), ' 不同' ])); if (lastuser === params.user) { switch (params.type) { case 'vand': Morebits.status.info(wgULS('信息', '資訊'), [ wgULS('最新修订版本由 ', '最新修訂版本由 '), Morebits.htmlNode('strong', userNorm), wgULS(' 做出,因我们假定破坏,继续回退操作。', ' 做出,因我們假定破壞,繼續回退操作。') ]); break; case 'agf': Morebits.status.warn('警告', [ wgULS('最新修订版本由 ', '最新修訂版本由 '), Morebits.htmlNode('strong', userNorm), wgULS(' 做出,因我们假定善意,取消回退操作,因为问题可能已被修复。', ' 做出,因我們假定善意,取消回退操作,因為問題可能已被修復。') ]); return; default: Morebits.status.warn('提示', [ wgULS('最新修订版本由 ', '最新修訂版本由 '), Morebits.htmlNode('strong', userNorm), wgULS(' 做出,但我们还是不回退了。', ' 做出,但我們還是不回退了。') ]); return; } } else if (params.type === 'vand' && // Okay to test on user since it will either fail or sysop will correctly access it // Besides, none of the trusted bots are going to be revdel'd Twinkle.fluff.trustedBots.indexOf(top.getAttribute('user')) !== -1 && revs.length > 1 && revs[1].getAttribute('revid') === params.revid) { Morebits.status.info(wgULS('信息', '資訊'), wgULS([ '最新修订版本由 ', Morebits.htmlNode('strong', lastuser), ',一个可信的机器人做出,但之前的版本被认为是破坏,继续回退操作。' ], [ '最新修訂版本由 ', Morebits.htmlNode('strong', lastuser), ',一個可信的機器人做出,但之前的版本被認為是破壞,繼續回退操作。' ])); index = 2; } else { Morebits.status.error(wgULS('错误', '錯誤'), wgULS([ '最新修订版本由 ', Morebits.htmlNode('strong', lastuser), ' 做出,所以这个修订版本可能已经被回退了,取消回退操作。'], [ '最新修訂版本由 ', Morebits.htmlNode('strong', lastuser), ' 做出,所以這個修訂版本可能已經被回退了,取消回退操作。'])); return; } } else { // Expected revision is the same, so the users must match; // this allows sysops to know whether the users are the same params.user = lastuser; userNorm = params.user || Twinkle.fluff.hiddenName; } if (Twinkle.fluff.trustedBots.indexOf(params.user) !== -1) { switch (params.type) { case 'vand': Morebits.status.info(wgULS('信息', '資訊'), [ wgULS('将对 ', '將對 '), Morebits.htmlNode('strong', userNorm), wgULS(' 执行破坏回退,这是一个可信的机器人,我们假定您要回退前一个修订版本。', ' 執行破壞回退,這是一個可信的機器人,我們假定您要回退前一個修訂版本。') ]); index = 2; params.user = revs[1].getAttribute('user'); params.userHidden = revs[1].getAttribute('userhidden') === ''; break; case 'agf': Morebits.status.warn('提示', [ wgULS('将对 ', '將對 '), Morebits.htmlNode('strong', userNorm), wgULS(' 执行善意回退,但这是一个可信的机器人,取消回退操作。', ' 執行善意回退,但這是一個可信的機器人,取消回退操作。') ]); return; case 'norm': /* falls through */ default: var cont = confirm(wgULS('选择了常规回退,但最新修改是由一个可信的机器人(', '選擇了常規回退,但最新修改是由一個可信的機器人(') + userNorm + wgULS(')做出的。确定以回退前一个修订版本,取消以回退机器人的修改', ')做出的。確定以回退前一個修訂版本,取消以回退機器人的修改')); if (cont) { Morebits.status.info(wgULS('信息', '資訊'), [ wgULS('将对 ', '將對 '), Morebits.htmlNode('strong', userNorm), wgULS(' 执行常规回退,这是一个可信的机器人,基于确认,我们将回退前一个修订版本。', ' 執行常規回退,這是一個可信的機器人,基於確認,我們將回退前一個修訂版本。') ]); index = 2; params.user = revs[1].getAttribute('user'); params.userHidden = revs[1].getAttribute('userhidden') === ''; userNorm = params.user || Twinkle.fluff.hiddenName; } else { Morebits.status.warn('提示', [ wgULS('将对 ', '將對 '), Morebits.htmlNode('strong', userNorm), wgULS(' 执行常规回退,这是一个可信的机器人,基于确认,我们仍将回退这个修订版本。', ' 執行常規回退,這是一個可信的機器人,基於確認,我們仍將回退這個修訂版本。') ]); } break; } } var found = false; var count = 0; for (var i = index; i < revs.length; ++i) { ++count; if (revs[i].getAttribute('user') !== params.user) { found = i; break; } } if (!found) { statelem.error([ wgULS('未找到之前的修订版本,可能 ', '未找到之前的修訂版本,可能 '), Morebits.htmlNode('strong', userNorm), wgULS(' 是唯一贡献者,或这个用户连续做出了超过 ', ' 是唯一貢獻者,或這個用戶連續做出了超過 ') + mw.language.convertNumber(Twinkle.getPref('revertMaxRevisions')) + wgULS(' 次编辑。', ' 次編輯。') ]); return; } if (!count) { Morebits.status.error(wgULS('错误', '錯誤'), wgULS('我们将要回退0个修订版本,这没有意义,所以取消回退操作。可能是因为这个修订版本已经被回退,但修订版本ID仍是一样的。', '我們將要回退0個修訂版本,這沒有意義,所以取消回退操作。可能是因為這個修訂版本已經被回退,但修訂版本ID仍是一樣的。')); return; } var good_revision = revs[found]; var userHasAlreadyConfirmedAction = false; if (params.type !== 'vand' && count > 1) { if (!confirm(userNorm + wgULS(' 连续做出了 ', ' 連續做出了 ') + mw.language.convertNumber(count) + wgULS(' 次编辑,是否要全部回退?', ' 次編輯,是否要全部回退?'))) { Morebits.status.info('提示', wgULS('用户取消操作', '使用者取消操作')); return; } userHasAlreadyConfirmedAction = true; } params.count = count; params.goodid = good_revision.getAttribute('revid'); params.gooduser = good_revision.getAttribute('user'); params.gooduserHidden = good_revision.getAttribute('userhidden') === ''; statelem.status([ Morebits.htmlNode('strong', mw.language.convertNumber(count)), wgULS(' 个修订版本之前由 ', ' 個修訂版本之前由 '), Morebits.htmlNode('strong', params.gooduserHidden ? Twinkle.fluff.hiddenName : params.gooduser), wgULS(' 做出的修订版本 ', ' 做出的修訂版本 '), Morebits.htmlNode('strong', params.goodid) ]); var summary, extra_summary; switch (params.type) { case 'agf': extra_summary = prompt(wgULS('可选的编辑摘要:', '可選的編輯摘要:') + ' ', params.summary); // padded out to widen prompt in Firefox if (extra_summary === null) { statelem.error(wgULS('用户取消操作。', '使用者取消操作。')); return; } userHasAlreadyConfirmedAction = true; summary = Twinkle.fluff.formatSummary(wgULS('回退$USER做出的出于[[w:WP:AGF|善意]]的编辑', '回退$USER做出的出於[[w:WP:AGF|善意]]的編輯'), params.userHidden ? null : params.user, extra_summary); break; case 'vand': summary = Twinkle.fluff.formatSummary('回退$USER做出的' + params.count + wgULS('次编辑,到由', '次編輯,到由') + (params.gooduserHidden ? Twinkle.fluff.hiddenName : params.gooduser) + wgULS('做出的最后修订版本 ', '做出的最後修訂版本 '), params.userHidden ? null : params.user); break; case 'norm': /* falls through */ default: if (Twinkle.getPref('offerReasonOnNormalRevert')) { extra_summary = prompt(wgULS('可选的编辑摘要:', '可選的編輯摘要:') + ' ', params.summary); // padded out to widen prompt in Firefox if (extra_summary === null) { statelem.error(wgULS('用户取消操作。', '使用者取消操作。')); return; } userHasAlreadyConfirmedAction = true; } summary = Twinkle.fluff.formatSummary('回退$USER做出的' + params.count + wgULS('次编辑', '次編輯'), params.userHidden ? null : params.user, extra_summary); break; } if ((Twinkle.getPref('confirmOnFluff') || // Mobile user agent taken from [[en:MediaWiki:Gadget-confirmationRollback-mobile.js]] (Twinkle.getPref('confirmOnMobileFluff') && /Android|webOS|iPhone|iPad|iPod|BlackBerry|Mobile|Opera Mini/i.test(navigator.userAgent))) && !userHasAlreadyConfirmedAction && !confirm(wgULS('回退页面:您确定吗?', '回退頁面:您確定嗎?'))) { statelem.error(wgULS('用户取消操作。', '使用者取消操作。')); return; } // Decide whether to notify the user on success if (!Twinkle.fluff.skipTalk && Twinkle.getPref('openTalkPage').indexOf(params.type) !== -1 && !params.userHidden && mw.config.get('wgUserName') !== params.user) { params.notifyUser = true; // Pass along to the warn module params.vantimestamp = top.getAttribute('timestamp'); } var query = { action: 'edit', title: params.pagename, summary: summary, tags: Twinkle.changeTags, token: csrftoken, undo: lastrevid, undoafter: params.goodid, basetimestamp: touched, starttimestamp: loadtimestamp, minor: Twinkle.getPref('markRevertedPagesAsMinor').indexOf(params.type) !== -1 ? true : undefined, bot: true }; // Handle watching, possible expiry if (Twinkle.getPref('watchRevertedPages').indexOf(params.type) !== -1) { var watchOrExpiry = Twinkle.getPref('watchRevertedExpiry'); if (!watchOrExpiry || watchOrExpiry === 'no') { query.watchlist = 'nochange'; } else if (watchOrExpiry === 'default' || watchOrExpiry === 'preferences') { query.watchlist = 'preferences'; } else { query.watchlist = 'watch'; // number allowed but not used in Twinkle.config.watchlistEnums if (typeof watchOrExpiry === 'string' && watchOrExpiry !== 'yes') { query.watchlistexpiry = watchOrExpiry; } } } if (!Twinkle.fluff.rollbackInPlace) { Morebits.wiki.actionCompleted.redirect = params.pagename; } Morebits.wiki.actionCompleted.notice = '回退完成'; var wikipedia_api = new Morebits.wiki.api(wgULS('保存回退内容', '儲存回退內容'), query, Twinkle.fluff.callbacks.complete, statelem); wikipedia_api.params = params; wikipedia_api.post(); }, complete: function (apiobj) { // TODO Most of this is copy-pasted from Morebits.wiki.page#fnSaveSuccess. Unify it var xml = apiobj.getXML(); var $edit = $(xml).find('edit'); if ($(xml).find('captcha').length > 0) { apiobj.statelem.error(wgULS('不能回退,因维基服务器要求您输入验证码。', '不能回退,因維基伺服器要求您輸入驗證碼。')); } else if ($edit.attr('nochange') === '') { apiobj.statelem.error(wgULS('要回退到的版本与当前版本相同,没什么要做的', '要回退到的版本與目前版本相同,沒什麼要做的')); } else { apiobj.statelem.info('完成'); var params = apiobj.params; if (params.notifyUser && !params.userHidden) { // notifyUser only from main, not from toRevision Morebits.status.info(wgULS('信息', '資訊'), [wgULS('开启用户 ', '開啟使用者 '), Morebits.htmlNode('strong', params.user), wgULS(' 的讨论页', ' 的討論頁')]); var windowQuery = { title: 'User talk:' + params.user, action: 'edit', preview: 'yes', vanarticle: params.pagename.replace(/_/g, ' '), vanarticlerevid: params.revid, vantimestamp: params.vantimestamp, vanarticlegoodrevid: params.goodid, type: params.type, count: params.count }; switch (Twinkle.getPref('userTalkPageMode')) { case 'tab': window.open(mw.util.getUrl('', windowQuery), '_blank'); break; case 'blank': window.open(mw.util.getUrl('', windowQuery), '_blank', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800'); break; case 'window': /* falls through */ default: window.open(mw.util.getUrl('', windowQuery), window.name === 'twinklewarnwindow' ? '_blank' : 'twinklewarnwindow', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800'); break; } } } } }; // If builtInString contains the string "$USER", it will be replaced // by an appropriate user link if a user name is provided Twinkle.fluff.formatSummary = function(builtInString, userName, customString) { var result = builtInString; // append user's custom reason if (customString) { result += ':' + Morebits.string.toUpperCaseFirstChar(customString); } // find number of UTF-8 bytes the resulting string takes up, and possibly add // a contributions or contributions+talk link if it doesn't push the edit summary // over the 499-byte limit if (/\$USER/.test(builtInString)) { if (userName) { var resultLen = unescape(encodeURIComponent(result.replace('$USER', ''))).length; var contribsLink = '[[Special:Contributions/' + userName + '|' + userName + ']]'; var contribsLen = unescape(encodeURIComponent(contribsLink)).length; if (resultLen + contribsLen <= 499) { var talkLink = '([[User talk:' + userName + wgULS('|讨论]])', '|討論]])'); if (resultLen + contribsLen + unescape(encodeURIComponent(talkLink)).length <= 499) { result = Morebits.string.safeReplace(result, '$USER', contribsLink + talkLink); } else { result = Morebits.string.safeReplace(result, '$USER', contribsLink); } } else { result = Morebits.string.safeReplace(result, '$USER', userName); } } else { result = Morebits.string.safeReplace(result, '$USER', Twinkle.fluff.hiddenName); } } return result; }; })(jQuery); // </nowiki> 25770kywouaaualp9ym8hy607abimtj MediaWiki:Gadget-twinkleprotect.js 8 1368873 9943047 6333137 2026-10-12T15:41:21Z Xiplus 25402 Repo at 375b1e1: Remove support for deprecated SD (Flow) page 9943047 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { /* **************************************** *** twinkleprotect.js: Protect/RPP module **************************************** * Mode of invocation: Tab ("PP"/"RPP") * Active on: Non-special, non-MediaWiki pages */ // Note: a lot of code in this module is re-used/called by batchprotect. Twinkle.protect = function twinkleprotect() { if (mw.config.get('wgNamespaceNumber') < 0) { return; } Twinkle.addPortletLink(Twinkle.protect.callback, wgULS('保护', '保護'), 'tw-rpp', Morebits.userIsSysop ? wgULS('保护页面', '保護頁面') : wgULS('请求保护页面', '請求保護頁面')); }; Twinkle.protect.callback = function twinkleprotectCallback() { var Window = new Morebits.simpleWindow(620, 530); Window.setTitle(Morebits.userIsSysop ? wgULS('施行或请求保护页面', '施行或請求保護頁面') : wgULS('请求保护页面', '請求保護頁面')); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('保护模板', '保護模板'), 'Template:Protection templates'); Window.addFooterLink(wgULS('保护方针', '保護方針'), 'WP:PROT'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'WP:TW/DOC#protect'); var form = new Morebits.quickForm(Twinkle.protect.callback.evaluate); var actionfield = form.append({ type: 'field', label: wgULS('操作类型', '操作類別') }); if (Morebits.userIsSysop) { actionfield.append({ type: 'radio', name: 'actiontype', event: Twinkle.protect.callback.changeAction, list: [ { label: wgULS('保护页面', '保護頁面'), value: 'protect', checked: true } ] }); } actionfield.append({ type: 'radio', name: 'actiontype', event: Twinkle.protect.callback.changeAction, list: [ { label: wgULS('请求保护页面', '請求保護頁面'), value: 'request', tooltip: wgULS('如果您想在WP:RFPP请求保护此页', '如果您想在WP:RFPP請求保護此頁') + (Morebits.userIsSysop ? '而不是自行完成。' : '。'), checked: !Morebits.userIsSysop }, { label: wgULS('用保护模板标记此页', '用保護模板標記此頁'), value: 'tag', tooltip: wgULS('可以用此为页面加上合适的保护模板。', '可以用此為頁面加上合適的保護模板。'), disabled: mw.config.get('wgArticleId') === 0 } ] }); form.append({ type: 'field', label: wgULS('默认', '預設'), name: 'field_preset' }); form.append({ type: 'field', label: '1', name: 'field1' }); form.append({ type: 'field', label: '2', name: 'field2' }); form.append({ type: 'submit' }); var result = form.render(); Window.setContent(result); Window.display(); // We must init the controls var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.actiontype[0].dispatchEvent(evt); // get current protection level asynchronously Twinkle.protect.fetchProtectionLevel(); }; // Contains the current protection level in an object // Once filled, it will look something like: // { edit: { level: "sysop", expiry: <some date>, cascade: true }, ... } Twinkle.protect.currentProtectionLevels = {}; Twinkle.protect.fetchProtectionLevel = function twinkleprotectFetchProtectionLevel() { var api = new mw.Api(); var protectDeferred = api.get({ format: 'json', indexpageids: true, action: 'query', list: 'logevents', letype: 'protect', letitle: mw.config.get('wgPageName'), prop: 'info', inprop: 'protection', titles: mw.config.get('wgPageName') }); $.when.apply($, [protectDeferred]).done(function(protectData) { var pageid = protectData.query.pageids[0]; var page = protectData.query.pages[pageid]; var current = {}; $.each(page.protection, function(index, protection) { if (protection.type !== 'aft') { current[protection.type] = { level: protection.level, expiry: protection.expiry, cascade: protection.cascade === '' }; } }); // show the protection level and log info Twinkle.protect.hasProtectLog = !!protectData.query.logevents.length; Twinkle.protect.currentProtectionLevels = current; Twinkle.protect.callback.showLogAndCurrentProtectInfo(); }); }; Twinkle.protect.callback.showLogAndCurrentProtectInfo = function twinkleprotectCallbackShowLogAndCurrentProtectInfo() { var currentlyProtected = !$.isEmptyObject(Twinkle.protect.currentProtectionLevels); if (Twinkle.protect.hasProtectLog || Twinkle.protect.hasStableLog) { var $linkMarkup = $('<span>'); if (Twinkle.protect.hasProtectLog) { $linkMarkup.append( $('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: mw.config.get('wgPageName'), type: 'protect'}) + '">' + wgULS('保护日志', '保護日誌') + '</a>'), Twinkle.protect.hasStableLog ? $('<span> &bull; </span>') : null ); } Morebits.status.init($('div[name="hasprotectlog"] span')[0]); Morebits.status.warn( currentlyProtected ? wgULS('早前保护', '早前保護') : wgULS('此页面曾在过去被保护', '此頁面曾在過去被保護'), $linkMarkup[0] ); } Morebits.status.init($('div[name="currentprot"] span')[0]); var protectionNode = [], statusLevel = 'info'; if (currentlyProtected) { $.each(Twinkle.protect.currentProtectionLevels, function(type, settings) { var label = Morebits.string.toUpperCaseFirstChar(type); protectionNode.push($('<b>' + label + ': ' + settings.level + '</b>')[0]); if (settings.expiry === 'infinity') { protectionNode.push(wgULS('(无限期)', '(無限期)')); } else { protectionNode.push(wgULS('(过期:', '(過期:') + new Date(settings.expiry).toUTCString() + ') '); } if (settings.cascade) { protectionNode.push(wgULS('(连锁)', '(連鎖)')); } }); statusLevel = 'warn'; } else { protectionNode.push($('<b>' + wgULS('无保护', '無保護') + '</b>')[0]); } Morebits.status[statusLevel](wgULS('当前保护等级', '當前保護等級'), protectionNode); }; Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) { var field_preset; var field1; var field2; // var isTemplate = mw.config.get('wgNamespaceNumber') === 10 || mw.config.get('wgNamespaceNumber') === 828; switch (e.target.values) { case 'protect': field_preset = new Morebits.quickForm.element({ type: 'field', label: wgULS('默认', '預設'), name: 'field_preset' }); field_preset.append({ type: 'select', name: 'category', label: wgULS('选择默认:', '選擇預設:'), event: Twinkle.protect.callback.changePreset, list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypesAdmin : Twinkle.protect.protectionTypesCreate }); field2 = new Morebits.quickForm.element({ type: 'field', label: wgULS('保护设置', '保護設定'), name: 'field2' }); field2.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback field2.append({ type: 'div', name: 'hasprotectlog', label: ' ' }); // for existing pages if (mw.config.get('wgArticleId')) { field2.append({ type: 'checkbox', name: 'editmodify', event: Twinkle.protect.formevents.editmodify, list: [ { label: wgULS('修改编辑权限', '修改編輯權限'), value: 'editmodify', tooltip: wgULS('如果此项关闭,编辑权限将不会修改。', '如果此項關閉,編輯權限將不會修改。'), checked: true } ] }); var editlevel = field2.append({ type: 'select', name: 'editlevel', label: wgULS('编辑权限:', '編輯權限:'), event: Twinkle.protect.formevents.editlevel }); editlevel.append({ type: 'option', label: wgULS('(站点默认)', '(站點預設)'), value: 'all' }); editlevel.append({ type: 'option', label: wgULS('仅允许自动确认用户', '僅允許自動確認用戶'), value: 'autoconfirmed' }); editlevel.append({ type: 'option', label: wgULS('仅管理员', '僅管理員'), value: 'sysop', selected: true }); field2.append({ type: 'select', name: 'editexpiry', label: '终止时间:', event: function(e) { if (e.target.value === 'custom') { Twinkle.protect.doCustomExpiry(e.target); } $('input[name=small]', $(e.target).closest('form'))[0].checked = e.target.selectedIndex >= 8; // 1 month }, // default expiry selection is conditionally set in Twinkle.protect.callback.changePreset list: wgULS([ { label: '1小时', value: '1 hour' }, { label: '2小时', value: '2 hours' }, { label: '3小时', value: '3 hours' }, { label: '6小时', value: '6 hours' }, { label: '1日', value: '1 day' }, { label: '3日', value: '3 days' }, { label: '1周', value: '1 week' }, { label: '2周', value: '2 weeks' }, { label: '1月', value: '1 month' }, { label: '3月', value: '3 months' }, { label: '6月', value: '6 months' }, { label: '1年', value: '1 year' }, { label: '无限期', value: 'indefinite' }, { label: '自定义…', value: 'custom' } ], [ { label: '1小時', value: '1 hour' }, { label: '2小時', value: '2 hours' }, { label: '3小時', value: '3 hours' }, { label: '6小時', value: '6 hours' }, { label: '1日', value: '1 day' }, { label: '3日', value: '3 days' }, { label: '1周', value: '1 week' }, { label: '2周', value: '2 weeks' }, { label: '1月', value: '1 month' }, { label: '3月', value: '3 months' }, { label: '6月', value: '6 months' }, { label: '1年', value: '1 year' }, { label: '無限期', value: 'indefinite' }, { label: '自訂…', value: 'custom' } ]) }); field2.append({ type: 'checkbox', name: 'movemodify', event: Twinkle.protect.formevents.movemodify, list: [ { label: wgULS('修改移动权限', '修改移動權限'), value: 'movemodify', tooltip: wgULS('如果此项被关闭,移动权限将不被修改。', '如果此項被關閉,移動權限將不被修改。'), checked: true } ] }); var movelevel = field2.append({ type: 'select', name: 'movelevel', label: wgULS('移动权限:', '移動權限:'), event: Twinkle.protect.formevents.movelevel }); movelevel.append({ type: 'option', label: wgULS('(站点默认)', '(站點預設)'), value: 'all' }); movelevel.append({ type: 'option', label: wgULS('仅允许自动确认用户', '僅允許自動確認用戶'), value: 'autoconfirmed' }); movelevel.append({ type: 'option', label: wgULS('仅管理员', '僅管理員'), value: 'sysop', selected: true }); field2.append({ type: 'select', name: 'moveexpiry', label: wgULS('终止时间:', '終止時間:'), event: function(e) { if (e.target.value === 'custom') { Twinkle.protect.doCustomExpiry(e.target); } }, // default expiry selection is conditionally set in Twinkle.protect.callback.changePreset list: wgULS([ { label: '1小时', value: '1 hour' }, { label: '2小时', value: '2 hours' }, { label: '3小时', value: '3 hours' }, { label: '6小时', value: '6 hours' }, { label: '1日', value: '1 day' }, { label: '3日', value: '3 days' }, { label: '1周', value: '1 week' }, { label: '2周', value: '2 weeks' }, { label: '1月', value: '1 month' }, { label: '3月', value: '3 months' }, { label: '6月', value: '6 months' }, { label: '1年', value: '1 year' }, { label: '无限期', value: 'indefinite' }, { label: '自定义…', value: 'custom' } ], [ { label: '1小時', value: '1 hour' }, { label: '2小時', value: '2 hours' }, { label: '3小時', value: '3 hours' }, { label: '6小時', value: '6 hours' }, { label: '1日', value: '1 day' }, { label: '3日', value: '3 days' }, { label: '1周', value: '1 week' }, { label: '2周', value: '2 weeks' }, { label: '1月', value: '1 month' }, { label: '3月', value: '3 months' }, { label: '6月', value: '6 months' }, { label: '1年', value: '1 year' }, { label: '無限期', value: 'indefinite' }, { label: '自訂…', value: 'custom' } ]) }); } else { // for non-existing pages var createlevel = field2.append({ type: 'select', name: 'createlevel', label: wgULS('创建权限:', '建立權限:'), event: Twinkle.protect.formevents.createlevel }); createlevel.append({ type: 'option', label: '全部', value: 'all' }); createlevel.append({ type: 'option', label: wgULS('仅允许自动确认用户', '僅允許自動確認用戶'), value: 'autoconfirmed' }); createlevel.append({ type: 'option', label: wgULS('仅管理员', '僅管理員'), value: 'sysop', selected: true }); field2.append({ type: 'select', name: 'createexpiry', label: wgULS('终止时间:', '終止時間:'), event: function(e) { if (e.target.value === 'custom') { Twinkle.protect.doCustomExpiry(e.target); } }, list: wgULS([ { label: '1小时', value: '1 hour' }, { label: '2小时', value: '2 hours' }, { label: '3小时', value: '3 hours' }, { label: '6小时', value: '6 hours' }, { label: '1日', value: '1 day' }, { label: '3日', value: '3 days' }, { label: '1周', value: '1 week' }, { label: '2周', value: '2 weeks' }, { label: '1月', value: '1 month' }, { label: '3月', value: '3 months' }, { label: '6月', value: '6 months' }, { label: '1年', selected: true, value: '1 year' }, { label: '无限期', value: 'indefinite' }, { label: '自定义…', value: 'custom' } ], [ { label: '1小時', value: '1 hour' }, { label: '2小時', value: '2 hours' }, { label: '3小時', value: '3 hours' }, { label: '6小時', value: '6 hours' }, { label: '1日', value: '1 day' }, { label: '3日', value: '3 days' }, { label: '1周', value: '1 week' }, { label: '2周', value: '2 weeks' }, { label: '1月', value: '1 month' }, { label: '3月', value: '3 months' }, { label: '6月', value: '6 months' }, { label: '1年', selected: true, value: '1 year' }, { label: '無限期', value: 'indefinite' }, { label: '自訂…', value: 'custom' } ]) }); } field2.append({ type: 'textarea', name: 'protectReason', label: wgULS('理由(保护日志):', '理由(保護日誌):') }); if (!mw.config.get('wgArticleId')) { // tagging isn't relevant for non-existing pages break; } /* falls through */ case 'tag': field1 = new Morebits.quickForm.element({ type: 'field', label: wgULS('标记设置', '標記設定'), name: 'field1' }); field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' }); field1.append({ type: 'select', name: 'tagtype', label: wgULS('选择保护模板:', '選擇保護模板:'), list: Twinkle.protect.protectionTags, event: Twinkle.protect.formevents.tagtype }); field1.append({ type: 'checkbox', list: [ { name: 'small', label: wgULS('使用图标(small=yes)', '使用圖示(small=yes)'), tooltip: wgULS('将给模板加上|small=yes参数,显示成右上角的一把挂锁。', '將給模板加上|small=yes參數,顯示成右上角的一把掛鎖。') }, { name: 'noinclude', label: wgULS('用&lt;noinclude&gt;包裹保护模板', '用&lt;noinclude&gt;包裹保護模板'), tooltip: wgULS('将保护模板包裹在&lt;noinclude&gt;中', '將保護模板包裹在&lt;noinclude&gt;中'), checked: mw.config.get('wgNamespaceNumber') === 10 } ] }); break; case 'request': field_preset = new Morebits.quickForm.element({ type: 'field', label: wgULS('保护类型', '保護類別'), name: 'field_preset' }); field_preset.append({ type: 'select', name: 'category', label: wgULS('类型和理由:', '類別和理由:'), event: Twinkle.protect.callback.changePreset, list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate }); field1 = new Morebits.quickForm.element({ type: 'field', label: wgULS('设置', '設定'), name: 'field1' }); field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' }); field1.append({ type: 'select', name: 'expiry', label: wgULS('时长:', '時長:'), list: [ { label: wgULS('临时', '臨時'), value: 'temporary' }, { label: '永久', value: 'indefinite' }, { label: '', selected: true, value: '' } ] }); field1.append({ type: 'textarea', name: 'reason', label: '理由:' }); break; default: alert(wgULS('这玩意儿被逆袭的天邪鬼吃掉了!', '這玩意兒被逆襲的天邪鬼吃掉了!')); break; } var oldfield; if (field_preset) { oldfield = $(e.target.form).find('fieldset[name="field_preset"]')[0]; oldfield.parentNode.replaceChild(field_preset.render(), oldfield); } else { $(e.target.form).find('fieldset[name="field_preset"]').css('display', 'none'); } if (field1) { oldfield = $(e.target.form).find('fieldset[name="field1"]')[0]; oldfield.parentNode.replaceChild(field1.render(), oldfield); } else { $(e.target.form).find('fieldset[name="field1"]').css('display', 'none'); } if (field2) { oldfield = $(e.target.form).find('fieldset[name="field2"]')[0]; oldfield.parentNode.replaceChild(field2.render(), oldfield); } else { $(e.target.form).find('fieldset[name="field2"]').css('display', 'none'); } if (e.target.values === 'protect') { // fake a change event on the preset dropdown var evt = document.createEvent('Event'); evt.initEvent('change', true, true); e.target.form.category.dispatchEvent(evt); // reduce vertical height of dialog $(e.target.form).find('fieldset[name="field2"] select').parent().css({ display: 'inline-block', marginRight: '0.5em' }); } // re-add protection level and log info, if it's available Twinkle.protect.callback.showLogAndCurrentProtectInfo(); }; Twinkle.protect.formevents = { editmodify: function twinkleprotectFormEditmodifyEvent(e) { e.target.form.editlevel.disabled = !e.target.checked; e.target.form.editexpiry.disabled = !e.target.checked || (e.target.form.editlevel.value === 'all'); e.target.form.editlevel.style.color = e.target.form.editexpiry.style.color = e.target.checked ? '' : 'transparent'; }, editlevel: function twinkleprotectFormEditlevelEvent(e) { e.target.form.editexpiry.disabled = e.target.value === 'all'; }, movemodify: function twinkleprotectFormMovemodifyEvent(e) { // sync move settings with edit settings if applicable if (e.target.form.movelevel.disabled && !e.target.form.editlevel.disabled) { e.target.form.movelevel.value = e.target.form.editlevel.value; e.target.form.moveexpiry.value = e.target.form.editexpiry.value; } else if (e.target.form.editlevel.disabled) { e.target.form.movelevel.value = 'sysop'; e.target.form.moveexpiry.value = 'indefinite'; } e.target.form.movelevel.disabled = !e.target.checked; e.target.form.moveexpiry.disabled = !e.target.checked || (e.target.form.movelevel.value === 'all'); e.target.form.movelevel.style.color = e.target.form.moveexpiry.style.color = e.target.checked ? '' : 'transparent'; }, movelevel: function twinkleprotectFormMovelevelEvent(e) { e.target.form.moveexpiry.disabled = e.target.value === 'all'; }, createlevel: function twinkleprotectFormCreatelevelEvent(e) { e.target.form.createexpiry.disabled = e.target.value === 'all'; }, tagtype: function twinkleprotectFormTagtypeEvent(e) { e.target.form.small.disabled = e.target.form.noinclude.disabled = (e.target.value === 'none') || (e.target.value === 'noop'); } }; Twinkle.protect.doCustomExpiry = function twinkleprotectDoCustomExpiry(target) { var custom = prompt(wgULS('输入自定义终止时间。\n您可以使用相对时间,如“1 minute”或“19 days”,或绝对时间“yyyymmddhhmm”(如“200602011405”是2006年02月01日14:05(UTC))', '輸入自訂終止時間。\n您可以使用相對時間,如「1 minute」或「19 days」,或絕對時間「yyyymmddhhmm」(如「200602011405」是2006年02月01日14:05(UTC))'), ''); if (custom) { var option = document.createElement('option'); option.setAttribute('value', custom); option.textContent = custom; target.appendChild(option); target.value = custom; } else { target.selectedIndex = 0; } }; Twinkle.protect.protectionTypesAdmin = wgULS([ { label: '解除保护', value: 'unprotect' }, { label: '全保护', list: [ { label: '常规(全)', value: 'pp-protected' }, { label: '争议、编辑战(全)', value: 'pp-dispute' }, { label: '长期破坏(全)', value: 'pp-vandalism' }, { label: '高风险模板(全)', value: 'pp-template' }, { label: '已封禁用户的用户页(全)', value: 'pp-userpage' }, { label: '已封禁用户的讨论页(全)', value: 'pp-usertalk' } ] }, { label: '半保护', list: [ { label: '常规(半)', value: 'pp-semi-protected' }, { label: '长期破坏(半)', value: 'pp-semi-vandalism' }, { label: '违反生者传记方针(半)', value: 'pp-semi-blp' }, { label: '傀儡破坏(半)', value: 'pp-semi-sock' }, { label: '高风险模板(半)', value: 'pp-semi-template' }, { label: '已封禁用户的讨论页(半)', value: 'pp-semi-usertalk' } ] }, { label: '移动保护', list: [ { label: '常规(移动)', value: 'pp-move' }, { label: '争议、移动战(移动)', value: 'pp-move-dispute' }, { label: '移动破坏(移动)', value: 'pp-move-vandalism' }, { label: '高风险页面(移动)', value: 'pp-move-indef' } ] } ], [ { label: '解除保護', value: 'unprotect' }, { label: '全保護', list: [ { label: '常規(全)', value: 'pp-protected' }, { label: '爭議、編輯戰(全)', value: 'pp-dispute' }, { label: '長期破壞(全)', value: 'pp-vandalism' }, { label: '高風險模板(全)', value: 'pp-template' }, { label: '已封禁用戶的用戶頁(全)', value: 'pp-userpage' }, { label: '已封禁用戶的討論頁(全)', value: 'pp-usertalk' } ] }, { label: '半保護', list: [ { label: '常規(半)', value: 'pp-semi-protected' }, { label: '長期破壞(半)', value: 'pp-semi-vandalism' }, { label: '違反生者傳記方針(半)', value: 'pp-semi-blp' }, { label: '傀儡破壞(半)', value: 'pp-semi-sock' }, { label: '高風險模板(半)', value: 'pp-semi-template' }, { label: '已封禁用戶的討論頁(半)', value: 'pp-semi-usertalk' } ] }, { label: '移動保護', list: [ { label: '常規(移動)', value: 'pp-move' }, { label: '爭議、移動戰(移動)', value: 'pp-move-dispute' }, { label: '移動破壞(移動)', value: 'pp-move-vandalism' }, { label: '高風險頁面(移動)', value: 'pp-move-indef' } ] } ]); Twinkle.protect.protectionTypesCreateOnly = wgULS([ { label: '白纸保护', list: [ { label: '常规(白纸)', value: 'pp-create' }, { label: '多次重复创建(白纸)', value: 'pp-create-repeat' }, { label: '已封禁用户的用户页(白纸)', value: 'pp-create-userpage' } ] } ], [ { label: '白紙保護', list: [ { label: '常規(白紙)', value: 'pp-create' }, { label: '多次重複建立(白紙)', value: 'pp-create-repeat' }, { label: '已封禁用戶的用戶頁(白紙)', value: 'pp-create-userpage' } ] } ]); Twinkle.protect.protectionTypes = Twinkle.protect.protectionTypesAdmin.concat( Twinkle.protect.protectionTypesCreateOnly); Twinkle.protect.protectionTypesCreate = wgULS([ { label: '解除保护', value: 'unprotect' } ], [ { label: '解除保護', value: 'unprotect' } ]).concat(Twinkle.protect.protectionTypesCreateOnly); // NOTICE: keep this synched with [[MediaWiki:Protect-dropdown]] Twinkle.protect.protectionPresetsInfo = wgULS({ 'pp-protected': { edit: 'sysop', move: 'sysop', reason: null }, 'pp-dispute': { edit: 'sysop', move: 'sysop', reason: '编辑战' }, 'pp-vandalism': { edit: 'sysop', move: 'sysop', reason: '被自动确认用户破坏' }, 'pp-template': { edit: 'sysop', editexpiry: 'indefinite', move: 'sysop', moveexpiry: 'indefinite', reason: '高风险模板' }, 'pp-userpage': { edit: 'sysop', editexpiry: 'indefinite', move: 'sysop', moveexpiry: 'indefinite', reason: '被永久封禁的用户页', template: 'noop' }, 'pp-usertalk': { edit: 'sysop', move: 'sysop', reason: '已封禁用户滥用其对话页' }, 'pp-semi-vandalism': { edit: 'autoconfirmed', reason: '被IP用户或新用户破坏', template: 'pp-vandalism' }, 'pp-semi-blp': { edit: 'autoconfirmed', reason: 'IP用户或新用户违反生者传记方针' }, 'pp-semi-usertalk': { edit: 'autoconfirmed', move: 'sysop', reason: '已封禁用户滥用其对话页' }, 'pp-semi-template': { // removed for now edit: 'autoconfirmed', editexpiry: 'indefinite', move: 'sysop', moveexpiry: 'indefinite', reason: '高风险模板', template: 'pp-template' }, 'pp-semi-sock': { edit: 'autoconfirmed', reason: '持续的傀儡破坏' }, 'pp-semi-protected': { edit: 'autoconfirmed', reason: null, template: 'pp-protected' }, 'pp-move': { move: 'sysop', reason: null }, 'pp-move-dispute': { move: 'sysop', reason: '页面移动战' }, 'pp-move-vandalism': { move: 'sysop', reason: '移动破坏' }, 'pp-move-indef': { move: 'sysop', moveexpiry: 'indefinite', reason: '高风险页面' }, 'unprotect': { edit: 'all', move: 'all', create: 'all', reason: null, template: 'none' }, 'pp-create': { create: 'autoconfirmed', reason: null }, 'pp-create-repeat': { create: 'autoconfirmed', reason: '多次重复创建' }, 'pp-create-userpage': { create: 'sysop', createexpiry: 'indefinite', reason: '被永久封禁的用户页' } }, { 'pp-protected': { edit: 'sysop', move: 'sysop', reason: null }, 'pp-dispute': { edit: 'sysop', move: 'sysop', reason: '編輯戰' }, 'pp-vandalism': { edit: 'sysop', move: 'sysop', reason: '被自動確認用戶破壞' }, 'pp-template': { edit: 'sysop', editexpiry: 'indefinite', move: 'sysop', moveexpiry: 'indefinite', reason: '高風險模板' }, 'pp-userpage': { edit: 'sysop', editexpiry: 'indefinite', move: 'sysop', moveexpiry: 'indefinite', reason: '被永久封禁的用戶頁', template: 'noop' }, 'pp-usertalk': { edit: 'sysop', move: 'sysop', reason: '已封禁用戶濫用其對話頁' }, 'pp-semi-vandalism': { edit: 'autoconfirmed', reason: '被IP用戶或新用戶破壞', template: 'pp-vandalism' }, 'pp-semi-blp': { edit: 'autoconfirmed', reason: 'IP用戶或新用戶違反生者傳記方針' }, 'pp-semi-usertalk': { edit: 'autoconfirmed', move: 'sysop', reason: '已封禁用戶濫用其對話頁' }, 'pp-semi-template': { // removed for now edit: 'autoconfirmed', move: 'sysop', reason: '高風險模板', template: 'pp-template' }, 'pp-semi-sock': { edit: 'autoconfirmed', reason: '持續的傀儡破壞' }, 'pp-semi-protected': { edit: 'autoconfirmed', reason: null, template: 'pp-protected' }, 'pp-move': { move: 'sysop', reason: null }, 'pp-move-dispute': { move: 'sysop', reason: '頁面移動戰' }, 'pp-move-vandalism': { move: 'sysop', reason: '移動破壞' }, 'pp-move-indef': { move: 'sysop', moveexpiry: 'indefinite', reason: '高風險頁面' }, 'unprotect': { edit: 'all', move: 'all', create: 'all', reason: null, template: 'none' }, 'pp-create': { create: 'autoconfirmed', reason: null }, 'pp-create-repeat': { create: 'autoconfirmed', reason: '多次重複建立' }, 'pp-create-userpage': { create: 'sysop', createexpiry: 'indefinite', reason: '被永久封禁的用戶頁' } }); Twinkle.protect.protectionTags = wgULS([ { label: '无(移除现有模板)', value: 'none' }, { label: '无(不移除现有模板)', value: 'noop' }, { label: '全保护模板', list: [ { label: '{{pp-dispute}}: 争议', value: 'pp-dispute', selected: true }, { label: '{{pp-usertalk}}: 封禁的用户', value: 'pp-usertalk' } ] }, { label: '全、半保护模板', list: [ { label: '{{pp-vandalism}}: 破坏', value: 'pp-vandalism' }, { label: '{{pp-template}}: 高风险模板', value: 'pp-template' }, { label: '{{pp-protected}}: 常规', value: 'pp-protected' } ] }, { label: '半保护模板', list: [ { label: '{{pp-semi-usertalk}}: 封禁的用户', value: 'pp-semi-usertalk' }, { label: '{{pp-semi-sock}}: 傀儡', value: 'pp-semi-sock' }, { label: '{{pp-semi-blp}}: 生者传记', value: 'pp-semi-blp' }, { label: '{{pp-semi-indef}}: 长期', value: 'pp-semi-indef' } ] }, { label: '移动保护模板', list: [ { label: '{{pp-move-dispute}}: 争议', value: 'pp-move-dispute' }, { label: '{{pp-move-vandalism}}: 破坏', value: 'pp-move-vandalism' }, { label: '{{pp-move-indef}}: 长期', value: 'pp-move-indef' }, { label: '{{pp-move}}: 常规', value: 'pp-move' } ] } ], [ { label: '無(移除現有模板)', value: 'none' }, { label: '無(不移除現有模板)', value: 'noop' }, { label: '全保護模板', list: [ { label: '{{pp-dispute}}: 爭議', value: 'pp-dispute', selected: true }, { label: '{{pp-usertalk}}: 封禁的用戶', value: 'pp-usertalk' } ] }, { label: '全、半保護模板', list: [ { label: '{{pp-vandalism}}: 破壞', value: 'pp-vandalism' }, { label: '{{pp-template}}: 高風險模板', value: 'pp-template' }, { label: '{{pp-protected}}: 常規', value: 'pp-protected' } ] }, { label: '半保護模板', list: [ { label: '{{pp-semi-usertalk}}: 封禁的用戶', value: 'pp-semi-usertalk' }, { label: '{{pp-semi-sock}}: 傀儡', value: 'pp-semi-sock' }, { label: '{{pp-semi-blp}}: 生者傳記', value: 'pp-semi-blp' }, { label: '{{pp-semi-indef}}: 長期', value: 'pp-semi-indef' } ] }, { label: '移動保護模板', list: [ { label: '{{pp-move-dispute}}: 爭議', value: 'pp-move-dispute' }, { label: '{{pp-move-vandalism}}: 破壞', value: 'pp-move-vandalism' }, { label: '{{pp-move-indef}}: 長期', value: 'pp-move-indef' }, { label: '{{pp-move}}: 常規', value: 'pp-move' } ] } ]); Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) { var form = e.target.form; var actiontypes = form.actiontype; var actiontype; for (var i = 0; i < actiontypes.length; i++) { if (!actiontypes[i].checked) { continue; } actiontype = actiontypes[i].values; break; } if (actiontype === 'protect') { // actually protecting the page var item = Twinkle.protect.protectionPresetsInfo[form.category.value]; if (mw.config.get('wgArticleId')) { if (item.edit) { form.editmodify.checked = true; Twinkle.protect.formevents.editmodify({ target: form.editmodify }); form.editlevel.value = item.edit; Twinkle.protect.formevents.editlevel({ target: form.editlevel }); form.editexpiry.value = item.editexpiry || '1 week'; } else { form.editmodify.checked = false; Twinkle.protect.formevents.editmodify({ target: form.editmodify }); } if (item.move) { form.movemodify.checked = true; Twinkle.protect.formevents.movemodify({ target: form.movemodify }); form.movelevel.value = item.move; Twinkle.protect.formevents.movelevel({ target: form.movelevel }); form.moveexpiry.value = item.moveexpiry || '1 week'; } else { form.movemodify.checked = false; Twinkle.protect.formevents.movemodify({ target: form.movemodify }); } } else { if (item.create) { form.createlevel.value = item.create; Twinkle.protect.formevents.createlevel({ target: form.createlevel }); form.createexpiry.value = item.createexpiry || '1 week'; } } var reasonField = actiontype === 'protect' ? form.protectReason : form.reason; if (item.reason) { reasonField.value = item.reason; } else { reasonField.value = ''; } // sort out tagging options if (mw.config.get('wgArticleId')) { if (form.category.value === 'unprotect') { form.tagtype.value = 'none'; } else { form.tagtype.value = item.template ? item.template : form.category.value; } Twinkle.protect.formevents.tagtype({ target: form.tagtype }); if (/template/.test(form.category.value)) { form.noinclude.checked = true; form.editexpiry.value = form.moveexpiry.value = 'indefinite'; } else if (mw.config.get('wgNamespaceNumber') !== 10) { form.noinclude.checked = false; } } } else { // RPP request if (form.category.value === 'unprotect') { form.expiry.value = ''; form.expiry.disabled = true; } else { form.expiry.value = ''; form.expiry.disabled = false; } } }; Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) { var form = e.target; var actiontypes = form.actiontype; var actiontype; for (var i = 0; i < actiontypes.length; i++) { if (!actiontypes[i].checked) { continue; } actiontype = actiontypes[i].values; break; } var tagparams; if (!mw.config.get('wgArticleId')) { tagparams = { tag: 'noop' }; } else if (actiontype === 'tag' || (actiontype === 'protect' && mw.config.get('wgArticleId'))) { tagparams = { tag: form.tagtype.value, reason: (form.tagtype.value === 'pp-protected' || form.tagtype.value === 'pp-semi-protected' || form.tagtype.value === 'pp-move') && form.protectReason ? form.protectReason.value : null, expiry: actiontype === 'protect' ? form.editmodify.checked ? form.editexpiry.value : form.movemodify.checked ? form.moveexpiry.value : null : null, small: form.small.checked, noinclude: form.noinclude.checked }; } switch (actiontype) { case 'protect': // protect the page Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName'); Morebits.wiki.actionCompleted.notice = wgULS('保护完成', '保護完成'); var statusInited = false; var thispage; var allDone = function twinkleprotectCallbackAllDone() { if (thispage) { thispage.getStatusElement().info('完成'); } if (tagparams) { Twinkle.protect.callbacks.taggingPageInitial(tagparams); } }; var protectIt = function twinkleprotectCallbackProtectIt(next) { thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), wgULS('保护页面', '保護頁面')); if (mw.config.get('wgArticleId')) { if (form.editmodify.checked) { thispage.setEditProtection(form.editlevel.value, form.editexpiry.value); } if (form.movemodify.checked) { thispage.setMoveProtection(form.movelevel.value, form.moveexpiry.value); } } else { thispage.setCreateProtection(form.createlevel.value, form.createexpiry.value); thispage.setWatchlist(false); } if (form.protectReason.value) { thispage.setEditSummary(form.protectReason.value); thispage.setChangeTags(Twinkle.changeTags); } else { alert(wgULS('您必须输入保护理由,这将被记录在保护日志中。', '您必須輸入保護理由,這將被記錄在保護日誌中。')); return; } if (!statusInited) { Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); statusInited = true; } thispage.protect(next); }; if ((form.editmodify && form.editmodify.checked) || (form.movemodify && form.movemodify.checked) || !mw.config.get('wgArticleId')) { protectIt(allDone); } else { alert(wgULS('请告诉Twinkle要做什么!\n如果您只是想标记该页,请选择上面的“用保护模板标记此页”选项。', '請告訴Twinkle要做什麼!\n如果您只是想標記該頁,請選擇上面的「用保護模板標記此頁」選項。')); } break; case 'tag': // apply a protection template Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName'); Morebits.wiki.actionCompleted.followRedirect = false; Morebits.wiki.actionCompleted.notice = wgULS('标记完成', '標記完成'); Twinkle.protect.callbacks.taggingPageInitial(tagparams); break; case 'request': // file request at RPP var typename, typereason; switch (form.category.value) { case 'pp-dispute': case 'pp-vandalism': case 'pp-template': case 'pp-usertalk': case 'pp-protected': typename = '全保护'; break; case 'pp-semi-vandalism': case 'pp-semi-usertalk': case 'pp-semi-template': // removed for now case 'pp-semi-sock': case 'pp-semi-blp': case 'pp-semi-protected': typename = '半保护'; break; case 'pp-move': case 'pp-move-dispute': case 'pp-move-indef': case 'pp-move-vandalism': typename = '移动保护'; break; case 'pp-create': case 'pp-create-offensive': case 'pp-create-blp': case 'pp-create-salt': case 'pp-create-userpage': case 'pp-create-repeat': typename = '白纸保护'; break; case 'unprotect': /* falls through */ default: typename = '解除保护'; break; } switch (form.category.value) { case 'pp-dispute': typereason = '争议、编辑战'; break; case 'pp-vandalism': case 'pp-semi-vandalism': typereason = '长期破坏'; break; case 'pp-template': case 'pp-semi-template': // removed for now typereason = '高风险模板'; break; case 'pp-userpage': case 'pp-create-userpage': typereason = '被永久封禁的用户页'; break; case 'pp-usertalk': case 'pp-semi-usertalk': typereason = '已封禁用户的讨论页'; break; case 'pp-semi-sock': typereason = '傀儡破坏'; break; case 'pp-semi-blp': typereason = '违反生者传记方针'; break; case 'pp-move-dispute': typereason = '争议、移动战'; break; case 'pp-move-vandalism': typereason = '移动破坏'; break; case 'pp-move-indef': typereason = '高风险页面'; break; case 'pp-create-repeat': typereason = '多次重复创建'; break; default: typereason = ''; break; } var reason = typereason; if (form.reason.value !== '') { if (typereason !== '') { reason += ':'; } reason += form.reason.value; } if (reason !== '' && reason.charAt(reason.length - 1) !== '。') { reason += '。'; } var rppparams = { reason: reason, typename: typename, category: form.category.value, expiry: form.expiry.value }; Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); var rppName = 'Wikipedia:请求保护页面'; // Updating data for the action completed event Morebits.wiki.actionCompleted.redirect = rppName; Morebits.wiki.actionCompleted.notice = wgULS('提名完成,重定向到讨论页', '提名完成,重定向到討論頁'); var rppPage = new Morebits.wiki.page(rppName, wgULS('请求保护页面', '請求保護頁面')); rppPage.setFollowRedirect(true); rppPage.setCallbackParameters(rppparams); rppPage.load(Twinkle.protect.callbacks.fileRequest); break; default: alert('twinkleprotect: 未知操作类型'); break; } }; Twinkle.protect.callbacks = { taggingPageInitial: function(tagparams) { if (tagparams.tag === 'noop') { Morebits.status.info(wgULS('应用保护模板', '應用保護模板'), wgULS('没什么要做的', '沒什麼要做的')); return; } var pageName = mw.config.get('wgPageName'); var protectedPage = new Morebits.wiki.page(pageName, wgULS('标记页面', '標記頁面')); protectedPage.setCallbackParameters(tagparams); protectedPage.load(Twinkle.protect.callbacks.taggingPage); }, getTaggedPage: function(params, text) { var tag, summary; var oldtag_re = /\s*(?:<noinclude>)?\s*\{\{\s*(pp-[^{}]*?|protected|(?:t|v|s|p-|usertalk-v|usertalk-s|sb|move)protected(?:2)?|protected template|privacy protection)\s*?\}\}\s*(?:<\/noinclude>)?\s*/gi; var re_result = oldtag_re.exec(text); if (re_result) { if (confirm(wgULS('在页面上找到{{' + re_result[1] + '}}\n点击确定以移除,或点击取消以取消。', '在頁面上找到{{' + re_result[1] + '}}\n點選確定以移除,或點選取消以取消。'))) { text = text.replace(oldtag_re, ''); } } if (params.tag !== 'none') { tag = params.tag; if (params.reason) { tag += '|reason=' + params.reason; } if (['indefinite', 'infinite', 'never', null].indexOf(params.expiry) === -1) { tag += '|expiry={{subst:#time:c|' + (/^\s*\d+\s*$/.exec(params.expiry) ? params.expiry : '+' + params.expiry) + '}}'; } if (params.small) { tag += '|small=yes'; } } if (params.tag === 'none') { summary = wgULS('移除保护模板', '移除保護模板'); } else { if (params.noinclude) { text = '<noinclude>{{' + tag + '}}</noinclude>' + text; } else if (/^\s*#(redirect|重定向|重新導向)/i.test(text)) { // redirect page text = text + '\n{{' + tag + '}}'; } else { text = '{{' + tag + '}}\n' + text; } summary = '加入{{' + params.tag + '}}'; } return { text: text, summary: summary }; }, taggingPage: function(protectedPage) { var params = protectedPage.getCallbackParameters(); var text = protectedPage.getPageText(); var newVersion = Twinkle.protect.callbacks.getTaggedPage(params, text); protectedPage.setEditSummary(newVersion.summary); protectedPage.setChangeTags(Twinkle.changeTags); protectedPage.setPageText(newVersion.text); protectedPage.setCreateOption('nocreate'); protectedPage.suppressProtectWarning(); // no need to let admins know they are editing through protection protectedPage.save(); }, fileRequest: function(rppPage) { var params = rppPage.getCallbackParameters(); var text = rppPage.getPageText(); var statusElement = rppPage.getStatusElement(); var rppRe = new RegExp('===\\s*(\\[\\[)?\\s*:?\\s*' + RegExp.escape(Morebits.pageNameNorm, true) + '\\s*(\\]\\])?\\s*===', 'm'); var tag = rppRe.exec(text); var rppLink = document.createElement('a'); rppLink.setAttribute('href', mw.util.getUrl(rppPage.getPageName())); rppLink.appendChild(document.createTextNode(rppPage.getPageName())); if (tag) { statusElement.error(wgULS([ '已有对此条目的保护提名,在 ', rppLink, ',取消操作。' ], [ '已有對此條目的保護提名,在 ', rppLink, ',取消操作。' ])); return; } var newtag = '=== [[:' + mw.config.get('wgPageName') + ']] ===' + '\n'; if (new RegExp('^' + RegExp.escape(newtag).replace(/\s+/g, '\\s*'), 'm').test(text)) { statusElement.error(wgULS([ '已有对此条目的保护提名,在 ', rppLink, ',取消操作。' ], [ '已有對此條目的保護提名,在 ', rppLink, ',取消操作。' ])); return; } var words; switch (params.expiry) { case 'temporary': words = '临时'; break; case 'indefinite': words = '永久'; break; default: words = ''; break; } words += params.typename; newtag += '请求' + Morebits.string.toUpperCaseFirstChar(words) + (params.reason !== '' ? ':' + Morebits.string.formatReasonText(params.reason) : '。') + '--~~~~'; newtag += '\n:: <small>当前保护状态:{{protection status|' + mw.config.get('wgPageName') + '}}</small>'; var reg; if (params.category === 'unprotect') { reg = /(==\s*请求解除保护\s*==)/; } else { reg = /({{\s*\/header\s*}})/; } var originalTextLength = text.length; text = text.replace(reg, '$1\n' + newtag + '\n'); if (text.length === originalTextLength) { var linknode = document.createElement('a'); linknode.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/修复RFPP')); linknode.appendChild(document.createTextNode('如何修复RFPP')); statusElement.error(wgULS([ '无法在WP:RFPP上找到相关位点标记,要修复此问题,请参见', linknode, '。' ], [ '無法在WP:RFPP上找到相關位點標記,要修複此問題,請參見', linknode, '。' ])); return; } statusElement.status('加入新提名…'); rppPage.setEditSummary('请求对[[' + Morebits.pageNameNorm + ']]' + params.typename); rppPage.setChangeTags(Twinkle.changeTags); rppPage.setPageText(text); rppPage.setCreateOption('recreate'); rppPage.save(); } }; })(jQuery); // </nowiki> qw07p594na58pxej4k4ukcaex3tybkz MediaWiki:Gadget-twinklespeedy.js 8 1368874 9943048 6333138 2026-10-12T15:41:24Z Xiplus 25402 Repo at 375b1e1: Use info status for redirect checking 9943048 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { /* **************************************** *** twinklespeedy.js: CSD module **************************************** * Mode of invocation: Tab ("CSD") * Active on: Non-special, existing pages * * NOTE FOR DEVELOPERS: * If adding a new criterion, add it to the appropriate places at the top of * twinkleconfig.js. Also check out the default values of the CSD preferences * in twinkle.js, and add your new criterion to those if you think it would be * good. */ Twinkle.speedy = function twinklespeedy() { // Disable on: // * special pages // * non-existent pages if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) { return; } Twinkle.addPortletLink(Twinkle.speedy.callback, wgULS('速删', '速刪'), 'tw-csd', Morebits.userIsSysop ? wgULS('快速删除', '快速刪除') : wgULS('请求快速删除', '請求快速刪除')); }; // This function is run when the CSD tab/header link is clicked Twinkle.speedy.callback = function twinklespeedyCallback() { Twinkle.speedy.initDialog(Morebits.userIsSysop ? Twinkle.speedy.callback.evaluateSysop : Twinkle.speedy.callback.evaluateUser, true); }; // Used by unlink feature Twinkle.speedy.dialog = null; // The speedy criteria list can be in one of several modes Twinkle.speedy.mode = { sysopSingleSubmit: 1, // radio buttons, no subgroups, submit when "Submit" button is clicked sysopRadioClick: 2, // radio buttons, no subgroups, submit when a radio button is clicked sysopMultipleSubmit: 3, // check boxes, subgroups, "Submit" button already present sysopMultipleRadioClick: 4, // check boxes, subgroups, need to add a "Submit" button userMultipleSubmit: 5, // check boxes, subgroups, "Submit" button already pressent userMultipleRadioClick: 6, // check boxes, subgroups, need to add a "Submit" button userSingleSubmit: 7, // radio buttons, subgroups, submit when "Submit" button is clicked userSingleRadioClick: 8, // radio buttons, subgroups, submit when a radio button is clicked // are we in "delete page" mode? // (sysops can access both "delete page" [sysop] and "tag page only" [user] modes) isSysop: function twinklespeedyModeIsSysop(mode) { return mode === Twinkle.speedy.mode.sysopSingleSubmit || mode === Twinkle.speedy.mode.sysopMultipleSubmit || mode === Twinkle.speedy.mode.sysopRadioClick || mode === Twinkle.speedy.mode.sysopMultipleRadioClick; }, // do we have a "Submit" button once the form is created? hasSubmitButton: function twinklespeedyModeHasSubmitButton(mode) { return mode === Twinkle.speedy.mode.sysopSingleSubmit || mode === Twinkle.speedy.mode.sysopMultipleSubmit || mode === Twinkle.speedy.mode.sysopMultipleRadioClick || mode === Twinkle.speedy.mode.userMultipleSubmit || mode === Twinkle.speedy.mode.userMultipleRadioClick || mode === Twinkle.speedy.mode.userSingleSubmit; }, // is db-multiple the outcome here? isMultiple: function twinklespeedyModeIsMultiple(mode) { return mode === Twinkle.speedy.mode.userMultipleSubmit || mode === Twinkle.speedy.mode.sysopMultipleSubmit || mode === Twinkle.speedy.mode.userMultipleRadioClick || mode === Twinkle.speedy.mode.sysopMultipleRadioClick; } }; // Prepares the speedy deletion dialog and displays it Twinkle.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc) { var dialog; Twinkle.speedy.dialog = new Morebits.simpleWindow(Twinkle.getPref('speedyWindowWidth'), Twinkle.getPref('speedyWindowHeight')); dialog = Twinkle.speedy.dialog; dialog.setTitle(wgULS('选择快速删除理由', '選擇快速刪除理由')); dialog.setScriptName('Twinkle'); dialog.addFooterLink(wgULS('快速删除方针', '快速刪除方針'), 'Wiktionary:CSD'); // dialog.addFooterLink( wgULS("常见错误", "常見錯誤"), "Wikipedia:管理员错误自查表/快速删除" ); // dialog.addFooterLink( wgULS("Twinkle帮助", "Twinkle說明"), "Wiktionary:TW/DOC#speedy" ); var form = new Morebits.quickForm(callbackfunc, Twinkle.getPref('speedySelectionStyle') === 'radioClick' ? 'change' : null); if (Morebits.userIsSysop) { form.append({ type: 'checkbox', list: [ { label: wgULS('只标记,不删除', '只標記,不刪除'), value: 'tag_only', name: 'tag_only', tooltip: wgULS('如果您只想标记此页面而不是将其删除', '如果您只想標記此頁面而不是將其刪除'), checked: Twinkle.getPref('deleteSysopDefaultToTag'), event: function(event) { var cForm = event.target.form; var cChecked = event.target.checked; // enable/disable talk page checkbox if (cForm.talkpage) { cForm.talkpage.disabled = cChecked; cForm.talkpage.checked = !cChecked && Twinkle.getPref('deleteTalkPageOnDelete'); } // enable/disable redirects checkbox cForm.redirects.disabled = cChecked; cForm.redirects.checked = !cChecked; // enable/disable delete multiple cForm.delmultiple.disabled = cChecked; cForm.delmultiple.checked = false; // enable/disable open talk page checkbox cForm.openusertalk.disabled = cChecked; cForm.openusertalk.checked = false; // enable/disable notify checkbox cForm.notify.disabled = !cChecked; cForm.notify.checked = cChecked; // enable/disable multiple cForm.multiple.disabled = !cChecked; cForm.multiple.checked = false; Twinkle.speedy.callback.modeChanged(cForm); event.stopPropagation(); } } ] }); var deleteOptions = form.append({ type: 'div', name: 'delete_options' }); deleteOptions.append({ type: 'header', label: wgULS('删除相关设置', '刪除相關設定') }); if (mw.config.get('wgNamespaceNumber') % 2 === 0 && (mw.config.get('wgNamespaceNumber') !== 2 || (/\//).test(mw.config.get('wgTitle')))) { // hide option for user pages, to avoid accidentally deleting user talk page deleteOptions.append({ type: 'checkbox', list: [ { label: wgULS('删除讨论页', '刪除討論頁'), value: 'talkpage', name: 'talkpage', tooltip: wgULS('删除时附带删除此页面的讨论页。', '刪除時附帶刪除此頁面的討論頁。'), checked: Twinkle.getPref('deleteTalkPageOnDelete'), disabled: Twinkle.getPref('deleteSysopDefaultToTag'), event: function(event) { event.stopPropagation(); } } ] }); } deleteOptions.append({ type: 'checkbox', list: [ { label: wgULS('删除重定向', '刪除重定向'), value: 'redirects', name: 'redirects', tooltip: wgULS('删除到此页的重定向。', '刪除到此頁的重定向。'), checked: Twinkle.getPref('deleteRedirectsOnDelete'), disabled: Twinkle.getPref('deleteSysopDefaultToTag'), event: function(event) { event.stopPropagation(); } } ] }); deleteOptions.append({ type: 'checkbox', list: [ { label: wgULS('应用多个理由删除', '應用多個理由刪除'), value: 'delmultiple', name: 'delmultiple', tooltip: wgULS('您可选择应用于该页的多个理由。', '您可選擇應用於該頁的多個理由。'), event: function(event) { Twinkle.speedy.callback.modeChanged(event.target.form); event.stopPropagation(); } } ] }); deleteOptions.append({ type: 'checkbox', list: [ { label: wgULS('开启用户对话页', '開啟用戶對話頁'), value: 'openusertalk', name: 'openusertalk', tooltip: wgULS('此项的默认值为您的开启对话页设置。在您选择应用多条理由删除时此项将保持不变。', '此項的預設值為您的開啟對話頁設定。在您選擇應用多條理由刪除時此項將保持不變。'), checked: false } ] }); } var tagOptions = form.append({ type: 'div', name: 'tag_options' }); if (Morebits.userIsSysop) { tagOptions.append({ type: 'header', label: wgULS('标记相关选项', '標記相關選項') }); } tagOptions.append({ type: 'checkbox', list: [ { label: wgULS('如可能,通知创建者', '如可能,通知建立者'), value: 'notify', name: 'notify', tooltip: wgULS('一个通知模板将会被加入创建者的对话页,如果您启用了该理据的通知。', '一個通知模板將會被加入建立者的對話頁,如果您啟用了該理據的通知。'), // checked: !Morebits.userIsSysop || Twinkle.getPref('deleteSysopDefaultToTag'), checked: false, // disabled: Morebits.userIsSysop && !Twinkle.getPref('deleteSysopDefaultToTag'), hidden: true, event: function(event) { event.stopPropagation(); } } ] }); tagOptions.append({ type: 'checkbox', list: [ { label: wgULS('应用多个理由', '應用多個理由'), value: 'multiple', name: 'multiple', tooltip: wgULS('您可选择应用于该页的多个理由。', '您可選擇應用於該頁的多個理由。'), disabled: Morebits.userIsSysop && !Twinkle.getPref('deleteSysopDefaultToTag'), event: function(event) { Twinkle.speedy.callback.modeChanged(event.target.form); event.stopPropagation(); } } ] }); form.append({ type: 'div', name: 'work_area', label: wgULS('初始化CSD模块失败,请重试,或将这报告给Twinkle开发者。', '初始化CSD模組失敗,請重試,或將這報告給Twinkle開發者。') }); if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') { form.append({ type: 'submit', className: 'tw-speedy-submit' }); // Renamed in modeChanged } var result = form.render(); dialog.setContent(result); dialog.display(); Twinkle.speedy.callback.modeChanged(result); // if sysop, check if CSD is already on the page and fill in custom rationale if (Morebits.userIsSysop && $('#delete-reason').length) { var customOption = $('input[name=csd][value=reason]')[0]; if (Twinkle.getPref('speedySelectionStyle') !== 'radioClick') { // force listeners to re-init customOption.click(); customOption.parentNode.appendChild(customOption.subgroup); } customOption.subgroup.querySelector('input').value = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' '); } }; Twinkle.speedy.callback.getMode = function twinklespeedyCallbackGetMode(form) { var mode = Twinkle.speedy.mode.userSingleSubmit; if (form.tag_only && !form.tag_only.checked) { if (form.delmultiple.checked) { mode = Twinkle.speedy.mode.sysopMultipleSubmit; } else { mode = Twinkle.speedy.mode.sysopSingleSubmit; } } else { if (form.multiple.checked) { mode = Twinkle.speedy.mode.userMultipleSubmit; } else { mode = Twinkle.speedy.mode.userSingleSubmit; } } if (Twinkle.getPref('speedySelectionStyle') === 'radioClick') { mode++; } return mode; }; Twinkle.speedy.callback.modeChanged = function twinklespeedyCallbackModeChanged(form) { var namespace = mw.config.get('wgNamespaceNumber'); // first figure out what mode we're in var mode = Twinkle.speedy.callback.getMode(form); if (Twinkle.speedy.mode.isSysop(mode)) { $('[name=delete_options]').show(); $('[name=tag_options]').hide(); $('button.tw-speedy-submit').text(wgULS('删除页面', '刪除頁面')); } else { $('[name=delete_options]').hide(); $('[name=tag_options]').show(); $('button.tw-speedy-submit').text(wgULS('标记页面', '標記頁面')); } var work_area = new Morebits.quickForm.element({ type: 'div', name: 'work_area' }); if (mode === Twinkle.speedy.mode.userMultipleRadioClick || mode === Twinkle.speedy.mode.sysopMultipleRadioClick) { var evaluateType = Twinkle.speedy.mode.isSysop(mode) ? 'evaluateSysop' : 'evaluateUser'; work_area.append({ type: 'div', label: wgULS('当选择完成后,点击:', '當選擇完成後,點選:') }); work_area.append({ type: 'button', name: 'submit-multiple', label: '提交', event: function(event) { Twinkle.speedy.callback[evaluateType](event); event.stopPropagation(); } }); } var radioOrCheckbox = Twinkle.speedy.mode.isMultiple(mode) ? 'checkbox' : 'radio'; // if (Twinkle.speedy.mode.isSysop(mode) && !Twinkle.speedy.mode.isMultiple(mode)) { work_area.append({ type: 'header', label: wgULS('自定义理由', '自訂理由') }); work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.customRationale, mode) }); // } switch (namespace) { case 0: // main work_area.append({ type: 'header', label: wgULS('词条', '詞條') }); work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.articleList, mode) }); break; case 2: // user work_area.append({ type: 'header', label: wgULS('用户页', '使用者頁面') }); work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.userList, mode) }); break; case 3: // user talk if (mw.util.isIPAddress(mw.config.get('wgRelevantUserName'))) { work_area.append({ type: 'header', label: wgULS('用户讨论', '使用者討論') }); work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.usertalkList, mode) }); } break; case 14: // category work_area.append({ type: 'header', label: wgULS('分类', '分類') }); work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.categoryList, mode) }); break; default: break; } // custom rationale lives under general criteria when tagging var generalCriteria = Twinkle.speedy.generalList; if (!Twinkle.speedy.mode.isSysop(mode)) { generalCriteria = Twinkle.speedy.customRationale.concat(generalCriteria); } work_area.append({ type: 'header', label: wgULS('常规', '常規') }); work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(generalCriteria, mode) }); if (mw.config.get('wgIsRedirect') || Morebits.userIsSysop) { work_area.append({ type: 'header', label: '重定向' }); if (namespace === 0 || namespace === 118) { work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.redirectArticleList, mode) }); } work_area.append({ type: radioOrCheckbox, name: 'csd', list: Twinkle.speedy.generateCsdList(Twinkle.speedy.redirectList, mode) }); } var old_area = Morebits.quickForm.getElements(form, 'work_area')[0]; form.replaceChild(work_area.render(), old_area); }; Twinkle.speedy.generateCsdList = function twinklespeedyGenerateCsdList(list, mode) { // mode switches var isSysop = Twinkle.speedy.mode.isSysop(mode); var multiple = Twinkle.speedy.mode.isMultiple(mode); var hasSubmitButton = Twinkle.speedy.mode.hasSubmitButton(mode); var openSubgroupHandler = function(e) { $(e.target.form).find('input').prop('disabled', true); $(e.target.form).children().css('color', 'gray'); $(e.target).parent().css('color', 'black').find('input').prop('disabled', false); $(e.target).parent().find('input:text')[0].focus(); e.stopPropagation(); }; var submitSubgroupHandler = function(e) { var evaluateType = Twinkle.speedy.mode.isSysop(mode) ? 'evaluateSysop' : 'evaluateUser'; Twinkle.speedy.callback[evaluateType](e); e.stopPropagation(); }; return $.map(list, function(critElement) { var criterion = $.extend({}, critElement); // hack to get the g11 radio / checkbox right if (criterion.value === 'g11') { criterion.style = Twinkle.getPref('enlargeG11Input') ? 'height: 2em; width: 2em; height: -moz-initial; width: -moz-initial; -moz-transform: scale(2); -o-transform: scale(2);' : ''; } if (multiple) { if (criterion.hideWhenMultiple) { return null; } if (criterion.hideSubgroupWhenMultiple) { criterion.subgroup = null; } } else { if (criterion.hideWhenSingle) { return null; } if (criterion.hideSubgroupWhenSingle) { criterion.subgroup = null; } } if (isSysop) { if (criterion.hideWhenSysop) { return null; } if (criterion.hideSubgroupWhenSysop) { criterion.subgroup = null; } } else { if (criterion.hideWhenUser) { return null; } if (criterion.hideSubgroupWhenUser) { criterion.subgroup = null; } } if (mw.config.get('wgIsRedirect') && criterion.hideWhenRedirect) { return null; } if (criterion.subgroup && !hasSubmitButton) { if ($.isArray(criterion.subgroup)) { criterion.subgroup.push({ type: 'button', name: 'submit', label: '提交', event: submitSubgroupHandler }); } else { criterion.subgroup = [ criterion.subgroup, { type: 'button', name: 'submit', // ends up being called "csd.submit" so this is OK label: '提交', event: submitSubgroupHandler } ]; } // FIXME: does this do anything? criterion.event = openSubgroupHandler; } if (isSysop) { var originalEvent = criterion.event; criterion.event = function(e) { if (multiple) { return originalEvent(e); } var normalizedCriterion = Twinkle.speedy.normalizeHash[e.target.value]; $('[name=openusertalk]').prop('checked', Twinkle.getPref('openUserTalkPageOnSpeedyDelete').indexOf(normalizedCriterion) !== -1 ); if (originalEvent) { return originalEvent(e); } }; } return criterion; }); }; Twinkle.speedy.customRationale = [ { label: wgULS('自定义理由' + (Morebits.userIsSysop ? '(自定义删除理由)' : ''), '自訂理由' + (Morebits.userIsSysop ? '(自訂刪除理由)' : '')), value: 'reason', tooltip: wgULS('该页至少应该符合一条快速删除的标准,并且您必须在理由中提到。这不是万能的删除理由。', '該頁至少應該符合一條快速刪除的標準,並且您必須在理由中提到。這不是萬能的刪除理由。'), subgroup: { name: 'reason_1', type: 'input', label: '理由:', size: 60 } // hideWhenMultiple: true } ]; Twinkle.speedy.fileList = []; Twinkle.speedy.articleList = [ { label: wgULS('A1: 标题拼写错误', 'A1: 標題拼寫錯誤'), value: 'a1', tooltip: wgULS('如果正确的标题尚不存在,应移动到正确的标题。如果页面包含有用的内容,应使用{{History merge}}请求合并历史到正确的标题页面。不适用于常见的拼写错误,如有疑虑应提交Wiktionary:删除请求。', '如果正確的標題尚不存在,應移動到正確的標題。如果頁面包含有用的內容,應使用{{History merge}}請求合併歷史到正確的標題頁面。不適用於常見的拼寫錯誤,如有疑慮應提交Wiktionary:刪除請求。') } ]; Twinkle.speedy.categoryList = [ { label: wgULS('O4: 空的分类', 'O4: 空的分類'), value: 'o4' } ]; Twinkle.speedy.draftList = []; Twinkle.speedy.userList = [ { label: wgULS('O1: 用户请求删除自己的用户页', 'O1: 用戶請求刪除自己的用戶頁'), value: 'o1' } ]; Twinkle.speedy.usertalkList = [ { label: wgULS('O3: 匿名用户的讨论页', 'O3: 匿名用戶的討論頁'), value: 'o3' } ]; Twinkle.speedy.generalList = [ { label: wgULS('G1: 无实际内容或非词典', 'G1: 無實際內容或非詞典'), value: 'g1' }, { label: wgULS('G2: 测试页', 'G2: 測試頁'), value: 'g2' }, { label: wgULS('G3: 破坏', 'G3: 破壞'), value: 'g3', tooltip: wgULS('包括过度扩展或重复内容、无聊或恶搞、重定向破坏。', '包括過度擴充或重複內容、無聊或惡搞、重定向破壞。') }, { label: wgULS('G5: 曾经被删除的重建内容', 'G5: 曾經被刪除的重建內容'), value: 'g5', subgroup: { name: 'g5_1', type: 'input', label: wgULS('删除讨论位置:', '刪除討論位置:'), tooltip: wgULS('必须以“Wiktionary:”开头', '必須以「Wiktionary:」開頭'), size: 60 }, hideSubgroupWhenMultiple: true }, { label: wgULS('G7: 未翻译的内容', 'G7: 未翻譯的內容'), value: 'g7' }, { label: wgULS('G8: 管理员因技术原因删除页面', 'G8: 管理員因技術原因刪除頁面'), value: 'g8', hideWhenUser: true }, { label: wgULS('G9: 内容只包含参考、连接、模板或/及分类', 'G9: 內容只包含參考、連接、模板或/及分類'), value: 'g9' }, { label: wgULS('G10: 作者请求或原作者清空页面', 'G10: 作者請求或原作者清空頁面'), value: 'g10', subgroup: { name: 'g10_rationale', type: 'input', label: wgULS('可选的解释:', '可選的解釋:'), tooltip: wgULS('比如作者在哪里请求了删除。', '比如作者在哪裡請求了刪除。'), size: 60 }, hideSubgroupWhenSysop: true }, { label: wgULS('G11: 广告或宣传', 'G11: 廣告或宣傳'), value: 'g11' }, { label: wgULS('G13: 明显的、拙劣的机器翻译', 'G13: 明顯的、拙劣的機器翻譯'), value: 'g13' }, { label: wgULS('G15: 孤立页面', 'G15: 孤立頁面'), value: 'g15', tooltip: wgULS('包括以下几种类型:1. 重定向页指向不存在的页面;2. 孤立的讨论页。', '包括以下幾種類型:1. 重定向頁指向不存在的頁面;2. 孤立的討論頁。') } ]; Twinkle.speedy.redirectArticleList = [ { label: wgULS('R2: 跨名字空间重定向', 'R2: 跨名字空間重定向'), value: 'r2' } ]; Twinkle.speedy.redirectList = [ { label: wgULS('R3: 名称错误的重定向', 'R3: 名稱錯誤的重定向'), value: 'r3', hideSubgroupWhenSysop: true } ]; Twinkle.speedy.normalizeHash = { 'reason': 'db', 'multiple': 'multiple', 'multiple-finish': 'multiple-finish', 'g1': 'g1', 'g2': 'g2', 'g3': 'g3', 'g5': 'g5', 'g7': 'g7', 'g8': 'g8', 'g9': 'g9', 'g10': 'g10', 'g11': 'g11', 'g13': 'g13', 'g15': 'g15', 'a1': 'a1', 'r2': 'r2', 'r3': 'r3', 'o1': 'o1', 'o3': 'o3', 'o4': 'o4' }; // keep this synched with [[MediaWiki:Deletereason-dropdown]] Twinkle.speedy.reasonHash = wgULS({ 'reason': '', // General 'g1': '无实际内容或非词典', 'g2': '测试页', 'g3': '破坏', 'g5': '曾经被删除的重建内容', 'g7': '未翻译的内容', 'g8': '技术原因', 'g9': '内容只包含参考、连接、模板或/及分类', 'g10': '作者请求或原作者清空页面', 'g11': '广告或宣传', 'g13': '明显的、拙劣的机器翻译', 'g15': '孤立页面', // Articles 'a1': '标题拼写错误', // Redirects 'r2': '跨名字空间重定向', 'r3': '名称错误的重定向', // Images and media // User pages 'o1': '用户请求删除自己的用户页', 'o3': '匿名用户的讨论页', // Categories 'o4': '空的类别' // Templates // Portals }, { 'reason': '', // General 'g1': '無實際內容或非詞典', 'g2': '測試頁', 'g3': '破壞', 'g5': '曾經被刪除的重建內容', 'g7': '未翻譯的內容', 'g8': '技術原因', 'g9': '內容只包含參考、連接、模板或/及分類', 'g10': '作者請求或原作者清空頁面', 'g11': '廣告或宣傳', 'g13': '明顯的、拙劣的機器翻譯', 'g15': '孤立頁面', // Articles 'a1': '標題拼寫錯誤', // Redirects 'r2': '跨名字空間重定向', 'r3': '名稱錯誤的重定向', // Images and media // User pages 'o1': '用戶請求刪除自己的用戶頁', 'o3': '匿名用戶的討論頁', // Categories 'o4': '空的分類' // Templates // Portals }); Twinkle.speedy.callbacks = { getTemplateCodeAndParams: function(params) { var code, parameters, i; if (params.normalizeds.length > 1) { code = '{{delete'; params.utparams = {}; $.each(params.normalizeds, function(index, norm) { if (norm !== 'db') { code += '|' + norm.toUpperCase(); } parameters = params.templateParams[index] || []; for (var i in parameters) { if (typeof parameters[i] === 'string') { code += '|' + parameters[i]; } } $.extend(params.utparams, Twinkle.speedy.getUserTalkParameters(norm, parameters)); }); code += '}}'; } else { parameters = params.templateParams[0] || []; code = '{{delete'; if (params.values[0] !== 'reason') { code += '|' + params.values[0]; } for (i in parameters) { if (typeof parameters[i] === 'string') { code += '|' + parameters[i]; } } code += '}}'; params.utparams = Twinkle.speedy.getUserTalkParameters(params.normalizeds[0], parameters); } return [code, params.utparams]; }, parseWikitext: function(title, wikitext, callback) { var query = { action: 'parse', prop: 'text', pst: 'true', text: wikitext, contentmodel: 'wikitext', title: title }; var statusIndicator = new Morebits.status(wgULS('构造删除理由', '構造刪除理由')); var api = new Morebits.wiki.api(wgULS('解析删除模板', '解析刪除模板'), query, function(apiObj) { var reason = decodeURIComponent($(apiObj.getXML().querySelector('text').childNodes[0].nodeValue).find('#delete-reason').text().replace(/\+/g, ' ')); if (!reason) { statusIndicator.warn(wgULS('未能从删除模板生成删除理由', '未能從刪除模板生成刪除理由')); } else { statusIndicator.info('完成'); } callback(reason); }, statusIndicator); api.post(); }, sysop: { main: function(params) { var reason; if (!params.normalizeds.length && params.normalizeds[0] === 'db') { reason = prompt(wgULS('输入删除理由:', '輸入刪除理由:'), ''); Twinkle.speedy.callbacks.sysop.deletePage(reason, params); } else { var code = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params)[0]; Twinkle.speedy.callbacks.parseWikitext(mw.config.get('wgPageName'), code, function(reason) { if (params.promptForSummary) { reason = prompt(wgULS('输入删除理由,或点击确定以接受自动生成的:', '輸入刪除理由,或點選確定以接受自動生成的:'), reason); } Twinkle.speedy.callbacks.sysop.deletePage(reason, params); }); } }, deletePage: function(reason, params) { var thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), wgULS('删除页面', '刪除頁面')); if (mw.config.get('wgPageName') === mw.config.get('wgMainPageTitle')) { var statusElement = thispage.getStatusElement(); new mw.Api().getToken('block').then(function(token) { statusElement.status('The Enrichment Center is required to remind you that you will be baked. 恭喜您!您是继[[User:燃玉]]之后又一个通过Twinkle删除首页的管理员!'); var mbApi = new Morebits.wiki.api('颁发荣誉', { action: 'block', user: mw.config.get('wgUserName'), reason: '试图[[w:Wikipedia:不要删除首页|像User:燃玉那样创造奇迹]]', allowusertalk: true, expiry: '31 hours', tags: Twinkle.changeTags, token: token }, function() { statusElement.info(wgULS('请记得阅读[[w:Wikipedia:不要删除主页]]。', '請記得閱讀[[w:Wikipedia:不要刪除首頁]]。')); }); mbApi.post(); }, function() { statusElement.error(wgULS('未能抓取操作令牌', '未能抓取操作權杖')); }); } else { if (reason === null) { return Morebits.status.error(wgULS('询问理由', '詢問理由'), wgULS('用户取消操作。', '用戶取消操作。')); } else if (!reason || !reason.replace(/^\s*/, '').replace(/\s*$/, '')) { return Morebits.status.error(wgULS('询问理由', '詢問理由'), wgULS('你不给我理由…我就…不管了…', '你不給我理由…我就…不管了…')); } var deleteMain = function() { thispage.setEditSummary(reason); thispage.setChangeTags(Twinkle.changeTags); thispage.deletePage(function() { thispage.getStatusElement().info('完成'); Twinkle.speedy.callbacks.sysop.deleteTalk(params); }); }; // look up initial contributor. If prompting user for deletion reason, just display a link. // Otherwise open the talk page directly if (params.openUserTalk) { thispage.setCallbackParameters(params); thispage.lookupCreation(function() { Twinkle.speedy.callbacks.sysop.openUserTalkPage(thispage); deleteMain(); }); } else { deleteMain(); } } }, deleteTalk: function(params) { // delete talk page if (params.deleteTalkPage && params.normalized !== 'f7' && params.normalized !== 'o1' && !document.getElementById('ca-talk').classList.contains('new')) { var talkpage = new Morebits.wiki.page(Morebits.wikipedia.namespaces[mw.config.get('wgNamespaceNumber') + 1] + ':' + mw.config.get('wgTitle'), wgULS('删除讨论页', '刪除討論頁')); talkpage.setEditSummary('[[Wiktionary:CSD|G15]]: 孤立页面: 已删除页面“' + Morebits.pageNameNorm + '”的讨论页'); talkpage.setChangeTags(Twinkle.changeTags); talkpage.deletePage(); // this is ugly, but because of the architecture of wiki.api, it is needed // (otherwise success/failure messages for the previous action would be suppressed) window.setTimeout(function() { Twinkle.speedy.callbacks.sysop.deleteRedirects(params); }, 1800); } else { Twinkle.speedy.callbacks.sysop.deleteRedirects(params); } }, deleteRedirects: function(params) { // delete redirects if (params.deleteRedirects) { var query = { 'action': 'query', 'titles': mw.config.get('wgPageName'), 'prop': 'redirects', 'rdlimit': 5000 // 500 is max for normal users, 5000 for bots and sysops }; var wikipedia_api = new Morebits.wiki.api(wgULS('获取重定向清单…', '取得重定向清單…'), query, Twinkle.speedy.callbacks.sysop.deleteRedirectsMain, new Morebits.status(wgULS('删除重定向', '刪除重定向'))); wikipedia_api.params = params; wikipedia_api.post(); } // prompt for protect on G11 var $link, $bigtext; if (params.normalized === 'g11') { $link = $('<a/>', { 'href': '#', 'text': wgULS('点击这里施行保护', '點選這裡施行保護'), 'css': { 'fontSize': '130%', 'fontWeight': 'bold' }, 'click': function() { Morebits.wiki.actionCompleted.redirect = null; Twinkle.speedy.dialog.close(); mw.config.set('wgArticleId', 0); Twinkle.protect.callback(); } }); $bigtext = $('<span/>', { 'text': wgULS('白纸保护该页', '白紙保護該頁'), 'css': { 'fontSize': '130%', 'fontWeight': 'bold' } }); Morebits.status.info($bigtext[0], $link[0]); } // promote Unlink tool if (mw.config.get('wgNamespaceNumber') === 6 && params.normalized !== 'f7') { $link = $('<a/>', { 'href': '#', 'text': wgULS('点击这里前往反链工具', '點選這裏前往反鏈工具'), 'css': { 'fontWeight': 'bold' }, 'click': function() { Morebits.wiki.actionCompleted.redirect = null; Twinkle.speedy.dialog.close(); Twinkle.unlink.callback(wgULS('取消对已删除文件 ', '取消對已刪除檔案 ') + Morebits.pageNameNorm + ' 的使用'); } }); $bigtext = $('<span/>', { 'text': wgULS('取消对已删除文件的使用', '取消對已刪除檔案的使用'), 'css': { 'fontWeight': 'bold' } }); Morebits.status.info($bigtext[0], $link[0]); } else if (params.normalized !== 'f7') { $link = $('<a/>', { 'href': '#', 'text': wgULS('点击这里前往反链工具', '點選這裏前往反鏈工具'), 'css': { 'fontWeight': 'bold' }, 'click': function() { Morebits.wiki.actionCompleted.redirect = null; Twinkle.speedy.dialog.close(); Twinkle.unlink.callback(wgULS('取消对已删除页面 ', '取消對已刪除頁面 ') + Morebits.pageNameNorm + wgULS(' 的链接', ' 的連結')); } }); $bigtext = $('<span/>', { 'text': wgULS('取消对已删除页面的链接', '取消對已刪除頁面的連結'), 'css': { 'fontWeight': 'bold' } }); Morebits.status.info($bigtext[0], $link[0]); } }, openUserTalkPage: function(pageobj) { pageobj.getStatusElement().unlink(); // don't need it anymore var user = pageobj.getCreator(); var params = pageobj.getCallbackParameters(); var query = { 'title': 'User talk:' + user, 'action': 'edit', 'preview': 'yes', 'vanarticle': Morebits.pageNameNorm }; if (params.normalized === 'db' || Twinkle.getPref('promptForSpeedyDeletionSummary').indexOf(params.normalized) !== -1) { // provide a link to the user talk page var $link, $bigtext; $link = $('<a/>', { 'href': mw.util.wikiScript('index') + '?' + $.param(query), 'text': wgULS('点此开启User talk:', '點此開啟User talk:') + user, 'target': '_blank', 'css': { 'fontSize': '130%', 'fontWeight': 'bold' } }); $bigtext = $('<span/>', { 'text': wgULS('通知页面创建者', '通知頁面建立者'), 'css': { 'fontSize': '130%', 'fontWeight': 'bold' } }); Morebits.status.info($bigtext[0], $link[0]); } else { // open the initial contributor's talk page var statusIndicator = new Morebits.status(wgULS('开启用户' + user + '对话页编辑窗体', '開啟用戶' + user + '對話頁編輯表單'), wgULS('开启中…', '開啟中…')); switch (Twinkle.getPref('userTalkPageMode')) { case 'tab': window.open(mw.util.wikiScript('index') + '?' + $.param(query), '_blank'); break; case 'blank': window.open(mw.util.wikiScript('index') + '?' + $.param(query), '_blank', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800'); break; case 'window': /* falls through */ default: window.open(mw.util.wikiScript('index') + '?' + $.param(query), window.name === 'twinklewarnwindow' ? '_blank' : 'twinklewarnwindow', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800'); break; } statusIndicator.info('完成'); } }, deleteRedirectsMain: function(apiobj) { var xmlDoc = apiobj.getXML(); var $snapshot = $(xmlDoc).find('redirects rd'); var total = $snapshot.length; var statusIndicator = apiobj.statelem; if (!total) { statusIndicator.info(wgULS('未发现重定向', '未發現重定向')); return; } statusIndicator.status('0%'); var current = 0; var onsuccess = function(apiobjInner) { var now = parseInt(100 * ++current / total, 10) + '%'; statusIndicator.update(now); apiobjInner.statelem.unlink(); if (current >= total) { statusIndicator.info(now + '(完成)'); Morebits.wiki.removeCheckpoint(); } }; Morebits.wiki.addCheckpoint(); $snapshot.each(function(key, value) { var title = $(value).attr('title'); var page = new Morebits.wiki.page(title, wgULS('删除重定向 "', '刪除重定向 "') + title + '"'); page.setEditSummary('[[Wiktionary:CSD|G15]]: 孤立页面: 重定向到已删除页面“' + Morebits.pageNameNorm + '”'); page.setChangeTags(Twinkle.changeTags); page.deletePage(onsuccess); }); } }, user: { main: function(pageobj) { var statelem = pageobj.getStatusElement(); // defaults to /doc for lua modules, which may not exist if (!pageobj.exists() && mw.config.get('wgPageContentModel') !== 'Scribunto') { statelem.error(wgULS('页面不存在,可能已被删除', '頁面不存在,可能已被刪除')); return; } var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); statelem.status(wgULS('检查页面已有标记…', '檢查頁面已有標記…')); // check for existing deletion tags var textNoSd = text.replace(/\{\{\s*(db(-\w*)?|d|delete|deletebecause|speedy|csd|速刪|速删|快删|快刪)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, ''); if (text !== textNoSd && !confirm(wgULS('在页面上找到快速删除模板,要移除并添加新的吗?', '在頁面上找到快速刪除模板,要移除並添加新的嗎?'))) { statelem.error(wgULS('快速删除模板已被置于页面中。', '快速刪除模板已被置於頁面中。')); return; } text = textNoSd; var copyvio = /(?:\{\{\s*(copyvio|侵权|侵權)[^{}]*?\}\})/i.exec(text); if (copyvio && !confirm(wgULS('著作权验证模板已被置于页面中,您是否仍想加入一个快速删除模板?', '版權驗證模板已被置於頁面中,您是否仍想加入一個快速刪除模板?'))) { statelem.error(wgULS('页面中已有著作权验证模板。', '頁面中已有版權驗證模板。')); return; } var xfd = /(?:\{\{([rsaiftcmv]fd|md1|proposed deletion)[^{}]*?\}\})/i.exec(text); if (xfd && !confirm(wgULS('删除相关模板{{' + xfd[1] + '}}已被置于页面中,您是否仍想加入一个快速删除模板?', '刪除相關模板{{' + xfd[1] + '}}已被置於頁面中,您是否仍想加入一個快速刪除模板?'))) { statelem.error(wgULS('页面已被提交至存废讨论。', '頁面已被提交至存廢討論。')); return; } // given the params, builds the template and also adds the user talk page parameters to the params that were passed in // returns => [<string> wikitext, <object> utparams] var buildData = Twinkle.speedy.callbacks.getTemplateCodeAndParams(params), code = buildData[0]; params.utparams = buildData[1]; var thispage = new Morebits.wiki.page(mw.config.get('wgPageName')); // patrol the page, if reached from Special:NewPages if (Twinkle.getPref('markSpeedyPagesAsPatrolled')) { thispage.patrol(); } // Wrap SD template in noinclude tags if we are in template space. // Won't work with userboxes in userspace, or any other transcluded page outside template space if (mw.config.get('wgNamespaceNumber') === 10) { // Template: code = '<noinclude>' + code + '</noinclude>'; } // Remove tags that become superfluous with this action text = text.replace(/\{\{\s*([Nn]ew unreviewed article|[Uu]nreviewed|[Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, ''); if (mw.config.get('wgNamespaceNumber') === 6) { // remove "move to Commons" tag - deletion-tagged files cannot be moved to Commons text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, ''); } // Generate edit summary for edit var editsummary; if (params.normalizeds.length > 1) { editsummary = '请求快速删除('; $.each(params.normalizeds, function(index, norm) { if (norm !== 'db') { editsummary += '[[Wiktionary:CSD|CSD ' + norm.toUpperCase() + ']]、'; } }); editsummary = editsummary.substr(0, editsummary.length - 1); // remove trailing comma editsummary += ')。'; } else if (params.normalizeds[0] === 'db') { editsummary = '请求[[Wiktionary:CSD|快速删除]]:' + params.templateParams[0]['1']; } else { editsummary = '请求快速删除([[Wiktionary:CSD|CSD ' + params.normalizeds[0].toUpperCase() + ']])'; } pageobj.setPageText(code + '\n' + text); pageobj.setEditSummary(editsummary); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setWatchlist(params.watch); if (params.scribunto) { pageobj.setCreateOption('recreate'); // Module /doc might not exist if (params.watch) { // Watch module in addition to /doc subpage var watch_query = { action: 'watch', titles: mw.config.get('wgPageName'), token: mw.user.tokens.get('watchToken') }; new Morebits.wiki.api(wgULS('将模块加入到监视列表', '將模組加入到監視清單'), watch_query).post(); } } pageobj.save(Twinkle.speedy.callbacks.user.tagComplete); }, tagComplete: function(pageobj) { var params = pageobj.getCallbackParameters(); // Notification to first contributor if (params.usertalk) { var callback = function(pageobj) { var initialContrib = pageobj.getCreator(); // disallow warning yourself if (initialContrib === mw.config.get('wgUserName')) { Morebits.status.warn(wgULS('您(' + initialContrib + ')创建了该页,跳过通知', '您(' + initialContrib + ')建立了該頁,跳過通知')); // don't notify users when their user talk page is nominated } else if (initialContrib === mw.config.get('wgTitle') && mw.config.get('wgNamespaceNumber') === 3) { Morebits.status.warn(wgULS('通知页面创建者:用户创建了自己的对话页', '通知頁面建立者:用戶建立了自己的對話頁')); } else if (mw.util.isIPAddress(initialContrib)) { Morebits.status.info(wgULS('通知页面创建者:IP用户创建了该页,跳过通知', '通知頁面建立者:IP使用者建立了該頁,跳過通知')); initialContrib = null; } else { var talkPageName = 'User talk:' + initialContrib; var usertalkpage = new Morebits.wiki.page(talkPageName, wgULS('通知页面创建者(' + initialContrib + ')', '通知頁面建立者(' + initialContrib + ')')), notifytext; notifytext = '\n{{subst:db-notice|target=' + Morebits.pageNameNorm; notifytext += (params.welcomeuser ? '' : '|nowelcome=yes') + '}}--~~~~'; var editsummary = '通知:'; if (params.normalizeds.indexOf('g12') === -1) { // no article name in summary for G10 deletions editsummary += '页面[[' + Morebits.pageNameNorm + ']]'; } else { editsummary += '一攻击性页面'; } editsummary += '快速删除提名'; usertalkpage.setAppendText(notifytext); usertalkpage.setEditSummary(editsummary); usertalkpage.setChangeTags(Twinkle.changeTags); usertalkpage.setCreateOption('recreate'); usertalkpage.setFollowRedirect(true); usertalkpage.append(); } // add this nomination to the user's userspace log, if the user has enabled it if (params.lognomination) { Twinkle.speedy.callbacks.user.addToLog(params, initialContrib); } }; var thispage = new Morebits.wiki.page(Morebits.pageNameNorm); thispage.lookupCreation(callback); // or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name } else if (params.lognomination) { Twinkle.speedy.callbacks.user.addToLog(params, null); } }, // note: this code is also invoked from twinkleimage // the params used are: // for CSD: params.values, params.normalizeds (note: normalizeds is an array) // for DI: params.fromDI = true, params.templatename, params.normalized (note: normalized is a string) addToLog: function(params, initialContrib) { var wikipedia_page = new Morebits.wiki.page('User:' + mw.config.get('wgUserName') + '/' + Twinkle.getPref('speedyLogPageName'), wgULS('加入项目到用户日志', '加入項目到用戶日誌')); params.logInitialContrib = initialContrib; wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.speedy.callbacks.user.saveLog); }, saveLog: function(pageobj) { var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); var appendText = ''; // add blurb if log page doesn't exist if (!pageobj.exists()) { appendText += '这是该用户使用Twinkle的速删模块做出的[[Wiktionary:CSD|快速删除]]提名列表。\n\n' + '如果您不再想保留此日志,请在[[' + Twinkle.getPref('configPage') + '|参数设置]]中关掉,并' + '使用[[Wiktionary:CSD|CSD O1]]提交快速删除。\n'; if (Morebits.userIsSysop) { appendText += '\n此日志并不记录用Twinkle直接执行的删除。\n'; } } // create monthly header var date = new Date(pageobj.getLoadTime()); var headerRe = new RegExp('^==+\\s*' + date.getUTCFullYear() + '\\s*年\\s*' + (date.getUTCMonth() + 1) + '\\s*月\\s*==+', 'm'); if (!headerRe.exec(text)) { appendText += '\n\n=== ' + date.getUTCFullYear() + '年' + (date.getUTCMonth() + 1) + '月 ==='; } appendText += '\n# [[:' + Morebits.pageNameNorm + ']]: '; if (params.fromDI) { if (params.normalized === 'f3 f4') { appendText += '图版[[Wiktionary:CSD|CSD F3]]+[[Wiktionary:CSD|CSD F4]]({{tl|no source no license/auto}})'; } else { appendText += '图版[[Wiktionary:CSD|CSD ' + params.normalized.toUpperCase() + ']]({{tl|' + params.templatename + '}})'; } } else { if (params.normalizeds.length > 1) { appendText += '多个理由('; $.each(params.normalizeds, function(index, norm) { appendText += '[[Wiktionary:CSD|' + norm.toUpperCase() + ']]、'; }); appendText = appendText.substr(0, appendText.length - 1); // remove trailing comma appendText += ')'; } else if (params.normalizeds[0] === 'db') { appendText += '自定义理由'; } else { appendText += '[[Wiktionary:CSD|CSD ' + params.normalizeds[0].toUpperCase() + ']]'; } } if (params.logInitialContrib) { appendText += ';通知{{user|' + params.logInitialContrib + '}}'; } appendText += ' ~~~~~\n'; pageobj.setAppendText(appendText); pageobj.setEditSummary('记录对[[' + Morebits.pageNameNorm + ']]的快速删除提名'); pageobj.setChangeTags(Twinkle.changeTags); pageobj.setCreateOption('recreate'); pageobj.append(); } } }; // validate subgroups in the form passed into the speedy deletion tag Twinkle.speedy.getParameters = function twinklespeedyGetParameters(form, values) { var parameters = []; $.each(values, function(index, value) { var currentParams = []; var redimage; switch (value) { case 'reason': if (form['csd.reason_1']) { var dbrationale = form['csd.reason_1'].value; if (!dbrationale || !dbrationale.trim()) { alert(wgULS('自定义理由:请指定理由。', '自訂理由:請指定理由。')); parameters = null; return false; } currentParams['1'] = dbrationale; } break; case 'a3': if (form['csd.a3_pagename'] && form['csd.a3_pagename'].value) { currentParams.pagename = form['csd.a3_pagename'].value; } break; case 'a5': if (form['csd.a5_pagename']) { var otherpage = form['csd.a5_pagename'].value; if (!otherpage || !otherpage.trim()) { alert(wgULS('CSD A5:请提供现有条目的名称。', 'CSD A5:請提供現有條目的名稱。')); parameters = null; return false; } currentParams.pagename = otherpage; } break; case 'a6': if (form['csd.a6_pagename'] && form['csd.a6_pagename'].value) { currentParams.pagename = form['csd.a6_pagename'].value; } break; case 'g5': if (form['csd.g5_1']) { var deldisc = form['csd.g5_1'].value; if (deldisc) { if (deldisc.substring(0, 9) !== 'Wiktionary' && deldisc.substring(0, 5) !== '维基词典:' && deldisc.substring(0, 5) !== '維基詞典:') { alert(wgULS('CSD G5:您提供的讨论页名必须以“Wiktionary:”开头。', 'CSD G5:您提供的討論頁名必須以「Wiktionary:」開頭。')); parameters = null; return false; } currentParams['1'] = deldisc; } } break; case 'g10': if (form['csd.g10_rationale'] && form['csd.g10_rationale'].value) { currentParams.rationale = form['csd.g10_rationale'].value; } break; case 'f1': if (form['csd.f1_filename']) { redimage = form['csd.f1_filename'].value; if (!redimage || !redimage.trim()) { alert(wgULS('CSD F1:请提供另一文件的名称。', 'CSD F1:請提供另一檔案的名稱。')); parameters = null; return false; } currentParams.filename = redimage.replace(/^\s*(Image|File|文件|檔案):/i, ''); } break; case 'f5': if (form['csd.f5_filename']) { redimage = form['csd.f5_filename'].value; if (!redimage || !redimage.trim()) { alert(wgULS('CSD F5:请提供另一文件的名称。', 'CSD F5:請提供另一檔案的名稱。')); parameters = null; return false; } currentParams.filename = redimage.replace(/^\s*(Image|File|文件|檔案):/i, ''); } break; case 'f7': if (form['csd.f7_filename']) { var filename = form['csd.f7_filename'].value; if (filename && filename !== Morebits.pageNameNorm) { if (filename.indexOf('Image:') === 0 || filename.indexOf('File:') === 0 || filename.indexOf('文件:') === 0 || filename.indexOf('檔案:') === 0) { currentParams['1'] = filename; } else { currentParams['1'] = 'File:' + filename; } } } break; case 'r3': if (form['csd.r3_type']) { var redirtype = form['csd.r3_type'].value; if (!redirtype) { alert(wgULS('CSD R3:请选择适用类型。', 'CSD R3:請選擇適用類別。')); parameters = null; return false; } currentParams['1'] = redirtype; } break; default: break; } parameters.push(currentParams); }); return parameters; }; // function for processing talk page notification template parameters Twinkle.speedy.getUserTalkParameters = function twinklespeedyGetUserTalkParameters(normalized, parameters) { // eslint-disable-line no-unused-vars var utparams = []; switch (normalized) { default: break; } return utparams; }; Twinkle.speedy.resolveCsdValues = function twinklespeedyResolveCsdValues(e) { var values = (e.target.form ? e.target.form : e.target).getChecked('csd'); if (values.length === 0) { alert(wgULS('请选择一个理据!', '請選擇一個理據!')); return null; } return values; }; Twinkle.speedy.callback.evaluateSysop = function twinklespeedyCallbackEvaluateSysop(e) { var form = e.target.form ? e.target.form : e.target; if (e.target.type === 'checkbox' || e.target.type === 'text' || e.target.type === 'select') { return; } var tag_only = form.tag_only; if (tag_only && tag_only.checked) { Twinkle.speedy.callback.evaluateUser(e); return; } var values = Twinkle.speedy.resolveCsdValues(e); if (!values) { return; } var normalizeds = values.map(function(value) { return Twinkle.speedy.normalizeHash[value]; }); // analyse each criterion to determine whether to watch the page, prompt for summary, or open user talk page var watchPage, promptForSummary; normalizeds.forEach(function(norm) { if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) { watchPage = true; } if (Twinkle.getPref('promptForSpeedyDeletionSummary').indexOf(norm) !== -1) { promptForSummary = true; } }); var params = { values: values, normalizeds: normalizeds, watch: watchPage, deleteTalkPage: form.talkpage && form.talkpage.checked, deleteRedirects: form.redirects.checked, openUserTalk: form.openusertalk.checked, promptForSummary: promptForSummary, templateParams: Twinkle.speedy.getParameters(form, values) }; if (!params.templateParams) { return; } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); Twinkle.speedy.callbacks.sysop.main(params); }; Twinkle.speedy.callback.evaluateUser = function twinklespeedyCallbackEvaluateUser(e) { var form = e.target.form ? e.target.form : e.target; if (e.target.type === 'checkbox' || e.target.type === 'text' || e.target.type === 'select') { return; } var values = Twinkle.speedy.resolveCsdValues(e); if (!values) { return; } // var multiple = form.multiple.checked; var normalizeds = []; $.each(values, function(index, value) { var norm = Twinkle.speedy.normalizeHash[value]; normalizeds.push(norm); }); // analyse each criterion to determine whether to watch the page/notify the creator var watchPage = false; $.each(normalizeds, function(index, norm) { if (Twinkle.getPref('watchSpeedyPages').indexOf(norm) !== -1) { watchPage = true; return false; // break } }); /* var notifyuser = false; if (form.notify.checked) { $.each(normalizeds, function(index, norm) { if (Twinkle.getPref('notifyUserOnSpeedyDeletionNomination').indexOf(norm) !== -1) { notifyuser = true; return false; // break } }); } */ var welcomeuser = false; /* if (notifyuser) { $.each(normalizeds, function(index, norm) { if (Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(norm) !== -1) { welcomeuser = true; return false; // break } }); } */ var csdlog = false; if (Twinkle.getPref('logSpeedyNominations')) { $.each(normalizeds, function(index, norm) { if (Twinkle.getPref('noLogOnSpeedyNomination').indexOf(norm) === -1) { csdlog = true; return false; // break } }); } var params = { values: values, normalizeds: normalizeds, watch: watchPage, usertalk: false, welcomeuser: welcomeuser, lognomination: csdlog, templateParams: Twinkle.speedy.getParameters(form, values) }; if (!params.templateParams) { return; } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(form); Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName'); Morebits.wiki.actionCompleted.notice = wgULS('标记完成', '標記完成'); // Modules can't be tagged, follow standard at TfD and place on /doc subpage params.scribunto = mw.config.get('wgPageContentModel') === 'Scribunto'; var wikipedia_page = params.scribunto ? new Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', wgULS('标记模块文件页', '標記模組文件頁')) : new Morebits.wiki.page(mw.config.get('wgPageName'), wgULS('标记页面', '標記頁面')); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.speedy.callbacks.user.main); }; })(jQuery); // </nowiki> 6kfl7tv77pyoa2fzfhyuj5tomeuth27 MediaWiki:Gadget-twinklexfd.js 8 1368876 9943050 6333139 2026-10-12T15:41:44Z Xiplus 25402 Repo at 375b1e1: Do not edit legacy IP page 9943050 javascript text/javascript // <nowiki> // vim: set noet sts=0 sw=8: (function($) { /* **************************************** *** twinklexfd.js: XFD module **************************************** * Mode of invocation: Tab ("XFD") * Active on: Existing, non-special pages, except for file pages with no local (non-Commons) file which are not redirects */ Twinkle.xfd = function twinklexfd() { // Disable on: // * special pages // * non-existent pages // * files on Commons, whether there is a local page or not (unneeded local pages of files on Commons are eligible for CSD F2) // * file pages without actual files (these are eligible for CSD G8) if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId') || (mw.config.get('wgNamespaceNumber') === 6 && (document.getElementById('mw-sharedupload') || (!document.getElementById('mw-imagepage-section-filehistory') && !Morebits.wiki.isPageRedirect())))) { return; } Twinkle.addPortletLink(Twinkle.xfd.callback, wgULS('提删', '提刪'), 'tw-xfd', wgULS('提交删除讨论', '提交刪除討論')); }; Twinkle.xfd.currentRationale = null; // error callback on Morebits.status.object Twinkle.xfd.printRationale = function twinklexfdPrintRationale() { if (Twinkle.xfd.currentRationale) { Morebits.status.printUserText(Twinkle.xfd.currentRationale, wgULS('您的理由已在下方提供,如果您想重新提交,请将其复制到一新窗口中:', '您的理由已在下方提供,如果您想重新提交,請將其複製到一新窗口中:')); // only need to print the rationale once Twinkle.xfd.currentRationale = null; } }; Twinkle.xfd.callback = function twinklexfdCallback() { var Window = new Morebits.simpleWindow(600, 350); Window.setTitle(wgULS('提交存废讨论', '提交存廢討論')); Window.setScriptName('Twinkle'); Window.addFooterLink(wgULS('关于存废讨论', '關於存廢討論'), 'Wiktionary:RFD'); Window.addFooterLink(wgULS('Twinkle帮助', 'Twinkle說明'), 'w:Help:Twinkle#提刪'); var form = new Morebits.quickForm(Twinkle.xfd.callback.evaluate); var categories = form.append({ type: 'select', name: 'category', label: wgULS('提交类型:', '提交類別:'), event: Twinkle.xfd.callback.change_category }); categories.append({ type: 'option', label: wgULS('页面存废讨论', '頁面存廢討論'), selected: mw.config.get('wgNamespaceNumber') === 0, // Main namespace value: 'afd' }); form.append({ type: 'checkbox', list: [ { label: wgULS('如可能,通知页面创建者', '如可能,通知頁面建立者'), value: 'notify', name: 'notify', tooltip: wgULS('在页面创建者对话页上放置一通知模板。', '在頁面建立者對話頁上放置一通知模板。'), checked: true } ] } ); form.append({ type: 'field', label: '工作区', name: 'work_area' }); form.append({ type: 'submit' }); var result = form.render(); Window.setContent(result); Window.display(); // We must init the controls var evt = document.createEvent('Event'); evt.initEvent('change', true, true); result.category.dispatchEvent(evt); }; Twinkle.xfd.callback.change_category = function twinklexfdCallbackChangeCategory(e) { var value = e.target.value; var form = e.target.form; var old_area = Morebits.quickForm.getElements(e.target.form, 'work_area')[0]; var work_area = null; var oldreasontextbox = form.getElementsByTagName('textarea')[0]; var oldreason = oldreasontextbox ? oldreasontextbox.value : ''; var appendReasonBox = function twinklexfdAppendReasonBox(xfd_cat) { switch (xfd_cat) { case 'fwdcsd': oldreason = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' '); break; case 'fame': oldreason = Twinkle.getPref('afdFameDefaultReason'); break; case 'substub': oldreason = Twinkle.getPref('afdSubstubDefaultReason'); break; default: break; } work_area.append({ type: 'textarea', name: 'xfdreason', label: wgULS('提删理由:', '提刪理由:'), value: oldreason, tooltip: wgULS('您可以使用维基格式,Twinkle将自动为您加入签名。如果您使用批量提删功能,存废讨论页只会使用第一次提交的理由,但您仍需在之后提供以用于删除通告模板的参数。', '您可以使用維基格式,Twinkle將自動為您加入簽名。如果您使用批量提刪功能,存廢討論頁只會使用第一次提交的理由,但您仍需在之後提供以用於刪除通告模板的參數。') }); // TODO possible future "preview" link here }; switch (value) { case 'afd': work_area = new Morebits.quickForm.element({ type: 'field', label: wgULS('页面存废讨论', '頁面存廢討論'), name: 'work_area' }); work_area.append({ type: 'checkbox', list: [ { label: '使用&lt;noinclude&gt;包裹模板', value: 'noinclude', name: 'noinclude', checked: mw.config.get('wgNamespaceNumber') === 10, // Template namespace tooltip: wgULS('使其不会在被包含时出现。', '使其不會在被包含時出現。') } ] }); var afd_category = work_area.append({ type: 'select', name: 'xfdcat', label: wgULS('选择提删类型:', '選擇提刪類別:'), event: Twinkle.xfd.callback.change_afd_category }); var afd_cat = 'delete'; if (Twinkle.getPref('afdDefaultCategory') === 'same') { if (localStorage.Twinkle_afdCategory === undefined) { localStorage.Twinkle_afdCategory = 'delete'; } else { afd_cat = localStorage.Twinkle_afdCategory; } } afd_category.append({ type: 'option', label: wgULS('删除', '刪除'), value: 'delete', selected: afd_cat === 'delete' }); afd_category.append({ type: 'option', label: wgULS('合并', '合併'), value: 'merge', selected: afd_cat === 'merge' }); afd_category.append({ type: 'option', label: wgULS('移动到维基百科', '移動到維基百科'), value: 'vmw', selected: afd_cat === 'vmw' }); afd_category.append({ type: 'option', label: wgULS('移动到维基文库', '移動到維基文庫'), value: 'vms', selected: afd_cat === 'vms' }); afd_category.append({ type: 'option', label: wgULS('移动到维基教科书', '移動到維基教科書'), value: 'vmb', selected: afd_cat === 'vmb' }); afd_category.append({ type: 'option', label: wgULS('移动到维基语录', '移動到維基語錄'), value: 'vmq', selected: afd_cat === 'vmq' }); afd_category.append({ type: 'option', label: wgULS('移动到维基导游', '移動到維基導遊'), value: 'vmvoy', selected: afd_cat === 'vmvoy' }); afd_category.append({ type: 'option', label: wgULS('移动到维基学院', '移動到維基學院'), value: 'vmv', selected: afd_cat === 'vmv' }); if (Twinkle.getPref('FwdCsdToXfd')) { afd_category.append({ type: 'option', label: wgULS('转交自快速删除候选', '轉交自快速刪除候選'), value: 'fwdcsd', selected: afd_cat === 'fwdcsd' }); } work_area.append({ type: 'input', name: 'mergeinto', label: wgULS('合并到:', '合併到:'), hidden: true }); appendReasonBox(afd_cat); work_area.append({ type: 'textarea', name: 'fwdcsdreason', label: wgULS('转交理由:', '轉交理由:'), tooltip: wgULS('您可以使用维基格式,Twinkle将自动为您加入签名。', '您可以使用維基格式,Twinkle將自動為您加入簽名。'), hidden: true }); work_area = work_area.render(); old_area.parentNode.replaceChild(work_area, old_area); var evt = document.createEvent('Event'); evt.initEvent('change', true, true); form.xfdcat.dispatchEvent(evt); break; case 'ffd': work_area = new Morebits.quickForm.element({ type: 'field', label: wgULS('文件存废讨论', '檔案存廢討論'), name: 'work_area' }); appendReasonBox('ffd'); work_area = work_area.render(); old_area.parentNode.replaceChild(work_area, old_area); break; default: work_area = new Morebits.quickForm.element({ type: 'field', label: wgULS('未定义', '未定義'), name: 'work_area' }); work_area = work_area.render(); old_area.parentNode.replaceChild(work_area, old_area); break; } // Return to checked state when switching form.notify.checked = true; form.notify.disabled = false; }; Twinkle.xfd.callback.change_afd_category = function twinklexfdCallbackChangeAfdCategory(e) { if (e.target.value === 'merge') { e.target.form.mergeinto.parentElement.removeAttribute('hidden'); e.target.form.fwdcsdreason.parentElement.setAttribute('hidden', ''); e.target.form.mergeinto.previousElementSibling.innerHTML = wgULS('合并到:', '合併到:'); } else if (e.target.value === 'fwdcsd') { e.target.form.mergeinto.parentElement.removeAttribute('hidden'); e.target.form.fwdcsdreason.parentElement.removeAttribute('hidden'); e.target.form.mergeinto.previousElementSibling.innerHTML = '提交人:'; e.target.form.xfdreason.value = decodeURIComponent($('#delete-reason').text()).replace(/\+/g, ' '); } else if (e.target.value === 'fame') { e.target.form.mergeinto.parentElement.setAttribute('hidden', ''); e.target.form.fwdcsdreason.parentElement.setAttribute('hidden', ''); e.target.form.xfdreason.value = Twinkle.getPref('afdFameDefaultReason'); } else if (e.target.value === 'substub') { e.target.form.mergeinto.parentElement.setAttribute('hidden', ''); e.target.form.fwdcsdreason.parentElement.setAttribute('hidden', ''); e.target.form.xfdreason.value = Twinkle.getPref('afdSubstubDefaultReason'); } else { e.target.form.mergeinto.parentElement.setAttribute('hidden', ''); e.target.form.fwdcsdreason.parentElement.setAttribute('hidden', ''); } if (Twinkle.getPref('afdDefaultCategory') === 'same') { localStorage.Twinkle_afdCategory = e.target.value; } }; Twinkle.xfd.callbacks = { afd: { main: function(pageobj) { // this is coming in from lookupCreation...! var params = pageobj.getCallbackParameters(); // Adding discussion var wikipedia_page = new Morebits.wiki.page(params.logpage, wgULS('加入讨论到当日清单', '加入討論到當日清單')); wikipedia_page.setFollowRedirect(true); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.xfd.callbacks.afd.todaysList); // Notification to first contributor if (params.usertalk) { var initialContrib = pageobj.getCreator(); // Disallow warning yourself if (initialContrib === mw.config.get('wgUserName')) { pageobj.getStatusElement().warn(wgULS('您(' + initialContrib + ')创建了该页,跳过通知', '您(' + initialContrib + ')建立了該頁,跳過通知')); return; } if (mw.util.isIPAddress(initialContrib)) { pageobj.getStatusElement().info(wgULS('通知页面创建者:IP用户创建了该页,跳过通知', '通知頁面建立者:IP使用者建立了該頁,跳過通知')); return; } var talkPageName = 'User talk:' + initialContrib; var usertalkpage = new Morebits.wiki.page(talkPageName, wgULS('通知页面创建者(' + initialContrib + ')', '通知頁面建立者(' + initialContrib + ')')); var notifytext = '\n{{subst:AFDNote|' + Morebits.pageNameNorm + '}}--~~~~'; usertalkpage.setAppendText(notifytext); usertalkpage.setEditSummary('通知:页面[[' + Morebits.pageNameNorm + ']]存废讨论提名'); usertalkpage.setChangeTags(Twinkle.changeTags); usertalkpage.setCreateOption('recreate'); switch (Twinkle.getPref('xfdWatchUser')) { case 'yes': usertalkpage.setWatchlist(true); break; case 'no': usertalkpage.setWatchlistFromPreferences(false); break; default: usertalkpage.setWatchlistFromPreferences(true); break; } usertalkpage.setFollowRedirect(true); usertalkpage.append(); } }, taggingArticle: function(pageobj) { var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); var tag = '{{vfd|' + Morebits.string.formatReasonText(params.reason); switch (params.xfdcat) { case 'vmw': tag += '|wikt'; break; case 'vms': tag += '|s'; break; case 'vmb': tag += '|b'; break; case 'vmq': tag += '|q'; break; case 'vmvoy': tag += '|voy'; break; case 'vmv': tag += '|v'; break; default: break; } if (Morebits.wiki.isPageRedirect()) { tag += '|r'; } tag += '|date={{subst:#time:Y/m/d}}}}'; if (params.noinclude) { tag = '<noinclude>' + tag + '</noinclude>'; // 只有表格需要单独加回车,其他情况加回车会破坏模板。 if (text.indexOf('{|') === 0) { tag += '\n'; } } else { tag += '\n'; } // Then, test if there are speedy deletion-related templates on the article. var textNoSd = text.replace(/\{\{\s*(db(-\w*)?|d|delete|(?:hang|hold)[- ]?on)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, ''); if (text !== textNoSd && confirm(wgULS('在页面上找到快速删除模板,要移除吗?', '在頁面上找到快速刪除模板,要移除嗎?'))) { text = textNoSd; } // Mark the page as patrolled, if wanted if (Twinkle.getPref('markXfdPagesAsPatrolled')) { pageobj.patrol(); } pageobj.setPageText(tag + text); pageobj.setEditSummary(wgULS('页面存废讨论:[[', '頁面存廢討論:[[') + params.logpage + '#' + Morebits.pageNameNorm + ']]'); pageobj.setChangeTags(Twinkle.changeTags); switch (Twinkle.getPref('xfdWatchPage')) { case 'yes': pageobj.setWatchlist(true); break; case 'no': pageobj.setWatchlistFromPreferences(false); break; default: pageobj.setWatchlistFromPreferences(true); break; } // pageobj.setCreateOption('recreate'); pageobj.save(); if (Twinkle.getPref('markXfdPagesAsPatrolled')) { pageobj.patrol(); } }, todaysList: function(pageobj) { var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); var type = ''; var to = ''; switch (params.xfdcat) { case 'vmw': case 'vms': case 'vmb': case 'vmq': case 'vmvoy': case 'vmv': type = 'vm'; to = params.xfdcat; break; case 'fwdcsd': case 'merge': to = params.mergeinto; /* Fall through */ default: type = params.xfdcat; break; } var append = true; switch (type) { case 'fame': case 'substub': case 'batch': var commentText = '<!-- Twinkle: User:' + mw.config.get('wgUserName') + ' 的 ' + type + ' 提刪插入點,請勿變更或移除此行,除非不再於此頁提刪 -->'; var newText = '===[[' + Morebits.pageNameNorm + ']]==='; if (type === 'fame') { newText += '\n{{Findsources|' + Morebits.pageNameNorm + '}}'; } if (text.indexOf(commentText) !== -1) { text = text.replace(commentText, newText + '\n\n' + commentText); pageobj.setPageText(text); append = false; } else { var appendText = '\n{{safesubst:SafeAfdHead}}\n' + { 'fame': '==30天后仍掛有{{tl|notability}}模板的條目==\n' + '<span style="font-size:smaller;">(已掛[[template:notability|關注度模板]]30天)</span>', 'substub': '==到期篩選的小小作品==', 'batch': '==批量提刪==' }[type] + '\n' + newText + '\n\n' + commentText + '\n' + '----\n' + '(請不要在橫線下参与讨论,以免出现错误。)\n' + ':{{删除}}理據:' + Morebits.string.formatReasonText(params.reason) + '\n' + '提报以上' + { 'fame': '<u>关注度不足</u>条目', 'substub': '<u>小小作品</u>', 'batch': '頁面' }[type] + '的維基人及時間:<br id="no-new-title" />~~~~'; pageobj.setAppendText(appendText); } break; default: pageobj.setAppendText('\n{{subst:DRItem|Type=' + type + '|DRarticles=' + Morebits.pageNameNorm + '|Reason=' + Morebits.string.formatReasonText(params.reason) + (params.fwdcsdreason.trim() !== '' ? '<br>\n轉交理由:' + params.fwdcsdreason : '') + '|To=' + to + '}}--~~~~'); break; } pageobj.setEditSummary('加入[[' + Morebits.pageNameNorm + ']]'); pageobj.setChangeTags(Twinkle.changeTags); switch (Twinkle.getPref('xfdWatchDiscussion')) { case 'yes': pageobj.setWatchlist(true); break; case 'no': pageobj.setWatchlistFromPreferences(false); break; default: pageobj.setWatchlistFromPreferences(true); break; } pageobj.setCreateOption('recreate'); if (append) { pageobj.append(); } else { pageobj.save(); } Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki }, tryTagging: function (pageobj) { var statelem = pageobj.getStatusElement(); // defaults to /doc for lua modules, which may not exist if (!pageobj.exists() && mw.config.get('wgPageContentModel') !== 'Scribunto') { statelem.error(wgULS('页面不存在,可能已被删除', '頁面不存在,可能已被刪除')); return; } var text = pageobj.getPageText(); var xfd = /(?:\{\{([rsaiftcmv]fd|md1|proposed deletion)[^{}]*?\}\})/i.exec(text); if (xfd && !confirm(wgULS('删除相关模板{{' + xfd[1] + '}}已被置于页面中,您是否仍想继续提报?', '刪除相關模板{{' + xfd[1] + '}}已被置於頁面中,您是否仍想繼續提報?'))) { statelem.error(wgULS('页面已被提交至存废讨论。', '頁面已被提交至存廢討論。')); return; } var copyvio = /(?:\{\{\s*(copyvio)[^{}]*?\}\})/i.exec(text); if (copyvio) { statelem.error(wgULS('页面中已有著作权验证模板。', '頁面中已有版權驗證模板。')); return; } Twinkle.xfd.callbacks.afd.taggingArticle(pageobj); // Notification to first contributor var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName')); wikipedia_page.setCallbackParameters(pageobj.getCallbackParameters()); wikipedia_page.setLookupNonRedirectCreator(true); // Look for author of first non-redirect revision wikipedia_page.lookupCreation(Twinkle.xfd.callbacks.afd.main); } }, ffd: { main: function(pageobj) { // this is coming in from lookupCreation...! var params = pageobj.getCallbackParameters(); var initialContrib = pageobj.getCreator(); params.uploader = initialContrib; // Adding discussion var wikipedia_page = new Morebits.wiki.page(params.logpage, wgULS('加入讨论到当日清单', '加入討論到當日清單')); wikipedia_page.setFollowRedirect(true); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.xfd.callbacks.ffd.todaysList); // Notification to first contributor if (params.usertalk) { // Disallow warning yourself if (initialContrib === mw.config.get('wgUserName')) { pageobj.getStatusElement().warn(wgULS('您(' + initialContrib + ')创建了该页,跳过通知', '您(' + initialContrib + ')建立了該頁,跳過通知')); return; } if (mw.util.isIPAddress(initialContrib)) { pageobj.getStatusElement().info(wgULS('通知页面创建者:IP用户创建了该页,跳过通知', '通知頁面建立者:IP使用者建立了該頁,跳過通知')); return; } var talkPageName = 'User talk:' + initialContrib; var usertalkpage = new Morebits.wiki.page(talkPageName, wgULS('通知页面创建者(' + initialContrib + ')', '通知頁面建立者(' + initialContrib + ')')); var notifytext = '\n{{subst:idw|File:' + mw.config.get('wgTitle') + '}}--~~~~'; usertalkpage.setAppendText(notifytext); usertalkpage.setEditSummary('通知:文件[[' + Morebits.pageNameNorm + ']]存废讨论提名'); usertalkpage.setChangeTags(Twinkle.changeTags); usertalkpage.setCreateOption('recreate'); switch (Twinkle.getPref('xfdWatchUser')) { case 'yes': usertalkpage.setWatchlist(true); break; case 'no': usertalkpage.setWatchlistFromPreferences(false); break; default: usertalkpage.setWatchlistFromPreferences(true); break; } usertalkpage.setFollowRedirect(true); usertalkpage.append(); } }, taggingImage: function(pageobj) { var text = pageobj.getPageText(); var params = pageobj.getCallbackParameters(); pageobj.setPageText('{{ifd|' + Morebits.string.formatReasonText(params.reason) + '|date={{subst:#time:c}}}}\n' + text); pageobj.setEditSummary(wgULS('文件存废讨论:[[', '檔案存廢討論:[[') + params.logpage + '#' + Morebits.pageNameNorm + ']]'); pageobj.setChangeTags(Twinkle.changeTags); switch (Twinkle.getPref('xfdWatchPage')) { case 'yes': pageobj.setWatchlist(true); break; case 'no': pageobj.setWatchlistFromPreferences(false); break; default: pageobj.setWatchlistFromPreferences(true); break; } pageobj.setCreateOption('recreate'); // it might be possible for a file to exist without a description page pageobj.save(); }, todaysList: function(pageobj) { var params = pageobj.getCallbackParameters(); pageobj.setAppendText('\n{{subst:IfdItem|Filename=' + mw.config.get('wgTitle') + '|Uploader=' + params.uploader + '|Reason=' + Morebits.string.formatReasonText(params.reason) + '}}--~~~~'); pageobj.setEditSummary('加入[[' + Morebits.pageNameNorm + ']]'); pageobj.setChangeTags(Twinkle.changeTags); switch (Twinkle.getPref('xfdWatchDiscussion')) { case 'yes': pageobj.setWatchlist(true); break; case 'no': pageobj.setWatchlistFromPreferences(false); break; default: pageobj.setWatchlistFromPreferences(true); break; } pageobj.setCreateOption('recreate'); pageobj.append(function() { Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki }); }, tryTagging: function (pageobj) { var statelem = pageobj.getStatusElement(); if (!pageobj.exists()) { statelem.error(wgULS('页面不存在,可能已被删除', '頁面不存在,可能已被刪除')); return; } var text = pageobj.getPageText(); var xfd = /(?:\{\{([rsaiftcmv]fd|md1|proposed deletion)[^{}]*?\}\})/i.exec(text); if (xfd && !confirm(wgULS('删除相关模板{{' + xfd[1] + '}}已被置于页面中,您是否仍想继续提报?', '刪除相關模板{{' + xfd[1] + '}}已被置於頁面中,您是否仍想繼續提報?'))) { statelem.error(wgULS('页面已被提交至存废讨论。', '頁面已被提交至存廢討論。')); return; } Twinkle.xfd.callbacks.ffd.taggingImage(pageobj); // Contributor specific edits var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName')); wikipedia_page.setCallbackParameters(pageobj.getCallbackParameters()); wikipedia_page.setLookupNonRedirectCreator(true); // Look for author of first non-redirect revision wikipedia_page.lookupCreation(Twinkle.xfd.callbacks.ffd.main); } } }; Twinkle.xfd.callback.evaluate = function(e) { var type = e.target.category.value; var usertalk = e.target.notify.checked; var reason = e.target.xfdreason.value; var fwdcsdreason, xfdcat, mergeinto, noinclude; if (type === 'afd') { fwdcsdreason = e.target.fwdcsdreason.value; noinclude = e.target.noinclude.checked; xfdcat = e.target.xfdcat.value; mergeinto = e.target.mergeinto.value; } Morebits.simpleWindow.setButtonsEnabled(false); Morebits.status.init(e.target); Twinkle.xfd.currentRationale = reason; Morebits.status.onError(Twinkle.xfd.printRationale); if (!type) { Morebits.status.error('错误', '未定义的动作'); return; } var wikipedia_page, logpage, params; switch (type) { case 'afd': // AFD logpage = 'Wiktionary:删除请求'; params = { usertalk: usertalk, xfdcat: xfdcat, mergeinto: mergeinto, noinclude: noinclude, reason: reason, fwdcsdreason: fwdcsdreason, logpage: logpage }; Morebits.wiki.addCheckpoint(); // Updating data for the action completed event Morebits.wiki.actionCompleted.redirect = logpage; Morebits.wiki.actionCompleted.notice = wgULS('提名完成,重定向到讨论页', '提名完成,重定向到討論頁'); // Tagging page var isScribunto = mw.config.get('wgPageContentModel') === 'Scribunto'; wikipedia_page = isScribunto ? new Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', wgULS('加入存废讨论模板到模块文件页', '加入存廢討論模板到模組文件頁')) : new Morebits.wiki.page(mw.config.get('wgPageName'), wgULS('加入存废讨论模板到页面', '加入存廢討論模板到頁面')); wikipedia_page.setFollowRedirect(false); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.xfd.callbacks.afd.tryTagging); Morebits.wiki.removeCheckpoint(); break; case 'ffd': // FFD logpage = 'Wiktionary:删除请求'; params = { usertalk: usertalk, reason: reason, logpage: logpage }; Morebits.wiki.addCheckpoint(); // Updating data for the action completed event Morebits.wiki.actionCompleted.redirect = logpage; Morebits.wiki.actionCompleted.notice = wgULS('提名完成,重定向到讨论页', '提名完成,重定向到討論頁'); // Tagging file wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), wgULS('加入存废讨论模板到文件描述页', '加入存廢討論模板到檔案描述頁')); wikipedia_page.setFollowRedirect(false); wikipedia_page.setCallbackParameters(params); wikipedia_page.load(Twinkle.xfd.callbacks.ffd.tryTagging); Morebits.wiki.removeCheckpoint(); break; default: alert('twinklexfd:未定义的类别'); break; } }; })(jQuery); // </nowiki> tm49l01tfwp8x91wc2yf2h1iroxxtvk 遼東白豕 0 1373153 9943136 8455876 2026-10-12T23:32:56Z Fglffer 55252 9943136 wikitext text/x-wiki ==漢語== {{zh-forms|s=辽东白豕|type=211}} ===詞源=== 出自漢·{{w|朱浮}}《[[s:為幽州牧與彭寵書|為幽州牧與彭寵書]]》: : {{zh-x|往時 '''^遼東''' 有 '''豕''',生子 '''白''' 頭,異 而 獻 之。行 至 河東,見 群 豕 皆 白,懷 慚 而 還{huán}。||CL}} ===發音=== {{zh-pron |m=liáodōng白shǐ |c=liu4 dung1 baak6 ci2 |cat=cy }} ===成語=== {{head|zh|成語}} # {{lb|zh|比喻|自謙}} [[少見多怪]]而[[自視]][[不凡]],或因[[見識]][[淺薄]]而[[羞慚]] ====同義詞==== * {{zh-l|遼東白}} * {{zh-l|遼東豕}} * {{zh-l|遼東白頭}} * {{zh-l|遼豕}} ===參考資料=== * {{R:zh:cbgycdxdb|63086}} bofjtbowhjs5uvg4oy77fn7gawmdygi 9943137 9943136 2026-10-12T23:34:35Z Fglffer 55252 9943137 wikitext text/x-wiki ==漢語== {{zh-forms|s=辽东白豕|type=211}} ===詞源=== 出自漢·{{w|朱浮}}《[[s:為幽州牧與彭寵書|為幽州牧與彭寵書]]》: {{zh-x|往時 '''^遼東''' 有 '''豕''',生子 '''白''' 頭,異 而 獻 之。行 至 河東,見 群 豕 皆 白,懷 慚 而 還{huán}。||CL|collapsed=y}} ===發音=== {{zh-pron |m=liáodōng白shǐ |c=liu4 dung1 baak6 ci2 |cat=cy }} ===成語=== {{head|zh|成語}} # {{lb|zh|比喻|自謙}} [[少見多怪]]而[[自視]][[不凡]],或因[[見識]][[淺薄]]而[[羞慚]] ====同義詞==== * {{zh-l|遼東白}} * {{zh-l|遼東豕}} * {{zh-l|遼東白頭}} * {{zh-l|遼豕}} ===參考資料=== * {{R:zh:cbgycdxdb|63086}} dasx0w7e31luam6h2jdp4ru71c3k3dc MediaWiki:Gadget-Twinkle.css 8 1381118 9943036 8649587 2026-10-12T15:40:28Z Xiplus 25402 Repo at 375b1e1: update onsite fix (#392); vector-2022: hovering should open menu 9943036 css text/css /** * Explicitly set width of TW menu so that we can use a hidden peer gadget * to add space where the TW menu would go before it loads. See * twinkle-pagestyles.css */ .skin-vector .vector-menu-dropdown #p-twinkle { width: 3.24em; } /** * In skin vector-2022, open the TW menu when mouse hovers over it. Credit to * [[en:User:Nardog]] for this fix. */ #p-twinkle:hover > .vector-menu-content { opacity: 1; visibility: visible; height: auto; } /* twinkleclose.js */ a.twinkle-close-button { color: red; font-weight: bold; } a.twinkle-close-button-disabled { color: gray; } body.skin-vector-2022 #p-twinkle { display: flex; } /* The additional box on user skin.js and twinklepreferences.js pages */ #twinkle-config-headerbox { border: 1px #f60 solid; background: #fed; padding: 0.6em; margin: 0.5em auto; text-align: center; } /* twinkleoptions.js */ #twinkle-config-headerbox.config-twopt-box { font-weight: bold; width: 80%; border-width: 2px; } /* skin-specific js */ #twinkle-config-headerbox.config-userskin-box { width: 60%; } oyn8766d76nmqhjwsmrlysn3joefy2v 昼食 0 1393354 9943060 8531347 2026-10-12T17:21:34Z Jiba1219 110530 9943060 wikitext text/x-wiki ==日語== {{ja-kanjitab|ちゅう|しょく|yomi=o,kanon}} ===讀音=== {{ja-pron|ちゅうしょく|acc=0|acc_ref=NHK}} ===名詞=== {{ja-noun|ちゅうしょく|hist1=ちうしよく}} # [[午餐]],[[午飯]] ====近義詞==== * {{ja-r|お昼|おひる}} * {{ja-r|昼ご飯|ひるごはん}} * {{ja-r|昼飯|ひるめし}} * {{ja-r|ランチ}} ====派生詞==== * {{ja-r|昼%食%会|ちゅう%しょく%かい}} ====相關詞彙==== * {{ja-r|朝%食|ちょう%しょく|gloss=[[早餐]]}} * {{ja-r|夕%食|ゆう%しょく|gloss=[[晚餐]]}} * {{ja-r|夜%食|gloss=[[宵夜]]|や%しょく}} ===參考資料=== {{reflist}} :* {{R:Shogakukan}} [[Category:日語 飲食|ちゅうしょく]] rl7k6mh38kemothcsxczh0p09hjih8p Module:It-headword 828 1426245 9943113 9909077 2026-10-12T19:24:08Z TongcyDai 53191 同步 enwikt(rev 93432739):改以 termobj_has_decorations() 檢查複數代碼是否帶行內修飾符(新版 [[Module:headword utilities]] 所需) 9943113 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 m_headword_utilities.termobj_has_decorations(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 8wunr97xqwbq03q457rx4c6cw5mhncg Module:Fr-headword 828 1426246 9943111 9909066 2026-10-12T19:24:00Z TongcyDai 53191 同步 enwikt(rev 93432992):改以 termobj_has_decorations() 檢查複數代碼是否帶行內修飾符(新版 [[Module:headword utilities]] 所需) 9943111 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 m_headword_utilities.termobj_has_decorations(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 g0pkd7nu4ej5niz8mudmf0z47hbp356 Module:Pt-headword 828 1426258 9943115 9909099 2026-10-12T19:24:15Z TongcyDai 53191 同步 enwikt(rev 93432801):改以 termobj_has_decorations() 檢查複數代碼是否帶行內修飾符(新版 [[Module:headword utilities]] 所需) 9943115 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 m_headword_utilities.termobj_has_decorations(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 r5l2dk2slkwfk5tiiv37hqxc80uyoy6 Module:Es-headword 828 1426264 9943110 9909056 2026-10-12T19:23:56Z TongcyDai 53191 同步 enwikt(rev 93432827):改以 termobj_has_decorations() 檢查複數代碼是否帶行內修飾符(新版 [[Module:headword utilities]] 所需);mfpls 錯誤修正 9943110 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 m_headword_utilities.termobj_has_decorations(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 mfpls = 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(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|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 5v90nfl7fardd93c3ll2v2mj70fbk9t 𱃌 0 1448293 9943672 5863232 2026-10-13T08:59:22Z Fglffer 55252 9943672 wikitext text/x-wiki {{character info}} ==跨語言== ===漢字=== {{Han char|rn=182|rad=風|as=11|sn=20|four=|canj=|ids=⿰⿳⺈回夕風}} ====來源==== * {{Han ref|kx=|dkj=|dj=|hdz=|uh=310CC}} ==漢語== {{zh-see|颼|v}} lz42w2n29386l6wj07htfjwpqe7j1v0 Module:Ca-headword 828 1480791 9943109 9909049 2026-10-12T19:23:52Z TongcyDai 53191 同步 enwikt(rev 93432993):改以 termobj_has_decorations() 檢查複數代碼是否帶行內修飾符(新版 [[Module:headword utilities]] 所需) 9943109 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 m_headword_utilities.termobj_has_decorations(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 7etrpla0cx9igwa9zielgavx3verhc2 女同性戀 0 1732884 9943025 9444704 2026-10-12T12:31:01Z 内存溢出的猫 85038 /* 近義詞 */ 9943025 wikitext text/x-wiki ==漢語== {{zh-forms|s=女同性恋|type=13}} ===發音=== {{zh-pron |m=nǚtóngxìngliàn |cat=n }} ===名詞=== {{head|zh|名詞}} # 生理上為[[女性]],且對[[同性]]產生[[性幻想]]並希望與其建立親密關係的[[性向]] ====近義詞==== * {{qualifier|短形式}} {{zh-l|女同}} * {{qualifier|网络用语}} {{zh-l|le}} {{zh-dial}} ====參見==== * {{zh-l|男同性戀}} {{C|zh|LGBTQ}} 0h6up4zn3q5dkacbc5kv1nd9mbc5keq 龍駕 0 1761827 9943141 8306181 2026-10-12T23:39:14Z Fglffer 55252 9943141 wikitext text/x-wiki ==漢語== {{zh-forms|s=龙驾}} ===發音=== {{zh-pron |m=lóngjià |cat=n }} ===名詞=== {{head|zh|名詞}} # [[天子]]的[[尊稱]],後用於對[[官員]]的尊稱 c2488ionuntv0o773amn9v9yuxtriwd 紙片人 0 1775013 9943134 8306641 2026-10-12T23:32:36Z Fglffer 55252 9943134 wikitext text/x-wiki ==汉语== {{zh-forms|type=21|s=纸片人}} ===名词=== {{zh-pron |m=zhǐpiànrén |c=zi2 pin3 jan4 |cat=n }} ===名词=== {{head|zh|名詞}} # 像[[指]]一样[[薄]]的人 # {{lb|zh|新词|网络俚语|ACG|幽默|自谦}} 2D平面上[[創作]]的[[角色]],尤指[[日本]][[動畫]][[風格]]的角色 7ruvc66s8fhlxl9whn4fw0lwc9tqapq 9943162 9943134 2026-10-13T01:14:43Z Sayonzei 40728 /* 名词 */ 修正筆誤 9943162 wikitext text/x-wiki ==汉语== {{zh-forms|type=21|s=纸片人}} ===名词=== {{zh-pron |m=zhǐpiànrén |c=zi2 pin3 jan4 |cat=n }} ===名词=== {{head|zh|名詞}} # 像[[紙]]一样[[薄]]的人 # {{lb|zh|新词|网络俚语|ACG|幽默|自谦}} 2D平面上[[創作]]的[[角色]],尤指[[日本]][[動畫]][[風格]]的角色 ffbq7eiro8jgj0f3tboxxhn18u3hvz5 Module:Headword utilities 828 1824706 9943116 9909266 2026-10-12T19:24:41Z TongcyDai 53191 同步 enwikt(rev 93489953)並本地化:process_headword 改版(pos_functions 可用「head.詞類」索引、infls 支援 accel_form、~ 代表頁名、abbr=、inflsuf=、? ! +- 等模式符號、simplify_pos);pl=- 的標籤由「無複數」改為「不可數」 9943116 Scribunto text/plain local export = {} --[==[ FIXME: 1. Implement ? = unknown or uncertain. There should be no label by default but it should be configurable. The default category should be "{plpos} with unknown or uncertain plurals" for nouns/proper nouns. It should be an error to specify values along with this. [DONE] 2. Implement ! = unattested. The default label should be "{label} not attested". The default category should be "{plpos} with unattested plurals" for nouns/proper nouns. It should be an error to specify values along with this. [DONE] 3. Implement +- = sometimes has a value, e.g. "countable and uncountable" for plurals. [DONE] 4. Implement ~ standing for the pagename in heads and inflections. [DONE] 5. Implement abbr= for abbreviations. [DONE] 6. Split insert_inflection into process_for_insertion() (which implements the logic for handling special mode signals) and insert_processed_inflection() (which implements the labels and categories for mode signals) so that we can handle mode signals correctly. [DONE] 7. Support no_label for booleans. 8. Document the various types of parts of speech and implement pos_category per part of speech definition. [DONE] 9. Implement apply_link_modifiers() for head=~~... 10. Correctly handle var=both. [DONE] 11. Implement checkredlinks and port simplify_pos to [[Module:headword]]. [CHECKREDLINKS SET; SIMPLIFY_POS NOT PORTED YET.] 12. Fix longstanding accelerator bug: if embedded links in inflection, don't make it green. [DONE] 13. Implement process_terms() callback after process_for_insertion. [DONE] 14. Implement validate_and_canonicalize() callback. [NOT DONE; INSTEAD HAVE A SEPARATE CANONICALIZE CALLBACK] 15. Document new methods. 16. Implement process_one_term() callback after process_for_insertion. [DONE] 17. Implement process() at a stage to happen universally when there is no argument to parse. [DONE] 18. Skip mode signals during validation and canonicalization. [DONE] 19. Implement inflsuf= with special handling and special inflectional categories. [DONE] 20. Add support for "boolean or string" type. 21. Support invocation-specified or user-specified languages. [DONE] ]==] local require_when_needed = require("Module:utilities/require when needed") local affix_module = "Module:affix" local debug_track_module = "Module:debug/track" local decorations_module = "Module:decorations" local en_utilities_module = "Module:en-utilities" local fun_is_callable_module = "Module:fun/isCallable" local headword_module = "Module:headword" local headword_data_module = "Module:headword/data" local languages_module = "Module:languages" local links_module = "Module:links" local parameters_module = "Module:parameters" local parse_interface_module = "Module:parse interface" 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 contains = require_when_needed(table_module, "contains") local deep_equals = require_when_needed(table_module, "deepEquals") local extend = require_when_needed(table_module, "extend") local insert_if_not = require_when_needed(table_module, "insertIfNot") local is_array = require_when_needed(table_module, "isArray") local list_to_set = require_when_needed(table_module, "listToSet") local serial_comma_join = require_when_needed(table_module, "serialCommaJoin") local shallow_copy = require_when_needed(table_module, "shallowCopy") local split = require_when_needed(string_utilities_module, "split") local ugsub = require_when_needed(string_utilities_module, "gsub") local umatch = require_when_needed(string_utilities_module, "match") local pattern_escape = require_when_needed(string_pattern_escape_module) local replacement_escape = require_when_needed(string_replacement_escape_module) local escape_wikicode = require_when_needed(parse_utilities_module, "escape_wikicode") local parse_inline_modifiers = require_when_needed(parse_utilities_module, "parse_inline_modifiers") local term_contains_top_level_html = require_when_needed(parse_utilities_module, "term_contains_top_level_html") local get_lang_by_code = require_when_needed(languages_module, "getByCode") local is_callable = require_when_needed(fun_is_callable_module) local format_decorations = require_when_needed(decorations_module, "format_decorations") local singularize = require_when_needed(en_utilities_module, "singularize") local canonicalize_pos = require_when_needed(headword_module, "canonicalize_pos") local pluralize_pos = require_when_needed(headword_module, "pluralize_pos") --[==[ intro: This module contains utilities for constructing language-specific headword modules. [fill in] ===About parts of speech=== There are several versions of the part of speech (POS) of a given term: * The ''primary categorizing POS'' is the basic part of speech to which a term belongs. The primary POS determines whether the term is a lemma or non-lemma form, and in its plural form, it is used to categorize the terms (unless it is a variant term), along with caetegorizing into a ''lemmas'' or ''non-lemma forms'' category. (Variant terms, typically marked by {{para|var|1}}, are categorized into ''variant lemmas'' or ''variant non-lemma forms'' and are not categorized into a primary POS category.) * The ''raw {{tl|head}} POS'' is parameter {{para|2}} to {{tl|head}}. This is used to determine the primary categorizing POS by a process of canonicalization, because this parameter may contain abbreviations (e.g. ''a'' or ''adj'' for an adjective) and may be (and usually is) given in its singular form. * The ''template-indicated POS'' is the part of speech indicated by a headword template such as {{tl|nl-noun}} or {{tl|mn-head}}. It is either inherent to the template (in a case like {{tl|nl-noun}}), in which case it is specified by the template code and should always be in canonical, plural form; or it is specified by the user in the template call (in a case like {{tl|mn-head}} or other generic POS template, usually ending in `-head`), in which case it may be given in raw form, like for the raw {{tl|head}} POS, and is canonicalized using the same code that canonicalizes raw {{tl|head}} parts of speech. The template-indicate POS may not be a valid primary categorizing POS. For example, Dutch has a template-indicated POS ''diminutiva tantum nouns'', specified through the template {{tl|nl-dim-tant}}, but there is no such primary categorizing POS (that POS would be `nouns`). Similarly, Arabic has a template-indicated POS ''collective nouns'', whose primary categorizing POS is also `nouns`. As a result, the template-indicated POS may need to be converted to a primary categorizing POS through a ''normalization'' process (which uses a `pos_category` field attached to the `pos_functions` structure for the template-indicated POS). Note that the template-indicated POS comes in several variants: *# It may be ''head-raw'', meaning that it uses the literal string `head` in place of the user-specified POS in a generic POS template like {{tl|mn-head}}; it may be ''non-head'', meaning that it uses the actual user-specified POS in such a case; or it may be ''head-augmented'', meaning that in the case of a generic POS template it is of the form e.g. `head.nouns`, contanining both the literal `head` and the user-specified POS. *# It may be ''suffix-raw'', meaning that it uses the primary categorizing POS `suffixes` in place of the template-indicated POS when the term is a suffix, or it may be ''non-suffix'', meaning that it uses the template-indicated POS in such a case. The headword for a suffix uses the template corresponding to whatever POS the suffix forms, e.g. {{nl-noun}} for Dutch noun-forming suffixes. *# The ''indexing POS'' is the variant of the template-indicated POS that is used to index into the `pos_functions` table. Normally it is head-raw and non-suffix, but it will be head-augmented if the corresponding head-augmented entry exists in `pos_functions`. *# The ''unnormalized categorizing POS'' is the variant of the template-indicated POS that is potentially usable as the primary categorizing POS (see above). It is non-head and suffix-raw, i.e. it uses the actual template-specified or user-specified form of the template-indicated POS except when the term is a suffix, in which case it has the value `suffixes`. In most circumstances this can be used directly as the primary categorizing POS, but in some cases this POS is not a valid primary categorizing POS and needs to be normalized to get the actual primary categorizing POS, as mentioned above under the description for the template-indicated POS. * Finally, there is a ''simplified categorizing POS''. This collapses variations of primary categorizing parts of speech together for use in red-link and similar categories. For example, the POS `proper nouns` is collapsed into `nouns`, and all types of participles are collapsed into `participles`. There are two variants of the simplification process, ''lemma-status-preserving'' and ''non-lemma-status-preserving''. The latter is the default and is simpler, in that e.g. it means any part of speech ending in `nouns` to `nouns`, even if this causes non-lemma forms to become lemmas, as with `diminutive nouns` and `verbal nouns` (which are non-lemma forms) and likewise with `comparative adjectives` and `superlative adjectives` (which are again non-lemma forms). The former, ''lemma-status-preserving'', keeps lemmas as lemmas and (more important) non-lemmas as non-lemmas by converting e.g. `diminutive nouns` and `verbal nouns` to `noun forms` rather than `nouns`. The reason two variants exist in that they have different use cases. The default, non-lemma-status-preserving, type is used for tracking categories such as red-link and request categories, where completeness is more important than clutter. The lemma-status-preserving variant is used for informative categories such as ` ``lang`` masculine nouns`, which should only have lemmas in them to avoid clutter. (For the same reason, variant terms specified using {{para|var|1}} aren't normally categorized into informative categories but are placed in tracking categories.) ===About properties and data objects=== [fill in] ]==] -- l10n: 標籤與分類名稱中文化;語言名不在分類開頭時,以 {L} 標示語言名的位置 local inflection_to_cats_and_label = { ["複數"] = { filter_pos = function(posvars) -- plurals also occur with determiners, adjectives etc. and we don't want to generate categories like -- 'countable determiners', 'countable adjectives', etc. Note that the passed-in `simp_plpos` has -- `proper nouns` converted to `nouns`. return posvars.simp_plpos == "名詞" end, cats = {"可數名詞"}, no_cats = {"不可數名詞"}, fixed_adj_label = "<<可數>>", fixed_adj_no_label = "<<不可數>>", sometimes_label = "<<可數>>和<<不可數>>", unattested_cats = "有無紀錄複數的{L}名詞", unknown_cats = "有未知或不確定複數的{L}名詞", }, ["比較級"] = { cats = {"有比較級{simp_plpos}"}, no_cats = {"無比較級{simp_plpos}"}, fixed_adj_label = "有<<比較級>>", -- l10n: 中文的否定無法只在 fixed_adj_label 前加一個字,另行指定 fixed_adj_no_label = "無<<比較級>>", }, ["陰性等價詞"] = { cats = {"有其他性別等價詞的{L}{simp_plpos}"}, }, ["陽性等價詞"] = { cats = {"有其他性別等價詞的{L}{simp_plpos}"}, }, } local param_mods = { alt = {}, -- always allow so e.g. - can be specified as a suffix form 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"}, lang = {type = "language"}, sc = {type = "script"}, t = {item_dest = "gloss"}, gloss = {}, pos = {}, ng = {}, lit = {}, tr = {}, ts = {}, face = {}, nolinkinfl = {type = "boolean"}, } local optional_headword_param_mods = { sc = {type = "script"}, tr = {}, ts = {}, } local function split_on_comma(val) if val:find(",") then return require(parse_interface_module).split_on_comma(val) else return {val} end end local function ine(val) if not val then return nil end val = mw.text.trim(val) return val ~= "" and val or nil end --[=[ Add decorations to a term. `termobj` is the object describing 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`; `text` is the text of the term itself, and `lang` is the language object. ]=] local function add_decorations(text, termobj, lang) local function field_non_empty(field) local list = termobj[field] if not list then return nil end if type(list) ~= "table" then error(("Internal error: Wrong type for `termobj.%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 text = format_decorations { lang = lang, text = text, q = termobj.q, qq = termobj.qq, l = termobj.l, ll = termobj.ll, refs = termobj.refs, } end return text end --[==[ 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. * `disable_special_modes`: If specified, don't recognize and treat specially the special mode signals (e.g. '-' for no inflection, '?' for unknown, etc.). * `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 not data.disable_special_modes and export.special_modes[term] then return { term = term, _is_mode = true } end 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 -- If a part of speech ends with one of these, either pluralized or followed by ' forms', chop off whatever precedes. -- Also, if a part of speech is of the form `BASIC_POS FOO forms` e.g. `noun construct forms`, remove the `FOO` -- portion. Note that "phrase" is *NOT* listed here because 'prepositional phrase' and 'postpositional phrase' are not -- types of 'phrase' per the definition of these parts of speech in Wiktionary. -- l10n: 詞類名稱中文化 local basic_poses = { "形容詞", "副詞", "縮約形", "專有名詞", -- may be converted to noun "名詞", "分詞", "代詞", "符號", "動詞", } -- l10n: 這些中文詞類名稱以基本詞類結尾,但對應的英語名稱並非如此(converbs、punctuation marks、diacritical -- marks、iteration marks),和原版一樣不簡化 local not_simplified_poses = { ["動副詞"] = true, ["標點符號"] = true, ["附加符號"] = true, ["疊字符號"] = true, } -- l10n: 中文的詞形類以「變格形」、「變位形式」或「形式」結尾(相當於英語的 ' forms');傳回去掉結尾後的部分, -- 不是詞形類則傳回 nil local function get_form_type(pos) return pos:match("^(.+)變格形$") or pos:match("^(.+)變位形式$") or pos:match("^(.+)形式$") end -- l10n: 相當於英語的 basic_pos .. " forms";詞形類統一作「變格形」,動詞另作「變位形式」 local function forms_of_basic_pos(basic_pos) return basic_pos == "動詞" and "動詞變位形式" or basic_pos .. "變格形" end --[==[ Canonicalize and then simplify a qualified part of speech (e.g. ''comparative adjectives'', ''past participles'', ''pronoun possessive forms'') to its unqualified equivalent (e.g. ''adjectives'', ''participles'', ''pronoun forms'''). This is used for categorization, since generally the simplified part of speech is used in categories other than the one for the part of speech itself. (For example, comparative adjectives are normally categorized as adjectives in categories such as `Requests for inflections in ``lang`` adjective entries`, and proper nouns are, sometimes at least, categorized under ` ``lang`` nouns with ...` rather than ` ``lang`` proper nouns with ...`.) There are two cases where simplification does not happen: phrases and (optionally) proper nouns. In Wiktionary, ''phrase'' really means a clause or similar constituent (or occasionally non-constituent), so `prepositional phrases` are not types of `phrases` and are not simplified. Proper nouns are simplified to nouns for some categories but left as-is in other category names; by default, this function simplifies `proper nouns` to `nouns`, but if the parameter `preserve_proper_nouns` is given, `proper nouns` is left as-is. If `preserve_lemma_status` is given, the function attempts to return a part of speech that has the same lemma status (lemma or non-lemma form) as the original part of speech; thus, `comparative adjectives` becomes `adjective forms` and `diminutive nouns` becomes `noun forms` because both are non-lemma forms. If `preserve_lemma_status` is not given, the function simply chops off the qualifiers, converting e.g. `comparative adjectives` to `adjectives` even though this changes the lemma status of the part of speech. The part of speech can be given singular or plural on input, but is always returned in plural form. ]==] function export.simplify_pos(pos, preserve_proper_nouns, preserve_lemma_status) -- This allows for both singular and plural parts of speech in 'pos'. local plpos = canonicalize_pos(pos) -- l10n: 以下依中文詞類名稱改寫。中文詞類名稱沒有空格與複數,限定成分可能在基本詞類之前(「過去分詞」) -- 或之後(「形容詞比較級」、「名詞複數形式」),因此先比對以基本詞類結尾者,再比對以基本詞類開頭者 if not_simplified_poses[plpos] then return plpos end local form_type = get_form_type(plpos) local function simplified_basic_pos(basic_pos) if preserve_lemma_status then local lemma_status = require(headword_module).pos_lemma_or_nonlemma(plpos) if lemma_status == "非詞元形式" then return forms_of_basic_pos(basic_pos) end end return basic_pos end for _, basic_pos in ipairs(basic_poses) do if basic_pos ~= "專有名詞" or preserve_proper_nouns then if form_type then if form_type == basic_pos then -- So 'proper noun forms' doesn't get converted to 'noun forms' in the next round return plpos end if form_type:sub(-#basic_pos) == basic_pos then return forms_of_basic_pos(basic_pos) end else if plpos == basic_pos then -- So 'proper nouns' doesn't get converted to 'nouns' in the next round return plpos end if plpos:sub(-#basic_pos) == basic_pos then return simplified_basic_pos(basic_pos) end end end end for _, basic_pos in ipairs(basic_poses) do if basic_pos ~= "專有名詞" or preserve_proper_nouns then if form_type then if form_type:sub(1, #basic_pos) == basic_pos then return forms_of_basic_pos(basic_pos) end elseif plpos == basic_pos .. "比較級" or plpos == basic_pos .. "最高級" then return simplified_basic_pos(basic_pos) end end end return plpos end function export.get_pos_variants(pos) local retval = {} retval.origpos = pos retval.plpos = canonicalize_pos(pos) retval.sgpos = singularize(retval.plpos) retval.simp_plpos = export.simplify_pos(retval.plpos) retval.simp_sgpos = singularize(retval.simp_plpos) retval.simp_with_propn_plpos = export.simplify_pos(retval.plpos, "preserve_proper_nouns") retval.simp_with_propn_sgpos = singularize(retval.simp_with_propn_plpos) return retval end --[==[ Canonicalize a single term or term-like object or a list of either into a list of term-like objects. `abterms` is the term or list to canonicalize, and `field` is the name of the field holding the term (defaulting to {"term"}). This does the minimal work necessary, meaning that the return value may partly or completely share memory with the value passed in. As a special case, if `abterms` is {nil}, {nil} is returned. If `origin` or `originating_term` are specified, add fields `_origin` and/or `_originating_term` containing the specified value(s) to each resulting term-like object (in this case, the object will be copied a necessary to avoid side-effecting the passed-in objects). `origin` is intended to be a string indicating where the term originated from (e.g. {"default"} if a default value, {"resolve_special"} if originating from a shortcut value such as {"+"}) and `originating_term` is the shortcut term itself, if any. (There is generally no such term for default values.) If `termobj_for_string_terms` is specified, any terms in `abterms` specified as strings will have all their properties other than `field` taken from `termobj_for_string_terms`. ]==] function export.canonicalize_termobj_list(abterms, field, origin, originating_term, termobj_for_string_terms) if abterms == nil then return nil end local function make_termobj_from_string_term(term) local retval if termobj_for_string_terms then retval = shallow_copy(termobj_for_string_terms) else retval = {} end retval[field] = term retval._origin = origin retval._originating_term = originating_term return retval end local function shallow_copy_and_add_origin(termobj) termobj = shallow_copy(termobj) termobj._origin = origin termobj._originating_term = originating_term return termobj end field = field or "term" if type(abterms) == "string" then return {make_termobj_from_string_term(abterms)} elseif not abterms[1] then if origin ~= nil or originating_term ~= nil then abterms = shallow_copy_and_add_origin(abterms) end return {abterms} else -- Check if already in full list term and return directly if so (unless `origin_val` is given, in which case we -- need to shallow-copy both the list and each term in it). local must_convert = false for _, term in ipairs(abterms) do if type(term) == "string" then must_convert = true break end end if not must_convert then if origin ~= nil or originating_term ~= nil then abterms = shallow_copy(abterms) for i, abterm in ipairs(abterms) do abterms[i] = shallow_copy_and_add_origin(abterm) end end return abterms end end local retval = {} for _, term in ipairs(abterms) do if type(term) == "string" then insert(retval, make_termobj_from_string_term(term)) else if origin ~= nil or originating_term ~= nil then term = shallow_copy_and_add_origin(term) end insert(retval, term) end end return retval end function export.canonicalize_category_list(abcats) -- FIXME: We should allow category objects in `headdata.categories` and not just strings. This seems to require -- changes to [[Module:headword]]. if abcats == nil then return nil end if abcats == false then return {} end if type(abcats) == "string" then abcats = {abcats} end return abcats end --[==[ Insert a fixed inflection (a label not associated with any inflection values) 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. * `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. * `originating_term`: The term object from which this label is derived. If specified, decorations will be taken from this object. ]==] 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 origterm = shallow_copy(origterm) -- Preserve decorations origterm.term = nil origterm.label = export.replace_glossary_links_in_label(label) insert(inflobj.inflections, origterm) end end local function yes_and_no_cats(props) if props.cats and props.no_cats then local yes_cats = export.canonicalize_category_list(props.cats) local no_cats = export.canonicalize_category_list(props.no_cats) local cats = shallow_copy(yes_cats) extend(cats, no_cats) return cats end end local function sometimes_or_yes_and_no_cats(props) local sometimes_cats = export.canonicalize_category_list(props.sometimes_cats) if sometimes_cats then return sometimes_cats else return yes_and_no_cats(props) end end -- l10n: 以下各標籤中文化 local function no_value_fallback_label(props) if props.fixed_adj_no_label then return props.fixed_adj_no_label end if props.fixed_adj_label then return "不" .. props.fixed_adj_label end return "無" .. (props.label_for_cats_and_modes or props.label) end local function with_value_fallback_label(props) if props.fixed_adj_label then return props.fixed_adj_label end -- FIXME, should this say 'with a plural/diminutive' etc. or is 'with plural/diminutive' enough? return "有" .. (props.label_for_cats_and_modes or props.label) end --[==[ var: Table describing how to handle the "mode" signals at the beginning of an inflection, generally indicating whether the inflection exists and to what extent. The key is the mode signal itself, and the value is table with the following keys: * `exists`: The value to return in the `exists` field of the object returned by `process_for_insertion`. This is currently used in calling code simply to check for different modes; it is an easier-to-read equivalent of the mode signal itself. * `prefix`: The prefix used to directly specify the label and category/categories associated with the mode in calling code. For example, the mode `--+` (which also arises when `-` is used with a following term) has prefix `usually_no_`, which means that to directly specify its label, use `usually_no_label` and to directly specify its categories, use `usually_no_cats`. If these fields don't exist, the functions in `fallback_label` and `fallback_cats` are used to generate the label and categories. * `fallback_label`: This is a function of one argument, `props` (the properties passed in by the caller), which should generate the label if the user didn't directly specify a label (see `prefix` above). * `fallback_cats`: This is a function of one argument, `props` (the properties passed in by the caller), which should generate the categories if the user didn't directly specify them (see `prefix` above). * `add_default`: If true, the code will attempt to add a default value if the user specifies this mode without giving additional terms. This depends on the caller passing in a `mode_default` property (which can be a string, a term object, a list of either, or a function generating any of those values). In many cases the `mode_default` property may simply have a value such as {"+"}, which is processed later by resolve_special(). * `disallow_additional`: If true, no additional terms are allowed after the mode signal; if present, an error is thrown. ]==] -- l10n: 以下各標籤中文化 export.special_modes = { ["-"] = {exists = "no", prefix = "no_", fallback_label = no_value_fallback_label, }, -- converted to "--+" if a term follows ["--+"] = {exists = "usually no", prefix = "usually_no_", add_default = true, fallback_label = function(props) if props.no_label then return "通常" .. props.no_label end return "通常" .. no_value_fallback_label(props) end, fallback_cats = sometimes_or_yes_and_no_cats, }, ["+-"] = {exists = "sometimes", prefix = "sometimes_", add_default = true, fallback_label = function(props) return "有時" .. with_value_fallback_label(props) end, fallback_cats = yes_and_no_cats, }, ["++-"] = {exists = "usually", prefix = "usually_", add_default = true, fallback_label = function(props) return "有時" .. with_value_fallback_label(props) end, fallback_cats = sometimes_or_yes_and_no_cats, }, ["!"] = {exists = "unattested", prefix = "unattested_", disallow_additional = true, fallback_label = function(props) return (props.label_for_cats_and_modes or props.label) .. "未證實" end, -- No default cats; provided for plurals in inflection_to_cats_and_label. }, ["?"] = {exists = "unknown", prefix = "unknown_", disallow_additional = true, -- No default label as people have preferred not to see 'plural unknown or uncertain' -- written out. No default cats; provided for plurals in inflection_to_cats_and_label. }, } function export.process_for_insertion(data) local terms, mode_default, headdata = data.terms, data.mode_default, data.headdata local retval = { headdata = data.headdata, } if terms and terms[1] then local mode = terms[1].term local modespec = export.special_modes[mode] if modespec then terms = shallow_copy(terms) retval.mode_term = terms[1] remove(terms, 1) if mode == "-" and terms[1] then mode = "--+" end if terms[1] and modespec.disallow_additional then error(("It doesn't make sense to specify explicit terms along with mode signal %s (%s)"):format( mode, modespec.exists)) elseif not terms[1] and modespec.add_default and mode_default then if type(mode_default) == "function" then mode_default = mode_default(headdata, retval.mode_term) end if mode_default then terms = export.canonicalize_termobj_list(mode_default, "term", "mode_default", retval.mode_term) end end retval.numterms = #terms retval.terms = terms retval.mode = mode retval.exists = modespec.exists retval.prefix = modespec.prefix else retval.numterms = #terms retval.terms = terms retval.exists = "yes" retval.prefix = "" end else retval.numterms = 0 retval.terms = {} -- retval.exists = nil retval.request = data.request end return retval 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. * `fixed_adj_label`: If specified, an adjective used to construct the defaults for the fixed labels specified by `no_label`, `usually_no_label` and `sometimes_label`. Examples are "comparable" for a comparative and "countable" for a plural. * `no_label`: If the term is {"-"} and there are no other terms, insert a fixed label with this value. If `fixed_adj_label` is given, defaults to {"not "} plus the adjective, else {"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 "} plus the value of `no_label`. * `sometimes_label`: If the term is {"+-"}, insert a fixed label with this value. If `fixed_adj_label` is given, defaults to {"sometimes "} plus the adjective, else {"sometimes with a(n) "} plus the label. * `cats`: List of categories to insert when terms are given that are not {"-"}. Each category is a string naming a full category to insert (including the appropriate language name prefixed). * `no_cats`: List of categories to insert when a term is given as {"-"}. * `usually_no_cats`: List of categories to insert when a term is given as {"-"} and additional terms are specified as well (representing, e.g. for the inflection {"plural"}, a term which usually has no plural but does under some circumstances). If omitted, both the categories in `cats` and `no_cats` are inserted. * `sometimes_cats`: List of categories to insert when a term is given as {"+-"} (indicating that the inflection sometimes exists and sometimes does not, normally depending on the precise sense involved). If omitted, both the categories in `cats` and `no_cats` are inserted (same default as for `usually_no_cats`). * `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"}. * `terms`: The terms that were inserted. If there are no terms, this is an empty list. * `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_processed_inflection(data) local headdata, processed, label = data.headdata, data.processed, data.label local inflobj = data.inflobj or headdata local accel = data.accel if data.accel_form then if accel then error("Internal error: can't specify both data.accel and data.accel_form") end if headdata.heads then local lemmas = {} local lemma_translits = {} for i, headobj in ipairs(headdata.heads) do lemmas[i] = headobj.term if lemmas[i] == "+" then error("Internal error: If you use data.accel_form, you should have resolved all occurrences of + in heads appropriately") end lemma_translits[i] = headobj.tr end accel = { lemma = lemmas, lemma_translit = lemma_translits, form = data.accel_form, } else accel = { form = data.accel_form, } end end local function insert_cats(cats) for _, cat in ipairs(cats) do insert(headdata.categories, cat) end end -- Handle the fixed label (if any) and cats (if any) associated with a mode. if processed.mode then local modespec = export.special_modes[processed.mode] local mode_prefix = modespec.prefix local fixed_label = data[mode_prefix .. "label"] if not fixed_label and modespec.fallback_label then fixed_label = modespec.fallback_label(data) end if fixed_label then export.insert_fixed_inflection { headdata = headdata, inflobj = inflobj, originating_term = processed.mode_term, label = fixed_label, } end local mode_cats = data[mode_prefix .. "cats"] if not mode_cats and modespec.fallback_cats then mode_cats = modespec.fallback_cats(data) end mode_cats = export.canonicalize_category_list(mode_cats) if mode_cats then insert_cats(mode_cats) end elseif processed.terms[1] and data.cats then -- If no mode, no fixed label but there may be categories. insert_cats(data.cats) end local terms = processed.terms if terms[1] then -- FIXME, if there's a mode we shallow_copy() twice. terms = shallow_copy(terms) terms.label = export.replace_glossary_links_in_label(label) if accel then terms.accel = accel end terms.enable_auto_translit = data.enable_auto_translit inflobj.inflections = inflobj.inflections or {} insert(inflobj.inflections, terms) elseif processed.request then inflobj.inflections = inflobj.inflections or {} insert(inflobj.inflections, { label = export.replace_glossary_links_in_label(label), request = true, }) 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. * `fixed_adj_label`: If specified, an adjective used to construct the defaults for the fixed labels specified by `no_label`, `usually_no_label` and `sometimes_label`. Examples are "comparable" for a comparative and "countable" for a plural. * `no_label`: If the term is {"-"} and there are no other terms, insert a fixed label with this value. If `fixed_adj_label` is given, defaults to {"not "} plus the adjective, else {"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 "} plus the value of `no_label`. * `sometimes_label`: If the term is {"+-"}, insert a fixed label with this value. If `fixed_adj_label` is given, defaults to {"sometimes "} plus the adjective, else {"sometimes with a(n) "} plus the label. * `cats`: List of categories to insert when terms are given that are not {"-"}. Each category is a string naming a full category to insert (including the appropriate language name prefixed). * `no_cats`: List of categories to insert when a term is given as {"-"}. * `usually_no_cats`: List of categories to insert when a term is given as {"-"} and additional terms are specified as well (representing, e.g. for the inflection {"plural"}, a term which usually has no plural but does under some circumstances). If omitted, both the categories in `cats` and `no_cats` are inserted. * `sometimes_cats`: List of categories to insert when a term is given as {"+-"} (indicating that the inflection sometimes exists and sometimes does not, normally depending on the precise sense involved). If omitted, both the categories in `cats` and `no_cats` are inserted (same default as for `usually_no_cats`). * `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"}. * `terms`: The terms that were inserted. If there are no terms, this is an empty list. * `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, mode_default = data.headdata, data.terms, data.mode_default local processed = export.process_for_insertion { headdata = headdata, terms = terms, mode_default = mode_default, } data = shallow_copy(data) data.processed = processed export.insert_processed_inflection(data) return processed 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 deep_equals(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_by_code(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 function export.format_validation_error_list(data) local valid_list, valid_set, canonicalize = data.valid_list, data.valid_set, data.canonicalize if not valid_list then valid_list = {} for valid_item, value in pairs(valid_set) do if canonicalize then if valid_item == value then insert(valid_list, valid_item) else insert(valid_list, ("%s (mapped to %s)"):format(valid_item, value)) end else insert(valid_list, valid_item) end end table.sort(valid_list) end return mw.text.listToText(valid_list, nil, " or ") end function export.validation_error(data) local item_type, item, valid_list, valid_set, canonicalize = data.item_type, data.item, data.valid_list, data.valid_set, data.canonicalize if not valid_list then valid_list = {} for valid_item, value in pairs(valid_set) do if canonicalize then if valid_item == value then insert(valid_list, valid_item) else insert(valid_list, ("%s (mapped to %s)"):format(valid_item, value)) end else insert(valid_list, valid_item) end end table.sort(valid_list) end error(("Invalid %s: %s; expected one of %s"):format(item_type, item, export.format_validation_error_list { valid_list = valid_list, valid_set = valid_set, canonicalize = canonicalize, } )) end --[=[ Validate the items in `items` against the list or set of valid items in `valid_items`. If `field` is given, fetch the item to check from that-named field of each object in `items`; otherwise use the items in `items` directly. If an error occurs, `item_type` specifies the type of item to mention in the error message, which will also list the allowed items (either taken directly from `valid_items` if a list, or from the sorted keys if a set). If `canonicalize`, `valid_items` must be a set mapping items to their canonical equivalents, and the items in `items` will be canonicalized accordingly and returned. ]=] local function validate_items(data, canonicalize) local items, field, valid_items, item_type = data.items, data.field, data.valid_items, data.item_type local valid_list, valid_set if not canonicalize and valid_items[1] and is_array(valid_items) then valid_list = valid_items valid_set = list_to_set(valid_items) else valid_set = valid_items end for _, item in ipairs(items) do if field then item = item[field] end if not item._is_mode and not valid_set[item] then export.validation_error { item_type = item_type, item = item, valid_list = valid_list, valid_set = not valid_list and valid_set or nil, canonicalize = canonicalize, } end end end function export.validate_genders(data) local genders, valid_genders, gender_type, no_augment, canonicalize = data.genders, data.valid_genders, data.gender_type, data.no_augment, data.canonicalize if not genders then return genders end gender_type = gender_type or "headword" local valid_gender_set if not data.canonicalize and valid_genders[1] and is_array(valid_genders) then valid_gender_set = list_to_set(valid_genders) else valid_gender_set = valid_genders end local augmented_gender_set if no_augment then augmented_gender_set = valid_gender_set else augmented_gender_set = {} for g, gval in pairs(valid_gender_set) do augmented_gender_set[g] = gval if g:find("^m") and not g:find("^mf") and valid_gender_set[g:gsub("^m", "f")] then augmented_gender_set[g:gsub("^m", "mf")] = type(gval) == "string" and (gval:gsub("^m", "mf")) or gval augmented_gender_set[g:gsub("^m", "mfbysense")] = type(gval) == "string" and (gval:gsub("^m", "mfbysense")) or gval augmented_gender_set[g:gsub("^m", "mfequiv")] = type(gval) == "string" and (gval:gsub("^m", "mfequiv")) or gval end end end return validate_items { items = genders, field = "spec", valid_items = augmented_gender_set, item_type = ("%s gender"):format(gender_type), canonicalize = canonicalize, } end function export.has_plural_gender(genders) local saw_p, saw_non_p for _, val in ipairs(genders) do local g = val.spec if g:find("p$") then saw_p = true elseif g ~= "?" then saw_non_p = true end end return saw_p, saw_non_p end function export.is_plurale_tantum(genders) local saw_p, saw_non_p = export.has_plural_gender(genders) return saw_p and not saw_non_p end local Headdata = {} function Headdata:resolve_prop(prop, ...) if type(prop) == "function" then prop = prop(self, ...) end return prop end function Headdata:get_pos_variants() if self.process_props.pos_variants == nil then self.process_props.pos_variants = export.get_pos_variants(self.pos_category) end return self.process_props.pos_variants end --[==[ Canonicalize a category. The category string will have the full language name (i.e. the name of the L2 language under which an entry is inserted, which may a parent language if the language in question is an etymology-only language) prepended to it, and any occurrences of `{plpos}` in the string replaced with the actual plural part of speech (with some canonicalization; specifically, `proper nouns` is converted to `nouns` when replacing `{plpos}`). To specify a full category and not have the language name prepended to it, precede it with {"Category:"}, which will be removed. ]==] function Headdata:canonicalize_category(category) -- l10n: 中文分類的語言名不一定在開頭;分類中有 {L} 時以語言名取代之,否則語言名直接接在開頭(不加空格) local has_langname = category:find("{L}", nil, true) if has_langname then category = category:gsub("{L}", replacement_escape(self.langfullname)) end if category:find("{") then category = require(string_utilities_module).format(category, self:get_pos_variants()) end if category:find("^Category:") then return (category:gsub("^Category:", "")) elseif has_langname then return category else return self.langfullname .. category end end --[==[ Canonicalize a list of categories according to the process described in `Headdata:canonicalize_category`. This simply loops over each category in `categories` and calls `Headdata:canonicalize_category` on each one. ]==] function Headdata:canonicalize_categories(categories) if not categories then return categories end categories = export.canonicalize_category_list(categories) local canon_cats = {} for _, cat in ipairs(categories) do insert(canon_cats, self:canonicalize_category(cat)) end return canon_cats end --[==[ Insert a category into the `categories` list in the headword `data` structure. `category` is normally a string naming the category, which will have the full language name prepended to it and any occurrences of `{plpos}` in the string replaced with the actual plural part of speech (with some canonicalization; specifically, `proper nouns` is converted to `nouns` when replacing `{plpos}`). To specify a full category and not have the language name prepended to it, precede it with {"Category:"}. ]==] function Headdata:insert_category(category) insert(self.categories, self:canonicalize_category(category)) end --[==[ Validate the genders in `genders` (a list of gender spec objects, as produced by {type = "genders"} in [[Module:parameters]] and accepted by [[Module:gender and number]]), checking that all specified genders are in the list or set given in `valid_genders`. Optional `props` controls how the validation happens. In particular, unless `props.no_augment` is given, then for any gender beginning with `m`, if a corresponding gender beginning with `f` occurs, analogous genders beginning with `mf`, `mfbysense` and `mfequiv` are also allowed. For example, if `m-d` (masculine dual) and `f-d` (feminine dual) both occur, genders `mf-d`, `mfbysense-d` and `mfequiv-d` are also allowed. If a disallowed gender is given, an error occurs, giving the disallowed gender along with the list of all allowed genders. ]==] function Headdata:validate_genders(genders, valid_genders, props) -- FIXME: This has no dependency on self. Uses should be converted to call export.validate_genders() directly. if not genders then return end props = props or {} local gender_type, no_augment = props.gender_type, props.no_augment export.validate_genders { genders = genders, valid_genders = valid_genders, gender_type = gender_type, no_augment = no_augment, } end --[==[ Parse an inflection specified in `field`, the name of a parameter holding an inflection. If the parameter is numeric, the field should be given as a number (as with the `params` structure passed to [[Module:parameters]]), not a string containing the representation of a number. The field can specify multiple comma-separated terms, and each term can have associated inline modifiers that will be parsed (unless there is top-level HTML in the parameter, i.e. HTML not contained inside an inline modifier, e.g. as may be generated by using {{tl|l}} or similar template inside a parameter). This is a wrapper around the top-level `parse_term_with_modifiers()` function. `props` is an optional structure containing additional properties, including all additional properties documented for the top-level `parse_term_with_modifiers()` function. If the parameter in `field` is unspecified, the return value of this function will be an empty list, not {nil}, so it is always safe to iterate over the return value. By default, the allowed modifiers are the same as for `parse_term_with_modifiers()`, except that (normally) the `<tr:...>` modifier will be allowed if `include_tr` was specified in the original call to `process_headword()`; likewise for the `<ts:...>` modifier if `include_ts` was specified and the `<sc:...>` modifier if `include_sc` was specified. If If you pass in your own `include_mods` list of additional allowed modifiers, it will (normally) automatically be augmented with {"tr"}, {"ts"} and/or {"sc"} if `include_tr`, `include_ts` and/or `include_sc` was specified when calling `process_headword()`. To disable automatic augmentation of these modifiers (whether or not you specify an `include_mods` property), specify {no_augment_include_mods = true} in `props`. ]==] function Headdata:parse_inflection(field, props) local val = self.process_props.args[field] if not val then return {} end props = props and shallow_copy(props) or {} local include_mods = props.include_mods local calldata = self.process_props.calldata if not props.no_augment_include_mods and (calldata.include_tr or calldata.include_ts or calldata.include_sc) then include_mods = include_mods and shallow_copy(include_mods) or {} if calldata.include_tr then insert_if_not(include_mods, "tr") end if calldata.include_ts then insert_if_not(include_mods, "ts") end if calldata.include_sc then insert_if_not(include_mods, "sc") end end props.val = val props.paramname = field props.splitchar = props.splitchar or "," props.include_mods = include_mods local orig_frob = props.frob local function convert_tilde_to_pagename(term) if orig_frob then term = orig_frob(term) end if not term:find("~", nil, true) then return term end term = term:gsub("\\~", "\1"):gsub("~", replacement_escape(self.pagename)):gsub("\1", "~") return term end props.frob = convert_tilde_to_pagename return export.parse_term_with_modifiers(props) or {} end function Headdata:process_for_insertion(terms, props) props = props and shallow_copy(props) or {} props.headdata = self props.terms = terms return export.process_for_insertion(props) end --[==[ Insert previously-parsed terms into the `inflections` of the headword `data` structure. This is a wrapper around the top-level `insert_inflection()` function. `terms` is the list of parsed terms. (If {nil}, nothing happens unless `request` is set in `props`.) `label` is the 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.) `props` is an optional structure containing additional properties, including all additional properties documented for the top-level `insert_inflection()` function. Unless `no_auto_cats` is given in `props`, certain labels automatically trigger the insertion of additional categories in specific circumstances. This is controlled by the `inflection_to_cats_and_label` structure in [[Module:headword utilities]]. For example, if the part of speech is {"nouns"} or {"proper nouns"} and the label (after removing any links and `<<...>>` glossary specs) is {"plural"}, an additional category <code><var>lang</var> countable nouns</code> will be added if a plural value is given (i.e. the value is not {"-"}). If the value is {"-"} (which indicates that there is no plural and triggers the insertion of the fixed inflection label {"no plural"}), <code><var>lang</var> uncountable nouns</code> will be inserted instead, and if both {"-"} and a value are given (which triggers the insertion of the {"usually no plural"} fixed inflection label), both categories are added. Similar categories are inserted when a comparative is given (with a label {"comparative"}), and if the label is {"female equivalent"} or {"male equivalent"} and the value is not {"-"}, a category such as <code><var>lang</var> nouns with other-gender equivalents</code> is inserted. ]==] function Headdata:insert_processed_inflection(processed, label, props) props = props and shallow_copy(props) or {} if not props.no_auto_cats then local bare_label = props.label_for_cats_and_modes or label if bare_label:find("[[", nil, true) then bare_label = require(links_module).remove_links(bare_label) end if bare_label:find("<<", nil, true) then bare_label = bare_label:gsub("<<.-|(.-)>>", "%1"):gsub("<<(.-)>>", "%1") end local spec = inflection_to_cats_and_label[bare_label] if spec and (not spec.filter_pos or spec.filter_pos(self:get_pos_variants())) then -- Copy the properties other than filter_pos into `props`, but don't overwrite any existing values. for specprop, specval in pairs(spec) do if specprop ~= "filter_pos" and props[specprop] == nil then props[specprop] = specval end end end end -- Now resolve and canonicalize the properties. local canon_props = {} for prop, val in pairs(props) do if prop ~= "no_auto_cats" and (prop == "cats" or prop:find("_cats$")) then val = self:canonicalize_categories(self:resolve_prop(val)) elseif prop:find("_label$") then val = self:resolve_prop(val) end canon_props[prop] = val end canon_props.headdata = self canon_props.processed = processed canon_props.label = label export.insert_processed_inflection(canon_props) return processed end --[==[ Insert previously-parsed terms into the `inflections` of the headword `data` structure. This is a wrapper around the top-level `insert_inflection()` function. `terms` is the list of parsed terms. (If {nil}, nothing happens unless `request` is set in `props`.) `label` is the 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.) `props` is an optional structure containing additional properties, including all additional properties documented for the top-level `insert_inflection()` function. Unless `no_auto_cats` is given in `props`, certain labels automatically trigger the insertion of additional categories in specific circumstances. This is controlled by the `inflection_to_cats_and_label` structure in [[Module:headword utilities]]. For example, if the part of speech is {"nouns"} or {"proper nouns"} and the label (after removing any links and `<<...>>` glossary specs) is {"plural"}, an additional category <code><var>lang</var> countable nouns</code> will be added if a plural value is given (i.e. the value is not {"-"}). If the value is {"-"} (which indicates that there is no plural and triggers the insertion of the fixed inflection label {"no plural"}), <code><var>lang</var> uncountable nouns</code> will be inserted instead, and if both {"-"} and a value are given (which triggers the insertion of the {"usually no plural"} fixed inflection label), both categories are added. Similar categories are inserted when a comparative is given (with a label {"comparative"}), and if the label is {"female equivalent"} or {"male equivalent"} and the value is not {"-"}, a category such as <code><var>lang</var> nouns with other-gender equivalents</code> is inserted. ]==] function Headdata:insert_inflection(terms, label, props) local processed = self:process_for_insertion(terms, props) self:insert_processed_inflection(processed, label, props) return processed end --[==[ Insert a "fixed" inflection (a label without associated values) into the `inflections` table of the headword `data` structure, labeled according to `label` (which can have glossary links in it specified using `<<...>>`, exactly as for `:insert_inflection()`). An example label (from {{tl|mn-noun}} in [[Module:mn-headword]]) is {"hidden-g declension"}, specifying that the noun belongs to the hidden-''g'' declension. This is a direct wrapper around the top-level function `insert_fixed_inflection()`; see that function for more details on optional `props`. ]==] function Headdata:insert_fixed_inflection(label, props) props = props and shallow_copy(props) or {} props.headdata = self props.label = label export.insert_fixed_inflection(props) end --[==[ Parse the inflection(s) specified in `field` and insert them into the `inflections` table of the headword `data` structure, labeled according to `label`. This is equivalent to calling {terms = data:parse_inflection(field, props)} followed by {return data:insert_inflection(terms, label, props)} and behaves the same as the combination of those two functions. See their documentation for more details. ]==] function Headdata:parse_and_insert_inflection(field, label, props) local terms = self:parse_inflection(field, props) return self:insert_inflection(terms, label, props) end --[==[ Generate an inflection that may be specified explicitly or defaulted (which involves looping over the specified or defaulted heads and determining the script of each one, since the formation of the default depends on the script). `data` is the data object passed into the POS handler. `terms` is the list of terms to process. Those where the term itself is not `+` will be returned unchanged, while those where the term is `+` will be handled by generating the appropriate inflections from the headwords using `make_inflection` (which is passed three arguments, `head`, `tr` and `sccode`, i.e. the script code of `head`) and should return two values, term and translit, either of which can be nil. A nil head will be ignored, and otherwise the decorations specified on the `+` term will be combined with the decorations specified on the head. The return value is a list of inflections where no requests for the default inflection remain. ]==] function Headdata:resolve_special(terms, handle_special, props) props = props or {} local infls = {} local is_special = props.is_special or function(_data, infl) return infl.term == "+" end for _, termobj in ipairs(terms) do local is_special_retval if type(is_special) == "function" then is_special_retval = is_special(self, termobj) elseif type(is_special) == "table" then is_special_retval = contains(is_special, termobj.term) else error(("Internal error: When checking whether term is special, is_special is neither a function nor a " .. "list, but is %s; term is %s"):format(dump(is_special), dump(termobj))) end if not is_special_retval then insert(infls, termobj) else for _, headobj in ipairs(self.heads) do local head = headobj.term or self.pagename local head_no_links if props.with_links then head = head:find("%[") and head or require(headword_module).add_multiword_links(head, not headobj.term) head_no_links = require(links_module).remove_links(head) else head = require(links_module).remove_links(head) head_no_links = head end local newterms = handle_special { headdata = self, head = head, head_no_links = head_no_links, tr = headobj.tr, infl = termobj, sc = self.lang:findBestScript(head_no_links), } if newterms then newterms = export.canonicalize_termobj_list(newterms, "term", "resolve_special", termobj) for _, newterm in ipairs(newterms) do if not props.no_combine_handle_special_retval_with_origin then export.combine_termobj_decorations(newterm, termobj) end if not props.no_combine_handle_special_retval_with_head then export.combine_termobj_decorations(newterm, headobj) end insert(infls, newterm) end end end end end return infls end --[==[ Add the current page to a tracking page named `Wiktionary:Tracking/``lang``-headword/``page```, where ``lang`` is the language code of the current language. For example, if the current language is `mak` and `page` is {"redundant-lon"}, the current page will get added to the tracking page `Wiktionary:Tracking/mak-headword/redundant-lon`. All pages added to that tracking page can be seen by going to [[Special:WhatLinksHere/Wiktionary:Tracking/mak-headword/redundant-lon]]. This is typically used to track issues occurring in user-specified parameters that do not rise to the level of errors (e.g. redundant parameters, deprecated usages or other dispreferred values). ]==] function Headdata:track(page) return require(debug_track_module)(self.langcode .. "-headword/" .. page) end local boolean_param = {type = "boolean"} --[==[ Process an arbitrary headword in an arbitrary language, handling generic and language-specific arguments and calling `full_headword()` in [[Module:headword]]. This is intended for use in implementing headword modules (e.g. [[Module:uz-headword]] for Uzbek, [[Module:mn-headword]] for Uzbek, [[Module:gsw-headword]] for Alemannic German, etc.) and provides a general implementation of such modules. On input, `data` is an object with the following fields: * `lang`: The language object of the language being handled. '''Required.''' Use the special value {false} to indicate that the language is specified by the user in {{para|1}}. * `frame`: The frame object passed into the `show()` function of your module, which implements headword-handling for all parts of speech in the module, including a generic POS-handling template (e.g. {{tl|uz-head}} or {{tl|mn-head}}), which allows arbitrary parts of speech to be handled. '''Required.''' * `pos_functions`: A table listing, for each part of speech requiring special handling, the extra parameters (if any) that the part of speech accepts, along with how to handle them. See the examples below. '''Required.''' * `validate_lang`: If {lang = true} is specified, this is a function of one argument (a language object, based on the language specified in {{para|1}}) that should throw an error if the language object is disallowed. If omitted, all languages are allowed. * `numbered_head`: If true, explicit headwords are specified in a numbered param instead of in {{para|head}}. The param used is usually {{para|1}}, but is {{para|2}} for generic POS templates such as {{tl|mn-head}} or for POS-specific templates when {lang = true} is specified (e.g. {{tl|arb-noun}}), and is {{para|3}} for generic POS templates when {lang = true} is spcified (e.g. {{tl|arb-head}}). * `include_tr`: If true, allow explicit transliterations to be specified. The transliteration(s) for the headword(s) themselves is/are specified in {{para|tr}} or through the {{cd|<tr:...>}} inline modifier on headwords, and transliterations of inflections are specified through the {{cd|<tr:...>}} inline modifier. This should generally be given when a headword for the language may be in a script other than Latin. * `include_ts`: If true, allow explicit transcriptions to be specified. The transcription(s) for the headword(s) themselves is/are specified in {{para|ts}} or through the {{cd|<ts:...>}} inline modifier on headwords, and transcriptions of inflections are specified through the {{cd|<ts:...>}} inline modifier. This should generally only be given for certain languages where the spelling is radically different from the pronunciation (e.g. in cuneiform languages such as Hittite and Akkadian, and potentially in Tibetan), and represents a pronunciation-based rendering (usually not direct IPA). * `include_sc`: If true, allow an explicit script code to be specified. The overall script code for the headword(s) themselves can be specified using {{para|sc}}, and per-headword or per-inflection script codes are specified using the {{cd|<sc:...>}} inline modifier. This should generally be given when a language supports multiple scripts. * `infls`: An inflections structure specifying extra generic parameters that apply to all parts of speech and how to handle them. The format is the same as for the `infls` structure in `pos_functions`. * `augment_params`: A callback function to add extra generic parameters, or modify existing generic parameters in the `params` structure; but in general, extra generic parameters should be added through the `infls` structure instead. This callback should not be used to add part-of-speech-specific parameters; those are handled through the appropriate setting in `pos_functions`. It is passed two single arguments, the `headdata` object and the `params` table to be augmented. See below for extra fields stored in the `process_props` structure of the `headdata` object. This function is called after initializing the `params` table and processing the overall `infls` structure, but just before adding part-of-speech-specific parameters (from `pos_functions`) to `params`. Thus, it can override any generic parameters but may itself be overridden by a part-of-speech-specific parameter. * `augment_headdata`: A callback function to modify the `headdata` object passed to `full_headword()` in [[Module:headword]]. This should not be used to for part-of-speech-specific parameter handling; this is handled through the appropriate setting in `pos_functions`. It is passed a single argument, the `headdata` object, as for `augment_params`; but the `process_props` structure and other fields will be more filled out, as this callback is called later. This can be used, for example, to override the value of a generic setting in `headdata` (e.g. [[Module:uz-headword]] uses this to mark non-Latin terms as variant forms by setting `headdata.var`, being careful not to override a value already set by the user). This function is called after initializing the `headdata` table with all information taken from generic parameters and processing generic parameters specified in the overall `infls` structure, and just before processing the appropriate part-of-speech-specific `infls` structure in `pos_functions` (which in turn is followed by any handler function in `pos_functions`). Thus, it can override any value set during generic parameter processing but may itself be overridden by a part-of-speech-specific parameter or handler. * `force_cat`: If true, add the headword to the appropriate categories even on non-mainspace pages. This can be used for testing category handling in sample template calls on userspace test pages or template documentation pages. It should not be set in production code. * `enable_auto_translit`: If true, turn on automatic transliteration of inflections at a global level (i.e. applying to all inflections). This has no effect on headwords, which are automatically transliterated by default if in a non-Latin script and automatic transliteration is available for the language. You can also set this value for particular inflections in the `insert_inflection()` function. The `headdata` headword data structure has an extra field in it called `process_props` that is specific to the `process_headword()` function, containing various extra properies. As the operation of `process_headword()` proceeds, this object gets filled out with more fields. For example, once parameter parsing happens, the resulting values are available in the `args` field of `process_props`. The following fields are found in `process_props` (note that `pos_category`, the canonicalized plural part of speech of the headword being processed, is *not* present here; it's directly on `headdata`): * `namespace`: The name of the current namespace; an empty string for the mainspace. This references the namespace of the actual page and isn't affected by the {{para|pagename}} parameter. * `indexing_pos`: The canonicalized part of speech of the headword used to index into `pos_functions`. This is the same as `poscat` for specific part-of-speech templates such as {{tl|uz-noun}}, but has the value {"head"} for generic part-of-speech templates such as {{tl|uz-head}}. (Note that `poscat` is directly available on `headdata`.) * `generic_pos_template`: True if a generic POS templates like {{tl|uz-head}} or {{tl|mn-head}} was used. (This is signaled by omitting the invocation parameter {{para|1}} to `process_headword`.) * `lang_in_1`: True if the language code is to be fetched from {{para|1}}. * `pos_param`: The parameter holding the part of speech, if a generic POS tempalte like {{tl|mn-head}} is being processed (i.e. `generic_pos_template` is set). In such a case, it will have the value of {1} or {2}, depending on whether the language code is being fetched from {{para|1}} (see `lang_in_1`). Otherwise it will be {nil}. * `head_param`: The parameter holding the explicit headword. If `numbered_head` was specified (as for Mongolian headword templates), this has the value {1}, {2} or {3} depending on whether the language code is being fetched from {{para|1}} (see `lang_in_1`) and whether a generic POS template like {{tl|mn-head}} is being processed (see `generic_pos_template`). Otherwise, it has the value {"head"}. Also see the `lang` and `numbered_head` properties in the `data` structure sent to `process_headword()`. * `is_suffix`: True if the current term is a suffix. This is set when processing the `suffix`, `nosuffix` and `clitic` parameters; it is always {false} beforehand (i.e. during `augment_params` and processing of the general `infls` structure). * `insert_specs`: This is a table mapping parameter names to the return value of `Headdata:insert_inflection()`, filled out as parameter values are processed. This lets a given parameter processing function in `infls` gain access to the result of calling `insert_inflection()` on previous parameters (which indicates the number of items inserted as well as whether `-` was specified). The `pos_functions` table contains an entry for each part of speech needing special handling, where the key is the canonical plural part of speech (e.g. {"adverbs"} or {"proper nouns"}). The value associated with each key is a table normally containing a field `infls`, listing the extra part-of-speech-specific inflection and other parameters along with how to handle them. The specs in `infls` are used in three ways: # to augment the `params` object passed to the `process()` function in [[Module:parameters]], specifying how to parse the appropriate inflectional parameters; # to specify how to process any inflectional parameters given and insert them into the `headdata` object passed to `full_headword()` in [[Module:headword]]; # to generate appropriate documentation for the parameters and other changes made by the headword template (e.g. inserting categories). Alternatively, you can separately control the augmentation of the `params` object and the procesing of the resulting arguments. This is done by specifying two fields in place of `infls`, named `params` and `func`. `params` is a table containing extra parameters to add to the overall `params` object passed to the `process()` function in [[Module:parameters]]. `func` is a function of two arguments, normally called `data` (the headword data structure `headdata`) and `args` (the processed arguments table). However, this alternative method is not normally recommended because it leads to duplication between the `params and `func` fields and the documentation, which must be manually specified. A simple example, as used to handle pronouns for Turoyo, is { local valid_genders = {"m", "f", "m-p", "f-p", "p", "?"} pos_functions["pronouns"] = { infls = { {2, type = "genders", validate = valid_genders}, {"f", label = "feminine"}, {"pl", label = "plural"}, }, } } The equivalent using `params` and `func` is { local valid_genders = {"m", "f", "m-p", "f-p", "p", "?"} pos_functions["pronouns"] = { params = { [2] = {type = "genders"}, f = true, pl = true, }, func = function(data, args) data:validate_genders(args[2], valid_genders) data.genders = args[2] data:parse_and_insert_inflection("f", "feminine") data:parse_and_insert_inflection("pl", "plural") end } } Note how the version with separate `params and `func` is longer and splits information on the parameters between the two fields. The `params` structure sets extra user-specifiable parameters {{para|2}} for genders (since the headword is in {{para|1}}) as well a {{para|f}} and {{para|pl}}, and the `func` handler processes those parameters. Note how this is done by calling methods on the headword `data` structure. Each such parameter can have multiple comma-separated values, and each value can have inline modifiers attached to it to specify further properties of the value. The `infls` version ends up making the same method calls, but does it for you instead of you having to do it yourself. These methods are implemented through a metatable set on the headword `data` structure, which is removed before calling `full_headword()` in [[Module:headword]]. The methods access extra information related to headword processing (such as the `args` table) that is stored in the `process_props` field of the headword `data` strucuture. This field is also removed prior to calling `full_headword()`. The methods available on the headword `data` structure are as follows. Each one also has its own documentation. * {parse_inflection(field, props)}: Parse value(s) specified in `field` (a user-specified parameter in the `args` table) and return a list of term objects. Optional `props` specifies additional properties controlling the parsing. * {insert_inflection(terms, label, props)}: Insert the terms in `terms` (a list of term objects as returned by `parse_inflection()`) into the `inflections` list in the headword `data` structure, giving the inflection the label as specified in `label`. Optional `props` specifies additional properties controlling the parsing. * {parse_and_insert_inflection(field, label, props)}: A combination of `parse_inflection()` and `insert_inflection()`, if no further processing of the parsed values needs to be done before insertion. * {insert_fixed_inflection(label, props)}: Insert a "fixed" inflection (a label without associated values) into the `inflections` table. An example (from {{tl|mn-noun}} in [[Module:mn-headword]]) is {"hidden-g declension"}, specifying that the noun belongs to the hidden-''g'' declension. * {resolve_special(terms, handle_special, props)}: Resolve "special" indicators as specified by the user in an inflection parameter. A typical example is {"+"}, requesting a default value. `terms` is the list of parsed term objects and `handle_special` is a handler function to process special indicators and convert them to their actual values. * {validate_genders(genders, valid_genders, props)}: Validate that the user-specified genders in `genders` all belong to the list given in `valid_genders`, throwing an error if not. * {insert_category(category)}: Insert a category into the `categories` list in the headword `data` structure. `category` is normally a string naming the category, which will have the language prepended to it and any occurrences of `{plpos}` in the string replaced with the actual plural part of speech. ]==] function export.process_headword(data) local data_lang, frame, pos_functions, validate_lang, numbered_head, include_tr, include_ts, include_sc, force_cat, enable_auto_translit, checkredlinks, infls, augment_params, augment_headdata = data.lang, data.frame, data.pos_functions, data.validate_lang, data.numbered_head, data.include_tr, data.include_ts, data.include_sc, data.force_cat, data.enable_auto_translit, data.checkredlinks, data.infls, data.augment_params, data.augment_headdata local iparams = { [1] = true, def = true, } if data_lang == "invocation" or data_lang == "invocation-or-user" then iparams.lang = true end local iargs = require(parameters_module).process(frame.args, iparams) local parargs = frame:getParent().args local lang local function generate_data_lang_error() error("Internal error: `data.lang` must be either a language object, \"invocation\" for a " .. "language specified in lang= in the invocation arguments, \"user\" for a language specified in 1= in " .. "the user template call, or \"invocation-or-user\" for either; saw " .. dump(data.lang)) end if not data_lang then generate_data_lang_error() end local lang_in_1 if type(data_lang) == "string" then if data_lang == "invocation" then lang = iargs.lang if not lang then error("Internal error: lang= must be specified in the invocation arguments") end elseif data_lang == "invocation-or-user" or data_lang == "user" then if data_lang == "invocation-or-user" then lang = iargs.lang end if not lang then lang = ine(parargs[1]) if not lang then error("Language code (see [[WT:Language codes]]) must be specified in 1=") end lang_in_1 = true end else generate_data_lang_error() end lang = require(languages_module).getByCode(lang, lang_in_1 and 1 or "lang", true) elseif type(data_lang) ~= "table" or not data_lang.hasType or not data_lang:hasType("language") then generate_data_lang_error() else lang = data_lang end local langcode = lang:getCode() if validate_lang then if type(validate_lang) == "function" then validate_lang(langcode) else validate_items { items = {langcode}, valid_items = validate_lang, item_type = "language code" } end end local template_indicated_pos = iargs[1] local generic_pos_template = not template_indicated_pos local pos_param if generic_pos_template then pos_param = lang_in_1 and 2 or 1 template_indicated_pos = ine(parargs[pos_param]) or -- l10n: 本站頁面標題首字母大寫 mw.title.getCurrentTitle().fullText == ("Template:%s-head"):format(mw.getContentLanguage():ucfirst(langcode)) and "interjection" or error(("Part of speech must be specified in %s="):format(pos_param)) template_indicated_pos = canonicalize_pos(template_indicated_pos) end local head_param = numbered_head and (generic_pos_template and lang_in_1 and 3 or (generic_pos_template or lang_in_1) and 2 or 1) or "head" local indexing_pos if generic_pos_template then indexing_pos = "head." .. template_indicated_pos if not pos_functions[indexing_pos] then indexing_pos = "head" end else indexing_pos = template_indicated_pos end local namespace = mw.loadData(headword_data_module).page.namespace -- Partly initialize headdata now for use in generic infls callbacks. Will be further initialized later after -- processing parameters. local headdata = { lang = lang, langcode = langcode, langfullcode = lang:getFullCode(), langname = lang:getCanonicalName(), langfullname = lang:getFullName(), process_props = { namespace = namespace, calldata = data, -- preserve template-indicated POS in case we normalize it or change it to "suffix" template_indicated_pos = template_indicated_pos, indexing_pos = indexing_pos, generic_pos_template = generic_pos_template, lang_in_1 = lang_in_1, pos_param = pos_param, head_param = head_param, is_suffix = false, processed_specs = {}, insert_specs = {}, }, -- We may change this below to "suffix" or normalize it. pos_category = template_indicated_pos, categories = {}, inflections = {enable_auto_translit = enable_auto_translit}, force_cat_output = force_cat, no_redundant_head_cat = true, } setmetatable(headdata, {__index = Headdata}) local function resolve_prop(...) return headdata:resolve_prop(...) end local params = { [head_param] = {template_default = iargs.def}, head2 = {replaced_by = false, instead = ("use comma-separated |%s="):format(head_param)}, id = true, sort = true, cat = true, nolink = boolean_param, nolinkhead = {type = "boolean", alias_of = "nolink"}, suffix = boolean_param, nosuffix = boolean_param, -- If true, this is an inflectional suffix (forming a given person/number/gender/etc. part of the inflection of -- a given part of speech, potentially restricted to a given inflectional class). We always treat these as -- suffixes, rather than suffix forms, even if they form non-lemma forms (e.g. modern English -s and archaic -- -eth form the third-singular present indicative of a verb, i.e. they form a verb form, but they are not -- themselves forms of some other suffix, which is what a suffix form is). We also display the label of -- inflectional suffixes specially, and add an additional category 'LANG inflectional suffixes' along with -- categories like 'LANG POS inflectional suffixes' in place of 'LANG POS suffix forms' etc. inflsuf = boolean_param, clitic = {type = "boolean or string"}, addlpos = true, var = {type = "boolean or string", set = {"both"}}, abbr = true, json = boolean_param, pagename = true, -- for testing } if include_sc then params.sc = {type = "script"} end if include_tr then params.tr = true params.tr2 = {replaced_by = false, instead = "use comma-separated |tr= or <tr:...> inline modifier on head"} end if include_ts then params.ts = true params.ts2 = {replaced_by = false, instead = "use comma-separated |ts= or <ts:...> inline modifier on head"} end if lang_in_1 then params[1] = {required = true} -- required but ignored as already processed above end if generic_pos_template then params[pos_param] = {required = true} -- required but ignored as already processed above end local function augment_params_from_infls(infls) infls = resolve_prop(infls) for _, infl in ipairs(infls) do local function interr(txt) error(("Internal error: %s (coming from infls spec %s)"):format(txt, dump(infl))) end local param = infl[1] if param then param = resolve_prop(param) if type(param) ~= "string" and type(param) ~= "number" then interr(("Parameter name %s must be a string or number"):format(dump(param))) end -- We handle defaults as well as validation ourselves. local typ = resolve_prop(infl.type) or "string" if typ ~= "genders" and typ ~= "boolean" and typ ~= "string" then -- FIXME: Handle more types. interr(('Unrecognized type %s; can only currently handle "genders", "boolean" and "string" (the default)'):format( dump(typ))) end params[param] = {type = typ, required = resolve_prop(infl.required), template_default = resolve_prop(infl.template_default)} if typ ~= "boolean" then local allow_and_ignore_list_params = resolve_prop(infl.allow_and_ignore_list_params) local ignore_param if type(allow_and_ignore_list_params) == "string" then ignore_param = allow_and_ignore_list_params elseif type(param) == "string" then ignore_param = param end if ignore_param then local old_list_param = allow_and_ignore_list_params and true or {replaced_by = false, instead = ("use comma-separated |%s="):format(param)} params[ignore_param .. "2"] = old_list_param params[ignore_param .. "3"] = old_list_param params[ignore_param .. "4"] = old_list_param end end end end end if infls then augment_params_from_infls(infls) end if augment_params then augment_params(headdata, params) end if pos_functions[indexing_pos] then local pos_infls = pos_functions[indexing_pos].infls if pos_infls then augment_params_from_infls(pos_infls) end local pos_params = pos_functions[indexing_pos].params if pos_params then for key, val in pairs(pos_params) do params[key] = val end end end local args = require("Module:parameters").process(parargs, params) local pagename = args.pagename or mw.loadData(headword_data_module).pagename local sc = args.sc or lang:findBestScript(pagename) headdata.pagename = pagename headdata.process_props.args = args headdata.sc = sc headdata.id = args.id headdata.sort = args.sort -- No redundant script cat unless the user explicitly gave sc= headdata.no_script_code_cat = not args.sc headdata.var = args.var local extra_term_mods = {} if include_tr then insert(extra_term_mods, "tr") end if include_ts then insert(extra_term_mods, "ts") end if include_sc then insert(extra_term_mods, "sc") end if not extra_term_mods[1] then extra_term_mods = nil end local trs = args.tr and split_on_comma(args.tr) or {} local num_trs = #trs local tss = args.ts and split_on_comma(args.ts) or {} local num_tss = #tss local heads = args[head_param] and export.parse_term_with_modifiers { val = args[head_param], paramname = head_param, splitchar = ",", is_head = true, include_mods = extra_term_mods, } or {} local num_heads = #heads if num_heads > 0 and num_trs > 0 and num_heads ~= num_trs then error(("%s head%s specified explicitly but %s translit%s; they must match; use '+' to stand for the default head (the pagename) or default automatic translit and '-' to stand for no translit"):format( num_heads, num_heads > 1 and "s" or "", num_trs, num_trs > 1 and "s" or "")) end if num_heads > 0 and num_tss > 0 and num_heads ~= num_tss then error(("%s head%s specified explicitly but %s transcription%s; they must match; use '+' to stand for the default head (the pagename) and '-' to stand for no transcription"):format( num_heads, num_heads > 1 and "s" or "", num_tss, num_tss > 1 and "s" or "")) end if num_trs > 0 and num_tss > 0 and num_trs ~= num_tss then error(("%s translit%s specified explicitly but %s transcription%s; they must match; use '+' to stand for default automatic translit and '-' to stand for no translit or transcription"):format( num_trs, num_trs > 1 and "s" or "", num_tss, num_tss > 1 and "s" or "")) end -- Be careful here not to overwrite user_specified_heads if it's empty so we can later check user_specified_heads -- to see if the user provided any heads. local max_tr_ts = math.max(num_trs, num_tss) if num_heads == 0 and max_tr_ts > 0 then heads = {} for i = 1, max_tr_ts do heads[i] = {term = "+"} end end if not heads[1] then heads = {{term = "+"}} end for i, headobj in ipairs(heads) do if headobj.tr and trs[i] then if headobj.tr ~= trs[i] then error(("Saw two different translits '%s' and '%s' for head #%s"):format( headobj.tr, trs[i], i)) end else headobj.tr = headobj.tr or trs[i] end if headobj.tr == "+" then headobj.tr = nil end if headobj.ts and tss[i] then if headobj.ts ~= tss[i] then error(("Saw two different transcriptions '%s' and '%s' for head #%s"):format( headobj.ts, tss[i], i)) end else headobj.ts = headobj.ts or tss[i] end if headobj.ts == "-" then headobj.ts = nil end if headobj.term == "+" then headobj.term = args.nolink and pagename or nil if headobj.term and namespace == "Reconstruction" then headobj.term = "*" .. headobj.term end end end headdata.heads = heads local pos_category = headdata.pos_category -- Do this as late as possible in case the caller wants to conditionalize the primary categorizing POS on -- arguments or even the presence or absence of explicit heads. if pos_functions[indexing_pos] then pos_category = resolve_prop(pos_functions[indexing_pos].pos_category) or pos_category end headdata.process_props.normalized_template_indicated_pos = pos_category headdata.pos_category = pos_category headdata.process_props.pos_variants = nil -- erase the cache local function pagename_is_suffix() if sc:getCode() == "Latn" then -- shortcut Latin terms to avoid unnecessarily loading [[Module:affix]] return pagename:find("^%-") and not pagename:find("%-$") else local affix_type, _, _, _ = require(affix_module).parse_term_for_affixes(pagename, lang, sc) return affix_type == "suffix" end end local clitic_label = args.clitic if clitic_label == true then -- l10n: 標籤中文化 clitic_label = "附著語素" end if clitic_label then -- l10n: 分類名稱中文化 headdata:insert_category("附著語素") headdata:insert_fixed_inflection(clitic_label) elseif args.suffix or ( -- l10n: 詞類名稱中文化 not args.nosuffix and pagename_is_suffix() and pos_category ~= "後綴" and pos_category ~= "後綴變格形" ) then headdata.process_props.is_suffix = true local pos_list_for_inflectional_suffix = {} local pos_list_for_suffix_form = {} local pos_list_for_forming_suffix = {} local function handle_suffix_pos(pos, is_first) -- Use the actual POS for the label we insert, but simplify the POS for the category. We need to separately check -- each of them to see if they end in 'forms' because e.g. 'comparative adjectives' gets simplified to -- 'adjective forms'. -- l10n: 詞類名稱、分類與標籤中文化;中文詞類名稱不分單複數 local form_type = get_form_type(pos) if form_type then if args.inflsuf then insert(pos_list_for_inflectional_suffix, form_type) else insert(pos_list_for_suffix_form, form_type) end else local singular_pos = pos if args.inflsuf then insert(pos_list_for_inflectional_suffix, singular_pos) else insert(pos_list_for_forming_suffix, singular_pos) end end if args.inflsuf then headdata:insert_category("屈折後綴") end local simplified_pos = export.simplify_pos(pos, "preserve_proper_nouns", "preserve_lemma_status") local simplified_form_type = get_form_type(simplified_pos) if simplified_form_type then if args.inflsuf then headdata:insert_category(("%s屈折後綴"):format(simplified_form_type)) else headdata:insert_category(("%s後綴變格形"):format(simplified_form_type)) end else local singular_simplified_pos = simplified_pos if args.inflsuf then headdata:insert_category(("%s屈折後綴"):format(singular_simplified_pos)) else headdata:insert_category(("構成%s的{L}後綴"):format(singular_simplified_pos)) end end local postype if args.inflsuf then postype = "詞元" end if not postype then postype = require(headword_module).pos_lemma_or_nonlemma(pos) end if not postype then postype = require(headword_module).pos_lemma_or_nonlemma(simplified_pos) end if not postype then error(("Unrecognized canonicalized part of speech '%s' in addlpos=, cannot determine whether lemma or non-lemma form"):format( pos )) end -- l10n: 詞類名稱中文化 local this_pos_category = postype == "詞元" and "後綴" or "後綴變格形" if is_first then headdata.pos_category = this_pos_category headdata.process_props.pos_variants = nil -- erase the cache elseif headdata.pos_category ~= this_pos_category then local singular_normalized_this_pos_category = require(en_utilities_module).singularize(this_pos_category) local singular_normalized_overall_pos_category = require(en_utilities_module).singularize(headdata.pos_category) error(("Cannot mix suffixes and suffix forms using addlpos=; '%s' is of a %s while overall POS " .. "'%s' is a %s; use separate POS headers for the two"):format(pos, singular_normalized_this_pos_category, pos_category, singular_normalized_overall_pos_category)) end end handle_suffix_pos(pos_category, true) if args.addlpos then for _, addlpos in ipairs(split(args.addlpos, "%s*,%s*")) do addlpos = canonicalize_pos(addlpos) handle_suffix_pos(addlpos, false) end end -- l10n: 標籤與連接詞中文化 if pos_list_for_inflectional_suffix[1] then headdata:insert_fixed_inflection(serial_comma_join(pos_list_for_inflectional_suffix, {conj = "和"}) .. "屈折後綴") end if pos_list_for_suffix_form[1] then headdata:insert_fixed_inflection(serial_comma_join(pos_list_for_suffix_form, {conj = "和"}) .. "後綴變格形") end if pos_list_for_forming_suffix[1] then headdata:insert_fixed_inflection("構成" .. serial_comma_join(pos_list_for_forming_suffix, {conj = "和"}) .. "的後綴") end end if args.cat then for _, cat in ipairs(split_on_comma(args.cat)) do headdata:insert_category(cat) end end local function augment_headdata_from_infls(infls) infls = resolve_prop(infls) for _, infl in ipairs(infls) do local function interr(txt) error(("Internal error: %s (coming from infls spec %s)"):format(txt, dump(infl))) end local function process_labelobjs(labelobjs, originating_term, handle_labelobj) if labelobjs == nil then return end if type(labelobjs) ~= "string" and type(labelobjs) ~= "table" then interr(("Wrong type '%s' for label object(s) %s, expected string or table"):format( type(labelobjs), dump(labelobjs) )) end if type(labelobjs) == "string" or type(labelobjs) == "table" and not labelobjs[1] then labelobjs = {labelobjs} end for _, labelobj in ipairs(labelobjs) do local label, termobj if type(labelobj) == "string" then label = labelobj termobj = originating_term elseif type(labelobj) ~= "table" then interr(("Wrong type '%s' for label object %s, expected string or table"):format( type(labelobj), dump(labelobj) )) label = labelobj.term if type(label) ~= "string" then interr(("Wrong type '%s' for label %s from label object %s, expected string"):format( type(label), dump(label), dump(labelobj) )) end termobj = labelobj end handle_labelobj(label, termobj) end end local param = infl[1] if param then local vals -- Fetch the param and make sure it's a string or number. param = resolve_prop(param) if type(param) ~= "string" and type(param) ~= "number" then interr(("Parameter name %s must be a string or number"):format(dump(param))) end -- Fetch the type and validate. local typ = resolve_prop(infl.type) if typ == nil then typ = "string" end if typ ~= "genders" and typ ~= "boolean" and typ ~= "string" then -- FIXME: Handle more types. interr(('Unrecognized type %s; can only currently handle "genders", "boolean" and "string" (the default)'):format( dump(typ))) end -- Fetch the value(s). if typ == "genders" or typ == "boolean" then vals = args[param] elseif typ == "string" then local parse_inflection_props = resolve_prop(infl.parse_inflection_props) local include_mods = resolve_prop(infl.include_mods) local no_augment_include_mods = resolve_prop(infl.no_augment_include_mods) if include_mods ~= nil or no_augment_include_mods ~= nil then if parse_inflection_props == nil then parse_inflection_props = {} else parse_inflection_props = shallow_copy(parse_inflection_props) end if include_mods ~= nil then parse_inflection_props.include_mods = include_mods end if no_augment_include_mods ~= nil then parse_inflection_props.no_augment_include_mods = no_augment_include_mods end end vals = headdata:parse_inflection(param, parse_inflection_props) -- Convert an empty list to nil for consistent checking below. if not vals[1] then vals = nil end else interr(("Unrecognized type '%s"):format(typ)) end -- If value(s) nil, fetch the default. if vals == nil and infl.default ~= nil then local default = resolve_prop(infl.default) if typ == "genders" then vals = export.canonicalize_termobj_list(default, "spec", "default") elseif typ == "boolean" then vals = default elseif typ == "string" then vals = export.canonicalize_termobj_list(default, "term", "default") else interr(("Unrecognized type '%s"):format(typ)) end end -- Resolve "special" values (special signals a string values, such as requesting the default with "+"). if vals ~= nil and infl.resolve_special then if typ ~= "string" then interr(("Cannot specify resolve_special= for type %s"):format(dump(typ))) end local resolve_special_props = resolve_prop(infl.resolve_special_props, vals) if infl.is_special ~= nil then if resolve_special_props == nil then resolve_special_props = {} else resolve_special_props = shallow_copy(resolve_special_props) end resolve_special_props.is_special = infl.is_special end vals = headdata:resolve_special(vals, infl.resolve_special, resolve_special_props) end -- Canonicalize the value(s). if vals ~= nil and infl.canonicalize ~= nil then if typ == "boolean" then interr('Cannot specify canonicalize= when type is "boolean"') end local newvals = {} local field = typ == "genders" and "spec" or "term" for _, val in ipairs(vals) do local newval if val._is_mode then newval = val else if type(infl.canonicalize) == "function" then newval = infl.canonicalize(val[field]) else newval = val[field] newval = infl.canonicalize[newval] or newval end end if val[field] ~= newval then val = shallow_copy(val) val[field] = newval end insert(newvals, val) end vals = newvals end -- Validate the value(s). if vals ~= nil and infl.validate ~= nil then if typ == "boolean" then interr('Cannot specify validate= when type is "boolean"') elseif type(infl.validate) == "function" then infl.validate(headdata, vals) elseif typ == "genders" then export.validate_genders { genders = vals, valid_genders = infl.validate, no_augment = resolve_prop(infl.no_augment_genders), } elseif typ == "string" then validate_items { items = vals, field = "term", valid_items = infl.validate, item_type = ("values in |%s="):format(param), } else interr(("Unrecognized type '%s"):format(typ)) end end local function run_and_check_return_value(fun, funname, ...) local retval, intentionally_nil = fun(...) if retval == nil and not intentionally_nil then interr(("If you return nil from %s, you must return a second non-nil return " .. "value to indicate that the nil return value was intentional"):format(funname)) end return retval end -- Run the process_after_parse handler, if it exists. if vals ~= nil and infl.process_after_parse ~= nil then vals = run_and_check_return_value(infl.process_after_parse, "process_after_parse", headdata, vals) end -- Process strings for insertion, to handle special mode signals like -, +-, ?, etc. local processed if typ == "string" then local process_for_insertion_props = resolve_prop(infl.process_for_insertion_props, vals) processed = headdata:process_for_insertion(vals, process_for_insertion_props) headdata.process_props.processed_specs[param] = processed if processed.numterms > 0 and infl.process_one_term ~= nil then local processed_terms = {} for _, termobj in ipairs(processed.terms) do local processed_term = run_and_check_return_value(infl.process_one_term, "process_one_term", headdata, termobj) if processed_term ~= nil then -- FIXME: Should we add an origin of "process_one_term" here? (But this would erase -- any "default" or "resolve_special" origins.) local newterms = export.canonicalize_termobj_list(processed_term, "term", nil, nil, termobj) for _, newterm in ipairs(newterms) do insert(processed_terms, newterm) end end end processed = shallow_copy(processed) processed.terms = processed_terms processed.numterms = #processed_terms end if processed.numterms > 0 and infl.process_terms ~= nil then local process_terms_result = run_and_check_return_value(infl.process_terms, "process_terms", processed) process_terms_result = process_terms_result or {} if process_terms_result[1] or not next(process_terms_result) then processed = shallow_copy(processed) processed.terms = process_terms_result processed.numterms = #process_terms_result elseif not process_terms_result.terms or not process_terms_result.numterms then error("Return value from process_terms() should be either a list of term objects or a " .. "`processed` structure containing such a list") else processed = process_terms_result end end vals = processed.terms end local inserted_fixed_label -- "Implement" the values. Fixed labels and cats only fire when non-falsy (i.e. we don't want to fire -- on boolean false or empty list). We call insert_processed_inflection() always because there may be -- a mode even when there are no values. if vals == true or type(vals) == "table" and vals[1] then if infl.fixed_label and infl.all_fixed_label then interr("Cannot specify both fixed_label= and all_fixed_label=; specify one or the other") end local function check_fixed_label_references_val(label) if type(label) == "table" and label[1] then for _, lab in ipairs(label) do if check_fixed_label_references_val(lab) then return true end end return false end if type(label) == "table" then if not label.term then interr(("Fixed label structure %s does not have a value for `.term`"):format(dump(label))) end label = label.term end if type(label) ~= "string" then interr(("Wrong type for fixed label %s, should be string"):format(type(label))) end return not not label:find("{val}") end local fixed_label = infl.fixed_label local all_fixed_label = infl.all_fixed_label -- If the value being processed is boolean, there's only one value so treat a fixed_label as an -- all_fixed_label and output only once; likewise if the caller specified a fixed_label without -- {val} in it. if fixed_label and (typ == "boolean" or type(fixed_label) ~= "function" and not check_fixed_label_references_val(fixed_label)) then all_fixed_label = fixed_label fixed_label = nil end if fixed_label then if typ == "boolean" then interr("Boolean fixed_label values should have been converted to all_fixed_label") end for _, valobj in ipairs(vals) do local labelobjs = resolve_prop(fixed_label, valobj) process_labelobjs(labelobjs, valobj, function(label, termobj) if label:find("{val}") then if typ ~= "string" then interr(('Cannot specify {val} in fixed_label %s when type is "%s"'):format(dump(label), typ)) end label = label:gsub("{val}", replacement_escape(valobj.term)) end headdata:insert_fixed_inflection(label, { originating_term = termobj }) inserted_fixed_label = true end) end elseif all_fixed_label then local labelobjs = resolve_prop(all_fixed_label, vals) process_labelobjs(labelobjs, nil, function(label, termobj) if label:find("{vals}") then if typ ~= "string" then interr(('Cannot specify {vals} in all_fixed_label %s when type is "%s"'):format(dump(label), typ)) end local formatted_labels = {} for _, valobj in ipairs(vals) do insert(formatted_labels, add_decorations(valobj.term, valobj, lang)) end -- l10n: 連接詞中文化 label = label:gsub("{vals}", replacement_escape(serial_comma_join(formatted_labels, {conj = "和"}))) end headdata:insert_fixed_inflection(label, termobj) inserted_fixed_label = true end) end end -- See above; if there's a label (which can only happen when typ == "string"), always call -- insert_processed_inflection. local inserted_vals if infl.label ~= nil then if typ ~= "string" then interr(("label=%s can only be specified for type 'string', not '%s'"):format( dump(infl.label), typ )) end local label = resolve_prop(infl.label, vals) if label ~= nil then local insert_processed_inflection_props = resolve_prop(infl.insert_processed_inflection_props, vals) local function copy_to_insert_processed_inflection_props(field, val) local fieldval = val or resolve_prop(infl[field], vals) if fieldval ~= nil then if insert_processed_inflection_props == nil then insert_processed_inflection_props = {} else insert_processed_inflection_props = shallow_copy(insert_processed_inflection_props) end insert_processed_inflection_props[field] = fieldval end end copy_to_insert_processed_inflection_props("label_for_cats_and_modes") copy_to_insert_processed_inflection_props("no_auto_cats") copy_to_insert_processed_inflection_props("accel") copy_to_insert_processed_inflection_props("accel_form") copy_to_insert_processed_inflection_props("request") if not processed then interr("`processed` structure wasn't properly created") end headdata:insert_processed_inflection(processed, label, insert_processed_inflection_props) inserted_vals = true end end -- See above; only fire for cats (and gender insertion) on non-falsy values. if vals == true or type(vals) == "table" and vals[1] then if typ == "genders" then headdata.genders = vals end local inserted_cat if infl.cat then if typ == "boolean" then vals = {vals} end local allcats = {} for _, valobj in ipairs(vals) do local cats = resolve_prop(infl.cat, valobj) if type(cats) == "string" then cats = {cats} end if cats ~= nil then for _, cat in ipairs(cats) do if cat:find("{val}") then cat = cat:gsub("{val}", replacement_escape(valobj.term)) end insert_if_not(allcats, cat) end end end for _, cat in ipairs(allcats) do headdata:insert_category(cat) inserted_cat = true end end if typ == "boolean" then if not inserted_fixed_label and not inserted_cat then interr(("User set boolean setting for %s= but no fixed label added and no category " .. "inserted; if you took action in process_after_parse(), make sure to return " .. "`nil, true`"):format(param)) end elseif typ == "string" then if not inserted_vals and not inserted_fixed_label and not inserted_cat then interr(("User set value(s) %s for %s= but no inflection inserted and no fixed label " .. "or category added"):format(dump(vals), param)) end end if processed then -- FIXME, convert to use `processed_specs` field and remove this headdata.process_props.insert_specs[param] = processed end end else -- no param specified if infl.label or infl.all_fixed_label then interr("Cannot have label= or all_fixed_label= without specifying a param") end if infl.process then infl.process(headdata) end if infl.fixed_label then local labelobjs = resolve_prop(infl.fixed_label) process_labelobjs(labelobjs, nil, function(label, termobj) if label:find("{val}") then interr("Cannot specify {val} in a fixed_label= value without specifying a param") end headdata:insert_fixed_inflection(label, { originating_term = termobj }) end) end if infl.cat then local cats = resolve_prop(infl.cat) if type(cats) == "string" then cats = {cats} end if cats ~= nil then for _, cat in ipairs(cats) do if cat:find("{val}") then interr("Cannot specify {val} in a cat= value without specifying a param") end headdata:insert_category(cat) end end end end end end if infls then augment_headdata_from_infls(infls) end if augment_headdata then augment_headdata(headdata, args) end if pos_functions[indexing_pos] then local pos_infls = pos_functions[indexing_pos].infls if pos_infls then augment_headdata_from_infls(pos_infls) end local func = pos_functions[indexing_pos].func if func then func(headdata, args) end end -- l10n: 標籤中文化 headdata:parse_and_insert_inflection("abbr", "縮寫") headdata.checkredlinks = resolve_prop(checkredlinks) setmetatable(headdata, nil) headdata.process_props = nil if args.json then return require("Module:JSON").toJSON(headdata) end return require(headword_module).full_headword(headdata) end return export 2kl4hmf0jbkox7tdp79ns88co485y4e fèt 0 1886938 9943157 9787168 2026-10-13T00:00:26Z Chihunglu83 87715 9943157 wikitext text/x-wiki {{also|fet|FET|Fet}} ==海地克里奧爾語== ===發音=== * {{IPA|ht|/fɛt/}} ===词源1=== 来自{{inh|ht|fr|faite}}。 ====動詞==== {{head|ht|動詞}} # [[出世]],[[出生]] # {{past participle of|ht|fè}} ===词源2=== 来自{{inh|ht|fr|fête|t=派对}}。 ====名詞==== {{head|ht|名詞}} # [[派對]] 335ebdtmuzxrenfhios7pme3ptwjggj User:Sayonzei/Kotori 2 2129326 9943215 9942465 2026-10-13T01:46:46Z Sayonzei 40728 9943215 wikitext text/x-wiki 精製鳥飼料:[[User:KotoriBot]] ==列表== ===法語=== *一個-er結尾的第一類法語動詞(連同本體)理論上可產出'''40'''個新詞條。<br> :{{col3|fr|}}<!--明日: ;需自行創建 :{{col3|fr|retransmigrer|redésentrelacer|retransfigurer|réobséder|désobséder|désamocher|réamocher|rebuissonner|débuissonner|recolmater|décolmater|désaffréter|redélester|réindividuer|recharrier|recémenter|décémenter|réabsolutiser|désabsolutiser|désacadémiser|désaccastiller|réaccompagner|désaccompagner|réagrémenter|désagrémenter|désalambiquer|désanalphabétiser|désanathématiser|réapostropher|réajourner|désajourner|redétremper|redogmatiser|dédogmatiser|réextirper|réintituler|resolutionner|resouscrire|resulfater|désulfater|réagenouiller|désagenouiller|débouillir|désaccomplir|réaccomplir|réaccoutrer|hâbler}}--> ===加泰=== *一個-ar結尾的普通加泰羅尼亞語動詞(連同本體)理論上可產出'''44'''個新詞條。<br> :{{col3|ca|}} ===葡加利=== *一個-ar結尾的普通葡萄牙語動詞(連同本體)理論上可產出'''53'''個新詞條。<br> :{{col3|pt|}}<!--明日:--> ===西=== *一個-ar結尾的普通西班牙語動詞(連同本體、不含合詞)理論上可產出'''58'''個新詞條。<br> :{{col3|es|beatificar}}<!--明日: ;需自行創建 :{{col3|es|}}--> ===德語=== :{{col3|de|}} ===拉丁語=== :{{col3|la|beatifico}}<!--明日:--> :{{col3|la|beatificans|beatificaturus|beatificandus|beatificatus}}<!--明日:--> ===意大利語=== :{{col3|it|balbettare|beatificare|borbottare|brancolare}}<!--明日:--> ===俄語=== :{{col3|ru|}} :{{col3|ru|}} bt4wo0g4isuuxyillmxtwlbd67yiona 9943675 9943215 2026-10-13T11:37:52Z Sayonzei 40728 9943675 wikitext text/x-wiki 精製鳥飼料:[[User:KotoriBot]] ==列表== ===法語=== *一個-er結尾的第一類法語動詞(連同本體)理論上可產出'''40'''個新詞條。<br> :{{col3|fr|}}<!--明日: ;需自行創建 :{{col3|fr|retransmigrer|redésentrelacer|retransfigurer|réobséder|désobséder|désamocher|réamocher|rebuissonner|débuissonner|recolmater|décolmater|désaffréter|redélester|réindividuer|recharrier|recémenter|décémenter|réabsolutiser|désabsolutiser|désacadémiser|désaccastiller|réaccompagner|désaccompagner|réagrémenter|désagrémenter|désalambiquer|désanalphabétiser|désanathématiser|réapostropher|réajourner|désajourner|redétremper|redogmatiser|dédogmatiser|réextirper|réintituler|resolutionner|resouscrire|resulfater|désulfater|réagenouiller|désagenouiller|débouillir|désaccomplir|réaccomplir|réaccoutrer|hâbler}}--> ===加泰=== *一個-ar結尾的普通加泰羅尼亞語動詞(連同本體)理論上可產出'''44'''個新詞條。<br> :{{col3|ca|}} ===葡加利=== *一個-ar結尾的普通葡萄牙語動詞(連同本體)理論上可產出'''53'''個新詞條。<br> :{{col3|pt|}}<!--明日:--> ===西=== *一個-ar結尾的普通西班牙語動詞(連同本體、不含合詞)理論上可產出'''58'''個新詞條。<br> :{{col3|es|}}<!--明日: ;需自行創建 :{{col3|es|}}--> ===德語=== :{{col3|de|}} ===拉丁語=== :{{col3|la|}}<!--明日:--> :{{col3|la|}}<!--明日:--> ===意大利語=== :{{col3|it|}}<!--明日:--> ===俄語=== :{{col3|ru|подменять}} :{{col3|ru|}} cauy3rd7qxcyueg5dnilpc4uo3v3xw4 adresse de courrier électronique 0 2174828 9943030 7641096 2026-10-12T15:21:57Z TongcyDai 53191 9943030 wikitext text/x-wiki ==法語== ===詞源=== 源自 {{m|fr|courrier électronique}}。 ===發音=== * {{fr-IPA}} * {{audio|fr|LL-Q150 (fra)-LoquaxFR-adresse de courrier électronique.wav|a=孚日}} ===名詞=== {{fr-noun|f}} # {{lb|fr|網際網路|過時}} [[電子郵件地址]] #: {{syn|fr|adresse électronique|adresse de messagerie|adresse courriel|adresse e-mail|adresse mail}} 6q36qat9t9odza3pwlcu3gcgt8h4cm4 9943031 9943030 2026-10-12T15:22:23Z TongcyDai 53191 9943031 wikitext text/x-wiki ==法語== ===詞源=== 源自 {{m|fr|adresse}} + {{m|fr|de}} + {{m|fr|courrier électronique}}。 ===發音=== * {{fr-IPA}} * {{audio|fr|LL-Q150 (fra)-LoquaxFR-adresse de courrier électronique.wav|a=孚日}} ===名詞=== {{fr-noun|f}} # {{lb|fr|網際網路|過時}} [[電子郵件地址]] #: {{syn|fr|adresse électronique|adresse de messagerie|adresse courriel|adresse e-mail|adresse mail}} 04j2r6js0ma56cghc8njv28hs53c1cc アヘ顔 0 2186074 9943029 9342080 2026-10-12T15:21:46Z YeBoy371 43905 /* 發音 */ 9943029 wikitext text/x-wiki ==日語== {{ja-kanjitab|かお|k1=がお|r=y|yomi=kun}} ===詞源=== {{compound|ja|アヘアヘ|tr1=aheahe|pos1=形容人大口喘氣的擬聲詞|顔|tr2=kao|t2=臉}}的組詞。{{rendaku2|kao|gao}} ===發音=== {{ja-pron|アヘがお}} {{rfp|ja|音高?}} {{rfap|ja}} ===名詞=== {{ja-noun|あへがお|アヘがお}} # {{lb|ja|俚語}} [[高潮]][[臉]],[[阿嘿顏]] ====派生語彙==== {{top2}} * {{desc|en|ahegao|bor=1}} * {{desc|ko|아헤가오|bor=1}} * {{desc|cmn|阿嘿顏|bor=1}} * {{desc|fa|آهگائو|tr=âhegâ'o|bor=1}} * {{desc|ru|ахэга́о|bor=1}} * {{desc|tg|аҳегао|bor=1}} * {{desc|uk|aхеґао|bor=1}} {{bottom}} {{C|ja|性}} f8wndoh1bjxevszefm1qzqa0d3pgua0 Module:Gl-headword 828 2345220 9943112 9909067 2026-10-12T19:24:04Z TongcyDai 53191 同步 enwikt(rev 93432996):改以 termobj_has_decorations() 檢查複數代碼是否帶行內修飾符(新版 [[Module:headword utilities]] 所需) 9943112 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 m_headword_utilities.termobj_has_decorations(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 c2xh3svui7jtak1sg3qlr9ki6qcdt4v Module:Labels/data/lang/ak 828 2499781 9943118 8656744 2026-10-12T19:25:13Z TongcyDai 53191 同步 enwikt(rev 93519260)並本地化:新增阿夸佩姆契維語(Akuapem)、阿散蒂契維語(Asante);鍵名改用中文,原英語鍵名列為別名;Wikipedia 改指向中文維基百科條目 9943118 Scribunto text/plain local labels = {} labels["阿夸佩姆契維語"] = { aliases = {"Akuapem", "阿夸佩姆"}, Wikipedia = "en:Akuapem Twi", plain_categories = true, parent = "契維語", region = "[[加納]][[w:東部大區 (加納)|東部大區]]的[[w:en:Akuapem|阿夸佩姆]]地區", } labels["阿散蒂契維語"] = { aliases = {"Asante", "阿散蒂"}, Wikipedia = "en:Asante Twi", plain_categories = true, parent = "契維語", region = "[[加納]][[w:阿散蒂大區|阿散蒂大區]]", } labels["芳蒂語"] = { aliases = {"Fante", "Fantse", "Fanti", "Mfantse", "芳蒂"}, Wikipedia = true, regional_categories = "芳蒂", } labels["Kromanti"] = { display = "Kromanti 附身儀式語言", Wikipedia = "en:Jamaican Maroon spirit-possession language", regional_categories = true, } labels["契維語"] = { aliases = {"Twi", "Akan Kasa", "契維"}, Wikipedia = true, regional_categories = "契維", } return require("Module:labels").finalize_data(labels) aicokm8a48c3neigk9l9i66skd6zgvr 叔伯兄弟 0 2647443 9943055 8561048 2026-10-12T15:59:08Z TongcyDai 53191 9943055 wikitext text/x-wiki ==漢語== {{zh-forms|type=22}} ===發音1=== {{zh-pron |m=叔伯 xiōngdì |m-s=su2be2xiong1di4 |g='suh6 bah6 'xiung1 ti5 |h=pfs=suk-pak-hiûng-thi;hrs=h:shug bag hiungˋ ti˖;gd=sug5 bag5 hiung1 ti4 |j=sueh4-3 bieh5 xyng1 di3 |mn=tp,kh,hc,km,mg,tc,tn,yl:chek-peh-hiaⁿ-tī/lk:chek-peh-hiaⁿ-tǐ |x=shou6 be6 xiong1 di |cat=n }} ====名詞==== {{head|zh|名詞}} # {{lb|zh|口語}} [[父親]]的[[兄弟]]的[[兒子]];[[堂兄弟]] =====近義詞===== {{zh-dial|堂兄弟}} ===發音2=== {{zh-pron |m=叔伯 xiōngdi,1nb=Mainland,2nb=Taiwan |g='suh6 bah6 'xiang1 ti5 |cat=n }} ====名詞==== {{head|zh|名詞}} # {{lb|zh|口語}} [[父親]]的[[兄弟]]的[[兒子]]中比自己年幼者;[[堂弟]] =====近義詞===== {{zh-dial|堂弟}} {{C|zh|男性家庭成員}} 3j77c3fmybnv5pe7gwv4rq2b3db1lv3 マクドナルド 0 2718180 9943033 9921346 2026-10-12T15:25:05Z YeBoy371 43905 /* 日語 */ 9943033 wikitext text/x-wiki ==日語== {{wp|lang=ja}} ===詞源=== {{bor+|ja|en|McDonald}} 或 {{m|en|MacDonald}}。 ===發音=== {{ja-pron|acc=4|acc_ref=NKD2}} {{rfap|en}} ===專有名詞=== {{ja-pos|proper|^マク^ドナルド}} # [[麥當勞]] {{gloss|快餐品牌}} # {{name translit|ja|en|McDonald|MacDonald|type=姓氏}} ====近義詞==== * {{sense|快餐品牌}} {{ja-r|^マック}}、{{qualifier|主要用於關西}} {{ja-r|^マクド}} ===參考資料=== {{reflist}} {{cln|ja|名祖}} {{c|ja|食物}} bzf1keyn7bnczeslt2ogcm6n5jkqv14 9943034 9943033 2026-10-12T15:25:15Z YeBoy371 43905 /* 發音 */ 9943034 wikitext text/x-wiki ==日語== {{wp|lang=ja}} ===詞源=== {{bor+|ja|en|McDonald}} 或 {{m|en|MacDonald}}。 ===發音=== {{ja-pron|acc=4|acc_ref=NKD2}} {{rfap|ja}} ===專有名詞=== {{ja-pos|proper|^マク^ドナルド}} # [[麥當勞]] {{gloss|快餐品牌}} # {{name translit|ja|en|McDonald|MacDonald|type=姓氏}} ====近義詞==== * {{sense|快餐品牌}} {{ja-r|^マック}}、{{qualifier|主要用於關西}} {{ja-r|^マクド}} ===參考資料=== {{reflist}} {{cln|ja|名祖}} {{c|ja|食物}} o6x64oj6ceg4mdkob5ibxu7qgcu3mu7 Module:Ber-headword 828 3131066 9943117 9909329 2026-10-12T19:24:44Z TongcyDai 53191 同步 enwikt(rev 93426245):配合 [[Module:headword utilities]] 新介面(process_props.template_indicated_pos、calldata);可用的性別加入複數(m-p、f-p 等) 9943117 Scribunto text/plain local export = {} local pos_functions = {} local M = require("Module:module loader").init { require = { headword_utilities = "Module:headword utilities", languages = "Module:languages", scripts = "Module:scripts", links = "Module:links", en_utilities = "Module:en-utilities", latn_tfng_translit = "Module:Latn-Tfng-translit", shi_latn_arab_translit = "Module:shi-Latn-Arab-translit", rif_verb_form = "Module:rif-verb form", gho_verb_form = "Module:gho-verb form", }, } local force_cat = false local main_script = {tzm = "Tfng", zgh = "Tfng"} local auto_tifinagh = {rif = true, shi = true, sjs = true} local auto_arabic = {shi = true} local verb_form_modules = {rif = "rif_verb_form", gho = "gho_verb_form"} local valid_genders = { "m", "f", "mf", "mfbysense", "p", "?", "m-p", "f-p", "mf-p", "mfbysense-p", } -- l10n: 詞類名稱中文化 local gender_default = { ["名詞"] = "?", ["專有名詞"] = "?", ["數詞"] = "?", ["代詞"] = "?", ["集合名詞"] = "m", ["個體名詞"] = "f", } -- l10n: 詞類名稱中文化 local pos_category_for = { ["集合名詞"] = "名詞", ["個體名詞"] = "名詞", } local function main_script_code(lang) local code = main_script[lang:getCode()] if code then return code end local script_codes = lang:getScriptCodes() for _, script_code in ipairs(script_codes) do if script_code == "Latn" then return script_code end end return script_codes[1] end local function validate_genders(data, genders, gender_type) data:validate_genders(genders, valid_genders, {gender_type = gender_type, no_augment = true}) end local function without_lang(frame) local args = {} for key, value in pairs(frame.args) do if key ~= "lang" then args[key] = value end end return { args = args, getParent = function() return frame:getParent() end, } end local function insert_spelling(data, terms, script_code, form) local sc = M.scripts.getByCode(script_code) local genders = {} for _, gender in ipairs(data.genders or {}) do table.insert(genders, gender.spec) end for _, termobj in ipairs(terms) do termobj.sc = sc termobj.accel = { form = form, lemma = data.pagename, -- l10n: 中文詞類名稱不分單複數 pos = data.process_props.template_indicated_pos, target = termobj.term, gender = genders[1] and table.concat(genders, ",") or nil, } end -- l10n: 標籤中文化 data:insert_inflection(terms, sc:getCanonicalName() .. "寫法") end local function add_other_script_spellings(data, args) if data.sc:getCode() ~= "Latn" then return end local pagename = M.links.remove_links(data.pagename) if auto_tifinagh[data.langcode] and not args.notfng then insert_spelling(data, {{term = M.latn_tfng_translit.tr(pagename, data.langcode, "Latn")}}, "Tfng", "Tifinagh") end if auto_arabic[data.langcode] and not args.noarab then local terms = data:parse_inflection("ar") if not terms[1] then terms = {{term = M.shi_latn_arab_translit.tr(pagename, data.langcode, "Latn")}} end insert_spelling(data, terms, "Arab", "Arabic") end end function export.show(frame) local code = frame.args.lang or error("Specify the language code in lang=, e.g. {{#invoke:ber-headword|show|lang=rif}}") local props = { lang = M.languages.getByCode(code, "lang"), frame = without_lang(frame), pos_functions = pos_functions, include_tr = true, force_cat = force_cat, infls = { {"g", type = "genders", default = function(data) return gender_default[data.process_props.template_indicated_pos] end, validate = function(data, genders) validate_genders(data, genders) end, }, }, augment_params = function(data, params) if auto_tifinagh[code] then params.notfng = {type = "boolean"} end if auto_arabic[code] then params.noarab = {type = "boolean"} params.ar = true end local pos = pos_functions[data.process_props.template_indicated_pos] if pos and pos.augment_params then pos.augment_params(data, params) end end, augment_headdata = function(data) data.pos_category = pos_category_for[data.pos_category] or data.pos_category -- l10n: 詞類名稱中文化 if data.var == nil and data.process_props.template_indicated_pos ~= "字母" then data.var = data.sc:getCode() ~= main_script_code(data.lang) end data.inflections.enable_auto_translit = data.sc:getCode() == "Tfng" end, } local headword = M.headword_utilities.process_headword(props) if props.definition then return headword .. "\n# " .. props.definition end return headword end pos_functions["head"] = { infls = function(data) local pos = pos_functions[data.process_props.template_indicated_pos] return pos and pos.infls or {} end, func = function(data, args) local pos = pos_functions[data.process_props.template_indicated_pos] if pos and pos.func then pos.func(data, args) end add_other_script_spellings(data, args) end, } local function form(param, label, default_gender) return {param, label = label, include_mods = {"g"}, process_after_parse = function(data, terms) for _, termobj in ipairs(terms) do if default_gender and not termobj.genders and termobj.term ~= "-" then termobj.genders = {{spec = default_gender}} end validate_genders(data, termobj.genders, param) end return terms end, } end local function annex_label(label) -- l10n: 標籤中文化 if type(label) == "function" then return function(data, terms) return label(data, terms) .. "附屬態" end end return label .. "附屬態" end local function annexed(param, label) return {form(param, label), form(param .. "annex", annex_label(label))} end local function plural(label) local infl = form("pl", label) local process_terms = infl.process_after_parse infl.process_after_parse = function(data, terms) if terms[1].term == "?" then if terms[2] then error("Can't specify explicit plurals along with '?' for unknown/uncertain plural") end -- l10n: 分類名稱中文化 data:insert_category("有未知或不確定複數的{L}名詞") return nil, true end return process_terms(data, terms) end return {infl, form("plannex", annex_label(label))} end local function infl_list(items) local infls = {} for _, item in ipairs(items) do if type(item[1]) == "table" then for _, infl in ipairs(item) do table.insert(infls, infl) end else table.insert(infls, item) end end return infls end -- l10n: 以下各詞類的名稱、標籤與分類名稱皆中文化 local annex = form("annex", "附屬態") local function noun_infls() return infl_list { annex, annexed("d", "雙數"), plural("複數"), annexed("dim", "指小詞"), annexed("aug", "指大詞"), annexed("f", "陰性等價詞"), annexed("m", "陽性等價詞"), } end pos_functions["名詞"] = { infls = noun_infls(), } pos_functions["專有名詞"] = { infls = noun_infls(), } local function collective_singulative(label, cat, other_param, other_label, other_gender) return { infls = infl_list { {fixed_label = label, cat = cat}, form(other_param, other_label, other_gender), annex, plural("複數"), annexed("dim", "指小詞"), }, } end pos_functions["集合名詞"] = collective_singulative("集合", "集合名詞", "sing", "個體數", "f") pos_functions["個體名詞"] = collective_singulative("個體數", "個體名詞", "coll", "集合", "m") pos_functions["形容詞"] = { infls = infl_list { annex, annexed("f", "陰性"), annexed("pl", "陽性複數"), annexed("fpl", "陰性複數"), annexed("cpl", "通性複數"), annexed("dim", "指小詞"), }, } local function participle_plural_label(data) return data.langcode == "gho" and "複數" or "陽性複數" end local function participles() return { infls = infl_list { annex, annexed("f", "陰性"), annexed("pl", participle_plural_label), annexed("fpl", "陰性複數"), annexed("cpl", "通性複數"), }, } end pos_functions["主動分詞"] = participles() pos_functions["被動分詞"] = participles() pos_functions["副詞"] = { infls = infl_list { annex, form("d", "雙數"), }, } pos_functions["數詞"] = { infls = infl_list { {cat = "基數詞"}, annex, annexed("f", "陰性"), plural("複數"), }, } pos_functions["冠詞"] = { infls = infl_list { annexed("f", "陰性"), plural("複數"), }, } pos_functions["助詞"] = { infls = infl_list { annex, annexed("f", "陰性"), }, } pos_functions["代詞"] = { infls = infl_list { annex, plural("複數"), annexed("f", "陰性"), }, } pos_functions["動詞"] = { infls = { form("aorist", "不定過去式"), form("pret", "完整體"), form("negpret", "否定完整體"), form("cont", "非完整體"), form("vn", "動詞性名詞"), form("ap", "主動分詞"), form("pp", "被動分詞"), }, } pos_functions["動詞變位形式"] = { augment_params = function(data, params) if verb_form_modules[data.langcode] then params[2] = {sublist = "comma without whitespace"} for _, param in ipairs {"slots", "noslots", "conjid", "noconjid", "t"} do params[param] = true end end end, func = function(data, args) if args[2] then data.process_props.calldata.definition = M[verb_form_modules[data.langcode]].definition_line { pagename = data.pagename, lemmas = args[2], slots = args.slots, noslots = args.noslots, conjid = args.conjid, noconjid = args.noconjid, gloss = args.t, } end end, } return export skbvb34xj67cu7x0c8h5abfao8wzfi1 Module:Mwl-headword 828 3298766 9943114 9909086 2026-10-12T19:24:12Z TongcyDai 53191 同步 enwikt(rev 93433006):改以 termobj_has_decorations() 檢查複數代碼是否帶行內修飾符(新版 [[Module:headword utilities]] 所需) 9943114 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 m_headword_utilities.termobj_has_decorations(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 0dmt6letnmp1wmlmk65nw891cesxbdn Category:契維阿坎語 14 3498966 9943122 9836712 2026-10-12T19:26:23Z TongcyDai 53191 補上 lect=1 與地區說明(原本的 {{auto cat}} 會報錯) 9943122 wikitext text/x-wiki {{auto cat|lect=1|[[加納]]南部和中部}} 9u775db5a878dh1csu5tn9s42m7p40x 拳拳到肉 0 3545787 9943667 9941862 2026-10-13T08:42:13Z Fglffer 55252 9943667 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=quánquándàoròu }} ===短語=== {{head|zh|短語}} # {{lb|zh|指[[動作片]]或[[搏擊]]}} [[實打實]]的[[身體]][[對抗]],每一拳都打在對方[[肉體]]上,而非[[虛張聲勢]],形容[[打鬥]][[場面]][[逼真]],[[打擊]]感[[密集]]而[[強烈]] bh5w4vh993x7wfsir0nsjdvlh48cuwj 沉降東 0 3546545 9943024 2026-10-12T12:29:12Z 内存溢出的猫 85038 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|s=沉降东|type=21|lit=沉降的東站}} [[File:202308 Platform of Hangzhoudong Railway Station.jpg|thumb|杭州東站站台]] ===詞源=== 源自{{w|杭州東站}}部分站台出現沉降現象: * {{quote-web |zh |author=黃煜軒 |date=2025-03-13 |title=“要跨一大步上下车”,杭州东站的站台真的“变矮”了? |work=杭州网 |publisher=杭州网络传媒有限公司 |url=http://hznews.hangzhou…」的新頁面 9943024 wikitext text/x-wiki ==漢語== {{zh-forms|s=沉降东|type=21|lit=沉降的東站}} [[File:202308 Platform of Hangzhoudong Railway Station.jpg|thumb|杭州東站站台]] ===詞源=== 源自{{w|杭州東站}}部分站台出現沉降現象: * {{quote-web |zh |author=黃煜軒 |date=2025-03-13 |title=“要跨一大步上下车”,杭州东站的站台真的“变矮”了? |work=杭州网 |publisher=杭州网络传媒有限公司 |url=http://hznews.hangzhou.com.cn/chengshi/content/2025-03/13/content_8893468.htm |passage=在部分网络社交平台上,部分网友认为这是因为杭州东站的部分站台区域出现了沉降,才会导致高铁列车车门和站台出现较大的高差,甚至还有一些网友将杭州东站戏称为“'''沉降东'''”。 |archiveurl=https://web.archive.org/web/20260610094010/https://hznews.hangzhou.com.cn/chengshi/content/2025-03/13/content_8893468.htm |archivedate=2026-06-10 }} ===讀音=== {{zh-pron |m=Chénjiàng Dōng |cat=pn }} ===專有名詞=== {{head|zh|專有名詞}} # {{lb|zh|網絡用語|幽默}} {{place|zh|火車站|t=[[w:zh:杭州東站|杭州東站]]|city/杭州|p/浙江|country/中國}} 5fg8b3331qyaahxufyul56zbastpp50 9943026 9943024 2026-10-12T12:47:15Z Sayonzei 40728 /* 漢語 */ 9943026 wikitext text/x-wiki ==漢語== {{zh-forms|s=沉降东|type=21|lit=沉降的東站}} [[File:202308 Platform of Hangzhoudong Railway Station.jpg|thumb|杭州東站站台]] ===詞源=== 源自{{w|杭州東站}}部分站台出現沉降現象: * {{quote-web |zh |author=黃煜軒 |date=2025-03-13 |title=“要跨一大步上下车”,杭州东站的站台真的“变矮”了? |work=杭州网 |publisher=杭州网络传媒有限公司 |url=http://hznews.hangzhou.com.cn/chengshi/content/2025-03/13/content_8893468.htm |archiveurl=https://web.archive.org/web/20260610094010/https://hznews.hangzhou.com.cn/chengshi/content/2025-03/13/content_8893468.htm |archivedate=2026-06-10 }} *: {{zh-q|simp=y|在 部分 网络 社交 平台 上,部分{fèn} 网友 认为{wéi} 这 是 因为 w:杭州东站\^杭州.^东站 的 部分{fèn} 站台 区域 出现 了 沉降,才 会 导致 高铁 列车 车门 和 站台 出现 较 大 的 高差,甚至 还有 一些 网友 将 w:杭州东站\^杭州.^东站 戏称 为{wéi} “^沉降.^东”。|collapsed=y}} ===發音=== {{zh-pron |m=Chénjiàng Dōng |cat=pn }} ===專有名詞=== {{head|zh|專有名詞}} # {{lb|zh|網絡用語|幽默}} {{place|zh|火車站|t=[[w:杭州東站|杭州東站]]|city/杭州|p/浙江|country/中國}} i8zt3h8x18o6itk1nfp3bbxrav4y7hz courriers électroniques 0 3546546 9943032 2026-10-12T15:22:33Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==法語== ===名詞=== {{head|fr|名詞變格形|g=m}} # {{plural of|fr|courrier électronique}}」的新頁面 9943032 wikitext text/x-wiki ==法語== ===名詞=== {{head|fr|名詞變格形|g=m}} # {{plural of|fr|courrier électronique}} joisgeuewqo99atzzdwbkho6vrw4p1v 堂細佬 0 3546547 9943056 2026-10-12T16:27:59Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|s=堂细佬|type=12}} ===發音=== {{zh-pron |c=tong4 sai3 lou2 |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|粵語}} [[堂弟]] ====近義詞==== {{zh-dial|堂弟}} {{C|zh|男性家庭成員}}」的新頁面 9943056 wikitext text/x-wiki ==漢語== {{zh-forms|s=堂细佬|type=12}} ===發音=== {{zh-pron |c=tong4 sai3 lou2 |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|粵語}} [[堂弟]] ====近義詞==== {{zh-dial|堂弟}} {{C|zh|男性家庭成員}} s1l9i8wyjac0bqo9zus1vaunbml9rhn 叔伯弟弟 0 3546548 9943057 2026-10-12T16:29:00Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|type=22}} ===發音=== {{zh-pron |m=叔伯 dìdi |m-s=su2be2 di4di4-1 |j=sueh4-3 bieh5 di3 di3-2 |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|口語}} [[堂弟]] ====近義詞==== {{zh-dial|堂弟}} {{C|zh|男性家庭成員}}」的新頁面 9943057 wikitext text/x-wiki ==漢語== {{zh-forms|type=22}} ===發音=== {{zh-pron |m=叔伯 dìdi |m-s=su2be2 di4di4-1 |j=sueh4-3 bieh5 di3 di3-2 |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|口語}} [[堂弟]] ====近義詞==== {{zh-dial|堂弟}} {{C|zh|男性家庭成員}} jpvnr4hjxqh5xqa4xxvr2ecm4e6vpls 隔腹小弟 0 3546549 9943058 2026-10-12T16:30:24Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|type=22}} ===發音=== {{zh-pron |mn=qz,jj,ph:keh-pak-sió-tǐ/xm,sg:keh-pak-sió-tī/zz:keeh-pak-sió-tī |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|泉漳話}} [[堂弟]] ====近義詞==== {{zh-dial|堂弟}} ====同類詞彙==== * {{zh-l|堂兄弟}} * {{zh-l|堂哥}} * {{zh-l|堂兄}} ===參考資料=== * {{R:nan-hbl:Zhou 2006|pp=698}} {{C|zh|男性家庭成員}}」的新頁面 9943058 wikitext text/x-wiki ==漢語== {{zh-forms|type=22}} ===發音=== {{zh-pron |mn=qz,jj,ph:keh-pak-sió-tǐ/xm,sg:keh-pak-sió-tī/zz:keeh-pak-sió-tī |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|泉漳話}} [[堂弟]] ====近義詞==== {{zh-dial|堂弟}} ====同類詞彙==== * {{zh-l|堂兄弟}} * {{zh-l|堂哥}} * {{zh-l|堂兄}} ===參考資料=== * {{R:nan-hbl:Zhou 2006|pp=698}} {{C|zh|男性家庭成員}} mi3in8q6uhvypaldqhdexz74agknq3y 醫肚 0 3546550 9943059 2026-10-12T16:52:23Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|s=医肚|lit=醫治肚子}} ===發音=== {{zh-pron |c=ji1 tou5 |cat=v }} ===動詞=== {{zh-verb|type=vo}} # {{lb|zh|粵語}} [[充飢]],[[果腹]] {{C|zh|飲食}}」的新頁面 9943059 wikitext text/x-wiki ==漢語== {{zh-forms|s=医肚|lit=醫治肚子}} ===發音=== {{zh-pron |c=ji1 tou5 |cat=v }} ===動詞=== {{zh-verb|type=vo}} # {{lb|zh|粵語}} [[充飢]],[[果腹]] {{C|zh|飲食}} 1pe4lu912dq3skv4vj01f8jzvv8c2on 압생트 0 3546551 9943062 2026-10-12T17:29:48Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==朝鮮語== ===詞源=== {{bor+|ko|fr|absinthe}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[苦艾酒]]」的新頁面 9943062 wikitext text/x-wiki ==朝鮮語== ===詞源=== {{bor+|ko|fr|absinthe}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[苦艾酒]] jpjw2oycv9qtk9nrme8esratq70o38n 아니스 0 3546552 9943063 2026-10-12T17:31:11Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==朝鮮語== {{swp|ko:}} ===詞源=== {{etymon|ko|:bor|en:anise}} {{bor+|ko|en|anise}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[茴芹]]({{taxfmt|Pimpinella anisum|species}}) {{C|ko|芹科植物|香料和香草}}」的新頁面 9943063 wikitext text/x-wiki ==朝鮮語== {{swp|ko:}} ===詞源=== {{etymon|ko|:bor|en:anise}} {{bor+|ko|en|anise}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[茴芹]]({{taxfmt|Pimpinella anisum|species}}) {{C|ko|芹科植物|香料和香草}} l55x9bwqibtav2xbbo2b3dqhoguywto 창백하다 0 3546553 9943064 2026-10-12T17:33:36Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==朝鮮語== ===詞源=== 源自 {{af|ko|창백(蒼白)|-하다|t2=做|pos2=構成形容詞的[[輕動詞]]}}。 ===發音=== {{ko-IPA}} ===形容詞=== {{ko-adj|hanja=蒼白하다}} # [[蒼白]]的 ====活用==== {{ko-conj/adj}}」的新頁面 9943064 wikitext text/x-wiki ==朝鮮語== ===詞源=== 源自 {{af|ko|창백(蒼白)|-하다|t2=做|pos2=構成形容詞的[[輕動詞]]}}。 ===發音=== {{ko-IPA}} ===形容詞=== {{ko-adj|hanja=蒼白하다}} # [[蒼白]]的 ====活用==== {{ko-conj/adj}} 2nl1tznnbhcrilk967o1ute16s9m5h6 리큐어 0 3546554 9943065 2026-10-12T17:38:59Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==朝鮮語== ===詞源=== {{bor+|ko|en|liqueur}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[利口酒]]」的新頁面 9943065 wikitext text/x-wiki ==朝鮮語== ===詞源=== {{bor+|ko|en|liqueur}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # [[利口酒]] 3lqbb55zo85wp2ac92c1yo1bmd3rzyz 희석하다 0 3546555 9943067 2026-10-12T17:41:35Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==朝鮮語== ===詞源=== 源自 {{af|ko|희석(稀釋)|-하다|t2=做|pos2=構成主動動詞的[[輕動詞]]}}。 ===發音=== {{ko-IPA}} ===動詞=== {{ko-verb|hanja=稀釋하다}} # [[稀釋]] ====活用==== {{ko-conj/verb}}」的新頁面 9943067 wikitext text/x-wiki ==朝鮮語== ===詞源=== 源自 {{af|ko|희석(稀釋)|-하다|t2=做|pos2=構成主動動詞的[[輕動詞]]}}。 ===發音=== {{ko-IPA}} ===動詞=== {{ko-verb|hanja=稀釋하다}} # [[稀釋]] ====活用==== {{ko-conj/verb}} e3mr82bqahlihh4ipkb7g4swmxjlide 堂大佬 0 3546556 9943069 2026-10-12T17:44:57Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|type=12}} ===發音=== {{zh-pron |c=tong4 daai6 lou2 |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|粵語}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} {{C|zh|男性家庭成員}}」的新頁面 9943069 wikitext text/x-wiki ==漢語== {{zh-forms|type=12}} ===發音=== {{zh-pron |c=tong4 daai6 lou2 |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|粵語}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} {{C|zh|男性家庭成員}} nhv1g15g492hkh8wuu0i3i8z5ceh7wh 隔腹兄 0 3546557 9943070 2026-10-12T17:45:44Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|type=21}} ===發音=== {{zh-pron |mn=xm,qz,sg:keh-pak-hiaⁿ/zz:keeh-pak-hiaⁿ |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|泉漳話}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} ===參考資料=== * {{R:nan-hbl:Zhou 2006|pp=698}} {{C|zh|男性家庭成員}}」的新頁面 9943070 wikitext text/x-wiki ==漢語== {{zh-forms|type=21}} ===發音=== {{zh-pron |mn=xm,qz,sg:keh-pak-hiaⁿ/zz:keeh-pak-hiaⁿ |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|泉漳話}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} ===參考資料=== * {{R:nan-hbl:Zhou 2006|pp=698}} {{C|zh|男性家庭成員}} gs0bx6leth7yblns8k5v8f1o63ygxw9 隔腹阿兄 0 3546558 9943071 2026-10-12T17:45:48Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|type=22}} ===發音=== {{zh-pron |mn=sg:keh-pak-a-hiaⁿ/ph:keh-pak-á-hiaⁿ |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|新加坡福建話|&|咱人話}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} {{C|zh|家庭}}」的新頁面 9943071 wikitext text/x-wiki ==漢語== {{zh-forms|type=22}} ===發音=== {{zh-pron |mn=sg:keh-pak-a-hiaⁿ/ph:keh-pak-á-hiaⁿ |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|新加坡福建話|&|咱人話}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} {{C|zh|家庭}} gsf519cpteykivwae6dvmkp26ghxtxl 堂阿哥 0 3546559 9943072 2026-10-12T17:45:53Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|type=12}} ===發音=== {{zh-pron |w=sh:6daon aq ku |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|吳語}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} {{C|zh|男性家庭成員}}」的新頁面 9943072 wikitext text/x-wiki ==漢語== {{zh-forms|type=12}} ===發音=== {{zh-pron |w=sh:6daon aq ku |cat=n }} ===名詞=== {{head|zh|名詞}} # {{lb|zh|吳語}} [[堂兄]],[[堂哥]] ====近義詞==== {{zh-dial|堂兄}} {{C|zh|男性家庭成員}} 0xqry8j7l6d4zfetsdnle3r2r9wyy54 ö́gtʼûŋ 0 3546560 9943073 2026-10-12T17:50:30Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==阿林語== ===詞源=== {{surf|nocat=1|xrn|ö́gga<t:六><tr:ʌgga>|<alt:-tʼûŋ><t:十><tr:-tʸu꞉ŋ>}}({{der+|xrn|qfa-yen-pro|<alt:*aˀəgə><t:六>}})。 ===發音=== * {{IPA|xrn|[ʌɡtʲuːŋ]}} ===數詞=== {{head|xrn|數詞|tr=ʌgtʸu꞉ŋ}} # [[六十]] ===參考資料=== * Vajda, E. J., & Werner, H. K. (2022). Comparative-Historical Yeniseian Dictionary. Volume I (a–pʰ). LINCOM GmbH. (pp. 15–16) * Werner, H. K. (200…」的新頁面 9943073 wikitext text/x-wiki ==阿林語== ===詞源=== {{surf|nocat=1|xrn|ö́gga<t:六><tr:ʌgga>|<alt:-tʼûŋ><t:十><tr:-tʸu꞉ŋ>}}({{der+|xrn|qfa-yen-pro|<alt:*aˀəgə><t:六>}})。 ===發音=== * {{IPA|xrn|[ʌɡtʲuːŋ]}} ===數詞=== {{head|xrn|數詞|tr=ʌgtʸu꞉ŋ}} # [[六十]] ===參考資料=== * Vajda, E. J., & Werner, H. K. (2022). Comparative-Historical Yeniseian Dictionary. Volume I (a–pʰ). LINCOM GmbH. (pp. 15–16) * Werner, H. K. (2002). Vergleichendes Wörterbuch der Jenissej-Sprachen. Band 1: A–K. Harrassowitz Verlag. (p. 93) * Werner, H. K. (2002). Vergleichendes Wörterbuch der Jenissej-Sprachen. Band 2: L– Š. Harrassowitz Verlag. (p. 318) * Werner, H. K. (2005). Die Jenissej-Sprachen des 18. Jahrhunderts. Harrassowitz Verlag. (p. 162) c7wnsyt3ehr9ldpummkvs0v0bb278fa Category:有國際音標的阿林語詞 14 3546561 9943074 2026-10-12T17:50:40Z TongcyBot 83009 養貓 9943074 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:派生自原始葉尼塞語的阿林語詞 14 3546562 9943075 2026-10-12T17:50:46Z TongcyBot 83009 養貓 9943075 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:派生自葉尼塞語系的阿林語詞 14 3546563 9943076 2026-10-12T17:50:52Z TongcyBot 83009 養貓 9943076 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:派生自德內-葉尼塞語門的阿林語詞 14 3546564 9943077 2026-10-12T17:50:58Z TongcyBot 83009 養貓 9943077 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿林語派生詞 14 3546565 9943078 2026-10-12T17:51:04Z TongcyBot 83009 養貓 9943078 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿林語詞源 14 3546566 9943079 2026-10-12T17:51:09Z TongcyBot 83009 養貓 9943079 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿林語數詞 14 3546567 9943080 2026-10-12T17:51:16Z TongcyBot 83009 養貓 9943080 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx tataupa tinamou 0 3546568 9943081 2026-10-12T18:03:36Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==英語== {{wp|en:}} ===名詞=== {{en-noun}} # {{w|塔陶穴䳍}},塔陶巴穴䳍{{gl|{{taxfmt|Crypturellus tataupa|species}}}} #: {{syn|en|tataupa}} {{C|en|䳍形目}}」的新頁面 9943081 wikitext text/x-wiki ==英語== {{wp|en:}} ===名詞=== {{en-noun}} # {{w|塔陶穴䳍}},塔陶巴穴䳍{{gl|{{taxfmt|Crypturellus tataupa|species}}}} #: {{syn|en|tataupa}} {{C|en|䳍形目}} 4eilm6kcpiulsk7ani31979net7tlbl Category:英語 䳍形目 14 3546569 9943082 2026-10-12T18:03:47Z TongcyBot 83009 養貓 9943082 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:䳍形目 14 3546570 9943083 2026-10-12T18:03:52Z TongcyBot 83009 養貓 9943083 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:英語 古顎類 14 3546571 9943084 2026-10-12T18:04:00Z TongcyBot 83009 養貓 9943084 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Sangakubu Hyōjunji 0 3546572 9943085 2026-10-12T18:05:03Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==日語== ===羅馬化=== {{ja-romaji|head=[[sangaku|Sangaku]][[bu]] [[hyōjunji|Hyōjunji]]}} # {{ja-romanization of|さんがくぶひょうじゅんじ}}」的新頁面 9943085 wikitext text/x-wiki ==日語== ===羅馬化=== {{ja-romaji|head=[[sangaku|Sangaku]][[bu]] [[hyōjunji|Hyōjunji]]}} # {{ja-romanization of|さんがくぶひょうじゅんじ}} h7hcfs8wfj5t9ovefnj9ykxo8c0kjvj さんがくぶひょうじゅんじ 0 3546573 9943086 2026-10-12T18:05:13Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==日語== {{ja-see|山岳部標準時}}」的新頁面 9943086 wikitext text/x-wiki ==日語== {{ja-see|山岳部標準時}} c7yh2tgspk8yh9zur6voirgcntqkqo0 tataupa tinamous 0 3546574 9943087 2026-10-12T18:05:20Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==英語== ===名詞=== {{head|en|名詞變格形}} # {{plural of|en|tataupa tinamou}}」的新頁面 9943087 wikitext text/x-wiki ==英語== ===名詞=== {{head|en|名詞變格形}} # {{plural of|en|tataupa tinamou}} gwokq44ptrwluoqhlfn5o32fi4gh4gi 山岳部標準時 0 3546575 9943088 2026-10-12T18:05:56Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==日語== {{ja-kanjitab|さん|がく|ぶ|ひょう|じゅん|じ|yomi=o}} ===專有名詞=== {{ja-pos|proper|さんがくぶ ひょうじゅんじ}} # {{w|北美山地時區}} {{C|ja|時區|sort=さんがくぶひょうじゅんじ}}」的新頁面 9943088 wikitext text/x-wiki ==日語== {{ja-kanjitab|さん|がく|ぶ|ひょう|じゅん|じ|yomi=o}} ===專有名詞=== {{ja-pos|proper|さんがくぶ ひょうじゅんじ}} # {{w|北美山地時區}} {{C|ja|時區|sort=さんがくぶひょうじゅんじ}} 5ua2uy6x3id46pk5pwp4f8xeo5fzicr ɔbɔmmɔfoɔ 0 3546576 9943089 2026-10-12T18:06:10Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==阿坎語== ===名詞=== {{ak-head|名詞|pl=abɔmmɔfoɔ}} # [[獵人]] ====參考資料==== * {{R:ak:LearnAkan|hunter|en}} {{C|ak|職業}}」的新頁面 9943089 wikitext text/x-wiki ==阿坎語== ===名詞=== {{ak-head|名詞|pl=abɔmmɔfoɔ}} # [[獵人]] ====參考資料==== * {{R:ak:LearnAkan|hunter|en}} {{C|ak|職業}} te40pi0y4jpbclmzp0yv6k2fzmgkyge Template:Ak-head 10 3546577 9943090 2026-10-12T18:06:39Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「{{#invoke:ak-headword|show}}<!-- --><noinclude>{{documentation}}{{hwcat}}</noinclude>」的新頁面 9943090 wikitext text/x-wiki {{#invoke:ak-headword|show}}<!-- --><noinclude>{{documentation}}{{hwcat}}</noinclude> pqwlv1osgv86tv7p9npb9164n93b1wf Category:阿坎語標題行模板 14 3546578 9943091 2026-10-12T18:06:52Z TongcyBot 83009 養貓 9943091 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿坎語 職業 14 3546579 9943092 2026-10-12T18:13:11Z TongcyBot 83009 養貓 9943092 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿坎語 人 14 3546580 9943093 2026-10-12T18:13:16Z TongcyBot 83009 養貓 9943093 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿坎語 工作 14 3546581 9943094 2026-10-12T18:13:23Z TongcyBot 83009 養貓 9943094 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿坎語 人類活動 14 3546582 9943095 2026-10-12T18:13:29Z TongcyBot 83009 養貓 9943095 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Template:R:ak:LearnAkan 10 3546583 9943096 2026-10-12T18:13:45Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「{{#invoke:quote|call_template | template = cite-web | author = LearnAkan | year = 2018–present | worklang = en | work = LearnAkanDictionary.com | title = {{{termlang|{{{2|ak}}}}}}:{{{entry|{{{1|{{pagename}}}}}}}} | titleurl = https://learnakandictionary.com/{{#switch: {{{termlang|{{{2|ak}}}}}} | en = english-twi | ak = twi-english }}/{{urlencode:{{{entry|{{{1|{{pagename}}}}…」的新頁面 9943096 wikitext text/x-wiki {{#invoke:quote|call_template | template = cite-web | author = LearnAkan | year = 2018–present | worklang = en | work = LearnAkanDictionary.com | title = {{{termlang|{{{2|ak}}}}}}:{{{entry|{{{1|{{pagename}}}}}}}} | titleurl = https://learnakandictionary.com/{{#switch: {{{termlang|{{{2|ak}}}}}} | en = english-twi | ak = twi-english }}/{{urlencode:{{{entry|{{{1|{{pagename}}}}}}}}}}/ | allowparams = entry, 1, termlang, 2 | propagateparams = archiveurl, archivedate, accessdate }}<!-- --><noinclude>{{documentation}}{{refcat}}</noinclude> agxiq3baxr3uzwcc81yqbx7oabivr36 Reconstruction:原始馬來-波利尼西亞語/bituqən 118 3546584 9943097 2026-10-12T18:15:16Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「{{reconstructed}} ==原始馬來-波利尼西亞語== ===詞源=== {{ety|poz-pro|:inh|map-pro:*bituqən|text=+|tree=1}} ===發音=== * {{IPA|poz-pro|/bituʔən/}} ===名詞=== {{head|poz-pro|名詞}} # [[星]],[[星星]],[[星辰]] ====派生語彙==== * {{desc|poz-cet-pro|-}} ** 中馬來-波利尼西亞語支 *** 巴巴爾語支: **** {{desc|drn|tonno}} *** 中馬魯古語支: **** {{desc|ges|ftuin}} *** 帝汶語群: **** {{…」的新頁面 9943097 wikitext text/x-wiki {{reconstructed}} ==原始馬來-波利尼西亞語== ===詞源=== {{ety|poz-pro|:inh|map-pro:*bituqən|text=+|tree=1}} ===發音=== * {{IPA|poz-pro|/bituʔən/}} ===名詞=== {{head|poz-pro|名詞}} # [[星]],[[星星]],[[星辰]] ====派生語彙==== * {{desc|poz-cet-pro|-}} ** 中馬來-波利尼西亞語支 *** 巴巴爾語支: **** {{desc|drn|tonno}} *** 中馬魯古語支: **** {{desc|ges|ftuin}} *** 帝汶語群: **** {{desc|mgm|hiuta}} **** {{desc|lti|ptuna}} **** {{desc|lex|-}} ***** Wetan:{{l|lex|witiona}} **** {{desc|tdt|fitun}} *** 塞拉魯語: **** {{desc|slu|ftun}} ** {{desc|pqe-pro|-}} *** {{desc|poz-hce-pro|-}} **** {{desc|xmt|toen}} **** {{desc|slz|tuén|tuin}} *** {{desc|poz-oce-pro|*pituqun}} {{see desc}} * {{desc|ch|puti'on}} * Land Dayak: ** {{desc|rej|ꤷꥇꤳꥏ|tr=bitang}} * Moklenic: ** {{desc|mwt|bituek}} * {{desc|kaw|wintaṅ}} * {{desc|phi-pro|*bituqən}} ** {{desc|akl|bituon}} ** {{desc|bno|bituon}} ** {{desc|bcl|bituon}} ** {{desc|ceb|bitoon}}、{{l|ceb|bituon}} ** {{desc|ilo|bituen}} ** {{desc|ivv|vituhen}} ** {{desc|pam|batuin}} ** {{desc|krj|bituën}} ** {{desc|msb|bituon}} ** {{desc|btn|bituon}} ** {{desc|tl|bituin}} * {{desctree|poz-mly-pro|*bintaŋ}} * {{desc|su|ᮘᮦᮔ᮪ᮒᮀ}} * {{desc|poz-ssw-pro|-}} ** {{desc|bug|wittoeng}} ** {{desc|mak|bintoeng}} ** {{desc|mdr|bittoeng}} ===參考資料=== * {{R:map:ACD|bituqen|25551}} * {{R:map-pro:ACD|bituqən|25551}} ===延伸閱讀=== * [https://abvd.shh.mpg.de/austronesian/word.php?v=130 Austronesian Basic Vocabulary Database – star] 99gniicjebsjni7zo1wsg2skxeny7qe vituhen 0 3546585 9943098 2026-10-12T18:15:22Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==伊萬特語== ===詞源=== {{ety|ivv|:inh|phi-pro:*bituən<ety:inh<poz-pro:*bituqən>>|text=3|tree=1}} ===發音=== * {{hyph|ivv|vi|tu|hen}} ===名詞=== {{head|ivv|名詞|head=vituhën}} # [[星]],[[星星]],[[星辰]]」的新頁面 9943098 wikitext text/x-wiki ==伊萬特語== ===詞源=== {{ety|ivv|:inh|phi-pro:*bituən<ety:inh<poz-pro:*bituqən>>|text=3|tree=1}} ===發音=== * {{hyph|ivv|vi|tu|hen}} ===名詞=== {{head|ivv|名詞|head=vituhën}} # [[星]],[[星星]],[[星辰]] 41skvbs822eev40by87lhnkct9apgo5 Category:有etymon的伊萬特語詞條 14 3546586 9943099 2026-10-12T18:15:46Z TongcyBot 83009 養貓 9943099 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:有詞源文字的伊萬特語詞條 14 3546587 9943100 2026-10-12T18:15:52Z TongcyBot 83009 養貓 9943100 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:有詞源樹的伊萬特語詞條 14 3546588 9943101 2026-10-12T18:15:57Z TongcyBot 83009 養貓 9943101 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Reconstruction:原始南島語/bituqən 118 3546589 9943102 2026-10-12T18:16:21Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「{{reconstructed}} ==原始南島語== ===發音=== * {{IPA|map-pro|/bituqən/}} ===名詞=== {{head|map-pro|名詞}} # [[星]],[[星星]],[[星辰]] ====派生語彙==== * 北台灣南島語族: ** 平原語支: *** {{desc|xsy|bintoe'an}} *** {{desc|pzh|bintún|bintul}} * {{desc|bnn|bintuqan|bintuhan}} (郡社群) * {{desc|pyu|vitu'en}} (卡地布) * {{desc|pwn|vitjuqan}} * {{desc|poz-pro|*bituqən}} {{see desc}} ===參考資料=…」的新頁面 9943102 wikitext text/x-wiki {{reconstructed}} ==原始南島語== ===發音=== * {{IPA|map-pro|/bituqən/}} ===名詞=== {{head|map-pro|名詞}} # [[星]],[[星星]],[[星辰]] ====派生語彙==== * 北台灣南島語族: ** 平原語支: *** {{desc|xsy|bintoe'an}} *** {{desc|pzh|bintún|bintul}} * {{desc|bnn|bintuqan|bintuhan}} (郡社群) * {{desc|pyu|vitu'en}} (卡地布) * {{desc|pwn|vitjuqan}} * {{desc|poz-pro|*bituqən}} {{see desc}} ===參考資料=== * {{R:map:ACD|bituqen|25551}} * {{R:map-pro:ACD|bituqən|25551}} ===延伸閱讀=== * [https://abvd.shh.mpg.de/austronesian/word.php?v=130 Austronesian Basic Vocabulary Database – star] h2gezlx1j9gkp1m8iyhswh3q5z8fzlw Category:有詞源文字的原始馬來-波利尼西亞語詞條 14 3546590 9943103 2026-10-12T18:17:04Z TongcyBot 83009 養貓 9943103 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Template:Slr-verb-2 10 3546591 9943104 2026-10-12T18:17:25Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「{{#invoke:checkparams|warn}}<!-- Validate template parameters -->{{head|slr|動詞|head={{{head|}}}|現在時|{{#if:{{{pres|}}}|{{{pres}}}|{{#switch:{{{1|}}}|bar={{pres}}{{{1}}}}}}}|過去時|{{#if:{{{past|}}}|{{{past}}}|{{#switch:{{{1|}}}|ci={{pagename}}{{{1}}}}}}}}}<noinclude>{{tcat|hw}}</noinclude>」的新頁面 9943104 wikitext text/x-wiki {{#invoke:checkparams|warn}}<!-- Validate template parameters -->{{head|slr|動詞|head={{{head|}}}|現在時|{{#if:{{{pres|}}}|{{{pres}}}|{{#switch:{{{1|}}}|bar={{pres}}{{{1}}}}}}}|過去時|{{#if:{{{past|}}}|{{{past}}}|{{#switch:{{{1|}}}|ci={{pagename}}{{{1}}}}}}}}}<noinclude>{{tcat|hw}}</noinclude> fpk0ah5emjhoh1u7rrkjobbn5b77f8t tüxürğüsi 0 3546592 9943105 2026-10-12T18:19:01Z TongcyDai 53191 [[w:WP:AES|←]]建立內容為「==撒拉語== ===詞源=== {{inh+|slr|trk-pro|*tüpkür-}}。和{{cog|az|tüpürmək}}、{{cog|gag|tükürmää}}、{{cog|tr|tükürmek}}、{{cog|tk|tüýkürmek}} 同源。 ===發音=== * {{IPA|slr|/tʰyxur(ɣysi)/|/t͡ɕyxur(ɣysi)/|a=青海循化}} * {{IPA|slr|/ʒʰixur(ɣysi)/|a=青海孟達}} ===動詞=== {{slr-verb-2|pres=tüxürer|past=tükürci}} # [[吐]]{{gl|口水等}} ====變位==== {{slr-conj|tüxür}} ===參考資料=== *…」的新頁面 9943105 wikitext text/x-wiki ==撒拉語== ===詞源=== {{inh+|slr|trk-pro|*tüpkür-}}。和{{cog|az|tüpürmək}}、{{cog|gag|tükürmää}}、{{cog|tr|tükürmek}}、{{cog|tk|tüýkürmek}} 同源。 ===發音=== * {{IPA|slr|/tʰyxur(ɣysi)/|/t͡ɕyxur(ɣysi)/|a=青海循化}} * {{IPA|slr|/ʒʰixur(ɣysi)/|a=青海孟達}} ===動詞=== {{slr-verb-2|pres=tüxürer|past=tükürci}} # [[吐]]{{gl|口水等}} ====變位==== {{slr-conj|tüxür}} ===參考資料=== * {{R:slr:Potanin}} * {{R:slr:Kakuk|page=193}} * {{R:slr:Stroy|page=527, 568}} * {{R:slr:Lianyun1992|page=51}} * {{R:slr:MaChengjun|page=293}} * {{R:slr:MaWei2|page=283}} j75bc8j5lmi8923myeh2zzvg5cifrf5 Category:撒拉語動詞 14 3546593 9943106 2026-10-12T18:19:08Z TongcyBot 83009 養貓 9943106 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Module:Ak-headword 828 3546594 9943119 2026-10-12T19:25:16Z TongcyDai 53191 自 enwikt 搬運(rev 93480652)並本地化:詞類與 lect= 可用英語或中文指定 9943119 Scribunto text/plain local export = {} local pos_functions = {} local M = require("Module:module loader").init { require = { headword_utilities = "Module:headword utilities", labels = "Module:labels", }, lazy = { lang = function() return require("Module:languages").getByCode("ak") end, }, } local force_cat = false -- l10n: 別名加入中文名稱,lect= 可用英語或中文指定 local lect_data = { {"Twi", aliases = {"t", "契維語", "契維"}}, {"Akuapem", parent = "Twi", aliases = {"ak", "阿夸佩姆契維語", "阿夸佩姆"}}, {"Asante", parent = "Twi", aliases = {"as", "阿散蒂契維語", "阿散蒂"}}, {"Fante", aliases = {"f", "芳蒂語", "芳蒂"}}, } local lect_names = {} for _, lect in ipairs(lect_data) do lect_names[lect[1]] = lect[1] for _, alias in ipairs(lect.aliases) do lect_names[alias] = lect[1] end end local function get_lects(value) if not value or not value:find("%S") then return {} end local list = mw.text.split(value, "%s*,%s*") for i, lect in ipairs(list) do list[i] = lect_names[lect] if not list[i] then local valid = {} for _, data in ipairs(lect_data) do table.insert(valid, ("%s (%s)"):format(data[1], data.aliases[1])) end error(("Unrecognized lect '%s'; use %s"):format(lect, mw.text.listToText(valid, ", ", " or "))) end end return list end function export.show(frame) local headword = M.headword_utilities.process_headword { lang = M.lang, frame = frame, pos_functions = pos_functions, force_cat = force_cat, augment_params = function(data, params) params.lect = true end, } local args = frame:getParent().args local list = get_lects(args.lect) if not list[1] or args.json then return headword end return headword .. " " .. M.labels.show_labels {labels = list, lang = M.lang, mode = "term-label"} end -- l10n: 以下各詞類的名稱與標籤皆中文化;詞類可用英語(noun、n)或中文(名詞)指定 pos_functions["head.名詞"] = { infls = { {"pl", label = "複數"}, }, } pos_functions["head.形容詞"] = { infls = { {"pl", label = "複數"}, {"redup", label = "重疊式"}, {"triplication", label = "三疊式"}, }, } pos_functions["head.副詞"] = { infls = { {"triplication", label = "三疊式"}, }, } local function verb_form(param, label, form) return {param, label = label, accel_form = form, } end pos_functions["head.動詞"] = { infls = { verb_form("tpast", "及物過去時", "tr|past"), verb_form("ipast", "不及物過去時", "intr|past"), verb_form("neg", "否定", "neg"), verb_form("fut", "將來時", "fut"), verb_form("prog", "進行時", "prog"), verb_form("pros", "前瞻體", "pros"), verb_form("perf", "完成時", "perf"), verb_form("imp", "命令式", "imp"), verb_form("redup", "重疊式"), }, } return export m1bhx6m1oa9wrbmlheawjp8oaqeegqy Category:阿坎語變體 14 3546595 9943120 2026-10-12T19:26:14Z TongcyDai 53191 建立分類(配合 [[Module:ak-headword]] 與 [[Module:labels/data/lang/ak]] 的本地化) 9943120 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:地域阿坎語方言 14 3546596 9943121 2026-10-12T19:26:18Z TongcyDai 53191 建立分類(配合 [[Module:ak-headword]] 與 [[Module:labels/data/lang/ak]] 的本地化) 9943121 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:芳蒂阿坎語 14 3546597 9943123 2026-10-12T19:26:29Z TongcyDai 53191 建立分類(配合 [[Module:ak-headword]] 與 [[Module:labels/data/lang/ak]] 的本地化) 9943123 wikitext text/x-wiki {{auto cat|lect=1|[[加納]][[w:中部大區 (加納)|中部大區]]由[[w:en:Fante people|芳蒂人]]}} kkh833qnhrlj8u23py5lbercs42gw5o Category:Kromanti阿坎語 14 3546598 9943124 2026-10-12T19:26:35Z TongcyDai 53191 建立分類(配合 [[Module:ak-headword]] 與 [[Module:labels/data/lang/ak]] 的本地化) 9943124 wikitext text/x-wiki {{auto cat|lect=1|[[w:牙買加馬龍人|牙買加馬龍人]]的附身儀式中}} 1173420mqf3t7ildg0zkz6prbcu3pde Category:阿坎語可數名詞 14 3546599 9943125 2026-10-12T19:26:41Z TongcyDai 53191 建立分類(配合 [[Module:ak-headword]] 與 [[Module:labels/data/lang/ak]] 的本地化) 9943125 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx Category:阿夸佩姆契維語 14 3546600 9943126 2026-10-12T19:26:56Z TongcyDai 53191 建立分類(配合 [[Module:ak-headword]] 與 [[Module:labels/data/lang/ak]] 的本地化) 9943126 wikitext text/x-wiki {{auto cat|lect=1}} ertee7cys9kcm61xm19ibbz6xr74sxz Template:Ak-head/doc 10 3546601 9943127 2026-10-12T19:27:43Z TongcyDai 53191 譯自 enwikt 9943127 wikitext text/x-wiki {{documentation subpage}} {{uses lua|Module:ak-headword}} 本模板用於生成阿坎語詞條的標題行。{{para|1}} 填詞類,其他可用的參數視詞類而定。 ==參數== ;{{para|1|req=1}} : 詞類。可以寫中文(如 <code>名詞</code>、<code>動詞</code>),也可以寫英文全名或縮寫,例如 {{cd|n}} 表示名詞、{{cd|v}} 表示動詞、{{cd|adj}} 表示形容詞、{{cd|nf}} 表示名詞變格形、{{cd|intj}} 表示感嘆詞。縮寫一覽見 [[Template:head#詞性]]。 ;{{para|head}} : 詞頭,與頁面名稱不同時才需要填,例如標出聲調({{para|head|àbɔ̀frá}}),或為多詞詞條裡的各個詞加上連結。多個詞頭以逗號分隔,逗號後不加空格。 ;{{para|lect}} : 詞條所屬的方言,多個以逗號分隔。方言會顯示在標題行之後,並和 {{tl|tlb}} 一樣為詞條加上分類,因此不必另外使用 {{tl|tlb}}。 {| class="wikitable" ! 方言 !! 可以填的值 |- | 契維語 || <code>Twi</code>、<code>t</code>、<code>契維語</code>、<code>契維</code> |- | &nbsp;&nbsp;阿夸佩姆契維語 || <code>Akuapem</code>、<code>ak</code>、<code>阿夸佩姆契維語</code>、<code>阿夸佩姆</code> |- | &nbsp;&nbsp;阿散蒂契維語 || <code>Asante</code>、<code>as</code>、<code>阿散蒂契維語</code>、<code>阿散蒂</code> |- | 芳蒂語 || <code>Fante</code>、<code>f</code>、<code>芳蒂語</code>、<code>芳蒂</code> |} : 阿夸佩姆契維語與阿散蒂契維語是契維語的變體。詞條專屬於某個變體時填該變體(如 <code>lect=as</code>),否則填契維語。 ;{{para|id}}、{{para|sort}}、{{para|cat}}、{{para|nolink}}、{{para|var}}、{{para|abbr}} : 用法同 [[Module:headword utilities]]。{{para|cat}} 填不含語言名稱的分類名,以逗號分隔。 {{para|pl}} 等詞形參數可以填一個或多個詞形,以逗號分隔,逗號後不加空格,例如 <code>pl=<var>詞形1</var>,<var>詞形2</var></code>。<code>~</code> 代表頁面名稱。詞形可以帶行內修飾符:{{cd|<q:...>}} 與 {{cd|<qq:...>}} 表示前置與後置限定語,{{cd|<l:...>}} 與 {{cd|<ll:...>}} 表示前置與後置標籤,{{cd|<ref:...>}} 表示參考資料,例如 <code>pl=<var>詞形1</var>,<var>詞形2</var><nowiki><q:方言></nowiki></code>。詞形內若需要一個後面不接空格的逗號,寫作 <code>\,</code>。 ==有額外參數的詞類== {| class="wikitable" ! {{para|1}} !! 參數 |- | <code>名詞</code> || {{para|pl}} 複數(沒有複數填 <code>-</code>;「通常沒有複數」填 <code>-</code> 後接詞形) |- | <code>形容詞</code> || {{para|pl}} 複數、{{para|redup}} 重疊式、{{para|triplication}} 三疊式 |- | <code>副詞</code> || {{para|triplication}} 三疊式 |- | <code>動詞</code> || {{para|tpast}} 及物過去時、{{para|ipast}} 不及物過去時、{{para|neg}} 否定、{{para|fut}} 將來時、{{para|prog}} 進行時、{{para|pros}} 前瞻體、{{para|perf}} 完成時、{{para|imp}} 命令式、{{para|redup}} 重疊式 |} 其他詞類,例如 <code>專有名詞</code>、<code>代詞</code>、<code>名詞變格形</code>、<code>動詞變位形式</code>,只接受[[#參數|上一節]]列出的參數。 ==例子== {{demo|<nowiki>{{ak-head|名詞|head=àbɔ̀frá|pl=mmɔfra|lect=Twi|pagename=abɔfra}}</nowiki>}} {{demo|<nowiki>{{ak-head|名詞|pl=-|pagename=nsuo}}</nowiki>}} {{demo|<nowiki>{{ak-head|形容詞|redup=harehare|pagename=hare}}</nowiki>}} {{demo|<nowiki>{{ak-head|動詞|tpast=tuu|ipast=tuiɛ|neg=ntu|redup=tutu|lect=Twi|pagename=tu}}</nowiki>}} {{demo|<nowiki>{{ak-head|noun|pl=mmoa|lect=as|pagename=aboa}}</nowiki>}} 9cuijq9tk7vg82ql9xlwh6yqkgrw1of Category:漢語自謙用語 14 3546602 9943135 2026-10-12T23:32:52Z Fglffer 55252 新詞條 9943135 wikitext text/x-wiki {{auto cat}} eomzlm5v4j7ond1phrju7cnue91g5qx 駕籠 0 3546603 9943142 2026-10-12T23:41:08Z Fglffer 55252 新詞條 9943142 wikitext text/x-wiki ==日語== {{ja-kanjitab|か|こ|k2=ご|r=y|yomi=k|alt=駕篭}} [[File:Transportation Photoarrangement. (10796946264).jpg|thumb|right|200px|A kago with bearers]] ===發音=== {{ja-pron|かご|acc=0|acc_ref=ZAJ}} ===名詞=== {{ja-noun|かご}} # {{lb|ja|chiefly|_|historical}} 一種[[敞篷]][[轎子]] ===來源=== {{reflist}} :* {{R:Kanjipedia Kotoba|0000753000|〈<sup>▲</sup>駕籠〉}} {{C|ja|交通工具}} 1kpk2tux7fgxonimakhifbyo28yr8pb 9943143 9943142 2026-10-12T23:41:36Z Fglffer 55252 9943143 wikitext text/x-wiki ==日語== {{ja-kanjitab|か|こ|k2=ご|r=y|yomi=k|alt=駕篭}} [[File:Transportation Photoarrangement. (10796946264).jpg|thumb|right|200px]] ===發音=== {{ja-pron|かご|acc=0|acc_ref=ZAJ}} ===名詞=== {{ja-noun|かご}} # {{lb|ja|chiefly|_|historical}} 一種[[敞篷]][[轎子]] ===來源=== {{reflist}} :* {{R:Kanjipedia Kotoba|0000753000|〈<sup>▲</sup>駕籠〉}} {{C|ja|交通工具}} ltllwq8hyw255ethcndv3vvwmia6si9 敞篷 0 3546604 9943144 2026-10-12T23:42:43Z Fglffer 55252 新詞條 9943144 wikitext text/x-wiki ==漢語== {{zh-forms}} ===發音=== {{zh-pron |m=chǎngpéng |c=cong2 pung4 |w=sh:5tshan bon |cat=a }} ===形容詞=== {{head|zh|形容詞}} # {{lb|zh|attributive|用於交通工具}} [[沒有]][[頂部]]的 #: {{zh-x|敞篷 巴士}} ====衍生詞==== {{col3|zh|敞篷車}} 3ntnx6a9e7ohxos4o395uuti2eaw18b ぼうげつ 0 3546605 9943155 2026-10-12T23:55:11Z Fglffer 55252 新詞條 9943155 wikitext text/x-wiki ==日語== {{ja-see|望月}} fvwsy29vwy3anr5scgdbay3zm2wbrb8 clock face 0 3546606 9943160 2026-10-13T00:06:27Z Fglffer 55252 新詞條 9943160 wikitext text/x-wiki {{also|clockface|Clock Face}} ==英語== {{wp|en:}} [[File:AnalogClockAnimation1 2hands 12h in 1min.gif|thumb|This [[clock face]] shows the [[minute hand]] and the [[hour hand]] moving [[clockwise]].]] [[File:AnalogClockAnimation2 3hands 1h in realtime.gif|thumb|the ticking of a second hand]] ===發音=== * {{IPA|en|/klɒk feɪs/}} * {{audio|en|LL-Q1860 (eng)-Vealhurl-clock face.wav|a=Southern England}} ===名詞=== {{en-noun}} # [[鐘面]],[[錶盤]] #: {{alti|en|clockface}} #: {{mer|en|dial#Noun|;|hour hand|little hand|;|minute hand|big hand|;|second hand<id:hand showing seconds>}} # {{lb|en|較少用}} 任何時鐘的錶盤,不論是帶有指針還是數位式的 #: {{cot|en|watch face<id:Q122945059>|watchface<id:Q122945059>}} ====衍生詞==== * {{l|en|clock-face timetable}} {{C|en|時鐘}} fgqhhf7pt5vq4ijmd88kp4mnd5zhxfg 9943161 9943160 2026-10-13T00:06:40Z Fglffer 55252 9943161 wikitext text/x-wiki {{also|clockface|Clock Face}} ==英語== {{wp|en:}} ===發音=== * {{IPA|en|/klɒk feɪs/}} * {{audio|en|LL-Q1860 (eng)-Vealhurl-clock face.wav|a=Southern England}} ===名詞=== {{en-noun}} # [[鐘面]],[[錶盤]] #: {{alti|en|clockface}} #: {{mer|en|dial#Noun|;|hour hand|little hand|;|minute hand|big hand|;|second hand<id:hand showing seconds>}} # {{lb|en|較少用}} 任何時鐘的錶盤,不論是帶有指針還是數位式的 #: {{cot|en|watch face<id:Q122945059>|watchface<id:Q122945059>}} ====衍生詞==== * {{l|en|clock-face timetable}} {{C|en|時鐘}} jhzgjy50gqia84hai254o8s0fqe3mbp もちづき 0 3546607 9943164 2026-10-13T01:16:35Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==日語== {{ja-see|望月}}」的新頁面 9943164 wikitext text/x-wiki ==日語== {{ja-see|望月}} fvwsy29vwy3anr5scgdbay3zm2wbrb8 mochizuki 0 3546608 9943165 2026-10-13T01:16:38Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==日語== ===羅馬化=== {{ja-rom}} # {{ja-rom of|もちづき}}」的新頁面 9943165 wikitext text/x-wiki ==日語== ===羅馬化=== {{ja-rom}} # {{ja-rom of|もちづき}} 89yggfyaxffugzigqczlkoa8xji0ol6 clock faces 0 3546609 9943166 2026-10-13T01:16:54Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「{{also|clockfaces}} ==英語== ===名詞=== {{head|en|名詞變格形}} # {{plural of|en|clock face}}」的新頁面 9943166 wikitext text/x-wiki {{also|clockfaces}} ==英語== ===名詞=== {{head|en|名詞變格形}} # {{plural of|en|clock face}} o8zsks16oy46xukv0c0h0becduxh949 咿呀學語 0 3546610 9943169 2026-10-13T01:23:28Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==漢語== {{zh-forms|s=咿呀学语|type=211}} ===發音=== {{zh-pron |m=yīyāxuéyǔ |cat=v }} ===動詞=== {{zh-verb}} # [[嬰兒]]初學[[說話]] #: {{syn|zh|牙牙學語}}」的新頁面 9943169 wikitext text/x-wiki ==漢語== {{zh-forms|s=咿呀学语|type=211}} ===發音=== {{zh-pron |m=yīyāxuéyǔ |cat=v }} ===動詞=== {{zh-verb}} # [[嬰兒]]初學[[說話]] #: {{syn|zh|牙牙學語}} obmo67x751msleiyn2mwsrcx02tulj4 balbettato 0 3546611 9943171 2026-10-13T01:23:38Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|balbettare}}」的新頁面 9943171 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|balbettare}} gkio9h5as9vi3rlmwy5ned6rt9yn9tl balbettati 0 3546612 9943172 2026-10-13T01:24:09Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|balbettato}}」的新頁面 9943172 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|balbettato}} cx5qm9d4n7aoz4mff13ex5pjppwxuhj balbettata 0 3546613 9943173 2026-10-13T01:24:11Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|balbettato}}」的新頁面 9943173 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|balbettato}} 183m0osjq3ko4kkylex55qjyfefftot balbettate 0 3546614 9943174 2026-10-13T01:24:13Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===詞源1=== ====動詞==== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|p|pres|indc|;|2|p|impr}} ===詞源2=== ====分詞==== {{head|it|過去分詞變格形|g=f-p}} # {{feminine plural of|it|balbettato}}」的新頁面 9943174 wikitext text/x-wiki ==意大利語== ===詞源1=== ====動詞==== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|p|pres|indc|;|2|p|impr}} ===詞源2=== ====分詞==== {{head|it|過去分詞變格形|g=f-p}} # {{feminine plural of|it|balbettato}} db7zi6zdw5woi4z6fk96vj2ek1e0l7c balbettante 0 3546615 9943175 2026-10-13T01:24:38Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|balbettare}} ===形容詞=== {{it-adj}} # [[口吃]]的,[[結巴]]的,[[支支吾吾]]的」的新頁面 9943175 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|balbettare}} ===形容詞=== {{it-adj}} # [[口吃]]的,[[結巴]]的,[[支支吾吾]]的 4l15sabsyuc4opz651oxlnfhcnpxmc8 balbettanti 0 3546616 9943176 2026-10-13T01:24:51Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|balbettante}} ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|balbettante}}」的新頁面 9943176 wikitext text/x-wiki ==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|balbettante}} ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|balbettante}} dah1369yxn5cm0m2z084gy4dv4ucij8 beatifica 0 3546617 9943178 2026-10-13T01:31:45Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「{{also|beatificá|beatificà}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|s|pres|indc|;|2|s|impr}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificā}} # {{inflec…」的新頁面 9943178 wikitext text/x-wiki {{also|beatificá|beatificà}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|s|pres|indc|;|2|s|impr}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificā}} # {{inflection of|la|beātificō||2|s|pres|actv|impr}} ===形容詞=== {{head|la|形容詞變格形|head=beātifica}} # {{inflection of|la|beātificus||nom//voc|s|f}} ===參考資料=== * {{R:la:du Cange}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==羅馬尼亞語== ===詞源=== {{ety|ro|:bor|fr:béatifier|text=+|tree=1}} ===動詞=== {{ro-verb|beatifică|beatificat|beatifice}} # {{lb|ro|及物}} 使……[[蒙福]] # {{lb|ro|及物|天主教}} 為……行[[宣福禮]] ====變位==== {{ro-conj-a}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 2rmq0vbtvazzcuwxyeh425wxwt37hn7 조리개 0 3546618 9943179 2026-10-13T01:32:15Z 列维劳德 79702 /* */ 9943179 wikitext text/x-wiki ==朝鮮語== {{wp|ko:}} ===詞源=== {{com|ko|졸다|alt1=졸-|t1=締緊、收縮|-이-<type:suf>|pos2=被動/使動接綴|-개|pos3=表示工具或器具}}。 ===發音=== {{ko-IPA}} ===名詞=== {{ko-noun}} # {{lb|ko|photography}} [[光圈]] #: {{ux|ko|피사계의 심도는 렌즈의 '''조리개'''가 좁을수록 깊어진다.|'''光圈'''越小,景深越深。}} # [[束口袋]]的[[拉繩]]、[[束口扣]]、[[束帶]]、[[收口]][[裝置]] 1r1zw7q5n9cpe1rq9de6mfvaahecndj beatifico 0 3546619 9943180 2026-10-13T01:34:20Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「{{also|beatificó|beatificò}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|s|pres|indc}} ==拉丁語== ===詞源=== {{ety|la|title=beātificō|:af|beātus<t:幸福;幸運>|-ficō|text=…」的新頁面 9943180 wikitext text/x-wiki {{also|beatificó|beatificò}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|s|pres|indc}} ==拉丁語== ===詞源=== {{ety|la|title=beātificō|:af|beātus<t:幸福;幸運>|-ficō|text=+|tree=1}} ===發音=== * {{la-IPA|beātificō}} ===動詞=== {{la-verb|1+|beātificō}} # {{lb|la|及物}} 使……[[蒙福]] # {{lb|la|及物|天主教}} 為……行[[宣福禮]] ====變位==== {{la-conj|1+|beātificō}} ====派生語彙==== {{top2}} * {{desc|ca|beatificar|bor=1}} * {{desc|gl|beatificar|bor=1}} * {{desc|it|beatificare|bor=1}} * {{desc|pms|beatifiché|biatfché|bor=1}} * {{desc|fr|béatifier|bor=1}} * {{desc|en|beatify|bor=1}} * {{desc|pt|beatificar|bor=1}} * {{desc|ro|beatifica|bor=1}} * {{desc|scn|beatificari|bor=1}} * {{desc|es|beatificar|bor=1}} {{bottom}} ===參考資料=== * {{R:la:L&S}} * {{R:la:Gaffiot}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} gnmja8pzn27eqj3td6tztp0apcr78ym beatificante 0 3546620 9943181 2026-10-13T01:34:29Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|beatificare}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificante}} # {{inflection of|la|beātificāns||abl|m//f//n|s}}」的新頁面 9943181 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|beatificare}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificante}} # {{inflection of|la|beātificāns||abl|m//f//n|s}} rphjk190n68s762po53djeutforymy9 beatificato 0 3546621 9943182 2026-10-13T01:34:36Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|beatificare}} ===形容詞=== {{it-adj}} # 受[[賜福]]的 ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātō}} # {{inflection of|la|beātificō||2//3|s|futr|actv|impr}}」的新頁面 9943182 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|beatificare}} ===形容詞=== {{it-adj}} # 受[[賜福]]的 ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātō}} # {{inflection of|la|beātificō||2//3|s|futr|actv|impr}} ew98h0unlp27693dpt9ukzv5v57piyx beatificanti 0 3546622 9943183 2026-10-13T01:34:45Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|beatificante}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantī}} # {{inflection of|la|beātificāns||dat|m//f//n|s}}」的新頁面 9943183 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|beatificante}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantī}} # {{inflection of|la|beātificāns||dat|m//f//n|s}} 42cs0p8gdbv7vggehkp2k2p9x6qj7zc beatificata 0 3546623 9943184 2026-10-13T01:34:56Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|beatificato}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificāta}} # {{inflection of|la|beātificātus||nom//voc|f|s|;|nom//acc//voc|n|p}} ===分詞=== {{head|la|分詞變格形|head=beātificātā}} # {{inflection of|la|beātificātus||abl|f|s}}」的新頁面 9943184 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|beatificato}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificāta}} # {{inflection of|la|beātificātus||nom//voc|f|s|;|nom//acc//voc|n|p}} ===分詞=== {{head|la|分詞變格形|head=beātificātā}} # {{inflection of|la|beātificātus||abl|f|s}} 7tcyzlb2jzievsmiz8z3arjob2p6qq5 beatificati 0 3546624 9943185 2026-10-13T01:34:58Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|beatificato}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātī}} # {{inflection of|la|beātificātus||nom//voc|m|p|;|gen|m//n|s}}」的新頁面 9943185 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|beatificato}} ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātī}} # {{inflection of|la|beātificātus||nom//voc|m|p|;|gen|m//n|s}} s6o316ws77i0l494ln2kta0uygh08io beatificate 0 3546625 9943186 2026-10-13T01:35:40Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==英語== ===詞源=== {{bor+|en|la|beātificātus|beātificāt-}},{{m|la|beātificō||使……蒙福;祝福}} 的完成被動分詞詞幹。等價於 {{af|en|beatific|-ate|id2=動詞}}。 ===動詞=== {{en-verb}} # {{lb|en|棄用}} {{synonym of|en|beatify}} #* {{RQ:Fuller Church History|passage=It seemed good therefore to his Holiness , not to canonise Garnet for a solemn saint , much less for a martyr , but only to '''beatificate'…」的新頁面 9943186 wikitext text/x-wiki ==英語== ===詞源=== {{bor+|en|la|beātificātus|beātificāt-}},{{m|la|beātificō||使……蒙福;祝福}} 的完成被動分詞詞幹。等價於 {{af|en|beatific|-ate|id2=動詞}}。 ===動詞=== {{en-verb}} # {{lb|en|棄用}} {{synonym of|en|beatify}} #* {{RQ:Fuller Church History|passage=It seemed good therefore to his Holiness , not to canonise Garnet for a solemn saint , much less for a martyr , but only to '''beatificate''' him}} #* {{quote-book|en|year=1812|author=Charles Paul Landon|title=A Collection of Etchings{{...}}|passage=The composition, not offering any historical fact, but an assemblage of '''beatificated''' personages, who lived at different periods, it is needless to give an account of it.}} ===參考資料=== * {{R:Webster 1913}} ==意大利語== ===詞源1=== ====動詞==== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|p|pres|indc|;|2|p|impr}} ===詞源2=== ====分詞==== {{head|it|過去分詞變格形|g=f-p}} # {{feminine plural of|it|beatificato}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāte}} # {{inflection of|la|beātificō||2|p|pres|actv|impr}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 1dh68qnphs7xjzx8w2crznrua9degw2 beatificans 0 3546626 9943188 2026-10-13T01:37:34Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==拉丁語== ===分詞=== {{la-part|beātificāns}} # {{infl of|la|beātificō||pres|act|part}} ====變格==== {{la-adecl|beātificāns<3-P+>}}」的新頁面 9943188 wikitext text/x-wiki ==拉丁語== ===分詞=== {{la-part|beātificāns}} # {{infl of|la|beātificō||pres|act|part}} ====變格==== {{la-adecl|beātificāns<3-P+>}} 3ctnnw3ad0hm50visgmdgwugxmajz2k beatificaturus 0 3546627 9943189 2026-10-13T01:37:48Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==拉丁語== ===分詞=== {{la-part|beātificātūrus}} # {{infl of|la|beātificō||futr|act|part}} ====變格==== {{la-adecl|beātificātūrus}}」的新頁面 9943189 wikitext text/x-wiki ==拉丁語== ===分詞=== {{la-part|beātificātūrus}} # {{infl of|la|beātificō||futr|act|part}} ====變格==== {{la-adecl|beātificātūrus}} ohmpuv48jdmvsbo9fn4yvshorkkfy4y beatificandus 0 3546628 9943190 2026-10-13T01:37:59Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==拉丁語== ===分詞=== {{la-part|beātificandus}} # {{infl of|la|beātificō||futr|pass|part}} ====變格==== {{la-adecl|beātificandus}}」的新頁面 9943190 wikitext text/x-wiki ==拉丁語== ===分詞=== {{la-part|beātificandus}} # {{infl of|la|beātificō||futr|pass|part}} ====變格==== {{la-adecl|beātificandus}} sx5nqmx81af95xi5jq3pakig564nk92 beatificatus 0 3546629 9943191 2026-10-13T01:38:04Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==拉丁語== ===分詞=== {{la-part|beātificātus}} # {{infl of|la|beātificō||perf|pass|part}} ====變格==== {{la-adecl|beātificātus}}」的新頁面 9943191 wikitext text/x-wiki ==拉丁語== ===分詞=== {{la-part|beātificātus}} # {{infl of|la|beātificō||perf|pass|part}} ====變格==== {{la-adecl|beātificātus}} q37dt2m7vdo6dz65wwbdgx2fy8hgiqc balenieri 0 3546630 9943193 2026-10-13T01:39:01Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|baleniere}} ===形容詞=== {{head|it|形容詞變格形}} # {{masculine plural of|it|baleniero}} ===異序詞=== * {{anagrams|it|a=abeeiilnr|bilineare|liberiane}}」的新頁面 9943193 wikitext text/x-wiki ==意大利語== ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|baleniere}} ===形容詞=== {{head|it|形容詞變格形}} # {{masculine plural of|it|baleniero}} ===異序詞=== * {{anagrams|it|a=abeeiilnr|bilineare|liberiane}} iapkqqtmokkaq5nrjqqp4zhb2nhcp7s baleniera 0 3546631 9943194 2026-10-13T01:39:11Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== {{wp|it:}} ===發音=== {{it-pr}} ===名詞=== {{it-noun|f}} # [[捕鯨船]] ====相關詞彙==== * {{l|it|balena}} * {{l|it|baleniere}} ===延伸閱讀=== {{R:it:Treccani|baleniera}} ===異序詞=== * {{anagrams|it|a=aabeeilnr|balenerai|baleneria}} {{C|it|船}}」的新頁面 9943194 wikitext text/x-wiki ==意大利語== {{wp|it:}} ===發音=== {{it-pr}} ===名詞=== {{it-noun|f}} # [[捕鯨船]] ====相關詞彙==== * {{l|it|balena}} * {{l|it|baleniere}} ===延伸閱讀=== {{R:it:Treccani|baleniera}} ===異序詞=== * {{anagrams|it|a=aabeeilnr|balenerai|baleneria}} {{C|it|船}} fcu2sq3i8cgwogv15trmjbqbbsissbm banchieri 0 3546632 9943196 2026-10-13T01:40:11Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「{{also|Banchieri}} ==意大利語== ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|banchiere}} ===異序詞=== * {{anagrams|it|a=abcehiinr|bianchire}}」的新頁面 9943196 wikitext text/x-wiki {{also|Banchieri}} ==意大利語== ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|banchiere}} ===異序詞=== * {{anagrams|it|a=abcehiinr|bianchire}} btke97owbku85w9hxf7knzxvishe04c bloccanti 0 3546633 9943199 2026-10-13T01:42:02Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|bloccante}} ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|bloccante}}」的新頁面 9943199 wikitext text/x-wiki ==意大利語== ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|bloccante}} ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|bloccante}} gzdvv2gdp1oeish4hia565pys187vr1 borbottante 0 3546634 9943201 2026-10-13T01:44:43Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|borbottare}}」的新頁面 9943201 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|borbottare}} 4hv7gebfhbr5b9cjxnm4ivom4451uuf borbottato 0 3546635 9943202 2026-10-13T01:44:45Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|borbottare}}」的新頁面 9943202 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|borbottare}} psnyo9up4cw7v1gfqh0gqwgdwane81c borbottanti 0 3546636 9943204 2026-10-13T01:44:59Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|borbottante}}」的新頁面 9943204 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|borbottante}} ei7e2dw0kxelc363jcsl5uwnk0nbl1a borbottata 0 3546637 9943205 2026-10-13T01:45:02Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|borbottato}}」的新頁面 9943205 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|borbottato}} hoys753neq2p1hrwl3aru22f1b3yfew borbottati 0 3546638 9943206 2026-10-13T01:45:05Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|borbottato}}」的新頁面 9943206 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|borbottato}} pyi02dd8ouuera3b0okc0oaoyk76gcq borbottate 0 3546639 9943207 2026-10-13T01:45:07Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===詞源1=== ====動詞==== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|p|pres|indc|;|2|p|impr}} ===詞源2=== ====分詞==== {{head|it|過去分詞變格形|g=f-p}} # {{feminine plural of|it|borbottato}}」的新頁面 9943207 wikitext text/x-wiki ==意大利語== ===詞源1=== ====動詞==== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|p|pres|indc|;|2|p|impr}} ===詞源2=== ====分詞==== {{head|it|過去分詞變格形|g=f-p}} # {{feminine plural of|it|borbottato}} a0pcnoibxtgcamlrww4zw9educ4jpc5 brancolante 0 3546640 9943209 2026-10-13T01:46:13Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|brancolare}}」的新頁面 9943209 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-presp}} # {{present participle of|it|brancolare}} e4m3c8yef73r9bxb9yds925s14t9a5u brancolato 0 3546641 9943210 2026-10-13T01:46:14Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|brancolare}}」的新頁面 9943210 wikitext text/x-wiki ==意大利語== ===分詞=== {{it-pp}} # {{past participle of|it|brancolare}} r2heaae5iylkof98vnnqj5tcw3t05ny brancolanti 0 3546642 9943211 2026-10-13T01:46:25Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|brancolante}}」的新頁面 9943211 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|分詞變格形}} # {{plural of|it|brancolante}} etkkryh73lyu2a79biba6buglw4vqlc brancolata 0 3546643 9943212 2026-10-13T01:46:29Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|brancolato}}」的新頁面 9943212 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=f-s}} # {{feminine singular of|it|brancolato}} l8ry59em6c44ebpa9jk9a8670s07hdq brancolati 0 3546644 9943213 2026-10-13T01:46:38Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|brancolato}}」的新頁面 9943213 wikitext text/x-wiki ==意大利語== ===分詞=== {{head|it|過去分詞變格形|g=m-p}} # {{masculine plural of|it|brancolato}} h8vdet6dq8topiiaa7qya96gcjmypbq brancolate 0 3546645 9943214 2026-10-13T01:46:39Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===詞源1=== ====動詞==== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|p|pres|indc|;|2|p|impr}} ===詞源2=== ====分詞==== {{head|it|過去分詞變格形|g=f-p}} # {{feminine plural of|it|brancolato}}」的新頁面 9943214 wikitext text/x-wiki ==意大利語== ===詞源1=== ====動詞==== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|p|pres|indc|;|2|p|impr}} ===詞源2=== ====分詞==== {{head|it|過去分詞變格形|g=f-p}} # {{feminine plural of|it|brancolato}} bjfaxdqot6ae52czw5q0k3xp9cd4d4s balbetta 0 3546646 9943217 2026-10-13T01:47:47Z TongcyBot 83009 半自動導入非詞元形式 9943217 wikitext text/x-wiki ==意大利語== ===發音=== {{it-pr}} ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|s|pres|indc|;|2|s|impr}} e712cea0hgxe97sfkarhxyjgfojdi6c balbettai 0 3546647 9943218 2026-10-13T01:47:48Z TongcyBot 83009 半自動導入非詞元形式 9943218 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|s|phis}} ===異序詞=== * {{anagrams|it|a=aabbeiltt|abbattile}} gq6jnlqusnlartm5dc40o0iuach1y5n balbettammo 0 3546648 9943219 2026-10-13T01:47:49Z TongcyBot 83009 半自動導入非詞元形式 9943219 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|p|phis}} doug6oanj4c7kn3w8ao6fle8gkmf1lh balbettando 0 3546649 9943220 2026-10-13T01:47:50Z TongcyBot 83009 半自動導入非詞元形式 9943220 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動名詞}} # {{gerund of|it|balbettare}} hplv8tht97q4bsy8ok04365z83r1bff balbettano 0 3546650 9943221 2026-10-13T01:47:51Z TongcyBot 83009 半自動導入非詞元形式 9943221 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|p|pres|indc}} 9ho9ox58bntbvoaapwd848xzuy2wc0j balbettarono 0 3546651 9943222 2026-10-13T01:47:52Z TongcyBot 83009 半自動導入非詞元形式 9943222 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|p|phis}} 96bxt44ay62ea0pa7l2eebwcc0mhji5 balbettasse 0 3546652 9943223 2026-10-13T01:47:54Z TongcyBot 83009 半自動導入非詞元形式 9943223 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|s|impf|subj}} 26ixkv243yjwfyxraikesaeisnsbuf0 balbettassero 0 3546653 9943224 2026-10-13T01:47:56Z TongcyBot 83009 半自動導入非詞元形式 9943224 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|p|impf|subj}} ci7ic5jj2u3l2gncurwg3slbpm47k24 balbettassi 0 3546654 9943225 2026-10-13T01:47:57Z TongcyBot 83009 半自動導入非詞元形式 9943225 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1//2|s|impf|subj}} fzf5tlilss5m7ezp79hk20w1n5coj52 balbettassimo 0 3546655 9943226 2026-10-13T01:47:58Z TongcyBot 83009 半自動導入非詞元形式 9943226 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|p|impf|subj}} 61ogzr9xa829owrpcsg0fjzswl8d3ue balbettaste 0 3546656 9943227 2026-10-13T01:47:59Z TongcyBot 83009 半自動導入非詞元形式 9943227 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|p|phis|;|2|p|impf|subj}} 2gsoccogyvwkhfli0t1l83202liy6g2 balbettasti 0 3546657 9943228 2026-10-13T01:48:00Z TongcyBot 83009 半自動導入非詞元形式 9943228 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|s|phis}} s1zr5jkash88gc1vsbc2w4232w8cdvk balbettava 0 3546658 9943230 2026-10-13T01:48:01Z TongcyBot 83009 半自動導入非詞元形式 9943230 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|s|impf|indc}} djy4uefflbuxydt4a6uwtbi5irjy93m balbettavamo 0 3546659 9943231 2026-10-13T01:48:02Z TongcyBot 83009 半自動導入非詞元形式 9943231 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|p|impf|indc}} 11wjaq58gye6wqrnxtdasnnwhd172hs balbettavano 0 3546660 9943232 2026-10-13T01:48:03Z TongcyBot 83009 半自動導入非詞元形式 9943232 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|p|impf|indc}} 9gt5ji79mfa88ll290co5bjysx8anyl balbettavate 0 3546661 9943233 2026-10-13T01:48:04Z TongcyBot 83009 半自動導入非詞元形式 9943233 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|p|impf|indc}} ik0q8ddcocb8c1cpt6wo5huc80v9h72 brasiliana 0 3546662 9943234 2026-10-13T01:48:04Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===發音=== {{it-pr}} ===形容詞=== {{head|it|形容詞變格形}} # {{feminine singular of|it|brasiliano}} ===名詞=== {{it-noun|f}} # {{female equivalent of|it|brasiliano}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliāna}} # {{inflection of|la|brasiliānus||nom//voc|f|s|;|nom//acc//voc|n|p}} ===形容詞=== {{head|la|形容詞變格形|head=brasiliānā}} # {{inflection of|la|brasil…」的新頁面 9943234 wikitext text/x-wiki ==意大利語== ===發音=== {{it-pr}} ===形容詞=== {{head|it|形容詞變格形}} # {{feminine singular of|it|brasiliano}} ===名詞=== {{it-noun|f}} # {{female equivalent of|it|brasiliano}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliāna}} # {{inflection of|la|brasiliānus||nom//voc|f|s|;|nom//acc//voc|n|p}} ===形容詞=== {{head|la|形容詞變格形|head=brasiliānā}} # {{inflection of|la|brasiliānus||abl|f|s}} ==葡萄牙語== ===發音=== {{pt-IPA}} * {{rhyme|pt|ɐnɐ|s=5}} * {{hyph|pt|bra|si|li|a|na}} ===形容詞=== {{head|pt|形容詞變格形}} # {{feminine singular of|pt|brasiliano}} ===名詞=== {{head|pt|名詞變格形}} # {{female equivalent of|pt|brasiliano}} bmo1rp0mte8e4q2w109no1hjx1nwm9p balbettavi 0 3546663 9943235 2026-10-13T01:48:05Z TongcyBot 83009 半自動導入非詞元形式 9943235 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|s|impf|indc}} 9c9w6zypoh2rfplbchw2h9muil7ub03 brasiliani 0 3546664 9943236 2026-10-13T01:48:06Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{masculine plural of|it|brasiliano}} ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|brasiliano}} ===異序詞=== * {{anagrams|it|a=aabiiilnrs|risanabili}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliānī}} # {{inflection of|la|brasiliānus||nom//voc|m|p|;|gen|m//n|s}}」的新頁面 9943236 wikitext text/x-wiki ==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{masculine plural of|it|brasiliano}} ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|brasiliano}} ===異序詞=== * {{anagrams|it|a=aabiiilnrs|risanabili}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliānī}} # {{inflection of|la|brasiliānus||nom//voc|m|p|;|gen|m//n|s}} d0lakss6xc5my6fq63p4k9x8zzdnjfp balbettavo 0 3546665 9943237 2026-10-13T01:48:06Z TongcyBot 83009 半自動導入非詞元形式 9943237 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|s|impf|indc}} jl64myigxuctobeeuf1lg589rpmk32t balbetterai 0 3546666 9943238 2026-10-13T01:48:07Z TongcyBot 83009 半自動導入非詞元形式 9943238 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|s|futr}} 9zfbjdsgljyea95p6165gek6mvvg500 brasiliane 0 3546667 9943239 2026-10-13T01:48:07Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{feminine plural of|it|brasiliano}} ===名詞=== {{head|it|名詞變格形|g=f}} # {{plural of|it|brasiliana}} ===異序詞=== * {{anagrams|it|a=aabeiilnrs|risanabile}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliāne}} # {{inflection of|la|brasiliānus||voc|m|s}}」的新頁面 9943239 wikitext text/x-wiki ==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{feminine plural of|it|brasiliano}} ===名詞=== {{head|it|名詞變格形|g=f}} # {{plural of|it|brasiliana}} ===異序詞=== * {{anagrams|it|a=aabeiilnrs|risanabile}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliāne}} # {{inflection of|la|brasiliānus||voc|m|s}} n74a5ok8h2p7fn4ryr23s2nj9c2ixi5 balbetteranno 0 3546668 9943240 2026-10-13T01:48:08Z TongcyBot 83009 半自動導入非詞元形式 9943240 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|p|futr}} 0m9ij2ff69vs89hr650z4guv7o3lvcd balbetterebbe 0 3546669 9943241 2026-10-13T01:48:09Z TongcyBot 83009 半自動導入非詞元形式 9943241 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|s|cond}} g258lqdengl7yfm2nzarfqmsxzue1n6 balbetterebbero 0 3546670 9943242 2026-10-13T01:48:10Z TongcyBot 83009 半自動導入非詞元形式 9943242 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|p|cond}} 0symrivw8jc04lw2045oiif5glgafe6 balbetterei 0 3546671 9943243 2026-10-13T01:48:11Z TongcyBot 83009 半自動導入非詞元形式 9943243 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|s|cond}} ===異序詞=== * {{anagrams|it|a=abbeeeilrtt|latiterebbe}} 123bmitslhyb4q9pi9l36n6zyyrd0vd balbetteremmo 0 3546672 9943244 2026-10-13T01:48:12Z TongcyBot 83009 半自動導入非詞元形式 9943244 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|p|cond}} bo2rty4w8zq6pcsxu4ay7ln095mobx3 balbetteremo 0 3546673 9943245 2026-10-13T01:48:13Z TongcyBot 83009 半自動導入非詞元形式 9943245 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|p|futr}} f0dxq10zw00j0v22wb9oqqodpahta2x balbettereste 0 3546674 9943246 2026-10-13T01:48:14Z TongcyBot 83009 半自動導入非詞元形式 9943246 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|p|cond}} pd13e5hqgcm4dgwyn7k9udyxl1erts3 balbetteresti 0 3546675 9943247 2026-10-13T01:48:16Z TongcyBot 83009 半自動導入非詞元形式 9943247 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|s|cond}} imudmdwjw6lsec9pnq1n6211fjdyq8j balbetterete 0 3546676 9943248 2026-10-13T01:48:16Z TongcyBot 83009 半自動導入非詞元形式 9943248 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|p|futr}} 2zu6ux57rccn24tmvnsr8r9lynup4l2 balbetterà 0 3546677 9943249 2026-10-13T01:48:17Z TongcyBot 83009 半自動導入非詞元形式 9943249 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|s|futr}} ===異序詞=== * {{anagrams|it|a=aabbeelrtt|abbatterle|balbettare}} icqfyb4islmsamj7ksrmdowlzd5fuaf balbetterò 0 3546678 9943250 2026-10-13T01:48:20Z TongcyBot 83009 半自動導入非詞元形式 9943250 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|s|futr}} ioerqui8h0z1z8gzhe56sdra1swjq2m balbetti 0 3546679 9943251 2026-10-13T01:48:21Z TongcyBot 83009 半自動導入非詞元形式 9943251 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|s|pres|indc|;|1//2//3|s|pres|subj|;|3|s|impr}} m7jfxd4i7ggmimqi3dxn3jz3faap7la balbettiamo 0 3546680 9943252 2026-10-13T01:48:22Z TongcyBot 83009 半自動導入非詞元形式 9943252 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|p|pres|ind//sub|;|1|p|impr}} 6tldyu8wdwlmq27utedkxo3vbk4gi11 balbettiate 0 3546681 9943253 2026-10-13T01:48:23Z TongcyBot 83009 半自動導入非詞元形式 9943253 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||2|p|pres|subj}} n7tde7lg5rc8oo0f2ec8kkqp9o437l3 balbettino 0 3546682 9943254 2026-10-13T01:48:24Z TongcyBot 83009 半自動導入非詞元形式 9943254 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|p|pres|subj|;|3|p|impr}} pahi5j1yisrty3bn5a32mhjanggbkpx balbetto 0 3546683 9943255 2026-10-13T01:48:25Z TongcyBot 83009 半自動導入非詞元形式 9943255 wikitext text/x-wiki {{also|balbettò}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||1|s|pres|indc}} jjuac3p963z6nnumzhacmz4hyaykev5 balbettò 0 3546684 9943256 2026-10-13T01:48:26Z TongcyBot 83009 半自動導入非詞元形式 9943256 wikitext text/x-wiki {{also|balbetto}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|balbettare||3|s|phis}} s04o4z9eb88ey4kane1lp0nmjsldtlb beatificaba 0 3546685 9943257 2026-10-13T01:48:27Z TongcyBot 83009 半自動導入非詞元形式 9943257 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificabades 0 3546686 9943258 2026-10-13T01:48:28Z TongcyBot 83009 半自動導入非詞元形式 9943258 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificabais 0 3546687 9943259 2026-10-13T01:48:29Z TongcyBot 83009 半自動導入非詞元形式 9943259 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p brasilianos 0 3546688 9943260 2026-10-13T01:48:29Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliānōs}} # {{inflection of|la|brasiliānus||acc|m|p}} ==葡萄牙語== ===名詞=== {{head|pt|名詞變格形}} # {{plural of|pt|brasiliano}}」的新頁面 9943260 wikitext text/x-wiki ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliānōs}} # {{inflection of|la|brasiliānus||acc|m|p}} ==葡萄牙語== ===名詞=== {{head|pt|名詞變格形}} # {{plural of|pt|brasiliano}} 5zy3zfrqo5zu8oxhykyk4f1y7m741gm beatificabam 0 3546689 9943261 2026-10-13T01:48:30Z TongcyBot 83009 半自動導入非詞元形式 9943261 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābam}} # {{inflection of|la|beātificō||1|s|impf|actv|indc}} q5imtqszqqrgrrabzxqd6r7u7n5y1zo beatificabamini 0 3546690 9943262 2026-10-13T01:48:31Z TongcyBot 83009 半自動導入非詞元形式 9943262 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābāminī}} # {{inflection of|la|beātificō||2|p|impf|pasv|indc}} o1mto7o2dmtobiklh2w6wn62iozf0k1 brasilianas 0 3546691 9943263 2026-10-13T01:48:32Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliānās}} # {{inflection of|la|brasiliānus||acc|f|p}} ==葡萄牙語== ===名詞=== {{head|pt|名詞變格形}} # {{plural of|pt|brasiliana}}」的新頁面 9943263 wikitext text/x-wiki ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=brasiliānās}} # {{inflection of|la|brasiliānus||acc|f|p}} ==葡萄牙語== ===名詞=== {{head|pt|名詞變格形}} # {{plural of|pt|brasiliana}} aaab3womfug0a3sm6czsrzin86b3iwn beatificabamos 0 3546692 9943264 2026-10-13T01:48:33Z TongcyBot 83009 半自動導入非詞元形式 9943264 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificabamur 0 3546693 9943265 2026-10-13T01:48:34Z TongcyBot 83009 半自動導入非詞元形式 9943265 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābāmur}} # {{inflection of|la|beātificō||1|p|impf|pasv|indc}} bjv0hp6705147l43p8n95fohfnv7rz0 beatificabamus 0 3546694 9943266 2026-10-13T01:48:35Z TongcyBot 83009 半自動導入非詞元形式 9943266 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābāmus}} # {{inflection of|la|beātificō||1|p|impf|actv|indc}} aziokj3isdy44gsj7lrh8n9w7za6bj3 beatificaban 0 3546695 9943267 2026-10-13T01:48:36Z TongcyBot 83009 半自動導入非詞元形式 9943267 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificabant 0 3546696 9943268 2026-10-13T01:48:37Z TongcyBot 83009 半自動導入非詞元形式 9943268 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābant}} # {{inflection of|la|beātificō||3|p|impf|actv|indc}} ifrebscgy8e5osk2gdu0lo1cer6tm3r beatificabantur 0 3546697 9943269 2026-10-13T01:48:38Z TongcyBot 83009 半自動導入非詞元形式 9943269 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābantur}} # {{inflection of|la|beātificō||3|p|impf|pasv|indc}} j2wv9ju49cy1t0l9q4axblvri4nsdpg beatificabar 0 3546698 9943270 2026-10-13T01:48:39Z TongcyBot 83009 半自動導入非詞元形式 9943270 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābar}} # {{inflection of|la|beātificō||1|s|impf|pasv|indc}} mmspywh435w9zbjcu76egmkqhtjyqi7 beatificabare 0 3546699 9943271 2026-10-13T01:48:40Z TongcyBot 83009 半自動導入非詞元形式 9943271 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābāre}} # {{inflection of|la|beātificō||2|s|impf|pasv|indc}} s0fic6vxyb0jjxpg7tk7vw2j0dva6io beatificabaris 0 3546700 9943272 2026-10-13T01:48:41Z TongcyBot 83009 半自動導入非詞元形式 9943272 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābāris}} # {{inflection of|la|beātificō||2|s|impf|pasv|indc}} ejhl2suwi8cbajj8hr3ilpcybk3ljdg beatificabas 0 3546701 9943273 2026-10-13T01:48:42Z TongcyBot 83009 半自動導入非詞元形式 9943273 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābās}} # {{inflection of|la|beātificō||2|s|impf|actv|indc}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} ehime6k0wfw9sygoshwzho410ykh5sy beatificabat 0 3546702 9943274 2026-10-13T01:48:43Z TongcyBot 83009 半自動導入非詞元形式 9943274 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābat}} # {{inflection of|la|beātificō||3|s|impf|actv|indc}} hd5273pnkn45eecfhg7s65zkm3jcanc beatificabatis 0 3546703 9943275 2026-10-13T01:48:44Z TongcyBot 83009 半自動導入非詞元形式 9943275 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābātis}} # {{inflection of|la|beātificō||2|p|impf|actv|indc}} gsb1k3o5oal7tv3kwume8vj3gymgy1u beatificabatur 0 3546704 9943276 2026-10-13T01:48:45Z TongcyBot 83009 半自動導入非詞元形式 9943276 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābātur}} # {{inflection of|la|beātificō||3|s|impf|pasv|indc}} tkbi78ug58m7oruv511hv60ek4x2q25 beatificabere 0 3546705 9943277 2026-10-13T01:48:47Z TongcyBot 83009 半自動導入非詞元形式 9943277 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābere}} # {{inflection of|la|beātificō||2|s|futr|pasv|indc}} 7gah3gs89f54swayadlpihq1bghac6r beatificaberis 0 3546706 9943278 2026-10-13T01:48:48Z TongcyBot 83009 半自動導入非詞元形式 9943278 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāberis}} # {{inflection of|la|beātificō||2|s|futr|pasv|indc}} q9cosgcgy5saxzavgwaoswj0t3p7rz4 beatificabimini 0 3546707 9943279 2026-10-13T01:48:49Z TongcyBot 83009 半自動導入非詞元形式 9943279 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābiminī}} # {{inflection of|la|beātificō||2|p|futr|pasv|indc}} q47j2aqpn3j7pffssp128vzj9jqfkdf beatificabimur 0 3546708 9943280 2026-10-13T01:48:50Z TongcyBot 83009 半自動導入非詞元形式 9943280 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābimur}} # {{inflection of|la|beātificō||1|p|futr|pasv|indc}} jg7pkbtyec07bua055b4vdadn6fsj6o beatificabimus 0 3546709 9943281 2026-10-13T01:48:51Z TongcyBot 83009 半自動導入非詞元形式 9943281 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābimus}} # {{inflection of|la|beātificō||1|p|futr|actv|indc}} f5nybc1g777gh9zxaqor5djmztssbr7 beatificabis 0 3546710 9943282 2026-10-13T01:48:52Z TongcyBot 83009 半自動導入非詞元形式 9943282 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābis}} # {{inflection of|la|beātificō||2|s|futr|actv|indc}} 4f7a1qm9jdqx5b6vxy8j16wbybpgsps beatificabit 0 3546711 9943283 2026-10-13T01:48:53Z TongcyBot 83009 半自動導入非詞元形式 9943283 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābit}} # {{inflection of|la|beātificō||3|s|futr|actv|indc}} o5hvxiv6k07zjwkpqtv5mn54bd9exjy beatificabitis 0 3546712 9943284 2026-10-13T01:48:54Z TongcyBot 83009 半自動導入非詞元形式 9943284 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābitis}} # {{inflection of|la|beātificō||2|p|futr|actv|indc}} r3t8pslzgekmu59ze1i50ypumgbesbm beatificabitur 0 3546713 9943285 2026-10-13T01:48:55Z TongcyBot 83009 半自動導入非詞元形式 9943285 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābitur}} # {{inflection of|la|beātificō||3|s|futr|pasv|indc}} d8jjnr1sq3zls639pg5ksenu06q4evh beatificabo 0 3546714 9943286 2026-10-13T01:48:56Z TongcyBot 83009 半自動導入非詞元形式 9943286 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābō}} # {{inflection of|la|beātificō||1|s|futr|actv|indc}} ocvs87v53kpm0n7h69rraootaf23y8g beatificabor 0 3546715 9943287 2026-10-13T01:48:57Z TongcyBot 83009 半自動導入非詞元形式 9943287 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābor}} # {{inflection of|la|beātificō||1|s|futr|pasv|indc}} 92msmf4cusllgv3arkefqw3uhvv79cq beatificabunt 0 3546716 9943288 2026-10-13T01:48:58Z TongcyBot 83009 半自動導入非詞元形式 9943288 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābunt}} # {{inflection of|la|beātificō||3|p|futr|actv|indc}} a7n86sc2n0h608ozjy22w4zvoqkvl6c beatificabuntur 0 3546717 9943289 2026-10-13T01:49:00Z TongcyBot 83009 半自動導入非詞元形式 9943289 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificābuntur}} # {{inflection of|la|beātificō||3|p|futr|pasv|indc}} 1uld2t0a01u15mnalpkw0x6d7iypuc8 beatificaches 0 3546718 9943290 2026-10-13T01:49:01Z TongcyBot 83009 半自動導入非詞元形式 9943290 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificad 0 3546719 9943291 2026-10-13T01:49:02Z TongcyBot 83009 半自動導入非詞元形式 9943291 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificada 0 3546720 9943292 2026-10-13T01:49:03Z TongcyBot 83009 半自動導入非詞元形式 9943292 wikitext text/x-wiki ==加泰羅尼亞語== ===發音=== * {{ca-IPA}} ===分詞=== {{head|ca|過去分詞變格形|g=f-s}} # {{feminine singular of|ca|beatificat}} ==葡萄牙語== ===分詞=== {{head|pt|過去分詞變格形|g=f-s}} # {{feminine singular of|pt|beatificado}} ==西班牙語== ===發音=== {{es-pr}} ===分詞=== {{head|es|過去分詞變格形|g=f-s}} # {{feminine singular of|es|beatificado}} sxyeopluw9nnpp0ttgav1tiu0z8dteu beatificadas 0 3546721 9943293 2026-10-13T01:49:04Z TongcyBot 83009 半自動導入非詞元形式 9943293 wikitext text/x-wiki ==葡萄牙語== ===分詞=== {{head|pt|過去分詞變格形|g=f-p}} # {{feminine plural of|pt|beatificado}} ==西班牙語== ===發音=== {{es-pr}} ===分詞=== {{head|es|過去分詞變格形|g=f-p}} # {{feminine plural of|es|beatificado}} 22hbha1m5srfrv4916auj36fdlygli0 beatificade 0 3546722 9943294 2026-10-13T01:49:05Z TongcyBot 83009 半自動導入非詞元形式 9943294 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificades 0 3546723 9943295 2026-10-13T01:49:06Z TongcyBot 83009 半自動導入非詞元形式 9943295 wikitext text/x-wiki ==加泰羅尼亞語== ===發音=== * {{ca-IPA}} ===分詞=== {{head|ca|過去分詞變格形|g=f-p}} # {{feminine plural of|ca|beatificat}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 02048owdth5dkaa21il1m48nbl5uiaw beatificado 0 3546724 9943296 2026-10-13T01:49:07Z TongcyBot 83009 半自動導入非詞元形式 9943296 wikitext text/x-wiki ==加利西亞語== ===分詞=== {{gl-pp}} # {{past participle of|gl|beatificar}} ==葡萄牙語== ===分詞=== {{pt-pp}} # {{past participle of|pt|beatificar}} ==西班牙語== ===發音=== {{es-pr}} ===分詞=== {{es-past participle}} # {{past participle of|es|beatificar}} sg92uau0g58btqy5ruqsa8wzkaovdwp beatificados 0 3546725 9943297 2026-10-13T01:49:08Z TongcyBot 83009 半自動導入非詞元形式 9943297 wikitext text/x-wiki ==葡萄牙語== ===分詞=== {{head|pt|過去分詞變格形|g=m-p}} # {{masculine plural of|pt|beatificado}} ==西班牙語== ===發音=== {{es-pr}} ===分詞=== {{head|es|過去分詞變格形|g=m-p}} # {{masculine plural of|es|beatificado}} 5ikt1lkwxnlb72snwj5tar03440nogv beatificai 0 3546726 9943298 2026-10-13T01:49:09Z TongcyBot 83009 半自動導入非詞元形式 9943298 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|s|phis}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} 0v2owa61h4o0u1po3er59dwlp4lv7by beatificais 0 3546727 9943299 2026-10-13T01:49:10Z TongcyBot 83009 半自動導入非詞元形式 9943299 wikitext text/x-wiki {{also|beatificáis}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} 65jjvczv22dduk6s6am54w72gla1lqo beatificam 0 3546728 9943300 2026-10-13T01:49:12Z TongcyBot 83009 半自動導入非詞元形式 9943300 wikitext text/x-wiki ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=beātificam}} # {{inflection of|la|beātificus||acc|f|s}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} f58igdp82w7585ehv8eihbmka1ww76g beatificamini 0 3546729 9943301 2026-10-13T01:49:13Z TongcyBot 83009 半自動導入非詞元形式 9943301 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāminī}} # {{inflection of|la|beātificō||2|p|pres|pasv|ind//imp}} l4yhqdv6t6pggzj3hqk7rj274ha4vtm beatificammo 0 3546730 9943302 2026-10-13T01:49:14Z TongcyBot 83009 半自動導入非詞元形式 9943302 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|p|phis}} fk6azodqgwvxefimni01ms0gynm9njw beatificamos 0 3546731 9943303 2026-10-13T01:49:15Z TongcyBot 83009 半自動導入非詞元形式 9943303 wikitext text/x-wiki {{also|beatificámos}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 0czr2b2blk62jygvplein0ozy9yhk1w beatificamur 0 3546732 9943304 2026-10-13T01:49:16Z TongcyBot 83009 半自動導入非詞元形式 9943304 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāmur}} # {{inflection of|la|beātificō||1|p|pres|pasv|indc}} gqwbsu0g0s9ac3zg7z6rwyo6ulyu4oc beatificamus 0 3546733 9943305 2026-10-13T01:49:17Z TongcyBot 83009 半自動導入非詞元形式 9943305 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāmus}} # {{inflection of|la|beātificō||1|p|pres|actv|indc}} gf8hhcl0v12oxzfko4cg36l46tv3pcq beatifican 0 3546734 9943306 2026-10-13T01:49:18Z TongcyBot 83009 半自動導入非詞元形式 9943306 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificanda 0 3546735 9943307 2026-10-13T01:49:19Z TongcyBot 83009 半自動導入非詞元形式 9943307 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificanda}} # {{inflection of|la|beātificandus||nom//voc|f|s|;|nom//acc//voc|n|p}} ===分詞=== {{head|la|分詞變格形|head=beātificandā}} # {{inflection of|la|beātificandus||abl|f|s}} 8wwyrn3w1wjosckjvyzlmtjkvz700lg beatificandae 0 3546736 9943308 2026-10-13T01:49:20Z TongcyBot 83009 半自動導入非詞元形式 9943308 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificandae}} # {{inflection of|la|beātificandus||nom//voc|f|p|;|gen//dat|f|s}} dkoqwag271kbh1igxzacbaetsppper1 beatificandam 0 3546737 9943309 2026-10-13T01:49:21Z TongcyBot 83009 半自動導入非詞元形式 9943309 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificandam}} # {{inflection of|la|beātificandus||acc|f|s}} tp6s6pxpxzcqpqusslsk8fz4mpotwve beatificandarum 0 3546738 9943310 2026-10-13T01:49:22Z TongcyBot 83009 半自動導入非詞元形式 9943310 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificandārum}} # {{inflection of|la|beātificandus||gen|f|p}} sjuza5l21rsmm160wdv0z2fgzeb0wgv beatificandas 0 3546739 9943311 2026-10-13T01:49:24Z TongcyBot 83009 半自動導入非詞元形式 9943311 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificandās}} # {{inflection of|la|beātificandus||acc|f|p}} re2n52d7oz2914k8j669s1ysy1w54mk beatificande 0 3546740 9943312 2026-10-13T01:49:25Z TongcyBot 83009 半自動導入非詞元形式 9943312 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificande}} # {{inflection of|la|beātificandus||voc|m|s}} qydr0u9w95o6avxgvxbyucm2cs2xn0m beatificandi 0 3546741 9943313 2026-10-13T01:49:26Z TongcyBot 83009 半自動導入非詞元形式 9943313 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificandī}} # {{inflection of|la|beātificō||gen|動名詞}} f3d8psb5xubzm9ftqz3bc6na29s9noj beatificandis 0 3546742 9943314 2026-10-13T01:49:27Z TongcyBot 83009 半自動導入非詞元形式 9943314 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificandīs}} # {{inflection of|la|beātificandus||dat//abl|m//f//n|p}} 321nmeot6w92tbx65roi6f58lr2gp5f beatificando 0 3546743 9943315 2026-10-13T01:49:28Z TongcyBot 83009 半自動導入非詞元形式 9943315 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動名詞}} # {{gl-verb form of|beatificar}} ==意大利語== ===動詞=== {{head|it|動名詞}} # {{gerund of|it|beatificare}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificandō}} # {{inflection of|la|beātificō||dat|動名詞}} ==葡萄牙語== ===動詞=== {{head|pt|動名詞}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動名詞}} # {{es-verb form of|beatificar}} p4i2y0hu45wbg0t5nompcznbqs5hcwj beatificandorum 0 3546744 9943316 2026-10-13T01:49:29Z TongcyBot 83009 半自動導入非詞元形式 9943316 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificandōrum}} # {{inflection of|la|beātificandus||gen|m//n|p}} ajsxz988r147rum8l3s0fcpbp5yvxu3 beatificandos 0 3546745 9943317 2026-10-13T01:49:30Z TongcyBot 83009 半自動導入非詞元形式 9943317 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificandōs}} # {{inflection of|la|beātificandus||acc|m|p}} myzhyzoyji8qwc02xgmhagjnbrj9pe9 beatificandum 0 3546746 9943318 2026-10-13T01:49:31Z TongcyBot 83009 半自動導入非詞元形式 9943318 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificandum}} # {{inflection of|la|beātificō||acc|動名詞}} 61e8qz545xyigqmg4rsqdib4gsmuhjg beatificano 0 3546747 9943319 2026-10-13T01:49:33Z TongcyBot 83009 半自動導入非詞元形式 9943319 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|p|pres|indc}} gklhtvn1jc0ppon2d4i3gwc8xqekuhb beatificant 0 3546748 9943320 2026-10-13T01:49:34Z TongcyBot 83009 半自動導入非詞元形式 9943320 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動名詞}} # {{gerund of|ca|beatificar}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificant}} # {{inflection of|la|beātificō||3|p|pres|actv|indc}} lt6znsc73g405mvz1vqt26gu84hfy9g beatificantem 0 3546749 9943321 2026-10-13T01:49:35Z TongcyBot 83009 半自動導入非詞元形式 9943321 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantem}} # {{inflection of|la|beātificāns||acc|m//f|s}} kpou5t17i70ujc7krcdk9zhiws9ppcf beatificantes 0 3546750 9943322 2026-10-13T01:49:36Z TongcyBot 83009 半自動導入非詞元形式 9943322 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantēs}} # {{inflection of|la|beātificāns||nom//acc//voc|m//f|p}} 1immz17z2bt58n7o7eveb6s3roysno4 beatificantia 0 3546751 9943323 2026-10-13T01:49:37Z TongcyBot 83009 半自動導入非詞元形式 9943323 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantia}} # {{inflection of|la|beātificāns||nom//acc//voc|n|p}} 8y75l9z8p417oav5k5y8fbezmnno5oq beatificantibus 0 3546752 9943324 2026-10-13T01:49:39Z TongcyBot 83009 半自動導入非詞元形式 9943324 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantibus}} # {{inflection of|la|beātificāns||dat//abl|m//f//n|p}} j3rvj06n5iatm51yjhucp3f3wb1k886 beatificantis 0 3546753 9943325 2026-10-13T01:49:40Z TongcyBot 83009 半自動導入非詞元形式 9943325 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantis}} # {{inflection of|la|beātificāns||gen|m//f//n|s}} o9e8eq3gej862yfje4e4pynzatb9acz beatificantium 0 3546754 9943326 2026-10-13T01:49:41Z TongcyBot 83009 半自動導入非詞元形式 9943326 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificantium}} # {{inflection of|la|beātificāns||gen|m//f//n|p}} rbm0hl2ulvhus50p9jmo8idtixgtyth beatificanto 0 3546755 9943327 2026-10-13T01:49:42Z TongcyBot 83009 半自動導入非詞元形式 9943327 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificantō}} # {{inflection of|la|beātificō||3|p|futr|actv|impr}} kch4mtalmewe5u86vunr5d3o9mhm4gw beatificantor 0 3546756 9943328 2026-10-13T01:49:43Z TongcyBot 83009 半自動導入非詞元形式 9943328 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificantor}} # {{inflection of|la|beātificō||3|p|futr|pasv|impr}} jvp53v6xpr2wvxqbvqv0qfttbylyliy beatificantur 0 3546757 9943329 2026-10-13T01:49:44Z TongcyBot 83009 半自動導入非詞元形式 9943329 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificantur}} # {{inflection of|la|beātificō||3|p|pres|pasv|indc}} fn9cxir8sbigbxyk7eaxtahal2zo6y5 beatificara 0 3546758 9943330 2026-10-13T01:49:45Z TongcyBot 83009 半自動導入非詞元形式 9943330 wikitext text/x-wiki {{also|beatificará|beatificarà}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 9yjjp9aq5ipobi0ajo1ljddihxqf5ta beatificarades 0 3546759 9943331 2026-10-13T01:49:46Z TongcyBot 83009 半自動導入非詞元形式 9943331 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificarais 0 3546760 9943332 2026-10-13T01:49:47Z TongcyBot 83009 半自動導入非詞元形式 9943332 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificaram 0 3546761 9943333 2026-10-13T01:49:49Z TongcyBot 83009 半自動導入非詞元形式 9943333 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificaramos 0 3546762 9943334 2026-10-13T01:49:50Z TongcyBot 83009 半自動導入非詞元形式 9943334 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificaran 0 3546763 9943335 2026-10-13T01:49:52Z TongcyBot 83009 半自動導入非詞元形式 9943335 wikitext text/x-wiki {{also|beatificarán}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} qk22f34auwvz2jjo3us6muyrp99wzts beatificaras 0 3546764 9943336 2026-10-13T01:49:53Z TongcyBot 83009 半自動導入非詞元形式 9943336 wikitext text/x-wiki {{also|beatificarás|beatificaràs}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārās}} # {{inflection of|la|beātificō||2|s|plup|actv|indc}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 3xjkk9epqobj70jyx3ib99vx9j64u6p beatificardes 0 3546765 9943337 2026-10-13T01:49:54Z TongcyBot 83009 半自動導入非詞元形式 9943337 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} eq34y3k0kfo0mwd57chp3dage3icyir beatificaredes 0 3546766 9943338 2026-10-13T01:49:55Z TongcyBot 83009 半自動導入非詞元形式 9943338 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificarei 0 3546767 9943339 2026-10-13T01:49:56Z TongcyBot 83009 半自動導入非詞元形式 9943339 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} eq34y3k0kfo0mwd57chp3dage3icyir beatificareis 0 3546768 9943340 2026-10-13T01:49:57Z TongcyBot 83009 半自動導入非詞元形式 9943340 wikitext text/x-wiki {{also|beatificáreis|beatificaréis}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} q0v6q77vvi6mv8wh4b018ee6ubx9nv3 beatificarem 0 3546769 9943341 2026-10-13T01:49:58Z TongcyBot 83009 半自動導入非詞元形式 9943341 wikitext text/x-wiki {{also|beatificàrem}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārem}} # {{inflection of|la|beātificō||1|s|impf|actv|subj}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} j6wqt1xdcbleojpduloskatfjq6m4zs beatificaremini 0 3546770 9943342 2026-10-13T01:49:59Z TongcyBot 83009 半自動導入非詞元形式 9943342 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārēminī}} # {{inflection of|la|beātificō||2|p|impf|pasv|subj}} 5dx79kkqtqotipxjlpgp6qxhsmzk3l1 beatificaremos 0 3546771 9943343 2026-10-13T01:50:00Z TongcyBot 83009 半自動導入非詞元形式 9943343 wikitext text/x-wiki {{also|beatificáremos}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} lljnc55wyrn4gn6uetxeh7lnrbeh81t beatificaremur 0 3546772 9943344 2026-10-13T01:50:01Z TongcyBot 83009 半自動導入非詞元形式 9943344 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārēmur}} # {{inflection of|la|beātificō||1|p|impf|pasv|subj}} nyfx7qk7vlxqqt24ioogp2pwt0omdoo beatificaremus 0 3546773 9943345 2026-10-13T01:50:02Z TongcyBot 83009 半自動導入非詞元形式 9943345 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārēmus}} # {{inflection of|la|beātificō||1|p|impf|actv|subj}} al8sow7abox07gorg51lihc691jdgz6 beatificaren 0 3546774 9943346 2026-10-13T01:50:04Z TongcyBot 83009 半自動導入非詞元形式 9943346 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} i9s272c6gh8n5u1abq8wk1gtjd4j6zw beatificarent 0 3546775 9943347 2026-10-13T01:50:05Z TongcyBot 83009 半自動導入非詞元形式 9943347 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārent}} # {{inflection of|la|beātificō||3|p|impf|actv|subj}} 0v15r75pcuefca2zui8unfqs57hky88 beatificarentur 0 3546776 9943348 2026-10-13T01:50:06Z TongcyBot 83009 半自動導入非詞元形式 9943348 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārentur}} # {{inflection of|la|beātificō||3|p|impf|pasv|subj}} 6sl5std7d2yw5bowq6u8hhnlvas6s9i beatificarer 0 3546777 9943349 2026-10-13T01:50:07Z TongcyBot 83009 半自動導入非詞元形式 9943349 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārer}} # {{inflection of|la|beātificō||1|s|impf|pasv|subj}} fcpb7kxqimuqrg3kkc06tp9q6m8wi4j beatificarere 0 3546778 9943350 2026-10-13T01:50:08Z TongcyBot 83009 半自動導入非詞元形式 9943350 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārēre}} # {{inflection of|la|beātificō||2|s|impf|pasv|subj}} fn49k1o8gwkcl54oqw908k43eu3qs5a beatificareris 0 3546779 9943351 2026-10-13T01:50:09Z TongcyBot 83009 半自動導入非詞元形式 9943351 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārēris}} # {{inflection of|la|beātificō||2|s|impf|pasv|subj}} ndyudy643yslvzgozf67n1c8a18zl3l beatificares 0 3546780 9943352 2026-10-13T01:50:10Z TongcyBot 83009 半自動導入非詞元形式 9943352 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārēs}} # {{inflection of|la|beātificō||2|s|impf|actv|subj}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} oyl71ij5nhfqlfmgbd950y5iuz8zk70 beatificaret 0 3546781 9943353 2026-10-13T01:50:11Z TongcyBot 83009 半自動導入非詞元形式 9943353 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāret}} # {{inflection of|la|beātificō||3|s|impf|actv|subj}} 9nxy093jjjhw5lm4ropjhutnnwmd8qr beatificaretis 0 3546782 9943354 2026-10-13T01:50:12Z TongcyBot 83009 半自動導入非詞元形式 9943354 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārētis}} # {{inflection of|la|beātificō||2|p|impf|actv|subj}} 2q2dxx0knxs62cpdpeax6hjc52g4n0m beatificaretur 0 3546783 9943355 2026-10-13T01:50:13Z TongcyBot 83009 半自動導入非詞元形式 9943355 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārētur}} # {{inflection of|la|beātificō||3|s|impf|pasv|subj}} i6vu6ayswbeu2hu2hil2qkrz2i12i6e beatificareu 0 3546784 9943356 2026-10-13T01:50:14Z TongcyBot 83009 半自動導入非詞元形式 9943356 wikitext text/x-wiki {{also|beatificàreu}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} cng0aub79ozvem93s10svzsyfof5943 beatificari 0 3546785 9943357 2026-10-13T01:50:15Z TongcyBot 83009 半自動導入非詞元形式 9943357 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificārī}} # {{inflection of|la|beātificō||pres|pasv|inf}} omh4c6c0ntsi7slkxzdvf8p1zd0kjt8 beatificaria 0 3546786 9943358 2026-10-13T01:50:17Z TongcyBot 83009 半自動導入非詞元形式 9943358 wikitext text/x-wiki {{also|beatificaría}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} 2nx7ra30x03iqalrdklpdom65wzioj6 beatificariades 0 3546787 9943359 2026-10-13T01:50:18Z TongcyBot 83009 半自動導入非詞元形式 9943359 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificariam 0 3546788 9943360 2026-10-13T01:50:19Z TongcyBot 83009 半自動導入非詞元形式 9943360 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificariamos 0 3546789 9943361 2026-10-13T01:50:20Z TongcyBot 83009 半自動導入非詞元形式 9943361 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificarias 0 3546790 9943362 2026-10-13T01:50:21Z TongcyBot 83009 半自動導入非詞元形式 9943362 wikitext text/x-wiki {{also|beatificarías}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} m15cxxwmvh7tryuti9kxk9oxr2pjtng beatificarien 0 3546791 9943363 2026-10-13T01:50:22Z TongcyBot 83009 半自動導入非詞元形式 9943363 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificaries 0 3546792 9943364 2026-10-13T01:50:23Z TongcyBot 83009 半自動導入非詞元形式 9943364 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificaris 0 3546793 9943365 2026-10-13T01:50:24Z TongcyBot 83009 半自動導入非詞元形式 9943365 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāris}} # {{inflection of|la|beātificō||2|s|pres|pasv|indc}} aedbe6r1upolkq2dl1yspsdhk98zra4 beatificarla 0 3546794 9943366 2026-10-13T01:50:25Z TongcyBot 83009 半自動導入非詞元形式 9943366 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarlas 0 3546795 9943367 2026-10-13T01:50:26Z TongcyBot 83009 半自動導入非詞元形式 9943367 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarle 0 3546796 9943368 2026-10-13T01:50:27Z TongcyBot 83009 半自動導入非詞元形式 9943368 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarles 0 3546797 9943369 2026-10-13T01:50:28Z TongcyBot 83009 半自動導入非詞元形式 9943369 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarlo 0 3546798 9943370 2026-10-13T01:50:30Z TongcyBot 83009 半自動導入非詞元形式 9943370 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarlos 0 3546799 9943371 2026-10-13T01:50:31Z TongcyBot 83009 半自動導入非詞元形式 9943371 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarme 0 3546800 9943372 2026-10-13T01:50:32Z TongcyBot 83009 半自動導入非詞元形式 9943372 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarmos 0 3546801 9943373 2026-10-13T01:50:33Z TongcyBot 83009 半自動導入非詞元形式 9943373 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} eq34y3k0kfo0mwd57chp3dage3icyir beatificarnos 0 3546802 9943374 2026-10-13T01:50:34Z TongcyBot 83009 半自動導入非詞元形式 9943374 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificaron 0 3546803 9943375 2026-10-13T01:50:35Z TongcyBot 83009 半自動導入非詞元形式 9943375 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificarono 0 3546804 9943376 2026-10-13T01:50:36Z TongcyBot 83009 半自動導入非詞元形式 9943376 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|p|phis}} r2p92tc7s9cmnzt5yk6e4oncs0o6iyp beatificaros 0 3546805 9943377 2026-10-13T01:50:38Z TongcyBot 83009 半自動導入非詞元形式 9943377 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarse 0 3546806 9943378 2026-10-13T01:50:39Z TongcyBot 83009 半自動導入非詞元形式 9943378 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarte 0 3546807 9943379 2026-10-13T01:50:40Z TongcyBot 83009 半自動導入非詞元形式 9943379 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificarà 0 3546808 9943380 2026-10-13T01:50:41Z TongcyBot 83009 半自動導入非詞元形式 9943380 wikitext text/x-wiki {{also|beatificara|beatificará}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 2ryi0rp02n6h7nc7s2k2e0jdr7sd6ox beatificaràs 0 3546809 9943381 2026-10-13T01:50:43Z TongcyBot 83009 半自動導入非詞元形式 9943381 wikitext text/x-wiki {{also|beatificaras|beatificarás}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} no1g431c3om9rug20ifkbv0tqf95bcw beatificará 0 3546810 9943382 2026-10-13T01:50:44Z TongcyBot 83009 半自動導入非詞元形式 9943382 wikitext text/x-wiki {{also|beatificara|beatificarà}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 0og7087khjw6we805gybnkj96wnghn7 beatificarán 0 3546811 9943383 2026-10-13T01:50:45Z TongcyBot 83009 半自動導入非詞元形式 9943383 wikitext text/x-wiki {{also|beatificaran}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} f485txetaegrpn7otg1g0ea6zqyob1e beatificarás 0 3546812 9943384 2026-10-13T01:50:46Z TongcyBot 83009 半自動導入非詞元形式 9943384 wikitext text/x-wiki {{also|beatificaras|beatificaràs}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} dz7n7lzd66opsyy7v2i6t47ubcfhwbx beatificarão 0 3546813 9943385 2026-10-13T01:50:47Z TongcyBot 83009 半自動導入非詞元形式 9943385 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificaré 0 3546814 9943386 2026-10-13T01:50:48Z TongcyBot 83009 半自動導入非詞元形式 9943386 wikitext text/x-wiki {{also|beatificare}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} rgpw0mxvhbesc9z22zf1msf1dmc0taw beatificaréis 0 3546815 9943387 2026-10-13T01:50:49Z TongcyBot 83009 半自動導入非詞元形式 9943387 wikitext text/x-wiki {{also|beatificareis|beatificáreis}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} cfyrgczbfbqs057r2bfhghw2kju804w beatificaría 0 3546816 9943388 2026-10-13T01:50:50Z TongcyBot 83009 半自動導入非詞元形式 9943388 wikitext text/x-wiki {{also|beatificaria}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} tjlooo05b5bnafhmh6rhllkqvi5ny7a beatificaríais 0 3546817 9943389 2026-10-13T01:50:51Z TongcyBot 83009 半自動導入非詞元形式 9943389 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificaríamos 0 3546818 9943390 2026-10-13T01:50:52Z TongcyBot 83009 半自動導入非詞元形式 9943390 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} bitsp1laumrp5c9jq5ayptoesqaw5ey beatificarían 0 3546819 9943391 2026-10-13T01:50:54Z TongcyBot 83009 半自動導入非詞元形式 9943391 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificarías 0 3546820 9943392 2026-10-13T01:50:55Z TongcyBot 83009 半自動導入非詞元形式 9943392 wikitext text/x-wiki {{also|beatificarias}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 9lar9mkh9raxx23b3urvdckrfcvx10z beatificaríeis 0 3546821 9943393 2026-10-13T01:50:56Z TongcyBot 83009 半自動導入非詞元形式 9943393 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificaríem 0 3546822 9943394 2026-10-13T01:50:57Z TongcyBot 83009 半自動導入非詞元形式 9943394 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificaríeu 0 3546823 9943395 2026-10-13T01:50:58Z TongcyBot 83009 半自動導入非詞元形式 9943395 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificas 0 3546824 9943396 2026-10-13T01:50:59Z TongcyBot 83009 半自動導入非詞元形式 9943396 wikitext text/x-wiki {{also|beatificás}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificās}} # {{inflection of|la|beātificō||2|s|pres|actv|indc}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 0ma3lepjszgxuzb8l7eupxss99csmey beatificase 0 3546825 9943397 2026-10-13T01:51:00Z TongcyBot 83009 半自動導入非詞元形式 9943397 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificaseis 0 3546826 9943398 2026-10-13T01:51:01Z TongcyBot 83009 半自動導入非詞元形式 9943398 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificasen 0 3546827 9943399 2026-10-13T01:51:03Z TongcyBot 83009 半自動導入非詞元形式 9943399 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificases 0 3546828 9943400 2026-10-13T01:51:04Z TongcyBot 83009 半自動導入非詞元形式 9943400 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificasse 0 3546829 9943401 2026-10-13T01:51:05Z TongcyBot 83009 半自動導入非詞元形式 9943401 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|s|impf|subj}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāsse}} # {{inflection of|la|beātificō||perf|actv|inf}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} hohz0xkrqj0htkcsgqyuq4p1ld4cm88 beatificassem 0 3546830 9943402 2026-10-13T01:51:06Z TongcyBot 83009 半自動導入非詞元形式 9943402 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāssem}} # {{inflection of|la|beātificō||1|s|plup|actv|subj}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} kfwiu05m8ry9ifam9xvbu4d9usyhecq beatificassero 0 3546831 9943403 2026-10-13T01:51:08Z TongcyBot 83009 半自動導入非詞元形式 9943403 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|p|impf|subj}} mrne7cjh3skd7mbgvznw4uo1kinqh4t beatificasses 0 3546832 9943404 2026-10-13T01:51:09Z TongcyBot 83009 半自動導入非詞元形式 9943404 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāssēs}} # {{inflection of|la|beātificō||2|s|plup|actv|subj}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} hs2t2wb8znwdfzx440vpusgwnzj6ray beatificassi 0 3546833 9943405 2026-10-13T01:51:10Z TongcyBot 83009 半自動導入非詞元形式 9943405 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1//2|s|impf|subj}} 90sns0o2r1cuu915evyomkjs0nr83u0 beatificassimo 0 3546834 9943406 2026-10-13T01:51:11Z TongcyBot 83009 半自動導入非詞元形式 9943406 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|p|impf|subj}} a7enp2xtgfx97gpexah4inbjafxo0hi beatificaste 0 3546835 9943407 2026-10-13T01:51:12Z TongcyBot 83009 半自動導入非詞元形式 9943407 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|p|phis|;|2|p|impf|subj}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 1d5icdlsj7adefqzavbiiganjfyhiwz beatificasteis 0 3546836 9943408 2026-10-13T01:51:14Z TongcyBot 83009 半自動導入非詞元形式 9943408 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificastes 0 3546837 9943409 2026-10-13T01:51:15Z TongcyBot 83009 半自動導入非詞元形式 9943409 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} eq34y3k0kfo0mwd57chp3dage3icyir beatificasti 0 3546838 9943410 2026-10-13T01:51:16Z TongcyBot 83009 半自動導入非詞元形式 9943410 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|s|phis}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāstī}} # {{inflection of|la|beātificō||2|s|perf|actv|indc}} t3ouqa946djjnltn5rsvqbaksafptbu beatificat 0 3546839 9943411 2026-10-13T01:51:17Z TongcyBot 83009 半自動導入非詞元形式 9943411 wikitext text/x-wiki ==加泰羅尼亞語== ===發音=== * {{ca-IPA}} * {{rhymes|ca|at|s=5}} ===分詞=== {{ca-pp}} # {{past participle of|ca|beatificar}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificat}} # {{inflection of|la|beātificō||3|s|pres|actv|indc}} gbyeg2emzd2xlnehziz11zkmsezp4y3 beatificatae 0 3546840 9943412 2026-10-13T01:51:18Z TongcyBot 83009 半自動導入非詞元形式 9943412 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātae}} # {{inflection of|la|beātificātus||nom//voc|f|p|;|gen//dat|f|s}} s03hfa95zuikrjov6mdy1hhvpasevg2 beatificatam 0 3546841 9943413 2026-10-13T01:51:20Z TongcyBot 83009 半自動導入非詞元形式 9943413 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātam}} # {{inflection of|la|beātificātus||acc|f|s}} m47aodioynptge67bc8bsoh7pa0k5bw beatificatarum 0 3546842 9943414 2026-10-13T01:51:21Z TongcyBot 83009 半自動導入非詞元形式 9943414 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātārum}} # {{inflection of|la|beātificātus||gen|f|p}} dryzrlle6htkrozxb0qnj8qude5c4uq beatificatas 0 3546843 9943415 2026-10-13T01:51:22Z TongcyBot 83009 半自動導入非詞元形式 9943415 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātās}} # {{inflection of|la|beātificātus||acc|f|p}} srhux6l5pec43baqjse1r2k7zi4hwjg beatificatis 0 3546844 9943416 2026-10-13T01:51:23Z TongcyBot 83009 半自動導入非詞元形式 9943416 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātis}} # {{inflection of|la|beātificō||2|p|pres|actv|indc}} chnu0ppqaw4zh8u9ohtmutscod7i09f beatificator 0 3546845 9943417 2026-10-13T01:51:24Z TongcyBot 83009 半自動導入非詞元形式 9943417 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātor}} # {{inflection of|la|beātificō||2//3|s|futr|pasv|impr}} ===參考資料=== * {{R:la:Gaffiot}} * {{R:la:NLW}} nzs1ecwr1gi2pil1btkdys872bmx4ha beatificatorum 0 3546846 9943418 2026-10-13T01:51:25Z TongcyBot 83009 半自動導入非詞元形式 9943418 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātōrum}} # {{inflection of|la|beātificātus||gen|m//n|p}} rqcnpsz357mkppgyfq9dfaradbhrxlw beatificatos 0 3546847 9943419 2026-10-13T01:51:26Z TongcyBot 83009 半自動導入非詞元形式 9943419 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātōs}} # {{inflection of|la|beātificātus||acc|m|p}} k9ksfss8x29iv1d1w31a75bj4dk436q beatificatote 0 3546848 9943420 2026-10-13T01:51:27Z TongcyBot 83009 半自動導入非詞元形式 9943420 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātōte}} # {{inflection of|la|beātificō||2|p|futr|actv|impr}} qrtjv1rxkrxcghhh5t7opi6s8jmtg3w beatificats 0 3546849 9943421 2026-10-13T01:51:28Z TongcyBot 83009 半自動導入非詞元形式 9943421 wikitext text/x-wiki ==加泰羅尼亞語== ===發音=== * {{ca-IPA}} * {{rhymes|ca|ats|s=5}} ===分詞=== {{head|ca|過去分詞變格形|g=m-p}} # {{masculine plural of|ca|beatificat}} 7o7prc8vut6dakvsqe2uem4sfpglvb2 beatificatu 0 3546850 9943422 2026-10-13T01:51:29Z TongcyBot 83009 半自動導入非詞元形式 9943422 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātū}} # {{inflection of|la|beātificō||abl|sup}} tpujip2s0mpu4wff37mijde16yr3b44 beatificatum 0 3546851 9943423 2026-10-13T01:51:30Z TongcyBot 83009 半自動導入非詞元形式 9943423 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātum}} # {{inflection of|la|beātificō||acc|sup}} nw60tnohf6h0cwbeg02taaysah2nwda beatificatur 0 3546852 9943424 2026-10-13T01:51:32Z TongcyBot 83009 半自動導入非詞元形式 9943424 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificātur}} # {{inflection of|la|beātificō||3|s|pres|pasv|indc}} 30yj0uwu0fiodyy08xrg9lx18owmxmg beatificatura 0 3546853 9943425 2026-10-13T01:51:33Z TongcyBot 83009 半自動導入非詞元形式 9943425 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūra}} # {{inflection of|la|beātificātūrus||nom//voc|f|s|;|nom//acc//voc|n|p}} ===分詞=== {{head|la|分詞變格形|head=beātificātūrā}} # {{inflection of|la|beātificātūrus||abl|f|s}} c1fn77iplpfxvlhxvcn6g5aedljnx54 beatificaturae 0 3546854 9943426 2026-10-13T01:51:34Z TongcyBot 83009 半自動導入非詞元形式 9943426 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrae}} # {{inflection of|la|beātificātūrus||nom//voc|f|p|;|gen//dat|f|s}} fn6xpjonund85fkz7vcj9j95nufbssf beatificaturam 0 3546855 9943427 2026-10-13T01:51:35Z TongcyBot 83009 半自動導入非詞元形式 9943427 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūram}} # {{inflection of|la|beātificātūrus||acc|f|s}} i7gm8djg9maw32v45bc6xzoaxsyamlx beatificaturarum 0 3546856 9943428 2026-10-13T01:51:36Z TongcyBot 83009 半自動導入非詞元形式 9943428 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrārum}} # {{inflection of|la|beātificātūrus||gen|f|p}} aj66klhpqpaiu3gdht1lbh6gxed3ab4 beatificaturas 0 3546857 9943429 2026-10-13T01:51:37Z TongcyBot 83009 半自動導入非詞元形式 9943429 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrās}} # {{inflection of|la|beātificātūrus||acc|f|p}} pedvbm0ibsp8yphe6gcz84vc9fkg4zv beatificature 0 3546858 9943430 2026-10-13T01:51:38Z TongcyBot 83009 半自動導入非詞元形式 9943430 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūre}} # {{inflection of|la|beātificātūrus||voc|m|s}} 4ur7om4fhe7532li2z6023io2gmcu6o beatificaturi 0 3546859 9943431 2026-10-13T01:51:39Z TongcyBot 83009 半自動導入非詞元形式 9943431 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrī}} # {{inflection of|la|beātificātūrus||nom//voc|m|p|;|gen|m//n|s}} p6214kg7r4e2oup6bl5ciohiv8pui2b beatificaturis 0 3546860 9943432 2026-10-13T01:51:40Z TongcyBot 83009 半自動導入非詞元形式 9943432 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrīs}} # {{inflection of|la|beātificātūrus||dat//abl|m//f//n|p}} 1eov37y9q62gzu172flwtklbd1b3qxs beatificaturo 0 3546861 9943433 2026-10-13T01:51:41Z TongcyBot 83009 半自動導入非詞元形式 9943433 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrō}} # {{inflection of|la|beātificātūrus||dat//abl|m//n|s}} ceer9t8lfra5o1y16e0r4lz0huxiul6 beatificaturorum 0 3546862 9943434 2026-10-13T01:51:42Z TongcyBot 83009 半自動導入非詞元形式 9943434 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrōrum}} # {{inflection of|la|beātificātūrus||gen|m//n|p}} 1tt8r5j3ww87oy0d90j9kzurltgb49l beatificaturos 0 3546863 9943435 2026-10-13T01:51:43Z TongcyBot 83009 半自動導入非詞元形式 9943435 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrōs}} # {{inflection of|la|beātificātūrus||acc|m|p}} py4nmv1cmqy5gf9cwuymnr3l4yh3neb beatificaturum 0 3546864 9943436 2026-10-13T01:51:44Z TongcyBot 83009 半自動導入非詞元形式 9943436 wikitext text/x-wiki ==拉丁語== ===分詞=== {{head|la|分詞變格形|head=beātificātūrum}} # {{inflection of|la|beātificātūrus||nom//acc//voc|n|s|;|acc|m|s}} rn69c3oz1hjky04cmozzwqfejnzm7os beatificava 0 3546865 9943437 2026-10-13T01:51:46Z TongcyBot 83009 半自動導入非詞元形式 9943437 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|s|impf|indc}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} hmw3zoapourc0uvb8hv8hxb3w8tww9e beatificavam 0 3546866 9943438 2026-10-13T01:51:47Z TongcyBot 83009 半自動導入非詞元形式 9943438 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificavamo 0 3546867 9943439 2026-10-13T01:51:48Z TongcyBot 83009 半自動導入非詞元形式 9943439 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|p|impf|indc}} dbqymcjxvjmu3113n1q067vyq9deh6l beatificavano 0 3546868 9943440 2026-10-13T01:51:49Z TongcyBot 83009 半自動導入非詞元形式 9943440 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|p|impf|indc}} 0vxitcih9aa0l8klbitokjhp4d1vahn beatificavas 0 3546869 9943441 2026-10-13T01:51:50Z TongcyBot 83009 半自動導入非詞元形式 9943441 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificavate 0 3546870 9943442 2026-10-13T01:51:51Z TongcyBot 83009 半自動導入非詞元形式 9943442 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|p|impf|indc}} 1shp8n38wwuzi51wq4b1q1kbcef0g4k beatificaven 0 3546871 9943443 2026-10-13T01:51:52Z TongcyBot 83009 半自動導入非詞元形式 9943443 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificaveram 0 3546872 9943444 2026-10-13T01:51:53Z TongcyBot 83009 半自動導入非詞元形式 9943444 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāveram}} # {{inflection of|la|beātificō||1|s|plup|actv|indc}} 3m3xkmrtapk9xpcav7afkw311bxrxkm beatificaveramus 0 3546873 9943445 2026-10-13T01:51:54Z TongcyBot 83009 半自動導入非詞元形式 9943445 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverāmus}} # {{inflection of|la|beātificō||1|p|plup|actv|indc}} 7piijeyin0d35f4xoeq25po4x32cjd9 beatificaverant 0 3546874 9943446 2026-10-13T01:51:55Z TongcyBot 83009 半自動導入非詞元形式 9943446 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverant}} # {{inflection of|la|beātificō||3|p|plup|actv|indc}} 5tvew1qxnwplk3r4t7fo0q8b5wi39d6 beatificaveras 0 3546875 9943447 2026-10-13T01:51:57Z TongcyBot 83009 半自動導入非詞元形式 9943447 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverās}} # {{inflection of|la|beātificō||2|s|plup|actv|indc}} 7sj1ojhqbhijwygk3i1x8a2kpxi9rlb beatificaverat 0 3546876 9943448 2026-10-13T01:51:58Z TongcyBot 83009 半自動導入非詞元形式 9943448 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverat}} # {{inflection of|la|beātificō||3|s|plup|actv|indc}} 0b7l43uxg50wgpm1pkeaki222mnkq2u beatificaveratis 0 3546877 9943449 2026-10-13T01:51:59Z TongcyBot 83009 半自動導入非詞元形式 9943449 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverātis}} # {{inflection of|la|beātificō||2|p|plup|actv|indc}} 83prrl7lx9punnqph7qp3dlex00fghw beatificavere 0 3546878 9943450 2026-10-13T01:52:00Z TongcyBot 83009 半自動導入非詞元形式 9943450 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvēre}} # {{inflection of|la|beātificō||3|p|perf|actv|indc}} 0mbd81gxoi88lbbyxpaovsl0xgm9k44 beatificaverim 0 3546879 9943451 2026-10-13T01:52:01Z TongcyBot 83009 半自動導入非詞元形式 9943451 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverim}} # {{inflection of|la|beātificō||1|s|perf|actv|subj}} rt6hy56x65znd6sb4zf1lqmwpar7o64 beatificaverimus 0 3546880 9943452 2026-10-13T01:52:02Z TongcyBot 83009 半自動導入非詞元形式 9943452 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverimus}} # {{inflection of|la|beātificō||1|p|futr|perf|actv|indc}} ===動詞=== {{head|la|動詞變位形式|head=beātificāverīmus}} # {{inflection of|la|beātificō||1|p|perf|actv|subj}} 0x8uebo93tzqtoybq6yf5x8gefgcp2q beatificaverint 0 3546881 9943453 2026-10-13T01:52:03Z TongcyBot 83009 半自動導入非詞元形式 9943453 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverint}} # {{inflection of|la|beātificō||3|p|futr|perf|actv|indc|;|3|p|perf|actv|subj}} 89o892d0vitahnml54n1rs7f86mbj5j beatificaveris 0 3546882 9943454 2026-10-13T01:52:04Z TongcyBot 83009 半自動導入非詞元形式 9943454 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāveris}} # {{inflection of|la|beātificō||2|s|futr|perf|actv|indc}} ===動詞=== {{head|la|動詞變位形式|head=beātificāverīs}} # {{inflection of|la|beātificō||2|s|perf|actv|subj}} hklr5lgmjs8bczlfu0lcg4f3b2y3rhz beatificaverit 0 3546883 9943455 2026-10-13T01:52:05Z TongcyBot 83009 半自動導入非詞元形式 9943455 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverit}} # {{inflection of|la|beātificō||3|s|futr|perf|actv|indc|;|3|s|perf|actv|subj}} 6a6uakxb0h9qbh3bl6zwr189b8hdyns beatificaveritis 0 3546884 9943456 2026-10-13T01:52:06Z TongcyBot 83009 半自動導入非詞元形式 9943456 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāveritis}} # {{inflection of|la|beātificō||2|p|futr|perf|actv|indc}} ===動詞=== {{head|la|動詞變位形式|head=beātificāverītis}} # {{inflection of|la|beātificō||2|p|perf|actv|subj}} d41un8bmvaa0cgo876wuie4jbps1sy7 beatificavero 0 3546885 9943457 2026-10-13T01:52:07Z TongcyBot 83009 半自動導入非詞元形式 9943457 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāverō}} # {{inflection of|la|beātificō||1|s|futr|perf|actv|indc}} rh1srmyps6zkhpwpgqx3ua64ggn2pa0 beatificaverunt 0 3546886 9943458 2026-10-13T01:52:08Z TongcyBot 83009 半自動導入非詞元形式 9943458 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvērunt}} # {{inflection of|la|beātificō||3|p|perf|actv|indc}} awo47crn4v8lrz05nbldogl8rqb5dal beatificaves 0 3546887 9943459 2026-10-13T01:52:10Z TongcyBot 83009 半自動導入非詞元形式 9943459 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificavi 0 3546888 9943460 2026-10-13T01:52:11Z TongcyBot 83009 半自動導入非詞元形式 9943460 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|s|impf|indc}} ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvī}} # {{inflection of|la|beātificō||1|s|perf|actv|indc}} oryzx8hqsmoosncj8yhwa0okzqs8jyq beatificavimus 0 3546889 9943461 2026-10-13T01:52:12Z TongcyBot 83009 半自動導入非詞元形式 9943461 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvimus}} # {{inflection of|la|beātificō||1|p|perf|actv|indc}} fuuamh3h0n5ojsq6hc79j7s3ns16n87 beatificavisse 0 3546890 9943462 2026-10-13T01:52:13Z TongcyBot 83009 半自動導入非詞元形式 9943462 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvisse}} # {{inflection of|la|beātificō||perf|actv|inf}} mnurzwf6ccehazndm27bzwb7mgf82o5 beatificavissem 0 3546891 9943463 2026-10-13T01:52:14Z TongcyBot 83009 半自動導入非詞元形式 9943463 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvissem}} # {{inflection of|la|beātificō||1|s|plup|actv|subj}} b79dfbnyzyekm2xykrirzvyd79tkj7r beatificavissemus 0 3546892 9943464 2026-10-13T01:52:15Z TongcyBot 83009 半自動導入非詞元形式 9943464 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvissēmus}} # {{inflection of|la|beātificō||1|p|plup|actv|subj}} 7b2n5lits69astdkgmtg30qmvojvzoy beatificavissent 0 3546893 9943465 2026-10-13T01:52:16Z TongcyBot 83009 半自動導入非詞元形式 9943465 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvissent}} # {{inflection of|la|beātificō||3|p|plup|actv|subj}} joscp7n4hakkt9a45qrdz2mhrglau39 beatificavisses 0 3546894 9943466 2026-10-13T01:52:17Z TongcyBot 83009 半自動導入非詞元形式 9943466 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvissēs}} # {{inflection of|la|beātificō||2|s|plup|actv|subj}} rq83fj4k1rpa89owzccnmh2q3jenrl6 beatificavisset 0 3546895 9943467 2026-10-13T01:52:18Z TongcyBot 83009 半自動導入非詞元形式 9943467 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvisset}} # {{inflection of|la|beātificō||3|s|plup|actv|subj}} hicyp8q8fntzgrfjfh27la0k1oshdwz beatificavissetis 0 3546896 9943468 2026-10-13T01:52:19Z TongcyBot 83009 半自動導入非詞元形式 9943468 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvissētis}} # {{inflection of|la|beātificō||2|p|plup|actv|subj}} rozlhwm5a7kts69b2k5dewll49wdjex beatificavisti 0 3546897 9943469 2026-10-13T01:52:20Z TongcyBot 83009 半自動導入非詞元形式 9943469 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvistī}} # {{inflection of|la|beātificō||2|s|perf|actv|indc}} 2bi7hz5a80221tqo69geb389nqkgnuq beatificavistis 0 3546898 9943470 2026-10-13T01:52:21Z TongcyBot 83009 半自動導入非詞元形式 9943470 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvistis}} # {{inflection of|la|beātificō||2|p|perf|actv|indc}} bcqot18nbna2s0axeeqiiva8f2ahqf6 beatificavit 0 3546899 9943471 2026-10-13T01:52:22Z TongcyBot 83009 半自動導入非詞元形式 9943471 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificāvit}} # {{inflection of|la|beātificō||3|s|perf|actv|indc}} mtpflrrnxo5av7dgpm2pm2x37z0ehi4 beatificavo 0 3546900 9943472 2026-10-13T01:52:24Z TongcyBot 83009 半自動導入非詞元形式 9943472 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|s|impf|indc}} tb99ieyzoy37do0wns8fsdh7pslpf62 beatificem 0 3546901 9943473 2026-10-13T01:52:25Z TongcyBot 83009 半自動導入非詞元形式 9943473 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificem}} # {{inflection of|la|beātificō||1|s|pres|actv|subj}} c3scgcs5h8lmzlo6kbfwtlzozedauhp beatificemini 0 3546902 9943474 2026-10-13T01:52:26Z TongcyBot 83009 半自動導入非詞元形式 9943474 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificēminī}} # {{inflection of|la|beātificō||2|p|pres|pasv|subj}} 5pqsmyp97jeav84kvg8o5tctrx6a903 beatificemur 0 3546903 9943475 2026-10-13T01:52:28Z TongcyBot 83009 半自動導入非詞元形式 9943475 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificēmur}} # {{inflection of|la|beātificō||1|p|pres|pasv|subj}} d33m2fs0f55zf021yveymdycw1neiiu beatificemus 0 3546904 9943476 2026-10-13T01:52:29Z TongcyBot 83009 半自動導入非詞元形式 9943476 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificēmus}} # {{inflection of|la|beātificō||1|p|pres|actv|subj}} mkptbugrvkqbtg12i7hqljd7t371ajk beatificent 0 3546905 9943477 2026-10-13T01:52:29Z TongcyBot 83009 半自動導入非詞元形式 9943477 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificent}} # {{inflection of|la|beātificō||3|p|pres|actv|subj}} pqfrfmehn9fbxm6xbxsfwknwhhfm93f beatificentur 0 3546906 9943478 2026-10-13T01:52:31Z TongcyBot 83009 半自動導入非詞元形式 9943478 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificentur}} # {{inflection of|la|beātificō||3|p|pres|pasv|subj}} bsulu8soug64vk77gqf54d9rse9uvbw beatificer 0 3546907 9943479 2026-10-13T01:52:32Z TongcyBot 83009 半自動導入非詞元形式 9943479 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificer}} # {{inflection of|la|beātificō||1|s|pres|pasv|subj}} jm7t6gh7yl32j6ptykbnz7smr6qpoov beatificere 0 3546908 9943480 2026-10-13T01:52:33Z TongcyBot 83009 半自動導入非詞元形式 9943480 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificēre}} # {{inflection of|la|beātificō||2|s|pres|pasv|subj}} 4brkdl54eiqyvghkdd5ye26dn3165xf beatificeris 0 3546909 9943481 2026-10-13T01:52:34Z TongcyBot 83009 半自動導入非詞元形式 9943481 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificēris}} # {{inflection of|la|beātificō||2|s|pres|pasv|subj}} bhgla4ygcbnv6oh5ya6mj8kotcber9h beatifices 0 3546910 9943482 2026-10-13T01:52:35Z TongcyBot 83009 半自動導入非詞元形式 9943482 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificēs}} # {{inflection of|la|beātificō||2|s|pres|actv|subj}} 3yrko99389h8djpqwbwfv6fzdsz5nsf beatificet 0 3546911 9943483 2026-10-13T01:52:37Z TongcyBot 83009 半自動導入非詞元形式 9943483 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificet}} # {{inflection of|la|beātificō||3|s|pres|actv|subj}} ck2b674j6gkb1eseleohu8v1u914t0o beatificetis 0 3546912 9943484 2026-10-13T01:52:38Z TongcyBot 83009 半自動導入非詞元形式 9943484 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificētis}} # {{inflection of|la|beātificō||2|p|pres|actv|subj}} mt3xkqxr8wm26agebco5ozp8vhow4nm beatificetur 0 3546913 9943485 2026-10-13T01:52:39Z TongcyBot 83009 半自動導入非詞元形式 9943485 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificētur}} # {{inflection of|la|beātificō||3|s|pres|pasv|subj}} cbfmwrqmv8elh8f7mpcv3vqj5spkgve beatificherai 0 3546914 9943486 2026-10-13T01:52:40Z TongcyBot 83009 半自動導入非詞元形式 9943486 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|s|futr}} sb1camws1fwq12mktfi0yb525mkf3ed beatificheranno 0 3546915 9943487 2026-10-13T01:52:41Z TongcyBot 83009 半自動導入非詞元形式 9943487 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|p|futr}} s008slhqbqhshgs4y6se3x0hikib6bf beatificherebbe 0 3546916 9943488 2026-10-13T01:52:42Z TongcyBot 83009 半自動導入非詞元形式 9943488 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|s|cond}} bolzketc93cr2pulm6qr1aridlwdq1n beatificherebbero 0 3546917 9943489 2026-10-13T01:52:43Z TongcyBot 83009 半自動導入非詞元形式 9943489 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|p|cond}} oxsjrb0ipdjc7nhgh3lj6mpqtxv31j3 beatificherei 0 3546918 9943490 2026-10-13T01:52:44Z TongcyBot 83009 半自動導入非詞元形式 9943490 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|s|cond}} jxyx43nkd9n2ndyowlunpcxdwa9u2f4 beatificheremmo 0 3546919 9943491 2026-10-13T01:52:45Z TongcyBot 83009 半自動導入非詞元形式 9943491 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|p|cond}} 4greipyb9bu0fyhck32576we89stli2 beatificheremo 0 3546920 9943492 2026-10-13T01:52:46Z TongcyBot 83009 半自動導入非詞元形式 9943492 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|p|futr}} 6r4qhx82kcy8xuehgy71tmhud1e699f beatifichereste 0 3546921 9943493 2026-10-13T01:52:47Z TongcyBot 83009 半自動導入非詞元形式 9943493 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|p|cond}} 0i9fdo6e4wbufx1qii1s5jxih3v3cy9 beatificheresti 0 3546922 9943494 2026-10-13T01:52:48Z TongcyBot 83009 半自動導入非詞元形式 9943494 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|s|cond}} hudtjomiqftnl7b0ukt5x1q68qbsp9b beatificherete 0 3546923 9943495 2026-10-13T01:52:50Z TongcyBot 83009 半自動導入非詞元形式 9943495 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|p|futr}} 3krrs7kusoh2aswabiohr41p2khy0q4 beatificherà 0 3546924 9943496 2026-10-13T01:52:51Z TongcyBot 83009 半自動導入非詞元形式 9943496 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|s|futr}} sj4agykczwtghbwo3cr5akb883uhewd beatificherò 0 3546925 9943497 2026-10-13T01:52:52Z TongcyBot 83009 半自動導入非詞元形式 9943497 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|s|futr}} qzmu5dzpvxfo8kaib2f51mmdhovfczw beatifichi 0 3546926 9943498 2026-10-13T01:52:53Z TongcyBot 83009 半自動導入非詞元形式 9943498 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|s|pres|indc|;|1//2//3|s|pres|subj|;|3|s|impr}} axewxqpezv4u21htxbdkzq4l23stzvp beatifichiamo 0 3546927 9943499 2026-10-13T01:52:54Z TongcyBot 83009 半自動導入非詞元形式 9943499 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||1|p|pres|ind//sub|;|1|p|impr}} 4t4l6jv3nwvu5am23903f6l84jntcqs beatifichiate 0 3546928 9943500 2026-10-13T01:52:55Z TongcyBot 83009 半自動導入非詞元形式 9943500 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||2|p|pres|subj}} j7tj55i4gaws92pgp8r2za5yc6ohan2 beatifichino 0 3546929 9943501 2026-10-13T01:52:56Z TongcyBot 83009 半自動導入非詞元形式 9943501 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|p|pres|subj|;|3|p|impr}} ===異序詞=== * {{anagrams|it|a=abcefhiiinot|anfibiotiche|bonifichiate}} nhnrg050bx8any6q5w6qmhvyr5spfhu beatificor 0 3546930 9943502 2026-10-13T01:52:57Z TongcyBot 83009 半自動導入非詞元形式 9943502 wikitext text/x-wiki ==拉丁語== ===動詞=== {{head|la|動詞變位形式|head=beātificor}} # {{inflection of|la|beātificō||1|s|pres|pasv|indc}} rjto6s271rh4sern9yemt5l5iio0b9z beatificou 0 3546931 9943503 2026-10-13T01:52:58Z TongcyBot 83009 半自動導入非詞元形式 9943503 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} eq34y3k0kfo0mwd57chp3dage3icyir beatificà 0 3546932 9943504 2026-10-13T01:52:59Z TongcyBot 83009 半自動導入非詞元形式 9943504 wikitext text/x-wiki {{also|beatifica|beatificá}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 7c9vwxzgh1lhc9gl5r8tadxxgndel4m beatificàrem 0 3546933 9943505 2026-10-13T01:53:00Z TongcyBot 83009 半自動導入非詞元形式 9943505 wikitext text/x-wiki {{also|beatificarem}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} pjtihpp9ktoy45vhh34rr7wd4zwib7g beatificàreu 0 3546934 9943506 2026-10-13T01:53:01Z TongcyBot 83009 半自動導入非詞元形式 9943506 wikitext text/x-wiki {{also|beatificareu}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} k6qkkd4u0h3xfsdoby2darn51qfj3op beatificàvem 0 3546935 9943507 2026-10-13T01:53:02Z TongcyBot 83009 半自動導入非詞元形式 9943507 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificàveu 0 3546936 9943508 2026-10-13T01:53:04Z TongcyBot 83009 半自動導入非詞元形式 9943508 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatificá 0 3546937 9943509 2026-10-13T01:53:05Z TongcyBot 83009 半自動導入非詞元形式 9943509 wikitext text/x-wiki {{also|beatifica|beatificà}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} n3bktwq21bjk9ebo44i5wb33xh8mivh beatificábamos 0 3546938 9943510 2026-10-13T01:53:06Z TongcyBot 83009 半自動導入非詞元形式 9943510 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificáis 0 3546939 9943511 2026-10-13T01:53:07Z TongcyBot 83009 半自動導入非詞元形式 9943511 wikitext text/x-wiki {{also|beatificais}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} ilvcoydgkfmzzgf2pl98filc8406k9w beatificámos 0 3546940 9943512 2026-10-13T01:53:08Z TongcyBot 83009 半自動導入非詞元形式 9943512 wikitext text/x-wiki {{also|beatificamos}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} 2d7fc8h93u5aig4uojaifdc4hu8cq5q beatificándola 0 3546941 9943513 2026-10-13T01:53:09Z TongcyBot 83009 半自動導入非詞元形式 9943513 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándolas 0 3546942 9943514 2026-10-13T01:53:10Z TongcyBot 83009 半自動導入非詞元形式 9943514 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándole 0 3546943 9943515 2026-10-13T01:53:11Z TongcyBot 83009 半自動導入非詞元形式 9943515 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándoles 0 3546944 9943516 2026-10-13T01:53:12Z TongcyBot 83009 半自動導入非詞元形式 9943516 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándolo 0 3546945 9943517 2026-10-13T01:53:13Z TongcyBot 83009 半自動導入非詞元形式 9943517 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándolos 0 3546946 9943518 2026-10-13T01:53:14Z TongcyBot 83009 半自動導入非詞元形式 9943518 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándome 0 3546947 9943519 2026-10-13T01:53:16Z TongcyBot 83009 半自動導入非詞元形式 9943519 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándonos 0 3546948 9943520 2026-10-13T01:53:17Z TongcyBot 83009 半自動導入非詞元形式 9943520 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándoos 0 3546949 9943521 2026-10-13T01:53:18Z TongcyBot 83009 半自動導入非詞元形式 9943521 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándose 0 3546950 9943522 2026-10-13T01:53:19Z TongcyBot 83009 半自動導入非詞元形式 9943522 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificándote 0 3546951 9943523 2026-10-13T01:53:20Z TongcyBot 83009 半自動導入非詞元形式 9943523 wikitext text/x-wiki ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} b3x4g3pdksemlmkzkx72c0n6tb7bt3p beatificáramos 0 3546952 9943524 2026-10-13T01:53:21Z TongcyBot 83009 半自動導入非詞元形式 9943524 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} bitsp1laumrp5c9jq5ayptoesqaw5ey beatificáreis 0 3546953 9943525 2026-10-13T01:53:23Z TongcyBot 83009 半自動導入非詞元形式 9943525 wikitext text/x-wiki {{also|beatificareis|beatificaréis}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} 2b8xewxrrgl7ekb85scde06cs3gahcs beatificáremos 0 3546954 9943526 2026-10-13T01:53:24Z TongcyBot 83009 半自動導入非詞元形式 9943526 wikitext text/x-wiki {{also|beatificaremos}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} kvzbvtoat2s3w9n22v51hzuyc3j35c4 beatificás 0 3546955 9943527 2026-10-13T01:53:25Z TongcyBot 83009 半自動導入非詞元形式 9943527 wikitext text/x-wiki {{also|beatificas}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} mbye6028pl8vk3l8np1fcmu8mfwz0xq beatificásedes 0 3546956 9943528 2026-10-13T01:53:27Z TongcyBot 83009 半自動導入非詞元形式 9943528 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatificásemos 0 3546957 9943529 2026-10-13T01:53:28Z TongcyBot 83009 半自動導入非詞元形式 9943529 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} o0012xu2udzbq3og8sd9e8fwpi8srsq beatificásseis 0 3546958 9943530 2026-10-13T01:53:29Z TongcyBot 83009 半自動導入非詞元形式 9943530 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificássemos 0 3546959 9943531 2026-10-13T01:53:30Z TongcyBot 83009 半自動導入非詞元形式 9943531 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificávamos 0 3546960 9943532 2026-10-13T01:53:32Z TongcyBot 83009 半自動導入非詞元形式 9943532 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificáveis 0 3546961 9943533 2026-10-13T01:53:33Z TongcyBot 83009 半自動導入非詞元形式 9943533 wikitext text/x-wiki ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} rkrbz37vfrkn2m9r3qv0yi34sonupnj beatificò 0 3546962 9943534 2026-10-13T01:53:34Z TongcyBot 83009 半自動導入非詞元形式 9943534 wikitext text/x-wiki {{also|beatifico|beatificó}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|beatificare||3|s|phis}} 0r1mebotkdeuyytbj7m1iqzxilm3prb beatificó 0 3546963 9943535 2026-10-13T01:53:35Z TongcyBot 83009 半自動導入非詞元形式 9943535 wikitext text/x-wiki {{also|beatifico|beatificò}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} s6ljktqhgaqdcyyxo90zakimg0oosoc beatifique 0 3546964 9943536 2026-10-13T01:53:36Z TongcyBot 83009 半自動導入非詞元形式 9943536 wikitext text/x-wiki {{also|beatifiqué|béatifique}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 8c35f48om2oipdw2lija5vpyok9ah7i beatifiquedes 0 3546965 9943537 2026-10-13T01:53:37Z TongcyBot 83009 半自動導入非詞元形式 9943537 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} 5ji96ll05n83mwbztwsg28yaxeljutf beatifiquei 0 3546966 9943538 2026-10-13T01:53:38Z TongcyBot 83009 半自動導入非詞元形式 9943538 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} eq34y3k0kfo0mwd57chp3dage3icyir beatifiqueis 0 3546967 9943539 2026-10-13T01:53:39Z TongcyBot 83009 半自動導入非詞元形式 9943539 wikitext text/x-wiki {{also|beatifiquéis}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} 3bdp27rq7ssv56tqutxnzswx6jdkm4f beatifiquem 0 3546968 9943540 2026-10-13T01:53:41Z TongcyBot 83009 半自動導入非詞元形式 9943540 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} r5nweqqtdscl9x8ge9k3ijmzyaod5uh beatifiquemos 0 3546969 9943541 2026-10-13T01:53:42Z TongcyBot 83009 半自動導入非詞元形式 9943541 wikitext text/x-wiki ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} ny6zsd7lv6db3wwkataw7dbxo6jdgmk beatifiquen 0 3546970 9943542 2026-10-13T01:53:43Z TongcyBot 83009 半自動導入非詞元形式 9943542 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} i9s272c6gh8n5u1abq8wk1gtjd4j6zw beatifiques 0 3546971 9943543 2026-10-13T01:53:44Z TongcyBot 83009 半自動導入非詞元形式 9943543 wikitext text/x-wiki {{also|beatifiqués|béatifiques}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==加利西亞語== ===動詞=== {{head|gl|動詞變位形式}} # {{gl-verb form of|beatificar}} ==葡萄牙語== ===動詞=== {{head|pt|動詞變位形式}} # {{pt-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} mdulinhxsfkqj4p6h6dhz93eme0xy8e beatifiquessin 0 3546972 9943544 2026-10-13T01:53:45Z TongcyBot 83009 半自動導入非詞元形式 9943544 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatifiquessis 0 3546973 9943545 2026-10-13T01:53:46Z TongcyBot 83009 半自動導入非詞元形式 9943545 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatifiqueu 0 3546974 9943546 2026-10-13T01:53:47Z TongcyBot 83009 半自動導入非詞元形式 9943546 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatifiqui 0 3546975 9943547 2026-10-13T01:53:48Z TongcyBot 83009 半自動導入非詞元形式 9943547 wikitext text/x-wiki {{also|beatifiquí}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ghbml7rnjpl7tee4rqcjs2arctx5mj2 beatifiquin 0 3546976 9943548 2026-10-13T01:53:49Z TongcyBot 83009 半自動導入非詞元形式 9943548 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatifiquis 0 3546977 9943549 2026-10-13T01:53:50Z TongcyBot 83009 半自動導入非詞元形式 9943549 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatifiqué 0 3546978 9943550 2026-10-13T01:53:51Z TongcyBot 83009 半自動導入非詞元形式 9943550 wikitext text/x-wiki {{also|beatifique|béatifique}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} awn5rbitb2xadzxdybl9yq76dtx03tv beatifiquéis 0 3546979 9943551 2026-10-13T01:53:53Z TongcyBot 83009 半自動導入非詞元形式 9943551 wikitext text/x-wiki {{also|beatifiqueis}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} 56zqhsxwllsxn8t970t3x71pzywld2f beatifiqués 0 3546980 9943552 2026-10-13T01:53:54Z TongcyBot 83009 半自動導入非詞元形式 9943552 wikitext text/x-wiki {{also|beatifiques|béatifiques}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} ==西班牙語== ===動詞=== {{head|es|動詞變位形式}} # {{es-verb form of|beatificar}} nuq9l34ujum8k37y43mu874dhh7gbq2 beatifiquéssim 0 3546981 9943553 2026-10-13T01:53:55Z TongcyBot 83009 半自動導入非詞元形式 9943553 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatifiquéssiu 0 3546982 9943554 2026-10-13T01:53:56Z TongcyBot 83009 半自動導入非詞元形式 9943554 wikitext text/x-wiki ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 0m0rl0zryt7pj2g7cqxz4bylc6wkax2 beatifiquí 0 3546983 9943555 2026-10-13T01:53:57Z TongcyBot 83009 半自動導入非詞元形式 9943555 wikitext text/x-wiki {{also|beatifiqui}} ==加泰羅尼亞語== ===動詞=== {{head|ca|動詞變位形式}} # {{ca-verb form of|beatificar}} 3lpq0jg3d3zgu7ezrp2m3czw62mgzrw borbotta 0 3546984 9943556 2026-10-13T01:53:58Z TongcyBot 83009 半自動導入非詞元形式 9943556 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|s|pres|indc|;|2|s|impr}} kse2zzy70xtaip3mghzvygdsrn3d5xm borbottai 0 3546985 9943557 2026-10-13T01:53:59Z TongcyBot 83009 半自動導入非詞元形式 9943557 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|s|phis}} dzdwopff3d70zsrnpyiykpf7yum5byh borbottammo 0 3546986 9943558 2026-10-13T01:54:00Z TongcyBot 83009 半自動導入非詞元形式 9943558 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|p|phis}} 75zz0md5yzzsg2zobq3bsgcrzg3ypsg borbottano 0 3546987 9943559 2026-10-13T01:54:01Z TongcyBot 83009 半自動導入非詞元形式 9943559 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|p|pres|indc}} o062bovxwhhpv2holx2yduu9fhwh48k borbottarono 0 3546988 9943560 2026-10-13T01:54:02Z TongcyBot 83009 半自動導入非詞元形式 9943560 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|p|phis}} qlibonyf97o41kgd4imdafsn95o1ec3 borbottasse 0 3546989 9943561 2026-10-13T01:54:03Z TongcyBot 83009 半自動導入非詞元形式 9943561 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|s|impf|subj}} r96jee0fvzigyeu4phax8w14efsnf84 borbottassero 0 3546990 9943562 2026-10-13T01:54:06Z TongcyBot 83009 半自動導入非詞元形式 9943562 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|p|impf|subj}} 9wx71g942t766iknya1glqjnzpt9115 borbottassi 0 3546991 9943563 2026-10-13T01:54:06Z TongcyBot 83009 半自動導入非詞元形式 9943563 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1//2|s|impf|subj}} ldj08bmc8iwgq5aq5drv626zod0ic42 borbottassimo 0 3546992 9943564 2026-10-13T01:54:08Z TongcyBot 83009 半自動導入非詞元形式 9943564 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|p|impf|subj}} r4sgaewn2888eou4c5wx0954xoow5x3 borbottaste 0 3546993 9943565 2026-10-13T01:54:08Z TongcyBot 83009 半自動導入非詞元形式 9943565 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|p|phis|;|2|p|impf|subj}} jmzpotgvp7ir17wn9vziaf4s64d27t0 borbottasti 0 3546994 9943566 2026-10-13T01:54:09Z TongcyBot 83009 半自動導入非詞元形式 9943566 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|s|phis}} tnp8cjgk6adlpsjjm1ytdunb48w5qx2 borbottava 0 3546995 9943567 2026-10-13T01:54:10Z TongcyBot 83009 半自動導入非詞元形式 9943567 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|s|impf|indc}} sre2onfia5pksk32hfw8fsx867ioaug borbottavamo 0 3546996 9943568 2026-10-13T01:54:12Z TongcyBot 83009 半自動導入非詞元形式 9943568 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|p|impf|indc}} 0luc82o261f17w9dl2qg5y27f26alnj borbottavano 0 3546997 9943569 2026-10-13T01:54:13Z TongcyBot 83009 半自動導入非詞元形式 9943569 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|p|impf|indc}} 517uizesi0ko9634r9qgpifdy62b16p borbottavate 0 3546998 9943570 2026-10-13T01:54:14Z TongcyBot 83009 半自動導入非詞元形式 9943570 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|p|impf|indc}} 3g4b4sk0x4spgn9ab66gt8yh7hl4fbk borbottavi 0 3546999 9943571 2026-10-13T01:54:15Z TongcyBot 83009 半自動導入非詞元形式 9943571 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|s|impf|indc}} gpu21bdtguh0i39xamxwls9izs5kxci borbottavo 0 3547000 9943572 2026-10-13T01:54:16Z TongcyBot 83009 半自動導入非詞元形式 9943572 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|s|impf|indc}} 3bhq7l7lnqk1s39eumn4y0qgx5gufd0 borbotterai 0 3547001 9943573 2026-10-13T01:54:17Z TongcyBot 83009 半自動導入非詞元形式 9943573 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|s|futr}} 36sjr2gm1r92txfim2m1y603iaqznp9 borbotteranno 0 3547002 9943574 2026-10-13T01:54:19Z TongcyBot 83009 半自動導入非詞元形式 9943574 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|p|futr}} t6bi8bks7io97wbik27v4ucbbamvctq borbotterebbe 0 3547003 9943575 2026-10-13T01:54:20Z TongcyBot 83009 半自動導入非詞元形式 9943575 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|s|cond}} rhc0z3gf3cgliwleh8yjfvanlfpq3qh borbotterebbero 0 3547004 9943576 2026-10-13T01:54:21Z TongcyBot 83009 半自動導入非詞元形式 9943576 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|p|cond}} 3sj0oear137fivdlaszwv9uok5yycp0 borbotterei 0 3547005 9943577 2026-10-13T01:54:22Z TongcyBot 83009 半自動導入非詞元形式 9943577 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|s|cond}} ag59rh0xbgqzl4q8it6nwpx3zz1jfoz borbotteremmo 0 3547006 9943578 2026-10-13T01:54:23Z TongcyBot 83009 半自動導入非詞元形式 9943578 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|p|cond}} gfpw5bcfc2127funqyau0fjy1fw4fpu borbotteremo 0 3547007 9943579 2026-10-13T01:54:24Z TongcyBot 83009 半自動導入非詞元形式 9943579 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|p|futr}} 2xvdtgrufgzcnk46wagm52y8ojsmi6t borbottereste 0 3547008 9943580 2026-10-13T01:54:25Z TongcyBot 83009 半自動導入非詞元形式 9943580 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|p|cond}} 8waeg24y865gyn2ifq9pupbbreu1bo4 borbotteresti 0 3547009 9943581 2026-10-13T01:54:26Z TongcyBot 83009 半自動導入非詞元形式 9943581 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|s|cond}} p2s8pj9dvk0o5vm7bcqbk9yf76qwdt5 borbotterete 0 3547010 9943582 2026-10-13T01:54:27Z TongcyBot 83009 半自動導入非詞元形式 9943582 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|p|futr}} gknxohlrraqijuq2o2pg95tue9z7tkf borbotterà 0 3547011 9943583 2026-10-13T01:54:29Z TongcyBot 83009 半自動導入非詞元形式 9943583 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|s|futr}} ===異序詞=== * {{anagrams|it|a=abbeoorrtt|borbottare}} ly55blvsunxse6fvgz4hn2vzub52igt borbotterò 0 3547012 9943584 2026-10-13T01:54:30Z TongcyBot 83009 半自動導入非詞元形式 9943584 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|s|futr}} g3jbhvuztlbd5ncogf695ceaunjt5e5 borbotti 0 3547013 9943585 2026-10-13T01:54:31Z TongcyBot 83009 半自動導入非詞元形式 9943585 wikitext text/x-wiki {{also|Borbotti}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|s|pres|indc|;|1//2//3|s|pres|subj|;|3|s|impr}} 6idm79j21s3w4euatulzc8zkh5qz54h borbottiate 0 3547014 9943586 2026-10-13T01:54:33Z TongcyBot 83009 半自動導入非詞元形式 9943586 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||2|p|pres|subj}} gf6nrq1jb2e7s1vqzxbzh2a0r2kl2eq borbottiamo 0 3547015 9943587 2026-10-13T01:54:34Z TongcyBot 83009 半自動導入非詞元形式 9943587 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|p|pres|ind//sub|;|1|p|impr}} r1d9kktqo29tx586kuypksmrykxalbq borbottino 0 3547016 9943588 2026-10-13T01:54:35Z TongcyBot 83009 半自動導入非詞元形式 9943588 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|p|pres|subj|;|3|p|impr}} ===異序詞=== * {{anagrams|it|a=bbinooortt|borbottoni}} lxi8em5agqlgonq6ja993igka3eklno borbotto 0 3547017 9943589 2026-10-13T01:54:36Z TongcyBot 83009 半自動導入非詞元形式 9943589 wikitext text/x-wiki {{also|borbottò}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||1|s|pres|indc}} 1xxojzzkhbrrqcz1yvjvz94t95n8e9y borbottò 0 3547018 9943590 2026-10-13T01:54:37Z TongcyBot 83009 半自動導入非詞元形式 9943590 wikitext text/x-wiki {{also|borbotto}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|borbottare||3|s|phis}} 9obf9od2mspnkzgl4z5ixz7z6xfs9p3 brancola 0 3547019 9943591 2026-10-13T01:54:38Z TongcyBot 83009 半自動導入非詞元形式 9943591 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|s|pres|indc|;|2|s|impr}} edbfrk1s3e7tazbb682gduhm5kun1dj brancolai 0 3547020 9943592 2026-10-13T01:54:39Z TongcyBot 83009 半自動導入非詞元形式 9943592 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|s|phis}} ===異序詞=== * {{anagrams|it|a=aabcilnor|bracaloni|calabroni|calibrano|labronica}} 7c82altivjf20hiyagqohmn18733gga brancolammo 0 3547021 9943593 2026-10-13T01:54:40Z TongcyBot 83009 半自動導入非詞元形式 9943593 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|p|phis}} otbjul1fukuxl0dejw7vkf7vvlhuvll brancolando 0 3547022 9943594 2026-10-13T01:54:41Z TongcyBot 83009 半自動導入非詞元形式 9943594 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動名詞}} # {{gerund of|it|brancolare}} kvo4irriypc3fwa4as1yy70atbnzbdd brancolano 0 3547023 9943595 2026-10-13T01:54:42Z TongcyBot 83009 半自動導入非詞元形式 9943595 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|p|pres|indc}} 7antgjn4nebsij4jqschv8yblocaf69 brancolarono 0 3547024 9943596 2026-10-13T01:54:44Z TongcyBot 83009 半自動導入非詞元形式 9943596 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|p|phis}} rgrhjax8pnpguqmzhy6rig5i6id4c42 brancolasse 0 3547025 9943597 2026-10-13T01:54:45Z TongcyBot 83009 半自動導入非詞元形式 9943597 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|s|impf|subj}} 32332jra5ha9o3z5ixjr7hi6cyqi1ve brancolassero 0 3547026 9943598 2026-10-13T01:54:46Z TongcyBot 83009 半自動導入非詞元形式 9943598 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|p|impf|subj}} 8dgsphj5aqtvt9mbnjcgm8dex67wes8 brancolassi 0 3547027 9943599 2026-10-13T01:54:47Z TongcyBot 83009 半自動導入非詞元形式 9943599 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1//2|s|impf|subj}} iuyv2jv6yma212ckmkuv40clspeazq6 brancolassimo 0 3547028 9943600 2026-10-13T01:54:49Z TongcyBot 83009 半自動導入非詞元形式 9943600 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|p|impf|subj}} ckmob3i3705hcpdhbjhbmpsljiutll1 brancolaste 0 3547029 9943601 2026-10-13T01:54:50Z TongcyBot 83009 半自動導入非詞元形式 9943601 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|p|phis|;|2|p|impf|subj}} l81myr3l70r2o211ygm7pt8uk7188lr brancolasti 0 3547030 9943602 2026-10-13T01:54:51Z TongcyBot 83009 半自動導入非詞元形式 9943602 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|s|phis}} mpnmzmis3vfqdu092o200khgja6zg7x brancolava 0 3547031 9943603 2026-10-13T01:54:52Z TongcyBot 83009 半自動導入非詞元形式 9943603 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|s|impf|indc}} b9xuxwucuxx0dnvp8h70veak0f1svzq brancolavamo 0 3547032 9943604 2026-10-13T01:54:53Z TongcyBot 83009 半自動導入非詞元形式 9943604 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|p|impf|indc}} 6v70do08986wp0hidipr8csesyfitrq brancolavano 0 3547033 9943605 2026-10-13T01:54:54Z TongcyBot 83009 半自動導入非詞元形式 9943605 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|p|impf|indc}} 6lb9mryvdjtvi3rrc4swqep3hl7l80y brancolavate 0 3547034 9943606 2026-10-13T01:54:55Z TongcyBot 83009 半自動導入非詞元形式 9943606 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|p|impf|indc}} fxducqtwaqas89p0zekmh4zq6glzscf brancolavi 0 3547035 9943607 2026-10-13T01:54:56Z TongcyBot 83009 半自動導入非詞元形式 9943607 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|s|impf|indc}} 97surh1frf7nd0q8msfhxk176yse9dx brancolavo 0 3547036 9943608 2026-10-13T01:54:57Z TongcyBot 83009 半自動導入非詞元形式 9943608 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|s|impf|indc}} nzin6oh5nhjr8pblxmgbywktmowxe3h brancolerai 0 3547037 9943609 2026-10-13T01:54:59Z TongcyBot 83009 半自動導入非詞元形式 9943609 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|s|futr}} kk9s4vctjcsxe1mi6yf25vlqjz09uek brancoleranno 0 3547038 9943610 2026-10-13T01:55:00Z TongcyBot 83009 半自動導入非詞元形式 9943610 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|p|futr}} sx1nsyt2dt6k8c413p5gp851lo0j0ct brancolerebbe 0 3547039 9943611 2026-10-13T01:55:01Z TongcyBot 83009 半自動導入非詞元形式 9943611 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|s|cond}} br7fspwtug022ooc0ihmv6gxnriol7i brancolerebbero 0 3547040 9943612 2026-10-13T01:55:02Z TongcyBot 83009 半自動導入非詞元形式 9943612 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|p|cond}} i8etr73qlvo4qbsse2dm8yym2jdzklo brancolerei 0 3547041 9943613 2026-10-13T01:55:03Z TongcyBot 83009 半自動導入非詞元形式 9943613 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|s|cond}} ===異序詞=== * {{anagrams|it|a=abceeilnorr|ricelebrano}} 9ia91ga6qjfswusihrilbjmqff50qci brancoleremmo 0 3547042 9943614 2026-10-13T01:55:04Z TongcyBot 83009 半自動導入非詞元形式 9943614 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|p|cond}} ie71osxulh6ks0hkucax0ekx5xdrwcf brancoleremo 0 3547043 9943615 2026-10-13T01:55:05Z TongcyBot 83009 半自動導入非詞元形式 9943615 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|p|futr}} j3csmod8xbhigfn1dy388t67f9z52co brancolereste 0 3547044 9943616 2026-10-13T01:55:06Z TongcyBot 83009 半自動導入非詞元形式 9943616 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|p|cond}} au5481hiprxreclvnd10nmk4sakd2pj brancoleresti 0 3547045 9943617 2026-10-13T01:55:07Z TongcyBot 83009 半自動導入非詞元形式 9943617 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|s|cond}} 2p2196a3edr3grt9lqkvd6a8orw8rf8 brancolerete 0 3547046 9943618 2026-10-13T01:55:08Z TongcyBot 83009 半自動導入非詞元形式 9943618 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|p|futr}} t098hutuv6tyivks2bj5j3znl3ricx0 brancolerà 0 3547047 9943619 2026-10-13T01:55:09Z TongcyBot 83009 半自動導入非詞元形式 9943619 wikitext text/x-wiki ==意大利語== ===發音=== {{it-pr}} ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|s|futr}} ===異序詞=== * {{anagrams|it|a=aabcelnorr|brancolare}} q7zfdcpw1wbfmtkjtn6kznr5g6z1vjc brancolerò 0 3547048 9943620 2026-10-13T01:55:11Z TongcyBot 83009 半自動導入非詞元形式 9943620 wikitext text/x-wiki ==意大利語== ===發音=== {{it-pr}} ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|s|futr}} 9g5dllpj5gjo0xnss5bnlywtfeersy9 brancoli 0 3547049 9943621 2026-10-13T01:55:12Z TongcyBot 83009 半自動導入非詞元形式 9943621 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|s|pres|indc|;|1//2//3|s|pres|subj|;|3|s|impr}} pfmnnewwy8z3mn7vim27e749zxesb3e brancoliamo 0 3547050 9943622 2026-10-13T01:55:13Z TongcyBot 83009 半自動導入非詞元形式 9943622 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|p|pres|ind//sub|;|1|p|impr}} ===異序詞=== * {{anagrams|it|a=aabcilmnoor|carambolino|monocalibra}} idhc6oumewcpptcpra22xk4q8fap8fv brancoliate 0 3547051 9943623 2026-10-13T01:55:14Z TongcyBot 83009 半自動導入非詞元形式 9943623 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||2|p|pres|subj}} ===異序詞=== * {{anagrams|it|a=aabceilnort|tabernacoli}} r7ym32s08wyp3cd58rdgeyi7wvwoyyc brancolino 0 3547052 9943624 2026-10-13T01:55:15Z TongcyBot 83009 半自動導入非詞元形式 9943624 wikitext text/x-wiki ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|p|pres|subj|;|3|p|impr}} on0zurp2hicz1vj1vnftfzd9lyfp959 brancolo 0 3547053 9943625 2026-10-13T01:55:16Z TongcyBot 83009 半自動導入非詞元形式 9943625 wikitext text/x-wiki {{also|brancolò}} ==意大利語== ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||1|s|pres|indc}} jsl2w8jf5ym3d9oqdb05fc0zllbzo5m brancolò 0 3547054 9943626 2026-10-13T01:55:17Z TongcyBot 83009 半自動導入非詞元形式 9943626 wikitext text/x-wiki {{also|brancolo}} ==意大利語== ===發音=== {{it-pr}} ===動詞=== {{head|it|動詞變位形式}} # {{inflection of|it|brancolare||3|s|phis}} d1l9yxpjrkbianzx75483sawn1930eb biodegradabili 0 3547055 9943629 2026-10-13T02:03:22Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|biodegradabile}}」的新頁面 9943629 wikitext text/x-wiki ==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|biodegradabile}} q7xocya6pa1j4mw17pj0swmxxgf37ox biodegradabilità 0 3547056 9943630 2026-10-13T02:04:03Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===名詞=== {{it-noun|f|#}} # [[生物]][[降解]]性 ====相關詞彙==== * {{l|it|biodegradabile}} * {{l|it|biodegradare}} * {{l|it|biodegradazione}}」的新頁面 9943630 wikitext text/x-wiki ==意大利語== ===名詞=== {{it-noun|f|#}} # [[生物]][[降解]]性 ====相關詞彙==== * {{l|it|biodegradabile}} * {{l|it|biodegradare}} * {{l|it|biodegradazione}} q0ogz5tjp1lpxvyom4qnbczrrcfupml degradabile 0 3547057 9943631 2026-10-13T02:04:48Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===詞源=== {{ety|it|:af|degradare|-abile}} 源自 {{suffix|it|degradare|abile}}。 ===發音=== {{it-pr}} ===形容詞=== {{it-adj}} # 可[[降級]]的;可[[貶黜]]的 # 可[[降解]]的 ====派生詞彙==== * {{l|it|biodegradabile}} * {{l|it|degradabilità}}」的新頁面 9943631 wikitext text/x-wiki ==意大利語== ===詞源=== {{ety|it|:af|degradare|-abile}} 源自 {{suffix|it|degradare|abile}}。 ===發音=== {{it-pr}} ===形容詞=== {{it-adj}} # 可[[降級]]的;可[[貶黜]]的 # 可[[降解]]的 ====派生詞彙==== * {{l|it|biodegradabile}} * {{l|it|degradabilità}} qoevgcfx727qpjk3r8yc3fpodkc6wl0 degradabili 0 3547058 9943632 2026-10-13T02:04:54Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|degradabile}}」的新頁面 9943632 wikitext text/x-wiki ==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|degradabile}} hie53rzpjn9ecjttefsryse3f86g1u3 bidimensionali 0 3547059 9943634 2026-10-13T02:05:17Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|bidimensionale}}」的新頁面 9943634 wikitext text/x-wiki ==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|bidimensionale}} iewe8072lj5xzcsnkyr009slbe0z5au biciclette 0 3547060 9943636 2026-10-13T02:06:33Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===名詞=== {{head|it|名詞變格形|g=f}} # {{plural of|it|bicicletta}}」的新頁面 9943636 wikitext text/x-wiki ==意大利語== ===名詞=== {{head|it|名詞變格形|g=f}} # {{plural of|it|bicicletta}} nfp7kd0f6qwq347324qjy6adwdh40ms bilaterali 0 3547061 9943638 2026-10-13T02:08:55Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|bilaterale}} ===異序詞=== * {{anagrams|it|a=aabeiillrt|allibriate|alterabili|liberalità}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=bilaterālī}} # {{inflection of|la|bilaterālis||dat//abl|m//f//n|s}} ==馬爾他語== ===詞源=== {{ety|mt|:bor|it:bilaterale|text=+|tree=1}} ===發音=== * {{IPA|mt|/bɪ.la.tɛˈraː.lɪ/}} * {{…」的新頁面 9943638 wikitext text/x-wiki ==意大利語== ===形容詞=== {{head|it|形容詞變格形}} # {{plural of|it|bilaterale}} ===異序詞=== * {{anagrams|it|a=aabeiillrt|allibriate|alterabili|liberalità}} ==拉丁語== ===形容詞=== {{head|la|形容詞變格形|head=bilaterālī}} # {{inflection of|la|bilaterālis||dat//abl|m//f//n|s}} ==馬爾他語== ===詞源=== {{ety|mt|:bor|it:bilaterale|text=+|tree=1}} ===發音=== * {{IPA|mt|/bɪ.la.tɛˈraː.lɪ/}} * {{rhymes|mt|aːlɪ|s=5}} ===形容詞=== {{mt-adj|-}} # 兩側的 #* {{quote-book|mt |author=Antoinette Borg |title=Amina |year=2018 |isbn=9789990917765 |publisher=Merlin Publishers |text=Pereżempju qatt innutajtu s-simetrija '''bilaterali''' perfetta tal-ġwienaħ ta’ farfett, tad-denb ta’ pagun, jew għall-inqas tal-wiċċ uman? |t=例如,你是否注意到蝴蝶翅膀、孔雀尾巴,或者至少是人脸的完美'''两侧'''对称性? }} # [[双边]]的,[[雙方]]的 afxxxy0j8x98oo5zjq2w21wqibooqd9 britannici 0 3547062 9943640 2026-10-13T02:10:19Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===發音=== {{it-pr|^à}} ===形容詞=== {{head|it|形容詞變格形}} # {{adj form of|it|britannico||m|p}} ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|britannico}} ==拉丁語== ===發音=== * {{la-IPA|britannicī}} ===形容詞=== {{head|la|形容詞變格形|head=britannicī}} # {{inflection of|la|britannicus||nom//voc|m|p|;|gen|m//n|s}}」的新頁面 9943640 wikitext text/x-wiki ==意大利語== ===發音=== {{it-pr|^à}} ===形容詞=== {{head|it|形容詞變格形}} # {{adj form of|it|britannico||m|p}} ===名詞=== {{head|it|名詞變格形|g=m}} # {{plural of|it|britannico}} ==拉丁語== ===發音=== * {{la-IPA|britannicī}} ===形容詞=== {{head|la|形容詞變格形|head=britannicī}} # {{inflection of|la|britannicus||nom//voc|m|p|;|gen|m//n|s}} 09l0curneyc2ri0ai3s5z396ixof0zp britanniche 0 3547063 9943641 2026-10-13T02:10:21Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== ===發音=== {{it-pr|^à}} ===形容詞=== {{head|it|形容詞變格形}} # {{adj form of|it|britannico||f|p}} ===名詞=== {{head|it|名詞變格形|g=f}} # {{plural of|it|britannica}}」的新頁面 9943641 wikitext text/x-wiki ==意大利語== ===發音=== {{it-pr|^à}} ===形容詞=== {{head|it|形容詞變格形}} # {{adj form of|it|britannico||f|p}} ===名詞=== {{head|it|名詞變格形|g=f}} # {{plural of|it|britannica}} 8bapv623x4p77r31bvnn5rcegh6xcah Gran Bretagna 0 3547064 9943642 2026-10-13T02:12:44Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「==意大利語== {{wp|it:}} ===發音=== {{it-pr|Gran_Bretàgna}} ===專有名詞=== {{it-proper noun|f|head=[[gran|Gran]] [[Bretagna]]}} # {{place|it|位於<<r/西歐>>的<<島嶼>>(有時也包括一些周圍的較小島嶼),由[[英格蘭]]、[[蘇格蘭]]和[[威爾斯]]組成|t=大不列顛}} #: {{hyper|it|Regno Unito|Regno Unito di Gran Bretagna e Irlanda del Nord}} #: {{hypo|it|Inghilterra|Galles|Scozia}} # {{lb|it|loosely}} {…」的新頁面 9943642 wikitext text/x-wiki ==意大利語== {{wp|it:}} ===發音=== {{it-pr|Gran_Bretàgna}} ===專有名詞=== {{it-proper noun|f|head=[[gran|Gran]] [[Bretagna]]}} # {{place|it|位於<<r/西歐>>的<<島嶼>>(有時也包括一些周圍的較小島嶼),由[[英格蘭]]、[[蘇格蘭]]和[[威爾斯]]組成|t=大不列顛}} #: {{hyper|it|Regno Unito|Regno Unito di Gran Bretagna e Irlanda del Nord}} #: {{hypo|it|Inghilterra|Galles|Scozia}} # {{lb|it|loosely}} {{place|it|[[英國]](<<r/西歐>><<國家>>)|cat=英國}} ====參見==== * {{l|it|Bretagna}} avjlqre3g64s06azp3ocddo5j37hr9r kōshi 0 3547065 9943676 2026-10-13T11:38:41Z Sayonzei 40728 [[w:WP:AES|←]]建立內容為「{{also|Kōshi}} ==日語== ===羅馬化=== {{ja-romaji}} # {{ja-romanization of|こうし}}」的新頁面 9943676 wikitext text/x-wiki {{also|Kōshi}} ==日語== ===羅馬化=== {{ja-romaji}} # {{ja-romanization of|こうし}} 57oylhwkwworynarz5zb3i8gfqefa3n