Compare commits
	
		
			14 Commits
		
	
	
		
			v6.1.0
			...
			chore-upda
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 567a0586dc | ||
|  | 17dfbbed84 | ||
|  | acdffe0121 | ||
|  | 116133ba32 | ||
|  | 50b2ca6f4a | ||
|  | b46a914756 | ||
|  | bc3e5fbe2f | ||
|  | ea990d6f9d | ||
|  | c60705d5fa | ||
|  | 85cd1e4a46 | ||
|  | 8afbb2f3a9 | ||
|  | 329c24d962 | ||
|  | a30a6f9215 | ||
|  | 2adec54c00 | 
| @@ -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 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2071
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2071
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										54
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								package.json
									
									
									
									
									
								
							| @@ -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" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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}]`)); | ||||||
|   | |||||||
| @@ -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(); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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[] { | ||||||
|   | |||||||
| @@ -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']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -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`]); | ||||||
|  | 	}); | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user