Ticket #67: internationalization.patch

File internationalization.patch, 507.9 KB (added by Adrián Chaves, 11 years ago)
  • new file inaries/data/localization/.tx/config

    diff --git a/binaries/data/localization/.tx/config b/binaries/data/localization/.tx/config
    new file mode 100644
    index 0000000..9695d34
    - +  
     1[main]
     2host = https://www.transifex.com
     3
     4[0ad.engine]
     5file_filter = <lang>.engine.po
     6source_file = engine.pot
     7source_lang = en
     8
  • new file inaries/data/mods/public/globalscripts/localization.js

    diff --git a/binaries/data/mods/public/globalscripts/localization.js b/binaries/data/mods/public/globalscripts/localization.js
    new file mode 100644
    index 0000000..158f9e4
    - +  
     1// Localizes (translates) any string value in the specified JavaScript object that is associated with a key included in
     2// the specified keys array.
     3function localizeObjectKeys(object, keys) {
     4    for (property in object)
     5    {
     6        if (keys.indexOf(property) > -1)
     7        {
     8            if (object[property] != "" && object[property] !== undefined)
     9            {
     10                object[property] = Engine.localize(object[property]);
     11            }
     12        }
     13        else
     14        {
     15            // From http://stackoverflow.com/a/7390612/939364
     16            var variableType = ({}).toString.call(object[property]).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
     17            if (variableType === "object" || variableType == "array")
     18            {
     19                localizeObjectKeys(object[property], keys);
     20            }
     21        }
     22    }
     23}
  • 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..320b061
    - +  
     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.localize('[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.localize('[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.localize('[sprintf] huh?'));
     153                            }
     154                        }
     155                    }
     156                    else {
     157                        throw(Engine.localize('[sprintf] huh?'));
     158                    }
     159                    match[2] = field_list;
     160                }
     161                else {
     162                    arg_names |= 2;
     163                }
     164                if (arg_names === 3) {
     165                    throw(Engine.localize('[sprintf] mixing positional and named placeholders is not (yet) supported'));
     166                }
     167                parse_tree.push(match);
     168            }
     169            else {
     170                throw(Engine.localize('[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..796bcc2 100644
    a b function init(settings)  
    55{
    66    g_Callback = settings.callback;
    77
     8    localizeObjectKeys(settings.ais, ["name", "description"]);
    89    g_AIs = [
    9         {id: "", data: {name: "None", description: "AI will be disabled for this player."}}
     10        {id: "", data: {name: Engine.localizeWithContext("ai", "None"), description: Engine.localize("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 = [ Engine.localize(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 = [Engine.localize("Sandbox"), Engine.localize("Easy"), Engine.localize("Medium"), Engine.localize("Hard"), Engine.localize("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..830b37c 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            <localizableAttribute id="caption">AI Configuration</localizableAttribute>
     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                <localizableAttribute id="caption">AI Player</localizableAttribute>
    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                <localizableAttribute id="caption">AI Difficulty</localizableAttribute>
    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            <localizableAttribute id="caption">OK</localizableAttribute>
    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            <localizableAttribute id="caption">Cancel</localizableAttribute>
    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..6b1edc9 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(Engine.localizeWithContext("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(Engine.localizeWithContext("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(Engine.localizePluralWithContext("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(Engine.localizePluralWithContext("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(Engine.localize("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(Engine.localizePluralWithContext("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(Engine.localizeWithContext("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(Engine.localizeWithContext("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..dcbd084 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            <localizableAttribute id="caption">Civilizations</localizableAttribute>
     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                <localizableAttribute id="caption">Civilization Selection</localizableAttribute>
    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            <localizableAttribute id="caption">Close</localizableAttribute>
    116119            <action on="Press">
    117120                <![CDATA[
    118121                    Engine.PopGuiPage();
  • 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..7665de9 100644
    a b function loadCivData()  
    1414    for each (var filename in civFiles)
    1515    {   // Parse data if valid file
    1616        var data = parseJSONData(filename);
     17        localizeObjectKeys(data, ["Name", "Description", "History", "Special"]);
    1718        civData[data.Code] = data;
    1819    }
    1920   
  • 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..04a4da7 100644
    a b function messageBox (mbWidth, mbHeight, mbMessage, mbTitle, mbMode, mbButtonCapt  
    2727
    2828// ====================================================================
    2929
     30var g_FpsDisplayString = Engine.localize("FPS: %(fps)s")
    3031function updateFPS()
    3132{   
    32     getGUIObjectByName("fpsCounter").caption = "FPS: " + getFPS();
     33    getGUIObjectByName("fpsCounter").caption = sprintf(g_FpsDisplayString, { fps: getFPS() });
    3334}
  • 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..a0ac0a3 100644
    a b  
    55
    66// ====================================================================
    77
     8var g_localizedTimeFormat = Engine.localize("HH:mm:ss");
     9
    810function getRandom(randomMin, randomMax)
    911{
    1012    // Returns a random whole number in a min..max range.
    function parseJSONData(pathname)  
    6466    var rawData = readFile(pathname);
    6567    if (!rawData)
    6668    {
    67         error("Failed to read file: "+pathname);
     69        error(sprintf(Engine.localize("Failed to read file: %(path)s"), { path: pathname }));
    6870    }
    6971    else
    7072    {
    function parseJSONData(pathname)  
    7375            // TODO: Need more info from the parser on why it failed: line number, position, etc!
    7476            data = JSON.parse(rawData);
    7577            if (!data)
    76                 error("Failed to parse JSON data in: "+pathname+" (check for valid JSON data)");
     78                error(sprintf(Engine.localize("Failed to parse JSON data in: %(path)s (check for valid JSON data)"), { path: pathname }));
    7779
    7880
    7981        }
    8082        catch(err)
    8183        {
    82             error(err.toString()+": parsing JSON data in "+pathname);
     84            error(sprintf(Engine.localize("%(error)s: parsing JSON data in %(path)s"), { error: err.toString(), path: pathname }));
    8385        }
    8486    }
    8587
    function parseJSONFromDataFile(filename)  
    141143    var path = "simulation/data/"+filename;
    142144    var rawData = readFile(path);
    143145    if (!rawData)
    144         error("Failed to read file: "+path);
     146        error(sprintf(Engine.localize("Failed to read file: %(path)s"), { path: path }));
    145147
    146148    try
    147149    {
    function parseJSONFromDataFile(filename)  
    152154    }
    153155    catch(err)
    154156    {
    155         error(err.toString()+": parsing JSON data in "+path);
     157        error(sprintf(Engine.localize("%(error)s: parsing JSON data in %(path)s"), { error: err.toString(), path: path }));
    156158    }
    157159
    158160    return undefined;
    function initPlayerDefaults()  
    167169
    168170    var data = parseJSONFromDataFile("player_defaults.json");
    169171    if (!data || !data.PlayerData)
    170         error("Failed to parse player defaults in player_defaults.json (check for valid JSON data)");
     172        error(Engine.localize("Failed to parse player defaults in player_defaults.json (check for valid JSON data)"));
    171173    else
     174    {
     175        localizeObjectKeys(data.PlayerData, ["Name"])
    172176        defaults = data.PlayerData;
     177    }
    173178
    174179    return defaults;
    175180}
    function initMapSizes()  
    187192
    188193    var data = parseJSONFromDataFile("map_sizes.json");
    189194    if (!data || !data.Sizes)
    190         error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)");
     195        error(Engine.localize("Failed to parse map sizes in map_sizes.json (check for valid JSON data)"));
    191196    else
    192197    {
     198        localizeObjectKeys(data, ["Name", "LongName"]);
    193199        for (var i = 0; i < data.Sizes.length; ++i)
    194200        {
    195201            sizes.names.push(data.Sizes[i].LongName);
    function initGameSpeeds()  
    216222
    217223    var data = parseJSONFromDataFile("game_speeds.json");
    218224    if (!data || !data.Speeds)
    219         error("Failed to parse game speeds in game_speeds.json (check for valid JSON data)");
     225        error(Engine.localize("Failed to parse game speeds in game_speeds.json (check for valid JSON data)"));
    220226    else
    221227    {
     228        localizeObjectKeys(data, ["Name"]);
    222229        for (var i = 0; i < data.Speeds.length; ++i)
    223230        {
    224231            gameSpeeds.names.push(data.Speeds[i].Name);
    function iColorToString(color)  
    254261 */
    255262function timeToString(time)
    256263{
    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);
     264    return Engine.formatMillisecondsIntoDateString(time, g_localizedTimeFormat);
    261265}
    262266
    263267// ====================================================================
  • 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..13e8572 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: Engine.localize("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..43d911e 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(Engine.localize("%(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(Engine.localize("%(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(Engine.localize("%(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(Engine.localize("%(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(Engine.localize("%(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(Engine.localize("%(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(Engine.localize("%(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(Engine.localize("%(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(Engine.localize("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..edf5b87 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, Engine.localize("yyyy-MM-dd HH:mm:ss"));
     9    if (metadata.description)
     10    {
     11        return sprintf(Engine.localize("[%(date)s] %(map)s - %(description)s"), { date: dateTimeString, map: metadata.initAttributes.map, description: metadata.description });
     12    }
     13    else
     14    {
     15        return sprintf(Engine.localize("[%(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..129b261 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(Engine.localize("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(Engine.localize("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..20a0d6f 100644
    a b Music.prototype.updateState = function()  
    108108            break;
    109109
    110110        default:
    111             warn("Music.updateState(): Unknown music state: " + this.currentState);
     111            warn(sprintf(Engine.localize("%(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(Engine.localize("%(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..2f946e0 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 Engine.localize("Unknown reason");
     7    case 1: return Engine.localize("Unexpected shutdown");
     8    case 2: return Engine.localize("Incorrect network protocol version");
     9    case 3: return Engine.localize("Game has already started");
     10    default: return sprintf(Engine.localize("[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        Engine.localize("Lost connection to the server.") + "\n\n" + sprintf(Engine.localize("Reason: %(reason)s."), { reason: reasontext }),
     20        Engine.localize("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..8cf9a61 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(Engine.localize("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..3e8850f 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 = [Engine.localize("Conquest"), Engine.localizeWithContext("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", Engine.localize("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 = [Engine.localize("Very Low"), Engine.localize("Low"), Engine.localize("Medium"), Engine.localize("High"), Engine.localize("Very High"), Engine.localize("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 = {};  
    4949
    5050var g_MapFilters = [];
    5151
     52var g_randomCivilizationString = "[color=\"orange\"]" + Engine.localizeWithContext("civilization", "Random")
     53
    5254// 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.";
     55var g_warning = "[font=\"serif-bold-12\"][color=\"orange\"]" + Engine.localize("Warning:") + "[/color][/font]"
     56var g_NavalWarning = "\n\n" + sprintf(Engine.localize("%(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."), { warning: g_warning });
    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(Engine.localize("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 = [Engine.localize("Scenario"), Engine.localizeWithContext("map", "Skirmish"), Engine.localizeWithContext("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", Engine.localize("Default"), function(settings) { return settings && !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"]); });
     116    addFilter("naval", Engine.localize("Naval Maps"), function(settings) { return settings && keywordTestAND(settings.Keywords, ["naval"]); });
     117    addFilter("demo", Engine.localize("Demo Maps"), function(settings) { return settings && keywordTestAND(settings.Keywords, ["demo"]); });
     118    addFilter("all", Engine.localize("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 = [Engine.localizeWithContext("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(g_randomCivilizationString);
    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(Engine.localize("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\"]" + Engine.localizeWithContext("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            localizeObjectKeys(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            {
     540                g_MapData[name] = { settings: { "Name": Engine.localizeWithContext("map", "Random"), "Description": Engine.localize("Randomly selects a map from the list") } };
     541            }
    537542            else
     543            {
    538544                g_MapData[name] = parseJSONData(name+".json");
     545                localizeObjectKeys(g_MapData[name], ["Name", "Description"]);
     546            }
    539547            break;
    540548
    541549        default:
    542             error("loadMapData: Unexpected map type '"+g_GameAttributes.mapType+"'");
     550            error(sprintf(Engine.localize("loadMapData: Unexpected map type '%(mapType)s'"), { mapType: g_GameAttributes.mapType }));
    543551            return undefined;
    544552        }
    545553    }
    function selectNumPlayers(num)  
    620628            if (g_IsNetworked)
    621629                Engine.AssignNetworkPlayer(player, "");
    622630            else
    623                 g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
     631                g_PlayerAssignments = { "local": { "name": Engine.localize("You"), "player": 1, "civ": "", "team": -1} };
    624632        }
    625633    }
    626634
    function selectMapType(type)  
    674682        break;
    675683
    676684    default:
    677         error("selectMapType: Unexpected map type '"+g_GameAttributes.mapType+"'");
     685        error(sprintf(Engine.localize("selectMapType: Unexpected map type '%(mapType)s'"), { mapType: g_GameAttributes.mapType }));
    678686        return;
    679687    }
    680688
    function selectMapType(type)  
    683691    updateGameAttributes();
    684692}
    685693
    686 function selectMapFilter(filterName)
     694function selectMapFilter(id)
    687695{
    688696    // Avoid recursion
    689697    if (g_IsInGuiUpdate)
    function selectMapFilter(filterName)  
    693701    if (g_IsNetworked && !g_IsController)
    694702        return;
    695703
    696     g_GameAttributes.mapFilter = filterName;
     704    g_GameAttributes.mapFilter = id;
    697705
    698706    initMapNameList();
    699707
    function selectMap(name)  
    721729    // Copy any new settings
    722730    g_GameAttributes.map = name;
    723731    g_GameAttributes.script = mapSettings.Script;
    724     if (mapData !== "Random")
     732    if (g_GameAttributes.map !== "random")
    725733        for (var prop in mapSettings)
    726734            g_GameAttributes.settings[prop] = mapSettings[prop];
    727735
    function selectMap(name)  
    737745    // Reset player assignments on map change
    738746    if (!g_IsNetworked)
    739747    {   // Slot 1
    740         g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
     748        g_PlayerAssignments = { "local": { "name": Engine.localize("You"), "player": 1, "civ": "", "team": -1} };
    741749    }
    742750    else
    743751    {
    function launchGame()  
    759767{
    760768    if (g_IsNetworked && !g_IsController)
    761769    {
    762         error("Only host can start game");
     770        error(Engine.localize("Only host can start game"));
    763771        return;
    764772    }
    765773
    function launchGame()  
    812820                    usedName++;
    813821           
    814822            // Assign civ specific names to AI players
     823            chosenName = Engine.localize(chosenName);
    815824            if (usedName)
    816                 g_GameAttributes.settings.PlayerData[i].Name = chosenName + " " + romanNumbers[usedName+1];
     825                g_GameAttributes.settings.PlayerData[i].Name = sprintf(Engine.localize("%(playerName)s %(romanNumber)s"), { playerName: chosenName, romanNumber: romanNumbers[usedName+1]});
    817826            else
    818827                g_GameAttributes.settings.PlayerData[i].Name = chosenName;
    819828        }
    function onGameAttributesChange()  
    862871    // Update some controls for clients
    863872    if (!g_IsController)
    864873    {
    865         getGUIObjectByName("mapFilterText").caption = g_GameAttributes.mapFilter;
     874        var mapFilderSelection = getGUIObjectByName("mapFilterText");
     875        var mapFilterId = mapFilderSelection.list_data.indexOf(g_GameAttributes.mapFilter);
     876        getGUIObjectByName("mapFilterText").caption = mapFilderSelection.list[mapFilterId];
    866877        var mapTypeSelection = getGUIObjectByName("mapTypeSelection");
    867878        var idx = mapTypeSelection.list_data.indexOf(g_GameAttributes.mapType);
    868879        getGUIObjectByName("mapTypeText").caption = mapTypeSelection.list[idx];
    function onGameAttributesChange()  
    900911    var speedIdx = (g_GameAttributes.gameSpeed !== undefined && g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) != -1) ? g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) : g_GameSpeeds["default"];
    901912    var victoryIdx = (VICTORY_DATA.indexOf(mapSettings.GameType) != -1 ? VICTORY_DATA.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
    902913    enableCheats.checked = (g_GameAttributes.settings.CheatsEnabled === undefined || !g_GameAttributes.settings.CheatsEnabled ? false : true)
    903     enableCheatsText.caption = (enableCheats.checked ? "Yes" : "No");
     914    enableCheatsText.caption = (enableCheats.checked ? Engine.localize("Yes") : Engine.localize("No"));
    904915    gameSpeedText.caption = g_GameSpeeds.names[speedIdx];
    905916    populationCap.selected = (POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);
    906917    populationCapText.caption = POPULATION_CAP[populationCap.selected];
    function onGameAttributesChange()  
    934945            populationCapText.hidden = true;
    935946            startingResourcesText.hidden = true;
    936947           
    937             mapSizeText.caption = "Map size:";
     948            mapSizeText.caption = Engine.localize("Map size:");
    938949            mapSize.selected = sizeIdx;
    939             revealMapText.caption = "Reveal map:";
     950            revealMapText.caption = Engine.localize("Reveal map:");
    940951            revealMap.checked = (mapSettings.RevealMap ? true : false);
    941952
    942             victoryConditionText.caption = "Victory condition:";
     953            victoryConditionText.caption = Engine.localize("Victory condition:");
    943954            victoryCondition.selected = victoryIdx;
    944             lockTeamsText.caption = "Teams locked:";
     955            lockTeamsText.caption = Engine.localize("Teams locked:");
    945956            lockTeams.checked = (mapSettings.LockTeams ? true : false);
    946957        }
    947958        else
    function onGameAttributesChange()  
    959970
    960971            numPlayersText.caption = numPlayers;
    961972            mapSizeText.caption = g_MapSizes.names[sizeIdx];
    962             revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
     973            revealMapText.caption = (mapSettings.RevealMap ? Engine.localize("Yes") : Engine.localize("No"));
    963974            victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    964             lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
     975            lockTeamsText.caption = (mapSettings.LockTeams ? Engine.localize("Yes") : Engine.localize("No"));
    965976        }
    966977
    967978        break;
    function onGameAttributesChange()  
    10351046        startingResourcesText.hidden = false;
    10361047
    10371048        numPlayersText.caption = numPlayers;
    1038         mapSizeText.caption = "Default";
    1039         revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
     1049        mapSizeText.caption = Engine.localize("Default");
     1050        revealMapText.caption = (mapSettings.RevealMap ? Engine.localize("Yes") : Engine.localize("No"));
    10401051        victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    1041         lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
     1052        lockTeamsText.caption = (mapSettings.LockTeams ? Engine.localize("Yes") : Engine.localize("No"));
    10421053        getGUIObjectByName("populationCap").selected = POPULATION_CAP_DEFAULTIDX;
    10431054
    10441055        break;
    10451056
    10461057    default:
    1047         error("onGameAttributesChange: Unexpected map type '"+g_GameAttributes.mapType+"'");
     1058        error(sprintf(Engine.localize("onGameAttributesChange: Unexpected map type '%(mapType)s'"), { mapType: g_GameAttributes.mapType }));
    10481059        return;
    10491060    }
    10501061
    function onGameAttributesChange()  
    10521063    getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);
    10531064   
    10541065    // Load the description from the map file, if there is one
    1055     var description = mapSettings.Description || "Sorry, no description available.";
     1066    var description = mapSettings.Description || Engine.localize("Sorry, no description available.");
    10561067
    1057     if (g_GameAttributes.mapFilter == "Naval Maps")
     1068    if (g_GameAttributes.mapFilter == "naval")
    10581069        description += g_NavalWarning;
    10591070
    10601071    // Describe the number of players
    1061     var playerString = numPlayers + " " + (numPlayers == 1 ? "player" : "players") + ". ";
     1072    var playerString = sprintf(Engine.localizePlural("%(number)s player. %(description)s", "%(number)s players. %(description)s", numPlayers), { number: numPlayers, description: description });
    10621073
    10631074    for (var i = 0; i < MAX_PLAYERS; ++i)
    10641075    {
    function onGameAttributesChange()  
    10971108            pTeam.hidden = true;
    10981109            // Set text values
    10991110            if (civ == "random")
    1100                 pCivText.caption = "[color=\"orange\"]Random";
     1111                pCivText.caption = g_randomCivilizationString;
    11011112            else
    11021113                pCivText.caption = g_CivData[civ].Name;
    11031114            pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
    function onGameAttributesChange()  
    11141125        }
    11151126    }
    11161127
    1117     getGUIObjectByName("mapInfoDescription").caption = playerString + description;
     1128    getGUIObjectByName("mapInfoDescription").caption = playerString;
    11181129
    11191130    g_IsInGuiUpdate = false;
    11201131
    function updatePlayerList()  
    11781189        }
    11791190        // Give AI a different color so it stands out
    11801191        aiAssignments[ai.id] = hostNameList.length;
    1181         hostNameList.push("[color=\"70 150 70 255\"]AI: " + ai.data.name);
     1192        hostNameList.push("[color=\"70 150 70 255\"]" + sprintf(Engine.localize("AI: %(ai)s"), { ai: Engine.localize(ai.data.name) }));
    11821193        hostGuidList.push("ai:" + ai.id);
    11831194    }
    11841195
    11851196    noAssignment = hostNameList.length;
    1186     hostNameList.push("[color=\"140 140 140 255\"]Unassigned");
     1197    hostNameList.push("[color=\"140 140 140 255\"]" + Engine.localize("Unassigned"));
    11871198    hostGuidList.push("");
    11881199
    11891200    for (var i = 0; i < MAX_PLAYERS; ++i)
    function updatePlayerList()  
    12101221                if (aiId in aiAssignments)
    12111222                    selection = aiAssignments[aiId];
    12121223                else
    1213                     warn("AI \""+aiId+"\" not present. Defaulting to unassigned.");
     1224                    warn(sprintf(Engine.localize("AI \"%(id)s\" not present. Defaulting to unassigned."), { id: aiId }));
    12141225            }
    12151226
    12161227            if (!selection)
    function addChatMessage(msg)  
    13851396    switch (msg.type)
    13861397    {
    13871398    case "connect":
    1388         formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has joined[/color]';
     1399        var formattedUsername = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
     1400        formatted = '[color="gold"]' + sprintf(Engine.localize("%(username)s has joined"), { username: formattedUsername });
    13891401        break;
    13901402
    13911403    case "disconnect":
    1392         formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has left[/color]';
     1404        var formattedUsername = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
     1405        formatted = '[color="gold"]' + sprintf(Engine.localize("%(username)s has left"), { username: formattedUsername });
    13931406        break;
    13941407
    13951408    case "message":
    1396         formatted = '[font="serif-bold-13"]<[color="'+ color +'"]' + username + '[/color]>[/font] ' + message;
     1409        var formattedUsername = '[color="'+ color +'"]' + username + '[/color]'
     1410        var formattedUsernamePrefix = '[font="serif-bold-13"]' + sprintf(Engine.localize("<%(username)s>"), { username: formattedUsername }) + '[/font]'
     1411        formatted = sprintf(Engine.localize("%(username)s %(message)s"), { username: formattedUsernamePrefix, message: message });
    13971412        break;
    13981413
    13991414    default:
    1400         error("Invalid chat message '" + uneval(msg) + "'");
     1415        error(sprintf(Engine.localize("Invalid chat message '%(message)s'"), { message: uneval(msg) }));
    14011416        return;
    14021417    }
    14031418
    function toggleMoreOptions()  
    14151430// Basic map filters API
    14161431
    14171432// Add a new map list filter
    1418 function addFilter(name, filterFunc)
     1433function addFilter(id, name, filterFunc)
    14191434{
    14201435    if (filterFunc instanceof Object)
    14211436    {   // Basic validity test
    14221437        var newFilter = {};
     1438        newFilter.id = id;
    14231439        newFilter.name = name;
    14241440        newFilter.filter = filterFunc;
    14251441
    function addFilter(name, filterFunc)  
    14271443    }
    14281444    else
    14291445    {
    1430         error("Invalid map filter: "+name);
     1446        error(sprintf(Engine.localize("Invalid map filter: %(name)s"), { name: name }));
    14311447    }
    14321448}
    14331449
     1450// Get array of map filter IDs
     1451function getFilterIds()
     1452{
     1453    var filters = [];
     1454    for (var i = 0; i < g_MapFilters.length; ++i)
     1455        filters.push(g_MapFilters[i].id);
     1456
     1457    return filters;
     1458}
     1459
    14341460// Get array of map filter names
    1435 function getFilters()
     1461function getFilterNames()
    14361462{
    14371463    var filters = [];
    14381464    for (var i = 0; i < g_MapFilters.length; ++i)
    function getFilters()  
    14421468}
    14431469
    14441470// Test map filter on given map settings object
    1445 function testFilter(name, mapSettings)
     1471function testFilter(id, mapSettings)
    14461472{
    14471473    for (var i = 0; i < g_MapFilters.length; ++i)
    1448         if (g_MapFilters[i].name == name)
     1474        if (g_MapFilters[i].id == id)
    14491475            return g_MapFilters[i].filter(mapSettings);
    14501476
    1451     error("Invalid map filter: "+name);
     1477    error(sprintf(Engine.localize("Invalid map filter: %(id)s"), { id: id }));
    14521478    return false;
    14531479}
    14541480
  • 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..a6e49d9 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            <localizableAttribute id="caption">Match Setup</localizableAttribute>
    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                <localizableAttribute id="caption">Loading</localizableAttribute>
    2222            </object>
    2323
    2424            <object type="text" style="CenteredLabelText">
    25                 Loading map data. Please wait...
     25                <localizableAttribute id="caption">Loading map data. Please wait...</localizableAttribute>
    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                        <localizableAttribute id="caption">Number of players:</localizableAttribute>
     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                        <localizableAttribute id="tooltip">Select number of players.</localizableAttribute>
    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                        <localizableAttribute id="caption">Player Name</localizableAttribute>
     66                    </object>
     67                    <object name="playerPlacementHeading" type="text" style="CenteredLabelText" size="25%+5 0 55% 100%">
     68                        <localizableAttribute id="caption">Player Placement</localizableAttribute>
     69                    </object>
     70                    <object name="playerCivHeading" type="text" style="CenteredLabelText" size="55%+65 0 85% 100%">
     71                        <localizableAttribute id="caption">Civilization</localizableAttribute>
     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                        <localizableAttribute id="tooltip">View civilization info</localizableAttribute>
    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                        <localizableAttribute id="caption">Team</localizableAttribute>
     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                                <localizableAttribute id="tooltip">Select player.</localizableAttribute>
     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                                <localizableAttribute id="caption">Settings</localizableAttribute>
     102                                <localizableAttribute id="tooltip">Configure AI settings.</localizableAttribute>
     103                            </object>
     104                            <object name="playerCiv[n]" type="dropdown" style="StoneDropDown" size="55%+65 2 85% 30" tooltip_style="onscreenToolTip">
     105                                <localizableAttribute id="tooltip">Select player's civilization.</localizableAttribute>
     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                                <localizableAttribute id="tooltip">Select player's team.</localizableAttribute>
     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                    <localizableAttribute id="caption">Match Type:</localizableAttribute>
     123                </object>
     124                <object type="text" style="RightLabelText" size="0 32 100% 62">
     125                    <localizableAttribute id="caption">Map Filter:</localizableAttribute>
     126                </object>
     127                <object type="text" style="RightLabelText" size="0 64 100% 94">
     128                    <localizableAttribute id="caption">Select Map:</localizableAttribute>
     129                </object>
     130                <object type="text" style="RightLabelText" size="0 96 100% 126">
     131                    <localizableAttribute id="caption">Map Size:</localizableAttribute>
     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                <localizableAttribute id="tooltip">Select a map type.</localizableAttribute>
    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                <localizableAttribute id="tooltip">Select a map filter.</localizableAttribute>
     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                    <localizableAttribute id="tooltip">Select a map to play on.</localizableAttribute>
    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                <localizableAttribute id="tooltip">Select map size. (Larger sizes may reduce performance.)</localizableAttribute>
     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                    <localizableAttribute id="caption">More Options</localizableAttribute>
     201                    <localizableAttribute id="tooltip">See more game options</localizableAttribute>
    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                    <localizableAttribute id="caption">More Options</localizableAttribute>
    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                            <localizableAttribute id="caption">Game Speed:</localizableAttribute>
     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                        <localizableAttribute id="tooltip">Select game speed.</localizableAttribute>
     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                            <localizableAttribute id="caption">Victory Condition:</localizableAttribute>
     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                        <localizableAttribute id="tooltip">Select victory condition.</localizableAttribute>
     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                            <localizableAttribute id="caption">Population Cap:</localizableAttribute>
     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                        <localizableAttribute id="tooltip">Select population cap.</localizableAttribute>
     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                            <localizableAttribute id="caption">Starting Resources:</localizableAttribute>
     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                        <localizableAttribute id="tooltip">Select the game's starting resources.</localizableAttribute>
     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                            <localizableAttribute id="caption">Reveal Map:</localizableAttribute>
     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                            <localizableAttribute id="caption">Teams Locked:</localizableAttribute>
     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                            <localizableAttribute id="caption">Cheats:</localizableAttribute>
     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                            <localizableAttribute id="tooltip">Toggle reveal map.</localizableAttribute>
     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                            <localizableAttribute id="tooltip">Toggle locked teams.</localizableAttribute>
     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                            <localizableAttribute id="tooltip">Toggle the usability of cheats.</localizableAttribute>
     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                    <localizableAttribute id="caption">OK</localizableAttribute>
     308                    <localizableAttribute id="tooltip">Close more game options window</localizableAttribute>
    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                <localizableAttribute id="caption">[Tooltip text]</localizableAttribute>
     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                <localizableAttribute id="caption">Start game!</localizableAttribute>
     352                <localizableAttribute id="tooltip">Start a new game with the current settings.</localizableAttribute>
    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                <localizableAttribute id="caption">Main menu</localizableAttribute>
     365                <localizableAttribute id="tooltip">Return to the main menu.</localizableAttribute>
    308366                <action on="Press">
    309367                    <![CDATA[
    310368                        cancelSetup();
  • 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..27fb66e 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(Engine.localize("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 = Engine.localize("Connecting to server…");
    3939}
    4040
    4141function onTick()
    function onTick()  
    4949        if (!message)
    5050            break;
    5151
    52         log("Net message: "+uneval(message));
     52        log(sprintf(Engine.localize("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(Engine.localize("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(Engine.localize("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 = Engine.localize("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 = Engine.localize("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(Engine.localize("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(Engine.localize("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(Engine.localize("Cannot host game: %(message)s."), { message: e.message }),
     160            Engine.localize("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(Engine.localize("Cannot join game: %(message)s."), { message: e.message }),
     181            Engine.localize("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..a69ce49 100644
    a b  
    1616        </action>
    1717
    1818        <object style="TitleText" type="text" size="50%-128 0%-16 50%+128 16">
    19             Multiplayer
     19            <localizableAttribute id="caption">Multiplayer</localizableAttribute>
    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                <localizableAttribute id="caption">Joining an existing game.</localizableAttribute>
    2626            </object>
    2727
    2828            <object type="text" size="0 40 200 70" style="RightLabelText">
    29                 Player name:
     29                <localizableAttribute id="caption">Player name:</localizableAttribute>
    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                <localizableAttribute id="caption">Server Hostname or IP:</localizableAttribute>
    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                <localizableAttribute id="caption">Continue</localizableAttribute>
    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                <localizableAttribute id="caption">Set up your server to host.</localizableAttribute>
    6666            </object>
    6767
    6868            <object type="text" size="0 40 200 70" style="RightLabelText">
    69                 Player name:
     69                <localizableAttribute id="caption">Player name:</localizableAttribute>
    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                <localizableAttribute id="caption">Server name:</localizableAttribute>
    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                <localizableAttribute id="caption">Continue</localizableAttribute>
    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            <localizableAttribute id="caption">Cancel</localizableAttribute>
    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                <localizableAttribute id="caption">[Connection status]</localizableAttribute>
    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..51d0047 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.localizeLines(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(Engine.localize("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(Engine.localize("Loading \"%(map)s\""), {map: mapName});
    5252            break;
    5353
    5454        case "random":
    55             loadingMapName.caption = "Generating \"" + mapName + "\"";
     55            loadingMapName.caption = sprintf(Engine.localize("Generating \"%(map)s\""), {map: mapName});
    5656            break;
    5757
    5858        default:
    59             error("Unknown map type: " + data.attribs.mapType);
     59            error(sprintf(Engine.localize("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 = Engine.localize(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..35dbedb 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                <localizableAttribute id="caption">Quote of the Day:</localizableAttribute>
     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..74e1020
    - +  
     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 saveSelectedLocale()
     15{
     16    var languageList = getGUIObjectByName("languageList");
     17    Engine.SetLocale(languageList.list_data[languageList.selected]);
     18    Engine.PopGuiPage();
     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..9b9cf7d
    - +  
     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            <localizableAttribute id="caption">Language</localizableAttribute>
     15        </object>
     16
     17        <object size="0 32 100% 100%">
     18            <object type="text" size="0 0 140 30" style="RightLabelText">
     19                <localizableAttribute id="caption">Language:</localizableAttribute>
     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                <localizableAttribute id="caption">Note: Restart the game for these changes to take effect.</localizableAttribute>
     30            </object>
     31
     32            <object type="button" size="50%-154 100%-60 50%+10 100%-32" style="StoneButton">
     33                <localizableAttribute id="caption">Accept</localizableAttribute>
     34                <action on="Press">saveSelectedLocale();</action>
     35            </object>
     36        </object>
     37
     38        <object type="button" style="StoneButton" size="50%+26 100%-60 50%+154 100%-32">
     39            <localizableAttribute id="caption">Cancel</localizableAttribute>
     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..b385202 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.localizeLines(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..f48fca8 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            <localizableAttribute id="caption">Manual</localizableAttribute>
     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            <localizableAttribute id="caption">Online Manual</localizableAttribute>
    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            <localizableAttribute id="caption">Close</localizableAttribute>
    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..d410a20
    - +  
     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 = [Engine.localize("Yes"), Engine.localize("No")];
     34        break;
     35    case 2:
     36        // Okay-only box
     37        data.buttonCaptions = [Engine.localize("OK")];
     38        break;
     39    case 3:
     40        // Retry/Abort/Ignore box (will we ever need this?!)
     41        data.buttonCaptions = [Engine.localize("Retry"), Engine.localize("Ignore"), Engine.localize("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..fa1d768 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"/>
    1818
    19         <!-- Settings Window -->
     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            <localizableAttribute id="caption">Game Options</localizableAttribute>
    2424        </object>
    2525
    26                 <object size="50%-190 50%-80 50%+140 50%+95">
     26        <object size="50%-190 50%-80 50%+140 50%+95">
    2727
    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>   
     28            <!-- Settings / shadows -->
     29            <object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">
     30                <localizableAttribute id="caption">Enable Shadows</localizableAttribute>
     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                <localizableAttribute id="caption">Enable Shadow Filtering</localizableAttribute>
     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                <localizableAttribute id="caption">Enable Music</localizableAttribute>
     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>
     66        </object>
    6167
    62                 <object type="button" style="StoneButton" size="50%-64 100%-64 50%+64 100%-32">
    63             Cancel
     68        <object type="button" style="StoneButton" size="50%-64 100%-64 50%+64 100%-32">
     69            <localizableAttribute id="caption">Cancel</localizableAttribute>
    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..4a69503 100644
    a b function formatUserReportStatus(status)  
    103103    var d = status.split(/:/, 3);
    104104
    105105    if (d[0] == "disabled")
    106         return "disabled";
     106        return Engine.localize("disabled");
    107107
    108108    if (d[0] == "connecting")
    109         return "connecting to server";
     109        return Engine.localize("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(Engine.localize("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 Engine.localize("upload succeeded");
    122122        else
    123             return "upload failed (" + httpCode + ")";
     123            return sprintf(Engine.localize("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(Engine.localize("upload failed (%{errorMessage}s)"), { errorMessage: errMessage });
    131131    }
    132132
    133     return "unknown";
     133    return Engine.localize("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(Engine.localize("%{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                Engine.localize("Please press \"Read More\" for more information or \"OK\" to continue."),
     173                Engine.localize("WARNING!"),
    171174                0,
    172                 ["Ok", "Read More"],
     175                [Engine.localize("OK"), Engine.localize("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(Engine.localize("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 = [Engine.localize("Yes"), Engine.localize("No")];
     335    var btCode = [exit, null];
     336    messageBox(400, 200, Engine.localize("Are you sure you want to quit 0 A.D.?"), Engine.localize("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, Engine.localize("The scenario editor is not available or failed to load."), Engine.localize("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..d8f9a20 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                    <localize>Help improve 0 A.D.!</localize>
     98                    <keep>[/font]\n\n</keep>
     99                    <localize>You can automatically send us anonymous feedback that will help us fix bugs, and improve performance and compatibility.</localize>
     100                </attribute>
    97101            </object>
    98102            <object type="button" style="StoneButton" size="8 100%-36 146 100%-8">
    99                 Enable feedback
     103                <localizableAttribute id="caption">Enable feedback</localizableAttribute>
    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                <localizableAttribute id="caption">Technical details</localizableAttribute>
    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                    <localize>Thank you for helping improve 0 A.D.!</localize>
     126                    <keep>[/font]\n\n</keep>
     127                    <localize>Anonymous feedback is currently enabled.</localize>
     128                    <keep>\n</keep>
     129                    <localize>Status: $status.</localize>
     130                </attribute>
     131            </object>
    123132
    124133            <object type="button" style="StoneButton" size="8 100%-36 146 100%-8">
    125                 Disable feedback
     134                <localizableAttribute id="caption">Disable feedback</localizableAttribute>
    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                <localizableAttribute id="caption">Technical details</localizableAttribute>
    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                    <localizableAttribute id="caption">Matches</localizableAttribute>
     178                    <localizableAttribute id="tooltip">Click here to start a new single player game.</localizableAttribute>
    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                    <localizableAttribute id="caption">Campaigns</localizableAttribute>
     192                    <localizableAttribute id="tooltip">Relive history through historical military campaigns. [NOT YET IMPLEMENTED]</localizableAttribute>
    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                    <localizableAttribute id="caption">Load Game</localizableAttribute>
     209                    <localizableAttribute id="tooltip">Click here to load a saved game.</localizableAttribute>
    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                    <localizableAttribute id="caption">Join Game</localizableAttribute>
     232                    <localizableAttribute id="tooltip">Joining an existing multiplayer game.</localizableAttribute>
    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                    <localizableAttribute id="caption">Host Game</localizableAttribute>
     247                    <localizableAttribute id="tooltip">Host a multiplayer game.\n\nRequires UDP port 20595 to be open.</localizableAttribute>
    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                    <localizableAttribute id="caption">Options</localizableAttribute>
     271                    <localizableAttribute id="tooltip">Adjust game settings. [NOT YET IMPLEMENTED]</localizableAttribute>
    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                    <localizableAttribute id="caption">Language</localizableAttribute>
     288                    <localizableAttribute id="tooltip">Choose the language of the game.</localizableAttribute>
    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                    <localizableAttribute id="caption">Scenario Editor</localizableAttribute>
     304                    <localizableAttribute 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;.</localizableAttribute>
     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                    <localizableAttribute id="caption">Learn To Play</localizableAttribute>
     358                    <localizableAttribute id="tooltip">The 0 A.D. Game Manual</localizableAttribute>
    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                    <localizableAttribute id="caption">Single Player</localizableAttribute>
     375                    <localizableAttribute id="tooltip">Challenge the computer player to a single player match.</localizableAttribute>
    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                    <localizableAttribute id="caption">Multiplayer</localizableAttribute>
     390                    <localizableAttribute id="tooltip">Fight against one or more human players in a multiplayer game.</localizableAttribute>
    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                    <localizableAttribute id="caption">Tools &amp; Options</localizableAttribute>
     405                    <localizableAttribute id="tooltip">Game options and scenario design tools.</localizableAttribute>
    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                    <localizableAttribute id="caption">History</localizableAttribute>
     420                    <localizableAttribute id="tooltip">Learn about the many civilizations featured in 0 A.D.</localizableAttribute>
    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                    <localizableAttribute id="caption">Exit</localizableAttribute>
     437                    <localizableAttribute id="tooltip">Exit Game</localizableAttribute>
     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                        <localize>Alpha XIV: Naukratis</localize>
     460                        <keep>[/font]\n\n</keep>
     461                        <localize>WARNING: This is an early development version of the game. Many features have not been added yet.</localize>
     462                        <keep>\n\n</keep>
     463                        <localize>Get involved at: play0ad.com</localize>
     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                    <localizableAttribute id="caption">Fundraiser</localizableAttribute>
     474                    <localizableAttribute id="tooltip">Click to view fundraiser information.</localizableAttribute>
    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                    <localizableAttribute id="caption">Website</localizableAttribute>
     488                    <localizableAttribute id="tooltip">Click to open play0ad.com in your web browser.</localizableAttribute>
    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                    <localizableAttribute id="caption">Chat</localizableAttribute>
     502                    <localizableAttribute id="tooltip">Click to open the 0 A.D. IRC chat in your browser. (#0ad on webchat.quakenet.org)</localizableAttribute>
    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                    <localizableAttribute id="caption">Report a Bug</localizableAttribute>
     516                    <localizableAttribute id="tooltip">Click to visit 0 A.D. Trac to report a bug, crash, or error</localizableAttribute>
    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                    <localizableAttribute id="caption">WILDFIRE GAMES</localizableAttribute>
     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..a21376b3 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 = [Engine.localize("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(Engine.localize("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 = [Engine.localize("Yes"), Engine.localize("No")];
    5757    var btCode = [function(){ reallyDeleteGame(gameID); }, null];
    5858    messageBox(500, 200, "\""+gameLabel+"\"\nSaved game will be permanently deleted, are you sure?", "DELETE", 0, btCaptions, btCode);
     59    messageBox(500, 200, sprintf(Engine.localize("\"%(label)s\""), { label: gameLabel }) + "\n" + Engine.localize("Saved game will be permanently deleted, are you sure?"), Engine.localize("DELETE"), 0, btCaptions, btCode);
    5960}
    6061
    6162function reallyDeleteGame(gameID)
    6263{
    6364    if (!Engine.DeleteSavedGame(gameID))
    6465        error("Could not delete saved game '"+gameID+"'");
     66        error(sprintf(Engine.localize("Could not delete saved game '%(id)s'"), { id: gameID }));
    6567
    6668    // Run init again to refresh saved game list
    6769    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..c280d72 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            <localizableAttribute id="caption">Load Game</localizableAttribute>
    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            <localizableAttribute id="caption">Load</localizableAttribute>
    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            <localizableAttribute id="caption">Delete</localizableAttribute>
    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            <localizableAttribute id="caption">Cancel</localizableAttribute>
    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..2cae368 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 = [Engine.localize("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 = [Engine.localize("Yes"), Engine.localize("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(Engine.localize("\"%(label)s\""), { label: gameLabel }) + "\n" + Engine.localize("Saved game will be permanently overwritten, are you sure?"), Engine.localize("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 = [Engine.localize("Yes"), Engine.localize("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(Engine.localize("\"%(label)s\""), { label: gameLabel }) + "\n" + Engine.localize("Saved game will be permanently deleted, are you sure?"), Engine.localize("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(Engine.localize("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..7cebc88 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            <localizableAttribute id="caption">Save Game</localizableAttribute>
    1616        </object>
    1717
    1818        <object name="gameSelection"
    1919            style="StoneList"
    2020            type="list"
    21            
    2221            size="24 24 100%-24 100%-124">
    2322            <action on="selectionchange">
    2423                selectDescription();
    2524            </action>
    2625        </object>
    2726
    28         <object size="24 100%-124 100%-24 100%-100" name="descLabel" type="text" style="LeftLabelText">Description:</object>
     27        <object size="24 100%-124 100%-24 100%-100" name="descLabel" type="text" style="LeftLabelText">
     28            <localizableAttribute id="caption">Description:</localizableAttribute>
     29        </object>
    2930
    3031        <object name="saveGameDesc" size="24 100%-96 100%-24 100%-72" type="input" style="StoneInput"/>
    3132
    3233        <object name="saveButton" type="button" size="0%+25 100%-60 33%+10 100%-32" style="StoneButton">
    33             Save
     34            <localizableAttribute id="caption">Save</localizableAttribute>
    3435            <action on="Press">saveGame();</action>
    3536        </object>
    3637
    3738        <object name="deleteGameButton" type="button" size="33%+20 100%-60 66%-15 100%-32" style="StoneButton">
    38             Delete
     39            <localizableAttribute id="caption">Delete</localizableAttribute>
    3940            <action on="Press">deleteGame();</action>
    4041        </object>
    4142
    4243        <object type="button" style="StoneButton" size="66%-5 100%-60 100%-25 100%-32">
    43             Cancel
     44            <localizableAttribute id="caption">Cancel</localizableAttribute>
    4445            <action on="Press">closeSave(true);</action>
    4546        </object>
    4647
  • 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 301a808..4596427 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(Engine.localize("Basic range: %(range)s"), { range: Math.round(cmd.range/4) }) + "\n" + sprintf(Engine.localize("Average bonus range: %(range)s"), { range: Math.round((averageRange - cmd.range)/4) });
    139139            }
    140140            return true;
    141141        }
    function getActionInfo(action, target)  
    243243            data.command = "garrison";
    244244            data.target = target;
    245245            cursor = "action-garrison";
    246             tooltip = "Current garrison: " + targetState.garrisonHolder.entities.length
    247                 + "/" + targetState.garrisonHolder.capacity;
     246            tooltip = sprintf(Engine.localize("Current garrison: %(garrisoned)s/%(capacity)s"), {
     247                garrisoned: targetState.garrisonHolder.entities.length,
     248                capacity: targetState.garrisonHolder.capacity
     249            });
    248250            if (targetState.garrisonHolder.entities.length >= targetState.garrisonHolder.capacity)
    249251                tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
    250252        }
    function getActionInfo(action, target)  
    293295                data.target = traderData.secondMarket;
    294296                data.source = traderData.firstMarket;
    295297                cursor = "action-setup-trade-route";
    296                 tooltip = "Right-click to establish a default route for new traders.";
     298                tooltip = Engine.localize("Right-click to establish a default route for new traders.");
    297299                if (trader)
    298                     tooltip += "\nGain (metal): " + getTradingTooltip(gain);
     300                    tooltip += "\n" + sprintf(Engine.localize("Gain (metal): %(gain)s"), { gain: getTradingTooltip(gain) });
    299301                else // Foundation or cannot produce traders
    300                     tooltip += "\nExpected gain (metal): " + getTradingTooltip(gain);
     302                    tooltip += "\n" + sprintf(Engine.localize("Expected gain (metal): %(gain)s"), { gain: getTradingTooltip(gain) });
    301303            }
    302304        }
    303305
    function getActionInfo(action, target)  
    335337        case "garrison":
    336338            if (hasClass(entState, "Unit") && targetState.garrisonHolder && (playerOwned || mutualAllyOwned))
    337339            {
    338                 var tooltip = "Current garrison: " + targetState.garrisonHolder.entities.length
    339                     + "/" + targetState.garrisonHolder.capacity;
     340                var tooltip = sprintf(Engine.localize("Current garrison: %(garrisoned)s/%(capacity)s"), {
     341                    garrisoned: targetState.garrisonHolder.entities.length,
     342                    capacity: targetState.garrisonHolder.capacity
     343                });
    340344                if (targetState.garrisonHolder.entities.length >= targetState.garrisonHolder.capacity)
    341345                    tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
    342346                var allowedClasses = targetState.garrisonHolder.allowedClasses;
    function getActionInfo(action, target)  
    362366                switch (tradingDetails.type)
    363367                {
    364368                case "is first":
    365                     tooltip = "Origin trade market.";
     369                    tooltip = Engine.localize("Origin trade market.");
    366370                    if (tradingDetails.hasBothMarkets)
    367                         tooltip += "\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
     371                        tooltip += "\n" + sprintf(Engine.localize("Gain (%(goods)s): %(gain)s"), {
     372                            goods: tradingDetails.goods,
     373                            gain: getTradingTooltip(tradingDetails.gain)
     374                        });
    368375                    else
    369                         tooltip += "\nRight-click on another market to set it as a destination trade market."
     376                        tooltip += "\n" + Engine.localize("Right-click on another market to set it as a destination trade market.")
    370377                    break;
    371378                case "is second":
    372                     tooltip = "Destination trade market.\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
     379                    tooltip = Engine.localize("Destination trade market.") + "\n" + sprintf(Engine.localize("Gain (%(goods)s): %(gain)s"), {
     380                        goods: tradingDetails.goods,
     381                        gain: getTradingTooltip(tradingDetails.gain)
     382                    });
    373383                    break;
    374384                case "set first":
    375                     tooltip = "Right-click to set as origin trade market";
     385                    tooltip = Engine.localize("Right-click to set as origin trade market");
    376386                    break;
    377387                case "set second":
    378                     tooltip = "Right-click to set as destination trade market.\nGain (" + tradingDetails.goods + "): " + getTradingTooltip(tradingDetails.gain);
     388                    tooltip = Engine.localize("Right-click to set as destination trade market.") + "\n" + sprintf(Engine.localize("Gain (%(goods)s): %(gain)s"), {
     389                        goods: tradingDetails.goods,
     390                        gain: getTradingTooltip(tradingDetails.gain)
     391                    });
    379392                    break;
    380393                }
    381394                return {"possible": true, "tooltip": tooltip};
    function tryPlaceBuilding(queued)  
    552565{
    553566    if (placementSupport.mode !== "building")
    554567    {
    555         error("[tryPlaceBuilding] Called while in '"+placementSupport.mode+"' placement mode instead of 'building'");
     568        error(sprintf(Engine.localize("[%(functionName)s] Called while in '%(mode)s' placement mode instead of 'building'"), {
     569            functionName: "tryPlaceBuilding",
     570            mode: placementSupport.mode
     571        }));
    556572        return false;
    557573    }
    558574
    function tryPlaceWall(queued)  
    593609{
    594610    if (placementSupport.mode !== "wall")
    595611    {
    596         error("[tryPlaceWall] Called while in '" + placementSupport.mode + "' placement mode; expected 'wall' mode");
     612        error(sprintf(Engine.localize("[%(functionName)s] Called while in '%(mode)s' placement mode; expected 'wall' mode"), {
     613            functionName: "tryPlaceWall",
     614            mode: placementSupport.mode
     615        }));
    597616        return false;
    598617    }
    599618   
    600619    var wallPlacementInfo = updateBuildingPlacementPreview(); // entities making up the wall (wall segments, towers, ...)
    601620    if (!(wallPlacementInfo === false || typeof(wallPlacementInfo) === "object"))
    602621    {
    603         error("[tryPlaceWall] Unexpected return value from updateBuildingPlacementPreview: '" + uneval(placementInfo) + "'; expected either 'false' or 'object'");
     622        error(sprintf(Engine.localize("[%(functionName)s] Unexpected return value from %(function2Name)s: '%(value)s'; expected either 'false' or 'object'"), {
     623            functionName: "tryPlaceWall",
     624            function2Name: "updateBuildingPlacementPreview",
     625            value: uneval(placementInfo)
     626        }));
    604627        return false;
    605628    }
    606629   
    function handleInputBeforeGui(ev, hoveredObject)  
    932955                    }
    933956                    else
    934957                    {
    935                         placementSupport.tooltipMessage = "Cannot build wall here!";
     958                        placementSupport.tooltipMessage = Engine.localize("Cannot build wall here!");
    936959                    }
    937960                   
    938961                    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..9d209d1 100644
    a b function resignMenuButton()  
    133133    closeMenu();
    134134    closeOpenDialogs();
    135135    pauseGame();
    136     var btCaptions = ["Yes", "No"];
     136    var btCaptions = [Engine.localize("Yes"), Engine.localize("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, Engine.localize("Are you sure you want to resign?"), Engine.localize("Confirmation"), 0, btCaptions, btCode);
    139139}
    140140
    141141function exitMenuButton()
    function exitMenuButton()  
    143143    closeMenu();
    144144    closeOpenDialogs();
    145145    pauseGame();
    146     var btCaptions = ["Yes", "No"];
     146    var btCaptions = [Engine.localize("Yes"), Engine.localize("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, Engine.localize("Are you sure you want to quit?"), Engine.localize("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 = [Engine.localize("Yes"), Engine.localize("No")];
    162162    var btCode = [deleteSelectedEntities, resumeGame];
    163163
    164     messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode);
     164    messageBox(400, 200, Engine.localize("Destroy everything currently selected?"), Engine.localize("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) ? Engine.localizeWithContext("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] ? Engine.localize("Ally") : (players[i].isNeutral[we] ? Engine.localize("Neutral") : Engine.localize("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 = Engine.localize("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 = Engine.localize("x");
    339339                else
    340340                    button.caption = "";
    341341            }
    342342            else // "enemy"
    343343            {
    344344                if (players[we].isEnemy[i])
    345                     button.caption = "x";
     345                    button.caption = Engine.localize("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    {
     425        submitChatDirectly(Engine.localize("The Developer Overlay was opened."));
     426    }
     427    else
     428    {
     429        submitChatDirectly(Engine.localize("The Developer Overlay was closed."));
     430    }
    426431    // Update the options dialog
    427432    getGUIObjectByName("developerOverlayCheckbox").checked = devCommands.hidden;
    428433    devCommands.hidden = !devCommands.hidden;
    function closeOpenDialogs()  
    439444function formatTributeTooltip(player, resource, amount)
    440445{
    441446    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) + ".";
     447    return sprintf(Engine.localize("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s. Shift-click to tribute %(greaterAmount)s."), {
     448        resourceAmount: amount,
     449        resourceType: resource,
     450        playerName: "[color=\"" + playerColor + "\"]" + player.name + "[/color]",
     451        greaterAmount: (amount < 500 ? 500 : amount + 500)
     452    });
    444453}
  • 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 708d053..f84a213 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(Engine.localize("Cheat name '%(name)s' is already present"), { name: currentCheat.Name }));
    2323        else
    2424            cheats[currentCheat.Name] = currentCheat.Data;
    2525    }
    function displayNotifications()  
    141141// Messages
    142142function handleNetMessage(message)
    143143{
    144     log("Net message: " + uneval(message));
     144    log(sprintf(Engine.localize("Net message: %(message)s"), { message: uneval(message) }));
    145145
    146146    switch (message.type)
    147147    {
    function handleNetMessage(message)  
    154154        switch (message.status)
    155155        {
    156156        case "waiting_for_players":
    157             obj.caption = "Waiting for other players to connect...";
     157            obj.caption = Engine.localize("Waiting for other players to connect...");
    158158            obj.hidden = false;
    159159            break;
    160160        case "join_syncing":
    161             obj.caption = "Synchronising gameplay with other players...";
     161            obj.caption = Engine.localize("Synchronising gameplay with other players...");
    162162            obj.hidden = false;
    163163            break;
    164164        case "active":
    function handleNetMessage(message)  
    166166            obj.hidden = true;
    167167            break;
    168168        case "connected":
    169             obj.caption = "Connected to the server.";
     169            obj.caption = Engine.localize("Connected to the server.");
    170170            obj.hidden = false;
    171171            break;
    172172        case "authenticated":
    173             obj.caption = "Connection to the server has been authenticated.";
     173            obj.caption = Engine.localize("Connection to the server has been authenticated.");
    174174            obj.hidden = false;
    175175            break;
    176176        case "disconnected":
    177177            g_Disconnected = true;
    178             obj.caption = "Connection to the server has been lost.\n\nThe game has ended.";
     178            obj.caption = Engine.localize("Connection to the server has been lost.") + "\n\n" + Engine.localize("The game has ended.");
    179179            obj.hidden = false;
    180180            break;
    181181        default:
    182             error("Unrecognised netstatus type "+message.status);
     182            error(sprintf(Engine.localize("Unrecognised netstatus type %(netType)s"), { netType: message.status }));
    183183            break;
    184184        }
    185185        break;
    function handleNetMessage(message)  
    225225        break;
    226226
    227227    default:
    228         error("Unrecognised net message type "+message.type);
     228        error(sprintf(Engine.localize("Unrecognised net message type %(messageType)s"), { messageType: message.type }));
    229229    }
    230230}
    231231
    function addChatMessage(msg, playerAssignments)  
    307307
    308308    var playerColor, username;
    309309
    310     // No prefix by default. May be set by parseChatCommands().
    311     msg.prefix = "";
     310    // No context by default. May be set by parseChatCommands().
     311    msg.context = "";
    312312
    313313    if (playerAssignments[msg.guid])
    314314    {
    function addChatMessage(msg, playerAssignments)  
    335335    else
    336336    {
    337337        playerColor = "255 255 255";
    338         username = "Unknown player";
     338        username = Engine.localize("Unknown player");
    339339    }
    340340
    341341    var message = escapeText(msg.text);
    function addChatMessage(msg, playerAssignments)  
    345345    switch (msg.type)
    346346    {
    347347    case "connect":
    348         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has joined the game.";
     348        formatted = sprintf(Engine.localize("%(player)s has joined the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    349349        break;
    350350    case "disconnect":
    351         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has left the game.";
     351        formatted = sprintf(Engine.localize("%(player)s has left the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    352352        break;
    353353    case "defeat":
    354354        // In singleplayer, the local player is "You". "You has" is incorrect.
    355         var verb = (!g_IsNetworked && msg.player == Engine.GetPlayerID()) ? "have" : "has";
    356         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] " + verb + " been defeated.";
     355        if (!g_IsNetworked && msg.player == Engine.GetPlayerID())
     356        {
     357            formatted = Engine.localize("You have been defeated.");
     358        }
     359        else
     360        {
     361            formatted = sprintf(Engine.localize("%(player)s has been defeated."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     362        }
    357363        break;
    358364    case "diplomacy":
    359365        var status = (msg.status == "ally" ? "allied" : (msg.status == "enemy" ? "at war" : "neutral"));
    function addChatMessage(msg, playerAssignments)  
    361367        {
    362368            username= escapeText(g_Players[msg.player1].name);
    363369            playerColor = g_Players[msg.player1].color.r + " " + g_Players[msg.player1].color.g + " " + g_Players[msg.player1].color.b;
    364             formatted = "You are now "+status+" with [color=\"" + playerColor + "\"]"+username + "[/color].";
     370            if (msg.status == "ally")
     371            {
     372                formatted = sprintf(Engine.localize("You are now allied with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     373            }
     374            else if (msg.status == "enemy")
     375            {
     376                formatted = sprintf(Engine.localize("You are now at war with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     377            }
     378            else
     379            {
     380                formatted = sprintf(Engine.localize("You are now neutral with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     381            }
    365382        }
    366383        else if (msg.player1 == Engine.GetPlayerID())
    367384        {
    368385            username= escapeText(g_Players[msg.player].name);
    369386            playerColor = g_Players[msg.player].color.r + " " + g_Players[msg.player].color.g + " " + g_Players[msg.player].color.b;
    370             formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] is now " + status + " with you."
     387            if (msg.status == "ally")
     388            {
     389                formatted = sprintf(Engine.localize("%(player)s is now allied with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     390            }
     391            else if (msg.status == "enemy")
     392            {
     393                formatted = sprintf(Engine.localize("%(player)s is now at war with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     394            }
     395            else
     396            {
     397                formatted = sprintf(Engine.localize("%(player)s is now neutral with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     398            }
    371399        }
    372400        else // No need for other players to know of this.
    373401            return;
    function addChatMessage(msg, playerAssignments)  
    387415        if (amounts.length > 1)
    388416        {
    389417            var lastAmount = amounts.pop();
    390             amounts = amounts.join(", ") + " and " + lastAmount;
     418            amounts = sprintf(Engine.localize("%(previousAmounts)s and %(lastAmount)s"), {
     419                previousAmounts: amounts.join(Engine.localize(", ")),
     420                lastAmount: lastAmount
     421            });
    391422        }
    392423
    393         formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has sent you " + amounts + ".";
     424        formatted = sprintf(Engine.localize("%(player)s has sent you %(amounts)s."), {
     425            player: "[color=\"" + playerColor + "\"]" + username + "[/color]",
     426            amounts: amounts
     427        });
    394428        break;
    395429    case "attack":
    396430        if (msg.player != Engine.GetPlayerID())
    function addChatMessage(msg, playerAssignments)  
    405439
    406440        if (msg.action)
    407441        {
    408             Engine.Console_Write(msg.prefix + "* " + username + " " + message);
    409             formatted = msg.prefix + "* [color=\"" + playerColor + "\"]" + username + "[/color] " + message;
     442            if (msg.context !== "")
     443            {
     444                Engine.Console_Write(sprintf(Engine.localize("(%(context)s) * %(user)s %(message)s"), {
     445                    context: msg.context,
     446                    user: username,
     447                    message: message
     448                }));
     449                formatted = sprintf(Engine.localize("(%(context)s) * %(user)s %(message)s"), {
     450                    context: msg.context,
     451                    user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
     452                    message: message
     453                });
     454            }
     455            else
     456            {
     457                Engine.Console_Write(sprintf(Engine.localize("* %(user)s %(message)s"), {
     458                    user: username,
     459                    message: message
     460                }));
     461                formatted = sprintf(Engine.localize("* %(user)s %(message)s"), {
     462                    user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
     463                    message: message
     464                });
     465            }
    410466        }
    411467        else
    412468        {
    413             Engine.Console_Write(msg.prefix + "<" + username + "> " + message);
    414             formatted = msg.prefix + "<[color=\"" + playerColor + "\"]" + username + "[/color]> " + message;
     469            var userTag = sprintf(Engine.localize("<%(user)s>"), { user: username })
     470            var formattedUserTag = sprintf(Engine.localize("<%(user)s>"), { user: "[color=\"" + playerColor + "\"]" + username + "[/color]" })
     471            if (msg.context !== "")
     472            {
     473                Engine.Console_Write(sprintf(Engine.localize("(%(context)s) %(userTag)s %(message)s"), {
     474                    context: msg.context,
     475                    userTag: userTag,
     476                    message: message
     477                }));
     478                formatted = sprintf(Engine.localize("(%(context)s) %(userTag)s %(message)s"), {
     479                    context: msg.context,
     480                    userTag: formattedUserTag,
     481                    message: message
     482                });
     483            }
     484            else
     485            {
     486                Engine.Console_Write(sprintf(Engine.localize("%(userTag)s %(message)s"), { userTag: userTag, message: message}));
     487                formatted = sprintf(Engine.localize("%(userTag)s %(message)s"), { userTag: formattedUserTag, message: message});
     488            }
    415489        }
    416490        break;
    417491    default:
    418         error("Invalid chat message '" + uneval(msg) + "'");
     492        error(sprintf(Engine.localize("Invalid chat message '%(message)s'"), { message: uneval(msg) }));
    419493        return;
    420494    }
    421495
    function parseChatCommands(msg, playerAssignments)  
    457531    {
    458532    case "/all":
    459533        // Resets values that 'team' or 'enemy' may have set.
    460         msg.prefix = "";
     534        msg.context = "";
    461535        msg.hide = false;
    462536        recurse = true;
    463537        break;
    function parseChatCommands(msg, playerAssignments)  
    468542            if (g_Players[Engine.GetPlayerID()].team != g_Players[sender].team)
    469543                msg.hide = true;
    470544            else
    471                 msg.prefix = "(Team) ";
     545                msg.context = Engine.localize("Team");
    472546        }
    473547        else
    474548            msg.hide = true;
    function parseChatCommands(msg, playerAssignments)  
    481555            if (g_Players[Engine.GetPlayerID()].team == g_Players[sender].team && sender != Engine.GetPlayerID())
    482556                msg.hide = true;
    483557            else
    484                 msg.prefix = "(Enemy) ";
     558                msg.context = Engine.localize("Enemy");
    485559        }
    486560        recurse = true;
    487561        break;
    function parseChatCommands(msg, playerAssignments)  
    501575        var playerName = g_Players[Engine.GetPlayerID()].name;
    502576        if (matched.length && (matched == playerName || sender == Engine.GetPlayerID()))
    503577        {
    504             msg.prefix = "(Private) ";
     578            msg.context = Engine.localize("Private");
    505579            msg.text = trimmed.substr(matched.length + 1);
    506580            msg.hide = false; // Might override team message hiding.
    507581            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..ae16124 100644
    a b  
     1var localizedFish = Engine.localize("Fish");
     2var localizedFood = Engine.localize("Food");
     3var localizedFruit = Engine.localize("Fruit");
     4var localizedGrain = Engine.localize("Grain");
     5var localizedMeat = Engine.localize("Meat");
     6var localizedMetal = Engine.localize("Metal");
     7var localizedOre = Engine.localize("Ore");
     8var localizedRock = Engine.localize("Rock");
     9var localizedRuins = Engine.localize("Ruins");
     10var localizedStone = Engine.localize("Stone");
     11var localizedTreasure = Engine.localize("Treasure");
     12var localizedTree = Engine.localize("Tree");
     13var localizedWood = Engine.localize("Wood");
     14
    115function layoutSelectionSingle()
    216{
    317    getGUIObjectByName("detailsAreaSingle").hidden = false;
    function layoutSelectionMultiple()  
    1024    getGUIObjectByName("detailsAreaSingle").hidden = true;
    1125}
    1226
     27function getLocalizedResourceName(resourceCode)
     28{
     29    switch(resourceCode)
     30    {
     31        case "food": return localizedFood;
     32        case "meat": return localizedMeat;
     33        case "metal": return localizedMetal;
     34        case "ore": return localizedOre;
     35        case "rock": return localizedRock;
     36        case "ruins": return localizedRuins;
     37        case "stone": return localizedStone;
     38        case "treasure": return localizedTreasure;
     39        case "tree": return localizedTree;
     40        case "wood": return localizedWood;
     41        case "fruit": return localizedFruit;
     42        case "grain": return localizedGrain;
     43        case "fish": return localizedFish;
     44        default:
     45            warn(sprintf(Engine.localize("Internationalization: unexpected resource type found with code ‘%(resource)s’. This resource type must be internationalized."), { resource: resourceCode }));
     46            return resourceCode; // It should never get here.
     47    }
     48}
     49
     50function getResourceTypeDisplayName(resourceType)
     51{
     52    var resourceCode = resourceType["generic"];
     53    var displayName = "";
     54    if (resourceCode == "treasure")
     55    {
     56        displayName = getLocalizedResourceName(resourceType["specific"]);
     57    }
     58    else
     59    {
     60        displayName = getLocalizedResourceName(resourceCode);
     61    }
     62    return displayName;
     63}
     64
    1365// Fills out information that most entities have
    1466function displaySingle(entState, template)
    1567{
    function displaySingle(entState, template)  
    1870    var genericName = template.name.generic != template.name.specific? template.name.generic : "";
    1971    // If packed, add that to the generic name (reduces template clutter)
    2072    if (genericName && template.pack && template.pack.state == "packed")
    21         genericName += " -- Packed";
     73        genericName = sprintf(Engine.localize("%(genericName)s — Packed"), { genericName: genericName });
    2274    var playerState = g_Players[entState.player];
    2375
    2476    var civName = g_CivData[playerState.civ].Name;
    function displaySingle(entState, template)  
    3082    // Indicate disconnected players by prefixing their name
    3183    if (g_Players[entState.player].offline)
    3284    {
    33         playerName = "[OFFLINE] " + playerName;
     85        playerName = sprintf(Engine.localize("[OFFLINE] %(player)s"), { player: playerName });
    3486    }
    3587
    3688    // Rank
    3789    if (entState.identity && entState.identity.rank && entState.identity.classes)
    3890    {
    39         getGUIObjectByName("rankIcon").tooltip = entState.identity.rank + " Rank";
     91        getGUIObjectByName("rankIcon").tooltip = sprintf(Engine.localize("%(rank)s Rank"), { rank: entState.identity.rank });
    4092        getGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);
    4193        getGUIObjectByName("rankIcon").hidden = false;
    4294    }
    function displaySingle(entState, template)  
    54106        healthSize.rright = 100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
    55107        unitHealthBar.size = healthSize;
    56108
    57         var hitpoints = Math.ceil(entState.hitpoints) + " / " + entState.maxHitpoints;
    58         getGUIObjectByName("healthStats").caption = hitpoints;
     109        getGUIObjectByName("healthStats").caption = sprintf(Engine.localize("%(hitpoints)s / %(maxHitpoints)s"), {
     110            hitpoints: Math.ceil(entState.hitpoints),
     111            maxHitpoints: entState.maxHitpoints
     112        });
    59113        getGUIObjectByName("healthSection").hidden = false;
    60114    }
    61115    else
    function displaySingle(entState, template)  
    84138 
    85139        var experience = "[font=\"serif-bold-13\"]Experience: [/font]" + Math.floor(entState.promotion.curr);
    86140        if (entState.promotion.curr < entState.promotion.req)
    87             experience += " / " + entState.promotion.req;
    88         getGUIObjectByName("experience").tooltip = experience;
     141        {
     142            getGUIObjectByName("experience").tooltip = sprintf(Engine.localize("%(experience)s %(current)s / %(required)s"), {
     143                experience: "[font=\"serif-bold-13\"]" + Engine.localize("Experience:") + "[/font]",
     144                current: Math.floor(entState.promotion.curr),
     145                required: entState.promotion.req
     146            });
     147        }
     148        else
     149        {
     150            getGUIObjectByName("experience").tooltip = sprintf(Engine.localize("%(experience)s %(current)s"), {
     151                experience: "[font=\"serif-bold-13\"]" + Engine.localize("Experience:") + "[/font]",
     152                current: Math.floor(entState.promotion.curr)
     153            });
     154        }
    89155        getGUIObjectByName("experience").hidden = false;
    90156    }
    91157    else
    function displaySingle(entState, template)  
    96162    // Resource stats
    97163    if (entState.resourceSupply)
    98164    {
    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"];
     165        var resources = entState.resourceSupply.isInfinite ? Engine.localize("∞") :  // Infinity symbol
     166                        sprintf(Engine.localize("%(amount)s / %(max)s"), { amount: Math.ceil(+entState.resourceSupply.amount), max: entState.resourceSupply.max });
     167        var resourceType = getResourceTypeDisplayName(entState.resourceSupply.type);
    104168
    105169        var unitResourceBar = getGUIObjectByName("resourceBar");
    106170        var resourceSize = unitResourceBar.size;
    function displaySingle(entState, template)  
    108172        resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
    109173                        100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
    110174        unitResourceBar.size = resourceSize;
    111         getGUIObjectByName("resourceLabel").caption = toTitleCase(resourceType) + ":";
     175        getGUIObjectByName("resourceLabel").caption = sprintf(Engine.localize("%(resource)s:"), { resource: resourceType });
    112176        getGUIObjectByName("resourceStats").caption = resources;
    113177
    114178        if (entState.hitpoints)
    function displaySingle(entState, template)  
    132196        getGUIObjectByName("resourceCarryingIcon").hidden = false;
    133197        getGUIObjectByName("resourceCarryingText").hidden = false;
    134198        getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
    135         getGUIObjectByName("resourceCarryingText").caption = carried.amount + " / " + carried.max;
     199        getGUIObjectByName("resourceCarryingText").caption = sprintf(Engine.localize("%(amount)s / %(max)s"), { amount: carried.amount, max: carried.max });
    136200        getGUIObjectByName("resourceCarryingIcon").tooltip = "";
    137201    }
    138202    // Use the same indicators for traders
    function displaySingle(entState, template)  
    147211        if (entState.trader.goods.amount.market2Gain)
    148212            totalGain += entState.trader.goods.amount.market2Gain;
    149213        getGUIObjectByName("resourceCarryingText").caption = totalGain;
    150         getGUIObjectByName("resourceCarryingIcon").tooltip = "Gain: " + getTradingTooltip(entState.trader.goods.amount);
     214        getGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(Engine.localize("Gain: %(amount)s"), { amount: getTradingTooltip(entState.trader.goods.amount) });
    151215    }
    152216    // And for number of workers
    153217    else if (entState.foundation)
    function displaySingle(entState, template)  
    156220        getGUIObjectByName("resourceCarryingText").hidden = false;
    157221        getGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
    158222        getGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + "    ";
    159         getGUIObjectByName("resourceCarryingIcon").tooltip = "Number of builders";
     223        getGUIObjectByName("resourceCarryingIcon").tooltip = Engine.localize("Number of builders");
    160224    }
    161225    else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
    162226    {
    163227        getGUIObjectByName("resourceCarryingIcon").hidden = false;
    164228        getGUIObjectByName("resourceCarryingText").hidden = false;
    165229        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";
     230        getGUIObjectByName("resourceCarryingText").caption = sprintf(Engine.localize("%(amount)s / %(max)s"), { amount: entState.resourceSupply.gatherers.length, max: entState.resourceSupply.maxGatherers }) + "    ";
     231        getGUIObjectByName("resourceCarryingIcon").tooltip = Engine.localize("Current/max gatherers");
    168232    }
    169233    else
    170234    {
    function displaySingle(entState, template)  
    179243   
    180244    if (genericName)
    181245    {
    182         getGUIObjectByName("generic").caption = "(" + genericName + ")";
     246        getGUIObjectByName("generic").caption = sprintf(Engine.localize("(%(genericName)s)"), { genericName: genericName });
    183247    }
    184248    else
    185249    {
    function displaySingle(entState, template)  
    209273        getGUIObjectByName("icon").sprite = "bkFillBlack";
    210274    }
    211275
     276    var armorLabel = "[font=\"serif-bold-13\"]" + Engine.localize("Armor:") + "[/font]"
     277    var armorString = sprintf(Engine.localize("%(label)s %(details)s"), { label: armorLabel, details: armorTypeDetails(entState.armour) });
     278
    212279    // Attack and Armor
    213     var type = "";
    214     var attack = "[font=\"serif-bold-13\"]"+type+"Attack:[/font] " + damageTypeDetails(entState.attack);
    215280    if (entState.attack)
    216281    {
    217         type = entState.attack.type + " ";
    218 
    219         // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     282        var attack;
     283        var label = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(entState.attack.type) + "[/font]"
    220284        if (entState.attack.type == "Ranged")
    221285        {
     286            // Show max attack range if ranged attack, also convert to tiles (4m per tile)
    222287            var realRange = entState.attack.elevationAdaptedRange;
    223288            var range =  entState.attack.maxRange;
    224             attack += ", [font=\"serif-bold-13\"]Range:[/font] " +
    225                 Math.round(range/4);
     289            var rangeLabel = "[font=\"serif-bold-13\"]" + Engine.localize("Range:") + "[/font]"
    226290
    227291            if (Math.round((realRange - range)/4) > 0)
    228292            {
    229                 attack += " (+" + Math.round((realRange - range)/4) + ")";
     293                attack = sprintf(Engine.localize("%(label)s %(details)s, %(rangeLabel)s %(range)s (%(relative)s)"), {
     294                    label: label,
     295                    details: damageTypeDetails(entState.attack),
     296                    rangeLabel: rangeLabel,
     297                    range: Math.round(range/4),
     298                    relative: "+" + Math.round((realRange - range)/4)
     299                });
    230300            }
    231301            else if (Math.round((realRange - range)/4) < 0)
    232302            {
    233                 attack += " (" + Math.round((realRange - range)/4) + ")";
     303                attack = sprintf(Engine.localize("%(label)s %(details)s, %(rangeLabel)s %(range)s"), {
     304                    label: label,
     305                    details: damageTypeDetails(entState.attack),
     306                    rangeLabel: rangeLabel,
     307                    range: Math.round(range/4),
     308                    relative: Math.round((realRange - range)/4)
     309                });
    234310            } // don't show when it's 0
    235 
     311            else
     312            {
     313                attack = sprintf(Engine.localize("%(label)s %(details)s, %(rangeLabel)s %(range)s"), {
     314                    label: label,
     315                    details: damageTypeDetails(entState.attack),
     316                    rangeLabel: rangeLabel,
     317                    range: Math.round(range/4)
     318                });
     319            }
    236320        }
     321        else
     322        {
     323            attack = sprintf(Engine.localize("%(label)s %(details)s"), {
     324                label: label,
     325                details: damageTypeDetails(entState.attack)
     326            });
     327        }
     328        getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n" + armorString;
     329    }
     330    else
     331    {
     332        getGUIObjectByName("attackAndArmorStats").tooltip = armorString;
    237333    }
    238    
    239     getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
    240334
    241335    // Icon Tooltip
    242336    var iconTooltip = "";
    function displayMultiple(selection, template)  
    280374        healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
    281375        unitHealthBar.size = healthSize;
    282376
    283         var hitpoints = "[font=\"serif-bold-13\"]Hitpoints [/font]" + averageHealth + " / " + maxHealth;
     377        var hitpointsLabel = "[font=\"serif-bold-13\"]" + Engine.localize("Hitpoints:") + "[/font]"
     378        var hitpoints = sprintf(Engine.localize("%(label)s %(current)s / %(max)s"), { label: hitpointsLabel, current: averageHealth, max: maxHealth });
    284379        var healthMultiple = getGUIObjectByName("healthMultiple");
    285380        healthMultiple.tooltip = hitpoints;
    286381        healthMultiple.hidden = false;
  • 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 365753e..71a4130 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": Engine.localize("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        localizeObjectKeys(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        localizeObjectKeys(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": Engine.localize("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 = Engine.localize("You have been disconnected.");
    222236    }
    223237    else if (playerState.state == "won")
    224238    {
    225         gameResult = "You have won the battle!";
     239        gameResult = Engine.localize("You have won the battle!");
    226240    }
    227241    else if (playerState.state == "defeated")
    228242    {
    229         gameResult = "You have been defeated...";
     243        gameResult = Engine.localize("You have been defeated...");
    230244    }
    231245    else // "active"
    232246    {
    233         gameResult = "You have abandoned the game.";
     247        gameResult = Engine.localize("You have abandoned the game.");
    234248
    235249        // Tell other players that we have given up and been defeated
    236250        Engine.PostNetworkCommand({
    function checkPlayerState()  
    356370            if (Engine.IsAtlasRunning())
    357371            {
    358372                // If we're in Atlas, we can't leave the game
    359                 var btCaptions = ["OK"];
     373                var btCaptions = [Engine.localize("OK")];
    360374                var btCode = [null];
    361                 var message = "Press OK to continue";
     375                var message = Engine.localize("Press OK to continue");
    362376            }
    363377            else
    364378            {
    365                 var btCaptions = ["Yes", "No"];
     379                var btCaptions = [Engine.localize("Yes"), Engine.localize("No")];
    366380                var btCode = [leaveGame, null];
    367                 var message = "Do you want to quit?";
     381                var message = Engine.localize("Do you want to quit?");
    368382            }
    369             messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode);
     383            messageBox(400, 200, message, Engine.localize("DEFEATED!"), 0, btCaptions, btCode);
    370384        }
    371385        else if (playerState.state == "won")
    372386        {
    function checkPlayerState()  
    382396            if (Engine.IsAtlasRunning())
    383397            {
    384398                // If we're in Atlas, we can't leave the game
    385                 var btCaptions = ["OK"];
     399                var btCaptions = [Engine.localize("OK")];
    386400                var btCode = [null];
    387                 var message = "Press OK to continue";
     401                var message = Engine.localize("Press OK to continue");
    388402            }
    389403            else
    390404            {
    391                 var btCaptions = ["Yes", "No"];
     405                var btCaptions = [Engine.localize("Yes"), Engine.localize("No")];
    392406                var btCode = [leaveGame, null];
    393                 var message = "Do you want to quit?";
     407                var message = Engine.localize("Do you want to quit?");
    394408            }
    395             messageBox(400, 200, message, "VICTORIOUS!", 0, btCaptions, btCode);
     409            messageBox(400, 200, message, Engine.localize("VICTORIOUS!"), 0, btCaptions, btCode);
    396410        }
    397411    }
    398412}
    function updateHero()  
    473487
    474488    // Setup tooltip
    475489    var tooltip = "[font=\"serif-bold-16\"]" + template.name.specific + "[/font]";
    476     tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + heroState.hitpoints + "/" + heroState.maxHitpoints;
    477     tooltip += "\n[font=\"serif-bold-13\"]" + (heroState.attack ? heroState.attack.type + " " : "")
    478                + "Attack:[/font] " + damageTypeDetails(heroState.attack);
    479     // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     490    var healthLabel = "[font=\"serif-bold-13\"]" + Engine.localize("Health:") + "[/font]";
     491    tooltip += "\n" + sprintf(Engine.localize("%(label)s %(current)s / %(max)s"), { label: healthLabel, current: heroState.hitpoints, max: heroState.maxHitpoints });
     492    var attackLabel = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(heroState.attack.type) + "[/font]";
    480493    if (heroState.attack && heroState.attack.type == "Ranged")
    481         tooltip += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(heroState.attack.maxRange/4);
     494    {
     495        var rangeLabel = "[font=\"serif-bold-13\"]" + Engine.localize("Range:") + "[/font]";
     496        // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     497        tooltip += "\n" + sprintf(Engine.localize("%(attackLabel)s %(details)s, %(rangeLabel)s %(range)s"), { attackLabel: attackLabel, details: damageTypeDetails(heroState.attack), rangeLabel: rangeLabel, range: Math.round(heroState.attack.maxRange/4) });
     498    }
     499    else
     500    {
     501        tooltip += "\n" + sprintf(Engine.localize("%(label)s %(details)s"), { label: attackLabel, details: damageTypeDetails(heroState.armour) });
     502    }
    482503
    483     tooltip += "\n[font=\"serif-bold-13\"]Armor:[/font] " + damageTypeDetails(heroState.armour);
     504    var armorLabel = "[font=\"serif-bold-13\"]" + Engine.localize("Armor:") + "[/font]";
     505    tooltip += "\n" + sprintf(Engine.localize("%(label)s %(details)s"), { label: armorLabel, details: damageTypeDetails(heroState.attack) });
    484506    tooltip += "\n" + template.tooltip;
    485507
    486508    heroButton.tooltip = tooltip;
    function updateResearchDisplay()  
    626648function updateTimeElapsedCounter()
    627649{
    628650    var simState = GetSimState();
    629     var speed = g_CurrentSpeed != 1.0 ? " (" + g_CurrentSpeed + "x)" : "";
    630651    var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter");
    631     timeElapsedCounter.caption = timeToString(simState.timeElapsed) + speed;
     652    if (g_CurrentSpeed != 1.0)
     653    {
     654        timeElapsedCounter.caption = sprintf(Engine.localize("%(time)s (%(speed)sx)"), { time: timeToString(simState.timeElapsed), speed: Engine.formatDecimalNumberIntoString(g_CurrentSpeed) });
     655    }
     656    else
     657    {
     658        timeElapsedCounter.caption = timeToString(simState.timeElapsed);
     659    }
    632660}
    633661
    634662// Toggles the display of status bars for all of the player's entities.
    function playRandomAmbient(type)  
    668696            break;
    669697
    670698        default:
    671             Engine.Console_Write("Unrecognized ambient type: " + type);
     699            Engine.Console_Write(sprintf(Engine.localize("Unrecognized ambient type: %(ambientType)s"), { ambientType: type }));
    672700            break;
    673701    }
    674702}
    function stopAmbient()  
    682710        currentAmbient = null;
    683711    }
    684712}
     713
     714function getBuildString()
     715{
     716    return sprintf(Engine.localize("Build: %(buildDate)s (%(revision)s)"), { buildDate: Engine.GetBuildTimestamp(0), revision: Engine.GetBuildTimestamp(2) });
     717}
     718
     719function showTimeWarpMessageBox()
     720{
     721    messageBox(500, 250, Engine.localize("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."), Engine.localize("Time warp mode"), 2);
     722}
  • 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..6656de8 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            <localizableAttribute id="caption">Control all units</localizableAttribute>
     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            <localizableAttribute id="caption">Change perspective</localizableAttribute>
     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            <localizableAttribute id="caption">Display selection state</localizableAttribute>
     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            <localizableAttribute id="caption">Pathfinder overlay</localizableAttribute>
     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            <localizableAttribute id="caption">Obstruction overlay</localizableAttribute>
     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            <localizableAttribute id="caption">Unit motion overlay</localizableAttribute>
     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            <localizableAttribute id="caption">Range overlay</localizableAttribute>
     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            <localizableAttribute id="caption">Bounding box overlay</localizableAttribute>
     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            <localizableAttribute id="caption">Restrict camera</localizableAttribute>
     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            <localizableAttribute id="caption">Reveal map</localizableAttribute>
     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            <localizableAttribute id="caption">Enable time warp</localizableAttribute>
     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            <localizableAttribute id="caption">Promote selected units</localizableAttribute>
     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            <localizableAttribute id="caption">Game Paused</localizableAttribute>
     364        </object>
     365        <object size="50%-128 50%+20 50%+128 50%+30" type="text" style="PauseMessageText" ghost="true" z="0">
     366            <localizableAttribute id="caption">Click to Resume Game</localizableAttribute>
     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            <localizableAttribute id="caption">Send</localizableAttribute>
     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            <localizableAttribute id="caption">Cancel</localizableAttribute>
     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            <localizableAttribute id="caption">Diplomacy</localizableAttribute>
     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                <localizableAttribute id="caption">Name</localizableAttribute>
     424            </object>
     425            <object name="diplomacyHeaderCiv" size="150 0 250 100%" type="text" style="chatPanel" ghost="true">
     426                <localizableAttribute id="caption">Civilization</localizableAttribute>
     427            </object>
     428            <object name="diplomacyHeaderTeam" size="250 0 300 100%" type="text" style="chatPanel" ghost="true">
     429                <localizableAttribute id="caption">Team</localizableAttribute>
     430            </object>
     431            <object name="diplomacyHeaderTheirs" size="300 0 360 100%" type="text" style="chatPanel" ghost="true">
     432                <localizableAttribute id="caption">Theirs</localizableAttribute>
     433            </object>
     434            <object name="diplomacyHeaderAlly" size="100%-180 0 100%-160 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     435                <localizableAttribute id="caption">A</localizableAttribute>
     436                <localizableAttribute id="tooltip">Ally</localizableAttribute>
     437            </object>
     438            <object name="diplomacyHeaderNeutral" size="100%-160 0 100%-140 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     439                <localizableAttribute id="caption">N</localizableAttribute>
     440                <localizableAttribute id="tooltip">Neutral</localizableAttribute>
     441            </object>
     442            <object name="diplomacyHeaderEnemy" size="100%-140 0 100%-120 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     443                <localizableAttribute id="caption">E</localizableAttribute>
     444                <localizableAttribute id="tooltip">Enemy</localizableAttribute>
     445            </object>
     446            <object name="diplomacyHeaderTribute" size="100%-110 0 100% 100%" type="text" style="chatPanel">
     447                <localizableAttribute id="caption">Tribute</localizableAttribute>
     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                <localizableAttribute id="caption">Settings</localizableAttribute>
     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                    <localizableAttribute id="caption">Enable Shadows</localizableAttribute>
     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                    <localizableAttribute id="caption">Enable Shadow Filtering</localizableAttribute>
     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                    <localizableAttribute id="caption">Water - HQ Waviness</localizableAttribute>
     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                    <localizableAttribute id="caption">Water - Use Actual Depth</localizableAttribute>
     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                    <localizableAttribute id="caption">Water - Enable Reflections</localizableAttribute>
     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                    <localizableAttribute id="caption">Water - Enable Refraction</localizableAttribute>
     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                    <localizableAttribute id="caption">Water - Enable Shore Foam</localizableAttribute>
     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                    <localizableAttribute id="caption">Water - Enable Shore Waves</localizableAttribute>
     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                    <localizableAttribute id="caption">Water - Use Surface Shadows</localizableAttribute>
     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                    <localizableAttribute id="caption">Enable Particles</localizableAttribute>
     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                    <localizableAttribute id="caption">Enable Unit Silhouettes</localizableAttribute>
     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                    <localizableAttribute id="caption">Enable Music</localizableAttribute>
     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                    <localizableAttribute id="caption">Developer Overlay</localizableAttribute>
     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            <localizableAttribute id="tooltip">Food</localizableAttribute>
    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            <localizableAttribute id="tooltip">Wood</localizableAttribute>
    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            <localizableAttribute id="tooltip">Stone</localizableAttribute>
    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            <localizableAttribute id="tooltip">Metal</localizableAttribute>
    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            <localizableAttribute id="tooltip">Population (current / limit)</localizableAttribute>
    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            <localizableAttribute id="tooltip">Choose player to view</localizableAttribute>
     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 ↓ -->
     706            <localizableAttribute id="caption">ALPHA XIV : Naukratis</localizableAttribute>
    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            <localizableAttribute id="tooltip">Game speed</localizableAttribute>
    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            <localizableAttribute id="tooltip">Choose game speed</localizableAttribute>
     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            <localizableAttribute id="tooltip">Diplomacy</localizableAttribute>
    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            <localizableAttribute id="caption">MENU</localizableAttribute>
     769        </object>
    681770        <action on="Press">
    682771            toggleMenu();
    683772        </action>
     
    702791            size="0 0 100% 28"
    703792            tooltip_style="sessionToolTip"
    704793        >
    705             Settings
     794            <localizableAttribute id="caption">Settings</localizableAttribute>
    706795            <action on="Press">settingsMenuButton();</action>
    707796        </object>
    708797
     
    714803            size="0 32 100% 60"
    715804            tooltip_style="sessionToolTip"
    716805        >
    717             Save
     806            <localizableAttribute id="caption">Save</localizableAttribute>
    718807            <action on="Press">
    719808            openSave();
    720809            </action>
     
    727816            size="0 64 100% 92"
    728817            tooltip_style="sessionToolTip"
    729818        >
    730             Chat
     819            <localizableAttribute id="caption">Chat</localizableAttribute>
    731820            <action on="Press">chatMenuButton();</action>
    732821        </object>
    733822
     
    738827            size="0 96 100% 124"
    739828            tooltip_style="sessionToolTip"
    740829        >
    741             Resign
     830            <localizableAttribute id="caption">Resign</localizableAttribute>
    742831            <action on="Press">resignMenuButton();</action>
    743832        </object>
    744833
     
    749838            size="0 128 100% 156"
    750839            tooltip_style="sessionToolTip"
    751840        >
    752             Exit
     841            <localizableAttribute id="caption">Exit</localizableAttribute>
    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                <localizableAttribute id="caption">Pause</localizableAttribute>
     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            <localizableAttribute id="caption">Manual</localizableAttribute>
    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            <localizableAttribute id="tooltip">Attack and Armor</localizableAttribute>
    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            <localizableAttribute id="tooltip">Click to select grouped units.</localizableAttribute>
    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                <localizableAttribute id="tooltip">Find idle worker</localizableAttribute>
    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                <localizableAttribute id="tooltip">Exchange resources:</localizableAttribute>
     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                    <localizableAttribute id="tooltip">Health:</localizableAttribute>
     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                    <localizableAttribute id="tooltip">Stamina:</localizableAttribute>
     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                    <localizableAttribute id="tooltip">Attack and Armor</localizableAttribute>
     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                    <localizableAttribute id="tooltip">Experience</localizableAttribute>
     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                    <localizableAttribute id="tooltip">Rank</localizableAttribute>
     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                <localizableAttribute id="tooltip">Hitpoints</localizableAttribute>
    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                <localizableAttribute id="tooltip">Stamina</localizableAttribute>
    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            <localizableAttribute id="caption">Return to Main Menu</localizableAttribute>
     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 0c846db..513402d 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};
    var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "  
    3838// Indexes of resources to sell and buy on barter panel
    3939var g_barterSell = 0;
    4040
     41// Internationalization cache — General
     42var localizedBuildingsTimesBatchSize = Engine.localize("%(buildings)s*%(batchSize)s");
     43var localizedClassSeparator = Engine.localizeWithContext("classSeparator", " ");
     44var localizedCountAndLimit = Engine.localize("Current Count: %(count)s, Limit: %(limit)s.");
     45var localizedPopulationCapacity = Engine.localize("Insufficient population capacity:");
     46var localizedPopulationRequirement = Engine.localize("%(population)s %(neededSlots)s");
     47var localizedRequiresTechnology = Engine.localize("Requires %(technology)s");
     48var localizedSetResource = Engine.localize("Set %(resource)s as trading goods");
     49var localizedShiftClick = Engine.localize("Shift-click");
     50
     51// Internationalization cache — Training
     52var localizedTrain = Engine.localize("%(action)s to train %(number)s.");
     53var localizedTrainFull = Engine.localize("%(action)s to train %(number)s (%(fullBatch)s).");
     54var localizedTrainFullAndRemainder = Engine.localize("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s).");
     55
     56// Internationalization cache — Loading and unloading
     57var localizedUnload = Engine.localize("Unload %(name)s");
     58var localizedUnloadHelp = Engine.localize("Single-click to unload 1. Shift-click to unload all of this type.");
     59
     60// Internationalization cache — Stances
     61var localizedViolent = Engine.localize("Violent");
     62var localizedAggressive = Engine.localize("Aggressive");
     63var localizedPassive = Engine.localize("Passive");
     64var localizedDefensive = Engine.localize("Defensive");
     65var localizedStandground = Engine.localize("Standground");
     66
     67// Internationalization cache — Formations
     68var localizedFormationsArray = {}
     69var localizedDisabledFormation = Engine.localize("%(formation)s (disabled)");
     70var localizedUnitsRequirementClasses = Engine.localize("Only units of type %(unitType)s allowed.")
     71var localizedUnitsRequirementNumber = Engine.localize("%(number)s units required");
     72
     73// Internationalization cache — Locking and unlocking
     74var localizedLockGate = Engine.localize("Lock gate");
     75var localizedUnlockGate = Engine.localize("Unlock gate");
     76
     77// Internationalization cache — Packing and unpacking
     78var localizedPack = Engine.localize("Pack");
     79var localizedUnpack = Engine.localize("Unpack");
     80var localizedCancelPacking = Engine.localize("Cancel packing");
     81var localizedCancelUnpacking = Engine.localize("Cancel unpacking");
     82
     83// Internationalization cache — Walls
     84var localizedConvertSiegeWallIntoGate = Engine.localize("Convert Siege Wall into Siege Wall Gate");
     85var localizedConvertStoneWallIntoGate = Engine.localize("Convert Store Wall into City Gate");
     86var localizedConvertWallIntoGate = Engine.localize("Convert %(wall)s into %(gate)s");
     87var localizedConvertWoodenWallIntoGate = Engine.localize("Convert Wooden Wall into Wooden Gate");
     88
    4189// Lay out a row of centered buttons (does not work inside a loop like the other function)
    4290function layoutButtonRowCentered(rowNumber, guiName, startIndex, endIndex, width)
    4391{
    function formatLimitString(trainEntLimit, trainEntCount)  
    146194{
    147195    if (trainEntLimit == undefined)
    148196        return "";
    149     var text = "\n\nCurrent Count: " + trainEntCount + ", Limit: " + trainEntLimit + ".";
     197    var text = "\n\n" + sprintf(localizedCountAndLimit, { count: trainEntCount, limit: trainEntLimit });
    150198    if (trainEntCount >= trainEntLimit)
    151199        text = "[color=\"red\"]" + text + "[/color]";
    152200    return text;
    function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize  
    174222    if (buildingsCountToTrainFullBatch > 0)
    175223    {
    176224        if (buildingsCountToTrainFullBatch > 1)
    177             fullBatchesString += buildingsCountToTrainFullBatch + "*";
    178         fullBatchesString += fullBatchSize;
     225        {
     226            fullBatchesString = sprintf(localizedBuildingsTimesBatchSize, {
     227                buildings: buildingsCountToTrainFullBatch,
     228                batchSize: fullBatchSize
     229            });
     230        }
     231        else
     232        {
     233            fullBatchesString = fullBatchSize;
     234        }
    179235    }
    180236    var remainderBatchString = remainderBatch > 0 ? remainderBatch : "";
    181237    var batchDetailsString = "";
     238    var action = "[font=\"serif-bold-13\"]" + localizedShiftClick + "[/font][font=\"serif-13\"]"
     239
    182240    // We need to display the batch details part if there is either more than
    183241    // one building with full batch or one building with the full batch and
    184242    // another with a partial batch
    185243    if (buildingsCountToTrainFullBatch > 1 ||
    186244        (buildingsCountToTrainFullBatch == 1 && remainderBatch > 0))
    187245    {
    188         batchDetailsString += " (" + fullBatchesString;
    189         if (remainderBatchString != "")
    190             batchDetailsString += " + " + remainderBatchString;
    191         batchDetailsString += ")";
     246        if (remainderBatch > 0)
     247        {
     248            return "\n[font=\"serif-13\"]" + sprintf(localizedTrainFullAndRemainder, {
     249                action: action,
     250                number: totalBatchTrainingCount,
     251                fullBatch: fullBatchesString,
     252                remainderBatch: remainderBatch
     253            }) + "[/font]";
     254        }
     255        else
     256        {
     257            return "\n[font=\"serif-13\"]" + sprintf(localizedTrainFull, {
     258                action: action,
     259                number: totalBatchTrainingCount,
     260                fullBatch: fullBatchesString
     261            }) + "[/font]";
     262        }
    192263    }
     264    else
     265    {
     266        return "\n[font=\"serif-13\"]" + sprintf(localizedTrain, {
     267            action: action,
     268            number: totalBatchTrainingCount
     269        }) + "[/font]";
     270    }
     271}
    193272
    194     return "\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train "
    195         + totalBatchTrainingCount + batchDetailsString + ".[/font]";
     273function getStanceDisplayName(name)
     274{
     275    var displayName;
     276    switch(name)
     277    {
     278        case "violent":
     279            displayName = localizedViolent;
     280            break;
     281        case "aggressive":
     282            displayName = localizedAggressive;
     283            break;
     284        case "passive":
     285            displayName = localizedPassive;
     286            break;
     287        case "defensive":
     288            displayName = localizedDefensive;
     289            break;
     290        case "standground":
     291            displayName = localizedStandground;
     292            break;
     293        default:
     294            displayName = name;
     295            break;
     296    }
     297    return displayName;
     298}
     299
     300function getFormationDisplayName(name)
     301{
     302    if (localizedFormationsArray[name] === undefined)
     303    {
     304        localizedFormationsArray[name] = Engine.localize(name);
     305    }
     306    return localizedFormationsArray[name];
    196307}
    197308
    198309/**
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    374485                break;
    375486        }
    376487
     488
    377489        switch (guiName)
    378490        {
    379491            case SELECTION:
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    386498            case QUEUE:
    387499                var tooltip = getEntityNames(template);
    388500                if (item.neededSlots)
    389                     tooltip += "\n[color=\"red\"]Insufficient population capacity:\n[/color]"+getCostComponentDisplayName("population")+" "+item.neededSlots;
     501                    tooltip += "\n[color=\"red\"]" + localizedPopulationCapacity + "\n[/color]" + sprintf(localizedPopulationRequirement, { population: getCostComponentDisplayName("population"), neededSlots: item.neededSlots });
    390502
    391503                var progress = Math.round(item.progress*100) + "%";
    392504                getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (item.count > 1 ? item.count : "");
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    404516
    405517            case GARRISON:
    406518                var name = getEntityNames(template);
    407                 var tooltip = "Unload " + name + "\nSingle-click to unload 1. Shift-click to unload all of this type.";
     519                var tooltip = sprintf(localizedUnload, { name: name })+ "\n" + localizedUnloadHelp;
    408520                var count = garrisonGroups.getCount(item);
    409521                getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : "");
    410522                break;
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    440552                break;
    441553
    442554            case STANCE:
     555                var tooltip = getStanceDisplayName(item);
     556                break;
     557
    443558            case FORMATION:
    444                 var tooltip = toTitleCase(item);
     559                var tooltip = getFormationDisplayName(item);
    445560                break;
    446561
    447562            case TRAINING:
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    464579                    getEntityLimitAndCount(playerState, entType);
    465580                tooltip += formatLimitString(trainEntLimit, trainEntCount);
    466581
    467                 tooltip += "[color=\"255 251 131\"]" + formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) + "[/color]";
    468582                break;
    469583
    470584            case RESEARCH:
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    517631                break;
    518632        }
    519633
     634
    520635        // Button
    521636        var button = getGUIObjectByName("unit"+guiName+"Button["+i+"]");
    522637        var button1 = getGUIObjectByName("unit"+guiName+"Button["+(i+rowLength)+"]");
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    588703                    "formationName": item
    589704                });
    590705
    591                 button.tooltip += " (disabled)";
     706                button.tooltip = sprintf(localizedDisabledFormation, { formation: button.tooltip });
    592707                if (requirements.count > 1)
    593                     button.tooltip += "\n" + requirements.count + " units required";
     708                    button.tooltip += "\n" + sprintf(localizedUnitsRequirementNumber, { number: requirements.count });
    594709                if (requirements.classesRequired)
    595710                {
    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.";
     711                    button.tooltip += "\n" + sprintf(localizedUnitsRequirementClasses, { unitType: Engine.localizeArray(requirements.classesRequired).join(localizedClassSeparator) });
    602712                }
    603713            }
    604714
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    631741            // If already a gate, show locking actions
    632742            if (item.gate)
    633743            {
    634                 gateIcon = "icons/lock_" + GATE_ACTIONS[item.locked ? 0 : 1].toLowerCase() + "ed.png";
     744                gateIcon = "icons/lock_" + GATE_ACTIONS[item.locked ? 0 : 1] + "ed.png";
    635745                selection.hidden = item.gate.locked === undefined ? false : item.gate.locked != item.locked;
    636746            }
    637747            // otherwise show gate upgrade icon
    function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, c  
    670780            {
    671781                button.enabled = false;
    672782                var techName = getEntityNames(GetTechnologyData(template.requiredTechnology));
    673                 button.tooltip += "\nRequires " + techName;
     783                button.tooltip += "\n" + sprintf(localizedRequiresTechnology, { technology: techName });
    674784                grayscale = "grayscale:";
    675785                affordableMask.hidden = false;
    676786                affordableMask.sprite = "colour: 0 0 0 127";
    function setupUnitTradingPanel(usedPanels, unitEntState, selection)  
    9111021        var selectTradingPreferredGoodsData = { "entities": selection, "preferredGoods": resource };
    9121022        button.onpress = (function(e){ return function() { selectTradingPreferredGoods(e); } })(selectTradingPreferredGoodsData);
    9131023        button.enabled = true;
    914         button.tooltip = "Set " + resource + " as trading goods";
     1024        button.tooltip = sprintf(localizedSetResource, { resource: resource });
    9151025        var icon = getGUIObjectByName("unitTradingIcon["+i+"]");
    9161026        var preferredGoods = unitEntState.trader.preferredGoods;
    9171027        var selected = getGUIObjectByName("unitTradingSelection["+i+"]");
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    10981208                    var gateTemplate = getWallGateTemplate(state.id);
    10991209                    if (gateTemplate)
    11001210                    {
    1101                         var wallName = GetTemplateData(state.template).name.generic;
    1102                         var gateName = GetTemplateData(gateTemplate).name.generic;
     1211                        var wallName = GetTemplateDataWithoutLocalization(state.template).name.generic;
     1212                        var gateName = GetTemplateDataWithoutLocalization(gateTemplate).name.generic;
     1213                        var tooltipString;
     1214
     1215                        // For internationalization purposes, when possible, available combinations should be provided
     1216                        // as placeholder-free strings as below.
     1217                        //
     1218                        // The platceholder implementation is provided only so that undetected new combinations of wall
     1219                        // and gate names are not simply printed in English, but as close to a perfect translation as
     1220                        // possible.
     1221
     1222                        if (wallName === "Wooden Wall" && gateName === "Wooden Gate")
     1223                        {
     1224                            tooltipString = localizedConvertWoodenWallIntoGate;
     1225                        }
     1226                        else if (wallName === "Stone Wall" && gateName === "City Gate")
     1227                        {
     1228                            tooltipString = localizedConvertStoneWallIntoGate;
     1229                        }
     1230                        else if (wallName === "Siege Wall" && gateName === "Siege Wall Gate")
     1231                        {
     1232                            tooltipString = localizedConvertSiegeWallIntoGate;
     1233                        }
     1234                        else
     1235                        {
     1236                            tooltipString = sprintf(localizedConvertWallIntoGate, { wall: Engine.localize(wallName), gate: Engine.localize(gateName) });
     1237                        }
    11031238
    11041239                        walls.push({
    1105                             "tooltip": "Convert " + wallName + " to " + gateName,
     1240                            "tooltip": tooltipString,
    11061241                            "template": gateTemplate,
    11071242                            "callback": function (item) { transformWallToGate(item.template); }
    11081243                        });
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    11121247                    longWallTypes[state.template] = true;
    11131248                }
    11141249                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                         });
     1250                {
     1251                    gates.push({
     1252                        "gate": state.gate,
     1253                        "tooltip": localizedLockGate,
     1254                        "locked": true,
     1255                        "callback": function (item) { lockGate(item.locked); }
     1256                    });
     1257                    gates.push({
     1258                        "gate": state.gate,
     1259                        "tooltip": localizedUnlockGate,
     1260                        "locked": false,
     1261                        "callback": function (item) { lockGate(item.locked); }
     1262                    });
     1263                }
    11221264                // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
    11231265                else if (state.gate && state.gate.locked != gates[0].gate.locked)
    11241266                    for (var j = 0; j < gates.length; ++j)
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    11631305                }
    11641306            }
    11651307            if (packButton)
    1166                 items.push({ "packing": false, "packed": false, "tooltip": "Pack", "callback": function() { packUnit(true); } });
     1308                items.push({ "packing": false, "packed": false, "tooltip": localizedPack, "callback": function() { packUnit(true); } });
    11671309            if (unpackButton)
    1168                 items.push({ "packing": false, "packed": true, "tooltip": "Unpack", "callback": function() { packUnit(false); } });
     1310                items.push({ "packing": false, "packed": true, "tooltip": localizedUnpack, "callback": function() { packUnit(false); } });
    11691311            if (packCancelButton)
    1170                 items.push({ "packing": true, "packed": false, "tooltip": "Cancel packing", "callback": function() { cancelPackUnit(true); } });
     1312                items.push({ "packing": true, "packed": false, "tooltip": localizedCancelPacking, "callback": function() { cancelPackUnit(true); } });
    11711313            if (unpackCancelButton)
    1172                 items.push({ "packing": true, "packed": true, "tooltip": "Cancel unpacking", "callback": function() { cancelPackUnit(false); } });
     1314                items.push({ "packing": true, "packed": true, "tooltip": localizedCancelUnpacking, "callback": function() { cancelPackUnit(false); } });
    11731315
    11741316            if (items.length)
    11751317                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 1ba9158..14529aa 100644
    a b const COST_DISPLAY_NAMES = {  
    1212    "time": "[icon=\"iconTime\"]"
    1313};
    1414
     15
     16// Localizable strings.
     17
     18var localizedArmor = Engine.localize("(%(armorPercentage)s)");
     19var localizedAttack = Engine.localize("%(attackLabel)s %(damageTypes)s");
     20var localizedAttackAndRange = Engine.localize("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(range)s");
     21var localizedAttackLabel = Engine.localize("Attack:");
     22var localizedChargeAttack = Engine.localize("Charge Attack:");
     23var localizedComponentCost = Engine.localize("%(component)s %(cost)s");
     24var localizedComposedName = Engine.localize("%(specificName)s (%(genericName)s)");
     25var localizedCosts = Engine.localize("Costs:");
     26var localizedCostsListSeparator = Engine.localize("  ");
     27var localizedCrush = Engine.localize("Crush");
     28var localizedDamage = Engine.localize("%(damage)s %(damageType)s");
     29var localizedDamageAndArmor = Engine.localize("%(damage)s %(damageType)s %(armorPercentage)s");
     30var localizedDelete = Engine.localize("Delete");
     31var localizedFocusOnRallyPoint = Engine.localize("Focus on Rally Point");
     32var localizedGain = Engine.localize("%(gain)s (%(player)s)");
     33var localizedGarrison = Engine.localize("Garrison");
     34var localizedHack = Engine.localize("Hack");
     35var localizedInsufficientResources = Engine.localize("Insufficient resources:");
     36var localizedListSeparator = Engine.localize(", ");
     37var localizedMeleeAttack = Engine.localize("Melee Attack:");
     38var localizedMovements = Engine.localize("%(speed)s %(movementType)s");
     39var localizedNone = Engine.localize("(None)");
     40var localizedPierce = Engine.localize("Pierce");
     41var localizedPlayerName = Engine.localize("player %(name)s");
     42var localizedPlus = Engine.localize("+");
     43var localizedPopulationBonus = Engine.localize("%(label)s %(populationBonus)s");
     44var localizedPopulationBonusLabel = Engine.localize("Population Bonus:");
     45var localizedRange = Engine.localize("Range:");
     46var localizedRangedAttack = Engine.localize("Ranged Attack:");
     47var localizedRankAndName = Engine.localize("%(rank)s %(name)s");
     48var localizedRepair = Engine.localize("Repair");
     49var localizedRun = Engine.localize("Run");
     50var localizedSpeed = Engine.localize("%(label)s %(speeds)s");
     51var localizedSpeedLabel = Engine.localize("Speed:");
     52var localizedStop = Engine.localize("Stop");
     53var localizedTowers = Engine.localize("Towers:  %(costs)s");
     54var localizedUnknown = Engine.localize("???");
     55var localizedUnloadAll = Engine.localize("Unload All");
     56var localizedWalk = Engine.localize("Walk");
     57var localizedWalls = Engine.localize("Walls:  %(costs)s");
     58var localizedYou = Engine.localize("you");
     59
    1560//-------------------------------- -------------------------------- --------------------------------
    1661// Utility functions
    1762//-------------------------------- -------------------------------- --------------------------------
    function damageTypeDetails(dmg)  
    154199    if (dmg)
    155200    {
    156201        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]");
     202        if (dmg.hack)
     203        {
     204            var type = "[font=\"sans-10\"][color=\"orange\"]" + localizedHack + "[/color][/font]";
     205            dmgArray.push(sprintf(localizedDamage, { damage: dmg.hack, damageType: type }));
     206        }
     207        if (dmg.pierce)
     208        {
     209            var type = "[font=\"sans-10\"][color=\"orange\"]" + localizedPierce + "[/color][/font]";
     210            dmgArray.push(sprintf(localizedDamage, { damage: dmg.pierce, damageType: type }));
     211        }
     212        if (dmg.crush)
     213        {
     214            var type = "[font=\"sans-10\"][color=\"orange\"]" + localizedCrush + "[/color][/font]";
     215            dmgArray.push(sprintf(localizedDamage, { damage: dmg.crush, damageType: type }));
     216        }
    160217
    161         return dmgArray.join(", ");
     218        return dmgArray.join(localizedListSeparator);
    162219    }
    163220    else
    164221    {
    165         return "[font=\"serif-12\"](None)[/font]";
     222        return "[font=\"serif-12\"]" + localizedNone + "[/font]";
    166223    }
    167224}
    168225
    169226// Converts an armor level into the actual reduction percentage
    170 function armorLevelToPercentage(level)
     227function armorLevelToPercentageString(level)
    171228{
    172     return 100 - Math.round(Math.pow(0.9, level) * 100);
     229    return (100 - Math.round(Math.pow(0.9, level) * 100)) + "%";
    173230}
    174231
    175232// Also for the unit details panel
    function armorTypeDetails(dmg)  
    180237        var dmgArray = [];
    181238        if (dmg.hack)
    182239        {
    183             dmgArray.push(dmg.hack + "[font=\"sans-10\"][color=\"orange\"] Hack[/color][/font] " +
    184                 " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.hack) + "%)[/font]");
     240            var damageType = "[font=\"sans-10\"][color=\"orange\"]" + localizedHack + "[/color][/font]";
     241            var armor = "[font=\"sans-10\"]" + sprintf(localizedArmor, { armorPercentage: armorLevelToPercentageString(dmg.hack) }) + "[/font]";
     242            dmgArray.push(sprintf(localizedDamageAndArmor, { damage: dmg.hack, damageType: damageType, armorPercentage: armor }));
    185243        }
    186244        if (dmg.pierce)
    187245        {
    188             dmgArray.push(dmg.pierce + "[font=\"sans-10\"][color=\"orange\"] Pierce[/color][/font] " +
    189                 " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.pierce) + "%)[/font]");
     246            var damageType = "[font=\"sans-10\"][color=\"orange\"]" + localizedPierce + "[/color][/font]";
     247            var armor = "[font=\"sans-10\"]" + sprintf(localizedArmor, { armorPercentage: armorLevelToPercentageString(dmg.pierce) }) + "[/font]";
     248            dmgArray.push(sprintf(localizedDamageAndArmor, { damage: dmg.pierce, damageType: damageType, armorPercentage: armor }));
    190249        }
    191250        if (dmg.crush)
    192251        {
    193             dmgArray.push(dmg.crush + "[font=\"sans-10\"][color=\"orange\"] Crush[/color][/font] " +
    194                 " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.crush) + "%)[/font]");
     252            var damageType = "[font=\"sans-10\"][color=\"orange\"]" + localizedCrush + "[/color][/font]";
     253            var armor = "[font=\"sans-10\"]" + sprintf(localizedArmor, { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]";
     254            dmgArray.push(sprintf(localizedDamageAndArmor, { damage: dmg.crush, damageType: damageType, armorPercentage: armor }));
    195255        }
    196256
    197         return dmgArray.join(", ");
     257        return dmgArray.join(localizedListSeparator);
    198258    }
    199259    else
    200260    {
    201         return "[font=\"serif-12\"](None)[/font]";
     261        return "[font=\"serif-12\"]" + localizedNone + "[/font]";
    202262    }
    203263}
    204264
    205265// For the training tooltip
    206266function damageTypesToText(dmg)
    207267{
    208     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 
    218     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]");
     268    if (dmg)
     269    {
     270        var dmgArray = [];
     271        if (dmg.hack)
     272        {
     273            var type = "[font=\"serif-12\"]" + localizedHack + "[/font]";
     274            dmgArray.push(sprintf(localizedDamage, { damage: dmg.hack, damageType: type }));
     275        }
     276        if (dmg.pierce)
     277        {
     278            var type = "[font=\"serif-12\"]" + localizedPierce + "[/font]";
     279            dmgArray.push(sprintf(localizedDamage, { damage: dmg.pierce, damageType: type }));
     280        }
     281        if (dmg.crush)
     282        {
     283            var type = "[font=\"serif-12\"]" + localizedCrush + "[/font]";
     284            dmgArray.push(sprintf(localizedDamage, { damage: dmg.crush, damageType: type }));
     285        }
     286
     287        return dmgArray.join("[font=\"serif-12\"]" + localizedListSeparator + "[/font]");
     288    }
     289    else
     290    {
     291        return "[font=\"serif-12\"]" + localizedNone + "[/font]";
     292    }
    224293}
    225294
    226295// Also for the training tooltip
    227296function armorTypesToText(dmg)
    228297{
    229     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]";
    238 
    239     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]");
     298    if (dmg)
     299    {
     300        var dmgArray = [];
     301        if (dmg.hack)
     302        {
     303            var type = "[font=\"serif-12\"]" + localizedHack + "[/font]";
     304            var armor = "[font=\"sans-10\"]" + sprintf(localizedArmor, { armorPercentage: armorLevelToPercentageString(dmg.hack) }) + "[/font]";
     305            dmgArray.push(sprintf(localizedDamageAndArmor, { damage: dmg.hack, damageType: type, armorPercentage: armor }));
     306        }
     307        if (dmg.pierce)
     308        {
     309            var type = "[font=\"serif-12\"]" + localizedPierce + "[/font]";
     310            var armor = "[font=\"sans-10\"]" + sprintf(localizedArmor, { armorPercentage: armorLevelToPercentageString(dmg.pierce) }) + "[/font]";
     311            dmgArray.push(sprintf(localizedDamageAndArmor, { damage: dmg.pierce, damageType: type, armorPercentage: armor }));
     312        }
     313        if (dmg.crush)
     314        {
     315            var type = "[font=\"serif-12\"]" + localizedCrush + "[/font]";
     316            var armor = "[font=\"sans-10\"]" + sprintf(localizedArmor, { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]";
     317            dmgArray.push(sprintf(localizedDamageAndArmor, { damage: dmg.crush, damageType: type, armorPercentage: armor }));
     318        }
     319
     320        return dmgArray.join("[font=\"serif-12\"]" + localizedListSeparator + "[/font]");
     321    }
     322    else
     323    {
     324        return "[font=\"serif-12\"]" + localizedNone + "[/font]";
     325    }
    245326}
    246327
    247328function getEntityCommandsList(entState)
    function getEntityCommandsList(entState)  
    251332    {
    252333        commands.push({
    253334            "name": "unload-all",
    254             "tooltip": "Unload All",
     335            "tooltip": localizedUnloadAll,
    255336            "icon": "garrison-out.png"
    256337        });
    257338    }
    258339
    259340    commands.push({
    260341        "name": "delete",
    261         "tooltip": "Delete",
     342        "tooltip": localizedDelete,
    262343        "icon": "kill_small.png"
    263344    });
    264345
    function getEntityCommandsList(entState)  
    266347    {
    267348        commands.push({
    268349            "name": "stop",
    269             "tooltip": "Stop",
     350            "tooltip": localizedStop,
    270351            "icon": "stop.png"
    271352        });
    272353        commands.push({
    273354            "name": "garrison",
    274             "tooltip": "Garrison",
     355            "tooltip": localizedGarrison,
    275356            "icon": "garrison.png"
    276357        });
    277358    }
    function getEntityCommandsList(entState)  
    280361    {
    281362        commands.push({
    282363            "name": "repair",
    283             "tooltip": "Repair",
     364            "tooltip": localizedRepair,
    284365            "icon": "repair.png"
    285366        });
    286367    }
    function getEntityCommandsList(entState)  
    289370    {
    290371        commands.push({
    291372            "name": "focus-rally",
    292             "tooltip": "Focus on Rally Point",
     373            "tooltip": localizedFocusOnRallyPoint,
    293374            "icon": "focus-rally.png"
    294375        });
    295376    }
    function getEntityCostComponentsTooltipString(template, trainNum, entity)  
    330411    totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", {"entity": entity, "batchSize": trainNum}) : 1));
    331412
    332413    var costs = [];
    333     if (totalCosts.food) costs.push(getCostComponentDisplayName("food") + " " + totalCosts.food);
    334     if (totalCosts.wood) costs.push(getCostComponentDisplayName("wood") + " " + totalCosts.wood);
    335     if (totalCosts.metal) costs.push(getCostComponentDisplayName("metal") + " " + totalCosts.metal);
    336     if (totalCosts.stone) costs.push(getCostComponentDisplayName("stone") + " " + totalCosts.stone);
    337     if (totalCosts.population) costs.push(getCostComponentDisplayName("population") + " " + totalCosts.population);
    338     if (totalCosts.time) costs.push(getCostComponentDisplayName("time") + " " + totalCosts.time);
     414    if (totalCosts.food) costs.push(sprintf(localizedComponentCost, { component: getCostComponentDisplayName("food"), cost: totalCosts.food }));
     415    if (totalCosts.wood) costs.push(sprintf(localizedComponentCost, { component: getCostComponentDisplayName("wood"), cost: totalCosts.wood }));
     416    if (totalCosts.metal) costs.push(sprintf(localizedComponentCost, { component: getCostComponentDisplayName("metal"), cost: totalCosts.metal }));
     417    if (totalCosts.stone) costs.push(sprintf(localizedComponentCost, { component: getCostComponentDisplayName("stone"), cost: totalCosts.stone }));
     418    if (totalCosts.population) costs.push(sprintf(localizedComponentCost, { component: getCostComponentDisplayName("population"), cost: totalCosts.population }));
     419    if (totalCosts.time) costs.push(sprintf(localizedComponentCost, { component: getCostComponentDisplayName("time"), cost: totalCosts.time }));
    339420    return costs;
    340421}
    341422
    function getWallPieceTooltip(wallTypes)  
    391472        }
    392473    }
    393474    else
     475    {
    394476        for (var i = 0; i < wallTypes.length; ++i)
     477        {
    395478            out.push(getEntityCostComponentsTooltipString(wallTypes[i]).join(", "));
     479        }
     480    }
    396481
    397482    return out;
    398483}
    function getEntityCostTooltip(template, trainNum, entity)  
    416501        var wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
    417502        var towerCosts = getEntityCostComponentsTooltipString(templateTower);
    418503
    419         cost += "\n";
    420         cost += " Walls:  " + wallCosts.join("  ") + "\n";
    421         cost += " Towers: " + towerCosts.join("  ");
     504        cost += " " + sprintf(localizedWalls, { costs: wallCosts.join(localizedCostsListSeparator) }) + "\n";
     505        cost += " " + sprintf(localizedTowers, { costs: towerCosts.join(localizedCostsListSeparator) });
    422506    }
    423507    else if (template.cost)
    424508    {
    425509        var costs = getEntityCostComponentsTooltipString(template, trainNum, entity);
    426         cost += costs.join("  ");
     510        cost = costs.join(localizedCostsListSeparator);
    427511    }
    428512    else
    429513    {
    function getPopulationBonusTooltip(template)  
    440524{
    441525    var popBonus = "";
    442526    if (template.cost && template.cost.populationBonus)
    443         popBonus = "\n[font=\"serif-bold-13\"]Population Bonus:[/font] " + template.cost.populationBonus;
     527    {
     528        var labelString = "[font=\"serif-bold-13\"]" + localizedPopulationBonusLabel + "[/font]"
     529        popBonus = "\n" + sprintf(localizedPopulationBonus, { label: labelString, populationBonus: template.cost.populationBonus });
     530    }
    444531    return popBonus;
    445532}
    446533
    function getNeededResourcesTooltip(resources)  
    451538{
    452539    var formatted = [];
    453540    for (var resource in resources)
    454         formatted.push("[font=\"serif-12\"]" + getCostComponentDisplayName(resource) + "[/font] " + resources[resource]);
     541    {
     542        var componentLabel = "[font=\"serif-12\"]" + getCostComponentDisplayName(resource) + "[/font]"
     543        formatted.push(sprintf(localizedComponentCost, { component: componentLabel, cost: resources[resource] }));
     544    }
    455545
    456     return "\n\n[font=\"serif-bold-13\"][color=\"red\"]Insufficient resources:[/color][/font]\n" + formatted.join("  ");
     546    return "\n\n[font=\"serif-bold-13\"][color=\"red\"]" + localizedInsufficientResources + "[/color][/font]\n" + formatted.join(localizedCostsListSeparator);
    457547}
    458548
    459549function getEntitySpeed(template)
    function getEntitySpeed(template)  
    461551    var speed = "";
    462552    if (template.speed)
    463553    {
    464         speed += "[font=\"serif-bold-13\"]Speed:[/font] ";
     554        var label = "[font=\"serif-bold-13\"]" + localizedSpeedLabel + "[/font]"
    465555        var speeds = [];
    466         if (template.speed.walk) speeds.push(template.speed.walk + " [font=\"serif-12\"]Walk[/font]");
    467         if (template.speed.run) speeds.push(template.speed.run + " [font=\"serif-12\"]Run[/font]");
     556        if (template.speed.walk) speeds.push(sprintf(localizedMovements, { speed: template.speed.walk, movementType: "[font=\"serif-12\"]" + localizedWalk + "[/font]"}));
     557        if (template.speed.run) speeds.push(sprintf(localizedMovements, { speed: template.speed.run, movementType: "[font=\"serif-12\"]" + localizedRun + "[/font]"}));
    468558
    469         speed += speeds.join(", ");
     559        speed = sprintf(localizedSpeed, { label: label, speeds: speeds.join(localizedListSeparator) })
    470560    }
    471561    return speed;
    472562}
    function getEntityAttack(template)  
    480570        delete template.attack['Slaughter'];
    481571        for (var type in template.attack)
    482572        {
    483             var attack = "[font=\"serif-bold-13\"]" + type + " Attack:[/font] " + damageTypesToText(template.attack[type]);
    484             // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     573            var attack = "";
     574            var attackLabel = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(type) + "[/font]";
    485575            if (type == "Ranged")
    486                 attack += ", [font=\"serif-bold-13\"]Range:[/font] "+Math.round(template.attack[type].maxRange/4);
     576            {
     577                // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     578                attack = sprintf(localizedAttackAndRange, {
     579                    attackLabel: attackLabel,
     580                    damageTypes: damageTypesToText(template.attack[type]),
     581                    rangeLabel: "[font=\"serif-bold-13\"]" + localizedRange + "[/font]",
     582                    range: Math.round(template.attack[type].maxRange/4)
     583                });
     584            }
     585            else
     586            {
     587                attack = sprintf(localizedAttack, {
     588                    attackLabel: attackLabel,
     589                    damageTypes: damageTypesToText(template.attack[type])
     590                });
     591            }
    487592            attacks.push(attack);
    488593        }
    489594    }
    function getEntityAttack(template)  
    492597
    493598function getEntityName(template)
    494599{
    495     return template.name.specific || template.name.generic || "???";
     600    if (template.name.specific)
     601    {
     602        return Engine.localize(template.name.specific);
     603    }
     604    else if (template.name.generic)
     605    {
     606        return Engine.localize(template.name.generic);
     607    }
     608    else
     609    {
     610        return localizedUnknown;
     611    }
    496612}
    497613
    498614function getEntityNames(template)
    499615{
    500     var names = [];
    501616    if (template.name.specific)
    502617    {
    503         names.push(template.name.specific);
    504         if (template.name.generic && names[0] != template.name.generic)
    505             names.push("(" + template.name.generic + ")");
     618        if (template.name.generic && template.name.specific != template.name.generic)
     619        {
     620            return sprintf(localizedComposedName, {
     621                specificName: Engine.localize(template.name.specific),
     622                genericName: Engine.localize(template.name.generic)
     623            });
     624        }
     625        else
     626        {
     627            return Engine.localize(template.name.specific);
     628        }
    506629    }
    507630    else if (template.name.generic)
    508         names.push(template.name.generic);
    509 
    510     return (names.length) ? names.join(" ") : "???";
     631    {
     632        return Engine.localize(template.name.generic);
     633    }
     634    else
     635    {
     636        return localizedUnknown;
     637    }
    511638}
    512639
    513640function getEntityNamesFormatted(template)
    function getEntityRankedName(entState)  
    537664    var template = GetTemplateData(entState.template)
    538665    var rank = entState.identity.rank;
    539666    if (rank)
    540         return rank + " " + template.name.specific;
     667        return sprintf(localizedRankAndName, { rank: rank, name: template.name.specific });
    541668    else
    542669        return template.name.specific;
    543670}
    function getRankIconSprite(entState)  
    559686 */
    560687function getTradingTooltip(gain)
    561688{
    562     var tooltip = gain.traderGain;
     689    var gainString = gain.traderGain;
    563690    if (gain.market1Gain && gain.market1Owner == gain.traderOwner)
    564         tooltip += "+" + gain.market1Gain;
     691    {
     692        gainString += localizedPlus + gain.market1Gain;
     693    }
    565694    if (gain.market2Gain && gain.market2Owner == gain.traderOwner)
    566         tooltip += "+" + gain.market2Gain;
    567     tooltip += " (you)";
     695    {
     696        gainString += localizedPlus + gain.market2Gain;
     697    }
     698
     699    var tooltip = sprintf(localizedGain, {
     700        gain: gainString,
     701        player: localizedYou
     702    });
    568703
    569704    if (gain.market1Gain && gain.market1Owner != gain.traderOwner)
    570         tooltip += ", " + gain.market1Gain + " (player " + gain.market1Owner + ")";
     705    {
     706        tooltip += localizedListSeparator + sprintf(localizedGain, {
     707            gain: gain.market1Gain,
     708            player: sprintf(localizedPlayerName, { name: gain.market1Owner })
     709        });
     710    }
    571711    if (gain.market2Gain && gain.market2Owner != gain.traderOwner)
    572         tooltip += ", " + gain.market2Gain + " (player " + gain.market2Owner + ")";
     712    {
     713        tooltip += localizedListSeparator + sprintf(localizedGain, {
     714            gain: gain.market2Gain,
     715            player: sprintf(localizedPlayerName, { name: gain.market2Owner })
     716        });
     717    }
    573718
    574719    return tooltip;
    575720}
     721
     722function getAttackTypeLabel(type)
     723{
     724    if (type === "Charge") return localizedChargeAttack;
     725    else if (type === "Melee") return localizedMeleeAttack;
     726    else if (type === "Ranged") return localizedRangedAttack;
     727    else return localizedAttackLabel;
     728}
  • 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..b630ce2 100644
    a b  
    11function init(data)
    22{
    3     getGUIObjectByName("mainText").caption = readFile("gui/splashscreen/" + data.page + ".txt");
     3    getGUIObjectByName("mainText").caption = Engine.localizeLines(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..a947d23 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            <localizableAttribute id="caption">Welcome to 0 A.D. !</localizableAttribute>
     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            <localizableAttribute id="caption">OK</localizableAttribute>
    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            <localizableAttribute id="caption">Visit Fundraiser</localizableAttribute>
    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                    <localizableAttribute id="caption">Don't show this again</localizableAttribute>
     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..4a74538 100644
    a b function adjustTabDividers(tabSize)  
    3737function init(data)
    3838{
    3939    var civData = loadCivData();
    40     var mapSize = "Scenario";
     40    var mapDisplayType = Engine.localize("Scenario");
    4141
    42     getGUIObjectByName("timeElapsed").caption = "Time elapsed: " + timeToString(data.timeElapsed);
     42    getGUIObjectByName("timeElapsed").caption = sprintf(Engine.localize("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(Engine.localize("%(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..88988cd 100644
    a b  
    2020        </action>
    2121
    2222        <object style="TitleText" type="text" size="50%-128 4 50%+128 36">
    23             Summary
     23            <localizableAttribute id="caption">Summary</localizableAttribute>
    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                <localizableAttribute id="caption">Score</localizableAttribute>
     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                <localizableAttribute id="caption">Units/buildings</localizableAttribute>
     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                <localizableAttribute id="caption">Conquest</localizableAttribute>
     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                <localizableAttribute id="caption">Resources</localizableAttribute>
     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                <localizableAttribute id="caption">Market</localizableAttribute>
     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                    <localizableAttribute id="caption">Player name</localizableAttribute>
    90100                </object>
    91101                <object name="economyScoreHeading" type="text" style="CenteredTabLabelText">
    92                     Economy&#10;score
     102                    <localizableAttribute id="caption">Economy score</localizableAttribute>
    93103                </object>
    94104                <object name="militaryScoreHeading" type="text" style="CenteredTabLabelText">
    95                     Military&#10;score
     105                    <localizableAttribute id="caption">Military score</localizableAttribute>
    96106                </object>
    97107                <object name="explorationScoreHeading" type="text" style="CenteredTabLabelText">
    98                     Exploration&#10;score
     108                    <localizableAttribute id="caption">Exploration score</localizableAttribute>
    99109                </object>
    100110                <object name="totalScoreHeading" type="text" style="CenteredTabLabelText">
    101                     Total&#10;score
     111                    <localizableAttribute id="caption">Total score</localizableAttribute>
    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                    <localizableAttribute id="caption">Player name</localizableAttribute>
    125135                </object>
    126136                <object name="unitsTrainedHeading" type="text" style="CenteredTabLabelText">
    127                     Units&#10;trained
     137                    <localizableAttribute id="caption">Units trained</localizableAttribute>
    128138                </object>
    129139                <object name="unitsLostHeading" type="text" style="CenteredTabLabelText">
    130                     Units&#10;lost
     140                    <localizableAttribute id="caption">Units lost</localizableAttribute>
    131141                </object>
    132142                <object name="enemyUnitsKilledHeading" type="text" style="CenteredTabLabelText">
    133                     Enemy units&#10;killed
     143                    <localizableAttribute id="caption">Enemy units killed</localizableAttribute>
    134144                </object>
    135145                <object name="buildingsConstructedHeading" type="text" style="CenteredTabLabelText">
    136                     Buildings&#10;constructed
     146                    <localizableAttribute id="caption">Buildings constructed</localizableAttribute>
    137147                </object>
    138148                <object name="buildingsLostHeading" type="text" style="CenteredTabLabelText">
    139                     Buildings&#10;lost
     149                    <localizableAttribute id="caption">Buildings lost</localizableAttribute>
    140150                </object>
    141151                <object name="enemyBuildingsDestroyedHeading" type="text" style="CenteredTabLabelText">
    142                     Enemy&#10;buildings&#10;destroyed
     152                    <localizableAttribute id="caption">Enemy buildings destroyed</localizableAttribute>
    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                    <localizableAttribute id="caption">Player name</localizableAttribute>
    168178                </object>
    169179                <object name="civCentresBuiltHeading" type="text" style="CenteredTabLabelText">
    170                     Civ centres&#10;built
     180                    <localizableAttribute id="caption">Civ centres built</localizableAttribute>
    171181                </object>
    172182                <object name="enemyCivCentresDestroyedHeading" type="text" style="CenteredTabLabelText">
    173                     Enemy&#10;civ centres&#10;destroyed
     183                    <localizableAttribute id="caption">Enemy civ centres destroyed</localizableAttribute>
    174184                </object>
    175185                <object name="mapExplorationHeading" type="text" style="CenteredTabLabelText">
    176                     Map&#10;exploration
     186                    <localizableAttribute id="caption">Map exploration</localizableAttribute>
    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                    <localizableAttribute id="caption">Player name</localizableAttribute>
    199209                </object>
    200210                <object name="resourceHeading" type="text" style="CenteredTabLabelText">
    201                     Resource Statistics (Gathered / Used)
     211                    <localizableAttribute id="caption">Resource Statistics (Gathered / Used)</localizableAttribute>
    202212                </object>
    203213                <object name="foodGatheredHeading" type="text" style="CenteredTabLabelText">
    204                     Food
     214                    <localizableAttribute id="caption">Food</localizableAttribute>
    205215                </object>
    206216                <object name="woodGatheredHeading" type="text" style="CenteredTabLabelText">
    207                     Wood
     217                    <localizableAttribute id="caption">Wood</localizableAttribute>
    208218                </object>
    209219                <object name="stoneGatheredHeading" type="text" style="CenteredTabLabelText">
    210                     Stone
     220                    <localizableAttribute id="caption">Stone</localizableAttribute>
    211221                </object>
    212222                <object name="metalGatheredHeading" type="text" style="CenteredTabLabelText">
    213                     Metal
     223                    <localizableAttribute id="caption">Metal</localizableAttribute>
    214224                </object>
    215225                <object name="vegetarianRatioHeading" type="text" style="CenteredTabLabelText">
    216                     Vegetarian&#10;ratio
     226                    <localizableAttribute id="caption">Vegetarian ratio</localizableAttribute>
    217227                </object>
    218228                <object name="treasuresCollectedHeading" type="text" style="CenteredTabLabelText">
    219                     Treasures&#10;collected
     229                    <localizableAttribute id="caption">Treasures collected</localizableAttribute>
    220230                </object>
    221231                <object name="resourcesTributedHeading" type="text" style="CenteredTabLabelText">
    222                     Tributes&#10;(Sent / Received)
     232                    <localizableAttribute id="caption">Tributes (Sent / Received)</localizableAttribute>
    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                    <localizableAttribute id="caption">Player name</localizableAttribute>
    249259                </object>
    250260                <object name="exchangedFoodHeading" type="text" style="CenteredTabLabelText">
    251                     Food&#10;exchanged
     261                    <localizableAttribute id="caption">Food exchanged</localizableAttribute>
    252262                </object>
    253263                <object name="exchangedWoodHeading" type="text" style="CenteredTabLabelText">
    254                     Wood&#10;exchanged
     264                    <localizableAttribute id="caption">Wood exchanged</localizableAttribute>
    255265                </object>
    256266                <object name="exchangedStoneHeading" type="text" style="CenteredTabLabelText">
    257                     Stone&#10;exchanged
     267                    <localizableAttribute id="caption">Stone exchanged</localizableAttribute>
    258268                </object>
    259269                <object name="exchangedMetalHeading" type="text" style="CenteredTabLabelText">
    260                     Metal&#10;exchanged
     270                    <localizableAttribute id="caption">Metal exchanged</localizableAttribute>
    261271                </object>
    262272                <object name="barterEfficiencyHeading" type="text" style="CenteredTabLabelText">
    263                     Barter&#10;efficiency
     273                    <localizableAttribute id="caption">Barter efficiency</localizableAttribute>
    264274                </object>
    265275                <object name="tradeIncomeHeading" type="text" style="CenteredTabLabelText">
    266                     Trade&#10;income
     276                    <localizableAttribute id="caption">Trade income</localizableAttribute>
    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            <localizableAttribute id="caption">Continue</localizableAttribute>
    290300            <action on="Press">
    291301                Engine.SwitchGuiPage("page_pregame.xml");
    292302            </action>
  • new file inaries/data/mods/public/localization/.tx/config

    diff --git a/binaries/data/mods/public/localization/.tx/config b/binaries/data/mods/public/localization/.tx/config
    new file mode 100644
    index 0000000..733e2ab
    - +  
     1[main]
     2host = https://www.transifex.com
     3
     4[0ad.public]
     5file_filter = <lang>.public.po
     6source_file = public.pot
     7source_lang = en
     8
  • new file inaries/data/mods/public/localization/messages.sh

    diff --git a/binaries/data/mods/public/localization/messages.sh b/binaries/data/mods/public/localization/messages.sh
    new file mode 100644
    index 0000000..f3d9f0b
    - +  
     1# This file provides information about this mod that helps the internationalization system to find certain types of
     2# files that need to be localized that cannot be detected automatically.
     3#
     4# Paths are relative to the root directory of the mod.
     5
     6
     7# Functions to generate file lists dynamically.
     8
     9loadTxtLineByLineFilesDynamically()
     10{
     11  # Define required variables.
     12  local basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     13  local root="$basedir/.."
     14
     15  # Enter the mod folder, so that paths are relative to it.
     16  pushd $root &> /dev/null
     17
     18  for filepath in $(find "gui/text/tips" -name "*.txt")
     19  do
     20    txtLineByLineFiles+=("$filepath")
     21  done
     22
     23  # Leave the mod folder.
     24  popd &> /dev/null
     25}
     26
     27loadJsonFilesDynamically()
     28{
     29  # Define required variables.
     30  local basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     31  local root="$basedir/.."
     32
     33  # Enter the mod folder, so that paths are relative to it.
     34  pushd $root &> /dev/null
     35
     36  for filepath in $(find "civs" -name "*.json")
     37  do
     38    jsonFilesWithFilters+=("$filepath::Name,Description,History,Special,Formations,AINames")
     39  done
     40
     41  for filepath in $(find "maps/random" -name "*.json")
     42  do
     43    jsonFilesWithFilters+=("$filepath::Name,Description")
     44  done
     45
     46  for filepath in $(find "simulation/ai" -name "*.json")
     47  do
     48    jsonFilesWithFilters+=("$filepath::name,description")
     49  done
     50
     51  # Determine civilization codes, as specific names are specified as "specificName: { "<civilization code>": "<name>" }.
     52  pushd civs &> /dev/null
     53  civilizationCodes=`for item in $(find . -type f); do echo -n "${item//.\/},"; done;`
     54  popd &> /dev/null
     55  civilizationCodes=${civilizationCodes//.json}
     56  length=$((${#civilizationCodes} - 1))
     57  civilizationCodes=${civilizationCodes:0:$length}
     58
     59  for filepath in $(find "simulation/data/technologies" -name "*.json")
     60  do
     61    jsonFilesWithFilters+=("$filepath::specificName,genericName,description,tooltip,requirementsTooltip,$civilizationCodes")
     62  done
     63
     64  # Leave the mod folder.
     65  popd &> /dev/null
     66}
     67
     68loadEntityFilesDynamically()
     69{
     70  # Define required variables.
     71  local basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     72  local root="$basedir/.."
     73
     74  # Enter the mod folder, so that paths are relative to it.
     75  pushd $root &> /dev/null
     76
     77  for filepath in $(find "simulation/templates" -name "*.xml")
     78  do
     79    entityFiles+=("$filepath")
     80  done
     81
     82  # Leave the mod folder.
     83  popd &> /dev/null
     84}
     85
     86loadXmlFilesWithJsonDynamically()
     87{
     88  # Define required variables.
     89  local basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     90  local root="$basedir/.."
     91
     92  # Enter the mod folder, so that paths are relative to it.
     93  pushd $root &> /dev/null
     94
     95  while IFS= read -d $'\0' -r filepath ; do
     96    xmlFilesWithJsonWithFilters+=("$filepath::ScriptSettings::Name,Description")
     97  done < <(find "maps/scenarios" -name "*.xml" -print0)
     98
     99  while IFS= read -d $'\0' -r filepath ; do
     100    xmlFilesWithJsonWithFilters+=("$filepath::ScriptSettings::Name,Description")
     101  done < <(find "maps/skirmishes" -name "*.xml" -print0)
     102
     103  # Leave the mod folder.
     104  popd &> /dev/null
     105}
     106
     107
     108# Variables used by the ‘(0 A.D. root folder)/localization/update-pot.sh’ script.
     109
     110declare -a txtLineByLineFiles=(
     111  "gui/manual/intro.txt"
     112  "gui/manual/userreport.txt"
     113  "gui/text/quotes.txt"
     114  "gui/splashscreen/splashscreen.txt"
     115)
     116loadTxtLineByLineFilesDynamically
     117
     118declare -a jsonFilesWithFilters=(
     119  "simulation/data/game_speeds.json::Name"
     120  "simulation/data/map_sizes.json::Name,LongName"
     121  "simulation/data/player_defaults.json::Name"
     122)
     123loadJsonFilesDynamically
     124
     125declare -a entityFiles=()
     126loadEntityFilesDynamically
     127
     128declare -a xmlFilesWithJsonWithFilters=()
     129loadXmlFilesWithJsonDynamically
  • build/premake/extern_libs4.lua

    diff --git a/build/premake/extern_libs4.lua b/build/premake/extern_libs4.lua
    index 6101b5e..a336f74 100644
    a b extern_lib_defs = {  
    343343            })
    344344        end,
    345345    },
     346    icu = {
     347        compile_settings = function()
     348            if os.is("windows") then
     349                add_default_include_paths("u")
     350            end
     351        end,
     352        link_settings = function()
     353            if os.is("windows") then
     354                add_default_lib_paths("icu")
     355            end
     356            add_default_links({
     357                win_names  = { "icu" },
     358                unix_names = { "icui18n", "icuuc" },
     359            })
     360        end,
     361    },
    346362    libcurl = {
    347363        compile_settings = function()
    348364            if os.is("windows") then
    extern_lib_defs = {  
    567583            end
    568584        end,
    569585    },
     586    tinygettext = {
     587        compile_settings = function()
     588            includedirs { libraries_source_dir .. "tinygettext" }
     589        end,
     590        link_settings = function()
     591            add_source_lib_paths("tinygettext")
     592            add_default_links({
     593                win_names  = { "tinygettext" },
     594                unix_names = { "tinygettext" },
     595            })
     596        end,
     597    },
    570598    valgrind = {
    571599        compile_settings = function()
    572600            add_source_include_paths("valgrind")
  • build/premake/premake4.lua

    diff --git a/build/premake/premake4.lua b/build/premake/premake4.lua
    index bba9714..a5b948a 100644
    a b function setup_all_libs ()  
    591591        "zlib",
    592592        "boost",
    593593        "enet",
    594         "libcurl"
     594        "libcurl",
     595        "tinygettext",
     596        "icu",
    595597    }
    596598   
    597599    if not _OPTIONS["without-audio"] then
    function setup_all_libs ()  
    641643        "spidermonkey",
    642644        "sdl",  -- key definitions
    643645        "opengl",
    644         "boost"
     646        "boost",
     647        "tinygettext",
    645648    }
    646649    setup_static_lib_project("gui", source_dirs, extern_libs, {})
    647650
    function setup_all_libs ()  
    672675        "libjpg",
    673676        "valgrind",
    674677        "cxxtest",
     678        "tinygettext",
    675679    }
    676680
    677681    -- CPU architecture-specific
    used_extern_libs = {  
    762766    "cxxtest",
    763767    "comsuppw",
    764768    "enet",
     769    "tinygettext",
    765770    "libcurl",
     771    "icu",
    766772
    767773    "valgrind",
    768774}
  • libraries/LICENSE.txt

    diff --git a/libraries/LICENSE.txt b/libraries/LICENSE.txt
    index 58654d2..cd69609 100644
    a b win32/ contains headers and precompiled static libs for Windows builds.  
    2323  source/spidermonkey
    2424    MPL / GPL / LGPL
    2525
     26  source/tinygettext
     27    GNU GPL v2 (see COPYING)
     28
    2629  source/valgrind
    2730    BSD
    2831
  • new file libraries/source/tinygettext/CMakeLists.txt

    diff --git a/libraries/source/tinygettext/CMakeLists.txt b/libraries/source/tinygettext/CMakeLists.txt
    new file mode 100644
    index 0000000..abe2f98
    - +  
     1#
     2# TinyGetText build script
     3# Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
     4#
     5# This program is free software; you can redistribute it and/or modify
     6# it under the terms of the GNU General Public License as published by
     7# the Free Software Foundation; either version 2 of the License, or
     8# (at your option) any later version.
     9#
     10# This program is distributed in the hope that it will be useful,
     11# but WITHOUT ANY WARRANTY; without even the implied warranty of
     12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13# GNU General Public License for more details.
     14#
     15# You should have received a copy of the GNU General Public License
     16# along with this program; if not, write to the Free Software
     17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     18#
     19
     20
     21#
     22# INSTRUCTIONS:
     23# -------------
     24#
     25# Create a directory build/ and change to it. Run
     26#
     27#   cmake ..
     28#
     29# This creates a set of Makefiles to build the project. Run
     30#
     31#   make
     32#
     33
     34
     35CMAKE_POLICY(SET CMP0005 NEW)
     36
     37## Project name to use as command prefix
     38
     39PROJECT(tinygettext)
     40SET(VERSION "0.1")
     41
     42### CMake configuration
     43
     44CMAKE_MINIMUM_REQUIRED(VERSION 2.4)
     45IF(COMMAND cmake_policy)
     46    CMAKE_POLICY(SET CMP0003 NEW)
     47ENDIF(COMMAND cmake_policy)
     48SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${tinygettext_SOURCE_DIR})
     49
     50# move some config clutter to the advanced section
     51MARK_AS_ADVANCED(
     52    CMAKE_BACKWARDS_COMPATIBILITY
     53    CMAKE_BUILD_TYPE
     54    CMAKE_INSTALL_PREFIX
     55    EXECUTABLE_OUTPUT_PATH
     56    CMAKE_OSX_ARCHITECTURES
     57    CMAKE_OSX_SYSROOT
     58)
     59
     60## Reveal library type choice to users
     61OPTION(BUILD_SHARED_LIBS "Produce dynamic library instead of static archive" ON)
     62
     63## Add iconv to include directories
     64
     65FIND_PACKAGE(ICONV REQUIRED)
     66INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR})
     67
     68## Check iconv_const
     69
     70INCLUDE(CheckCXXSourceCompiles)
     71
     72SET(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${ICONV_INCLUDE_DIR})
     73CHECK_CXX_SOURCE_COMPILES(
     74    "
     75    #include <iconv.h>
     76    // this declaration will fail when there already exists a non const char** version which returns size_t
     77    double iconv(iconv_t cd,  char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
     78    int main() { return 0; }
     79    "
     80    HAVE_ICONV_CONST
     81)
     82
     83# TODO: better way of config
     84
     85IF(HAVE_ICONV_CONST)
     86  ADD_DEFINITIONS(-DHAVE_ICONV_CONST)
     87ELSE(HAVE_ICONV_CONST)
     88  REMOVE_DEFINITIONS(-DHAVE_ICONV_CONST)
     89ENDIF(HAVE_ICONV_CONST)
     90
     91## TinyGetText library compilation
     92
     93## build list of source files
     94
     95FILE(GLOB TINYGETTEXT_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} tinygettext/*.cpp)
     96FILE(GLOB TINYGETTEXT_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} tinygettext/*.hpp)
     97
     98## define a target for building the library
     99
     100ADD_LIBRARY(tinygettext ${TINYGETTEXT_SOURCES})
     101
     102## Add tinygettext dir to search path
     103
     104INCLUDE_DIRECTORIES(${tinygettext_SOURCE_DIR})
     105
     106## Debug options
     107
     108OPTION(WERROR "Stops on first compiler warning in debug mode" OFF)
     109IF(CMAKE_COMPILER_IS_GNUCC)
     110  ADD_DEFINITIONS(-O3 -Wall -Wextra -Weffc++ -pedantic)
     111  # -ansi fails in MinGW
     112  OPTION(WARNINGS "Enable long list of warnings for compiler to check" ON)
     113  IF(WARNINGS)
     114    ADD_DEFINITIONS(
     115          -Wabi  -Wctor-dtor-privacy
     116          -Wstrict-null-sentinel
     117          -Wold-style-cast
     118          -Woverloaded-virtual
     119          -Wsign-promo -Wswitch-enum
     120          -Wcast-align  -Wcast-qual
     121          -Wdisabled-optimization
     122          -Wfloat-equal
     123          -Wformat=2
     124          -Winit-self
     125          -Winvalid-pch  -Wunsafe-loop-optimizations
     126          -Wlogical-op
     127          -Wmissing-format-attribute  -Wmissing-include-dirs -Wmissing-noreturn
     128          -Wpacked
     129          -Wredundant-decls
     130          -Wshadow
     131          -Wsign-conversion  -Wstack-protector
     132          -Wstrict-overflow=5
     133          -Wswitch-default  -Wswitch-enum
     134          -Wundef)
     135        # Still left:
     136        # -Wconversion  (find alternative to using toupper(int) on char)
     137        # -Wpadded      (DictionaryManager has a bool that sticks out)
     138  ENDIF(WARNINGS)
     139  IF(WERROR)
     140    ADD_DEFINITIONS(-Werror)
     141  ENDIF(WERROR)
     142ENDIF(CMAKE_COMPILER_IS_GNUCC)
     143
     144## Extra definitions
     145
     146ADD_DEFINITIONS(-DVERSION=\\\"${VERSION}\\\")
     147
     148## Generate test executables in the right place
     149
     150SET(EXECUTABLE_OUTPUT_PATH ${tinygettext_BINARY_DIR}/test)
     151
     152## Build tinygettext tests
     153
     154FOREACH(TEST tinygettext_test po_parser_test)
     155  ## Add target for tinygettext test
     156  ADD_EXECUTABLE(${TEST} test/${TEST}.cpp)
     157  ## Link with tinygettext library
     158  TARGET_LINK_LIBRARIES(${TEST} tinygettext)
     159  TARGET_LINK_LIBRARIES(${TEST} ${ICONV_LIBRARY})
     160ENDFOREACH(TEST)
     161
     162## Install tinygettext
     163
     164# use standardized variable name
     165SET(LIB_SUBDIR "lib${LIB_SUFFIX}"
     166    CACHE STRING "Subdirectory of prefix into which libraries are installed (e.g., lib32, lib64)")
     167
     168## prepare tinygettext.pc
     169CONFIGURE_FILE(tinygettext.pc.in tinygettext.pc @ONLY)
     170
     171INSTALL(TARGETS tinygettext
     172    ARCHIVE DESTINATION ${LIB_SUBDIR}
     173    LIBRARY DESTINATION ${LIB_SUBDIR})
     174INSTALL(FILES ${TINYGETTEXT_HEADERS}
     175    DESTINATION include/tinygettext)
     176INSTALL(FILES ${tinygettext_BINARY_DIR}/tinygettext.pc
     177    DESTINATION ${LIB_SUBDIR}/pkgconfig)
  • new file libraries/source/tinygettext/COPYING

    diff --git a/libraries/source/tinygettext/COPYING b/libraries/source/tinygettext/COPYING
    new file mode 100644
    index 0000000..c45fcd8
    - +  
     1                    GNU GENERAL PUBLIC LICENSE
     2                       Version 2, June 1991
     3
     4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
     5 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
     6 Everyone is permitted to copy and distribute verbatim copies
     7 of this license document, but changing it is not allowed.
     8
     9                            Preamble
     10
     11  The licenses for most software are designed to take away your
     12freedom to share and change it.  By contrast, the GNU General Public
     13License is intended to guarantee your freedom to share and change free
     14software--to make sure the software is free for all its users.  This
     15General Public License applies to most of the Free Software
     16Foundation's software and to any other program whose authors commit to
     17using it.  (Some other Free Software Foundation software is covered by
     18the GNU Lesser General Public License instead.)  You can apply it to
     19your programs, too.
     20
     21  When we speak of free software, we are referring to freedom, not
     22price.  Our General Public Licenses are designed to make sure that you
     23have the freedom to distribute copies of free software (and charge for
     24this service if you wish), that you receive source code or can get it
     25if you want it, that you can change the software or use pieces of it
     26in new free programs; and that you know you can do these things.
     27
     28  To protect your rights, we need to make restrictions that forbid
     29anyone to deny you these rights or to ask you to surrender the rights.
     30These restrictions translate to certain responsibilities for you if you
     31distribute copies of the software, or if you modify it.
     32
     33  For example, if you distribute copies of such a program, whether
     34gratis or for a fee, you must give the recipients all the rights that
     35you have.  You must make sure that they, too, receive or can get the
     36source code.  And you must show them these terms so they know their
     37rights.
     38
     39  We protect your rights with two steps: (1) copyright the software, and
     40(2) offer you this license which gives you legal permission to copy,
     41distribute and/or modify the software.
     42
     43  Also, for each author's protection and ours, we want to make certain
     44that everyone understands that there is no warranty for this free
     45software.  If the software is modified by someone else and passed on, we
     46want its recipients to know that what they have is not the original, so
     47that any problems introduced by others will not reflect on the original
     48authors' reputations.
     49
     50  Finally, any free program is threatened constantly by software
     51patents.  We wish to avoid the danger that redistributors of a free
     52program will individually obtain patent licenses, in effect making the
     53program proprietary.  To prevent this, we have made it clear that any
     54patent must be licensed for everyone's free use or not licensed at all.
     55
     56  The precise terms and conditions for copying, distribution and
     57modification follow.
     58
     59                    GNU GENERAL PUBLIC LICENSE
     60   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
     61
     62  0. This License applies to any program or other work which contains
     63a notice placed by the copyright holder saying it may be distributed
     64under the terms of this General Public License.  The "Program", below,
     65refers to any such program or work, and a "work based on the Program"
     66means either the Program or any derivative work under copyright law:
     67that is to say, a work containing the Program or a portion of it,
     68either verbatim or with modifications and/or translated into another
     69language.  (Hereinafter, translation is included without limitation in
     70the term "modification".)  Each licensee is addressed as "you".
     71
     72Activities other than copying, distribution and modification are not
     73covered by this License; they are outside its scope.  The act of
     74running the Program is not restricted, and the output from the Program
     75is covered only if its contents constitute a work based on the
     76Program (independent of having been made by running the Program).
     77Whether that is true depends on what the Program does.
     78
     79  1. You may copy and distribute verbatim copies of the Program's
     80source code as you receive it, in any medium, provided that you
     81conspicuously and appropriately publish on each copy an appropriate
     82copyright notice and disclaimer of warranty; keep intact all the
     83notices that refer to this License and to the absence of any warranty;
     84and give any other recipients of the Program a copy of this License
     85along with the Program.
     86
     87You may charge a fee for the physical act of transferring a copy, and
     88you may at your option offer warranty protection in exchange for a fee.
     89
     90  2. You may modify your copy or copies of the Program or any portion
     91of it, thus forming a work based on the Program, and copy and
     92distribute such modifications or work under the terms of Section 1
     93above, provided that you also meet all of these conditions:
     94
     95    a) You must cause the modified files to carry prominent notices
     96    stating that you changed the files and the date of any change.
     97
     98    b) You must cause any work that you distribute or publish, that in
     99    whole or in part contains or is derived from the Program or any
     100    part thereof, to be licensed as a whole at no charge to all third
     101    parties under the terms of this License.
     102
     103    c) If the modified program normally reads commands interactively
     104    when run, you must cause it, when started running for such
     105    interactive use in the most ordinary way, to print or display an
     106    announcement including an appropriate copyright notice and a
     107    notice that there is no warranty (or else, saying that you provide
     108    a warranty) and that users may redistribute the program under
     109    these conditions, and telling the user how to view a copy of this
     110    License.  (Exception: if the Program itself is interactive but
     111    does not normally print such an announcement, your work based on
     112    the Program is not required to print an announcement.)
     113
     114These requirements apply to the modified work as a whole.  If
     115identifiable sections of that work are not derived from the Program,
     116and can be reasonably considered independent and separate works in
     117themselves, then this License, and its terms, do not apply to those
     118sections when you distribute them as separate works.  But when you
     119distribute the same sections as part of a whole which is a work based
     120on the Program, the distribution of the whole must be on the terms of
     121this License, whose permissions for other licensees extend to the
     122entire whole, and thus to each and every part regardless of who wrote it.
     123
     124Thus, it is not the intent of this section to claim rights or contest
     125your rights to work written entirely by you; rather, the intent is to
     126exercise the right to control the distribution of derivative or
     127collective works based on the Program.
     128
     129In addition, mere aggregation of another work not based on the Program
     130with the Program (or with a work based on the Program) on a volume of
     131a storage or distribution medium does not bring the other work under
     132the scope of this License.
     133
     134  3. You may copy and distribute the Program (or a work based on it,
     135under Section 2) in object code or executable form under the terms of
     136Sections 1 and 2 above provided that you also do one of the following:
     137
     138    a) Accompany it with the complete corresponding machine-readable
     139    source code, which must be distributed under the terms of Sections
     140    1 and 2 above on a medium customarily used for software interchange; or,
     141
     142    b) Accompany it with a written offer, valid for at least three
     143    years, to give any third party, for a charge no more than your
     144    cost of physically performing source distribution, a complete
     145    machine-readable copy of the corresponding source code, to be
     146    distributed under the terms of Sections 1 and 2 above on a medium
     147    customarily used for software interchange; or,
     148
     149    c) Accompany it with the information you received as to the offer
     150    to distribute corresponding source code.  (This alternative is
     151    allowed only for noncommercial distribution and only if you
     152    received the program in object code or executable form with such
     153    an offer, in accord with Subsection b above.)
     154
     155The source code for a work means the preferred form of the work for
     156making modifications to it.  For an executable work, complete source
     157code means all the source code for all modules it contains, plus any
     158associated interface definition files, plus the scripts used to
     159control compilation and installation of the executable.  However, as a
     160special exception, the source code distributed need not include
     161anything that is normally distributed (in either source or binary
     162form) with the major components (compiler, kernel, and so on) of the
     163operating system on which the executable runs, unless that component
     164itself accompanies the executable.
     165
     166If distribution of executable or object code is made by offering
     167access to copy from a designated place, then offering equivalent
     168access to copy the source code from the same place counts as
     169distribution of the source code, even though third parties are not
     170compelled to copy the source along with the object code.
     171
     172  4. You may not copy, modify, sublicense, or distribute the Program
     173except as expressly provided under this License.  Any attempt
     174otherwise to copy, modify, sublicense or distribute the Program is
     175void, and will automatically terminate your rights under this License.
     176However, parties who have received copies, or rights, from you under
     177this License will not have their licenses terminated so long as such
     178parties remain in full compliance.
     179
     180  5. You are not required to accept this License, since you have not
     181signed it.  However, nothing else grants you permission to modify or
     182distribute the Program or its derivative works.  These actions are
     183prohibited by law if you do not accept this License.  Therefore, by
     184modifying or distributing the Program (or any work based on the
     185Program), you indicate your acceptance of this License to do so, and
     186all its terms and conditions for copying, distributing or modifying
     187the Program or works based on it.
     188
     189  6. Each time you redistribute the Program (or any work based on the
     190Program), the recipient automatically receives a license from the
     191original licensor to copy, distribute or modify the Program subject to
     192these terms and conditions.  You may not impose any further
     193restrictions on the recipients' exercise of the rights granted herein.
     194You are not responsible for enforcing compliance by third parties to
     195this License.
     196
     197  7. If, as a consequence of a court judgment or allegation of patent
     198infringement or for any other reason (not limited to patent issues),
     199conditions are imposed on you (whether by court order, agreement or
     200otherwise) that contradict the conditions of this License, they do not
     201excuse you from the conditions of this License.  If you cannot
     202distribute so as to satisfy simultaneously your obligations under this
     203License and any other pertinent obligations, then as a consequence you
     204may not distribute the Program at all.  For example, if a patent
     205license would not permit royalty-free redistribution of the Program by
     206all those who receive copies directly or indirectly through you, then
     207the only way you could satisfy both it and this License would be to
     208refrain entirely from distribution of the Program.
     209
     210If any portion of this section is held invalid or unenforceable under
     211any particular circumstance, the balance of the section is intended to
     212apply and the section as a whole is intended to apply in other
     213circumstances.
     214
     215It is not the purpose of this section to induce you to infringe any
     216patents or other property right claims or to contest validity of any
     217such claims; this section has the sole purpose of protecting the
     218integrity of the free software distribution system, which is
     219implemented by public license practices.  Many people have made
     220generous contributions to the wide range of software distributed
     221through that system in reliance on consistent application of that
     222system; it is up to the author/donor to decide if he or she is willing
     223to distribute software through any other system and a licensee cannot
     224impose that choice.
     225
     226This section is intended to make thoroughly clear what is believed to
     227be a consequence of the rest of this License.
     228
     229  8. If the distribution and/or use of the Program is restricted in
     230certain countries either by patents or by copyrighted interfaces, the
     231original copyright holder who places the Program under this License
     232may add an explicit geographical distribution limitation excluding
     233those countries, so that distribution is permitted only in or among
     234countries not thus excluded.  In such case, this License incorporates
     235the limitation as if written in the body of this License.
     236
     237  9. The Free Software Foundation may publish revised and/or new versions
     238of the General Public License from time to time.  Such new versions will
     239be similar in spirit to the present version, but may differ in detail to
     240address new problems or concerns.
     241
     242Each version is given a distinguishing version number.  If the Program
     243specifies a version number of this License which applies to it and "any
     244later version", you have the option of following the terms and conditions
     245either of that version or of any later version published by the Free
     246Software Foundation.  If the Program does not specify a version number of
     247this License, you may choose any version ever published by the Free Software
     248Foundation.
     249
     250  10. If you wish to incorporate parts of the Program into other free
     251programs whose distribution conditions are different, write to the author
     252to ask for permission.  For software which is copyrighted by the Free
     253Software Foundation, write to the Free Software Foundation; we sometimes
     254make exceptions for this.  Our decision will be guided by the two goals
     255of preserving the free status of all derivatives of our free software and
     256of promoting the sharing and reuse of software generally.
     257
     258                            NO WARRANTY
     259
     260  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
     261FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
     262OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
     263PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
     264OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     265MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
     266TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
     267PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
     268REPAIR OR CORRECTION.
     269
     270  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
     271WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
     272REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
     273INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
     274OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
     275TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
     276YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
     277PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
     278POSSIBILITY OF SUCH DAMAGES.
     279
     280                     END OF TERMS AND CONDITIONS
     281
     282            How to Apply These Terms to Your New Programs
     283
     284  If you develop a new program, and you want it to be of the greatest
     285possible use to the public, the best way to achieve this is to make it
     286free software which everyone can redistribute and change under these terms.
     287
     288  To do so, attach the following notices to the program.  It is safest
     289to attach them to the start of each source file to most effectively
     290convey the exclusion of warranty; and each file should have at least
     291the "copyright" line and a pointer to where the full notice is found.
     292
     293    tinygettext - A gettext replacement that works directly on .po files
     294    Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     295
     296    This program is free software; you can redistribute it and/or modify
     297    it under the terms of the GNU General Public License as published by
     298    the Free Software Foundation; either version 2 of the License, or
     299    (at your option) any later version.
     300
     301    This program is distributed in the hope that it will be useful,
     302    but WITHOUT ANY WARRANTY; without even the implied warranty of
     303    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     304    GNU General Public License for more details.
     305
     306    You should have received a copy of the GNU General Public License along
     307    with this program; if not, write to the Free Software Foundation, Inc.,
     308    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     309
     310Also add information on how to contact you by electronic and paper mail.
     311
     312If the program is interactive, make it output a short notice like this
     313when it starts in an interactive mode:
     314
     315    Gnomovision version 69, Copyright (C) year name of author
     316    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
     317    This is free software, and you are welcome to redistribute it
     318    under certain conditions; type `show c' for details.
     319
     320The hypothetical commands `show w' and `show c' should show the appropriate
     321parts of the General Public License.  Of course, the commands you use may
     322be called something other than `show w' and `show c'; they could even be
     323mouse-clicks or menu items--whatever suits your program.
     324
     325You should also get your employer (if you work as a programmer) or your
     326school, if any, to sign a "copyright disclaimer" for the program, if
     327necessary.  Here is a sample; alter the names:
     328
     329  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
     330  `Gnomovision' (which makes passes at compilers) written by James Hacker.
     331
     332  <signature of Ty Coon>, 1 April 1989
     333  Ty Coon, President of Vice
     334
     335This General Public License does not permit incorporating your program into
     336proprietary programs.  If your program is a subroutine library, you may
     337consider it more useful to permit linking proprietary applications with the
     338library.  If this is what you want to do, use the GNU Lesser General
     339Public License instead of this License.
  • new file libraries/source/tinygettext/FindICONV.cmake

    diff --git a/libraries/source/tinygettext/FindICONV.cmake b/libraries/source/tinygettext/FindICONV.cmake
    new file mode 100644
    index 0000000..65e3609
    - +  
     1#
     2#  Copyright (c) 2006, Peter Kümmel, <syntheticpp@gmx.net>
     3#
     4#  Redistribution and use in source and binary forms, with or without
     5#  modification, are permitted provided that the following conditions
     6#  are met:
     7#
     8#  1. Redistributions of source code must retain the copyright
     9#     notice, this list of conditions and the following disclaimer.
     10#  2. Redistributions in binary form must reproduce the copyright
     11#     notice, this list of conditions and the following disclaimer in the
     12#     documentation and/or other materials provided with the distribution.
     13#  3. The name of the author may not be used to endorse or promote products
     14#     derived from this software without specific prior written permission.
     15#
     16#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17#  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18#  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19#  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20#  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     21#  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     22#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     23#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     24#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     25#  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26#
     27
     28set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true)
     29
     30if (ICONV_INCLUDE_DIR)
     31  # Already in cache, be silent
     32  set(ICONV_FIND_QUIETLY TRUE)
     33endif()
     34
     35find_path(ICONV_INCLUDE_DIR iconv.h
     36 /usr/include
     37 /usr/local/include)
     38
     39set(POTENTIAL_ICONV_LIBS iconv libiconv libiconv2)
     40
     41find_library(ICONV_LIBRARY NAMES ${POTENTIAL_ICONV_LIBS}
     42    PATHS /usr/lib /usr/local/lib)
     43
     44if(WIN32)
     45    set(ICONV_DLL_NAMES iconv.dll  libiconv.dll libiconv2.dll)
     46    find_file(ICONV_DLL
     47                    NAMES ${ICONV_DLL_NAMES}
     48                    PATHS ENV PATH
     49                    NO_DEFAULT_PATH)
     50    find_file(ICONV_DLL_HELP
     51                    NAMES ${ICONV_DLL_NAMES}
     52                    PATHS ENV PATH
     53                    ${ICONV_INCLUDE_DIR}/../bin)
     54    if(ICONV_FIND_REQUIRED)
     55        if(NOT ICONV_DLL AND NOT ICONV_DLL_HELP)
     56            message(FATAL_ERROR "Could not find iconv.dll, please add correct your PATH environment variable")
     57        endif()
     58        if(NOT ICONV_DLL AND ICONV_DLL_HELP)
     59            get_filename_component(ICONV_DLL_HELP ${ICONV_DLL_HELP} PATH)
     60            message(STATUS)
     61            message(STATUS "Could not find iconv.dll in standard search path, please add ")
     62            message(STATUS "${ICONV_DLL_HELP}")
     63            message(STATUS "to your PATH environment variable.")
     64            message(STATUS)
     65            message(FATAL_ERROR "exit cmake")
     66        endif()
     67    endif()
     68    if(ICONV_INCLUDE_DIR AND ICONV_LIBRARY AND ICONV_DLL)
     69        set(ICONV_FOUND TRUE)
     70    endif()
     71else()
     72    include(CheckFunctionExists)
     73    check_function_exists(iconv HAVE_ICONV_IN_LIBC)
     74    if(ICONV_INCLUDE_DIR AND HAVE_ICONV_IN_LIBC)
     75        set(ICONV_FOUND TRUE)
     76        set(ICONV_LIBRARY  CACHE TYPE STRING FORCE)
     77    endif()
     78    if(ICONV_INCLUDE_DIR AND ICONV_LIBRARY)
     79        set(ICONV_FOUND TRUE)
     80    endif()
     81endif()
     82
     83
     84
     85if(ICONV_FOUND)
     86   if(NOT ICONV_FIND_QUIETLY)
     87      message(STATUS "Found iconv library: ${ICONV_LIBRARY}")
     88      #message(STATUS "Found iconv   dll  : ${ICONV_DLL}")
     89   endif()
     90else()
     91   if(ICONV_FIND_REQUIRED)
     92      message(STATUS "Looked for iconv library named ${POTENTIAL_ICONV_LIBS}.")
     93      message(STATUS "Found no acceptable iconv library. This is fatal.")
     94      message(STATUS "iconv header: ${ICONV_INCLUDE_DIR}")
     95      message(STATUS "iconv lib   : ${ICONV_LIBRARY}")
     96      message(FATAL_ERROR "Could NOT find iconv library")
     97   endif()
     98endif()
     99
     100mark_as_advanced(ICONV_LIBRARY ICONV_INCLUDE_DIR)
  • new file libraries/source/tinygettext/NEWS

    diff --git a/libraries/source/tinygettext/NEWS b/libraries/source/tinygettext/NEWS
    new file mode 100644
    index 0000000..fcf6ef6
    - +  
     1tinygettext 0.0.1 - (??. Feb 2009)
     2==================================
     3
     4* initial release
     5
     6# EOF #
  • new file libraries/source/tinygettext/README

    diff --git a/libraries/source/tinygettext/README b/libraries/source/tinygettext/README
    new file mode 100644
    index 0000000..0eeabe9
    - +  
     1tinygettext
     2===========
     3
     4tinygettext is a minimal gettext() replacement written in C++. It can
     5read .po files directly and doesn't need .mo files generated from .po.
     6It also can read the .po files from arbitary locations, so its much
     7better suited for non-Unix systems and situations in which one wants
     8to store or distrubite .po files seperatly from the software itself.
     9
     10
     11
     12Detecting the locale setting
     13============================
     14
     15Different operating systems store the default locale in different
     16places; a portable way to find it is provided by FindLocale:
     17
     18 * http://icculus.org/~aspirin/findlocale/
     19
     20
     21# EOF #
  • new file libraries/source/tinygettext/SConstruct

    diff --git a/libraries/source/tinygettext/SConstruct b/libraries/source/tinygettext/SConstruct
    new file mode 100644
    index 0000000..cc5f9ec
    - +  
     1# -*- python -*-
     2
     3env = Environment(CXXFLAGS=['-O0',
     4                            '-g3',
     5                            '-Wall',
     6                            '-Wcast-qual',
     7                            '-Wconversion',
     8                            '-Weffc++',
     9                            '-Werror',
     10                            '-Wextra',
     11                            '-Winit-self',
     12                            '-Wno-unused-parameter',
     13                            '-Wnon-virtual-dtor',
     14                            '-Wshadow',
     15                            '-ansi',
     16                            '-pedantic',
     17                            ],
     18                  CPPPATH=['tinygettext', '.'])
     19
     20# env.ParseConfig("sdl-config --cflags --libs")
     21# env['CPPDEFINES'] += HAVE_SDL
     22
     23libtinygettext = env.SharedLibrary('tinygettext/tinygettext',
     24                                   ['tinygettext/tinygettext.cpp',
     25                                    'tinygettext/language.cpp',
     26                                    'tinygettext/plural_forms.cpp',
     27                                    'tinygettext/dictionary.cpp',
     28                                    'tinygettext/dictionary_manager.cpp',
     29                                    'tinygettext/unix_file_system.cpp',
     30                                    'tinygettext/po_parser.cpp',
     31                                    'tinygettext/iconv.cpp',
     32                                    'tinygettext/log.cpp'])
     33
     34env.Program('test/tinygettext_test', ['test/tinygettext_test.cpp', libtinygettext])
     35env.Program('test/po_parser_test', ['test/po_parser_test.cpp', libtinygettext])
     36
     37# EOF #
  • new file libraries/source/tinygettext/TODO

    diff --git a/libraries/source/tinygettext/TODO b/libraries/source/tinygettext/TODO
    new file mode 100644
    index 0000000..899dc2c
    - +  
     1tinygettext API related stuff:
     2==============================
     3
     4* translate, translate_ctxt, translate_ctxt_plural, ... could be
     5  unified via overloading, not sure if that is a good idea. For the
     6  same reason add_translation() could be de-overloaded, to
     7  add_translation_ctxt, ...
     8
     9* iconv handling needs cleanup and more flexibility, since some
     10  systems don't provide iconv or only through SDL
     11
     12* Customizability could use grouping and documentation or other means
     13  to make it more obvious:
     14
     15  - POParser::pedantic
     16  - iconv (???)
     17  - logging (log_callback(std::string))
     18
     19* handle errors better, not with log_* stream, cases of errors:
     20
     21  - couldn't translate
     22  - collision while adding translation
     23  - failure to open file or directory
     24  - unknown language
     25  - iconv failure to convert charset
     26
     27* ABI management/freezing. If tinygettext is to benefit other projects,
     28  it should be able to provide a stable API (and, better, ABI).
     29
     30
     31tinygettext implementation details:
     32===================================
     33
     34* with PluralForms moved into Dictionary a lot of the Langugae stuff
     35  is pointless
     36
     37* get rid of goto
     38
     39* POParser can handle Big5, but needs testing.
     40  Big5 is one byte for ASCII letters and two bytes for chinese ones,
     41  this means some two byte characters collide with '\', some .po files
     42  seem to escape the \ properly so that the string can be read as
     43  usual, while others don't.
     44
     45* _()  -> getext() (gettext default)
     46  N_(id) -> gettext_noop(id) (gettext default)
     47  C_(ctxt, id) -> pgettext(ctxt, id) (Gnome does this: http://library.gnome.org/devel/glib/2.16/glib-I18N.html#Q-:CAPS)
     48  NC_(ctxt, id) -> pgettext(ctxt, id) (Gnome does this: http://library.gnome.org/devel/glib/2.16/glib-I18N.html#Q-:CAPS)
     49
     50* figure out how up-to-date other tinygettext implementations in the
     51  wild are (LinCity):
     52
     53  Pingus:
     54  -------
     55  dictionary.hpp (synced with Pingus)
     56  dictionary_manager.hpp (PhysFS vs opendir())
     57  language_def.hpp (synced with Pingus)
     58  po_file_reader.hpp (UTF-8 0xef-'header', lots of stuff from mathner)
     59  tinygettext.hpp (iconv vs convert)
     60
     61
    062
     63Random Unimportant Stuff
     64========================
     65
     66* a hashmap instead of std::map might be a good idea
     67
     68* support for .gmo files would be cool
     69
    170
     71tinygettext Documentation:
     72==========================
     73
     74* recommend a way to handle translation of speech and other data files
     75
     76* document how to use tinygettext and make it work like gettext
  • new file libraries/source/tinygettext/build.sh

    +
    
    +# EOF #
    diff --git a/libraries/source/tinygettext/build.sh b/libraries/source/tinygettext/build.sh
    new file mode 100755
    index 0000000..1f9f99b
    - +  
     1#!/bin/sh
     2
     3set -e
     4
     5JOBS=${JOBS:="-j2"}
     6
     7echo "Building TinyGettext…"
     8echo
     9
     10scons ${JOBS}
     11
     12mkdir -p lib/
     13
     14if [ "`uname -s`" = "Darwin" ]
     15then
     16  extension=dylib
     17else
     18  extension=so
     19fi
     20
     21filepath=tinygettext/libtinygettext.${extension}
     22cp $filepath lib/
     23cp $filepath ../../../binaries/system/
  • new file libraries/source/tinygettext/test/broken.po

    diff --git a/libraries/source/tinygettext/test/broken.po b/libraries/source/tinygettext/test/broken.po
    new file mode 100644
    index 0000000..c841a73
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR Free Software Foundation, Inc.
     3# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     4#
     5#, fuzzy
     6msgid ""
     7msgstr ""
     8"Project-Id-Version: PACKAGE VERSION\n"
     9"Report-Msgid-Bugs-To: \n"
     10"POT-Creation-Date: 2009-01-30 08:01+0100\n"
     11"PO-Revision-Date: 2009-01-30 08:39+0100\n"
     12"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     13"Language-Team: LANGUAGE <LL@li.org>\n"
     14"MIME-Version: 1.0\n"
     15"Content-Type: text/plain; charset=UTF-8\n"
     16"Content-Transfer-Encoding: 8bit\n"
     17
     18#: helloworld.cpp:7
     19msgid "Short Hello World"
     20msgstr "kurzes Hallo Welt
     21
     22#: helloworld.cpp:8 helloworld.cpp:14
     23#, fuzzy
     24msgid "Hello World"
     25msgid_plural "Hello Worlds"
     26msgstr[0] "Hallo Welt (singular)"
     27msgstr[1] "Hallo Welt (plural)"
     28msgstr[10] "Hallo Welt (plural)"
     29
     30#: helloworld.cpp:10 helloworld.cpp:16
     31#, fuzzy
     32msgctxt ""
     33msgid "Hello World"
     34msgid_plural "Hello Worlds"
     35msgstr[0] Hallo Welt (singular) mit leerem Kontext"
     36msgstr[1] "Hallo Welt (plural) mit leerem Kontext"
     37
     38#: helloworld.cpp:11 helloworld.cpp:17
     39msgctxt "console"
     40msgid "Hello World"
     41msgid_plural "Hello Worlds"
     42msgstr[0]"Hallo Welt (singular) in der Console"
     43msgstr[1] "Hallo Welt (plural) in der Console"
     44
     45#: helloworld.cpp:13
     46msgid "gui"
     47msgid_plural "Hello World"
     48msgstr[0]   "Hallo Welt (singular)"
     49msgstr[1] "Hallo Welt (plural)"
     50
     51#: helloworld.cpp:18
     52#, fuzzy
     53msgctxt "gui"
     54msgid "Hello World"
     55msgid_plural "Hello Worlds"
     56msgstr[0] "Hallo Welt im GUI"
     57msgstr[1] "Hallo Welt (plural) im GUI"
  • new file libraries/source/tinygettext/test/game/de.po

    diff --git a/libraries/source/tinygettext/test/game/de.po b/libraries/source/tinygettext/test/game/de.po
    new file mode 100644
    index 0000000..a92db3b
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR Free Software Foundation, Inc.
     3# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     4#
     5msgid ""
     6msgstr ""
     7"Project-Id-Version: Pingus 0.6.0\n"
     8"Report-Msgid-Bugs-To: \n"
     9"POT-Creation-Date: 2004-04-09 18:37+0000\n"
     10"PO-Revision-Date: 2003-04-15 17:31+0200\n"
     11"Last-Translator: David Philippi <david@torangan.de>\n"
     12"Language-Team: David Philippi <david@torangan.de>, Ingo Ruhnke <grumbel@gmx."
     13"de>, Giray Devlet <giray@devlet.cc>\n"
     14"MIME-Version: 1.0\n"
     15"Content-Type: text/plain; charset=ISO-8859-1\n"
     16"Content-Transfer-Encoding: 8bit\n"
     17
     18#: src/actions/bridger.cxx:48 src/actions/bridger.cxx:232
     19#: src/pingu_enums.cxx:40
     20msgid "Bridger"
     21msgstr "Bridger"
     22
     23#: src/config.cxx:73
     24msgid "Couldn't open: "
     25msgstr "Datei oder Verzeichnis konnte nicht geöffnet werden: "
     26
     27#: src/config.cxx:172
     28msgid "Unexpected char: '"
     29msgstr "Unerwartetes Zeichen: '"
     30
     31#: src/config.cxx:206
     32msgid "Unexpected char '"
     33msgstr "Unerwartetes Zeichen: '"
     34
     35#: src/credits.cxx:48
     36msgid "-Idea"
     37msgstr "-Idee"
     38
     39#: src/credits.cxx:52
     40msgid "-Maintaining"
     41msgstr "-Verwaltung"
     42
     43#: src/credits.cxx:56
     44msgid "-Programming"
     45msgstr "-Programmierung"
     46
     47#: src/credits.cxx:62
     48msgid "-Porting (Win32)"
     49msgstr "-Portierung (Win32)"
     50
     51#: src/credits.cxx:70
     52msgid "-Gfx"
     53msgstr "-Grafiken"
     54
     55#: src/credits.cxx:82
     56msgid "-Music"
     57msgstr "-Musik"
     58
     59#: src/credits.cxx:88
     60msgid "-Level Design"
     61msgstr "-Level Design"
     62
     63#: src/credits.cxx:93
     64msgid "-Story"
     65msgstr "-Geschichte"
     66
     67#: src/credits.cxx:97
     68msgid "-Translation"
     69msgstr "-Übersetzung"
     70
     71#: src/credits.cxx:112
     72msgid "-Special"
     73msgstr "-Besonderen"
     74
     75#: src/credits.cxx:113
     76msgid "-Thanks to"
     77msgstr "-Dank an"
     78
     79#: src/credits.cxx:154
     80msgid "_And a very Special Thanks"
     81msgstr "_Und einen besonderen Dank"
     82
     83#: src/credits.cxx:155
     84msgid "_to all the people who"
     85msgstr "_an alle Leute die"
     86
     87#: src/credits.cxx:156
     88msgid "_contribute to"
     89msgstr "_an Freier Software"
     90
     91#: src/credits.cxx:157
     92msgid "_Free Software!"
     93msgstr "_mitarbeiten!"
     94
     95#: src/credits.cxx:163
     96msgid "_Thank you for"
     97msgstr "_Dankeschön fürs"
     98
     99#: src/credits.cxx:164
     100msgid "_playing!"
     101msgstr "_spielen!"
     102
     103#: src/editor/editor_event.cxx:588
     104msgid "Enter filename to save as:"
     105msgstr "Datei speichern als:  "
     106
     107#: src/editor/editor_help_screen.cxx:43
     108msgid "Editor Helpscreen (hide with F1)"
     109msgstr "Editor Hilfe (ausblenden mit F1)"
     110
     111#: src/editor/editor_help_screen.cxx:51
     112msgid "F1 - show/hide this help screen"
     113msgstr "F1 - Hilfe anzeigen/verstecken"
     114
     115#: src/editor/editor_help_screen.cxx:52
     116msgid "F2 - launch level building tutorial"
     117msgstr "F2 - Starte Levelbau Tutorial"
     118
     119#: src/editor/editor_help_screen.cxx:53
     120msgid "F3 - toggle background color"
     121msgstr "F3 - Hintergrundfarbe aendern"
     122
     123#: src/editor/editor_help_screen.cxx:54
     124msgid "F4 - play/test the level"
     125msgstr "F4 - Level starten/testen"
     126
     127#: src/editor/editor_help_screen.cxx:55
     128msgid "F5 - load a level"
     129msgstr "Level Laden (F5)"
     130
     131#: src/editor/editor_help_screen.cxx:56
     132msgid "F6 - save this level"
     133msgstr "Level Speichern (F6)"
     134
     135#: src/editor/editor_help_screen.cxx:57
     136msgid "F7 - [unset]"
     137msgstr "F7 - [nicht belegt]"
     138
     139#: src/editor/editor_help_screen.cxx:58
     140msgid "F8 - quick save/backup save?!"
     141msgstr "F8 - Schnellspeichern/Backup"
     142
     143#: src/editor/editor_help_screen.cxx:59
     144msgid "F9 - change level width and height"
     145msgstr "F9 - Level Höhe und Breite ändern"
     146
     147#: src/editor/editor_help_screen.cxx:60
     148msgid "F10 - [unset]"
     149msgstr "F10 - [nicht belegt]"
     150
     151#: src/editor/editor_help_screen.cxx:61
     152msgid "F11 - toggle fps counter"
     153msgstr "F11 - fps Zähler an/aus"
     154
     155#: src/editor/editor_help_screen.cxx:62
     156msgid "F12 - make screenshot"
     157msgstr "F12 - Screenshot erstellen"
     158
     159#: src/editor/editor_help_screen.cxx:66
     160msgid "Home - increase object size"
     161msgstr "Einfg - Objekt vergrößern"
     162
     163#: src/editor/editor_help_screen.cxx:67
     164msgid "End  - decrease object size"
     165msgstr "Ende - Objekt verkleinern"
     166
     167#: src/editor/editor_help_screen.cxx:68
     168msgid "Cursor Keys - Move object"
     169msgstr "Cursor Tasten - Objekt bewegen"
     170
     171#: src/editor/editor_help_screen.cxx:69
     172msgid "Shift 'Cursor Keys' - Move objects fast "
     173msgstr "Shift Cursor Tasten - Objekt schneller bewegen"
     174
     175#: src/editor/editor_help_screen.cxx:70
     176msgid "PageUp   - level object up"
     177msgstr "Bild rauf - Objekt nach oben"
     178
     179#: src/editor/editor_help_screen.cxx:71
     180msgid "PageDown - level object down"
     181msgstr "Bild runter - Objekt nach unten"
     182
     183#: src/editor/editor_help_screen.cxx:72
     184msgid "Shift PageUp   - increase objects z-pos by 50"
     185msgstr "Shift Bild rauf - Objekt nach oben"
     186
     187#: src/editor/editor_help_screen.cxx:73
     188msgid "Shift PageDown - decrease objects z-pos by 50"
     189msgstr "Shift Bild runter - Objekt nach unten"
     190
     191#: src/editor/editor_help_screen.cxx:74
     192msgid "Enter - Set default zoom (1:1)"
     193msgstr "Eingabe - Setze Standard Zoom (1:1)"
     194
     195#: src/editor/editor_help_screen.cxx:75
     196msgid "d - duplicate object"
     197msgstr "d - Objekt kopieren"
     198
     199#: src/editor/editor_help_screen.cxx:76
     200msgid "a - mark all objects"
     201msgstr "a - Alle Objekte markieren"
     202
     203#: src/editor/editor_help_screen.cxx:77
     204msgid "shift leftmouseclick - add object to selection"
     205msgstr "Shift + linke Maustaste - Objekt zur Auswahl tun"
     206
     207#: src/editor/editor_help_screen.cxx:78
     208msgid "leftmouseclick - select object"
     209msgstr "linke Maustaste - Objekt auswählen"
     210
     211#: src/editor/editor_help_screen.cxx:79
     212msgid "Insert - insert new object"
     213msgstr "Einfügen - neues Objekt einfügen"
     214
     215#: src/editor/editor_help_screen.cxx:80
     216msgid "Remove - remove selected object"
     217msgstr "Entfernen - entferne ausgewähltes Objekt"
     218
     219#: src/editor/editor_help_screen.cxx:81
     220msgid "g - ungroup/group current selection"
     221msgstr "g - Auswahl gruppieren / Gruppierung aufheben"
     222
     223#: src/editor/editor_help_screen.cxx:82
     224msgid "Ctrl PageUp   - increase objects z-pos by 1"
     225msgstr "Strg Bild rauf - z-pos des Objekts um 1 erhöhen"
     226
     227#: src/editor/editor_help_screen.cxx:83
     228msgid "Ctrl PageDown - decrease objects z-pos by 1"
     229msgstr "Strg Bild runter - z-pos des Objekts um 1 senken"
     230
     231#: src/editor/editor_help_screen.cxx:89
     232msgid "Naming Convention: <LEVELNAME><NUMBER>-<CREATOR>.plf"
     233msgstr "Dateinamensvorgabe: <LEVELNAME><NUMMER>-<AUTOR>.pfl"
     234
     235#: src/editor/editor_help_screen.cxx:91
     236msgid ""
     237"When you have created a level and want to have it in the next Pingus "
     238"release,\n"
     239"please mail it to pingus-devel@nongnu.org."
     240msgstr ""
     241"Falls Du einen Level erstellt hast und ihn gerne im nächsten Pingus\n"
     242"Release hättest, schicke ihn an pingus-devel@nongnu.org."
     243
     244#: src/editor/object_selector.cxx:106
     245msgid "1 - guillotine"
     246msgstr "1 - Guillotine"
     247
     248#: src/editor/object_selector.cxx:107
     249msgid "2 - hammer"
     250msgstr "2 - Hammer"
     251
     252#: src/editor/object_selector.cxx:108
     253msgid "3 - spike"
     254msgstr "3 - Stacheln"
     255
     256#: src/editor/object_selector.cxx:109
     257msgid "4 - laser_exit"
     258msgstr "4 - Laser Ausgang"
     259
     260#: src/editor/object_selector.cxx:110
     261msgid "5 - fake_exit"
     262msgstr "5 - Täusch Ausgang"
     263
     264#: src/editor/object_selector.cxx:111
     265msgid "6 - smasher"
     266msgstr "6 - Stampfer"
     267
     268#: src/editor/object_selector.cxx:112
     269msgid "7 - bumper"
     270msgstr "7 - Stosser"
     271
     272#: src/editor/object_selector.cxx:186
     273msgid "Select a WorldObj"
     274msgstr "Wähle ein WeltObjekt"
     275
     276#: src/editor/object_selector.cxx:187
     277msgid "1 - teleporter"
     278msgstr "1 - Teleporter"
     279
     280#: src/editor/object_selector.cxx:188
     281msgid "2 - switch and door"
     282msgstr "2 - Schalter und Tür"
     283
     284#: src/editor/object_selector.cxx:189
     285msgid "3 - ConveyorBelt"
     286msgstr "3 - Förderband"
     287
     288#: src/editor/object_selector.cxx:190
     289msgid "4 - IceBlock"
     290msgstr "4 - Eisblock"
     291
     292#: src/editor/object_selector.cxx:191
     293msgid "5 - InfoBox"
     294msgstr "5 - InfoBox"
     295
     296#: src/editor/object_selector.cxx:232
     297msgid "Select a weather"
     298msgstr "Wähle ein Wetter"
     299
     300#: src/editor/object_selector.cxx:233
     301msgid "1 - snow"
     302msgstr "1 - Schnee"
     303
     304#: src/editor/object_selector.cxx:234
     305msgid "2 - rain"
     306msgstr "2 - Regen"
     307
     308#: src/editor/object_selector.cxx:265
     309msgid "Select an entrance"
     310msgstr "Wähle einen Eingang"
     311
     312#: src/editor/object_selector.cxx:266
     313msgid "1 - generic"
     314msgstr "1 - allgemein"
     315
     316#: src/editor/object_selector.cxx:267
     317msgid "2 - woodthing"
     318msgstr "2 - hölzern"
     319
     320#: src/editor/object_selector.cxx:268
     321msgid "3 - cloud"
     322msgstr "3 - Wolke"
     323
     324#: src/editor/object_selector.cxx:269
     325msgid "h - entrance surface (hotspot)"
     326msgstr "h - Eingangs Grafik (hotspot)"
     327
     328#: src/editor/object_selector.cxx:343
     329msgid "What object type do you want?"
     330msgstr "Was für ein Objekt willst du?"
     331
     332#: src/editor/object_selector.cxx:344 src/editor/object_selector.cxx:400
     333msgid "h - Hotspot"
     334msgstr "h - Grafikelement (hotspot)"
     335
     336#: src/editor/object_selector.cxx:345
     337msgid "g - Groundpiece (ground) [not implemented]"
     338msgstr "g - Bodenstück (ground) [nicht implementiert]"
     339
     340#: src/editor/object_selector.cxx:394 src/editor/object_selector.cxx:562
     341msgid "Which object do you want?"
     342msgstr "Welches Objekt willst du?"
     343
     344#: src/editor/object_selector.cxx:395
     345msgid "g - Groundpiece (ground)"
     346msgstr "g - Bodenstück (ground)"
     347
     348#: src/editor/object_selector.cxx:396
     349msgid "s - Groundpiece (solid)"
     350msgstr "s - Bodenelement (Stahl)"
     351
     352#: src/editor/object_selector.cxx:397
     353msgid "b - Groundpiece (bridge)"
     354msgstr "b - Bodenelement (Bruecke)"
     355
     356#: src/editor/object_selector.cxx:398
     357msgid "n - Groundpiece (transparent)"
     358msgstr "n - Bodenelement (transparent)"
     359
     360#: src/editor/object_selector.cxx:399
     361msgid "r - Groundpiece (remove)"
     362msgstr "r - Bodenelement (entfernen)"
     363
     364#: src/editor/object_selector.cxx:401
     365msgid "e - Entrance"
     366msgstr "e - Eingang"
     367
     368#: src/editor/object_selector.cxx:402
     369msgid "x - Exit"
     370msgstr "x - Ausgang"
     371
     372#: src/editor/object_selector.cxx:403
     373msgid "l - Liquid"
     374msgstr "l - Flüssigkeit"
     375
     376#: src/editor/object_selector.cxx:404
     377msgid "w - Weather"
     378msgstr "w - Wetter"
     379
     380#: src/editor/object_selector.cxx:405
     381msgid "t - Traps"
     382msgstr "t - Falle"
     383
     384#: src/editor/object_selector.cxx:406
     385msgid "o - WorldObject"
     386msgstr "o - WeltObjekt"
     387
     388#: src/editor/object_selector.cxx:407
     389msgid "z - Background"
     390msgstr "z - Hintergrund"
     391
     392#: src/editor/object_selector.cxx:408
     393msgid "p - Prefab (ObjectGroup)"
     394msgstr "p - Prefab (Objekt Gruppe)"
     395
     396#: src/editor/object_selector.cxx:409
     397msgid "f - something from file (~/.pingus/images/)"
     398msgstr "f - etwas aus einer Datei (~/.pingus/images/)"
     399
     400#: src/editor/object_selector.cxx:498
     401msgid "Which prefab do you want?"
     402msgstr "Welche Prefab willst du?"
     403
     404#: src/editor/object_selector.cxx:563
     405msgid "1 - Surface Background"
     406msgstr "1 - Bild Hintergrund"
     407
     408#: src/editor/object_selector.cxx:564
     409msgid "2 - Solid Color Background"
     410msgstr "2 - Farb Hintergrund"
     411
     412#: src/editor/object_selector.cxx:565
     413msgid "3 - Starfield Background"
     414msgstr "3 - Sternen Hintergrund"
     415
     416#: src/editor/object_selector.cxx:566
     417msgid "4 - Thunderstorm Background"
     418msgstr "4 - Gewitter Hintergrund"
     419
     420#: src/editor/panel_icons.cxx:33
     421msgid "Load a level (F5)"
     422msgstr "Level laden (F5)"
     423
     424#: src/editor/panel_icons.cxx:46
     425msgid "Exit the editor (Escape)"
     426msgstr "Editor Beenden (Esc)"
     427
     428#: src/editor/panel_icons.cxx:58
     429msgid "Save this level (F6)"
     430msgstr "Level Speichern (F6)"
     431
     432#: src/editor/panel_icons.cxx:70
     433msgid "Delete marked objects (delete)"
     434msgstr "Markierte Objekte Löschen (Entfernen)"
     435
     436#: src/editor/panel_icons.cxx:82
     437msgid "Duplicate current object (d)"
     438msgstr "Aktuelles Object kopieren (d)"
     439
     440#: src/editor/panel_icons.cxx:94
     441msgid "Edit Level Properties"
     442msgstr "Leveleigenschaften ändern"
     443
     444#: src/editor/panel_icons.cxx:106
     445msgid "Edit Object Properties"
     446msgstr "Objekteigenschaften ändern"
     447
     448#: src/editor/panel_icons.cxx:118
     449msgid "Start the level and test it (F4)"
     450msgstr "Level starten und testen (F4)"
     451
     452#: src/editor/panel_icons.cxx:130
     453msgid "Create a new level from scratch"
     454msgstr "Neuen Level erstellen"
     455
     456#: src/editor/panel_icons.cxx:142
     457msgid "Insert an object (Insert)"
     458msgstr "Objekt einfuegen (Einfg)"
     459
     460#: src/editor/panel_icons.cxx:154
     461msgid "Zoom into a region"
     462msgstr "Einen Bereich vergrössern"
     463
     464#: src/editor/panel_icons.cxx:166
     465msgid "Zoom in"
     466msgstr "Vergroessern"
     467
     468#: src/editor/panel_icons.cxx:179
     469msgid "Zoom out"
     470msgstr "Verkleinern"
     471
     472#: src/editor/panel_icons.cxx:192
     473msgid "Setup Number of Actions"
     474msgstr "Stelle die Anzahl der Fähigkeiten ein"
     475
     476#: src/editor/panel_icons.cxx:204
     477msgid "Display Help Screen (F1)"
     478msgstr "Hilfe Anzeigen (F1)"
     479
     480#: src/exit_menu.cxx:48
     481msgid "Yes"
     482msgstr "Ja"
     483
     484#: src/exit_menu.cxx:81
     485msgid "No"
     486msgstr "Nein"
     487
     488#: src/exit_menu.cxx:114
     489msgid "Exit Pingus?"
     490msgstr "Pingus beenden?"
     491
     492#: src/fps_counter.cxx:48
     493msgid "unknown"
     494msgstr "unbekannt"
     495
     496#: src/game_time.cxx:70
     497msgid "unlimited"
     498msgstr "unbegrenzt"
     499
     500#: src/level_desc.cxx:74
     501msgid "Designed by "
     502msgstr "Erstellt von "
     503
     504#: src/level_desc.cxx:79
     505#, c-format
     506msgid "Pingus to Save: %d"
     507msgstr "Zu rettende Pingus: %d"
     508
     509#: src/level_desc.cxx:81
     510#, c-format
     511msgid "Number of Pingus: %d"
     512msgstr "Anzahl an Pingus: %d"
     513
     514#: src/level_desc.cxx:86
     515msgid "Loading..."
     516msgstr "Ladevorgang läuft..."
     517
     518#: src/level_desc.cxx:89
     519msgid "Loading finished. Press a mouse button to start the level"
     520msgstr ""
     521"Ladevorgang abgeschlossen. Drücke eine Maus Taste um den Level zu starten"
     522
     523#: src/level_result.cxx:61
     524msgid "Results:"
     525msgstr "Ergebnisse:"
     526
     527#: src/level_result.cxx:67
     528#, c-format
     529msgid "Pingus saved:   %3d/%3d"
     530msgstr "Gerettete Pingus: %3d/%3d"
     531
     532#: src/level_result.cxx:72
     533#, c-format
     534msgid "Pingus died:  %3d/%3d"
     535msgstr "Tote Pingus: %3d/%3d"
     536
     537#: src/level_result.cxx:88
     538msgid "Press button to continue..."
     539msgstr "Knopf drücken um fortzufahren"
     540
     541#: src/level_result.cxx:102
     542msgid ""
     543"As many Pingus escaped as entered the level. That's going to be hard to "
     544"beat.... unless this game becomes pornographic."
     545msgstr ""
     546"Es wurden alle Pingu dieses Levels gerettet. Das wird hart zu schlagen "
     547"sein... es sei denn dieses Spiel wird pornographisch."
     548
     549#: src/level_result.cxx:104
     550msgid "Very impressive indeed."
     551msgstr "In der Tat sehr eindrucksvoll."
     552
     553#: src/level_result.cxx:106
     554msgid "Good work. Still room for improvement though."
     555msgstr "Gute Arbeit. Aber Übung macht den Meiser!"
     556
     557#: src/level_result.cxx:108
     558msgid "Not too shabby, not too shabby at all."
     559msgstr "Nicht schlecht, nicht schlecht!"
     560
     561#: src/level_result.cxx:110
     562msgid ""
     563"That was OK, but Pingu life insurance premiums have just gotten more "
     564"expensive."
     565msgstr ""
     566"Das war ok, aber die Lebensversicherungsprämien für Pingus sind gerade "
     567"gestiegen."
     568
     569#: src/level_result.cxx:112
     570msgid "Maybe this level calls for a different strategy."
     571msgstr "Möglicherweise verlangt dieser Level eine andere Strategie."
     572
     573#: src/level_result.cxx:114
     574msgid "Exactly half. Are you saving only the female ones?"
     575msgstr "Genau die Hälfte! Rettest du nur die Weibchen?"
     576
     577#: src/level_result.cxx:116
     578msgid "If I were a Pingu, I never would have left that entrance."
     579msgstr "Wenn ich ein Pingu wäre, hätte ich den Eingang nie verlassen."
     580
     581#: src/level_result.cxx:118
     582msgid "Maybe you would feel more at home playing Quake."
     583msgstr "Möglicherweise wäre es besser Quake zu spielen?"
     584
     585#: src/level_result.cxx:120
     586msgid ""
     587"Maybe this level calls for a different strategy. Like attempting to save "
     588"them, for example."
     589msgstr ""
     590"Es kann sein, dass wir etwas anderes versuchen sollten.  Vielleicht könnten "
     591"wir ja die Pingus retten?"
     592
     593#: src/level_result.cxx:122
     594msgid "Ever considered a career as a Pingu exterminator?"
     595msgstr "Hast du mal über eine Karriere als Pingu Zerstörer nachgedacht?"
     596
     597#: src/level_result.cxx:124
     598msgid "You missed one! What's your excuse!?"
     599msgstr "Du hast einen vergessen! Was ist deine Entschuldigung?"
     600
     601#: src/level_result.cxx:126
     602msgid "Please reassure me that you hit the Armageddon button."
     603msgstr "Bestätige mir bitte, dass das die Armageddon Taste war."
     604
     605#: src/level_result.cxx:128
     606msgid "You've got a negative save/total value, something is buggy."
     607msgstr "Ein negativer Wert? Hier liegt ein Fehler vor."
     608
     609#: src/menu_button.cxx:181
     610msgid "..:: The people who brought this game to you ::.."
     611msgstr "..:: Die Leute, die fuer dieses Spiel verantwortlich sind... ::.."
     612
     613#: src/menu_button.cxx:184
     614msgid "Credits"
     615msgstr "Mitwirkende"
     616
     617#: src/menu_button.cxx:216
     618msgid "..:: Takes you to the options menu ::.."
     619msgstr "..:: Einstellungen, Cheats und Debugging stuff ::.."
     620
     621#: src/menu_button.cxx:219
     622msgid "Options"
     623msgstr "Einstellungen"
     624
     625#: src/menu_button.cxx:252
     626msgid "..:: Bye, bye ::.."
     627msgstr "..:: Auf Wiedersehen ::.."
     628
     629#: src/menu_button.cxx:255
     630msgid "Exit"
     631msgstr "Beenden"
     632
     633#: src/menu_button.cxx:306
     634msgid "..:: Launch the level editor ::.."
     635msgstr "..:: Erstelle deinen eigenen Level ::.."
     636
     637#: src/menu_button.cxx:309
     638msgid "Create a"
     639msgstr "Bau einen"
     640
     641#: src/menu_button.cxx:310
     642msgid "Level"
     643msgstr "Level"
     644
     645#: src/menu_button.cxx:344
     646msgid "..:: Start the game ::.."
     647msgstr "..:: das Spiel starten ::.."
     648
     649#: src/menu_button.cxx:345
     650msgid "Start"
     651msgstr "Start"
     652
     653#: src/menu_button.cxx:375
     654msgid "..:: Start a contrib level ::.."
     655msgstr "..:: Contrib level Spielen ::.."
     656
     657#: src/menu_button.cxx:377
     658msgid "Contrib"
     659msgstr "Levels"
     660
     661#: src/menu_button.cxx:399
     662msgid "..:: Multiplayer Modes... experimental stuff ::.."
     663msgstr "..:: Mehrspieler Modus ::.. Experimentelles Zeug ::.."
     664
     665#: src/menu_button.cxx:401
     666msgid "Multi"
     667msgstr "Multi"
     668
     669#: src/pingu_enums.cxx:35
     670msgid "Angel"
     671msgstr "Angel"
     672
     673#: src/pingu_enums.cxx:36
     674msgid "Basher"
     675msgstr "Basher"
     676
     677#: src/pingu_enums.cxx:37
     678msgid "Blocker"
     679msgstr "Blocker"
     680
     681#: src/pingu_enums.cxx:38
     682msgid "Boarder"
     683msgstr "Boarder"
     684
     685#: src/pingu_enums.cxx:39
     686msgid "Bomber"
     687msgstr "Bomber"
     688
     689#: src/pingu_enums.cxx:41
     690msgid "Climber"
     691msgstr "Climber"
     692
     693#: src/pingu_enums.cxx:42
     694msgid "Digger"
     695msgstr "Digger"
     696
     697#: src/pingu_enums.cxx:43
     698msgid "Drown"
     699msgstr "Drown"
     700
     701#: src/pingu_enums.cxx:44
     702msgid "Exiter"
     703msgstr "Exiter"
     704
     705#: src/pingu_enums.cxx:45
     706msgid "Faller"
     707msgstr "Faller"
     708
     709#: src/pingu_enums.cxx:46
     710msgid "Floater"
     711msgstr "Floater"
     712
     713#: src/pingu_enums.cxx:47
     714msgid "Jumper"
     715msgstr "Jumper"
     716
     717#: src/pingu_enums.cxx:48
     718msgid "Laserkill"
     719msgstr "Laserkill"
     720
     721#: src/pingu_enums.cxx:49
     722msgid "Miner"
     723msgstr "Miner"
     724
     725#: src/pingu_enums.cxx:50
     726msgid "Rocketlauncher"
     727msgstr "Rocketlauncher"
     728
     729#: src/pingu_enums.cxx:51
     730msgid "Slider"
     731msgstr "Slider"
     732
     733#: src/pingu_enums.cxx:52
     734msgid "Smashed"
     735msgstr "Smashed"
     736
     737#: src/pingu_enums.cxx:53
     738msgid "Splashed"
     739msgstr "Splashed"
     740
     741#: src/pingu_enums.cxx:54
     742msgid "Superman"
     743msgstr "Superman"
     744
     745#: src/pingu_enums.cxx:55
     746msgid "Teleported"
     747msgstr "Teleported"
     748
     749#: src/pingu_enums.cxx:56
     750msgid "Waiter"
     751msgstr "Waiter"
     752
     753#: src/pingu_enums.cxx:57
     754msgid "Walker"
     755msgstr "Walker"
     756
     757#: src/pingus_counter.cxx:52
     758#, c-format
     759msgid "Released:%3d/%-3d   Out:%3d   Saved:%3d/%-3d"
     760msgstr "Rein: %3d/%-3d  Raus: %3d Gerettet: %3d/%-3d"
     761
     762#: src/pingus_main.cxx:90
     763msgid "| segfault_handler: catched a SIGSEGV."
     764msgstr "| segfault_handler: SIGSEGV abgefangen."
     765
     766#: src/pingus_main.cxx:92
     767msgid "| Woops, Pingus just crashed, congratulations you've found a bug."
     768msgstr ""
     769"| Woops, Pingus ist abgestürzt. Gratuliere, du hast einen Bug gefunden."
     770
     771#: src/pingus_main.cxx:93
     772msgid ""
     773"| Please write a little bug report to <grumbel@gmx.de>, include informations"
     774msgstr ""
     775"| Bitte schreibe einen kleinen Report an <pingus-devel@nongnu.org, mit "
     776"Informationen,"
     777
     778#: src/pingus_main.cxx:94
     779msgid "| where exacly the SIGSEGV occured and how to reproduce it."
     780msgstr "| wo genau der SIGSEGV auftrat und wie man ihn reproduziert."
     781
     782#: src/pingus_main.cxx:95
     783msgid "| Also try include a backtrace, you can get it like this:"
     784msgstr "| Versuche auch einen backtrace zu erstellen, du bekommst ihn so:"
     785
     786#: src/pingus_main.cxx:101
     787msgid "| If that doesn't work, try this:"
     788msgstr "| Wenn das nicht geht, versuche dies:"
     789
     790#: src/pingus_main.cxx:105
     791msgid "| [play until it crashes again]"
     792msgstr "| [spiele bis es wieder crasht]"
     793
     794#: src/pingus_main.cxx:113
     795msgid "| Warning: Pingus recieved a SIGINT, exiting now."
     796msgstr "| Warnung: Pingus erhielt einen SIGINT, beende jetzt."
     797
     798#: src/pingus_main.cxx:301
     799msgid "Warning: Larger resolution than 800x600 will result in visual problems"
     800msgstr ""
     801"Warnung: Auflösungen grösser als 800x600 können zu visuellen Problemen führen"
     802
     803#: src/pingus_main.cxx:488
     804msgid "Unknow char: "
     805msgstr "Unbekannter Buchstabe: "
     806
     807#: src/pingus_main.cxx:489
     808msgid "Usage: "
     809msgstr "Benutzung: "
     810
     811#: src/pingus_main.cxx:489
     812msgid " [OPTIONS]... [LEVELFILE]"
     813msgstr "[OPTIONEN]... [LEVELDATEI]"
     814
     815#: src/pingus_main.cxx:492
     816msgid "Options:"
     817msgstr "Einstellungen"
     818
     819#: src/pingus_main.cxx:494
     820msgid "Set the resolution for pingus (default: 800x600)"
     821msgstr "Setze die Auflösung für Pingus (Standard: 800x600)"
     822
     823#: src/pingus_main.cxx:495
     824msgid "Displays this help"
     825msgstr "Hilfe Anzeigen"
     826
     827#: src/pingus_main.cxx:496
     828msgid "Disable intro"
     829msgstr "Intro abschalten"
     830
     831#: src/pingus_main.cxx:497
     832msgid "Use OpenGL"
     833msgstr "OpenGL benutzen"
     834
     835#: src/pingus_main.cxx:499
     836msgid "Start in Window Mode"
     837msgstr "Pingus im Fenster starten"
     838
     839#: src/pingus_main.cxx:500
     840msgid "Start in Fullscreen"
     841msgstr "Pingus im Vollbild starten"
     842
     843#: src/pingus_main.cxx:504
     844msgid "FILE      "
     845msgstr "Datei     "
     846
     847#: src/pingus_main.cxx:504
     848msgid "Load a custom level from FILE"
     849msgstr "Einen Level aus DATEI laden"
     850
     851#: src/pingus_main.cxx:505
     852msgid "FILE        "
     853msgstr "Datei      "
     854
     855#: src/pingus_main.cxx:505
     856msgid "Load a custom worldmap from FILE"
     857msgstr "Eine Weltkarte aus DATEI laden"
     858
     859#: src/pingus_main.cxx:506
     860msgid "Print some more messages to stdout, can be set"
     861msgstr "Gibt mehr Nachrichten auf stdout aus, kann"
     862
     863#: src/pingus_main.cxx:507
     864msgid "multiple times to increase verbosity"
     865msgstr "mehrmals gesetzt werden, um die Genauigkeit zu erhöhen"
     866
     867#: src/pingus_main.cxx:508
     868msgid "Prints version number and exit"
     869msgstr "Version ausgeben und beenden"
     870
     871#: src/pingus_main.cxx:509
     872msgid "Launch the Level editor (experimental)"
     873msgstr "Level Editor starten (Experimentell)"
     874
     875#: src/pingus_main.cxx:510
     876msgid "Disable automatic scrolling"
     877msgstr "Automatisches Scrollen abschalten"
     878
     879#: src/pingus_main.cxx:512
     880msgid "Enable software cursor"
     881msgstr "Aktiviere Software Cursor"
     882
     883#: src/pingus_main.cxx:515
     884msgid "Don't read ~/.pingus/config"
     885msgstr "~/.pingus/config nicht einlesen"
     886
     887#: src/pingus_main.cxx:516
     888msgid "FILE       "
     889msgstr "Datei      "
     890
     891#: src/pingus_main.cxx:516
     892msgid "Read config from FILE (default: ~/.pingus/config)"
     893msgstr "Konfiguration aus DATEI lesen"
     894
     895#: src/pingus_main.cxx:517
     896msgid "reduce CPU usage, might speed up the game on slower machines"
     897msgstr ""
     898"reduziere CPU Belastung, könnte das Spiel auf langsamen Rechnern "
     899"beschleunigen"
     900
     901#: src/pingus_main.cxx:518
     902msgid "Uses the controller given in FILE"
     903msgstr "Controller aus FILE benutzen"
     904
     905#: src/pingus_main.cxx:520
     906msgid "Debugging and experimental stuff:"
     907msgstr "Debug und Experimentelles Zeug"
     908
     909#: src/pingus_main.cxx:521
     910msgid "Enables some features, only interesting programmers"
     911msgstr "Aktiviere einige Funktionen, nur für Programmierer"
     912
     913#: src/pingus_main.cxx:522
     914msgid "Enable the output of debugging infos, possible"
     915msgstr "Aktiviere die Ausgabe von Debug Informationen, mögliche"
     916
     917#: src/pingus_main.cxx:523
     918msgid "OPTION's are tiles, gametime, actions, sound, resources, gui,"
     919msgstr "Optionen sind tiles, gametime, actions, sound, resources, gui,"
     920
     921#: src/pingus_main.cxx:525
     922msgid "Skip at least N frames, larger values speed the game up"
     923msgstr ""
     924"Überspringe mindestens N Bilder, größere Werte erhöhen die Geschwindigkeit"
     925
     926#: src/pingus_main.cxx:526
     927msgid "Skip at most N frames"
     928msgstr "Überspringe maximal N Bilder"
     929
     930#: src/pingus_main.cxx:527
     931msgid "Set both min and max frameskip to N"
     932msgstr "Setze sowohl minimalen als auch maximalen Bildsprung auf N"
     933
     934#: src/pingus_main.cxx:528
     935msgid "Set the game speed (0=fastest, >0=slower)"
     936msgstr "Setze die Geschwindigkeit (0=schnellste, >0=langsamer)"
     937
     938#: src/pingus_main.cxx:529
     939msgid "Prints the fps to stdout"
     940msgstr "Schreibe die fps auf stdout"
     941
     942#: src/pingus_main.cxx:530
     943msgid "Set the size of the map tiles (default: 32)"
     944msgstr "Setze die Größe der Kartenteile (Standard: 32)"
     945
     946#: src/pingus_main.cxx:531
     947msgid "Disable some cpu intensive features"
     948msgstr "Deaktivere einige CPU intensive Funktionen"
     949
     950#: src/pingus_main.cxx:532
     951msgid "Reduces the CPU usage by issuing sleep()"
     952msgstr "Reduziert die CPU Belastung durch Verwendung von sleep()"
     953
     954#: src/pingus_main.cxx:534
     955msgid "Demo playing and recording:"
     956msgstr "Demo aufzeichnung und wiedergabe:"
     957
     958#: src/pingus_main.cxx:535
     959msgid "FILE     "
     960msgstr "Datei    "
     961
     962#: src/pingus_main.cxx:535
     963msgid "Plays a demo session from FILE"
     964msgstr "Spielt eine Demo Sitzung aus FILE"
     965
     966#: src/pingus_main.cxx:537
     967msgid "Record demos for each played level"
     968msgstr "Nehme Demos für jeden gespielten Level auf"
     969
     970#: src/pingus_main.cxx:539
     971msgid "Sound:"
     972msgstr "Sound"
     973
     974#: src/pingus_main.cxx:540
     975msgid "Disable sound"
     976msgstr "Sound deaktivieren"
     977
     978#: src/pingus_main.cxx:541
     979msgid "Disable music"
     980msgstr "Musik deaktivieren"
     981
     982#: src/pingus_main.cxx:720
     983msgid "clanVorbis support:           ok"
     984msgstr "clanVorbis Unterstützung:    ok"
     985
     986#: src/pingus_main.cxx:722
     987msgid "clanVoribs support:  missing (.ogg music files will not be playable)"
     988msgstr ""
     989"clanVorbis Unterstützung: fehlt (.ogg Musik Datein können nicht abgespielt "
     990"werden)"
     991
     992#: src/pingus_main.cxx:726
     993msgid "clanMikMod support:           ok"
     994msgstr "clanMikMod Unterstützung:    ok"
     995
     996#: src/pingus_main.cxx:728
     997msgid "clanMikMod support:  missing (music files will not be playable)"
     998msgstr ""
     999"clanMikMod Untersützung: fehlt (Musikdateien können nicht abgespielt werden)"
     1000
     1001#: src/pingus_main.cxx:732
     1002msgid "getext support:               ok"
     1003msgstr "gettext Unterstützung:       ok"
     1004
     1005#: src/pingus_main.cxx:733
     1006msgid "gettext language:        english"
     1007msgstr "gettext Sprache:        deutsch"
     1008
     1009#: src/pingus_main.cxx:739
     1010msgid "sound support:           enabled"
     1011msgstr "Sound Unterstützung:         an"
     1012
     1013#: src/pingus_main.cxx:741
     1014msgid "sound support:          disabled"
     1015msgstr "Sound Unterstützung:        aus"
     1016
     1017#: src/pingus_main.cxx:744
     1018msgid "music support:           enabled"
     1019msgstr "Musik Unterstützung:         an"
     1020
     1021#: src/pingus_main.cxx:746
     1022msgid "music support:          disabled"
     1023msgstr "Musik Unterstützung:        aus"
     1024
     1025#: src/pingus_main.cxx:748
     1026msgid "resolution set to:       "
     1027msgstr "Auflösung:              "
     1028
     1029#: src/pingus_main.cxx:749
     1030msgid "fullscreen:              "
     1031msgstr "Vollbild:           "
     1032
     1033#: src/pingus_main.cxx:750
     1034msgid " enabled"
     1035msgstr "aktiviert"
     1036
     1037#: src/pingus_main.cxx:750
     1038msgid "disabled"
     1039msgstr "deaktiviert"
     1040
  • new file libraries/source/tinygettext/test/helloworld.cpp

    diff --git a/libraries/source/tinygettext/test/helloworld.cpp b/libraries/source/tinygettext/test/helloworld.cpp
    new file mode 100644
    index 0000000..071ab96
    - +  
     1#include <iostream>
     2
     3#define _(x) gettext(x)
     4
     5int main()
     6{
     7  std::cout << _("Short Hello World") << std::endl;
     8  std::cout << gettext("Hello World") << std::endl;
     9  std::cout << gettext("Hello Worlds") << std::endl;
     10
     11  std::cout << pgettext("", "Hello World") << std::endl;
     12  std::cout << pgettext("console", "Hello World") << std::endl;
     13
     14  std::cout << ngettext("gui", "Hello World") << std::endl;
     15  std::cout << ngettext("Hello World", "Hello Worlds", 5) << std::endl;
     16
     17  std::cout << npgettext("", "Hello World", "Hello Worlds", 5) << std::endl;
     18  std::cout << npgettext("console", "Hello World", "Hello Worlds", 5) << std::endl;
     19  std::cout << npgettext("gui", "Hello World", "Hello Worlds", 5) << std::endl;
     20
     21  return 0;
     22}
     23
     24/* EOF */
  • new file libraries/source/tinygettext/test/helloworld/de.po

    diff --git a/libraries/source/tinygettext/test/helloworld/de.po b/libraries/source/tinygettext/test/helloworld/de.po
    new file mode 100644
    index 0000000..048acc6
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR Free Software Foundation, Inc.
     3# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     4#
     5#, fuzzy
     6msgid ""
     7msgstr ""
     8"Project-Id-Version: PACKAGE VERSION\n"
     9"Report-Msgid-Bugs-To: \n"
     10"POT-Creation-Date: 2009-01-30 08:01+0100\n"
     11"PO-Revision-Date: 2009-01-30 08:39+0100\n"
     12"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     13"Language-Team: LANGUAGE <LL@li.org>\n"
     14"MIME-Version: 1.0\n"
     15"Content-Type: text/plain; charset=UTF-8\n"
     16"Content-Transfer-Encoding: 8bit\n"
     17
     18#: helloworld.cpp:7
     19msgid "Short Hello World"
     20msgstr "kurzes Hallo Welt"
     21
     22#: helloworld.cpp:8 helloworld.cpp:14
     23#, fuzzy
     24msgid "Hello World"
     25msgid_plural "Hello Worlds"
     26msgstr[0] "Hallo Welt (singular)"
     27msgstr[1] "Hallo Welt (plural)"
     28
     29#: helloworld.cpp:10 helloworld.cpp:16
     30#, fuzzy
     31msgctxt ""
     32msgid "Hello World"
     33msgid_plural "Hello Worlds"
     34msgstr[0] "Hallo Welt (singular) mit leerem Kontext"
     35msgstr[1] "Hallo Welt (plural) mit leerem Kontext"
     36
     37#: helloworld.cpp:11 helloworld.cpp:17
     38msgctxt "console"
     39msgid "Hello World"
     40msgid_plural "Hello Worlds"
     41msgstr[0] "Hallo Welt (singular) in der Console"
     42msgstr[1] "Hallo Welt (plural) in der Console"
     43
     44#: helloworld.cpp:13
     45msgid "gui"
     46msgid_plural "Hello World"
     47msgstr[0] "Hallo Welt (singular)"
     48msgstr[1] "Hallo Welt (plural)"
     49
     50#: helloworld.cpp:18
     51#, fuzzy
     52msgctxt "gui"
     53msgid "Hello World"
     54msgid_plural "Hello Worlds"
     55msgstr[0] "Hallo Welt im GUI"
     56msgstr[1] "Hallo Welt (plural) im GUI"
  • new file libraries/source/tinygettext/test/helloworld/helloworld.pot

    diff --git a/libraries/source/tinygettext/test/helloworld/helloworld.pot b/libraries/source/tinygettext/test/helloworld/helloworld.pot
    new file mode 100644
    index 0000000..a9174f8
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
     3# This file is distributed under the same license as the PACKAGE package.
     4# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     5#
     6#, fuzzy
     7msgid ""
     8msgstr ""
     9"Project-Id-Version: PACKAGE VERSION\n"
     10"Report-Msgid-Bugs-To: \n"
     11"POT-Creation-Date: 2009-01-30 08:10+0100\n"
     12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
     13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     14"Language-Team: LANGUAGE <LL@li.org>\n"
     15"MIME-Version: 1.0\n"
     16"Content-Type: text/plain; charset=CHARSET\n"
     17"Content-Transfer-Encoding: 8bit\n"
     18"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
     19
     20#: helloworld.cpp:7
     21msgid "Short Hello World"
     22msgstr ""
     23
     24#: helloworld.cpp:8 helloworld.cpp:14
     25msgid "Hello World"
     26msgid_plural "Hello Worlds"
     27msgstr[0] ""
     28msgstr[1] ""
     29
     30#: helloworld.cpp:10 helloworld.cpp:16
     31msgctxt ""
     32msgid "Hello World"
     33msgid_plural "Hello Worlds"
     34msgstr[0] ""
     35msgstr[1] ""
     36
     37#: helloworld.cpp:11 helloworld.cpp:17
     38msgctxt "console"
     39msgid "Hello World"
     40msgid_plural "Hello Worlds"
     41msgstr[0] ""
     42msgstr[1] ""
     43
     44#: helloworld.cpp:13
     45msgid "gui"
     46msgid_plural "Hello World"
     47msgstr[0] ""
     48msgstr[1] ""
     49
     50#: helloworld.cpp:18
     51msgctxt "gui"
     52msgid "Hello World"
     53msgid_plural "Hello Worlds"
     54msgstr[0] ""
     55msgstr[1] ""
  • new file libraries/source/tinygettext/test/level/de.po

    diff --git a/libraries/source/tinygettext/test/level/de.po b/libraries/source/tinygettext/test/level/de.po
    new file mode 100644
    index 0000000..69e0206
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR Free Software Foundation, Inc.
     3# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     4#
     5#, fuzzy
     6msgid ""
     7msgstr ""
     8"Project-Id-Version: PACKAGE VERSION\n"
     9"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
     10"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     11"Language-Team: LANGUAGE <LL@li.org>\n"
     12"MIME-Version: 1.0\n"
     13"Content-Type: text/plain; charset=CHARSET\n"
     14"Content-Transfer-Encoding: 8bit\n"
     15
     16#: src/pingus_main.cxx:760
     17msgid "PingusMain: Starting Main: "
     18msgstr "PingusMain: Hauptteil wird gestartet: "
     19
     20#: src/pingus_main.cxx:805
     21msgid "PingusMain: Levelfile not found, ignoring: "
     22msgstr "PingusMain: Leveldatei nicht gefunden, Fehler wird ignoriert: "
     23
     24#: src/pingus_main.cxx:909
     25msgid "Error caught from ClanLib: "
     26msgstr "ClanLib Fehler abgefangen: "
     27
     28#: src/pingus_main.cxx:913
     29msgid "Error caught from Pingus: "
     30msgstr "Pingus Fehler wurde abgefangen: "
     31
     32#: src/pingus_main.cxx:917
     33msgid "Pingus: Out of memory!"
     34msgstr "Pingus: Speicher ist ausgegangen"
     35
     36#: src/pingus_main.cxx:921
     37msgid "Pingus: Standard exception caught!:\n"
     38msgstr "Pingus: Standard Ausnahme abgefangen!:\n"
     39
     40#: src/pingus_main.cxx:925
     41msgid "Pingus: Unknown throw caught!"
     42msgstr "Pingus: Unbekannte Ausnahme abgefangen!"
     43
     44#: src/result_screen.cxx:99 src/start_screen.cxx:111
     45msgid "Abort"
     46msgstr "Abbruch"
     47
     48#: src/result_screen.cxx:167
     49msgid "Retry"
     50msgstr "Versuche erneut"
     51
     52#: src/result_screen.cxx:175
     53msgid "Success!"
     54msgstr "Erfolg!"
     55
     56#: src/result_screen.cxx:182
     57msgid "Failure!"
     58msgstr "Fehler!"
     59
     60#: src/result_screen.cxx:191
     61msgid "Perfect! You saved everyone possible - great!"
     62msgstr "Perfekt! Du hast alle gerettet!"
     63
     64#: src/result_screen.cxx:193
     65msgid "No-one got killed, pretty good work."
     66msgstr "Keiner wurde getötet, sehr gute Arbeit."
     67
     68#: src/result_screen.cxx:195
     69msgid ""
     70"You saved exactly what you needed - you made it, but\n"
     71"maybe you can do better?"
     72msgstr ""
     73"Du hast so viele gerettet wie nötig. Du hast es\n"
     74"geschafft, aber vielleicht kannst Du es noch besser?"
     75
     76#: src/result_screen.cxx:198
     77msgid "Not everybody was saved, but still good work!"
     78msgstr "Nicht alle wurden gerettet, aber trotzdem noch gute Arbeit!"
     79
     80#: src/result_screen.cxx:200
     81msgid "What can I say, you made it - congratulations!"
     82msgstr "Was soll ich sagen, Du hast es geschafft. Gratuliere!"
     83
     84#: src/result_screen.cxx:205
     85msgid "You killed everybody, not good."
     86msgstr "Du hast alle getötet, nicht gut."
     87
     88#: src/result_screen.cxx:207
     89msgid "No-one got saved - I know you can do better."
     90msgstr "Keiner wurde gerettet, ich weiß, dass du es besser kannst."
     91
     92#: src/result_screen.cxx:209
     93msgid ""
     94"You didn't save enough, but you saved a few.  Next\n"
     95"time you might do better."
     96msgstr ""
     97"Du hast nicht genug gerettet, aber immerhin ein paar.\n"
     98"Nächstes Mal machst du es bestimmt besser."
     99
     100#: src/result_screen.cxx:212
     101msgid "Only one more and you would have made it - try again!"
     102msgstr "Einer mehr und du hast es, versuch es nochmal!"
     103
     104#: src/result_screen.cxx:214
     105msgid "Only a handful more and you would have made it - try again!"
     106msgstr "Nur ein paar mehr und du hättest es, versuch es nochmal!"
     107
     108#: src/result_screen.cxx:216
     109msgid "Better luck next time!"
     110msgstr "Mehr Glück fürs nächste Mal!"
     111
     112#: src/result_screen.cxx:232
     113msgid "Saved: "
     114msgstr "Gerettet: "
     115
     116#: src/result_screen.cxx:236
     117msgid "Died: "
     118msgstr "Getötet: "
     119
     120#: src/result_screen.cxx:240
     121msgid "Time left: "
     122msgstr "Verbliebene Zeit: "
     123
     124#: src/screenshot.cxx:45
     125msgid "Screenshot: Saving screenshot to: "
     126msgstr "Screenshot: Speichere Screenshot als: "
     127
     128#: src/screenshot.cxx:47
     129msgid "Screenshot: Screenshot is done."
     130msgstr "Screenshot: Screenshot wurde erstellt."
     131
     132#: src/screenshot.cxx:53
     133msgid "Screenshot: Couldn't save screenshot"
     134msgstr "Screenshot: Screenshot konnte nicht gespeichert werden"
     135
     136#: src/screenshot.cxx:138
     137msgid "Screenshot: Couldn't write file: "
     138msgstr "Screenshot: Datei konnte nicht geschrieben werden: "
     139
     140#: src/spot_map.cxx:286
     141#, c-format
     142msgid "Image has wrong color depth: %d"
     143msgstr "Bild hat falsche Farbtiefe: %d"
     144
     145#: src/start_screen.cxx:75
     146msgid "Ok"
     147msgstr "Ok"
     148
     149#: src/start_screen.cxx:158
     150msgid "Number of Pingus: "
     151msgstr "Anzahl an Pingus: "
     152
     153#: src/start_screen.cxx:161
     154msgid "Number to Save: "
     155msgstr "Zu rettende Pingus: "
     156
     157#: src/start_screen.cxx:164
     158msgid "Time: "
     159msgstr "Zeit: "
     160
     161#: src/start_screen.cxx:167
     162msgid "Difficulty:"
     163msgstr "Schwierigkeitsgrad:"
     164
     165#: src/start_screen.cxx:175
     166msgid "Author: "
     167msgstr "Autor: "
     168
     169#: src/start_screen.cxx:178
     170msgid "Filename: "
     171msgstr "Dateiname: "
     172
     173#: src/story.cxx:41
     174msgid "The Journey Continues"
     175msgstr "Die Reise geht weiter"
     176
     177#: src/story.cxx:46
     178msgid ""
     179"Now after you and the Pingus have learned the basics and\n"
     180"practiced a bit it is time to move on and begin the journey into\n"
     181"the world. Since the ice floe with which the Pingus traveled to the\n"
     182"Tutorial Island isn't going to hold on the whole way into the warmer\n"
     183"climates the Pingus have to find something else to guide\n"
     184"them on their journey.\n"
     185msgstr ""
     186"Nachdem Du und die Pingus nun die Grundlagen gelernt und\n"
     187"ein wenig Übung haben, ist es an der Zeit, weiter zu gehen und die\n"
     188"Reise in die Welt zu beginnen. Da die Scholle, mit der die Pingus zum\n"
     189"Tutorial Island reisten, nicht den ganzen Weg in die wärmeren Regionen\n"
     190"halten wird, müssen die Pingus etwas anderes finden, um sie auf der\n"
     191"Reise zu begleiten.\n"
     192
     193#: src/story.cxx:58
     194msgid ""
     195"But as the eldest have said, the Tutorial Island provides not\n"
     196"only a good way to practice, but it is also the starting point into\n"
     197"the world. After some searching the Pingus discovered the meaning\n"
     198"of this, and they found the large tree at the end of the island\n"
     199"which gave them wood to construct a float.\n"
     200msgstr ""
     201"Aber wie die Weisen sagten, ist Tutorial Island nicht nur\n"
     202"eine Möglichkeit zum üben, es ist auch der erste Schritt in\n"
     203"die Welt, denn das Holz der Bäume am Ende der Insel liefert\n"
     204"gutes Material, um ein Floß zu bauen.\n"
     205
     206#: src/story.cxx:69
     207msgid ""
     208"So the Pingus set out and constructed some large rafts, enough\n"
     209"to carry them all. After also packing a bunch of provisions\n"
     210"they were prepared to start their journey and leave\n"
     211"their familiar ground and enter the unknown parts of the world.\n"
     212msgstr ""
     213"So gingen die Pingus hin und bauten ein paar große\n"
     214"Flöße, genug, um alle zu tragen. Nachdem sie auch noch einigen\n"
     215"Proviant eingepackt hatten, waren sie endlich bereit, ihre Reise zu\n"
     216"beginnen, den bekannten Bereich zu verlassen und die unbekannten\n"
     217"Bereiche der Welt zu betreten.\n"
     218
     219#: src/story.cxx:79
     220msgid ""
     221"So the Pingus sit on their raft, worrying about what's to come and\n"
     222"where to go, while floating into the sunset.\n"
     223"\n"
     224"To be continued..."
     225msgstr ""
     226"So saßen die Pingus nur auf ihrem Floß und sorgten sich, was denn nun\n"
     227"kommen möge, während sie in den Sonnenuntergang trieben.\n"
     228"\n"
     229"Fortsetzung folgt..."
     230
     231#: src/story.cxx:91
     232msgid "The Journey Begins"
     233msgstr "Die Reise beginnt"
     234
     235#: src/story.cxx:96
     236msgid ""
     237"For a long time, the Pingus have lived happily in peace on the\n"
     238"South Pole along with all the other animals. Everything was in\n"
     239"balance and it seemed like nothing could disrupt their peace.  The\n"
     240"Pingus were happy and it seemed like this could never end.\n"
     241msgstr ""
     242"Die Pingus lebten schon lange glücklich und in Frieden am Südpol,\n"
     243"zusammen mit all den anderen Tieren. Alles war im Gleichgewicht und\n"
     244"es schien, als könnte nichts ihren Frieden stören. Die Pingus waren\n"
     245"glücklich und es schien, als würde dies niemals enden.\n"
     246
     247#: src/story.cxx:106
     248msgid ""
     249"But then one day, things began to change slowly: the sky got darker\n"
     250"and the earth got warmer. Firstly, they thought this was just a\n"
     251"normal fluctuation in the world's climate, but things seemed to get\n"
     252"worse and worse with every year."
     253msgstr ""
     254"Aber dann, eines Tages, veränderten sich die Dinge langsam. Der\n"
     255"Himmel wurde dunkler und die Erde wärmer. Zuerst dachten sie, dies\n"
     256"wäre nur eine normale Schwankung im Klima, aber es wurde von Jahr \n"
     257"zu Jahr immer schlimmer."
     258
     259#: src/story.cxx:117
     260msgid ""
     261"The snow began to melt away in a few areas and food became an\n"
     262"issue. Other animals tried to leave the region to search\n"
     263"for colder areas, but the Pingus knew that this wouldn't help:\n"
     264"they knew that they had to do something about it."
     265msgstr ""
     266"Der Schnee begann in einigen Bereichen zu schmelzen und Nahrung\n"
     267"wurde ein Problem. Andere Tiere begannen bereits, die Region zu\n"
     268"verlassen, um kältere Gebiete zu suchen. Aber die Pingus wussten,\n"
     269"dass dies nicht helfen würde, sie wussten, es musste etwas gegen\n"
     270"dieses Problem getan werden."
     271
     272#: src/story.cxx:127
     273msgid ""
     274"So the circle of the eldest came together to decide what to do\n"
     275"about it. They decided to send out an expedition around the world\n"
     276"to find the cause of this warming. The expedition consisted of\n"
     277"hundreds of the bravest Pingus on the South Pole."
     278msgstr ""
     279"Darum traf sich der Rat der Ältesten, um zu entscheiden, was dagegen\n"
     280"getan werden sollte. Sie entschieden, eine Expedition um die Welt\n"
     281"zu senden, um den Grund für die Erwärmung zu finden. Die Expedition\n"
     282"bestand aus hunderten der mutigsten Pingus des ganzen Südpols."
     283
     284#: src/story.cxx:137
     285msgid ""
     286"And they picked you to lead them on their journey around the\n"
     287"world. Since the journey will be dangerous and difficult, your\n"
     288"first goal is the Island of Mogorok, also known as the Tutorial\n"
     289"Island. According to the eldest, this island has always been the\n"
     290"first stop of Pingus that were sent out into the world."
     291msgstr ""
     292"Und sie wählten dich, um sie auf der Reise um die Welt zu führen.\n"
     293"Denn die Reise wird gefährlich und schwierig werden. Dein erstes Ziel ist\n"
     294"die Insel Mogorok, auch bekannt als Tutorial Island. Den Weisen\n"
     295"zufolge war diese Insel immer der erste Anlaufpunkt für Pingus,\n"
     296"die in die Welt gesandt wurden."
     297
     298#: src/story.cxx:148
     299msgid ""
     300"The island consists of many areas that are ideal to teach the\n"
     301"Pingus their abilities. The Pingus can learn to build\n"
     302"bridges, climb, bash and use many other talents which they\n"
     303"will need on their long and dangerous journey."
     304msgstr ""
     305"Die Insel besteht aus verschiedenen Regionen, die ideal sind, um die\n"
     306"Fähigkeiten der Pingus zu trainieren. Sie können dort ihre Fähig-\n"
     307"keiten im Brückenbau, klettern, graben und all die anderen Talente\n"
     308"trainieren, die sie sicher auf der langen und gefährlichen Reise\n"
     309"benötigen werden."
     310
     311#: src/story.cxx:158
     312msgid ""
     313"While there, you can practice your abilities in commanding\n"
     314"and guiding the Pingus. You can also get familiar with\n"
     315"all the abilities the Pingus provide and learn to master them,\n"
     316"since you will need all of them on your journey around the\n"
     317"world."
     318msgstr ""
     319"Währenddessen kannst Du auf der anderen Seite deine Fähigkeit, die\n"
     320"Pingus zu leiten und zu führen, trainieren. Du kannst dich dort auch mit\n"
     321"allen Fähigkeiten vertraut machen, die die Pingus besitzen und\n"
     322"erlernen, sie zu meistern, denn du wirst sie sicher alle auf der\n"
     323"Reise um die Welt benötigen."
     324
     325#: src/story.cxx:169
     326msgid ""
     327"Now that you and the Pingus have arrived at Tutorial Island it\n"
     328"is time to take command and begin your mission.\n"
     329"\n"
     330"Good Luck!"
     331msgstr ""
     332"Nun da du und die Pingus im Tutorial Island angekommen sind,\n"
     333"musst du das Kommando übernehmen und deine Mission beginnen.\n"
     334"\n"
     335"Viel Glück!"
     336
     337#: src/system.cxx:273
     338msgid "Environment variable $HOME not set, fix that and start again."
     339msgstr ""
     340"Umgebungs Variable $HOME ist nicht gesetzt. Setze sie und starte erneut."
     341
     342#: src/worldmap/level_dot.cxx:174 src/worldmap/level_dot.cxx:184
     343msgid "locked"
     344msgstr "gesperrt"
     345
     346#: src/worldmap/manager.cxx:103
     347msgid "Show Ending?"
     348msgstr "Ende anzeigen?"
     349
     350#: src/worldmap/manager.cxx:133
     351msgid "Show Story?"
     352msgstr "Story zeigen?"
     353
     354#: src/worldmap/manager.cxx:161
     355msgid "Leave?"
     356msgstr "Verlassen?"
     357
     358#: src/worldmap/manager.cxx:201
     359msgid "Enter?"
     360msgstr "Betreten?"
     361
     362#: src/worldmap/worldmap.cxx:77
     363msgid "WorldMap: File not found: "
     364msgstr "Worldmap: Datei nicht gefunden: "
     365
     366#: src/worldmap/worldmap.cxx:261
     367msgid "...walking..."
     368msgstr "...laufe..."
     369
     370msgid "You got %d error.\n"
     371msgid_plural "You got %d error.\n"
     372msgstr[0] "Du hast %d fehler\n"
     373msgstr[1] "Du hast %d fehlers\n"
     374
     375msgid "found %d fatal error"
     376msgid_plural "found %d fatal errors"
     377msgstr[0] "s'ha trobat %d error fatal"
     378msgstr[1] "s'han trobat %d errors fatals"
     379
  • new file libraries/source/tinygettext/test/po/de.po

    diff --git a/libraries/source/tinygettext/test/po/de.po b/libraries/source/tinygettext/test/po/de.po
    new file mode 100644
    index 0000000..e863f81
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR Free Software Foundation, Inc.
     3# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     4#
     5msgid ""
     6msgstr ""
     7"Project-Id-Version: Pingus 0.6.0\n"
     8"Report-Msgid-Bugs-To: \n"
     9"POT-Creation-Date: 2004-04-09 18:37+0000\n"
     10"PO-Revision-Date: 2009-02-01 17:44+0100\n"
     11"Last-Translator: David Philippi <david@torangan.de>\n"
     12"Language-Team: David Philippi <david@torangan.de>, Ingo Ruhnke <grumbel@gmx.de>, Giray Devlet <giray@devlet.cc>\n"
     13"MIME-Version: 1.0\n"
     14"Plural-Forms: nplurals=2; plural=(n != 1);\n"
     15"Content-Type: text/plain; charset=UTF-8\n"
     16"Content-Transfer-Encoding: 8bit\n"
     17
     18msgid "umlaut"
     19msgstr "ÄÖÜäöüß"
     20
     21#: src/actions/bridger.cxx:48 src/actions/bridger.cxx:232
     22#: src/pingu_enums.cxx:40
     23msgid "Bridger"
     24msgstr "Bridger"
     25
     26#: src/config.cxx:73
     27msgid "Couldn't open: "
     28msgstr "Datei oder Verzeichnis konnte nicht geöffnet werden: "
     29
     30#: src/config.cxx:172
     31msgid "Unexpected char: '"
     32msgstr "Unerwartetes Zeichen: '"
     33
     34#: src/config.cxx:206
     35msgid "Unexpected char '"
     36msgstr "Unerwartetes Zeichen: '"
     37
     38#: src/credits.cxx:48
     39msgid "-Idea"
     40msgstr "-Idee"
     41
     42#: src/credits.cxx:52
     43msgid "-Maintaining"
     44msgstr "-Verwaltung"
     45
     46#: src/credits.cxx:56
     47msgid "-Programming"
     48msgstr "-Programmierung"
     49
     50#: src/credits.cxx:62
     51msgid "-Porting (Win32)"
     52msgstr "-Portierung (Win32)"
     53
     54#: src/credits.cxx:70
     55msgid "-Gfx"
     56msgstr "-Grafiken"
     57
     58#: src/credits.cxx:82
     59msgid "-Music"
     60msgstr "-Musik"
     61
     62#: src/credits.cxx:88
     63msgid "-Level Design"
     64msgstr "-Level Design"
     65
     66#: src/credits.cxx:93
     67msgid "-Story"
     68msgstr "-Geschichte"
     69
     70#: src/credits.cxx:97
     71msgid "-Translation"
     72msgstr "-Übersetzung"
     73
     74#: src/credits.cxx:112
     75msgid "-Special"
     76msgstr "-Besonderen"
     77
     78#: src/credits.cxx:113
     79msgid "-Thanks to"
     80msgstr "-Dank an"
     81
     82#: src/credits.cxx:154
     83msgid "_And a very Special Thanks"
     84msgstr "_Und einen besonderen Dank"
     85
     86#: src/credits.cxx:155
     87msgid "_to all the people who"
     88msgstr "_an alle Leute die"
     89
     90#: src/credits.cxx:156
     91msgid "_contribute to"
     92msgstr "_an Freier Software"
     93
     94#: src/credits.cxx:157
     95msgid "_Free Software!"
     96msgstr "_mitarbeiten!"
     97
     98#: src/credits.cxx:163
     99msgid "_Thank you for"
     100msgstr "_Dankeschön fürs"
     101
     102#: src/credits.cxx:164
     103msgid "_playing!"
     104msgstr "_spielen!"
     105
     106#: src/editor/editor_event.cxx:588
     107msgid "Enter filename to save as:"
     108msgstr "Datei speichern als:  "
     109
     110#: src/editor/editor_help_screen.cxx:43
     111msgid "Editor Helpscreen (hide with F1)"
     112msgstr "Editor Hilfe (ausblenden mit F1)"
     113
     114#: src/editor/editor_help_screen.cxx:51
     115msgid "F1 - show/hide this help screen"
     116msgstr "F1 - Hilfe anzeigen/verstecken"
     117
     118#: src/editor/editor_help_screen.cxx:52
     119msgid "F2 - launch level building tutorial"
     120msgstr "F2 - Starte Levelbau Tutorial"
     121
     122#: src/editor/editor_help_screen.cxx:53
     123msgid "F3 - toggle background color"
     124msgstr "F3 - Hintergrundfarbe aendern"
     125
     126#: src/editor/editor_help_screen.cxx:54
     127msgid "F4 - play/test the level"
     128msgstr "F4 - Level starten/testen"
     129
     130#: src/editor/editor_help_screen.cxx:55
     131msgid "F5 - load a level"
     132msgstr "Level Laden (F5)"
     133
     134#: src/editor/editor_help_screen.cxx:56
     135msgid "F6 - save this level"
     136msgstr "Level Speichern (F6)"
     137
     138#: src/editor/editor_help_screen.cxx:57
     139msgid "F7 - [unset]"
     140msgstr "F7 - [nicht belegt]"
     141
     142#: src/editor/editor_help_screen.cxx:58
     143msgid "F8 - quick save/backup save?!"
     144msgstr "F8 - Schnellspeichern/Backup"
     145
     146#: src/editor/editor_help_screen.cxx:59
     147msgid "F9 - change level width and height"
     148msgstr "F9 - Level Höhe und Breite ändern"
     149
     150#: src/editor/editor_help_screen.cxx:60
     151msgid "F10 - [unset]"
     152msgstr "F10 - [nicht belegt]"
     153
     154#: src/editor/editor_help_screen.cxx:61
     155msgid "F11 - toggle fps counter"
     156msgstr "F11 - fps Zähler an/aus"
     157
     158#: src/editor/editor_help_screen.cxx:62
     159msgid "F12 - make screenshot"
     160msgstr "F12 - Screenshot erstellen"
     161
     162#: src/editor/editor_help_screen.cxx:66
     163msgid "Home - increase object size"
     164msgstr "Einfg - Objekt vergrößern"
     165
     166#: src/editor/editor_help_screen.cxx:67
     167msgid "End  - decrease object size"
     168msgstr "Ende - Objekt verkleinern"
     169
     170#: src/editor/editor_help_screen.cxx:68
     171msgid "Cursor Keys - Move object"
     172msgstr "Cursor Tasten - Objekt bewegen"
     173
     174#: src/editor/editor_help_screen.cxx:69
     175msgid "Shift 'Cursor Keys' - Move objects fast "
     176msgstr "Shift Cursor Tasten - Objekt schneller bewegen"
     177
     178#: src/editor/editor_help_screen.cxx:70
     179msgid "PageUp   - level object up"
     180msgstr "Bild rauf - Objekt nach oben"
     181
     182#: src/editor/editor_help_screen.cxx:71
     183msgid "PageDown - level object down"
     184msgstr "Bild runter - Objekt nach unten"
     185
     186#: src/editor/editor_help_screen.cxx:72
     187msgid "Shift PageUp   - increase objects z-pos by 50"
     188msgstr "Shift Bild rauf - Objekt nach oben"
     189
     190#: src/editor/editor_help_screen.cxx:73
     191msgid "Shift PageDown - decrease objects z-pos by 50"
     192msgstr "Shift Bild runter - Objekt nach unten"
     193
     194#: src/editor/editor_help_screen.cxx:74
     195msgid "Enter - Set default zoom (1:1)"
     196msgstr "Eingabe - Setze Standard Zoom (1:1)"
     197
     198#: src/editor/editor_help_screen.cxx:75
     199msgid "d - duplicate object"
     200msgstr "d - Objekt kopieren"
     201
     202#: src/editor/editor_help_screen.cxx:76
     203msgid "a - mark all objects"
     204msgstr "a - Alle Objekte markieren"
     205
     206#: src/editor/editor_help_screen.cxx:77
     207msgid "shift leftmouseclick - add object to selection"
     208msgstr "Shift + linke Maustaste - Objekt zur Auswahl tun"
     209
     210#: src/editor/editor_help_screen.cxx:78
     211msgid "leftmouseclick - select object"
     212msgstr "linke Maustaste - Objekt auswählen"
     213
     214#: src/editor/editor_help_screen.cxx:79
     215msgid "Insert - insert new object"
     216msgstr "Einfügen - neues Objekt einfügen"
     217
     218#: src/editor/editor_help_screen.cxx:80
     219msgid "Remove - remove selected object"
     220msgstr "Entfernen - entferne ausgewähltes Objekt"
     221
     222#: src/editor/editor_help_screen.cxx:81
     223msgid "g - ungroup/group current selection"
     224msgstr "g - Auswahl gruppieren / Gruppierung aufheben"
     225
     226#: src/editor/editor_help_screen.cxx:82
     227msgid "Ctrl PageUp   - increase objects z-pos by 1"
     228msgstr "Strg Bild rauf - z-pos des Objekts um 1 erhöhen"
     229
     230#: src/editor/editor_help_screen.cxx:83
     231msgid "Ctrl PageDown - decrease objects z-pos by 1"
     232msgstr "Strg Bild runter - z-pos des Objekts um 1 senken"
     233
     234#: src/editor/editor_help_screen.cxx:89
     235msgid "Naming Convention: <LEVELNAME><NUMBER>-<CREATOR>.plf"
     236msgstr "Dateinamensvorgabe: <LEVELNAME><NUMMER>-<AUTOR>.pfl"
     237
     238#: src/editor/editor_help_screen.cxx:91
     239msgid ""
     240"When you have created a level and want to have it in the next Pingus "
     241"release,\n"
     242"please mail it to pingus-devel@nongnu.org."
     243msgstr ""
     244"Falls Du einen Level erstellt hast und ihn gerne im nächsten Pingus\n"
     245"Release hättest, schicke ihn an pingus-devel@nongnu.org."
     246
     247#: src/editor/object_selector.cxx:106
     248msgid "1 - guillotine"
     249msgstr "1 - Guillotine"
     250
     251#: src/editor/object_selector.cxx:107
     252msgid "2 - hammer"
     253msgstr "2 - Hammer"
     254
     255#: src/editor/object_selector.cxx:108
     256msgid "3 - spike"
     257msgstr "3 - Stacheln"
     258
     259#: src/editor/object_selector.cxx:109
     260msgid "4 - laser_exit"
     261msgstr "4 - Laser Ausgang"
     262
     263#: src/editor/object_selector.cxx:110
     264msgid "5 - fake_exit"
     265msgstr "5 - Täusch Ausgang"
     266
     267#: src/editor/object_selector.cxx:111
     268msgid "6 - smasher"
     269msgstr "6 - Stampfer"
     270
     271#: src/editor/object_selector.cxx:112
     272msgid "7 - bumper"
     273msgstr "7 - Stosser"
     274
     275#: src/editor/object_selector.cxx:186
     276msgid "Select a WorldObj"
     277msgstr "Wähle ein WeltObjekt"
     278
     279#: src/editor/object_selector.cxx:187
     280msgid "1 - teleporter"
     281msgstr "1 - Teleporter"
     282
     283#: src/editor/object_selector.cxx:188
     284msgid "2 - switch and door"
     285msgstr "2 - Schalter und Tür"
     286
     287#: src/editor/object_selector.cxx:189
     288msgid "3 - ConveyorBelt"
     289msgstr "3 - Förderband"
     290
     291#: src/editor/object_selector.cxx:190
     292msgid "4 - IceBlock"
     293msgstr "4 - Eisblock"
     294
     295#: src/editor/object_selector.cxx:191
     296msgid "5 - InfoBox"
     297msgstr "5 - InfoBox"
     298
     299#: src/editor/object_selector.cxx:232
     300msgid "Select a weather"
     301msgstr "Wähle ein Wetter"
     302
     303#: src/editor/object_selector.cxx:233
     304msgid "1 - snow"
     305msgstr "1 - Schnee"
     306
     307#: src/editor/object_selector.cxx:234
     308msgid "2 - rain"
     309msgstr "2 - Regen"
     310
     311#: src/editor/object_selector.cxx:265
     312msgid "Select an entrance"
     313msgstr "Wähle einen Eingang"
     314
     315#: src/editor/object_selector.cxx:266
     316msgid "1 - generic"
     317msgstr "1 - allgemein"
     318
     319#: src/editor/object_selector.cxx:267
     320msgid "2 - woodthing"
     321msgstr "2 - hölzern"
     322
     323#: src/editor/object_selector.cxx:268
     324msgid "3 - cloud"
     325msgstr "3 - Wolke"
     326
     327#: src/editor/object_selector.cxx:269
     328msgid "h - entrance surface (hotspot)"
     329msgstr "h - Eingangs Grafik (hotspot)"
     330
     331#: src/editor/object_selector.cxx:343
     332msgid "What object type do you want?"
     333msgstr "Was für ein Objekt willst du?"
     334
     335#: src/editor/object_selector.cxx:344 src/editor/object_selector.cxx:400
     336msgid "h - Hotspot"
     337msgstr "h - Grafikelement (hotspot)"
     338
     339#: src/editor/object_selector.cxx:345
     340msgid "g - Groundpiece (ground) [not implemented]"
     341msgstr "g - Bodenstück (ground) [nicht implementiert]"
     342
     343#: src/editor/object_selector.cxx:394 src/editor/object_selector.cxx:562
     344msgid "Which object do you want?"
     345msgstr "Welches Objekt willst du?"
     346
     347#: src/editor/object_selector.cxx:395
     348msgid "g - Groundpiece (ground)"
     349msgstr "g - Bodenstück (ground)"
     350
     351#: src/editor/object_selector.cxx:396
     352msgid "s - Groundpiece (solid)"
     353msgstr "s - Bodenelement (Stahl)"
     354
     355#: src/editor/object_selector.cxx:397
     356msgid "b - Groundpiece (bridge)"
     357msgstr "b - Bodenelement (Bruecke)"
     358
     359#: src/editor/object_selector.cxx:398
     360msgid "n - Groundpiece (transparent)"
     361msgstr "n - Bodenelement (transparent)"
     362
     363#: src/editor/object_selector.cxx:399
     364msgid "r - Groundpiece (remove)"
     365msgstr "r - Bodenelement (entfernen)"
     366
     367#: src/editor/object_selector.cxx:401
     368msgid "e - Entrance"
     369msgstr "e - Eingang"
     370
     371#: src/editor/object_selector.cxx:402
     372msgid "x - Exit"
     373msgstr "x - Ausgang"
     374
     375#: src/editor/object_selector.cxx:403
     376msgid "l - Liquid"
     377msgstr "l - Flüssigkeit"
     378
     379#: src/editor/object_selector.cxx:404
     380msgid "w - Weather"
     381msgstr "w - Wetter"
     382
     383#: src/editor/object_selector.cxx:405
     384msgid "t - Traps"
     385msgstr "t - Falle"
     386
     387#: src/editor/object_selector.cxx:406
     388msgid "o - WorldObject"
     389msgstr "o - WeltObjekt"
     390
     391#: src/editor/object_selector.cxx:407
     392msgid "z - Background"
     393msgstr "z - Hintergrund"
     394
     395#: src/editor/object_selector.cxx:408
     396msgid "p - Prefab (ObjectGroup)"
     397msgstr "p - Prefab (Objekt Gruppe)"
     398
     399#: src/editor/object_selector.cxx:409
     400msgid "f - something from file (~/.pingus/images/)"
     401msgstr "f - etwas aus einer Datei (~/.pingus/images/)"
     402
     403#: src/editor/object_selector.cxx:498
     404msgid "Which prefab do you want?"
     405msgstr "Welche Prefab willst du?"
     406
     407#: src/editor/object_selector.cxx:563
     408msgid "1 - Surface Background"
     409msgstr "1 - Bild Hintergrund"
     410
     411#: src/editor/object_selector.cxx:564
     412msgid "2 - Solid Color Background"
     413msgstr "2 - Farb Hintergrund"
     414
     415#: src/editor/object_selector.cxx:565
     416msgid "3 - Starfield Background"
     417msgstr "3 - Sternen Hintergrund"
     418
     419#: src/editor/object_selector.cxx:566
     420msgid "4 - Thunderstorm Background"
     421msgstr "4 - Gewitter Hintergrund"
     422
     423#: src/editor/panel_icons.cxx:33
     424msgid "Load a level (F5)"
     425msgstr "Level laden (F5)"
     426
     427#: src/editor/panel_icons.cxx:46
     428msgid "Exit the editor (Escape)"
     429msgstr "Editor Beenden (Esc)"
     430
     431#: src/editor/panel_icons.cxx:58
     432msgid "Save this level (F6)"
     433msgstr "Level Speichern (F6)"
     434
     435#: src/editor/panel_icons.cxx:70
     436msgid "Delete marked objects (delete)"
     437msgstr "Markierte Objekte Löschen (Entfernen)"
     438
     439#: src/editor/panel_icons.cxx:82
     440msgid "Duplicate current object (d)"
     441msgstr "Aktuelles Object kopieren (d)"
     442
     443#: src/editor/panel_icons.cxx:94
     444msgid "Edit Level Properties"
     445msgstr "Leveleigenschaften ändern"
     446
     447#: src/editor/panel_icons.cxx:106
     448msgid "Edit Object Properties"
     449msgstr "Objekteigenschaften ändern"
     450
     451#: src/editor/panel_icons.cxx:118
     452msgid "Start the level and test it (F4)"
     453msgstr "Level starten und testen (F4)"
     454
     455#: src/editor/panel_icons.cxx:130
     456msgid "Create a new level from scratch"
     457msgstr "Neuen Level erstellen"
     458
     459#: src/editor/panel_icons.cxx:142
     460msgid "Insert an object (Insert)"
     461msgstr "Objekt einfuegen (Einfg)"
     462
     463#: src/editor/panel_icons.cxx:154
     464msgid "Zoom into a region"
     465msgstr "Einen Bereich vergrössern"
     466
     467#: src/editor/panel_icons.cxx:166
     468msgid "Zoom in"
     469msgstr "Vergroessern"
     470
     471#: src/editor/panel_icons.cxx:179
     472msgid "Zoom out"
     473msgstr "Verkleinern"
     474
     475#: src/editor/panel_icons.cxx:192
     476msgid "Setup Number of Actions"
     477msgstr "Stelle die Anzahl der Fähigkeiten ein"
     478
     479#: src/editor/panel_icons.cxx:204
     480msgid "Display Help Screen (F1)"
     481msgstr "Hilfe Anzeigen (F1)"
     482
     483#: src/exit_menu.cxx:48
     484msgid "Yes"
     485msgstr "Ja"
     486
     487#: src/exit_menu.cxx:81
     488msgid "No"
     489msgstr "Nein"
     490
     491#: src/exit_menu.cxx:114
     492msgid "Exit Pingus?"
     493msgstr "Pingus beenden?"
     494
     495#: src/fps_counter.cxx:48
     496msgid "unknown"
     497msgstr "unbekannt"
     498
     499#: src/game_time.cxx:70
     500msgid "unlimited"
     501msgstr "unbegrenzt"
     502
     503#: src/level_desc.cxx:74
     504msgid "Designed by "
     505msgstr "Erstellt von "
     506
     507#: src/level_desc.cxx:79
     508#, c-format
     509msgid "Pingus to Save: %d"
     510msgstr "Zu rettende Pingus: %d"
     511
     512#: src/level_desc.cxx:81
     513#, c-format
     514msgid "Number of Pingus: %d"
     515msgstr "Anzahl an Pingus: %d"
     516
     517#: src/level_desc.cxx:86
     518msgid "Loading..."
     519msgstr "Ladevorgang läuft..."
     520
     521#: src/level_desc.cxx:89
     522msgid "Loading finished. Press a mouse button to start the level"
     523msgstr ""
     524"Ladevorgang abgeschlossen. Drücke eine Maus Taste um den Level zu starten"
     525
     526#: src/level_result.cxx:61
     527msgid "Results:"
     528msgstr "Ergebnisse:"
     529
     530#: src/level_result.cxx:67
     531#, c-format
     532msgid "Pingus saved:   %3d/%3d"
     533msgstr "Gerettete Pingus: %3d/%3d"
     534
     535#: src/level_result.cxx:72
     536#, c-format
     537msgid "Pingus died:  %3d/%3d"
     538msgstr "Tote Pingus: %3d/%3d"
     539
     540#: src/level_result.cxx:88
     541msgid "Press button to continue..."
     542msgstr "Knopf drücken um fortzufahren"
     543
     544#: src/level_result.cxx:102
     545msgid ""
     546"As many Pingus escaped as entered the level. That's going to be hard to "
     547"beat.... unless this game becomes pornographic."
     548msgstr ""
     549"Es wurden alle Pingu dieses Levels gerettet. Das wird hart zu schlagen "
     550"sein... es sei denn dieses Spiel wird pornographisch."
     551
     552#: src/level_result.cxx:104
     553msgid "Very impressive indeed."
     554msgstr "In der Tat sehr eindrucksvoll."
     555
     556#: src/level_result.cxx:106
     557msgid "Good work. Still room for improvement though."
     558msgstr "Gute Arbeit. Aber Übung macht den Meiser!"
     559
     560#: src/level_result.cxx:108
     561msgid "Not too shabby, not too shabby at all."
     562msgstr "Nicht schlecht, nicht schlecht!"
     563
     564#: src/level_result.cxx:110
     565msgid ""
     566"That was OK, but Pingu life insurance premiums have just gotten more "
     567"expensive."
     568msgstr ""
     569"Das war ok, aber die Lebensversicherungsprämien für Pingus sind gerade "
     570"gestiegen."
     571
     572#: src/level_result.cxx:112
     573msgid "Maybe this level calls for a different strategy."
     574msgstr "Möglicherweise verlangt dieser Level eine andere Strategie."
     575
     576#: src/level_result.cxx:114
     577msgid "Exactly half. Are you saving only the female ones?"
     578msgstr "Genau die Hälfte! Rettest du nur die Weibchen?"
     579
     580#: src/level_result.cxx:116
     581msgid "If I were a Pingu, I never would have left that entrance."
     582msgstr "Wenn ich ein Pingu wäre, hätte ich den Eingang nie verlassen."
     583
     584#: src/level_result.cxx:118
     585msgid "Maybe you would feel more at home playing Quake."
     586msgstr "Möglicherweise wäre es besser Quake zu spielen?"
     587
     588#: src/level_result.cxx:120
     589msgid ""
     590"Maybe this level calls for a different strategy. Like attempting to save "
     591"them, for example."
     592msgstr ""
     593"Es kann sein, dass wir etwas anderes versuchen sollten.  Vielleicht könnten "
     594"wir ja die Pingus retten?"
     595
     596#: src/level_result.cxx:122
     597msgid "Ever considered a career as a Pingu exterminator?"
     598msgstr "Hast du mal über eine Karriere als Pingu Zerstörer nachgedacht?"
     599
     600#: src/level_result.cxx:124
     601msgid "You missed one! What's your excuse!?"
     602msgstr "Du hast einen vergessen! Was ist deine Entschuldigung?"
     603
     604#: src/level_result.cxx:126
     605msgid "Please reassure me that you hit the Armageddon button."
     606msgstr "Bestätige mir bitte, dass das die Armageddon Taste war."
     607
     608#: src/level_result.cxx:128
     609msgid "You've got a negative save/total value, something is buggy."
     610msgstr "Ein negativer Wert? Hier liegt ein Fehler vor."
     611
     612#: src/menu_button.cxx:181
     613msgid "..:: The people who brought this game to you ::.."
     614msgstr "..:: Die Leute, die fuer dieses Spiel verantwortlich sind... ::.."
     615
     616#: src/menu_button.cxx:184
     617msgid "Credits"
     618msgstr "Mitwirkende"
     619
     620#: src/menu_button.cxx:216
     621msgid "..:: Takes you to the options menu ::.."
     622msgstr "..:: Einstellungen, Cheats und Debugging stuff ::.."
     623
     624#: src/menu_button.cxx:219
     625msgid "Options"
     626msgstr "Einstellungen"
     627
     628#: src/menu_button.cxx:252
     629msgid "..:: Bye, bye ::.."
     630msgstr "..:: Auf Wiedersehen ::.."
     631
     632#: src/menu_button.cxx:255
     633msgid "Exit"
     634msgstr "Beenden"
     635
     636#: src/menu_button.cxx:306
     637msgid "..:: Launch the level editor ::.."
     638msgstr "..:: Erstelle deinen eigenen Level ::.."
     639
     640#: src/menu_button.cxx:309
     641msgid "Create a"
     642msgstr "Bau einen"
     643
     644#: src/menu_button.cxx:310
     645msgid "Level"
     646msgstr "Level"
     647
     648#: src/menu_button.cxx:344
     649msgid "..:: Start the game ::.."
     650msgstr "..:: das Spiel starten ::.."
     651
     652#: src/menu_button.cxx:345
     653msgid "Start"
     654msgstr "Start"
     655
     656#: src/menu_button.cxx:375
     657msgid "..:: Start a contrib level ::.."
     658msgstr "..:: Contrib level Spielen ::.."
     659
     660#: src/menu_button.cxx:377
     661msgid "Contrib"
     662msgstr "Levels"
     663
     664#: src/menu_button.cxx:399
     665msgid "..:: Multiplayer Modes... experimental stuff ::.."
     666msgstr "..:: Mehrspieler Modus ::.. Experimentelles Zeug ::.."
     667
     668#: src/menu_button.cxx:401
     669msgid "Multi"
     670msgstr "Multi"
     671
     672#: src/pingu_enums.cxx:35
     673msgid "Angel"
     674msgstr "Angel"
     675
     676#: src/pingu_enums.cxx:36
     677msgid "Basher"
     678msgstr "Basher"
     679
     680#: src/pingu_enums.cxx:37
     681msgid "Blocker"
     682msgstr "Blocker"
     683
     684#: src/pingu_enums.cxx:38
     685msgid "Boarder"
     686msgstr "Boarder"
     687
     688#: src/pingu_enums.cxx:39
     689msgid "Bomber"
     690msgstr "Bomber"
     691
     692#: src/pingu_enums.cxx:41
     693msgid "Climber"
     694msgstr "Climber"
     695
     696#: src/pingu_enums.cxx:42
     697msgid "Digger"
     698msgstr "Digger"
     699
     700#: src/pingu_enums.cxx:43
     701msgid "Drown"
     702msgstr "Drown"
     703
     704#: src/pingu_enums.cxx:44
     705msgid "Exiter"
     706msgstr "Exiter"
     707
     708#: src/pingu_enums.cxx:45
     709msgid "Faller"
     710msgstr "Faller"
     711
     712#: src/pingu_enums.cxx:46
     713msgid "Floater"
     714msgstr "Floater"
     715
     716#: src/pingu_enums.cxx:47
     717msgid "Jumper"
     718msgstr "Jumper"
     719
     720#: src/pingu_enums.cxx:48
     721msgid "Laserkill"
     722msgstr "Laserkill"
     723
     724#: src/pingu_enums.cxx:49
     725msgid "Miner"
     726msgstr "Miner"
     727
     728#: src/pingu_enums.cxx:50
     729msgid "Rocketlauncher"
     730msgstr "Rocketlauncher"
     731
     732#: src/pingu_enums.cxx:51
     733msgid "Slider"
     734msgstr "Slider"
     735
     736#: src/pingu_enums.cxx:52
     737msgid "Smashed"
     738msgstr "Smashed"
     739
     740#: src/pingu_enums.cxx:53
     741msgid "Splashed"
     742msgstr "Splashed"
     743
     744#: src/pingu_enums.cxx:54
     745msgid "Superman"
     746msgstr "Superman"
     747
     748#: src/pingu_enums.cxx:55
     749msgid "Teleported"
     750msgstr "Teleported"
     751
     752#: src/pingu_enums.cxx:56
     753msgid "Waiter"
     754msgstr "Waiter"
     755
     756#: src/pingu_enums.cxx:57
     757msgid "Walker"
     758msgstr "Walker"
     759
     760#: src/pingus_counter.cxx:52
     761#, c-format
     762msgid "Released:%3d/%-3d   Out:%3d   Saved:%3d/%-3d"
     763msgstr "Rein: %3d/%-3d  Raus: %3d Gerettet: %3d/%-3d"
     764
     765#: src/pingus_main.cxx:90
     766msgid "| segfault_handler: catched a SIGSEGV."
     767msgstr "| segfault_handler: SIGSEGV abgefangen."
     768
     769#: src/pingus_main.cxx:92
     770msgid "| Woops, Pingus just crashed, congratulations you've found a bug."
     771msgstr ""
     772"| Woops, Pingus ist abgestürzt. Gratuliere, du hast einen Bug gefunden."
     773
     774#: src/pingus_main.cxx:93
     775msgid ""
     776"| Please write a little bug report to <grumbel@gmx.de>, include informations"
     777msgstr ""
     778"| Bitte schreibe einen kleinen Report an <pingus-devel@nongnu.org, mit "
     779"Informationen,"
     780
     781#: src/pingus_main.cxx:94
     782msgid "| where exacly the SIGSEGV occured and how to reproduce it."
     783msgstr "| wo genau der SIGSEGV auftrat und wie man ihn reproduziert."
     784
     785#: src/pingus_main.cxx:95
     786msgid "| Also try include a backtrace, you can get it like this:"
     787msgstr "| Versuche auch einen backtrace zu erstellen, du bekommst ihn so:"
     788
     789#: src/pingus_main.cxx:101
     790msgid "| If that doesn't work, try this:"
     791msgstr "| Wenn das nicht geht, versuche dies:"
     792
     793#: src/pingus_main.cxx:105
     794msgid "| [play until it crashes again]"
     795msgstr "| [spiele bis es wieder crasht]"
     796
     797#: src/pingus_main.cxx:113
     798msgid "| Warning: Pingus recieved a SIGINT, exiting now."
     799msgstr "| Warnung: Pingus erhielt einen SIGINT, beende jetzt."
     800
     801#: src/pingus_main.cxx:301
     802msgid "Warning: Larger resolution than 800x600 will result in visual problems"
     803msgstr ""
     804"Warnung: Auflösungen grösser als 800x600 können zu visuellen Problemen führen"
     805
     806#: src/pingus_main.cxx:488
     807msgid "Unknow char: "
     808msgstr "Unbekannter Buchstabe: "
     809
     810#: src/pingus_main.cxx:489
     811msgid "Usage: "
     812msgstr "Benutzung: "
     813
     814#: src/pingus_main.cxx:489
     815msgid " [OPTIONS]... [LEVELFILE]"
     816msgstr "[OPTIONEN]... [LEVELDATEI]"
     817
     818#: src/pingus_main.cxx:492
     819msgid "Options:"
     820msgstr "Einstellungen"
     821
     822#: src/pingus_main.cxx:494
     823msgid "Set the resolution for pingus (default: 800x600)"
     824msgstr "Setze die Auflösung für Pingus (Standard: 800x600)"
     825
     826#: src/pingus_main.cxx:495
     827msgid "Displays this help"
     828msgstr "Hilfe Anzeigen"
     829
     830#: src/pingus_main.cxx:496
     831msgid "Disable intro"
     832msgstr "Intro abschalten"
     833
     834#: src/pingus_main.cxx:497
     835msgid "Use OpenGL"
     836msgstr "OpenGL benutzen"
     837
     838#: src/pingus_main.cxx:499
     839msgid "Start in Window Mode"
     840msgstr "Pingus im Fenster starten"
     841
     842#: src/pingus_main.cxx:500
     843msgid "Start in Fullscreen"
     844msgstr "Pingus im Vollbild starten"
     845
     846#: src/pingus_main.cxx:504
     847msgid "FILE      "
     848msgstr "Datei     "
     849
     850#: src/pingus_main.cxx:504
     851msgid "Load a custom level from FILE"
     852msgstr "Einen Level aus DATEI laden"
     853
     854#: src/pingus_main.cxx:505
     855msgid "FILE        "
     856msgstr "Datei      "
     857
     858#: src/pingus_main.cxx:505
     859msgid "Load a custom worldmap from FILE"
     860msgstr "Eine Weltkarte aus DATEI laden"
     861
     862#: src/pingus_main.cxx:506
     863msgid "Print some more messages to stdout, can be set"
     864msgstr "Gibt mehr Nachrichten auf stdout aus, kann"
     865
     866#: src/pingus_main.cxx:507
     867msgid "multiple times to increase verbosity"
     868msgstr "mehrmals gesetzt werden, um die Genauigkeit zu erhöhen"
     869
     870#: src/pingus_main.cxx:508
     871msgid "Prints version number and exit"
     872msgstr "Version ausgeben und beenden"
     873
     874#: src/pingus_main.cxx:509
     875msgid "Launch the Level editor (experimental)"
     876msgstr "Level Editor starten (Experimentell)"
     877
     878#: src/pingus_main.cxx:510
     879msgid "Disable automatic scrolling"
     880msgstr "Automatisches Scrollen abschalten"
     881
     882#: src/pingus_main.cxx:512
     883msgid "Enable software cursor"
     884msgstr "Aktiviere Software Cursor"
     885
     886#: src/pingus_main.cxx:515
     887msgid "Don't read ~/.pingus/config"
     888msgstr "~/.pingus/config nicht einlesen"
     889
     890#: src/pingus_main.cxx:516
     891msgid "FILE       "
     892msgstr "Datei      "
     893
     894#: src/pingus_main.cxx:516
     895msgid "Read config from FILE (default: ~/.pingus/config)"
     896msgstr "Konfiguration aus DATEI lesen"
     897
     898#: src/pingus_main.cxx:517
     899msgid "reduce CPU usage, might speed up the game on slower machines"
     900msgstr ""
     901"reduziere CPU Belastung, könnte das Spiel auf langsamen Rechnern "
     902"beschleunigen"
     903
     904#: src/pingus_main.cxx:518
     905msgid "Uses the controller given in FILE"
     906msgstr "Controller aus FILE benutzen"
     907
     908#: src/pingus_main.cxx:520
     909msgid "Debugging and experimental stuff:"
     910msgstr "Debug und Experimentelles Zeug"
     911
     912#: src/pingus_main.cxx:521
     913msgid "Enables some features, only interesting programmers"
     914msgstr "Aktiviere einige Funktionen, nur für Programmierer"
     915
     916#: src/pingus_main.cxx:522
     917msgid "Enable the output of debugging infos, possible"
     918msgstr "Aktiviere die Ausgabe von Debug Informationen, mögliche"
     919
     920#: src/pingus_main.cxx:523
     921msgid "OPTION's are tiles, gametime, actions, sound, resources, gui,"
     922msgstr "Optionen sind tiles, gametime, actions, sound, resources, gui,"
     923
     924#: src/pingus_main.cxx:525
     925msgid "Skip at least N frames, larger values speed the game up"
     926msgstr ""
     927"Überspringe mindestens N Bilder, größere Werte erhöhen die Geschwindigkeit"
     928
     929#: src/pingus_main.cxx:526
     930msgid "Skip at most N frames"
     931msgstr "Überspringe maximal N Bilder"
     932
     933#: src/pingus_main.cxx:527
     934msgid "Set both min and max frameskip to N"
     935msgstr "Setze sowohl minimalen als auch maximalen Bildsprung auf N"
     936
     937#: src/pingus_main.cxx:528
     938msgid "Set the game speed (0=fastest, >0=slower)"
     939msgstr "Setze die Geschwindigkeit (0=schnellste, >0=langsamer)"
     940
     941#: src/pingus_main.cxx:529
     942msgid "Prints the fps to stdout"
     943msgstr "Schreibe die fps auf stdout"
     944
     945#: src/pingus_main.cxx:530
     946msgid "Set the size of the map tiles (default: 32)"
     947msgstr "Setze die Größe der Kartenteile (Standard: 32)"
     948
     949#: src/pingus_main.cxx:531
     950msgid "Disable some cpu intensive features"
     951msgstr "Deaktivere einige CPU intensive Funktionen"
     952
     953#: src/pingus_main.cxx:532
     954msgid "Reduces the CPU usage by issuing sleep()"
     955msgstr "Reduziert die CPU Belastung durch Verwendung von sleep()"
     956
     957#: src/pingus_main.cxx:534
     958msgid "Demo playing and recording:"
     959msgstr "Demo aufzeichnung und wiedergabe:"
     960
     961#: src/pingus_main.cxx:535
     962msgid "FILE     "
     963msgstr "Datei    "
     964
     965#: src/pingus_main.cxx:535
     966msgid "Plays a demo session from FILE"
     967msgstr "Spielt eine Demo Sitzung aus FILE"
     968
     969#: src/pingus_main.cxx:537
     970msgid "Record demos for each played level"
     971msgstr "Nehme Demos für jeden gespielten Level auf"
     972
     973#: src/pingus_main.cxx:539
     974msgid "Sound:"
     975msgstr "Sound"
     976
     977#: src/pingus_main.cxx:540
     978msgid "Disable sound"
     979msgstr "Sound deaktivieren"
     980
     981#: src/pingus_main.cxx:541
     982msgid "Disable music"
     983msgstr "Musik deaktivieren"
     984
     985#: src/pingus_main.cxx:720
     986msgid "clanVorbis support:           ok"
     987msgstr "clanVorbis Unterstützung:    ok"
     988
     989#: src/pingus_main.cxx:722
     990msgid "clanVoribs support:  missing (.ogg music files will not be playable)"
     991msgstr ""
     992"clanVorbis Unterstützung: fehlt (.ogg Musik Datein können nicht abgespielt "
     993"werden)"
     994
     995#: src/pingus_main.cxx:726
     996msgid "clanMikMod support:           ok"
     997msgstr "clanMikMod Unterstützung:    ok"
     998
     999#: src/pingus_main.cxx:728
     1000msgid "clanMikMod support:  missing (music files will not be playable)"
     1001msgstr ""
     1002"clanMikMod Untersützung: fehlt (Musikdateien können nicht abgespielt werden)"
     1003
     1004#: src/pingus_main.cxx:732
     1005msgid "getext support:               ok"
     1006msgstr "gettext Unterstützung:       ok"
     1007
     1008#: src/pingus_main.cxx:733
     1009msgid "gettext language:        english"
     1010msgstr "gettext Sprache:        deutsch"
     1011
     1012#: src/pingus_main.cxx:739
     1013msgid "sound support:           enabled"
     1014msgstr "Sound Unterstützung:         an"
     1015
     1016#: src/pingus_main.cxx:741
     1017msgid "sound support:          disabled"
     1018msgstr "Sound Unterstützung:        aus"
     1019
     1020#: src/pingus_main.cxx:744
     1021msgid "music support:           enabled"
     1022msgstr "Musik Unterstützung:         an"
     1023
     1024#: src/pingus_main.cxx:746
     1025msgid "music support:          disabled"
     1026msgstr "Musik Unterstützung:        aus"
     1027
     1028#: src/pingus_main.cxx:748
     1029msgid "resolution set to:       "
     1030msgstr "Auflösung:              "
     1031
     1032#: src/pingus_main.cxx:749
     1033msgid "fullscreen:              "
     1034msgstr "Vollbild:           "
     1035
     1036#: src/pingus_main.cxx:750
     1037msgid " enabled"
     1038msgstr "aktiviert"
     1039
     1040#: src/pingus_main.cxx:750
     1041msgid "disabled"
     1042msgstr "däktiviert"
     1043
     1044
     1045#: src/pingus_main.cxx:751
     1046msgid "reenabled"
     1047msgstr ""
  • new file libraries/source/tinygettext/test/po/de_AT.po

    diff --git a/libraries/source/tinygettext/test/po/de_AT.po b/libraries/source/tinygettext/test/po/de_AT.po
    new file mode 100644
    index 0000000..f4c3d81
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR Free Software Foundation, Inc.
     3# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     4#
     5#, fuzzy
     6msgid ""
     7msgstr ""
     8"Project-Id-Version: PACKAGE VERSION\n"
     9"PO-Revision-Date: 2009-02-02 12:07+0100\n"
     10"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     11"Language-Team: LANGUAGE <LL@li.org>\n"
     12"MIME-Version: 1.0\n"
     13"Content-Type: text/plain; charset=UTF-8\n"
     14"Content-Transfer-Encoding: 8bit\n"
     15"Plural-Forms: nplurals=2; plural=( n != 1);\n"
     16
     17msgid "umlaut"
     18msgstr "ÄÖÜäöü߀¢"
     19
     20msgid "You got %d error."
     21msgid_plural "You got %d error."
     22msgstr[0] "Du hast %d fehler"
     23msgstr[1] "Du hast %d fehlers"
     24
     25msgid "found %d fatal error"
     26msgid_plural "found %d fatal errors"
     27msgstr[0] "s'ha trobat %d error fätal"
     28msgstr[1] "s'han trobat %d errors fätals"
     29
     30
     31msgid "I ate %d pizza."
     32msgid_plural "I ate %d pizzas."
     33msgstr[0] ""
  • new file libraries/source/tinygettext/test/po/fr.po

    diff --git a/libraries/source/tinygettext/test/po/fr.po b/libraries/source/tinygettext/test/po/fr.po
    new file mode 100644
    index 0000000..0670f48
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR Free Software Foundation, Inc.
     3# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     4#
     5#, fuzzy
     6msgid ""
     7msgstr ""
     8"Project-Id-Version: PACKAGE VERSION\n"
     9"PO-Revision-Date: 2009-01-28 08:45+0100\n"
     10"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     11"Language-Team: LANGUAGE <LL@li.org>\n"
     12"MIME-Version: 1.0\n"
     13"Content-Type: text/plain; charset=utf-8\n"
     14"Content-Transfer-Encoding: 8bit\n"
     15
     16#: src/actions/bridger.cxx:48 src/actions/bridger.cxx:232
     17#: src/pingu_enums.cxx:40
     18msgid "invalid"
     19msgstr "ungütig"
     20
  • new file libraries/source/tinygettext/test/po_parser_test.cpp

    diff --git a/libraries/source/tinygettext/test/po_parser_test.cpp b/libraries/source/tinygettext/test/po_parser_test.cpp
    new file mode 100644
    index 0000000..4e288a9
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include <errno.h>
     19#include <string.h>
     20#include <iostream>
     21#include <fstream>
     22#include <stdexcept>
     23
     24#include "tinygettext/po_parser.hpp"
     25#include "tinygettext/tinygettext.hpp"
     26#include "tinygettext/log.hpp"
     27
     28void my_log_callback(const std::string& err)
     29{
     30  std::cerr << err;
     31}
     32
     33int main(int argc, char** argv)
     34{
     35  if (argc < 2)
     36  {
     37    std::cout << argv[0] << " FILENAME..." << std::endl;
     38  }
     39  else
     40  {
     41    tinygettext::Log::set_log_info_callback(my_log_callback);
     42    tinygettext::Log::set_log_warning_callback(my_log_callback);
     43    tinygettext::Log::set_log_error_callback(my_log_callback);
     44
     45    for(int i = 1; i < argc; ++i)
     46    {
     47      std::ifstream in(argv[i]);
     48      if (!in)
     49      {
     50        std::cerr << argv[0] << ": cannot access " << argv[i] << ": " << strerror(errno) << std::endl;
     51      }
     52      else
     53      {
     54        try
     55        {
     56          tinygettext::Dictionary dict1;
     57          tinygettext::POParser::parse(argv[i], in, dict1);
     58
     59          //tinygettext::Dictionary dict2;
     60          //tinygettext::POFileReader::read(in, dict2);
     61        }
     62        catch(std::runtime_error& err)
     63        {
     64          std::cout << argv[i] << ": exception: " << err.what() << std::endl;
     65        }
     66      }
     67    }
     68  }
     69}
     70
     71/* EOF */
  • new file libraries/source/tinygettext/test/test.sh

    diff --git a/libraries/source/tinygettext/test/test.sh b/libraries/source/tinygettext/test/test.sh
    new file mode 100644
    index 0000000..a7b7789
    - +  
     1#!/bin/sh
     2
     3./tinygettext_test translate po/fr.po  "invalid"
     4./tinygettext_test directory po/ umlaut Deutsch
     5./tinygettext_test directory po/ umlaut deutsch
     6./tinygettext_test directory po/ umlaut de
     7
     8# EOF #
  • new file libraries/source/tinygettext/test/tinygettext_test.cpp

    diff --git a/libraries/source/tinygettext/test/tinygettext_test.cpp b/libraries/source/tinygettext/test/tinygettext_test.cpp
    new file mode 100644
    index 0000000..cea845e
    - +  
     1//  TinyGetText
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include <iostream>
     19#include <string.h>
     20#include <fstream>
     21#include <stdlib.h>
     22#include <iostream>
     23#include <stdexcept>
     24#include "tinygettext/po_parser.hpp"
     25#include "tinygettext/tinygettext.hpp"
     26
     27using namespace tinygettext;
     28
     29void print_msg(const std::string& msgid, const std::vector<std::string>& msgstrs)
     30{
     31  std::cout << "Msgid: " << msgid << std::endl;
     32  for(std::vector<std::string>::const_iterator i = msgstrs.begin(); i != msgstrs.end(); ++i)
     33    {
     34      std::cout << *i << std::endl;
     35    }
     36}
     37
     38void print_msg_ctxt(const std::string& ctxt, const std::string& msgid, const std::vector<std::string>& msgstrs)
     39{
     40  std::cout << "Msgctxt: " << ctxt << std::endl;
     41  std::cout << "Msgid: " << msgid << std::endl;
     42  for(std::vector<std::string>::const_iterator i = msgstrs.begin(); i != msgstrs.end(); ++i)
     43    {
     44      std::cout << *i << std::endl;
     45    }
     46}
     47
     48void print_usage(int /*argc*/, char** argv)
     49{
     50  std::cout << "Usage: " << argv[0] << " translate FILE MESSAGE" << std::endl;
     51  std::cout << "       " << argv[0] << " translate FILE MESSAGE_S MESSAGE_P NUM" << std::endl;
     52  std::cout << "       " << argv[0] << " directory DIRECTORY MESSAGE [LANG]" << std::endl;
     53  std::cout << "       " << argv[0] << " language LANGUAGE" << std::endl;
     54  std::cout << "       " << argv[0] << " language-dir DIR" << std::endl;
     55  std::cout << "       " << argv[0] << " list-msgstrs FILE" << std::endl;
     56}
     57
     58void read_dictionary(const std::string& filename, Dictionary& dict)
     59{
     60  std::ifstream in(filename.c_str());
     61
     62  if (!in)
     63    {
     64      throw std::runtime_error("Couldn't open " + filename);
     65    }
     66  else
     67    {
     68      POParser::parse(filename, in, dict);
     69      in.close();
     70    }
     71}
     72
     73int main(int argc, char** argv)
     74{
     75  try
     76  {
     77    if (argc == 3 && strcmp(argv[1], "language-dir") == 0)
     78    {
     79      DictionaryManager dictionary_manager;
     80      dictionary_manager.add_directory(argv[2]);
     81      const std::set<Language>& languages = dictionary_manager.get_languages();
     82      std::cout << "Number of languages: " << languages.size() << std::endl;
     83      for (std::set<Language>::const_iterator i = languages.begin(); i != languages.end(); ++i)
     84      {
     85        const Language& language = *i;
     86        std::cout << "Env:       " << language.str()           << std::endl
     87                  << "Name:      " << language.get_name()      << std::endl
     88                  << "Language:  " << language.get_language()  << std::endl
     89                  << "Country:   " << language.get_country()   << std::endl
     90                  << "Modifier:  " << language.get_modifier()  << std::endl
     91                  << std::endl;
     92      }
     93    }
     94    else if (argc == 3 && strcmp(argv[1], "language") == 0)
     95    {
     96      Language language = Language::from_name(argv[2]);
     97
     98      if (language)
     99        std::cout << "Env:       " << language.str()           << std::endl
     100                  << "Name:      " << language.get_name()      << std::endl
     101                  << "Language:  " << language.get_language()  << std::endl
     102                  << "Country:   " << language.get_country()   << std::endl
     103                  << "Modifier:  " << language.get_modifier()  << std::endl;
     104      else
     105        std::cout << "not found" << std::endl;
     106    }
     107    else if (argc == 4 && strcmp(argv[1], "translate") == 0)
     108    {
     109      const char* filename = argv[2];
     110      const char* message  = argv[3];
     111
     112      Dictionary dict;
     113      read_dictionary(filename, dict);
     114      std::cout << "TRANSLATION: \"\"\"" << dict.translate(message) << "\"\"\""<< std::endl;
     115    }
     116    else if (argc == 5 && strcmp(argv[1], "translate") == 0)
     117    {
     118      const char* filename = argv[2];
     119      const char* context  = argv[3];
     120      const char* message  = argv[4];
     121
     122      Dictionary dict;
     123      read_dictionary(filename, dict);
     124      std::cout << dict.translate_ctxt(context, message) << std::endl;
     125    }
     126    else if (argc == 6 && strcmp(argv[1], "translate") == 0)
     127    {
     128      const char* filename = argv[2];
     129      const char* message_singular = argv[3];
     130      const char* message_plural   = argv[4];
     131      int num = atoi(argv[5]);
     132
     133      Dictionary dict;
     134      read_dictionary(filename, dict);
     135      std::cout << dict.translate_plural(message_singular, message_plural, num) << std::endl;
     136    }
     137    else if (argc == 7 && strcmp(argv[1], "translate") == 0)
     138    {
     139      const char* filename = argv[2];
     140      const char* context  = argv[3];
     141      const char* message_singular = argv[4];
     142      const char* message_plural   = argv[5];
     143      int num = atoi(argv[6]);
     144
     145      Dictionary dict;
     146      read_dictionary(filename, dict);
     147      std::cout << dict.translate_ctxt_plural(context, message_singular, message_plural, num) << std::endl;
     148    }
     149    else if ((argc == 4 || argc == 5) && strcmp(argv[1], "directory") == 0)
     150    {
     151      const char* directory = argv[2];
     152      const char* message   = argv[3];
     153      const char* language  = (argc == 5) ? argv[4] : NULL;
     154
     155      DictionaryManager manager;
     156      manager.add_directory(directory);
     157
     158      if (language)
     159      {
     160        Language lang = Language::from_name(language);
     161        if (lang)
     162        {
     163          manager.set_language(lang);
     164        }
     165        else
     166        {
     167          std::cout << "Unknown language: " << language << std::endl;
     168          exit(EXIT_FAILURE);
     169        }
     170      }
     171
     172      std::cout << "Directory:   '" << directory << "'"  << std::endl;
     173      std::cout << "Message:     '" << message << "'" << std::endl;
     174      std::cout << "Language:    '" << manager.get_language().str() << "' (name: '"
     175                << manager.get_language().get_name() << "', language: '"
     176                << manager.get_language().get_language() << "', country: '"
     177                << manager.get_language().get_country() << "', modifier: '"
     178                << manager.get_language().get_modifier() << "')"
     179                << std::endl;
     180      std::cout << "Translation: '" << manager.get_dictionary().translate(message) << "'" << std::endl;
     181    }
     182    else if ((argc == 3) && strcmp(argv[1], "list-msgstrs") == 0)
     183    {
     184      const char* filename = argv[2];
     185
     186      Dictionary dict;
     187      read_dictionary(filename, dict);
     188      dict.foreach(print_msg);
     189      dict.foreach_ctxt(print_msg_ctxt);
     190    }
     191    else
     192    {
     193      print_usage(argc, argv);
     194    }
     195  }
     196  catch(std::exception& err)
     197  {
     198    std::cout << "Exception: " << err.what() << std::endl;
     199  }
     200
     201  return 0;
     202}
     203
     204/* EOF */
  • new file libraries/source/tinygettext/tinygettext.pc.in

    diff --git a/libraries/source/tinygettext/tinygettext.pc.in b/libraries/source/tinygettext/tinygettext.pc.in
    new file mode 100644
    index 0000000..ef82a93
    - +  
     1# Unfortunately, since this project is built with cmake,
     2# these variables can't properly be set.
     3
     4prefix=@CMAKE_INSTALL_PREFIX@
     5exec_prefix=${prefix}
     6libdir=${exec_prefix}/@LIB_SUBDIR@
     7includedir=${prefix}/include
     8
     9Name: @PROJECT_NAME@
     10Description: tiny, minimal gettext replacement
     11Version: @VERSION@
     12URL: http://tinygettext.googlecode.com/
     13Requires:
     14Libs: -L${libdir} -ltinygettext
     15Libs.private:
     16Cflags: -I${includedir}
  • new file libraries/source/tinygettext/tinygettext/dictionary.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/dictionary.cpp b/libraries/source/tinygettext/tinygettext/dictionary.cpp
    new file mode 100644
    index 0000000..9765d75
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include <assert.h>
     19#include "log_stream.hpp"
     20#include "dictionary.hpp"
     21
     22namespace tinygettext {
     23
    024
     25Dictionary::Dictionary(const std::string& charset_) :
     26  entries(),
     27  ctxt_entries(),
     28  charset(charset_),
     29  plural_forms()
     30{
     31}
     32
     33Dictionary::~Dictionary()
     34{
     35}
     36
     37std::string
     38Dictionary::get_charset() const
     39{
     40  return charset;
     41}
     42
     43void
     44Dictionary::set_plural_forms(const PluralForms& plural_forms_)
     45{
     46  plural_forms = plural_forms_;
     47}
     48
     49PluralForms
     50Dictionary::get_plural_forms() const
     51{
     52  return plural_forms;
     53}
     54
     55std::string
     56Dictionary::translate_plural(const std::string& msgid, const std::string& msgid_plural, int num)
     57{
     58  return translate_plural(entries, msgid, msgid_plural, num);
     59}
     60
     61std::string
     62Dictionary::translate_plural(const Entries& dict, const std::string& msgid, const std::string& msgid_plural, int count)
     63{
     64  Entries::const_iterator i = dict.find(msgid);
     65  const std::vector<std::string>& msgstrs = i->second;
     66
     67  if (i != dict.end())
     68  {
     69    unsigned int n = 0;
     70    n = plural_forms.get_plural(count);
     71    assert(/*n >= 0 &&*/ n < msgstrs.size());
     72
     73    if (!msgstrs[n].empty())
     74      return msgstrs[n];
     75    else
     76      if (count == 1) // default to english rules
     77        return msgid;
     78      else
     79        return msgid_plural;
     80  }
     81  else
     82  {
     83    log_info << "Couldn't translate: " << msgid << std::endl;
     84    log_info << "Candidates: " << std::endl;
     85    for (i = dict.begin(); i != dict.end(); ++i)
     86      log_info << "'" << i->first << "'" << std::endl;
     87
     88    if (count == 1) // default to english rules
     89      return msgid;
     90    else
     91      return msgid_plural;
     92  }
     93}
     94
     95std::string
     96Dictionary::translate(const std::string& msgid)
     97{
     98  return translate(entries, msgid);
     99}
     100
     101std::string
     102Dictionary::translate(const Entries& dict, const std::string& msgid)
     103{
     104  Entries::const_iterator i = dict.find(msgid);
     105  if (i != dict.end() && !i->second.empty())
     106  {
     107    return i->second[0];
     108  }
     109  else
     110  {
     111    log_info << "Couldn't translate: " << msgid << std::endl;
     112    return msgid;
     113  }
     114}
     115
     116std::string
     117Dictionary::translate_ctxt(const std::string& msgctxt, const std::string& msgid)
     118{
     119  CtxtEntries::iterator i = ctxt_entries.find(msgctxt);
     120  if (i != ctxt_entries.end())
     121  {
     122    return translate(i->second, msgid);
     123  }
     124  else
     125  {
     126    log_info << "Couldn't translate: " << msgid << std::endl;
     127    return msgid;
     128  }
     129}
     130
     131std::string
     132Dictionary::translate_ctxt_plural(const std::string& msgctxt,
     133                                  const std::string& msgid, const std::string& msgidplural, int num)
     134{
     135  CtxtEntries::iterator i = ctxt_entries.find(msgctxt);
     136  if (i != ctxt_entries.end())
     137  {
     138    return translate_plural(i->second, msgid, msgidplural, num);
     139  }
     140  else
     141  {
     142    log_info << "Couldn't translate: " << msgid << std::endl;
     143    if (num != 1) // default to english
     144      return msgidplural;
     145    else
     146      return msgid;
     147  }
     148}
     149
    1150
     151void
     152Dictionary::add_translation(const std::string& msgid, const std::string& ,
     153                            const std::vector<std::string>& msgstrs)
     154{
     155  // Do we need msgid2 for anything? its after all supplied to the
     156  // translate call, so we just throw it away here
     157  entries[msgid] = msgstrs;
     158}
     159
     160void
     161Dictionary::add_translation(const std::string& msgid, const std::string& msgstr)
     162{
     163  std::vector<std::string>& vec = entries[msgid];
     164  if (vec.empty())
     165  {
     166    vec.push_back(msgstr);
     167  }
     168  else
     169  {
     170    log_warning << "collision in add_translation: '"
     171                << msgid << "' -> '" << msgstr << "' vs '" << vec[0] << "'" << std::endl;
     172    vec[0] = msgstr;
     173  }
     174}
     175
     176void
     177Dictionary::add_translation(const std::string& msgctxt,
     178                            const std::string& msgid, const std::string& msgid_plural,
     179                            const std::vector<std::string>& msgstrs)
     180{
     181  std::vector<std::string>& vec = ctxt_entries[msgctxt][msgid];
     182  if (vec.empty())
     183  {
     184    vec = msgstrs;
     185  }
     186  else
     187  {
     188    log_warning << "collision in add_translation(\"" << msgctxt << "\", \"" << msgid << "\", \"" << msgid_plural << "\")" << std::endl;
     189    vec = msgstrs;
     190  }
     191}
     192
     193void
     194Dictionary::add_translation(const std::string& msgctxt, const std::string& msgid, const std::string& msgstr)
     195{
     196  std::vector<std::string>& vec = ctxt_entries[msgctxt][msgid];
     197  if (vec.empty())
     198  {
     199    vec.push_back(msgstr);
     200  }
     201  else
     202  {
     203    log_warning << "collision in add_translation(\"" << msgctxt << "\", \"" << msgid << "\")" << std::endl;
     204    vec[0] = msgstr;
     205  }
     206}
     207
    2208
  • new file libraries/source/tinygettext/tinygettext/dictionary.hpp

    +} // namespace tinygettext
    +
    +/* EOF */
    diff --git a/libraries/source/tinygettext/tinygettext/dictionary.hpp b/libraries/source/tinygettext/tinygettext/dictionary.hpp
    new file mode 100644
    index 0000000..743e075
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_DICTIONARY_HPP
     19#define HEADER_TINYGETTEXT_DICTIONARY_HPP
     20
     21#include <map>
     22#include <vector>
     23#include <string>
     24#include "plural_forms.hpp"
     25
     26namespace tinygettext {
     27
     28/** A simple dictionary class that mimics gettext() behaviour. Each
     29    Dictionary only works for a single language, for managing multiple
     30    languages and .po files at once use the DictionaryManager. */
     31class Dictionary
     32{
     33private:
     34  typedef std::map<std::string, std::vector<std::string> > Entries;
     35  Entries entries;
     36
     37  typedef std::map<std::string, Entries> CtxtEntries;
     38  CtxtEntries ctxt_entries;
     39
     40  std::string charset;
     41  PluralForms plural_forms;
     42
     43  std::string translate(const Entries& dict, const std::string& msgid);
     44  std::string translate_plural(const Entries& dict, const std::string& msgid, const std::string& msgidplural, int num);
     45
     46public:
     47  /** Constructs a dictionary converting to the specified \a charset (default UTF-8) */
     48  Dictionary(const std::string& charset = "UTF-8");
     49  ~Dictionary();
     50
     51  /** Return the charset used for this dictionary */
     52  std::string get_charset() const;
     53
     54  void set_plural_forms(const PluralForms&);
     55  PluralForms get_plural_forms() const;
     56
     57
     58  /** Translate the string \a msgid. */
     59  std::string translate(const std::string& msgid);
     60
     61  /** Translate the string \a msgid to its correct plural form, based
     62      on the number of items given by \a num. \a msgid_plural is \a msgid in
     63      plural form. */
     64  std::string translate_plural(const std::string& msgid, const std::string& msgidplural, int num);
     65
     66  /** Translate the string \a msgid that is in context \a msgctx. A
     67      context is a way to disambiguate msgids that contain the same
     68      letters, but different meaning. For example "exit" might mean to
     69      quit doing something or it might refer to a door that leads
     70      outside (i.e. 'Ausgang' vs 'Beenden' in german) */
     71  std::string translate_ctxt(const std::string& msgctxt, const std::string& msgid);
     72
     73  std::string translate_ctxt_plural(const std::string& msgctxt, const std::string& msgid, const std::string& msgidplural, int num);
     74
     75  /** Add a translation from \a msgid to \a msgstr to the dictionary,
     76      where \a msgid is the singular form of the message, msgid_plural the
     77      plural form and msgstrs a table of translations. The right
     78      translation will be calculated based on the \a num argument to
     79      translate(). */
     80  void add_translation(const std::string& msgid, const std::string& msgid_plural,
     81                       const std::vector<std::string>& msgstrs);
     82  void add_translation(const std::string& msgctxt,
     83                       const std::string& msgid, const std::string& msgid_plural,
     84                       const std::vector<std::string>& msgstrs);
     85
     86  /** Add a translation from \a msgid to \a msgstr to the
     87      dictionary */
     88  void add_translation(const std::string& msgid, const std::string& msgstr);
     89  void add_translation(const std::string& msgctxt, const std::string& msgid, const std::string& msgstr);
     90
     91  /** Iterate over all messages, Func is of type:
     92      void func(const std::string& msgid, const std::vector<std::string>& msgstrs) */
     93  template<class Func>
     94  Func foreach(Func func)
     95  {
     96    for(Entries::iterator i = entries.begin(); i != entries.end(); ++i)
     97    {
     98      func(i->first, i->second);
     99    }
     100    return func;
     101  }
     102
     103  /** Iterate over all messages with a context, Func is of type:
     104      void func(const std::string& ctxt, const std::string& msgid, const std::vector<std::string>& msgstrs) */
     105  template<class Func>
     106  Func foreach_ctxt(Func func)
     107  {
     108    for(CtxtEntries::iterator i = ctxt_entries.begin(); i != ctxt_entries.end(); ++i)
     109    {
     110      for(Entries::iterator j = i->second.begin(); j != i->second.end(); ++j)
     111      {
     112        func(i->first, j->first, j->second);
     113      }
     114    }
     115    return func;
     116  }
     117};
     118
     119} // namespace tinygettext
     120
     121#endif
     122
     123/* EOF */
  • new file libraries/source/tinygettext/tinygettext/dictionary_manager.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/dictionary_manager.cpp b/libraries/source/tinygettext/tinygettext/dictionary_manager.cpp
    new file mode 100644
    index 0000000..f923470
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include "dictionary_manager.hpp"
     19
     20#include <memory>
     21#include <assert.h>
     22#include <stdlib.h>
     23#include <string.h>
     24#include <fstream>
     25#include <algorithm>
     26
     27#include "log_stream.hpp"
     28#include "po_parser.hpp"
     29#include "unix_file_system.hpp"
     30
     31namespace tinygettext {
     32
     33static bool has_suffix(const std::string& lhs, const std::string rhs)
     34{
     35  if (lhs.length() < rhs.length())
     36    return false;
     37  else
     38    return lhs.compare(lhs.length() - rhs.length(), rhs.length(), rhs) == 0;
     39}
     40
     41DictionaryManager::DictionaryManager(const std::string& charset_) :
     42  dictionaries(),
     43  search_path(),
     44  charset(charset_),
     45  use_fuzzy(true),
     46  current_language(),
     47  current_dict(0),
     48  empty_dict(),
     49  filesystem(new UnixFileSystem)
     50{
     51}
     52
     53DictionaryManager::~DictionaryManager()
     54{
     55  for(Dictionaries::iterator i = dictionaries.begin(); i != dictionaries.end(); ++i)
     56  {
     57    delete i->second;
     58  }
     59}
     60
     61void
     62DictionaryManager::clear_cache()
     63{
     64  for(Dictionaries::iterator i = dictionaries.begin(); i != dictionaries.end(); ++i)
     65  {
     66    delete i->second;
     67  }
     68  dictionaries.clear();
     69
     70  current_dict = 0;
     71}
     72
     73Dictionary&
     74DictionaryManager::get_dictionary()
     75{
     76  if (current_dict)
     77  {
     78    return *current_dict;
     79  }
     80  else
     81  {
     82    if (current_language)
     83    {
     84      current_dict = &get_dictionary(current_language);
     85      return *current_dict;
     86    }
     87    else
     88    {
     89      return empty_dict;
     90    }
     91  }
     92}
     93
     94Dictionary&
     95DictionaryManager::get_dictionary(const Language& language)
     96{
     97  //log_debug << "Dictionary for language \"" << spec << "\" requested" << std::endl;
     98  //log_debug << "...normalized as \"" << lang << "\"" << std::endl;
     99  assert(language);
     100
     101  Dictionaries::iterator i = dictionaries.find(language);
     102  if (i != dictionaries.end())
     103  {
     104    return *i->second;
     105  }
     106  else // Dictionary for languages lang isn't loaded, so we load it
     107  {
     108    //log_debug << "get_dictionary: " << lang << std::endl;
     109    Dictionary* dict = new Dictionary(charset);
     110
     111    dictionaries[language] = dict;
     112
     113    for (SearchPath::reverse_iterator p = search_path.rbegin(); p != search_path.rend(); ++p)
     114    {
     115      std::vector<std::string> files = filesystem->open_directory(*p);
     116
     117      std::string best_filename;
     118      int best_score = 0;
     119
     120      for(std::vector<std::string>::iterator filename = files.begin(); filename != files.end(); filename++)
     121      {
     122        // check if filename matches requested language
     123        if (has_suffix(*filename, ".po"))
     124        { // ignore anything that isn't a .po file
     125          Language po_language = Language::from_env(filename->substr(0, filename->size()-3));
     126
     127          if (!po_language)
     128          {
     129            log_warning << *filename << ": warning: ignoring, unknown language" << std::endl;
     130          }
     131          else
     132          {
     133            int score = Language::match(language, po_language);
     134                         
     135            if (score > best_score)
     136            {
     137              best_score = score;
     138              best_filename = *filename;
     139            }
     140          }
     141        }
     142      }
     143             
     144      if (!best_filename.empty())
     145      {
     146        std::string pofile = *p + "/" + best_filename;
     147        try
     148        {
     149          std::auto_ptr<std::istream> in = filesystem->open_file(pofile);
     150          if (!in.get())
     151          {
     152            log_error << "error: failure opening: " << pofile << std::endl;
     153          }
     154          else
     155          {
     156            POParser::parse(pofile, *in, *dict);
     157          }
     158        }
     159        catch(std::exception& e)
     160        {
     161          log_error << "error: failure parsing: " << pofile << std::endl;
     162          log_error << e.what() << "" << std::endl;
     163        }
     164      }
     165    }
     166
     167    return *dict;
     168  }
     169}
     170
     171std::set<Language>
     172DictionaryManager::get_languages()
     173{
     174  std::set<Language> languages;
     175
     176  for (SearchPath::iterator p = search_path.begin(); p != search_path.end(); ++p)
     177  {
     178    std::vector<std::string> files = filesystem->open_directory(*p);
     179
     180    for(std::vector<std::string>::iterator file = files.begin(); file != files.end(); ++file)
     181    {
     182      if (has_suffix(*file, ".po"))
     183      {
     184        languages.insert(Language::from_env(file->substr(0, file->size()-3)));
     185      }
     186    }
     187  }
     188  return languages;
     189}
     190
     191void
     192DictionaryManager::set_language(const Language& language)
     193{
     194  if (current_language != language)
     195  {
     196    current_language = language;
     197    current_dict     = 0;
     198  }
     199}
     200
     201Language
     202DictionaryManager::get_language() const
     203{
     204  return current_language;
     205}
     206
     207void
     208DictionaryManager::set_charset(const std::string& charset_)
     209{
     210  clear_cache(); // changing charset invalidates cache
     211  charset = charset_;
     212}
     213
     214void
     215DictionaryManager::set_use_fuzzy(bool t)
     216{
     217  clear_cache();
     218  use_fuzzy = t;
     219}
     220
     221bool
     222DictionaryManager::get_use_fuzzy() const
     223{
     224  return use_fuzzy;
     225}
     226
     227void
     228DictionaryManager::add_directory(const std::string& pathname)
     229{
     230  clear_cache(); // adding directories invalidates cache
     231  search_path.push_back(pathname);
     232}
     233
     234void
     235DictionaryManager::set_filesystem(std::auto_ptr<FileSystem> filesystem_)
     236{
     237  filesystem = filesystem_;
     238}
     239
     240} // namespace tinygettext
     241
     242/* EOF */
  • new file libraries/source/tinygettext/tinygettext/dictionary_manager.hpp

    diff --git a/libraries/source/tinygettext/tinygettext/dictionary_manager.hpp b/libraries/source/tinygettext/tinygettext/dictionary_manager.hpp
    new file mode 100644
    index 0000000..7c3d7f3
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_DICTIONARY_MANAGER_HPP
     19#define HEADER_TINYGETTEXT_DICTIONARY_MANAGER_HPP
     20
     21#include <map>
     22#include <set>
     23#include <string>
     24#include <vector>
     25#include <memory>
     26
     27#include "dictionary.hpp"
     28#include "language.hpp"
     29
     30namespace tinygettext {
     31
     32class FileSystem;
     33
     34/** Manager class for dictionaries, you give it a bunch of directories
     35    with .po files and it will then automatically load the right file
     36    on demand depending on which language was set. */
     37class DictionaryManager
     38{
     39private:
     40  typedef std::map<Language, Dictionary*> Dictionaries;
     41  Dictionaries dictionaries;
     42
     43  typedef std::vector<std::string> SearchPath;
     44  SearchPath search_path;
     45
     46  std::string charset;
     47  bool        use_fuzzy;
     48 
     49  Language    current_language;
     50  Dictionary* current_dict;
     51
     52  Dictionary  empty_dict;
     53
     54  std::auto_ptr<FileSystem> filesystem;
     55
     56  void clear_cache();
     57
     58public:
     59  DictionaryManager(const std::string& charset_ = "UTF-8");
     60  ~DictionaryManager();
     61
     62  /** Return the currently active dictionary, if none is set, an empty
     63      dictionary is returned. */
     64  Dictionary& get_dictionary();
     65
     66  /** Get dictionary for language */
     67  Dictionary& get_dictionary(const Language& language);
     68
     69  /** Set a language based on a four? letter country code */
     70  void set_language(const Language& language);
     71
     72  /** returns the (normalized) country code of the currently used language */
     73  Language get_language() const;
     74
     75  void set_use_fuzzy(bool t);
     76  bool get_use_fuzzy() const;
     77
     78  /** Set a charset that will be set on the returned dictionaries */
     79  void set_charset(const std::string& charset);
     80
     81  /** Add a directory to the search path for dictionaries, earlier
     82      added directories have higher priority then later added ones */
     83  void add_directory(const std::string& pathname);
     84
     85  /** Return a set of the available languages in their country code */
     86  std::set<Language> get_languages();
     87
     88  void set_filesystem(std::auto_ptr<FileSystem> filesystem);
     89
     90private:
     91  DictionaryManager (const DictionaryManager&);
     92  DictionaryManager& operator= (const DictionaryManager&);
     93};
     94
     95} // namespace tinygettext
     96
     97#endif
     98
     99/* EOF */
  • new file libraries/source/tinygettext/tinygettext/file_system.hpp

    diff --git a/libraries/source/tinygettext/tinygettext/file_system.hpp b/libraries/source/tinygettext/tinygettext/file_system.hpp
    new file mode 100644
    index 0000000..af47aaf
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_FILE_SYSTEM_HPP
     19#define HEADER_TINYGETTEXT_FILE_SYSTEM_HPP
     20
     21#include <vector>
     22#include <memory>
     23#include <iosfwd>
     24#include <string>
     25
     26namespace tinygettext {
     27
     28class FileSystem
     29{
     30public:
     31  virtual ~FileSystem() {}
     32
     33  virtual std::vector<std::string>    open_directory(const std::string& pathname) =0;
     34  virtual std::auto_ptr<std::istream> open_file(const std::string& filename)      =0;
     35};
     36
     37} // namespace tinygettext
     38
     39#endif
     40
     41/* EOF */
     42
  • new file libraries/source/tinygettext/tinygettext/iconv.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/iconv.cpp b/libraries/source/tinygettext/tinygettext/iconv.cpp
    new file mode 100644
    index 0000000..c0b8b60
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include <ctype.h>
     19#include <assert.h>
     20#include <sstream>
     21#include <errno.h>
     22#include <stdexcept>
     23#include <string.h>
     24#include <stdlib.h>
     25
     26#include "iconv.hpp"
     27#include "log_stream.hpp"
     28
     29namespace tinygettext {
     30
     31#ifndef tinygettext_ICONV_CONST
     32#  define tinygettext_ICONV_CONST
     33#endif
     34
     35IConv::IConv()
     36  : to_charset(),
     37    from_charset(),
     38    cd(0)
     39{}
     40 
     41IConv::IConv(const std::string& from_charset_, const std::string& to_charset_)
     42  : to_charset(),
     43    from_charset(),
     44    cd(0)
     45{
     46  set_charsets(from_charset_, to_charset_);
     47}
     48 
     49IConv::~IConv()
     50{
     51  if (cd)
     52    tinygettext_iconv_close(cd);
     53}
     54 
     55void
     56IConv::set_charsets(const std::string& from_charset_, const std::string& to_charset_)
     57{
     58  if (cd)
     59    tinygettext_iconv_close(cd);
     60
     61  from_charset = from_charset_;
     62  to_charset   = to_charset_;
     63
     64  for(std::string::iterator i = to_charset.begin(); i != to_charset.end(); ++i)
     65    *i = static_cast<char>(toupper(*i));
     66
     67  for(std::string::iterator i = from_charset.begin(); i != from_charset.end(); ++i)
     68    *i = static_cast<char>(toupper(*i));
     69
     70  if (to_charset == from_charset)
     71  {
     72    cd = 0;
     73  }
     74  else
     75  {
     76    cd = tinygettext_iconv_open(to_charset.c_str(), from_charset.c_str());
     77    if (cd == reinterpret_cast<tinygettext_iconv_t>(-1))
     78    {
     79      if(errno == EINVAL)
     80      {
     81        std::ostringstream str;
     82        str << "IConv construction failed: conversion from '" << from_charset
     83            << "' to '" << to_charset << "' not available";
     84        throw std::runtime_error(str.str());
     85      }
     86      else
     87      {
     88        std::ostringstream str;
     89        str << "IConv: construction failed: " << strerror(errno);
     90        throw std::runtime_error(str.str());
     91      }
     92    }
     93  }
     94}
     95
     96/// Convert a string from encoding to another.
     97std::string
     98IConv::convert(const std::string& text)
     99{
     100  if (!cd)
     101  {
     102    return text;
     103  }
     104  else
     105  {
     106    size_t inbytesleft  = text.size();
     107    size_t outbytesleft = 4*inbytesleft; // Worst case scenario: ASCII -> UTF-32?
     108
     109    // We try to avoid to much copying around, so we write directly into
     110    // a std::string
     111    tinygettext_ICONV_CONST char* inbuf = const_cast<char*>(&text[0]);
     112    std::string result(outbytesleft, 'X');
     113    char* outbuf = &result[0];
     114 
     115    // Try to convert the text.
     116    size_t ret = tinygettext_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
     117    if (ret == static_cast<size_t>(-1))
     118    {
     119      if (errno == EILSEQ || errno == EINVAL)
     120      { // invalid multibyte sequence
     121        tinygettext_iconv(cd, NULL, NULL, NULL, NULL); // reset state
     122
     123        // FIXME: Could try to skip the invalid byte and continue
     124        log_error << "error: tinygettext:iconv: invalid multibyte sequence in:  \"" << text << "\"" << std::endl;
     125      }
     126      else if (errno == E2BIG)
     127      { // output buffer to small
     128        assert(!"tinygettext/iconv.cpp: E2BIG: This should never be reached");
     129      }
     130      else if (errno == EBADF)
     131      {
     132        assert(!"tinygettext/iconv.cpp: EBADF: This should never be reached");
     133      }
     134      else
     135      {
     136        assert(!"tinygettext/iconv.cpp: <unknown>: This should never be reached");
     137      }
     138    }
     139
     140    result.resize(4*text.size() - outbytesleft);
     141
     142    return result;
     143  }
     144}
     145
     146} // namespace tinygettext
     147
     148/* EOF */
  • new file libraries/source/tinygettext/tinygettext/iconv.hpp

    diff --git a/libraries/source/tinygettext/tinygettext/iconv.hpp b/libraries/source/tinygettext/tinygettext/iconv.hpp
    new file mode 100644
    index 0000000..1ae1750
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software: you can redistribute it and/or modify
     5//  it under the terms of the GNU General Public License as published by
     6//  the Free Software Foundation, either version 3 of the License, or
     7//  (at your option) any later version.
     8// 
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13// 
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16
     17#ifndef HEADER_TINYGETTEXT_ICONV_HPP
     18#define HEADER_TINYGETTEXT_ICONV_HPP
     19
     20#include <string>
     21
     22#ifdef HAVE_SDL
     23#  include "SDL.h"
     24
     25#  define tinygettext_ICONV_CONST const
     26#  define tinygettext_iconv_t     SDL_iconv_t
     27#  define tinygettext_iconv       SDL_iconv
     28#  define tinygettext_iconv_open  SDL_iconv_open
     29#  define tinygettext_iconv_close SDL_iconv_close
     30#else
     31#  include <iconv.h>
     32
     33#  ifdef HAVE_ICONV_CONST
     34#    define tinygettext_ICONV_CONST ICONV_CONST
     35#  else
     36#    define tinygettext_ICONV_CONST
     37#  endif
     38
     39#  define tinygettext_iconv_t     iconv_t
     40#  define tinygettext_iconv       iconv
     41#  define tinygettext_iconv_open  iconv_open
     42#  define tinygettext_iconv_close iconv_close
     43#endif
     44
     45namespace tinygettext {
     46
     47class IConv
     48{
     49private:
     50  std::string to_charset;
     51  std::string from_charset;
     52  tinygettext_iconv_t cd;
     53
     54public:
     55  IConv();
     56  IConv(const std::string& fromcode, const std::string& tocode);
     57  ~IConv();
     58
     59  void set_charsets(const std::string& fromcode, const std::string& tocode);
     60  std::string convert(const std::string& text);
     61
     62private:
     63  IConv (const IConv&);
     64  IConv& operator= (const IConv&);
     65};
     66
     67} // namespace tinygettext
     68
     69#endif
     70
     71/* EOF */
  • new file libraries/source/tinygettext/tinygettext/language.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/language.cpp b/libraries/source/tinygettext/tinygettext/language.cpp
    new file mode 100644
    index 0000000..1b6aac0
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include "language.hpp"
     19
     20#include <map>
     21#include <assert.h>
     22#include <vector>
     23
     24namespace tinygettext {
     25
    026
     27struct LanguageSpec {
     28  /** Language code: "de", "en", ... */
     29  const char* language;
     30
     31  /** Country code: "BR", "DE", ..., can be 0 */
     32  const char* country;
     33
     34  /** Modifier/Varint: "Latn", "ije", "latin"..., can be 0 */
     35  const char* modifier;
     36
     37  /** Language name: "German", "English", "French", ... */
     38  const char* name;
     39};
     40
    141
     42/** Language Definitions */
     43//*{
     44LanguageSpec languages[] = {
     45  { "aa", 0,    0, "Afar"                        },
     46  { "af", 0,    0, "Afrikaans"                   },
     47  { "af", "ZA", 0, "Afrikaans (South Africa)"    },
     48  { "am", 0,    0, "Amharic"                     },
     49  { "ar", 0,    0, "Arabic"                      },
     50  { "ar", "AR", 0, "Arabic (Argentina)"          },
     51  { "ar", "OM", 0, "Arabic (Oman)"               },
     52  { "ar", "SA", 0, "Arabic (Saudi Arabia)"       },
     53  { "ar", "SY", 0, "Arabic (Syrian Arab Republic)" },
     54  { "ar", "TN", 0, "Arabic (Tunisia)"            },
     55  { "as", 0,    0, "Assamese"                    },
     56  { "ast",0,    0, "Asturian"                    },
     57  { "ay", 0,    0, "Aymara"                      },
     58  { "az", 0,    0, "Azerbaijani"                 },
     59  { "az", "IR", 0, "Azerbaijani (Iran)"          },
     60  { "be", 0,    0, "Belarusian"                  },
     61  { "be", 0, "latin", "Belarusian"               },
     62  { "bg", 0,    0, "Bulgarian"                   },
     63  { "bg", "BG", 0, "Bulgarian (Bulgaria)"        },
     64  { "bn", 0,    0, "Bengali"                     },
     65  { "bn", "BD", 0, "Bengali (Bangladesh)"        },
     66  { "bn", "IN", 0, "Bengali (India)"             },
     67  { "bo", 0,    0, "Tibetan"                     },
     68  { "br", 0,    0, "Breton"                      },
     69  { "bs", 0,    0, "Bosnian"                     },
     70  { "bs", "BA", 0, "Bosnian (Bosnia/Herzegovina)"},
     71  { "bs", "BS", 0, "Bosnian (Bahamas)"           },
     72  { "ca", "ES", "valencia", "Catalan (valencia)" },
     73  { "ca", "ES", 0, "Catalan (Spain)"             },
     74  { "ca", 0,    "valencia", "Catalan (valencia)" },
     75  { "ca", 0,    0, "Catalan"                     },
     76  { "co", 0,    0, "Corsican"                    },
     77  { "cs", 0,    0, "Czech"                       },
     78  { "cs", "CZ", 0, "Czech (Czech Republic)"      },
     79  { "cy", 0,    0, "Welsh"                       },
     80  { "cy", "GB", 0, "Welsh (Great Britain)"       },
     81  { "cz", 0,    0, "Unknown language"            },
     82  { "da", 0,    0, "Danish"                      },
     83  { "da", "DK", 0, "Danish (Denmark)"            },
     84  { "de", 0,    0, "German"                      },
     85  { "de", "AT", 0, "German (Austria)"            },
     86  { "de", "CH", 0, "German (Switzerland)"        },
     87  { "de", "DE", 0, "German (Germany)"            },
     88  { "dk", 0,    0, "Unknown language"            },
     89  { "dz", 0,    0, "Dzongkha"                    },
     90  { "el", 0,    0, "Greek"                       },
     91  { "el", "GR", 0, "Greek (Greece)"              },
     92  { "en", 0,    0, "English"                     },
     93  { "en", "AU", 0, "English (Australia)"         },
     94  { "en", "CA", 0, "English (Canada)"            },
     95  { "en", "GB", 0, "English (Great Britain)"     },
     96  { "en", "US", 0, "English (United States)"     },
     97  { "en", "ZA", 0, "English (South Africa)"      },
     98  { "en", 0, "boldquot", "English"               },
     99  { "en", 0, "quot", "English"                   },
     100  { "en", "US", "piglatin", "English"            },
     101  { "eo", 0,    0, "Esperanto"                   },
     102  { "es", 0,    0, "Spanish"                     },
     103  { "es", "AR", 0, "Spanish (Argentina)"         },
     104  { "es", "CL", 0, "Spanish (Chile)"             },
     105  { "es", "CO", 0, "Spanish (Colombia)"          },
     106  { "es", "CR", 0, "Spanish (Costa Rica)"        },
     107  { "es", "DO", 0, "Spanish (Dominican Republic)"},
     108  { "es", "EC", 0, "Spanish (Ecuador)"           },
     109  { "es", "ES", 0, "Spanish (Spain)"             },
     110  { "es", "GT", 0, "Spanish (Guatemala)"         },
     111  { "es", "HN", 0, "Spanish (Honduras)"          },
     112  { "es", "LA", 0, "Spanish (Laos)"              },
     113  { "es", "MX", 0, "Spanish (Mexico)"            },
     114  { "es", "NI", 0, "Spanish (Nicaragua)"         },
     115  { "es", "PA", 0, "Spanish (Panama)"            },
     116  { "es", "PE", 0, "Spanish (Peru)"              },
     117  { "es", "PR", 0, "Spanish (Puerto Rico)"       },
     118  { "es", "SV", 0, "Spanish (El Salvador)"       },
     119  { "es", "UY", 0, "Spanish (Uruguay)"           },
     120  { "es", "VE", 0, "Spanish (Venezuela)"         },
     121  { "et", 0,    0, "Estonian"                    },
     122  { "et", "EE", 0, "Estonian (Estonia)"          },
     123  { "et", "ET", 0, "Estonian (Ethiopia)"         },
     124  { "eu", 0,    0, "Basque"                      },
     125  { "eu", "ES", 0, "Basque (Spain)"              },
     126  { "fa", 0,    0, "Persian"                     },
     127  { "fa", "AF", 0, "Persian (Afghanistan)"       },
     128  { "fa", "IR", 0, "Persian (Iran)"              },
     129  { "fi", 0,    0, "Finnish"                     },
     130  { "fi", "FI", 0, "Finnish (Finland)"           },
     131  { "fo", 0,    0, "Faroese"                     },
     132  { "fo", "FO", 0, "Faeroese (Faroe Islands)"    },
     133  { "fr", 0,    0, "French"                      },
     134  { "fr", "CA", 0, "French (Canada)"             },
     135  { "fr", "CH", 0, "French (Switzerland)"        },
     136  { "fr", "FR", 0, "French (France)"             },
     137  { "fr", "LU", 0, "French (Luxembourg)"         },
     138  { "fy", 0,    0, "Frisian"                     },
     139  { "ga", 0,    0, "Irish"                       },
     140  { "gd", 0,    0, "Gaelic Scots"                },
     141  { "gl", 0,    0, "Galician"                    },
     142  { "gl", "ES", 0, "Galician (Spain)"            },
     143  { "gn", 0,    0, "Guarani"                     },
     144  { "gu", 0,    0, "Gujarati"                    },
     145  { "gv", 0,    0, "Manx"                        },
     146  { "ha", 0,    0, "Hausa"                       },
     147  { "he", 0,    0, "Hebrew"                      },
     148  { "he", "IL", 0, "Hebrew (Israel)"             },
     149  { "hi", 0,    0, "Hindi"                       },
     150  { "hr", 0,    0, "Croatian"                    },
     151  { "hr", "HR", 0, "Croatian (Croatia)"          },
     152  { "hu", 0,    0, "Hungarian"                   },
     153  { "hu", "HU", 0, "Hungarian (Hungary)"         },
     154  { "hy", 0,    0, "Armenian"                    },
     155  { "ia", 0,    0, "Interlingua"                 },
     156  { "id", 0,    0, "Indonesian"                  },
     157  { "id", "ID", 0, "Indonesian (Indonesia)"      },
     158  { "is", 0,    0, "Icelandic"                   },
     159  { "is", "IS", 0, "Icelandic (Iceland)"         },
     160  { "it", 0,    0, "Italian"                     },
     161  { "it", "CH", 0, "Italian (Switzerland)"       },
     162  { "it", "IT", 0, "Italian (Italy)"             },
     163  { "iu", 0,    0, "Inuktitut"                   },
     164  { "ja", 0,    0, "Japanese"                    },
     165  { "ja", "JP", 0, "Japanese (Japan)"            },
     166  { "ka", 0,    0, "Georgian"                    },
     167  { "kk", 0,    0, "Kazakh"                      },
     168  { "kl", 0,    0, "Kalaallisut"                 },
     169  { "km", 0,    0, "Khmer"                       },
     170  { "km", "KH", 0, "Khmer (Cambodia)"            },
     171  { "kn", 0,    0, "Kannada"                     },
     172  { "ko", 0,    0, "Korean"                      },
     173  { "ko", "KR", 0, "Korean (Korea)"              },
     174  { "ku", 0,    0, "Kurdish"                     },
     175  { "kw", 0,    0, "Cornish"                     },
     176  { "ky", 0,    0, "Kirghiz"                     },
     177  { "la", 0,    0, "Latin"                       },
     178  { "lo", 0,    0, "Lao"                         },
     179  { "lt", 0,    0, "Lithuanian"                  },
     180  { "lt", "LT", 0, "Lithuanian (Lithuania)"      },
     181  { "lv", 0,    0, "Latvian"                     },
     182  { "lv", "LV", 0, "Latvian (Latvia)"            },
     183  { "mg", 0,    0, "Malagasy"                    },
     184  { "mi", 0,    0, "Maori"                       },
     185  { "mk", 0,    0, "Macedonian"                  },
     186  { "mk", "MK", 0, "Macedonian (Macedonia)"      },
     187  { "ml", 0,    0, "Malayalam"                   },
     188  { "mn", 0,    0, "Mongolian"                   },
     189  { "mr", 0,    0, "Marathi"                     },
     190  { "ms", 0,    0, "Malay"                       },
     191  { "ms", "MY", 0, "Malay (Malaysia)"            },
     192  { "mt", 0,    0, "Maltese"                     },
     193  { "my", 0,    0, "Burmese"                     },
     194  { "my", "MM", 0, "Burmese (Myanmar)"           },
     195  { "nb", 0,    0, "Norwegian Bokmal"            },
     196  { "nb", "NO", 0, "Norwegian Bokmål (Norway)"   },
     197  { "ne", 0,    0, "Nepali"                      },
     198  { "nl", 0,    0, "Dutch"                       },
     199  { "nl", "BE", 0, "Dutch (Belgium)"             },
     200  { "nl", "NL", 0, "Dutch (Netherlands)"         },
     201  { "nn", 0,    0, "Norwegian Nynorsk"           },
     202  { "nn", "NO", 0, "Norwegian Nynorsk (Norway)"  },
     203  { "no", 0,    0, "Norwegian"                   },
     204  { "no", "NO", 0, "Norwegian (Norway)"          },
     205  { "no", "NY", 0, "Norwegian (NY)"              },
     206  { "nr", 0,    0, "Ndebele, South"              },
     207  { "oc", 0,    0, "Occitan post 1500"           },
     208  { "om", 0,    0, "Oromo"                       },
     209  { "or", 0,    0, "Oriya"                       },
     210  { "pa", 0,    0, "Punjabi"                     },
     211  { "pl", 0,    0, "Polish"                      },
     212  { "pl", "PL", 0, "Polish (Poland)"             },
     213  { "ps", 0,    0, "Pashto"                      },
     214  { "pt", 0,    0, "Portuguese"                  },
     215  { "pt", "BR", 0, "Brazilian"                   },
     216  { "pt", "PT", 0, "Portuguese (Portugal)"       },
     217  { "qu", 0,    0, "Quechua"                     },
     218  { "rm", 0,    0, "Rhaeto-Romance"              },
     219  { "ro", 0,    0, "Romanian"                    },
     220  { "ro", "RO", 0, "Romanian (Romania)"          },
     221  { "ru", 0,    0, "Russian"                     },
     222  { "ru", "RU", 0, "Russian (Russia"             },
     223  { "rw", 0,    0, "Kinyarwanda"                 },
     224  { "sa", 0,    0, "Sanskrit"                    },
     225  { "sd", 0,    0, "Sindhi"                      },
     226  { "se", 0,    0, "Sami"                        },
     227  { "se", "NO", 0, "Sami (Norway)"               },
     228  { "si", 0,    0, "Sinhalese"                   },
     229  { "sk", 0,    0, "Slovak"                      },
     230  { "sk", "SK", 0, "Slovak (Slovakia)"           },
     231  { "sl", 0,    0, "Slovenian"                   },
     232  { "sl", "SI", 0, "Slovenian (Slovenia)"        },
     233  { "sl", "SL", 0, "Slovenian (Sierra Leone)"    },
     234  { "sm", 0,    0, "Samoan"                      },
     235  { "so", 0,    0, "Somali"                      },
     236  { "sp", 0,    0, "Unknown language"            },
     237  { "sq", 0,    0, "Albanian"                    },
     238  { "sq", "AL", 0, "Albanian (Albania)"          },
     239  { "sr", 0,    0, "Serbian"                     },
     240  { "sr", "YU", 0, "Serbian (Yugoslavia)"        },
     241  { "sr", 0,"ije", "Serbian"                     },
     242  { "sr", 0, "latin", "Serbian"                  },
     243  { "sr", 0, "Latn",  "Serbian"                  },
     244  { "ss", 0,    0, "Swati"                       },
     245  { "st", 0,    0, "Sotho"                       },
     246  { "sv", 0,    0, "Swedish"                     },
     247  { "sv", "SE", 0, "Swedish (Sweden)"            },
     248  { "sv", "SV", 0, "Swedish (El Salvador)"       },
     249  { "sw", 0,    0, "Swahili"                     },
     250  { "ta", 0,    0, "Tamil"                       },
     251  { "te", 0,    0, "Telugu"                      },
     252  { "tg", 0,    0, "Tajik"                       },
     253  { "th", 0,    0, "Thai"                        },
     254  { "th", "TH", 0, "Thai (Thailand)"             },
     255  { "ti", 0,    0, "Tigrinya"                    },
     256  { "tk", 0,    0, "Turkmen"                     },
     257  { "tl", 0,    0, "Tagalog"                     },
     258  { "to", 0,    0, "Tonga"                       },
     259  { "tr", 0,    0, "Turkish"                     },
     260  { "tr", "TR", 0, "Turkish (Turkey)"            },
     261  { "ts", 0,    0, "Tsonga"                      },
     262  { "tt", 0,    0, "Tatar"                       },
     263  { "ug", 0,    0, "Uighur"                      },
     264  { "uk", 0,    0, "Ukrainian"                   },
     265  { "uk", "UA", 0, "Ukrainian (Ukraine)"         },
     266  { "ur", 0,    0, "Urdu"                        },
     267  { "ur", "PK", 0, "Urdu (Pakistan)"             },
     268  { "uz", 0,    0, "Uzbek"                       },
     269  { "uz", 0, "cyrillic", "Uzbek"                 },
     270  { "vi", 0,    0, "Vietnamese"                  },
     271  { "vi", "VN", 0, "Vietnamese (Vietnam)"        },
     272  { "wa", 0,    0, "Walloon"                     },
     273  { "wo", 0,    0, "Wolof"                       },
     274  { "xh", 0,    0, "Xhosa"                       },
     275  { "yi", 0,    0, "Yiddish"                     },
     276  { "yo", 0,    0, "Yoruba"                      },
     277  { "zh", 0,    0, "Chinese"                     },
     278  { "zh", "CN", 0, "Chinese (simplified)"        },
     279  { "zh", "HK", 0, "Chinese (Hong Kong)"         },
     280  { "zh", "TW", 0, "Chinese (traditional)"       },
     281  { "zu", 0,    0, "Zulu"                        },
     282  { NULL, 0,    0, NULL                          }
     283};
     284//*}
     285
    2286
     287std::string
     288resolve_language_alias(const std::string& name)
     289{
     290  typedef std::map<std::string, std::string> Aliases;
     291  static Aliases language_aliases;
     292  if (language_aliases.empty())
     293  {
     294    // FIXME: Many of those are not useful for us, since we leave
     295    // encoding to the app, not to the language, we could/should
     296    // also match against all language names, not just aliases from
     297    // locale.alias
     298
     299    // Aliases taken from /etc/locale.alias
     300    language_aliases["bokmal"]           = "nb_NO.ISO-8859-1";
     301    language_aliases["bokmål"]           = "nb_NO.ISO-8859-1";
     302    language_aliases["catalan"]          = "ca_ES.ISO-8859-1";
     303    language_aliases["croatian"]         = "hr_HR.ISO-8859-2";
     304    language_aliases["czech"]            = "cs_CZ.ISO-8859-2";
     305    language_aliases["danish"]           = "da_DK.ISO-8859-1";
     306    language_aliases["dansk"]            = "da_DK.ISO-8859-1";
     307    language_aliases["deutsch"]          = "de_DE.ISO-8859-1";
     308    language_aliases["dutch"]            = "nl_NL.ISO-8859-1";
     309    language_aliases["eesti"]            = "et_EE.ISO-8859-1";
     310    language_aliases["estonian"]         = "et_EE.ISO-8859-1";
     311    language_aliases["finnish"]          = "fi_FI.ISO-8859-1";
     312    language_aliases["français"]         = "fr_FR.ISO-8859-1";
     313    language_aliases["french"]           = "fr_FR.ISO-8859-1";
     314    language_aliases["galego"]           = "gl_ES.ISO-8859-1";
     315    language_aliases["galician"]         = "gl_ES.ISO-8859-1";
     316    language_aliases["german"]           = "de_DE.ISO-8859-1";
     317    language_aliases["greek"]            = "el_GR.ISO-8859-7";
     318    language_aliases["hebrew"]           = "he_IL.ISO-8859-8";
     319    language_aliases["hrvatski"]         = "hr_HR.ISO-8859-2";
     320    language_aliases["hungarian"]        = "hu_HU.ISO-8859-2";
     321    language_aliases["icelandic"]        = "is_IS.ISO-8859-1";
     322    language_aliases["italian"]          = "it_IT.ISO-8859-1";
     323    language_aliases["japanese"]         = "ja_JP.eucJP";
     324    language_aliases["japanese.euc"]     = "ja_JP.eucJP";
     325    language_aliases["ja_JP"]            = "ja_JP.eucJP";
     326    language_aliases["ja_JP.ujis"]       = "ja_JP.eucJP";
     327    language_aliases["japanese.sjis"]    = "ja_JP.SJIS";
     328    language_aliases["korean"]           = "ko_KR.eucKR";
     329    language_aliases["korean.euc"]       = "ko_KR.eucKR";
     330    language_aliases["ko_KR"]            = "ko_KR.eucKR";
     331    language_aliases["lithuanian"]       = "lt_LT.ISO-8859-13";
     332    language_aliases["no_NO"]            = "nb_NO.ISO-8859-1";
     333    language_aliases["no_NO.ISO-8859-1"] = "nb_NO.ISO-8859-1";
     334    language_aliases["norwegian"]        = "nb_NO.ISO-8859-1";
     335    language_aliases["nynorsk"]          = "nn_NO.ISO-8859-1";
     336    language_aliases["polish"]           = "pl_PL.ISO-8859-2";
     337    language_aliases["portuguese"]       = "pt_PT.ISO-8859-1";
     338    language_aliases["romanian"]         = "ro_RO.ISO-8859-2";
     339    language_aliases["russian"]          = "ru_RU.ISO-8859-5";
     340    language_aliases["slovak"]           = "sk_SK.ISO-8859-2";
     341    language_aliases["slovene"]          = "sl_SI.ISO-8859-2";
     342    language_aliases["slovenian"]        = "sl_SI.ISO-8859-2";
     343    language_aliases["spanish"]          = "es_ES.ISO-8859-1";
     344    language_aliases["swedish"]          = "sv_SE.ISO-8859-1";
     345    language_aliases["thai"]             = "th_TH.TIS-620";
     346    language_aliases["turkish"]          = "tr_TR.ISO-8859-9";
     347  }
     348
     349  std::string name_lowercase;
     350  name_lowercase.resize(name.size());
     351  for(std::string::size_type i = 0; i < name.size(); ++i)
     352    name_lowercase[i] = static_cast<char>(tolower(name[i]));
     353
     354  Aliases::iterator i = language_aliases.find(name_lowercase);
     355  if (i != language_aliases.end())
     356  {
     357    return i->second;
     358  }
     359  else
     360  {
     361    return name;
     362  }
     363}
     364
    3365
     366Language
     367Language::from_spec(const std::string& language, const std::string& country, const std::string& modifier)
     368{
     369  static std::map<std::string, std::vector<LanguageSpec*> > language_map;
     370
     371  if (language_map.empty())
     372  { // Init language_map
     373    for(int i = 0; languages[i].language != NULL; ++i)
     374      language_map[languages[i].language].push_back(&languages[i]);
     375  }
     376 
     377  std::map<std::string, std::vector<LanguageSpec*> >::iterator i = language_map.find(language);
     378  if (i != language_map.end())
     379  {
     380    std::vector<LanguageSpec*>& lst = i->second;
     381
     382    LanguageSpec tmpspec;
     383    tmpspec.language = language.c_str();
     384    tmpspec.country  = country.c_str();
     385    tmpspec.modifier = modifier.c_str();
     386    Language tmplang(&tmpspec);
     387     
     388    LanguageSpec* best_match = 0;
     389    int best_match_score = 0;
     390    for(std::vector<LanguageSpec*>::iterator j = lst.begin(); j != lst.end(); ++j)
     391    { // Search for the language that best matches the given spec, value country more then modifier
     392      int score = Language::match(Language(*j), tmplang);
     393
     394      if (score > best_match_score)
     395      {
     396        best_match = *j;
     397        best_match_score = score;
     398      }
     399    }
     400    assert(best_match);
     401    return Language(best_match);
     402  }
     403  else
     404  {
     405    return Language();
     406  }
     407}
     408
     409Language
     410Language::from_name(const std::string& spec_str)
     411{
     412  return from_env(resolve_language_alias(spec_str));
     413}
     414
     415Language
     416Language::from_env(const std::string& env)
     417{
     418  // Split LANGUAGE_COUNTRY.CODESET@MODIFIER into parts
     419  std::string::size_type ln = env.find('_');
     420  std::string::size_type dt = env.find('.');
     421  std::string::size_type at = env.find('@');
     422
     423  std::string language;
     424  std::string country;
     425  std::string codeset;
     426  std::string modifier;
     427
     428  //std::cout << ln << " " << dt << " " << at << std::endl;
     429
     430  language = env.substr(0, std::min(std::min(ln, dt), at));
     431
     432  if (ln != std::string::npos && ln+1 < env.size()) // _
     433  {
     434    country = env.substr(ln+1, (std::min(dt, at) == std::string::npos) ? std::string::npos : std::min(dt, at) - (ln+1));
     435  }
     436
     437  if (dt != std::string::npos && dt+1 < env.size()) // .
     438  {
     439    codeset = env.substr(dt+1, (at == std::string::npos) ? std::string::npos : (at - (dt+1)));
     440  }
     441
     442  if (at != std::string::npos && at+1 < env.size()) // @
     443  {
     444    modifier = env.substr(at+1);
     445  }
     446
     447  return from_spec(language, country, modifier);
     448}
     449
    4450
     451Language::Language(LanguageSpec* language_spec_)
     452  : language_spec(language_spec_)
     453{
     454}
     455
     456Language::Language()
     457  : language_spec(0)
     458{
     459}
     460
     461int
     462Language::match(const Language& lhs, const Language& rhs)
     463{
     464  if (lhs.get_language() != rhs.get_language())
     465  {
     466    return 0;
     467  }
     468  else
     469  {
     470    static int match_tbl[3][3] = {
     471      // modifier match, wildchard, miss
     472      { 9, 8, 5 }, // country match
     473      { 7, 6, 3 }, // country wildcard
     474      { 4, 2, 1 }, // country miss
     475    };
     476 
     477    int c;
     478    if (lhs.get_country() == rhs.get_country())
     479      c = 0;
     480    else if (lhs.get_country().empty() || rhs.get_country().empty())
     481      c = 1;
     482    else
     483      c = 2;
     484 
     485    int m;
     486    if (lhs.get_modifier() == rhs.get_modifier())
     487      m = 0;
     488    else if (lhs.get_modifier().empty() || rhs.get_modifier().empty())
     489      m = 1;
     490    else
     491      m = 2;
     492
     493    return match_tbl[c][m];
     494  }
     495}
     496
     497std::string
     498Language::get_language() const
     499{
     500  if (language_spec)
     501    return language_spec->language;
     502  else
     503    return "";
     504}
     505
     506std::string
     507Language::get_country()  const
     508{
     509  if (language_spec && language_spec->country)
     510    return language_spec->country;
     511  else
     512    return "";
     513}
     514
     515std::string
     516Language::get_modifier() const
     517{
     518  if (language_spec && language_spec->modifier)
     519    return language_spec->modifier;
     520  else
     521    return "";
     522}
     523
     524std::string
     525Language::get_name()  const
     526{
     527  if (language_spec)
     528    return language_spec->name;
     529  else
     530    return "";
     531}
     532
     533std::string
     534Language::str() const
     535{
     536  if (language_spec)
     537  {
     538    std::string var;
     539    var += language_spec->language;
     540    if (language_spec->country)
     541    {
     542      var += "_";
     543      var += language_spec->country;
     544    }
     545
     546    if (language_spec->modifier)
     547    {
     548      var += "@";
     549      var += language_spec->modifier;
     550    }
     551    return var;
     552  }
     553  else
     554  {
     555    return "";
     556  }
     557}
     558
     559bool
     560Language::operator==(const Language& rhs) const
     561{
     562  return language_spec == rhs.language_spec;
     563}
     564
     565bool
     566Language::operator!=(const Language& rhs) const
     567{
     568  return language_spec != rhs.language_spec;
  • new file libraries/source/tinygettext/tinygettext/language.hpp

    +}
    +
    
    +} // namespace tinygettext
    +
    +/* EOF */
    diff --git a/libraries/source/tinygettext/tinygettext/language.hpp b/libraries/source/tinygettext/tinygettext/language.hpp
    new file mode 100644
    index 0000000..b997a13
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_LANGUAGE_HPP
     19#define HEADER_TINYGETTEXT_LANGUAGE_HPP
     20
     21#include <string>
     22
     23namespace tinygettext {
     24
     25struct LanguageSpec;
     26
     27/** Lightweight wrapper around LanguageSpec */
     28class Language
     29{
     30private:
     31  LanguageSpec* language_spec;
     32
     33  Language(LanguageSpec* language_spec);
     34
     35public:
     36  /** Create a language from language and country code:
     37      Example: Languge("de", "DE"); */
     38  static Language from_spec(const std::string& language,
     39                            const std::string& country = std::string(),
     40                            const std::string& modifier = std::string());
     41
     42  /** Create a language from language and country code:
     43      Example: Languge("deutsch");
     44      Example: Languge("de_DE"); */
     45  static Language from_name(const std::string& str);
     46
     47  /** Create a language from an environment variable style string (e.g de_DE.UTF-8@modifier) */
     48  static Language from_env(const std::string& env);
     49
     50  /** Compares two Languages, returns 0 on missmatch and a score
     51      between 1 and 9 on match, the higher the score the better the
     52      match */
     53  static int match(const Language& lhs, const Language& rhs);
     54
     55  /** Create an undefined Language object */
     56  Language();
     57 
     58  operator bool() const { return language_spec; }
     59
     60  /** Returns the language code (i.e. de, en, fr) */
     61  std::string get_language() const;
     62
     63  /** Returns the country code (i.e. DE, AT, US) */
     64  std::string get_country()  const;
     65
     66  /** Returns the modifier of the language (i.e. latn or Latn for
     67      Serbian with non-cyrilic characters) */
     68  std::string get_modifier()  const;
     69
     70  /** Returns the human readable name of the Language */
     71  std::string get_name() const;
     72
     73  /** Returns the Language as string in the form of an environment
     74      variable: {language}_{country}@{modifier} */
     75  std::string str() const;
     76
     77  bool operator==(const Language& rhs) const;
     78  bool operator!=(const Language& rhs) const;
     79
     80  friend bool operator<(const Language& lhs, const Language& rhs);
     81};
     82
     83inline bool operator<(const Language& lhs, const Language& rhs) {
     84  return lhs.language_spec < rhs.language_spec;
     85}
     86
     87} // namespace tinygettext
     88
     89#endif
     90
     91/* EOF */
  • new file libraries/source/tinygettext/tinygettext/log.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/log.cpp b/libraries/source/tinygettext/tinygettext/log.cpp
    new file mode 100644
    index 0000000..55c89df
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include <iostream>
     19#include "log.hpp"
     20
     21namespace tinygettext {
     22
    023
     24Log::log_callback_t Log::log_info_callback    = &Log::default_log_callback;
     25Log::log_callback_t Log::log_warning_callback = &Log::default_log_callback;
     26Log::log_callback_t Log::log_error_callback   = &Log::default_log_callback;
     27
    128
     29void
     30Log::default_log_callback(const std::string& str)
     31{
     32//   std::cerr << "tinygettext: " << str;
     33}
     34
     35void
     36Log::set_log_info_callback(log_callback_t callback)
     37{
     38  log_info_callback = callback;
     39}
     40
     41void
     42Log::set_log_warning_callback(log_callback_t callback)
     43{
     44  log_warning_callback = callback;
     45}
     46
     47void
     48Log::set_log_error_callback(log_callback_t callback)
     49{
     50  log_error_callback = callback;
     51}
     52
    253
     54Log::Log(log_callback_t callback_) :
     55  callback(callback_),
     56  out()
     57{
     58}
     59
     60Log::~Log()
     61{
     62  callback(out.str());
     63}
     64
     65std::ostream&
     66Log::get()
     67{
     68  return out;
     69}
     70
  • new file libraries/source/tinygettext/tinygettext/log.hpp

    +} // namespace tinygettext
    +
    +/* EOF */
    diff --git a/libraries/source/tinygettext/tinygettext/log.hpp b/libraries/source/tinygettext/tinygettext/log.hpp
    new file mode 100644
    index 0000000..a8eadb4
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_LOG_HPP
     19#define HEADER_TINYGETTEXT_LOG_HPP
     20
     21#include <sstream>
     22
     23namespace tinygettext {
     24
     25class Log
     26{
     27public:
     28  typedef void (*log_callback_t)(const std::string&);
     29
     30  static log_callback_t log_info_callback;
     31  static log_callback_t log_warning_callback;
     32  static log_callback_t log_error_callback;
     33
     34
     35  static void default_log_callback(const std::string& str);
     36
     37  static void set_log_info_callback(log_callback_t callback);
     38  static void set_log_warning_callback(log_callback_t callback);
     39  static void set_log_error_callback(log_callback_t callback);
     40
     41private:
     42  log_callback_t callback;
     43  std::ostringstream out;
     44
     45public:
     46  Log(log_callback_t callback);
     47  ~Log();
     48 
     49  std::ostream& get();
     50};
     51
     52} // namespace tinygettext
     53
     54#endif
     55
     56/* EOF */
  • new file libraries/source/tinygettext/tinygettext/log_stream.hpp

    diff --git a/libraries/source/tinygettext/tinygettext/log_stream.hpp b/libraries/source/tinygettext/tinygettext/log_stream.hpp
    new file mode 100644
    index 0000000..8ad4c02
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_LOG_STREAM_HPP
     19#define HEADER_TINYGETTEXT_LOG_STREAM_HPP
     20
     21#include "log.hpp"
     22
     23namespace tinygettext {
     24
     25// FIXME: very bad to have such things in the API
     26#define log_error   if (!Log::log_error_callback);   else (Log(Log::log_error_callback)).get()
     27#define log_warning if (!Log::log_warning_callback); else (Log(Log::log_warning_callback)).get()
     28#define log_info    if (!Log::log_info_callback);    else (Log(Log::log_warning_callback)).get()
     29
     30} // namespace tinygettext
     31
     32#endif
     33
     34/* EOF */
  • new file libraries/source/tinygettext/tinygettext/plural_forms.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/plural_forms.cpp b/libraries/source/tinygettext/tinygettext/plural_forms.cpp
    new file mode 100644
    index 0000000..8271437
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include "plural_forms.hpp"
     19
     20#include <map>
     21
     22namespace tinygettext {
     23
    024
     25/**
     26 *  Plural functions are used to select a string that matches a given
     27 *  count. \a n is the count and the return value is the string index
     28 *  used in the .po file, for example:
     29 *   
     30 *   msgstr[0] = "You got %d error";
     31 *   msgstr[1] = "You got %d errors";       
     32 *          ^-- return value of plural function
     33 */
     34unsigned int plural1(int )     { return 0; }
     35unsigned int plural2_1(int n)  { return (n != 1); }
     36unsigned int plural2_2(int n)  { return (n > 1); }
     37unsigned int plural2_mk(int n) { return n==1 || n%10==1 ? 0 : 1; }
     38unsigned int plural3_lv(int n) { return static_cast<unsigned int>(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2); }
     39unsigned int plural3_ga(int n) { return static_cast<unsigned int>(n==1 ? 0 : n==2 ? 1 : 2); }
     40unsigned int plural3_lt(int n) { return static_cast<unsigned int>(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); }
     41unsigned int plural3_1(int n)  { return static_cast<unsigned int>(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
     42unsigned int plural3_sk(int n) { return static_cast<unsigned int>( (n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2 ); }
     43unsigned int plural3_pl(int n) { return static_cast<unsigned int>(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
     44unsigned int plural3_sl(int n) { return static_cast<unsigned int>(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3); }
     45unsigned int plural4_ar(int n) { return static_cast<unsigned int>( n==1 ? 0 : n==2 ? 1 : n>=3 && n<=10 ? 2 : 3 ); }
     46
    147
     48PluralForms
     49PluralForms::from_string(const std::string& str)
     50{
     51  static std::map<std::string, struct PluralForms> plural_forms;
     52   
     53  if (plural_forms.empty())
     54  {
     55    // Note that the plural forms here shouldn't contain any spaces
     56    plural_forms["Plural-Forms:nplurals=1;plural=0;"] = PluralForms(1, plural1);
     57    plural_forms["Plural-Forms:nplurals=2;plural=(n!=1);"] = PluralForms(2, plural2_1);
     58    plural_forms["Plural-Forms:nplurals=2;plural=n!=1;"] = PluralForms(2, plural2_1);
     59    plural_forms["Plural-Forms:nplurals=2;plural=(n>1);"] = PluralForms(2, plural2_2);
     60    plural_forms["Plural-Forms:nplurals=2;plural=n==1||n%10==1?0:1;"] = PluralForms(2, plural2_mk);
     61    plural_forms["Plural-Forms:nplurals=3;plural=n%10==1&&n%100!=11?0:n!=0?1:2);"] = PluralForms(2, plural3_lv);
     62    plural_forms["Plural-Forms:nplurals=3;plural=n==1?0:n==2?1:2;"] = PluralForms(3, plural3_ga);
     63    plural_forms["Plural-Forms:nplurals=3;plural=(n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?1:2);"] = PluralForms(3, plural3_lt);
     64    plural_forms["Plural-Forms:nplurals=3;plural=(n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2);"] = PluralForms(3, plural3_1);
     65    plural_forms["Plural-Forms:nplurals=3;plural=(n==1)?0:(n>=2&&n<=4)?1:2;"] = PluralForms(3, plural3_sk);
     66    plural_forms["Plural-Forms:nplurals=3;plural=(n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2);"] = PluralForms(3, plural3_pl);
     67    plural_forms["Plural-Forms:nplurals=3;plural=(n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3);"] = PluralForms(3, plural3_sl);
     68
     69    plural_forms["Plural-Forms:nplurals=4;plural=n==1?0:n==2?1:n>=3&&n<=10?2:3;"]=PluralForms(4, plural4_ar);
     70  }
     71 
     72  // Remove spaces from string before lookup
     73  std::string space_less_str;
     74  for(std::string::size_type i = 0; i < str.size(); ++i)
     75    if (!isspace(str[i]))
     76      space_less_str += str[i];
     77 
     78  std::map<std::string, struct PluralForms>::const_iterator it= plural_forms.find(space_less_str);
     79  if (it != plural_forms.end())
     80  {
     81    return it->second;
     82  }
     83  else
     84  {
     85    return PluralForms();
     86  }
     87}
     88
     89} // namespace tinygettext
  • new file libraries/source/tinygettext/tinygettext/plural_forms.hpp

    +
    +/* EOF */
    diff --git a/libraries/source/tinygettext/tinygettext/plural_forms.hpp b/libraries/source/tinygettext/tinygettext/plural_forms.hpp
    new file mode 100644
    index 0000000..0b06449
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_PLURAL_FORMS_HPP
     19#define HEADER_TINYGETTEXT_PLURAL_FORMS_HPP
     20
     21#include <string>
     22
     23namespace tinygettext {
     24
     25typedef unsigned int (*PluralFunc)(int n);
     26
     27class PluralForms
     28{
     29private:
     30  unsigned int nplural;
     31  PluralFunc   plural;
     32
     33public:
     34  static PluralForms from_string(const std::string& str);
     35
     36  PluralForms()
     37    : nplural(0),
     38      plural(0)
     39  {}
     40
     41  PluralForms(unsigned int nplural_, PluralFunc plural_)
     42    : nplural(nplural_),
     43      plural(plural_)
     44  {}
     45
     46  unsigned int get_nplural() const { return nplural; }
     47  unsigned int get_plural(int n) const { if (plural) return plural(n); else return 0; }
     48
     49  bool operator==(const PluralForms& other) { return nplural == other.nplural && plural == other.plural; }
     50  bool operator!=(const PluralForms& other) { return !(*this == other); }
     51
     52  operator bool() const {
     53    return plural;
     54  }
     55};
     56
     57} // namespace tinygettext
     58
     59#endif
     60
     61/* EOF */
  • new file libraries/source/tinygettext/tinygettext/po_parser.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/po_parser.cpp b/libraries/source/tinygettext/tinygettext/po_parser.cpp
    new file mode 100644
    index 0000000..ec73eed
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include "po_parser.hpp"
     19
     20#include <iostream>
     21#include <ctype.h>
     22#include <string>
     23#include <istream>
     24#include <string.h>
     25#include <map>
     26#include <stdlib.h>
     27
     28#include "language.hpp"
     29#include "log_stream.hpp"
     30#include "iconv.hpp"
     31#include "dictionary.hpp"
     32#include "plural_forms.hpp"
     33
     34namespace tinygettext {
     35
     36bool POParser::pedantic = true;
     37
     38void
     39POParser::parse(const std::string& filename, std::istream& in, Dictionary& dict, bool use_fuzzy_)
     40{
     41  POParser parser(filename, in, dict, use_fuzzy_);
     42  parser.parse();
     43}
     44
    045
     46class POParserError {};
     47
     48POParser::POParser(const std::string& filename_, std::istream& in_, Dictionary& dict_, bool use_fuzzy_) :
     49  filename(filename_),
     50  in(in_),
     51  dict(dict_),
     52  use_fuzzy(use_fuzzy_),
     53  running(false),
     54  eof(false),
     55  big5(false),
     56  line_number(0),
     57  current_line(),
     58  conv()
     59{
     60}
     61
     62POParser::~POParser()
     63{
     64}
     65
     66void
     67POParser::warning(const std::string& msg)
     68{
     69  log_warning << filename << ":" << line_number << ": warning: " << msg << ": " << current_line << std::endl;
     70  //log_warning << "Line: " << current_line << std::endl;
     71}
     72
     73void
     74POParser::error(const std::string& msg)
     75{
     76  log_error << filename << ":" << line_number << ": error: " << msg  << ": " << current_line << std::endl;
     77
     78  // Try to recover from an error by searching for start of another entry
     79  do
     80    next_line();
     81  while(!eof && !is_empty_line());
     82
     83  throw POParserError();
     84}
     85
     86void
     87POParser::next_line()
     88{
     89  line_number += 1;
     90  if (!std::getline(in, current_line))
     91    eof = true;
     92}
     93
     94void
     95POParser::get_string_line(std::ostringstream& out,std::string::size_type skip)
     96{
     97  if (skip+1 >= static_cast<std::string::size_type>(current_line.size()))
     98    error("unexpected end of line");
     99
     100  if (current_line[skip] != '"')
     101    error("expected start of string '\"'");
     102 
     103  std::string::size_type i;
     104  for(i = skip+1; current_line[i] != '\"'; ++i)
     105  {
     106    if (big5 && static_cast<unsigned char>(current_line[i]) >= 0x81 && static_cast<unsigned char>(current_line[i]) <= 0xfe)
     107    {
     108      out << current_line[i];
     109
     110      i += 1;
     111         
     112      if (i >= current_line.size())
     113        error("invalid big5 encoding");
     114         
     115      out << current_line[i];
     116    }
     117    else if (i >= current_line.size())
     118    {
     119      error("unexpected end of string");
     120    }
     121    else if (current_line[i] == '\\')
     122    {
     123      i += 1;
     124
     125      if (i >= current_line.size())
     126        error("unexpected end of string in handling '\\'");
     127
     128      switch (current_line[i])
     129      {
     130        case 'a':  out << '\a'; break;
     131        case 'b':  out << '\b'; break;
     132        case 'v':  out << '\v'; break;
     133        case 'n':  out << '\n'; break;
     134        case 't':  out << '\t'; break;
     135        case 'r':  out << '\r'; break;
     136        case '"':  out << '"'; break;
     137        case '\\': out << '\\'; break;
     138        default:
     139          std::ostringstream err;
     140          err << "unhandled escape '\\" << current_line[i] << "'";
     141          warning(err.str());
     142
     143          out << current_line[i-1] << current_line[i];
     144          break;
     145      }
     146    }
     147    else
     148    {
     149      out << current_line[i];
     150    }
     151  }
     152
     153  // process trailing garbage in line and warn if there is any
     154  for(i = i+1; i < current_line.size(); ++i)
     155    if (!isspace(current_line[i]))
     156    {
     157      warning("unexpected garbage after string ignoren");
     158      break;
     159    }
     160}
     161
     162std::string
     163POParser::get_string(std::string::size_type skip)
     164{
     165  std::ostringstream out;
     166
     167  if (skip+1 >= static_cast<std::string::size_type>(current_line.size()))
     168    error("unexpected end of line");
     169
     170  if (current_line[skip] == ' ' && current_line[skip+1] == '"')
     171  {
     172    get_string_line(out, skip+1);
     173  }
     174  else
     175  {
     176    if (pedantic)
     177      warning("keyword and string must be seperated by a single space");
     178
     179    for(;;)
     180    {
     181      if (skip >= static_cast<std::string::size_type>(current_line.size()))
     182        error("unexpected end of line");
     183      else if (current_line[skip] == '\"')
     184      {
     185        get_string_line(out, skip);
     186        break;
     187      }
     188      else if (!isspace(current_line[skip]))
     189      {
     190        error("string must start with '\"'");
     191      }
     192      else
     193      {
     194        // skip space
     195      }
     196
     197      skip += 1;
     198    }
     199  }
     200 
     201next:
     202  next_line();
     203  for(std::string::size_type i = 0; i < current_line.size(); ++i)
     204  {
     205    if (current_line[i] == '"')
     206    {
     207      if (i == 1)
     208        if (pedantic)
     209          warning("leading whitespace before string");
     210
     211      get_string_line(out, i);
     212      goto next;
     213    }
     214    else if (isspace(current_line[i]))
     215    {
     216      // skip
     217    }
     218    else
     219    {
     220      break;
     221    }
     222  }
     223
     224  return out.str();
     225}
     226
     227static bool has_prefix(const std::string& lhs, const std::string rhs)
     228{
     229  if (lhs.length() < rhs.length())
     230    return false;
     231  else
     232    return lhs.compare(0, rhs.length(), rhs) == 0;
     233}
     234
     235void
     236POParser::parse_header(const std::string& header)
     237{
     238  std::string from_charset;
     239  std::string::size_type start = 0;
     240  for(std::string::size_type i = 0; i < header.length(); ++i)
     241  {
     242    if (header[i] == '\n')
     243    {
     244      std::string line = header.substr(start, i - start);
     245
     246      if (has_prefix(line, "Content-Type:"))
     247      {
     248        // from_charset = line.substr(len);
     249        std::string::size_type len = strlen("Content-Type: text/plain; charset=");
     250        if (line.compare(0, len, "Content-Type: text/plain; charset=") == 0)
     251        {
     252          from_charset = line.substr(len);
     253
     254          for(std::string::iterator ch = from_charset.begin(); ch != from_charset.end(); ++ch)
     255            *ch = static_cast<char>(toupper(*ch));
     256        }
     257        else
     258        {
     259          warning("malformed Content-Type header");
     260        }
     261      }
     262      else if (has_prefix(line, "Plural-Forms:"))
     263      {
     264        PluralForms plural_forms = PluralForms::from_string(line);
     265        if (!plural_forms)
     266        {
     267          warning("unknown Plural-Forms given");
     268        }
     269        else
     270        {
     271          if (!dict.get_plural_forms())
     272          {
     273            dict.set_plural_forms(plural_forms);
     274          }
     275          else
     276          {
     277            if (dict.get_plural_forms() != plural_forms)
     278            {
     279              warning("Plural-Forms missmatch between .po file and dictionary");
     280            }
     281          }
     282        }
     283      }
     284      start = i+1;
     285    }
     286  }
     287
     288  if (from_charset.empty() || from_charset == "CHARSET")
     289  {
     290    warning("charset not specified for .po, fallback to utf-8");
     291    from_charset = "UTF-8";
     292  }
     293  else if (from_charset == "BIG5")
     294  {
     295    big5 = true;
     296  }
     297
     298  conv.set_charsets(from_charset, dict.get_charset());
     299}
     300
     301bool
     302POParser::is_empty_line()
     303{
     304  if (current_line.empty())
     305  {
     306    return true;
     307  }
     308  else if (current_line[0] == '#')
     309  { // handle comments as empty lines
     310    if (current_line.size() == 1 || (current_line.size() >= 2 && isspace(current_line[1])))
     311      return true;
     312    else
     313      return false;
     314  }
     315  else
     316  {
     317    for(std::string::iterator i = current_line.begin(); i != current_line.end(); ++i)
     318    {
     319      if (!isspace(*i))
     320        return false;
     321    }
     322  }
     323  return true;
     324}
     325
     326bool
     327POParser::prefix(const char* prefix_str)
     328{
     329  return current_line.compare(0, strlen(prefix_str), prefix_str) == 0;
     330}
     331
     332void
     333POParser::parse()
     334{
     335  next_line();
     336
     337  // skip UTF-8 intro that some text editors produce
     338  // see http://en.wikipedia.org/wiki/Byte-order_mark
     339  if (current_line.size() >= 3 &&
     340      current_line[0] == static_cast<char>(0xef) &&
     341      current_line[1] == static_cast<char>(0xbb) &&
     342      current_line[2] == static_cast<char>(0xbf))
     343  {
     344    current_line = current_line.substr(3);
     345  }
     346
     347  // Parser structure
     348  while(!eof)
     349  {
     350    try
     351    {
     352      bool fuzzy =  false;
     353      bool has_msgctxt = false;
     354      std::string msgctxt;
     355      std::string msgid;
     356
     357      while(prefix("#"))
     358      {
     359        if (current_line.size() >= 2 && current_line[1] == ',')
     360        {
     361          // FIXME: Rather simplistic hunt for fuzzy flag
     362          if (current_line.find("fuzzy", 2) != std::string::npos)
     363            fuzzy = true;
     364        }
     365
     366        next_line();
     367      }
     368
     369      if (!is_empty_line())
     370      {
     371        if (prefix("msgctxt"))
     372        {
     373          has_msgctxt = true;
     374          msgctxt = get_string(7);
     375        }
     376
     377        if (prefix("msgid"))
     378          msgid = get_string(5);
     379        else
     380          error("expected 'msgid'");
     381
     382        if (prefix("msgid_plural"))
     383        {
     384          std::string msgid_plural = get_string(12);
     385          std::vector<std::string> msgstr_num;
     386      bool saw_nonempty_msgstr = false;
     387
     388        next:
     389          if (is_empty_line())
     390          {
     391            if (msgstr_num.empty())
     392              error("expected 'msgstr[N] (0 <= N <= 9)'");
     393          }
     394          else if (prefix("msgstr[") &&
     395                   current_line.size() > 8 &&
     396                   isdigit(current_line[7]) && current_line[8] == ']')
     397          {
     398            std::string::size_type number = static_cast<std::string::size_type>(current_line[7] - '0');
     399        std::string msgstr = get_string(9);
     400
     401        if(!msgstr.empty())
     402          saw_nonempty_msgstr = true;
     403
     404            if (number >= msgstr_num.size())
     405              msgstr_num.resize(number+1);
     406
     407            msgstr_num[number] = conv.convert(msgstr);
     408            goto next;
     409          }
     410          else
     411          {
     412            error("expected 'msgstr[N]'");
     413          }
     414
     415          if (!is_empty_line())
     416            error("expected 'msgstr[N]' or empty line");
     417
     418      if (saw_nonempty_msgstr)
     419      {
     420        if (use_fuzzy || !fuzzy)
     421            {
     422          if (!dict.get_plural_forms())
     423          {
     424        warning("msgstr[N] seen, but no Plural-Forms given");
     425          }
     426          else
     427          {
     428        if (msgstr_num.size() != dict.get_plural_forms().get_nplural())
     429        {
     430          warning("msgstr[N] count doesn't match Plural-Forms.nplural");
     431        }
     432          }
     433
     434          if (has_msgctxt)
     435        dict.add_translation(msgctxt, msgid, msgid_plural, msgstr_num);
     436          else
     437        dict.add_translation(msgid, msgid_plural, msgstr_num);
     438        }
     439
     440        if (0)
     441        {
     442          std::cout << (fuzzy?"fuzzy":"not-fuzzy") << std::endl;
     443          std::cout << "msgid \"" << msgid << "\"" << std::endl;
     444          std::cout << "msgid_plural \"" << msgid_plural << "\"" << std::endl;
     445          for(std::vector<std::string>::size_type i = 0; i < msgstr_num.size(); ++i)
     446        std::cout << "msgstr[" << i << "] \"" << conv.convert(msgstr_num[i]) << "\"" << std::endl;
     447          std::cout << std::endl;
     448        }
     449      }
     450        }
     451        else if (prefix("msgstr"))
     452        {
     453          std::string msgstr = get_string(6);
     454
     455          if (msgid.empty())
     456          {
     457            parse_header(msgstr);
     458          }
     459          else if(!msgstr.empty())
     460          {
     461            if (use_fuzzy || !fuzzy)
     462            {
     463              if (has_msgctxt)
     464                dict.add_translation(msgctxt, msgid, conv.convert(msgstr));
     465              else
     466                dict.add_translation(msgid, conv.convert(msgstr));
     467            }
     468
     469            if (0)
     470            {
     471              std::cout << (fuzzy?"fuzzy":"not-fuzzy") << std::endl;
     472              std::cout << "msgid \"" << msgid << "\"" << std::endl;
     473              std::cout << "msgstr \"" << conv.convert(msgstr) << "\"" << std::endl;
     474              std::cout << std::endl;
     475            }
     476          }
     477        }
     478        else
     479        {
     480          error("expected 'msgstr' or 'msgid_plural'");
     481        }
     482      }
     483     
     484      if (!is_empty_line())
     485        error("expected empty line");
     486
     487      next_line();
     488    }
     489    catch(POParserError&)
     490    {         
     491    }
     492  }
     493}
     494
     495} // namespace tinygettext
     496
  • new file libraries/source/tinygettext/tinygettext/po_parser.hpp

    +/* EOF */
    diff --git a/libraries/source/tinygettext/tinygettext/po_parser.hpp b/libraries/source/tinygettext/tinygettext/po_parser.hpp
    new file mode 100644
    index 0000000..841382c
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_PO_PARSER_HPP
     19#define HEADER_TINYGETTEXT_PO_PARSER_HPP
     20
     21#include <iosfwd>
     22
     23#include "iconv.hpp"
     24
     25namespace tinygettext {
     26
     27class Dictionary;
     28
     29class POParser
     30{
     31private:
     32  std::string filename;
     33  std::istream& in;
     34  Dictionary& dict;
     35  bool  use_fuzzy;
     36
     37  bool running;
     38  bool eof;
     39  bool big5;
     40
     41  int line_number;
     42  std::string current_line;
     43
     44  IConv conv;
     45 
     46  POParser(const std::string& filename, std::istream& in_, Dictionary& dict_, bool use_fuzzy = true);
     47  ~POParser();
     48
     49  void parse_header(const std::string& header);
     50  void parse();
     51  void next_line();
     52  std::string get_string(std::string::size_type skip);
     53  void get_string_line(std::ostringstream& str,std::string::size_type skip);
     54  bool is_empty_line();
     55  bool prefix(const char* );
     56  void error(const std::string& msg) __attribute__((__noreturn__));
     57  void warning(const std::string& msg);
     58
     59public:
     60  /** @param filename name of the istream, only used in error messages
     61      @param in stream from which the PO file is read.
     62      @param dict dictionary to which the strings are written */
     63  static void parse(const std::string& filename, std::istream& in, Dictionary& dict, bool use_fuzzy = true);
     64  static bool pedantic;
     65
     66private:
     67  POParser (const POParser&);
     68  POParser& operator= (const POParser&);
     69};
     70
     71} // namespace tinygettext
     72
     73#endif
     74
     75/* EOF */
  • new file libraries/source/tinygettext/tinygettext/tinygettext.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/tinygettext.cpp b/libraries/source/tinygettext/tinygettext/tinygettext.cpp
    new file mode 100644
    index 0000000..7f5adc8
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18namespace tinygettext {
     19
     20} // namespace tinygettext
     21
     22/* EOF */
  • new file libraries/source/tinygettext/tinygettext/tinygettext.hpp

    diff --git a/libraries/source/tinygettext/tinygettext/tinygettext.hpp b/libraries/source/tinygettext/tinygettext/tinygettext.hpp
    new file mode 100644
    index 0000000..19fd4c6
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2006 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_TINYGETTEXT_HPP
     19#define HEADER_TINYGETTEXT_TINYGETTEXT_HPP
     20
     21#include "dictionary.hpp"
     22#include "dictionary_manager.hpp"
     23#include "language.hpp"
     24
     25#endif
     26
     27/* EOF */
  • new file libraries/source/tinygettext/tinygettext/unix_file_system.cpp

    diff --git a/libraries/source/tinygettext/tinygettext/unix_file_system.cpp b/libraries/source/tinygettext/tinygettext/unix_file_system.cpp
    new file mode 100644
    index 0000000..5e14d17
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#include "unix_file_system.hpp"
     19
     20#include <sys/types.h>
     21#include <fstream>
     22#include <dirent.h>
     23#include <stdlib.h>
     24#include <dirent.h>
     25#include <string.h>
     26
     27namespace tinygettext {
     28
     29UnixFileSystem::UnixFileSystem()
     30{
     31}
     32
     33std::vector<std::string>
     34UnixFileSystem::open_directory(const std::string& pathname)
     35{
     36  DIR* dir = opendir(pathname.c_str());
     37  if (!dir)
     38  {
     39    // FIXME: error handling
     40    return std::vector<std::string>();
     41  }
     42  else
     43  {
     44    std::vector<std::string> files;
     45
     46    struct dirent* dp;
     47    while((dp = readdir(dir)) != 0)
     48    {
     49      files.push_back(dp->d_name);
     50    }
     51    closedir(dir);
     52
     53    return files;
     54  }
     55}
     56 
     57std::auto_ptr<std::istream>
     58UnixFileSystem::open_file(const std::string& filename)
     59{
     60  return std::auto_ptr<std::istream>(new std::ifstream(filename.c_str()));
     61}
     62
     63} // namespace tinygettext
     64
     65/* EOF */
  • new file libraries/source/tinygettext/tinygettext/unix_file_system.hpp

    diff --git a/libraries/source/tinygettext/tinygettext/unix_file_system.hpp b/libraries/source/tinygettext/tinygettext/unix_file_system.hpp
    new file mode 100644
    index 0000000..80f3e6d
    - +  
     1//  tinygettext - A gettext replacement that works directly on .po files
     2//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
     3//
     4//  This program is free software; you can redistribute it and/or
     5//  modify it under the terms of the GNU General Public License
     6//  as published by the Free Software Foundation; either version 2
     7//  of the License, or (at your option) any later version.
     8//
     9//  This program is distributed in the hope that it will be useful,
     10//  but WITHOUT ANY WARRANTY; without even the implied warranty of
     11//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12//  GNU General Public License for more details.
     13//
     14//  You should have received a copy of the GNU General Public License
     15//  along with this program; if not, write to the Free Software
     16//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     17
     18#ifndef HEADER_TINYGETTEXT_UNIX_FILE_SYSTEM_HPP
     19#define HEADER_TINYGETTEXT_UNIX_FILE_SYSTEM_HPP
     20
     21#include "file_system.hpp"
     22
     23namespace tinygettext {
     24
     25class UnixFileSystem : public FileSystem
     26{
     27public:
     28  UnixFileSystem();
     29
     30  std::vector<std::string>    open_directory(const std::string& pathname);
     31  std::auto_ptr<std::istream> open_file(const std::string& filename);
     32};
     33
     34} // namespace tinygettext
     35
     36#endif
     37
     38/* EOF */
  • source/gui/CGUI.cpp

    diff --git a/source/gui/CGUI.cpp b/source/gui/CGUI.cpp
    index e6bfdec..c6e7ccc 100644
    a b CGUI  
    4545#include "graphics/TextRenderer.h"
    4646#include "lib/input.h"
    4747#include "lib/bits.h"
     48#include "lib/localization.h"
    4849#include "lib/timer.h"
    4950#include "lib/sysdep/sysdep.h"
     51#include "lib/utf8.h"
    5052#include "ps/CLogger.h"
    5153#include "ps/Filesystem.h"
    5254#include "ps/Font.h"
    void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec  
    11801182    ELMT(object);
    11811183    ELMT(action);
    11821184    ELMT(repeat);
     1185    ELMT(localizableAttribute);
     1186    ELMT(localize);
     1187    ELMT(attribute);
     1188    ELMT(keep);
    11831189    ATTR(style);
    11841190    ATTR(type);
    11851191    ATTR(name);
    void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec  
    11871193    ATTR(z);
    11881194    ATTR(on);
    11891195    ATTR(file);
     1196    ATTR(id);
    11901197
    11911198    //
    11921199    //  Read Style and set defaults
    void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec  
    13271334                code = scriptfile.DecodeUTF8(); // assume it's UTF-8
    13281335            }
    13291336
    1330             // Read the inline code (concatenating to the file code, if both are specified)
    1331             code += CStr(child.GetText());
     1337            XMBElementList grandchildren = child.GetChildNodes();
     1338            if (grandchildren.Count > 0) // The <action> element contains <keep> and <localize> tags.
     1339            {
     1340                for (int i = 0; i < grandchildren.Count; ++i)
     1341                {
     1342                    XMBElement grandchild = grandchildren.Item(i);
     1343                    if (grandchild.GetNodeName() == elmt_localize)
     1344                    {
     1345                        code += Localization::getInstance().localize(grandchild.GetText());
     1346                    }
     1347                    else if (grandchild.GetNodeName() == elmt_keep)
     1348                    {
     1349                        code += grandchild.GetText();
     1350                    }
     1351                }
     1352            }
     1353            else // It’s pure JavaScript code.
     1354            {
     1355                // Read the inline code (concatenating to the file code, if both are specified)
     1356                code += CStr(child.GetText());
     1357            }
    13321358
    13331359            CStr action = CStr(child.GetAttributes().GetNamedItem(attr_on));
    13341360            object->RegisterScriptHandler(action.LowerCase(), code, this);
    void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObjec  
    13371363        {
    13381364            Xeromyces_ReadRepeat(child, pFile, object, Paths);
    13391365        }
     1366        else if (element_name == elmt_localizableAttribute)
     1367        {
     1368            // This is an element in the form “<localizableAttribute id="attributeName">attributeValue</localizableAttribute>”.
     1369            CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
     1370            if (!attributeName.empty())
     1371            {
     1372                CStr value(child.GetText());
     1373                if (!value.empty())
     1374                {
     1375                    CStr localizedValue(Localization::getInstance().localize(value));
     1376                    object->SetSetting(attributeName, localizedValue.UnescapeBackslashes().FromUTF8(), true);
     1377                }
     1378            }
     1379            else // Ignore.
     1380            {
     1381                LOGERROR(L"GUI: ‘attribute’ XML element with empty ‘id’ XML attribute found. (object: %hs)", object->GetPresentableName().c_str());
     1382            }
     1383        }
     1384        else if (element_name == elmt_attribute)
     1385        {
     1386            // This is an element in the form “<attribute id="attributeName"><keep>Don’t translate this part
     1387            // </keep><localize>but translate this one.</localize></attribute>”.
     1388            CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
     1389            if (!attributeName.empty())
     1390            {
     1391                CStr localizedValue;
     1392
     1393                XMBElementList grandchildren = child.GetChildNodes();
     1394                for (int i = 0; i < grandchildren.Count; ++i)
     1395                {
     1396                    XMBElement grandchild = grandchildren.Item(i);
     1397                    if (grandchild.GetNodeName() == elmt_localize)
     1398                    {
     1399                        localizedValue += Localization::getInstance().localize(grandchild.GetText());
     1400                    }
     1401                    else if (grandchild.GetNodeName() == elmt_keep)
     1402                    {
     1403                        localizedValue += grandchild.GetText();
     1404                    }
     1405                }
     1406                object->SetSetting(attributeName, localizedValue.UnescapeBackslashes().FromUTF8(), true);
     1407            }
     1408            else // Ignore.
     1409            {
     1410                LOGERROR(L"GUI: ‘attribute’ XML element with empty ‘id’ XML attribute found. (object: %hs)", object->GetPresentableName().c_str());
     1411            }
     1412        }
    13401413        else
    13411414        {
    13421415            // Try making the object read the tag.
  • source/gui/GUIRenderer.cpp

    diff --git a/source/gui/GUIRenderer.cpp b/source/gui/GUIRenderer.cpp
    index 9531158..e5f2b09 100644
    a b  
    2222#include "graphics/ShaderManager.h"
    2323#include "graphics/TextureManager.h"
    2424#include "gui/GUIutil.h"
     25#include "lib/localization.h"
    2526#include "lib/ogl.h"
    2627#include "lib/utf8.h"
    2728#include "lib/res/h_mgr.h"
    void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, const CStr& SpriteName,  
    200201
    201202        if (! cit->m_TextureName.empty())
    202203        {
    203             CTextureProperties textureProps(cit->m_TextureName);
     204            CTextureProperties textureProps(Localization::getInstance().localizePath(cit->m_TextureName));
    204205            textureProps.SetWrap(cit->m_WrapMode);
    205206            CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
    206207            texture->Prefetch();
  • source/gui/scripting/ScriptFunctions.cpp

    diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
    index 6913c2b..5058436 100644
    a b  
    2424#include "graphics/MapReader.h"
    2525#include "gui/GUIManager.h"
    2626#include "graphics/scripting/JSInterface_GameView.h"
     27#include "lib/localization.h"
     28#include "lib/svn_revision.h"
    2729#include "lib/timer.h"
    2830#include "lib/utf8.h"
    2931#include "lib/sysdep/sysdep.h"
    void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)  
    650652    ICmpSelectable::ms_EnableDebugOverlays = enabled;
    651653}
    652654
     655// Return the date/time at which the current executable was compiled.
     656// params: none (-> "date time (svn revision)") OR an integer specifying
     657//   what to display: 0 for date, 1 for time, 2 for svn revision
     658// returns: string with the requested timestamp info
     659// notes:
     660// - Displayed on main menu screen; tells non-programmers which auto-build
     661//   they are running. Could also be determined via .EXE file properties,
     662//   but that's a bit more trouble.
     663// - To be exact, the date/time returned is when scriptglue.cpp was
     664//   last compiled, but the auto-build does full rebuilds.
     665// - svn revision is generated by calling svnversion and cached in
     666//   lib/svn_revision.cpp. it is useful to know when attempting to
     667//   reproduce bugs (the main EXE and PDB should be temporarily reverted to
     668//   that revision so that they match user-submitted crashdumps).
     669std::wstring GetBuildTimestamp(void* UNUSED(cbdata), int mode)
     670{
     671    char buf[200];
     672    if (mode == -1) // Date, time and revision.
     673    {
     674        UDate dateTime = Localization::getInstance().parseDateTime(__DATE__ " " __TIME__, "MMM d yyyy HH:mm:ss", Locale::getUS());
     675        std::string dateTimeString = Localization::getInstance().localizeDateTime(dateTime, Localization::DateTime, SimpleDateFormat::DATE_TIME);
     676        char svnRevision[32];
     677        sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
     678        if (strcmp(svnRevision, "custom build") == 0)
     679        {
     680            // Localization: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
     681            sprintf_s(buf, ARRAY_SIZE(buf), Localization::getInstance().localize("%s (custom build)").c_str(), dateTimeString.c_str());
     682        }
     683        else
     684        {
     685            // Localization: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
     686            sprintf_s(buf, ARRAY_SIZE(buf), Localization::getInstance().localize("%s (%ls)").c_str(), dateTimeString.c_str(), svn_revision);
     687        }
     688    }
     689    else if (mode == 0) // Date.
     690    {
     691        UDate dateTime = Localization::getInstance().parseDateTime(__DATE__, "MMM d yyyy", Locale::getUS());
     692        std::string dateTimeString = Localization::getInstance().localizeDateTime(dateTime, Localization::Date, SimpleDateFormat::MEDIUM);
     693        sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
     694    }
     695    else if (mode == 1) // Time.
     696    {
     697        UDate dateTime = Localization::getInstance().parseDateTime(__TIME__, "HH:mm:ss", Locale::getUS());
     698        std::string dateTimeString = Localization::getInstance().localizeDateTime(dateTime, Localization::Time, SimpleDateFormat::MEDIUM);
     699        sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
     700    }
     701    else if (mode == 2) // Revision.
     702    {
     703        char svnRevision[32];
     704        sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
     705        if (strcmp(svnRevision, "custom build") == 0)
     706        {
     707            sprintf_s(buf, ARRAY_SIZE(buf), Localization::getInstance().localize("custom build").c_str());
     708        }
     709        else
     710        {
     711            sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
     712        }
     713    }
     714
     715    return wstring_from_utf8(buf);
     716}
     717
     718// Return the current locale code.
     719std::string getCurrentLocale(void* UNUSED(cbdata))
     720{
     721    return Localization::getInstance().getCurrentLocale().getLanguage();
     722}
     723
     724// Return a localized version of the given string.
     725std::wstring localize(void* UNUSED(cbdata), std::wstring sourceString)
     726{
     727    return wstring_from_utf8(Localization::getInstance().localize(utf8_from_wstring(sourceString)));
     728}
     729
     730// Return a localized version of the given string, for the specified context.
     731std::wstring localizeWithContext(void* UNUSED(cbdata), std::string context, std::wstring sourceString)
     732{
     733    return wstring_from_utf8(Localization::getInstance().localizeWithContext(context, utf8_from_wstring(sourceString)));
     734}
     735
     736// Return a localized version of the given strings (singular and plural) depending on an integer value.
     737std::wstring localizePlural(void* UNUSED(cbdata), std::wstring singularSourceString, std::wstring pluralSourceString, int number)
     738{
     739    return wstring_from_utf8(Localization::getInstance().localizePlural(utf8_from_wstring(singularSourceString), utf8_from_wstring(pluralSourceString), number));
     740}
     741
     742// Return a localized version of the given strings (singular and plural) depending on an integer value, for the specified context.
     743std::wstring localizePluralWithContext(void* UNUSED(cbdata), std::string context, std::wstring singularSourceString, std::wstring pluralSourceString, int number)
     744{
     745    return wstring_from_utf8(Localization::getInstance().localizePluralWithContext(context, utf8_from_wstring(singularSourceString), utf8_from_wstring(pluralSourceString), number));
     746}
     747
     748// Return a localized version of the given string, localizing it line by line.
     749std::wstring localizeLines(void* UNUSED(cbdata), std::wstring sourceString)
     750{
     751    return wstring_from_utf8(Localization::getInstance().localizeLines(utf8_from_wstring(sourceString)));
     752}
     753
     754// Return a localized version of the items in the specified array.
     755std::vector<std::wstring> localizeArray(void* UNUSED(cbdata), std::vector<std::wstring> sourceArray)
     756{
     757    std::vector<std::wstring> localizedArray;
     758    for (std::vector<std::wstring>::iterator iterator = sourceArray.begin(); iterator != sourceArray.end(); ++iterator)
     759    {
     760        localizedArray.push_back(wstring_from_utf8(Localization::getInstance().localize(utf8_from_wstring(*iterator))));
     761    }
     762    return localizedArray;
     763}
     764
     765// Return a localized version of a time given in milliseconds.
     766std::wstring formatMillisecondsIntoDateString(void* UNUSED(cbdata), int milliseconds, std::wstring formatString)
     767{
     768    return wstring_from_utf8(Localization::getInstance().formatMillisecondsIntoDateString(milliseconds, utf8_from_wstring(formatString)));
     769}
     770
     771// Return a localized version of the given decimal number.
     772std::wstring formatDecimalNumberIntoString(void* UNUSED(cbdata), double number)
     773{
     774    return wstring_from_utf8(Localization::getInstance().formatDecimalNumberIntoString(number));
     775}
     776
     777// Return a localized version of the given decimal number.
     778std::wstring markToLocalize(void* UNUSED(cbdata), std::wstring sourceString)
     779{
     780    return sourceString;
     781}
     782
     783std::vector<std::string> GetSupportedLocaleCodes(void* UNUSED(cbdata))
     784{
     785    return Localization::getInstance().getSupportedLocaleCodes();
     786}
     787
     788std::vector<std::wstring> GetSupportedLocaleDisplayNames(void* UNUSED(cbdata))
     789{
     790    return Localization::getInstance().getSupportedLocaleDisplayNames();
     791}
     792
     793int GetCurrentLocaleIndex(void* UNUSED(cbdata))
     794{
     795    return Localization::getInstance().getCurrentLocaleIndex();
     796}
     797
     798void SetLocale(void* UNUSED(cbdata), std::string locale)
     799{
     800    g_ConfigDB.CreateValue(CFG_USER, "locale")->m_String = locale;
     801    g_ConfigDB.WriteFile(CFG_USER);
     802}
     803
    653804} // namespace
    654805
    655806void GuiScriptingInit(ScriptInterface& scriptInterface)
    void GuiScriptingInit(ScriptInterface& scriptInterface)  
    743894    scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
    744895    scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
    745896    scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
     897
     898    // Internationalization and localization functions
     899    scriptInterface.RegisterFunction<std::wstring, int, &GetBuildTimestamp>("GetBuildTimestamp");
     900    scriptInterface.RegisterFunction<std::string, &getCurrentLocale>("getCurrentLocale");
     901    scriptInterface.RegisterFunction<std::wstring, std::wstring, &localize>("localize");
     902    scriptInterface.RegisterFunction<std::wstring, std::string, std::wstring, &localizeWithContext>("localizeWithContext");
     903    scriptInterface.RegisterFunction<std::wstring, std::wstring, std::wstring, int, &localizePlural>("localizePlural");
     904    scriptInterface.RegisterFunction<std::wstring, std::string, std::wstring, std::wstring, int, &localizePluralWithContext>("localizePluralWithContext");
     905    scriptInterface.RegisterFunction<std::wstring, std::wstring, &localizeLines>("localizeLines");
     906    scriptInterface.RegisterFunction<std::vector<std::wstring>, std::vector<std::wstring>, &localizeArray>("localizeArray");
     907    scriptInterface.RegisterFunction<std::wstring, int, std::wstring, &formatMillisecondsIntoDateString>("formatMillisecondsIntoDateString");
     908    scriptInterface.RegisterFunction<std::wstring, double, &formatDecimalNumberIntoString>("formatDecimalNumberIntoString");
     909    scriptInterface.RegisterFunction<std::wstring, std::wstring, &markToLocalize>("markToLocalize");
     910    scriptInterface.RegisterFunction<std::vector<std::string>, &GetSupportedLocaleCodes>("GetSupportedLocaleCodes");
     911    scriptInterface.RegisterFunction<std::vector<std::wstring>, &GetSupportedLocaleDisplayNames>("GetSupportedLocaleDisplayNames");
     912    scriptInterface.RegisterFunction<int, &GetCurrentLocaleIndex>("GetCurrentLocaleIndex");
     913    scriptInterface.RegisterFunction<void, std::string, &SetLocale>("SetLocale");
    746914}
  • new file source/lib/external_libraries/icu.h

    diff --git a/source/lib/external_libraries/icu.h b/source/lib/external_libraries/icu.h
    new file mode 100644
    index 0000000..07b8ba2
    - +  
     1/* Copyright (c) 2013 Wildfire Games
     2 *
     3 * Permission is hereby granted, free of charge, to any person obtaining
     4 * a copy of this software and associated documentation files (the
     5 * "Software"), to deal in the Software without restriction, including
     6 * without limitation the rights to use, copy, modify, merge, publish,
     7 * distribute, sublicense, and/or sell copies of the Software, and to
     8 * permit persons to whom the Software is furnished to do so, subject to
     9 * the following conditions:
     10 *
     11 * The above copyright notice and this permission notice shall be included
     12 * in all copies or substantial portions of the Software.
     13 *
     14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21 */
     22
     23#ifndef INCLUDED_ICU
     24#define INCLUDED_ICU
     25
     26#include <unicode/smpdtfmt.h>
     27
     28#endif  // #ifndef INCLUDED_ICU
  • new file source/lib/external_libraries/tinygettext.h

    diff --git a/source/lib/external_libraries/tinygettext.h b/source/lib/external_libraries/tinygettext.h
    new file mode 100644
    index 0000000..f347395
    - +  
     1/* Copyright (c) 2011 Wildfire Games
     2 *
     3 * Permission is hereby granted, free of charge, to any person obtaining
     4 * a copy of this software and associated documentation files (the
     5 * "Software"), to deal in the Software without restriction, including
     6 * without limitation the rights to use, copy, modify, merge, publish,
     7 * distribute, sublicense, and/or sell copies of the Software, and to
     8 * permit persons to whom the Software is furnished to do so, subject to
     9 * the following conditions:
     10 *
     11 * The above copyright notice and this permission notice shall be included
     12 * in all copies or substantial portions of the Software.
     13 *
     14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21 */
     22
     23/*
     24 * Bring in the TinyGettext header file.
     25 */
     26
     27#ifndef INCLUDED_TINYGETTEXT
     28#define INCLUDED_TINYGETTEXT
     29
     30#include <tinygettext/tinygettext.hpp>
     31#include <tinygettext/po_parser.hpp>
     32
     33#endif  // INCLUDED_TINYGETTEXT
  • new file source/lib/localization.cpp

    diff --git a/source/lib/localization.cpp b/source/lib/localization.cpp
    new file mode 100644
    index 0000000..11eaf74
    - +  
     1/* Copyright (c) 2013 Wildfire Games
     2 *
     3 * Permission is hereby granted, free of charge, to any person obtaining
     4 * a copy of this software and associated documentation files (the
     5 * "Software"), to deal in the Software without restriction, including
     6 * without limitation the rights to use, copy, modify, merge, publish,
     7 * distribute, sublicense, and/or sell copies of the Software, and to
     8 * permit persons to whom the Software is furnished to do so, subject to
     9 * the following conditions:
     10 *
     11 * The above copyright notice and this permission notice shall be included
     12 * in all copies or substantial portions of the Software.
     13 *
     14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21 */
     22
     23#include "lib/localization.h"
     24
     25#include <iostream>
     26#include <string>
     27#include <boost/concept_check.hpp>
     28
     29#include "lib/file/file_system.h"
     30#include "lib/utf8.h"
     31
     32#include "ps/CLogger.h"
     33#include "ps/ConfigDB.h"
     34#include "ps/Filesystem.h"
     35
     36
     37Localization& Localization::getInstance()
     38{
     39    static Localization instance;
     40    return instance;
     41}
     42
     43Localization::Localization()
     44: currentLocaleIsOriginalGameLocale(false) // determineCurrentLocale() takes care of setting this value to true.
     45{
     46    loadListOfAvailableLocales();
     47    determineCurrentLocale();
     48    loadDictionaryFromCurrentLocale();
     49}
     50
     51Localization::~Localization()
     52{
     53    delete dictionary;
     54}
     55
     56Locale Localization::getCurrentLocale()
     57{
     58    return currentLocale;
     59}
     60
     61std::vector< std::string > Localization::getSupportedLocaleCodes()
     62{
     63    std::vector<std::string> supportedLocaleCodes;
     64    for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
     65    {
     66        supportedLocaleCodes.push_back((*iterator)->getBaseName());
     67    }
     68    return supportedLocaleCodes;
     69}
     70
     71std::vector< std::wstring > Localization::getSupportedLocaleDisplayNames()
     72{
     73    std::vector<std::wstring> supportedLocaleDisplayNames;
     74    for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
     75    {
     76        UnicodeString utf16LocaleDisplayName;
     77        (**iterator).getDisplayName(**iterator, utf16LocaleDisplayName);
     78        std::string localeDisplayName;
     79        utf16LocaleDisplayName.toUTF8String(localeDisplayName);
     80        supportedLocaleDisplayNames.push_back(wstring_from_utf8(localeDisplayName));
     81    }
     82    return supportedLocaleDisplayNames;
     83}
     84
     85int Localization::getCurrentLocaleIndex()
     86{
     87    std::vector<std::string> supportedLocaleCodes;
     88
     89    // Try to match a whole code first.
     90    for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
     91    {
     92        if (strcmp((**iterator).getBaseName(), getCurrentLocale().getBaseName()) == 0)
     93        {
     94            return iterator - availableLocales.begin();
     95        }
     96    }
     97
     98    // Match language code only if no whole code matched.
     99    for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
     100    {
     101        if (strcmp((**iterator).getLanguage(), getCurrentLocale().getLanguage()) == 0)
     102        {
     103            return iterator - availableLocales.begin();
     104        }
     105    }
     106
     107    // Use en_US, the default locale.
     108    for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
     109    {
     110        if (strcmp((**iterator).getLanguage(), Locale::getUS().getLanguage()) == 0)
     111        {
     112            return iterator - availableLocales.begin();
     113        }
     114    }
     115
     116    return 0; // It should never get this far.
     117}
     118
     119std::string Localization::localize(const std::string& sourceString)
     120{
     121    if (!currentLocaleIsOriginalGameLocale)
     122    {
     123        return dictionary->translate(sourceString);
     124    }
     125    else
     126    {
     127        return sourceString;
     128    }
     129}
     130
     131std::string Localization::localizeWithContext(const std::string& context, const std::string& sourceString)
     132{
     133    if (!currentLocaleIsOriginalGameLocale)
     134    {
     135        return dictionary->translate_ctxt(context, sourceString);
     136    }
     137    else
     138    {
     139        return sourceString;
     140    }
     141}
     142
     143std::string Localization::localizePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number)
     144{
     145    if (!currentLocaleIsOriginalGameLocale)
     146    {
     147        return dictionary->translate_plural(singularSourceString, pluralSourceString, number);
     148    }
     149    else
     150    {
     151        if (number == 1)
     152        {
     153            return singularSourceString;
     154        }
     155        else
     156        {
     157            return pluralSourceString;
     158        }
     159    }
     160}
     161
     162std::string Localization::localizePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number)
     163{
     164    if (!currentLocaleIsOriginalGameLocale)
     165    {
     166        return dictionary->translate_ctxt_plural(context, singularSourceString, pluralSourceString, number);
     167    }
     168    else
     169    {
     170        if (number == 1)
     171        {
     172            return singularSourceString;
     173        }
     174        else
     175        {
     176            return pluralSourceString;
     177        }
     178    }
     179}
     180
     181std::string Localization::localizeLines(const std::string& sourceString)
     182{
     183    std::string targetString;
     184    std::stringstream stringOfLines(sourceString);
     185    std::string line;
     186
     187    while (std::getline(stringOfLines, line)) {
     188        targetString.append(localize(line));
     189        targetString.append("\n");
     190    }
     191
     192    return targetString;
     193}
     194
     195UDate Localization::parseDateTime(const std::string& dateTimeString, const std::string& dateTimeFormat, const Locale& locale)
     196{
     197    UErrorCode success = U_ZERO_ERROR;
     198    UnicodeString utf16DateTimeString = UnicodeString::fromUTF8(dateTimeString);
     199    UnicodeString utf16DateTimeFormat = UnicodeString::fromUTF8(dateTimeFormat);
     200
     201    DateFormat* dateFormatter = new SimpleDateFormat(utf16DateTimeFormat, locale, success);
     202    UDate date = dateFormatter->parse(utf16DateTimeString, success);
     203    delete dateFormatter;
     204
     205    return date;
     206}
     207
     208std::string Localization::localizeDateTime(const UDate& dateTime, DateTimeType type, DateFormat::EStyle style)
     209{
     210    UnicodeString utf16Date;
     211    std::string utf8Date;
     212
     213    DateFormat* dateFormatter = createDateTimeInstance(type, style, currentLocale);
     214    dateFormatter->format(dateTime, utf16Date);
     215    utf16Date.toUTF8String(utf8Date);
     216    delete dateFormatter;
     217
     218    return utf8Date;
     219}
     220
     221std::string Localization::formatMillisecondsIntoDateString(int milliseconds, const std::string& formatString)
     222{
     223    UErrorCode success = U_ZERO_ERROR;
     224    std::string utf8Date;
     225    UnicodeString utf16Date;
     226    UnicodeString utf16SourceDateTimeFormat = UnicodeString::fromUTF8("A");
     227    UnicodeString utf16LocalizedDateTimeFormat = UnicodeString::fromUTF8(formatString);
     228    char buffer[32];
     229    std::string utf8MillisecondsString(std::string(buffer, sprintf(buffer, "%d", milliseconds)));
     230    UnicodeString utf16MillisecondsString = UnicodeString::fromUTF8(utf8MillisecondsString);
     231
     232    SimpleDateFormat* dateFormatter = new SimpleDateFormat(utf16SourceDateTimeFormat, currentLocale, success);
     233    UDate dateTime = dateFormatter->parse(utf16MillisecondsString, success);
     234    dateFormatter->applyLocalizedPattern(utf16LocalizedDateTimeFormat, success);
     235    dateFormatter->format(dateTime, utf16Date);
     236    utf16Date.toUTF8String(utf8Date);
     237    delete dateFormatter;
     238
     239    return utf8Date;
     240}
     241
     242std::string Localization::formatDecimalNumberIntoString(double number)
     243{
     244    UErrorCode success = U_ZERO_ERROR;
     245    UnicodeString utf16Number;
     246    std::string utf8Number;
     247    NumberFormat* numberFormatter = NumberFormat::createInstance(currentLocale, UNUM_DECIMAL, success);
     248    numberFormatter->format(number, utf16Number);
     249    utf16Number.toUTF8String(utf8Number);
     250    return utf8Number;
     251}
     252
     253VfsPath Localization::localizePath(VfsPath sourcePath)
     254{
     255    VfsPath path = sourcePath;
     256
     257    VfsPath localizedPath = sourcePath.Parent() / L"localization" / wstring_from_utf8(currentLocale.getLanguage()) / sourcePath.Filename();
     258    if (VfsFileExists(localizedPath))
     259    {
     260        path = localizedPath;
     261    }
     262
     263    return path;
     264}
     265
     266void Localization::determineCurrentLocale()
     267{
     268    CConfigValue *value = g_ConfigDB.GetValue(CFG_COMMAND, "locale");
     269    if (value)
     270    {
     271        currentLocale = Locale(Locale::createCanonical(value->m_String.c_str()));
     272    }
     273    else
     274    {
     275        currentLocale = Locale::getDefault();
     276    }
     277
     278    if (currentLocale == Locale::getUS())
     279    {
     280        currentLocaleIsOriginalGameLocale = true;
     281    }
     282    else
     283    {
     284        currentLocaleIsOriginalGameLocale = false;
     285    }
     286}
     287
     288void Localization::loadDictionaryFromCurrentLocale()
     289{
     290    if (!currentLocaleIsOriginalGameLocale)
     291    {
     292        delete dictionary;
     293        dictionary = new tinygettext::Dictionary();
     294
     295        VfsPaths filenames;
     296        if (vfs::GetPathnames(g_VFS, L"localization/", (wstring_from_utf8(currentLocale.getLanguage()) + L".*.po").c_str(), filenames) < 0)
     297            return;
     298
     299        for (VfsPaths::iterator it = filenames.begin(); it != filenames.end(); ++it)
     300        {
     301            VfsPath filename = *it;
     302            CVFSFile file;
     303            file.Load(g_VFS, filename);
     304            std::string content = file.DecodeUTF8();
     305            readPoIntoDictionary(content, dictionary);
     306        }
     307    }
     308}
     309
     310void Localization::loadListOfAvailableLocales()
     311{
     312    for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
     313    {
     314        delete *iterator;
     315    }
     316    availableLocales.clear();
     317
     318    Locale* defaultLocale = new Locale(Locale::getUS());
     319    availableLocales.push_back(defaultLocale); // Always available.
     320
     321    VfsPaths filenames;
     322    if (vfs::GetPathnames(g_VFS, L"localization/", L"*.po", filenames) < 0)
     323        return;
     324
     325    for (VfsPaths::iterator it = filenames.begin(); it != filenames.end(); ++it)
     326    {
     327        // Note: PO files follow this naming convention: “localization/<locale code>.<mod name>.po”. For example: “localization/gl.public.po”.
     328        VfsPath filepath = *it;
     329        std::string filename = utf8_from_wstring(filepath.string()).substr(strlen("localization/"));
     330        std::size_t lengthToFirstDot = filename.find('.');
     331        std::string localeCode = filename.substr(0, lengthToFirstDot);
     332        Locale* locale = new Locale(Locale::createCanonical(localeCode.c_str()));
     333
     334        bool localeIsAlreadyAvailable = false;
     335        for (std::vector<Locale*>::iterator iterator = availableLocales.begin(); iterator != availableLocales.end(); ++iterator)
     336        {
     337            if (*locale == **iterator)
     338            {
     339                localeIsAlreadyAvailable = true;
     340                break;
     341            }
     342        }
     343
     344        if (!localeIsAlreadyAvailable)
     345        {
     346            availableLocales.push_back(locale);
     347        }
     348    }
     349}
     350
     351void Localization::readPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary)
     352{
     353    try
     354    {
     355        std::istringstream inputStream(poContent);
     356        tinygettext::POParser::parse("virtual PO file", inputStream, *dictionary, false);
     357    }
     358    catch(std::exception& e)
     359    {
     360        LOGERROR(L"[Localization] Exception while reading virtual PO file");
     361    }
     362}
     363
     364DateFormat* Localization::createDateTimeInstance(Localization::DateTimeType type, DateFormat::EStyle style, const Locale& locale)
     365{
     366    if (type == Date)
     367    {
     368        return SimpleDateFormat::createDateInstance(style, locale);
     369    }
     370    else if (type == Time)
     371    {
     372        return SimpleDateFormat::createTimeInstance(style, locale);
     373    }
     374    else // Provide DateTime by default.
     375    {
     376        return SimpleDateFormat::createDateTimeInstance(style, style, locale);
     377    }
     378}
  • new file source/lib/localization.h

    diff --git a/source/lib/localization.h b/source/lib/localization.h
    new file mode 100644
    index 0000000..5b34605
    - +  
     1/* Copyright (c) 2013 Wildfire Games
     2 *
     3 * Permission is hereby granted, free of charge, to any person obtaining
     4 * a copy of this software and associated documentation files (the
     5 * "Software"), to deal in the Software without restriction, including
     6 * without limitation the rights to use, copy, modify, merge, publish,
     7 * distribute, sublicense, and/or sell copies of the Software, and to
     8 * permit persons to whom the Software is furnished to do so, subject to
     9 * the following conditions:
     10 *
     11 * The above copyright notice and this permission notice shall be included
     12 * in all copies or substantial portions of the Software.
     13 *
     14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21 */
     22
     23#ifndef LOCALIZATION_H
     24#define LOCALIZATION_H
     25
     26#include <string>
     27#include <vector>
     28
     29#include "lib/external_libraries/icu.h"
     30#include "lib/external_libraries/tinygettext.h"
     31
     32#include "lib/file/vfs/vfs_path.h"
     33
     34class Localization
     35{
     36public:
     37
     38    enum DateTimeType { DateTime, Date, Time };
     39
     40    static Localization& getInstance();
     41
     42    Localization();
     43    Localization(Localization const&); // Don’t implement.
     44    void operator=(Localization const&); // Don’t implement.
     45    ~Localization();
     46
     47    Locale getCurrentLocale();
     48    std::vector<std::string> getSupportedLocaleCodes();
     49    std::vector<std::wstring> getSupportedLocaleDisplayNames();
     50    int getCurrentLocaleIndex();
     51
     52    std::string localize(const std::string& sourceString);
     53    std::string localizeWithContext(const std::string& context, const std::string& sourceString);
     54    std::string localizePlural(const std::string& singularSourceString, const std::string& pluralSourceString, int number);
     55    std::string localizePluralWithContext(const std::string& context, const std::string& singularSourceString, const std::string& pluralSourceString, int number);
     56    std::string localizeLines(const std::string& sourceString);
     57
     58    UDate parseDateTime(const std::string& dateTimeString, const std::string& dateTimeFormat, const Locale& locale);
     59    std::string localizeDateTime(const UDate& dateTime, DateTimeType type, DateFormat::EStyle style);
     60    std::string formatMillisecondsIntoDateString(int milliseconds, const std::string& formatString);
     61    std::string formatDecimalNumberIntoString(double number);
     62
     63    VfsPath localizePath(VfsPath sourcePath);
     64
     65//  bool               isRTLLanguage() const;
     66
     67//  std::string        getCurrentLanguageName();
     68
     69private:
     70    tinygettext::Dictionary* dictionary;
     71    bool isRtlLanguage;
     72    Locale currentLocale;
     73    std::vector<Locale*> availableLocales;
     74    bool currentLocaleIsOriginalGameLocale;
     75
     76
     77    void determineCurrentLocale();
     78    void loadDictionaryFromCurrentLocale();
     79    void loadListOfAvailableLocales();
     80
     81    void readPoIntoDictionary(const std::string& poContent, tinygettext::Dictionary* dictionary);
     82
     83    DateFormat* createDateTimeInstance(DateTimeType type, DateFormat::EStyle style, const Locale& locale);
     84};
     85
     86
     87extern Localization* localization;
     88
     89#endif // LOCALIZATION_H
  • source/ps/CLogger.cpp

    diff --git a/source/ps/CLogger.cpp b/source/ps/CLogger.cpp
    index f791924..0d06bbd 100644
    a b  
    2121#include "CConsole.h"
    2222#include "graphics/ShaderManager.h"
    2323#include "graphics/TextRenderer.h"
     24#include "lib/localization.h"
    2425#include "lib/ogl.h"
    2526#include "lib/timer.h"
    2627#include "lib/utf8.h"
    void CLogger::Render()  
    300301
    301302    for (std::deque<RenderedMessage>::iterator it = m_RenderMessages.begin(); it != m_RenderMessages.end(); ++it)
    302303    {
    303         const wchar_t* type;
    304304        if (it->method == Normal)
    305305        {
    306             type = L"info";
    307306            textRenderer.Color(0.0f, 0.8f, 0.0f);
     307            textRenderer.PrintfAdvance(wstring_from_utf8(Localization::getInstance().localize("[%8.3f] info: ")).c_str(), it->time);
    308308        }
    309309        else if (it->method == Warning)
    310310        {
    311             type = L"warning";
    312311            textRenderer.Color(1.0f, 1.0f, 0.0f);
     312            textRenderer.PrintfAdvance(wstring_from_utf8(Localization::getInstance().localize("[%8.3f] warning: ")).c_str(), it->time);
    313313        }
    314314        else
    315315        {
    316             type = L"error";
    317316            textRenderer.Color(1.0f, 0.0f, 0.0f);
     317            textRenderer.PrintfAdvance(wstring_from_utf8(Localization::getInstance().localize("[%8.3f] error: ")).c_str(), it->time);
    318318        }
    319319
    320320        CMatrix3D savedTransform = textRenderer.GetTransform();
    321321
    322         textRenderer.PrintfAdvance(L"[%8.3f] %ls: ", it->time, type);
    323322        // Display the actual message in white so it's more readable
    324323        textRenderer.Color(1.0f, 1.0f, 1.0f);
    325324        textRenderer.Put(0.0f, 0.0f, it->message.c_str());
  • source/ps/GameSetup/GameSetup.cpp

    diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp
    index b675343..3a8c3a1 100644
    a b static void InitVfs(const CmdLineArgs& args, int flags)  
    455455    g_VFS->Mount(L"config/", readonlyConfig);
    456456    bool dev = (g_VFS->GetFileInfo(L"config/dev.cfg", NULL) == INFO::OK);
    457457
     458    // Engine localization files.
     459    g_VFS->Mount(L"localization/", paths.RData()/"localization"/"");
     460
    458461    const std::vector<CStr> mods = GetMods(args, dev);
    459462
    460463    OsPath modPath = paths.RData()/"mods";
  • source/ps/SavedGame.cpp

    diff --git a/source/ps/SavedGame.cpp b/source/ps/SavedGame.cpp
    index 274d22f..2b93ad8 100644
    a b  
    2222#include "gui/GUIManager.h"
    2323#include "lib/allocators/shared_ptr.h"
    2424#include "lib/file/archive/archive_zip.h"
     25#include "lib/localization.h"
     26#include "lib/utf8.h"
    2527#include "ps/CLogger.h"
    2628#include "ps/Filesystem.h"
    2729#include "scriptinterface/ScriptInterface.h"
    Status SavedGames::Save(const std::wstring& name, const std::wstring& descriptio  
    106108
    107109    OsPath realPath;
    108110    WARN_RETURN_STATUS_IF_ERR(g_VFS->GetRealPath(filename, realPath));
    109     LOGMESSAGERENDER(L"Saved game to %ls\n", realPath.string().c_str());
     111    LOGMESSAGERENDER(wstring_from_utf8(Localization::getInstance().localize("Saved game to %ls") + "\n").c_str(), realPath.string().c_str());
    110112
    111113    return INFO::OK;
    112114}
    std::vector<CScriptValRooted> SavedGames::GetSavedGames(ScriptInterface& scriptI  
    198200        if (!archiveReader)
    199201        {
    200202            // Triggered by e.g. the file being open in another program
    201             LOGWARNING(L"Failed to read saved game '%ls'", realPath.string().c_str());
     203            LOGWARNING(wstring_from_utf8(Localization::getInstance().localize("Failed to read saved game '%ls'") + "\n").c_str(), realPath.string().c_str());
    202204            continue; // skip this file
    203205        }
    204206
  • source/scripting/ScriptGlue.cpp

    diff --git a/source/scripting/ScriptGlue.cpp b/source/scripting/ScriptGlue.cpp
    index b2c7353..ecb1686 100644
    a b  
    3434#include "gui/GUIManager.h"
    3535#include "gui/IGUIObject.h"
    3636#include "lib/frequency_filter.h"
    37 #include "lib/svn_revision.h"
    3837#include "lib/timer.h"
    3938#include "lib/sysdep/sysdep.h"  // sys_OpenFile
    4039#include "maths/scripting/JSInterface_Vector3D.h"
    JSBool GetGUIObjectByName(JSContext* cx, uintN argc, jsval* vp)  
    229228    }
    230229}
    231230
    232 //-----------------------------------------------------------------------------
    233 // Miscellany
    234 //-----------------------------------------------------------------------------
    235 
    236 // Return the date/time at which the current executable was compiled.
    237 // params: none (-> "date time (svn revision)") OR an integer specifying
    238 //   what to display: 0 for date, 1 for time, 2 for svn revision
    239 // returns: string with the requested timestamp info
    240 // notes:
    241 // - Displayed on main menu screen; tells non-programmers which auto-build
    242 //   they are running. Could also be determined via .EXE file properties,
    243 //   but that's a bit more trouble.
    244 // - To be exact, the date/time returned is when scriptglue.cpp was
    245 //   last compiled, but the auto-build does full rebuilds.
    246 // - svn revision is generated by calling svnversion and cached in
    247 //   lib/svn_revision.cpp. it is useful to know when attempting to
    248 //   reproduce bugs (the main EXE and PDB should be temporarily reverted to
    249 //   that revision so that they match user-submitted crashdumps).
    250 JSBool GetBuildTimestamp(JSContext* cx, uintN argc, jsval* vp)
    251 {
    252     JSU_REQUIRE_MAX_PARAMS(1);
    253 
    254     char buf[200];
    255 
    256     // see function documentation
    257     const int mode = argc? JSVAL_TO_INT(JS_ARGV(cx, vp)[0]) : -1;
    258     switch(mode)
    259     {
    260     case -1:
    261         sprintf_s(buf, ARRAY_SIZE(buf), "%s %s (%ls)", __DATE__, __TIME__, svn_revision);
    262         break;
    263     case 0:
    264         sprintf_s(buf, ARRAY_SIZE(buf), "%s", __DATE__);
    265         break;
    266     case 1:
    267         sprintf_s(buf, ARRAY_SIZE(buf), "%s", __TIME__);
    268         break;
    269     case 2:
    270         sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
    271         break;
    272     }
    273 
    274     JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, buf)));
    275     return JS_TRUE;
    276 }
    277 
    278231#if MOZJS_DEBUG_ABI
    279232void DumpHeap(const char* basename, int idx, JSContext* cx)
    280233{
    JSFunctionSpec ScriptFunctionTable[] =  
    395348    JS_FUNC("getGUIObjectByName", GetGUIObjectByName, 1)
    396349
    397350    // Miscellany
    398     JS_FUNC("buildTime", GetBuildTimestamp, 0)
    399351    JS_FUNC("dumpHeaps", DumpHeaps, 0)
    400352
    401353    // end of table marker
  • new file source/tools/localization/entities.its

    diff --git a/source/tools/localization/entities.its b/source/tools/localization/entities.its
    new file mode 100644
    index 0000000..644e358
    - +  
     1<its:rules
     2    xmlns:its="http://www.w3.org/2005/11/its"
     3    xmlns:itst="http://itstool.org/extensions/"
     4    version="1.0">
     5
     6  <its:translateRule selector="//Entity" translate="no" />
     7
     8  <!-- Translate the value of these elements only. -->
     9  <its:translateRule selector="//GenericName" translate="yes" />
     10  <its:translateRule selector="//SpecificName" translate="yes" />
     11  <!--
     12    To be added in the future: (available in the XML files, not visible in-game)
     13    <its:translateRule selector="//History" translate="yes" />
     14  -->
     15  <itst:preserveSpaceRule preserveSpace="yes" selector="//Tooltip" />
     16  <its:translateRule selector="//Tooltip" translate="yes" />
     17
     18</its:rules>
  • new file source/tools/localization/gui.its

    diff --git a/source/tools/localization/gui.its b/source/tools/localization/gui.its
    new file mode 100644
    index 0000000..331b3cd
    - +  
     1<its:rules
     2    xmlns:its="http://www.w3.org/2005/11/its"
     3    version="1.0">
     4 
     5  <its:translateRule selector="//objects" translate="no" />
     6  <its:translateRule selector="//page" translate="no" />
     7  <its:translateRule selector="//setup" translate="no" />
     8  <its:translateRule selector="//sprites" translate="no" />
     9  <its:translateRule selector="//styles" translate="no" />
     10 
     11  <!-- Translate the value of these elements only. -->
     12  <its:translateRule selector="//localizableAttribute" translate="yes" />
     13  <its:translateRule selector="//localize" translate="yes" />
     14 
     15</its:rules>
  • new file source/tools/localization/sync-transifex.sh

    diff --git a/source/tools/localization/sync-transifex.sh b/source/tools/localization/sync-transifex.sh
    new file mode 100644
    index 0000000..9202dfc
    - +  
     1#!/bin/bash
     2
     3basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     4rootdir="$basedir/../../.."
     5
     6# Go to the 0ad root, so that the paths in the POT file go from there.
     7pushd $rootdir &> /dev/null
     8
     9
     10# ENGINE
     11
     12pushd "binaries/data/localization" &> /dev/null
     13tx pull -af
     14popd &> /dev/null
     15
     16
     17# MODS
     18
     19# Get a list of modules with a ‘po’ directory.
     20declare -a modPaths
     21for modFolderPath in binaries/data/mods/*
     22do   
     23    poPath="${modFolderPath}/localization"
     24    if [ -d "${poPath}" ]; then
     25
     26        pushd ${poPath} &> /dev/null
     27
     28        # Initialize the ‘.tx’ folder if it is not initialized yet.
     29        txDir=".tx"
     30        if [ ! -d "${txDir}" ]; then
     31            mkdir "${txDir}"
     32            echo "[main]"                            > "${txDir}/config"
     33            echo "host = https://www.transifex.net" >> "${txDir}/config"
     34            modName=$(basename ${modFolderPath})
     35            tx set \
     36                --auto-local \
     37                -r 0-ad-unofficial.${modName} \
     38                --source-language=en \
     39                --source-file "${modName}.pot" \
     40                --execute \
     41                "<lang>.${modName}.po" \
     42                &> /dev/null
     43        fi
     44
     45        tx pull -af
     46
     47        popd &> /dev/null
     48    fi
     49done
     50
     51popd &> /dev/null
     52
  • new file source/tools/localization/update-pot.sh

    diff --git a/source/tools/localization/update-pot.sh b/source/tools/localization/update-pot.sh
    new file mode 100644
    index 0000000..aff6ae7
    - +  
     1#!/bin/bash
     2#
     3# This is true spaghetti code, but it works.
     4# You should ask Gallaecio to rewrite this in an actual scripting language such as Python instead of Bash.
     5
     6basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     7rootdir="$basedir/../../.."
     8xgettextKeywords="-klocalize -klocalizePlural:1,2 -klocalizeWithContext:1c,2 -klocalizePluralWithContext:1c,2,3 -kmarkToLocalize"
     9
     10# Go to the 0ad root, so that the paths in the POT file go from there.
     11pushd $rootdir &> /dev/null
     12
     13
     14# ENGINE
     15
     16# Extract C++ code strings.
     17declare -a sourceFiles
     18for sourceFile in $(find "source" -name "*.cpp")
     19do
     20    sourceFiles=("${sourceFiles[@]}" "$sourceFile")
     21done
     22outputPot="binaries/data/localization/engine.pot"
     23xgettext --from-code=utf-8 $xgettextKeywords -cLocalization -o ${outputPot} ${sourceFiles[@]} &> /dev/null
     24unset sourceFiles
     25
     26
     27# MODS
     28
     29for modFolderPath in binaries/data/mods/*
     30do
     31    # Work only on mods that contain a ‘localization’ folder. Other mods are considered as ‘disabled’ for internationalization.
     32    if [ -d "$modFolderPath/localization" ]; then
     33
     34        source "$modFolderPath/localization/messages.sh"
     35
     36        modName=$(basename $modFolderPath)
     37
     38        # Extract JavaScript code strings.
     39        declare -a sourceFiles
     40        for sourceFile in $(find $modFolderPath -name "*.js")
     41        do
     42            sourceFiles=("${sourceFiles[@]}" "$sourceFile")
     43        done
     44        outputPot=$modFolderPath/localization/${modName}-javascript.pot
     45        xgettext --language=Python --from-code=utf-8 $xgettextKeywords -o ${outputPot} ${sourceFiles[@]} &> /dev/null
     46        unset sourceFiles
     47
     48
     49        # Extract GUI XML strings.
     50        if [ -d "$modFolderPath/gui" ]; then
     51
     52            declare -a sourceFiles
     53            for sourceFile in $(find $modFolderPath/gui -name "*.xml")
     54            do
     55                sourceFiles=("${sourceFiles[@]}" "$sourceFile")
     56            done
     57
     58            outputPot=$modFolderPath/localization/${modName}-gui-xml.pot
     59
     60            itstool -o ${outputPot} -i $basedir/gui.its ${sourceFiles[@]}
     61            unset sourceFiles
     62
     63            sed -e "s/\&amp;/\&/g" -i ${outputPot}
     64        fi
     65
     66
     67        # Extract GUI TXT strings.
     68        if [ -d "$modFolderPath/gui" ]; then
     69
     70            outputPot=$modFolderPath/localization/${modName}-gui-txt.pot
     71
     72            # Print POT header.
     73            echo "msgid \"\""                                                > $outputPot
     74            echo "msgstr \"\""                                              >> $outputPot
     75            echo "\"Project-Id-Version: PACKAGE VERSION\n\""                >> $outputPot
     76            echo "\"POT-Creation-Date: $(date +"%Y-%m-%d %H:%M:%S%z")\n\""  >> $outputPot
     77            echo "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n\""            >> $outputPot
     78            echo "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n\""         >> $outputPot
     79            echo "\"Language-Team: LANGUAGE <LL@li.org>\n\""                >> $outputPot
     80            echo "\"MIME-Version: 1.0\n\""                                  >> $outputPot
     81            echo "\"Content-Type: text/plain; charset=UTF-8\n\""            >> $outputPot
     82            echo "\"Content-Transfer-Encoding: 8bit\n\""                    >> $outputPot
     83            echo ""                                                         >> $outputPot
     84
     85            # Parse TXT files line by line.
     86            for filepath in "${txtLineByLineFiles[@]}"
     87            do
     88                lineNumber=0
     89                while read -r line
     90                do
     91                    lineNumber=$((lineNumber + 1))
     92                    if [ -n "${line}" ]; then
     93                        echo "#: ${filepath}:${lineNumber}"                                       >> $outputPot
     94                        echo "msgid \"$(echo "${line}" | sed -e 's#\\#\\\\#g' -e 's#"#\\\"#g')\"" >> $outputPot
     95                        echo "msgstr \"\""                                                        >> $outputPot
     96                        echo ""                                                                   >> $outputPot
     97                    fi
     98                done < "$modFolderPath/${filepath}"
     99            done
     100
     101            msguniq $outputPot -o $outputPot
     102
     103        fi
     104
     105
     106        # Extract JSON strings.
     107        outputPot="$modFolderPath/localization/${modName}-json.pot"
     108        temporaryOutputPot="$modFolderPath/localization/${modName}-json-tmp.pot"
     109        temporaryOutputPot2="$modFolderPath/localization/${modName}-json-tmp2.pot"
     110        rm -rf $outputPot; touch $outputPot
     111        for filepathAndFilter in "${jsonFilesWithFilters[@]}"
     112        do
     113            filepathAndFilterArray=(${filepathAndFilter//::/ })
     114            json2po --duplicates=merge --filter=${filepathAndFilterArray[1]} "$modFolderPath/${filepathAndFilterArray[0]}" $temporaryOutputPot &> /dev/null
     115            sed "/msgctxt/d" -i $temporaryOutputPot
     116            perl -pe 'BEGIN{undef $/;} s/(#[^\n]*\n)+msgid ""\nmsgstr ""\n\n//smg' -i $temporaryOutputPot # Removes troublesome empty msgid entries.
     117            msgcat --use-first $outputPot $temporaryOutputPot > $temporaryOutputPot2
     118            perl -pe 'BEGIN{undef $/;} s/(#[^\n]*\n)+msgid "_: (\.[A-Za-z]+(\[[0-9]\])?)+\\n"\nmsgstr ""\n\n//smg' -i $temporaryOutputPot2 # Removes some weird entries generated from empty JSON entries.
     119            rm $temporaryOutputPot
     120            mv $temporaryOutputPot2 $outputPot
     121        done
     122
     123
     124        # Extract Entity (XML) strings.
     125        outputPot="$rootdir/$modFolderPath/localization/${modName}-entities-xml.pot"
     126        pushd $modFolderPath &> /dev/null
     127        itstool -o ${outputPot} -i "$basedir/entities.its" ${entityFiles[@]}
     128        sed -e "s/\&amp;/\&/g" -i ${outputPot}
     129        popd &> /dev/null
     130
     131
     132        # Extract JSON strings within XML files.
     133        outputPot="$modFolderPath/localization/${modName}-xml-json.pot"
     134        temporaryOutputJson="$modFolderPath/localization/${modName}-xml-json-tmp.json"
     135        temporaryOutputPot="$modFolderPath/localization/${modName}-xml-json-tmp.pot"
     136        temporaryOutputPot2="$modFolderPath/localization/${modName}-xml-json-tmp2.pot"
     137        rm -rf $outputPot; touch $outputPot
     138        for filepathAndFilter in "${xmlFilesWithJsonWithFilters[@]}"
     139        do
     140            declare -a filepathAndElementAndFilterArray=()
     141            while IFS= read -d $'\t' -r item ; do
     142              filepathAndElementAndFilterArray+=("$item")
     143            done < <(printf "$(echo "$filepathAndFilter" | sed "s/::/\t/g")\t")
     144            xmlstarlet sel -t -v "//${filepathAndElementAndFilterArray[1]}" "$modFolderPath/${filepathAndElementAndFilterArray[0]}" > $temporaryOutputJson
     145            json2po --duplicates=merge --filter=${filepathAndElementAndFilterArray[2]} "$temporaryOutputJson" $temporaryOutputPot &> /dev/null
     146            sed "/msgctxt/d" -i $temporaryOutputPot
     147            perl -pe 'BEGIN{undef $/;} s/(#[^\n]*\n)+msgid ""\nmsgstr ""\n\n//smg' -i $temporaryOutputPot # Removes troublesome empty msgid entries.
     148            msgcat --use-first $outputPot $temporaryOutputPot > $temporaryOutputPot2
     149            perl -pe 'BEGIN{undef $/;} s/(#[^\n]*\n)+msgid "_: (\.[A-Za-z]+(\[[0-9]\])?)+\\n"\nmsgstr ""\n\n//smg' -i $temporaryOutputPot2 # Removes some weird entries generated from empty JSON entries.
     150            rm $temporaryOutputJson
     151            rm $temporaryOutputPot
     152            mv $temporaryOutputPot2 $outputPot
     153            unset filepathAndElementAndFilterArray
     154        done
     155
     156
     157        # Unset data from messages.sh, so that it doesn’t affect the parsing of other mods.
     158        unset txtLineByLineFiles
     159        unset jsonFilesWithFilters
     160        unset entityFiles
     161        unset xmlFilesWithJsonWithFilters
     162
     163
     164        # Merge POT files into a single one.
     165        # NOTE: Depending on how much they grow, we might want to consider leaving them split in the future.
     166
     167        temporalPot="$modFolderPath/localization/${modName}.pot.tmp"
     168        finalPot="$modFolderPath/localization/${modName}.pot"
     169        rm ${finalPot} # Remove old POT.
     170        msgcat $modFolderPath/localization/*.pot > ${temporalPot} # Mix new POT files into a single POT file.
     171        msguniq ${temporalPot} -o ${temporalPot}
     172        rm $modFolderPath/localization/*.pot # Remove all POT files but the one that cointains the rest.
     173        mv ${temporalPot} ${finalPot} # Give the right name to the main POT file.
     174
     175    fi # [ -d "$modFolderPath/localization" ]
     176
     177done
     178
     179popd &> /dev/null
     180
     181
     182