Moduldokumentasjon

Funksjonalitet for {{Ukens konkurranse kriterium}} rediger

Kode Resultat
{{#invoke:UKB|criterion|ny}} opprettes i løpet av konkurransen
{{#invoke:UKB|criterion|ny|redirects=true}} opprettes i løpet av konkurransen (inkludert omdirigeringer)
{{#invoke:UKB|criterion|eksisterende}} ble opprettet før konkurransen startet
{{#invoke:UKB|criterion|stubb}} nil
{{#invoke:UKB|criterion|bytes|1000}} utvides med minst 1000 byte
{{#invoke:UKB|criterion|navnerom|0|10}} er en artikkel eller mal
{{#invoke:UKB|criterion|navnerom|0|10|site=www.wikidata.org}} er en artikkel eller mal på www.wikidata.org
{{#invoke:UKB|criterion|navnerom|0}} er en artikkel
{{#invoke:UKB|criterion|tilbakelenke|Liste over Egypts presidenter}} lenkes fra Liste over Egypts presidenter
{{#invoke:UKB|criterion|fremlenke|Bergen}} lenker til Bergen
{{#invoke:UKB|criterion|mal|Opprydning}} merket med {{Opprydning}}
{{#invoke:UKB|criterion|mal|Referanseløs|Kildeløs}} merket med {{Referanseløs}} eller {{Kildeløs}}
{{#invoke:UKB|criterion|mal|Opprydning|se:Neutralitehta}} merket med {{Opprydning}} eller {{Neutralitehta}}
{{#invoke:UKB|criterion|kategori|Island}} ligger i kategorien Island
{{#invoke:UKB|criterion|kategori|Island|nn:Island|se:Islánda|smn:Island}} ligger i minst en av kategoriene Island, Island, Islánda eller Island
{{#invoke:UKB|criterion|kategori|Primater|ignore=Mennesker,Utdødde pattedyr‎}} ligger i kategorien Primater
{{#invoke:UKB|criterion|enkeltsider|Island|Grønland}} Island eller Grønland
{{#invoke:UKB|criterion|sparql|query=?item wdt:P31 wd:Q146. }} har et wikidata-element som matcher denne SPARQL-spørringen
{{#invoke:UKB|criterion|sparql|query=?item wdt:P31 wd:Q146. |description=om katter }} om katter (Wikidata-spørring)

Hvis teksten malen genererer ikke passer kan den overstyres med |description=:

Kode Resultat
{{#invoke:UKB|criterion|navnerom|0|description=i hovednavnerommet}} i hovednavnerommet

Funksjonalitet for {{Ukens konkurranse poeng}} rediger

Kode Resultat
{{#invoke:UKB|rule|ny|20}} Det gis 20 poeng for opprettelse av side (ikke omdirigeringsside)
{{#invoke:UKB|rule|ny|10|site=commons.wikimedia.org}} Det gis 10 poeng for opprettelse av side (ikke omdirigeringsside) på commons.wikimedia.org
{{#invoke:UKB|rule|ny|10|site=commons.wikimedia.org|description=opplasting av bilder}} Det gis 10 poeng for opplasting av bilder på commons.wikimedia.org
{{#invoke:UKB|rule|omdirigering|1}} Det gis 1 poeng for opprettelse av omdirigeringsside
{{#invoke:UKB|rule|kvalifisert|2}} Det gis 2 poeng for hver kvalifiserte side
{{#invoke:UKB|rule|endring|1}} Det gis 1 poeng for hver redigering
{{#invoke:UKB|rule|byte|0.1}} Det gis 0,1 poeng for hver tilføyde byte
{{#invoke:UKB|rule|byte|1|max=100}} Det gis 1 poeng for hver tilføyde byte, men maks 100 poeng per side
{{#invoke:UKB|rule|bytebonus|20|3000}} Det gis 20 bonuspoeng når det tilføres mer enn 3000 bytes til en side
{{#invoke:UKB|rule|ord|3}} Det gis 3 poeng for hvert tilføyde ord i brødtekst (ikke i maler, tabeller, o.l.)
{{#invoke:UKB|rule|ord|3|max=100}} Det gis 3 poeng for hvert tilføyde ord i brødtekst (ikke i maler, tabeller, o.l.), men maks 100 poeng per side
{{#invoke:UKB|rule|ordbonus|20|3000}} Det gis 20 bonuspoeng når det tilføres mer enn 3000 ord til en side
{{#invoke:UKB|rule|bilde|3}} Det gis 3 poeng for hvert tilføyde bilde
{{#invoke:UKB|rule|bilde|3|max=100}} Det gis 3 poeng for hvert tilføyde bilde, men maks 100 poeng per side
{{#invoke:UKB|rule|bilde|3|max=100|ownimage=10}} Det gis 3 poeng for hvert tilføyde bilde (10 for egenopplastede), men maks 100 poeng per side
{{#invoke:UKB|rule|bilde|3|max=100|ownimage=10|initialimagelimit=0}} Det gis 3 poeng for hvert tilføyde bilde til sider som hadde maks 0 bilder fra før (10 for egenopplastede), men maks 100 poeng per side
{{#invoke:UKB|rule|ref|3|1}} Det gis 3 poeng for hver tilføyde kilde og 1 for hver henvisning til eksisterende (navngitt) kilde
{{#invoke:UKB|rule|ref|3|1|max=100}} Det gis 3 poeng for hver tilføyde kilde og 1 for hver henvisning til eksisterende (navngitt) kilde, men maks 100 poeng per side
{{#invoke:UKB|rule|malfjerning|10|Trenger referanse|Kildeløs}} Det gis 10 poeng for fjerning av {{Trenger referanse}} eller {{Kildeløs}}
{{#invoke:UKB|rule|ekstern lenke|3}} Det gis 3 poeng for innlegging av ekstern lenke
{{#invoke:UKB|rule|ekstern lenke|2|max=10}} Det gis 2 poeng for innlegging av ekstern lenke, men maks 10 poeng per side
{{#invoke:UKB|rule|seksjon|10|Referans[ea]r}} Det gis 10 poeng for tilføyelse av seksjonen «Referans[ea]r»
{{#invoke:UKB|rule|seksjon|10|Referans[ea]r|description=referanseseksjon}} Det gis 10 poeng for referanseseksjon
{{#invoke:UKB|rule|wikidata|2|properties=P18,P2096}} Det gis 2 poeng for tilføyelse av P18 eller P2096 på elementer som ikke har dette fra før
{{#invoke:UKB|rule|wikidata|2|properties=P569|require_reference=yes}} Det gis 2 poeng for tilføyelse av P569 på elementer som ikke har dette fra før (kun referansebelagte utsagn teller)
{{#invoke:UKB|rule|wikidata|2|max=10|properties=P569|all=yes}} Det gis 2 poeng for hver tilføyde P569, men maks 10 poeng per side
{{#invoke:UKB|rule|wikidata|2|labels=nb,nn,se}} Det gis 2 poeng for tilføyelse av Wikidata-etikett (nb, nn eller se) på elementer som ikke har dette fra før
{{#invoke:UKB|rule|wikidata|2|aliases=nb,nn,se}} Det gis 2 poeng for tilføyelse av Wikidata-alias (nb, nn eller se) på elementer som ikke har dette fra før
{{#invoke:UKB|rule|wikidata|2|descriptions=nb,nn,se,smn}} Det gis 2 poeng for tilføyelse av Wikidata-beskrivelse (nb, nn, se eller smn) på elementer som ikke har dette fra før

Se også rediger

--[ Localization and config ]-----------------------------------------------------------------

local messages = {
    ['and'] = 'og',
    ['or'] = 'eller',
    ['page_at_site'] = '%(page)s på %(site)s',
    ['argument_missing'] = 'Argument mangler: %s',
    ['anon_argument_missing'] = 'Ingen %s ble angitt',
    ['invalid_criterion'] = '«%s» er ikke et gyldig kriterium',
    ['invalid_rule'] = '«%s» er ikke en gyldig poengregel',

    -- Criteria

    ['templates'] = 'maler',
    ['templates_criterion_singular'] = 'merket med %s',
    ['templates_criterion_plural'] = 'merket med %s',

    ['categories'] = 'kategorier',
    ['categories_criterion_singular'] = 'ligger i kategorien %s',
    ['categories_criterion_plural'] = 'ligger i minst en av kategoriene %s',
    ['categories_criterion_ignore'] = ', men ikke i %s',

    ['backlinks'] = 'tilbakelenker',
    ['backlinks_criterion_singular'] = 'lenkes fra %s',
    ['backlinks_criterion_plural'] = 'lenkes fra %s',

    ['forwardlinks'] = 'framlenker',
    ['forwardlinks_criterion_singular'] = 'lenker til %s',
    ['forwardlinks_criterion_plural'] = 'lenker til %s',

    ['pages'] = 'sider',
    ['pages_criterion_singular'] = '%s',
    ['pages_criterion_plural'] = '%s',

    ['sparql_criterion'] = 'har et wikidata-element som matcher [%(queryLink)s denne SPARQL-spørringen]',
    ['sparql_criterion_with_explanation'] = '%(description)s ([%(queryLink)s Wikidata-spørring])',

    ['bytes_criterion'] = 'utvides med minst %s byte',

    ['namespaces_criterion_singular'] = 'er en %s',
    ['namespaces_criterion_plural'] = 'er en %s',
    ['article'] = 'artikkel',

    ['new_criterion'] = 'opprettes i løpet av konkurransen',
    ['new_criterion_with_redirects'] = 'opprettes i løpet av konkurransen (inkludert omdirigeringer)',

    ['existing_criterion'] = 'ble opprettet før konkurransen startet',

    -- Rules


    ['rule_site'] = '%(baserule)s på %(site)s',
    ['base_rule_max'] = '%(baserule)s, men maks %(maxpoints)s poeng per side',

    ['custom_rule'] = 'Det gis %(points)s poeng for %(description)s',
    ['newpage_rule'] = 'Det gis %(points)s poeng for opprettelse av side (ikke omdirigeringsside)',
    ['newredirect_rule'] = 'Det gis %(points)s poeng for opprettelse av omdirigeringsside',
    ['page_rule'] = 'Det gis %(points)s poeng for hver kvalifiserte side',
    ['edit_rule'] = 'Det gis %(points)s poeng for hver redigering',
    ['byte_rule'] = 'Det gis %(points)s poeng for hver tilføyde byte',
    ['listbyte_rule'] = 'Det gis %(points)s poeng for hver tilføyde byte i en listeartikkel',
    ['word_rule'] = 'Det gis %(points)s poeng for hvert tilføyde ord i brødtekst (ikke i maler, tabeller, o.l.)',

    ['image_rule'] = 'Det gis %(points)s poeng for hvert tilføyde bilde',
    ['image_rule_limited'] = 'Det gis %(points)s poeng for hvert tilføyde bilde til sider som hadde maks %(initialimagelimit)s bilder fra før',
    ['image_rule_own'] = '(%(ownimage)s for egenopplastede)',

    ['reference_rule'] = 'Det gis %(points)s poeng for hver tilføyde kilde og %(refpoints)s for hver henvisning til eksisterende (navngitt) kilde',

    ['templateremoval_rule'] = 'Det gis %(points)s poeng for fjerning av %(templates)s',
    ['categoryremoval_rule'] = 'Det gis %(points)s poeng for fjerning av %(categories)s',
    ['exlink_rule'] = 'Det gis %(points)s poeng for innlegging av [[WP:EL|ekstern lenke]]',

    ['section_rule'] = 'Det gis %(points)s poeng for tilføyelse av seksjonen «%(sections)s»',
    ['section_rule_desc'] = 'Det gis %(points)s poeng for tilføyelse av %(description)s',

    ['wikidata_rule_first'] = 'Det gis %(points)s poeng for tilføyelse av %(thing)s på elementer som ikke har dette fra før',
    ['wikidata_rule_all'] = 'Det gis %(points)s poeng for hver tilføyde %(thing)s',
    ['wikidata_rule_require_reference'] = '(kun referansebelagte utsagn teller)',
    ['properties'] = 'egenskaper',
    ['labels'] = 'etiketter',
    ['aliases'] = 'alias',
    ['descriptions'] = 'beskrivelser',
    ['label'] = 'Wikidata-etikett',
    ['alias'] = 'Wikidata-alias',
    ['description'] = 'Wikidata-beskrivelse',

    ['bytebonus_rule'] = 'Det gis %(points)s bonuspoeng når det tilføres mer enn %(bytes)s bytes til en side',
    ['wordbonus_rule'] = 'Det gis %(points)s bonuspoeng når det tilføres mer enn %(words)s ord til en side',
}

local config = {
    ['decimal_separator'] = ',',
    ['template_link_template'] = 'Mal',
    ['error_message_template'] = 'Feil',
    -- Map localized argument values for the criterion template
    ['criteria'] = {
        ['ny'] = 'new',
        ['eksisterende'] = 'existing',
        ['stubb'] = 'stub',  -- deprecated
        ['bytes'] = 'bytes',
        ['navnerom'] = 'namespaces',
        ['kategori'] = 'categories',
        ['mal'] = 'templates',
        ['tilbakelenke'] = 'backlinks',
        ['fremlenke'] = 'forwardlinks',
        ['enkeltsider'] = 'pages',
        ['sparql'] = 'sparql',
        ['distinct'] = 'distinct',
    },
    -- Localized argument values for the rule template
    ['rules'] = {
        ['ny'] = 'newpage',
        ['omdirigering'] = 'newredirect',
        ['kvalifisert'] = 'page',
        ['endring'] = 'edit',
        ['stubb'] = 'stubremoval',
        ['byte'] = 'byte',
        ['listebyte'] = 'listbyte',
        ['ord'] = 'word',
        ['bilde'] = 'image',
        ['ref'] = 'reference',
        ['bytebonus'] = 'bytebonus',
        ['ordbonus'] = 'wordbonus',
        ['malfjerning'] = 'templateremoval',
        ['seksjon'] = 'section',
        ['kategorifjerning'] = 'categoryremoval',
        ['ekstern lenke'] = 'exlink',
        ['wikidata'] = 'wikidata'
    }
}

local category_prefix = {
    ['smn'] = 'smn:Luokka',
    ['se'] = 'se:Kategoriija',
    ['nn'] = 'nn:Kategori',
    ['no'] = 'Kategori',
    ['commons'] = 'commons:Category',
    ['default'] = 'Kategori'
}

--[ Helper methods ] ------------------------------------------------------------------

--[[ Named Parameters with Formatting Codes
     Source: <http://lua-users.org/wiki/StringInterpolation>, author:RiciLake ]]
local function sprintf(s, tab)
    return (s:gsub('%%%((%a%w*)%)([-0-9%.]*[cdeEfgGiouxXsq])',
            function(k, fmt) return tab[k] and ("%"..fmt):format(tab[k]) or
                '%('..k..')'..fmt end))
end

local function make_error(template, arg)
    return string.format(
        '{{%s|%s}}',
        config['error_message_template'],
        string.format(messages[template], arg)
    )
end

local function parse_args(frame)
    local args = {}
    local kwargs = {}
    for k, v in pairs(frame.args) do
        v = mw.text.trim(frame:preprocess(v))
        if v ~= '' then
            if type(k) == 'number' then
                args[k] = v
            else
                kwargs[k] = v
            end
        end
    end
    return args, kwargs
end

local function shift_args(in_args)
    local args = {}
    for i, v in ipairs(in_args) do
        if i > 1 then
            args[i - 1] = v
        end
    end
    return in_args[1], args
end


local function format_plural(items, item_type)
    if #items == 0 then
        return make_error('anon_argument_missing', messages[item_type])
    end
    if #items == 1 then
        return items[1]
    end
    return mw.text.listToText(items, ', ', ' ' .. messages['or'] .. ' ')
end

local function format_plural_criterion(items, item_type)
    local value = format_plural(items, item_type)
    if #items == 0 then
        return value
    end
    if #items == 1 then
        return string.format(messages[item_type .. '_criterion_singular'], value)
    end
    return string.format(messages[item_type .. '_criterion_plural'], value)
end

local function make_template_list(args)
    local templates = {}
    for i, v in ipairs(args) do
        local lang, link = string.match(v, '^([a-z]+):(.+)$')
        if lang then
            table.insert(templates, string.format('{{%s|%s|%s}}', config['template_link_template'], link, lang))
        else
            table.insert(templates, string.format('{{%s|%s}}', config['template_link_template'], v))
        end
    end
    return templates
end

local function make_category_link(v)
    local lang = 'default'
    local name = v
    local m, n = string.match(v, '^([a-z]+):(.+)$')
    if m then
        lang = m
        name = n
    end
    return string.format('[[:%s:%s|%s]]', category_prefix[lang], name, name)
end

local function make_category_list(args)
    local category_links = {}
    for i, v in ipairs(args) do
        v = mw.text.trim(v)
        if v ~= '' then
            table.insert(category_links, make_category_link(v))
        end
    end
    return category_links
end

local function pagelist(args)
    local r = {}
    for i, v in ipairs(args) do
        v = mw.text.trim(v)
        if v ~= '' then
            local lang, page = string.match(v, '^([a-z]+):(.+)$')
            if lang then
                table.insert(r, string.format('[[:%s:%s|%s]]', lang, page, page))
            else
                table.insert(r, string.format('[[:%s]]', v))
            end
        end
    end
    return r
end

local function nslist(args)
    local r = {}
    local namespaceName = messages['article']
    for i, namespaceId in ipairs(args) do
        namespaceId = mw.text.trim(namespaceId)
        if namespaceId ~= '' then
            if namespaceId ~= "0" then
                namespaceName = '{{lc:{{ns:' .. namespaceId .. '}}}}'
            end
            table.insert(r, namespaceName)
        end
    end
    return r
end

--[ Criterion format methods ]-------------------------------------------------------------

local criterion = {}

function criterion.backlinks(args, kwargs, frame)
    return format_plural_criterion(pagelist(args), 'backlinks')
end

function criterion.bytes(args, kwargs, frame)
   return string.format(messages['bytes_criterion'], args[1])
end

function criterion.categories(args, kwargs, frame)
    local msg = format_plural_criterion(make_category_list(args), 'categories')

    if args.ignore ~= nil then
        r = mw.text.split(args.ignore, ',')
        for i, v in ipairs(r) do
            v = mw.text.trim(v)
            r[i] = make_category_link(v)
        end
        msg = msg .. string.format(messages['category_criterion_ignore'], mw.text.listToText(r, ', ', ' ' .. messages['or'] .. ' '))
    end

    return msg
end

function criterion.existing(args, kwargs, frame)
    return messages['existing_criterion']
end

function criterion.forwardlinks(args, kwargs, frame)
    return format_plural_criterion(pagelist(args), 'forwardlinks')
end

function criterion.namespaces(args, kwargs, frame)
    local site = kwargs.site
    local msg = format_plural_criterion(nslist(args, site), 'namespaces')
    if site ~= nil then
        return sprintf(messages['page_at_site'], {
            ['page'] = msg,
            ['site'] = string.format('[https://%s %s]', site, site),
        })
    end
    return msg
end

function criterion.new(args, kwargs, frame)
    local msg = messages['new_criterion']
    if kwargs.redirects ~= nil then
        msg = messages['new_criterion_with_redirects']
    end
    return msg
end

function criterion.pages(args, kwargs, frame)
    return format_plural_criterion(pagelist(args), 'pages')
end

function criterion.sparql(args, kwargs, frame)
	local query = ''
	if kwargs.distinct ~= nil then
		query = 'SELECT DISTINCT ?item WHERE {\n  ' .. kwargs.query .. '\n}'
	else
		query = 'SELECT ?item WHERE {\n  ' .. kwargs.query .. '\n}'
	end
    local url = 'http://query.wikidata.org/#' .. frame:callParserFunction('urlencode', { query, 'PATH' })
    local vizUrl = 'https://tools.wmflabs.org/hay/vizquery/#' .. frame:callParserFunction('urlencode', { query, 'PATH' })

    if kwargs.description ~= nil then
        return sprintf(messages['sparql_criterion_with_explanation'], {
            description = kwargs.description,
            queryLink = url,
            vizQueryLink = vizUrl
        })
    end

    return sprintf(messages['sparql_criterion'], {
        queryLink=url,
        vizQueryLink=vizUrl
    })
end

function criterion.stub(args, kwargs, frame)
    -- deprecated
    return messages['stub_criterion']
end

function criterion.templates(args, kwargs, frame)
    return format_plural_criterion(make_template_list(args), 'templates')
end

function criterion.format(frame)
    local args, kwargs = parse_args(frame)
    local criterion_arg, args = shift_args(args)

    -- Try to find the corresponding formatter or bail out if not found
    if criterion_arg == nil then
        return frame:preprocess(make_error('argument_missing', 'criterion'))
    end
    local formatter = config.criteria[criterion_arg]
    if formatter == nil or criterion[formatter] == nil then
        return frame:preprocess(make_error('invalid_criterion', criterion_arg))
    end

    -- Use manual description if given
    if kwargs.description ~= nil and formatter ~= 'sparql' then
        return kwargs.description
    end

    -- Generate auto-generated description
    return frame:preprocess(criterion[formatter](args, kwargs, frame))
end

--[ Rule format methods ]-------------------------------------------------------------

local rule = {}

function rule.custom(points, args, kwargs)
    return sprintf(messages['custom_rule'], {
        ['points'] = points,
        ['description'] = kwargs.description,
    })
end

function rule.image(points, args, kwargs)
    local out
    local tplargs = {
        ['points'] = points,
    }
    if kwargs.initialimagelimit ~= nil then
        out = messages['image_rule_limited']
        tplargs['initialimagelimit'] = kwargs.initialimagelimit
    else
        out = messages['image_rule']
    end
    if kwargs.ownimage ~= nil then
        out = out .. ' ' .. messages['image_rule_own']
        tplargs['ownimage'] = kwargs.ownimage
    end
    return sprintf(out, tplargs)
end

function rule.wikidata(points, args, kwargs)
    local out
    local params
    local arg_types = { messages['properties'], messages['labels'], messages['aliases'], messages['descriptions'] }
    local results = {}
    if kwargs.properties == nil and kwargs.labels == nil and kwargs.aliases == nil and kwargs.descriptions == nil then
        return make_error(
            'argument_missing',
            mw.text.listToText( arg_types, ', ', ' ' .. messages['or'] .. ' ' )
        )
    end
    if kwargs.properties ~= nil then
        params = mw.text.split(kwargs.properties, ',')
        for k, v in pairs(params) do
            params[k] = string.format('[[:d:Property:%s|%s]]', v, v)
        end
        table.insert(results, mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ))
    end
    if kwargs.labels ~= nil then
        params = mw.text.split(kwargs.labels, ',')
        table.insert(results, messages['label'] .. ' (' .. mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ) .. ')')
    end
    if kwargs.aliases ~= nil then
        params = mw.text.split(kwargs.aliases, ',')
        table.insert(results, messages['alias'] .. ' (' .. mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ) .. ')')
    end
    if kwargs.descriptions ~= nil then
        params = mw.text.split(kwargs.descriptions, ',')
        table.insert(results, messages['description'] .. ' (' .. mw.text.listToText( params, ', ', ' ' .. messages['or'] .. ' ' ) .. ')')
    end
    results = table.concat( results, ' ' .. messages['and'] .. ' ' )
    if kwargs.all ~= nil then
        out = messages['wikidata_rule_all']
    else
        out = messages['wikidata_rule_first']
    end
    if kwargs.require_reference ~= nil then
        out = out .. ' ' .. messages['wikidata_rule_require_reference']
    end
    return sprintf(out, {
        ['points'] = points,
        ['thing'] = results,
    })
end

function rule.reference(points, args, kwargs)
    return sprintf(messages['reference_rule'], {
        ['points'] = points,
        ['refpoints'] = args[1],
    })
end

function rule.templateremoval(points, args, kwargs)
    local templates = format_plural(make_template_list(args), 'templates')
    return sprintf(messages['templateremoval_rule'], {
        ['points'] = points,
        ['templates'] = templates,
    })
end

function rule.categoryremoval(points, args, kwargs)
    local categories = format_plural(make_category_list(args), 'categories')
    return sprintf(messages['categoryremoval_rule'], {
        ['points'] = points,
        ['categories'] = categories,
    })
end

function rule.section(points, args, kwargs)
    if kwargs.description ~= nil then
        return sprintf(messages['section_rule_desc'], {
            ['points'] = points,
            ['description'] = kwargs.description,
        })
    end
    local sections = format_plural(args, 'sections')
    return sprintf(messages['section_rule'], {
        ['points'] = points,
        ['sections'] = sections,
    })
end

function rule.bytebonus(points, args, kwargs)
    return sprintf(messages['bytebonus_rule'], {
        ['points'] = points,
        ['bytes'] = args[1],
    })
end

function rule.wordbonus(points, args, kwargs)
    return sprintf(messages['wordbonus_rule'], {
        ['points'] = points,
        ['words'] = args[1],
    })
end

function rule.format(frame)
    -- Make tables of anonymous and named arguments
    local args, kwargs = parse_args(frame)
    rule_arg, args = shift_args(args)
    points, args = shift_args(args)

    -- Try to find the corresponding formatter or bail out if not found
    if rule_arg == nil then
        return frame:preprocess(make_error('argument_missing', 'rule'))
    end
    local formatter = config.rules[rule_arg]
    if formatter == nil then
        return frame:preprocess(make_error('invalid_rule', rule_arg))
    end
    if kwargs.description ~= nil then
        formatter = 'custom'
    end

    -- All rules requires argument 1: number of points awarded
    if points == nil then
        return frame:preprocess(make_error('argument_missing', '1 (number of points)'))
    end

    points = points:gsub( '%.', config['decimal_separator'])

    -- If there's a rule formatter function, use it.
    -- Otherwise, use the string from the messages table.
    local out
    if rule[formatter] ~= nil then
        out = rule[formatter](points, args, kwargs)
    else
        out = sprintf(messages[formatter .. '_rule'], {
            ['points'] = points,
        })
    end

    if kwargs.site ~= nil then
        out = sprintf(messages['rule_site'], {
            ['baserule'] = out,
            ['site'] = string.format('[https://%s %s]', kwargs.site, kwargs.site),
        })
    end

    if kwargs.max ~= nil then
        out = sprintf(messages['base_rule_max'], {
            ['baserule'] = out,
            ['maxpoints'] = kwargs.max:gsub( '%.', config['decimal_separator']),
        })
    end

    return frame:preprocess(out)
end

-- Export
return {
    ['criterion'] = criterion.format,
    ['rule'] = rule.format,
}