• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

What about a translation library?

Status
Not open for further replies.
Level 15
Joined
Nov 30, 2007
Messages
1,202
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.


JASS:
// 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)
JASS:
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
JASS:
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
 

Attachments

  • upload_2019-1-11_11-33-5.png
    upload_2019-1-11_11-33-5.png
    137.3 KB · Views: 65
Last edited:
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:
Level 15
Joined
Nov 30, 2007
Messages
1,202
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?

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.

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.[/QUOTA]
I'm not sure you can change the language in game, are you talking about shpping different coppies oft he same map?
 
Last edited:
I'm not sure you can change the language in game, are you talking about shpping different coppies oft he same map?
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:
  • Like
Reactions: ~El
Level 15
Joined
Nov 30, 2007
Messages
1,202
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.
JASS:
// ~ 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.
JASS:
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:
JASS:
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:
JASS:
// ~ 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
 

Attachments

  • Translator 0.03.w3x
    63.5 KB · Views: 24
Last edited:
Status
Not open for further replies.
Top