UNPKG

angular-l10n

Version:

An Angular library to translate messages, dates and numbers

266 lines 9.7 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ import { Injectable } from '@angular/core'; import { LocaleService } from './locale.service'; import { IntlAPI } from './intl-api'; import { formatDigitsAliases } from '../models/intl-formatter'; import { Logger } from '../models/logger'; /** * @record */ export function ILocaleValidation() { } if (false) { /** * @param {?} s * @param {?=} digits * @param {?=} defaultLocale * @return {?} */ ILocaleValidation.prototype.parseNumber = function (s, digits, defaultLocale) { }; } /** * Provides the methods for locale validation. */ export class LocaleValidation { /** * @param {?} locale */ constructor(locale) { this.locale = locale; } /** * Converts a string to a number according to default locale. * If the string cannot be converted to a number, returns NaN. * @param {?} s The string to be parsed * @param {?=} digits An alias of the format. Default is '1.0-3' * @param {?=} defaultLocale The default locale to use. Default is the current locale * @return {?} */ parseNumber(s, digits, defaultLocale) { if (s == "" || s == null) return null; // Replaces whitespace metacharacters. s = s.replace(/\s/g, ' '); this.decimalCode = this.getDecimalCode(defaultLocale); this.numberCodes = this.getNumberCodes(defaultLocale); if (!this.validateNumber(s, digits)) return NaN; /** @type {?} */ let value = ""; /** @type {?} */ const characters = s.split(""); for (const char of characters) { /** @type {?} */ const charCode = this.toUnicode(char); /** @type {?} */ const index = this.numberCodes.indexOf(charCode); if (index != -1) { value += index; } else if (charCode == this.decimalCode.minusSign) { value += "-"; } else if (charCode == this.decimalCode.decimalSeparator) { value += "."; } else if (charCode == this.decimalCode.thousandSeparator) { continue; } else { return NaN; } } return parseFloat(value); } /** * @param {?} s * @param {?=} digits * @return {?} */ validateNumber(s, digits) { /** @type {?} */ let options = {}; if (digits) { /** @type {?} */ const digitsOptions = formatDigitsAliases(digits); if (digitsOptions != null) { options = digitsOptions; } else { Logger.log('LocaleValidation', 'invalidNumberFormatAlias'); } } /** @type {?} */ const minInt = options.minimumIntegerDigits !== undefined ? options.minimumIntegerDigits : 1; /** @type {?} */ const minFraction = options.minimumFractionDigits !== undefined ? options.minimumFractionDigits : 0; /** @type {?} */ const maxFraction = options.maximumFractionDigits !== undefined ? options.maximumFractionDigits : 3; /** @type {?} */ const minusSign = this.decimalCode.minusSign; /** @type {?} */ const zero = this.numberCodes[0]; /** @type {?} */ const decimalSeparator = this.decimalCode.decimalSeparator; /** @type {?} */ const thousandSeparator = this.decimalCode.thousandSeparator; /** @type {?} */ const nine = this.numberCodes[9]; // Pattern for 1.0-2 digits: /^-?[0-9]{1,}(\.[0-9]{0,2})?$/ // Unicode pattern = "^\u002d?[\u0030-\u0039]{1,}(\\u002e[\u0030-\u0039]{0,2})?$" // Complete Pattern with thousand separator: // /^-?([0-9]{1,}|(?=(?:\,*[0-9]){1,}(\.|$))(?!0(?!\.|[0-9]))[0-9]{1,3}(\,[0-9]{3})*)(\.[0-9]{0,2})?$/ // where: // (?=(?:\,*[0-9]){1,}(\.|$)) => Positive Lookahead to count the integer digits // (?!0(?!\.|[0-9])) => Negative Lookahead to avoid 0,1111.00 // [0-9]{1,3}(\,[0-9]{3})* => Allows thousand separator /** @type {?} */ const d = `[${zero}-${nine}]`; /** @type {?} */ const n = `{${minInt},}`; /** @type {?} */ const nm = `{${minFraction},${maxFraction}}`; /** @type {?} */ const plainPattern = `${d}${n}`; // tslint:disable-next-line /** @type {?} */ const thousandPattern = `(?=(?:\\${thousandSeparator}*${d})${n}(\\${decimalSeparator}|$))(?!${zero}(?!\\${decimalSeparator}|${d}))${d}{1,3}(\\${thousandSeparator}${d}{3})*`; /** @type {?} */ let pattern = `^${minusSign}?(${plainPattern}|${thousandPattern})`; if (minFraction > 0 && maxFraction > 0) { // Decimal separator is mandatory. pattern += `\\${decimalSeparator}${d}${nm}$`; } else if (minFraction == 0 && maxFraction > 0) { // Decimal separator is optional. pattern += `(\\${decimalSeparator}${d}${nm})?$`; } else { // Integer number. pattern += `$`; } pattern = this.toChar(pattern); /** @type {?} */ const NUMBER_REGEXP = new RegExp(pattern); return NUMBER_REGEXP.test(s); } /** * @param {?=} defaultLocale * @return {?} */ getDecimalCode(defaultLocale) { /** @type {?} */ let decimalCode = { minusSign: this.toUnicode("-"), decimalSeparator: this.toUnicode("."), thousandSeparator: this.toUnicode(",") }; if (IntlAPI.hasNumberFormat()) { /** @type {?} */ const value = -1000.9; // Reference value. /** @type {?} */ const localeValue = this.locale.formatDecimal(value, '1.1-1', defaultLocale); /** @type {?} */ const unicodeChars = []; for (let i = 0; i < localeValue.length; i++) { /** @type {?} */ let unicodeChar = this.toUnicode(localeValue.charAt(i)); // Replaces NO-BREAK SPACE unicodeChar = unicodeChar.replace("\\u202F", "\\u0020"); unicodeChar = unicodeChar.replace("\\u00A0", "\\u0020"); unicodeChars.push(unicodeChar); } /** @type {?} */ const thousandSeparator = localeValue.length >= 8 ? true : false; // Expected positions. // Right to left: // checks Unicode characters 'RIGHT-TO-LEFT MARK' (U+200F) & 'Arabic Letter Mark' (U+061C), // or the reverse order. // Left to right: // checks Unicode character 'LEFT-TO-RIGHT MARK' (U+200E). /** @type {?} */ let positions; if (unicodeChars[0] == "\\u200F" || unicodeChars[0] == "\\u061C") { positions = thousandSeparator ? [1, 7, 3] : [1, 6]; } else if (unicodeChars[0] == this.toUnicode(this.locale.formatDecimal(1, '1.0-0', defaultLocale))) { positions = thousandSeparator ? [7, 5, 1] : [6, 4]; } else if (unicodeChars[0] == "\\u200E") { positions = thousandSeparator ? [1, 7, 3] : [1, 6]; } else { positions = thousandSeparator ? [0, 6, 2] : [0, 5]; } decimalCode = { minusSign: unicodeChars[positions[0]], decimalSeparator: unicodeChars[positions[1]], thousandSeparator: thousandSeparator ? unicodeChars[positions[2]] : "" }; } return decimalCode; } /** * @param {?=} defaultLocale * @return {?} */ getNumberCodes(defaultLocale) { /** @type {?} */ const numberCodes = []; for (let num = 0; num <= 9; num++) { numberCodes.push(this.toUnicode(num.toString())); } if (IntlAPI.hasNumberFormat()) { for (let num = 0; num <= 9; num++) { numberCodes[num] = this.toUnicode(this.locale.formatDecimal(num, '1.0-0', defaultLocale)); } } return numberCodes; } /** * @param {?} pattern * @return {?} */ toChar(pattern) { return pattern.replace(/\\u[\dA-F]{4}/gi, (match) => { return String.fromCharCode(parseInt(match.replace(/\\u/g, ""), 16)); }); } /** * @param {?} c * @return {?} */ toUnicode(c) { return "\\u" + this.toHex(c.charCodeAt(0)); } /** * @param {?} value * @return {?} */ toHex(value) { /** @type {?} */ let hex = value.toString(16).toUpperCase(); // With padding. hex = "0000".substr(0, 4 - hex.length) + hex; return hex; } } LocaleValidation.decorators = [ { type: Injectable } ]; /** @nocollapse */ LocaleValidation.ctorParameters = () => [ { type: LocaleService } ]; if (false) { /** @type {?} */ LocaleValidation.prototype.decimalCode; /** @type {?} */ LocaleValidation.prototype.numberCodes; /** @type {?} */ LocaleValidation.prototype.locale; } //# sourceMappingURL=locale-validation.js.map