1. Melee Mapping contest #3 - Poll is up! Vote for the best 4v4 melee maps!
    Dismiss Notice
  2. The 30th edition of the Modeling Contest is finally up! The Portable Buildings need your attention, so come along and have a blast!
    Dismiss Notice
  3. We have a new contest going on right now! Join the 11th Music Contest! You are to make a Cinematic modern sound-track for this contest, so come and compete with other people for fun.
    Dismiss Notice

What about a translation library?

Discussion in 'The Lab' started by Pinzu, Jan 11, 2019.

  1. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    1,064
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    It should be pretty straight forward to switch languages now that all (most) of the UI components are changeable. I am myself too lazy to do all of it, but I did create a basic manager for all strings using a table array, as well as translation binding to unit types.

    This library only allow you to configure different language definitions which you can use to update the UI using wrapper methods for SetUnitName, SetItemName, SetAbilityTooltip, and so on. It does however not automatically update strings when the language is changed (except for units entering map). Not sure if I should keep this feature, expand on it or remove it entierly.


    Code (vJASS):

    // WORK IN PROGRESS...

    library Translator uses Table, PArrayList optional WorldBounds
        globals
            constant integer NO_LANGUAGE                        = 0
            constant integer EN                                    = 1
            constant integer SE                                    = 2
            constant integer DE                                    = 3
         
            private constant integer DEFAULT_LANGUAGE             = 2
            private constant integer NUM_SUPPORTED_LANGUAGES     = 3
         
            private constant boolean REPLACE_ENTERING_UNITS_WITH_BASE_UNIT    = true
         
        endglobals
     
        // ~ ITEM-TYPE TRANSLATOR LIBRARY uses STRINGS TRANSLATOR
     
        struct ItemTranslator
            private static Table table
            private integer itemTypeId
         
            integer nameId
            integer descriptionId
            integer tooltipId
            integer extendedTooltipId
         
            static method create takes integer itemTypeId returns thistype
                local thistype this
                if thistype.table.has(itemTypeId) then
                    return 0
                endif
                set this = .allocate()
                set this.itemTypeId = itemTypeId
                set thistype.table[itemTypeId] = this
                return this
            endmethod
         
            method destroy takes nothing returns nothing
                call thistype.table.remove(.itemTypeId)
                set .nameId = 0
                set .descriptionId = 0
                set .tooltipId = 0
                set .extendedTooltipId = 0
                call .deallocate()
            endmethod
         
            static method get takes integer itemTypeId returns thistype
                return thistype.table[itemTypeId]
            endmethod
         
            static method has takes integer itemTypeId returns boolean
                return thistype.table.has(itemTypeId)
            endmethod
         
            private static method onInit takes nothing returns nothing
                set thistype.table = Table.create()
            endmethod
        endstruct
     
        // ~ ABILITY TRANSLATOR LIBRARY uses STRINGS TRANSLATOR
     
        private struct AbilityLevel
            integer tooltip
            integer activatedTooltip
            integer activatedExtendedTooltip
            integer extendedTooltip
            integer researchTooltip
            integer researchExtendedTooltip
         
            method onDestroy takes nothing returns nothing
                set .tooltip = 0
                set .activatedTooltip = 0
                set .activatedExtendedTooltip = 0
                set .extendedTooltip = 0
                set .researchTooltip = 0
                set .researchExtendedTooltip = 0
            endmethod
     
        //! runtextmacro SPELL_WRAPPER("Tooltip",                     " BlzSetAbilityTooltip",                     ".tooltip")      
        //! runtextmacro SPELL_WRAPPER("ActivatedTooltip",             " BlzSetAbilityActivatedTooltip",             ".activatedTooltip")  
        //! runtextmacro SPELL_WRAPPER("ActivatedExtendedTooltip",     " BlzSetAbilityActivatedExtendedTooltip",     ".activatedExtendedTooltip")  
        //! runtextmacro SPELL_WRAPPER("ExtendedTooltip",             " BlzSetAbilityExtendedTooltip",             ".extendedTooltip")  
        //! runtextmacro SPELL_WRAPPER("ResearcTooltip",             " BlzSetAbilityResearchTooltip",             ".researchTooltip")  
        //! runtextmacro SPELL_WRAPPER("ResearchExtendedTooltip",     " BlzSetAbilityResearchExtendedTooltip",     ".researchExtendedTooltip")  
     
        //! textmacro_once SPELL_WRAPPER takes NAME, NATIVE, VAR
            method set$NAME$ takes player p, integer abilCode, integer level returns nothing
                local string s =  Strings.load(p, $VAR$)
                if s != null then
                    call $NATIVE$(abilCode, s, level)
                endif
            endmethod
        //! endtextmacro
        endstruct
     
        struct AbilityTranslator
         
            private integer maxLevel
            private integer abilCode
            private Table abil
         
            static method create takes integer abilCode, integer maxLevel returns thistype
                local thistype this = .allocate()
                local integer i = 0
                set this.maxLevel = maxLevel
                set this.abilCode = abilCode
                set this.abil = Table.create()
                loop
                    exitwhen i == maxLevel
                    set this.abil[i] = AbilityLevel.create()
                    set i = i + 1
                endloop
                return this
            endmethod
         
            method destroy takes nothing returns nothing
                local AbilityLevel ab
                local integer i = 0
                loop
                    exitwhen i == .maxLevel
                    set ab = .abil[i]
                    call ab.destroy()
                    set i = i + 1
                endloop
                call .abil.destroy()
                call .deallocate()
            endmethod
         
        //! runtextmacro SPELL_TRANS_SETTERS("Tooltip",                     "tooltip")      
        //! runtextmacro SPELL_TRANS_SETTERS("ActivatedTooltip",             "activatedTooltip")  
        //! runtextmacro SPELL_TRANS_SETTERS("ActivatedExtendedTooltip",     "activatedExtendedTooltip")  
        //! runtextmacro SPELL_TRANS_SETTERS("ExtendedTooltip",             "extendedTooltip")  
        //! runtextmacro SPELL_TRANS_SETTERS("ResearchTooltip",                 "researchTooltip")  
        //! runtextmacro SPELL_TRANS_SETTERS("ResearchExtendedTooltip",     "researchExtendedTooltip")  
        //! textmacro_once SPELL_TRANS_SETTERS takes NAME, VAR
            method set$NAME$ takes integer level, integer stringId returns nothing
                local AbilityLevel ab
                if level <= 0 or level >= .maxLevel then
                    // Level out of bounds
                    return
                endif
                set ab = .abil[level - 1]
                set ab.$VAR$ = stringId
            endmethod
     
        //! endtextmacro
         
            method updatePlayerUI takes player p returns nothing
                local AbilityLevel ab
                local integer lvl = 1
                if p == GetLocalPlayer() then
                    loop
                        exitwhen lvl == .maxLevel + 1
                        set ab = .abil[lvl - 1]
                        call ab.setTooltip(p, .abilCode, lvl)
                        call ab.setActivatedTooltip(p, .abilCode, lvl)
                        call ab.setActivatedExtendedTooltip(p, .abilCode, lvl)
                        call ab.setExtendedTooltip(p, .abilCode, lvl)
                        call ab.setResearcTooltip(p, .abilCode, lvl)
                        call ab.setResearchExtendedTooltip(p, .abilCode, lvl)
                        set lvl = lvl + 1
                    endloop
                endif
            endmethod
         
            method updateUI takes nothing returns nothing
                local integer i = 0
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS                                            // needs fix
                    call .updatePlayerUI(Player(i))                                                // needs fix
                    set i = i + 1
                endloop
            endmethod
        endstruct
     
        // ~ UNIT-TYPE LANGUAGE LIMIT LIBRARY uses STRINGS TRANSLATOR
     
        struct UnitLimitTranslator
     
            static constant integer UNLIMITED = -1
         
            private static IntArrayList instances
            private integer languageId
            private integer unitTypeId
         
            integer normalLimit // Must be made player local instead of global
         
            static method create takes integer languageId, integer unitTypeId, integer normalLimit returns thistype
                local thistype this = .allocate()
                call thistype.instances.add(thistype.instances.size(), this)
                set this.languageId = languageId
                set this.unitTypeId = unitTypeId
                set this.normalLimit = normalLimit
                return this
            endmethod
         
            method destroy takes nothing returns nothing
                local integer index = thistype.instances.indexOf(this, true)
                call thistype.instances.remove(index)
                call .deallocate()
            endmethod
         
            static method updatePlayerUI takes player p returns nothing
                local thistype this
                local integer i = 0
                loop
                    exitwhen i == thistype.instances.size()
                    set this = thistype.instances[i]
                    if Strings.getPlayerLaguage(p) == this.languageId then
                        call SetPlayerTechMaxAllowedSwap(this.unitTypeId, this.normalLimit, p)
                    else
                        call SetPlayerTechMaxAllowedSwap(this.unitTypeId, 0, p)
                    endif
                    set i = i + 1
                endloop
            endmethod
            static method updateUI takes nothing returns nothing
                local integer i = 0
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS                                            // needs fix
                    call .updatePlayerUI(Player(i))                                                // needs fix
                    set i = i + 1
                endloop
            endmethod
         
            private static method onInit takes nothing returns nothing
                set thistype.instances = IntArrayList.create()
            endmethod
        endstruct
     
        // Need hooks for changing player unit type limits...
     
     
     
     
     
     
        private struct UnitTranslator
            static Table table
         
            private Table attached
         
            integer unitNameId
         
            integer baseUnitTypeId
         
            static method create takes integer unitTypeId returns thistype
                local thistype this
                if thistype.table.has(unitTypeId) then
                    return 0
                endif
                set this = .allocate()
                set this.baseUnitTypeId = unitTypeId
                set thistype.table[unitTypeId] = this
                return this
            endmethod
         
         
            method onLanguageChange takes player p returns nothing
         
            endmethod
         
            static method get takes integer unitTypeId returns thistype
                return thistype.table[unitTypeId]
            endmethod
         
            static method has takes integer unitTypeId returns boolean
                return thistype.table.has(unitTypeId)
            endmethod
         
            private static method onInit takes nothing returns nothing
                set thistype.table = Table.create()
            endmethod
         
        endstruct
     
        /*
     
            private static IntLinedList list
     
            integer unitTypeId
            integer languageId
            integer normalLimit
         
            static method create takes integer unitTypeId, integer languageId returns thistype
                local thistype this = .allocate()
                set this.unitTypeId = unitTypeId
                set this.languageId = languageId
                set this.normalLimit = -1    // unlimited
                //call thistype.list.add(list.size(), this)
                call this.refresh()
                return this
            endmethod
         
            method onPlayerLanguageChange takes player p, integer newPlayerLanguage returns nothing
                if this.languageId != newPlayerLanguage then
                    call SetPlayerTechMaxAllowedSwap(unitTypeId, 0, p)
                else
                    call SetPlayerTechMaxAllowedSwap(unitTypeId, this.normalLimit, p)
                endif
            endmethod
         
            method refresh takes nothing returns nothing
                local integer i = 0
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                 
                    set i = i + 1
                endloop
            endmethod
         
            static method getAll takes nothing returns IntLinkedList
                return thistype.list
            endmethod
         
            private static method onInit takes nothing returns nothing
                set thistype.list = IntLinkedList.create()
            endmethod
        endstruct
     
        function BindUnitTypeToLanguage takes integer unitTypeId, integer languageId returns nothing
         
        endfunction
        */

     
        struct Strings
            private static TableArray tableArray
            private static Table strToIndex
            private static integer array playerLanguage
            private static integer defaultLanguage
            private static Table table
            private static player array p
         
         
            private static method onLanguageChange takes player p returns nothing
         
            endmethod
         
            static method setPlayerLanguage takes player p, integer languageId returns nothing
                set thistype.playerLanguage[GetPlayerId(p)] = languageId
                set udg_languageChange = 1.
                set udg_languageChange = 0.
            endmethod
         
            static method getPlayerLaguage takes player p returns integer
                return thistype.playerLanguage[GetPlayerId(p)]
            endmethod
            static method setDefaultLanguage takes integer languageId returns nothing
                set thistype.defaultLanguage = languageId
            endmethod
         
            static method load takes player p, integer stringId returns string
                local string s = thistype.tableArray[thistype.playerLanguage[GetPlayerId(p)]].string[stringId]
                if s == null then
                    set s = thistype.tableArray[thistype.defaultLanguage].string[stringId]
                endif
                return s
            endmethod
            static method remove takes integer stringId, integer languageId returns nothing
                call thistype.tableArray[languageId].remove(stringId)
            endmethod
            static method save takes integer stringId, integer languageId, string text returns nothing
                set thistype.tableArray[languageId].string[stringId] = text
                set thistype.strToIndex[StringHash(text)] = languageId
            endmethod
         
            static method getStringId takes string text returns integer
                return thistype.strToIndex[StringHash(text)]
            endmethod
         
        /*
            UNIT -- TO BE MOVED
        */

     
            static method setUnitTypeName takes integer unitTypeId, integer stringId returns nothing
                local UnitTranslator trans
                if ItemTranslator.has(unitTypeId) then
                    set trans = ItemTranslator.get(unitTypeId)
                else
                    set trans = ItemTranslator.create(unitTypeId)
                endif
                set trans.unitNameId = stringId
            endmethod
         
            static method attachUnitTypeToBaseUnitType takes integer baseUnitTypeid, integer languageId, integer subUnitTypeId returns nothing
         
            endmethod
         
            static method bindUnitTypeToIndex takes integer unitTypeId, integer stringId returns nothing
                set thistype.table.integer[unitTypeId] = stringId
            endmethod
            static method getUnitTypeIndex takes integer unitTypeId returns integer
                return thistype.table.integer[unitTypeId]
            endmethod
         
        //! runtextmacro HANDLE_WRAPPER("BlzSetUnitName", "UnitName", "unit")  
        //! runtextmacro HANDLE_WRAPPER("BlzSetHeroProperName", "HeroProperName", "unit")
        //! runtextmacro HANDLE_WRAPPER("BlzSetItemName", "ItemName", "item")
        //! runtextmacro HANDLE_WRAPPER("BlzSetItemDescription", "ItemDescription", "item")
        //! runtextmacro HANDLE_WRAPPER("BlzSetItemTooltip", "ItemTooltip", "item")
        //! runtextmacro HANDLE_WRAPPER("BlzSetItemExtendedTooltip", "ItemExtendedTooltip", "item")
        //! textmacro_once HANDLE_WRAPPER takes NATIVE_CODE, API_CODE, TYPE
     
            static method setPlayer$API_CODE$ takes player p, $TYPE$ t, integer stringId returns nothing
                local string s = thistype.load(p, stringId)
                if s != null and p == GetLocalPlayer() then
                    call $NATIVE_CODE$(t, s)
                endif
            endmethod
     
            static method set$API_CODE$ takes $TYPE$ t, integer stringId returns nothing
                local integer i
                if stringId == 0 then
                    return
                endif
                set i = 0
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                    call thistype.setPlayer$API_CODE$(thistype.p[i], t, stringId)
                    set i = i + 1
                endloop
            endmethod
        //! endtextmacro
         
         
        //! runtextmacro ITEM_TYPE("Name", "nameId")
        //! runtextmacro ITEM_TYPE("Description", "descriptionId")
        //! runtextmacro ITEM_TYPE("Tooltip", "tooltipId")
        //! runtextmacro ITEM_TYPE("ExtendedTooltip", "extendedTooltipId")
     
        //! textmacro_once ITEM_TYPE takes NAME, VAR
     
            static method setItemType$NAME$ takes integer itemTypeId, integer stringId returns nothing
                local ItemTranslator trans
                if ItemTranslator.has(itemTypeId) then
                    set trans = ItemTranslator.get(itemTypeId)
                else
                    set trans = ItemTranslator.create(itemTypeId)
                endif
                set trans.$VAR$ = stringId
            endmethod
        //! endtextmacro
            static method translateItemByType takes item it returns nothing
                local ItemTranslator trans = ItemTranslator.get(GetItemTypeId(it))
                if trans == 0 then
                    return
                endif
                if trans.nameId != 0 then
                    call thistype.setItemName(it, trans.nameId)
                endif
                if trans.descriptionId != 0 then
                    call thistype.setItemDescription(it, trans.descriptionId)
                endif
                if trans.tooltipId != 0 then
                    call thistype.setItemTooltip(it, trans.tooltipId)
                endif
                if trans.extendedTooltipId != 0 then
                    call thistype.setItemExtendedTooltip(it, trans.extendedTooltipId)
                endif
            endmethod
         
            static method translatePlayerItemByType takes player p, item it returns nothing
                local ItemTranslator trans = ItemTranslator.get(GetItemTypeId(it))
                if trans == 0 then
                    return
                endif
                if trans.nameId != 0 then
                    call thistype.setPlayerItemName(p, it, trans.nameId)
                endif
                if trans.descriptionId != 0 then
                    call thistype.setPlayerItemDescription(p, it, trans.descriptionId)
                endif
                if trans.tooltipId != 0 then
                    call thistype.setPlayerItemTooltip(p, it, trans.tooltipId)
                endif
                if trans.extendedTooltipId != 0 then
                    call thistype.setPlayerItemExtendedTooltip(p, it, trans.extendedTooltipId)
                endif
            endmethod
         
            private static method onUnitEnter takes nothing returns boolean
                local unit u = GetTriggerUnit()
                call thistype.setUnitName(u, thistype.getUnitTypeIndex(GetUnitTypeId(u)))
                set u = null
                return false
            endmethod
         
            private static method iterAllUnits takes nothing returns nothing
                local unit u = GetEnumUnit()
                call thistype.setUnitName(u, thistype.getUnitTypeIndex(GetUnitTypeId(u)))
                set u = null
            endmethod
         
            private static method delayedSetup takes nothing returns nothing
                local timer t = GetExpiredTimer()
                local trigger trgUnitEnter = CreateTrigger()
                local rect world
                call DestroyTimer(t)
                set t = null
                static if LIBRARY_WorldBounds then
                    set world = WorldBounds.world
                else
                    set world = GetEntireMapRect()
                endif
                call TriggerRegisterEnterRectSimple(trgUnitEnter, world)
                call TriggerAddCondition(trgUnitEnter, Condition(function thistype.onUnitEnter))
                set bj_wantDestroyGroup = true
                call ForGroup(GetUnitsInRectAll(world), function thistype.iterAllUnits)
            endmethod  
     
            private static method onInit takes nothing returns nothing
                local integer i = 0
                local timer t = CreateTimer()
                call TimerStart(t, 0.1, false, function thistype.delayedSetup)
                set t = null
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                    set thistype.p[i] = Player(i)
                    call thistype.setPlayerLanguage(thistype.p[i], DEFAULT_LANGUAGE)
                    set i = i + 1
                endloop
                set thistype.strToIndex = Table.create()
                set thistype.tableArray = TableArray[0x2000]
                call thistype.setDefaultLanguage(DEFAULT_LANGUAGE)
                set thistype.table = Table.create()
            endmethod      
        endstruct
     
    endlibrary

     


    Example:

    • OnLanguageChange
      • Events
        • Dialog - A dialog button is clicked for languageDialog
      • Conditions
      • Actions
        • Set clickedDialog = (Clicked dialog button)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • clickedDialog Equal to langaugeDialogButton[0]
          • Then - Actions
            • Custom script: call Strings.setPlayerLanguage(GetTriggerPlayer(), EN)
            • Trigger - Run UpdateLanguage <gen> (ignoring conditions)
            • Skip remaining actions
          • Else - Actions
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • clickedDialog Equal to langaugeDialogButton[1]
          • Then - Actions
            • Custom script: call Strings.setPlayerLanguage(GetTriggerPlayer(), SE)
            • Trigger - Run UpdateLanguage <gen> (ignoring conditions)
            • Skip remaining actions
          • Else - Actions
    • ShowLanguagePicker
      • Events
        • Player - Player 1 (Red) skips a cinematic sequence
      • Conditions
      • Actions
        • Dialog - Show languageDialog for Player 1 (Red)

    Code (vJASS):

    scope UpdateLanguage initializer Init
        private function IterAllUnits takes nothing returns nothing
            local unit u = GetEnumUnit()
            call Strings.setUnitName(u, Strings.getUnitTypeIndex(GetUnitTypeId(u)))
            // check if its a hero and then try to update hero name...
            // check if its carrying items... and then try updating the items...
            set u = null
        endfunction
        private function Update takes nothing returns nothing
            local player p = GetTriggerPlayer()
            call BJDebugMsg(Strings.load(p, ID_LANG_CHANGED) + I2S(Strings.getPlayerLaguage(p)))
     
            // This must be done again every time the language changes
     
            call Strings.setPlayerAbilityActivatedTooltip(p, 'AHhb',             3, 1)
     
            call Strings.setPlayerAbilityActivatedTooltip(p, 'AHhb',             3, 1)
            call Strings.setPlayerAbilityActivatedExtendedTooltip(p, 'AHhb',     4, 1)
     
            call Strings.setPlayerAbilityExtendedTooltip(p, 'AHhb',             5, 1)
            call Strings.setPlayerAbilityExtendedTooltip(p, 'AHhb',             11, 2)
            call Strings.setPlayerAbilityExtendedTooltip(p, 'AHhb',             12, 3)
     
            call Strings.setPlayerAbilityResearchTooltip(p, 'AHhb',             6, 1)
            call Strings.setPlayerAbilityResearchExtendedTooltip(p, 'AHhb',     7, 1)
     
            call Strings.setPlayerAbilityTooltip(p, 'AHhb',                     8, 1)
            call Strings.setPlayerAbilityTooltip(p, 'AHhb',                     9, 2)
            call Strings.setPlayerAbilityTooltip(p, 'AHhb',                     10, 3)
     
            set bj_wantDestroyGroup = true
            call ForGroup(GetUnitsInRectAll(GetEntireMapRect()), function IterAllUnits)
     
            // Pick all items laying about and update them also...
     
        endfunction
     
        private function Init takes nothing returns nothing
            set gg_trg_UpdateLanguage = CreateTrigger()
            call TriggerAddAction(gg_trg_UpdateLanguage, function Update)
        endfunction
    endscope
     

    Code (vJASS):

    library Strings initializer Init uses Language
        globals
            constant integer ID_LANG_CHANGED     = 15
            constant integer ID_HELLO            = 16
            constant integer ID_GENERIC            = 17
        endglobals
        private function OnTime takes nothing returns nothing
            call BJDebugMsg(Strings.load(Player(0), ID_HELLO))
            call BJDebugMsg(Strings.load(Player(0), ID_GENERIC))
        endfunction
     
        private function Init takes nothing returns nothing
            local timer t = CreateTimer()
            call Strings.save(ID_HELLO, EN, "Hello there")
            call Strings.save(ID_HELLO, SE, "Hej där")
            call Strings.save(ID_GENERIC, EN, "Just a generic text")
            call Strings.save(ID_GENERIC, SE, "Endast en generisk text")
     
            // Footmen test
            call Strings.save(2, EN, "Footman")
            call Strings.save(2, SE, "Fotsoldat")
            call Strings.save(2, SE, "Fotsoldat")
     
            call Strings.bindUnitTypeToIndex('hfoo', 2)
     
     
            call Strings.save(3, EN, "BlzSetAbilityActivatedTooltip")
            call Strings.save(3, SE, "BlzSetAbilityActivatedTooltip")
     
            call Strings.save(4, EN, "BlzSetAbilityActivatedExtendedTooltip")
            call Strings.save(4, SE, "BlzSetAbilityActivatedExtendedTooltip")
     
            call Strings.save(5, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA1> or deal half damage to an enemy Undead unit.")
            call Strings.save(5, SE, "Ett heligt ljus som kan återställa <AHhb,DataA1> hälsa eller skada en levande död fiende hälften så mycket.")
     
     
     
            call Strings.save(6, EN, "Learn Holy Ligh|cffffcc00t|r - [|cffffcc00Level %d|r]")
            call Strings.save(6, SE, "Lär Helig|cffffcc00t|r ljus - [|cffffcc00Nivå %d|r]")
     
            call Strings.save(7, EN, "A holy light that can heal a friendly living unit or damage an enemy Undead unit. |n|n|cffffcc00Level 1|r - Heals for <AHhb,DataA1> hp. |n|cffffcc00Level 2|r - Heals for <AHhb,DataA2> hp. |n|cffffcc00Level 3|r - Heals for <AHhb,DataA3> hp. ")
            call Strings.save(7, SE, "THIS IS NOT SHOWING")
     
            /*
            Ett heligt ljus som kan heala en allierad levadande enhet eller åsamka skada till en levande död. |n|n|cffffcc00Nivå 1|r - Healar  <AHhb,DataA1> hp. |n|cffffcc00Nivå 2|r - Healar <AHhb,DataA2> hp. |n|cffffcc00Nivå 3|r - Healar <AHhb,DataA3> hp.
            */

     
            call Strings.save(8, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 1|r]")
            call Strings.save(8, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 1|r]")
            call Strings.save(9, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 2|r]")
            call Strings.save(9, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 2|r]")
            call Strings.save(10, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 3|r]")
            call Strings.save(10, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 3|r]")
     
            call Strings.save(11, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA2> or deal half damage to an enemy Undead unit.")
            call Strings.save(11, SE, "Ett heligt ljus som kan återställa <AHhb,DataA2> hälsa eller skada en levande död fiende hälften så mycket.")
            call Strings.save(12, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA3> or deal half damage to an enemy Undead unit.")
            call Strings.save(12, SE, "Ett heligt ljus som kan återställa <AHhb,DataA3> hälsa eller skada en levande död fiende hälften så mycket.")
     
            call Strings.save(ID_LANG_CHANGED, EN, "Language changed to: ")
            call Strings.save(ID_LANG_CHANGED, SE, "Språk ändrat till: ")
     
            call TimerStart(t, 8.0, true, function OnTime)
        endfunction
    endlibrary

     


    Not sure exactly how to handle the HeroProperNames, though one can use the wrapper method to override the Object editor proper name.

    If you happen to have an idea of why the extended research tooltip is being clipped do tell.

    Finally, do post the natives that let you change labels of unit/items being sold/trained, can't seem to find them.

    upload_2019-1-11_11-30-56.png
     

    Attached Files:

    Last edited: Jan 13, 2019
  2. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,323
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    I think it's a cool idea, but without some form of automation it seems cumbersome.
    Also why not follow some existing standard like i18n?
     
  3. Tasyen

    Tasyen

    Joined:
    Jul 18, 2010
    Messages:
    938
    Resources:
    9
    Tools:
    1
    Maps:
    2
    Spells:
    5
    Tutorials:
    1
    Resources:
    9
    Nice work but to me it looks like you are recreating wts-Files in vJass, unsure how effective that is.

    Afterall WTS-Files already have multilanguage support which (almost) nobody seems to use: Reason could be it's a quite hidden undocumented feature, it is slow and most projects are english-only anyway.

    Wouldn't underestimate the effort to create a MultiLingual map.
    You have to externalize all user relevant text of your code and write 1 clone for each supported language, that is alot of work.
    Object Editor Data is already externalized lucky.
    GUI people have it easier in that aspect cause their raw text is already externalized except for this fused String actions. (String A + String B)​
    With the wts-Files you have the advantage that you can send it to anyone and he could translate it, even if he can not read (v)jass. Cause the format of wts is quite easy.
    Also warcraft 3 manages the usage of wts automatically.
     
    Last edited: Jan 11, 2019
  4. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    1,064
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    Im uncertin in what way I'm breaking the standard except for the IDs being unlabeled. Care to elaborate?

    Think the problem with merchants can be solved by limiting units and items available to certain players, and simply translating in the object editor, then when they enter the map they are manageable again.

    The problem with automation is that you can't keep track of all existing items and displayed text messages/labels. The user would still have to handle that himself so my thinking is, either it can automate all (unit types, hero names, items, item types, abilities) or nothing. But maybe my thinking isn't straight here. I could add a real variable event change for when language is changed.

    I'm not sure you can change the language in game, are you talking about shpping different coppies oft he same map?
     
    Last edited: Jan 12, 2019
  5. WaterKnight

    WaterKnight

    Joined:
    Aug 18, 2009
    Messages:
    4,018
    Resources:
    5
    Maps:
    1
    Tutorials:
    4
    Resources:
    5
    And here I thought you would use a translator service like Google Translator to convert all the strings :p The original data could, of course, be mined. Maybe you don't have to update everything in one go but on demand.
     
  6. Tasyen

    Tasyen

    Joined:
    Jul 18, 2010
    Messages:
    938
    Resources:
    9
    Tools:
    1
    Maps:
    2
    Spells:
    5
    Tutorials:
    1
    Resources:
    9
    You shifft different copies, if you have inlined strings and got rid of the wts-File for whatever reason ,proably loading speed. Then you can translate the txt files and the game will still detect them as the same map. Problematic may text hardcoded in your code.

    If one is using wts-Files one can add 1 wts-file for each supported language into the map (by mpq Editor) each of the wts files is named "war3map<Lcid>.wts". the default neutral one is just "war3map.wts" it is used if none for the own language is found.

    German for example is "war3map#0407.wts" ("#0407" managed by the mpq if one uses the set localize button problematic is adding them with the mpq editors gui, if you added the neutral english one first).
    If there is "war3map.wts" and "war3map#0407.wts" in a map, any german warcraft 3 player will load the triggerstrings from "war3map#0407.wts" while others see the text from "war3map.wts".

    Edit: This wts-File feature was broken from V1.29 to V1.30.1
     
    Last edited: Jan 12, 2019
  7. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    1,064
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    I finished Ability translation but I'm having problems with string length limit and especially ResearchExtendedTooltip is giving me troubles. Sometimes its clipped or show strange texts...

    upload_2019-1-13_16-31-29.png

    This is the relevant part of the library the Strings.load method you can lookup in the opening post, but its basically just loading from a table array.
    Code (vJASS):

    // ~ ABILITY TRANSLATOR LIBRARY uses STRINGS TRANSLATOR
     
        private struct AbilityLevel
            integer tooltip
            integer activatedTooltip
            integer activatedExtendedTooltip
            integer extendedTooltip
            integer researchTooltip
            integer researchExtendedTooltip
     
            method onDestroy takes nothing returns nothing
                set .tooltip = 0
            endmethod
     
        //! runtextmacro SPELL_WRAPPER("Tooltip",                     " BlzSetAbilityTooltip",                     ".tooltip")
        //! runtextmacro SPELL_WRAPPER("ActivatedTooltip",             " BlzSetAbilityActivatedTooltip",             ".activatedTooltip")
        //! runtextmacro SPELL_WRAPPER("ActivatedExtendedTooltip",     " BlzSetAbilityActivatedExtendedTooltip",     ".activatedExtendedTooltip")
        //! runtextmacro SPELL_WRAPPER("ExtendedTooltip",             " BlzSetAbilityExtendedTooltip",             ".extendedTooltip")
        //! runtextmacro SPELL_WRAPPER("ResearcTooltip",             " BlzSetAbilityResearchTooltip",             ".researchTooltip")
        //! runtextmacro SPELL_WRAPPER("ResearchExtendedTooltip",     " BlzSetAbilityResearchExtendedTooltip",     ".researchExtendedTooltip")
     
        //! textmacro_once SPELL_WRAPPER takes NAME, NATIVE, VAR
            method set$NAME$ takes player p, integer abilCode, integer level returns nothing
                local string s =  Strings.load(p, $VAR$)
                if s != null then
                    call $NATIVE$(abilCode, s, level)
                endif
            endmethod
        //! endtextmacro
        endstruct
     
        struct AbilityTranslator
     
            private integer maxLevel
            private integer abilCode
            private Table abil
     
            static method create takes integer abilCode, integer maxLevel returns thistype
                local thistype this = .allocate()
                local integer i = 0
                set this.maxLevel = maxLevel
                set this.abilCode = abilCode
                set this.abil = Table.create()
                loop
                    exitwhen i == maxLevel
                    set this.abil[i] = AbilityLevel.create()
                    set i = i + 1
                endloop
                return this
            endmethod
     
            method destroy takes nothing returns nothing
                local AbilityLevel ab
                local integer i = 0
                loop
                    exitwhen i == .maxLevel
                    set ab = .abil[i]
                    call ab.destroy()
                    set i = i + 1
                endloop
                call .abil.destroy()
                call .deallocate()
            endmethod
     
        //! runtextmacro SPELL_TRANS_SETTERS("Tooltip",                     "tooltip")
        //! runtextmacro SPELL_TRANS_SETTERS("ActivatedTooltip",             "activatedTooltip")
        //! runtextmacro SPELL_TRANS_SETTERS("ActivatedExtendedTooltip",     "activatedExtendedTooltip")
        //! runtextmacro SPELL_TRANS_SETTERS("ExtendedTooltip",             "extendedTooltip")
        //! runtextmacro SPELL_TRANS_SETTERS("ResearchTooltip",                 "researchTooltip")
        //! runtextmacro SPELL_TRANS_SETTERS("ResearchExtendedTooltip",     "researchExtendedTooltip")
        //! textmacro_once SPELL_TRANS_SETTERS takes NAME, VAR
            method set$NAME$ takes integer level, integer stringId returns nothing
                local AbilityLevel ab
                if level <= 0 or level >= .maxLevel then
                    // Level out of bounds
                    return
                endif
                set ab = .abil[level - 1]
                set ab.$VAR$ = stringId
            endmethod
     
        //! endtextmacro
     
            method updatePlayerUI takes player p returns nothing
                local AbilityLevel ab
                local integer lvl = 1
                if p == GetLocalPlayer() then
                    loop
                        exitwhen lvl == .maxLevel + 1
                        set ab = .abil[lvl - 1]
                        call ab.setTooltip(p, .abilCode, lvl)
                        call ab.setActivatedTooltip(p, .abilCode, lvl)
                        call ab.setActivatedExtendedTooltip(p, .abilCode, lvl)
                        call ab.setExtendedTooltip(p, .abilCode, lvl)
                        call ab.setResearcTooltip(p, .abilCode, lvl)
                        call ab.setResearchExtendedTooltip(p, .abilCode, lvl)
                        set lvl = lvl + 1
                    endloop
                endif
            endmethod
     
            method updateUI takes nothing returns nothing
                local integer i = 0
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS                                            // needs fix
                    call .updatePlayerUI(Player(i))                                                // needs fix
                    set i = i + 1
                endloop
            endmethod
        endstruct


    Here is example usage of how to set it up and how to change translation when a player selects a new language.
    Code (vJASS):

    scope SpellsExample initializer Init
     
        globals
            constant integer AHhb                                 = 0
     
            constant integer ID_AHHB_TOOLTIP_1                    = 700
            constant integer ID_AHHB_TOOLTIP_2                    = 701
            constant integer ID_AHHB_TOOLTIP_3                    = 702
            constant integer ID_AHHB_TOOLTIP_LEARN_0            = 704
            constant integer ID_AHHB_TOOLTIP_EXT_1                = 705
            constant integer ID_AHHB_TOOLTIP_EXT_2                = 706
            constant integer ID_AHHB_TOOLTIP_EXT_3                = 707
     
            constant integer ID_AHHB_TOOLTIP_LEARN_EXT_1        = 708
            constant integer ID_AHHB_TOOLTIP_LEARN_EXT_2        = 709
            constant integer ID_AHHB_TOOLTIP_LEARN_EXT_3        = 710
     
            AbilityTranslator array spellTranslator
        endglobals
     
        private function OnPlayerLanguageChange takes nothing returns nothing
            local player p = GetTriggerPlayer()
            call abilityTranslator[AHhb].updatePlayerUI(p)    // Used to apply changes for triggering player
     
            // Do your other spells here
     
            set p = null
        endfunction
        private function Init takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_languageChange", EQUAL, 1)
            call TriggerAddAction(t, function OnPlayerLanguageChange)
     
            // Holy Light with 3 levels
            set abilityTranslator[AHhb] = AbilityTranslator.create('AHhb', 3)
     
            call Strings.save(ID_AHHB_TOOLTIP_1, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 1|r]")
            call Strings.save(ID_AHHB_TOOLTIP_1, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 1|r]")
            call Strings.save(ID_AHHB_TOOLTIP_2, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 2|r]")
            call Strings.save(ID_AHHB_TOOLTIP_2, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 2|r]")
            call Strings.save(ID_AHHB_TOOLTIP_3, EN, "Holy Ligh|cffffcc00t|r - [|cffffcc00Level 3|r]")
            call Strings.save(ID_AHHB_TOOLTIP_3, SE, "Helig|cffffcc00t|r Ljus - [|cffffcc00Nivå 3|r]")
            call abilityTranslator[AHhb].setTooltip(1, ID_AHHB_TOOLTIP_1)
            call abilityTranslator[AHhb].setTooltip(2, ID_AHHB_TOOLTIP_1)
            call abilityTranslator[AHhb].setTooltip(3, ID_AHHB_TOOLTIP_1)
     
            call Strings.save(ID_AHHB_TOOLTIP_LEARN_0, EN, "Learn Holy Ligh|cffffcc00t|r - [|cffffcc00Level %d|r]")
            call Strings.save(ID_AHHB_TOOLTIP_LEARN_0, SE, "Lär Helig|cffffcc00t|r ljus - [|cffffcc00Nivå %d|r]")
            call abilityTranslator[AHhb].setResearchTooltip(1, ID_AHHB_TOOLTIP_LEARN_0)
            call abilityTranslator[AHhb].setResearchTooltip(2, ID_AHHB_TOOLTIP_LEARN_0)
            call abilityTranslator[AHhb].setResearchTooltip(3, ID_AHHB_TOOLTIP_LEARN_0)
     
            call Strings.save(ID_AHHB_TOOLTIP_EXT_1, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA1> or deal half damage to an enemy Undead unit.")
            call Strings.save(ID_AHHB_TOOLTIP_EXT_1, SE, "Ett heligt ljus som kan återställa <AHhb,DataA1> hälsa eller skada en levande död fiende hälften så mycket.")
            call Strings.save(ID_AHHB_TOOLTIP_EXT_2, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA2> or deal half damage to an enemy Undead unit.")
            call Strings.save(ID_AHHB_TOOLTIP_EXT_2, SE, "Ett heligt ljus som kan återställa <AHhb,DataA2> hälsa eller skada en levande död fiende hälften så mycket.")
            call Strings.save(ID_AHHB_TOOLTIP_EXT_3, EN, "A holy light that can heal a friendly living unit for <AHhb,DataA3> or deal half damage to an enemy Undead unit.")
            call Strings.save(ID_AHHB_TOOLTIP_EXT_3, SE, "Ett heligt ljus som kan återställa <AHhb,DataA3> hälsa eller skada en levande död fiende hälften så mycket.")
            call abilityTranslator[AHhb].setExtendedTooltip(1, ID_AHHB_TOOLTIP_EXT_1)
            call abilityTranslator[AHhb].setExtendedTooltip(2, ID_AHHB_TOOLTIP_EXT_2)
            call abilityTranslator[AHhb].setExtendedTooltip(3, ID_AHHB_TOOLTIP_EXT_3)
     
            // I think this is not showing properly due to size limit, not sure what can be done to mitigate this...
            //call Strings.save(ID_AHHB_TOOLTIP_LEARN_EXT_1, EN, "A holy light that can heal a friendly living unit or damage an enemy Undead unit. |n|n|cffffcc00Level 1|r - Heals for <AHhb,DataA1> //hp. |n|cffffcc00Level 2|r - Heals for <AHhb,DataA2> hp. |n|cffffcc00Level 3|r - Heals for <AHhb,DataA3> hp. ")
     
            //call Strings.save(ID_AHHB_TOOLTIP_LEARN_EXT_1, SE, "Ett heligt ljus som kan heala en allierad eller leverera skada mot en levande död. |n|n|cffffcc00Nivå 1|r - Healar <AHhb,DataA1> hp. //|n|cffffcc00Nivå 2|r - Healar <AHhb,DataA2> hp. |n|cffffcc00Nivå 3|r - Healar <AHhb,DataA3> hp.")
     
            call Strings.save(ID_AHHB_TOOLTIP_LEARN_EXT_1, EN, "A holy light that can heal a friendly living unit or damage an enemy Undead unit.")
            call Strings.save(ID_AHHB_TOOLTIP_LEARN_EXT_1, SE, "Ett heligt ljus som kan heala en allierad eller leverera skada mot en levande död.")
            call abilityTranslator[AHhb].setResearchExtendedTooltip(1, ID_AHHB_TOOLTIP_LEARN_EXT_1)
            call abilityTranslator[AHhb].setResearchExtendedTooltip(2, ID_AHHB_TOOLTIP_LEARN_EXT_1)
            call abilityTranslator[AHhb].setResearchExtendedTooltip(3, ID_AHHB_TOOLTIP_LEARN_EXT_1)
     
            call abilityTranslator[AHhb].updateUI()    // Used to apply changes to all players
        endfunction
     
    endscope


    I could need some help in figuring out a method for extending the string length in a translation and why the research tooltip is not showing up correctly. I've provided a test map for you to look at.

    You are my only hope. :)

    --------------------------------

    I also added UnitLimtiTranslator so that trained units can be restricted depending on which language the user has. It's problematic in the sense that one would need to maintain multiple object editor units for each real unit. When they enter the map they also need to be replaced with the "real unit". Aditionally the tech-tree poses another problem which I wont touch. Any feedback on most convenient implementation?

    upload_2019-1-13_17-47-5.png
    upload_2019-1-13_17-50-0.png
    Note: This is also applicable on items sold.

    And here is an example of how toggling between the two would look like:
    Code (vJASS):

    scope TypeLimitExamnple initializer Init
     
        globals
            private TypeLimitTranslator languageLimiter
        endglobals
     
        private function OnChange takes nothing returns nothing
            local player p = GetTriggerPlayer()
            call TypeLimitTranslator.updateAllPlayerUI(p)
            set p = null
        endfunction
     
        private function Init takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_languageChange", EQUAL, 1)
            call TriggerAddAction(t, function OnChange)
       
            // Unit Types
            set languageLimiter = TypeLimitTranslator.create(EN, 'hfoo', TypeLimitTranslator.UNLIMITED)
            set languageLimiter = TypeLimitTranslator.create(SE, 'h000', TypeLimitTranslator.UNLIMITED)
            set languageLimiter = TypeLimitTranslator.create(EN, 'hrif', TypeLimitTranslator.UNLIMITED)
            set languageLimiter = TypeLimitTranslator.create(SE, 'h001', TypeLimitTranslator.UNLIMITED)
       
            // Item Types
            set languageLimiter = TypeLimitTranslator.create(EN, 'mcri', TypeLimitTranslator.UNLIMITED)
            set languageLimiter = TypeLimitTranslator.create(SE, 'I000', TypeLimitTranslator.UNLIMITED)
       
            call TypeLimitTranslator.updateAllUI()
        endfunction
     
    endscope
     


    Relevant part of the library:
    Code (vJASS):

    // ~ TYPE-LIMIT LIBRARY uses STRINGS TRANSLATOR
     
        struct TypeLimitTranslator
     
            static constant integer UNLIMITED = -1
       
            private static IntArrayList instances
            private integer languageId
            private integer typeId
       
            integer normalLimit // Must be made player local instead of global
       
            static method create takes integer languageId, integer typeId, integer normalLimit returns thistype
                local thistype this = .allocate()
                call thistype.instances.add(thistype.instances.size(), this)
                set this.languageId = languageId
                set this.typeId = typeId
                set this.normalLimit = normalLimit
                return this
            endmethod
       
            method destroy takes nothing returns nothing
                local integer index = thistype.instances.indexOf(this, true)
                call thistype.instances.remove(index)
                call .deallocate()
            endmethod
       
            static method updateAllPlayerUI takes player p returns nothing
                local thistype this
                local integer i = 0
                loop
                    exitwhen i == thistype.instances.size()
                    set this = thistype.instances[i]
                    if Strings.getPlayerLaguage(p) == this.languageId then
                        call ReducePlayerTechMaxAllowed(p, this.typeId, this.normalLimit)
                    else
                        call ReducePlayerTechMaxAllowed(p, this.typeId, 0)
                    endif
                    set i = i + 1
                endloop
            endmethod
            static method updateAllUI takes nothing returns nothing
                local integer i = 0
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS                                            // needs fix
                    call .updateAllPlayerUI(Player(i))                                            // needs fix
                    set i = i + 1
                endloop
            endmethod
       
            private static method onInit takes nothing returns nothing
                set thistype.instances = IntArrayList.create()
            endmethod
        endstruct
     
        struct UnitTypeTranslator
     
     
        endstruct
     
        // Need hooks for changing player unit type limits...
     
     


    The problem with limiting types currently is that you cannot limit it to neutral merchants even if it works fine for player buildings (as seen below). The reason is that the limit native appears to only work on units trained and items made and not on units sold and items made.

    I'm not sure yet of what the best method is to circumvent this problem.
    upload_2019-1-13_18-35-22.png
     

    Attached Files:

    Last edited: Jan 13, 2019