From 6e0df2fb25e1bf9038eb4252ba993541a7fa2b4a Mon Sep 17 00:00:00 2001 From: Christian Kaisermann Date: Sat, 23 Nov 2019 23:38:15 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fallback=20behaviour=20an?= =?UTF-8?q?d=20simplify=20API=20contact=20points?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/src/routes/_layout.svelte | 9 ++-- src/client/includes/loaderQueue.ts | 8 ++-- src/client/includes/lookup.ts | 5 +-- src/client/includes/utils.ts | 16 ++------ src/client/index.ts | 20 ++++----- src/client/stores/dictionary.ts | 27 ++++++------ src/client/stores/format.ts | 12 ++---- src/client/stores/locale.ts | 66 +++++++++++++++++++----------- test/client/index.test.ts | 16 ++------ 9 files changed, 83 insertions(+), 96 deletions(-) diff --git a/example/src/routes/_layout.svelte b/example/src/routes/_layout.svelte index 7771265..36f972f 100644 --- a/example/src/routes/_layout.svelte +++ b/example/src/routes/_layout.svelte @@ -1,10 +1,11 @@ diff --git a/src/client/includes/loaderQueue.ts b/src/client/includes/loaderQueue.ts index 988c541..37e0d97 100644 --- a/src/client/includes/loaderQueue.ts +++ b/src/client/includes/loaderQueue.ts @@ -4,11 +4,9 @@ import { $dictionary, addMessages, } from '../stores/dictionary' -import { getCurrentLocale } from '../stores/locale' +import { getCurrentLocale, getFallbacksOf } from '../stores/locale' import { $isLoading } from '../stores/loading' -import { getAllFallbackLocales } from './utils' - type Queue = Set const loaderQueue: Record = {} @@ -25,7 +23,7 @@ function getLocaleQueue(locale: string) { } function getLocalesQueues(locale: string) { - return getAllFallbackLocales(locale) + return getFallbacksOf(locale) .reverse() .map<[string, MessagesLoader[]]>(localeItem => { const queue = getLocaleQueue(localeItem) @@ -35,7 +33,7 @@ function getLocalesQueues(locale: string) { } export function hasLocaleQueue(locale: string) { - return getAllFallbackLocales(locale) + return getFallbacksOf(locale) .reverse() .some(getLocaleQueue) } diff --git a/src/client/includes/lookup.ts b/src/client/includes/lookup.ts index 39d0ec4..9f786c2 100644 --- a/src/client/includes/lookup.ts +++ b/src/client/includes/lookup.ts @@ -2,8 +2,7 @@ import resolvePath from 'object-resolve-path' import { hasLocaleDictionary } from '../stores/dictionary' - -import { getFallbackLocale } from './utils' +import { getFallbackOf } from '../stores/locale' const lookupCache: Record> = {} @@ -34,6 +33,6 @@ export const lookupMessage = ( return addToCache( path, locale, - lookupMessage(dictionary, path, getFallbackLocale(locale)) + lookupMessage(dictionary, path, getFallbackOf(locale)) ) } diff --git a/src/client/includes/utils.ts b/src/client/includes/utils.ts index fdfe24c..e3335d1 100644 --- a/src/client/includes/utils.ts +++ b/src/client/includes/utils.ts @@ -16,15 +16,6 @@ export function lower(str: string) { 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 keyVal = urlPart .substr(1) @@ -49,12 +40,11 @@ export const getClientLocale = ({ pathname, hostname, default: defaultLocale, + fallback = defaultLocale, }: GetClientLocaleOptions) => { let locale - if (typeof window === 'undefined') { - return defaultLocale - } + if (typeof window === 'undefined') return fallback if (hostname) { locale = getFirstMatch(window.location.hostname, hostname) @@ -88,5 +78,5 @@ export const getClientLocale = ({ if (locale) return locale } - return defaultLocale + return fallback } diff --git a/src/client/index.ts b/src/client/index.ts index 24f88df..7b08c6c 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,22 +1,18 @@ import merge from 'deepmerge' -import { GetClientLocaleOptions, MessageObject } from './types' -import { getClientLocale } from './includes/utils' -import { $locale } from './stores/locale' +import { MessageObject } from './types' // defineMessages allow us to define and extract dynamic message ids export function defineMessages(i: Record) { return i } -export function waitInitialLocale(options: GetClientLocaleOptions | string) { - if (typeof options === 'string') { - return $locale.set(options) - } - return $locale.set(getClientLocale(options)) -} - -export { $locale as locale, loadLocale as preloadLocale } from './stores/locale' +export { + $locale as locale, + setInitialLocale, + // @deprecated + setInitialLocale as waitInitialLocale, +} from './stores/locale' export { $dictionary as dictionary, $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' // utilities -export { getClientLocale, merge } +export { merge } export { customFormats, addCustomFormats } from './includes/formats' export { flushQueue as waitLocale, diff --git a/src/client/stores/dictionary.ts b/src/client/stores/dictionary.ts index 04428b8..ba2b729 100644 --- a/src/client/stores/dictionary.ts +++ b/src/client/stores/dictionary.ts @@ -3,22 +3,25 @@ import { writable, derived } from 'svelte/store' import { LocaleDictionary } from '../types/index' +import { getFallbackOf } from './locale' + let dictionary: LocaleDictionary - const $dictionary = writable({}) -$dictionary.subscribe(newDictionary => { - dictionary = newDictionary -}) -function getDictionary() { +export function getDictionary() { return dictionary } -function hasLocaleDictionary(locale: string) { +export function hasLocaleDictionary(locale: string) { 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[locale] = merge.all([dictionary[locale] || {}].concat(partials)) return d @@ -29,10 +32,6 @@ const $locales = derived([$dictionary], ([$dictionary]) => Object.keys($dictionary) ) -export { - $dictionary, - $locales, - getDictionary, - hasLocaleDictionary, - addMessages, -} +$dictionary.subscribe(newDictionary => (dictionary = newDictionary)) + +export { $dictionary, $locales } diff --git a/src/client/stores/format.ts b/src/client/stores/format.ts index cf86055..78b4f10 100644 --- a/src/client/stores/format.ts +++ b/src/client/stores/format.ts @@ -3,13 +3,7 @@ import { derived } from 'svelte/store' import { Formatter, MessageObject } from '../types' import { lookupMessage } from '../includes/lookup' import { hasLocaleQueue } from '../includes/loaderQueue' -import { - getAllFallbackLocales, - capital, - upper, - lower, - title, -} from '../includes/utils' +import { capital, upper, lower, title } from '../includes/utils' import { getMessageFormatter, getTimeFormatter, @@ -18,7 +12,7 @@ import { } from '../includes/formats' import { getDictionary, $dictionary } from './dictionary' -import { getCurrentLocale, $locale } from './locale' +import { getCurrentLocale, getFallbacksOf, $locale } from './locale' const formatMessage: Formatter = (id, options = {}) => { if (typeof id === 'object') { @@ -38,7 +32,7 @@ const formatMessage: Formatter = (id, options = {}) => { if (!message) { console.warn( - `[svelte-i18n] The message "${id}" was not found in "${getAllFallbackLocales( + `[svelte-i18n] The message "${id}" was not found in "${getFallbacksOf( locale ).join('", "')}". ${ hasLocaleQueue(getCurrentLocale()) diff --git a/src/client/stores/locale.ts b/src/client/stores/locale.ts index 949ea87..c924da4 100644 --- a/src/client/stores/locale.ts +++ b/src/client/stores/locale.ts @@ -1,30 +1,54 @@ import { writable } from 'svelte/store' -import { getFallbackLocale, getAllFallbackLocales } from '../includes/utils' 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 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() { return current } -function getAvailableLocale(locale: string): string | null { - if (locale in getDictionary() || locale == null) return locale - return getAvailableLocale(getFallbackLocale(locale)) -} - -function loadLocale(localeToLoad: string) { - return Promise.all( - getAllFallbackLocales(localeToLoad).map(localeItem => - flushQueue(localeItem) - .then(() => [localeItem, { err: undefined }]) - .catch(e => [localeItem, { err: e }]) - ) - ) +export function setInitialLocale(options: GetClientLocaleOptions) { + if (typeof options.fallback === 'string') { + setfallbackLocale(options.fallback) + } + return $locale.set(getClientLocale(options)) } $locale.subscribe((newLocale: string) => { @@ -37,17 +61,13 @@ $locale.subscribe((newLocale: string) => { const localeSet = $locale.set $locale.set = (newLocale: string): void | Promise => { - if (getAvailableLocale(newLocale)) { - if (hasLocaleQueue(newLocale)) { - return flushQueue(newLocale).then(() => localeSet(newLocale)) - } - return localeSet(newLocale) + if (getAvailableLocale(newLocale) && hasLocaleQueue(newLocale)) { + return flushQueue(newLocale).then(() => localeSet(newLocale)) } - - throw Error(`[svelte-i18n] Locale "${newLocale}" not found.`) + return localeSet(newLocale) } $locale.update = (fn: (locale: string) => void | Promise) => localeSet(fn(current)) -export { $locale, loadLocale, flushQueue, getCurrentLocale } +export { $locale, flushQueue, getCurrentLocale } diff --git a/test/client/index.test.ts b/test/client/index.test.ts index bf2e2d5..43140e7 100644 --- a/test/client/index.test.ts +++ b/test/client/index.test.ts @@ -4,13 +4,12 @@ import { dictionary, locale, format, - getClientLocale, addCustomFormats, customFormats, - preloadLocale, register, waitLocale, } from '../../src/client' +import { getClientLocale } from '../../src/client/includes/utils' global.Intl = require('intl') @@ -42,19 +41,9 @@ describe('locale', () => { await locale.set('en-US') expect(currentLocale).toBe('en-US') }) - - it("should throw an error if locale doesn't exist", () => { - expect(() => locale.set('FOO')).toThrow() - }) }) 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 () => { await locale.set('en') register('en', () => import('../fixtures/partials/en.json')) @@ -96,7 +85,8 @@ describe('formatting', () => { 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') })