@o3r/localization
Version:
This module provides a runtime dynamic language/translation support and debug tools.
252 lines • 14.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ngAddLocalization = void 0;
exports.ngAddLocalizationFn = ngAddLocalizationFn;
const node_path_1 = require("node:path");
const schematics_1 = require("@angular-devkit/schematics");
const schematics_2 = require("@o3r/schematics");
const ast_utils_1 = require("@schematics/angular/utility/ast-utils");
const change_1 = require("@schematics/angular/utility/change");
const ts = require("typescript");
const localizationProperties = [
'translations'
];
const checkLocalization = (componentPath, tree, baseFileName) => {
const files = [
node_path_1.posix.join((0, node_path_1.dirname)(componentPath), `${baseFileName}.localization.json`),
node_path_1.posix.join((0, node_path_1.dirname)(componentPath), `${baseFileName}.translation.ts`)
];
if (files.some((file) => tree.exists(file))) {
throw new schematics_2.O3rCliError(`Unable to add localization to this component because it already has at least one of these files: ${files.join(', ')}.`);
}
const componentSourceFile = ts.createSourceFile(componentPath, tree.readText(componentPath), ts.ScriptTarget.ES2020, true);
const o3rClassDeclaration = componentSourceFile.statements.find((statement) => ts.isClassDeclaration(statement)
&& (0, schematics_2.isO3rClassComponent)(statement));
if (o3rClassDeclaration.members.some((classElement) => ts.isPropertyDeclaration(classElement)
&& ts.isIdentifier(classElement.name)
&& localizationProperties.includes(classElement.name.escapedText.toString()))) {
throw new schematics_2.O3rCliError(`Unable to add localization to this component because it already has at least one of these properties: ${localizationProperties.join(', ')}.`);
}
};
/**
* Add localization architecture to an existing component
* @param options
*/
function ngAddLocalizationFn(options) {
return async (tree, context) => {
try {
const baseFileName = (0, node_path_1.basename)(options.path, '.component.ts');
const { name, selector, standalone, templateRelativePath } = (0, schematics_2.getO3rComponentInfoOrThrowIfNotFound)(tree, options.path);
checkLocalization(options.path, tree, baseFileName);
const properties = {
...options,
componentTranslation: name.concat('Translation'),
componentSelector: selector,
name: (0, node_path_1.basename)(options.path, '.component.ts')
};
const createLocalizationFilesRule = (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./templates'), [
(0, schematics_1.template)(properties),
(0, schematics_1.renameTemplateFiles)(),
(0, schematics_1.move)((0, node_path_1.dirname)(options.path))
]), schematics_1.MergeStrategy.Overwrite);
const updateComponentRule = (0, schematics_1.chain)([
(0, schematics_2.addImportsRule)(options.path, [
{
from: '@angular/core',
importNames: [
'Input'
]
},
{
from: '@o3r/localization',
importNames: [
'Localization',
...(standalone ? ['LocalizationModule'] : []),
'Translatable'
]
},
{
from: `./${properties.name}.translation`,
importNames: [
'translations',
properties.componentTranslation
]
}
]),
() => {
const componentSourceFile = ts.createSourceFile(options.path, tree.readText(options.path), ts.ScriptTarget.ES2020, true);
const result = ts.transform(componentSourceFile, [
(0, schematics_2.addInterfaceToClassTransformerFactory)(`Translatable<${properties.componentTranslation}>`, schematics_2.isO3rClassComponent),
...(standalone
? [(0, schematics_2.addImportsIntoComponentDecoratorTransformerFactory)(['LocalizationModule'])]
: []),
(ctx) => (rootNode) => {
const { factory } = ctx;
const visit = (node) => {
if (ts.isClassDeclaration(node) && (0, schematics_2.isO3rClassComponent)(node)) {
const propertiesToAdd = (0, schematics_2.generateClassElementsFromString)(`
@Input()
public translations: ${properties.componentTranslation};
`);
const constructorDeclaration = node.members.find((classElement) => ts.isConstructorDeclaration(classElement));
const localizationConstructorBlockStatements = (0, schematics_2.generateBlockStatementsFromString)('this.translations = translations;');
const newContructorDeclaration = constructorDeclaration
? factory.updateConstructorDeclaration(constructorDeclaration, ts.getModifiers(constructorDeclaration) || [], constructorDeclaration.parameters, constructorDeclaration.body
? factory.updateBlock(constructorDeclaration.body, constructorDeclaration.body.statements.concat(localizationConstructorBlockStatements))
: factory.createBlock(localizationConstructorBlockStatements, true))
: factory.createConstructorDeclaration([], [], factory.createBlock(localizationConstructorBlockStatements, true));
const newModifiers = []
.concat(ts.getDecorators(node) || [])
.concat(ts.getModifiers(node) || []);
const newMembers = node.members
.filter((classElement) => !ts.isConstructorDeclaration(classElement))
.concat(propertiesToAdd, newContructorDeclaration)
.sort(schematics_2.sortClassElement);
(0, schematics_2.addCommentsOnClassProperties)(newMembers, {
translations: 'Localization of the component'
});
return factory.updateClassDeclaration(node, newModifiers, node.name, node.typeParameters, node.heritageClauses, newMembers);
}
return ts.visitEachChild(node, visit, ctx);
};
return ts.visitNode(rootNode, visit);
}
]);
const printer = ts.createPrinter({
removeComments: false,
newLine: ts.NewLineKind.LineFeed
});
tree.overwrite(options.path, printer.printFile(result.transformed[0]));
const sf = ts.createSourceFile(options.path, tree.readText(options.path), ts.ScriptTarget.ES2020, true);
// Has to be done at the end because ts.Printer as some issues with Decorators with arguments
const translationsPropDeclaration = sf.statements
.find((statement) => ts.isClassDeclaration(statement) && (0, schematics_2.isO3rClassComponent)(statement))
.members.find((member) => ts.isPropertyDeclaration(member) && member.name.getText() === 'translations');
const translationsPropLastDecorator = [...(ts.getDecorators(translationsPropDeclaration) || [])].at(-1);
tree.commitUpdate(tree
.beginUpdate(options.path)
.insertRight(translationsPropLastDecorator.getEnd(), `\n @Localization('./${baseFileName}.localization.json')`));
return tree;
}
]);
const updateTemplateRule = () => {
const templatePath = templateRelativePath && node_path_1.posix.join((0, node_path_1.dirname)(options.path), templateRelativePath);
if (templatePath && tree.exists(templatePath)) {
tree.commitUpdate(tree
.beginUpdate(templatePath)
.insertLeft(0, '<div>Localization: {{ translations.dummyLoc1 | o3rTranslate }}</div>\n'));
}
return tree;
};
const specFilePath = options.specFilePath || node_path_1.posix.join((0, node_path_1.dirname)(options.path), `${baseFileName}.spec.ts`);
const updateSpecRule = (0, schematics_1.chain)([
(0, schematics_2.addImportsRule)(specFilePath, [
{
from: '@angular/core',
importNames: ['Provider']
},
{
from: '@o3r/localization',
importNames: ['LocalizationService']
},
{
from: '@o3r/testing/localization',
importNames: ['mockTranslationModules']
},
{
from: '@ngx-translate/core',
importNames: ['TranslateCompiler', 'TranslateFakeCompiler']
}
]),
() => {
if (!tree.exists(specFilePath)) {
context.logger.warn(`No update applied on spec file because ${specFilePath} does not exist.`);
return;
}
let specSourceFile = ts.createSourceFile(specFilePath, tree.readText(specFilePath), ts.ScriptTarget.ES2020, true);
const recorder = tree.beginUpdate(specFilePath);
const lastImport = [...specSourceFile.statements].reverse().find((statement) => ts.isImportDeclaration(statement));
const changes = [new change_1.InsertChange(specFilePath, lastImport?.getEnd() || 0, `
const localizationConfiguration = {language: 'en'};
const mockTranslations = {
en: {${options.activateDummy
? `
'${properties.componentSelector}.dummyLoc1': 'Dummy 1'
`
: ''}}
};
const mockTranslationsCompilerProvider: Provider = {
provide: TranslateCompiler,
useClass: TranslateFakeCompiler
};
`)];
(0, change_1.applyToUpdateRecorder)(recorder, changes);
tree.commitUpdate(recorder);
specSourceFile = ts.createSourceFile(specFilePath, tree.readText(specFilePath), ts.ScriptTarget.ES2020, true);
const result = ts.transform(specSourceFile, [
(ctx) => (0, schematics_2.addImportsAndCodeBlockStatementAtSpecInitializationTransformerFactory)([
ctx.factory.createSpreadElement(ctx.factory.createCallExpression(ctx.factory.createIdentifier('mockTranslationModules'), undefined, [
ctx.factory.createIdentifier('localizationConfiguration'),
ctx.factory.createIdentifier('mockTranslations'),
ctx.factory.createIdentifier('mockTranslationsCompilerProvider')
]))
], `
const localizationService = TestBed.inject(LocalizationService);
localizationService.configure();
`)(ctx)
]);
const printer = ts.createPrinter({
removeComments: false,
newLine: ts.NewLineKind.LineFeed
});
const newContent = printer.printFile(result.transformed[0]);
tree.overwrite(specFilePath, newContent);
return tree;
}
]);
const updateModuleRule = () => {
const moduleFilePath = options.path.replace(/component.ts$/, 'module.ts');
const moduleSourceFile = ts.createSourceFile(moduleFilePath, tree.readText(moduleFilePath), ts.ScriptTarget.ES2020, true);
const recorder = tree.beginUpdate(moduleFilePath);
const changes = (0, ast_utils_1.addImportToModule)(moduleSourceFile, moduleFilePath, 'LocalizationModule', '@o3r/localization');
(0, change_1.applyToUpdateRecorder)(recorder, changes);
tree.commitUpdate(recorder);
};
const addDummyKeyRule = (0, schematics_1.schematic)('add-localization-key', {
path: options.path,
skipLinter: options.skipLinter,
key: 'dummyLoc1',
description: 'Dummy 1 description',
value: 'Dummy 1'
});
return (0, schematics_1.chain)([
createLocalizationFilesRule,
updateComponentRule,
updateSpecRule,
standalone ? (0, schematics_1.noop)() : updateModuleRule,
...(options.activateDummy ? [addDummyKeyRule, updateTemplateRule] : []),
options.skipLinter ? (0, schematics_1.noop)() : (0, schematics_2.applyEsLintFix)()
]);
}
catch (e) {
if (e instanceof schematics_2.NoOtterComponent && context.interactive) {
const shouldConvertComponent = await (0, schematics_2.askConfirmationToConvertComponent)();
if (shouldConvertComponent) {
return (0, schematics_1.chain)([
(0, schematics_1.externalSchematic)('@o3r/core', 'convert-component', {
path: options.path
}),
ngAddLocalizationFn(options)
]);
}
}
throw e;
}
};
}
/**
* Add localization architecture to an existing component
* @param options
*/
exports.ngAddLocalization = (0, schematics_2.createOtterSchematic)(ngAddLocalizationFn);
//# sourceMappingURL=index.js.map