1 /*
  2  * LocaleInfo.js - Encode locale-specific defaults
  3  *
  4  * Copyright © 2012-2015, 2018, 2021 JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 // !data localeinfo
 21 
 22 var ilib = require("../index.js");
 23 var Utils = require("./Utils.js");
 24 var Locale = require("./Locale.js");
 25 var LocaleMatcher = require("./LocaleMatcher.js");
 26 
 27 /**
 28  * @class
 29  * Create a new locale info instance. Locale info instances give information about
 30  * the default settings for a particular locale. These settings may be overridden
 31  * by various parts of the code, and should be used as a fall-back setting of last
 32  * resort. <p>
 33  *
 34  * The optional options object holds extra parameters if they are necessary. The
 35  * current list of supported options are:
 36  *
 37  * <ul>
 38  * <li><i>onLoad</i> - a callback function to call when the locale info object is fully
 39  * loaded. When the onLoad option is given, the localeinfo object will attempt to
 40  * load any missing locale data using the ilib loader callback.
 41  * When the constructor is done (even if the data is already preassembled), the
 42  * onLoad function is called with the current instance as a parameter, so this
 43  * callback can be used with preassembled or dynamic loading or a mix of the two.
 44  *
 45  * <li><i>sync</i> - tell whether to load any missing locale data synchronously or
 46  * asynchronously. If this option is given as "false", then the "onLoad"
 47  * callback must be given, as the instance returned from this constructor will
 48  * not be usable for a while.
 49  *
 50  * <li><i>loadParams</i> - an object containing parameters to pass to the
 51  * loader callback function when locale data is missing. The parameters are not
 52  * interpretted or modified in any way. They are simply passed along. The object
 53  * may contain any property/value pairs as long as the calling code is in
 54  * agreement with the loader callback function as to what those parameters mean.
 55  * </ul>
 56  *
 57  * If this copy of ilib is pre-assembled and all the data is already available,
 58  * or if the data was already previously loaded, then this constructor will call
 59  * the onLoad callback immediately when the initialization is done.
 60  * If the onLoad option is not given, this class will only attempt to load any
 61  * missing locale data synchronously.
 62  *
 63  *
 64  * @constructor
 65  * @see {ilib.setLoaderCallback} for information about registering a loader callback
 66  * function
 67  * @param {Locale|string=} locale the locale for which the info is sought, or undefined for
 68  * @param {Object=} options the locale for which the info is sought, or undefined for
 69  * the current locale
 70  */
 71 var LocaleInfo = function(locale, options) {
 72     var sync = true,
 73         loadParams = undefined;
 74 
 75     /**
 76       @private
 77       @type {{
 78         calendar:string,
 79         clock:string,
 80         currency:string,
 81         delimiter: {quotationStart:string,quotationEnd:string,alternateQuotationStart:string,alternateQuotationEnd:string},
 82         firstDayOfWeek:number,
 83         meridiems:string,
 84         numfmt:{
 85             currencyFormats:{common:string,commonNegative:string,iso:string,isoNegative:string},
 86             decimalChar:string,
 87             exponential:string,
 88             groupChar:string,
 89             negativenumFmt:string,
 90             negativepctFmt:string,
 91             pctChar:string,
 92             pctFmt:string,
 93             prigroupSize:number,
 94             roundingMode:string,
 95             script:string,
 96             secgroupSize:number,
 97             useNative:boolean
 98         },
 99         timezone:string,
100         units:string,
101         weekendEnd:number,
102         weekendStart:number,
103         paperSizes:{regular:string}
104       }}
105     */
106     this.info = LocaleInfo.defaultInfo;
107 
108     switch (typeof(locale)) {
109         case "string":
110             this.locale = new Locale(locale);
111             break;
112         default:
113         case "undefined":
114             this.locale = new Locale();
115             break;
116         case "object":
117             this.locale = locale;
118             break;
119     }
120     var manipulateLocale = ["pa-PK", "ha-CM", "ha-SD"];
121 
122     if (manipulateLocale.indexOf(this.locale.getSpec()) != -1) {
123         new LocaleMatcher({
124             locale: this.locale.getSpec(),
125             sync:sync,
126             loadParams:loadParams,
127             onLoad: ilib.bind(this, function(lm){
128                 this.locale = new Locale(lm.getLikelyLocale());
129             })
130         })
131     }
132 
133     if (options) {
134         if (typeof(options.sync) !== 'undefined') {
135             sync = !!options.sync;
136         }
137 
138         if (typeof(options.loadParams) !== 'undefined') {
139             loadParams = options.loadParams;
140         }
141     }
142 
143     Utils.loadData({
144         object: "LocaleInfo",
145         locale: this.locale,
146         name: "localeinfo.json",
147         sync: sync,
148         loadParams: loadParams,
149         callback: ilib.bind(this, function (info) {
150             this.info = info || LocaleInfo.defaultInfo;
151             if (options && typeof(options.onLoad) === 'function') {
152                 options.onLoad(this);
153             }
154         })
155     });
156 };
157 
158 LocaleInfo.defaultInfo = ilib.data.localeinfo;
159 LocaleInfo.defaultInfo = LocaleInfo.defaultInfo || {
160     "calendar": "gregorian",
161     "clock": "24",
162     "currency": "USD",
163     "delimiter": {
164         "quotationStart": "“",
165         "quotationEnd": "”",
166         "alternateQuotationStart": "‘",
167         "alternateQuotationEnd": "’"
168     },
169     "firstDayOfWeek": 1,
170     "meridiems": "gregorian",
171     "numfmt": {
172         "script": "Latn",
173         "decimalChar": ".",
174         "groupChar": ",",
175         "pctChar": "%",
176         "exponential": "E",
177         "prigroupSize": 3,
178         "currencyFormats": {
179             "common": "{s} {n}",
180             "commonNegative": "-{s} {n}",
181             "iso": "{s} {n}",
182             "isoNegative": "({s} {n})"
183         },
184         "negativenumFmt": "-{n}",
185         "pctFmt": "{n}%",
186         "negativepctFmt": "-{n}%",
187         "roundingMode": "halfdown",
188         "secGroupSize": null,
189         "useNative": false
190     },
191     "paperSizes": {
192         "regular": "A4"
193     },
194     "timezone": "Etc/UTC",
195     "units": "metric",
196     "weekendEnd": 0,
197     "weekendStart": 6
198 };
199 
200 LocaleInfo.prototype = {
201     /**
202      * Return the name of the locale's language in English.
203      * @returns {string} the name of the locale's language in English
204      */
205     getLanguageName: function () {
206         return this.info["language.name"];
207     },
208 
209     /**
210      * Return the name of the locale's region in English. If the locale
211      * has no region, this returns undefined.
212      *
213      * @returns {string|undefined} the name of the locale's region in English
214      */
215     getRegionName: function () {
216         return this.info["region.name"];
217     },
218 
219     /**
220      * Return whether this locale commonly uses the 12- or the 24-hour clock.
221      *
222      * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24"
223      * if the locale commonly uses a 24-hour clock.
224      */
225     getClock: function() {
226         return this.info.clock;
227     },
228 
229     /**
230      * Return the locale that this info object was created with.
231      * @returns {Locale} The locale spec of the locale used to construct this info instance
232      */
233     getLocale: function () {
234         return this.locale;
235     },
236 
237     /**
238      * Return the name of the measuring system that is commonly used in the given locale.
239      * Valid values are "uscustomary", "imperial", and "metric".
240      *
241      * @returns {string} The name of the measuring system commonly used in the locale
242      */
243     getUnits: function () {
244         return this.info.units;
245     },
246 
247     /**
248      * Return the name of the calendar that is commonly used in the given locale.
249      *
250      * @returns {string} The name of the calendar commonly used in the locale
251      */
252     getCalendar: function () {
253         return this.info.calendar;
254     },
255 
256     /**
257      * Return the day of week that starts weeks in the current locale. Days are still
258      * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars
259      * should be displayed and weeks calculated with the day of week returned from this
260      * function as the first day of the week.
261      *
262      * @returns {number} the day of the week that starts weeks in the current locale.
263      */
264     getFirstDayOfWeek: function () {
265         return this.info.firstDayOfWeek;
266     },
267 
268     /**
269      * Return the day of week that starts weekend in the current locale. Days are still
270      * numbered the standard way with 0 for Sunday through 6 for Saturday.
271      *
272      * @returns {number} the day of the week that starts weeks in the current locale.
273      */
274     getWeekEndStart: function () {
275         return this.info.weekendStart;
276     },
277 
278     /**
279      * Return the day of week that starts weekend in the current locale. Days are still
280      * numbered the standard way with 0 for Sunday through 6 for Saturday.
281      *
282      * @returns {number} the day of the week that starts weeks in the current locale.
283      */
284     getWeekEndEnd: function () {
285         return this.info.weekendEnd;
286     },
287 
288     /**
289      * Return the default time zone for this locale. Many locales span across multiple
290      * time zones. In this case, the time zone with the largest population is chosen
291      * to represent the locale. This is obviously not that accurate, but then again,
292      * this method's return value should only be used as a default anyways.
293      * @returns {string} the default time zone for this locale.
294      */
295     getTimeZone: function () {
296         return this.info.timezone;
297     },
298 
299     /**
300      * Return the decimal separator for formatted numbers in this locale.
301      * @returns {string} the decimal separator char
302      */
303     getDecimalSeparator: function () {
304         return this.info.numfmt.decimalChar;
305     },
306 
307     /**
308      * Return the decimal separator for formatted numbers in this locale for native script.
309      * @returns {string} the decimal separator char
310      */
311     getNativeDecimalSeparator: function () {
312         return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar;
313     },
314 
315     /**
316      * Return the separator character used to separate groups of digits on the
317      * integer side of the decimal character.
318      * @returns {string} the grouping separator char
319      */
320     getGroupingSeparator: function () {
321         return this.info.numfmt.groupChar;
322     },
323 
324     /**
325      * Return the separator character used to separate groups of digits on the
326      * integer side of the decimal character for the native script if present other than the default script.
327      * @returns {string} the grouping separator char
328      */
329     getNativeGroupingSeparator: function () {
330         return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar;
331     },
332 
333     /**
334      * Return the minimum number of digits grouped together on the integer side
335      * for the first (primary) group.
336      * In western European cultures, groupings are in 1000s, so the number of digits
337      * is 3.
338      * @returns {number} the number of digits in a primary grouping, or 0 for no grouping
339      */
340     getPrimaryGroupingDigits: function () {
341         return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0;
342     },
343 
344     /**
345      * Return the minimum number of digits grouped together on the integer side
346      * for the second or more (secondary) group.<p>
347      *
348      * In western European cultures, all groupings are by 1000s, so the secondary
349      * size should be 0 because there is no secondary size. In general, if this
350      * method returns 0, then all groupings are of the primary size.<p>
351      *
352      * For some other cultures, the first grouping (primary)
353      * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be
354      * written as: "1,00,000".
355      *
356      * @returns {number} the number of digits in a secondary grouping, or 0 for no
357      * secondary grouping.
358      */
359     getSecondaryGroupingDigits: function () {
360         return this.info.numfmt.secgroupSize || 0;
361     },
362 
363     /**
364      * Return the format template used to format percentages in this locale.
365      * @returns {string} the format template for formatting percentages
366      */
367     getPercentageFormat: function () {
368         return this.info.numfmt.pctFmt;
369     },
370 
371     /**
372      * Return the format template used to format percentages in this locale
373      * with negative amounts.
374      * @returns {string} the format template for formatting percentages
375      */
376     getNegativePercentageFormat: function () {
377         return this.info.numfmt.negativepctFmt;
378     },
379 
380     /**
381      * Return the symbol used for percentages in this locale.
382      * @returns {string} the symbol used for percentages in this locale
383      */
384     getPercentageSymbol: function () {
385         return this.info.numfmt.pctChar || "%";
386     },
387 
388     /**
389      * Return the symbol used for exponential in this locale.
390      * @returns {string} the symbol used for exponential in this locale
391      */
392     getExponential: function () {
393         return this.info.numfmt.exponential;
394     },
395 
396     /**
397      * Return the symbol used for exponential in this locale for native script.
398      * @returns {string} the symbol used for exponential in this locale for native script
399      */
400     getNativeExponential: function () {
401         return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential;
402     },
403 
404     /**
405      * Return the symbol used for percentages in this locale for native script.
406      * @returns {string} the symbol used for percentages in this locale for native script
407      */
408     getNativePercentageSymbol: function () {
409         return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%";
410 
411     },
412     /**
413      * Return the format template used to format negative numbers in this locale.
414      * @returns {string} the format template for formatting negative numbers
415      */
416     getNegativeNumberFormat: function () {
417         return this.info.numfmt.negativenumFmt;
418     },
419 
420     /**
421      * Return an object containing the format templates for formatting currencies
422      * in this locale. The object has a number of properties in it that each are
423      * a particular style of format. Normally, this contains a "common" and an "iso"
424      * style, but may contain others in the future.
425      * @returns {Object} an object containing the format templates for currencies
426      */
427     getCurrencyFormats: function () {
428         return this.info.numfmt.currencyFormats;
429     },
430 
431     /**
432      * Return the currency that is legal in the locale, or which is most commonly
433      * used in regular commerce.
434      * @returns {string} the ISO 4217 code for the currency of this locale
435      */
436     getCurrency: function () {
437         return this.info.currency;
438     },
439 
440     /**
441      * Return a string that describes the style of digits used by this locale.
442      * Possible return values are:
443      * <ul>
444      * <li><i>western</i> - uses the regular western 10-based digits 0 through 9
445      * <li><i>optional</i> - native 10-based digits exist, but in modern usage,
446      * this locale most often uses western digits
447      * <li><i>native</i> - native 10-based native digits exist and are used
448      * regularly by this locale
449      * <li><i>custom</i> - uses native digits by default that are not 10-based
450      * </ul>
451      * @returns {string} string that describes the style of digits used in this locale
452      */
453     getDigitsStyle: function () {
454         if (this.info.numfmt && this.info.numfmt.useNative) {
455             return "native";
456         }
457         if (typeof(this.info.native_numfmt) !== 'undefined') {
458             return "optional";
459         }
460         return "western";
461     },
462 
463     /**
464      * Return the digits of the default script if they are defined.
465      * If not defined, the default should be the regular "Arabic numerals"
466      * used in the Latin script. (0-9)
467      * @returns {string|undefined} the digits used in the default script
468      */
469     getDigits: function () {
470         return this.info.numfmt.digits;
471     },
472 
473     /**
474      * Return the digits of the native script if they are defined.
475      * @returns {string|undefined} the digits used in the default script
476      */
477     getNativeDigits: function () {
478         return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits);
479     },
480 
481     /**
482      * If this locale typically uses a different type of rounding for numeric
483      * formatting other than halfdown, especially for currency, then it can be
484      * specified in the localeinfo. If the locale uses the default, then this
485      * method returns undefined. The locale's rounding method overrides the
486      * rounding method for the currency itself, which can sometimes shared
487      * between various locales so it is less specific.
488      * @returns {string} the name of the rounding mode typically used in this
489      * locale, or "halfdown" if the locale does not override the default
490      */
491     getRoundingMode: function () {
492         return this.info.numfmt.roundingMode;
493     },
494 
495     /**
496      * Return the default script used to write text in the language of this
497      * locale. Text for most languages is written in only one script, but there
498      * are some languages where the text can be written in a number of scripts,
499      * depending on a variety of things such as the region, ethnicity, religion,
500      * etc. of the author. This method returns the default script for the
501      * locale, in which the language is most commonly written.<p>
502      *
503      * The script is returned as an ISO 15924 4-letter code.
504      *
505      * @returns {string} the ISO 15924 code for the default script used to write
506      * text in this locale
507      */
508     getDefaultScript: function() {
509         return (this.info.scripts) ? this.info.scripts[0] : "Latn";
510     },
511 
512     /**
513      * Return the script used for the current locale. If the current locale
514      * explicitly defines a script, then this script is returned. If not, then
515      * the default script for the locale is returned.
516      *
517      * @see LocaleInfo.getDefaultScript
518      * @returns {string} the ISO 15924 code for the script used to write
519      * text in this locale
520      */
521     getScript: function() {
522         return this.locale.getScript() || this.getDefaultScript();
523     },
524 
525     /**
526      * Return an array of script codes which are used to write text in the current
527      * language. Text for most languages is written in only one script, but there
528      * are some languages where the text can be written in a number of scripts,
529      * depending on a variety of things such as the region, ethnicity, religion,
530      * etc. of the author. This method returns an array of script codes in which
531      * the language is commonly written.
532      *
533      * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used
534      * to write text in this language
535      */
536     getAllScripts: function() {
537         return this.info.scripts || ["Latn"];
538     },
539 
540     /**
541      * Return the default style of meridiems used in this locale. Meridiems are
542      * times of day like AM/PM. In a few locales with some calendars, for example
543      * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be
544      * split into different segments than simple AM/PM as in the Gregorian
545      * calendar. Only a few locales are like that. For most locales, formatting
546      * a Gregorian date will use the regular Gregorian AM/PM meridiems.
547      *
548      * @returns {string} the default meridiems style used in this locale. Possible
549      * values are "gregorian", "chinese", and "ethiopic"
550      */
551     getMeridiemsStyle: function () {
552         return this.info.meridiems || "gregorian";
553     },
554     /**
555      * Return the default PaperSize information in this locale.
556      * @returns {string} default PaperSize in this locale
557      */
558     getPaperSize: function () {
559         return this.info.paperSizes.regular;
560     },
561     /**
562      * Return the default Delimiter QuotationStart information in this locale.
563      * @returns {string} default QuotationStart in this locale
564      */
565     getDelimiterQuotationStart: function () {
566         return this.info.delimiter.quotationStart;
567     },
568     /**
569      * Return the default Delimiter QuotationEnd information in this locale.
570      * @returns {string} default QuotationEnd in this locale
571      */
572     getDelimiterQuotationEnd: function () {
573         return this.info.delimiter.quotationEnd;
574     }
575 };
576 
577 module.exports = LocaleInfo;
578