Module:External links
require('Module:No globals')
 -- local genitive = require('Module:Genitive')._genitive
 local contLang = mw.language.getContentLanguage()
 
 local cmodule = {}
 local conf = require 'Module:External links/conf'(contLang:getCode())
 local hasdatafromwikidata = false
 local hasdatafromlocal = false
 local haswikidatalink = true -- we assume it's connected
 
 local p = {}
 
 local function getLabel(entity, use_genitive, pagetitle)
 	local label = (pagetitle and pagetitle ~= '') and pagetitle or nil
 	if not label and not entity then
 		label = mw.title.getCurrentTitle().text
 	elseif not label then
 		label = mw.wikibase.label(entity.id) or mw.title.getCurrentTitle().text
 	end
 --	return use_genitive and genitive(label, 'sitt') or label
 	return use_genitive and label .. &quot;'s&quot; or label
 end
 
 -- @todo cleanup, this is in production, use the console
 local function dump(obj)
 	return &quot;&lt;pre&gt;&quot; .. mw.dumpObject(obj) .. &quot;&lt;/pre&gt;&quot;
 end
 
 
 local function stringFormatter( datavalue )
 	if datavalue == nil or datavalue['type'] ~= 'string' then
 		return nil
 	end
 	return datavalue.value
 end
 
 local pval = {}
 pval.P1793 = { -- format as a regular expression
 	types = {
 		snaktype = 'value',
 		datatype = 'string',
 	},
 }
 
 pval.P407 = { -- language of work or name
 	types = {
 		snaktype = 'value',
 		datatype = 'wikibase-item',
 		datavalue = {
 			type = 'wikibase-entityid', 
 		}
 	},
 }
 
 pval.P2439 = { -- language
 	types = {
 		snaktype = 'value',
 		datatype = 'wikibase-item',
 		datavalue = {
 			type = 'wikibase-entityid', 
 		}
 	},
 }
 
 pval.P364 = { -- original language of work 
 	types = {
 		snaktype = 'value',
 		datatype = 'wikibase-item',
 		datavalue = {
 			type = 'wikibase-entityid', 
 		}
 	},
 }
 
 pval.P218 = { -- ISO 639-1 language 
 	types = {
 		snaktype = 'value',
 		datatype = 'external-id',
 		datavalue = {
 			type = 'string', 
 		}
 	},
 }
 
 pval.P305 = { -- IETF language tag
 	types = {
 		snaktype = 'value',
 		datatype = 'external-id',
 		datavalue = {
 			type = 'string', 
 		}
 	},
 }
 
 pval.P582 = { -- end time
 	types = {
 		snaktype = 'value',
 		datatype = 'time',
 		datavalue = {
 			type = 'string', 
 		}
 	},
 }
 
 
 -- This is a really makeshift crappy converter, but it'll do some basic
 -- conversion from PCRE to Lua-style patterns (note that this only work
 -- in very few cases)
 local function regexConverter( regex )
 	local output = regex
 	output = string.gsub(output, &quot;\\d{2}&quot;, &quot;%%d%%d&quot;)
 	output = string.gsub(output, &quot;\\d{3}&quot;, &quot;%%d%%d%%d&quot;)
 	output = string.gsub(output, &quot;\\d{4}&quot;, &quot;%%d%%d%%d%%d&quot;)
 	output = string.gsub(output, &quot;\\d{5}&quot;, &quot;%%d%%d%%d%%d%%d&quot;)
 	output = string.gsub(output, &quot;\\d{6}&quot;, &quot;%%d%%d%%d%%d%%d%%d&quot;)
 	output = string.gsub(output, &quot;\\d{7}&quot;, &quot;%%d%%d%%d%%d%%d%%d%%d&quot;)
 	output = string.gsub(output, &quot;\\d{8}&quot;, &quot;%%d%%d%%d%%d%%d%%d%%d%%d&quot;)
 	output = string.gsub(output, &quot;\\d&quot;, &quot;%%d&quot;)
 	
 	return output
 end
 
 
 local function getFormatterUrl( prop, value )
 	local head = &quot;&quot;
 	local tail = &quot;&quot;
 	local entity = mw.wikibase.getEntity(prop)
 	-- to avoid deep tests
 	if not entity or not entity.claims then
 		return head
 	end
 	-- get the claims for this entity
 	local statements = entity.claims['P1630'] -- formatter URL
 	-- to avoid deep tests
 	if not statements then
 		return head
 	end
 	local formatters = {}
 	-- let's go through the claims
 	for _, claim in ipairs( statements ) do
 		-- to avoid deep tests
 		if not claim then
 			claim = {}
 		end
 		local valid = claim['type'] == 'statement'
 			and claim['rank'] ~= 'deprecated'
 		if valid then
 			local mainsnak = claim.mainsnak or {}
 			local preferred = claim['rank'] == 'preferred'
 			-- get any qualifiers for this claim (we are interested in P1793 for
 			-- indication of which claim is correct) 
 			local qualifiers = claim.qualifiers or {}
 			-- now let's check the qualifier we are interested in
 			local qualid = 'P1793' -- format as a regular expression
 			-- if the claim has this qualifier
 			if qualifiers[qualid] then
 				-- it's here, let's check it out!
 				local items = {}
 				-- traverse all snaks in this qualifier
 				for _, qualsnak in ipairs( qualifiers[qualid] ) do
 					if qualsnak and pval[qualid] then
 						--mw.log(&quot;qualsnak = &quot; .. dump(qualsnak))
 						-- check if the snak is of the correct snaktype and datatype
 						local valid = qualsnak.snaktype == pval[qualid].types.snaktype
 							and qualsnak.datatype == pval[qualid].types.datatype
 						if valid then
 							-- we'll have to convert the regex to Lua-style
 							local regex = regexConverter(qualsnak.datavalue.value)
 							local test = string.match( value, '^'..regex..'$' )
 							if test then
 								-- it matched, this is correct and overrides any other.
 								if preferred then
 									head = mainsnak.datavalue.value
 								else
 									tail = mainsnak.datavalue.value
 								end
 							end
 						end
 					end
 				end
 			else
 				-- we don't have any qualifier, is it preferred?
 				if (head == '' and preferred) or (tail == '' and not preferred) then
 					-- if we don't have any other, use this one
 					if preferred and head == '' then
 						head = mainsnak.datavalue.value
 					elseif not preferred and tail == '' then
 						tail = mainsnak.datavalue.value
 					end
 				end
 			end
 		end
 	end
 	return head ~= '' and head or tail
 
 end
 
 
 
 local function getLanguageData(prop, qid)
 	local head = {}
 	local tail = {}
 	-- mw.log(&quot;getLanguageData, prop=&quot;..dump(prop)..&quot; qid=&quot;..dump(qid))
 	-- get the entity we are checking
 	local entity = mw.wikibase.getEntityObject(qid)
 	-- to avoid deep tests
 	if not entity then
 		return nil
 	end
 	if not entity.claims then
 		return {}
 	end
 	-- get the claims for this entity
 	local statements = entity.claims[prop]
 	-- to avoid deep tests
 	if not statements then
 		return {}
 	end
 	-- mw.log(&quot;getLanguageData going through claims=&quot;..dump(statements))
 	-- let's go through the claims
 	for _, claim in ipairs( statements ) do
 		-- to avoid deep tests
 		if not claim then
 			claim = {}
 		end
 		local valid = claim['type'] == 'statement'
 			and claim['rank'] ~= 'deprecated'
 		if valid then
 			local mainsnak = claim.mainsnak or {}
 			local preferred = claim['rank'] == 'preferred'
 			-- verify the item is what we expect
 			local valid = mainsnak.snaktype == pval[prop].types.snaktype
 				and mainsnak.datatype == pval[prop].types.datatype
 				and mainsnak.datavalue.type == pval[prop].types.datavalue.type
 			if valid then
 				-- mw.log(&quot;getLanguageData claim is valid=&quot;..dump(claim))
 				-- if this is the correct P-value, dive into it and get P218 (ISO 639-1)
 				if mainsnak.property == 'P364' then -- original language of work
 					if preferred then
 						head[#head+1] = table.concat(getLanguageData('P218', 'Q'..mainsnak.datavalue.value['numeric-id']), conf:a('mod-filter-separator'))
 					else
 						tail[#tail+1] = table.concat(getLanguageData('P218', 'Q'..mainsnak.datavalue.value['numeric-id']), conf:a('mod-filter-separator'))
 					end
 				elseif mainsnak.property == 'P218' or mainsnak.property == 'P305' then -- ISO 639-1 code or IETF language tag
 					if preferred then
 						head[#head+1] = stringFormatter(mainsnak.datavalue)
 					else
 						tail[#tail+1] = stringFormatter(mainsnak.datavalue)
 					end
 				end
 			end
 		end
 	end
 	-- mw.log(&quot;getLanguageData returning head=&quot;..dump(head)..&quot; tail=&quot;..dump(tail))
 	return #head&gt;0 and head or tail
 end
 
 local langqvalorder = {'P407','P2439','P364'}
 local otherqvalorder = {'P582'}
 
 local function getValuesFromWikidata(props)
 	local head = {}
 	local tail = {}
 	-- mw.log(&quot;getValuesFromWikidata, props=&quot;..dump(props))
 	-- get the entity we are checking
 	local entity = mw.wikibase.getEntityObject()
 	-- to avoid deep tests
 	if not entity then
 		--mw.log(&quot;getValuesFromWikidata no entity&quot;)
 		return nil
 	end
 	if not entity.claims or not props or not props.prop or props.prop == '' then
 		--mw.log(&quot;getValuesFromWikidata no claims or no props&quot;)
 		return {}
 	end
 	-- get the claims for this entity
 	local statements = entity.claims[props.prop]
 	-- to avoid deep tests
 	if not statements then
 		return {}
 	end
 	-- let's go through the claims
 	for _, claim in ipairs( statements ) do
 		-- to avoid deep tests
 		if not claim then
 			claim = {}
 		end
 		local valid = claim['type'] == 'statement'
 			and claim['rank'] ~= 'deprecated'
 		if valid then
 			-- mw.log(&quot;getValuesFromWikidata valid claim=&quot;..dump(claim))
 			local mainsnak = claim.mainsnak or {}
 			local preferred = claim['rank'] == 'preferred'
 			-- get the content of the claim (the identifier)
 			local langcode = props.langcode
 			local checklangcode = nil
 			if props.langcode and props.langcode ~= '' then
 				checklangcode = string.find(langcode, &quot;([pP]%d+)&quot;)
 			end
 			if checklangcode and checklangcode ~= &quot;&quot; then
 				-- this is a P-value for language-code, so we'll check qualifiers for languagedata
 				-- first get any qualifiers
 				local qualifiers = claim.qualifiers or {}
 				for _, qualid in ipairs( langqvalorder ) do
 					-- if the claim has this qualifier
 					if qualifiers[qualid] then
 						-- it's here, let's check it out!
 						local items = {}
 						-- traverse all snaks in this qualifier
 						for _, qualsnak in ipairs( qualifiers[qualid] ) do
 							if qualsnak and pval[qualid] then
 								-- mw.log(&quot;qualsnak = &quot; .. dump(qualsnak))
 								-- check if the snak is of the correct snaktype and datatype
 								local valid = qualsnak.snaktype == pval[qualid].types.snaktype
 									and qualsnak.datatype == pval[qualid].types.datatype
 								if valid then
 									-- now get the actual data
 									langcode = table.concat(getLanguageData('P305', 'Q'..qualsnak.datavalue.value['numeric-id']), '')
 								end
 							end
 						end
 					end
 					-- mw.log(&quot;langcode is now=&quot;..dump(langcode))
 				end
 				if string.find(langcode, &quot;([pP]%d+)&quot;) then
 					-- we still don't have any langcode, so we default to &quot;en&quot;
 					langcode = nil
 				end
 			end
 			local stillvalid = true
 			-- we should check a couple of other qualifiers as well
 			-- first get any qualifiers
 			local qualifiers = claim.qualifiers or {}
 			for _, qualid in ipairs( otherqvalorder ) do
 				-- if the claim has this qualifier
 				if qualifiers[qualid] then
 					-- it's here, let's check it out!
 					local items = {}
 					-- traverse all snaks in this qualifier
 					for _, qualsnak in ipairs( qualifiers[qualid] ) do
 						if qualsnak and pval[qualid] then
 							-- mw.log(&quot;qualsnak = &quot; .. dump(qualsnak))
 							-- check if the snak is of the correct snaktype and datatype
 							local valid = qualsnak.snaktype == pval[qualid].types.snaktype
 								and qualsnak.datatype == pval[qualid].types.datatype
 							if not valid then
 								-- sorry, this is not correct
 								mw.log(&quot;qualsnak = INCORRECT&quot;)
 								stillvalid = false
 							end
 						end
 					end
 				end
 				-- mw.log(&quot;langcode is now=&quot;..dump(langcode))
 			end
 			if stillvalid then
 				if preferred then
 					head[#head+1] = { value=stringFormatter(mainsnak.datavalue) }
 					if langcode and langcode ~= '' then
 						head[#head]['langcode'] = langcode
 					end
 				else
 					tail[#tail+1] = { value=stringFormatter(mainsnak.datavalue) }
 					if langcode and langcode ~= '' then
 						tail[#tail]['langcode'] = langcode
 					end
 				end
 			end
 		end
 	end
 	-- mw.log(&quot;getValuesFromWikidata returning head=&quot;..dump(head)..&quot; tail=&quot;..dump(tail))
 	return #head&gt;0 and head or tail
 end
 
 local function findMainLinksOnWikidata(props, pagetitle, short_links)
 	local output = {}
 	local pid = nil
 	-- get the entity we are checking
 	local entity = mw.wikibase.getEntityObject()
 	-- to avoid deep tests
 	if not entity then
 		return nil
 	end
 	local values = getValuesFromWikidata(props)
 	for _, value in ipairs( values ) do
 		local verified_value = nil
 		if props.regex then
 			-- we have a local defined regex, so this will have to pass first
 			-- maybe we'll have to convert the regex to Lua-style
 			local regex = regexConverter(props.regex)
 			local test = string.match( value.value, '^'..regex..'$' )
 			--mw.log(&quot;testing with &quot;..regex.. &quot; and test=&quot;..dump(test)..&quot; and value=&quot;..id)
 			if test then
 				-- it matched, this is correct and overrides any other.
 				verified_value = value.value
 			end
 		else
 			verified_value = value.value
 		end
 		if verified_value then
 			local url = ''
 			output[#output+1] = {}
 			output[#output].langcode = value.langcode
 			output[#output].category = {}
 			if props.url_f then
 				-- we have a local defined url-formatter function, use it as first priority
 				url = props.url_f(verified_value)
 				if props.track and not string.find(props.langcode, &quot;([pP]%d+)&quot;) then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-wd'), props.prop):plain()
 				elseif props.track then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-wd'), props.prop):plain()
 				end
 			elseif props.url then
 				-- we have a local defined url-formatter string, use it as second priority
 				url = mw.message.newRawMessage(props.url, verified_value):plain()
 				if props.track and not string.find(props.langcode, &quot;([pP]%d+)&quot;) then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-wd'), props.prop):plain()
 				elseif props.track then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-wd'), props.prop):plain()
 				end
 			else
 				-- get the formatvalue from the property, if it exists
 				local formatterUrl = getFormatterUrl(props.prop, verified_value)
 				if formatterUrl ~= '' then
 					url = mw.message.newRawMessage(formatterUrl, verified_value):plain()
 					if props.track then 
 						output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-wd'), props.prop):plain()
 					end
 				end
 			end
 			if url ~= '' then
 				local this_wiki = mw.getContentLanguage()
 				local this_wiki_code = this_wiki:getCode()
 				local langlink = (value.langcode and value.langcode ~= '' and value.langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), value.langcode, mw.language.fetchLanguageName(value.langcode, this_wiki_code)) or &quot;&quot;
 				if short_links and props.short then
 					output[#output].text =
 						mw.message.newRawMessage(props.short,
 							getLabel(entity, props.genitive, pagetitle),
 							url,
 							langlink,
 							verified_value,
 							mw.uri.encode(verified_value, 'PATH'))
 						:plain()
 				else
 					output[#output].text =
 						mw.message.newRawMessage(props.message,
 							getLabel(entity, props.genitive, pagetitle),
 							url,
 							langlink,
 							verified_value,
 							mw.uri.encode(verified_value, 'PATH'))
 						:plain()
 				end
 			end
 		end
 	end
 	--mw.log(&quot;findMainLinksOnWikidata returning=&quot;..dump(output))
 	return output
 end
 
 local function getSitelinksFromWikidata(props, entity)
 	local output = {}
 	--mw.log(&quot;getSitelinksFromWikidata, props=&quot;..dump(props))
 	-- to avoid deep tests
 	if not entity then
 		entity = mw.wikibase.getEntityObject()
 		if not entity then
 			--mw.log(&quot;getSitelinksFromWikidata no entity&quot;)
 			return nil
 		end
 	end
 	local requested_sitelink = string.match(props.prop, &quot;SL(%l+)&quot;)
 	local sitelinks = entity:getSitelink(requested_sitelink)
 	if sitelinks and sitelinks ~= '' then
 		output[#output+1] = { value = sitelinks }
 	end
 	--mw.log(&quot;getSitelinksFromWikidata returning output=&quot;..dump(output))
 	return output
 end
 
 
 local function findSiteLinksOnWikidata(props, pagetitle, short_links)
 	local output = {}
 	local pid = nil
 	-- get the entity we are checking
 	local entity = mw.wikibase.getEntityObject()
 	-- to avoid deep tests
 	if not entity then
 		return nil
 	end
 	local values = getSitelinksFromWikidata(props)
 	for _, value in ipairs( values ) do
 		local verified_value = nil
 		if props.regex then
 			-- we have a local defined regex, so this will have to pass first
 			-- maybe we'll have to convert the regex to Lua-style
 			local regex = regexConverter(props.regex)
 			local test = string.match( value.value, '^'..regex..'$' )
 			--mw.log(&quot;testing with &quot;..regex.. &quot; and test=&quot;..dump(test)..&quot; and value=&quot;..id)
 			if test then
 				-- it matched, this is correct and overrides any other.
 				verified_value = value.value
 			end
 		else
 			verified_value = value.value
 		end
 		if verified_value then
 			--mw.log(&quot;it's verified..&quot;)
 			local url = ''
 			output[#output+1] = {}
 			output[#output].langcode = value.langcode
 			output[#output].category = {}
 			if props.url_f then
 				-- we have a local defined url-formatter function, use it as first priority
 				url = props.url_f(verified_value)
 				if props.track and not string.find(props.langcode, &quot;(SL%l+)&quot;) then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-wd'), props.prop):plain()
 				elseif props.track then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-wd'), props.prop):plain()
 				end
 			elseif props.url then
 				-- we have a local defined url-formatter string, use it as second priority
 				url = mw.message.newRawMessage(props.url, verified_value):plain()
 				if props.track and not string.find(props.langcode, &quot;(SL%l+)&quot;) then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-wd'), props.prop):plain()
 				elseif props.track then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-wd'), props.prop):plain()
 				end
 			else
 				url = verified_value:gsub(' ','_')
 				if props.track then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-wd'), props.prop):plain()
 				end
 			end
 			if url ~= '' then
 				local this_wiki = mw.getContentLanguage()
 				local this_wiki_code = this_wiki:getCode()
 				local langlink = (value.langcode and value.langcode ~= '' and value.langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), value.langcode, mw.language.fetchLanguageName(value.langcode, this_wiki_code)) or &quot;&quot;
 				if short_links and props.short then
 					output[#output].text =
 						mw.message.newRawMessage(props.short,
 							getLabel(entity, props.genitive, pagetitle),
 							url,
 							langlink,
 							verified_value,
 							mw.uri.encode(verified_value, 'PATH'))
 						:plain()
 				else
 					output[#output].text =
 						mw.message.newRawMessage(props.message,
 							getLabel(entity, props.genitive, pagetitle),
 							url,
 							langlink,
 							verified_value,
 							mw.uri.encode(verified_value, 'PATH'))
 						:plain()
 				end
 			end
 		end
 	end
 	--mw.log(&quot;findSiteLinksOnWikidata returning=&quot;..dump(output))
 	return output
 end
 
 
 local function findMainLinksLocal(props, pagetitle, short_links, local_value)
 	local output = {}
 	-- to avoid deep tests
 	if not props.prop then
 		return nil
 	end
 	if not (local_value or local_value == '') then
 		-- bail out if no value is present
 		return output
 	end
 	-- get the formatvalue from the property
 	local verified_value = local_value
 	if props.regex and props.regex ~= '' then
 		-- let's verify the id
 		-- maybe we'll have to convert the regex to Lua-style
 		local regex = regexConverter(props.regex)
 		local test = string.match( local_value, '^'..regex..'$' )
 		if test then
 			-- it matched, this is correct
 			verified_value = local_value
 		else
 			verified_value = nil
 		end
 		
 	end
 	if not verified_value then
 		return output
 	end
 	local wikidata_property = string.find(props.prop, &quot;([pP]%d+)&quot;)
 	local wikidata_values = {}
 	if wikidata_property then
 		-- get any wikidata values to see if they are equal to local values
 		wikidata_values = getValuesFromWikidata(props)
 	end
 	if wikidata_property or (props.url and props.url ~= '') or (props.url_f) then
 		output[#output+1] = {}
 		output[#output].langcode = string.find(props.langcode, &quot;([pP]%d+)&quot;) and &quot;&quot; or props.langcode
 		--mw.log(&quot;findMainLinksLocal - props=&quot;..dump(props)..&quot; langcode=&quot;..output[#output].langcode)
 		output[#output].category = {}
 		local url = ''
 		if props.track and wikidata_property and wikidata_values and #wikidata_values then
 			local local_value_in_wikidata = false
 			for _,value in ipairs( wikidata_values ) do
 				if value.value == verified_value then
 					local_value_in_wikidata = true
 				end
 			end
 			output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), (local_value_in_wikidata and 'track-cat-local-wd-equal' or 'track-cat-local-wd-unequal')), props.prop):plain()
 		end
 		if wikidata_property and wikidata_values and #wikidata_values then
 			hasdatafromwikidata = true -- signal up the chain this article has a wikidata claim
 		end
 		if props.url_f then
 			-- we have a local defined url-formatter function, use it as first priority
 			url = props.url_f(verified_value)
 			if props.track then 
 				output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-local'), props.prop):plain()
 			end
 		elseif props.url then
 			-- we have a local defined url-formatter string, use it as second priority
 			url = mw.message.newRawMessage(props.url, verified_value):plain()
 			if props.track then 
 				output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-local'), props.prop):plain()
 			end
 		elseif wikidata_property then
 			-- get the formatvalue from the property, if it exists
 			local formatterUrl = getFormatterUrl(props.prop, verified_value)
 			if formatterUrl ~= '' then
 				url = mw.message.newRawMessage(formatterUrl, verified_value):plain()
 				if props.track then 
 					output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-local'), props.prop):plain()
 				end
 			end
 		else
 			-- no other choice, bail out
 			return {}
 		end
 		local this_wiki = mw.getContentLanguage()
 		local this_wiki_code = this_wiki:getCode()
 		local langlink = (output[#output].langcode and output[#output].langcode ~= '' and output[#output].langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), props.langcode, mw.language.fetchLanguageName(props.langcode, this_wiki_code)) or &quot;&quot;
 		if short_links and props.short then
 			output[#output].text =
 				mw.message.newRawMessage(props.short,
 					getLabel(nil, props.genitive, pagetitle),
 					url,
 					langlink,
 					verified_value,
 					mw.uri.encode(verified_value, 'PATH'))
 				:plain()
 		else
 			output[#output].text =
 				mw.message.newRawMessage(props.message,
 					getLabel(nil, props.genitive, pagetitle),
 					url,
 					langlink,
 					verified_value,
 					mw.uri.encode(verified_value, 'PATH'))
 				:plain()
 		end
 	end
 	--mw.log(&quot;findMainLinksLocal returning=&quot;..dump(output))
 	return output
 end
 
 local function findSiteLinksLocal(props, pagetitle, short_links, local_value)
 	local output = {}
 	-- to avoid deep tests
 	if not props.prop then
 		return nil
 	end
 	if not (local_value or local_value == '') then
 		-- bail out if no value is present
 		return output
 	end
 	-- get the formatvalue from the property
 	local verified_value = local_value
 	if props.regex and props.regex ~= '' then
 		-- let's verify the id
 		-- maybe we'll have to convert the regex to Lua-style
 		local regex = regexConverter(props.regex)
 		local test = string.match( local_value, '^'..regex..'$' )
 		if test then
 			-- it matched, this is correct
 			verified_value = local_value
 		else
 			verified_value = nil
 		end
 		
 	end
 	if not verified_value then
 		return output
 	end
 	local wikidata_property = string.find(props.prop, &quot;(SL.+)&quot;)
 	local wikidata_values = {}
 	if wikidata_property then
 		-- get any wikidata values to see if they are equal to local values
 		wikidata_values = getSitelinksFromWikidata(props)
 	end
 	if wikidata_property or (props.url and props.url ~= '') or (props.url_f) then
 		output[#output+1] = {}
 		output[#output].langcode = string.find(props.langcode, &quot;(SL.+)&quot;) and &quot;&quot; or props.langcode
 		--mw.log(&quot;findSiteLinksLocal - props=&quot;..dump(props)..&quot; langcode=&quot;..output[#output].langcode ..&quot; wikidata_values=&quot;..dump(wikidata_values))
 		output[#output].category = {}
 		local url = ''
 		if props.track and wikidata_property and wikidata_values and #wikidata_values then
 			local local_value_in_wikidata = false
 			for _,value in ipairs( wikidata_values ) do
 				if value.value == verified_value then
 					local_value_in_wikidata = true
 				end
 			end
 			output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), (local_value_in_wikidata and 'track-cat-local-wd-equal' or 'track-cat-local-wd-unequal')), props.prop):plain()
 		end
 		if wikidata_property and wikidata_values and #wikidata_values then
 			hasdatafromwikidata = true -- signal up the chain this article has a wikidata claim
 		end
 		if props.url_f then
 			-- we have a local defined url-formatter function, use it as first priority
 			url = props.url_f(verified_value)
 			if props.track then 
 				output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-local'), props.prop):plain()
 			end
 		elseif props.url then
 			-- we have a local defined url-formatter string, use it as second priority
 			url = mw.message.newRawMessage(props.url, verified_value):plain()
 			if props.track then 
 				output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-local-local'), props.prop):plain()
 			end
 		elseif wikidata_property then
 			url = verified_value:gsub(' ','_')
 			if props.track then 
 				output[#output].category[#output[#output].category+1] = mw.message.newRawMessage(cmodule:getMessage(contLang:getCode(), 'track-cat-wd-local'), props.prop):plain()
 			end
 		else
 			-- no other choice, bail out
 			return {}
 		end
 		local this_wiki = mw.getContentLanguage()
 		local this_wiki_code = this_wiki:getCode()
 		local langlink = (output[#output].langcode and output[#output].langcode ~= '' and output[#output].langcode ~= this_wiki_code) and mw.message.newRawMessage(conf:g('msg-langcode'), props.langcode, mw.language.fetchLanguageName(props.langcode, this_wiki_code)) or &quot;&quot;
 		if short_links and props.short then
 			output[#output].text =
 				mw.message.newRawMessage(props.short,
 					getLabel(nil, props.genitive, pagetitle),
 					url,
 					langlink,
 					verified_value,
 					mw.uri.encode(verified_value, 'PATH'))
 				:plain()
 		else
 			output[#output].text =
 				mw.message.newRawMessage(props.message,
 					getLabel(nil, props.genitive, pagetitle),
 					url,
 					langlink,
 					verified_value,
 					mw.uri.encode(verified_value, 'PATH'))
 				:plain()
 		end
 	end
 	--mw.log(&quot;findSiteLinksLocal returning=&quot;..dump(output))
 	return output
 end
 
 
 local function addLinkback(str, property)
 	local id = mw.wikibase.getEntityObject()
 	if not id then
 		return str
 	end
 	if type(id) == 'table' then
 		id = id.id
 	end
 	
 	local class = ''
 	local url = ''
 	if property then
 		class = 'wd_' .. string.lower(property)
 		url = mw.uri.fullUrl('d:' .. id .. '#' .. property)
 		url.fragment = property
 	else
 		url = mw.uri.fullUrl('d:' .. id )
 	end
 	
 	local title = conf:g('wikidata-linkback-edit')
 	local icon = '[%s [[File:Blue pencil.svg|%s|10px|text-top|link=]] ]'
 	url = tostring(url)
 	local v = mw.html.create('span')
 		:addClass(class)
 		:wikitext(str)
 		:tag('span')
 			:addClass('noprint plainlinks wikidata-linkback')
 			:css('padding-left', '.3em')
 			:wikitext(icon:format(url, title))
 		:allDone()
 	return tostring(v)
 end
 
 
 local function getArgument(frame, argument)
 	local args = frame.args
 	if args[1] == nil then
 		local pFrame = frame:getParent();
 		args = pFrame.args;
 		for k,v in pairs( frame.args ) do
 			args[k] = v;
 		end
 	end
 	if args[argument] then
 		return args[argument]
 	end
 	return nil
 end
 
 
 local function removeEntry(conf_claims, identifier, property)
 	for i, props in ipairs(conf_claims) do
 		if props[identifier] == property then
 			table.remove(conf_claims, i)
 		end
 	end
 	return conf_claims
 end
 
 function p.getLinks(frame)
 	local configured_conf = getArgument(frame, conf:a('arg-conf'))
 	if configured_conf then
 		cmodule = require ('Module:External_links/conf/'..configured_conf)
 	else
 		error(mw.message.newRawMessage(conf:g('missing-conf'), configured_conf):plain())
 	end
 	local output = {}
 	local category = {}
 	local conf_claims = cmodule:getConfiguredClaims(contLang:getCode())
 	local limits = cmodule:getLimits()
 	assert(limits, mw.message.newRawMessage(conf:g('missing-limits'), configured_conf):plain())
 	local links_shown = getArgument(frame, conf:a('arg-maxlink'))
 	local pagetitle = getArgument(frame, conf:a('arg-title'))
 	-- get a list of tracked properties from the article itself
 	local requested_tracking = getArgument(frame, conf:a('arg-track'))
 	if requested_tracking and requested_tracking ~= '' then
 		-- the properties should be written as P1234, P2345 and other 
 		-- version corresponding to the applicable property-identifiers in the config
 		for track_prop in string.gmatch(requested_tracking,&quot;([^ ,;:]+)&quot;) do
 			-- get the requested properties and be able to access them
 			-- like req_prop['P345'] to verify if it was requested
 			local remove_track = string.match(track_prop, &quot;^\-(.*)&quot;)
 			for i,claim in ipairs ( conf_claims )  do
 				if remove_track == claim.prop or remove_track == conf:a('mod-filter-all') then
 					-- if a property starts with &quot;-&quot;, then we'll simply remove that 
 					-- property from the conf_claims
 					conf_claims[i]['track'] = false
 				elseif track_prop == claim.prop or track_prop == conf:a('mod-filter-all') then
 					conf_claims[i]['track'] = true
 				end
 			end
 		end
 	end
 	-- get a list of &quot;approved&quot; properties from the article itself
 	local requested_properties = getArgument(frame, conf:a('arg-properties'))
 	--mw.log(&quot;requested_properties=&quot;..dump(requested_properties))
 	-- assume all properties are allowed
 	local req_prop = {}
 	local no_req_prop = false  -- we'll allow properties to be filtered for now 
 	if requested_properties and requested_properties ~= '' then
 		-- the properties should be written as P1234, P2345 and other 
 		-- version corresponding to the applicable property-identifiers in the config
 		for i in string.gmatch(requested_properties,&quot;([^ ,;:]+)&quot;) do
 			-- get the requested properties and be able to access them
 			-- like req_prop['P345'] to verify if it was requested
 			if i == conf:a('mod-filter-all') then
 				-- this is a special modifier, saying we should ignore 
 				-- all previous and future positive filters and remove the
 				-- filter (with exception of negative filters)
 				req_prop = {}
 				no_req_prop = true
 			end
 			local remove_prop = string.match(i, &quot;^\-(.*)&quot;)
 			if remove_prop then
 				-- if a property starts with &quot;-&quot;, then we'll simply remove that 
 				-- property from the conf_claims
 				conf_claims = removeEntry(conf_claims, 'prop', remove_prop)
 			elseif not no_req_prop then -- only if we are allowing properties to be filtered 
 				req_prop[i] = 1
 				-- cheat to make #req_prop indicate populated table
 				req_prop[1] = 1
 			end
 		end
 	end
 	local requested_langs = getArgument(frame, conf:a('arg-languages'))
 	--mw.log(&quot;requested_langs=&quot;..dump(requested_langs))
 	-- assume all languages are allowed
 	local req_lang = {}
 	local no_req_lang = false  -- we'll allow languages to be filtered for now
 	if requested_langs and requested_langs ~= '' then
 		-- the languages should be written as langcodes as used in the conf_claims
 		for i in string.gmatch(requested_langs,&quot;([^ ,;:]+)&quot;) do
 			-- get the requested languages and be able to access them
 			if i == conf:a('mod-filter-all') then
 				-- this is a special modifier, saying we should ignore 
 				-- all previous and future positive filters and remove the
 				-- filter (with exception of negative filters)
 				req_lang = {}
 				no_req_lang = true
 			end
 			-- like req_lang['en'] to verify if it was requested
 			local remove_lang = string.match(i, &quot;^\-(.*)&quot;)
 			if remove_lang then
 				-- if a language starts with &quot;-&quot;, then we'll simply remove that 
 				-- language from the conf_claims
 				conf_claims = removeEntry(conf_claims, 'langcode', remove_lang)
 			elseif not no_req_lang then -- only if we are allowing languages to be filtered 
 				req_lang[i] = 1
 				-- cheat to make #req_lang indicate populated table
 				req_lang[1] = 1
 			end
 		end
 	end
 	local short_links = getArgument(frame, conf:a('arg-short'))
 	if short_links and short_links ~= '' then
 		short_links = true
 	else
 		short_links = false
 	end
 	local showinline = getArgument(frame, conf:a('arg-inline'))
 	if showinline and showinline ~= '' then
 		showinline = true
 	else
 		showinline = false
 	end
 	if not links_shown or links_shown == '' then
 		links_shown = limits['links-shown'] and limits['links-shown'] or 10
 	else
 		links_shown = tonumber(links_shown)
 	end
 	local somedataonwikidata = (short_links and false or true)
 	--mw.log(&quot;conf_claims=&quot;..dump(conf_claims))
 	--mw.log(&quot;req_prop=&quot;..dump(req_prop))
 	--mw.log(&quot;req_lang=&quot;..dump(req_lang))
 	--mw.log(&quot;short_links=&quot;..dump(short_links))
 	for _, props in ipairs(conf_claims) do
 		-- if we're called with a list of approved properties or languages, check if this one is &quot;approved&quot;
 		if (#req_prop==0 or req_prop[props.prop]) and (#req_lang==0 or req_lang[props.langcode] or string.find(props.langcode, &quot;([pP]%d+)&quot;)) then
 			--mw.log(&quot;checking claim=&quot;..dump(props))
 			local links = {}
 			local checkedonwikidata = false
 			-- get the any local overriding value from the call
 			local wikivalue = getArgument(frame, props.prop)
 			--mw.log(&quot;wikivalue=&quot;..dump(wikivalue))
 			if (not wikivalue or wikivalue == &quot;&quot;) and string.find(props.prop, &quot;([pP]%d+)&quot;) then
 				-- the property is a Pnnn type, and therefore on Wikidata
 				links = findMainLinksOnWikidata(props, pagetitle, short_links)
 				if links == nil then
 					-- a nil-value indicated no wikidata-link
 					haswikidatalink = false
 					links = {}
 				else
 					checkedonwikidata = true
 				end
 			elseif (not wikivalue or wikivalue == &quot;&quot;) and string.find(props.prop, &quot;(SL%l+)&quot;) then
 				-- this is a sitelink-type (SLspecieswiki)
 				--mw.log(&quot;finding sitelinks..&quot;)
 				links = findSiteLinksOnWikidata(props, pagetitle, short_links)
 				if links == nil then
 					-- a nil-value indicated no wikidata-link
 					haswikidatalink = false
 					links = {}
 				else
 					checkedonwikidata = true
 				end
 			elseif (wikivalue and wikivalue ~= &quot;&quot;) and string.find(props.prop, &quot;(SL%l+)&quot;) then
 				-- this is a sitelink-type (SLspecieswiki)
 				links = findSiteLinksLocal(props, pagetitle, short_links, wikivalue)
 			elseif wikivalue and wikivalue ~= '' then
 				-- the property is of another annotation, and therefore a local construct
 				links = findMainLinksLocal(props, pagetitle, short_links, wikivalue)
 			end
 			--mw.log(&quot;links=&quot;..dump(links))
 			for _,v in ipairs(links) do
 				-- we'll have to check langcodes again as they may have come from wikidata
 				if (#req_lang==0 or req_lang[v.langcode]) then
 					if checkedonwikidata and not hasdatafromwikidata then
 						-- add a general tracking category for articles with data from wikidata
 						hasdatafromwikidata = true
 						category[#category+1] = cmodule:getMessage(contLang:getCode(), 'with-data-cat')
 					elseif not checkedonwikidata and not hasdatafromlocal then
 						-- add a general tracking category for articles with data from template-calls in local articles
 						hasdatafromlocal = true
 						category[#category+1] = cmodule:getMessage(contLang:getCode(), 'with-local-cat')
 					end
 					if short_links and props.short and v.text and v.text ~= '' then
 						-- if short links were requested, and a short definition exists for this property, let's use it
 						if #output==0 then
 							output[#output+1] = v.text
 						else
 							output[#output] = output[#output] .. cmodule:getMessage(contLang:getCode(),'short-list-separator') .. v.text
 						end
 						somedataonwikidata = true
 					elseif not short_links and not showinline and v.text and v.text ~= '' then
 						-- only if short links were not requested
 						output[#output+1] = (#output&gt;=1 and conf:g('msg-ul-prepend') or '')			-- if this is the first link, we won't output a list-element (msg-ul-prepend) 
 							.. (checkedonwikidata and addLinkback(v.text, props.prop) or v.text)	-- if the link comes from wikidata, also output a linkback.
 					elseif not short_links and showinline and v.text and v.text ~= '' then
 						-- only if short links were not requested
 						output[#output+1] = v.text
 					end
 					if props.track and v.category and #v.category then
 						-- add category if tracking is on for this property and a category exists in the link-result.
 						for _,cats in ipairs( v.category ) do
 							category[#category+1] = cats
 						end
 					end
 					if links_shown&gt;0 then
 						links_shown = links_shown - 1
 					else
 						break
 					end
 				end
 			end
 			if links_shown==0 then
 				break
 			end
 		end
 	end
 	local outtext = &quot;&quot; 
 	if short_links and #output&gt;0 then
 		-- if these are short links, output the whole thing with linkback to wikidata
 		--mw.log(&quot;somedataonwikidata=&quot;..dump(somedataonwikidata)..&quot; and output=&quot;..dump(output)..&quot; and #output=&quot;..dump(#output))
 		outtext = (somedataonwikidata 
 			and addLinkback(table.concat(output,cmodule:getMessage(contLang:getCode(),'short-list-separator')), nil)
 			or table.concat(output,cmodule:getMessage(contLang:getCode(),'short-list-separator')))
 	elseif not short_links and not showinline and #output&gt;0 then
 		outtext = table.concat(output,&quot;\n&quot;)
 	elseif not short_links and showinline and #output&gt;0 then
 		outtext = table.concat(output,conf:g('msg-inline-separator'))
 	end
 	if not hasdatafromwikidata then
 		category[#category+1] = cmodule:getMessage(contLang:getCode(), 'no-data-cat')
 		if not hasdatafromlocal and not short_links then
 			outtext = cmodule:getMessage(contLang:getCode(), 'no-data-text')
 		end
 	end
 	if not haswikidatalink then
 		category[#category+1] = cmodule:getMessage(contLang:getCode(), 'no-wikilink-cat')
 		if not hasdatafromlocal and not short_links then
 			outtext = cmodule:getMessage(contLang:getCode(), 'no-wikilink')
 		end
 	end
 	local nocategory = getArgument(frame, conf:a('arg-no-categories'))
 	category = #category&gt;0 and &quot;\n&quot; .. table.concat(category,&quot;\n&quot;) or &quot;&quot;
 	--mw.log(&quot;nocategory=&quot;..dump(nocategory)..&quot; and outtext=&quot;..dump(outtext)..&quot; and category=&quot;..dump(category))
 	outtext = outtext .. (nocategory and '' or category)
 	return outtext
 end
 
 function p.getLanguageCode(frame)
 	local prop = getArgument(frame, conf:a('arg-properties'))
 	local output = getLanguageData(prop)
 	return table.concat(output, conf:a('mod-filter-separator'))
 end