diff --git a/client/src/components/PDF/TripPDF.test.ts b/client/src/components/PDF/TripPDF.test.ts
index 8b41770b3..25ae66311 100644
--- a/client/src/components/PDF/TripPDF.test.ts
+++ b/client/src/components/PDF/TripPDF.test.ts
@@ -286,13 +286,47 @@ describe('downloadTripPDF', () => {
expect(iframe!.srcdoc).toContain('CONF999')
})
- it('FE-COMP-TRIPPDF-016: renders place description and price chip', async () => {
+ it('FE-COMP-TRIPPDF-016: renders place description and a currency-formatted price chip', async () => {
await downloadTripPDF(richArgs)
const iframe = getIframe()
expect(iframe!.srcdoc).toContain('Ancient amphitheater')
- // Price chip: 15 EUR
- expect(iframe!.srcdoc).toContain('15')
- expect(iframe!.srcdoc).toContain('EUR')
+ // richArgs trip has no explicit currency, place has no currency override —
+ // formatMoney falls back to EUR, formatted via Intl (symbol, not literal "EUR" text).
+ expect(iframe!.srcdoc).toContain('15,00')
+ expect(iframe!.srcdoc).toContain('€')
+ })
+
+ it('FE-COMP-TRIPPDF-016b: formats price chip and totals in the trip currency, not EUR', async () => {
+ const usdArgs = {
+ ...richArgs,
+ trip: { ...richArgs.trip, currency: 'USD' },
+ }
+ await downloadTripPDF(usdArgs)
+ const iframe = getIframe()
+ // Place price chip: place has no currency override, falls back to trip.currency (USD).
+ expect(iframe!.srcdoc).toContain('$15.00')
+ // No literal "EUR" text should leak into a USD trip's export.
+ expect(iframe!.srcdoc).not.toContain('EUR')
+ // Price chip icon must stay currency-neutral — not the euro-shaped glyph.
+ expect(iframe!.srcdoc).not.toContain('M14 5c-3.87 0-7 3.13-7 7s3.13 7 7 7c2.17 0 4.1-.99 5.4-2.55')
+ })
+
+ it('FE-COMP-TRIPPDF-016c: a place with its own currency overrides the trip currency for its price chip', async () => {
+ const mixedArgs = {
+ ...richArgs,
+ trip: { ...richArgs.trip, currency: 'EUR' },
+ assignments: {
+ '10': [{
+ ...assignmentForDay,
+ place: { ...placeWithDetails, currency: 'JPY', price: '1500' },
+ }],
+ } as any,
+ }
+ await downloadTripPDF(mixedArgs)
+ const iframe = getIframe()
+ // JPY is a zero-decimal currency (currencyDecimals) and uses its own symbol, not EUR.
+ // Note: Intl renders JPY with the fullwidth yen sign (U+FFE5 "¥"), not U+00A5 "¥".
+ expect(iframe!.srcdoc).toContain('¥1,500')
})
it('FE-COMP-TRIPPDF-017: renders trip description on cover', async () => {
diff --git a/client/src/components/PDF/TripPDF.tsx b/client/src/components/PDF/TripPDF.tsx
index 76139379d..32155aded 100644
--- a/client/src/components/PDF/TripPDF.tsx
+++ b/client/src/components/PDF/TripPDF.tsx
@@ -5,7 +5,7 @@ import { FileText, Info, Clock, MapPin, Navigation, Train, Plane, Bus, Car, Ship
import { accommodationsApi, mapsApi, pluginsApi } from '../../api/client'
import type { Trip, Day, Place, Category, AssignmentsMap, DayNote } from '../../types'
import { isDayInAccommodationRange, getDayOrder } from '../../utils/dayOrder'
-import { splitReservationDateTime } from '../../utils/formatters'
+import { formatMoney, splitReservationDateTime } from '../../utils/formatters'
import { getFlightLegs, getTrainLegs } from '../../utils/flightLegs'
function renderLucideIcon(icon:LucideIcon, props = {}) {
@@ -40,7 +40,7 @@ const svgPin = ``
const svgClock2= ``
const svgCheck = ``
-const svgEuro = ``
+const svgMoney = ``
function escHtml(str) {
if (!str) return ''
@@ -92,9 +92,9 @@ function longDateRange(days, locale) {
return `${f.toLocaleDateString(locale, { day: 'numeric', month: 'long', timeZone: 'UTC' })} – ${l.toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' })}`
}
-function dayCost(assignments, dayId, locale) {
+function dayCost(assignments, dayId, locale, currency) {
const total = (assignments[String(dayId)] || []).reduce((s, a) => s + (parseFloat(a.place?.price) || 0), 0)
- return total > 0 ? `${total.toLocaleString(locale)} EUR` : null
+ return total > 0 ? formatMoney(total, currency, locale) : null
}
// Pre-fetch place photos for all assigned places.
@@ -204,7 +204,7 @@ export async function downloadTripPDF({ trip, days, places, assignments, categor
const daysHtml = sorted.map((day, di) => {
const assigned = assignments[String(day.id)] || []
const notes = (dayNotes || []).filter(n => n.day_id === day.id)
- const cost = dayCost(assignments, day.id, loc)
+ const cost = dayCost(assignments, day.id, loc, trip.currency)
// Reservations for this day (hotel rendered via accommodations block; car middle-phase rendered in sidebar header only)
const dayReservations = pdfGetTransportForDay(day.id)
@@ -320,7 +320,7 @@ export async function downloadTripPDF({ trip, days, places, assignments, categor
const chips = [
place.place_time ? `${svgClock}${escHtml(place.place_time)}` : '',
- place.price && parseFloat(place.price) > 0 ? `${svgEuro}${Number(place.price).toLocaleString(loc)} EUR` : '',
+ place.price && parseFloat(place.price) > 0 ? `${svgMoney}${formatMoney(Number(place.price), place.currency || trip.currency, loc)}` : '',
].filter(Boolean).join('')
return `
@@ -625,7 +625,7 @@ export async function downloadTripPDF({ trip, days, places, assignments, categor
${escHtml(tr('pdf.planned'))}
${totalCost > 0 ? `
-
${totalCost.toLocaleString(loc)}
+
${formatMoney(totalCost, trip.currency, loc)}
${escHtml(tr('pdf.costLabel'))}
` : ''}
diff --git a/shared/src/i18n/ar/pdf.ts b/shared/src/i18n/ar/pdf.ts
index 3e2936ecc..86a7a9423 100644
--- a/shared/src/i18n/ar/pdf.ts
+++ b/shared/src/i18n/ar/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'خطة السفر',
'pdf.planned': 'مخطط',
- 'pdf.costLabel': 'التكلفة EUR',
+ 'pdf.costLabel': 'التكلفة',
'pdf.preview': 'معاينة PDF',
'pdf.saveAsPdf': 'حفظ كـ PDF',
};
diff --git a/shared/src/i18n/br/pdf.ts b/shared/src/i18n/br/pdf.ts
index a890a14dc..7ca63ccb4 100644
--- a/shared/src/i18n/br/pdf.ts
+++ b/shared/src/i18n/br/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Plano de viagem',
'pdf.planned': 'Planejado',
- 'pdf.costLabel': 'Custo (EUR)',
+ 'pdf.costLabel': 'Custo',
'pdf.preview': 'Pré-visualização do PDF',
'pdf.saveAsPdf': 'Salvar como PDF',
};
diff --git a/shared/src/i18n/cs/pdf.ts b/shared/src/i18n/cs/pdf.ts
index dae561e75..cce6e43e2 100644
--- a/shared/src/i18n/cs/pdf.ts
+++ b/shared/src/i18n/cs/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Cestovní plán',
'pdf.planned': 'Naplánováno',
- 'pdf.costLabel': 'Náklady EUR',
+ 'pdf.costLabel': 'Náklady',
'pdf.preview': 'Náhled PDF',
'pdf.saveAsPdf': 'Uložit jako PDF',
};
diff --git a/shared/src/i18n/de/pdf.ts b/shared/src/i18n/de/pdf.ts
index 003832ea6..a259c54ad 100644
--- a/shared/src/i18n/de/pdf.ts
+++ b/shared/src/i18n/de/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Reiseplan',
'pdf.planned': 'Eingeplant',
- 'pdf.costLabel': 'Kosten EUR',
+ 'pdf.costLabel': 'Kosten',
'pdf.preview': 'PDF Vorschau',
'pdf.saveAsPdf': 'Als PDF speichern',
};
diff --git a/shared/src/i18n/en/pdf.ts b/shared/src/i18n/en/pdf.ts
index 3ee899f64..1dcedfea0 100644
--- a/shared/src/i18n/en/pdf.ts
+++ b/shared/src/i18n/en/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Travel Plan',
'pdf.planned': 'Planned',
- 'pdf.costLabel': 'Cost EUR',
+ 'pdf.costLabel': 'Cost',
'pdf.preview': 'PDF Preview',
'pdf.saveAsPdf': 'Save as PDF',
};
diff --git a/shared/src/i18n/es/pdf.ts b/shared/src/i18n/es/pdf.ts
index 10f5dd25c..e3eaafb7f 100644
--- a/shared/src/i18n/es/pdf.ts
+++ b/shared/src/i18n/es/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Plan de viaje',
'pdf.planned': 'Planificado',
- 'pdf.costLabel': 'Coste EUR',
+ 'pdf.costLabel': 'Coste',
'pdf.preview': 'Vista previa PDF',
'pdf.saveAsPdf': 'Guardar como PDF',
};
diff --git a/shared/src/i18n/fr/pdf.ts b/shared/src/i18n/fr/pdf.ts
index c4a493eea..b717f9822 100644
--- a/shared/src/i18n/fr/pdf.ts
+++ b/shared/src/i18n/fr/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Plan de voyage',
'pdf.planned': 'Planifié',
- 'pdf.costLabel': 'Coût EUR',
+ 'pdf.costLabel': 'Coût',
'pdf.preview': 'Aperçu PDF',
'pdf.saveAsPdf': 'Enregistrer en PDF',
};
diff --git a/shared/src/i18n/gr/pdf.ts b/shared/src/i18n/gr/pdf.ts
index 8915c1996..0b616f89e 100644
--- a/shared/src/i18n/gr/pdf.ts
+++ b/shared/src/i18n/gr/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Ταξιδιωτικό Πρόγραμμα',
'pdf.planned': 'Προγραμματισμένο',
- 'pdf.costLabel': 'Κόστος EUR',
+ 'pdf.costLabel': 'Κόστος',
'pdf.preview': 'Προεπισκόπηση PDF',
'pdf.saveAsPdf': 'Αποθήκευση ως PDF',
};
diff --git a/shared/src/i18n/id/pdf.ts b/shared/src/i18n/id/pdf.ts
index c20334b99..f63e35b5f 100644
--- a/shared/src/i18n/id/pdf.ts
+++ b/shared/src/i18n/id/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Rencana Perjalanan',
'pdf.planned': 'Direncanakan',
- 'pdf.costLabel': 'Biaya EUR',
+ 'pdf.costLabel': 'Biaya',
'pdf.preview': 'Pratinjau PDF',
'pdf.saveAsPdf': 'Simpan sebagai PDF',
};
diff --git a/shared/src/i18n/it/pdf.ts b/shared/src/i18n/it/pdf.ts
index 57c07fd70..e070d6b3a 100644
--- a/shared/src/i18n/it/pdf.ts
+++ b/shared/src/i18n/it/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Programma di viaggio',
'pdf.planned': 'Programmato',
- 'pdf.costLabel': 'Costo EUR',
+ 'pdf.costLabel': 'Costo',
'pdf.preview': 'Anteprima PDF',
'pdf.saveAsPdf': 'Salva come PDF',
};
diff --git a/shared/src/i18n/ja/pdf.ts b/shared/src/i18n/ja/pdf.ts
index 5dd39fda5..77b45b604 100644
--- a/shared/src/i18n/ja/pdf.ts
+++ b/shared/src/i18n/ja/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': '旅行計画',
'pdf.planned': '予定',
- 'pdf.costLabel': '費用(EUR)',
+ 'pdf.costLabel': '費用',
'pdf.preview': 'PDFプレビュー',
'pdf.saveAsPdf': 'PDFとして保存',
};
diff --git a/shared/src/i18n/ko/pdf.ts b/shared/src/i18n/ko/pdf.ts
index f6ca14170..58950e70f 100644
--- a/shared/src/i18n/ko/pdf.ts
+++ b/shared/src/i18n/ko/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': '여행 계획',
'pdf.planned': '계획됨',
- 'pdf.costLabel': '비용 (원)',
+ 'pdf.costLabel': '비용',
'pdf.preview': 'PDF 미리보기',
'pdf.saveAsPdf': 'PDF로 저장',
};
diff --git a/shared/src/i18n/nl/pdf.ts b/shared/src/i18n/nl/pdf.ts
index 89cc8a246..751200f91 100644
--- a/shared/src/i18n/nl/pdf.ts
+++ b/shared/src/i18n/nl/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Reisplan',
'pdf.planned': 'Gepland',
- 'pdf.costLabel': 'Kosten EUR',
+ 'pdf.costLabel': 'Kosten',
'pdf.preview': 'PDF-voorbeeld',
'pdf.saveAsPdf': 'Opslaan als PDF',
};
diff --git a/shared/src/i18n/pl/pdf.ts b/shared/src/i18n/pl/pdf.ts
index 3b9f89e64..45863f772 100644
--- a/shared/src/i18n/pl/pdf.ts
+++ b/shared/src/i18n/pl/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Plan podróży',
'pdf.planned': 'Zaplanowane',
- 'pdf.costLabel': 'Koszt w EUR',
+ 'pdf.costLabel': 'Koszt',
'pdf.preview': 'Podgląd PDF',
'pdf.saveAsPdf': 'Zapisz jako PDF',
};
diff --git a/shared/src/i18n/ru/pdf.ts b/shared/src/i18n/ru/pdf.ts
index a8c02d050..38e42597b 100644
--- a/shared/src/i18n/ru/pdf.ts
+++ b/shared/src/i18n/ru/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'План поездки',
'pdf.planned': 'Запланировано',
- 'pdf.costLabel': 'Стоимость EUR',
+ 'pdf.costLabel': 'Стоимость',
'pdf.preview': 'Предпросмотр PDF',
'pdf.saveAsPdf': 'Сохранить как PDF',
};
diff --git a/shared/src/i18n/sv/pdf.ts b/shared/src/i18n/sv/pdf.ts
index 56756ce01..18e57da00 100644
--- a/shared/src/i18n/sv/pdf.ts
+++ b/shared/src/i18n/sv/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Resplan',
'pdf.planned': 'Planerat',
- 'pdf.costLabel': 'Kostnad EUR',
+ 'pdf.costLabel': 'Kostnad',
'pdf.preview': 'Förhandsgranskning av PDF',
'pdf.saveAsPdf': 'Spara som PDF',
};
diff --git a/shared/src/i18n/tr/pdf.ts b/shared/src/i18n/tr/pdf.ts
index f38bb7552..d0c67970b 100644
--- a/shared/src/i18n/tr/pdf.ts
+++ b/shared/src/i18n/tr/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Seyahat Planı',
'pdf.planned': 'Planlandı',
- 'pdf.costLabel': 'Maliyet EUR',
+ 'pdf.costLabel': 'Maliyet',
'pdf.preview': 'PDF Önizleme',
'pdf.saveAsPdf': 'PDF olarak Kaydet',
};
diff --git a/shared/src/i18n/uk/pdf.ts b/shared/src/i18n/uk/pdf.ts
index 30073b81a..415d46a00 100644
--- a/shared/src/i18n/uk/pdf.ts
+++ b/shared/src/i18n/uk/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'План поїздки',
'pdf.planned': 'Заплановано',
- 'pdf.costLabel': 'Вартість EUR',
+ 'pdf.costLabel': 'Вартість',
'pdf.preview': 'Попередній перегляд PDF',
'pdf.saveAsPdf': 'Зберегти як PDF',
};
diff --git a/shared/src/i18n/vi/pdf.ts b/shared/src/i18n/vi/pdf.ts
index 68decf5d4..d8bfe1618 100644
--- a/shared/src/i18n/vi/pdf.ts
+++ b/shared/src/i18n/vi/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': 'Kế hoạch du lịch',
'pdf.planned': 'Đã lên kế hoạch',
- 'pdf.costLabel': 'Chi phí EUR',
+ 'pdf.costLabel': 'Chi phí',
'pdf.preview': 'PDF Xem trước',
'pdf.saveAsPdf': 'Lưu dưới dạng PDF',
};
diff --git a/shared/src/i18n/zh-TW/pdf.ts b/shared/src/i18n/zh-TW/pdf.ts
index 9636e197e..8c0e4bcd7 100644
--- a/shared/src/i18n/zh-TW/pdf.ts
+++ b/shared/src/i18n/zh-TW/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': '旅行計劃',
'pdf.planned': '已規劃',
- 'pdf.costLabel': '費用 EUR',
+ 'pdf.costLabel': '費用',
'pdf.preview': 'PDF 預覽',
'pdf.saveAsPdf': '儲存為 PDF',
};
diff --git a/shared/src/i18n/zh/pdf.ts b/shared/src/i18n/zh/pdf.ts
index 2eb2e11cf..b7634db07 100644
--- a/shared/src/i18n/zh/pdf.ts
+++ b/shared/src/i18n/zh/pdf.ts
@@ -3,7 +3,7 @@ import type { TranslationStrings } from '../types';
const pdf: TranslationStrings = {
'pdf.travelPlan': '旅行计划',
'pdf.planned': '已规划',
- 'pdf.costLabel': '费用 EUR',
+ 'pdf.costLabel': '费用',
'pdf.preview': 'PDF 预览',
'pdf.saveAsPdf': '保存为 PDF',
};