mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-09-28 15:14:45 +02: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('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')
|
||||||
|
7
package-lock.json
generated
7
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "svelte-i18n",
|
"name": "svelte-i18n",
|
||||||
"version": "2.0.1",
|
"version": "2.0.3",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2502,6 +2502,11 @@
|
|||||||
"integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==",
|
"integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"dlv": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||||
|
},
|
||||||
"doctrine": {
|
"doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
|
@ -80,6 +80,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^4.0.1",
|
"commander": "^4.0.1",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
|
"dlv": "^1.1.3",
|
||||||
"estree-walker": "^0.9.0",
|
"estree-walker": "^0.9.0",
|
||||||
"fast-memoize": "^2.5.1",
|
"fast-memoize": "^2.5.1",
|
||||||
"intl-messageformat": "^7.5.2",
|
"intl-messageformat": "^7.5.2",
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
// todo invalidate only keys with null values
|
import { getMessageFromDictionary } from '../stores/dictionary'
|
||||||
import resolvePath from 'object-resolve-path'
|
|
||||||
|
|
||||||
import { hasLocaleDictionary } from '../stores/dictionary'
|
|
||||||
import { getFallbackOf } from '../stores/locale'
|
import { getFallbackOf } from '../stores/locale'
|
||||||
|
|
||||||
const lookupCache: Record<string, Record<string, string>> = {}
|
const lookupCache: Record<string, Record<string, string>> = {}
|
||||||
@ -13,26 +10,14 @@ const addToCache = (path: string, locale: string, message: string) => {
|
|||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lookupMessage = (
|
export const lookupMessage = (path: string, locale: string): string => {
|
||||||
dictionary: any,
|
|
||||||
path: string,
|
|
||||||
locale: string
|
|
||||||
): string => {
|
|
||||||
if (locale == null) return null
|
if (locale == null) return null
|
||||||
if (locale in lookupCache && path in lookupCache[locale]) {
|
if (locale in lookupCache && path in lookupCache[locale]) {
|
||||||
return lookupCache[locale][path]
|
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(
|
const message = getMessageFromDictionary(locale, path)
|
||||||
path,
|
if (message) return message
|
||||||
locale,
|
|
||||||
lookupMessage(dictionary, path, getFallbackOf(locale))
|
return addToCache(path, locale, lookupMessage(path, getFallbackOf(locale)))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,11 @@ export function defineMessages(i: Record<string, MessageObject>) {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
export { $locale as locale, setInitialLocale } from './stores/locale'
|
export {
|
||||||
|
$locale as locale,
|
||||||
|
setInitialLocale,
|
||||||
|
setFallbackLocale,
|
||||||
|
} from './stores/locale'
|
||||||
export {
|
export {
|
||||||
$dictionary as dictionary,
|
$dictionary as dictionary,
|
||||||
$locales as locales,
|
$locales as locales,
|
||||||
@ -26,4 +30,3 @@ export {
|
|||||||
|
|
||||||
// @deprecated
|
// @deprecated
|
||||||
export { getClientLocale } from './includes/utils'
|
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 merge from 'deepmerge'
|
||||||
import { writable, derived } from 'svelte/store'
|
import { writable, derived } from 'svelte/store'
|
||||||
|
|
||||||
import { LocaleDictionary } from '../types/index'
|
import { Dictionary } from '../types/index'
|
||||||
|
|
||||||
import { getFallbackOf } from './locale'
|
import { getFallbackOf } from './locale'
|
||||||
|
|
||||||
let dictionary: LocaleDictionary
|
let dictionary: Dictionary
|
||||||
const $dictionary = writable<LocaleDictionary>({})
|
const $dictionary = writable<Dictionary>({})
|
||||||
|
|
||||||
|
export function getLocaleDictionary(locale: string) {
|
||||||
|
return (dictionary[locale] as Dictionary) || null
|
||||||
|
}
|
||||||
|
|
||||||
export function getDictionary() {
|
export function getDictionary() {
|
||||||
return dictionary
|
return dictionary
|
||||||
@ -16,14 +21,28 @@ export function hasLocaleDictionary(locale: string) {
|
|||||||
return locale in dictionary
|
return locale in dictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAvailableLocale(locale: string): string | null {
|
export function getMessageFromDictionary(locale: string, id: string) {
|
||||||
if (locale in dictionary || locale == null) return locale
|
if (hasLocaleDictionary(locale)) {
|
||||||
return getAvailableLocale(getFallbackOf(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.update(d => {
|
||||||
dictionary[locale] = merge.all([dictionary[locale] || {}].concat(partials))
|
dictionary[locale] = merge.all<Dictionary>(
|
||||||
|
[getLocaleDictionary(locale) || {}].concat(partials)
|
||||||
|
)
|
||||||
return d
|
return d
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
getNumberFormatter,
|
getNumberFormatter,
|
||||||
} from '../includes/formats'
|
} from '../includes/formats'
|
||||||
|
|
||||||
import { getDictionary, $dictionary } from './dictionary'
|
import { $dictionary } from './dictionary'
|
||||||
import { getCurrentLocale, getFallbacksOf, $locale } from './locale'
|
import { getCurrentLocale, getFallbacksOf, $locale } from './locale'
|
||||||
|
|
||||||
const formatMessage: Formatter = (id, options = {}) => {
|
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) {
|
if (!message) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
@ -4,7 +4,7 @@ import { flushQueue, hasLocaleQueue } from '../includes/loaderQueue'
|
|||||||
import { getClientLocale } from '../includes/utils'
|
import { getClientLocale } from '../includes/utils'
|
||||||
import { GetClientLocaleOptions } from '../types'
|
import { GetClientLocaleOptions } from '../types'
|
||||||
|
|
||||||
import { getAvailableLocale } from './dictionary'
|
import { getClosestAvailableLocale } from './dictionary'
|
||||||
|
|
||||||
let fallbackLocale: string = null
|
let fallbackLocale: string = null
|
||||||
let current: string
|
let current: string
|
||||||
@ -71,7 +71,7 @@ $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) && hasLocaleQueue(newLocale)) {
|
if (getClosestAvailableLocale(newLocale) && hasLocaleQueue(newLocale)) {
|
||||||
return flushQueue(newLocale).then(() => localeSet(newLocale))
|
return flushQueue(newLocale).then(() => localeSet(newLocale))
|
||||||
}
|
}
|
||||||
return localeSet(newLocale)
|
return localeSet(newLocale)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export interface LocaleDictionary {
|
export interface Dictionary {
|
||||||
[key: string]: LocaleDictionary | LocaleDictionary[] | string | object
|
[key: string]: string | string[] | Dictionary | Dictionary[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageObject {
|
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'
|
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,
|
$locale,
|
||||||
isRelatedLocale,
|
isRelatedLocale,
|
||||||
} from '../../src/client/stores/locale'
|
} from '../../src/client/stores/locale'
|
||||||
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setFallbackLocale(undefined)
|
setFallbackLocale(undefined)
|
||||||
|
$locale.set(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('sets and gets the fallback locale', () => {
|
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)
|
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('az-Cyrl-AZ')).toBe('az-Cyrl')
|
||||||
expect(getFallbackOf('en-US')).toBe('en')
|
expect(getFallbackOf('en-US')).toBe('en')
|
||||||
expect(getFallbackOf('en')).toBe(null)
|
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')
|
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('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'])
|
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')
|
setFallbackLocale('pt')
|
||||||
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', 'pt'])
|
expect(getFallbacksOf('en-US')).toEqual(['en', 'en-US', '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',
|
'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')
|
setFallbackLocale('pt-BR')
|
||||||
expect(getFallbacksOf('pt-BR')).toEqual(['pt', 'pt-BR'])
|
expect(getFallbacksOf('pt-BR')).toEqual(['pt', 'pt-BR'])
|
||||||
expect(getFallbacksOf('pt')).toEqual(['pt'])
|
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