Module:Lang: Difference between revisions

Jump to navigation Jump to search
m Added overrides for AuraFireRateBonus and MoveSlowDuration
added processing of InlineAttributeName and InlineAttributeIcon
Line 49: Line 49:
local function process_html_tags(text, frame)
local function process_html_tags(text, frame)
     if not text or type(text) ~= "string" then return text end
     if not text or type(text) ~= "string" then return text end
   
 
     local replacements = {
     local replacements = {
    BonusFireRate = {icon='{{Icon/Brown|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
        BonusFireRate = {icon='{{Icon/Brown|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    BonusMoveSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
        BonusMoveSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
    BonusSprintSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Sprint speed|20px]]}}', label_color='#ffefd7', link='Sprint speed'},
        BonusSprintSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Sprint speed|20px]]}}', label_color='#ffefd7', link='Sprint speed'},
    BonusSpiritDamage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
        BonusSpiritDamage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    BonusWeaponDamage = {icon='{{Icon/NoColor|[[File:Bullet Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
        BonusWeaponDamage = {icon='{{Icon/NoColor|[[File:Bullet Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
    bullet_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
        bullet_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
    bullet_damage = {icon='{{Icon/Brown|[[File:Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
        bullet_damage = {icon='{{Icon/Brown|[[File:Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
    BulletResist = {icon='{{Icon/Brown|[[File:Bullet_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#ec981a', link='Damage_Resistance'},
        BulletResist = {icon='{{Icon/Brown|[[File:Bullet_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#ec981a', link='Damage_Resistance'},
    cast = {icon='{{Icon/White|[[File:Cast.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        cast = {icon='{{Icon/White|[[File:Cast.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    CombatBarrier = {icon='{{Icon/White|[[File:Bullet Armor (item).png|link=Damage_Resistance|20px]]}}', label_color='#ffefd7', link='Barrier'},
        CombatBarrier = {icon='{{Icon/White|[[File:Bullet Armor (item).png|link=Damage_Resistance|20px]]}}', label_color='#ffefd7', link='Barrier'},
    cooldown = {icon='{{Icon/White|[[File:AttributeIconTechDuration.png|link=Ability Cooldown|20px]]}}', label_color='#ffefd7', link='Ability Cooldown'},
        cooldown = {icon='{{Icon/White|[[File:AttributeIconTechDuration.png|link=Ability Cooldown|20px]]}}', label_color='#ffefd7', link='Ability Cooldown'},
    damage = {icon='{{Icon/NoColor|[[File:Damage heart.png|link=Damage|20px]]}}', label_color='#ec981a', link='Damage'},
        damage = {icon='{{Icon/NoColor|[[File:Damage heart.png|link=Damage|20px]]}}', label_color='#ec981a', link='Damage'},
    FireRate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
        FireRate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    fire_rate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
        fire_rate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    Heal = {icon='{{Icon/Green|[[File:Healing.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
        Heal = {icon='{{Icon/Green|[[File:Healing.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
    Healing = {icon='{{Icon/Green|[[File:Healing.png|link=Healing|20px]]}}', label_color='#13f278', link='Healing'},
        Healing = {icon='{{Icon/Green|[[File:Healing.png|link=Healing|20px]]}}', label_color='#13f278', link='Healing'},
    Immobilize = {icon='{{Icon/White|[[File:Immobilize.png|link=Status Effects#Immobilized|20px]]}}', label_color='#ffefd7', link='Status Effects#Immobilized'},
        Immobilize = {icon='{{Icon/White|[[File:Immobilize.png|link=Status Effects#Immobilized|20px]]}}', label_color='#ffefd7', link='Status Effects#Immobilized'},
    invisible = {icon='{{Icon/White|[[File:Invisible.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        invisible = {icon='{{Icon/White|[[File:Invisible.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    KnockBack = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
        KnockBack = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    KnockUp =   {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
        KnockUp = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    MaxHealth = {icon='{{Icon/Green|[[File:Healing.png|link=Health|20px]]}}', label_color='#13f278', link='Health'},
        MaxHealth = {icon='{{Icon/Green|[[File:Healing.png|link=Health|20px]]}}', label_color='#13f278', link='Health'},
    MeleeDamage = {icon='{{Icon/Brown|[[File:Melee damage.png|link=Melee Damage|20px]]}}', label_color='#ec981a', link='Melee Damage'},
        MeleeDamage = {icon='{{Icon/Brown|[[File:Melee damage.png|link=Melee Damage|20px]]}}', label_color='#ec981a', link='Melee Damage'},
    MoveSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
        MoveSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
    move_speed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
        move_speed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
    Pull =     {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
        Pull = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    Pulling =   {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
        Pulling = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    Pulls =     {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
        Pulls = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    ReducedFireRate = {icon='{{Icon/Brown|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
        ReducedFireRate = {icon='{{Icon/Brown|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    Regen = {icon='{{Icon/Green|[[File:Healing.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
        Regen = {icon='{{Icon/Green|[[File:Healing.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
    revealed = {icon='{{Icon/White|[[File:Revealed.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        revealed = {icon='{{Icon/White|[[File:Revealed.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    silence = {icon='{{Icon/White|[[File:Silence.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        silence = {icon='{{Icon/White|[[File:Silence.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    Silence = {icon='{{Icon/Purple|[[File:Silence.png|link=Silence|20px]]}}', label_color='#bc8ee8', link='Silence'},
        Silence = {icon='{{Icon/Purple|[[File:Silence.png|link=Silence|20px]]}}', label_color='#bc8ee8', link='Silence'},
    Slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
        Slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
    slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
        slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
    SlowResistance = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow#Movement_Slow_Resist|20px]]}}', label_color='#ffefd7', link='Movement_Slow#Movement_Slow_Resist'},
        SlowResistance = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow#Movement_Slow_Resist|20px]]}}', label_color='#ffefd7', link='Movement_Slow#Movement_Slow_Resist'},
    Spirit = {icon='{{Icon/NoColor|[[File:Spirit_icon.png|link=Spirit Power|20px]]}}', label_color='#bc8ee8', link='Spirit Power'},
        Spirit = {icon='{{Icon/NoColor|[[File:Spirit_icon.png|link=Spirit Power|20px]]}}', label_color='#bc8ee8', link='Spirit Power'},
    SpiritDamage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
        SpiritDamage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    SpiritDPS = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
        SpiritDPS = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    SpiritIcon = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', link='Spirit Damage'},
        SpiritIcon = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', link='Spirit Damage'},
    SpiritResist = {icon='{{Icon/Purple|[[File:Spirit_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#bc8ee8', link='Damage_Resistance'},
        SpiritResist = {icon='{{Icon/Purple|[[File:Spirit_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#bc8ee8', link='Damage_Resistance'},
    StatDesc_TechPower = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
        StatDesc_TechPower = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    Stun = {icon='{{Icon/White|[[File:Status_Stun.png|link=Stun|20px]]}}', link='Stun'},
        Stun = {icon='{{Icon/White|[[File:Status_Stun.png|link=Stun|20px]]}}', link='Stun'},
    stun = {icon='{{Icon/White|[[File:Stun.png|link=|20px]]}}', label_color='#ffefd7', link=''},
        stun = {icon='{{Icon/White|[[File:Stun.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    tech_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
        tech_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
    tech_damage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', link='Spirit Damage'},
        tech_damage = {icon='{{Icon/NoColor|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}', link='Spirit Damage'},
    WeaponDamage = {icon='{{Icon/NoColor|[[File:Bullet Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
        WeaponDamage = {icon='{{Icon/NoColor|[[File:Bullet Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
}
    }
 
 
     -- Process HTML spans
     -- 1. Highlight, diminish
     text = text:gsub('<span class="highlight">(.-)</span>', "<span style= font-weight:bold>%1</span>")
     text = text:gsub('<span class="highlight">(.-)</span>', "<span style='font-weight:bold'>%1</span>")
     text = text:gsub('<span class="highlight_spirit">(.-)</span>', '<span style="font-weight:bold; color:#bc8ee8">%1</span>')
     text = text:gsub('<span class="highlight_spirit">(.-)</span>', '<span style="font-weight:bold; color:#bc8ee8">%1</span>')
     text = text:gsub('<span class="diminish">(.-)</span>', '<span style="font-style: italic;color:#C0C0C0">%1</span>')
     text = text:gsub('<span class="diminish">(.-)</span>', '<span style="font-style:italic;color:#C0C0C0">%1</span>')
     text = text:gsub('<span class=".-">(.-)</span>', '%1')
 
     text = text:gsub('<Panel class=\"AbilityPropertyIcon StatDesc_TechPower\">', '{{Icon/Purple|[[File:AttributeIconTechShieldHealth.png|link=Spirit Damage|12px]]}}')
     -- 2. InlineAttributeName
    text = text:gsub('</Panel>', '')
     text = text:gsub('<span class="InlineAttributeName (%w+)">(.-)</span>', function(key, inner)
   
    -- Process panel tags
text = text:gsub('<Panel class=\"AbilityPropertyIcon prop_(.-)\">', function(key)
    local replacement = replacements[key]
    local replacement = replacements[key]
    if replacement then
    local label_color = replacement and replacement.label_color or '#ffefd7'
        local label_color = replacement.label_color or 'inherit'
 
        local icon = replacement.icon or ''
    local translated
        local link = replacement.link or ''
    if inner and inner ~= '' then
        -- pre-process string, then check to omit link if the string is empty
        translated = inner
        local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
       
        if translated == '' then
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s</span>', label_color, icon)
        else
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s [[%s|%s]]</span>',
                label_color,
                icon,
                link,
                translated)
        end
    else
    else
        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing attribute definition - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
        translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
    end
    end
   
    return string.format('<span style="font-weight:bold; color:%s;">%s</span>', label_color, translated)
end)
end)
   
 
     -- Process citadel_inline_attribute tags
 
text = text:gsub("{g:citadel_inline_attribute:'(.-)'}", function(key)
     -- 3. AbilityPropertyIcon
    text = text:gsub('<[Pp]anel class="AbilityPropertyIcon prop_(.-)"[^>]->', function(key)
        local replacement = replacements[key]
        if replacement then
            local icon = replacement.icon or ''
            local label_color = replacement.label_color or 'inherit'
            local link = replacement.link or ''
            local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
            if translated == '' then
                return string.format('<span class="no-blue-link" style="font-weight:bold; color:%s;">%s</span>', label_color, icon)
            else
                return string.format('<span class="no-blue-link" style="font-weight:bold; color:%s;">%s [[%s|%s]]</span>', label_color, icon, link, translated)
            end
        else
            return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;">['..key..']</span>'
        end
    end)
 
    -- 4. InlineAttributeIcon
    text = text:gsub('<[Pp]anel class="InlineAttributeIcon (%w+)"[^>]->', function(key)
    local replacement = replacements[key]
    local replacement = replacements[key]
    if replacement then
    if replacement then
        local label_color = replacement.label_color or 'inherit'
        return string.format('<span class="no-blue-link" style="font-weight:bold; color:%s;">%s</span>', replacement.label_color or 'inherit', replacement.icon or '')
        local icon = replacement.icon or ''
        local link = replacement.link or ''
        -- pre-process string, then check to omit link if the string is empty
        local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
       
        if translated == '' then
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s</span>', label_color, icon)
        else
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s [[%s|%s]]</span>',
                label_color,
                icon,
                link,
                translated)
        end
    else
    else
        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing attribute definition - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;">['..key..']</span>'
    end
    end
end)
end)


     KEYBINDS = {
 
         -- Movement
    -- 5. citadel_inline_attribute
     text = text:gsub("{g:citadel_inline_attribute:'(.-)'}", function(key)
        local replacement = replacements[key]
         if replacement then
            local icon = replacement.icon or ''
            local label_color = replacement.label_color or 'inherit'
            local link = replacement.link or ''
            local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
            if translated == '' then
                return string.format('<span class="no-blue-link" style="font-weight:bold; color:%s;">%s</span>', label_color, icon)
            else
                return string.format('<span class="no-blue-link" style="font-weight:bold; color:%s;">%s [[%s|%s]]</span>', label_color, icon, link, translated)
            end
        else
            return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;">['..key..']</span>'
        end
    end)
 
    -- 6. Удаляем закрывающие теги panel
    text = text:gsub('</[Pp]anel>', '')
 
    -- 7. Keybinds
    local KEYBINDS = {
         MoveForward = "'''Move Forward button'''",
         MoveForward = "'''Move Forward button'''",
         MoveBackwards = "'''Move Backward button'''",
         MoveBackwards = "'''Move Backward button'''",
Line 167: Line 182:
         MoveDown = "'''Fly Down button'''",
         MoveDown = "'''Fly Down button'''",
         MoveUp = "'''Fly Up button'''",
         MoveUp = "'''Fly Up button'''",
        -- Combat
         Attack = "'''Fire button'''",
         Attack = "'''Fire button'''",
         ADS = "'''Alt-Fire button'''",
         ADS = "'''Alt-Fire button'''",
Line 174: Line 187:
         AbilityMelee = "'''Melee button'''",
         AbilityMelee = "'''Melee button'''",
         HeldItem = "'''Melee Parry/Throw Held Item button'''",
         HeldItem = "'''Melee Parry/Throw Held Item button'''",
        -- Abilities
         Ability1 = "'''Ability 1 button'''",
         Ability1 = "'''Ability 1 button'''",
         Ability2 = "'''Ability 2 button'''",
         Ability2 = "'''Ability 2 button'''",
Line 181: Line 192:
         Ability4 = "'''Ability 4 button'''",
         Ability4 = "'''Ability 4 button'''",
         AltCast = "'''Alternate Cast'''",
         AltCast = "'''Alternate Cast'''",
        -- UI & Menus
         OpenHeroSheet = "'''Open Shop button'''",
         OpenHeroSheet = "'''Open Shop button'''",
         PurchaseQuickbuy = "'''Purchase Quickbuy button'''",
         PurchaseQuickbuy = "'''Purchase Quickbuy button'''",
         Scoreboard = "'''Scoreboard button'''",
         Scoreboard = "'''Scoreboard button'''",
         OpenMainMenu = "'''Open Main Menu button'''",
         OpenMainMenu = "'''Open Main Menu button'''",
        -- Communication
         PushToTalk = "'''Voice Chat button'''",
         PushToTalk = "'''Voice Chat button'''",
        -- Misc & Hideout
         Pause = "'''Pause button'''",
         Pause = "'''Pause button'''",
         ReplayDeath = "'''Replay Death button'''",
         ReplayDeath = "'''Replay Death button'''",
Line 197: Line 202:
         HideoutCancelMatchmaking = "'''Cancel Matchmaking button'''",
         HideoutCancelMatchmaking = "'''Cancel Matchmaking button'''",
     }
     }
   
 
     -- Process citadel_binding/citadel_keybind tags to replace with keybind strings/templates
     text = text:gsub("{g:citadel_binding:'(.-)'}", function(key)
text = text:gsub("{g:citadel_binding:'(.-)'}", function(key)
        return KEYBINDS[key] or '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;">['..key..']</span>'
    local replacement = KEYBINDS[key]
    end)
    if replacement then
    text = text:gsub("{g:citadel_keybind:'(.-)'}", function(key)
        return replacement
        return KEYBINDS[key] or '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;">['..key..']</span>'
    else
    end)
        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing keybind - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
 
    end
     -- Force spaces around keybinds
end)
text = text:gsub("{g:citadel_keybind:'(.-)'}", function(key)
local replacement = KEYBINDS[key]
    if replacement then
        return replacement
    else
        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing keybind - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
    end
end)
     -- Force spaces around keybinds where the source data omits them
     text = text:gsub("'''([a-zA-Z])", "''' %1")
     text = text:gsub("'''([a-zA-Z])", "''' %1")
    -- Clean up extra spaces between a keybind and following punctuation
     text = text:gsub("'''%s+:", "''':")
     text = text:gsub("'''%s+:", "''':")


return frame:preprocess(text)
    return frame:preprocess(text)
end
end