diff --git a/.gitignore b/.gitignore index 6b2ccd7..ed7beea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # IDE .vscode +.idea # Logs and other files npm-debug.log* diff --git a/package.json b/package.json index e20bfd7..362e9f5 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dependencies": { "@types/glob": "^5.0.30", "@types/lodash": "^4.14.41", + "cheerio": "~0.22.0", "cli": "^1.0.1", "fs": "0.0.1-security", "glob": "^7.1.1", diff --git a/src/example.ts b/src/example.ts index a87283b..4fd8f97 100644 --- a/src/example.ts +++ b/src/example.ts @@ -1,6 +1,5 @@ import { Extractor } from './extractor'; import { JsonSerializer } from './serializers/json.serializer'; -import { PotSerializer } from './serializers/pot.serializer'; const dir = '/path/to/extract/strings/from'; const dest = '/path/to/save/template/to/template.pot'; diff --git a/src/extractor.ts b/src/extractor.ts index c280c95..3a3446a 100644 --- a/src/extractor.ts +++ b/src/extractor.ts @@ -1,23 +1,20 @@ import { ParserInterface } from './parsers/parser.interface'; -import { HtmlParser } from './parsers/html.parser'; -import { TypescriptParser } from './parsers/typescript.parser'; +import { PipeParser } from './parsers/pipe.parser'; +import { DirectiveParser } from "./parsers/directive.parser"; +import { ServiceParser } from './parsers/service.parser'; import { SerializerInterface } from './serializers/serializer.interface'; -import { PotSerializer } from './serializers/pot.serializer'; import * as lodash from 'lodash'; import * as glob from 'glob'; import * as fs from 'fs'; -export interface TypeParserMap { - [ext: string]: ParserInterface -} - export class Extractor { - public parsers: TypeParserMap = { - html: new HtmlParser(), - ts: new TypescriptParser() - }; + public parsers: ParserInterface[] = [ + new PipeParser(), + new ServiceParser(), + new DirectiveParser() + ]; public globPatterns: string[] = [ '/**/*.ts', @@ -67,15 +64,14 @@ export class Extractor { * Extract messages from file using specialized parser */ protected _extractMessages(filePath: string): string[] { - const ext: string = filePath.split('.').pop(); - if (!this.parsers.hasOwnProperty(ext)) { - return []; - } + let results = []; const contents: string = fs.readFileSync(filePath, 'utf-8'); - const parser: ParserInterface = this.parsers[ext]; + this.parsers.forEach((parser: ParserInterface) => { + results = results.concat(parser.process(contents)); + }); - return parser.process(contents); + return results; } } \ No newline at end of file diff --git a/src/parsers/directive.parser.ts b/src/parsers/directive.parser.ts new file mode 100644 index 0000000..446b81b --- /dev/null +++ b/src/parsers/directive.parser.ts @@ -0,0 +1,66 @@ +import {ParserInterface} from './parser.interface'; +import * as $ from 'cheerio'; + +export class DirectiveParser implements ParserInterface { + + public patterns = { + template: `template:\\s?(("|'|\`)(.|[\\r\\n])+?[^\\\\]\\2)` + }; + + protected _parseTemplate(content) { + let results: string[] = [], + template = content.trim() + // hack for cheerio that doesn't support wrapped attributes + .replace('[translate]=', '__translate__='); + + $(template).find('[translate],[__translate__]').contents().filter(function() { + return this.nodeType === 3; // node type 3 = text node + }).each(function() { + let key, + $this = $(this), + element = $(this).parent(), + wrappedAttr = element.attr('__translate__'), // previously [translate]= + attr = element.attr('translate'); // translate= + + // only support string values for now + if(wrappedAttr && wrappedAttr.match(/^['"].*['"]$/)) { + key = wrappedAttr.substr(1, wrappedAttr.length - 2); + } else if(attr) { + key = attr; + } + + if(!key) { + key = $this.text().replace(/\\n/gi, '').trim(); + } + + if(key) { + results.push(key); + } + }); + + return results; + } + + public process(contents: string): string[] { + const regExp = new RegExp(this.patterns.template, 'gi'); + + let results: string[] = [], + hasTemplate = false, + matches; + + while(matches = regExp.exec(contents)) { + let content = matches[1] + .substr(1, matches[1].length - 2); + + hasTemplate = true; + results = results.concat(this._parseTemplate(content)); + } + + if(!hasTemplate) { + this._parseTemplate(contents); + } + + return results; + } + +} diff --git a/src/parsers/html.parser.ts b/src/parsers/pipe.parser.ts similarity index 60% rename from src/parsers/html.parser.ts rename to src/parsers/pipe.parser.ts index 6c6d9ec..17baf3a 100644 --- a/src/parsers/html.parser.ts +++ b/src/parsers/pipe.parser.ts @@ -1,11 +1,10 @@ import { ParserInterface } from './parser.interface'; -export class HtmlParser implements ParserInterface { +export class PipeParser implements ParserInterface { public patterns = { - PipeSingleQuote: '\'((?:\\\\.|[^\'\\\\])*)\'\\s*\\|\\s*translate(:.*?)?', - PipeDoubleQuote: '"((?:\\\\.|[^"\\\\])*)"\\s*\\|\\s*translate(:.*?)?' - } + pipe: `(['"\`])([^\\1\\r\\n]*)\\1\\s+\\|\\s*translate(:.*?)?` + }; public process(contents: string): string[] { let results: string[] = []; @@ -15,7 +14,7 @@ export class HtmlParser implements ParserInterface { let matches; while (matches = regExp.exec(contents)) { - results.push(matches[1]); + results.push(matches[2]); } } diff --git a/src/parsers/typescript.parser.ts b/src/parsers/service.parser.ts similarity index 78% rename from src/parsers/typescript.parser.ts rename to src/parsers/service.parser.ts index 35895c2..e3b0b29 100644 --- a/src/parsers/typescript.parser.ts +++ b/src/parsers/service.parser.ts @@ -1,11 +1,10 @@ import { ParserInterface } from './parser.interface'; -export class TypescriptParser implements ParserInterface { +export class ServiceParser implements ParserInterface { public patterns = { - TranslateServiceMethodsSingleQuote: '{{TRANSLATE_SERVICE}}\.(?:get|instant)\\s*\\\(\\s*\'((?:\\\\.|[^\'\\\\])*)\\s*\'', - TranslateServiceMethodsDoubleQuote: '{{TRANSLATE_SERVICE}}\.(?:get|instant)\\s*\\\(\\s*"((?:\\\\.|[^"\\\\])*)\\s*"', - } + translateServiceMethods: `{{TRANSLATE_SERVICE}}\.(?:get|instant)\\s*\\\(\\s*(['"\`])([^\\1\\r\\n]+)\\1`, + }; public process(contents: string): string[] { let results: string[] = []; @@ -22,7 +21,7 @@ export class TypescriptParser implements ParserInterface { let matches; while (matches = regExp.exec(contents)) { - results.push(matches[1]); + results.push(matches[2]); } } @@ -49,7 +48,7 @@ export class TypescriptParser implements ParserInterface { * 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) + const matches = contents.match(/([a-z0-9_]+)\s*:\s*TranslateService/i); if (matches === null) { return ''; }