Module:dialect synonyms
පෙනුම
- පහත දැක්වෙන උපදෙස්, Module:dialect synonyms/documentation හි පිහිටා ඇත. Module:dialect synonyms/documentation]]. [සංස්කරණය]
- ප්රයෝජනවත් සබැඳි: උප පිටු ලැයිස්තුව • සබැඳි • transclusions • testcases • sandbox
See {{dialect synonyms}}.
-- Module:dialect synonyms
-- Generates dialectal synonym tables for various languages.
local export = {}
local m_links = require("Module:links")
local m_languages = require("Module:languages")
local m_table = require("Module:table")
local m_param_utils = require("Module:parameter utilities")
local PARAM_MODS = {
group = { type = "string" },
note = { type = "string" },
ipa = { type = "string" },
alt = {},
t = { item_dest = "gloss" },
gloss = {},
tr = {},
ts = {},
g = { item_dest = "genders", sublist = true },
pos = {},
lit = {},
id = {},
sc = { type = "script" },
q = { type = "qualifier" },
qq = { type = "qualifier" },
l = { type = "labels" },
ll = { type = "labels" },
}
local TERM_NA = "—"
local langs = {}
local handler_cache = {}
-- TODO: 3 collapsing modes? collapsed, uncollapsed (all locations matching the current entry title), uncollapsed (all)
-- Create the main table
local function __create_table()
return mw.html.create("table")
:addClass("dial-syn")
:addClass("wikitable")
:addClass("mw-collapsible")
:addClass("mw-collapsed")
:done()
end
-- Create the title row of the table
local function __create_title_row(columns, title, colour)
return mw.html.create("tr")
:tag("th")
:attr("colspan", #columns)
:css("background-color", colour)
:wikitext(title)
:done()
:done()
end
-- Create the header row with column names
local function __create_columns_row(columns, colour)
local tr = mw.html.create("tr"):done()
for _, col in ipairs(columns) do
tr:tag("th")
:css("background-color", colour)
:wikitext(col)
:done()
end
return tr
end
-- Create the row with "view map" and "edit data" links
local function __create_view_map_row(columns, colour, view_map, edit_link)
return mw.html.create("tr")
:tag("td")
:attr("colspan", #columns)
:css("text-align", "right")
:css("background-color", colour)
:wikitext(("[[%s|view map]]; [[%s|edit data]]"):format(view_map, edit_link))
:done()
:done()
end
-- Helper to generate a link to create a missing data module
local function __prompt_create_data(module_path, preload_path)
local url = mw.uri.fullUrl(module_path, { action = 'edit', preload = preload_path })
return ("→Create [%s %s]?"):format(tostring(url), module_path)
end
-- Retrieve or load a language-specific handler module
local function __get_handler(code_main)
if not code_main then return nil end
if handler_cache[code_main] == nil then
local path = "Module:dialect synonyms/handlers/" .. code_main
handler_cache[code_main] = mw.title.new(path).exists and require(path) or false
end
return handler_cache[code_main] or nil
end
export.get_handler = __get_handler
function export.collect_aliases(varieties)
local alias_map = {}
local function __collect_recursive(node)
if node.name then
alias_map[node.name] = node.name
end
if node.aliases then
if type(node.aliases) == "table" then
for _, alias in ipairs(node.aliases) do
alias_map[alias] = node.name
end
elseif type(node.aliases) == "string" then
alias_map[node.aliases] = node.name
end
end
for _, child in ipairs(node) do
__collect_recursive(child)
end
end
for _, variety in ipairs(varieties) do
__collect_recursive(variety)
end
return alias_map
end
-- Process a single term object using the handler and standard normalization techniques.
-- This handles legacy syntax parsing (e.g. term:note) and applies language-specific logic.
function export.process_term(data_variety, data, invalid_langs)
-- Re-use language objects if possible to avoid overhead
if not langs[data_variety.code] then
local lang = m_languages.getByCode(data_variety.code, nil, true)
if not lang then
if invalid_langs then
table.insert(invalid_langs, data_variety.code)
end
lang = m_languages.getByCode(data_variety.code_main)
elseif lang:hasType("etymology-only") then
lang = lang:getFull()
end
langs[data_variety.code] = lang
end
data.lang = data.lang or langs[data_variety.code]
data.alt = data.alt or data.term
if data_variety.nolink then data.term = nil end
-- Language-specific processing via handlers (e.g. stripping Etymology N numbers)
local handler = __get_handler(data_variety.code_main)
if handler and handler.process then
data = handler.process(data_variety, data)
end
return data
end
-- Format a processed term object into a final string for display (link + qualifiers).
-- This uses Module:links to generate the standard Wiktionary link.
function export.format_term(data_variety, data)
if type(data) == "string" then return data end -- Already formatted or special string (e.g. TERM_NA)
-- Convert q to qq so qualifiers appear after the term
if data.q and not data.qq then
data.qq = data.q
data.q = nil
elseif data.q and data.qq then
-- Merge q into qq if both exist
if type(data.q) ~= "table" then data.q = {data.q} end
if type(data.qq) ~= "table" then data.qq = {data.qq} end
for _, v in ipairs(data.q) do
table.insert(data.qq, v)
end
data.q = nil
end
local word
local handler = __get_handler(data_variety.code_main)
if handler and handler.format_term then
word = handler.format_term(data_variety, data)
elseif handler and handler.make_link then
word = handler.make_link(data)
else
-- Pass data with show_qualifiers to let full_link handle qualifiers automatically
local link_data = {}
for k, v in pairs(data) do
link_data[k] = v
end
if data.qq then
link_data.show_qualifiers = true
end
word = m_links.full_link(link_data)
end
return word
end
-- Format the display text for a variety or location (e.g. making parens small).
local function __format_text_display(data_variety, is_leaf)
if data_variety.text_display then
return data_variety
end
data_variety.text_display = data_variety.english or data_variety.name
if not data_variety.text_display then
return data_variety
end
data_variety.text_display = mw.ustring.gsub(data_variety.text_display, '(%(.+%))', '<small>%1</small>')
-- Language-specific display formatting via handlers (leaf nodes only)
if is_leaf then
local handler = __get_handler(data_variety.code_main)
if handler and handler.format_display then
data_variety = handler.format_display(data_variety)
end
end
-- Fallback: simple Wikipedia link
if (not data_variety.text_display_formatted) and data_variety.link then
data_variety.text_display = ('[[w:%s|%s]]'):format(data_variety.link, data_variety.text_display)
end
return data_variety
end
-- Main data fetching function.
function export.get_data(lang_code, term, id, demo_mode, skip_validation)
local module_path = "Module:dialect synonyms"
local synonym_data_path
local handler = __get_handler(lang_code)
if handler and handler.get_synonym_data_path then
synonym_data_path = handler.get_synonym_data_path(lang_code, term, id)
end
local language_data_path = module_path .. "/" .. lang_code
local term_path = term .. (id and id ~= "" and "/" .. id or "")
if not synonym_data_path then
synonym_data_path = language_data_path .. "/" .. term_path
end
-- Set defaults
local language_data_req = mw.title.new(language_data_path).exists and require(language_data_path) or nil
local synonym_data_req = mw.title.new(synonym_data_path).exists and require(synonym_data_path) or nil
if demo_mode then
synonym_data_path = language_data_path
synonym_data_req = {}
synonym_data_req.syns = {}
end
if (not language_data_req) or (not synonym_data_req) then
return nil, language_data_path, synonym_data_path -- Return paths for create prompts
end
if handler and handler.get_map_params then
local params = handler.get_map_params(language_data_req, synonym_data_req)
for k, v in pairs(params) do
language_data_req[k] = v
end
end
language_data_req.title = language_data_req.title or "Dialectal synonyms of %s"
language_data_req.columns = language_data_req.columns or { "Variety", "Location", "Words" }
language_data_req.notes = language_data_req.notes or {}
language_data_req.sources = language_data_req.sources or {}
language_data_req.note_aliases = language_data_req.note_aliases or {}
local collected_notes = {}
local collected_notes_set = {}
if synonym_data_req.note then
local notes = type(synonym_data_req.note) == "string" and { synonym_data_req.note } or synonym_data_req.note
for _, note_key in ipairs(notes) do
if note_key ~= "" and not collected_notes_set[note_key] then
collected_notes_set[note_key] = true
table.insert(collected_notes, note_key)
end
end
end
if synonym_data_req.notes then
for _, note_key in ipairs(synonym_data_req.notes) do
if note_key ~= "" and not collected_notes_set[note_key] then
collected_notes_set[note_key] = true
table.insert(collected_notes, note_key)
end
end
end
local expanded_sources = {}
if synonym_data_req.source then
for _, src_key in ipairs(synonym_data_req.source) do
local expanded = language_data_req.sources[src_key]
if expanded then
table.insert(expanded_sources, expanded)
elseif src_key ~= "" then
table.insert(expanded_sources, src_key)
end
end
end
local combined_sources = #expanded_sources > 0 and ("'''Sources:''' " .. table.concat(expanded_sources, "; ")) or nil
local allowed_keys = {
["syns"] = true,
["gloss"] = true,
["meaning"] = true,
["title"] = true,
["note"] = true,
["notes"] = true,
["source"] = true,
}
-- Validation tracking
local used_keys = {}
local duplicate_usage = {}
local duplicate_terms = {}
local invalid_langs = {}
local is_module_ns = mw.title.getCurrentTitle().nsText == "Module"
if skip_validation then is_module_ns = false end
local alias_map = export.collect_aliases(language_data_req.varieties)
if synonym_data_req.syns then
for alias, canonical in pairs(alias_map) do
if alias ~= canonical and synonym_data_req.syns[alias] then
synonym_data_req.syns[canonical] = synonym_data_req.syns[canonical] or {}
for _, term in ipairs(synonym_data_req.syns[alias]) do
table.insert(synonym_data_req.syns[canonical], term)
end
used_keys[alias] = true
end
end
end
local function __populate_tree(data_variety)
local new_node = m_table.shallowCopy(data_variety)
if #new_node == 0 then
-- Leaf node
if (synonym_data_req.syns[new_node.name]) then
if is_module_ns then
if used_keys[new_node.name] then
table.insert(duplicate_usage, new_node.name)
end
used_keys[new_node.name] = true
-- Check for duplicate terms within the same list
local seen_terms = {}
for _, t in ipairs(synonym_data_req.syns[new_node.name]) do
if seen_terms[t] then
if not duplicate_terms[new_node.name] then
duplicate_terms[new_node.name] = {}
end
local already_added = false
for _, dt in ipairs(duplicate_terms[new_node.name]) do
if dt == t then already_added = true break end
end
if not already_added then
table.insert(duplicate_terms[new_node.name], t)
end
end
seen_terms[t] = true
end
end
if (synonym_data_req.syns[new_node.name][1] ~= '') then
new_node.syns = synonym_data_req.syns[new_node.name]
end
end
if demo_mode then
new_node.syns = { '-' }
end
if (new_node.default) and (not new_node.syns) then
if new_node.default == 'module name' then
new_node.syns = { term } -- default fallback
end
end
if (new_node.syns) and (new_node.syns[1] == '-') then
new_node.nolink = true
new_node.syns[1] = TERM_NA
end
if (not new_node.syns) then
return nil
end
-- Process synonyms
local terms = new_node.syns
local terms_processed = {}
-- Special case for TERM_NA
if terms[1] == TERM_NA and #terms == 1 then
terms_processed = terms
else
local simple_processing = true
for _, term_str in ipairs(terms) do
if type(term_str) == "string" and term_str:find("[<,;]") then
simple_processing = false
break
end
end
if simple_processing then
for _, term_entry in ipairs(terms) do
local term_obj
if type(term_entry) == "table" then
term_obj = term_entry
else
term_obj = { term = term_entry }
end
table.insert(terms_processed, export.process_term(new_node, term_obj, invalid_langs))
end
else
local masked_terms = {}
for _, term_str in ipairs(terms) do
local masked_term = term_str:gsub("<(%/?sup[^>]*)>", "\1%1\2")
table.insert(masked_terms, masked_term)
end
local parsed_terms = m_param_utils.parse_list_with_inline_modifiers_and_separate_params({
processed_args = { masked_terms },
param_mods = PARAM_MODS,
termarg = 1,
track_module = "dialect synonyms",
})
for _, term_obj in ipairs(parsed_terms) do
if term_obj.term then
term_obj.term = term_obj.term:gsub("\1", "<"):gsub("\2", ">")
end
if term_obj.note and not collected_notes_set[term_obj.note] then
collected_notes_set[term_obj.note] = true
table.insert(collected_notes, term_obj.note)
end
if term_obj.q then
for i, qual in ipairs(term_obj.q) do
local alias = qual:match("^%[(.+)%]$")
if alias then
term_obj.q[i] = alias
if not collected_notes_set[alias] then
collected_notes_set[alias] = true
table.insert(collected_notes, alias)
end
end
end
end
table.insert(terms_processed, export.process_term(new_node, term_obj, invalid_langs))
end
end
end
new_node.syns = terms_processed
new_node = __format_text_display(new_node, true)
return new_node
else
-- Branch node
new_node = __format_text_display(new_node, false)
if not new_node.code then
new_node.code = (new_node.parent and new_node.parent.code or lang_code)
end
local valid_children = {}
for i, child_raw in ipairs(data_variety) do
local child_clone = m_table.shallowCopy(child_raw)
child_clone.parent = new_node -- Point to the new parent
child_clone.code_main = lang_code
child_clone.code = (child_clone.code or new_node.code)
child_clone.colour = (child_clone.colour or new_node.colour)
local populated_child = __populate_tree(child_clone)
if populated_child then
if populated_child.leaf_count then
new_node.leaf_count = (new_node.leaf_count or 0) + populated_child.leaf_count
else
new_node.leaf_count = (new_node.leaf_count or 0) + 1
end
table.insert(valid_children, populated_child)
end
end
for i = #new_node, 1, -1 do
new_node[i] = nil
end
for i, child in ipairs(valid_children) do
new_node[i] = child
end
if #valid_children == 0 then
return nil
end
return new_node
end
end
local populated_varieties = __populate_tree(language_data_req.varieties)
local expanded_notes = {}
for _, note_key in ipairs(collected_notes) do
if note_key == "" then
-- Skip empty
elseif note_key:match("^%[(.+)%]$") then
local alias = note_key:match("^%[(.+)%]$")
local expanded = language_data_req.note_aliases[alias] or (language_data_req.notes and language_data_req.notes[alias])
if expanded then
table.insert(expanded_notes, expanded)
else
table.insert(expanded_notes, alias)
end
else
local expanded = language_data_req.note_aliases[note_key] or (language_data_req.notes and language_data_req.notes[note_key])
if expanded then
table.insert(expanded_notes, expanded)
else
table.insert(expanded_notes, note_key)
end
end
end
local combined_notes = #expanded_notes > 0 and ("'''Notes:''' " .. table.concat(expanded_notes, " ")) or nil
-- Post-processing validation results
local validation = {
unused_keys = {},
invalid_keys = {},
invalid_langs = invalid_langs,
duplicate_usage = duplicate_usage,
duplicate_terms = duplicate_terms
}
if is_module_ns then
for loc, _ in pairs(synonym_data_req.syns) do
if not used_keys[loc] then
table.insert(validation.unused_keys, loc)
end
end
for key, _ in pairs(synonym_data_req) do
if not allowed_keys[key] then
table.insert(validation.invalid_keys, key)
end
end
end
local root_path = mw.title.getCurrentTitle().prefixedText == language_data_path
return {
varieties = populated_varieties,
properties = {
title = (root_path and "Dialectal varieties") or language_data_req.title,
columns = language_data_req.columns,
notes = language_data_req.notes,
combined_notes = combined_notes,
combined_sources = combined_sources,
gloss = (synonym_data_req.gloss ~= "" and synonym_data_req.gloss) or (synonym_data_req.meaning ~= "" and synonym_data_req.meaning) or nil,
map_template_path = "Template:dialect map/" .. lang_code .. "/" .. (root_path and ".all" or term_path),
synonym_data_path = synonym_data_path,
},
validation = validation
}
end
function export.show(frame)
local params = {
[1] = { required = true, default = "und" },
[2] = { default = mw.loadData("Module:headword/data").pagename },
['id'] = {},
['dpath syns'] = {}, -- Keeping param name for compatibility
['demo mode'] = { type = "boolean" },
}
local args = require("Module:parameters").process(frame:getParent().args, params, nil, "dialect synonyms", "show")
local lang_code = args[1]
local term = args[2]
local id = args['id']
local dataset, language_data_path, synonym_data_path = export.get_data(lang_code, term, id, args['demo mode'])
if not dataset then
if language_data_path and synonym_data_path then
local has_lang = mw.title.new(language_data_path).exists
if not has_lang then
return __prompt_create_data(language_data_path, 'Module:dialect synonyms/und')
else
return __prompt_create_data(synonym_data_path, 'Module:dialect synonyms/' .. lang_code .. '/')
end
else
return "Error loading data."
end
end
local varieties = dataset.varieties
local props = dataset.properties
local validation = dataset.validation
if not varieties then
return "No varieties found."
end
local dialect_synonyms_table
local handler = __get_handler(lang_code)
if handler and handler.create_table then
dialect_synonyms_table = handler.create_table()
else
dialect_synonyms_table = __create_table()
end
dialect_synonyms_table = dialect_synonyms_table:done()
local lang, lang_qualifier = string.match(args[1], '^(.+):(.+)$')
lang = lang or args[1]
local main_word_link = m_links.full_link({
lang = m_languages.getByCode(lang_qualifier and "en" or lang),
term = term,
gloss = props.gloss,
id = id,
}, "term")
local header_colour = 'var(--wikt-palette-green-0)'
local title_row
if handler and handler.create_title_row then
title_row = handler.create_title_row(props.columns, (props.title):format(main_word_link), header_colour)
else
title_row = __create_title_row(props.columns, (props.title):format(main_word_link), header_colour)
end
dialect_synonyms_table:node(title_row)
local map_row
if handler and handler.create_map_row then
map_row = handler.create_map_row(props.columns, header_colour, props.map_template_path, props.synonym_data_path)
else
map_row = __create_view_map_row(props.columns, header_colour, props.map_template_path, props.synonym_data_path)
end
dialect_synonyms_table:node(map_row)
local columns_row
if handler and handler.create_columns_row then
columns_row = handler.create_columns_row(props.columns, header_colour)
else
columns_row = __create_columns_row(props.columns, header_colour)
end
dialect_synonyms_table:node(columns_row)
dialect_synonyms_table:done()
-- Recursive function to render the tree of varieties into HTML rows
local function __render_tree(data_variety, tr)
if not data_variety then return end
if #m_table.numKeys(data_variety) == 0 then
-- Leaf node
if not tr then
tr = mw.html.create('tr'):done()
end
local syns_text_list = {}
for _, item in ipairs(data_variety.syns) do
table.insert(syns_text_list, export.format_term(data_variety, item))
end
local syns_str = table.concat(syns_text_list, ', ')
tr
:tag('th')
:attr('colspan', data_variety.colspan)
:attr('data-lat', data_variety.lat)
:attr('data-lon', data_variety.long)
:attr('data-term', data_variety.text_display)
:css("background-color", data_variety.colour)
:tag('span')
:wikitext(data_variety.text_display)
:done()
:done()
-- TODO: fully commit to [[WT:Palette]] and output `-0` colors for normal table cells?
:tag('td')
:css("background-color", data_variety.colour)
:wikitext(syns_str)
:done()
:done()
dialect_synonyms_table:node(tr):done()
else
-- Branch node
if (not tr) and (data_variety.parent) then
tr = mw.html.create('tr'):done()
end
if tr then
tr
:tag('th')
:attr('rowspan', data_variety.leaf_count)
:attr('colspan', data_variety.colspan)
:css("background-color", data_variety.colour)
:tag('span')
:wikitext(data_variety.text_display)
:done()
:done()
:done()
end
local first = true
for i, _ in m_table.sparseIpairs(data_variety) do
if not first then tr = nil end
__render_tree(data_variety[i], tr)
first = false
end
end
end
if handler and handler.render_table_content then
handler.render_table_content(varieties, dialect_synonyms_table)
else
__render_tree(varieties)
end
-- Output validation warnings (unused keys, duplicates)
local validation_errors = {}
if #validation.unused_keys > 0 then
table.sort(validation.unused_keys)
table.insert(validation_errors, {
track = "dialect synonyms/unused key",
warning = "Warning: The following locations are not present in the varieties data:",
items = validation.unused_keys
})
end
if #validation.invalid_keys > 0 then
table.sort(validation.invalid_keys)
table.insert(validation_errors, {
track = "dialect synonyms/invalid key",
warning = "Warning: The following keys are not allowed in the dataset:",
items = validation.invalid_keys
})
end
if #validation.invalid_langs > 0 then
table.sort(validation.invalid_langs)
table.insert(validation_errors, {
track = "dialect synonyms/invalid lang code",
warning = "Warning: The following language codes are invalid (falling back to main language):",
items = validation.invalid_langs
})
end
if #validation.duplicate_usage > 0 then
table.sort(validation.duplicate_usage)
table.insert(validation_errors, {
track = "dialect synonyms/duplicate key usage",
warning = "Warning: The following locations are used multiple times in the varieties data:",
items = validation.duplicate_usage
})
end
if next(validation.duplicate_terms) then
local items = {}
for loc, terms in pairs(validation.duplicate_terms) do
table.insert(items, loc .. ": " .. table.concat(terms, ", "))
end
table.sort(items)
table.insert(validation_errors, {
track = "dialect synonyms/duplicate terms",
warning = "Warning: The following locations contain duplicate terms:",
items = items
})
end
if #validation_errors > 0 then
local track = require("Module:debug/track")
local warning_div = mw.html.create("div")
:css("background-color", "var(--wikt-palette-red-1)")
:css("border", "1px solid var(--wikt-palette-red-5)")
:css("padding", "0.5em")
:css("margin", "0.5em 0")
for _, err in ipairs(validation_errors) do
track(err.track)
warning_div:tag("strong"):wikitext(err.warning):done()
local ul = warning_div:tag("ul"):css("margin-left", "1.5em")
for _, item in ipairs(err.items) do
ul:tag("li"):wikitext(item):done()
end
end
dialect_synonyms_table:node(warning_div:done())
end
-- Append combined notes row
local all_notes = {}
for _, note in ipairs(props.notes) do
table.insert(all_notes, note)
end
if props.combined_notes then
table.insert(all_notes, props.combined_notes)
end
if #all_notes > 0 then
local notes_text = table.concat(all_notes, "<br>")
dialect_synonyms_table:tag('tr'):tag('td'):attr("colspan", #props.columns):wikitext(notes_text):done():done():done()
end
if props.combined_sources then
dialect_synonyms_table:tag('tr'):tag('td'):attr("colspan", #props.columns):css("font-size", "90%"):wikitext(props.combined_sources):done():done():done()
end
if props.map_template_path and mw.title.getCurrentTitle().nsText == "Module" then
local data_module_path = "Module:dialect map/data/" .. lang_code
if mw.title.new(data_module_path).exists then
local map_title = mw.title.new(props.map_template_path)
if map_title and not map_title.exists then
require("Module:debug/track")("dialect synonyms/missing map")
end
end
end
return tostring(dialect_synonyms_table) .. require("Module:TemplateStyles")("Template:dialect synonyms/styles.css")
end
return export