diff --git a/docs/FAQ.md b/docs/FAQ.md index 5ddb479..84a4c0c 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -32,3 +32,24 @@ export default { } ``` + +##### `How can I use svelte-i18n with SvelteKit?` + +You can find a guide on how to use `svelte-i18n` with `SvelteKit` [here](/docs/Svelte-Kit.md). + +##### `Can I use the formatter functions outside of a Svelte component?` + +Yes, you can! Since the exported formatters are stores, you need to subscribe to them to get the value. `svelte-i18n` provides a utility method called `unwrapFunctionStore` to help you with that: + +```js +// some-file.js +import { unwrapFunctionStore, format, formatNumber } from 'svelte-i18n'; + +const $formatNumber = unwrapFunctionStore(formatNumber); +const $format = unwrapFunctionStore(format); + +console.log( + $formatNumber(1000, 'en-US', { style: 'currency', currency: 'USD' }), +); // $1,000.00 +console.log($format('Hello {name}', { name: 'John' }, 'en-US')); // Hello John +``` diff --git a/src/runtime/configs.ts b/src/runtime/configs.ts index 82cee6f..40d9315 100644 --- a/src/runtime/configs.ts +++ b/src/runtime/configs.ts @@ -1,5 +1,5 @@ import { $locale, getCurrentLocale, getPossibleLocales } from './stores/locale'; -import { hasLocaleQueue } from './includes/loaderQueue'; +import { hasLocaleQueue } from './modules/loaderQueue'; import type { ConfigureOptions, diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 0f18e6b..897db48 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -1,13 +1,13 @@ import { getCurrentLocale, $locale } from './stores/locale'; import { getOptions, init } from './configs'; -import { flush, registerLocaleLoader } from './includes/loaderQueue'; +import { flush, registerLocaleLoader } from './modules/loaderQueue'; import { getLocaleFromHostname, getLocaleFromPathname, getLocaleFromNavigator, getLocaleFromQueryString, getLocaleFromHash, -} from './includes/localeGetters'; +} from './modules/localeGetters'; import { $dictionary, $locales, addMessages } from './stores/dictionary'; import { $isLoading } from './stores/loading'; import { @@ -22,9 +22,10 @@ import { getNumberFormatter, getTimeFormatter, getMessageFormatter, -} from './includes/formatters'; +} from './modules/formatters'; +import { unwrapFunctionStore } from './modules/unwrapFunctionStore'; -import type { MessageObject } from './types'; +import type { MessageObject } from './types/index'; // defineMessages allow us to define and extract dynamic message ids export function defineMessages(i: Record) { @@ -60,6 +61,7 @@ export { getTimeFormatter, getMessageFormatter, // utils + unwrapFunctionStore, getLocaleFromHostname, getLocaleFromPathname, getLocaleFromNavigator, diff --git a/src/runtime/includes/formatters.ts b/src/runtime/modules/formatters.ts similarity index 100% rename from src/runtime/includes/formatters.ts rename to src/runtime/modules/formatters.ts diff --git a/src/runtime/includes/loaderQueue.ts b/src/runtime/modules/loaderQueue.ts similarity index 100% rename from src/runtime/includes/loaderQueue.ts rename to src/runtime/modules/loaderQueue.ts diff --git a/src/runtime/includes/localeGetters.ts b/src/runtime/modules/localeGetters.ts similarity index 100% rename from src/runtime/includes/localeGetters.ts rename to src/runtime/modules/localeGetters.ts diff --git a/src/runtime/includes/lookup.ts b/src/runtime/modules/lookup.ts similarity index 100% rename from src/runtime/includes/lookup.ts rename to src/runtime/modules/lookup.ts diff --git a/src/runtime/includes/memoize.ts b/src/runtime/modules/memoize.ts similarity index 100% rename from src/runtime/includes/memoize.ts rename to src/runtime/modules/memoize.ts diff --git a/src/runtime/modules/unwrapFunctionStore.ts b/src/runtime/modules/unwrapFunctionStore.ts new file mode 100644 index 0000000..520f50c --- /dev/null +++ b/src/runtime/modules/unwrapFunctionStore.ts @@ -0,0 +1,42 @@ +import type { Readable } from 'svelte/store'; + +type UnwrapStore = T extends Readable ? U : T; + +/** + * Unwraps a function from a store and make it function calleable easily outside of a Svelte component. + * + * It works by creating a subscription to the store and getting local reference to the store value. + * Then when the returned function is called, it will execute the function by using the local reference. + * + * The returned function has a 'freeze' method that will stop listening to the store. + * + * @example + * // some-js-file.js + * import { format } from 'svelte-i18n'; + * + * const $format = unwrapFunctionStore(format); + * + * console.log($format('hello', { name: 'John' })); + * + */ +export function unwrapFunctionStore< + S extends Readable<(...args: any[]) => any>, + Fn extends UnwrapStore, +>( + store: S, +): Fn & { + /** + * Stops listening to the store. + */ + freeze: () => void; +} { + let localReference: Fn; + + const cancel = store.subscribe((value) => (localReference = value as Fn)); + + const fn = (...args: Parameters) => localReference(...args); + + fn.freeze = cancel; + + return fn as Fn & { freeze: () => void }; +} diff --git a/src/runtime/stores/dictionary.ts b/src/runtime/stores/dictionary.ts index a4d9b56..817ac7e 100644 --- a/src/runtime/stores/dictionary.ts +++ b/src/runtime/stores/dictionary.ts @@ -3,7 +3,7 @@ import deepmerge from 'deepmerge'; import { getPossibleLocales } from './locale'; import { delve } from '../../shared/delve'; -import { lookupCache } from '../includes/lookup'; +import { lookupCache } from '../modules/lookup'; import type { LocaleDictionary, LocalesDictionary } from '../types/index'; diff --git a/src/runtime/stores/formatters.ts b/src/runtime/stores/formatters.ts index 551eb7a..5f9fb6e 100644 --- a/src/runtime/stores/formatters.ts +++ b/src/runtime/stores/formatters.ts @@ -1,12 +1,12 @@ import { derived } from 'svelte/store'; -import { lookup } from '../includes/lookup'; +import { lookup } from '../modules/lookup'; import { getMessageFormatter, getTimeFormatter, getDateFormatter, getNumberFormatter, -} from '../includes/formatters'; +} from '../modules/formatters'; import { getOptions } from '../configs'; import { $dictionary } from './dictionary'; import { getCurrentLocale, $locale } from './locale'; diff --git a/src/runtime/stores/locale.ts b/src/runtime/stores/locale.ts index d843aa7..79b603d 100644 --- a/src/runtime/stores/locale.ts +++ b/src/runtime/stores/locale.ts @@ -1,6 +1,6 @@ import { writable } from 'svelte/store'; -import { flush, hasLocaleQueue } from '../includes/loaderQueue'; +import { flush, hasLocaleQueue } from '../modules/loaderQueue'; import { getOptions } from '../configs'; import { getClosestAvailableLocale } from './dictionary'; import { $isLoading } from './loading'; diff --git a/test/runtime/index.test.ts b/test/runtime/index.test.ts index ea42649..d34b03c 100644 --- a/test/runtime/index.test.ts +++ b/test/runtime/index.test.ts @@ -1,6 +1,6 @@ import { defineMessages, waitLocale, register, init } from '../../src/runtime'; import { $locale } from '../../src/runtime/stores/locale'; -import { hasLocaleQueue } from '../../src/runtime/includes/loaderQueue'; +import { hasLocaleQueue } from '../../src/runtime/modules/loaderQueue'; import { getLocaleDictionary, $dictionary, diff --git a/test/runtime/includes/formatters.test.ts b/test/runtime/modules/formatters.test.ts similarity index 100% rename from test/runtime/includes/formatters.test.ts rename to test/runtime/modules/formatters.test.ts diff --git a/test/runtime/includes/loaderQueue.test.ts b/test/runtime/modules/loaderQueue.test.ts similarity index 97% rename from test/runtime/includes/loaderQueue.test.ts rename to test/runtime/modules/loaderQueue.test.ts index 0b0f03f..f2a8ebd 100644 --- a/test/runtime/includes/loaderQueue.test.ts +++ b/test/runtime/modules/loaderQueue.test.ts @@ -3,7 +3,7 @@ import { flush, registerLocaleLoader, resetQueues, -} from '../../../src/runtime/includes/loaderQueue'; +} from '../../../src/runtime/modules/loaderQueue'; import { getMessageFromDictionary } from '../../../src/runtime/stores/dictionary'; beforeEach(() => { diff --git a/test/runtime/includes/lookup.test.ts b/test/runtime/modules/lookup.test.ts similarity index 96% rename from test/runtime/includes/lookup.test.ts rename to test/runtime/modules/lookup.test.ts index 14c9c5a..e8dbe2f 100644 --- a/test/runtime/includes/lookup.test.ts +++ b/test/runtime/modules/lookup.test.ts @@ -1,5 +1,5 @@ import { init } from '../../../src/runtime/configs'; -import { lookup, lookupCache } from '../../../src/runtime/includes/lookup'; +import { lookup, lookupCache } from '../../../src/runtime/modules/lookup'; import { $dictionary, addMessages, diff --git a/test/runtime/modules/unwrapFunctionStore.test.ts b/test/runtime/modules/unwrapFunctionStore.test.ts new file mode 100644 index 0000000..5fc7965 --- /dev/null +++ b/test/runtime/modules/unwrapFunctionStore.test.ts @@ -0,0 +1,46 @@ +import { writable, derived, get } from 'svelte/store'; + +import { unwrapFunctionStore } from '../../../src/runtime'; + +import type { Readable } from 'svelte/store'; + +test('unwraps the function from a store', () => { + const store = writable(0); + const functionStore = derived(store, ($store) => () => $store) as Readable< + () => number + >; + + const unwrapped = unwrapFunctionStore(functionStore); + + expect(get(functionStore)()).toBe(0); + expect(unwrapped()).toBe(0); + + store.set(1); + + expect(get(functionStore)()).toBe(1); + expect(unwrapped()).toBe(1); +}); + +test('stops listening to store changes when .freeze is called', () => { + const store = writable(0); + const functionStore = derived(store, ($store) => () => $store) as Readable< + () => number + >; + + const unwrapped = unwrapFunctionStore(functionStore); + + expect(get(functionStore)()).toBe(0); + expect(unwrapped()).toBe(0); + + store.set(1); + + expect(get(functionStore)()).toBe(1); + expect(unwrapped()).toBe(1); + + unwrapped.freeze(); + + store.set(2); + + expect(get(functionStore)()).toBe(2); + expect(unwrapped()).toBe(1); +}); diff --git a/test/runtime/includes/utils.test.ts b/test/runtime/modules/utils.test.ts similarity index 96% rename from test/runtime/includes/utils.test.ts rename to test/runtime/modules/utils.test.ts index 26a2f06..d3b3dad 100644 --- a/test/runtime/includes/utils.test.ts +++ b/test/runtime/modules/utils.test.ts @@ -4,7 +4,7 @@ import { getLocaleFromNavigator, getLocaleFromPathname, getLocaleFromHostname, -} from '../../../src/runtime/includes/localeGetters'; +} from '../../../src/runtime/modules/localeGetters'; describe('getting client locale', () => { beforeEach(() => { diff --git a/test/runtime/stores/locale.test.ts b/test/runtime/stores/locale.test.ts index 996243f..af3fbee 100644 --- a/test/runtime/stores/locale.test.ts +++ b/test/runtime/stores/locale.test.ts @@ -1,6 +1,6 @@ import { get } from 'svelte/store'; -import { lookup } from '../../../src/runtime/includes/lookup'; +import { lookup } from '../../../src/runtime/modules/lookup'; import { getPossibleLocales, getCurrentLocale, @@ -8,7 +8,7 @@ import { } from '../../../src/runtime/stores/locale'; import { getOptions, init } from '../../../src/runtime/configs'; import { register, isLoading } from '../../../src/runtime'; -import { hasLocaleQueue } from '../../../src/runtime/includes/loaderQueue'; +import { hasLocaleQueue } from '../../../src/runtime/modules/loaderQueue'; beforeEach(() => { init({ fallbackLocale: undefined as any });