This commit is contained in:
Mateusz Gruszczyński
2026-04-06 10:46:48 +02:00
parent 1ba1a26291
commit 0c7414101a
23 changed files with 962 additions and 355 deletions

View File

@@ -1,4 +1,5 @@
import { Injectable, signal } from '@angular/core';
import { Injectable, inject, signal } from '@angular/core';
import { UiService } from './ui.service';
export type ToastItem = {
id: number;
@@ -9,6 +10,7 @@ export type ToastItem = {
@Injectable({ providedIn: 'root' })
export class ToastService {
private readonly ui = inject(UiService);
readonly items = signal<ToastItem[]>([]);
private counter = 0;
@@ -22,23 +24,29 @@ export class ToastService {
this.items.update((items) => items.filter((item) => item.id !== id));
}
success(message: string, title = 'Gotowe') {
success(message: string, title = this.ui.t('toast.ready')) {
this.show(message, 'success', title);
}
error(message: string, title = 'Błąd') {
error(message: string, title = this.ui.t('toast.error')) {
this.show(message, 'danger', title);
}
warning(message: string, title = 'Uwaga') {
warning(message: string, title = this.ui.t('toast.warning')) {
this.show(message, 'warning', title);
}
info(message: string, title = 'Informacja') {
info(message: string, title = this.ui.t('toast.info')) {
this.show(message, 'info', title);
}
private defaultTitle(tone: ToastItem['tone']) {
return tone === 'success' ? 'Gotowe' : tone === 'danger' ? 'Błąd' : tone === 'warning' ? 'Uwaga' : 'Informacja';
return tone === 'success'
? this.ui.t('toast.ready')
: tone === 'danger'
? this.ui.t('toast.error')
: tone === 'warning'
? this.ui.t('toast.warning')
: this.ui.t('toast.info');
}
}

View File

@@ -14,6 +14,7 @@ const translations: Record<UiLanguage, Record<string, string>> = {
'nav.reports': 'Raporty',
'nav.categories': 'Kategorie',
'nav.admin': 'Administracja',
'action.logout': 'Wyloguj',
'action.addExpense': 'Dodaj wydatek',
'action.openReports': 'Raporty',
@@ -23,38 +24,241 @@ const translations: Record<UiLanguage, Record<string, string>> = {
'action.creatingAccount': 'Tworzenie konta...',
'action.loginMode': 'Logowanie',
'action.registerMode': 'Rejestracja',
'action.save': 'Zapisz',
'action.add': 'Dodaj',
'action.edit': 'Edytuj',
'action.delete': 'Usuń',
'action.cancel': 'Anuluj',
'action.reset': 'Reset',
'action.show': 'Pokaż',
'action.filter': 'Filtruj',
'action.refreshPreview': 'Odśwież podgląd',
'action.sendNow': 'Wyślij teraz',
'action.testSmtp': 'Test SMTP',
'action.saveChanges': 'Zapisz zmiany',
'action.cancelEdit': 'Anuluj edycję',
'action.addMerchant': 'Dodaj kontrahenta',
'action.saveMerchant': 'Zapisz kontrahenta',
'action.addCategory': 'Dodaj kategorię',
'action.block': 'Zablokuj',
'action.unblock': 'Odblokuj',
'action.setUser': 'Ustaw USER',
'action.setAdmin': 'Ustaw ADMIN',
'theme.label': 'Motyw',
'theme.dark': 'Ciemny',
'theme.light': 'Jasny',
'lang.label': 'Język',
'lang.pl': 'Polski',
'lang.en': 'English',
'login.email': 'E-mail',
'login.password': 'Hasło',
'login.fullName': 'Imię i nazwisko',
'dashboard.section': 'Panel wydatków',
'dashboard.subtitle': 'Szybki podgląd wydatków, kontrahentów i raportów SMTP.',
'login.subtitle': 'Zaloguj się, aby zarządzać wydatkami, kontrahentami i raportami.',
'register.subtitle': 'Utwórz konto i zacznij zbierać potwierdzenia oraz statystyki.',
'login.footer': 'Użyj swojego konta, aby zarządzać wydatkami, raportami i uprawnieniami.',
'register.footer': 'Po utworzeniu konta od razu wrócisz do logowania.',
'login.needAccount': 'Nie masz konta?',
'login.haveAccount': 'Masz już konto?',
'login.error': 'Nie udało się zalogować.',
'register.success': 'Konto zostało utworzone.',
'register.error': 'Nie udało się utworzyć konta.',
'dashboard.total': 'Suma miesiąca',
'dashboard.count': 'Liczba wydatków',
'dashboard.avg': 'Średnia',
'dashboard.top': 'Największa kategoria',
'dashboard.share': 'Udział kategorii',
'dashboard.shareHint': 'Miesięczny przekrój kosztów według kategorii.',
'dashboard.areas': 'Najmocniejsze obszary kosztów',
'dashboard.areasHint': 'Najważniejsze obszary kosztowe w aktualnym okresie.',
'dashboard.recent': 'Ostatnie wydatki',
'dashboard.recentHint': 'Ostatnio dodane pozycje wraz z kontrahentami.',
'dashboard.noChartData': 'Brak danych do pokazania wykresu kategorii.',
'stats.title': 'Statystyki',
'stats.subtitle': 'Analiza miesięczna, kwartalna i roczna z podziałem na kategorie i zakres dat.',
'stats.period': 'Okres',
'stats.period.month': 'Miesięczny',
'stats.period.quarter': 'Kwartalny',
'stats.period.year': 'Roczny',
'stats.from': 'Od',
'stats.to': 'Do',
'stats.sum': 'Suma',
'stats.average': 'Średnia',
'stats.share': 'Udział kategorii',
'stats.trend': 'Trend wydatków',
'stats.breakdown': 'Podział kategorii',
'stats.noCategoryChart': 'Brak danych do wykresu kategorii.',
'stats.noTrendChart': 'Brak danych do wykresu trendu.',
'stats.expensesLabel': 'Wydatki',
'expenses.title': 'Wydatki',
'expenses.subtitle': 'Dodawaj wydatki, zapisuj potwierdzenia i wybieraj kontrahentów z listy.',
'expenses.new': 'Nowy wydatek',
'expenses.edit': 'Edytuj wydatek',
'expenses.requiredHint': 'Uzupełnij wymagane pola oznaczone *.',
'expenses.field.title': 'Tytuł',
'expenses.field.amount': 'Kwota',
'expenses.field.date': 'Data',
'expenses.field.category': 'Kategoria',
'expenses.field.payment': 'Płatność',
'expenses.field.merchantPicker': 'Kontrahent / Sprzedawca',
'expenses.field.merchantName': 'Nazwa na wydatku',
'expenses.field.description': 'Opis',
'expenses.field.proofType': 'Typ potwierdzenia',
'expenses.field.proofLabel': 'Etykieta',
'expenses.field.file': 'Plik',
'expenses.field.proofNote': 'Notatka do potwierdzenia',
'expenses.field.crop': 'Kadrowanie',
'expenses.field.cropPreview': 'Podgląd po cropie',
'expenses.payment.none': 'Brak',
'expenses.payment.card': 'Karta',
'expenses.payment.cash': 'Gotówka',
'expenses.payment.transfer': 'Przelew',
'expenses.payment.other': 'Inne',
'expenses.customEntry': 'Własny wpis',
'expenses.allCategories': 'Wszystkie kategorie',
'expenses.search': 'Szukaj',
'expenses.filters': 'Filtry i ostatnie wydatki',
'expenses.noMerchant': 'Brak kontrahenta',
'expenses.noItems': 'Brak wydatków do wyświetlenia.',
'expenses.proof': 'Potwierdzenie',
'expenses.saving': 'Zapisywanie...',
'expenses.added': 'Wydatek został dodany.',
'expenses.saved': 'Wydatek został zapisany.',
'expenses.deleted': 'Wydatek został usunięty.',
'expenses.addError': 'Nie udało się dodać wydatku.',
'expenses.saveError': 'Nie udało się zapisać wydatku.',
'expenses.deleteError': 'Nie udało się usunąć wydatku.',
'expenses.validation.title': 'Podaj tytuł wydatku.',
'expenses.validation.amount': 'Podaj poprawną kwotę większą od 0.',
'expenses.validation.date': 'Wybierz datę wydatku.',
'expenses.validation.category': 'Wybierz kategorię.',
'proof.receipt': 'Paragon',
'proof.invoice': 'Faktura',
'proof.note': 'Notatka',
'proof.statement': 'Wyciąg',
'proof.other': 'Inne',
'merchant.title': 'Kontrahenci',
'merchant.subtitle': 'Zapisani sprzedawcy i usługodawcy do szybkiego wyboru przy wydatkach.',
'merchant.new': 'Nowy kontrahent',
'merchant.edit': 'Edytuj kontrahenta',
'merchant.name': 'Nazwa',
'merchant.type': 'Typ',
'merchant.notes': 'Notatki',
'merchant.showOnLists': 'Pokazuj na listach wyboru',
'merchant.noneSaved': 'Brak zapisanych kontrahentów.',
'merchant.added': 'Kontrahent został dodany.',
'merchant.saved': 'Kontrahent został zapisany.',
'merchant.deleted': 'Kontrahent został usunięty.',
'merchant.saveError': 'Nie udało się zapisać kontrahenta.',
'merchant.deleteError': 'Nie udało się usunąć kontrahenta.',
'merchant.kind.merchant': 'Sprzedawca',
'merchant.kind.service': 'Usługodawca',
'merchant.kind.other': 'Inny',
'reports.title': 'Raporty',
'reports.subtitle': 'Skonfiguruj raporty SMTP, podgląd i ręczne wysyłanie podsumowań.',
'reports.emailTitle': 'Raporty e-mail',
'reports.enable': 'Włącz raporty',
'reports.frequency': 'Częstotliwość',
'reports.frequency.monthly': 'Miesięczna',
'reports.frequency.yearly': 'Roczna',
'reports.frequency.threshold': 'Po przekroczeniu progu',
'reports.targetEmail': 'Adres docelowy',
'reports.threshold': 'Próg kwotowy',
'reports.categories': 'Kategorie raportu',
'reports.preview': 'Podgląd raportu',
'reports.noData': 'Brak danych raportu.',
'reports.saved': 'Ustawienia raportów zapisane.',
'reports.saveError': 'Nie udało się zapisać raportów.',
'reports.previewError': 'Nie udało się pobrać podglądu.',
'reports.sendError': 'Nie udało się wysłać raportu.',
'reports.sentTo': 'Raport wysłano na {email}.',
'categories.title': 'Kategorie',
'categories.subtitle': 'Zarządzaj kategoriami systemowymi i własnymi dla raportów oraz wydatków.',
'categories.new': 'Nowa kategoria',
'categories.edit': 'Edytuj kategorię',
'categories.name': 'Nazwa',
'categories.color': 'Kolor',
'categories.type': 'Typ',
'categories.system': 'Systemowa',
'categories.custom': 'Własna',
'categories.saved': 'Kategoria została zapisana.',
'categories.added': 'Kategoria została dodana.',
'categories.deleted': 'Kategoria została usunięta.',
'categories.saveError': 'Nie udało się zapisać kategorii.',
'categories.deleteError': 'Nie udało się usunąć kategorii.',
'admin.title': 'Administracja',
'admin.subtitle': 'Ustawienia aplikacji, SMTP oraz zarządzanie użytkownikami.',
'admin.settings': 'Ustawienia aplikacji',
'admin.appName': 'Nazwa aplikacji',
'admin.defaultCurrency': 'Domyślna waluta',
'admin.allowedProofTypes': 'Typy potwierdzeń',
'admin.registration': 'Włącz rejestrację',
'admin.smtp': 'SMTP',
'admin.smtpEnabled': 'Włącz SMTP',
'admin.host': 'Host',
'admin.port': 'Port',
'admin.user': 'Użytkownik',
'admin.password': 'Hasło',
'admin.fromName': 'Nazwa nadawcy',
'admin.fromEmail': 'E-mail nadawcy',
'admin.secureConnection': 'Bezpieczne połączenie',
'admin.users': 'Użytkownicy',
'admin.userLabel': 'Użytkownik',
'admin.role': 'Rola',
'admin.status': 'Status',
'admin.date': 'Data',
'admin.noUsers': 'Brak użytkowników.',
'admin.settingsSaved': 'Ustawienia zapisane.',
'admin.settingsError': 'Nie udało się zapisać ustawień.',
'admin.missingFromEmail': 'Uzupełnij e-mail nadawcy.',
'admin.testSent': 'Wiadomość testowa została wysłana.',
'admin.testError': 'Nie udało się wysłać testu SMTP.',
'admin.roleUpdated': 'Rola została zaktualizowana.',
'admin.roleError': 'Nie udało się zmienić roli.',
'admin.statusUpdated': 'Status konta został zaktualizowany.',
'admin.statusError': 'Nie udało się zmienić statusu.',
'common.none': 'Brak',
'common.select': 'Wybierz',
'common.noData': 'Brak danych.',
'common.noExpenses': 'Brak wydatków.',
'common.noCategories': 'Brak kategorii.'
'common.noCategories': 'Brak kategorii.',
'common.active': 'Aktywny',
'common.hidden': 'Ukryty',
'common.blocked': 'Zablokowany',
'common.selected': 'OK',
'table.title': 'Tytuł',
'table.merchant': 'Kontrahent',
'table.date': 'Data',
'table.amount': 'Kwota',
'table.count': 'Liczba',
'table.category': 'Kategoria',
'toast.ready': 'Gotowe',
'toast.error': 'Błąd',
'toast.warning': 'Uwaga',
'toast.info': 'Informacja'
},
en: {
'app.name': 'Expense Control',
'nav.dashboard': 'Dashboard',
'nav.expenses': 'Expenses',
'nav.stats': 'Statistics',
'nav.merchants': 'Partners',
'nav.merchants': 'Merchants',
'nav.reports': 'Reports',
'nav.categories': 'Categories',
'nav.admin': 'Admin',
'nav.admin': 'Administration',
'action.logout': 'Sign out',
'action.addExpense': 'Add expense',
'action.openReports': 'Reports',
@@ -64,28 +268,230 @@ const translations: Record<UiLanguage, Record<string, string>> = {
'action.creatingAccount': 'Creating account...',
'action.loginMode': 'Sign in',
'action.registerMode': 'Register',
'action.save': 'Save',
'action.add': 'Add',
'action.edit': 'Edit',
'action.delete': 'Delete',
'action.cancel': 'Cancel',
'action.reset': 'Reset',
'action.show': 'Show',
'action.filter': 'Filter',
'action.refreshPreview': 'Refresh preview',
'action.sendNow': 'Send now',
'action.testSmtp': 'SMTP test',
'action.saveChanges': 'Save changes',
'action.cancelEdit': 'Cancel editing',
'action.addMerchant': 'Add merchant',
'action.saveMerchant': 'Save merchant',
'action.addCategory': 'Add category',
'action.block': 'Block',
'action.unblock': 'Unblock',
'action.setUser': 'Set USER',
'action.setAdmin': 'Set ADMIN',
'theme.label': 'Theme',
'theme.dark': 'Dark',
'theme.light': 'Light',
'lang.label': 'Language',
'lang.pl': 'Polish',
'lang.en': 'English',
'login.email': 'Email',
'login.password': 'Password',
'login.fullName': 'Full name',
'dashboard.section': 'Expense overview',
'dashboard.subtitle': 'Fast access to expenses, partners and SMTP reports.',
'login.subtitle': 'Sign in to manage expenses, merchants and reports.',
'register.subtitle': 'Create an account and start collecting proofs and analytics.',
'login.footer': 'Use your account to manage expenses, reports and permissions.',
'register.footer': 'After creating an account you will be taken back to sign in.',
'login.needAccount': 'Need an account?',
'login.haveAccount': 'Already registered?',
'login.error': 'Sign in failed.',
'register.success': 'Account created successfully.',
'register.error': 'Account creation failed.',
'dashboard.total': 'Month total',
'dashboard.count': 'Expense count',
'dashboard.avg': 'Average',
'dashboard.top': 'Top category',
'dashboard.share': 'Category share',
'dashboard.shareHint': 'Monthly cost split by category.',
'dashboard.areas': 'Top cost areas',
'dashboard.areasHint': 'Most important cost areas in the current period.',
'dashboard.recent': 'Recent expenses',
'dashboard.recentHint': 'Most recently added items with merchants.',
'dashboard.noChartData': 'No category chart data available.',
'stats.title': 'Statistics',
'stats.subtitle': 'Monthly, quarterly and yearly analysis by category and date range.',
'stats.period': 'Period',
'stats.period.month': 'Monthly',
'stats.period.quarter': 'Quarterly',
'stats.period.year': 'Yearly',
'stats.from': 'From',
'stats.to': 'To',
'stats.sum': 'Total',
'stats.average': 'Average',
'stats.share': 'Category share',
'stats.trend': 'Expense trend',
'stats.breakdown': 'Category breakdown',
'stats.noCategoryChart': 'No category chart data available.',
'stats.noTrendChart': 'No trend chart data available.',
'stats.expensesLabel': 'Expenses',
'expenses.title': 'Expenses',
'expenses.subtitle': 'Add expenses, store proofs and pick merchants from the list.',
'expenses.new': 'New expense',
'expenses.edit': 'Edit expense',
'expenses.requiredHint': 'Complete the required fields marked with *.',
'expenses.field.title': 'Title',
'expenses.field.amount': 'Amount',
'expenses.field.date': 'Date',
'expenses.field.category': 'Category',
'expenses.field.payment': 'Payment',
'expenses.field.merchantPicker': 'Merchant / Seller',
'expenses.field.merchantName': 'Name on expense',
'expenses.field.description': 'Description',
'expenses.field.proofType': 'Proof type',
'expenses.field.proofLabel': 'Label',
'expenses.field.file': 'File',
'expenses.field.proofNote': 'Proof note',
'expenses.field.crop': 'Crop',
'expenses.field.cropPreview': 'Cropped preview',
'expenses.payment.none': 'None',
'expenses.payment.card': 'Card',
'expenses.payment.cash': 'Cash',
'expenses.payment.transfer': 'Transfer',
'expenses.payment.other': 'Other',
'expenses.customEntry': 'Custom entry',
'expenses.allCategories': 'All categories',
'expenses.search': 'Search',
'expenses.filters': 'Filters and recent expenses',
'expenses.noMerchant': 'No merchant',
'expenses.noItems': 'No expenses to display.',
'expenses.proof': 'Proof',
'expenses.saving': 'Saving...',
'expenses.added': 'Expense added successfully.',
'expenses.saved': 'Expense saved successfully.',
'expenses.deleted': 'Expense deleted successfully.',
'expenses.addError': 'Failed to add the expense.',
'expenses.saveError': 'Failed to save the expense.',
'expenses.deleteError': 'Failed to delete the expense.',
'expenses.validation.title': 'Enter an expense title.',
'expenses.validation.amount': 'Enter a valid amount greater than 0.',
'expenses.validation.date': 'Select an expense date.',
'expenses.validation.category': 'Select a category.',
'proof.receipt': 'Receipt',
'proof.invoice': 'Invoice',
'proof.note': 'Note',
'proof.statement': 'Statement',
'proof.other': 'Other',
'merchant.title': 'Merchants',
'merchant.subtitle': 'Saved sellers and service providers for faster expense entry.',
'merchant.new': 'New merchant',
'merchant.edit': 'Edit merchant',
'merchant.name': 'Name',
'merchant.type': 'Type',
'merchant.notes': 'Notes',
'merchant.showOnLists': 'Show in pickers',
'merchant.noneSaved': 'No merchants saved.',
'merchant.added': 'Merchant added successfully.',
'merchant.saved': 'Merchant saved successfully.',
'merchant.deleted': 'Merchant deleted successfully.',
'merchant.saveError': 'Failed to save the merchant.',
'merchant.deleteError': 'Failed to delete the merchant.',
'merchant.kind.merchant': 'Seller',
'merchant.kind.service': 'Service provider',
'merchant.kind.other': 'Other',
'reports.title': 'Reports',
'reports.subtitle': 'Configure SMTP reports, preview them and send summaries manually.',
'reports.emailTitle': 'Email reports',
'reports.enable': 'Enable reports',
'reports.frequency': 'Frequency',
'reports.frequency.monthly': 'Monthly',
'reports.frequency.yearly': 'Yearly',
'reports.frequency.threshold': 'When threshold is exceeded',
'reports.targetEmail': 'Destination email',
'reports.threshold': 'Amount threshold',
'reports.categories': 'Report categories',
'reports.preview': 'Report preview',
'reports.noData': 'No report data available.',
'reports.saved': 'Report settings saved.',
'reports.saveError': 'Failed to save report settings.',
'reports.previewError': 'Failed to load the preview.',
'reports.sendError': 'Failed to send the report.',
'reports.sentTo': 'Report sent to {email}.',
'categories.title': 'Categories',
'categories.subtitle': 'Manage system and custom categories for reports and expenses.',
'categories.new': 'New category',
'categories.edit': 'Edit category',
'categories.name': 'Name',
'categories.color': 'Color',
'categories.type': 'Type',
'categories.system': 'System',
'categories.custom': 'Custom',
'categories.saved': 'Category saved successfully.',
'categories.added': 'Category added successfully.',
'categories.deleted': 'Category deleted successfully.',
'categories.saveError': 'Failed to save the category.',
'categories.deleteError': 'Failed to delete the category.',
'admin.title': 'Administration',
'admin.subtitle': 'Application settings, SMTP and user management.',
'admin.settings': 'Application settings',
'admin.appName': 'Application name',
'admin.defaultCurrency': 'Default currency',
'admin.allowedProofTypes': 'Allowed proof types',
'admin.registration': 'Enable registration',
'admin.smtp': 'SMTP',
'admin.smtpEnabled': 'Enable SMTP',
'admin.host': 'Host',
'admin.port': 'Port',
'admin.user': 'User',
'admin.password': 'Password',
'admin.fromName': 'Sender name',
'admin.fromEmail': 'Sender email',
'admin.secureConnection': 'Secure connection',
'admin.users': 'Users',
'admin.userLabel': 'User',
'admin.role': 'Role',
'admin.status': 'Status',
'admin.date': 'Date',
'admin.noUsers': 'No users found.',
'admin.settingsSaved': 'Settings saved successfully.',
'admin.settingsError': 'Failed to save settings.',
'admin.missingFromEmail': 'Enter the sender email address.',
'admin.testSent': 'Test message sent successfully.',
'admin.testError': 'Failed to send the SMTP test.',
'admin.roleUpdated': 'Role updated successfully.',
'admin.roleError': 'Failed to change the role.',
'admin.statusUpdated': 'Account status updated successfully.',
'admin.statusError': 'Failed to change the account status.',
'common.none': 'None',
'common.select': 'Select',
'common.noData': 'No data.',
'common.noExpenses': 'No expenses.',
'common.noCategories': 'No categories.'
'common.noCategories': 'No categories.',
'common.active': 'Active',
'common.hidden': 'Hidden',
'common.blocked': 'Blocked',
'common.selected': 'OK',
'table.title': 'Title',
'table.merchant': 'Merchant',
'table.date': 'Date',
'table.amount': 'Amount',
'table.count': 'Count',
'table.category': 'Category',
'toast.ready': 'Done',
'toast.error': 'Error',
'toast.warning': 'Warning',
'toast.info': 'Information'
}
};
@@ -123,8 +529,14 @@ export class UiService {
this.language.set(language);
}
t(key: string) {
return translations[this.language()][key] ?? translations.pl[key] ?? key;
t(key: string, params?: Record<string, string | number>) {
let value = translations[this.language()][key] ?? translations.pl[key] ?? key;
if (params) {
Object.entries(params).forEach(([paramKey, paramValue]) => {
value = value.replace(new RegExp(`\\{${paramKey}\\}`, 'g'), String(paramValue));
});
}
return value;
}
private readTheme(): UiTheme {