diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a1f0b50..186930f33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Changed - Improved status bar styling (#1221) +- Added method to translate last saved time (#1223) ## [0.30.1] - 2025-06-09 diff --git a/package.json b/package.json index 8eba613a0..5ff4c4c5b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "classnames": "^2.3.2", "codemirror": "^6.0.1", "container-query-polyfill": "^1.0.2", - "date-fns": "^2.29.3", + "date-fns": "^4.1.0", "eslint-config-prettier": "^8.8.0", "file-saver": "^2.0.5", "fs-extra": "^9.0.1", diff --git a/src/components/SaveStatus/SaveStatus.jsx b/src/components/SaveStatus/SaveStatus.jsx index 04bd8099a..7f504b3c9 100644 --- a/src/components/SaveStatus/SaveStatus.jsx +++ b/src/components/SaveStatus/SaveStatus.jsx @@ -1,5 +1,3 @@ -import { intlFormatDistance } from "date-fns"; - import React, { useState, useEffect } from "react"; import { useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; @@ -9,9 +7,13 @@ import CloudTickIcon from "../../assets/icons/cloud_tick.svg"; import CloudUploadIcon from "../../assets/icons/cloud_upload.svg"; import "../../assets/stylesheets/SaveStatus.scss"; +import { formatRelativeTime } from "../../utils/formatRelativeTime"; const SaveStatus = ({ isMobile = false }) => { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); + const locale = i18n.language; + const fallbackLng = i18n.options.fallbackLng; + const lastSavedTime = useSelector((state) => state.editor.lastSavedTime); const saving = useSelector((state) => state.editor.saving); const [time, setTime] = useState(Date.now()); @@ -54,7 +56,7 @@ const SaveStatus = ({ isMobile = false }) => {
{t("saveStatus.saved")}{" "} - {intlFormatDistance(lastSavedTime, time, { style: "narrow" })} + {formatRelativeTime(lastSavedTime, time, locale, fallbackLng)}
)} diff --git a/src/utils/formatRelativeTime.js b/src/utils/formatRelativeTime.js new file mode 100644 index 000000000..b520d619c --- /dev/null +++ b/src/utils/formatRelativeTime.js @@ -0,0 +1,21 @@ +import { intlFormatDistance } from "date-fns"; + +const localeMap = { + "en-US": "en-US", + en: "en-GB", + "fr-FR": "fr", + "es-LA": "es", +}; + +export const formatRelativeTime = ( + lastSavedTime, + now, + languageCode, + fallbackLng, +) => { + const locale = localeMap[languageCode] || fallbackLng; + return intlFormatDistance(lastSavedTime, now, { + style: "short", + locale, + }); +}; diff --git a/src/utils/formatRelativeTime.test.js b/src/utils/formatRelativeTime.test.js new file mode 100644 index 000000000..d5dbe5b88 --- /dev/null +++ b/src/utils/formatRelativeTime.test.js @@ -0,0 +1,74 @@ +import { formatRelativeTime } from "./formatRelativeTime"; + +describe("formatRelativeTime", () => { + describe("formats the times for now in different locales", () => { + const now = Date.now(); + + test("formats relative time in en", () => { + const result = formatRelativeTime(now, now, "en"); + expect(result).toEqual("now"); + }); + + test("formats relative time in en-US", () => { + const result = formatRelativeTime(now, now, "en-US"); + expect(result).toEqual("now"); + }); + + test("formats relative time in fr-FR", () => { + const result = formatRelativeTime(now, now, "fr-FR"); + expect(result).toEqual("maintenant"); + }); + + test("formats relative time in es-LA", () => { + const result = formatRelativeTime(now, now, "es-LA"); + expect(result).toEqual("ahora"); + }); + }); + describe("formats the times for one minute ago in different locales", () => { + const oneMinuteAgo = new Date(Date.now() - 60 * 1000); // 1 minute ago + + test("formats relative time in en", () => { + const result = formatRelativeTime(oneMinuteAgo, Date.now(), "en"); + expect(result).toEqual("1 min ago"); + }); + + test("formats relative time in en-US", () => { + const result = formatRelativeTime(oneMinuteAgo, Date.now(), "en-US"); + expect(result).toEqual("1 min. ago"); + }); + + test("formats relative time in fr-FR", () => { + const result = formatRelativeTime(oneMinuteAgo, Date.now(), "fr-FR"); + expect(result).toEqual("il y a 1\u00A0min"); + }); + + test("formats relative time in es-LA", () => { + const result = formatRelativeTime(oneMinuteAgo, Date.now(), "es-LA"); + expect(result).toEqual("hace 1 min"); + }); + }); + + describe("formats the times for one hour ago in different locales", () => { + const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); // 1 hour ago + + test("formats relative time in en", () => { + const result = formatRelativeTime(oneHourAgo, Date.now(), "en"); + expect(result).toEqual("1 hr ago"); + }); + + test("formats relative time in en-US", () => { + const result = formatRelativeTime(oneHourAgo, Date.now(), "en-US"); + expect(result).toEqual("1 hr. ago"); + }); + + test("formats relative time in fr-FR", () => { + const result = formatRelativeTime(oneHourAgo, Date.now(), "fr-FR"); + expect(result).toEqual("il y a 1\u00A0h"); + }); + + test("formats relative time in es-LA", () => { + const result = formatRelativeTime(oneHourAgo, Date.now(), "es-LA"); + expect(result).toEqual("hace 1 h"); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index f800fb29e..f2520e018 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1611,7 +1611,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": version: 7.25.7 resolution: "@babel/runtime@npm:7.25.7" dependencies: @@ -2792,7 +2792,7 @@ __metadata: css-loader: 4.3.0 curl: ^0.1.4 cypress: 12.12.0 - date-fns: ^2.29.3 + date-fns: ^4.1.0 dotenv: 8.2.0 dotenv-expand: 5.1.0 dotenv-webpack: 8.1.0 @@ -7171,12 +7171,10 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:^2.29.3": - version: 2.30.0 - resolution: "date-fns@npm:2.30.0" - dependencies: - "@babel/runtime": ^7.21.0 - checksum: f7be01523282e9bb06c0cd2693d34f245247a29098527d4420628966a2d9aad154bd0e90a6b1cf66d37adcb769cd108cf8a7bd49d76db0fb119af5cdd13644f4 +"date-fns@npm:^4.1.0": + version: 4.1.0 + resolution: "date-fns@npm:4.1.0" + checksum: fb681b242cccabed45494468f64282a7d375ea970e0adbcc5dcc92dcb7aba49b2081c2c9739d41bf71ce89ed68dd73bebfe06ca35129490704775d091895710b languageName: node linkType: hard