From cf23b5c8e559aef22dd485cff351e4b395a39d59 Mon Sep 17 00:00:00 2001 From: Saksham Jain Date: Thu, 13 Aug 2026 13:58:01 +0530 Subject: [PATCH 1/2] fix(ci): update workflow triggers, static i18n export, and deployment checks --- .github/workflows/ci.yml | 86 ++++++++++++++++++++---------------- .github/workflows/nextjs.yml | 67 ++++++++++++++-------------- AGENTS.md | 2 +- next.config.ts | 11 ++--- src/app/[locale]/layout.tsx | 21 ++++++--- src/i18n/metadata.ts | 28 +++++++----- src/i18n/request.ts | 11 ++++- 7 files changed, 133 insertions(+), 93 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ae5a88..3bb958a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,37 +1,49 @@ -name: Next.js CI - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [20.x] - - steps: - - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Run Lint - run: npm run lint - - - name: Build project - run: npm run build \ No newline at end of file +name: Next.js CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [22.x] + + steps: + - name: Checkout repository + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: ${{ matrix.node-version }} + cache: ${{ hashFiles('package-lock.json') != '' && 'npm' || '' }} + cache-dependency-path: ${{ hashFiles('package-lock.json') != '' && 'package-lock.json' || '' }} + + - name: Install dependencies + run: | + if [ -f package-lock.json ]; then + npm ci + else + npm install + fi + + - name: Run Lint + run: npm run lint + + - name: Build project + run: npm run build \ No newline at end of file diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml index ed74736..775e545 100644 --- a/.github/workflows/nextjs.yml +++ b/.github/workflows/nextjs.yml @@ -1,36 +1,28 @@ -# Sample workflow for building and deploying a Next.js site to GitHub Pages -# -# To get started with Next.js see: https://nextjs.org/docs/getting-started -# name: Deploy Next.js site to Pages on: - # Runs on pushes targeting the default branch push: branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab + pull_request: + branches: ["main"] workflow_dispatch: -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read - pages: write - id-token: write -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: - group: "pages" + group: ${{ github.event_name == 'pull_request' && format('pages-pr-{0}', github.event.pull_request.number) || 'pages' }} cancel-in-progress: false jobs: - # Build job build: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - name: Detect package manager id: detect-package-manager run: | @@ -38,50 +30,60 @@ jobs: echo "manager=yarn" >> $GITHUB_OUTPUT echo "command=install" >> $GITHUB_OUTPUT echo "runner=yarn" >> $GITHUB_OUTPUT + echo "lockfile=yarn.lock" >> $GITHUB_OUTPUT exit 0 - elif [ -f "${{ github.workspace }}/package.json" ]; then + elif [ -f "${{ github.workspace }}/package-lock.json" ]; then echo "manager=npm" >> $GITHUB_OUTPUT echo "command=ci" >> $GITHUB_OUTPUT echo "runner=npx --no-install" >> $GITHUB_OUTPUT + echo "lockfile=package-lock.json" >> $GITHUB_OUTPUT + exit 0 + elif [ -f "${{ github.workspace }}/package.json" ]; then + echo "manager=npm" >> $GITHUB_OUTPUT + echo "command=install" >> $GITHUB_OUTPUT + echo "runner=npx --no-install" >> $GITHUB_OUTPUT + echo "lockfile=" >> $GITHUB_OUTPUT exit 0 else echo "Unable to determine package manager" exit 1 fi + - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "20" - cache: ${{ steps.detect-package-manager.outputs.manager }} + node-version: "22" + cache: ${{ steps.detect-package-manager.outputs.lockfile != '' && steps.detect-package-manager.outputs.manager || '' }} + cache-dependency-path: ${{ steps.detect-package-manager.outputs.lockfile }} + - name: Setup Pages - uses: actions/configure-pages@v5 - with: - # Automatically inject basePath in your Next.js configuration file and disable - # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). - # - # You may remove this line if you want to manage the configuration yourself. - static_site_generator: next + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v5 + - name: Restore cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | .next/cache - # Generate a new cache whenever packages or source files change. key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} - # If source files changed but packages didn't, rebuild from a prior cache. restore-keys: | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- + - name: Install dependencies run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} + - name: Build with Next.js run: ${{ steps.detect-package-manager.outputs.runner }} next build + - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 with: path: ./out - # Deployment job deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} @@ -90,4 +92,5 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 + diff --git a/AGENTS.md b/AGENTS.md index 24be58b..01ffc38 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,6 @@ TODO: Project-specific context and directives for AI agents. ## 📦 Project Boundaries -- **Config alias map:** Webpack & Turbopack alias resolvers are manually defined in [`next.config.ts`](next.config.ts) mapping `"next-intl/config"` to our local request setup. Do not import the native `@swc/core` compiler plugin wrapper `createNextIntlPlugin` directly to avoid security policy native module blocks. +- **Config Alias Map:** Turbopack and Webpack alias resolvers map `"next-intl/config"` to our local request setup inside [`next.config.ts`](next.config.ts) using `createNextIntlPlugin("./src/i18n/request.ts")`. - **Branding Assets:** The official branding logo, favicon, and style specifications reside inside [`public/brand/`](public/brand/). diff --git a/next.config.ts b/next.config.ts index ef8ec19..929c1f4 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,17 +1,18 @@ import type { NextConfig } from "next"; +import createNextIntlPlugin from "next-intl/plugin"; import path from "path"; +const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); + const nextConfig: NextConfig = { output: "export", images: { unoptimized: true, }, turbopack: { - root: path.resolve(process.cwd()), - resolveAlias: { - "next-intl/config": "./src/i18n/request.ts", - }, + root: path.resolve(__dirname), }, }; -export default nextConfig; +export default withNextIntl(nextConfig); + diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 59d899e..85df97e 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -3,20 +3,29 @@ import { Inter, Noto_Sans_Devanagari } from "next/font/google"; import { routing } from "@/i18n/routing"; import { generateLocaleMetadata } from "@/i18n/metadata"; import { notFound } from "next/navigation"; -import { getMessages, setRequestLocale } from "next-intl/server"; +import { setRequestLocale } from "next-intl/server"; import { NextIntlClientProvider } from "next-intl"; +import enMessages from "@/messages/en.json"; +import hiMessages from "@/messages/hi.json"; import { ThemeProvider } from "@/components/providers/theme-provider"; import { LenisProvider } from "@/components/providers/lenis-provider"; import "./globals.css"; +const messagesMap: Record> = { + en: enMessages as Record, + hi: hiMessages as Record, +}; + const inter = Inter({ variable: "--font-inter", subsets: ["latin"], + display: "swap", }); const devanagari = Noto_Sans_Devanagari({ variable: "--font-devanagari", subsets: ["devanagari"], + display: "swap", }); export const dynamicParams = false; @@ -34,6 +43,7 @@ export async function generateMetadata({ if (!routing.locales.includes(locale as (typeof routing.locales)[number])) { notFound(); } + setRequestLocale(locale); return generateLocaleMetadata(locale, "Home"); } @@ -55,8 +65,8 @@ export default async function RootLayout({ // Enable static rendering setRequestLocale(locale); - // Provide messages to Client Components - const messages = await getMessages({ locale }); + // Provide messages to Client Components directly via messagesMap + const messages = messagesMap[locale] || enMessages; return ( - - + + {children} @@ -76,3 +86,4 @@ export default async function RootLayout({ ); } + diff --git a/src/i18n/metadata.ts b/src/i18n/metadata.ts index b9e08c9..59745a6 100644 --- a/src/i18n/metadata.ts +++ b/src/i18n/metadata.ts @@ -1,20 +1,26 @@ -import { getTranslations } from 'next-intl/server'; import type { Metadata } from 'next'; +import enMessages from '../messages/en.json'; +import hiMessages from '../messages/hi.json'; + +const messagesMap: Record = { + en: enMessages, + hi: hiMessages, +}; export async function generateLocaleMetadata( locale: string, - namespace: string + namespace: keyof typeof enMessages = 'Home' ): Promise { - const t = await getTranslations({ locale, namespace }); + const messages = messagesMap[locale] || enMessages; + const meta = (messages[namespace] as typeof enMessages.Home) || enMessages.Home; const rawSiteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://TODO:project.aossie.org'; const siteUrl = rawSiteUrl.replace(/\/$/, ''); const localeUrl = `${siteUrl}/${locale}`; - return { - title: t('metaTitle'), - description: t('metaDescription'), + title: meta.metaTitle, + description: meta.metaDescription, icons: { icon: '/brand/icons/favicon.ico', }, @@ -26,10 +32,9 @@ export async function generateLocaleMetadata( }, }, openGraph: { - title: t('metaTitle'), - description: t('metaDescription'), + title: meta.metaTitle, + description: meta.metaDescription, url: localeUrl, - siteName: 'AOSSIE', images: [ { @@ -44,9 +49,10 @@ export async function generateLocaleMetadata( }, twitter: { card: 'summary_large_image', - title: t('metaTitle'), - description: t('metaDescription'), + title: meta.metaTitle, + description: meta.metaDescription, images: [`${siteUrl}/brand/icons/aossie_logo.svg`], }, }; } + diff --git a/src/i18n/request.ts b/src/i18n/request.ts index c21b424..6913b46 100644 --- a/src/i18n/request.ts +++ b/src/i18n/request.ts @@ -1,16 +1,23 @@ import { getRequestConfig } from 'next-intl/server'; +import enMessages from '../messages/en.json'; +import hiMessages from '../messages/hi.json'; import { routing } from './routing'; +const messagesMap: Record> = { + en: enMessages as Record, + hi: hiMessages as Record, +}; + export default getRequestConfig(async ({ requestLocale }) => { let locale = await requestLocale; - // Ensure the locale is valid if (!locale || !routing.locales.includes(locale as (typeof routing.locales)[number])) { locale = routing.defaultLocale; } return { locale, - messages: (await import(`../messages/${locale}.json`)).default, + messages: messagesMap[locale] || enMessages, }; }); + From 745ba933e34594c844dd34d8fc14ee6af6ad54b5 Mon Sep 17 00:00:00 2001 From: Saksham Jain Date: Thu, 13 Aug 2026 14:18:56 +0530 Subject: [PATCH 2/2] refactor(i18n): centralize catalog map in messages.ts and restrict metadata namespace --- .github/workflows/ci.yml | 4 ++-- .github/workflows/nextjs.yml | 1 - src/app/[locale]/layout.tsx | 11 +++-------- src/i18n/messages.ts | 11 +++++++++++ src/i18n/metadata.ts | 14 ++++---------- src/i18n/request.ts | 10 ++-------- 6 files changed, 22 insertions(+), 29 deletions(-) create mode 100644 src/i18n/messages.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bb958a..bb2304d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Next.js CI on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] permissions: contents: read diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml index 775e545..34703c8 100644 --- a/.github/workflows/nextjs.yml +++ b/.github/workflows/nextjs.yml @@ -93,4 +93,3 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 - diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 85df97e..3d7ca06 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -5,17 +5,11 @@ import { generateLocaleMetadata } from "@/i18n/metadata"; import { notFound } from "next/navigation"; import { setRequestLocale } from "next-intl/server"; import { NextIntlClientProvider } from "next-intl"; -import enMessages from "@/messages/en.json"; -import hiMessages from "@/messages/hi.json"; +import { messagesMap, defaultMessages } from "@/i18n/messages"; import { ThemeProvider } from "@/components/providers/theme-provider"; import { LenisProvider } from "@/components/providers/lenis-provider"; import "./globals.css"; -const messagesMap: Record> = { - en: enMessages as Record, - hi: hiMessages as Record, -}; - const inter = Inter({ variable: "--font-inter", subsets: ["latin"], @@ -66,7 +60,7 @@ export default async function RootLayout({ setRequestLocale(locale); // Provide messages to Client Components directly via messagesMap - const messages = messagesMap[locale] || enMessages; + const messages = messagesMap[locale] || defaultMessages; return ( > = { + en: enMessages as Record, + hi: hiMessages as Record, +}; + +export const defaultMessages = enMessages; diff --git a/src/i18n/metadata.ts b/src/i18n/metadata.ts index 59745a6..1957b64 100644 --- a/src/i18n/metadata.ts +++ b/src/i18n/metadata.ts @@ -1,18 +1,12 @@ import type { Metadata } from 'next'; -import enMessages from '../messages/en.json'; -import hiMessages from '../messages/hi.json'; - -const messagesMap: Record = { - en: enMessages, - hi: hiMessages, -}; +import { messagesMap, defaultMessages, Messages } from './messages'; export async function generateLocaleMetadata( locale: string, - namespace: keyof typeof enMessages = 'Home' + namespace: 'Home' = 'Home' ): Promise { - const messages = messagesMap[locale] || enMessages; - const meta = (messages[namespace] as typeof enMessages.Home) || enMessages.Home; + const messages = (messagesMap[locale] || defaultMessages) as Messages; + const meta = messages[namespace] || defaultMessages.Home; const rawSiteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://TODO:project.aossie.org'; const siteUrl = rawSiteUrl.replace(/\/$/, ''); diff --git a/src/i18n/request.ts b/src/i18n/request.ts index 6913b46..cebe564 100644 --- a/src/i18n/request.ts +++ b/src/i18n/request.ts @@ -1,13 +1,7 @@ import { getRequestConfig } from 'next-intl/server'; -import enMessages from '../messages/en.json'; -import hiMessages from '../messages/hi.json'; +import { messagesMap, defaultMessages } from './messages'; import { routing } from './routing'; -const messagesMap: Record> = { - en: enMessages as Record, - hi: hiMessages as Record, -}; - export default getRequestConfig(async ({ requestLocale }) => { let locale = await requestLocale; @@ -17,7 +11,7 @@ export default getRequestConfig(async ({ requestLocale }) => { return { locale, - messages: messagesMap[locale] || enMessages, + messages: messagesMap[locale] || defaultMessages, }; });