Compare commits

...

17 Commits

Author SHA1 Message Date
Kim Biesbjerg
567a0586dc Update deps 2021-04-14 10:08:21 +02:00
Jabi
17dfbbed84 enable piped argument on function calls (#233) 2021-04-14 10:00:44 +02:00
Kim Biesbjerg
acdffe0121 Merge branch 'master' of https://github.com/biesbjerg/ngx-translate-extract into master 2020-09-29 11:56:31 +02:00
Kim Biesbjerg
116133ba32 Bump version 7.0.3 2020-09-29 11:56:03 +02:00
Kim Biesbjerg
50b2ca6f4a (chore) Update deps, allow newer version of typescript and angular compiler 2020-09-29 11:55:17 +02:00
Kim Biesbjerg
b46a914756 Update README.md 2020-08-05 12:59:00 +02:00
Kim Biesbjerg
bc3e5fbe2f fix typo 2020-06-25 10:52:24 +02:00
Kim Biesbjerg
ea990d6f9d update deps, bump version 2020-06-25 10:43:59 +02:00
Kim Biesbjerg
c60705d5fa run prettier on code 2020-05-28 19:36:21 +02:00
Kim Biesbjerg
85cd1e4a46 fix(directive-parser) add support for bound attributes 2020-05-28 19:35:26 +02:00
Kim Biesbjerg
8afbb2f3a9 bump version 2020-05-28 00:29:39 +02:00
Kim Biesbjerg
329c24d962 update deps 2020-05-28 00:29:02 +02:00
Kim Biesbjerg
a30a6f9215 clean up some tests 2020-05-28 00:28:13 +02:00
Kim Biesbjerg
2adec54c00 fix(directive-parser) refactor + correct handling of whitespace 2020-05-28 00:09:53 +02:00
Alexander von Weiss
619b3c56ea fix(pipe-parser): add support for more sophisticated expressions that worked in version 4.2.0 with the regex based parser (#185)
* fix(pipe-parser): add support for more sophisticated expressions
2020-05-20 15:18:31 +02:00
Kim Biesbjerg
5e0da552b0 Add username to GitHub sponsors 2020-04-16 15:00:34 +02:00
Kim Biesbjerg
5f2eb2a7a0 Update package.json example scripts 2020-04-16 14:57:51 +02:00
9 changed files with 1180 additions and 1438 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,2 @@
github: #biesbjerg github: biesbjerg
custom: https://donate.biesbjerg.com custom: https://donate.biesbjerg.com

View File

@@ -15,7 +15,8 @@ Add a script to your project's `package.json`:
```json ```json
... ...
"scripts": { "scripts": {
"extract-i18n": "ngx-translate-extract --input ./src --output ./src/assets/i18n/strings.json --key-as-default-value --clean --sort --format namespaced-json" "i18n:init": "ngx-translate-extract --input ./src --output ./src/assets/i18n/template.json --key-as-default-value --replace --format json",
"i18n:extract": "ngx-translate-extract --input ./src --output ./src/assets/i18n/{en,da,de,fi,nb,nl,sv}.json --clean --format json"
} }
... ...
``` ```
@@ -100,6 +101,7 @@ Examples:
ngx-translate-extract -i './src/**/*.{ts,tsx,html}' -o strings.json Extract from ts, tsx and html ngx-translate-extract -i './src/**/*.{ts,tsx,html}' -o strings.json Extract from ts, tsx and html
ngx-translate-extract -i './src/**/!(*.spec).{ts,html}' -o Extract from ts, html, excluding files with ".spec" ngx-translate-extract -i './src/**/!(*.spec).{ts,html}' -o Extract from ts, html, excluding files with ".spec"
strings.json strings.json
```
## Note for GetText users ## Note for GetText users

2071
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@biesbjerg/ngx-translate-extract", "name": "@biesbjerg/ngx-translate-extract",
"version": "6.0.4", "version": "7.0.3",
"description": "Extract strings from projects using ngx-translate", "description": "Extract strings from projects using ngx-translate",
"main": "dist/index.js", "main": "dist/index.js",
"typings": "dist/index.d.ts", "typings": "dist/index.d.ts",
@@ -61,44 +61,44 @@
}, },
"config": {}, "config": {},
"devDependencies": { "devDependencies": {
"@angular/compiler": "^9.1.1", "@angular/compiler": "^11.2.9",
"@types/braces": "^3.0.0", "@types/braces": "^3.0.0",
"@types/chai": "^4.2.11", "@types/chai": "^4.2.16",
"@types/flat": "^5.0.0", "@types/flat": "^5.0.1",
"@types/gettext-parser": "4.0.0", "@types/gettext-parser": "4.0.0",
"@types/glob": "^7.1.1", "@types/glob": "^7.1.3",
"@types/mkdirp": "^1.0.0", "@types/mkdirp": "^1.0.1",
"@types/mocha": "^7.0.2", "@types/mocha": "^8.2.2",
"@types/node": "^12.12.31", "@types/node": "^14.14.37",
"@types/yargs": "^15.0.4", "@types/yargs": "^16.0.1",
"braces": "^3.0.2", "braces": "^3.0.2",
"chai": "^4.2.0", "chai": "^4.3.4",
"husky": "^4.2.3", "husky": "^6.0.0",
"lint-staged": "^10.0.9", "lint-staged": "^10.5.4",
"mocha": "^7.1.1", "mocha": "^8.3.2",
"prettier": "^2.0.2", "prettier": "^2.2.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^8.8.1", "ts-node": "^9.1.1",
"tslint": "^6.1.0", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"tslint-eslint-rules": "^5.4.0", "tslint-eslint-rules": "^5.4.0",
"tslint-etc": "^1.10.1", "tslint-etc": "^1.13.9",
"typescript": "^3.8.3" "typescript": "^4.2.4"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/compiler": "^8.0.0 || ^9.0.0", "@angular/compiler": ">=8.0.0",
"typescript": "^3.0.0" "typescript": ">=3.0.0"
}, },
"dependencies": { "dependencies": {
"@phenomnomnominal/tsquery": "^4.0.0", "@phenomnomnominal/tsquery": "^4.1.1",
"boxen": "^4.2.0", "boxen": "^5.0.1",
"colorette": "^1.1.0", "colorette": "^1.2.2",
"flat": "^5.0.0", "flat": "^5.0.2",
"gettext-parser": "^4.0.3", "gettext-parser": "^4.0.4",
"glob": "^7.1.6", "glob": "^7.1.6",
"mkdirp": "^1.0.3", "mkdirp": "^1.0.4",
"path": "^0.12.7", "path": "^0.12.7",
"terminal-link": "^2.1.1", "terminal-link": "^2.1.1",
"yargs": "^15.3.1" "yargs": "^16.2.0"
} }
} }

View File

@@ -74,7 +74,7 @@ export class ExtractTask implements TaskInterface {
try { try {
let event = 'CREATED'; let event = 'CREATED';
if (fs.existsSync(outputPath)) { if (fs.existsSync(outputPath)) {
this.options.replace ? event = 'REPLACED' : event = 'MERGED'; this.options.replace ? (event = 'REPLACED') : (event = 'MERGED');
} }
this.save(outputPath, final); this.save(outputPath, final);
this.out(`%s %s`, dim(`- ${outputPath}`), green(`[${event}]`)); this.out(`%s %s`, dim(`- ${outputPath}`), green(`[${event}]`));

View File

@@ -1,87 +1,180 @@
import {
parseTemplate,
TmplAstNode as Node,
TmplAstElement as Element,
TmplAstText as Text,
TmplAstTemplate as Template,
TmplAstTextAttribute as TextAttribute,
TmplAstBoundAttribute as BoundAttribute,
AST,
ASTWithSource,
LiteralPrimitive,
Conditional,
Binary,
BindingPipe,
Interpolation,
LiteralArray,
LiteralMap
} from '@angular/compiler';
import { ParserInterface } from './parser.interface'; import { ParserInterface } from './parser.interface';
import { TranslationCollection } from '../utils/translation.collection'; import { TranslationCollection } from '../utils/translation.collection';
import { isPathAngularComponent, extractComponentInlineTemplate } from '../utils/utils'; import { isPathAngularComponent, extractComponentInlineTemplate } from '../utils/utils';
import { parseTemplate, TmplAstNode, TmplAstElement, TmplAstTextAttribute } from '@angular/compiler'; const TRANSLATE_ATTR_NAME = 'translate';
type ElementLike = Element | Template;
export class DirectiveParser implements ParserInterface { export class DirectiveParser implements ParserInterface {
public extract(source: string, filePath: string): TranslationCollection | null { public extract(source: string, filePath: string): TranslationCollection | null {
let collection: TranslationCollection = new TranslationCollection();
if (filePath && isPathAngularComponent(filePath)) { if (filePath && isPathAngularComponent(filePath)) {
source = extractComponentInlineTemplate(source); source = extractComponentInlineTemplate(source);
} }
const nodes: Node[] = this.parseTemplate(source, filePath);
const elements: ElementLike[] = this.getElementsWithTranslateAttribute(nodes);
let collection: TranslationCollection = new TranslationCollection(); elements.forEach((element) => {
const attribute = this.getAttribute(element, TRANSLATE_ATTR_NAME);
if (attribute?.value) {
collection = collection.add(attribute.value);
return;
}
const nodes: TmplAstNode[] = this.parseTemplate(source, filePath); const boundAttribute = this.getBoundAttribute(element, TRANSLATE_ATTR_NAME);
this.getTranslatableElements(nodes).forEach((element) => { if (boundAttribute?.value) {
const key = this.getElementTranslateAttrValue(element) || this.getElementContent(element); this.getLiteralPrimitives(boundAttribute.value).forEach((literalPrimitive) => {
collection = collection.add(key); collection = collection.add(literalPrimitive.value);
}); });
return;
}
const textNodes = this.getTextNodes(element);
textNodes.forEach((textNode) => {
collection = collection.add(textNode.value.trim());
});
});
return collection; return collection;
} }
protected getTranslatableElements(nodes: TmplAstNode[]): TmplAstElement[] { /**
return nodes * Find all ElementLike nodes with a translate attribute
.filter((element) => this.isElement(element)) * @param nodes
.reduce((result: TmplAstElement[], element: TmplAstElement) => { */
return result.concat(this.findChildrenElements(element)); protected getElementsWithTranslateAttribute(nodes: Node[]): ElementLike[] {
}, []) let elements: ElementLike[] = [];
.filter((element) => this.isTranslatable(element)); nodes.filter(this.isElementLike).forEach((element) => {
if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) {
elements = [...elements, element];
}
if (this.hasBoundAttribute(element, TRANSLATE_ATTR_NAME)) {
elements = [...elements, element];
}
const childElements = this.getElementsWithTranslateAttribute(element.children);
if (childElements.length) {
elements = [...elements, ...childElements];
}
});
return elements;
} }
protected findChildrenElements(node: TmplAstNode): TmplAstElement[] { /**
if (!this.isElement(node)) { * Get direct child nodes of type Text
return []; * @param element
*/
protected getTextNodes(element: ElementLike): Text[] {
return element.children.filter(this.isText);
} }
// If element has translate attribute all its content is translatable /**
// so we don't need to traverse any deeper * Check if attribute is present on element
if (this.isTranslatable(node)) { * @param element
return [node]; */
protected hasAttribute(element: ElementLike, name: string): boolean {
return this.getAttribute(element, name) !== undefined;
} }
return node.children.reduce( /**
(result: TmplAstElement[], childNode: TmplAstNode) => { * Get attribute value if present on element
if (this.isElement(childNode)) { * @param element
const children = this.findChildrenElements(childNode); */
return result.concat(children); protected getAttribute(element: ElementLike, name: string): TextAttribute {
} return element.attributes.find((attribute) => attribute.name === name);
return result;
},
[node]
);
} }
protected parseTemplate(template: string, path: string): TmplAstNode[] { /**
* Check if bound attribute is present on element
* @param element
* @param name
*/
protected hasBoundAttribute(element: ElementLike, name: string): boolean {
return this.getBoundAttribute(element, name) !== undefined;
}
/**
* Get bound attribute if present on element
* @param element
* @param name
*/
protected getBoundAttribute(element: ElementLike, name: string): BoundAttribute {
return element.inputs.find((input) => input.name === name);
}
/**
* Get literal primitives from expression
* @param exp
*/
protected getLiteralPrimitives(exp: AST): LiteralPrimitive[] {
if (exp instanceof LiteralPrimitive) {
return [exp];
}
let visit: AST[] = [];
if (exp instanceof Interpolation) {
visit = exp.expressions;
} else if (exp instanceof LiteralArray) {
visit = exp.expressions;
} else if (exp instanceof LiteralMap) {
visit = exp.values;
} else if (exp instanceof BindingPipe) {
visit = [exp.exp];
} else if (exp instanceof Conditional) {
visit = [exp.trueExp, exp.falseExp];
} else if (exp instanceof Binary) {
visit = [exp.left, exp.right];
} else if (exp instanceof ASTWithSource) {
visit = [exp.ast];
}
let results: LiteralPrimitive[] = [];
visit.forEach((child) => {
results = [...results, ...this.getLiteralPrimitives(child)];
});
return results;
}
/**
* Check if node type is ElementLike
* @param node
*/
protected isElementLike(node: Node): node is ElementLike {
return node instanceof Element || node instanceof Template;
}
/**
* Check if node type is Text
* @param node
*/
protected isText(node: Node): node is Text {
return node instanceof Text;
}
/**
* Parse a template into nodes
* @param template
* @param path
*/
protected parseTemplate(template: string, path: string): Node[] {
return parseTemplate(template, path).nodes; return parseTemplate(template, path).nodes;
} }
protected isElement(node: any): node is TmplAstElement {
return node?.attributes && node?.children;
}
protected isTranslatable(node: TmplAstNode): boolean {
if (this.isElement(node) && node.attributes.some((attribute) => attribute.name === 'translate')) {
return true;
}
return false;
}
protected getElementTranslateAttrValue(element: TmplAstElement): string {
const attr: TmplAstTextAttribute = element.attributes.find((attribute) => attribute.name === 'translate');
return attr?.value ?? '';
}
protected getElementContent(element: TmplAstElement): string {
const content = element.sourceSpan.start.file.content;
const start = element.startSourceSpan.end.offset;
const end = element.endSourceSpan.start.offset;
const val = content.substring(start, end);
return this.cleanKey(val);
}
protected cleanKey(val: string): string {
return val.replace(/\r?\n|\r|\t/g, '').trim();
}
} }

View File

@@ -1,4 +1,17 @@
import { TmplAstNode, parseTemplate, BindingPipe, LiteralPrimitive, Conditional, TmplAstTextAttribute } from '@angular/compiler'; import {
AST,
TmplAstNode,
parseTemplate,
BindingPipe,
LiteralPrimitive,
Conditional,
TmplAstTextAttribute,
Binary,
LiteralMap,
LiteralArray,
Interpolation,
MethodCall
} from '@angular/compiler';
import { ParserInterface } from './parser.interface'; import { ParserInterface } from './parser.interface';
import { TranslationCollection } from '../utils/translation.collection'; import { TranslationCollection } from '../utils/translation.collection';
@@ -36,9 +49,8 @@ export class PipeParser implements ParserInterface {
); );
} }
if (node?.value?.ast?.expressions) { if (node?.value?.ast) {
const translateables = node.value.ast.expressions.filter((exp: any) => this.expressionIsOrHasBindingPipe(exp)); ret.push(...this.getTranslatablesFromAst(node.value.ast));
ret.push(...translateables);
} }
if (node?.attributes) { if (node?.attributes) {
@@ -51,17 +63,8 @@ export class PipeParser implements ParserInterface {
if (node?.inputs) { if (node?.inputs) {
node.inputs.forEach((input: any) => { node.inputs.forEach((input: any) => {
// <element [attrib]="'identifier' | translate"> // <element [attrib]="'identifier' | translate">
if (input?.value?.ast && this.expressionIsOrHasBindingPipe(input.value.ast)) { if (input?.value?.ast) {
ret.push(input.value.ast); ret.push(...this.getTranslatablesFromAst(input.value.ast));
}
// <element attrib="{{'identifier' | translate}}>"
if (input?.value?.ast?.expressions) {
input.value.ast.expressions.forEach((exp: BindingPipe) => {
if (this.expressionIsOrHasBindingPipe(exp)) {
ret.push(exp);
}
});
} }
}); });
} }
@@ -84,7 +87,67 @@ export class PipeParser implements ParserInterface {
return ret; return ret;
} }
protected expressionIsOrHasBindingPipe(exp: any): boolean { protected getTranslatablesFromAst(ast: AST): BindingPipe[] {
// the entire expression is the translate pipe, e.g.:
// - 'foo' | translate
// - (condition ? 'foo' : 'bar') | translate
if (this.expressionIsOrHasBindingPipe(ast)) {
return [ast];
}
// angular double curly bracket interpolation, e.g.:
// - {{ expressions }}
if (ast instanceof Interpolation) {
return this.getTranslatablesFromAsts(ast.expressions);
}
// ternary operator, e.g.:
// - condition ? null : ('foo' | translate)
// - condition ? ('foo' | translate) : null
if (ast instanceof Conditional) {
return this.getTranslatablesFromAsts([ast.trueExp, ast.falseExp]);
}
// string concatenation, e.g.:
// - 'foo' + 'bar' + ('baz' | translate)
if (ast instanceof Binary) {
return this.getTranslatablesFromAsts([ast.left, ast.right]);
}
// a pipe on the outer expression, but not the translate pipe - ignore the pipe, visit the expression, e.g.:
// - { foo: 'Hello' | translate } | json
if (ast instanceof BindingPipe) {
return this.getTranslatablesFromAst(ast.exp);
}
// object - ignore the keys, visit all values, e.g.:
// - { key1: 'value1' | translate, key2: 'value2' | translate }
if (ast instanceof LiteralMap) {
return this.getTranslatablesFromAsts(ast.values);
}
// array - visit all its values, e.g.:
// - [ 'value1' | translate, 'value2' | translate ]
if (ast instanceof LiteralArray) {
return this.getTranslatablesFromAsts(ast.expressions);
}
if (ast instanceof MethodCall) {
return this.getTranslatablesFromAsts(ast.args);
}
return [];
}
protected getTranslatablesFromAsts(asts: AST[]): BindingPipe[] {
return this.flatten(asts.map((ast) => this.getTranslatablesFromAst(ast)));
}
protected flatten<T extends AST>(array: T[][]): T[] {
return [].concat(...array);
}
protected expressionIsOrHasBindingPipe(exp: any): exp is BindingPipe {
if (exp.name && exp.name === TRANSLATE_PIPE_NAME) { if (exp.name && exp.name === TRANSLATE_PIPE_NAME) {
return true; return true;
} }

View File

@@ -12,34 +12,89 @@ describe('DirectiveParser', () => {
parser = new DirectiveParser(); parser = new DirectiveParser();
}); });
it('should not choke when no html is present in template', () => {
const contents = 'Hello World'; it('should extract keys when using literal map in bound attribute', () => {
const contents = `<div [translate]="{ key1: 'value1' | translate, key2: 'value2' | translate }"></div>`;
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal([]); expect(keys).to.deep.equal(['value1', 'value2']);
}); });
it('should use contents as key when there is no translate attribute value provided', () => { it('should extract keys when using literal arrays in bound attribute', () => {
const contents = `<div [translate]="[ 'value1' | translate, 'value2' | translate ]"></div>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['value1', 'value2']);
});
it('should extract keys when using binding pipe in bound attribute', () => {
const contents = `<div [translate]="'KEY1' | withPipe"></div>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['KEY1']);
});
it('should extract keys when using binary expression in bound attribute', () => {
const contents = `<div [translate]="keyVar || 'KEY1'"></div>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['KEY1']);
});
it('should extract keys when using literal primitive in bound attribute', () => {
const contents = `<div [translate]="'KEY1'"></div>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['KEY1']);
});
it('should extract keys when using conditional in bound attribute', () => {
const contents = `<div [translate]="condition ? 'KEY1' : 'KEY2'"></div>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['KEY1', 'KEY2']);
});
it('should extract keys when using nested conditionals in bound attribute', () => {
const contents = `<div [translate]="isSunny ? (isWarm ? 'Sunny and warm' : 'Sunny but cold') : 'Not sunny'"></div>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Sunny and warm', 'Sunny but cold', 'Not sunny']);
});
it('should extract keys when using interpolation', () => {
const contents = `<div translate="{{ 'KEY1' + key2 + 'KEY3' }}"></div>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['KEY1', 'KEY3']);
});
it('should extract keys keeping proper whitespace', () => {
const contents = `
<div translate>
Wubba
Lubba
Dub Dub
</div>
`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Wubba Lubba Dub Dub']);
});
it('should use element contents as key when no translate attribute value is present', () => {
const contents = '<div translate>Hello World</div>'; const contents = '<div translate>Hello World</div>';
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello World']); expect(keys).to.deep.equal(['Hello World']);
}); });
it('should use translate attribute value as key when provided', () => { it('should use translate attribute value as key when present', () => {
const contents = '<div translate="MY_KEY">Hello World<div>'; const contents = '<div translate="MY_KEY">Hello World<div>';
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['MY_KEY']); expect(keys).to.deep.equal(['MY_KEY']);
}); });
it('should not process children when translate attribute is present', () => { it('should extract keys from child elements when translate attribute is present', () => {
const contents = `<div translate>Hello <strong translate>World</strong></div>`; const contents = `<div translate>Hello <strong translate>World</strong></div>`;
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello <strong translate>World</strong>']); expect(keys).to.deep.equal(['Hello', 'World']);
}); });
it('should not exclude html tags in children', () => { it('should not extract keys from child elements when translate attribute is not present', () => {
const contents = `<div translate>Hello <strong>World</strong></div>`; const contents = `<div translate>Hello <strong>World</strong></div>`;
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello <strong>World</strong>']); expect(keys).to.deep.equal(['Hello']);
}); });
it('should extract and parse inline template', () => { it('should extract and parse inline template', () => {
@@ -72,40 +127,13 @@ describe('DirectiveParser', () => {
expect(collection.values).to.deep.equal({}); expect(collection.values).to.deep.equal({});
}); });
it('should extract contents from within custom tags', () => { it('should extract contents from custom elements', () => {
const contents = `<custom-table><tbody><tr><td translate>Hello World</td></tr></tbody></custom-table>`; const contents = `<custom-table><tbody><tr><td translate>Hello World</td></tr></tbody></custom-table>`;
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello World']); expect(keys).to.deep.equal(['Hello World']);
}); });
it('should not cause error when no html is present in template', () => { it('should extract from template without leading/trailing whitespace', () => {
const contents = `
import { Component } from '@angular/core';
@Component({
template: '{{ variable }}'
})
export class MyComponent {
variable: string
}
`;
const keys = parser.extract(contents, componentFilename).keys();
expect(keys).to.deep.equal([]);
});
it('should extract contents without line breaks', () => {
const contents = `
<p translate>
Please leave a message for your client letting them know why you
rejected the field and what they need to do to fix it.
</p>
`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal([
'Please leave a message for your client letting them know why you rejected the field and what they need to do to fix it.'
]);
});
it('should extract contents without indent spaces', () => {
const contents = ` const contents = `
<div *ngIf="!isLoading && studentsToGrid && studentsToGrid.length == 0" class="no-students" mt-rtl translate>There <div *ngIf="!isLoading && studentsToGrid && studentsToGrid.length == 0" class="no-students" mt-rtl translate>There
are currently no students in this class. The good news is, adding students is really easy! Just use the options are currently no students in this class. The good news is, adding students is really easy! Just use the options
@@ -118,13 +146,7 @@ describe('DirectiveParser', () => {
]); ]);
}); });
it('should extract contents without indent spaces', () => { it('should extract keys from element without leading/trailing whitespace', () => {
const contents = `<button mat-button (click)="search()" translate>client.search.searchBtn</button>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['client.search.searchBtn']);
});
it('should extract contents without indent spaces and trim leading/trailing whitespace', () => {
const contents = ` const contents = `
<div translate> <div translate>
this is an example this is an example
@@ -134,11 +156,18 @@ describe('DirectiveParser', () => {
<div> <div>
<p translate> <p translate>
this is an example this is an example
of a long label of another a long label
</p> </p>
</div> </div>
`; `;
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['this is an example of a long label']); expect(keys).to.deep.equal(['this is an example of a long label', 'this is an example of another a long label']);
}); });
it('should collapse excessive whitespace', () => {
const contents = '<p translate>this is an example</p>';
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['this is an example']);
});
}); });

View File

@@ -47,12 +47,48 @@ describe('PipeParser', () => {
expect(keys).to.deep.equal(['Hello', 'World']); expect(keys).to.deep.equal(['Hello', 'World']);
}); });
it('should extract strings from ternary operators right expression', () => {
const contents = `{{ condition ? null : ('World' | translate) }}`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['World']);
});
it('should extract strings from ternary operators inside attribute bindings', () => {
const contents = `<span [attr]="condition ? null : ('World' | translate)"></span>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['World']);
});
it('should extract strings from ternary operators left expression', () => {
const contents = `{{ condition ? ('World' | translate) : null }}`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['World']);
});
it('should extract strings inside string concatenation', () => {
const contents = `{{ 'a' + ('Hello' | translate) + 'b' + 'c' + ('World' | translate) + 'd' }}`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello', 'World']);
});
it('should extract strings from object', () => {
const contents = `{{ { foo: 'Hello' | translate, bar: ['World' | translate], deep: { nested: { baz: 'Yes' | translate } } } | json }}`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello', 'World', 'Yes']);
});
it('should extract strings from ternary operators inside attribute bindings', () => { it('should extract strings from ternary operators inside attribute bindings', () => {
const contents = `<span [attr]="(condition ? 'Hello' : 'World') | translate"></span>`; const contents = `<span [attr]="(condition ? 'Hello' : 'World') | translate"></span>`;
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello', 'World']); expect(keys).to.deep.equal(['Hello', 'World']);
}); });
it('should extract strings from nested expressions', () => {
const contents = `<span [attr]="{ foo: ['a' + ((condition ? 'Hello' : 'World') | translate) + 'b'] }"></span>`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['Hello', 'World']);
});
it('should extract strings from nested ternary operators ', () => { it('should extract strings from nested ternary operators ', () => {
const contents = `<h3>{{ (condition ? 'Hello' : anotherCondition ? 'Nested' : 'World' ) | translate }}</h3>`; const contents = `<h3>{{ (condition ? 'Hello' : anotherCondition ? 'Nested' : 'World' ) | translate }}</h3>`;
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
@@ -159,4 +195,10 @@ describe('PipeParser', () => {
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal([]); expect(keys).to.deep.equal([]);
}); });
it('should extract strings from piped arguments inside a function calls on templates', () => {
const contents = `{{ callMe('Hello' | translate, 'World' | translate ) }}`;
const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal([`Hello`, `World`]);
});
}); });