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