UNPKG

@jsverse/transloco-keys-manager

Version:

Extract translatable keys from projects that uses Transloco

149 lines 5.54 kB
import { RecursiveAstVisitor, } from '@angular/compiler'; import { addKey } from '../add-key.js'; import { resolveAliasAndKey } from '../utils/resolvers.utils.js'; import { isBoundAttribute, isBoundText, isElement, isInterpolation, isCall, isNgTemplateTag, isSupportedNode, isTemplate, parseTemplate, isBlockNode, resolveBlockChildNodes, resolveKeysFromLiteralMap, } from './utils.js'; import { isConditionalExpression, isLiteralExpression, isLiteralMap } from '@jsverse/angular-utils'; export function structuralDirectiveExtractor(config) { const ast = parseTemplate(config); traverse(ast.nodes, [], config); } export function traverse(nodes, containers, config) { for (const node of nodes) { if (isBlockNode(node)) { traverse(resolveBlockChildNodes(node), containers, config); continue; } let methodUsages = []; if (isBoundText(node)) { const { expressions } = node.value .ast; methodUsages = getMethodUsages(expressions, containers); } else if (isSupportedNode(node, [isTemplate, isElement])) { if (isTranslocoTemplate(node)) { for (const metadata of resolveMetadata(node)) { containers.push(metadata); } } let attrsSource = node.inputs; if (isTemplate(node)) { attrsSource = node.inputs.concat(node.templateAttrs.filter(isBoundAttribute)); } const boundAttrs = attrsSource .map((input) => { const { ast } = input.value; return isInterpolation(ast) ? ast.expressions : ast; }) .flat(); methodUsages = getMethodUsages(boundAttrs, containers); traverse(node.children, containers, config); } addKeysFromAst(methodUsages, config); } } class MethodCallUnwrapper extends RecursiveAstVisitor { expressions = []; visitCall(method, context) { this.expressions.push(method); super.visitCall(method, context); } } /** * Extract method calls from an AST. */ function unwrapMethodCalls(exp) { const unwrapper = new MethodCallUnwrapper(); unwrapper.visit(exp); return unwrapper.expressions; } function getMethodUsages(expressions, containers) { return expressions .flatMap(unwrapMethodCalls) .filter((exp) => isTranslocoMethod(exp, containers)) .map((exp) => { const [keyNode, paramsNode] = exp.args; return { keyNode, params: isLiteralMap(paramsNode) ? resolveKeysFromLiteralMap(paramsNode) : [], ...containers.find(({ name, spanOffset: { start, end } }) => { const inRange = exp.sourceSpan.end < end && exp.sourceSpan.start > start; return exp.receiver.name === name && inRange; }), }; }); } function isTranslocoAttr(attr) { return attr.name === 'transloco'; } function isPrefixAttr(attr) { return attr.name === 'translocoPrefix' || attr.name === 'translocoRead'; } function isTranslocoTemplate(node) { return (isTemplate(node) && (node.templateAttrs.some(isTranslocoAttr) || (isNgTemplateTag(node) && node.attributes.some(isTranslocoAttr)))); } function isTranslocoMethod(exp, containers) { return (isCall(exp) && containers.some(({ name }) => name === exp.receiver.name)); } function resolveMetadata(node) { /* * An ngTemplate element might have more than once implicit variables, we need to capture all of them. * */ let metadata; if (isNgTemplateTag(node)) { const implicitVars = node.variables.filter((attr) => !attr.value); let read = node.attributes.find(isPrefixAttr)?.value; if (!read) { const ast = node.inputs.find(isPrefixAttr)?.value?.ast; if (isLiteralExpression(ast)) { read = ast.value; } } metadata = implicitVars.map(({ name }) => ({ name, read })); } else { const { name } = node.variables.find((variable) => variable.value === '$implicit'); const read = node.templateAttrs.find(isPrefixAttr)?.value; metadata = isLiteralExpression(read?.ast) ? [{ name, read: read.ast.value }] : [{ name }]; } return metadata.map((metadata) => { const sourceSpan = node.sourceSpan; return { ...metadata, spanOffset: { start: sourceSpan.start.offset, end: sourceSpan.end.offset, }, }; }); } function addKeysFromAst(expressions, config) { for (const { keyNode, read, params } of expressions) { if (isConditionalExpression(keyNode)) { addKeysFromAst([keyNode.trueExp, keyNode.falseExp].map((kn) => { return { keyNode: kn, read, params, }; }), config); } else if (isLiteralExpression(keyNode) && keyNode.value) { let value = read ? `${read}.${keyNode.value}` : keyNode.value; const [key, scopeAlias] = resolveAliasAndKey(value, config.scopes); addKey({ ...config, params, keyWithoutScope: key, scopeAlias, }); } } } //# sourceMappingURL=structural-directive.extractor.js.map