Модуль:Languages
ТӀера куц
Этот модуль относится к критическим. У него очень много включений или он используется с подстановкой. Из-за опасности вандализма или ошибочного редактирования он был защищён.Категория:Модули:Критические |
Этот модуль используется в шаблоне {{l6e}} при создании собственно языковых ссылок путём конструкции {{#invoke:Languages|list|множество языков}}. За более подробной документацией насчёт работы шаблона следует идти в его документацию.
Также модуль используется для различных преобразований кодов языков.
Тесты
[тоаде чура]4 tests failed.
test_getRefHtml:
| Text | Expected | Actual | |
|---|---|---|---|
| {{#invoke:Languages | getRefHtmlFrame | }} | Ошибка скрипта: Функции «getRefHtmlFrame» не существует. | ||
| {{#invoke:Languages | getRefHtmlFrame | QNNN }} | Ошибка скрипта: Функции «getRefHtmlFrame» не существует. | ||
| {{#invoke:Languages | getRefHtmlFrame | Q1860 }} | (англ.) | Ошибка скрипта: Функции «getRefHtmlFrame» не существует. | |
| {{#invoke:Languages | getRefHtmlFrame | Q7737 }} | (рус.) | Ошибка скрипта: Функции «getRefHtmlFrame» не существует. |
-- Модуль для работы с языками ISO 639
-- загрузка модуля данных с таблицей языков
-- Module:Languages/data. The expected layout of the record is:
-- langData[1] → abbreviation (e.g. 'англ.')
-- langData[2] → article title in Russian (e.g. 'Английский язык')
-- langData[3] → prepositional form (optional)
-- langData[4] → genitive form (optional)
local languages = mw.loadData('Module:Languages/data')
local languagesEn = mw.loadData('Module:Languages/data-en')
local wikidataLanguageCodes = mw.loadData('Module:Wikidata/Language-codes')
local getArgs = require('Module:Arguments').getArgs
local p = {}
-- forward declaration so helpers above can call it
local transformLangCase
-- константы
local CATEGORY_UNKNOWN_LANG =
'[[Category:Википедия:Статьи с нераспознанным языком]]'
local CATEGORY_UNKNOWN_LANG_REF =
'[[Category:Википедия:Статьи с нераспознанным языком (ref)]]'
local LOG_NO_LANG_DATA = 'Language description for code %s not found'
local DEFAULT_REF_CLASS = 'ref-info'
local TITLE_PREFIX = 'на '
-- Returns true when the string is either nil or an empty string.
-- This helper is used to guard early-exit branches and avoid
-- calling mw.ustring functions on invalid values.
local function isEmpty(s)
return s == nil or s == ''
end
-- Returns bracket characters based on variant type.
-- Parameters:
-- variant - 'square' for square brackets, 'none' for no brackets, otherwise round brackets
-- Returns:
-- openB, closeB - opening and closing bracket characters
local function getBrackets(variant)
if variant == 'square' then
return '[', ']'
elseif variant == 'none' then
return '', ''
else
return '(', ')'
end
end
-- Generates HTML error message for unknown language codes or Wikidata IDs.
-- Parameters:
-- type - 'lang' for language codes, 'wikidata' for Wikidata IDs
-- code - the unknown code/ID to display in error
local function generateError(type, code)
if type == 'wikidata' then
return '<span class="error">Неизвестный Wikidata ID: ' .. mw.text.nowiki(code) .. '</span>'
else
return '<span class="error">Неизвестный языковой код: '
.. mw.text.nowiki(code)
.. '. Обратитесь на [[Обсуждение модуля:Languages|специальную страницу]] для добавления данного кода.</span>'
end
end
-- Builds a wiki link from a language record coming from
-- Returns string like '[[Английский язык|англ.]]'.
local function buildLanguageLink(langData)
if not langData then
return ''
end
return '[[' .. langData[2] .. '|' .. langData[1] .. ']]'
end
-- Builds a HTML span used as an inline language reference.
-- Parameters:
-- text — abbreviation to show inside brackets.
-- title — tooltip text (full wording in the target case).
-- variant — 'square' to use [ ... ], otherwise round ( ... ).
-- includeNbsp — when false no leading NBSP is added; otherwise NBSP.
-- Returns a string: ' <span class="ref-info" title="...">(англ.)</span>'
local function buildRefSpan(text, title, variant, includeNbsp, className)
local openB, closeB = getBrackets(variant)
local prefix = (includeNbsp == false) and '' or ' '
local encodedClassName = mw.text.encode(className or DEFAULT_REF_CLASS)
local encodedTitle = mw.text.encode(title or '')
return prefix
.. '<span class="'
.. encodedClassName
.. '" title="'
.. TITLE_PREFIX
.. encodedTitle
.. '" style="cursor:help;">'
.. openB
.. (text or '')
.. closeB
.. '</span>'
end
-- A thin wrapper around Module:Arguments.getArgs with the
-- options we use consistently across entry points:
-- trim = true — trims whitespace
-- removeBlanks = false — preserves explicit empty parameters
local function getInvokeArgs(frame)
return getArgs(frame, { trim = true, removeBlanks = false, frameOnly = true })
end
-- Looks up a language record by code in Module:Languages/data.
-- Normalizes case to lowercase. Returns two values:
-- 1) record table or nil when code is unknown
-- 2) normalized code (lowercase)
local function getLanguageData(code)
if isEmpty(code) then
return nil
end
local normalized_code = mw.ustring.lower(code)
local record = languages[normalized_code]
return record, normalized_code
end
-- Resolves language record and normalized code with unified error handling.
-- Returns:
-- l, normalized when code exists in data
-- nil, nil, errorOrEmptyString when code is empty/unknown ('' or error html)
local function resolveLanguage(code, showError)
if isEmpty(code) then
return nil, nil, ''
end
local l, normalized = getLanguageData(code)
if l then
return l, normalized
end
return nil, nil, (showError and generateError('lang', code) or '')
end
-- Given a language code, returns:
-- • rendered link to the language article using its abbreviation, and
-- • normalized code to be used in the 'lang' attribute.
-- If the code is unknown but non-empty, returns the raw code and empty lang.
-- If the input is empty, returns two empty strings.
local function buildLanguageLinkForCode(code)
local l, normalized_code = resolveLanguage(code, false)
if l then
return buildLanguageLink(l), (normalized_code or '')
elseif not isEmpty(code) then
return code, ''
else
return '', ''
end
end
-- Renders a ref span for a known ISO 639 code. Returns nil when code
-- is unknown so caller can decide on a fallback strategy.
local function buildRefForCode(code, variant, includeNbsp, className, mode)
local l, normalized = resolveLanguage(code, false)
if not l then
return nil
end
local text = (mode == 'iso') and normalized or l[1]
return buildRefSpan(
text,
transformLangCase(normalized or code, 'prepositional'),
variant,
includeNbsp,
className
)
end
-- Fallback renderer for unknown codes via Template:ref-<code>, with
-- categorization. Uses 'und' when a specific template doesn't exist.
local function renderUnknownRef(code, showError, refVariant, customClass, includeNbsp)
if showError then
return generateError('lang', code)
end
-- Generate HTML for unknown language reference
local openB, closeB = getBrackets(refVariant)
local className = customClass or DEFAULT_REF_CLASS
local title = TITLE_PREFIX .. 'неопределённом языке'
local text = 'неопр.'
local nbsp = includeNbsp and ' ' or ''
local encodedClassName = mw.text.encode(className)
local encodedTitle = mw.text.encode(title)
return nbsp .. '<small class="' .. encodedClassName .. '" style="cursor:help;" title="' .. encodedTitle .. '">' .. openB .. text .. closeB .. '</small>' .. CATEGORY_UNKNOWN_LANG_REF
end
-- Internal implementation of the language-name case transformation.
-- Computes either prepositional ('на английском языке') or
-- genitive ('английского языка') forms. If specific forms are
-- provided in Module:Languages/data via l[3]/l[4], they win.
-- Otherwise, applies a set of heuristics to generate the forms.
local function _transformLangCaseImpl(code, case)
-- Resolve the language record; unknown code gets a readable placeholder
local l = getLanguageData(code)
if not l then
return '<неизвестный код ' .. code .. '>'
end
-- Prefer explicit forms from data when present:
-- l[3] = prepositional, l[4] = genitive
if case == 'prepositional' and l[3] then
return l[3]
end
if case == 'genitive' and l[4] then
return l[4]
end
-- Derive a base noun phrase:
-- 1) take explicit Russian title if present (l[2]),
-- otherwise fetch the language name via mw.language
-- 2) lowercase everything for stable pattern processing
-- 3) strip the word "язык" and optional parenthetical "(язык)"
local ln = mw.ustring
.lower(l[2] or mw.language.fetchLanguageName(code, 'ru'))
:gsub('%s+язык%s+', ' ')
:gsub('%s*%(?язык%)?%s*', '')
if not ln then
-- Final fallback string when even a base name is unavailable
local fallback = (
case == 'prepositional' and ('языке с ISO-кодом ' .. code .. ' (?)')
or ('языка с ISO-кодом ' .. code .. ' (?)')
)
return fallback
end
-- Special-case: names ending with "лингва" (e.g., "линга франка")
-- only need a final vowel change: а → е/ы depending on target case
if ln:match('.*лингва$') then
local r = ln:gsub('а$', (case == 'prepositional' and 'е' or 'ы'))
return r
end
-- Adjectival language names ("...ский", "...цкий", "...ный"),
-- optionally followed by a parenthetical qualifier, require
-- morphological agreement. The rules below implement common
-- Russian inflection patterns:
-- - "нЫЙ/нИЙ" → "нОМ/нЕМ" (prep.) or "нОГО/нЕГО" (gen.)
-- - "СКИЙ/ЦКИЙ" → "СКОМ/ЦКОМ" (prep.) or "СКОГО/ЦКОГО" (gen.)
if
mw.ustring.match(ln, '[сц]кий$')
or ln:match('ный$')
or mw.ustring.match(ln, '[сц]кий%s%b()$')
or ln:match('ный%s%b()$')
then
ln = mw.ustring
.gsub(
mw.ustring
.gsub(ln, 'н([ыи])й(%A)', function(y, s)
-- "нЫЙ/нИЙ" endings: pick target case and keep following separator
return (
case == 'prepositional' and (y == 'ы' and 'ном' or 'нем')
or (y == 'ы' and 'ного' or 'него')
) .. s
end)
:gsub('ный$', (case == 'prepositional' and 'ном' or 'ного')),
'([сц]к)ий(%A)',
(case == 'prepositional' and '%1ом%2' or '%1ого%2')
)
:gsub('ский$', (case == 'prepositional' and 'ском' or 'ского'))
:gsub('цкий$', (case == 'prepositional' and 'цком' or 'цкого'))
-- If the name ends with a qualifier in parentheses and the case ending
-- did not land immediately before the closing paren, append
-- "языке/языка" before the qualifier to keep a natural phrasing.
local hasClosingParen = ln:match('%)$') ~= nil
local endsWithCase1 = ln:match(case == 'prepositional' and 'ом%)$' or 'ого%)$') ~= nil
local endsWithCase2 = ln:match(case == 'prepositional' and 'нем%)$' or 'него%)$')
~= nil
if hasClosingParen and not (endsWithCase1 or endsWithCase2) then
local r, s = ln:gsub(
'(%s)(%b())$',
(case == 'prepositional' and '%1языке%1%2' or '%1языка%1%2')
)
if s == 1 then
return r
end
end
-- Default phrasing for adjectival names
local result = (case == 'prepositional' and (ln .. ' языке') or (ln .. ' языка'))
return result
else
-- Non‑adjectival names (base nouns): prepend "языке/языка"
local result = (case == 'prepositional' and ('языке ' .. ln) or ('языка ' .. ln))
return result
end
end
-- Public wrapper kept for backwards compatibility with existing
-- callers in this module and other modules. Do not move above
-- function definitions to avoid upvalue shadowing.
function transformLangCase(code, case)
return _transformLangCaseImpl(code, case)
end
-- export for other modules that `require("Module:Languages").transformLangCase`
p.transformLangCase = transformLangCase
-- P U B L I C E N T R Y P O I N T S
-- Entry point: builds a ref span by Wikidata item id coming from #invoke.
-- Delegates to p.getWikidataRefHtml after parsing the argument.
function p.getWikidataRefHtmlFrame(frame)
local args = getInvokeArgs(frame)
local showError = args['error'] and true or false
return p.getWikidataRefHtml(args[1] or '', showError)
end
-- Builds a ref span from a Wikidata item id of a language.
-- Uses a preloaded mapping Module:Wikidata/Language-codes to
-- resolve item → ISO 639 code, then renders the standard ref span.
function p.getWikidataRefHtml(wikidataItemId, showError)
local code = wikidataLanguageCodes[wikidataItemId]
if code == nil then
if showError then
return generateError('wikidata', wikidataItemId)
end
mw.log('Language code not found for ' .. wikidataItemId)
return ''
end
local rendered = buildRefForCode(code, nil, false, nil, '')
if not rendered then
if showError then
return generateError('lang', code)
end
mw.log(string.format(LOG_NO_LANG_DATA, tostring(code)))
return ''
end
return rendered
end
-- Entry point: returns a language abbreviation by ISO 639 code.
-- Unknown/empty codes produce an empty string.
function p.abbr(frame)
local args = getInvokeArgs(frame)
local code = args[1] or ''
local showError = args['error'] and true or false
local l, _normalized, err = resolveLanguage(code, showError)
if l then
return l[1]
end
return err or ''
end
-- Entry point: returns the Russian article title (full language name)
-- by ISO 639 code. Unknown/empty codes produce an empty string.
-- When output=en, returns English language name from data_en.
function p.name(frame)
local args = getInvokeArgs(frame)
local code = args[1] or ''
local mode = args['mode'] or ''
local showError = args['error'] and true or false
if not isEmpty(code) then
if mode == 'en' then
local _, normalized = getLanguageData(code)
local l = normalized and languagesEn[normalized] or nil
if l then
return l[1]
elseif showError then
return generateError('lang', code)
end
else
local l = getLanguageData(code)
if l then
return l[2]
elseif showError then
return generateError('lang', code)
end
end
end
return ''
end
-- TODO: Вынести в отдельный модуль, так как логика сложная не для этого модуля, тут не только работа с языками
--
-- Entry point: builds a comma-separated list of pairs
-- (language link + supplied text). The input is a flat sequence:
-- code1 | text1 | code2 | text2 | ...
-- If text for the last code is missing, only the language link is emitted.
-- Unknown codes are marked with a category and wrapped in a span.
function p.list(frame)
local args = getInvokeArgs(frame)
local curr_lang = nil
local parts = {}
-- Итерируемся по позиционным аргументам по порядку
local maxIndex = 0
for k, _ in pairs(args) do
if type(k) == 'number' and k > maxIndex then
maxIndex = k
end
end
-- Use ipairs for sequential iteration, but handle gaps by checking maxIndex
for i = 1, maxIndex do
local v = args[i]
local value = v or ''
if curr_lang == nil then
if value ~= '' then
curr_lang = value
end
else
if value ~= '' then
local link, lang_code = buildLanguageLinkForCode(curr_lang)
local list_item
if lang_code ~= '' then
list_item = link
.. " <span dir='auto' lang='"
.. lang_code
.. "'>"
.. value
.. '</span>'
else
list_item = link
.. " <span class='unknown-foreign-lang'>"
.. value
.. '</span>'
.. CATEGORY_UNKNOWN_LANG
end
table.insert(parts, list_item)
else
local link, _ = buildLanguageLinkForCode(curr_lang)
table.insert(parts, link)
end
curr_lang = nil
end
end
if curr_lang ~= nil then
local link, _ = buildLanguageLinkForCode(curr_lang)
table.insert(parts, link)
end
if #parts == 0 then
return nil
end
return table.concat(parts, ', ')
end
-- Entry point: builds a sequence of inline language references
-- for the provided ISO 639 codes. Positional args are codes.
-- When parameter 'в' is present, square brackets are used.
-- For unknown codes tries Template:ref-<code>, otherwise uses 'und'.
function p.list_ref(frame)
local args = getInvokeArgs(frame)
local brackets = args['brackets'] or args['в']
local mode = args['mode'] or ''
local showError = args['error'] and true or false
local includeNbsp = args['includeNbsp'] ~= 'false' and args['includeNbsp'] ~= false
local refVariant
if brackets == 'none' then
refVariant = 'none'
elseif brackets == nil or brackets == '' or brackets == '()' then
refVariant = nil -- round
elseif brackets == '[]' or brackets == 'в' or brackets == 'square' or brackets == '1' or brackets == true then
refVariant = 'square'
else
refVariant = nil
end
-- We rely strictly on named params for brackets/mode; only numeric args are language codes.
-- But we need to filter out positional args that were used as options
local chunks = {}
local customClass = args['class'] or nil
local join = not isEmpty(args['join'])
local joinTexts, joinTitles = {}, {}
-- Process positional arguments as language codes
-- Use ipairs to iterate through args, but skip known named parameters
for idx, code in ipairs(args) do
-- Skip if this is a known named parameter
local isNamedParam = false
if code == brackets or code == tostring(brackets) then
isNamedParam = true
elseif code == mode then
isNamedParam = true
elseif code == customClass then
isNamedParam = true
elseif code == 'join' or code == 'error' or code == 'class' then
isNamedParam = true
end
if not isNamedParam and code and not isEmpty(code) then
local normalized = mw.ustring.lower(code)
if join then
local l = getLanguageData(normalized)
if l then
local text = mode == 'iso' and normalized or l[1]
table.insert(joinTexts, text)
table.insert(joinTitles, transformLangCase(normalized, 'prepositional'))
else
table.insert(chunks, renderUnknownRef(normalized, showError, refVariant, customClass, includeNbsp))
end
else
local rendered = buildRefForCode(normalized, refVariant, includeNbsp, customClass, mode)
table.insert(chunks, rendered or renderUnknownRef(normalized, showError, refVariant, customClass, includeNbsp))
end
end
end
if join and #joinTexts > 0 then
local text = table.concat(joinTexts, ', ')
local title = table.concat(joinTitles, ', ')
table.insert(chunks, buildRefSpan(text, title, refVariant, includeNbsp, customClass))
end
return table.concat(chunks)
end
-- args:
-- 1 — language code
-- case|form — 'l2'|'l3'|'l4' or 'nom'|'gen'|'prep' or 'р'|'п' (default 'l2')
-- 'р' → genitive, 'п' → prepositional
-- mode — 'en' or 'iso' to override language output
-- error — when truthy, returns error html for unknown code
function p.transform(frame)
local args = getInvokeArgs(frame)
local code = args[1] or ''
-- Treat empty strings as absent to ensure proper fallback to 'l2'
local form = args['case']
if isEmpty(form) then
form = args['form']
end
if isEmpty(form) then
form = 'l2'
end
local mode = args['mode'] or ''
local showError = args['error'] and true or false
if isEmpty(code) then
return ''
end
local l, normalized = getLanguageData(code)
local normCode = normalized or ''
-- Output overrides
if mode == 'iso' then
return normCode
elseif mode == 'en' then
local e = languagesEn[normCode]
if e then
return e[1]
end
return showError and generateError('lang', code) or ''
end
-- Russian forms
if not l then
return showError and generateError('lang', code) or ''
end
local f = mw.ustring.lower(form)
if f == 'l2' or f == 'nom' or f == 'nominative' then
return l[2] and mw.ustring.lower(l[2]) or ''
elseif f == 'l3' or f == 'gen' or f == 'genitive' or f == 'р' then
return transformLangCase(normCode, 'genitive')
elseif f == 'l4' or f == 'prep' or f == 'prepositional' or f == 'п' then
return transformLangCase(normCode, 'prepositional')
else
-- default to nominative (lowercase)
return l[2] and mw.ustring.lower(l[2]) or ''
end
end
-- Entry point: returns the prepositional form for a language code.
function p.transform_lang(frame)
local args = getInvokeArgs(frame)
local code = args[1] or ''
local showError = args['error'] and true or false
local l, normalized, err = resolveLanguage(code, showError)
if l then
return transformLangCase(normalized or code, 'prepositional')
end
return err or ''
end
-- Entry point: returns the genitive form for a language code.
function p.transform_lang_genitive(frame)
local args = getInvokeArgs(frame)
local code = args[1] or ''
local showError = args['error'] and true or false
local l, normalized, err = resolveLanguage(code, showError)
if l then
return transformLangCase(normalized or code, 'genitive')
end
return err or ''
end
return p