Jump to content

Module:Formatnum/sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Trigenibinion (talk | contribs) at 23:24, 27 February 2021 (Second version of declension handling). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
-- This module is intended to replace the functionality of Template:Formatnum and related templates. 
local p = {} -- Holds functions to be returned from #invoke, and functions to make available to other Lua modules.

--[[
Helper functions used to avoid redundant code.
]]

local function err(msg)
    -- Generates wikitext error messages.
    return mw.ustring.format('<strong class="error">Formatting error: %s</strong>', msg)
end

local function getArgs(frame)
    local args = {}
    for key, value in pairs(frame:getParent().args) do
        args[key] = value
    end
    for key, value in pairs(frame.args) do
        args[key] = value
    end
    return args
end

local function _round(value, precision)
    local rescale = math.pow(10, precision or 0);
    return math.floor(value * rescale + 0.5) / rescale;
end

--[[
------------------------------------------------------------------------------------
-- isPositiveInteger
--
-- This function returns true if the given value is a positive integer, and false
-- if not. Although it doesn't operate on tables, it is included here as it is
-- useful for determining whether a given table key is in the array part or the
-- hash part of a table.
------------------------------------------------------------------------------------
--]]
function isPositiveInteger(v)
    return type(v) == 'number' and v >= 1 and math.floor(v) == v and v < math.huge
end


--[[
------------------------------------------------------------------------------------
-- reverseNumKeys
--
-- This takes a table and returns an array containing the numbers of any numerical
-- keys that have non-nil values, sorted in reverse numerical order.
------------------------------------------------------------------------------------
--]]
local function reverseNumKeys(t)
    local nums = {}
    for k, v in pairs(t) do
        if isPositiveInteger(k) then
            nums[#nums + 1] = k
        end
    end
    table.sort(nums, function(a, b) return a > b end)
    return nums
end

--[[
------------------------------------------------------------------------------------
-- reverseSparseIpairs
--
-- This is a reverse iterator for sparse arrays. It can be used like a resersed ipairs, but can
-- handle nil values.
------------------------------------------------------------------------------------
--]]
local function reverseSparseIpairs(t)
    local nums = reverseNumKeys(t)
    local i = 0
    local lim = #nums
    return function ()
        i = i + 1
        if i <= lim then
            local key = nums[i]
            return key, t[key]
        else
            return nil, nil
        end
    end
end


function p.main(frame)
    local args = getArgs(frame)
    local prec    = args.prec or ''
    local sep     = args.sep or ''
    local number  = args[1] or args.number or ''
    local lang    = args[2] or args.lang or ''
    -- validate the language parameter within MediaWiki's caller frame
    if lang:lower() == "none" then
        -- no language, so do nothing
    elseif lang == "arabic-indic" then -- only for back-compatibility ("arabic-indic" is not a SupportedLanguage)
        lang = "fa" -- better support than "ks"
    elseif lang == '' or not mw.language.isSupportedLanguage(lang) then
        -- Note that 'SupportedLanguages' are not necessarily 'BuiltinValidCodes', and so they are not necessarily
        -- 'KnownLanguages' (with a language name defined at least in the default localisation of the local wiki).
        -- But they all are ValidLanguageCodes (suitable as Wiki subpages or identifiers: no slash, colon, HTML tags, or entities)
        -- In addition, they do not contain any capital letter in order to be unique in page titles (restriction inexistant in BCP47),
        -- but they may violate the standard format of BCP47 language tags for specific needs in MediaWiki.
        -- Empty/unspecified and unsupported languages are treated here in Commons using the user's language,
        -- instead of the local 'ContentLanguage' of the Wiki.
        lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language
        if not mw.language.isSupportedLanguage(lang) then
            lang = mw.language.getContentLanguage().code
        end
    end
    return p.formatNum(number, lang, prec, sep ~= '')
end

local digit = { -- substitution of decimal digits for languages not supported by mw.language:formatNum() in core Lua libraries for MediaWiki
    ["ml-old"] = { '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯' },
    ["mn"]     = { '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙'},
    ["ta"]     = { '௦', '௧', '௨', '௩', '௪', '௫', '௬', '௭', '௮', '௯'},
    ["te"]     = { '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯'},
    ["th"]     = { '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙'}
}

function p.formatNum(number, lang, prec, compact)
    -- Do not alter the specified value when it is not a valid number, return it as is
    local value = tonumber(number)
    if value == nil then
        return number
    end
    -- Basic ASCII-only formatting (without paddings)
    number = tostring(value)

    -- Check the presence of an exponent (incorrectly managed in mw.language:FormatNum() and even forgotten due to an internal bug, e.g. in Hindi)
    local exponent
    local pos = string.find(number, '[Ee]')
    if pos ~= nil then
        exponent = string.sub(number, pos + 1, string.len(number))
        number = string.sub(number, 1, pos - 1)
    else
        exponent = ''
    end

    -- Check the minimum precision requested
    prec = tonumber(prec) -- nil if not specified as a true number
    if prec ~= nil then
        prec = math.floor(prec)
        if prec < 0 then
            prec = nil -- discard an incorrect precision (not a positive integer)
        elseif prec > 14 then
            prec = 14 -- maximum precision supported by tostring(number)
        end
    end

    -- Preprocess the minimum precision in the ASCII string
    local dot
    if (prec or 0) > 0 then
        pos = string.find(number, '.', 1, true) -- plain search, no regexp
        if pos ~= nil then
            prec = pos + prec - string.len(number) -- effective number of trailing decimals to add or remove
            dot = '' -- already present
        else
            dot = '.' -- must be added
        end
    else
        dot = '' -- don't add dot
        prec = 0 -- don't alter the precision
    end
    
    if lang ~= nil and mw.language.isKnownLanguageTag(lang) == true then
        -- Convert number to localized digits, decimal separator, and group separators
        local language = mw.getLanguage(lang)
        if compact then
            number = language:formatNum(tonumber(number), { noCommafy = 'y' }) -- caveat: can load localized resources for up to 20 languages
        else
            number = language:formatNum(tonumber(number)) -- caveat: can load localized resources for up to 20 languages
        end
        -- Postprocessing the precision
        if prec > 0 then
            local zero = language:formatNum(0)
            number = number .. dot .. mw.ustring.rep(zero, prec)
        elseif prec < 0 then
            -- TODO: rounding of last decimal; here only truncate decimals in excess
            number = mw.ustring.sub(number, 1, mw.ustring.len(number) + prec)
        end
        -- Append the localized base-10 exponent without grouping separators (there's no reliable way to detect a localized leading symbol 'E')
        if exponent ~= '' then
            number = number .. 'E' .. language:formatNum(tonumber(exponent),{noCommafy=true})
        end
    else -- not localized, ASCII only
        -- Postprocessing the precision
        if prec > 0 then
            number = number .. dot .. mw.string.rep('0', prec)
        elseif prec < 0 then
            -- TODO: rounding of last decimal; here only truncate decimals in excess
            number = mw.string.sub(number, 1, mw.string.len(number) + prec)
        end
        -- Append the base-10 exponent
        if exponent ~= '' then
            number = number .. 'E' .. exponent
        end
    end

    -- Special cases for substitution of ASCII digits (missing support in Lua core libraries for some languages)
    if digit[lang] then
        for i, v in ipairs(digit[lang]) do
            number = mw.ustring.gsub(number, tostring(i - 1), v)
        end
    end

    return number
end

--[[
wordify

Usage:
{{#invoke:Formatnum | wordify | x | y | round= | precision= }}

--]]

local usa = {
    [6] = 'million',
    [9] = 'billion',
    [12] = 'trillion',
    [15] = 'quadrillion',
    [18] = 'quintillion',
    [21] = 'sextillion',
    [24] = 'septillion',
    [27] = 'octillion',
    [30] = 'nonillion',
    [33] = 'decillion',
    [36] = 'undecillion',
    [39] = 'duodecillion',
    [42] = 'tredecillion',
    [45] = 'quattuordecillion',
    [48] = 'quindecillion',
    [51] = 'sexdecillion',
    [54] = 'septendecillion',
    [57] = 'octodecillion',
    [60] = 'novemdecillion',
    [63] = 'vigintillion',
    [100] = 'googol',
    [303] = 'centillion'
}

local ind = {
    [5] = 'lakh',
    [7] = 'crore',
    [12] = 'lakh crore',
    [14] = 'crore crore'
}

local declension = {
    ['pl'] = { '', 'y', [5] = 'ów', ["fraction"] = 'a' }
}

local latin = {
    [6] = {
        ['']  = { 'million' },
		['ca'] = { 'milió', 'milions' },
        ['da'] = { 'million', 'millioner' },
        ['de'] = { 'Million', 'Millionen' },
        ['en'] = { 'million' },
		['eo'] = { 'miliono', 'milionoj' },
        ['es'] = { 'millón', 'millones' },
        ['fr'] = { 'million', 'millions' },
        ['it'] = { 'milione', 'milioni' },
        ['la'] = { 'millio', 'millionibus' },
        ['nl'] = { 'miljoen', 'miljoenen' },
		['no'] = { 'million', 'millioner' },
        ['pl'] = { 'milion', 'miliony', [5] = 'milionów', ["fraction"] = 'miliona' },
        ['pt'] = { 'milhão', 'milhões' },
		['sk'] = { 'milión', 'miliónov' },
		['sl'] = { 'milijon', 'milijonov' },
        ['sv'] = { 'miljon', 'miljoner' }
    },
    [9] = {
        ['']  = { 'milliard' },
	    ['ca'] = { 'miliard', 'miliards' },
        ['da'] = { 'milliard', 'milliarder' },
        ['de'] = { 'Milliarde', 'Milliarden' },
        ['en'] = { 'milliard' },
		['eo'] = { 'miliardo', 'miliardoj' },
        ['es'] = { 'millardo', 'millardos' },
        ['fr'] = { 'milliard', 'milliards' },
        ['it'] = { 'miliardo', 'miliardi' },
        ['la'] = { 'millliardum', 'milliarda' },
        ['nl'] = { 'miljard', 'miljard' },
		['no'] = { 'milliard', 'milliarder' },
        ['pl'] = { 'miliard', 'miliardy', [5] = 'miliardów', ["fraction"] = 'miliarda' },
        ['pt'] = { 'mil milhões', 'mil milhões', ["simplify"] = true },
		['sk'] = { 'miliarda', 'miliárd' },
		['sl'] = { 'milijarda', 'milijard' },
        ['sv'] = { 'miljard', 'miljarder' }
    },
    [12] = {
        ['']  = { 'billion' },
		['ca'] = { 'bilió', 'bilions' },
        ['da'] = { 'billion', 'billioner' },
        ['de'] = { 'Billion', 'Billionen' },
        ['en'] = { 'billion' },
		['eo'] = { 'duiliono', 'duilionoj' },
        ['es'] = { 'billón', 'billones' },
        ['fr'] = { 'billion', 'billions' },
        ['it'] = { 'bilione', 'bilioni' },
        ['la'] = { 'billio', 'billionibus' },
        ['nl'] = { 'biljoen', 'biljoen' },
		['no'] = { 'billion', 'billioner' },
        ['pl'] = { 'bilion', 'biliony', [5] = 'bilionów', ["fraction"] = 'biliona' },
        ['pt'] = { 'bilião', 'biliões' },
		['sk'] = { 'bilión', 'biliónov' },
		['sl'] = { 'bilijon', 'bilijonov' },
        ['sv'] = { 'biljon', 'biljoner' }
    },
    [15] = {
        ['']  = { 'billiard' },
		['ca'] = { 'biliard', 'biliards' },
        ['da'] = { 'billiard', 'billiarder' },
        ['de'] = { 'Billiarde', 'Billiarden' },
        ['en'] = { 'billiard' },
		['eo'] = { 'duiliardo', 'duiliardoj' },
        ['es'] = { 'mil billones', 'mil billones', ["simplify"] = true },
        ['fr'] = { 'billiard', 'billiards' },
        ['it'] = { 'biliardo', 'biliardi' },
        ['la'] = { 'billliardum', 'billiarda' },
        ['nl'] = { 'biljard', 'biljard' },
		['no'] = { 'billiard', 'billiarder' },
        ['pl'] = { 'biliard', 'biliardy', [5] = 'biliardów', ["fraction"] = 'biliarda' },
        ['pt'] = { 'mil biliões', 'mil biliões', ["simplify"] = true },
		['sk'] = { 'biliarda', 'biliárd' },
		['sl'] = { 'bilijarda', 'bilijard' },
        ['sv'] = { 'biljard', 'biljarder' }
    },
    [18] = {
        ['']  = { 'trillion' },
		['ca'] = { 'trilió', 'trilions' },
        ['da'] = { 'trillion', 'trillioner' },
        ['de'] = { 'Trillion', 'Trillionen' },
        ['en'] = { 'trillion' },
		['eo'] = { 'triliono', 'trilionoj' },
        ['es'] = { 'trillón', 'trillones' },
        ['fr'] = { 'trillion', 'trillions' },
        ['it'] = { 'trilione', 'trilioni' },
        ['la'] = { 'trillio', 'trillionibus' },
        ['nl'] = { 'triljoen', 'triljoen' },
		['no'] = { 'trillion', 'trillioner' },
        ['pl'] = { 'trylion', 'tryliony', [5] = 'trylionów', ["fraction"] = 'tryliona' },
        ['pt'] = { 'trilião', 'triliões' },
		['sk'] = { 'trilión', 'triliónov' },
		['sl'] = { 'trilijon', 'trilijonov' },
        ['sv'] = { 'triljon', 'triljoner' }
    },
    [21] = {
        ['']  = { 'trilliard' },
		['ca'] = { 'triliard', 'triliards' },
        ['da'] = { 'trilliard', 'trilliarder' },
        ['de'] = { 'Trilliarde', 'Trilliarden' },
        ['en'] = { 'trilliard' },
		['eo'] = { 'triliardo', 'triliardoj' },
        ['es'] = { 'mil trillones', 'mil trillones', ["simplify"] = true },
        ['fr'] = { 'trilliard', 'trilliards' },
        ['it'] = { 'triliardo', 'triliardi' },
        ['la'] = { 'trillliardum', 'trilliarda' },
        ['nl'] = { 'triljard', 'triljard' },
		['no'] = { 'trilliard', 'trilliarder' },
        ['pl'] = { 'tryliard', 'tryliardy', [5] = 'tryliardów', ["fraction"] = 'tryliarda' },
        ['pt'] = { 'mil triliões', 'mil triliões', ["simplify"] = true },
		['sk'] = { 'triliarda', 'triliárd' },
		['sl'] = { 'trilijarda', 'trilijard' },
        ['sv'] = { 'triljard', 'triljarder' }
    },
    [24] = {
        ['']  = { 'quadrillion' },
		['ca'] = { 'quadrilió', 'quadrilions' },
        ['da'] = { 'kvadrillion', 'kvadrillioner' },
        ['de'] = { 'Quadrillion', 'Quadrillionen' },
        ['en'] = { 'quadrillion' },
		['eo'] = { 'kvariliono', 'kvarilionoj' },
        ['es'] = { 'cuatrillón', 'cuatrillones' },
        ['fr'] = { 'quadrillion', 'quadrillions' },
        ['it'] = { 'quadrilione', 'quadrilioni' },
        ['la'] = { 'quadrillio', 'quadrillionibus' },
        ['nl'] = { 'quadriljoen', 'quadriljoen' },
		['no'] = { 'kvadrillion', 'kvadrillioner' },
        ['pl'] = { 'kwadrylion', 'kwadryliony', [5] = 'kwadrylionów', ["fraction"] = 'kwadryliona' },
        ['pt'] = { 'quadrilião', 'quadriliões' },
		['sk'] = { 'kvadrilión', 'kvadriliónov' },
		['sl'] = { 'kvadrilijon', 'kvadrilijonov' },
        ['sv'] = { 'kvadriljon', 'kvadriljoner' }
    },
    [27] = {
        ['']  = { 'quadrilliard' },
		['ca'] = { 'quadriliard', 'quadriliards' },
        ['da'] = { 'kvadrilliard', 'kvadrilliarder' },
        ['de'] = { 'Quadrilliarde', 'Quadrilliarden' },
        ['en'] = { 'quadrilliard' },
		['eo'] = { 'kvariliardo', 'kvariliardoj' },
        ['es'] = { 'mil cuatrillones', 'mil cuatrillones', ["simplify"] = true },
        ['fr'] = { 'quadrilliard', 'quadrilliards' },
        ['it'] = { 'quadriliardo', 'quadriliardi' },
        ['la'] = { 'quadrillliardum', 'quadrilliarda' },
        ['nl'] = { 'quadriljard', 'quadriljard' },
		['no'] = { 'kvadrilliard', 'kvadrilliarder' },
        ['pl'] = { 'kwadryliard', 'kwadryliardy', [5] = 'kwadryliardów', ["fraction"] = 'kwaddryliarda' },
        ['pt'] = { 'mil quadriliões', 'mil quadriliões', ["simplify"] = true },
		['sk'] = { 'kvadriliarda', 'kvadriliárd' },
		['sl'] = { 'kvadrilijarda', 'kvadrilijard' },
        ['sv'] = { 'kvadriljard', 'kvadriljarder' }
    },
    [30] = {
        ['']  = { 'quintillion' },
		['ca'] = { 'quintilió', 'quintilions' },
        ['da'] = { 'kvintillion', 'kvintillioner' },
        ['de'] = { 'Quintillion', 'Quintillionen' },
        ['en'] = { 'quintillion' },
		['eo'] = { 'kviniliono', 'kvinilionoj' },
        ['es'] = { 'quintillón', 'quintillones' },
        ['fr'] = { 'quintillion', 'quintillions' },
        ['it'] = { 'quintilione', 'quintilioni' },
        ['la'] = { 'quintillio', 'quintillionibus' },
        ['nl'] = { 'quintiljoen', 'quintiljoen' },
		['no'] = { 'kvintillion', 'kvintillioner' },
        ['pl'] = { 'kwintylion', 'kwintyliony', [5] = 'kwintylionów', ["fraction"] = 'kwintyliona' },
        ['pt'] = { 'quintilião', 'quintiliões' },
		['sk'] = { 'kvintilión', 'kvintiliónov' },
		['sl'] = { 'kvintilijon', 'kvintilijonov' },
        ['sv'] = { 'kvintiljon', 'kvintiljoner' }
    },
    [33] = {
        ['']  = { 'quintilliard' },
		['ca'] = { 'quintiliard', 'quintiliards' },
        ['da'] = { 'kvintilliard', 'kvintilliarder' },
        ['de'] = { 'Quintilliarde', 'Quintilliarden' },
        ['en'] = { 'quintilliard' },
		['eo'] = { 'kviniliardo', 'kviniliardoj' },
        ['es'] = { 'mil quintillones', 'mil quintillones', ["simplify"] = true },
        ['fr'] = { 'quintilliard', 'quintilliards' },
        ['it'] = { 'quintiliardo', 'quintiliardi' },
        ['la'] = { 'quintillliardum', 'quintilliarda' },
        ['nl'] = { 'quintiljard', 'quintiljard' },
		['no'] = { 'kvintilliard', 'kvintilliarder' },
        ['pl'] = { 'kwintyliard', 'kwintyliardy', [5] = 'kwintyliardów', ["fraction"] = 'kwintyliarda' },
        ['pt'] = { 'mil quintiliões', 'mil quintiliões', ["simplify"] = true },
		['sk'] = { 'kvintiliarda', 'kvintiliárd' },
		['sl'] = { 'kvintilijarda', 'kvintilijard' },
        ['sv'] = { 'kvintiljard', 'kvintiljarder' }
    },
    [36] = {
        ['']  = { 'sextillion' },
		['ca'] = { 'sextilió', 'sextilions' },
        ['da'] = { 'sekstillion', 'sekstillioner' },
        ['de'] = { 'Sextillion', 'Sextillionen' },
        ['en'] = { 'sextillion' },
		['eo'] = { 'sesiliono', 'sesilionoj' },
        ['es'] = { 'sextillón', 'sextillones' },
        ['fr'] = { 'sextillion', 'sextillions' },
        ['la'] = { 'sextillio', 'sextillionibus' },
        ['nl'] = { 'sextiljoen', 'sextiljoen' },
		['no'] = { 'sekstillion', 'sekstillioner' },
        ['pl'] = { 'sekstylion', 'sekstyliony', [5] = 'sekstylionów', ["fraction"] = 'sekstyliona' },
        ['pt'] = { 'sextilião', 'sextiliões' },
		['sk'] = { 'sextilión', 'sextiliónov' },
		['sl'] = { 'sekstilijon', 'sekstilijonov' },
        ['sv'] = { 'sextiljon', 'sextiljoner' }
    },
    [39] = {
        ['']  = { 'sextilliard' },
		['ca'] = { 'sextiliard', 'sextiliards' },
        ['da'] = { 'sekstilliard', 'sekstilliarder' },
        ['de'] = { 'Sextilliarde', 'Sextilliarden' },
        ['en'] = { 'sextilliard' },
		['eo'] = { 'sesiliardo', 'sesiliardoj' },
        ['es'] = { 'mil sextillones', 'mil sextillones', ["simplify"] = true },
        ['fr'] = { 'sextilliard', 'sextilliards' },
        ['la'] = { 'sextillliardum', 'sextilliarda' },
        ['nl'] = { 'sextiljard', 'sextiljard' },
		['no'] = { 'sekstilliard', 'sekstilliarder' },
        ['pl'] = { 'sekstyliard', 'sekstyliardy', [5] = 'sekstyliardów', ["fraction"] = 'sekstyliarda' },
        ['pt'] = { 'mil sextiliões', 'mil sextiliões', ["simplify"] = true },
		['sk'] = { 'sextiliarda', 'sextiliárd' },
		['sl'] = { 'sekstilijarda', 'sekstilijard' },
        ['sv'] = { 'sextiljard', 'sextiljarder' }
    },
    [42] = {
        ['']  = { 'septillion' },
		['ca'] = { 'septilió', 'septilions' },
        ['da'] = { 'septillion', 'septillioner' },
        ['de'] = { 'Septillion', 'Septillionen' },
        ['en'] = { 'septillion' },
		['eo'] = { 'sepiliono', 'sepilionoj' },
        ['es'] = { 'septillón', 'septillones' },
        ['fr'] = { 'septillion', 'septillions' },
        ['la'] = { 'septillio', 'septillionibus' },
        ['nl'] = { 'septiljoen', 'septiljoen' },
		['no'] = { 'septillion', 'septillioner' },
        ['pl'] = { 'septylion', 'septyliony', [5] = 'septylionów', ["fraction"] = 'septyliona' },
        ['pt'] = { 'septilião', 'septiliões' },
		['sk'] = { 'septilión', 'septiliónov' },
		['sl'] = { 'septilijon', 'septilijonov' },
        ['sv'] = { 'septiljon', 'septiljoner' }
    },
    [45] = {
        ['']  = { 'septilliard' },
		['ca'] = { 'septiliard', 'septiliards' },
        ['da'] = { 'septilliard', 'septilliarder' },
        ['de'] = { 'Septilliarde', 'Septilliarden' },
        ['en'] = { 'septilliard' },
		['eo'] = { 'sepiliardo', 'sepiliardoj' },
        ['es'] = { 'mil septillones', 'mil septillones', ["simplify"] = true },
        ['fr'] = { 'septilliard', 'septilliards' },
        ['la'] = { 'septillliardum', 'septilliarda' },
        ['nl'] = { 'septiljard', 'septiljard' },
		['no'] = { 'septilliard', 'septilliarder' },
        ['pl'] = { 'septyliard', 'septyliardy', [5] = 'septyliardów', ["fraction"] = 'septyliarda' },
        ['pt'] = { 'mil septiliões', 'mil septiliões', ["simplify"] = true },
		['sk'] = { 'septiliarda', 'septiliárd' },
		['sl'] = { 'septilijarda', 'septilijard' },
        ['sv'] = { 'septiljard', 'septiljarder' }
    },
    [48] = {
        ['']  = { 'octillion' },
		['ca'] = { 'octilió', 'octilions' },
        ['da'] = { 'oktillion', 'oktillioner' },
        ['de'] = { 'Oktillion', 'Oktillionen' },
        ['en'] = { 'octillion' },
		['eo'] = { 'okiliono', 'okilionoj' },
        ['es'] = { 'octillón', 'octillones' },
        ['fr'] = { 'octillion', 'octillions' },
        ['la'] = { 'octillio', 'octillionibus' },
        ['nl'] = { 'octiljoen', 'octiljoen' },
		['no'] = { 'oktillion', 'oktillioner' },
        ['pl'] = { 'oktylion', 'oktyliony', [5] = 'oktylionów', ["fraction"] = 'oktyliona' },
        ['pt'] = { 'octilião', 'octiliões' },
		['sk'] = { 'oktilión', 'oktiliónov' },
		['sl'] = { 'oktilijon', 'oktilijonov' },
        ['sv'] = { 'oktiljon', 'oktiljoner' }
    },
    [51] = {
        ['']  = { 'octilliard' },
		['ca'] = { 'octiliard', 'octiliards' },
        ['da'] = { 'oktilliard', 'oktilliarder' },
        ['de'] = { 'Oktilliarde', 'Oktilliarden' },
        ['en'] = { 'octilliard' },
		['eo'] = { 'okiliardo', 'okiliardoj' },
        ['es'] = { 'mil octillones', 'mil octillones', ["simplify"] = true },
        ['fr'] = { 'octilliard', 'octilliards' },
        ['la'] = { 'octillliardum', 'octilliarda' },
        ['nl'] = { 'octiljard', 'octiljard' },
		['no'] = { 'oktilliard', 'oktilliarder' },
        ['pl'] = { 'oktyliard', 'oktyliardy', [5] = 'oktyliardów', ["fraction"] = 'oktyliarda' },
        ['pt'] = { 'mil octiliões', 'mil octiliões', ["simplify"] = true },
		['sk'] = { 'oktiliarda', 'oktiliárd' },
		['sl'] = { 'oktilijarda', 'oktilijard' },
        ['sv'] = { 'oktiljard', 'oktiljarder' }
    },
    [54] = {
        ['']  = { 'nonillion' },
		['ca'] = { 'nonilió', 'nonilions' },
        ['da'] = { 'nonillion', 'nonillioner' },
        ['de'] = { 'Nonillion', 'Nonillionen' },
        ['en'] = { 'nonillion' },
		['eo'] = { 'naŭiliono', 'naŭilionoj' },
        ['es'] = { 'nonillón', 'nonillones' },
        ['fr'] = { 'nonillion', 'nonillions' },
        ['la'] = { 'nonillio', 'nonillionibus' },
        ['nl'] = { 'noniljoen', 'noniljoen' },
		['no'] = { 'nonillion', 'nonillioner' },
        ['pl'] = { 'nonilion', 'noniliony', [5] = 'nonilionów', ["fraction"] = 'noniliona' },
        ['pt'] = { 'nonilião', 'noniliões' },
		['sk'] = { 'nonilión', 'noniliónov' },
		['sl'] = { 'nonilijon', 'nonilijonov' },
        ['sv'] = { 'noniljon', 'noniljoner' }
    },
    [57] = {
        ['']  = { 'nonilliard' },
		['ca'] = { 'noniliard', 'noniliards' },
        ['da'] = { 'nonilliard', 'nonilliarder' },
        ['de'] = { 'Nonilliarde', 'Nonilliarden' },
        ['en'] = { 'nonilliard' },
		['eo'] = { 'naŭiliardo', 'naŭiliardoj' },
        ['es'] = { 'mil nonillones', 'mil nonillones', ["simplify"] = true },
        ['fr'] = { 'nonilliard', 'nonilliards' },
        ['la'] = { 'nonillliardum', 'nonilliarda' },
        ['nl'] = { 'noniljard', 'noniljard' },
		['no'] = { 'nonilliard', 'nonilliarder' },
        ['pl'] = { 'noniliard', 'noniliardy', [5] = 'noniliardów', ["fraction"] = 'noniliarda' },
        ['pt'] = { 'mil noniliões', 'mil noniliões', ["simplify"] = true },
		['sk'] = { 'noniliarda', 'noniliárd' },
		['sl'] = { 'nonilijarda', 'nonilijard' },
        ['sv'] = { 'noniljard', 'noniljarder' }
    },
    [60] = {
        ['']  = { 'decillion' },
		['ca'] = { 'decilió', 'decilions' },
        ['da'] = { 'decillion', 'decillioner' },
        ['de'] = { 'Dezillion', 'Dezillionen' },
        ['en'] = { 'decillion' },
		['eo'] = { 'dekiliono ', 'dekilionoj' },
        ['es'] = { 'decillón', 'decillones' },
        ['fr'] = { 'décillions', 'décillions' },
        ['it'] = { 'decilione', 'decilioni' },
        ['la'] = { 'decillio', 'decillionibus' },
        ['nl'] = { 'deciljoen', 'deciljoen' },
		['no'] = { 'desillion', 'desillioner' },
        ['pl'] = { 'decylion', 'decyliony', [5] = 'decylionów', ["fraction"] = 'decyliona' },
        ['pt'] = { 'decilião', 'deciliões' },
		['sk'] = { 'decilión', 'deciliónov' },
		['sl'] = { 'decilijon', 'decilijonov' },
        ['sv'] = { 'deciljon', 'deciljoner' }
    },
    [63] = {
        ['']  = { 'decilliard' },
		['ca'] = { 'deciliard', 'deciliards' },
        ['da'] = { 'decilliard', 'decilliarder' },
        ['de'] = { 'Dezilliarde', 'Dezilliarden' },
        ['en'] = { 'decilliard' },
		['eo'] = { 'dekiliardo', 'dekiliardoj' },
        ['es'] = { 'mil decillones', 'mil decillones', ["simplify"] = true },
        ['fr'] = { 'décilliard', 'décilliards' },
        ['la'] = { 'decillliardum', 'decilliarda' },
        ['nl'] = { 'deciljard', 'deciljard' },
		['no'] = { 'desilliard', 'desilliarder' },
        ['pl'] = { 'decyliard', 'decyliardy', [5] = 'decyliardów', ["fraction"] = 'decyliarda' },
        ['pt'] = { 'mil deciliões', 'mil deciliões', ["simplify"] = true },
		['sk'] = { 'deciliarda', 'deciliárd' },
		['sl'] = { 'decilijarda', 'decilijard' },
        ['sv'] = { 'deciljard', 'deciljarder' }
    }
}

local function unit(arr, i, lang, r)
    if r == 1 then
        return arr[i][(lang or "")][1]
    elseif r == 2 then
        if declension[lang] and declension[lang][2] then
            return arr[i][(lang or "")][1] .. declension[lang][2]
        elseif arr[i][(lang or "")][2] then
            return arr[i][(lang or "")][2]
        else
            return arr[i][(lang or "")][1]
        end
    elseif r == 3 or r == 4 then
        if declension[lang] and declension[lang][3] then
            return arr[i][(lang or "")][1] .. declension[lang][3]
        elseif arr[i][(lang or "")][3] then
            return arr[i][(lang or "")][3]
        elseif declension[lang] and declension[lang][2] then
            return arr[i][(lang or "")][1] .. declension[lang][2]
        elseif arr[i][(lang or "")][2] then
            return arr[i][(lang or "")][2]
        else
            return arr[i][(lang or "")][1]
        end
    elseif r == _round(r, 0) then
        if declension[lang] and declension[lang][5] then
            return arr[i][(lang or "")][1] .. declension[lang][5]
        elseif arr[i][(lang or "")][5] then
            return arr[i][(lang or "")][5]
        elseif declension[lang] and declension[lang][3] then
            return arr[i][(lang or "")][1] .. declension[lang][3]
        elseif arr[i][(lang or "")][3] then
            return arr[i][(lang or "")][3]
        elseif declension[lang] and declension[lang][2] then
            return arr[i][(lang or "")][1] .. declension[lang][2]
        elseif arr[i][(lang or "")][2] then
            return arr[i][(lang or "")][2]
        else
            return arr[i][(lang or "")][1]
        end
    else
        if declension[lang] and declension[lang]["fraction"] then
            return arr[i][(lang or "")][1] .. declension[lang]["fraction"]
        elseif arr[i][(lang or "")]["fraction"] then
            return arr[i][(lang or "")]["fraction"]
        elseif arr[i][(lang or "")][2] then
            return (r < 2 and arr[i][(lang or "")][1] or arr[i][(lang or "")][2])
        else
            return arr[i][(lang or "")][1]
        end
    end
end

local function linkend(u)
    return "|" .. u .. "]]"
end

local function unitlink(lk, i, u, lang)
    if lk then
		if lang == "ca" then
            return "[[ca:" .. "Escales curta i llarga" .. linkend(u)
        elseif lang == "da" then
            return "[[da:" .. "Store tal" .. linkend(u)
        elseif lang == "de" then
            return "[[de:" .. "Zahlennamen" .. linkend(u)
		elseif lang == "eo" then
            return "[[eo:" .. "Vortoj por grandegaj nombroj" .. linkend(u)
        elseif lang == "fr" then
            return "[[fr:" .. (i > 36 and "Noms des grands nombres" or ("Ordres de grandeur de nombres#10" .. i)) .. linkend(u)
        elseif lang == "it" then
            return "[[it:" .. ("Ordini di grandezza (numeri)#10" .. i) .. linkend(u)
        elseif lang == "la" then
            return "[[la:" .. "Nomina permagnorum numerorum" .. linkend(u)
        elseif lang == "nl" then
            return "[[nl:" .. "Lijst van machten van tien" .. linkend(u)
		elseif lang == "no" then
            return "[[no:" .. "Navn på store tall" .. linkend(u)
        elseif lang == "pl" then
            return "[[pl:" .. "Liczebniki główne potęg tysiąca" .. linkend(u)
		elseif lang == "sk" then
            return "[[sk:" .. "Veľké čísla" .. linkend(u)
		elseif lang == "sl" then
            return "[[sl:" .. "Imena velikih števil" .. linkend(u)
        elseif lang == "sv" then
            return "[[sv:" .. "Namn på stora tal" .. linkend(u)
        else
            return "[[en:" .. (i > 39 and "Names of large numbers" or ("Orders of magnitude (numbers)#10" .. i)) .. linkend(u)
        end
    else
        return u
    end
end

function p.wordify(frame)
    local args = getArgs(frame)
    local x = args[1]
    local numsys = args.numsys
    local prec =  args.prec
    local lk = args.lk
    local lang = args.lang
    local simplify = args.simplify
    return p._wordify(x, numsys, prec, (lk == "on" and true), lang, (simplify == "yes" and true))
end

function p._wordify(x, numsys, prec, lk, lang, simplify)
    if tonumber(x) then
        if numsys == nil or numsys == "" or numsys:lower() == "usa" then
            for i, v in reverseSparseIpairs(usa) do
                local y = x / math.pow(10,i)
                local r = _round(y, prec)
                if r >= 1 then
                    if r == 1 and simplify then
                        return unitlink(lk, i, v, (lang or "en"))
                    else
                        return p.formatNum(r, (lang or "en"), prec) .. " " .. unitlink(lk, i, v, (lang or "en"))
                    end
                end
            end
                    
            return p.formatNum(_round(x, prec), (lang or "en"), prec) 
        elseif numsys:lower() == "fra" then
            for i, v in reverseSparseIpairs(latin) do
                if latin[i][(lang or "")] then
                    local y = x / math.pow(10,i)
                    local r = _round(y, prec)
                    if r >= 1 then
                        local u = unit(latin, i, lang, r)
                        if r == 1 and (simplify or latin[i][(lang or "")]["simplify"] == true) then
                            return unitlink(lk, i, u, (lang or "")) 
                        else
                            return p.formatNum(r, (lang or "en"), prec) .. " " .. unitlink(lk, i, u, (lang or "en"))
                        end
                    end
                end
            end

            return p.formatNum(_round(x, prec), (lang or "en"), prec) 
        elseif numsys:lower() == "ind" then
            local y = x / 1E14
            local r = _round(y, prec)
            if r >= 1 then
                if r == 1 and simplify then
                    return (lk and "[[crore]] crore" or ind[14])
                else
                    return p.formatNum(r, (lang or "en"), prec) .. " " .. (lk and "[[crore]] crore" or ind[14])
                end
            else
                local y = x / 1E12
                local r = _round(y, prec)
                if r >= 1 then
                    if r == 1 and simplify then
                        return (lk and "[[lakh]] [[crore]]" or ind[12])
                    else
                        return p.formatNum(r, (lang or "en"), prec) .. " " .. (lk and "[[lakh]] [[crore]]" or ind[12])
                    end
                else
                    local y = x / 1E7
                    local r = _round(y, prec)
                    if r >= 1 then
                        local v = ind[7]
                        if r == 1 and simplify then
                            return (lk and "[[" .. v .. "]]" or v)
                        else
                            return p.formatNum(r, (lang or "en"), prec) .. " " .. (lk and "[[" .. v .. "]]" or v)
                        end
                    else
                        local y = x / 1E5
                        local r = _round(y, prec)
                        if r >= 1 then
                            local v = ind[5]
                            if r == 1 and simplify then
                                return (lk and "[[" .. v .. "]]" or v)
                            else
                                return p.formatNum(r, (lang or "en"), prec) .. " " .. (lk and "[[" .. v .. "]]" or v)
                            end
                        else
                            return p.formatNum(_round(x, prec), (lang or "en"), prec) 
                        end
                    end
                end
            end
        else
            return err("number system not supported")
        end 
    else
      return err("Not a number: " .. x)
    end
end

--[[
Helper function that interprets the input numerically.  If the
input does not appear to be a number, attempts evaluating it as
a parser functions expression.
]]

function p._cleanNumber(number_string)
    if type(number_string) == 'number' then
        -- We were passed a number, so we don't need to do any processing.
        return number_string, tostring(number_string)
    elseif type(number_string) ~= 'string' or not number_string:find('%S') then
        -- We were passed a non-string or a blank string, so exit.
        return nil, nil;
    end

    -- Attempt basic conversion
    local number = tonumber(number_string)

    -- If failed, attempt to evaluate input as an expression
    if number == nil then
        local success, result = pcall(mw.ext.ParserFunctions.expr, number_string)
        if success then
            number = tonumber(result)
            number_string = tostring(number)
        else
            number = nil
            number_string = nil
        end
    else
        number_string = number_string:match("^%s*(.-)%s*$") -- String is valid but may contain padding, clean it.
        number_string = number_string:match("^%+(.*)$") or number_string -- Trim any leading + signs.
        if number_string:find('^%-?0[xX]') then
            -- Number is using 0xnnn notation to indicate base 16; use the number that Lua detected instead.
            number_string = tostring(number)
        end
    end

    return number, number_string
end

return p