Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/lib/currency.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { CURRENCIES, isValidExchangeRates, type Currency, type CurrencyInfo } from "./currency";
import { CURRENCIES, currencyForLanguages, isValidExchangeRates, type Currency, type CurrencyInfo } from "./currency";

describe("CURRENCIES", () => {
it("is a non-empty array", () => {
Expand Down Expand Up @@ -94,3 +94,30 @@ describe("isValidExchangeRates", () => {
expect(isValidExchangeRates("nope")).toBe(false);
});
});

describe("currencyForLanguages", () => {
it("keeps EUR for a Spanish list that ends in en-US (Android WebView)", () => {
expect(currencyForLanguages(["es-ES", "en-US"])).toBe("EUR");
expect(currencyForLanguages(["es-ES", "es", "en-US", "en"])).toBe("EUR");
});

it("uses the first region's currency", () => {
expect(currencyForLanguages(["en-US"])).toBe("USD");
expect(currencyForLanguages(["en-GB", "en-US"])).toBe("GBP");
expect(currencyForLanguages(["de-CH", "de-DE"])).toBe("CHF");
});

it("skips languages without a region", () => {
expect(currencyForLanguages(["pl", "pl-PL"])).toBe("PLN");
});

it("reads the region after a script subtag", () => {
expect(currencyForLanguages(["zh-Hant-TW"])).toBe("TWD");
});

it("falls back to EUR", () => {
expect(currencyForLanguages([])).toBe("EUR");
expect(currencyForLanguages(["es"])).toBe("EUR");
expect(currencyForLanguages(["not a locale"])).toBe("EUR");
});
});
27 changes: 22 additions & 5 deletions src/lib/currency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,33 @@ const REGION_TO_CURRENCY: Record<string, Currency> = {
AU: "AUD", NZ: "NZD",
};

function detectBrowserCurrency(): Currency {
if (typeof navigator === "undefined") return "EUR";
const langs = navigator.languages ?? [navigator.language];
function regionOf(lang: string): string | undefined {
try {
return new Intl.Locale(lang).region?.toUpperCase();
} catch {
return undefined;
}
}

/**
* The first language that names a region decides: that region's currency, or
* EUR when the region is not in the map (Spain, France, ...). Later languages
* never override it. Android's WebView appends en-US to every list, so
* ["es-ES", "en-US"] must stay EUR.
*/
export function currencyForLanguages(langs: readonly string[]): Currency {
for (const lang of langs) {
const region = lang.split("-")[1]?.toUpperCase();
if (region && region in REGION_TO_CURRENCY) return REGION_TO_CURRENCY[region];
const region = regionOf(lang);
if (region) return REGION_TO_CURRENCY[region] ?? "EUR";
}
return "EUR";
}

function detectBrowserCurrency(): Currency {
if (typeof navigator === "undefined") return "EUR";
return currencyForLanguages(navigator.languages ?? [navigator.language]);
}

// ---------------------------------------------------------------------------
// Exchange rates (ECB publishes ~30 currencies; others won't convert)
// ---------------------------------------------------------------------------
Expand Down
Loading