Module:Languages

From The Satanic Wiki
Jump to navigation Jump to search

See Global Lua Modules/Languages

Subpages
{{#dpl: | namespace = Module | titlematch = Languages/% | skipthispage = false | nottitlematch = Languages/i18n|Languages/i18n/% }}

-- <nowiki>
--------------------------------------------------------------------------------
-- Lua templating and link generation for language subpages.
--
-- @module languages
-- @alias l
-- @release stable
-- @require [[w:c:dev:Global Lua Modules/Arguments|Module:Arguments]]
-- @require [[w:c:dev:Global Lua Modules/I18n|Module:I18n]]
-- @require [[w:c:dev:Module:Fallbacklist|Module:Fallbacklist]]
-- @require [[w:c:dev:Module:Common/i18n|Module:Common/i18n]]
-- @author [[User:Nanaki|Nanaki]] - original version
-- @author [[User:KockaAdmiralac|KockaAdmiralac]]
-- @author [[User:MACH-59330|MACH-59330]]
-- @see [[project:Internationalization]]
--------------------------------------------------------------------------------
local l = {}
local getArgs = require('Module:Arguments').getArgs
local fallbacks = mw.loadData('Module:Fallbacklist')
local i18n = require('Module:I18n').loadMessages('Common')
local contentLang = mw.getContentLanguage():getCode()
local title = mw.title.getCurrentTitle()

local function intOrI18n(msg, ...)
	local frame = mw.getCurrentFrame()
	if not frame then
		return nil
	end

	local hasArgs = false
	local args = {}
	for i = 1, select('#', ...) do
		hasArgs = true
		local val = select(i, ...)
		val = val == nil and '' or tostring(val)
		args[i] = val
	end

	local text = frame:preprocess('{{int:' .. msg .. (hasArgs and '|' .. table.concat(args, '|')) .. '}}')
	if text == '<' .. msg .. '>' or text == '⧼' .. msg .. '⧽' then
		text = frame:preprocess(i18n:msg{
			key = msg,
			args = args,
		})
	end
	return text
end

local function escRx(text, spaces)
    text = text:gsub('[\\().+*?^$-/=!:]', '\\%0')

    if spaces then
        text = text:gsub(' ', '_')
    end

    return text
end

local function makeRedLink(page, text, query)
    page = mw.ustring.gsub(tostring(page), '^:*', '')

    local frame = mw.getCurrentFrame()
    local title = frame
        and frame:preprocess('{{int:red-link-title|{{ucfirst:' .. page .. '}}}}')
        or ''

    query = query or {}
    query.action = 'edit'
    query.redlink = 1

    return mw.html.create('span')
        :addClass('redlink')
        :attr('title', title)
        :wikitext('[' .. tostring(mw.uri.fullUrl(page, query)) .. ' ' .. text .. ']')
end

local function makeBlueLink(page, text)
    page = mw.ustring.gsub(tostring(page), '^:*', '')

    return mw.html.create('')
        :wikitext('[[:' .. page .. '|' .. text .. ']]')
end

local function getAttr(t, name)
    for i, attr in ipairs(t.attributes) do
        if attr.name == name then
            return attr.val
        end
    end
end

--------------------------------------------------------------------------------
-- @type Links
--------------------------------------------------------------------------------
-- @property {table} Links.list
--------------------------------------------------------------------------------
-- @property {table} Links.keys
--------------------------------------------------------------------------------
-- @function prepLinks
-- @param {string} root
-- @param {table} list
-- @param {table} order
-- @param[opt] {string} editintro
-- @return {Links}
--------------------------------------------------------------------------------
local function prepLinks(root, list, order, editintro)
    local links = {
        list = {},
        keys = {}
    }

    editintro = editintro or 'Template:I18ndoc/editintro'

    for _, k in ipairs(order) do
        local lang = mw.language.fetchLanguageName(k)

        if lang ~= '' then
            local obj = {
                lang = k
            }

            if list[k] then
                if type(list[k]) == 'string' then
                    obj.link = makeBlueLink(list[k], lang)
                    obj.page = list[k]
                elseif k == contentLang then
                    obj.link = makeBlueLink(root, lang)
                    obj.page = root
                else
                    obj.link = makeBlueLink(root .. '/' .. k, lang)
                    obj.page = root .. '/' .. k
                end
            else
                local options = {
                    editintro = editintro,
                    preload = 'Template:I18ndoc',
                    summary = 'Automatic generation of i18n documentation'
                }

                obj.link = makeRedLink(root .. '/' .. k, lang, options)
                obj.page = root .. '/' .. k
                obj.new = true
            end

            table.insert(links.list, obj)

            links.keys[k] = obj
        end
    end

    return links
end

--------------------------------------------------------------------------------
-- @function l.userLang
-- @return {Lang}
--------------------------------------------------------------------------------
function l.userLang()
    local frame = mw.getCurrentFrame()
    local lang

    if frame == nil then
        mw.log('userLang(): can\'t get user\'s language without frame object, returning content language for now (' .. mw.language.fetchLanguageName(contentLang) .. ')')

        return mw.getContentLanguage()
    end

    local code = frame:preprocess('{{int:lang}}')

    if mw.language.fetchLanguageName(code) == '' then
        mw.log('userLang(): unrecognised language code, returning content language (' .. mw.language.fetchLanguageName(contentLang) .. ')')

        return mw.getContentLanguage()
    end

    return mw.language.new(code)
end

--------------------------------------------------------------------------------
-- @function l.pageLink
-- @param {Links} links
-- @param {table} args1
-- @param {table} args2
-- @return {table}
--------------------------------------------------------------------------------
function l.pageLink(links, args1, args2)
    local keys = links.keys
    local frame = mw.getCurrentFrame()
    local link = {}

    local pageLang = #mw.language.fetchLanguageName(title.subpageText) ~= 0
        and title.subpageText
        or contentLang
    local userLang = frame
        and l.userLang():getCode()
        or 'szl'

    if pageLang == contentLang then
        link = keys[userLang]

        for i, l in ipairs(fallbacks[userLang] or {}) do
            if not link and keys[l] and not keys[l].new then
                link = keys[l]

                break
            end
        end
    end

    link = (link and not link.new)
        and link
        or keys[pageLang]

    if link.new then
        local langs = {}

        for l, _ in pairs(keys) do
            table.insert(langs, l)
        end

        table.sort(langs)

        link = keys[langs[1]]
    end

    do
        local lastPart = (link.page or ''):match('[^/]+$') or ''

        link.lang = mw.language.fetchLanguageName(lastPart) ~= ''
            and lastPart
            or contentLang
    end

    return link
end

--------------------------------------------------------------------------------
-- @function l.editData
-- @param {Links} links
-- @param {table} args1
-- @param {table} args2
-- @return {table}
--------------------------------------------------------------------------------
function l.editData(links, args1, args2)
    local frame = mw.getCurrentFrame()
    local userLang = frame:preprocess('{{int:lang}}')
    local pageLang = #mw.language.fetchLanguageName(title.subpageText) ~= 0
        and title.subpageText
        or l.pageLink(links, args1, args2).lang

    return tostring(mw.html.create('span')
        :attr('class', 'lang-select-data wds-is-hidden')
        -- Start LangSelect.js attributes.
        :attr('data-lang', pageLang)
        :attr('data-lang-name', mw.language.fetchLanguageName(pageLang))
        :attr('data-userlang-name', mw.language.fetchLanguageName(userLang))
        :attr('data-userlang-exists', tostring(not (links.keys[userLang] or {}).new))
        -- End LangSelect.js attributes.
    )
end

l.formats = {}

--------------------------------------------------------------------------------
-- @function l.formats.default
-- @param {Links} links
-- @param {table} args1
-- @param {table} args2
-- @param {string} root
-- @return {table}
--------------------------------------------------------------------------------
function l.formats.default(links, args1, args2, uselangs)
    local frame = mw.getCurrentFrame()

    local div = mw.html.create('div')
        :addClass(args1.class)

    if frame and mw.ustring.lower(frame:preprocess('{{int:rte-ck-dir}}')) == 'rtl' then
        div:attr('dir', 'rtl')
    end

    div
        :tag('strong')
            :wikitext(frame and frame:preprocess('[[w:c:Module:Languages|{{int:interlang-section-header-desktop}}]]') or 'Languages:')
            :done()
        :wikitext(' ')

    local separator = frame
        and frame:preprocess('{{int:pipe-separator}}')
        or '|'
    local highlight = mw.ustring.lower(args1.highlight or '')

    for i, v in ipairs(links.list) do
        if i > 1 then
            div:wikitext(' ' .. separator .. ' ')
        end

        local link = v.link

        link.tagName = 'span'
        link:attr('data-lang', v.lang)

        if highlight == v.lang then
            link:addClass('highlight')
        end

        div:node(link)
    end

    local selected = mw.ustring.lower(args1.select or '')

    if selected ~= '' then
        local flag = false

        if links.keys[selected] and not links.keys[selected].new then
            links.keys[selected].link:addClass('selected')
            flag = true
        else
            for i, v in ipairs(fallbacks[selected] or {}) do
                if links.keys[v] and not links.keys[v].new then
                    links.keys[v].link:addClass('selected')
                    flag = true

                    break
                end
            end
        end

        if not flag then
            links.keys.en.link:addClass('selected')
        end
    end

    if not uselangs then
        div = tostring(div) .. l.editData(links, args1, args2)
    end

    return div
end

--------------------------------------------------------------------------------
-- @function l.formats.uselangs
-- @param {Links} links
-- @param {table} args1
-- @param {table} args2
-- @param {string} root
-- @return {table}
--------------------------------------------------------------------------------
function l.formats.uselangs(links, args1, args2, root)
    local div = l.formats.default(links, args1, args2, root, true)

    for i, v in ipairs(div.nodes) do
        local node = div.nodes[i]

        if node.tagName ~= nil and getAttr(node, 'data-lang') then
            local lang = getAttr(node, 'data-lang')
            local langName = mw.language.fetchLanguageName(lang)

            if langName ~= '' then
                node.nodes = {}
                node:wikitext('[' .. tostring(mw.uri.fullUrl(root, {uselang = lang})) .. ' ' .. langName .. ']')
            end
        end
    end

    div = tostring(div) .. l.editData(links, args1, args2)

    return div
end

--------------------------------------------------------------------------------
-- @function l.formats.list
-- @param {Links} links
-- @param {table} args1
-- @param {table} args2
-- @return {table}
--------------------------------------------------------------------------------
function l.formats.list(links, args1, args2)
    local frame = mw.getCurrentFrame()

    local ul = mw.html.create('ul')
        :addClass(args1.class)

    if frame and mw.ustring.lower(frame:preprocess('{{int:rte-ck-dir}}')) == 'rtl' then
        ul:attr('dir', 'rtl')
    end

    local highlight = mw.ustring.lower(args1.highlight or '')

    for i, v in ipairs(links.list) do
        local link = v.link

        link.tagName = 'li'
        link:attr('data-lang', v.lang)

        if highlight == v.lang then
            link:addClass('highlight')
        end

        ul:node(link):newline()
    end

    local selected = mw.ustring.lower(args1.select or '')

    if selected ~= '' then
        local flag = false

        if links.keys[selected] and not links.keys[selected].new then
            links.keys[selected].link:addClass('selected')
            flag = true
        else
            for i, v in ipairs(fallbacks[selected] or {}) do
                if links.keys[v] and not links.keys[v].new then
                    links.keys[v].link:addClass('selected')
                    flag = true

                    break
                end
            end
        end

        if not flag then
            links.keys.en.link:addClass('selected')
        end
    end

    return ul
end

--------------------------------------------------------------------------------
-- @function l.formats.transclude
-- @param {Links} links
-- @param {table} args1
-- @param {table} args2
-- @return {table}
--------------------------------------------------------------------------------
function l.formats.transclude(links, args1, args2)
    local frame = mw.getCurrentFrame()

    local lang = frame
        and l.userLang():getCode()
        or 'szl'
    local link = l.pageLink(links, args1, args2)

    local res = mw.html.create('')

    local notice = mw.ustring.lower(args1.notice or '')

    if notice == 'top' or notice == 'both' then
        res
            :tag('div')
                :addClass('transclude-notice transclude-notice-top')
                :wikitext(intOrI18n('custom-languages-notice', link.page, tostring(mw.uri.fullUrl(link.page, 'action=edit')), '') or 'Here goes the notice')
                :done()
            :newline()
    end

    if frame then
        if not pcall(function ()
            res:wikitext(frame:expandTemplate{
                title = mw.ustring.gsub(link.page, '^:*', ':')
            })
        end) then
            return args1.missing or '[[:' .. link.page .. ']]'
        end

    else
        res:wikitext('Here goes the transcluded page: ' .. mw.ustring.gsub(link.page, '^:*', ':'))
    end

    if notice == 'bottom' or notice == 'both' then
        res
            :newline()
            :tag('div')
                :addClass('transclude-notice transclude-notice-bottom')
                :wikitext(intOrI18n('custom-languages-notice', link.page, tostring(mw.uri.fullUrl(link.page, 'action=edit')), '*') or 'Here goes the notice')
    end

    if frame and mw.ustring.lower(frame:preprocess('{{int:rte-ck-dir}}')) == 'rtl' then
        ul:attr('dir', 'rtl')
    end

    return res
end

--------------------------------------------------------------------------------
-- @function l.formats.interwiki
-- @param {Links} links
-- @param {table} args1
-- @param {table} args2
-- @param {string} prefixedRoot
-- @return {table}
--------------------------------------------------------------------------------
function l.formats.interwiki(links, args1, args2, prefixedRoot)
    local str = ''
    local frame = mw.getCurrentFrame()

    for k, v in ipairs(links.list) do
        if not v.new and v.lang ~= contentLang then
            str = str .. '[[' .. v.lang .. ':' .. prefixedRoot .. '/' .. v.lang .. ']]'
        end
    end

    str = str .. l.editData(links, args1, args2)

    return frame
        and frame:preprocess(str)
        or mw.text.nowiki(str)
end

--------------------------------------------------------------------------------
-- @function l.subpages
-- @param {string} page
-- @param[opt] {string} namespace
-- @return {table}
--------------------------------------------------------------------------------
function l.subpages(page, namespace)
    local frame = mw.getCurrentFrame()

    if frame == nil then
        return {'en', 'fr', 'pl', 'es', 'de', 'bad-code'}
    end

    local existing = mw.ustring.lower(frame:preprocess('{{#dpl:namespace=' .. (namespace or '') .. '|titleregexp=^' .. escRx(page, true) .. '\\/[a-z-]+$|replaceintitle=/^' .. escRx(page, false) .. '\\//,|redirects=include|skipthispage=no|format=¦,%TITLE%¦|noresultsheader=¦¦}}'))

    existing = select(3, mw.ustring.find(existing, '^%s*%|([%|a-z-]*)%|%s*$'))

    if existing then
        return mw.text.split(existing, '%s*|%s*')
    end
end

--------------------------------------------------------------------------------
-- @function l.langs
-- @param {Frame} frame
-- @return {string}
--------------------------------------------------------------------------------
function l.langs(frame)
    -- Invoke-only parameters
    local args1 = getArgs(frame, {
        trim = true,
        removeBlanks = true,
        frameOnly = true,
        readOnly = true
    })

    -- Overwritable parameters
    local args2 = getArgs(frame, {
        trim = true,
        removeBlanks = true,
        parentFirst = true,
        readOnly = true
    })

    -- Get the root page name
    local root
    local rootTitle = args1.page
        and mw.title.new(args1.page)
        or mw.title.getCurrentTitle()

    if
        mw.ustring.find(rootTitle.subpageText, '[a-z-]+') and
        mw.language.fetchLanguageName(rootTitle.subpageText) ~= ''
    then
        root = rootTitle.baseText
    else
        root = rootTitle.text
    end

    local prefixedRoot = rootTitle.nsText

    prefixedRoot = mw.ustring.gsub(prefixedRoot .. ':' .. root, '^:*', '')

    -- Must-have languages
    local langs = {}

    for i, v in ipairs(args1 or {}) do
        v = mw.ustring.lower(mw.text.trim(v or ''))

        if v ~= '' then
            langs[v] = false
        end
    end

    langs[contentLang] = true

    -- Go over subpages of root
    local existing = l.subpages(root, rootTitle.nsText) or {}

    for i, v in ipairs(existing) do
        if v ~= '' then
            if v == contentLang then -- English has a separate subpage
                langs[contentLang] = mw.ustring.gsub(rootTitle.nsText .. ':' .. root, '^:', '') .. '/' .. contentLang
            else
                langs[v] = true
            end
        end
    end

    -- Look for parameters overriding language pages
    for k, v in pairs(args2) do
        if type(k) == 'string' and mw.language.fetchLanguageName(k) ~= '' then
            langs[k] = v
        end
    end

    -- Get a list of langs sorted by code
    local ordered = {}

    for k, v in pairs(langs) do
        if k ~= contentLang then
            ordered[#ordered + 1] = k
        end
    end

    table.sort(ordered)
    table.insert(ordered, 1, contentLang) -- with English being first

    -- Get list of links
    local links = prepLinks(prefixedRoot, langs, ordered, args1.editintro)

    -- Pass to format function
    local format = mw.ustring.lower(args1.format or 'interwiki')

    return l.formats[format](links, args1, args2, prefixedRoot)
end

--------------------------------------------------------------------------------
-- Preload function for i18n documentation
--
-- @function l.preload
-- @param {Frame} frame
-- @return {string}
--------------------------------------------------------------------------------
function l.preload(frame)
    -- Fetch page
    local page = frame.args[1]
    local txt = mw.title.new(page):getContent():gsub('<!%-(.-)%->', '')

    -- Generate untranslated doc
    local ret = '{{Untranslated}}\n'
    local tbl, i18n

    -- Temporary parsing of legacy i18n
    local LANGSELECT = '{{LangSelect}}'
    local LANGUAGES = '{{Languages}}'
    local txtInline = mw.text.trim((txt:gsub('\n', '')))

    if
        txtInline == LANGSELECT or
        txtInline == LANGSELECT .. LANGUAGES or
        txtInline == LANGUAGES .. LANGSELECT
    then
        ret = ret .. mw.title.new(page .. '/en'):getContent():gsub('<!%-(.-)%->', '')

    elseif not txt:find('{{{') then
        ret = ret .. frame:preprocess(txt)

    -- Parsing parameter defaults in base page
   else
        ret = ret .. '{{:' .. page

        for en in txt:gmatch('{{%b{}}}') do
            en = en:match('^{{{(.-)}}}$')
            tbl = mw.text.split(en, '|')
            i18n = {
                key = en:match('^([^|]+)') or '',
                val = en:match('^[^|]+|*(.*)$') or ''
            }

            if not ret:find('| ' .. (i18n.key:gsub('%-', '%%-')) .. ' = ') then
                ret = ret .. '\n| ' .. i18n.key .. ' = ' .. i18n.val
            end
        end

        ret = ret .. '\n}}'

    end

    return ret

end

return l