Skip to content

Commit 1045e53

Browse files
committed
i18n: translate the review comments feature
The comments panel, thread cards, and Add Comment dialog rendered ~39 strings outside t(), so they stayed English in every locale even at 100% catalog coverage. This wraps them and translates them across all 17. Existing keys are reused rather than duplicated: comments.title, comments.defaultAuthorName, collaborate.sessionCode, collaborate.copied, collaborate.connecting, and common.cancel. Counts interpolate with {{count}} but take no plural suffix: the number sits in parentheses and no noun agrees with it, so a single form is correct in every language. Verified in a browser against the built app in German and Arabic, including RTL mirroring and the <strong> interpolation in postingAs.
1 parent 7c92830 commit 1045e53

21 files changed

Lines changed: 780 additions & 66 deletions

File tree

apps/geolibre-desktop/src/components/comments/AddCommentDialog.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from "react";
2-
import { useTranslation } from "react-i18next";
2+
import { Trans, useTranslation } from "react-i18next";
33
import {
44
Button,
55
Dialog,
@@ -46,7 +46,7 @@ export function AddCommentDialog({ pendingComment, onSubmit, onCancel }: AddComm
4646
e.preventDefault();
4747
if (!text.trim()) return;
4848

49-
const finalName = authorName.trim() || savedName?.trim() || "Author";
49+
const finalName = authorName.trim() || savedName?.trim() || t("comments.defaultAuthorName");
5050
if (typeof localStorage !== "undefined" && finalName) {
5151
try {
5252
localStorage.setItem("geolibre_author_name", finalName);
@@ -99,15 +99,21 @@ export function AddCommentDialog({ pendingComment, onSubmit, onCancel }: AddComm
9999
<>
100100
<Layers className="h-3.5 w-3.5 text-sky-400 shrink-0" />
101101
<span className="truncate font-medium text-foreground">
102-
Anchored to Feature #{String(anchor.featureId)} ({anchor.layerId})
102+
{t("comments.anchoredToFeature", {
103+
id: String(anchor.featureId),
104+
layer: anchor.layerId,
105+
})}
103106
</span>
104107
</>
105108
) : (
106109
<>
107110
<MapPin className="h-3.5 w-3.5 text-amber-400 shrink-0" />
108111
<span className="font-medium text-foreground">
109-
Anchored to (
110-
{lngLat ? `${lngLat[1].toFixed(4)}, ${lngLat[0].toFixed(4)}` : "Map Point"})
112+
{t("comments.anchoredToPoint", {
113+
coords: lngLat
114+
? `${lngLat[1].toFixed(4)}, ${lngLat[0].toFixed(4)}`
115+
: t("comments.mapPoint"),
116+
})}
111117
</span>
112118
</>
113119
)}
@@ -135,7 +141,11 @@ export function AddCommentDialog({ pendingComment, onSubmit, onCancel }: AddComm
135141
) : (
136142
<div className="flex items-center justify-between text-[11px] text-muted-foreground px-1">
137143
<span>
138-
Posting as <strong className="text-foreground">{savedName}</strong>
144+
<Trans
145+
i18nKey="comments.postingAs"
146+
values={{ name: savedName }}
147+
components={{ strong: <strong className="text-foreground" /> }}
148+
/>
139149
</span>
140150
<button
141151
type="button"
@@ -150,7 +160,7 @@ export function AddCommentDialog({ pendingComment, onSubmit, onCancel }: AddComm
150160
}}
151161
className="text-primary hover:underline text-[10px]"
152162
>
153-
Change Name
163+
{t("comments.changeName")}
154164
</button>
155165
</div>
156166
)}
@@ -175,7 +185,7 @@ export function AddCommentDialog({ pendingComment, onSubmit, onCancel }: AddComm
175185

176186
<div className="flex items-center justify-end gap-2 pt-1">
177187
<Button type="button" variant="outline" size="sm" onClick={onCancel}>
178-
Cancel
188+
{t("common.cancel")}
179189
</Button>
180190
<Button
181191
type="submit"

apps/geolibre-desktop/src/components/comments/CommentThread.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function CommentThread({
6464
style={{ backgroundColor: comment.author?.color || "#3b82f6" }}
6565
/>
6666
<span className="font-semibold text-foreground truncate">
67-
{comment.author?.name || "Author"}
67+
{comment.author?.name || t("comments.defaultAuthorName")}
6868
</span>
6969
<span className="text-[10px] text-muted-foreground">
7070
{new Date(comment.createdAt).toLocaleDateString(undefined, {
@@ -76,7 +76,7 @@ export function CommentThread({
7676
</span>
7777
{comment.resolved && (
7878
<span className="text-[10px] bg-emerald-500/10 text-emerald-500 font-medium px-1.5 py-0.5 rounded border border-emerald-500/20">
79-
Resolved
79+
{t("comments.resolvedBadge")}
8080
</span>
8181
)}
8282
</div>
@@ -88,7 +88,7 @@ export function CommentThread({
8888
size="icon"
8989
className="h-7 w-7 text-muted-foreground hover:text-foreground"
9090
onClick={() => onZoomTo(comment)}
91-
title="Zoom to location"
91+
title={t("comments.zoomToLocation")}
9292
>
9393
<Navigation className="h-3.5 w-3.5" />
9494
</Button>
@@ -106,7 +106,7 @@ export function CommentThread({
106106
: "text-muted-foreground hover:text-emerald-500",
107107
)}
108108
onClick={() => onToggleResolve(comment.id, !comment.resolved)}
109-
title={comment.resolved ? "Reopen comment thread" : "Mark as resolved"}
109+
title={comment.resolved ? t("comments.reopenThread") : t("comments.markResolved")}
110110
>
111111
{comment.resolved ? (
112112
<RotateCcw className="h-3.5 w-3.5" />
@@ -128,7 +128,7 @@ export function CommentThread({
128128
onDelete(comment.id);
129129
}
130130
}}
131-
title="Delete comment thread"
131+
title={t("comments.deleteThread")}
132132
>
133133
<Trash2 className="h-3.5 w-3.5" />
134134
</Button>
@@ -143,18 +143,21 @@ export function CommentThread({
143143
<>
144144
<Layers className="h-3 w-3 text-sky-400 shrink-0" />
145145
<span className="truncate">
146-
Feature #{String(comment.anchor.featureId)} ({comment.anchor.layerId})
146+
{t("comments.anchorFeature", {
147+
id: String(comment.anchor.featureId),
148+
layer: comment.anchor.layerId,
149+
})}
147150
</span>
148151
</>
149152
) : (
150153
<>
151154
<MapPin className="h-3 w-3 text-amber-400 shrink-0" />
152155
<span>
153-
Point (
154-
{comment.anchor.lngLat
155-
? `${comment.anchor.lngLat[1].toFixed(4)}, ${comment.anchor.lngLat[0].toFixed(4)}`
156-
: "Map"}
157-
)
156+
{t("comments.anchorPoint", {
157+
coords: comment.anchor.lngLat
158+
? `${comment.anchor.lngLat[1].toFixed(4)}, ${comment.anchor.lngLat[0].toFixed(4)}`
159+
: t("comments.anchorMap"),
160+
})}
158161
</span>
159162
</>
160163
)}
@@ -174,7 +177,7 @@ export function CommentThread({
174177
style={{ backgroundColor: reply.author?.color || "#3b82f6" }}
175178
/>
176179
<span className="font-semibold text-foreground text-[11px]">
177-
{reply.author?.name || "Author"}
180+
{reply.author?.name || t("comments.defaultAuthorName")}
178181
</span>
179182
<span className="text-[10px] text-muted-foreground">
180183
{new Date(reply.createdAt).toLocaleDateString(undefined, {
@@ -202,14 +205,14 @@ export function CommentThread({
202205
onClick={() => setIsReplying(true)}
203206
>
204207
<CornerDownRight className="h-3 w-3" />
205-
<span>Reply</span>
208+
<span>{t("comments.reply")}</span>
206209
</Button>
207210
) : (
208211
<form onSubmit={handleSubmitReply} className="mt-2 space-y-2">
209212
<Textarea
210213
value={replyText}
211214
onChange={(e) => setReplyText(e.target.value)}
212-
placeholder="Write a reply..."
215+
placeholder={t("comments.replyPlaceholder")}
213216
rows={2}
214217
className="text-xs min-h-14 resize-none"
215218
autoFocus
@@ -224,11 +227,11 @@ export function CommentThread({
224227
setReplyText("");
225228
}}
226229
>
227-
Cancel
230+
{t("common.cancel")}
228231
</Button>
229232
<Button type="submit" variant="default" size="sm" className="gap-1">
230233
<Send className="h-3 w-3" />
231-
<span>Reply</span>
234+
<span>{t("comments.reply")}</span>
232235
</Button>
233236
</div>
234237
</form>

apps/geolibre-desktop/src/components/comments/CommentsPanel.tsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export function CommentsPanel({
171171
try {
172172
await collaboration.join(targetCode, selfAuthorName, selfAuthorColor);
173173
} catch (err: unknown) {
174-
setSessionError(err instanceof Error ? err.message : "Failed to join session.");
174+
setSessionError(err instanceof Error ? err.message : t("comments.joinFailed"));
175175
} finally {
176176
setBusy(false);
177177
}
@@ -184,7 +184,7 @@ export function CommentsPanel({
184184
try {
185185
await collaboration.start(selfAuthorName, selfAuthorColor, "co-edit");
186186
} catch (err: unknown) {
187-
setSessionError(err instanceof Error ? err.message : "Failed to start session.");
187+
setSessionError(err instanceof Error ? err.message : t("comments.startFailed"));
188188
} finally {
189189
setBusy(false);
190190
}
@@ -249,7 +249,7 @@ export function CommentsPanel({
249249
<div className="flex items-center justify-between h-10 px-3 border-b border-border bg-card shrink-0">
250250
<div className="flex items-center gap-2">
251251
<MessageSquare className="h-4 w-4 text-primary" />
252-
<span className="font-semibold text-xs text-foreground">Comments</span>
252+
<span className="font-semibold text-xs text-foreground">{t("comments.title")}</span>
253253
<span className="inline-flex items-center justify-center h-4 px-1.5 text-[10px] font-bold rounded-full bg-muted text-muted-foreground">
254254
{unresolvedComments.length}
255255
</span>
@@ -261,10 +261,10 @@ export function CommentsPanel({
261261
size="sm"
262262
onClick={onActivateCommentTool}
263263
className="gap-1 h-7 px-2 text-xs"
264-
title="Place a new review comment on the map"
264+
title={t("comments.addCommentTooltip")}
265265
>
266266
<Plus className="h-3.5 w-3.5" />
267-
<span>Add Comment</span>
267+
<span>{t("comments.addComment")}</span>
268268
<kbd className="ms-1 rounded border border-current/25 px-1 font-mono text-[9px] leading-4 opacity-70">
269269
C
270270
</kbd>
@@ -284,7 +284,7 @@ export function CommentsPanel({
284284
onChange={(e) => setNameInput(e.target.value)}
285285
onKeyDown={handleNameKeyDown}
286286
onBlur={handleSaveName}
287-
placeholder="Your name…"
287+
placeholder={t("comments.yourNamePlaceholder")}
288288
className="h-6 text-xs flex-1 min-w-0 px-1.5"
289289
/>
290290
<button
@@ -294,7 +294,7 @@ export function CommentsPanel({
294294
handleSaveName();
295295
}}
296296
className="text-emerald-500 hover:text-emerald-600 shrink-0"
297-
title="Save name"
297+
title={t("comments.saveName")}
298298
>
299299
<Check className="h-3.5 w-3.5" />
300300
</button>
@@ -307,14 +307,14 @@ export function CommentsPanel({
307307
</span>
308308
) : (
309309
<span className="text-[11px] text-muted-foreground italic truncate">
310-
No name set — comments will show as "Author"
310+
{t("comments.noNameSet", { name: t("comments.defaultAuthorName") })}
311311
</span>
312312
)}
313313
<button
314314
type="button"
315315
onClick={handleEditName}
316316
className="text-muted-foreground hover:text-foreground shrink-0 ms-auto"
317-
title={selfAuthorName ? "Change name" : "Set your name"}
317+
title={selfAuthorName ? t("comments.changeNameTooltip") : t("comments.setYourName")}
318318
>
319319
<Pencil className="h-3 w-3" />
320320
</button>
@@ -332,7 +332,7 @@ export function CommentsPanel({
332332
<div className="min-w-0 flex-1">
333333
<div className="flex items-center gap-1 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
334334
<KeyRound className="h-3 w-3 text-primary shrink-0" />
335-
<span>Session Code</span>
335+
<span>{t("collaborate.sessionCode")}</span>
336336
</div>
337337
<div className="font-mono text-xs font-bold text-primary tracking-widest truncate mt-0.5">
338338
{activeCode}
@@ -347,7 +347,7 @@ export function CommentsPanel({
347347
title={t("collaborate.copyLink")}
348348
>
349349
<Copy className="h-3 w-3 me-1" />
350-
{copied ? "Copied" : "Copy"}
350+
{copied ? t("collaborate.copied") : t("comments.copy")}
351351
</Button>
352352
</div>
353353
) : (
@@ -361,7 +361,7 @@ export function CommentsPanel({
361361
className="w-full h-8 text-xs gap-1.5"
362362
>
363363
<Radio className="h-3.5 w-3.5 text-emerald-500" />
364-
{busy ? "Starting…" : "Start Live Session"}
364+
{busy ? t("comments.starting") : t("comments.startLiveSession")}
365365
</Button>
366366
)}
367367

@@ -373,7 +373,7 @@ export function CommentsPanel({
373373
onChange={(e) =>
374374
setJoinCodeInput(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, ""))
375375
}
376-
placeholder="ENTER SESSION CODE"
376+
placeholder={t("comments.sessionCodePlaceholder")}
377377
className="h-7 font-mono text-xs uppercase tracking-wider"
378378
disabled={busy}
379379
/>
@@ -386,7 +386,7 @@ export function CommentsPanel({
386386
className="h-7 px-2.5 text-[11px] gap-1 shrink-0"
387387
>
388388
<Link2 className="h-3 w-3" />
389-
<span>{busy ? "Joining…" : "Connect"}</span>
389+
<span>{busy ? t("comments.joining") : t("comments.connect")}</span>
390390
</Button>
391391
</div>
392392
)}
@@ -402,10 +402,10 @@ export function CommentsPanel({
402402
/>
403403
<span className="font-medium text-[11px] truncate">
404404
{collab.connecting
405-
? "Connecting…"
405+
? t("collaborate.connecting")
406406
: collab.isActive
407-
? `Live Sync (${collab.participants?.length ?? 1} online)`
408-
: "Offline / Private Workspace"}
407+
? t("comments.liveSync", { count: collab.participants?.length ?? 1 })
408+
: t("comments.offlineWorkspace")}
409409
</span>
410410
</div>
411411
{collab.isActive && (
@@ -416,7 +416,7 @@ export function CommentsPanel({
416416
onClick={handleDisconnectSession}
417417
className="h-6 px-1.5 text-[10px] text-destructive hover:text-destructive hover:bg-destructive/10 shrink-0"
418418
>
419-
Disconnect
419+
{t("comments.disconnect")}
420420
</Button>
421421
)}
422422
</div>
@@ -447,10 +447,10 @@ export function CommentsPanel({
447447
)}
448448
>
449449
{f === "open"
450-
? `Open (${unresolvedComments.length})`
450+
? t("comments.filterOpen", { count: unresolvedComments.length })
451451
: f === "resolved"
452-
? `Resolved (${resolvedComments.length})`
453-
: `All (${comments.length})`}
452+
? t("comments.filterResolved", { count: resolvedComments.length })
453+
: t("comments.filterAll", { count: comments.length })}
454454
</button>
455455
))}
456456
</div>
@@ -461,11 +461,10 @@ export function CommentsPanel({
461461
<div className="flex flex-col items-center justify-center min-h-[160px] text-center p-4 border border-dashed border-border/60 rounded-lg bg-card/40 my-2">
462462
<MessageCircle className="h-8 w-8 text-muted-foreground/50 mb-2" />
463463
<p className="text-xs font-medium text-foreground mb-1">
464-
{filter === "resolved" ? "No resolved comments" : "No open comments"}
464+
{filter === "resolved" ? t("comments.emptyResolved") : t("comments.emptyOpen")}
465465
</p>
466466
<p className="text-[11px] text-muted-foreground max-w-[200px]">
467-
Click &quot;Add Comment&quot; above and select any location or feature on the map to
468-
drop a review note.
467+
{t("comments.emptyHint")}
469468
</p>
470469
</div>
471470
) : (

0 commit comments

Comments
 (0)