@object-ui/plugin-detail 3.1.5 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.turbo/turbo-build.log +50 -47
  2. package/CHANGELOG.md +20 -0
  3. package/dist/{AddressField-DBkEyMcG.js → AddressField-CDLSeyNx.js} +1 -1
  4. package/dist/{AutoNumberField-Baa191z-.js → AutoNumberField-CtE7suf5.js} +1 -1
  5. package/dist/{AvatarField-YGj51ozd.js → AvatarField-Xuieq0ZI.js} +1 -1
  6. package/dist/{BooleanField-CaA898Tk.js → BooleanField-DwfMKknK.js} +1 -1
  7. package/dist/{CodeField-BU51nl1L.js → CodeField-CfwgRxx2.js} +1 -1
  8. package/dist/{ColorField-Cnf6ZM7c.js → ColorField-YKHA7dBD.js} +1 -1
  9. package/dist/{CurrencyField-Wg-XOId2.js → CurrencyField-tvS3fPAF.js} +1 -1
  10. package/dist/{DateField-Cth1ky_m.js → DateField-BKqXpkOh.js} +1 -1
  11. package/dist/{DateTimeField-B0m6FhHL.js → DateTimeField-CR-nJCE7.js} +1 -1
  12. package/dist/{EmailField-Do7qT_L_.js → EmailField-CgvW1Qal.js} +1 -1
  13. package/dist/{FileField-aRJAdbQb.js → FileField-BVAme2ML.js} +1 -1
  14. package/dist/{FormulaField-DTMkagFx.js → FormulaField-DamJ2VaG.js} +1 -1
  15. package/dist/{GeolocationField-RqpHWTEv.js → GeolocationField-C99z7ZBM.js} +1 -1
  16. package/dist/{GridField-D4IH0cpo.js → GridField-C9JbpTx_.js} +1 -1
  17. package/dist/{ImageField-BYCFajjr.js → ImageField-CDANtgVV.js} +1 -1
  18. package/dist/{LocationField-Bi_ew9sd.js → LocationField-ZSyZ0O-h.js} +1 -1
  19. package/dist/{LookupField-BjwlDPtt.js → LookupField-B3hQJt95.js} +307 -306
  20. package/dist/LookupField-D00z6gn_.js +2 -0
  21. package/dist/{MasterDetailField-I1A9oEGC.js → MasterDetailField-B0HTmmD7.js} +1 -1
  22. package/dist/{NumberField-D_NucQlp.js → NumberField-DL2QAL7X.js} +1 -1
  23. package/dist/{ObjectField-CG-LaM65.js → ObjectField-JYvUnuRO.js} +1 -1
  24. package/dist/{PasswordField-DBtluGJ1.js → PasswordField-DVTimsc3.js} +1 -1
  25. package/dist/{PercentField-B6sO_J3i.js → PercentField-DjR6BSpw.js} +1 -1
  26. package/dist/{PhoneField-CcQAWwR6.js → PhoneField-CX1JL-jp.js} +1 -1
  27. package/dist/{QRCodeField-CEjWs-J5.js → QRCodeField-CH_1pU6R.js} +1 -1
  28. package/dist/{RatingField-B_Mnr63i.js → RatingField-rRi_P0N0.js} +1 -1
  29. package/dist/{RichTextField-qOEJl5Ai.js → RichTextField-CJqLWlrb.js} +1 -1
  30. package/dist/SelectField-DGoDoRM_.js +30 -0
  31. package/dist/SelectField-XBVI50AD.js +2 -0
  32. package/dist/{SignatureField-CddhEK9u.js → SignatureField-2CnhcWI0.js} +1 -1
  33. package/dist/{SliderField-Df5hMzNc.js → SliderField-DEpMVXko.js} +1 -1
  34. package/dist/{SummaryField-DgiFm-Cr.js → SummaryField-7ch9aqAu.js} +1 -1
  35. package/dist/{TextAreaField-DuriTqsD.js → TextAreaField-Cmw1oXcw.js} +1 -1
  36. package/dist/{TextField-CGNSl7RU.js → TextField-OTLa3p51.js} +1 -1
  37. package/dist/{TimeField-YO58ctFg.js → TimeField-DKPoNWoR.js} +1 -1
  38. package/dist/{UrlField-1-BMM1jn.js → UrlField-CxbmzP9f.js} +1 -1
  39. package/dist/{UserField-B6GqxP_S.js → UserField-ChvwUkMK.js} +1 -1
  40. package/dist/{VectorField-BkEjbSt0.js → VectorField-BVClL8Vw.js} +1 -1
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +1252 -1199
  43. package/dist/index.umd.cjs +47 -46
  44. package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
  45. package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
  46. package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
  47. package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
  48. package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
  49. package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
  50. package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
  51. package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
  52. package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
  53. package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
  54. package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
  55. package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
  56. package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
  57. package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
  58. package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
  59. package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
  60. package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
  61. package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
  62. package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
  63. package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
  64. package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
  65. package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
  66. package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
  67. package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
  68. package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
  69. package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
  70. package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
  71. package/dist/plugin-detail.css +1 -1
  72. package/dist/{src-CXr1-vVl.js → src-C56Ly5uG.js} +35777 -35335
  73. package/dist/useFieldTranslation-CkxqyB82.js +9 -0
  74. package/package.json +10 -10
  75. package/src/CommentAttachment.tsx +5 -3
  76. package/src/DiffView.tsx +7 -5
  77. package/src/RecordActivityTimeline.tsx +21 -17
  78. package/src/RecordChatterPanel.tsx +9 -7
  79. package/src/RecordComments.tsx +11 -9
  80. package/src/RecordNavigationEnhanced.tsx +9 -7
  81. package/src/RichTextCommentInput.tsx +11 -9
  82. package/src/SubscriptionToggle.tsx +4 -2
  83. package/src/ThreadedReplies.tsx +4 -2
  84. package/src/__tests__/RecordActivityTimeline.test.tsx +9 -9
  85. package/src/__tests__/RecordChatterPanel.test.tsx +6 -6
  86. package/src/__tests__/RecordCommentsPinSearch.test.tsx +6 -6
  87. package/src/useDetailTranslation.ts +69 -0
  88. package/vite.config.ts +1 -0
  89. package/dist/SelectField-C8hWu3gm.js +0 -30
  90. package/dist/src/ActivityTimeline.d.ts.map +0 -1
  91. package/dist/src/CommentAttachment.d.ts.map +0 -1
  92. package/dist/src/CommentInput.d.ts.map +0 -1
  93. package/dist/src/DetailSection.d.ts.map +0 -1
  94. package/dist/src/DetailTabs.d.ts.map +0 -1
  95. package/dist/src/DetailView.d.ts.map +0 -1
  96. package/dist/src/DetailView.stories.d.ts.map +0 -1
  97. package/dist/src/DiffView.d.ts.map +0 -1
  98. package/dist/src/FieldChangeItem.d.ts.map +0 -1
  99. package/dist/src/HeaderHighlight.d.ts.map +0 -1
  100. package/dist/src/InlineCreateRelated.d.ts.map +0 -1
  101. package/dist/src/MentionAutocomplete.d.ts.map +0 -1
  102. package/dist/src/PointInTimeRestore.d.ts.map +0 -1
  103. package/dist/src/ReactionPicker.d.ts.map +0 -1
  104. package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
  105. package/dist/src/RecordChatterPanel.d.ts.map +0 -1
  106. package/dist/src/RecordComments.d.ts.map +0 -1
  107. package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
  108. package/dist/src/RelatedList.d.ts.map +0 -1
  109. package/dist/src/RelationshipGraph.d.ts.map +0 -1
  110. package/dist/src/RichTextCommentInput.d.ts.map +0 -1
  111. package/dist/src/SectionGroup.d.ts.map +0 -1
  112. package/dist/src/SubscriptionToggle.d.ts.map +0 -1
  113. package/dist/src/ThreadedReplies.d.ts.map +0 -1
  114. package/dist/src/autoLayout.d.ts.map +0 -1
  115. package/dist/src/index.d.ts.map +0 -1
  116. package/dist/src/useDetailTranslation.d.ts.map +0 -1
  117. /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
  118. /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
  119. /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
  120. /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
  121. /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
  122. /package/dist/{src → packages/plugin-detail/src}/DetailView.d.ts +0 -0
  123. /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
  124. /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
  125. /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
  126. /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
  127. /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
  128. /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
  129. /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
  130. /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
  131. /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
  132. /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
  133. /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
  134. /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
  135. /package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +0 -0
  136. /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
  137. /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
  138. /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
  139. /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
  140. /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
  141. /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
  142. /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
  143. /package/dist/{src → packages/plugin-detail/src}/useDetailTranslation.d.ts +0 -0
@@ -0,0 +1,9 @@
1
+ import { dn as e } from "./src-C56Ly5uG.js";
2
+ var t = e({
3
+ "common.selectOption": "Select an option",
4
+ "common.select": "Select...",
5
+ "common.search": "Search",
6
+ "table.selected": "{{count}} selected"
7
+ }, "common.selectOption");
8
+ //#endregion
9
+ export { t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-detail",
3
- "version": "3.1.5",
3
+ "version": "3.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "DetailView plugin for Object UI - comprehensive detail page with sections, tabs, and related lists",
@@ -24,12 +24,12 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "lucide-react": "^0.577.0",
28
- "@object-ui/components": "3.1.5",
29
- "@object-ui/core": "3.1.5",
30
- "@object-ui/types": "3.1.5",
31
- "@object-ui/fields": "3.1.5",
32
- "@object-ui/react": "3.1.5"
27
+ "lucide-react": "^1.8.0",
28
+ "@object-ui/components": "3.3.0",
29
+ "@object-ui/core": "3.3.0",
30
+ "@object-ui/fields": "3.3.0",
31
+ "@object-ui/react": "3.3.0",
32
+ "@object-ui/types": "3.3.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",
@@ -39,10 +39,10 @@
39
39
  "@types/react": "19.2.14",
40
40
  "@types/react-dom": "19.2.3",
41
41
  "@vitejs/plugin-react": "^6.0.1",
42
- "typescript": "^5.9.3",
43
- "vite": "^8.0.1",
42
+ "typescript": "^6.0.2",
43
+ "vite": "^8.0.8",
44
44
  "vite-plugin-dts": "^4.5.4",
45
- "vitest": "^4.1.0"
45
+ "vitest": "^4.1.4"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "vite build",
@@ -9,6 +9,7 @@
9
9
  import * as React from 'react';
10
10
  import { cn, Button } from '@object-ui/components';
11
11
  import { Paperclip, X, FileText, Image, FileArchive, File, Upload } from 'lucide-react';
12
+ import { useDetailTranslation } from './useDetailTranslation';
12
13
 
13
14
  export interface Attachment {
14
15
  id: string;
@@ -53,6 +54,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
53
54
  className,
54
55
  readOnly = false,
55
56
  }) => {
57
+ const { t } = useDetailTranslation();
56
58
  const [isDragOver, setIsDragOver] = React.useState(false);
57
59
  const fileInputRef = React.useRef<HTMLInputElement>(null);
58
60
 
@@ -117,7 +119,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
117
119
  >
118
120
  <Upload className="h-5 w-5 mx-auto text-muted-foreground mb-1" />
119
121
  <p className="text-xs text-muted-foreground">
120
- Drop files here or click to upload
122
+ {t('detail.dropFilesToUpload')}
121
123
  </p>
122
124
  <input
123
125
  ref={fileInputRef}
@@ -135,7 +137,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
135
137
  <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
136
138
  <Paperclip className="h-3 w-3" />
137
139
  <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 })}
139
141
  </span>
140
142
  </div>
141
143
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
@@ -176,7 +178,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
176
178
  size="icon"
177
179
  className="h-6 w-6 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
178
180
  onClick={() => onRemove(attachment.id)}
179
- title="Remove attachment"
181
+ title={t('detail.removeAttachment')}
180
182
  >
181
183
  <X className="h-3.5 w-3.5" />
182
184
  </Button>
package/src/DiffView.tsx CHANGED
@@ -9,6 +9,7 @@
9
9
  import * as React from 'react';
10
10
  import { cn, Button, Card, CardHeader, CardTitle, CardContent } from '@object-ui/components';
11
11
  import { Columns2, Rows3 } from 'lucide-react';
12
+ import { useDetailTranslation } from './useDetailTranslation';
12
13
 
13
14
  export type DiffFieldType = 'string' | 'number' | 'boolean' | 'json' | 'date';
14
15
  export type DiffMode = 'unified' | 'side-by-side';
@@ -98,6 +99,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
98
99
  mode: initialMode = 'unified',
99
100
  className,
100
101
  }) => {
102
+ const { t } = useDetailTranslation();
101
103
  const [mode, setMode] = React.useState<DiffMode>(initialMode);
102
104
 
103
105
  const oldLines = React.useMemo(() => valueToLines(oldValue, fieldType), [oldValue, fieldType]);
@@ -146,7 +148,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
146
148
  size="icon"
147
149
  className="h-7 w-7"
148
150
  onClick={() => setMode('unified')}
149
- title="Unified diff"
151
+ title={t('detail.unifiedDiff')}
150
152
  >
151
153
  <Rows3 className="h-3.5 w-3.5" />
152
154
  </Button>
@@ -155,7 +157,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
155
157
  size="icon"
156
158
  className="h-7 w-7"
157
159
  onClick={() => setMode('side-by-side')}
158
- title="Side-by-side diff"
160
+ title={t('detail.sideBySideDiff')}
159
161
  >
160
162
  <Columns2 className="h-3.5 w-3.5" />
161
163
  </Button>
@@ -164,7 +166,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
164
166
  </CardHeader>
165
167
  <CardContent className="p-0">
166
168
  {!hasChanges ? (
167
- <p className="px-4 py-3 text-sm text-muted-foreground">No changes</p>
169
+ <p className="px-4 py-3 text-sm text-muted-foreground">{t('detail.noChanges')}</p>
168
170
  ) : mode === 'unified' ? (
169
171
  /* Unified diff view */
170
172
  <div className="font-mono text-xs overflow-x-auto">
@@ -192,10 +194,10 @@ export const DiffView: React.FC<DiffViewProps> = ({
192
194
  <div className="grid grid-cols-2 divide-x font-mono text-xs min-w-0">
193
195
  {/* Headers */}
194
196
  <div className="px-3 py-1.5 text-xs font-medium text-muted-foreground bg-muted/50">
195
- Previous
197
+ {t('detail.previousVersion')}
196
198
  </div>
197
199
  <div className="px-3 py-1.5 text-xs font-medium text-muted-foreground bg-muted/50">
198
- Current
200
+ {t('detail.currentVersion')}
199
201
  </div>
200
202
  {/* Rows */}
201
203
  {sideBySidePairs.map((pair, index) => (
@@ -28,6 +28,7 @@ import { FieldChangeItem } from './FieldChangeItem';
28
28
  import { ReactionPicker } from './ReactionPicker';
29
29
  import { ThreadedReplies } from './ThreadedReplies';
30
30
  import { SubscriptionToggle } from './SubscriptionToggle';
31
+ import { useDetailTranslation } from './useDetailTranslation';
31
32
 
32
33
  export type FeedFilterMode = 'all' | 'comments_only' | 'changes_only' | 'tasks_only';
33
34
 
@@ -81,12 +82,14 @@ const FEED_TYPE_COLORS: Record<FeedItemType, string> = {
81
82
  call: 'bg-teal-100 text-teal-600',
82
83
  };
83
84
 
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
+ }
90
93
 
91
94
  function formatTimestamp(timestamp: string): string {
92
95
  try {
@@ -144,6 +147,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
144
147
  collapseWhenEmpty = false,
145
148
  className,
146
149
  }) => {
150
+ const { t } = useDetailTranslation();
147
151
  const [internalFilter, setInternalFilter] = React.useState<FeedFilterMode>('all');
148
152
  const [commentText, setCommentText] = React.useState('');
149
153
  const [isSubmitting, setIsSubmitting] = React.useState(false);
@@ -229,7 +233,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
229
233
  <div className="flex items-center justify-between">
230
234
  <CardTitle className="flex items-center gap-2 text-base">
231
235
  <Activity className="h-4 w-4" />
232
- Activity
236
+ {t('detail.activity')}
233
237
  <span className="text-sm font-normal text-muted-foreground">
234
238
  ({filtered.length})
235
239
  </span>
@@ -252,9 +256,9 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
252
256
  className="rounded-md border border-input bg-background px-2.5 py-1.5 text-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
253
257
  value={activeFilter}
254
258
  onChange={(e) => handleFilterChange(e.target.value as FeedFilterMode)}
255
- aria-label="Filter activity"
259
+ aria-label={t('detail.filterActivity')}
256
260
  >
257
- {FILTER_OPTIONS.map((opt) => (
261
+ {getFilterOptions(t).map((opt) => (
258
262
  <option key={opt.value} value={opt.value}>
259
263
  {opt.label}
260
264
  </option>
@@ -268,7 +272,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
268
272
  <div className="flex gap-2">
269
273
  <textarea
270
274
  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')}
272
276
  value={commentText}
273
277
  onChange={(e) => setCommentText(e.target.value)}
274
278
  onKeyDown={handleKeyDown}
@@ -280,7 +284,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
280
284
  onClick={handleAddComment}
281
285
  disabled={!commentText.trim() || isSubmitting}
282
286
  className="shrink-0 self-end"
283
- aria-label="Submit comment"
287
+ aria-label={t('detail.submitComment')}
284
288
  >
285
289
  <MessageSquare className="h-4 w-4" />
286
290
  </Button>
@@ -291,7 +295,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
291
295
  {filtered.length === 0 ? (
292
296
  collapseWhenEmpty ? null : (
293
297
  <p className="text-sm text-muted-foreground text-center py-4">
294
- No activity recorded
298
+ {t('detail.noActivity')}
295
299
  </p>
296
300
  )
297
301
  ) : (
@@ -332,17 +336,17 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
332
336
  <span className="text-sm font-medium">{item.actor}</span>
333
337
  {item.source && (
334
338
  <span className="text-xs text-muted-foreground">
335
- via {item.source}
339
+ {t('detail.via', { source: item.source })}
336
340
  </span>
337
341
  )}
338
342
  <span className="text-xs text-muted-foreground">
339
343
  {formatTimestamp(item.createdAt)}
340
344
  </span>
341
345
  {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>
343
347
  )}
344
348
  {item.pinned && (
345
- <span className="text-xs text-amber-600">📌 Pinned</span>
349
+ <span className="text-xs text-amber-600">📌 {t('detail.pinned')}</span>
346
350
  )}
347
351
  </div>
348
352
 
@@ -412,14 +416,14 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
412
416
  size="sm"
413
417
  onClick={handleLoadMore}
414
418
  disabled={isLoadingMore}
415
- aria-label="Load more activity"
419
+ aria-label={t('detail.loadMore')}
416
420
  >
417
421
  {isLoadingMore ? (
418
422
  <Loader2 className="h-4 w-4 animate-spin mr-1" />
419
423
  ) : (
420
424
  <ChevronDown className="h-4 w-4 mr-1" />
421
425
  )}
422
- Load more
426
+ {t('detail.loadMore')}
423
427
  </Button>
424
428
  </div>
425
429
  )}
@@ -12,6 +12,7 @@ import { MessageSquare, PanelRightOpen, PanelRightClose, X } from 'lucide-react'
12
12
  import type { RecordChatterComponentProps, FeedItem, RecordSubscription } from '@object-ui/types';
13
13
  import { RecordActivityTimeline } from './RecordActivityTimeline';
14
14
  import type { FeedFilterMode, RecordActivityTimelineProps } from './RecordActivityTimeline';
15
+ import { useDetailTranslation } from './useDetailTranslation';
15
16
 
16
17
  export interface RecordChatterPanelProps {
17
18
  /** Chatter panel configuration from RecordChatterComponentProps */
@@ -73,6 +74,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
73
74
  const collapsible = config?.collapsible ?? true;
74
75
  const defaultCollapsed = (collapseWhenEmpty && items.length === 0) || (config?.defaultCollapsed ?? false);
75
76
 
77
+ const { t } = useDetailTranslation();
76
78
  const [collapsed, setCollapsed] = React.useState(defaultCollapsed);
77
79
 
78
80
  const isSidebar = position === 'right' || position === 'left';
@@ -94,7 +96,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
94
96
  size="icon"
95
97
  className="h-8 w-8 mx-1"
96
98
  onClick={() => setCollapsed(false)}
97
- aria-label="Open discussion panel"
99
+ aria-label={t('detail.openDiscussion')}
98
100
  >
99
101
  <PanelRightOpen className="h-4 w-4" />
100
102
  </Button>
@@ -115,7 +117,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
115
117
  <div className="flex items-center justify-between px-4 py-3 border-b">
116
118
  <div className="flex items-center gap-2">
117
119
  <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>
119
121
  </div>
120
122
  {collapsible && (
121
123
  <Button
@@ -123,7 +125,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
123
125
  size="icon"
124
126
  className="h-7 w-7"
125
127
  onClick={() => setCollapsed(true)}
126
- aria-label="Close discussion panel"
128
+ aria-label={t('detail.closeDiscussion')}
127
129
  >
128
130
  <X className="h-3.5 w-3.5" />
129
131
  </Button>
@@ -161,10 +163,10 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
161
163
  variant="ghost"
162
164
  className="w-full justify-start gap-2 text-muted-foreground"
163
165
  onClick={() => setCollapsed(false)}
164
- aria-label="Show discussion"
166
+ aria-label={t('detail.showDiscussion', { count: items.length })}
165
167
  >
166
168
  <MessageSquare className="h-4 w-4" />
167
- <span>Show Discussion ({items.length})</span>
169
+ <span>{t('detail.showDiscussion', { count: items.length })}</span>
168
170
  </Button>
169
171
  ) : (
170
172
  <div>
@@ -172,14 +174,14 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
172
174
  <div className="flex items-center justify-between mb-2">
173
175
  <div className="flex items-center gap-2 text-sm font-medium">
174
176
  <MessageSquare className="h-4 w-4" />
175
- Discussion
177
+ {t('detail.discussion')}
176
178
  </div>
177
179
  <Button
178
180
  variant="ghost"
179
181
  size="icon"
180
182
  className="h-7 w-7"
181
183
  onClick={() => setCollapsed(true)}
182
- aria-label="Hide discussion"
184
+ aria-label={t('detail.hideDiscussion')}
183
185
  >
184
186
  <PanelRightClose className="h-3.5 w-3.5" />
185
187
  </Button>
@@ -10,6 +10,7 @@ import * as React from 'react';
10
10
  import { cn, Button, Card, CardHeader, CardTitle, CardContent } from '@object-ui/components';
11
11
  import { MessageSquare, Send, Pin, Search, X } from 'lucide-react';
12
12
  import type { CommentEntry } from '@object-ui/types';
13
+ import { useDetailTranslation } from './useDetailTranslation';
13
14
 
14
15
  export interface RecordCommentsProps {
15
16
  comments: CommentEntry[];
@@ -50,6 +51,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
50
51
  searchable = false,
51
52
  className,
52
53
  }) => {
54
+ const { t } = useDetailTranslation();
53
55
  const [newComment, setNewComment] = React.useState('');
54
56
  const [isSubmitting, setIsSubmitting] = React.useState(false);
55
57
  const [searchQuery, setSearchQuery] = React.useState('');
@@ -98,7 +100,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
98
100
  <CardHeader>
99
101
  <CardTitle className="flex items-center gap-2 text-base">
100
102
  <MessageSquare className="h-4 w-4" />
101
- Comments
103
+ {t('detail.comments')}
102
104
  <span className="text-sm font-normal text-muted-foreground">
103
105
  ({comments.length})
104
106
  </span>
@@ -112,16 +114,16 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
112
114
  <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
113
115
  <input
114
116
  className="w-full rounded-md border border-input bg-background pl-8 pr-8 py-1.5 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
115
- placeholder="Search comments…"
117
+ placeholder={t('detail.searchComments')}
116
118
  value={searchQuery}
117
119
  onChange={(e) => setSearchQuery(e.target.value)}
118
- aria-label="Search comments"
120
+ aria-label={t('detail.searchComments')}
119
121
  />
120
122
  {searchQuery && (
121
123
  <button
122
124
  className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
123
125
  onClick={() => setSearchQuery('')}
124
- aria-label="Clear search"
126
+ aria-label={t('detail.clearSearch')}
125
127
  type="button"
126
128
  >
127
129
  <X className="h-3.5 w-3.5" />
@@ -136,7 +138,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
136
138
  <div className="flex gap-2">
137
139
  <textarea
138
140
  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"
139
- placeholder="Add a comment… (Ctrl+Enter to submit)"
141
+ placeholder={t('detail.addCommentPlaceholder')}
140
142
  value={newComment}
141
143
  onChange={(e) => setNewComment(e.target.value)}
142
144
  onKeyDown={handleKeyDown}
@@ -157,7 +159,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
157
159
  {/* Comment List */}
158
160
  {sortedComments.length === 0 ? (
159
161
  <p className="text-sm text-muted-foreground text-center py-4">
160
- {searchQuery.trim() ? 'No matching comments' : 'No comments yet'}
162
+ {searchQuery.trim() ? t('detail.noMatchingComments') : t('detail.noCommentsYet')}
161
163
  </p>
162
164
  ) : (
163
165
  <div className="space-y-3">
@@ -187,7 +189,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
187
189
  {comment.pinned && (
188
190
  <span className="text-xs text-amber-600 flex items-center gap-0.5">
189
191
  <Pin className="h-3 w-3" />
190
- Pinned
192
+ {t('detail.pinned')}
191
193
  </span>
192
194
  )}
193
195
  </div>
@@ -198,10 +200,10 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
198
200
  type="button"
199
201
  className="mt-1 text-xs text-muted-foreground hover:text-foreground flex items-center gap-1"
200
202
  onClick={() => onTogglePin(comment.id)}
201
- aria-label={comment.pinned ? 'Unpin comment' : 'Pin comment'}
203
+ aria-label={comment.pinned ? t('detail.unpin') : t('detail.pin')}
202
204
  >
203
205
  <Pin className="h-3 w-3" />
204
- {comment.pinned ? 'Unpin' : 'Pin'}
206
+ {comment.pinned ? t('detail.unpin') : t('detail.pin')}
205
207
  </button>
206
208
  )}
207
209
  </div>
@@ -15,6 +15,7 @@ import {
15
15
  ChevronRight,
16
16
  Search,
17
17
  } from 'lucide-react';
18
+ import { useDetailTranslation } from './useDetailTranslation';
18
19
 
19
20
  export interface RecordNavigationEnhancedProps {
20
21
  currentIndex: number;
@@ -33,6 +34,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
33
34
  onSearch,
34
35
  className,
35
36
  }) => {
37
+ const { t } = useDetailTranslation();
36
38
  const [searchQuery, setSearchQuery] = React.useState('');
37
39
  const [isSearchOpen, setIsSearchOpen] = React.useState(false);
38
40
  const searchInputRef = React.useRef<HTMLInputElement>(null);
@@ -133,7 +135,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
133
135
  className="h-8 w-8"
134
136
  disabled={!canGoFirst}
135
137
  onClick={handleFirst}
136
- title="First record (Home)"
138
+ title={t('detail.firstRecord')}
137
139
  >
138
140
  <ChevronsLeft className="h-4 w-4" />
139
141
  </Button>
@@ -145,14 +147,14 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
145
147
  className="h-8 w-8"
146
148
  disabled={!canGoPrev}
147
149
  onClick={handlePrev}
148
- title="Previous record ()"
150
+ title={t('detail.previousRecordKey')}
149
151
  >
150
152
  <ChevronLeft className="h-4 w-4" />
151
153
  </Button>
152
154
 
153
155
  {/* Position indicator */}
154
156
  <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')}
156
158
  </span>
157
159
 
158
160
  {/* Next */}
@@ -162,7 +164,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
162
164
  className="h-8 w-8"
163
165
  disabled={!canGoNext}
164
166
  onClick={handleNext}
165
- title="Next record ()"
167
+ title={t('detail.nextRecordKey')}
166
168
  >
167
169
  <ChevronRight className="h-4 w-4" />
168
170
  </Button>
@@ -174,7 +176,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
174
176
  className="h-8 w-8"
175
177
  disabled={!canGoLast}
176
178
  onClick={handleLast}
177
- title="Last record (End)"
179
+ title={t('detail.lastRecord')}
178
180
  >
179
181
  <ChevronsRight className="h-4 w-4" />
180
182
  </Button>
@@ -187,7 +189,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
187
189
  size="icon"
188
190
  className="h-8 w-8"
189
191
  onClick={handleToggleSearch}
190
- title="Search while navigating"
192
+ title={t('detail.searchWhileNavigating')}
191
193
  >
192
194
  <Search className="h-4 w-4" />
193
195
  </Button>
@@ -197,7 +199,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
197
199
  <Input
198
200
  ref={searchInputRef}
199
201
  type="text"
200
- placeholder="Search records…"
202
+ placeholder={t('detail.searchRecords')}
201
203
  value={searchQuery}
202
204
  onChange={handleSearchChange}
203
205
  className="h-8 w-48 text-sm"
@@ -18,6 +18,7 @@ import {
18
18
  Edit,
19
19
  Send,
20
20
  } from 'lucide-react';
21
+ import { useDetailTranslation } from './useDetailTranslation';
21
22
 
22
23
  export interface MentionSuggestion {
23
24
  id: string;
@@ -71,10 +72,11 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
71
72
  onChange,
72
73
  onSubmit,
73
74
  mentionSuggestions = [],
74
- placeholder = 'Write a comment…',
75
+ placeholder,
75
76
  className,
76
77
  disabled = false,
77
78
  }) => {
79
+ const { t } = useDetailTranslation();
78
80
  const [isPreview, setIsPreview] = React.useState(false);
79
81
  const [showMentions, setShowMentions] = React.useState(false);
80
82
  const [mentionQuery, setMentionQuery] = React.useState('');
@@ -214,7 +216,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
214
216
  className="h-7 w-7"
215
217
  onClick={handleBold}
216
218
  disabled={disabled || isPreview}
217
- title="Bold (Ctrl+B)"
219
+ title={t('detail.bold')}
218
220
  >
219
221
  <Bold className="h-3.5 w-3.5" />
220
222
  </Button>
@@ -224,7 +226,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
224
226
  className="h-7 w-7"
225
227
  onClick={handleItalic}
226
228
  disabled={disabled || isPreview}
227
- title="Italic (Ctrl+I)"
229
+ title={t('detail.italic')}
228
230
  >
229
231
  <Italic className="h-3.5 w-3.5" />
230
232
  </Button>
@@ -234,7 +236,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
234
236
  className="h-7 w-7"
235
237
  onClick={handleList}
236
238
  disabled={disabled || isPreview}
237
- title="List"
239
+ title={t('detail.listFormat')}
238
240
  >
239
241
  <List className="h-3.5 w-3.5" />
240
242
  </Button>
@@ -244,7 +246,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
244
246
  className="h-7 w-7"
245
247
  onClick={handleCode}
246
248
  disabled={disabled || isPreview}
247
- title="Inline code"
249
+ title={t('detail.inlineCode')}
248
250
  >
249
251
  <Code className="h-3.5 w-3.5" />
250
252
  </Button>
@@ -254,7 +256,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
254
256
  className="h-7 w-7"
255
257
  onClick={handleMentionTrigger}
256
258
  disabled={disabled || isPreview}
257
- title="Mention someone"
259
+ title={t('detail.mentionSomeone')}
258
260
  >
259
261
  <AtSign className="h-3.5 w-3.5" />
260
262
  </Button>
@@ -266,7 +268,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
266
268
  size="icon"
267
269
  className="h-7 w-7"
268
270
  onClick={() => setIsPreview(!isPreview)}
269
- title={isPreview ? 'Edit' : 'Preview'}
271
+ title={isPreview ? t('detail.edit') : t('detail.preview')}
270
272
  >
271
273
  {isPreview ? (
272
274
  <Edit className="h-3.5 w-3.5" />
@@ -282,7 +284,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
282
284
  className="h-7 w-7"
283
285
  onClick={onSubmit}
284
286
  disabled={disabled || !value.trim()}
285
- title="Submit (Ctrl+Enter)"
287
+ title={t('detail.submitComment')}
286
288
  >
287
289
  <Send className="h-3.5 w-3.5" />
288
290
  </Button>
@@ -301,7 +303,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
301
303
  <textarea
302
304
  ref={textareaRef}
303
305
  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')}
305
307
  value={value}
306
308
  onChange={handleTextChange}
307
309
  onKeyDown={handleKeyDown}
@@ -10,6 +10,7 @@ import * as React from 'react';
10
10
  import { cn, Button } from '@object-ui/components';
11
11
  import { Bell, BellOff } from 'lucide-react';
12
12
  import type { RecordSubscription } from '@object-ui/types';
13
+ import { useDetailTranslation } from './useDetailTranslation';
13
14
 
14
15
  export interface SubscriptionToggleProps {
15
16
  /** Current subscription state */
@@ -28,6 +29,7 @@ export const SubscriptionToggle: React.FC<SubscriptionToggleProps> = ({
28
29
  onToggle,
29
30
  className,
30
31
  }) => {
32
+ const { t } = useDetailTranslation();
31
33
  const [isLoading, setIsLoading] = React.useState(false);
32
34
 
33
35
  const handleToggle = React.useCallback(async () => {
@@ -47,8 +49,8 @@ export const SubscriptionToggle: React.FC<SubscriptionToggleProps> = ({
47
49
  className={cn('h-8 w-8', className)}
48
50
  onClick={handleToggle}
49
51
  disabled={isLoading || !onToggle}
50
- aria-label={subscription.subscribed ? 'Unsubscribe from notifications' : 'Subscribe to notifications'}
51
- title={subscription.subscribed ? 'Subscribed — click to unsubscribe' : 'Subscribe to notifications'}
52
+ aria-label={subscription.subscribed ? t('detail.unsubscribeAriaLabel') : t('detail.subscribeAriaLabel')}
53
+ title={subscription.subscribed ? t('detail.subscribedTooltip') : t('detail.unsubscribedTooltip')}
52
54
  >
53
55
  {subscription.subscribed ? (
54
56
  <Bell className="h-4 w-4 text-primary" />
@@ -10,6 +10,7 @@ import * as React from 'react';
10
10
  import { cn, Button } from '@object-ui/components';
11
11
  import { MessageSquare, ChevronDown, ChevronRight, Send } from 'lucide-react';
12
12
  import type { FeedItem } from '@object-ui/types';
13
+ import { useDetailTranslation } from './useDetailTranslation';
13
14
 
14
15
  export interface ThreadedRepliesProps {
15
16
  /** Parent feed item (root comment) */
@@ -53,6 +54,7 @@ export const ThreadedReplies: React.FC<ThreadedRepliesProps> = ({
53
54
  showReplyInput = true,
54
55
  className,
55
56
  }) => {
57
+ const { t } = useDetailTranslation();
56
58
  const [expanded, setExpanded] = React.useState(false);
57
59
  const [replyText, setReplyText] = React.useState('');
58
60
  const [isSubmitting, setIsSubmitting] = React.useState(false);
@@ -97,7 +99,7 @@ export const ThreadedReplies: React.FC<ThreadedRepliesProps> = ({
97
99
  <ChevronRight className="h-3 w-3" />
98
100
  )}
99
101
  <MessageSquare className="h-3 w-3" />
100
- <span>{replies.length} {replies.length === 1 ? 'reply' : 'replies'}</span>
102
+ <span>{replies.length === 1 ? t('detail.replyCount', { count: replies.length }) : t('detail.replyCountPlural', { count: replies.length })}</span>
101
103
  </button>
102
104
  )}
103
105
 
@@ -138,7 +140,7 @@ export const ThreadedReplies: React.FC<ThreadedRepliesProps> = ({
138
140
  <div className="flex gap-1.5 mt-1.5">
139
141
  <input
140
142
  className="flex-1 rounded-md border border-input bg-background px-2 py-1 text-xs placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
141
- placeholder="Reply…"
143
+ placeholder={t('detail.replyPlaceholder')}
142
144
  value={replyText}
143
145
  onChange={(e) => setReplyText(e.target.value)}
144
146
  onKeyDown={handleKeyDown}