Commit 03814018e6197be9fba922e8697798129e580233
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().
Showing
5 changed files
with
66 additions
and
28 deletions
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<MessageKey, string> = { | @@ -887,6 +903,22 @@ export const zhCN: Record<MessageKey, string> = { | ||
| 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 '@/components/PageHeader' | @@ -6,10 +6,12 @@ import { PageHeader } from '@/components/PageHeader' | ||
| 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 '@/components/PageHeader' | @@ -6,10 +6,12 @@ import { PageHeader } from '@/components/PageHeader' | ||
| 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 '@/components/PageHeader' | @@ -6,10 +6,12 @@ import { PageHeader } from '@/components/PageHeader' | ||
| 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 '@/components/PageHeader' | @@ -6,10 +6,12 @@ import { PageHeader } from '@/components/PageHeader' | ||
| 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 | )} |