UNPKG

@o3r/localization

Version:

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

252 lines • 14.8 kB
"use strict"; 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