Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules

This module provides functions for determining whether a given language code is LTR or RTL.

  • It uses the RTL or LTR status of languages defined in the data returned by Module:Dir/RTL overrides.
  • Otherwise, if a language code is not mapped, it uses the language direction provided by the MediaWiki language library (but this only works for a limited number of distinct languages in the same viewed page, as the MediaWiki library sets a quota on the number of languages for which it can load localized data). For this reason, if a page uses lots of languages (for example with Template:Multilingual description) the data should be as complete as possible to avoid reaching this quota.

This module includes a quick test function whose status is displayed at top of its description page: this status is OK if there are no errors in the data, i.e. only valid or known language codes are mapped. When you run it in the console, it regenerates the Lua code that may be imported to Module:Dir/RTL overrides, and containing deduplicated and sorted code lists, including missing codes detected and supported by the MediaWiki API.

Usage

edit

From wikitext

edit

From wikitext, this module may be used through a template, Template:Dir. Please see the template page for documentation.

However, the template version is standalone and does not need to use this Lua module (for performance reason, in order to avoid the extra cost of instantiating a Lua engine).

This module is still faster than preprocessing a template from Lua. So the list of RTL languages in the template version should be kept in sync with the list of overrides used by this module.

From Lua

edit

From Lua you can use the module's isRTL function. First, load the module:

local dir = require('Module:Dir')

Then you can use the function like this:

dir.isRTL(lang) -- returns false or true
The lang variable is the language code string. The function returns true if the language for the code is detected as RTL, and it returns false if it is LTR or if the code is absent or invalid.
dir.select(lang, rtl, ltr)  -- returns either the rtl or ltr parameter values
Same thing except that you can define the value to return for each case.

Testing

edit

In the Lua console, you can run the included quick test to scan the data defined in Module:Dir/RTL overrides: it provides a detailed report of invalid language codes, missing languages known by MediaWiki, and helps maintaining this data.

For seeing the complete list of languages, tested with their directionality defined here along with other properties, look at the comprehensive results on Module talk:Multilingual description/sort/testcases.

Code

--[==[
This module implements [[Template:Dir]].

Enter this to run tests in the Lua console:
=getmetatable(p).quickTests()

--]==]
local rtlOverrides = require('Module:Dir/RTL overrides')
local p = {}

local function trim(s)
    if s and s ~= '' then s = tostring(s):match('^%s*(.-)%s*$') end
    if s == '' then return nil end
    return s
end

function p.rtlLangs(isRTL)
    if isRTL == nil then isRTL = true end
    return rtlOverrides[isRTL] or {}
end

function p.isRTL(code)
    if type(code) ~= 'string' then return nil end
    local v = rtlOverrides[code] -- very fast and not limited in the number of supported languages
    if v ~= nil then return v end -- return it if mapped, otherwise use MediaWiki library:
    local success, ret = pcall(function ()
        return mw.language.new(code):isRTL() -- expensive and limited to 20 languages per MediaWiki instance
    end)
    return success and ret
end

function p.select(code, rtl, ltr)
    if p.isRTL(code) then
        return rtl
    else
        return ltr
    end
end

-- The 3 parameters are automatically trimmed:
-- Parameter 1 takes the default value from {{Int:Lang}} if it's empty or not specified.
-- Parameter 2 can be named rtl, may be explicitly empty, otherwise takes the default value 'rtl' only if it's not specified.
-- Parameter 3 can be named ltr, may be explicitly empty, otherwise takes the default value 'ltr' only if it's not specified.
function p.main(frame)
    local args = frame:getParent().args -- Parameters used to transclude Template:Dir
    local code = trim(args[1]) or frame:callParserFunction('Int', 'Lang')
    local rtl = trim(args.rtl or args[2] or 'rtl')
    local ltr = trim(args.ltr or args[3] or 'ltr')
    return p.select(code, rtl, ltr)
end

setmetatable(p, { quickTests = function ()
    local rtlLangs = p.rtlLangs(true)
    local ltrLangs = p.rtlLangs(false)

    -- Basic check of data format
    local function checkLangs(name, langs)
        for k, lang in pairs(langs) do
            assert(type(k) == 'number' and k == math.floor(k)
                and type(lang) == 'string' and #lang >= 2 and #lang <= 16
                and lang:find('^[a-z][%-0-9a-z]*[0-9a-z]$') == 1,
                ": Invalid sequence of language codes, " .. name .. "['" .. k .. "'] = '" .. lang .. "'")
        end
        return true
    end
    local ok, msg
    ok, msg = pcall(checkLangs, 'rtlLangs', rtlLangs)
    if not ok then return false, msg end
    ok, msg = pcall(checkLangs, 'ltrLangs', ltrLangs)
    if not ok then return false, msg end

    -- Build inverse maps of languages having each direction
    local isrtl, isltr = {}, {}
    for _, lang in ipairs(rtlLangs) do isrtl[lang] = true end
    for _, lang in ipairs(ltrLangs) do isltr[lang] = true end
    -- Check conflicts using the two inverse maps
    for _, lang in ipairs(rtlLangs) do
        if isltr[lang] then return false, ": Direction conflict for '" .. lang .. "'" end
    end
    for _, lang in ipairs(ltrLangs) do
        if isrtl[lang] then return false, ": Direction conflict for '" .. lang .. "'" end
    end

    -- Log missing languages (allows filling the tables above) according to MediaWiki internal data
    local knownLangs, isKnownLang = mw.language.fetchLanguageNames(), {}
    for lang, _ in pairs(knownLangs) do
         isKnownLang[lang] = true
         if rtlOverrides[lang] == nil then -- only if we still don't have data for this language
             -- Note: we cannot check more than 20 languages at once, then MediaWiki raises an error.
             -- So this test only runs on the Lua console, where you can update the tables at top.
             -- This also means we cannot compare what MediaWiki returns with the direction we map here for all languages.
             ok, value = pcall(function() return tostring(mw.language.new(lang):isRTL()) end)
             mw.log("Warning: missing direction for language '" .. lang .. "', MediaWiki returns '" .. value .. "'")
         end
    end

    -- utility: reverse order iterator on sequences
    local function revipairs(t)
        return function(t, i)
            i = i - 1
            local v = t[i]
            if v == nil then return nil end
            return i, v
        end, t, #t + 1
    end
    -- Sort and deduplicate language code values (by scanning backward) for data cleanup
    -- Also log languages having a direction mapping in this module but still not known by MediaWiki
    table.sort(rtlLangs)
    table.sort(ltrLangs)
    for i, lang in revipairs(rtlLangs) do
        if rtlLangs[i - 1] == rtlLangs[i] then table.remove(rtlLangs, i) end
        if isKnownLang[lang] == nil then
             mw.log("RTL language '" .. lang .. "' still not known by Mediawiki (could be a missing alias or variant)")
        end
    end
    for i, lang in revipairs(ltrLangs) do
        if ltrLangs[i - 1] == ltrLangs[i] then table.remove(ltrLangs, i) end
        if isKnownLang[lang] == nil then
             mw.log("LTR language '" .. lang .. "' still not known by Mediawiki (could be a missing alias or variant)")
        end
    end

    -- Final presentation of current lists, sorted and deduplicated
    mw.log("----")
    mw.log("local rtlLangs = { '" .. table.concat(rtlLangs, "', '") .. "' }")
    mw.log("local ltrLangs = { '" .. table.concat(ltrLangs, "', '") .. "' }")
    return true
end })

return p