Module:Technique

--[=[ This module is intended to be the engine behind "Template:Technique". It can also be used directly from Lua using the function p._technique.

Setting "compat=yes" makes the functions p.technique and p._technique behave more like the old "Template:Technique". Differences to the old "Template:Technique" also in compat mode: special cases aren't used (unlike in "Template:Technique" as of 2019-08-11T12) Special:Search/template: insource:lenient insource:technique -intitle:technique finds 4 direct uses of it (as of 2019-09-12). function p.noungroup in gl, mk, vi ]=]
 * if following non-fitting parameters are set without their predecessors
 * no support for the "lenient" parameter,
 * use of Module:Linguistic with different order of words in

local p = {}

local linguistic = require('Module:Linguistic') local synonyms = require('Module:Technique/synonyms') local declension = require('Module:Declension') local fallback = require('Module:Fallback')

local function getSingularTerm(term) local plurals = require('Module:Technique/WikidataLUT').plurals local singular = nil if plurals[term] then singular = plurals[term] end return singular end

local function getWDmapping(term) term = synonyms.nouns[term] or term local qid_LUT = require('Module:Technique/WikidataLUT').nouns return qid_LUT[term] end

-- return table of fallback languages and their existing subpages for the language local function findLang(lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) for i,l in ipairs(langList) do local page = mw.title.new('Technique/' .. l, 'Module') langList[i] = {lang = l, subpage = page.exists and 'Module:Technique/' .. l or nil} --page.exist checking could be swapped out from here to reduce		its expensive access end return langList end

local function getDeprecatedCat(term) local result = '' local sortTerm = synonyms.nouns[term] or term local map = getWDmapping(term) if map and map.deprecated then result = '' end return result end

local function getUnsupportedText(term, wordtype, lang) local languageSubpage = ('Template:Technique/'		.. lang		.. (wordtype == 'adjectives' and '/adjectives' or '')) local unsupportedText = mw.getCurrentFrame:expandTemplate{ title = 'Please Translate', args = {term, languageSubpage, demo='yes'}} -- TODO: rethink demo parameter! if wordtype == 'nouns' then unsupportedText = (unsupportedText			.. '') end return unsupportedText end

local function makeQIDnoLabelText(term, qid) return ' ' .. term .. ' ' end

-- if the reference item isn't a material, but is a technique and has one -- valid product statement then return the qid of the product (for labeling) function p.getProductQid(map) local productQid if not map.material and map.process then local productStatements = mw.wikibase.getBestStatements(map.qid, 'P1056') if #productStatements == 1 then productQid = productStatements[1].mainsnak.datavalue.value.id		end end return productQid end

-- fallback chain: Commons module subpage -> Wikidata label -> those two in next language -> … function p.getLabelTranslationFallback(term, wordtype, langList) local labelQid, map, label, usedLang -- loop over language fallback list looking for label in the specific language for i,langTable in pairs(langList) do		usedLang = langTable.lang if langTable.subpage and i>1 then local langData = require(langTable.subpage) local termData = langData[wordtype][term] label = termData and ((type(termData) == 'string') and termData or termData.default or termData.n)			-- TODO: adapt for adjectives if label then break end -- label found and we are done end if not labelQid and wordtype == 'nouns' then map = getWDmapping(term) labelQid = (map and map['qid']) and (p.getProductQid(map) or map['qid']) or '0' end if labelQid ~= 0 and wordtype == 'nouns' then label = mw.wikibase.getLabelByLang(labelQid, langTable.lang) -- gives nil if not found if label then break end -- label found and we are done end end if label and usedLang ~= langList[1].lang then label = mw.ustring.format(' \'\'%s\'\' ', usedLang, label) end label = (label		or (map and map['qid'] and makeQIDnoLabelText(term, map['qid']))		or nil) -- TODO: is this ok? (This point should not be reached normally.) -- TODO: add tooltip with info how to add in chosen language return label end

function p._wikidataLinkFallback(lang, term, wikitext) if not (mw.ustring.find(wikitext, '%[%[') or mw.ustring.find(wikitext, '//')) then term = synonyms.nouns[term] or term term = getSingularTerm(term) or term local map = getWDmapping(term) if map and map['qid'] then local qids = {map['qid']} if map['altQids'] then for _,qid in ipairs(map['altQids']) do					qids[#qids+1] = qid end end local sitelink = nil for _,qid in ipairs(qids) do sitelink = mw.wikibase.getSitelink(qid, lang .. 'wiki') if sitelink then break end end if sitelink then wikitext =  .. wikitext ..  else wikitext =  .. wikitext ..  end end end return wikitext -- TODO: implement fallback chain wikipedia -> commmons -> wikidata for links there? end

function p.wikidataLinkFallback(frame) local term = frame.args.term if term == '' then return '' end local wordtype = frame.args.wordtype or 'nouns' local lang = frame.args.lang local query = frame.args.query local noun = frame.args.noun local caseletter = frame.args.caseletter local agreement if noun then agreement = frame:expandTemplate{title = 'technique/' .. lang, args={noun, query = 'gender'}} if agreement == '' then local canonicalNoun = synonyms.nouns[noun] agreement = frame:expandTemplate{title = 'technique/' .. lang, args={canonicalNoun, query = 'gender'}} end -- give a default gender and number agreement for some languages -- that don't specify a default ending -- Template:Technique/ca doesn't have switches for all terms which means -- translations are given as agreement -- --> also give a default gender and number agreement for -- cases where the agreement doesn't match '^[a-z][a-z]$' local defaultGenderNumber = { ca='m', da='c', fr='m', gl='m', it='m', pl='ms', pt='m', ro='m', scn='m'} if defaultGenderNumber[lang] and not string.find(agreement, '^[a-z][a-z]?$') then agreement = defaultGenderNumber[lang] end agreement = agreement .. (caseletter or '') end local wikitext if wordtype == 'nouns' then wikitext= frame:preprocess('') elseif wordtype == 'adjectives' then wikitext= frame:preprocess('') end local unsupportedText = getUnsupportedText(term, wordtype, lang) if wikitext == '' or wikitext == unsupportedText then local canonicalTerm = synonyms[wordtype][term] if canonicalTerm then if wordtype == 'nouns' then wikitext= frame:preprocess('') elseif wordtype == 'adjectives' then wikitext= frame:preprocess('') end end if wikitext == '' or wikitext == (getUnsupportedText(canonicalTerm, wordtype, lang) or unsupportedText) then term = synonyms[wordtype][term] or term local langList = findLang(lang) wikitext = p.getLabelTranslationFallback(term, wordtype, langList) end if not wikitext or wikitext == '' then wikitext = unsupportedText end end if wordtype == 'nouns' then wikitext = p._wikidataLinkFallback(lang, term, wikitext) wikitext = wikitext .. getDeprecatedCat(term) -- TODO: consider to implement this for adjectives as well end return wikitext end

-- turn a adj + noun group into a human-readable string local function makegroup(noun, adj, langData, lang, case, beforeMountedGrammar, compat) -- I preprocess parameters if noun == '' then noun = nil end if adj == '' then adj  = nil end if not (noun or adj) then return nil end if noun then noun = string.lower(noun) end if adj then adj  = string.lower(adj) end -- tansform the noun into another one supported by language specific lists noun = synonyms.nouns[noun] or noun adj = synonyms.adjectives[adj] or adj local canonicalNoun = noun local nounData = langData.nouns[noun] local adjData = langData.adjectives[adj] -- TODO: don't use synonyms before unsuccessful lookup!! local langList = findLang(lang) -- II fallback/error messages if noun and not langData.nouns[noun] then nounData = p.getLabelTranslationFallback(canonicalNoun, 'nouns', langList) or (			' ' .. noun .. ' ' ..			'') end if adj and not langData.adjectives[adj] and not compat == 'yes' then adjData = ' ' .. adj .. ' ' ..			''	end -- III process adj (before noun as it needs the original noun	local gender, number, decl = nil, nil, nil -- do not work if they are initialized in an if	if adj then		-- adjectives with declension		if type(adjData) == 'table' then			local genderConvert = {m=1, f=2, n=3, c=1}			gender = nounData and nounData.gender and genderConvert[nounData.gender]			number = nounData and nounData.number			decl = langData.declension[case]			local parts = adjData.parts			for i = 1,#parts do				if type(parts[i]) == 'table' then					parts[i] = declension.selectAdjectiveForm(parts[i], {number=number, case=decl, gender=gender})				end			end			adj = table.concat(parts)		-- invariable adjectives		elseif type(adjData) == 'string' then			adj = adjData		end	end	-- IV process noun	local group = ''	if noun then		local nounLabel		if type(nounData) == 'table' then -- complex languages			nounLabel = nounData[langData.declension[case] or 'default'] or nounData.default or nounData.n			-- if langData doesn't define cases use 'default' -- if desired case isn't available, fall back to 'default' then nominative -- TODO: use nominative/'n' at all (here)? if not nounLabel then -- TODO: cleanup! nounLabel = p.getLabelTranslationFallback(canonicalNoun, 'nouns', langList) or (					' ' .. noun .. ' ') -- the part after the "or" should never get reached!! end elseif type(nounData) == 'string' then -- languages without declension nounLabel = nounData end local outNoun if nounData.link and not mw.ustring.find(nounLabel, '%[%[') then -- TODO: remove the 2nd part! (once not needed any more) outNoun =  .. nounLabel ..  else outNoun = p._wikidataLinkFallback(lang, canonicalNoun, nounLabel) end outNoun = outNoun .. getDeprecatedCat(canonicalNoun) group = linguistic.noungroup(outNoun, adj, lang) else group = adj .. ''		-- add maintenance category if noun is missing end -- V finalize return langData.nomgroup(group, case, beforeMountedGrammar) end

local function makeQSstatementCore(term, case, compat) -- currently only supports default and "on" cases local qid local map = getWDmapping(term) local core if compat == 'yes' then qid = mw.getCurrentFrame:expandTemplate{title='Technique/WikidataLUT', args={term}} elseif map then qid = map['qid'] end if qid and qid ~= '' then local prop if map['material'] or compat == 'yes' then prop = 'P186' elseif map['process'] then prop = 'P2079' end if prop then core = prop .. ',' .. qid if case and case == 'on' then core = core .. ',P518,Q861259' end end end return core end

local function makeQSstring(args, isSimple) local QScode = '' if not (isSimple and args.noun1) then -- TODO: noun1 could be not bound if not in compat mode return '' end local fragments = {'noun1', 'on'} local statements = {} local isAllFound = true for _,f in ipairs(fragments) do		if args[f] then -- only continue if the term is given --		do local case if f == 'on' then case = 'on' end local statement = makeQSstatementCore(args[f], case, args.compat) if statement and statement ~= '' then table.insert(statements, statement) else isAllFound = false -- TODO: simply return '' and get rid of this variable?? end end end if isAllFound then QScode = ' medium QS:' .. table.concat(statements, ';') .. ' ' --		QScode = '''medium QS:' .. table.concat(statements, ';') ..  -- for debugging end return QScode end

-- main function used by the module function p._technique(args, lang) -- escape the rest for special values with separate translation local isSimple = true for k,_ in pairs(args) do		if k ~= 'noun1' and k ~= 1 and k ~= 'on' and k ~= 2 and k ~= 'compat' and k ~= 'lang' then isSimple = false end end local test = string.lower((args.noun1 or ) .. (args.on or )) if isSimple and test == 'oilcanvas' then local QScode = makeQSstring(args, isSimple) return fallback.translatelua({args={'I18n/oil on canvas', lang=lang}}) .. QScode elseif isSimple and (test == 'oilwood' or test == 'oilpanel') then local QScode = makeQSstring(args, isSimple) return fallback.translatelua({args={'I18n/oil on panel', lang=lang}}) .. QScode elseif isSimple and test == 'unknown' then return mw.getCurrentFrame:expandTemplate{title='unknown', args={'technique'}} end

-- set language local langData local langList = findLang(lang) for _,t in pairs(langList) do		if t.subpage then langData = require(t.subpage) break end end local beforeMountedData for _,noun in ipairs({'on', 'over', 'noun5', 'noun4', 'noun3', 'noun2', 'noun1'}) do		if args[noun] then beforeMountedData = langData.nouns[args[noun]] break end end local beforeMountedGender = beforeMountedData and beforeMountedData.gender local genderConvert = {m=1, f=2, n=3, c=1} beforeMountedGender = beforeMountedGender and genderConvert[beforeMountedGender] local beforeMountedNumber = beforeMountedData and beforeMountedData.number local beforeMountedGrammar = { gender = beforeMountedGender, number = beforeMountedNumber}

-- group arguments local group1 = makegroup(args.noun1, args.adj1, langData, lang, 'default', args.compat) local group2 = makegroup(args.noun2, args.adj2, langData, lang, 'default', args.compat) local group3 = makegroup(args.noun3, args.adj3, langData, lang, 'default', args.compat) local group4 = makegroup(args.noun4, args.adj4, langData, lang, 'default', args.compat) local group5 = makegroup(args.noun5, args.adj5, langData, lang, 'default', args.compat) local maingroup = linguistic.conj({group1, group2, group3, group4, group5}, lang) -- technique without "on", "mounted" and "over" local overgroup = makegroup(args.over, args.adjover, langData, lang, 'over', args.compat) or '' local ongroup = makegroup(args.on, args.adjon, langData, lang, 'on', args.compat) or '' local mountedgroup = makegroup(args.mounted, args.adjmounted, langData, lang, 'mounted', beforeMountedGrammar, args.compat) or '' -- groups set to '' so they can be concatenated by function p.grouporder of the /lang pages, -- this produces excessive spaces ' ' which will be removed below local result = langData.grouporder(maingroup, overgroup, ongroup, mountedgroup) result = mw.text.trim(result) -- maybe useful for compatibility: result = mw.ustring.gsub(result, '%s+', ' ') -- the following should improve word order in some situations with mixed -- RTL and LTR text if mw.language.new(lang):isRTL and not args.compat then result = string.format(' %s ', result) end local QScode = makeQSstring(args, isSimple) result = result .. QScode if not isSimple then result = result .. ''	end return result end

-- function to be called from template namespace function p.technique(frame) local args = {} local templateargs = frame:getParent.args for name, value in pairs(templateargs) do		if value ~= '' then -- nuke empty strings args[name] = mw.text.trim(value) end end -- resolve parameter names -- TODO: adapt to allow arbitrary number of terms and term combinations args.noun1 = args[1] args.noun2 = args['and'] args.noun3 = args['and2'] args.noun4 = args['and3'] args.noun5 = args['and4'] args.on = args[2] or args['on'] args.over = args['over'] args.mounted = args['mounted'] args.adj1 = args['adj'] or args['color'] args.adj2 = args['adjand'] or args['colorand'] args.adj3 = args['adjand2'] or args['colorand2'] args.adj4 = args['adjand3'] or args['colorand3'] args.adj5 = args['adjand4'] or args['colorand4'] args.adjon = args['adjon'] or args['coloron'] args.adjover = args['adjover'] or args['colorover'] args.adjmounted = args['adjmounted'] or args['colormounted']

-- read values given with invoking module, needed e.g. for testing for name, value in pairs(frame.args) do		if value ~= '' then -- nuke empty strings args[name] = value end end local lang = args.lang if not lang or lang == '' then lang = frame:callParserFunction('int', 'lang') -- get user's chosen language end return p._technique(args, lang) end

return p