UNPKG

@boost/translate

Version:

Package and application level translations made easy.

110 lines (90 loc) 2.74 kB
/* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable promise/prefer-await-to-then */ import type { BackendModule, Resource, ResourceKey, ResourceKeys } from 'i18next'; import { Contract, json, Path, yaml } from '@boost/common'; import type { Blueprint, Schemas } from '@boost/common/optimal'; import { importAbsoluteModule } from '@boost/internal'; import { TranslateError } from './TranslateError'; import type { Format, Locale } from './types'; const EXTS: { [K in Format]: string[] } = { cjs: ['cjs'], js: ['js'], json: ['json', 'json5'], mjs: ['mjs'], yaml: ['yaml', 'yml'], }; export interface FileBackendOptions { format?: Format; paths?: Path[]; } export class FileBackend extends Contract<FileBackendOptions> implements BackendModule { fileCache = new Map<Path, ResourceKey>(); type = 'backend' as const; resources: ResourceKeys = {}; init(services: unknown, options: Partial<FileBackendOptions>) { this.configure(options); // Validate resource paths are directories this.options.paths.forEach((path) => { if (path.exists() && !path.isDirectory()) { throw new TranslateError('RESOURCE_PATH_INVALID', [path.path()]); } }); } blueprint(schemas: Schemas): Blueprint<FileBackendOptions> { const { array, instance, string } = schemas; return { format: string('yaml').oneOf<Format>(['cjs', 'js', 'mjs', 'json', 'yaml']), paths: array().of(instance().of(Path, { loose: true }).notNullable()), }; } // istanbul ignore next create() { // We don't need this but is required by the interface } read( locale: Locale, namespace: string, handler: (error: Error | null, resources: Resource) => void, ): void { const { format, paths } = this.options; const resources: ResourceKey = {}; Promise.all( paths.map(async (path) => { await Promise.all( EXTS[format].map(async (ext) => { const resPath = path.append(locale, `${namespace}.${ext}`); if (!resPath.exists()) { return; } const isCached = this.fileCache.has(resPath); if (!isCached) { let content: ResourceKey; switch (ext) { case 'yml': case 'yaml': content = yaml.load(resPath); break; case 'json': case 'json5': content = json.load(resPath); break; default: content = await importAbsoluteModule<ResourceKey>(resPath.path()); break; } this.fileCache.set(resPath, content); } Object.assign(resources, this.fileCache.get(resPath)); }), ); }), ) .then(() => { this.resources = resources; handler(null, resources); }) .catch((error) => { handler(error as Error, {}); }); } }