Skip to content

Commit e2fa3e5

Browse files
committed
Multi-lingual changes
1 parent 0fcaf83 commit e2fa3e5

20 files changed

Lines changed: 316 additions & 95 deletions

apps/web/src/components/ActivityFeed.jsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { formatDistanceToNow } from "date-fns";
1111
import { fetchActivityFeed } from "../redux/social/actions";
1212
import { getLanguageLabel } from "../lib/lang";
1313
import ProjectThumbnail from "./ProjectThumbnail";
14+
import { useTranslation } from "@zxplay/i18n";
1415
import { sep } from "../constants";
1516

1617
function getLanguageColor(lang) {
@@ -27,6 +28,7 @@ function getLanguageColor(lang) {
2728
}
2829

2930
export default function ActivityFeed() {
31+
const { t } = useTranslation();
3032
const dispatch = useDispatch();
3133

3234
const currentUserId = useSelector((state) => state?.identity.userId);
@@ -51,10 +53,7 @@ export default function ActivityFeed() {
5153
if (!currentUserId) {
5254
return (
5355
<Card className="m-2">
54-
<Message
55-
severity="info"
56-
text="Please log in to view your activity feed"
57-
/>
56+
<Message severity="info" text={t("feed.loginPrompt")} />
5857
</Card>
5958
);
6059
}
@@ -70,14 +69,19 @@ export default function ActivityFeed() {
7069
return (
7170
<Titled title={(s) => `Feed ${sep} ${s}`}>
7271
<div className="m-2">
73-
<Card title="Feed">
72+
<Card title={t("nav.feed")}>
7473
<p className="text-500 mb-4">
75-
Recent projects from users you follow
74+
{t("feed.subtitle")}
7675
{feedTotalCount > 0 && (
7776
<span className="ml-2">
78-
(showing {feedPage * feedPageSize + 1} -{" "}
79-
{Math.min((feedPage + 1) * feedPageSize, feedTotalCount)} of{" "}
80-
{feedTotalCount})
77+
{t("feed.showing", {
78+
first: feedPage * feedPageSize + 1,
79+
last: Math.min(
80+
(feedPage + 1) * feedPageSize,
81+
feedTotalCount
82+
),
83+
total: feedTotalCount,
84+
})}
8185
</span>
8286
)}
8387
</p>
@@ -126,13 +130,13 @@ export default function ActivityFeed() {
126130

127131
<div className="mt-auto text-400 text-sm relative z-1">
128132
<div>
129-
by @
133+
{t("feed.by")} @
130134
{userSlug ||
131135
project.owner?.greeting_name ||
132136
"unknown"}
133137
</div>
134138
<div>
135-
Updated{" "}
139+
{t("feed.updated")}{" "}
136140
{formatDistanceToNow(
137141
new Date(project.updated_at),
138142
{ addSuffix: true }
@@ -160,10 +164,8 @@ export default function ActivityFeed() {
160164
) : (
161165
<div className="text-center py-4">
162166
<i className="pi pi-inbox text-4xl text-300 mb-3" />
163-
<p className="text-500">No activity yet</p>
164-
<p className="text-sm">
165-
Follow other users to see their public projects here
166-
</p>
167+
<p className="text-500">{t("feed.none")}</p>
168+
<p className="text-sm">{t("feed.followHint")}</p>
167169
</div>
168170
)}
169171
</Card>

apps/web/src/components/AvatarPixelEditor.jsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState, useEffect } from "react";
22
import { Button } from "primereact/button";
3+
import { useTranslation } from "@zxplay/i18n";
34

45
// ZX Spectrum color palette
56
const SPECTRUM_COLORS = [
@@ -21,6 +22,7 @@ const SPECTRUM_COLORS = [
2122
];
2223

2324
export default function AvatarPixelEditor({ identifier, onSave, onCancel, customAvatarData }) {
25+
const { t } = useTranslation();
2426
const [grid, setGrid] = useState(() => {
2527
// Initialize empty 8x8 grid
2628
return Array(8)
@@ -151,7 +153,7 @@ export default function AvatarPixelEditor({ identifier, onSave, onCancel, custom
151153
{/* Left side - Drawing canvas */}
152154
<div className="flex-grow-1">
153155
<div className="mb-2">
154-
<h4 className="m-1">Draw Your Avatar</h4>
156+
<h4 className="m-1">{t("avatar.drawTitle")}</h4>
155157
</div>
156158

157159
<div
@@ -197,25 +199,25 @@ export default function AvatarPixelEditor({ identifier, onSave, onCancel, custom
197199
{/* Tools */}
198200
<div className="mt-3 flex gap-2">
199201
<Button
200-
label="Clear"
202+
label={t("avatar.clear")}
201203
icon="pi pi-trash"
202204
className="p-button-sm p-button-outlined"
203205
onClick={clearGrid}
204206
/>
205207
<Button
206-
label="Fill"
208+
label={t("avatar.fill")}
207209
icon="pi pi-stop"
208210
className="p-button-sm p-button-outlined"
209211
onClick={fillGrid}
210212
/>
211213
<Button
212-
label="Flip"
214+
label={t("avatar.flip")}
213215
icon="pi pi-arrows-h"
214216
className="p-button-sm p-button-outlined"
215217
onClick={flipHorizontal}
216218
/>
217219
<Button
218-
label="Flip"
220+
label={t("avatar.flip")}
219221
icon="pi pi-arrows-v"
220222
className="p-button-sm p-button-outlined"
221223
onClick={flipVertical}
@@ -225,7 +227,7 @@ export default function AvatarPixelEditor({ identifier, onSave, onCancel, custom
225227

226228
{/* Right side - Color palette and preview */}
227229
<div style={{ width: "200px" }}>
228-
<h4 className="m-0 mb-2">Colors</h4>
230+
<h4 className="m-0 mb-2">{t("avatar.colors")}</h4>
229231

230232
{/* Tool selection */}
231233
<div className="flex gap-1 mb-2">
@@ -278,7 +280,7 @@ export default function AvatarPixelEditor({ identifier, onSave, onCancel, custom
278280
</div>
279281

280282
{/* Preview */}
281-
<h4 className="mt-3 mb-2">Preview</h4>
283+
<h4 className="mt-3 mb-2">{t("avatar.preview")}</h4>
282284
<div
283285
style={{
284286
padding: "8px",
@@ -303,12 +305,12 @@ export default function AvatarPixelEditor({ identifier, onSave, onCancel, custom
303305
{/* Action buttons */}
304306
<div className="flex justify-content-end gap-2 mt-3">
305307
<Button
306-
label="Cancel"
308+
label={t("actions.cancel")}
307309
className="p-button-text p-button-sm"
308310
onClick={onCancel}
309311
/>
310312
<Button
311-
label="Use This Avatar"
313+
label={t("avatar.useThis")}
312314
icon="pi pi-check"
313315
className="p-button-sm"
314316
onClick={handleSave}

apps/web/src/components/AvatarSelector.jsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Button } from "primereact/button";
44
import { TabView, TabPanel } from "primereact/tabview";
55
import { generateRetroSpriteAvatar } from "../lib/retroSpriteAvatar";
66
import AvatarPixelEditor from "./AvatarPixelEditor";
7+
import { useTranslation } from "@zxplay/i18n";
78

89
export default function AvatarSelector({
910
visible,
@@ -12,6 +13,7 @@ export default function AvatarSelector({
1213
onSelect,
1314
customAvatarData,
1415
}) {
16+
const { t } = useTranslation();
1517
const [selectedVariant, setSelectedVariant] = useState(0);
1618
const [avatars, setAvatars] = useState([]);
1719
const [currentPage, setCurrentPage] = useState(0);
@@ -90,7 +92,7 @@ export default function AvatarSelector({
9092
<span
9193
className="text-sm px-1 avatar-button-min-80"
9294
>
93-
Page {currentPage + 1}/{totalPages}
95+
{t("avatar.page", { current: currentPage + 1, total: totalPages })}
9496
</span>
9597
<Button
9698
icon="pi pi-chevron-right"
@@ -103,18 +105,18 @@ export default function AvatarSelector({
103105
</div>
104106
<div className="flex gap-2">
105107
<Button
106-
label="Randomize"
108+
label={t("avatar.randomize")}
107109
icon="pi pi-refresh"
108110
className="p-button-outlined p-button-sm"
109111
onClick={handleRandomize}
110112
/>
111113
<Button
112-
label="Cancel"
114+
label={t("actions.cancel")}
113115
className="p-button-text p-button-sm"
114116
onClick={onHide}
115117
/>
116118
<Button
117-
label="Select"
119+
label={t("avatar.select")}
118120
icon="pi pi-check"
119121
className="p-button-sm"
120122
onClick={handleConfirm}
@@ -125,7 +127,7 @@ export default function AvatarSelector({
125127

126128
return (
127129
<Dialog
128-
header="Choose Your Avatar"
130+
header={t("avatar.chooseTitle")}
129131
visible={visible}
130132
onHide={onHide}
131133
footer={footer}
@@ -135,9 +137,9 @@ export default function AvatarSelector({
135137
activeIndex={activeTab}
136138
onTabChange={(e) => setActiveTab(e.index)}
137139
>
138-
<TabPanel header="Selection">
140+
<TabPanel header={t("avatar.selection")}>
139141
<div className="text-center mb-5 mt-3">
140-
<p className="text-500">Each pattern is unique to your username.</p>
142+
<p className="text-500">{t("avatar.unique")}</p>
141143
</div>
142144

143145
<div className="grid">
@@ -178,14 +180,11 @@ export default function AvatarSelector({
178180
</div>
179181

180182
<div className="text-center mt-2 avatar-preview-centered">
181-
<small className="text-500">
182-
Tip: Use arrow buttons to browse more patterns, or click Randomize
183-
for a surprise!
184-
</small>
183+
<small className="text-500">{t("avatar.tip")}</small>
185184
</div>
186185
</TabPanel>
187186

188-
<TabPanel header="Create Your Own">
187+
<TabPanel header={t("avatar.createOwn")}>
189188
<AvatarPixelEditor
190189
identifier={identifier}
191190
onSave={handleCustomAvatarSave}

apps/web/src/components/DemoAssemblyEditor.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import { dashboardLock } from "../dashboard_lock";
88
import { showLoading } from "../dashboard_loading";
99
import LineNumbersToggle from "./LineNumbersToggle";
1010
import { Divider } from "primereact/divider";
11+
import { useTranslation } from "@zxplay/i18n";
1112

1213
export function DemoAssemblyEditor() {
14+
const { t } = useTranslation();
1315
const dispatch = useDispatch();
1416
const cmRef = useRef(null);
1517
const asmCode = useSelector((state) => state?.demo.asmCode);
@@ -47,7 +49,7 @@ export function DemoAssemblyEditor() {
4749
onChange={(cm, _) => dispatch(setAssemblyCode(cm.getValue()))}
4850
/>
4951
<Button
50-
label="Play"
52+
label={t("actions.play")}
5153
icon="pi pi-play"
5254
className="margin-top-8"
5355
onClick={() => {

apps/web/src/components/DemoSinclairBasicEditor.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import {dashboardLock} from "../dashboard_lock";
88
import {showLoading} from "../dashboard_loading";
99
import LineNumbersToggle from "./LineNumbersToggle";
1010
import {Divider} from "primereact/divider";
11+
import {useTranslation} from "@zxplay/i18n";
1112

1213
export function DemoSinclairBasicEditor() {
14+
const {t} = useTranslation();
1315
const dispatch = useDispatch();
1416
const cmRef = useRef(null);
1517
const code = useSelector(state => state?.demo.sinclairBasicCode);
@@ -47,7 +49,7 @@ export function DemoSinclairBasicEditor() {
4749
onChange={(cm, _) => dispatch(setSinclairBasicCode(cm.getValue()))}
4850
/>
4951
<Button
50-
label="Play"
52+
label={t("actions.play")}
5153
icon="pi pi-play"
5254
className="margin-top-8"
5355
onClick={() => {

apps/web/src/components/LineNumbersToggle.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import React from "react";
22
import { useDispatch, useSelector } from "react-redux";
33
import { InputSwitch } from "primereact/inputswitch";
44
import { toggleLineNumbers } from "../redux/app/actions";
5+
import { useTranslation } from "@zxplay/i18n";
56

67
export default function LineNumbersToggle() {
8+
const { t } = useTranslation();
79
const dispatch = useDispatch();
810
const lineNumbers = useSelector((state) => state?.app?.lineNumbers || false);
911

@@ -14,7 +16,7 @@ export default function LineNumbersToggle() {
1416
return (
1517
<div className="flex align-items-center gap-2">
1618
<i className="pi pi-list" />
17-
<span>Line Numbers</span>
19+
<span>{t("pages.lineNumbers")}</span>
1820
<InputSwitch
1921
checked={lineNumbers}
2022
onChange={(e) => handleToggle(e.value)}

apps/web/src/components/NewProjectPage.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,24 @@ import { InputText } from "primereact/inputtext";
77
import { Button } from "primereact/button";
88
import { createNewProject } from "../redux/project/actions";
99
import { getLanguageLabel } from "../lib/lang";
10+
import { useTranslation } from "@zxplay/i18n";
1011
import { sep } from "../constants";
1112

1213
NewProjectPage.propTypes = {
1314
type: PropTypes.string.isRequired,
1415
};
1516

1617
export default function NewProjectPage(props) {
18+
const { t } = useTranslation();
1719
const dispatch = useDispatch();
1820
const [title, setTitle] = useState("");
1921
const lang = getLanguageLabel(props.type);
2022

2123
return (
22-
<Titled title={(s) => `New Project ${sep} ${s}`}>
24+
<Titled title={(s) => `${t("nav.newProject")} ${sep} ${s}`}>
2325
<Card className="m-2">
24-
<h1>New {lang} Project</h1>
25-
<h3>Project Name</h3>
26+
<h1>{t("newProject.title", { lang })}</h1>
27+
<h3>{t("editor.projectName")}</h3>
2628
<div className="field">
2729
<InputText
2830
value={title}
@@ -36,7 +38,7 @@ export default function NewProjectPage(props) {
3638
/>
3739
</div>
3840
<Button
39-
label="Create Project"
41+
label={t("newProject.create")}
4042
onClick={() => dispatch(createNewProject(props.type, title))}
4143
/>
4244
</Card>

0 commit comments

Comments
 (0)