Open main menu

Module:script utilities

This module provides access to Module:scripts from templates, so that they can make use of the information stored there. It also provides a number of functions that can be used by other modules.

Data is found in Module:script utilities/data.

Exported functions

lang_t

{{#invoke:script utilities|lang_t}}

This is used by {{lang}} to wrap portions of text in a language tag. See there for more information.

tag_text

tag_text(text, lang, sc, face, class)

Wraps the given text in HTML tags with appropriate CSS classes (see WT:CSS) for the language and script. This is required for all non-English text on Wiktionary.

The actual tags and CSS classes that are added are determined by the face parameter. It can be one of the following:

"term"
The text is wrapped in <i class="(sc) mention" lang="(lang)">...</i>.
"head"
The text is wrapped in <strong class="(sc) headword" lang="(lang)">...</strong>.
"hypothetical"
The text is wrapped in <span class="hypothetical-star">*</span><i class="(sc) hypothetical" lang="(lang)">...</i>.
"bold"
The text is wrapped in <b class="(sc)" lang="(lang)">...</b>.
nil
The text is wrapped in <span class="(sc)" lang="(lang)">...</span>.

The optional class parameter can be used to specify an additional CSS class to be added to the tag.

tag_translit

tag_translit(translit, lang, kind, attributes)

Tags the transliteration for given text translit and language lang. It will add the language, script subtag (as defined in BCP 47 2.2.3) and dir (directional) attributes as needed.

The optional kind parameter can be one of the following:

"term"
tag transliteration for {{mention}}
"usex"
tag transliteration for {{usex}}
"head"
tag transliteration for {{head}}
"default"
default

The optional attributes parameter is used to specify additional HTML attributes for the tag.

request_script

request_script(lang, sc)

Generates a request to provide a term in its native script, if it is missing. This is used by the {{rfscript}} template as well as by the functions in Module:links.

The function will add entries to one of the subcategories of Category:Terms needing native script by language, and do several checks on the given language and script. In particular:

  • If the script was given, a subcategory named "needing (script) script" is added, but only if the language has more than one script. Otherwise, the main "needing native script" category is used.
  • Nothing is added at all if the language has no scripts other than Latin and its varieties.

template_rfscript

{{#invoke:script utilities|template_rfscript}}

This is used by {{rfscript}}. See there for more information.

See also


local export = {}

--[=[
	Modules used:
	[[Module:script utilities/data]]
	[[Module:scripts]]
	[[Module:languages]]
	[[Module:parameters]]
	[[Module:utilities]]
	[[Module:debug]]
]=]

function export.is_Latin_script(sc)
	-- Latn, Latf, Latinx, nv-Latn, pjt-Latn
	return sc:getCode():find("Lat") and true or false
end

-- Used by [[Template:lang]]
function export.lang_t(frame)
	params = {
		[1] = {},
		[2] = { allow_empty = true, default = "" },
		["sc"] = {},
		["face"] = {},
		["class"] = {},
	}
	
	local args = require("Module:parameters").process(frame:getParent().args, params)
	local NAMESPACE = mw.title.getCurrentTitle().nsText
	
	local lang = args[1] or (NAMESPACE == "Template" and "und") or error("Language code has not been specified. Please pass parameter 1 to the template.")
	lang = require("Module:languages").getByCode(lang) or require("Module:languages").err(lang, 1)
	
	local text = args[2]
	
	local sc = args["sc"]
	sc = (sc and (require("Module:scripts").getByCode(sc) or error("The script code \"" .. sc .. "\" is not valid.")) or nil)
	
	local face = args["face"]
	
	return export.tag_text(text, lang, sc, face, class)
end

-- Ustring turns on the codepoint-aware string matching. The basic string function
-- should be used for simple sequences of characters, Ustring function for
-- sets – [].
local function trackPattern(text, pattern, tracking, ustring)
	local find = ustring and mw.ustring.find or string.find
	if pattern and find(text, pattern) then
		require("Module:debug").track("script/" .. tracking)
	end
end

local function track(text, lang, sc)
	local U = mw.ustring.char
	
	if lang and text then
		local langCode = lang:getCode()
		
		-- [[Special:WhatLinksHere/Template:tracking/script/ang/acute]]
		if langCode == "ang" then
			local decomposed = mw.ustring.toNFD(text)
			local acute = U(0x301)
			
			trackPattern(decomposed, acute, "ang/acute")
		
		--[=[
		[[Special:WhatLinksHere/Template:tracking/script/Greek/wrong-phi]]
		[[Special:WhatLinksHere/Template:tracking/script/Greek/wrong-theta]]
		[[Special:WhatLinksHere/Template:tracking/script/Greek/wrong-kappa]]
		[[Special:WhatLinksHere/Template:tracking/script/Greek/wrong-rho]]
			ϑ, ϰ, ϱ, ϕ should generally be replaced with θ, κ, ρ, φ.
		]=]
		elseif langCode == "el" or langCode == "grc" then
			trackPattern(text, "ϑ", "Greek/wrong-theta")
			trackPattern(text, "ϰ", "Greek/wrong-kappa")
			trackPattern(text, "ϱ", "Greek/wrong-rho")
			trackPattern(text, "ϕ", "Greek/wrong-phi")
		
			--[=[
			[[Special:WhatLinksHere/Template:tracking/script/Ancient Greek/spacing-coronis]]
			[[Special:WhatLinksHere/Template:tracking/script/Ancient Greek/spacing-smooth-breathing]]
			[[Special:WhatLinksHere/Template:tracking/script/Ancient Greek/wrong-apostrophe]]
				When spacing coronis and spacing smooth breathing are used as apostrophes, 
				they should be replaced with right single quotation marks (’).
			]=]
			if langCode == "grc" then
				trackPattern(text, U(0x1FBD), "Ancient Greek/spacing-coronis")
				trackPattern(text, U(0x1FBF), "Ancient Greek/spacing-smooth-breathing")
				trackPattern(text, "[" .. U(0x1FBD) .. U(0x1FBF) .. "]", "Ancient Greek/wrong-apostrophe", true)
			end
		
		-- [[Special:WhatLinksHere/Template:tracking/script/Russian/grave-accent]]
		elseif langCode == "ru" then
			local decomposed = mw.ustring.toNFD(text)
			
			trackPattern(decomposed, U(0x300), "Russian/grave-accent")
		
		-- [[Special:WhatLinksHere/Template:tracking/script/Tibetan/trailing-punctuation]]
		elseif langCode == "bo" then
			trackPattern(text, "[་།]$", "Tibetan/trailing-punctuation", true)
			trackPattern(text, "[་།]%]%]$", "Tibetan/trailing-punctuation", true)

		--[=[
		[[Special:WhatLinksHere/Template:tracking/script/Thai/broken-ae]]
		[[Special:WhatLinksHere/Template:tracking/script/Thai/broken-am]]
		[[Special:WhatLinksHere/Template:tracking/script/Thai/wrong-rue-lue]]
		]=]
		elseif langCode == "th" then
			trackPattern(text, "เ".."เ", "Thai/broken-ae")
			trackPattern(text, "ํ[่้๊๋]?า", "Thai/broken-am", true)
			trackPattern(text, "[ฤฦ]า", "Thai/wrong-rue-lue", true)

		--[=[
		[[Special:WhatLinksHere/Template:tracking/script/Lao/broken-ae]]
		[[Special:WhatLinksHere/Template:tracking/script/Lao/broken-am]]
		]=]
		elseif langCode == "lo" then
			trackPattern(text, "ເ".."ເ", "Lao/broken-ae")
			trackPattern(text, "ໍ[່້໊໋]?າ", "Lao/broken-am", true)
		end
	end
end

-- Wrap text in the appropriate HTML tags with language and script class.
function export.tag_text(text, lang, sc, face, class, id)
	if not sc then
		sc = require("Module:scripts").findBestScript(text, lang)
	end
	
	track(text, lang, sc)
		
	-- Replace space characters with newlines in Mongolian-script text, which is written top-to-bottom.
	if sc and sc:getDirection() == "down" then
		--[[	First, escape targets of wikilinks and HTML tags,
				which should not have their spaces
				replaced with line breaks. ]]
		local escaped = {}
		local i = 0
		local escape_format = "$%d"
		
		local function escape(text, pattern, prefix)
			return text:gsub(
				pattern,
				function(item)
					i = i + 1
					escaped[i] = item
					return (prefix or "") .. escape_format:format(i)
				end)
		end
		
		text = escape(text, "%[%[([^|]+|)", "[[")
		text = escape(text, "<[^>]+>")
		
		text = text:gsub(" +", "<br>")
		
		-- Unescape whatever was escaped.
		text = text:gsub(
			"$(%d)",
			function(a)
				a = tonumber(a)
				return escaped[a]
			end
		)
	end

	if sc:getCode() == "Imag" then
		face = nil
	end

	local function class_attr(classes)
		table.insert(classes, 1, sc:getCode())
		if class and class ~= '' then
			table.insert(classes, class)
		end
		return 'class="' .. table.concat(classes, ' ') .. '"'
	end
	
	local function tag_attr(...)
		local output = {}
		if id then
			table.insert(output, 'id="' .. require("Module:utilities").make_id(lang, id) .. '"')
		end
		
		table.insert(output, class_attr({...}) )
		
		if lang then
			table.insert(output, 'lang="' .. lang:getCode() .. '"')
		end
		
		return table.concat(output, " ")
	end
	
	if face == "hypothetical" then
	-- [[Special:WhatLinksHere/Template:tracking/script-utilities/face/hypothetical]]
		require("Module:debug").track("script-utilities/face/hypothetical")
	end
	
	local data = mw.loadData("Module:script utilities/data").faces[face or "nil"]
	
	if sc:getDirection() == "rtl" then
		text = mw.ustring.gsub(text, "%p$", "%0&lrm;")
	end
	
	local post = ""
	if face == "translation" and sc:getDirection() == "rtl" then
		post = "&lrm;"
	end
	
	-- Add a script wrapper
	if data then
		return ( data.prefix or "" ) .. '<' .. data.tag .. ' ' .. tag_attr(data.class) .. '>' .. text .. '</' .. data.tag .. '>' .. post
	else
		error('Invalid script face "' .. face .. '".')
	end
end

function export.tag_translit(translit, lang, kind, attributes)
	if type(lang) == "table" then
		lang = lang.getCode and lang:getCode()
			or error("Third argument to tag_translit should be a language code or language object.")
	end
	
	local data = mw.loadData("Module:script utilities/data").translit[kind or "default"]
	
	local opening_tag = {}
	
	table.insert(opening_tag, data.tag)
	if lang == "ja" then
		table.insert(opening_tag, 'class="' .. (data.classes and data.classes .. " " or "") .. 'tr"')
	else
		table.insert(opening_tag, 'lang="' .. lang .. '-Latn"')
		table.insert(opening_tag, 'class="' .. (data.classes and data.classes .. " " or "") .. 'tr Latn"')
	end
	
	if data.dir then
		table.insert(opening_tag, 'dir="' .. data.dir .. '"')
	end
	
	table.insert(opening_tag, attributes)
	
	return "<" .. table.concat(opening_tag, " ") .. ">" .. translit .. "</" .. data.tag .. ">"
end

function export.tag_transcription(transcription, lang, kind, attributes)
	if type(lang) == "table" then
		lang = lang.getCode and lang:getCode()
			or error("Third argument to tag_translit should be a language code or language object.")
	end
	
	local data = mw.loadData("Module:script utilities/data").transcription[kind or "default"]
	
	local opening_tag = {}
	
	table.insert(opening_tag, data.tag)
	if lang == "ja" then
		table.insert(opening_tag, 'class="' .. (data.classes and data.classes .. " " or "") .. 'ts"')
	else
		table.insert(opening_tag, 'lang="' .. lang .. '-Latn"')
		table.insert(opening_tag, 'class="' .. (data.classes and data.classes .. " " or "") .. 'ts Latn"')
	end
	
	if data.dir then
		table.insert(opening_tag, 'dir="' .. data.dir .. '"')
	end
	
	table.insert(opening_tag, attributes)
	
	return "<" .. table.concat(opening_tag, " ") .. ">" .. transcription .. "</" .. data.tag .. ">"	
end

-- Add a notice to request the native script of a word
function export.request_script(lang, sc)
	local scripts = lang.getScripts and lang:getScripts() or error('The language "' .. lang:getCode() .. '" does not have the method getScripts. It may be unwritten.')
	
	-- By default, request for "native" script
	local cat_script = "native"
	local disp_script = "script"
	
	-- If the script was not specified, and the language has only one script, use that.
	if not sc and #scripts == 1 then
		sc = scripts[1]
	end
	
	-- Is the script known?
	if sc then
		-- If the script is Latin, return nothing.
		if export.is_Latin_script(sc) then
			return ""
		end
		
		if sc:getCode() ~= scripts[1]:getCode() then
			disp_script = sc:getCanonicalName()
		end
		
		-- The category needs to be specific to script only if there is chance
		-- of ambiguity. This occurs when lang=und, or when the language has
		-- multiple scripts.
		if lang:getCode() == "und" or scripts[2] then
			cat_script = sc:getCanonicalName()
		end
	else
		-- The script is not known.
		-- Does the language have at least one non-Latin script in its list?
		local has_nonlatin = false
		
		for i, val in ipairs(scripts) do
			if not export.is_Latin_script(val) then
				has_nonlatin = true
				break
			end
		end
		
		-- If there are non-Latin scripts, return nothing.
		if not has_nonlatin then
			return ""
		end
	end
	
	local category = ""
	
	if mw.title.getCurrentTitle().nsText ~= "Template" then
		category = "[[Category:" .. lang:getCanonicalName() .. " terms needing " .. cat_script .. " script]]"
	end
	
	return "<small>[" .. disp_script .. " needed]</small>" .. category
end

function export.template_rfscript(frame)
	local args = frame.args
	local lang = args[1] or error("The first parameter (language code) has not been given")
	local sc = args["sc"]; if sc == "" then sc = nil end
	lang = require("Module:languages").getByCode(lang) or error("The language code \"" .. lang .. "\" is not valid.")
	sc = (sc and (require("Module:scripts").getByCode(sc) or error("The script code \"" .. sc .. "\" is not valid.")) or nil)
	
	local ret = export.request_script(lang, sc)
	
	if ret == "" then
		error("This language is written in the Latin alphabet. It does not need a native script.")
	else
		return ret
	end
end

function export.checkScript(text, scriptCode, result)
	local scriptObject = require("Module:scripts").getByCode(scriptCode)
	
	if not scriptObject then
		error('The script code "' .. scriptCode .. '" is not recognized.')
	end
	
	local originalText = text
	
	-- Remove non-letter characters.
	text = mw.ustring.gsub(text, "[%A]", "")
	
	-- Remove all characters of the script in question.
	text = mw.ustring.gsub(text, "[" .. scriptObject:getCharacters() .. "]", "")
	
	if text ~= "" then
		if type(result) == "string" then
			error(result)
		else
			error('The text "' .. originalText .. '" contains the letters "' .. text .. '" that do not belong to the ' .. scriptObject:getCategoryName() .. '.', 2)
		end
	end
end

return export