Add option to merge extracted strings with existing translations (Thanks to @ocombe)
This commit is contained in:
parent
59ef277c64
commit
6a76e7b5cb
11
README.md
11
README.md
@ -25,16 +25,19 @@ You can also install the package globally:
|
|||||||
|
|
||||||
Now you can execute the script from everywhere:
|
Now you can execute the script from everywhere:
|
||||||
|
|
||||||
`ng2-translate-extract --dir /extract/from/this/dir --output /save/to/this/dir --format pot`
|
`ng2-translate-extract --dir /extract/from/this/dir --output /save/to/this/dir --format json --merge --clean`
|
||||||
## Commandline arguments
|
## Commandline arguments
|
||||||
```
|
```
|
||||||
Usage:
|
Usage:
|
||||||
ng2-translate-extract [OPTIONS] [ARGS]
|
ng2-translate-extract [OPTIONS] [ARGS]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-d, --dir [DIR] Directory path you would like to extract strings from (Default is /Users/kim/ionic/ng2-translate-extract/bin)
|
-d, --dir [DIR] Directory path you would like to extract strings from (Default is current directory)
|
||||||
-o, --output [DIR] Directory path you would like to save extracted
|
-o, --output [DIR] Directory path you would like to save extracted
|
||||||
strings (Default is /Users/kim/ionic/ng2-translate-extract/bin)
|
strings (Default is Default is current directory)
|
||||||
-f, --format [VALUE] Output format. VALUE must be either [json|pot] (Default is json)
|
-f, --format [VALUE] Output format. VALUE must be either [json|pot] (Default is json)
|
||||||
-h, --help Display help and usage details
|
-m, --merge [BOOLEAN] Merge extracted strings with existing file if it
|
||||||
|
exists (Default is true)
|
||||||
|
-c, --clean BOOLEAN Remove unused keys when merging
|
||||||
|
-h, --help Display help and usage details
|
||||||
```
|
```
|
||||||
|
@ -5,13 +5,15 @@ var fs = require('fs');
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
|
||||||
var Extractor = require('../dist/extractor').Extractor;
|
var Extractor = require('../dist/extractor').Extractor;
|
||||||
var JsonSerializer = require('../dist/serializers/json.serializer').JsonSerializer;
|
var JsonCompiler = require('../dist/compilers/json.compiler').JsonCompiler;
|
||||||
var PotSerializer = require('../dist/serializers/pot.serializer').PotSerializer;
|
var PoCompiler = require('../dist/compilers/po.compiler').PoCompiler
|
||||||
|
|
||||||
var options = cli.parse({
|
var options = cli.parse({
|
||||||
dir: ['d', 'Directory path you would like to extract strings from', 'dir', process.env.PWD],
|
dir: ['d', 'Directory path you would like to extract strings from', 'dir', process.env.PWD],
|
||||||
output: ['o', 'Directory path you would like to save extracted strings', 'dir', process.env.PWD],
|
output: ['o', 'Directory path you would like to save extracted strings', 'dir', process.env.PWD],
|
||||||
format: ['f', 'Output format', ['json', 'pot'], 'json']
|
format: ['f', 'Output format', ['json', 'pot'], 'json'],
|
||||||
|
merge: ['m', 'Merge extracted strings with existing file if it exists', 'boolean', true],
|
||||||
|
clean: ['c', 'Remove unused keys when merging', 'boolean', false]
|
||||||
});
|
});
|
||||||
|
|
||||||
[options.dir, options.output].forEach(dir => {
|
[options.dir, options.output].forEach(dir => {
|
||||||
@ -22,25 +24,41 @@ var options = cli.parse({
|
|||||||
|
|
||||||
switch (options.format) {
|
switch (options.format) {
|
||||||
case 'pot':
|
case 'pot':
|
||||||
var serializer = new PotSerializer();
|
var compiler = new PoCompiler();
|
||||||
break;
|
break;
|
||||||
case 'json':
|
case 'json':
|
||||||
var serializer = new JsonSerializer();
|
var compiler = new JsonCompiler();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = 'template.' + options.format;
|
var filename = 'template.' + options.format;
|
||||||
var destination = path.join(options.output, filename);
|
var dest = path.join(options.output, filename);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var extractor = new Extractor(serializer);
|
var extracted = new Extractor().process(options.dir);
|
||||||
var collection = extractor.process(options.dir);
|
cli.info(`* Extracted ${extracted.count()} strings`);
|
||||||
if (collection.count() > 0) {
|
|
||||||
extractor.save(destination);
|
if (extracted.isEmpty()) {
|
||||||
cli.ok(`Extracted ${collection.count()} strings: '${destination}'`);
|
process.exit();
|
||||||
} else {
|
|
||||||
cli.info(`Found no extractable strings in the supplied directory path: '${options.dir}'`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let collection = extracted;
|
||||||
|
|
||||||
|
if (options.merge && fs.existsSync(dest)) {
|
||||||
|
const existing = compiler.parse(fs.readFileSync(dest, 'utf-8'));
|
||||||
|
|
||||||
|
collection = extracted.union(existing);
|
||||||
|
cli.info(`* Merged with existing strings`);
|
||||||
|
|
||||||
|
if (options.clean) {
|
||||||
|
const stringCount = collection.count();
|
||||||
|
collection = collection.intersect(extracted);
|
||||||
|
cli.info(`* Removed ${stringCount - collection.count()} unused strings`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(dest, compiler.compile(collection));
|
||||||
|
cli.ok(`Saved to: '${dest}'`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
cli.fatal(e.toString());
|
cli.fatal(e.toString());
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
"cheerio": "~0.22.0",
|
"cheerio": "~0.22.0",
|
||||||
"cli": "^1.0.1",
|
"cli": "^1.0.1",
|
||||||
"fs": "0.0.1-security",
|
"fs": "0.0.1-security",
|
||||||
|
"gettext-parser": "^1.2.1",
|
||||||
"glob": "^7.1.1",
|
"glob": "^7.1.1",
|
||||||
"path": "^0.12.7"
|
"path": "^0.12.7"
|
||||||
}
|
}
|
||||||
|
9
src/compilers/compiler.interface.ts
Normal file
9
src/compilers/compiler.interface.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
|
export interface CompilerInterface {
|
||||||
|
|
||||||
|
compile(collection: TranslationCollection): string;
|
||||||
|
|
||||||
|
parse(contents: string): TranslationCollection;
|
||||||
|
|
||||||
|
}
|
14
src/compilers/json.compiler.ts
Normal file
14
src/compilers/json.compiler.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { CompilerInterface } from './compiler.interface';
|
||||||
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
|
export class JsonCompiler implements CompilerInterface {
|
||||||
|
|
||||||
|
public compile(collection: TranslationCollection): string {
|
||||||
|
return JSON.stringify(collection.values, null, '\t');
|
||||||
|
}
|
||||||
|
|
||||||
|
public parse(contents: string): TranslationCollection {
|
||||||
|
return new TranslationCollection(JSON.parse(contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
53
src/compilers/po.compiler.ts
Normal file
53
src/compilers/po.compiler.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { CompilerInterface } from './compiler.interface';
|
||||||
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
|
import * as gettext from 'gettext-parser';
|
||||||
|
|
||||||
|
export class PoCompiler implements CompilerInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation domain
|
||||||
|
*/
|
||||||
|
public domain = '';
|
||||||
|
|
||||||
|
public compile(collection: TranslationCollection): string {
|
||||||
|
const data = {
|
||||||
|
charset: 'utf-8',
|
||||||
|
headers: {
|
||||||
|
'mime-version': '1.0',
|
||||||
|
'content-type': 'text/plain; charset=utf-8',
|
||||||
|
'content-transfer-encoding': '8bit'
|
||||||
|
},
|
||||||
|
translations: {
|
||||||
|
'default': Object.keys(collection.values).reduce((translations, key) => {
|
||||||
|
translations[key] = {
|
||||||
|
msgid: key,
|
||||||
|
msgstr: collection.get(key)
|
||||||
|
};
|
||||||
|
return translations;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return gettext.po.compile(data, 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public parse(contents: string): TranslationCollection {
|
||||||
|
const collection = new TranslationCollection();
|
||||||
|
|
||||||
|
const po = gettext.po.parse(contents, 'utf-8');
|
||||||
|
if (!po.translations.hasOwnProperty(this.domain)) {
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = Object.keys(po.translations[this.domain])
|
||||||
|
.filter(key => key.length > 0)
|
||||||
|
.reduce((values, key) => {
|
||||||
|
values[key] = po.translations[this.domain][key].msgstr.pop();
|
||||||
|
return values;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return new TranslationCollection(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
src/declarations.d.ts
vendored
Normal file
1
src/declarations.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module '*';
|
@ -1,17 +1,16 @@
|
|||||||
import { Extractor } from './extractor';
|
import { Extractor } from './extractor';
|
||||||
import { JsonSerializer } from './serializers/json.serializer';
|
import { JsonCompiler } from './compilers/json.compiler';
|
||||||
import { StringCollection } from './utils/string.collection';
|
import { TranslationCollection } from './utils/translation.collection';
|
||||||
|
|
||||||
const serializer = new JsonSerializer();
|
const compiler = new JsonCompiler();
|
||||||
const extractor = new Extractor(serializer);
|
const extractor = new Extractor();
|
||||||
|
|
||||||
const src = '/your/project';
|
const dirPath = '/your/project';
|
||||||
const dest = '/your/project/template.json';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const collection: StringCollection = extractor.process(src);
|
const collection: TranslationCollection = extractor.process(dirPath);
|
||||||
const serialized: string = extractor.save(dest);
|
const result: string = compiler.compile(collection);
|
||||||
console.log({ strings: collection.keys(), serialized });
|
console.log(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Something went wrong: ${e.toString()}`);
|
console.log(`Something went wrong: ${e.toString()}`);
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,7 @@ import { ParserInterface } from './parsers/parser.interface';
|
|||||||
import { PipeParser } from './parsers/pipe.parser';
|
import { PipeParser } from './parsers/pipe.parser';
|
||||||
import { DirectiveParser } from './parsers/directive.parser';
|
import { DirectiveParser } from './parsers/directive.parser';
|
||||||
import { ServiceParser } from './parsers/service.parser';
|
import { ServiceParser } from './parsers/service.parser';
|
||||||
import { SerializerInterface } from './serializers/serializer.interface';
|
import { TranslationCollection } from './utils/translation.collection';
|
||||||
import { StringCollection } from './utils/string.collection';
|
|
||||||
|
|
||||||
import * as glob from 'glob';
|
import * as glob from 'glob';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
@ -22,42 +21,24 @@ export class Extractor {
|
|||||||
new ServiceParser()
|
new ServiceParser()
|
||||||
];
|
];
|
||||||
|
|
||||||
public collection: StringCollection = new StringCollection();
|
|
||||||
|
|
||||||
public constructor(public serializer: SerializerInterface) { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process dir
|
* Extract strings from dir
|
||||||
*/
|
*/
|
||||||
public process(dir: string): StringCollection {
|
public process(dir: string): TranslationCollection {
|
||||||
|
let collection: TranslationCollection = new TranslationCollection();
|
||||||
|
|
||||||
this._readDir(dir, this.patterns).forEach(path => {
|
this._readDir(dir, this.patterns).forEach(path => {
|
||||||
const contents: string = fs.readFileSync(path, 'utf-8');
|
const contents: string = fs.readFileSync(path, 'utf-8');
|
||||||
this.parsers.forEach((parser: ParserInterface) => {
|
this.parsers.forEach((parser: ParserInterface) => {
|
||||||
this.collection.merge(parser.extract(contents, path));
|
collection = collection.union(parser.extract(contents, path));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize and return output
|
* Get all files in dir matching patterns
|
||||||
*/
|
|
||||||
public serialize(): string {
|
|
||||||
return this.serializer.serialize(this.collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize and save to destination
|
|
||||||
*/
|
|
||||||
public save(destination: string): string {
|
|
||||||
const data = this.serialize();
|
|
||||||
fs.writeFileSync(destination, data);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all files in dir matching find patterns
|
|
||||||
*/
|
*/
|
||||||
protected _readDir(dir: string, patterns: string[]): string[] {
|
protected _readDir(dir: string, patterns: string[]): string[] {
|
||||||
return patterns.reduce((results, pattern) => {
|
return patterns.reduce((results, pattern) => {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { ParserInterface } from './parser.interface';
|
import { ParserInterface } from './parser.interface';
|
||||||
import { AbstractTemplateParser } from './abstract-template.parser';
|
import { AbstractTemplateParser } from './abstract-template.parser';
|
||||||
import { StringCollection } from '../utils/string.collection';
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
import * as $ from 'cheerio';
|
import * as $ from 'cheerio';
|
||||||
|
|
||||||
export class DirectiveParser extends AbstractTemplateParser implements ParserInterface {
|
export class DirectiveParser extends AbstractTemplateParser implements ParserInterface {
|
||||||
|
|
||||||
public extract(contents: string, path?: string): StringCollection {
|
public extract(contents: string, path?: string): TranslationCollection {
|
||||||
if (path && this._isAngularComponent(path)) {
|
if (path && this._isAngularComponent(path)) {
|
||||||
contents = this._extractInlineTemplate(contents);
|
contents = this._extractInlineTemplate(contents);
|
||||||
}
|
}
|
||||||
@ -14,8 +14,8 @@ export class DirectiveParser extends AbstractTemplateParser implements ParserInt
|
|||||||
return this._parseTemplate(contents);
|
return this._parseTemplate(contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _parseTemplate(template: string): StringCollection {
|
protected _parseTemplate(template: string): TranslationCollection {
|
||||||
const collection = new StringCollection();
|
let collection: TranslationCollection = new TranslationCollection();
|
||||||
|
|
||||||
template = this._normalizeTemplateAttributes(template);
|
template = this._normalizeTemplateAttributes(template);
|
||||||
$(template)
|
$(template)
|
||||||
@ -26,7 +26,7 @@ export class DirectiveParser extends AbstractTemplateParser implements ParserInt
|
|||||||
const attr = $element.attr('translate') || $element.attr('ng2-translate');
|
const attr = $element.attr('translate') || $element.attr('ng2-translate');
|
||||||
|
|
||||||
if (attr) {
|
if (attr) {
|
||||||
collection.add(attr);
|
collection = collection.add(attr);
|
||||||
} else {
|
} else {
|
||||||
$element
|
$element
|
||||||
.contents()
|
.contents()
|
||||||
@ -34,7 +34,7 @@ export class DirectiveParser extends AbstractTemplateParser implements ParserInt
|
|||||||
.filter(node => node.type === 'text')
|
.filter(node => node.type === 'text')
|
||||||
.map(node => node.nodeValue.trim())
|
.map(node => node.nodeValue.trim())
|
||||||
.filter(text => text.length > 0)
|
.filter(text => text.length > 0)
|
||||||
.forEach(text => collection.add(text));
|
.forEach(text => collection = collection.add(text));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { StringCollection } from '../utils/string.collection';
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
export interface ParserInterface {
|
export interface ParserInterface {
|
||||||
|
|
||||||
extract(contents: string, path?: string): StringCollection;
|
extract(contents: string, path?: string): TranslationCollection;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ParserInterface } from './parser.interface';
|
import { ParserInterface } from './parser.interface';
|
||||||
import { AbstractTemplateParser } from './abstract-template.parser';
|
import { AbstractTemplateParser } from './abstract-template.parser';
|
||||||
import { StringCollection } from '../utils/string.collection';
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
export class PipeParser extends AbstractTemplateParser implements ParserInterface {
|
export class PipeParser extends AbstractTemplateParser implements ParserInterface {
|
||||||
|
|
||||||
public extract(contents: string, path?: string): StringCollection {
|
public extract(contents: string, path?: string): TranslationCollection {
|
||||||
if (path && this._isAngularComponent(path)) {
|
if (path && this._isAngularComponent(path)) {
|
||||||
contents = this._extractInlineTemplate(contents);
|
contents = this._extractInlineTemplate(contents);
|
||||||
}
|
}
|
||||||
@ -12,14 +12,14 @@ export class PipeParser extends AbstractTemplateParser implements ParserInterfac
|
|||||||
return this._parseTemplate(contents);
|
return this._parseTemplate(contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _parseTemplate(template: string): StringCollection {
|
protected _parseTemplate(template: string): TranslationCollection {
|
||||||
const collection = new StringCollection();
|
let collection: TranslationCollection = new TranslationCollection();
|
||||||
|
|
||||||
const regExp = new RegExp(/(['"`])([^\1\r\n]*)\1\s*\|\s*translate(:.*?)?/, 'g');
|
const regExp = new RegExp(/(['"`])([^\1\r\n]*)\1\s*\|\s*translate(:.*?)?/, 'g');
|
||||||
|
|
||||||
let matches;
|
let matches;
|
||||||
while (matches = regExp.exec(template)) {
|
while (matches = regExp.exec(template)) {
|
||||||
collection.add(matches[2]);
|
collection = collection.add(matches[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ParserInterface } from './parser.interface';
|
import { ParserInterface } from './parser.interface';
|
||||||
import { StringCollection } from '../utils/string.collection';
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
export class ServiceParser implements ParserInterface {
|
export class ServiceParser implements ParserInterface {
|
||||||
|
|
||||||
public extract(contents: string, path?: string): StringCollection {
|
public extract(contents: string, path?: string): TranslationCollection {
|
||||||
const collection = new StringCollection();
|
let collection: TranslationCollection = new TranslationCollection();
|
||||||
|
|
||||||
const translateServiceVar = this._extractTranslateServiceVar(contents);
|
const translateServiceVar = this._extractTranslateServiceVar(contents);
|
||||||
if (!translateServiceVar) {
|
if (!translateServiceVar) {
|
||||||
@ -17,10 +17,9 @@ export class ServiceParser implements ParserInterface {
|
|||||||
let matches;
|
let matches;
|
||||||
while (matches = regExp.exec(contents)) {
|
while (matches = regExp.exec(contents)) {
|
||||||
if (this._stringContainsArray(matches[1])) {
|
if (this._stringContainsArray(matches[1])) {
|
||||||
const matchCollection = StringCollection.fromArray(this._stringToArray(matches[1]));
|
collection = collection.addKeys(this._stringToArray(matches[1]));
|
||||||
collection.merge(matchCollection);
|
|
||||||
} else {
|
} else {
|
||||||
collection.add(matches[3]);
|
collection = collection.add(matches[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { SerializerInterface } from './serializer.interface';
|
|
||||||
import { StringCollection } from '../utils/string.collection';
|
|
||||||
|
|
||||||
export class JsonSerializer implements SerializerInterface {
|
|
||||||
|
|
||||||
public serialize(collection: StringCollection): string {
|
|
||||||
return JSON.stringify(collection.values, null, '\t');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
import { SerializerInterface } from './serializer.interface';
|
|
||||||
import { StringCollection } from '../utils/string.collection';
|
|
||||||
|
|
||||||
export class PotSerializer implements SerializerInterface {
|
|
||||||
|
|
||||||
protected _headers = {
|
|
||||||
'Content-Type': 'text/plain; charset=utf-8',
|
|
||||||
'Content-Transfer-Encoding': '8bit'
|
|
||||||
};
|
|
||||||
|
|
||||||
protected _buffer: string[] = [];
|
|
||||||
|
|
||||||
public serialize(collection: StringCollection): string {
|
|
||||||
this._reset();
|
|
||||||
this._addHeader(this._headers);
|
|
||||||
this._addMessages(collection);
|
|
||||||
|
|
||||||
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(collection: StringCollection): void {
|
|
||||||
Object.keys(collection.values).forEach(key => {
|
|
||||||
this._add('msgid', key);
|
|
||||||
this._add('msgstr', collection.get(key));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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\\"');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import { StringCollection } from '../utils/string.collection';
|
|
||||||
|
|
||||||
export interface SerializerInterface {
|
|
||||||
|
|
||||||
serialize(collection: StringCollection): string;
|
|
||||||
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
export interface StringType {
|
|
||||||
[key: string]: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export class StringCollection {
|
|
||||||
|
|
||||||
public values: StringType = {};
|
|
||||||
|
|
||||||
public static fromObject(values: StringType): StringCollection {
|
|
||||||
const collection = new StringCollection();
|
|
||||||
Object.keys(values).forEach(key => collection.add(key, values[key]));
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static fromArray(keys: string[]): StringCollection {
|
|
||||||
const collection = new StringCollection();
|
|
||||||
keys.forEach(key => collection.add(key));
|
|
||||||
return collection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public add(keys: string | string[], val: string = ''): StringCollection {
|
|
||||||
if (!Array.isArray(keys)) {
|
|
||||||
keys = [keys];
|
|
||||||
}
|
|
||||||
keys.forEach(key => this.values[key] = val);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public remove(key: string): StringCollection {
|
|
||||||
delete this.values[key];
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public merge(collection: StringCollection): StringCollection {
|
|
||||||
this.values = Object.assign({}, this.values, collection.values);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(key: string): string {
|
|
||||||
return this.values[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
public keys(): string[] {
|
|
||||||
return Object.keys(this.values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public count(): number {
|
|
||||||
return Object.keys(this.values).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
69
src/utils/translation.collection.ts
Normal file
69
src/utils/translation.collection.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
export interface TranslationType {
|
||||||
|
[key: string]: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export class TranslationCollection {
|
||||||
|
|
||||||
|
public values: TranslationType = {};
|
||||||
|
|
||||||
|
public constructor(values: TranslationType = {}) {
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(key: string, val: string = ''): TranslationCollection {
|
||||||
|
return new TranslationCollection(Object.assign({}, this.values, { [key]: val }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public addKeys(keys: string[]): TranslationCollection {
|
||||||
|
const values = keys.reduce((results, key) => {
|
||||||
|
results[key] = '';
|
||||||
|
return results;
|
||||||
|
}, {});
|
||||||
|
return new TranslationCollection(Object.assign({}, this.values, values));
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(key: string): TranslationCollection {
|
||||||
|
let newCollection = new TranslationCollection();
|
||||||
|
Object.keys(this.values).forEach(collectionKey => {
|
||||||
|
if (key !== collectionKey) {
|
||||||
|
newCollection = newCollection.add(key, this.values[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public union(collection: TranslationCollection): TranslationCollection {
|
||||||
|
return new TranslationCollection(Object.assign({}, this.values, collection.values));
|
||||||
|
}
|
||||||
|
|
||||||
|
public intersect(collection: TranslationCollection): TranslationCollection {
|
||||||
|
let newCollection = new TranslationCollection();
|
||||||
|
Object.keys(this.values).forEach(key => {
|
||||||
|
if (collection.has(key)) {
|
||||||
|
newCollection = newCollection.add(key, this.values[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public has(key: string): boolean {
|
||||||
|
return this.values.hasOwnProperty(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(key: string): string {
|
||||||
|
return this.values[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public keys(): string[] {
|
||||||
|
return Object.keys(this.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public count(): number {
|
||||||
|
return Object.keys(this.values).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isEmpty(): boolean {
|
||||||
|
return Object.keys(this.values).length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
import { StringCollection } from '../../src/utils/string.collection';
|
|
||||||
|
|
||||||
describe('StringCollection', () => {
|
|
||||||
|
|
||||||
let collection: StringCollection;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
collection = new StringCollection();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add item with value', () => {
|
|
||||||
collection.add('key', 'translation');
|
|
||||||
expect(collection.get('key')).to.equal('translation');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add item with default value', () => {
|
|
||||||
collection.add('key');
|
|
||||||
expect(collection.get('key')).to.equal('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add array of items with default values', () => {
|
|
||||||
collection.add(['key', 'key2']);
|
|
||||||
expect(collection.count()).to.equal(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove item', () => {
|
|
||||||
collection.add('key1').add('key2').remove('key1');
|
|
||||||
expect(collection.count()).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return number of items', () => {
|
|
||||||
collection.add('key1').add('key2');
|
|
||||||
expect(collection.count()).to.equal(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize with array of keys', () => {
|
|
||||||
const newCollection = StringCollection.fromArray(['Hello', 'World']);
|
|
||||||
expect(newCollection.count()).to.equal(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize with key/value pairs', () => {
|
|
||||||
const newCollection = StringCollection.fromObject({'key': 'translation'});
|
|
||||||
expect(newCollection.get('key')).to.equal('translation');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should merge with other collection', () => {
|
|
||||||
collection.add('Hello');
|
|
||||||
const newCollection = StringCollection.fromArray(['World']);
|
|
||||||
expect(collection.merge(newCollection).count()).to.equal(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
82
tests/utils/translation.collection.spec.ts
Normal file
82
tests/utils/translation.collection.spec.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
import { TranslationCollection } from '../../src/utils/translation.collection';
|
||||||
|
|
||||||
|
describe('StringCollection', () => {
|
||||||
|
|
||||||
|
let collection: TranslationCollection;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
collection = new TranslationCollection();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize with key/value pairs', () => {
|
||||||
|
collection = new TranslationCollection({ key1: 'val1', key2: 'val2' });
|
||||||
|
expect(collection.values).to.deep.equal({ key1: 'val1', key2: 'val2' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add key with value', () => {
|
||||||
|
const newCollection = collection.add('theKey', 'theVal');
|
||||||
|
expect(newCollection.get('theKey')).to.equal('theVal');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add key with default value', () => {
|
||||||
|
collection = collection.add('theKey');
|
||||||
|
expect(collection.get('theKey')).to.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not mutate collection when adding key', () => {
|
||||||
|
collection.add('theKey', 'theVal');
|
||||||
|
expect(collection.has('theKey')).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add array of keys with default value', () => {
|
||||||
|
collection = collection.addKeys(['key1', 'key2']);
|
||||||
|
expect(collection.values).to.deep.equal({ key1: '', key2: '' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when collection has key', () => {
|
||||||
|
collection = collection.add('key');
|
||||||
|
expect(collection.has('key')).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when collection does not have key', () => {
|
||||||
|
expect(collection.has('key')).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove key', () => {
|
||||||
|
collection = new TranslationCollection({ removeThisKey: '' });
|
||||||
|
collection = collection.remove('removeThisKey');
|
||||||
|
expect(collection.has('removeThisKey')).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not mutate collection when removing key', () => {
|
||||||
|
collection = new TranslationCollection({ removeThisKey: '' });
|
||||||
|
collection.remove('removeThisKey');
|
||||||
|
expect(collection.has('removeThisKey')).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return number of keys', () => {
|
||||||
|
collection = collection.addKeys(['key1', 'key2', 'key3']);
|
||||||
|
expect(collection.count()).to.equal(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge with other collection', () => {
|
||||||
|
collection = collection.add('oldKey', 'oldVal');
|
||||||
|
const newCollection = new TranslationCollection({ newKey: 'newVal' });
|
||||||
|
expect(collection.union(newCollection).values).to.deep.equal({ oldKey: 'oldVal', newKey: 'newVal' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should intersect with passed collection', () => {
|
||||||
|
collection = collection.addKeys(['red', 'green', 'blue']);
|
||||||
|
const newCollection = new TranslationCollection( { red: '', blue: '' });
|
||||||
|
expect(collection.intersect(newCollection).values).to.deep.equal({ red: '', blue: '' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should intersect with passed collection and keep original values', () => {
|
||||||
|
collection = new TranslationCollection({ red: 'rød', blue: 'blå' });
|
||||||
|
const newCollection = new TranslationCollection({ red: 'no value', blue: 'also no value' });
|
||||||
|
expect(collection.intersect(newCollection).values).to.deep.equal({ red: 'rød', blue: 'blå' });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user