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}} - 有性型
===參考資料===
* {{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, '"') + '">' + 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 <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 = ·
$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, 'http').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> • </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('用<noinclude>包裹保护模板', '用<noinclude>包裹保護模板'),
tooltip: wgULS('将保护模板包裹在<noinclude>中', '將保護模板包裹在<noinclude>中'),
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: '使用<noinclude>包裹模板',
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>
|-
| 阿夸佩姆契維語 || <code>Akuapem</code>、<code>ak</code>、<code>阿夸佩姆契維語</code>、<code>阿夸佩姆</code>
|-
| 阿散蒂契維語 || <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