///
///
///
const stripBom = require("strip-bom");
import fs = require("fs");
import path = require('path');
import lodash = require('lodash');
import metadata = require('MetadataClasses');
const TAB = " ";
export default class Locales {
protected locales = ["ru", "en"];
protected currentLocale: string;
protected entityName: string;
protected translations: Map ;
protected localePathTemplate: lodash.TemplateExecutor;
constructor(entityName: string, currentLocale: string, localePathTemplate: lodash.TemplateExecutor) {
this.localePathTemplate = localePathTemplate;
if (lodash.indexOf(this.locales, currentLocale) == -1) {
throw new Error(`Unknown locale: ${currentLocale}.`);
}
this.translations = {};
for (let locale of this.locales) {
this.translations[locale] = [];
}
this.currentLocale = currentLocale;
this.entityName = entityName;
lodash.remove(this.locales, (n: string) => { return n == currentLocale });
}
setupForm(form: metadata.Form) {
if (!form.caption)
form.caption = "";
let value = this.escapeValue(form.caption);
this.push(
`caption: '${value}'`,
`caption: '${form.name}'`
);
form.caption = `t 'forms.${this.entityName}.caption'`;
}
setupEditFormAttribute(projAttr: metadata.ProjAttr) {
if (!projAttr.caption)
projAttr.caption = "";
let value = this.escapeValue(projAttr.caption);
this.push(
`'${projAttr.name}-caption': '${value}'`,
`'${projAttr.name}-caption': '${projAttr.name}'`
);
projAttr.caption = `t 'forms.${this.entityName}.${projAttr.name}-caption'`;
}
push(currentLocaleStr: string, otherLocalesStr: string) {
this.translations[this.currentLocale].push(currentLocaleStr);
for (let locale of this.locales) {
this.translations[locale].push(otherLocalesStr);
}
}
getLodashVariablesWithSuffix(suffix: string, level: number) {
let lodashVariables = {};
let availableLocales = [ this.currentLocale ].concat(this.locales);
for (let locale of availableLocales) {
let source = this.parseMergeSnippet(`{${this.getProperties(locale)}}`);
let target;
// validate target snippet content
try {
target = this.loadTargetSnippet(locale);
if (target) {
this.generateProperties(target);
}
} catch {
throw new Error(`Invalid target snippet content to merge. File: ${this.localePathTemplate({ "locale": locale })} .`);
}
source = this.merge(source, target);
lodashVariables[`${locale}${suffix}`] = this.generateProperties(source, level);
}
return lodashVariables;
}
getLodashVariablesProperties() {
return this.getLodashVariablesWithSuffix("Properties", 1);
}
protected escapeValue(value: string) {
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
}
protected quote(propName: string) {
if (propName.indexOf("-") == -1)
return propName;
return `'${propName}'`;
}
protected generateProperties(obj, level: number = 0) {
let strings = [];
let tab = (new Array(level + 1)).join(TAB);
let self = this;
lodash.forOwn(obj, function(value, key) {
let str = `${tab}${self.quote(key)}: `;
let nextLevelExists = lodash.isPlainObject(value);
str += nextLevelExists ? '{' : `'${self.escapeValue(value)}'`;
if (nextLevelExists) {
strings.push(str);
str = self.generateProperties(value, level + 1);
if (str.length)
strings.push(str);
str = `${tab}}`;
}
strings.push(`${str},`);
})
if (strings.length) {
let last = strings.length - 1;
strings[last] = strings[last].slice(0, -1);
}
return strings.join('\n');
}
protected parseMergeSnippet(content: string) {
const SNIPPET_REGEXP = /{[\s\S]*}/;
let match = content.match(SNIPPET_REGEXP);
if (match) {
return eval(`(${match.toString()})`);
}
return undefined;
}
protected parseTargetSnippet(content: string) {
return this.parseMergeSnippet(content);
}
protected loadTargetSnippet(locale: string) {
let localePath = this.localePathTemplate({ "locale": locale });
if (!fs.existsSync(localePath)) {
return undefined;
}
let content = stripBom(fs.readFileSync(localePath, "utf8"));
return this.parseTargetSnippet(content);
}
protected merge(masterObj, overlapObj) {
if (overlapObj && lodash.keys(overlapObj).length) {
// prevent arguments modification
let masterClone = lodash.cloneDeep(masterObj);
let overlapClone = lodash.cloneDeep(overlapObj);
this.mergeProperties(masterClone, overlapClone);
return masterClone;
}
return masterObj;
}
protected getProperties(locale: string) {
return ` ${this.translations[locale].join(",\n ")}`;
}
/*
* Replaces the values of simple-typed master object properties with that of same-named overlap object properties
* and adds missing ones from the overlap object.
* WARNING! This method modifies arguments.
*/
private mergeProperties(masterObj, overlapObj) {
let masterKeys = lodash.keys(masterObj);
let overlapKeys = lodash.keys(overlapObj);
// get missing master object properties names
let missingKeys = lodash.difference(overlapKeys, masterKeys);
let self = this;
// add missing propertires from the overlap object
if (missingKeys.length) {
let missingObj = lodash.cloneDeep(lodash.pick(overlapObj, missingKeys));
lodash.forOwn(missingObj, function(value, key) {
masterObj[key] = value;
});
}
// get same-named overlap object properties
overlapObj = lodash.omit(overlapObj, missingKeys);
// replace the values of simple-typed master object properties with that of same-named overlap object properties
if (!lodash.keys(overlapObj).length) {
return;
}
lodash.forOwn(overlapObj, function(overlapValue, key) {
let masterValue = masterObj[key];
let masterValueIsObject = lodash.isPlainObject(masterValue);
let overlapValueIsObject = lodash.isPlainObject(overlapValue);
if (masterValueIsObject != overlapValueIsObject) {
return;
}
if (masterValueIsObject) {
self.mergeProperties(masterValue, overlapValue);
return;
}
masterObj[key] = overlapValue;
});
}
}
export class ApplicationMenuLocales extends Locales {
constructor(currentLocale: string, targetPathTemplate: lodash.TemplateExecutor) {
super("", currentLocale, targetPathTemplate);
}
protected getProperties(locale: string) {
return `${this.translations[locale].join(",\n")}`;
}
protected parseTargetSnippet(content: string) {
const SNIPPET_REGEXP = /[ ,\n]application[ \n]*:[ \n]*{[\s\S]*[ ,\n]'edit-form'[ \n]*:[ \n]*{/;
const EXCLUDE_PROPERTIES = ["application-name", "application-version", "index"];
let match = content.match(SNIPPET_REGEXP);
if (match) {
let obj = super.parseMergeSnippet(match.toString());
return lodash.omit(obj.sitemap, EXCLUDE_PROPERTIES);
}
return undefined;
}
}
export class ModelLocales extends Locales {
protected getProperties(locale: string) {
let translation = this.translations[locale].join(",\n ");
if (translation != "") {
translation = ` ${translation}`;
}
return ` projections: {\n${translation}\n }`;
}
constructor(model: metadata.Model, modelsDir: string, currentLocale: string, targetPathTemplate: lodash.TemplateExecutor) {
super("", currentLocale, targetPathTemplate);
let projections: string[] = [];
let projectionsOtherLocales: string[] = [];
let projName: string;
if (model.projections.length === 0) {
return null;
}
for (let proj of model.projections) {
let projAttrs: SortedPair[] = [];
for (let attr of proj.attrs) {
projAttrs.push(this.declareProjAttr(attr, 4));
}
for (let belongsTo of proj.belongsTo) {
projAttrs.push(this.joinProjBelongsTo(belongsTo, 4));
}
for (let hasMany of proj.hasMany) {
let hasManyAttrs: SortedPair[] = [];
let modelFile = path.join(modelsDir, hasMany.relatedTo + ".json");
let detailModel: metadata.Model = JSON.parse(stripBom(fs.readFileSync(modelFile, "utf8")));
projName = hasMany.projectionName;
let detailProj = lodash.find(detailModel.projections, function (pr: metadata.ProjectionForModel) { return pr.name === projName; });
if (detailProj) {
for (let detailAttr of detailProj.attrs) {
hasManyAttrs.push(this.declareProjAttr(detailAttr, 5));
}
for (let detailBelongsTo of detailProj.belongsTo) {
hasManyAttrs.push(this.joinProjBelongsTo(detailBelongsTo, 5));
}
for (let detailHasMany of detailProj.hasMany) {
hasManyAttrs.push(this.joinProjHasMany(detailHasMany, modelsDir, 5));
}
}
hasManyAttrs = lodash.sortBy(hasManyAttrs, ["index"]);
hasManyAttrs.unshift(new SortedPair(-1, ` __caption__: '${this.escapeValue(hasMany.caption)}'`, ` __caption__: '${hasMany.name}'`));
let attrsStr = lodash.map(hasManyAttrs, "str").join(",\n ");
let attrsStrOtherLocales = lodash.map(hasManyAttrs, "strOtherLocales").join(",\n ");
projAttrs.push(new SortedPair(Number.MAX_VALUE,
`${hasMany.name}: {\n${attrsStr}\n }`,
`${hasMany.name}: {\n${attrsStrOtherLocales}\n }`
));
}
projAttrs = lodash.sortBy(projAttrs, ["index"]);
let attrsStr = lodash.map(projAttrs, "str").join(",\n ");
let attrsStrOtherLocales = lodash.map(projAttrs, "strOtherLocales").join(",\n ");
this.push(
`${proj.name}: {\n ${attrsStr}\n }`,
`${proj.name}: {\n ${attrsStrOtherLocales}\n }`
);
}
}
joinProjHasMany(detailHasMany: metadata.ProjHasMany, modelsDir: string, level: number): SortedPair {
let hasManyAttrs: SortedPair[] = [];
let modelFile = path.join(modelsDir, detailHasMany.relatedTo + ".json");
let hasManyModel: metadata.Model = JSON.parse(stripBom(fs.readFileSync(modelFile, "utf8")));
let hasManyProj = lodash.find(hasManyModel.projections, function (pr: metadata.ProjectionForModel) { return pr.name === detailHasMany.projectionName; });
if (hasManyProj) {
for (let attr of hasManyProj.attrs) {
hasManyAttrs.push(this.declareProjAttr(attr, level));
}
for (let belongsTo of hasManyProj.belongsTo) {
hasManyAttrs.push(this.joinProjBelongsTo(belongsTo, level + 1));
}
let indent: string[] = [];
for (let i = 0; i < level; i++) {
indent.push(TAB);
}
let indentStr = indent.join("");
indent.pop();
let indentStr2 = indent.join("");
hasManyAttrs = lodash.sortBy(hasManyAttrs, ["index"]);
hasManyAttrs.unshift(new SortedPair(-1, `__caption__: '${this.escapeValue(detailHasMany.caption)}'`, `__caption__: '${detailHasMany.name}'`));
let attrsStr = lodash.map(hasManyAttrs, "str").join(",\n" + indentStr);
let attrsStrOtherLocales = lodash.map(hasManyAttrs, "strOtherLocales").join(",\n" + indentStr);
return new SortedPair(Number.MAX_VALUE,
`${detailHasMany.name}: {\n${indentStr}${attrsStr}\n${indentStr2}}`,
`${detailHasMany.name}: {\n${indentStr}${attrsStrOtherLocales}\n${indentStr2}}`
);
}
return new SortedPair(Number.MAX_VALUE, "", "");
}
joinProjBelongsTo(belongsTo: metadata.ProjBelongsTo, level: number): SortedPair {
let belongsToAttrs: SortedPair[] = [];
let index = Number.MAX_VALUE;
for (let attr of belongsTo.attrs) {
belongsToAttrs.push(this.declareProjAttr(attr, level + 1));
}
for (let belongsTo2 of belongsTo.belongsTo) {
belongsToAttrs.push(this.joinProjBelongsTo(belongsTo2, level + 1));
}
let indent: string[] = [];
for (let i = 0; i < level; i++) {
indent.push(TAB);
}
let indentStr = indent.join("");
indent.pop();
let indentStr2 = indent.join("");
belongsToAttrs = lodash.sortBy(belongsToAttrs, ["index"]);
if (belongsToAttrs.length === 0) {
index = Number.MAX_VALUE;
} else {
index = belongsToAttrs[0].index;
}
belongsToAttrs.unshift(new SortedPair(-1, `__caption__: '${this.escapeValue(belongsTo.caption)}'`, `__caption__: '${belongsTo.name}'`));
let attrsStr = lodash.map(belongsToAttrs, "str").join(",\n" + indentStr);
let attrsStrOtherLocales = lodash.map(belongsToAttrs, "strOtherLocales").join(",\n" + indentStr);
return new SortedPair(index,
`${belongsTo.name}: {\n${indentStr}${attrsStr}\n${indentStr2}}`,
`${belongsTo.name}: {\n${indentStr}${attrsStrOtherLocales}\n${indentStr2}}`
);
}
declareProjAttr(attr: metadata.ProjAttr, level: number): SortedPair {
let indent: string[] = [];
for (let i = 0; i < level; i++) {
indent.push(TAB);
}
let indentStr = indent.join("");
indent.pop();
let indentStr2 = indent.join("");
return new SortedPair(attr.index,
`${attr.name}: {\n${indentStr}__caption__: '${this.escapeValue(attr.caption)}'\n${indentStr2}}`,
`${attr.name}: {\n${indentStr}__caption__: '${attr.name}'\n${indentStr2}}`
);
}
}
class SortedPair {
index: number;
str: string;
strOtherLocales: string;
constructor(index: number, str: string, strOtherLocales: string) {
this.index = index;
this.str = str;
this.strOtherLocales = strOtherLocales;
}
}
interface Map {
[K: string]: T;
}