Commit 03814018e6197be9fba922e8697798129e580233

Authored by zichun
1 parent 79009234

feat(web): i18n for remaining edit pages (Location, SO, PO, WO)

Add 15 new message keys for the four Edit*Page components that
were missed in the first pass. All hardcoded labels, hints, and
error messages now go through useT().
web/src/i18n/messages.ts
@@ -448,6 +448,22 @@ export const en = { @@ -448,6 +448,22 @@ export const en = {
448 // ─── User status labels ─────────────────────────────────── 448 // ─── User status labels ───────────────────────────────────
449 'label.activeStatus': 'Active', 449 'label.activeStatus': 'Active',
450 'label.disabled': 'Disabled', 450 'label.disabled': 'Disabled',
  451 +
  452 + // ─── Edit order pages ─────────────────────────────────────
  453 + 'page.editLocation.title': 'Edit {code}',
  454 + 'page.editLocation.subtitle': 'Type: {type} (read-only after creation)',
  455 + 'page.editSalesOrder.title': 'Edit {code}',
  456 + 'page.editSalesOrder.subtitle': 'Customer: {partner}',
  457 + 'page.editSalesOrder.readOnlyHint': 'Order code, partner, lines, and currency are read-only after creation. Use this form to update custom fields.',
  458 + 'page.editSalesOrder.notEditable': 'This sales order is in {status} status and cannot be edited. Only DRAFT orders are editable.',
  459 + 'page.editPurchaseOrder.title': 'Edit {code}',
  460 + 'page.editPurchaseOrder.subtitle': 'Supplier: {partner}',
  461 + 'page.editPurchaseOrder.readOnlyHint': 'Order code, partner, lines, and currency are read-only after creation. Use this form to update custom fields.',
  462 + 'page.editPurchaseOrder.notEditable': 'This purchase order is in {status} status and cannot be edited. Only DRAFT orders are editable.',
  463 + 'page.editWorkOrder.title': 'Edit {code}',
  464 + 'page.editWorkOrder.subtitle': 'Output: {item}',
  465 + 'page.editWorkOrder.readOnlyHint': 'Order code, output item, BOM inputs, and routing operations are read-only after creation.',
  466 + 'page.editWorkOrder.notEditable': 'This work order is in {status} status and cannot be edited. Only DRAFT orders are editable.',
451 } as const 467 } as const
452 468
453 export const zhCN: Record<MessageKey, string> = { 469 export const zhCN: Record<MessageKey, string> = {
@@ -887,6 +903,22 @@ export const zhCN: Record&lt;MessageKey, string&gt; = { @@ -887,6 +903,22 @@ export const zhCN: Record&lt;MessageKey, string&gt; = {
887 // ─── 用户状态标签 ──────────────────────────────────────── 903 // ─── 用户状态标签 ────────────────────────────────────────
888 'label.activeStatus': '已启用', 904 'label.activeStatus': '已启用',
889 'label.disabled': '已禁用', 905 'label.disabled': '已禁用',
  906 +
  907 + // ─── 编辑订单页面 ────────────────────────────────────────
  908 + 'page.editLocation.title': '编辑 {code}',
  909 + 'page.editLocation.subtitle': '类型: {type}(创建后只读)',
  910 + 'page.editSalesOrder.title': '编辑 {code}',
  911 + 'page.editSalesOrder.subtitle': '客户: {partner}',
  912 + 'page.editSalesOrder.readOnlyHint': '订单编码、合作伙伴、行项目和币种创建后只读。使用此表单更新自定义字段。',
  913 + 'page.editSalesOrder.notEditable': '此销售订单处于 {status} 状态,无法编辑。只有草稿状态的订单可编辑。',
  914 + 'page.editPurchaseOrder.title': '编辑 {code}',
  915 + 'page.editPurchaseOrder.subtitle': '供应商: {partner}',
  916 + 'page.editPurchaseOrder.readOnlyHint': '订单编码、合作伙伴、行项目和币种创建后只读。使用此表单更新自定义字段。',
  917 + 'page.editPurchaseOrder.notEditable': '此采购订单处于 {status} 状态,无法编辑。只有草稿状态的订单可编辑。',
  918 + 'page.editWorkOrder.title': '编辑 {code}',
  919 + 'page.editWorkOrder.subtitle': '产出: {item}',
  920 + 'page.editWorkOrder.readOnlyHint': '工单编码、产出物料、BOM 投入和工艺路线创建后只读。',
  921 + 'page.editWorkOrder.notEditable': '此工单处于 {status} 状态,无法编辑。只有草稿状态的工单可编辑。',
890 } 922 }
891 923
892 export const locales = { 924 export const locales = {
web/src/pages/EditLocationPage.tsx
@@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39; @@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39;
6 import { Loading } from '@/components/Loading' 6 import { Loading } from '@/components/Loading'
7 import { ErrorBox } from '@/components/ErrorBox' 7 import { ErrorBox } from '@/components/ErrorBox'
8 import { DynamicExtFields } from '@/components/DynamicExtFields' 8 import { DynamicExtFields } from '@/components/DynamicExtFields'
  9 +import { useT } from '@/i18n/LocaleContext'
9 10
10 export function EditLocationPage() { 11 export function EditLocationPage() {
11 const { id = '' } = useParams<{ id: string }>() 12 const { id = '' } = useParams<{ id: string }>()
12 const navigate = useNavigate() 13 const navigate = useNavigate()
  14 + const t = useT()
13 const [location, setLocation] = useState<Location | null>(null) 15 const [location, setLocation] = useState<Location | null>(null)
14 const [name, setName] = useState('') 16 const [name, setName] = useState('')
15 const [active, setActive] = useState(true) 17 const [active, setActive] = useState(true)
@@ -53,25 +55,25 @@ export function EditLocationPage() { @@ -53,25 +55,25 @@ export function EditLocationPage() {
53 return ( 55 return (
54 <div> 56 <div>
55 <PageHeader 57 <PageHeader
56 - title={`Edit ${location.code}`}  
57 - subtitle={`Type: ${location.type} (read-only after creation)`}  
58 - actions={<button className="btn-secondary" onClick={() => navigate('/locations')}>Cancel</button>} 58 + title={t('page.editLocation.title').replace('{code}', location.code)}
  59 + subtitle={t('page.editLocation.subtitle').replace('{type}', location.type)}
  60 + actions={<button className="btn-secondary" onClick={() => navigate('/locations')}>{t('action.cancel')}</button>}
59 /> 61 />
60 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl"> 62 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl">
61 <div> 63 <div>
62 - <label className="block text-sm font-medium text-slate-700">Name</label> 64 + <label className="block text-sm font-medium text-slate-700">{t('label.name')}</label>
63 <input type="text" required value={name} onChange={(e) => setName(e.target.value)} 65 <input type="text" required value={name} onChange={(e) => setName(e.target.value)}
64 className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" /> 66 className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
65 </div> 67 </div>
66 <div className="flex items-center gap-2"> 68 <div className="flex items-center gap-2">
67 <input type="checkbox" checked={active} onChange={(e) => setActive(e.target.checked)} 69 <input type="checkbox" checked={active} onChange={(e) => setActive(e.target.checked)}
68 className="rounded border-slate-300" id="active" /> 70 className="rounded border-slate-300" id="active" />
69 - <label htmlFor="active" className="text-sm text-slate-700">Active</label> 71 + <label htmlFor="active" className="text-sm text-slate-700">{t('label.active')}</label>
70 </div> 72 </div>
71 <DynamicExtFields entityName="Location" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} /> 73 <DynamicExtFields entityName="Location" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} />
72 {error && <ErrorBox error={error} />} 74 {error && <ErrorBox error={error} />}
73 <button type="submit" className="btn-primary" disabled={submitting}> 75 <button type="submit" className="btn-primary" disabled={submitting}>
74 - {submitting ? 'Saving\u2026' : 'Save Changes'} 76 + {submitting ? t('action.saving') : t('action.saveChanges')}
75 </button> 77 </button>
76 </form> 78 </form>
77 </div> 79 </div>
web/src/pages/EditPurchaseOrderPage.tsx
@@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39; @@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39;
6 import { Loading } from '@/components/Loading' 6 import { Loading } from '@/components/Loading'
7 import { ErrorBox } from '@/components/ErrorBox' 7 import { ErrorBox } from '@/components/ErrorBox'
8 import { DynamicExtFields } from '@/components/DynamicExtFields' 8 import { DynamicExtFields } from '@/components/DynamicExtFields'
  9 +import { useT } from '@/i18n/LocaleContext'
9 10
10 export function EditPurchaseOrderPage() { 11 export function EditPurchaseOrderPage() {
11 const { id = '' } = useParams<{ id: string }>() 12 const { id = '' } = useParams<{ id: string }>()
12 const navigate = useNavigate() 13 const navigate = useNavigate()
  14 + const t = useT()
13 const [order, setOrder] = useState<PurchaseOrder | null>(null) 15 const [order, setOrder] = useState<PurchaseOrder | null>(null)
14 const [loading, setLoading] = useState(true) 16 const [loading, setLoading] = useState(true)
15 const [ext, setExt] = useState<Record<string, unknown>>({}) 17 const [ext, setExt] = useState<Record<string, unknown>>({})
@@ -50,24 +52,23 @@ export function EditPurchaseOrderPage() { @@ -50,24 +52,23 @@ export function EditPurchaseOrderPage() {
50 return ( 52 return (
51 <div> 53 <div>
52 <PageHeader 54 <PageHeader
53 - title={`Edit ${order.code}`}  
54 - subtitle={`Supplier: ${order.partnerCode} \u00b7 ${order.orderDate} \u00b7 ${order.currencyCode}`}  
55 - actions={<button className="btn-secondary" onClick={() => navigate(`/purchase-orders/${id}`)}>Cancel</button>} 55 + title={t('page.editPurchaseOrder.title').replace('{code}', order.code)}
  56 + subtitle={`${t('page.editPurchaseOrder.subtitle').replace('{partner}', order.partnerCode)} \u00B7 ${order.orderDate} \u00B7 ${order.currencyCode}`}
  57 + actions={<button className="btn-secondary" onClick={() => navigate(`/purchase-orders/${id}`)}>{t('action.cancel')}</button>}
56 /> 58 />
57 {!editable ? ( 59 {!editable ? (
58 <div className="card p-6 max-w-2xl"> 60 <div className="card p-6 max-w-2xl">
59 - <ErrorBox error={`This purchase order is in ${order.status} status and cannot be edited. Only DRAFT orders are editable.`} /> 61 + <ErrorBox error={t('page.editPurchaseOrder.notEditable').replace('{status}', order.status)} />
60 </div> 62 </div>
61 ) : ( 63 ) : (
62 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl"> 64 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl">
63 <div className="rounded-md border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"> 65 <div className="rounded-md border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
64 - Order code, partner, lines, and currency are read-only after creation.  
65 - Use this form to update custom fields. 66 + {t('page.editPurchaseOrder.readOnlyHint')}
66 </div> 67 </div>
67 <DynamicExtFields entityName="PurchaseOrder" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} /> 68 <DynamicExtFields entityName="PurchaseOrder" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} />
68 {error && <ErrorBox error={error} />} 69 {error && <ErrorBox error={error} />}
69 <button type="submit" className="btn-primary" disabled={submitting}> 70 <button type="submit" className="btn-primary" disabled={submitting}>
70 - {submitting ? 'Saving\u2026' : 'Save Changes'} 71 + {submitting ? t('action.saving') : t('action.saveChanges')}
71 </button> 72 </button>
72 </form> 73 </form>
73 )} 74 )}
web/src/pages/EditSalesOrderPage.tsx
@@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39; @@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39;
6 import { Loading } from '@/components/Loading' 6 import { Loading } from '@/components/Loading'
7 import { ErrorBox } from '@/components/ErrorBox' 7 import { ErrorBox } from '@/components/ErrorBox'
8 import { DynamicExtFields } from '@/components/DynamicExtFields' 8 import { DynamicExtFields } from '@/components/DynamicExtFields'
  9 +import { useT } from '@/i18n/LocaleContext'
9 10
10 export function EditSalesOrderPage() { 11 export function EditSalesOrderPage() {
11 const { id = '' } = useParams<{ id: string }>() 12 const { id = '' } = useParams<{ id: string }>()
12 const navigate = useNavigate() 13 const navigate = useNavigate()
  14 + const t = useT()
13 const [order, setOrder] = useState<SalesOrder | null>(null) 15 const [order, setOrder] = useState<SalesOrder | null>(null)
14 const [loading, setLoading] = useState(true) 16 const [loading, setLoading] = useState(true)
15 const [ext, setExt] = useState<Record<string, unknown>>({}) 17 const [ext, setExt] = useState<Record<string, unknown>>({})
@@ -50,24 +52,23 @@ export function EditSalesOrderPage() { @@ -50,24 +52,23 @@ export function EditSalesOrderPage() {
50 return ( 52 return (
51 <div> 53 <div>
52 <PageHeader 54 <PageHeader
53 - title={`Edit ${order.code}`}  
54 - subtitle={`Customer: ${order.partnerCode} \u00b7 ${order.orderDate} \u00b7 ${order.currencyCode}`}  
55 - actions={<button className="btn-secondary" onClick={() => navigate(`/sales-orders/${id}`)}>Cancel</button>} 55 + title={t('page.editSalesOrder.title').replace('{code}', order.code)}
  56 + subtitle={`${t('page.editSalesOrder.subtitle').replace('{partner}', order.partnerCode)} \u00B7 ${order.orderDate} \u00B7 ${order.currencyCode}`}
  57 + actions={<button className="btn-secondary" onClick={() => navigate(`/sales-orders/${id}`)}>{t('action.cancel')}</button>}
56 /> 58 />
57 {!editable ? ( 59 {!editable ? (
58 <div className="card p-6 max-w-2xl"> 60 <div className="card p-6 max-w-2xl">
59 - <ErrorBox error={`This sales order is in ${order.status} status and cannot be edited. Only DRAFT orders are editable.`} /> 61 + <ErrorBox error={t('page.editSalesOrder.notEditable').replace('{status}', order.status)} />
60 </div> 62 </div>
61 ) : ( 63 ) : (
62 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl"> 64 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl">
63 <div className="rounded-md border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"> 65 <div className="rounded-md border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
64 - Order code, partner, lines, and currency are read-only after creation.  
65 - Use this form to update custom fields. 66 + {t('page.editSalesOrder.readOnlyHint')}
66 </div> 67 </div>
67 <DynamicExtFields entityName="SalesOrder" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} /> 68 <DynamicExtFields entityName="SalesOrder" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} />
68 {error && <ErrorBox error={error} />} 69 {error && <ErrorBox error={error} />}
69 <button type="submit" className="btn-primary" disabled={submitting}> 70 <button type="submit" className="btn-primary" disabled={submitting}>
70 - {submitting ? 'Saving\u2026' : 'Save Changes'} 71 + {submitting ? t('action.saving') : t('action.saveChanges')}
71 </button> 72 </button>
72 </form> 73 </form>
73 )} 74 )}
web/src/pages/EditWorkOrderPage.tsx
@@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39; @@ -6,10 +6,12 @@ import { PageHeader } from &#39;@/components/PageHeader&#39;
6 import { Loading } from '@/components/Loading' 6 import { Loading } from '@/components/Loading'
7 import { ErrorBox } from '@/components/ErrorBox' 7 import { ErrorBox } from '@/components/ErrorBox'
8 import { DynamicExtFields } from '@/components/DynamicExtFields' 8 import { DynamicExtFields } from '@/components/DynamicExtFields'
  9 +import { useT } from '@/i18n/LocaleContext'
9 10
10 export function EditWorkOrderPage() { 11 export function EditWorkOrderPage() {
11 const { id = '' } = useParams<{ id: string }>() 12 const { id = '' } = useParams<{ id: string }>()
12 const navigate = useNavigate() 13 const navigate = useNavigate()
  14 + const t = useT()
13 const [order, setOrder] = useState<WorkOrder | null>(null) 15 const [order, setOrder] = useState<WorkOrder | null>(null)
14 const [outputQuantity, setOutputQuantity] = useState('') 16 const [outputQuantity, setOutputQuantity] = useState('')
15 const [dueDate, setDueDate] = useState('') 17 const [dueDate, setDueDate] = useState('')
@@ -56,28 +58,28 @@ export function EditWorkOrderPage() { @@ -56,28 +58,28 @@ export function EditWorkOrderPage() {
56 return ( 58 return (
57 <div> 59 <div>
58 <PageHeader 60 <PageHeader
59 - title={`Edit ${order.code}`}  
60 - subtitle={`Output: ${order.outputItemCode}`}  
61 - actions={<button className="btn-secondary" onClick={() => navigate(`/work-orders/${id}`)}>Cancel</button>} 61 + title={t('page.editWorkOrder.title').replace('{code}', order.code)}
  62 + subtitle={t('page.editWorkOrder.subtitle').replace('{item}', order.outputItemCode)}
  63 + actions={<button className="btn-secondary" onClick={() => navigate(`/work-orders/${id}`)}>{t('action.cancel')}</button>}
62 /> 64 />
63 {!editable ? ( 65 {!editable ? (
64 <div className="card p-6 max-w-2xl"> 66 <div className="card p-6 max-w-2xl">
65 - <ErrorBox error={`This work order is in ${order.status} status and cannot be edited. Only DRAFT orders are editable.`} /> 67 + <ErrorBox error={t('page.editWorkOrder.notEditable').replace('{status}', order.status)} />
66 </div> 68 </div>
67 ) : ( 69 ) : (
68 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl"> 70 <form onSubmit={onSubmit} className="card p-6 space-y-4 max-w-2xl">
69 <div className="rounded-md border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"> 71 <div className="rounded-md border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
70 - Order code, output item, BOM inputs, and routing operations are read-only after creation. 72 + {t('page.editWorkOrder.readOnlyHint')}
71 </div> 73 </div>
72 <div className="grid grid-cols-1 gap-4 sm:grid-cols-2"> 74 <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
73 <div> 75 <div>
74 - <label className="block text-sm font-medium text-slate-700">Output quantity</label> 76 + <label className="block text-sm font-medium text-slate-700">{t('label.outputQty')}</label>
75 <input type="number" required min="1" step="any" value={outputQuantity} 77 <input type="number" required min="1" step="any" value={outputQuantity}
76 onChange={(e) => setOutputQuantity(e.target.value)} 78 onChange={(e) => setOutputQuantity(e.target.value)}
77 className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" /> 79 className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
78 </div> 80 </div>
79 <div> 81 <div>
80 - <label className="block text-sm font-medium text-slate-700">Due date</label> 82 + <label className="block text-sm font-medium text-slate-700">{t('label.dueDate')}</label>
81 <input type="date" value={dueDate} onChange={(e) => setDueDate(e.target.value)} 83 <input type="date" value={dueDate} onChange={(e) => setDueDate(e.target.value)}
82 className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" /> 84 className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
83 </div> 85 </div>
@@ -85,7 +87,7 @@ export function EditWorkOrderPage() { @@ -85,7 +87,7 @@ export function EditWorkOrderPage() {
85 <DynamicExtFields entityName="WorkOrder" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} /> 87 <DynamicExtFields entityName="WorkOrder" values={ext} onChange={(k, v) => setExt(prev => ({ ...prev, [k]: v }))} />
86 {error && <ErrorBox error={error} />} 88 {error && <ErrorBox error={error} />}
87 <button type="submit" className="btn-primary" disabled={submitting}> 89 <button type="submit" className="btn-primary" disabled={submitting}>
88 - {submitting ? 'Saving\u2026' : 'Save Changes'} 90 + {submitting ? t('action.saving') : t('action.saveChanges')}
89 </button> 91 </button>
90 </form> 92 </form>
91 )} 93 )}