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 04:48, 28 February 2021 (Fix declension algorithm). 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 = {
    ['ca'] = { '', function (u) 
                        local n = mw.ustring.len(u) 
                        if mw.ustring.sub(u, n, n) == "d" then return u .. "s" else return mw.ustring.sub(u, 1, -2) .. "ons" end 
                   end },
    ['da'] = { '', 'er' },
    ['de'] = { '', function (u) 
                        local n = mw.ustring.len(u) 
                        if mw.ustring.sub(u, n, n) == "e" then return u .. "n" else return u .. "en" end 
                   end },
    ['eo'] = { '', 'j' },
    ['es'] = { '', function (u) 
                        if mw.ustring.sub(u, 1, 4) == "mil " then
                            return u
                        else
                            local n = mw.ustring.len(u) 
                            if mw.ustring.sub(u, n, n) == "o" then return u .. "s" else return mw.ustring.sub(u, 1, -3) .. "ones" end 
                        end
                   end },
    ['fr'] = { '', 's' },
    ['it'] = { '', function (u) 
                        return mw.ustring.sub(u, 1, -2) .. "i"
                   end },
    ['la'] = { '', function (u) 
                        local n = mw.ustring.len(u) 
                        if mw.ustring.sub(u, n, n) == "o" then return u .. "nibus" else return mw.ustring.sub(u, 1, -3) .. "a" end 
                   end },
    ['no'] = { '', 'er' },
    ['pl'] = { '', 'y', [5] = 'ów', ["fraction"] = 'a' },
	['sk'] = { '', function (u) 
						local n = mw.ustring.len(u) 
						if mw.ustring.sub(u, n, n) == "n" then return u .. 'y' else return mw.ustring.sub(u, 1, -2) .. "y" end
				   end,
	               [5] = function (u) 
							local n = mw.ustring.len(u) 
							if mw.ustring.sub(u, n, n) == "n" then return u .. 'ov' else return mw.ustring.sub(u, 1, -5) .. "árd" end 
						 end
			 },
	['sl'] = { '', function (u) 
						local n = mw.ustring.len(u) 
						if mw.ustring.sub(u, n, n) == "a" then return mw.ustring.sub(u, 1, -2) .. 'i' else return u .. "a" end
				   end,
	               function (u) 
						local n = mw.ustring.len(u) 
						if mw.ustring.sub(u, n, n) == "a" then return mw.ustring.sub(u, 1, -2) .. 'e' else return u .. "e" end
				   end,
	               [5] = function (u) 
							local n = mw.ustring.len(u) 
							if mw.ustring.sub(u, n, n) == "a" then return mw.ustring.sub(u, 1, -2) else return u .. "ov" end 
						 end,
				   ["fraction"] = function (u) 
									local n = mw.ustring.len(u) 
									if mw.ustring.sub(u, n, n) == "a" then return mw.ustring.sub(u, 1, -2) .. 'e' else return u .. "a" end 
								  end 
			 },
    ['sv'] = { '', 'er' }
}

local latin = {
    [6] = {
        ['']  = { 'million' },
        ['ca'] = { 'milió' },
        ['da'] = { 'million' },
        ['de'] = { 'Million' },
        ['en'] = { 'million' },
        ['eo'] = { 'miliono' },
        ['es'] = { 'millón' },
        ['fr'] = { 'million' },
        ['it'] = { 'milione' },
        ['la'] = { 'millio' },
        ['nl'] = { 'miljoen', 'miljoenen' },
        ['no'] = { 'million' },
        ['pl'] = { 'milion' },
        ['pt'] = { 'milhão', 'milhões' },
        ['sk'] = { 'milión' },
        ['sl'] = { 'milijon' },
        ['sv'] = { 'miljon' }
    },
    [9] = {
        ['']  = { 'milliard' },
        ['ca'] = { 'miliard' },
        ['da'] = { 'milliard' },
        ['de'] = { 'Milliarde' },
        ['en'] = { 'milliard' },
        ['eo'] = { 'miliardo' },
        ['es'] = { 'millardo' },
        ['fr'] = { 'milliard' },
        ['it'] = { 'miliardo' },
        ['la'] = { 'milliardum' },
        ['nl'] = { 'miljard', 'miljard' },
        ['no'] = { 'milliard' },
        ['pl'] = { 'miliard' },
        ['pt'] = { 'mil milhões', 'mil milhões', ["simplify"] = true },
        ['sk'] = { 'miliarda' },
        ['sl'] = { 'milijarda' },
        ['sv'] = { 'miljard' }
    },
    [12] = {
        ['']  = { 'billion' },
        ['ca'] = { 'bilió' },
        ['da'] = { 'billion' },
        ['de'] = { 'Billion' },
        ['en'] = { 'billion' },
        ['eo'] = { 'duiliono' },
        ['es'] = { 'billón' },
        ['fr'] = { 'billion' },
        ['it'] = { 'bilione' },
        ['la'] = { 'billio' },
        ['nl'] = { 'biljoen', 'biljoen' },
        ['no'] = { 'billion' },
        ['pl'] = { 'bilion' },
        ['pt'] = { 'bilião', 'biliões' },
        ['sk'] = { 'bilión' },
        ['sl'] = { 'bilijon' },
        ['sv'] = { 'biljon' }
    },
    [15] = {
        ['']  = { 'billiard' },
        ['ca'] = { 'biliard' },
        ['da'] = { 'billiard' },
        ['de'] = { 'Billiarde' },
        ['en'] = { 'billiard' },
        ['eo'] = { 'duiliardo' },
        ['es'] = { 'mil billones', ["simplify"] = true },
        ['fr'] = { 'billiard' },
        ['it'] = { 'biliardo' },
        ['la'] = { 'billiardum' },
        ['nl'] = { 'biljard', 'biljard' },
        ['no'] = { 'billiard' },
        ['pl'] = { 'biliard' },
        ['pt'] = { 'mil biliões', 'mil biliões', ["simplify"] = true },
        ['sk'] = { 'biliarda' },
        ['sl'] = { 'bilijarda' },
        ['sv'] = { 'biljard' }
    },
    [18] = {
        ['']  = { 'trillion' },
        ['ca'] = { 'trilió' },
        ['da'] = { 'trillion' },
        ['de'] = { 'Trillion' },
        ['en'] = { 'trillion' },
        ['eo'] = { 'triliono' },
        ['es'] = { 'trillón' },
        ['fr'] = { 'trillion' },
        ['it'] = { 'trilione' },
        ['la'] = { 'trillio' },
        ['nl'] = { 'triljoen', 'triljoen' },
        ['no'] = { 'trillion' },
        ['pl'] = { 'trylion' },
        ['pt'] = { 'trilião', 'triliões' },
        ['sk'] = { 'trilión' },
        ['sl'] = { 'trilijon' },
        ['sv'] = { 'triljon' }
    },
    [21] = {
        ['']  = { 'trilliard' },
        ['ca'] = { 'triliard' },
        ['da'] = { 'trilliard' },
        ['de'] = { 'Trilliarde' },
        ['en'] = { 'trilliard' },
        ['eo'] = { 'triliardo' },
        ['es'] = { 'mil trillones', ["simplify"] = true },
        ['fr'] = { 'trilliard' },
        ['it'] = { 'triliardo' },
        ['la'] = { 'trilliardum' },
        ['nl'] = { 'triljard', 'triljard' },
        ['no'] = { 'trilliard' },
        ['pl'] = { 'tryliard' },
        ['pt'] = { 'mil triliões', 'mil triliões', ["simplify"] = true },
        ['sk'] = { 'triliarda' },
        ['sl'] = { 'trilijarda' },
        ['sv'] = { 'triljard' }
    },
    [24] = {
        ['']  = { 'quadrillion' },
        ['ca'] = { 'quadrilió' },
        ['da'] = { 'kvadrillion' },
        ['de'] = { 'Quadrillion' },
        ['en'] = { 'quadrillion' },
        ['eo'] = { 'kvariliono' },
        ['es'] = { 'cuatrillón' },
        ['fr'] = { 'quadrillion' },
        ['it'] = { 'quadrilione' },
        ['la'] = { 'quadrillio' },
        ['nl'] = { 'quadriljoen', 'quadriljoen' },
        ['no'] = { 'kvadrillion' },
        ['pl'] = { 'kwadrylion' },
        ['pt'] = { 'quadrilião', 'quadriliões' },
        ['sk'] = { 'kvadrilión' },
        ['sl'] = { 'kvadrilijon' },
        ['sv'] = { 'kvadriljon' }
    },
    [27] = {
        ['']  = { 'quadrilliard' },
        ['ca'] = { 'quadriliard' },
        ['da'] = { 'kvadrilliard' },
        ['de'] = { 'Quadrilliarde' },
        ['en'] = { 'quadrilliard' },
        ['eo'] = { 'kvariliardo' },
        ['es'] = { 'mil cuatrillones', ["simplify"] = true },
        ['fr'] = { 'quadrilliard' },
        ['it'] = { 'quadriliardo' },
        ['la'] = { 'quadrilliardum' },
        ['nl'] = { 'quadriljard', 'quadriljard' },
        ['no'] = { 'kvadrilliard' },
        ['pl'] = { 'kwadryliard' },
        ['pt'] = { 'mil quadriliões', 'mil quadriliões', ["simplify"] = true },
        ['sk'] = { 'kvadriliarda' },
        ['sl'] = { 'kvadrilijarda' },
        ['sv'] = { 'kvadriljard' }
    },
    [30] = {
        ['']  = { 'quintillion' },
        ['ca'] = { 'quintilió' },
        ['da'] = { 'kvintillion' },
        ['de'] = { 'Quintillion' },
        ['en'] = { 'quintillion' },
        ['eo'] = { 'kviniliono' },
        ['es'] = { 'quintillón' },
        ['fr'] = { 'quintillion' },
        ['it'] = { 'quintilione' },
        ['la'] = { 'quintillio' },
        ['nl'] = { 'quintiljoen', 'quintiljoen' },
        ['no'] = { 'kvintillion' },
        ['pl'] = { 'kwintylion' },
        ['pt'] = { 'quintilião', 'quintiliões' },
        ['sk'] = { 'kvintilión' },
        ['sl'] = { 'kvintilijon' },
        ['sv'] = { 'kvintiljon' }
    },
    [33] = {
        ['']  = { 'quintilliard' },
        ['ca'] = { 'quintiliard' },
        ['da'] = { 'kvintilliard' },
        ['de'] = { 'Quintilliarde' },
        ['en'] = { 'quintilliard' },
        ['eo'] = { 'kviniliardo' },
        ['es'] = { 'mil quintillones', ["simplify"] = true },
        ['fr'] = { 'quintilliard' },
        ['it'] = { 'quintiliardo' },
        ['la'] = { 'quintilliardum' },
        ['nl'] = { 'quintiljard', 'quintiljard' },
        ['no'] = { 'kvintilliard' },
        ['pl'] = { 'kwintyliard' },
        ['pt'] = { 'mil quintiliões', 'mil quintiliões', ["simplify"] = true },
        ['sk'] = { 'kvintiliarda' },
        ['sl'] = { 'kvintilijarda' },
        ['sv'] = { 'kvintiljard' }
    },
    [36] = {
        ['']  = { 'sextillion' },
        ['ca'] = { 'sextilió' },
        ['da'] = { 'sekstillion' },
        ['de'] = { 'Sextillion' },
        ['en'] = { 'sextillion' },
        ['eo'] = { 'sesiliono' },
        ['es'] = { 'sextillón' },
        ['fr'] = { 'sextillion' },
        ['la'] = { 'sextillio' },
        ['nl'] = { 'sextiljoen', 'sextiljoen' },
        ['no'] = { 'sekstillion' },
        ['pl'] = { 'sekstylion' },
        ['pt'] = { 'sextilião', 'sextiliões' },
        ['sk'] = { 'sextilión' },
        ['sl'] = { 'sekstilijon' },
        ['sv'] = { 'sextiljon' }
    },
    [39] = {
        ['']  = { 'sextilliard' },
        ['ca'] = { 'sextiliard' },
        ['da'] = { 'sekstilliard' },
        ['de'] = { 'Sextilliarde' },
        ['en'] = { 'sextilliard' },
        ['eo'] = { 'sesiliardo' },
        ['es'] = { 'mil sextillones', ["simplify"] = true },
        ['fr'] = { 'sextilliard' },
        ['la'] = { 'sextilliardum' },
        ['nl'] = { 'sextiljard', 'sextiljard' },
        ['no'] = { 'sekstilliard' },
        ['pl'] = { 'sekstyliard' },
        ['pt'] = { 'mil sextiliões', 'mil sextiliões', ["simplify"] = true },
        ['sk'] = { 'sextiliarda' },
        ['sl'] = { 'sekstilijarda' },
        ['sv'] = { 'sextiljard' }
    },
    [42] = {
        ['']  = { 'septillion' },
        ['ca'] = { 'septilió' },
        ['da'] = { 'septillion' },
        ['de'] = { 'Septillion' },
        ['en'] = { 'septillion' },
        ['eo'] = { 'sepiliono' },
        ['es'] = { 'septillón' },
        ['fr'] = { 'septillion' },
        ['la'] = { 'septillio' },
        ['nl'] = { 'septiljoen', 'septiljoen' },
        ['no'] = { 'septillion' },
        ['pl'] = { 'septylion' },
        ['pt'] = { 'septilião', 'septiliões' },
        ['sk'] = { 'septilión' },
        ['sl'] = { 'septilijon' },
        ['sv'] = { 'septiljon' }
    },
    [45] = {
        ['']  = { 'septilliard' },
        ['ca'] = { 'septiliard' },
        ['da'] = { 'septilliard' },
        ['de'] = { 'Septilliarde' },
        ['en'] = { 'septilliard' },
        ['eo'] = { 'sepiliardo' },
        ['es'] = { 'mil septillones', ["simplify"] = true },
        ['fr'] = { 'septilliard' },
        ['la'] = { 'septilliardum' },
        ['nl'] = { 'septiljard', 'septiljard' },
        ['no'] = { 'septilliard' },
        ['pl'] = { 'septyliard' },
        ['pt'] = { 'mil septiliões', 'mil septiliões', ["simplify"] = true },
        ['sk'] = { 'septiliarda' },
        ['sl'] = { 'septilijarda' },
        ['sv'] = { 'septiljard' }
    },
    [48] = {
        ['']  = { 'octillion' },
        ['ca'] = { 'octilió' },
        ['da'] = { 'oktillion' },
        ['de'] = { 'Oktillion' },
        ['en'] = { 'octillion' },
        ['eo'] = { 'okiliono' },
        ['es'] = { 'octillón' },
        ['fr'] = { 'octillion' },
        ['la'] = { 'octillio' },
        ['nl'] = { 'octiljoen', 'octiljoen' },
        ['no'] = { 'oktillion' },
        ['pl'] = { 'oktylion' },
        ['pt'] = { 'octilião', 'octiliões' },
        ['sk'] = { 'oktilión' },
        ['sl'] = { 'oktilijon' },
        ['sv'] = { 'oktiljon' }
    },
    [51] = {
        ['']  = { 'octilliard' },
        ['ca'] = { 'octiliard' },
        ['da'] = { 'oktilliard' },
        ['de'] = { 'Oktilliarde' },
        ['en'] = { 'octilliard' },
        ['eo'] = { 'okiliardo' },
        ['es'] = { 'mil octillones', ["simplify"] = true },
        ['fr'] = { 'octilliard' },
        ['la'] = { 'octilliardum' },
        ['nl'] = { 'octiljard', 'octiljard' },
        ['no'] = { 'oktilliard' },
        ['pl'] = { 'oktyliard' },
        ['pt'] = { 'mil octiliões', 'mil octiliões', ["simplify"] = true },
        ['sk'] = { 'oktiliarda' },
        ['sl'] = { 'oktilijarda' },
        ['sv'] = { 'oktiljard' }
    },
    [54] = {
        ['']  = { 'nonillion' },
        ['ca'] = { 'nonilió' },
        ['da'] = { 'nonillion' },
        ['de'] = { 'Nonillion' },
        ['en'] = { 'nonillion' },
        ['eo'] = { 'naŭiliono' },
        ['es'] = { 'nonillón' },
        ['fr'] = { 'nonillion' },
        ['la'] = { 'nonillio' },
        ['nl'] = { 'noniljoen', 'noniljoen' },
        ['no'] = { 'nonillion' },
        ['pl'] = { 'nonilion' },
        ['pt'] = { 'nonilião', 'noniliões' },
        ['sk'] = { 'nonilión' },
        ['sl'] = { 'nonilijon' },
        ['sv'] = { 'noniljon' }
    },
    [57] = {
        ['']  = { 'nonilliard' },
        ['ca'] = { 'noniliard' },
        ['da'] = { 'nonilliard' },
        ['de'] = { 'Nonilliarde' },
        ['en'] = { 'nonilliard' },
        ['eo'] = { 'naŭiliardo' },
        ['es'] = { 'mil nonillones', ["simplify"] = true },
        ['fr'] = { 'nonilliard' },
        ['la'] = { 'nonilliardum' },
        ['nl'] = { 'noniljard', 'noniljard' },
        ['no'] = { 'nonilliard' },
        ['pl'] = { 'noniliard' },
        ['pt'] = { 'mil noniliões', 'mil noniliões', ["simplify"] = true },
        ['sk'] = { 'noniliarda' },
        ['sl'] = { 'nonilijarda' },
        ['sv'] = { 'noniljard' }
    },
    [60] = {
        ['']  = { 'decillion' },
        ['ca'] = { 'decilió' },
        ['da'] = { 'decillion' },
        ['de'] = { 'Dezillion' },
        ['en'] = { 'decillion' },
        ['eo'] = { 'dekiliono ' },
        ['es'] = { 'decillón' },
        ['fr'] = { 'décillions' },
        ['it'] = { 'decilione' },
        ['la'] = { 'decillio' },
        ['nl'] = { 'deciljoen', 'deciljoen' },
        ['no'] = { 'desillion' },
        ['pl'] = { 'decylion' },
        ['pt'] = { 'decilião', 'deciliões' },
        ['sk'] = { 'decilión' },
        ['sl'] = { 'decilijon' },
        ['sv'] = { 'deciljon' }
    },
    [63] = {
        ['']  = { 'decilliard' },
        ['ca'] = { 'deciliard' },
        ['da'] = { 'decilliard' },
        ['de'] = { 'Dezilliarde' },
        ['en'] = { 'decilliard' },
        ['eo'] = { 'dekiliardo' },
        ['es'] = { 'mil decillones', ["simplify"] = true },
        ['fr'] = { 'décilliard' },
        ['la'] = { 'decilliardum' },
        ['nl'] = { 'deciljard', 'deciljard' },
        ['no'] = { 'desilliard' },
        ['pl'] = { 'decyliard' },
        ['pt'] = { 'mil deciliões', 'mil deciliões', ["simplify"] = true },
        ['sk'] = { 'deciliarda' },
        ['sl'] = { 'decilijarda' },
        ['sv'] = { 'deciljard' }
    }
}

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
            local d = declension[lang][2]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        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
            local d = declension[lang][3]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        elseif arr[i][(lang or "")][3] then
            return arr[i][(lang or "")][3]
        elseif declension[lang] and declension[lang][2] then
            local d = declension[lang][2]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        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
            local d = declension[lang][5]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        elseif arr[i][(lang or "")][5] then
            return arr[i][(lang or "")][5]
        elseif declension[lang] and declension[lang][3] then
            local d = declension[lang][3]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        elseif arr[i][(lang or "")][3] then
            return arr[i][(lang or "")][3]
        elseif declension[lang] and declension[lang][2] then
            local d = declension[lang][2]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        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
            local d = declension[lang]["fraction"]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        elseif arr[i][(lang or "")]["fraction"] then
            return arr[i][(lang or "")]["fraction"]
        elseif declension[lang] and declension[lang][5] then
            local d = declension[lang][5]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        elseif arr[i][(lang or "")][5] then
            return arr[i][(lang or "")][5]
        elseif declension[lang] and declension[lang][3] then
            local d = declension[lang][3]
            if type(d) == 'function' then
                return d(arr[i][(lang or "")][1])
            else
                return arr[i][(lang or "")][1] .. d
            end
        elseif arr[i][(lang or "")][3] then
            return arr[i][(lang or "")][3]
        elseif declension[lang] and declension[lang][2] then
            local d = declension[lang][2]
            if type(d) == 'function' then
                return (r < 2 and arr[i][(lang or "")][1] or d(arr[i][(lang or "")][1]))
            else
                return (r < 2 and arr[i][(lang or "")][1] or (arr[i][(lang or "")][1] .. d))
            end
        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