Module:LuaTemplateTLD
Appearance
Documentation for this module may be created at Module:LuaTemplateTLD/doc
-- Module:LuaTemplateTLD
-- Versatile module for rendering TLD/ccTLD atricle templates with extensive normalization and dynamic content. It processes type and subtype values using canonical mapping, auto-detects IDN status (and when active, converts the page title to punycode with an "xn--" prefix); it also normalizes dates, country names, and website links. The module dynamically assigns categories based on country, PIC, and domain type, as well as integrating social media components along with an NTLDStats section for additional data when applicable.
local p = {}
-- Dependencies
local CanonicalForms = require('Module:CanonicalForms')
local countryNormalization = require('Module:CountryNormalization')
local dateNormalization = require('Module:DateNormalization')
local linkParser = require('Module:LinkParser')
local MultiCountryDisplay = require('Module:MultiCountryDisplay')
local punycode = require('Module:Punycode')
local socialFooter = require('Module:SocialMedia')
local TemplateStructure = require('Module:TemplateStructure')
--------------------------------------------------------------------------------
-- Configuration and Constants
--------------------------------------------------------------------------------
local Config = {
typeMapping = {
{canonical = "gTLD",
synonyms = {"gtld", "generic", "tld", "generic top level domain", "generic top-level domain", "generic tld"},
category = "[[Category:gTLD]]"},
{canonical = "ccTLD",
synonyms = {"cctld", "country", "cc", "country code top level domain", "country code top-level domain", "country tld"},
category = "[[Category:ccTLD]]"}
},
subtypeMapping = {
{canonical="geoTLD",
synonyms={"geotld","geo tld","geo","geographic","geographical","geographic top level domain","geographic top-level domain","geographic tld"},
css="tld-template-subtype-geotld",
category="[[Category:geoTLD]]"},
{canonical="dotBrand",
synonyms={"dotbrand","brand","brandtld","brand tld","brand top level domain","brand top-level domain"},css="tld-template-subtype-brandtld",
category="[[Category:dotBrand]]"},
{canonical="Sponsored TLD",
synonyms={"sponsored tld","sponsored","sponsored top level domain","sponsored top-level domain"},
css="tld-template-subtype-sponsored",
category="[[Category:Sponsored TLD]]"},
{canonical="Legacy TLD",
synonyms={"legacy","legacy tld","leagacy top level domain","legacy top-level domain"},
css="tld-template-subtype-legacytld",
category="[[Category:Legacy TLD]]"},
{canonical="gTLD Round: 2012",
synonyms={"gtld round: 2012", "gtld round 2012","2012 ngtld round","2012 ngtld","ngtld","2012"},
css="tld-template-subtype-ngtld-round-2012",
category="[[Category:gTLD Round: 2012]]"}
},
fields = {
{key="type",label="Type"},
{key="subtype",label="Subtype"},
{key="status",label="Status"},
{keys={"country", "territory"},label="Country"},
{key="introduced",label="Introduced"},
{keys={"date", "implemented"},label="Implemented"},
{keys={"script", "language"},label="Script"},
{key="translation",label="English version"},
{key="ascii",label="Punycode"},
{keys={"registry", "registryprovider"},label="Registry"},
{key="website",label="Website"},
{keys={"RVC", "PIC"},label="PIC/RVC"}
},
legacyTLDs = {
com=true,net=true,org=true,edu=true,gov=true,mil=true,int=true,info=true,aero=true,asia=true,cat=true,coop=true,jobs=true,mobi=true,museum=true,post=true,tel=true,travel=true,xxx=true
},
patterns = {
tldExtension="%.([^%.]+)$",
countryDelimiter="([^;]+)"
}
}
--------------------------------------------------------------------------------
-- Helper Functions
--------------------------------------------------------------------------------
local IDNHelpers = {
processIDN = function(args, rawType)
if args["idn"] then
local idnVal = string.lower(args["idn"])
if idnVal == "yes" or idnVal == "true" then return true end
end
return string.find(string.lower(rawType), "idn") and true or false
end,
getASCII = function(pageName)
return "xn--" .. punycode.encode(pageName:gsub("^%.", ""))
end
}
-- Gets the first non-empty value from a list of equivalent keys
local function getFieldValue(args, field)
-- For fields with multiple equivalent keys
if field.keys then
for _, key in ipairs(field.keys) do
if args[key] and args[key] ~= "" then
return key, args[key]
end
end
return nil, nil
end
-- For single-key fields
return field.key, (args[field.key] and args[field.key] ~= "") and args[field.key] or nil
end
-- Process multiple country entries handled by the MultiCountryDisplay module
--------------------------------------------------------------------------------
-- Block Rendering Functions
--------------------------------------------------------------------------------
local function renderTitleBlock(args)
return string.format('! colspan="2" class="template-title template-title-tld" | %s',
(args._type == "ccTLD") and "ccTLD" or "gTLD")
end
local function renderLogoBlock(args)
return args["logo"] and args["logo"] ~= "" and string.format("|-\n| colspan=\"2\" class=\"tld-logo\" | [[Image:%s]]", args["logo"]) or ""
end
local function renderFlairBlock(args)
local classes = {"tld-flair"}
if args._idnFlag then
table.insert(classes, args._type == "ccTLD" and "tld-template-idn-cctld" or "tld-template-idn-gtld")
elseif args["subtype"] and args["subtype"] ~= "" then
local _, css = CanonicalForms.normalize(args["subtype"], Config.subtypeMapping)
if css then table.insert(classes, css) end
end
return string.format('|-\n| colspan="2" class="%s" |', table.concat(classes, " "))
end
local function renderFieldsBlock(args)
local out = {}
for _, field in ipairs(Config.fields) do
local key, value = getFieldValue(args, field)
if value then
if key == "type" then
value = select(1, CanonicalForms.normalize(value, Config.typeMapping)) or value
elseif key == "subtype" then
value = select(1, CanonicalForms.normalize(value, Config.subtypeMapping)) or value
elseif key == "website" then
value = string.format("[%s %s]", value, linkParser.strip(value))
elseif key == "date" or key == "implemented" then
value = tostring(dateNormalization.formatDate(value))
elseif key == "country" or key == "territory" then
value = MultiCountryDisplay.formatCountries(value)
elseif (key == "RVC" or key == "PIC") and value:match("^%d+$") then
value = string.format("[https://gtldresult.icann.org/applicationstatus/applicationdetails/%s Here]", value)
end
table.insert(out, string.format("|-\n| '''%s''':\n| %s", field.label, value))
end
end
return table.concat(out, "\n")
end
local function renderNTLDStatsBlock(args, pageName)
if args._type == "ccTLD" then return "" end
local tldName = pageName:gsub("^%.", "")
local tldExtension = tldName:match(Config.patterns.tldExtension)
if Config.legacyTLDs[mw.ustring.lower(tldName)] or (tldExtension and Config.legacyTLDs[mw.ustring.lower(tldExtension)]) then
return ""
end
return table.concat({
"|-",
'| colspan="2" class="template-divider" |',
"|-",
'| colspan="2" class="icannwiki-centered" | <span class="icannwiki-bold">Find out more:</span>',
"|-",
string.format('| colspan="2" | <div class="ntldstats icannwiki-centered">[[File:NTLDStatsLogo.png|100px|alt=NTLDStats|link=https://ntldstats.com/tld/%s]]</div>', tldName)
}, "\n")
end
--------------------------------------------------------------------------------
-- Category Assignment
--------------------------------------------------------------------------------
local function computeCategories(args)
local cats = {}
-- Process country categories from either country or territory key
local countryValue = args["country"] or args["territory"]
if countryValue and countryValue ~= "" then
local normalizedCountries = MultiCountryDisplay.getCountriesForCategories(countryValue)
for _, countryName in ipairs(normalizedCountries) do
table.insert(cats, string.format("[[Category:%s]]", countryName))
end
end
-- Check both RVC and PIC keys for category assignment
if (args["RVC"] and args["RVC"] ~= "") or (args["PIC"] and args["PIC"] ~= "") then
table.insert(cats, "[[Category:TLDs with RVCs]]")
end
if args._idnFlag then
table.insert(cats, "[[Category:IDN]]")
if args._type == "ccTLD" then table.insert(cats, "[[Category:IDN ccTLD]]") end
end
for _, group in ipairs(Config.typeMapping) do
if group.canonical == args._type then
table.insert(cats, group.category)
break
end
end
if args["subtype"] and args["subtype"] ~= "" then
local canonicalSubtype, _, subtypeCategory = CanonicalForms.normalize(args["subtype"], Config.subtypeMapping)
if subtypeCategory and subtypeCategory ~= "" then
table.insert(cats, subtypeCategory)
elseif canonicalSubtype and canonicalSubtype ~= "" then
table.insert(cats, string.format("[[Category:%s]]", canonicalSubtype))
end
end
return table.concat(cats, "\n")
end
--------------------------------------------------------------------------------
-- Main Render Function
--------------------------------------------------------------------------------
function p.render(frame)
local args = frame:getParent().args or {}
local titleObj = mw.title.getCurrentTitle()
local pageName = titleObj and titleObj.text or ""
-- Process type and IDN status
local rawType = args["type"] or ""
args._type = select(1, CanonicalForms.normalize(rawType, Config.typeMapping))
args._idnFlag = IDNHelpers.processIDN(args, rawType)
-- Set ASCII value for IDN
if args._idnFlag and titleObj then
args["ascii"] = IDNHelpers.getASCII(pageName)
end
-- Configure and render template
local config = {
blocks = {
function(a) return renderTitleBlock(a) end,
function(a) return renderLogoBlock(a) end,
function(a) return renderFlairBlock(a) end,
function(a) return renderFieldsBlock(a) end,
function(a) return socialFooter.render(a) or "" end,
function(a) return renderNTLDStatsBlock(a, pageName) end
}
}
return TemplateStructure.render(args, config) .. "\n" .. computeCategories(args)
end
return p