fix(costs): amounts follow their currency's decimals and format, and payments count on the local day

Several small things in the costs views, on desktop and phone alike:

- The outstanding card no longer says "You're all settled up" while a
  payment is still open in Settle up.
- The custom split shows its suggested shares as a faint placeholder in
  the currency's own form (30000 for won, not 30000.00), so it no longer
  reads like an amount already typed.
- Amount fields take only the decimals the currency has: won and yen take
  none, KWD and the other three-decimal currencies three. A switch of
  currency cuts what was already typed. 1000.555 won was saved as typed,
  listed as 1,001 and reopened as 1001.
- The expense sheet on the phone prints shares and sums with the same
  formatter as the list, with grouping and the currency's decimals.
- A payment recorded with Settle or Settle all is dated on the viewer's
  own day, and one without a date of its own is grouped by its UTC record
  time read on the local calendar. In Seoul before 09:00 it landed on the
  day before.
- "1 expenses need a payer" now uses the plural forms of each language.

Refs #2604
This commit is contained in:
Maurice
2026-10-12 15:04:32 +02:00
parent 9064dbb2f0
commit dbbbd07b1c
37 changed files with 424 additions and 74 deletions
+5
View File
@@ -63,6 +63,11 @@ const budget: TranslationStrings = {
'costs.outstanding': 'مبلغ غير مسدَّد',
'costs.outstandingSub': 'مصاريف بلا دافع بعد',
'costs.outstandingItems': 'مصاريف بحاجة إلى دافع',
'costs.outstandingItems.zero': 'مصاريف بحاجة إلى دافع',
'costs.outstandingItems.one': 'مصروف بحاجة إلى دافع',
'costs.outstandingItems.two': 'مصروفان بحاجة إلى دافع',
'costs.outstandingItems.few': 'مصروفات بحاجة إلى دافع',
'costs.outstandingItems.many': 'مصروفًا بحاجة إلى دافع',
'costs.expenses': 'المصروفات',
'costs.entries': '{count} إدخال',
'costs.entries.zero': 'لا توجد إدخالات',
+1
View File
@@ -66,6 +66,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Gözləyən məbləğ',
'costs.outstandingSub': 'Ödəyicisi hələ təyin edilməyən xərclər',
'costs.outstandingItems': 'xərc üçün ödəyici təyin edilməlidir',
'costs.outstandingItems.one': 'xərc üçün ödəyici təyin edilməlidir',
'costs.expenses': 'Xərclər',
'costs.entries': '{count} qeyd',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Valor pendente',
'costs.outstandingSub': 'Despesas sem pagador',
'costs.outstandingItems': 'despesas sem pagador',
'costs.outstandingItems.one': 'despesa sem pagador',
'costs.expenses': 'Despesas',
'costs.entries': '{count} lançamentos',
'costs.entries.one': '{count} lançamento',
+1
View File
@@ -168,6 +168,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Import pendent',
'costs.outstandingSub': 'Despeses encara sense pagador',
'costs.outstandingItems': 'despeses necessiten un pagador',
'costs.outstandingItems.one': 'despesa necessita un pagador',
'costs.multiplePayers': 'Han pagat diverses persones',
'costs.singlePayer': 'Ha pagat una sola persona',
'costs.payersUnbalanced': 'Els imports dels pagadors han de sumar {amount}',
+2
View File
@@ -60,6 +60,8 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Nevyřízená částka',
'costs.outstandingSub': 'Výdaje zatím bez plátce',
'costs.outstandingItems': 'výdajů potřebuje plátce',
'costs.outstandingItems.one': 'výdaj potřebuje plátce',
'costs.outstandingItems.few': 'výdaje potřebují plátce',
'costs.expenses': 'Výdaje',
'costs.entries': '{count} položek',
'costs.entries.one': '{count} položka',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Ausstehender Betrag',
'costs.outstandingSub': 'Ausgaben noch ohne Zahler',
'costs.outstandingItems': 'Ausgaben ohne Zahler',
'costs.outstandingItems.one': 'Ausgabe ohne Zahler',
'costs.expenses': 'Ausgaben',
'costs.entries': '{count} Einträge',
'costs.entries.one': '{count} Eintrag',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Outstanding amount',
'costs.outstandingSub': 'Expenses with no payer yet',
'costs.outstandingItems': 'expenses need a payer',
'costs.outstandingItems.one': 'expense needs a payer',
'costs.expenses': 'Expenses',
'costs.entries': '{count} entries',
'costs.entries.one': '{count} entry',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Importe pendiente',
'costs.outstandingSub': 'Gastos sin pagador aún',
'costs.outstandingItems': 'gastos sin pagador',
'costs.outstandingItems.one': 'gasto sin pagador',
'costs.expenses': 'Gastos',
'costs.entries': '{count} entradas',
'costs.entries.one': '{count} entrada',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Tasumata summa',
'costs.outstandingSub': 'Kulud, millel veel maksja puudub',
'costs.outstandingItems': 'kulu vajab maksjat',
'costs.outstandingItems.one': 'kulu vajab maksjat',
'costs.expenses': 'Kulud',
'costs.entries': '{count} kirjet',
'costs.entries.one': '{count} kirje',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Montant en attente',
'costs.outstandingSub': 'Dépenses sans payeur',
'costs.outstandingItems': 'dépenses sans payeur',
'costs.outstandingItems.one': 'dépense sans payeur',
'costs.expenses': 'Dépenses',
'costs.entries': '{count} entrées',
'costs.entries.one': '{count} entrée',
+1
View File
@@ -61,6 +61,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Εκκρεμές ποσό',
'costs.outstandingSub': 'Έξοδα χωρίς πληρωτή ακόμη',
'costs.outstandingItems': 'έξοδα χρειάζονται πληρωτή',
'costs.outstandingItems.one': 'έξοδο χρειάζεται πληρωτή',
'costs.expenses': 'Έξοδα',
'costs.entries': '{count} εγγραφές',
'costs.entries.one': '{count} εγγραφή',
+1
View File
@@ -61,6 +61,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Rendezetlen összeg',
'costs.outstandingSub': 'Fizető nélküli költségek',
'costs.outstandingItems': 'költség fizetőre vár',
'costs.outstandingItems.one': 'költség fizetőre vár',
'costs.expenses': 'Költségek',
'costs.entries': '{count} bejegyzés',
'costs.entries.one': '{count} bejegyzés',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Importo in sospeso',
'costs.outstandingSub': 'Spese senza pagante',
'costs.outstandingItems': 'spese senza pagante',
'costs.outstandingItems.one': 'spesa senza pagante',
'costs.expenses': 'Spese',
'costs.entries': '{count} voci',
'costs.entries.one': '{count} voce',
+1
View File
@@ -60,6 +60,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Openstaand bedrag',
'costs.outstandingSub': 'Uitgaven zonder betaler',
'costs.outstandingItems': 'uitgaven zonder betaler',
'costs.outstandingItems.one': 'uitgave zonder betaler',
'costs.expenses': 'Uitgaven',
'costs.entries': '{count} invoeren',
'costs.entries.one': '{count} invoer',
+4 -1
View File
@@ -60,7 +60,10 @@ const budget: TranslationStrings = {
'costs.youPaid': 'Zapłaciłeś',
'costs.outstanding': 'Kwota nierozliczona',
'costs.outstandingSub': 'Wydatki bez płatnika',
'costs.outstandingItems': 'wydatków potrzebuje płatnika',
'costs.outstandingItems': 'wydatku potrzebuje płatnika',
'costs.outstandingItems.one': 'wydatek potrzebuje płatnika',
'costs.outstandingItems.few': 'wydatki potrzebują płatnika',
'costs.outstandingItems.many': 'wydatków potrzebuje płatnika',
'costs.expenses': 'Wydatki',
'costs.entries': '{count} wpisu',
'costs.entries.one': '{count} wpis',
+4 -1
View File
@@ -60,7 +60,10 @@ const budget: TranslationStrings = {
'costs.youPaid': 'Вы заплатили',
'costs.outstanding': 'Неурегулированная сумма',
'costs.outstandingSub': 'Расходы без плательщика',
'costs.outstandingItems': 'расходов без плательщика',
'costs.outstandingItems': 'расхода без плательщика',
'costs.outstandingItems.one': 'расход без плательщика',
'costs.outstandingItems.few': 'расхода без плательщика',
'costs.outstandingItems.many': 'расходов без плательщика',
'costs.expenses': 'Расходы',
'costs.entries': '{count} записи',
'costs.entries.one': '{count} запись',
+2
View File
@@ -60,6 +60,8 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Nevyriešená suma',
'costs.outstandingSub': 'Výdavky zatiaľ bez platcu',
'costs.outstandingItems': 'výdavkov potrebuje platcu',
'costs.outstandingItems.one': 'výdavok potrebuje platcu',
'costs.outstandingItems.few': 'výdavky potrebujú platcu',
'costs.expenses': 'Výdavky',
'costs.entries': '{count} položiek',
'costs.entries.one': '{count} položka',
+1
View File
@@ -59,6 +59,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Utestående belopp',
'costs.outstandingSub': 'Utgifter utan betalare än',
'costs.outstandingItems': 'utgifter saknar betalare',
'costs.outstandingItems.one': 'utgift saknar betalare',
'costs.expenses': 'Kostnader',
'costs.entries': '{count} inlägg',
'costs.entries.one': '{count} inlägg',
+1
View File
@@ -60,6 +60,7 @@ const budget: TranslationStrings = {
'costs.outstanding': 'Bekleyen tutar',
'costs.outstandingSub': 'Henüz ödeyeni olmayan harcamalar',
'costs.outstandingItems': 'harcamanın ödeyeni yok',
'costs.outstandingItems.one': 'harcamanın ödeyeni yok',
'costs.expenses': 'Harcamalar',
'costs.entries': '{count} kayıt',
'costs.entries.one': '{count} kayıt',
+4 -1
View File
@@ -60,7 +60,10 @@ const budget: TranslationStrings = {
'costs.youPaid': 'Ви заплатили',
'costs.outstanding': 'Неврегульована сума',
'costs.outstandingSub': 'Витрати без платника',
'costs.outstandingItems': 'витрат без платника',
'costs.outstandingItems': 'витрати без платника',
'costs.outstandingItems.one': 'витрата без платника',
'costs.outstandingItems.few': 'витрати без платника',
'costs.outstandingItems.many': 'витрат без платника',
'costs.expenses': 'Витрати',
'costs.entries': '{count} запису',
'costs.entries.one': '{count} запис',