Skip to content

Commit 0cdc597

Browse files
Copilothotlong
andcommitted
fix: add i18n support for detail page field labels, related list headers, and highlight fields
- Add missing i18n keys (activity, editRow, deleteRow, etc.) to en.ts and zh.ts - Use useSafeFieldLabel in DetailSection for field label internationalization - Use useSafeFieldLabel in RelatedList for auto-generated column headers - Use useSafeFieldLabel in HeaderHighlight for highlight field labels - Thread objectName prop from DetailView through SectionGroup, DetailSection, HeaderHighlight, RelatedList - Fix RelatedList sortable headers to use effectiveColumns instead of raw columns Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 250f290 commit 0cdc597

7 files changed

Lines changed: 59 additions & 7 deletions

File tree

packages/i18n/src/locales/en.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,17 @@ const en = {
180180
viewAll: 'View All',
181181
new: 'New',
182182
emptyValue: '—',
183+
activity: 'Activity',
184+
editRow: 'Edit',
185+
deleteRow: 'Delete',
186+
deleteRowConfirmation: 'Are you sure you want to delete this record?',
187+
actions: 'Actions',
188+
previousPage: 'Previous',
189+
nextPage: 'Next',
190+
pageOf: 'Page {{current}} of {{total}}',
191+
sortBy: 'Sort by',
192+
filterPlaceholder: 'Filter...',
193+
highlightFields: 'Key Fields',
183194
},
184195
chart: {
185196
noData: 'No chart data available',

packages/i18n/src/locales/zh.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,17 @@ const zh = {
180180
viewAll: '查看全部',
181181
new: '新建',
182182
emptyValue: '—',
183+
activity: '活动',
184+
editRow: '编辑',
185+
deleteRow: '删除',
186+
deleteRowConfirmation: '确定要删除此记录吗?',
187+
actions: '操作',
188+
previousPage: '上一页',
189+
nextPage: '下一页',
190+
pageOf: '第 {{current}} 页,共 {{total}} 页',
191+
sortBy: '排序',
192+
filterPlaceholder: '筛选...',
193+
highlightFields: '关键字段',
183194
},
184195
chart: {
185196
noData: '暂无图表数据',

packages/plugin-detail/src/DetailSection.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ import { getCellRenderer } from '@object-ui/fields';
2929
import type { DetailViewSection as DetailViewSectionType, DetailViewField, FieldMetadata } from '@object-ui/types';
3030
import { applyDetailAutoLayout } from './autoLayout';
3131
import { useDetailTranslation } from './useDetailTranslation';
32+
import { useSafeFieldLabel } from '@object-ui/react';
3233

3334
export interface DetailSectionProps {
3435
section: DetailViewSectionType;
3536
data?: any;
3637
className?: string;
3738
/** Object schema from DataSource for field type enrichment */
3839
objectSchema?: any;
40+
/** Object name for i18n field label resolution */
41+
objectName?: string;
3942
/** Whether inline editing is active */
4043
isEditing?: boolean;
4144
/** Callback when a field value changes during inline editing */
@@ -47,12 +50,14 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
4750
data,
4851
className,
4952
objectSchema,
53+
objectName,
5054
isEditing = false,
5155
onFieldChange,
5256
}) => {
5357
const [isCollapsed, setIsCollapsed] = React.useState(section.defaultCollapsed ?? false);
5458
const [copiedField, setCopiedField] = React.useState<string | null>(null);
5559
const { t } = useDetailTranslation();
60+
const { fieldLabel } = useSafeFieldLabel();
5661

5762
const handleCopyField = React.useCallback((fieldName: string, value: any) => {
5863
const textValue = value !== null && value !== undefined ? String(value) : '';
@@ -111,7 +116,7 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
111116
return (
112117
<div key={field.name} className={cn("space-y-1.5 group", spanClass)}>
113118
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
114-
{field.label || field.name}
119+
{objectName ? fieldLabel(objectName, field.name, field.label || field.name) : (field.label || field.name)}
115120
</div>
116121
{isEditing && !field.readonly ? (
117122
<div className="min-h-[44px] sm:min-h-0">

packages/plugin-detail/src/DetailView.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
572572

573573
{/* Header Highlight Area */}
574574
{schema.highlightFields && schema.highlightFields.length > 0 && (
575-
<HeaderHighlight fields={schema.highlightFields} data={data} />
575+
<HeaderHighlight fields={schema.highlightFields} data={data} objectName={schema.objectName} />
576576
)}
577577

578578
{/* Auto Tabs mode: wrap sections, related, activity into tabs */}
@@ -620,6 +620,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
620620
group={group}
621621
data={{ ...data, ...editedValues }}
622622
objectSchema={objectSchema}
623+
objectName={schema.objectName}
623624
isEditing={isInlineEditing}
624625
onFieldChange={handleInlineFieldChange}
625626
/>
@@ -632,6 +633,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
632633
section={section}
633634
data={{ ...data, ...editedValues }}
634635
objectSchema={objectSchema}
636+
objectName={schema.objectName}
635637
isEditing={isInlineEditing}
636638
onFieldChange={handleInlineFieldChange}
637639
/>
@@ -645,6 +647,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
645647
}}
646648
data={{ ...data, ...editedValues }}
647649
objectSchema={objectSchema}
650+
objectName={schema.objectName}
648651
isEditing={isInlineEditing}
649652
onFieldChange={handleInlineFieldChange}
650653
/>
@@ -672,6 +675,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
672675
data={related.data}
673676
columns={related.columns as any}
674677
dataSource={dataSource}
678+
objectName={related.api}
675679
/>
676680
))}
677681
</div>
@@ -696,6 +700,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
696700
group={group}
697701
data={{ ...data, ...editedValues }}
698702
objectSchema={objectSchema}
703+
objectName={schema.objectName}
699704
isEditing={isInlineEditing}
700705
onFieldChange={handleInlineFieldChange}
701706
/>
@@ -712,6 +717,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
712717
section={section}
713718
data={{ ...data, ...editedValues }}
714719
objectSchema={objectSchema}
720+
objectName={schema.objectName}
715721
isEditing={isInlineEditing}
716722
onFieldChange={handleInlineFieldChange}
717723
/>
@@ -728,6 +734,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
728734
}}
729735
data={{ ...data, ...editedValues }}
730736
objectSchema={objectSchema}
737+
objectName={schema.objectName}
731738
isEditing={isInlineEditing}
732739
onFieldChange={handleInlineFieldChange}
733740
/>
@@ -751,6 +758,7 @@ export const DetailView: React.FC<DetailViewProps> = ({
751758
data={related.data}
752759
columns={related.columns as any}
753760
dataSource={dataSource}
761+
objectName={related.api}
754762
/>
755763
))}
756764
</div>

packages/plugin-detail/src/HeaderHighlight.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,23 @@
99
import * as React from 'react';
1010
import { cn, Card, CardContent } from '@object-ui/components';
1111
import type { HighlightField } from '@object-ui/types';
12+
import { useSafeFieldLabel } from '@object-ui/react';
1213

1314
export interface HeaderHighlightProps {
1415
fields: HighlightField[];
1516
data?: any;
1617
className?: string;
18+
/** Object name for i18n field label resolution */
19+
objectName?: string;
1720
}
1821

1922
export const HeaderHighlight: React.FC<HeaderHighlightProps> = ({
2023
fields,
2124
data,
2225
className,
26+
objectName,
2327
}) => {
28+
const { fieldLabel } = useSafeFieldLabel();
2429
if (!fields.length || !data) return null;
2530

2631
// Filter to only fields with values
@@ -47,7 +52,7 @@ export const HeaderHighlight: React.FC<HeaderHighlightProps> = ({
4752
<div key={field.name} className="flex flex-col gap-0.5">
4853
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
4954
{field.icon && <span className="mr-1">{field.icon}</span>}
50-
{field.label}
55+
{objectName ? fieldLabel(objectName, field.name, field.label) : field.label}
5156
</span>
5257
<span className="text-sm font-semibold truncate">
5358
{String(value)}

packages/plugin-detail/src/RelatedList.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from 'lucide-react';
2828
import type { DataSource } from '@object-ui/types';
2929
import { useDetailTranslation } from './useDetailTranslation';
30+
import { useSafeFieldLabel } from '@object-ui/react';
3031

3132
export interface RelatedListProps {
3233
title: string;
@@ -37,6 +38,8 @@ export interface RelatedListProps {
3738
columns?: any[];
3839
className?: string;
3940
dataSource?: DataSource;
41+
/** Object name for i18n field label resolution */
42+
objectName?: string;
4043
/** Callback when "New" button is clicked */
4144
onNew?: () => void;
4245
/** Callback when "View All" button is clicked */
@@ -62,6 +65,7 @@ export const RelatedList: React.FC<RelatedListProps> = ({
6265
columns,
6366
className,
6467
dataSource,
68+
objectName,
6569
onNew,
6670
onViewAll,
6771
onRowEdit,
@@ -78,6 +82,7 @@ export const RelatedList: React.FC<RelatedListProps> = ({
7882
const [filterText, setFilterText] = React.useState('');
7983
const [objectSchema, setObjectSchema] = React.useState<any>(null);
8084
const { t } = useDetailTranslation();
85+
const { fieldLabel: resolveFieldLabel } = useSafeFieldLabel();
8186

8287
// Sync internal state when data prop changes (e.g., parent fetches async data)
8388
React.useEffect(() => {
@@ -180,13 +185,16 @@ export const RelatedList: React.FC<RelatedListProps> = ({
180185
const effectiveColumns = React.useMemo(() => {
181186
if (columns && columns.length > 0) return columns;
182187
if (!objectSchema?.fields) return [];
188+
const resolvedObjectName = objectName || api || '';
183189
return Object.entries(objectSchema.fields)
184190
.filter(([key]) => !key.startsWith('_'))
185191
.map(([key, def]: [string, any]) => ({
186192
accessorKey: key,
187-
header: def.label || key,
193+
header: resolvedObjectName
194+
? resolveFieldLabel(resolvedObjectName, key, def.label || key)
195+
: (def.label || key),
188196
}));
189-
}, [columns, objectSchema]);
197+
}, [columns, objectSchema, objectName, api, resolveFieldLabel]);
190198

191199
const viewSchema = React.useMemo(() => {
192200
if (schema) return schema;
@@ -258,9 +266,9 @@ export const RelatedList: React.FC<RelatedListProps> = ({
258266
)}
259267

260268
{/* Sortable column headers */}
261-
{sortable && columns && columns.length > 0 && relatedData.length > 0 && (
269+
{sortable && effectiveColumns && effectiveColumns.length > 0 && relatedData.length > 0 && (
262270
<div className="flex flex-wrap gap-1 mb-3">
263-
{columns.map((col: any) => {
271+
{effectiveColumns.map((col: any) => {
264272
const field = col.accessorKey || col.field || col.name;
265273
if (!field) return null;
266274
const label = col.header || col.label || field;

packages/plugin-detail/src/SectionGroup.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface SectionGroupProps {
2222
data?: any;
2323
className?: string;
2424
objectSchema?: any;
25+
/** Object name for i18n field label resolution */
26+
objectName?: string;
2527
isEditing?: boolean;
2628
onFieldChange?: (field: string, value: any) => void;
2729
}
@@ -31,6 +33,7 @@ export const SectionGroup: React.FC<SectionGroupProps> = ({
3133
data,
3234
className,
3335
objectSchema,
36+
objectName,
3437
isEditing = false,
3538
onFieldChange,
3639
}) => {
@@ -45,6 +48,7 @@ export const SectionGroup: React.FC<SectionGroupProps> = ({
4548
section={section}
4649
data={data}
4750
objectSchema={objectSchema}
51+
objectName={objectName}
4852
isEditing={isEditing}
4953
onFieldChange={onFieldChange}
5054
/>

0 commit comments

Comments
 (0)