angular-l10n
Version:
An Angular library to translate messages, dates and numbers
266 lines • 9.7 kB
JavaScript
/**
* @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