diff --git a/qbs/modules/i18n/i18n.qbs b/qbs/modules/i18n/i18n.qbs new file mode 100644 index 000000000..333bed20b --- /dev/null +++ b/qbs/modules/i18n/i18n.qbs @@ -0,0 +1,205 @@ +import qbs.File +import qbs.FileInfo +import qbs.TextFile + +/** + This module generates 'i18n.pro' artifact, which then acts as an input for 'lupdate' program, which in turn produces + translation files, which are compiled by 'lrelease' program into 'qm' files, which can be loaded by an application. + */ +Module { + Depends { name: "Qt.core" } + + additionalProductTypes: ["i18n"] + + /* + Unfortunately you can not simply add empty files to the product, cause 'Qt.core' module has a rule, which calls 'lrelease' on + every 'ts' file in the product and 'lrelease' triggers error if these files are empty. Additionaly 'lupdate' also triggers + errors, when parsing 'pro' file. Instead this property can be used to create new translation file. + */ + property stringList additionalTranslations: [] + + // Explicitly trigger build even if build a product + property bool update: false + + // Build with legacy way though .pro file + property bool buildWithPro: true + + property string lupdateName: "lupdate" + + Rule { + condition: update && buildWithPro + multiplex: true + inputs: ["i18n.hpp", "i18n.src", "i18n.ui", "i18n.res", "i18n.ts"] + + prepare: { + var proCmd = new JavaScriptCommand(); + proCmd.description = 'generating ' + output.filePath; + proCmd.highlight = 'codegen'; + proCmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + try { + f.writeLine("lupdate_only {"); + if (inputs["i18n.hpp"] !== undefined) + for (var i = 0; i < inputs["i18n.hpp"].length; i++) + f.writeLine("HEADERS += " + FileInfo.relativePath(product.sourceDirectory, inputs["i18n.hpp"][i].filePath)); + f.writeLine(""); + if (inputs["i18n.src"] !== undefined) + for (var i = 0; i < inputs["i18n.src"].length; i++) + f.writeLine("SOURCES += " + FileInfo.relativePath(product.sourceDirectory, inputs["i18n.src"][i].filePath)); + f.writeLine(""); + if (inputs["i18n.ui"] !== undefined) + for (var i = 0; i < inputs["i18n.ui"].length; i++) + f.writeLine("FORMS += " + FileInfo.relativePath(product.sourceDirectory, inputs["i18n.ui"][i].filePath)); + f.writeLine(""); + // lupdate processes QML files that are listed in the .qrc file + if (inputs["i18n.res"] !== undefined) + for (var i = 0; i < inputs["i18n.res"].length; i++) + f.writeLine("RESOURCES += " + FileInfo.relativePath(product.sourceDirectory, inputs["i18n.res"][i].filePath)); + f.writeLine("}"); + + f.writeLine(""); + if (inputs["i18n.ts"] !== undefined) + for (var i = 0; i < inputs["i18n.ts"].length; i++) + f.writeLine("TRANSLATIONS += " + FileInfo.relativePath(product.sourceDirectory, inputs["i18n.ts"][i].filePath)); + for (var i = 0; i < product.i18n.additionalTranslations.length; i++) { + var targetDirectory = product.sourceDirectory + "/" + FileInfo.path(product.i18n.additionalTranslations[i]); + if (!File.exists(targetDirectory)) + console.error("Directory '" + targetDirectory + "' does not exists. Please create it."); + f.writeLine("TRANSLATIONS += " + product.i18n.additionalTranslations[i]); + } + } finally { + f.close(); + } + } + return [proCmd]; + } + + Artifact { + filePath: product.sourceDirectory + "/" + product.name + ".i18n.pro" + fileTags: ["i18n.pro"] + } + } + + Rule { + condition: buildWithPro + inputs: ["i18n.pro"] + + prepare: { + var lupdateName = product.i18n.lupdateName; + var cmdLupdate = new Command(product.Qt.core.binPath + '/' + lupdateName, ["-verbose", input.filePath]); + cmdLupdate.description = "Invoking '" + lupdateName + "' program"; + cmdLupdate.highlight = 'filegen'; + + var cmdClean = new JavaScriptCommand(); + cmdClean.description = "Removing " + input.fileName; + cmdClean.highlight = "filegen"; + cmdClean.sourceCode = function() { + File.remove(input.filePath); + } + return [cmdLupdate, cmdClean] + } + + outputFileTags: ["i18n"] + } + + Rule { + condition: update && !buildWithPro + multiplex: true + inputs: ["i18n.hpp", "i18n.src", "i18n.ui", "i18n.ts"] + + prepare: { + var proCmd = new JavaScriptCommand(); + proCmd.description = 'generating ' + output.filePath; + proCmd.highlight = 'codegen'; + proCmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + try { + // Since Qt 5.13 lupdate supports passing a project description in JSON file. For producing such a + // description from .pro file we can use new tool lprodump. But tehnically we don't need it. We can + // totally fake format. + // JSON file structure: + // Project ::= { + // string projectFile // Name of the project file. (required) + // string codec // Source code codec. Valid values are + // // currently "utf-16" or "utf-8" (default). + // string[] translations // List of .ts files of the project. (required) + // string[] includePaths // List of include paths. + // string[] sources // List of source files. (required) + // string[] excluded // List of source files, which are + // // excluded for translation. + // Project[] subProjects // List of sub-projects. + // } + // It seems all we need are projectFile, sources and translations options. + + var sources = []; + if (inputs["i18n.hpp"] !== undefined) + for (var i = 0; i < inputs["i18n.hpp"].length; i++) + sources.push(inputs["i18n.hpp"][i].filePath); + + if (inputs["i18n.src"] !== undefined) + for (var i = 0; i < inputs["i18n.src"].length; i++) + sources.push(inputs["i18n.src"][i].filePath); + + if (inputs["i18n.ui"] !== undefined) + for (var i = 0; i < inputs["i18n.ui"].length; i++) + sources.push(inputs["i18n.ui"][i].filePath); + + // lupdate processes QML files that are listed in the .qrc file + if (inputs["i18n.res"] !== undefined) + for (var i = 0; i < inputs["i18n.res"].length; i++) + sources.push(inputs["i18n.res"][i].filePath); + + var translations = []; + if (inputs["i18n.ts"] !== undefined) + for (var i = 0; i < inputs["i18n.ts"].length; i++) + translations.push(inputs["i18n.ts"][i].filePath); + + for (var i = 0; i < product.i18n.additionalTranslations.length; i++) { + var targetDirectory = product.sourceDirectory + "/" + FileInfo.path(product.i18n.additionalTranslations[i]); + if (!File.exists(targetDirectory)) + console.error("Directory '" + targetDirectory + "' does not exists. Please create it."); + translations.push(product.i18n.additionalTranslations[i]); + } + + var project = { + projectFile: "", // Looks like can be empty + sources: sources.sort(), + translations: translations.sort() + }; + + f.write(JSON.stringify([project], null, 2)); + } finally { + f.close(); + } + } + return [proCmd]; + } + + Artifact { + filePath: product.sourceDirectory + "/" + product.name + ".i18n.json" + fileTags: ["i18n.json"] + } + } + + Rule { + condition: !buildWithPro + inputs: ["i18n.json"] + + prepare: { + var lupdateName = product.i18n.lupdateName; + var cmdLupdate = new Command(product.Qt.core.binPath + '/' + lupdateName, ["-verbose", "-project", input.filePath]); + cmdLupdate.description = "Invoking '" + lupdateName + "' program"; + cmdLupdate.highlight = 'filegen'; + + var cmdClean = new JavaScriptCommand(); + cmdClean.description = "Removing " + input.fileName; + cmdClean.highlight = "filegen"; + cmdClean.sourceCode = function() { + File.remove(input.filePath); + } + return [cmdLupdate, cmdClean] + } + + outputFileTags: ["i18n"] + } +} diff --git a/scripts/qbs_lupdate.sh b/scripts/qbs_lupdate.sh new file mode 100755 index 000000000..f2b79d75c --- /dev/null +++ b/scripts/qbs_lupdate.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Run this script if you want to find and update all strings in the code. +# Please, run this script from folder /scripts. + +start=$(date +%s) + +# Download all translations from transifex.com. +cd ../share/translations +RESOURCES=`find . -regextype sed -regex ".*/measurements_p[0-9]\{1,2\}\.ts"` +cd ../../scripts + +# Empty means unstable branch +MEASUREMENTS_BRANCH='' # For example _05x +VALENTINA_BRANCH='' # for example 05x + +NUMBER=( $RESOURCES ) +NUMBER=${#NUMBER[@]} + +# Certant languages like he_IL and zh_CN are not supported by math parser +for ((i=0;i