Initial commit
This commit is contained in:
parent
916ba888aa
commit
b5e1125efb
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"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user