From 20c6cfcfad5e6c63ff1e8473503671d78faf0a5f Mon Sep 17 00:00:00 2001 From: Mercury233 Date: Tue, 11 Nov 2025 10:34:07 +0800 Subject: [PATCH] Fix(i18n): refactor secondsToHumanReadableFormat (#6281) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Frank Elsinga --- src/mixins/lang.js | 4 ++-- src/pages/Details.vue | 4 ++-- src/pages/EditMonitor.vue | 6 +++--- src/util-frontend.js | 39 +++++++++++++++++++++++++++++---------- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/mixins/lang.js b/src/mixins/lang.js index 0fff8cdc8..508e15c28 100644 --- a/src/mixins/lang.js +++ b/src/mixins/lang.js @@ -1,5 +1,5 @@ import { currentLocale } from "../i18n"; -import { setPageLocale, relativeTimeFormatter } from "../util-frontend"; +import { setPageLocale, timeDurationFormatter } from "../util-frontend"; const langModules = import.meta.glob("../lang/*.json"); export default { @@ -34,7 +34,7 @@ export default { this.$i18n.locale = lang; localStorage.locale = lang; setPageLocale(); - relativeTimeFormatter.updateLocale(lang); + timeDurationFormatter.updateLocale(lang); }, }, }; diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 0ba0a44eb..3538e746a 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -487,7 +487,7 @@ import { getMonitorRelativeURL } from "../util.ts"; import { URL } from "whatwg-url"; import DOMPurify from "dompurify"; import { marked } from "marked"; -import { getResBaseURL, relativeTimeFormatter } from "../util-frontend"; +import { getResBaseURL, timeDurationFormatter } from "../util-frontend"; import { highlight, languages } from "prismjs/components/prism-core"; import "prismjs/components/prism-clike"; import "prismjs/components/prism-javascript"; @@ -928,7 +928,7 @@ export default { }, secondsToHumanReadableFormat(seconds) { - return relativeTimeFormatter.secondsToHumanReadableFormat(seconds); + return timeDurationFormatter.secondsToHumanReadableFormat(seconds); }, }, }; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 425cf4ff5..b5213ef5e 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -1174,7 +1174,7 @@ import { MIN_INTERVAL_SECOND, sleep, } from "../util.ts"; -import { hostNameRegexPattern, relativeTimeFormatter } from "../util-frontend"; +import { hostNameRegexPattern, timeDurationFormatter } from "../util-frontend"; import HiddenInput from "../components/HiddenInput.vue"; import EditMonitorConditions from "../components/EditMonitorConditions.vue"; @@ -1190,7 +1190,7 @@ const monitorDefaults = { method: "GET", ipFamily: null, interval: 60, - humanReadableInterval: relativeTimeFormatter.secondsToHumanReadableFormat(60), + humanReadableInterval: timeDurationFormatter.secondsToHumanReadableFormat(60), retryInterval: 60, resendInterval: 0, maxretries: 0, @@ -1574,7 +1574,7 @@ message HealthCheckResponse { this.monitor.retryInterval = value; } // Converting monitor.interval to human readable format. - this.monitor.humanReadableInterval = relativeTimeFormatter.secondsToHumanReadableFormat(value); + this.monitor.humanReadableInterval = timeDurationFormatter.secondsToHumanReadableFormat(value); }, "monitor.timeout"(value, oldValue) { diff --git a/src/util-frontend.js b/src/util-frontend.js index 7ed3a0cf2..fdb2a8157 100644 --- a/src/util-frontend.js +++ b/src/util-frontend.js @@ -214,13 +214,18 @@ export function getToastErrorTimeout() { return errorTimeout; } -class RelativeTimeFormatter { +class TimeDurationFormatter { /** - * Default locale and options for Relative Time Formatter + * Default locale and options for Time Duration Formatter (supports both DurationFormat and RelativeTimeFormat) */ constructor() { - this.options = { numeric: "always" }; - this.instance = new Intl.RelativeTimeFormat(currentLocale(), this.options); + this.durationFormatOptions = { style: "long" }; + this.relativeTimeFormatOptions = { numeric: "always" }; + if (Intl.DurationFormat !== undefined) { + this.durationFormatInstance = new Intl.DurationFormat(currentLocale(), this.durationFormatOptions); + } else { + this.relativeTimeFormatInstance = new Intl.RelativeTimeFormat(currentLocale(), this.relativeTimeFormatOptions); + } } /** @@ -229,7 +234,11 @@ class RelativeTimeFormatter { * @returns {void} No return value. */ updateLocale(locale) { - this.instance = new Intl.RelativeTimeFormat(locale, this.options); + if (Intl.DurationFormat !== undefined) { + this.durationFormatInstance = new Intl.DurationFormat(locale, this.durationFormatOptions); + } else { + this.relativeTimeFormatInstance = new Intl.RelativeTimeFormat(locale, this.relativeTimeFormatOptions); + } } /** @@ -242,6 +251,17 @@ class RelativeTimeFormatter { const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor(((seconds % 86400) % 3600) / 60); const secs = ((seconds % 86400) % 3600) % 60; + + if (this.durationFormatInstance !== undefined) { + // use Intl.DurationFormat if available + return this.durationFormatInstance.format({ + days, + hours, + minutes, + seconds: secs + }); + } + const parts = []; /** * Build the formatted string from parts @@ -253,12 +273,11 @@ class RelativeTimeFormatter { * @returns {void} */ const toFormattedPart = (value, unitOfTime) => { - const partsArray = this.instance.formatToParts(value, unitOfTime); + const partsArray = this.relativeTimeFormatInstance.formatToParts(value, unitOfTime); const filteredParts = partsArray .filter( (part, index) => - (part.type === "literal" || part.type === "integer") && - index > 0 + part.type === "integer" || (part.type === "literal" && index > 0) ) .map((part) => part.value); @@ -282,9 +301,9 @@ class RelativeTimeFormatter { if (parts.length > 0) { return `${parts.join(" ")}`; } - return this.instance.format(0, "second"); // Handle case for 0 seconds + return this.relativeTimeFormatInstance.format(0, "second"); // Handle case for 0 seconds } } -export const relativeTimeFormatter = new RelativeTimeFormatter(); +export const timeDurationFormatter = new TimeDurationFormatter();