diff --git a/share/qtcreator/CMakeLists.txt b/share/qtcreator/CMakeLists.txt index 6a18147f9fd..a0becf4281d 100644 --- a/share/qtcreator/CMakeLists.txt +++ b/share/qtcreator/CMakeLists.txt @@ -5,6 +5,7 @@ set(resource_directories indexer_preincludes jsonschemas lua-plugins + lua-lupdate modeleditor qmldesigner qmlicons diff --git a/share/qtcreator/lua-lupdate/README.md b/share/qtcreator/lua-lupdate/README.md new file mode 100644 index 00000000000..2ea70bd3d06 --- /dev/null +++ b/share/qtcreator/lua-lupdate/README.md @@ -0,0 +1,65 @@ +# lupdate.lua + +lupdate.lua allows you to update your .ts files from your .lua files. + +## Installation + +You need to install lua, luarocks and luafilesystem. + +On macOS: + +```sh +$ brew install lua luarocks +$ luarocks install luafilesystem +``` + +For other platforms see: [Download luarocks](https://github.com/luarocks/luarocks/wiki/Download) + +## Usage + +You need to add a "languages" key to your plugin spec file. + +```lua +--- In your plugin.lua file +return { + Name = "MyPlugin", + Version = "1.0.0", + languages = {"de", "fr", "en"}, + --- .... +} --[[@as QtcPlugin]] +``` + +Then run the lupdate.lua script in your plugin directory. Make sure that lupdate is in your PATH. + +```sh +$ # export PATH=$PATH:/path/to/Qt/bin +$ cd my-plugin +$ lua lupdate.lua +``` + +Once you have the .ts files you can use Qt Linguist to translate your strings. + +After translation you can run lrelease to generate the .qm files. + +```sh +$ cd ts +$ lrelease *.ts +``` + +## Background + +Since Qt's lupdate does not currently support lua files, the lupdate.lua script uses a trick +to make it work. It creates a temporary file for each lua file and adds a comment at the start +and end of the file: + +```lua +--- class Plugin { Q_OBJECT + +print(tr("Hello World")) + +--- } +``` + +That way lupdate thinks its inside a C++ Plugin and takes as context the name of the class, in this case "Plugin". + +It then starts the actual lupdate tool to update the .ts files. diff --git a/share/qtcreator/lua-lupdate/lupdate.lua b/share/qtcreator/lua-lupdate/lupdate.lua new file mode 100644 index 00000000000..5c1d3d5b705 --- /dev/null +++ b/share/qtcreator/lua-lupdate/lupdate.lua @@ -0,0 +1,99 @@ +--- luarocks install luafilesystem +local lfs = require "lfs" + +function string:endswith(suffix) + return self:sub(- #suffix) == suffix +end + +function findLUpdate() + if os.execute("lupdate -version 2>/dev/null") then + return "lupdate" + end + QtDir = os.getenv("QTDIR") + if QtDir then + local path = QtDir .. "/bin/lupdate" + if os.execute(path .. " -version 2>/dev/null") then + return path + end + end + return "lupdate" +end + +LUpdatePath = findLUpdate() +TmpFiles = {} + + +local curdir, err = lfs.currentdir() +if not curdir then + print("Error: " .. err) + return +end + +local folderName = curdir:match("([^/]+)$") +print("Working on: " .. curdir) +local pluginSpecName = folderName .. ".lua" + +--- Noop tr function +function tr(str) return str end + +local specScript, err = loadfile(curdir .. "/" .. pluginSpecName) +if not specScript then + print("Error: " .. err) + return +end + +local spec, err = specScript() + +if not spec then + print("Error: " .. err) + return +end + +if not spec.languages then + print("Error: No languages specified in plugin spec.") + return +end + +TrContext = spec.Name:gsub("[^a-zA-Z]", "_") + +for file in lfs.dir(".") do + if file ~= "." and file ~= ".." and file:endswith(".lua") and file ~= "lupdate.lua" then + local f = io.open(file, "r") + if f then + local contents = f:read("a") + local tmpname = os.tmpname() + local tf = io.open(tmpname, "w") + if tf then + tf:write("--- class " .. TrContext .. " { Q_OBJECT \n") + tf:write(contents) + tf:write("--- }\n") + tf:close() + table.insert(TmpFiles, tmpname) + end + end + end +end + +AllFiles = table.concat(TmpFiles, "\n") +LstFileName = os.tmpname() +local lstFile = io.open(LstFileName, "w") + +if lstFile then + lstFile:write(AllFiles) + lstFile:close() + + local allLangs = "" + for _, lang in ipairs(spec.languages) do + local name = "ts/" .. string.lower(folderName) .. "_" .. lang .. ".ts" + allLangs = allLangs .. name .. " " + end + + lfs.mkdir("ts") + os.execute(LUpdatePath .. " @" .. LstFileName .. " -ts " .. allLangs) + + --- Cleanup + os.remove(LstFileName) + for _, file in ipairs(TmpFiles) do + os.remove(file) + end +end diff --git a/src/plugins/lua/CMakeLists.txt b/src/plugins/lua/CMakeLists.txt index 38fe8f2d127..fba3459805c 100644 --- a/src/plugins/lua/CMakeLists.txt +++ b/src/plugins/lua/CMakeLists.txt @@ -17,6 +17,7 @@ add_qtc_plugin(Lua bindings/qtcprocess.cpp bindings/settings.cpp bindings/texteditor.cpp + bindings/translate.cpp bindings/utils.cpp luaengine.cpp luaengine.h diff --git a/src/plugins/lua/bindings/translate.cpp b/src/plugins/lua/bindings/translate.cpp new file mode 100644 index 00000000000..ccc99e9aa15 --- /dev/null +++ b/src/plugins/lua/bindings/translate.cpp @@ -0,0 +1,23 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "../luaengine.h" + +#include + +namespace Lua::Internal { + +void addTranslateModule() +{ + ::Lua::LuaEngine::autoRegister([](sol::state_view lua) { + const ScriptPluginSpec *pluginSpec = lua.get("PluginSpec"); + const QString trContext + = QString(pluginSpec->name).replace(QRegularExpression("[^a-zA-Z]"), "_"); + + lua["tr"] = [trContext](const char *text) -> QString { + return QCoreApplication::translate(trContext.toUtf8().constData(), text); + }; + }); +} + +} // namespace Lua::Internal diff --git a/src/plugins/lua/luaengine.cpp b/src/plugins/lua/luaengine.cpp index f96a795614c..2b921f900d4 100644 --- a/src/plugins/lua/luaengine.cpp +++ b/src/plugins/lua/luaengine.cpp @@ -192,8 +192,9 @@ expected_str LuaEngine::loadPlugin(const Utils::FilePath &path) return make_unexpected(contents.error()); sol::state lua; + lua["tr"] = [](const QString &str) { return str; }; - auto result = lua.safe_script( + sol::protected_function_result result = lua.safe_script( std::string_view(contents->data(), contents->size()), sol::script_pass_on_error, path.fileName().toUtf8().constData()); diff --git a/src/plugins/lua/luaplugin.cpp b/src/plugins/lua/luaplugin.cpp index eff6f0f91f5..b3036b34e0a 100644 --- a/src/plugins/lua/luaplugin.cpp +++ b/src/plugins/lua/luaplugin.cpp @@ -46,6 +46,7 @@ void addProcessModule(); void addQtModule(); void addSettingsModule(); void addTextEditorModule(); +void addTranslateModule(); void addUtilsModule(); class LuaJsExtension : public QObject @@ -262,6 +263,7 @@ public: addQtModule(); addSettingsModule(); addTextEditorModule(); + addTranslateModule(); addUtilsModule(); Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); }); diff --git a/src/plugins/lua/luapluginspec.cpp b/src/plugins/lua/luapluginspec.cpp index f00e48936bc..7a2d219ff13 100644 --- a/src/plugins/lua/luapluginspec.cpp +++ b/src/plugins/lua/luapluginspec.cpp @@ -6,6 +6,8 @@ #include "luaengine.h" #include "luatr.h" +#include + #include #include @@ -14,6 +16,7 @@ #include #include +#include Q_LOGGING_CATEGORY(luaPluginSpecLog, "qtc.lua.pluginspec", QtWarningMsg) @@ -45,6 +48,7 @@ LuaPluginSpec::LuaPluginSpec() expected_str LuaPluginSpec::create(const FilePath &filePath, sol::table pluginTable) { + const FilePath directory = filePath.parentDir(); std::unique_ptr pluginSpec(new LuaPluginSpec()); if (!pluginTable.get_or("setup", {})) @@ -63,8 +67,20 @@ expected_str LuaPluginSpec::create(const FilePath &filePath, so if (!r) return make_unexpected(r.error()); + const QString langId = Core::ICore::userInterfaceLanguage(); + FilePath path = directory / "ts" / QString("%1_%2.qm").arg(directory.fileName()).arg(langId); + + QTranslator *translator = new QTranslator(qApp); + bool success = translator->load(path.toFSPathString(), directory.toFSPathString()); + if (success) + qApp->installTranslator(translator); + else { + delete translator; + qCInfo(luaPluginSpecLog) << "No translation found"; + } + pluginSpec->setFilePath(filePath); - pluginSpec->setLocation(filePath.parentDir()); + pluginSpec->setLocation(directory); pluginSpec->d->pluginScriptPath = filePath; pluginSpec->d->printToOutputPane = pluginTable.get_or("printToOutputPane", false); diff --git a/src/plugins/lua/meta/qtc.lua b/src/plugins/lua/meta/qtc.lua index 72d94fe1cf3..01ff31bb971 100644 --- a/src/plugins/lua/meta/qtc.lua +++ b/src/plugins/lua/meta/qtc.lua @@ -1,5 +1,9 @@ ---@meta +---@class PluginSpec +---@field name string The name of the plugin. +---@field pluginDirectory FilePath The directory of the plugin. +PluginSpec = {} ---The global qtc object defined in the Lua plugin. ---@class qtc Qtc = {} @@ -24,6 +28,7 @@ Qtc = {} ---@field Mimetypes? string XML MIME-info for registering additional or adapting built-in MIME types. ---@field JsonWizardPaths? string[] A list of paths relative to the plugin location or paths to the Qt resource system that are searched for template-based wizards. ---@field printToOutputPane? boolean Whether the `print(...)` function should print to the output pane or not. ( Default: false ) +---@field languages? string[] A list of languages that the plugin supports. QtcPlugin = {} ---@class QtcPluginDependency