Skip to content

Commit c99c24b

Browse files
CopilotCopilot
andcommitted
feat(plugin-detail): replace hardcoded strings with useDetailTranslation
Updated 6 components to use useDetailTranslation hook: - RecordChatterPanel: discussion labels - RichTextCommentInput: toolbar tooltips, placeholder - SubscriptionToggle: subscribe/unsubscribe tooltips - RecordNavigationEnhanced: nav titles, position indicator, search - RecordActivityTimeline: filter options, placeholders, labels - ThreadedReplies: reply count, placeholder FILTER_OPTIONS converted to getFilterOptions(t) function for i18n. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f4fd4f1 commit c99c24b

7 files changed

Lines changed: 55 additions & 39 deletions

packages/plugin-detail/src/CommentAttachment.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import * as React from 'react';
1010
import { cn, Button } from '@object-ui/components';
1111
import { Paperclip, X, FileText, Image, FileArchive, File, Upload } from 'lucide-react';
12+
import { useDetailTranslation } from './useDetailTranslation';
1213

1314
export interface Attachment {
1415
id: string;
@@ -53,6 +54,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
5354
className,
5455
readOnly = false,
5556
}) => {
57+
const { t } = useDetailTranslation();
5658
const [isDragOver, setIsDragOver] = React.useState(false);
5759
const fileInputRef = React.useRef<HTMLInputElement>(null);
5860

@@ -117,7 +119,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
117119
>
118120
<Upload className="h-5 w-5 mx-auto text-muted-foreground mb-1" />
119121
<p className="text-xs text-muted-foreground">
120-
Drop files here or click to upload
122+
{t('detail.dropFilesToUpload')}
121123
</p>
122124
<input
123125
ref={fileInputRef}
@@ -135,7 +137,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
135137
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
136138
<Paperclip className="h-3 w-3" />
137139
<span>
138-
{attachments.length} attachment{attachments.length !== 1 ? 's' : ''}
140+
{attachments.length !== 1 ? t('detail.attachmentCountPlural', { count: attachments.length }) : t('detail.attachmentCount', { count: attachments.length })}
139141
</span>
140142
</div>
141143
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
@@ -176,7 +178,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
176178
size="icon"
177179
className="h-6 w-6 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
178180
onClick={() => onRemove(attachment.id)}
179-
title="Remove attachment"
181+
title={t('detail.removeAttachment')}
180182
>
181183
<X className="h-3.5 w-3.5" />
182184
</Button>

packages/plugin-detail/src/RecordActivityTimeline.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { FieldChangeItem } from './FieldChangeItem';
2828
import { ReactionPicker } from './ReactionPicker';
2929
import { ThreadedReplies } from './ThreadedReplies';
3030
import { SubscriptionToggle } from './SubscriptionToggle';
31+
import { useDetailTranslation } from './useDetailTranslation';
3132

3233
export type FeedFilterMode = 'all' | 'comments_only' | 'changes_only' | 'tasks_only';
3334

@@ -81,12 +82,14 @@ const FEED_TYPE_COLORS: Record<FeedItemType, string> = {
8182
call: 'bg-teal-100 text-teal-600',
8283
};
8384

84-
const FILTER_OPTIONS: { value: FeedFilterMode; label: string }[] = [
85-
{ value: 'all', label: 'All Activity' },
86-
{ value: 'comments_only', label: 'Comments Only' },
87-
{ value: 'changes_only', label: 'Field Changes' },
88-
{ value: 'tasks_only', label: 'Tasks Only' },
89-
];
85+
function getFilterOptions(t: (key: string) => string): { value: FeedFilterMode; label: string }[] {
86+
return [
87+
{ value: 'all', label: t('detail.allActivity') },
88+
{ value: 'comments_only', label: t('detail.commentsOnly') },
89+
{ value: 'changes_only', label: t('detail.fieldChangesFilter') },
90+
{ value: 'tasks_only', label: t('detail.tasksOnly') },
91+
];
92+
}
9093

9194
function formatTimestamp(timestamp: string): string {
9295
try {
@@ -144,6 +147,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
144147
collapseWhenEmpty = false,
145148
className,
146149
}) => {
150+
const { t } = useDetailTranslation();
147151
const [internalFilter, setInternalFilter] = React.useState<FeedFilterMode>('all');
148152
const [commentText, setCommentText] = React.useState('');
149153
const [isSubmitting, setIsSubmitting] = React.useState(false);
@@ -229,7 +233,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
229233
<div className="flex items-center justify-between">
230234
<CardTitle className="flex items-center gap-2 text-base">
231235
<Activity className="h-4 w-4" />
232-
Activity
236+
{t('detail.activity')}
233237
<span className="text-sm font-normal text-muted-foreground">
234238
({filtered.length})
235239
</span>
@@ -254,7 +258,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
254258
onChange={(e) => handleFilterChange(e.target.value as FeedFilterMode)}
255259
aria-label="Filter activity"
256260
>
257-
{FILTER_OPTIONS.map((opt) => (
261+
{getFilterOptions(t).map((opt) => (
258262
<option key={opt.value} value={opt.value}>
259263
{opt.label}
260264
</option>
@@ -268,7 +272,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
268272
<div className="flex gap-2">
269273
<textarea
270274
className="flex-1 min-h-[60px] rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-none"
271-
placeholder="Leave a comment… (Ctrl+Enter to submit)"
275+
placeholder={t('detail.leaveCommentPlaceholder')}
272276
value={commentText}
273277
onChange={(e) => setCommentText(e.target.value)}
274278
onKeyDown={handleKeyDown}
@@ -291,7 +295,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
291295
{filtered.length === 0 ? (
292296
collapseWhenEmpty ? null : (
293297
<p className="text-sm text-muted-foreground text-center py-4">
294-
No activity recorded
298+
{t('detail.noActivity')}
295299
</p>
296300
)
297301
) : (
@@ -332,17 +336,17 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
332336
<span className="text-sm font-medium">{item.actor}</span>
333337
{item.source && (
334338
<span className="text-xs text-muted-foreground">
335-
via {item.source}
339+
{t('detail.via', { source: item.source })}
336340
</span>
337341
)}
338342
<span className="text-xs text-muted-foreground">
339343
{formatTimestamp(item.createdAt)}
340344
</span>
341345
{item.edited && (
342-
<span className="text-xs text-muted-foreground italic">(edited)</span>
346+
<span className="text-xs text-muted-foreground italic">{t('detail.edited')}</span>
343347
)}
344348
{item.pinned && (
345-
<span className="text-xs text-amber-600">📌 Pinned</span>
349+
<span className="text-xs text-amber-600">📌 {t('detail.pinned')}</span>
346350
)}
347351
</div>
348352

@@ -419,7 +423,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
419423
) : (
420424
<ChevronDown className="h-4 w-4 mr-1" />
421425
)}
422-
Load more
426+
{t('detail.loadMore')}
423427
</Button>
424428
</div>
425429
)}

packages/plugin-detail/src/RecordChatterPanel.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { MessageSquare, PanelRightOpen, PanelRightClose, X } from 'lucide-react'
1212
import type { RecordChatterComponentProps, FeedItem, RecordSubscription } from '@object-ui/types';
1313
import { RecordActivityTimeline } from './RecordActivityTimeline';
1414
import type { FeedFilterMode, RecordActivityTimelineProps } from './RecordActivityTimeline';
15+
import { useDetailTranslation } from './useDetailTranslation';
1516

1617
export interface RecordChatterPanelProps {
1718
/** Chatter panel configuration from RecordChatterComponentProps */
@@ -73,6 +74,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
7374
const collapsible = config?.collapsible ?? true;
7475
const defaultCollapsed = (collapseWhenEmpty && items.length === 0) || (config?.defaultCollapsed ?? false);
7576

77+
const { t } = useDetailTranslation();
7678
const [collapsed, setCollapsed] = React.useState(defaultCollapsed);
7779

7880
const isSidebar = position === 'right' || position === 'left';
@@ -115,7 +117,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
115117
<div className="flex items-center justify-between px-4 py-3 border-b">
116118
<div className="flex items-center gap-2">
117119
<MessageSquare className="h-4 w-4" />
118-
<span className="text-sm font-medium">Discussion</span>
120+
<span className="text-sm font-medium">{t('detail.discussion')}</span>
119121
</div>
120122
{collapsible && (
121123
<Button
@@ -164,15 +166,15 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
164166
aria-label="Show discussion"
165167
>
166168
<MessageSquare className="h-4 w-4" />
167-
<span>Show Discussion ({items.length})</span>
169+
<span>{t('detail.showDiscussion', { count: items.length })}</span>
168170
</Button>
169171
) : (
170172
<div>
171173
{collapsible && (
172174
<div className="flex items-center justify-between mb-2">
173175
<div className="flex items-center gap-2 text-sm font-medium">
174176
<MessageSquare className="h-4 w-4" />
175-
Discussion
177+
{t('detail.discussion')}
176178
</div>
177179
<Button
178180
variant="ghost"

packages/plugin-detail/src/RecordNavigationEnhanced.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ChevronRight,
1616
Search,
1717
} from 'lucide-react';
18+
import { useDetailTranslation } from './useDetailTranslation';
1819

1920
export interface RecordNavigationEnhancedProps {
2021
currentIndex: number;
@@ -33,6 +34,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
3334
onSearch,
3435
className,
3536
}) => {
37+
const { t } = useDetailTranslation();
3638
const [searchQuery, setSearchQuery] = React.useState('');
3739
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
3840
const searchInputRef = React.useRef<HTMLInputElement>(null);
@@ -133,7 +135,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
133135
className="h-8 w-8"
134136
disabled={!canGoFirst}
135137
onClick={handleFirst}
136-
title="First record (Home)"
138+
title={t('detail.firstRecord')}
137139
>
138140
<ChevronsLeft className="h-4 w-4" />
139141
</Button>
@@ -145,14 +147,14 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
145147
className="h-8 w-8"
146148
disabled={!canGoPrev}
147149
onClick={handlePrev}
148-
title="Previous record (←)"
150+
title={t('detail.previousRecordKey')}
149151
>
150152
<ChevronLeft className="h-4 w-4" />
151153
</Button>
152154

153155
{/* Position indicator */}
154156
<span className="text-xs text-muted-foreground whitespace-nowrap px-1.5 tabular-nums">
155-
{totalRecords > 0 ? `${currentIndex + 1} of ${totalRecords}` : 'No records'}
157+
{totalRecords > 0 ? t('detail.recordOf', { current: currentIndex + 1, total: totalRecords }) : t('detail.noRecords')}
156158
</span>
157159

158160
{/* Next */}
@@ -162,7 +164,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
162164
className="h-8 w-8"
163165
disabled={!canGoNext}
164166
onClick={handleNext}
165-
title="Next record (→)"
167+
title={t('detail.nextRecordKey')}
166168
>
167169
<ChevronRight className="h-4 w-4" />
168170
</Button>
@@ -174,7 +176,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
174176
className="h-8 w-8"
175177
disabled={!canGoLast}
176178
onClick={handleLast}
177-
title="Last record (End)"
179+
title={t('detail.lastRecord')}
178180
>
179181
<ChevronsRight className="h-4 w-4" />
180182
</Button>
@@ -187,7 +189,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
187189
size="icon"
188190
className="h-8 w-8"
189191
onClick={handleToggleSearch}
190-
title="Search while navigating"
192+
title={t('detail.searchWhileNavigating')}
191193
>
192194
<Search className="h-4 w-4" />
193195
</Button>
@@ -197,7 +199,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
197199
<Input
198200
ref={searchInputRef}
199201
type="text"
200-
placeholder="Search records…"
202+
placeholder={t('detail.searchRecords')}
201203
value={searchQuery}
202204
onChange={handleSearchChange}
203205
className="h-8 w-48 text-sm"

packages/plugin-detail/src/RichTextCommentInput.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
Edit,
1919
Send,
2020
} from 'lucide-react';
21+
import { useDetailTranslation } from './useDetailTranslation';
2122

2223
export interface MentionSuggestion {
2324
id: string;
@@ -71,10 +72,11 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
7172
onChange,
7273
onSubmit,
7374
mentionSuggestions = [],
74-
placeholder = 'Write a comment…',
75+
placeholder = '',
7576
className,
7677
disabled = false,
7778
}) => {
79+
const { t } = useDetailTranslation();
7880
const [isPreview, setIsPreview] = React.useState(false);
7981
const [showMentions, setShowMentions] = React.useState(false);
8082
const [mentionQuery, setMentionQuery] = React.useState('');
@@ -214,7 +216,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
214216
className="h-7 w-7"
215217
onClick={handleBold}
216218
disabled={disabled || isPreview}
217-
title="Bold (Ctrl+B)"
219+
title={t('detail.bold')}
218220
>
219221
<Bold className="h-3.5 w-3.5" />
220222
</Button>
@@ -224,7 +226,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
224226
className="h-7 w-7"
225227
onClick={handleItalic}
226228
disabled={disabled || isPreview}
227-
title="Italic (Ctrl+I)"
229+
title={t('detail.italic')}
228230
>
229231
<Italic className="h-3.5 w-3.5" />
230232
</Button>
@@ -234,7 +236,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
234236
className="h-7 w-7"
235237
onClick={handleList}
236238
disabled={disabled || isPreview}
237-
title="List"
239+
title={t('detail.listFormat')}
238240
>
239241
<List className="h-3.5 w-3.5" />
240242
</Button>
@@ -244,7 +246,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
244246
className="h-7 w-7"
245247
onClick={handleCode}
246248
disabled={disabled || isPreview}
247-
title="Inline code"
249+
title={t('detail.inlineCode')}
248250
>
249251
<Code className="h-3.5 w-3.5" />
250252
</Button>
@@ -254,7 +256,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
254256
className="h-7 w-7"
255257
onClick={handleMentionTrigger}
256258
disabled={disabled || isPreview}
257-
title="Mention someone"
259+
title={t('detail.mentionSomeone')}
258260
>
259261
<AtSign className="h-3.5 w-3.5" />
260262
</Button>
@@ -266,7 +268,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
266268
size="icon"
267269
className="h-7 w-7"
268270
onClick={() => setIsPreview(!isPreview)}
269-
title={isPreview ? 'Edit' : 'Preview'}
271+
title={isPreview ? t('detail.edit') : t('detail.preview')}
270272
>
271273
{isPreview ? (
272274
<Edit className="h-3.5 w-3.5" />
@@ -282,7 +284,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
282284
className="h-7 w-7"
283285
onClick={onSubmit}
284286
disabled={disabled || !value.trim()}
285-
title="Submit (Ctrl+Enter)"
287+
title={t('detail.submitComment')}
286288
>
287289
<Send className="h-3.5 w-3.5" />
288290
</Button>
@@ -301,7 +303,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
301303
<textarea
302304
ref={textareaRef}
303305
className="w-full min-h-[80px] px-3 py-2 text-sm bg-transparent resize-none focus:outline-none placeholder:text-muted-foreground"
304-
placeholder={placeholder}
306+
placeholder={placeholder || t('detail.writeComment')}
305307
value={value}
306308
onChange={handleTextChange}
307309
onKeyDown={handleKeyDown}

packages/plugin-detail/src/SubscriptionToggle.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as React from 'react';
1010
import { cn, Button } from '@object-ui/components';
1111
import { Bell, BellOff } from 'lucide-react';
1212
import type { RecordSubscription } from '@object-ui/types';
13+
import { useDetailTranslation } from './useDetailTranslation';
1314

1415
export interface SubscriptionToggleProps {
1516
/** Current subscription state */
@@ -28,6 +29,7 @@ export const SubscriptionToggle: React.FC<SubscriptionToggleProps> = ({
2829
onToggle,
2930
className,
3031
}) => {
32+
const { t } = useDetailTranslation();
3133
const [isLoading, setIsLoading] = React.useState(false);
3234

3335
const handleToggle = React.useCallback(async () => {
@@ -48,7 +50,7 @@ export const SubscriptionToggle: React.FC<SubscriptionToggleProps> = ({
4850
onClick={handleToggle}
4951
disabled={isLoading || !onToggle}
5052
aria-label={subscription.subscribed ? 'Unsubscribe from notifications' : 'Subscribe to notifications'}
51-
title={subscription.subscribed ? 'Subscribed — click to unsubscribe' : 'Subscribe to notifications'}
53+
title={subscription.subscribed ? t('detail.subscribedTooltip') : t('detail.unsubscribedTooltip')}
5254
>
5355
{subscription.subscribed ? (
5456
<Bell className="h-4 w-4 text-primary" />

0 commit comments

Comments
 (0)