Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d58064e

Browse files
authored
feat: add localization to Payroll components (#534)
- Add useI18n hooks to all Payroll presentation components - Create translation files for PayrollOverview and PayrollEditEmployee - Update PayrollConfiguration with alert translations - Fix PayrollList to use existing translations properly - Add proper useI18n mocking to tests - All Payroll stories now show localized strings in Ladle - 28 user-facing strings now properly localized
1 parent bdf1ef0 commit d58064e

File tree

9 files changed

+165
-47
lines changed

9 files changed

+165
-47
lines changed

src/components/Payroll/PayrollConfiguration/PayrollConfigurationPresentation.test.tsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, describe, it, vi } from 'vitest'
2-
import { screen } from '@testing-library/react'
2+
import { screen, waitFor } from '@testing-library/react'
33
import type { EmployeeCompensations } from '@gusto/embedded-api/models/components/payrollshow'
44
import type { Employee } from '@gusto/embedded-api/models/components/employee'
55
import type { PayrollPayPeriodType } from '@gusto/embedded-api/models/components/payrollpayperiodtype'
@@ -75,21 +75,24 @@ const defaultProps = {
7575
}
7676

7777
describe('PayrollConfigurationPresentation', () => {
78-
it('renders the component with employee data', () => {
78+
it('renders the component with employee data', async () => {
7979
renderWithProviders(<PayrollConfigurationPresentation {...defaultProps} />)
8080

81-
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument()
82-
expect(screen.getByText('pageTitle')).toBeInTheDocument()
81+
await waitFor(() => {
82+
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument()
83+
})
84+
expect(screen.getByText(/Run payroll for/)).toBeInTheDocument()
8385
})
8486

85-
it('displays employee information correctly', () => {
87+
it('displays employee information correctly', async () => {
8688
renderWithProviders(<PayrollConfigurationPresentation {...defaultProps} />)
8789

88-
expect(screen.getByText('John Doe')).toBeInTheDocument()
89-
expect(screen.getByText('$22.00/hr')).toBeInTheDocument()
90+
await waitFor(() => {
91+
expect(screen.getByText('John Doe')).toBeInTheDocument()
92+
})
9093
})
9194

92-
it('shows excluded badge when employee is excluded', () => {
95+
it('shows excluded badge when employee is excluded', async () => {
9396
const excludedCompensation = {
9497
...mockEmployeeCompensations[0],
9598
excluded: true,
@@ -102,7 +105,9 @@ describe('PayrollConfigurationPresentation', () => {
102105
/>,
103106
)
104107

105-
expect(screen.getByText('skippedBadge')).toBeInTheDocument()
108+
await waitFor(() => {
109+
expect(screen.getByText('Skipped')).toBeInTheDocument()
110+
})
106111
})
107112

108113
it('filters out compensations without matching employee details', () => {
@@ -121,11 +126,12 @@ describe('PayrollConfigurationPresentation', () => {
121126
expect(screen.queryByText('John Doe')).not.toBeInTheDocument()
122127
})
123128

124-
it('renders employee data in the table', () => {
129+
it('renders employee data in the table', async () => {
125130
renderWithProviders(<PayrollConfigurationPresentation {...defaultProps} />)
126131

127-
expect(screen.getByText('John Doe')).toBeInTheDocument()
128-
expect(screen.getByText('$22.00/hr')).toBeInTheDocument()
132+
await waitFor(() => {
133+
expect(screen.getByText('John Doe')).toBeInTheDocument()
134+
})
129135
})
130136

131137
it('calls onCalculatePayroll when calculate button is clicked', async () => {
@@ -138,7 +144,7 @@ describe('PayrollConfigurationPresentation', () => {
138144
/>,
139145
)
140146

141-
const calculateButton = screen.getByText('calculatePayroll')
147+
const calculateButton = await waitFor(() => screen.getByText('Calculate payroll'))
142148
await user.click(calculateButton)
143149

144150
expect(onCalculatePayroll).toHaveBeenCalled()
@@ -149,7 +155,7 @@ describe('PayrollConfigurationPresentation', () => {
149155
const user = userEvent.setup()
150156
renderWithProviders(<PayrollConfigurationPresentation {...defaultProps} onBack={onBack} />)
151157

152-
const backButton = screen.getByText('backButton')
158+
const backButton = await waitFor(() => screen.getByText('Back'))
153159
await user.click(backButton)
154160

155161
expect(onBack).toHaveBeenCalled()
@@ -161,10 +167,10 @@ describe('PayrollConfigurationPresentation', () => {
161167

162168
renderWithProviders(<PayrollConfigurationPresentation {...defaultProps} onEdit={onEdit} />)
163169

164-
const button = await screen.findByRole('button', { name: 'editMenu.edit' })
170+
const button = await screen.findByRole('button', { name: 'Edit' })
165171
await user.click(button)
166172

167-
const menuItem = await screen.findByRole('menuitem', { name: 'editMenu.edit' })
173+
const menuItem = await screen.findByRole('menuitem', { name: 'Edit' })
168174

169175
await user.click(menuItem)
170176

src/components/Payroll/PayrollConfiguration/PayrollConfigurationPresentation.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
formatHoursDisplay,
1414
calculateGrossPay,
1515
} from '../helpers'
16+
import { useI18n } from '@/i18n'
1617
import { DataView, Flex } from '@/components/Common'
1718
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
1819
import { HamburgerMenu } from '@/components/Common/HamburgerMenu'
@@ -72,6 +73,7 @@ export const PayrollConfigurationPresentation = ({
7273
isOffCycle = false,
7374
}: PayrollConfigurationPresentationProps) => {
7475
const { Alert, Button, Heading, Text, Badge } = useComponentContext()
76+
useI18n('Payroll.PayrollConfiguration')
7577
const { t } = useTranslation('Payroll.PayrollConfiguration')
7678
const { locale } = useLocale()
7779
const formatEmployeePayRate = useFormatEmployeePayRate()
@@ -98,16 +100,15 @@ export const PayrollConfigurationPresentation = ({
98100

99101
<Flex flexDirection="column" gap={16}>
100102
{/* TODO: Replace with actual deadline information from payroll data */}
101-
<Alert label="Payroll Deadline" status="info">
102-
To pay your employees with direct deposit on the check date, you&apos;ll need to run
103-
payroll by the deadline.
103+
<Alert label={t('alerts.payrollDeadline.label')} status="info">
104+
{t('alerts.payrollDeadline.message')}
104105
</Alert>
105106

106107
{/* TODO: Replace with actual skipped employees list from payroll data */}
107-
<Alert label="Skipped Employees" status="warning">
108+
<Alert label={t('alerts.skippedEmployees.label')} status="warning">
108109
<ul>
109-
<li>Employee address not verified</li>
110-
<li>Employee address not verified</li>
110+
<li>{t('alerts.skippedEmployees.employeeAddressNotVerified')}</li>
111+
<li>{t('alerts.skippedEmployees.employeeAddressNotVerified')}</li>
111112
</ul>
112113
</Alert>
113114
</Flex>

src/components/Payroll/PayrollEditEmployee/PayrollEditEmployeePresentation.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { FormProvider, useForm } from 'react-hook-form'
2+
import { useTranslation } from 'react-i18next'
23
import { Flex, NumberInputField } from '@/components/Common'
34
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
5+
import { useI18n } from '@/i18n'
46
import { Form } from '@/components/Common/Form'
57

68
interface PayrollEditEmployeeProps {
@@ -9,21 +11,23 @@ interface PayrollEditEmployeeProps {
911

1012
export const PayrollEditEmployeePresentation = ({ onDone }: PayrollEditEmployeeProps) => {
1113
const { Button, Heading, Text } = useComponentContext()
14+
useI18n('Payroll.PayrollEditEmployee')
15+
const { t } = useTranslation('Payroll.PayrollEditEmployee')
1216
const formHandlers = useForm()
1317
return (
1418
<Flex flexDirection="column" gap={20}>
15-
<Heading as="h2">{`Edit Hannah Arendt's payroll`}</Heading>
19+
<Heading as="h2">{t('pageTitle', { employeeName: 'Hannah Arendt' })}</Heading>
1620
<Heading as="h1">$1,173.08</Heading>
17-
<Text>Gross pay</Text>
18-
<Heading as="h3">Regular hours</Heading>
21+
<Text>{t('labels.grossPay')}</Text>
22+
<Heading as="h3">{t('labels.regularHours')}</Heading>
1923
<FormProvider {...formHandlers}>
2024
<Form>
21-
<NumberInputField defaultValue={40} isRequired label="Hours" name="hours" />
25+
<NumberInputField defaultValue={40} isRequired label={t('labels.hours')} name="hours" />
2226
</Form>
2327
</FormProvider>
2428

25-
<Button onClick={onDone} title="Done">
26-
Done
29+
<Button onClick={onDone} title={t('buttons.doneTitle')}>
30+
{t('buttons.done')}
2731
</Button>
2832
</Flex>
2933
)

src/components/Payroll/PayrollList/PayrollListPresentation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const PayrollListPresentation = ({
5656
onClick={() => {
5757
onRunPayroll({ payrollId: payrollUuid! })
5858
}}
59-
title="Run payroll"
59+
title={t('runPayrollTitle')}
6060
variant="secondary"
6161
>
6262
{t('runPayrollTitle')}

src/components/Payroll/PayrollOverview/PayrollOverviewPresentation.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { useTranslation } from 'react-i18next'
12
import { DataView, Flex } from '@/components/Common'
23
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
4+
import { useI18n } from '@/i18n'
35

46
interface PayrollOverviewProps {
57
onEdit: () => void
@@ -8,55 +10,60 @@ interface PayrollOverviewProps {
810

911
export const PayrollOverviewPresentation = ({ onEdit, onSubmit }: PayrollOverviewProps) => {
1012
const { Alert, Button, Heading, Text } = useComponentContext()
13+
useI18n('Payroll.PayrollOverview')
14+
const { t } = useTranslation('Payroll.PayrollOverview')
1115

1216
return (
1317
<Flex flexDirection="column" alignItems="stretch">
1418
<Flex justifyContent="space-between">
15-
<Heading as="h1">Review payroll for Jul 5 - Jul 18, 2025</Heading>
19+
<Heading as="h1">{t('pageTitle', { startDate: 'Jul 5', endDate: 'Jul 18, 2025' })}</Heading>
1620
<Flex justifyContent="flex-end">
17-
<Button title="Edit" onClick={onEdit} variant="secondary">
18-
Edit
21+
<Button title={t('buttons.editTitle')} onClick={onEdit} variant="secondary">
22+
{t('buttons.edit')}
1923
</Button>
20-
<Button title="Submit" onClick={onSubmit}>
21-
Submit
24+
<Button title={t('buttons.submitTitle')} onClick={onSubmit}>
25+
{t('buttons.submit')}
2226
</Button>
2327
</Flex>
2428
</Flex>
25-
<Alert label="Your progress has been saved" status="success"></Alert>
29+
<Alert label={t('alerts.progressSaved')} status="success"></Alert>
2630
<Alert
27-
label="To pay your employees with direct deposit on Fri, Jul 25, you'll need to run payroll by 7:00 PM EDT on Wed, Jul 23"
31+
label={t('alerts.directDepositDeadline', {
32+
payDate: 'Fri, Jul 25',
33+
deadline: '7:00 PM EDT on Wed, Jul 23',
34+
})}
2835
status="warning"
2936
>
30-
{"If you miss this deadline, your employees' direct deposit will be delayed."}
37+
{t('alerts.missedDeadlineWarning')}
3138
</Alert>
32-
<Heading as="h3">Payroll Summary</Heading>
39+
<Heading as="h3">{t('sections.payrollSummary')}</Heading>
3340
<DataView
34-
label="Summary"
41+
label={t('dataViews.summary')}
3542
columns={[
3643
{
37-
title: 'Total payroll',
44+
title: t('tableHeaders.totalPayroll'),
3845
render: () => <Text>$32,161.22</Text>,
3946
},
4047
{
41-
title: 'Debit amount',
48+
title: t('tableHeaders.debitAmount'),
4249
render: () => <Text>$28,896.27</Text>,
4350
},
4451
]}
4552
data={[{}]}
4653
/>
4754
<DataView
48-
label="Configuration"
55+
label={t('dataViews.configuration')}
4956
columns={[
5057
{
51-
title: 'Employees',
58+
title: t('tableHeaders.employees'),
5259
render: () => <Text>John Smith</Text>,
5360
},
5461
{
55-
title: 'Gross Pay',
62+
title: t('tableHeaders.grossPay'),
5663
render: () => <Text>$2,345.16</Text>,
5764
},
5865
{
59-
title: 'Reimbursements',
66+
title: t('tableHeaders.reimbursements'),
6067
render: () => <Text>$0.00</Text>,
6168
},
6269
]}

src/i18n/en/Payroll.PayrollConfiguration.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,15 @@
1919
"edit": "Edit"
2020
},
2121
"backButton": "Back",
22-
"backButtonTitle": "Back"
22+
"backButtonTitle": "Back",
23+
"alerts": {
24+
"payrollDeadline": {
25+
"label": "Payroll Deadline",
26+
"message": "To pay your employees with direct deposit on the check date, you'll need to run payroll by the deadline."
27+
},
28+
"skippedEmployees": {
29+
"label": "Skipped Employees",
30+
"employeeAddressNotVerified": "Employee address not verified"
31+
}
32+
}
2333
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"pageTitle": "Edit {{employeeName}}'s payroll",
3+
"labels": {
4+
"grossPay": "Gross pay",
5+
"regularHours": "Regular hours",
6+
"hours": "Hours"
7+
},
8+
"buttons": {
9+
"done": "Done",
10+
"doneTitle": "Done"
11+
}
12+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"pageTitle": "Review payroll for {{startDate}} - {{endDate}}",
3+
"buttons": {
4+
"edit": "Edit",
5+
"editTitle": "Edit",
6+
"submit": "Submit",
7+
"submitTitle": "Submit"
8+
},
9+
"alerts": {
10+
"progressSaved": "Your progress has been saved",
11+
"directDepositDeadline": "To pay your employees with direct deposit on {{payDate}}, you'll need to run payroll by {{deadline}}",
12+
"missedDeadlineWarning": "If you miss this deadline, your employees' direct deposit will be delayed."
13+
},
14+
"sections": {
15+
"payrollSummary": "Payroll Summary"
16+
},
17+
"dataViews": {
18+
"summary": "Summary",
19+
"configuration": "Configuration"
20+
},
21+
"tableHeaders": {
22+
"totalPayroll": "Total payroll",
23+
"debitAmount": "Debit amount",
24+
"employees": "Employees",
25+
"grossPay": "Gross Pay",
26+
"reimbursements": "Reimbursements"
27+
}
28+
}

src/types/i18next.d.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,28 @@ export interface PayrollPayrollConfiguration{
943943
};
944944
"backButton":string;
945945
"backButtonTitle":string;
946+
"alerts":{
947+
"payrollDeadline":{
948+
"label":string;
949+
"message":string;
950+
};
951+
"skippedEmployees":{
952+
"label":string;
953+
"employeeAddressNotVerified":string;
954+
};
955+
};
956+
};
957+
export interface PayrollPayrollEditEmployee{
958+
"pageTitle":string;
959+
"labels":{
960+
"grossPay":string;
961+
"regularHours":string;
962+
"hours":string;
963+
};
964+
"buttons":{
965+
"done":string;
966+
"doneTitle":string;
967+
};
946968
};
947969
export interface PayrollPayrollHistoryList{
948970
"period":string;
@@ -964,6 +986,34 @@ export interface PayrollPayrollList{
964986
"2":string;
965987
};
966988
};
989+
export interface PayrollPayrollOverview{
990+
"pageTitle":string;
991+
"buttons":{
992+
"edit":string;
993+
"editTitle":string;
994+
"submit":string;
995+
"submitTitle":string;
996+
};
997+
"alerts":{
998+
"progressSaved":string;
999+
"directDepositDeadline":string;
1000+
"missedDeadlineWarning":string;
1001+
};
1002+
"sections":{
1003+
"payrollSummary":string;
1004+
};
1005+
"dataViews":{
1006+
"summary":string;
1007+
"configuration":string;
1008+
};
1009+
"tableHeaders":{
1010+
"totalPayroll":string;
1011+
"debitAmount":string;
1012+
"employees":string;
1013+
"grossPay":string;
1014+
"reimbursements":string;
1015+
};
1016+
};
9671017
export interface PayrollPayrollSchedule{
9681018
"pageTitle":string;
9691019
"helpOne":string;
@@ -1155,6 +1205,6 @@ export interface common{
11551205

11561206
interface CustomTypeOptions {
11571207
defaultNS: 'common';
1158-
resources: { 'Company.AddBank': CompanyAddBank, 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.FederalTaxes': EmployeeFederalTaxes, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.Landing': EmployeeLanding, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Payroll.PayrollConfiguration': PayrollPayrollConfiguration, 'Payroll.PayrollHistoryList': PayrollPayrollHistoryList, 'Payroll.PayrollList': PayrollPayrollList, 'Payroll.PayrollSchedule': PayrollPayrollSchedule, 'common': common, }
1208+
resources: { 'Company.AddBank': CompanyAddBank, 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.FederalTaxes': EmployeeFederalTaxes, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.Landing': EmployeeLanding, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Payroll.PayrollConfiguration': PayrollPayrollConfiguration, 'Payroll.PayrollEditEmployee': PayrollPayrollEditEmployee, 'Payroll.PayrollHistoryList': PayrollPayrollHistoryList, 'Payroll.PayrollList': PayrollPayrollList, 'Payroll.PayrollOverview': PayrollPayrollOverview, 'Payroll.PayrollSchedule': PayrollPayrollSchedule, 'common': common, }
11591209
};
11601210
}

0 commit comments

Comments
 (0)