Fix Spanish translations in category and delete flows (#397)

## Summary
- translate category type dropdown labels
- translate bulk delete confirmation text
- add focused i18n tests

## Tests
- npm run test -- resources/js/types/category.test.ts
resources/js/lib/transaction-delete-confirmation.test.ts
- npx eslint resources/js/types/category.ts
resources/js/types/category.test.ts
resources/js/components/categories/create-category-dialog.tsx
resources/js/components/categories/edit-category-dialog.tsx
resources/js/lib/transaction-delete-confirmation.ts
resources/js/lib/transaction-delete-confirmation.test.ts
resources/js/pages/transactions/index.tsx
This commit is contained in:
Víctor Falcón 2026-05-14 11:36:53 +01:00 committed by GitHub
parent 81c0fd4a81
commit 4f55ced837
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 86 additions and 8 deletions

View File

@ -446,6 +446,7 @@
"Delete Label": "Eliminar Etiqueta",
"Delete Transaction": "Eliminar Transacción",
"Delete Transactions": "Eliminar Transacciones",
"Delete :count Transactions": "Borrar :count transacciones",
"Delete account": "Eliminar cuenta",
"Delete accounts": "Eliminar cuentas",
"Delete balance": "Eliminar balance",

View File

@ -26,6 +26,7 @@ import {
CATEGORY_ICONS,
CATEGORY_TYPES,
getCategoryColorClasses,
getCategoryTypeLabel,
} from '@/types/category';
import { __ } from '@/utils/i18n';
import { Form } from '@inertiajs/react';
@ -166,8 +167,7 @@ export function CreateCategoryDialog({
<SelectContent>
{CATEGORY_TYPES.map((type) => (
<SelectItem key={type} value={type}>
{type.charAt(0).toUpperCase() +
type.slice(1)}
{getCategoryTypeLabel(type)}
</SelectItem>
))}
</SelectContent>
@ -209,7 +209,9 @@ export function CreateCategoryDialog({
{__('Cancel')}
</Button>
<Button type="submit" disabled={processing}>
{processing ? 'Creating...' : 'Create'}
{processing
? __('Creating...')
: __('Create')}
</Button>
</div>
</>

View File

@ -24,6 +24,7 @@ import {
CATEGORY_ICONS,
CATEGORY_TYPES,
getCategoryColorClasses,
getCategoryTypeLabel,
type Category,
} from '@/types/category';
import { __ } from '@/utils/i18n';
@ -177,8 +178,7 @@ export function EditCategoryDialog({
<SelectContent>
{CATEGORY_TYPES.map((type) => (
<SelectItem key={type} value={type}>
{type.charAt(0).toUpperCase() +
type.slice(1)}
{getCategoryTypeLabel(type)}
</SelectItem>
))}
</SelectContent>

View File

@ -0,0 +1,25 @@
import { setTranslations } from '@/utils/i18n';
import { afterEach, describe, expect, it } from 'vitest';
import { getBulkDeleteConfirmationText } from './transaction-delete-confirmation';
describe('getBulkDeleteConfirmationText', () => {
afterEach(() => {
setTranslations({});
});
it('returns translated bulk delete confirmation text', () => {
setTranslations({
'Delete :count Transactions': 'Borrar :count transacciones',
});
expect(getBulkDeleteConfirmationText(12)).toBe(
'Borrar 12 transacciones',
);
});
it('falls back to English confirmation text', () => {
expect(getBulkDeleteConfirmationText(12)).toBe(
'Delete 12 Transactions',
);
});
});

View File

@ -0,0 +1,5 @@
import { __ } from '@/utils/i18n';
export function getBulkDeleteConfirmationText(count: number): string {
return __('Delete :count Transactions', { count });
}

View File

@ -79,6 +79,7 @@ import {
} from '@/lib/cursor-pagination';
import { consoleDebug } from '@/lib/debug';
import { captureEvent } from '@/lib/posthog';
import { getBulkDeleteConfirmationText } from '@/lib/transaction-delete-confirmation';
import { mergeReEvaluatedTransaction } from '@/lib/transaction-re-evaluation';
import { cn } from '@/lib/utils';
import { transactionSyncService } from '@/services/transaction-sync';
@ -941,6 +942,11 @@ export default function Transactions({
const selectedCount = useMemo(() => selectedIds.length, [selectedIds]);
const bulkDeleteConfirmationText = useMemo(
() => getBulkDeleteConfirmationText(selectedCount),
[selectedCount],
);
function handleBulkDeleteClick() {
if (selectedIds.length === 0) {
return;
@ -1298,7 +1304,7 @@ export default function Transactions({
'This action cannot be undone. To confirm, type',
)}{' '}
<span className="font-semibold text-foreground">
delete {selectedCount} transactions
{bulkDeleteConfirmationText}
</span>
</DialogDescription>
</DialogHeader>
@ -1307,7 +1313,7 @@ export default function Transactions({
onChange={(e) =>
setBulkDeleteConfirmation(e.target.value)
}
placeholder={`delete ${selectedCount} transactions`}
placeholder={bulkDeleteConfirmationText}
disabled={isBulkDeleting}
autoFocus
/>
@ -1329,7 +1335,7 @@ export default function Transactions({
disabled={
isBulkDeleting ||
bulkDeleteConfirmation !==
`delete ${selectedCount} transactions`
bulkDeleteConfirmationText
}
>
{isBulkDeleting ? __('Deleting...') : __('Delete')}

View File

@ -0,0 +1,27 @@
import { setTranslations } from '@/utils/i18n';
import { afterEach, describe, expect, it } from 'vitest';
import { getCategoryTypeLabel } from './category';
describe('getCategoryTypeLabel', () => {
afterEach(() => {
setTranslations({});
});
it('returns translated category type labels', () => {
setTranslations({
Income: 'Ingresos',
Expense: 'Gasto',
Transfer: 'Transferencia',
});
expect(getCategoryTypeLabel('income')).toBe('Ingresos');
expect(getCategoryTypeLabel('expense')).toBe('Gasto');
expect(getCategoryTypeLabel('transfer')).toBe('Transferencia');
});
it('falls back to English labels without translations', () => {
expect(getCategoryTypeLabel('income')).toBe('Income');
expect(getCategoryTypeLabel('expense')).toBe('Expense');
expect(getCategoryTypeLabel('transfer')).toBe('Transfer');
});
});

View File

@ -1,3 +1,5 @@
import { __ } from '@/utils/i18n';
import { UUID } from './uuid';
export const CATEGORY_ICONS = [
@ -107,6 +109,16 @@ export const CATEGORY_TYPES = ['income', 'expense', 'transfer'] as const;
export type CategoryType = (typeof CATEGORY_TYPES)[number];
const CATEGORY_TYPE_LABELS: Record<CategoryType, string> = {
income: 'Income',
expense: 'Expense',
transfer: 'Transfer',
};
export function getCategoryTypeLabel(type: CategoryType): string {
return __(CATEGORY_TYPE_LABELS[type]);
}
export const CATEGORY_CASHFLOW_DIRECTIONS = [
'hidden',
'inflow',