diff --git a/.yarn/cache/next-intl-npm-2.19.1-d3fdbffe0b-b4c3cfbb22.zip b/.yarn/cache/next-intl-npm-2.19.1-d3fdbffe0b-b4c3cfbb22.zip deleted file mode 100644 index 38645bbba..000000000 Binary files a/.yarn/cache/next-intl-npm-2.19.1-d3fdbffe0b-b4c3cfbb22.zip and /dev/null differ diff --git a/.yarn/cache/next-intl-npm-3.0.0-rc.10-e930606451-8b54c0c6d8.zip b/.yarn/cache/next-intl-npm-3.0.0-rc.10-e930606451-8b54c0c6d8.zip new file mode 100644 index 000000000..68afb4b75 Binary files /dev/null and b/.yarn/cache/next-intl-npm-3.0.0-rc.10-e930606451-8b54c0c6d8.zip differ diff --git a/.yarn/cache/use-intl-npm-2.19.1-f53642df96-9c249791a4.zip b/.yarn/cache/use-intl-npm-2.19.1-f53642df96-9c249791a4.zip deleted file mode 100644 index 5997b9463..000000000 Binary files a/.yarn/cache/use-intl-npm-2.19.1-f53642df96-9c249791a4.zip and /dev/null differ diff --git a/.yarn/cache/use-intl-npm-3.0.0-rc.6-599d44c8b3-b81452e795.zip b/.yarn/cache/use-intl-npm-3.0.0-rc.6-599d44c8b3-b81452e795.zip new file mode 100644 index 000000000..d3a863037 Binary files /dev/null and b/.yarn/cache/use-intl-npm-3.0.0-rc.6-599d44c8b3-b81452e795.zip differ diff --git a/app/[locale]/about/page.tsx b/app/[locale]/about/page.tsx index 039f64945..c7f7779c9 100644 --- a/app/[locale]/about/page.tsx +++ b/app/[locale]/about/page.tsx @@ -4,6 +4,7 @@ import { SupportedLocale } from 'components/layout/navbar/language-control'; import Navbar from 'components/layout/navbar'; import { getCart, getPage, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; import AboutNaraiDetail from './about-narai-detail'; @@ -19,6 +20,10 @@ export const metadata = { }; export default async function Page({ params }: { params: { locale?: SupportedLocale } }) { + if (!!params?.locale) { + unstable_setRequestLocale(params.locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/bar/page.tsx b/app/[locale]/bar/page.tsx index 2015c0ee2..8e9e4beab 100644 --- a/app/[locale]/bar/page.tsx +++ b/app/[locale]/bar/page.tsx @@ -4,6 +4,7 @@ import { SupportedLocale } from 'components/layout/navbar/language-control'; import Navbar from 'components/layout/navbar'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; import SagyobarDetail from './sagyobar-detail'; @@ -19,6 +20,10 @@ export const metadata = { }; export default async function Page({ params }: { params: { locale?: SupportedLocale } }) { + if (!!params?.locale) { + unstable_setRequestLocale(params.locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/company/page.tsx b/app/[locale]/company/page.tsx index 5315416b6..337c45d58 100644 --- a/app/[locale]/company/page.tsx +++ b/app/[locale]/company/page.tsx @@ -4,6 +4,7 @@ import { SupportedLocale } from 'components/layout/navbar/language-control'; import Navbar from 'components/layout/navbar'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; import CompanyDetail from './company-detail'; @@ -19,6 +20,10 @@ export const metadata = { }; export default async function Page({ params }: { params: { locale?: SupportedLocale } }) { + if (!!params?.locale) { + unstable_setRequestLocale(params.locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/concept/page.tsx b/app/[locale]/concept/page.tsx index 009ad8c02..76ed2631b 100644 --- a/app/[locale]/concept/page.tsx +++ b/app/[locale]/concept/page.tsx @@ -4,6 +4,7 @@ import { SupportedLocale } from 'components/layout/navbar/language-control'; import Navbar from 'components/layout/navbar'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; import ConceptDetail from './concept-detail'; @@ -19,6 +20,10 @@ export const metadata = { }; export default async function Page({ params }: { params: { locale?: SupportedLocale } }) { + if (!!params?.locale) { + unstable_setRequestLocale(params.locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/disclosures/page.tsx b/app/[locale]/disclosures/page.tsx index d643eec3a..c93039592 100644 --- a/app/[locale]/disclosures/page.tsx +++ b/app/[locale]/disclosures/page.tsx @@ -4,6 +4,7 @@ import { SupportedLocale } from 'components/layout/navbar/language-control'; import Navbar from 'components/layout/navbar'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; import Disclosures from './disclosures'; @@ -23,6 +24,10 @@ export default async function DisclosuresPage({ }: { params: { locale?: SupportedLocale }; }) { + if (!!locale) { + unstable_setRequestLocale(locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 2e8f5ed97..de2a48d68 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -4,6 +4,8 @@ import { ReactNode, Suspense } from 'react'; import { SupportedLocale } from 'components/layout/navbar/language-control'; import { NextIntlClientProvider } from 'next-intl'; +import { unstable_setRequestLocale } from 'next-intl/server'; +import { notFound } from 'next/navigation'; import Analytics from './analytics'; import './globals.css'; @@ -69,8 +71,10 @@ const noto = Noto_Serif_JP({ variable: '--font-noto' }); +export const SupportedLocales: SupportedLocale[] = ['ja', 'en']; + export function generateStaticParams() { - return [{ locale: 'ja' }, { locale: 'en' }]; + return SupportedLocales.map((locale) => ({ locale })); } export default async function RootLayout({ @@ -80,6 +84,14 @@ export default async function RootLayout({ children: ReactNode; params: { locale?: SupportedLocale }; }) { + // Validate that the incoming `locale` parameter is valid + const isValidLocale = SupportedLocales.some((cur: string) => cur === params?.locale); + if (!isValidLocale) notFound(); + + if (params?.locale) { + unstable_setRequestLocale(params.locale); + } + const messages = (await import(`../../messages/${params?.locale}.json`)).default; return ( diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 5704a378c..b86c4ba0c 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -22,6 +22,7 @@ import StoriesPreview from 'components/layout/stories-preview'; import { BLOG_HANDLE } from 'lib/constants'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import Image from 'next/image'; import { Suspense } from 'react'; @@ -41,6 +42,10 @@ export default async function HomePage({ }: { params: { locale?: SupportedLocale }; }) { + if (!!locale) { + unstable_setRequestLocale(locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/privacy/page.tsx b/app/[locale]/privacy/page.tsx index 5d419fc6d..8938f89df 100644 --- a/app/[locale]/privacy/page.tsx +++ b/app/[locale]/privacy/page.tsx @@ -4,6 +4,7 @@ import { SupportedLocale } from 'components/layout/navbar/language-control'; import Navbar from 'components/layout/navbar'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; import PrivacyPolicy from './privacy-policy'; @@ -23,6 +24,10 @@ export default async function PrivacyPage({ }: { params: { locale?: SupportedLocale }; }) { + if (!!locale) { + unstable_setRequestLocale(locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/product/[handle]/page.tsx b/app/[locale]/product/[handle]/page.tsx index d14214840..3e22b9912 100644 --- a/app/[locale]/product/[handle]/page.tsx +++ b/app/[locale]/product/[handle]/page.tsx @@ -1,5 +1,4 @@ import type { Metadata } from 'next'; -import { notFound } from 'next/navigation'; import { ChevronDoubleRightIcon } from '@heroicons/react/24/outline'; import clsx from 'clsx'; @@ -14,6 +13,7 @@ import { VariantSelector } from 'components/product/variant-selector'; import { HIDDEN_PRODUCT_TAG } from 'lib/constants'; import { getProduct, getProductRecommendations } from 'lib/shopify'; import { Image as MediaImage, Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import Image from 'next/image'; import Link from 'next/link'; import { Suspense } from 'react'; @@ -28,7 +28,7 @@ export async function generateMetadata({ language: params?.locale?.toUpperCase() }); - if (!product) return notFound(); + if (!product) return {}; const { url, width, height, altText: alt } = product.featuredImage || {}; const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG); @@ -64,6 +64,10 @@ export default async function ProductPage({ }: { params: { handle: string; locale?: SupportedLocale }; }) { + if (!!params?.locale) { + unstable_setRequestLocale(params.locale); + } + const numberOfOtherImages = 3; const product = await getProduct({ handle: params.handle, @@ -77,7 +81,7 @@ export default async function ProductPage({ .filter((image) => image?.url !== product.featuredImage?.url); } - if (!product) return notFound(); + if (!product) return {}; const productJsonLd = { '@context': 'https://schema.org', diff --git a/app/[locale]/products/page.tsx b/app/[locale]/products/page.tsx index 9309b5130..be6aed406 100644 --- a/app/[locale]/products/page.tsx +++ b/app/[locale]/products/page.tsx @@ -5,6 +5,7 @@ import { ProductGrid } from 'components/grid/product-grid'; import Navbar from 'components/layout/navbar'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; @@ -23,6 +24,10 @@ export default async function ProductPage({ }: { params: { locale?: SupportedLocale }; }) { + if (!!locale) { + unstable_setRequestLocale(locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/shop-list/page.tsx b/app/[locale]/shop-list/page.tsx index 664262338..fee9833af 100644 --- a/app/[locale]/shop-list/page.tsx +++ b/app/[locale]/shop-list/page.tsx @@ -5,9 +5,9 @@ import Navbar from 'components/layout/navbar'; import { SupportedLocale } from 'components/layout/navbar/language-control'; import { getCart, getPage, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { unstable_noStore } from 'next/cache'; import { cookies } from 'next/headers'; -import { notFound } from 'next/navigation'; import { Suspense } from 'react'; import ShopListDetail from './shop-list-detail'; import ShopsNav from './shops-nav'; @@ -23,7 +23,7 @@ export async function generateMetadata({ language: params?.locale?.toUpperCase() || 'JA' }); - if (!page) return notFound(); + if (!page) return {}; return { title: page.seo?.title || page.title, @@ -37,6 +37,10 @@ export async function generateMetadata({ } export default async function Page({ params }: { params: { locale?: SupportedLocale } }) { + if (!!params?.locale) { + unstable_setRequestLocale(params.locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/app/[locale]/shop-list/shop-list-detail.tsx b/app/[locale]/shop-list/shop-list-detail.tsx index d68640c2d..8fc6e4fc8 100644 --- a/app/[locale]/shop-list/shop-list-detail.tsx +++ b/app/[locale]/shop-list/shop-list-detail.tsx @@ -7,7 +7,7 @@ import { notFound } from 'next/navigation'; export default async function ShopListDetail({ language }: { language?: string }) { const page = await getPage({ handle: 'shop-list', language }); - if (!page) return notFound(); + if (!page) notFound(); return ; } diff --git a/app/[locale]/stories/[handle]/page.tsx b/app/[locale]/stories/[handle]/page.tsx index 1c89d5fea..ed92e8ec4 100644 --- a/app/[locale]/stories/[handle]/page.tsx +++ b/app/[locale]/stories/[handle]/page.tsx @@ -6,6 +6,7 @@ import Prose from 'components/prose'; import { BLOG_HANDLE, HIDDEN_ARTICLE_TAG } from 'lib/constants'; import { getBlogArticle } from 'lib/shopify'; import { BlogArticle } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import Image from 'next/image'; export async function generateMetadata({ @@ -19,7 +20,7 @@ export async function generateMetadata({ language: params?.locale?.toUpperCase() }); - if (!article) return notFound(); + if (!article) return {}; const { url, width, height, altText: alt } = article.image || {}; const indexable = !article?.tags?.includes(HIDDEN_ARTICLE_TAG); @@ -55,13 +56,17 @@ export default async function BlogArticlePage({ }: { params: { handle: string; locale?: SupportedLocale }; }) { + if (!!params?.locale) { + unstable_setRequestLocale(params.locale); + } + const article: BlogArticle | undefined = await getBlogArticle({ handle: BLOG_HANDLE, articleHandle: params.handle, language: params?.locale?.toUpperCase() }); - if (!article) return notFound(); + if (!article) notFound(); return ( <> diff --git a/app/[locale]/terms/page.tsx b/app/[locale]/terms/page.tsx index 98d903f0d..d2763d9b6 100644 --- a/app/[locale]/terms/page.tsx +++ b/app/[locale]/terms/page.tsx @@ -4,6 +4,7 @@ import { SupportedLocale } from 'components/layout/navbar/language-control'; import Navbar from 'components/layout/navbar'; import { getCart, getProduct } from 'lib/shopify'; import { Product } from 'lib/shopify/types'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { cookies } from 'next/headers'; import { Suspense } from 'react'; import TermsOfUse from './terms-of-use'; @@ -23,6 +24,10 @@ export default async function TermsPage({ }: { params: { locale?: SupportedLocale }; }) { + if (!!locale) { + unstable_setRequestLocale(locale); + } + const cartId = cookies().get('cartId')?.value; let cart; diff --git a/components/layout/navbar/language-control.tsx b/components/layout/navbar/language-control.tsx index 993fbf32a..0fadc34ce 100644 --- a/components/layout/navbar/language-control.tsx +++ b/components/layout/navbar/language-control.tsx @@ -1,7 +1,7 @@ 'use client'; import clsx from 'clsx'; -import Link from 'next-intl/link'; +import Link from 'next/link'; import { usePathname } from 'next/navigation'; export type SupportedLocale = 'en' | 'ja'; diff --git a/i18n.ts b/i18n.ts new file mode 100644 index 000000000..5fb3a774f --- /dev/null +++ b/i18n.ts @@ -0,0 +1,5 @@ +import { getRequestConfig } from 'next-intl/server'; + +export default getRequestConfig(async ({ locale }) => ({ + messages: (await import(`./messages/${locale}.json`)).default +})); diff --git a/next.config.js b/next.config.js index 887f03d8f..01e61e8c5 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,8 @@ /** @type {import('next').NextConfig} */ -module.exports = { + +const withNextIntl = require('next-intl/plugin')('./i18n.ts'); + +module.exports = withNextIntl({ eslint: { // Disabling on production builds because we're running checks on PRs via GitHub Actions. ignoreDuringBuilds: true @@ -26,4 +29,4 @@ module.exports = { } ]; } -}; +}); diff --git a/package.json b/package.json index 4cefa5b47..a700ca260 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "negotiator": "^0.6.3", "next": "canary", "next-gtm": "latest", - "next-intl": "latest", + "next-intl": "next", "prettier-plugin-organize-imports": "^3.2.3", "react": "canary", "react-dom": "canary", diff --git a/yarn.lock b/yarn.lock index 6f21d0c50..2c6744220 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1382,7 +1382,7 @@ __metadata: negotiator: ^0.6.3 next: canary next-gtm: latest - next-intl: latest + next-intl: next postcss: ^8.4.27 prettier: 3.0.1 prettier-plugin-organize-imports: ^3.2.3 @@ -3683,17 +3683,17 @@ __metadata: languageName: node linkType: hard -"next-intl@npm:latest": - version: 2.19.1 - resolution: "next-intl@npm:2.19.1" +"next-intl@npm:next": + version: 3.0.0-rc.10 + resolution: "next-intl@npm:3.0.0-rc.10" dependencies: "@formatjs/intl-localematcher": ^0.2.32 negotiator: ^0.6.3 - use-intl: ^2.19.1 + use-intl: 3.0.0-rc.6 peerDependencies: - next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 + next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: b4c3cfbb22645b75fcb549259d18484f3df284a8240054f269f7403bc083fc57e53dc555abd4e29ba68fcd56c52fdb0a54d5021791597441519150d0054e9665 + checksum: 8b54c0c6d8086da133e4a2264b6ec85910da1f814183a50e75899cce8caa08858ffc81267bb2190a4f50089681acfa8780b7e1812726d248d050c75d2df74091 languageName: node linkType: hard @@ -5665,15 +5665,15 @@ __metadata: languageName: node linkType: hard -"use-intl@npm:^2.19.1": - version: 2.19.1 - resolution: "use-intl@npm:2.19.1" +"use-intl@npm:3.0.0-rc.6": + version: 3.0.0-rc.6 + resolution: "use-intl@npm:3.0.0-rc.6" dependencies: "@formatjs/ecma402-abstract": ^1.11.4 intl-messageformat: ^9.3.18 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 9c249791a4cb4213de6426d5542cc44685eb4a714c44f3761dc7713b48fc505d02e5ee5047f9ef31aec3a9e869fff838b96a6d1363f5ead583e6b6da0a96d142 + checksum: b81452e79543fa487ff001c1e45f593e77336e78ac6ad0d4372283cfac36f22030f5ac53f5a09c81fd061a91a0d0f279276329ef0444c0717f07f8b0a224bcee languageName: node linkType: hard