mirror of
https://github.com/arthur-pbty/flint.git
synced 2026-08-01 20:29:03 +02:00
add multi language for the website
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { DashboardClient } from "../../components/dashboard-client";
|
import { DashboardClient } from "../../../components/dashboard-client";
|
||||||
|
|
||||||
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:4000";
|
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:4000";
|
||||||
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { IBM_Plex_Mono, Space_Grotesk } from "next/font/google";
|
||||||
|
import { hasLocale, NextIntlClientProvider } from "next-intl";
|
||||||
|
import { getMessages } from "next-intl/server";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
|
import LanguageSwitcher from "../../components/LanguageSwitcher";
|
||||||
|
import { routing, type AppLocale } from "../../i18n/routing";
|
||||||
|
import enMessages from "../../messages/en.json";
|
||||||
|
import frMessages from "../../messages/fr.json";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
const headingFont = Space_Grotesk({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-heading",
|
||||||
|
weight: ["500", "700"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const monoFont = IBM_Plex_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mono",
|
||||||
|
weight: ["400", "500"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const metadataByLocale: Record<AppLocale, { title: string; description: string }> = {
|
||||||
|
en: enMessages.metadata,
|
||||||
|
fr: frMessages.metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return routing.locales.map((locale) => ({ locale }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: string }>;
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { locale: candidateLocale } = await params;
|
||||||
|
const locale = hasLocale(routing.locales, candidateLocale)
|
||||||
|
? candidateLocale
|
||||||
|
: routing.defaultLocale;
|
||||||
|
const metadata = metadataByLocale[locale];
|
||||||
|
|
||||||
|
const languages = routing.locales.reduce<Record<string, string>>(
|
||||||
|
(accumulator, currentLocale) => {
|
||||||
|
accumulator[currentLocale] = `/${currentLocale}`;
|
||||||
|
return accumulator;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: metadata.title,
|
||||||
|
description: metadata.description,
|
||||||
|
alternates: {
|
||||||
|
canonical: `/${locale}`,
|
||||||
|
languages,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function LocaleLayout({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: Promise<{ locale: string }>;
|
||||||
|
}>) {
|
||||||
|
const { locale } = await params;
|
||||||
|
|
||||||
|
if (!hasLocale(routing.locales, locale)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = await getMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale}>
|
||||||
|
<body className={`${headingFont.variable} ${monoFont.variable}`}>
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
<div className="locale-switcher-shell">
|
||||||
|
<LanguageSwitcher />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { getT } from "../../../i18n/server";
|
||||||
|
|
||||||
|
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:4000";
|
||||||
|
|
||||||
|
export default async function LoginPage() {
|
||||||
|
const t = await getT();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="page-shell">
|
||||||
|
<section className="panel panel-login reveal-up">
|
||||||
|
<p className="eyebrow">{t("login.eyebrow")}</p>
|
||||||
|
<h1>{t("login.title")}</h1>
|
||||||
|
<p>{t("login.description")}</p>
|
||||||
|
<a className="button-primary" href={`${apiBaseUrl}/auth/discord/login`}>
|
||||||
|
{t("login.cta")}
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import Hero from "../components/Hero";
|
import Features from "../../components/Features";
|
||||||
import Features from "../components/Features";
|
import Footer from "../../components/Footer";
|
||||||
import HowItWorks from "../components/HowItWorks";
|
import Hero from "../../components/Hero";
|
||||||
import Footer from "../components/Footer";
|
import HowItWorks from "../../components/HowItWorks";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
@@ -37,6 +37,43 @@ body {
|
|||||||
linear-gradient(140deg, var(--sand-50), #f8f7f4 55%, #fff3e3);
|
linear-gradient(140deg, var(--sand-50), #f8f7f4 55%, #fff3e3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.locale-switcher-shell {
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
z-index: 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-switcher {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||||
|
background: rgba(23, 20, 16, 0.74);
|
||||||
|
padding: 0.2rem 0.55rem;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-switcher select {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--font-heading), sans-serif;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-switcher select:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-switcher select:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: progress;
|
||||||
|
}
|
||||||
|
|
||||||
.page-shell {
|
.page-shell {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -283,3 +320,14 @@ input:focus {
|
|||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.locale-switcher-shell {
|
||||||
|
top: 0.7rem;
|
||||||
|
right: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.locale-switcher select {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import type { Metadata } from "next";
|
|
||||||
import { IBM_Plex_Mono, Space_Grotesk } from "next/font/google";
|
|
||||||
|
|
||||||
import "./globals.css";
|
|
||||||
|
|
||||||
const headingFont = Space_Grotesk({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-heading",
|
|
||||||
weight: ["500", "700"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const monoFont = IBM_Plex_Mono({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-mono",
|
|
||||||
weight: ["400", "500"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Discord Bot SaaS",
|
|
||||||
description: "Multi-tenant Discord bot management platform",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
return (
|
|
||||||
<html lang="fr">
|
|
||||||
<body className={`${headingFont.variable} ${monoFont.variable}`}>{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:4000";
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
|
||||||
return (
|
|
||||||
<main className="page-shell">
|
|
||||||
<section className="panel panel-login reveal-up">
|
|
||||||
<p className="eyebrow">Discord Bot SaaS Platform</p>
|
|
||||||
<h1>Connecte ton compte Discord</h1>
|
|
||||||
<p>
|
|
||||||
Authentifie-toi via OAuth2 pour accéder à ton espace multi-tenant et piloter les bots de ton
|
|
||||||
organisation.
|
|
||||||
</p>
|
|
||||||
<a className="button-primary" href={`${apiBaseUrl}/auth/discord/login`}>
|
|
||||||
Continuer avec Discord
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import FeatureCard from "./FeatureCard";
|
import FeatureCard from "./FeatureCard";
|
||||||
|
import { getT } from "../i18n/server";
|
||||||
|
|
||||||
|
export default async function Features() {
|
||||||
|
const t = await getT();
|
||||||
|
|
||||||
export default function Features() {
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
title: "Multi-bot",
|
title: t("features.items.autoCommands.title"),
|
||||||
description:
|
description: t("features.items.autoCommands.description"),
|
||||||
"Gérez plusieurs bots indépendants depuis un tableau de bord unique — configurations et logs centralisés.",
|
|
||||||
icon: (
|
icon: (
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="text-white">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="text-white">
|
||||||
<path d="M4 7h16v10H4z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
<path d="M4 7h16v10H4z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
@@ -14,8 +16,8 @@ export default function Features() {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Gestion simple",
|
title: t("features.items.componentsV2.title"),
|
||||||
description: "Démarrez, arrêtez, et configurez en quelques clics — interface claire et rapide.",
|
description: t("features.items.componentsV2.description"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 3v18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
<path d="M12 3v18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
@@ -24,8 +26,38 @@ export default function Features() {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Scalable",
|
title: t("features.items.autoHelp.title"),
|
||||||
description: "Scale automatique des instances pour absorber la charge et garder la disponibilité.",
|
description: t("features.items.autoHelp.description"),
|
||||||
|
icon: (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 12h18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
<path d="M6 8v8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("features.items.multiLanguage.title"),
|
||||||
|
description: t("features.items.multiLanguage.description"),
|
||||||
|
icon: (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 7h16v10H4z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
<path d="M8 11h0" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("features.items.dynamicVariables.title"),
|
||||||
|
description: t("features.items.dynamicVariables.description"),
|
||||||
|
icon: (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 3v18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
<path d="M5 8h14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("features.items.hosting.title"),
|
||||||
|
description: t("features.items.hosting.description"),
|
||||||
icon: (
|
icon: (
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M3 12h18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
<path d="M3 12h18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
@@ -37,8 +69,8 @@ export default function Features() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-bold text-white">Fonctionnalités principales</h2>
|
<h2 className="text-2xl font-bold text-white">{t("features.title")}</h2>
|
||||||
<p className="mt-2 text-gray-400 max-w-2xl">Toutes les fonctionnalités essentielles pour piloter vos bots en production.</p>
|
<p className="mt-2 text-gray-400 max-w-2xl">{t("features.subtitle")}</p>
|
||||||
|
|
||||||
<div className="mt-6 grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="mt-6 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
{items.map((it, idx) => (
|
{items.map((it, idx) => (
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
export default function Footer() {
|
import { getT } from "../i18n/server";
|
||||||
|
|
||||||
|
export default async function Footer() {
|
||||||
|
const t = await getT();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="mt-20 border-t border-white/6 bg-gradient-to-t from-black/0 to-black/30">
|
<footer className="mt-20 border-t border-white/6 bg-gradient-to-t from-black/0 to-black/30">
|
||||||
<div className="container mx-auto px-6 py-8 flex items-center justify-between">
|
<div className="container mx-auto px-6 py-8 flex items-center justify-between">
|
||||||
<div className="text-sm text-gray-400">© {new Date().getFullYear()} Shadow</div>
|
<div className="text-sm text-gray-400">
|
||||||
|
Copyright {new Date().getFullYear()} Shadow - {t("footer.tagline")}
|
||||||
|
</div>
|
||||||
<div className="text-sm text-gray-400">
|
<div className="text-sm text-gray-400">
|
||||||
<a href="https://github.com/" target="_blank" rel="noreferrer" className="hover:text-white transition">GitHub</a>
|
<a href="https://github.com/" target="_blank" rel="noreferrer" className="hover:text-white transition">GitHub</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import Link from "next/link";
|
import { Link } from "../i18n/navigation";
|
||||||
|
import { getT } from "../i18n/server";
|
||||||
|
|
||||||
import Logo from "./Logo";
|
import Logo from "./Logo";
|
||||||
|
|
||||||
export default function Hero() {
|
export default async function Hero() {
|
||||||
|
const t = await getT();
|
||||||
|
|
||||||
const bots = [
|
const bots = [
|
||||||
{ name: "Moderation", status: "online" },
|
{ name: t("hero.bots.moderation"), status: "online" as const },
|
||||||
{ name: "Welcome", status: "stopped" },
|
{ name: t("hero.bots.welcome"), status: "stopped" as const },
|
||||||
{ name: "Analytics", status: "online" },
|
{ name: t("hero.bots.analytics"), status: "online" as const },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -13,28 +17,29 @@ export default function Hero() {
|
|||||||
<header className="flex items-center justify-between">
|
<header className="flex items-center justify-between">
|
||||||
<Logo />
|
<Logo />
|
||||||
<nav className="flex items-center gap-4">
|
<nav className="flex items-center gap-4">
|
||||||
<Link href="/login" className="text-sm text-gray-400 hover:text-white transition">Se connecter</Link>
|
<Link href="/login" className="text-sm text-gray-400 hover:text-white transition">
|
||||||
|
{t("hero.navLogin")}
|
||||||
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="mt-12 lg:flex lg:items-center lg:justify-between">
|
<div className="mt-12 lg:flex lg:items-center lg:justify-between">
|
||||||
<div className="lg:w-7/12">
|
<div className="lg:w-7/12">
|
||||||
<h1 className="text-4xl sm:text-5xl font-extrabold leading-tight tracking-tight text-white">
|
<h1 className="text-4xl sm:text-5xl font-extrabold leading-tight tracking-tight text-white">
|
||||||
Shadow — Gérez vos bots Discord simplement
|
{t("hero.title")}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="mt-4 text-lg text-gray-300 max-w-2xl">
|
<p className="mt-4 text-lg text-gray-300 max-w-2xl">
|
||||||
Centralisez, déployez et contrôlez tous vos bots depuis un tableau de bord moderne. Démarrez,
|
{t("hero.description")}
|
||||||
stoppez et scalez en temps réel, en toute simplicité.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-8 flex items-center gap-4">
|
<div className="mt-8 flex items-center gap-4">
|
||||||
<Link href="/dashboard" className="inline-flex items-center gap-3 px-5 py-3 bg-white text-black rounded-md shadow hover:scale-[1.02] transition transform">
|
<Link href="/login" className="inline-flex items-center gap-3 px-5 py-3 bg-white text-black rounded-md shadow hover:scale-[1.02] transition transform">
|
||||||
Accéder au dashboard
|
{t("hero.ctaStart")}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link href="/login" className="inline-flex items-center gap-2 px-4 py-3 border border-gray-700 text-gray-200 rounded-md hover:bg-gray-800 transition">
|
<Link href="/login" className="inline-flex items-center gap-2 px-4 py-3 border border-gray-700 text-gray-200 rounded-md hover:bg-gray-800 transition">
|
||||||
Se connecter
|
{t("hero.ctaLogin")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,20 +51,22 @@ export default function Hero() {
|
|||||||
<div key={i} className="flex items-center justify-between bg-gray-900/40 p-3 rounded-lg">
|
<div key={i} className="flex items-center justify-between bg-gray-900/40 p-3 rounded-lg">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-white">{b.name}</div>
|
<div className="text-sm font-medium text-white">{b.name}</div>
|
||||||
<div className="text-xs text-gray-400">Instances: 1 · CPU 35%</div>
|
<div className="text-xs text-gray-400">{t("hero.card.instanceMetrics")}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className={`px-2 py-1 text-xs rounded-full ${b.status === "online" ? "bg-green-500/20 text-green-300" : "bg-gray-700 text-gray-300"}`}>
|
<span className={`px-2 py-1 text-xs rounded-full ${b.status === "online" ? "bg-green-500/20 text-green-300" : "bg-gray-700 text-gray-300"}`}>
|
||||||
{b.status === "online" ? "Online" : "Stopped"}
|
{b.status === "online" ? t("hero.status.online") : t("hero.status.stopped")}
|
||||||
</span>
|
</span>
|
||||||
<button className="px-3 py-1 bg-white/10 text-sm rounded-md hover:bg-white/20 transition">Manage</button>
|
<button className="px-3 py-1 bg-white/10 text-sm rounded-md hover:bg-white/20 transition">
|
||||||
|
{t("hero.card.manage")}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 text-xs text-gray-400">Aperçu en temps réel du statut de vos bots</div>
|
<div className="mt-4 text-xs text-gray-400">{t("hero.card.preview")}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,23 +1,27 @@
|
|||||||
export default function HowItWorks() {
|
import { getT } from "../i18n/server";
|
||||||
|
|
||||||
|
export default async function HowItWorks() {
|
||||||
|
const t = await getT();
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: "Ajouter un bot",
|
title: t("howItWorks.steps.addBot.title"),
|
||||||
desc: "Connectez votre application Discord via OAuth et enregistrez-la dans Shadow.",
|
desc: t("howItWorks.steps.addBot.description"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Gérer depuis le dashboard",
|
title: t("howItWorks.steps.dashboard.title"),
|
||||||
desc: "Accédez aux commandes, aux logs et aux paramètres depuis une interface centralisée.",
|
desc: t("howItWorks.steps.dashboard.description"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Contrôler en temps réel",
|
title: t("howItWorks.steps.realtime.title"),
|
||||||
desc: "Démarrez, arrêtez et scalez vos bots instantanément selon la demande.",
|
desc: t("howItWorks.steps.realtime.description"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-bold text-white">Comment ça marche</h2>
|
<h2 className="text-2xl font-bold text-white">{t("howItWorks.title")}</h2>
|
||||||
<p className="mt-2 text-gray-400 max-w-2xl">Trois étapes simples pour prendre le contrôle de vos bots.</p>
|
<p className="mt-2 text-gray-400 max-w-2xl">{t("howItWorks.subtitle")}</p>
|
||||||
|
|
||||||
<div className="mt-6 grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="mt-6 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
{steps.map((s, i) => (
|
{steps.map((s, i) => (
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useLocale } from "next-intl";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { useTransition } from "react";
|
||||||
|
|
||||||
|
import { useT } from "../i18n/client";
|
||||||
|
import { usePathname, useRouter } from "../i18n/navigation";
|
||||||
|
import { routing, type AppLocale } from "../i18n/routing";
|
||||||
|
|
||||||
|
export default function LanguageSwitcher() {
|
||||||
|
const t = useT();
|
||||||
|
const locale = useLocale() as AppLocale;
|
||||||
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
const localeLabels: Record<AppLocale, string> = {
|
||||||
|
en: t("localeSwitcher.locales.en"),
|
||||||
|
fr: t("localeSwitcher.locales.fr"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLocaleChange = (nextLocale: AppLocale) => {
|
||||||
|
const query = Object.fromEntries(searchParams.entries());
|
||||||
|
|
||||||
|
startTransition(() => {
|
||||||
|
router.replace({ pathname, query }, { locale: nextLocale });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label className="locale-switcher">
|
||||||
|
<span className="sr-only">{t("localeSwitcher.label")}</span>
|
||||||
|
<select
|
||||||
|
aria-label={t("localeSwitcher.label")}
|
||||||
|
disabled={isPending}
|
||||||
|
onChange={(event) => handleLocaleChange(event.target.value as AppLocale)}
|
||||||
|
value={locale}
|
||||||
|
>
|
||||||
|
{routing.locales.map((availableLocale) => (
|
||||||
|
<option key={availableLocale} value={availableLocale}>
|
||||||
|
{localeLabels[availableLocale]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Link from "next/link";
|
import { Link } from "../i18n/navigation";
|
||||||
|
|
||||||
export default function Logo({ className = "" }: { className?: string }) {
|
export default function Logo({ className = "" }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { type FormEvent, useCallback, useEffect, useState } from "react";
|
import { type FormEvent, useCallback, useEffect, useState } from "react";
|
||||||
|
import { useT } from "../i18n/client";
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -27,15 +28,25 @@ type DashboardClientProps = {
|
|||||||
apiBaseUrl: string;
|
apiBaseUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusLabel: Record<BotStatus, string> = {
|
type BotAction = "start" | "stop" | "restart";
|
||||||
stopped: "Arrete",
|
|
||||||
starting: "Demarrage",
|
|
||||||
running: "En ligne",
|
|
||||||
stopping: "Arret",
|
|
||||||
error: "Erreur",
|
|
||||||
};
|
|
||||||
|
|
||||||
export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
||||||
|
const t = useT();
|
||||||
|
|
||||||
|
const statusLabel: Record<BotStatus, string> = {
|
||||||
|
stopped: t("dashboard.status.stopped"),
|
||||||
|
starting: t("dashboard.status.starting"),
|
||||||
|
running: t("dashboard.status.running"),
|
||||||
|
stopping: t("dashboard.status.stopping"),
|
||||||
|
error: t("dashboard.status.error"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionLabel: Record<BotAction, string> = {
|
||||||
|
start: t("dashboard.actions.start"),
|
||||||
|
stop: t("dashboard.actions.stop"),
|
||||||
|
restart: t("dashboard.actions.restart"),
|
||||||
|
};
|
||||||
|
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [bots, setBots] = useState<Bot[]>([]);
|
const [bots, setBots] = useState<Bot[]>([]);
|
||||||
const [token, setToken] = useState("");
|
const [token, setToken] = useState("");
|
||||||
@@ -61,7 +72,7 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!meResponse.ok) {
|
if (!meResponse.ok) {
|
||||||
throw new Error("Impossible de recuperer la session utilisateur");
|
throw new Error(t("dashboard.errors.fetchSession"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const meJson = await meResponse.json();
|
const meJson = await meResponse.json();
|
||||||
@@ -72,18 +83,18 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!botsResponse.ok) {
|
if (!botsResponse.ok) {
|
||||||
throw new Error("Impossible de recuperer la liste des bots");
|
throw new Error(t("dashboard.errors.fetchBots"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const botsJson = await botsResponse.json();
|
const botsJson = await botsResponse.json();
|
||||||
setBots((botsJson.bots ?? []) as Bot[]);
|
setBots((botsJson.bots ?? []) as Bot[]);
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
const message = cause instanceof Error ? cause.message : "Erreur inattendue";
|
const message = cause instanceof Error ? cause.message : t("dashboard.errors.unexpected");
|
||||||
setError(message);
|
setError(message);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [apiBaseUrl]);
|
}, [apiBaseUrl, t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void refreshData();
|
void refreshData();
|
||||||
@@ -119,21 +130,21 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorJson = await response.json().catch(() => ({}));
|
const errorJson = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorJson.error ?? "Ajout du bot impossible");
|
throw new Error(errorJson.error ?? t("dashboard.errors.addBot"));
|
||||||
}
|
}
|
||||||
|
|
||||||
setToken("");
|
setToken("");
|
||||||
setDisplayName("");
|
setDisplayName("");
|
||||||
await refreshData();
|
await refreshData();
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
const message = cause instanceof Error ? cause.message : "Erreur inattendue";
|
const message = cause instanceof Error ? cause.message : t("dashboard.errors.unexpected");
|
||||||
setError(message);
|
setError(message);
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const triggerAction = async (botId: string, action: "start" | "stop" | "restart") => {
|
const triggerAction = async (botId: string, action: BotAction) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -144,28 +155,28 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorJson = await response.json().catch(() => ({}));
|
const errorJson = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorJson.error ?? `Action ${action} impossible`);
|
throw new Error(errorJson.error ?? t("dashboard.errors.actionFailed", { action: actionLabel[action] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
await refreshData();
|
await refreshData();
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
const message = cause instanceof Error ? cause.message : "Erreur inattendue";
|
const message = cause instanceof Error ? cause.message : t("dashboard.errors.unexpected");
|
||||||
setError(message);
|
setError(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <p className="muted">Chargement du dashboard...</p>;
|
return <p className="muted">{t("dashboard.loading")}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return (
|
return (
|
||||||
<div className="stack">
|
<div className="stack">
|
||||||
<p className="eyebrow">Session requise</p>
|
<p className="eyebrow">{t("dashboard.sessionRequired")}</p>
|
||||||
<h1>Connexion necessaire</h1>
|
<h1>{t("dashboard.loginRequired")}</h1>
|
||||||
<p>Ton dashboard est protege. Connecte-toi via Discord pour administrer tes bots.</p>
|
<p>{t("dashboard.loginDescription")}</p>
|
||||||
<a className="button-primary" href={`${apiBaseUrl}/auth/discord/login`}>
|
<a className="button-primary" href={`${apiBaseUrl}/auth/discord/login`}>
|
||||||
Se connecter
|
{t("dashboard.loginCta")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -175,12 +186,12 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
<div className="stack">
|
<div className="stack">
|
||||||
<header className="dashboard-header">
|
<header className="dashboard-header">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Tenant {user.tenantId}</p>
|
<p className="eyebrow">{t("dashboard.tenant", { tenantId: user.tenantId })}</p>
|
||||||
<h1>{user.username}</h1>
|
<h1>{user.username}</h1>
|
||||||
<p className="muted">Role: {user.role}</p>
|
<p className="muted">{t("dashboard.role", { role: user.role })}</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="button-ghost" onClick={handleLogout} type="button">
|
<button className="button-ghost" onClick={handleLogout} type="button">
|
||||||
Se deconnecter
|
{t("dashboard.logout")}
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -188,11 +199,11 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
|
|
||||||
<section className="card-grid">
|
<section className="card-grid">
|
||||||
<article className="card add-bot-card">
|
<article className="card add-bot-card">
|
||||||
<h2>Ajouter un bot</h2>
|
<h2>{t("dashboard.addBot.title")}</h2>
|
||||||
<p>Le token est verifie cote API puis chiffre avant stockage en base.</p>
|
<p>{t("dashboard.addBot.description")}</p>
|
||||||
<form className="stack-tight" onSubmit={handleAddBot}>
|
<form className="stack-tight" onSubmit={handleAddBot}>
|
||||||
<label>
|
<label>
|
||||||
Token bot Discord
|
{t("dashboard.addBot.tokenLabel")}
|
||||||
<input
|
<input
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
name="token"
|
name="token"
|
||||||
@@ -204,7 +215,7 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Nom d'affichage (optionnel)
|
{t("dashboard.addBot.displayNameLabel")}
|
||||||
<input
|
<input
|
||||||
name="displayName"
|
name="displayName"
|
||||||
onChange={(event) => setDisplayName(event.target.value)}
|
onChange={(event) => setDisplayName(event.target.value)}
|
||||||
@@ -214,40 +225,40 @@ export function DashboardClient({ apiBaseUrl }: DashboardClientProps) {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button className="button-primary" disabled={submitting} type="submit">
|
<button className="button-primary" disabled={submitting} type="submit">
|
||||||
{submitting ? "Validation en cours..." : "Ajouter le bot"}
|
{submitting ? t("dashboard.addBot.submitPending") : t("dashboard.addBot.submit")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="card bots-card">
|
<article className="card bots-card">
|
||||||
<div className="row-between">
|
<div className="row-between">
|
||||||
<h2>Mes bots</h2>
|
<h2>{t("dashboard.bots.title")}</h2>
|
||||||
<button className="button-ghost" onClick={() => void refreshData()} type="button">
|
<button className="button-ghost" onClick={() => void refreshData()} type="button">
|
||||||
Rafraichir
|
{t("dashboard.bots.refresh")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bots.length === 0 ? (
|
{bots.length === 0 ? (
|
||||||
<p className="muted">Aucun bot pour ce tenant.</p>
|
<p className="muted">{t("dashboard.bots.empty")}</p>
|
||||||
) : (
|
) : (
|
||||||
<ul className="bot-list">
|
<ul className="bot-list">
|
||||||
{bots.map((bot) => (
|
{bots.map((bot) => (
|
||||||
<li className="bot-item" key={bot.id}>
|
<li className="bot-item" key={bot.id}>
|
||||||
<div className="bot-main">
|
<div className="bot-main">
|
||||||
<p className="bot-name">{bot.displayName}</p>
|
<p className="bot-name">{bot.displayName}</p>
|
||||||
<p className="muted mono">Discord ID: {bot.discordBotId}</p>
|
<p className="muted mono">{t("dashboard.bots.discordId", { discordBotId: bot.discordBotId })}</p>
|
||||||
<p className={`status status-${bot.status}`}>{statusLabel[bot.status]}</p>
|
<p className={`status status-${bot.status}`}>{statusLabel[bot.status]}</p>
|
||||||
{bot.lastError ? <p className="error-inline">Derniere erreur: {bot.lastError}</p> : null}
|
{bot.lastError ? <p className="error-inline">{t("dashboard.bots.lastError", { message: bot.lastError })}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<button onClick={() => void triggerAction(bot.id, "start")} type="button">
|
<button onClick={() => void triggerAction(bot.id, "start")} type="button">
|
||||||
Start
|
{actionLabel.start}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => void triggerAction(bot.id, "stop")} type="button">
|
<button onClick={() => void triggerAction(bot.id, "stop")} type="button">
|
||||||
Stop
|
{actionLabel.stop}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => void triggerAction(bot.id, "restart")} type="button">
|
<button onClick={() => void triggerAction(bot.id, "restart")} type="button">
|
||||||
Restart
|
{actionLabel.restart}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export function useT() {
|
||||||
|
return useTranslations();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { createNavigation } from "next-intl/navigation";
|
||||||
|
|
||||||
|
import { routing } from "./routing";
|
||||||
|
|
||||||
|
export const { Link, redirect, usePathname, useRouter, getPathname } =
|
||||||
|
createNavigation(routing);
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
import "next-intl";
|
||||||
|
|
||||||
|
import enMessages from "../messages/en.json";
|
||||||
|
|
||||||
|
import { routing } from "./routing";
|
||||||
|
|
||||||
|
declare module "next-intl" {
|
||||||
|
interface AppConfig {
|
||||||
|
Locale: (typeof routing.locales)[number];
|
||||||
|
Messages: typeof enMessages;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { hasLocale } from "next-intl";
|
||||||
|
import { getRequestConfig } from "next-intl/server";
|
||||||
|
|
||||||
|
import { routing, type AppLocale } from "./routing";
|
||||||
|
|
||||||
|
type Messages = Record<string, unknown>;
|
||||||
|
|
||||||
|
const messageLoaders: Record<AppLocale, () => Promise<Messages>> = {
|
||||||
|
en: async () => (await import("../messages/en.json")).default as Messages,
|
||||||
|
fr: async () => (await import("../messages/fr.json")).default as Messages,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isObject = (value: unknown): value is Record<string, unknown> => {
|
||||||
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeMessages = (base: Messages, override: Messages): Messages => {
|
||||||
|
const merged: Messages = { ...base };
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(override)) {
|
||||||
|
const current = merged[key];
|
||||||
|
|
||||||
|
if (isObject(current) && isObject(value)) {
|
||||||
|
merged[key] = mergeMessages(current, value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
merged[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
|
const requestedLocale = await requestLocale;
|
||||||
|
const locale = hasLocale(routing.locales, requestedLocale)
|
||||||
|
? requestedLocale
|
||||||
|
: routing.defaultLocale;
|
||||||
|
|
||||||
|
const defaultMessages = await messageLoaders[routing.defaultLocale]();
|
||||||
|
const localeMessages = await messageLoaders[locale]();
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages:
|
||||||
|
locale === routing.defaultLocale
|
||||||
|
? defaultMessages
|
||||||
|
: mergeMessages(defaultMessages, localeMessages),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineRouting } from "next-intl/routing";
|
||||||
|
|
||||||
|
export const routing = defineRouting({
|
||||||
|
locales: ["en", "fr"],
|
||||||
|
defaultLocale: "en",
|
||||||
|
localePrefix: "always",
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AppLocale = (typeof routing.locales)[number];
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
|
export async function getT() {
|
||||||
|
return getTranslations();
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"title": "Shadow - Discord bot hosting (EUR 2/month per bot)",
|
||||||
|
"description": "Multi-tenant SaaS platform to host and manage Discord bots with auto commands, components v2, multi-language support and dynamic variables."
|
||||||
|
},
|
||||||
|
"localeSwitcher": {
|
||||||
|
"label": "Language",
|
||||||
|
"locales": {
|
||||||
|
"en": "English",
|
||||||
|
"fr": "Francais"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"navLogin": "Sign in",
|
||||||
|
"title": "Shadow - Host your Discord bots (from EUR 2/month per bot)",
|
||||||
|
"description": "Host and manage your bots with auto commands (prefix and slash), components v2, automatic help, multi-language support and dynamic variables.",
|
||||||
|
"ctaStart": "Get started - EUR 2/month",
|
||||||
|
"ctaLogin": "Sign in",
|
||||||
|
"bots": {
|
||||||
|
"moderation": "Moderation",
|
||||||
|
"welcome": "Welcome",
|
||||||
|
"analytics": "Analytics"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"online": "Online",
|
||||||
|
"stopped": "Stopped"
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"instanceMetrics": "Instances: 1 | CPU 35%",
|
||||||
|
"manage": "Manage",
|
||||||
|
"preview": "Real-time preview of your bot status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"title": "Main features",
|
||||||
|
"subtitle": "Everything you need to run your bots in production.",
|
||||||
|
"items": {
|
||||||
|
"autoCommands": {
|
||||||
|
"title": "Automatic commands",
|
||||||
|
"description": "Prefix and slash commands are generated and synced automatically for each bot."
|
||||||
|
},
|
||||||
|
"componentsV2": {
|
||||||
|
"title": "Components v2",
|
||||||
|
"description": "Full support for components v2 and reliable rich interactions."
|
||||||
|
},
|
||||||
|
"autoHelp": {
|
||||||
|
"title": "Automatic help",
|
||||||
|
"description": "Help and command documentation generated automatically."
|
||||||
|
},
|
||||||
|
"multiLanguage": {
|
||||||
|
"title": "Multi-language",
|
||||||
|
"description": "Translate messages and command labels, then enable multiple locales quickly."
|
||||||
|
},
|
||||||
|
"dynamicVariables": {
|
||||||
|
"title": "Dynamic variables",
|
||||||
|
"description": "Dynamic templates with variables like bot_latency, guild_count and more."
|
||||||
|
},
|
||||||
|
"hosting": {
|
||||||
|
"title": "Hosting and scalability",
|
||||||
|
"description": "Simple plans at EUR 2/month per bot with monitoring and scalability included."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"howItWorks": {
|
||||||
|
"title": "How it works",
|
||||||
|
"subtitle": "Three simple steps to take control of your bots.",
|
||||||
|
"steps": {
|
||||||
|
"addBot": {
|
||||||
|
"title": "Add a bot",
|
||||||
|
"description": "Connect your Discord application with OAuth and register it in Shadow."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Manage from dashboard",
|
||||||
|
"description": "Access commands, logs and settings from one centralized interface."
|
||||||
|
},
|
||||||
|
"realtime": {
|
||||||
|
"title": "Control in real time",
|
||||||
|
"description": "Start, stop and scale your bots instantly based on demand."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"tagline": "Discord bot hosting - plans from EUR 2/month per bot"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"eyebrow": "Shadow - Discord bot hosting",
|
||||||
|
"title": "Connect your Discord account",
|
||||||
|
"description": "Authenticate with OAuth2 to access your space and host your bots.",
|
||||||
|
"cta": "Continue with Discord"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"loading": "Loading dashboard...",
|
||||||
|
"sessionRequired": "Session required",
|
||||||
|
"loginRequired": "Sign in required",
|
||||||
|
"loginDescription": "Your dashboard is protected. Sign in with Discord to manage your bots.",
|
||||||
|
"loginCta": "Sign in",
|
||||||
|
"tenant": "Tenant {tenantId}",
|
||||||
|
"role": "Role: {role}",
|
||||||
|
"logout": "Sign out",
|
||||||
|
"addBot": {
|
||||||
|
"title": "Add a bot",
|
||||||
|
"description": "The token is validated by the API and encrypted before it is stored.",
|
||||||
|
"tokenLabel": "Discord bot token",
|
||||||
|
"displayNameLabel": "Display name (optional)",
|
||||||
|
"submit": "Add bot",
|
||||||
|
"submitPending": "Validating..."
|
||||||
|
},
|
||||||
|
"bots": {
|
||||||
|
"title": "My bots",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"empty": "No bot for this tenant.",
|
||||||
|
"discordId": "Discord ID: {discordBotId}",
|
||||||
|
"lastError": "Last error: {message}"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"stopped": "Stopped",
|
||||||
|
"starting": "Starting",
|
||||||
|
"running": "Online",
|
||||||
|
"stopping": "Stopping",
|
||||||
|
"error": "Error"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"start": "Start",
|
||||||
|
"stop": "Stop",
|
||||||
|
"restart": "Restart"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fetchSession": "Unable to fetch user session",
|
||||||
|
"fetchBots": "Unable to fetch bot list",
|
||||||
|
"unexpected": "Unexpected error",
|
||||||
|
"addBot": "Unable to add bot",
|
||||||
|
"actionFailed": "Action failed: {action}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"title": "Shadow - Hebergement de bots Discord (a partir de 2 EUR/mois par bot)",
|
||||||
|
"description": "Plateforme SaaS multi-tenant pour heberger et gerer vos bots Discord avec commandes automatiques, components v2, multi-langue et variables dynamiques."
|
||||||
|
},
|
||||||
|
"localeSwitcher": {
|
||||||
|
"label": "Langue",
|
||||||
|
"locales": {
|
||||||
|
"en": "English",
|
||||||
|
"fr": "Francais"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hero": {
|
||||||
|
"navLogin": "Se connecter",
|
||||||
|
"title": "Shadow - Hebergez vos bots Discord (a partir de 2 EUR/mois par bot)",
|
||||||
|
"description": "Hebergez et gerez vos bots avec commandes automatiques (prefix et slash), components v2, aide automatique, support multi-langue et variables dynamiques.",
|
||||||
|
"ctaStart": "Commencer - 2 EUR/mois",
|
||||||
|
"ctaLogin": "Se connecter",
|
||||||
|
"bots": {
|
||||||
|
"moderation": "Moderation",
|
||||||
|
"welcome": "Bienvenue",
|
||||||
|
"analytics": "Analytics"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"online": "En ligne",
|
||||||
|
"stopped": "Arrete"
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"instanceMetrics": "Instances: 1 | CPU 35%",
|
||||||
|
"manage": "Gerer",
|
||||||
|
"preview": "Apercu en temps reel du statut de vos bots"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"title": "Fonctionnalites principales",
|
||||||
|
"subtitle": "Tout ce quil faut pour piloter vos bots en production.",
|
||||||
|
"items": {
|
||||||
|
"autoCommands": {
|
||||||
|
"title": "Commandes automatiques",
|
||||||
|
"description": "Les commandes prefix et slash sont generees et synchronisees automatiquement pour chaque bot."
|
||||||
|
},
|
||||||
|
"componentsV2": {
|
||||||
|
"title": "Components v2",
|
||||||
|
"description": "Support complet des components v2 et des interactions riches fiables."
|
||||||
|
},
|
||||||
|
"autoHelp": {
|
||||||
|
"title": "Aide automatique",
|
||||||
|
"description": "Aide et documentation des commandes generees automatiquement."
|
||||||
|
},
|
||||||
|
"multiLanguage": {
|
||||||
|
"title": "Multi-langue",
|
||||||
|
"description": "Traduisez les messages et les labels de commandes puis activez plusieurs langues facilement."
|
||||||
|
},
|
||||||
|
"dynamicVariables": {
|
||||||
|
"title": "Variables dynamiques",
|
||||||
|
"description": "Templates dynamiques avec des variables comme bot_latency, guild_count et plus."
|
||||||
|
},
|
||||||
|
"hosting": {
|
||||||
|
"title": "Hebergement et scalabilite",
|
||||||
|
"description": "Plans simples a 2 EUR/mois par bot avec monitoring et scalabilite inclus."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"howItWorks": {
|
||||||
|
"title": "Comment ca marche",
|
||||||
|
"subtitle": "Trois etapes simples pour prendre le controle de vos bots.",
|
||||||
|
"steps": {
|
||||||
|
"addBot": {
|
||||||
|
"title": "Ajouter un bot",
|
||||||
|
"description": "Connectez votre application Discord via OAuth et enregistrez-la dans Shadow."
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"title": "Gerer depuis le dashboard",
|
||||||
|
"description": "Accedez aux commandes, aux logs et aux parametres depuis une interface centralisee."
|
||||||
|
},
|
||||||
|
"realtime": {
|
||||||
|
"title": "Controler en temps reel",
|
||||||
|
"description": "Demarrez, arretez et scalez vos bots instantanement selon la demande."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"tagline": "Hebergement de bots Discord - plans des 2 EUR/mois par bot"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"eyebrow": "Shadow - Hebergement de bots Discord",
|
||||||
|
"title": "Connectez votre compte Discord",
|
||||||
|
"description": "Authentifiez-vous via OAuth2 pour acceder a votre espace et heberger vos bots.",
|
||||||
|
"cta": "Continuer avec Discord"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"loading": "Chargement du dashboard...",
|
||||||
|
"sessionRequired": "Session requise",
|
||||||
|
"loginRequired": "Connexion necessaire",
|
||||||
|
"loginDescription": "Votre dashboard est protege. Connectez-vous via Discord pour administrer vos bots.",
|
||||||
|
"loginCta": "Se connecter",
|
||||||
|
"tenant": "Tenant {tenantId}",
|
||||||
|
"role": "Role: {role}",
|
||||||
|
"logout": "Se deconnecter",
|
||||||
|
"addBot": {
|
||||||
|
"title": "Ajouter un bot",
|
||||||
|
"description": "Le token est verifie cote API puis chiffre avant stockage.",
|
||||||
|
"tokenLabel": "Token bot Discord",
|
||||||
|
"displayNameLabel": "Nom daffichage (optionnel)",
|
||||||
|
"submit": "Ajouter le bot",
|
||||||
|
"submitPending": "Validation en cours..."
|
||||||
|
},
|
||||||
|
"bots": {
|
||||||
|
"title": "Mes bots",
|
||||||
|
"refresh": "Rafraichir",
|
||||||
|
"empty": "Aucun bot pour ce tenant.",
|
||||||
|
"discordId": "Discord ID: {discordBotId}",
|
||||||
|
"lastError": "Derniere erreur: {message}"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"stopped": "Arrete",
|
||||||
|
"starting": "Demarrage",
|
||||||
|
"running": "En ligne",
|
||||||
|
"stopping": "Arret",
|
||||||
|
"error": "Erreur"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"start": "Demarrer",
|
||||||
|
"stop": "Arreter",
|
||||||
|
"restart": "Redemarrer"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fetchSession": "Impossible de recuperer la session utilisateur",
|
||||||
|
"fetchBots": "Impossible de recuperer la liste des bots",
|
||||||
|
"unexpected": "Erreur inattendue",
|
||||||
|
"addBot": "Ajout du bot impossible",
|
||||||
|
"actionFailed": "Action impossible: {action}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import createMiddleware from "next-intl/middleware";
|
||||||
|
|
||||||
|
import { routing } from "./i18n/routing";
|
||||||
|
|
||||||
|
export default createMiddleware(routing);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
||||||
|
};
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
|
||||||
|
const withNextIntl = createNextIntlPlugin("./i18n/request.ts");
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default withNextIntl(nextConfig);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "15.1.2",
|
"next": "15.1.2",
|
||||||
|
"next-intl": "^4.9.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+690
-3
@@ -68,6 +68,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "15.1.2",
|
"next": "15.1.2",
|
||||||
|
"next-intl": "^4.9.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
@@ -681,6 +682,36 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "3.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.4.tgz",
|
||||||
|
"integrity": "sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/icu-skeleton-parser": "2.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.3.tgz",
|
||||||
|
"integrity": "sha512-pHUjWb9NuhnMs8+PxQdzBtZRFJHlGhrURGAbm6Ltwl82BFajeuiIR3jblSa7ia3r62rXe/0YtVpUG3xWr5bFCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "3.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@img/sharp-darwin-arm64": {
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
"version": "0.33.5",
|
"version": "0.33.5",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
||||||
@@ -1586,6 +1617,313 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@parcel/watcher": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.3",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"node-addon-api": "^7.0.0",
|
||||||
|
"picomatch": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@parcel/watcher-android-arm64": "2.5.6",
|
||||||
|
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||||
|
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||||
|
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||||
|
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||||
|
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||||
|
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||||
|
"@parcel/watcher-win32-x64": "2.5.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-android-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-arm64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-ia32": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-x64": {
|
||||||
|
"version": "2.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||||
|
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher/node_modules/picomatch": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@pinojs/redact": {
|
"node_modules/@pinojs/redact": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
|
||||||
@@ -1641,6 +1979,204 @@
|
|||||||
"npm": ">=7.0.0"
|
"npm": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@schummar/icu-type-parser": {
|
||||||
|
"version": "1.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
|
||||||
|
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-arm64": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-x64": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-musl": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-ppc64-gnu": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-s390x-gnu": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-gnu": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-musl": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-x64-msvc": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
@@ -1656,6 +2192,15 @@
|
|||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@swc/types": {
|
||||||
|
"version": "0.1.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
|
||||||
|
"integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/body-parser": {
|
"node_modules/@types/body-parser": {
|
||||||
"version": "1.19.6",
|
"version": "1.19.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||||
@@ -2402,7 +2947,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -2942,12 +3486,37 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/icu-minify": {
|
||||||
|
"version": "4.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.1.tgz",
|
||||||
|
"integrity": "sha512-6NkfF9GHHFouqnz+wuiLjCWQiyxoEyJ5liUv4Jxxo/8wyhV7MY0L0iTEGDAVEa4aAD58WqTxFMa20S5nyMjwNw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/icu-messageformat-parser": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "11.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.1.tgz",
|
||||||
|
"integrity": "sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "3.1.2",
|
||||||
|
"@formatjs/icu-messageformat-parser": "3.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ioredis": {
|
"node_modules/ioredis": {
|
||||||
"version": "5.10.1",
|
"version": "5.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
|
||||||
@@ -3021,7 +3590,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -3031,7 +3599,6 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@@ -3304,6 +3871,7 @@
|
|||||||
"integrity": "sha512-nLJDV7peNy+0oHlmY2JZjzMfJ8Aj0/dd3jCwSZS8ZiO5nkQfcZRqDrRN3U5rJtqVTQneIOGZzb6LCNrk7trMCQ==",
|
"integrity": "sha512-nLJDV7peNy+0oHlmY2JZjzMfJ8Aj0/dd3jCwSZS8ZiO5nkQfcZRqDrRN3U5rJtqVTQneIOGZzb6LCNrk7trMCQ==",
|
||||||
"deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.",
|
"deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.1.2",
|
"@next/env": "15.1.2",
|
||||||
"@swc/counter": "0.1.3",
|
"@swc/counter": "0.1.3",
|
||||||
@@ -3353,12 +3921,104 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-intl": {
|
||||||
|
"version": "4.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.1.tgz",
|
||||||
|
"integrity": "sha512-N7ga0CjtYcdxNvaKNIi6eJ2mmatlHK5hp8rt0YO2Omoc1m0gean242/Ukdj6+gJNiReBVcYIjK0HZeNx7CV1ug==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "^0.8.1",
|
||||||
|
"@parcel/watcher": "^2.4.1",
|
||||||
|
"@swc/core": "^1.15.2",
|
||||||
|
"icu-minify": "^4.9.1",
|
||||||
|
"negotiator": "^1.0.0",
|
||||||
|
"next-intl-swc-plugin-extractor": "^4.9.1",
|
||||||
|
"po-parser": "^2.1.1",
|
||||||
|
"use-intl": "^4.9.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-intl-swc-plugin-extractor": {
|
||||||
|
"version": "4.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.1.tgz",
|
||||||
|
"integrity": "sha512-8whJJ6oxJz8JqkHarggmmuEDyXgC7nEnaPhZD91CJwEWW4xp0AST3Mw17YxvHyP2vAF3taWfFbs1maD+WWtz3w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/next-intl/node_modules/@swc/core": {
|
||||||
|
"version": "1.15.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz",
|
||||||
|
"integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3",
|
||||||
|
"@swc/types": "^0.1.26"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/swc"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@swc/core-darwin-arm64": "1.15.30",
|
||||||
|
"@swc/core-darwin-x64": "1.15.30",
|
||||||
|
"@swc/core-linux-arm-gnueabihf": "1.15.30",
|
||||||
|
"@swc/core-linux-arm64-gnu": "1.15.30",
|
||||||
|
"@swc/core-linux-arm64-musl": "1.15.30",
|
||||||
|
"@swc/core-linux-ppc64-gnu": "1.15.30",
|
||||||
|
"@swc/core-linux-s390x-gnu": "1.15.30",
|
||||||
|
"@swc/core-linux-x64-gnu": "1.15.30",
|
||||||
|
"@swc/core-linux-x64-musl": "1.15.30",
|
||||||
|
"@swc/core-win32-arm64-msvc": "1.15.30",
|
||||||
|
"@swc/core-win32-ia32-msvc": "1.15.30",
|
||||||
|
"@swc/core-win32-x64-msvc": "1.15.30"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@swc/helpers": ">=0.5.17"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@swc/helpers": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-intl/node_modules/negotiator": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-abort-controller": {
|
"node_modules/node-abort-controller": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||||
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
|
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/node-gyp-build-optional-packages": {
|
"node_modules/node-gyp-build-optional-packages": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
||||||
@@ -3631,6 +4291,12 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/po-parser": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
@@ -4710,6 +5376,27 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-intl": {
|
||||||
|
"version": "4.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.1.tgz",
|
||||||
|
"integrity": "sha512-iGVV/xFYlhe3btafRlL8RPLD2Jsuet4yqn9DR6LWWbMhULsJnXgLonDkzDmsAIBIwFtk02oJuX/Ox2vwHKF+UQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "^3.1.0",
|
||||||
|
"@schummar/icu-type-parser": "1.21.5",
|
||||||
|
"icu-minify": "^4.9.1",
|
||||||
|
"intl-messageformat": "^11.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user