diff --git a/packages/common/src/i18n/currencies.ts b/packages/common/src/i18n/currencies.ts index 944e1b423b..f041820a89 100644 --- a/packages/common/src/i18n/currencies.ts +++ b/packages/common/src/i18n/currencies.ts @@ -9,8 +9,10 @@ // THIS CODE IS GENERATED - DO NOT MODIFY // See angular/tools/gulp-tasks/cldr/extract.js +export type CurrenciesSymbols = [string] | [string | undefined, string]; + /** @internal */ -export const CURRENCIES: {[code: string]: (string | undefined)[]} = { +export const CURRENCIES_EN: {[code: string]: CurrenciesSymbols} = { 'AOA': [, 'Kz'], 'ARS': [, '$'], 'AUD': ['A$', '$'], @@ -111,5 +113,5 @@ export const CURRENCIES: {[code: string]: (string | undefined)[]} = { 'XOF': ['CFA'], 'XPF': ['CFPF'], 'ZAR': [, 'R'], - 'ZMW': [, 'ZK'], + 'ZMW': [, 'ZK'] }; diff --git a/packages/common/src/i18n/format_number.ts b/packages/common/src/i18n/format_number.ts index 6ea59aac7b..4e0f7c9114 100644 --- a/packages/common/src/i18n/format_number.ts +++ b/packages/common/src/i18n/format_number.ts @@ -18,33 +18,28 @@ const DIGIT_CHAR = '#'; const CURRENCY_CHAR = '¤'; const PERCENT_CHAR = '%'; -/** @internal */ -export type FormatNumberRes = { - str: string | null, - error?: string -}; - /** - * Transform a number to a locale string based on a style and a format - * - * @internal + * Transforms a string into a number (if needed) */ -export function formatNumber( - value: number | string, locale: string, style: NumberFormatStyle, digitsInfo?: string | null, - currency: string | null = null): FormatNumberRes { - const res: FormatNumberRes = {str: null}; - const format = getLocaleNumberFormat(locale, style); - let num; - +function strToNumber(value: number | string): number { // Convert strings to numbers if (typeof value === 'string' && !isNaN(+value - parseFloat(value))) { - num = +value; - } else if (typeof value !== 'number') { - res.error = `${value} is not a number`; - return res; - } else { - num = value; + return +value; } + if (typeof value !== 'number') { + throw new Error(`${value} is not a number`); + } + return value; +} + +/** + * Transforms a number to a locale string based on a style and a format + */ +function formatNumber( + value: number | string, locale: string, style: NumberFormatStyle, groupSymbol: NumberSymbol, + decimalSymbol: NumberSymbol, digitsInfo?: string): string { + const format = getLocaleNumberFormat(locale, style); + const num = strToNumber(value); const pattern = parseNumberFormat(format, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign)); let formattedText = ''; @@ -66,8 +61,7 @@ export function formatNumber( if (digitsInfo) { const parts = digitsInfo.match(NUMBER_FORMAT_REGEXP); if (parts === null) { - res.error = `${digitsInfo} is not a valid digit info`; - return res; + throw new Error(`${digitsInfo} is not a valid digit info`); } const minIntPart = parts[1]; const minFractionPart = parts[3]; @@ -125,12 +119,10 @@ export function formatNumber( groups.unshift(digits.join('')); } - const groupSymbol = currency ? NumberSymbol.CurrencyGroup : NumberSymbol.Group; formattedText = groups.join(getLocaleNumberSymbol(locale, groupSymbol)); // append the decimal digits if (decimals.length) { - const decimalSymbol = currency ? NumberSymbol.CurrencyDecimal : NumberSymbol.Decimal; formattedText += getLocaleNumberSymbol(locale, decimalSymbol) + decimals.join(''); } @@ -145,22 +137,42 @@ export function formatNumber( formattedText = pattern.posPre + formattedText + pattern.posSuf; } - if (style === NumberFormatStyle.Currency && currency !== null) { - res.str = formattedText - .replace(CURRENCY_CHAR, currency) - // if we have 2 time the currency character, the second one is ignored - .replace(CURRENCY_CHAR, ''); - return res; - } + return formattedText; +} - if (style === NumberFormatStyle.Percent) { - res.str = formattedText.replace( - new RegExp(PERCENT_CHAR, 'g'), getLocaleNumberSymbol(locale, NumberSymbol.PercentSign)); - return res; - } +/** + * Formats a currency to a locale string + */ +export function formatCurrency( + value: number | string, locale: string, currency: string, currencyCode?: string, + digitsInfo?: string): string { + const res = formatNumber( + value, locale, NumberFormatStyle.Currency, NumberSymbol.CurrencyGroup, + NumberSymbol.CurrencyDecimal, digitsInfo); + return res + .replace(CURRENCY_CHAR, currency) + // if we have 2 time the currency character, the second one is ignored + .replace(CURRENCY_CHAR, ''); +} - res.str = formattedText; - return res; +/** + * Formats a percentage to a locale string + */ +export function formatPercent(value: number | string, locale: string, digitsInfo?: string): string { + const res = formatNumber( + value, locale, NumberFormatStyle.Percent, NumberSymbol.Group, NumberSymbol.Decimal, + digitsInfo); + return res.replace( + new RegExp(PERCENT_CHAR, 'g'), getLocaleNumberSymbol(locale, NumberSymbol.PercentSign)); +} + +/** + * Formats a number to a locale string + */ +export function formatDecimal(value: number | string, locale: string, digitsInfo?: string): string { + return formatNumber( + value, locale, NumberFormatStyle.Decimal, NumberSymbol.Group, NumberSymbol.Decimal, + digitsInfo); } interface ParsedNumberFormat { diff --git a/packages/common/src/i18n/locale_data.ts b/packages/common/src/i18n/locale_data.ts index 8dd54bcbc3..af36a3caa2 100644 --- a/packages/common/src/i18n/locale_data.ts +++ b/packages/common/src/i18n/locale_data.ts @@ -54,6 +54,7 @@ export const enum LocaleDataIndex { NumberFormats, CurrencySymbol, CurrencyName, + Currencies, PluralCase, ExtraData } @@ -66,3 +67,8 @@ export const enum ExtraLocaleDataIndex { ExtraDayPeriodStandalone, ExtraDayPeriodsRules } + +/** + * Index of each value in currency data (used to describe CURRENCIES_EN in currencies.ts) + */ +export const enum CurrencyIndex {Symbol = 0, SymbolNarrow} diff --git a/packages/common/src/i18n/locale_data_api.ts b/packages/common/src/i18n/locale_data_api.ts index 4bf240e497..7b065109db 100644 --- a/packages/common/src/i18n/locale_data_api.ts +++ b/packages/common/src/i18n/locale_data_api.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {CURRENCIES} from './currencies'; import localeEn from './locale_en'; -import {LOCALE_DATA, LocaleDataIndex, ExtraLocaleDataIndex} from './locale_data'; +import {LOCALE_DATA, LocaleDataIndex, ExtraLocaleDataIndex, CurrencyIndex} from './locale_data'; +import {CURRENCIES_EN, CurrenciesSymbols} from './currencies'; /** * The different format styles that can be used to represent numbers. @@ -391,6 +391,14 @@ export function getLocaleCurrencyName(locale: string): string|null { return data[LocaleDataIndex.CurrencyName] || null; } +/** + * Returns the currency values for the locale + */ +function getLocaleCurrencies(locale: string): {[code: string]: CurrenciesSymbols} { + const data = findLocaleData(locale); + return data[LocaleDataIndex.Currencies]; +} + /** * The locale plural function used by ICU expressions to determine the plural case to use. * See {@link NgPlural} for more information. @@ -526,18 +534,19 @@ export function findLocaleData(locale: string): any { } /** - * Return the currency symbol for a given currency code, or the code if no symbol available + * Returns the currency symbol for a given currency code, or the code if no symbol available * (e.g.: format narrow = $, format wide = US$, code = USD) + * If no locale is provided, it uses the locale "en" by default * * @experimental i18n support is experimental. */ -export function getCurrencySymbol(code: string, format: 'wide' | 'narrow'): string { - const currency = CURRENCIES[code] || []; - const symbolNarrow = currency[1]; +export function getCurrencySymbol(code: string, format: 'wide' | 'narrow', locale = 'en'): string { + const currency = getLocaleCurrencies(locale)[code] || CURRENCIES_EN[code] || []; + const symbolNarrow = currency[CurrencyIndex.SymbolNarrow]; if (format === 'narrow' && typeof symbolNarrow === 'string') { return symbolNarrow; } - return currency[0] || code; + return currency[CurrencyIndex.Symbol] || code; } diff --git a/packages/common/src/i18n/locale_en.ts b/packages/common/src/i18n/locale_en.ts index 03337e974c..6d8aa0ffb5 100644 --- a/packages/common/src/i18n/locale_en.ts +++ b/packages/common/src/i18n/locale_en.ts @@ -48,5 +48,5 @@ export default [ '{1} \'at\' {0}', ], ['.', ',', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'], - ['#,##0.###', '#,##0%', '¤#,##0.00', '#E0'], '$', 'US Dollar', plural + ['#,##0.###', '#,##0%', '¤#,##0.00', '#E0'], '$', 'US Dollar', {}, plural ]; diff --git a/packages/common/src/pipes/number_pipe.ts b/packages/common/src/pipes/number_pipe.ts index bb4616c29a..9b49b6018d 100644 --- a/packages/common/src/pipes/number_pipe.ts +++ b/packages/common/src/pipes/number_pipe.ts @@ -7,8 +7,8 @@ */ import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core'; -import {formatNumber} from '../i18n/format_number'; -import {NumberFormatStyle, getCurrencySymbol, getLocaleCurrencyName, getLocaleCurrencySymbol} from '../i18n/locale_data_api'; +import {formatCurrency, formatDecimal, formatPercent} from '../i18n/format_number'; +import {getCurrencySymbol} from '../i18n/locale_data_api'; import {invalidPipeArgumentError} from './invalid_pipe_argument_error'; /** @@ -41,18 +41,16 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error'; export class DecimalPipe implements PipeTransform { constructor(@Inject(LOCALE_ID) private _locale: string) {} - transform(value: any, digits?: string, locale?: string): string|null { + transform(value: any, digitsInfo?: string, locale?: string): string|null { if (isEmpty(value)) return null; locale = locale || this._locale; - const {str, error} = formatNumber(value, locale, NumberFormatStyle.Decimal, digits); - - if (error) { - throw invalidPipeArgumentError(DecimalPipe, error); + try { + return formatDecimal(value, locale, digitsInfo); + } catch (error) { + throw invalidPipeArgumentError(DecimalPipe, error.message); } - - return str; } } @@ -65,7 +63,7 @@ export class DecimalPipe implements PipeTransform { * * Formats a number as percentage. * - * - `digitInfo` See {@link DecimalPipe} for detailed description. + * - `digitInfo` See {@link DecimalPipe} for a detailed description. * - `locale` is a `string` defining the locale to use (uses the current {@link LOCALE_ID} by * default) * @@ -79,18 +77,16 @@ export class DecimalPipe implements PipeTransform { export class PercentPipe implements PipeTransform { constructor(@Inject(LOCALE_ID) private _locale: string) {} - transform(value: any, digits?: string, locale?: string): string|null { + transform(value: any, digitsInfo?: string, locale?: string): string|null { if (isEmpty(value)) return null; locale = locale || this._locale; - const {str, error} = formatNumber(value, locale, NumberFormatStyle.Percent, digits); - - if (error) { - throw invalidPipeArgumentError(PercentPipe, error); + try { + return formatPercent(value, locale, digitsInfo); + } catch (error) { + throw invalidPipeArgumentError(PercentPipe, error.message); } - - return str; } } @@ -104,14 +100,15 @@ export class PercentPipe implements PipeTransform { * * - `currencyCode` is the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, such * as `USD` for the US dollar and `EUR` for the euro. - * - `display` indicates whether to show the currency symbol or the code. + * - `display` indicates whether to show the currency symbol, the code or a custom value * - `code`: use code (e.g. `USD`). * - `symbol`(default): use symbol (e.g. `$`). * - `symbol-narrow`: some countries have two symbols for their currency, one regular and one * narrow (e.g. the canadian dollar CAD has the symbol `CA$` and the symbol-narrow `$`). + * - `string`: use this value instead of a code or a symbol * - boolean (deprecated from v5): `true` for symbol and false for `code` * If there is no narrow symbol for the chosen currency, the regular symbol will be used. - * - `digitInfo` See {@link DecimalPipe} for detailed description. + * - `digitInfo` See {@link DecimalPipe} for a detailed description. * - `locale` is a `string` defining the locale to use (uses the current {@link LOCALE_ID} by * default) * @@ -127,7 +124,7 @@ export class CurrencyPipe implements PipeTransform { transform( value: any, currencyCode?: string, - display: 'code'|'symbol'|'symbol-narrow'|boolean = 'symbol', digits?: string, + display: 'code'|'symbol'|'symbol-narrow'|string|boolean = 'symbol', digitsInfo?: string, locale?: string): string|null { if (isEmpty(value)) return null; @@ -141,18 +138,20 @@ export class CurrencyPipe implements PipeTransform { display = display ? 'symbol' : 'code'; } - let currency = currencyCode || 'USD'; + let currency: string = currencyCode || 'USD'; if (display !== 'code') { - currency = getCurrencySymbol(currency, display === 'symbol' ? 'wide' : 'narrow'); + if (display === 'symbol' || display === 'symbol-narrow') { + currency = getCurrencySymbol(currency, display === 'symbol' ? 'wide' : 'narrow', locale); + } else { + currency = display; + } } - const {str, error} = formatNumber(value, locale, NumberFormatStyle.Currency, digits, currency); - - if (error) { - throw invalidPipeArgumentError(CurrencyPipe, error); + try { + return formatCurrency(value, locale, currency, currencyCode, digitsInfo); + } catch (error) { + throw invalidPipeArgumentError(CurrencyPipe, error.message); } - - return str; } } diff --git a/packages/common/test/i18n/locale_data_api_spec.ts b/packages/common/test/i18n/locale_data_api_spec.ts index ad7b673bf7..e7bc84ff06 100644 --- a/packages/common/test/i18n/locale_data_api_spec.ts +++ b/packages/common/test/i18n/locale_data_api_spec.ts @@ -11,6 +11,7 @@ import localeEn from '@angular/common/locales/en'; import localeFr from '@angular/common/locales/fr'; import localeZh from '@angular/common/locales/zh'; import localeFrCA from '@angular/common/locales/fr-CA'; +import localeEnAU from '@angular/common/locales/en-AU'; import {registerLocaleData} from '../../src/i18n/locale_data'; import {findLocaleData, getCurrencySymbol, getLocaleDateFormat, FormatWidth} from '../../src/i18n/locale_data_api'; @@ -24,6 +25,7 @@ import {findLocaleData, getCurrencySymbol, getLocaleDateFormat, FormatWidth} fro registerLocaleData(localeFr, 'fake-id'); registerLocaleData(localeFrCA, 'fake_Id2'); registerLocaleData(localeZh); + registerLocaleData(localeEnAU); }); describe('findLocaleData', () => { @@ -54,7 +56,7 @@ import {findLocaleData, getCurrencySymbol, getLocaleDateFormat, FormatWidth} fro }); }); - describe('getCurrencySymbolElseCode', () => { + describe('getting currency symbol', () => { it('should return the correct symbol', () => { expect(getCurrencySymbol('USD', 'wide')).toEqual('$'); expect(getCurrencySymbol('USD', 'narrow')).toEqual('$'); @@ -62,8 +64,13 @@ import {findLocaleData, getCurrencySymbol, getLocaleDateFormat, FormatWidth} fro expect(getCurrencySymbol('AUD', 'narrow')).toEqual('$'); expect(getCurrencySymbol('CRC', 'wide')).toEqual('CRC'); expect(getCurrencySymbol('CRC', 'narrow')).toEqual('₡'); - expect(getCurrencySymbol('FAKE', 'wide')).toEqual('FAKE'); - expect(getCurrencySymbol('FAKE', 'narrow')).toEqual('FAKE'); + expect(getCurrencySymbol('unexisting_ISO_code', 'wide')).toEqual('unexisting_ISO_code'); + expect(getCurrencySymbol('unexisting_ISO_code', 'narrow')).toEqual('unexisting_ISO_code'); + expect(getCurrencySymbol('USD', 'wide', 'en-AU')).toEqual('USD'); + expect(getCurrencySymbol('USD', 'narrow', 'en-AU')).toEqual('$'); + expect(getCurrencySymbol('AUD', 'wide', 'en-AU')).toEqual('$'); + expect(getCurrencySymbol('AUD', 'narrow', 'en-AU')).toEqual('$'); + expect(getCurrencySymbol('USD', 'wide', 'fr')).toEqual('$US'); }); }); diff --git a/packages/common/test/pipes/number_pipe_spec.ts b/packages/common/test/pipes/number_pipe_spec.ts index 9b9ee0ad24..a2a79f3dd8 100644 --- a/packages/common/test/pipes/number_pipe_spec.ts +++ b/packages/common/test/pipes/number_pipe_spec.ts @@ -125,7 +125,15 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin expect(pipe.transform(5.1234, 'CAD', 'symbol-narrow', '5.2-2')).toEqual('$00,005.12'); expect(pipe.transform(5.1234, 'CAD', 'symbol-narrow', '5.2-2', 'fr')) .toEqual('00 005,12 $'); - expect(pipe.transform(5.1234, 'FAKE', 'symbol')).toEqual('FAKE5.12'); + expect(pipe.transform(5, 'USD', 'symbol', '', 'fr')).toEqual('5,00 $US'); + }); + + it('should support any currency code name', () => { + // currency code is unknown, default formatting options will be used + expect(pipe.transform(5.1234, 'unexisting_ISO_code', 'symbol')) + .toEqual('unexisting_ISO_code5.12'); + // currency code is USD, the pipe will format based on USD but will display "Custom name" + expect(pipe.transform(5.1234, 'USD', 'Custom name')).toEqual('Custom name5.12'); }); it('should not support other objects', () => { diff --git a/tools/gulp-tasks/cldr/extract.js b/tools/gulp-tasks/cldr/extract.js index 3f16ba1f1b..e255a470b7 100644 --- a/tools/gulp-tasks/cldr/extract.js +++ b/tools/gulp-tasks/cldr/extract.js @@ -54,33 +54,36 @@ module.exports = (gulp, done) => { if (!fs.existsSync(RELATIVE_I18N_DATA_EXTRA_FOLDER)) { fs.mkdirSync(RELATIVE_I18N_DATA_EXTRA_FOLDER); } + + console.log(`Writing file ${I18N_FOLDER}/currencies.ts`); + fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/currencies.ts`, generateCurrenciesFile()); + + const baseCurrencies = generateBaseCurrencies(new cldrJs('en')); + // additional "en" file that will be included in common + console.log(`Writing file ${I18N_FOLDER}/locale_en.ts`); + const localeEnFile = generateLocale('en', new cldrJs('en'), baseCurrencies); + fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/locale_en.ts`, localeEnFile); + LOCALES.forEach((locale, index) => { const localeData = new cldrJs(locale); console.log(`${index + 1}/${LOCALES.length}`); console.log(`\t${I18N_DATA_FOLDER}/${locale}.ts`); - fs.writeFileSync(`${RELATIVE_I18N_DATA_FOLDER}/${locale}.ts`, generateLocale(locale, localeData)); + fs.writeFileSync(`${RELATIVE_I18N_DATA_FOLDER}/${locale}.ts`, locale === 'en'? localeEnFile : generateLocale(locale, localeData, baseCurrencies)); console.log(`\t${I18N_DATA_EXTRA_FOLDER}/${locale}.ts`); fs.writeFileSync(`${RELATIVE_I18N_DATA_EXTRA_FOLDER}/${locale}.ts`, generateLocaleExtra(locale, localeData)); }); console.log(`${LOCALES.length} locale files generated.`); - // additional "en" file that will be included in common - console.log(`Writing file ${I18N_FOLDER}/locale_en.ts`); - fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/locale_en.ts`, generateLocale('en', new cldrJs('en'))); - - console.log(`Writing file ${I18N_FOLDER}/currencies.ts`); - fs.writeFileSync(`${RELATIVE_I18N_FOLDER}/currencies.ts`, generateCurrencies()); - console.log(`All i18n cldr files have been generated, formatting files..."`); const format = require('gulp-clang-format'); const clangFormat = require('clang-format'); return gulp .src([ - `${I18N_DATA_FOLDER}/**/*.ts`, - `${I18N_FOLDER}/currencies.ts`, - `${I18N_FOLDER}/locale_en.ts` - ], {base: '.'}) + `${I18N_DATA_FOLDER}/**/*.ts`, + `${I18N_FOLDER}/currencies.ts`, + `${I18N_FOLDER}/locale_en.ts` + ], {base: '.'}) .pipe(format.format('file', clangFormat)) .pipe(gulp.dest('.')); }; @@ -88,16 +91,17 @@ module.exports = (gulp, done) => { /** * Generate file that contains basic locale data */ -function generateLocale(locale, localeData) { +function generateLocale(locale, localeData, baseCurrencies) { // [ localeId, dateTime, number, currency, pluralCase ] let data = stringify([ locale, ...getDateTimeTranslations(localeData), ...getDateTimeSettings(localeData), ...getNumberSettings(localeData), - ...getCurrencySettings(locale, localeData) - ]) - // We remove "undefined" added by spreading arrays when there is no value + ...getCurrencySettings(locale, localeData), + generateLocaleCurrencies(localeData, baseCurrencies) + ], true) + // We remove "undefined" added by spreading arrays when there is no value .replace(/undefined/g, ''); // adding plural function after, because we don't want it as a string @@ -149,11 +153,12 @@ export default ${stringify(dayPeriodsSupplemental).replace(/undefined/g, '')}; } /** - * Generate a file that contains the list of currencies and their symbols + * Generate a list of currencies to be used as a based for other currencies + * e.g.: {'ARS': [, '$'], 'AUD': ['A$', '$'], ...} */ -function generateCurrencies() { - const currenciesData = new cldrJs('en').main('numbers/currencies'); - const currencies = []; +function generateBaseCurrencies(localeData, addDigits) { + const currenciesData = localeData.main('numbers/currencies'); + const currencies = {}; Object.keys(currenciesData).forEach(key => { let symbolsArray = []; const symbol = currenciesData[key].symbol; @@ -169,14 +174,52 @@ function generateCurrencies() { } } if (symbolsArray.length > 0) { - currencies.push(` '${key}': ${stringify(symbolsArray)},\n`); + currencies[key] = symbolsArray; } }); + return currencies; +} + +/** + * To minimize the file even more, we only output the differences compared to the base currency + */ +function generateLocaleCurrencies(localeData, baseCurrencies) { + const currenciesData = localeData.main('numbers/currencies'); + const currencies = {}; + Object.keys(currenciesData).forEach(code => { + let symbolsArray = []; + const symbol = currenciesData[code].symbol; + const symbolNarrow = currenciesData[code]['symbol-alt-narrow']; + if (symbol && symbol !== code) { + symbolsArray.push(symbol); + } + if (symbolNarrow && symbolNarrow !== symbol) { + if (symbolsArray.length > 0) { + symbolsArray.push(symbolNarrow); + } else { + symbolsArray = [, symbolNarrow]; + } + } + + // if locale data are different, set the value + if ((baseCurrencies[code] || []).toString() !== symbolsArray.toString()) { + currencies[code] = symbolsArray; + } + }); + return currencies; +} + +/** + * Generate a file that contains the list of currencies and their symbols + */ +function generateCurrenciesFile() { + const baseCurrencies = generateBaseCurrencies(new cldrJs('en'), true); return `${HEADER} +export type CurrenciesSymbols = [string] | [string | undefined, string]; + /** @internal */ -export const CURRENCIES: {[code: string]: (string | undefined)[]} = { -${currencies.join('')}}; +export const CURRENCIES_EN: {[code: string]: CurrenciesSymbols} = ${stringify(baseCurrencies, true)}; `; } diff --git a/tools/gulp-tasks/cldr/util.js b/tools/gulp-tasks/cldr/util.js index b8f943b50c..ae8059031e 100644 --- a/tools/gulp-tasks/cldr/util.js +++ b/tools/gulp-tasks/cldr/util.js @@ -4,12 +4,10 @@ * Like JSON.stringify, but without double quotes around keys, and without null instead of undefined * values * Based on https://github.com/json5/json5/blob/master/lib/json5.js + * Use option "quoteKeys" to preserve quotes for keys */ -module.exports.stringify = function(obj, replacer, space) { - if (replacer && (typeof(replacer) !== 'function' && !isArray(replacer))) { - throw new Error('Replacer must be a function or an array'); - } - var getReplacedValueOrUndefined = function(holder, key, isTopLevel) { +module.exports.stringify = function(obj, quoteKeys) { + var getReplacedValueOrUndefined = function(holder, key) { var value = holder[key]; // Replace the value with its toJSON value first, if possible @@ -17,21 +15,7 @@ module.exports.stringify = function(obj, replacer, space) { value = value.toJSON(); } - // If the user-supplied replacer if a function, call it. If it's an array, check objects' string - // keys for - // presence in the array (removing the key/value pair from the resulting JSON if the key is - // missing). - if (typeof(replacer) === 'function') { - return replacer.call(holder, key, value); - } else if (replacer) { - if (isTopLevel || isArray(holder) || replacer.indexOf(key) >= 0) { - return value; - } else { - return undefined; - } - } else { - return value; - } + return value; }; function isWordChar(c) { @@ -80,34 +64,6 @@ module.exports.stringify = function(obj, replacer, space) { } } - function makeIndent(str, num, noNewLine) { - if (!str) { - return ''; - } - // indentation no more than 10 chars - if (str.length > 10) { - str = str.substring(0, 10); - } - - var indent = noNewLine ? '' : '\n'; - for (var i = 0; i < num; i++) { - indent += str; - } - - return indent; - } - - var indentStr; - if (space) { - if (typeof space === 'string') { - indentStr = space; - } else if (typeof space === 'number' && space >= 0) { - indentStr = makeIndent(' ', space, true); - } else { - // ignore space parameter - } - } - // Copied from Crokford's implementation of JSON // See // https://github.com/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js#L195 @@ -123,24 +79,24 @@ module.exports.stringify = function(obj, replacer, space) { '"' : '\\"', '\\': '\\\\' }; - function escapeString(str) { + function escapeString(str, keepQuotes) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; - return escapable.test(str) ? '"' + str.replace(escapable, function(a) { + return escapable.test(str) && !keepQuotes ? '"' + str.replace(escapable, function(a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + str + '"'; } // End - function internalStringify(holder, key, isTopLevel) { + function internalStringify(holder, key) { var buffer, res; // Replace the value, if necessary - var obj_part = getReplacedValueOrUndefined(holder, key, isTopLevel); + var obj_part = getReplacedValueOrUndefined(holder, key); if (obj_part && !isDate(obj_part)) { // unbox objects @@ -169,8 +125,7 @@ module.exports.stringify = function(obj, replacer, space) { objStack.push(obj_part); for (var i = 0; i < obj_part.length; i++) { - res = internalStringify(obj_part, i, false); - buffer += makeIndent(indentStr, objStack.length); + res = internalStringify(obj_part, i); if (res === null) { buffer += 'null'; } else if (typeof res === 'undefined') { // modified to support empty array values @@ -180,14 +135,9 @@ module.exports.stringify = function(obj, replacer, space) { } if (i < obj_part.length - 1) { buffer += ','; - } else if (indentStr) { - buffer += '\n'; } } objStack.pop(); - if (obj_part.length) { - buffer += makeIndent(indentStr, objStack.length, true); - } buffer += ']'; } else { checkForCircular(obj_part); @@ -197,19 +147,16 @@ module.exports.stringify = function(obj, replacer, space) { for (var prop in obj_part) { if (obj_part.hasOwnProperty(prop)) { var value = internalStringify(obj_part, prop, false); - isTopLevel = false; if (typeof value !== 'undefined' && value !== null) { - buffer += makeIndent(indentStr, objStack.length); nonEmpty = true; - key = isWord(prop) ? prop : escapeString(prop); - buffer += key + ':' + (indentStr ? ' ' : '') + value + ','; + key = isWord(prop) && !quoteKeys ? prop : escapeString(prop, quoteKeys); + buffer += key + ':' + value + ','; } } } objStack.pop(); if (nonEmpty) { - buffer = buffer.substring(0, buffer.length - 1) + - makeIndent(indentStr, objStack.length) + '}'; + buffer = buffer.substring(0, buffer.length - 1) + '}'; } else { buffer = '{}'; } @@ -228,5 +175,5 @@ module.exports.stringify = function(obj, replacer, space) { if (obj === undefined) { return getReplacedValueOrUndefined(topLevelHolder, '', true); } - return internalStringify(topLevelHolder, '', true); + return internalStringify(topLevelHolder, ''); }; diff --git a/tools/public_api_guard/common/common.d.ts b/tools/public_api_guard/common/common.d.ts index 7edceead7b..523109388e 100644 --- a/tools/public_api_guard/common/common.d.ts +++ b/tools/public_api_guard/common/common.d.ts @@ -18,7 +18,7 @@ export declare class CommonModule { /** @stable */ export declare class CurrencyPipe implements PipeTransform { constructor(_locale: string); - transform(value: any, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | boolean, digits?: string, locale?: string): string | null; + transform(value: any, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string | boolean, digitsInfo?: string, locale?: string): string | null; } /** @stable */ @@ -30,7 +30,7 @@ export declare class DatePipe implements PipeTransform { /** @stable */ export declare class DecimalPipe implements PipeTransform { constructor(_locale: string); - transform(value: any, digits?: string, locale?: string): string | null; + transform(value: any, digitsInfo?: string, locale?: string): string | null; } /** @stable */ @@ -79,7 +79,7 @@ export declare enum FormStyle { } /** @experimental */ -export declare function getCurrencySymbol(code: string, format: 'wide' | 'narrow'): string; +export declare function getCurrencySymbol(code: string, format: 'wide' | 'narrow', locale?: string): string; /** @experimental */ export declare function getLocaleCurrencyName(locale: string): string | null; @@ -386,7 +386,7 @@ export declare class PathLocationStrategy extends LocationStrategy { /** @stable */ export declare class PercentPipe implements PipeTransform { constructor(_locale: string); - transform(value: any, digits?: string, locale?: string): string | null; + transform(value: any, digitsInfo?: string, locale?: string): string | null; } /** @stable */