Fix leaderboards crash for season 0

This commit is contained in:
Kalle
2025-06-30 21:35:31 +03:00
parent 29220bebf7
commit 213b58bcdf
4 changed files with 89 additions and 5 deletions

View File

@@ -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 (
<div
className={clsx(`twf twf-${countryCode.toLowerCase()}`, {
"twf-s": tiny,
})}
data-testid={`flag-${countryCode}`}
title={new Intl.DisplayNames([i18n.language], { type: "region" }).of(
title={countryCodeToTranslatedName({
countryCode,
)}
language: i18n.language,
})}
/>
);
}

View File

@@ -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,

38
app/utils/i18n.test.ts Normal file
View File

@@ -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");
});
});

41
app/utils/i18n.ts Normal file
View File

@@ -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
}
}