From OpenStreetMap Wiki
Jump to navigation Jump to search
[Edit] [Purge] Documentation

This module provides utilities for working with languages and language names. Most notably, it powers {{Languages}}, the language bar at the top of almost every page of this wiki.


Returns the title of the source page corresponding to the given page name. If the page name is not specified as the first argument, then the current page name is used. In a set of translated pages that covers the same topic, the source page is the page written in the wiki's default content language, English.

A number of testcases verify the correctness of this function. Run the unit tests.


Returns a flat list of links to translations of the current page, in wikitext format. The source page's name is inferred from the current page's name, but a page name can be specified as the first argument to override this behavior. The link order and labeling are defined by Module:Languages/config. This function is used by {{Languages}} and replaces {{Languages/div}}.


|1 =
(Optional) The name of the source page (the corresponding page in English). By default, the source page is inferred from the current page title, so this parameter is optional as long as the current page title is based on the English page's title.
|defaultsort =
(Optional) Set this parameter to no to omit the DEFAULTSORT that would be added by default. It is normally unnecessary to set this parameter.

This function generally does not check whether a translation exists; it merely links to all the pages that would contain translations in the wiki's languages. Outside of the content namespaces, for any language that has its own language namespace, this function does check whether there exists a page in the form "Namespace:XY:Page name" or "Namespace:Xy:Page name", because both spellings are possible.

MediaWiki:Common.css and MediaWiki:Mobile.css hide redlinks by default; clicking the "Other languages" link unhides them so that the user can quickly begin translating.

If the page title begins with a language pseudonamespace, such as "El" for Greek, a DEFAULTSORT omitting the pseudonamespace is appended to the return value of this method. Set |defaultsort = no to disable this behavior.

If a translation is missing in any language that has its own language namespace, this function automatically adds the current page to the corresponding tracking category. The current page's sort key in the category consists of the current page's language code and title.

See also

local p = {}
local config = mw.loadData("Module:Languages/config")
local siteLanguage = mw.getContentLanguage()

--- Returns the language pseudonamespace in the title of a page in the main
--- namespace, or nil if the title contains no pseudonamespace.
function p.pseudoNamespaceFromTitle(title)
	local pseudoNS = title.text:match("^(%w%w%w?):") or
	-- A few pseudonamespaces indicate topics rather than languages.
	if pseudoNS and config.languageNamesByCode[pseudoNS:lower()] then
		return pseudoNS

--- Infers and returns the given title’s page language, defaulting to the wiki’s
--- content language.
function p.languageFromTitle(title, fallback)
	-- Language-specific namespace
	local ns = title.subjectNsText
	if config.namespacesByLanguage[ns:lower()] then
		return ns:lower()
	-- Pseudonamespace in the main namespace
	local pseudoNS = p.pseudoNamespaceFromTitle(title)
	return pseudoNS and pseudoNS:lower() or fallback or siteLanguage:getCode()

--- Guesses the source title from the given title, which may be the source or a
--- translation.
function p.sourceTitleFromTitle(title)
	local pseudoNS = p.pseudoNamespaceFromTitle(title)
	if pseudoNS then
		local sourcePageName = title.text:sub(#pseudoNS + 2)
		return, title.nsText)
	elseif config.namespacesByLanguage[title.subjectNsText:lower()] then
		return, title.isTalkPage and 1 or 0)
		return title

--- Quickly uppercases the first character of the string, disregarding Unicode.
local function ucfirst(s)
	return s:sub(1, 1):upper() .. s:sub(2, -1)

--- Returns the page name of a translation in the given language.
--- If simulateLangNS is true and the page lies in a non-content namespace, the
--- pseudonamespace is capitalized to mimic a dedicated language namespace.
function p.translationPageName(languageCode, sourceTitle, simulateLangNS)
	local isInMainNS = #sourceTitle.subjectNsText == 0
	local pageNameParts = {
	if isInMainNS and config.namespacesByLanguage[languageCode] then
		local ns = config.namespacesByLanguage[languageCode]
		if sourceTitle.isTalkPage then
			ns =[ns]
		if #ns > 0 then
			table.insert(pageNameParts, 1, ns)
		local pseudoNS = ucfirst(languageCode)
		local langNS = config.namespacesByLanguage[languageCode]
		if langNS and (simulateLangNS or #langNS == 0) then
			pseudoNS = langNS
		if #pseudoNS > 0 then
			table.insert(pageNameParts, 1, pseudoNS)
		if #sourceTitle.nsText > 0 then
			table.insert(pageNameParts, 1, sourceTitle.nsText)
	return table.concat(pageNameParts, ":")

--- Returns a link to a wiki page.
local function listItem(languageCode, pageName, label)
	local link = "<span dir=\"auto\" lang=\"" .. languageCode .. "\">[[:" .. pageName .. "|&nbsp;" .. label .. "&nbsp;]]</span>"
	-- By default, hlist inserts an interpunct as CSS generated content after
	-- each list item. [[MediaWiki:Common.css]] hides redlinks but not the
	-- interpuncts, which follow the redlinks visually but aren’t siblings.
	-- This module uses hlist-with-seps, so hlist-sep gets the interpunct
	-- instead of the list item. As a sibling of the redlink, hlist-sep gets
	-- hidden along with the redlink.
	local sep = mw.html.create("span")
	local li = mw.html.create("li")
	return tostring(li)
p.listItem = listItem

--- Returns an unordered list of links to each possible translation page.
function p.languageList(currentTitle, sourceTitle)
	local currentLanguage = p.languageFromTitle(currentTitle)
	local isInMainNS = #sourceTitle.subjectNsText == 0
	local listItems = {}
	for i, code in ipairs(config.languageCodes) do
		-- Link to the translation.
		local pageName
		if code == currentLanguage then
			-- Translations’ page names may themselves be translated, so force
			-- the current page to ensure that the link is boldfaced.
			pageName = currentTitle.fullText
			pageName = p.translationPageName(code, sourceTitle)
			-- Languages with their own namespaces either uppercase or titlecase
			-- pages in non-content namespaces.
			if not isInMainNS and config.namespacesByLanguage[code]
				and not, sourceTitle.nsText).exists then
				pageName = p.translationPageName(code, sourceTitle, true)
		local item = listItem(code, pageName, config.languageNamesByCode[code])
		-- Add the current page to a tracking category if a translation is
		-- unavailable in a language that has a dedicated namespace.
		if config.namespacesByLanguage[code] and
			not, sourceTitle.nsText).exists then
			local category = "Category:" ..
			local sortingKey = currentLanguage .. currentTitle.text
			item = item .. "[[" .. category .. "|" .. sortingKey .. "]]"
		table.insert(listItems, item)
	return table.concat(listItems, "\n")

--- Returns the current page’s language based on either the page’s title or its
--- content language (specified by the pagelang argument).
function p.currentPageLanguage(frame)
	local currentTitle = mw.title.getCurrentTitle()
	return p.languageFromTitle(currentTitle, frame.args.pagelang)

--- Guesses the source title from the given title, which may be the source or a
--- translation.
function p.sourceTitle(frame)
	local currentTitle = mw.title.getCurrentTitle()
	local sourcePageName = #(frame.args[1] or "") > 0 and frame.args[1]
	return sourcePageName and or

--- Returns a flat list of links to translations of the current page, in
--- wikitext format.
function p.languages(frame)
	local currentTitle = mw.title.getCurrentTitle()
	local sourceTitle = p.sourceTitle(frame)
	local languageList = p.languageList(currentTitle, sourceTitle)
	local hlist = mw.html.create("div")
	local wikitext = tostring(hlist)
	-- By default, sort the page in categories by the title sans pseudotitle.
	if frame.args.defaultsort ~= "no" then
		local pseudoNS = p.pseudoNamespaceFromTitle(currentTitle)
		if pseudoNS then
			local sortingKey = currentTitle.text:sub(#pseudoNS + 2)
			-- If another DEFAULTSORT appeared earlier on the page, this
			-- DEFAULTSORT has no effect. If a different DEFAULTSORT appears
			-- later, it needs to specify “noerror” to suppress the error about
			-- conflicting DEFAULTSORTs.
			local defaultSort = frame:callParserFunction {
				name = "DEFAULTSORT",
				args = {
			wikitext = wikitext .. defaultSort
	return wikitext

return p