From 3ada1aef83917c10f9723e7c1afd7da23c27baf5 Mon Sep 17 00:00:00 2001 From: Marcos Gil Date: Thu, 12 Nov 2020 17:08:57 +0100 Subject: [PATCH] feat(localize): date and number postProcessors --- .changeset/friendly-islands-repair.md | 5 + packages/localize/src/LocalizeManager.js | 24 +++++ packages/localize/src/date/formatDate.js | 20 ++++ packages/localize/src/number/formatNumber.js | 22 ++++ .../localize/test/date/formatDate.test.js | 100 ++++++++++++++++++ .../localize/test/number/formatNumber.test.js | 65 ++++++++++++ .../localize/types/LocalizeMixinTypes.d.ts | 12 +++ 7 files changed, 248 insertions(+) create mode 100644 .changeset/friendly-islands-repair.md diff --git a/.changeset/friendly-islands-repair.md b/.changeset/friendly-islands-repair.md new file mode 100644 index 000000000..b243353bb --- /dev/null +++ b/.changeset/friendly-islands-repair.md @@ -0,0 +1,5 @@ +--- +'@lion/localize': minor +--- + +Localize set date and number postProcessors for locale diff --git a/packages/localize/src/LocalizeManager.js b/packages/localize/src/LocalizeManager.js index 33eff676f..ba7ecd7e8 100644 --- a/packages/localize/src/LocalizeManager.js +++ b/packages/localize/src/LocalizeManager.js @@ -6,6 +6,9 @@ import isLocalizeESModule from './isLocalizeESModule.js'; * @typedef {import('../types/LocalizeMixinTypes').NamespaceObject} NamespaceObject */ +/** @typedef {import('../types/LocalizeMixinTypes').DatePostProcessor} DatePostProcessor */ +/** @typedef {import('../types/LocalizeMixinTypes').NumberPostProcessor} NumberPostProcessor */ + /** * `LocalizeManager` manages your translations (includes loading) */ @@ -30,6 +33,13 @@ export class LocalizeManager { this.formatNumberOptions = { returnIfNaN: '', + /** @type {Map} */ + postProcessors: new Map(), + }; + + this.formatDateOptions = { + /** @type {Map} */ + postProcessors: new Map(), }; /** @@ -523,4 +533,18 @@ export class LocalizeManager { return String(result || ''); } + + /** + * @param {{locale:string, postProcessor:DatePostProcessor}} options + */ + setDatePostProcessorForLocale({ locale, postProcessor }) { + this.formatDateOptions.postProcessors.set(locale, postProcessor); + } + + /** + * @param {{locale:string, postProcessor:NumberPostProcessor}} options + */ + setNumberPostProcessorForLocale({ locale, postProcessor }) { + this.formatNumberOptions.postProcessors.set(locale, postProcessor); + } } diff --git a/packages/localize/src/date/formatDate.js b/packages/localize/src/date/formatDate.js index 08d33cda1..9cee2a552 100644 --- a/packages/localize/src/date/formatDate.js +++ b/packages/localize/src/date/formatDate.js @@ -1,6 +1,9 @@ +import { localize } from '../localize.js'; import { getLocale } from './getLocale.js'; import { normalizeIntlDate } from './normalizeIntlDate.js'; +/** @typedef {import('../../types/LocalizeMixinTypes').DatePostProcessor} DatePostProcessor */ + /** * Formats date based on locale and options * @@ -33,5 +36,22 @@ export function formatDate(date, options) { } catch (e) { formattedDate = ''; } + + if (localize.formatDateOptions.postProcessors.size > 0) { + Array.from(localize.formatDateOptions.postProcessors).forEach(([locale, fn]) => { + if (locale === computedLocale) { + formattedDate = fn(formattedDate); + } + }); + } + + if (formatOptions.postProcessors && formatOptions.postProcessors.size > 0) { + Array.from(formatOptions.postProcessors).forEach(([locale, fn]) => { + if (locale === computedLocale) { + formattedDate = fn(formattedDate); + } + }); + } + return normalizeIntlDate(formattedDate, computedLocale, formatOptions); } diff --git a/packages/localize/src/number/formatNumber.js b/packages/localize/src/number/formatNumber.js index f1bed42f4..829873301 100644 --- a/packages/localize/src/number/formatNumber.js +++ b/packages/localize/src/number/formatNumber.js @@ -1,6 +1,9 @@ import { localize } from '../localize.js'; +import { getLocale } from './getLocale.js'; import { formatNumberToParts } from './formatNumberToParts.js'; +/** @typedef {import('../../types/LocalizeMixinTypes').NumberPostProcessor} NumberPostProcessor */ + /** * Formats a number based on locale and options. It uses Intl for the formatting. * @@ -27,5 +30,24 @@ export function formatNumber(number, options = /** @type {FormatOptions} */ ({}) const part = /** @type {FormatNumberPart} */ (formattedToParts[i]); printNumberOfParts += part.value; } + + const computedLocale = getLocale(options && options.locale); + + if (localize.formatNumberOptions.postProcessors.size > 0) { + Array.from(localize.formatNumberOptions.postProcessors).forEach(([locale, fn]) => { + if (locale === computedLocale) { + printNumberOfParts = fn(printNumberOfParts); + } + }); + } + + if (options.postProcessors && options.postProcessors.size > 0) { + Array.from(options.postProcessors).forEach(([locale, fn]) => { + if (locale === computedLocale) { + printNumberOfParts = fn(printNumberOfParts); + } + }); + } + return printNumberOfParts; } diff --git a/packages/localize/test/date/formatDate.test.js b/packages/localize/test/date/formatDate.test.js index 7cc6a8ffa..b73f96c70 100644 --- a/packages/localize/test/date/formatDate.test.js +++ b/packages/localize/test/date/formatDate.test.js @@ -232,4 +232,104 @@ describe('formatDate', () => { // @ts-ignore tests what happens if you use a wrong type expect(formatDate(date)).to.equal(''); }); + + describe('Date post processors', () => { + /** + * Uppercase processor + * + * @param {string} str + * @returns {string} + */ + const upperCaseProcessor = str => { + return str.toUpperCase(); + }; + + /** + * Lowercase processor + * + * @param {string} str + * @returns {string} + */ + const lowerCaseProcessor = str => { + return str.toLocaleLowerCase(); + }; + + it('displays the appropriate date after post processor set in options', async () => { + const testDate = new Date('2012/05/21'); + const postProcessors = new Map(); + postProcessors.set('nl-NL', upperCaseProcessor); + postProcessors.set('de-DE', lowerCaseProcessor); + + const options = { + weekday: 'long', + year: 'numeric', + month: 'long', + day: '2-digit', + postProcessors, + }; + + // locale is en-GB + expect(formatDate(testDate, options)).to.equal('Monday, 21 May 2012'); + localize.locale = 'nl-NL'; + expect(formatDate(testDate, options)).to.equal('MAANDAG 21 MEI 2012'); + localize.locale = 'de-DE'; + expect(formatDate(testDate, options)).to.equal('montag, 21. mai 2012'); + localize.locale = 'en-US'; + expect(formatDate(testDate, options)).to.equal('Monday, May 21, 2012'); + }); + + it('displays the appropriate date after post processor set in localize', async () => { + const testDate = new Date('2012/05/21'); + const options = { + weekday: 'long', + year: 'numeric', + month: 'long', + day: '2-digit', + }; + localize.setDatePostProcessorForLocale({ + locale: 'nl-NL', + postProcessor: upperCaseProcessor, + }); + localize.setDatePostProcessorForLocale({ + locale: 'de-DE', + postProcessor: upperCaseProcessor, + }); + + expect(formatDate(testDate, options)).to.equal('Monday, 21 May 2012'); + localize.locale = 'nl-NL'; + expect(formatDate(testDate, options)).to.equal('MAANDAG 21 MEI 2012'); + localize.locale = 'de-DE'; + expect(formatDate(testDate, options)).to.equal('MONTAG, 21. MAI 2012'); + localize.locale = 'en-US'; + expect(formatDate(testDate, options)).to.equal('Monday, May 21, 2012'); + }); + + it('displays the appropriate date after post processors set in options and localize', async () => { + const testDate = new Date('2012/05/21'); + const postProcessors = new Map(); + postProcessors.set('nl-NL', upperCaseProcessor); + postProcessors.set('de-DE', upperCaseProcessor); + + const options = { + weekday: 'long', + year: 'numeric', + month: 'long', + day: '2-digit', + postProcessors, + }; + + localize.setDatePostProcessorForLocale({ + locale: 'de-DE', + postProcessor: lowerCaseProcessor, + }); + + expect(formatDate(testDate, options)).to.equal('Monday, 21 May 2012'); + localize.locale = 'nl-NL'; + expect(formatDate(testDate, options)).to.equal('MAANDAG 21 MEI 2012'); + localize.locale = 'de-DE'; + expect(formatDate(testDate, options)).to.equal('MONTAG, 21. MAI 2012'); + localize.locale = 'en-US'; + expect(formatDate(testDate, options)).to.equal('Monday, May 21, 2012'); + }); + }); }); diff --git a/packages/localize/test/number/formatNumber.test.js b/packages/localize/test/number/formatNumber.test.js index 9d75ba521..e47ebc5ad 100644 --- a/packages/localize/test/number/formatNumber.test.js +++ b/packages/localize/test/number/formatNumber.test.js @@ -362,4 +362,69 @@ describe('formatNumber', () => { }); }); }); + + describe('postProcessors', () => { + /** @type {Map} */ + let savedpostProcessors; + beforeEach(() => { + savedpostProcessors = localize.formatNumberOptions.postProcessors; + }); + afterEach(() => { + localize.formatNumberOptions.postProcessors = savedpostProcessors; + }); + + /** + * Comma to spaces processor + * + * @param {string} str + * @returns {string} + */ + const commaToSpaceProcessor = str => { + return str.replace(/,/g, ' '); + }; + + /** + * First space to dot processor + * + * @param {string} str + * @returns {string} + */ + const firstSpaceToDotProcessor = str => { + return str.replace(' ', '.'); + }; + + it('uses `options.postProcessors`', () => { + const postProcessors = new Map(); + postProcessors.set('en-GB', commaToSpaceProcessor); + expect( + formatNumber(112345678, { + postProcessors, + }), + ).to.equal('112 345 678'); + }); + + it('uses `localize.formatNumberOptions.postProcessors`', () => { + localize.setNumberPostProcessorForLocale({ + locale: 'en-GB', + postProcessor: commaToSpaceProcessor, + }); + + expect(formatNumber(112345678)).to.equal('112 345 678'); + }); + + it('uses `options.postProcessors` and `localize.formatNumberOptions.postProcessors`', () => { + const postProcessors = new Map(); + postProcessors.set('en-GB', commaToSpaceProcessor); + + localize.setNumberPostProcessorForLocale({ + locale: 'en-GB', + postProcessor: firstSpaceToDotProcessor, + }); + expect( + formatNumber(112345678, { + postProcessors, + }), + ).to.equal('112 345 678'); + }); + }); }); diff --git a/packages/localize/types/LocalizeMixinTypes.d.ts b/packages/localize/types/LocalizeMixinTypes.d.ts index f79d5ef99..dde294a95 100644 --- a/packages/localize/types/LocalizeMixinTypes.d.ts +++ b/packages/localize/types/LocalizeMixinTypes.d.ts @@ -6,6 +6,10 @@ export interface FormatNumberPart { value: string; } +declare function DatePostProcessorImplementation(date: string): string; + +export type DatePostProcessor = typeof DatePostProcessorImplementation; + // Take the DateTimeFormat and add the missing resolved options as well as optionals export declare interface FormatDateOptions extends Intl.DateTimeFormatOptions { locale?: string; @@ -17,8 +21,14 @@ export declare interface FormatDateOptions extends Intl.DateTimeFormatOptions { returnIfNaN?: string; decimalSeparator?: string; mode?: 'pasted' | 'auto'; + + postProcessors?: Map; } +declare function NumberPostProcessorImplementation(number: string): string; + +export type NumberPostProcessor = typeof NumberPostProcessorImplementation; + // Take the DateTimeFormat and add the missing resolved options as well as optionals, and our own export declare interface FormatNumberOptions extends Intl.NumberFormatOptions { locale?: string; @@ -27,6 +37,8 @@ export declare interface FormatNumberOptions extends Intl.NumberFormatOptions { returnIfNaN?: string; decimalSeparator?: string; mode?: 'pasted' | 'auto'; + + postProcessors?: Map; } interface StringToFunctionMap {