import { FluentBundle, FluentResource } from '@fluent/bundle'; import type { LocaleCode, NamespaceCode, ResourceKey, } from '../locale-manager/base-locale-manager'; import type { BundleSource, Resource } from './base'; // hmmm export interface FTLModule { default: string; } type DynamicResource = [LocaleCode, NamespaceCode, Promise | string]; export type BuiltResource = [LocaleCode, NamespaceCode, FTLModule | string]; export interface RawBundleSourceParams { resources: Resource[]; // could control things like... idk caching or whatever here possibly. } export class RawBundleSource implements BundleSource { private readonly bundles: Map = new Map(); private readonly keys: Set = new Set(); // TODO - we should make a bundle source that handles this a little more // elegantly, but for now this should be fine. static async dynamicResources( dynamicResources: DynamicResource[], ): Promise { const resources: Resource[] = []; for (const [locale, namespace, source] of dynamicResources) { if (typeof source === 'string') { resources.push([locale, namespace, source]); } else { const module = await source; if (typeof module.default !== 'string') throw new Error('.ftl text needs to be the default export!'); resources.push([locale, namespace, module.default]); } } return resources; } static normalizeResource(resource: BuiltResource): Resource { if (typeof resource[2] === 'string') { return [resource[0], resource[1], resource[2]]; } else { const module = resource[2]; if (typeof module.default !== 'string') throw new Error('.ftl text needs to be the default export!'); return [resource[0], resource[1], module.default]; } } static builtResources(builtResources: BuiltResource[]): Resource[] { const resources: Resource[] = []; for (const resource of builtResources) { resources.push(this.normalizeResource(resource)); } return resources; } constructor(params: RawBundleSourceParams) { for (const [locale, namespace, source] of params.resources) { const key = this.getKey(locale, namespace); if (this.keys.has(key)) throw new Error(`${key} was provided more than once!`); this.keys.add(key); this.getBundle(locale, namespace).addResource(new FluentResource(source)); } } private getBundle( locale: LocaleCode, namespace: NamespaceCode, ): FluentBundle { if (this.bundles.has(this.getKey(locale, namespace))) { // @ts-expect-error - this is guaranteed to not be undefined. return this.bundles.get(this.getKey(locale, namespace)); } const bundle = new FluentBundle(locale); this.bundles.set(this.getKey(locale, namespace), bundle); return bundle; } private getKey(locale: LocaleCode, namespace: NamespaceCode): ResourceKey { return `${namespace}/${locale}`; } getBundles( locales: LocaleCode[], namespaces: NamespaceCode[], ): FluentBundle[] { const bundles = []; for (const locale of locales) { for (const namespace of namespaces) { const key = this.getKey(locale, namespace); if (!this.keys.has(key)) { throw new Error(`resource ${key} was not provided at init!`); } bundles.push(this.getBundle(locale, namespace)); } } return bundles; } addSource([locale, namespace, source]: Resource): void { const key = this.getKey(locale, namespace); if (this.keys.has(key)) { throw new Error(`${key} was provided more than once!`); } this.keys.add(key); this.getBundle(locale, namespace).addResource(new FluentResource(source)); } }