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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@afterlife/shared": "*",
|
||||
"next": "^15",
|
||||
"next-intl": "^4.8.3",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"@afterlife/shared": "*"
|
||||
"react-dom": "^19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"tailwindcss": "^4",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"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