وحدة:Navseasoncats/ملعب

من ويكيبيديا، الموسوعة الحرة
require[[strict]]
local p = {}
local horizontal = require('Module:List').horizontal

--[[==========================================================================]]
--[[								Globals									  ]]
--[[==========================================================================]]

local currtitle = mw.title.getCurrentTitle()
local nexistingcats = 0
local errors = ''
local testcasecolon = ''
local testcases = string.match(currtitle.subpageText, '^testcases') or string.match(currtitle.subpageText, '^مختبر')
if	  testcases then testcasecolon = ':' end
local navborder = true
local followRs = true
local skipgaps = false
local listall = false
local tlistall = {}
local tlistallbwd = {}
local tlistallfwd = {}
local tracking_cats_dir = { --when reindexing, Ctrl+H 'trackcat(1,' & 'ttrackingcats[1]'
	'صيانة شريط تصنيف يستخدم cat',
	'صيانة شريط تصنيف يستخدم testcase',
	'تصنيف موسم يستخدم وسيط غير معروف',
	'صيانة شريط تصنيف لا يستخدم شرطة',
	'صيانة شريط تصنيف مدى مختصر',
	'تصنيف موسم تحويل مدى (تغيير القاعدة)',
	'Navseasoncats range redirected (var change)',
	'تصنيف موسم تحويل مدى (نهاية)',
	'تصنيف موسم تحويل مدى (دليل الأسلوب)',
	'تصنيف موسم تحويل مدى (أخرى)',
	'تصنيف موسم مدى الفارق',
	'تصنيف موسم مدى غير منتظم',
	'تصنيف موسم مدى غير منتظم الطول-0',
	'تصنيف موسم نهاية المدى (حالي)',
	'تصنيف موسم نهاية المدى (فارغ)',
	'صيانة شريط تصنيف معزول',
	'تصنيف موسم حجم الفارق',
	'تصنيف موسم تحويل تصنيف عقد',
	'تصنيف موسم تحويل تصنيف سنة',
	'تصنيف موسم تحويل تصنيف سنة (تغيير)', --new
	'تصنيف موسم تحويل تصنيف سنة (أخرى)',
	'تصنيف موسم تحويل تصنيف الأرقام الرومانية',
	'تصنيف موسم تحويل تصنيف ترتيب',
	'تصنيف موسم تحويل تصنيف ترتيب-كلمة',
	'تصنيف موسم تحويل تصنيف موسم تلفزيون',
	'تصنيف موسم يستخدم وسيط skip-gaps',
	"تصنيف موسم سنة ومدى",
	"تصنيف موسم سنة وعقد",
	"تصنيف موسم عقد وقرن" ,
	"تصنيف موسم في مقالة" ,
	"تصنيف موسم خطأ في إعادة التوجيه",
}
local ttrackingcats = { --when reindexing, Ctrl+H 'trackcat(13,' & 'ttrackingcats[16]'
	'', -- [1] placeholder for [[تصنيف:صيانة شريط تصنيف يستخدم cat]]
	'', -- [2] placeholder for [[تصنيف:صيانة شريط تصنيف يستخدم testcase]]
	'', -- [3] placeholder for [[تصنيف:تصنيف موسم يستخدم وسيط غير معروف]]
	'', -- [4] placeholder for [[تصنيف:صيانة شريط تصنيف لا يستخدم شرطة]]
	'', -- [5] placeholder for [[تصنيف:صيانة شريط تصنيف مدى مختصر]]
	'', -- [6] placeholder for [[تصنيف:تصنيف موسم تحويل مدى (تغيير القاعدة)]]
	'', -- [7] placeholder for [[Category:Navseasoncats range redirected (var change)]] --new
	'', -- [8] placeholder for [[Category:Navseasoncats range redirected (end)]]
	'', -- [9] placeholder for [[تصنيف:تصنيف موسم تحويل مدى (دليل الأسلوب)]]
	'', --[10] placeholder for [[Category:Navseasoncats range redirected (other)]]
	'', --[11] placeholder for [[تصنيف:تصنيف موسم مدى الفارق]]
	'', --[12] placeholder for [[تصنيف:تصنيف موسم مدى غير منتظم]]
	'', --[13] placeholder for [[تصنيف:تصنيف موسم مدى غير منتظم الطول-0]]
	'', --[14] placeholder for [[Category:Navseasoncats range ends (present)]]
	'', --[15] placeholder for [[Category:Navseasoncats range ends (blank, MOS)]]
	'', --[16] placeholder for [[تصنيف:صيانة شريط تصنيف معزول]]
	'', --[17] placeholder for [[تصنيف:تصنيف موسم حجم الفارق]]
	'', --[18] placeholder for [[تصنيف:تصنيف موسم تحويل تصنيف عقد]]
	'', --[19] placeholder for [[تصنيف:تصنيف موسم تحويل تصنيف سنة]]
	'', --[20] placeholder for [[Category:Navseasoncats year redirected (var change)]]
	'', --[21] placeholder for [[تصنيف:تصنيف موسم تحويل تصنيف سنة (أخرى)]]
	'', --[22] placeholder for [[تصنيف:تصنيف موسم تحويل تصنيف الأرقام الرومانية]]
	'', --[23] placeholder for [[تصنيف:تصنيف موسم تحويل تصنيف ترتيب]]
	'', --[24] placeholder for [[تصنيف:تصنيف موسم تحويل تصنيف ترتيب-كلمة]]
	'', --[25] placeholder for [[تصنيف:تصنيف موسم تحويل تصنيف موسم تلفزيون]]
	'', --[26] placeholder for [[تصنيف:تصنيف موسم يستخدم وسيط skip-gaps]]
	'', --[27] placeholder for [[تصنيف:تصنيف موسم سنة ومدى]]
	'', --[28] placeholder for [[تصنيف:تصنيف موسم سنة وعقد]]
	'', --[29] placeholder for [[تصنيف:تصنيف موسم عقد وقرن]]
	'', --[30] placeholder for [[Category:Navseasoncats in mainspace]]
	'', --[31] placeholder for [[Category:Navseasoncats redirection error]]
}
local avoidself =  (not string.match(currtitle.text, 'Navseasoncats with') and
					not string.match(currtitle.text, 'Navseasoncats.*/شرح') and
					not string.match(currtitle.text, 'Navseasoncats.*/ملعب') and

					not string.match(currtitle.text, 'تصنيف موسم.*/شرح') and
					not string.match(currtitle.text, 'تصنيف موسم.*/ملعب') and

					currtitle.text ~= 'Navseasoncats' and
					currtitle.text ~= 'تصنيف_موسم' and 
					currtitle.nsText ~= 'User_talk' and
					currtitle.nsText ~= 'نقاش_المستخدم' and
					currtitle.nsText ~= 'نقاش_المستخدمة' and
					currtitle.nsText ~= 'Template_talk' and
					currtitle.nsText ~= 'نقاش_القالب' and
					(currtitle.nsText ~= 'Template' or currtitle.nsText ~= 'قالب' or testcases)) --avoid nested transclusion errors (i.e. {{Infilmdecade}})

local er_counts = 0
--[[==========================================================================]]
--[[					  Utility & category functions						  ]]
--[[==========================================================================]]

--Determine if a category exists (in a function for easier localization).
local function catexists( title )
	return mw.title.new( title, 'تصنيف' ).exists
end

--Error message handling.
function p.errorclass( msg )
	return mw.text.tag( 'span', {class='error mw-ext-cite-error'}, '<b>Error!</b> '..string.gsub(msg, '&#', '&amp;#') )
end

--Failure handling.
function p.failedcat( errors, sortkey )
	if avoidself then
		return (errors or '') .. '&#42;&#42;&#42;صيانة شريط تصنيف فشل في إنشاء صندوق شريط***' .. 
			   '[[' .. testcasecolon .. 'تصنيف:صيانة شريط تصنيف فشل في إنشاء صندوق شريط|' .. (sortkey or 'O') .. ']]\n'
	end
	return ''
end

--Tracking cat handling.
--	key: 15 (when reindexing ttrackingcats{}, Ctrl+H 'trackcat(13,' & 'ttrackingcats[16]')
--	cat: 'Navseasoncats isolated'; '' to remove
--Used by main, all nav_*(), & several utility functions.
local function trackcat( key, cat )
	cat = tracking_cats_dir[key]
	if avoidself and key and cat then
		if cat ~= '' then
			ttrackingcats[key] = '[[' .. testcasecolon .. 'تصنيف:' .. cat .. ']]'
		else
			ttrackingcats[key] = ''
		end
	end
	return
end

--Check for unknown parameters.
--Used by main only.
local function checkforunknownparams( tbl )
	local knownparams = {
		['min'] = 'min',
		['max'] = 'max',
		['cat'] = 'cat',
		['testcase'] = 'testcase',
		['testcasegap'] = 'testcasegap',
		['skip-gaps'] = 'skip-gaps',
		['list-all-links'] = 'list-all-links',
		['follow-redirects'] = 'follow-redirects',
	}
	for k, _ in pairs (tbl) do
		if knownparams[k] == nil then
			trackcat(3, 'Navseasoncats using unknown parameter')
			break
		end
	end
end

--Check for nav_*() navigational isolation (not necessarily an error).
--Used by all nav_*().
local function isolatedcat()
	if nexistingcats == 0 then
		trackcat(16, 'Navseasoncats isolated')
	end
end

--Returns the target of {{تحويل تصنيف}}, if it exists, else returns the original cat.
--{{Title year}}, etc., if found, are evaluated.
--Used by catlinkfollowr(), and so indirectly by all nav_*().

-- local rtarget_cash = {}

local function rtarget( frame, cat )
	local catcontent = mw.title.new( cat or '', 'تصنيف' ):getContent()
	if string.match( catcontent or '', '{{ *تحويل' ) then --prelim test
		local regex = {
			--the following 11 pages (7 condensed) redirect to [[قالب:تحويل تصنيف]] (as of 6/2019):
			'{{ *[Cc]ategory *[Rr]edirect', --most likely match 1st
			'{{ *[Cc]at *redirect',			--444+240 transclusions
			'{{ *[Cc]at *redir',			--8+3
			'{{ *[Cc]ategory *move',		--6
			'{{ *[Cc]at *red',				--6
			'{{ *[Cc]atr',					--4
			'{{ *[Cc]at *move',				--0
			'{{تحويل تصنيف',
		}
		for _, v in pairs (regex) do
			local rtarget = mw.ustring.match( catcontent, v..'%s*|%s*([^|}]+)' )
			if rtarget then
				if string.match(rtarget, '{{') then --{{Title year}}, etc., exists; evaluate
					local regex_ty = '%s*|%s*([^{}]*{{([^{|}]+)}}[^{}]-)%s*}}' --eval null-param templates only; expanded if/as needed
					local rtarget_orig, ty = mw.ustring.match( catcontent, v..regex_ty )
					if rtarget_orig then
						local ty_eval = frame:expandTemplate{ title = ty, args = { page = cat } } --frame:newChild doesn't work, use 'page' param instead
						local rtarget_eval = mw.ustring.gsub(rtarget_orig, '{{%s*'..ty..'%s*}}', ty_eval )
						-- rtarget_cash[cat] = rtarget_eval
						return rtarget_eval
					else --sub-parameters present; track & return default
						trackcat(31, 'Navseasoncats redirection error')
					end
				end
				rtarget = mw.ustring.gsub(rtarget, '^1%s*=%s*', '')
				rtarget = string.gsub(rtarget, '^[Cc]ategory:', '')
				rtarget = string.gsub(rtarget, '^تصنيف:', '')
				-- rtarget_cash[cat] = rtarget
				return rtarget
			end
		end --for
	end --if
	return cat
end

--Similar to {{LinkCatIfExists2}}: make a piped link to a category, if it exists;
--if it doesn't exist, just display the greyed link title without linking.
--Follows {{تحويل تصنيف}}s.
--Returns {
--			['cat'] = cat,
--			['catexists'] = true,
--			['rtarget'] = <#R target>,
--			['navelement'] = <#R target navelement>,
--			['displaytext'] = displaytext,
--		  } if #R followed;
--returns {
--			['cat'] = cat,
--			['catexists'] = <true|false>,
--			['rtarget'] = nil,
--			['navelement'] = <cat navelement>,
--			['displaytext'] = displaytext,
--		  } otherwise.
--Used by all nav_*().
local catlinkfollowr_cash = {}

local function catlinkfollowr( frame, cat, displaytext, displayend )
	cat			= mw.text.trim(cat or '')
	displaytext = mw.text.trim(displaytext or '')
	--mw.log('displaytext:' .. displaytext)
	if string.match( cat , ".*%s*ق%s*م%s*.*" ) and not string.match( displaytext .. ' ' , ".*%s*ق%s*م%s*.*" )  then
		displaytext = displaytext .. " ق م"
	end
	-- --mw.log("cat:'" .. cat .. "',displaytext:'" .. displaytext .. "'")
	displayend	= displayend or false --bool flag to override displaytext IIF the cat/target is terminal (e.g. "2021–present" or "2021–")
	
	local grey = '#888'
	local disp = cat
	if displaytext ~= '' then --use 'displaytext' parameter if present
		disp = mw.ustring.gsub(displaytext, '%s+%(.+$', ''); --strip any trailing disambiguator
	end
	
	local link, nilorR
	--local exists = mw.title.new( cat, 'تصنيف' )
	local exists = catexists(cat)
	if exists then
		nexistingcats = nexistingcats + 1
		if followRs then
			local R = rtarget(frame, cat) --find & follow #R
			if R ~= cat then --#R followed
				nilorR = R
			end
			
			if displayend then
				local y, hyph, ending = mw.ustring.match(R, '^.-(%d+)([–-])(.*)$')
				if ending == 'present' then
					disp = y..hyph..ending
				elseif ending == '' then
					disp = y..hyph..'<span style="visibility:hidden">'..y..'</span>' --hidden y to match spacing
				end
			end
			
			link = '[[:تصنيف:' .. R .. '|' .. disp .. ']]'
		else
			link = '[[:تصنيف:' .. cat .. '|' .. disp .. ']]'
		end
	else
		link = '<span style="color:'..grey..'">'..disp..'</span>'
	end
	
	if listall then
		if nilorR then --#R followed
			table.insert( tlistall, '[[:تصنيف:' .. cat .. ']] → ' .. '[[:تصنيف:' .. nilorR .. ']] (' .. link .. ')' )
		else --no #R
			table.insert( tlistall, '[[:تصنيف:' .. cat .. ']] (' .. link .. ')' )
		end
	end
	
	return {
		['cat'] = cat,
		['catexists'] = exists,
		['rtarget'] = nilorR,
		['navelement'] = link,
		['displaytext'] = disp,
	}
end

--Returns a numbered list of all {{تحويل تصنيف}}s followed by catlinkfollowr() -> rtarget().
--For a nav_hyphen() cat, also returns a formatted list of all cats searched for & found, & all loop indices.
--Used by all nav_*().
local function listalllinks()
	local nl = '\n# '
	local out = ''
	if currtitle.nsText == 'تصنيف' then
		errors = p.errorclass('The <b><code>|list-all-links=yes</code></b> parameter/utility '..
							'should not be saved in category space, only previewed.')
		out = p.failedcat(errors, 'Z')
	end
	
	local bwd, fwd = '', ''
	if tlistallbwd[1] then
		bwd = '\n\nbackward search:\n# '..table.concat(tlistallbwd, nl)
	end
	if tlistallfwd[1] then
		fwd = '\n\nforward search:\n# '..table.concat(tlistallfwd, nl)
	end
	
	if tlistall[1] then
		return out..nl..table.concat(tlistall, nl)..bwd..fwd
	else
		return out..nl..'No links found!?'..bwd..fwd
	end
end

--Returns the difference b/w 2 ints separated by endash|hyphen, nil if error.
--Used by nav_hyphen() only.
local function find_duration( cat )
	local from, to = mw.ustring.match(cat, '(%d+)[–-](%d+)')
	if from and to then
		if to == '00' then return nil end --doesn't follow MOS:DATERANGE
		if (#from == 4) and (#to == 2) then				--1900-01
			to = string.match(from, '(%d%d)%d%d')..to	--1900-1901
		elseif (#from == 2) and (#to == 4) then			--	01-1902
			from = string.match(to, '(%d%d)%d%d')..from --1901-1902
		end
		return (tonumber(to) - tonumber(from))
	end
	return 0
end

--Returns the ending of a terminal cat, and sets the appropriate tracking cat, else nil.
--Used by nav_hyphen() only.
local function find_terminaltxt( cat )
	local terminaltxt = nil
	if mw.ustring.match(cat, '%d+[–-]present$') then
		terminaltxt = 'present'
		trackcat(14, 'Navseasoncats range ends (present)')
	elseif mw.ustring.match(cat, '%d+[–-]$') then
		terminaltxt = ''
		trackcat(15, 'Navseasoncats range ends (blank, MOS)')
	end
	return terminaltxt
end

--Returns an unsigned string of the 1-4 digit decade ending in "0", else nil.
--Used by nav_decade() only.
local function sterilizedec( decade )
	if decade == nil or decade == '' then
		return nil
	end
	
	local dec = string.match(decade, '^[-%+]?(%d?%d?%d?0)$') or
				string.match(decade, '^[-%+]?(%d?%d?%d?0)%D')
	if dec then
		return dec
	else
		--fix 2-4 digit decade
		local decade_fixed234 = string.match(decade, '^[-%+]?(%d%d?%d?)%d$') or
								string.match(decade, '^[-%+]?(%d%d?%d?)%d%D')
		if decade_fixed234 then
			return decade_fixed234..'0'
		end
		
		--fix 1-digit decade
		local decade_fixed1	  = string.match(decade, '^[-%+]?(%d)$') or
								string.match(decade, '^[-%+]?(%d)%D')
		if decade_fixed1 then
			return '0'
		end
		
		--unfixable
		return nil
	end
end

--Check for nav_hyphen default gap size + isolatedcat() (not necessarily an error).
--Used by nav_hyphen() only.
local function defaultgapcat( bool )
	if bool and nexistingcats == 0 then
		--using "nexistingcats > 0" isn't as useful, since the default gap size obviously worked
		trackcat(17, 'Navseasoncats default season gap size')
	end
end

--12 -> 12th, etc.
--Used by nav_nordinal() & nav_wordinal().
function p.addord( i )
	if tonumber(i) then
		local s = tostring(i)
		
		local tens = string.match(s, '1%d$')
		if	  tens then return s..'th' end
		
		local  ones = string.match(s, '%d$')
		if	   ones == '1' then return s..'st'
		elseif ones == '2' then return s..'nd'
		elseif ones == '3' then return s..'rd' end
		
		return s..'th'
	end
	return i
end

--Returns the properly formatted central nav element.
--Expects an integer i, and a catlinkfollowr() table.
--Used by nav_decade() & nav_ordinal() only.
local function navcenter( i, catlink )
	if i == 0 then --center nav element
		if navborder == true then
			return '<b>' .. catlink.displaytext .. '</b>\n'
		else
			return '<b>' .. catlink.navelement .. '</b>\n'
		end
	else
		return '' .. catlink.navelement .. '\n'
	end
end

--Return conditionally aligned stacked navs.
--Used by main only.
local function nav1nav2( nav1, nav2 )
	local line = nav1 .. '\n' .. nav2
	if er_counts ~= 0 then
		line = '#countall: ' .. er_counts .. '\n' .. line
	end
	return line
	--[[if avoidself then
		local forcealign = '<div style="display:block !important; max-width: calc(100% - 25em);">'
		return forcealign..'\n'..nav1..'\n'..nav2..'\n</div>'
	else
		return nav1..'\n'..nav2
	end]]
end


--[[==========================================================================]]
--[[				  Formerly separated templates/modules					  ]]
--[[==========================================================================]]


--[[==========================={{  nav_hyphen  }}=============================]]

local function nav_hyphen( frame, start, hyph, finish, firstpart, lastpart, minseas, maxseas, testgap )
	--Expects a PAGENAME of the form "Some sequential 2015–16 example cat", where
	--	start	  = 2015
	--	hyph	  = –
	--	finish	  = 16 (sequential years can be abbreviated, but others should be full year, e.g. "2001–2005")
	--	firstpart = Some sequential
	--	lastpart  = example cat
	--	minseas	  = 1800 ('min' starting season shown; optional; defaults to -9999)
	--	maxseas	  = 2000 ('max' starting season shown; optional; defaults to 9999; 2000 will show 2000-01)
	--	testgap	  = 0 (testcasegap parameter for easier testing; optional)
	
	--sterilize start
	if string.match(start or '', '^%d%d?%d?%d?$') == nil then --1-4 digits, AD only
		local start_fixed = mw.ustring.match(start or '', '^%s*(%d%d?%d?%d?)%D')
		if start_fixed then
			start = start_fixed
		else
			errors = p.errorclass('Function nav_hyphen can\'t recognize the number "'..(start or '')..'" '..
								  'in the first part of the "season" that was passed to it. '..
								  'For e.g. "2015–16", "2015" is expected via "|2015|–|16|".')
			return p.failedcat(errors, 'H')
		end
	end
	local nstart = tonumber(start)
	
	--en dash check
	if hyph ~= '–' then
		trackcat(4, 'Navseasoncats range not using en dash') --nav still processable, but track
	end
	
	--sterilize finish & check for weird parents
	local tgaps	  = {} --table of gap sizes found b/w terms	   { [<gap size found>]	   = 1 }
	local ttlens  = {} --table of term lengths found w/i terms { [<term length found>] = 1 }
	local tirregs = {} --table of ir/regular-term-length cats' "from"s & "to"s found
	local regularparent = true
	if (finish == -1) or --"Members of the Scottish Parliament 2021–present"
	   (finish == 0)	 --"Members of the Scottish Parliament 2021–"
	then
		regularparent = false
		if maxseas == nil or maxseas == '' then
			maxseas = start --hide subsequent ranges
		end
		if finish == -1 then trackcat(14, 'Navseasoncats range ends (present)')
		else				 trackcat(15, 'Navseasoncats range ends (blank, MOS)') end
	elseif (start == finish) and
		   (ttrackingcats[16] ~= '') --nav_year found isolated; check for surrounding hyphenated terms (e.g. UK MPs 1974)
	then
		trackcat(16, '') --reset for another check later
		trackcat(13, 'Navseasoncats range irregular, 0-length')
		ttlens[0] = 1 --calc ttlens for std cases below
		regularparent = 'isolated'
	end
	if (string.match(finish or '', '^%d+$') == nil) and
	   (string.match(finish or '', '^%-%d+$') == nil)
	then
		local finish_fixed = mw.ustring.match(finish or '', '^%s*(%d%d?%d?%d?)%D')
		if finish_fixed then
			finish = finish_fixed
		else
			errors = p.errorclass('Function nav_hyphen can\'t recognize "'..(finish or '')..'" '..
								  'in the second part of the "season" that was passed to it. '..
								  'For e.g. "2015–16", "16" is expected via "|2015|–|16|".')
			return p.failedcat(errors, 'I')
		end
	else
		if string.len(finish) >= 5 then
			errors = p.errorclass('The second part of the season passed to function nav_hyphen should only be four or fewer digits, not "'..(finish or '')..'". '..
								  'See [[MOS:DATERANGE]] for details.')
			return p.failedcat(errors, 'J')
		end
	end
	local nfinish = tonumber(finish)
	
	--save sterilized parent range for easier lookup later
	tirregs['from0'] = nstart
	tirregs['to0']	 = nfinish
	
	--sterilize min/max
	local nminseas_default = -9999
	local nmaxseas_default =  9999
	local nminseas = tonumber(minseas) or nminseas_default --same behavior as nav_year
	local nmaxseas = tonumber(maxseas) or nmaxseas_default --same behavior as nav_year
	if nminseas > nstart then nminseas = nstart end
	if nmaxseas < nstart then nmaxseas = nstart end
	
	local lspace = ' ' --assume a leading space (most common)
	local tspace = ' ' --assume a trailing space (most common)
	if string.match(firstpart, '%($') then lspace = '' end --DNE for "Madrid city councillors (2007–2011)"-type cats
	if string.match(lastpart,  '^%)') then tspace = '' end --DNE for "Madrid city councillors (2007–2011)"-type cats
	
	--calculate term length/intRAseason size & finishing year
	local term_limit = 10
	local t = 1
	while t <= term_limit and regularparent == true do
		local nish = nstart + t --use switchADBC to flip this sign to work for years BC, if/when the time comes
		if (nish == nfinish) or (string.match(nish, '%d?%d$') == finish) then
			ttlens[t] = 1
			break
		end
		if t == term_limit then
			errors = p.errorclass('Function nav_hyphen can\'t determine a reasonable term length for "'..start..hyph..finish..'".')
			return p.failedcat(errors, 'K')
		end
		t = t + 1
	end
	
	--apply MOS:DATERANGE to parent
	local lenstart = string.len(start)
	local lenfinish = string.len(finish)
	if lenstart == 4 and regularparent == true then --"2001–..."
		if t == 1 then --"2001–02" & "2001–2002" both allowed
			if lenfinish ~= 2 and lenfinish ~= 4 then
				errors = p.errorclass('The second part of the season passed to function nav_hyphen should be two or four digits, not "'..finish..'".')
				return p.failedcat(errors, 'L')
			end
		else --"2001–2005" is required for t > 1; track "2001–05"; anything else = error
			if lenfinish == 2 then
				trackcat(5, 'Navseasoncats range abbreviated (MOS)')
			elseif lenfinish ~= 4 then
				errors = p.errorclass('The second part of the season passed to function nav_hyphen should be four digits, not "'..finish..'".')
				return p.failedcat(errors, 'M')
			end
		end
		if finish == '00' then --full year required regardless of term length
			trackcat(5, 'Navseasoncats range abbreviated (MOS)')
		end
	end
	
	--calculate intERseason gap size
	local hgap_default	   = 0 --assume & start at the most common case: 2001–02 -> 2002–03, etc.
	local hgap_limit_reg   = 6 --less expensive per-increment (inc x 4)
	local hgap_limit_irreg = 6 --more expensive per-increment (inc x 23: inc x (k_bwd + k_fwd) = inc x (12 + 11))
	local hgap_success = false
	local hgap = hgap_default
	while hgap <= hgap_limit_reg and regularparent == true do --verify
		local prevseason2 = firstpart..lspace..(nstart-t-hgap)..hyph..string.match(nstart-hgap, '%d?%d$')	 ..tspace..lastpart
		local nextseason2 = firstpart..lspace..(nstart+t+hgap)..hyph..string.match(nstart+2*t+hgap, '%d?%d$')..tspace..lastpart
		local prevseason4 = firstpart..lspace..(nstart-t-hgap)..hyph..(nstart-hgap)	   ..tspace..lastpart
		local nextseason4 = firstpart..lspace..(nstart+t+hgap)..hyph..(nstart+2*t+hgap)..tspace..lastpart
		if t == 1 then --test abbreviated range first, then full range, to be frugal with expensive functions
			if catexists(prevseason2) or --use 'or', in case we're at the edge of the cat structure,
			   catexists(nextseason2) or --or we hit a "–00"/"–2000" situation on one side
			   catexists(prevseason4) or
			   catexists(nextseason4)
			then
				hgap_success = true
				break
			end
		elseif t > 1 then --test full range first, then abbreviated range, to be frugal with expensive functions
			if catexists(prevseason4) or --use 'or', in case we're at the edge of the cat structure,
			   catexists(nextseason4) or --or we hit a "–00"/"–2000" situation on one side
			   catexists(prevseason2) or
			   catexists(nextseason2)
			then
				hgap_success = true
				break
			end
		end
		hgap = hgap + 1
	end
	if hgap_success == false then
		hgap = tonumber(testgap) or hgap_default --tracked via defaultgapcat()
	end
	
	--preliminary scan to determine ir/regular spacing of nearby cats;
	--to limit expensive function calls, MOS:DATERANGE-violating cats are ignored;
	--an irregular-term-length series should follow "YYYY..hyph..YYYY" throughout
	if hgap <= hgap_limit_reg then --also to isolate temp vars
		--find # of nav-visible ir/regular-term-length cats
		local bwanchor = nstart		  --backward anchor/common year
		local fwanchor = bwanchor + t --forward anchor/common year
		if regularparent == 'isolated' then
			fwanchor = bwanchor
		end
		local spangreen = '[<span style="color:green">j, g, k = ' --used for/when debugging via list-all-links=yes
		local spanblue = '<span style="color:blue">'
		local spanred = ' (<span style="color:red">'
		local span = '</span>'
		local lastg = nil --to check for run-on searches
		local lastk = nil --to check for run-on searches
		local endfound = false --switch used to stop searching forward
		local iirregs = 0 --index of tirregs[] for j < 0, since search starts from parent
		local j = -3 --index of tirregs[] for j > 0 & pseudo nav position
		while j <= 3 do
			
			if j < 0 then --search backward from parent
				local gbreak = false --switch used to break out of g-loop
				local g = 0 --gap size
				while g <= hgap_limit_irreg do
					local k = 0 --term length: 0 = "0-length", 1+ = normal
					while k <= term_limit do
						local from = bwanchor - k - g
						local to   = bwanchor - g
						local full = mw.text.trim( firstpart..lspace..from..hyph..to..tspace..lastpart )
						if k == 0 then
							if regularparent ~= 'isolated' then --+restrict to g == 0 if repeating year problems arise
								to = '0-length'
								full = mw.text.trim( firstpart..lspace..from..tspace..lastpart )
								if catlinkfollowr( frame, full ).rtarget ~= nil then --#R followed
									table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full..spanred..'#R ignored'..span..')' )
									full, to = '', '' --don't use/follow 0-length cat #Rs from nav_hyphen(); otherwise gets messy
								end
							end
						end
						if (k >= 1) or		  --the normal case; only continue k = 0 if 0-length found
						   (to == '0-length') --ghetto "continue" (thx Lua) to avoid expensive searches for "UK MPs 1974-1974", etc.
						then
							table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full )
							if (k == 1) and
							   (g == 0 or g == 1) and
							   (catexists(full) == false)
							then --allow bare-bones MOS:DATERANGE alternation, in case we're on a 0|1-gap, 1-year term series
								local to2 = string.match(to, '%d%d$')
								if to2 and to2 ~= '00' then --and not at a century transition (i.e. 1999–2000)
									to = to2
									full = mw.text.trim( firstpart..lspace..from..hyph..to..tspace..lastpart )
									table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full )
								end
							end
							if catexists(full) then
								if to == '0-length' then
									trackcat(13, 'Navseasoncats range irregular, 0-length')
								end
								tlistallbwd[#tlistallbwd] = spanblue..tlistallbwd[#tlistallbwd]..span..' (found)'
								ttlens[ find_duration(full) ] = 1
								tgaps[g] = 1
								iirregs = iirregs + 1
								tirregs['from-'..iirregs] = from
								tirregs['to-'..iirregs] = to
								bwanchor = from --ratchet down
								if to ~= '0-length' then
									gbreak = true
									break
								else
									g = 0 --soft-reset g, to keep stepping thru k
									j = j + 1 --save, but keep searching thru k
									if j > 3 then --lest we keep searching & finding 0-length cats ("MEPs for the Republic of Ireland 1973" & down)
										gbreak = true
										break
									end
								end
							end
						end --ghetto "continue"
						k = k + 1
						lastk = k
					end --while k
					if gbreak == true then break end
					g = g + 1
					lastg = g
				end --while g
			end --if j < 0
			
			if j > 0 and endfound == false then --search forward from parent
				local gbreak = false --switch used to break out of g-loop
				local g = 0 --gap size
				while g <= hgap_limit_irreg do
					local k = -2 --term length: -2 = "0-length", -1 = "2020–present", 0 = "2020–", 1+ = normal
					while k <= term_limit do
						local from = fwanchor + g
						local to4  = fwanchor + k + g	--override carefully
						local to2  = nil				--last 2 digits of to4, IIF exists
						if k == -1 then to4 = 'present'	--see if end-cat exists (present)
						elseif k == 0 then to4 = '' end	--see if end-cat exists (blank)
						local full = mw.text.trim( firstpart..lspace..from..hyph..to4..tspace..lastpart )
						if k == -2 then
							if regularparent ~= 'isolated' then --+restrict to g == 0 if repeating year problems arise
								to4 = '0-length' --see if 0-length cat exists
								full = mw.text.trim( firstpart..lspace..from..tspace..lastpart )
								if catlinkfollowr( frame, full ).rtarget ~= nil then --#R followed
									table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full..spanred..'#R ignored'..span..')' )
									full, to4 = '', '' --don't use/follow 0-length cat #Rs from nav_hyphen(); otherwise gets messy
								end
							end
						end
						if (k >= -1) or		   --only continue k = -2 if 0-length found
						   (to4 == '0-length') --ghetto "continue" (thx Lua) to avoid expensive searches for "UK MPs 1974-1974", etc.
						then
							table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full )
							if (k == 1) and
							   (g == 0 or g == 1) and
							   (catexists(full) == false)
							then --allow bare-bones MOS:DATERANGE alternation, in case we're on a 0|1-gap, 1-year term series
								to2 = string.match(to4, '%d%d$')
								if to2 and to2 ~= '00' then --and not at a century transition (i.e. 1999–2000)
									full = mw.text.trim( firstpart..lspace..from..hyph..to2..tspace..lastpart )
									table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full )
								end
							end
							if catexists(full) then
								if to4 == '0-length' then
									if rtarget(frame, full) == full then --only use 0-length cats that don't #R
										trackcat(13, 'Navseasoncats range irregular, 0-length')
									end
								end
								tirregs['from'..j] = from
								tirregs['to'..j] = (to2 or to4)
								if (k == -1) or (k == 0) then
									endfound = true --tentative
								else --k == { -2, > 0 }
									tlistallfwd[#tlistallfwd] = spanblue..tlistallfwd[#tlistallfwd]..span..' (found)'
									ttlens[ find_duration(full) ] = 1
									tgaps[g] = 1
									endfound = false
									if to4 ~= '0-length' then --k > 0
										fwanchor = to4 --ratchet up
										gbreak = true
										break --only break on k > 0 b/c old end-cat #Rs still exist like "Members of the Scottish Parliament 2011–"
									else --k == -2
										j = j + 1 --save, but keep searching k's, in case "1974" → "1974-1979"
										if j > 3 then --lest we keep searching & finding 0-length cats ("2018 CONCACAF Champions League" & up)
											gbreak = true
											break
										end
									end
								end
							end
						end --ghetto "continue"
						k = k + 1
						lastk = k
					end --while k
					if gbreak == true then break end
					g = g + 1
					lastg = g
				end --while g
			end --if j > 0
			
			if (lastg == (hgap_limit_irreg + 1)) and
			   (lastk == (term_limit + 1))
			then --search exhausted
				if j < 0 then j = 0 --bwd search exhausted; continue fwd
				elseif j > 0 then break end --fwd search exhausted
			end
			
			j = j + 1
		end --while j <= 3
	end --if hgap <= hgap_limit_reg
	
	--begin navhyphen
	local navh = '{| class="toccolours" style="text-align: center; margin: auto;"\n|\n'
	
	local navlist = {}
	local terminalcat = false --switch used to hide future cats
	local terminaltxt = nil
	local i = -3 --nav position
	while i <= 3 do
		local from = nstart + i*(t+hgap) --the logical, but not necessarily correct, 'from'
		if tirregs['from'..i] then from = tonumber(tirregs['from'..i]) end --prefer irregular term table
		local from2 = string.match(from, '%d?%d$')
		
		local to = tostring(from+t)	--the logical, naive range, but
		if tirregs['to'..i] then	--prefer irregular term table
			to = tirregs['to'..i]
		elseif regularparent == false and tirregs and i > 0 then
			to = tirregs['to-1']	--special treatment for parent terminal cats, since they have no natural 'to'
		end
		local to2 = string.match(to, '%d?%d$')
		local tofinal = (to2 or '')	   --assume t=1 and abbreviated 'to' (the most common case)
		if t > 1 or					   --per MOS:DATERANGE (e.g. 1999-2004)
		  (from2 - (to2 or from2)) > 0 --century transition exception (e.g. 1999–2000)
		then
			tofinal = (to or '')	   --default to the MOS-correct format, in case no fallbacks found
		end
		if to == '0-length' then
			tofinal = to
		end
		
		--check existance of 4-digit, MOS-correct range, with abbreviation fallback
		if tofinal ~= '0-length' then
			if t > 1 and string.len(from) == 4 then --e.g. 1999-2004
				--determine which link exists (full or abbr)
				local full = firstpart..lspace..from..hyph..tofinal..tspace..lastpart
				if not catexists(full) then
					local abbr = firstpart..lspace..from..hyph..to2..tspace..lastpart
					if catexists(abbr) then
						tofinal = (to2 or '') --rv to MOS-incorrect format; if full AND abbr DNE, then tofinal is still in its MOS-correct format
					end
				end
			elseif t == 1 then --full-year consecutive ranges are also allowed
				local abbr = firstpart..lspace..from..hyph..tofinal..tspace..lastpart --assume tofinal is in abbr format
				if not catexists(abbr) and tofinal ~= to then
					local full = firstpart..lspace..from..hyph..to..tspace..lastpart
					if catexists(full) then
						tofinal = (to or '') --if abbr AND full DNE, then tofinal is still in its abbr format (unless it's a century transition)
		end	end	end	end
		
		--populate navh
		if i ~= 0 then --left/right navh
			local orig = firstpart..lspace..from..hyph..tofinal..tspace..lastpart
			local disp = from..hyph..tofinal
			if tofinal == '0-length' then
				orig = firstpart..lspace..from..tspace..lastpart
				disp = from
			end
			local catlink = catlinkfollowr(frame, orig, disp, true) --force terminal cat display
			
			if terminalcat == false then
				terminaltxt = find_terminaltxt( disp ) --also sets tracking cats
				terminalcat = (terminaltxt ~= nil)
			end
			if catlink.rtarget and avoidself then --a {{تحويل تصنيف}} was followed, figure out why
				--determine new term length & gap size
				ttlens[ find_duration( catlink.rtarget ) ] = 1
				if i > -3 then
					local lastto = tirregs['to'..(i-1)]
					if lastto == nil then
						local lastfrom = nstart + (i-1)*(t+hgap)
						lastto = lastfrom+t --use last logical 'from' to calc lastto
					end
					if lastto then
						local gapcat = lastto..'-'..from --dummy cat to calc with
						local gap = find_duration(gapcat) or -1	--in case of nil,
						tgaps[ gap ] = 1						--tgaps[-1] is ignored
					end
				end
				
				--display/tracking handling
				local base_regex = '%d+[–-]%d+'
				local origbase = mw.ustring.gsub(orig, base_regex, '')
				local rtarbase, rtarbase_success = mw.ustring.gsub(catlink.rtarget, base_regex, '')
				if rtarbase_success == 0 then
					local base_regex_lax = '%d%d%d%d' --in case rtarget is a year cat
					rtarbase, rtarbase_success = mw.ustring.gsub(catlink.rtarget, base_regex_lax, '')
				end
				local terminal_regex = '%d+[–-]'..(terminaltxt or '')..'$' --more manual ORs bc Lua regex sux
				if mw.ustring.match(orig, terminal_regex) then
					origbase = mw.ustring.gsub(orig, terminal_regex, '')
				end
				if mw.ustring.match(catlink.rtarget, terminal_regex) then
					--finagle/overload terminalcat type to set nmaxseas on 1st occurence only
					if terminalcat == false then terminalcat = 1 end
					local dummy = find_terminaltxt( catlink.rtarget ) --also sets tracking cats
					rtarbase = mw.ustring.gsub(catlink.rtarget, terminal_regex, '')
				end
				origbase = mw.text.trim(origbase)
				rtarbase = mw.text.trim(rtarbase)
				if origbase ~= rtarbase then
					trackcat(6, 'Navseasoncats range redirected (base change)')
				elseif terminalcat == 1 then
					trackcat(8, 'Navseasoncats range redirected (end)')
				else --origbase == rtarbase
					local all4s_regex = '%d%d%d%d[–-]%d%d%d%d'
					local orig_all4s = mw.ustring.match(orig, all4s_regex)
					local rtar_all4s = mw.ustring.match(catlink.rtarget, all4s_regex)
					if orig_all4s and rtar_all4s then
						trackcat(10, 'Navseasoncats range redirected (other)')
					else
						local year_regex1 = '%d%d%d%d$'
						local year_regex2 = '%d%d%d%d[%s%)]'
						local year_rtar = mw.ustring.match(catlink.rtarget, year_regex1) or
										  mw.ustring.match(catlink.rtarget, year_regex2)
						if orig_all4s and year_rtar then
							trackcat(7, 'Navseasoncats range redirected (var change)')
						else
							trackcat(9, 'Navseasoncats range redirected (MOS)')
						end
					end
				end
			end
			
			if terminalcat then --true or 1
				if type(terminalcat) ~= 'boolean' then nmaxseas = from end --only want to do this once
				terminalcat = true --done finagling/overloading
			end
			if (from >= 0) and (nminseas <= from) and (from <= nmaxseas) then
				table.insert(navlist, catlink.navelement)
				if terminalcat then nmaxseas = nminseas_default end --prevent display of future ranges
			else
				local hidden = '<span style="visibility:hidden">'..disp..'</span>'
				table.insert(navlist, hidden)
				if listall then
					tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
				end
			end
		else --center navh
			if finish == -1 then finish = 'present'
			elseif finish == 0 then finish = '<span style="visibility:hidden">'..start..'</span>' end
			local disp = start..hyph..finish
			if regularparent == 'isolated' then disp = start end
			table.insert(navlist, '<b>'..disp..'</b>')
		end
		
		i = i + 1
	end
	
	-- add the list
	navh = navh..horizontal(navlist)..'\n'
	
	--tracking cats & finalize
	if avoidself then
		local igaps	 = 0 --# of diff gap sizes > 0 found
		local itlens = 0 --# of diff term lengths found
		for s = 1, hgap_limit_reg do --must loop; #tgaps, #ttlens unreliable
			igaps = igaps + (tgaps[s] or 0)
		end
		for s = 0, term_limit do
			itlens = itlens + (ttlens[s] or 0)
		end
		if igaps  > 0 then trackcat(11, 'Navseasoncats range gaps') end
		if itlens > 1 and ttrackingcats[13] == '' then --avoid duplication in "Navseasoncats range irregular, 0-length"
			trackcat(12, 'Navseasoncats range irregular')
		end
	end
	isolatedcat()
	defaultgapcat(not hgap_success)
	if listall then
		return listalllinks()
	else
		return navh..'|}'
	end
end


--[[=========================={{  nav_tvseason	}}============================]]

local function nav_tvseason( frame, firstpart, tv, lastpart, maximumtv )
	--Expects a PAGENAME of the form "Futurama (season 1) episodes", where
	--	firstpart = Futurama (season
	--	tv		  = 1
	--	lastpart  = ) episodes
	--	maximumtv = 7 ('max' tv season parameter; optional; defaults to 9999)
	tv = tonumber(tv)
	if tv == nil then
		errors = p.errorclass('Function nav_tvseason can\'t recognize the TV season number sent to its 2nd parameter.')
		return p.failedcat(errors, 'T')
	end
	
	local maxtv = tonumber(maximumtv) or 9999 --allow +/- qualifier
	if maxtv < tv then maxtv = tv end --input error; maxtv should be >= parent
	
	--begin navtvseason
	local navt = '{| class="toccolours" style="text-align: center; margin: auto;"\n|\n'
	
	local navlist = {}
	local i = -5 --nav position
	while i <= 5 do
		local t = tv + i
		if i ~= 0 then --left/right navt
			local catlink = catlinkfollowr( frame, firstpart..' '..t..lastpart, t )
			if (t >= 1 and t <= maxtv) then --hardcode mintv
				if catlink.rtarget then --a {{تحويل تصنيف}} was followed
					trackcat(25, 'Navseasoncats TV season redirected')
				end
				table.insert(navlist, catlink.navelement)
			else
				local hidden = '<span style="visibility:hidden">'..'0'..'</span>' --'0' to maintain dot spacing
				table.insert(navlist, hidden)
				if listall then
					tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
				end
			end
		else --center navt
			table.insert(navlist, '<b>'..tv..'</b>')
		end
		
		i = i + 1
	end
	-- add the list
	navt = navt..horizontal(navlist)..'\n'
	isolatedcat()
	if listall then
		return listalllinks()
	else
		return navt..'|}'
	end
end


--[[==========================={{  nav_decade  }}=============================]]

local function nav_decade( frame, firstpart, decade, lastpart, mindecade, maxdecade )
	--Expects a PAGENAME of the form "Some sequential 2000 example cat", where
	--	firstpart = Some sequential
	--	decade	  = 2000
	--	lastpart  = example cat
	--	mindecade = 1800 ('min' decade parameter; optional; defaults to -9999)
	--	maxdecade = 2020 ('max' decade parameter; optional; defaults to 9999)
	
	--sterilize dec
	local dec = sterilizedec(decade)
	if dec == nil then
		errors = p.errorclass('Function nav_decade was sent "'..(decade or '')..'" as its 2nd parameter, '..
							'but expects a 1 to 4-digit year ending in "0".')
		return p.failedcat(errors, 'D')
	end
	local ndec = tonumber(dec)
	
	--sterilize mindecade & determine AD/BC
	local mindefault = '-9999'
	local mindec = sterilizedec(mindecade) --returns a tostring(unsigned int), or nil
	if mindec then
		if string.match(mindecade, '-%d') or
		   string.match(mindecade, 'BC')
		then
			mindec = '-'..mindec --better +/-0 behavior with strings (0-initialized int == "-0" string...)
		end
	elseif mindec == nil and mindecade and mindecade ~= '' then
		errors = p.errorclass('Function nav_decade was sent "'..(mindecade or '')..'" as its 4th parameter, '..
							'but expects a 1 to 4-digit year ending in "0", the earliest decade to be shown.')
		return p.failedcat(errors, 'E')
	else --mindec == nil
		mindec = mindefault --tonumber() later, after error checks
	end
	
	--sterilize maxdecade & determine AD/BC
	local maxdefault = '9999'
	local maxdec = sterilizedec(maxdecade) --returns a tostring(unsigned int), or nil + error
	if maxdec then
		if string.match(maxdecade, '-%d') or
		   string.match(maxdecade, 'BC')
		then					 --better +/-0 behavior with strings (0-initialized int == "-0" string...),
			maxdec = '-'..maxdec --but a "-0" string -> tonumber() -> tostring() = "-0",
		end						 --and a  "0" string -> tonumber() -> tostring() =	"0"
	elseif maxdec == nil and maxdecade and maxdecade ~= '' then
		errors = p.errorclass('Function nav_decade was sent "'..(maxdecade or '')..'" as its 5th parameter, '..
							'but expects a 1 to 4-digit year ending in "0", the highest decade to be shown.')
		return p.failedcat(errors, 'F')
	else --maxdec == nil
		maxdec = maxdefault
	end
	
	local tspace = ' ' --assume trailing space for "1950s in X"-type cats
	if string.match(lastpart, '^-') then tspace = '' end --DNE for "1970s-related"-type cats
	
	--AD/BC switches & vars
	
	-- local parentBC = string.match(lastpart, '^BC') --following the "0s BC" convention for all years BC
	local parentBC = string.match(lastpart, '^%s*(ق%sم)')
	lastpart = mw.ustring.gsub(lastpart, '^BC%s*', '') --handle BC separately; AD never used
	--TODO?: handle BCE, but only if it exists in the wild
	lastpart = mw.ustring.gsub(lastpart, '^ق م%s*', '')
	
	local dec0to40AD = (ndec >= 0 and ndec <= 40 and not parentBC) --special behavior in this range
	local switchADBC = 1				 --	 1=AD parent
	if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later
	local BCdisp = ''
	local D = -math.huge --secondary switch & iterator for AD/BC transition
	
	--check non-default min/max more carefully
	if mindec ~= mindefault then
		if tonumber(mindec) > ndec*switchADBC then
			mindec = tostring(ndec*switchADBC) --input error; mindec should be <= parent
		end
	end
	if maxdec ~= maxdefault then
		if tonumber(maxdec) < ndec*switchADBC then
			maxdec = tostring(ndec*switchADBC) --input error; maxdec should be >= parent
		end
	end
	local nmindec = tonumber(mindec) --similar behavior to nav_year & nav_nordinal
	local nmaxdec = tonumber(maxdec) --similar behavior to nav_nordinal
	
	--begin navdecade
	local bnb = '' --border/no border
	if navborder == false then --for Navseasoncats year and decade
		bnb = ' border-style: none; background-color: transparent;'
	end
	local navd = '{| class="toccolours" style="text-align: center; margin: auto;'..bnb..'"\n|\n'
	
	local navlist = {}
	local i = -50 --nav position x 10
	while i <= 50 do
		local d = ndec + i*switchADBC
		
		local BC = ''
		BCdisp = ''
		if dec0to40AD then
			if D < -10 then
				d = math.abs(d + 10) --b/c 2 "0s" decades exist: "0s BC" & "0s" (AD)
				-- BC = 'BC '
				BC = ' ق م '
				if d == 0 then
					D = -10 --track 1st d = 0 use (BC)
				end
			elseif D >= -10 then
				D = D + 10 --now iterate from 0s AD
				d = D	   --2nd d = 0 use
			end
		elseif parentBC then
			if switchADBC == -1 then --parentBC looking at the BC side (the common case)
				BC = ' ق م '
				-- BC = 'BC '
				if d == 0 then	   --prepare to switch to the AD side on the next iteration
					switchADBC = 1 --1st d = 0 use (BC)
					D = -10		   --prep
				end
			elseif switchADBC == 1 then --switched to the AD side
				D = D + 10 --now iterate from 0s AD
				d = D	   --2nd d = 0 use (on first use)
			end
		end
		if BC ~= '' and ndec <= 50 then
			--BCdisp = ' BC' --show BC for all BC decades whenever a "0s" is displayed on the nav
			BCdisp = ' ق م '
		end
		
		--determine target cat
		-- local disp = d .. 's' .. BCdisp
		local disp = 'عقد ' .. d .. BCdisp

		-- local link1 = firstpart .. ' ' .. d .. 's' .. tspace .. BC .. lastpart
		local link1 = firstpart .. ' ' .. d .. '' .. tspace .. BC .. lastpart
		local catlink = catlinkfollowr( frame, link1, disp )
		if catlink.rtarget then --a {{تحويل تصنيف}} was followed
			trackcat(18, 'Navseasoncats decade redirected')
		end
		
		--populate left/right navd
		local shown = navcenter(i, catlink)
		local hidden = '<span style="visibility:hidden">'..disp..'</span>'
		local dsign = d --use d for display & dsign for logic
		if BC ~= '' then dsign = -dsign end
		if (nmindec <= dsign) and (dsign <= nmaxdec) then
			if dsign == 0 and (nmindec == 0 or nmaxdec == 0) then --distinguish b/w -0 (BC) & 0 (AD)
				--"zoom in" on +/- 0 and turn dsign/min/max temporarily into +/- 1 for easier processing
				local zsign, zmin, zmax = 1, nmindec, nmaxdec
				if BC ~= '' then zsign = -1 end
				if	   mindec == '-0' then zmin = -1
				elseif mindec ==  '0' then zmin =  1 end
				if	   maxdec == '-0' then zmax = -1
				elseif maxdec ==  '0' then zmax =  1 end
				
				if (zmin <= zsign) and (zsign <= zmax) then
					table.insert(navlist, shown)
					hidden = nil
				else
					table.insert(navlist, hidden)
				end
			else
				table.insert(navlist, shown)--the common case
				hidden = nil
			end
		else
			table.insert(navlist, hidden)
		end
		if listall and hidden then
			tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
		end
		
		i = i + 10
	end
	-- add the list
	navd = navd..horizontal(navlist)..'\n'
	isolatedcat()
	if listall then
		return listalllinks()
	else
		return navd..'|}'
	end
end


--[[============================{{	nav_year  }}==============================]]

local function nav_year( frame, firstpart, year, lastpart, minimumyear, maximumyear )
	--Expects a PAGENAME of the form "Some sequential 1760 example cat", where
	--	firstpart	= Some sequential
	--	year		= 1760
	--	lastpart	= example cat
	--	minimumyear = 1758 ('min' year parameter; optional)
	--	maximumyear = 1800 ('max' year parameter; optional)
	local minyear_default = -9999
	local maxyear_default = 9999
	year = tonumber(year) or tonumber(mw.ustring.match(year or "", "^%s*(%d*)"))
	local minyear = tonumber(string.match(minimumyear or "", "-?%d+")) or minyear_default
	local maxyear = tonumber(string.match(maximumyear or "", "-?%d+")) or maxyear_default
	if string.match(minimumyear or "", "BC") then
		minyear = -math.abs(minyear)
	end
	if string.match(maximumyear or "", "BC") then
		maxyear = -math.abs(maxyear)
	end
	if year == nil then
		errors = p.errorclass("Function nav_year can't recognize the year sent to its 2nd parameter.")
		return p.failedcat(errors, "Y")
	end
	local space = " "
	-- if string.match(firstpart, '[%-VW]$') then space = '' end

	local parentBC = string.match(lastpart, "^%s*(ق%sم)") 
	lastpart = mw.ustring.gsub(lastpart, "^%s*ق%s*م%s*", "")

	local BCe = parentBC or "ق م"

	local year1to15AD = (year >= 1 and year <= 15 and not parentBC)
	local switchADBC = 1
	if parentBC then
		switchADBC = -1
	end
	mw.log("switchADBC:" .. switchADBC)
	local Y = 0
	if minyear > year * switchADBC then
		minyear = year * switchADBC
	end
	if maxyear < year * switchADBC then
		maxyear = year * switchADBC
	end

	local ygapdefault = 1
	local ygap = ygapdefault
	mw.log("firstpart '" .. firstpart .. "'")
	if
		mw.ustring.match(firstpart, "%s*كأس%s*العالم%s*") or 
		mw.ustring.match(firstpart, "%s*الأولمبية%s*") or 
			mw.ustring.match(firstpart, "%s*البارالمبية%s*") or
			mw.ustring.match(firstpart, "%s*الألعاب%s*الآسيوية%s*") or
			mw.ustring.match(firstpart, "%s*الرئاسة%s*الأمريكية%s*") or
			mw.ustring.match(firstpart, "%s*الألعاب%s*الأفريقية%s*") or
			mw.ustring.match(firstpart, "%s*الألعاب%s*الأوروبية%s*") or
			mw.ustring.match(firstpart, "%s*أمريكا%s*الوسطى%s*والكاريبي%s*") or
			mw.ustring.match(firstpart, "%s*ألعاب%s*الكومنولث%s*") or
			mw.ustring.match(firstpart, "%s*الألعاب%s*الأمريكية%s*")
	 then
		ygap = 4
	end
	lastpart = " " .. lastpart
	if string.match(firstpart, "انتخابات الرئاسة") then
		local ygap1, ygap2 = ygapdefault, ygapdefault
		local ygap1_success, ygap2_success = false, false
		local prevseason = nil
		while ygap1 <= 5 do
			prevseason = firstpart .. space .. (year - ygap1) .. lastpart
			if mw.title.new(prevseason, "تصنيف").exists then
				ygap1_success = true
				break
			end
			ygap1 = ygap1 + 1
		end
		local nextseason = nil
		while ygap2 <= 5 do
			nextseason = firstpart .. space .. (year + ygap2) .. lastpart
			if mw.title.new(nextseason, "تصنيف").exists then
				ygap2_success = true
				break
			end
			ygap2 = ygap2 + 1
		end
		if ygap1_success and ygap2_success then
			if ygap1 == ygap2 then
				ygap = ygap1
			end
		elseif ygap1_success then
			ygap = ygap1
		elseif ygap2_success then
			ygap = ygap2
		end
	end
	local ynogaps = {}
	local skipgaps_limit = 25
	if skipgaps then
		if minyear == minyear_default then
			minyear = 0
		end
		if (year > 70) or (minyear >= 0 and not parentBC) then
			local yskipped = {}
			local cat, found, Yeary
			local Year = year
			local i = 1
			while i <= 5 do
				local y = 1
				while y <= skipgaps_limit do
					found = false
					Yeary = Year + y
					if yskipped[Yeary] == nil then
						yskipped[Yeary] = Yeary
						cat = firstpart .. space .. Yeary .. " " .. lastpart
						found = mw.title.new(cat, "تصنيف").exists
						if found then
							break
						end
					end
					y = y + 1
				end
				if found then
					Year = Yeary
				else
					Year = Year + 1
				end
				ynogaps[i] = Year
				i = i + 1
			end
			ynogaps[0] = year
			Year = year
			i = -1
			while i >= -5 do
				local y = -1
				while y >= -skipgaps_limit do
					found = false
					Yeary = Year + y
					if yskipped[Yeary] == nil then
						yskipped[Yeary] = Yeary
						cat = firstpart .. space .. Yeary .. " " .. lastpart
						found = mw.title.new(cat, "تصنيف").exists
						if found then
							break
						end
					end
					y = y - 1
				end
				if found then
					Year = Yeary
				else
					Year = Year - 1
				end
				ynogaps[i] = Year
				i = i - 1
			end
		else
			skipgaps = false
		end
	end
	local navy = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n' .. "|\n"
	local y
	local i = -5
	while i <= 5 do
		if skipgaps then
			y = ynogaps[i]
		else
			y = year + i * ygap * switchADBC
		end
		mw.log("y: (" .. y .. ")")
		local BCdisp = ""
		if i ~= 0 then
			local AD = ""
			local BC = ""
			if year1to15AD then
				mw.log("year1to15AD, i: (" .. i .. ")")
				if year >= 11 then
					if y <= 10 then
						AD = " ق م "
					end
				elseif year >= 1 then
					if y <= 0 then
						BC = BCe .. " "
						y = math.abs(y - 1)
					elseif y >= 1 and y <= 10 then
						AD = " ق م "
					end
				end
			elseif parentBC then
				mw.log("parentBC, i: (" .. i .. ")")
				if switchADBC == -1 then
					if y >= 1 then
						BC = BCe .. " "
					elseif y == 0 then
						switchADBC = 1
					end
				end
				if switchADBC == 1 then
					Y = Y + 1
					y = Y
					AD = " ق م "
				end
			end
			if BC ~= "" and year <= 5 then
				BCdisp = " " .. BCe
			end
			local ysign = y
			local disp = y .. " " .. BCdisp
			if BC ~= "" then
				ysign = -ysign
			end
			local link1 = firstpart .. space .. y .. " " .. BC .. lastpart
			-- ---------------------------
			-- ---------------------------
			if (minyear <= ysign) and (ysign <= maxyear) then
				local catlinkAD = catlinkfollowr(frame, link1, disp)
				local catlink = catlinkAD
				--[[if AD ~= "" then
					local catlinkNoAD = catlinkfollowr(frame, link1, disp)
					if catlinkNoAD.catexists == true then
						catlink = catlinkNoAD
					elseif listall then
						tlistall[#tlistall] = tlistall[#tlistall] .. " (tried; not displayed)"
					end
				end]]
				-- ---------------------------
				local yHyph_4 = y .. "–" .. (y + 1)
				local link2 = firstpart .. space .. " " .. yHyph_4 .. BC .. lastpart
				-- ---------------------------
				if (AD .. BC == "") and (catlink.catexists == false) and (y >= 1000) then
					-- ---------------------------
					local done = false
					-- ---------------------------
					if link2 ~= link1 then 
						local catlinkHyph_4 = catlinkfollowr(frame, link2, yHyph_4)
						-- ---------------------------
						if catlinkHyph_4.catexists and catlinkHyph_4.rtarget == nil then
							catlink = catlinkHyph_4
							trackcat(27, "Navseasoncats year and range")
							done = true
						else
							if listall and link2 ~= link1 then
								tlistall[#tlistall] = tlistall[#tlistall] .. " (tried; not displayed)"
							end
						end
						-- ---------------------------
					end
					-- ---------------------------
					if not done then 
						local yHyph_2 = y .. "–" .. string.match(y + 1, "%d%d$")
						local link3 = firstpart .. space .. " " .. yHyph_2 .. BC .. lastpart
						-- ---------------------------
						if link3 ~= link1 and link3 ~= link2 then 
							local catlinkHyph_2 = catlinkfollowr(frame, link3, yHyph_2)
							-- ---------------------------
							if catlinkHyph_2.catexists and catlinkHyph_2.rtarget == nil then
								catlink = catlinkHyph_2
								trackcat(27, 'Navseasoncats year and range')
							elseif listall then
								tlistall[#tlistall] = tlistall[#tlistall] .. " (tried; not displayed)"
							end
						end
					end
				end
				-- ---------------------------
				if catlink.rtarget then
					local r = catlink.rtarget
					local c = catlink.cat
					local regex_year = "%d%d%d%d[–-]?%d?%d?%d?%d?"
					local regex_num = "%d+"
					local regex_final = nil
					if string.match(r, regex_year) and string.match(c, regex_year) then
						regex_final = regex_year
					elseif string.match(r, regex_num) and string.match(c, regex_num) then
						regex_final = regex_num
					end
					if regex_final then
						local r_base = mw.ustring.gsub(r, regex_final, "")
						local c_base = mw.ustring.gsub(c, regex_final, "")
						if r_base ~= c_base then
							trackcat(19, 'Navseasoncats year redirected (base change)') --acceptable #R target
						else
							trackcat(21, 'Navseasoncats year redirected (other)') --exceptions go here
						end
					else
						trackcat(20, 'Navseasoncats year redirected (var change)') --e.g. "V2 engines" to "V-twin engines"
					end
				end
				navy = navy .. "*" .. catlink.navelement .. "\n"
			else
				local hidden = '<span style="visibility:hidden">' .. disp .. "</span>"
				navy = navy .. "*" .. hidden .. "\n"
				if listall then
					-- local dummy = catlinkfollowr(frame, link, disp)
					tlistall[#tlistall] = tlistall[#tlistall] .. " (" .. hidden .. ")"
				end
			end
		else
			-- code code 
			-- code code 
			-- code code 
			-- code code 
			-- code code 
			if parentBC then
				BCdisp = " " .. BCe
			end
			local line = year .. BCdisp
			navy = navy .. "*<b>" .. line .. "</b>\n"
		end
		i = i + 1
	end
	isolatedcat()
	if listall then
		return listalllinks()
	else
		return navy .. "|}"
	end
end


--[[==========================={{  nav_roman  }}==============================]]

local function nav_roman( frame, firstpart, roman, lastpart, minimumrom, maximumrom )
	local toarabic = require('Module:ConvertNumeric/en').roman_to_numeral
	local toroman  = require('Module:Roman').main
	
	--sterilize/convert rom/num
	local num = tonumber(toarabic(roman))
	local rom = toroman({ [1] = num })
	if num == nil or rom == nil then --out of range or some other error
		errors = p.errorclass('Function nav_roman can\'t recognize one or more of "'..(num or 'nil')..'" & "'..
							(rom or 'nil')..'" in category "'..firstpart..' '..roman..' '..lastpart..'".')
		return p.failedcat(errors, 'R')
	end
	
	--sterilize min/max
	local minrom = tonumber(minimumrom or '') or tonumber(toarabic(minimumrom or ''))
	local maxrom = tonumber(maximumrom or '') or tonumber(toarabic(maximumrom or ''))
	if minrom < 1 then minrom = 1 end	 --toarabic() returns -1 on error
	if maxrom < 1 then maxrom = 9999 end --toarabic() returns -1 on error
	if minrom > num then minrom = num end
	if maxrom < num then maxrom = num end
	
	--begin navroman
	local navr = '{| class="toccolours" style="text-align: center; margin: auto;"\n|\n'
	
	local navlist = {}
	local i = -5 --nav position
	while i <= 5 do
		local n = num + i
		
		if n >= 1 then
			local r = toroman({ [1] = n })
			if i ~= 0 then --left/right navr
				local catlink = catlinkfollowr( frame, firstpart..' '..r..' '..lastpart, r )
				if minrom <= n and n <= maxrom then
					if catlink.rtarget then --a {{تحويل تصنيف}} was followed
						trackcat(22, 'Navseasoncats roman numeral redirected')
					end
					table.insert(navlist, catlink.navelement)
				else
					local hidden = '<span style="visibility:hidden">'..r..'</span>'
					table.insert(navlist, hidden)
					if listall then
						tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
					end
				end
			else --center navr
				table.insert(navlist, '<b>'..r..'</b>')
			end
		else
			table.insert(navlist, '<span style="visibility:hidden">I</span>')
		end
		
		i = i + 1
	end
	
	-- add the list
	navr = navr..horizontal(navlist)..'\n'
	isolatedcat()
	if listall then
		return listalllinks()
	else
		return navr..'|}'
	end
end


--[[=========================={{  nav_nordinal	}}============================]]

local function nav_nordinal( frame, firstpart, ord, lastpart, minimumord, maximumord )
	local nord = tonumber(ord)
	local minord = tonumber(string.match(minimumord or '', '(-?%d+)[snrt]?[tdh]?')) or -9999 --allow full ord & +/- qualifier
	local maxord = tonumber(string.match(maximumord or '', '(-?%d+)[snrt]?[tdh]?')) or	9999 --allow full ord & +/- qualifier
	if string.match(minimumord or '', 'BC') then minord = -math.abs(minord) end --allow BC qualifier (AD otherwise assumed)
	if string.match(maximumord or '', 'BC') then maxord = -math.abs(maxord) end --allow BC qualifier (AD otherwise assumed)
	
	-- local temporal = string.match(lastpart, 'century') or string.match(lastpart, 'millennium')
	local temporal = string.match(firstpart, 'القرن') or -- century
					 string.match(firstpart, 'الألفية') -- millennium
	mw.log('nav_nordinal firstpart:' .. firstpart)
	local tspace = ' ' --assume a trailing space after ordinal
	if string.match(lastpart, '^-') then tspace = '' end --DNE for "19th-century"-type cats
	
	--AD/BC switches & vars
	
	local ordBCElastparts = { --needed for parent = AD 1-5, when the BC/E format is unknown
		--lists the lastpart of valid BCE cats
		--"BCE" removed to match both AD & BCE cats; easier & faster than multiple string.match()s
		['-century Hebrew people'] = 'BCE', --WP:CFD/Log/2016 June 21#Category:11th-century BC Hebrew people
		['-century Jews']		   = 'BCE', --co-nominated
		['-century Judaism']	   = 'BCE', --co-nominated
		['-century rabbis']		   = 'BCE', --co-nominated
		['-century High Priests of Israel'] = 'BCE',
	}
	-- local parentBC = mw.ustring.match(lastpart, '%s(BCE?)')		  --"1st-century BC" format
	local parentBC = mw.ustring.match(lastpart, '^%s*(ق%sم)')
	-- local lastpartNoBC = mw.ustring.gsub(lastpart, '%sBCE?', '')	 --easier than splitting lastpart up in 2; AD never used
	local lastpartNoBC = mw.ustring.gsub(lastpart, '^%s*ق%s*م%s*' , '')
	-- local BCe = parentBC or ordBCElastparts[lastpartNoBC] or 'BC' --"BC" default
	local BCe = parentBC or 'ق م' --"BC" default
	
	local switchADBC = 1				 --	 1=AD parent
	if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later
	local O = 0 --secondary iterator for AD-on-a-BC-parent
	
	if not temporal and minord < 1 then minord = 1 end --nothing before "1st parliament", etc.
	if minord > nord*switchADBC then minord = nord*switchADBC end --input error; minord should be <= parent
	if maxord < nord*switchADBC then maxord = nord*switchADBC end --input error; maxord should be >= parent
	
	--begin navnordinal
	local bnb = '' --border/no border
	if navborder == false then --for Navseasoncats decade and century
		bnb = ' border-style: none; background-color: transparent;'
	end
	local navo = '{| class="toccolours" style="text-align: center; margin: auto;'..bnb..'"\n|\n'
	
	firstpart = ' ' .. firstpart .. ' '
	local navlist = {}
	local i = -5 --nav position
	while i <= 5 do
		local o = nord + i*switchADBC
		local BC = ''
		local BCdisp = ''
		if parentBC then
			if switchADBC == -1 then --parentBC looking at the BC side
				if o >= 1 then	   --the common case
					BC = ' '..BCe
				elseif o == 0 then --switch to the AD side
					BC = ''
					switchADBC = 1
				end
			end
			if switchADBC == 1 then --displayed o is now in the AD regime
				O = O + 1 --skip o = 0 (DNE)
				o = O	  --easiest solution: start another iterator for these AD o's displayed on a BC year parent
			end
		elseif o <= 0 then --parentAD looking at BC side
			BC = ' '..BCe
			o = math.abs(o - 1) --skip o = 0 (DNE)
		end
		if BC ~= '' and nord <= 5 then --only show 'BC' for parent ords <= 5: saves room, easier to read,
			BCdisp = ' '..BCe		   --and 6 is the first/last nav ord that doesn't need a disambiguator;
		end							   --the center/parent ord will always show BC, so no need to show it another 10x
		
		--populate left/right navo
		local oth = ' ' .. o --p.addord(o)
		local osign = o --use o for display & osign for logic
		if BC ~= '' then osign = -osign end
		local hidden = '<span style="visibility:hidden">'..oth..'</span>'
		if temporal then --e.g. "3rd-century BC"
			local lastpart = lastpartNoBC --lest we recursively add multiple "BC"s
			if BC ~= '' then
				lastpart = string.gsub(lastpart, temporal, temporal..BC) --replace BC if needed
			end
			-- local catlink = catlinkfollowr( frame, firstpart .. ' ' .. oth .. tspace .. lastpart, oth .. BCdisp )
			local catlink = catlinkfollowr( frame, firstpart .. o .. ' ' .. BCdisp .. ' ' .. tspace .. lastpart , temporal .. ' ' .. oth .. BCdisp )
			if (minord <= osign) and (osign <= maxord) then
				if catlink.rtarget then --a {{تحويل تصنيف}} was followed
					trackcat(23, 'Navseasoncats nordinal redirected')
				end
				table.insert(navlist, navcenter(i, catlink))
			else
				table.insert(navlist, hidden)
				if listall then
					tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
				end
			end
		elseif BC == '' and minord <= osign and osign <= maxord then --e.g. >= "1st parliament"
			-- local catlink = catlinkfollowr( frame, firstpart .. ' ' .. oth .. tspace .. lastpart, oth )
			local catlink = catlinkfollowr( frame, firstpart .. o .. ' ' .. BCdisp .. ' ' .. tspace .. lastpart, temporal .. ' ' .. oth )
			if catlink.rtarget then --a {{تحويل تصنيف}} was followed
				trackcat(23, 'Navseasoncats nordinal redirected')
			end
			table.insert(navlist, navcenter(i, catlink))
		else --either out-of-range (hide), or non-temporal + BC = something might be wrong (2nd X parliament BC?); handle exceptions if/as they arise
			table.insert(navlist, hidden)
		end
		
		i = i + 1
	end
	
	navo = navo..horizontal(navlist)..'\n'
	
	isolatedcat()
	if listall then
		return listalllinks()
	else
		return navo..'|}'
	end
end


--[[========================={{	 nav_wordinal  }}=============================]]

local function nav_wordinal( frame, firstpart, word, lastpart, minimumword, maximumword, ordinal, frame )
	--Module:ConvertNumeric.spell_number2() args:
	--	 ordinal == true : 'second' is output instead of 'two'
	--	 ordinal == false: 'two' is output instead of 'second'
	local ord2eng = require('Module:ConvertNumeric/en').numeral_to_english
	local eng2ord = require('Module:ConvertNumeric/en').english_to_ordinal
	local th = 'th'
	if not ordinal then
		th = ''
		eng2ord = require('Module:ConvertNumeric/en').english_to_numeral
	end
	local capitalize = nil ~= string.match(word, '^%u') --determine capitalization
	local nord = eng2ord(string.lower(word)) --operate on/with lowercase, and restore any capitalization later
	
	local lspace = ' ' --assume a leading space (most common)
	local tspace = ' ' --assume a trailing space (most common)
	if string.match(firstpart, '[%-%(]$') then lspace = '' end --DNE for "Straight-eight engines"-type cats
	if string.match(lastpart, '^[%-%)]' ) then tspace = '' end --DNE for "Nine-cylinder engines"-type cats
	
	--sterilize min/max
	local minword = 1
	local maxword = 99
	if minimumword then
		local num = tonumber(minimumword)
		if num and 0 < num and num < maxword then
			minword = num
		else
			local ord = eng2ord(minimumword)
			if 0 < ord and ord < maxword then
				minword = ord
			end
		end
	end
	if maximumword then
		local num = tonumber(maximumword)
		if num and 0 < num and num < maxword then
			maxword = num
		else
			local ord = eng2ord(maximumword)
			if 0 < ord and ord < maxword then
				maxword = ord
			end
		end
	end
	if minword > nord then minword = nord end
	if maxword < nord then maxword = nord end
	
	--begin navwordinal
	local navw = '{| class="toccolours" style="text-align: center; margin: auto;"\n|\n'
	
	local navlist = {}
	local i = -5 --nav position
	while i <= 5 do
		local n = nord + i
		
		if n >= 1 then
			-- local nth = p.addord(n)
			local nth = " القرن " .. n
			if not ordinal then nth = n end
			if i ~= 0 then --left/right navw
				local w = ord2eng{ num = n, ordinal = ordinal, capitalize = capitalize }
				local catlink = catlinkfollowr( frame, firstpart..lspace..w..tspace..lastpart, nth )
				if minword <= n and n <= maxword then
					if catlink.rtarget then --a {{تحويل تصنيف}} was followed
						trackcat(24, 'Navseasoncats wordinal redirected')
					end
					table.insert(navlist, catlink.navelement)
				else
					local hidden = '<span style="visibility:hidden">'..nth..'</span>'
					table.insert(navlist, hidden)
					if listall then
						tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
					end
				end
			else --center navw
				table.insert(navlist, '<b>'..nth..'</b>')
			end
		else
			table.insert(navlist, '<span style="visibility:hidden">'..'0'..th..'</span>')
		end
		
		i = i + 1
	end
	-- Add the list
	navw = navw..horizontal(navlist)..'\n'
	
	isolatedcat()
	if listall then
		return listalllinks()
	else
		return navw..'|}'
	end
end


--[[==========================={{  find_var	 }}===============================]]

local function find_var( pn )
	--Extracts the variable text (e.g. 2015, 2015–16, 2000s, 3rd, III, etc.) from a string,
	--and returns { ['vtype'] = <'year'|'season'|etc.>, <v> = <2015|2015–16|etc.> }
	local pagename = currtitle.text
	if pn and pn ~= '' then
		pagename = pn
	end
	
	local cpagename = 'تصنيف:' .. pagename --limited-Lua-regex workaround
	
	local d_season = mw.ustring.match(cpagename, ':عقد (%d+).+%(%d+[–-]%d+%)') --i.e. [[Category:1760s in the Province of Quebec (1763–1791)]]
	
	local y_season = mw.ustring.match(cpagename, ':(%d+) .+%(%d+[–-]%d+%)') --i.e. "1763 establishments in the Province of Quebec (1763–1791)"
	
	local e_season = mw.ustring.match(cpagename, '%s(%d+[–-])$') or --irreg; ending unknown, e.g. "Members of the Scottish Parliament 2021–"
					 mw.ustring.match(cpagename, '%s(%d+[–-]present)$') --e.g. "UK MPs 2019–present"
	
	local season   = mw.ustring.match(cpagename, '[:%s%(](%d+[–-]%d+)[%)%s]') or --split in 2 b/c you can't frontier '$'/eos?
					 mw.ustring.match(cpagename, '[:%s](%d+[–-]%d+)$')
	
	local tvseason = mw.ustring.match(cpagename, 'الموسم (%d+)') or
					 mw.ustring.match(cpagename, 'series (%d+)')
	
	local nordinal = mw.ustring.match(cpagename, '[:%s](%d+[snrt][tdh])[-%s]') or
					 mw.ustring.match(cpagename, '[:%s](%d+[snrt][tdh])$')
	
	local decade  = mw.ustring.match(cpagename, '[%:%s]عقد%s(%d+)[%s-]') or
					mw.ustring.match(cpagename, '[%:%s]عقد%s(%d+)%s') or
					mw.ustring.match(cpagename, '[%:%s]عقد%s(%d+)%sق%sم%s') or
					mw.ustring.match(cpagename, '[%:%s]عقد%s(%d+)$')
					
	local century  = mw.ustring.match(cpagename, '[:%s]القرن%s(%d+)[%s-]') or
					mw.ustring.match(cpagename,	 '[:%s]القرن%s(%d+)%s') or
					mw.ustring.match(cpagename,	 '[:%s]القرن%s(%d+)%s') or
					mw.ustring.match(cpagename,	 '[:%s]القرن%s(%d+)%sق%sم') or
					mw.ustring.match(cpagename,	 '[:%s]القرن%s(%d+)$')
					
	local millennium  = mw.ustring.match(cpagename, '[:%s]الألفية%s(%d+)[%s-]') or
					mw.ustring.match(cpagename,	 '[:%s]الألفية%s(%d+)%s') or
					mw.ustring.match(cpagename,	 '[:%s]الألفية%s(%d+)$')
					
	local year	   = mw.ustring.match(cpagename, '[:%s](%d+)$') or --%) for [[Category:Futurama (season 1) episodes]], etc.
					 mw.ustring.match(cpagename, '[:%s](%d+)[%s%)]') or
					 mw.ustring.match(cpagename, '[:ب](%d+)$') 
					 
	local year44	 = mw.ustring.match(cpagename, '[:%s](%d%d%d%d)%s') or --prioritize 4-digit years
					 mw.ustring.match(cpagename, '[:%s](%d%d%d%d)$') or
					 mw.ustring.match(cpagename, '[:%s](%d+)%s') or
					 mw.ustring.match(cpagename, '[:%s](%d+)$') or
					 --expand/combine exceptions below as needed
					 mw.ustring.match(cpagename, '[:%s](%d+)-related') or
					 mw.ustring.match(cpagename, '[:%s](%d+)-cylinder') or
					 mw.ustring.match(cpagename, '[:%-VW](%d+)%s') --e.g. "Straight-8 engines"
	
	local roman	   = mw.ustring.match(cpagename, '%s([IVXLCDM]+)%s')
	
	local found	   = d_season or y_season or e_season or season or tvseason or
					 nordinal or decade or year or roman
	
	if found then
		if string.match(found, '%d%d%d%d%d') == nil then
			--return in order of decreasing complexity/least chance for duplication
			if nordinal and season --i.e. "18th-century establishments in the Province of Quebec (1763–1791)"
						then return { ['vtype'] = 'nordinal', ['v'] = nordinal } end
						
			if millennium then	return { ['vtype'] = 'nordinal',   ['v'] = millennium } end
			if century then	 return { ['vtype'] = 'century',   ['v'] = century } end
			if d_season then return { ['vtype'] = 'decade',	  ['v'] = d_season } end
			if y_season then return { ['vtype'] = 'year',	  ['v'] = y_season } end
			if e_season then return { ['vtype'] = 'ending',	  ['v'] = e_season } end
			if season	then return { ['vtype'] = 'season',	  ['v'] = season   } end
			if tvseason then return { ['vtype'] = 'tvseason', ['v'] = tvseason } end
			if nordinal then return { ['vtype'] = 'nordinal', ['v'] = nordinal } end
			if decade	then return { ['vtype'] = 'decade',	  ['v'] = decade   } end
			if year		then return { ['vtype'] = 'year',	  ['v'] = year	   } end
			if roman	then return { ['vtype'] = 'roman',	  ['v'] = roman	   } end
		end
	else
		--try wordinals ('zeroth' to 'ninety-ninth' only)
		local eng2ord = require('Module:ConvertNumeric/en').english_to_ordinal
		local split = mw.text.split(pagename, ' ')
		for i=1, #split do
			if eng2ord(split[i]) > -1 then
				return { ['vtype'] = 'wordinal', ['v'] = split[i] }
			end
		end
		--[[
		--try English numerics ('one'/'single' to 'ninety-nine' only)
		local eng2num = require('Module:ConvertNumeric/en').english_to_numeral
		local split = mw.text.split(pagename, '[%s%-]') --e.g. "Nine-cylinder engines"
		for i=1, #split do
			if eng2num(split[i]) > -1 then
				return { ['vtype'] = 'enumeric', ['v'] = split[i] }
			end
		end]]
	end
	
	errors = p.errorclass('Function find_var can\'t find the variable text in category "'..pagename..'".')
	return { ['vtype'] = 'error', ['v'] = p.failedcat(errors, 'V') }
end


--[[==========================================================================]]
--[[								  Main									  ]]
--[[==========================================================================]]

function p.navseasoncats( frame )
	--arg checks & handling
	local args = frame:getParent().args
	checkforunknownparams(args)		  --for template args
	checkforunknownparams(frame.args) --for #invoke'd args
	local cat  = args['cat']				--'testcase' alias for catspace
	local list = args['list-all-links']		--debugging utility to output all links & followed #Rs
	local follow = args['follow-redirects'] --default 'yes'
	local testcase	  = args['testcase']
	local testcasegap = args['testcasegap']
	local minimum = args['min']
	local maximum = args['max']
	local skip_gaps = args['skip-gaps']
	
	--apply args
	local pagename = testcase or cat or currtitle.text
	local testcaseindent = ''
	if testcasecolon == ':' then testcaseindent = '\n::' end
	if follow and follow == 'no' then followRs = false end
	if list and list == 'yes' then listall = true end
	if skip_gaps and skip_gaps == 'yes' then
		skipgaps = true
		trackcat(26, 'Navseasoncats using skip-gaps parameter')
	end
	
	--ns checks
	if currtitle.nsText == 'تصنيف' then
		if cat and cat ~= '' then
			trackcat(1, 'Navseasoncats using cat parameter')
		end
		if testcase and testcase ~= '' then
			trackcat(2, 'Navseasoncats using testcase parameter')
		end
	elseif currtitle.nsText == '' then
		trackcat(30, 'Navseasoncats in mainspace')
	end
	
	--find the variable parts of pagename
	local findvar = find_var(pagename)
	if findvar.vtype == 'error' then --basic format error checking in find_var()
		return findvar.v..table.concat(ttrackingcats)
	end
	local start = string.match(findvar.v, '^%d+')
	mw.log('findvar.vtype:' .. findvar.vtype)
	--the rest is static
	local findvar_escaped = string.gsub( findvar.v, '%-', '%%%-')
	local firstpart, lastpart = string.match(pagename, '^(.-)'..findvar_escaped..'(.*)$')
	if findvar.vtype == 'tvseason' then --double check for cases like "30 Rock (season 3) episodes"
		firstpart, lastpart = string.match(pagename, '^(.-season )'..findvar_escaped..'(.*)$')
		if firstpart == nil then
			firstpart, lastpart = string.match(pagename, '^(.-series )'..findvar_escaped..'(.*)$')
		end
	end
	firstpart = mw.text.trim(firstpart or '')
	lastpart  = mw.text.trim(lastpart or '')
	
	--call the appropriate nav function, in order of decreasing popularity
	if findvar.vtype == 'year' then		--e.g. "500", "2001"; nav_year..nav_decade; ~75% of cats
		local nav1 = nav_year( frame, firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
		
		local dec = math.floor(findvar.v/10)
		local decadecat = nil
		local firstpart_dec = firstpart
		if firstpart_dec ~= '' then
			firstpart_dec = firstpart_dec..' '
		elseif firstpart_dec == 'AD' and dec <= 1 then
			firstpart_dec = ''
			if dec == 0 then dec = '' end
		end
		-- local decade = dec .. '0s '
		local decade = dec .. '0 '
		
		if mw.text.trim(decade) == '00' then decade = '0 ' end
			
		firstpart_dec = firstpart_dec .. ' عقد '
		firstpart_dec = string.gsub(firstpart_dec, ' سنة%s*عقد%s*$', ' عقد ')
		decadecat = mw.text.trim( firstpart_dec..' '..decade..lastpart )
		local exists = catexists(decadecat)
		
		mw.log('find cat:' .. decadecat )
		
		if exists then
			navborder = false
			trackcat(28, 'Navseasoncats year and decade')
			local nav2 = nav_decade( frame, firstpart_dec, decade, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
			return nav1nav2( nav1, nav2 )
		elseif ttrackingcats[16] ~= '' then --nav_year isolated; check nav_hyphen (e.g. UK MPs 1974, Moldovan MPs 2009, etc.)
			local hyphen = '–'
			local finish = start
			local nav2 = nav_hyphen( frame, start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats)
			if ttrackingcats[16] ~= '' then return nav1 --still isolated; rv to nav_year
			else return nav2 end
		else --regular nav_year
			return nav1
		end
		
	elseif findvar.vtype == 'decade' then	--e.g. "0s", "2010s"; nav_decade..nav_nordinal; ~12% of cats
		local nav1 = nav_decade( frame, firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
		
		-- local decade = tonumber(string.match(findvar.v, '^(%d+)s'))
		local decade = tonumber(string.match(findvar.v, '^(%d+)'))
		local century = math.floor( ((decade-1)/100) + 1 ) --from {{CENTURY}}
		if century == 0 then century = 1 end --no 0th century
		if string.match(decade, '00$') then
			century = century + 1 --'2000' is in the 20th, but the rest of the 2000s is in the 21st
		end
		local clastpart = ' ' .. lastpart
		if firstpart == "عقد" then 
			firstpart = " القرن "
		else
			firstpart = string.gsub(firstpart, ' عقد%s*$', ' القرن ')
		end
		
		local centurycat = mw.text.trim( firstpart .. century .. clastpart )
		
		--mw.log('centurycat:'	.. centurycat)
		
		local exists = catexists(centurycat)
		if not exists then --check for hyphenated century
			clastpart = ' ' .. lastpart
			centurycat = mw.text.trim( firstpart .. century .. clastpart )
			exists = catexists(centurycat)
		end
		if exists then
			navborder = false
			trackcat(29, 'Navseasoncats decade and century')
			local nav2 = nav_nordinal( frame, firstpart, century, clastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
			return nav1nav2( nav1, nav2 )
		else
			return nav1
		end
		
	elseif findvar.vtype == 'nordinal' or findvar.vtype == 'century' then --e.g. "1st", "99th"; ~7.5% of cats
		return nav_nordinal( frame, firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
		
	elseif findvar.vtype == 'season' then	--e.g. "1–4", "1999–2000", "2001–02", "2001–2002", "2005–2010", etc.; ~5.25%
		local hyphen, finish = mw.ustring.match(findvar.v, '%d([–-])(%d+)') --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd
		return nav_hyphen( frame, start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats)
		
	elseif findvar.vtype == 'tvseason' then --e.g. "1", "15" but preceded with "season" or "series"; <1% of cats
		return nav_tvseason( frame, firstpart, start, lastpart, maximum )..testcaseindent..table.concat(ttrackingcats) --"minimum" defaults to 1
		
	elseif findvar.vtype == 'wordinal' then --e.g. "first", "ninety-ninth"; <<1% of cats
		local ordinal = true
		return nav_wordinal( frame, firstpart, findvar.v, lastpart, minimum, maximum, ordinal, frame )..testcaseindent..table.concat(ttrackingcats)
		
	elseif findvar.vtype == 'enumeric' then --e.g. "one", "ninety-nine"; <<1% of cats
		local ordinal = false
		return nav_wordinal( frame, firstpart, findvar.v, lastpart, minimum, maximum, ordinal, frame )..testcaseindent..table.concat(ttrackingcats)
		
	elseif findvar.vtype == 'roman' then	--e.g. "I", "XXVIII"; <<1% of cats
		return nav_roman( frame, firstpart, findvar.v, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
		
	elseif findvar.vtype == 'ending' then	--e.g. "2021–" (irregular; ending unknown); <<<1% of cats
		local hyphen, finish = mw.ustring.match(findvar.v, '%d([–-])present$'), -1 --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd
		if hyphen == nil then
			hyphen, finish = mw.ustring.match(findvar.v, '%d([–-])$'), 0 --0/-1 are hardcoded switches for nav_hyphen()
		end
		return nav_hyphen( frame, start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats)
		
	else								 --malformed
		errors = p.errorclass('Failed to determine the appropriate nav function from malformed season "'..findvar.v..'". ')
		return p.failedcat(errors, 'N')..table.concat(ttrackingcats)
	end
end

return p