Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 332x 6103x 332x 6084x 5760x 5760x 5760x 332x 332x 6103x 12705x 6103x 6103x 6103x 19x 6084x 6084x 6084x 11834x 5566x 518x 518x 332x 6865x 6865x 6865x 6865x 6865x 6865x 6865x 91x 91x 6103x 6956x 6956x 332x 2652x 2652x | /// <reference types="vite/client" />
import { createIsomorphicFn } from "@tanstack/react-start";
import { parse } from "cookie";
import type { BackendModule, ParseKeys, ResourceKey } from "i18next";
import type { I18nContext } from "~app/utils/i18n";
import type { Language, Namespace } from "~app/utils/i18n-data";
import { baseLanguage, isLanguage } from "~app/utils/i18n-data";
import { LANGUAGE_STORE_NAME } from "~app/utils/store/language";
const getCookie = (headers: Headers | undefined) =>
headers ? (headers.get("cookie") ?? "") : document.cookie;
const getHeaderLanguages = (headers: Headers | undefined) =>
headers
? (headers.get("accept-language") ?? "")
.split(",")
.map((lang) => {
const [tag = "", q = "1"] = lang.trim().split(";q=");
return {
fullTag: tag.toLowerCase(),
baseTag: tag.split("-")[0]?.toLowerCase() || "",
q: Number(q),
};
})
.toSorted((a, b) => b.q - a.q)
.flatMap(({ fullTag, baseTag }) => [fullTag, baseTag])
: window.navigator.languages;
type Strategy = "cookie" | "header" | "baseLocale";
const strategies: Strategy[] = ["cookie", "header", "baseLocale"];
export const getLanguageFromRequest = (headers: Headers | undefined) => {
for (const strategy of strategies) {
switch (strategy) {
case "cookie": {
const cookies = parse(getCookie(headers));
const cookieLanguage = cookies[LANGUAGE_STORE_NAME] ?? "";
if (isLanguage(cookieLanguage)) {
return cookieLanguage;
}
break;
}
case "header": {
const headerLanguages = getHeaderLanguages(headers);
for (const headerLanguage of headerLanguages) {
if (isLanguage(headerLanguage)) {
return headerLanguage;
}
}
break;
}
case "baseLocale":
return baseLanguage;
}
}
return baseLanguage;
};
const readBackend = createIsomorphicFn()
.server(async (language: Language, namespace: Namespace) => {
/* oxlint-disable import/no-nodejs-modules */
const fs = await import("node:fs/promises");
const url = await import("node:url");
/* oxlint-enable import/no-nodejs-modules */
const publicPath = "../../public";
const jsonUrl = new url.URL(
`${publicPath}/locales/${language}/${namespace}.json`,
import.meta.url,
);
const fileContent = await fs.readFile(url.fileURLToPath(jsonUrl));
const resource = JSON.parse(fileContent.toString("utf8")) as ResourceKey;
return resource;
})
.client(async (language, namespace) => {
const response = await fetch(`/locales/${language}/${namespace}.json`);
return response.json() as Promise<ResourceKey>;
});
export const getBackendModule = (): BackendModule => ({
type: "backend",
init: () => {
/* empty */
},
// Improper types in i18next
// oxlint-disable-next-line typescript/no-misused-promises typescript/strict-void-return
read: async (language, namespace) => {
try {
return await readBackend(language as Language, namespace as Namespace);
} catch (error) {
// oxlint-disable-next-line no-console
console.error(`Failed to load ${language}/${namespace} i18n translation`);
throw error;
}
},
});
type TitleKey<T extends ParseKeys> = T extends `titles.${infer X}` ? X : never;
export const getTitle = (
i18nContext: I18nContext,
pageKey: TitleKey<ParseKeys>,
params?: Record<string, unknown>,
) => {
const t = i18nContext.getTranslation();
return t("titles.template", { page: t(`titles.${pageKey}`, params) });
};
|