From 0c391a8ac075ff7bfb3d6305e5f087e9a98b7b5e Mon Sep 17 00:00:00 2001
From: myelinated-wackerow
<263208946+myelinated-wackerow@users.noreply.github.com>
Date: Fri, 3 Apr 2026 22:49:21 +0000
Subject: [PATCH 1/3] feat(i18n): localize event location country names
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add i18n-iso-countries package and country translation
utilities to geography.ts. Event locations on community
events pages and homepage now display translated country
names (e.g., "Denver, USA" -> "Denver, アメリカ合衆国").
- getCountryTranslation(): reusable country name lookup
- localizeLocation(): parses "City, Country" and translates
- Updated EventCard, ContinentTabs, and homepage events
- City names remain in Latin script (country-only MVP)
Co-Authored-By: Claude Opus 4.6
Co-Authored-By: wackerow <54227730+wackerow@users.noreply.github.com>
---
.../events/_components/ContinentTabs.tsx | 5 +-
.../events/_components/EventCard.tsx | 9 +--
app/[locale]/page.tsx | 3 +-
package.json | 1 +
pnpm-lock.yaml | 16 +++++
src/lib/utils/geography.ts | 72 +++++++++++++++++++
6 files changed, 100 insertions(+), 6 deletions(-)
diff --git a/app/[locale]/community/events/_components/ContinentTabs.tsx b/app/[locale]/community/events/_components/ContinentTabs.tsx
index a63b383b471..652bd86dc6c 100644
--- a/app/[locale]/community/events/_components/ContinentTabs.tsx
+++ b/app/[locale]/community/events/_components/ContinentTabs.tsx
@@ -22,6 +22,7 @@ import { Tag } from "@/components/ui/tag"
import { cn } from "@/lib/utils/cn"
import { formatDateRange } from "@/lib/utils/date"
+import { localizeLocation } from "@/lib/utils/geography"
import { TAG_STATUS_MAPPING } from "../utils"
@@ -174,7 +175,9 @@ export default function ContinentTabs({
{event.title}
- {event.location}
+
+ {localizeLocation(event.location, locale)}
+
diff --git a/app/[locale]/community/events/_components/EventCard.tsx b/app/[locale]/community/events/_components/EventCard.tsx
index e0d945b0f7a..7d67283efe0 100644
--- a/app/[locale]/community/events/_components/EventCard.tsx
+++ b/app/[locale]/community/events/_components/EventCard.tsx
@@ -8,6 +8,7 @@ import { Tag } from "@/components/ui/tag"
import { cn } from "@/lib/utils/cn"
import { formatDate, formatDateRange } from "@/lib/utils/date"
+import { localizeLocation } from "@/lib/utils/geography"
import { TAG_STATUS_MAPPING } from "../utils"
@@ -15,7 +16,7 @@ interface EventCardProps {
event: EventItem
variant?: "grid" | "highlight"
className?: string
- locale?: string
+ locale: string
showTypeTag?: boolean
customEventOptions?: MatomoEventOptions
}
@@ -72,7 +73,7 @@ function EventCardGrid({
{event.title}
{formattedDate && {formattedDate}
}
- {event.location}
+ {localizeLocation(event.location, locale)}
@@ -113,7 +114,7 @@ function EventCardHighlight({
{event.title}
-
{event.location}
+
{localizeLocation(event.location, locale)}
{formatDateRange(event.startTime, event.endTime, locale)}
@@ -128,7 +129,7 @@ export default function EventCard({
event,
variant,
className,
- locale = "en",
+ locale,
showTypeTag,
customEventOptions,
}: EventCardProps) {
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
index 36bf989131f..fa6fe8ccd28 100644
--- a/app/[locale]/page.tsx
+++ b/app/[locale]/page.tsx
@@ -59,6 +59,7 @@ import { parseAppsOfTheWeek } from "@/lib/utils/apps"
import { cn } from "@/lib/utils/cn"
import { formatDateRange } from "@/lib/utils/date"
import { getDirection } from "@/lib/utils/direction"
+import { localizeLocation } from "@/lib/utils/geography"
import { getMetadata } from "@/lib/utils/metadata"
import { formatPriceUSD } from "@/lib/utils/numbers"
import { polishRSSList } from "@/lib/utils/rss"
@@ -850,7 +851,7 @@ const Page = async (props: { params: Promise
}) => {
})}
- {location}
+ {localizeLocation(location, locale)}
diff --git a/package.json b/package.json
index 990f516f8ec..2aa0bfe5efe 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
"howler": "^2.2.4",
"html-react-parser": "^5.2.17",
"humanize-duration": "^3.33.1",
+ "i18n-iso-countries": "^7.14.0",
"lodash": "^4.18.1",
"lucide-react": "^0.516.0",
"motion": "^12.36.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 48188d95bc8..dd86676d46d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -147,6 +147,9 @@ importers:
humanize-duration:
specifier: ^3.33.1
version: 3.33.1
+ i18n-iso-countries:
+ specifier: ^7.14.0
+ version: 7.14.0
lodash:
specifier: ^4.18.1
version: 4.18.1
@@ -6474,6 +6477,9 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+ diacritics@1.3.0:
+ resolution: {integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==}
+
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
@@ -7456,6 +7462,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ i18n-iso-countries@7.14.0:
+ resolution: {integrity: sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==}
+ engines: {node: '>= 12'}
+
icss-utils@5.1.0:
resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
engines: {node: ^10 || ^12 || >= 14}
@@ -19379,6 +19389,8 @@ snapshots:
dependencies:
dequal: 2.0.3
+ diacritics@1.3.0: {}
+
didyoumean@1.2.2: {}
diff@4.0.2: {}
@@ -20691,6 +20703,10 @@ snapshots:
husky@9.1.7: {}
+ i18n-iso-countries@7.14.0:
+ dependencies:
+ diacritics: 1.3.0
+
icss-utils@5.1.0(postcss@8.5.4):
dependencies:
postcss: 8.5.4
diff --git a/src/lib/utils/geography.ts b/src/lib/utils/geography.ts
index 053e5a1af71..31e4b8ee073 100644
--- a/src/lib/utils/geography.ts
+++ b/src/lib/utils/geography.ts
@@ -1,3 +1,5 @@
+import countries from "i18n-iso-countries"
+
import type { Continent } from "@/lib/types"
// Continent → countries mapping for location parsing
@@ -106,3 +108,73 @@ export function parseLocationToContinent(location: string): Continent | null {
if (!country) return null
return COUNTRY_TO_CONTINENT[country] || null
}
+
+/**
+ * Aliases for country names that i18n-iso-countries doesn't recognize.
+ * Maps free-text names from external data to forms the package understands.
+ */
+const COUNTRY_NAME_ALIASES: Record = {
+ "Hong Kong SAR": "Hong Kong",
+ // Add more here as needed
+}
+
+/**
+ * Translate an English country name into the target locale.
+ *
+ * Accepts informal English country names (e.g., "USA", "United States",
+ * "Hong Kong SAR") and returns the localized name.
+ *
+ * @param country - English country name (informal forms accepted)
+ * @param locale - Target locale code (e.g., "ja", "es", "ar")
+ * @returns Localized country name, or the original string if not recognized
+ */
+export function getCountryTranslation(
+ country: string,
+ locale: string
+): string {
+ if (!country) return country
+
+ const normalized = COUNTRY_NAME_ALIASES[country] ?? country
+ const code = countries.getAlpha2Code(normalized, "en")
+ if (!code) return country
+
+ return countries.getName(code, locale) ?? country
+}
+
+/**
+ * Localize an event location string by translating the country portion.
+ *
+ * Parses "City, Country" format, translates the country into the target
+ * locale, and reassembles. City names are left in their original script.
+ *
+ * Returns the original string unchanged if:
+ * - The location is "Online" (handled separately by i18n keys)
+ * - The country cannot be identified or translated
+ * - The locale is "en" (no translation needed)
+ *
+ * @param location - Raw location string (e.g., "Denver, USA")
+ * @param locale - Target locale code (e.g., "ja", "es", "ar")
+ * @returns Localized location string (e.g., "Denver, アメリカ合衆国")
+ */
+export function localizeLocation(location: string, locale: string): string {
+ if (!location || locale === "en") return location
+
+ if (location.toLowerCase() === "online") return location
+
+ const parts = location.split(/,\s*/)
+ const rawCountry = parts[parts.length - 1].trim()
+
+ const localizedCountry = getCountryTranslation(rawCountry, locale)
+
+ // Country wasn't translated -- return original
+ if (localizedCountry === rawCountry) return location
+
+ if (parts.length === 1) {
+ // No comma -- the whole string was the country (e.g., "Hong Kong SAR")
+ return localizedCountry
+ }
+
+ // Reassemble: "City, TranslatedCountry"
+ const city = parts.slice(0, -1).join(", ")
+ return `${city}, ${localizedCountry}`
+}
From 99d6cd63d95fa50a0be482053e792ec1387f2e63 Mon Sep 17 00:00:00 2001
From: myelinated-wackerow
<263208946+myelinated-wackerow@users.noreply.github.com>
Date: Mon, 20 Apr 2026 11:45:21 -0700
Subject: [PATCH 2/3] fix: localize event locations server-side
Pre-translate the event location field at the server level
(in mapEventTranslations and getMeetupGroups) rather than
calling localizeLocation inside client components. Client-side
i18n-iso-countries needs locale data registered, which would
bloat the bundle. Doing translation server-side keeps client
components dumb and keeps the bundle lean.
Fixes untranslated locations on /community/events/ search
results and /community/events/meetups/.
Co-Authored-By: Claude Opus 4.6
Co-Authored-By: wackerow <54227730+wackerow@users.noreply.github.com>
---
.../events/_components/ContinentTabs.tsx | 5 +----
.../community/events/_components/EventCard.tsx | 5 ++---
.../community/events/conferences/page.tsx | 2 +-
app/[locale]/community/events/meetups/page.tsx | 4 ++--
app/[locale]/community/events/page.tsx | 4 ++--
app/[locale]/community/events/search/page.tsx | 2 +-
app/[locale]/community/events/utils.ts | 17 +++++++++++------
app/[locale]/page.tsx | 9 ++++++---
src/lib/utils/geography.ts | 10 +++++-----
9 files changed, 31 insertions(+), 27 deletions(-)
diff --git a/app/[locale]/community/events/_components/ContinentTabs.tsx b/app/[locale]/community/events/_components/ContinentTabs.tsx
index 652bd86dc6c..a63b383b471 100644
--- a/app/[locale]/community/events/_components/ContinentTabs.tsx
+++ b/app/[locale]/community/events/_components/ContinentTabs.tsx
@@ -22,7 +22,6 @@ import { Tag } from "@/components/ui/tag"
import { cn } from "@/lib/utils/cn"
import { formatDateRange } from "@/lib/utils/date"
-import { localizeLocation } from "@/lib/utils/geography"
import { TAG_STATUS_MAPPING } from "../utils"
@@ -175,9 +174,7 @@ export default function ContinentTabs({
{event.title}
-
- {localizeLocation(event.location, locale)}
-
+ {event.location}
diff --git a/app/[locale]/community/events/_components/EventCard.tsx b/app/[locale]/community/events/_components/EventCard.tsx
index 7d67283efe0..bf1f02b3cd3 100644
--- a/app/[locale]/community/events/_components/EventCard.tsx
+++ b/app/[locale]/community/events/_components/EventCard.tsx
@@ -8,7 +8,6 @@ import { Tag } from "@/components/ui/tag"
import { cn } from "@/lib/utils/cn"
import { formatDate, formatDateRange } from "@/lib/utils/date"
-import { localizeLocation } from "@/lib/utils/geography"
import { TAG_STATUS_MAPPING } from "../utils"
@@ -73,7 +72,7 @@ function EventCardGrid({
{event.title}
{formattedDate && {formattedDate}
}
- {localizeLocation(event.location, locale)}
+ {event.location}
@@ -114,7 +113,7 @@ function EventCardHighlight({
{event.title}
-
{localizeLocation(event.location, locale)}
+
{event.location}
{formatDateRange(event.startTime, event.endTime, locale)}
diff --git a/app/[locale]/community/events/conferences/page.tsx b/app/[locale]/community/events/conferences/page.tsx
index 3ec263e3f29..c1a9032ea5b 100644
--- a/app/[locale]/community/events/conferences/page.tsx
+++ b/app/[locale]/community/events/conferences/page.tsx
@@ -32,7 +32,7 @@ const Page = async (props: { params: Promise
}) => {
const t = await getTranslations("page-community-events")
// Apply translations and compute eventTypes from tags if missing
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
// Filter to conferences only (includes hackathons as they're often conference-adjacent)
const conferences = events.filter(
diff --git a/app/[locale]/community/events/meetups/page.tsx b/app/[locale]/community/events/meetups/page.tsx
index b00fde43761..8b74eded2c6 100644
--- a/app/[locale]/community/events/meetups/page.tsx
+++ b/app/[locale]/community/events/meetups/page.tsx
@@ -28,7 +28,7 @@ const Page = async (props: { params: Promise }) => {
const t = await getTranslations("page-community-events")
// Apply translations and compute eventTypes from tags if missing
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
// Combine API meetup events with legacy meetup groups
// Exclude conferences and hackathons - they have their own section
@@ -37,7 +37,7 @@ const Page = async (props: { params: Promise }) => {
!e.eventTypes?.includes("conference") &&
!e.eventTypes?.includes("hackathon")
)
- const meetupGroups = getMeetupGroups()
+ const meetupGroups = getMeetupGroups(locale)
// Show API meetups first (sorted by date), then groups (sorted alphabetically)
const meetups = [...apiMeetups, ...meetupGroups]
diff --git a/app/[locale]/community/events/page.tsx b/app/[locale]/community/events/page.tsx
index 00be50ac273..75611ec68f2 100644
--- a/app/[locale]/community/events/page.tsx
+++ b/app/[locale]/community/events/page.tsx
@@ -61,7 +61,7 @@ const Page = async (props: { params: Promise }) => {
locale as Lang
)
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
// Get highlighted conferences (with highlight flag or first 3)
const conferences = events.filter(
@@ -82,7 +82,7 @@ const Page = async (props: { params: Promise }) => {
!e.eventTypes?.includes("conference") &&
!e.eventTypes?.includes("hackathon")
)
- const meetupGroups = getMeetupGroups()
+ const meetupGroups = getMeetupGroups(locale)
const meetups = [...apiMeetups, ...meetupGroups]
// Continent labels for tabs
diff --git a/app/[locale]/community/events/search/page.tsx b/app/[locale]/community/events/search/page.tsx
index 9aff9dd4761..db1e543dc8d 100644
--- a/app/[locale]/community/events/search/page.tsx
+++ b/app/[locale]/community/events/search/page.tsx
@@ -41,7 +41,7 @@ const Page = async (props: {
const t = await getTranslations("page-community-events")
const tCommon = await getTranslations("common")
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
const filteredEvents = ((): EventItem[] => {
if (!q) return []
diff --git a/app/[locale]/community/events/utils.ts b/app/[locale]/community/events/utils.ts
index 2d686fd6169..543fcbfb8a0 100644
--- a/app/[locale]/community/events/utils.ts
+++ b/app/[locale]/community/events/utils.ts
@@ -4,7 +4,10 @@ import type { EventItem, EventType } from "@/lib/types"
import { TagProps } from "@/components/ui/tag"
-import { parseLocationToContinent } from "@/lib/utils/geography"
+import {
+ localizeLocation,
+ parseLocationToContinent,
+} from "@/lib/utils/geography"
import { slugify } from "@/lib/utils/url"
import communityMeetups from "@/data/community-meetups.json"
@@ -25,7 +28,8 @@ export const sanitize = (s: string) =>
export const mapEventTranslations = (
events: EventItem[],
- t: ReturnType
+ t: ReturnType,
+ locale: string
): EventItem[] =>
events.map((event) => {
// Use existing eventTypes if they have values, otherwise compute from tags
@@ -38,6 +42,7 @@ export const mapEventTranslations = (
...event,
eventTypes,
eventTypesLabels: eventTypes.map((type) => t(`page-events-tag-${type}`)),
+ location: localizeLocation(event.location, locale),
}
})
@@ -50,14 +55,14 @@ interface MeetupGroup {
bannerImage?: string
}
-function transformMeetupGroup(group: MeetupGroup): EventItem {
+function transformMeetupGroup(group: MeetupGroup, locale: string): EventItem {
return {
title: group.title,
logoImage: group.logoImage || "",
bannerImage: group.bannerImage || "",
startTime: "",
endTime: null,
- location: group.location,
+ location: localizeLocation(group.location, locale),
link: group.link,
tags: ["meetup"],
id: slugify(`${group.title}-${group.location}`),
@@ -71,8 +76,8 @@ function transformMeetupGroup(group: MeetupGroup): EventItem {
* Get meetup groups from community-meetups.json
* These are ongoing community groups (not individual events with dates)
*/
-export function getMeetupGroups(): EventItem[] {
+export function getMeetupGroups(locale: string): EventItem[] {
return (communityMeetups as MeetupGroup[])
- .map(transformMeetupGroup)
+ .map((group) => transformMeetupGroup(group, locale))
.sort((a, b) => a.title.localeCompare(b.title))
}
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx
index d54110b9eab..2d18e28b453 100644
--- a/app/[locale]/page.tsx
+++ b/app/[locale]/page.tsx
@@ -161,8 +161,11 @@ const Page = async (props: { params: Promise }) => {
// Extract totalEthStaked from beaconchainData
const { totalEthStaked } = beaconchainData
- // Events - use empty array as fallback
- const upcomingEvents = (eventsData ?? []).slice(0, 3)
+ // Events - use empty array as fallback; localize location country names
+ const upcomingEvents = (eventsData ?? []).slice(0, 3).map((event) => ({
+ ...event,
+ location: localizeLocation(event.location, locale),
+ }))
const appsOfTheWeek = parseAppsOfTheWeek(appsData)
@@ -861,7 +864,7 @@ const Page = async (props: { params: Promise }) => {
})}
- {localizeLocation(location, locale)}
+ {location}
diff --git a/src/lib/utils/geography.ts b/src/lib/utils/geography.ts
index 31e4b8ee073..12a757dd647 100644
--- a/src/lib/utils/geography.ts
+++ b/src/lib/utils/geography.ts
@@ -128,17 +128,17 @@ const COUNTRY_NAME_ALIASES: Record = {
* @param locale - Target locale code (e.g., "ja", "es", "ar")
* @returns Localized country name, or the original string if not recognized
*/
-export function getCountryTranslation(
- country: string,
- locale: string
-): string {
+export function getCountryTranslation(country: string, locale: string): string {
if (!country) return country
const normalized = COUNTRY_NAME_ALIASES[country] ?? country
const code = countries.getAlpha2Code(normalized, "en")
if (!code) return country
- return countries.getName(code, locale) ?? country
+ // Strip region suffix: "pt-br" -> "pt", "zh-tw" -> "zh"
+ // (i18n-iso-countries uses base language codes)
+ const baseLocale = locale.split("-")[0]
+ return countries.getName(code, baseLocale) ?? country
}
/**
From 3e15f96befa383b9ff0dbe17fe9a6695100066db Mon Sep 17 00:00:00 2001
From: myelinated-wackerow
<263208946+myelinated-wackerow@users.noreply.github.com>
Date: Mon, 20 Apr 2026 12:00:47 -0700
Subject: [PATCH 3/3] fix: treat "Remote" location as online event
External event data sometimes uses "Remote" instead of "Online"
for virtual events. Normalize both to the same isOnline flag so
they render with the same translated "Online" tag in the UI.
- Added isOnlineLocation() helper with a set of sentinel values
- Used in fetchEvents, parseLocationToContinent, localizeLocation
Co-Authored-By: Claude Opus 4.6
Co-Authored-By: wackerow <54227730+wackerow@users.noreply.github.com>
---
src/data-layer/fetchers/fetchEvents.ts | 7 +++++--
src/lib/utils/geography.ts | 21 ++++++++++++++++++---
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/src/data-layer/fetchers/fetchEvents.ts b/src/data-layer/fetchers/fetchEvents.ts
index 87144bacb02..10b120eea97 100644
--- a/src/data-layer/fetchers/fetchEvents.ts
+++ b/src/data-layer/fetchers/fetchEvents.ts
@@ -1,6 +1,9 @@
import type { EventItem, EventType, GeodeApiEventItem } from "@/lib/types"
-import { parseLocationToContinent } from "@/lib/utils/geography"
+import {
+ isOnlineLocation,
+ parseLocationToContinent,
+} from "@/lib/utils/geography"
import { slugify } from "@/lib/utils/url"
import { uploadToS3 } from "../s3"
@@ -46,7 +49,7 @@ function transformEvent(event: GeodeApiEventItem): EventItem {
...event,
id: slugify(event.title),
eventTypes: getEventTypes(event.tags),
- isOnline: event.location.toLowerCase() === "online",
+ isOnline: isOnlineLocation(event.location),
continent: parseLocationToContinent(event.location),
}
}
diff --git a/src/lib/utils/geography.ts b/src/lib/utils/geography.ts
index 12a757dd647..efb53a57bd5 100644
--- a/src/lib/utils/geography.ts
+++ b/src/lib/utils/geography.ts
@@ -97,12 +97,26 @@ export const COUNTRY_TO_CONTINENT: Record = Object.entries(
{} as Record
)
+/**
+ * Sentinel location strings that indicate a virtual/remote event rather
+ * than a physical location. Matched case-insensitively.
+ */
+const ONLINE_LOCATION_VALUES = new Set(["online", "remote"])
+
+/**
+ * Returns true if the location string represents a virtual event
+ * (e.g., "Online", "Remote") rather than a physical location.
+ */
+export function isOnlineLocation(location: string): boolean {
+ return ONLINE_LOCATION_VALUES.has(location?.trim().toLowerCase() ?? "")
+}
+
/**
* Parse a location string (e.g., "Berlin, Germany") to its continent.
* Returns null for online events or unrecognized locations.
*/
export function parseLocationToContinent(location: string): Continent | null {
- if (!location || location.toLowerCase() === "online") return null
+ if (!location || isOnlineLocation(location)) return null
const parts = location.split(/,\s*/)
const country = parts[parts.length - 1]?.trim()
if (!country) return null
@@ -148,7 +162,8 @@ export function getCountryTranslation(country: string, locale: string): string {
* locale, and reassembles. City names are left in their original script.
*
* Returns the original string unchanged if:
- * - The location is "Online" (handled separately by i18n keys)
+ * - The location is a virtual event sentinel (e.g., "Online", "Remote") --
+ * these are handled separately by i18n keys via the isOnline flag
* - The country cannot be identified or translated
* - The locale is "en" (no translation needed)
*
@@ -159,7 +174,7 @@ export function getCountryTranslation(country: string, locale: string): string {
export function localizeLocation(location: string, locale: string): string {
if (!location || locale === "en") return location
- if (location.toLowerCase() === "online") return location
+ if (isOnlineLocation(location)) return location
const parts = location.split(/,\s*/)
const rawCountry = parts[parts.length - 1].trim()