File indexing completed on 2025-02-09 04:28:39

0001 /*
0002   This file is part of the KTextTemplate library
0003 
0004   SPDX-FileCopyrightText: 2010 Stephen Kelly <steveire@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.1-or-later
0007 
0008 */
0009 
0010 #include "qtlocalizer.h"
0011 
0012 #include <QCoreApplication>
0013 #include <QDateTime>
0014 #include <QLibraryInfo>
0015 #include <QList>
0016 #include <QTranslator>
0017 
0018 #include <QLoggingCategory>
0019 
0020 Q_LOGGING_CATEGORY(KTEXTTEMPLATE_LOCALIZER, "kf.texttemplate.qtlocalizer")
0021 
0022 struct Locale {
0023     explicit Locale(const QLocale &_locale)
0024         : locale(_locale)
0025     {
0026     }
0027 
0028     ~Locale()
0029     {
0030         qDeleteAll(systemTranslators);
0031         qDeleteAll(themeTranslators);
0032     }
0033 
0034     const QLocale locale;
0035     QList<QTranslator *> externalSystemTranslators; // Not owned by us!
0036     QList<QTranslator *> systemTranslators;
0037     QList<QTranslator *> themeTranslators;
0038 };
0039 
0040 namespace KTextTemplate
0041 {
0042 
0043 class QtLocalizerPrivate
0044 {
0045     QtLocalizerPrivate(QtLocalizer *qq, const QLocale &locale)
0046         : q_ptr(qq)
0047     {
0048         auto localeStruct = new Locale(locale);
0049         m_availableLocales.insert(locale.name(), localeStruct);
0050         m_locales.push_back(localeStruct);
0051     }
0052 
0053     ~QtLocalizerPrivate()
0054     {
0055         m_locales.clear();
0056         qDeleteAll(m_availableLocales);
0057     }
0058 
0059     QLocale currentLocale() const
0060     {
0061         Q_ASSERT(!m_locales.isEmpty());
0062         if (m_locales.isEmpty()) {
0063             qCWarning(KTEXTTEMPLATE_LOCALIZER) << "Invalid Locale";
0064             return {};
0065         }
0066         return m_locales.last()->locale;
0067     }
0068 
0069     Q_DECLARE_PUBLIC(QtLocalizer)
0070     QtLocalizer *const q_ptr;
0071 
0072     QString translate(const QString &input, const QString &context, int count = -1) const;
0073 
0074     QHash<QString, Locale *> m_availableLocales;
0075 
0076     QList<Locale *> m_locales;
0077     QString m_appTranslatorPath;
0078     QString m_appTranslatorPrefix;
0079 };
0080 }
0081 
0082 using namespace KTextTemplate;
0083 
0084 static void replacePercentN(QString *result, int n)
0085 {
0086     if (n >= 0) {
0087         auto percentPos = 0;
0088         auto len = 0;
0089         while ((percentPos = result->indexOf(QLatin1Char('%'), percentPos + len)) != -1) {
0090             len = 1;
0091             QString fmt;
0092             if (result->at(percentPos + len) == QLatin1Char('L')) {
0093                 ++len;
0094                 fmt = QStringLiteral("%L1");
0095             } else {
0096                 fmt = QStringLiteral("%1");
0097             }
0098             if (result->at(percentPos + len) == QLatin1Char('n')) {
0099                 fmt = fmt.arg(n);
0100                 ++len;
0101                 result->replace(percentPos, len, fmt);
0102                 len = fmt.length();
0103             }
0104         }
0105     }
0106 }
0107 
0108 QString QtLocalizerPrivate::translate(const QString &input, const QString &context, int count) const
0109 {
0110     QString result;
0111 
0112     if (m_locales.isEmpty()) {
0113         result = input;
0114         replacePercentN(&result, count);
0115         return result;
0116     }
0117 
0118     auto locale = m_locales.last();
0119     for (QTranslator *translator : std::as_const(locale->themeTranslators)) {
0120         result = translator->translate("GR_FILENAME", input.toUtf8().constData(), context.toUtf8().constData(), count);
0121     }
0122     if (result.isEmpty()) {
0123         auto translators = locale->externalSystemTranslators + locale->systemTranslators;
0124         if (translators.isEmpty())
0125             return QCoreApplication::translate("GR_FILENAME", input.toUtf8().constData(), context.toUtf8().constData(), count);
0126         for (QTranslator *translator : std::as_const(translators)) {
0127             result = translator->translate("GR_FILENAME", input.toUtf8().constData(), context.toUtf8().constData(), count);
0128             if (!result.isEmpty())
0129                 break;
0130         }
0131     }
0132     if (!result.isEmpty()) {
0133         replacePercentN(&result, count);
0134         return result;
0135     }
0136     auto fallback = input;
0137     replacePercentN(&fallback, count);
0138     return fallback;
0139 }
0140 
0141 QtLocalizer::QtLocalizer(const QLocale &locale)
0142     : d_ptr(new QtLocalizerPrivate(this, locale))
0143 {
0144 }
0145 
0146 QtLocalizer::~QtLocalizer()
0147 {
0148     delete d_ptr;
0149 }
0150 
0151 void QtLocalizer::setAppTranslatorPath(const QString &path)
0152 {
0153     Q_D(QtLocalizer);
0154     d->m_appTranslatorPath = path;
0155 }
0156 
0157 void QtLocalizer::setAppTranslatorPrefix(const QString &prefix)
0158 {
0159     Q_D(QtLocalizer);
0160     d->m_appTranslatorPrefix = prefix;
0161 }
0162 
0163 void QtLocalizer::installTranslator(QTranslator *translator, const QString &localeName)
0164 {
0165     Q_D(QtLocalizer);
0166     if (!d->m_availableLocales.contains(localeName)) {
0167         const QLocale namedLocale(localeName);
0168         d->m_availableLocales.insert(localeName, new Locale(namedLocale));
0169     }
0170     d->m_availableLocales[localeName]->externalSystemTranslators.prepend(translator);
0171 }
0172 
0173 QString QtLocalizer::localizeDate(const QDate &date, QLocale::FormatType formatType) const
0174 {
0175     Q_D(const QtLocalizer);
0176     return d->currentLocale().toString(date, formatType);
0177 }
0178 
0179 QString QtLocalizer::localizeTime(const QTime &time, QLocale::FormatType formatType) const
0180 {
0181     Q_D(const QtLocalizer);
0182     return d->currentLocale().toString(time, formatType);
0183 }
0184 
0185 QString QtLocalizer::localizeDateTime(const QDateTime &dateTime, QLocale::FormatType formatType) const
0186 {
0187     Q_D(const QtLocalizer);
0188     return d->currentLocale().toString(dateTime, formatType);
0189 }
0190 
0191 QString QtLocalizer::localizeNumber(int number) const
0192 {
0193     Q_D(const QtLocalizer);
0194     return d->currentLocale().toString(number);
0195 }
0196 
0197 QString QtLocalizer::localizeNumber(qreal number) const
0198 {
0199     Q_D(const QtLocalizer);
0200     return d->currentLocale().toString(number, 'f', 2);
0201 }
0202 
0203 QString QtLocalizer::localizeMonetaryValue(qreal value, const QString &currencyCode) const
0204 {
0205     Q_D(const QtLocalizer);
0206     auto currencySymbol = QStringLiteral("$");
0207     if (currencyCode == QStringLiteral("EUR")) {
0208         currencySymbol = QChar(0x20AC);
0209     } else if (currencyCode == QStringLiteral("GBP")) {
0210         currencySymbol = QStringLiteral("£");
0211     } else {
0212         currencySymbol = currencyCode;
0213     }
0214     return currencySymbol + QLatin1Char(' ') + d->currentLocale().toString(value, 'f', 2);
0215 }
0216 
0217 static QString substituteArguments(const QString &input, const QVariantList &arguments)
0218 {
0219     auto string = input;
0220     for (const QVariant &arg : arguments) {
0221         if (arg.userType() == qMetaTypeId<int>())
0222             string = string.arg(arg.value<int>());
0223         else if (arg.userType() == qMetaTypeId<double>())
0224             string = string.arg(arg.value<double>());
0225         else if (arg.userType() == qMetaTypeId<QDateTime>())
0226             string = string.arg(arg.value<QDateTime>().toString());
0227         else
0228             string = string.arg(arg.value<QString>());
0229     }
0230     return string;
0231 }
0232 
0233 QString QtLocalizer::localizeContextString(const QString &string, const QString &context, const QVariantList &arguments) const
0234 {
0235     Q_D(const QtLocalizer);
0236     const auto translated = d->translate(string, context);
0237     return substituteArguments(translated, arguments);
0238 }
0239 
0240 QString QtLocalizer::localizeString(const QString &string, const QVariantList &arguments) const
0241 {
0242     Q_D(const QtLocalizer);
0243     const auto translated = d->translate(string, QString());
0244     return substituteArguments(translated, arguments);
0245 }
0246 
0247 QString QtLocalizer::localizePluralContextString(const QString &string, const QString &pluralForm, const QString &context, const QVariantList &_arguments) const
0248 {
0249     Q_UNUSED(pluralForm)
0250     Q_D(const QtLocalizer);
0251     auto arguments = _arguments;
0252     const auto N = arguments.takeFirst().toInt();
0253     const auto translated = d->translate(string, context, N);
0254     return substituteArguments(translated, arguments);
0255 }
0256 
0257 QString QtLocalizer::localizePluralString(const QString &string, const QString &pluralForm, const QVariantList &_arguments) const
0258 {
0259     Q_UNUSED(pluralForm)
0260     Q_D(const QtLocalizer);
0261     auto arguments = _arguments;
0262     const auto N = arguments.takeFirst().toInt();
0263     const auto translated = d->translate(string, QString(), N);
0264     return substituteArguments(translated, arguments);
0265 }
0266 
0267 QString QtLocalizer::currentLocale() const
0268 {
0269     Q_D(const QtLocalizer);
0270     return d->currentLocale().name();
0271 }
0272 
0273 void QtLocalizer::pushLocale(const QString &localeName)
0274 {
0275     Q_D(QtLocalizer);
0276     Locale *localeStruct = nullptr;
0277     if (!d->m_availableLocales.contains(localeName)) {
0278         localeStruct = new Locale(QLocale(localeName));
0279         auto qtTranslator = new QTranslator;
0280         (void)qtTranslator->load(QStringLiteral("qt_") + localeName, QLibraryInfo::path(QLibraryInfo::TranslationsPath));
0281         localeStruct->systemTranslators.append(qtTranslator);
0282         auto appTranslator = new QTranslator;
0283         (void)appTranslator->load(d->m_appTranslatorPrefix + localeName, d->m_appTranslatorPath);
0284         localeStruct->systemTranslators.append(appTranslator);
0285         d->m_availableLocales.insert(localeName, localeStruct);
0286     } else {
0287         localeStruct = d->m_availableLocales[localeName];
0288     }
0289     Q_ASSERT(localeStruct);
0290     d->m_locales.push_back(localeStruct);
0291 }
0292 
0293 void QtLocalizer::popLocale()
0294 {
0295     Q_D(QtLocalizer);
0296     Q_ASSERT(!d->m_locales.isEmpty());
0297     d->m_locales.takeLast();
0298 }
0299 
0300 void QtLocalizer::loadCatalog(const QString &path, const QString &catalog)
0301 {
0302     Q_D(QtLocalizer);
0303     auto it = d->m_availableLocales.constBegin();
0304     const auto end = d->m_availableLocales.constEnd();
0305     for (; it != end; ++it) {
0306         auto translator = new QTranslator();
0307         const auto loaded = translator->load(it.key() + QLatin1Char('/') + catalog, path);
0308         if (!loaded)
0309             continue;
0310 
0311         translator->setObjectName(catalog);
0312 
0313         it.value()->themeTranslators.prepend(translator);
0314     }
0315 }
0316 
0317 void QtLocalizer::unloadCatalog(const QString &catalog)
0318 {
0319     Q_D(QtLocalizer);
0320     auto it = d->m_availableLocales.constBegin();
0321     const auto end = d->m_availableLocales.constEnd();
0322     for (; it != end; ++it) {
0323         auto tranIt = (*it)->themeTranslators.begin();
0324         while (tranIt != (*it)->themeTranslators.end()) {
0325             if ((*tranIt)->objectName() == catalog) {
0326                 delete *tranIt;
0327                 tranIt = (*it)->themeTranslators.erase(tranIt);
0328             } else {
0329                 ++tranIt;
0330             }
0331         }
0332     }
0333 }