@o3r/localization
Version:
This module provides a runtime dynamic language/translation support and debug tools.
178 lines • 9.02 kB
JavaScript
;
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