This is the module for Persian headword templates. Currently, only the noun and adjective function is working and being used (and verb function in {{fa-verb/new}}
).
-- Credit: Fenakhay for base of the module, adjectives, and verb functions
-- Saam-andar for nouns function and expansion of adjectives and verbs
-- Also Theknightwho and Benwing2
local lang = require("Module:languages").getByCode("fa")
local fa_cls = require("Module:fa-cls-translit")
local fa_ira = require("Module:fa-translit")
local m_parameters = require("Module:parameters")
local m_table = require("Module:table")
local export = {}
local pos_functions = {}
local u = mw.ustring.char
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
-- Diacritics
local A = u(0x064E) -- fatḥa
local AN = u(0x064B) -- fatḥatān (fatḥa tanwīn)
local U = u(0x064F) -- ḍamma
local I = u(0x0650) -- kasra
local SK = u(0x0652) -- sukūn
local SH = u(0x0651) -- šadda
-----------------------
-- Utility functions --
-----------------------
-- version of mw.ustring.gsub() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = mw.ustring.gsub(term, foo, bar)
return retval
end
-- Tracking functions
local trackfn = require("Module:debug/track")
local function track(page)
trackfn("fa-headword/" .. page)
return true
end
local function remove_links(text)
text = rsub(text, "%]*|", "")
text = rsub(text, "%[%[", "")
text = rsub(text, "%]%]", "")
return text
end
-- Clean up translit strings by removing HTML spans and normalizing
local function clean_translit(tr)
if not tr then return nil end
-- Remove HTML spans that might be added by {{fa-xlit}}
tr = rsub(tr, "</?span*>", "")
-- Normalize whitespace around separators
tr = rsub(tr, "%s*/%s*", " / ")
tr = rsub(tr, "%s*<i>or</i>%s*", " <i>or</i> ")
tr = mw.text.trim(tr)
return tr ~= "" and tr or nil
end
function export.ZWNJ(word)
word = mw.ustring.gsub(word, "+$", "")
if mw.ustring.match(word, "", -1) then
return "\226\128\140" -- U+200C
end
return ""
end
-- Define parameter specifications
local function get_param_spec(poscat)
local base_params = {
= {}, -- legacy head parameter
= {list = true, allow_holes = true},
= {},
= {},
= {list = true, allow_holes = true},
= {list = true, allow_holes = true},
= {list = true, allow_holes = true},
= {list = true, allow_holes = true},
= {type = "boolean"},
}
if poscat == "nouns" then
base_params = {list = true, allow_holes = true}
-- Each plural can have main translit + 3 alternatives
for i = 1, 4 do
local base_key = "pl" .. (i == 1 and "" or tostring(i)) .. "tr"
base_params = {}
base_params = {} -- alias for main
for j = 2, 3 do
base_params = {}
end
end
-- Legacy parameter support
base_params = {}
base_params = {}
base_params = {}
base_params = {}
base_params = {}
elseif poscat == "verbs" then
base_params = {list = true, allow_holes = true}
base_params = {list = true, allow_holes = true}
base_params = {list = true, allow_holes = true}
base_params = {list = true, allow_holes = true}
for i = 1, 3 do
local stem_key = "prstem" .. tostring(i)
base_params = {}
for j = 1, 3 do
base_params = {}
base_params = {}
base_params = {}
end
base_params = {}
base_params = {}
base_params = {}
end
elseif poscat == "adjectives" then
base_params = {}
base_params = {}
base_params = {}
end
return base_params
end
-- The main entry point.
function export.show(frame)
local PAGENAME = mw.loadData("Module:headword/data").pagename
local poscat = frame.args or error(
"Part of speech has not been specified. Please pass parameter 1 to the module invocation.")
local params = get_param_spec(poscat)
local args = m_parameters.process(frame:getParent().args, params)
-- Handle legacy parameter mapping for nouns
if poscat == "nouns" then
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
elseif poscat == "verbs" then
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
if args and not args then
args = args
end
end
-- Gather parameters
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = {},
translits = {},
inflections = {},
id = args,
sort_key = args,
nomultiwordcat = args
}
-- Process heads
local heads = {}
if args then
for i, head in ipairs(args) do
if head then
table.insert(heads, head)
end
end
elseif args then
table.insert(heads, args)
else
table.insert(heads, PAGENAME)
end
if #heads == 0 then
heads = {PAGENAME}
end
-- Process transliterations
local function process_translits(heads, args)
local cls_list = {}
local ir_list = {}
local tr_list = {}
for i = 1, math.max(#heads, 3) do
local cls_raw = args
local ir_raw = args or cls_raw
local tr = clean_translit(args)
local cls_tr = ""
local ir_tr = ""
if cls_raw then
cls_tr = fa_cls.tr(cls_raw) or ""
end
if ir_raw then
ir_tr = fa_ira.IRA_tr(ir_raw) or ""
end
cls_list = cls_tr
ir_list = ir_tr
tr_list = tr
end
return cls_list, ir_list, tr_list
end
local cls_list, ir_list, tr_list = process_translits(heads, args)
if #heads == 1 then
-- Single head: combine all transliterations
local tr_overrides = {}
for i = 1, 3 do
local tr = tr_list
if tr and tr ~= "" then
table.insert(tr_overrides, tr)
end
end
local cls_filtered, ira_filtered = {}, {}
for i = 1, 3 do
if not (tr_list and tr_list ~= "") then
if cls_list and cls_list ~= "" then
table.insert(cls_filtered, cls_list)
end
if ir_list and ir_list ~= "" then
table.insert(ira_filtered, ir_list)
end
end
end
local cls_str = table.concat(cls_filtered, ", ")
local ira_str = table.concat(ira_filtered, ", ")
local base_tr = ""
if cls_str ~= "" and ira_str ~= "" then
base_tr = (cls_str == ira_str) and cls_str or (cls_str .. " / " .. ira_str)
elseif cls_str ~= "" then
base_tr = cls_str
elseif ira_str ~= "" then
base_tr = ira_str
end
local final_tr = base_tr
if #tr_overrides > 0 then
local overrides_str = table.concat(tr_overrides, " <i>or</i> ")
final_tr = (final_tr ~= "") and (final_tr .. " <i>or</i> " .. overrides_str) or overrides_str
end
data.heads = heads
data.translits = final_tr
else
-- Multiple heads: assign translit to head
data.heads = heads
for i = 1, #heads do
local tr = tr_list
if tr and tr ~= "" then
data.translits = tr
else
local cls_tr = cls_list or ""
local ir_tr = ir_list or ""
if cls_tr ~= "" and ir_tr ~= "" then
data.translits = (cls_tr == ir_tr) and cls_tr or (cls_tr .. " / " .. ir_tr)
else
data.translits = cls_tr .. ir_tr
end
end
end
end
data.no_redundant_head_cat = (#heads > 1)
-- Call POS-specific function
if pos_functions then
pos_functions.func(args, data)
end
return require("Module:headword").full_headword(data)
end
-- Helper function to add Tajik forms
local function add_tajik_forms(args, data)
for i = 1, 3 do
local cls = args
local tg = args
if tg ~= "-" then
if not tg and cls then
tg = require("Module:tg-Latn-Cyrl-translit").tr(fa_cls.tr(cls))
end
if tg then
table.insert(data.inflections, {
label = (i == 1 and "Tajik spelling" or "<i>or</i>"),
{ term = tg, lang = require("Module:languages").getByCode("tg") }
})
end
end
end
end
-- Get inflection forms helper
local function get_inflection_forms(args, prefix)
local forms = {}
for i = 1, 4 do
local key = prefix .. (i == 1 and "" or tostring(i))
local form = args
if form then
local translit_key = key .. "tr"
local translit = clean_translit(args)
table.insert(forms, {term = form, translit = translit})
end
end
return forms
end
pos_functions = {
func = function(args, data)
data.pos_category = "nouns"
for i = 1, 4 do
local pl = args
if not pl then break end
local tr_variants = {}
-- Main translit parameter
local main_tr_key = "pl" .. (i == 1 and "" or tostring(i)) .. "tr"
local main_tr = clean_translit(args)
if not main_tr then
main_tr = clean_translit(args)
end
local alt_tr2 = clean_translit(args)
local alt_tr3 = clean_translit(args)
if not main_tr then
local base = data.heads
local base_zwnj = export.ZWNJ(base)
local has_zwnj = (base_zwnj ~= "")
local suffix_map = {
= {has_zwnj and "-hā" or "hā", has_zwnj and "-hâ" or "hâ"},
= {"āt", "ât"},
= {"ān", "ân"},
= {"yān", "yân"}
}
local applied_suffix
for suffix, _ in pairs(suffix_map) do
if pl == base .. suffix or pl == base .. base_zwnj .. suffix then
applied_suffix = suffix
break
end
end
if applied_suffix and suffix_map then
local cls_suffix = suffix_map
local ir_suffix = suffix_map
local cls_tr_list = {}
local ir_tr_list = {}
local seen_cls = {}
local seen_ir = {}
for j = 1, 3 do
local cls_raw = args
local ir_raw = args
if cls_raw then
-- Handle comma-separated values
for _, form in ipairs(mw.text.split(cls_raw, ",", true)) do
form = mw.text.trim(form)
if not seen_cls then
seen_cls = true
local cls_tr = fa_cls.tr(form)
if cls_tr and cls_tr ~= "" then
local final_cls_suffix = cls_suffix
if applied_suffix == "یان" and (mw.ustring.match(cls_tr, "$")) then
final_cls_suffix = "yān"
end
table.insert(cls_tr_list, cls_tr .. final_cls_suffix)
end
end
end
end
if ir_raw then
-- Handle comma-separated values
for _, form in ipairs(mw.text.split(ir_raw, ",", true)) do
form = mw.text.trim(form)
if not seen_ir then
seen_ir = true
local ir_tr = fa_ira.IRA_tr(form)
if ir_tr and ir_tr ~= "" then
local final_ir_suffix = ir_suffix
if applied_suffix == "یان" and mw.ustring.match(ir_tr, "i$") then
final_ir_suffix = "yân"
end
table.insert(ir_tr_list, ir_tr .. final_ir_suffix)
end
end
end
elseif cls_raw and not ir_raw then
for _, form in ipairs(mw.text.split(cls_raw, ",", true)) do
form = mw.text.trim(form)
if not seen_ir then
seen_ir = true
local ir_tr = fa_ira.IRA_tr(form)
if ir_tr and ir_tr ~= "" then
local final_ir_suffix = ir_suffix
if applied_suffix == "یان" and mw.ustring.match(ir_tr, "i$") then
final_ir_suffix = "yân"
end
table.insert(ir_tr_list, ir_tr .. final_ir_suffix)
end
end
end
end
end
local cls_str = table.concat(cls_tr_list, ", ")
local ir_str = table.concat(ir_tr_list, ", ")
if cls_str ~= "" and ir_str ~= "" then
main_tr = (cls_str == ir_str) and cls_str or (cls_str .. " / " .. ir_str)
elseif cls_str ~= "" then
main_tr = cls_str
elseif ir_str ~= "" then
main_tr = ir_str
end
end
end
if main_tr then
table.insert(tr_variants, main_tr)
end
if alt_tr2 then
table.insert(tr_variants, alt_tr2)
end
if alt_tr3 then
table.insert(tr_variants, alt_tr3)
end
-- Create final transliteration string
local final_tr = (#tr_variants > 0) and table.concat(tr_variants, " <i>or</i> ") or nil
-- Add to inflections
table.insert(data.inflections, {
label = (i == 1 and "plural" or "<i>or</i>"),
{term = pl, translit = final_tr}
})
end
-- Add Tajik forms
add_tajik_forms(args, data)
end
}
pos_functions = {
func = function(args, data)
data.pos_category = "verbs"
local prstems = {}
for i = 1, 3 do
local stem = args
if stem then
table.insert(prstems, stem)
end
end
if #prstems > 0 then
local prstem_translits = {}
if #prstems == 1 then
-- Single present stem: process multiple transliteration variants
local cls_list = {}
local ir_list = {}
local tr_list = {}
for i = 1, 3 do
local cls_raw = args
local ir_raw = args or cls_raw
local tr = clean_translit(args)
local cls_tr = ""
local ir_tr = ""
if cls_raw then
cls_tr = fa_cls.tr(cls_raw) or ""
end
if ir_raw then
ir_tr = fa_ira.IRA_tr(ir_raw) or ""
end
cls_list = cls_tr
ir_list = ir_tr
tr_list = tr
end
local tr_overrides = {}
for i = 1, 3 do
local tr = tr_list
if tr and tr ~= "" then
table.insert(tr_overrides, tr)
end
end
local cls_filtered, ira_filtered = {}, {}
for i = 1, 3 do
if not (tr_list and tr_list ~= "") then
if cls_list and cls_list ~= "" then
table.insert(cls_filtered, cls_list)
end
if ir_list and ir_list ~= "" then
table.insert(ira_filtered, ir_list)
end
end
end
local cls_str = table.concat(cls_filtered, ", ")
local ira_str = table.concat(ira_filtered, ", ")
local base_tr = ""
if cls_str ~= "" and ira_str ~= "" then
base_tr = (cls_str == ira_str) and cls_str or (cls_str .. " / " .. ira_str)
elseif cls_str ~= "" then
base_tr = cls_str
elseif ira_str ~= "" then
base_tr = ira_str
end
local final_tr = base_tr
if #tr_overrides > 0 then
local overrides_str = table.concat(tr_overrides, " <i>or</i> ")
final_tr = (final_tr ~= "") and (final_tr .. " <i>or</i> " .. overrides_str) or overrides_str
end
prstem_translits = final_tr
else
-- Multiple present stems: assign translit to stem
for i = 1, #prstems do
local cls_list = {}
local ir_list = {}
local tr_list = {}
for j = 1, 3 do
local cls_raw, ir_raw, tr
if i == 1 then
cls_raw = args
ir_raw = args or cls_raw
tr = clean_translit(args)
else
local stem_prefix = "prstem" .. tostring(i)
cls_raw = (j == 1) and args or args
ir_raw = (j == 1) and args or args
if not ir_raw then ir_raw = cls_raw end
tr = clean_translit((j == 1) and args or args)
end
local cls_tr = ""
local ir_tr = ""
if cls_raw then
cls_tr = fa_cls.tr(cls_raw) or ""
end
if ir_raw then
ir_tr = fa_ira.IRA_tr(ir_raw) or ""
end
cls_list = cls_tr
ir_list = ir_tr
tr_list = tr
end
local tr_overrides = {}
for j = 1, 3 do
local tr = tr_list
if tr and tr ~= "" then
table.insert(tr_overrides, tr)
end
end
local cls_filtered, ira_filtered = {}, {}
for j = 1, 3 do
if not (tr_list and tr_list ~= "") then
if cls_list and cls_list ~= "" then
table.insert(cls_filtered, cls_list)
end
if ir_list and ir_list ~= "" then
table.insert(ira_filtered, ir_list)
end
end
end
local cls_str = table.concat(cls_filtered, ", ")
local ira_str = table.concat(ira_filtered, ", ")
local base_tr = ""
if cls_str ~= "" and ira_str ~= "" then
base_tr = (cls_str == ira_str) and cls_str or (cls_str .. " / " .. ira_str)
elseif cls_str ~= "" then
base_tr = cls_str
elseif ira_str ~= "" then
base_tr = ira_str
end
local final_tr = base_tr
if #tr_overrides > 0 then
local overrides_str = table.concat(tr_overrides, " <i>or</i> ")
final_tr = (final_tr ~= "") and (final_tr .. " <i>or</i> " .. overrides_str) or overrides_str
end
prstem_translits = final_tr
end
end
for i, stem in ipairs(prstems) do
table.insert(data.inflections, {
label = (i == 1 and "present stem" or "<i>or</i>"),
{term = stem, translit = prstem_translits}
})
end
end
-- Add Tajik forms
add_tajik_forms(args, data)
end
}
pos_functions = {
func = function(args, data)
data.pos_category = "adjectives"
local word = data.heads
local zwnj = export.ZWNJ(word)
-- Handle comparative/superlative
if args == "+" then
local cmp_forms = {{term = word .. zwnj .. "ت" .. A .. "ر"}}
local sup_forms = {{term = word .. zwnj .. "ت" .. A .. "رین"}}
-- Handle alternative stem
if args then
local alt = args
local alt_zwnj = (args ~= "-") and export.ZWNJ(alt) or ""
local tr_cls = fa_cls.tr(alt) or ""
local tr_ir = fa_ira.IRA_tr(alt) or ""
local cls_suffix_cmp = alt_zwnj ~= "" and "-tar" or "tar"
local cls_suffix_sup = alt_zwnj ~= "" and "-tarīn" or "tarīn"
local ir_suffix_cmp = alt_zwnj ~= "" and "-tar" or "tar"
local ir_suffix_sup = alt_zwnj ~= "" and "-tarin" or "tarin"
local function combine_translit(cls, ira, cls_suffix, ir_suffix)
if cls == ira or ira == "" then
return cls .. cls_suffix
elseif cls == "" then
return ira .. ir_suffix
else
return cls .. cls_suffix .. " / " .. ira .. ir_suffix
end
end
local tr_cmp = combine_translit(tr_cls, tr_ir, cls_suffix_cmp, ir_suffix_cmp)
local tr_sup = combine_translit(tr_cls, tr_ir, cls_suffix_sup, ir_suffix_sup)
table.insert(cmp_forms, {
term = alt .. alt_zwnj .. "ت" .. A .. "ر",
translit = tr_cmp
})
table.insert(sup_forms, {
term = alt .. alt_zwnj .. "ت" .. A .. "رین",
translit = tr_sup
})
end
table.insert(data.inflections, {
label = "comparative",
accel = {form = "comparative"},
unpack(cmp_forms)
})
table.insert(data.inflections, {
label = "superlative",
accel = {form = "superlative"},
unpack(sup_forms)
})
end
-- Add Tajik forms
add_tajik_forms(args, data)
end
}
return export