Viewing File: /usr/local/cpanel/base/cjt/locale.js

/* eslint camelcase: 0 */
/* eslint guard-for-in: 0 */

(function(window) {
    "use strict";

    if (!window.CPANEL) {
        window.CPANEL = {};
    }

    var CPANEL = window.CPANEL;

    var DEFAULT_ELLIPSIS = {
        initial: "…{0}",
        medial: "{0}…{1}",
        "final": "{0}…",
    };

    var html_apos = "'".html_encode();
    var html_quot = "\"".html_encode();
    var html_amp  = "&".html_encode();
    var html_lt   = "<".html_encode();
    var html_gt   = ">".html_encode();

    // JS getUTCDay() starts from Sunday, but CLDR starts from Monday.
    var get_cldr_day = function(the_date) {
        var num = the_date.getUTCDay() - 1;
        return (num < 0) ? 6 : num;
    };

    var Locale = function() {};
    CPANEL.Locale = Locale;
    Locale._locales = {};
    Locale.add_locale = function(tag, construc) {
        Locale._locales[tag] = construc;
        construc.prototype._locale_tag = tag;
    };
    Locale.remove_locale = function(tag) { // For testing
        return delete Locale._locales[tag];
    };
    Locale.get_handle = function() {
        var cur_arg;
        var arg_count = arguments.length;
        for (var a = 0; a < arg_count; a++) {
            cur_arg = arguments[a];
            if (cur_arg in Locale._locales) {
                return new Locale._locales[cur_arg]();
            }
        }

        // We didn't find anything from the given arguments, so check _locales.
        // We can't trust JS's iteration order, so grab keys and take the first one.
        var loc = Object.keys(Locale._locales).min();

        return loc ? new Locale._locales[loc]() : new Locale();
    };

    // ymd_string_to_date will be smarter once case 52389 is done.
    // For now, we need the ymd order from the server.
    CPANEL.Locale.ymd = null;
    CPANEL.Locale.ymd_string_to_date = function(str) {
        var str_split = str.split(/\D+/);
        var ymd = this.ymd || "mdy"; // U.S. English;

        var day = str_split[ymd.indexOf("d")];
        var month = str_split[ymd.indexOf("m")];
        var year = str_split[ymd.indexOf("y")];

        // It seems unlikely that we'd care about ancient times.
        if (year && (year.length < 4)) {
            var deficit = 4 - year.length;
            year = String((new Date()).getFullYear()).substr(0, deficit) + year;
        }

        var date = new Date(year, month - 1, day);
        return isNaN(date.getTime()) ? undefined : date;
    };

    // temporary, until case 52389 is in
    CPANEL.Locale.date_template = null;
    Date.prototype.to_ymd_string = function() {
        var date = this;

        var template = CPANEL.Locale.date_template || "{month}/{day}/{year}"; // U.S. English
        return template.replace(/\{(?:month|day|year)\}/g, function(subst) {
            switch (subst) {
                case "{day}":
                    return date.getDate();
                case "{month}":
                    return date.getMonth() + 1;
                case "{year}":
                    return date.getFullYear();
            }
        });
    };

    var bracket_re = /([^~\[\]]+|~.|\[|\]|~)/g;

    // cf. Locale::Maketext re DEL
    var faux_comma = "\x07";
    var faux_comma_re = new RegExp(faux_comma, "g");

    // For outside a bracket group
    var tilde_chars = {
        "[": 1,
        "]": 1,
        "~": 1,
    };

    var underscore_digit_re = /^_(\d+)$/;

    var func_substitutions = {
        "#": "numf",
        "*": "quant",
    };

    // NOTE: There is no widely accepted consensus of exactly how to measure data
    // and which units to use for it.
    // For example, some bodies define "B" to mean bytes, while others don't.
    // (NB: SI defines "B" to mean bels.) Some folks use k for kilo; others use K.
    // Some say kilo should be 1,024; others say it's 1,000 (and "kibi" would be
    // 1,024). What we do here is at least in longstanding use at cPanel.
    var data_abbreviations = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];

    // NOTE: args *must* be a list, not an Array object (as is permissible with
    // most other functions in this module).
    var _maketext = function(str /* , args list */ ) { // ## no extract maketext
        if (!str) {
            return;
        }

        str = this.LEXICON && this.LEXICON[str] || str;

        if (str.indexOf("[") === -1) {
            return String(str);
        }

        var assembled = [];

        var pieces = str.match(bracket_re);
        var pieces_length = pieces.length;

        var in_group = false;
        var bracket_args = "";

        var p, cur_p, a;

        PIECE: for (p = 0; p < pieces_length; p++) {
            cur_p = pieces[p];
            if ((cur_p === "[")) {
                if (in_group) {
                    throw "Invalid maketext string: " + str; // ## no extract maketext
                }

                in_group = true;
            } else if (cur_p === "]") {

                if (!in_group || !bracket_args) {
                    throw "Invalid maketext string: " + str; // ## no extract maketext
                }

                in_group = false;

                var real_args = bracket_args.split(",");
                var len = real_args.length;
                var func;

                if (len === 1) {
                    var arg = real_args[0].match(underscore_digit_re);
                    if (!arg) {
                        throw "Invalid maketext string: " + str; // ## no extract maketext
                    }
                    var looked_up = arguments[arg[1]];
                    if (typeof looked_up === "undefined") {
                        throw "Invalid argument \"" + arg[1] + "\" passed to maketext string: " + str; // ## no extract maketext
                    } else {
                        bracket_args = "";
                        assembled.push(looked_up);
                        continue PIECE;
                    }
                } else {
                    func = real_args.shift();
                    len -= 1;
                    func = func_substitutions[func] || func;

                    if (typeof this[func] !== "function") {
                        throw "Invalid function \"" + func + "\" in maketext string: " + str; // ## no extract maketext
                    }
                }

                if (bracket_args.indexOf(faux_comma) !== -1) {
                    for (a = 0; a < len; a++) {
                        real_args[a] = real_args[a].replace(faux_comma_re, ",");
                    }
                }

                var cur_arg, alen;

                for (a = 0; a < len; a++) {
                    cur_arg = real_args[a];
                    if (cur_arg.charAt(0) === "_") {
                        if (cur_arg === "_*") {
                            real_args.splice(a, 1);
                            for (a = 1, alen = arguments.length; a < alen; a++) {
                                real_args.push(arguments[a]);
                            }
                        } else {
                            var arg_num = cur_arg.match(underscore_digit_re);
                            if (arg_num) {
                                if (arg_num[1] in arguments) {
                                    real_args[a] = arguments[arg_num[1]];
                                } else {
                                    throw "Invalid variable \"" + arg_num[1] + "\" in maketext string: " + str; // ## no extract maketext
                                }
                            } else {
                                throw "Invalid maketext string: " + str; // ## no extract maketext
                            }
                        }
                    }
                }

                bracket_args = "";
                assembled.push(this[func].apply(this, real_args));
            } else if (cur_p.charAt(0) === "~") {
                var real_char = cur_p.charAt(1) || "~";
                if (in_group) {
                    if (real_char === ",") {
                        bracket_args += faux_comma;
                    } else {
                        bracket_args += real_char;
                    }
                } else if (real_char in tilde_chars) {
                    assembled.push(real_char);
                } else {
                    assembled.push(cur_p);
                }
            } else if (in_group) {
                bracket_args += cur_p;
            } else {
                assembled.push(cur_p);
            }
        }

        if (in_group) {
            throw "Invalid maketext string: " + str; // ## no extract maketext
        }

        return assembled.join("");
    };

    // Do this without YUI so testing via node.js is easier.
    // Most of what is here ports functionality from CPAN Locale::Maketext::Utils.
    var prototype_stuff = {
        LEXICON: (typeof window === "undefined") ?
            global.LEXICON || (global.LEXICON = {}) :
            window.LEXICON || (window.LEXICON = {}),

        /**
         * Use this method to localize a static string. These strings are harvested normally.
         *
         * @method maketext                                                                                         // ## no extract maketext
         * @param {String} template Template to process.
         * @param {...*}   [args]   Optional replacement arguments for the template.
         * @return {String}
         */
        maketext: _maketext, // ## no extract maketext

        /**
         * Like maketext() but does not lookup the phrase in the lexicon and compiles the phrase exactly as given.  // ## no extract maketext
         *
         * @note  In the current implementation this works just like maketext, but will need to be modified once we // ## no extract maketext
         * start doing lexicon lookups.
         *
         * @method maketext                                                                                         // ## no extract maketext
         * @param {String} template Template to process.
         * @param {...*}   [args]   Optional replacement arguments for the template.
         * @return {String}
         */
        makethis: _maketext,                                                                                       // ## no extract maketext

        /**
         * Use this method instead of maketext if you are passing a variable that contains the maketext template.   // ## no extract maketext
         *
         * @method makevar
         * @param {String} template Template to process.
         * @param {...*}   [args]   Optional replacement arguments for the template.
         * @return {String}
         * @example
         *
         * var translatable = LOCALE.translatable;                                                                  // ## no extract maketext
         * var template = translatable("What is this [numf,_1] thing.");                                            // ## no extract maketext
         * ...
         * var localized = LOCALE.makevar(template)
         *
         * or
         *
         * var template = LOCALE.translatable("What is this [numf,_1] thing.");                                     // ## no extract maketext
         * ...
         * var localized = LOCALE.makevar(template)
         */
        makevar: _maketext, // this is a marker method that is ignored in phrase harvesting, but is functionally equivalent to maketext otherwise. // ## no extract maketext

        /**
         * Marks the phrase as translatable for the harvester.                                                      // ## no extract maketext
         *
         * @method translatable                                                                                     // ## no extract maketext
         * @param  {String} str Translatable string
         * @return {Strung}     Same string, this is just a marker function for the harvester
         */
        translatable: function(str) { // ## no extract maketext
            return str;
        },

        _locale_tag: null,

        get_language_tag: function() {
            return this._locale_tag;
        },

        // These methods are locale-independent and should not need overrides.
        join: function(sep, list) {
            sep = String(sep);

            if (typeof list === "object") {
                return list.join(sep);
            } else {
                var str = String(arguments[1]);
                for (var a = 2; a < arguments.length; a++) {
                    str += sep + arguments[a];
                }
                return str;
            }
        },

        // Perl has undef, but JavaScript has both null *and* undefined.
        // Let's treat null as undefined since JSON doesn't know what
        // undefined is, so serializers use null instead.
        "boolean": function(condition, when_true, when_false, when_null) {
            if (condition) {
                return "" + when_true;
            }

            if (((arguments.length > 3) && (condition === null || condition === undefined))) {
                return "" + when_null;
            }

            return "" + when_false;
        },

        comment: function() {
            return "";
        },

        // A "dispatch" function for the output_* methods below.
        output: function(sub_func, str) {
            var that = this;

            var sub_args = Array.prototype.concat.apply([], arguments).slice(1);

            // Implementation of the chr() and amp() embeddable methods
            if (sub_args && typeof sub_args[0] === "string") {
                sub_args[0] = sub_args[0].replace(/chr\((\d+|\S)\)/g, function(str, p1) {
                    return that.output_chr(p1);
                });
                sub_args[0] = sub_args[0].replace(/amp\(\)/g, function(str) {
                    return that.output_amp();
                });
            }

            if (typeof this["output_" + sub_func] === "function") {
                return this["output_" + sub_func].apply(this, sub_args);
            } else {
                if (window.console) {
                    window.console.warn("Locale output function \"" + sub_func + "\" is not implemented.");
                }
                return str;
            }
        },

        output_apos: function() {
            return html_apos;
        },
        output_quot: function() {
            return html_quot;
        },

        // TODO: Implement embeddable methods described at
        // https://metacpan.org/pod/Locale::Maketext::Utils#asis()
        output_asis: String,
        asis: String,

        output_underline: function(str) {
            return "<u>" + str + "</u>";
        },
        output_strong: function(str) {
            return "<strong>" + str + "</strong>";
        },
        output_em: function(str) {
            return "<em>" + str + "</em>";
        },

        output_abbr: function(abbr, full) {
            return "<abbr title=\"__FULL__\">".replace(/__FULL__/, full) + abbr + "</abbr>";
        },

        output_acronym: function(abbr, full) {
            return this.output_abbr(abbr, full).replace(/^(<[a-z]+)/i, "$1 class=\"initialism\"");
        },

        output_class: function(str) {
            var cls = Array.prototype.slice.call(arguments, 1);

            return "<span class=\"" + cls.join(" ") + "\">" + str + "</span>";
        },
        output_chr: function(num) {
            return isNaN(+num) ? String(num) : String.fromCharCode(num).html_encode();
        },
        output_amp: function() {
            return html_amp;
        },
        output_lt: function() {
            return html_lt;
        },
        output_gt: function() {
            return html_gt;
        },

        // Multiple forms possible:
        //  A) output_url( dest, text, [ config_obj ] )
        //  B) output_url( dest, text, [ key1, val1, [...] ] )
        //  C) output_url( dest, [ config_obj ] )
        //  D) output_url( dest, [ key1, val1, [...] ] )
        output_url: function(dest) {
            var
                args_length = arguments.length,
                config = arguments[args_length - 1],
                text,
                key,
                value,
                start_i,
                a,
                len;

            // object properties hash, form A or C
            if (typeof config === "object") {
                text = (args_length === 3) ? arguments[1] : (config.html || dest);

                // Go ahead and clobber other stuff.
                if ("_type" in config && config._type === "offsite") {
                    config["class"] = "offsite";
                    config.target = "_blank";
                    delete config._type;
                }
            } else {
                config = {};

                if (args_length % 2) {
                    start_i = 1;
                } else {
                    text = arguments[1];
                    start_i = 2;
                }
                a = start_i;
                len = arguments.length;
                while (a < len) {
                    key = arguments[a];
                    value = arguments[++a];
                    if (key === "_type" && value === "offsite") {
                        config.target = "_blank";
                        config["class"] = "offsite";
                    } else {
                        config[key] = value;
                    }
                    a++;
                }

                if (!text) {
                    text = config.html || dest;
                }
            }

            var html = "<a href=\"" + dest + "\"";
            if (typeof config === "object") {
                for (key in config) {
                    html += " " + key + "=\"" + config[key] + "\"";
                }
            }
            html += ">" + text + "</a>";

            return html;
        },


        // Flattening argument lists in JS is much hairier than in Perl,
        // so this doesn't flatten array objects. Hopefully CLDR will soon
        // implement list_or; then we could deprecate this function.
        // cf. http://unicode.org/cldr/trac/ticket/4051
        list_separator: ", ",
        oxford_separator: ",",
        list_default_and: "&",
        list: function(word /* , [foo,bar,...] | foo, bar, ... */ ) {
            if (!word) {
                word = this.list_default_and;
            } // copying our Perl
            var list_sep = this.list_separator;
            var oxford_sep = this.oxford_separator;

            var the_list;
            if (typeof arguments[1] === "object" && arguments[1] instanceof Array) {
                the_list = arguments[1];
            } else {
                the_list = Array.prototype.concat.apply([], arguments).slice(1);
            }

            var len = the_list.length;

            if (!len) {
                return "";
            }

            if (len === 1) {
                return String(the_list[0]);
            } else if (len === 2) {
                return (the_list[0] + " " + word + " " + the_list[1]);
            } else {

                // Use slice() here to avoid altering the array
                // since it may have been passed in as an object.
                return (the_list.slice(0, -1).join(list_sep) + [oxford_sep, word, the_list.slice(-1)].join(" "));
            }
        },


        // This depends on locale-specific overrides of base functionality
        // but should not itself need an override.
        format_bytes: function(bytes, decimal_places) {
            if (decimal_places === undefined) {
                decimal_places = 2;
            }
            bytes = Number(bytes);
            var exponent = bytes && Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), data_abbreviations.length);
            if (!exponent) {

                // This is a special, internal-to-format_bytes, phrase: developers will not have to deal with this phrase directly.
                return this.maketext("[quant,_1,%s byte,%s bytes]", bytes); // the space between the '%s' and the 'b' is a non-break space (e.g. option-spacebar, not spacebar) // ## no extract maketext
                // We do not use &nbsp; or \u00a0 since:
                //   * parsers would need to know how to interpolate them in order to work with the phrase in the context of the system
                //   * the non-breaking space character behaves as you'd expect its various representations to.
                // Should a second instance of this sort of thing happen we can revisit the idea of adding [comment] in the phrase itself or perhaps supporting an embedded call to [output,nbsp].
            } else {

                // We use \u00a0 here because it won't affect lookup since it is not
                // being used in a source phrase and we don't want to worry about
                // whether an entity is going to be interpreted or not.
                return this.numf(bytes / Math.pow(1024, exponent), decimal_places) + "\u00a0" + data_abbreviations[exponent - 1];
            }
        },

        // CLDR-informed functions

        numerate: function(num) {
            if (this.get_plural_form) { // from CPAN Locales
                var numerated = this.get_plural_form.apply(this, arguments)[0];
                if (numerated === undefined) {
                    numerated = arguments[arguments.length - 1];
                }
                return numerated;
            } else { // English-language logic, in the absence of CLDR
                // The -1 case here is debatable.
                // cf. http://unicode.org/cldr/trac/ticket/4049
                var abs = Math.abs(num);

                if (abs === 1) {
                    return "" + arguments[1];
                } else if (abs === 0) {
                    return "" + arguments[arguments.length - 1];
                } else {
                    return "" + arguments[2];
                }
            }
        },
        quant: function(num) {
            var numerated, is_special_zero, decimal_places = 3;

            if (num instanceof Array) {
                decimal_places = num[1];
                num = num[0];
            }

            if (this.get_plural_form) { // from CPAN Locales
                var gpf = this.get_plural_form.apply(this, arguments);
                numerated = gpf[0];

                // If there's a mismatch between the actual number of forms
                // (singular, plural, etc.) and the real number, this can be
                // undefined, which can break code.  We pick the rightmost, or
                // "most plural," form as a fallback.
                if (numerated === undefined) {
                    numerated = arguments[arguments.length - 1];
                }
                is_special_zero = gpf[1];
            } else { // no CLDR, fall back to English
                numerated = this.numerate.apply(this, arguments);

                // Check: num is 0, we gave a special_zero value, and that numerate() gave it
                is_special_zero = (parseInt(num, 10) === 0) &&
                    (arguments.length > 3) &&
                    (numerated === String(arguments[3]));
            }

            var formatted = this.numf(num, decimal_places);

            if (numerated.indexOf("%s") !== -1) {
                return numerated.replace(/%s/g, formatted);
            }

            if (is_special_zero) {
                return numerated;
            }

            return this.is_rtl() ? (numerated + " " + formatted) : (formatted + " " + numerated);
        },

        _max_decimal_places: 6,
        numf: function(num, decimal_places) {
            if (decimal_places === undefined) {
                decimal_places = this._max_decimal_places;
            }

            // exponential -> don't know how to deal
            if (/e/.test(num)) {
                return String(num);
            }

            var cldr, decimal_format, decimal_group, decimal_decimal;
            try {
                cldr = this.get_cldr("misc_info").cldr_formats;
                decimal_format = cldr.decimal;
                decimal_group = cldr._decimal_format_group;
                decimal_decimal = cldr._decimal_format_decimal;
            } catch (e) {}

            // No CLDR, so fall back to hard-coded English values.
            if (!decimal_format || !decimal_group || !decimal_decimal) {
                decimal_format = "#,##0.###";
                decimal_group = ",";
                decimal_decimal = ".";
            }

            var is_negative = num < 0;
            num = Math.abs(num);

            // trim the decimal part to 6 digits and round
            var whole = Math.floor(num);
            var normalized, fraction;
            if (/(?!')\.(?!')/.test(num)) {

                // This weirdness is necessary to avoid floating-point
                // errors that can crop up with large-ish numbers.

                // Convert to a simple fraction.
                fraction = String(num).replace(/^[^.]+/, "0");

                // Now round to the desired precision.
                fraction = Number(fraction).toFixed(decimal_places);

                // e.g., 1.9999 when only 3 decimal places are desired.
                if (/^1/.test(fraction)) {
                    whole++;
                    num = whole;
                    fraction = undefined;
                } else {
                    fraction = fraction.replace(/^.*\./, "").replace(/0+$/, "");
                }

                normalized = Number(whole + "." + fraction);
            } else {
                normalized = num;
            }

            var pattern_with_outside_symbols;
            if (/(?!');(?!')/.test(decimal_format)) {
                pattern_with_outside_symbols = decimal_format.split(/(?!');(?!')/)[is_negative ? 1 : 0];
            } else {
                pattern_with_outside_symbols = (is_negative ? "-" : "") + decimal_format;
            }
            var inner_pattern = pattern_with_outside_symbols.match(/[0#].*[0#]/)[0];

            // Applying the integer part of the pattern is much easier if it's
            // done with the strings reversed.

            var pattern_split = inner_pattern.split(/(?!')\.(?!')/);
            var int_pattern_split = pattern_split[0].reverse().split(/(?!'),(?!')/);

            // If there is only one part of the int pattern, then set the "joiner"
            // to empty string. (http://unicode.org/cldr/trac/ticket/4094)
            var group_joiner;
            if (int_pattern_split.length === 1) {
                group_joiner = "";
            } else {

                // Most patterns look like #,##0.###, for which the leftmost # is
                // just a placeholder so we know where to put the group separator.
                int_pattern_split.pop();
                group_joiner = decimal_group;
            }

            var whole_reverse = String(whole).split("").reverse();
            var whole_assembled = []; // reversed
            var pattern;
            var replacer = function(chr) {
                switch (chr) {
                    case "#":
                        return whole_reverse.shift() || "";
                    case "0":
                        return whole_reverse.shift() || "0";
                }
            };
            while (whole_reverse.length) {
                if (int_pattern_split.length) {
                    pattern = int_pattern_split.shift();
                }

                // Since this is reversed, we can just replace a character
                // at a time, in regular forward order. Make sure we leave quoted
                // stuff alone while paying attention to stuff *by* quoted stuff.
                var assemble_chunk = pattern
                    .replace(/(?!')[0#]|[0#](?!')/g, replacer)
                    .replace(/'([.,0#;¤%E])'$/, "")
                    .replace(/'([.,0#;¤%E])'/, "$1");

                whole_assembled.push(assemble_chunk);
            }

            var formatted_num = whole_assembled.join(group_joiner).reverse() + (fraction ? decimal_decimal + fraction : "");
            return pattern_with_outside_symbols.replace(/[0#].*[0#]/, formatted_num);
        },

        list_and: function() {
            return this._list_join_cldr("list", arguments);
        },

        list_or: function() {
            return this._list_join_cldr("list_or", arguments);
        },

        _list_join_cldr: function(templates_name, args) {
            var the_list;
            if ((typeof args[0] === "object") && args[0] instanceof Array) {
                the_list = args[0].slice(0); // do not edit the values outside of this function
            } else if ((typeof args[0] === "object")) {
                if (args[0] instanceof Array) {
                    the_list = args.slice(0); // do not edit the values outside of this function
                } else {
                    the_list = [args[0]]; // do not edit the values outside of this function
                }
            } else if (typeof args === "object" && args[0] !== undefined) {
                if (args[0] instanceof Array) {
                    the_list = args[0].slice(0); // do not edit the values outside of this function
                } else {
                    the_list = [];
                    for (var k in args) {
                        the_list[k] = args[k];
                    }
                }
            }

            if (the_list === undefined) {
                the_list = [""];
            }

            var cldr_list;
            var len = the_list.length;
            var pattern;
            var text;

            try {
                cldr_list = this.get_cldr("misc_info").cldr_formats[templates_name];
            } catch (e) {
                var conjunction = (templates_name === "list_or") ? "or" : "and";

                cldr_list = {
                    2: "{0} " + conjunction + " {1}",
                    start: "{0}, {1}",
                    middle: "{0}, {1}",
                    end: "{0}, " + conjunction + " {1}",
                };
            }

            var replacer = function(str, p1) {
                switch (p1) {
                    case "0":
                        return text;
                    case "1":
                        return the_list[i++];
                }
            };

            switch (len) {
                case 0:
                    return;
                case 1:
                    return String(the_list[0]);
                default:
                    if (len === 2) {
                        text = cldr_list["2"];
                    } else {
                        text = cldr_list.start;
                    }

                    text = text.replace(/\{([01])\}/g, function(all, bit) {
                        return the_list[bit];
                    });
                    if (len === 2) {
                        return text;
                    }

                    var i = 2;
                    while (i < len) {
                        pattern = cldr_list[(i === len - 1) ? "end" : "middle"];

                        text = pattern.replace(/\{([01])\}/g, replacer);
                    }

                    return text;
            }
        },

        list_and_quoted: function() {
            return this._list_quoted("list_and", arguments);
        },
        list_or_quoted: function() {
            return this._list_quoted("list_or", arguments);
        },

        // This *may* be useful publicly.
        _quote: function(str) {
            var delimiters;
            try {
                delimiters = this.get_cldr("misc_info").delimiters;
            } catch (e) {
                delimiters = {
                    quotation_start: "“",
                    quotation_end: "”",
                };
            }
            return delimiters["quotation_start"] + str + delimiters["quotation_end"];
        },

        _list_quoted: function(join_fn, args) {
            var the_list;
            if (typeof (args[0]) === "object") {
                if (args[0] instanceof Array) {

                    // slice() so that we don’t change the caller’s data
                    the_list = args[0].slice();
                } else {
                    throw ( "Unrecognized list_and_quoted() argument: " + args[0].toString() );
                }
            } else {
                the_list = Array.prototype.slice.apply(args);
            }

            // Emulate Locales.pm _quote_get_list_items() list_quote_mode 'all'.
            // list_or(), currently not implemented in JS (no reason for it not to be), will need to behave the same
            if (the_list === undefined || the_list.length === 0) {

                the_list = [""]; // disambiguate no args
            }

            var locale = this;
            return this[join_fn](the_list.map( function() {
                return locale._quote.apply(locale, arguments);
            } ) );
        },

        local_datetime: function(my_date, format_string) {
            if (!this._cldr) {
                return this.datetime.apply(this, arguments);
            }

            if (my_date instanceof Date) {
                my_date = new Date(my_date);
            } else if (/^-?\d+$/.test(my_date)) {
                my_date = new Date(my_date * 1000);
            } else {
                my_date = new Date();
            }

            var tz_offset = my_date.getTimezoneOffset();

            my_date.setMinutes(my_date.getMinutes() - tz_offset);

            var non_utc = this.datetime(my_date, format_string);

            // This is really hackish...but should be safe.
            if (non_utc.indexOf("UTC") > -1) {
                var hours = (tz_offset > 0) ? "-" : "+";
                hours += Math.floor(Math.abs(tz_offset) / 60).toString().lpad(2, "0");
                var minutes = (tz_offset % 60).toString().lpad(2, "0");
                non_utc = non_utc.replace("UTC", "GMT" + hours + minutes);
            }

            return non_utc;
        },

        // time can be either epoch seconds or a JS Date object
        // format_string can match the regexp below or be a [ date, time ] suffix pair
        // (e.g., [ "medium", "short" ] -> "Aug 30, 2011 5:12 PM")
        datetime: function datetime(my_date, format_string) {
            if (!my_date && (my_date !== 0)) {
                my_date = new Date();
            } else if (!(my_date instanceof Date)) {
                my_date = new Date(my_date * 1000);
            }

            var loc_strs = this.get_cldr("datetime");

            if (!loc_strs) {
                return my_date.toString();
            }

            if (format_string) {

                // Make sure we don't just grab any random CLDR datetime key.
                if (/^(?:date|time|datetime|special)_format_/.test(format_string)) {
                    format_string = loc_strs[format_string];
                }
            } else {
                format_string = loc_strs.date_format_long;
            }

            var substituter = function() {

                // Check for quoted strings
                if (arguments[1]) {
                    return arguments[1].substr( 1, arguments[1].length - 2 );
                }

                // No quoted string, eh? OK, let’s check for a known pattern.
                var key = arguments[2];
                var xformed = (function() {
                    switch (key) {
                        case "yy":
                            return Math.abs(my_date.getUTCFullYear()).toString().slice(-2);
                        case "y":
                        case "yyy":
                        case "yyyy":
                            return Math.abs(my_date.getUTCFullYear());
                        case "MMMMM":
                            return loc_strs.month_format_narrow[my_date.getUTCMonth()];
                        case "LLLLL":
                            return loc_strs.month_stand_alone_narrow[my_date.getUTCMonth()];
                        case "MMMM":
                            return loc_strs.month_format_wide[my_date.getUTCMonth()];
                        case "LLLL":
                            return loc_strs.month_stand_alone_wide[my_date.getUTCMonth()];
                        case "MMM":
                            return loc_strs.month_format_abbreviated[my_date.getUTCMonth()];
                        case "LLL":
                            return loc_strs.month_stand_alone_abbreviated[my_date.getUTCMonth()];
                        case "MM":
                        case "LL":
                            return (my_date.getUTCMonth() + 1).toString().lpad(2, "0");
                        case "M":
                        case "L":
                            return my_date.getUTCMonth() + 1;
                        case "EEEE":
                            return loc_strs.day_format_wide[ get_cldr_day(my_date) ];
                        case "EEE":
                        case "EE":
                        case "E":
                            return loc_strs.day_format_abbreviated[ get_cldr_day(my_date) ];
                        case "EEEEE":
                            return loc_strs.day_format_narrow[ get_cldr_day(my_date) ];
                        case "cccc":
                            return loc_strs.day_stand_alone_wide[ get_cldr_day(my_date) ];
                        case "ccc":
                        case "cc":
                        case "c":
                            return loc_strs.day_stand_alone_abbreviated[ get_cldr_day(my_date) ];
                        case "ccccc":
                            return loc_strs.day_stand_alone_narrow[ get_cldr_day(my_date) ];
                        case "dd":
                            return my_date.getUTCDate().toString().lpad(2, "0");
                        case "d":
                            return my_date.getUTCDate();
                        case "h":
                        case "hh":
                            var twelve_hours = my_date.getUTCHours();
                            if (twelve_hours > 12) {
                                twelve_hours -= 12;
                            }
                            if (twelve_hours === 0) {
                                twelve_hours = 12;
                            }
                            return (key === "hh") ? twelve_hours.toString().lpad(2, "0") : twelve_hours;
                        case "H":
                            return my_date.getUTCHours();
                        case "HH":
                            return my_date.getUTCHours().toString().lpad(2, "0");
                        case "m":
                            return my_date.getUTCMinutes();
                        case "mm":
                            return my_date.getUTCMinutes().toString().lpad(2, "0");
                        case "s":
                            return my_date.getUTCSeconds();
                        case "ss":
                            return my_date.getUTCSeconds().toString().lpad(2, "0");
                        case "a":
                            var hours = my_date.getUTCHours();
                            if (hours < 12) {
                                return loc_strs.am_pm_abbreviated[0];
                            } else if (hours > 12) {
                                return loc_strs.am_pm_abbreviated[1];
                            }

                            // CLDR defines "noon", but CPAN DateTime::Locale doesn't have it.
                            return loc_strs.am_pm_abbreviated[1];
                        case "z":
                        case "zzzz":
                        case "v":
                        case "vvvv":
                            return "UTC";
                        case "G":
                        case "GG":
                        case "GGG":
                            return loc_strs.era_abbreviated[my_date.getUTCFullYear() < 0 ? 0 : 1];
                        case "GGGGG":
                            return loc_strs.era_narrow[my_date.getUTCFullYear() < 0 ? 0 : 1];
                        case "GGGG":
                            return loc_strs.era_wide[my_date.getUTCFullYear() < 0 ? 0 : 1];
                    }

                    if (window.console) {
                        console.warn("Unknown CLDR date/time pattern: " + key + " (" + format_string + ")" );
                    }
                    return key;
                })();

                return xformed;
            };

            return format_string.replace(
                /('[^']+')|(([a-zA-Z])\3*)/g,
                substituter
            );
        },

        is_rtl: function() {
            try {
                return this.get_cldr("misc_info").orientation.characters === "right-to-left";
            } catch (e) {
                return false;
            }
        },

        /**
         * Shorten a string into one or two end fragments, using CLDR formatting.
         *
         * ex.: elide( "123456", 2 )    //"12…"
         * ex.: elide( "123456", 2, 2 ) //"12…56"
         * ex.: elide( "123456", 0, 2 ) //"…56"
         *
         * @param str     {String} The actual string to shorten.
         * @param start_length {Number} How many initial characters to put into the result.
         * @param end_length {Number} How many final characters to put into the result. (optional)
         * @return        {String} The processed string.
         */
        elide: function(str, start_length, end_length) {
            start_length = start_length || 0;
            end_length = end_length || 0;

            if (str.length <= (start_length + end_length)) {
                return str;
            }

            var template, substring0, substring1;
            if (start_length) {
                if (end_length) {
                    template = "medial";
                    substring0 = str.substr(0, start_length);
                    substring1 = str.substr(str.length - end_length);
                } else {
                    template = "final";
                    substring0 = str.substr(0, start_length);
                }
            } else if (end_length) {
                template = "initial";
                substring0 = str.substr(str.length - end_length);
            } else {
                return "";
            }

            try {
                template = this._cldr.misc_info.cldr_formats.ellipsis[template]; // JS reserved word
            } catch (e) {
                template = DEFAULT_ELLIPSIS[template];
            }

            if (substring1) { // medial
                return template
                    .replace("{0}", substring0)
                    .replace("{1}", substring1);
            }

            return template.replace("{0}", substring0);
        },

        get_first_day_of_week: function() {
            var fd = Number(this.get_cldr("datetime").first_day_of_week) + 1;
            return (fd === 8) ? 0 : fd;
        },

        set_cldr: function(cldr) {
            var cldr_obj = this._cldr;
            if (!cldr_obj) {
                cldr_obj = this._cldr = {};
            }
            for (var key in cldr) {
                cldr_obj[key] = cldr[key];
            }
        },

        get_cldr: function(key) {
            if (!this._cldr) {
                return;
            }

            if ((typeof key === "object") && (key instanceof Array)) {
                return key.map(this.get_cldr, this);
            } else {
                return key ? this._cldr[key] : this._cldr;
            }
        },

        // For testing. Don't "delete" since this will cause prototype traversal.
        reset_cldr: function() {
            this._cldr = undefined;
        },

        _cldr: null,
    };

    /**
     * Generate a new locale from the various CLDR data passed in.
     *
     * @param  {String} tag            Locale tag name.
     * @param  {Object} functionsMixin Collection of functions to mix into the locale class passed from the CLDR data.
     * @param  {Object} dateTimeInfo   Datetime specific formatting information for the locale
     * @param  {Object} miscInfo       Miscellaneous formatting information for the locale.
     * @return {Object}                Reference to the locale just added.
     */
    CPANEL.Locale.generateClassFromCldr = function(tag, functionsMixin, dateTimeInfo, miscInfo) {
        if (CPANEL.Locale._locales[tag]) {

            // Already generated
            return Locale._locales[tag];
        }

        // Create a custom class for the locale generated from the CLDR data.
        var GeneratedLocale = function() {
            GeneratedLocale.superclass.constructor.apply(this, arguments);
            this.set_cldr( { datetime: dateTimeInfo } );
            this.set_cldr( { misc_info: miscInfo } );
        };

        // Mix in the base Locale and the CLDR locale functions into the new class.
        YAHOO.lang.extend( GeneratedLocale, CPANEL.Locale, functionsMixin );

        // Add the new locale class to the collection
        Locale.add_locale(tag, GeneratedLocale);

        // Update the locale handle since this is the most likey use case
        window.LOCALE = CPANEL.Locale.get_handle();

        return CPANEL.Locale._locales[tag];
    };


    for (var key in prototype_stuff) {
        Locale.prototype[key] = prototype_stuff[key];
    }

    // This will be overwritten in minified code.
    window.LOCALE = Locale.get_handle();

})(window);
Back to Directory File Manager