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 | ||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @@ -1 +1,14 @@ | ||||
| # 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