mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-11-16 09:59:58 +01:00
test: 💍 add tests for dictionary store and utilities
This commit is contained in:
parent
07fb37e325
commit
965c18acc1
@ -1,5 +1,7 @@
|
||||
import { register } from 'svelte-i18n'
|
||||
import { register, setFallbackLocale } from 'svelte-i18n'
|
||||
|
||||
register('en', () => import('../messages/en.json'))
|
||||
register('pt-BR', () => import('../messages/pt-BR.json'))
|
||||
register('es-ES', () => import('../messages/es-ES.json'))
|
||||
|
||||
setFallbackLocale('en')
|
||||
|
7
package-lock.json
generated
7
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "svelte-i18n",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -2502,6 +2502,11 @@
|
||||
"integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==",
|
||||
"dev": true
|
||||
},
|
||||
"dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"doctrine": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||
|
@ -80,6 +80,7 @@
|
||||
"dependencies": {
|
||||
"commander": "^4.0.1",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dlv": "^1.1.3",
|
||||
"estree-walker": "^0.9.0",
|
||||
"fast-memoize": "^2.5.1",
|
||||
"intl-messageformat": "^7.5.2",
|
||||
|
@ -1,7 +1,4 @@
|
||||
// todo invalidate only keys with null values
|
||||
import resolvePath from 'object-resolve-path'
|
||||
|
||||
import { hasLocaleDictionary } from '../stores/dictionary'
|
||||
import { getMessageFromDictionary } from '../stores/dictionary'
|
||||
import { getFallbackOf } from '../stores/locale'
|
||||
|
||||
const lookupCache: Record<string, Record<string, string>> = {}
|
||||
@ -13,26 +10,14 @@ const addToCache = (path: string, locale: string, message: string) => {
|
||||
return message
|
||||
}
|
||||
|
||||
export const lookupMessage = (
|
||||
dictionary: any,
|
||||
path: string,
|
||||
locale: string
|
||||
): string => {
|
||||
export const lookupMessage = (path: string, locale: string): string => {
|
||||
if (locale == null) return null
|
||||
if (locale in lookupCache && path in lookupCache[locale]) {
|
||||
return lookupCache[locale][path]
|
||||
}
|
||||
if (hasLocaleDictionary(locale)) {
|
||||
if (path in dictionary[locale]) {
|
||||
return addToCache(path, locale, dictionary[locale][path])
|
||||
}
|
||||
const message = resolvePath(dictionary[locale], path)
|
||||
if (message) return addToCache(path, locale, message)
|
||||
}
|
||||
|
||||
return addToCache(
|
||||
path,
|
||||
locale,
|
||||
lookupMessage(dictionary, path, getFallbackOf(locale))
|
||||
)
|
||||
const message = getMessageFromDictionary(locale, path)
|
||||
if (message) return message
|
||||
|
||||
return addToCache(path, locale, lookupMessage(path, getFallbackOf(locale)))
|
||||
}
|
||||
|
@ -7,7 +7,11 @@ export function defineMessages(i: Record<string, MessageObject>) {
|
||||
return i
|
||||
}
|
||||
|
||||
export { $locale as locale, setInitialLocale } from './stores/locale'
|
||||
export {
|
||||
$locale as locale,
|
||||
setInitialLocale,
|
||||
setFallbackLocale,
|
||||
} from './stores/locale'
|
||||
export {
|
||||
$dictionary as dictionary,
|
||||
$locales as locales,
|
||||
@ -26,4 +30,3 @@ export {
|
||||
|
||||
// @deprecated
|
||||
export { getClientLocale } from './includes/utils'
|
||||
export { setInitialLocale as waitInitialLocale } from './stores/locale'
|
||||
|
@ -1,12 +1,17 @@
|
||||
import delve from 'dlv'
|
||||
import merge from 'deepmerge'
|
||||
import { writable, derived } from 'svelte/store'
|
||||
|
||||
import { LocaleDictionary } from '../types/index'
|
||||
import { Dictionary } from '../types/index'
|
||||
|
||||
import { getFallbackOf } from './locale'
|
||||
|
||||
let dictionary: LocaleDictionary
|
||||
const $dictionary = writable<LocaleDictionary>({})
|
||||
let dictionary: Dictionary
|
||||
const $dictionary = writable<Dictionary>({})
|
||||
|
||||
export function getLocaleDictionary(locale: string) {
|
||||
return (dictionary[locale] as Dictionary) || null
|
||||
}
|
||||
|
||||
export function getDictionary() {
|
||||
return dictionary
|
||||
@ -16,14 +21,28 @@ export function hasLocaleDictionary(locale: string) {
|
||||
return locale in dictionary
|
||||
}
|
||||
|
||||
export function getAvailableLocale(locale: string): string | null {
|
||||
if (locale in dictionary || locale == null) return locale
|
||||
return getAvailableLocale(getFallbackOf(locale))
|
||||
export function getMessageFromDictionary(locale: string, id: string) {
|
||||
if (hasLocaleDictionary(locale)) {
|
||||
const localeDictionary = getLocaleDictionary(locale)
|
||||
if (id in localeDictionary) {
|
||||
return localeDictionary[id]
|
||||
}
|
||||
const message = delve(localeDictionary, id)
|
||||
if (message) return message
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function addMessages(locale: string, ...partials: LocaleDictionary[]) {
|
||||
export function getClosestAvailableLocale(locale: string): string | null {
|
||||
if (locale == null || hasLocaleDictionary(locale)) return locale
|
||||
return getClosestAvailableLocale(getFallbackOf(locale))
|
||||
}
|
||||
|
||||
export function addMessages(locale: string, ...partials: Dictionary[]) {
|
||||
$dictionary.update(d => {
|
||||
dictionary[locale] = merge.all([dictionary[locale] || {}].concat(partials))
|
||||
dictionary[locale] = merge.all<Dictionary>(
|
||||
[getLocaleDictionary(locale) || {}].concat(partials)
|
||||
)
|
||||
return d
|
||||
})
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
getNumberFormatter,
|
||||
} from '../includes/formats'
|
||||
|
||||
import { getDictionary, $dictionary } from './dictionary'
|
||||
import { $dictionary } from './dictionary'
|
||||
import { getCurrentLocale, getFallbacksOf, $locale } from './locale'
|
||||
|
||||
const formatMessage: Formatter = (id, options = {}) => {
|
||||
@ -28,7 +28,7 @@ const formatMessage: Formatter = (id, options = {}) => {
|
||||
)
|
||||
}
|
||||
|
||||
const message = lookupMessage(getDictionary(), id, locale)
|
||||
const message = lookupMessage(id, locale)
|
||||
|
||||
if (!message) {
|
||||
console.warn(
|
||||
|
@ -4,7 +4,7 @@ import { flushQueue, hasLocaleQueue } from '../includes/loaderQueue'
|
||||
import { getClientLocale } from '../includes/utils'
|
||||
import { GetClientLocaleOptions } from '../types'
|
||||
|
||||
import { getAvailableLocale } from './dictionary'
|
||||
import { getClosestAvailableLocale } from './dictionary'
|
||||
|
||||
let fallbackLocale: string = null
|
||||
let current: string
|
||||
@ -71,7 +71,7 @@ $locale.subscribe((newLocale: string) => {
|
||||
|
||||
const localeSet = $locale.set
|
||||
$locale.set = (newLocale: string): void | Promise<void> => {
|
||||
if (getAvailableLocale(newLocale) && hasLocaleQueue(newLocale)) {
|
||||
if (getClosestAvailableLocale(newLocale) && hasLocaleQueue(newLocale)) {
|
||||
return flushQueue(newLocale).then(() => localeSet(newLocale))
|
||||
}
|
||||
return localeSet(newLocale)
|
||||
|
@ -1,5 +1,5 @@
|
||||
export interface LocaleDictionary {
|
||||
[key: string]: LocaleDictionary | LocaleDictionary[] | string | object
|
||||
export interface Dictionary {
|
||||
[key: string]: string | string[] | Dictionary | Dictionary[]
|
||||
}
|
||||
|
||||
export interface MessageObject {
|
||||
|
2
src/client/types/modules.d.ts
vendored
2
src/client/types/modules.d.ts
vendored
@ -1,2 +1,2 @@
|
||||
declare module 'object-resolve-path'
|
||||
declare module 'dlv'
|
||||
declare module 'nano-memoize'
|
||||
|
114
test/client/dictionary.test.ts
Normal file
114
test/client/dictionary.test.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import {
|
||||
getDictionary,
|
||||
hasLocaleDictionary,
|
||||
getClosestAvailableLocale,
|
||||
getMessageFromDictionary,
|
||||
addMessages,
|
||||
$dictionary,
|
||||
$locales,
|
||||
getLocaleDictionary,
|
||||
} from '../../src/client/stores/dictionary'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
beforeEach(() => {
|
||||
$dictionary.set({})
|
||||
})
|
||||
|
||||
test('adds a new dictionary to a locale', () => {
|
||||
addMessages('en', { field_1: 'name' })
|
||||
addMessages('pt', { field_1: 'nome' })
|
||||
|
||||
expect(get($dictionary)).toMatchObject({
|
||||
en: { field_1: 'name' },
|
||||
pt: { field_1: 'nome' },
|
||||
})
|
||||
})
|
||||
|
||||
test('merges the existing dictionaries with new ones', () => {
|
||||
addMessages('en', { field_1: 'name', deep: { prop1: 'foo' } })
|
||||
addMessages('en', { field_2: 'lastname', deep: { prop2: 'foo' } })
|
||||
addMessages('pt', { field_1: 'nome', deep: { prop1: 'foo' } })
|
||||
addMessages('pt', { field_2: 'sobrenome', deep: { prop2: 'foo' } })
|
||||
|
||||
expect(get($dictionary)).toMatchObject({
|
||||
en: {
|
||||
field_1: 'name',
|
||||
field_2: 'lastname',
|
||||
deep: { prop1: 'foo', prop2: 'foo' },
|
||||
},
|
||||
pt: {
|
||||
field_1: 'nome',
|
||||
field_2: 'sobrenome',
|
||||
deep: { prop1: 'foo', prop2: 'foo' },
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('gets the whole current dictionary', () => {
|
||||
addMessages('en', { field_1: 'name' })
|
||||
expect(getDictionary()).toMatchObject({
|
||||
en: { field_1: 'name' },
|
||||
})
|
||||
})
|
||||
|
||||
test('gets the dictionary of a locale', () => {
|
||||
addMessages('en', { field_1: 'name' })
|
||||
expect(getLocaleDictionary('en')).toMatchObject({ field_1: 'name' })
|
||||
})
|
||||
|
||||
test('checks if a locale dictionary exists', () => {
|
||||
addMessages('pt', { field_1: 'name' })
|
||||
expect(hasLocaleDictionary('en')).toBe(false)
|
||||
expect(hasLocaleDictionary('pt')).toBe(true)
|
||||
})
|
||||
|
||||
test('gets the closest available locale', () => {
|
||||
addMessages('pt', { field_1: 'name' })
|
||||
expect(getClosestAvailableLocale('pt-BR')).toBe('pt')
|
||||
})
|
||||
|
||||
test("returns null if there's no closest locale available", () => {
|
||||
addMessages('pt', { field_1: 'name' })
|
||||
expect(getClosestAvailableLocale('it-IT')).toBe(null)
|
||||
})
|
||||
|
||||
test('lists all locales in the dictionary', () => {
|
||||
addMessages('en', {})
|
||||
addMessages('pt', {})
|
||||
addMessages('pt-BR', {})
|
||||
expect(get($locales)).toEqual(['en', 'pt', 'pt-BR'])
|
||||
})
|
||||
|
||||
describe('getting messages', () => {
|
||||
test('gets a message from a shallow dictionary', () => {
|
||||
addMessages('en', { message: 'Some message' })
|
||||
expect(getMessageFromDictionary('en', 'message')).toBe('Some message')
|
||||
})
|
||||
|
||||
test('gets a message from a deep object in the dictionary', () => {
|
||||
addMessages('en', { messages: { message_1: 'Some message' } })
|
||||
expect(getMessageFromDictionary('en', 'messages.message_1')).toBe(
|
||||
'Some message'
|
||||
)
|
||||
})
|
||||
|
||||
test('gets a message from an array in the dictionary', () => {
|
||||
addMessages('en', { messages: ['Some message', 'Other message'] })
|
||||
expect(getMessageFromDictionary('en', 'messages.0')).toBe('Some message')
|
||||
expect(getMessageFromDictionary('en', 'messages.1')).toBe('Other message')
|
||||
})
|
||||
|
||||
test('accepts english in dictionary keys', () => {
|
||||
addMessages('pt', {
|
||||
'Hey man, how are you today?': 'E ai cara, como você vai hoje?',
|
||||
})
|
||||
expect(getMessageFromDictionary('pt', 'Hey man, how are you today?')).toBe(
|
||||
'E ai cara, como você vai hoje?'
|
||||
)
|
||||
})
|
||||
|
||||
test('returns null for missing messages', () => {
|
||||
addMessages('en', {})
|
||||
expect(getMessageFromDictionary('en', 'foo')).toBe(null)
|
||||
})
|
||||
})
|
@ -9,9 +9,11 @@ import {
|
||||
$locale,
|
||||
isRelatedLocale,
|
||||
} from '../../src/client/stores/locale'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
beforeEach(() => {
|
||||
setFallbackLocale(undefined)
|
||||
$locale.set(undefined)
|
||||
})
|
||||
|
||||
test('sets and gets the fallback locale', () => {
|
||||
@ -33,7 +35,7 @@ test('checks if a locale is a fallback locale of another locale', () => {
|
||||
expect(isRelatedLocale('en-US', 'it')).toBe(false)
|
||||
})
|
||||
|
||||
test('gets the next fallback locale of a certain locale', () => {
|
||||
test('gets the next fallback locale of a locale', () => {
|
||||
expect(getFallbackOf('az-Cyrl-AZ')).toBe('az-Cyrl')
|
||||
expect(getFallbackOf('en-US')).toBe('en')
|
||||
expect(getFallbackOf('en')).toBe(null)
|
||||
@ -54,13 +56,13 @@ test('if global fallback locale has a fallback, it should return it', () => {
|
||||
expect(getFallbackOf('en-US')).toBe('en')
|
||||
})
|
||||
|
||||
test('gets all fallback locales of a certain locale', () => {
|
||||
test('gets all fallback locales of a locale', () => {
|
||||
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US'])
|
||||
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US'])
|
||||
expect(getFallbacksOf('az-Cyrl-AZ')).toEqual(['az', 'az-Cyrl', 'az-Cyrl-AZ'])
|
||||
})
|
||||
|
||||
test('gets all fallback locales of a certain locale including the global fallback locale', () => {
|
||||
test('gets all fallback locales of a locale including the global fallback locale', () => {
|
||||
setFallbackLocale('pt')
|
||||
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt'])
|
||||
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt'])
|
||||
@ -71,9 +73,35 @@ test('gets all fallback locales of a certain locale including the global fallbac
|
||||
'pt',
|
||||
])
|
||||
})
|
||||
test('gets all fallback locales of a locale including the global fallback locale and its fallbacks', () => {
|
||||
setFallbackLocale('pt-BR')
|
||||
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR'])
|
||||
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR'])
|
||||
expect(getFallbacksOf('az-Cyrl-AZ')).toEqual([
|
||||
'az',
|
||||
'az-Cyrl',
|
||||
'az-Cyrl-AZ',
|
||||
'pt',
|
||||
'pt-BR',
|
||||
])
|
||||
})
|
||||
|
||||
test('should not list fallback locale twice', () => {
|
||||
test("don't list fallback locale twice", () => {
|
||||
setFallbackLocale('pt-BR')
|
||||
expect(getFallbacksOf('pt-BR')).toEqual(['pt', 'pt-BR'])
|
||||
expect(getFallbacksOf('pt')).toEqual(['pt'])
|
||||
})
|
||||
|
||||
test('gets the current locale', () => {
|
||||
expect(getCurrentLocale()).toBe(undefined)
|
||||
$locale.set('es-ES')
|
||||
expect(getCurrentLocale()).toBe('es-ES')
|
||||
})
|
||||
|
||||
test('sets the global fallback when defining initial locale', () => {
|
||||
setInitialLocale({
|
||||
fallback: 'pt',
|
||||
})
|
||||
expect(get($locale)).toBe('pt')
|
||||
expect(getFallbackLocale()).toBe('pt')
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user