UNPKG

ilib-loctool-webos-qml

Version:
422 lines (362 loc) 15.8 kB
/* * QMLFile.js - plugin to extract resources from a QML source code file * * Copyright (c) 2020-2023, 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"); /** * Create a new QML file with the given path name and within * the given project. * * @param {Project} project the project object * @param {String} pathName path to the file relative to the root * of the project file * @param {FileType} type the file type of this instance */ var QMLFile = function(props) { this.project = props.project; this.pathName = props.pathName; this.type = props.type; this.API = props.project.getAPI(); this.logger = this.API.getLogger("loctool.plugin.webOSQmlFile"); this.set = this.API.newTranslationSet(this.project ? this.project.sourceLocale : "zxx-XX"); }; /** * Unescape the string to make the same string that would be * in memory in the target programming language. * * @static * @param {String} string the string to unescape * @returns {String} the unescaped string */ QMLFile.unescapeString = function(string) { var unescaped = string; unescaped = unescaped. replace(/^\\\\/, "\\"). // unescape backslashes replace(/([^\\])\\\\/g, "$1\\"). replace(/([^\\])\\"/g, '$1"'). replace(/\\"/g, '"'). replace(/\\'/g, "'"). replace(/\\n/g, "\n"); return unescaped; }; /** * Use a key for the given string as it is. Not manipulating at all. * * @private * @param {String} source the source string to make a resource * key for * @returns {String} a unique key for this string */ QMLFile.prototype.makeKey = function(source) { return QMLFile.unescapeString(source); }; /** * Clean up i18n comments to be displayed neatly * * @private * @param {String} string the string to clean * @returns {String} the cleaned string */ QMLFile.trimComment = function(commentString) { if (!commentString) return; var trimComment = commentString. replace(/\s*\*\//, ""). replace(/\s*\:\s*/, ""). replace(/\/\s*\**\s*/, ""). replace(/\s*\*\s*/, ""). trim(); return trimComment; } /** * Remove single and multi-lines comments except for i18n comment style. * * @private * @param {String} string the string to clean * @returns {String} the cleaned string */ QMLFile.removeCommentLines = function(data) { if (!data) return; // comment style: // , /* */ single, multi line var trimData = data.replace(/\r(\n)*/g, "\n"). // newline character for window replace(/\/\/(?!\:|\~)\s*((?!i18n).)*[$/\n]/g, ""). replace(/\/\*+([^*]|\*(?!\/))*\*+\//g, ""). replace(/\/\*(.*)\*\//g, ""); return trimData; }; QMLFile.makeContextValue = function(fullpath) { if (!fullpath) return; var path = fullpath.replace(/^.*[\\\/]/, '').replace(/\.(qml|js)/, ""); return path; }; QMLFile.searchbyLine = function(data, matchString) { var lineNum = -1; for (var i=0; i < data.length; i++) { if (data[i].includes(matchString)) { lineNum = i; return lineNum; } } return lineNum; }; QMLFile.extractComments = function(data) { if (!data) return; var result = undefined; var comment, extraComment; data.forEach(function(item) { comment = reI18nMainComment.exec(item); extraComment = reI18nExtraComment.exec(item); if (comment) { result = comment[1]; } if (extraComment) { result = (typeof result == "undefined" ? extraComment[1] : result.concat(extraComment[1])); } }); return result; }; var reqsTrString = new RegExp(/\b(qsTr|qsTrNoOp|QT_TR_NOOP|QT_TR_N_NOOP)\s*\(\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*\)/g); var reqsTrWithDisambiguation = new RegExp(/\b(qsTr|qsTrNoOp|QT_TR_NOOP|QT_TR_N_NOOP)\s*\(\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*,\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*\)/g); var reqsTranslateString = new RegExp(/\b(qsTranslate|qsTranslateNoOp|QT_TRANSLATE_NOOP|QT_TRANSLATE_NOOP3|QT_TRANSLATE_N_NOOP)\s*\(\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*,\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*\)/g); var reqsTranslateStringWithDisambiguation = new RegExp(/\b(qsTranslate|qsTranslateNoOp|QT_TRANSLATE_NOOP3)\s*\(\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*,\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*,\s*("((\\"|[^"])*)"|'((\\'|[^'])*)')\s*\)/g); var reI18nwebOSComment = new RegExp(/\/(\*|\/)\s*i18n\s*(.*)($|\*\/)/); var reI18nMainComment = new RegExp(/\/\/:\s+(.*)$/); var reI18nExtraComment = new RegExp(/\/\/~\s+(.*)$/); /** * Parse the data string looking for the localizable strings and add them to the * project's translation set. * @param {String} data the string to parse */ QMLFile.prototype.parse = function(data) { this.logger.debug("Extracting strings from " + this.pathName); data = QMLFile.removeCommentLines(data); var parsebyLine = data.split("\n"); this.resourceIndex = 0; var match, key; var paramContext = undefined; // To extract resBundle_qsTr() reqsTrString.lastIndex = 0; // just to be safe var result = reqsTrString.exec(data); while (result && result.length > 3 && result[2]) { match = (result[2][0] === '"')? result[3]: result[5]; var comment = undefined; var preLines = []; if (match && match.length) { this.logger.trace("Found string key: " + this.makeKey(match) + ", string: '" + match + "'"); var last = data.indexOf('\n', reqsTrString.lastIndex); last = (last === -1) ? data.length : last; var line = data.substring(reqsTrString.lastIndex, last); commentwebOSResult = reI18nwebOSComment.exec(line); comment = ((commentwebOSResult && commentwebOSResult.length > 1) ? commentwebOSResult[2] : undefined); if (!comment) { // Extract general i18n comment following qml spec var lineNum = QMLFile.searchbyLine(parsebyLine, match); preLines.push(parsebyLine[lineNum-2]); preLines.push(parsebyLine[lineNum-1]); comment = QMLFile.extractComments(preLines); } match = QMLFile.unescapeString(match); var params = { resType: "string", project: this.project.getProjectId(), key: match, sourceLocale: this.project.sourceLocale, source: match, autoKey: true, pathName: this.pathName, state: "new", comment: QMLFile.trimComment(comment), datatype: this.type.datatype, index: this.resourceIndex++, context: QMLFile.makeContextValue(this.pathName) } var r = this.API.newResource(params); /*var r = this.API.newResource();*/ this.set.add(r); } else { this.logger.warn("Warning: Bogus empty string in get string call: "); this.logger.warn("... " + data.substring(result.index, reGetString.lastIndex) + " ..."); } result = reqsTrString.exec(data); } // To extract resBundle_qsTr() with disambiguation(key) reqsTrWithDisambiguation.lastIndex = 0; // just to be safe var result = reqsTrWithDisambiguation.exec(data); while (result && result.length > 1 && result[2]) { match = (result[2][0] === '"')? result[3]: result[5]; key = (result[7][0] === '"')? result[8]: result[10]; var comment = undefined; var preLines = []; if (match && match.length) { this.logger.trace("Found string key: " + this.makeKey(match) + ", string: '" + match + "'"); var last = data.indexOf('\n', reqsTrWithDisambiguation.lastIndex); last = (last === -1) ? data.length : last; var line = data.substring(reqsTrWithDisambiguation.lastIndex, last); commentwebOSResult = reI18nwebOSComment.exec(line); comment = ((commentwebOSResult && commentwebOSResult.length > 1) ? commentwebOSResult[2] : undefined); if (!comment) { // Extract general i18n comment following qml spec var lineNum = QMLFile.searchbyLine(parsebyLine, match); preLines.push(parsebyLine[lineNum-2]); preLines.push(parsebyLine[lineNum-1]); comment = QMLFile.extractComments(preLines); } match = QMLFile.unescapeString(match); key = QMLFile.unescapeString(key); var params = { resType: "string", project: this.project.getProjectId(), key: key, sourceLocale: this.project.sourceLocale, source: match, autoKey: true, pathName: this.pathName, state: "new", comment: QMLFile.trimComment(comment), datatype: this.type.datatype, index: this.resourceIndex++, context: QMLFile.makeContextValue(this.pathName) }; var r = this.API.newResource(params); this.set.add(r); } else { this.logger.warn("Warning: Bogus empty string in get string call: "); this.logger.warn("... " + data.substring(result.index, reqsTrWithDisambiguation.lastIndex) + " ..."); } result = reqsTrWithDisambiguation.exec(data); } // To extract resBundle_qsTranslate() reqsTranslateString.lastIndex = 0; // just to be safe var result = reqsTranslateString.exec(data); while (result && result.length > 7 && result[7]) { match = (result[7][0] === '"')? result[8]: result[10]; var comment = undefined; var preLines = []; paramContext = (result[2][0] === '"')? result[3]: result[5]; if (match && match.length) { this.logger.trace("Found string key: " + this.makeKey(match) + ", string: '" + match + "'"); var last = data.indexOf('\n', reqsTranslateString.lastIndex); last = (last === -1) ? data.length : last; var line = data.substring(reqsTranslateString.lastIndex, last); commentwebOSResult = reI18nwebOSComment.exec(line); comment = ((commentwebOSResult && commentwebOSResult.length > 1) ? commentwebOSResult[2] : undefined); if (!comment) { // Extract general i18n comment following qml spec var lineNum = QMLFile.searchbyLine(parsebyLine, match); preLines.push(parsebyLine[lineNum-2]); preLines.push(parsebyLine[lineNum-1]); comment = QMLFile.extractComments(preLines); } match = QMLFile.unescapeString(match); var params = { resType: "string", project: this.project.getProjectId(), key: match, sourceLocale: this.project.sourceLocale, source: match, autoKey: true, pathName: this.pathName, state: "new", comment: QMLFile.trimComment(comment), datatype: this.type.datatype, index: this.resourceIndex++, context: paramContext || QMLFile.makeContextValue(this.pathName) }; var r = this.API.newResource(params); this.set.add(r); } else { this.logger.warn("Warning: Bogus empty string in get string call: "); this.logger.warn("... " + data.substring(result.index, reqsTranslateString.lastIndex) + " ..."); } result = reqsTranslateString.exec(data); } // To extract resBundle_qsTranslate() with disambiguation(key) reqsTranslateStringWithDisambiguation.lastIndex = 0; // just to be safe var result = reqsTranslateStringWithDisambiguation.exec(data); while (result && result.length > 7 && result[7]) { match = result[4]; key = result[6]; match = (result[7][0] === '"')? result[8]: result[10]; key = (result[12][0] === '"')? result[13]: result[15]; paramContext = (result[2][0] === '"')? result[3]: result[5]; var comment = undefined; var preLines = []; if (match && match.length) { this.logger.trace("Found string key: " + this.makeKey(match) + ", string: '" + match + "'"); var last = data.indexOf('\n', reqsTranslateStringWithDisambiguation.lastIndex); last = (last === -1) ? data.length : last; var line = data.substring(reqsTranslateStringWithDisambiguation.lastIndex, last); commentwebOSResult = reI18nwebOSComment.exec(line); comment = ((commentwebOSResult && commentwebOSResult.length > 1) ? commentwebOSResult[2] : undefined); if (!comment) { // Extract general i18n comment following qml spec var lineNum = QMLFile.searchbyLine(parsebyLine, match); preLines.push(parsebyLine[lineNum-2]); preLines.push(parsebyLine[lineNum-1]); comment = QMLFile.extractComments(preLines); } match = QMLFile.unescapeString(match); var params ={ resType: "string", project: this.project.getProjectId(), key: key, sourceLocale: this.project.sourceLocale, source: match, autoKey: true, pathName: this.pathName, state: "new", comment: QMLFile.trimComment(comment), datatype: this.type.datatype, index: this.resourceIndex++, context: paramContext || QMLFile.makeContextValue(this.pathName) }; var r = this.API.newResource(params); this.set.add(r); } else { this.logger.warn("Warning: Bogus empty string in get string call: "); this.logger.warn("... " + data.substring(result.index, reqsTranslateStringWithDisambiguation.lastIndex) + " ..."); } result = reqsTranslateStringWithDisambiguation.exec(data); } }; /** * Extract all the localizable strings from the QML file and add them to the * project's translation set. */ QMLFile.prototype.extract = function() { this.logger.debug("Extracting strings from " + this.pathName); if (this.pathName) { var p = path.join(this.project.root, this.pathName); try { var data = fs.readFileSync(p, "utf-8"); if (data) { this.parse(data); } } catch (e) { this.logger.warn("Could not read file: " + p); } } }; /** * Return the set of resources found in the current QML file. * * @returns {TranslationSet} The set of resources found in the * current QML file. */ QMLFile.prototype.getTranslationSet = function() { return this.set; } // we don't localize or write c source files QMLFile.prototype.localize = function() {}; QMLFile.prototype.write = function() {}; module.exports = QMLFile;