mirror of
https://github.com/vercel/commerce.git
synced 2025-05-19 07:56:59 +00:00
add basic internationalization support
This commit is contained in:
parent
b786cc2228
commit
2cb348259a
BIN
.yarn/cache/@formatjs-intl-localematcher-npm-0.4.0-9a73a446bf-c65108e9a8.zip
vendored
Normal file
BIN
.yarn/cache/@formatjs-intl-localematcher-npm-0.4.0-9a73a446bf-c65108e9a8.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@types-negotiator-npm-0.6.1-bd102330ab-e39f985874.zip
vendored
Normal file
BIN
.yarn/cache/@types-negotiator-npm-0.6.1-bd102330ab-e39f985874.zip
vendored
Normal file
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -1,4 +1,5 @@
|
|||||||
import Navbar from 'components/layout/navbar';
|
import Navbar from 'components/layout/navbar';
|
||||||
|
import { i18n } from 'i18n-config';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
import { ReactNode, Suspense } from 'react';
|
import { ReactNode, Suspense } from 'react';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
@ -34,12 +35,22 @@ const inter = Inter({
|
|||||||
variable: '--font-inter'
|
variable: '--font-inter'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default async function RootLayout({ children }: { children: ReactNode }) {
|
export async function generateStaticParams() {
|
||||||
|
return i18n.locales.map((locale) => ({ lang: locale }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children,
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
params: { lang: string };
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={inter.variable}>
|
<html lang={params.lang} className={inter.variable}>
|
||||||
<body className="bg-dark text-white selection:bg-green-800 selection:text-green-400">
|
<body className="bg-dark text-white selection:bg-green-800 selection:text-green-400">
|
||||||
<div className="mx-auto max-w-screen-2xl">
|
<div className="mx-auto max-w-screen-2xl">
|
||||||
<Navbar />
|
<Navbar lang={params.lang} />
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
</Suspense>
|
</Suspense>
|
@ -2,6 +2,8 @@ import { Carousel } from 'components/carousel';
|
|||||||
import { ThreeItemGrid } from 'components/grid/three-items';
|
import { ThreeItemGrid } from 'components/grid/three-items';
|
||||||
import Footer from 'components/layout/footer';
|
import Footer from 'components/layout/footer';
|
||||||
import { LanguageControl } from 'components/layout/navbar/language-control';
|
import { LanguageControl } from 'components/layout/navbar/language-control';
|
||||||
|
import type { Locale } from '../../i18n-config';
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Namemark from 'public/assets/images/namemark.png';
|
import Namemark from 'public/assets/images/namemark.png';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
@ -15,11 +17,13 @@ export const metadata = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage({ params: { lang } }: { params: { lang: Locale } }) {
|
||||||
|
// const dictionary = await getDictionary(lang);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="invisible absolute right-40 top-12 md:visible">
|
<div className="invisible absolute right-40 top-12 md:visible">
|
||||||
<LanguageControl />
|
<LanguageControl lang={lang} />
|
||||||
</div>
|
</div>
|
||||||
<div className="px-6 pb-12 pt-6 md:py-12 md:pl-6">
|
<div className="px-6 pb-12 pt-6 md:py-12 md:pl-6">
|
||||||
<Image
|
<Image
|
@ -1,32 +1,21 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
import { GridTileImage } from 'components/grid/tile';
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
import { getCollectionProducts } from 'lib/shopify';
|
import { getCollectionProducts } from 'lib/shopify';
|
||||||
import type { Product } from 'lib/shopify/types';
|
import type { Product } from 'lib/shopify/types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
function ThreeItemGridItem({
|
function ThreeItemGridItem({ item, priority }: { item: Product; priority?: boolean }) {
|
||||||
item,
|
|
||||||
size,
|
|
||||||
priority
|
|
||||||
}: {
|
|
||||||
item: Product;
|
|
||||||
size: 'full' | 'half';
|
|
||||||
priority?: boolean;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={clsx('md:col-span-2 md:row-span-1')}>
|
||||||
className={size === 'full' ? 'md:col-span-4 md:row-span-2' : 'md:col-span-2 md:row-span-1'}
|
|
||||||
>
|
|
||||||
<Link className="relative block aspect-square h-full w-full" href={`/product/${item.handle}`}>
|
<Link className="relative block aspect-square h-full w-full" href={`/product/${item.handle}`}>
|
||||||
<GridTileImage
|
<GridTileImage
|
||||||
src={item.featuredImage.url}
|
src={item.featuredImage.url}
|
||||||
fill
|
fill
|
||||||
sizes={
|
sizes={'(min-width: 768px) 33vw, 100vw'}
|
||||||
size === 'full' ? '(min-width: 768px) 66vw, 100vw' : '(min-width: 768px) 33vw, 100vw'
|
|
||||||
}
|
|
||||||
priority={priority}
|
priority={priority}
|
||||||
alt={item.title}
|
alt={item.title}
|
||||||
label={{
|
label={{
|
||||||
position: size === 'full' ? 'center' : 'bottom',
|
position: 'bottom',
|
||||||
title: item.title as string,
|
title: item.title as string,
|
||||||
amount: item.priceRange.maxVariantPrice.amount,
|
amount: item.priceRange.maxVariantPrice.amount,
|
||||||
currencyCode: item.priceRange.maxVariantPrice.currencyCode
|
currencyCode: item.priceRange.maxVariantPrice.currencyCode
|
||||||
@ -48,10 +37,15 @@ export async function ThreeItemGrid() {
|
|||||||
const [firstProduct, secondProduct, thirdProduct] = homepageItems;
|
const [firstProduct, secondProduct, thirdProduct] = homepageItems;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6 md:grid-rows-2">
|
<section
|
||||||
<ThreeItemGridItem size="full" item={firstProduct} priority={true} />
|
className={clsx(
|
||||||
<ThreeItemGridItem size="half" item={secondProduct} priority={true} />
|
'mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6',
|
||||||
<ThreeItemGridItem size="half" item={thirdProduct} />
|
'md:grid-rows-3'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ThreeItemGridItem item={firstProduct} priority={true} />
|
||||||
|
<ThreeItemGridItem item={secondProduct} priority={true} />
|
||||||
|
<ThreeItemGridItem item={thirdProduct} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,36 +14,21 @@ export function GridTileImage({
|
|||||||
title: string;
|
title: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
position?: 'bottom' | 'center';
|
|
||||||
};
|
};
|
||||||
} & React.ComponentProps<typeof Image>) {
|
} & React.ComponentProps<typeof Image>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex flex-col space-y-2">
|
||||||
className={clsx(
|
|
||||||
'flex h-full w-full items-center justify-center overflow-hidden rounded-lg border bg-white hover:border-blue-600 dark:bg-black',
|
|
||||||
{
|
|
||||||
relative: label,
|
|
||||||
'border-2 border-blue-600': active,
|
|
||||||
'border-neutral-200 dark:border-neutral-800': !active
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{props.src ? (
|
{props.src ? (
|
||||||
// eslint-disable-next-line jsx-a11y/alt-text -- `alt` is inherited from `props`, which is being enforced with TypeScript
|
// eslint-disable-next-line jsx-a11y/alt-text -- `alt` is inherited from `props`, which is being enforced with TypeScript
|
||||||
<Image
|
<Image
|
||||||
className={clsx('relative h-full w-full object-contain', {
|
className={clsx('h-full w-full object-contain', {
|
||||||
'transition duration-300 ease-in-out hover:scale-105': isInteractive
|
'transition duration-300 ease-in-out hover:scale-105': isInteractive
|
||||||
})}
|
})}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{label ? (
|
{label ? (
|
||||||
<Label
|
<Label title={label.title} amount={label.amount} currencyCode={label.currencyCode} />
|
||||||
title={label.title}
|
|
||||||
amount={label.amount}
|
|
||||||
currencyCode={label.currencyCode}
|
|
||||||
position={label.position}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,24 +4,18 @@ import Price from './price';
|
|||||||
const Label = ({
|
const Label = ({
|
||||||
title,
|
title,
|
||||||
amount,
|
amount,
|
||||||
currencyCode,
|
currencyCode
|
||||||
position = 'bottom'
|
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
position?: 'bottom' | 'center';
|
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={clsx('@container/label')}>
|
||||||
className={clsx('absolute bottom-0 left-0 flex w-full px-4 pb-4 @container/label', {
|
<div className="flex flex-col space-y-2">
|
||||||
'lg:px-20 lg:pb-[35%]': position === 'center'
|
<h3 className="mr-4 line-clamp-2 flex-grow text-3xl">{title}</h3>
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="flex items-center rounded-full border bg-white/70 p-1 text-xs font-semibold text-black backdrop-blur-md dark:border-neutral-800 dark:bg-black/70 dark:text-white">
|
|
||||||
<h3 className="mr-4 line-clamp-2 flex-grow pl-2 leading-none tracking-tight">{title}</h3>
|
|
||||||
<Price
|
<Price
|
||||||
className="flex-none rounded-full bg-blue-600 p-2 text-white"
|
className="flex-none"
|
||||||
amount={amount}
|
amount={amount}
|
||||||
currencyCode={currencyCode}
|
currencyCode={currencyCode}
|
||||||
currencyCodeClassName="hidden @[275px]/label:inline"
|
currencyCodeClassName="hidden @[275px]/label:inline"
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
import { Dialog, Transition } from '@headlessui/react';
|
import { Dialog, Transition } from '@headlessui/react';
|
||||||
import CloseIcon from 'components/icons/close';
|
import CloseIcon from 'components/icons/close';
|
||||||
import MenuIcon from 'components/icons/menu';
|
import MenuIcon from 'components/icons/menu';
|
||||||
|
import type { Locale } from 'i18n-config';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Fragment, useRef, useState } from 'react';
|
import { Fragment, useRef, useState } from 'react';
|
||||||
import { LanguageControl } from '../navbar/language-control';
|
import { LanguageControl } from '../navbar/language-control';
|
||||||
|
|
||||||
export function MenuModal() {
|
export function MenuModal({ lang }: { lang: Locale }) {
|
||||||
let [isOpen, setIsOpen] = useState(false);
|
let [isOpen, setIsOpen] = useState(false);
|
||||||
let closeButtonRef = useRef(null);
|
let closeButtonRef = useRef(null);
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ export function MenuModal() {
|
|||||||
<Transition.Child as={Fragment}>
|
<Transition.Child as={Fragment}>
|
||||||
<div className="fixed right-5 top-6 z-40 px-2 py-1 md:top-11">
|
<div className="fixed right-5 top-6 z-40 px-2 py-1 md:top-11">
|
||||||
<div className="flex flex-row space-x-4">
|
<div className="flex flex-row space-x-4">
|
||||||
<LanguageControl />
|
<LanguageControl lang={lang} />
|
||||||
|
|
||||||
<button ref={closeButtonRef} onClick={close} className="">
|
<button ref={closeButtonRef} onClick={close} className="">
|
||||||
<CloseIcon className="h-10 w-10 stroke-current transition-opacity duration-150 hover:opacity-50" />
|
<CloseIcon className="h-10 w-10 stroke-current transition-opacity duration-150 hover:opacity-50" />
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import Cart from 'components/cart';
|
import Cart from 'components/cart';
|
||||||
import OpenCart from 'components/cart/open-cart';
|
import OpenCart from 'components/cart/open-cart';
|
||||||
|
import type { Locale } from 'i18n-config';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { MenuModal } from '../menu/modal';
|
import { MenuModal } from '../menu/modal';
|
||||||
|
|
||||||
export default async function Navbar() {
|
export default async function Navbar({ lang }: { lang: Locale }) {
|
||||||
return (
|
return (
|
||||||
<nav className="fixed right-0 top-6 z-10 md:top-12">
|
<nav className="fixed right-0 top-6 z-10 md:top-12">
|
||||||
<div className="flex justify-end pr-5">
|
<div className="flex justify-end pr-5">
|
||||||
<Suspense fallback={<OpenCart />}>
|
<Suspense fallback={<OpenCart />}>
|
||||||
<div className="flex flex-col-reverse items-center justify-center space-y-2 rounded bg-dark/40 px-2 backdrop-blur-sm md:flex-row md:space-x-6">
|
<div className="flex flex-col-reverse items-center justify-center space-y-2 rounded bg-dark/40 px-2 backdrop-blur-sm md:flex-row md:space-x-6">
|
||||||
<Cart />
|
<Cart />
|
||||||
<MenuModal />
|
<MenuModal lang={lang} />
|
||||||
</div>
|
</div>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,45 @@
|
|||||||
export const LanguageControl = () => {
|
'use client';
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import type { Locale } from 'i18n-config';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
export const LanguageControl = ({ lang }: { lang?: Locale }) => {
|
||||||
|
const pathName = usePathname();
|
||||||
|
console.debug({ lang });
|
||||||
|
const redirectedPathName = (locale: string) => {
|
||||||
|
if (!pathName) return '/';
|
||||||
|
const segments = pathName.split('/');
|
||||||
|
segments[1] = locale;
|
||||||
|
return segments.join('/');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row space-x-0">
|
<div className="flex flex-row space-x-0">
|
||||||
<span className="px-2 py-4">JP</span>
|
<span className="px-2 py-4">
|
||||||
|
<Link
|
||||||
|
href={redirectedPathName('ja')}
|
||||||
|
className={clsx(
|
||||||
|
lang === 'ja' && 'opacity-70',
|
||||||
|
'transition-opacity duration-150 hover:opacity-50'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
JP
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
<span className="py-4">/</span>
|
<span className="py-4">/</span>
|
||||||
<span className="px-2 py-4">EN</span>
|
<span className="px-2 py-4">
|
||||||
|
<Link
|
||||||
|
href={redirectedPathName('en')}
|
||||||
|
className={clsx(
|
||||||
|
lang === 'en' && 'opacity-70',
|
||||||
|
'transition-opacity duration-150 hover:opacity-50'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
EN
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
6
dictionaries/en.json
Normal file
6
dictionaries/en.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"hello": {
|
||||||
|
"title": "Hello World",
|
||||||
|
"description": "This is a description"
|
||||||
|
}
|
||||||
|
}
|
12
dictionaries/index.ts
Normal file
12
dictionaries/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import 'server-only';
|
||||||
|
import type { Locale } from '../i18n-config';
|
||||||
|
|
||||||
|
// We enumerate all dictionaries here for better linting and typescript support
|
||||||
|
// We also get the default import for cleaner types
|
||||||
|
const dictionaries = {
|
||||||
|
en: () => import('./en.json').then((module) => module.default),
|
||||||
|
ja: () => import('./ja.json').then((module) => module.default)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDictionary = async (locale: Locale) =>
|
||||||
|
dictionaries[locale]?.() ?? dictionaries.en();
|
6
dictionaries/ja.json
Normal file
6
dictionaries/ja.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"hello": {
|
||||||
|
"title": "こんにちは",
|
||||||
|
"description": "これはせつめいですよ"
|
||||||
|
}
|
||||||
|
}
|
6
i18n-config.ts
Normal file
6
i18n-config.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const i18n = {
|
||||||
|
defaultLocale: 'en',
|
||||||
|
locales: ['en', 'ja']
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type Locale = (typeof i18n)['locales'][number];
|
59
middleware.ts
Normal file
59
middleware.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import type { NextRequest } from 'next/server';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
import { match as matchLocale } from '@formatjs/intl-localematcher';
|
||||||
|
import { i18n } from 'i18n-config';
|
||||||
|
import Negotiator from 'negotiator';
|
||||||
|
|
||||||
|
function getLocale(request: NextRequest): string | undefined {
|
||||||
|
// Negotiator expects plain object so we need to transform headers
|
||||||
|
const negotiatorHeaders: Record<string, string> = {};
|
||||||
|
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
|
||||||
|
|
||||||
|
// @ts-ignore locales are readonly
|
||||||
|
const locales: string[] = i18n.locales;
|
||||||
|
|
||||||
|
// Use negotiator and intl-localematcher to get best locale
|
||||||
|
let languages = new Negotiator({ headers: negotiatorHeaders }).languages(locales);
|
||||||
|
|
||||||
|
const locale = matchLocale(languages, locales, i18n.defaultLocale);
|
||||||
|
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const pathname = request.nextUrl.pathname;
|
||||||
|
|
||||||
|
// `/_next/` and `/api/` are ignored by the watcher, but we need to ignore files in `public` manually.
|
||||||
|
// If you have one
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'/public/addets/images/logo.png',
|
||||||
|
'/public/addets/images/logo+namemark.png',
|
||||||
|
'/public/addets/images/namemark.png'
|
||||||
|
// Your other files in `public`
|
||||||
|
].includes(pathname)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check if there is any supported locale in the pathname
|
||||||
|
const pathnameIsMissingLocale = i18n.locales.every(
|
||||||
|
(locale: any) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Redirect if there is no locale
|
||||||
|
if (pathnameIsMissingLocale) {
|
||||||
|
const locale = getLocale(request);
|
||||||
|
|
||||||
|
// e.g. incoming request is /products
|
||||||
|
// The new URL is now /en-US/products
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL(`/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`, request.url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
// Matcher ignoring `/_next/` and `/api/`
|
||||||
|
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
|
||||||
|
};
|
@ -23,11 +23,13 @@
|
|||||||
"*": "prettier --write --ignore-unknown"
|
"*": "prettier --write --ignore-unknown"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "^0.4.0",
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"eslint-plugin-tailwindcss": "^3.13.0",
|
"eslint-plugin-tailwindcss": "^3.13.0",
|
||||||
"eslint-plugin-unused-imports": "^3.0.0",
|
"eslint-plugin-unused-imports": "^3.0.0",
|
||||||
|
"negotiator": "^0.6.3",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"prettier-plugin-organize-imports": "^3.2.3",
|
"prettier-plugin-organize-imports": "^3.2.3",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
@ -36,6 +38,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
|
"@types/negotiator": "^0.6.1",
|
||||||
"@types/node": "20.4.4",
|
"@types/node": "20.4.4",
|
||||||
"@types/react": "18.2.16",
|
"@types/react": "18.2.16",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
|
19
yarn.lock
19
yarn.lock
@ -97,6 +97,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@formatjs/intl-localematcher@npm:^0.4.0":
|
||||||
|
version: 0.4.0
|
||||||
|
resolution: "@formatjs/intl-localematcher@npm:0.4.0"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^2.4.0
|
||||||
|
checksum: c65108e9a81c3733d2b6240ceedc846d0ae59c3606041cb5cc71c13453cdabe295b0dc8559dc4a8acaafdc45876807bd5e9ef37a3ec1cb864e78db655d434b66
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@headlessui/react@npm:^1.7.15":
|
"@headlessui/react@npm:^1.7.15":
|
||||||
version: 1.7.16
|
version: 1.7.16
|
||||||
resolution: "@headlessui/react@npm:1.7.16"
|
resolution: "@headlessui/react@npm:1.7.16"
|
||||||
@ -395,6 +404,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/negotiator@npm:^0.6.1":
|
||||||
|
version: 0.6.1
|
||||||
|
resolution: "@types/negotiator@npm:0.6.1"
|
||||||
|
checksum: e39f985874a30bd13186249eaaede0c5eec107178f55f2bd719c2c9d1397de329a5e0869a25dd5e7c702cdd311bd5e4153e25963900b4353545842354a2d1bc4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/node@npm:20.4.4":
|
"@types/node@npm:20.4.4":
|
||||||
version: 20.4.4
|
version: 20.4.4
|
||||||
resolution: "@types/node@npm:20.4.4"
|
resolution: "@types/node@npm:20.4.4"
|
||||||
@ -1172,10 +1188,12 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "commerce@workspace:."
|
resolution: "commerce@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@formatjs/intl-localematcher": ^0.4.0
|
||||||
"@headlessui/react": ^1.7.15
|
"@headlessui/react": ^1.7.15
|
||||||
"@heroicons/react": ^2.0.18
|
"@heroicons/react": ^2.0.18
|
||||||
"@tailwindcss/container-queries": ^0.1.1
|
"@tailwindcss/container-queries": ^0.1.1
|
||||||
"@tailwindcss/typography": ^0.5.9
|
"@tailwindcss/typography": ^0.5.9
|
||||||
|
"@types/negotiator": ^0.6.1
|
||||||
"@types/node": 20.4.4
|
"@types/node": 20.4.4
|
||||||
"@types/react": 18.2.16
|
"@types/react": 18.2.16
|
||||||
"@types/react-dom": 18.2.7
|
"@types/react-dom": 18.2.7
|
||||||
@ -1188,6 +1206,7 @@ __metadata:
|
|||||||
eslint-plugin-unicorn: ^48.0.0
|
eslint-plugin-unicorn: ^48.0.0
|
||||||
eslint-plugin-unused-imports: ^3.0.0
|
eslint-plugin-unused-imports: ^3.0.0
|
||||||
lint-staged: ^13.2.3
|
lint-staged: ^13.2.3
|
||||||
|
negotiator: ^0.6.3
|
||||||
next: latest
|
next: latest
|
||||||
postcss: ^8.4.27
|
postcss: ^8.4.27
|
||||||
prettier: 3.0.1
|
prettier: 3.0.1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user