mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-06-24 05:36:39 -05:00
134 lines
3.5 KiB
TypeScript
134 lines
3.5 KiB
TypeScript
import type { LanguageCode } from "~/modules/i18n/config";
|
||
import type { Namespace } from "~/modules/i18n/resources.server";
|
||
import { logger } from "./logger";
|
||
import { assertType } from "./types";
|
||
|
||
// note: cannot get from resources.server.ts directly, because that is a server-only file
|
||
const ALL_NAMESPACES = [
|
||
"common",
|
||
"analyzer",
|
||
"badges",
|
||
"builds",
|
||
"calendar",
|
||
"contributions",
|
||
"faq",
|
||
"forms",
|
||
"game-badges",
|
||
"game-misc",
|
||
"gear",
|
||
"user",
|
||
"weapons",
|
||
"scrims",
|
||
"tournament",
|
||
"team",
|
||
"tier-list-maker",
|
||
"vods",
|
||
"art",
|
||
"q",
|
||
"lfg",
|
||
"org",
|
||
"front",
|
||
"friends",
|
||
"settings",
|
||
"params",
|
||
] as const;
|
||
assertType<Namespace, (typeof ALL_NAMESPACES)[number]>();
|
||
assertType<(typeof ALL_NAMESPACES)[number], Namespace>();
|
||
|
||
export function allI18nNamespaces() {
|
||
return [...ALL_NAMESPACES];
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
}) {
|
||
if (countryCode === "GB-WLS") return "Wales";
|
||
if (countryCode === "GB-SCT") return "Scotland";
|
||
if (countryCode === "GB-NIR") return "Northern Ireland";
|
||
if (countryCode === "GB-ENG") return "England";
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Ordinal placement suffixes per language, keyed by the CLDR ordinal plural
|
||
* category given by `Intl.PluralRules(language, { type: "ordinal" })`. A leading
|
||
* `^` marks a suffix that should render as superscript. Languages without a
|
||
* written ordinal suffix use explicit `null` – rendering no suffix is preferred
|
||
* over borrowing the English one.
|
||
*/
|
||
const ORDINAL_SUFFIXES: Record<
|
||
LanguageCode,
|
||
Partial<Record<Intl.LDMLPluralRule, string>> | null
|
||
> = {
|
||
da: null,
|
||
he: null,
|
||
nl: null,
|
||
en: { one: "^st", two: "^nd", few: "^rd", other: "^th" },
|
||
"es-ES": { other: "º" },
|
||
"es-US": { other: "^o" },
|
||
"fr-CA": { one: "^er", other: "^ème" },
|
||
"fr-EU": { one: "^er", other: "^ème" },
|
||
de: { other: "^." },
|
||
it: { many: "^o", other: "^o" },
|
||
ja: { other: "位" },
|
||
ko: { other: "^등" },
|
||
pl: { other: "^." },
|
||
"pt-BR": { other: "^º" },
|
||
ru: { other: "^ое" },
|
||
zh: { other: "名" },
|
||
};
|
||
|
||
const pluralRulesCache = new Map<string, Intl.PluralRules>();
|
||
|
||
/**
|
||
* Returns the localized ordinal suffix for a placement number in the given
|
||
* language (e.g. `"^st"` for `1` in English). A leading `^` marks a suffix the
|
||
* caller should render as superscript. Returns an empty string for languages
|
||
* without a written ordinal suffix.
|
||
*/
|
||
export function ordinalSuffix(placement: number, language: string): string {
|
||
const category = ordinalPluralRules(language).select(placement);
|
||
|
||
return ORDINAL_SUFFIXES[language as LanguageCode]?.[category] ?? "";
|
||
}
|
||
|
||
function ordinalPluralRules(language: string): Intl.PluralRules {
|
||
let rules = pluralRulesCache.get(language);
|
||
if (!rules) {
|
||
rules = new Intl.PluralRules(language, { type: "ordinal" });
|
||
pluralRulesCache.set(language, rules);
|
||
}
|
||
|
||
return rules;
|
||
}
|