export type Locale = { code: string, name: string, }; export interface LocaleResolverFunction { (localeResolver: LocaleResolver): Locale | undefined, } export type LocaleResolverParameters = { fallbackLocale: Locale, availableLocales: Locale[], localeStorage?: Storage, localeStorageKey?: string, resolvers?: LocaleResolverFunction[], }; export class LocaleResolver { private readonly fallbackLocale: Locale; private readonly localeStorageKey: string; private readonly indexedLocales: Map; private readonly localeStorage?: Storage; private readonly resolvers: LocaleResolverFunction[]; constructor(parameters: LocaleResolverParameters) { this.fallbackLocale = parameters.fallbackLocale; this.indexedLocales = new Map( parameters.availableLocales.map((locale: Locale) => [locale.code, locale]), ); this.localeStorage = parameters.localeStorage; this.resolvers = parameters.resolvers ?? []; this.localeStorageKey = parameters.localeStorageKey ?? 'lang'; } resolve(): Locale { for (const resolver of this.resolvers) { const resolvedLocale: Locale | undefined = resolver(this); if (resolvedLocale !== undefined) { return resolvedLocale; } } return this.fallbackLocale; } static tryResolveFromBrowser(localeResolver: LocaleResolver): Locale | undefined { if (typeof navigator === 'undefined') { throw new Error('Trying to resolve locale from browser whereas navigator is undefined'); } for (const language of navigator.languages) { const matchingLocale: Locale | undefined = localeResolver.tryFindMatchingLocale(language.substring(0, 2)); if (matchingLocale) { return matchingLocale; } } return undefined; } static tryResolveFromStorage(localeResolver: LocaleResolver): Locale | undefined { if (localeResolver.localeStorage === undefined) { throw new Error('Trying to resolve locale from storage whereas the \'localeStorage\' parameter is undefined'); } const storageLocaleCode: string | null = localeResolver.localeStorage.getItem(localeResolver.localeStorageKey); return storageLocaleCode ? localeResolver.tryFindMatchingLocale(storageLocaleCode) : undefined; } tryFindMatchingLocale(localeCode: string): Locale | undefined { return this.indexedLocales.get(localeCode); } hasLocaleStorage() { return this.localeStorage !== undefined; } setPreferredLocale(newLocale: Locale) { if (this.localeStorage === undefined) { throw new Error('No locale storage defined while trying to set a preferred locale'); } this.localeStorage.setItem(this.localeStorageKey, newLocale.code); } getAvailableLocales(): Locale[] { return Array.from(this.indexedLocales.values()); } }