Iavra

Iavra Localization - Core

Dec 24th, 2015 (edited)
5,234
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*:
  2.  * @plugindesc The core plugin for localization, which contains all logic to load text and convert escape codes.
  3.  * <Iavra Localization Core>
  4.  * @author Iavra
  5.  *
  6.  * @param Escape Code
  7.  * @desc Code, that will be used to retrieve localized strings. "{key}" serves as placeholder for the text key.
  8.  * @default #{{key}}
  9.  *
  10.  * @param Languages
  11.  * @desc Comma-separated list of languages, that should be supported. The first entry will be used as the default.
  12.  * @default en
  13.  *
  14.  * @param File Path
  15.  * @desc Path to the language files to load. The sequence "{lang}" will be replaced with the languages entered above.
  16.  * @default {lang}.json
  17.  *
  18.  * @help
  19.  * To setup this plugin, register all supported languages inside the "Languages" parameter, separated by commas. You
  20.  * also need to place the corresponding files inside your project folder. So, for example, if you want to support the
  21.  * languages "en" (english) and "de" (german) and the parameter "File Path" is set to its default value, you'll need
  22.  * to add the two files "de.json" and "en.json" to the root folder of your project.
  23.  *
  24.  * The first language will automatically used as the default langage.
  25.  *
  26.  * During runtime, every instance of "#{...}" (can be changed via plugin parameter "Escape Code") will get replaced
  27.  * with a localized string, where "..." stands for a text key. So, if your language file looks like this:
  28.  *
  29.  * {
  30.  *     "text.test": "This is a test text"
  31.  * }
  32.  *
  33.  * Every instance of "#{text.test}" will be replaced with "This is a test text". For better maintainability, it's also
  34.  * possible to split keys at dots:
  35.  *
  36.  * {
  37.  *     "text": {
  38.  *         "test": "This is a test text"
  39.  *     }
  40.  * }
  41.  *
  42.  * The above example will result in the same key as the first one, but makes it easier to construct nested keys, for
  43.  * example for the names of all actors. Instead of objects, you can also use arrays, like this:
  44.  *
  45.  * {
  46.  *     "text": ["Text 1", "Text 2", "Text 3"]
  47.  * }
  48.  *
  49.  * This will create the keys "text.0", "text.1" and "text.2", each pointing to the text listed at that array index. If
  50.  * needed, you are free to combine the object and array notations.
  51.  *
  52.  * Keys can also contain keys themselves, which will be replaced recursively. This allows you, to define important
  53.  * strings, like city names, at a single position and reference it everywhere else:
  54.  *
  55.  * {
  56.  *     "text": "Hi, my name is #{actor}",
  57.  *     "actor": "Harold"
  58.  * }
  59.  *
  60.  * You can use all escape characters, like "\V[...]" inside the files, but will need to double the backslashes. Line
  61.  * breaks can be entered by using "\n", since JSON doesn't support real linebreaks inside strings.
  62.  *
  63.  * The plugin offers the following script calls:
  64.  *
  65.  * IAVRA.I18N.language;         Returns the current language.
  66.  * IAVRA.I18N.language = lang;  Sets the current language to lang.
  67.  * IAVRA.I18N.languages();      Returns a list of all available languages.
  68.  * IAVRA.I18N.localize(text);   Localizes the given text.
  69.  */
  70.  
  71.  var IAVRA = IAVRA || {};
  72.  
  73. (function($) {
  74.     "use strict";
  75.  
  76.     /**
  77.      * Read plugin parameters independent from the plugin's actual filename.
  78.      */
  79.     var _p = $plugins.filter(function(p) { return p.description.contains('<Iavra Localization Core>'); })[0].parameters;
  80.     var _param_escape = _p['Escape Code'];
  81.     var _param_languages = _p['Languages'].split(/\s*,\s*/).filter(function(lang) { return !!lang; });
  82.     var _param_filePath = _p['File Path'];
  83.  
  84.     /**
  85.      * Regex used to replace escape codes with localized data.
  86.      */
  87.     var _regex = new RegExp(_param_escape.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&").replace('\\{key\\}', '([\\w\\.]+?)'), 'g');
  88.  
  89.     /**
  90.      * The current language.
  91.      */
  92.     var _lang = _param_languages[0];
  93.  
  94.     /**
  95.      * Contains all localized data read from the data files defined "File Path". We intentionally don't use the object
  96.      * literal {}, but create a new Object with a null prototype, so we don't inherit any properties.
  97.      */
  98.     var _data = _param_languages.reduce(function(map, lang) { map[lang] = null; return map; }, Object.create(null));
  99.  
  100.     /**
  101.      * Indicated, whether we are currently inside of "drawTextEx". Not thread-/async-safe!
  102.      */
  103.     var _inDrawTextEx = false;
  104.  
  105.     /**
  106.      * Initializes the language data by reading all files and storing their content inside the _data object. The
  107.      * undefined data serves as fallback, if no language is given in the "Languages" plugin parameter.
  108.      */
  109.     var _initialize = function() {
  110.         _param_languages.forEach(function(lang) {
  111.             _loadFile(_param_filePath.replace('{lang}', lang), function(data) { _data[lang] = _flatten(data); });
  112.         });
  113.         _data[undefined] = {};
  114.     };
  115.  
  116.     /**
  117.      * Loads a JSON file and executes the given callback with the parsed file contents as a parameter.
  118.      */
  119.     var _loadFile = function(url, callback) {
  120.         var request = new XMLHttpRequest();
  121.         request.open('GET', url);
  122.         request.overrideMimeType('application/json');
  123.         request.onload = function() { callback(JSON.parse(request.responseText)); };
  124.         request.onerror = function() { throw new Error("There was an error loading the file '" + url + "'."); };
  125.         request.send();
  126.     };
  127.  
  128.     /**
  129.      * Flattens the given data object by joining nested object keys and array indizes with "." and returns a single-
  130.      * level object, whose keys can be used in escape codes.
  131.      */
  132.     var _flatten = function(object) {
  133.         var result = {}, temp, value;
  134.         for(var key in object) {
  135.             if(typeof (value = object[key]) === 'object') {
  136.                 temp = _flatten(value);
  137.                 for(var x in temp) { result[key + '.' + x] = temp[x]; }
  138.             } else { result[key] = value; }
  139.         }
  140.         return result;
  141.     };
  142.  
  143.     /**
  144.      * Returns true, if all language files have been loaded.
  145.      */
  146.     var _isReady = function() { return _param_languages.every(function(lang) { return !!_data[lang]; }); };
  147.  
  148.     /**
  149.      * Recursively replaces all escape codes in the text with the localized data. Note, that we also do an implicit
  150.      * conversion of the given data to String, since otherwise the function would crash, when invoked with a number.
  151.      */
  152.     var _replace = function(text) {
  153.         if(text === undefined || text === null) { return text; }
  154.         var f = true, d = _data[_lang], r = '' + text;
  155.         while(f) { f = false; r = r.replace(_regex, function(m, k) { f = true; return d[k]; }); }
  156.         return r;
  157.     };
  158.  
  159.     //=============================================================================
  160.     // IAVRA.I18N
  161.     //=============================================================================
  162.  
  163.     $.I18N = {
  164.         /**
  165.          * Localizes a given text. Can be used, when the automatic localization happens too late (or not at all):
  166.          */
  167.         localize: function(text) { return _replace(text); },
  168.  
  169.         /**
  170.          * Returns the list of all registered languages. Can be used to create an option menu or similar.
  171.          */
  172.         languages: function() { return _param_languages; }
  173.     };
  174.  
  175.     /**
  176.      * Property used to read and set the current language. If the given value wasn't registered in the "Languages"
  177.      * plugin parameter, fallback to the first language, instead. Also, when changing the language, we need to update
  178.      * the document title, since the game title might be localized.
  179.      */
  180.     Object.defineProperty($.I18N, 'language', {
  181.         get: function() { return _lang; },
  182.         set: function(value) {
  183.             _lang = _param_languages.contains(value) ? value : _param_languages[0];
  184.             Scene_Boot.prototype.updateDocumentTitle();
  185.         }
  186.     });
  187.  
  188.     //=============================================================================
  189.     // Scene_Boot
  190.     //=============================================================================
  191.  
  192.     (function($) {
  193.  
  194.         /**
  195.          * When creating Scene_Boot, also start loading all language files to initialize the plugin.
  196.          */
  197.         var alias_create = $.prototype.create;
  198.         $.prototype.create = function() { alias_create.call(this); _initialize(); };
  199.  
  200.         /**
  201.          * Also wait, until all language data has been read.
  202.          */
  203.         var alias_isReady = $.prototype.isReady;
  204.         $.prototype.isReady = function() { return _isReady() && alias_isReady.call(this); };
  205.  
  206.         /**
  207.          * We override this method, because we may have to convert the game title, before setting the document title.
  208.          * Make sure, that $dataSystem is already initialized, because an option menu will set the current language,
  209.          * before data has been loaded. But don't worry, the correct title will be set during Scene_Boot.start.
  210.          */
  211.         $.prototype.updateDocumentTitle = function() {
  212.             if($dataSystem) { document.title = _replace($dataSystem.gameTitle); }
  213.         };
  214.  
  215.     })(Scene_Boot);
  216.  
  217.     //=============================================================================
  218.     // Window_Base
  219.     //=============================================================================
  220.  
  221.     (function($) {
  222.  
  223.         /**
  224.          * Set a marker indicating, that we are currently inside drawTextEx. This marker is not threadsafe, so the
  225.          * plugin likely won't work with other plugins, that are asynchronously drawing text.
  226.          */
  227.         var alias_drawTextEx = $.prototype.drawTextEx;
  228.         $.prototype.drawTextEx = function(text, x, y) {
  229.             _inDrawTextEx = true;
  230.             var result = alias_drawTextEx.call(this, text, x, y);
  231.             _inDrawTextEx = false;
  232.             return result;
  233.         };
  234.  
  235.         /**
  236.          * Replaces all escape codes, before AND after converting escape characters. We have to do this twice, because
  237.          * otherwise we would miss escape codes in character names, that have been added via escape characters.
  238.          */
  239.         var alias_convertEscapeCharacters = $.prototype.convertEscapeCharacters;
  240.         $.prototype.convertEscapeCharacters = function(text) {
  241.             return _replace(alias_convertEscapeCharacters.call(this, _replace(text)));
  242.         };
  243.  
  244.     })(Window_Base);
  245.  
  246.     //=============================================================================
  247.     // Bitmap
  248.     //=============================================================================
  249.  
  250.     (function($) {
  251.  
  252.         /**
  253.          * When drawing text, replace escape codes unless we are currently inside drawTextEx. Without this check, we
  254.          * would effectively call our replace method for every single character of the text given to drawTextEx.
  255.          */
  256.         var alias_drawText = $.prototype.drawText;
  257.         $.prototype.drawText = function(text, x, y, maxWidth, lineHeight, align) {
  258.             alias_drawText.call(this, _inDrawTextEx ? text : _replace(text), x, y, maxWidth, lineHeight, align);
  259.         };
  260.  
  261.         /**
  262.          * Measuring text is done, before drawing it, so escape codes won't be resolved, yet.
  263.          */
  264.         var alias_measureTextWidth = $.prototype.measureTextWidth;
  265.         $.prototype.measureTextWidth = function(text) {
  266.             return alias_measureTextWidth.call(this, _replace(text));
  267.         };
  268.  
  269.     })(Bitmap);
  270.  
  271.     //=============================================================================
  272.     // String
  273.     //=============================================================================
  274.  
  275.     (function($) {
  276.  
  277.         /**
  278.          * Needs to be aliased, so the state and skill messages (among others) works correctly.
  279.          */
  280.         var alias_format = $.prototype.format;
  281.         $.prototype.format = function() {
  282.             return alias_format.apply(_replace(this), arguments);
  283.         };
  284.  
  285.     })(String);
  286.  
  287. })(IAVRA);
Add Comment
Please, Sign In to add comment