-- This module implements Template:Track listing -- এই মডিউল সম্পূর্ণরূপে স্থানীয়করণ করা হয়েছে। হালনাগাদ করার সময় সতর্ক থাকুন  local yesno = require('Module:Yesno') local checkType = require('libraryUtil').checkType local convert = require('মডিউল:সংখ্যা রূপান্তরকারী').convert  local SHOW_WARNINGS = false local INPUT_ERROR_CATEGORY = 'ইনপুট ত্রুটিসহ ট্র্যাক তালিকায়ন'  -- Helper functions  -- Add a mixin to a class. local function addMixin(class, mixin) 	for k, v in pairs(mixin) do 		if k ~= 'init' then 			class[k] = v 		end 	end end  -- অনুবাদে কোন পরিবর্তন হলে এখানে করুন -- যদি ক্ষেত্রের জন্য বেশ কিছু bangla পরামিতি থাকে, তাহলে Lua টেবিল থেকে একটি স্ট্রিং প্রতিস্থাপন করুন local fieldTranslations = { 	-- Track.fields 	['নম্বর'] = 'number', 	['শিরোনাম'] = 'title', 	['টীকা'] = 'note', 	['দৈর্ঘ্য'] = 'length', 	['গীতিকার'] = 'lyrics', 	['সুরকার'] = 'music', 	['লেখক'] = 'writer', 	['অতিরিক্ত'] = 'extra',  	-- TrackListing.fields 	['সবগুলির_লেখক'] = 'all_writing', 	['সবগুলির_গীতিকার'] = 'all_lyrics', 	['সবগুলির_সুরকার'] = 'all_music', 	['ভাঁজকৃত'] = 'collapsed', 	['শীর্ষ'] = 'headline', 	['অতিরিক্ত_কলাম'] = 'extra_column', 	['মোট_দৈর্ঘ্য'] = 'total_length', 	['শিরোনামের_প্রস্থ'] = 'title_width', 	['লেখকের_প্রস্থ'] = 'writing_width', 	['গীতিকারের_প্রস্থ'] = 'lyrics_width', 	['সুরকারের_প্রস্থ'] = 'music_width', 	['অতিরিক্ত_প্রস্থ'] = 'extra_width', 	['বিষয়শ্রেণী'] = 'category',  	-- TrackListing.deprecatedFields 	['লেখকের_কৃতিত্ব'] = 'writing_credits', 	['গীতিকারের_কৃতিত্ব'] = 'lyrics_credits', 	['সুরকারের_কৃতিত্ব'] = 'music_credits', }  local function translateFields(param) 	return fieldTranslations[param] or param end  -- Validation mixin  local Validation = {}  function Validation.init(self) 	self.warnings = {} 	self.categories = {} end  function Validation:addWarning(msg, category) 	table.insert(self.warnings, msg) 	table.insert(self.categories, category) end  function Validation:addCategory(category) 	table.insert(self.categories, category) end  function Validation:getWarnings() 	return self.warnings end  function Validation:getCategories() 	return self.categories end  -- Validate a track length. If a track length is invalid, a warning is added. -- A type error is raised if the length is not of type string or nil. function Validation:validateLength(length) 	checkType('validateLength', 1, length, 'string', true) 	if length == nil then 		-- Do nothing if no length specified 		return nil 	end  	local enLength = convert('en', length) 	local hours, minutes, seconds  	-- Try to match times like "1:23:45". 	hours, minutes, seconds = enLength:match('^(%d+):(%d%d):(%d%d)$') 	if hours and hours:sub(1, 1) == '0' then 		-- Disallow times like "0:12:34" 		self:addWarning(string.format( 			"অবৈধ সময় '%s' ('ঘ:মিমি:সেসে' বিন্যাস শূন্য দিয়ে শুরু হতে পারবে না)", 			mw.text.nowiki(length) 		), INPUT_ERROR_CATEGORY) 		return nil 	end  	if not seconds then 		-- The previous attempt didn't match. Try to match times like "1:23". 		minutes, seconds = enLength:match('^(%d?%d):(%d%d)$') 		if minutes and minutes:find('^0%d$') then 			-- Special case to disallow lengths like "01:23". This check has to 			-- be here so that lengths like "1:01:23" are still allowed. 			self:addWarning(string.format( 				"অবৈধ সময় '%s' ('মিমি:সেসে' বিন্যাস শূন্য দিয়ে শুরু হতে পারবে না)", 				mw.text.nowiki(length) 			), INPUT_ERROR_CATEGORY) 			return nil 		end 	end  	-- Add a warning and return if we did not find a match. 	if not seconds then 		self:addWarning(string.format( 			"অবৈধ সময় '%s' (সময় অবশ্যই 'মি:সেসে', 'মিমি:সেসে', বা 'ঘ:মিমি:সেসে' বিন্যাসে হতে হবে)", 			mw.text.nowiki(length) 		), INPUT_ERROR_CATEGORY) 		return nil 	end  	-- Check that the minutes are less than 60 if we have an hours field. 	if hours and tonumber(minutes) >= 60 then 		self:addWarning(string.format( 			"অবৈধ ট্র্যাকের দৈর্ঘ্য '%s' (যদি ঘণ্টা নির্দিষ্ট করা হয়, তবে মিনিটের মান ৬০-এর কম হতে হবে)", 			mw.text.nowiki(length) 		), INPUT_ERROR_CATEGORY) 		return nil 	end  	-- Check that the seconds are less than 60 	if tonumber(seconds) >= 60 then 		self:addWarning(string.format( 			"অবৈধ ট্র্যাকের দৈর্ঘ্য '%s' (সেকেন্ডের মান ৬০-এর কম হতে হবে)", 			mw.text.nowiki(length) 		), INPUT_ERROR_CATEGORY) 	end  	return nil end  -- Track class  local Track = {} Track.__index = Track addMixin(Track, Validation)  Track.fields = { 	number = true, 	title = true, 	note = true, 	length = true, 	lyrics = true, 	music = true, 	writer = true, 	extra = true, }  Track.cellMethods = { 	number = 'makeNumberCell', 	title = 'makeTitleCell', 	writer = 'makeWriterCell', 	lyrics = 'makeLyricsCell', 	music = 'makeMusicCell', 	extra = 'makeExtraCell', 	length = 'makeLengthCell', }  function Track.new(data) 	local self = setmetatable({}, Track) 	Validation.init(self) 	for field in pairs(Track.fields) do 		self[field] = data[field] 	end 	self.number = assert(tonumber(self.number)) 	self:validateLength(self.length) 	return self end  function Track:getLyricsCredit() 	return self.lyrics end  function Track:getMusicCredit() 	return self.music end  function Track:getWriterCredit() 	return self.writer end  function Track:getExtraField() 	return self.extra end  -- Note: called with single dot syntax function Track.makeSimpleCell(wikitext) 	return mw.html.create('td') 		:css('vertical-align', 'top') 		:wikitext(wikitext or ' ') end  function Track:makeNumberCell() 	return mw.html.create('td') 		:css('padding-right', '10px') 		:css('text-align', 'right') 		:css('vertical-align', 'top') 		:wikitext( convert('bn', tostring(self.number)) .. '.') end  function Track:makeTitleCell() 	local titleCell = mw.html.create('td') 	titleCell 		:css('vertical-align', 'top') 		:wikitext(self.title and string.format('"%s"', self.title) or 'শিরোনামহীন') 	if self.note then 		titleCell 			:wikitext(' ') 			:tag('span') 				:css('font-size', '85%') 				:wikitext(string.format('(%s)', self.note)) 	end 	return titleCell end  function Track:makeWriterCell() 	return Track.makeSimpleCell(self.writer) end  function Track:makeLyricsCell() 	return Track.makeSimpleCell(self.lyrics) end  function Track:makeMusicCell() 	return Track.makeSimpleCell(self.music) end  function Track:makeExtraCell() 	return Track.makeSimpleCell(self.extra) end  function Track:makeLengthCell() 	return mw.html.create('td') 		:css('padding-right', '10px') 		:css('text-align', 'right') 		:css('vertical-align', 'top') 		:wikitext(convert('bn', self.length) or ' ') end  function Track:exportRow(options) 	options = options or {} 	local columns = options.columns or {} 	local row = mw.html.create('tr') 	row:css('background-color', options.color or '#fff') 	for i, column in ipairs(columns) do 		local method = Track.cellMethods[column] 		if method then 			row:node(self[method](self)) 		end 	end 	return row end  -- TrackListing class  local TrackListing = {} TrackListing.__index = TrackListing addMixin(TrackListing, Validation)  TrackListing.fields = { 	all_writing = true, 	all_lyrics = true, 	all_music = true, 	collapsed = true, 	headline = true, 	extra_column = true, 	total_length = true, 	title_width = true, 	writing_width = true, 	lyrics_width = true, 	music_width = true, 	extra_width = true, 	category = true, }  TrackListing.deprecatedFields = { 	writing_credits = true, 	lyrics_credits = true, 	music_credits = true, }  function TrackListing.new(data) 	local self = setmetatable({}, TrackListing) 	Validation.init(self)  	-- Check for deprecated arguments 	for deprecatedField in pairs(TrackListing.deprecatedFields) do 		if data[deprecatedField] then 			self:addCategory('অবচিত প্যারামিটারসহ ট্র্যাক তালিকায়ন') 			break 		end 	end  	-- Validate total length 	if data.total_length then 		self:validateLength(data.total_length) 	end  	-- Add properties 	for field in pairs(TrackListing.fields) do 		self[field] = data[field] 	end  	-- Evaluate boolean properties 	self.collapsed = yesno(self.collapsed, false) 	self.showCategories = yesno(self.category) ~= false 	self.category = nil  	-- Make track objects 	self.tracks = {} 	for i, trackData in ipairs(data.tracks or {}) do 		table.insert(self.tracks, Track.new(trackData)) 	end  	-- Find which of the optional columns we have. 	-- We could just check every column for every track object, but that would 	-- be no fun^H^H^H^H^H^H inefficient, so we use four different strategies 	-- to try and check only as many columns and track objects as necessary. 	do 		local optionalColumns = {} 		local columnMethods = { 			lyrics = 'getLyricsCredit', 			music = 'getMusicCredit', 			writer = 'getWriterCredit', 			extra = 'getExtraField', 		} 		local doneWriterCheck = false 		for i, trackObj in ipairs(self.tracks) do 			for column, method in pairs(columnMethods) do 				if trackObj[method](trackObj) then 					optionalColumns[column] = true 					columnMethods[column] = nil 				end 			end 			if not doneWriterCheck and optionalColumns.writer then 				doneWriterCheck = true 				optionalColumns.lyrics = nil 				optionalColumns.music = nil 				columnMethods.lyrics = nil 				columnMethods.music = nil 			end 			if not next(columnMethods) then 				break 			end 		end 		self.optionalColumns = optionalColumns 	end  	return self end  function TrackListing:makeIntro() 	if self.all_writing then 		return string.format( 			'সকল গানের গীতিকার %s।', 			self.all_writing 		) 	elseif self.all_lyrics and self.all_music then 		return string.format( 			'সকল গানের গীতিকার %s; সকল গানের সুরকার %s।', 			self.all_lyrics, 			self.all_music 		) 	elseif self.all_lyrics then 		return string.format( 			'সকল গানের গীতিকার %s।', 			self.all_lyrics 		) 	elseif self.all_music then 		return string.format( 			'সকল গানের সুরকার %s।', 			self.all_music 		) 	else 		return 	end end  function TrackListing:renderTrackingCategories() 	if not self.showCategories or mw.title.getCurrentTitle().namespace ~= 0 then 		return  	end  	local ret = 	local function addCategory(cat) 		ret = ret .. string.format('বিষয়শ্রেণী:%s', cat) 	end  	for i, category in ipairs(self:getCategories()) do 		addCategory(category) 	end  	for i, track in ipairs(self.tracks) do 		for j, category in ipairs(track:getCategories()) do 			addCategory(category) 		end 	end  	return ret end  function TrackListing:renderWarnings() 	if not SHOW_WARNINGS then 		return  	end  	local ret = {}  	local function addWarning(msg) 		table.insert(ret, string.format( 			'ট্র্যাক তালিকায়ন ত্রুটি: %s', 			msg 		)) 	end  	for i, warning in ipairs(self:getWarnings()) do 		addWarning(warning) 	end  	for i, track in ipairs(self.tracks) do 		for j, warning in ipairs(track:getWarnings()) do 			addWarning(warning) 		end 	end  	return table.concat(ret, '') end  function TrackListing:__tostring() 	-- Find columns to output 	local columns = {'number', 'title'} 	if self.optionalColumns.writer then 		columns[#columns + 1] = 'writer' 	else 		if self.optionalColumns.lyrics then 			columns[#columns + 1] = 'lyrics' 		end 		if self.optionalColumns.music then 			columns[#columns + 1] = 'music' 		end 	end 	if self.optionalColumns.extra then 		columns[#columns + 1] = 'extra' 	end 	columns[#columns + 1] = 'length'  	-- Find colspan and column width 	local nColumns = #columns 	local nOptionalColumns = nColumns - 3 	local titleColumnWidth 	if nColumns >= 5 then 		titleColumnWidth = 40 	elseif nColumns >= 4 then 		titleColumnWidth = 60 	else 		titleColumnWidth = 100 	end 	local optionalColumnWidth = (100 - titleColumnWidth) / nOptionalColumns 	titleColumnWidth = titleColumnWidth .. '%' 	optionalColumnWidth = optionalColumnWidth .. '%'  	-- Root of the output 	local root = mw.html.create()  	-- Intro 	root:node(self:makeIntro())  	-- Start of track listing table 	local tableRoot = root:tag('table') 	tableRoot 		:addClass('tracklist') 		:addClass(self.collapsed and 'collapsible collapsed' or nil) 		:css('display', 'block') 		:css('border-spacing', '0px') 		:css('border-collapse', 'collapse') 		:css('border', self.collapsed and '#aaa 1px solid' or nil) 		:css('padding', self.collapsed and '3px' or '4px')  	-- Header row 	if self.headline or self.collapsed then 		tableRoot:tag('tr'):tag('th') 			:addClass('tlheader mbox-text') 			:attr('colspan', nColumns) 			:css('text-align', 'left') 			:css('background-color', '#fff') 			:wikitext(self.headline or 'ট্র্যাক তালিকায়ন') 	end  	-- Headers 	local headerRow = tableRoot:tag('tr')  	---- Track number 	headerRow 		:tag('th') 			:addClass('tlheader') 			:attr('scope', 'col') 			:css('width', '2em') 			:css('padding-left', '10px') 			:css('padding-right', '10px') 			:css('text-align', 'right') 			:css('background-color', '#eee') 			:tag('abbr') 				:attr('title', 'নম্বর') 				:wikitext('নং.')  	---- Title 	headerRow:tag('th') 		:addClass('tlheader') 		:attr('scope', 'col') 		:css('width', self.title_width or titleColumnWidth) 		:css('text-align', 'left') 		:css('background-color', '#eee') 		:wikitext('শিরোনাম')  	---- Optional headers: writer, lyrics, music, and extra 	local function addOptionalHeader(field, headerText, width) 		if self.optionalColumns[field] then 			headerRow:tag('th') 				:addClass('tlheader') 				:attr('scope', 'col') 				:css('width', width or optionalColumnWidth) 				:css('text-align', 'left') 				:css('background-color', '#eee') 				:wikitext(headerText) 		end 	end 	addOptionalHeader('writer', 'রচয়িতা', self.writing_width) 	addOptionalHeader('lyrics', 'গীতিকার', self.lyrics_width) 	addOptionalHeader('music', 'সুরকার', self.music_width) 	addOptionalHeader( 		'extra', 		self.extra_column or '', 		self.extra_width 	)  	---- Track length 	headerRow:tag('th') 		:addClass('tlheader') 		:attr('scope', 'col') 		:css('width', '4em') 		:css('padding-right', '10px') 		:css('text-align', 'right') 		:css('background-color', '#eee') 		:wikitext('দৈর্ঘ্য')  	-- Tracks 	for i, track in ipairs(self.tracks) do 		tableRoot:node(track:exportRow({ 			columns = columns, 			color = i % 2 == 0 and '#f7f7f7' or '#fff' 		})) 	end  	-- Total length 	if self.total_length then 		tableRoot 			:tag('tr') 				:tag('td') 					:attr('colspan', nColumns - 1) 					:css('padding', 0) 					:tag('span') 						:css('width', '7.5em') 						:css('float', 'right') 						:css('padding-left', '10px') 						:css('background-color', '#eee') 						:css('margin-right', '2px') 						:wikitext("মোট দৈর্ঘ্য:") 						:done() 					:done() 				:tag('td') 					:css('padding', '0 10px 0 0') 					:css('text-align', 'right') 					:css('background-color', '#eee') 					:wikitext(string.format("%s", convert('bn', self.total_length))) 	end  	-- Warnings and tracking categories 	root:wikitext(self:renderWarnings()) 	root:wikitext(self:renderTrackingCategories())  	return tostring(root) end  -- Exports  local p = {}  function p._main(args) 	-- Process numerical args so that we can iterate through them. 	if yesno(args.warn) then  -- add "|warn=y" to  to display warnings 		SHOW_WARNINGS = true 	end 	local data, tracks = {}, {} 	for k, v in pairs(args) do 		if type(k) == 'string' then 			local prefix, num = mw.ustring.match(k, '^(%D.-)(%d+)$') 			num = convert('en', num) 			prefix = translateFields(prefix) 			if prefix and Track.fields[prefix] and (num == '0' or num:sub(1, 1) ~= '0') then 				-- Allow numbers like 0, 1, 2 ..., but not 00, 01, 02..., 				-- 000, 001, 002... etc. 				num = tonumber(num) 				tracks[num] = tracks[num] or {} 				tracks[num][prefix] = v 			else 				data[translateFields(k)] = convert('en', v) 			end 		end 	end 	data.tracks = (function (t) 		-- Compress sparse array 		local ret = {} 		for num, trackData in pairs(t) do 			trackData.number = num 			table.insert(ret, trackData) 		end 		table.sort(ret, function (t1, t2) 			return t1.number < t2.number 		end) 		return ret 	end)(tracks)  	return tostring(TrackListing.new(data)) end  function p.main(frame) 	local args = require('Module:Arguments').getArgs(frame, { 		wrappers = 'টেমপ্লেট:ট্র্যাক তালিকায়ন' 	}) 	return p._main(args) end  return p