diff --git a/web/src/pages/AccountsPage.tsx b/web/src/pages/AccountsPage.tsx index 754c495..0a9462b 100644 --- a/web/src/pages/AccountsPage.tsx +++ b/web/src/pages/AccountsPage.tsx @@ -5,10 +5,12 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' const ACCOUNT_TYPES = ['ASSET', 'LIABILITY', 'EQUITY', 'REVENUE', 'EXPENSE'] as const export function AccountsPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -46,44 +48,44 @@ export function AccountsPage() { } const columns: Column[] = [ - { header: 'Code', key: 'code', render: (r) => {r.code} }, - { header: 'Name', key: 'name' }, - { header: 'Type', key: 'accountType' }, - { header: 'Description', key: 'description', render: (r) => r.description ?? '—' }, + { header: t('label.code'), key: 'code', render: (r) => {r.code} }, + { header: t('label.name'), key: 'name' }, + { header: t('label.type'), key: 'accountType' }, + { header: t('label.description'), key: 'description', render: (r) => r.description ?? '\u2014' }, ] return (
setShowCreate(!showCreate)}> - {showCreate ? 'Cancel' : '+ New Account'} + {showCreate ? t('action.cancel') : t('action.newAccount')} } /> {showCreate && (
- + setCode(e.target.value)} placeholder="1300" className="mt-1 w-full rounded-md border border-slate-300 px-2 py-1.5 text-sm" />
- + setName(e.target.value)} placeholder="Prepaid expenses" className="mt-1 w-full rounded-md border border-slate-300 px-2 py-1.5 text-sm" />
- +
)} diff --git a/web/src/pages/AdjustStockPage.tsx b/web/src/pages/AdjustStockPage.tsx index 4294efa..b5c27cb 100644 --- a/web/src/pages/AdjustStockPage.tsx +++ b/web/src/pages/AdjustStockPage.tsx @@ -4,9 +4,11 @@ import { catalog, inventory } from '@/api/client' import type { Item, Location } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' +import { useT } from '@/i18n/LocaleContext' export function AdjustStockPage() { const navigate = useNavigate() + const t = useT() const [items, setItems] = useState([]) const [locations, setLocations] = useState([]) const [itemCode, setItemCode] = useState('') @@ -37,7 +39,9 @@ export function AdjustStockPage() { quantity: Number(quantity), }) setResult( - `Balance set: ${bal.itemCode} @ location = ${bal.quantity}`, + t('page.adjustStock.result') + .replace('{itemCode}', bal.itemCode) + .replace('{quantity}', String(bal.quantity)), ) } catch (err: unknown) { setError(err instanceof Error ? err : new Error(String(err))) @@ -49,27 +53,27 @@ export function AdjustStockPage() { return (
navigate('/balances')}>← Balances} + title={t('page.adjustStock.title')} + subtitle={t('page.adjustStock.subtitle')} + actions={} />
- +
- +
- + setQuantity(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm text-right" /> @@ -81,7 +85,7 @@ export function AdjustStockPage() {
)}
diff --git a/web/src/pages/BalancesPage.tsx b/web/src/pages/BalancesPage.tsx index 3efe48d..657518e 100644 --- a/web/src/pages/BalancesPage.tsx +++ b/web/src/pages/BalancesPage.tsx @@ -6,6 +6,7 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' interface Row extends Record { id: string @@ -15,6 +16,7 @@ interface Row extends Record { } export function BalancesPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -37,14 +39,14 @@ export function BalancesPage() { }, []) const columns: Column[] = [ - { header: 'Item', key: 'itemCode', render: (r) => {r.itemCode} }, + { header: t('label.item'), key: 'itemCode', render: (r) => {r.itemCode} }, { - header: 'Location', + header: t('label.location'), key: 'locationCode', render: (r) => {r.locationCode}, }, { - header: 'Quantity', + header: t('label.quantity'), key: 'quantity', render: (r) => {String(r.quantity)}, }, @@ -53,9 +55,9 @@ export function BalancesPage() { return (
Adjust Stock} + title={t('page.balances.title')} + subtitle={t('page.balances.subtitle')} + actions={{t('action.adjustStock')}} /> {loading && } {error && } diff --git a/web/src/pages/CreateItemPage.tsx b/web/src/pages/CreateItemPage.tsx index e1fbd4f..33288c1 100644 --- a/web/src/pages/CreateItemPage.tsx +++ b/web/src/pages/CreateItemPage.tsx @@ -5,11 +5,13 @@ import type { Uom } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' const ITEM_TYPES = ['GOOD', 'SERVICE', 'DIGITAL'] as const export function CreateItemPage() { const navigate = useNavigate() + const t = useT() const [code, setCode] = useState('') const [name, setName] = useState('') const [description, setDescription] = useState('') @@ -49,31 +51,31 @@ export function CreateItemPage() { return (
navigate('/items')}>Cancel} + title={t('page.createItem.title')} + subtitle={t('page.createItem.subtitle')} + actions={} />
- + setCode(e.target.value)} placeholder="PAPER-120G-A3" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- + setName(e.target.value)} placeholder="120g A3 coated paper" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- +
- + setDescription(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
@@ -92,7 +94,7 @@ export function CreateItemPage() { /> {error && }
diff --git a/web/src/pages/CreateLocationPage.tsx b/web/src/pages/CreateLocationPage.tsx index ad1f7e5..074bf72 100644 --- a/web/src/pages/CreateLocationPage.tsx +++ b/web/src/pages/CreateLocationPage.tsx @@ -4,11 +4,13 @@ import { inventory } from '@/api/client' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' const LOCATION_TYPES = ['WAREHOUSE', 'BIN', 'VIRTUAL'] as const export function CreateLocationPage() { const navigate = useNavigate() + const t = useT() const [code, setCode] = useState('') const [name, setName] = useState('') const [type, setType] = useState('WAREHOUSE') @@ -34,26 +36,26 @@ export function CreateLocationPage() { return (
navigate('/locations')}>Cancel} + title={t('page.createLocation.title')} + subtitle={t('page.createLocation.subtitle')} + actions={} />
- + setCode(e.target.value)} placeholder="WH-NEW" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- + setName(e.target.value)} placeholder="New Warehouse" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- +
{error && }
diff --git a/web/src/pages/CreatePartnerPage.tsx b/web/src/pages/CreatePartnerPage.tsx index 4251c40..e69a180 100644 --- a/web/src/pages/CreatePartnerPage.tsx +++ b/web/src/pages/CreatePartnerPage.tsx @@ -4,11 +4,13 @@ import { partners } from '@/api/client' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' const PARTNER_TYPES = ['CUSTOMER', 'SUPPLIER', 'BOTH'] as const export function CreatePartnerPage() { const navigate = useNavigate() + const t = useT() const [code, setCode] = useState('') const [name, setName] = useState('') const [type, setType] = useState('CUSTOMER') @@ -41,36 +43,36 @@ export function CreatePartnerPage() { return (
navigate('/partners')}>Cancel} + title={t('page.createPartner.title')} + subtitle={t('page.createPartner.subtitle')} + actions={} />
- + setCode(e.target.value)} placeholder="CUST-NEWCO" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- + setName(e.target.value)} placeholder="New Company Ltd." className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- +
- + setEmail(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- + setPhone(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
@@ -82,7 +84,7 @@ export function CreatePartnerPage() { /> {error && }
diff --git a/web/src/pages/CreatePurchaseOrderPage.tsx b/web/src/pages/CreatePurchaseOrderPage.tsx index 4973dac..beef4ff 100644 --- a/web/src/pages/CreatePurchaseOrderPage.tsx +++ b/web/src/pages/CreatePurchaseOrderPage.tsx @@ -5,6 +5,7 @@ import type { Item, Partner } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' interface LineInput { itemCode: string @@ -14,6 +15,7 @@ interface LineInput { export function CreatePurchaseOrderPage() { const navigate = useNavigate() + const t = useT() const [code, setCode] = useState('') const [partnerCode, setPartnerCode] = useState('') const [expectedDate, setExpectedDate] = useState('') @@ -78,19 +80,19 @@ export function CreatePurchaseOrderPage() { return (
navigate('/purchase-orders')}>Cancel} + title={t('page.createPurchaseOrder.title')} + subtitle={t('page.createPurchaseOrder.subtitle')} + actions={} />
- + setCode(e.target.value)} placeholder="PO-2026-0002" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- +
- + setExpectedDate(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
@@ -107,8 +109,8 @@ export function CreatePurchaseOrderPage() {
- - + +
{lines.map((line, idx) => ( @@ -116,17 +118,17 @@ export function CreatePurchaseOrderPage() { {idx + 1} - updateLine(idx, 'quantity', e.target.value)} className="w-20 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-right" /> - updateLine(idx, 'unitPrice', e.target.value)} className="w-24 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-right" /> + onClick={() => removeLine(idx)} title={t('action.delete')}>×
))}
@@ -139,7 +141,7 @@ export function CreatePurchaseOrderPage() { /> {error && }
diff --git a/web/src/pages/CreateSalesOrderPage.tsx b/web/src/pages/CreateSalesOrderPage.tsx index bdbe7cc..2b889b4 100644 --- a/web/src/pages/CreateSalesOrderPage.tsx +++ b/web/src/pages/CreateSalesOrderPage.tsx @@ -5,6 +5,7 @@ import type { Item, Partner } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' interface LineInput { itemCode: string @@ -14,6 +15,7 @@ interface LineInput { export function CreateSalesOrderPage() { const navigate = useNavigate() + const t = useT() const [code, setCode] = useState('') const [partnerCode, setPartnerCode] = useState('') const [currencyCode] = useState('USD') @@ -80,18 +82,18 @@ export function CreateSalesOrderPage() { return (
navigate('/sales-orders')}> - Cancel + {t('action.cancel')} } />
- +
- +
- +
@@ -143,7 +145,7 @@ export function CreateSalesOrderPage() { onChange={(e) => updateLine(idx, 'itemCode', e.target.value)} className="flex-1 rounded-md border border-slate-300 px-2 py-1.5 text-sm" > - + {items.map((it) => ( diff --git a/web/src/pages/CreateUserPage.tsx b/web/src/pages/CreateUserPage.tsx index 917e50c..346f78f 100644 --- a/web/src/pages/CreateUserPage.tsx +++ b/web/src/pages/CreateUserPage.tsx @@ -3,9 +3,11 @@ import { useNavigate } from 'react-router-dom' import { identity } from '@/api/client' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' +import { useT } from '@/i18n/LocaleContext' export function CreateUserPage() { const navigate = useNavigate() + const t = useT() const [username, setUsername] = useState('') const [displayName, setDisplayName] = useState('') const [email, setEmail] = useState('') @@ -31,29 +33,29 @@ export function CreateUserPage() { return (
navigate('/users')}>Cancel} + title={t('page.createUser.title')} + subtitle={t('page.createUser.subtitle')} + actions={} />
- + setUsername(e.target.value)} placeholder="jdoe" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- + setDisplayName(e.target.value)} placeholder="Jane Doe" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- + setEmail(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
{error && }
diff --git a/web/src/pages/CreateWorkOrderPage.tsx b/web/src/pages/CreateWorkOrderPage.tsx index db5ce3b..186dad1 100644 --- a/web/src/pages/CreateWorkOrderPage.tsx +++ b/web/src/pages/CreateWorkOrderPage.tsx @@ -5,12 +5,14 @@ import type { Item, Location } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' interface BomLine { itemCode: string; quantityPerUnit: string; sourceLocationCode: string } interface OpLine { operationCode: string; workCenter: string; standardMinutes: string } export function CreateWorkOrderPage() { const navigate = useNavigate() + const t = useT() const [code, setCode] = useState('') const [outputItemCode, setOutputItemCode] = useState('') const [outputQuantity, setOutputQuantity] = useState('') @@ -77,55 +79,55 @@ export function CreateWorkOrderPage() { return (
navigate('/work-orders')}>Cancel} + title={t('page.createWorkOrder.title')} + subtitle={t('page.createWorkOrder.subtitle')} + actions={} />
- + setCode(e.target.value)} placeholder="WO-PRINT-0002" className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- +
- + setOutputQuantity(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm text-right" />
- + setDueDate(e.target.value)} className="mt-1 max-w-xs rounded-md border border-slate-300 px-3 py-2 text-sm" />
- {/* ─── BOM inputs ─────────────────────────────────────── */} + {/* BOM inputs */}
- - + +
- {bom.length === 0 &&

No BOM lines. Output will be produced without consuming materials.

} + {bom.length === 0 &&

{t('label.noBomHint')}

}
{bom.map((b, idx) => (
{idx + 1} - updateBom(idx, 'quantityPerUnit', e.target.value)} className="w-24 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-right" /> updateOp(idx, 'operationCode', e.target.value)} className="w-28 rounded-md border border-slate-300 px-2 py-1.5 text-sm" /> - updateOp(idx, 'workCenter', e.target.value)} className="flex-1 rounded-md border border-slate-300 px-2 py-1.5 text-sm" /> - updateOp(idx, 'standardMinutes', e.target.value)} className="w-20 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-right" /> @@ -171,7 +173,7 @@ export function CreateWorkOrderPage() { /> {error && }
diff --git a/web/src/pages/DashboardPage.tsx b/web/src/pages/DashboardPage.tsx index cfd5915..8138369 100644 --- a/web/src/pages/DashboardPage.tsx +++ b/web/src/pages/DashboardPage.tsx @@ -13,6 +13,7 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { useAuth } from '@/auth/AuthContext' +import { useT } from '@/i18n/LocaleContext' interface DashboardCounts { items: number @@ -27,6 +28,7 @@ interface DashboardCounts { export function DashboardPage() { const { username } = useAuth() + const t = useT() const [counts, setCounts] = useState(null) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -69,73 +71,68 @@ export function DashboardPage() { return (
{loading && } {error && } {counts && (
- - - + + + - + - +
)}
-

Getting started

+

{t('page.dashboard.gettingStarted')}

- The framework's buy-make-sell loop, end to end. + {t('page.dashboard.gettingStartedDesc')}

  1. - Set up master data — create{' '} - items,{' '} - partners, and{' '} - locations. - Then{' '} - adjust stock{' '} - to set opening balances. + {t('page.dashboard.step1')} — {t('page.dashboard.step1Desc')}{' '} + {t('page.dashboard.step1DescItems')},{' '} + {t('page.dashboard.step1DescPartners')}{t('page.dashboard.step1DescAnd')}{' '} + {t('page.dashboard.step1DescLocations')}{t('page.dashboard.step1DescThen')}{' '} + {t('page.dashboard.step1DescAdjust')}{' '} + {t('page.dashboard.step1DescEnd')}
  2. - Create a sales order —{' '} - new order{' '} - with line items. Confirm it — the system auto-generates production work orders and - posts an AR journal entry with double-entry lines (DR Accounts Receivable, CR Revenue). + {t('page.dashboard.step2')} —{' '} + {t('page.dashboard.step2Link')}{' '} + {t('page.dashboard.step2Desc')}
  3. - Walk the work order — start it, walk routing operations on the{' '} - Shop Floor, - then complete it. Materials are consumed, finished goods credited. + {t('page.dashboard.step3')} — {t('page.dashboard.step3Desc1')}{' '} + {t('page.dashboard.step3ShopFloor')}{t('page.dashboard.step3Desc2')}
  4. - Ship the sales order — stock leaves the warehouse, the AR journal - entry settles. View the ledger in{' '} - Movements{' '} - and double-entry lines in{' '} - Journal Entries. + {t('page.dashboard.step4')} — {t('page.dashboard.step4Desc1')}{' '} + {t('page.dashboard.step4Movements')}{' '} + {t('page.dashboard.step4Desc2')}{' '} + {t('page.dashboard.step4JE')}.
  5. - Restock via purchase — create a{' '} - purchase order, - confirm, and receive into a warehouse. AP journal entry posts and settles. + {t('page.dashboard.step5')} — {t('page.dashboard.step5Desc1')}{' '} + {t('page.dashboard.step5Link')}{t('page.dashboard.step5Desc2')}
diff --git a/web/src/pages/EditItemPage.tsx b/web/src/pages/EditItemPage.tsx index 4b10033..2ff934f 100644 --- a/web/src/pages/EditItemPage.tsx +++ b/web/src/pages/EditItemPage.tsx @@ -6,12 +6,14 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' const ITEM_TYPES = ['GOOD', 'SERVICE', 'DIGITAL'] as const export function EditItemPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() + const t = useT() const [item, setItem] = useState(null) const [name, setName] = useState('') const [description, setDescription] = useState('') @@ -60,39 +62,39 @@ export function EditItemPage() { return (
navigate('/items')}>Cancel} + title={t('page.editItem.title').replace('{code}', item.code)} + subtitle={t('page.editItem.subtitle').replace('{uom}', item.baseUomCode)} + actions={} />
- + setName(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- +
- + setDescription(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
setActive(e.target.checked)} className="rounded border-slate-300" id="active" /> - +
setExt(prev => ({ ...prev, [k]: v }))} /> {error && }
diff --git a/web/src/pages/EditPartnerPage.tsx b/web/src/pages/EditPartnerPage.tsx index 5d18a9b..4448c9d 100644 --- a/web/src/pages/EditPartnerPage.tsx +++ b/web/src/pages/EditPartnerPage.tsx @@ -6,12 +6,14 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DynamicExtFields } from '@/components/DynamicExtFields' +import { useT } from '@/i18n/LocaleContext' const PARTNER_TYPES = ['CUSTOMER', 'SUPPLIER', 'BOTH'] as const export function EditPartnerPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() + const t = useT() const [partner, setPartner] = useState(null) const [name, setName] = useState('') const [type, setType] = useState('CUSTOMER') @@ -61,31 +63,31 @@ export function EditPartnerPage() { return (
navigate('/partners')}>Cancel} + title={t('page.editPartner.title').replace('{code}', partner.code)} + subtitle={t('page.editPartner.subtitle')} + actions={} />
- + setName(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- +
- + setEmail(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
- + setPhone(e.target.value)} className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm" />
@@ -93,7 +95,7 @@ export function EditPartnerPage() { setExt(prev => ({ ...prev, [k]: v }))} /> {error && }
diff --git a/web/src/pages/FormDesignerPage.tsx b/web/src/pages/FormDesignerPage.tsx index 9794d51..9180b39 100644 --- a/web/src/pages/FormDesignerPage.tsx +++ b/web/src/pages/FormDesignerPage.tsx @@ -17,6 +17,7 @@ import { ErrorBox } from '@/components/ErrorBox' import { Loading } from '@/components/Loading' import { vibeWidgets } from '@/components/form-widgets' import { vibeTemplates } from '@/components/form-widgets/vibeErpTheme' +import { useT } from '@/i18n/LocaleContext' // ── Types ────────────────────────────────────────────────────────── @@ -165,6 +166,7 @@ function hydrateFields(def: FormDefinition): DesignerField[] { export function FormDesignerPage() { const { slug: routeSlug } = useParams<{ slug: string }>() const navigate = useNavigate() + const t = useT() const isEdit = Boolean(routeSlug) // ── Top bar state ── @@ -302,15 +304,15 @@ export function FormDesignerPage() { return (
{/* ── Top bar ── */}
- +
- +
- +
- + - {saving ? 'Saving...' : 'Save'} + {saving ? t('action.saving') : t('action.save')}
@@ -387,7 +389,7 @@ export function FormDesignerPage() { {/* ── Left panel: field list (3/5 = 60%) ── */}

- Fields + {t('label.fields')}

{fields.map((field, idx) => ( @@ -409,10 +411,10 @@ export function FormDesignerPage() {
@@ -420,12 +422,12 @@ export function FormDesignerPage() { {/* ── Right panel: live preview (2/5 = 40%) ── */}

- Live Preview + {t('label.livePreview')}

{Object.keys(jsonSchema.properties ?? {}).length === 0 ? (

- Add at least one field with a key to see the preview. + {t('label.addFieldHint')}

) : (
@@ -485,20 +489,20 @@ function FieldRow({ onMove={onMove} /> - Section + {t('label.section')} onUpdate({ sectionTitle: e.target.value })} - placeholder="Section title" + placeholder={t('label.sectionTitle')} className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-sm" /> @@ -524,7 +528,7 @@ function FieldRow({ type="text" value={field.label} onChange={(e) => onUpdate({ label: e.target.value })} - placeholder="Label" + placeholder={t('label.label')} className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-sm" /> @@ -547,7 +551,7 @@ function FieldRow({ onChange={(e) => onUpdate({ required: e.target.checked })} className="rounded border-slate-300" /> - Req + {t('label.req')} @@ -567,7 +571,7 @@ function FieldRow({ type="button" onClick={onToggleExpand} className="text-slate-400 hover:text-slate-600 text-sm px-1" - title="Toggle details" + title={t('label.toggleDetails')} > {isExpanded ? '\u25B2' : '\u25BC'} @@ -575,7 +579,7 @@ function FieldRow({ type="button" onClick={onRemove} className="text-slate-400 hover:text-rose-500 text-sm px-1" - title="Remove field" + title={t('label.removeField')} > x @@ -586,7 +590,7 @@ function FieldRow({
- Show when + {t('label.showWhen')} - equals + {t('label.equals')} void }) { + const t = useT() return (
@@ -718,7 +723,7 @@ function ReorderButtons({ onClick={() => onMove('down')} disabled={index === total - 1} className="text-slate-400 hover:text-slate-600 disabled:opacity-30 text-xs leading-none" - title="Move down" + title={t('label.moveDown')} > ▼ diff --git a/web/src/pages/ItemsPage.tsx b/web/src/pages/ItemsPage.tsx index 4da06bf..e4b06a7 100644 --- a/web/src/pages/ItemsPage.tsx +++ b/web/src/pages/ItemsPage.tsx @@ -6,8 +6,10 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' export function ItemsPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -22,28 +24,28 @@ export function ItemsPage() { const columns: Column[] = [ { - header: 'Code', + header: t('label.code'), key: 'code', render: (r) => ( {r.code} ), }, - { header: 'Name', key: 'name' }, - { header: 'Type', key: 'itemType' }, + { header: t('label.name'), key: 'name' }, + { header: t('label.type'), key: 'itemType' }, { header: 'UoM', key: 'baseUomCode', render: (r) => {r.baseUomCode} }, { - header: 'Active', + header: t('label.active'), key: 'active', - render: (r) => (r.active ? : ), + render: (r) => (r.active ? {'\u25CF'} : {'\u25CF'}), }, ] return (
+ New Item} + title={t('page.items.title')} + subtitle={t('page.items.subtitle')} + actions={{t('action.newItem')}} /> {loading && } {error && } diff --git a/web/src/pages/JournalEntriesPage.tsx b/web/src/pages/JournalEntriesPage.tsx index e66cb07..92fa5a7 100644 --- a/web/src/pages/JournalEntriesPage.tsx +++ b/web/src/pages/JournalEntriesPage.tsx @@ -5,8 +5,10 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { StatusBadge } from '@/components/StatusBadge' +import { useT } from '@/i18n/LocaleContext' export function JournalEntriesPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -34,26 +36,26 @@ export function JournalEntriesPage() { return (
{loading && } {error && } {!loading && !error && rows.length === 0 && ( -
No journal entries yet.
+
{t('label.noJournalEntriesYet')}
)} {!loading && !error && rows.length > 0 && (
- - - - - - - + + + + + + + @@ -66,7 +68,7 @@ export function JournalEntriesPage() { className="hover:bg-slate-50 cursor-pointer" onClick={() => toggle(je.id)} > - + @@ -83,11 +85,11 @@ export function JournalEntriesPage() {
PostedTypeStatusOrderPartnerAmountLines{t('label.posted')}{t('label.type')}{t('label.status')}{t('label.order')}{t('label.partner')}{t('label.amount')}{t('label.lines')}
{je.postedAt ? new Date(je.postedAt).toLocaleString() : '—'}{je.postedAt ? new Date(je.postedAt).toLocaleString() : '\u2014'} {je.type} {je.orderCode}
- - - - - + + + + + diff --git a/web/src/pages/ListViewDesignerPage.tsx b/web/src/pages/ListViewDesignerPage.tsx index d0cdb15..bf86d54 100644 --- a/web/src/pages/ListViewDesignerPage.tsx +++ b/web/src/pages/ListViewDesignerPage.tsx @@ -16,6 +16,7 @@ import { metadata } from '@/api/client' import { PageHeader } from '@/components/PageHeader' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' // ─── Designer state types ────────────────────────────────────────── @@ -65,6 +66,7 @@ function emptyState(): DesignerState { export function ListViewDesignerPage() { const navigate = useNavigate() + const t = useT() const { slug: routeSlug } = useParams<{ slug: string }>() const isEdit = Boolean(routeSlug) @@ -254,7 +256,7 @@ export function ListViewDesignerPage() { if (loading) { return ( -
Loading...
+
{t('label.loading')}
) } @@ -268,14 +270,14 @@ export function ListViewDesignerPage() { return (
navigate('/admin/metadata')} > - Cancel + {t('action.cancel')} } /> @@ -285,11 +287,11 @@ export function ListViewDesignerPage() { {/* ── Top bar: title, entity, slug ───────────────────── */}
-

General

+

{t('label.general')}

-

Columns

+

{t('label.columns')}

{state.columns.length === 0 ? (

- No columns defined yet. Click "Add Column" to start. + {t('label.noColumnsHint')}

) : (
#AccountDebitCreditDescription{t('label.lineNo')}{t('label.accountType')}{t('label.debit')}{t('label.credit')}{t('label.description')}
- - - - - - + + + + + + @@ -428,7 +430,7 @@ export function ListViewDesignerPage() { className="rounded p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 disabled:opacity-30" disabled={idx === 0} onClick={() => moveColumn(idx, -1)} - title="Move up" + title={t('label.moveUp')} > ▲ @@ -437,7 +439,7 @@ export function ListViewDesignerPage() { className="rounded p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 disabled:opacity-30" disabled={idx === state.columns.length - 1} onClick={() => moveColumn(idx, 1)} - title="Move down" + title={t('label.moveDown')} > ▼ @@ -448,7 +450,7 @@ export function ListViewDesignerPage() { type="button" className="text-slate-400 hover:text-rose-500" onClick={() => removeColumn(idx)} - title="Remove column" + title={t('label.removeColumn')} > × @@ -464,19 +466,19 @@ export function ListViewDesignerPage() { {/* ── Filters section ────────────────────────────────── */}
-

Filters

+

{t('label.filters')}

{state.filters.length === 0 ? (

- No filters defined. Click "Add Filter" to add filterable fields. + {t('label.noFiltersHint')}

) : (
@@ -488,7 +490,7 @@ export function ListViewDesignerPage() { onChange={(e) => updateFilter(idx, { field: e.target.value }) } - placeholder="Field name" + placeholder={t('label.fieldName')} className={smallInputCls + ' flex-1'} /> - + {state.columns .filter((c) => c.field.trim()) .map((c) => ( @@ -564,7 +566,7 @@ export function ListViewDesignerPage() {
-

Preview

+

{t('label.preview')}

{previewColumns.length === 0 ? (

- Add visible columns with a field name to see a preview. + {t('label.addColumnsHint')}

) : (
diff --git a/web/src/pages/LocationsPage.tsx b/web/src/pages/LocationsPage.tsx index 9422592..95e2d86 100644 --- a/web/src/pages/LocationsPage.tsx +++ b/web/src/pages/LocationsPage.tsx @@ -6,8 +6,10 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' export function LocationsPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -21,22 +23,22 @@ export function LocationsPage() { }, []) const columns: Column[] = [ - { header: 'Code', key: 'code', render: (r) => {r.code} }, - { header: 'Name', key: 'name' }, - { header: 'Type', key: 'type' }, + { header: t('label.code'), key: 'code', render: (r) => {r.code} }, + { header: t('label.name'), key: 'name' }, + { header: t('label.type'), key: 'type' }, { - header: 'Active', + header: t('label.active'), key: 'active', - render: (r) => (r.active ? : ), + render: (r) => (r.active ? {'\u25CF'} : {'\u25CF'}), }, ] return (
+ New Location} + title={t('page.locations.title')} + subtitle={t('page.locations.subtitle')} + actions={{t('action.newLocation')}} /> {loading && } {error && } diff --git a/web/src/pages/LoginPage.tsx b/web/src/pages/LoginPage.tsx index f1f6de4..55eb05e 100644 --- a/web/src/pages/LoginPage.tsx +++ b/web/src/pages/LoginPage.tsx @@ -4,6 +4,7 @@ import { useAuth } from '@/auth/AuthContext' import { meta } from '@/api/client' import type { MetaInfo } from '@/types/api' import { ErrorBox } from '@/components/ErrorBox' +import { useT } from '@/i18n/LocaleContext' interface LocationState { from?: string @@ -13,6 +14,7 @@ export function LoginPage() { const { login, token, loading } = useAuth() const navigate = useNavigate() const location = useLocation() + const t = useT() const [username, setUsername] = useState('admin') const [password, setPassword] = useState('') const [error, setError] = useState(null) @@ -47,13 +49,13 @@ export function LoginPage() {

vibe_erp

- Composable ERP framework for the printing industry + {t('page.login.tagline')}

- +
- +

- The bootstrap admin password is printed to the application boot log on first start. + {t('page.login.passwordHint')}

{error ? : null}

{info ? ( <> - Connected to {info.name}{' '} + {t('page.login.connectedTo')} {info.name}{' '} {info.implementationVersion} ) : ( - 'Connecting…' + t('page.login.connecting') )}

diff --git a/web/src/pages/MetadataAdminPage.tsx b/web/src/pages/MetadataAdminPage.tsx index 2baa0e7..86e09c7 100644 --- a/web/src/pages/MetadataAdminPage.tsx +++ b/web/src/pages/MetadataAdminPage.tsx @@ -363,8 +363,8 @@ export function MetadataAdminPage() { const entityCols: Column[] = [ { header: t('label.name'), key: 'name' }, - { header: 'PBC', key: 'pbc' }, - { header: 'Table', key: 'table' }, + { header: t('label.pbc'), key: 'pbc' }, + { header: t('label.table'), key: 'table' }, { header: t('label.description'), key: 'description', render: (r) => r.description ?? '—' }, { header: t('label.source'), key: 'source', render: (r) => }, ] @@ -373,8 +373,8 @@ export function MetadataAdminPage() { { header: t('label.fieldKey'), key: 'key', render: (r) => {r.key} }, { header: t('label.targetEntity'), key: 'targetEntity' }, { header: t('label.fieldType'), key: 'type', render: (r) => r.type.kind }, - { header: 'Required', key: 'required', render: (r) => (r.required ? 'Yes' : 'No') }, - { header: 'PII', key: 'pii', render: (r) => (r.pii ? 'Yes' : 'No') }, + { header: t('label.required'), key: 'required', render: (r) => (r.required ? t('label.yes') : t('label.no')) }, + { header: t('label.pii'), key: 'pii', render: (r) => (r.pii ? t('label.yes') : t('label.no')) }, { header: t('label.source'), key: 'source', render: (r) => }, { header: '', @@ -386,7 +386,7 @@ export function MetadataAdminPage() { className="text-xs text-blue-600 hover:underline" onClick={() => openCfEdit(r)} > - Edit + {t('action.edit')}
navigate('/admin/metadata/forms/new')} > - + New Form + {t('action.newForm')}
navigate('/admin/metadata/list-views/new')} > - + New List View + {t('action.newListView')}
- + Add Condition + {t('action.addCondition')}
@@ -982,7 +982,7 @@ export function MetadataAdminPage() { ]) } > - + Add Action + {t('action.addAction')} @@ -1030,7 +1030,7 @@ export function MetadataAdminPage() {
{/* Tab bar */} diff --git a/web/src/pages/MovementsPage.tsx b/web/src/pages/MovementsPage.tsx index 8415822..dacefd5 100644 --- a/web/src/pages/MovementsPage.tsx +++ b/web/src/pages/MovementsPage.tsx @@ -5,6 +5,7 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' interface Row extends Record { id: string @@ -17,6 +18,7 @@ interface Row extends Record { } export function MovementsPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -46,15 +48,15 @@ export function MovementsPage() { const columns: Column[] = [ { - header: 'Occurred', + header: t('label.occurred'), key: 'occurredAt', render: (r) => - r.occurredAt ? new Date(r.occurredAt).toLocaleString() : '—', + r.occurredAt ? new Date(r.occurredAt).toLocaleString() : '\u2014', }, - { header: 'Item', key: 'itemCode', render: (r) => {r.itemCode} }, - { header: 'Location', key: 'locationCode', render: (r) => {r.locationCode} }, + { header: t('label.item'), key: 'itemCode', render: (r) => {r.itemCode} }, + { header: t('label.location'), key: 'locationCode', render: (r) => {r.locationCode} }, { - header: 'Δ', + header: t('label.delta'), key: 'delta', render: (r) => { const n = Number(r.delta) @@ -62,15 +64,15 @@ export function MovementsPage() { return {String(r.delta)} }, }, - { header: 'Reason', key: 'reason' }, - { header: 'Reference', key: 'reference', render: (r) => r.reference ?? '—' }, + { header: t('label.reason'), key: 'reason' }, + { header: t('label.reference'), key: 'reference', render: (r) => r.reference ?? '\u2014' }, ] return (
{loading && } {error && } diff --git a/web/src/pages/PartnersPage.tsx b/web/src/pages/PartnersPage.tsx index a78721f..805d514 100644 --- a/web/src/pages/PartnersPage.tsx +++ b/web/src/pages/PartnersPage.tsx @@ -6,8 +6,10 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' export function PartnersPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -22,29 +24,29 @@ export function PartnersPage() { const columns: Column[] = [ { - header: 'Code', + header: t('label.code'), key: 'code', render: (r) => ( {r.code} ), }, - { header: 'Name', key: 'name' }, - { header: 'Type', key: 'type' }, - { header: 'Email', key: 'email', render: (r) => r.email ?? '—' }, - { header: 'Phone', key: 'phone', render: (r) => r.phone ?? '—' }, + { header: t('label.name'), key: 'name' }, + { header: t('label.type'), key: 'type' }, + { header: t('label.email'), key: 'email', render: (r) => r.email ?? '\u2014' }, + { header: t('label.phone'), key: 'phone', render: (r) => r.phone ?? '\u2014' }, { - header: 'Active', + header: t('label.active'), key: 'active', - render: (r) => (r.active ? : ), + render: (r) => (r.active ? {'\u25CF'} : {'\u25CF'}), }, ] return (
+ New Partner} + title={t('page.partners.title')} + subtitle={t('page.partners.subtitle')} + actions={{t('action.newPartner')}} /> {loading && } {error && } diff --git a/web/src/pages/PurchaseOrderDetailPage.tsx b/web/src/pages/PurchaseOrderDetailPage.tsx index f6bc6e3..3cd372e 100644 --- a/web/src/pages/PurchaseOrderDetailPage.tsx +++ b/web/src/pages/PurchaseOrderDetailPage.tsx @@ -1,7 +1,7 @@ -// Purchase-order detail screen — symmetric to SalesOrderDetailPage. +// Purchase-order detail screen -- symmetric to SalesOrderDetailPage. // Confirm a DRAFT, receive a CONFIRMED into a warehouse, or cancel // either. Each action surfaces the corresponding stock movement -// (PURCHASE_RECEIPT) and pbc-finance journal entry (AP, POSTED → +// (PURCHASE_RECEIPT) and pbc-finance journal entry (AP, POSTED -> // SETTLED on receive) inline. import { useCallback, useEffect, useState } from 'react' @@ -12,10 +12,12 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { StatusBadge } from '@/components/StatusBadge' +import { useT } from '@/i18n/LocaleContext' export function PurchaseOrderDetailPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() + const t = useT() const [order, setOrder] = useState(null) const [locations, setLocations] = useState([]) const [receivingLocation, setReceivingLocation] = useState('') @@ -68,7 +70,7 @@ export function PurchaseOrderDetailPage() { const updated = await purchaseOrders.confirm(order.id) setOrder(updated) await reloadSideEffects(updated.code) - setActionMessage('Confirmed. pbc-finance has posted an AP journal entry.') + setActionMessage(t('page.purchaseOrderDetail.confirmMsg')) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { @@ -78,7 +80,7 @@ export function PurchaseOrderDetailPage() { const onReceive = async () => { if (!receivingLocation) { - setError(new Error('Pick a receiving location first.')) + setError(new Error(t('page.purchaseOrderDetail.pickLocation'))) return } setActing(true) @@ -89,7 +91,7 @@ export function PurchaseOrderDetailPage() { setOrder(updated) await reloadSideEffects(updated.code) setActionMessage( - `Received into ${receivingLocation}. Stock credited, journal entry settled.`, + t('page.purchaseOrderDetail.receiveMsg').replace('{location}', receivingLocation), ) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) @@ -106,7 +108,7 @@ export function PurchaseOrderDetailPage() { const updated = await purchaseOrders.cancel(order.id) setOrder(updated) await reloadSideEffects(updated.code) - setActionMessage('Cancelled. pbc-finance has reversed any open journal entry.') + setActionMessage(t('page.purchaseOrderDetail.cancelMsg')) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { @@ -121,11 +123,11 @@ export function PurchaseOrderDetailPage() { return (
navigate('/purchase-orders')}> - ← Back + {t('action.back')} } /> @@ -133,11 +135,11 @@ export function PurchaseOrderDetailPage() {
- Status: + {t('label.status')}:
-
Total
+
{t('label.total')}
{Number(order.totalAmount).toLocaleString(undefined, { minimumFractionDigits: 2, @@ -154,10 +156,10 @@ export function PurchaseOrderDetailPage() {
-

Actions

+

{t('label.actions')}

ShowFieldLabelFormatSortableOrder{t('label.show')}{t('label.field')}{t('label.label')}{t('label.format')}{t('label.sortable')}{t('label.orderMeta')}
- - - - - + + + + + @@ -222,18 +224,18 @@ export function PurchaseOrderDetailPage() {
-

Inventory movements

+

{t('label.inventoryMovements')}

{movements.length === 0 ? ( -
No movements yet.
+
{t('label.noMovementsYet')}
) : (
#ItemQtyUnit priceLine total{t('label.lineNo')}{t('label.item')}{t('label.qty')}{t('label.unitPrice')}{t('label.lineTotal')}
- - - - + + + + @@ -242,7 +244,7 @@ export function PurchaseOrderDetailPage() { - + ))} @@ -251,18 +253,18 @@ export function PurchaseOrderDetailPage() {
-

Journal entries

+

{t('label.journalEntries')}

{journalEntries.length === 0 ? ( -
No entries yet.
+
{t('label.noEntriesYet')}
) : (
ItemΔReasonReference{t('label.item')}{t('label.delta')}{t('label.reason')}{t('label.reference')}
{m.itemCode} {String(m.delta)} {m.reason}{m.reference ?? '—'}{m.reference ?? '\u2014'}
- - - - + + + + diff --git a/web/src/pages/PurchaseOrdersPage.tsx b/web/src/pages/PurchaseOrdersPage.tsx index b8419c4..4304d7a 100644 --- a/web/src/pages/PurchaseOrdersPage.tsx +++ b/web/src/pages/PurchaseOrdersPage.tsx @@ -7,8 +7,10 @@ import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' import { StatusBadge } from '@/components/StatusBadge' +import { useT } from '@/i18n/LocaleContext' export function PurchaseOrdersPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -23,7 +25,7 @@ export function PurchaseOrdersPage() { const columns: Column[] = [ { - header: 'Code', + header: t('label.code'), key: 'code', render: (r) => ( @@ -31,12 +33,12 @@ export function PurchaseOrdersPage() { ), }, - { header: 'Supplier', key: 'partnerCode', render: (r) => {r.partnerCode} }, - { header: 'Order date', key: 'orderDate' }, - { header: 'Expected', key: 'expectedDate', render: (r) => r.expectedDate ?? '—' }, - { header: 'Status', key: 'status', render: (r) => }, + { header: t('label.supplier'), key: 'partnerCode', render: (r) => {r.partnerCode} }, + { header: t('label.orderDate'), key: 'orderDate' }, + { header: t('label.expected'), key: 'expectedDate', render: (r) => r.expectedDate ?? '\u2014' }, + { header: t('label.status'), key: 'status', render: (r) => }, { - header: 'Total', + header: t('label.total'), key: 'totalAmount', render: (r) => ( @@ -52,9 +54,9 @@ export function PurchaseOrdersPage() { return (
+ New Order} + title={t('page.purchaseOrders.title')} + subtitle={t('page.purchaseOrders.subtitle')} + actions={{t('action.newOrder')}} /> {loading && } {error && } diff --git a/web/src/pages/RolesPage.tsx b/web/src/pages/RolesPage.tsx index 2579872..d62e284 100644 --- a/web/src/pages/RolesPage.tsx +++ b/web/src/pages/RolesPage.tsx @@ -5,8 +5,10 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' export function RolesPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -43,36 +45,36 @@ export function RolesPage() { } const columns: Column[] = [ - { header: 'Code', key: 'code', render: (r) => {r.code} }, - { header: 'Name', key: 'name' }, - { header: 'Description', key: 'description', render: (r) => r.description ?? '—' }, + { header: t('label.code'), key: 'code', render: (r) => {r.code} }, + { header: t('label.name'), key: 'name' }, + { header: t('label.description'), key: 'description', render: (r) => r.description ?? '\u2014' }, ] return (
setShowCreate(!showCreate)}> - {showCreate ? 'Cancel' : '+ New Role'} + {showCreate ? t('action.cancel') : t('action.newRole')} } /> {showCreate && (
- + setCode(e.target.value)} placeholder="sales-clerk" className="mt-1 w-full rounded-md border border-slate-300 px-2 py-1.5 text-sm" />
- + setName(e.target.value)} placeholder="Sales Clerk" className="mt-1 w-full rounded-md border border-slate-300 px-2 py-1.5 text-sm" />
)} diff --git a/web/src/pages/SalesOrderDetailPage.tsx b/web/src/pages/SalesOrderDetailPage.tsx index 6aff3ea..215d7da 100644 --- a/web/src/pages/SalesOrderDetailPage.tsx +++ b/web/src/pages/SalesOrderDetailPage.tsx @@ -3,11 +3,11 @@ // **The headline demo flow.** Confirm a DRAFT, ship a CONFIRMED, // or cancel either. Each action updates the order in place, // reloads the journal entry list to show the AR row appearing -// (POSTED → SETTLED), and reloads stock movements to show the +// (POSTED -> SETTLED), and reloads stock movements to show the // SALES_SHIPMENT ledger entry appearing. // // **Why ship asks for a location.** The framework requires every -// shipment to name the warehouse the goods came from — the +// shipment to name the warehouse the goods came from -- the // inventory ledger tags the row with that location and the audit // trail must always answer "which warehouse shipped this". The UI // proposes the first WAREHOUSE-typed location it finds; an @@ -21,10 +21,12 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { StatusBadge } from '@/components/StatusBadge' +import { useT } from '@/i18n/LocaleContext' export function SalesOrderDetailPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() + const t = useT() const [order, setOrder] = useState(null) const [locations, setLocations] = useState([]) const [shippingLocation, setShippingLocation] = useState('') @@ -77,7 +79,7 @@ export function SalesOrderDetailPage() { const updated = await salesOrders.confirm(order.id) setOrder(updated) await reloadSideEffects(updated.code) - setActionMessage('Confirmed. pbc-finance has posted an AR journal entry.') + setActionMessage(t('page.salesOrderDetail.confirmMsg')) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { @@ -87,7 +89,7 @@ export function SalesOrderDetailPage() { const onShip = async () => { if (!shippingLocation) { - setError(new Error('Pick a shipping location first.')) + setError(new Error(t('page.salesOrderDetail.pickLocation'))) return } setActing(true) @@ -98,7 +100,7 @@ export function SalesOrderDetailPage() { setOrder(updated) await reloadSideEffects(updated.code) setActionMessage( - `Shipped from ${shippingLocation}. Stock debited, journal entry settled.`, + t('page.salesOrderDetail.shipMsg').replace('{location}', shippingLocation), ) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) @@ -115,7 +117,7 @@ export function SalesOrderDetailPage() { const updated = await salesOrders.cancel(order.id) setOrder(updated) await reloadSideEffects(updated.code) - setActionMessage('Cancelled. pbc-finance has reversed any open journal entry.') + setActionMessage(t('page.salesOrderDetail.cancelMsg')) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { @@ -130,11 +132,11 @@ export function SalesOrderDetailPage() { return (
navigate('/sales-orders')}> - ← Back + {t('action.back')} } /> @@ -142,11 +144,11 @@ export function SalesOrderDetailPage() {
- Status: + {t('label.status')}:
-
Total
+
{t('label.total')}
{Number(order.totalAmount).toLocaleString(undefined, { minimumFractionDigits: 2, @@ -164,10 +166,10 @@ export function SalesOrderDetailPage() {
-

Actions

+

{t('label.actions')}

CodeTypeStatusAmount{t('label.code')}{t('label.type')}{t('label.status')}{t('label.amount')}
- - - - - + + + + + @@ -232,18 +234,18 @@ export function SalesOrderDetailPage() {
-

Inventory movements

+

{t('label.inventoryMovements')}

{movements.length === 0 ? ( -
No movements yet.
+
{t('label.noMovementsYet')}
) : (
#ItemQtyUnit priceLine total{t('label.lineNo')}{t('label.item')}{t('label.qty')}{t('label.unitPrice')}{t('label.lineTotal')}
- - - - + + + + @@ -252,7 +254,7 @@ export function SalesOrderDetailPage() { - + ))} @@ -261,18 +263,18 @@ export function SalesOrderDetailPage() {
-

Journal entries

+

{t('label.journalEntries')}

{journalEntries.length === 0 ? ( -
No entries yet.
+
{t('label.noEntriesYet')}
) : (
ItemΔReasonReference{t('label.item')}{t('label.delta')}{t('label.reason')}{t('label.reference')}
{m.itemCode} {String(m.delta)} {m.reason}{m.reference ?? '—'}{m.reference ?? '\u2014'}
- - - - + + + + @@ -293,7 +295,7 @@ export function SalesOrderDetailPage() {
CodeTypeStatusAmount{t('label.code')}{t('label.type')}{t('label.status')}{t('label.amount')}
)}
- View all journal entries → + {t('label.viewAllJournalEntries')} →
diff --git a/web/src/pages/SalesOrdersPage.tsx b/web/src/pages/SalesOrdersPage.tsx index ff3c9b8..decc75e 100644 --- a/web/src/pages/SalesOrdersPage.tsx +++ b/web/src/pages/SalesOrdersPage.tsx @@ -7,8 +7,10 @@ import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' import { StatusBadge } from '@/components/StatusBadge' +import { useT } from '@/i18n/LocaleContext' export function SalesOrdersPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -23,7 +25,7 @@ export function SalesOrdersPage() { const columns: Column[] = [ { - header: 'Code', + header: t('label.code'), key: 'code', render: (r) => ( @@ -31,11 +33,11 @@ export function SalesOrdersPage() { ), }, - { header: 'Customer', key: 'partnerCode', render: (r) => {r.partnerCode} }, - { header: 'Date', key: 'orderDate' }, - { header: 'Status', key: 'status', render: (r) => }, + { header: t('label.customer'), key: 'partnerCode', render: (r) => {r.partnerCode} }, + { header: t('label.date'), key: 'orderDate' }, + { header: t('label.status'), key: 'status', render: (r) => }, { - header: 'Total', + header: t('label.total'), key: 'totalAmount', render: (r) => ( @@ -48,7 +50,7 @@ export function SalesOrdersPage() { ), }, { - header: 'Lines', + header: t('label.lines'), key: 'lines', render: (r) => {r.lines.length}, }, @@ -57,10 +59,10 @@ export function SalesOrdersPage() { return (
+ New Order + {t('action.newOrder')} } /> {loading && } diff --git a/web/src/pages/ShopFloorPage.tsx b/web/src/pages/ShopFloorPage.tsx index 9a37bbd..55c9fad 100644 --- a/web/src/pages/ShopFloorPage.tsx +++ b/web/src/pages/ShopFloorPage.tsx @@ -3,7 +3,7 @@ // Polls /api/v1/production/work-orders/shop-floor every 5s and // renders one card per IN_PROGRESS work order with its current // operation, planned vs actual minutes, and operations completed. -// Designed to be projected on a wall-mounted screen — the cards +// Designed to be projected on a wall-mounted screen -- the cards // are large, the typography is high-contrast, and the only state // is "what's running right now". @@ -15,10 +15,12 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { StatusBadge } from '@/components/StatusBadge' +import { useT } from '@/i18n/LocaleContext' const POLL_MS = 5000 export function ShopFloorPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -52,16 +54,16 @@ export function ShopFloorPage() { return (
{loading && } {error && } {!loading && rows.length === 0 && (
- No work orders are in progress right now. + {t('page.shopFloor.noWorkOrders')}
)}
@@ -80,15 +82,15 @@ export function ShopFloorPage() { {r.workOrderCode} - {r.operationsCompleted} / {r.operationsTotal} ops + {r.operationsCompleted} / {r.operationsTotal} {t('page.shopFloor.ops')}
- Output: {r.outputItemCode} ×{' '} + {t('label.output')}: {r.outputItemCode} {'\u00D7'}{' '} {String(r.outputQuantity)}
-
Current operation
+
{t('page.shopFloor.currentOp')}
{r.currentOperationCode ? (
{r.currentOperationCode} @@ -96,13 +98,13 @@ export function ShopFloorPage() { {r.currentOperationStatus && }
) : ( -
No routing
+
{t('page.shopFloor.noRouting')}
)}
- {act.toFixed(0)} actual min - {std.toFixed(0)} std min + {act.toFixed(0)} {t('page.shopFloor.actualMin')} + {std.toFixed(0)} {t('page.shopFloor.stdMin')}
-
Task ID
+
{t('label.taskId')}
{task.taskId}
-
Process
+
{t('label.process')}
{task.processDefinitionKey}
-
Created
+
{t('label.created')}
{task.createTime}
-
Assignee
+
{t('label.assignee')}
{task.assignee ?? '\u2014'}
-
Form Key
+
{t('label.formKey')}
{task.formKey ?? '\u2014'}
@@ -100,7 +100,7 @@ export function TaskDetailPage() { ) : (
-

Variables

+

{t('label.variables')}

                 {JSON.stringify(task.variables, null, 2)}
               
diff --git a/web/src/pages/UomsPage.tsx b/web/src/pages/UomsPage.tsx index 0d189cf..37baa94 100644 --- a/web/src/pages/UomsPage.tsx +++ b/web/src/pages/UomsPage.tsx @@ -5,8 +5,10 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' export function UomsPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -20,16 +22,16 @@ export function UomsPage() { }, []) const columns: Column[] = [ - { header: 'Code', key: 'code', render: (r) => {r.code} }, - { header: 'Name', key: 'name' }, - { header: 'Dimension', key: 'dimension' }, + { header: t('label.code'), key: 'code', render: (r) => {r.code} }, + { header: t('label.name'), key: 'name' }, + { header: t('label.dimension'), key: 'dimension' }, ] return (
{loading && } {error && } diff --git a/web/src/pages/UserDetailPage.tsx b/web/src/pages/UserDetailPage.tsx index 677ae9e..b2bb644 100644 --- a/web/src/pages/UserDetailPage.tsx +++ b/web/src/pages/UserDetailPage.tsx @@ -11,10 +11,12 @@ import type { Role, User } from '@/types/api' import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' +import { useT } from '@/i18n/LocaleContext' export function UserDetailPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() + const t = useT() const [user, setUser] = useState(null) const [allRoles, setAllRoles] = useState([]) const [userRoleCodes, setUserRoleCodes] = useState([]) @@ -64,10 +66,10 @@ export function UserDetailPage() {
navigate('/users')}> - ← Back + {t('action.back')} } /> @@ -75,12 +77,12 @@ export function UserDetailPage() { {error && }
-

Roles

+

{t('page.userDetail.roles')}

- Toggle roles on/off. Changes take effect on the user's next login. + {t('page.userDetail.rolesHint')}

{allRoles.length === 0 && ( -

No roles defined yet. Create one on the Roles page.

+

{t('page.userDetail.noRoles')}

)}
{allRoles.map((role) => { @@ -99,7 +101,7 @@ export function UserDetailPage() { disabled={acting} onClick={() => toggle(role.code, has)} > - {has ? 'Revoke' : 'Assign'} + {has ? t('action.revoke') : t('action.assign')}
) diff --git a/web/src/pages/UserTasksPage.tsx b/web/src/pages/UserTasksPage.tsx index bd13484..f44a647 100644 --- a/web/src/pages/UserTasksPage.tsx +++ b/web/src/pages/UserTasksPage.tsx @@ -31,7 +31,7 @@ export function UserTasksPage() { const columns: Column[] = [ { - header: 'Task Name', + header: t('label.taskName'), key: 'taskName', render: (r) => (
diff --git a/web/src/pages/UsersPage.tsx b/web/src/pages/UsersPage.tsx index 3929653..bd12527 100644 --- a/web/src/pages/UsersPage.tsx +++ b/web/src/pages/UsersPage.tsx @@ -6,8 +6,10 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { DataTable, type Column } from '@/components/DataTable' +import { useT } from '@/i18n/LocaleContext' export function UsersPage() { + const t = useT() const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -22,7 +24,7 @@ export function UsersPage() { const columns: Column[] = [ { - header: 'Username', + header: t('label.username'), key: 'username', render: (r) => ( @@ -30,16 +32,16 @@ export function UsersPage() { ), }, - { header: 'Display name', key: 'displayName' }, - { header: 'Email', key: 'email', render: (r) => r.email ?? '—' }, + { header: t('label.displayName'), key: 'displayName' }, + { header: t('label.email'), key: 'email', render: (r) => r.email ?? '\u2014' }, { - header: 'Enabled', + header: t('label.enabled'), key: 'enabled', render: (r) => r.enabled ? ( - Active + {t('label.activeStatus')} ) : ( - Disabled + {t('label.disabled')} ), }, ] @@ -47,9 +49,9 @@ export function UsersPage() { return (
+ New User} + title={t('page.users.title')} + subtitle={t('page.users.subtitle')} + actions={{t('action.newUser')}} /> {loading && } {error && } diff --git a/web/src/pages/WorkOrderDetailPage.tsx b/web/src/pages/WorkOrderDetailPage.tsx index 7df50e7..b54cbac 100644 --- a/web/src/pages/WorkOrderDetailPage.tsx +++ b/web/src/pages/WorkOrderDetailPage.tsx @@ -1,4 +1,4 @@ -// Work-order detail screen — read-only header + start/complete +// Work-order detail screen -- read-only header + start/complete // action verbs that drive the v2 state machine. The shop-floor // dashboard at /shop-floor handles the per-operation walk for v3 // routing-equipped orders. v1 SPA keeps this screen simple: start a @@ -12,10 +12,12 @@ import { PageHeader } from '@/components/PageHeader' import { Loading } from '@/components/Loading' import { ErrorBox } from '@/components/ErrorBox' import { StatusBadge } from '@/components/StatusBadge' +import { useT } from '@/i18n/LocaleContext' export function WorkOrderDetailPage() { const { id = '' } = useParams<{ id: string }>() const navigate = useNavigate() + const t = useT() const [order, setOrder] = useState(null) const [locations, setLocations] = useState([]) const [outputLocation, setOutputLocation] = useState('') @@ -60,7 +62,7 @@ export function WorkOrderDetailPage() { try { await production.startWorkOrder(order.id) await refresh() - setActionMessage('Started. Operations can now be walked from the Shop Floor screen.') + setActionMessage(t('page.workOrderDetail.startMsg')) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) } finally { @@ -70,7 +72,7 @@ export function WorkOrderDetailPage() { const onComplete = async () => { if (!outputLocation) { - setError(new Error('Pick an output location first.')) + setError(new Error(t('page.workOrderDetail.pickLocation'))) return } setActing(true) @@ -80,7 +82,7 @@ export function WorkOrderDetailPage() { await production.completeWorkOrder(order.id, outputLocation) await refresh() setActionMessage( - `Completed. Materials issued, finished goods credited to ${outputLocation}.`, + t('page.workOrderDetail.completeMsg').replace('{location}', outputLocation), ) } catch (e: unknown) { setError(e instanceof Error ? e : new Error(String(e))) @@ -95,20 +97,20 @@ export function WorkOrderDetailPage() { return (
navigate('/work-orders')}> - ← Back + {t('action.back')} } />
- Status: + {t('label.status')}:
{actionMessage && ( @@ -119,10 +121,10 @@ export function WorkOrderDetailPage() {
-

Actions

+

{t('label.actions')}