feat: 🎸 add custom formats support

BREAKING CHANGE: This PR modifies the formatter method arguments.
This commit is contained in:
Christian Kaisermann 2019-11-11 23:15:09 -03:00
parent f056e70ec5
commit d483244a9f
6 changed files with 1322 additions and 1187 deletions

View File

@ -1,5 +1,5 @@
<script> <script>
import { locale, _ } from 'svelte-i18n' import { locale, _ } from '../../src/index.js'
let name = '' let name = ''
let pluralN = 2 let pluralN = 2
@ -20,18 +20,19 @@
bind:value={name} /> bind:value={name} />
<br /> <br />
<h1>{$_.title('greeting.message', { name })}</h1> <h1>{$_.title('greeting.message', { values: { name } })}</h1>
<br /> <br />
<input type="range" min="0" max="5" step="1" bind:value={pluralN} /> <input type="range" min="0" max="5" step="1" bind:value={pluralN} />
<h2>Plural: {$_('photos', { n: pluralN })}</h2> <h2>Plural: {$_('photos', { values: { n: pluralN } })}</h2>
<br /> <br />
<input type="range" min="100" max="100000000" step="10000" bind:value={catsN} /> <input type="range" min="100" max="100000000" step="10000" bind:value={catsN} />
<h2>Number: {$_('cats', { n: catsN })}</h2> <h2>Number: {$_('cats', { values: { n: catsN } })}</h2>
<br /> <br />
<h2>Number util: {$_.number(catsN)}</h2> <h2>Number util: {$_.number(catsN)}</h2>
<h2>Number util: {$_.number(10000000, { format: 'compactShort' })}</h2>
<br /> <br />
<h2>Date util: {$_.date(date, 'short')}</h2> <h2>Date util: {$_.date(date, 'short')}</h2>
@ -41,5 +42,5 @@
<br /> <br />
<button on:click={() => locale.set(oppositeLocale)}> <button on:click={() => locale.set(oppositeLocale)}>
{$_('switch.lang', null, oppositeLocale)} {$_('switch.lang', { locale: oppositeLocale })}
</button> </button>

View File

@ -1,4 +1,18 @@
import { locale, dictionary, getClientLocale } from 'svelte-i18n' import {
locale,
dictionary,
getClientLocale,
addCustomFormats,
} from '../../src/index.js'
addCustomFormats({
number: {
compact: {
notation: 'compact',
compactDisplay: 'long',
},
},
})
// defining a locale dictionary // defining a locale dictionary
dictionary.set({ dictionary.set({

2291
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -53,27 +53,27 @@
"collectCoverage": true "collectCoverage": true
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.5", "@babel/core": "^7.7.2",
"@babel/preset-env": "^7.4.5", "@babel/preset-env": "^7.7.1",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.8.0", "babel-jest": "^24.9.0",
"eslint": "^5.16.0", "eslint": "^6.6.0",
"eslint-config-kaisermann": "0.0.3", "eslint-config-kaisermann": "0.0.3",
"jest": "^24.8.0", "jest": "^24.9.0",
"prettier": "^1.18.2", "prettier": "^1.19.1",
"rollup": "^1.15.1", "rollup": "^1.26.5",
"rollup-plugin-auto-external": "^2.0.0", "rollup-plugin-auto-external": "^2.0.0",
"rollup-plugin-commonjs": "^10.0.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.0.1", "rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^5.0.0", "rollup-plugin-terser": "^5.1.2",
"svelte": "^3.5.1" "svelte": "^3.14.0"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^3.5.1" "svelte": "^3.14.0"
}, },
"dependencies": { "dependencies": {
"intl-messageformat": "^4.0.1", "intl-messageformat": "^7.5.2",
"micro-memoize": "^4.0.7", "micro-memoize": "^4.0.8",
"object-resolve-path": "^1.1.1" "object-resolve-path": "^1.1.1"
} }
} }

View File

@ -1,4 +1,4 @@
import { writable, derived } from 'svelte/store.mjs' import { writable, derived } from 'svelte/store/index.js'
import resolvePath from 'object-resolve-path' import resolvePath from 'object-resolve-path'
import IntlMessageFormat from 'intl-messageformat' import IntlMessageFormat from 'intl-messageformat'
import memoize from 'micro-memoize' import memoize from 'micro-memoize'
@ -8,7 +8,22 @@ import { capital, title, upper, lower, getClientLocale } from './utils.js'
let currentLocale let currentLocale
let currentDictionary let currentDictionary
const getAvailableLocale = newLocale => { const customFormats = {
number: {
scientific: { notation: 'scientific' },
engineering: { notation: 'engineering' },
compactLong: { notation: 'compact', compactDisplay: 'long' },
compactShort: { notation: 'compact', compactDisplay: 'short' },
},
}
function addCustomFormats(formats) {
if ('number' in formats) Object.assign(customFormats.number, formats.number)
if ('date' in formats) Object.assign(customFormats.date, formats.date)
if ('time' in formats) Object.assign(customFormats.time, formats.time)
}
function getAvailableLocale(newLocale) {
if (currentDictionary[newLocale]) return newLocale if (currentDictionary[newLocale]) return newLocale
// istanbul ignore else // istanbul ignore else
@ -24,7 +39,7 @@ const getAvailableLocale = newLocale => {
} }
const getMessageFormatter = memoize( const getMessageFormatter = memoize(
(message, locale, formats) => new IntlMessageFormat(message, locale, formats), (message, locale) => new IntlMessageFormat(message, locale, customFormats),
) )
const lookupMessage = memoize((path, locale) => { const lookupMessage = memoize((path, locale) => {
@ -34,37 +49,41 @@ const lookupMessage = memoize((path, locale) => {
) )
}) })
const formatMessage = (message, interpolations, locale = currentLocale) => { function formatString(string, { values, locale = currentLocale } = {}) {
return getMessageFormatter(message, locale).format(interpolations) return getMessageFormatter(string, locale).format(values)
} }
const getLocalizedMessage = (path, interpolations, locale = currentLocale) => { function formatMessage(path, { values, locale = currentLocale } = {}) {
if (typeof interpolations === 'string') {
locale = interpolations
interpolations = undefined
}
const message = lookupMessage(path, locale) const message = lookupMessage(path, locale)
if (!message) return path if (!message) {
if (!interpolations) return message console.warn(
`[svelte-i18n] The message "${path}" was not found in the locale "${locale}".`,
)
return path
}
return getMessageFormatter(message, locale).format(interpolations) if (!values) return message
return getMessageFormatter(message, locale).format(values)
} }
getLocalizedMessage.time = (t, format = 'short', locale) => formatMessage.time = (t, { format = 'short' } = {}) =>
formatMessage(`{t,time,${format}}`, { t }, locale) formatString(`{t,time,${format}}`, { values: { t } })
getLocalizedMessage.date = (d, format = 'short', locale) =>
formatMessage(`{d,date,${format}}`, { d }, locale) formatMessage.date = (d, { format = 'short' } = {}) =>
getLocalizedMessage.number = (n, locale) => formatString(`{d,date,${format}}`, { values: { d } })
formatMessage('{n,number}', { n }, locale)
getLocalizedMessage.capital = (path, interpolations, locale) => formatMessage.number = (n, { format } = {}) =>
capital(getLocalizedMessage(path, interpolations, locale)) formatString(`{n,number,${format}}`, { values: { n } })
getLocalizedMessage.title = (path, interpolations, locale) =>
title(getLocalizedMessage(path, interpolations, locale)) formatMessage.capital = (path, options) => capital(formatMessage(path, options))
getLocalizedMessage.upper = (path, interpolations, locale) =>
upper(getLocalizedMessage(path, interpolations, locale)) formatMessage.title = (path, options) => title(formatMessage(path, options))
getLocalizedMessage.lower = (path, interpolations, locale) =>
lower(getLocalizedMessage(path, interpolations, locale)) formatMessage.upper = (path, options) => upper(formatMessage(path, options))
formatMessage.lower = (path, options) => lower(formatMessage(path, options))
const dictionary = writable({}) const dictionary = writable({})
dictionary.subscribe(newDictionary => { dictionary.subscribe(newDictionary => {
@ -79,14 +98,22 @@ locale.set = newLocale => {
return localeSet(availableLocale) return localeSet(availableLocale)
} }
console.warn(`[svelte-i18n] Locale "${newLocale}" not found.`) throw Error(`[svelte-i18n] Locale "${newLocale}" not found.`)
return localeSet(newLocale)
} }
locale.update = fn => localeSet(fn(currentLocale)) locale.update = fn => localeSet(fn(currentLocale))
locale.subscribe(newLocale => { locale.subscribe(newLocale => {
currentLocale = newLocale currentLocale = newLocale
}) })
const format = derived([locale, dictionary], () => getLocalizedMessage) const format = derived([locale, dictionary], () => formatMessage)
export { locale, format as _, format, dictionary, getClientLocale } export {
locale,
format as _,
format,
formatString,
dictionary,
getClientLocale,
customFormats,
addCustomFormats,
}

View File

@ -1,4 +1,11 @@
import { dictionary, locale, format, getClientLocale } from '../src/index' import {
dictionary,
locale,
format,
getClientLocale,
addCustomFormats,
customFormats,
} from '../src/index.js'
let _ let _
let currentLocale let currentLocale
@ -48,9 +55,10 @@ it('should fallback to existing locale', () => {
locale.set('en-US') locale.set('en-US')
expect(currentLocale).toBe('en') expect(currentLocale).toBe('en')
})
locale.set('non-existent') it("should throw an error if locale doesn't exist", () => {
expect(currentLocale).toBe('non-existent') expect(() => locale.set('FOO')).toThrow()
}) })
it('should fallback to message id if id is not found', () => { it('should fallback to message id if id is not found', () => {
@ -66,20 +74,20 @@ it('should translate to current locale', () => {
}) })
it('should translate to passed locale', () => { it('should translate to passed locale', () => {
expect(_('switch.lang', 'pt')).toBe('Trocar idioma') expect(_('switch.lang', { locale: 'pt' })).toBe('Trocar idioma')
expect(_('switch.lang', 'en')).toBe('Switch language') expect(_('switch.lang', { locale: 'en' })).toBe('Switch language')
}) })
it('should interpolate message with variables', () => { it('should interpolate message with variables', () => {
expect(_('greeting.message', { name: 'Chris' })).toBe( expect(_('greeting.message', { values: { name: 'Chris' } })).toBe(
'Hello Chris, how are you?', 'Hello Chris, how are you?',
) )
}) })
it('should interpolate message with variables according to passed locale', () => { it('should interpolate message with variables according to passed locale', () => {
expect(_('greeting.message', { name: 'Chris' }, 'pt')).toBe( expect(
'Olá Chris, como vai?', _('greeting.message', { values: { name: 'Chris' }, locale: 'pt' }),
) ).toBe('Olá Chris, como vai?')
}) })
describe('utilities', () => { describe('utilities', () => {
@ -141,12 +149,12 @@ describe('utilities', () => {
it('should format a time value', () => { it('should format a time value', () => {
locale.set('en') locale.set('en')
expect(_.time(date)).toBe('11:45 PM') expect(_.time(date)).toBe('11:45 PM')
expect(_.time(date, 'medium')).toBe('11:45:00 PM') expect(_.time(date, { format: 'medium' })).toBe('11:45:00 PM')
}) })
it('should format a date value', () => { it('should format a date value', () => {
expect(_.date(date)).toBe('4/24/19') expect(_.date(date)).toBe('4/24/19')
expect(_.date(date, 'medium')).toBe('Apr 24, 2019') expect(_.date(date, { format: 'medium' })).toBe('Apr 24, 2019')
}) })
// number // number
it('should format a date value', () => { it('should format a date value', () => {
@ -154,3 +162,45 @@ describe('utilities', () => {
}) })
}) })
}) })
describe('custom formats', () => {
beforeAll(() => {
locale.set('pt-BR')
})
it('should have default number custom formats', () => {
expect(customFormats.number).toMatchObject({
scientific: { notation: 'scientific' },
engineering: { notation: 'engineering' },
compactLong: { notation: 'compact', compactDisplay: 'long' },
compactShort: { notation: 'compact', compactDisplay: 'short' },
})
})
it('should allow to add custom formats', () => {
addCustomFormats({
number: {
usd: { style: 'currency', currency: 'USD' },
},
})
expect(customFormats.number).toMatchObject({
usd: { style: 'currency', currency: 'USD' },
})
})
it('should format messages with custom formats', () => {
addCustomFormats({
number: {
usd: { style: 'currency', currency: 'USD' },
brl: { style: 'currency', currency: 'BRL' },
},
})
expect(_.number(123123123, { format: 'usd' })).toContain('US$')
expect(_.number(123123123, { format: 'usd' })).toContain('123,123,123.00')
expect(_.number(123123123, { format: 'brl' })).toContain('R$')
expect(_.number(123123123, { format: 'brl' })).toContain('123,123,123.00')
})
})