@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
JavaScript
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