From 213b58bcdf1d2fc4924c59a8bc0d8080d92c515d Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:35:31 +0300 Subject: [PATCH] Fix leaderboards crash for season 0 --- app/components/Flag.tsx | 7 +++- .../user-page/routes/u.$identifier.edit.tsx | 8 ++-- app/utils/i18n.test.ts | 38 +++++++++++++++++ app/utils/i18n.ts | 41 +++++++++++++++++++ 4 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 app/utils/i18n.test.ts create mode 100644 app/utils/i18n.ts diff --git a/app/components/Flag.tsx b/app/components/Flag.tsx index 2608df563..9dbcf91c6 100644 --- a/app/components/Flag.tsx +++ b/app/components/Flag.tsx @@ -1,5 +1,6 @@ import clsx from "clsx"; import { useTranslation } from "react-i18next"; +import { countryCodeToTranslatedName } from "~/utils/i18n"; export function Flag({ countryCode, @@ -9,15 +10,17 @@ export function Flag({ tiny?: boolean; }) { const { i18n } = useTranslation(); + return (
); } diff --git a/app/features/user-page/routes/u.$identifier.edit.tsx b/app/features/user-page/routes/u.$identifier.edit.tsx index af614ca0b..41044eaea 100644 --- a/app/features/user-page/routes/u.$identifier.edit.tsx +++ b/app/features/user-page/routes/u.$identifier.edit.tsx @@ -21,6 +21,7 @@ import { BADGE } from "~/features/badges/badges-constants"; import { BadgesSelector } from "~/features/badges/components/BadgesSelector"; import { useIsMounted } from "~/hooks/useIsMounted"; import { useHasRole } from "~/modules/permissions/hooks"; +import { countryCodeToTranslatedName } from "~/utils/i18n"; import invariant from "~/utils/invariant"; import { rawSensToString } from "~/utils/strings"; import { FAQ_PAGE } from "~/utils/urls"; @@ -215,12 +216,13 @@ function CountrySelect() { const isMounted = useIsMounted(); const [value, setValue] = React.useState(data.user.country ?? null); - const displayName = new Intl.DisplayNames(i18n.language, { type: "region" }); - // TODO: if react-aria-components start supporting "suppressHydrationWarning" it would likely be a better solution here const items = COUNTRY_CODES.map((countryCode) => ({ name: isMounted - ? (displayName.of(countryCode) ?? countryCode) + ? countryCodeToTranslatedName({ + countryCode, + language: i18n.language, + }) : countryCode, id: countryCode, key: countryCode, diff --git a/app/utils/i18n.test.ts b/app/utils/i18n.test.ts new file mode 100644 index 000000000..9e76874d5 --- /dev/null +++ b/app/utils/i18n.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; +import { countryCodeToTranslatedName } from "./i18n"; + +describe("countryCodeToTranslatedName()", () => { + it("returns the translated country name for a valid code", () => { + const result = countryCodeToTranslatedName({ + countryCode: "FI", + language: "fi", + }); + expect(result).toBe("Suomi"); + }); + + it("returns the country code if it contains a dash (Intl.DisplayNames throws)", () => { + const result = countryCodeToTranslatedName({ + countryCode: "GB-WLS", + language: "en", + }); + expect(result).toBe("GB-WLS"); + }); + + it("returns the country code as is for unknown country", () => { + const result = countryCodeToTranslatedName({ + countryCode: "UNKNOWN", + language: "en", + }); + + expect(result).toBe("UNKNOWN"); + }); + + it("defaults to english for unknown language", () => { + const result = countryCodeToTranslatedName({ + countryCode: "FI", + language: "unknown", + }); + + expect(result).toBe("Finland"); + }); +}); diff --git a/app/utils/i18n.ts b/app/utils/i18n.ts new file mode 100644 index 000000000..e68eab63f --- /dev/null +++ b/app/utils/i18n.ts @@ -0,0 +1,41 @@ +import { logger } from "./logger"; + +/** + * Returns the localized display name for a given ISO country code using the specified language. If the country code is unknown or the function fails for othe reason, returns the country code itself as a fallback. + * + * @example + * ```typescript + * function CountryNameComponent() { + * const { i18n } = useTranslation(); + * const countryName = countryCodeToTranslatedName({ + * countryCode: "FI", + * language: i18n.language, + * }); // "Suomi" in Finnish + * } + * ``` + */ +export function countryCodeToTranslatedName({ + countryCode, + language, +}: { + countryCode: string; + language: string; +}) { + // known limitation, function cannot handle e.g. GB-WLS (Wales) + if (countryCode.includes("-")) { + return countryCode; + } + + try { + return ( + new Intl.DisplayNames([language], { type: "region" }).of(countryCode) ?? + countryCode + ); + } catch (e) { + logger.error( + `Error getting display name for country code "${countryCode}":`, + e, + ); + return countryCode; // fallback to the code itself if display name fails + } +}