UNPKG

@ui5/task-adaptation

Version:

Custom task for ui5-builder which allows building UI5 Flexibility Adaptation Projects for SAP BTP, Cloud Foundry environment

203 lines 8.17 kB
import Comparator from "./annotations/comparator/comparator.js"; import AnnotationDiffStructureError from "./model/annotationDiffStructureError.js"; import { getUniqueName } from "./util/commonUtil.js"; import { join } from "path/posix"; // Ensure standardized dir separators to ensure Windows compatibility // To generate keys, english language is more common, so compare all other // languages with it export class PropertyValue { value; isReference = false; qualifier; language; static OLD = "__old"; static NEW = "__new"; constructor(qualifier, language, value) { this.qualifier = qualifier; this.language = language; this.value = value; } get isOld() { return this.qualifier === PropertyValue.OLD; } set(diff, references) { this.value = diff[this.qualifier]; this.isReference = references.has(diff[this.qualifier]); } static oldFrom(language, value = "") { return new PropertyValue(PropertyValue.OLD, language, value); } static newFrom(language, value = "") { return new PropertyValue(PropertyValue.NEW, language, value); } } export class I18nFileContent { properties = new Map(); constructor(languages) { languages.forEach(language => this.properties.set(language, new Map())); } add(prop, key) { this.getOrCreateLanguageContent(prop.language).set(key, prop.value); this.initKeyValuesForOtherLanguages(prop, key); } initKeyValuesForOtherLanguages(prop, key) { // "", EN, EN_US, DE, FR // If we already compared 2 languages and saved translations, we assume // that we have generated all property keys, but suddenly 3d language or // further contain translation which was not present in previous // languages we need to update all other languages then. These values // will be overwritten later by actual values of the language: // this.getOrCreateLanguageContent(prop.language).set(key, prop.value); if (prop.isOld) { this.properties.forEach((keyValue, language) => { if (language !== prop.language && !keyValue.has(key)) { keyValue.set(key, prop.value); } }); } } hasTranslations() { // this.properties.size can be 0 or 2+, it can't be 1 return [...this.properties.values()].some(keyValue => keyValue.size > 0); } getOrCreateLanguageContent(language) { if (!this.properties.has(language)) { this.properties.set(language, new Map()); } return this.properties.get(language); } createFiles(i18nPathName) { const files = new Map(); if (this.hasTranslations()) { this.properties.forEach((i18nLines, language) => { let filename = "i18n"; if (language.i18n) { filename += "_" + language.i18n; } const filepath = join(i18nPathName, filename + ".properties"); files.set(filepath, [...i18nLines].map(([key, value]) => `${key}=${value}`).join("\n")); }); } return files; } } export default class I18nManager { modelName; appVariantId; references = new Map(); existingKeys = new Set(); i18nFileContent; constructor(modelName, appVariantId, languages) { this.modelName = modelName; this.appVariantId = appVariantId; this.i18nFileContent = new I18nFileContent(languages); } processDiff(properties, previousLanguage, currentLanguage) { // json-diff uses __old and __new value as diff, // so we assign it with languages which were in comparison const propertyValues = [ PropertyValue.oldFrom(previousLanguage), PropertyValue.newFrom(currentLanguage) ]; for (const property of properties) { this.replaceWithModelReference(property, propertyValues); } } createFiles(i18nPathName) { return this.i18nFileContent.createFiles(i18nPathName); } async populateTranslations(annotationJsons) { /* We compare annotations. Diff gives us: { "string": { "__old": "A" "__new": "B" } } we generate key and extract to properties file. Now it looks so: { "string": "{@i18n>id_KEY}" } and we compare this with next language and diff gives us: { "string": { "__old": "{@i18n>id_KEY}" "__new": "C" } } we use already generate key id_KEY and extract translation value to properties file again. And then compare with the next language and so on and so on */ let defaultAnnotation = I18nManager.extractDefaultLanguageAnnotation(annotationJsons); if (annotationJsons.size > 0 && defaultAnnotation) { defaultAnnotation = await this.populate([...annotationJsons], defaultAnnotation); } return defaultAnnotation; } async populate(annotationJsons, defaultAnnotation) { let p = defaultAnnotation; for (const c of annotationJsons.map(([language, json]) => ({ language, json }))) { try { const comparator = new Comparator(await p.json, await c.json); const pDiff = comparator.compare(); this.processDiff(pDiff.properties, p.language, c.language); p.json = pDiff.json; } catch (error) { if (error instanceof AnnotationDiffStructureError) { throw new Error(`The structure of the OData annotation xml of language '${p.language}' is different from xml of language '${c.language}' near element: ${error.message}`); } throw error; } } return p; } static extractDefaultLanguageAnnotation(annotationJsons) { let json = null; for (const [language, json] of annotationJsons) { if (language.isDefault) { annotationJsons.delete(language); return { json, language }; } } const language = [...annotationJsons.keys()][0]; json = annotationJsons.get(language); annotationJsons.delete(language); return { json, language }; } replaceWithModelReference({ object, property }, propertyValues) { const diff = object[property]; this.initPropertyValues(diff, propertyValues); // If there are already generated key, like on step 3. above comment, we // take it (from __old), so we don't need to generate new const propReference = propertyValues.find(prop => prop.isReference); let reference = propReference?.value ?? this.createReference(diff.__old); object[property] = reference; // Other values, which are not references, are extracted to .properties const key = this.references.get(reference); if (key) { propertyValues.filter(prop => !prop.isReference).forEach(prop => this.i18nFileContent.add(prop, key)); } } createReference(value) { const key = this.appVariantId + "_" + this.getUniqueKeyForValue(value); const reference = `{${this.modelName}>${key}}`; this.references.set(reference, key); return reference; } getUniqueKeyForValue(value) { if (typeof value !== "string") { throw new Error("Failed to create unique key from: " + JSON.stringify(value)); } const propertyName = value.replace(/\W/gi, "_").toUpperCase(); const key = getUniqueName([...this.existingKeys.keys()], propertyName); this.existingKeys.add(key); return key; } hasTranslations() { return this.i18nFileContent.hasTranslations(); } initPropertyValues(diff, propertyValues) { propertyValues.forEach(prop => prop.set(diff, this.references)); } } //# sourceMappingURL=i18nManager.js.map