Module:Project I18n

From Commons Wiki
Jump to navigation Jump to search
-- <nowiki>
---
-- I18n report module for Dev Wiki.
-- @module      p
-- @version     0.5.2
-- @author      [[User:KockaAdmiralac|KockaAdmiralac]]
-- @author      [[User:Speedit|Speedit]]
-- @release     stable
-- @todo        Standardise {{T|LangSelect}} so that parameters work.
local p = {}

-- Module dependencies.
local yesno = require('Module:Yesno')
require('Module:No interwiki access')

-- Module variables.
-- @section vars
local languages = {
    'ar',
    'bg',
    'ca',
    'cs',
    'da',
    'de',
    'el',
    'es',
    'et',
    'fa',
    'fi',
    'fr',
    'he',
    'hi',
    'hr',
    'hu',
    'id',
    'it',
    'ja',
    'ko',
    'ms',
    'nl',
    'no',
    'pl',
    'pt-br',
    'ro',
    'ru',
    'sr-ec',
    'sr-el',
    'sv',
    'th',
    'tl',
    'tr',
    'uk',
    'vi',
    'zh-hans',
    'zh-hant'
}
local nonscripts = {
    ['MediaWiki:FANDOM-Monaco/code.js'] = true,
    ['MediaWiki:I18nEdit/code.js']      = true,
    ['MediaWiki:I18nEditTests/code.js'] = true
}
local title = mw.title.getCurrentTitle()

-- Module utilities.
local function selectLang(frame)
    local selected = frame.preprocess
        and frame:preprocess('{{int:lang}}')
        or  mw.getContentLanguage():getCode()
    for i, lang in ipairs(languages) do
        supported = supported and true or lang == selected
    end
    selected = supported and selected or 'pl'
    return selected
end

local function padright(str, len, char)
    char = char or ' '
    return str .. string.rep(char, len - #str)
end
 
-- Package functions.

-- Language list generator.
-- @param       {table} frame Frame table from invocation.
-- @return      {string} list List of languages as <ul>
-- @see         p.main
function p.langs(frame)
    local selected = selectLang(frame)
    local uri = mw.uri.fullUrl(title.prefixedText, { uselang = selected })

    local list = mw.html.create('ul')
        :addClass('template-documentation-langs')
        :css('flex', '0 0 auto')
            :tag('li'):addClass('selected')
            :wikitext('[' .. tostring(uri) .. ' ' .. mw.language.fetchLanguageName(selected) .. ']')
        :done()

    for i, lang in ipairs(languages) do
        uri:extend({ uselang = lang })
        uri.fragment = frame.args[3]
        if lang ~= selected then
            list:tag('li')
                :wikitext('[' .. tostring(uri) .. ' ' .. mw.language.fetchLanguageName(lang) .. ']')
        end
    end

    list = tostring(list)
    return frame
        and frame:preprocess(list)
        or  mw.text.nowiki(list)
end

-- Untranslated JS report.
-- @param       {table} frame Frame table from invocation.
-- @return      {string} report List of untranslated scripts
-- @see         p.main
function p.scripts(frame)
    local selected = selectLang(frame)
    local name = mw.language.fetchLanguageName(selected)

    local report = frame:preprocess(table.concat({
        '{{#dpl:',
        '| allowcachedresults = 1',
        '| namespace       = 0',
        '| category        = Scripts using I18n-js',
        '| notcategory     = Translated scripts/', name,
        '| notcategory     = Archived',
        '| format          = ,\\n* [[%TITLE%]] ([[Special:BlankPage/I18nEdit/%TITLE%/', selected .. '|translate]]),,',
        '| noresultsheader = ²{int:wikiasearch2-noresults}²',
        '}}'
    }))
    return report
end

-- Untranslated documentation report.
-- @param       {table} frame Frame table from invocation.
-- @return      {string} report List of untranslated documentation
-- @see         p.main
function p.pages(frame)
    local selected = selectLang(frame)

    local report = frame:preprocess(table.concat({
        '{{#dpl:',
        '| allowcachedresults = 1',
        '| namespace       = 0',
        '| nottitleregexp  = /',
        '| format          = ,²{#ifeq:²{#dpl:¦title = %PAGE%/', selected,
        '¦ noresultsheader = no}²¦no¦* [[%PAGE%]]\n}²,,',
        '| categoryregexp  = JavaScript¦CSS',
        '| notcategory     = Archived',
        '| noresultsheader = <nowiki />',
        '}}'
    }))
    return report
end

-- Partially translated documentation report.
-- @param       {table} frame Frame table from invocation.
-- @return      {string} report List of all partially translated docs
-- @see         p.main
function p.partials(frame)
    local report = frame:preprocess(table.concat({
        '{{#dpl:',
        '| allowcachedresults = 1',
        '| category        = Please translate',
        '| nottitlematch   = Maintenance/Dummy page',
        '| noresultsheader = There are no articles with {{t|Untranslated}}.',
        '}}'
    }, '\n'))
    return report
end

---
-- I18n-js support dashboard.
-- @param       {table} frame Frame table from invocation.
-- @return      {string} report List of all scripts by I18n-js status
-- @see         p.main
function p.imports(frame)
    return frame:preprocess(table.concat({
        '{{#dpl:',
        '| allowcachedresults = 1',
        '| ordermethod        = title',
        '| titleregexp        = .+i18n\\.json',
        '| namespace          = MediaWiki',
        '| format             = ',
            '«table class="WikiaTable sortable" style="margin: 0; width: 100%;"»',
                '«tr»',
                    '«th»Script page:«/th»',
                    '«th»I18n-js status:«/th»',
                '«/tr»,',
                '²{#invoke:Project I18n¦importSupport',
                    '¦MediaWiki:²{#invoke:Project I18n¦importCode',
                        '¦²{#dpl:',
                            '¦ allowcachedresults = 1',
                            '¦ include            = {Infobox JavaScript}:Code',
                            '¦ title              = ',
                                '²{#vardefineecho:PAGENAME¦',
                                    '²{#dplreplace:%PAGE%¦/^.*Custom\\-([^\\/]+).*$/¦\\1}²',
                                '}²',
                        '}²',
                        '¦²{#var:PAGENAME}²',
                    '}²',
                '}²,,',
            '«/table»',
        '}}',
    }))
end

-- .
-- @param       {table} frame Frame table from invocation.
-- @param       {table} frame.args Arguments table.
-- @param       {string} frame.args[1] Package method - report.
-- @param       {string} frame.args[2] URL fragment name (for link shortcuts).
-- @param       {string} frame.args.langs Boolean for language list.
-- @return      {string} report List of all scripts by I18n-js status
-- @see         p.main
function p.main(frame)
    local method   = mw.text.trim(frame.args[1] or '')
    local fragment = mw.text.trim(frame.args[2] or '')
    local langs    = yesno(frame.args.langs, false)
    local ret

    if langs then
        ret = mw.html.create('div')

        ret:attr {
            ['class'] = 'template-documentation',
            ['id']    = fragment
        }
        ret:css {
            ["box-sizing"] = "border-box",
            ["display"]    = "flex",
            ["flex-flow"]  = "column nowrap",
            ["height"]     = "100%",
            ["margin"]     = "0"
        }

        ret
            :wikitext(p.langs(frame))
            :newline()
            :tag('div')
                :attr {
                    ['class'] = 'template-documentation-content'
                }
                :wikitext(p[method](frame))
            :done():newline()
        
    else
        ret = p[method](frame)
    end

    return tostring(ret)
end

-- Utilities for import report (I18n-js).
-- @section     import

--- Script name extractor from JSON page name.
function p.importScriptName(frame)
    return frame.args[1]:match('Custom%-([^/]+)')
end

--------------------------------------------------------------------------------
-- Gets the code page from a list of links.
--
-- @param {string} scripts
-- @return {string|nil}
--------------------------------------------------------------------------------
local function getScript(scripts)
    local modules = {}
    for script in scripts:gmatch(':([^:]-%.js)') do
        if
            script:find('/') == nil or
            script:find('/code')
        then
            table.insert(modules, script)
        end
    end
    return #modules > 0 and modules[#modules] or nil
end

--------------------------------------------------------------------------------
-- Code page extractor from code data.
--
-- @param {Frame} frame
-- @return {string}
--------------------------------------------------------------------------------
function p.importCode(frame)
    local scripts = mw.text.trim(frame.args[1] or '')
    local default = mw.text.trim(frame.args[2] or '')
    local result  = getScript(scripts)
    if result then
        return result
    else
        local page = default:match(':([^:]-)$') or default;
        return getScript(frame:preprocess('{{Code|' .. (default:match(':([^:]-)$') or default) .. '}}'))
            or (page:match('(.-)%.js$') or page) .. '.js'
    end
end

--- I18n-js status from MediaWiki script page.
function p.importSupport(frame)
    local mwPage = mw.text.trim(frame.args[1])
    if nonscripts[mwPage] then
        return ''
    end
    local result = frame:preprocess(table.concat({
        '{{#dpl:',
        '| allowcachedresults = 1',
        '| title            = ', mwPage,
        '| mode             = inline',
        '| include          = *',
        '| includematch     = /[Ii]18n-js/',
        '| includemaxlength = 0',
        '| format           = ,«!--,--»,',
        '| format           = ,«!--,--»,',
        '| resultsfooter    = [[', mwPage, ']]¦1',
        '| noresultsfooter  = [[', mwPage, ']]¦0',
        '}}'
    }))
    -- Not interested in text that might appear before the footer.
    local pageLink, reportBool = string.match(result, "^.*(%[%[.-%]%])|([01])$")
    local pageLink = pageLink or ''
    local reportBool = yesno(reportBool or '')

    local reportField = mw.html.create('tr')

    reportField
        :tag('td')
            :wikitext(pageLink)
        :done()
        :tag('td')
            :tag('small')
                :addClass('wds-button')
                :css {
                    ['background']  = '#' .. (reportBool and '32cd32' or 'ffba01'),
                    ['border']      = 'none',
                    ['margin']      = '2px',
                    ['padding']     = '0',
                    ['user-select'] = 'none',
                    ['width']       = '1.5em'
                }
                :wikitext(reportBool and '✓' or '✗')
            :done()
            :wikitext((reportBool
                and ' I18n-js supported'
                or  ' I18n-js unsupported'
            ))
        :done()

    return tostring(reportField)
end

--- Todo list generator in Lua I18n submodules.
function p._todo(source)
    local i18n = require('Module:' .. source .. '/i18n')
    local content = mw.title.new('Module:' .. source .. '/i18n')
        :getContent()
        :gsub('\n%-%-[^\n]*', '')

    local i18n_languages = {}
    local i18n_keys = {}
    local maxlen_key = 0

    for _, code in ipairs(languages) do
        i18n[code] = i18n[code] or {}
    end

    for key in pairs(i18n.en or {}) do
        maxlen_key = #key >= maxlen_key
            and #key
            or  maxlen_key
        table.insert(i18n_keys, key)
    end
    maxlen_key = 1 + math.ceil( (maxlen_key + 2) / 4 ) * 4
    table.sort(i18n_keys, function(a, b)
        local i1 = content:find('"' .. a .. '"', nil, true)
        local i2 = content:find('"' .. b .. '"', nil, true)
        return i1 < i2
    end)

    for code, messages in pairs(i18n) do
        if i18n_languages ~= 'en' then
            local translated = true

            for _, key in pairs(i18n_keys) do
                if not i18n[code][key] then
                    translated = false
                    break
                end
            end

            if not translated then
                table.insert(i18n_languages, code)
            end
        end
    end
    table.sort(i18n_languages)

    local ret, todo = {}, ''
    for _, key in pairs(i18n_keys) do
        todo = '--  @todo ' .. padright('"' .. key .. '"', maxlen_key) .. ' -'

        for _, code in ipairs(i18n_languages) do
            todo = todo .. ' ' .. (not (i18n[code] or {})[key] and code or code:gsub('.', ' '))
        end

        if not todo:find(' %- +$') then
            table.insert(ret, todo)
        end
    end

    return table.concat(ret, '\n')
end

return p