feat(web): scaffold Next.js 14 app with Tailwind and next-intl
- App Router under src/app/[locale] with locale 'it' as default - next-intl middleware and getRequestConfig with setRequestLocale - Tailwind CSS with shared font variable and content from packages/ui - ESLint via @ketopath/eslint-config/nextjs, tsconfig via @ketopath/tsconfig/nextjs - Inter font, base layout, home page with translated copy and disclaimer Tweaks to shared eslint-config: - Disable consistent-type-imports for .cjs files (next parser is not @typescript-eslint) - Configure import resolver to discover tsconfig in apps/* and packages/* - Drop *.config.* ignore patterns (overrides handle them with env.node) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
87ca5af765
commit
94eeb1d817
18 files changed
+249
-11
No files matched your search
@@ -0,0 +1,58 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getMessages, getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { locales, type Locale } from '@/i18n';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-sans',
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { locale },
|
||||
}: {
|
||||
params: { locale: string };
|
||||
}): Promise<Metadata> {
|
||||
const t = await getTranslations({ locale, namespace: 'Metadata' });
|
||||
return {
|
||||
title: t('title'),
|
||||
description: t('description'),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params: { locale },
|
||||
}: {
|
||||
children: ReactNode;
|
||||
params: { locale: string };
|
||||
}) {
|
||||
if (!locales.includes(locale as Locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
setRequestLocale(locale);
|
||||
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang={locale} className={inter.variable}>
|
||||
<body className="min-h-screen bg-white font-sans text-slate-900 antialiased">
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
|
||||
export default function HomePage({ params: { locale } }: { params: { locale: string } }) {
|
||||
setRequestLocale(locale);
|
||||
const t = useTranslations('Home');
|
||||
|
||||
return (
|
||||
<main className="mx-auto flex min-h-screen max-w-3xl flex-col items-start justify-center gap-6 px-6 py-16">
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{t('title')}</h1>
|
||||
<p className="text-lg text-slate-600">{t('tagline')}</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md bg-emerald-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-emerald-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500"
|
||||
>
|
||||
{t('ctaPrimary')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-400"
|
||||
>
|
||||
{t('ctaSecondary')}
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-8 text-xs text-slate-500">{t('disclaimer')}</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
|
||||
export const locales = ['it'] as const;
|
||||
export const defaultLocale = 'it' as const;
|
||||
|
||||
export type Locale = (typeof locales)[number];
|
||||
|
||||
export default getRequestConfig(async ({ locale }) => {
|
||||
if (!locales.includes(locale as Locale)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return {
|
||||
locale: locale as Locale,
|
||||
messages: (await import(`../messages/${locale}.json`)).default,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import createMiddleware from 'next-intl/middleware';
|
||||
|
||||
import { defaultLocale, locales } from './i18n';
|
||||
|
||||
export default createMiddleware({
|
||||
locales,
|
||||
defaultLocale,
|
||||
localePrefix: 'as-needed',
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user