2019-11-19 17:18:42 +01:00
|
|
|
import { writable, derived } from 'svelte/store'
|
|
|
|
import resolvePath from 'object-resolve-path'
|
2019-11-20 05:32:30 +01:00
|
|
|
import merge from 'deepmerge'
|
2019-11-19 17:18:42 +01:00
|
|
|
|
2019-11-20 03:38:01 +01:00
|
|
|
import {
|
|
|
|
capital,
|
|
|
|
title,
|
|
|
|
upper,
|
|
|
|
lower,
|
|
|
|
getClientLocale,
|
|
|
|
getGenericLocaleFrom,
|
2019-11-20 06:05:34 +01:00
|
|
|
getLocalesFrom,
|
2019-11-20 03:38:01 +01:00
|
|
|
} from './utils'
|
2019-11-19 17:18:42 +01:00
|
|
|
import { MessageObject, Formatter } from './types'
|
|
|
|
import {
|
|
|
|
getMessageFormatter,
|
|
|
|
getDateFormatter,
|
|
|
|
getNumberFormatter,
|
|
|
|
getTimeFormatter,
|
|
|
|
} from './formatters'
|
|
|
|
|
2019-11-20 06:05:34 +01:00
|
|
|
const $loading = writable(false)
|
|
|
|
|
2019-11-19 17:18:42 +01:00
|
|
|
let currentLocale: string
|
2019-11-20 03:38:01 +01:00
|
|
|
let currentDictionary: Record<string, Record<string, any>>
|
2019-11-20 06:05:34 +01:00
|
|
|
const dictQueue: Record<string, (() => Promise<any>)[]> = {}
|
2019-11-19 17:18:42 +01:00
|
|
|
|
2019-11-20 01:04:50 +01:00
|
|
|
const hasLocale = (locale: string) => locale in currentDictionary
|
|
|
|
|
2019-11-20 05:32:30 +01:00
|
|
|
async function registerLocaleLoader(locale: string, loader: any) {
|
|
|
|
if (!(locale in currentDictionary)) {
|
|
|
|
$dictionary.update(d => {
|
|
|
|
d[locale] = {}
|
|
|
|
return d
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (!(locale in dictQueue)) dictQueue[locale] = []
|
|
|
|
dictQueue[locale].push(loader)
|
|
|
|
}
|
2019-11-20 03:38:01 +01:00
|
|
|
function getAvailableLocale(locale: string): string | null {
|
2019-11-20 06:05:34 +01:00
|
|
|
if (locale in currentDictionary || locale == null) return locale
|
2019-11-20 03:38:01 +01:00
|
|
|
return getAvailableLocale(getGenericLocaleFrom(locale))
|
2019-11-19 17:18:42 +01:00
|
|
|
}
|
|
|
|
|
2019-11-20 05:32:30 +01:00
|
|
|
const lookupCache: Record<string, Record<string, string>> = {}
|
|
|
|
const addToCache = (path: string, locale: string, message: string) => {
|
|
|
|
if (!(locale in lookupCache)) lookupCache[locale] = {}
|
|
|
|
if (!(path in lookupCache[locale])) lookupCache[locale][path] = message
|
|
|
|
return message
|
|
|
|
}
|
|
|
|
const invalidateLookupCache = (locale: string) => {
|
|
|
|
delete lookupCache[locale]
|
|
|
|
}
|
|
|
|
const lookupMessage = (path: string, locale: string): string => {
|
2019-11-20 03:38:01 +01:00
|
|
|
if (locale == null) return null
|
2019-11-20 05:32:30 +01:00
|
|
|
if (locale in lookupCache && path in lookupCache[locale]) {
|
|
|
|
return lookupCache[locale][path]
|
|
|
|
}
|
2019-11-20 03:38:01 +01:00
|
|
|
if (hasLocale(locale)) {
|
2019-11-20 05:32:30 +01:00
|
|
|
if (path in currentDictionary[locale]) {
|
|
|
|
return addToCache(path, locale, currentDictionary[locale][path])
|
|
|
|
}
|
2019-11-20 03:38:01 +01:00
|
|
|
const message = resolvePath(currentDictionary[locale], path)
|
2019-11-20 05:32:30 +01:00
|
|
|
if (message) return addToCache(path, locale, message)
|
2019-11-19 21:12:15 +01:00
|
|
|
}
|
2019-11-20 01:04:50 +01:00
|
|
|
|
2019-11-20 03:38:01 +01:00
|
|
|
return lookupMessage(path, getGenericLocaleFrom(locale))
|
2019-11-20 05:32:30 +01:00
|
|
|
}
|
2019-11-19 17:18:42 +01:00
|
|
|
|
|
|
|
const formatMessage: Formatter = (id, options = {}) => {
|
|
|
|
if (typeof id === 'object') {
|
|
|
|
options = id as MessageObject
|
|
|
|
id = options.id
|
|
|
|
}
|
|
|
|
|
|
|
|
const { values, locale = currentLocale, default: defaultValue } = options
|
2019-11-20 05:32:30 +01:00
|
|
|
|
|
|
|
if (locale == null) {
|
|
|
|
throw new Error(
|
|
|
|
'[svelte-i18n] Cannot format a message without first setting the initial locale.'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-11-19 17:18:42 +01:00
|
|
|
const message = lookupMessage(id, locale)
|
|
|
|
|
|
|
|
if (!message) {
|
2019-11-20 01:35:49 +01:00
|
|
|
console.warn(
|
2019-11-20 06:05:34 +01:00
|
|
|
`[svelte-i18n] The message "${id}" was not found in "${getLocalesFrom(locale).join('", "')}".`
|
2019-11-20 01:35:49 +01:00
|
|
|
)
|
2019-11-20 03:38:01 +01:00
|
|
|
return defaultValue || id
|
2019-11-19 17:18:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!values) return message
|
|
|
|
return getMessageFormatter(message, locale).format(values)
|
|
|
|
}
|
|
|
|
|
2019-11-19 21:12:15 +01:00
|
|
|
formatMessage.time = (t, options) => getTimeFormatter(currentLocale, options).format(t)
|
|
|
|
formatMessage.date = (d, options) => getDateFormatter(currentLocale, options).format(d)
|
|
|
|
formatMessage.number = (n, options) => getNumberFormatter(currentLocale, 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))
|
|
|
|
|
2019-11-20 03:38:01 +01:00
|
|
|
const $dictionary = writable<Record<string, Record<string, any>>>({})
|
|
|
|
$dictionary.subscribe(newDictionary => (currentDictionary = newDictionary))
|
|
|
|
|
2019-11-20 05:32:30 +01:00
|
|
|
function loadLocale(localeToLoad: string) {
|
2019-11-20 03:38:01 +01:00
|
|
|
return Promise.all(
|
2019-11-20 06:05:34 +01:00
|
|
|
getLocalesFrom(localeToLoad).map(localeItem =>
|
2019-11-20 05:32:30 +01:00
|
|
|
flushLocaleQueue(localeItem)
|
|
|
|
.then(() => [localeItem, { err: undefined }])
|
|
|
|
.catch(e => [localeItem, { err: e }])
|
|
|
|
)
|
2019-11-20 03:38:01 +01:00
|
|
|
)
|
2019-11-20 05:32:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async function flushLocaleQueue(locale: string = currentLocale) {
|
|
|
|
if (!(locale in dictQueue)) return
|
|
|
|
|
2019-11-20 06:05:34 +01:00
|
|
|
$loading.set(true)
|
|
|
|
|
|
|
|
return Promise.all(dictQueue[locale].map((loader: any) => loader()))
|
|
|
|
.then(partials => {
|
|
|
|
dictQueue[locale] = []
|
|
|
|
|
|
|
|
partials = partials.map(partial => partial.default || partial)
|
|
|
|
invalidateLookupCache(locale)
|
|
|
|
$dictionary.update(d => {
|
|
|
|
d[locale] = merge.all<any>([d[locale] || {}].concat(partials))
|
|
|
|
return d
|
|
|
|
})
|
2019-11-20 03:38:01 +01:00
|
|
|
})
|
2019-11-20 06:05:34 +01:00
|
|
|
.then(() => $loading.set(false))
|
2019-11-20 03:38:01 +01:00
|
|
|
}
|
2019-11-19 21:12:15 +01:00
|
|
|
|
|
|
|
const $locale = writable(null)
|
|
|
|
const localeSet = $locale.set
|
|
|
|
$locale.set = (newLocale: string): void | Promise<void> => {
|
2019-11-20 03:38:01 +01:00
|
|
|
const locale = getAvailableLocale(newLocale)
|
|
|
|
if (locale) {
|
2019-11-20 06:05:34 +01:00
|
|
|
return flushLocaleQueue(newLocale).then(() => localeSet(newLocale))
|
2019-11-19 17:18:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
throw Error(`[svelte-i18n] Locale "${newLocale}" not found.`)
|
|
|
|
}
|
2019-11-19 21:12:15 +01:00
|
|
|
$locale.update = (fn: (locale: string) => void | Promise<void>) => localeSet(fn(currentLocale))
|
|
|
|
$locale.subscribe((newLocale: string) => (currentLocale = newLocale))
|
|
|
|
|
2019-11-20 03:38:01 +01:00
|
|
|
const $format = derived([$locale, $dictionary], () => formatMessage)
|
|
|
|
const $locales = derived([$dictionary], ([$dictionary]) => Object.keys($dictionary))
|
2019-11-19 17:18:42 +01:00
|
|
|
|
|
|
|
// defineMessages allow us to define and extract dynamic message ids
|
|
|
|
const defineMessages = (i: Record<string, MessageObject>) => i
|
|
|
|
|
|
|
|
export { customFormats, addCustomFormats } from './formatters'
|
|
|
|
export {
|
2019-11-20 06:05:34 +01:00
|
|
|
$loading as loading,
|
2019-11-19 21:12:15 +01:00
|
|
|
$locale as locale,
|
|
|
|
$dictionary as dictionary,
|
2019-11-20 03:38:01 +01:00
|
|
|
$format as _,
|
|
|
|
$format as format,
|
2019-11-20 05:32:30 +01:00
|
|
|
$locales as locales,
|
2019-11-19 17:18:42 +01:00
|
|
|
getClientLocale,
|
|
|
|
defineMessages,
|
2019-11-20 03:38:01 +01:00
|
|
|
loadLocale as preloadLocale,
|
2019-11-20 06:05:34 +01:00
|
|
|
registerLocaleLoader as register,
|
|
|
|
flushLocaleQueue as waitLocale,
|
|
|
|
merge,
|
2019-11-19 17:18:42 +01:00
|
|
|
}
|