File indexing completed on 2024-06-02 05:18:44
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "localizer.h" 0008 0009 #include <KItinerary/JsonLdDocument> 0010 #include <KItinerary/Place> 0011 #include <KItinerary/PriceUtil> 0012 0013 #include <KContacts/Address> 0014 0015 #include <KCountry> 0016 #include <KFormat> 0017 #include <KLocalizedString> 0018 0019 #include <QDateTime> 0020 #include <QLocale> 0021 #include <QMetaProperty> 0022 #include <QTimeZone> 0023 0024 #ifdef Q_OS_ANDROID 0025 #include "kandroidextras/javatypes.h" 0026 #include "kandroidextras/jnisignature.h" 0027 #include "kandroidextras/javalocale.h" 0028 0029 using namespace KAndroidExtras; 0030 #endif 0031 0032 #include <cmath> 0033 #include <cstring> 0034 0035 using namespace KItinerary; 0036 0037 static QString readFromGadget(const QMetaObject *mo, const QVariant &gadget, const char *propName) 0038 { 0039 const auto propIdx = mo->indexOfProperty(propName); 0040 if (propIdx < 0) { 0041 return {}; 0042 } 0043 const auto prop = mo->property(propIdx); 0044 if (!prop.isValid()) { 0045 return {}; 0046 } 0047 return prop.readOnGadget(gadget.constData()).toString(); 0048 } 0049 0050 static KContacts::Address variantToKContactsAddress(const QVariant &obj) 0051 { 0052 KContacts::Address address; 0053 if (JsonLd::isA<PostalAddress>(obj)) { 0054 const auto a = obj.value<PostalAddress>(); 0055 address.setStreet(a.streetAddress()); 0056 address.setPostalCode(a.postalCode()); 0057 address.setLocality(a.addressLocality()); 0058 address.setRegion(a.addressRegion()); 0059 address.setCountry(a.addressCountry()); 0060 } else if (std::strcmp(obj.typeName(), "KOSMIndoorMap::OSMAddress") == 0) { 0061 const auto mo = QMetaType(obj.userType()).metaObject(); 0062 address.setStreet(readFromGadget(mo, obj, "street") + QLatin1Char(' ') + readFromGadget(mo, obj, "houseNumber")); 0063 address.setPostalCode(readFromGadget(mo, obj, "postalCode")); 0064 address.setLocality(readFromGadget(mo, obj, "city")); 0065 address.setRegion(readFromGadget(mo, obj, "state")); 0066 address.setCountry(readFromGadget(mo, obj, "country")); 0067 } 0068 return address; 0069 } 0070 0071 QString Localizer::formatAddress(const QVariant &obj) const 0072 { 0073 KContacts::Address address = variantToKContactsAddress(obj); 0074 if (address.isEmpty()) { 0075 return {}; 0076 } 0077 return address.formatted(KContacts::AddressFormatStyle::MultiLineInternational); 0078 } 0079 0080 static bool addressEmptyExceptForCountry(const KContacts::Address &address) 0081 { 0082 return address.street().isEmpty() && address.locality().isEmpty() 0083 && address.postalCode().isEmpty() && address.region().isEmpty() 0084 && !address.country().isEmpty(); 0085 } 0086 0087 QString Localizer::formatAddressWithContext(const QVariant &obj, const QVariant &otherObj, const QString &homeCountryIsoCode) 0088 { 0089 const KContacts::Address address = variantToKContactsAddress(obj); 0090 0091 if (address.isEmpty()) { 0092 return {}; 0093 } 0094 0095 const bool includeCountry = [&] { 0096 if (homeCountryIsoCode.isEmpty()) { 0097 return true; 0098 } 0099 0100 if (!addressEmptyExceptForCountry(address)) { 0101 return true; 0102 } 0103 0104 if (address.country() != homeCountryIsoCode) { 0105 return true; 0106 } 0107 0108 const KContacts::Address otherAddress = variantToKContactsAddress(otherObj); 0109 if (!otherAddress.country().isEmpty() && address.country() != otherAddress.country()) { 0110 return true; 0111 } 0112 0113 return false; 0114 }(); 0115 0116 if (includeCountry) { 0117 return address.formatted(KContacts::AddressFormatStyle::MultiLineInternational); 0118 } else { 0119 return address.formatted(KContacts::AddressFormatStyle::MultiLineDomestic); 0120 } 0121 } 0122 0123 static bool needsTimeZone(const QDateTime &dt) 0124 { 0125 if (dt.timeSpec() == Qt::TimeZone && dt.timeZone().abbreviation(dt) != QTimeZone::systemTimeZone().abbreviation(dt)) { 0126 return true; 0127 } else if (dt.timeSpec() == Qt::OffsetFromUTC && dt.timeZone().offsetFromUtc(dt) != dt.offsetFromUtc()) { 0128 return true; 0129 } else if (dt.timeSpec() == Qt::UTC && QTimeZone::systemTimeZone() != QTimeZone::utc()) { 0130 return true; 0131 } 0132 return false; 0133 } 0134 0135 static QString tzAbbreviation(const QDateTime &dt) 0136 { 0137 const auto tz = dt.timeZone(); 0138 0139 #ifdef Q_OS_ANDROID 0140 // the QTimeZone backend implementation on Android isn't as complete as the desktop ones, so we need to do this ourselves here 0141 // eventually, this should be upstreamed to Qt 0142 auto abbr = QJniObject::callStaticObjectMethod("org/kde/itinerary/QTimeZone", "abbreviation", 0143 Jni::signature<java::lang::String(java::lang::String, jlong, java::util::Locale, bool)>(), 0144 QJniObject::fromString(QString::fromUtf8(tz.id())).object(), dt.toMSecsSinceEpoch(), 0145 KAndroidExtras::Locale::current().object(), tz.isDaylightTime(dt)).toString(); 0146 0147 if (!abbr.isEmpty()) { 0148 return abbr; 0149 } 0150 #endif 0151 0152 return tz.abbreviation(dt); 0153 } 0154 0155 QString Localizer::formatTime(const QVariant &obj, const QString &propertyName) const 0156 { 0157 const auto dt = JsonLdDocument::readProperty(obj, propertyName.toUtf8().constData()).toDateTime(); 0158 if (!dt.isValid()) { 0159 return {}; 0160 } 0161 0162 QString output; 0163 if (QLocale().timeFormat(QLocale::ShortFormat).contains(QStringLiteral("ss"))) { 0164 output = QLocale().toString(dt.time(), QStringLiteral("hh:mm")); 0165 } else { 0166 output = QLocale().toString(dt.time(), QLocale::ShortFormat); 0167 } 0168 if (needsTimeZone(dt)) { 0169 output += QLatin1Char(' ') + tzAbbreviation(dt); 0170 } 0171 return output; 0172 } 0173 0174 QString Localizer::formatDate(const QVariant &obj, const QString &propertyName) const 0175 { 0176 const auto dt = JsonLdDocument::readProperty(obj, propertyName.toUtf8().constData()).toDate(); 0177 if (!dt.isValid()) { 0178 return {}; 0179 } 0180 0181 if (dt.year() <= 1900) { // no year specified 0182 return dt.toString(i18nc("day-only date format", "dd MMMM")); 0183 } 0184 return QLocale().toString(dt, QLocale::ShortFormat); 0185 } 0186 0187 QString Localizer::formatDateTime(const QVariant& obj, const QString& propertyName) const 0188 { 0189 const auto dt = JsonLdDocument::readProperty(obj, propertyName.toUtf8().constData()).toDateTime(); 0190 if (!dt.isValid()) { 0191 return {}; 0192 } 0193 0194 auto s = QLocale().toString(dt, QLocale::ShortFormat); 0195 if (needsTimeZone(dt)) { 0196 s += QLatin1Char(' ') + tzAbbreviation(dt); 0197 } 0198 return s; 0199 } 0200 0201 QString Localizer::formatDateOrDateTimeLocal(const QVariant& obj, const QString& propertyName) const 0202 { 0203 const auto dt = JsonLdDocument::readProperty(obj, propertyName.toUtf8().constData()).toDateTime(); 0204 if (!dt.isValid()) { 0205 return {}; 0206 } 0207 0208 // detect likely date-only values 0209 if (dt.timeSpec() == Qt::LocalTime && (dt.time() == QTime{0, 0, 0} || dt.time() == QTime{23, 59, 59})) { 0210 return QLocale().toString(dt.date(), QLocale::ShortFormat); 0211 } 0212 0213 return QLocale().toString(dt.toLocalTime(), QLocale::ShortFormat); 0214 } 0215 0216 QString Localizer::formatDuration(int seconds) const 0217 { 0218 if (seconds < 0) { 0219 return QLocale().negativeSign() + KFormat().formatDuration((-seconds * 1000), KFormat::HideSeconds); 0220 } 0221 return KFormat().formatDuration(seconds * 1000, KFormat::HideSeconds); 0222 } 0223 0224 QString Localizer::formatDistance(int meter) 0225 { 0226 if (meter < 1000) { 0227 return i18nc("distance in meter", "%1 m", meter); 0228 } 0229 if (meter < 10000) { 0230 return i18nc("distance in kilometer", "%1 km", ((int)meter/100)/10.0); 0231 } 0232 return i18nc("distance in kilometer", "%1 km", (int)qRound(meter/1000.0)); 0233 } 0234 0235 QString Localizer::formatSpeed(int km_per_hour) 0236 { 0237 // TODO locale-specific unit conversion 0238 return i18nc("speed in kilometers per hour", "%1 km/h", km_per_hour); 0239 } 0240 0241 QString Localizer::formatWeight(int gram) 0242 { 0243 if (gram < 1000) { 0244 return i18nc("weight in gram", "%1 g", gram); 0245 } 0246 if (gram < 10000) { 0247 return i18nc("weight in kilogram", "%1 kg", ((int)gram/100)/10.0); 0248 } 0249 return i18nc("weight in kilogram", "%1 kg", (int)qRound(gram/1000.0)); 0250 0251 } 0252 0253 QString Localizer::formatTemperature(double temperature) 0254 { 0255 return i18nc("temperature", "%1°C", (int)qRound(temperature)); 0256 } 0257 0258 QString Localizer::formatCurrency(double value, const QString &isoCode) 0259 { 0260 const auto decimalCount = PriceUtil::decimalCount(isoCode); 0261 0262 // special case for displaying conversion rates (which can be very small) 0263 // and thus need a higher precision than regular values 0264 double i = 0.0; 0265 double f = std::modf(value * std::pow(10, decimalCount), &i); 0266 if (i == 0.0 && f > 0.0) { 0267 return QLocale().toCurrencyString(value, isoCode); 0268 } 0269 0270 return QLocale().toCurrencyString(value, isoCode, decimalCount); 0271 } 0272 0273 #include "moc_localizer.cpp"