Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# [3.5.0-alpha.0](https://github.com/kaisermann/svelte-i18n/compare/v3.4.0...v3.5.0-alpha.0) (2022-11-19)


### Features

* support using an array of locales for setting current locale ([280c5b4](https://github.com/kaisermann/svelte-i18n/commit/280c5b43fbc0a786ab8ee9e388db49d30af1a009))



## [3.4.1](https://github.com/kaisermann/svelte-i18n/compare/v3.4.0...v3.4.1) (2022-11-19)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "svelte-i18n",
"version": "3.4.1",
"version": "3.5.0-alpha.0",
"main": "dist/runtime.cjs.js",
"module": "dist/runtime.esm.js",
"types": "dist/runtime.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/runtime/includes/loaderQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export function registerLocaleLoader(locale: string, loader: MessagesLoader) {
// istanbul ignore if
if (getLocaleQueue(locale).has(loader)) return;

// Add an empty dictionary for this locale if it doesn't exist already
if (!hasLocaleDictionary(locale)) {
$dictionary.update((d) => {
d[locale] = {};
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/stores/dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function getMessageFromDictionary(locale: string, id: string) {
}

export function getClosestAvailableLocale(
refLocale: string | null | undefined,
refLocale: string | string[] | null | undefined,
): string | undefined {
if (refLocale == null) return undefined;

Expand Down
2 changes: 2 additions & 0 deletions src/runtime/stores/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const formatMessage: MessageFormatter = (id, options = {}) => {
default: defaultValue,
} = messageObj;

locale; //

if (locale == null) {
throw new Error(
'[svelte-i18n] Cannot format a message without first setting the initial locale.',
Expand Down
104 changes: 65 additions & 39 deletions src/runtime/stores/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { getOptions } from '../configs';
import { getClosestAvailableLocale } from './dictionary';
import { $isLoading } from './loading';

let current: string | null | undefined;
const internalLocale = writable<string | null | undefined>(null);
type LocaleStoreValue = string | null | undefined;
let current: LocaleStoreValue;
const internalLocale = writable<LocaleStoreValue>(null);

function getSubLocales(refLocale: string) {
return refLocale
Expand All @@ -16,70 +17,95 @@ function getSubLocales(refLocale: string) {
}

export function getPossibleLocales(
refLocale: string,
referenceLocales: string | string[],
fallbackLocale = getOptions().fallbackLocale,
): string[] {
const locales = getSubLocales(refLocale);
const allSubLocales = Array.isArray(referenceLocales)
? referenceLocales.flatMap((locale) => getSubLocales(locale))
: getSubLocales(referenceLocales);

if (fallbackLocale) {
return [...new Set([...locales, ...getSubLocales(fallbackLocale)])];
return [...new Set([...allSubLocales, ...getSubLocales(fallbackLocale)])];
}

return locales;
return allSubLocales;
}

export function getCurrentLocale() {
return current ?? undefined;
}

internalLocale.subscribe((newLocale: string | null | undefined) => {
internalLocale.subscribe((newLocale: LocaleStoreValue) => {
current = newLocale ?? undefined;

if (typeof window !== 'undefined' && newLocale != null) {
document.documentElement.setAttribute('lang', newLocale);
}
});

const set = (newLocale: string | null | undefined): void | Promise<void> => {
/**
* Sets the current locale and loads any pending messages
* for the specified locale.
*
* If an array of locales is passed, the first locale available
* in the dictionary will be used.
*
* Note: for a locale to be available, it must have been loaded
* or registered via (`addMessages` or `register`).
*/
const set = (
newLocale: string | string[] | null | undefined,
): Promise<void> => {
const availableLocale = Array.isArray(newLocale)
? /**
* if an array was passed, get the closest available locale
* i.e if the dictionary has 'en', 'de' and 'es' and the user requests
* 'it' and 'es', 'es' will be used
*/
getClosestAvailableLocale(newLocale)
: newLocale;

if (!hasLocaleQueue(availableLocale)) {
internalLocale.set(availableLocale);

return Promise.resolve();
}

const { loadingDelay } = getOptions();

let loadingTimer: number;

// if there's no current locale, we don't wait to set isLoading to true
// because it would break pages when loading the initial locale
if (
newLocale &&
getClosestAvailableLocale(newLocale) &&
hasLocaleQueue(newLocale)
typeof window !== 'undefined' &&
getCurrentLocale() != null &&
loadingDelay
) {
const { loadingDelay } = getOptions();

let loadingTimer: number;

// if there's no current locale, we don't wait to set isLoading to true
// because it would break pages when loading the initial locale
if (
typeof window !== 'undefined' &&
getCurrentLocale() != null &&
loadingDelay
) {
loadingTimer = window.setTimeout(
() => $isLoading.set(true),
loadingDelay,
);
} else {
loadingTimer = window.setTimeout(() => {
$isLoading.set(true);
}

return flush(newLocale as string)
.then(() => {
internalLocale.set(newLocale);
})
.finally(() => {
clearTimeout(loadingTimer);
$isLoading.set(false);
});
}, loadingDelay);
} else {
$isLoading.set(true);
}

return internalLocale.set(newLocale);
return flush(newLocale as string)
.then(() => {
internalLocale.set(availableLocale);
})
.finally(() => {
clearTimeout(loadingTimer);
$isLoading.set(false);
});
};

const $locale = {
...internalLocale,
subscribe: internalLocale.subscribe,
update: (
fn: (value: LocaleStoreValue) => string | string[] | null | undefined,
) => {
return set(fn(getCurrentLocale()));
},
set,
};

Expand Down
30 changes: 20 additions & 10 deletions test/runtime/stores/dictionary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,6 @@ test('checks if a locale dictionary exists', () => {
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')).toBeUndefined();
});

test('lists all locales in the dictionary', () => {
addMessages('en', {});
addMessages('pt', {});
Expand Down Expand Up @@ -137,3 +127,23 @@ describe('getting messages', () => {
expect(getMessageFromDictionary('en', 'foo.potato')).toBeUndefined();
});
});

describe('getClosestAvailableLocale', () => {
it('gets the closest available locale', () => {
addMessages('pt', { field_1: 'name' });
expect(getClosestAvailableLocale('pt-BR')).toBe('pt');
});

it("returns undefined if there's no closest locale available", () => {
addMessages('pt', { field_1: 'name' });
expect(getClosestAvailableLocale('it-IT')).toBeUndefined();
});

it('gets the closest available locale of a list of locales', () => {
addMessages('pt', { field_1: 'name' });
addMessages('es', { field_1: 'name' });

expect(getClosestAvailableLocale(['pt-BR'])).toBe('pt');
expect(getClosestAvailableLocale(['es'])).toBe('es');
});
});
Loading