diff --git a/docs/OBSOLETE_TRANSLATION_KEYS.md b/docs/OBSOLETE_TRANSLATION_KEYS.md
new file mode 100644
index 000000000..dfae32df2
--- /dev/null
+++ b/docs/OBSOLETE_TRANSLATION_KEYS.md
@@ -0,0 +1,62 @@
+# Obsolete translation keys
+
+Remove in part 2 of the PR
+
+Mark these keys as obsolete in Weblate. Remove them from all catalogs after
+release; older app versions may still use them.
+
+Keep the current `common` and `home` namespaces.
+
+## Namespace `common`
+
+| Obsolete key | Replacement |
+| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `actors.you_accusativus` | Complete ICU messages |
+| `actors.you_dativus` | Complete ICU messages |
+| `expense_details.balance_list.are_settled_up` | `expense_details.balance_list.settled` |
+| `expense_details.balance_list.is_settled_up` | `expense_details.balance_list.settled` |
+| `ui.added_by` | `expense_metadata.added_by` |
+| `ui.deleted_by` | `expense_metadata.deleted_by` |
+| `ui.edited_by` | `expense_metadata.edited_by` |
+| `ui.balance` | `balance_summary.more` (`count`) |
+| `ui.balances` | `balance_summary.more` (`count`) |
+| `ui.expense.for` | `activity.*_for_expense`, `expense_list.conversion_for_users` |
+| `ui.expense.you.deleted` | `activity.user_deleted_expense` (`actor`) |
+| `ui.expense.user.deleted` | `activity.user_deleted_expense` (`actor`) |
+| `ui.expense.you.get` | `expense_participant.gets`, `balance_list.gets` |
+| `ui.expense.user.get` | `expense_participant.gets`, `balance_list.gets` |
+| `ui.expense.you.lent` | `expense_details.balance_list.lent`, `ui.expense.statements.you_lent*` |
+| `ui.expense.user.lent` | `expense_details.balance_list.lent` (`actor`) |
+| `ui.expense.you.owe` | `expense_details.balance_list.owes`, `expense_participant.owes`, `balance_summary.you_owe_friend`, `ui.expense.statements.you_owe*` |
+| `ui.expense.user.owe` | `expense_details.balance_list.owes`, `expense_participant.owes`, `balance_summary.friend_owes_you` |
+| `ui.expense.you.pay` | `balance_list.pays`, `ui.transfer`, `ui.expense.statements.you_pay_user` |
+| `ui.expense.user.pay` | `balance_list.pays`, `ui.transfer`, `ui.expense.statements.user_pays_you`, `ui.expense.statements.user_pays_user` |
+| `ui.expense.you.paid` | `expense_participant.paid` |
+| `ui.expense.user.paid` | `expense_participant.paid` |
+| `ui.expense.you.received` | `expense_participant.received` |
+| `ui.expense.user.received` | `expense_participant.received` |
+
+## Namespace `home`
+
+| Obsolete key | Replacement |
+| --------------------- | ----------------------------- |
+| `hero.title_part1` | `hero.title_before_highlight` |
+| `hero.subtitle_part1` | `hero.subtitle` |
+| `hero.subtitle_link` | `hero.subtitle` |
+| `hero.subtitle_part2` | `hero.subtitle` |
+
+## Keep
+
+All keys not listed above remain unchanged, including `actors.you`,
+`ui.expense.{paid_by,received_by,from,to}`,
+`ui.and`, `ui.on`, `account.messages.submit_*`, `errors.invalid_split`,
+`errors.request_error`, and `errors.subscribe_error`.
+
+## Translation notes
+
+- Restored keys use English fallback until translated.
+- Keep `errors.signin_error`; it now requires `{error}`.
+- Use `{name}` for interpolation. Nested ICU braces such as
+ `other {{person} owes}` are valid; do not replace them globally.
+- Participant messages must include `` and `` once per branch.
+ They render the existing clickable user and styled amount components.
diff --git a/next-i18next.config.js b/next-i18next.config.js
index b1c29aa5c..0a379061b 100644
--- a/next-i18next.config.js
+++ b/next-i18next.config.js
@@ -1,3 +1,12 @@
+// @ts-expect-error i18next-icu's CommonJS export has an invalid ambient module declaration.
+import ICUModule from 'i18next-icu/cjs';
+
+const ICU = ICUModule.default ?? ICUModule;
+
+/** @param {string} lng */
+const parseLngForICU = (lng) =>
+ 'default' === lng ? 'en' : 'ca@valencia' === lng ? 'ca-ES-valencia' : lng;
+
/** @type {import('next-i18next').UserConfig} */
const config = {
i18n: {
@@ -22,7 +31,16 @@ const config = {
],
localeDetection: false,
},
+ fallbackLng: 'en',
localePath: './public/locales',
+ i18nFormat: {
+ // Keep the existing route and Weblate locale codes while ICU uses valid BCP 47 tags.
+ parseLngForICU,
+ },
+ onPreInitI18next: (i18n) => {
+ i18n.use(ICU);
+ },
+ serializeConfig: false,
};
export default config;
diff --git a/package.json b/package.json
index 995ff0f52..ef4d4f0ac 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,9 @@
"i18next": "^25.10.10",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.6",
+ "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.5.18",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b86928770..54d0cbe49 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -97,9 +97,15 @@ importers:
i18next-http-backend:
specifier: ^3.0.6
version: 3.0.6
+ i18next-icu:
+ specifier: ^2.4.1
+ version: 2.4.4(intl-messageformat@10.7.18)
input-otp:
specifier: ^1.2.3
version: 1.4.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ intl-messageformat:
+ specifier: ^10.7.18
+ version: 10.7.18
lucide-react:
specifier: ^0.544.0
version: 0.544.0(react@19.2.1)
@@ -664,6 +670,21 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+ '@formatjs/ecma402-abstract@2.3.6':
+ resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==}
+
+ '@formatjs/fast-memoize@2.2.7':
+ resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
+
+ '@formatjs/icu-messageformat-parser@2.11.4':
+ resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==}
+
+ '@formatjs/icu-skeleton-parser@1.8.16':
+ resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==}
+
+ '@formatjs/intl-localematcher@0.6.2':
+ resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==}
+
'@heroicons/react@2.2.0':
resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==}
peerDependencies:
@@ -3026,6 +3047,11 @@ packages:
i18next-http-backend@3.0.6:
resolution: {integrity: sha512-mBOqy8993jtqAoj6XaI1XeC/8/9v6EPS+681ziegrPvTB0DoaCY7PpTS0SpY56qLMoS4OI1TZEM2Zf59zNh05w==}
+ i18next-icu@2.4.4:
+ resolution: {integrity: sha512-yfYdGTftClNUnZAQG1cu0yHHLaMVsrqfcgQHHchGDufkunBLKHuw2wXTCDuE+8mWMMcdePl8UBR0EUUA/3+FDw==}
+ peerDependencies:
+ intl-messageformat: '>=10.3.3 <12.0.0'
+
i18next@25.10.10:
resolution: {integrity: sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==}
peerDependencies:
@@ -3063,6 +3089,9 @@ packages:
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+ intl-messageformat@10.7.18:
+ resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
+
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
@@ -4779,6 +4808,32 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
+ '@formatjs/ecma402-abstract@2.3.6':
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.7
+ '@formatjs/intl-localematcher': 0.6.2
+ decimal.js: 10.5.0
+ tslib: 2.8.1
+
+ '@formatjs/fast-memoize@2.2.7':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/icu-messageformat-parser@2.11.4':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.6
+ '@formatjs/icu-skeleton-parser': 1.8.16
+ tslib: 2.8.1
+
+ '@formatjs/icu-skeleton-parser@1.8.16':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.6
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.6.2':
+ dependencies:
+ tslib: 2.8.1
+
'@heroicons/react@2.2.0(react@19.2.1)':
dependencies:
react: 19.2.1
@@ -7104,6 +7159,10 @@ snapshots:
transitivePeerDependencies:
- encoding
+ i18next-icu@2.4.4(intl-messageformat@10.7.18):
+ dependencies:
+ intl-messageformat: 10.7.18
+
i18next@25.10.10(typescript@5.7.3):
dependencies:
'@babel/runtime': 7.29.2
@@ -7135,6 +7194,13 @@ snapshots:
react: 19.2.1
react-dom: 19.2.1(react@19.2.1)
+ intl-messageformat@10.7.18:
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.6
+ '@formatjs/fast-memoize': 2.2.7
+ '@formatjs/icu-messageformat-parser': 2.11.4
+ tslib: 2.8.1
+
is-arrayish@0.2.1: {}
is-fullwidth-code-point@3.0.0: {}
diff --git a/prisma/seed-checksum.txt b/prisma/seed-checksum.txt
index 119c95043..d184dc10a 100644
--- a/prisma/seed-checksum.txt
+++ b/prisma/seed-checksum.txt
@@ -1 +1 @@
-157cf91ec892d975f64aaf8ef567b06bdc5cfbd238a1ff3c59f92cdc4081d77f
+0259523777d99a90e4fbda5a66a33f00033a1021a4eed5687f11d1899f77c125
diff --git a/public/locales/cs/common.json b/public/locales/cs/common.json
index 176c777eb..9fcaa6417 100644
--- a/public/locales/cs/common.json
+++ b/public/locales/cs/common.json
@@ -97,7 +97,7 @@
"you_dativus": "Vám"
},
"auth": {
- "continue_with": "Pokračovat s {{provider}}",
+ "continue_with": "Pokračovat s {provider}",
"email_placeholder": "Zadejte váš email",
"no_providers_configured": "Není nastaven žádný poskytovatel přihlášení",
"no_providers_instructions": "Abyste se mohli přihlásit, nastavte prosím alespoň jednoho poskytovatele přihlášení. Pro více informací se podívejte na",
@@ -140,7 +140,7 @@
"group_name_update_failed": "Chyba při aktualizaci jména skupiny",
"import_failed": "Chyba při importování souboru",
"invalid_cron_expression": "Neplatný cron výraz",
- "invalid_currency_code": "Neplatný kód měny {{code}}",
+ "invalid_currency_code": "Neplatný kód měny {code}",
"language_change_failed": "Chyba při změně jazyka",
"less_than": "Soubor by měl být menší než",
"name_required": "Musí být zadáno jméno",
@@ -153,7 +153,7 @@
"request_error": "Chyba při požadavku na notifikace",
"saving_expense": "Chyba při ukládání výdaje",
"setting_update_failed": "Nastavení se nepodařilo aktualizovat",
- "signin_error": "Při přihlašování došlo k chybě: ",
+ "signin_error": "Při přihlašování došlo k chybě: {error}",
"signup_disabled": "Registrace nových účtů je v této instanci zakázána",
"something_went_wrong": "Něco se pokazilo",
"subscribe_error": "Nelze se přihlásit k odběru oznámení",
@@ -344,7 +344,7 @@
"edit_description": "Toto změní rozvrh pro budoucí výdaje. Existující výdaje upraveny nebudou.",
"edit_title": "Upravit opakování",
"empty": "Žádné výdaje zatím nejsou opakované",
- "expense_for_the_amount_of": "Výdaj {{name}} za {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Výdaj {name} za {amount} {currency}",
"last_day": "Poslední den",
"months": "Měsíce",
"never": "Není nastaven žádný plán",
diff --git a/public/locales/de/common.json b/public/locales/de/common.json
index b86f6d65b..53027a7fa 100644
--- a/public/locales/de/common.json
+++ b/public/locales/de/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Dir"
},
"auth": {
- "continue_with": "Weiter mit {{provider}}",
+ "continue_with": "Weiter mit {provider}",
"email_placeholder": "Mailadresse eingeben",
"no_providers_configured": "Keine Authentifizierungsanbieter konfiguriert",
"no_providers_instructions": "Bitte konfigurieren Sie mindestens einen Authentifizierungsanbieter, um sich anzumelden. Siehe die",
@@ -165,9 +165,9 @@
"image_compression_failed": "Bildkomprimierung fehlgeschlagen, versuche Originaldatei hochzuladen",
"import_failed": "Fehler beim Importieren der Datei",
"invalid_cron_expression": "Ungültiger Cron-Ausdruck",
- "invalid_currency_code": "Ungültiger Währungscode {{code}}",
+ "invalid_currency_code": "Ungültiger Währungscode {code}",
"language_change_failed": "Fehler beim Ändern der Sprache",
- "less_than": "Datei sollte kleiner als {{size}}MB sein",
+ "less_than": "Datei sollte kleiner als {size}MB sein",
"name_required": "Name ist erforderlich",
"no_providers": "Fehler beim Abrufen der Authentifizierungsanbieter. Bitte prüfen Sie die Backend-Logs.",
"notification_not_supported": "Benachrichtigungen werden nicht unterstützt",
@@ -178,7 +178,7 @@
"request_error": "Fehler bei der Anfrage der Benachrichtigung",
"saving_expense": "Fehler beim Speichern der Ausgabe",
"setting_update_failed": "Einstellung konnte nicht aktualisiert werden",
- "signin_error": "Beim Anmelden ist ein Fehler aufgetreten: ",
+ "signin_error": "Beim Anmelden ist ein Fehler aufgetreten: {error}",
"signup_disabled": "Neuanmeldung ist in dieser Instanz deaktiviert",
"something_went_wrong": "Etwas ist schiefgelaufen",
"subscribe_error": "Fehler beim Abonnieren der Benachrichtigung",
@@ -377,7 +377,7 @@
"edit_description": "Dadurch ändert sich der Zeitplan für zukünftige Ausgaben. Bestehende Ausgaben werden nicht geändert.",
"edit_title": "Wiederholung bearbeiten",
"empty": "Noch keine wiederkehrenden Ausgaben",
- "expense_for_the_amount_of": "Ausgabe {{name}} in Höhe von {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Ausgabe {name} in Höhe von {amount} {currency}",
"last_day": "Letzter Tag",
"months": "Monate",
"never": "Kein Zeitplan festgelegt",
diff --git a/public/locales/el/common.json b/public/locales/el/common.json
index 58ec7fff8..ff90ba6fc 100644
--- a/public/locales/el/common.json
+++ b/public/locales/el/common.json
@@ -99,7 +99,7 @@
"you_dativus": "Εσύ"
},
"auth": {
- "continue_with": "Συνέχεια με {{provider}}"
+ "continue_with": "Συνέχεια με {provider}"
},
"expense_details": {
"add_expense": "Προσθήκη εξόδου",
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 7b0717bca..ee0cab553 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -57,7 +57,11 @@
"submit_feedback": "Submit feedback",
"submit_feedback_details": {
"placeholder": "Enter your feedback",
- "title": "Submit feedback"
+ "title": "Submit feedback",
+ "messages": {
+ "submit_error": "Failed to send feedback",
+ "submit_success": "Feedback sent"
+ }
},
"support_us": "Sponsor us",
"title": "Account",
@@ -95,7 +99,8 @@
"settle_up": "Settle up",
"submit": "Submit",
"reconnect": "Reconnect",
- "connect": "Connect"
+ "connect": "Connect",
+ "remind_later": "Remind me later"
},
"actors": {
"all": "All",
@@ -108,7 +113,7 @@
"you_dativus": "You"
},
"auth": {
- "continue_with": "Continue with {{provider}}",
+ "continue_with": "Continue with {provider}",
"email_placeholder": "Enter your email",
"no_providers_configured": "No authentication providers configured",
"no_providers_instructions": "Please configure at least one authentication provider to sign in. See the",
@@ -125,7 +130,9 @@
"fetching_rate": "Fetching rate...",
"rate": "Rate",
"success_toast": "Currency conversion set successfully",
- "title": "Currency conversion"
+ "title": "Currency conversion",
+ "amount_aria_label": "Amount",
+ "converted_amount_aria_label": "Converted amount"
},
"errors": {
"currency_conversion_error": "Error while setting currency conversion",
@@ -137,10 +144,10 @@
"add_member_failed": "Failed to add member to the group",
"group_name_update_failed": "Error in updating group name",
"import_failed": "Error importing file",
- "invalid_currency_code": "Invalid currency code {{code}}",
+ "invalid_currency_code": "Invalid currency code {code}",
"invalid_split": "Adjust who owes this expense before saving.",
"language_change_failed": "Error changing language",
- "less_than": "File should be less than {{size}}MB",
+ "less_than": "File should be less than {size}MB",
"image_compression_failed": "Image compression failed, trying to upload original file",
"name_required": "Name is required",
"notification_not_supported": "Notification is not supported",
@@ -149,7 +156,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",
@@ -163,6 +170,12 @@
"currency_conversion_failed": "Failed to convert currencies",
"recurrence_delete_failed": "Failed to delete recurrence",
"recurrence_update_failed": "Failed to update recurrence",
+ "notification_request_failed": "Error requesting notification permission",
+ "notification_subscribe_failed": "Cannot subscribe to notifications",
+ "expense_submission_failed": "An unexpected error occurred while submitting the expense.",
+ "expense_deletion_failed": "An unexpected error occurred while deleting the expense.",
+ "friend_deletion_failed": "Failed to delete friend",
+ "signin_failed": "Sign-in failed. Please try again.",
"invite_email_failed": "Failed to send invite email. Check the SMTP configuration."
},
"bank_transactions": {
@@ -189,14 +202,15 @@
"note": "Note: sending invite is disabled for now because of spam",
"only_one_group_time": "You can have only one group at a time",
"send_invite": "Send invite to user",
- "warning": "Warning: Don't use send invite if it's invalid email. use add to Split Pro instead. Your account will be blocked if this feature is misused"
+ "warning": "Warning: Don't use send invite if it's invalid email. use add to Split Pro instead. Your account will be blocked if this feature is misused",
+ "empty_image_alt": "No users found"
},
"split_type_section": {
"direction": {
"no_money_flow": "No one else owes anything",
- "owes_payer": "{{debtor}} owes {{payer}}",
- "owes_you": "{{debtor}} owes you",
- "you_owe": "You owe {{payer}}"
+ "owes_payer": "{debtor} owes {payer}",
+ "owes_you": "{debtor} owes you",
+ "you_owe": "You owe {payer}"
},
"split_equally": "split equally",
"split_unequally": "split unequally",
@@ -222,7 +236,8 @@
"title": "Share",
"total_shares": "Total shares"
}
- }
+ },
+ "split_equally_with_count": "split equally among {count, plural, one {# person} other {# people}}"
},
"sponsor_us": "Sponsor us",
"upload_file": {
@@ -240,7 +255,10 @@
"balance_list": {
"are_settled_up": "are settled up",
"is_settled_up": "is settled up",
- "press_balance_info": "Press on individual balance to initiate settlement"
+ "press_balance_info": "Press on individual balance to initiate settlement",
+ "settled": "{actor, select, you {You are settled up} other {{person} is settled up}}",
+ "lent": "{actor, select, you {You lent} other {{person} lent}}",
+ "owes": "{actor, select, you {You owe} other {{person} owes}}"
},
"delete_expense_details": {
"text": "This action cannot be undone. This will permanently delete your expense.",
@@ -259,7 +277,13 @@
"submit_all": "Submit all",
"pending": "Pending",
"no_transactions_yet": "No transactions yet",
- "already_added": "Already added"
+ "already_added": "Already added",
+ "receipt": {
+ "title": "Expense receipt",
+ "thumbnail_alt": "Expense receipt thumbnail",
+ "image_alt": "Expense receipt"
+ },
+ "empty_state_image_alt": "No expenses"
},
"group_details": {
"add_members": "Add members",
@@ -382,7 +406,7 @@
"never": "No schedule configured",
"empty": "No recurring expenses yet",
"recurring": "Recurring",
- "expense_for_the_amount_of": "Expense {{name}} for the amount of {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Expense {name} for the amount of {amount} {currency}",
"delete_title": "Delete Recurrence",
"delete_description": "This will stop future expense creation. Existing expenses created by this recurrence will not be deleted.",
"edit_title": "Edit Recurrence",
@@ -400,7 +424,8 @@
"derived_edit_warning": "This expense is part of a recurrence. Changes will only affect this expense.",
"template_delete_warning": "This is a recurring expense template. Deleting it will stop all future recurring expenses.",
"derived_delete_warning": "This expense is part of a recurrence. Only this expense will be deleted. Future recurring expenses will continue.",
- "view_recurring_page": "View Recurring page"
+ "view_recurring_page": "View Recurring page",
+ "toggle_options": "Toggle recurring expense options"
},
"ui": {
"added_by": "Added by",
@@ -433,6 +458,21 @@
"paid": "paid",
"pay": "pay",
"received": "received"
+ },
+ "payer": {
+ "current_user": "You"
+ },
+ "recipient": {
+ "current_user": "You"
+ },
+ "statements": {
+ "you_lent": "You lent",
+ "you_owe": "You owe",
+ "you_lent_amount": "You lent {amount}",
+ "you_owe_amount": "You owe {amount}",
+ "you_pay_user": "You pay {user}",
+ "user_pays_you": "{user} pays you",
+ "user_pays_user": "{payer} pays {receiver}"
}
},
"expense_details": "Expense details",
@@ -451,6 +491,55 @@
"share_text": "Check out SplitPro. It's an open source free alternative for Splitwise",
"show_all_currencies": "Show all currencies",
"today": "Today",
- "in_group": "In group"
+ "in_group": "In group",
+ "transfer": "{senderRole, select, you {You pay {receiver}} other {{receiverRole, select, you {{sender} pays you} other {{sender} pays {receiver}}}}}"
+ },
+ "friend": {
+ "delete_confirmation": {
+ "title": "Are you absolutely sure?",
+ "description": "Do you really want to continue?",
+ "outstanding_balance": "You cannot remove a friend with outstanding balances. Settle up first."
+ }
+ },
+ "activity": {
+ "you_paid_for_expense": "You paid {amount} for {expense}",
+ "you_received_for_expense": "You received {amount} for {expense}",
+ "user_paid_for_expense": "{user} paid {amount} for {expense}",
+ "user_received_for_expense": "{user} received {amount} for {expense}",
+ "user_deleted_expense": "{actor, select, you {You deleted {expense}} other {{user} deleted {expense}}}",
+ "you_paid_amount": "You paid {amount}",
+ "you_received_amount": "You received {amount}"
+ },
+ "balance_summary": {
+ "friend_owes_you": "{friend} owes you {amount}",
+ "you_owe_friend": "You owe {friend} {amount}",
+ "more": "{count, plural, one {# more balance} other {# more balances}}"
+ },
+ "balance_list": {
+ "pays": "{payerRole, select, you {You pay {amount} to {receiver}} other {{payer} pays {amount} to {receiverRole, select, you {you} other {{receiver}}}}}",
+ "gets": "{receiverRole, select, you {You get {amount} from {payer}} other {{receiver} gets {amount} from {payerRole, select, you {you} other {{payer}}}}}"
+ },
+ "expense_participant": {
+ "gets": "{actor, select, you { get } other { gets }}",
+ "owes": "{actor, select, you { owe } other { owes }}",
+ "paid": " paid ",
+ "received": " received "
+ },
+ "expense_metadata": {
+ "edited_by": "{actor, select, you {Edited by you on {date}} other {Edited by {user} on {date}}}",
+ "deleted_by": "{actor, select, you {Deleted by you on {date}} other {Deleted by {user} on {date}}}",
+ "added_by": "{actor, select, you {Added by you on {date}} other {Added by {user} on {date}}}"
+ },
+ "expense_list": {
+ "user_paid": "{actor, select, you {You paid {amount}} other {{user} paid {amount}}}",
+ "user_received": "{actor, select, you {You received {amount}} other {{user} received {amount}}}",
+ "settlement": "{payerRole, select, you {You paid {amount} to {receiver}} other {{receiverRole, select, you {{payer} paid you {amount}} other {{payer} paid {amount} to {receiver}}}}}",
+ "conversion_for_users": "Currency conversion for {payerRole, select, you {you} other {{payer}}} and {receiverRole, select, you {you} other {{receiver}}}"
+ },
+ "notifications": {
+ "enable_title": "Enable notifications",
+ "enable_description": "Don't miss important events. Subscribe to receive notifications when expenses are added or updated.",
+ "permission_granted": "You will receive notifications now",
+ "subscribe": "Subscribe"
}
}
diff --git a/public/locales/en/home.json b/public/locales/en/home.json
index b2d1b5e77..1232d3f14 100644
--- a/public/locales/en/home.json
+++ b/public/locales/en/home.json
@@ -64,7 +64,11 @@
"subtitle_part1": "An",
"subtitle_part2": "alternative to SplitWise",
"title_highlight": "free",
- "title_part1": "Split Expenses with your friends for"
+ "title_part1": "Split Expenses with your friends for",
+ "image_alt": "SplitPro app preview",
+ "title_before_highlight": "Split expenses with your friends for",
+ "title_after_highlight": ".",
+ "subtitle": "An open source alternative to SplitWise."
},
"meta": {
"description": "SplitPro: Split Expenses with your friends for free",
diff --git a/public/locales/es-AR/common.json b/public/locales/es-AR/common.json
index acb3038fd..4479273f4 100644
--- a/public/locales/es-AR/common.json
+++ b/public/locales/es-AR/common.json
@@ -97,7 +97,7 @@
"you_dativus": "A vos"
},
"auth": {
- "continue_with": "Continuar con {{provider}}",
+ "continue_with": "Continuar con {provider}",
"email_placeholder": "Ingresá tu correo electrónico",
"no_providers_configured": "No hay proveedores de autenticación configurados",
"no_providers_instructions": "Por favor, configurá al menos un proveedor de autenticación para iniciar sesión. Consultá las",
@@ -141,7 +141,7 @@
"group_name_update_failed": "Error al actualizar el nombre del grupo",
"import_failed": "Error al importar el archivo",
"invalid_cron_expression": "Expresión cron inválida",
- "invalid_currency_code": "Código de moneda inválido {{code}}",
+ "invalid_currency_code": "Código de moneda inválido {code}",
"language_change_failed": "Error al cambiar el idioma",
"less_than": "El archivo debe ser menor que",
"name_required": "El nombre es requerido",
@@ -154,7 +154,7 @@
"request_error": "Error al solicitar notificaciones",
"saving_expense": "Error al guardar el gasto",
"setting_update_failed": "Error al actualizar la configuración",
- "signin_error": "Ocurrió un error al iniciar sesión: ",
+ "signin_error": "Ocurrió un error al iniciar sesión: {error}",
"signup_disabled": "El registro de nuevas cuentas está deshabilitado en esta instancia",
"something_went_wrong": "Algo salió mal",
"subscribe_error": "No se puede suscribir a las notificaciones",
@@ -348,7 +348,7 @@
"edit_description": "Esto cambiará el cronograma de los gastos futuros. Los gastos existentes no se modificarán.",
"edit_title": "Editar recurrencia",
"empty": "Sin gastos recurrentes aún",
- "expense_for_the_amount_of": "Gasto {{name}} por el monto de {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Gasto {name} por el monto de {amount} {currency}",
"last_day": "Último día",
"months": "Meses",
"never": "Sin agenda configurada",
diff --git a/public/locales/es-MX/common.json b/public/locales/es-MX/common.json
index f85f66b06..a29da7dfc 100644
--- a/public/locales/es-MX/common.json
+++ b/public/locales/es-MX/common.json
@@ -97,7 +97,7 @@
"you_dativus": "A ti"
},
"auth": {
- "continue_with": "Continuar con {{provider}}",
+ "continue_with": "Continuar con {provider}",
"email_placeholder": "Introduce tu correo electrónico",
"no_providers_configured": "No hay proveedores de autenticación configurados",
"no_providers_instructions": "Por favor, configure al menos un proveedor de autenticación para iniciar sesión. Consulte las",
@@ -140,7 +140,7 @@
"group_name_update_failed": "Error al actualizar el nombre del grupo",
"import_failed": "Error al importar el archivo",
"invalid_cron_expression": "Expresión cron inválida",
- "invalid_currency_code": "Código de moneda inválido {{code}}",
+ "invalid_currency_code": "Código de moneda inválido {code}",
"language_change_failed": "Error al cambiar el idioma",
"less_than": "El archivo debe ser menor que",
"name_required": "El nombre es requerido",
@@ -153,7 +153,7 @@
"request_error": "Error al solicitar notificaciones",
"saving_expense": "Error al guardar el gasto",
"setting_update_failed": "Error al actualizar la configuración",
- "signin_error": "Ocurrió un error al iniciar sesión: ",
+ "signin_error": "Ocurrió un error al iniciar sesión: {error}",
"signup_disabled": "El registro de nuevas cuentas está deshabilitado en esta instancia",
"something_went_wrong": "Algo salió mal",
"submit_error": "Error al enviar los comentarios",
@@ -347,7 +347,7 @@
"edit_description": "",
"edit_title": "",
"empty": "No hay gastos recurrentes aún",
- "expense_for_the_amount_of": "Gasto {{name}} por la cantidad de {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Gasto {name} por la cantidad de {amount} {currency}",
"last_day": "",
"months": "Meses",
"never": "No hay horario configurado",
diff --git a/public/locales/es/common.json b/public/locales/es/common.json
index 720c3488f..4cba9fb7f 100644
--- a/public/locales/es/common.json
+++ b/public/locales/es/common.json
@@ -97,7 +97,7 @@
"you_dativus": "A ti"
},
"auth": {
- "continue_with": "Continuar con {{provider}}",
+ "continue_with": "Continuar con {provider}",
"email_placeholder": "Introduce tu correo electrónico",
"no_providers_configured": "No hay proveedores de autenticación configurados",
"no_providers_instructions": "Configure al menos un provedor de autenticación para iniciar sesión. Consulte las",
@@ -140,7 +140,7 @@
"group_name_update_failed": "Error al actualizar el nombre del grupo",
"import_failed": "Error al importar el archivo",
"invalid_cron_expression": "Expresión \"cron\" inválida",
- "invalid_currency_code": "Código de moneda inválido {{code}}",
+ "invalid_currency_code": "Código de moneda inválido {code}",
"language_change_failed": "Error al cambiar de idioma",
"less_than": "El archivo debe ser menor que",
"name_required": "El nombre es obligatorio",
@@ -153,7 +153,7 @@
"request_error": "Error al solicitar notificaciones",
"saving_expense": "Error al guardar el gasto",
"setting_update_failed": "Error al actualizar los ajustes",
- "signin_error": "Error al iniciar sesión: ",
+ "signin_error": "Error al iniciar sesión: {error}",
"signup_disabled": "El registro de nuevas cuentas está deshabilitado",
"something_went_wrong": "Algo salió mal",
"subscribe_error": "No se pudo suscribir a la notificación",
@@ -344,7 +344,7 @@
"edit_description": "Esto modificará la programación de futuros gastos. Los gastos existentes no serán modificados.",
"edit_title": "Editar Recurrencia",
"empty": "Sin gastos recurrentes",
- "expense_for_the_amount_of": "Gasto {{name}} por la cantidad de {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Gasto {name} por la cantidad de {amount} {currency}",
"last_day": "Último día",
"months": "Meses",
"never": "Sin recurrencia",
diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json
index f99b42ee5..d415f4037 100644
--- a/public/locales/fr/common.json
+++ b/public/locales/fr/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Toi"
},
"auth": {
- "continue_with": "Continuer avec {{provider}}",
+ "continue_with": "Continuer avec {provider}",
"email_placeholder": "Ton adresse mail",
"no_providers_configured": "Aucun fournisseur d'authentification configuré",
"no_providers_instructions": "Veuillez configurer au moins un fournisseur d'authentification pour vous connecter. Voir les",
@@ -163,10 +163,10 @@
"image_compression_failed": "Échec de compression de l'image, tentative d'envoi de l'image originale",
"import_failed": "Une erreur est survenue lors de l'import du fichier",
"invalid_cron_expression": "Expression cron invalide",
- "invalid_currency_code": "Le code de devise {{code}} est invalide",
+ "invalid_currency_code": "Le code de devise {code} est invalide",
"invalid_split": "Indique qui a payé cette dépense avant d'enregistrer.",
"language_change_failed": "Le changement de langage a échoué",
- "less_than": "Le poids du fichier doit être inférieur à {{size}}Mo",
+ "less_than": "Le poids du fichier doit être inférieur à {size}Mo",
"name_required": "Un nom est requis",
"no_providers": "Une erreur est survenue lors du chargement des fournisseurs d'authentification. Vérifiez les logs du backend sur le serveur.",
"notification_not_supported": "Notification non supportée",
@@ -177,7 +177,7 @@
"request_error": "Une erreur est survenue lors du chargement des notifications",
"saving_expense": "Un erreur est survenue lors de l'enregistrement de la dépense",
"setting_update_failed": "Une erreur est survenue lors de la mise à jour des paramètres",
- "signin_error": "Une erreur est survenue lors de la connexion : ",
+ "signin_error": "Une erreur est survenue lors de la connexion : {error}",
"signup_disabled": "L'inscription de nouveaux comptes est désactivée sur cette instance",
"something_went_wrong": "Une erreur est survenue",
"subscribe_error": "Une erreur est survenue lors de l'abonnement aux notifications",
@@ -203,9 +203,9 @@
"split_type_section": {
"direction": {
"no_money_flow": "Personne d'autre ne doit d'argent",
- "owes_payer": "{{debtor}} doit cette somme à {{payer}}",
- "owes_you": "{{debtor}} te doit",
- "you_owe": "Ta dette envers {{payer}}"
+ "owes_payer": "{debtor} doit cette somme à {payer}",
+ "owes_you": "{debtor} te doit",
+ "you_owe": "Ta dette envers {payer}"
},
"split_equally": "partagé à parts égales",
"split_unequally": "partagé inégalement",
@@ -382,7 +382,7 @@
"edit_description": "Ceci changera la programmation des futures dépenses sur ce modèle, mais les dépenses déjà créées ne seront pas modifiées.",
"edit_title": "Modifier la récurrence",
"empty": "Aucune dépense récurrente pour l'instant",
- "expense_for_the_amount_of": "Dépense {{name}} pour un montant de {{amount}}{{currency}}",
+ "expense_for_the_amount_of": "Dépense {name} pour un montant de {amount}{currency}",
"last_day": "Dernier jour",
"months": "Mois",
"never": "Aucun programme planifié",
diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json
index 51a660bb7..491aac2c5 100644
--- a/public/locales/hu/common.json
+++ b/public/locales/hu/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Neked"
},
"auth": {
- "continue_with": "Folytatás ezzel: {{provider}}",
+ "continue_with": "Folytatás ezzel: {provider}",
"email_placeholder": "Add meg az e-mail címed",
"no_providers_configured": "Nincsenek konfigurált hitelesítési szolgáltatók",
"no_providers_instructions": "Kérjük, konfigurálj legalább egy hitelesítési szolgáltatót a bejelentkezéshez. Lásd a",
@@ -163,10 +163,10 @@
"image_compression_failed": "A képtömörítés sikertelen, eredeti fájl feltöltése",
"import_failed": "Hiba a fájl importálása során",
"invalid_cron_expression": "Érvénytelen cron kifejezés",
- "invalid_currency_code": "Érvénytelen pénznemkód {{code}}",
+ "invalid_currency_code": "Érvénytelen pénznemkód {code}",
"invalid_split": "Mentés előtt állítsd be, hogy ki tartozik ezzel a költséggel.",
"language_change_failed": "Hiba a nyelv módosításakor",
- "less_than": "A fájl mérete nem haladhatja meg a {{size}} MB-ot",
+ "less_than": "A fájl mérete nem haladhatja meg a {size} MB-ot",
"name_required": "Név megadása kötelező",
"no_providers": "Hiba történt a hitelesítési szolgáltatók lekérésekor. Kérjük, ellenőrizd a háttérnaplókat.",
"notification_not_supported": "Az értesítések nem támogatottak",
@@ -177,7 +177,7 @@
"request_error": "Hiba az értesítés kérése során",
"saving_expense": "Hiba a költség mentése közben",
"setting_update_failed": "Nem sikerült frissíteni a beállítást",
- "signin_error": "Hiba történt bejelentkezés közben: ",
+ "signin_error": "Hiba történt bejelentkezés közben: {error}",
"signup_disabled": "Új fiókok regisztrációja ebben az esetben le van tiltva",
"something_went_wrong": "Valami hiba történt",
"subscribe_error": "Nem lehet feliratkozni az értesítésekre",
@@ -203,9 +203,9 @@
"split_type_section": {
"direction": {
"no_money_flow": "Senki sem tartozik semmivel",
- "owes_payer": "{{debtor}} tartozik neki: {{payer}}",
- "owes_you": "{{debtor}} tartozik neked",
- "you_owe": "Te tartozol neki: {{payer}}"
+ "owes_payer": "{debtor} tartozik neki: {payer}",
+ "owes_you": "{debtor} tartozik neked",
+ "you_owe": "Te tartozol neki: {payer}"
},
"split_equally": "egyenlően elosztva",
"split_unequally": "egyenlőtlenül oszlik meg",
@@ -382,7 +382,7 @@
"edit_description": "Ez megváltoztatja a jövőbeli kiadások ütemezését. A meglévő kiadások nem módosulnak.",
"edit_title": "Ismétlődés szerkesztése",
"empty": "Még nincsenek ismétlődő költségek",
- "expense_for_the_amount_of": "{{name}} kiadás {{amount}} {{currency}} összegben",
+ "expense_for_the_amount_of": "{name} kiadás {amount} {currency} összegben",
"last_day": "Utolsó nap",
"months": "Hónapok",
"never": "Nincs konfigurálva ütemezés",
diff --git a/public/locales/id/common.json b/public/locales/id/common.json
index 54c49e08b..c752ff57b 100644
--- a/public/locales/id/common.json
+++ b/public/locales/id/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Anda"
},
"auth": {
- "continue_with": "Lanjutkan dengan {{provider}}",
+ "continue_with": "Lanjutkan dengan {provider}",
"email_placeholder": "Masukkan email Anda",
"no_providers_configured": "Tidak ada penyedia autentikasi yang dikonfigurasi",
"no_providers_instructions": "Harap konfigurasikan setidaknya satu penyedia autentikasi untuk masuk. Lihat",
@@ -162,9 +162,9 @@
"image_compression_failed": "Kompresi gambar gagal, mencoba mengunggah berkas asli",
"import_failed": "Gagal mengimpor berkas",
"invalid_cron_expression": "Ekspresi cron tidak valid",
- "invalid_currency_code": "Kode mata uang tidak valid {{code}}",
+ "invalid_currency_code": "Kode mata uang tidak valid {code}",
"language_change_failed": "Gagal mengubah bahasa",
- "less_than": "Ukuran berkas harus kurang dari {{size}}MB",
+ "less_than": "Ukuran berkas harus kurang dari {size}MB",
"name_required": "Nama wajib diisi",
"no_providers": "Gagal saat mengambil penyedia autentikasi. Harap periksa log backend.",
"notification_not_supported": "Notifikasi tidak didukung",
@@ -175,7 +175,7 @@
"request_error": "Gagal meminta notifikasi",
"saving_expense": "Gagal saat menyimpan pengeluaran",
"setting_update_failed": "Gagal memperbarui pengaturan",
- "signin_error": "Terjadi kesalahan saat masuk: ",
+ "signin_error": "Terjadi kesalahan saat masuk: {error}",
"signup_disabled": "Pendaftaran akun baru dinonaktifkan pada instansi ini",
"something_went_wrong": "Ada yang salah",
"subscribe_error": "Tidak dapat berlangganan notifikasi",
@@ -374,7 +374,7 @@
"edit_description": "Ini akan mengubah jadwal untuk pengeluaran di masa mendatang. Pengeluaran yang ada tidak akan diubah.",
"edit_title": "Edit Pengulangan",
"empty": "Belum ada pengeluaran berulang",
- "expense_for_the_amount_of": "Pengeluaran {{name}} sebesar {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Pengeluaran {name} sebesar {amount} {currency}",
"last_day": "Hari terakhir",
"months": "Bulan",
"never": "Tidak ada jadwal yang dikonfigurasi",
diff --git a/public/locales/it/common.json b/public/locales/it/common.json
index 81c79e909..e9bd52d15 100644
--- a/public/locales/it/common.json
+++ b/public/locales/it/common.json
@@ -97,7 +97,7 @@
"you_dativus": "Te"
},
"auth": {
- "continue_with": "Continua con {{provider}}",
+ "continue_with": "Continua con {provider}",
"email_placeholder": "Inserisci la tua email",
"no_providers_configured": "Nessun provider di autenticazione configurato",
"no_providers_instructions": "Si prega di configurare almeno un provider di autenticazione per accedere. Consulta le",
@@ -142,7 +142,7 @@
"group_name_update_failed": "Errore nell'aggiornamento del nome del gruppo",
"import_failed": "Errore durante l'importazione del file",
"invalid_cron_expression": "Espressione cron non valida",
- "invalid_currency_code": "Codice valuta {{code}} non valido",
+ "invalid_currency_code": "Codice valuta {code} non valido",
"language_change_failed": "Errore nel cambiare lingua",
"less_than": "Il file deve essere più piccolo di",
"name_required": "Il nome è obbligatorio",
@@ -155,7 +155,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",
@@ -349,7 +349,7 @@
"edit_description": "Questo cambierà il programma delle spese future. Le spese già create non saranno modificate.",
"edit_title": "Modifica ricorrenza",
"empty": "Ancora nessuna spesa ricorrente",
- "expense_for_the_amount_of": "Spesa {{name}} di {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Spesa {name} di {amount} {currency}",
"last_day": "Ultimo giorno",
"months": "Mesi",
"never": "Nessuna pianificazione configurata",
diff --git a/public/locales/ne/common.json b/public/locales/ne/common.json
index 89a5bc85d..90378e6ff 100644
--- a/public/locales/ne/common.json
+++ b/public/locales/ne/common.json
@@ -106,7 +106,7 @@
"you_dativus": "तपाईंका लागि"
},
"auth": {
- "continue_with": "{{provider}} बाट अगाडि बढ्नुहोस्",
+ "continue_with": "{provider} बाट अगाडि बढ्नुहोस्",
"email_placeholder": "इमेल ठेगाना",
"no_providers_configured": "कुनै प्रदायक कन्फिगर गरिएको छैन",
"no_providers_instructions": "कृपया लगइन प्रदायकहरू सेट अप गर्नुहोस्",
@@ -160,9 +160,9 @@
"image_compression_failed": "छवि कम्प्रेसन असफल भयो",
"import_failed": "आयात असफल भयो",
"invalid_cron_expression": "अमान्य क्रोन अभिव्यक्ति",
- "invalid_currency_code": "अमान्य मुद्रा कोड {{code}}",
+ "invalid_currency_code": "अमान्य मुद्रा कोड {code}",
"language_change_failed": "भाषा परिवर्तन गर्न असफल",
- "less_than": "File {{size}}MB भन्दा कम",
+ "less_than": "File {size}MB भन्दा कम",
"name_required": "नाम आवश्यक छ",
"no_providers": "प्रमाणीकरण प्रदायकहरू (authentication providers) प्राप्त गर्दा त्रुटि भयो। कृपया ब्याकइन्ड लगहरू जाँच गर्नुहोस्।",
"notification_not_supported": "यो ब्राउजरमा सूचना समर्थन गर्दैन",
@@ -173,7 +173,7 @@
"request_error": "अनुरोधमा त्रुटि",
"saving_expense": "खर्च बचत गर्दा त्रुटि",
"setting_update_failed": "सेटिङ अपडेट गर्न असफल",
- "signin_error": "साइन इन गर्दा त्रुटि ",
+ "signin_error": "साइन इन गर्दा त्रुटि {error}",
"signup_disabled": "साइन अप असक्षम गरिएको छ",
"something_went_wrong": "केही गलत भयो",
"subscribe_error": "सबस्क्राइब गर्दा त्रुटि",
@@ -369,7 +369,7 @@
"edit_description": "यसले भविष्यका खर्चहरूको तालिका (schedule) परिवर्तन गर्नेछ। हाल भइरहेका खर्चहरूमा कुनै परिमार्जन हुने छैन।",
"edit_title": "आवर्ती खर्च सम्पादन गर्नुहोस्",
"empty": "कुनै आवर्ती खर्च छैन",
- "expense_for_the_amount_of": "खर्च {{name}}, {{amount}} {{currency}} को रकमको लागि",
+ "expense_for_the_amount_of": "खर्च {name}, {amount} {currency} को रकमको लागि",
"last_day": "अन्तिम दिन",
"months": "महिनाहरू",
"never": "कहिल्यै पनि",
diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json
index 3701cf232..5bef79d58 100644
--- a/public/locales/nl/common.json
+++ b/public/locales/nl/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Jou"
},
"auth": {
- "continue_with": "Doorgaan met {{provider}}",
+ "continue_with": "Doorgaan met {provider}",
"email_placeholder": "Voer je e-mailadres in",
"no_providers_configured": "Geen authenticatieproviders geconfigureerd",
"no_providers_instructions": "Configureer ten minste één authenticatieprovider om in te loggen. Zie de",
@@ -162,10 +162,10 @@
"image_compression_failed": "Afbeeldingscompressie mislukt, origineel bestand wordt geüpload",
"import_failed": "Fout bij importeren bestand",
"invalid_cron_expression": "Ongeldige cron-uitdrukking",
- "invalid_currency_code": "Ongeldige valutacode {{code}}",
+ "invalid_currency_code": "Ongeldige valutacode {code}",
"invalid_split": "Bepaal wie deze kosten moet betalen voordat je het opslaat.",
"language_change_failed": "Fout bij taalwijziging",
- "less_than": "Bestand moet minder dan {{size}} MB zijn",
+ "less_than": "Bestand moet minder dan {size} MB zijn",
"name_required": "Naam is vereist",
"no_providers": "Fout bij het ophalen van authenticatieproviders. Controleer de backend-logbestanden.",
"notification_not_supported": "Notificaties worden niet ondersteund",
@@ -176,7 +176,7 @@
"request_error": "Fout bij ophalen notificatie",
"saving_expense": "Fout tijdens het opslaan van de uitgave",
"setting_update_failed": "Instelling bijwerken mislukt",
- "signin_error": "Een fout deed zich voor bij het inloggen: ",
+ "signin_error": "Een fout deed zich voor bij het inloggen: {error}",
"signup_disabled": "Het registreren van nieuwe accounts is uitgeschakeld op deze instantie",
"something_went_wrong": "Er is iets misgegaan",
"subscribe_error": "Aanmelden voor meldingen niet mogelijk",
@@ -202,9 +202,9 @@
"split_type_section": {
"direction": {
"no_money_flow": "Niemand anders is iets verschuldigd",
- "owes_payer": "{{debtor}} is {{payer}} verschuldigd",
- "owes_you": "{{debtor}} is je geld verschuldigd",
- "you_owe": "Je bent {{payer}} geld verschuldigd"
+ "owes_payer": "{debtor} is {payer} verschuldigd",
+ "owes_you": "{debtor} is je geld verschuldigd",
+ "you_owe": "Je bent {payer} geld verschuldigd"
},
"split_equally": "gelijk verdelen",
"split_unequally": "ongelijk verdelen",
@@ -381,7 +381,7 @@
"edit_description": "Dit zal de planning voor toekomstige uitgaven veranderen. Bestaande uitgaven blijven ongewijzigd.",
"edit_title": "Herhaling bewerken",
"empty": "Nog geen terugkerende uitgaven",
- "expense_for_the_amount_of": "Uitgave {{name}} voor het bedrag van {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Uitgave {name} voor het bedrag van {amount} {currency}",
"last_day": "Laatste dag",
"months": "Maanden",
"never": "Geen planning ingesteld",
diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json
index 99a648619..e059a8095 100644
--- a/public/locales/pl/common.json
+++ b/public/locales/pl/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Ciebie"
},
"auth": {
- "continue_with": "Kontynuuj z {{provider}}",
+ "continue_with": "Kontynuuj z {provider}",
"email_placeholder": "Wprowadź swój email",
"no_providers_configured": "Brak skonfigurowanych dostawców uwierzytelniania",
"no_providers_instructions": "Skonfiguruj przynajmniej jednego dostawcę uwierzytelniania, aby się zalogować. Zobacz",
@@ -165,9 +165,9 @@
"image_compression_failed": "Kompresja obrazu nie powiodła się. Wczytuję oryginalny plik",
"import_failed": "Błąd podczas importowania pliku",
"invalid_cron_expression": "Błędne wyrażenie cron",
- "invalid_currency_code": "Nieprawidłowy kod waluty {{code}}",
+ "invalid_currency_code": "Nieprawidłowy kod waluty {code}",
"language_change_failed": "Błąd podczas zmiany języka",
- "less_than": "Plik powinien być mniejszy niż {{size}}MB",
+ "less_than": "Plik powinien być mniejszy niż {size}MB",
"name_required": "Nazwa jest wymagana",
"no_providers": "Błąd podczas pobierania dostawców uwierzytelniania. Sprawdź logi.",
"notification_not_supported": "Powiadomienia nie są obsługiwane",
@@ -178,7 +178,7 @@
"request_error": "Błąd podczas żądania powiadomienia",
"saving_expense": "Błąd podczas zapisywania wydatku",
"setting_update_failed": "Nie udało się zaktualizować ustawienia",
- "signin_error": "Wystąpił błąd podczas logowania: ",
+ "signin_error": "Wystąpił błąd podczas logowania: {error}",
"signup_disabled": "Rejestracja nowych kont jest wyłączona w tej instancji",
"something_went_wrong": "Coś poszło nie tak",
"subscribe_error": "Nie można zasubskrybować powiadomień",
@@ -386,7 +386,7 @@
"edit_description": "Spowoduje zmianę harmonogramu przyszłych wydatków. Istniejące wydatki nie zostaną zmienione.",
"edit_title": "Edytuj cykliczność",
"empty": "Brak wydatków cyklicznych",
- "expense_for_the_amount_of": "Wydatek {{name}} w wysokości {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Wydatek {name} w wysokości {amount} {currency}",
"last_day": "Ostatni dzień",
"months": "Miesiące",
"never": "Brak harmonogramu",
diff --git a/public/locales/pt-BR/common.json b/public/locales/pt-BR/common.json
index 8302ea679..34ae8bd17 100644
--- a/public/locales/pt-BR/common.json
+++ b/public/locales/pt-BR/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Você"
},
"auth": {
- "continue_with": "Continuar com {{provider}}",
+ "continue_with": "Continuar com {provider}",
"email_placeholder": "Insira seu e-mail",
"no_providers_configured": "Nenhum provedor de autenticação configurado",
"no_providers_instructions": "Por favor, configure pelo menos um provedor de autenticação para fazer login. Consulte as",
@@ -162,9 +162,9 @@
"image_compression_failed": "A compressão da imagem falhou. Tentando enviar o arquivo original",
"import_failed": "Erro ao importar arquivo",
"invalid_cron_expression": "Expressão cron inválida",
- "invalid_currency_code": "Moeda inválida com código {{code}}",
+ "invalid_currency_code": "Moeda inválida com código {code}",
"language_change_failed": "Erro ao alterar idioma",
- "less_than": "O arquivo deve ter menos de {{size}}MB",
+ "less_than": "O arquivo deve ter menos de {size}MB",
"name_required": "O nome é obrigatório",
"no_providers": "Erro ao buscar provedores de autenticação. Verifique os logs do backend.",
"notification_not_supported": "Sem suporte a notificações",
@@ -175,7 +175,7 @@
"request_error": "Erro ao solicitar notificação",
"saving_expense": "Erro ao salvar despesa",
"setting_update_failed": "Falha ao atualizar configuração",
- "signin_error": "Erro ao fazer login: ",
+ "signin_error": "Erro ao fazer login: {error}",
"signup_disabled": "Esta instância não permite criação de novas contas",
"something_went_wrong": "Algo deu errado",
"subscribe_error": "Não foi possível inscrever-se nas notificações",
@@ -374,7 +374,7 @@
"edit_description": "Isso vai mudar a programação das despesas futuras. As despesas existentes não serão modificadas.",
"edit_title": "Editar Recorrência",
"empty": "Nenhuma despesa recorrente ainda",
- "expense_for_the_amount_of": "Despesa {{name}} pelo valor de {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Despesa {name} pelo valor de {amount} {currency}",
"last_day": "Último dia",
"months": "Meses",
"never": "Nenhum pagamento recorrente configurado",
diff --git a/public/locales/pt-PT/common.json b/public/locales/pt-PT/common.json
index 91d8f6b6d..4546a08c1 100644
--- a/public/locales/pt-PT/common.json
+++ b/public/locales/pt-PT/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Tu"
},
"auth": {
- "continue_with": "Continuar com {{provider}}",
+ "continue_with": "Continuar com {provider}",
"email_placeholder": "Inserir email",
"no_providers_configured": "Nenhum fornecedor de autenticação configurado",
"no_providers_instructions": "Por favor, configura pelo menos um fornecedor de autenticação para iniciar sessão. Consulta as",
@@ -164,10 +164,10 @@
"image_compression_failed": "A compressão da imagem falhou. Tentar a enviar o ficheiro original",
"import_failed": "Error ao importar ficheiro",
"invalid_cron_expression": "Expressão \"cron\" inválida",
- "invalid_currency_code": "Código {{code}} de moeda inválido",
+ "invalid_currency_code": "Código {code} de moeda inválido",
"invalid_split": "Define quem deve esta despesa antes de guardar.",
"language_change_failed": "Erro ao alterar idioma",
- "less_than": "O ficheiro deve ter menos que {{size}}MB",
+ "less_than": "O ficheiro deve ter menos que {size}MB",
"name_required": "O nome é obrigatório",
"no_providers": "Ocorreu um erro ao transferir os fornecedores de autenticação. Por favor consulta os logs do backend.",
"notification_not_supported": "Notificação não suportada",
@@ -178,7 +178,7 @@
"request_error": "Erro requisitando notificação",
"saving_expense": "Erro ao salvar despesa",
"setting_update_failed": "Falha ao atualizar a configuração",
- "signin_error": "Ocorreu um erro durante a autenticação: ",
+ "signin_error": "Ocorreu um erro durante a autenticação: {error}",
"signup_disabled": "Criação de novas contas encontra-se desativo para esta instância",
"something_went_wrong": "Algo deu errado",
"subscribe_error": "Não foi possivel subscrever notificações",
@@ -205,9 +205,9 @@
"split_type_section": {
"direction": {
"no_money_flow": "Mais ninguém deve nada",
- "owes_payer": "{{debtor}} deve a {{payer}}",
- "owes_you": "{{debtor}} deve-te",
- "you_owe": "Tu deves a {{payer}}"
+ "owes_payer": "{debtor} deve a {payer}",
+ "owes_you": "{debtor} deve-te",
+ "you_owe": "Tu deves a {payer}"
},
"split_equally": "dividir igualmente",
"split_unequally": "dividir desigualmente",
@@ -386,7 +386,7 @@
"edit_description": "Isto vai mudar a programação das despesas futuras. As despesas existentes não serão modificadas.",
"edit_title": "Editar Recorrência",
"empty": "Nenhuma despesa recorrente ainda",
- "expense_for_the_amount_of": "Despesa {{name}} pelo valor de {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Despesa {name} pelo valor de {amount} {currency}",
"last_day": "Último dia",
"months": "Meses",
"never": "Nenhum pagamento recorrente configurado",
diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json
index 5d1926524..7250b1ec3 100644
--- a/public/locales/ru/common.json
+++ b/public/locales/ru/common.json
@@ -96,7 +96,7 @@
"you_dativus": "Вы"
},
"auth": {
- "continue_with": "Продолжить с {{provider}}",
+ "continue_with": "Продолжить с {provider}",
"email_placeholder": "Введите свой имейл",
"no_providers_configured": "Провайдеры аутентификации не настроены",
"no_providers_instructions": "Пожалуйста, настройте хотя бы одного провайдера аутентификации для входа в систему. Смотрите",
@@ -123,7 +123,7 @@
"feedback_required": "Требуется отзыв",
"group_name_update_failed": "Ошибка при обновлении названия группы",
"import_failed": "Ошибка при импорте файла",
- "invalid_currency_code": "Неправильный код валюты {{code}}",
+ "invalid_currency_code": "Неправильный код валюты {code}",
"language_change_failed": "Ошибка смены языка",
"no_providers": "Ошибка при получении данных о провадерах аутентификации. Пожалуйста, проверьте логи бэкэнда."
}
diff --git a/public/locales/sk/common.json b/public/locales/sk/common.json
index e775b9af5..a675e5199 100644
--- a/public/locales/sk/common.json
+++ b/public/locales/sk/common.json
@@ -97,7 +97,7 @@
"you_dativus": "Tebe"
},
"auth": {
- "continue_with": "Pokračuj s {{provider}}",
+ "continue_with": "Pokračuj s {provider}",
"email_placeholder": "Zadaj svoj e-mail",
"no_providers_configured": "Nie sú nakonfigurovaný žiadny poskytovatelia overovania",
"no_providers_instructions": "Prosím nakonfigurujte si aspoň jedného poskytovateľa overovania na prihlásenie. Pozrite si",
@@ -140,7 +140,7 @@
"group_name_update_failed": "Chyba pri zmene mena skupiny",
"import_failed": "Chyba pri importe súboru",
"invalid_cron_expression": "Neplatná cron výraz",
- "invalid_currency_code": "Neplatný kód meny {{code}}",
+ "invalid_currency_code": "Neplatný kód meny {code}",
"language_change_failed": "Chyba pri zmene jazyka",
"less_than": "Súbor by mal byť menší ako",
"name_required": "Meno je povinné",
@@ -153,7 +153,7 @@
"request_error": "Chyba pri vyžiadaní upozornenia",
"saving_expense": "Chyba pri ukladaní výdavku",
"setting_update_failed": "Zlyhala zmena nastavení",
- "signin_error": "Nastala chyba počas prihlasovania: ",
+ "signin_error": "Nastala chyba počas prihlasovania: {error}",
"signup_disabled": "Registrácia nových uživateľov je zakázaná",
"something_went_wrong": "Niečo sa pokazilo",
"subscribe_error": "Nedá sa prihlásiť na odber upozornení",
@@ -344,7 +344,7 @@
"edit_description": "Týmto sa zmení rozvrh budúcich výdavkov. Existujúce výdavky sa neupravia.",
"edit_title": "Upraviť opakovanie",
"empty": "Zatiaľ nie sú žiadne opakujúce sa výdavky",
- "expense_for_the_amount_of": "Výdavok {{name}} vo výške {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Výdavok {name} vo výške {amount} {currency}",
"last_day": "Posledný deň",
"months": "Mesiace",
"never": "Nie je nakonfigurovaný žiadny plán",
diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json
index 5799b83dd..cda94d42f 100644
--- a/public/locales/sv/common.json
+++ b/public/locales/sv/common.json
@@ -108,7 +108,7 @@
"you_dativus": "Dig"
},
"auth": {
- "continue_with": "Fortsätt med {{provider}}",
+ "continue_with": "Fortsätt med {provider}",
"email_placeholder": "Ange din email",
"no_providers_configured": "Inga autentiseringsleverantörer konfigurerade",
"no_providers_instructions": "Vänligen konfigurera minst en autentiseringsleverantör för att logga in. Se",
@@ -163,10 +163,10 @@
"image_compression_failed": "Bildkomprimering misslyckades, försöker ladda upp originalfilen",
"import_failed": "Fel vid import",
"invalid_cron_expression": "Ogiltigt cron‑uttryck",
- "invalid_currency_code": "Ogiltig valutakod {{code}}",
+ "invalid_currency_code": "Ogiltig valutakod {code}",
"invalid_split": "Justera vem som står för denna utgift innan du sparar.",
"language_change_failed": "Fel vid ändring av språk",
- "less_than": "Filen ska vara mindre än {{size}}MB",
+ "less_than": "Filen ska vara mindre än {size}MB",
"name_required": "Namn krävs",
"no_providers": "Fel vid hämtning av autentiseringsleverantörer. Kontrollera serverloggarna.",
"notification_not_supported": "Notifikationer stöds inte",
@@ -177,7 +177,7 @@
"request_error": "Fel vid begäran om notifikationer",
"saving_expense": "Fel vid sparande av utgift",
"setting_update_failed": "Misslyckades med att uppdatera inställning",
- "signin_error": "Ett fel uppstod vid inloggning: ",
+ "signin_error": "Ett fel uppstod vid inloggning: {error}",
"signup_disabled": "Registrering av nya konton är inaktiverat",
"something_went_wrong": "Något gick fel",
"subscribe_error": "Kan inte prenumerera på notifikationer",
@@ -203,9 +203,9 @@
"split_type_section": {
"direction": {
"no_money_flow": "Ingen annan är skyldig något",
- "owes_payer": "{{debtor}} är skyldig {{payer}}",
- "owes_you": "{{debtor}} är skyldig dig",
- "you_owe": "Du är skyldig {{payer}}"
+ "owes_payer": "{debtor} är skyldig {payer}",
+ "owes_you": "{debtor} är skyldig dig",
+ "you_owe": "Du är skyldig {payer}"
},
"split_equally": "dela lika",
"split_unequally": "dela ojämnt",
@@ -382,7 +382,7 @@
"edit_description": "Detta kommer att ändra schemat för framtida utgifter. Befintliga utgifter kommer inte att ändras.",
"edit_title": "Redigera återkommande händelse",
"empty": "Inga återkommande utgifter",
- "expense_for_the_amount_of": "Utgift {{name}} på beloppet {{amount}} {{currency}}",
+ "expense_for_the_amount_of": "Utgift {name} på beloppet {amount} {currency}",
"last_day": "Sista dagen",
"months": "Månader",
"never": "Ingen schemaläggning är inställd",
diff --git a/public/locales/zh-Hans/common.json b/public/locales/zh-Hans/common.json
index 5d1558d13..f3e606f09 100644
--- a/public/locales/zh-Hans/common.json
+++ b/public/locales/zh-Hans/common.json
@@ -108,7 +108,7 @@
"you_dativus": "你"
},
"auth": {
- "continue_with": "使用 {{provider}} 继续",
+ "continue_with": "使用 {provider} 继续",
"email_placeholder": "输入邮箱",
"no_providers_configured": "尚未配置身份验证方式",
"no_providers_instructions": "请至少配置一种身份验证方式后再登录。详见",
@@ -163,10 +163,10 @@
"image_compression_failed": "图片压缩失败,正在尝试上传原始文件",
"import_failed": "导入文件时出错",
"invalid_cron_expression": "无效的 Cron 表达式",
- "invalid_currency_code": "无效的货币代码 {{code}}",
+ "invalid_currency_code": "无效的货币代码 {code}",
"invalid_split": "请调整这笔支出由谁承担后再保存。",
"language_change_failed": "更改语言时出错",
- "less_than": "文件大小应小于 {{size}}MB",
+ "less_than": "文件大小应小于 {size}MB",
"name_required": "请输入姓名",
"no_providers": "获取身份验证方式时出错,请检查后端日志。",
"notification_not_supported": "当前不支持通知功能",
@@ -177,7 +177,7 @@
"request_error": "请求通知权限时出错",
"saving_expense": "保存支出时出错",
"setting_update_failed": "更新设置失败",
- "signin_error": "登录时发生错误: ",
+ "signin_error": "登录时发生错误: {error}",
"signup_disabled": "此实例已关闭新账户注册",
"something_went_wrong": "出了点问题",
"subscribe_error": "无法订阅通知",
@@ -203,9 +203,9 @@
"split_type_section": {
"direction": {
"no_money_flow": "其他人无需承担",
- "owes_payer": "{{debtor}} 欠 {{payer}}",
- "owes_you": "{{debtor}} 欠你",
- "you_owe": "你欠 {{payer}}"
+ "owes_payer": "{debtor} 欠 {payer}",
+ "owes_you": "{debtor} 欠你",
+ "you_owe": "你欠 {payer}"
},
"split_equally": "均分",
"split_unequally": "自定义分摊",
@@ -382,7 +382,7 @@
"edit_description": "修改后将更改未来支出的重复周期,已有支出不会受到影响。",
"edit_title": "编辑定期设置",
"empty": "暂无定期支出",
- "expense_for_the_amount_of": "支出 {{name}}:{{amount}} {{currency}}",
+ "expense_for_the_amount_of": "支出 {name}:{amount} {currency}",
"last_day": "最后一天",
"months": "月份",
"never": "未设置重复周期",
diff --git a/public/locales/zh_Hans/currencies.json b/public/locales/zh-Hans/currencies.json
similarity index 100%
rename from public/locales/zh_Hans/currencies.json
rename to public/locales/zh-Hans/currencies.json
diff --git a/src/components/Account/SubmitFeedback.tsx b/src/components/Account/SubmitFeedback.tsx
index abfe4c848..714d40113 100644
--- a/src/components/Account/SubmitFeedback.tsx
+++ b/src/components/Account/SubmitFeedback.tsx
@@ -36,10 +36,12 @@ export const SubmitFeedback: React.FC = () => {
try {
await submitFeedbackMutation.mutateAsync({ feedback: values.feedback });
feedbackForm.reset();
- toast.success(t('ui.messages.submit_success'), { duration: 1500 });
+ toast.success(t('account.submit_feedback_details.messages.submit_success'), {
+ duration: 1500,
+ });
} catch (e) {
console.info(e);
- toast.error(t('ui.messages.submit_error'));
+ toast.error(t('account.submit_feedback_details.messages.submit_error'));
}
setFeedbackOpen(false);
},
diff --git a/src/components/AddExpense/AddExpensePage.tsx b/src/components/AddExpense/AddExpensePage.tsx
index 86eec7494..19ab15d4d 100644
--- a/src/components/AddExpense/AddExpensePage.tsx
+++ b/src/components/AddExpense/AddExpensePage.tsx
@@ -184,7 +184,7 @@ export const AddOrEditExpensePage: React.FC<{
if (error instanceof Error) {
toast.error(error.message);
} else {
- toast.error('An unexpected error occurred while submitting the expense.');
+ toast.error(t('errors.expense_submission_failed'));
}
}
}, [
@@ -208,6 +208,7 @@ export const AddOrEditExpensePage: React.FC<{
cronExpression,
multipleTransactions,
setSingleTransaction,
+ t,
update,
]);
@@ -336,7 +337,9 @@ export const AddOrEditExpensePage: React.FC<{
className="text-primary h-8 max-w-full min-w-0 justify-start px-1.5 py-0 text-base sm:max-w-none"
>
- {displayName(paidBy, currentUser?.id, 'dativus')}
+ {paidBy?.id === currentUser?.id
+ ? t(`ui.expense.${isNegative ? 'recipient' : 'payer'}.current_user`)
+ : displayName(paidBy)}
@@ -395,7 +398,7 @@ export const AddOrEditExpensePage: React.FC<{
'size-6',
)}
/>
- Toggle recurring expense options
+ {t('recurrence.toggle_options')}
)}
diff --git a/src/components/AddExpense/SelectUserOrGroup.tsx b/src/components/AddExpense/SelectUserOrGroup.tsx
index 765dd9cd8..c81760909 100644
--- a/src/components/AddExpense/SelectUserOrGroup.tsx
+++ b/src/components/AddExpense/SelectUserOrGroup.tsx
@@ -250,7 +250,12 @@ export const SelectUserOrGroup: React.FC<{
{0 === filteredFriends?.length && 0 === filteredGroups?.length ? (
-
+
) : null}
diff --git a/src/components/Expense/BalanceList.tsx b/src/components/Expense/BalanceList.tsx
index 311ab2992..a229a68eb 100644
--- a/src/components/Expense/BalanceList.tsx
+++ b/src/components/Expense/BalanceList.tsx
@@ -47,7 +47,7 @@ export const BalanceList: React.FC<{
total: {},
};
console.error('BalanceList: userId not found in users list', balance.userId);
- toast.error(t('common:errors.group_balances_malformed'));
+ toast.error(t('errors.group_balances_malformed'));
}
if (!res[balance.userId]!.balances[balance.friendId]) {
res[balance.userId]!.balances[balance.friendId] = {};
@@ -80,21 +80,20 @@ export const BalanceList: React.FC<{
- {displayName(user, userQuery.data?.id)}
{Object.values(total).every((amount) => 0n === amount) ? (
- {' '}
- {isCurrentUser
- ? t('expense_details.balance_list.are_settled_up')
- : t('expense_details.balance_list.is_settled_up')}
+ {t('expense_details.balance_list.settled', {
+ actor: isCurrentUser ? 'you' : 'other',
+ person: displayName(user),
+ })}
) : (
<>
- {' '}
- {t(
- `ui.expense.${isCurrentUser ? 'you' : 'user'}.${0 < totalAmount[1] ? 'lent' : 'owe'}`,
- )}{' '}
+ {t(`expense_details.balance_list.${0 < totalAmount[1] ? 'lent' : 'owes'}`, {
+ actor: isCurrentUser ? 'you' : 'other',
+ person: displayName(user),
+ })}{' '}
- {displayName(friend, userQuery.data?.id)}
-
- {' '}
- {t(
- `ui.expense.${friend.id === userQuery.data?.id ? 'you' : 'user'}.${0 > amount ? 'get' : 'pay'}`,
- )}{' '}
-
-
- {getCurrencyHelpersCached(currency).toUIString(BigMath.abs(amount))}
-
-
- {' '}
- {t(`ui.expense.${0 < amount ? 'to' : 'from'}`, {
- ns: 'common',
- })}{' '}
-
-
- {displayName(user, userQuery.data?.id, 'accusativus')}
-
+ {t(0 < amount ? 'balance_list.pays' : 'balance_list.gets', {
+ payerRole:
+ (0 < amount ? friend : user).id === userQuery.data?.id
+ ? 'you'
+ : 'other',
+ receiverRole:
+ (0 < amount ? user : friend).id === userQuery.data?.id
+ ? 'you'
+ : 'other',
+ payer: displayName(0 < amount ? friend : user),
+ receiver: displayName(0 < amount ? user : friend),
+ amount: getCurrencyHelpersCached(currency).toUIString(
+ BigMath.abs(amount),
+ ),
+ })}
diff --git a/src/components/Expense/ConvertibleBalance.tsx b/src/components/Expense/ConvertibleBalance.tsx
index 666ecc921..c3c518bb5 100644
--- a/src/components/Expense/ConvertibleBalance.tsx
+++ b/src/components/Expense/ConvertibleBalance.tsx
@@ -281,7 +281,7 @@ const AmountDisplay: React.FC<{
className,
)}
>
- {t('actors.you')} {t(`ui.expense.you.${isPositive ? 'lent' : 'owe'}`)}
+ {t(`ui.expense.statements.${isPositive ? 'you_lent' : 'you_owe'}`)}
)}
diff --git a/src/components/Expense/CumulatedBalances.tsx b/src/components/Expense/CumulatedBalances.tsx
index 3e3557594..3dd603d5c 100644
--- a/src/components/Expense/CumulatedBalances.tsx
+++ b/src/components/Expense/CumulatedBalances.tsx
@@ -39,7 +39,7 @@ export const CumulatedBalances: React.FC<{
) : (
<>
= ({ expenseId, recurre
toast.error(`Error: ${error.message}`);
} else {
console.error('Unexpected error:', error);
- toast.error('An unexpected error occurred while deleting the expense.');
+ toast.error(t('errors.expense_deletion_failed'));
}
return;
}
router.back();
- }, [expenseId, deleteExpenseMutation, router]);
+ }, [expenseId, deleteExpenseMutation, router, t]);
const description = useMemo(() => {
if (!isPartOfRecurrence) {
diff --git a/src/components/Expense/EditRecurrenceDialog.tsx b/src/components/Expense/EditRecurrenceDialog.tsx
index ae6f7708e..3dfdd81cd 100644
--- a/src/components/Expense/EditRecurrenceDialog.tsx
+++ b/src/components/Expense/EditRecurrenceDialog.tsx
@@ -84,6 +84,7 @@ export const EditRecurrenceDialog: React.FC = ({
= ({ user, expense }) => {
) : null}
{expense.updatedByUser ? (
- {t('ui.edited_by')} {displayName(expense.updatedByUser, user.id, 'dativus')}{' '}
- {t('ui.on')} {toUIDate(expense.updatedAt, { year: true })}
+ {t('expense_metadata.edited_by', {
+ actor: expense.updatedByUser.id === user.id ? 'you' : 'other',
+ user: displayName(expense.updatedByUser),
+ date: toUIDate(expense.updatedAt, { year: true }),
+ })}
) : null}
{expense.deletedByUser ? (
- {t('ui.deleted_by')} {displayName(expense.deletedByUser, user.id, 'dativus')}{' '}
- {t('ui.on')} {toUIDate(expense.deletedAt ?? expense.createdAt, { year: true })}
+ {t('expense_metadata.deleted_by', {
+ actor: expense.deletedByUser.id === user.id ? 'you' : 'other',
+ user: displayName(expense.deletedByUser),
+ date: toUIDate(expense.deletedAt ?? expense.createdAt, { year: true }),
+ })}
) : (
- {t('ui.added_by')} {displayName(expense.addedByUser, user.id, 'dativus')}{' '}
- {t('ui.on')} {toUIDate(expense.createdAt, { year: true })}
+ {t('expense_metadata.added_by', {
+ actor: expense.addedByUser.id === user.id ? 'you' : 'other',
+ user: displayName(expense.addedByUser),
+ date: toUIDate(expense.createdAt, { year: true }),
+ })}
)}
{expense.recurrence ? (
@@ -117,24 +128,34 @@ const ExpenseDetails: React.FC = ({ user, expense }) => {
- expense.amount ? 'expense_participant.received' : 'expense_participant.paid'
}
- >
-
-
-
- {t(
- `ui.expense.${expense.paidByUser.id === user.id ? 'you' : 'user'}.${expense.amount < 0 ? 'received' : 'paid'}`,
- )}
-
- 0 ? 'text-negative' : 'text-positive'}>
- {toUIString(expense.amount)}
-
+ values={{ actor: expense.paidByUser.id === user.id ? 'you' : 'other' }}
+ components={{
+ person: (
+
+
+
+ ),
+ amount: (
+
+ {toUIString(expense.amount)}
+
+ ),
+ }}
+ />
{expense.expenseParticipants
@@ -180,16 +201,22 @@ const ExpenseParticipantEntry: React.FC<{
return (
-
-
-
-
- {t(`ui.expense.${isCurrentUser ? 'you' : 'user'}.${isPositive ? 'get' : 'owe'}`)}
-
- {toUIString(participant.amount)}
+
+
+
+ ),
+ amount: {toUIString(participant.amount)},
+ }}
+ />
);
};
@@ -261,6 +288,7 @@ export const EditCurrencyConversion: React.FC<{ expense: ExpenseDetailsOutput }>
export const EditSettlement: React.FC<{ expense: ExpenseDetailsOutput }> = ({ expense }) => {
const { displayName, t, getCurrencyHelpersCached } = useTranslationWithUtils();
+ const { data: session } = useSession();
const sender = expense.paidByUser;
const receiver = expense.expenseParticipants.find((p) => p.userId !== expense.paidBy)?.user;
@@ -352,7 +380,12 @@ export const EditSettlement: React.FC<{ expense: ExpenseDetailsOutput }> = ({ ex
- {displayName(sender)} {t('ui.expense.user.pay')} {displayName(receiver)}
+ {t('ui.transfer', {
+ senderRole: sender.id === session?.user.id ? 'you' : 'other',
+ receiverRole: receiver.id === session?.user.id ? 'you' : 'other',
+ sender: displayName(sender),
+ receiver: displayName(receiver),
+ })}
{expense.group ? (
{expense.group.name}
diff --git a/src/components/Expense/ExpenseList.tsx b/src/components/Expense/ExpenseList.tsx
index f54b6e0d3..3d755be56 100644
--- a/src/components/Expense/ExpenseList.tsx
+++ b/src/components/Expense/ExpenseList.tsx
@@ -2,6 +2,7 @@ import { SplitType } from '@prisma/client';
import { type inferRouterOutputs } from '@trpc/server';
import Image from 'next/image';
import Link from 'next/link';
+import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import React from 'react';
import { toast } from 'sonner';
@@ -111,8 +112,11 @@ const Expense: ExpenseComponent = ({ e, userId }) => {
{e.name}
- {displayName(e.paidByUser, userId)}{' '}
- {t(`ui.expense.user.${e.amount < 0n ? 'received' : 'paid'}`)} {toUIString(e.amount)}
+ {t(e.amount < 0n ? 'expense_list.user_received' : 'expense_list.user_paid', {
+ actor: e.paidByUser.id === userId ? 'you' : 'other',
+ user: displayName(e.paidByUser),
+ amount: toUIString(e.amount),
+ })}
@@ -120,7 +124,7 @@ const Expense: ExpenseComponent = ({ e, userId }) => {
{youPaid || 0n !== yourExpenseAmount ? (
<>
- {t('actors.you')} {t(`ui.expense.you.${youPaid ? 'lent' : 'owe'}`)}
+ {t(youPaid ? 'ui.expense.statements.you_lent' : 'ui.expense.statements.you_owe')}
{
- {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('expense_list.settlement', {
+ payerRole: e.paidByUser.id === userId ? 'you' : 'other',
+ receiverRole: receiverId === userId ? 'you' : 'other',
+ payer: displayName(e.paidByUser),
+ amount: toUIString(e.amount),
+ receiver: displayName(userDetails.data),
+ })}
@@ -192,16 +200,30 @@ const CurrencyConversion: ExpenseComponent = ({ e, userId }) => {
}
- {t('ui.expense.for')} {displayName(e.paidByUser, userId)} {t('ui.and')}{' '}
- {displayName(userDetails.data, userId)}
+ {t('expense_list.conversion_for_users', {
+ payerRole: e.paidByUser.id === userId ? 'you' : 'other',
+ receiverRole: receiverId === userId ? 'you' : 'other',
+ payer: displayName(e.paidByUser),
+ receiver: displayName(userDetails.data),
+ })}
);
};
-const NoExpenses = () => (
-
-
-
-);
+const NoExpenses = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/Expense/Receipt.tsx b/src/components/Expense/Receipt.tsx
index c0c2cba3b..77c6c74d1 100644
--- a/src/components/Expense/Receipt.tsx
+++ b/src/components/Expense/Receipt.tsx
@@ -1,6 +1,9 @@
+import { useTranslation } from 'next-i18next';
+
import { AppDrawer } from '../ui/drawer';
export const Receipt = ({ fileKey }: { fileKey: string }) => {
+ const { t } = useTranslation();
const thumbKey = fileKey.replace('.webp', '-thumb.webp');
const thumbUrl = `/api/files/${thumbKey}`;
const fullUrl = `/api/files/${fileKey}`;
@@ -11,7 +14,7 @@ export const Receipt = ({ fileKey }: { fileKey: string }) => {
// oxlint-disable-next-line next/no-img-element
{
className="h-14 w-14 rounded-md object-cover object-center data-[loaded=false]:animate-pulse data-[loaded=false]:bg-gray-100/10"
/>
}
- leftAction="Close"
- title="Expense Receipt"
+ leftAction={t('actions.close')}
+ title={t('expense_details.receipt.title')}
className="h-[98vh]"
>
@@ -29,7 +32,7 @@ export const Receipt = ({ fileKey }: { fileKey: string }) => {
src={fullUrl}
width={300}
height={800}
- alt="Expense receipt"
+ alt={t('expense_details.receipt.image_alt')}
data-loaded="false"
onLoad={setDataLoaded}
className="h-full w-full rounded-2xl object-cover data-[loaded=false]:animate-pulse data-[loaded=false]:bg-gray-100/10"
diff --git a/src/components/Friend/CurrencyConversion.tsx b/src/components/Friend/CurrencyConversion.tsx
index b58974a3d..0b4e0503e 100644
--- a/src/components/Friend/CurrencyConversion.tsx
+++ b/src/components/Friend/CurrencyConversion.tsx
@@ -199,7 +199,7 @@ export const CurrencyConversion: React.FC<{
{t('currency_conversion.rate')}
= ({ friendId, disabled }) => {
+ const { t } = useTranslation();
const router = useRouter();
const deleteFriendMutation = api.user.deleteFriend.useMutation();
@@ -22,7 +24,7 @@ export const DeleteFriend: React.FC<{
await deleteFriendMutation.mutateAsync({ friendId });
} catch (e) {
console.error('Failed to delete friend', e);
- toast.error('Failed to delete user');
+ toast.error(t('errors.friend_deletion_failed'));
return;
}
utils.expense.getBalances.invalidate().catch(console.error);
@@ -32,11 +34,11 @@ export const DeleteFriend: React.FC<{
return (
= ({
'Settlement',
];
- const { getCurrencyHelpersCached } = useTranslationWithUtils('common');
+ const { getCurrencyHelpersCached, t } = useTranslationWithUtils('common');
const exportToCSV = () => {
const csvHeaders = headers.join(',');
@@ -87,7 +87,7 @@ export const Export: React.FC = ({
return (
);
};
diff --git a/src/components/Friend/FriendBalance.tsx b/src/components/Friend/FriendBalance.tsx
index aa462ab86..0d0ba4b13 100644
--- a/src/components/Friend/FriendBalance.tsx
+++ b/src/components/Friend/FriendBalance.tsx
@@ -27,7 +27,7 @@ export const FriendBalance: React.FC<{
- {t('actors.you')} {isPositive ? t('ui.expense.you.lent') : t('ui.expense.you.owe')}
+ {t(`ui.expense.statements.${isPositive ? 'you_lent' : 'you_owe'}`)}
{toUIString(balance.amount)}
diff --git a/src/components/Friend/GroupSettleup.tsx b/src/components/Friend/GroupSettleup.tsx
index 3e8a34d63..0fb3b1bdf 100644
--- a/src/components/Friend/GroupSettleup.tsx
+++ b/src/components/Friend/GroupSettleup.tsx
@@ -40,8 +40,20 @@ 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 isUserPaying = 0 > _amount;
+ const sender = isUserPaying ? user : friend;
+ const receiver = isUserPaying ? friend : user;
+ const payerRole = sender.id === data?.user.id ? 'you' : 'other';
+ const receiverRole = receiver.id === data?.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) {
@@ -98,11 +110,7 @@ export const GroupSettleUp: React.FC<{
-
- {displayName(sender, data?.user.id)}{' '}
- {t(`ui.expense.${sender.id === data?.user.id ? 'you' : 'user'}.pay`)}{' '}
- {displayName(receiver, data?.user.id)}
-
+ {paymentText}
{isCurrentUserPaying
- ? `${t('actors.you')} ${t('ui.expense.you.pay')} ${displayName(friend)}`
- : `${displayName(friend)} ${t('ui.expense.user.pay')} ${t('actors.you')}`}
+ ? t('ui.expense.statements.you_pay_user', { user: displayName(friend) })
+ : t('ui.expense.statements.user_pays_you', { user: displayName(friend) })}
{balanceToSettle.groupName ? (
{balanceToSettle.groupName}
diff --git a/src/components/NotificationModal.tsx b/src/components/NotificationModal.tsx
index 09934d8c9..35331351a 100644
--- a/src/components/NotificationModal.tsx
+++ b/src/components/NotificationModal.tsx
@@ -1,5 +1,6 @@
import { Bell } from 'lucide-react';
import React, { useEffect, useState } from 'react';
+import { useTranslation } from 'next-i18next';
import { toast } from 'sonner';
import { useAppStore } from '~/store/appStore';
@@ -33,6 +34,7 @@ const NOTIFICATION_DISMISSED_TIME = 'notification_dismissed_time';
const NOTIFICATION_DISMISSED_TIME_THRESHOLD = 1000 * 60 * 60 * 24 * 30; // 14 days
export const NotificationModal: React.FC = () => {
+ const { t } = useTranslation();
const updatePushSubscription = api.user.updatePushNotification.useMutation();
const webPushPublicKey = useAppStore((s) => s.webPushPublicKey);
@@ -40,7 +42,7 @@ export const NotificationModal: React.FC = () => {
useEffect(() => {
if ('undefined' !== typeof window && 'serviceWorker' in navigator) {
- // run only in browser
+ // Run only in browser
navigator.serviceWorker.ready
.then((reg) => {
reg.pushManager
@@ -66,7 +68,7 @@ export const NotificationModal: React.FC = () => {
try {
const result = await Notification.requestPermission();
if ('granted' === result) {
- toast.success('You will receive notifications now');
+ toast.success(t('notifications.permission_granted'));
navigator.serviceWorker.ready
.then(async (reg) => {
if (!webPushPublicKey) {
@@ -80,13 +82,13 @@ export const NotificationModal: React.FC = () => {
updatePushSubscription.mutate({ subscription: JSON.stringify(sub) });
})
.catch((e) => {
- toast.error('Cannot subscribe to notification');
+ toast.error(t('errors.notification_subscribe_failed'));
console.error(e);
});
setModalOpen(false);
}
} catch (e) {
- toast.error('Error requesting notification');
+ toast.error(t('errors.notification_request_failed'));
console.error(e);
}
}
@@ -104,16 +106,14 @@ export const NotificationModal: React.FC = () => {
- Enable notifications
-
- Don't miss on important events. Subscribe to get notification for added expenses
-
+ {t('notifications.enable_title')}
+ {t('notifications.enable_description')}
- Remind later
+ {t('actions.remind_later')}
- Subscribe
+ {t('notifications.subscribe')}
diff --git a/src/components/SimpleConfirmationDialog.tsx b/src/components/SimpleConfirmationDialog.tsx
index 16076a547..b28491e66 100644
--- a/src/components/SimpleConfirmationDialog.tsx
+++ b/src/components/SimpleConfirmationDialog.tsx
@@ -1,5 +1,5 @@
import { type VariantProps } from 'class-variance-authority';
-import { useCallback, useState } from 'react';
+import { useState } from 'react';
import { useTranslation } from 'next-i18next';
import {
@@ -14,19 +14,21 @@ import {
} from './ui/alert-dialog';
import { Button, type buttonVariants } from './ui/button';
-export const SimpleConfirmationDialog: React.FC<
- {
- title: string;
- open?: boolean;
- onOpenChange?: (open: boolean) => void;
- onCancel?: () => void;
- description: React.ReactNode;
- hasPermission: boolean;
- onConfirm: () => void | Promise;
- loading: boolean;
- children?: React.ReactNode;
- } & VariantProps
-> = ({
+type SimpleConfirmationDialogProps = {
+ title: string;
+ onCancel?: () => void;
+ description: React.ReactNode;
+ hasPermission: boolean;
+ onConfirm: () => void | Promise;
+ loading: boolean;
+ children?: React.ReactNode;
+} & VariantProps &
+ (
+ | { open: boolean; onOpenChange: (open: boolean) => void }
+ | { open?: never; onOpenChange?: never }
+ );
+
+export const SimpleConfirmationDialog: React.FC = ({
open: controlledOpen,
onOpenChange: controlledOnOpenChange,
onCancel,
@@ -41,8 +43,8 @@ export const SimpleConfirmationDialog: React.FC<
const { t } = useTranslation();
const [internalOpen, setInternalOpen] = useState(false);
const isControlled = typeof controlledOpen === 'boolean';
- const open = isControlled ? controlledOpen! : internalOpen;
- const setOpen = isControlled ? controlledOnOpenChange! : setInternalOpen;
+ const open = isControlled ? controlledOpen : internalOpen;
+ const setOpen = isControlled ? controlledOnOpenChange : setInternalOpen;
return (
diff --git a/src/components/group/GroupMyBalance.tsx b/src/components/group/GroupMyBalance.tsx
index 7c1d29511..78824de58 100644
--- a/src/components/group/GroupMyBalance.tsx
+++ b/src/components/group/GroupMyBalance.tsx
@@ -22,47 +22,38 @@ const GroupMyBalance: React.FC = ({
const userMap = useMemo(
() =>
- users.reduce(
- (acc, user) => {
- acc[user.id] = user;
- return acc;
- },
- {} as Record,
- ),
+ users.reduce>((acc, user) => {
+ acc[user.id] = user;
+ return acc;
+ }, {}),
[users],
);
const friendBalances = useMemo(
() =>
- groupBalances.reduce(
- (acc, balance) => {
- if (balance.userId === userId && 0 < BigMath.abs(balance.amount)) {
- acc[balance.friendId] ??= {};
- const friendBalance = acc[balance.friendId]!;
- friendBalance[balance.currency] =
- (friendBalance[balance.currency] ?? 0n) + balance.amount;
- }
- return acc;
- },
- {} as Record>,
- ),
+ groupBalances.reduce>>((acc, balance) => {
+ if (balance.userId === userId && 0 < BigMath.abs(balance.amount)) {
+ acc[balance.friendId] ??= {};
+ const friendBalance = acc[balance.friendId]!;
+ friendBalance[balance.currency] =
+ (friendBalance[balance.currency] ?? 0n) + balance.amount;
+ }
+ return acc;
+ }, {}),
[groupBalances, userId],
);
const cumulatedBalances = useMemo(
() =>
Object.entries(
- Object.values(friendBalances).reduce(
- (acc, balances) => {
- if (balances) {
- Object.entries(balances).forEach(([currency, amount]) => {
- acc[currency] = (acc[currency] ?? 0n) + amount;
- });
- }
- return acc;
- },
- {} as Record,
- ),
+ Object.values(friendBalances).reduce>((acc, balances) => {
+ if (balances) {
+ Object.entries(balances).forEach(([currency, amount]) => {
+ acc[currency] = (acc[currency] ?? 0n) + amount;
+ });
+ }
+ return acc;
+ }, {}),
).map(([currency, amount]) => ({ currency, amount })),
[friendBalances],
);
@@ -80,10 +71,15 @@ const GroupMyBalance: React.FC = ({
{Object.entries(balances).map(([currency, amount]) => (
- {0 < amount
- ? `${friend?.name} ${t('ui.expense.user.owe')} ${t('actors.you_dativus').toLowerCase()}`
- : `${t('actors.you')} ${t('ui.expense.you.owe')} ${friend?.name}`}{' '}
- {getCurrencyHelpersCached(currency).toUIString(amount)}
+ {t(
+ 0 < amount
+ ? 'balance_summary.friend_owes_you'
+ : 'balance_summary.you_owe_friend',
+ {
+ friend: friend?.name ?? '',
+ amount: getCurrencyHelpersCached(currency).toUIString(amount),
+ },
+ )}
))}
@@ -92,8 +88,7 @@ const GroupMyBalance: React.FC = ({
{2 < Object.keys(friendBalances).length ? (
- +{Object.keys(friendBalances).length - 2}{' '}
- {Object.keys(friendBalances).length === 3 ? t('ui.balance') : t('ui.balances')}...
+ {t('balance_summary.more', { count: Object.keys(friendBalances).length - 2 })}
) : null}
diff --git a/src/dummies/splitGenerator.ts b/src/dummies/splitGenerator.ts
index 3a26b5950..08ff0e281 100644
--- a/src/dummies/splitGenerator.ts
+++ b/src/dummies/splitGenerator.ts
@@ -50,11 +50,12 @@ const generatePercentageShares = (participants: ParticipantId[]): SplitShares =>
}; // 100%
}
- // Generate random percentages for first n-1 participants
- const percentages = participants.slice(0, -1).map(() => faker.number.int({ min: 1, max: 99 }));
-
- const totalPercentage = percentages.reduce((sum, p) => sum + p, 0);
- const lastParticipantPercentage = Math.max(0, 100 - totalPercentage);
+ let remainingPercentage = 100;
+ const percentages = participants.slice(0, -1).map(() => {
+ const percentage = faker.number.int({ min: 0, max: Math.min(99, remainingPercentage) });
+ remainingPercentage -= percentage;
+ return percentage;
+ });
// Convert percentages to the format used (0-10000)
const shares: SplitShares = {};
@@ -66,7 +67,7 @@ const generatePercentageShares = (participants: ParticipantId[]): SplitShares =>
});
shares[participants[participants.length - 1]!.id] = {
...initSplitShares(),
- [SplitType.PERCENTAGE]: BigInt(lastParticipantPercentage * 100),
+ [SplitType.PERCENTAGE]: BigInt(remainingPercentage * 100),
};
return shares;
diff --git a/src/pages/account.tsx b/src/pages/account.tsx
index 3f37ad261..5ce3ade8d 100644
--- a/src/pages/account.tsx
+++ b/src/pages/account.tsx
@@ -86,7 +86,10 @@ const AccountPage: NextPageWithUser<{
[updateDetailsMutation, utils.user.me, t],
);
- const header = useMemo(() => Account
, []);
+ const header = useMemo(
+ () => {t('account.title')}
,
+ [t],
+ );
const onSignOut = useCallback(async () => {
await signOut({ redirect: false });
diff --git a/src/pages/activity.tsx b/src/pages/activity.tsx
index 62e66c648..10891d515 100644
--- a/src/pages/activity.tsx
+++ b/src/pages/activity.tsx
@@ -31,9 +31,9 @@ function getPaymentString(
} else if (isSettlement) {
return (
- {t('actors.you')}{' '}
- {user.id === paidBy ? t('ui.expense.you.paid') : t('ui.expense.you.received')}{' '}
- {toUIString(amount)}
+ {t(user.id === paidBy ? 'activity.you_paid_amount' : 'activity.you_received_amount', {
+ amount: toUIString(amount),
+ })}
);
} else {
@@ -41,9 +41,14 @@ function getPaymentString(
- {t(`actors.you`)}{' '}
- {t(`ui.expense.you.${(user.id === paidBy) !== amount < 0n ? 'lent' : 'owe'}`)}{' '}
- {toUIString(expenseUserAmt)}
+ {t(
+ (user.id === paidBy) !== amount < 0n
+ ? 'ui.expense.statements.you_lent_amount'
+ : 'ui.expense.statements.you_owe_amount',
+ {
+ amount: toUIString(expenseUserAmt),
+ },
+ )}
);
}
@@ -93,24 +98,28 @@ const ActivityPage: NextPageWithUser = ({ user }) => {
{e.expense.deletedByUser ? (
-
- {displayName(e.expense.deletedByUser, user.id)}
- {' '}
- {t(
- `ui.expense.${e.expense.deletedByUser.id === user.id ? 'you' : 'user'}.deleted`,
- )}{' '}
- {e.expense.name}
+ {t('activity.user_deleted_expense', {
+ actor: e.expense.deletedByUser.id === user.id ? 'you' : 'other',
+ user: displayName(e.expense.deletedByUser),
+ expense: e.expense.name,
+ })}
) : (
-
- {displayName(e.expense.paidByUser, user.id)}
- {' '}
{t(
- `ui.expense.${e.expense.paidByUser.id === user.id ? 'you' : 'user'}.${e.expense.amount > 0n ? 'paid' : 'received'}`,
- )}{' '}
- {toUIString(e.expense.amount)} {t('ui.expense.for')}{' '}
- {e.expense.name}
+ e.expense.paidByUser.id === user.id
+ ? e.expense.amount > 0n
+ ? 'activity.you_paid_for_expense'
+ : 'activity.you_received_for_expense'
+ : e.expense.amount > 0n
+ ? 'activity.user_paid_for_expense'
+ : 'activity.user_received_for_expense',
+ {
+ user: displayName(e.expense.paidByUser),
+ amount: toUIString(e.expense.amount),
+ expense: e.expense.name,
+ },
+ )}
)}
diff --git a/src/pages/auth/signin.tsx b/src/pages/auth/signin.tsx
index 2e2cfe3d7..d99f02879 100644
--- a/src/pages/auth/signin.tsx
+++ b/src/pages/auth/signin.tsx
@@ -105,7 +105,7 @@ const Home: NextPage<{
} else if ('SessionRequired' === error) {
return;
} else {
- toast.error(t('errors.signin_error') + error);
+ toast.error(t('errors.signin_error', { error }));
console.error('Error during sign-in:', error);
}
}
@@ -121,7 +121,7 @@ const Home: NextPage<{
const oauthProvider = oauthProviders[0];
if (shouldAutoRedirect && oauthProvider && !showVerificationStep && !isLoadingProviders) {
void signIn(oauthProvider.id, { callbackUrl }).catch(() => {
- toast.error(t('errors.signin_error'));
+ toast.error(t('errors.signin_failed'));
});
}
}, [
diff --git a/src/pages/balances.tsx b/src/pages/balances.tsx
index 34b1a74c7..2ca9efb6c 100644
--- a/src/pages/balances.tsx
+++ b/src/pages/balances.tsx
@@ -86,12 +86,12 @@ const BalancePage: NextPageWithUser = ({ user }) => {
) : (
<>
diff --git a/src/pages/home.tsx b/src/pages/home.tsx
index 224afd9e0..18eba273c 100644
--- a/src/pages/home.tsx
+++ b/src/pages/home.tsx
@@ -53,6 +53,10 @@ export default function Home() {
return (
<>
+
+
{t('meta.title')}
+
+