mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-11-16 18:10:43 +01:00
feat: 🎸 add custom formats support
BREAKING CHANGE: This PR modifies the formatter method arguments.
This commit is contained in:
parent
f056e70ec5
commit
d483244a9f
@ -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>
|
||||||
|
@ -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
2291
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
89
src/index.js
89
src/index.js
@ -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,
|
||||||
|
}
|
||||||
|
@ -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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user