@jjlmoya/utils-finance 1.41.0 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +4 -1
  3. package/src/tests/locale_completeness.test.ts +2 -2
  4. package/src/tests/tool_validation.test.ts +2 -2
  5. package/src/tool/rentalDepositReturn/bibliography.astro +11 -0
  6. package/src/tool/rentalDepositReturn/bibliography.ts +6 -0
  7. package/src/tool/rentalDepositReturn/component.astro +53 -0
  8. package/src/tool/rentalDepositReturn/controller.ts +156 -0
  9. package/src/tool/rentalDepositReturn/currency.ts +67 -0
  10. package/src/tool/rentalDepositReturn/dom-views.ts +31 -0
  11. package/src/tool/rentalDepositReturn/entry.ts +28 -0
  12. package/src/tool/rentalDepositReturn/i18n/content-factory.ts +36 -0
  13. package/src/tool/rentalDepositReturn/i18n/de.ts +3 -0
  14. package/src/tool/rentalDepositReturn/i18n/en.ts +116 -0
  15. package/src/tool/rentalDepositReturn/i18n/es.ts +3 -0
  16. package/src/tool/rentalDepositReturn/i18n/fr.ts +3 -0
  17. package/src/tool/rentalDepositReturn/i18n/id.ts +3 -0
  18. package/src/tool/rentalDepositReturn/i18n/it.ts +3 -0
  19. package/src/tool/rentalDepositReturn/i18n/ja.ts +3 -0
  20. package/src/tool/rentalDepositReturn/i18n/ko.ts +3 -0
  21. package/src/tool/rentalDepositReturn/i18n/nl.ts +3 -0
  22. package/src/tool/rentalDepositReturn/i18n/pl.ts +3 -0
  23. package/src/tool/rentalDepositReturn/i18n/pt.ts +3 -0
  24. package/src/tool/rentalDepositReturn/i18n/ru.ts +3 -0
  25. package/src/tool/rentalDepositReturn/i18n/sv.ts +3 -0
  26. package/src/tool/rentalDepositReturn/i18n/tr.ts +3 -0
  27. package/src/tool/rentalDepositReturn/i18n/translations.ts +34 -0
  28. package/src/tool/rentalDepositReturn/i18n/zh.ts +3 -0
  29. package/src/tool/rentalDepositReturn/index.ts +11 -0
  30. package/src/tool/rentalDepositReturn/logic.test.ts +53 -0
  31. package/src/tool/rentalDepositReturn/logic.ts +108 -0
  32. package/src/tool/rentalDepositReturn/rental-deposit-return-calculator.css +591 -0
  33. package/src/tool/rentalDepositReturn/seo.astro +11 -0
  34. package/src/tool/rentalDepositReturn/ui.ts +53 -0
  35. package/src/tools.ts +2 -1
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('fr', localizedCopies.fr!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('id', localizedCopies.id!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('it', localizedCopies.it!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('ja', localizedCopies.ja!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('ko', localizedCopies.ko!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('nl', localizedCopies.nl!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('pl', localizedCopies.pl!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('pt', localizedCopies.pt!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('ru', localizedCopies.ru!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('sv', localizedCopies.sv!);
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('tr', localizedCopies.tr!);
@@ -0,0 +1,34 @@
1
+ import type { RentalDepositLocaleCopy } from './content-factory';
2
+
3
+ type Seed = { title: string; description: string; ui: Record<string, string>; faq: Array<{ question: string; answer: string }>; howTo: Array<{ name: string; text: string }>; seoTitle: string; seoText: string; seoLimit: string };
4
+
5
+ const makeCopy = (seed: Seed): RentalDepositLocaleCopy => ({
6
+ title: seed.title,
7
+ description: seed.description,
8
+ ui: seed.ui,
9
+ faq: seed.faq,
10
+ howTo: seed.howTo,
11
+ seo: [
12
+ { type: 'title', text: seed.seoTitle, level: 2 },
13
+ { type: 'paragraph', html: `<p>${seed.seoText}</p>` },
14
+ { type: 'title', text: seed.seoLimit, level: 3 },
15
+ { type: 'paragraph', html: `<p>${seed.description}</p>` },
16
+ ],
17
+ });
18
+
19
+ export const localizedCopies: Record<string, RentalDepositLocaleCopy> = {
20
+ sv: makeCopy({ title: 'Kalkylator för återbetalning av hyresdeposition', description: 'Uppskatta återbetalningen av din hyresdeposition genom att skilja bekräftade och bestridda avdrag åt.', ui: { depositLabel: 'Betald deposition', deductionsTitle: 'Belopp som hålls från depositionen', descriptionLabel: 'Beskrivning', amountLabel: 'Belopp', statusLabel: 'Status för avdrag', confirmedOption: 'Bekräftat', disputedOption: 'Bestritt', addButton: 'Lägg till avdrag', resetButton: 'Återställ exempel', currencyLabel: 'Valuta', summaryTitle: 'Bekräftat eller bestridt', estimatedReturnLabel: 'Beräknad återbetalning', undisputedReturnLabel: 'Utan bestridda avdrag', totalDeductionsLabel: 'Totala avdrag', confirmedLabel: 'Bekräftade avdrag', disputedLabel: 'Bestridda avdrag', returnFlowTitle: 'Deposition att återbetala', returnNode: 'Beräknad återbetalning', copyButton: 'Kopiera sammanfattning', copiedButton: 'Sammanfattning kopierad', methodTitle: 'Så fungerar uppskattningen' }, faq: [{ question: 'Vad uppskattar kalkylatorn?', answer: 'Den drar av de angivna beloppen från depositionen och visar effekten av varje bestridet avdrag.' }, { question: 'Avgör kalkylatorn om ett avdrag är lagligt?', answer: 'Nej. Den räknar bara om dina egna belopp.' }, { question: 'Vad är ett bestridet avdrag?', answer: 'En kostnad som du vill kontrollera.' }], howTo: [{ name: 'Ange depositionen', text: 'Skriv in depositionen och välj valuta.' }, { name: 'Lägg till avdrag', text: 'Ange varje avdrag med belopp och status.' }, { name: 'Jämför återbetalningar', text: 'Jämför resultatet med varje scenario.' }, { name: 'Kopiera sammanfattningen', text: 'Kopiera beloppen och kontrollera underlagen.' }], seoTitle: 'Gör en depositionsredovisning till en tydlig återbetalningsuppskattning', seoText: 'Ange deposition och avdrag för att se återbetalningen och effekten av varje bestridande.', seoLimit: 'En beräkning, inte ett juridiskt beslut' }),
21
+ de: makeCopy({ title: 'Rechner für die Rückzahlung der Mietkaution', description: 'Schätze die Rückzahlung deiner Mietkaution anhand bestätigter und strittiger Abzüge.', ui: { depositLabel: 'Gezahlte Kaution', deductionsTitle: 'Von der Kaution einbehalten', descriptionLabel: 'Beschreibung', amountLabel: 'Betrag', statusLabel: 'Status des Abzugs', confirmedOption: 'Bestätigt', disputedOption: 'Strittig', addButton: 'Abzug hinzufügen', resetButton: 'Beispiel wiederherstellen', currencyLabel: 'Währung', summaryTitle: 'Bestätigt oder strittig', estimatedReturnLabel: 'Geschätzte Rückzahlung', undisputedReturnLabel: 'Ohne strittige Abzüge', totalDeductionsLabel: 'Abzüge insgesamt', confirmedLabel: 'Bestätigte Abzüge', disputedLabel: 'Strittige Abzüge', returnFlowTitle: 'Kaution zurück', returnNode: 'Geschätzt zurück', copyButton: 'Zusammenfassung kopieren', copiedButton: 'Zusammenfassung kopiert', methodTitle: 'So funktioniert die Schätzung' }, faq: [{ question: 'Was schätzt der Rechner?', answer: 'Er zieht die eingegebenen Abzüge von der Kaution ab und zeigt Szenarien ohne strittige Positionen.' }, { question: 'Ist das eine Rechtsentscheidung?', answer: 'Nein. Regeln und Fristen hängen vom Vertrag und vom Ort ab.' }, { question: 'Was ist eine strittige Position?', answer: 'Eine Forderung, die du prüfen oder zurückweisen möchtest.' }], howTo: [{ name: 'Kaution eingeben', text: 'Gib die gezahlte Kaution ein und wähle die Währung.' }, { name: 'Abzüge hinzufügen', text: 'Trage jede Position mit Betrag und Status ein.' }, { name: 'Rückzahlung vergleichen', text: 'Vergleiche das Ergebnis mit den strittigen Szenarien.' }, { name: 'Zusammenfassung kopieren', text: 'Kopiere die Zahlen und prüfe Vertrag und Belege.' }], seoTitle: 'Eine Kautionsabrechnung in eine Rückzahlungsschätzung verwandeln', seoText: 'Gib Kaution und Abzüge ein, um bestätigte und strittige Positionen zu vergleichen.', seoLimit: 'Eine Berechnung, keine Rechtsentscheidung' }),
22
+ es: makeCopy({ title: 'Calculadora de devolución de fianza de alquiler', description: 'Estima cuánto podría devolverse de tu fianza después de separar cargos confirmados y discutidos.', ui: { depositLabel: 'Fianza pagada', deductionsTitle: 'Importes retenidos de la fianza', descriptionLabel: 'Descripción', amountLabel: 'Importe', statusLabel: 'Estado del cargo', confirmedOption: 'Confirmado', disputedOption: 'Discutido', addButton: 'Añadir cargo', resetButton: 'Restaurar ejemplo', currencyLabel: 'Moneda', summaryTitle: 'Confirmado frente a discutido', estimatedReturnLabel: 'Devolución estimada', undisputedReturnLabel: 'Sin cargos discutidos', totalDeductionsLabel: 'Cargos totales', confirmedLabel: 'Cargos confirmados', disputedLabel: 'Cargos discutidos', returnFlowTitle: 'Fianza a devolver', returnNode: 'Devolución estimada', copyButton: 'Copiar resumen', copiedButton: 'Resumen copiado', methodTitle: 'Cómo funciona esta estimación' }, faq: [{ question: '¿Qué estima esta calculadora?', answer: 'Resta de la fianza los cargos introducidos y muestra el efecto de cada cargo discutido.' }, { question: '¿Decide si un cargo es legal?', answer: 'No. Solo transforma tus cifras.' }, { question: '¿Qué es un cargo discutido?', answer: 'Una reparación, limpieza, factura u otra cantidad que quieres revisar.' }], howTo: [{ name: 'Introduce la fianza', text: 'Escribe la fianza pagada y elige su moneda.' }, { name: 'Añade los cargos', text: 'Introduce cada cargo con su importe y estado.' }, { name: 'Compara las devoluciones', text: 'Compara la devolución con cada escenario discutido.' }, { name: 'Copia el resumen', text: 'Copia las cifras y revisa el contrato y los justificantes.' }], seoTitle: 'Convierte una liquidación de fianza en una estimación clara', seoText: 'Introduce la fianza y sus cargos para ver la devolución y el efecto de cada disputa.', seoLimit: 'Un cálculo, no una resolución legal' }),
23
+ fr: makeCopy({ title: 'Calculateur de restitution de dépôt de garantie', description: 'Estimez la restitution de votre dépôt après avoir séparé les retenues confirmées et contestées.', ui: { depositLabel: 'Dépôt versé', deductionsTitle: 'Sommes retenues sur le dépôt', descriptionLabel: 'Description', amountLabel: 'Montant', statusLabel: 'Statut de la retenue', confirmedOption: 'Confirmée', disputedOption: 'Contestée', addButton: 'Ajouter une retenue', resetButton: 'Restaurer l\'exemple', currencyLabel: 'Devise', summaryTitle: 'Confirmé ou contesté', estimatedReturnLabel: 'Restitution estimée', undisputedReturnLabel: 'Sans retenues contestées', totalDeductionsLabel: 'Retenues totales', confirmedLabel: 'Retenues confirmées', disputedLabel: 'Retenues contestées', returnFlowTitle: 'Dépôt à restituer', returnNode: 'Restitution estimée', copyButton: 'Copier le résumé', copiedButton: 'Résumé copié', methodTitle: 'Comment fonctionne l\'estimation' }, faq: [{ question: 'Que calcule cet outil ?', answer: 'Il soustrait les retenues saisies du dépôt et montre l\'effet de chaque contestation.' }, { question: 'Décide-t-il si une retenue est légale ?', answer: 'Non. Il transforme uniquement vos montants.' }, { question: 'Qu\'est-ce qu\'une retenue contestée ?', answer: 'Une dépense que vous souhaitez vérifier.' }], howTo: [{ name: 'Saisir le dépôt', text: 'Indiquez le dépôt versé et choisissez sa devise.' }, { name: 'Ajouter les retenues', text: 'Saisissez chaque ligne, son montant et son statut.' }, { name: 'Comparer les restitutions', text: 'Comparez le résultat avec chaque scénario.' }, { name: 'Copier le résumé', text: 'Copiez les chiffres et vérifiez les justificatifs.' }], seoTitle: 'Transformer un décompte de dépôt en estimation', seoText: 'Saisissez le dépôt et les retenues pour voir la restitution et l\'effet de chaque contestation.', seoLimit: 'Un calcul, pas une décision juridique' }),
24
+ id: makeCopy({ title: 'Kalkulator Pengembalian Deposit Sewa', description: 'Perkirakan pengembalian deposit sewa setelah memisahkan potongan yang disetujui dan disengketakan.', ui: { depositLabel: 'Deposit dibayar', deductionsTitle: 'Potongan dari deposit', descriptionLabel: 'Deskripsi', amountLabel: 'Jumlah', statusLabel: 'Status potongan', confirmedOption: 'Disetujui', disputedOption: 'Disengketakan', addButton: 'Tambah potongan', resetButton: 'Pulihkan contoh', currencyLabel: 'Mata uang', summaryTitle: 'Disetujui vs disengketakan', estimatedReturnLabel: 'Perkiraan pengembalian', undisputedReturnLabel: 'Tanpa potongan yang disengketakan', totalDeductionsLabel: 'Total potongan', confirmedLabel: 'Potongan disetujui', disputedLabel: 'Potongan disengketakan', returnFlowTitle: 'Deposit yang dikembalikan', returnNode: 'Perkiraan kembali', copyButton: 'Salin ringkasan', copiedButton: 'Ringkasan disalin', methodTitle: 'Cara kerja perkiraan' }, faq: [{ question: 'Apa yang dihitung kalkulator ini?', answer: 'Kalkulator mengurangi potongan dari deposit dan menunjukkan dampak setiap sengketa.' }, { question: 'Apakah kalkulator menentukan legalitas?', answer: 'Tidak. Kalkulator hanya mengolah angka Anda.' }, { question: 'Apa itu potongan yang disengketakan?', answer: 'Biaya yang ingin Anda periksa.' }], howTo: [{ name: 'Masukkan deposit', text: 'Masukkan deposit dan pilih mata uang.' }, { name: 'Tambahkan potongan', text: 'Masukkan setiap potongan, jumlah, dan statusnya.' }, { name: 'Bandingkan hasil', text: 'Bandingkan pengembalian dengan setiap skenario.' }, { name: 'Salin ringkasan', text: 'Salin angka dan periksa dokumen pendukung.' }], seoTitle: 'Ubah rincian deposit menjadi perkiraan pengembalian', seoText: 'Masukkan deposit dan potongan untuk melihat pengembalian serta dampak setiap sengketa.', seoLimit: 'Perhitungan, bukan keputusan hukum' }),
25
+ it: makeCopy({ title: 'Calcolatore della restituzione del deposito cauzionale', description: 'Stima quanto del deposito potrebbe essere restituito separando trattenute confermate e contestate.', ui: { depositLabel: 'Deposito versato', deductionsTitle: 'Importi trattenuti dal deposito', descriptionLabel: 'Descrizione', amountLabel: 'Importo', statusLabel: 'Stato della trattenuta', confirmedOption: 'Confermata', disputedOption: 'Contestata', addButton: 'Aggiungi trattenuta', resetButton: 'Ripristina esempio', currencyLabel: 'Valuta', summaryTitle: 'Confermate e contestate', estimatedReturnLabel: 'Restituzione stimata', undisputedReturnLabel: 'Senza trattenute contestate', totalDeductionsLabel: 'Trattenute totali', confirmedLabel: 'Trattenute confermate', disputedLabel: 'Trattenute contestate', returnFlowTitle: 'Deposito da restituire', returnNode: 'Restituzione stimata', copyButton: 'Copia riepilogo', copiedButton: 'Riepilogo copiato', methodTitle: 'Come funziona la stima' }, faq: [{ question: 'Cosa stima questo calcolatore?', answer: 'Sottrae le trattenute dal deposito e mostra l\'effetto di ogni contestazione.' }, { question: 'Decide se una trattenuta è legittima?', answer: 'No. Elabora solo i tuoi importi.' }, { question: 'Cos\'è una trattenuta contestata?', answer: 'Una voce che vuoi verificare.' }], howTo: [{ name: 'Inserisci il deposito', text: 'Inserisci il deposito e scegli la valuta.' }, { name: 'Aggiungi le trattenute', text: 'Inserisci ogni voce con importo e stato.' }, { name: 'Confronta i risultati', text: 'Confronta la restituzione con ogni scenario.' }, { name: 'Copia il riepilogo', text: 'Copia i valori e controlla i documenti.' }], seoTitle: 'Trasforma il rendiconto del deposito in una stima', seoText: 'Inserisci deposito e trattenute per vedere la restituzione e l\'effetto di ogni contestazione.', seoLimit: 'Un calcolo, non una decisione legale' }),
26
+ nl: makeCopy({ title: 'Rekenhulp voor terugbetaling van de huurwaarborg', description: 'Schat de terugbetaling van je huurwaarborg na bevestigde en betwiste inhoudingen.', ui: { depositLabel: 'Betaalde waarborg', deductionsTitle: 'Inhoudingen op de waarborg', descriptionLabel: 'Omschrijving', amountLabel: 'Bedrag', statusLabel: 'Status van inhouding', confirmedOption: 'Bevestigd', disputedOption: 'Betwist', addButton: 'Inhouding toevoegen', resetButton: 'Voorbeeld herstellen', currencyLabel: 'Valuta', summaryTitle: 'Bevestigd tegenover betwist', estimatedReturnLabel: 'Geschatte terugbetaling', undisputedReturnLabel: 'Zonder betwiste inhoudingen', totalDeductionsLabel: 'Totale inhoudingen', confirmedLabel: 'Bevestigde inhoudingen', disputedLabel: 'Betwiste inhoudingen', returnFlowTitle: 'Waarborg terug', returnNode: 'Geschat terug', copyButton: 'Samenvatting kopiëren', copiedButton: 'Samenvatting gekopieerd', methodTitle: 'Zo werkt de schatting' }, faq: [{ question: 'Wat schat deze rekentool?', answer: 'De tool trekt inhoudingen af en toont het effect van elke betwiste post.' }, { question: 'Bepaalt de tool of een inhouding wettelijk is?', answer: 'Nee. De tool rekent met je eigen bedragen.' }, { question: 'Wat is een betwiste post?', answer: 'Een bedrag dat je wilt controleren.' }], howTo: [{ name: 'Voer de waarborg in', text: 'Voer de waarborg en valuta in.' }, { name: 'Voeg inhoudingen toe', text: 'Voer elke post met bedrag en status in.' }, { name: 'Vergelijk terugbetalingen', text: 'Bekijk het scenario per betwiste post.' }, { name: 'Kopieer de samenvatting', text: 'Kopieer de bedragen en controleer de stukken.' }], seoTitle: 'Maak van een waarborgafrekening een duidelijke schatting', seoText: 'Voer de waarborg en inhoudingen in om terugbetaling en betwiste posten te vergelijken.', seoLimit: 'Een berekening, geen juridisch oordeel' }),
27
+ pl: makeCopy({ title: 'Kalkulator zwrotu kaucji za wynajem', description: 'Oszacuj zwrot kaucji po rozdzieleniu potrąceń potwierdzonych i kwestionowanych.', ui: { depositLabel: 'Wpłacona kaucja', deductionsTitle: 'Potrącenia z kaucji', descriptionLabel: 'Opis', amountLabel: 'Kwota', statusLabel: 'Status potrącenia', confirmedOption: 'Potwierdzone', disputedOption: 'Kwestionowane', addButton: 'Dodaj potrącenie', resetButton: 'Przywróć przykład', currencyLabel: 'Waluta', summaryTitle: 'Potwierdzone i kwestionowane', estimatedReturnLabel: 'Szacowany zwrot', undisputedReturnLabel: 'Bez kwestionowanych potrąceń', totalDeductionsLabel: 'Suma potrąceń', confirmedLabel: 'Potwierdzone potrącenia', disputedLabel: 'Kwestionowane potrącenia', returnFlowTitle: 'Kaucja do zwrotu', returnNode: 'Szacowany zwrot', copyButton: 'Kopiuj podsumowanie', copiedButton: 'Podsumowanie skopiowane', methodTitle: 'Jak działa szacunek' }, faq: [{ question: 'Co oblicza ten kalkulator?', answer: 'Odejmuje potrącenia od kaucji i pokazuje wpływ każdej spornej pozycji.' }, { question: 'Czy rozstrzyga legalność potrącenia?', answer: 'Nie. Przetwarza podane kwoty.' }, { question: 'Co oznacza kwestionowane potrącenie?', answer: 'Pozycję, którą chcesz sprawdzić.' }], howTo: [{ name: 'Wpisz kaucję', text: 'Wpisz kaucję i wybierz walutę.' }, { name: 'Dodaj potrącenia', text: 'Wpisz każdą pozycję, kwotę i status.' }, { name: 'Porównaj zwroty', text: 'Porównaj wynik z każdym scenariuszem.' }, { name: 'Skopiuj podsumowanie', text: 'Skopiuj liczby i sprawdź dokumenty.' }], seoTitle: 'Zamień rozliczenie kaucji w czytelny szacunek zwrotu', seoText: 'Wpisz kaucję i potrącenia, aby zobaczyć zwrot oraz wpływ każdej spornej pozycji.', seoLimit: 'Obliczenie, nie decyzja prawna' }),
28
+ pt: makeCopy({ title: 'Calculadora de devolução do depósito de arrendamento', description: 'Estime a devolução do seu depósito depois de separar deduções confirmadas e contestadas.', ui: { depositLabel: 'Depósito pago', deductionsTitle: 'Valores retidos do depósito', descriptionLabel: 'Descrição', amountLabel: 'Valor', statusLabel: 'Estado da dedução', confirmedOption: 'Confirmada', disputedOption: 'Contestada', addButton: 'Adicionar dedução', resetButton: 'Restaurar exemplo', currencyLabel: 'Moeda', summaryTitle: 'Confirmadas e contestadas', estimatedReturnLabel: 'Devolução estimada', undisputedReturnLabel: 'Sem deduções contestadas', totalDeductionsLabel: 'Total das deduções', confirmedLabel: 'Deduções confirmadas', disputedLabel: 'Deduções contestadas', returnFlowTitle: 'Depósito a devolver', returnNode: 'Devolução estimada', copyButton: 'Copiar resumo', copiedButton: 'Resumo copiado', methodTitle: 'Como funciona a estimativa' }, faq: [{ question: 'O que estima esta calculadora?', answer: 'Subtrai as deduções do depósito e mostra o efeito de cada contestação.' }, { question: 'Decide se uma dedução é legal?', answer: 'Não. Calcula os seus valores.' }, { question: 'O que é uma dedução contestada?', answer: 'Uma cobrança que pretende verificar.' }], howTo: [{ name: 'Introduza o depósito', text: 'Indique o depósito e escolha a moeda.' }, { name: 'Adicione as deduções', text: 'Introduza cada linha, valor e estado.' }, { name: 'Compare as devoluções', text: 'Compare a devolução com cada cenário.' }, { name: 'Copie o resumo', text: 'Copie os valores e confirme os documentos.' }], seoTitle: 'Transforme o acerto do depósito numa estimativa clara', seoText: 'Introduza o depósito e as deduções para ver a devolução e o efeito de cada contestação.', seoLimit: 'Um cálculo, não uma decisão legal' }),
29
+ tr: makeCopy({ title: 'Kira depozitosu iade hesaplayici', description: 'Onaylanan ve itiraz edilen kesintileri ayırarak kira depozitosu iadesini tahmin edin.', ui: { depositLabel: 'Ödenen depozito', deductionsTitle: 'Depozitodan kesilenler', descriptionLabel: 'Açıklama', amountLabel: 'Tutar', statusLabel: 'Kesinti durumu', confirmedOption: 'Onaylandı', disputedOption: 'İtiraz edildi', addButton: 'Kesinti ekle', resetButton: 'Örneği geri yükle', currencyLabel: 'Para birimi', summaryTitle: 'Onaylanan ve itiraz edilen', estimatedReturnLabel: 'Tahmini iade', undisputedReturnLabel: 'İtiraz edilen kesintiler çıkarılırsa', totalDeductionsLabel: 'Toplam kesinti', confirmedLabel: 'Onaylanan kesintiler', disputedLabel: 'İtiraz edilen kesintiler', returnFlowTitle: 'İade edilecek depozito', returnNode: 'Tahmini iade', copyButton: 'Özeti kopyala', copiedButton: 'Özet kopyalandı', methodTitle: 'Tahmin nasıl çalışır' }, faq: [{ question: 'Bu hesaplayıcı neyi tahmin eder?', answer: 'Kesintileri depozitodan çıkarır ve her itirazın etkisini gösterir.' }, { question: 'Kesintinin yasal olup olmadığını belirler mi?', answer: 'Hayır. Kendi tutarlarınızı hesaplar.' }, { question: 'İtiraz edilen kesinti nedir?', answer: 'Kontrol etmek istediğiniz bir kalemdir.' }], howTo: [{ name: 'Depozitoyu girin', text: 'Depozitoyu ve para birimini seçin.' }, { name: 'Kesintileri ekleyin', text: 'Her kalemin tutarını ve durumunu girin.' }, { name: 'İadeleri karşılaştırın', text: 'Her senaryoyu karşılaştırın.' }, { name: 'Özeti kopyalayın', text: 'Tutarları kopyalayın ve belgeleri kontrol edin.' }], seoTitle: 'Depozito dökümünü anlaşılır bir iade tahminine dönüştürün', seoText: 'Depozito ve kesintileri girerek iade ile her itirazın etkisini görün.', seoLimit: 'Hesaplamadır, hukuki karar değildir' }),
30
+ ja: makeCopy({ title: '\u8cc3\u8cb8\u4fdd\u8a3c\u91d1\u8fd4\u9084\u8a08\u7b97\u6a5f', description: '\u78ba\u5b9a\u3057\u305f\u63a7\u9664\u3068\u4e89\u3063\u3066\u3044\u308b\u63a7\u9664\u3092\u5206\u3051\u3066\u3001\u8cc3\u8cb8\u4fdd\u8a3c\u91d1\u306e\u8fd4\u9084\u984d\u3092\u898b\u7a4d\u3082\u308a\u307e\u3059\u3002', ui: { depositLabel: '\u652f\u6255\u3063\u305f\u4fdd\u8a3c\u91d1', deductionsTitle: '\u4fdd\u8a3c\u91d1\u304b\u3089\u5dee\u3057\u5f15\u304b\u308c\u305f\u9805\u76ee', descriptionLabel: '\u5185\u5bb9', amountLabel: '\u91d1\u984d', statusLabel: '\u63a7\u9664\u306e\u72b6\u614b', confirmedOption: '\u78ba\u5b9a', disputedOption: '\u4e89\u8b70\u4e2d', addButton: '\u63a7\u9664\u3092\u8ffd\u52a0', resetButton: '\u4f8b\u3092\u5fa9\u5143', currencyLabel: '\u901a\u8ca8', summaryTitle: '\u78ba\u5b9a\u3068\u4e89\u8b70\u4e2d', estimatedReturnLabel: '\u8fd4\u9084\u898b\u8fbc\u307f', undisputedReturnLabel: '\u4e89\u8b70\u4e2d\u306e\u63a7\u9664\u3092\u9664\u3044\u305f\u5834\u5408', totalDeductionsLabel: '\u63a7\u9664\u5408\u8a08', confirmedLabel: '\u78ba\u5b9a\u3057\u305f\u63a7\u9664', disputedLabel: '\u4e89\u8b70\u4e2d\u306e\u63a7\u9664', returnFlowTitle: '\u8fd4\u9084\u3055\u308c\u308b\u4fdd\u8a3c\u91d1', returnNode: '\u8fd4\u9084\u898b\u8fbc\u307f', copyButton: '\u6982\u8981\u3092\u30b3\u30d4\u30fc', copiedButton: '\u6982\u8981\u3092\u30b3\u30d4\u30fc\u3057\u307e\u3057\u305f', methodTitle: '\u8a08\u7b97\u65b9\u6cd5' }, faq: [{ question: '\u3053\u306e\u8a08\u7b97\u6a5f\u306f\u4f55\u3092\u898b\u7a4d\u3082\u308a\u307e\u3059\u304b\uff1f', answer: '\u4fdd\u8a3c\u91d1\u304b\u3089\u63a7\u9664\u3092\u5dee\u3057\u5f15\u304d\u3001\u4e89\u8b70\u4e2d\u306e\u9805\u76ee\u306e\u5f71\u97ff\u3092\u8868\u793a\u3057\u307e\u3059\u3002' }, { question: '\u6cd5\u7684\u306a\u5224\u65ad\u3067\u3059\u304b\uff1f', answer: '\u3044\u3048\u3001\u91d1\u984d\u306e\u8a08\u7b97\u3067\u3059\u3002' }, { question: '\u4e89\u8b70\u4e2d\u306e\u9805\u76ee\u3068\u306f\u4f55\u3067\u3059\u304b\uff1f', answer: '\u78ba\u8a8d\u3057\u305f\u3044\u8acb\u6c42\u3067\u3059\u3002' }], howTo: [{ name: '\u4fdd\u8a3c\u91d1\u3092\u5165\u529b', text: '\u4fdd\u8a3c\u91d1\u3068\u901a\u8ca8\u3092\u9078\u3073\u307e\u3059\u3002' }, { name: '\u63a7\u9664\u3092\u8ffd\u52a0', text: '\u5404\u9805\u76ee\u306e\u91d1\u984d\u3068\u72b6\u614b\u3092\u5165\u529b\u3057\u307e\u3059\u3002' }, { name: '\u8fd4\u9084\u984d\u3092\u6bd4\u8f03', text: '\u5404\u30b7\u30ca\u30ea\u30aa\u3092\u6bd4\u8f03\u3057\u307e\u3059\u3002' }, { name: '\u6982\u8981\u3092\u30b3\u30d4\u30fc', text: '\u6570\u5024\u3092\u30e1\u30e2\u306b\u30b3\u30d4\u30fc\u3057\u307e\u3059\u3002' }], seoTitle: '\u4fdd\u8a3c\u91d1\u306e\u660e\u7d30\u3092\u8fd4\u9084\u984d\u306e\u898b\u7a4d\u3082\u308a\u306b\u5909\u3048\u308b', seoText: '\u4fdd\u8a3c\u91d1\u3068\u63a7\u9664\u306e\u660e\u7d30\u3092\u5165\u529b\u3057\u3001\u8fd4\u9084\u984d\u3068\u5404\u9805\u76ee\u306e\u5f71\u97ff\u3092\u78ba\u8a8d\u3067\u304d\u307e\u3059\u3002', seoLimit: '\u6cd5\u5f8b\u306e\u5224\u65ad\u3067\u306f\u306a\u304f\u8a08\u7b97\u3067\u3059' }),
31
+ ko: makeCopy({ title: '\uc784\ub300 \ubcf4\uc99d\uae08 \ubc18\ud658 \uacc4\uc0b0\uae30', description: '\ud655\uc815 \uacf5\uc81c\uc640 \uc774\uc758 \uc81c\uae30 \uacf5\uc81c\ub97c \ub098\ub204\uc5b4 \uc784\ub300 \ubcf4\uc99d\uae08 \ubc18\ud658\uc561\uc744 \uc608\uc0c1\ud569\ub2c8\ub2e4.', ui: { depositLabel: '\ub0a9\ubd80\ud55c \ubcf4\uc99d\uae08', deductionsTitle: '\ubcf4\uc99d\uae08\uc5d0\uc11c \uacf5\uc81c\ub41c \ud56d\ubaa9', descriptionLabel: '\ub0b4\uc6a9', amountLabel: '\uae08\uc561', statusLabel: '\uacf5\uc81c \uc0c1\ud0dc', confirmedOption: '\ud655\uc815', disputedOption: '\uc774\uc758 \uc81c\uae30', addButton: '\uacf5\uc81c \ucd94\uac00', resetButton: '\uc608\uc2dc \ubcf5\uc6d0', currencyLabel: '\ud1b5\ud654', summaryTitle: '\ud655\uc815\uacfc \uc774\uc758 \uc81c\uae30', estimatedReturnLabel: '\uc608\uc0c1 \ubc18\ud658\uc561', undisputedReturnLabel: '\uc774\uc758 \uc81c\uae30 \uacf5\uc81c \uc81c\uc678 \uc2dc', totalDeductionsLabel: '\ucd1d \uacf5\uc81c\uc561', confirmedLabel: '\ud655\uc815 \uacf5\uc81c', disputedLabel: '\uc774\uc758 \uc81c\uae30 \uacf5\uc81c', returnFlowTitle: '\ubc18\ud658\ub420 \ubcf4\uc99d\uae08', returnNode: '\uc608\uc0c1 \ubc18\ud658', copyButton: '\uc694\uc57d \ubcf5\uc0ac', copiedButton: '\uc694\uc57d\uc774 \ubcf5\uc0ac\ub418\uc5c8\uc2b5\ub2c8\ub2e4', methodTitle: '\uacc4\uc0b0 \ubc29\ubc95' }, faq: [{ question: '\uc774 \uacc4\uc0b0\uae30\ub294 \ubb34\uc5c7\uc744 \uc608\uc0c1\ud558\ub098\uc694?', answer: '\ubcf4\uc99d\uae08\uc5d0\uc11c \uacf5\uc81c\uc561\uc744 \ube7c\uace0 \uc774\uc758 \uc81c\uae30 \ud56d\ubaa9\uc758 \uc601\ud5a5\uc744 \ubcf4\uc5ec\uc90d\ub2c8\ub2e4.' }, { question: '\ubc95\uc801 \ud310\ub2e8\uc744 \ud558\ub098\uc694?', answer: '\uc544\ub2c8\uc694. \uc785\ub825\ud55c \uae08\uc561\ub9cc \uacc4\uc0b0\ud569\ub2c8\ub2e4.' }, { question: '\uc774\uc758 \uc81c\uae30 \ud56d\ubaa9\uc774\ub780 \ubb34\uc5c7\uc778\uac00\uc694?', answer: '\ud655\uc778\ud558\uace0 \uc2f6\uc740 \uacf5\uc81c \ud56d\ubaa9\uc785\ub2c8\ub2e4.' }], howTo: [{ name: '\ubcf4\uc99d\uae08 \uc785\ub825', text: '\ubcf4\uc99d\uae08\uacfc \ud1b5\ud654\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4.' }, { name: '\uacf5\uc81c \ucd94\uac00', text: '\uac01 \ud56d\ubaa9\uc758 \uae08\uc561\uacfc \uc0c1\ud0dc\ub97c \uc785\ub825\ud569\ub2c8\ub2e4.' }, { name: '\ubc18\ud658\uc561 \ube44\uad50', text: '\uac01 \uc2dc\ub098\ub9ac\uc624\ub97c \ube44\uad50\ud569\ub2c8\ub2e4.' }, { name: '\uc694\uc57d \ubcf5\uc0ac', text: '\uc218\uce58\ub97c \uba54\ubaa8\uc5d0 \ubcf5\uc0ac\ud569\ub2c8\ub2e4.' }], seoTitle: '\ubcf4\uc99d\uae08 \uba85\uc138\ub97c \ubc18\ud658\uc561 \uc608\uc0c1\uc73c\ub85c \ubc14\uafb8\uae30', seoText: '\ubcf4\uc99d\uae08\uacfc \uacf5\uc81c \ub0b4\uc5ed\uc744 \uc785\ub825\ud558\uba74 \uc608\uc0c1 \ubc18\ud658\uc561\uc744 \ud655\uc778\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.', seoLimit: '\ubc95\uc801 \ud310\ub2e8\uc774 \uc544\ub2cc \uacc4\uc0b0' }),
32
+ ru: makeCopy({ title: '\u041a\u0430\u043b\u044c\u043a\u0443\u043b\u044f\u0442\u043e\u0440 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0430\u0440\u0435\u043d\u0434\u043d\u043e\u0433\u043e \u0437\u0430\u043b\u043e\u0433\u0430', description: '\u041e\u0446\u0435\u043d\u0438\u0442\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0437\u0430\u043b\u043e\u0433\u0430, \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0432 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043d\u043d\u044b\u0435 \u0438 \u043e\u0441\u043f\u0430\u0440\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f.', ui: { depositLabel: '\u0412\u043d\u0435\u0441\u0451\u043d\u043d\u044b\u0439 \u0437\u0430\u043b\u043e\u0433', deductionsTitle: '\u0423\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u0438\u0437 \u0437\u0430\u043b\u043e\u0433\u0430', descriptionLabel: '\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435', amountLabel: '\u0421\u0443\u043c\u043c\u0430', statusLabel: '\u0421\u0442\u0430\u0442\u0443\u0441 \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f', confirmedOption: '\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043e', disputedOption: '\u041e\u0441\u043f\u0430\u0440\u0438\u0432\u0430\u0435\u0442\u0441\u044f', addButton: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435', resetButton: '\u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440', currencyLabel: '\u0412\u0430\u043b\u044e\u0442\u0430', summaryTitle: '\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043e \u0438 \u043e\u0441\u043f\u0430\u0440\u0438\u0432\u0430\u0435\u0442\u0441\u044f', estimatedReturnLabel: '\u0420\u0430\u0441\u0447\u0451\u0442\u043d\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0442', undisputedReturnLabel: '\u0411\u0435\u0437 \u043e\u0441\u043f\u0430\u0440\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0439', totalDeductionsLabel: '\u0412\u0441\u0435 \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f', confirmedLabel: '\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043d\u043d\u044b\u0435', disputedLabel: '\u041e\u0441\u043f\u0430\u0440\u0438\u0432\u0430\u0435\u043c\u044b\u0435', returnFlowTitle: '\u0417\u0430\u043b\u043e\u0433 \u043a \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0443', returnNode: '\u0420\u0430\u0441\u0447\u0451\u0442\u043d\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0442', copyButton: '\u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0434\u043a\u0443', copiedButton: '\u0421\u0432\u043e\u0434\u043a\u0430 \u0441\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0430', methodTitle: '\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0440\u0430\u0441\u0447\u0451\u0442' }, faq: [{ question: '\u0427\u0442\u043e \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442?', answer: '\u041e\u043d \u0432\u044b\u0447\u0438\u0442\u0430\u0435\u0442 \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f \u0438\u0437 \u0437\u0430\u043b\u043e\u0433\u0430.' }, { question: '\u042d\u0442\u043e \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435?', answer: '\u041d\u0435\u0442, \u044d\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0441\u0447\u0451\u0442.' }, { question: '\u0427\u0442\u043e \u0442\u0430\u043a\u043e\u0435 \u0441\u043f\u043e\u0440\u043d\u043e\u0435 \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435?', answer: '\u0421\u0443\u043c\u043c\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c.' }], howTo: [{ name: '\u0412\u0432\u0435\u0441\u0442\u0438 \u0437\u0430\u043b\u043e\u0433', text: '\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u0437\u0430\u043b\u043e\u0433 \u0438 \u0432\u0430\u043b\u044e\u0442\u0443.' }, { name: '\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f', text: '\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u0443\u043c\u043c\u044b \u0438 \u0441\u0442\u0430\u0442\u0443\u0441\u044b.' }, { name: '\u0421\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u0432\u043e\u0437\u0432\u0440\u0430\u0442', text: '\u0421\u0440\u0430\u0432\u043d\u0438\u0442\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438.' }, { name: '\u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0434\u043a\u0443', text: '\u0421\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u0447\u0438\u0441\u043b\u0430.' }], seoTitle: '\u041f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u0435 \u0440\u0430\u0441\u0447\u0451\u0442 \u0437\u0430\u043b\u043e\u0433\u0430 \u0432 \u043f\u043e\u043d\u044f\u0442\u043d\u0443\u044e \u043e\u0446\u0435\u043d\u043a\u0443', seoText: '\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0437\u0430\u043b\u043e\u0433 \u0438 \u0443\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0432\u043e\u0437\u0432\u0440\u0430\u0442 \u0438 \u0432\u043b\u0438\u044f\u043d\u0438\u0435 \u0441\u043f\u043e\u0440\u043e\u0432.', seoLimit: '\u0420\u0430\u0441\u0447\u0451\u0442, \u0430 \u043d\u0435 \u044e\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435' }),
33
+ zh: makeCopy({ title: '\u79df\u8d41\u62bc\u91d1\u9000\u8fd8\u8ba1\u7b97\u5668', description: '\u533a\u5206\u5df2\u786e\u8ba4\u548c\u6709\u4e89\u8bae\u7684\u6263\u6b3e\uff0c\u4f30\u7b97\u79df\u8d41\u62bc\u91d1\u9000\u8fd8\u91d1\u989d\u3002', ui: { depositLabel: '\u5df2\u4ed8\u62bc\u91d1', deductionsTitle: '\u4ece\u62bc\u91d1\u4e2d\u6263\u9664\u7684\u9879\u76ee', descriptionLabel: '\u8bf4\u660e', amountLabel: '\u91d1\u989d', statusLabel: '\u6263\u6b3e\u72b6\u6001', confirmedOption: '\u5df2\u786e\u8ba4', disputedOption: '\u6709\u4e89\u8bae', addButton: '\u6dfb\u52a0\u6263\u6b3e', resetButton: '\u6062\u590d\u793a\u4f8b', currencyLabel: '\u8d27\u5e01', summaryTitle: '\u5df2\u786e\u8ba4\u4e0e\u6709\u4e89\u8bae', estimatedReturnLabel: '\u9884\u8ba1\u9000\u8fd8', undisputedReturnLabel: '\u79fb\u9664\u6709\u4e89\u8bae\u6263\u6b3e\u540e', totalDeductionsLabel: '\u6263\u6b3e\u603b\u989d', confirmedLabel: '\u5df2\u786e\u8ba4\u6263\u6b3e', disputedLabel: '\u6709\u4e89\u8bae\u6263\u6b3e', returnFlowTitle: '\u53ef\u9000\u62bc\u91d1', returnNode: '\u9884\u8ba1\u9000\u8fd8', copyButton: '\u590d\u5236\u6458\u8981', copiedButton: '\u6458\u8981\u5df2\u590d\u5236', methodTitle: '\u8ba1\u7b97\u65b9\u5f0f' }, faq: [{ question: '\u8fd9\u4e2a\u8ba1\u7b97\u5668\u4f30\u7b97\u4ec0\u4e48\uff1f', answer: '\u4ece\u62bc\u91d1\u4e2d\u51cf\u53bb\u6263\u6b3e\uff0c\u5e76\u663e\u793a\u6bcf\u9879\u4e89\u8bae\u7684\u5f71\u54cd\u3002' }, { question: '\u4f1a\u5224\u65ad\u6263\u6b3e\u662f\u5426\u5408\u6cd5\u5417\uff1f', answer: '\u4e0d\u4f1a\uff0c\u53ea\u8ba1\u7b97\u4f60\u8f93\u5165\u7684\u91d1\u989d\u3002' }, { question: '\u4ec0\u4e48\u662f\u6709\u4e89\u8bae\u7684\u6263\u6b3e\uff1f', answer: '\u8fd9\u662f\u4f60\u60f3\u6838\u67e5\u7684\u9879\u76ee\u3002' }], howTo: [{ name: '\u8f93\u5165\u62bc\u91d1', text: '\u8f93\u5165\u62bc\u91d1\u5e76\u9009\u62e9\u8d27\u5e01\u3002' }, { name: '\u6dfb\u52a0\u6263\u6b3e', text: '\u8f93\u5165\u6bcf\u4e2a\u9879\u76ee\u7684\u91d1\u989d\u548c\u72b6\u6001\u3002' }, { name: '\u6bd4\u8f83\u9000\u8fd8\u91d1\u989d', text: '\u6bd4\u8f83\u9884\u8ba1\u9000\u8fd8\u548c\u6bcf\u4e2a\u4e89\u8bae\u573a\u666f\u3002' }, { name: '\u590d\u5236\u6458\u8981', text: '\u590d\u5236\u91d1\u989d\u5e76\u68c0\u67e5\u8bc1\u660e\u6750\u6599\u3002' }], seoTitle: '\u628a\u62bc\u91d1\u660e\u7ec6\u53d8\u6210\u6e05\u6670\u7684\u9000\u8fd8\u4f30\u7b97', seoText: '\u8f93\u5165\u62bc\u91d1\u548c\u6263\u6b3e\u5373\u53ef\u770b\u5230\u9884\u8ba1\u9000\u8fd8\u4ee5\u53ca\u6bcf\u9879\u4e89\u8bae\u7684\u5f71\u54cd\u3002', seoLimit: '\u8fd9\u662f\u8ba1\u7b97\uff0c\u4e0d\u662f\u6cd5\u5f8b\u5224\u65ad' }),
34
+ };
@@ -0,0 +1,3 @@
1
+ import { createRentalDepositContent } from './content-factory';
2
+ import { localizedCopies } from './translations';
3
+ export const content = createRentalDepositContent('zh', localizedCopies.zh!);
@@ -0,0 +1,11 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { rentalDepositReturn } from './entry';
3
+
4
+ export * from './entry';
5
+
6
+ export const RENTAL_DEPOSIT_RETURN_TOOL: ToolDefinition = {
7
+ entry: rentalDepositReturn,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { CURRENCIES, getDefaultCurrency } from './currency';
3
+ import { calculateRentalDepositReturn, type DeductionInput } from './logic';
4
+
5
+ const deductions: DeductionInput[] = [
6
+ { id: 'bills', description: 'Final bills', amount: 84, status: 'confirmed' },
7
+ { id: 'repair', description: 'Wall repair', amount: 165, status: 'disputed' },
8
+ { id: 'cleaning', description: 'Cleaning', amount: 60, status: 'disputed' },
9
+ ];
10
+
11
+ describe('calculateRentalDepositReturn', () => {
12
+ it('supports the shared finance currency set and locale defaults', () => {
13
+ expect(Object.keys(CURRENCIES)).toHaveLength(20);
14
+ expect(getDefaultCurrency('en')).toBe('USD');
15
+ expect(getDefaultCurrency('es')).toBe('EUR');
16
+ expect(getDefaultCurrency('en-GB')).toBe('GBP');
17
+ expect(getDefaultCurrency('ja')).toBe('JPY');
18
+ });
19
+
20
+ it('separates the estimated return from the return without disputed deductions', () => {
21
+ const result = calculateRentalDepositReturn({ currency: 'eur', deposit: 1200, deductions });
22
+ expect(result).toMatchObject({
23
+ totalDeductions: 309,
24
+ confirmedDeductions: 84,
25
+ disputedDeductions: 225,
26
+ estimatedReturn: 891,
27
+ undisputedReturn: 1116,
28
+ });
29
+ });
30
+
31
+ it('creates a scenario for every disputed deduction', () => {
32
+ const result = calculateRentalDepositReturn({ currency: 'USD', deposit: 1200, deductions });
33
+ expect(result?.scenarios).toEqual([
34
+ { deductionId: 'repair', description: 'Wall repair', returnAmount: 1056, additionalReturn: 165 },
35
+ { deductionId: 'cleaning', description: 'Cleaning', returnAmount: 951, additionalReturn: 60 },
36
+ ]);
37
+ });
38
+
39
+ it('does not return a negative balance and flags deductions above the deposit', () => {
40
+ const result = calculateRentalDepositReturn({
41
+ currency: 'GBP',
42
+ deposit: 100,
43
+ deductions: [{ id: 'repair', description: 'Repair', amount: 140, status: 'confirmed' }],
44
+ });
45
+ expect(result).toMatchObject({ estimatedReturn: 0, undisputedReturn: 0, overDeducted: 40 });
46
+ });
47
+
48
+ it('rejects invalid currency, deposit and deduction data', () => {
49
+ expect(calculateRentalDepositReturn({ currency: 'EURO', deposit: 100, deductions })).toBeNull();
50
+ expect(calculateRentalDepositReturn({ currency: 'EUR', deposit: -1, deductions })).toBeNull();
51
+ expect(calculateRentalDepositReturn({ currency: 'EUR', deposit: 100, deductions: [{ ...deductions[0]!, amount: -1 }] })).toBeNull();
52
+ });
53
+ });
@@ -0,0 +1,108 @@
1
+ import { isCurrencyCode, type CurrencyCode } from './currency';
2
+
3
+ export type DeductionStatus = 'confirmed' | 'disputed';
4
+
5
+ export interface DeductionInput {
6
+ id: string;
7
+ description: string;
8
+ amount: number;
9
+ status: DeductionStatus;
10
+ }
11
+
12
+ export interface RentalDepositInput {
13
+ currency: string;
14
+ deposit: number;
15
+ deductions: DeductionInput[];
16
+ }
17
+
18
+ export interface DeductionResult extends DeductionInput {
19
+ shareOfDeposit: number;
20
+ }
21
+
22
+ export interface DisputedScenario {
23
+ deductionId: string;
24
+ description: string;
25
+ returnAmount: number;
26
+ additionalReturn: number;
27
+ }
28
+
29
+ export interface RentalDepositResult {
30
+ currency: CurrencyCode;
31
+ deposit: number;
32
+ deductions: DeductionResult[];
33
+ totalDeductions: number;
34
+ confirmedDeductions: number;
35
+ disputedDeductions: number;
36
+ estimatedReturn: number;
37
+ undisputedReturn: number;
38
+ overDeducted: number;
39
+ scenarios: DisputedScenario[];
40
+ }
41
+
42
+ const roundMoney = (value: number): number => Number(value.toFixed(2));
43
+
44
+ const clampReturn = (deposit: number, deductions: number): number =>
45
+ roundMoney(Math.max(0, deposit - deductions));
46
+
47
+ const isStatus = (value: string): value is DeductionStatus =>
48
+ value === 'confirmed' || value === 'disputed';
49
+
50
+ const isValidDeduction = (deduction: DeductionInput): boolean =>
51
+ deduction.id.trim().length > 0
52
+ && deduction.description.trim().length > 0
53
+ && Number.isFinite(deduction.amount)
54
+ && deduction.amount >= 0
55
+ && isStatus(deduction.status);
56
+
57
+ function summarizeDeductions(input: RentalDepositInput): { total: number; confirmed: number; disputed: number } {
58
+ const total = roundMoney(input.deductions.reduce((sum, deduction) => sum + deduction.amount, 0));
59
+ const confirmed = roundMoney(input.deductions
60
+ .filter((deduction) => deduction.status === 'confirmed')
61
+ .reduce((sum, deduction) => sum + deduction.amount, 0));
62
+ return { total, confirmed, disputed: roundMoney(total - confirmed) };
63
+ }
64
+
65
+ function addShares(input: RentalDepositInput): DeductionResult[] {
66
+ return input.deductions.map((deduction) => ({
67
+ ...deduction,
68
+ shareOfDeposit: input.deposit > 0 ? roundMoney((deduction.amount / input.deposit) * 100) : 0,
69
+ }));
70
+ }
71
+
72
+ function buildScenarios(input: RentalDepositInput, totalDeductions: number, estimatedReturn: number): DisputedScenario[] {
73
+ return input.deductions
74
+ .filter((deduction) => deduction.status === 'disputed')
75
+ .map((deduction) => {
76
+ const returnAmount = clampReturn(input.deposit, totalDeductions - deduction.amount);
77
+ return {
78
+ deductionId: deduction.id,
79
+ description: deduction.description,
80
+ returnAmount,
81
+ additionalReturn: roundMoney(returnAmount - estimatedReturn),
82
+ };
83
+ });
84
+ }
85
+
86
+ export function calculateRentalDepositReturn(input: RentalDepositInput): RentalDepositResult | null {
87
+ const currency = input.currency.trim().toUpperCase();
88
+ if (!isCurrencyCode(currency) || !Number.isFinite(input.deposit) || input.deposit < 0) return null;
89
+ if (input.deductions.some((deduction) => !isValidDeduction(deduction))) return null;
90
+ const { total: totalDeductions, confirmed: confirmedDeductions, disputed: disputedDeductions } = summarizeDeductions(input);
91
+ const estimatedReturn = clampReturn(input.deposit, totalDeductions);
92
+ const undisputedReturn = clampReturn(input.deposit, confirmedDeductions);
93
+ const deductions = addShares(input);
94
+ const scenarios = buildScenarios(input, totalDeductions, estimatedReturn);
95
+
96
+ return {
97
+ currency,
98
+ deposit: roundMoney(input.deposit),
99
+ deductions,
100
+ totalDeductions,
101
+ confirmedDeductions,
102
+ disputedDeductions,
103
+ estimatedReturn,
104
+ undisputedReturn,
105
+ overDeducted: roundMoney(Math.max(0, totalDeductions - input.deposit)),
106
+ scenarios,
107
+ };
108
+ }