mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-06-30 18:54:49 +02:00
fix: 🐛 fallback behaviour and simplify API contact points
This commit is contained in:
parent
ea2ac47d14
commit
6e0df2fb25
|
@ -1,10 +1,11 @@
|
||||||
<script context="module">
|
<script context="module">
|
||||||
import { isLoading, waitInitialLocale } from 'svelte-i18n'
|
import { isLoading, setInitialLocale } from 'svelte-i18n'
|
||||||
|
|
||||||
export async function preload() {
|
export async function preload() {
|
||||||
return waitInitialLocale({
|
return setInitialLocale({
|
||||||
default: 'en-US',
|
fallback: 'en-US',
|
||||||
navigator: true,
|
// navigator: true,
|
||||||
|
search: 'lang'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,11 +4,9 @@ import {
|
||||||
$dictionary,
|
$dictionary,
|
||||||
addMessages,
|
addMessages,
|
||||||
} from '../stores/dictionary'
|
} from '../stores/dictionary'
|
||||||
import { getCurrentLocale } from '../stores/locale'
|
import { getCurrentLocale, getFallbacksOf } from '../stores/locale'
|
||||||
import { $isLoading } from '../stores/loading'
|
import { $isLoading } from '../stores/loading'
|
||||||
|
|
||||||
import { getAllFallbackLocales } from './utils'
|
|
||||||
|
|
||||||
type Queue = Set<MessagesLoader>
|
type Queue = Set<MessagesLoader>
|
||||||
const loaderQueue: Record<string, Queue> = {}
|
const loaderQueue: Record<string, Queue> = {}
|
||||||
|
|
||||||
|
@ -25,7 +23,7 @@ function getLocaleQueue(locale: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLocalesQueues(locale: string) {
|
function getLocalesQueues(locale: string) {
|
||||||
return getAllFallbackLocales(locale)
|
return getFallbacksOf(locale)
|
||||||
.reverse()
|
.reverse()
|
||||||
.map<[string, MessagesLoader[]]>(localeItem => {
|
.map<[string, MessagesLoader[]]>(localeItem => {
|
||||||
const queue = getLocaleQueue(localeItem)
|
const queue = getLocaleQueue(localeItem)
|
||||||
|
@ -35,7 +33,7 @@ function getLocalesQueues(locale: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasLocaleQueue(locale: string) {
|
export function hasLocaleQueue(locale: string) {
|
||||||
return getAllFallbackLocales(locale)
|
return getFallbacksOf(locale)
|
||||||
.reverse()
|
.reverse()
|
||||||
.some(getLocaleQueue)
|
.some(getLocaleQueue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
import resolvePath from 'object-resolve-path'
|
import resolvePath from 'object-resolve-path'
|
||||||
|
|
||||||
import { hasLocaleDictionary } from '../stores/dictionary'
|
import { hasLocaleDictionary } from '../stores/dictionary'
|
||||||
|
import { getFallbackOf } from '../stores/locale'
|
||||||
import { getFallbackLocale } from './utils'
|
|
||||||
|
|
||||||
const lookupCache: Record<string, Record<string, string>> = {}
|
const lookupCache: Record<string, Record<string, string>> = {}
|
||||||
|
|
||||||
|
@ -34,6 +33,6 @@ export const lookupMessage = (
|
||||||
return addToCache(
|
return addToCache(
|
||||||
path,
|
path,
|
||||||
locale,
|
locale,
|
||||||
lookupMessage(dictionary, path, getFallbackLocale(locale))
|
lookupMessage(dictionary, path, getFallbackOf(locale))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,6 @@ export function lower(str: string) {
|
||||||
return str.toLocaleLowerCase()
|
return str.toLocaleLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFallbackLocale(locale: string) {
|
|
||||||
const index = locale.lastIndexOf('-')
|
|
||||||
return index > 0 ? locale.slice(0, index) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAllFallbackLocales(locale: string) {
|
|
||||||
return locale.split('-').map((_, i, arr) => arr.slice(0, i + 1).join('-'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFromURL = (urlPart: string, key: string) => {
|
const getFromURL = (urlPart: string, key: string) => {
|
||||||
const keyVal = urlPart
|
const keyVal = urlPart
|
||||||
.substr(1)
|
.substr(1)
|
||||||
|
@ -49,12 +40,11 @@ export const getClientLocale = ({
|
||||||
pathname,
|
pathname,
|
||||||
hostname,
|
hostname,
|
||||||
default: defaultLocale,
|
default: defaultLocale,
|
||||||
|
fallback = defaultLocale,
|
||||||
}: GetClientLocaleOptions) => {
|
}: GetClientLocaleOptions) => {
|
||||||
let locale
|
let locale
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') return fallback
|
||||||
return defaultLocale
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hostname) {
|
if (hostname) {
|
||||||
locale = getFirstMatch(window.location.hostname, hostname)
|
locale = getFirstMatch(window.location.hostname, hostname)
|
||||||
|
@ -88,5 +78,5 @@ export const getClientLocale = ({
|
||||||
if (locale) return locale
|
if (locale) return locale
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultLocale
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
import merge from 'deepmerge'
|
import merge from 'deepmerge'
|
||||||
|
|
||||||
import { GetClientLocaleOptions, MessageObject } from './types'
|
import { MessageObject } from './types'
|
||||||
import { getClientLocale } from './includes/utils'
|
|
||||||
import { $locale } from './stores/locale'
|
|
||||||
|
|
||||||
// defineMessages allow us to define and extract dynamic message ids
|
// defineMessages allow us to define and extract dynamic message ids
|
||||||
export function defineMessages(i: Record<string, MessageObject>) {
|
export function defineMessages(i: Record<string, MessageObject>) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
export function waitInitialLocale(options: GetClientLocaleOptions | string) {
|
export {
|
||||||
if (typeof options === 'string') {
|
$locale as locale,
|
||||||
return $locale.set(options)
|
setInitialLocale,
|
||||||
}
|
// @deprecated
|
||||||
return $locale.set(getClientLocale(options))
|
setInitialLocale as waitInitialLocale,
|
||||||
}
|
} from './stores/locale'
|
||||||
|
|
||||||
export { $locale as locale, loadLocale as preloadLocale } from './stores/locale'
|
|
||||||
export {
|
export {
|
||||||
$dictionary as dictionary,
|
$dictionary as dictionary,
|
||||||
$locales as locales,
|
$locales as locales,
|
||||||
|
@ -26,7 +22,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 { getClientLocale, merge }
|
export { merge }
|
||||||
export { customFormats, addCustomFormats } from './includes/formats'
|
export { customFormats, addCustomFormats } from './includes/formats'
|
||||||
export {
|
export {
|
||||||
flushQueue as waitLocale,
|
flushQueue as waitLocale,
|
||||||
|
|
|
@ -3,22 +3,25 @@ import { writable, derived } from 'svelte/store'
|
||||||
|
|
||||||
import { LocaleDictionary } from '../types/index'
|
import { LocaleDictionary } from '../types/index'
|
||||||
|
|
||||||
|
import { getFallbackOf } from './locale'
|
||||||
|
|
||||||
let dictionary: LocaleDictionary
|
let dictionary: LocaleDictionary
|
||||||
|
|
||||||
const $dictionary = writable<LocaleDictionary>({})
|
const $dictionary = writable<LocaleDictionary>({})
|
||||||
$dictionary.subscribe(newDictionary => {
|
|
||||||
dictionary = newDictionary
|
|
||||||
})
|
|
||||||
|
|
||||||
function getDictionary() {
|
export function getDictionary() {
|
||||||
return dictionary
|
return dictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasLocaleDictionary(locale: string) {
|
export function hasLocaleDictionary(locale: string) {
|
||||||
return locale in dictionary
|
return locale in dictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMessages(locale: string, ...partials: LocaleDictionary[]) {
|
export function getAvailableLocale(locale: string): string | null {
|
||||||
|
if (locale in dictionary || locale == null) return locale
|
||||||
|
return getAvailableLocale(getFallbackOf(locale))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addMessages(locale: string, ...partials: LocaleDictionary[]) {
|
||||||
$dictionary.update(d => {
|
$dictionary.update(d => {
|
||||||
dictionary[locale] = merge.all([dictionary[locale] || {}].concat(partials))
|
dictionary[locale] = merge.all([dictionary[locale] || {}].concat(partials))
|
||||||
return d
|
return d
|
||||||
|
@ -29,10 +32,6 @@ const $locales = derived([$dictionary], ([$dictionary]) =>
|
||||||
Object.keys($dictionary)
|
Object.keys($dictionary)
|
||||||
)
|
)
|
||||||
|
|
||||||
export {
|
$dictionary.subscribe(newDictionary => (dictionary = newDictionary))
|
||||||
$dictionary,
|
|
||||||
$locales,
|
export { $dictionary, $locales }
|
||||||
getDictionary,
|
|
||||||
hasLocaleDictionary,
|
|
||||||
addMessages,
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,13 +3,7 @@ import { derived } from 'svelte/store'
|
||||||
import { Formatter, MessageObject } from '../types'
|
import { Formatter, MessageObject } from '../types'
|
||||||
import { lookupMessage } from '../includes/lookup'
|
import { lookupMessage } from '../includes/lookup'
|
||||||
import { hasLocaleQueue } from '../includes/loaderQueue'
|
import { hasLocaleQueue } from '../includes/loaderQueue'
|
||||||
import {
|
import { capital, upper, lower, title } from '../includes/utils'
|
||||||
getAllFallbackLocales,
|
|
||||||
capital,
|
|
||||||
upper,
|
|
||||||
lower,
|
|
||||||
title,
|
|
||||||
} from '../includes/utils'
|
|
||||||
import {
|
import {
|
||||||
getMessageFormatter,
|
getMessageFormatter,
|
||||||
getTimeFormatter,
|
getTimeFormatter,
|
||||||
|
@ -18,7 +12,7 @@ import {
|
||||||
} from '../includes/formats'
|
} from '../includes/formats'
|
||||||
|
|
||||||
import { getDictionary, $dictionary } from './dictionary'
|
import { getDictionary, $dictionary } from './dictionary'
|
||||||
import { getCurrentLocale, $locale } from './locale'
|
import { getCurrentLocale, getFallbacksOf, $locale } from './locale'
|
||||||
|
|
||||||
const formatMessage: Formatter = (id, options = {}) => {
|
const formatMessage: Formatter = (id, options = {}) => {
|
||||||
if (typeof id === 'object') {
|
if (typeof id === 'object') {
|
||||||
|
@ -38,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 "${getAllFallbackLocales(
|
`[svelte-i18n] The message "${id}" was not found in "${getFallbacksOf(
|
||||||
locale
|
locale
|
||||||
).join('", "')}". ${
|
).join('", "')}". ${
|
||||||
hasLocaleQueue(getCurrentLocale())
|
hasLocaleQueue(getCurrentLocale())
|
||||||
|
|
|
@ -1,30 +1,54 @@
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
import { getFallbackLocale, getAllFallbackLocales } from '../includes/utils'
|
|
||||||
import { flushQueue, hasLocaleQueue } from '../includes/loaderQueue'
|
import { flushQueue, hasLocaleQueue } from '../includes/loaderQueue'
|
||||||
|
import { getClientLocale } from '../includes/utils'
|
||||||
|
import { GetClientLocaleOptions } from '../types'
|
||||||
|
|
||||||
import { getDictionary } from './dictionary'
|
import { getAvailableLocale } from './dictionary'
|
||||||
|
|
||||||
|
let fallback: string = null
|
||||||
let current: string
|
let current: string
|
||||||
const $locale = writable(null)
|
const $locale = writable(null)
|
||||||
|
|
||||||
|
export function getFallbackLocale() {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setfallbackLocale(locale: string) {
|
||||||
|
fallback = locale
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFallbackLocaleOf(localeA: string, localeB: string) {
|
||||||
|
return localeB.indexOf(localeA) === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFallbackOf(locale: string) {
|
||||||
|
const index = locale.lastIndexOf('-')
|
||||||
|
if (index > 0) return locale.slice(0, index)
|
||||||
|
if (fallback && !isFallbackLocaleOf(locale, fallback)) return fallback
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFallbacksOf(locale: string): string[] {
|
||||||
|
const locales = locale
|
||||||
|
.split('-')
|
||||||
|
.map((_, i, arr) => arr.slice(0, i + 1).join('-'))
|
||||||
|
|
||||||
|
if (fallback != null && !isFallbackLocaleOf(locale, fallback)) {
|
||||||
|
return locales.concat(getFallbacksOf(fallback))
|
||||||
|
}
|
||||||
|
return locales
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentLocale() {
|
function getCurrentLocale() {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAvailableLocale(locale: string): string | null {
|
export function setInitialLocale(options: GetClientLocaleOptions) {
|
||||||
if (locale in getDictionary() || locale == null) return locale
|
if (typeof options.fallback === 'string') {
|
||||||
return getAvailableLocale(getFallbackLocale(locale))
|
setfallbackLocale(options.fallback)
|
||||||
}
|
}
|
||||||
|
return $locale.set(getClientLocale(options))
|
||||||
function loadLocale(localeToLoad: string) {
|
|
||||||
return Promise.all(
|
|
||||||
getAllFallbackLocales(localeToLoad).map(localeItem =>
|
|
||||||
flushQueue(localeItem)
|
|
||||||
.then(() => [localeItem, { err: undefined }])
|
|
||||||
.catch(e => [localeItem, { err: e }])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$locale.subscribe((newLocale: string) => {
|
$locale.subscribe((newLocale: string) => {
|
||||||
|
@ -37,17 +61,13 @@ $locale.subscribe((newLocale: string) => {
|
||||||
|
|
||||||
const localeSet = $locale.set
|
const localeSet = $locale.set
|
||||||
$locale.set = (newLocale: string): void | Promise<void> => {
|
$locale.set = (newLocale: string): void | Promise<void> => {
|
||||||
if (getAvailableLocale(newLocale)) {
|
if (getAvailableLocale(newLocale) && hasLocaleQueue(newLocale)) {
|
||||||
if (hasLocaleQueue(newLocale)) {
|
return flushQueue(newLocale).then(() => localeSet(newLocale))
|
||||||
return flushQueue(newLocale).then(() => localeSet(newLocale))
|
|
||||||
}
|
|
||||||
return localeSet(newLocale)
|
|
||||||
}
|
}
|
||||||
|
return localeSet(newLocale)
|
||||||
throw Error(`[svelte-i18n] Locale "${newLocale}" not found.`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$locale.update = (fn: (locale: string) => void | Promise<void>) =>
|
$locale.update = (fn: (locale: string) => void | Promise<void>) =>
|
||||||
localeSet(fn(current))
|
localeSet(fn(current))
|
||||||
|
|
||||||
export { $locale, loadLocale, flushQueue, getCurrentLocale }
|
export { $locale, flushQueue, getCurrentLocale }
|
||||||
|
|
|
@ -4,13 +4,12 @@ import {
|
||||||
dictionary,
|
dictionary,
|
||||||
locale,
|
locale,
|
||||||
format,
|
format,
|
||||||
getClientLocale,
|
|
||||||
addCustomFormats,
|
addCustomFormats,
|
||||||
customFormats,
|
customFormats,
|
||||||
preloadLocale,
|
|
||||||
register,
|
register,
|
||||||
waitLocale,
|
waitLocale,
|
||||||
} from '../../src/client'
|
} from '../../src/client'
|
||||||
|
import { getClientLocale } from '../../src/client/includes/utils'
|
||||||
|
|
||||||
global.Intl = require('intl')
|
global.Intl = require('intl')
|
||||||
|
|
||||||
|
@ -42,19 +41,9 @@ describe('locale', () => {
|
||||||
await locale.set('en-US')
|
await locale.set('en-US')
|
||||||
expect(currentLocale).toBe('en-US')
|
expect(currentLocale).toBe('en-US')
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should throw an error if locale doesn't exist", () => {
|
|
||||||
expect(() => locale.set('FOO')).toThrow()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('dictionary', () => {
|
describe('dictionary', () => {
|
||||||
it('load a locale and its derived locales if dictionary is a loader', async () => {
|
|
||||||
const loaded = await preloadLocale('pt-PT')
|
|
||||||
expect(loaded[0][0]).toEqual('pt')
|
|
||||||
expect(loaded[1][0]).toEqual('pt-PT')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('load a partial dictionary and merge it with the existing one', async () => {
|
it('load a partial dictionary and merge it with the existing one', async () => {
|
||||||
await locale.set('en')
|
await locale.set('en')
|
||||||
register('en', () => import('../fixtures/partials/en.json'))
|
register('en', () => import('../fixtures/partials/en.json'))
|
||||||
|
@ -96,7 +85,8 @@ describe('formatting', () => {
|
||||||
expect(_({ id: 'switch.lang' })).toBe('Switch language')
|
expect(_({ id: 'switch.lang' })).toBe('Switch language')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should translate to passed locale', () => {
|
it('should translate to passed locale', async () => {
|
||||||
|
await waitLocale('pt-BR')
|
||||||
expect(_('switch.lang', { locale: 'pt' })).toBe('Trocar idioma')
|
expect(_('switch.lang', { locale: 'pt' })).toBe('Trocar idioma')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user