UNPKG

ng-extract-i18n-merge

Version:

Extract and merge i18n xliff translation files for angular projects.

142 lines (141 loc) 7.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Merger = void 0; const translationFileModels_1 = require("./model/translationFileModels"); const strings_1 = require("@angular-devkit/core/src/utils/strings"); const stringUtils_1 = require("./stringUtils"); const FUZZY_THRESHOLD = 0.2; function onlyXmlNodes(source) { return source.replace(/<[^>]+\/>|<([\w-]+)[^>]*>.*<\/\1>/g, '').trim() === ''; } class Merger { constructor(options, normalizedTranslationSourceFile, initialTranslationState) { this.options = options; this.normalizedTranslationSourceFile = normalizedTranslationSourceFile; this.initialTranslationState = initialTranslationState; this.idMapping = {}; } mergeWithMapping(destFileContent, isSourceLang) { var _a, _b, _c; const inUnitsById = new Map(this.normalizedTranslationSourceFile.units.map(unit => [unit.id, unit])); const destUnitsById = new Map(destFileContent.units.map(unit => [unit.id, unit])); const allInUnitsWithoutDestinationUnit = this.normalizedTranslationSourceFile.units.filter(u => !destUnitsById.has(u.id)); const allInUnitsWithDestinationUnit = this.normalizedTranslationSourceFile.units.filter(u => destUnitsById.has(u.id)); // collect (potentially) obsolete units (defer actual removal to allow for fuzzy matching..): const removeNodes = destFileContent.units.filter(destUnit => !inUnitsById.has(destUnit.id)); const result = new translationFileModels_1.TranslationFile([...destFileContent.units], destFileContent.sourceLang, destFileContent.targetLang, destFileContent.xmlHeader); for (const unit of allInUnitsWithDestinationUnit) { result.replaceUnit(unit, this.handle(unit, destUnitsById.get(unit.id), isSourceLang, removeNodes)); } if ((_a = this.options.fuzzyMatch) !== null && _a !== void 0 ? _a : true) { const bestMatchesIdToUnits = new Map(allInUnitsWithoutDestinationUnit.map((inUnit) => [inUnit.id, findCloseMatches(inUnit, removeNodes)])); while (bestMatchesIdToUnits.size) { const inUnitId = (_b = getMinScoreId(bestMatchesIdToUnits)) !== null && _b !== void 0 ? _b : Array.from(bestMatchesIdToUnits.keys())[0]; const bestMatch = (_c = bestMatchesIdToUnits.get(inUnitId)[0]) === null || _c === void 0 ? void 0 : _c.elem; const updated = this.handle(inUnitsById.get(inUnitId), bestMatch, isSourceLang, removeNodes); if (bestMatch) { result.replaceUnit(bestMatch, updated); } else { result.addUnit(updated); } bestMatchesIdToUnits.delete(inUnitId); if (bestMatch) { bestMatchesIdToUnits.forEach(x => { const i = x.findIndex(y => y.elem === bestMatch); if (i >= 0) { x.splice(i, 1); } }); } } } else { for (const unit of allInUnitsWithoutDestinationUnit) { result.addUnit(this.handle(unit, undefined, isSourceLang, removeNodes)); } } console.debug(`removing ${removeNodes.length} ids: ${removeNodes.map(n => n.id).join(', ')}`); return result.mapUnitsList(units => units.filter(unit => !removeNodes.includes(unit))); } normalize(source) { var _a, _b; if ((_a = this.options.collapseWhitespace) !== null && _a !== void 0 ? _a : true) { const adjusted = ((_b = this.options.prettyNestedTags) !== null && _b !== void 0 ? _b : true) && onlyXmlNodes(source) ? ` ${source.replace(/></g, '> <')} ` : source; return (0, stringUtils_1.doCollapseWhitespace)(adjusted); } else { return source; } } /** Syncs `unit` to `destUnit` or adds `unit` as new, if `destUnit` is not given. */ handle(unit, destUnit, isSourceLang, removeNodes) { var _a; if (destUnit) { let updatedDestUnit = destUnit; if (this.normalize(destUnit.source) !== this.normalize(unit.source)) { console.debug(`update element with id "${unit.id}" with new source: ${unit.source} (was: ${destUnit.source})`); const syncSourceLang = isSourceLang && destUnit.source === destUnit.target; // sync source language only if target is unchanged const syncTarget = syncSourceLang || isUntranslated(destUnit, this.initialTranslationState); updatedDestUnit = { ...destUnit, state: isSourceLang ? 'final' : this.initialTranslationState, source: unit.source, target: syncTarget && !(destUnit.target === undefined && this.options.newTranslationTargetsBlank === 'omit') ? unit.source : destUnit.target }; } if (destUnit.id !== unit.id) { console.debug(`matched unit with previous id "${destUnit.id}" to new id: "${unit.id}"`); this.idMapping[destUnit.id] = unit.id; removeNodes.splice(removeNodes.indexOf(destUnit), 1); updatedDestUnit = { ...updatedDestUnit, id: unit.id }; } updatedDestUnit = { ...updatedDestUnit, locations: unit.locations, meaning: unit.meaning, description: unit.description }; return updatedDestUnit; } else { console.debug(`adding element with id "${unit.id}"`); return { ...unit, target: this.options.newTranslationTargetsBlank === 'omit' ? undefined : (((_a = this.options.newTranslationTargetsBlank) !== null && _a !== void 0 ? _a : false) && !isSourceLang ? '' : unit.source), state: isSourceLang ? 'final' : this.initialTranslationState }; } } } exports.Merger = Merger; function isUntranslated(destUnit, initialState) { return destUnit.state === initialState && (destUnit.target === undefined || destUnit.source === destUnit.target); } function findCloseMatches(originUnit, destUnits) { const originText = originUnit.source; return destUnits .map(n => ({ elem: n, score: (0, strings_1.levenshtein)(originText, n.source) / originText.length })) .filter(x => x.score < FUZZY_THRESHOLD) .sort((a, b) => a.score - b.score); } function getMinScoreId(bestMatchesIdToUnits) { let minScoreId; let minScore = Number.MAX_VALUE; bestMatchesIdToUnits.forEach((x, id) => { if (x.length) { const score = x[0].score; if (score < minScore) { minScore = score; minScoreId = id; } } }); return minScoreId; }