forked from qt-creator/qt-creator
Lua: Add support for translation
Change-Id: I5398480233c830bb08c641bc8193b068fb037032 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -5,6 +5,7 @@ set(resource_directories
|
|||||||
indexer_preincludes
|
indexer_preincludes
|
||||||
jsonschemas
|
jsonschemas
|
||||||
lua-plugins
|
lua-plugins
|
||||||
|
lua-lupdate
|
||||||
modeleditor
|
modeleditor
|
||||||
qmldesigner
|
qmldesigner
|
||||||
qmlicons
|
qmlicons
|
||||||
|
65
share/qtcreator/lua-lupdate/README.md
Normal file
65
share/qtcreator/lua-lupdate/README.md
Normal file
@@ -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.
|
99
share/qtcreator/lua-lupdate/lupdate.lua
Normal file
99
share/qtcreator/lua-lupdate/lupdate.lua
Normal file
@@ -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
|
@@ -17,6 +17,7 @@ add_qtc_plugin(Lua
|
|||||||
bindings/qtcprocess.cpp
|
bindings/qtcprocess.cpp
|
||||||
bindings/settings.cpp
|
bindings/settings.cpp
|
||||||
bindings/texteditor.cpp
|
bindings/texteditor.cpp
|
||||||
|
bindings/translate.cpp
|
||||||
bindings/utils.cpp
|
bindings/utils.cpp
|
||||||
luaengine.cpp
|
luaengine.cpp
|
||||||
luaengine.h
|
luaengine.h
|
||||||
|
23
src/plugins/lua/bindings/translate.cpp
Normal file
23
src/plugins/lua/bindings/translate.cpp
Normal file
@@ -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 <QCoreApplication>
|
||||||
|
|
||||||
|
namespace Lua::Internal {
|
||||||
|
|
||||||
|
void addTranslateModule()
|
||||||
|
{
|
||||||
|
::Lua::LuaEngine::autoRegister([](sol::state_view lua) {
|
||||||
|
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("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
|
@@ -192,8 +192,9 @@ expected_str<LuaPluginSpec *> LuaEngine::loadPlugin(const Utils::FilePath &path)
|
|||||||
return make_unexpected(contents.error());
|
return make_unexpected(contents.error());
|
||||||
|
|
||||||
sol::state lua;
|
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()),
|
std::string_view(contents->data(), contents->size()),
|
||||||
sol::script_pass_on_error,
|
sol::script_pass_on_error,
|
||||||
path.fileName().toUtf8().constData());
|
path.fileName().toUtf8().constData());
|
||||||
|
@@ -46,6 +46,7 @@ void addProcessModule();
|
|||||||
void addQtModule();
|
void addQtModule();
|
||||||
void addSettingsModule();
|
void addSettingsModule();
|
||||||
void addTextEditorModule();
|
void addTextEditorModule();
|
||||||
|
void addTranslateModule();
|
||||||
void addUtilsModule();
|
void addUtilsModule();
|
||||||
|
|
||||||
class LuaJsExtension : public QObject
|
class LuaJsExtension : public QObject
|
||||||
@@ -262,6 +263,7 @@ public:
|
|||||||
addQtModule();
|
addQtModule();
|
||||||
addSettingsModule();
|
addSettingsModule();
|
||||||
addTextEditorModule();
|
addTextEditorModule();
|
||||||
|
addTranslateModule();
|
||||||
addUtilsModule();
|
addUtilsModule();
|
||||||
|
|
||||||
Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
|
Core::JsExpander::registerGlobalObject("Lua", [] { return new LuaJsExtension(); });
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
#include "luaengine.h"
|
#include "luaengine.h"
|
||||||
#include "luatr.h"
|
#include "luatr.h"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
#include <extensionsystem/extensionsystemtr.h>
|
#include <extensionsystem/extensionsystemtr.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
#include <QTranslator>
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(luaPluginSpecLog, "qtc.lua.pluginspec", QtWarningMsg)
|
Q_LOGGING_CATEGORY(luaPluginSpecLog, "qtc.lua.pluginspec", QtWarningMsg)
|
||||||
|
|
||||||
@@ -45,6 +48,7 @@ LuaPluginSpec::LuaPluginSpec()
|
|||||||
|
|
||||||
expected_str<LuaPluginSpec *> LuaPluginSpec::create(const FilePath &filePath, sol::table pluginTable)
|
expected_str<LuaPluginSpec *> LuaPluginSpec::create(const FilePath &filePath, sol::table pluginTable)
|
||||||
{
|
{
|
||||||
|
const FilePath directory = filePath.parentDir();
|
||||||
std::unique_ptr<LuaPluginSpec> pluginSpec(new LuaPluginSpec());
|
std::unique_ptr<LuaPluginSpec> pluginSpec(new LuaPluginSpec());
|
||||||
|
|
||||||
if (!pluginTable.get_or<sol::function>("setup", {}))
|
if (!pluginTable.get_or<sol::function>("setup", {}))
|
||||||
@@ -63,8 +67,20 @@ expected_str<LuaPluginSpec *> LuaPluginSpec::create(const FilePath &filePath, so
|
|||||||
if (!r)
|
if (!r)
|
||||||
return make_unexpected(r.error());
|
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->setFilePath(filePath);
|
||||||
pluginSpec->setLocation(filePath.parentDir());
|
pluginSpec->setLocation(directory);
|
||||||
|
|
||||||
pluginSpec->d->pluginScriptPath = filePath;
|
pluginSpec->d->pluginScriptPath = filePath;
|
||||||
pluginSpec->d->printToOutputPane = pluginTable.get_or("printToOutputPane", false);
|
pluginSpec->d->printToOutputPane = pluginTable.get_or("printToOutputPane", false);
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
---@meta
|
---@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.
|
---The global qtc object defined in the Lua plugin.
|
||||||
---@class qtc
|
---@class qtc
|
||||||
Qtc = {}
|
Qtc = {}
|
||||||
@@ -24,6 +28,7 @@ Qtc = {}
|
|||||||
---@field Mimetypes? string XML MIME-info for registering additional or adapting built-in MIME types.
|
---@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 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 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 = {}
|
QtcPlugin = {}
|
||||||
|
|
||||||
---@class QtcPluginDependency
|
---@class QtcPluginDependency
|
||||||
|
Reference in New Issue
Block a user