mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-11-16 18:10:43 +01:00
fix: 🐛 support more specific fallback locale (i.e en-GB vs en)
✅ Closes: #137
This commit is contained in:
parent
d082d0e25d
commit
5db1dbc3a4
@ -4,7 +4,7 @@ import {
|
||||
$dictionary,
|
||||
addMessages,
|
||||
} from '../stores/dictionary';
|
||||
import { getRelatedLocalesOf } from '../stores/locale';
|
||||
import { getPossibleLocales } from '../stores/locale';
|
||||
|
||||
type Queue = Set<MessagesLoader>;
|
||||
const queue: Record<string, Queue> = {};
|
||||
@ -32,8 +32,7 @@ function getLocaleQueue(locale: string) {
|
||||
}
|
||||
|
||||
function getLocalesQueues(locale: string) {
|
||||
return getRelatedLocalesOf(locale)
|
||||
.reverse()
|
||||
return getPossibleLocales(locale)
|
||||
.map<[string, MessagesLoader[]]>((localeItem) => {
|
||||
const localeQueue = getLocaleQueue(localeItem);
|
||||
|
||||
@ -43,9 +42,9 @@ function getLocalesQueues(locale: string) {
|
||||
}
|
||||
|
||||
export function hasLocaleQueue(locale: string) {
|
||||
return getRelatedLocalesOf(locale)
|
||||
.reverse()
|
||||
.some((localeQueue) => getLocaleQueue(localeQueue)?.size);
|
||||
return getPossibleLocales(locale).some(
|
||||
(localeQueue) => getLocaleQueue(localeQueue)?.size,
|
||||
);
|
||||
}
|
||||
|
||||
function loadLocaleQueue(locale: string, localeQueue: MessagesLoader[]) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getMessageFromDictionary } from '../stores/dictionary';
|
||||
import { getFallbackOf } from '../stores/locale';
|
||||
import { getPossibleLocales } from '../stores/locale';
|
||||
|
||||
export const lookupCache: {
|
||||
[locale: string]: {
|
||||
@ -15,25 +15,25 @@ const addToCache = (path: string, locale: string, message: string) => {
|
||||
return message;
|
||||
};
|
||||
|
||||
const searchForMessage = (path: string, locale: string): any => {
|
||||
if (locale == null) return undefined;
|
||||
export const lookup = (path: string, refLocale: string) => {
|
||||
if (refLocale == null) return undefined;
|
||||
|
||||
const message = getMessageFromDictionary(locale, path);
|
||||
|
||||
if (message) return message;
|
||||
|
||||
return searchForMessage(path, getFallbackOf(locale));
|
||||
};
|
||||
|
||||
export const lookup = (path: string, locale: string) => {
|
||||
if (locale in lookupCache && path in lookupCache[locale]) {
|
||||
return lookupCache[locale][path];
|
||||
if (refLocale in lookupCache && path in lookupCache[refLocale]) {
|
||||
return lookupCache[refLocale][path];
|
||||
}
|
||||
|
||||
const message = searchForMessage(path, locale);
|
||||
const locales = getPossibleLocales(refLocale);
|
||||
|
||||
if (message) {
|
||||
return addToCache(path, locale, message);
|
||||
for (let i = 0; i < locales.length; i++) {
|
||||
const locale = locales[i];
|
||||
const message = getMessageFromDictionary(locale, path);
|
||||
|
||||
if (message) {
|
||||
// Used the requested locale as the cache key
|
||||
// Ex: { en: { title: "Title" }}
|
||||
// lookup('title', 'en-GB') should cache with 'en-GB' instead of 'en'
|
||||
return addToCache(path, refLocale, message);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -2,9 +2,10 @@ import { writable, derived } from 'svelte/store';
|
||||
import deepmerge from 'deepmerge';
|
||||
|
||||
import type { LocaleDictionary, LocalesDictionary } from '../types/index';
|
||||
import { getFallbackOf } from './locale';
|
||||
import { getPossibleLocales } from './locale';
|
||||
import { delve } from '../../shared/delve';
|
||||
import { lookupCache } from '../includes/lookup';
|
||||
import { locales } from '..';
|
||||
|
||||
let dictionary: LocalesDictionary;
|
||||
const $dictionary = writable<LocalesDictionary>({});
|
||||
@ -33,10 +34,20 @@ export function getMessageFromDictionary(locale: string, id: string) {
|
||||
return match;
|
||||
}
|
||||
|
||||
export function getClosestAvailableLocale(locale: string): string | null {
|
||||
if (locale == null || hasLocaleDictionary(locale)) return locale;
|
||||
export function getClosestAvailableLocale(refLocale: string): string | null {
|
||||
if (refLocale == null) return undefined;
|
||||
|
||||
return getClosestAvailableLocale(getFallbackOf(locale));
|
||||
const relatedLocales = getPossibleLocales(refLocale);
|
||||
|
||||
for (let i = 0; i < relatedLocales.length; i++) {
|
||||
const locale = relatedLocales[i];
|
||||
|
||||
if (hasLocaleDictionary(locale)) {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function addMessages(locale: string, ...partials: LocaleDictionary[]) {
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
} from '../includes/formatters';
|
||||
import { getOptions } from '../configs';
|
||||
import { $dictionary } from './dictionary';
|
||||
import { getCurrentLocale, getRelatedLocalesOf, $locale } from './locale';
|
||||
import { getCurrentLocale, getPossibleLocales, $locale } from './locale';
|
||||
|
||||
const formatMessage: MessageFormatter = (id, options = {}) => {
|
||||
if (typeof id === 'object') {
|
||||
@ -44,7 +44,7 @@ const formatMessage: MessageFormatter = (id, options = {}) => {
|
||||
if (getOptions().warnOnMissingMessages) {
|
||||
// istanbul ignore next
|
||||
console.warn(
|
||||
`[svelte-i18n] The message "${id}" was not found in "${getRelatedLocalesOf(
|
||||
`[svelte-i18n] The message "${id}" was not found in "${getPossibleLocales(
|
||||
locale,
|
||||
).join('", "')}".${
|
||||
hasLocaleQueue(getCurrentLocale())
|
||||
|
@ -8,41 +8,33 @@ import { $isLoading } from './loading';
|
||||
let current: string;
|
||||
const $locale = writable(null);
|
||||
|
||||
export function isFallbackLocaleOf(localeA: string, localeB: string) {
|
||||
export function isFallbackLocale(localeA: string, localeB: string) {
|
||||
return localeB.indexOf(localeA) === 0 && localeA !== localeB;
|
||||
}
|
||||
|
||||
export function isRelatedLocale(localeA: string, localeB: string) {
|
||||
return (
|
||||
localeA === localeB ||
|
||||
isFallbackLocaleOf(localeA, localeB) ||
|
||||
isFallbackLocaleOf(localeB, localeA)
|
||||
isFallbackLocale(localeA, localeB) ||
|
||||
isFallbackLocale(localeB, localeA)
|
||||
);
|
||||
}
|
||||
|
||||
export function getFallbackOf(locale: string) {
|
||||
const index = locale.lastIndexOf('-');
|
||||
|
||||
if (index > 0) return locale.slice(0, index);
|
||||
|
||||
const { fallbackLocale } = getOptions();
|
||||
|
||||
if (fallbackLocale && !isRelatedLocale(locale, fallbackLocale)) {
|
||||
return fallbackLocale;
|
||||
}
|
||||
|
||||
return null;
|
||||
function getSubLocales(refLocale: string) {
|
||||
return refLocale
|
||||
.split('-')
|
||||
.map((_, i, arr) => arr.slice(0, i + 1).join('-'))
|
||||
.reverse();
|
||||
}
|
||||
|
||||
export function getRelatedLocalesOf(locale: string): string[] {
|
||||
const locales = locale
|
||||
.split('-')
|
||||
.map((_, i, arr) => arr.slice(0, i + 1).join('-'));
|
||||
export function getPossibleLocales(
|
||||
refLocale: string,
|
||||
fallbackLocale = getOptions().fallbackLocale,
|
||||
): string[] {
|
||||
const locales = getSubLocales(refLocale);
|
||||
|
||||
const { fallbackLocale } = getOptions();
|
||||
|
||||
if (fallbackLocale && !isRelatedLocale(locale, fallbackLocale)) {
|
||||
return locales.concat(getRelatedLocalesOf(fallbackLocale));
|
||||
if (fallbackLocale) {
|
||||
return [...new Set([...locales, ...getSubLocales(fallbackLocale)])];
|
||||
}
|
||||
|
||||
return locales;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { init } from '../../../src/runtime/configs';
|
||||
import { lookup, lookupCache } from '../../../src/runtime/includes/lookup';
|
||||
import {
|
||||
$dictionary,
|
||||
@ -91,3 +92,11 @@ test('clears a locale lookup cache when new messages are added', () => {
|
||||
addMessages('en', { field: 'name2' });
|
||||
expect(lookup('field', 'en')).toBe('name2');
|
||||
});
|
||||
|
||||
test('fallback to fallback locale', () => {
|
||||
init({ fallbackLocale: 'en-GB', initialLocale: 'en-AU' });
|
||||
|
||||
addMessages('en-GB', { field: 'name' });
|
||||
|
||||
expect(lookup('field', 'en-AU')).toBe('name');
|
||||
});
|
||||
|
@ -68,7 +68,7 @@ test('gets the closest available locale', () => {
|
||||
|
||||
test("returns null if there's no closest locale available", () => {
|
||||
addMessages('pt', { field_1: 'name' });
|
||||
expect(getClosestAvailableLocale('it-IT')).toBeNull();
|
||||
expect(getClosestAvailableLocale('it-IT')).toBeUndefined();
|
||||
});
|
||||
|
||||
test('lists all locales in the dictionary', () => {
|
||||
|
@ -2,9 +2,8 @@ import { get } from 'svelte/store';
|
||||
|
||||
import { lookup } from '../../../src/runtime/includes/lookup';
|
||||
import {
|
||||
isFallbackLocaleOf,
|
||||
getFallbackOf,
|
||||
getRelatedLocalesOf,
|
||||
isFallbackLocale,
|
||||
getPossibleLocales,
|
||||
getCurrentLocale,
|
||||
$locale,
|
||||
isRelatedLocale,
|
||||
@ -24,9 +23,9 @@ test('sets and gets the fallback locale', () => {
|
||||
});
|
||||
|
||||
test('checks if a locale is a fallback locale of another locale', () => {
|
||||
expect(isFallbackLocaleOf('en', 'en-US')).toBe(true);
|
||||
expect(isFallbackLocaleOf('en', 'en')).toBe(false);
|
||||
expect(isFallbackLocaleOf('it', 'en-US')).toBe(false);
|
||||
expect(isFallbackLocale('en', 'en-US')).toBe(true);
|
||||
expect(isFallbackLocale('en', 'en')).toBe(false);
|
||||
expect(isFallbackLocale('it', 'en-US')).toBe(false);
|
||||
});
|
||||
|
||||
test('checks if a locale is a related locale of another locale', () => {
|
||||
@ -37,65 +36,59 @@ test('checks if a locale is a related locale of another locale', () => {
|
||||
expect(isRelatedLocale('en-US', 'it')).toBe(false);
|
||||
});
|
||||
|
||||
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')).toBeNull();
|
||||
});
|
||||
|
||||
test('gets the global fallback locale if set', () => {
|
||||
init({ fallbackLocale: 'en' });
|
||||
expect(getFallbackOf('it')).toBe('en');
|
||||
});
|
||||
|
||||
test('should not get the global fallback as the fallback of itself', () => {
|
||||
init({ fallbackLocale: 'en' });
|
||||
expect(getFallbackOf('en')).toBeNull();
|
||||
});
|
||||
|
||||
test('if global fallback locale has a fallback, it should return it', () => {
|
||||
init({ fallbackLocale: 'en-US' });
|
||||
expect(getFallbackOf('en-US')).toBe('en');
|
||||
});
|
||||
|
||||
test('gets all fallback locales of a locale', () => {
|
||||
expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US']);
|
||||
expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US']);
|
||||
expect(getRelatedLocalesOf('az-Cyrl-AZ')).toEqual([
|
||||
'az',
|
||||
'az-Cyrl',
|
||||
test('gets all possible locales from a reference locale', () => {
|
||||
expect(getPossibleLocales('en-US')).toEqual(['en-US', 'en']);
|
||||
expect(getPossibleLocales('az-Cyrl-AZ')).toEqual([
|
||||
'az-Cyrl-AZ',
|
||||
'az-Cyrl',
|
||||
'az',
|
||||
]);
|
||||
});
|
||||
|
||||
test('gets all fallback locales of a locale including the global fallback locale', () => {
|
||||
init({ fallbackLocale: 'pt' });
|
||||
expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt']);
|
||||
expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt']);
|
||||
expect(getRelatedLocalesOf('az-Cyrl-AZ')).toEqual([
|
||||
'az',
|
||||
'az-Cyrl',
|
||||
expect(getPossibleLocales('en-US')).toEqual(['en-US', 'en', 'pt']);
|
||||
expect(getPossibleLocales('az-Cyrl-AZ')).toEqual([
|
||||
'az-Cyrl-AZ',
|
||||
'az-Cyrl',
|
||||
'az',
|
||||
'pt',
|
||||
]);
|
||||
});
|
||||
|
||||
test('remove duplicate fallback locales', () => {
|
||||
expect(getPossibleLocales('en-AU', 'en-GB')).toEqual([
|
||||
'en-AU',
|
||||
'en',
|
||||
'en-GB',
|
||||
]);
|
||||
});
|
||||
|
||||
test('gets all fallback locales of a locale including the global fallback locale and its fallbacks', () => {
|
||||
init({ fallbackLocale: 'pt-BR' });
|
||||
expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR']);
|
||||
expect(getRelatedLocalesOf('en-US')).toEqual(['en', 'en-US', 'pt', 'pt-BR']);
|
||||
expect(getRelatedLocalesOf('az-Cyrl-AZ')).toEqual([
|
||||
'az',
|
||||
'az-Cyrl',
|
||||
'az-Cyrl-AZ',
|
||||
'pt',
|
||||
expect(getPossibleLocales('en-US', 'pt-BR')).toEqual([
|
||||
'en-US',
|
||||
'en',
|
||||
'pt-BR',
|
||||
'pt',
|
||||
]);
|
||||
expect(getPossibleLocales('en-US', 'pt-BR')).toEqual([
|
||||
'en-US',
|
||||
'en',
|
||||
'pt-BR',
|
||||
'pt',
|
||||
]);
|
||||
expect(getPossibleLocales('az-Cyrl-AZ', 'pt-BR')).toEqual([
|
||||
'az-Cyrl-AZ',
|
||||
'az-Cyrl',
|
||||
'az',
|
||||
'pt-BR',
|
||||
'pt',
|
||||
]);
|
||||
});
|
||||
|
||||
test("don't list fallback locale twice", () => {
|
||||
init({ fallbackLocale: 'pt-BR' });
|
||||
expect(getRelatedLocalesOf('pt-BR')).toEqual(['pt', 'pt-BR']);
|
||||
expect(getRelatedLocalesOf('pt')).toEqual(['pt']);
|
||||
expect(getPossibleLocales('pt-BR', 'pt-BR')).toEqual(['pt-BR', 'pt']);
|
||||
expect(getPossibleLocales('pt', 'pt-BR')).toEqual(['pt', 'pt-BR']);
|
||||
});
|
||||
|
||||
test('gets the current locale', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user