Module to update ts files.

This commit is contained in:
Roman Telezhynskyi 2023-01-12 18:33:39 +02:00
parent 44b9e1b77c
commit 0a681d7f93
5 changed files with 407 additions and 0 deletions

205
qbs/modules/i18n/i18n.qbs Normal file
View File

@ -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"]
}
}

40
scripts/qbs_lupdate.sh Executable file
View File

@ -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 <root_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<NUMBER;i++)); do
tx pull -r valentina-project.measurements_p${i}${MEASUREMENTS_BRANCH}ts --mode=developer -f --skip -l "uk,de_DE,cs,he_IL,fr_FR,it_IT,nl,id,es,fi,en_US,en_CA,en_IN,ro_RO,zh_CN,pt_BR,el_GR,pl_PL" &
sleep 1
done
tx pull -r valentina-project.valentina_${VALENTINA_BRANCH}ts --mode=developer -f --skip &
sleep 1
tx pull -r valentina-project.measurements_p998${MEASUREMENTS_BRANCH}ts --mode=developer -f --skip -l "uk,de_DE,cs,he_IL,fr_FR,it_IT,nl,id,es,fi,en_US,en_CA,en_IN,ro_RO,zh_CN,pt_BR,el_GR,pl_PL" &
wait
# Resolve any changes to config
qbs resolve -d ../../build_translations modules.i18n.update:true
# Update local strings
qbs -d ../../build_translations -f ../valentina.qbs -p 'Translations' modules.i18n.update:true
qbs -d ../../build_translations -f ../valentina.qbs -p 'MTranslations' modules.i18n.update:true
end=$(date +%s)
runtime=$(python -c "print('Time passed %u:%02u seconds' % ((${end} - ${start})/60, (${end} - ${start})%60))")
echo $runtime

View File

@ -0,0 +1,72 @@
import qbs.FileInfo
Product {
Depends { name: "i18n" }
name: "MTranslations"
type: "ts"
builtByDefault: false
Group {
name: "Headers"
prefix: FileInfo.joinPaths(project.sourceDirectory, "src", FileInfo.pathSeparator())
files: [
"libs/vpatterndb/vtranslatemeasurements.h"
]
fileTags: "i18n.hpp"
}
Group {
name: "Sources"
prefix: FileInfo.joinPaths(project.sourceDirectory, "src", FileInfo.pathSeparator())
files: [
"libs/vpatterndb/vtranslatemeasurements.cpp"
]
fileTags: "i18n.src"
}
Group {
name: "Translations"
files: {
var files = [];
var locales = [
"uk_UA",
"de_DE",
"cs_CZ",
"he_IL",
"fr_FR",
"it_IT",
"nl_NL",
"id_ID",
"es_ES",
"fi_FI",
"en_US",
"en_CA",
"en_IN",
"ro_RO",
"zh_CN",
"pt_BR",
"el_GR",
"pl_PL"
];
var pmSystems = [
"p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", "p11", "p12", "p13", "p14", "p15",
"p16", "p17", "p18", "p19", "p20", "p21", "p22", "p23", "p24", "p25", "p26", "p27", "p28", "p29",
"p30", "p31", "p32", "p33", "p34", "p35", "p36", "p37", "p38", "p39", "p40", "p41", "p42", "p43",
"p44", "p45", "p46", "p47", "p48", "p49", "p50", "p51", "p52", "p53", "p54", "p998"
]
for (var i = 0; i < pmSystems.length; i++) {
files.push("measurements_" + pmSystems[i] + ".ts");
for (var j = 0; j < locales.length; j++) {
files.push("measurements_" + pmSystems[i] + "_" + locales[j] + ".ts");
}
}
return files;
}
fileTags: "i18n.ts"
}
}

View File

@ -0,0 +1,88 @@
import qbs.FileInfo
Product {
Depends { name: "i18n" }
name: "Translations"
type: "ts"
builtByDefault: false
Group {
name: "Headers"
prefix: FileInfo.joinPaths(project.sourceDirectory, "src", FileInfo.pathSeparator())
files: [
"app/**/*.h",
"app/**/*.hpp",
"libs/**/*.h",
"libs/**/*.hpp"
]
excludeFiles: [
"libs/vpatterndb/vtranslatemeasurements.h"
]
fileTags: "i18n.hpp"
}
Group {
name: "Sources"
prefix: FileInfo.joinPaths(project.sourceDirectory, "src", FileInfo.pathSeparator())
files: [
"app/**/*.cpp",
"app/**/*.js",
"app/**/*.qml",
"libs/**/*.cpp",
"libs/**/*.js",
"libs/**/*.qml"
]
excludeFiles: [
"libs/vpatterndb/vtranslatemeasurements.cpp"
]
fileTags: "i18n.src"
}
Group {
name: "Forms"
prefix: FileInfo.joinPaths(project.sourceDirectory, "src", FileInfo.pathSeparator())
files: [
"app/**/*.ui",
"libs/**/*.ui"
]
fileTags: "i18n.ui"
}
Group {
name: "Translations"
files: {
var files = [];
var locales = [
"uk_UA",
"de_DE",
"cs_CZ",
"he_IL",
"fr_FR",
"it_IT",
"nl_NL",
"id_ID",
"es_ES",
"fi_FI",
"en_US",
"en_CA",
"en_IN",
"ro_RO",
"zh_CN",
"pt_BR",
"el_GR",
"pl_PL"
];
files.push("valentina.ts");
for (var i = 0; i < locales.length; i++) {
files.push("valentina_" + locales[i] + ".ts");
}
return files;
}
fileTags: "i18n.ts"
}
}

View File

@ -3,6 +3,8 @@ Project {
minimumQbsVersion: "1.16"
references: [
"src/src.qbs",
"share/translations/translations.qbs",
"share/translations/measurements.qbs",
]
qbsSearchPaths: "qbs"