UNPKG

ilib-loctool-webos-qml

Version:
455 lines (417 loc) 20.5 kB
/* * QMLFileType.js - Represents a collection of QML files * * Copyright (c) 2020-2023, 2025 JEDLSoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ var fs = require("fs"); var path = require("path"); var QMLFile = require("./QMLFile.js"); var TSResourceFileType = require("ilib-loctool-webos-ts-resource"); var ResourceString = require("loctool/lib/ResourceString.js"); var pluginUtils = require("ilib-loctool-webos-common/utils.js"); var QMLFileType = function(project) { this.type = "x-qml"; this.datatype = "x-qml"; this.resourceType = "ts"; this.extensions = [ ".qml", ".js"]; this.isloadCommonData = false; this.project = project; this.API = project.getAPI(); this.extracted = this.API.newTranslationSet(project.getSourceLocale()); this.newres = this.API.newTranslationSet(project.getSourceLocale()); this.pseudo = this.API.newTranslationSet(project.getSourceLocale()); this.logger = this.API.getLogger("loctool.plugin.webOSQmlFileType"); if (typeof project.pseudoLocale === "string") { project.pseudoLocale = [project.pseudoLocale]; } // generate all the pseudo bundles we'll need if (project.pseudoLocale && Array.isArray(project.pseudoLocale)) { this.pseudos = {}; project.pseudoLocale && project.pseudoLocale.forEach(function(locale) { var pseudo = this.API.getPseudoBundle(locale, this, project); if (pseudo) { this.pseudos[locale] = pseudo; } }.bind(this)); } if (project.pseudoLocales && typeof project.pseudoLocales == 'object') { this.pseudos = {}; for (locale in project.pseudoLocales) { var pseudo = this.API.getPseudoBundle(locale, this, project); if (pseudo) { this.pseudos[locale] = pseudo; } } } // for use with missing strings if (!project.settings.nopseudo) { this.missingPseudo = this.API.getPseudoBundle(project.pseudoLocale, this, project); } if (project.settings.webos && project.settings.webos["commonXliff"]){ this.commonPath = project.settings.webos["commonXliff"]; } }; /** * Return true if the given path is a qml or js file and is handled * by the current file type. * * @param {String} pathName path to the file being questions * @returns {boolean} true if the path is a qml file, or false * otherwise */ QMLFileType.prototype.handles = function(pathName) { this.logger.debug("QMLFileType handles " + pathName + "?"); var ret = false; if ((pathName.length > 4 && pathName.substring(pathName.length - 4) === ".qml") || (pathName.length > 3 && pathName.substring(pathName.length - 3) === ".js")) { ret = true; } this.logger.debug(ret ? "Yes" : "No"); return ret; }; QMLFileType.prototype.name = function() { return "QML File Type"; }; QMLFileType.prototype._getTranslationWithNewline = function(db, translated, res, locale, isCommon) { var newtranslated = translated; var newlinerestored = res.getSource().replace(/\n/g, "\\n"); this.logger.debug("_getTranslationWithNewline: " + newlinerestored); var deviceType = pluginUtils.getDeviceType(this.project.settings); var newres = res.clone(); newres.source = newlinerestored; newres.sourcehash = this.API.utils.hashKey(newlinerestored); var manipulateKey; if (isCommon) { manipulateKey = ResourceString.hashKey(this.commonPrjName, locale, res.getKey().replace(/\n/g, "\\n"), this.commonPrjType, res.getFlavor()); } else { manipulateKey = newres.cleanHashKeyForTranslation(locale).replace(newres.getContext(), "").replace(/\n/g, "\\n"); } db.getResourceByCleanHashKey(manipulateKey, function(err, translated) { if (translated) { translated.source = res.source; translated.reskey = res.reskey; var target = pluginUtils.getTarget(translated, deviceType); translated.setTarget(target.replace(/\\n/g, "\n")); newtranslated = translated; } }.bind(this)); return newtranslated; } /** * Write out the aggregated resources for this file type. In * some cases, the string are written out to a common resource * file, and in other cases, to a type-specific resource file. * In yet other cases, nothing is written out, as the each of * the files themselves are localized individually, so there * are no aggregated strings. * @param {TranslationSet} translations the set of translations from the * repository * @param {Array.<String>} locales the list of locales to localize to */ QMLFileType.prototype.write = function(translations, locales) { // distribute all the resources to their resource files // and then let them write themselves out var resFileType = this.project.getResourceFileType(this.resourceType); var deviceType = pluginUtils.getDeviceType(this.project.settings); var newlineChar = '\n'; var res, file, resources = this.extracted.getAll(), db = this.project.db, translationLocales = locales.filter(function(locale) { return locale !== this.project.sourceLocale && locale !== this.project.pseudoLocale; }.bind(this)); var customInheritLocale; if ((typeof(translations) !== 'undefined') && (typeof(translations.getProjects()) !== 'undefined') && (translations.getProjects().indexOf("common") !== -1)) { this.isloadCommonData = true; } if (this.commonPath) { if (!this.isloadCommonData) { this._loadCommonXliff(); this.isloadCommonData = true; } else { this._addComonDatatoTranslationSet(translations); } } for (var i = 0; i < resources.length; i++) { res = resources[i]; // for each extracted string, write out the translations of it translationLocales.forEach(function(locale) { this.logger.trace("Localizing QML strings to " + locale); customInheritLocale = this.project.getLocaleInherit(locale); db.getResourceByCleanHashKey(res.cleanHashKeyForTranslation(locale), function(err, translated) { var r = translated; if (!translated) { var manipulateKey = res.cleanHashKeyForTranslation(locale).replace(res.getContext(), ""); db.getResourceByCleanHashKey(manipulateKey, function(err, translated) { var r = translated; if (!translated && res.getSource().includes(newlineChar)) { translated = this._getTranslationWithNewline(db, translated, res, locale, false); r = translated; } if (!translated && this.isloadCommonData) { var manipulateKey = ResourceString.hashKey(this.commonPrjName, locale, res.getKey(), this.commonPrjType, res.getFlavor()); db.getResourceByCleanHashKey(manipulateKey, function(err, translated) { if (!translated && res.getSource().includes(newlineChar)) { translated = this._getTranslationWithNewline(db, translated, res, locale, true); r = translated; } if (translated) { pluginUtils.addResource(resFileType, translated, res, locale, undefined, deviceType); } else if(!translated && customInheritLocale){ var manipulateKey = res.cleanHashKeyForTranslation(customInheritLocale).replace(res.getContext(), ""); db.getResourceByCleanHashKey(manipulateKey, function(err, translated) { if (!translated && res.getSource().includes(newlineChar)) { translated = this._getTranslationWithNewline(db, translated, res, customInheritLocale, false); r = translated; } if (!translated){ var manipulateKey = ResourceString.hashKey(this.commonPrjName, customInheritLocale, res.getKey(), this.commonPrjType, res.getFlavor()); db.getResourceByCleanHashKey(manipulateKey, function(err, translated) { if (!translated && res.getSource().includes(newlineChar)) { translated = this._getTranslationWithNewline(db, translated, res, customInheritLocale, true); r = translated; } if (translated){ pluginUtils.addResource(resFileType, translated, res, locale, undefined, deviceType); } else { pluginUtils.addNewResource(this.newres, res, locale); } }.bind(this)); } else { pluginUtils.addResource(resFileType, translated, res, locale, undefined, deviceType); } }.bind(this)); } else { pluginUtils.addNewResource(this.newres, res, locale); } }.bind(this)); } else if (!translated && customInheritLocale) { var manipulateKey = res.cleanHashKeyForTranslation(customInheritLocale).replace(res.getContext(),""); db.getResourceByCleanHashKey(manipulateKey, function(err, translated) { var r = translated; if (!translated && res.getSource().includes(newlineChar)) { translated = this._getTranslationWithNewline(db, translated, res, customInheritLocale, false); r = translated; } if (translated){ pluginUtils.addResource(resFileType, translated, res, locale, undefined, deviceType); } else { pluginUtils.addNewResource(this.newres, res, locale); } }.bind(this)); } else if (!translated || ( this.API.utils.cleanString(res.getSource()) !== this.API.utils.cleanString(r.getSource()) && this.API.utils.cleanString(res.getSource()) !== this.API.utils.cleanString(r.getKey()))) { if (r) { this.logger.trace("extracted source: " + this.API.utils.cleanString(res.getSource())); this.logger.trace("translation source: " + this.API.utils.cleanString(r.getSource())); } pluginUtils.addNewResource(this.newres, res, locale); } else { if (res.reskey != r.reskey) { // if reskeys don't match, we matched on cleaned string. //so we need to overwrite reskey of the translated resource to match r = r.clone(); r.reskey = res.reskey; } var storeResource = r.clone(); // To keep the extracted source's filename. If not, xliff file name will be wrote to ts resource file. storeResource.pathName = res.getPath(); storeResource.context = res.getContext() || res.getPath().replace(/^.*[\\\/]/, '').replace(/\.(qml|js)/, ""); storeResource.setTarget(pluginUtils.getTarget(r, deviceType)); file = resFileType.getResourceFile(locale); file.addResource(storeResource); this.logger.trace("Added " + r.reskey + " to " + file.pathName); } }.bind(this)); } else { if (( this.API.utils.cleanString(res.getSource()) !== this.API.utils.cleanString(r.getSource()) && this.API.utils.cleanString(res.getSource()) !== this.API.utils.cleanString(r.getKey()))) { if (r) { this.logger.trace("extracted source: " + this.API.utils.cleanString(res.getSource())); this.logger.trace("translation source: " + this.API.utils.cleanString(r.getSource())); } pluginUtils.addNewResource(this.newres, res, locale); } else { if (res.reskey != r.reskey) { // if reskeys don't match, we matched on cleaned string. //so we need to overwrite reskey of the translated resource to match r = r.clone(); r.reskey = res.reskey; } var storeResource = r.clone(); storeResource.setTarget(pluginUtils.getTarget(r, deviceType)); // To keep the extracted source's filename. If not, xliff file name will be wrote to ts resource file. storeResource.pathName = res.getPath(); storeResource.context = res.getContext() || res.getPath().replace(/^.*[\\\/]/, '').replace(/\.(qml|js)/, ""); storeResource.setTarget(pluginUtils.getTarget(r, deviceType)); file = resFileType.getResourceFile(locale); file.addResource(storeResource); this.logger.trace("Added " + r.reskey + " to " + file.pathName); } } }.bind(this)); }.bind(this)); } resources = []; var typeValue = this.type.replace("x-", ""); if (this.project.settings[typeValue] === undefined || (this.project.settings[typeValue] && !(this.project.settings[typeValue].disablePseudo === true))){ resources = this.pseudo.getAll().filter(function(resource) { return resource.datatype === this.datatype; }.bind(this)); } for (var i = 0; i < resources.length; i++) { res = resources[i]; if (res.getTargetLocale() !== this.project.sourceLocale && res.getSource() !== pluginUtils.getTarget(res, deviceType)) { file = resFileType.getResourceFile(res.getTargetLocale()); res.setTarget(pluginUtils.getTarget(res, deviceType)); file.addResource(res); this.logger.trace("Added " + res.reskey + " to " + file.pathName); } } }; QMLFileType.prototype._addComonDatatoTranslationSet = function(tsdata) { var prots = this.project.getRepository().getTranslationSet(); var commonts = tsdata.getBy({project: "common"}); if (commonts.length > 0){ this.commonPrjName = commonts[0].getProject(); this.commonPrjType = commonts[0].getDataType(); commonts.forEach(function(ts){ prots.add(ts); }.bind(this)); } } QMLFileType.prototype._loadCommonXliff = function() { if (fs.existsSync(this.commonPath)){ var list = fs.readdirSync(this.commonPath); } var xliffStyle = (this.project.settings && this.project.settings.xliffStyle) ? this.project.settings.xliffStyle : "webOS"; if (list && list.length !== 0) { list.forEach(function(file){ var commonXliff = this.API.newXliff({ sourceLocale: this.project.getSourceLocale(), project: this.project.getProjectId(), path: this.commonPath, style: xliffStyle }); var pathName = path.join(this.commonPath, file); var data = fs.readFileSync(pathName, "utf-8"); commonXliff.deserialize(data); var resources = commonXliff.getResources(); var localts = this.project.getRepository().getTranslationSet(); if (resources.length > 0){ this.commonPrjName = resources[0].getProject(); this.commonPrjType = resources[0].getDataType(); resources.forEach(function(res){ localts.add(res); }.bind(this)); } }.bind(this)); } }; QMLFileType.prototype.newFile = function(path) { return new QMLFile({ project: this.project, pathName: path, type: this }); }; QMLFileType.prototype.getDataType = function() { return this.datatype; }; QMLFileType.prototype.getResourceTypes = function() { return { "string": "SourceContextResourceString" }; }; /** * Return the name of the node module that implements the resource file type, or * the path to a QML file that implements the resource filetype. * @returns {Function|undefined} node module name or path, or undefined if this file type does not * need resource files */ QMLFileType.prototype.getResourceFileType = function() { return TSResourceFileType; }; /** * Return the translation set containing all of the extracted * resources for all instances of this type of file. This includes * all new strings and all existing strings. If it was extracted * from a source file, it should be returned here. * * @returns {TranslationSet} the set containing all of the * extracted resources */ QMLFileType.prototype.getExtracted = function() { return this.extracted; }; /** * Ensure that all resources collected so far have a pseudo translation. */ QMLFileType.prototype.generatePseudo = function(locale, pb) { var resources = this.extracted.getBy({ sourceLocale: pb.getSourceLocale() }); this.logger.trace("Found " + resources.length + " source resources for " + pb.getSourceLocale()); var deviceType = pluginUtils.getDeviceType(this.project.settings); resources.forEach(function(resource) { this.logger.trace("Generating pseudo for " + resource.getKey()); var res = resource.generatePseudo(locale, pb); if (res && res.getSource() !== pluginUtils.getTarget(res, deviceType)) { this.pseudo.add(res); } }.bind(this)); }; /** * Add the contents of the given translation set to the extracted resources * for this file type. * * @param {TranslationSet} set set of resources to add to the current set */ QMLFileType.prototype.addSet = function(set) { this.extracted.addSet(set); }; /** * Return the translation set containing all of the new * resources for all instances of this type of file. * * @returns {TranslationSet} the set containing all of the * new resources */ QMLFileType.prototype.getNew = function() { return this.newres; }; /** * Return the translation set containing all of the pseudo * localized resources for all instances of this type of file. * * @returns {TranslationSet} the set containing all of the * pseudo localized resources */ QMLFileType.prototype.getPseudo = function() { return this.pseudo; }; /** * Return the list of file name extensions that this plugin can * process. * * @returns {Array.<string>} the list of file name extensions */ QMLFileType.prototype.getExtensions = function() { return this.extensions; }; module.exports = QMLFileType;