Skip to content

Commit 014e63a

Browse files
Add translations to times and numbers (#1223)
Editor-UI part of RaspberryPiFoundation/digital-editor-issues#588 --------- Co-authored-by: Lois Wells <88904316+loiswells97@users.noreply.github.com> Co-authored-by: Lois Wells <lois.wells@raspberrypi.org>
1 parent a91007c commit 014e63a

6 files changed

Lines changed: 109 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1919
## Changed
2020

2121
- Improved status bar styling (#1221)
22+
- Added method to translate last saved time (#1223)
2223

2324
## [0.30.1] - 2025-06-09
2425

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"classnames": "^2.3.2",
3131
"codemirror": "^6.0.1",
3232
"container-query-polyfill": "^1.0.2",
33-
"date-fns": "^2.29.3",
33+
"date-fns": "^4.1.0",
3434
"eslint-config-prettier": "^8.8.0",
3535
"file-saver": "^2.0.5",
3636
"fs-extra": "^9.0.1",

src/components/SaveStatus/SaveStatus.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { intlFormatDistance } from "date-fns";
2-
31
import React, { useState, useEffect } from "react";
42
import { useSelector } from "react-redux";
53
import { useTranslation } from "react-i18next";
@@ -9,9 +7,13 @@ import CloudTickIcon from "../../assets/icons/cloud_tick.svg";
97
import CloudUploadIcon from "../../assets/icons/cloud_upload.svg";
108

119
import "../../assets/stylesheets/SaveStatus.scss";
10+
import { formatRelativeTime } from "../../utils/formatRelativeTime";
1211

1312
const SaveStatus = ({ isMobile = false }) => {
14-
const { t } = useTranslation();
13+
const { t, i18n } = useTranslation();
14+
const locale = i18n.language;
15+
const fallbackLng = i18n.options.fallbackLng;
16+
1517
const lastSavedTime = useSelector((state) => state.editor.lastSavedTime);
1618
const saving = useSelector((state) => state.editor.saving);
1719
const [time, setTime] = useState(Date.now());
@@ -54,7 +56,7 @@ const SaveStatus = ({ isMobile = false }) => {
5456
</div>
5557
<div className="save-status__text">
5658
{t("saveStatus.saved")}{" "}
57-
{intlFormatDistance(lastSavedTime, time, { style: "narrow" })}
59+
{formatRelativeTime(lastSavedTime, time, locale, fallbackLng)}
5860
</div>
5961
</>
6062
)}

src/utils/formatRelativeTime.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { intlFormatDistance } from "date-fns";
2+
3+
const localeMap = {
4+
"en-US": "en-US",
5+
en: "en-GB",
6+
"fr-FR": "fr",
7+
"es-LA": "es",
8+
};
9+
10+
export const formatRelativeTime = (
11+
lastSavedTime,
12+
now,
13+
languageCode,
14+
fallbackLng,
15+
) => {
16+
const locale = localeMap[languageCode] || fallbackLng;
17+
return intlFormatDistance(lastSavedTime, now, {
18+
style: "short",
19+
locale,
20+
});
21+
};
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { formatRelativeTime } from "./formatRelativeTime";
2+
3+
describe("formatRelativeTime", () => {
4+
describe("formats the times for now in different locales", () => {
5+
const now = Date.now();
6+
7+
test("formats relative time in en", () => {
8+
const result = formatRelativeTime(now, now, "en");
9+
expect(result).toEqual("now");
10+
});
11+
12+
test("formats relative time in en-US", () => {
13+
const result = formatRelativeTime(now, now, "en-US");
14+
expect(result).toEqual("now");
15+
});
16+
17+
test("formats relative time in fr-FR", () => {
18+
const result = formatRelativeTime(now, now, "fr-FR");
19+
expect(result).toEqual("maintenant");
20+
});
21+
22+
test("formats relative time in es-LA", () => {
23+
const result = formatRelativeTime(now, now, "es-LA");
24+
expect(result).toEqual("ahora");
25+
});
26+
});
27+
describe("formats the times for one minute ago in different locales", () => {
28+
const oneMinuteAgo = new Date(Date.now() - 60 * 1000); // 1 minute ago
29+
30+
test("formats relative time in en", () => {
31+
const result = formatRelativeTime(oneMinuteAgo, Date.now(), "en");
32+
expect(result).toEqual("1 min ago");
33+
});
34+
35+
test("formats relative time in en-US", () => {
36+
const result = formatRelativeTime(oneMinuteAgo, Date.now(), "en-US");
37+
expect(result).toEqual("1 min. ago");
38+
});
39+
40+
test("formats relative time in fr-FR", () => {
41+
const result = formatRelativeTime(oneMinuteAgo, Date.now(), "fr-FR");
42+
expect(result).toEqual("il y a 1\u00A0min");
43+
});
44+
45+
test("formats relative time in es-LA", () => {
46+
const result = formatRelativeTime(oneMinuteAgo, Date.now(), "es-LA");
47+
expect(result).toEqual("hace 1 min");
48+
});
49+
});
50+
51+
describe("formats the times for one hour ago in different locales", () => {
52+
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); // 1 hour ago
53+
54+
test("formats relative time in en", () => {
55+
const result = formatRelativeTime(oneHourAgo, Date.now(), "en");
56+
expect(result).toEqual("1 hr ago");
57+
});
58+
59+
test("formats relative time in en-US", () => {
60+
const result = formatRelativeTime(oneHourAgo, Date.now(), "en-US");
61+
expect(result).toEqual("1 hr. ago");
62+
});
63+
64+
test("formats relative time in fr-FR", () => {
65+
const result = formatRelativeTime(oneHourAgo, Date.now(), "fr-FR");
66+
expect(result).toEqual("il y a 1\u00A0h");
67+
});
68+
69+
test("formats relative time in es-LA", () => {
70+
const result = formatRelativeTime(oneHourAgo, Date.now(), "es-LA");
71+
expect(result).toEqual("hace 1 h");
72+
});
73+
});
74+
});

yarn.lock

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,7 +1611,7 @@ __metadata:
16111611
languageName: node
16121612
linkType: hard
16131613

1614-
"@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":
1614+
"@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":
16151615
version: 7.25.7
16161616
resolution: "@babel/runtime@npm:7.25.7"
16171617
dependencies:
@@ -2792,7 +2792,7 @@ __metadata:
27922792
css-loader: 4.3.0
27932793
curl: ^0.1.4
27942794
cypress: 12.12.0
2795-
date-fns: ^2.29.3
2795+
date-fns: ^4.1.0
27962796
dotenv: 8.2.0
27972797
dotenv-expand: 5.1.0
27982798
dotenv-webpack: 8.1.0
@@ -7181,12 +7181,10 @@ __metadata:
71817181
languageName: node
71827182
linkType: hard
71837183

7184-
"date-fns@npm:^2.29.3":
7185-
version: 2.30.0
7186-
resolution: "date-fns@npm:2.30.0"
7187-
dependencies:
7188-
"@babel/runtime": ^7.21.0
7189-
checksum: f7be01523282e9bb06c0cd2693d34f245247a29098527d4420628966a2d9aad154bd0e90a6b1cf66d37adcb769cd108cf8a7bd49d76db0fb119af5cdd13644f4
7184+
"date-fns@npm:^4.1.0":
7185+
version: 4.1.0
7186+
resolution: "date-fns@npm:4.1.0"
7187+
checksum: fb681b242cccabed45494468f64282a7d375ea970e0adbcc5dcc92dcb7aba49b2081c2c9739d41bf71ce89ed68dd73bebfe06ca35129490704775d091895710b
71907188
languageName: node
71917189
linkType: hard
71927190

0 commit comments

Comments
 (0)