mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-24 15:08:44 -05:00
i18n support
This commit is contained in:
parent
e65580685c
commit
640eba9cea
|
|
@ -5,6 +5,7 @@ import { Image } from "../Image";
|
|||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { canPerformAdminActions } from "~/permissions";
|
||||
import { useUser } from "~/modules/auth";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function Menu({
|
||||
expanded,
|
||||
|
|
@ -15,6 +16,7 @@ export function Menu({
|
|||
}) {
|
||||
const user = useUser();
|
||||
const isMounted = useIsMounted();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// without this menu is initially visible due to SSR and not knowing user screen width on server (probably)
|
||||
if (!isMounted) return null;
|
||||
|
|
@ -47,7 +49,7 @@ export function Menu({
|
|||
path={`/img/layout/${navItem.name.replace(" ", "")}`}
|
||||
alt={navItem.name}
|
||||
/>
|
||||
<div>{navItem.displayName ?? navItem.name}</div>
|
||||
<div>{t(`pages.${navItem.name}`)}</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
[
|
||||
{
|
||||
"name": "admin",
|
||||
"displayName": "Admin",
|
||||
"url": "admin"
|
||||
},
|
||||
{ "name": "badges", "displayName": "Badges", "url": "badges" },
|
||||
{ "name": "badges", "url": "badges" },
|
||||
{
|
||||
"name": "plus",
|
||||
"displayName": "Plus Server",
|
||||
"url": "plus/suggestions"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
import { RemixBrowser } from "@remix-run/react";
|
||||
import { hydrateRoot } from "react-dom/client";
|
||||
import { i18nLoader } from "./modules/i18n";
|
||||
import i18next from "i18next";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
|
||||
i18nLoader().then(hydrate).catch(console.error);
|
||||
|
||||
// work around for react 18 + cypress problem - https://github.com/remix-run/remix/issues/2570#issuecomment-1099696456
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require("react-dom").hydrate(<RemixBrowser />, document);
|
||||
} else {
|
||||
hydrateRoot(document, <RemixBrowser />);
|
||||
function hydrate() {
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
return require("react-dom").hydrate(
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<RemixBrowser />
|
||||
</I18nextProvider>,
|
||||
document
|
||||
);
|
||||
} else {
|
||||
return hydrateRoot(
|
||||
document,
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<RemixBrowser />
|
||||
</I18nextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
import type { EntryContext } from "@remix-run/node";
|
||||
import { RemixServer } from "@remix-run/react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { renderToString } from "react-dom/server";
|
||||
import cron from "node-cron";
|
||||
import { updatePatreonData } from "./modules/patreon";
|
||||
import { i18Instance } from "./modules/i18n";
|
||||
|
||||
export default function handleRequest(
|
||||
export default async function handleRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext
|
||||
) {
|
||||
const i18n = await i18Instance(request, remixContext);
|
||||
|
||||
const markup = renderToString(
|
||||
<RemixServer context={remixContext} url={request.url} />
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<RemixServer context={remixContext} url={request.url} />
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
responseHeaders.set("Content-Type", "text/html");
|
||||
|
|
|
|||
8
app/modules/i18n/config.ts
Normal file
8
app/modules/i18n/config.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export const DEFAULT_LANGUAGE = "en";
|
||||
|
||||
export const config = {
|
||||
supportedLngs: ["en"],
|
||||
fallbackLng: DEFAULT_LANGUAGE,
|
||||
defaultNS: "common",
|
||||
react: { useSuspense: false },
|
||||
};
|
||||
20
app/modules/i18n/i18next.server.ts
Normal file
20
app/modules/i18n/i18next.server.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import Backend from "i18next-fs-backend";
|
||||
import { resolve } from "node:path";
|
||||
import { RemixI18Next } from "remix-i18next";
|
||||
import { config } from "./config";
|
||||
|
||||
export const i18next = new RemixI18Next({
|
||||
detection: {
|
||||
supportedLanguages: config.supportedLngs,
|
||||
fallbackLanguage: config.fallbackLng,
|
||||
},
|
||||
i18next: {
|
||||
...config,
|
||||
backend: {
|
||||
loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
|
||||
},
|
||||
},
|
||||
backend: Backend,
|
||||
});
|
||||
|
||||
export default i18next;
|
||||
4
app/modules/i18n/index.ts
Normal file
4
app/modules/i18n/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { i18nLoader } from "./loader";
|
||||
export { i18Instance } from "./loader.server";
|
||||
export { i18next } from "./i18next.server";
|
||||
export { DEFAULT_LANGUAGE } from "./config";
|
||||
28
app/modules/i18n/loader.server.ts
Normal file
28
app/modules/i18n/loader.server.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import type { EntryContext } from "@remix-run/server-runtime";
|
||||
import { createInstance } from "i18next";
|
||||
import Backend from "i18next-fs-backend";
|
||||
import { resolve } from "node:path";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import { config } from "./config";
|
||||
import i18next from "./i18next.server";
|
||||
|
||||
export async function i18Instance(request: Request, context: EntryContext) {
|
||||
const instance = createInstance();
|
||||
|
||||
const lng = await i18next.getLocale(request);
|
||||
const ns = i18next.getRouteNamespaces(context);
|
||||
|
||||
await instance
|
||||
.use(initReactI18next)
|
||||
.use(Backend)
|
||||
.init({
|
||||
...config,
|
||||
lng,
|
||||
ns,
|
||||
backend: {
|
||||
loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
|
||||
},
|
||||
});
|
||||
|
||||
return instance;
|
||||
}
|
||||
24
app/modules/i18n/loader.ts
Normal file
24
app/modules/i18n/loader.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import i18next from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import Backend from "i18next-http-backend";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import { getInitialNamespaces } from "remix-i18next";
|
||||
import { config } from "./config";
|
||||
|
||||
export function i18nLoader() {
|
||||
return i18next
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.use(Backend)
|
||||
.init({
|
||||
...config,
|
||||
ns: getInitialNamespaces(),
|
||||
backend: {
|
||||
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
||||
},
|
||||
detection: {
|
||||
order: ["htmlTag"],
|
||||
caches: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
32
app/root.tsx
32
app/root.tsx
|
|
@ -25,6 +25,9 @@ import { db } from "./db";
|
|||
import type { FindAllPatrons } from "./db/models/users.server";
|
||||
import type { UserWithPlusTier } from "./db/types";
|
||||
import { getUser } from "./modules/auth";
|
||||
import { DEFAULT_LANGUAGE, i18next } from "./modules/i18n";
|
||||
import { useChangeLanguage } from "remix-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const unstable_shouldReload: ShouldReloadFunction = () => false;
|
||||
|
||||
|
|
@ -44,6 +47,7 @@ export const meta: MetaFunction = () => ({
|
|||
});
|
||||
|
||||
export interface RootLoaderData {
|
||||
locale: string;
|
||||
patrons: FindAllPatrons;
|
||||
user?: Pick<
|
||||
UserWithPlusTier,
|
||||
|
|
@ -53,8 +57,10 @@ export interface RootLoaderData {
|
|||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
const user = await getUser(request);
|
||||
const locale = await i18next.getLocale(request);
|
||||
|
||||
return json<RootLoaderData>({
|
||||
locale,
|
||||
patrons: db.users.findAllPatrons(),
|
||||
user: user
|
||||
? {
|
||||
|
|
@ -67,17 +73,23 @@ export const loader: LoaderFunction = async ({ request }) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const handle = {
|
||||
i18n: "common",
|
||||
};
|
||||
|
||||
function Document({
|
||||
children,
|
||||
patrons,
|
||||
isCatchBoundary,
|
||||
data,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
patrons?: RootLoaderData["patrons"];
|
||||
isCatchBoundary?: boolean;
|
||||
data?: RootLoaderData;
|
||||
}) {
|
||||
const { i18n } = useTranslation();
|
||||
const locale = data?.locale ?? DEFAULT_LANGUAGE;
|
||||
useChangeLanguage(locale);
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang={locale} dir={i18n.dir()}>
|
||||
<head>
|
||||
<Meta />
|
||||
<meta name="color-scheme" content="dark light" />
|
||||
|
|
@ -85,7 +97,7 @@ function Document({
|
|||
</head>
|
||||
<body>
|
||||
<React.StrictMode>
|
||||
<Layout patrons={patrons} isCatchBoundary={isCatchBoundary}>
|
||||
<Layout patrons={data?.patrons} isCatchBoundary={!data}>
|
||||
{children}
|
||||
</Layout>
|
||||
</React.StrictMode>
|
||||
|
|
@ -98,12 +110,12 @@ function Document({
|
|||
}
|
||||
|
||||
export default function App() {
|
||||
// prop drilling patrons instead of using useLoaderData in the Footer directly because
|
||||
// useLoaderData can't be used in CatchBoundary and Footer is rendered in it as well
|
||||
// prop drilling data instead of using useLoaderData in the child components directly because
|
||||
// useLoaderData can't be used in CatchBoundary and layout is rendered in it as well
|
||||
const data = useLoaderData<RootLoaderData>();
|
||||
|
||||
return (
|
||||
<Document patrons={data.patrons}>
|
||||
<Document data={data}>
|
||||
<Outlet />
|
||||
</Document>
|
||||
);
|
||||
|
|
@ -111,7 +123,7 @@ export default function App() {
|
|||
|
||||
export function CatchBoundary() {
|
||||
return (
|
||||
<Document isCatchBoundary>
|
||||
<Document>
|
||||
<Catcher />
|
||||
</Document>
|
||||
);
|
||||
|
|
|
|||
283
package-lock.json
generated
283
package-lock.json
generated
|
|
@ -17,13 +17,19 @@
|
|||
"countries-list": "^2.6.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"i18next": "^21.8.14",
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"i18next-fs-backend": "^1.1.4",
|
||||
"i18next-http-backend": "^1.4.1",
|
||||
"just-shuffle": "^4.0.1",
|
||||
"node-cron": "3.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^11.18.1",
|
||||
"react-popper": "^2.3.0",
|
||||
"remix-auth": "^3.2.2",
|
||||
"remix-auth-oauth2": "^1.2.2",
|
||||
"remix-i18next": "^4.1.1",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"zod": "^3.17.3"
|
||||
},
|
||||
|
|
@ -31,6 +37,7 @@
|
|||
"@remix-run/dev": "^1.6.5",
|
||||
"@remix-run/eslint-config": "^1.6.5",
|
||||
"@types/better-sqlite3": "^7.5.0",
|
||||
"@types/i18next-fs-backend": "^1.1.2",
|
||||
"@types/node-cron": "^3.0.2",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
|
|
@ -2863,6 +2870,15 @@
|
|||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/i18next-fs-backend": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/i18next-fs-backend/-/i18next-fs-backend-1.1.2.tgz",
|
||||
"integrity": "sha512-ZzTRXA5B0x0oGhzKNp08IsYjZpli4LjRZpg3q4j0XFxN5lKG2MVLnR4yHX8PPExBk4sj9Yfk1z9O6CjPrAlmIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"i18next": "^21.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json-buffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz",
|
||||
|
|
@ -3364,6 +3380,11 @@
|
|||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/accept-language-parser": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz",
|
||||
"integrity": "sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw=="
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
|
|
@ -5056,6 +5077,14 @@
|
|||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"dependencies": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
|
|
@ -7446,8 +7475,7 @@
|
|||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.11",
|
||||
|
|
@ -8324,6 +8352,14 @@
|
|||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-tags": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
|
||||
|
|
@ -8396,6 +8432,49 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "21.8.14",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-21.8.14.tgz",
|
||||
"integrity": "sha512-4Yi+DtexvMm/Yw3Q9fllzY12SgLk+Mcmar+rCAccsOPul/2UmnBzoHbTGn/L48IPkFcmrNaH7xTLboBWIbH6pw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz",
|
||||
"integrity": "sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.6"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-fs-backend": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-1.1.4.tgz",
|
||||
"integrity": "sha512-/MfAGMP0jHonV966uFf9PkWWuDjPYLIcsipnSO3NxpNtAgRUKLTwvm85fEmsF6hGeu0zbZiCQ3W74jwO6K9uXA=="
|
||||
},
|
||||
"node_modules/i18next-http-backend": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.1.tgz",
|
||||
"integrity": "sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "3.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
|
@ -8549,6 +8628,14 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-parse-accept-language": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/intl-parse-accept-language/-/intl-parse-accept-language-1.0.0.tgz",
|
||||
"integrity": "sha512-YFMSV91JNBOSjw1cOfw2tup6hDP7mkz+2AUV7W1L1AM6ntgI75qC1ZeFpjPGMrWp+upmBRTX2fJWQ8c7jsUWpA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
|
|
@ -11061,7 +11148,6 @@
|
|||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
|
|
@ -12109,6 +12195,27 @@
|
|||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "11.18.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.1.tgz",
|
||||
"integrity": "sha512-S8cl4mvIOSA7OQCE5jNy2yhv705Vwi+7PinpqKIYcBmX/trJtHKqrf6CL67WJSA8crr2JU+oxE9jn9DQIrQezg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.5",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 19.0.0",
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
|
|
@ -12543,6 +12650,32 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/remix-i18next": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/remix-i18next/-/remix-i18next-4.1.1.tgz",
|
||||
"integrity": "sha512-uyHzycfyUPrqA039UGSpfC/5xUMidmxp+b8oTTl9/ghB75tLR24XuRv28eJzS1HeautJI4l/v/iC8L3cVnZK7A==",
|
||||
"dependencies": {
|
||||
"accept-language-parser": "^1.5.0",
|
||||
"intl-parse-accept-language": "^1.0.0",
|
||||
"lru-cache": "^7.10.0",
|
||||
"use-consistent-value": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@remix-run/react": "^1.0.0",
|
||||
"@remix-run/server-runtime": "^1.0.0",
|
||||
"i18next": "^21.3.3",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-i18next": "^11.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/remix-i18next/node_modules/lru-cache": {
|
||||
"version": "7.13.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz",
|
||||
"integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/repeat-element": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
|
||||
|
|
@ -14252,8 +14385,7 @@
|
|||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
|
||||
"dev": true
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"node_modules/trim-newlines": {
|
||||
"version": "3.0.1",
|
||||
|
|
@ -14809,6 +14941,20 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-consistent-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/use-consistent-value/-/use-consistent-value-1.0.0.tgz",
|
||||
"integrity": "sha512-enIOysu0IwqjafsUXvhZPajB6UYjxwu8w38xWaHLfVs1onIyg2c0DwPgcknxqO0TpYpAHXdFDXOp7Cs9HbTIkw==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.12.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
|
||||
|
|
@ -14978,6 +15124,14 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
|
|
@ -15017,14 +15171,12 @@
|
|||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
|
||||
"dev": true
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
|
|
@ -17335,6 +17487,15 @@
|
|||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/i18next-fs-backend": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/i18next-fs-backend/-/i18next-fs-backend-1.1.2.tgz",
|
||||
"integrity": "sha512-ZzTRXA5B0x0oGhzKNp08IsYjZpli4LjRZpg3q4j0XFxN5lKG2MVLnR4yHX8PPExBk4sj9Yfk1z9O6CjPrAlmIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"i18next": "^21.0.1"
|
||||
}
|
||||
},
|
||||
"@types/json-buffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz",
|
||||
|
|
@ -17709,6 +17870,11 @@
|
|||
"event-target-shim": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"accept-language-parser": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz",
|
||||
"integrity": "sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
|
|
@ -18964,6 +19130,14 @@
|
|||
"cross-spawn": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"requires": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
|
|
@ -20683,8 +20857,7 @@
|
|||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.11",
|
||||
|
|
@ -21358,6 +21531,14 @@
|
|||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"requires": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"html-tags": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
|
||||
|
|
@ -21411,6 +21592,35 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"i18next": {
|
||||
"version": "21.8.14",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-21.8.14.tgz",
|
||||
"integrity": "sha512-4Yi+DtexvMm/Yw3Q9fllzY12SgLk+Mcmar+rCAccsOPul/2UmnBzoHbTGn/L48IPkFcmrNaH7xTLboBWIbH6pw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.17.2"
|
||||
}
|
||||
},
|
||||
"i18next-browser-languagedetector": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz",
|
||||
"integrity": "sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.6"
|
||||
}
|
||||
},
|
||||
"i18next-fs-backend": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-1.1.4.tgz",
|
||||
"integrity": "sha512-/MfAGMP0jHonV966uFf9PkWWuDjPYLIcsipnSO3NxpNtAgRUKLTwvm85fEmsF6hGeu0zbZiCQ3W74jwO6K9uXA=="
|
||||
},
|
||||
"i18next-http-backend": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.1.tgz",
|
||||
"integrity": "sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA==",
|
||||
"requires": {
|
||||
"cross-fetch": "3.1.5"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
|
@ -21523,6 +21733,11 @@
|
|||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"intl-parse-accept-language": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/intl-parse-accept-language/-/intl-parse-accept-language-1.0.0.tgz",
|
||||
"integrity": "sha512-YFMSV91JNBOSjw1cOfw2tup6hDP7mkz+2AUV7W1L1AM6ntgI75qC1ZeFpjPGMrWp+upmBRTX2fJWQ8c7jsUWpA=="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
|
|
@ -23304,7 +23519,6 @@
|
|||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
|
|
@ -24074,6 +24288,15 @@
|
|||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||
},
|
||||
"react-i18next": {
|
||||
"version": "11.18.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.1.tgz",
|
||||
"integrity": "sha512-S8cl4mvIOSA7OQCE5jNy2yhv705Vwi+7PinpqKIYcBmX/trJtHKqrf6CL67WJSA8crr2JU+oxE9jn9DQIrQezg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.5",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
|
|
@ -24412,6 +24635,24 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"remix-i18next": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/remix-i18next/-/remix-i18next-4.1.1.tgz",
|
||||
"integrity": "sha512-uyHzycfyUPrqA039UGSpfC/5xUMidmxp+b8oTTl9/ghB75tLR24XuRv28eJzS1HeautJI4l/v/iC8L3cVnZK7A==",
|
||||
"requires": {
|
||||
"accept-language-parser": "^1.5.0",
|
||||
"intl-parse-accept-language": "^1.0.0",
|
||||
"lru-cache": "^7.10.0",
|
||||
"use-consistent-value": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "7.13.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.1.tgz",
|
||||
"integrity": "sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeat-element": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
|
||||
|
|
@ -25740,8 +25981,7 @@
|
|||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
|
||||
"dev": true
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"trim-newlines": {
|
||||
"version": "3.0.1",
|
||||
|
|
@ -26134,6 +26374,14 @@
|
|||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||
"dev": true
|
||||
},
|
||||
"use-consistent-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/use-consistent-value/-/use-consistent-value-1.0.0.tgz",
|
||||
"integrity": "sha512-enIOysu0IwqjafsUXvhZPajB6UYjxwu8w38xWaHLfVs1onIyg2c0DwPgcknxqO0TpYpAHXdFDXOp7Cs9HbTIkw==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"util": {
|
||||
"version": "0.12.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
|
||||
|
|
@ -26260,6 +26508,11 @@
|
|||
"unist-util-stringify-position": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
|
||||
},
|
||||
"warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
|
|
@ -26294,14 +26547,12 @@
|
|||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
|
||||
"dev": true
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
|
|
|
|||
|
|
@ -38,13 +38,19 @@
|
|||
"countries-list": "^2.6.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"i18next": "^21.8.14",
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"i18next-fs-backend": "^1.1.4",
|
||||
"i18next-http-backend": "^1.4.1",
|
||||
"just-shuffle": "^4.0.1",
|
||||
"node-cron": "3.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^11.18.1",
|
||||
"react-popper": "^2.3.0",
|
||||
"remix-auth": "^3.2.2",
|
||||
"remix-auth-oauth2": "^1.2.2",
|
||||
"remix-i18next": "^4.1.1",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"zod": "^3.17.3"
|
||||
},
|
||||
|
|
@ -52,6 +58,7 @@
|
|||
"@remix-run/dev": "^1.6.5",
|
||||
"@remix-run/eslint-config": "^1.6.5",
|
||||
"@types/better-sqlite3": "^7.5.0",
|
||||
"@types/i18next-fs-backend": "^1.1.2",
|
||||
"@types/node-cron": "^3.0.2",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
|
|
|
|||
5
public/locales/en/common.json
Normal file
5
public/locales/en/common.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"pages.admin": "Admin",
|
||||
"pages.badges": "Badges",
|
||||
"pages.plus": "Plus Server"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user