| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <algorithm> |
| | #include <QApplication> |
| | #include <QDir> |
| | #include <QKeyEvent> |
| | #include <QRegularExpression> |
| | #include <QStringList> |
| | #include <QTranslator> |
| | #include <QWidget> |
| |
|
| |
|
| | #include <App/Application.h> |
| | #include <Gui/TextEdit.h> |
| | #include "Translator.h" |
| |
|
| |
|
| | using namespace Gui; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | Translator* Translator::_pcSingleton = nullptr; |
| |
|
| | namespace Gui |
| | { |
| | class TranslatorP |
| | { |
| | public: |
| | std::string activatedLanguage; |
| | std::map<std::string, std::string> mapLanguageTopLevelDomain; |
| | TStringMap mapSupportedLocales; |
| | std::list<QTranslator*> translators; |
| | QStringList paths; |
| | }; |
| | } |
| |
|
| | class Translator::ParameterObserver: public ParameterGrp::ObserverType |
| | { |
| | public: |
| | ParameterObserver(Translator* client) |
| | : client(client) |
| | { |
| | hGrp = App::GetApplication().GetParameterGroupByPath( |
| | "User parameter:BaseApp/Preferences/General" |
| | ); |
| | hGrp->Attach(this); |
| | } |
| |
|
| | void OnChange(Base::Subject<const char*>& caller, const char* creason) override |
| | { |
| | (void)caller; |
| |
|
| | std::string_view reason = creason; |
| | if (reason == "UseLocaleFormatting") { |
| | int format = hGrp->GetInt("UseLocaleFormatting"); |
| | if (format == 0) { |
| | client->setLocale(); |
| | } |
| | else if (format == 1) { |
| | |
| | std::string language = hGrp->GetASCII("Language"); |
| | client->setLocale(language); |
| | } |
| | else if (format == 2) { |
| | client->setLocale("C"); |
| | } |
| | else { |
| | throw Base::ValueError("Parameter \"UseLocaleFormatting\" value out of bounds for Translator::formattingOptions"); |
| | } |
| | } |
| | else if (reason == "SubstituteDecimalSeparator") { |
| | bool value = hGrp->GetBool("SubstituteDecimal"); |
| | client->enableDecimalPointConversion(value); |
| | } |
| | } |
| |
|
| | Translator* client; |
| | static ParameterGrp::handle hGrp; |
| | }; |
| | ParameterGrp::handle Translator::ParameterObserver::hGrp; |
| |
|
| | Translator* Translator::instance() |
| | { |
| | if (!_pcSingleton) { |
| | _pcSingleton = new Translator; |
| | } |
| | return _pcSingleton; |
| | } |
| |
|
| | void Translator::destruct() |
| | { |
| | if (_pcSingleton) { |
| | delete _pcSingleton; |
| | } |
| | _pcSingleton = nullptr; |
| | } |
| |
|
| | Translator::Translator() |
| | { |
| | observer = std::make_unique<Translator::ParameterObserver>(this); |
| |
|
| | |
| | |
| | d = new TranslatorP; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Afrikaans" )] = "af"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Arabic" )] = "ar"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Basque" )] = "eu"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Belarusian" )] = "be"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Bulgarian" )] = "bg"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Catalan" )] = "ca"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Chinese (Simplified)" )] = "zh-CN"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Chinese (Traditional)" )] = "zh-TW"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Croatian" )] = "hr"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Czech" )] = "cs"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Danish" )] = "da"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Dutch" )] = "nl"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("English" )] = "en"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Filipino" )] = "fil"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Finnish" )] = "fi"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("French" )] = "fr"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Galician" )] = "gl"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Georgian" )] = "ka"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("German" )] = "de"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Greek" )] = "el"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Hungarian" )] = "hu"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Indonesian" )] = "id"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Italian" )] = "it"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Japanese" )] = "ja"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Kabyle" )] = "kab"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Korean" )] = "ko"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Lithuanian" )] = "lt"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Norwegian" )] = "no"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Polish" )] = "pl"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Portuguese (Brazilian)")] = "pt-BR"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Portuguese" )] = "pt-PT"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Romanian" )] = "ro"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Russian" )] = "ru"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Serbian" )] = "sr"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Serbian (Latin)" )] = "sr-CS"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Slovak" )] = "sk"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Slovenian" )] = "sl"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Spanish" )] = "es-ES"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Spanish (Argentina)" )] = "es-AR"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Swedish" )] = "sv-SE"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Turkish" )] = "tr"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Ukrainian" )] = "uk"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Valencian" )] = "val-ES"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Vietnamese" )] = "vi"; |
| | d->mapLanguageTopLevelDomain[QT_TR_NOOP("Malay")] = "ms"; |
| |
|
| | auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General"); |
| | auto entries = hGrp->GetASCII("AdditionalLanguageDomainEntries", ""); |
| | |
| | |
| | QRegularExpression matchingRE(QStringLiteral("\"(.*[^\\s]+.*)\"\\s*=\\s*\"([^\\s]+)\";?")); |
| | auto matches = matchingRE.globalMatch(QString::fromStdString(entries)); |
| | while (matches.hasNext()) { |
| | QRegularExpressionMatch match = matches.next(); |
| | QString language = match.captured(1); |
| | QString tld = match.captured(2); |
| | d->mapLanguageTopLevelDomain[language.toStdString()] = tld.toStdString(); |
| | } |
| | |
| |
|
| | d->activatedLanguage = "English"; |
| |
|
| | d->paths = directories(); |
| |
|
| | enableDecimalPointConversion(hGrp->GetBool("SubstituteDecimalSeparator", false)); |
| | } |
| |
|
| | Translator::~Translator() |
| | { |
| | removeTranslators(); |
| | delete d; |
| | } |
| |
|
| | TStringList Translator::supportedLanguages() const |
| | { |
| | TStringList languages; |
| | TStringMap locales = supportedLocales(); |
| | for (const auto& it : locales) { |
| | languages.push_back(it.first); |
| | } |
| |
|
| | return languages; |
| | } |
| |
|
| | TStringMap Translator::supportedLocales() const |
| | { |
| | if (!d->mapSupportedLocales.empty()) { |
| | return d->mapSupportedLocales; |
| | } |
| |
|
| | |
| | for (const auto& domainMap : d->mapLanguageTopLevelDomain) { |
| | for (const auto& directoryName : std::as_const(d->paths)) { |
| | QDir dir(directoryName); |
| | QString filter = QStringLiteral("*_%1.qm").arg(QString::fromStdString(domainMap.second)); |
| | QStringList fileNames = dir.entryList(QStringList(filter), QDir::Files, QDir::Name); |
| | if (!fileNames.isEmpty()) { |
| | d->mapSupportedLocales[domainMap.first] = domainMap.second; |
| | break; |
| | } |
| | } |
| | } |
| |
|
| | return d->mapSupportedLocales; |
| | } |
| |
|
| | void Translator::activateLanguage(const char* lang) |
| | { |
| | removeTranslators(); |
| | d->activatedLanguage = lang; |
| | TStringList languages = supportedLanguages(); |
| | if (std::ranges::find(languages, lang) != languages.end()) { |
| | refresh(); |
| | } |
| | } |
| |
|
| | std::string Translator::activeLanguage() const |
| | { |
| | return d->activatedLanguage; |
| | } |
| |
|
| | std::string Translator::locale(const std::string& lang) const |
| | { |
| | std::string loc; |
| | std::map<std::string, std::string>::const_iterator tld = d->mapLanguageTopLevelDomain.find(lang); |
| | if (tld != d->mapLanguageTopLevelDomain.end()) { |
| | loc = tld->second; |
| | } |
| |
|
| | return loc; |
| | } |
| |
|
| | void Translator::setLocale(const std::string& language) const |
| | { |
| | auto loc = QLocale::system(); |
| | if (language == "C" || language == "c") { |
| | loc = QLocale::c(); |
| | } |
| | else { |
| | auto bcp47 = locale(language); |
| | if (!bcp47.empty()) { |
| | loc = QLocale(QString::fromStdString(bcp47)); |
| | } |
| | } |
| | QLocale::setDefault(loc); |
| | updateLocaleChange(); |
| |
|
| | #ifdef FC_DEBUG |
| | Base::Console() |
| | .log("Locale changed to %s => %s\n", qPrintable(loc.bcp47Name()), qPrintable(loc.name())); |
| | #endif |
| | } |
| |
|
| | void Translator::updateLocaleChange() const |
| | { |
| | for (auto& topLevelWidget : qApp->topLevelWidgets()) { |
| | topLevelWidget->setLocale(QLocale()); |
| | } |
| | } |
| |
|
| | QStringList Translator::directories() const |
| | { |
| | QStringList list; |
| | auto dir = App::GetApplication() |
| | .GetParameterGroupByPath("User parameter:BaseApp/Preferences/General") |
| | ->GetASCII("AdditionalTranslationsDirectory", ""); |
| | if (!dir.empty()) { |
| | list.push_back(QString::fromStdString(dir)); |
| | } |
| | QDir home(QString::fromUtf8(App::Application::getUserAppDataDir().c_str())); |
| | list.push_back(home.absoluteFilePath(QLatin1String("translations"))); |
| | QDir resc(QString::fromUtf8(App::Application::getResourceDir().c_str())); |
| | list.push_back(resc.absoluteFilePath(QLatin1String("translations"))); |
| | list.push_back(QLatin1String(":/translations")); |
| |
|
| | return list; |
| | } |
| |
|
| | void Translator::addPath(const QString& path) |
| | { |
| | d->paths.push_back(path); |
| | } |
| |
|
| | void Translator::installQMFiles(const QDir& dir, const char* locale) |
| | { |
| | QString filter = QStringLiteral("*_%1.qm").arg(QLatin1String(locale)); |
| | QStringList fileNames = dir.entryList(QStringList(filter), QDir::Files, QDir::Name); |
| | for (const auto& it : fileNames) { |
| | bool ok = false; |
| | for (std::list<QTranslator*>::const_iterator tt = d->translators.begin(); |
| | tt != d->translators.end(); |
| | ++tt) { |
| | if ((*tt)->objectName() == it) { |
| | ok = true; |
| | break; |
| | } |
| | } |
| |
|
| | |
| | if (!ok) { |
| | auto translator = new QTranslator; |
| | translator->setObjectName(it); |
| | if (translator->load(dir.filePath(it))) { |
| | qApp->installTranslator(translator); |
| | d->translators.push_back(translator); |
| | } |
| | else { |
| | delete translator; |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | void Translator::refresh() |
| | { |
| | std::map<std::string, std::string>::iterator tld = d->mapLanguageTopLevelDomain.find( |
| | d->activatedLanguage |
| | ); |
| | if (tld == d->mapLanguageTopLevelDomain.end()) { |
| | return; |
| | } |
| | for (const QString& it : d->paths) { |
| | QDir dir(it); |
| | installQMFiles(dir, tld->second.c_str()); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | void Translator::removeTranslators() |
| | { |
| | for (QTranslator* it : d->translators) { |
| | qApp->removeTranslator(it); |
| | delete it; |
| | } |
| |
|
| | d->translators.clear(); |
| | } |
| |
|
| | bool Translator::eventFilter(QObject* obj, QEvent* ev) |
| | { |
| | if (ev->type() == QEvent::KeyPress || ev->type() == QEvent::KeyRelease) { |
| | QKeyEvent* kev = static_cast<QKeyEvent*>(ev); |
| | Qt::KeyboardModifiers mod = kev->modifiers(); |
| | int key = kev->key(); |
| | if ((mod & Qt::KeypadModifier) && (key == Qt::Key_Period || key == Qt::Key_Comma)) { |
| | if (ev->spontaneous()) { |
| | auto dp = QString(QLocale().decimalPoint()); |
| | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
| | int dpcode = QKeySequence(dp)[0]; |
| | #else |
| | int dpcode = QKeySequence(dp)[0].key(); |
| | #endif |
| | if (kev->text() != dp) { |
| | QKeyEvent modifiedKeyEvent( |
| | kev->type(), |
| | dpcode, |
| | mod, |
| | dp, |
| | kev->isAutoRepeat(), |
| | kev->count() |
| | ); |
| | qApp->sendEvent(obj, &modifiedKeyEvent); |
| | return true; |
| | } |
| | } |
| | if (dynamic_cast<Gui::TextEdit*>(obj) && key != Qt::Key_Period) { |
| | QKeyEvent modifiedKeyEvent( |
| | kev->type(), |
| | Qt::Key_Period, |
| | mod, |
| | QChar::fromLatin1('.'), |
| | kev->isAutoRepeat(), |
| | kev->count() |
| | ); |
| | qApp->sendEvent(obj, &modifiedKeyEvent); |
| | return true; |
| | } |
| | } |
| | } |
| | return false; |
| | } |
| |
|
| | void Translator::enableDecimalPointConversion(bool on) |
| | { |
| | if (!qApp) { |
| | return; |
| | } |
| |
|
| | if (!on) { |
| | decimalPointConverter.reset(); |
| | return; |
| | } |
| | #if FC_DEBUG |
| | if (on && decimalPointConverter) { |
| | Base::Console().instance().warning( |
| | "Translator: decimal point converter is already installed\n" |
| | ); |
| | } |
| | #endif |
| | if (on && !decimalPointConverter) { |
| | decimalPointConverter = std::unique_ptr<Translator, std::function<void(Translator*)>>( |
| | this, |
| | [](Translator* evFilter) { qApp->removeEventFilter(evFilter); } |
| | ); |
| | qApp->installEventFilter(decimalPointConverter.get()); |
| | } |
| | } |
| |
|
| | bool Translator::isEnabledDecimalPointConversion() const |
| | { |
| | return static_cast<bool>(decimalPointConverter); |
| | } |
| |
|
| | #include "moc_Translator.cpp" |
| |
|