otus-localization
Version:
A translation tool for Angular i18n(angular-t9n)
1,320 lines (1,276 loc) • 63.2 kB
JavaScript
'use strict';
var common = require('@nestjs/common');
var serveStatic = require('@nestjs/serve-static');
var path = require('path');
var core = require('@nestjs/core');
var classValidator = require('class-validator');
var levenshtein = require('js-levenshtein');
var rxjs = require('rxjs');
var operators = require('rxjs/operators');
var multer = require('@nestjs/platform-express/multer');
var child_process = require('child_process');
var util = require('util');
var promises = require('fs/promises');
var websockets = require('@nestjs/websockets');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
exports.LinkHelper = class LinkHelper {
constructor(request) {
const host = request.get('host');
this._origin = host ? `${request.protocol}://${host}` : '';
}
root() {
return `${this._origin}/api`;
}
sourceUnits(query) {
const route = `${this._origin}/api/source/units`;
return query && Object.keys(query).length
? `${route}?${new URLSearchParams(query).toString()}`
: route;
}
sourceUnit(unitOrId) {
const id = typeof unitOrId === 'string' ? unitOrId : unitOrId.id;
return `${this._origin}/api/source/units/${id}`;
}
sourceOrphans(query) {
const route = `${this._origin}/api/source/orphans`;
return query && Object.keys(query).length
? `${route}?${new URLSearchParams(query).toString()}`
: route;
}
sourceOrphan(unitOrId) {
const id = typeof unitOrId === 'string' ? unitOrId : unitOrId.unit.id;
return `${this._origin}/api/source/orphans/${id}`;
}
targets() {
return `${this._origin}/api/targets`;
}
target(language) {
return `${this._origin}/api/targets/${language}`;
}
targetUnits(target, query) {
const route = `${this._origin}/api/targets/${target.language}/units`;
return query && Object.keys(query).length
? `${route}?${new URLSearchParams(query).toString()}`
: route;
}
targetUnit(unitOrId, target) {
const id = typeof unitOrId === 'string' ? unitOrId : unitOrId.id;
return `${this._origin}/api/targets/${target.language}/units/${id}`;
}
targetOrphans(target, query) {
const route = `${this._origin}/api/targets/${target.language}/orphans`;
return query && Object.keys(query).length
? `${route}?${new URLSearchParams(query).toString()}`
: route;
}
targetOrphan(unitOrId, target) {
const id = typeof unitOrId === 'string' ? unitOrId : unitOrId.unit.id;
return `${this._origin}/api/targets/${target.language}/orphans/${id}`;
}
};
exports.LinkHelper = __decorate([
common.Injectable({ scope: common.Scope.REQUEST }),
__param(0, common.Inject(core.REQUEST)),
__metadata("design:paramtypes", [Object])
], exports.LinkHelper);
class LinkBuilder {
constructor() {
this._links = {};
}
self(href) {
return this.href('self', href);
}
hrefWhen(condition, name, hrefFactory) {
return condition ? this.href(name, hrefFactory()) : this;
}
href(name, href) {
this._links[name] = { href };
return this;
}
templatedHref(name, href) {
this._links[name] = { href, templated: true };
return this;
}
build() {
return Object.keys(this._links).length > 0 ? this._links : undefined;
}
}
class FilterableBuilder {
constructor(_selector = (m) => m) {
this._selector = _selector;
this._filterables = {};
}
addFilterables(...properties) {
for (const property of properties) {
this._filterables[property] = (f) => (e) => { var _a; return !!((_a = this._selector(e)[property]) === null || _a === void 0 ? void 0 : _a.toUpperCase().includes(f.toUpperCase())); };
}
return this;
}
build() {
return this._filterables;
}
}
const PAGE_PLACEHOLDER = 'PAGE_PLACEHOLDER';
class PaginationResponse {
constructor(params) {
let entries = params.entries.slice();
const { entriesPerPage, page, sort, ...query } = params.query;
entries = this._sort(entries, sort, params.sortables);
entries = this._filter(entries, query, params.filterables);
this.entriesPerPage = +entriesPerPage || 10;
this.currentPage = +page || 0;
this.totalEntries = entries.length;
this.totalPages = Math.ceil(entries.length / this.entriesPerPage);
const lastPage = this.totalPages - 1;
this._links = new LinkBuilder()
.self(params.urlFactory(params.query))
.hrefWhen(this.currentPage > 0, 'first', () => params.urlFactory({ ...params.query, page: '0' }))
.hrefWhen(this.currentPage > 0, 'previous', () => params.urlFactory({ ...params.query, page: `${this.currentPage - 1}` }))
.templatedHref('page', params
.urlFactory({ ...params.query, page: PAGE_PLACEHOLDER })
.replace(PAGE_PLACEHOLDER, '{page}'))
.hrefWhen(this.currentPage < lastPage, 'next', () => params.urlFactory({ ...params.query, page: `${this.currentPage + 1}` }))
.hrefWhen(this.currentPage < lastPage, 'last', () => params.urlFactory({ ...params.query, page: `${lastPage}` }))
.build();
const start = this.currentPage * this.entriesPerPage;
this._embedded = {
entries: entries.slice(start, start + this.entriesPerPage).map(params.responseMapper),
};
}
_sort(entries, sort, sortables) {
if (!sort || !sortables) {
return entries;
}
const direction = sort[0] !== '!' ? 'asc' : 'desc';
if (direction === 'desc') {
sort = sort.substring(1);
}
if (!(sort in sortables)) {
return entries;
}
const sorting = sortables[sort];
entries = entries.slice().sort(sorting);
return direction === 'asc' ? entries : entries.reverse();
}
_filter(entries, query, filterables) {
if (!filterables) {
return entries;
}
return Object.keys(filterables)
.filter((k) => query[k])
.reduce((current, next) => current.filter(filterables[next](query[next])), entries);
}
}
class SortableBuilder {
constructor(_selector = (m) => m) {
this._selector = _selector;
this._sortables = {};
}
addSortables(...properties) {
for (const property of properties) {
this._sortables[property] = (a, b) => this._selector(a)[property].localeCompare(this._selector(b)[property]);
}
return this;
}
addSafeSortables(...properties) {
for (const property of properties) {
this._sortables[property] = (a, b) => (this._selector(a)[property] || '').localeCompare(this._selector(b)[property] || '');
}
return this;
}
build() {
return this._sortables;
}
}
class SourceOrphanRequest {
}
__decorate([
classValidator.IsString(),
__metadata("design:type", String)
], SourceOrphanRequest.prototype, "id", void 0);
class TargetUnitRequest {
}
__decorate([
classValidator.IsString(),
__metadata("design:type", String)
], TargetUnitRequest.prototype, "target", void 0);
__decorate([
classValidator.IsIn(['initial', 'translated', 'reviewed', 'final']),
__metadata("design:type", String)
], TargetUnitRequest.prototype, "state", void 0);
class RootResponse {
constructor(source, linkHelper) {
this.sourceLanguage = source.language;
this.unitCount = source.units.length;
this._links = new LinkBuilder()
.self(linkHelper.root())
.href('targets', linkHelper.targets())
.href('sourceUnits', linkHelper.sourceUnits())
.href('orphans', linkHelper.sourceOrphans())
.templatedHref('orphan', linkHelper.sourceOrphan('{id}'))
.build();
}
}
class SourceUnitResponse {
constructor(unit, linkHelper) {
Object.assign(this, { ...unit });
this._links = new LinkBuilder().self(linkHelper.sourceUnit(unit)).build();
}
}
class SourceOrphanMatchResponse extends SourceUnitResponse {
constructor(distance, unit, linkHelper) {
super(unit, linkHelper);
this.distance = distance;
}
}
class SourceOrphanResponse extends SourceUnitResponse {
constructor(orphan, linkHelper) {
super(orphan.unit, linkHelper);
this._links.self = {
href: linkHelper.sourceOrphan(orphan),
};
this._embedded = {
similar: orphan.similar
.slice(0, 10)
.map(({ distance, unit }) => new SourceOrphanMatchResponse(distance, unit, linkHelper)),
locales: Array.from(orphan.targetOrphans.keys()).map((t) => t.language),
};
}
}
class TargetUnitResponse {
constructor(target, unit, linkHelper) {
Object.assign(this, { ...target.source.unitMap.get(unit.id), ...unit });
this._links = new LinkBuilder()
.self(linkHelper.targetUnit(unit, target))
.href('source', linkHelper.sourceUnit(unit.id))
.build();
}
}
class TargetOrphanMatchResponse extends TargetUnitResponse {
constructor(distance, target, unit, linkHelper) {
super(target, unit, linkHelper);
this.distance = distance;
}
}
class TargetOrphanResponse extends TargetUnitResponse {
constructor(target, orphan, linkHelper) {
super(target, orphan.unit, linkHelper);
this._links.self = {
href: linkHelper.targetOrphan(orphan, target),
};
this._embedded = {
similar: orphan.similar
.slice(0, 10)
.map(({ distance, unit }) => new TargetOrphanMatchResponse(distance, target, unit, linkHelper)),
};
}
}
class TargetResponse {
constructor(target, linkHelper) {
this.language = target.language;
const counter = { initial: 0, translated: 0, reviewed: 0, final: 0 };
target.units.forEach((u) => ++counter[u.state]);
this.unitCount = target.units.length;
this.initialCount = counter.initial;
this.translatedCount = counter.translated;
this.reviewedCount = counter.reviewed;
this.finalCount = counter.final;
this.orphanCount = target.orphans.length;
this._links = new LinkBuilder()
.self(linkHelper.target(target.language))
.href('units', linkHelper.targetUnits(target))
.templatedHref('unit', linkHelper.targetUnit('{id}', target))
.href('orphans', linkHelper.targetOrphans(target))
.templatedHref('orphan', linkHelper.targetOrphan('{id}', target))
.build();
}
}
class TargetsResponse {
constructor(languages, linkHelper) {
this.languages = languages;
this._links = new LinkBuilder()
.self(linkHelper.targets())
.templatedHref('target', linkHelper.target('{language}'))
.build();
}
}
class TranslationSource {
constructor(language, unitMap) {
this.language = language;
this.unitMap = unitMap;
this.units = Array.from(this.unitMap.values());
}
}
class TranslationTarget {
constructor(source, language, unitMap) {
this.source = source;
this.language = language;
this._changedSubject = new rxjs.Subject();
this.changed = this._changedSubject.asObservable();
this.units = this._generateTargetUnits(unitMap);
this.unitMap = this.units.reduce((current, next) => current.set(next.id, next), new Map());
this.orphans = this._findOrphans(unitMap);
this.orphanMap = this.orphans.reduce((current, next) => current.set(next.unit.id, next), new Map());
}
translateUnit(unit, update) {
unit.target = update.target;
unit.state = update.state;
this._changedSubject.next();
return unit;
}
migrateOrphan(orphan, targetUnit) {
if (this.orphanMap.has(orphan.unit.id)) {
this.translateUnit(targetUnit, orphan.unit);
this.deleteOrphan(orphan);
}
}
deleteOrphan(orphan) {
if (this.orphanMap.delete(orphan.unit.id)) {
const index = this.orphans.indexOf(orphan);
this.orphans.splice(index, 1);
this._changedSubject.next();
}
}
_generateTargetUnits(unitMap) {
return this.source.units.map((u) => unitMap.get(u.id) || {
id: u.id,
source: u.source,
state: 'initial',
target: '',
});
}
_findOrphans(unitMap) {
return Array.from(unitMap.values())
.filter((u) => !this.source.unitMap.has(u.id))
.map((o) => this._generateOrphan(o));
}
_generateOrphan(orphan) {
const similar = this.units
.map((unit) => ({
distance: this._calculateDistance(unit, orphan),
unit,
}))
.sort((a, b) => a.distance - b.distance);
return {
unit: orphan,
similar,
};
}
_calculateDistance(a, b) {
const sourceA = this._normalizeWhitespace(a.source);
const sourceB = this._normalizeWhitespace(b.source);
return levenshtein(sourceA, sourceB);
}
_normalizeWhitespace(value) {
return value.replace(/\s+/g, ' ').trim();
}
}
exports.AppController = class AppController {
constructor(_translationSource, _linkHelper) {
this._translationSource = _translationSource;
this._linkHelper = _linkHelper;
}
root() {
return new RootResponse(this._translationSource, this._linkHelper);
}
};
__decorate([
common.Get(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", RootResponse)
], exports.AppController.prototype, "root", null);
exports.AppController = __decorate([
common.Controller(),
__metadata("design:paramtypes", [TranslationSource, exports.LinkHelper])
], exports.AppController);
class PersistenceStrategy {
}
exports.TranslationTargetRegistry = class TranslationTargetRegistry {
constructor(_source, _persistenceStrategy) {
this._source = _source;
this._persistenceStrategy = _persistenceStrategy;
this._targets = new Map();
}
register(language, units, baseHref) {
const target = new TranslationTarget(this._source, language, units);
if (baseHref) {
target.baseHref = baseHref;
}
target.changed
.pipe(operators.debounceTime(300))
.subscribe(() => this._persistenceStrategy.update(target));
this._synchronizeSources(target);
this._targets.set(language, target);
return target;
}
async create(language, baseHref) {
const target = this.register(language, new Map(), baseHref);
await this._persistenceStrategy.create(target);
return target;
}
get(key) {
return this._targets.get(key);
}
has(key) {
return this._targets.has(key);
}
keys() {
return Array.from(this._targets.keys());
}
values() {
return Array.from(this._targets.values());
}
sortWithIds(ids, language) {
var _a;
const data = (_a = this._targets.get(language)) === null || _a === void 0 ? void 0 : _a.unitMap;
if (data === null || data === void 0 ? void 0 : data.size) {
const result = new Map();
ids.forEach((item) => {
result.set(item, data.get(item));
});
if (result.size) {
const target = new TranslationTarget(this._source, language, result);
target.changed
.pipe(operators.debounceTime(300))
.subscribe(() => this._persistenceStrategy.update(target));
this._synchronizeSources(target);
this._targets.set(language, target);
}
}
}
_synchronizeSources(target) {
let changeRequired = false;
for (const unit of target.units) {
const sourceUnit = target.source.unitMap.get(unit.id);
if (unit.source !== sourceUnit.source) {
if (this._normalizeWhitespace(unit.source) !== this._normalizeWhitespace(sourceUnit.source)) {
unit.state = 'initial';
}
unit.source = sourceUnit.source;
changeRequired = true;
}
}
if (changeRequired) {
this._persistenceStrategy.update(target);
}
}
_normalizeWhitespace(value) {
return value.replace(/\s+/g, ' ').trim();
}
};
exports.TranslationTargetRegistry = __decorate([
common.Injectable(),
__metadata("design:paramtypes", [TranslationSource,
PersistenceStrategy])
], exports.TranslationTargetRegistry);
exports.OrphanRegistry = class OrphanRegistry {
constructor(translationTargetRegistry, source) {
this.orphanMap = new Map();
for (const translationTarget of translationTargetRegistry.values()) {
for (const orphan of translationTarget.orphans) {
const sourceOrphan = this.orphanMap.get(orphan.unit.id);
if (sourceOrphan) {
sourceOrphan.targetOrphans.set(translationTarget, orphan);
}
else {
const { target, state, ...unit } = orphan.unit;
this.orphanMap.set(orphan.unit.id, {
unit,
similar: orphan.similar.map((s) => ({
distance: s.distance,
unit: source.unitMap.get(s.unit.id),
})),
targetOrphans: new Map().set(translationTarget, orphan),
});
}
}
}
this.orphans = Array.from(this.orphanMap.values()).sort((a, b) => a.unit.id.localeCompare(b.unit.id));
}
migrateOrphan(orphan, unit) {
if (this.orphanMap.delete(orphan.unit.id)) {
const index = this.orphans.indexOf(orphan);
this.orphans.splice(index, 1);
orphan.targetOrphans.forEach((targetOrphan, target) => {
const targetUnit = target.unitMap.get(unit.id);
target.migrateOrphan(targetOrphan, targetUnit);
});
}
}
deleteOrphan(orphan) {
if (this.orphanMap.delete(orphan.unit.id)) {
const index = this.orphans.indexOf(orphan);
this.orphans.splice(index, 1);
orphan.targetOrphans.forEach((targetOrphan, target) => target.deleteOrphan(targetOrphan));
}
}
};
exports.OrphanRegistry = __decorate([
common.Injectable(),
__metadata("design:paramtypes", [exports.TranslationTargetRegistry, TranslationSource])
], exports.OrphanRegistry);
class TargetPathBuilder {
constructor(_targetDirectory, sourceFile) {
this._targetDirectory = _targetDirectory;
this._extension = path.extname(sourceFile);
const sourceFileBasename = path.basename(sourceFile);
this._basename = sourceFileBasename.substring(0, sourceFileBasename.length - this._extension.length);
}
createPath(target) {
const language = typeof target === 'string' ? target : target.language;
return path.join(this._targetDirectory, `${this._basename}.${language}${this._extension}`);
}
getTargetDirectory() {
return this._targetDirectory;
}
}
exports.SourceUnitsController = class SourceUnitsController {
constructor(_translationSource, _translationTargetRegistry, _linkHelper) {
this._translationSource = _translationSource;
this._translationTargetRegistry = _translationTargetRegistry;
this._linkHelper = _linkHelper;
}
getPagination(queryParams) {
return new PaginationResponse({
query: queryParams,
entries: this._translationSource.units,
responseMapper: (unit) => {
const sourceUnit = new SourceUnitResponse(unit, this._linkHelper);
sourceUnit._embedded = this._createEmbeddedObject(unit);
return sourceUnit;
},
urlFactory: (query) => this._linkHelper.sourceUnits(query),
});
}
getSourceUnit(id) {
const unit = this._translationSource.unitMap.get(id);
if (!unit) {
throw new common.NotFoundException('Source unit does not exist');
}
const sourceUnit = new SourceUnitResponse(unit, this._linkHelper);
sourceUnit._embedded = this._createEmbeddedObject(unit);
return sourceUnit;
}
reWriteTargetLanguage(language, body) {
this._translationTargetRegistry.sortWithIds(body, language);
return { data: null, message: 'success' };
}
_createEmbeddedObject(unit) {
return this._translationTargetRegistry.values().reduce((current, next) => Object.assign(current, {
[next.language]: new TargetUnitResponse(next, next.unitMap.get(unit.id), this._linkHelper),
}), {});
}
};
__decorate([
common.Get(),
__param(0, common.Query()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", PaginationResponse)
], exports.SourceUnitsController.prototype, "getPagination", null);
__decorate([
common.Get(':id'),
__param(0, common.Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", SourceUnitResponse)
], exports.SourceUnitsController.prototype, "getSourceUnit", null);
__decorate([
common.Post(':language'),
__param(0, common.Param('language')),
__param(1, common.Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Array]),
__metadata("design:returntype", void 0)
], exports.SourceUnitsController.prototype, "reWriteTargetLanguage", null);
exports.SourceUnitsController = __decorate([
common.Controller('source/units'),
__metadata("design:paramtypes", [TranslationSource,
exports.TranslationTargetRegistry,
exports.LinkHelper])
], exports.SourceUnitsController);
exports.TargetOrphansController = class TargetOrphansController {
constructor(_translationTargetRegistry, _linkHelper) {
this._translationTargetRegistry = _translationTargetRegistry;
this._linkHelper = _linkHelper;
this._targetOrphanSortables = new SortableBuilder((o) => o.unit)
.addSortables('id', 'source', 'state')
.addSafeSortables('description', 'meaning', 'target')
.build();
this._targetOrphanFilterables = new FilterableBuilder((o) => o.unit)
.addFilterables('id', 'description', 'meaning', 'source', 'target', 'state')
.build();
}
getPagination(language, queryParams) {
const target = this._translationTargetRegistry.get(language);
if (!target) {
throw new common.NotFoundException('Target does not exist');
}
return new PaginationResponse({
query: queryParams,
entries: target.orphans,
responseMapper: (orphan) => new TargetOrphanResponse(target, orphan, this._linkHelper),
urlFactory: (query) => this._linkHelper.targetOrphans(target, query),
sortables: this._targetOrphanSortables,
filterables: this._targetOrphanFilterables,
});
}
getOrphan(language, id) {
const target = this._translationTargetRegistry.get(language);
if (!target) {
throw new common.NotFoundException('Target does not exist');
}
const orphan = target.orphanMap.get(id);
if (!orphan) {
throw new common.NotFoundException('Orphan does not exist');
}
return new TargetOrphanResponse(target, orphan, this._linkHelper);
}
deleteOrphan(language, id) {
const target = this._translationTargetRegistry.get(language);
if (!target) {
throw new common.NotFoundException('Target does not exist');
}
const orphan = target.orphanMap.get(id);
if (!orphan) {
throw new common.NotFoundException('Orphan does not exist');
}
target.deleteOrphan(orphan);
}
};
__decorate([
common.Get(),
__param(0, common.Param('language')),
__param(1, common.Query()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", PaginationResponse)
], exports.TargetOrphansController.prototype, "getPagination", null);
__decorate([
common.Get(':id'),
__param(0, common.Param('language')),
__param(1, common.Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", TargetOrphanResponse)
], exports.TargetOrphansController.prototype, "getOrphan", null);
__decorate([
common.Delete(':id'),
common.HttpCode(common.HttpStatus.NO_CONTENT),
__param(0, common.Param('language')),
__param(1, common.Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], exports.TargetOrphansController.prototype, "deleteOrphan", null);
exports.TargetOrphansController = __decorate([
common.Controller('targets/:language/orphans'),
__metadata("design:paramtypes", [exports.TranslationTargetRegistry,
exports.LinkHelper])
], exports.TargetOrphansController);
exports.TargetUnitsController = class TargetUnitsController {
constructor(_translationTargetRegistry, _linkHelper) {
this._translationTargetRegistry = _translationTargetRegistry;
this._linkHelper = _linkHelper;
this._targetUnitSortables = new SortableBuilder()
.addSortables('id', 'source', 'state')
.addSafeSortables('description', 'meaning', 'target')
.build();
this._targetUnitFilterables = new FilterableBuilder()
.addFilterables('id', 'description', 'meaning', 'source', 'target', 'state')
.build();
}
getPagination(language, queryParams) {
const target = this._translationTargetRegistry.get(language);
if (!target) {
throw new common.NotFoundException('Target does not exist');
}
return new PaginationResponse({
query: queryParams,
entries: target.units,
responseMapper: (unit) => new TargetUnitResponse(target, unit, this._linkHelper),
urlFactory: (query) => this._linkHelper.targetUnits(target, query),
sortables: this._targetUnitSortables,
filterables: this._targetUnitFilterables,
});
}
getTargetUnit(language, id) {
const target = this._translationTargetRegistry.get(language);
if (!target) {
throw new common.NotFoundException('Target does not exist');
}
const unit = target.unitMap.get(id);
if (!unit) {
throw new common.NotFoundException('Unit does not exist');
}
return new TargetUnitResponse(target, unit, this._linkHelper);
}
updateTargetUnit(language, id, body) {
const target = this._translationTargetRegistry.get(language);
if (!target) {
throw new common.NotFoundException('Target does not exist');
}
const unit = target.unitMap.get(id);
if (!unit) {
throw new common.NotFoundException('Unit does not exist');
}
const translatedUnit = target.translateUnit(unit, body);
return new TargetUnitResponse(target, translatedUnit, this._linkHelper);
}
};
__decorate([
common.Get(),
__param(0, common.Param('language')),
__param(1, common.Query()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", PaginationResponse)
], exports.TargetUnitsController.prototype, "getPagination", null);
__decorate([
common.Get(':id'),
__param(0, common.Param('language')),
__param(1, common.Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", TargetUnitResponse)
], exports.TargetUnitsController.prototype, "getTargetUnit", null);
__decorate([
common.Put(':id'),
__param(0, common.Param('language')),
__param(1, common.Param('id')),
__param(2, common.Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, TargetUnitRequest]),
__metadata("design:returntype", TargetUnitResponse)
], exports.TargetUnitsController.prototype, "updateTargetUnit", null);
exports.TargetUnitsController = __decorate([
common.Controller('targets/:language/units'),
__metadata("design:paramtypes", [exports.TranslationTargetRegistry,
exports.LinkHelper])
], exports.TargetUnitsController);
class TargetInfo {
constructor(project, sourceFile, sourceLanguage, autoTargetFile) {
this.project = project;
this.sourceFile = sourceFile;
this.sourceLanguage = sourceLanguage;
this.autoTargetFile = autoTargetFile;
}
}
function paddingZero(num) {
if (num > 9) {
return `${num}`;
}
return `0${num}`;
}
const exec = util.promisify(child_process.exec);
let GitService = class GitService {
constructor(_targetInfo) {
this._targetInfo = _targetInfo;
}
commitHandler() {
const date = new Date();
const year = date.getFullYear();
const month = paddingZero(date.getMonth() + 1);
const day = paddingZero(date.getDate());
const hour = paddingZero(date.getHours());
const minute = paddingZero(date.getMinutes());
const second = paddingZero(date.getSeconds());
const dateString = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
return exec(`git add ${this._targetInfo.autoTargetFile}`)
.then(() => {
return exec('git diff --cached --name-only');
})
.then((out) => {
if (!out.stdout) {
return Promise.reject('没有新翻译字段');
}
return exec(`git commit -m "localization: localized at ${dateString}" --no-verify`);
})
.then(() => {
const commitDateString = `${year}-${month}-${day}_${hour}-${minute}-${second}`;
return exec(`git push origin HEAD:localization/${commitDateString}`).then((out) => {
if (!out.code) {
return Promise.resolve();
}
return Promise.reject('推送远端失败!');
});
})
.catch((err) => {
if (err === '没有新翻译字段') {
return '';
}
return Promise.reject(err);
});
}
syncLocalizationToDesktop() {
return exec(`git show head:apps/otus-app/src/assets/translations/messages.zh-CN.json > ../messages.zh-CN.json`)
.then(() => {
return exec(`scp ../messages.zh-CN.json otus_public@192.168.31.206:data/otus-front_end/nginx-1.25.3/html/assets/translations/`);
})
.then(() => {
return exec(`rm ../messages.zh-CN.json`);
});
}
};
GitService = __decorate([
common.Injectable(),
__metadata("design:paramtypes", [TargetInfo])
], GitService);
let SyncService = class SyncService {
constructor(targetInfo, _targetPathBuilder) {
this.targetInfo = targetInfo;
this._targetPathBuilder = _targetPathBuilder;
}
extractI18n() {
return new Promise((resolve, reject) => {
child_process.exec('npx nx extract-i18n --skip-nx-cache', (err) => {
if (!err) {
resolve();
}
reject();
});
});
}
loadTranslatedFile() {
return promises.readFile(this.targetInfo.autoTargetFile).then(async (buffer) => {
let sortedIds = [];
try {
let data = JSON.parse(buffer.toString('utf-8'));
sortedIds = Object.keys(data);
if (!Array.isArray) {
sortedIds = [];
}
}
catch (error) { }
await this.saveSortedId(sortedIds);
return Promise.resolve(buffer);
});
}
saveTranslatedFile(file) {
return promises.writeFile(this.targetInfo.autoTargetFile, new Uint8Array(file.buffer), {
encoding: 'utf-8',
});
}
restart() {
process.exit(0);
}
saveSortedId(data) {
const path$1 = path.join(this._targetPathBuilder.getTargetDirectory(), 'sorted.json');
return promises.readFile(path$1, { encoding: 'utf-8' })
.catch(() => {
return '[]';
})
.then((dataString) => {
let list = [];
try {
list = JSON.parse(dataString);
if (!Array.isArray(list)) {
list = [];
}
}
catch (error) {
console.log(error, '读取排序文件错误, 将新建文件!');
}
const dataList = Array.isArray(data) ? data.filter(Boolean) : [];
const resultList = Array.from(new Set(list.concat(dataList)));
return promises.writeFile(path$1, JSON.stringify(resultList, undefined, 4), { encoding: 'utf-8' });
});
}
getSortedId() {
const path$1 = path.join(this._targetPathBuilder.getTargetDirectory(), 'sorted.json');
return promises.readFile(path$1, { encoding: 'utf-8' }).then((dataString) => {
let list = [];
try {
list = JSON.parse(dataString);
if (!Array.isArray(list)) {
list = [];
}
return list;
}
catch (error) {
console.log(error, '读取排序文件错误, 返回结果不排序!');
}
});
}
};
SyncService = __decorate([
common.Injectable(),
__metadata("design:paramtypes", [TargetInfo, TargetPathBuilder])
], SyncService);
exports.TargetsController = class TargetsController {
constructor(_translationTargetRegistry, _linkHelper, git, sync) {
this._translationTargetRegistry = _translationTargetRegistry;
this._linkHelper = _linkHelper;
this.git = git;
this.sync = sync;
}
targets() {
return new TargetsResponse(this._translationTargetRegistry.keys(), this._linkHelper);
}
async syncGitFromRepo() {
try {
const data = await this.git.commitHandler();
if (!data) {
return { data: null, message: 'Commited Success', code: 0 };
}
return { error: data, message: 'Commit Failed', code: 1 };
}
catch (error) {
return { error: error, message: 'Commit Failed', code: 1 };
}
}
async syncLocalization() {
try {
await this.git.syncLocalizationToDesktop();
return { data: null, code: 0 };
}
catch (error) {
return {
data: null,
error,
code: 1,
};
}
}
async extracti18n() {
try {
await this.sync.extractI18n();
return { data: null, message: 'extractI18n success' };
}
catch (error) {
return { data: error, message: 'extractI18n failed' };
}
}
async loadTranslatedFile(res) {
try {
const buffer = await this.sync.loadTranslatedFile();
res.set({
'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment;',
});
res.send(buffer);
}
catch (error) {
res.sendStatus(403);
}
}
async loadSortedId() {
return await this.sync.getSortedId();
}
async restart() {
this.sync.restart();
}
async saveTranslatedFile(file) {
try {
await this.sync.loadTranslatedFile();
await this.sync.saveTranslatedFile(file);
return { message: 'success', code: 0 };
}
catch (error) {
return { message: error, code: 1 };
}
}
target(language) {
const target = this._translationTargetRegistry.get(language);
if (!target) {
throw new common.NotFoundException('Target does not exist');
}
return new TargetResponse(target, this._linkHelper);
}
async createTarget(language) {
const existingTarget = this._translationTargetRegistry.get(language);
if (existingTarget) {
throw new common.BadRequestException('Target already exists');
}
const target = await this._translationTargetRegistry.create(language);
return new TargetResponse(target, this._linkHelper);
}
};
__decorate([
common.Get(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", TargetsResponse)
], exports.TargetsController.prototype, "targets", null);
__decorate([
common.Get('gitCommit'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "syncGitFromRepo", null);
__decorate([
common.Get('syncLocalization'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "syncLocalization", null);
__decorate([
common.Get('extractI18n'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "extracti18n", null);
__decorate([
common.Get('loadTranslatedFile'),
__param(0, common.Res()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "loadTranslatedFile", null);
__decorate([
common.Get('sortedId'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "loadSortedId", null);
__decorate([
common.Get('restart'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "restart", null);
__decorate([
common.Post('saveTranslatedFile'),
common.UseInterceptors(multer.FileInterceptor('file')),
__param(0, common.UploadedFile()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "saveTranslatedFile", null);
__decorate([
common.Get(':language'),
__param(0, common.Param('language')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], exports.TargetsController.prototype, "target", null);
__decorate([
common.Post(':language'),
__param(0, common.Param('language')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], exports.TargetsController.prototype, "createTarget", null);
exports.TargetsController = __decorate([
common.Controller('targets'),
__metadata("design:paramtypes", [exports.TranslationTargetRegistry,
exports.LinkHelper,
GitService,
SyncService])
], exports.TargetsController);
let SourceOrphansController = class SourceOrphansController {
constructor(_source, _orphanRegistry, _linkHelper) {
this._source = _source;
this._orphanRegistry = _orphanRegistry;
this._linkHelper = _linkHelper;
this._sourceOrphanSortables = new SortableBuilder((o) => o.unit)
.addSortables('id', 'source')
.addSafeSortables('description', 'meaning')
.build();
this._sourceOrphanFilterables = new FilterableBuilder((o) => o.unit)
.addFilterables('id', 'description', 'meaning', 'source')
.build();
}
getPagination(queryParams) {
return new PaginationResponse({
query: queryParams,
entries: this._orphanRegistry.orphans,
responseMapper: (orphan) => new SourceOrphanResponse(orphan, this._linkHelper),
urlFactory: (query) => this._linkHelper.sourceOrphans(query),
sortables: this._sourceOrphanSortables,
filterables: this._sourceOrphanFilterables,
});
}
getOrphan(id) {
const orphan = this._orphanRegistry.orphanMap.get(id);
if (!orphan) {
throw new common.NotFoundException('Orphan does not exist');
}
return new SourceOrphanResponse(orphan, this._linkHelper);
}
deleteOrphan(id, body) {
const orphan = this._orphanRegistry.orphanMap.get(id);
if (!orphan) {
throw new common.NotFoundException('Orphan does not exist');
}
if (!body || !body.id) {
this._orphanRegistry.deleteOrphan(orphan);
return;
}
const sourceUnit = this._source.unitMap.get(body.id);
if (!sourceUnit) {
throw new common.NotFoundException('Migration unit does not exist');
}
this._orphanRegistry.migrateOrphan(orphan, sourceUnit);
}
};
__decorate([
common.Get(),
__param(0, common.Query()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", PaginationResponse)
], SourceOrphansController.prototype, "getPagination", null);
__decorate([
common.Get(':id'),
__param(0, common.Param('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", SourceOrphanResponse)
], SourceOrphansController.prototype, "getOrphan", null);
__decorate([
common.Delete(':id'),
common.HttpCode(common.HttpStatus.NO_CONTENT),
__param(0, common.Param('id')),
__param(1, common.Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, SourceOrphanRequest]),
__metadata("design:returntype", void 0)
], SourceOrphansController.prototype, "deleteOrphan", null);
SourceOrphansController = __decorate([
common.Controller('source/orphans'),
__metadata("design:paramtypes", [TranslationSource,
exports.OrphanRegistry,
exports.LinkHelper])
], SourceOrphansController);
class TranslationDeserializer {
}
class XlfDeserializerBase extends TranslationDeserializer {
constructor(_parser) {
super();
this._parser = _parser;
}
_createDocument(content) {
const doc = this._parser.parse(content);
this._assertEncoding(doc);
this._assertXliff(doc);
return doc;
}
_assertEncoding(doc) {
const processingInstruction = Array.from(doc.childNodes).find((c) => c.nodeType === doc.PROCESSING_INSTRUCTION_NODE);
if (!processingInstruction) {
return;
}
const match = processingInstruction.data.match(/encoding="([^"]+)"/);
if (match && match[1].replace(/[ -]+/g, '').toUpperCase() !== 'UTF8') {
throw new Error(`otus-translation only supports UTF-8, but encoding ${match[1]} was detected '${doc.firstChild.toString()}'`);
}
}
_assertXliff(doc) {
if (doc.documentElement.nodeName !== 'xliff') {
throw new Error(`Expected document element to be 'xliff' (instead of ${doc.documentElement.nodeName})`);
}
else if (Array.from(doc.documentElement.childNodes).filter((c) => c.nodeName === 'file').length !== 1) {
throw new Error(`Expected exactly one <file> element in <xliff>`);
}
}
_assertTargetLanguage(targetLanguage) {
if (!targetLanguage) {
throw new Error(`Expected the xliff tag to have a trgLang attribute (e.g. <xliff trgLang="de-CH" ...)`);
}
}
_getFileNode(doc) {
return Array.from(doc.documentElement.childNodes).find((c) => c.nodeName === 'file');
}
_convertToString(node) {
if (!node.textContent && !node.childNodes.length) {
return '';
}
const nodeText = node.toString();
return nodeText.substring(nodeText.indexOf('>') + 1, nodeText.length - (node.nodeName.length + 3));
}
}
exports.XmlParser = class XmlParser {
constructor() {
this._parser = typeof DOMParser === 'undefined'
? new (require('@xmldom/xmldom').DOMParser)()
: new DOMParser();
}
parse(content) {
return this._parser.parseFromString(content, 'text/xml');
}
};
exports.XmlParser = __decorate([
common.Injectable()
], exports.XmlParser);
exports.XlfDeserializer = class XlfDeserializer extends XlfDeserializerBase {
constructor(parser) {
super(parser);
}
deserializeSource(content) {
const doc = this._createDocument(content);
const fileNode = this._getFileNode(doc);
const language = fileNode.getAttribute('source-language');
const unitMap = Array.from(fileNode.getElementsByTagName('trans-unit'))
.map((u) => this._deserializeSourceUnit(u))
.reduce((current, next) => current.set(next.id, next), new Map());
return { language, unitMap };
}
deserializeTarget(content) {
const doc = this._createDocument(content);
const fileNode = this._getFileNode(doc);
const language = fileNode.getAttribute('target-language');
this._assertTargetLanguage(language);
const unitMap = Array.from(fileNode.getElementsByTagName('trans-unit'))
.map((u) => this._deserializeTargetUnit(u))
.reduce((current, next) => current.set(next.id, next), new Map());
return { language, unitMap };
}
_assertXliff(doc) {
super._assertXliff(doc);
if (doc.documentElement.getAttribute('version') !== '1.2') {
throw new Error(`Expected the xliff tag to have a version attribute with value '1.2' (<xliff version="1.2" ...)`);
}
else if (!this._getFileNode(doc).getAttribute('source-language')) {
throw new Error(`Expected the file tag to have a source-language attribute (e.g. <file source-language="en" ...)`);
}
}
_deserializeTargetUnit(unit) {
return {
...this._deserializeSourceUnit(unit),
target: this._extractTarget(unit),
state: this._extractState(unit),
};
}
_deserializeSourceUnit(unitElement) {
const noteElements = Array.from(unitElement.getElementsByTagName('note'));
const unit = {
id: unitElement.getAttribute('id'),
source: this._extractSource(unitElement),
description: noteElements
.filter((e) => e.getAttribute('from') === 'description')
.map((e) => e.textContent)[0] || undefined,
meaning: noteElements
.filter((e) => e.getAttribute('from') === 'meaning')
.map((e) => e.textContent)[0] || undefined,
};
const contextGroups = Array.from(unitElement.getElements