refactor: 💡 shrink configs API contact points

This commit is contained in:
Christian Kaisermann 2019-11-25 23:04:10 -03:00
parent 965c18acc1
commit 36bf7f5038
11 changed files with 156 additions and 103 deletions

View File

@ -1,7 +1,18 @@
import { register, setFallbackLocale } from 'svelte-i18n' import { register, configure } from 'svelte-i18n'
configure({
fallbackLocale: 'en',
initialLocale: {
navigator: true,
},
formats: {
number: {
BRL: { style: 'currency', currency: 'BRL' },
},
},
loadingDelay: 200,
})
register('en', () => import('../messages/en.json')) register('en', () => import('../messages/en.json'))
register('pt-BR', () => import('../messages/pt-BR.json')) register('pt-BR', () => import('../messages/pt-BR.json'))
register('es-ES', () => import('../messages/es-ES.json')) register('es-ES', () => import('../messages/es-ES.json'))
setFallbackLocale('en')

View File

@ -1,12 +1,8 @@
<script context="module"> <script context="module">
import { isLoading, setInitialLocale } from 'svelte-i18n' import { isLoading, waitLocale } from 'svelte-i18n'
export async function preload() { export async function preload() {
return setInitialLocale({ return waitLocale()
fallback: 'en',
// navigator: true,
search: 'lang'
})
} }
</script> </script>

67
src/client/configs.ts Normal file
View File

@ -0,0 +1,67 @@
import { getClientLocale } from './includes/utils'
import { ConfigureOptions } from './types'
import { $locale } from './stores/locale'
let fallbackLocale: string = null
let loadingDelay = 200
const formats: any = {
number: {
scientific: { notation: 'scientific' },
engineering: { notation: 'engineering' },
compactLong: { notation: 'compact', compactDisplay: 'long' },
compactShort: { notation: 'compact', compactDisplay: 'short' },
},
date: {
short: { month: 'numeric', day: 'numeric', year: '2-digit' },
medium: { month: 'short', day: 'numeric', year: 'numeric' },
long: { month: 'long', day: 'numeric', year: 'numeric' },
full: { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' },
},
time: {
short: { hour: 'numeric', minute: 'numeric' },
medium: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
long: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short',
},
full: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short',
},
},
}
export function getFallbackLocale() {
return fallbackLocale
}
export function getLoadingDelay() {
return loadingDelay
}
export function getFormats() {
return formats
}
export function configure(opts: ConfigureOptions) {
fallbackLocale = opts.fallbackLocale
if (opts.initialLocale) {
$locale.set(getClientLocale(opts.initialLocale) || fallbackLocale)
} else {
$locale.set(fallbackLocale)
}
if (opts.formats) {
if ('number' in opts.formats)
Object.assign(formats.number, opts.formats.number)
if ('date' in opts.formats) Object.assign(formats.date, opts.formats.date)
if ('time' in opts.formats) Object.assign(formats.time, opts.formats.time)
}
if (loadingDelay != null) loadingDelay = loadingDelay
}

View File

@ -1,32 +1,17 @@
import IntlMessageFormat, { Formats } from 'intl-messageformat' import IntlMessageFormat from 'intl-messageformat'
import memoize from 'fast-memoize' import memoize from 'fast-memoize'
import { MemoizedIntlFormatter } from '../types' import { MemoizedIntlFormatter } from '../types'
import { getCurrentLocale } from '../stores/locale' import { getCurrentLocale } from '../stores/locale'
import { getFormats } from '../configs'
export const customFormats: any = {
number: {
scientific: { notation: 'scientific' },
engineering: { notation: 'engineering' },
compactLong: { notation: 'compact', compactDisplay: 'long' },
compactShort: { notation: 'compact', compactDisplay: 'short' },
},
date: {},
time: {},
}
export function addCustomFormats(formats: Partial<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)
}
const getIntlFormatterOptions = ( const getIntlFormatterOptions = (
type: 'time' | 'number' | 'date', type: 'time' | 'number' | 'date',
name: string name: string
): any => { ): any => {
if (type in customFormats && name in customFormats[type]) { const formats = getFormats()
return customFormats[type][name] if (type in formats && name in formats[type]) {
return formats[type][name]
} }
if ( if (
@ -77,5 +62,5 @@ export const getTimeFormatter: MemoizedIntlFormatter<
export const getMessageFormatter = memoize( export const getMessageFormatter = memoize(
(message: string, locale: string) => (message: string, locale: string) =>
new IntlMessageFormat(message, locale, customFormats) new IntlMessageFormat(message, locale, getFormats())
) )

View File

@ -4,8 +4,9 @@ import {
$dictionary, $dictionary,
addMessages, addMessages,
} from '../stores/dictionary' } from '../stores/dictionary'
import { getCurrentLocale, getFallbacksOf } from '../stores/locale' import { getCurrentLocale, getRelatedLocalesOf } from '../stores/locale'
import { $isLoading } from '../stores/loading' import { $isLoading } from '../stores/loading'
import { getLoadingDelay } from '../configs'
type Queue = Set<MessagesLoader> type Queue = Set<MessagesLoader>
const loaderQueue: Record<string, Queue> = {} const loaderQueue: Record<string, Queue> = {}
@ -23,7 +24,7 @@ function getLocaleQueue(locale: string) {
} }
function getLocalesQueues(locale: string) { function getLocalesQueues(locale: string) {
return getFallbacksOf(locale) return getRelatedLocalesOf(locale)
.reverse() .reverse()
.map<[string, MessagesLoader[]]>(localeItem => { .map<[string, MessagesLoader[]]>(localeItem => {
const queue = getLocaleQueue(localeItem) const queue = getLocaleQueue(localeItem)
@ -33,7 +34,7 @@ function getLocalesQueues(locale: string) {
} }
export function hasLocaleQueue(locale: string) { export function hasLocaleQueue(locale: string) {
return getFallbacksOf(locale) return getRelatedLocalesOf(locale)
.reverse() .reverse()
.some(getLocaleQueue) .some(getLocaleQueue)
} }
@ -45,14 +46,14 @@ export function addLoaderToQueue(locale: string, loader: MessagesLoader) {
const activeLocaleFlushes: { [key: string]: Promise<void> } = {} const activeLocaleFlushes: { [key: string]: Promise<void> } = {}
export async function flushQueue(locale: string = getCurrentLocale()) { export async function flushQueue(locale: string = getCurrentLocale()) {
if (!hasLocaleQueue(locale)) return if (!hasLocaleQueue(locale)) return
if (activeLocaleFlushes[locale]) return activeLocaleFlushes[locale] if (locale in activeLocaleFlushes) return activeLocaleFlushes[locale]
// get queue of XX-YY and XX locales // get queue of XX-YY and XX locales
const queues = getLocalesQueues(locale) const queues = getLocalesQueues(locale)
if (queues.length === 0) return if (queues.length === 0) return
removeLocaleFromQueue(locale) removeLocaleFromQueue(locale)
const loadingDelay = setTimeout(() => $isLoading.set(true), 200) const loadingDelay = setTimeout(() => $isLoading.set(true), getLoadingDelay())
// TODO what happens if some loader fails // TODO what happens if some loader fails
activeLocaleFlushes[locale] = Promise.all( activeLocaleFlushes[locale] = Promise.all(

View File

@ -39,8 +39,6 @@ export const getClientLocale = ({
search, search,
pathname, pathname,
hostname, hostname,
default: defaultLocale,
fallback = defaultLocale,
}: GetClientLocaleOptions) => { }: GetClientLocaleOptions) => {
let locale let locale
@ -78,5 +76,5 @@ export const getClientLocale = ({
if (locale) return locale if (locale) return locale
} }
return fallback return null
} }

View File

@ -1,5 +1,3 @@
import merge from 'deepmerge'
import { MessageObject } from './types' import { MessageObject } from './types'
// defineMessages allow us to define and extract dynamic message ids // defineMessages allow us to define and extract dynamic message ids
@ -7,11 +5,8 @@ export function defineMessages(i: Record<string, MessageObject>) {
return i return i
} }
export { export { configure } from './configs'
$locale as locale, export { $locale as locale } from './stores/locale'
setInitialLocale,
setFallbackLocale,
} from './stores/locale'
export { export {
$dictionary as dictionary, $dictionary as dictionary,
$locales as locales, $locales as locales,
@ -21,12 +16,7 @@ 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 } from './stores/format'
// utilities // utilities
export { merge }
export { customFormats, addCustomFormats } from './includes/formats'
export { export {
flushQueue as waitLocale, flushQueue as waitLocale,
registerLocaleLoader as register, registerLocaleLoader as register,
} from './includes/loaderQueue' } from './includes/loaderQueue'
// @deprecated
export { getClientLocale } from './includes/utils'

View File

@ -9,10 +9,10 @@ import {
getTimeFormatter, getTimeFormatter,
getDateFormatter, getDateFormatter,
getNumberFormatter, getNumberFormatter,
} from '../includes/formats' } from '../includes/formatters'
import { $dictionary } from './dictionary' import { $dictionary } from './dictionary'
import { getCurrentLocale, getFallbacksOf, $locale } from './locale' import { getCurrentLocale, getRelatedLocalesOf, $locale } from './locale'
const formatMessage: Formatter = (id, options = {}) => { const formatMessage: Formatter = (id, options = {}) => {
if (typeof id === 'object') { if (typeof id === 'object') {
@ -32,7 +32,7 @@ const formatMessage: Formatter = (id, options = {}) => {
if (!message) { if (!message) {
console.warn( console.warn(
`[svelte-i18n] The message "${id}" was not found in "${getFallbacksOf( `[svelte-i18n] The message "${id}" was not found in "${getRelatedLocalesOf(
locale locale
).join('", "')}". ${ ).join('", "')}". ${
hasLocaleQueue(getCurrentLocale()) hasLocaleQueue(getCurrentLocale())

View File

@ -5,19 +5,11 @@ import { getClientLocale } from '../includes/utils'
import { GetClientLocaleOptions } from '../types' import { GetClientLocaleOptions } from '../types'
import { getClosestAvailableLocale } from './dictionary' import { getClosestAvailableLocale } from './dictionary'
import { setFallbackLocale, getFallbackLocale } from '../configs'
let fallbackLocale: string = null
let current: string let current: string
const $locale = writable(null) const $locale = writable(null)
export function getFallbackLocale() {
return fallbackLocale
}
export function setFallbackLocale(locale: string) {
fallbackLocale = locale
}
export function isFallbackLocaleOf(localeA: string, localeB: string) { export function isFallbackLocaleOf(localeA: string, localeB: string) {
return localeB.indexOf(localeA) === 0 && localeA !== localeB return localeB.indexOf(localeA) === 0 && localeA !== localeB
} }
@ -33,19 +25,19 @@ export function isRelatedLocale(localeA: string, localeB: string) {
export function getFallbackOf(locale: string) { export function getFallbackOf(locale: string) {
const index = locale.lastIndexOf('-') const index = locale.lastIndexOf('-')
if (index > 0) return locale.slice(0, index) if (index > 0) return locale.slice(0, index)
if (fallbackLocale && !isRelatedLocale(locale, fallbackLocale)) { if (getFallbackLocale() && !isRelatedLocale(locale, getFallbackLocale())) {
return fallbackLocale return getFallbackLocale()
} }
return null return null
} }
export function getFallbacksOf(locale: string): string[] { export function getRelatedLocalesOf(locale: string): string[] {
const locales = locale const locales = locale
.split('-') .split('-')
.map((_, i, arr) => arr.slice(0, i + 1).join('-')) .map((_, i, arr) => arr.slice(0, i + 1).join('-'))
if (fallbackLocale && !isRelatedLocale(locale, fallbackLocale)) { if (getFallbackLocale() && !isRelatedLocale(locale, getFallbackLocale())) {
return locales.concat(getFallbacksOf(fallbackLocale)) return locales.concat(getRelatedLocalesOf(getFallbackLocale()))
} }
return locales return locales
} }
@ -54,13 +46,6 @@ export function getCurrentLocale() {
return current return current
} }
export function setInitialLocale(options: GetClientLocaleOptions) {
if (typeof options.fallback === 'string') {
setFallbackLocale(options.fallback)
}
return $locale.set(getClientLocale(options))
}
$locale.subscribe((newLocale: string) => { $locale.subscribe((newLocale: string) => {
current = newLocale current = newLocale

View File

@ -1,3 +1,5 @@
import { Formats } from 'intl-messageformat'
export interface Dictionary { export interface Dictionary {
[key: string]: string | string[] | Dictionary | Dictionary[] [key: string]: string | string[] | Dictionary | Dictionary[]
} }
@ -50,8 +52,14 @@ export interface GetClientLocaleOptions {
navigator?: boolean navigator?: boolean
hash?: string | RegExp hash?: string | RegExp
search?: string | RegExp search?: string | RegExp
fallback?: string
default?: string default?: string
pathname?: RegExp pathname?: RegExp
hostname?: RegExp hostname?: RegExp
} }
export interface ConfigureOptions {
fallbackLocale: string
initialLocale?: GetClientLocaleOptions
formats?: Partial<Formats>
loadingDelay?: number
}

View File

@ -1,23 +1,22 @@
import { get } from 'svelte/store'
import { import {
getFallbackLocale,
setFallbackLocale,
isFallbackLocaleOf, isFallbackLocaleOf,
getFallbackOf, getFallbackOf,
getFallbacksOf, getRelatedLocalesOf,
setInitialLocale,
getCurrentLocale, getCurrentLocale,
$locale, $locale,
isRelatedLocale, isRelatedLocale,
} from '../../src/client/stores/locale' } from '../../src/client/stores/locale'
import { get } from 'svelte/store' import { getFallbackLocale, configure } from '../../src/client/configs'
beforeEach(() => { beforeEach(() => {
setFallbackLocale(undefined) configure({ fallbackLocale: undefined })
$locale.set(undefined) $locale.set(undefined)
}) })
test('sets and gets the fallback locale', () => { test('sets and gets the fallback locale', () => {
setFallbackLocale('en') configure({ fallbackLocale: 'en' })
expect(getFallbackLocale()).toBe('en') expect(getFallbackLocale()).toBe('en')
}) })
@ -42,31 +41,35 @@ test('gets the next fallback locale of a locale', () => {
}) })
test('gets the global fallback locale if set', () => { test('gets the global fallback locale if set', () => {
setFallbackLocale('en') configure({ fallbackLocale: 'en' })
expect(getFallbackOf('it')).toBe('en') expect(getFallbackOf('it')).toBe('en')
}) })
test('should not get the global fallback as the fallback of itself', () => { test('should not get the global fallback as the fallback of itself', () => {
setFallbackLocale('en') configure({ fallbackLocale: 'en' })
expect(getFallbackOf('en')).toBe(null) expect(getFallbackOf('en')).toBe(null)
}) })
test('if global fallback locale has a fallback, it should return it', () => { test('if global fallback locale has a fallback, it should return it', () => {
setFallbackLocale('en-US') configure({ fallbackLocale: 'en-US' })
expect(getFallbackOf('en-US')).toBe('en') expect(getFallbackOf('en-US')).toBe('en')
}) })
test('gets all fallback locales of a locale', () => { test('gets all fallback locales of a locale', () => {
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US']) expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US'])
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US']) expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US'])
expect(getFallbacksOf('az-Cyrl-AZ')).toEqual(['az', 'az-Cyrl', 'az-Cyrl-AZ']) expect(getRelatedLocalesOf('az-Cyrl-AZ')).toEqual([
'az',
'az-Cyrl',
'az-Cyrl-AZ',
])
}) })
test('gets all fallback locales of a locale including the global fallback locale', () => { test('gets all fallback locales of a locale including the global fallback locale', () => {
setFallbackLocale('pt') configure({ fallbackLocale: 'pt' })
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt']) expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt'])
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt']) expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt'])
expect(getFallbacksOf('az-Cyrl-AZ')).toEqual([ expect(getRelatedLocalesOf('az-Cyrl-AZ')).toEqual([
'az', 'az',
'az-Cyrl', 'az-Cyrl',
'az-Cyrl-AZ', 'az-Cyrl-AZ',
@ -74,10 +77,10 @@ test('gets all fallback locales of a locale including the global fallback locale
]) ])
}) })
test('gets all fallback locales of a locale including the global fallback locale and its fallbacks', () => { test('gets all fallback locales of a locale including the global fallback locale and its fallbacks', () => {
setFallbackLocale('pt-BR') configure({ fallbackLocale: 'pt-BR' })
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR']) expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR'])
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR']) expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR'])
expect(getFallbacksOf('az-Cyrl-AZ')).toEqual([ expect(getRelatedLocalesOf('az-Cyrl-AZ')).toEqual([
'az', 'az',
'az-Cyrl', 'az-Cyrl',
'az-Cyrl-AZ', 'az-Cyrl-AZ',
@ -87,9 +90,9 @@ test('gets all fallback locales of a locale including the global fallback locale
}) })
test("don't list fallback locale twice", () => { test("don't list fallback locale twice", () => {
setFallbackLocale('pt-BR') configure({ fallbackLocale: 'pt-BR' })
expect(getFallbacksOf('pt-BR')).toEqual(['pt', 'pt-BR']) expect(getRelatedLocalesOf('pt-BR')).toEqual(['pt', 'pt-BR'])
expect(getFallbacksOf('pt')).toEqual(['pt']) expect(getRelatedLocalesOf('pt')).toEqual(['pt'])
}) })
test('gets the current locale', () => { test('gets the current locale', () => {
@ -98,10 +101,19 @@ test('gets the current locale', () => {
expect(getCurrentLocale()).toBe('es-ES') expect(getCurrentLocale()).toBe('es-ES')
}) })
test('sets the global fallback when defining initial locale', () => { test('if no initial locale is set, set the locale to the fallback', () => {
setInitialLocale({ configure({ fallbackLocale: 'pt' })
fallback: 'pt',
})
expect(get($locale)).toBe('pt') expect(get($locale)).toBe('pt')
expect(getFallbackLocale()).toBe('pt') expect(getFallbackLocale()).toBe('pt')
}) })
test('if no initial locale was found, set to the fallback locale', () => {
configure({
fallbackLocale: 'en',
initialLocale: {
hash: 'lang',
},
})
expect(get($locale)).toBe('en')
expect(getFallbackLocale()).toBe('en')
})