feat: configure next-intl i18n with ES/EN locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import type { NextConfig } from "next";
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {};
|
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
||||||
|
|
||||||
export default nextConfig;
|
const nextConfig = {};
|
||||||
|
|
||||||
|
export default withNextIntl(nextConfig);
|
||||||
|
|||||||
@@ -9,19 +9,20 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@afterlife/shared": "*",
|
||||||
"next": "^15",
|
"next": "^15",
|
||||||
|
"next-intl": "^4.8.3",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-dom": "^19",
|
"react-dom": "^19"
|
||||||
"@afterlife/shared": "*"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"tailwindcss": "^4",
|
|
||||||
"@tailwindcss/postcss": "^4",
|
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "^15"
|
"eslint-config-next": "^15",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
apps/web/src/app/[locale]/layout.tsx
Normal file
35
apps/web/src/app/[locale]/layout.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
|
import { getMessages } from "next-intl/server";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { routing } from "@/i18n/routing";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Project Afterlife",
|
||||||
|
description: "Preserving online games that deserve a second life",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function LocaleLayout({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: Promise<{ locale: string }>;
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
if (!routing.locales.includes(locale as any)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
const messages = await getMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale}>
|
||||||
|
<body>
|
||||||
|
<NextIntlClientProvider messages={messages}>
|
||||||
|
{children}
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { Metadata } from "next";
|
|
||||||
import "./globals.css";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Project Afterlife",
|
|
||||||
description: "Preserving online games that deserve a second life",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<html lang="es">
|
|
||||||
<body>{children}</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
13
apps/web/src/i18n/request.ts
Normal file
13
apps/web/src/i18n/request.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { getRequestConfig } from "next-intl/server";
|
||||||
|
import { routing } from "./routing";
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
|
let locale = await requestLocale;
|
||||||
|
if (!locale || !routing.locales.includes(locale as any)) {
|
||||||
|
locale = routing.defaultLocale;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages: (await import(`../messages/${locale}.json`)).default,
|
||||||
|
};
|
||||||
|
});
|
||||||
6
apps/web/src/i18n/routing.ts
Normal file
6
apps/web/src/i18n/routing.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { defineRouting } from "next-intl/routing";
|
||||||
|
|
||||||
|
export const routing = defineRouting({
|
||||||
|
locales: ["es", "en"],
|
||||||
|
defaultLocale: "es",
|
||||||
|
});
|
||||||
63
apps/web/src/messages/en.json
Normal file
63
apps/web/src/messages/en.json
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"home": "Home",
|
||||||
|
"catalog": "Catalog",
|
||||||
|
"about": "About Us",
|
||||||
|
"donate": "Donations"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"hero_title": "Project Afterlife",
|
||||||
|
"hero_subtitle": "Preserving online games that deserve a second life",
|
||||||
|
"latest_games": "Latest restored games",
|
||||||
|
"view_all": "View all",
|
||||||
|
"donate_cta": "Support preservation"
|
||||||
|
},
|
||||||
|
"catalog": {
|
||||||
|
"title": "Game Catalog",
|
||||||
|
"filter_genre": "Genre",
|
||||||
|
"filter_status": "Server status",
|
||||||
|
"all": "All",
|
||||||
|
"no_results": "No games found"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"released": "Released",
|
||||||
|
"shutdown": "Shutdown",
|
||||||
|
"developer": "Developer",
|
||||||
|
"publisher": "Publisher",
|
||||||
|
"server_status": "Server status",
|
||||||
|
"play_now": "Play now",
|
||||||
|
"view_documentary": "View documentary",
|
||||||
|
"status_online": "Online",
|
||||||
|
"status_maintenance": "Maintenance",
|
||||||
|
"status_coming_soon": "Coming soon"
|
||||||
|
},
|
||||||
|
"documentary": {
|
||||||
|
"chapters": "Chapters",
|
||||||
|
"listen": "Listen",
|
||||||
|
"reading_progress": "Reading progress"
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"play": "Play",
|
||||||
|
"pause": "Pause",
|
||||||
|
"speed": "Speed",
|
||||||
|
"chapter_mode": "By chapter",
|
||||||
|
"continuous_mode": "Continuous"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "About Us",
|
||||||
|
"mission": "Our Mission",
|
||||||
|
"team": "The Team",
|
||||||
|
"contribute": "How to Contribute"
|
||||||
|
},
|
||||||
|
"donate": {
|
||||||
|
"title": "Donations",
|
||||||
|
"description": "Project Afterlife is funded exclusively by donations. Your support keeps these games alive.",
|
||||||
|
"patreon": "Donate on Patreon",
|
||||||
|
"kofi": "Donate on Ko-fi",
|
||||||
|
"transparency": "Fund transparency"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"rights": "Project Afterlife. Preserving gaming history.",
|
||||||
|
"language": "Language"
|
||||||
|
}
|
||||||
|
}
|
||||||
63
apps/web/src/messages/es.json
Normal file
63
apps/web/src/messages/es.json
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"home": "Inicio",
|
||||||
|
"catalog": "Catálogo",
|
||||||
|
"about": "Sobre Nosotros",
|
||||||
|
"donate": "Donaciones"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"hero_title": "Project Afterlife",
|
||||||
|
"hero_subtitle": "Preservando juegos online que merecen una segunda vida",
|
||||||
|
"latest_games": "Últimos juegos restaurados",
|
||||||
|
"view_all": "Ver todos",
|
||||||
|
"donate_cta": "Apoya la preservación"
|
||||||
|
},
|
||||||
|
"catalog": {
|
||||||
|
"title": "Catálogo de Juegos",
|
||||||
|
"filter_genre": "Género",
|
||||||
|
"filter_status": "Estado del servidor",
|
||||||
|
"all": "Todos",
|
||||||
|
"no_results": "No se encontraron juegos"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"released": "Lanzado",
|
||||||
|
"shutdown": "Cerrado",
|
||||||
|
"developer": "Desarrolladora",
|
||||||
|
"publisher": "Distribuidora",
|
||||||
|
"server_status": "Estado del servidor",
|
||||||
|
"play_now": "Jugar ahora",
|
||||||
|
"view_documentary": "Ver documental",
|
||||||
|
"status_online": "En línea",
|
||||||
|
"status_maintenance": "Mantenimiento",
|
||||||
|
"status_coming_soon": "Próximamente"
|
||||||
|
},
|
||||||
|
"documentary": {
|
||||||
|
"chapters": "Capítulos",
|
||||||
|
"listen": "Escuchar",
|
||||||
|
"reading_progress": "Progreso de lectura"
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"play": "Reproducir",
|
||||||
|
"pause": "Pausar",
|
||||||
|
"speed": "Velocidad",
|
||||||
|
"chapter_mode": "Por capítulo",
|
||||||
|
"continuous_mode": "Continuo"
|
||||||
|
},
|
||||||
|
"about": {
|
||||||
|
"title": "Sobre Nosotros",
|
||||||
|
"mission": "Nuestra Misión",
|
||||||
|
"team": "El Equipo",
|
||||||
|
"contribute": "Cómo Contribuir"
|
||||||
|
},
|
||||||
|
"donate": {
|
||||||
|
"title": "Donaciones",
|
||||||
|
"description": "Project Afterlife se financia exclusivamente con donaciones. Tu apoyo mantiene vivos estos juegos.",
|
||||||
|
"patreon": "Donar en Patreon",
|
||||||
|
"kofi": "Donar en Ko-fi",
|
||||||
|
"transparency": "Transparencia de fondos"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"rights": "Project Afterlife. Preservando la historia del gaming.",
|
||||||
|
"language": "Idioma"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
apps/web/src/middleware.ts
Normal file
8
apps/web/src/middleware.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import createMiddleware from "next-intl/middleware";
|
||||||
|
import { routing } from "./i18n/routing";
|
||||||
|
|
||||||
|
export default createMiddleware(routing);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
|
||||||
|
};
|
||||||
1731
package-lock.json
generated
1731
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user