Initial commit
This commit is contained in:
		
							
								
								
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | # IDE | ||||||
|  | .vscode | ||||||
|  |  | ||||||
|  | # Logs and other files | ||||||
|  | npm-debug.log* | ||||||
|  | .DS_Store | ||||||
|  |  | ||||||
|  | # Compiled files | ||||||
|  | dist | ||||||
|  |  | ||||||
|  | # Extracted strings | ||||||
|  | *.json | ||||||
|  | *.pot | ||||||
|  |  | ||||||
|  | # Dependency directory | ||||||
|  | node_modules | ||||||
|  |  | ||||||
|  | # Source maps for JS builds | ||||||
|  | *.js.map | ||||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							| @@ -1 +1,14 @@ | |||||||
| # ng2-translate-extract | # ng2-translate-extract | ||||||
|  | Extract strings from projects using ng2-translate to json or pot files. | ||||||
|  |  | ||||||
|  | ## Install | ||||||
|  | 1. `git clone https://github.com/biesbjerg/ng2-translate-extract.git` | ||||||
|  | 2. `cd ng2-translate-extract` | ||||||
|  | 3. `npm install` | ||||||
|  |  | ||||||
|  | ## Try it out | ||||||
|  | 1. Run `npm run watch` in a separate terminal | ||||||
|  | 2. Edit `src/test.ts` to your likings | ||||||
|  | 3. `node dist/test.js` to run it | ||||||
|  |  | ||||||
|  | **THIS IS STILL VERY MUCH A WORK IN PROGRESS** | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | { | ||||||
|  |   "name": "ng2-translate-extract", | ||||||
|  |   "version": "0.1.0", | ||||||
|  |   "description": "Extract strings", | ||||||
|  |   "main": "dist/index.js", | ||||||
|  |   "typings": "dist/index.d.ts", | ||||||
|  |   "scripts": { | ||||||
|  |     "build": "tsc", | ||||||
|  |     "watch": "tsc -w", | ||||||
|  |     "postinstall": "npm run build" | ||||||
|  |   }, | ||||||
|  |   "repository": { | ||||||
|  |     "type": "git", | ||||||
|  |     "url": "https://github.com/biesbjerg/ng2-translate-extract.git" | ||||||
|  |   }, | ||||||
|  |   "keywords": [], | ||||||
|  |   "author": "Kim Biesbjerg <kim@biesbjerg.com>", | ||||||
|  |   "license": "MIT", | ||||||
|  |   "bugs": { | ||||||
|  |     "url": "https://github.com/biesbjerg/ng2-translate-extract/issues" | ||||||
|  |   }, | ||||||
|  |   "homepage": "https://github.com/biesbjerg/ng2-translate-extract", | ||||||
|  |   "engines": { | ||||||
|  |     "node": ">=4.1.1" | ||||||
|  |   }, | ||||||
|  |   "devDependencies": { | ||||||
|  |     "typescript": "~2.0.10" | ||||||
|  |   }, | ||||||
|  |   "config": {}, | ||||||
|  |   "dependencies": { | ||||||
|  |     "@types/glob": "^5.0.30", | ||||||
|  |     "@types/lodash": "^4.14.41", | ||||||
|  |     "fs": "0.0.1-security", | ||||||
|  |     "glob": "^7.1.1", | ||||||
|  |     "lodash": "^4.17.2" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										74
									
								
								src/extractor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/extractor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | import { ParserInterface } from './parsers/parser.interface'; | ||||||
|  | import { HtmlParser } from './parsers/html.parser'; | ||||||
|  | import { TypescriptParser } from './parsers/typescript.parser'; | ||||||
|  | import { SerializerInterface } from './serializers/serializer.interface'; | ||||||
|  | import { PotSerializer } from './serializers/pot.serializer'; | ||||||
|  |  | ||||||
|  | import { uniq as arrayUnique } from 'lodash'; | ||||||
|  | import { sync as readDir } from 'glob'; | ||||||
|  | import { readFileSync as readFile, writeFileSync as writeFile } from 'fs'; | ||||||
|  |  | ||||||
|  | export interface TypeParserMap { | ||||||
|  | 	[ext: string]: ParserInterface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class Extractor { | ||||||
|  |  | ||||||
|  | 	public messages: string[] = []; | ||||||
|  |  | ||||||
|  | 	public parsers: TypeParserMap = { | ||||||
|  | 		html: new HtmlParser(), | ||||||
|  | 		ts: new TypescriptParser() | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	public constructor(public serializer: SerializerInterface) { } | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Extracts messages from paths | ||||||
|  | 	 */ | ||||||
|  | 	public extract(paths: string[]): string[] { | ||||||
|  | 		let messages = []; | ||||||
|  | 		paths.forEach(path => { | ||||||
|  | 			const filePaths = readDir(path); | ||||||
|  | 			filePaths.forEach(filePath => { | ||||||
|  | 				const result = this._extractMessages(filePath); | ||||||
|  | 				messages = [...messages, ...result]; | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return this.messages = arrayUnique(messages); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Serialize and return output | ||||||
|  | 	 */ | ||||||
|  | 	public serialize(): string { | ||||||
|  | 		return this.serializer.serialize(this.messages); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Serialize and save to destination | ||||||
|  | 	 */ | ||||||
|  | 	public save(destination: string): string { | ||||||
|  | 		const data = this.serialize(); | ||||||
|  | 		writeFile(destination, data); | ||||||
|  |  | ||||||
|  | 		return data; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Extract messages from file using specialized parser | ||||||
|  | 	 */ | ||||||
|  | 	protected _extractMessages(filePath: string): string[] { | ||||||
|  | 		const ext: string = filePath.split('.').pop(); | ||||||
|  | 		if (!this.parsers.hasOwnProperty(ext)) { | ||||||
|  | 			return []; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const contents: string = readFile(filePath).toString(); | ||||||
|  | 		const parser: ParserInterface = this.parsers[ext]; | ||||||
|  |  | ||||||
|  | 		return parser.process(contents); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/parsers/html.parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/parsers/html.parser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | import { ParserInterface } from './parser.interface'; | ||||||
|  |  | ||||||
|  | export class HtmlParser implements ParserInterface { | ||||||
|  |  | ||||||
|  | 	public patterns = { | ||||||
|  | 		PipeSingleQuote: '{{\\s*\'((?:\\\\.|[^\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?\\s*}}', | ||||||
|  | 		PipeDoubleQuote: '{{\\s*"((?:\\\\.|[^"\\\\])*)"\\s*\\|\\s*translate(:.*?)?\\s*}}' | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public process(contents: string): string[] { | ||||||
|  | 		let results: string[] = []; | ||||||
|  |  | ||||||
|  | 		for (let patternName in this.patterns) { | ||||||
|  | 			const regExp = new RegExp(this.patterns[patternName], 'g'); | ||||||
|  |  | ||||||
|  | 			let matches; | ||||||
|  | 			while (matches = regExp.exec(contents)) { | ||||||
|  | 				results.push(matches[1]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return results; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								src/parsers/parser.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/parsers/parser.interface.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | export interface ParserInterface { | ||||||
|  |  | ||||||
|  | 	process(contents: string): string[]; | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								src/parsers/typescript.parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/parsers/typescript.parser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import { ParserInterface } from './parser.interface'; | ||||||
|  |  | ||||||
|  | export class TypescriptParser implements ParserInterface { | ||||||
|  |  | ||||||
|  | 	public patterns = { | ||||||
|  | 		TranslateServiceMethodsSingleQuote: '{{TRANSLATE_SERVICE}}\.(?:get|instant)\\s*\\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\\s*\'', | ||||||
|  | 		TranslateServiceMethodsDoubleQuote: '{{TRANSLATE_SERVICE}}\.(?:get|instant)\\s*\\\(\\s*"((?:\\\\.|[^"\\\\])*)\\s*"', | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public process(contents: string): string[] { | ||||||
|  | 		let results: string[] = []; | ||||||
|  |  | ||||||
|  | 		const translateServiceVar = this._extractTranslateServiceVar(contents); | ||||||
|  | 		if (!translateServiceVar) { | ||||||
|  | 			return []; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for (let patternName in this.patterns) { | ||||||
|  | 			const regExp = this._createRegExp(patternName, { | ||||||
|  | 				TRANSLATE_SERVICE: translateServiceVar | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			let matches; | ||||||
|  | 			while (matches = regExp.exec(contents)) { | ||||||
|  | 				results.push(matches[1]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return results; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Create regular expression, replacing placeholders with real values | ||||||
|  | 	 */ | ||||||
|  | 	protected _createRegExp(patternName: string, replaceVars: {} = {}): RegExp { | ||||||
|  | 		if (!this.patterns.hasOwnProperty(patternName)) { | ||||||
|  | 			throw new Error('Invalid pattern name'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		let pattern = this.patterns[patternName]; | ||||||
|  | 		Object.keys(replaceVars).forEach(key => { | ||||||
|  | 			pattern = pattern.replace('{{' + key + '}}', replaceVars[key]); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return new RegExp(pattern, 'gi'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Extract name of TranslateService variable for use in patterns | ||||||
|  | 	 */ | ||||||
|  | 	protected _extractTranslateServiceVar(contents: string): string { | ||||||
|  | 		const matches = contents.match(/([a-z0-9_]+)\s*:\s*TranslateService/i) | ||||||
|  | 		if (matches === null) { | ||||||
|  | 			return ''; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return matches[1]; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								src/serializers/json.serializer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/serializers/json.serializer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | import { SerializerInterface } from './serializer.interface'; | ||||||
|  |  | ||||||
|  | export class JsonSerializer implements SerializerInterface { | ||||||
|  |  | ||||||
|  | 	public serialize(messages: string[]): string { | ||||||
|  | 		let result = {}; | ||||||
|  | 		messages.forEach(message => { | ||||||
|  | 			result[message] = ''; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return JSON.stringify(result, null, '\t'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/serializers/pot.serializer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/serializers/pot.serializer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | import { SerializerInterface } from './serializer.interface'; | ||||||
|  |  | ||||||
|  | export class PotSerializer implements SerializerInterface { | ||||||
|  |  | ||||||
|  | 	protected _headers = { | ||||||
|  | 		'Content-Type': 'text/plain; charset=utf-8', | ||||||
|  | 		'Content-Transfer-Encoding': '8bit' | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	protected _buffer: string[] = []; | ||||||
|  |  | ||||||
|  | 	public serialize(messages: string[]): string { | ||||||
|  | 		this._reset(); | ||||||
|  | 		this._addHeader(this._headers); | ||||||
|  | 		this._addMessages(messages); | ||||||
|  | 		return this._buffer.join('\n'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected _addHeader(headers: {}): void { | ||||||
|  | 		this._add('msgid', ''); | ||||||
|  | 		this._add('msgstr', ''); | ||||||
|  | 		Object.keys(headers).forEach(key => { | ||||||
|  | 			this._buffer.push(`"${key}: ${headers[key]}\\n"`); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected _addMessages(messages: string[]): void { | ||||||
|  | 		messages.forEach(message => { | ||||||
|  | 			this._add('msgid', message); | ||||||
|  | 			this._add('msgstr', ''); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected _add(key: string, val: string): void { | ||||||
|  | 		this._buffer.push(`${key} "${this._escape(val)}"`); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected _reset(): void { | ||||||
|  | 		this._buffer = []; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected _escape(message: string): string { | ||||||
|  | 		return message.replace(/"([^"\\]+)*"/g, '\\"$1\\"'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								src/serializers/serializer.interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/serializers/serializer.interface.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | export interface SerializerInterface { | ||||||
|  |  | ||||||
|  | 	serialize(messages: string[]): string; | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								src/test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import { Extractor } from './extractor'; | ||||||
|  | import { JsonSerializer } from './serializers/json.serializer'; | ||||||
|  | import { PotSerializer } from './serializers/pot.serializer'; | ||||||
|  |  | ||||||
|  | const root = '/Users/kim/ionic/mindly-app/master/src'; | ||||||
|  | const paths = [ | ||||||
|  | 	root + '/**/*.html', | ||||||
|  | 	root + '/**/*.ts' | ||||||
|  | ]; | ||||||
|  | const destination = 'template.pot'; | ||||||
|  |  | ||||||
|  | // const serializer = new JsonSerializer(); | ||||||
|  | const serializer = new PotSerializer(); | ||||||
|  | const extractor = new Extractor(serializer); | ||||||
|  |  | ||||||
|  | try { | ||||||
|  | 	extractor.extract(paths); | ||||||
|  | 	const output = extractor.save(destination); | ||||||
|  | 	console.log(`Extracted strings to "${destination}"`); | ||||||
|  | 	console.log(); | ||||||
|  | 	console.log('OUTPUT:'); | ||||||
|  | 	console.log(output); | ||||||
|  | } catch (e) { | ||||||
|  | 	console.log(`Error extracting strings to "${destination}"`); | ||||||
|  | 	throw e; | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | { | ||||||
|  |     "compilerOptions": { | ||||||
|  |         "allowSyntheticDefaultImports": true, | ||||||
|  |         "experimentalDecorators": true, | ||||||
|  |         "target": "ES6", | ||||||
|  |         "module": "commonjs", | ||||||
|  |         "outDir": "./dist/", | ||||||
|  |         "sourceMap": true, | ||||||
|  |         "declaration": true, | ||||||
|  |         "removeComments": true, | ||||||
|  |         "forceConsistentCasingInFileNames": true | ||||||
|  |     }, | ||||||
|  |     "exclude": [ | ||||||
|  |         "dist", | ||||||
|  |         "node_modules" | ||||||
|  |     ] | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user