Compare commits

..

17 Commits

Author SHA1 Message Date
dependabot[bot]
0a7c2dd5f0 Bump minimatch and mocha
Bumps [minimatch](https://github.com/isaacs/minimatch) to 3.1.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together.


Updates `minimatch` from 3.0.4 to 3.1.2
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

Updates `mocha` from 8.3.2 to 10.1.0
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v8.3.2...v10.1.0)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 12:29:34 +00:00
dependabot[bot]
a108eb776c Bump ansi-regex from 3.0.0 to 3.0.1 (#3)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-22 09:13:58 +03:00
dependabot[bot]
a60ea65325 Bump minimist from 1.2.5 to 1.2.6 (#2)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-21 23:49:50 +03:00
dd31950151 Merge pull request #1 from unistack-org/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2022-01-12 17:36:04 +03:00
dependabot[bot]
f0cc5a2d9f Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-13 01:30:55 +00:00
Kim Biesbjerg
82eb652e4b Bump version 2021-04-14 10:10:21 +02:00
Kim Biesbjerg
4e91eb5fc5 Update deps (#234) 2021-04-14 10:09:26 +02:00
riot
0809e065ec fixes example call to generate i18n extracts (#225) 2021-04-14 10:03:06 +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
8 changed files with 863 additions and 1109 deletions

View File

@@ -20,7 +20,7 @@ Add a script to your project's `package.json`:
} }
... ...
``` ```
You can now run `npm run extract-i18n` and it will extract strings from your project. You can now run `npm run i18n:extract` and it will extract strings from your project.
## Usage ## Usage
@@ -101,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

1740
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": "7.0.0", "version": "7.0.4",
"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.9", "@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.1", "@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.42", "@types/node": "^14.14.37",
"@types/yargs": "^15.0.5", "@types/yargs": "^16.0.1",
"braces": "^3.0.2", "braces": "^3.0.2",
"chai": "^4.2.0", "chai": "^4.3.4",
"husky": "^4.2.5", "husky": "^6.0.0",
"lint-staged": "^10.2.6", "lint-staged": "^10.5.4",
"mocha": "^7.2.0", "mocha": "^10.1.0",
"prettier": "^2.0.5", "prettier": "^2.2.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^8.10.1", "ts-node": "^9.1.1",
"tslint": "^6.1.2", "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.9.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.1.0", "@phenomnomnominal/tsquery": "^4.1.1",
"boxen": "^4.2.0", "boxen": "^5.0.1",
"colorette": "^1.2.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.4", "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

@@ -3,7 +3,18 @@ import {
TmplAstNode as Node, TmplAstNode as Node,
TmplAstElement as Element, TmplAstElement as Element,
TmplAstText as Text, TmplAstText as Text,
TmplAstTemplate as Template TmplAstTemplate as Template,
TmplAstTextAttribute as TextAttribute,
TmplAstBoundAttribute as BoundAttribute,
AST,
ASTWithSource,
LiteralPrimitive,
Conditional,
Binary,
BindingPipe,
Interpolation,
LiteralArray,
LiteralMap
} from '@angular/compiler'; } from '@angular/compiler';
import { ParserInterface } from './parser.interface'; import { ParserInterface } from './parser.interface';
@@ -24,13 +35,22 @@ export class DirectiveParser implements ParserInterface {
const elements: ElementLike[] = this.getElementsWithTranslateAttribute(nodes); const elements: ElementLike[] = this.getElementsWithTranslateAttribute(nodes);
elements.forEach((element) => { elements.forEach((element) => {
const attr = this.getAttribute(element, TRANSLATE_ATTR_NAME); const attribute = this.getAttribute(element, TRANSLATE_ATTR_NAME);
if (attr) { if (attribute?.value) {
collection = collection.add(attr); collection = collection.add(attribute.value);
return; return;
} }
const boundAttribute = this.getBoundAttribute(element, TRANSLATE_ATTR_NAME);
if (boundAttribute?.value) {
this.getLiteralPrimitives(boundAttribute.value).forEach((literalPrimitive) => {
collection = collection.add(literalPrimitive.value);
});
return;
}
const textNodes = this.getTextNodes(element); const textNodes = this.getTextNodes(element);
textNodes.forEach((textNode: Text) => { textNodes.forEach((textNode) => {
collection = collection.add(textNode.value.trim()); collection = collection.add(textNode.value.trim());
}); });
}); });
@@ -43,16 +63,18 @@ export class DirectiveParser implements ParserInterface {
*/ */
protected getElementsWithTranslateAttribute(nodes: Node[]): ElementLike[] { protected getElementsWithTranslateAttribute(nodes: Node[]): ElementLike[] {
let elements: ElementLike[] = []; let elements: ElementLike[] = [];
nodes.filter(this.isElementLike) nodes.filter(this.isElementLike).forEach((element) => {
.forEach((element) => { if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) {
if (this.hasAttribute(element, TRANSLATE_ATTR_NAME)) { elements = [...elements, element];
elements = [...elements, element]; }
} if (this.hasBoundAttribute(element, TRANSLATE_ATTR_NAME)) {
const childElements = this.getElementsWithTranslateAttribute(element.children); elements = [...elements, element];
if (childElements.length) { }
elements = [...elements, ...childElements]; const childElements = this.getElementsWithTranslateAttribute(element.children);
} if (childElements.length) {
}); elements = [...elements, ...childElements];
}
});
return elements; return elements;
} }
@@ -76,8 +98,59 @@ export class DirectiveParser implements ParserInterface {
* Get attribute value if present on element * Get attribute value if present on element
* @param element * @param element
*/ */
protected getAttribute(element: ElementLike, name: string): string | undefined { protected getAttribute(element: ElementLike, name: string): TextAttribute {
return element.attributes.find((attribute) => attribute.name === name)?.value; return element.attributes.find((attribute) => attribute.name === name);
}
/**
* 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;
} }
/** /**
@@ -104,5 +177,4 @@ export class DirectiveParser implements ParserInterface {
protected parseTemplate(template: string, path: string): Node[] { protected parseTemplate(template: string, path: string): Node[] {
return parseTemplate(template, path).nodes; return parseTemplate(template, path).nodes;
} }
} }

View File

@@ -9,7 +9,8 @@ import {
Binary, Binary,
LiteralMap, LiteralMap,
LiteralArray, LiteralArray,
Interpolation Interpolation,
MethodCall
} from '@angular/compiler'; } from '@angular/compiler';
import { ParserInterface } from './parser.interface'; import { ParserInterface } from './parser.interface';
@@ -131,11 +132,15 @@ export class PipeParser implements ParserInterface {
return this.getTranslatablesFromAsts(ast.expressions); return this.getTranslatablesFromAsts(ast.expressions);
} }
if (ast instanceof MethodCall) {
return this.getTranslatablesFromAsts(ast.args);
}
return []; return [];
} }
protected getTranslatablesFromAsts(asts: AST[]): BindingPipe[] { protected getTranslatablesFromAsts(asts: AST[]): BindingPipe[] {
return this.flatten(asts.map(ast => this.getTranslatablesFromAst(ast))); return this.flatten(asts.map((ast) => this.getTranslatablesFromAst(ast)));
} }
protected flatten<T extends AST>(array: T[][]): T[] { protected flatten<T extends AST>(array: T[][]): T[] {

View File

@@ -12,6 +12,55 @@ describe('DirectiveParser', () => {
parser = new DirectiveParser(); parser = new DirectiveParser();
}); });
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();
expect(keys).to.deep.equal(['value1', 'value2']);
});
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', () => { it('should extract keys keeping proper whitespace', () => {
const contents = ` const contents = `
<div translate> <div translate>
@@ -120,4 +169,5 @@ describe('DirectiveParser', () => {
const keys = parser.extract(contents, templateFilename).keys(); const keys = parser.extract(contents, templateFilename).keys();
expect(keys).to.deep.equal(['this is an example']); expect(keys).to.deep.equal(['this is an example']);
}); });
}); });

View File

@@ -195,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`]);
});
}); });