mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-11-16 18:10:43 +01:00
feat: 🎸 make date,time and number formatters tree-shakeable
BREAKING CHANGE: Changes completely the API. Now, to format a number, date or time, the developer must explicitly import the formatter store: `import { time, date, number } from 'svelte-i18n'`
This commit is contained in:
parent
858c25cc80
commit
fb82a400f3
72
src/client/stores/formatters.ts
Normal file
72
src/client/stores/formatters.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { derived } from 'svelte/store'
|
||||
|
||||
import {
|
||||
MessageFormatter,
|
||||
MessageObject,
|
||||
TimeFormatter,
|
||||
DateFormatter,
|
||||
NumberFormatter,
|
||||
} from '../types'
|
||||
import { lookup } from '../includes/lookup'
|
||||
import { hasLocaleQueue } from '../includes/loaderQueue'
|
||||
import {
|
||||
getMessageFormatter,
|
||||
getTimeFormatter,
|
||||
getDateFormatter,
|
||||
getNumberFormatter,
|
||||
} from '../includes/formatters'
|
||||
import { getOptions } from '../configs'
|
||||
|
||||
import { $dictionary } from './dictionary'
|
||||
import { getCurrentLocale, getRelatedLocalesOf, $locale } from './locale'
|
||||
|
||||
const formatMessage: MessageFormatter = (id, options = {}) => {
|
||||
if (typeof id === 'object') {
|
||||
options = id as MessageObject
|
||||
id = options.id
|
||||
}
|
||||
|
||||
const { values, locale = getCurrentLocale(), default: defaultValue } = options
|
||||
|
||||
if (locale == null) {
|
||||
throw new Error(
|
||||
'[svelte-i18n] Cannot format a message without first setting the initial locale.'
|
||||
)
|
||||
}
|
||||
|
||||
const message = lookup(id, locale)
|
||||
|
||||
if (!message) {
|
||||
if (getOptions().warnOnMissingMessages) {
|
||||
// istanbul ignore next
|
||||
console.warn(
|
||||
`[svelte-i18n] The message "${id}" was not found in "${getRelatedLocalesOf(
|
||||
locale
|
||||
).join('", "')}".${
|
||||
hasLocaleQueue(getCurrentLocale())
|
||||
? `\n\nNote: there are at least one loader still registered to this locale that wasn't executed.`
|
||||
: ''
|
||||
}`
|
||||
)
|
||||
}
|
||||
|
||||
return defaultValue || id
|
||||
}
|
||||
|
||||
if (!values) return message
|
||||
return getMessageFormatter(message, locale).format(values)
|
||||
}
|
||||
|
||||
const formatTime: TimeFormatter = (t, options) =>
|
||||
getTimeFormatter(options).format(t)
|
||||
|
||||
const formatDate: DateFormatter = (d, options) =>
|
||||
getDateFormatter(options).format(d)
|
||||
|
||||
const formatNumber: NumberFormatter = (n, options) =>
|
||||
getNumberFormatter(options).format(n)
|
||||
|
||||
export const $format = derived([$locale, $dictionary], () => formatMessage)
|
||||
export const $formatTime = derived([$locale], () => formatTime)
|
||||
export const $formatDate = derived([$locale], () => formatDate)
|
||||
export const $formatNumber = derived([$locale], () => formatNumber)
|
@ -13,19 +13,31 @@ export function waitLocale(locale?: string) {
|
||||
}
|
||||
|
||||
export { init } from './configs'
|
||||
|
||||
export { $locale as locale } from './stores/locale'
|
||||
|
||||
export {
|
||||
$dictionary as dictionary,
|
||||
$locales as locales,
|
||||
addMessages,
|
||||
} from './stores/dictionary'
|
||||
export { registerLocaleLoader as register } from './includes/loaderQueue'
|
||||
|
||||
export { $isLoading as isLoading } from './stores/loading'
|
||||
export { $format as format, $format as _, $format as t } from './stores/format'
|
||||
|
||||
export {
|
||||
$format as format,
|
||||
$format as _,
|
||||
$format as t,
|
||||
$formatDate as date,
|
||||
$formatNumber as number,
|
||||
$formatTime as time,
|
||||
} from './stores/formatters'
|
||||
|
||||
// low-level
|
||||
export {
|
||||
getDateFormatter,
|
||||
getNumberFormatter,
|
||||
getTimeFormatter,
|
||||
getMessageFormatter,
|
||||
} from './includes/formatters'
|
||||
// utilities
|
||||
export { registerLocaleLoader as register } from './includes/loaderQueue'
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { derived } from 'svelte/store'
|
||||
|
||||
import { Formatter, MessageObject } from '../types'
|
||||
import {
|
||||
MessageFormatter,
|
||||
MessageObject,
|
||||
TimeFormatter,
|
||||
DateFormatter,
|
||||
NumberFormatter,
|
||||
} from '../types'
|
||||
import { lookup } from '../includes/lookup'
|
||||
import { hasLocaleQueue } from '../includes/loaderQueue'
|
||||
import { capital, upper, lower, title } from '../includes/utils'
|
||||
import {
|
||||
getMessageFormatter,
|
||||
getTimeFormatter,
|
||||
@ -15,7 +20,7 @@ import { getOptions } from '../configs'
|
||||
import { $dictionary } from './dictionary'
|
||||
import { getCurrentLocale, getRelatedLocalesOf, $locale } from './locale'
|
||||
|
||||
const formatMessage: Formatter = (id, options = {}) => {
|
||||
const formatMessage: MessageFormatter = (id, options = {}) => {
|
||||
if (typeof id === 'object') {
|
||||
options = id as MessageObject
|
||||
id = options.id
|
||||
@ -52,14 +57,16 @@ const formatMessage: Formatter = (id, options = {}) => {
|
||||
return getMessageFormatter(message, locale).format(values)
|
||||
}
|
||||
|
||||
formatMessage.time = (t, options) => getTimeFormatter(options).format(t)
|
||||
formatMessage.date = (d, options) => getDateFormatter(options).format(d)
|
||||
formatMessage.number = (n, options) => getNumberFormatter(options).format(n)
|
||||
formatMessage.capital = (id, options) => capital(formatMessage(id, options))
|
||||
formatMessage.title = (id, options) => title(formatMessage(id, options))
|
||||
formatMessage.upper = (id, options) => upper(formatMessage(id, options))
|
||||
formatMessage.lower = (id, options) => lower(formatMessage(id, options))
|
||||
const formatTime: TimeFormatter = (t, options) =>
|
||||
getTimeFormatter(options).format(t)
|
||||
|
||||
const $format = derived([$locale, $dictionary], () => formatMessage)
|
||||
const formatDate: DateFormatter = (d, options) =>
|
||||
getDateFormatter(options).format(d)
|
||||
|
||||
export { $format }
|
||||
const formatNumber: NumberFormatter = (n, options) =>
|
||||
getNumberFormatter(options).format(n)
|
||||
|
||||
export const $format = derived([$locale, $dictionary], () => formatMessage)
|
||||
export const $formatTime = derived([$locale], () => formatTime)
|
||||
export const $formatDate = derived([$locale], () => formatDate)
|
||||
export const $formatNumber = derived([$locale], () => formatNumber)
|
||||
|
@ -79,17 +79,6 @@ describe('collecting format calls', () => {
|
||||
expect(calls[2]).toMatchObject({ type: 'CallExpression' })
|
||||
expect(calls[3]).toMatchObject({ type: 'CallExpression' })
|
||||
})
|
||||
|
||||
test('ignores date, time and number calls', () => {
|
||||
const ast = parse(`<script>
|
||||
import { _ } from 'svelte-i18n'
|
||||
$_.number(1000)
|
||||
$_.date(new Date())
|
||||
$_.time(new Date())
|
||||
</script>`)
|
||||
const calls = collectFormatCalls(ast)
|
||||
expect(calls).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('collecting message definitions', () => {
|
||||
|
112
test/client/stores/formatters.test.ts
Normal file
112
test/client/stores/formatters.test.ts
Normal file
@ -0,0 +1,112 @@
|
||||
<<<<<<< HEAD:test/runtime/stores/format.test.ts
|
||||
import { Formatter } from '../../../src/runtime/types'
|
||||
import { $format } from '../../../src/runtime/stores/format'
|
||||
import { init } from '../../../src/runtime/configs'
|
||||
import { addMessages } from '../../../src/runtime/stores/dictionary'
|
||||
import { $locale } from '../../../src/runtime/stores/locale'
|
||||
=======
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
import {
|
||||
$format,
|
||||
$formatTime,
|
||||
$formatDate,
|
||||
$formatNumber,
|
||||
} from '../../../src/client/stores/formatters'
|
||||
import { init } from '../../../src/client/configs'
|
||||
import { addMessages } from '../../../src/client/stores/dictionary'
|
||||
import { $locale } from '../../../src/client/stores/locale'
|
||||
import { MessageFormatter } from '../../../src/client/types'
|
||||
import {
|
||||
TimeFormatter,
|
||||
DateFormatter,
|
||||
NumberFormatter,
|
||||
} from '../../../src/client/types/index'
|
||||
>>>>>>> feat: 🎸 make date,time and number formatters tree-shakeable:test/client/stores/formatters.test.ts
|
||||
|
||||
let formatMessage: MessageFormatter
|
||||
let formatTime: TimeFormatter
|
||||
let formatDate: DateFormatter
|
||||
let formatNumber: NumberFormatter
|
||||
$locale.subscribe(() => {
|
||||
formatMessage = get($format)
|
||||
formatTime = get($formatTime)
|
||||
formatDate = get($formatDate)
|
||||
formatNumber = get($formatNumber)
|
||||
})
|
||||
|
||||
addMessages('en', require('../../fixtures/en.json'))
|
||||
addMessages('en-GB', require('../../fixtures/en-GB.json'))
|
||||
addMessages('pt', require('../../fixtures/pt.json'))
|
||||
addMessages('pt-BR', require('../../fixtures/pt-BR.json'))
|
||||
addMessages('pt-PT', require('../../fixtures/pt-PT.json'))
|
||||
|
||||
beforeEach(() => {
|
||||
init({ fallbackLocale: 'en' })
|
||||
})
|
||||
|
||||
test('formats a message by its id and the current locale', () => {
|
||||
expect(formatMessage({ id: 'form.field_1_name' })).toBe('Name')
|
||||
})
|
||||
|
||||
test('formats a message by its id and the a passed locale', () => {
|
||||
expect(formatMessage({ id: 'form.field_1_name', locale: 'pt' })).toBe('Nome')
|
||||
})
|
||||
|
||||
test('formats a message with interpolated values', () => {
|
||||
expect(formatMessage({ id: 'photos', values: { n: 0 } })).toBe(
|
||||
'You have no photos.'
|
||||
)
|
||||
expect(formatMessage({ id: 'photos', values: { n: 1 } })).toBe(
|
||||
'You have one photo.'
|
||||
)
|
||||
expect(formatMessage({ id: 'photos', values: { n: 21 } })).toBe(
|
||||
'You have 21 photos.'
|
||||
)
|
||||
})
|
||||
|
||||
test('accepts a message id as first argument', () => {
|
||||
expect(formatMessage('form.field_1_name')).toBe('Name')
|
||||
})
|
||||
|
||||
test('accepts a message id as first argument and formatting options as second', () => {
|
||||
expect(formatMessage('form.field_1_name', { locale: 'pt' })).toBe('Nome')
|
||||
})
|
||||
|
||||
test('throws if no locale is set', () => {
|
||||
$locale.set(null)
|
||||
expect(() => formatMessage('form.field_1_name')).toThrow(
|
||||
'[svelte-i18n] Cannot format a message without first setting the initial locale.'
|
||||
)
|
||||
})
|
||||
|
||||
test('uses a missing message default value', () => {
|
||||
expect(formatMessage('missing', { default: 'Missing Default' })).toBe(
|
||||
'Missing Default'
|
||||
)
|
||||
})
|
||||
|
||||
test('warn on missing messages', () => {
|
||||
const warn = global.console.warn
|
||||
global.console.warn = jest.fn()
|
||||
|
||||
formatMessage('missing')
|
||||
|
||||
expect(console.warn).toBeCalledWith(
|
||||
`[svelte-i18n] The message "missing" was not found in "en".`
|
||||
)
|
||||
|
||||
global.console.warn = warn
|
||||
})
|
||||
|
||||
describe('format utilities', () => {
|
||||
test('time', () => {
|
||||
expect(formatTime(new Date(2019, 0, 1, 20, 37))).toBe('8:37 PM')
|
||||
})
|
||||
test('date', () => {
|
||||
expect(formatDate(new Date(2019, 0, 1, 20, 37))).toBe('1/1/19')
|
||||
})
|
||||
test('number', () => {
|
||||
expect(formatNumber(123123123)).toBe('123,123,123')
|
||||
})
|
||||
})
|
@ -1,11 +1,39 @@
|
||||
<<<<<<< HEAD:test/runtime/stores/format.test.ts
|
||||
import { Formatter } from '../../../src/runtime/types'
|
||||
import { $format } from '../../../src/runtime/stores/format'
|
||||
import { init } from '../../../src/runtime/configs'
|
||||
import { addMessages } from '../../../src/runtime/stores/dictionary'
|
||||
import { $locale } from '../../../src/runtime/stores/locale'
|
||||
=======
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
let format: Formatter
|
||||
$format.subscribe(f => (format = f))
|
||||
import {
|
||||
$format,
|
||||
$formatTime,
|
||||
$formatDate,
|
||||
$formatNumber,
|
||||
} from '../../../src/client/stores/formatters'
|
||||
import { init } from '../../../src/client/configs'
|
||||
import { addMessages } from '../../../src/client/stores/dictionary'
|
||||
import { $locale } from '../../../src/client/stores/locale'
|
||||
import { MessageFormatter } from '../../../src/client/types'
|
||||
import {
|
||||
TimeFormatter,
|
||||
DateFormatter,
|
||||
NumberFormatter,
|
||||
} from '../../../src/client/types/index'
|
||||
>>>>>>> feat: 🎸 make date,time and number formatters tree-shakeable:test/client/stores/formatters.test.ts
|
||||
|
||||
let formatMessage: MessageFormatter
|
||||
let formatTime: TimeFormatter
|
||||
let formatDate: DateFormatter
|
||||
let formatNumber: NumberFormatter
|
||||
$locale.subscribe(() => {
|
||||
formatMessage = get($format)
|
||||
formatTime = get($formatTime)
|
||||
formatDate = get($formatDate)
|
||||
formatNumber = get($formatNumber)
|
||||
})
|
||||
|
||||
addMessages('en', require('../../fixtures/en.json'))
|
||||
addMessages('en-GB', require('../../fixtures/en-GB.json'))
|
||||
@ -18,38 +46,42 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
test('formats a message by its id and the current locale', () => {
|
||||
expect(format({ id: 'form.field_1_name' })).toBe('Name')
|
||||
expect(formatMessage({ id: 'form.field_1_name' })).toBe('Name')
|
||||
})
|
||||
|
||||
test('formats a message by its id and the a passed locale', () => {
|
||||
expect(format({ id: 'form.field_1_name', locale: 'pt' })).toBe('Nome')
|
||||
expect(formatMessage({ id: 'form.field_1_name', locale: 'pt' })).toBe('Nome')
|
||||
})
|
||||
|
||||
test('formats a message with interpolated values', () => {
|
||||
expect(format({ id: 'photos', values: { n: 0 } })).toBe('You have no photos.')
|
||||
expect(format({ id: 'photos', values: { n: 1 } })).toBe('You have one photo.')
|
||||
expect(format({ id: 'photos', values: { n: 21 } })).toBe(
|
||||
expect(formatMessage({ id: 'photos', values: { n: 0 } })).toBe(
|
||||
'You have no photos.'
|
||||
)
|
||||
expect(formatMessage({ id: 'photos', values: { n: 1 } })).toBe(
|
||||
'You have one photo.'
|
||||
)
|
||||
expect(formatMessage({ id: 'photos', values: { n: 21 } })).toBe(
|
||||
'You have 21 photos.'
|
||||
)
|
||||
})
|
||||
|
||||
test('accepts a message id as first argument', () => {
|
||||
expect(format('form.field_1_name')).toBe('Name')
|
||||
expect(formatMessage('form.field_1_name')).toBe('Name')
|
||||
})
|
||||
|
||||
test('accepts a message id as first argument and formatting options as second', () => {
|
||||
expect(format('form.field_1_name', { locale: 'pt' })).toBe('Nome')
|
||||
expect(formatMessage('form.field_1_name', { locale: 'pt' })).toBe('Nome')
|
||||
})
|
||||
|
||||
test('throws if no locale is set', () => {
|
||||
$locale.set(null)
|
||||
expect(() => format('form.field_1_name')).toThrow(
|
||||
expect(() => formatMessage('form.field_1_name')).toThrow(
|
||||
'[svelte-i18n] Cannot format a message without first setting the initial locale.'
|
||||
)
|
||||
})
|
||||
|
||||
test('uses a missing message default value', () => {
|
||||
expect(format('missing', { default: 'Missing Default' })).toBe(
|
||||
expect(formatMessage('missing', { default: 'Missing Default' })).toBe(
|
||||
'Missing Default'
|
||||
)
|
||||
})
|
||||
@ -58,7 +90,7 @@ test('warn on missing messages', () => {
|
||||
const warn = global.console.warn
|
||||
global.console.warn = jest.fn()
|
||||
|
||||
format('missing')
|
||||
formatMessage('missing')
|
||||
|
||||
expect(console.warn).toBeCalledWith(
|
||||
`[svelte-i18n] The message "missing" was not found in "en".`
|
||||
@ -69,24 +101,12 @@ test('warn on missing messages', () => {
|
||||
|
||||
describe('format utilities', () => {
|
||||
test('time', () => {
|
||||
expect(format.time(new Date(2019, 0, 1, 20, 37))).toBe('8:37 PM')
|
||||
expect(formatTime(new Date(2019, 0, 1, 20, 37))).toBe('8:37 PM')
|
||||
})
|
||||
test('date', () => {
|
||||
expect(format.date(new Date(2019, 0, 1, 20, 37))).toBe('1/1/19')
|
||||
expect(formatDate(new Date(2019, 0, 1, 20, 37))).toBe('1/1/19')
|
||||
})
|
||||
test('number', () => {
|
||||
expect(format.number(123123123)).toBe('123,123,123')
|
||||
})
|
||||
test('capital', () => {
|
||||
expect(format.capital('title')).toBe('Page title')
|
||||
})
|
||||
test('title', () => {
|
||||
expect(format.title('title')).toBe('Page Title')
|
||||
})
|
||||
test('upper', () => {
|
||||
expect(format.upper('title')).toBe('PAGE TITLE')
|
||||
})
|
||||
test('lower', () => {
|
||||
expect(format.lower('title')).toBe('page title')
|
||||
expect(formatNumber(123123123)).toBe('123,123,123')
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user