Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@
"i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"i18next-icu": "^2.4.1",
"input-otp": "^1.2.3",
"intl-messageformat": "^10.7.18",
"lucide-react": "^0.544.0",
"nanoid": "^5.0.6",
"next": "15.4.7",
Expand Down
66 changes: 66 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 24 additions & 3 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
"request_error": "Error requesting notification",
"saving_expense": "Error while saving expense",
"setting_update_failed": "Failed to update setting",
"signin_error": "An error occurred while signing in: ",
"signin_error": "An error occurred while signing in: {{error}}",
"signup_disabled": "Signup of new accounts is disabled on this instance",
"something_went_wrong": "Something went wrong",
"subscribe_error": "Cannot subscribe to notification",
Expand Down Expand Up @@ -361,7 +361,6 @@
"lent": "lent",
"owe": "owes",
"paid": "paid",
"pay": "pays",
"received": "received"
},
"you": {
Expand All @@ -372,6 +371,27 @@
"paid": "paid",
"pay": "pay",
"received": "received"
},
"statements": {
"you_lent": "You lent",
"you_owe": "You owe",
"you_lent_amount": "You lent {{currency}} {{amount}}",
"you_owe_amount": "You owe {{currency}} {{amount}}",
"you_paid_amount": "You paid {{currency}} {{amount}}",
"you_received_amount": "You received {{currency}} {{amount}}",
"you_pay_user": "You pay {{user}}",
"user_pays_you": "{{user}} pays you",
"friend_owes_you": "{{friend}} owes {{youDative}}",
"you_owe_friend": "You owe {{friend}}",
"paid_for_beneficiary": "paid for {{beneficiary}}",
"user_paid_amount": "{{user}} paid {{currency}} {{amount}}",
"user_received_amount": "{{user}} received {{currency}} {{amount}}",
"user_paid_amount_to_user": "{{payer}} paid {{currency}} {{amount}} to {{receiver}}",
"user_received_amount_from_user": "{{payer}} received {{currency}} {{amount}} from {{receiver}}",
"you_paid_amount_to_user": "You paid {{currency}} {{amount}} to {{receiver}}",
"you_received_amount_from_user": "You received {{currency}} {{amount}} from {{payer}}",
"for_users": "for {{payer}} and {{receiver}}",
"user_pays_user": "{{payer}} pays {{receiver}}"
}
},
"expense_details": "Expense details",
Expand All @@ -386,6 +406,7 @@
"settlement": "Settlement",
"share_text": "Check out SplitPro. It's an open source free alternative for Splitwise",
"today": "Today",
"in_group": "In group"
"in_group": "In group",
"remaining_balances": "+{{count}} {{count, plural, one {balance} other {balances}}}..."
}
}
27 changes: 24 additions & 3 deletions public/locales/it/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"request_error": "Errore durante la richiesta delle notifiche",
"saving_expense": "Errore durante il salvataggio della spesa",
"setting_update_failed": "Errore nell'aggiornamento dell'impostazione",
"signin_error": "Si è verificato un errore durante l'accesso: ",
"signin_error": "Si è verificato un errore durante l'accesso: {{error}}",
"signup_disabled": "La registrazione di nuovi account è disabilitata in questa istanza",
"something_went_wrong": "Qualcosa è andato storto",
"subscribe_error": "Impossibile attivare le notifiche",
Expand Down Expand Up @@ -308,7 +308,6 @@
"lent": "ha prestato",
"owe": "deve",
"paid": "ha pagato",
"pay": "paga",
"received": "ha ricevuto"
},
"you": {
Expand All @@ -319,6 +318,27 @@
"paid": "hai pagato",
"pay": "paga",
"received": "hai ricevuto"
},
"statements": {
"you_lent": "Hai prestato",
"you_owe": "Devi",
"you_lent_amount": "Hai prestato {{currency}} {{amount}}",
"you_owe_amount": "Devi {{currency}} {{amount}}",
"you_paid_amount": "Hai pagato {{currency}} {{amount}}",
"you_received_amount": "Hai ricevuto {{currency}} {{amount}}",
"you_pay_user": "Paghi {{user}}",
"user_pays_you": "{{user}} ti paga",
"friend_owes_you": "{{friend}} ti deve",
"you_owe_friend": "Devi a {{friend}}",
"paid_for_beneficiary": "pagato per {{beneficiary}}",
"user_paid_amount": "{{user}} ha pagato {{currency}} {{amount}}",
"user_received_amount": "{{user}} ha ricevuto {{currency}} {{amount}}",
"user_paid_amount_to_user": "{{payer}} ha pagato {{currency}} {{amount}} a {{receiver}}",
"user_received_amount_from_user": "{{payer}} ha ricevuto {{currency}} {{amount}} da {{receiver}}",
"you_paid_amount_to_user": "Hai pagato {{currency}} {{amount}} a {{receiver}}",
"you_received_amount_from_user": "Hai ricevuto {{currency}} {{amount}} da {{payer}}",
"for_users": "per {{payer}} e {{receiver}}",
"user_pays_user": "{{payer}} paga {{receiver}}"
}
},
"expense_details": "Dettagli spesa",
Expand All @@ -332,6 +352,7 @@
"settled_up": "Regolato",
"settlement": "Regolamento",
"share_text": "Dai un'occhiata a SplitPro. È un'alternativa gratuita e open source a Splitwise",
"today": "Oggi"
"today": "Oggi",
"remaining_balances": "+{{count}} {{count, plural, one {saldo} other {saldi}}}..."
}
}
2 changes: 1 addition & 1 deletion src/components/Expense/BalanceEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const BalanceEntry: React.FC<{
isPositive ? 'text-emerald-500' : 'text-orange-600',
)}
>
{t('actors.you')} {t(`ui.expense.you.${isPositive ? 'lent' : 'owe'}`)}
{t(`ui.expense.statements.${isPositive ? 'you_lent' : 'you_owe'}`)}
</div>
<div className={`${isPositive ? 'text-emerald-500' : 'text-orange-600'} text-right`}>
{toUIString(amount)}
Expand Down
46 changes: 33 additions & 13 deletions src/components/Expense/ExpenseList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import Image from 'next/image';
import Link from 'next/link';
import React from 'react';
import {
CURRENCY_CONVERSION_ICON,
CategoryIcon,
SETTLEUP_ICON,
CURRENCY_CONVERSION_ICON as CurrencyConversionIcon,
SETTLEUP_ICON as SettleUpIcon,
} from '~/components/ui/categoryIcons';
import type { ExpenseRouter } from '~/server/api/routers/expense';
import { useTranslationWithUtils } from '~/hooks/useTranslationWithUtils';
Expand Down Expand Up @@ -70,6 +70,10 @@ const Expense: ExpenseComponent = ({ e, userId }) => {
const yourExpenseAmount = youPaid
? (theirExpense?.amount ?? yourExpense?.amount ?? 0n)
: -(yourExpense?.amount ?? 0n);
const isYouPayer = e.paidBy === userId;
const amountStatementKey = `ui.expense.statements.${isYouPayer ? 'you' : 'user'}_${
e.amount < 0n ? 'received_amount' : 'paid_amount'
}`;

const { toUIString } = getCurrencyHelpersCached(e.currency);

Expand All @@ -83,8 +87,11 @@ const Expense: ExpenseComponent = ({ e, userId }) => {
<div>
<p className="max-w-[180px] truncate text-sm lg:max-w-md lg:text-base">{e.name}</p>
<p className="flex text-center text-xs text-gray-500">
{displayName(e.paidByUser, userId)}{' '}
{t(`ui.expense.user.${e.amount < 0n ? 'received' : 'paid'}`)} {toUIString(e.amount)}
{t(amountStatementKey, {
user: displayName(e.paidByUser, userId),
currency: e.currency,
amount: toUIString(e.amount),
})}
</p>
</div>
</div>
Expand All @@ -94,7 +101,7 @@ const Expense: ExpenseComponent = ({ e, userId }) => {
<div
className={`text-right text-xs ${youPaid ? 'text-emerald-500' : 'text-orange-600'}`}
>
{t('actors.you')} {t(`ui.expense.you.${youPaid ? 'lent' : 'owe'}`)}
{t(`ui.expense.statements.${youPaid ? 'you_lent' : 'you_owe'}`)}
</div>
<div className={`text-right ${youPaid ? 'text-emerald-500' : 'text-orange-600'}`}>
{toUIString(yourExpenseAmount)}
Expand All @@ -117,18 +124,29 @@ const Settlement: ExpenseComponent = ({ e, userId }) => {

const receiverId = e.expenseParticipants.find((p) => p.userId !== e.paidBy)?.userId;
const userDetails = api.user.getUserDetails.useQuery({ userId: receiverId! });

const isYouPayer = e.paidBy === userId;
const isYouReceiver = receiverId === userId;
const settlementStatementKey = `ui.expense.statements.${
isYouPayer ? 'you' : 'user'
}_${e.amount < 0n ? 'received_amount_from_user' : 'paid_amount_to_user'}`;
const receiverLabel = isYouReceiver
? t('actors.you_dativus').toLowerCase()
: displayName(userDetails.data, userId);
const payerLabel = displayName(e.paidByUser, userId);
return (
<div className="flex items-center gap-4">
<div className="inline-block max-w-min text-center text-xs text-gray-500">
{toUIDate(e.expenseDate)}
</div>
<SETTLEUP_ICON className="size-5 text-gray-400" />
<SettleUpIcon className="size-5 text-gray-400" />
<div>
<p className="flex text-center text-sm text-gray-400">
{displayName(e.paidByUser, userId)}{' '}
{t(`ui.expense.user.${e.amount < 0n ? 'received' : 'paid'}`)} {toUIString(e.amount)}{' '}
{t('ui.expense.to')} {displayName(userDetails.data, userId)}
{t(settlementStatementKey, {
payer: payerLabel,
receiver: receiverLabel,
currency: e.currency,
amount: toUIString(e.amount),
})}
</p>
</div>
</div>
Expand All @@ -146,7 +164,7 @@ const CurrencyConversion: ExpenseComponent = ({ e, userId }) => {
<div className="inline-block max-w-min text-center text-xs text-gray-500">
{toUIDate(e.expenseDate)}
</div>
<CURRENCY_CONVERSION_ICON className="size-5 text-gray-400" />
<CurrencyConversionIcon className="size-5 text-gray-400" />
<div>
<p className="max-w-[180px] truncate text-sm lg:max-w-md lg:text-base">
{getCurrencyHelpersCached(e.currency).toUIString(e.amount)} ➡️{' '}
Expand All @@ -156,8 +174,10 @@ const CurrencyConversion: ExpenseComponent = ({ e, userId }) => {
}
</p>
<p className="flex text-center text-xs text-gray-500">
{t('ui.expense.for')} {displayName(e.paidByUser, userId)} {t('ui.and')}{' '}
{displayName(userDetails.data, userId)}
{t('ui.expense.statements.for_users', {
payer: displayName(e.paidByUser, userId),
receiver: displayName(userDetails.data, userId),
})}
</p>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Friend/FriendBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const FriendBalance: React.FC<{ user: User; balance: Balance }> = ({ user
<div
className={clsx('text-right text-xs', isPositive ? 'text-green-500' : 'text-orange-600')}
>
{t('actors.you')} {isPositive ? t('ui.expense.you.lent') : t('ui.expense.you.owe')}
{t(`ui.expense.statements.${isPositive ? 'you_lent' : 'you_owe'}`)}
</div>
<div className={`${isPositive ? 'text-green-500' : 'text-orange-600'} flex text-right`}>
{toUIString(balance.amount)}
Expand Down
21 changes: 14 additions & 7 deletions src/components/Friend/GroupSettleup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,19 @@ export const GroupSettleUp: React.FC<{
const addExpenseMutation = api.expense.addOrEditExpense.useMutation();
const utils = api.useUtils();

const sender = 0 > _amount ? user : friend;
const receiver = 0 > _amount ? friend : user;
const sender = 0 > amount ? user : friend;
const receiver = 0 > amount ? friend : user;
const payerRole = sender.id === user.id ? 'you' : 'other';
const receiverRole = receiver.id === user.id ? 'you' : 'other';
const paymentText =
payerRole === 'you'
? t('ui.expense.statements.you_pay_user', { user: displayName(receiver) })
: receiverRole === 'you'
? t('ui.expense.statements.user_pays_you', { user: displayName(sender) })
: t('ui.expense.statements.user_pays_user', {
payer: displayName(sender),
receiver: displayName(receiver),
});

const saveExpense = React.useCallback(() => {
if (!amount) {
Expand Down Expand Up @@ -98,11 +109,7 @@ export const GroupSettleUp: React.FC<{
<ArrowRightIcon className="h-6 w-6 text-gray-600" />
<EntityAvatar entity={receiver} />
</div>
<p className="mt-2 text-center text-sm text-gray-400">
{displayName(sender, data?.user.id)}{' '}
{t(`ui.expense.${sender.id === data?.user.id ? 'you' : 'user'}.pay`)}{' '}
{displayName(receiver, data?.user.id)}
</p>
<p className="mt-2 text-center text-sm text-gray-400">{paymentText}</p>
</div>
<CurrencyInput
currency={currency}
Expand Down
Loading