UNPKG

@o3r/localization

Version:

This module provides a runtime dynamic language/translation support and debug tools.

178 lines • 9.02 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("node:fs"); const path = require("node:path"); const architect_1 = require("@angular-devkit/architect"); const extractors_1 = require("@o3r/extractors"); const schematics_1 = require("@o3r/schematics"); const chokidar = require("chokidar"); const globby_1 = require("globby"); const localization_generator_1 = require("../helpers/localization.generator"); const validations_1 = require("./validations"); exports.default = (0, architect_1.createBuilder)((0, extractors_1.createBuilderWithMetricsIfInstalled)(async (options, context) => { context.reportRunning(); const localizationExtractor = new localization_generator_1.LocalizationExtractor(path.resolve(context.workspaceRoot, options.tsConfig), context.logger, options); const cache = { libs: {}, locs: {} }; const execute = async (isFirstLoad = true, files) => { /** Maximum number of steps */ const STEP_NUMBER = !isFirstLoad && files ? (files.libs && files.libs.length > 0 ? 1 : 0) + (files.locs && files.locs.length > 0 ? 1 : 0) + 2 : 4; let stepValue = 0; try { // load everything from TSConfig if (isFirstLoad) { context.reportProgress(STEP_NUMBER, stepValue++, 'Load localization'); cache.locs = await localizationExtractor.extractLocalizationFromTsConfig(files && files.extraFiles); context.reportProgress(STEP_NUMBER, stepValue++, 'Load metadata'); cache.libs = files && files.libs ? await localizationExtractor.getMetadataFromLibraries(files.libs) : {}; // Load specific files that have changed } else { if (files && files.locs) { context.reportProgress(STEP_NUMBER, stepValue++, 'reload localization'); const newLocs = await localizationExtractor.getLocalizationMap([...files.locs, ...(files.extraFiles || [])], Object.keys(cache.locs)); Object.keys(newLocs) .forEach((key) => { if (cache.locs[key]) { newLocs[key].isDependency = cache.locs[key].isDependency; } }); cache.locs = { ...cache.locs, ...newLocs }; } if (files && files.libs) { context.reportProgress(STEP_NUMBER, stepValue++, 'reload library metadata'); const newLibs = await localizationExtractor.getMetadataFromFiles(files.libs); cache.libs = { ...cache.libs, ...newLibs }; } } const metadata = localizationExtractor.generateMetadata(cache.locs, { ignoreDuplicateKeys: options.ignoreDuplicateKeys, libraryMetadata: cache.libs, outputFile: options.outputFile, sortKeys: options.sortKeys }); context.reportProgress(STEP_NUMBER, stepValue++, 'Check translations string validation'); const translationsWithIssue = metadata .filter((metadataItem) => !!metadataItem.value && validations_1.validators.reduce((isInvalid, validator) => isInvalid || !validator(metadataItem.value), false)); if (translationsWithIssue.length > 0) { throw new schematics_1.O3rCliError(`The following translations are invalid: ${translationsWithIssue.map((translation) => translation.key).join(', ')}`); } (0, extractors_1.validateJson)(metadata, JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../schemas/localization.metadata.schema.json'), { encoding: 'utf8' })), 'The output of localization metadata is not valid regarding the json schema, please check the details below : \n', options.strictMode); context.reportProgress(STEP_NUMBER, STEP_NUMBER, 'Generating metadata'); // Create output folder if does not exist const localizationMetadataFolder = path.dirname(path.resolve(context.workspaceRoot, options.outputFile)); if (!fs.existsSync(localizationMetadataFolder)) { fs.mkdirSync(localizationMetadataFolder, { recursive: true }); } // Write metadata file try { await fs.promises.mkdir(path.dirname(path.resolve(context.workspaceRoot, options.outputFile)), { recursive: true }); } catch { } await new Promise((resolve, reject) => fs.writeFile(path.resolve(context.workspaceRoot, options.outputFile), options.inline ? JSON.stringify(metadata) : JSON.stringify(metadata, null, 2), (err) => err ? reject(err) : resolve())); } catch (e) { if (e.stack) { context.logger.error(e.stack); } return { success: false, error: e.message || e.toString() }; } context.logger.info(`Localization metadata bundle extracted in ${options.outputFile}.`); return { success: true }; }; /** * Run a translation generation and report the result * @param execution Execution process */ const generateWithReport = async (execution) => { const result = await execution; if (result.success) { context.logger.info('Localization metadata updated'); } else if (result.error) { context.logger.error(result.error); } context.reportStatus('Waiting for changes...'); return result; }; const initialExtraLocs = options.extraFilePatterns.length > 0 ? (0, globby_1.sync)(options.extraFilePatterns, { cwd: context.currentDirectory }) : []; if (options.watch) { let currentProcess = generateWithReport(execute(true, { extraFiles: initialExtraLocs, libs: options.libraries })) .then(() => { currentProcess = undefined; }); await currentProcess; /** SCSS file watcher */ const watcher = chokidar.watch([...Object.keys(cache.locs), ...(options.extraFilePatterns || [])], { ignoreInitial: true }); const metadataWatcher = chokidar.watch(Object.keys(cache.libs), { ignoreInitial: true }); const { include, exclude, cwd } = localizationExtractor.getPatternsFromTsConfig(); const tsWatcher = chokidar.watch(include, { ignoreInitial: true, ignored: exclude, cwd }); tsWatcher .on('add', async (filePath, fileStat) => { if (!fileStat || !fileStat.isFile || !/\.ts$/.test(filePath)) { return; } if (currentProcess) { await currentProcess; } context.logger.info('Refreshed full metadata'); currentProcess = generateWithReport(execute(true, { extraFiles: initialExtraLocs, libs: options.libraries })); await currentProcess; currentProcess = undefined; watcher.add(Object.keys(cache.locs)); }); metadataWatcher .on('all', async (eventName, filePath) => { if (currentProcess) { context.logger.debug(`Ignored action ${eventName} on ${filePath}`); } else { context.logger.debug(`Refreshed for action ${eventName} on ${filePath}`); currentProcess = generateWithReport(execute(false, { libs: [filePath] })); await currentProcess; currentProcess = undefined; } }); watcher .on('all', async (eventName, filePath) => { if (currentProcess) { context.logger.debug(`Ignored action ${eventName} on ${filePath}`); } else { context.logger.debug(`Refreshed for action ${eventName} on ${filePath}`); currentProcess = generateWithReport(execute(false, { locs: [filePath] })); await currentProcess; currentProcess = undefined; } }); context.addTeardown(async () => { await watcher.close(); await metadataWatcher.close(); await tsWatcher.close(); }); // Exit on watcher failure return new Promise((_resolve, reject) => { // TODO remove cast after https://github.com/paulmillr/chokidar/issues/1392 watcher.on('error', (err) => reject(err)); metadataWatcher.on('error', (err) => reject(err)); tsWatcher.on('error', (err) => reject(err)); }); } else { return execute(true, { extraFiles: initialExtraLocs, libs: options.libraries }); } })); //# sourceMappingURL=index.js.map