@vendure/ngx-translate-extract
Version:
Extract strings from projects using ngx-translate
127 lines (126 loc) • 7.24 kB
JavaScript
import path from 'node:path';
import fs from 'node:fs';
import { findConfigFile, parseConfigFileTextToJson } from 'typescript';
import { TranslationCollection } from '../utils/translation.collection.js';
import { findClassDeclarations, findClassPropertiesByType, findPropertyCallExpressions, findMethodCallExpressions, getStringsFromExpression, findMethodParameterByType, findConstructorDeclaration, getSuperClassName, getImportPath, findFunctionExpressions, findVariableNameByInjectType, findInlineInjectCallExpressions, getAST, getNamedImport } from '../utils/ast-helpers.js';
const TRANSLATE_SERVICE_TYPE_REFERENCE = 'TranslateService';
const TRANSLATE_SERVICE_METHOD_NAMES = ['get', 'instant', 'stream'];
export class ServiceParser {
static propertyMap = new Map();
extract(source, filePath) {
const sourceFile = getAST(source, filePath);
const classDeclarations = findClassDeclarations(sourceFile);
const functionDeclarations = findFunctionExpressions(sourceFile);
if (!classDeclarations && !functionDeclarations) {
return null;
}
let collection = new TranslationCollection();
const translateServiceCallExpressions = [];
functionDeclarations.forEach((fnDeclaration) => {
const translateServiceVariableName = findVariableNameByInjectType(fnDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE);
const callExpressions = findMethodCallExpressions(sourceFile, translateServiceVariableName, TRANSLATE_SERVICE_METHOD_NAMES);
const inlineInjectCallExpressions = findInlineInjectCallExpressions(sourceFile, TRANSLATE_SERVICE_TYPE_REFERENCE, TRANSLATE_SERVICE_METHOD_NAMES);
translateServiceCallExpressions.push(...callExpressions, ...inlineInjectCallExpressions);
});
classDeclarations.forEach((classDeclaration) => {
const callExpressions = [
...this.findConstructorParamCallExpressions(classDeclaration),
...this.findPropertyCallExpressions(classDeclaration, sourceFile)
];
translateServiceCallExpressions.push(...callExpressions);
});
translateServiceCallExpressions
.filter((callExpression) => !!callExpression.arguments?.[0])
.forEach((callExpression) => {
const [firstArg] = callExpression.arguments;
const strings = getStringsFromExpression(firstArg);
collection = collection.addKeys(strings, filePath);
});
return collection;
}
findConstructorParamCallExpressions(classDeclaration) {
const constructorDeclaration = findConstructorDeclaration(classDeclaration);
if (!constructorDeclaration) {
return [];
}
const paramName = findMethodParameterByType(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE);
const methodCallExpressions = findMethodCallExpressions(constructorDeclaration, paramName, TRANSLATE_SERVICE_METHOD_NAMES);
const inlineInjectCallExpressions = findInlineInjectCallExpressions(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE, TRANSLATE_SERVICE_METHOD_NAMES);
const translateServiceLocalVariableName = findVariableNameByInjectType(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE);
const localVariableCallExpressions = translateServiceLocalVariableName
? findMethodCallExpressions(constructorDeclaration, translateServiceLocalVariableName, TRANSLATE_SERVICE_METHOD_NAMES)
: [];
return [...methodCallExpressions, ...localVariableCallExpressions, ...inlineInjectCallExpressions];
}
findPropertyCallExpressions(classDeclaration, sourceFile) {
const propNames = findClassPropertiesByType(classDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE);
if (propNames.length === 0) {
propNames.push(...this.findParentClassProperties(classDeclaration, sourceFile));
}
return propNames.flatMap((name) => findPropertyCallExpressions(classDeclaration, name, TRANSLATE_SERVICE_METHOD_NAMES));
}
findParentClassProperties(classDeclaration, ast) {
const superClassNameOrAlias = getSuperClassName(classDeclaration);
if (!superClassNameOrAlias) {
return [];
}
const importPath = getImportPath(ast, superClassNameOrAlias);
if (!importPath) {
return [];
}
const superClassName = getNamedImport(ast, superClassNameOrAlias, importPath);
const currDir = path.join(path.dirname(ast.fileName), '/');
const cacheKey = `${currDir}|${importPath}`;
if (ServiceParser.propertyMap.has(cacheKey)) {
return ServiceParser.propertyMap.get(cacheKey);
}
let superClassPath;
if (importPath.startsWith('.')) {
superClassPath = path.resolve(currDir, importPath);
}
else if (importPath.startsWith('/')) {
superClassPath = importPath;
}
else {
let baseUrl = currDir;
const tsconfigFilePath = findConfigFile(currDir, fs.existsSync);
if (tsconfigFilePath) {
const tsConfigFile = fs.readFileSync(tsconfigFilePath, { encoding: 'utf8' });
const config = parseConfigFileTextToJson(tsconfigFilePath, tsConfigFile).config;
const compilerOptionsBaseUrl = config.compilerOptions?.baseUrl ?? '';
baseUrl = path.resolve(path.dirname(tsconfigFilePath), compilerOptionsBaseUrl);
}
superClassPath = path.resolve(baseUrl, importPath);
}
const superClassFile = superClassPath + '.ts';
let potentialSuperFiles;
if (fs.existsSync(superClassFile) && fs.lstatSync(superClassFile).isFile()) {
potentialSuperFiles = [superClassFile];
}
else if (fs.existsSync(superClassPath) && fs.lstatSync(superClassPath).isDirectory()) {
potentialSuperFiles = fs
.readdirSync(superClassPath)
.filter((file) => file.endsWith('.ts'))
.map((file) => path.join(superClassPath, file));
}
else {
return [];
}
const allSuperClassPropertyNames = [];
potentialSuperFiles.forEach((file) => {
const superClassFileContent = fs.readFileSync(file, 'utf8');
const superClassAst = getAST(superClassFileContent, file);
const superClassDeclarations = findClassDeclarations(superClassAst, superClassName);
const superClassPropertyNames = superClassDeclarations
.flatMap((superClassDeclaration) => findClassPropertiesByType(superClassDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE));
if (superClassPropertyNames.length > 0) {
ServiceParser.propertyMap.set(cacheKey, superClassPropertyNames);
allSuperClassPropertyNames.push(...superClassPropertyNames);
}
else {
superClassDeclarations.forEach((declaration) => allSuperClassPropertyNames.push(...this.findParentClassProperties(declaration, superClassAst)));
}
});
return allSuperClassPropertyNames;
}
}