Module:NationAndOccupation

Documentation for this module may be created at Module:NationAndOccupation/doc

--[[  
  __  __           _       _        _   _       _   _                _              _  ___                             _   _             
 |  \/  | ___   __| |_   _| | ___ _| \ | | __ _| |_(_) ___  _ __    / \   _ __   __| |/ _ \  ___ ___ _   _ _ __   __ _| |_(_) ___  _ __  
 | |\/| |/ _ \ / _` | | | | |/ _ (_)  \| |/ _` | __| |/ _ \| '_ \  / _ \ | '_ \ / _` | | | |/ __/ __| | | | '_ \ / _` | __| |/ _ \| '_ \ 
 | |  | | (_) | (_| | |_| | |  __/_| |\  | (_| | |_| | (_) | | | |/ ___ \| | | | (_| | |_| | (_| (__| |_| | |_) | (_| | |_| | (_) | | | |
 |_|  |_|\___/ \__,_|\__,_|_|\___(_)_| \_|\__,_|\__|_|\___/|_| |_/_/   \_\_| |_|\__,_|\___/ \___\___|\__,_| .__/ \__,_|\__|_|\___/|_| |_|

 
This module translates a person’s nationality and profession into user’s preferred language. 
The template takes care for the right word order: {{NationAndOccupation|m|FR|painter|poet}} 
gives “French painter and poet”, if the user’s preferred language is set to English, but 
“pintor y poeta francés”, if the language is set to Spanish. This is especially useful with 
the “Description” field of {{Creator}} templates.

]]

-- =======================================
-- === Dependencies ======================
-- =======================================
local core  = require("Module:core")
local conj  = require('Module:Linguistic').conj
local q2iso = require("Module:NationAndOccupation/nationalityLUT")
local n2iso = require("Module:NationAndOccupation/CountryAdjective2iso")

-- ==================================================
-- === Internal functions ===========================
-- ==================================================

-------------------------------------------------------------------------------
local function getBareLabel(id, userLang) 
-- code equivalent to require("Module:Wikidata label")._getLabel with Wikidata=- option
	local label, link
	-- build language fallback list
	local langList = mw.language.getFallbacksFor(userLang)
	table.insert(langList, 1, userLang)
	for _, lang in ipairs(langList) do  -- loop over language fallback list looking for label in the specific language
		label = mw.wikibase.getLabelByLang(id, lang)
		if label then break end                    -- label found and we are done
	end	
	return label or id
end

------------------------------------------------------------------------------
-- straight union of two arrays (tables)
local function union ( a, b )
    local result = {}
    for _,v in pairs ( a or {} ) do
        table.insert( result, v )
    end
    for _,v in pairs ( b or {} ) do
         table.insert( result, v )
    end
    return result
end

------------------------------------------------------------------------------
-- get female forms of occupation using " female form of label (P2521) " property
local function getFemaleLabel(item, lang)
	local label = {}
	for _, statement in pairs( mw.wikibase.getBestStatements( item, 'P2521' )) do
		local v = statement.mainsnak.datavalue.value
		if v then 
			label[v.language] = v.text
		end
	end
	if label then
		label = core.langSwitch(label,lang)
	end
	if not label then
		label = getBareLabel(item, lang)
	end
	return label
end

--[[
Implementation of Template:NationAndOccupation/default
INPUTS:
* nationality - array of string in the form compatible with Template:Nationality
* occupation  - array of already translated strings
* gender      - single gender string "male" or "female"
* lang        - users language
]]
local function assembleNaO(nationality, occupation, gender, lang)

	local styleLUT = { -- language dependent order
		-- Occupation then nationality order
		ca=10 , es=10, eu=10, fa=10, he=10, it=10, pt=10, ro=10, vi=10,
		-- Occupation then nationality order with first nationality in a special form
		fr=11, 
		-- Nationality then Occupation order
		cs=20 , da=20, el=20, en=20, eo=20, et=20, hu=20, mk=20, ml=20, nl=20, 
		-- Nationality then Occupation order, no space
		zh=21,
		-- Nationality then Occupation order with 1st nationality in a special form and 2nd nationality upper case
		nds=22, de=22 , 
		-- Nationality then Occupation order with 1st nationality in a special form and 2nd nationality lower case
		pl=23, ru=23, sl=23, bg=23}
    -- Use LangSwitch to choose the style based on the language. That way language fallback chain is used
	local style = core.langSwitch(styleLUT, lang) 
	 
	-- create nationality string
	gender = gender or 'male'
	local frame = mw.getCurrentFrame()
	local nStr=''
	if nationality and #nationality==1 then --Single nationality case
		nStr = frame:expandTemplate{ title='Nationality', args={nationality[1], gender, lang=lang} }
	elseif nationality and #nationality>1 then                 --Double nationality case
		local N2 = frame:expandTemplate{ title='Nationality', args={nationality[2], gender, lang=lang} }
		if style==11 or style==22 or style==23 then -- nationality in a special form
			gender = 's'
		end
		local N1 = frame:expandTemplate{ title='Nationality', args={nationality[1], gender, lang=lang} }
		if style==23 then
			N2 = mw.ustring.lower(N2)
		end
		nStr = N1 .. '-' .. N2
	end
	
	-- Create final string
	if occupation then
		local oStr = conj(occupation, lang, 'and')
		if style<20 then -- Type 1: Occupation then nationality order
			return oStr .. ' ' .. nStr
		elseif style==21 then -- Type 1: Nationality then Occupation order, no space
			return nStr .. oStr
		else             -- Type 2: Nationality then Occupation order
			return nStr .. ' ' .. oStr
		end
	else
		return nStr
	end
end

--[[
Implementation of Template:NationAndOccupation
INPUTS:
* entity - wikidata entity 
* lang   - users language
OUTPUTS:
* data   - data structure with data extracted from Wikidata, including fields:
	* nationality   - array of string in the form compatible with Template:Nationality
	* occupation    - array of already translated occupation strings
	* occupationEN  - array of occupation strings in english
	* gender        - single gender string "male" or "female"
]]
local function harvest_wikidata(entity, lang)
	local occupation, occupationEN, nationality, gender, data = {}, {}, {}, {}, {}
	
	-- if wikidata q-code is provided than look up few properties
	if entity then
		-- harvest  properties from wikidata
		local property = {P21='gender', P27='country', P106='occupation', P172='ethnicity'}
		for prop, field in pairs( property ) do
			if entity.claims and entity.claims[prop] then -- if we have wikidata item and item has the property
				-- capture multiple "best" Wikidata value
				data[field] = core.parseStatements(entity:getBestStatements( prop ), nil)
			end
		end
	end
	
	-- Look up gender
	if data.gender then	
		local LUT = { Q6581097='male', Q2449503='male', Q6581072='female', Q1052281='female' }
		gender = LUT[data.gender[1]]
	end
	if gender~='male' and gender~='female' then
		gender = 'male'
	end
	
	-- Look up occupation
	local occ
	for i, oItem in ipairs(data.occupation or {}) do
		if i>6 then
			break -- only 6 occupations are allowed
		end
		local occEN = mw.wikibase.getLabelByLang(oItem, 'en')
		if gender == 'female' then -- get localized (translated) occupation labels in female form
			occ = getFemaleLabel(oItem, lang) 
		elseif lang=='en' then     -- get English occupation labels in male form
			occ = occEN
		else                       -- get localized (translated) occupation labels in male form
			occ = getBareLabel(oItem, lang)
		end
		table.insert(occupation  , occ) 
		table.insert(occupationEN, occEN)
	end
	
	-- Look up nationality
	if data.country or data.ethnicity then -- from wikidata
		-- process P27/country and P172/ethnicity
		local nTable = {} -- table of unique nationality iso codes stored as keys
		for _, v in ipairs( union(data.country, data.ethnicity) ) do
			for iso in mw.text.gsplit( q2iso[v] or '', '/', true ) do
				nTable[ iso ] = 1
			end
		end
		for nat, _ in pairs(nTable) do
			table.insert(nationality, nat)
		end
	end
	data = {nationality=nationality, occupation=occupation, gender=gender, occupationEN=occupationEN}
	return data
end

-- ==================================================
-- === External functions ===========================
-- ==================================================
local p = {}

-- ===========================================================================
-- === Version of the function to be called from other LUA codes
-- ===========================================================================

--[[
Implementation of Template:NationAndOccupation
INPUTS:
* args.nationality - '/' separated string with substrings in the form compatible 
                     with Template:Nationality
* args.occupation  - '/' separated string with substrings with english names of 
                     occupations compatible with Template:Occupations
* args.gender      - single gender string "male" or "female"
* args.wikidata    - wikidata q-code
* args.lang        - users language
OUTPUTS:
* OutStr - string with transpaced phrase like "english writer"
* args   - data structure with processed inputs
* data   - data structure with data extracted from Wikidata
]]
function p._NationAndOccupation(args0)
	local occupation, nationality, entity, occupationEN
	
	-- if wikidata q-code is provided than look up few properties
	local q = args0.wikidata
	if q and type(q)=='string' and string.sub(q,1,1)=="Q"  then --  
		entity = mw.wikibase.getEntity(q)
	elseif q then
		entity = q
	end
	local data   = harvest_wikidata(entity, args0.lang)
	local gender = args0.gender or data.gender
	
	-- Look up occupation
	if args0.occupation then -- from input arguments
		local frame = mw.getCurrentFrame()
		local occArray = mw.text.split(args0.occupation, '/')
		occupation = {}
		for i = 1,6 do 
			if occArray[i] and occArray[i]~='' then 
				local args={occArray[i], gender, lang=args0.lang}
				table.insert(occupation, frame:expandTemplate{ title='Occupation', args=args })
			end
		end
		if #occupation==0 then
			occupation = nil
		end
	end
	
	-- Look up nationality
	if args0.nationality then -- from input arguments
		nationality = mw.text.split(args0.nationality, '/')
		for i = 1,2 do -- if nationality is a word than see if we can find iso code
			local N = string.lower(nationality[i] or '')
			if #N>2 and n2iso[N] then 
				nationality[i] = n2iso[N]
			end
		end
		if #nationality==0 then
			nationality = nil
		end
	end
	local outStr = assembleNaO(nationality or data.nationality, occupation or data.occupation, gender, args0.lang)
	local args = {nationality=nationality, occupation=occupation, gender=args0.gender, occupationEN=occupationEN}
    --outStr = outStr .. '\n' .. mw.text.jsonEncode(data) .. '\n' .. mw.text.jsonEncode(args)
	return outStr, args, data
end

-- ===========================================================================
-- === Version of the functions to be called from template namespace
-- ===========================================================================

--[[
NationAndOccupation
 
This function is the core part of the NationAndOccupation template. 
 
Usage:
{{#invoke:}}
 
 Parameters:
  *nationality - '/' separated string with substrings in the form compatible 
                     with Template:Nationality
  * occupation  - '/' separated string with substrings with english names of 
                     occupations compatible with Template:Occupations
  * gender      - single gender string "male" or "female"
  * wikidata    - wikidata q-code
  * lang        - users language
 Error Handling:

]]
function p.NationAndOccupation(frame)
	local args0 = core.getArgs(frame)
	local outStr, args, data = p._NationAndOccupation(args0)
	return outStr
end

return p