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>
import { locale, _ } from 'svelte-i18n'
import { locale, _ } from '../../src/index.js'
let name = ''
let pluralN = 2
@ -20,18 +20,19 @@
bind:value={name} />
<br />
<h1>{$_.title('greeting.message', { name })}</h1>
<h1>{$_.title('greeting.message', { values: { name } })}</h1>
<br />
<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 />
<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 />
<h2>Number util: {$_.number(catsN)}</h2>
<h2>Number util: {$_.number(10000000, { format: 'compactShort' })}</h2>
<br />
<h2>Date util: {$_.date(date, 'short')}</h2>
@ -41,5 +42,5 @@
<br />
<button on:click={() => locale.set(oppositeLocale)}>
{$_('switch.lang', null, oppositeLocale)}
{$_('switch.lang', { locale: oppositeLocale })}
</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
dictionary.set({
@ -34,4 +48,4 @@ locale.set(
locale.subscribe(l => {
console.log('locale change', l)
})
})

2291
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -53,27 +53,27 @@
"collectCoverage": true
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/preset-env": "^7.4.5",
"@babel/core": "^7.7.2",
"@babel/preset-env": "^7.7.1",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.8.0",
"eslint": "^5.16.0",
"babel-jest": "^24.9.0",
"eslint": "^6.6.0",
"eslint-config-kaisermann": "0.0.3",
"jest": "^24.8.0",
"prettier": "^1.18.2",
"rollup": "^1.15.1",
"jest": "^24.9.0",
"prettier": "^1.19.1",
"rollup": "^1.26.5",
"rollup-plugin-auto-external": "^2.0.0",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-node-resolve": "^5.0.1",
"rollup-plugin-terser": "^5.0.0",
"svelte": "^3.5.1"
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^5.1.2",
"svelte": "^3.14.0"
},
"peerDependencies": {
"svelte": "^3.5.1"
"svelte": "^3.14.0"
},
"dependencies": {
"intl-messageformat": "^4.0.1",
"micro-memoize": "^4.0.7",
"intl-messageformat": "^7.5.2",
"micro-memoize": "^4.0.8",
"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 IntlMessageFormat from 'intl-messageformat'
import memoize from 'micro-memoize'
@ -8,7 +8,22 @@ import { capital, title, upper, lower, getClientLocale } from './utils.js'
let currentLocale
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
// istanbul ignore else
@ -24,7 +39,7 @@ const getAvailableLocale = newLocale => {
}
const getMessageFormatter = memoize(
(message, locale, formats) => new IntlMessageFormat(message, locale, formats),
(message, locale) => new IntlMessageFormat(message, locale, customFormats),
)
const lookupMessage = memoize((path, locale) => {
@ -34,37 +49,41 @@ const lookupMessage = memoize((path, locale) => {
)
})
const formatMessage = (message, interpolations, locale = currentLocale) => {
return getMessageFormatter(message, locale).format(interpolations)
function formatString(string, { values, locale = currentLocale } = {}) {
return getMessageFormatter(string, locale).format(values)
}
const getLocalizedMessage = (path, interpolations, locale = currentLocale) => {
if (typeof interpolations === 'string') {
locale = interpolations
interpolations = undefined
}
function formatMessage(path, { values, locale = currentLocale } = {}) {
const message = lookupMessage(path, locale)
if (!message) return path
if (!interpolations) return message
if (!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(`{t,time,${format}}`, { t }, locale)
getLocalizedMessage.date = (d, format = 'short', locale) =>
formatMessage(`{d,date,${format}}`, { d }, locale)
getLocalizedMessage.number = (n, locale) =>
formatMessage('{n,number}', { n }, locale)
getLocalizedMessage.capital = (path, interpolations, locale) =>
capital(getLocalizedMessage(path, interpolations, locale))
getLocalizedMessage.title = (path, interpolations, locale) =>
title(getLocalizedMessage(path, interpolations, locale))
getLocalizedMessage.upper = (path, interpolations, locale) =>
upper(getLocalizedMessage(path, interpolations, locale))
getLocalizedMessage.lower = (path, interpolations, locale) =>
lower(getLocalizedMessage(path, interpolations, locale))
formatMessage.time = (t, { format = 'short' } = {}) =>
formatString(`{t,time,${format}}`, { values: { t } })
formatMessage.date = (d, { format = 'short' } = {}) =>
formatString(`{d,date,${format}}`, { values: { d } })
formatMessage.number = (n, { format } = {}) =>
formatString(`{n,number,${format}}`, { values: { n } })
formatMessage.capital = (path, options) => capital(formatMessage(path, options))
formatMessage.title = (path, options) => title(formatMessage(path, options))
formatMessage.upper = (path, options) => upper(formatMessage(path, options))
formatMessage.lower = (path, options) => lower(formatMessage(path, options))
const dictionary = writable({})
dictionary.subscribe(newDictionary => {
@ -79,14 +98,22 @@ locale.set = newLocale => {
return localeSet(availableLocale)
}
console.warn(`[svelte-i18n] Locale "${newLocale}" not found.`)
return localeSet(newLocale)
throw Error(`[svelte-i18n] Locale "${newLocale}" not found.`)
}
locale.update = fn => localeSet(fn(currentLocale))
locale.subscribe(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 currentLocale
@ -48,9 +55,10 @@ it('should fallback to existing locale', () => {
locale.set('en-US')
expect(currentLocale).toBe('en')
})
locale.set('non-existent')
expect(currentLocale).toBe('non-existent')
it("should throw an error if locale doesn't exist", () => {
expect(() => locale.set('FOO')).toThrow()
})
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', () => {
expect(_('switch.lang', 'pt')).toBe('Trocar idioma')
expect(_('switch.lang', 'en')).toBe('Switch language')
expect(_('switch.lang', { locale: 'pt' })).toBe('Trocar idioma')
expect(_('switch.lang', { locale: 'en' })).toBe('Switch language')
})
it('should interpolate message with variables', () => {
expect(_('greeting.message', { name: 'Chris' })).toBe(
expect(_('greeting.message', { values: { name: 'Chris' } })).toBe(
'Hello Chris, how are you?',
)
})
it('should interpolate message with variables according to passed locale', () => {
expect(_('greeting.message', { name: 'Chris' }, 'pt')).toBe(
'Olá Chris, como vai?',
)
expect(
_('greeting.message', { values: { name: 'Chris' }, locale: 'pt' }),
).toBe('Olá Chris, como vai?')
})
describe('utilities', () => {
@ -141,12 +149,12 @@ describe('utilities', () => {
it('should format a time value', () => {
locale.set('en')
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', () => {
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
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')
})
})