New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More β†’
Socket
Sign inDemoInstall
Socket

@jsverse/transloco-keys-manager

Package Overview
Dependencies
Maintainers
0
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jsverse/transloco-keys-manager - npm Package Compare versions

Comparing version 5.0.0 to 5.1.0

3

keys-builder/add-key.d.ts

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc