diff --git a/client/src/components/Plugins/PluginFrame.tsx b/client/src/components/Plugins/PluginFrame.tsx index d5e6889c8..21e1d11d6 100644 --- a/client/src/components/Plugins/PluginFrame.tsx +++ b/client/src/components/Plugins/PluginFrame.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom' import { useTranslation } from '../../i18n' import { useAuthStore } from '../../store/authStore' import { useSettingsStore } from '../../store/settingsStore' +import { useTripStore } from '../../store/tripStore' import { useToast } from '../shared/Toast' import ConfirmDialog from '../shared/ConfirmDialog' import { pluginsApi } from '../../api/client' @@ -141,6 +142,10 @@ export default function PluginFrame({ pluginId, tripId = null, placeId = null, d const userAvatar = useAuthStore((s) => s.user?.avatar_url ?? null) const isAdmin = useAuthStore((s) => s.user?.role === 'admin') const settings = useSettingsStore((s) => s.settings) + // Plugins format money against a concrete code, so resolve the same chain Costs + // uses: the user's display currency, else the trip's own. + const tripCurrency = useTripStore((s) => s.trip?.currency) + const displayCurrency = (settings.default_currency || tripCurrency || 'EUR').toUpperCase() const [height, setHeight] = useState(null) // A host-rendered ConfirmDialog on the plugin's behalf: sandboxed frames have no // allow-modals and can't overlay the host, so destructive plugin actions get the @@ -177,7 +182,7 @@ export default function PluginFrame({ pluginId, tripId = null, placeId = null, d appearance: readAppearance(), formats: { locale, - currency: settings.default_currency, + currency: displayCurrency, timeFormat: settings.time_format, distanceUnit: settings.distance_unit, temperatureUnit: settings.temperature_unit, diff --git a/client/src/components/Settings/DisplaySettingsTab.tsx b/client/src/components/Settings/DisplaySettingsTab.tsx index 3801830cb..09b9cf2ad 100644 --- a/client/src/components/Settings/DisplaySettingsTab.tsx +++ b/client/src/components/Settings/DisplaySettingsTab.tsx @@ -40,13 +40,18 @@ export default function DisplaySettingsTab(): React.ReactElement { {/* Display currency */}
+ {/* Unset ('') means "no personal preference": Costs then shows each trip in its + own currency, instead of forcing every trip through one display currency. */} { try { await updateSetting('default_currency', String(v)) } catch (e: unknown) { toast.error(e instanceof Error ? e.message : t('common.error')) } }} - options={currenciesWith(settings.default_currency).map(c => ({ value: c, label: `${c} — ${SYMBOLS[c] || c}` }))} + options={[ + { value: '', label: t('settings.currencyTrip') }, + ...currenciesWith(settings.default_currency || '').map(c => ({ value: c, label: `${c} — ${SYMBOLS[c] || c}` })), + ]} searchable />

{t('settings.currencyHint')}

diff --git a/client/src/store/settingsStore.ts b/client/src/store/settingsStore.ts index eba06eb77..113e8ba3b 100644 --- a/client/src/store/settingsStore.ts +++ b/client/src/store/settingsStore.ts @@ -28,7 +28,8 @@ export const useSettingsStore = create((set, get) => ({ default_lng: 2.3522, default_zoom: 10, dark_mode: false, - default_currency: 'USD', + // Empty = no personal display currency, so Costs falls back to the trip's own. + default_currency: '', language: localStorage.getItem('app_language') || 'en', temperature_unit: 'fahrenheit', distance_unit: 'metric', diff --git a/client/src/types.ts b/client/src/types.ts index 56c4b3c16..e6a982ae1 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -110,7 +110,8 @@ export interface Settings { default_lng: number default_zoom: number dark_mode: boolean | string - default_currency: string + /** Display currency for Costs. Empty/null = follow each trip's own currency. */ + default_currency: string | null language: string temperature_unit: string distance_unit?: DistanceUnit diff --git a/client/tests/unit/stores/settingsStore.test.ts b/client/tests/unit/stores/settingsStore.test.ts index cc020f5ff..d4cfa898f 100644 --- a/client/tests/unit/stores/settingsStore.test.ts +++ b/client/tests/unit/stores/settingsStore.test.ts @@ -158,7 +158,10 @@ describe('settingsStore', () => { const state = useSettingsStore.getState(); expect(state.settings.dark_mode).toBe(true); - expect(state.settings.default_currency).toBe('USD'); + expect(state.settings.language).toBe('en'); + // No display currency of their own: Costs then follows each trip's own currency + // rather than forcing every trip through one code. + expect(state.settings.default_currency).toBe(''); }); }); diff --git a/shared/src/i18n/ar/settings.ts b/shared/src/i18n/ar/settings.ts index c240e9112..2d4b46d83 100644 --- a/shared/src/i18n/ar/settings.ts +++ b/shared/src/i18n/ar/settings.ts @@ -290,6 +290,7 @@ const settings: TranslationStrings = { 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', // en-fallback 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'عملة الرحلة', 'settings.passkey.title': 'مفاتيح المرور', 'settings.passkey.description': 'سجّل الدخول بشكل أسرع وأكثر مقاومة للتصيّد باستخدام مفتاح مرور — ببصمة إصبعك أو وجهك أو رمز PIN أو مفتاح أمان مادي. تبقى كلمة المرور كنسخة احتياطية.', diff --git a/shared/src/i18n/br/settings.ts b/shared/src/i18n/br/settings.ts index 8c0e76ef4..d70bba98a 100644 --- a/shared/src/i18n/br/settings.ts +++ b/shared/src/i18n/br/settings.ts @@ -303,6 +303,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Moeda da viagem', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Entre mais rápido e com proteção contra phishing usando uma passkey — sua impressão digital, rosto, PIN ou uma chave de segurança física. Sua senha continua disponível como reserva.', diff --git a/shared/src/i18n/cs/settings.ts b/shared/src/i18n/cs/settings.ts index 623f704c3..dd30317fb 100644 --- a/shared/src/i18n/cs/settings.ts +++ b/shared/src/i18n/cs/settings.ts @@ -297,6 +297,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Měna cesty', 'settings.passkey.title': 'Přístupové klíče', 'settings.passkey.description': 'Přihlašujte se rychleji a s ochranou proti phishingu pomocí přístupového klíče — otiskem prstu, obličejem, PINem nebo hardwarovým klíčem. Vaše heslo zůstává jako záloha.', diff --git a/shared/src/i18n/de/settings.ts b/shared/src/i18n/de/settings.ts index d360d9730..6e8a2c110 100644 --- a/shared/src/i18n/de/settings.ts +++ b/shared/src/i18n/de/settings.ts @@ -305,6 +305,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Währung', 'settings.currencyHint': 'Alle Beträge in Costs werden in diese Währung umgerechnet und angezeigt.', + 'settings.currencyTrip': 'Reisewährung', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Melde dich schneller und phishing-resistent mit einem Passkey an — per Fingerabdruck, Gesicht, PIN oder Hardware-Schlüssel. Dein Passwort bleibt als Backup erhalten.', diff --git a/shared/src/i18n/en/settings.ts b/shared/src/i18n/en/settings.ts index d551b4731..1c9f168a4 100644 --- a/shared/src/i18n/en/settings.ts +++ b/shared/src/i18n/en/settings.ts @@ -309,6 +309,7 @@ const settings: TranslationStrings = { 'settings.mfa.demoBlocked': 'Not available in demo mode', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Trip currency', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Sign in faster and phishing-resistant with a passkey — your fingerprint, face, PIN, or a hardware key. Your password stays as a backup.', diff --git a/shared/src/i18n/es/settings.ts b/shared/src/i18n/es/settings.ts index b00b49d3f..8bf3b8cc6 100644 --- a/shared/src/i18n/es/settings.ts +++ b/shared/src/i18n/es/settings.ts @@ -305,6 +305,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Moneda del viaje', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Inicia sesión más rápido y con protección frente al phishing usando una passkey: tu huella, tu cara, tu PIN o una llave de seguridad física. Tu contraseña sigue disponible como respaldo.', diff --git a/shared/src/i18n/fr/settings.ts b/shared/src/i18n/fr/settings.ts index 6611a982c..15b46064c 100644 --- a/shared/src/i18n/fr/settings.ts +++ b/shared/src/i18n/fr/settings.ts @@ -308,6 +308,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Devise du voyage', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Connectez-vous plus rapidement et de façon résistante au phishing avec une passkey — votre empreinte digitale, votre visage, votre code PIN ou une clé matérielle. Votre mot de passe reste disponible en secours.', diff --git a/shared/src/i18n/gr/settings.ts b/shared/src/i18n/gr/settings.ts index 6f3910e35..1f75e8dd4 100644 --- a/shared/src/i18n/gr/settings.ts +++ b/shared/src/i18n/gr/settings.ts @@ -312,6 +312,7 @@ const settings: TranslationStrings = { 'settings.mfa.demoBlocked': 'Δεν είναι διαθέσιμο σε λειτουργία demo', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Νόμισμα ταξιδιού', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Συνδεθείτε πιο γρήγορα και με προστασία από phishing χρησιμοποιώντας ένα passkey — το δαχτυλικό σας αποτύπωμα, το πρόσωπό σας, ένα PIN ή ένα κλειδί υλικού. Ο κωδικός σας παραμένει ως εφεδρεία.', diff --git a/shared/src/i18n/hu/settings.ts b/shared/src/i18n/hu/settings.ts index c098f6e4c..a33d1d36c 100644 --- a/shared/src/i18n/hu/settings.ts +++ b/shared/src/i18n/hu/settings.ts @@ -304,6 +304,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Az utazás pénzneme', 'settings.passkey.title': 'Passkey-k', 'settings.passkey.description': 'Jelentkezz be gyorsabban és adathalászat-állóan egy passkey-jel — ujjlenyomattal, arccal, PIN-kóddal vagy hardveres kulccsal. A jelszavad tartalékként megmarad.', diff --git a/shared/src/i18n/id/settings.ts b/shared/src/i18n/id/settings.ts index afed07c63..59747ae5b 100644 --- a/shared/src/i18n/id/settings.ts +++ b/shared/src/i18n/id/settings.ts @@ -301,6 +301,7 @@ const settings: TranslationStrings = { 'settings.bookingLabelsHint': 'Menampilkan nama stasiun / bandara di peta. Jika mati, hanya ikon ditampilkan.', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Mata uang perjalanan', 'settings.passkey.title': 'Passkey', 'settings.passkey.description': 'Masuk lebih cepat dan tahan terhadap phishing dengan passkey — sidik jari, wajah, PIN, atau kunci keamanan fisik kamu. Kata sandimu tetap tersedia sebagai cadangan.', diff --git a/shared/src/i18n/it/settings.ts b/shared/src/i18n/it/settings.ts index 02040e3eb..a65c1ffea 100644 --- a/shared/src/i18n/it/settings.ts +++ b/shared/src/i18n/it/settings.ts @@ -303,6 +303,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Valuta del viaggio', 'settings.passkey.title': 'Passkey', 'settings.passkey.description': 'Accedi più velocemente e in modo resistente al phishing con una passkey — la tua impronta digitale, il volto, il PIN o una chiave hardware. La tua password resta come riserva.', diff --git a/shared/src/i18n/ja/settings.ts b/shared/src/i18n/ja/settings.ts index ddf4f7a86..39e7c05eb 100644 --- a/shared/src/i18n/ja/settings.ts +++ b/shared/src/i18n/ja/settings.ts @@ -278,6 +278,7 @@ const settings: TranslationStrings = { 'settings.oauth.badge.machine': 'マシン', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': '旅行の通貨', 'settings.passkey.title': 'パスキー', 'settings.passkey.description': '指紋、顔認証、PIN、またはハードウェアキーを使うパスキーで、より速く、フィッシングに強いサインインができます。パスワードはバックアップとして残ります。', diff --git a/shared/src/i18n/ko/settings.ts b/shared/src/i18n/ko/settings.ts index 23444cd21..a3250db5f 100644 --- a/shared/src/i18n/ko/settings.ts +++ b/shared/src/i18n/ko/settings.ts @@ -294,6 +294,7 @@ const settings: TranslationStrings = { 'settings.oauth.badge.machine': '머신', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': '여행 통화', 'settings.passkey.title': '패스키', 'settings.passkey.description': '지문, 얼굴, PIN 또는 하드웨어 키 같은 패스키로 더 빠르고 피싱에 강하게 로그인하세요. 비밀번호는 백업으로 그대로 유지됩니다.', diff --git a/shared/src/i18n/nl/settings.ts b/shared/src/i18n/nl/settings.ts index b41c8e220..5d92e283e 100644 --- a/shared/src/i18n/nl/settings.ts +++ b/shared/src/i18n/nl/settings.ts @@ -304,6 +304,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Valuta', 'settings.currencyHint': 'Alle bedragen onder Onkosten worden omgerekend naar en weergegeven in deze valuta.', + 'settings.currencyTrip': 'Valuta van de reis', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Log sneller en phishingbestendig in met een passkey — je vingerafdruk, gezicht, pincode of een hardwaresleutel. Je wachtwoord blijft als back-up bestaan.', diff --git a/shared/src/i18n/pl/settings.ts b/shared/src/i18n/pl/settings.ts index 3663ade51..232d9369b 100644 --- a/shared/src/i18n/pl/settings.ts +++ b/shared/src/i18n/pl/settings.ts @@ -301,6 +301,7 @@ const settings: TranslationStrings = { 'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Waluta wyjazdu', 'settings.passkey.title': 'Klucze dostępu', 'settings.passkey.description': 'Loguj się szybciej i z odpornością na phishing za pomocą klucza dostępu — odcisku palca, twarzy, kodu PIN lub klucza sprzętowego. Twoje hasło pozostaje jako zapasowa opcja.', diff --git a/shared/src/i18n/ru/settings.ts b/shared/src/i18n/ru/settings.ts index 6d0fce764..821696610 100644 --- a/shared/src/i18n/ru/settings.ts +++ b/shared/src/i18n/ru/settings.ts @@ -302,6 +302,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': 'Валюта', 'settings.currencyHint': 'Все расходы будут конвертированы и показаны в этой валюте.', + 'settings.currencyTrip': 'Валюта поездки', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Входите быстрее и с защитой от фишинга с помощью passkey — отпечатка пальца, лица, PIN-кода или аппаратного ключа. Ваш пароль остаётся как резервный способ.', diff --git a/shared/src/i18n/sv/settings.ts b/shared/src/i18n/sv/settings.ts index 3995cc6a9..1ce6726a6 100644 --- a/shared/src/i18n/sv/settings.ts +++ b/shared/src/i18n/sv/settings.ts @@ -306,6 +306,7 @@ const settings: TranslationStrings = { 'settings.mfa.demoBlocked': 'Finns inte i demoläge', 'settings.currency': 'Valuta', 'settings.currencyHint': 'Alla belopp under kostnader omräknas till och redovisas i denna valuta.', + 'settings.currencyTrip': 'Resans valuta', 'settings.passkey.title': 'Inloggningsnycklar', 'settings.passkey.description': 'Logga in snabbare och med bättre skydd mot nätfiske med en inloggningsnyckel – ditt fingeravtryck, ditt ansikte, din PIN-kod eller en hårdvarunyckel. Ditt lösenord finns kvar som reserv.', diff --git a/shared/src/i18n/tr/settings.ts b/shared/src/i18n/tr/settings.ts index ffa4d26ff..e2f9009eb 100644 --- a/shared/src/i18n/tr/settings.ts +++ b/shared/src/i18n/tr/settings.ts @@ -300,6 +300,7 @@ const settings: TranslationStrings = { 'settings.oauth.badge.machine': 'makine', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Gezi para birimi', 'settings.passkey.title': 'Passkey’ler', 'settings.passkey.description': 'Passkey ile daha hızlı ve kimlik avına dayanıklı şekilde oturum açın — parmak iziniz, yüzünüz, PIN’iniz veya bir donanım anahtarı. Şifreniz yedek olarak kalır.', diff --git a/shared/src/i18n/uk/settings.ts b/shared/src/i18n/uk/settings.ts index 8c583e8c7..5c4149492 100644 --- a/shared/src/i18n/uk/settings.ts +++ b/shared/src/i18n/uk/settings.ts @@ -302,6 +302,7 @@ const settings: TranslationStrings = { 'settings.oauth.badge.machine': 'машина', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': 'Валюта подорожі', 'settings.passkey.title': 'Passkeys', 'settings.passkey.description': 'Входьте швидше та з захистом від фішингу за допомогою passkey — відбитка пальця, обличчя, PIN-коду або апаратного ключа. Ваш пароль залишається як резервний варіант.', diff --git a/shared/src/i18n/vi/settings.ts b/shared/src/i18n/vi/settings.ts index 16c1b7978..2cd451176 100644 --- a/shared/src/i18n/vi/settings.ts +++ b/shared/src/i18n/vi/settings.ts @@ -308,6 +308,7 @@ const settings: TranslationStrings = { 'settings.mfa.demoBlocked': 'Không có sẵn ở chế độ demo', 'settings.currency': 'Tiền tệ', 'settings.currencyHint': 'Tất cả số tiền trong Chi phí được chuyển đổi sang và hiển thị bằng loại tiền tệ này.', + 'settings.currencyTrip': 'Tiền tệ của chuyến đi', 'settings.passkey.title': 'Mật mã', 'settings.passkey.description': 'Đăng nhập nhanh hơn và chống lừa đảo bằng mật mã — vân tay, khuôn mặt, PIN hoặc khóa phần cứng của bạn. Mật khẩu của bạn vẫn là bản sao lưu.', diff --git a/shared/src/i18n/zh-TW/settings.ts b/shared/src/i18n/zh-TW/settings.ts index 02cc9f4de..e19d755cc 100644 --- a/shared/src/i18n/zh-TW/settings.ts +++ b/shared/src/i18n/zh-TW/settings.ts @@ -285,6 +285,7 @@ const settings: TranslationStrings = { 'settings.bookingLabelsHint': '在地圖上顯示車站 / 機場名稱。關閉時僅顯示圖示。', 'settings.currency': 'Currency', 'settings.currencyHint': 'All amounts in Costs are converted to and shown in this currency.', + 'settings.currencyTrip': '行程貨幣', 'settings.passkey.title': 'Passkey', 'settings.passkey.description': '使用 Passkey 更快登入,並可抵禦網路釣魚——透過你的指紋、臉部、PIN 碼或硬體金鑰。你的密碼仍會保留作為備援。', diff --git a/shared/src/i18n/zh/settings.ts b/shared/src/i18n/zh/settings.ts index 1eb77615e..64838ab32 100644 --- a/shared/src/i18n/zh/settings.ts +++ b/shared/src/i18n/zh/settings.ts @@ -283,6 +283,7 @@ const settings: TranslationStrings = { 'settings.notificationPreferences.ntfy': 'Ntfy', 'settings.currency': '货币', 'settings.currencyHint': '费用中的所有金额都会换算并以此货币显示。', + 'settings.currencyTrip': '行程货币', 'settings.passkey.title': '通行密钥', 'settings.passkey.description': '使用通行密钥更快登录,并能抵御钓鱼攻击——通过指纹、面容、PIN 或硬件密钥验证。你的密码仍可作为备用方式。',