From 4f55ced837cab9b8c1ffbe2047433bc3b78d8d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Thu, 14 May 2026 11:36:53 +0100 Subject: [PATCH] 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 --- lang/es.json | 1 + .../categories/create-category-dialog.tsx | 8 +++--- .../categories/edit-category-dialog.tsx | 4 +-- .../transaction-delete-confirmation.test.ts | 25 +++++++++++++++++ .../js/lib/transaction-delete-confirmation.ts | 5 ++++ resources/js/pages/transactions/index.tsx | 12 ++++++--- resources/js/types/category.test.ts | 27 +++++++++++++++++++ resources/js/types/category.ts | 12 +++++++++ 8 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 resources/js/lib/transaction-delete-confirmation.test.ts create mode 100644 resources/js/lib/transaction-delete-confirmation.ts create mode 100644 resources/js/types/category.test.ts diff --git a/lang/es.json b/lang/es.json index 190d454e..ddd75008 100644 --- a/lang/es.json +++ b/lang/es.json @@ -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", diff --git a/resources/js/components/categories/create-category-dialog.tsx b/resources/js/components/categories/create-category-dialog.tsx index e845abed..3306540e 100644 --- a/resources/js/components/categories/create-category-dialog.tsx +++ b/resources/js/components/categories/create-category-dialog.tsx @@ -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({ {CATEGORY_TYPES.map((type) => ( - {type.charAt(0).toUpperCase() + - type.slice(1)} + {getCategoryTypeLabel(type)} ))} @@ -209,7 +209,9 @@ export function CreateCategoryDialog({ {__('Cancel')} diff --git a/resources/js/components/categories/edit-category-dialog.tsx b/resources/js/components/categories/edit-category-dialog.tsx index d38387bd..4411faf1 100644 --- a/resources/js/components/categories/edit-category-dialog.tsx +++ b/resources/js/components/categories/edit-category-dialog.tsx @@ -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({ {CATEGORY_TYPES.map((type) => ( - {type.charAt(0).toUpperCase() + - type.slice(1)} + {getCategoryTypeLabel(type)} ))} diff --git a/resources/js/lib/transaction-delete-confirmation.test.ts b/resources/js/lib/transaction-delete-confirmation.test.ts new file mode 100644 index 00000000..b9c68a0b --- /dev/null +++ b/resources/js/lib/transaction-delete-confirmation.test.ts @@ -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', + ); + }); +}); diff --git a/resources/js/lib/transaction-delete-confirmation.ts b/resources/js/lib/transaction-delete-confirmation.ts new file mode 100644 index 00000000..a1d7d799 --- /dev/null +++ b/resources/js/lib/transaction-delete-confirmation.ts @@ -0,0 +1,5 @@ +import { __ } from '@/utils/i18n'; + +export function getBulkDeleteConfirmationText(count: number): string { + return __('Delete :count Transactions', { count }); +} diff --git a/resources/js/pages/transactions/index.tsx b/resources/js/pages/transactions/index.tsx index 357a1231..0bee2d88 100644 --- a/resources/js/pages/transactions/index.tsx +++ b/resources/js/pages/transactions/index.tsx @@ -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', )}{' '} - delete {selectedCount} transactions + {bulkDeleteConfirmationText} @@ -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')} diff --git a/resources/js/types/category.test.ts b/resources/js/types/category.test.ts new file mode 100644 index 00000000..50a7682c --- /dev/null +++ b/resources/js/types/category.test.ts @@ -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'); + }); +}); diff --git a/resources/js/types/category.ts b/resources/js/types/category.ts index 1d7da62a..3e0eba93 100644 --- a/resources/js/types/category.ts +++ b/resources/js/types/category.ts @@ -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 = { + income: 'Income', + expense: 'Expense', + transfer: 'Transfer', +}; + +export function getCategoryTypeLabel(type: CategoryType): string { + return __(CATEGORY_TYPE_LABELS[type]); +} + export const CATEGORY_CASHFLOW_DIRECTIONS = [ 'hidden', 'inflow',