Source

LocaleInfo.js

/*
 * LocaleInfo.js - Encode locale-specific defaults
 *
 * Copyright © 2012-2015, 2018, 2021 JEDLSoft
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// !data localeinfo

var ilib = require("../index.js");
var Utils = require("./Utils.js");
var Locale = require("./Locale.js");
var LocaleMatcher = require("./LocaleMatcher.js");

/**
 * @class
 * Create a new locale info instance. Locale info instances give information about
 * the default settings for a particular locale. These settings may be overridden
 * by various parts of the code, and should be used as a fall-back setting of last
 * resort. <p>
 *
 * The optional options object holds extra parameters if they are necessary. The
 * current list of supported options are:
 *
 * <ul>
 * <li><i>onLoad</i> - a callback function to call when the locale info object is fully
 * loaded. When the onLoad option is given, the localeinfo object will attempt to
 * load any missing locale data using the ilib loader callback.
 * When the constructor is done (even if the data is already preassembled), the
 * onLoad function is called with the current instance as a parameter, so this
 * callback can be used with preassembled or dynamic loading or a mix of the two.
 *
 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or
 * asynchronously. If this option is given as "false", then the "onLoad"
 * callback must be given, as the instance returned from this constructor will
 * not be usable for a while.
 *
 * <li><i>loadParams</i> - an object containing parameters to pass to the
 * loader callback function when locale data is missing. The parameters are not
 * interpretted or modified in any way. They are simply passed along. The object
 * may contain any property/value pairs as long as the calling code is in
 * agreement with the loader callback function as to what those parameters mean.
 * </ul>
 *
 * If this copy of ilib is pre-assembled and all the data is already available,
 * or if the data was already previously loaded, then this constructor will call
 * the onLoad callback immediately when the initialization is done.
 * If the onLoad option is not given, this class will only attempt to load any
 * missing locale data synchronously.
 *
 *
 * @constructor
 * @see {ilib.setLoaderCallback} for information about registering a loader callback
 * function
 * @param {Locale|string=} locale the locale for which the info is sought, or undefined for
 * @param {Object=} options the locale for which the info is sought, or undefined for
 * the current locale
 */
var LocaleInfo = function(locale, options) {
    var sync = true,
        loadParams = undefined;

    /**
      @private
      @type {{
        calendar:string,
        clock:string,
        currency:string,
        delimiter: {quotationStart:string,quotationEnd:string,alternateQuotationStart:string,alternateQuotationEnd:string},
        firstDayOfWeek:number,
        meridiems:string,
        numfmt:{
            currencyFormats:{common:string,commonNegative:string,iso:string,isoNegative:string},
            decimalChar:string,
            exponential:string,
            groupChar:string,
            negativenumFmt:string,
            negativepctFmt:string,
            pctChar:string,
            pctFmt:string,
            prigroupSize:number,
            roundingMode:string,
            script:string,
            secgroupSize:number,
            useNative:boolean
        },
        timezone:string,
        units:string,
        weekendEnd:number,
        weekendStart:number,
        paperSizes:{regular:string}
      }}
    */
    this.info = LocaleInfo.defaultInfo;

    switch (typeof(locale)) {
        case "string":
            this.locale = new Locale(locale);
            break;
        default:
        case "undefined":
            this.locale = new Locale();
            break;
        case "object":
            this.locale = locale;
            break;
    }
    var manipulateLocale = ["pa-PK", "ha-CM", "ha-SD"];

    if (manipulateLocale.indexOf(this.locale.getSpec()) != -1) {
        new LocaleMatcher({
            locale: this.locale.getSpec(),
            sync:sync,
            loadParams:loadParams,
            onLoad: ilib.bind(this, function(lm){
                this.locale = new Locale(lm.getLikelyLocale());
            })
        })
    }

    if (options) {
        if (typeof(options.sync) !== 'undefined') {
            sync = !!options.sync;
        }

        if (typeof(options.loadParams) !== 'undefined') {
            loadParams = options.loadParams;
        }
    }

    Utils.loadData({
        object: "LocaleInfo",
        locale: this.locale,
        name: "localeinfo.json",
        sync: sync,
        loadParams: loadParams,
        callback: ilib.bind(this, function (info) {
            this.info = info || LocaleInfo.defaultInfo;
            if (options && typeof(options.onLoad) === 'function') {
                options.onLoad(this);
            }
        })
    });
};

LocaleInfo.defaultInfo = ilib.data.localeinfo;
LocaleInfo.defaultInfo = LocaleInfo.defaultInfo || {
    "calendar": "gregorian",
    "clock": "24",
    "currency": "USD",
    "delimiter": {
        "quotationStart": "“",
        "quotationEnd": "”",
        "alternateQuotationStart": "‘",
        "alternateQuotationEnd": "’"
    },
    "firstDayOfWeek": 1,
    "meridiems": "gregorian",
    "numfmt": {
        "script": "Latn",
        "decimalChar": ".",
        "groupChar": ",",
        "pctChar": "%",
        "exponential": "E",
        "prigroupSize": 3,
        "currencyFormats": {
            "common": "{s} {n}",
            "commonNegative": "-{s} {n}",
            "iso": "{s} {n}",
            "isoNegative": "({s} {n})"
        },
        "negativenumFmt": "-{n}",
        "pctFmt": "{n}%",
        "negativepctFmt": "-{n}%",
        "roundingMode": "halfdown",
        "secGroupSize": null,
        "useNative": false
    },
    "paperSizes": {
        "regular": "A4"
    },
    "timezone": "Etc/UTC",
    "units": "metric",
    "weekendEnd": 0,
    "weekendStart": 6
};

LocaleInfo.prototype = {
    /**
     * Return the name of the locale's language in English.
     * @returns {string} the name of the locale's language in English
     */
    getLanguageName: function () {
        return this.info["language.name"];
    },

    /**
     * Return the name of the locale's region in English. If the locale
     * has no region, this returns undefined.
     *
     * @returns {string|undefined} the name of the locale's region in English
     */
    getRegionName: function () {
        return this.info["region.name"];
    },

    /**
     * Return whether this locale commonly uses the 12- or the 24-hour clock.
     *
     * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24"
     * if the locale commonly uses a 24-hour clock.
     */
    getClock: function() {
        return this.info.clock;
    },

    /**
     * Return the locale that this info object was created with.
     * @returns {Locale} The locale spec of the locale used to construct this info instance
     */
    getLocale: function () {
        return this.locale;
    },

    /**
     * Return the name of the measuring system that is commonly used in the given locale.
     * Valid values are "uscustomary", "imperial", and "metric".
     *
     * @returns {string} The name of the measuring system commonly used in the locale
     */
    getUnits: function () {
        return this.info.units;
    },

    /**
     * Return the name of the calendar that is commonly used in the given locale.
     *
     * @returns {string} The name of the calendar commonly used in the locale
     */
    getCalendar: function () {
        return this.info.calendar;
    },

    /**
     * Return the day of week that starts weeks in the current locale. Days are still
     * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars
     * should be displayed and weeks calculated with the day of week returned from this
     * function as the first day of the week.
     *
     * @returns {number} the day of the week that starts weeks in the current locale.
     */
    getFirstDayOfWeek: function () {
        return this.info.firstDayOfWeek;
    },

    /**
     * Return the day of week that starts weekend in the current locale. Days are still
     * numbered the standard way with 0 for Sunday through 6 for Saturday.
     *
     * @returns {number} the day of the week that starts weeks in the current locale.
     */
    getWeekEndStart: function () {
        return this.info.weekendStart;
    },

    /**
     * Return the day of week that starts weekend in the current locale. Days are still
     * numbered the standard way with 0 for Sunday through 6 for Saturday.
     *
     * @returns {number} the day of the week that starts weeks in the current locale.
     */
    getWeekEndEnd: function () {
        return this.info.weekendEnd;
    },

    /**
     * Return the default time zone for this locale. Many locales span across multiple
     * time zones. In this case, the time zone with the largest population is chosen
     * to represent the locale. This is obviously not that accurate, but then again,
     * this method's return value should only be used as a default anyways.
     * @returns {string} the default time zone for this locale.
     */
    getTimeZone: function () {
        return this.info.timezone;
    },

    /**
     * Return the decimal separator for formatted numbers in this locale.
     * @returns {string} the decimal separator char
     */
    getDecimalSeparator: function () {
        return this.info.numfmt.decimalChar;
    },

    /**
     * Return the decimal separator for formatted numbers in this locale for native script.
     * @returns {string} the decimal separator char
     */
    getNativeDecimalSeparator: function () {
        return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar;
    },

    /**
     * Return the separator character used to separate groups of digits on the
     * integer side of the decimal character.
     * @returns {string} the grouping separator char
     */
    getGroupingSeparator: function () {
        return this.info.numfmt.groupChar;
    },

    /**
     * Return the separator character used to separate groups of digits on the
     * integer side of the decimal character for the native script if present other than the default script.
     * @returns {string} the grouping separator char
     */
    getNativeGroupingSeparator: function () {
        return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar;
    },

    /**
     * Return the minimum number of digits grouped together on the integer side
     * for the first (primary) group.
     * In western European cultures, groupings are in 1000s, so the number of digits
     * is 3.
     * @returns {number} the number of digits in a primary grouping, or 0 for no grouping
     */
    getPrimaryGroupingDigits: function () {
        return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0;
    },

    /**
     * Return the minimum number of digits grouped together on the integer side
     * for the second or more (secondary) group.<p>
     *
     * In western European cultures, all groupings are by 1000s, so the secondary
     * size should be 0 because there is no secondary size. In general, if this
     * method returns 0, then all groupings are of the primary size.<p>
     *
     * For some other cultures, the first grouping (primary)
     * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be
     * written as: "1,00,000".
     *
     * @returns {number} the number of digits in a secondary grouping, or 0 for no
     * secondary grouping.
     */
    getSecondaryGroupingDigits: function () {
        return this.info.numfmt.secgroupSize || 0;
    },

    /**
     * Return the format template used to format percentages in this locale.
     * @returns {string} the format template for formatting percentages
     */
    getPercentageFormat: function () {
        return this.info.numfmt.pctFmt;
    },

    /**
     * Return the format template used to format percentages in this locale
     * with negative amounts.
     * @returns {string} the format template for formatting percentages
     */
    getNegativePercentageFormat: function () {
        return this.info.numfmt.negativepctFmt;
    },

    /**
     * Return the symbol used for percentages in this locale.
     * @returns {string} the symbol used for percentages in this locale
     */
    getPercentageSymbol: function () {
        return this.info.numfmt.pctChar || "%";
    },

    /**
     * Return the symbol used for exponential in this locale.
     * @returns {string} the symbol used for exponential in this locale
     */
    getExponential: function () {
        return this.info.numfmt.exponential;
    },

    /**
     * Return the symbol used for exponential in this locale for native script.
     * @returns {string} the symbol used for exponential in this locale for native script
     */
    getNativeExponential: function () {
        return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential;
    },

    /**
     * Return the symbol used for percentages in this locale for native script.
     * @returns {string} the symbol used for percentages in this locale for native script
     */
    getNativePercentageSymbol: function () {
        return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%";

    },
    /**
     * Return the format template used to format negative numbers in this locale.
     * @returns {string} the format template for formatting negative numbers
     */
    getNegativeNumberFormat: function () {
        return this.info.numfmt.negativenumFmt;
    },

    /**
     * Return an object containing the format templates for formatting currencies
     * in this locale. The object has a number of properties in it that each are
     * a particular style of format. Normally, this contains a "common" and an "iso"
     * style, but may contain others in the future.
     * @returns {Object} an object containing the format templates for currencies
     */
    getCurrencyFormats: function () {
        return this.info.numfmt.currencyFormats;
    },

    /**
     * Return the currency that is legal in the locale, or which is most commonly
     * used in regular commerce.
     * @returns {string} the ISO 4217 code for the currency of this locale
     */
    getCurrency: function () {
        return this.info.currency;
    },

    /**
     * Return a string that describes the style of digits used by this locale.
     * Possible return values are:
     * <ul>
     * <li><i>western</i> - uses the regular western 10-based digits 0 through 9
     * <li><i>optional</i> - native 10-based digits exist, but in modern usage,
     * this locale most often uses western digits
     * <li><i>native</i> - native 10-based native digits exist and are used
     * regularly by this locale
     * <li><i>custom</i> - uses native digits by default that are not 10-based
     * </ul>
     * @returns {string} string that describes the style of digits used in this locale
     */
    getDigitsStyle: function () {
        if (this.info.numfmt && this.info.numfmt.useNative) {
            return "native";
        }
        if (typeof(this.info.native_numfmt) !== 'undefined') {
            return "optional";
        }
        return "western";
    },

    /**
     * Return the digits of the default script if they are defined.
     * If not defined, the default should be the regular "Arabic numerals"
     * used in the Latin script. (0-9)
     * @returns {string|undefined} the digits used in the default script
     */
    getDigits: function () {
        return this.info.numfmt.digits;
    },

    /**
     * Return the digits of the native script if they are defined.
     * @returns {string|undefined} the digits used in the default script
     */
    getNativeDigits: function () {
        return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits);
    },

    /**
     * If this locale typically uses a different type of rounding for numeric
     * formatting other than halfdown, especially for currency, then it can be
     * specified in the localeinfo. If the locale uses the default, then this
     * method returns undefined. The locale's rounding method overrides the
     * rounding method for the currency itself, which can sometimes shared
     * between various locales so it is less specific.
     * @returns {string} the name of the rounding mode typically used in this
     * locale, or "halfdown" if the locale does not override the default
     */
    getRoundingMode: function () {
        return this.info.numfmt.roundingMode;
    },

    /**
     * Return the default script used to write text in the language of this
     * locale. Text for most languages is written in only one script, but there
     * are some languages where the text can be written in a number of scripts,
     * depending on a variety of things such as the region, ethnicity, religion,
     * etc. of the author. This method returns the default script for the
     * locale, in which the language is most commonly written.<p>
     *
     * The script is returned as an ISO 15924 4-letter code.
     *
     * @returns {string} the ISO 15924 code for the default script used to write
     * text in this locale
     */
    getDefaultScript: function() {
        return (this.info.scripts) ? this.info.scripts[0] : "Latn";
    },

    /**
     * Return the script used for the current locale. If the current locale
     * explicitly defines a script, then this script is returned. If not, then
     * the default script for the locale is returned.
     *
     * @see LocaleInfo.getDefaultScript
     * @returns {string} the ISO 15924 code for the script used to write
     * text in this locale
     */
    getScript: function() {
        return this.locale.getScript() || this.getDefaultScript();
    },

    /**
     * Return an array of script codes which are used to write text in the current
     * language. Text for most languages is written in only one script, but there
     * are some languages where the text can be written in a number of scripts,
     * depending on a variety of things such as the region, ethnicity, religion,
     * etc. of the author. This method returns an array of script codes in which
     * the language is commonly written.
     *
     * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used
     * to write text in this language
     */
    getAllScripts: function() {
        return this.info.scripts || ["Latn"];
    },

    /**
     * Return the default style of meridiems used in this locale. Meridiems are
     * times of day like AM/PM. In a few locales with some calendars, for example
     * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be
     * split into different segments than simple AM/PM as in the Gregorian
     * calendar. Only a few locales are like that. For most locales, formatting
     * a Gregorian date will use the regular Gregorian AM/PM meridiems.
     *
     * @returns {string} the default meridiems style used in this locale. Possible
     * values are "gregorian", "chinese", and "ethiopic"
     */
    getMeridiemsStyle: function () {
        return this.info.meridiems || "gregorian";
    },
    /**
     * Return the default PaperSize information in this locale.
     * @returns {string} default PaperSize in this locale
     */
    getPaperSize: function () {
        return this.info.paperSizes.regular;
    },
    /**
     * Return the default Delimiter QuotationStart information in this locale.
     * @returns {string} default QuotationStart in this locale
     */
    getDelimiterQuotationStart: function () {
        return this.info.delimiter.quotationStart;
    },
    /**
     * Return the default Delimiter QuotationEnd information in this locale.
     * @returns {string} default QuotationEnd in this locale
     */
    getDelimiterQuotationEnd: function () {
        return this.info.delimiter.quotationEnd;
    }
};

module.exports = LocaleInfo;