From 0d0b3d907c364d946f31487bb61607d2cc0fa965 Mon Sep 17 00:00:00 2001 From: Wang Zichong Date: Thu, 5 Jun 2025 19:46:29 +0800 Subject: [PATCH] chore: use ICU for relative dateime formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用 ICU 提供相对时间格式的时间展示,避免拼接字符串造成不易于本地化 的问题. Log: --- CMakeLists.txt | 1 + debian/control | 1 + panels/notification/CMakeLists.txt | 3 ++ panels/notification/center/notifyitem.cpp | 61 ++++++++++++++++++++--- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bda99aac..4d9b3903e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ include(KDEGitCommitHooks) find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Concurrent Quick WaylandClient DBus LinguistTools Sql) find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui) +find_package(ICU 74.2 REQUIRED COMPONENTS uc i18n io) find_package(WaylandProtocols REQUIRED) find_package(PkgConfig REQUIRED) diff --git a/debian/control b/debian/control index 46b1c14a3..6ebe496da 100644 --- a/debian/control +++ b/debian/control @@ -21,6 +21,7 @@ Build-Depends: libdtk6core-dev, libdtk6widget-dev, libdtkcommon-dev, + libicu-dev, libxcb-ewmh-dev, libxcb-res0-dev, libxcb-util-dev, diff --git a/panels/notification/CMakeLists.txt b/panels/notification/CMakeLists.txt index 83c3eb0b4..dc7bc1b2c 100644 --- a/panels/notification/CMakeLists.txt +++ b/panels/notification/CMakeLists.txt @@ -35,6 +35,9 @@ target_link_libraries(ds-notification-shared PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Sql Dtk${DTK_VERSION_MAJOR}::Core + ICU::uc + ICU::i18n + ICU::io ) install(TARGETS ds-notification-shared DESTINATION "${LIB_INSTALL_DIR}") diff --git a/panels/notification/center/notifyitem.cpp b/panels/notification/center/notifyitem.cpp index f76374e76..c6bb6abb5 100644 --- a/panels/notification/center/notifyitem.cpp +++ b/panels/notification/center/notifyitem.cpp @@ -7,6 +7,9 @@ #include #include +#include // For RelativeDateTimeFormatter +#include // For SimpleDateFormat + #include "notifyaccessor.h" namespace notification { @@ -62,12 +65,48 @@ QString AppNotifyItem::time() const return m_time; } +namespace +{ +QString toQString(const icu::UnicodeString &icuString) +{ + // Get a pointer to the internal UTF-16 buffer of the icu::UnicodeString. + // The buffer is not necessarily null-terminated, so we also need the length. + const UChar *ucharData = icuString.getBuffer(); + int32_t length = icuString.length(); + + // QString has a constructor that takes a const QChar* and a length. + // UChar is typically a 16-bit unsigned integer, which is compatible with QChar. + // Static_cast is used here for explicit type conversion, though often + // UChar and QChar are typedefs to the same underlying type (e.g., unsigned short). + return QString(reinterpret_cast(ucharData), length); +} + +icu::UnicodeString fromQString(const QString &qstr) +{ + return icu::UnicodeString(qstr.utf16(), qstr.length()); +} +} // anonymous namespace + void AppNotifyItem::updateTime() { QDateTime time = QDateTime::fromMSecsSinceEpoch(m_entity.cTime()); if (!time.isValid()) return; + using namespace icu; + static std::unique_ptr formatter; + static UErrorCode cachedStatus = U_ZERO_ERROR; + if (!formatter) { + cachedStatus = U_ZERO_ERROR; + formatter = std::make_unique(icu::Locale::getDefault(), + nullptr, // Use default NumberFormat + UDAT_STYLE_LONG, + UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE, + cachedStatus); + } + UErrorCode status = U_ZERO_ERROR; // For any per-call ICU operations + UnicodeString result; + QString ret; QDateTime currentTime = QDateTime::currentDateTime(); auto elapsedDay = time.daysTo(currentTime); @@ -77,21 +116,27 @@ void AppNotifyItem::updateTime() if (minute <= 0) { ret = tr("Just now"); } else if (minute > 0 && minute < 60) { - ret = tr("%1 minutes ago").arg(minute); + formatter->format(minute, UDAT_DIRECTION_LAST, UDAT_RELATIVE_MINUTES, result, status); + ret = toQString(result); } else { const auto hour = minute / 60; - if (hour == 1) { - ret = tr("1 hour ago"); - } else { - ret = tr("%1 hours ago").arg(hour); - } + formatter->format(hour, UDAT_DIRECTION_LAST, UDAT_RELATIVE_HOURS, result, status); + ret = toQString(result); } } else if (elapsedDay >= 1 && elapsedDay < 2) { - ret = tr("Yesterday ") + " " + time.toString("hh:mm"); + formatter->format(1, UDAT_DIRECTION_LAST, UDAT_RELATIVE_DAYS, result, status); + UnicodeString combinedString; + UErrorCode timeStatus = U_ZERO_ERROR; + SimpleDateFormat timeFormatter("HH:mm", icu::Locale::getDefault(), timeStatus); + UnicodeString timeString; + UDate udate = static_cast(m_entity.cTime()); + timeFormatter.format(udate, timeString, timeStatus); + formatter->combineDateAndTime(result, timeString, combinedString, status); + ret = toQString(combinedString); } else if (elapsedDay >= 2 && elapsedDay < 7) { ret = time.toString("ddd hh:mm"); } else { - ret = time.toString("yyyy/MM/dd"); + ret = time.toString(QLocale::system().dateFormat(QLocale::ShortFormat)); } m_time = ret;