feat: support translation generation
This commit is contained in:
committed by
Chris Rizzitello
parent
71c1bb87ca
commit
c3f0b18df6
@ -47,12 +47,13 @@ runs:
|
|||||||
zypper refresh
|
zypper refresh
|
||||||
zypper install -y --force-resolution \
|
zypper install -y --force-resolution \
|
||||||
cmake make ninja gcc-c++ rpm-build libopenssl-devel \
|
cmake make ninja gcc-c++ rpm-build libopenssl-devel \
|
||||||
glib2-devel libXtst-devel libxkbfile-devel qt6-base-devel qt6-tools-devel gtk3-devel \
|
glib2-devel libXtst-devel libxkbfile-devel qt6-base-devel qt6-tools-devel \
|
||||||
|
qt6-linguist-devel gtk3-devel \
|
||||||
googletest-devel googlemock-devel libei-devel libportal-devel help2man
|
googletest-devel googlemock-devel libei-devel libportal-devel help2man
|
||||||
elif [ ${{ inputs.like }} == "arch" ]; then
|
elif [ ${{ inputs.like }} == "arch" ]; then
|
||||||
pacman -Syu --noconfirm base-devel cmake ninja \
|
pacman -Syu --noconfirm base-devel cmake ninja \
|
||||||
gcc openssl glib2 libxtst libxkbfile gtest libei libportal \
|
gcc openssl glib2 libxtst libxkbfile gtest libei libportal \
|
||||||
qt6-base qt6-tools qt6-svg gtk3 help2man doxygen graphviz rsync
|
qt6-base qt6-tools qt6-svg qt6-translations qt6-declarative gtk3 help2man doxygen graphviz rsync
|
||||||
else
|
else
|
||||||
echo "Unknown like"
|
echo "Unknown like"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -166,6 +166,7 @@ configure_libs()
|
|||||||
|
|
||||||
add_subdirectory(doc)
|
add_subdirectory(doc)
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
add_subdirectory(translations)
|
||||||
|
|
||||||
# Install License, License is in the App Bundle on mac os (src/gui)
|
# Install License, License is in the App Bundle on mac os (src/gui)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|||||||
@ -26,6 +26,7 @@ path = [
|
|||||||
, "src/apps/deskflow-server/deskflow-server.exe.manifest"
|
, "src/apps/deskflow-server/deskflow-server.exe.manifest"
|
||||||
, "src/apps/res/manpage.txt"
|
, "src/apps/res/manpage.txt"
|
||||||
, "src/apps/res/deskflow.plist.in"
|
, "src/apps/res/deskflow.plist.in"
|
||||||
|
, "translations/*.ts"
|
||||||
]
|
]
|
||||||
SPDX-FileCopyrightText = "Deskflow Developers"
|
SPDX-FileCopyrightText = "Deskflow Developers"
|
||||||
SPDX-License-Identifier = "MIT"
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|||||||
@ -34,6 +34,7 @@ depends=(
|
|||||||
openssl
|
openssl
|
||||||
qt6-base
|
qt6-base
|
||||||
qt6-svg
|
qt6-svg
|
||||||
|
qt6-translations
|
||||||
)
|
)
|
||||||
|
|
||||||
options=('!debug')
|
options=('!debug')
|
||||||
|
|||||||
@ -30,6 +30,7 @@ CMake options:
|
|||||||
| ENABLE_COVERAGE | Enable test coverage | OFF | `gcov` |
|
| ENABLE_COVERAGE | Enable test coverage | OFF | `gcov` |
|
||||||
| SKIP_BUILD_TESTS | Skip running of tests at build time | OFF | |
|
| SKIP_BUILD_TESTS | Skip running of tests at build time | OFF | |
|
||||||
| VCPKG_QT | Build Qt w/ vcpkg (windows only) | OFF | |
|
| VCPKG_QT | Build Qt w/ vcpkg (windows only) | OFF | |
|
||||||
|
| CLEAN_TRS | Remove obsolete strings from tr files | OFF | |
|
||||||
|
|
||||||
Example cmake configuration.
|
Example cmake configuration.
|
||||||
`cmake -S. -Bbuild -DCMAKE_INSTALL_PREFIX=<INSTALLPREFIX>`
|
`cmake -S. -Bbuild -DCMAKE_INSTALL_PREFIX=<INSTALLPREFIX>`
|
||||||
|
|||||||
@ -2,8 +2,8 @@ sonar.organization=deskflow
|
|||||||
sonar.projectKey=deskflow_deskflow
|
sonar.projectKey=deskflow_deskflow
|
||||||
sonar.sources=src/apps,src/lib
|
sonar.sources=src/apps,src/lib
|
||||||
sonar.tests=src/unittests
|
sonar.tests=src/unittests
|
||||||
sonar.exclusions=subprojects/**,build/**
|
sonar.exclusions=subprojects/**,build/**,translations/**
|
||||||
sonar.coverage.exclusions=subprojects/**,src/unittests/**,src/apps/deskflow-gui/**,src/apps/res/**
|
sonar.coverage.exclusions=subprojects/**,src/unittests/**,src/apps/deskflow-gui/**,src/apps/res/**,translations/**
|
||||||
sonar.cpd.exclusions=**/*Test*.cpp
|
sonar.cpd.exclusions=**/*Test*.cpp
|
||||||
sonar.host.url=https://sonarcloud.io
|
sonar.host.url=https://sonarcloud.io
|
||||||
sonar.cfamily.compile-commands=build/compile_commands.json
|
sonar.cfamily.compile-commands=build/compile_commands.json
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include "VersionInfo.h"
|
#include "VersionInfo.h"
|
||||||
#include "common/Constants.h"
|
#include "common/Constants.h"
|
||||||
#include "common/ExitCodes.h"
|
#include "common/ExitCodes.h"
|
||||||
|
#include "common/I18N.h"
|
||||||
#include "common/UrlConstants.h"
|
#include "common/UrlConstants.h"
|
||||||
#include "gui/Diagnostic.h"
|
#include "gui/Diagnostic.h"
|
||||||
#include "gui/DotEnv.h"
|
#include "gui/DotEnv.h"
|
||||||
|
|||||||
@ -14,6 +14,8 @@ unset(SERVER_BINARY)
|
|||||||
add_library(common STATIC
|
add_library(common STATIC
|
||||||
Common.h
|
Common.h
|
||||||
ExitCodes.h
|
ExitCodes.h
|
||||||
|
I18N.h
|
||||||
|
I18N.cpp
|
||||||
Settings.h
|
Settings.h
|
||||||
Settings.cpp
|
Settings.cpp
|
||||||
QSettingsProxy.cpp
|
QSettingsProxy.cpp
|
||||||
|
|||||||
195
src/lib/common/I18N.cpp
Normal file
195
src/lib/common/I18N.cpp
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* Deskflow -- mouse and keyboard sharing utility
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "I18N.h"
|
||||||
|
|
||||||
|
#include "common/Constants.h"
|
||||||
|
#include "common/Settings.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTranslator>
|
||||||
|
|
||||||
|
I18N *I18N::instance()
|
||||||
|
{
|
||||||
|
static I18N m;
|
||||||
|
return &m;
|
||||||
|
}
|
||||||
|
|
||||||
|
I18N::I18N(QObject *parent) : QObject{parent}
|
||||||
|
{
|
||||||
|
const QList<QDir> appTrDirs{
|
||||||
|
QDir(QStringLiteral("%1/%2").arg(QCoreApplication::applicationDirPath(), QStringLiteral("translations"))),
|
||||||
|
QDir(QStringLiteral("%1/../translations").arg(QCoreApplication::applicationDirPath())),
|
||||||
|
QDir(QStringLiteral("%1/../share/%2/translations").arg(QCoreApplication::applicationDirPath(), kAppId)),
|
||||||
|
QDir(QStringLiteral("%1/.local/share/%2/translations").arg(QDir::homePath(), kAppId)),
|
||||||
|
QDir(QStringLiteral("/usr/local/share/%1/translations").arg(kAppId)),
|
||||||
|
QDir(QStringLiteral("/usr/share/%1/translations").arg(kAppId))
|
||||||
|
};
|
||||||
|
const QStringList appTrFilter{QStringLiteral("%1*.qm").arg(kAppId)};
|
||||||
|
|
||||||
|
for (const auto &dir : appTrDirs) {
|
||||||
|
if (!dir.entryList(appTrFilter, QDir::Files, QDir::Name).isEmpty()) {
|
||||||
|
m_appTrPath = dir.absolutePath();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_appTrPath.isEmpty()) {
|
||||||
|
qInfo() << "no app translations found";
|
||||||
|
}
|
||||||
|
|
||||||
|
const QList<QDir> qtTrDirs{
|
||||||
|
QDir(QStringLiteral("%1/%2").arg(QCoreApplication::applicationDirPath(), QStringLiteral("translations"))),
|
||||||
|
QDir(QStringLiteral("%1/../qt-depends/translations").arg(QCoreApplication::applicationDirPath())),
|
||||||
|
QDir(QStringLiteral("%1/../share/qt/translations").arg(QCoreApplication::applicationDirPath())),
|
||||||
|
QDir(QStringLiteral("%1/.local/share/%2/translations").arg(QDir::homePath(), QStringLiteral("qt"))),
|
||||||
|
QDir(QStringLiteral("/usr/local/share/qt/translations")),
|
||||||
|
QDir(QStringLiteral("/usr/share/qt/translations"))
|
||||||
|
};
|
||||||
|
const QStringList qtTrFilter{QStringLiteral("qt_*.qm")};
|
||||||
|
|
||||||
|
for (const auto &dir : qtTrDirs) {
|
||||||
|
if (!dir.entryList(qtTrFilter, QDir::Files, QDir::Name).isEmpty()) {
|
||||||
|
m_qtTrPath = dir.absolutePath();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_qtTrPath.isEmpty()) {
|
||||||
|
qInfo() << "no qt translations found";
|
||||||
|
}
|
||||||
|
|
||||||
|
detectLanguages();
|
||||||
|
|
||||||
|
if (Settings::value(Settings::Core::Language).isNull()) {
|
||||||
|
auto appTranslator = new QTranslator(this);
|
||||||
|
if (appTranslator->load(QLocale(), kAppId, "_", m_appTrPath)) {
|
||||||
|
m_currentTranslations.append(appTranslator);
|
||||||
|
QCoreApplication::installTranslator(appTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentLang = appTranslator->translate("i18n", "LocalizedName");
|
||||||
|
if (m_currentLang.isEmpty())
|
||||||
|
m_currentLang = QStringLiteral("English");
|
||||||
|
|
||||||
|
auto qtTranslator = new QTranslator(this);
|
||||||
|
if (qtTranslator->load(QLocale(), QStringLiteral("qt"), "_", m_qtTrPath)) {
|
||||||
|
m_currentTranslations.append(qtTranslator);
|
||||||
|
QCoreApplication::installTranslator(qtTranslator);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_currentLang = Settings::value(Settings::Core::Language).toString();
|
||||||
|
const auto translations = m_translations.value(m_currentLang);
|
||||||
|
for (const auto &translation : translations) {
|
||||||
|
auto translator = new QTranslator(this);
|
||||||
|
if (translator->load(translation)) {
|
||||||
|
m_currentTranslations.append(translator);
|
||||||
|
QCoreApplication::installTranslator(translator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList I18N::detectedLanguages()
|
||||||
|
{
|
||||||
|
return instance()->m_translations.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString I18N::currentLanguage()
|
||||||
|
{
|
||||||
|
return instance()->m_currentLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
void I18N::setLanguage(const QString &langName)
|
||||||
|
{
|
||||||
|
if (langName == instance()->m_currentLang) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instance()->m_translations.contains(langName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance()->m_currentLang = langName;
|
||||||
|
Settings::setValue(Settings::Core::Language, langName);
|
||||||
|
|
||||||
|
for (const auto &translation : std::as_const(instance()->m_currentTranslations))
|
||||||
|
QCoreApplication::removeTranslator(translation);
|
||||||
|
|
||||||
|
qDeleteAll(instance()->m_currentTranslations);
|
||||||
|
instance()->m_currentTranslations.clear();
|
||||||
|
|
||||||
|
const auto translations = instance()->m_translations.value(langName);
|
||||||
|
for (const auto &translation : translations) {
|
||||||
|
auto translator = new QTranslator(instance());
|
||||||
|
if (translator->load(translation)) {
|
||||||
|
instance()->m_currentTranslations.append(translator);
|
||||||
|
QCoreApplication::installTranslator(translator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_EMIT instance()->languageChanged(langName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I18N::reDetectLanguages()
|
||||||
|
{
|
||||||
|
instance()->detectLanguages();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I18N::detectLanguages()
|
||||||
|
{
|
||||||
|
const auto oldList = m_translations;
|
||||||
|
m_translations.clear();
|
||||||
|
|
||||||
|
QStringList nameFilter = {QStringLiteral("%1_*.qm").arg(kAppId)};
|
||||||
|
QMap<QString, QString> appTranslations;
|
||||||
|
QMap<QString, QString> shortToNative;
|
||||||
|
QStringList detectedLangCodes;
|
||||||
|
QDir dir(m_appTrPath);
|
||||||
|
QStringList langList = dir.entryList(nameFilter, QDir::Files, QDir::Name);
|
||||||
|
|
||||||
|
for (const QString &translation : std::as_const(langList)) {
|
||||||
|
QTranslator translator;
|
||||||
|
std::ignore = translator.load(translation, dir.absolutePath());
|
||||||
|
const auto longCode = translator.language();
|
||||||
|
//: Replace with your Language name
|
||||||
|
//: This is a required string
|
||||||
|
QString nativeLang = translator.translate("i18n", "LocalizedName");
|
||||||
|
if (nativeLang.isEmpty())
|
||||||
|
nativeLang = QStringLiteral("English");
|
||||||
|
|
||||||
|
QString shortCode;
|
||||||
|
if (longCode.startsWith(QStringLiteral("zh")) || longCode.startsWith(QStringLiteral("pt")))
|
||||||
|
shortCode = longCode;
|
||||||
|
else
|
||||||
|
shortCode = longCode.mid(0, 2);
|
||||||
|
|
||||||
|
appTranslations.insert(shortCode, translator.filePath());
|
||||||
|
shortToNative.insert(shortCode, nativeLang);
|
||||||
|
detectedLangCodes.append(QStringLiteral("qt_%1.qm").arg(shortCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.setPath(m_qtTrPath);
|
||||||
|
const static auto qtTrNameLen = 3; // length of qt_
|
||||||
|
langList = dir.entryList(detectedLangCodes, QDir::Files, QDir::Name);
|
||||||
|
|
||||||
|
QMap<QString, QString> qtTranslations;
|
||||||
|
for (const QString &translation : std::as_const(langList)) {
|
||||||
|
QString lang = translation.mid(qtTrNameLen, translation.lastIndexOf('.') - qtTrNameLen);
|
||||||
|
qtTranslations.insert(lang, QStringLiteral("%1/%2").arg(m_qtTrPath, translation));
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList keys = appTranslations.keys();
|
||||||
|
for (const QString &lang : keys)
|
||||||
|
m_translations.insert(shortToNative.value(lang), {appTranslations.value(lang), qtTranslations.value(lang)});
|
||||||
|
|
||||||
|
if (oldList != m_translations)
|
||||||
|
Q_EMIT langaugesChanged(m_translations.keys());
|
||||||
|
}
|
||||||
70
src/lib/common/I18N.h
Normal file
70
src/lib/common/I18N.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Deskflow -- mouse and keyboard sharing utility
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class QTranslator;
|
||||||
|
/**
|
||||||
|
* @brief The I18N singleton class handles detection and loading of translation files
|
||||||
|
*/
|
||||||
|
class I18N : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static I18N *instance();
|
||||||
|
/**
|
||||||
|
* @brief detectedLanguages
|
||||||
|
* @return List of detected languages (native names: English, Español etc..)
|
||||||
|
*/
|
||||||
|
static QStringList detectedLanguages();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief currentLanguage
|
||||||
|
* @return The current language string (native name: English, Español etc..)
|
||||||
|
*/
|
||||||
|
static QString currentLanguage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setLanguage Sets the current language
|
||||||
|
* @param langName The language name must be an is 639-1 name
|
||||||
|
*/
|
||||||
|
static void setLanguage(const QString &langName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief detectLanguages Detect new language files
|
||||||
|
*/
|
||||||
|
static void reDetectLanguages();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
/**
|
||||||
|
* @brief languageChanged Emitted when the current language changes
|
||||||
|
* @param language The current language (native name, i.e English, Español)
|
||||||
|
*/
|
||||||
|
void languageChanged(const QString language);
|
||||||
|
/**
|
||||||
|
* @brief langaugesChanged Emitted when the detected languages changes
|
||||||
|
* @param languages The current list of languages (native names i.e English, Español..)
|
||||||
|
*/
|
||||||
|
void langaugesChanged(const QStringList languages);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit I18N(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
I18N *operator=(I18N &other) = delete;
|
||||||
|
I18N(const I18N &other) = delete;
|
||||||
|
~I18N() override = default;
|
||||||
|
void detectLanguages();
|
||||||
|
|
||||||
|
QMap<QString, QStringList> m_translations;
|
||||||
|
QList<QTranslator *> m_currentTranslations;
|
||||||
|
QString m_currentLang = QStringLiteral("English");
|
||||||
|
QString m_appTrPath;
|
||||||
|
QString m_qtTrPath;
|
||||||
|
};
|
||||||
@ -54,6 +54,7 @@ public:
|
|||||||
inline static const auto Display = QStringLiteral("core/display");
|
inline static const auto Display = QStringLiteral("core/display");
|
||||||
inline static const auto RestartOnFailure = QStringLiteral("core/restartOnFailure");
|
inline static const auto RestartOnFailure = QStringLiteral("core/restartOnFailure");
|
||||||
inline static const auto UseHooks = QStringLiteral("core/useHooks");
|
inline static const auto UseHooks = QStringLiteral("core/useHooks");
|
||||||
|
inline static const auto Language = QStringLiteral("core/language");
|
||||||
};
|
};
|
||||||
struct Daemon
|
struct Daemon
|
||||||
{
|
{
|
||||||
@ -179,6 +180,7 @@ private:
|
|||||||
, Settings::Core::Display
|
, Settings::Core::Display
|
||||||
, Settings::Core::RestartOnFailure
|
, Settings::Core::RestartOnFailure
|
||||||
, Settings::Core::UseHooks
|
, Settings::Core::UseHooks
|
||||||
|
, Settings::Core::Language
|
||||||
, Settings::Daemon::Command
|
, Settings::Daemon::Command
|
||||||
, Settings::Daemon::Elevate
|
, Settings::Daemon::Elevate
|
||||||
, Settings::Daemon::LogFile
|
, Settings::Daemon::LogFile
|
||||||
|
|||||||
50
translations/CMakeLists.txt
Normal file
50
translations/CMakeLists.txt
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# SPDX-FileCopyrightText: Chris Rizzitello <sithlord48@gmail.com>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM 1)
|
||||||
|
option(CLEAN_TRS "Clean obsolete translations from tr files" OFF)
|
||||||
|
find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS LinguistTools)
|
||||||
|
|
||||||
|
# To add a new language Add 639 shortname
|
||||||
|
set (${CMAKE_PROJECT_NAME}_TRS
|
||||||
|
${CMAKE_PROJECT_NAME}_es.ts
|
||||||
|
)
|
||||||
|
|
||||||
|
set(TR_OPTIONS -no-ui-lines -locations none -silent)
|
||||||
|
if(CLEAN_TRS)
|
||||||
|
list(APPEND TR_OPTIONS -no-obsolete)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# English will have only plurals
|
||||||
|
qt_create_translation(TRS ${CMAKE_SOURCE_DIR}/src ${CMAKE_PROJECT_NAME}_en.ts OPTIONS -pluralonly ${TR_OPTIONS})
|
||||||
|
|
||||||
|
# Other languages contain the full set of strings.
|
||||||
|
qt_create_translation(TRS ${CMAKE_SOURCE_DIR}/src ${${CMAKE_PROJECT_NAME}_TRS} OPTIONS ${TR_OPTIONS})
|
||||||
|
|
||||||
|
#ensure that the targets are built always
|
||||||
|
add_custom_target(app_translations ALL DEPENDS ${TRS})
|
||||||
|
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
install(FILES ${TRS} DESTINATION share/${CMAKE_PROJECT_NAME}/translations)
|
||||||
|
elseif(WIN32)
|
||||||
|
install(FILES ${TRS} DESTINATION translations)
|
||||||
|
elseif(APPLE)
|
||||||
|
#install our translations
|
||||||
|
set(MAC_LANG_PATH ${CMAKE_PROJECT_PROPER_NAME}.app/Contents/MacOS/translations)
|
||||||
|
install(FILES ${TRS} DESTINATION ${MAC_LANG_PATH})
|
||||||
|
|
||||||
|
# find the qt translation files not deployed by macdeployqt
|
||||||
|
get_target_property(lupdate_executable Qt6::lupdate IMPORTED_LOCATION)
|
||||||
|
get_filename_component(_qt_bin_dir "${lupdate_executable}" DIRECTORY)
|
||||||
|
file(REAL_PATH "${_qt_bin_dir}/../" QT_ROOT_DIR)
|
||||||
|
find_file(_QT_QM_FILE NAMES qtbase_en.qm PATHS ${QT_ROOT_DIR} PATH_SUFFIXES "translations" "share/qt/translations" REQUIRED)
|
||||||
|
get_filename_component(MAC_QT_LANG_PATH "${_QT_QM_FILE}" DIRECTORY)
|
||||||
|
|
||||||
|
# install each lang file and rename to the same name
|
||||||
|
# rename does not work with a list so foreach is used instead
|
||||||
|
install(FILES ${MAC_QT_LANG_PATH}/qtbase_en.qm DESTINATION ${MAC_LANG_PATH} RENAME qt_en.qm)
|
||||||
|
foreach(LANG ${${CMAKE_PROJECT_NAME}_TRS})
|
||||||
|
string(REPLACE "${CMAKE_PROJECT_NAME}_" "" LANG ${LANG})
|
||||||
|
string(REPLACE ".ts" "" LANG ${LANG})
|
||||||
|
install(FILES ${MAC_QT_LANG_PATH}/qtbase_${LANG}.qm DESTINATION ${MAC_LANG_PATH} RENAME qt_${LANG}.qm)
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
15
translations/deskflow_en.ts
Normal file
15
translations/deskflow_en.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="en_US">
|
||||||
|
<context>
|
||||||
|
<name>MainWindow</name>
|
||||||
|
<message numerus="yes">
|
||||||
|
<source>%1 is connected, with %n client(s): %2</source>
|
||||||
|
<extracomment>Shown when in server mode and at least 1 client is connected %1 is replaced by the app name %2 will be a list of at least one client %n will be replaced by the number of clients (n is >=1), it is not requried to be in the translation</extracomment>
|
||||||
|
<translation>
|
||||||
|
<numerusform>%1 is connected, with a client: %2</numerusform>
|
||||||
|
<numerusform>%1 is connected, with %n clients: %2</numerusform>
|
||||||
|
</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
Reference in New Issue
Block a user