@jsverse/transloco-keys-manager
Advanced tools
Comparing version 5.0.0 to 5.1.0
@@ -5,4 +5,5 @@ import { BaseParams } from '../types'; | ||
keyWithoutScope: string; | ||
params?: string[]; | ||
} | ||
export declare function addKey({ defaultValue, scopeToKeys, scopeAlias, keyWithoutScope, scopes, }: AddKeysParams): void; | ||
export declare function addKey({ defaultValue, scopeToKeys, scopeAlias, keyWithoutScope, scopes, params, }: AddKeysParams): void; | ||
export {}; |
import { messages } from '../messages.js'; | ||
import { isNil } from '../utils/validators.utils.js'; | ||
export function addKey({ defaultValue, scopeToKeys, scopeAlias, keyWithoutScope, scopes, }) { | ||
export function addKey({ defaultValue, scopeToKeys, scopeAlias, keyWithoutScope, scopes, params = [], }) { | ||
if (!keyWithoutScope) { | ||
@@ -11,2 +11,3 @@ return; | ||
: keyWithoutScope; | ||
const paramsWithInterpolation = params.map((p) => `{{${p}}}`).join(' '); | ||
const keyValue = isNil(defaultValue) | ||
@@ -17,2 +18,3 @@ ? `${messages.missingValue} '${keyWithScope}'` | ||
.replace('{{keyWithoutScope}}', keyWithoutScope) | ||
.replace('{{params}}', paramsWithInterpolation) | ||
.replace('{{scope}}', scopeAlias || ''); | ||
@@ -19,0 +21,0 @@ if (scopePath) { |
import { ASTWithSource, } from '@angular/compiler'; | ||
import { addKey } from '../add-key.js'; | ||
import { resolveAliasAndKey } from '../utils/resolvers.utils.js'; | ||
import { isBlockNode, isBoundAttribute, isConditionalExpression, isElement, isInterpolation, isLiteralExpression, isSupportedNode, isTemplate, isTextAttribute, parseTemplate, resolveBlockChildNodes, } from './utils.js'; | ||
import { isBlockNode, isBoundAttribute, isConditionalExpression, isElement, isInterpolation, isLiteralExpression, isLiteralMap, isSupportedNode, isTemplate, isTextAttribute, parseTemplate, resolveBlockChildNodes, resolveKeysFromLiteralMap, } from './utils.js'; | ||
import { coerceArray } from '../../utils/collection.utils.js'; | ||
export function directiveExtractor(config) { | ||
@@ -18,3 +19,13 @@ const ast = parseTemplate(config); | ||
} | ||
const astTrees = [...node.inputs, ...node.attributes] | ||
const params = node.inputs | ||
.filter(isTranslocoParams) | ||
.map((ast) => { | ||
const value = ast.value; | ||
if (value instanceof ASTWithSource && isLiteralMap(value.ast)) { | ||
return resolveKeysFromLiteralMap(value.ast); | ||
} | ||
return []; | ||
}) | ||
.flat(); | ||
const keys = [...node.inputs, ...node.attributes] | ||
.filter(isTranslocoDirective) | ||
@@ -28,5 +39,7 @@ .map((ast) => { | ||
}) | ||
.flat() | ||
.map(resolveKey) | ||
.flat(); | ||
addKeys(keys, params, config); | ||
traverse(node.children, config); | ||
addKeysFromAst(astTrees, config); | ||
} | ||
@@ -37,18 +50,32 @@ } | ||
} | ||
function addKeysFromAst(expressions, config) { | ||
for (const exp of expressions) { | ||
const isString = typeof exp === 'string'; | ||
if (isConditionalExpression(exp)) { | ||
addKeysFromAst([exp.trueExp, exp.falseExp], config); | ||
function isTranslocoParams(ast) { | ||
return isBoundAttribute(ast) && ast.name === 'translocoParams'; | ||
} | ||
function resolveKey(ast) { | ||
return coerceArray(ast) | ||
.map((expression) => { | ||
if (typeof expression === 'string') { | ||
return expression; | ||
} | ||
else if (isLiteralExpression(exp) || isString) { | ||
const [key, scopeAlias] = resolveAliasAndKey(isString ? exp : exp.value, config.scopes); | ||
addKey({ | ||
...config, | ||
keyWithoutScope: key, | ||
scopeAlias, | ||
}); | ||
else if (isConditionalExpression(expression)) { | ||
return resolveKey([expression.trueExp, expression.falseExp]); | ||
} | ||
else if (isLiteralExpression(expression)) { | ||
return expression.value; | ||
} | ||
}) | ||
.filter(Boolean) | ||
.flat(); | ||
} | ||
function addKeys(keys, params, config) { | ||
for (const rawKey of keys) { | ||
const [key, scopeAlias] = resolveAliasAndKey(rawKey, config.scopes); | ||
addKey({ | ||
...config, | ||
keyWithoutScope: key, | ||
scopeAlias, | ||
params, | ||
}); | ||
} | ||
} | ||
//# sourceMappingURL=directive.extractor.js.map |
import { addKey } from '../add-key.js'; | ||
import { resolveAliasAndKey } from '../utils/resolvers.utils.js'; | ||
import { isBinaryExpression, isBindingPipe, isBoundText, isConditionalExpression, isElement, isInterpolation, isLiteralExpression, isLiteralMap, isCall, isPropertyRead, isTemplate, parseTemplate, isBlockNode, resolveBlockChildNodes, } from './utils.js'; | ||
import { isBinaryExpression, isBindingPipe, isBoundText, isConditionalExpression, isElement, isInterpolation, isLiteralExpression, isLiteralMap, isCall, isPropertyRead, isTemplate, parseTemplate, isBlockNode, resolveBlockChildNodes, resolveKeysFromLiteralMap, } from './utils.js'; | ||
import { notNil } from '../../utils/validators.utils.js'; | ||
export function pipeExtractor(config) { | ||
@@ -23,3 +24,8 @@ const ast = parseTemplate(config); | ||
for (const ast of astTrees) { | ||
addKeysFromAst(getPipeValuesFromAst(ast), config); | ||
const pipes = getTranslocoPipeAst(ast); | ||
const keysWithParams = pipes | ||
.map((p) => resolveKeyAndParam(p)) | ||
.flat() | ||
.filter(notNil); | ||
addKeysFromAst(keysWithParams, config); | ||
} | ||
@@ -36,18 +42,6 @@ } | ||
} | ||
function getPipeValuesFromAst(ast) { | ||
function getTranslocoPipeAst(ast) { | ||
let exp = []; | ||
if (isBindingPipe(ast) && isTranslocoPipe(ast)) { | ||
if (isLiteralExpression(ast.exp)) { | ||
return [ast.exp]; | ||
} | ||
else if (isConditionalExpression(ast.exp)) { | ||
return [ast.exp.trueExp, ast.exp.falseExp]; | ||
} | ||
else { | ||
let pipe = ast; | ||
while (isBindingPipe(pipe.exp)) { | ||
pipe = pipe.exp; | ||
} | ||
return [pipe.exp]; | ||
} | ||
return [ast]; | ||
} | ||
@@ -75,19 +69,45 @@ else if (isBindingPipe(ast)) { | ||
} | ||
return exp.map(getPipeValuesFromAst).flat(); | ||
return exp.map(getTranslocoPipeAst).flat(); | ||
} | ||
function addKeysFromAst(expressions, config) { | ||
for (const exp of expressions) { | ||
if (isConditionalExpression(exp)) { | ||
addKeysFromAst([exp.trueExp, exp.falseExp], config); | ||
function resolveKeyAndParam(pipe, paramsNode) { | ||
const resolvedParams = paramsNode ?? pipe.args[0]; | ||
if (isConditionalExpression(pipe.exp)) { | ||
return [pipe.exp.trueExp, pipe.exp.falseExp] | ||
.filter(isLiteralExpression) | ||
.map((keyNode) => { | ||
return { | ||
keyNode, | ||
paramsNode: resolvedParams, | ||
}; | ||
}); | ||
} | ||
else if (isLiteralExpression(pipe.exp)) { | ||
return { | ||
keyNode: pipe.exp, | ||
paramsNode: resolvedParams, | ||
}; | ||
} | ||
else if (isBindingPipe(pipe.exp)) { | ||
let nestedPipe = pipe; | ||
while (isBindingPipe(nestedPipe.exp)) { | ||
nestedPipe = nestedPipe.exp; | ||
} | ||
else if (isLiteralExpression(exp)) { | ||
const [key, scopeAlias] = resolveAliasAndKey(exp.value, config.scopes); | ||
addKey({ | ||
...config, | ||
keyWithoutScope: key, | ||
scopeAlias, | ||
}); | ||
} | ||
return resolveKeyAndParam(nestedPipe, resolvedParams); | ||
} | ||
return null; | ||
} | ||
function addKeysFromAst(keys, config) { | ||
for (const { keyNode, paramsNode } of keys) { | ||
const [key, scopeAlias] = resolveAliasAndKey(keyNode.value, config.scopes); | ||
const params = isLiteralMap(paramsNode) | ||
? resolveKeysFromLiteralMap(paramsNode) | ||
: []; | ||
addKey({ | ||
...config, | ||
keyWithoutScope: key, | ||
scopeAlias, | ||
params, | ||
}); | ||
} | ||
} | ||
//# sourceMappingURL=pipe.extractor.js.map |
@@ -1,5 +0,4 @@ | ||
import { AST, TmplAstNode } from '@angular/compiler'; | ||
import { TmplAstNode } from '@angular/compiler'; | ||
import { TemplateExtractorConfig } from './types'; | ||
interface ContainerMetaData { | ||
exp?: AST; | ||
name: string; | ||
@@ -6,0 +5,0 @@ read?: string; |
import { RecursiveAstVisitor, } from '@angular/compiler'; | ||
import { addKey } from '../add-key.js'; | ||
import { resolveAliasAndKey } from '../utils/resolvers.utils.js'; | ||
import { isBoundAttribute, isBoundText, isConditionalExpression, isElement, isInterpolation, isLiteralExpression, isCall, isNgTemplateTag, isSupportedNode, isTemplate, parseTemplate, isBlockNode, resolveBlockChildNodes, } from './utils.js'; | ||
import { isBoundAttribute, isBoundText, isConditionalExpression, isElement, isInterpolation, isLiteralExpression, isCall, isNgTemplateTag, isSupportedNode, isTemplate, parseTemplate, isBlockNode, resolveBlockChildNodes, isLiteralMap, resolveKeysFromLiteralMap, } from './utils.js'; | ||
export function structuralDirectiveExtractor(config) { | ||
@@ -63,4 +63,8 @@ const ast = parseTemplate(config); | ||
.map((exp) => { | ||
const [keyNode, paramsNode] = exp.args; | ||
return { | ||
exp: exp.args[0], | ||
keyNode, | ||
params: isLiteralMap(paramsNode) | ||
? resolveKeysFromLiteralMap(paramsNode) | ||
: [], | ||
...containers.find(({ name, spanOffset: { start, end } }) => { | ||
@@ -123,13 +127,18 @@ const inRange = exp.sourceSpan.end < end && exp.sourceSpan.start > start; | ||
function addKeysFromAst(expressions, config) { | ||
for (const { exp, read } of expressions) { | ||
if (isConditionalExpression(exp)) { | ||
for (const conditionValue of [exp.trueExp, exp.falseExp]) { | ||
addKeysFromAst([{ exp: conditionValue, read }], 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(exp) && exp.value) { | ||
let value = read ? `${read}.${exp.value}` : exp.value; | ||
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, | ||
@@ -136,0 +145,0 @@ scopeAlias, |
@@ -30,2 +30,3 @@ import { Binary, BindingPipe, Conditional, Interpolation, LiteralMap, LiteralPrimitive, Call, ParseTemplateOptions, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstTemplate, TmplAstTextAttribute, TmplAstNode, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIfBlockBranch, TmplAstSwitchBlockCase, TmplAstIfBlock, TmplAstSwitchBlock } from '@angular/compiler'; | ||
export declare function resolveBlockChildNodes(node: BlockNode): TmplAstNode[]; | ||
export declare function resolveKeysFromLiteralMap(node: LiteralMap): string[]; | ||
export {}; |
@@ -98,2 +98,17 @@ import { Binary, BindingPipe, Conditional, Interpolation, LiteralMap, LiteralPrimitive, Call, parseTemplate as ngParseTemplate, PropertyRead, TmplAstBoundAttribute, TmplAstBoundText, TmplAstElement, TmplAstTemplate, TmplAstTextAttribute, TmplAstDeferredBlock, TmplAstDeferredBlockError, TmplAstDeferredBlockLoading, TmplAstDeferredBlockPlaceholder, TmplAstForLoopBlock, TmplAstForLoopBlockEmpty, TmplAstIfBlockBranch, TmplAstSwitchBlockCase, TmplAstIfBlock, TmplAstSwitchBlock, } from '@angular/compiler'; | ||
} | ||
export function resolveKeysFromLiteralMap(node) { | ||
let keys = []; | ||
for (let i = 0; i < node.values.length; i++) { | ||
const { key } = node.keys[i]; | ||
const value = node.values[i]; | ||
if (isLiteralMap(value)) { | ||
const prefixedKeys = resolveKeysFromLiteralMap(value).map((k) => `${key}.${k}`); | ||
keys = keys.concat(prefixedKeys); | ||
} | ||
else { | ||
keys.push(key); | ||
} | ||
} | ||
return keys; | ||
} | ||
//# sourceMappingURL=utils.js.map |
import ts from 'typescript'; | ||
import { flatten } from 'flat'; | ||
export function buildKeysFromASTNodes(nodes, allowedMethods = ['translate', 'selectTranslate']) { | ||
const result = []; | ||
for (let node of nodes) { | ||
if (ts.isCallExpression(node.parent)) { | ||
const method = node.parent.expression; | ||
let methodName = ''; | ||
if (ts.isIdentifier(method)) { | ||
methodName = method.text; | ||
} | ||
else if (ts.isPropertyAccessExpression(method)) { | ||
methodName = method.name.text; | ||
} | ||
if (!allowedMethods.includes(methodName)) { | ||
continue; | ||
} | ||
const [keyNode, _, langNode] = node.parent.arguments; | ||
let lang = isStringNode(langNode) ? langNode.text : ''; | ||
let keys = []; | ||
if (isStringNode(keyNode)) { | ||
keys = [keyNode.text]; | ||
} | ||
else if (ts.isArrayLiteralExpression(keyNode)) { | ||
keys = keyNode.elements.filter(isStringNode).map((node) => node.text); | ||
} | ||
for (const key of keys) { | ||
result.push({ key, lang }); | ||
} | ||
if (!ts.isCallExpression(node.parent)) | ||
continue; | ||
const method = node.parent.expression; | ||
let methodName = ''; | ||
if (ts.isIdentifier(method)) { | ||
methodName = method.text; | ||
} | ||
else if (ts.isPropertyAccessExpression(method)) { | ||
methodName = method.name.text; | ||
} | ||
if (!allowedMethods.includes(methodName)) { | ||
continue; | ||
} | ||
const [keyNode, paramsNode, langNode] = node.parent.arguments; | ||
let lang = isStringNode(langNode) ? langNode.text : ''; | ||
let keys = []; | ||
const params = paramsNode && ts.isObjectLiteralExpression(paramsNode) | ||
? resolveParams(paramsNode) | ||
: []; | ||
if (isStringNode(keyNode)) { | ||
keys = [keyNode.text]; | ||
} | ||
else if (ts.isArrayLiteralExpression(keyNode)) { | ||
keys = keyNode.elements.filter(isStringNode).map((node) => node.text); | ||
} | ||
for (const key of keys) { | ||
result.push({ key, lang, params }); | ||
} | ||
} | ||
@@ -37,2 +41,31 @@ return result; | ||
} | ||
function resolveParams(params) { | ||
return Object.keys(flatten(traverseParams(params))); | ||
} | ||
function traverseParams(params) { | ||
const properties = {}; | ||
// Recursive function to handle nested properties | ||
function processProperty(property) { | ||
const key = property.name.getText().replace(/['"]/g, ''); | ||
const initializer = property.initializer; | ||
if (!initializer) | ||
return; | ||
if (ts.isObjectLiteralExpression(initializer)) { | ||
// Handle nested object | ||
properties[key] = traverseParams(initializer); | ||
} | ||
else { | ||
// Simple value (string, number, etc.) | ||
properties[key] = initializer.getText(); | ||
} | ||
} | ||
// Iterate through the properties of the ObjectLiteralExpression | ||
for (const property of params.properties) { | ||
if (ts.isPropertyAssignment(property)) { | ||
processProperty(property); | ||
} | ||
} | ||
// Convert the properties object to a JSON string | ||
return properties; | ||
} | ||
//# sourceMappingURL=build-keys-from-ast-nodes.js.map |
@@ -36,3 +36,3 @@ import { tsquery, ScriptKind } from '@phenomnomnominal/tsquery'; | ||
.flat() | ||
.forEach(({ key, lang }) => { | ||
.forEach(({ key, lang, params }) => { | ||
const [keyWithoutScope, scopeAlias] = resolveAliasAndKeyFromService(key, lang, scopes); | ||
@@ -42,2 +42,3 @@ addKey({ | ||
keyWithoutScope, | ||
params, | ||
...baseParams, | ||
@@ -44,0 +45,0 @@ }); |
export type TSExtractorResult = { | ||
key: string; | ||
lang: string; | ||
params: string[]; | ||
}[]; |
{ | ||
"name": "@jsverse/transloco-keys-manager", | ||
"version": "5.0.0", | ||
"version": "5.1.0", | ||
"description": "Extract translatable keys from projects that uses Transloco", | ||
@@ -5,0 +5,0 @@ "engines": { |
@@ -327,2 +327,27 @@ > [!IMPORTANT] | ||
- Supports params: | ||
```html | ||
<comp *transloco="let t;"> | ||
<h1>{{ t('key', {value: '123', another: property}) }}</h1> | ||
<p>{{ 'description' | transloco:{'param': 123} }}</p> | ||
<footer transloco="footer" [translocoParams]="{param: 123}"></footer> | ||
</comp> | ||
``` | ||
```ts | ||
import {translate} from '@jsverse/transloco'; | ||
translate('key', {param: 123}); | ||
class MyComponent { | ||
someMethod() { | ||
const value = translocoService.translate(`key`, {param: 123}); | ||
const value$ = translocoService.selectTranslate(`key`, {param: 123}); | ||
// Only literal params are supported, the following won't be extracted: | ||
translocoService.translate(`key`, this.myParams); | ||
} | ||
} | ||
``` | ||
## π΅ Keys Detective | ||
@@ -433,2 +458,3 @@ | ||
3. `{{scope}}` - the key's scope. | ||
4. `{{params}}` - the params used for this key. | ||
@@ -435,0 +461,0 @@ |
@@ -58,1 +58,2 @@ export type Config = { | ||
export type Translation = Record<string, any>; | ||
export type OrArray<T> = T | T[]; |
export declare function isObject(value: any): value is Record<string, any>; | ||
export declare function isFunction(value: any): value is (...args: any[]) => any; | ||
export declare function isNil(value: unknown): value is undefined | null; | ||
export declare function notNil<T>(value: T | undefined | null): value is T; | ||
export declare function isDirectory(path: string): boolean; | ||
export declare function isString(value: any): value is string; | ||
export declare function isUndefined(value: any): value is undefined; |
@@ -5,5 +5,11 @@ import { existsSync, lstatSync } from 'fs'; | ||
} | ||
export function isFunction(value) { | ||
return typeof value === 'function'; | ||
} | ||
export function isNil(value) { | ||
return isUndefined(value) || value === null; | ||
} | ||
export function notNil(value) { | ||
return !isNil(value); | ||
} | ||
export function isDirectory(path) { | ||
@@ -10,0 +16,0 @@ return existsSync(path) && lstatSync(path).isDirectory(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
199770
2607
544