Ticket #67: gui-i18n.patch

File gui-i18n.patch, 247.7 KB (added by Adrián Chaves, 11 years ago)

GUI internationalization (patch 2/3)

  • new file inaries/data/mods/public/globalscripts/l10n.js

    diff --git a/binaries/data/mods/public/globalscripts/l10n.js b/binaries/data/mods/public/globalscripts/l10n.js
    new file mode 100644
    index 0000000..3796687
    - +  
     1var g_translations = [];
     2var g_pluralTranslations = {};
     3var g_translationsWithContext = {};
     4var g_pluralTranslationsWithContext = {};
     5
     6
     7// Translates the specified English message into the current language.
     8//
     9// This function relies on the g_translations cache when possible. You should use this function instead of
     10// Engine.translate() whenever you can to minimize the number of C++ calls and string conversions involved.
     11function translate(message)
     12{
     13    var translation = g_translations[message];
     14    if (!translation)
     15        return g_translations[message] = Engine.translate(message);
     16    return translation;
     17}
     18
     19
     20// Translates the specified English message into the current language for the specified number.
     21//
     22// This function relies on the g_pluralTranslations cache when possible. You should use this function instead of
     23// Engine.translatePlural() whenever you can to minimize the number of C++ calls and string conversions involved.
     24function translatePlural(singularMessage, pluralMessage, number)
     25{
     26    var translation = g_pluralTranslations[singularMessage];
     27    if (!translation)
     28        g_pluralTranslations[singularMessage] = {};
     29
     30    var pluralTranslation = g_pluralTranslations[singularMessage][number];
     31    if (!pluralTranslation)
     32        return g_pluralTranslations[singularMessage][number] = Engine.translateWithContext(singularMessage, pluralMessage, number);
     33
     34    return pluralTranslation;
     35}
     36
     37
     38// Translates the specified English message into the current language for the specified context.
     39//
     40// This function relies on the g_translationsWithContext cache when possible. You should use this function instead of
     41// Engine.translateWithContext() whenever you can to minimize the number of C++ calls and string conversions involved.
     42function translateWithContext(context, message)
     43{
     44    var translationContext = g_translationsWithContext[context];
     45    if (!translationContext)
     46        g_translationsWithContext[context] = {}
     47
     48    var translationWithContext = g_translationsWithContext[context][message];
     49    if (!translationWithContext)
     50        return g_translationsWithContext[context][message] = Engine.translateWithContext(context, message);
     51
     52    return translationWithContext;
     53}
     54
     55
     56// Translates the specified English message into the current language for the specified context and number.
     57//
     58// This function relies on the g_pluralTranslationsWithContext cache when possible. You should use this function instead of
     59// Engine.translateWithContext() whenever you can to minimize the number of C++ calls and string conversions involved.
     60function translatePluralWithContext(context, singularMessage, pluralMessage, number)
     61{
     62    var translationContext = g_pluralTranslationsWithContext[context];
     63    if (!translationContext)
     64        g_pluralTranslationsWithContext[context] = {};
     65
     66    var translationWithContext = g_pluralTranslationsWithContext[context][singularMessage];
     67    if (!translationWithContext)
     68        g_pluralTranslationsWithContext[context][singularMessage] = {};
     69
     70    var pluralTranslationWithContext = g_pluralTranslationsWithContext[context][singularMessage][number];
     71    if (!pluralTranslationWithContext)
     72        return g_pluralTranslationsWithContext[context][message][number] = Engine.translateWithContext(context, singularMessage, pluralMessage, number);
     73
     74    return pluralTranslationWithContext;
     75}
     76
     77
     78// Translates any string value in the specified JavaScript object that is associated with a key included in
     79// the specified keys array.
     80function translateObjectKeys(object, keys) {
     81    for (property in object)
     82    {
     83        if (keys.indexOf(property) > -1)
     84        {
     85            if (object[property] != "" && object[property] !== undefined)
     86            {
     87                object[property] = translate(object[property]);
     88            }
     89        }
     90        else
     91        {
     92            // From http://stackoverflow.com/a/7390612/939364
     93            var variableType = ({}).toString.call(object[property]).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
     94            if (variableType === "object" || variableType == "array")
     95            {
     96                translateObjectKeys(object[property], keys);
     97            }
     98        }
     99    }
     100}
  • new file inaries/data/mods/public/globalscripts/sprintf.js

    diff --git a/binaries/data/mods/public/globalscripts/sprintf.js b/binaries/data/mods/public/globalscripts/sprintf.js
    new file mode 100644
    index 0000000..a641ec1
    - +  
     1/**
     2sprintf() for JavaScript 0.7-beta1
     3http://www.diveintojavascript.com/projects/javascript-sprintf
     4
     5Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
     6All rights reserved.
     7
     8Redistribution and use in source and binary forms, with or without
     9modification, are permitted provided that the following conditions are met:
     10    * Redistributions of source code must retain the above copyright
     11      notice, this list of conditions and the following disclaimer.
     12    * Redistributions in binary form must reproduce the above copyright
     13      notice, this list of conditions and the following disclaimer in the
     14      documentation and/or other materials provided with the distribution.
     15    * Neither the name of sprintf() for JavaScript nor the
     16      names of its contributors may be used to endorse or promote products
     17      derived from this software without specific prior written permission.
     18
     19THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     20ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     21WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     22DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
     23DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     24(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     25LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     26ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     28SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29
     30
     31Changelog:
     322010.09.06 - 0.7-beta1
     33  - features: vsprintf, support for named placeholders
     34  - enhancements: format cache, reduced global namespace pollution
     35
     362010.05.22 - 0.6:
     37 - reverted to 0.4 and fixed the bug regarding the sign of the number 0
     38 Note:
     39 Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
     40 who warned me about a bug in 0.5, I discovered that the last update was
     41 a regress. I appologize for that.
     42
     432010.05.09 - 0.5:
     44 - bug fix: 0 is now preceeded with a + sign
     45 - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
     46 - switched from GPL to BSD license
     47
     482007.10.21 - 0.4:
     49 - unit test and patch (David Baird)
     50
     512007.09.17 - 0.3:
     52 - bug fix: no longer throws exception on empty paramenters (Hans Pufal)
     53
     542007.09.11 - 0.2:
     55 - feature: added argument swapping
     56
     572007.04.03 - 0.1:
     58 - initial release
     59**/
     60
     61var sprintf = (function() {
     62    function get_type(variable) {
     63        return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
     64    }
     65    function str_repeat(input, multiplier) {
     66        for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
     67        return output.join('');
     68    }
     69
     70    var str_format = function() {
     71        if (!str_format.cache.hasOwnProperty(arguments[0])) {
     72            str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
     73        }
     74        return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
     75    };
     76
     77    str_format.format = function(parse_tree, argv) {
     78        var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
     79        for (i = 0; i < tree_length; i++) {
     80            node_type = get_type(parse_tree[i]);
     81            if (node_type === 'string') {
     82                output.push(parse_tree[i]);
     83            }
     84            else if (node_type === 'array') {
     85                match = parse_tree[i]; // convenience purposes only
     86                if (match[2]) { // keyword argument
     87                    arg = argv[cursor];
     88                    for (k = 0; k < match[2].length; k++) {
     89                        if (!arg.hasOwnProperty(match[2][k])) {
     90                            throw(sprintf(Engine.translate('[sprintf] property "%s" does not exist'), match[2][k]));
     91                        }
     92                        arg = arg[match[2][k]];
     93                    }
     94                }
     95                else if (match[1]) { // positional argument (explicit)
     96                    arg = argv[match[1]];
     97                }
     98                else { // positional argument (implicit)
     99                    arg = argv[cursor++];
     100                }
     101
     102                if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
     103                    throw(sprintf(Engine.translate('[sprintf] expecting number but found %s'), get_type(arg)));
     104                }
     105                switch (match[8]) {
     106                    case 'b': arg = arg.toString(2); break;
     107                    case 'c': arg = String.fromCharCode(arg); break;
     108                    case 'd': arg = parseInt(arg, 10); break;
     109                    case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
     110                    case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
     111                    case 'o': arg = arg.toString(8); break;
     112                    case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
     113                    case 'u': arg = Math.abs(arg); break;
     114                    case 'x': arg = arg.toString(16); break;
     115                    case 'X': arg = arg.toString(16).toUpperCase(); break;
     116                }
     117                arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
     118                pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
     119                pad_length = match[6] - String(arg).length;
     120                pad = match[6] ? str_repeat(pad_character, pad_length) : '';
     121                output.push(match[5] ? arg + pad : pad + arg);
     122            }
     123        }
     124        return output.join('');
     125    };
     126
     127    str_format.cache = {};
     128
     129    str_format.parse = function(fmt) {
     130        var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
     131        while (_fmt) {
     132            if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
     133                parse_tree.push(match[0]);
     134            }
     135            else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
     136                parse_tree.push('%');
     137            }
     138            else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
     139                if (match[2]) {
     140                    arg_names |= 1;
     141                    var field_list = [], replacement_field = match[2], field_match = [];
     142                    if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
     143                        field_list.push(field_match[1]);
     144                        while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
     145                            if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
     146                                field_list.push(field_match[1]);
     147                            }
     148                            else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
     149                                field_list.push(field_match[1]);
     150                            }
     151                            else {
     152                                throw(Engine.translate('[sprintf] huh?'));
     153                            }
     154                        }
     155                    }
     156                    else {
     157                        throw(Engine.translate('[sprintf] huh?'));
     158                    }
     159                    match[2] = field_list;
     160                }
     161                else {
     162                    arg_names |= 2;
     163                }
     164                if (arg_names === 3) {
     165                    throw(Engine.translate('[sprintf] mixing positional and named placeholders is not (yet) supported'));
     166                }
     167                parse_tree.push(match);
     168            }
     169            else {
     170                throw(Engine.translate('[sprintf] huh?'));
     171            }
     172            _fmt = _fmt.substring(match[0].length);
     173        }
     174        return parse_tree;
     175    };
     176
     177    return str_format;
     178})();
     179
     180var vsprintf = function(fmt, argv) {
     181    argv.unshift(fmt);
     182    return sprintf.apply(null, argv);
     183};
  • binaries/data/mods/public/gui/aiconfig/aiconfig.js

    diff --git a/binaries/data/mods/public/gui/aiconfig/aiconfig.js b/binaries/data/mods/public/gui/aiconfig/aiconfig.js
    index 76e6e20..1ce0a98 100644
    a b function init(settings)  
    55{
    66    g_Callback = settings.callback;
    77
     8    translateObjectKeys(settings.ais, ["name", "description"]);
    89    g_AIs = [
    9         {id: "", data: {name: "None", description: "AI will be disabled for this player."}}
     10        {id: "", data: {name: translateWithContext("ai", "None"), description: translate("AI will be disabled for this player.")}}
    1011    ].concat(settings.ais);
    1112
    1213    var aiSelection = getGUIObjectByName("aiSelection");
    13     aiSelection.list = [ ai.data.name for each (ai in g_AIs) ];
     14    aiSelection.list = [ translate(ai.data.name) for each (ai in g_AIs) ];
    1415
    1516    var selected = 0;
    1617    for (var i = 0; i < g_AIs.length; ++i)
    function init(settings)  
    2425    aiSelection.selected = selected;
    2526   
    2627    var aiDiff = getGUIObjectByName("aiDifficulty");
    27     aiDiff.list = [ "Sandbox", "Easy", "Medium", "Hard", "Very Hard" ];
     28    aiDiff.list = [translate("Sandbox"), translate("Easy"), translate("Medium"), translate("Hard"), translate("Very Hard")];
    2829    aiDiff.selected = settings.difficulty;
    2930}
    3031
  • binaries/data/mods/public/gui/aiconfig/aiconfig.xml

    diff --git a/binaries/data/mods/public/gui/aiconfig/aiconfig.xml b/binaries/data/mods/public/gui/aiconfig/aiconfig.xml
    index 19cf728..50b31b8 100644
    a b  
    99
    1010    <object type="image" style="StoneDialog" size="50%-200 40%-140 50%+200 40%+180">
    1111
    12         <object style="TitleText" type="text" size="50%-128 -16 50%+128 16">AI Configuration</object>
     12        <object style="TitleText" type="text" size="50%-128 -16 50%+128 16">
     13            <translatableAttribute id="caption">AI Configuration</translatableAttribute>
     14        </object>
    1315
    1416        <object size="50%-128 30 50%+128 80">
    1517            <object type="text" style="RightLabelText" size="-10 0 90 50%">
    16                 AI Player
     18                <translatableAttribute id="caption">AI Player</translatableAttribute>
    1719            </object>
    1820
    1921            <object name="aiSelection" type="dropdown" style="StoneDropDown" size="50%-24 0 50%+136 28">
     
    2123            </object>
    2224
    2325            <object type="text" style="RightLabelText" size="-10 35 90 50%+35">
    24                 AI Difficulty
     26                <translatableAttribute id="caption">AI Difficulty</translatableAttribute>
    2527            </object>
    2628           
    2729            <object name="aiDifficulty" type="dropdown" style="StoneDropDown" size="50%-24 35 50%+136 63">
     
    3739        </object>
    3840
    3941        <object type="button" style="StoneButton" size="50%-144 100%-60 50%-16 100%-32">
    40             OK
     42            <translatableAttribute id="caption">OK</translatableAttribute>
    4143            <action on="Press">returnAI();</action>
    4244        </object>
    4345
    4446        <object type="button" style="StoneButton" size="50%+16 100%-60 50%+144 100%-32">
    45             Cancel
     47            <translatableAttribute id="caption">Cancel</translatableAttribute>
    4648            <action on="Press">Engine.PopGuiPage();</action>
    4749        </object>
    4850    </object>
  • binaries/data/mods/public/gui/civinfo/civinfo.js

    diff --git a/binaries/data/mods/public/gui/civinfo/civinfo.js b/binaries/data/mods/public/gui/civinfo/civinfo.js
    index d283cd8..0ef9168 100644
    a b function selectCiv(code)  
    8282    var civInfo = g_CivData[code];
    8383   
    8484    if(!civInfo)
    85         error("Error loading civ data for \""+code+"\"");
     85        error(sprintf(translateWithContext("civinfo", "Error loading civ data for \"%(code)s\""), { code: code }));
    8686
    8787    // Update civ gameplay display
    88     getGUIObjectByName("civGameplayHeading").caption = heading(civInfo.Name+" Gameplay", 16);
    89 
     88    getGUIObjectByName("civGameplayHeading").caption = heading(sprintf(translateWithContext("civinfo", "%(civilization)s Gameplay"), { civilization: civInfo.Name }), 16);
    9089
    9190    // Bonuses
    92     var bonusCaption = heading("Civilization Bonus"+(civInfo.CivBonuses.length == 1 ? "" : "es"), 12) + '\n';
     91    var bonusCaption = heading(translatePluralWithContext("civinfo", "Civilization Bonus", "Civilization Bonuses", civInfo.CivBonuses.length), 12) + '\n';
    9392   
    9493    for(var i = 0; i < civInfo.CivBonuses.length; ++i)
    9594    {
    function selectCiv(code)  
    9796                    + civInfo.CivBonuses[i].History + '" tooltip_style="civInfoTooltip"]\n     ' + civInfo.CivBonuses[i].Description + '\n[/color]';
    9897    }
    9998
    100     bonusCaption += heading("Team Bonus"+(civInfo.TeamBonuses.length == 1 ? "" : "es"), 12) + '\n';
     99    bonusCaption += heading(translatePluralWithContext("civinfo", "Team Bonus", "Team Bonuses", civInfo.TeamBonuses.length), 12) + '\n';
    101100   
    102101    for(var i = 0; i < civInfo.TeamBonuses.length; ++i)
    103102    {
    function selectCiv(code)  
    109108
    110109
    111110    // Special techs / buildings
    112     var techCaption = heading("Special Technologies", 12) + '\n';
     111    var techCaption = heading(translate("Special Technologies"), 12) + '\n';
    113112   
    114113    for(var i = 0; i < civInfo.Factions.length; ++i)
    115114    {
    function selectCiv(code)  
    121120        }
    122121    }
    123122
    124     techCaption += heading("Special Building"+(civInfo.Structures.length == 1 ? "" : "s"), 12) + '\n';
     123    techCaption += heading(translatePluralWithContext("civinfo", "Special Building", "Special Buildings", civInfo.Structures.length), 12) + '\n';
    125124   
    126125    for(var i = 0; i < civInfo.Structures.length; ++i)
    127126    {
    function selectCiv(code)  
    133132
    134133
    135134    // Heroes
    136     var heroCaption = heading("Heroes", 12) + '\n';
     135    var heroCaption = heading(translateWithContext("civinfo", "Heroes"), 12) + '\n';
    137136   
    138137    for(var i = 0; i < civInfo.Factions.length; ++i)
    139138    {
    function selectCiv(code)  
    150149
    151150
    152151    // Update civ history display
    153     getGUIObjectByName("civHistoryHeading").caption = heading("History of the " + civInfo.Name, 16);
     152    getGUIObjectByName("civHistoryHeading").caption = heading(sprintf(translateWithContext("civinfo", "History of the %(civilization)s"), { civilization: civInfo.Name }), 16);
    154153    getGUIObjectByName("civHistoryText").caption = civInfo.History;
    155154}
  • binaries/data/mods/public/gui/civinfo/civinfo.xml

    diff --git a/binaries/data/mods/public/gui/civinfo/civinfo.xml b/binaries/data/mods/public/gui/civinfo/civinfo.xml
    index 2651364..22e3762 100644
    a b  
    1212
    1313    <object type="image" style="StoneDialog" size="50%-466 50%-316 50%+466 50%+316">
    1414
    15         <object style="TitleText" type="text" size="50%-128 -16 50%+128 16">Civilizations</object>
     15        <object style="TitleText" type="text" size="50%-128 -16 50%+128 16">
     16            <translatableAttribute id="caption">Civilizations</translatableAttribute>
     17        </object>
    1618
    1719        <!-- Civ selection -->
    1820        <object size="25 10 100% 30">
     
    2325                                textcolor="white"
    2426                text_align="left"
    2527                size="50%-320 10 50%-96 48">
    26                 Civilization Selection
     28                <translatableAttribute id="caption">Civilization Selection</translatableAttribute>
    2729            </object>
    2830
    2931            <object name="civSelection" type="dropdown" style="StoneDropDown" size="50%-96 10 50%+96 40">
     
    112114            type="button"
    113115            style="StoneButton"
    114116            size="100%-164 100%-52 100%-24 100%-24"
    115         >Close
     117        >
     118            <translatableAttribute id="caption">Close</translatableAttribute>
    116119            <action on="Press">
    117120                <![CDATA[
    118121                    Engine.PopGuiPage();
     
    122125
    123126    </object>
    124127
    125 </objects>
    126  No newline at end of file
     128</objects>
  • binaries/data/mods/public/gui/common/functions_civinfo.js

    diff --git a/binaries/data/mods/public/gui/common/functions_civinfo.js b/binaries/data/mods/public/gui/common/functions_civinfo.js
    index f1495d4..9fa5808 100644
    a b function loadCivData()  
    1414    for each (var filename in civFiles)
    1515    {   // Parse data if valid file
    1616        var data = parseJSONData(filename);
     17        translateObjectKeys(data, ["Name", "Description", "History", "Special"]);
    1718        civData[data.Code] = data;
    1819    }
    1920   
    2021    return civData;
    2122}
    2223
    23 // ====================================================================
    24  No newline at end of file
     24// ====================================================================
  • binaries/data/mods/public/gui/common/functions_global_object.js

    diff --git a/binaries/data/mods/public/gui/common/functions_global_object.js b/binaries/data/mods/public/gui/common/functions_global_object.js
    index a030587..b1bce13 100644
    a b function messageBox (mbWidth, mbHeight, mbMessage, mbTitle, mbMode, mbButtonCapt  
    2929
    3030function updateFPS()
    3131{   
    32     getGUIObjectByName("fpsCounter").caption = "FPS: " + getFPS();
     32    getGUIObjectByName("fpsCounter").caption = sprintf(translate("FPS: %(fps)s"), { fps: getFPS() });
    3333}
  • binaries/data/mods/public/gui/common/functions_utility.js

    diff --git a/binaries/data/mods/public/gui/common/functions_utility.js b/binaries/data/mods/public/gui/common/functions_utility.js
    index 1c1a049..7348833 100644
    a b function getJSONFileList(pathname)  
    6060function parseJSONData(pathname)
    6161{
    6262    var data = {};
    63        
     63
    6464    var rawData = readFile(pathname);
    6565    if (!rawData)
    6666    {
    67         error("Failed to read file: "+pathname);
     67        error(sprintf(translate("Failed to read file: %(path)s"), { path: pathname }));
    6868    }
    6969    else
    7070    {
    function parseJSONData(pathname)  
    7373            // TODO: Need more info from the parser on why it failed: line number, position, etc!
    7474            data = JSON.parse(rawData);
    7575            if (!data)
    76                 error("Failed to parse JSON data in: "+pathname+" (check for valid JSON data)");
    77            
    78            
     76                error(sprintf(translate("Failed to parse JSON data in: %(path)s (check for valid JSON data)"), { path: pathname }));
     77
     78
    7979        }
    8080        catch(err)
    8181        {
    82             error(err.toString()+": parsing JSON data in "+pathname);
     82            error(sprintf(translate("%(error)s: parsing JSON data in %(path)s"), { error: err.toString(), path: pathname }));
    8383        }
    8484    }
    85    
     85
    8686    return data;
    8787}
    8888
    function parseJSONFromDataFile(filename)  
    141141    var path = "simulation/data/"+filename;
    142142    var rawData = readFile(path);
    143143    if (!rawData)
    144         error("Failed to read file: "+path);
     144        error(sprintf(translate("Failed to read file: %(path)s"), { path: path }));
    145145
    146146    try
    147147    {
    function parseJSONFromDataFile(filename)  
    152152    }
    153153    catch(err)
    154154    {
    155         error(err.toString()+": parsing JSON data in "+path);
     155        error(sprintf(translate("%(error)s: parsing JSON data in %(path)s"), { error: err.toString(), path: path }));
    156156    }
    157157
    158158    return undefined;
    function initPlayerDefaults()  
    167167
    168168    var data = parseJSONFromDataFile("player_defaults.json");
    169169    if (!data || !data.PlayerData)
    170         error("Failed to parse player defaults in player_defaults.json (check for valid JSON data)");
     170        error(translate("Failed to parse player defaults in player_defaults.json (check for valid JSON data)"));
    171171    else
     172    {
     173        translateObjectKeys(data.PlayerData, ["Name"])
    172174        defaults = data.PlayerData;
     175    }
    173176
    174177    return defaults;
    175178}
    function initMapSizes()  
    187190
    188191    var data = parseJSONFromDataFile("map_sizes.json");
    189192    if (!data || !data.Sizes)
    190         error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)");
     193        error(translate("Failed to parse map sizes in map_sizes.json (check for valid JSON data)"));
    191194    else
    192195    {
     196        translateObjectKeys(data, ["Name", "LongName"]);
    193197        for (var i = 0; i < data.Sizes.length; ++i)
    194198        {
    195199            sizes.names.push(data.Sizes[i].LongName);
    function initGameSpeeds()  
    216220
    217221    var data = parseJSONFromDataFile("game_speeds.json");
    218222    if (!data || !data.Speeds)
    219         error("Failed to parse game speeds in game_speeds.json (check for valid JSON data)");
     223        error(translate("Failed to parse game speeds in game_speeds.json (check for valid JSON data)"));
    220224    else
    221225    {
     226        translateObjectKeys(data, ["Name"]);
    222227        for (var i = 0; i < data.Speeds.length; ++i)
    223228        {
    224229            gameSpeeds.names.push(data.Speeds[i].Name);
    function iColorToString(color)  
    254259 */
    255260function timeToString(time)
    256261{
    257     var hours   = Math.floor(time / 1000 / 60 / 60);
    258     var minutes = Math.floor(time / 1000 / 60) % 60;
    259     var seconds = Math.floor(time / 1000) % 60;
    260     return hours + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds);
     262    return Engine.formatMillisecondsIntoDateString(time, translate("HH:mm:ss"));
    261263}
    262264
    263265// ====================================================================
    function shuffleArray(source)  
    289291        result[j] = source[i];
    290292    }
    291293    return result;
    292 }
    293  No newline at end of file
     294}
  • binaries/data/mods/public/gui/common/functions_utility_error.js

    diff --git a/binaries/data/mods/public/gui/common/functions_utility_error.js b/binaries/data/mods/public/gui/common/functions_utility_error.js
    index 17254b3..e5618ee 100644
    a b function cancelOnError(msg)  
    1919            width: 500,
    2020            height: 200,
    2121            message: '[font="serif-bold-18"]' + msg + '[/font]',
    22             title: "Loading Aborted",
     22            title: translate("Loading Aborted"),
    2323            mode: 2
    2424        });
    2525    }
  • binaries/data/mods/public/gui/common/functions_utility_list.js

    diff --git a/binaries/data/mods/public/gui/common/functions_utility_list.js b/binaries/data/mods/public/gui/common/functions_utility_list.js
    index 0f3050c..9f77abb 100644
    a b  
    1111function removeItem (objectName, pos)
    1212{
    1313    if (getGUIObjectByName (objectName) == null)
    14         Engine.Console_Write ("removeItem(): " + objectName + " not found.");
     14        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "removeItem()", object: objectName }));
    1515
    1616    var list = getGUIObjectByName (objectName).list;
    1717    var selected = getGUIObjectByName (objectName).selected;
    function removeItem (objectName, pos)  
    4141function addItem (objectName, pos, value)
    4242{
    4343    if (getGUIObjectByName (objectName) == null)
    44         Engine.Console_Write ("addItem(): " + objectName + " not found.");
     44        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "addItem()", object: objectName }));
    4545
    4646    var list = getGUIObjectByName (objectName).list;
    4747    var selected = getGUIObjectByName (objectName).selected;
    function addItem (objectName, pos, value)  
    6666function pushItem (objectName, value)
    6767{
    6868    if (getGUIObjectByName (objectName) == null)
    69         Engine.Console_Write ("pushItem(): " + objectName + " not found.");
     69        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "pushItem()", object: objectName }));
    7070
    7171    var list = getGUIObjectByName (objectName).list;
    7272    list.push (value);
    function pushItem (objectName, value)  
    8181function popItem (objectName)
    8282{
    8383    if (getGUIObjectByName (objectName) == null)
    84         Engine.Console_Write ("popItem(): " + objectName + " not found.");
     84        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "popItem()", object: objectName }));
    8585
    8686    var selected = getGUIObjectByName (objectName).selected;
    8787    removeItem(objectName, getNumItems(objectName)-1);
    function popItem (objectName)  
    9898function getNumItems (objectName)
    9999{
    100100    if (getGUIObjectByName (objectName) == null)
    101         Engine.Console_Write ("getNumItems(): " + objectName + " not found.");
     101        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "getNumItems()", object: objectName }));
    102102
    103103    var list = getGUIObjectByName(objectName).list;
    104104    return list.length;
    function getNumItems (objectName)  
    110110function getItemValue (objectName, pos)
    111111{
    112112    if (getGUIObjectByName (objectName) == null)
    113         Engine.Console_Write ("getItemValue(): " + objectName + " not found.");
     113        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "getItemValue()", object: objectName }));
    114114
    115115    var list = getGUIObjectByName(objectName).list;
    116116    return list[pos];
    function getItemValue (objectName, pos)  
    122122function getCurrItemValue (objectName)
    123123{
    124124    if (getGUIObjectByName (objectName) == null)
    125         Engine.Console_Write ("getCurrItemValue(): " + objectName + " not found.");
     125        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "getCurrItemValue()", object: objectName }));
    126126
    127127    if (getGUIObjectByName(objectName).selected == -1)
    128128        return "";
    function getCurrItemValue (objectName)  
    137137function setCurrItemValue (objectName, string)
    138138{
    139139    if (getGUIObjectByName(objectName) == null) {
    140         Engine.Console_Write ("setCurrItemValue(): " + objectName + " not found.");
     140        Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "setCurrItemValue()", object: objectName }));
    141141        return -1;
    142142    }
    143143
    function setCurrItemValue (objectName, string)  
    157157    }
    158158
    159159    // Return -2 if failed to find value in list.
    160     Engine.Console_Write ("Requested string '" + string + "' not found in " + objectName + "'s list.");
     160    Engine.Console_Write (sprintf(translate("Requested string '%(string)s' not found in %(object)s's list."), { string: string, object: objectName }));
    161161    return -2;
    162162}
    163163
  • binaries/data/mods/public/gui/common/functions_utility_loadsave.js

    diff --git a/binaries/data/mods/public/gui/common/functions_utility_loadsave.js b/binaries/data/mods/public/gui/common/functions_utility_loadsave.js
    index 769a667..5807acc 100644
    a b function sortDecreasingDate(a, b)  
    33    return b.metadata.time - a.metadata.time;
    44}
    55
    6 function twoDigits(n)
    7 {
    8     return n < 10 ? "0" + n : n;
    9 }
    10 
    116function generateLabel(metadata)
    127{
    13     var t = new Date(metadata.time*1000);
    14 
    15     var date = t.getFullYear()+"-"+twoDigits(1+t.getMonth())+"-"+twoDigits(t.getDate());
    16     var time = twoDigits(t.getHours())+":"+twoDigits(t.getMinutes())+":"+twoDigits(t.getSeconds());
    17     return "["+date+" "+time+"] "+metadata.initAttributes.map+(metadata.description ? " - "+metadata.description : "");
     8    var dateTimeString = Engine.formatMillisecondsIntoDateString(metadata.time, translate("yyyy-MM-dd HH:mm:ss"));
     9    if (metadata.description)
     10    {
     11        return sprintf(translate("[%(date)s] %(map)s - %(description)s"), { date: dateTimeString, map: metadata.initAttributes.map, description: metadata.description });
     12    }
     13    else
     14    {
     15        return sprintf(translate("[%(date)s] %(map)s"), { date: dateTimeString, map: metadata.initAttributes.map });
     16    }
    1817}
  • binaries/data/mods/public/gui/common/functions_utility_music.js

    diff --git a/binaries/data/mods/public/gui/common/functions_utility_music.js b/binaries/data/mods/public/gui/common/functions_utility_music.js
    index f9631d8..3bc245b 100644
    a b function newRandomSound(soundType, soundSubType, soundPrePath)  
    5353    var soundArray = buildDirEntList(randomSoundPath, "*" + soundSubType + "*", false);
    5454    if (soundArray.length == 0)
    5555    {
    56         Engine.Console_Write ("Failed to find sounds matching '*"+soundSubType+"*'");
     56        Engine.Console_Write (sprintf(translate("Failed to find sounds matching '*%(subtype)s*'"), { soundSubType: subtype }));
    5757        return undefined;
    5858    }
    5959    // Get a random number within the sound's range.
    function newRandomSound(soundType, soundSubType, soundPrePath)  
    7575            return new AmbientSound(randomSoundPath);
    7676        break;
    7777        case "effect":
    78             Engine.Console_Write("am loading effect '*"+randomSoundPath+"*'");
     78            Engine.Console_Write(sprintf(translate("am loading effect '*%(path)s*'"), { path: randomSoundPath }));
    7979        break;
    8080        default:
    8181        break;
  • binaries/data/mods/public/gui/common/music.js

    diff --git a/binaries/data/mods/public/gui/common/music.js b/binaries/data/mods/public/gui/common/music.js
    index 843f3af..d2efef9 100644
    a b Music.prototype.updateState = function()  
    108108            break;
    109109
    110110        default:
    111             warn("Music.updateState(): Unknown music state: " + this.currentState);
     111            warn(sprintf(translate("%(functionName)s: Unknown music state: %(state)s"), { functionName: "Music.updateState()", state: this.currentState }));
    112112            break;
    113113        }
    114114    }
    Music.prototype.storeTracks = function(civMusic)  
    131131
    132132        if (type === undefined)
    133133        {
    134             warn("Music.storeTracks(): Unrecognized music type: " + music.Type);
     134            warn(sprintf(translate("%(functionName)s: Unrecognized music type: %(musicType)s"), { functionName: "Music.storeTracks()", musicType: music.Type }));
    135135            continue;
    136136        }
    137137
  • binaries/data/mods/public/gui/common/network.js

    diff --git a/binaries/data/mods/public/gui/common/network.js b/binaries/data/mods/public/gui/common/network.js
    index 081d0f0..89b2d66 100644
    a b function getDisconnectReason(id)  
    33    // Must be kept in sync with source/network/NetHost.h
    44    switch (id)
    55    {
    6     case 0: return "Unknown reason";
    7     case 1: return "Unexpected shutdown";
    8     case 2: return "Incorrect network protocol version";
    9     case 3: return "Game has already started";
    10     default: return "[Invalid value "+id+"]";
     6    case 0: return translate("Unknown reason");
     7    case 1: return translate("Unexpected shutdown");
     8    case 2: return translate("Incorrect network protocol version");
     9    case 3: return translate("Game has already started");
     10    default: return sprintf(translate("[Invalid value %(id)s]"), { id: id });
    1111    }
    1212}
    1313
    function reportDisconnect(reason)  
    1616    var reasontext = getDisconnectReason(reason);
    1717
    1818    messageBox(400, 200,
    19         "Lost connection to the server.\n\nReason: " + reasontext + ".",
    20         "Disconnected", 2);
     19        translate("Lost connection to the server.") + "\n\n" + sprintf(translate("Reason: %(reason)s."), { reason: reasontext }),
     20        translate("Disconnected"), 2);
    2121}
  • binaries/data/mods/public/gui/common/timer.js

    diff --git a/binaries/data/mods/public/gui/common/timer.js b/binaries/data/mods/public/gui/common/timer.js
    index 5391ba7..80c6a02 100644
    a b function updateTimers()  
    4545            t[1]();
    4646        } catch (e) {
    4747            var stack = e.stack.trimRight().replace(/^/mg, '  '); // indent the stack trace
    48             error("Error in timer: "+e+"\n"+stack+"\n");
     48            error(sprintf(translate("Error in timer: %(error)s"), { error: e })+"\n"+stack+"\n");
    4949        }
    5050        delete g_Timers[id];
    5151    }
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

    diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
    index 6ce3102..2001f6b 100644
    a b const DEFAULT_NETWORKED_MAP = "Acropolis 01";  
    44const DEFAULT_OFFLINE_MAP = "Acropolis 01";
    55
    66// TODO: Move these somewhere like simulation\data\game_types.json, Atlas needs them too
    7 const VICTORY_TEXT = ["Conquest", "None"];
     7const VICTORY_TEXT = [translate("Conquest"), translateWithContext("victory", "None")];
    88const VICTORY_DATA = ["conquest", "endless"];
    99const VICTORY_DEFAULTIDX = 0;
    10 const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", "Unlimited"];
     10const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", translate("Unlimited")];
    1111const POPULATION_CAP_DATA = [50, 100, 150, 200, 250, 300, 10000];
    1212const POPULATION_CAP_DEFAULTIDX = 5;
    13 const STARTING_RESOURCES = ["Very Low", "Low", "Medium", "High", "Very High", "Deathmatch"];
     13const STARTING_RESOURCES = [translate("Very Low"), translate("Low"), translate("Medium"), translate("High"), translate("Very High"), translate("Deathmatch")];
    1414const STARTING_RESOURCES_DATA = [100, 300, 500, 1000, 3000, 50000];
    1515const STARTING_RESOURCES_DEFAULTIDX = 1;
    1616// Max number of players for any map
    var g_CivData = {};  
    5050var g_MapFilters = [];
    5151
    5252// Warn about the AI's nonexistent naval map support.
    53 var g_NavalWarning = "\n\n[font=\"serif-bold-12\"][color=\"orange\"]Warning:[/color][/font] \
    54 The AI does not support naval maps and may cause severe performance issues. \
    55 Naval maps are recommended to be played with human opponents only.";
     53var g_NavalWarning = "\n\n" + sprintf(
     54    translate("%(warning)s The AI does not support naval maps and may cause severe performance issues. Naval maps are recommended to be played with human opponents only."),
     55    { warning: "[font=\"serif-bold-12\"][color=\"orange\"]" + translate("Warning:") + "[/color][/font]" }
     56);
    5657
    5758// To prevent the display locking up while we load the map metadata,
    5859// we'll start with a 'loading' message and switch to the main screen in the
    function init(attribs)  
    7879        g_IsController = false;
    7980        break;
    8081    default:
    81         error("Unexpected 'type' in gamesetup init: "+attribs.type);
     82        error(sprintf(translate("Unexpected 'type' in gamesetup init: %(unexpectedType)s"), { unexpectedType: attribs.type }));
    8283    }
    8384}
    8485
    function initMain()  
    107108
    108109    // Init map types
    109110    var mapTypes = getGUIObjectByName("mapTypeSelection");
    110     mapTypes.list = ["Scenario","Skirmish","Random"];
     111    mapTypes.list = [translate("Scenario"), translateWithContext("map", "Skirmish"), translateWithContext("map", "Random")];
    111112    mapTypes.list_data = ["scenario","skirmish","random"];
    112113
    113114    // Setup map filters - will appear in order they are added
    114     addFilter("Default", function(settings) { return settings && !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"]); });
    115     addFilter("Naval Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["naval"]); });
    116     addFilter("Demo Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["demo"]); });
    117     addFilter("All Maps", function(settings) { return true; });
     115    addFilter("default", translate("Default"), function(settings) { return settings && !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"]); });
     116    addFilter("naval", translate("Naval Maps"), function(settings) { return settings && keywordTestAND(settings.Keywords, ["naval"]); });
     117    addFilter("demo", translate("Demo Maps"), function(settings) { return settings && keywordTestAND(settings.Keywords, ["demo"]); });
     118    addFilter("all", translate("All Maps"), function(settings) { return true; });
    118119
    119120    // Populate map filters dropdown
    120121    var mapFilters = getGUIObjectByName("mapFilterSelection");
    121     mapFilters.list = getFilters();
    122     g_GameAttributes.mapFilter = "Default";
     122    mapFilters.list = getFilterNames();
     123    mapFilters.list_data = getFilterIds();
     124    g_GameAttributes.mapFilter = "default";
    123125
    124126    // Setup controls for host only
    125127    if (g_IsController)
    function initMain()  
    287289
    288290        // Populate team dropdowns
    289291        var team = getGUIObjectByName("playerTeam["+i+"]");
    290         team.list = ["None", "1", "2", "3", "4"];
     292        team.list = [translateWithContext("team", "None"), "1", "2", "3", "4"];
    291293        team.list_data = [-1, 0, 1, 2, 3];
    292294        team.selected = 0;
    293295
    function initCivNameList()  
    447449    var civListCodes = [ civ.code for each (civ in civList) ];
    448450
    449451    //  Add random civ to beginning of list
    450     civListNames.unshift("[color=\"orange\"]Random");
     452    civListNames.unshift("[color=\"orange\"]" + translateWithContext("civilization", "Random"));
    451453    civListCodes.unshift("random");
    452454
    453455    // Update the dropdowns
    function initMapNameList()  
    480482        break;
    481483
    482484    default:
    483         error("initMapNameList: Unexpected map type '"+g_GameAttributes.mapType+"'");
     485        error(sprintf(translate("initMapNameList: Unexpected map type '%(mapType)s'"), { mapType: g_GameAttributes.mapType }));
    484486        return;
    485487    }
    486488
    function initMapNameList()  
    498500    // Alphabetically sort the list, ignoring case
    499501    mapList.sort(sortNameIgnoreCase);
    500502    if (g_GameAttributes.mapType == "random")
    501         mapList.unshift({ "name": "[color=\"orange\"]Random[/color]", "file": "random" });
     503        mapList.unshift({ "name": "[color=\"orange\"]" + translateWithContext("map", "Random") + "[/color]", "file": "random" });
    502504
    503505    var mapListNames = [ map.name for each (map in mapList) ];
    504506    var mapListFiles = [ map.file for each (map in mapList) ];
    function loadMapData(name)  
    529531        case "scenario":
    530532        case "skirmish":
    531533            g_MapData[name] = Engine.LoadMapSettings(name);
     534            translateObjectKeys(g_MapData[name], ["Name", "Description"]);
    532535            break;
    533536
    534537        case "random":
    535538            if (name == "random")
    536                 g_MapData[name] = {settings : {"Name" : "Random", "Description" : "Randomly selects a map from the list"}};
     539                g_MapData[name] = { settings: { "Name": translateWithContext("map", "Random"), "Description": translate("Randomly selects a map from the list") } };
    537540            else
     541            {
    538542                g_MapData[name] = parseJSONData(name+".json");
     543                translateObjectKeys(g_MapData[name], ["Name", "Description"]);
     544            }
    539545            break;
    540546
    541547        default:
    542             error("loadMapData: Unexpected map type '"+g_GameAttributes.mapType+"'");
     548            error(sprintf(translate("loadMapData: Unexpected map type '%(mapType)s'"), { mapType: g_GameAttributes.mapType }));
    543549            return undefined;
    544550        }
    545551    }
    function selectNumPlayers(num)  
    620626            if (g_IsNetworked)
    621627                Engine.AssignNetworkPlayer(player, "");
    622628            else
    623                 g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
     629                g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1} };
    624630        }
    625631    }
    626632
    function selectMapType(type)  
    674680        break;
    675681
    676682    default:
    677         error("selectMapType: Unexpected map type '"+g_GameAttributes.mapType+"'");
     683        error(sprintf(translate("selectMapType: Unexpected map type '%(mapType)s'"), { mapType: g_GameAttributes.mapType }));
    678684        return;
    679685    }
    680686
    function selectMapType(type)  
    683689    updateGameAttributes();
    684690}
    685691
    686 function selectMapFilter(filterName)
     692function selectMapFilter(id)
    687693{
    688694    // Avoid recursion
    689695    if (g_IsInGuiUpdate)
    function selectMapFilter(filterName)  
    693699    if (g_IsNetworked && !g_IsController)
    694700        return;
    695701
    696     g_GameAttributes.mapFilter = filterName;
     702    g_GameAttributes.mapFilter = id;
    697703
    698704    initMapNameList();
    699705
    function selectMap(name)  
    721727    // Copy any new settings
    722728    g_GameAttributes.map = name;
    723729    g_GameAttributes.script = mapSettings.Script;
    724     if (mapData !== "Random")
     730    if (g_GameAttributes.map !== "random")
    725731        for (var prop in mapSettings)
    726732            g_GameAttributes.settings[prop] = mapSettings[prop];
    727733
    function selectMap(name)  
    737743    // Reset player assignments on map change
    738744    if (!g_IsNetworked)
    739745    {   // Slot 1
    740         g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
     746        g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1} };
    741747    }
    742748    else
    743749    {
    function launchGame()  
    759765{
    760766    if (g_IsNetworked && !g_IsController)
    761767    {
    762         error("Only host can start game");
     768        error(translate("Only host can start game"));
    763769        return;
    764770    }
    765771
    function launchGame()  
    812818                    usedName++;
    813819           
    814820            // Assign civ specific names to AI players
     821            chosenName = translate(chosenName);
    815822            if (usedName)
    816                 g_GameAttributes.settings.PlayerData[i].Name = chosenName + " " + romanNumbers[usedName+1];
     823                g_GameAttributes.settings.PlayerData[i].Name = sprintf(translate("%(playerName)s %(romanNumber)s"), { playerName: chosenName, romanNumber: romanNumbers[usedName+1]});
    817824            else
    818825                g_GameAttributes.settings.PlayerData[i].Name = chosenName;
    819826        }
    function onGameAttributesChange()  
    862869    // Update some controls for clients
    863870    if (!g_IsController)
    864871    {
    865         getGUIObjectByName("mapFilterText").caption = g_GameAttributes.mapFilter;
     872        var mapFilderSelection = getGUIObjectByName("mapFilterText");
     873        var mapFilterId = mapFilderSelection.list_data.indexOf(g_GameAttributes.mapFilter);
     874        getGUIObjectByName("mapFilterText").caption = mapFilderSelection.list[mapFilterId];
    866875        var mapTypeSelection = getGUIObjectByName("mapTypeSelection");
    867876        var idx = mapTypeSelection.list_data.indexOf(g_GameAttributes.mapType);
    868877        getGUIObjectByName("mapTypeText").caption = mapTypeSelection.list[idx];
    function onGameAttributesChange()  
    900909    var speedIdx = (g_GameAttributes.gameSpeed !== undefined && g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) != -1) ? g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) : g_GameSpeeds["default"];
    901910    var victoryIdx = (VICTORY_DATA.indexOf(mapSettings.GameType) != -1 ? VICTORY_DATA.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
    902911    enableCheats.checked = (g_GameAttributes.settings.CheatsEnabled === undefined || !g_GameAttributes.settings.CheatsEnabled ? false : true)
    903     enableCheatsText.caption = (enableCheats.checked ? "Yes" : "No");
     912    enableCheatsText.caption = (enableCheats.checked ? translate("Yes") : translate("No"));
    904913    gameSpeedText.caption = g_GameSpeeds.names[speedIdx];
    905914    populationCap.selected = (POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);
    906915    populationCapText.caption = POPULATION_CAP[populationCap.selected];
    function onGameAttributesChange()  
    934943            populationCapText.hidden = true;
    935944            startingResourcesText.hidden = true;
    936945           
    937             mapSizeText.caption = "Map size:";
     946            mapSizeText.caption = translate("Map size:");
    938947            mapSize.selected = sizeIdx;
    939             revealMapText.caption = "Reveal map:";
     948            revealMapText.caption = translate("Reveal map:");
    940949            revealMap.checked = (mapSettings.RevealMap ? true : false);
    941950
    942             victoryConditionText.caption = "Victory condition:";
     951            victoryConditionText.caption = translate("Victory condition:");
    943952            victoryCondition.selected = victoryIdx;
    944             lockTeamsText.caption = "Teams locked:";
     953            lockTeamsText.caption = translate("Teams locked:");
    945954            lockTeams.checked = (mapSettings.LockTeams ? true : false);
    946955        }
    947956        else
    function onGameAttributesChange()  
    959968
    960969            numPlayersText.caption = numPlayers;
    961970            mapSizeText.caption = g_MapSizes.names[sizeIdx];
    962             revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
     971            revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
    963972            victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    964             lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
     973            lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
    965974        }
    966975
    967976        break;
    function onGameAttributesChange()  
    10351044        startingResourcesText.hidden = false;
    10361045
    10371046        numPlayersText.caption = numPlayers;
    1038         mapSizeText.caption = "Default";
    1039         revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
     1047        mapSizeText.caption = translate("Default");
     1048        revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
    10401049        victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    1041         lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
     1050        lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
    10421051        getGUIObjectByName("populationCap").selected = POPULATION_CAP_DEFAULTIDX;
    10431052
    10441053        break;
    10451054
    10461055    default:
    1047         error("onGameAttributesChange: Unexpected map type '"+g_GameAttributes.mapType+"'");
     1056        error(sprintf(translate("onGameAttributesChange: Unexpected map type '%(mapType)s'"), { mapType: g_GameAttributes.mapType }));
    10481057        return;
    10491058    }
    10501059
    function onGameAttributesChange()  
    10521061    getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);
    10531062   
    10541063    // Load the description from the map file, if there is one
    1055     var description = mapSettings.Description || "Sorry, no description available.";
     1064    var description = mapSettings.Description || translate("Sorry, no description available.");
    10561065
    1057     if (g_GameAttributes.mapFilter == "Naval Maps")
     1066    if (g_GameAttributes.mapFilter == "naval")
    10581067        description += g_NavalWarning;
    10591068
    10601069    // Describe the number of players
    1061     var playerString = numPlayers + " " + (numPlayers == 1 ? "player" : "players") + ". ";
     1070    var playerString = sprintf(translatePlural("%(number)s player. %(description)s", "%(number)s players. %(description)s", numPlayers), { number: numPlayers, description: description });
    10621071
    10631072    for (var i = 0; i < MAX_PLAYERS; ++i)
    10641073    {
    function onGameAttributesChange()  
    10971106            pTeam.hidden = true;
    10981107            // Set text values
    10991108            if (civ == "random")
    1100                 pCivText.caption = "[color=\"orange\"]Random";
     1109                pCivText.caption = "[color=\"orange\"]" + translateWithContext("civilization", "Random");
    11011110            else
    11021111                pCivText.caption = g_CivData[civ].Name;
    11031112            pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
    function onGameAttributesChange()  
    11141123        }
    11151124    }
    11161125
    1117     getGUIObjectByName("mapInfoDescription").caption = playerString + description;
     1126    getGUIObjectByName("mapInfoDescription").caption = playerString;
    11181127
    11191128    g_IsInGuiUpdate = false;
    11201129
    function updatePlayerList()  
    11781187        }
    11791188        // Give AI a different color so it stands out
    11801189        aiAssignments[ai.id] = hostNameList.length;
    1181         hostNameList.push("[color=\"70 150 70 255\"]AI: " + ai.data.name);
     1190        hostNameList.push("[color=\"70 150 70 255\"]" + sprintf(translate("AI: %(ai)s"), { ai: translate(ai.data.name) }));
    11821191        hostGuidList.push("ai:" + ai.id);
    11831192    }
    11841193
    11851194    noAssignment = hostNameList.length;
    1186     hostNameList.push("[color=\"140 140 140 255\"]Unassigned");
     1195    hostNameList.push("[color=\"140 140 140 255\"]" + translate("Unassigned"));
    11871196    hostGuidList.push("");
    11881197
    11891198    for (var i = 0; i < MAX_PLAYERS; ++i)
    function updatePlayerList()  
    12101219                if (aiId in aiAssignments)
    12111220                    selection = aiAssignments[aiId];
    12121221                else
    1213                     warn("AI \""+aiId+"\" not present. Defaulting to unassigned.");
     1222                    warn(sprintf(translate("AI \"%(id)s\" not present. Defaulting to unassigned."), { id: aiId }));
    12141223            }
    12151224
    12161225            if (!selection)
    function addChatMessage(msg)  
    13851394    switch (msg.type)
    13861395    {
    13871396    case "connect":
    1388         formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has joined[/color]';
     1397        var formattedUsername = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
     1398        formatted = '[color="gold"]' + sprintf(translate("%(username)s has joined"), { username: formattedUsername });
    13891399        break;
    13901400
    13911401    case "disconnect":
    1392         formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has left[/color]';
     1402        var formattedUsername = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
     1403        formatted = '[color="gold"]' + sprintf(translate("%(username)s has left"), { username: formattedUsername });
    13931404        break;
    13941405
    13951406    case "message":
    1396         formatted = '[font="serif-bold-13"]<[color="'+ color +'"]' + username + '[/color]>[/font] ' + message;
     1407        var formattedUsername = '[color="'+ color +'"]' + username + '[/color]'
     1408        var formattedUsernamePrefix = '[font="serif-bold-13"]' + sprintf(translate("<%(username)s>"), { username: formattedUsername }) + '[/font]'
     1409        formatted = sprintf(translate("%(username)s %(message)s"), { username: formattedUsernamePrefix, message: message });
    13971410        break;
    13981411
    13991412    default:
    1400         error("Invalid chat message '" + uneval(msg) + "'");
     1413        error(sprintf(translate("Invalid chat message '%(message)s'"), { message: uneval(msg) }));
    14011414        return;
    14021415    }
    14031416
    function toggleMoreOptions()  
    14151428// Basic map filters API
    14161429
    14171430// Add a new map list filter
    1418 function addFilter(name, filterFunc)
     1431function addFilter(id, name, filterFunc)
    14191432{
    14201433    if (filterFunc instanceof Object)
    14211434    {   // Basic validity test
    14221435        var newFilter = {};
     1436        newFilter.id = id;
    14231437        newFilter.name = name;
    14241438        newFilter.filter = filterFunc;
    14251439
    function addFilter(name, filterFunc)  
    14271441    }
    14281442    else
    14291443    {
    1430         error("Invalid map filter: "+name);
     1444        error(sprintf(translate("Invalid map filter: %(name)s"), { name: name }));
    14311445    }
    14321446}
    14331447
     1448// Get array of map filter IDs
     1449function getFilterIds()
     1450{
     1451    var filters = [];
     1452    for (var i = 0; i < g_MapFilters.length; ++i)
     1453        filters.push(g_MapFilters[i].id);
     1454
     1455    return filters;
     1456}
     1457
    14341458// Get array of map filter names
    1435 function getFilters()
     1459function getFilterNames()
    14361460{
    14371461    var filters = [];
    14381462    for (var i = 0; i < g_MapFilters.length; ++i)
    function getFilters()  
    14421466}
    14431467
    14441468// Test map filter on given map settings object
    1445 function testFilter(name, mapSettings)
     1469function testFilter(id, mapSettings)
    14461470{
    14471471    for (var i = 0; i < g_MapFilters.length; ++i)
    1448         if (g_MapFilters[i].name == name)
     1472        if (g_MapFilters[i].id == id)
    14491473            return g_MapFilters[i].filter(mapSettings);
    14501474
    1451     error("Invalid map filter: "+name);
     1475    error(sprintf(translate("Invalid map filter: %(id)s"), { id: id }));
    14521476    return false;
    14531477}
    14541478
  • binaries/data/mods/public/gui/gamesetup/gamesetup.xml

    diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
    index 157afc5..cb2213a 100644
    a b  
    1212    <object type="image" style="StoneWindow" size="0 0 100% 100%">
    1313
    1414        <object style="TitleText" type="text" size="50%-128 4 50%+128 36">
    15             Match Setup
     15            <translatableAttribute id="caption">Match Setup</translatableAttribute>
    1616        </object>
    1717   
    1818        <object type="image" style="StoneDialog" size="50%-190 50%-80 50%+190 50%+80" name="loadingWindow">
    1919
    2020            <object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">
    21                 Loading
     21                <translatableAttribute id="caption">Loading</translatableAttribute>
    2222            </object>
    2323
    2424            <object type="text" style="CenteredLabelText">
    25                 Loading map data. Please wait...
     25                <translatableAttribute id="caption">Loading map data. Please wait...</translatableAttribute>
    2626            </object>
    2727
    2828        </object>
     
    3838
    3939                <!-- Number of Players-->
    4040                <object size="0 0 150 28">
    41                     <object size="0 0 100% 100%" type="text" style="RightLabelText">Number of players:</object>
     41                    <object size="0 0 100% 100%" type="text" style="RightLabelText">
     42                        <translatableAttribute id="caption">Number of players:</translatableAttribute>
     43                    </object>
    4244                </object>
    4345                   
    4446                <!-- Number of Players-->
     
    4850                            type="dropdown"
    4951                            style="StoneDropDown"
    5052                            size="0 0 100% 28"
    51                             tooltip_style="onscreenToolTip"
    52                             tooltip="Select number of players.">
     53                            tooltip_style="onscreenToolTip">
     54                        <translatableAttribute id="tooltip">Select number of players.</translatableAttribute>
    5355                        <action on="SelectionChange">selectNumPlayers(this.list_data[this.selected]);</action>
    5456                    </object>
    5557                </object>
     
    5961            <!-- Player assignments -->
    6062            <object size="24 59 100%-440 358" type="image" sprite="BackgroundIndentFillDark" name="playerAssignmentsPannel">
    6163                <object size="0 6 100% 30">
    62                     <object name="playerNameHeading" type="text" style="CenteredLabelText" size="0 0 25% 100%">Player Name</object>
    63                     <object name="playerPlacementHeading" type="text" style="CenteredLabelText" size="25%+5 0 55% 100%">Player Placement</object>
    64                     <object name="playerCivHeading" type="text" style="CenteredLabelText" size="55%+65 0 85% 100%">Civilization</object>
     64                    <object name="playerNameHeading" type="text" style="CenteredLabelText" size="0 0 25% 100%">
     65                        <translatableAttribute id="caption">Player Name</translatableAttribute>
     66                    </object>
     67                    <object name="playerPlacementHeading" type="text" style="CenteredLabelText" size="25%+5 0 55% 100%">
     68                        <translatableAttribute id="caption">Player Placement</translatableAttribute>
     69                    </object>
     70                    <object name="playerCivHeading" type="text" style="CenteredLabelText" size="55%+65 0 85% 100%">
     71                        <translatableAttribute id="caption">Civilization</translatableAttribute>
     72                    </object>
    6573                    <object name="civInfoButton"
    6674                        type="button"
    6775                        sprite="iconInfoGold"
    6876                        sprite_over="iconInfoWhite"
    6977                        size="85%-8 0 85%+8 16"
    7078                        tooltip_style="onscreenToolTip"
    71                         tooltip="View civilization info"
    7279                    >
     80                        <translatableAttribute id="tooltip">View civilization info</translatableAttribute>
    7381                        <action on="Press"><![CDATA[
    7482                            Engine.PushGuiPage("page_civinfo.xml");
    7583                        ]]></action>
    7684                    </object>
    77                     <object name="playerTeamHeading" type="text" style="CenteredLabelText" size="85%+5 0 100%-5 100%">Team</object>
     85                    <object name="playerTeamHeading" type="text" style="CenteredLabelText" size="85%+5 0 100%-5 100%">
     86                        <translatableAttribute id="caption">Team</translatableAttribute>
     87                    </object>
    7888                </object>
    7989                <object size="1 36 100%-1 100%">
    8090                    <repeat count="8">
    8191                        <object name="playerBox[n]" size="0 0 100% 32" hidden="true">
    8292                            <object name="playerColour[n]" type="image" size="0 0 100% 100%"/>
    8393                            <object name="playerName[n]" type="text" style="CenteredLabelText" size="0 2 25% 30"/>
    84                             <object name="playerAssignment[n]" type="dropdown" style="StoneDropDown" size="25%+5 2 55% 30" tooltip_style="onscreenToolTip" tooltip="Select player."/>
     94                            <object name="playerAssignment[n]" type="dropdown" style="StoneDropDown" size="25%+5 2 55% 30" tooltip_style="onscreenToolTip">
     95                                <translatableAttribute id="tooltip">Select player.</translatableAttribute>
     96                            </object>
    8597                            <object name="playerConfig[n]" type="button" style="StoneButton" size="55%+5 6 55%+60 26"
    8698                                tooltip_style="onscreenToolTip"
    87                                 tooltip="Configure AI settings."
    8899                                font="serif-bold-stroke-12"
    89                             >Settings</object>
    90                             <object name="playerCiv[n]" type="dropdown" style="StoneDropDown" size="55%+65 2 85% 30" tooltip_style="onscreenToolTip" tooltip="Select player's civilization."/>
     100                            >
     101                                <translatableAttribute id="caption">Settings</translatableAttribute>
     102                                <translatableAttribute id="tooltip">Configure AI settings.</translatableAttribute>
     103                            </object>
     104                            <object name="playerCiv[n]" type="dropdown" style="StoneDropDown" size="55%+65 2 85% 30" tooltip_style="onscreenToolTip">
     105                                <translatableAttribute id="tooltip">Select player's civilization.</translatableAttribute>
     106                            </object>
    91107                            <object name="playerCivText[n]" type="text" style="CenteredLabelText" size="55%+65 0 85% 30"/>
    92                             <object name="playerTeam[n]" type="dropdown" style="StoneDropDown" size="85%+5 2 100%-5 30" tooltip_style="onscreenToolTip" tooltip="Select player's team."/>
     108                            <object name="playerTeam[n]" type="dropdown" style="StoneDropDown" size="85%+5 2 100%-5 30" tooltip_style="onscreenToolTip">
     109                                <translatableAttribute id="tooltip">Select player's team.</translatableAttribute>
     110                            </object>
    93111                            <object name="playerTeamText[n]" type="text" style="CenteredLabelText" size="85%+5 0 100%-5 100%"/>
    94112                        </object>
    95113                    </repeat>
     
    100118           
    101119           
    102120            <object size="100%-425 363 100%-325 455" name="mapTypeSelectionTooltip">
    103                 <object type="text" style="RightLabelText" size="0 0 100% 30">Match Type:</object>
    104                 <object type="text" style="RightLabelText" size="0 32 100% 62">Map Filter:</object>
    105                 <object type="text" style="RightLabelText" size="0 64 100% 94">Select Map:</object>
    106                 <object type="text" style="RightLabelText" size="0 96 100% 126">Map Size:</object>
     121                <object type="text" style="RightLabelText" size="0 0 100% 30">
     122                    <translatableAttribute id="caption">Match Type:</translatableAttribute>
     123                </object>
     124                <object type="text" style="RightLabelText" size="0 32 100% 62">
     125                    <translatableAttribute id="caption">Map Filter:</translatableAttribute>
     126                </object>
     127                <object type="text" style="RightLabelText" size="0 64 100% 94">
     128                    <translatableAttribute id="caption">Select Map:</translatableAttribute>
     129                </object>
     130                <object type="text" style="RightLabelText" size="0 96 100% 126">
     131                    <translatableAttribute id="caption">Map Size:</translatableAttribute>
     132                </object>
    107133            </object>
    108134
    109135            <object size="100%-327 363 100%-25 423" name="mapFilterSelectionTooltip">
     
    117143                type="dropdown"
    118144                style="StoneDropDown"
    119145                size="100%-325 363 100%-25 391"
    120                 tooltip_style="onscreenToolTip"
    121                 tooltip="Select a map type.">
     146                tooltip_style="onscreenToolTip">
     147                <translatableAttribute id="tooltip">Select a map type.</translatableAttribute>
    122148                <action on="SelectionChange">selectMapType(this.list_data[this.selected]);</action>
    123149            </object>
    124150
     
    126152                type="dropdown"
    127153                style="StoneDropDown"
    128154                size="100%-325 395 100%-25 423"
    129                 tooltip_style="onscreenToolTip"
    130                 tooltip="Select a map filter.">
    131                 <action on="SelectionChange">selectMapFilter(this.list[this.selected]);</action>
     155                tooltip_style="onscreenToolTip">
     156                <translatableAttribute id="tooltip">Select a map filter.</translatableAttribute>
     157                <action on="SelectionChange">selectMapFilter(this.list_data[this.selected]);</action>
    132158            </object>
    133159           
    134160            <object size="100%-325 427 100%-25 455" name="mapSelectionPannel" z="55">
     
    137163                    style="StoneDropDown"
    138164                    type="dropdown"
    139165                    size="0 0 100% 100%"
    140                     tooltip_style="onscreenToolTip"
    141                     tooltip="Select a map to play on.">
     166                    tooltip_style="onscreenToolTip">
     167                    <translatableAttribute id="tooltip">Select a map to play on.</translatableAttribute>
    142168                    <action on="SelectionChange">selectMap(this.list_data[this.selected]);</action>
    143169                </object>
    144170
    145171            </object>
    146172           
    147             <object name="mapSize" size="100%-325 459 100%-25 487" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select map size. (Larger sizes may reduce performance.)"/>     
     173            <object name="mapSize" size="100%-325 459 100%-25 487" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip">
     174                <translatableAttribute id="tooltip">Select map size. (Larger sizes may reduce performance.)</translatableAttribute>
     175            </object>
    148176           
    149177            <!-- Map Preview -->
    150178            <object  type="image" sprite="BackgroundIndentFillDark" name="gamePreviewBox" size="100%-426 57 100%-24 359">
     
    168196                    style="StoneButton"
    169197                    size="100%-120 0 100% 28"
    170198                    tooltip_style="onscreenToolTip"
    171                     tooltip="See more game options"
    172199                >
    173                     More Options
     200                    <translatableAttribute id="caption">More Options</translatableAttribute>
     201                    <translatableAttribute id="tooltip">See more game options</translatableAttribute>
    174202                    <action on="Press">toggleMoreOptions();</action>
    175203                </object>
    176204               
     
    180208            <!-- More Options -->
    181209            <object name="moreOptions" type="image" sprite="StoneWindow" size="50%-200 50%-150 50%+200 50%+155" z="70" hidden="true">
    182210                <object style="TitleText" type="text" size="50%-128 11 50%+128 27">
    183                     More Options
     211                    <translatableAttribute id="caption">More Options</translatableAttribute>
    184212                </object>
    185213               
    186214                <object size="14 38 94% 66">
    187215                    <object size="0 0 40% 28">
    188                         <object size="0 0 100% 100%" type="text" style="RightLabelText">Game Speed:</object>
     216                        <object size="0 0 100% 100%" type="text" style="RightLabelText">
     217                            <translatableAttribute id="caption">Game Speed:</translatableAttribute>
     218                        </object>
    189219                    </object>
    190220                    <object name="gameSpeedText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
    191                     <object name="gameSpeed" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select game speed."/>
     221                    <object name="gameSpeed" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip">
     222                        <translatableAttribute id="tooltip">Select game speed.</translatableAttribute>
     223                    </object>
    192224                </object>
    193225
    194226                <object size="14 68 94% 96">
    195227                    <object size="0 0 40% 28">
    196                         <object size="0 0 100% 100%" type="text" style="RightLabelText">Victory Condition:</object>
     228                        <object size="0 0 100% 100%" type="text" style="RightLabelText">
     229                            <translatableAttribute id="caption">Victory Condition:</translatableAttribute>
     230                        </object>
    197231                    </object>
    198232                    <object name="victoryConditionText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
    199                     <object name="victoryCondition" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select victory condition."/>
     233                    <object name="victoryCondition" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip">
     234                        <translatableAttribute id="tooltip">Select victory condition.</translatableAttribute>
     235                    </object>
    200236                </object>
    201237               
    202238                <object size="14 98 94% 126">
    203239                    <object size="0 0 40% 28">
    204                         <object size="0 0 100% 100%" type="text" style="RightLabelText">Population Cap:</object>
     240                        <object size="0 0 100% 100%" type="text" style="RightLabelText">
     241                            <translatableAttribute id="caption">Population Cap:</translatableAttribute>
     242                        </object>
    205243                    </object>
    206244                    <object name="populationCapText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
    207                     <object name="populationCap" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select population cap."/>
     245                    <object name="populationCap" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip">
     246                        <translatableAttribute id="tooltip">Select population cap.</translatableAttribute>
     247                    </object>
    208248                </object>
    209249               
    210250                <object size="14 128 94% 156">
    211251                    <object size="0 0 40% 28">
    212                         <object size="0 0 100% 100%" type="text" style="RightLabelText">Starting Resources:</object>
     252                        <object size="0 0 100% 100%" type="text" style="RightLabelText">
     253                            <translatableAttribute id="caption">Starting Resources:</translatableAttribute>
     254                        </object>
    213255                    </object>
    214256                    <object name="startingResourcesText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
    215                     <object name="startingResources" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select the game's starting resources."/>
     257                    <object name="startingResources" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip">
     258                        <translatableAttribute id="tooltip">Select the game's starting resources.</translatableAttribute>
     259                    </object>
    216260                </object>
    217261               
    218262                <object size="14 158 94% 246">
    219263                    <object size="0 0 40% 28">
    220                         <object size="0 0 100% 100%" type="text" style="RightLabelText">Reveal Map:</object>
     264                        <object size="0 0 100% 100%" type="text" style="RightLabelText">
     265                            <translatableAttribute id="caption">Reveal Map:</translatableAttribute>
     266                        </object>
    221267                    </object>
    222268                    <object size="0 30 40% 58">
    223                         <object size="0 0 100% 100%" type="text" style="RightLabelText">Teams Locked:</object>
     269                        <object size="0 0 100% 100%" type="text" style="RightLabelText">
     270                            <translatableAttribute id="caption">Teams Locked:</translatableAttribute>
     271                        </object>
    224272                    </object>
    225273                    <object size="0 60 40% 88" name="enableCheatsDesc" hidden="true">
    226                         <object size="0 0 100% 100%" type="text" style="RightLabelText">Cheats:</object>
     274                        <object size="0 0 100% 100%" type="text" style="RightLabelText">
     275                            <translatableAttribute id="caption">Cheats:</translatableAttribute>
     276                        </object>
    227277                    </object>
    228278                   
    229279                    <object size="40% 0 100% 28">
    230280                        <object name="revealMapText" size="0 0 100% 100%" type="text" style="LeftLabelText"/>
    231                         <object name="revealMap" size="4 50%-8 20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip" tooltip="Toggle reveal map."/>
     281                        <object name="revealMap" size="4 50%-8 20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip">
     282                            <translatableAttribute id="tooltip">Toggle reveal map.</translatableAttribute>
     283                        </object>
    232284                    </object>
    233285                    <object size="40% 30 100% 58">
    234286                        <object name="lockTeamsText" size="0 0 100% 100%" type="text" style="LeftLabelText"/>
    235                         <object name="lockTeams" size="4 50%-8 20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip" tooltip="Toggle locked teams."/>
     287                        <object name="lockTeams" size="4 50%-8 20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip">
     288                            <translatableAttribute id="tooltip">Toggle locked teams.</translatableAttribute>
     289                        </object>
    236290                    </object>
    237291                    <object size="40% 60 100% 88">
    238292                        <object name="enableCheatsText" size="0 0 100% 100%" type="text" style="LeftLabelText" hidden="true"/>
    239                         <object name="enableCheats" size="4 50%-8 20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip" tooltip="Toggle the usability of cheats."/>
     293                        <object name="enableCheats" size="4 50%-8 20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip">
     294                            <translatableAttribute id="tooltip">Toggle the usability of cheats.</translatableAttribute>
     295                        </object>
    240296                    </object>
    241297                </object>
    242298
     
    247303                    style="StoneButton"
    248304                    size="50%-70 248 50%+70 274"
    249305                    tooltip_style="onscreenToolTip"
    250                     tooltip="Close more game options window"
    251306                >
    252                     OK
     307                    <translatableAttribute id="caption">OK</translatableAttribute>
     308                    <translatableAttribute id="tooltip">Close more game options window</translatableAttribute>
    253309                    <action on="Press">toggleMoreOptions();</action>
    254310                </object>
    255311            <!-- End More Options -->
     
    279335                sprite="BackgroundTranslucent"
    280336                hidden="true"
    281337                size="100%-700 100%-56 100%-312 100%-24"
    282             >[Tooltip text]</object>
     338            >
     339                <translatableAttribute id="caption">[Tooltip text]</translatableAttribute>
     340            </object>
    283341           
    284342            <!-- Start Button -->
    285343            <object
     
    288346                style="StoneButton"
    289347                size="100%-308 100%-52 100%-168 100%-24"
    290348                tooltip_style="onscreenToolTip"
    291                 tooltip="Start a new game with the current settings."
    292349                enabled="false"
    293350            >
    294                 Start game!
     351                <translatableAttribute id="caption">Start game!</translatableAttribute>
     352                <translatableAttribute id="tooltip">Start a new game with the current settings.</translatableAttribute>
    295353                <action on="Press">launchGame();</action>
    296354            </object>
    297355
     
    302360                style="StoneButton"
    303361                size="100%-164 100%-52 100%-24 100%-24"
    304362                tooltip_style="onscreenToolTip"
    305                 tooltip="Return to the main menu."
    306363            >
    307                 Main menu
     364                <translatableAttribute id="caption">Main menu</translatableAttribute>
     365                <translatableAttribute id="tooltip">Return to the main menu.</translatableAttribute>
    308366                <action on="Press">
    309367                    <![CDATA[
    310368                        cancelSetup();
     
    314372            </object>
    315373
    316374        </object>
    317            
     375
    318376    </object>
    319377
    320378</objects>
  • binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js

    diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js b/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js
    index a27c993..5615a65 100644
    a b function init(multiplayerGameType)  
    1818        getGUIObjectByName("pageHost").hidden = false;
    1919        break;
    2020    default:
    21         error("Unrecognised multiplayer game type : " + multiplayerGameType);
     21        error(sprintf(translate("Unrecognised multiplayer game type: %(gameType)s"), { gameType: multiplayerGameType }));
    2222        break;
    2323    }
    2424}
    function startConnectionStatus(type)  
    3535    g_GameType = type;
    3636    g_IsConnecting = true;
    3737    g_IsRejoining = false;
    38     getGUIObjectByName("connectionStatus").caption = "Connecting to server...";
     38    getGUIObjectByName("connectionStatus").caption = translate("Connecting to server...");
    3939}
    4040
    4141function onTick()
    function onTick()  
    4949        if (!message)
    5050            break;
    5151
    52         log("Net message: "+uneval(message));
     52        log(sprintf(translate("Net message: %(message)s"), { message: uneval(message) }));
    5353
    5454        // If we're rejoining an active game, we don't want to actually display
    5555        // the game setup screen, so perform similar processing to gamesetup.js
    function onTick()  
    6767                    return;
    6868
    6969                default:
    70                     error("Unrecognised netstatus type "+message.status);
     70                    error(sprintf(translate("Unrecognised netstatus type %(netType)s"), { netType: message.status }));
    7171                    break;
    7272                }
    7373                break;
    function onTick()  
    9393                break;
    9494
    9595            default:
    96                 error("Unrecognised net message type "+message.type);
     96                error(sprintf(translate("Unrecognised net message type %(messageType)s"), { messageType: message.type }));
    9797            }
    9898        }
    9999        else
    function onTick()  
    106106                switch (message.status)
    107107                {
    108108                case "connected":
    109                     getGUIObjectByName("connectionStatus").caption = "Registering with server...";
     109                    getGUIObjectByName("connectionStatus").caption = translate("Registering with server...");
    110110                    break;
    111111
    112112                case "authenticated":
    113113                    if (message.rejoining)
    114114                    {
    115                         getGUIObjectByName("connectionStatus").caption = "Game has already started - rejoining...";
     115                        getGUIObjectByName("connectionStatus").caption = translate("Game has already started, rejoining...");
    116116                        g_IsRejoining = true;
    117117                        return; // we'll process the game setup messages in the next tick
    118118                    }
    function onTick()  
    128128                    return;
    129129
    130130                default:
    131                     error("Unrecognised netstatus type "+message.status);
     131                    error(sprintf(translate("Unrecognised netstatus type %(netType)s"), { netType: message.status }));
    132132                    break;
    133133                }
    134134                break;
    135135            default:
    136                 error("Unrecognised net message type "+message.type);
     136                error(sprintf(translate("Unrecognised net message type %(messageType)s"), { messageType: message.type }));
    137137                break;
    138138            }
    139139        }
    function startHost(playername, servername)  
    156156    {
    157157        cancelSetup();
    158158        messageBox(400, 200,
    159             "Cannot host game: " + e.message + ".",
    160             "Error", 2);
     159            sprintf(translate("Cannot host game: %(message)s."), { message: e.message }),
     160            translate("Error"), 2);
    161161        return false;
    162162    }
    163163
    function startJoin(playername, ip)  
    177177    {
    178178        cancelSetup();
    179179        messageBox(400, 200,
    180             "Cannot join game: " + e.message + ".",
    181             "Error", 2);
     180            sprintf(translate("Cannot join game: %(message)s."), { message: e.message }),
     181            translate("Error"), 2);
    182182        return false;
    183183    }
    184184
  • binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml

    diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml
    index c8b4be8..13af6a6 100644
    a b  
    1616        </action>
    1717
    1818        <object style="TitleText" type="text" size="50%-128 0%-16 50%+128 16">
    19             Multiplayer
     19            <translatableAttribute id="caption">Multiplayer</translatableAttribute>
    2020        </object>
    2121
    2222        <object name="pageJoin" size="0 32 100% 100%" hidden="true">
    2323
    2424            <object type="text" style="CenteredLabelText" size="0 0 400 30">
    25                 Joining an existing game.
     25                <translatableAttribute id="caption">Joining an existing game.</translatableAttribute>
    2626            </object>
    2727
    2828            <object type="text" size="0 40 200 70" style="RightLabelText">
    29                 Player name:
     29                <translatableAttribute id="caption">Player name:</translatableAttribute>
    3030            </object>
    3131
    3232            <object name="joinPlayerName" type="input" size="210 40 100%-32 64" style="StoneInput">
     
    3636            </object>
    3737
    3838            <object type="text" size="0 80 200 110" style="RightLabelText">
    39                 Server Hostname or IP:
     39                <translatableAttribute id="caption">Server Hostname or IP:</translatableAttribute>
    4040            </object>
    4141
    4242            <object name="joinServer" type="input" size="210 80 100%-32 104" style="StoneInput">
     
    4646            </object>3 100%-33 103 100%-3
    4747
    4848            <object hotkey="confirm" type="button" size="50%-144 100%-60 50%-16 100%-32" style="StoneButton">
    49                 Continue
     49                <translatableAttribute id="caption">Continue</translatableAttribute>
    5050                <action on="Press">
    5151                    var joinPlayerName = getGUIObjectByName("joinPlayerName").caption;
    5252                    var joinServer = getGUIObjectByName("joinServer").caption;
     
    6262        <object name="pageHost" size="0 32 100% 100%" hidden="true">
    6363
    6464            <object type="text" style="CenteredLabelText" size="0 0 400 30">
    65                 Set up your server to host.
     65                <translatableAttribute id="caption">Set up your server to host.</translatableAttribute>
    6666            </object>
    6767
    6868            <object type="text" size="0 40 200 70" style="RightLabelText">
    69                 Player name:
     69                <translatableAttribute id="caption">Player name:</translatableAttribute>
    7070            </object>
    7171
    7272            <object name="hostPlayerName" type="input" size="210 40 100%-32 64" style="StoneInput">
     
    7777
    7878            <object hidden="true"> <!-- TODO: restore this when the server name is actually used -->
    7979            <object type="text" size="0 80 200 110" style="RightLabelText">
    80                 Server name:
     80                <translatableAttribute id="caption">Server name:</translatableAttribute>
    8181            </object>
    8282
    8383            <object name="hostServerName" type="input" size="210 80 100%-32 104" style="StoneInput">
     
    8888            </object>
    8989
    9090            <object type="button" size="50%-144 100%-60 50%-16 100%-32" style="StoneButton">
    91                 Continue
     91                <translatableAttribute id="caption">Continue</translatableAttribute>
    9292                <action on="Press">
    9393                    var hostPlayerName = getGUIObjectByName("hostPlayerName").caption;
    9494                    Engine.SaveMPConfig(hostPlayerName, Engine.GetDefaultMPServer());
     
    103103        </object>
    104104
    105105        <object type="button" style="StoneButton" size="50%+16 100%-60 50%+144 100%-32">
    106             Cancel
     106            <translatableAttribute id="caption">Cancel</translatableAttribute>
    107107            <action on="Press">cancelSetup();</action>
    108108        </object>
    109109
    110110        <object name="pageConnecting" hidden="true">
    111111            <object name="connectionStatus" type="text" style="CenteredLabelText" size="0 100 100% 120">
    112                 [Connection status]
     112                <translatableAttribute id="caption">[Connection status]</translatableAttribute>
    113113            </object>
    114114        </object>
    115115
  • binaries/data/mods/public/gui/loading/loading.js

    diff --git a/binaries/data/mods/public/gui/loading/loading.js b/binaries/data/mods/public/gui/loading/loading.js
    index 182d86c..5a2649f 100644
    a b function init(data)  
    1515    {
    1616        // Set tip text
    1717        var tipTextFilePath = tipTextLoadingArray[getRandom (0, tipTextLoadingArray.length-1)];
    18         var tipText = readFile(tipTextFilePath);
     18        var tipText = Engine.translateLines(readFile(tipTextFilePath));
    1919
    2020        if (tipText)
    2121        {
    function init(data)  
    3434    }
    3535    else
    3636    {
    37         error("Failed to find any matching tips for the loading screen.")
     37        error(translate("Failed to find any matching tips for the loading screen."))
    3838    }
    3939
    4040    // janwas: main loop now sets progress / description, but that won't
    function init(data)  
    4848        {
    4949        case "skirmish":
    5050        case "scenario":
    51             loadingMapName.caption = "Loading \"" + mapName + "\"";
     51            loadingMapName.caption = sprintf(translate("Loading \"%(map)s\""), {map: mapName});
    5252            break;
    5353
    5454        case "random":
    55             loadingMapName.caption = "Generating \"" + mapName + "\"";
     55            loadingMapName.caption = sprintf(translate("Generating \"%(map)s\""), {map: mapName});
    5656            break;
    5757
    5858        default:
    59             error("Unknown map type: " + data.attribs.mapType);
     59            error(sprintf(translate("Unknown map type: %(mapType)s"), { mapType: data.attribs.mapType }));
    6060        }
    6161    }
    6262
    function init(data)  
    6565
    6666    // Pick a random quote of the day (each line is a separate tip).
    6767    var quoteArray = readFileLines("gui/text/quotes.txt");
    68     getGUIObjectByName("quoteText").caption = quoteArray[getRandom(0, quoteArray.length-1)];
     68    getGUIObjectByName("quoteText").caption = translate(quoteArray[getRandom(0, quoteArray.length-1)]);
    6969}
    7070
    7171// ====================================================================
  • binaries/data/mods/public/gui/loading/loading.xml

    diff --git a/binaries/data/mods/public/gui/loading/loading.xml b/binaries/data/mods/public/gui/loading/loading.xml
    index 7ad8c38..d7b9a49 100644
    a b  
    4444
    4545        <!-- LOADING SCREEN QUOTE (needs increased z value to overcome the transparent area of the tip image above it -->
    4646        <object size="50%-448 50%+230 50%+448 100%-16" z="20">
    47             <object name="quoteTitleText" size="0 0 100% 30" type="text" style="LoadingTitleText">Quote of the Day:</object>
     47            <object name="quoteTitleText" size="0 0 100% 30" type="text" style="LoadingTitleText">
     48                <translatableAttribute id="caption">Quote of the Day:</translatableAttribute>
     49            </object>
    4850            <object name="quoteText" size="0 30 100% 100%" type="text" style="LoadingText"></object>
    4951        </object>
    5052    </object>
  • new file inaries/data/mods/public/gui/locale/locale.js

    diff --git a/binaries/data/mods/public/gui/locale/locale.js b/binaries/data/mods/public/gui/locale/locale.js
    new file mode 100644
    index 0000000..1aa587f
    - +  
     1function init()
     2{
     3    var languageList = getGUIObjectByName("languageList");
     4    languageList.list = Engine.GetSupportedLocaleDisplayNames();
     5    languageList.list_data = Engine.GetSupportedLocaleCodes();
     6    languageList.selected = Engine.GetCurrentLocaleIndex();
     7}
     8
     9function cancelSetup()
     10{
     11    Engine.PopGuiPage();
     12}
     13
     14function applySelectedLocale()
     15{
     16    var languageList = getGUIObjectByName("languageList");
     17    Engine.SetLocale(languageList.list_data[languageList.selected]);
     18    Engine.SwitchGuiPage("page_pregame.xml");
     19}
  • new file inaries/data/mods/public/gui/locale/locale.xml

    diff --git a/binaries/data/mods/public/gui/locale/locale.xml b/binaries/data/mods/public/gui/locale/locale.xml
    new file mode 100644
    index 0000000..f83fdff
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2
     3<objects>
     4
     5    <script file="gui/common/functions_global_object.js"/>
     6    <script file="gui/locale/locale.js"/>
     7
     8    <!-- Add a translucent black background to fade out the menu page -->
     9    <object type="image" z="0" sprite="bkTranslucent"/>
     10
     11    <object type="image" style="StoneDialog" size="50%-190 50%-100 50%+190 50%+100">
     12
     13        <object style="TitleText" type="text" size="50%-128 0%-16 50%+128 16">
     14            <translatableAttribute id="caption">Language</translatableAttribute>
     15        </object>
     16
     17        <object size="0 32 100% 100%">
     18            <object type="text" size="0 0 140 30" style="RightLabelText">
     19                <translatableAttribute id="caption">Language:</translatableAttribute>
     20            </object>
     21
     22            <object name="languageList"
     23                    type="dropdown"
     24                    style="StoneDropDown"
     25                    size="150 0 100%-32 28">
     26            </object>
     27
     28            <object type="text" size="30 40 100%-30 100" style="LeftLabelText">
     29                <translatableAttribute id="caption">Note: Restart the game for these changes to take effect.</translatableAttribute>
     30            </object>
     31
     32            <object type="button" size="50%-154 100%-60 50%+10 100%-32" style="StoneButton">
     33                <translatableAttribute id="caption">Accept</translatableAttribute>
     34                <action on="Press">applySelectedLocale();</action>
     35            </object>
     36        </object>
     37
     38        <object type="button" style="StoneButton" size="50%+26 100%-60 50%+154 100%-32">
     39            <translatableAttribute id="caption">Cancel</translatableAttribute>
     40            <action on="Press">cancelSetup();</action>
     41        </object>
     42
     43    </object>
     44
     45</objects>
  • binaries/data/mods/public/gui/manual/manual.js

    diff --git a/binaries/data/mods/public/gui/manual/manual.js b/binaries/data/mods/public/gui/manual/manual.js
    index 0d6ad6e..191d966 100644
    a b var closeCallback;  
    22
    33function init(data)
    44{
    5     getGUIObjectByName("mainText").caption = readFile("gui/manual/" + data.page + ".txt");
     5    getGUIObjectByName("mainText").caption = Engine.translateLines(readFile("gui/manual/" + data.page + ".txt"));
    66    closeCallback = data.closeCallback;
    77}
    88
  • binaries/data/mods/public/gui/manual/manual.xml

    diff --git a/binaries/data/mods/public/gui/manual/manual.xml b/binaries/data/mods/public/gui/manual/manual.xml
    index eff09d8..1c7fe52 100644
    a b  
    88    <object type="image" z="0" style="TranslucentPanel"/>
    99
    1010    <object type="image" style="StoneDialog" size="50%-466 50%-316 50%+466 50%+316">
    11     <object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">Manual</object>
     11        <object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">
     12            <translatableAttribute id="caption">Manual</translatableAttribute>
     13        </object>
    1214
    1315        <object type="image" sprite="BackgroundTranslucent" size="20 20 100%-20 100%-58">
    1416            <object name="mainText" type="text" style="textPanel"/>
    1517        </object>
    16     <object type="button" style="StoneButton" size="100%-308 100%-52 100%-168 100%-24">
    17             Online Manual
     18
     19        <object type="button" style="StoneButton" size="100%-308 100%-52 100%-168 100%-24">
     20            <translatableAttribute id="caption">Online Manual</translatableAttribute>
    1821            <action on="Press"><![CDATA[
    1922                var url = "http://trac.wildfiregames.com/wiki/0adManual";
    2023                Engine.OpenURL(url);
    2124                messageBox(450, 200, "Opening "+url+"\n in default web browser. Please wait...", "Opening page", 2);
    22                 ]]></action>
     25            ]]></action>
    2326        </object>
    2427        <object type="button" style="StoneButton" tooltip_style="snToolTip" size="100%-164 100%-52 100%-24 100%-24">
    25             Close
     28            <translatableAttribute id="caption">Close</translatableAttribute>
    2629            <action on="Press"><![CDATA[closeManual();]]></action>
    2730        </object>
    2831    </object>
  • new file inaries/data/mods/public/gui/msgbox/msgbox.js

    diff --git a/binaries/data/mods/public/gui/msgbox/msgbox.js b/binaries/data/mods/public/gui/msgbox/msgbox.js
    new file mode 100644
    index 0000000..954cab4
    - +  
     1function init(data)
     2{
     3    var mbMainObj = getGUIObjectByName("mbMain");
     4    var mbTitleObj = getGUIObjectByName("mbTitleBar");
     5    var mbTextObj = getGUIObjectByName("mbText");
     6
     7    var mbButton1Obj = getGUIObjectByName("mbButton1");
     8    var mbButton2Obj = getGUIObjectByName("mbButton2");
     9    var mbButton3Obj = getGUIObjectByName("mbButton3");
     10
     11    // Calculate size
     12    var mbLRDiff = data.width / 2;     // Message box left/right difference from 50% of screen
     13    var mbUDDiff = data.height / 2;    // Message box up/down difference from 50% of screen
     14
     15    var mbSizeString = "50%-" + mbLRDiff + " 50%-" + mbUDDiff + " 50%+" + mbLRDiff + " 50%+" + mbUDDiff;
     16
     17    mbMainObj.size = mbSizeString;
     18
     19    // Texts
     20    mbTitleObj.caption  = data.title;
     21    mbTextObj.caption   = data.message;
     22
     23    if (data.font)
     24        mbTextObj.font = data.font;
     25
     26    // Message box modes
     27    // There is a number of standard modes, and if none of these is used (mbMode == 0), the button captions will be
     28    // taken from the array mbButtonCaptions; there currently is a maximum of three buttons.
     29    switch (data.mode)
     30    {
     31    case 1:
     32        // Simple Yes/No question box
     33        data.buttonCaptions = [translate("Yes"), translate("No")];
     34        break;
     35    case 2:
     36        // Okay-only box
     37        data.buttonCaptions = [translate("OK")];
     38        break;
     39    case 3:
     40        // Retry/Abort/Ignore box (will we ever need this?!)
     41        data.buttonCaptions = [translate("Retry"), translate("Ignore"), translate("Abort")];
     42    default:
     43        break;
     44    }
     45
     46    // Buttons
     47    var codes = data.buttonCode;
     48    if (data.buttonCaptions.length >= 1)
     49    {
     50        mbButton1Obj.caption = data.buttonCaptions[0];
     51        mbButton1Obj.onPress = function () { Engine.PopGuiPage(); if (codes && codes[0]) codes[0](); }
     52        mbButton1Obj.hidden = false;
     53    }
     54    if (data.buttonCaptions.length >= 2)
     55    {
     56        mbButton2Obj.caption = data.buttonCaptions[1];
     57        mbButton2Obj.onPress = function () { Engine.PopGuiPage(); if (codes && codes[1]) codes[1](); }
     58        mbButton2Obj.hidden = false;
     59    }
     60    if (data.buttonCaptions.length >= 3)
     61    {
     62        mbButton3Obj.caption = data.buttonCaptions[2];
     63        mbButton3Obj.onPress = function () { Engine.PopGuiPage(); if (codes && codes[2]) codes[2](); }
     64        mbButton3Obj.hidden = false;
     65    }
     66
     67    switch (data.buttonCaptions.length)
     68    {
     69    case 1:
     70        mbButton1Obj.size = "50%-64 100%-76 50%+64 100%-48";
     71        break;
     72    case 2:
     73        mbButton1Obj.size = "50%-144 100%-76 50%-16 100%-48";
     74        mbButton2Obj.size = "50%+16 100%-76 50%+144 100%-48";
     75        break;
     76    case 3:
     77        mbButton1Obj.size = "10% 100%-76 30% 100%-48";
     78        mbButton2Obj.size = "40% 100%-76 60% 100%-48";
     79        mbButton3Obj.size = "70% 100%-76 90% 100%-48";
     80        break;
     81    }
     82}
  • binaries/data/mods/public/gui/msgbox/msgbox.xml

    diff --git a/binaries/data/mods/public/gui/msgbox/msgbox.xml b/binaries/data/mods/public/gui/msgbox/msgbox.xml
    index 9e05a0a..508de2d 100644
    a b  
    11<?xml version="1.0" encoding="utf-8"?>
    22
    33<objects>
    4     <script><![CDATA[
    5     function init(data)
    6     {
    7         var mbMainObj = getGUIObjectByName("mbMain");
    8         var mbTitleObj = getGUIObjectByName("mbTitleBar");
    9         var mbTextObj = getGUIObjectByName("mbText");
    10 
    11         var mbButton1Obj = getGUIObjectByName("mbButton1");
    12         var mbButton2Obj = getGUIObjectByName("mbButton2");
    13         var mbButton3Obj = getGUIObjectByName("mbButton3");
    14 
    15         // Calculate size
    16         var mbLRDiff = data.width / 2;     // Message box left/right difference from 50% of screen
    17         var mbUDDiff = data.height / 2;    // Message box up/down difference from 50% of screen
    18 
    19         var mbSizeString = "50%-" + mbLRDiff + " 50%-" + mbUDDiff + " 50%+" + mbLRDiff + " 50%+" + mbUDDiff;
    20 
    21         mbMainObj.size = mbSizeString;
    22 
    23         // Texts
    24         mbTitleObj.caption  = data.title;
    25         mbTextObj.caption   = data.message;
    26 
    27         if (data.font)
    28             mbTextObj.font = data.font;
    29 
    30         // Message box modes
    31         // There is a number of standard modes, and if none of these is used (mbMode == 0), the button captions will be
    32         // taken from the array mbButtonCaptions; there currently is a maximum of three buttons.
    33         switch (data.mode)
    34         {
    35         case 1:
    36             // Simple Yes/No question box
    37             data.buttonCaptions = ["Yes", "No"];
    38             break;
    39         case 2:
    40             // Okay-only box
    41             data.buttonCaptions = ["OK"];
    42             break;
    43         case 3:
    44             // Retry/Abort/Ignore box (will we ever need this?!)
    45             data.buttonCaptions = ["Retry", "Ignore", "Abort"];
    46         default:
    47             break;
    48         }
    49 
    50         // Buttons
    51         var codes = data.buttonCode;
    52         if (data.buttonCaptions.length >= 1)
    53         {
    54             mbButton1Obj.caption = data.buttonCaptions[0];
    55             mbButton1Obj.onPress = function () { Engine.PopGuiPage(); if (codes && codes[0]) codes[0](); }
    56             mbButton1Obj.hidden = false;
    57         }
    58         if (data.buttonCaptions.length >= 2)
    59         {
    60             mbButton2Obj.caption = data.buttonCaptions[1];
    61             mbButton2Obj.onPress = function () { Engine.PopGuiPage(); if (codes && codes[1]) codes[1](); }
    62             mbButton2Obj.hidden = false;
    63         }
    64         if (data.buttonCaptions.length >= 3)
    65         {
    66             mbButton3Obj.caption = data.buttonCaptions[2];
    67             mbButton3Obj.onPress = function () { Engine.PopGuiPage(); if (codes && codes[2]) codes[2](); }
    68             mbButton3Obj.hidden = false;
    69         }
    70 
    71         switch (data.buttonCaptions.length)
    72         {
    73         case 1:
    74             mbButton1Obj.size = "50%-64 100%-76 50%+64 100%-48";
    75             break;
    76         case 2:
    77             mbButton1Obj.size = "50%-144 100%-76 50%-16 100%-48";
    78             mbButton2Obj.size = "50%+16 100%-76 50%+144 100%-48";
    79             break;
    80         case 3:
    81             mbButton1Obj.size = "10% 100%-76 30% 100%-48";
    82             mbButton2Obj.size = "40% 100%-76 60% 100%-48";
    83             mbButton3Obj.size = "70% 100%-76 90% 100%-48";
    84             break;
    85         }
    86     }
    87     ]]></script>
     4    <script file="gui/msgbox/msgbox.js"/>
    885
    896    <object hotkey="leave">
    907        <action on="Press">Engine.PopGuiPage();</action>
  • binaries/data/mods/public/gui/options/options.xml

    diff --git a/binaries/data/mods/public/gui/options/options.xml b/binaries/data/mods/public/gui/options/options.xml
    index 6870c15..3650bef 100644
    a b  
    1313    <script file="gui/session/music.js"/>
    1414    <script file="gui/options/options.js"/>
    1515   
    16         <!-- Add a translucent black background to fade out the menu page -->
     16    <!-- Add a translucent black background to fade out the menu page -->
    1717    <object type="image" z="0" style="TranslucentPanel"/>
    18        
    19         <!-- Settings Window -->
     18
     19    <!-- Settings Window -->
    2020    <object name="options" type="image" style="StonePanelLight" size="50%-190 50%-120 50%+190 50%+120">
    2121
    22                 <object style="StoneDialogTitleBar" type="text" size="50%-128 0%-16 50%+128 16">
    23             Game Options
     22        <object style="StoneDialogTitleBar" type="text" size="50%-128 0%-16 50%+128 16">
     23            <translatableAttribute id="caption">Game Options</translatableAttribute>
    2424        </object>
    2525
    26                 <object size="50%-190 50%-80 50%+140 50%+95">
    27        
    28                         <!-- Settings / shadows -->
    29                         <object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">Enable Shadows</object>
    30                         <object name="shadowsCheckbox" size="100%-56 15 100%-30 40" type="checkbox" style="StoneCrossBox" checked="true">
    31                                 <action on="Load">this.checked = Engine.Renderer_GetShadowsEnabled();</action>
    32                                 <action on="Press">Engine.Renderer_SetShadowsEnabled(this.checked);</action>
    33                         </object>   
     26        <object size="50%-190 50%-80 50%+140 50%+95">
     27
     28            <!-- Settings / shadows -->
     29            <object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">
     30                <translatableAttribute id="caption">Enable Shadows</translatableAttribute>
     31            </object>
     32            <object name="shadowsCheckbox" size="100%-56 15 100%-30 40" type="checkbox" style="StoneCrossBox" checked="true">
     33                <action on="Load">this.checked = Engine.Renderer_GetShadowsEnabled();</action>
     34                <action on="Press">Engine.Renderer_SetShadowsEnabled(this.checked);</action>
     35            </object>
    3436
    35                         <!-- Settings / Shadow PCF -->
    36                         <object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
    37                         <object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
    38                                 <action on="Load">this.checked = Engine.Renderer_GetShadowPCFEnabled();</action>
    39                                 <action on="Press">Engine.Renderer_SetShadowPCFEnabled(this.checked);</action>
    40                         </object>
     37            <!-- Settings / Shadow PCF -->
     38            <object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">
     39                <translatableAttribute id="caption">Enable Shadow Filtering</translatableAttribute>
     40            </object>
     41            <object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
     42                <action on="Load">this.checked = Engine.Renderer_GetShadowPCFEnabled();</action>
     43                <action on="Press">Engine.Renderer_SetShadowPCFEnabled(this.checked);</action>
     44            </object>
    4145
    4246                        <!-- Settings / Water -->
    4347<!--                        <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Water Reflections</object>
     
    4650                                <action on="Press">Engine.Renderer_SetWaterNormalEnabled(this.checked);</action>
    4751                        </object>-->
    4852
    49                         <!-- Settings / Music-->
    50                         <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Music</object>
    51                         <object size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
    52                                 <action on="Press">if (this.checked) startMusic(); else stopMusic();</action>
    53                         </object>
     53            <!-- Settings / Music-->
     54            <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">
     55                <translatableAttribute id="caption">Enable Music</translatableAttribute>
     56            </object>
     57            <object size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
     58                <action on="Press">if (this.checked) startMusic(); else stopMusic();</action>
     59            </object>
    5460
    5561                        <!-- Settings / Dev Overlay -->
    5662<!--                        <object size="0 110 100%-80 135" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
    5763                                <object size="100%-56 115 100%-30 140" type="checkbox" style="StoneCrossBox" checked="false">
    5864                                <action on="Press">toggleDeveloperOverlay();</action>
    5965                        </object>-->
    60                 </object>
    61                
    62                 <object type="button" style="StoneButton" size="50%-64 100%-64 50%+64 100%-32">
    63             Cancel
     66        </object>
     67
     68        <object type="button" style="StoneButton" size="50%-64 100%-64 50%+64 100%-32">
     69            <translatableAttribute id="caption">Cancel</translatableAttribute>
    6470            <action on="Press"><![CDATA[Engine.PopGuiPage();]]></action>
    6571        </object>
    6672
    67         </object>
     73    </object>
    6874</objects>
  • new file inaries/data/mods/public/gui/page_locale.xml

    diff --git a/binaries/data/mods/public/gui/page_locale.xml b/binaries/data/mods/public/gui/page_locale.xml
    new file mode 100644
    index 0000000..ac75bc1
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2<page>
     3    <include>common/setup.xml</include>
     4    <include>common/styles.xml</include>
     5    <include>common/sprite1.xml</include>
     6    <include>common/common_sprites.xml</include>
     7    <include>common/common_styles.xml</include>
     8
     9    <include>locale/locale.xml</include>
     10
     11    <include>common/global.xml</include>
     12</page>
  • binaries/data/mods/public/gui/pregame/mainmenu.js

    diff --git a/binaries/data/mods/public/gui/pregame/mainmenu.js b/binaries/data/mods/public/gui/pregame/mainmenu.js
    index 43d1f4a..665da98 100644
    a b function formatUserReportStatus(status)  
    103103    var d = status.split(/:/, 3);
    104104
    105105    if (d[0] == "disabled")
    106         return "disabled";
     106        return translate("disabled");
    107107
    108108    if (d[0] == "connecting")
    109         return "connecting to server";
     109        return translate("connecting to server");
    110110
    111111    if (d[0] == "sending")
    112112    {
    113113        var done = d[1];
    114         return "uploading (" + Math.floor(100*done) + "%)";
     114        return sprintf(translate("uploading (%f%%)"), Math.floor(100*done));
    115115    }
    116116
    117117    if (d[0] == "completed")
    118118    {
    119119        var httpCode = d[1];
    120120        if (httpCode == 200)
    121             return "upload succeeded";
     121            return translate("upload succeeded");
    122122        else
    123             return "upload failed (" + httpCode + ")";
     123            return sprintf(translate("upload failed (%{errorCode}s)"), { errorCode: httpCode });
    124124    }
    125125
    126126    if (d[0] == "failed")
    127127    {
    128128        var errCode = d[1];
    129129        var errMessage = d[2];
    130         return "upload failed (" + errMessage + ")";
     130        return sprintf(translate("upload failed (%{errorMessage}s)"), { errorMessage: errMessage });
    131131    }
    132132
    133     return "unknown";
     133    return translate("unknown");
    134134}
    135135
    136136var lastTickTime = new Date;
    function onTick()  
    166166            messageBox(
    167167                600,
    168168                300,
    169                 "[font=\"serif-bold-16\"][color=\"200 20 20\"]Warning:[/color] You appear to be using non-shader (fixed function) graphics. This option will be removed in a future 0 A.D. release, to allow for more advanced graphics features. We advise upgrading your graphics card to a more recent, shader-compatible model.\n\nPlease press \"Read More\" for more information or \"Ok\" to continue.",
    170                 "WARNING!",
     169                "[font=\"serif-bold-16\"]" +
     170                sprintf(translate("%{startWarning}sWarning:%{endWarning}s You appear to be using non-shader (fixed function) graphics. This option will be removed in a future 0 A.D. release, to allow for more advanced graphics features. We advise upgrading your graphics card to a more recent, shader-compatible model."), { startWarning: "[color=\"200 20 20\"]", endWarning: "[/color]"}) +
     171                "\n\n" +
     172                translate("Please press \"Read More\" for more information or \"OK\" to continue."),
     173                translate("WARNING!"),
    171174                0,
    172                 ["Ok", "Read More"],
     175                [translate("OK"), translate("Read More")],
    173176                [null, function() { Engine.OpenURL("http://www.wildfiregames.com/forum/index.php?showtopic=16734"); }]
    174177            );
    175178    }
    function blendSubmenuIntoMain(topPosition, bottomPosition)  
    264267    bottomSprite.size = "100%-2 " + (bottomPosition) + " 100% 100%";
    265268}
    266269
     270function getBuildString()
     271{
     272    return sprintf(translate("Build: %(buildDate)s (%(revision)s)"), { buildDate: Engine.GetBuildTimestamp(0), revision: Engine.GetBuildTimestamp(2) });
     273}
     274
    267275/*
    268276 * FUNCTIONS BELOW DO NOT WORK YET
    269277 */
    function blendSubmenuIntoMain(topPosition, bottomPosition)  
    319327//      guiUnHide ("pg");
    320328//  }
    321329//}
     330
     331function exitGamePressed()
     332{
     333    closeMenu();
     334    var btCaptions = [translate("Yes"), translate("No")];
     335    var btCode = [exit, null];
     336    messageBox(400, 200, translate("Are you sure you want to quit 0 A.D.?"), translate("Confirmation"), 0, btCaptions, btCode);
     337}
     338
     339function pressedScenarioEditorButton()
     340{
     341    closeMenu();
     342    // Start Atlas
     343    if (Engine.AtlasIsAvailable())
     344        Engine.RestartInAtlas();
     345    else
     346        messageBox(400, 200, translate("The scenario editor is not available or failed to load."), translate("Error"), 2);
     347}
  • binaries/data/mods/public/gui/pregame/mainmenu.xml

    diff --git a/binaries/data/mods/public/gui/pregame/mainmenu.xml b/binaries/data/mods/public/gui/pregame/mainmenu.xml
    index 837f6bf..b905dcf 100644
    a b  
    9191            <object
    9292                type="text"
    9393                style="userReportText"
    94 >[font="serif-bold-16"]Help improve 0 A.D.![/font]
    95 
    96 You can automatically send us anonymous feedback that will help us fix bugs, and improve performance and compatibility.
     94            >
     95                <attribute id="caption">
     96                    <keep>[font="serif-bold-16"]</keep>
     97                    <translate>Help improve 0 A.D.!</translate>
     98                    <keep>[/font]\n\n</keep>
     99                    <translate>You can automatically send us anonymous feedback that will help us fix bugs, and improve performance and compatibility.</translate>
     100                </attribute>
    97101            </object>
    98102            <object type="button" style="StoneButton" size="8 100%-36 146 100%-8">
    99                 Enable feedback
     103                <translatableAttribute id="caption">Enable feedback</translatableAttribute>
    100104                <action on="Press">EnableUserReport(true);</action>
    101105            </object>
    102106            <object type="button" style="StoneButton" size="100%-146 100%-36 100%-8 100%-8">
    103                 Technical details
     107                <translatableAttribute id="caption">Technical details</translatableAttribute>
    104108                <action on="Press">Engine.PushGuiPage("page_manual.xml", { "page": "userreport" });</action>
    105109            </object>
    106110        </object>
    You can automatically send us anonymous feedback that will help us fix bugs, and  
    115119                name="userReportEnabledText"
    116120                type="text"
    117121                style="userReportText"
    118 >[font="serif-bold-16"]Thank you for helping improve 0 A.D.![/font]
    119 
    120 Anonymous feedback is currently enabled.
    121 Status: $status.
    122                         </object>
     122            >
     123                <attribute id="caption">
     124                    <keep>[font="serif-bold-16"]</keep>
     125                    <translate>Thank you for helping improve 0 A.D.!</translate>
     126                    <keep>[/font]\n\n</keep>
     127                    <translate>Anonymous feedback is currently enabled.</translate>
     128                    <keep>\n</keep>
     129                    <translate>Status: $status.</translate>
     130                </attribute>
     131            </object>
    123132
    124133            <object type="button" style="StoneButton" size="8 100%-36 146 100%-8">
    125                 Disable feedback
     134                <translatableAttribute id="caption">Disable feedback</translatableAttribute>
    126135                <action on="Press">EnableUserReport(false);</action>
    127136            </object>
    128137            <object type="button" style="StoneButton" size="100%-146 100%-36 100%-8 100%-8">
    129                 Technical details
     138                <translatableAttribute id="caption">Technical details</translatableAttribute>
    130139                <action on="Press">Engine.PushGuiPage("page_manual.xml", { "page": "userreport" });</action>
    131140            </object>
    132141        </object>
    Status: $status.  
    157166                type="image"
    158167                size="0 4 100%-4 100%-4"
    159168                tooltip_style="pgToolTip"
    160                 tooltip="The 0 A.D. Game Manual."
    161169                hidden="true"
    162170            >
    163171                <object name="subMenuSinglePlayerButton"
    Status: $status.  
    165173                    style="StoneButtonFancy"
    166174                    size="0 0 100% 28"
    167175                    tooltip_style="pgToolTip"
    168                     tooltip="Click here to start a new single player game."
    169176                >
    170                     Matches
     177                    <translatableAttribute id="caption">Matches</translatableAttribute>
     178                    <translatableAttribute id="tooltip">Click here to start a new single player game.</translatableAttribute>
    171179                    <action on="Press">
    172180                        Engine.SwitchGuiPage("page_gamesetup.xml", { type: "offline" });
    173181                    </action>
    Status: $status.  
    178186                    style="StoneButtonFancy"
    179187                    size="0 32 100% 60"
    180188                    tooltip_style="pgToolTip"
    181                     tooltip="Relive history through historical military campaigns. [NOT YET IMPLEMENTED]"
    182189                    enabled="false"
    183190                >
    184                     Campaigns
     191                    <translatableAttribute id="caption">Campaigns</translatableAttribute>
     192                    <translatableAttribute id="tooltip">Relive history through historical military campaigns. [NOT YET IMPLEMENTED]</translatableAttribute>
    185193                    <action on="Press">
    186194                    closeMenu();
    187195                        <![CDATA[
    Status: $status.  
    196204                    style="StoneButtonFancy"
    197205                    size="0 64 100% 92"
    198206                    tooltip_style="pgToolTip"
    199                     tooltip="Click here to load a saved game."
    200207                >
    201                     Load Game
     208                    <translatableAttribute id="caption">Load Game</translatableAttribute>
     209                    <translatableAttribute id="tooltip">Click here to load a saved game.</translatableAttribute>
    202210                    <action on="Press">
    203211                        closeMenu();
    204212                        Engine.PushGuiPage("page_loadgame.xml", { type: "offline" });
    Status: $status.  
    212220                type="image"
    213221                size="0 4 100%-4 100%-4"
    214222                tooltip_style="pgToolTip"
    215                 tooltip="The 0 A.D. Game Manual"
    216223                hidden="true"
    217224            >
    218225                <object name="subMenuMultiplayerJoinButton"
    Status: $status.  
    220227                    style="StoneButtonFancy"
    221228                    size="0 0 100% 28"
    222229                    tooltip_style="pgToolTip"
    223                     tooltip="Joining an existing multiplayer game."
    224230                >
    225                     Join Game
     231                    <translatableAttribute id="caption">Join Game</translatableAttribute>
     232                    <translatableAttribute id="tooltip">Joining an existing multiplayer game.</translatableAttribute>
    226233                    <action on="Press">
    227234                        closeMenu();
    228235                        // Open Multiplayer connection window with join option.
    Status: $status.  
    235242                    style="StoneButtonFancy"
    236243                    size="0 32 100% 60"
    237244                    tooltip_style="pgToolTip"
    238                     tooltip="Host a multiplayer game.\n\nRequires UDP port 20595 to be open."
    239245                >
    240                     Host Game
     246                    <translatableAttribute id="caption">Host Game</translatableAttribute>
     247                    <translatableAttribute id="tooltip">Host a multiplayer game.\n\nRequires UDP port 20595 to be open.</translatableAttribute>
    241248                    <action on="Press">
    242249                        closeMenu();
    243250                        // Open Multiplayer connection window with host option.
    Status: $status.  
    251258                type="image"
    252259                size="0 4 100%-4 100%-4"
    253260                tooltip_style="pgToolTip"
    254                 tooltip="The 0 A.D. Game Manual"
    255261                hidden="true"
    256262            >
    257263                <object name="submenuOptionsButton"
    Status: $status.  
    259265                    type="button"
    260266                    size="0 0 100% 28"
    261267                    tooltip_style="pgToolTip"
    262                     tooltip="Adjust game settings. [NOT YET IMPLEMENTED]"
    263268                    enabled="false"
    264269                >
    265                     Options
     270                    <translatableAttribute id="caption">Options</translatableAttribute>
     271                    <translatableAttribute id="tooltip">Adjust game settings. [NOT YET IMPLEMENTED]</translatableAttribute>
    266272                    <action on="Press">
    267273                        closeMenu();
    268274                        <![CDATA[
    Status: $status.  
    272278                    </action>
    273279                </object>
    274280
    275                 <object name="submenuEditorButton"
     281                <object name="submenuLocaleButton"
    276282                    style="StoneButtonFancy"
    277283                    type="button"
    278284                    size="0 32 100% 60"
    279285                    tooltip_style="pgToolTip"
    280                     tooltip="Open the Atlas Scenario Editor in a new window. You can run this more reliably by starting the game with the command-line argument &quot;-editor&quot;."
    281286                >
    282                     Scenario Editor
     287                    <translatableAttribute id="caption">Language</translatableAttribute>
     288                    <translatableAttribute id="tooltip">Choose the language of the game.</translatableAttribute>
    283289                    <action on="Press">
    284                         closeMenu();
    285290                        <![CDATA[
    286                             // Start Atlas
    287                             if (Engine.AtlasIsAvailable())
    288                             Engine.RestartInAtlas();
    289                             else
    290                             messageBox(400, 200, "The scenario editor is not available or failed to load.", "Error", 2);
     291                        closeMenu();
     292                        Engine.PushGuiPage("page_locale.xml");
    291293                        ]]>
    292294                    </action>
    293295                </object>
     296
     297                <object name="submenuEditorButton"
     298                    style="StoneButtonFancy"
     299                    type="button"
     300                    size="0 64 100% 92"
     301                    tooltip_style="pgToolTip"
     302                >
     303                    <translatableAttribute id="caption">Scenario Editor</translatableAttribute>
     304                    <translatableAttribute id="tooltip">Open the Atlas Scenario Editor in a new window. You can run this more reliably by starting the game with the command-line argument &quot;-editor&quot;.</translatableAttribute>
     305                    <action on="Press">
     306                        pressedScenarioEditorButton();
     307                    </action>
     308                </object>
    294309            </object>
    295310        </object><!-- end of submenu -->
    296311
    Status: $status.  
    338353                    style="StoneButtonFancy"
    339354                    size="4 4 100%-4 32"
    340355                    tooltip_style="pgToolTip"
    341                     tooltip="The 0 A.D. Game Manual"
    342356                >
    343                     Learn To Play
     357                    <translatableAttribute id="caption">Learn To Play</translatableAttribute>
     358                    <translatableAttribute id="tooltip">The 0 A.D. Game Manual</translatableAttribute>
    344359                    <action on="Press">
    345360                        closeMenu();
    346361                        <![CDATA[
    Status: $status.  
    355370                    type="button"
    356371                    size="4 36 100%-4 64"
    357372                    tooltip_style="pgToolTip"
    358                     tooltip="Challenge the computer player to a single player match."
    359373                >
    360                     Single Player
     374                    <translatableAttribute id="caption">Single Player</translatableAttribute>
     375                    <translatableAttribute id="tooltip">Challenge the computer player to a single player match.</translatableAttribute>
    361376                    <action on="Press">
    362377                        closeMenu();
    363378                        openMenu("submenuSinglePlayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
    Status: $status.  
    370385                    type="button"
    371386                    size="4 68 100%-4 96"
    372387                    tooltip_style="pgToolTip"
    373                     tooltip="Fight against one or more human players in a multiplayer game."
    374388                >
    375                     Multiplayer
     389                    <translatableAttribute id="caption">Multiplayer</translatableAttribute>
     390                    <translatableAttribute id="tooltip">Fight against one or more human players in a multiplayer game.</translatableAttribute>
    376391                    <action on="Press">
    377392                        closeMenu();
    378393                        openMenu("submenuMultiplayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 2);
    Status: $status.  
    385400                    type="button"
    386401                    size="4 100 100%-4 128"
    387402                    tooltip_style="pgToolTip"
    388                     tooltip="Game options and scenario design tools."
    389403                >
    390                     Tools <![CDATA[&]]> Options
     404                    <translatableAttribute id="caption">Tools &amp; Options</translatableAttribute>
     405                    <translatableAttribute id="tooltip">Game options and scenario design tools.</translatableAttribute>
    391406                    <action on="Press">
    392407                        closeMenu();
    393                         openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 2);
     408                        openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
    394409                    </action>
    395410                </object>
    396411
    Status: $status.  
    400415                    type="button"
    401416                    size="4 132 100%-4 160"
    402417                    tooltip_style="pgToolTip"
    403                     tooltip="Learn about the many civilizations featured in 0 A.D."
    404418                >
    405                     History
     419                    <translatableAttribute id="caption">History</translatableAttribute>
     420                    <translatableAttribute id="tooltip">Learn about the many civilizations featured in 0 A.D.</translatableAttribute>
    406421                    <action on="Press">
    407422                        closeMenu();
    408423                        <![CDATA[
    Status: $status.  
    417432                    style="StoneButtonFancy"
    418433                    size="4 164 100%-4 192"
    419434                    tooltip_style="pgToolTip"
    420                     tooltip="Exit Game"
    421435                >
    422                     Exit
    423                     <action on="Press">
    424                         closeMenu();
    425                         <![CDATA[
    426                             var btCaptions = ["Yes", "No"];
    427                             var btCode = [exit, null];
    428                             messageBox(400, 200, "Are you sure you want to quit 0 A.D.?", "Confirmation", 0, btCaptions, btCode);
    429                         ]]>
    430                     </action>
     436                    <translatableAttribute id="caption">Exit</translatableAttribute>
     437                    <translatableAttribute id="tooltip">Exit Game</translatableAttribute>
     438                    <action on="Press">exitGamePressed();</action>
    431439                </object>
    432440            </object>
    433441
    Status: $status.  
    445453                    size="8 8 100%-8 100%-36"
    446454                                        ghost="true"
    447455                >
    448 [font="serif-bold-16"]Alpha XIV: Naukratis<!-- IMPORTANT: remember to update session/session.xml in sync with this -->[/font]
    449 
    450 WARNING: This is an early development version of the game. Many features have not been added yet.
    451 
    452 Get involved at: play0ad.com
     456                    <!-- IMPORTANT: remember to update session/session.xml in sync with this: -->
     457                    <attribute id="caption">
     458                        <keep>[font="serif-bold-16"]</keep>
     459                        <translate>Alpha XIV: Naukratis</translate>
     460                        <keep>[/font]\n\n</keep>
     461                        <translate>WARNING: This is an early development version of the game. Many features have not been added yet.</translate>
     462                        <keep>\n\n</keep>
     463                        <translate>Get involved at: play0ad.com</translate>
     464                    </attribute>
    453465                </object>
    454466
    455467                <!-- FUNDRAISER -->
    456468                <object type="button"
    457469                    style="StoneButton"
    458470                    tooltip_style="pgToolTip"
    459                     tooltip="Click to view fundraiser information."
    460471                    size="8 100%-108 100%-8 100%-80"
    461472                >
    462                     Fundraiser
     473                    <translatableAttribute id="caption">Fundraiser</translatableAttribute>
     474                    <translatableAttribute id="tooltip">Click to view fundraiser information.</translatableAttribute>
    463475                    <action on="Press"><![CDATA[
    464476                        closeMenu();
    465477                        Engine.PushGuiPage("page_splashscreen.xml", { "page": "splashscreen" });
    Get involved at: play0ad.com  
    470482                <object type="button"
    471483                    style="StoneButton"
    472484                    tooltip_style="pgToolTip"
    473                     tooltip="Click to open play0ad.com in your web browser."
    474485                    size="8 100%-72 50%-4 100%-44"
    475486                >
    476                     Website
     487                    <translatableAttribute id="caption">Website</translatableAttribute>
     488                    <translatableAttribute id="tooltip">Click to open play0ad.com in your web browser.</translatableAttribute>
    477489                    <action on="Press"><![CDATA[
    478490                        var url = "http://play0ad.com/";
    479491                        Engine.OpenURL(url);
    Get involved at: play0ad.com  
    484496                <object type="button"
    485497                    style="StoneButton"
    486498                    tooltip_style="pgToolTip"
    487                     tooltip="Click to open the 0 A.D. IRC chat in your browser. (#0ad on webchat.quakenet.org)"
    488499                    size="50%+4 100%-72 100%-8 100%-44"
    489500                >
    490                     Chat
     501                    <translatableAttribute id="caption">Chat</translatableAttribute>
     502                    <translatableAttribute id="tooltip">Click to open the 0 A.D. IRC chat in your browser. (#0ad on webchat.quakenet.org)</translatableAttribute>
    491503                    <action on="Press"><![CDATA[
    492504                        var url = "http://webchat.quakenet.org/?channels=0ad";
    493505                        Engine.OpenURL(url);
    Get involved at: play0ad.com  
    498510                <object type="button"
    499511                    style="StoneButton"
    500512                    tooltip_style="pgToolTip"
    501                     tooltip="Click to visit 0 A.D. Trac to report a bug, crash, or error"
    502513                    size="8 100%-36 100%-8 100%-8"
    503514                >
    504                     Report a Bug
     515                    <translatableAttribute id="caption">Report a Bug</translatableAttribute>
     516                    <translatableAttribute id="tooltip">Click to visit 0 A.D. Trac to report a bug, crash, or error</translatableAttribute>
    505517                    <action on="Press"><![CDATA[
    506518                        var url = "http://trac.wildfiregames.com/wiki/ReportingErrors/";
    507519                        Engine.OpenURL(url);
    Get involved at: play0ad.com  
    530542                        style="MediumTitleText"
    531543                    ghost="true"
    532544                    size="50%-128 32 50%+128 48"
    533                 >WILDFIRE GAMES</object>
     545                >
     546                    <translatableAttribute id="caption">WILDFIRE GAMES</translatableAttribute>
     547                </object>
    534548            </object>
    535549
    536550            <!-- VERSION -->
    Get involved at: play0ad.com  
    540554                ghost="true"
    541555                size="50%-128 100%-36 50%+128 100%"
    542556            >
    543                 <action on="Load"><![CDATA[
    544                     this.caption = "Build: " + buildTime(0) + " - " + buildTime(2);
    545                 ]]></action>
     557                <action on="Load">
     558                    this.caption = getBuildString();
     559                </action>
    546560            </object>
    547561        </object>
    548562    </object>
  • binaries/data/mods/public/gui/savedgames/load.js

    diff --git a/binaries/data/mods/public/gui/savedgames/load.js b/binaries/data/mods/public/gui/savedgames/load.js
    index 3201421..f058ba8 100644
    a b function init()  
    55    var savedGames = Engine.GetSavedGames();
    66    if (savedGames.length == 0)
    77    {
    8         gameSelection.list = [ "No saved games found" ];
     8        gameSelection.list = [translate("No saved games found")];
    99        gameSelection.selected = 0;
    1010        getGUIObjectByName("loadGameButton").enabled = false;
    1111        getGUIObjectByName("deleteGameButton").enabled = false;
    function loadGame()  
    3232    {
    3333        // Probably the file wasn't found
    3434        // Show error and refresh saved game list
    35         error("Could not load saved game '"+gameID+"'");
     35        error(sprintf(translate("Could not load saved game '%(id)s'"), { id: gameID }));
    3636        init();
    3737    }
    3838    else
    function deleteGame()  
    5353    var gameID = gameSelection.list_data[gameSelection.selected];
    5454
    5555    // Ask for confirmation
    56     var btCaptions = ["Yes", "No"];
     56    var btCaptions = [translate("Yes"), translate("No")];
    5757    var btCode = [function(){ reallyDeleteGame(gameID); }, null];
    58     messageBox(500, 200, "\""+gameLabel+"\"\nSaved game will be permanently deleted, are you sure?", "DELETE", 0, btCaptions, btCode);
     58    messageBox(500, 200, sprintf(translate("\"%(label)s\""), { label: gameLabel }) + "\n" + translate("Saved game will be permanently deleted, are you sure?"), translate("DELETE"), 0, btCaptions, btCode);
    5959}
    6060
    6161function reallyDeleteGame(gameID)
    6262{
    6363    if (!Engine.DeleteSavedGame(gameID))
    64         error("Could not delete saved game '"+gameID+"'");
     64        error(sprintf(translate("Could not delete saved game '%(id)s'"), { id: gameID }));
    6565
    6666    // Run init again to refresh saved game list
    6767    init();
  • binaries/data/mods/public/gui/savedgames/load.xml

    diff --git a/binaries/data/mods/public/gui/savedgames/load.xml b/binaries/data/mods/public/gui/savedgames/load.xml
    index 81f245e..7d64312 100644
    a b  
    1212    <object type="image" style="StoneDialog" size="50%-300 50%-200 50%+300 50%+200">
    1313
    1414        <object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">
    15             Load Game
     15            <translatableAttribute id="caption">Load Game</translatableAttribute>
    1616        </object>
    1717
    1818        <object name="gameSelection"
     
    2222        </object>
    2323
    2424        <object name="loadGameButton" type="button" size="0%+25 100%-60 33%+10 100%-32" style="StoneButton">
    25             Load
     25            <translatableAttribute id="caption">Load</translatableAttribute>
    2626            <action on="Press">loadGame();</action>
    2727        </object>
    2828
    2929        <object name="deleteGameButton" type="button" size="33%+20 100%-60 66%-15 100%-32" style="StoneButton">
    30             Delete
     30            <translatableAttribute id="caption">Delete</translatableAttribute>
    3131            <action on="Press">deleteGame();</action>
    3232        </object>
    3333
    3434        <object type="button" style="StoneButton" size="66%-5 100%-60 100%-25 100%-32">
    35             Cancel
     35            <translatableAttribute id="caption">Cancel</translatableAttribute>
    3636            <action on="Press">Engine.PopGuiPage();</action>
    3737        </object>
    3838
  • binaries/data/mods/public/gui/savedgames/save.js

    diff --git a/binaries/data/mods/public/gui/savedgames/save.js b/binaries/data/mods/public/gui/savedgames/save.js
    index d734655..3fc55fd 100644
    a b function init(data)  
    3030    var savedGames = Engine.GetSavedGames();
    3131    if (savedGames.length == 0)
    3232    {
    33         gameSelection.list = [ "No saved games found" ];
     33        gameSelection.list = [translate("No saved games found")];
    3434        gameSelection.selected = -1;
    3535        return;
    3636    }
    function saveGame()  
    5959    if (gameSelection.selected != -1)
    6060    {
    6161        // Ask for confirmation
    62         var btCaptions = ["Yes", "No"];
     62        var btCaptions = [translate("Yes"), translate("No")];
    6363        var btCode = [function(){ reallySaveGame(name, desc, false); }, null];
    64         messageBox(500, 200, "\""+gameLabel+"\"\nSaved game will be permanently overwritten, are you sure?", "OVERWRITE SAVE", 0, btCaptions, btCode);
     64        messageBox(500, 200, sprintf(translate("\"%(label)s\""), { label: gameLabel }) + "\n" + translate("Saved game will be permanently overwritten, are you sure?"), translate("OVERWRITE SAVE"), 0, btCaptions, btCode);
    6565    }
    6666    else
    6767        reallySaveGame(name, desc, true);
    function deleteGame()  
    9191    var gameID = gameSelection.list_data[gameSelection.selected];
    9292
    9393    // Ask for confirmation
    94     var btCaptions = ["Yes", "No"];
     94    var btCaptions = [translate("Yes"), translate("No")];
    9595    var btCode = [function(){ reallyDeleteGame(gameID); }, null];
    96     messageBox(500, 200, "\""+gameLabel+"\"\nSaved game will be permanently deleted, are you sure?", "DELETE", 0, btCaptions, btCode);
     96    messageBox(500, 200, sprintf(translate("\"%(label)s\""), { label: gameLabel }) + "\n" + translate("Saved game will be permanently deleted, are you sure?"), translate("DELETE"), 0, btCaptions, btCode);
    9797}
    9898
    9999function reallyDeleteGame(gameID)
    100100{
    101101    if (!Engine.DeleteSavedGame(gameID))
    102         error("Could not delete saved game '"+gameID+"'");
     102        error(sprintf(translate("Could not delete saved game '%(id)s'"), { id: gameID }));
    103103
    104104    // Run init again to refresh saved game list
    105105    init();
  • binaries/data/mods/public/gui/savedgames/save.xml

    diff --git a/binaries/data/mods/public/gui/savedgames/save.xml b/binaries/data/mods/public/gui/savedgames/save.xml
    index 70d24e9..004aa91 100644
    a b  
    1212    <object type="image" style="StoneDialog" size="50%-300 50%-200 50%+300 50%+200">
    1313        <object type="image" z="0" sprite="BackgroundTranslucent"/>
    1414        <object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">
    15             Save Game
     15            <translatableAttribute id="caption">Save Game</translatableAttribute>
    1616        </object>
    1717
    1818        <object name="gameSelection"
     
    2525            </action>
    2626        </object>
    2727
    28         <object size="24 100%-124 100%-24 100%-100" name="descLabel" type="text" style="LeftLabelText">Description:</object>
     28        <object size="24 100%-124 100%-24 100%-100" name="descLabel" type="text" style="LeftLabelText">
     29            <translatableAttribute id="caption">Description:</translatableAttribute>
     30        </object>
    2931
    3032        <object name="saveGameDesc" size="24 100%-96 100%-24 100%-72" type="input" style="StoneInput"/>
    3133
    3234        <object name="saveButton" type="button" size="0%+25 100%-60 33%+10 100%-32" style="StoneButton">
    33             Save
     35            <translatableAttribute id="caption">Save</translatableAttribute>
    3436            <action on="Press">saveGame();</action>
    3537        </object>
    3638
    3739        <object name="deleteGameButton" type="button" size="33%+20 100%-60 66%-15 100%-32" style="StoneButton">
    38             Delete
     40            <translatableAttribute id="caption">Delete</translatableAttribute>
    3941            <action on="Press">deleteGame();</action>
    4042        </object>
    4143
    4244        <object type="button" style="StoneButton" size="66%-5 100%-60 100%-25 100%-32">
    43             Cancel
     45            <translatableAttribute id="caption">Cancel</translatableAttribute>
    4446            <action on="Press">closeSave(true);</action>
    4547        </object>
    4648
  • binaries/data/mods/public/gui/session/input.js

    diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
    index 5c51e12..2cac0d1 100644
    a b function updateBuildingPlacementPreview()  
    135135                    elevationBonus: placementSupport.attack.elevationBonus,
    136136                };
    137137                var averageRange = Engine.GuiInterfaceCall("GetAverageRangeForBuildings",cmd);
    138                 placementSupport.tooltipMessage = "Basic range: "+Math.round(cmd.range/4)+"\nAverage bonus range: "+Math.round((averageRange - cmd.range)/4);
     138                placementSupport.tooltipMessage = sprintf(translate("Basic range: %(range)s"), { range: Math.round(cmd.range/4) }) + "\n" + sprintf(translate("Average bonus range: %(range)s"), { range: Math.round((averageRange - cmd.range)/4) });
    139139            }
    140140            return true;
    141141        }
    function getActionInfo(action, target)  
    241241            data.command = "garrison";
    242242            data.target = target;
    243243            cursor = "action-garrison";
    244             tooltip = "Current garrison: " + targetState.garrisonHolder.entities.length
    245                 + "/" + targetState.garrisonHolder.capacity;
     244            tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
     245                garrisoned: targetState.garrisonHolder.entities.length,
     246                capacity: targetState.garrisonHolder.capacity
     247            });
    246248            if (targetState.garrisonHolder.entities.length >= targetState.garrisonHolder.capacity)
    247249                tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
    248250        }
    function getActionInfo(action, target)  
    287289                data.target = traderData.secondMarket;
    288290                data.source = traderData.firstMarket;
    289291                cursor = "action-setup-trade-route";
    290                 tooltip = "Right-click to establish a default route for new traders.";
     292                tooltip = translate("Right-click to establish a default route for new traders.");
    291293                if (trader)
    292                     tooltip += "\nGain (metal): " + getTradingTooltip(gain);
     294                    tooltip += "\n" + sprintf(translate("Gain (metal): %(gain)s"), { gain: getTradingTooltip(gain) });
    293295                else // Foundation or cannot produce traders
    294                     tooltip += "\nExpected gain (metal): " + getTradingTooltip(gain);
     296                    tooltip += "\n" + sprintf(translate("Expected gain (metal): %(gain)s"), { gain: getTradingTooltip(gain) });
    295297            }
    296298        }
    297299
    function getActionInfo(action, target)  
    328330        case "garrison":
    329331            if (hasClass(entState, "Unit") && targetState.garrisonHolder && (playerOwned || mutualAllyOwned))
    330332            {
    331                 var tooltip = "Current garrison: " + targetState.garrisonHolder.entities.length
    332                     + "/" + targetState.garrisonHolder.capacity;
     333                var tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
     334                    garrisoned: targetState.garrisonHolder.entities.length,
     335                    capacity: targetState.garrisonHolder.capacity
     336                });
    333337                if (targetState.garrisonHolder.entities.length >= targetState.garrisonHolder.capacity)
    334338                    tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
    335339                var allowedClasses = targetState.garrisonHolder.allowedClasses;
    function getActionInfo(action, target)  
    353357                switch (tradingDetails.type)
    354358                {
    355359                case "is first":
    356                     tooltip = "Origin trade market.";
     360                    tooltip = translate("Origin trade market.");
    357361                    if (tradingDetails.hasBothMarkets)
    358                         tooltip += "\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
     362                        tooltip += "\n" + sprintf(translate("Gain (%(goods)s): %(gain)s"), {
     363                            goods: tradingDetails.goods,
     364                            gain: getTradingTooltip(tradingDetails.gain)
     365                        });
    359366                    else
    360                         tooltip += "\nRight-click on another market to set it as a destination trade market."
     367                        tooltip += "\n" + translate("Right-click on another market to set it as a destination trade market.")
    361368                    break;
    362369                case "is second":
    363                     tooltip = "Destination trade market.\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
     370                    tooltip = translate("Destination trade market.") + "\n" + sprintf(translate("Gain (%(goods)s): %(gain)s"), {
     371                        goods: tradingDetails.goods,
     372                        gain: getTradingTooltip(tradingDetails.gain)
     373                    });
    364374                    break;
    365375                case "set first":
    366                     tooltip = "Right-click to set as origin trade market";
     376                    tooltip = translate("Right-click to set as origin trade market");
    367377                    break;
    368378                case "set second":
    369                     tooltip = "Right-click to set as destination trade market.\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
     379                    tooltip = translate("Right-click to set as destination trade market.") + "\n" + sprintf(translate("Gain (%(goods)s): %(gain)s"), {
     380                        goods: tradingDetails.goods,
     381                        gain: getTradingTooltip(tradingDetails.gain)
     382                    });
    370383                    break;
    371384                }
    372385                return {"possible": true, "tooltip": tooltip};
    function tryPlaceBuilding(queued)  
    542555{
    543556    if (placementSupport.mode !== "building")
    544557    {
    545         error("[tryPlaceBuilding] Called while in '"+placementSupport.mode+"' placement mode instead of 'building'");
     558        error(sprintf(translate("[%(functionName)s] Called while in '%(mode)s' placement mode instead of 'building'"), {
     559            functionName: "tryPlaceBuilding",
     560            mode: placementSupport.mode
     561        }));
    546562        return false;
    547563    }
    548564
    function tryPlaceWall(queued)  
    583599{
    584600    if (placementSupport.mode !== "wall")
    585601    {
    586         error("[tryPlaceWall] Called while in '" + placementSupport.mode + "' placement mode; expected 'wall' mode");
     602        error(sprintf(translate("[%(functionName)s] Called while in '%(mode)s' placement mode; expected 'wall' mode"), {
     603            functionName: "tryPlaceWall",
     604            mode: placementSupport.mode
     605        }));
    587606        return false;
    588607    }
    589608   
    590609    var wallPlacementInfo = updateBuildingPlacementPreview(); // entities making up the wall (wall segments, towers, ...)
    591610    if (!(wallPlacementInfo === false || typeof(wallPlacementInfo) === "object"))
    592611    {
    593         error("[tryPlaceWall] Unexpected return value from updateBuildingPlacementPreview: '" + uneval(placementInfo) + "'; expected either 'false' or 'object'");
     612        error(sprintf(translate("[%(functionName)s] Unexpected return value from %(function2Name)s: '%(value)s'; expected either 'false' or 'object'"), {
     613            functionName: "tryPlaceWall",
     614            function2Name: "updateBuildingPlacementPreview",
     615            value: uneval(placementInfo)
     616        }));
    594617        return false;
    595618    }
    596619   
    function handleInputBeforeGui(ev, hoveredObject)  
    922945                    }
    923946                    else
    924947                    {
    925                         placementSupport.tooltipMessage = "Cannot build wall here!";
     948                        placementSupport.tooltipMessage = translate("Cannot build wall here!");
    926949                    }
    927950                   
    928951                    updateBuildingPlacementPreview();
  • binaries/data/mods/public/gui/session/menu.js

    diff --git a/binaries/data/mods/public/gui/session/menu.js b/binaries/data/mods/public/gui/session/menu.js
    index 0046f64..1e2879c 100644
    a b function resignMenuButton()  
    133133    closeMenu();
    134134    closeOpenDialogs();
    135135    pauseGame();
    136     var btCaptions = ["Yes", "No"];
     136    var btCaptions = [translate("Yes"), translate("No")];
    137137    var btCode = [resignGame, resumeGame];
    138     messageBox(400, 200, "Are you sure you want to resign?", "Confirmation", 0, btCaptions, btCode);
     138    messageBox(400, 200, translate("Are you sure you want to resign?"), translate("Confirmation"), 0, btCaptions, btCode);
    139139}
    140140
    141141function exitMenuButton()
    function exitMenuButton()  
    143143    closeMenu();
    144144    closeOpenDialogs();
    145145    pauseGame();
    146     var btCaptions = ["Yes", "No"];
     146    var btCaptions = [translate("Yes"), translate("No")];
    147147    var btCode = [leaveGame, resumeGame];
    148     messageBox(400, 200, "Are you sure you want to quit?", "Confirmation", 0, btCaptions, btCode);
     148    messageBox(400, 200, translate("Are you sure you want to quit?"), translate("Confirmation"), 0, btCaptions, btCode);
    149149}
    150150
    151151function openDeleteDialog(selection)
    function openDeleteDialog(selection)  
    158158        Engine.PostNetworkCommand({"type": "delete-entities", "entities": selection});
    159159    };
    160160
    161     var btCaptions = ["Yes", "No"];
     161    var btCaptions = [translate("Yes"), translate("No")];
    162162    var btCode = [deleteSelectedEntities, resumeGame];
    163163
    164     messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode);
     164    messageBox(400, 200, translate("Destroy everything currently selected?"), translate("Delete"), 0, btCaptions, btCode);
    165165}
    166166
    167167// Menu functions
    function openDiplomacy()  
    264264        getGUIObjectByName("diplomacyPlayerName["+(i-1)+"]").caption = "[color=\"" + playerColor + "\"]" + players[i].name + "[/color]";
    265265        getGUIObjectByName("diplomacyPlayerCiv["+(i-1)+"]").caption = g_CivData[players[i].civ].Name;
    266266
    267         getGUIObjectByName("diplomacyPlayerTeam["+(i-1)+"]").caption = (players[i].team < 0) ? "None" : players[i].team+1;
     267        getGUIObjectByName("diplomacyPlayerTeam["+(i-1)+"]").caption = (players[i].team < 0) ? translateWithContext("team", "None") : players[i].team+1;
    268268
    269269        if (i != we)
    270             getGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = (players[i].isAlly[we] ? "Ally" : (players[i].isNeutral[we] ? "Neutral" : "Enemy"));
     270            getGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = (players[i].isAlly[we] ? translate("Ally") : (players[i].isNeutral[we] ? translate("Neutral") : translate("Enemy")));
    271271
    272272        // Don't display the options for ourself, or if we or the other player aren't active anymore
    273273        if (i == we || players[we].state != "active" || players[i].state != "active")
    function openDiplomacy()  
    328328            if (setting == "ally")
    329329            {
    330330                if (players[we].isAlly[i])
    331                     button.caption = "x";
     331                    button.caption = translate("x");
    332332                else
    333333                    button.caption = "";
    334334            }
    335335            else if (setting == "neutral")
    336336            {
    337337                if (players[we].isNeutral[i])
    338                     button.caption = "x";
     338                    button.caption = translate("x");
    339339                else
    340340                    button.caption = "";
    341341            }
    342342            else // "enemy"
    343343            {
    344344                if (players[we].isEnemy[i])
    345                     button.caption = "x";
     345                    button.caption = translate("x");
    346346                else
    347347                    button.caption = "";
    348348            }
    349            
    350349            button.onpress = (function(e){ return function() { setDiplomacy(e) } })({"player": i, "to": setting});
    351350            button.hidden = false;
    352351        }
    function openManual()  
    421420function toggleDeveloperOverlay()
    422421{
    423422    var devCommands = getGUIObjectByName("devCommands");
    424     var text = devCommands.hidden ? "opened." : "closed.";
    425     submitChatDirectly("The Developer Overlay was " + text);
     423    if (devCommands.hidden)
     424        submitChatDirectly(translate("The Developer Overlay was opened."));
     425    else
     426        submitChatDirectly(translate("The Developer Overlay was closed."));
    426427    // Update the options dialog
    427428    getGUIObjectByName("developerOverlayCheckbox").checked = devCommands.hidden;
    428429    devCommands.hidden = !devCommands.hidden;
    function closeOpenDialogs()  
    439440function formatTributeTooltip(player, resource, amount)
    440441{
    441442    var playerColor = player.color.r + " " + player.color.g + " " + player.color.b;
    442     return "Tribute " + amount + " " + resource + " to [color=\"" + playerColor + "\"]" + player.name +
    443         "[/color]. Shift-click to tribute " + (amount < 500 ? 500 : amount + 500) + ".";
     443    return sprintf(translate("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s. Shift-click to tribute %(greaterAmount)s."), {
     444        resourceAmount: amount,
     445        resourceType: resource,
     446        playerName: "[color=\"" + playerColor + "\"]" + player.name + "[/color]",
     447        greaterAmount: (amount < 500 ? 500 : amount + 500)
     448    });
    444449}
  • binaries/data/mods/public/gui/session/messages.js

    diff --git a/binaries/data/mods/public/gui/session/messages.js b/binaries/data/mods/public/gui/session/messages.js
    index f8a5171..1d35981 100644
    a b function getCheatsData()  
    1919    {
    2020        var currentCheat = parseJSONData("simulation/data/cheats/"+fileName+".json");
    2121        if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1)
    22             warn("Cheat name '"+currentCheat.Name+"' is already present");
     22            warn(sprintf(translate("Cheat name '%(name)s' is already present"), { name: currentCheat.Name }));
    2323        else
    2424            cheats[currentCheat.Name] = currentCheat.Data;
    2525    }
    function getUsernameAndColor(player)  
    153153// Messages
    154154function handleNetMessage(message)
    155155{
    156     log("Net message: " + uneval(message));
     156    log(sprintf(translate("Net message: %(message)s"), { message: uneval(message) }));
    157157
    158158    switch (message.type)
    159159    {
    function handleNetMessage(message)  
    166166        switch (message.status)
    167167        {
    168168        case "waiting_for_players":
    169             obj.caption = "Waiting for other players to connect...";
     169            obj.caption = translate("Waiting for other players to connect...");
    170170            obj.hidden = false;
    171171            break;
    172172        case "join_syncing":
    173             obj.caption = "Synchronising gameplay with other players...";
     173            obj.caption = translate("Synchronising gameplay with other players...");
    174174            obj.hidden = false;
    175175            break;
    176176        case "active":
    function handleNetMessage(message)  
    178178            obj.hidden = true;
    179179            break;
    180180        case "connected":
    181             obj.caption = "Connected to the server.";
     181            obj.caption = translate("Connected to the server.");
    182182            obj.hidden = false;
    183183            break;
    184184        case "authenticated":
    185             obj.caption = "Connection to the server has been authenticated.";
     185            obj.caption = translate("Connection to the server has been authenticated.");
    186186            obj.hidden = false;
    187187            break;
    188188        case "disconnected":
    189189            g_Disconnected = true;
    190             obj.caption = "Connection to the server has been lost.\n\nThe game has ended.";
     190            obj.caption = translate("Connection to the server has been lost.") + "\n\n" + translate("The game has ended.");
    191191            obj.hidden = false;
    192192            break;
    193193        default:
    194             error("Unrecognised netstatus type "+message.status);
     194            error(sprintf(translate("Unrecognised netstatus type %(netType)s"), { netType: message.status }));
    195195            break;
    196196        }
    197197        break;
    function handleNetMessage(message)  
    237237        break;
    238238
    239239    default:
    240         error("Unrecognised net message type "+message.type);
     240        error(sprintf(translate("Unrecognised net message type %(messageType)s"), { messageType: message.type }));
    241241    }
    242242}
    243243
    function addChatMessage(msg, playerAssignments)  
    319319
    320320    var playerColor, username;
    321321
    322     // No prefix by default. May be set by parseChatCommands().
    323     msg.prefix = "";
     322    // No context by default. May be set by parseChatCommands().
     323    msg.context = "";
    324324
    325325    if (playerAssignments[msg.guid])
    326326    {
    function addChatMessage(msg, playerAssignments)  
    344344    else
    345345    {
    346346        playerColor = "255 255 255";
    347         username = "Unknown player";
     347        username = translate("Unknown player");
    348348    }
    349349
    350350    var message = escapeText(msg.text);
    function addChatMessage(msg, playerAssignments)  
    354354    switch (msg.type)
    355355    {
    356356    case "connect":
    357         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has joined the game.";
     357        formatted = sprintf(translate("%(player)s has joined the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    358358        break;
    359359    case "disconnect":
    360         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has left the game.";
     360        formatted = sprintf(translate("%(player)s has left the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    361361        break;
    362362    case "defeat":
    363363        // In singleplayer, the local player is "You". "You has" is incorrect.
    364         var verb = (!g_IsNetworked && msg.player == Engine.GetPlayerID()) ? "have" : "has";
    365         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] " + verb + " been defeated.";
     364        if (!g_IsNetworked && msg.player == Engine.GetPlayerID())
     365            formatted = translate("You have been defeated.");
     366        else
     367            formatted = sprintf(translate("%(player)s has been defeated."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    366368        break;
    367369    case "diplomacy":
    368370        var status = (msg.status == "ally" ? "allied" : (msg.status == "enemy" ? "at war" : "neutral"));
    369371        if (msg.player == Engine.GetPlayerID())
    370372        {
    371373            [username, playerColor] = getUsernameAndColor(msg.player1);
    372             formatted = "You are now "+status+" with [color=\"" + playerColor + "\"]"+username + "[/color].";
     374            if (msg.status == "ally")
     375                formatted = sprintf(translate("You are now allied with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     376            else if (msg.status == "enemy")
     377                formatted = sprintf(translate("You are now at war with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     378            else // (msg.status == "neutral")
     379                formatted = sprintf(translate("You are now neutral with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    373380        }
    374381        else if (msg.player1 == Engine.GetPlayerID())
    375382        {
    376             [username, playerColor] = getUsernameAndColor(msg.player);
    377             formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] is now " + status + " with you."
     383            [username, playerColor] = getUsernameAndColor(msg.player1);
     384            if (msg.status == "ally")
     385                formatted = sprintf(translate("%(player)s is now allied with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     386            else if (msg.status == "enemy")
     387                formatted = sprintf(translate("%(player)s is now at war with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     388            else // (msg.status == "neutral")
     389                formatted = sprintf(translate("%(player)s is now neutral with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    378390        }
    379391        else // No need for other players to know of this.
    380392            return;
    function addChatMessage(msg, playerAssignments)  
    393405        if (amounts.length > 1)
    394406        {
    395407            var lastAmount = amounts.pop();
    396             amounts = amounts.join(", ") + " and " + lastAmount;
     408            amounts = sprintf(translate("%(previousAmounts)s and %(lastAmount)s"), {
     409                previousAmounts: amounts.join(translate(", ")),
     410                lastAmount: lastAmount
     411            });
    397412        }
    398413
    399         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has sent you " + amounts + ".";
     414        formatted = sprintf(translate("%(player)s has sent you %(amounts)s."), {
     415            player: "[color=\"" + playerColor + "\"]" + username + "[/color]",
     416            amounts: amounts
     417        });
    400418        break;
    401419    case "attack":
    402420        if (msg.player != Engine.GetPlayerID())
    403421            return;
    404422
    405423        [username, playerColor] = getUsernameAndColor(msg.attacker);
    406         formatted = "You have been attacked by [color=\"" + playerColor + "\"]" + username + "[/color]!";
     424        formatted = sprintf("You have been attacked by %(attacker)s!", { attacker: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    407425        break;
    408426    case "message":
    409427        // May have been hidden by the 'team' command.
    function addChatMessage(msg, playerAssignments)  
    412430
    413431        if (msg.action)
    414432        {
    415             Engine.Console_Write(msg.prefix + "* " + username + " " + message);
    416             formatted = msg.prefix + "* [color=\"" + playerColor + "\"]" + username + "[/color] " + message;
     433            if (msg.context !== "")
     434            {
     435                Engine.Console_Write(sprintf(translate("(%(context)s) * %(user)s %(message)s"), {
     436                    context: msg.context,
     437                    user: username,
     438                    message: message
     439                }));
     440                formatted = sprintf(translate("(%(context)s) * %(user)s %(message)s"), {
     441                    context: msg.context,
     442                    user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
     443                    message: message
     444                });
     445            }
     446            else
     447            {
     448                Engine.Console_Write(sprintf(translate("* %(user)s %(message)s"), {
     449                    user: username,
     450                    message: message
     451                }));
     452                formatted = sprintf(translate("* %(user)s %(message)s"), {
     453                    user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
     454                    message: message
     455                });
     456            }
    417457        }
    418458        else
    419459        {
    420             Engine.Console_Write(msg.prefix + "<" + username + "> " + message);
    421             formatted = msg.prefix + "<[color=\"" + playerColor + "\"]" + username + "[/color]> " + message;
     460            var userTag = sprintf(translate("<%(user)s>"), { user: username })
     461            var formattedUserTag = sprintf(translate("<%(user)s>"), { user: "[color=\"" + playerColor + "\"]" + username + "[/color]" })
     462            if (msg.context !== "")
     463            {
     464                Engine.Console_Write(sprintf(translate("(%(context)s) %(userTag)s %(message)s"), {
     465                    context: msg.context,
     466                    userTag: userTag,
     467                    message: message
     468                }));
     469                formatted = sprintf(translate("(%(context)s) %(userTag)s %(message)s"), {
     470                    context: msg.context,
     471                    userTag: formattedUserTag,
     472                    message: message
     473                });
     474            }
     475            else
     476            {
     477                Engine.Console_Write(sprintf(translate("%(userTag)s %(message)s"), { userTag: userTag, message: message}));
     478                formatted = sprintf(translate("%(userTag)s %(message)s"), { userTag: formattedUserTag, message: message});
     479            }
    422480        }
    423481        break;
    424482    default:
    425         error("Invalid chat message '" + uneval(msg) + "'");
     483        error(sprintf(translate("Invalid chat message '%(message)s'"), { message: uneval(msg) }));
    426484        return;
    427485    }
    428486
    function parseChatCommands(msg, playerAssignments)  
    464522    {
    465523    case "/all":
    466524        // Resets values that 'team' or 'enemy' may have set.
    467         msg.prefix = "";
     525        msg.context = "";
    468526        msg.hide = false;
    469527        recurse = true;
    470528        break;
    function parseChatCommands(msg, playerAssignments)  
    475533            if (g_Players[Engine.GetPlayerID()].team != g_Players[sender].team)
    476534                msg.hide = true;
    477535            else
    478                 msg.prefix = "(Team) ";
     536                msg.context = translate("Team");
    479537        }
    480538        else
    481539            msg.hide = true;
    function parseChatCommands(msg, playerAssignments)  
    488546            if (g_Players[Engine.GetPlayerID()].team == g_Players[sender].team && sender != Engine.GetPlayerID())
    489547                msg.hide = true;
    490548            else
    491                 msg.prefix = "(Enemy) ";
     549                msg.context = translate("Enemy");
    492550        }
    493551        recurse = true;
    494552        break;
    function parseChatCommands(msg, playerAssignments)  
    508566        var playerName = g_Players[Engine.GetPlayerID()].name;
    509567        if (matched.length && (matched == playerName || sender == Engine.GetPlayerID()))
    510568        {
    511             msg.prefix = "(Private) ";
     569            msg.context = translate("Private");
    512570            msg.text = trimmed.substr(matched.length + 1);
    513571            msg.hide = false; // Might override team message hiding.
    514572            return;
  • binaries/data/mods/public/gui/session/selection_details.js

    diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js
    index c454fe0..60f2994 100644
    a b function layoutSelectionMultiple()  
    1010    getGUIObjectByName("detailsAreaSingle").hidden = true;
    1111}
    1212
     13function getLocalizedResourceName(resourceCode)
     14{
     15    switch(resourceCode)
     16    {
     17        case "food": return translate("Food");
     18        case "meat": return translate("Meat");
     19        case "metal": return translate("Metal");
     20        case "ore": return translate("Ore");
     21        case "rock": return translate("Rock");
     22        case "ruins": return translate("Ruins");
     23        case "stone": return translate("Stone");
     24        case "treasure": return translate("Treasure");
     25        case "tree": return translate("Tree");
     26        case "wood": return translate("Wood");
     27        case "fruit": return translate("Fruit");
     28        case "grain": return translate("Grain");
     29        case "fish": return translate("Fish");
     30        default:
     31            warn(sprintf(translate("Internationalization: Unexpected resource type found with code ‘%(resource)s’. This resource type must be internationalized."), { resource: resourceCode }));
     32            return resourceCode; // It should never get here.
     33    }
     34}
     35
     36function getResourceTypeDisplayName(resourceType)
     37{
     38    var resourceCode = resourceType["generic"];
     39    var displayName = "";
     40    if (resourceCode == "treasure")
     41        displayName = getLocalizedResourceName(resourceType["specific"]);
     42    else
     43        displayName = getLocalizedResourceName(resourceCode);
     44    return displayName;
     45}
     46
    1347// Fills out information that most entities have
    1448function displaySingle(entState, template)
    1549{
    function displaySingle(entState, template)  
    1852    var genericName = template.name.generic != template.name.specific? template.name.generic : "";
    1953    // If packed, add that to the generic name (reduces template clutter)
    2054    if (genericName && template.pack && template.pack.state == "packed")
    21         genericName += " -- Packed";
     55        genericName = sprintf(translate("%(genericName)s — Packed"), { genericName: genericName });
    2256    var playerState = g_Players[entState.player];
    2357
    2458    var civName = g_CivData[playerState.civ].Name;
    function displaySingle(entState, template)  
    3064    // Indicate disconnected players by prefixing their name
    3165    if (g_Players[entState.player].offline)
    3266    {
    33         playerName = "[OFFLINE] " + playerName;
     67        playerName = sprintf(translate("[OFFLINE] %(player)s"), { player: playerName });
    3468    }
    3569
    3670    // Rank
    3771    if (entState.identity && entState.identity.rank && entState.identity.classes)
    38     {
    39         getGUIObjectByName("rankIcon").tooltip = entState.identity.rank + " Rank";
    40         getGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);                   
    41         getGUIObjectByName("rankIcon").hidden = false;
    42     }
     72    {
     73        getGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), { rank: entState.identity.rank });
     74        getGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);
     75        getGUIObjectByName("rankIcon").hidden = false;
     76    }
    4377    else
    4478    {
    4579        getGUIObjectByName("rankIcon").hidden = true;
    46         getGUIObjectByName("rankIcon").tooltip = "";
    47     }
    48                                
    49     // Hitpoints
    50     if (entState.hitpoints)
     80        getGUIObjectByName("rankIcon").tooltip = "";
     81    }
     82                               
     83    // Hitpoints
     84    if (entState.hitpoints)
    5185    {
    5286        var unitHealthBar = getGUIObjectByName("healthBar");
    5387        var healthSize = unitHealthBar.size;
    5488        healthSize.rright = 100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
    5589        unitHealthBar.size = healthSize;
    5690
    57         var hitpoints = Math.ceil(entState.hitpoints) + " / " + entState.maxHitpoints;
    58         getGUIObjectByName("healthStats").caption = hitpoints;
     91        getGUIObjectByName("healthStats").caption = sprintf(translate("%(hitpoints)s / %(maxHitpoints)s"), {
     92            hitpoints: Math.ceil(entState.hitpoints),
     93            maxHitpoints: entState.maxHitpoints
     94        });
    5995        getGUIObjectByName("healthSection").hidden = false;
    6096    }
    6197    else
    6298    {
    63         getGUIObjectByName("healthSection").hidden = true;
    64     }
    65    
    66     // TODO: Stamina
    67     var player = Engine.GetPlayerID();
     99        getGUIObjectByName("healthSection").hidden = true;
     100    }
     101   
     102    // TODO: Stamina
     103    var player = Engine.GetPlayerID();
    68104    if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))
    69     {
    70105        getGUIObjectByName("staminaSection").hidden = false;
    71     }
    72106    else
    73     {
    74107        getGUIObjectByName("staminaSection").hidden = true;
    75     }
    76108
    77109    // Experience
    78110    if (entState.promotion)
    79111    {
    80112        var experienceBar = getGUIObjectByName("experienceBar");
    81113        var experienceSize = experienceBar.size;
    82         experienceSize.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / +entState.promotion.req)));
    83         experienceBar.size = experienceSize;
    84  
    85         var experience = "[font=\"serif-bold-13\"]Experience: [/font]" + Math.floor(entState.promotion.curr);
    86         if (entState.promotion.curr < entState.promotion.req)
    87             experience += " / " + entState.promotion.req;
    88         getGUIObjectByName("experience").tooltip = experience;
     114        experienceSize.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / +entState.promotion.req)));
     115        experienceBar.size = experienceSize;
     116 
     117        var experience = "[font=\"serif-bold-13\"]Experience: [/font]" + Math.floor(entState.promotion.curr);
     118        if (entState.promotion.curr < entState.promotion.req)
     119            getGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s / %(required)s"), {
     120                experience: "[font=\"serif-bold-13\"]" + translate("Experience:") + "[/font]",
     121                current: Math.floor(entState.promotion.curr),
     122                required: entState.promotion.req
     123            });
     124        else
     125            getGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s"), {
     126                experience: "[font=\"serif-bold-13\"]" + translate("Experience:") + "[/font]",
     127                current: Math.floor(entState.promotion.curr)
     128            });
    89129        getGUIObjectByName("experience").hidden = false;
    90130    }
    91131    else
    function displaySingle(entState, template)  
    96136    // Resource stats
    97137    if (entState.resourceSupply)
    98138    {
    99         var resources = entState.resourceSupply.isInfinite ? "\u221E" :  // Infinity symbol
    100                         Math.ceil(+entState.resourceSupply.amount) + " / " + entState.resourceSupply.max;
    101         var resourceType = entState.resourceSupply.type["generic"];
    102         if (resourceType == "treasure")
    103             resourceType = entState.resourceSupply.type["specific"];
     139        var resources = entState.resourceSupply.isInfinite ? translate("∞") :  // Infinity symbol
     140                        sprintf(translate("%(amount)s / %(max)s"), { amount: Math.ceil(+entState.resourceSupply.amount), max: entState.resourceSupply.max });
     141        var resourceType = getResourceTypeDisplayName(entState.resourceSupply.type);
    104142
    105143        var unitResourceBar = getGUIObjectByName("resourceBar");
    106144        var resourceSize = unitResourceBar.size;
    function displaySingle(entState, template)  
    108146        resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
    109147                        100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
    110148        unitResourceBar.size = resourceSize;
    111         getGUIObjectByName("resourceLabel").caption = toTitleCase(resourceType) + ":";
     149        getGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), { resource: resourceType });
    112150        getGUIObjectByName("resourceStats").caption = resources;
    113151
    114152        if (entState.hitpoints)
    function displaySingle(entState, template)  
    120158    }
    121159    else
    122160    {
    123         getGUIObjectByName("resourceSection").hidden = true;
    124     }
    125 
    126     // Resource carrying
     161        getGUIObjectByName("resourceSection").hidden = true;
     162    }
     163
     164    // Resource carrying
    127165    if (entState.resourceCarrying && entState.resourceCarrying.length)
    128166    {
    129167        // We should only be carrying one resource type at once, so just display the first
    function displaySingle(entState, template)  
    132170        getGUIObjectByName("resourceCarryingIcon").hidden = false;
    133171        getGUIObjectByName("resourceCarryingText").hidden = false;
    134172        getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
    135         getGUIObjectByName("resourceCarryingText").caption = carried.amount + " / " + carried.max;
     173        getGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { amount: carried.amount, max: carried.max });
    136174        getGUIObjectByName("resourceCarryingIcon").tooltip = "";
    137175    }
    138176    // Use the same indicators for traders
    function displaySingle(entState, template)  
    147185        if (entState.trader.goods.amount.market2Gain)
    148186            totalGain += entState.trader.goods.amount.market2Gain;
    149187        getGUIObjectByName("resourceCarryingText").caption = totalGain;
    150         getGUIObjectByName("resourceCarryingIcon").tooltip = "Gain: " + getTradingTooltip(entState.trader.goods.amount);
    151     }
    152     // And for number of workers
    153     else if (entState.foundation)
    154     {
    155         getGUIObjectByName("resourceCarryingIcon").hidden = false;
    156         getGUIObjectByName("resourceCarryingText").hidden = false;
     188        getGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(amount)s"), { amount: getTradingTooltip(entState.trader.goods.amount) });
     189    }
     190    // And for number of workers
     191    else if (entState.foundation)
     192    {
     193        getGUIObjectByName("resourceCarryingIcon").hidden = false;
     194        getGUIObjectByName("resourceCarryingText").hidden = false;
    157195        getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
    158196        getGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + "    ";
    159         getGUIObjectByName("resourceCarryingIcon").tooltip = "Number of builders";
    160     }
    161     else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
    162     {
    163         getGUIObjectByName("resourceCarryingIcon").hidden = false;
    164         getGUIObjectByName("resourceCarryingText").hidden = false;
     197        getGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders");
     198    }
     199    else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
     200    {
     201        getGUIObjectByName("resourceCarryingIcon").hidden = false;
     202        getGUIObjectByName("resourceCarryingText").hidden = false;
    165203        getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
    166         getGUIObjectByName("resourceCarryingText").caption = entState.resourceSupply.gatherers.length + " / " + entState.resourceSupply.maxGatherers + "    ";
    167         getGUIObjectByName("resourceCarryingIcon").tooltip = "Current/max gatherers";
    168     }
    169     else
     204        getGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { amount: entState.resourceSupply.gatherers.length, max: entState.resourceSupply.maxGatherers }) + "    ";
     205        getGUIObjectByName("resourceCarryingIcon").tooltip = translate("Current/max gatherers");
     206    }
     207    else
    170208    {
    171209        getGUIObjectByName("resourceCarryingIcon").hidden = true;
    172210        getGUIObjectByName("resourceCarryingText").hidden = true;
    function displaySingle(entState, template)  
    174212
    175213    // Set Player details
    176214    getGUIObjectByName("specific").caption = specificName;
    177     getGUIObjectByName("player").caption = playerName;
    178     getGUIObjectByName("playerColorBackground").sprite = "colour: " + playerColor;
    179    
    180     if (genericName)
    181     {
    182         getGUIObjectByName("generic").caption = "(" + genericName + ")";
     215    getGUIObjectByName("player").caption = playerName;
     216    getGUIObjectByName("playerColorBackground").sprite = "colour: " + playerColor;
     217   
     218    if (genericName)
     219    {
     220        getGUIObjectByName("generic").caption = sprintf(translate("(%(genericName)s)"), { genericName: genericName });
    183221    }
    184222    else
    185223    {
    function displaySingle(entState, template)  
    209247        getGUIObjectByName("icon").sprite = "bkFillBlack";
    210248    }
    211249
     250    var armorLabel = "[font=\"serif-bold-13\"]" + translate("Armor:") + "[/font]"
     251    var armorString = sprintf(translate("%(label)s %(details)s"), { label: armorLabel, details: armorTypeDetails(entState.armour) });
     252
    212253    // Attack and Armor
    213     var type = "";
    214     var attack = "[font=\"serif-bold-13\"]"+type+"Attack:[/font] " + damageTypeDetails(entState.attack);
    215254    if (entState.attack)
    216255    {
    217         type = entState.attack.type + " ";
    218 
    219         // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     256        var attack;
     257        var label = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(entState.attack.type) + "[/font]"
    220258        if (entState.attack.type == "Ranged")
    221259        {
    222             var realRange = entState.attack.elevationAdaptedRange;
    223             var range =  entState.attack.maxRange;
    224             attack += ", [font=\"serif-bold-13\"]Range:[/font] " +
    225                 Math.round(range/4);
    226 
    227             if (Math.round((realRange - range)/4) > 0)
    228             {
    229                 attack += " (+" + Math.round((realRange - range)/4) + ")";
    230             }
    231             else if (Math.round((realRange - range)/4) < 0)
    232             {
    233                 attack += " (" + Math.round((realRange - range)/4) + ")";
    234             } // don't show when it's 0
    235 
    236         }
    237     }
    238    
    239     getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
    240 
     260            // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     261            var realRange = entState.attack.elevationAdaptedRange;
     262            var range =  entState.attack.maxRange;
     263            var rangeLabel = "[font=\"serif-bold-13\"]" + translate("Range:") + "[/font]"
     264            var relativeRange = Math.round((realRange - range)/4);
     265
     266            if (relativeRange > 0)
     267                attack = sprintf(translate("%(label)s %(details)s, %(rangeLabel)s %(range)s (%(relative)s)"), {
     268                    label: label,
     269                    details: damageTypeDetails(entState.attack),
     270                    rangeLabel: rangeLabel,
     271                    range: Math.round(range/4),
     272                    relative: "+" + relativeRange
     273                });
     274            else if (relativeRange < 0)
     275                attack = sprintf(translate("%(label)s %(details)s, %(rangeLabel)s %(range)s"), {
     276                    label: label,
     277                    details: damageTypeDetails(entState.attack),
     278                    rangeLabel: rangeLabel,
     279                    range: Math.round(range/4),
     280                    relative: relativeRange
     281                });
     282            else // don't show when it's 0
     283                attack = sprintf(translate("%(label)s %(details)s, %(rangeLabel)s %(range)s"), {
     284                    label: label,
     285                    details: damageTypeDetails(entState.attack),
     286                    rangeLabel: rangeLabel,
     287                    range: Math.round(range/4)
     288                });
     289        }
     290        else
     291        {
     292            attack = sprintf(translate("%(label)s %(details)s"), {
     293                label: label,
     294                details: damageTypeDetails(entState.attack)
     295            });
     296        }
     297        getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n" + armorString;
     298    }
     299    else
     300    {
     301        getGUIObjectByName("attackAndArmorStats").tooltip = armorString;
     302    }
     303
    241304    // Icon Tooltip
    242305    var iconTooltip = "";
    243306
    function displayMultiple(selection, template)  
    274337    }
    275338
    276339    if (averageHealth > 0)
    277     {
    278         var unitHealthBar = getGUIObjectByName("healthBarMultiple");
    279         var healthSize = unitHealthBar.size;   
    280         healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
    281         unitHealthBar.size = healthSize;
     340    {
     341        var unitHealthBar = getGUIObjectByName("healthBarMultiple");
     342        var healthSize = unitHealthBar.size;   
     343        healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
     344        unitHealthBar.size = healthSize;
    282345
    283         var hitpoints = "[font=\"serif-bold-13\"]Hitpoints [/font]" + averageHealth + " / " + maxHealth;
     346        var hitpointsLabel = "[font=\"serif-bold-13\"]" + translate("Hitpoints:") + "[/font]"
     347        var hitpoints = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: hitpointsLabel, current: averageHealth, max: maxHealth });
    284348        var healthMultiple = getGUIObjectByName("healthMultiple");
    285349        healthMultiple.tooltip = hitpoints;
    286350        healthMultiple.hidden = false;
    287351    }
    288352    else
    289353    {
    290         getGUIObjectByName("healthMultiple").hidden = true;
    291     }
    292    
    293     // TODO: Stamina
    294     // getGUIObjectByName("staminaBarMultiple");
     354        getGUIObjectByName("healthMultiple").hidden = true;
     355    }
     356   
     357    // TODO: Stamina
     358    // getGUIObjectByName("staminaBarMultiple");
    295359
    296360    getGUIObjectByName("numberOfUnits").caption = selection.length;
    297361
    function updateSelectionDetails()  
    307371    var detailsPanel = getGUIObjectByName("selectionDetails");
    308372    var commandsPanel = getGUIObjectByName("unitCommands");
    309373
    310     g_Selection.update();
    311     var selection = g_Selection.toList();
    312    
    313     if (selection.length == 0)
    314     {
     374    g_Selection.update();
     375    var selection = g_Selection.toList();
     376   
     377    if (selection.length == 0)
     378    {
    315379        getGUIObjectByName("detailsAreaMultiple").hidden = true;
    316         getGUIObjectByName("detailsAreaSingle").hidden = true;
    317         hideUnitCommands();
    318    
    319         supplementalDetailsPanel.hidden = true;
    320         detailsPanel.hidden = true;
     380        getGUIObjectByName("detailsAreaSingle").hidden = true;
     381        hideUnitCommands();
     382   
     383        supplementalDetailsPanel.hidden = true;
     384        detailsPanel.hidden = true;
    321385        commandsPanel.hidden = true;
    322386        return;
    323387    }
  • binaries/data/mods/public/gui/session/session.js

    diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js
    index 9f8349a..a09d08b 100644
    a b var g_CivData = {};  
    99var g_GameSpeeds = {};
    1010var g_CurrentSpeed;
    1111
    12 var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
     12var g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1 } };
    1313
    1414// Cache dev-mode settings that are frequently or widely used
    1515var g_DevSettings = {
    function GetEntityState(entId)  
    6161
    6262// Cache TemplateData
    6363var g_TemplateData = {}; // {id:template}
     64var g_TemplateDataWithoutLocalization = {};
    6465
    6566
    6667function GetTemplateData(templateName)
    function GetTemplateData(templateName)  
    6869    if (!(templateName in g_TemplateData))
    6970    {
    7071        var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
     72        translateObjectKeys(template, ["specific", "generic", "tooltip"]);
    7173        g_TemplateData[templateName] = template;
    7274    }
    7375
    7476    return g_TemplateData[templateName];
    7577}
    7678
     79function GetTemplateDataWithoutLocalization(templateName)
     80{
     81    if (!(templateName in g_TemplateDataWithoutLocalization))
     82    {
     83        var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
     84        g_TemplateDataWithoutLocalization[templateName] = template;
     85    }
     86
     87    return g_TemplateDataWithoutLocalization[templateName];
     88}
     89
    7790// Cache TechnologyData
    7891var g_TechnologyData = {}; // {id:template}
    7992
    function GetTechnologyData(technologyName)  
    8295    if (!(technologyName in g_TechnologyData))
    8396    {
    8497        var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
     98        translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
    8599        g_TechnologyData[technologyName] = template;
    86100    }
    87101
    function init(initData, hotloadData)  
    112126
    113127    // Cache civ data
    114128    g_CivData = loadCivData();
    115     g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia" };
     129    g_CivData["gaia"] = { "Code": "gaia", "Name": translate("Gaia") };
    116130
    117131    g_GameSpeeds = initGameSpeeds();
    118132    g_CurrentSpeed = Engine.GetSimRate();
    function leaveGame()  
    218232    var gameResult;
    219233    if (g_Disconnected)
    220234    {
    221         gameResult = "You have been disconnected."
     235        gameResult = translate("You have been disconnected.");
    222236    }
    223237    else if (playerState.state == "won")
    224238    {
    225         gameResult = "You have won the battle!";
     239        gameResult = translate("You have won the battle!");
    226240    }
    227241    else if (playerState.state == "defeated")
    228242    {
    229         gameResult = "You have been defeated...";
     243        gameResult = translate("You have been defeated...");
    230244    }
    231245    else // "active"
    232246    {
    233         gameResult = "You have abandoned the game.";
     247        gameResult = translate("You have abandoned the game.");
    234248
    235249        // Tell other players that we have given up and been defeated
    236250        Engine.PostNetworkCommand({
    function checkPlayerState()  
    359373            if (Engine.IsAtlasRunning())
    360374            {
    361375                // If we're in Atlas, we can't leave the game
    362                 var btCaptions = ["OK"];
     376                var btCaptions = [translate("OK")];
    363377                var btCode = [null];
    364                 var message = "Press OK to continue";
     378                var message = translate("Press OK to continue");
    365379            }
    366380            else
    367381            {
    368                 var btCaptions = ["Yes", "No"];
     382                var btCaptions = [translate("Yes"), translate("No")];
    369383                var btCode = [leaveGame, null];
    370                 var message = "Do you want to quit?";
     384                var message = translate("Do you want to quit?");
    371385            }
    372             messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode);
     386            messageBox(400, 200, message, translate("DEFEATED!"), 0, btCaptions, btCode);
    373387        }
    374388        else if (playerState.state == "won")
    375389        {
    function checkPlayerState()  
    385399            if (Engine.IsAtlasRunning())
    386400            {
    387401                // If we're in Atlas, we can't leave the game
    388                 var btCaptions = ["OK"];
     402                var btCaptions = [translate("OK")];
    389403                var btCode = [null];
    390                 var message = "Press OK to continue";
     404                var message = translate("Press OK to continue");
    391405            }
    392406            else
    393407            {
    394                 var btCaptions = ["Yes", "No"];
     408                var btCaptions = [translate("Yes"), translate("No")];
    395409                var btCode = [leaveGame, null];
    396                 var message = "Do you want to quit?";
     410                var message = translate("Do you want to quit?");
    397411            }
    398             messageBox(400, 200, message, "VICTORIOUS!", 0, btCaptions, btCode);
     412            messageBox(400, 200, message, translate("VICTORIOUS!"), 0, btCaptions, btCode);
    399413        }
    400414    }
    401415}
    function updateHero()  
    476490
    477491    // Setup tooltip
    478492    var tooltip = "[font=\"serif-bold-16\"]" + template.name.specific + "[/font]";
    479     tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + heroState.hitpoints + "/" + heroState.maxHitpoints;
    480     tooltip += "\n[font=\"serif-bold-13\"]" + (heroState.attack ? heroState.attack.type + " " : "")
    481                + "Attack:[/font] " + damageTypeDetails(heroState.attack);
    482     // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     493    var healthLabel = "[font=\"serif-bold-13\"]" + translate("Health:") + "[/font]";
     494    tooltip += "\n" + sprintf(translate("%(label)s %(current)s / %(max)s"), { label: healthLabel, current: heroState.hitpoints, max: heroState.maxHitpoints });
     495    var attackLabel = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(heroState.attack.type) + "[/font]";
    483496    if (heroState.attack && heroState.attack.type == "Ranged")
    484         tooltip += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(heroState.attack.maxRange/4);
     497        // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     498        tooltip += "\n" + sprintf(
     499            translate("%(attackLabel)s %(details)s, %(rangeLabel)s %(range)s"),
     500            {
     501                attackLabel: attackLabel,
     502                details: damageTypeDetails(heroState.attack),
     503                rangeLabel: "[font=\"serif-bold-13\"]" + translate("Range:") + "[/font]",
     504                range: Math.round(heroState.attack.maxRange/4)
     505            }
     506        );
     507    else
     508        tooltip += "\n" + sprintf(translate("%(label)s %(details)s"), { label: attackLabel, details: damageTypeDetails(heroState.armour) });
    485509
    486     tooltip += "\n[font=\"serif-bold-13\"]Armor:[/font] " + damageTypeDetails(heroState.armour);
     510    var armorLabel = "[font=\"serif-bold-13\"]" + translate("Armor:") + "[/font]";
     511    tooltip += "\n" + sprintf(translate("%(label)s %(details)s"), { label: armorLabel, details: damageTypeDetails(heroState.attack) });
    487512    tooltip += "\n" + template.tooltip;
    488513
    489514    heroButton.tooltip = tooltip;
    function updateResearchDisplay()  
    629654function updateTimeElapsedCounter()
    630655{
    631656    var simState = GetSimState();
    632     var speed = g_CurrentSpeed != 1.0 ? " (" + g_CurrentSpeed + "x)" : "";
    633657    var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter");
    634     timeElapsedCounter.caption = timeToString(simState.timeElapsed) + speed;
     658    if (g_CurrentSpeed != 1.0)
     659        timeElapsedCounter.caption = sprintf(translate("%(time)s (%(speed)sx)"), { time: timeToString(simState.timeElapsed), speed: Engine.formatDecimalNumberIntoString(g_CurrentSpeed) });
     660    else
     661        timeElapsedCounter.caption = timeToString(simState.timeElapsed);
    635662}
    636663
    637664// Toggles the display of status bars for all of the player's entities.
    function playRandomAmbient(type)  
    671698            break;
    672699
    673700        default:
    674             Engine.Console_Write("Unrecognized ambient type: " + type);
     701            Engine.Console_Write(sprintf(translate("Unrecognized ambient type: %(ambientType)s"), { ambientType: type }));
    675702            break;
    676703    }
    677704}
    function stopAmbient()  
    685712        currentAmbient = null;
    686713    }
    687714}
     715
     716function getBuildString()
     717{
     718    return sprintf(translate("Build: %(buildDate)s (%(revision)s)"), { buildDate: Engine.GetBuildTimestamp(0), revision: Engine.GetBuildTimestamp(2) });
     719}
     720
     721function showTimeWarpMessageBox()
     722{
     723    messageBox(500, 250, translate("Note: time warp mode is a developer option, and not intended for use over long periods of time. Using it incorrectly may cause the game to run out of memory or crash."), translate("Time warp mode"), 2);
     724}
  • binaries/data/mods/public/gui/session/session.xml

    diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
    index d5b3353..6d1c1d2 100644
    a b  
    251251        toggleDeveloperOverlay();
    252252        </action>
    253253
    254         <object size="0 0 100%-18 16" type="text" style="devCommandsText">Control all units</object>
     254        <object size="0 0 100%-18 16" type="text" style="devCommandsText">
     255            <translatableAttribute id="caption">Control all units</translatableAttribute>
     256        </object>
    255257        <object size="100%-16 0 100% 16" type="checkbox" name="devControlAll" style="StoneCrossBox">
    256258        <action on="Press">
    257259            g_DevSettings.controlAll = this.checked;
     
    259261        </action>
    260262        </object>
    261263
    262         <object size="0 16 100%-18 32" type="text" style="devCommandsText">Change perspective</object>
     264        <object size="0 16 100%-18 32" type="text" style="devCommandsText">
     265            <translatableAttribute id="caption">Change perspective</translatableAttribute>
     266        </object>
    263267        <object size="100%-16 16 100% 32" type="checkbox" style="StoneCrossBox">
    264268        <action on="Press">getGUIObjectByName("viewPlayer").hidden = !this.checked;</action>
    265269        </object>
    266270
    267         <object size="0 32 100%-18 48" type="text" style="devCommandsText">Display selection state</object>
     271        <object size="0 32 100%-18 48" type="text" style="devCommandsText">
     272            <translatableAttribute id="caption">Display selection state</translatableAttribute>
     273        </object>
    268274        <object size="100%-16 32 100% 48" type="checkbox" name="devDisplayState" style="StoneCrossBox"/>
    269275
    270         <object size="0 48 100%-18 64" type="text" style="devCommandsText">Pathfinder overlay</object>
     276        <object size="0 48 100%-18 64" type="text" style="devCommandsText">
     277            <translatableAttribute id="caption">Pathfinder overlay</translatableAttribute>
     278        </object>
    271279        <object size="100%-16 48 100% 64" type="checkbox" style="StoneCrossBox">
    272280        <action on="Press">Engine.GuiInterfaceCall("SetPathfinderDebugOverlay", this.checked);</action>
    273281        </object>
    274282
    275         <object size="0 64 100%-18 80" type="text" style="devCommandsText">Obstruction overlay</object>
     283        <object size="0 64 100%-18 80" type="text" style="devCommandsText">
     284            <translatableAttribute id="caption">Obstruction overlay</translatableAttribute>
     285        </object>
    276286        <object size="100%-16 64 100% 80" type="checkbox" style="StoneCrossBox">
    277287        <action on="Press">Engine.GuiInterfaceCall("SetObstructionDebugOverlay", this.checked);</action>
    278288        </object>
    279289
    280         <object size="0 80 100%-18 96" type="text" style="devCommandsText">Unit motion overlay</object>
     290        <object size="0 80 100%-18 96" type="text" style="devCommandsText">
     291            <translatableAttribute id="caption">Unit motion overlay</translatableAttribute>
     292        </object>
    281293        <object size="100%-16 80 100% 96" type="checkbox" style="StoneCrossBox">
    282294        <action on="Press">g_Selection.SetMotionDebugOverlay(this.checked);</action>
    283295        </object>
    284296
    285         <object size="0 96 100%-18 112" type="text" style="devCommandsText">Range overlay</object>
     297        <object size="0 96 100%-18 112" type="text" style="devCommandsText">
     298            <translatableAttribute id="caption">Range overlay</translatableAttribute>
     299        </object>
    286300        <object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox">
    287301        <action on="Press">Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);</action>
    288302        </object>
    289303
    290         <object size="0 112 100%-18 128" type="text" style="devCommandsText">Bounding box overlay</object>
     304        <object size="0 112 100%-18 128" type="text" style="devCommandsText">
     305            <translatableAttribute id="caption">Bounding box overlay</translatableAttribute>
     306        </object>
    291307        <object size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox">
    292308        <action on="Press">Engine.SetBoundingBoxDebugOverlay(this.checked);</action>
    293309        </object>
    294310
    295         <object size="0 128 100%-18 144" type="text" style="devCommandsText">Restrict camera</object>
     311        <object size="0 128 100%-18 144" type="text" style="devCommandsText">
     312            <translatableAttribute id="caption">Restrict camera</translatableAttribute>
     313        </object>
    296314        <object size="100%-16 128 100% 144" type="checkbox" style="StoneCrossBox" checked="true">
    297315        <action on="Press">Engine.GameView_SetConstrainCameraEnabled(this.checked);</action>
    298316        </object>
    299317
    300         <object size="0 144 100%-18 160" type="text" style="devCommandsText">Reveal map</object>
     318        <object size="0 144 100%-18 160" type="text" style="devCommandsText">
     319            <translatableAttribute id="caption">Reveal map</translatableAttribute>
     320        </object>
    301321        <object size="100%-16 144 100% 160" type="checkbox" name="devCommandsRevealMap" style="StoneCrossBox">
    302322        <action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
    303323        </object>
    304324
    305         <object size="0 160 100%-18 176" type="text" style="devCommandsText">Enable time warp</object>
     325        <object size="0 160 100%-18 176" type="text" style="devCommandsText">
     326            <translatableAttribute id="caption">Enable time warp</translatableAttribute>
     327        </object>
    306328        <object size="100%-16 160 100% 176" type="checkbox" name="devTimeWarp" style="StoneCrossBox">
    307329        <action on="Press">
    308330        if (this.checked)
    309           messageBox(500, 250, "Note: time warp mode is a developer option, and not intended\nfor use over long periods of time. Using it incorrectly may\ncause the game to run out of memory or crash.", "Time warp mode", 2);
     331          showTimeWarpMessageBox();
    310332        Engine.EnableTimeWarpRecording(this.checked ? 10 : 0);</action>
    311333        </object>
    312334
    313         <object size="0 176 100%-18 192" type="text" style="devCommandsText">Promote selected units</object>
     335        <object size="0 176 100%-18 192" type="text" style="devCommandsText">
     336            <translatableAttribute id="caption">Promote selected units</translatableAttribute>
     337        </object>
    314338        <object size="100%-16 176 100% 192" type="button" style="StoneCrossBox">
    315339        <action on="Press">Engine.PostNetworkCommand({"type": "promote", "entities": g_Selection.toList()});</action>
    316340        </object>
     
    335359        z="0"
    336360    >
    337361        <object size="0 0 100% 100%" type="image" sprite="devCommandsBackground" ghost="true" z="0"/>
    338         <object size="50%-128 50%-20 50%+128 50%+20" type="text" style="PauseText" ghost="true" z="0">Game Paused</object>
    339         <object size="50%-128 50%+20 50%+128 50%+30" type="text" style="PauseMessageText" ghost="true" z="0">Click to Resume Game</object>
     362        <object size="50%-128 50%-20 50%+128 50%+20" type="text" style="PauseText" ghost="true" z="0">
     363            <translatableAttribute id="caption">Game Paused</translatableAttribute>
     364        </object>
     365        <object size="50%-128 50%+20 50%+128 50%+30" type="text" style="PauseMessageText" ghost="true" z="0">
     366            <translatableAttribute id="caption">Click to Resume Game</translatableAttribute>
     367        </object>
    340368        <action on="Press">togglePause();</action>
    341369    </object>
    342370
     
    363391        </object>
    364392
    365393        <object size="16 100%-40 30%+16 100%-12" type="button" style="StoneButton">
    366         Send
    367         <action on="Press">submitChatInput();</action>
     394            <translatableAttribute id="caption">Send</translatableAttribute>
     395            <action on="Press">submitChatInput();</action>
    368396        </object>
    369397
    370398        <object size="30%+24 100%-40 60%+24 100%-12" type="button" style="StoneButton">
    371         Cancel
    372         <action on="Press">closeChat();</action>
     399            <translatableAttribute id="caption">Cancel</translatableAttribute>
     400            <action on="Press">closeChat();</action>
    373401        </object>
    374402        <object name="toggleTeamChat" size="60%+32 100%-34 60%+48 100%-6" type="checkbox" style="StoneCrossBox"/>
    375403        <object size="60%+48 100%-40 100% 100%-12" type="text"  style="LeftLabelText">
     
    386414        hidden="true"
    387415        sprite="StoneDialog"
    388416    >
    389         <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">Diplomacy</object>
     417        <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">
     418            <translatableAttribute id="caption">Diplomacy</translatableAttribute>
     419        </object>
    390420
    391421        <object name="diplomacyHeader" size="32 32 100%-32 64">
    392             <object name="diplomacyHeaderName" size="0 0 150 100%" type="text" style="chatPanel" ghost="true" caption="Name"/>
    393             <object name="diplomacyHeaderCiv" size="150 0 250 100%" type="text" style="chatPanel" ghost="true" caption="Civilization"/>
    394             <object name="diplomacyHeaderTeam" size="250 0 300 100%" type="text" style="chatPanel" ghost="true" caption="Team"/>
    395             <object name="diplomacyHeaderTheirs" size="300 0 360 100%" type="text" style="chatPanel" ghost="true" caption="Theirs"/>
    396             <object name="diplomacyHeaderAlly" size="100%-180 0 100%-160 100%" type="text" style="chatPanel" caption="A" tooltip="Ally" tooltip_style="sessionToolTipBold"/>
    397             <object name="diplomacyHeaderNeutral" size="100%-160 0 100%-140 100%" type="text" style="chatPanel" caption="N" tooltip="Neutral" tooltip_style="sessionToolTipBold"/>
    398             <object name="diplomacyHeaderEnemy" size="100%-140 0 100%-120 100%" type="text" style="chatPanel" caption="E" tooltip="Enemy" tooltip_style="sessionToolTipBold"/>
    399             <object name="diplomacyHeaderTribute" size="100%-110 0 100% 100%" type="text" style="chatPanel" caption="Tribute"/>
     422            <object name="diplomacyHeaderName" size="0 0 150 100%" type="text" style="chatPanel" ghost="true">
     423                <translatableAttribute id="caption">Name</translatableAttribute>
     424            </object>
     425            <object name="diplomacyHeaderCiv" size="150 0 250 100%" type="text" style="chatPanel" ghost="true">
     426                <translatableAttribute id="caption">Civilization</translatableAttribute>
     427            </object>
     428            <object name="diplomacyHeaderTeam" size="250 0 300 100%" type="text" style="chatPanel" ghost="true">
     429                <translatableAttribute id="caption">Team</translatableAttribute>
     430            </object>
     431            <object name="diplomacyHeaderTheirs" size="300 0 360 100%" type="text" style="chatPanel" ghost="true">
     432                <translatableAttribute id="caption">Theirs</translatableAttribute>
     433            </object>
     434            <object name="diplomacyHeaderAlly" size="100%-180 0 100%-160 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     435                <translatableAttribute id="caption">A</translatableAttribute>
     436                <translatableAttribute id="tooltip">Ally</translatableAttribute>
     437            </object>
     438            <object name="diplomacyHeaderNeutral" size="100%-160 0 100%-140 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     439                <translatableAttribute id="caption">N</translatableAttribute>
     440                <translatableAttribute id="tooltip">Neutral</translatableAttribute>
     441            </object>
     442            <object name="diplomacyHeaderEnemy" size="100%-140 0 100%-120 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     443                <translatableAttribute id="caption">E</translatableAttribute>
     444                <translatableAttribute id="tooltip">Enemy</translatableAttribute>
     445            </object>
     446            <object name="diplomacyHeaderTribute" size="100%-110 0 100% 100%" type="text" style="chatPanel">
     447                <translatableAttribute id="caption">Tribute</translatableAttribute>
     448            </object>
    400449        </object>
    401450
    402451        <object size="32 64 100%-32 384">
     
    445494            hidden="true"
    446495            z="200"
    447496            >
    448             <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">Settings</object>
     497            <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">
     498                <translatableAttribute id="caption">Settings</translatableAttribute>
     499            </object>
    449500           
    450501            <object style="TranslucentPanelThinBorder"
    451502                type="image"
    452503                size="32 32 100%-32 100%-70"
    453504                >
    454505                <!-- Settings / shadows -->
    455                 <object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">Enable Shadows</object>
     506                <object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">
     507                    <translatableAttribute id="caption">Enable Shadows</translatableAttribute>
     508                </object>
    456509                <object name="shadowsCheckbox" size="100%-56 15 100%-30 40" type="checkbox" style="StoneCrossBox" checked="true">
    457510                    <action on="Load">this.checked = Engine.Renderer_GetShadowsEnabled();</action>
    458511                    <action on="Press">Engine.Renderer_SetShadowsEnabled(this.checked);</action>
    459512                </object>
    460513               
    461514                <!-- Settings / Shadow PCF -->
    462                 <object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
     515                <object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">
     516                    <translatableAttribute id="caption">Enable Shadow Filtering</translatableAttribute>
     517                </object>
    463518                <object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
    464519                    <action on="Load">this.checked = Engine.Renderer_GetShadowPCFEnabled();</action>
    465520                    <action on="Press">Engine.Renderer_SetShadowPCFEnabled(this.checked);</action>
    466521                </object>
    467522               
    468523                <!-- Settings / Water Normals -->
    469                 <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Water - HQ Waviness</object>
     524                <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">
     525                    <translatableAttribute id="caption">Water - HQ Waviness</translatableAttribute>
     526                </object>
    470527                <object name="waterNormalCheckox" size="100%-56 65 100%-30 90"  type="checkbox" style="StoneCrossBox" checked="true">
    471528                    <action on="Load">this.checked = Engine.Renderer_GetWaterNormalEnabled();</action>
    472529                    <action on="Press">Engine.Renderer_SetWaterNormalEnabled(this.checked);</action>
    473530                </object>
    474531               
    475532                <!-- Settings / Real Depth -->
    476                 <object size="0 85 100%-80 110" type="text" style="RightLabelText" ghost="true">Water - Use Actual Depth</object>
     533                <object size="0 85 100%-80 110" type="text" style="RightLabelText" ghost="true">
     534                    <translatableAttribute id="caption">Water - Use Actual Depth</translatableAttribute>
     535                </object>
    477536                <object name="waterRealDepthCheckbox" size="100%-56 90 100%-30 115"  type="checkbox" style="StoneCrossBox" checked="true">
    478537                    <action on="Load">this.checked = Engine.Renderer_GetWaterRealDepthEnabled();</action>
    479538                    <action on="Press">Engine.Renderer_SetWaterRealDepthEnabled(this.checked);</action>
    480539                </object>
    481540
    482541                <!-- Settings / Reflection -->
    483                 <object size="0 110 100%-80 135" type="text" style="RightLabelText" ghost="true">Water - Enable Reflections</object>
     542                <object size="0 110 100%-80 135" type="text" style="RightLabelText" ghost="true">
     543                    <translatableAttribute id="caption">Water - Enable Reflections</translatableAttribute>
     544                </object>
    484545                <object name="waterReflectionCheckbox" size="100%-56 115 100%-30 140"  type="checkbox" style="StoneCrossBox" checked="true">
    485546                    <action on="Load">this.checked = Engine.Renderer_GetWaterReflectionEnabled();</action>
    486547                    <action on="Press">Engine.Renderer_SetWaterReflectionEnabled(this.checked);</action>
    487548                </object>
    488549               
    489550                <!-- Settings / Refraction -->
    490                 <object size="0 135 100%-80 160" type="text" style="RightLabelText" ghost="true">Water - Enable Refraction</object>
     551                <object size="0 135 100%-80 160" type="text" style="RightLabelText" ghost="true">
     552                    <translatableAttribute id="caption">Water - Enable Refraction</translatableAttribute>
     553                </object>
    491554                <object name="waterRefractionCheckbox" size="100%-56 140 100%-30 165"  type="checkbox" style="StoneCrossBox" checked="true">
    492555                    <action on="Load">this.checked = Engine.Renderer_GetWaterRefractionEnabled();</action>
    493556                    <action on="Press">Engine.Renderer_SetWaterRefractionEnabled(this.checked);</action>
    494557                </object>
    495558               
    496559                <!-- Settings / Foam -->
    497                 <object size="0 160 100%-80 185" type="text" style="RightLabelText" ghost="true">Water - Enable Shore Foam</object>
     560                <object size="0 160 100%-80 185" type="text" style="RightLabelText" ghost="true">
     561                    <translatableAttribute id="caption">Water - Enable Shore Foam</translatableAttribute>
     562                </object>
    498563                <object name="waterFoamCheckbox" size="100%-56 165 100%-30 190"  type="checkbox" style="StoneCrossBox" checked="true">
    499564                    <action on="Load">this.checked = Engine.Renderer_GetWaterFoamEnabled();</action>
    500565                    <action on="Press">Engine.Renderer_SetWaterFoamEnabled(this.checked);</action>
    501566                </object>
    502567               
    503568                <!-- Settings / Waves -->
    504                 <object size="0 185 100%-80 210" type="text" style="RightLabelText" ghost="true">Water - Enable Shore Waves</object>
     569                <object size="0 185 100%-80 210" type="text" style="RightLabelText" ghost="true">
     570                    <translatableAttribute id="caption">Water - Enable Shore Waves</translatableAttribute>
     571                </object>
    505572                <object name="waterCoastalWavesCheckbox" size="100%-56 190 100%-30 215"  type="checkbox" style="StoneCrossBox" checked="true">
    506573                    <action on="Load">this.checked = Engine.Renderer_GetWaterCoastalWavesEnabled();</action>
    507574                    <action on="Press">Engine.Renderer_SetWaterCoastalWavesEnabled(this.checked);</action>
    508575                </object>
    509576               
    510577                <!-- Settings / Shadows -->
    511                 <object size="0 210 100%-80 235" type="text" style="RightLabelText" ghost="true">Water - Use Surface Shadows</object>
     578                <object size="0 210 100%-80 235" type="text" style="RightLabelText" ghost="true">
     579                    <translatableAttribute id="caption">Water - Use Surface Shadows</translatableAttribute>
     580                </object>
    512581                <object name="waterShadowsCheckbox" size="100%-56 215 100%-30 240"  type="checkbox" style="StoneCrossBox" checked="true">
    513582                    <action on="Load">if (Engine.Renderer_GetWaterShadowEnabled()) this.checked = true; else this.checked = false;</action>
    514583                    <action on="Press">Engine.Renderer_SetWaterShadowEnabled(this.checked);</action>
    515584                </object>
    516585
    517586                <!-- Settings / Particles -->
    518                 <object size="0 235 100%-80 260" type="text" style="RightLabelText" ghost="true">Enable Particles</object>
     587                <object size="0 235 100%-80 260" type="text" style="RightLabelText" ghost="true">
     588                    <translatableAttribute id="caption">Enable Particles</translatableAttribute>
     589                </object>
    519590                <object name="particlesCheckbox" size="100%-56 240 100%-30 265" type="checkbox" style="StoneCrossBox" checked="true">
    520591                    <action on="Load">this.checked = Engine.Renderer_GetParticlesEnabled();</action>
    521592                    <action on="Press">Engine.Renderer_SetParticlesEnabled(this.checked);</action>
    522593                </object>
    523594               
    524595                <!-- Settings / Unit Silhouettes -->
    525                 <object size="0 260 100%-80 285" type="text" style="RightLabelText" ghost="true">Enable Unit Silhouettes</object>
     596                <object size="0 260 100%-80 285" type="text" style="RightLabelText" ghost="true">
     597                    <translatableAttribute id="caption">Enable Unit Silhouettes</translatableAttribute>
     598                </object>
    526599                <object name="silhouettesCheckbox" size="100%-56 265 100%-30 290" type="checkbox" style="StoneCrossBox" checked="true">
    527600                    <action on="Load">this.checked = Engine.Renderer_GetSilhouettesEnabled();</action>
    528601                    <action on="Press">Engine.Renderer_SetSilhouettesEnabled(this.checked);</action>
    529602                </object>
    530603               
    531604                <!-- Settings / Music-->
    532                 <object size="0 285 100%-80 310" type="text" style="RightLabelText" ghost="true">Enable Music</object>
     605                <object size="0 285 100%-80 310" type="text" style="RightLabelText" ghost="true">
     606                    <translatableAttribute id="caption">Enable Music</translatableAttribute>
     607                </object>
    533608                <object name="musicCheckbox" size="100%-56 290 100%-30 315" type="checkbox" style="StoneCrossBox" checked="true">
    534609                    <action on="Press">if (this.checked) global.music.start(); else global.music.stop();</action>
    535610                </object>
    536611               
    537612                <!-- Settings / Dev Overlay -->
    538                 <object size="0 310 100%-80 335" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
     613                <object size="0 310 100%-80 335" type="text" style="RightLabelText" ghost="true">
     614                    <translatableAttribute id="caption">Developer Overlay</translatableAttribute>
     615                </object>
    539616                <object name="developerOverlayCheckbox" size="100%-56 315 100%-30 340" type="checkbox" style="StoneCrossBox" checked="false">
    540617                    <action on="Press">toggleDeveloperOverlay();</action>
    541618                </object>
     
    567644        size="10 0 45% 100%"
    568645        >
    569646        <!-- Food -->
    570         <object size="0 0 90 100%" type="image" style="resourceCounter" tooltip="Food" tooltip_style="sessionToolTipBold">
     647        <object size="0 0 90 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     648            <translatableAttribute id="tooltip">Food</translatableAttribute>
    571649            <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
    572650            <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceFood"/>
    573651        </object>
    574652
    575653        <!-- Wood -->
    576         <object size="90 0 180 100%" type="image" style="resourceCounter" tooltip="Wood" tooltip_style="sessionToolTipBold">
     654        <object size="90 0 180 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     655            <translatableAttribute id="tooltip">Wood</translatableAttribute>
    577656            <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
    578657            <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceWood"/>
    579658        </object>
    580659
    581660        <!-- Stone -->
    582         <object size="180 0 270 100%" type="image" style="resourceCounter" tooltip="Stone" tooltip_style="sessionToolTipBold">
     661        <object size="180 0 270 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     662            <translatableAttribute id="tooltip">Stone</translatableAttribute>
    583663            <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
    584664            <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceStone"/>
    585665        </object>
    586666
    587667        <!-- Metal -->
    588         <object size="270 0 360 100%"  type="image" style="resourceCounter" tooltip="Metal" tooltip_style="sessionToolTipBold">
     668        <object size="270 0 360 100%"  type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     669            <translatableAttribute id="tooltip">Metal</translatableAttribute>
    589670            <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
    590671            <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceMetal"/>
    591672        </object>
    592673
    593674        <!-- Population -->
    594         <object size="360 0 450 100%" type="image" style="resourceCounter" tooltip="Population (current / limit)" tooltip_style="sessionToolTipBold">
     675        <object size="360 0 450 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     676            <translatableAttribute id="tooltip">Population (current / limit)</translatableAttribute>
    595677            <object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
    596678            <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
    597679        </object>
     
    603685        <object size="50%-48 -26 50%+48 70" name="civIcon" type="image" tooltip_style="sessionToolTipBold"/>
    604686
    605687        <!-- Switch the view perspective to another player's (largely for AI development) -->
    606         <object size="50%+50 5 50%+150 100%-5" name="viewPlayer" type="dropdown" hidden="true" style="StoneDropDown" tooltip_style="sessionToolTipBold" tooltip="Choose player to view">
    607         <action on="SelectionChange">selectViewPlayer(this.selected);</action>
     688        <object size="50%+50 5 50%+150 100%-5" name="viewPlayer" type="dropdown" hidden="true" style="StoneDropDown" tooltip_style="sessionToolTipBold">
     689            <translatableAttribute id="tooltip">Choose player to view</translatableAttribute>
     690            <action on="SelectionChange">selectViewPlayer(this.selected);</action>
    608691        </object>
    609692
    610693        <!-- ================================  ================================ -->
     
    619702
    620703        <!-- Displays Alpha name and number -->
    621704        <object size="50%+48 0 100%-226 100%" name="alphaLabel" type="text" style="CenteredLabelText" text_valign="top" ghost="true">
    622         ALPHA XIV : Naukratis<!-- IMPORTANT: remember to update pregame/mainmenu.xml in sync with this -->
     705            <!-- IMPORTANT: remember to update pregame/mainmenu.xml in sync with this: -->
     706            <translatableAttribute id="caption">ALPHA XIV : Naukratis</translatableAttribute>
    623707
    624708        <!-- Displays build date and revision number-->
    625709        <object size="50%-128 0 50%+128 100%-2" name="buildTimeLabel" type="text" style="BuildNameText" ghost="true">
    626             <action on="Load">this.caption = buildTime(0) + " (" + buildTime(2) + ")"</action>
     710            <action on="Load">this.caption = getBuildString()</action>
    627711        </object>
    628712        </object>
    629713
     
    635719        size="100%-226 4 100%-198 32"
    636720        style="iconButton"
    637721        tooltip_style="sessionToolTip"
    638         tooltip="Game speed"
    639         >
     722        >
     723            <translatableAttribute id="tooltip">Game speed</translatableAttribute>
    640724            <object size="5 5 100%-5 100%-5" type="image" sprite="stretched:session/icons/resources/time_small.png" ghost="true"/>
    641725            <action on="Press">
    642726                toggleGameSpeed();
    643727            </action>
    644728        </object>
    645729
    646         <object size="100%-348 40 100%-198 65" name="gameSpeed" type="dropdown" buffer_zone="5" style="StoneDropDown" hidden="true" tooltip="Choose game speed" tooltip_style="sessionToolTip"/>
     730        <object size="100%-348 40 100%-198 65" name="gameSpeed" type="dropdown" buffer_zone="5" style="StoneDropDown" hidden="true" tooltip_style="sessionToolTip">
     731            <translatableAttribute id="tooltip">Choose game speed</translatableAttribute>
     732        </object>
     733
    647734       
    648735        <!-- ================================  ================================ -->
    649736        <!-- Diplomacy Button -->
     
    653740        size="100%-194 4 100%-166 32"
    654741        style="iconButton"
    655742        tooltip_style="sessionToolTip"
    656         tooltip="Diplomacy"
    657         >
     743        >
     744            <translatableAttribute id="tooltip">Diplomacy</translatableAttribute>
    658745        <!-- TODO make the button less ugly -->
    659746        <object size="0 0 100% 100%" name="diplomacyButtonImage" type="image" sprite="stretched:session/icons/diplomacy.png" ghost="true"/>
    660747        <action on="Press">
     
    677764        them on top of the main menu button -->
    678765        <object size="0 -4 100% 0" type="image" sprite="horizontalThinBorder" ghost="true"/>
    679766
    680         <object size="50%-32 50%-16 50%+32 50%+16" type="image" sprite="menuButton" ghost="true">MENU</object>
     767        <object size="50%-32 50%-16 50%+32 50%+16" type="image" sprite="menuButton" ghost="true">
     768            <translatableAttribute id="caption">MENU</translatableAttribute>
     769        </object>
    681770        <action on="Press">
    682771            toggleMenu();
    683772        </action>
     
    702791            size="0 0 100% 28"
    703792            tooltip_style="sessionToolTip"
    704793        >
    705             Settings
     794            <translatableAttribute id="caption">Settings</translatableAttribute>
    706795            <action on="Press">settingsMenuButton();</action>
    707796        </object>
    708797
     
    714803            size="0 32 100% 60"
    715804            tooltip_style="sessionToolTip"
    716805        >
    717             Save
     806            <translatableAttribute id="caption">Save</translatableAttribute>
    718807            <action on="Press">
    719808            openSave();
    720809            </action>
     
    727816            size="0 64 100% 92"
    728817            tooltip_style="sessionToolTip"
    729818        >
    730             Chat
     819            <translatableAttribute id="caption">Chat</translatableAttribute>
    731820            <action on="Press">chatMenuButton();</action>
    732821        </object>
    733822
     
    738827            size="0 96 100% 124"
    739828            tooltip_style="sessionToolTip"
    740829        >
    741             Resign
     830            <translatableAttribute id="caption">Resign</translatableAttribute>
    742831            <action on="Press">resignMenuButton();</action>
    743832        </object>
    744833
     
    749838            size="0 128 100% 156"
    750839            tooltip_style="sessionToolTip"
    751840        >
    752             Exit
     841            <translatableAttribute id="caption">Exit</translatableAttribute>
    753842            <action on="Press">exitMenuButton();</action>
    754843        </object>
    755844
     
    760849            size="0 160 100% 188"
    761850            tooltip_style="sessionToolTip"
    762851        >
    763             <object name="pauseButtonText" type="text" style="CenteredButtonText" ghost="true">Pause</object>
     852            <object name="pauseButtonText" type="text" style="CenteredButtonText" ghost="true">
     853                <translatableAttribute id="caption">Pause</translatableAttribute>
     854            </object>
    764855            <action on="Press">togglePause();</action>
    765856        </object>
    766857
     
    771862            size="0 192 100% 220"
    772863            tooltip_style="sessionToolTip"
    773864        >
    774             Manual
     865            <translatableAttribute id="caption">Manual</translatableAttribute>
    775866            <action on="Press">openManual();</action>
    776867        </object>
    777868        </object>
     
    795886        size="0 36 50 86"
    796887    >
    797888        <object name="unitHeroButton" size="0 0 50 50" type="button" hidden="false" style="iconButton"
    798             tooltip_style="sessionToolTip" tooltip="Attack and Armor">
     889            tooltip_style="sessionToolTip">
     890            <translatableAttribute id="tooltip">Attack and Armor</translatableAttribute>
    799891            <object name="unitHeroImage" size="5 5 100%-5 100%-5" type="image" ghost="true"/>
    800892        </object>
    801893    </object>
     
    808900        size="0% 50%-216 0%+36 50%+144"
    809901    >
    810902        <repeat count="10">
    811         <object name="unitGroupButton[n]" size="0 0 36 36" type="button" hidden="false" style="iconButton" tooltip_style="sessionToolTipBottomBold"
    812             tooltip="Click to select grouped units.">
     903        <object name="unitGroupButton[n]" size="0 0 36 36" type="button" hidden="false" style="iconButton" tooltip_style="sessionToolTipBottomBold">
     904            <translatableAttribute id="tooltip">Click to select grouped units.</translatableAttribute>
    813905            <object name="unitGroupIcon[n]" size="3 3 33 33" type="image" sprite="groupsIcon" ghost="true"/>
    814906            <object name="unitGroupLabel[n]" type="text" style="largeCenteredOutlinedText" ghost="true"/>
    815907        </object>
     
    859951            <object type="button"
    860952                style="iconButton"
    861953                tooltip_style="sessionToolTip"
    862                 tooltip="Find idle worker"
    863954                hotkey="selection.idleworker"
    864955            >
     956                <translatableAttribute id="tooltip">Find idle worker</translatableAttribute>
    865957            <!-- TODO: should highlight the button if there's non-zero idle workers -->
    866958            <object size="0 0 100% 100%" type="image" sprite="idleWorker" ghost="true" />
    867959            <action on="Press">findIdleUnit(["Female", "Trade", "FishingBoat", "CitizenSoldier", "Healer"]);</action>
     
    904996            size="6 36 100% 100%"
    905997            hidden="true"
    906998        >
    907             <object ghost="true" style="resourceText" type="text" size="0 0 100% 20">Exchange resources:</object>
     999            <object ghost="true" style="resourceText" type="text" size="0 0 100% 20">
     1000                <translatableAttribute id="tooltip">Exchange resources:</translatableAttribute>
     1001            </object>
    9081002            <object size="0 32 100% 78">
    9091003            <repeat count="4">
    9101004                <object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
     
    9611055            <object size="0 8 100% 60" type="image" sprite="edgedPanelShader">
    9621056                <!-- Health bar -->
    9631057                <object size="88 0 100% 24" name="healthSection">
    964                 <object size="0 0 100% 16" name="healthLabel" type="text" style="StatsTextLeft" ghost="true">Health:</object>
     1058                <object size="0 0 100% 16" name="healthLabel" type="text" style="StatsTextLeft" ghost="true">
     1059                    <translatableAttribute id="tooltip">Health:</translatableAttribute>
     1060                </object>
    9651061                <object size="0 0 100% 16" name="healthStats" type="text" style="StatsTextRight" ghost="true"/>
    9661062                <object size="1 16 100% 23" name="health" type="image">
    9671063                    <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     
    9731069
    9741070                <!-- Stamina bar -->
    9751071                <object size="88 28 100% 52" name="staminaSection">
    976                 <object size="0 0 100% 16" name="staminaLabel" type="text" style="StatsTextLeft" ghost="true">Stamina:</object>
     1072                <object size="0 0 100% 16" name="staminaLabel" type="text" style="StatsTextLeft" ghost="true">
     1073                    <translatableAttribute id="tooltip">Stamina:</translatableAttribute>
     1074                </object>
    9771075                <object size="0 0 100% 16" name="staminaStats" type="text" style="StatsTextRight" ghost="true"/>
    9781076                <object size="1 16 100% 23" name="stamina" type="image">
    9791077                    <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     
    9981096           
    9991097            <object size="0 60 100% 96" type="image" sprite="edgedPanelShader">
    10001098                <!-- Attack and Armor -->
    1001                 <object size="90 -2 126 34" name="attackAndArmorStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip="Attack and Armor" tooltip_style="sessionToolTip"/>
     1099                <object size="90 -2 126 34" name="attackAndArmorStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip_style="sessionToolTip">
     1100                    <translatableAttribute id="tooltip">Attack and Armor</translatableAttribute>
     1101                </object>
    10021102
    10031103                <!-- Resource carrying icon/counter -->
    10041104                <!-- Used also for number of gatherers/builders -->
     
    10111111                <object size="1 1 100%-1 100%-1" type="image" name="icon" ghost="true"/>
    10121112           
    10131113                <!-- Experience bar -->
    1014                 <object size="2 2 6 100%-2" type="image" name="experience" tooltip="Experience" tooltip_style="sessionToolTip">
    1015                 <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
    1016                 <object type="image" sprite="experienceBackground" ghost="true"/>
    1017                 <object type="image" sprite="experienceForeground" ghost="true" name="experienceBar"/>
    1018                 <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
     1114                <object size="2 2 6 100%-2" type="image" name="experience" tooltip_style="sessionToolTip">
     1115                    <translatableAttribute id="tooltip">Experience</translatableAttribute>
     1116                    <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     1117                    <object type="image" sprite="experienceBackground" ghost="true"/>
     1118                    <object type="image" sprite="experienceForeground" ghost="true" name="experienceBar"/>
     1119                    <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
    10191120                </object>
    10201121           
    1021                 <object z="20" size="4 4 20 20" name="rankIcon" type="image" tooltip="Rank" tooltip_style="sessionToolTip"/>
     1122                <object z="20" size="4 4 20 20" name="rankIcon" type="image" tooltip_style="sessionToolTip">
     1123                    <translatableAttribute id="tooltip">Rank</translatableAttribute>
     1124                </object>
    10221125            </object>
    10231126            </object>
    10241127           
     
    10761179            <!-- Stats Bars -->
    10771180            <object size= "100%-38 50 100%-18 100%-44" type="image" tooltip_style="sessionToolTip">
    10781181            <!-- Health bar -->
    1079             <object size="4 0 11 100%" type="image" name="healthMultiple" tooltip="Hitpoints" tooltip_style="sessionToolTip">
     1182            <object size="4 0 11 100%" type="image" name="healthMultiple" tooltip_style="sessionToolTip">
     1183                <translatableAttribute id="tooltip">Hitpoints</translatableAttribute>
    10801184                <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
    10811185                <object type="image" sprite="healthBackground" ghost="true"/>
    10821186                <object type="image" sprite="healthForeground" ghost="true" name="healthBarMultiple"/>
     
    10841188            </object>
    10851189
    10861190            <!-- Stamina bar -->
    1087             <object size="15 0 22 100%" type="image" name="staminaMultiple" tooltip="Stamina" tooltip_style="sessionToolTipBold">
     1191            <object size="15 0 22 100%" type="image" name="staminaMultiple" tooltip_style="sessionToolTipBold">
     1192                <translatableAttribute id="tooltip">Stamina</translatableAttribute>
    10881193                <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
    10891194                <object type="image" sprite="staminaBackground" ghost="true"/>
    10901195                <object type="image" sprite="staminaForeground" ghost="true" name="staminaBarMultiple"/>
     
    12501355        size="50%-84 50%+128 50%+84 50%+160"
    12511356        tooltip_style="sessionToolTip"
    12521357    >
    1253         <object size="0 0 100% 100%" type="text" style="CenteredButtonText" name="disconnectedExitButtonText" ghost="true">Return to Main Menu</object>
     1358        <object size="0 0 100% 100%" type="text" style="CenteredButtonText" name="disconnectedExitButtonText" ghost="true">
     1359            <translatableAttribute id="caption">Return to Main Menu</translatableAttribute>
     1360        </object>
    12541361        <action on="Press">leaveGame()</action>
    12551362    </object>
    12561363
  • binaries/data/mods/public/gui/session/unit_commands.js

    diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js
    index c9d1e64..2977139 100644
    a b const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];  
    2727const BARTER_ACTIONS = ["Sell", "Buy"];
    2828
    2929// Gate constants
    30 const GATE_ACTIONS = ["Lock", "Unlock"];
     30const GATE_ACTIONS = ["lock", "unlock"];
    3131
    3232// The number of currently visible buttons (used to optimise showing/hiding)
    3333var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0, "Gate": 0, "Pack": 0};
    function formatLimitString(trainEntLimit, trainEntCount)  
    146146{
    147147    if (trainEntLimit == undefined)
    148148        return "";
    149     var text = "\n\nCurrent Count: " + trainEntCount + ", Limit: " + trainEntLimit + ".";
     149    var text = "\n\n" + sprintf(translate("Current Count: %(count)s, Limit: %(limit)s."), { count: trainEntCount, limit: trainEntLimit });
    150150    if (trainEntCount >= trainEntLimit)
    151151        text = "[color=\"red\"]" + text + "[/color]";
    152152    return text;
    function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize  
    174174    if (buildingsCountToTrainFullBatch > 0)
    175175    {
    176176        if (buildingsCountToTrainFullBatch > 1)
    177             fullBatchesString += buildingsCountToTrainFullBatch + "*";
    178         fullBatchesString += fullBatchSize;
     177            fullBatchesString = sprintf(translate("%(buildings)s*%(batchSize)s"), {
     178                buildings: buildingsCountToTrainFullBatch,
     179                batchSize: fullBatchSize
     180            });
     181        else
     182            fullBatchesString = fullBatchSize;
    179183    }
    180184    var remainderBatchString = remainderBatch > 0 ? remainderBatch : "";
    181185    var batchDetailsString = "";
     186    var action = "[font=\"serif-bold-13\"]" + translate("Shift-click") + "[/font][font=\"serif-13\"]"
     187
    182188    // We need to display the batch details part if there is either more than
    183189    // one building with full batch or one building with the full batch and
    184190    // another with a partial batch
    185191    if (buildingsCountToTrainFullBatch > 1 ||
    186192        (buildingsCountToTrainFullBatch == 1 && remainderBatch > 0))
    187193    {
    188         batchDetailsString += " (" + fullBatchesString;
    189         if (remainderBatchString != "")
    190             batchDetailsString += " + " + remainderBatchString;
    191         batchDetailsString += ")";
     194        if (remainderBatch > 0)
     195            return "\n[font=\"serif-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s)."), {
     196                action: action,
     197                number: totalBatchTrainingCount,
     198                fullBatch: fullBatchesString,
     199                remainderBatch: remainderBatch
     200            }) + "[/font]";
     201
     202        return "\n[font=\"serif-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s)."), {
     203            action: action,
     204            number: totalBatchTrainingCount,
     205            fullBatch: fullBatchesString
     206        }) + "[/font]";
    192207    }
    193208
    194     return "\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train "
    195         + totalBatchTrainingCount + batchDetailsString + ".[/font]";
     209    return "\n[font=\"serif-13\"]" + sprintf(translate("%(action)s to train %(number)s."), {
     210        action: action,
     211        number: totalBatchTrainingCount
     212    }) + "[/font]";
     213}
     214
     215function getStanceDisplayName(name)
     216{
     217    var displayName;
     218    switch(name)
     219    {
     220        case "violent":
     221            displayName = translate("Violent");
     222            break;
     223        case "aggressive":
     224            displayName = translate("Aggressive");
     225            break;
     226        case "passive":
     227            displayName = translate("Passive");
     228            break;
     229        case "defensive":
     230            displayName = translate("Defensive");
     231            break;
     232        case "standground":
     233            displayName = translate("Standground");
     234            break;
     235        default:
     236            warn(sprintf(translate("Internationalization: Unexpected stance found with code ‘%(stance)s’. This stance must be internationalized."), { stance: name }));
     237            displayName = name;
     238            break;
     239    }
     240    return displayName;
    196241}
    197242
    198243/**
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    374419                break;
    375420        }
    376421
     422
    377423        switch (guiName)
    378424        {
    379425            case SELECTION:
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    386432            case QUEUE:
    387433                var tooltip = getEntityNames(template);
    388434                if (item.neededSlots)
    389                     tooltip += "\n[color=\"red\"]Insufficient population capacity:\n[/color]"+getCostComponentDisplayName("population")+" "+item.neededSlots;
     435                    tooltip += "\n[color=\"red\"]" + translate("Insufficient population capacity:") + "\n[/color]" + sprintf(translate("%(population)s %(neededSlots)s"), { population: getCostComponentDisplayName("population"), neededSlots: item.neededSlots });
    390436
    391437                var progress = Math.round(item.progress*100) + "%";
    392438                getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (item.count > 1 ? item.count : "");
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    404450
    405451            case GARRISON:
    406452                var name = getEntityNames(template);
    407                 var tooltip = "Unload " + name + "\nSingle-click to unload 1. Shift-click to unload all of this type.";
     453                var tooltip = sprintf(translate("Unload %(name)s"), { name: name })+ "\n" + translate("Single-click to unload 1. Shift-click to unload all of this type.");
    408454                var count = garrisonGroups.getCount(item);
    409455                getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : "");
    410456                break;
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    440486                break;
    441487
    442488            case STANCE:
     489                var tooltip = getStanceDisplayName(item);
     490                break;
     491
    443492            case FORMATION:
    444                 var tooltip = toTitleCase(item);
     493                var tooltip = translate(item);
    445494                break;
    446495
    447496            case TRAINING:
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    517566                break;
    518567        }
    519568
     569
    520570        // Button
    521571        var button = getGUIObjectByName("unit"+guiName+"Button["+i+"]");
    522572        var button1 = getGUIObjectByName("unit"+guiName+"Button["+(i+rowLength)+"]");
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    588638                    "formationName": item
    589639                });
    590640
    591                 button.tooltip += " (disabled)";
     641                button.tooltip = sprintf(translate("%(formation)s (disabled)"), { formation: button.tooltip });
    592642                if (requirements.count > 1)
    593                     button.tooltip += "\n" + requirements.count + " units required";
     643                    button.tooltip += "\n" + sprintf(translate("%(number)s units required"), { number: requirements.count });
    594644                if (requirements.classesRequired)
    595645                {
    596                     button.tooltip += "\nOnly units of type";
    597                     for each (var classRequired in requirements.classesRequired)
    598                     {
    599                         button.tooltip += " " + classRequired;
    600                     }
    601                     button.tooltip += " allowed.";
     646                    button.tooltip += "\n" + sprintf(translate("Only units of type %(unitType)s allowed."), { unitType: Engine.translateArray(requirements.classesRequired).join(translateWithContext("classSeparator", " ")) });
    602647                }
    603648            }
    604649
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    631676            // If already a gate, show locking actions
    632677            if (item.gate)
    633678            {
    634                 gateIcon = "icons/lock_" + GATE_ACTIONS[item.locked ? 0 : 1].toLowerCase() + "ed.png";
     679                gateIcon = "icons/lock_" + GATE_ACTIONS[item.locked ? 0 : 1] + "ed.png";
    635680                guiSelection.hidden = item.gate.locked === undefined ? false : item.gate.locked != item.locked;
    636681            }
    637682            // otherwise show gate upgrade icon
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    670715            {
    671716                button.enabled = false;
    672717                var techName = getEntityNames(GetTechnologyData(template.requiredTechnology));
    673                 button.tooltip += "\nRequires " + techName;
     718                button.tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { technology: techName });
    674719                grayscale = "grayscale:";
    675720                affordableMask.hidden = false;
    676721                affordableMask.sprite = "colour: 0 0 0 127";
    function setupUnitTradingPanel(usedPanels, unitEntState, selection)  
    911956        var selectTradingPreferredGoodsData = { "entities": selection, "preferredGoods": resource };
    912957        button.onpress = (function(e){ return function() { selectTradingPreferredGoods(e); } })(selectTradingPreferredGoodsData);
    913958        button.enabled = true;
    914         button.tooltip = "Set " + resource + " as trading goods";
     959        button.tooltip = sprintf(translate("Set %(resource)s as trading goods"), { resource: resource });
    915960        var icon = getGUIObjectByName("unitTradingIcon["+i+"]");
    916961        var preferredGoods = unitEntState.trader.preferredGoods;
    917962        var selected = getGUIObjectByName("unitTradingSelection["+i+"]");
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    10981143                    var gateTemplate = getWallGateTemplate(state.id);
    10991144                    if (gateTemplate)
    11001145                    {
    1101                         var wallName = GetTemplateData(state.template).name.generic;
    1102                         var gateName = GetTemplateData(gateTemplate).name.generic;
     1146                        var wallName = GetTemplateDataWithoutLocalization(state.template).name.generic;
     1147                        var gateName = GetTemplateDataWithoutLocalization(gateTemplate).name.generic;
     1148                        var tooltipString;
     1149
     1150                        // For internationalization purposes, when possible, available combinations should be provided
     1151                        // as placeholder-free strings as below.
     1152                        //
     1153                        // The placeholder implementation is provided only so that undetected new combinations of wall
     1154                        // and gate names are not simply printed in English, but as close to a perfect translation as
     1155                        // possible.
     1156
     1157                        if (wallName === "Wooden Wall" && gateName === "Wooden Gate")
     1158                        {
     1159                            tooltipString = translate("Convert Wooden Wall into Wooden Gate");
     1160                        }
     1161                        else if (wallName === "Stone Wall" && gateName === "City Gate")
     1162                        {
     1163                            tooltipString = translate("Convert Store Wall into City Gate");
     1164                        }
     1165                        else if (wallName === "Siege Wall" && gateName === "Siege Wall Gate")
     1166                        {
     1167                            tooltipString = translate("Convert Siege Wall into Siege Wall Gate");
     1168                        }
     1169                        else
     1170                        {
     1171                            warn(sprintf(translate("Internationalization: Unexpected combination of ‘%(localizedWall)s’ (%(englishWall)s) and ‘%(localizedGate)s’ (%(englishGate)s). This combination of wall and gate types must be internationalized."), { localizedWall: translate(wallName), englishWall: wallName, localizedGate: translate(gateName), englishGate: gateName }));
     1172                            tooltipString = sprintf(translate("Convert %(wall)s into %(gate)s"), { wall: translate(wallName), gate: translate(gateName) });
     1173                        }
    11031174
    11041175                        walls.push({
    1105                             "tooltip": "Convert " + wallName + " to " + gateName,
     1176                            "tooltip": tooltipString,
    11061177                            "template": gateTemplate,
    11071178                            "callback": function (item) { transformWallToGate(item.template); }
    11081179                        });
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    11121183                    longWallTypes[state.template] = true;
    11131184                }
    11141185                else if (state.gate && !gates.length)
    1115                     for (var j = 0; j < GATE_ACTIONS.length; ++j)
    1116                         gates.push({
    1117                             "gate": state.gate,
    1118                             "tooltip": GATE_ACTIONS[j] + " gate",
    1119                             "locked": j == 0,
    1120                             "callback": function (item) { lockGate(item.locked); }
    1121                         });
     1186                {
     1187                    gates.push({
     1188                        "gate": state.gate,
     1189                        "tooltip": translate("Lock Gate"),
     1190                        "locked": true,
     1191                        "callback": function (item) { lockGate(item.locked); }
     1192                    });
     1193                    gates.push({
     1194                        "gate": state.gate,
     1195                        "tooltip": translate("Unlock Gate"),
     1196                        "locked": false,
     1197                        "callback": function (item) { lockGate(item.locked); }
     1198                    });
     1199                }
    11221200                // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
    11231201                else if (state.gate && state.gate.locked != gates[0].gate.locked)
    11241202                    for (var j = 0; j < gates.length; ++j)
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    11631241                }
    11641242            }
    11651243            if (packButton)
    1166                 items.push({ "packing": false, "packed": false, "tooltip": "Pack", "callback": function() { packUnit(true); } });
     1244                items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
    11671245            if (unpackButton)
    1168                 items.push({ "packing": false, "packed": true, "tooltip": "Unpack", "callback": function() { packUnit(false); } });
     1246                items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
    11691247            if (packCancelButton)
    1170                 items.push({ "packing": true, "packed": false, "tooltip": "Cancel packing", "callback": function() { cancelPackUnit(true); } });
     1248                items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
    11711249            if (unpackCancelButton)
    1172                 items.push({ "packing": true, "packed": true, "tooltip": "Cancel unpacking", "callback": function() { cancelPackUnit(false); } });
     1250                items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
    11731251
    11741252            if (items.length)
    11751253                setupUnitPanel(PACK, usedPanels, entState, playerState, items);
  • binaries/data/mods/public/gui/session/utility_functions.js

    diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js
    index ace9cc5..bbdd25b 100644
    a b function damageValues(dmg)  
    151151// For the unit details panel
    152152function damageTypeDetails(dmg)
    153153{
    154     if (dmg)
    155     {
    156         var dmgArray = [];
    157         if (dmg.hack) dmgArray.push(dmg.hack + "[font=\"sans-10\"][color=\"orange\"] Hack[/color][/font]");
    158         if (dmg.pierce) dmgArray.push(dmg.pierce + "[font=\"sans-10\"][color=\"orange\"] Pierce[/color][/font]");
    159         if (dmg.crush) dmgArray.push(dmg.crush + "[font=\"sans-10\"][color=\"orange\"] Crush[/color][/font]");
     154    if (!dmg)
     155        return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
    160156
    161         return dmgArray.join(", ");
    162     }
    163     else
    164     {
    165         return "[font=\"serif-12\"](None)[/font]";
    166     }
     157    var dmgArray = [];
     158    if (dmg.hack)
     159        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
     160            damage: dmg.hack,
     161            damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]"
     162        }));
     163    if (dmg.pierce)
     164        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
     165            damage: dmg.pierce,
     166            damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]"
     167        }));
     168    if (dmg.crush)
     169        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
     170            damage: dmg.crush,
     171            damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]"
     172        }));
     173
     174    return dmgArray.join(translate(", "));
    167175}
    168176
    169177// Converts an armor level into the actual reduction percentage
    170 function armorLevelToPercentage(level)
     178function armorLevelToPercentageString(level)
    171179{
    172     return 100 - Math.round(Math.pow(0.9, level) * 100);
     180    return (100 - Math.round(Math.pow(0.9, level) * 100)) + "%";
     181    //  return sprintf(translate("%(armorPercentage)s%"), { armorPercentage: (100 - Math.round(Math.pow(0.9, level) * 100)) }); // Not supported by our sprintf implementation.
    173182}
    174183
    175184// Also for the unit details panel
    176185function armorTypeDetails(dmg)
    177186{
    178     if (dmg)
    179     {
    180         var dmgArray = [];
    181         if (dmg.hack)
    182         {
    183             dmgArray.push(dmg.hack + "[font=\"sans-10\"][color=\"orange\"] Hack[/color][/font] " +
    184                 " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.hack) + "%)[/font]");
    185         }
    186         if (dmg.pierce)
    187         {
    188             dmgArray.push(dmg.pierce + "[font=\"sans-10\"][color=\"orange\"] Pierce[/color][/font] " +
    189                 " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.pierce) + "%)[/font]");
    190         }
    191         if (dmg.crush)
    192         {
    193             dmgArray.push(dmg.crush + "[font=\"sans-10\"][color=\"orange\"] Crush[/color][/font] " +
    194                 " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.crush) + "%)[/font]");
    195         }
     187    if (!dmg)
     188        return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
    196189
    197         return dmgArray.join(", ");
    198     }
    199     else
    200     {
    201         return "[font=\"serif-12\"](None)[/font]";
    202     }
     190    var dmgArray = [];
     191    if (dmg.hack)
     192        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
     193            damage: dmg.hack,
     194            damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]",
     195            armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.hack) }) + "[/font]"
     196        }));
     197    if (dmg.pierce)
     198        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
     199            damage: dmg.pierce,
     200            damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]",
     201            armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.pierce) }) + "[/font]"
     202        }));
     203    if (dmg.crush)
     204        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
     205            damage: dmg.crush,
     206            damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]",
     207            armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
     208        }));
     209
     210    return dmgArray.join(translate(", "));
    203211}
    204212
    205213// For the training tooltip
    206214function damageTypesToText(dmg)
    207215{
    208216    if (!dmg)
    209         return "[font=\"serif-12\"](None)[/font]";
    210 
    211     var hackLabel = "[font=\"serif-12\"] Hack[/font]";
    212     var pierceLabel = "[font=\"serif-12\"] Pierce[/font]";
    213     var crushLabel = "[font=\"serif-12\"] Crush[/font]";
    214     var hackDamage = dmg.hack;
    215     var pierceDamage = dmg.pierce;
    216     var crushDamage = dmg.crush;
     217        return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
    217218
    218219    var dmgArray = [];
    219     if (hackDamage) dmgArray.push(Math.round(hackDamage) + hackLabel);
    220     if (pierceDamage) dmgArray.push(Math.round(pierceDamage) + pierceLabel);
    221     if (crushDamage) dmgArray.push(Math.round(crushDamage) + crushLabel);
    222 
    223     return dmgArray.join("[font=\"serif-12\"], [/font]");
     220    if (dmg.hack)
     221        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
     222            damage: dmg.hack,
     223            damageType: "[font=\"serif-12\"]" + translate("Hack") + "[/font]"
     224        }));
     225    if (dmg.pierce)
     226        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
     227            damage: dmg.pierce,
     228            damageType: "[font=\"serif-12\"]" + translate("Pierce") + "[/font]"
     229        }));
     230    if (dmg.crush)
     231        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
     232            damage: dmg.crush,
     233            damageType: "[font=\"serif-12\"]" + translate("Crush") + "[/font]"
     234        }));
     235
     236    return dmgArray.join("[font=\"serif-12\"]" + translate(", ") + "[/font]");
    224237}
    225238
    226239// Also for the training tooltip
    227240function armorTypesToText(dmg)
    228241{
    229242    if (!dmg)
    230         return "[font=\"serif-12\"](None)[/font]";
    231 
    232     var hackDamage = dmg.hack;
    233     var pierceDamage = dmg.pierce;
    234     var crushDamage = dmg.crush;
    235     var hackLabel = "[font=\"serif-12\"] Hack (" + armorLevelToPercentage(hackDamage) + "%)[/font]";
    236     var pierceLabel = "[font=\"serif-12\"] Pierce (" + armorLevelToPercentage(pierceDamage) + "%)[/font]";
    237     var crushLabel = "[font=\"serif-12\"] Crush (" + armorLevelToPercentage(crushDamage) + "%)[/font]";
     243        return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
    238244
    239245    var dmgArray = [];
    240     if (hackDamage) dmgArray.push(hackDamage + hackLabel);
    241     if (pierceDamage) dmgArray.push(pierceDamage + pierceLabel);
    242     if (crushDamage) dmgArray.push(crushDamage + crushLabel);
    243 
    244     return dmgArray.join("[font=\"serif-12\"], [/font]");
     246    if (dmg.hack)
     247        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
     248            damage: dmg.hack,
     249            damageType: "[font=\"serif-12\"]" + translate("Hack") + "[/font]",
     250            armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.hack) }) + "[/font]"
     251        }));
     252    if (dmg.pierce)
     253        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
     254            damage: dmg.pierce,
     255            damageType: "[font=\"serif-12\"]" + translate("Pierce") + "[/font]",
     256            armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.pierce) }) + "[/font]"
     257        }));
     258    if (dmg.crush)
     259        dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
     260            damage: dmg.crush,
     261            damageType: "[font=\"serif-12\"]" + translate("Crush") + "[/font]",
     262            armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
     263        }));
     264
     265    return dmgArray.join("[font=\"serif-12\"]" + translate(", ") + "[/font]");
    245266}
    246267
    247268function getEntityCommandsList(entState)
    function getEntityCommandsList(entState)  
    251272    {
    252273        commands.push({
    253274            "name": "unload-all",
    254             "tooltip": "Unload All",
     275            "tooltip": translate("Unload All"),
    255276            "icon": "garrison-out.png"
    256277        });
    257278    }
    258279
    259280    commands.push({
    260281        "name": "delete",
    261         "tooltip": "Delete",
     282        "tooltip": translate("Delete"),
    262283        "icon": "kill_small.png"
    263284    });
    264285
    function getEntityCommandsList(entState)  
    266287    {
    267288        commands.push({
    268289            "name": "stop",
    269             "tooltip": "Stop",
     290            "tooltip": translate("Stop"),
    270291            "icon": "stop.png"
    271292        });
    272293        commands.push({
    273294            "name": "garrison",
    274             "tooltip": "Garrison",
     295            "tooltip": translate("Garrison"),
    275296            "icon": "garrison.png"
    276297        });
    277298    }
    function getEntityCommandsList(entState)  
    280301    {
    281302        commands.push({
    282303            "name": "repair",
    283             "tooltip": "Repair",
     304            "tooltip": translate("Repair"),
    284305            "icon": "repair.png"
    285306        });
    286307    }
    function getEntityCommandsList(entState)  
    289310    {
    290311        commands.push({
    291312            "name": "focus-rally",
    292             "tooltip": "Focus on Rally Point",
     313            "tooltip": translate("Focus on Rally Point"),
    293314            "icon": "focus-rally.png"
    294315        });
    295316    }
    296    
     317
    297318    if (entState.unitAI && entState.unitAI.lastWorkOrder)
    298319    {
    299320        commands.push({
    300321            "name": "back-to-work",
    301             "tooltip": "Back to Work",
     322            "tooltip": translate("Back to Work"),
    302323            "icon": "production.png"
    303324        });
    304325    }
    function getEntityCostComponentsTooltipString(template, trainNum, entity)  
    339360    totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", {"entity": entity, "batchSize": trainNum}) : 1));
    340361
    341362    var costs = [];
    342     if (totalCosts.food) costs.push(getCostComponentDisplayName("food") + " " + totalCosts.food);
    343     if (totalCosts.wood) costs.push(getCostComponentDisplayName("wood") + " " + totalCosts.wood);
    344     if (totalCosts.metal) costs.push(getCostComponentDisplayName("metal") + " " + totalCosts.metal);
    345     if (totalCosts.stone) costs.push(getCostComponentDisplayName("stone") + " " + totalCosts.stone);
    346     if (totalCosts.population) costs.push(getCostComponentDisplayName("population") + " " + totalCosts.population);
    347     if (totalCosts.time) costs.push(getCostComponentDisplayName("time") + " " + totalCosts.time);
     363    if (totalCosts.food) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("food"), cost: totalCosts.food }));
     364    if (totalCosts.wood) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("wood"), cost: totalCosts.wood }));
     365    if (totalCosts.metal) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("metal"), cost: totalCosts.metal }));
     366    if (totalCosts.stone) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("stone"), cost: totalCosts.stone }));
     367    if (totalCosts.population) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("population"), cost: totalCosts.population }));
     368    if (totalCosts.time) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("time"), cost: totalCosts.time }));
    348369    return costs;
    349370}
    350371
    function getWallPieceTooltip(wallTypes)  
    360381
    361382    // Initialize the acceptable types for '$x to $y $resource' mode.
    362383    for (var resource in wallTypes[0].cost)
     384    {
    363385        if (wallTypes[0].cost[resource])
    364386            resourceCount[resource] = [wallTypes[0].cost[resource]];
     387    }
    365388
    366389    var sameTypes = true;
    367390    for (var i = 1; i < wallTypes.length; ++i)
    function getEntityCostTooltip(template, trainNum, entity)  
    425448        var wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
    426449        var towerCosts = getEntityCostComponentsTooltipString(templateTower);
    427450
    428         cost += "\n";
    429         cost += " Walls:  " + wallCosts.join("  ") + "\n";
    430         cost += " Towers: " + towerCosts.join("  ");
     451        cost += " " + sprintf(translate("Walls:  %(costs)s"), { costs: wallCosts.join(translate("  ")) }) + "\n";
     452        cost += " " + sprintf(translate("Towers:  %(costs)s"), { costs: towerCosts.join(translate("  ")) });
    431453    }
    432454    else if (template.cost)
    433455    {
    434456        var costs = getEntityCostComponentsTooltipString(template, trainNum, entity);
    435         cost += costs.join("  ");
     457        cost = costs.join(translate("  "));
    436458    }
    437459    else
    438460    {
    function getPopulationBonusTooltip(template)  
    449471{
    450472    var popBonus = "";
    451473    if (template.cost && template.cost.populationBonus)
    452         popBonus = "\n[font=\"serif-bold-13\"]Population Bonus:[/font] " + template.cost.populationBonus;
     474        popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), {
     475            label: "[font=\"serif-bold-13\"]" + translate("Population Bonus:") + "[/font]",
     476            populationBonus: template.cost.populationBonus
     477        });
    453478    return popBonus;
    454479}
    455480
    function getNeededResourcesTooltip(resources)  
    460485{
    461486    var formatted = [];
    462487    for (var resource in resources)
    463         formatted.push("[font=\"serif-12\"]" + getCostComponentDisplayName(resource) + "[/font] " + resources[resource]);
     488        formatted.push(sprintf(translate("%(component)s %(cost)s"), {
     489            component: "[font=\"serif-12\"]" + getCostComponentDisplayName(resource) + "[/font]",
     490            cost: resources[resource]
     491        }));
    464492
    465     return "\n\n[font=\"serif-bold-13\"][color=\"red\"]Insufficient resources:[/color][/font]\n" + formatted.join("  ");
     493    return "\n\n[font=\"serif-bold-13\"][color=\"red\"]" + translate("Insufficient resources:") + "[/color][/font]\n" + formatted.join(translate("  "));
    466494}
    467495
    468496function getEntitySpeed(template)
    function getEntitySpeed(template)  
    470498    var speed = "";
    471499    if (template.speed)
    472500    {
    473         speed += "[font=\"serif-bold-13\"]Speed:[/font] ";
     501        var label = "[font=\"serif-bold-13\"]" + translate("Speed:") + "[/font]"
    474502        var speeds = [];
    475         if (template.speed.walk) speeds.push(template.speed.walk + " [font=\"serif-12\"]Walk[/font]");
    476         if (template.speed.run) speeds.push(template.speed.run + " [font=\"serif-12\"]Run[/font]");
     503        if (template.speed.walk) speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.walk, movementType: "[font=\"serif-12\"]" + translate("Walk") + "[/font]"}));
     504        if (template.speed.run) speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.run, movementType: "[font=\"serif-12\"]" + translate("Run") + "[/font]"}));
    477505
    478         speed += speeds.join(", ");
     506        speed = sprintf(translate("%(label)s %(speeds)s"), { label: label, speeds: speeds.join(translate(", ")) })
    479507    }
    480508    return speed;
    481509}
    function getEntityAttack(template)  
    489517        delete template.attack['Slaughter'];
    490518        for (var type in template.attack)
    491519        {
    492             var attack = "[font=\"serif-bold-13\"]" + type + " Attack:[/font] " + damageTypesToText(template.attack[type]);
    493             // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     520            var attack = "";
     521            var attackLabel = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(type) + "[/font]";
    494522            if (type == "Ranged")
    495                 attack += ", [font=\"serif-bold-13\"]Range:[/font] "+Math.round(template.attack[type].maxRange/4);
     523            {
     524                // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     525                attack = sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(range)s"), {
     526                    attackLabel: attackLabel,
     527                    damageTypes: damageTypesToText(template.attack[type]),
     528                    rangeLabel: "[font=\"serif-bold-13\"]" + translate("Range:") + "[/font]",
     529                    range: Math.round(template.attack[type].maxRange/4)
     530                });
     531            }
     532            else
     533            {
     534                attack = sprintf(translate("%(attackLabel)s %(damageTypes)s"), {
     535                    attackLabel: attackLabel,
     536                    damageTypes: damageTypesToText(template.attack[type])
     537                });
     538            }
    496539            attacks.push(attack);
    497540        }
    498541    }
    function getEntityAttack(template)  
    501544
    502545function getEntityName(template)
    503546{
    504     return template.name.specific || template.name.generic || "???";
     547    if (template.name.specific)
     548        return translate(template.name.specific);
     549    if (template.name.generic)
     550        return translate(template.name.generic);
     551
     552    warn(translate("Entity name requested on an entity without a name, specific or generic."));
     553    return translate("???");
    505554}
    506555
    507556function getEntityNames(template)
    508557{
    509     var names = [];
    510558    if (template.name.specific)
    511     {
    512         names.push(template.name.specific);
    513         if (template.name.generic && names[0] != template.name.generic)
    514             names.push("(" + template.name.generic + ")");
    515     }
    516     else if (template.name.generic)
    517         names.push(template.name.generic);
    518 
    519     return (names.length) ? names.join(" ") : "???";
     559    {
     560        if (template.name.generic && template.name.specific != template.name.generic)
     561            return sprintf(translate("%(specificName)s (%(genericName)s)"), {
     562                specificName: translate(template.name.specific),
     563                genericName: translate(template.name.generic)
     564            });
     565        return translate(template.name.specific);
     566    }
     567    if (template.name.generic)
     568        return translate(template.name.generic);
     569
     570    warn(translate("Entity name requested on an entity without a name, specific or generic."));
     571    return translate("???");
    520572}
    521573
    522574function getEntityNamesFormatted(template)
    function getEntityRankedName(entState)  
    546598    var template = GetTemplateData(entState.template)
    547599    var rank = entState.identity.rank;
    548600    if (rank)
    549         return rank + " " + template.name.specific;
     601        return sprintf(translate("%(rank)s %(name)s"), { rank: rank, name: template.name.specific });
    550602    else
    551603        return template.name.specific;
    552604}
    function getRankIconSprite(entState)  
    568620 */
    569621function getTradingTooltip(gain)
    570622{
    571     var tooltip = gain.traderGain;
     623    var gainString = gain.traderGain;
    572624    if (gain.market1Gain && gain.market1Owner == gain.traderOwner)
    573         tooltip += "+" + gain.market1Gain;
     625        gainString += translate("+") + gain.market1Gain;
    574626    if (gain.market2Gain && gain.market2Owner == gain.traderOwner)
    575         tooltip += "+" + gain.market2Gain;
    576     tooltip += " (you)";
     627        gainString += translate("+") + gain.market2Gain;
     628
     629    var tooltip = sprintf(translate("%(gain)s (%(player)s)"), {
     630        gain: gainString,
     631        player: translate("you")
     632    });
    577633
    578634    if (gain.market1Gain && gain.market1Owner != gain.traderOwner)
    579         tooltip += ", " + gain.market1Gain + " (player " + gain.market1Owner + ")";
     635        tooltip += translate(", ") + sprintf(translate("%(gain)s (%(player)s)"), {
     636            gain: gain.market1Gain,
     637            player: sprintf(translate("player %(name)s"), { name: gain.market1Owner })
     638        });
    580639    if (gain.market2Gain && gain.market2Owner != gain.traderOwner)
    581         tooltip += ", " + gain.market2Gain + " (player " + gain.market2Owner + ")";
     640        tooltip += translate(", ") + sprintf(translate("%(gain)s (%(player)s)"), {
     641            gain: gain.market2Gain,
     642            player: sprintf(translate("player %(name)s"), { name: gain.market2Owner })
     643        });
    582644
    583645    return tooltip;
    584646}
    585647
     648function getAttackTypeLabel(type)
     649{
     650    if (type === "Charge") return translate("Charge Attack:");
     651    if (type === "Melee") return translate("Melee Attack:");
     652    if (type === "Ranged") return translate("Ranged Attack:");
     653
     654    warn(sprintf(translate("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized."), { attackType: type }));
     655    return translate("Attack:");
     656}
     657
    586658/**
    587659 * Returns the entity itself except when garrisoned where it returns its garrisonHolder
    588660 */
  • binaries/data/mods/public/gui/splashscreen/splashscreen.js

    diff --git a/binaries/data/mods/public/gui/splashscreen/splashscreen.js b/binaries/data/mods/public/gui/splashscreen/splashscreen.js
    index abd975e..15ff54a 100644
    a b  
    11function init(data)
    22{
    3     getGUIObjectByName("mainText").caption = readFile("gui/splashscreen/" + data.page + ".txt");
     3    getGUIObjectByName("mainText").caption = Engine.translateLines(readFile("gui/splashscreen/" + data.page + ".txt"));
    44}
  • binaries/data/mods/public/gui/splashscreen/splashscreen.xml

    diff --git a/binaries/data/mods/public/gui/splashscreen/splashscreen.xml b/binaries/data/mods/public/gui/splashscreen/splashscreen.xml
    index 226d621..05b9328 100644
    a b  
    88    <object type="image" z="0" style="TranslucentPanel"/>
    99
    1010    <object type="image" style="StoneDialog" size="50%-300 50%-200 50%+300 50%+200">
    11         <object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">Welcome to 0 A.D. !</object>
     11        <object type="text" style="TitleText" size="50%-128 0%-16 50%+128 16">
     12            <translatableAttribute id="caption">Welcome to 0 A.D. !</translatableAttribute>
     13        </object>
    1214
    1315            <object type="image" sprite="BackgroundTranslucent" size="20 20 100%-20 100%-52">
    1416                <object name="openFundraiserPage" type="button" style="fundraiserButton" size="5 5 100% 150">
     
    2123                </object>
    2224            </object>
    2325        <object name="btnOK" type="button" style="StoneButton" tooltip_style="snToolTip" size="24 100%-52 188 100%-24">
    24             OK
     26            <translatableAttribute id="caption">OK</translatableAttribute>
    2527            <action on="Press"><![CDATA[
    2628            Engine.SetSplashScreenEnabled(!getGUIObjectByName("displaySplashScreen").checked);
    2729            Engine.PopGuiPage();
    2830            ]]></action>
    2931        </object>
    3032        <object name="btnFundraiser" type="button" style="StoneButton" tooltip_style="snToolTip" size="196 100%-52 360 100%-24">
    31             Visit Fundraiser
     33            <translatableAttribute id="caption">Visit Fundraiser</translatableAttribute>
    3234            <action on="Press"><![CDATA[
    3335            Engine.OpenURL("http://play0ad.com/fundraiser");
    3436            ]]></action>
    3537        </object>
    3638        <object size="368 100%-52 100%-32 100%-24">
    3739            <object size="0 0 100% 100%">
    38                 <object name="displaySplashScreenText" size="0 0 100%-32 100%" type="text" style="RightLabelText">Don't show this again</object>
     40                <object name="displaySplashScreenText" size="0 0 100%-32 100%" type="text" style="RightLabelText">
     41                    <translatableAttribute id="caption">Don't show this again</translatableAttribute>
     42                </object>
    3943                <object name="displaySplashScreen" checked="false" size="100%-16 50%-8 100% 50%+8" type="checkbox" style="StoneCrossBox"/>
    4044            </object>
    4145        </object>
  • binaries/data/mods/public/gui/summary/summary.js

    diff --git a/binaries/data/mods/public/gui/summary/summary.js b/binaries/data/mods/public/gui/summary/summary.js
    index 81129a3..8b9b4f5 100644
    a b function adjustTabDividers(tabSize)  
    3737function init(data)
    3838{
    3939    var civData = loadCivData();
    40     var mapSize = "Scenario";
     40    var mapDisplayType = translate("Scenario");
    4141
    42     getGUIObjectByName("timeElapsed").caption = "Time elapsed: " + timeToString(data.timeElapsed);
     42    getGUIObjectByName("timeElapsed").caption = sprintf(translate("Time elapsed: %(time)s"), { time: timeToString(data.timeElapsed) });
    4343
    4444    getGUIObjectByName("summaryText").caption = data.gameResult;
    4545
    function init(data)  
    5454        {
    5555            if (mapSizes.tiles[mapSizeIndex] == data.mapSettings.Size)
    5656            {
    57                 mapSize = mapSizes.names[mapSizeIndex];
     57                mapDisplayType = mapSizes.names[mapSizeIndex];
    5858                break;
    5959            }
    6060        }
    6161    }
    6262
    63     getGUIObjectByName("mapName").caption = data.mapSettings.Name + " - " + mapSize;
     63    getGUIObjectByName("mapName").caption = sprintf(translate("%(mapName)s - %(mapType)s"), { mapName: data.mapSettings.Name, mapType: mapDisplayType});
    6464
    6565    // Space player boxes
    6666    var boxSpacing = 32;
  • binaries/data/mods/public/gui/summary/summary.xml

    diff --git a/binaries/data/mods/public/gui/summary/summary.xml b/binaries/data/mods/public/gui/summary/summary.xml
    index 4e50416..6fbc09e 100644
    a b  
    2020        </action>
    2121
    2222        <object style="TitleText" type="text" size="50%-128 4 50%+128 36">
    23             Summary
     23            <translatableAttribute id="caption">Summary</translatableAttribute>
    2424        </object>
    2525
    2626        <object type="image" sprite="ForegroundBody" size="20 20 100%-20 70">
     
    5959
    6060        <object name="scorePanelButton" type="button" sprite="ForegroundTab" size="20 95 170 120">
    6161            <action on="Press">selectPanel(0);</action>
    62             <object type="text" style="TitleText" ghost="true">Score</object>
     62            <object type="text" style="TitleText" ghost="true">
     63                <translatableAttribute id="caption">Score</translatableAttribute>
     64            </object>
    6365        </object>
    6466       
    6567        <object name="unitsBuildingsPanelButton" type="button" sprite="BackgroundTab" size="176 95 326 120">
    6668            <action on="Press">selectPanel(1);</action>
    67             <object type="text" style="TitleText" ghost="true">Units/buildings</object>
     69            <object type="text" style="TitleText" ghost="true">
     70                <translatableAttribute id="caption">Units/buildings</translatableAttribute>
     71            </object>
    6872        </object>
    6973
    7074        <object name="conquestPanelButton" type="button" sprite="BackgroundTab" size="332 95 480 120">
    7175            <action on="Press">selectPanel(2);</action>
    72             <object type="text" style="TitleText" ghost="true">Conquest</object>
     76            <object type="text" style="TitleText" ghost="true">
     77                <translatableAttribute id="caption">Conquest</translatableAttribute>
     78            </object>
    7379        </object>
    7480
    7581        <object name="resourcesPanelButton" type="button" sprite="BackgroundTab" size="486 95 636 120">
    7682            <action on="Press">selectPanel(3);</action>
    77             <object type="text" style="TitleText" ghost="true">Resources</object>
     83            <object type="text" style="TitleText" ghost="true">
     84                <translatableAttribute id="caption">Resources</translatableAttribute>
     85            </object>
    7886        </object>
    7987
    8088        <object name="marketPanelButton" type="button" sprite="BackgroundTab" size="642 95 792 120">
    8189            <action on="Press">selectPanel(4);</action>
    82             <object type="text" style="TitleText" ghost="true">Market</object>
     90            <object type="text" style="TitleText" ghost="true">
     91                <translatableAttribute id="caption">Market</translatableAttribute>
     92            </object>
    8393        </object>
    8494
    8595        <object name="scorePanel" type="image" sprite="ForegroundBody" size="20 120 100%-20 100%-58">
    8696
    8797            <object size="0 0 100% 100%-50">
    8898                <object name="playerName0Heading" type="text" style="LeftTabLabelText">
    89                     Player name
     99                    <translatableAttribute id="caption">Player name</translatableAttribute>
    90100                </object>
    91101                <object name="economyScoreHeading" type="text" style="CenteredTabLabelText">
    92                     Economy&#10;score
     102                    <translatableAttribute id="caption">Economy score</translatableAttribute>
    93103                </object>
    94104                <object name="militaryScoreHeading" type="text" style="CenteredTabLabelText">
    95                     Military&#10;score
     105                    <translatableAttribute id="caption">Military score</translatableAttribute>
    96106                </object>
    97107                <object name="explorationScoreHeading" type="text" style="CenteredTabLabelText">
    98                     Exploration&#10;score
     108                    <translatableAttribute id="caption">Exploration score</translatableAttribute>
    99109                </object>
    100110                <object name="totalScoreHeading" type="text" style="CenteredTabLabelText">
    101                     Total&#10;score
     111                    <translatableAttribute id="caption">Total score</translatableAttribute>
    102112                </object>
    103113            </object>
    104114
     
    121131
    122132            <object size="0 0 100% 100%-50">
    123133                <object name="playerName1Heading" type="text" style="LeftTabLabelText">
    124                     Player name
     134                    <translatableAttribute id="caption">Player name</translatableAttribute>
    125135                </object>
    126136                <object name="unitsTrainedHeading" type="text" style="CenteredTabLabelText">
    127                     Units&#10;trained
     137                    <translatableAttribute id="caption">Units trained</translatableAttribute>
    128138                </object>
    129139                <object name="unitsLostHeading" type="text" style="CenteredTabLabelText">
    130                     Units&#10;lost
     140                    <translatableAttribute id="caption">Units lost</translatableAttribute>
    131141                </object>
    132142                <object name="enemyUnitsKilledHeading" type="text" style="CenteredTabLabelText">
    133                     Enemy units&#10;killed
     143                    <translatableAttribute id="caption">Enemy units killed</translatableAttribute>
    134144                </object>
    135145                <object name="buildingsConstructedHeading" type="text" style="CenteredTabLabelText">
    136                     Buildings&#10;constructed
     146                    <translatableAttribute id="caption">Buildings constructed</translatableAttribute>
    137147                </object>
    138148                <object name="buildingsLostHeading" type="text" style="CenteredTabLabelText">
    139                     Buildings&#10;lost
     149                    <translatableAttribute id="caption">Buildings lost</translatableAttribute>
    140150                </object>
    141151                <object name="enemyBuildingsDestroyedHeading" type="text" style="CenteredTabLabelText">
    142                     Enemy&#10;buildings&#10;destroyed
     152                    <translatableAttribute id="caption">Enemy buildings destroyed</translatableAttribute>
    143153                </object>
    144154            </object>
    145155
     
    164174
    165175            <object size="0 0 100% 100%-50">
    166176                <object name="playerName2Heading" type="text" style="LeftTabLabelText">
    167                     Player name
     177                    <translatableAttribute id="caption">Player name</translatableAttribute>
    168178                </object>
    169179                <object name="civCentresBuiltHeading" type="text" style="CenteredTabLabelText">
    170                     Civ centres&#10;built
     180                    <translatableAttribute id="caption">Civ centres built</translatableAttribute>
    171181                </object>
    172182                <object name="enemyCivCentresDestroyedHeading" type="text" style="CenteredTabLabelText">
    173                     Enemy&#10;civ centres&#10;destroyed
     183                    <translatableAttribute id="caption">Enemy civ centres destroyed</translatableAttribute>
    174184                </object>
    175185                <object name="mapExplorationHeading" type="text" style="CenteredTabLabelText">
    176                     Map&#10;exploration
     186                    <translatableAttribute id="caption">Map exploration</translatableAttribute>
    177187                </object>
    178188            </object>
    179189
     
    195205
    196206            <object size="0 0 100% 100%-50">
    197207                <object name="playerName3Heading" type="text" style="LeftTabLabelText">
    198                     Player name
     208                    <translatableAttribute id="caption">Player name</translatableAttribute>
    199209                </object>
    200210                <object name="resourceHeading" type="text" style="CenteredTabLabelText">
    201                     Resource Statistics (Gathered / Used)
     211                    <translatableAttribute id="caption">Resource Statistics (Gathered / Used)</translatableAttribute>
    202212                </object>
    203213                <object name="foodGatheredHeading" type="text" style="CenteredTabLabelText">
    204                     Food
     214                    <translatableAttribute id="caption">Food</translatableAttribute>
    205215                </object>
    206216                <object name="woodGatheredHeading" type="text" style="CenteredTabLabelText">
    207                     Wood
     217                    <translatableAttribute id="caption">Wood</translatableAttribute>
    208218                </object>
    209219                <object name="stoneGatheredHeading" type="text" style="CenteredTabLabelText">
    210                     Stone
     220                    <translatableAttribute id="caption">Stone</translatableAttribute>
    211221                </object>
    212222                <object name="metalGatheredHeading" type="text" style="CenteredTabLabelText">
    213                     Metal
     223                    <translatableAttribute id="caption">Metal</translatableAttribute>
    214224                </object>
    215225                <object name="vegetarianRatioHeading" type="text" style="CenteredTabLabelText">
    216                     Vegetarian&#10;ratio
     226                    <translatableAttribute id="caption">Vegetarian ratio</translatableAttribute>
    217227                </object>
    218228                <object name="treasuresCollectedHeading" type="text" style="CenteredTabLabelText">
    219                     Treasures&#10;collected
     229                    <translatableAttribute id="caption">Treasures collected</translatableAttribute>
    220230                </object>
    221231                <object name="resourcesTributedHeading" type="text" style="CenteredTabLabelText">
    222                     Tributes&#10;(Sent / Received)
     232                    <translatableAttribute id="caption">Tributes (Sent / Received)</translatableAttribute>
    223233                </object>
    224234            </object>
    225235
     
    245255
    246256            <object size="0 0 100% 100%-50">
    247257                <object name="playerName4Heading" type="text" style="LeftTabLabelText">
    248                     Player name
     258                    <translatableAttribute id="caption">Player name</translatableAttribute>
    249259                </object>
    250260                <object name="exchangedFoodHeading" type="text" style="CenteredTabLabelText">
    251                     Food&#10;exchanged
     261                    <translatableAttribute id="caption">Food exchanged</translatableAttribute>
    252262                </object>
    253263                <object name="exchangedWoodHeading" type="text" style="CenteredTabLabelText">
    254                     Wood&#10;exchanged
     264                    <translatableAttribute id="caption">Wood exchanged</translatableAttribute>
    255265                </object>
    256266                <object name="exchangedStoneHeading" type="text" style="CenteredTabLabelText">
    257                     Stone&#10;exchanged
     267                    <translatableAttribute id="caption">Stone exchanged</translatableAttribute>
    258268                </object>
    259269                <object name="exchangedMetalHeading" type="text" style="CenteredTabLabelText">
    260                     Metal&#10;exchanged
     270                    <translatableAttribute id="caption">Metal exchanged</translatableAttribute>
    261271                </object>
    262272                <object name="barterEfficiencyHeading" type="text" style="CenteredTabLabelText">
    263                     Barter&#10;efficiency
     273                    <translatableAttribute id="caption">Barter efficiency</translatableAttribute>
    264274                </object>
    265275                <object name="tradeIncomeHeading" type="text" style="CenteredTabLabelText">
    266                     Trade&#10;income
     276                    <translatableAttribute id="caption">Trade income</translatableAttribute>
    267277                </object>
    268278            </object>
    269279
     
    286296        </object>
    287297
    288298        <object type="button" style="StoneButton" size="100%-164 100%-52 100%-24 100%-24">
    289             Continue
     299            <translatableAttribute id="caption">Continue</translatableAttribute>
    290300            <action on="Press">
    291301                Engine.SwitchGuiPage("page_pregame.xml");
    292302            </action>