Compare commits

...

2 Commits

Author SHA1 Message Date
vchikalkin
c43a609b8d remove all official template trash 2024-12-11 15:53:38 +03:00
vchikalkin
9ab9880c0f add files from official template 2024-12-10 20:08:38 +03:00
21 changed files with 473 additions and 35 deletions

View File

@ -1,22 +1,24 @@
import '@repo/ui/globals.css';
import { Root } from '@/components/telegram';
import { I18nProvider } from '@/utils/i18n/provider';
import { type Metadata } from 'next';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
import { getLocale } from 'next-intl/server';
import { type PropsWithChildren } from 'react';
export const metadata: Metadata = {
description: 'Generated by create turbo',
title: 'Docs',
description: 'Your application description goes here',
title: 'Your Application Title Goes Here',
};
export default function RootLayout({
children,
}: {
readonly children: React.ReactNode;
}): JSX.Element {
export default async function RootLayout({ children }: Readonly<PropsWithChildren>) {
const locale = await getLocale();
return (
<html lang="ru">
<body className={inter.className}>{children}</body>
<html lang={locale}>
<body>
<I18nProvider>
<Root>{children}</Root>
</I18nProvider>
</body>
</html>
);
}

View File

@ -1,8 +0,0 @@
import Page from './page';
import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
test('Page', () => {
render(<Page />);
expect(screen.getByRole('button', { name: 'Click me' })).toBeDefined();
});

View File

@ -1,17 +1,14 @@
'use client';
import { Button } from '@repo/ui/components/ui/button';
import { useState } from 'react';
export default function Page() {
const [clicked, setClicked] = useState(false);
import { initData, isMiniAppDark, useSignal } from '@telegram-apps/sdk-react';
function handleClick() {
setClicked(!clicked);
}
export default function InitDataPage() {
const initDataState = useSignal(initData.state);
const isDark = isMiniAppDark();
return (
<main>
<Button onClick={() => handleClick()}>{clicked ? 'Clicked' : 'Click me'}</Button>
</main>
<code style={{ color: isDark ? 'white' : 'black' }}>
{JSON.stringify(initDataState, null, 2)}
</code>
);
}

View File

@ -0,0 +1,45 @@
/* eslint-disable sonarjs/function-return-type */
'use client';
import { useClientOnce, useDidMount, useTelegramMock } from '@/hooks/telegram';
import { setLocale } from '@/utils/i18n/locale';
import { init } from '@/utils/init';
import { initData, useLaunchParams, useSignal } from '@telegram-apps/sdk-react';
import { type PropsWithChildren, useEffect } from 'react';
export function Root(props: Readonly<PropsWithChildren>) {
// Unfortunately, Telegram Mini Apps does not allow us to use all features of
// the Server Side Rendering. That's why we are showing loader on the server
// side.
const didMount = useDidMount();
if (!didMount) return <div>Loading</div>;
return <RootInner {...props} />;
}
function RootInner({ children }: PropsWithChildren) {
const isDevelopment = process.env.NODE_ENV === 'development';
// Mock Telegram environment in development mode if needed.
if (isDevelopment) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useTelegramMock();
}
const lp = useLaunchParams();
const debug = isDevelopment || lp.startParam === 'debug';
// Initialize the library.
useClientOnce(() => {
init(debug);
});
const initDataUser = useSignal(initData.user);
// Set the user locale.
useEffect(() => {
if (initDataUser) setLocale(initDataUser.languageCode);
}, [initDataUser]);
return children;
}

View File

@ -0,0 +1 @@
export * from './Root';

View File

@ -0,0 +1,3 @@
export * from './use-client-once';
export * from './use-did-mount';
export * from './use-telegram-mock';

View File

@ -0,0 +1,9 @@
import { useRef } from 'react';
export function useClientOnce(callback: () => void): void {
const canCall = useRef(true);
if (typeof window !== 'undefined' && canCall.current) {
canCall.current = false;
callback();
}
}

View File

@ -0,0 +1,14 @@
import { useEffect, useState } from 'react';
/**
* @returns True, if component was mounted.
*/
export function useDidMount(): boolean {
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
}, []);
return didMount;
}

View File

@ -0,0 +1,76 @@
/* eslint-disable no-console */
import { useClientOnce } from '@/hooks/telegram/use-client-once';
import {
isTMA,
type LaunchParams,
mockTelegramEnv,
parseInitData,
retrieveLaunchParams,
} from '@telegram-apps/sdk-react';
/**
* Mocks Telegram environment in development mode.
*/
export function useTelegramMock(): void {
useClientOnce(() => {
if (!sessionStorage.getItem('env-mocked') && isTMA('simple')) {
return;
}
// Determine which launch params should be applied. We could already
// apply them previously, or they may be specified on purpose using the
// default launch parameters transmission method.
let lp: LaunchParams | undefined;
try {
lp = retrieveLaunchParams();
} catch {
const initDataRaw = new URLSearchParams([
[
'user',
JSON.stringify({
allows_write_to_pm: true,
first_name: 'Andrew',
id: 99_281_932,
is_premium: true,
language_code: 'en',
last_name: 'Rogue',
username: 'rogue',
}),
],
['hash', '89d6079ad6762351f38c6dbbc41bb53048019256a9443988af7a48bcad16ba31'],
['auth_date', '1716922846'],
['start_param', 'debug'],
['chat_type', 'sender'],
['chat_instance', '8428209589180549439'],
]).toString();
lp = {
initData: parseInitData(initDataRaw),
initDataRaw,
platform: 'tdesktop',
themeParams: {
accentTextColor: '#6ab2f2',
bgColor: '#17212b',
buttonColor: '#5288c1',
buttonTextColor: '#ffffff',
destructiveTextColor: '#ec3942',
headerBgColor: '#17212b',
hintColor: '#708499',
linkColor: '#6ab3f3',
secondaryBgColor: '#232e3c',
sectionBgColor: '#17212b',
sectionHeaderTextColor: '#6ab3f3',
subtitleTextColor: '#708499',
textColor: '#f5f5f5',
},
version: '8',
};
}
sessionStorage.setItem('env-mocked', '1');
mockTelegramEnv(lp);
console.warn(
'⚠️ As long as the current environment was not considered as the Telegram-based one, it was mocked. Take a note, that you should not do it in production and current behavior is only specific to the development process. Environment mocking is also applied only in development mode. So, after building the application, you will not see this behavior and related warning, leading to crashing the application outside Telegram.',
);
});
}

View File

@ -1,7 +1,10 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./utils/i18n/i18n.ts');
const nextConfig = withNextIntl({
reactStrictMode: true,
transpilePackages: ['@repo/ui'],
};
});
export default nextConfig;

View File

@ -17,8 +17,10 @@
"dependencies": {
"@apollo/client": "catalog:",
"@repo/ui": "workspace:*",
"@telegram-apps/sdk-react": "^2.0.19",
"graphql": "catalog:",
"next": "catalog:",
"next-intl": "^3.26.0",
"react": "catalog:",
"react-dom": "catalog:"
},
@ -38,6 +40,7 @@
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"autoprefixer": "catalog:",
"eruda": "^3.4.1",
"jsdom": "catalog:",
"lint-staged": "catalog:",
"postcss": "catalog:",

View File

@ -0,0 +1,6 @@
{
"i18n": {
"header": "Application supports i18n",
"footer": "You can select a different language from the dropdown menu."
}
}

View File

@ -0,0 +1,6 @@
{
"i18n": {
"header": "Поддержка i18n",
"footer": "Вы можете выбрать другой язык в выпадающем меню."
}
}

View File

@ -5,7 +5,11 @@
{
"name": "next"
}
]
],
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
},
"include": [
"next-env.d.ts",

View File

@ -0,0 +1,10 @@
export const defaultLocale = 'en';
export const timeZone = 'Europe/Amsterdam';
export const locales = [defaultLocale, 'ru'] as const;
export const localesMap = [
{ key: 'en', title: 'English' },
{ key: 'ru', title: 'Русский' },
];

View File

@ -0,0 +1,18 @@
import { defaultLocale, locales } from './config';
import { getLocale } from './locale';
import { type Locale } from './types';
import { getRequestConfig } from 'next-intl/server';
const requestConfig = getRequestConfig(async () => {
const locale = (await getLocale()) as Locale;
return {
locale,
messages:
locale === defaultLocale || !locales.includes(locale)
? (await import(`@/public/locales/${defaultLocale}.json`)).default
: (await import(`@/public/locales/${locale}.json`)).default,
};
});
export default requestConfig;

View File

@ -0,0 +1,20 @@
// use server is required
'use server';
import { defaultLocale } from './config';
import { type Locale } from './types';
import { cookies } from 'next/headers';
// In this example the locale is read from a cookie. You could alternatively
// also read it from a database, backend service, or any other source.
const COOKIE_NAME = 'NEXT_LOCALE';
const getLocale = async () => {
return cookies().get(COOKIE_NAME)?.value || defaultLocale;
};
const setLocale = async (locale?: string) => {
cookies().set(COOKIE_NAME, (locale as Locale) || defaultLocale);
};
export { getLocale, setLocale };

View File

@ -0,0 +1,14 @@
/* eslint-disable canonical/id-match */
import { timeZone } from './config';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { type PropsWithChildren } from 'react';
export async function I18nProvider({ children }: Readonly<PropsWithChildren>) {
const messages = await getMessages();
return (
<NextIntlClientProvider messages={messages} timeZone={timeZone}>
{children}
</NextIntlClientProvider>
);
}

View File

@ -0,0 +1,5 @@
import { type locales } from './config';
type Locale = (typeof locales)[number];
export type { Locale };

45
apps/web/utils/init.ts Normal file
View File

@ -0,0 +1,45 @@
/* eslint-disable no-console */
/* eslint-disable promise/prefer-await-to-then */
import {
backButton,
$debug as debugSDK,
initData,
init as initSDK,
miniApp,
themeParams,
viewport,
} from '@telegram-apps/sdk-react';
/**
* Initializes the application and configures its dependencies.
*/
export function init(debug: boolean): void {
// Set @telegram-apps/sdk-react debug mode.
debugSDK.set(debug);
// Initialize special event handlers for Telegram Desktop, Android, iOS, etc.
// Also, configure the package.
initSDK();
// Mount all components used in the project.
if (backButton.isSupported()) backButton.mount();
miniApp.mount();
themeParams.mount();
initData.restore();
void viewport
.mount()
.then(() => {
// Define components-related CSS variables.
viewport.bindCssVars();
miniApp.bindCssVars();
themeParams.bindCssVars();
})
.catch((error) => {
console.error('Something went wrong mounting the viewport', error);
});
// Add Eruda if needed.
if (debug) {
import('eruda').then((library) => library.default.init()).catch(console.error);
}
}

165
pnpm-lock.yaml generated
View File

@ -143,12 +143,18 @@ importers:
'@repo/ui':
specifier: workspace:*
version: link:../../packages/ui
'@telegram-apps/sdk-react':
specifier: ^2.0.19
version: 2.0.19(@types/react@19.0.1)(react@19.0.0)
graphql:
specifier: 'catalog:'
version: 16.9.0
next:
specifier: 'catalog:'
version: 15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-intl:
specifier: ^3.26.0
version: 3.26.0(next@15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
react:
specifier: 'catalog:'
version: 19.0.0
@ -201,6 +207,9 @@ importers:
autoprefixer:
specifier: 'catalog:'
version: 10.4.20(postcss@8.4.49)
eruda:
specifier: ^3.4.1
version: 3.4.1
jsdom:
specifier: 'catalog:'
version: 25.0.1
@ -1123,6 +1132,21 @@ packages:
resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@formatjs/ecma402-abstract@2.3.1':
resolution: {integrity: sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw==}
'@formatjs/fast-memoize@2.2.5':
resolution: {integrity: sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g==}
'@formatjs/icu-messageformat-parser@2.9.7':
resolution: {integrity: sha512-cuEHyRM5VqLQobANOjtjlgU7+qmk9Q3fDQuBiRRJ3+Wp3ZoZhpUPtUfuimZXsir6SaI2TaAJ+SLo9vLnV5QcbA==}
'@formatjs/icu-skeleton-parser@1.8.11':
resolution: {integrity: sha512-8LlHHE/yL/zVJZHAX3pbKaCjZKmBIO6aJY1mkVh4RMSEu/2WRZ4Ysvv3kKXJ9M8RJLBHdnk1/dUQFdod1Dt7Dw==}
'@formatjs/intl-localematcher@0.5.9':
resolution: {integrity: sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA==}
'@graphql-codegen/add@5.0.3':
resolution: {integrity: sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==}
peerDependencies:
@ -1812,6 +1836,36 @@ packages:
'@swc/helpers@0.5.13':
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
'@telegram-apps/bridge@1.7.1':
resolution: {integrity: sha512-oRbznpIC4UibMVygQ+tcS0ZSKx7DaI07MXQF42VETQ/VOCKeaWZeQFUifo4A+CzT6XMGo2hyse/CQP9ziX0H7g==}
'@telegram-apps/navigation@1.0.9':
resolution: {integrity: sha512-Ur24luu+fizrKCDQAoJWQzMj+IwNiqtQlrITz42DORKSohj5yvf9kD5AJO2r9sHC+iC2pLXHCn1dV34o6tbeaQ==}
'@telegram-apps/sdk-react@2.0.19':
resolution: {integrity: sha512-mQfBspQd+KbNW9gp26b2QN+T+SFiYfjZV1X91mM68hIPnChPjwCJRTZrNtx+nvT49B/NURMMtTT/eF84IX1ltA==}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
react: ^17.0.0 || ^18.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@telegram-apps/sdk@2.9.1':
resolution: {integrity: sha512-fW5e0B7yt4jIlEJfXzZwrY2cYufyXQw6xIu2eRfKl1l6N/ETIwZZRr6jOOsRCzzpWUfqePvwItqKIuRf8mGcog==}
'@telegram-apps/signals@1.1.0':
resolution: {integrity: sha512-5qN7cU8t3l7n0cKcnzc/1TYKJTwAggUinfwbLHL1SYmB47pBHjCvfsRiYliFohk6lb635SBmNuVZL6LHFmGZaw==}
'@telegram-apps/toolkit@1.0.0':
resolution: {integrity: sha512-fSVoveLuMzwRKWeXEufMSXxH+HvjsFKb1DeT3pG5qLpnb2rdtejnNcwAt6WEPtiZ3a4YntYaFuR3KYgVv0ZxeQ==}
'@telegram-apps/transformers@1.2.0':
resolution: {integrity: sha512-RufLdD044RPaAJdh+Mp/98JI+Wkp5mhX3WYCg6IZYFMRwu3QTu2FBwYmU9FdRmBF9utbcymSFrY1cqxh+Vtkfg==}
'@telegram-apps/types@1.2.0':
resolution: {integrity: sha512-HhvnSCsqlJpes5ZGsZP/qbDNq8eLLnjgZKaF5NRsDqAKUPvaIIFT1HdyDII/8EioUgoI4FHsP8MylK2Gzm2efg==}
'@testing-library/dom@10.4.0':
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
engines: {node: '>=18'}
@ -2684,6 +2738,9 @@ packages:
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
eruda@3.4.1:
resolution: {integrity: sha512-RmaO5yD97URY/9Q0lye3cmmNPoXNKreeePIw7c/zllbscR92CjGFZFuQ70+0fLIvLcKW3Xha8DS8NFhmeNbEBQ==}
es-abstract@1.23.5:
resolution: {integrity: sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==}
engines: {node: '>= 0.4'}
@ -3447,6 +3504,9 @@ packages:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
intl-messageformat@10.7.10:
resolution: {integrity: sha512-hp7iejCBiJdW3zmOe18FdlJu8U/JsADSDiBPQhfdSeI8B9POtvPRvPh3nMlvhYayGMKLv6maldhR7y3Pf1vkpw==}
invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
@ -3935,6 +3995,16 @@ packages:
resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==}
engines: {node: '>=18'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
next-intl@3.26.0:
resolution: {integrity: sha512-gkamnHIANQzeW8xpTGRxd0xiOCztQhY8GDp79fgdlw0GioqrjTEfSWLhHkgaAtvHRbuh/ByJdwiEY5eNK9bUSQ==}
peerDependencies:
next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
next@15.0.4:
resolution: {integrity: sha512-nuy8FH6M1FG0lktGotamQDCXhh5hZ19Vo0ht1AOIQWrYJLP598TIUagKtvJrfJ5AGwB/WmDqkKaKhMpVifvGPA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@ -5042,6 +5112,11 @@ packages:
urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
use-intl@3.26.0:
resolution: {integrity: sha512-HGXmpjGlbEv1uFZPfm557LK8p/hv0pKF9UwnrJeHUTxQx6bUGzMgpmPRLCVY3zkr7hfjy4LPwQJfk4Fhnn+dIg==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@ -6262,6 +6337,32 @@ snapshots:
dependencies:
levn: 0.4.1
'@formatjs/ecma402-abstract@2.3.1':
dependencies:
'@formatjs/fast-memoize': 2.2.5
'@formatjs/intl-localematcher': 0.5.9
decimal.js: 10.4.3
tslib: 2.8.1
'@formatjs/fast-memoize@2.2.5':
dependencies:
tslib: 2.8.1
'@formatjs/icu-messageformat-parser@2.9.7':
dependencies:
'@formatjs/ecma402-abstract': 2.3.1
'@formatjs/icu-skeleton-parser': 1.8.11
tslib: 2.8.1
'@formatjs/icu-skeleton-parser@1.8.11':
dependencies:
'@formatjs/ecma402-abstract': 2.3.1
tslib: 2.8.1
'@formatjs/intl-localematcher@0.5.9':
dependencies:
tslib: 2.8.1
'@graphql-codegen/add@5.0.3(graphql@16.9.0)':
dependencies:
'@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.9.0)
@ -7174,6 +7275,45 @@ snapshots:
dependencies:
tslib: 2.8.1
'@telegram-apps/bridge@1.7.1':
dependencies:
'@telegram-apps/signals': 1.1.0
'@telegram-apps/toolkit': 1.0.0
'@telegram-apps/transformers': 1.2.0
'@telegram-apps/types': 1.2.0
'@telegram-apps/navigation@1.0.9':
dependencies:
'@telegram-apps/bridge': 1.7.1
'@telegram-apps/signals': 1.1.0
'@telegram-apps/toolkit': 1.0.0
'@telegram-apps/sdk-react@2.0.19(@types/react@19.0.1)(react@19.0.0)':
dependencies:
'@telegram-apps/sdk': 2.9.1
react: 19.0.0
optionalDependencies:
'@types/react': 19.0.1
'@telegram-apps/sdk@2.9.1':
dependencies:
'@telegram-apps/bridge': 1.7.1
'@telegram-apps/navigation': 1.0.9
'@telegram-apps/signals': 1.1.0
'@telegram-apps/toolkit': 1.0.0
'@telegram-apps/transformers': 1.2.0
'@telegram-apps/signals@1.1.0': {}
'@telegram-apps/toolkit@1.0.0': {}
'@telegram-apps/transformers@1.2.0':
dependencies:
'@telegram-apps/toolkit': 1.0.0
'@telegram-apps/types': 1.2.0
'@telegram-apps/types@1.2.0': {}
'@testing-library/dom@10.4.0':
dependencies:
'@babel/code-frame': 7.26.2
@ -8176,6 +8316,8 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
eruda@3.4.1: {}
es-abstract@1.23.5:
dependencies:
array-buffer-byte-length: 1.0.1
@ -9246,6 +9388,13 @@ snapshots:
hasown: 2.0.2
side-channel: 1.0.6
intl-messageformat@10.7.10:
dependencies:
'@formatjs/ecma402-abstract': 2.3.1
'@formatjs/fast-memoize': 2.2.5
'@formatjs/icu-messageformat-parser': 2.9.7
tslib: 2.8.1
invariant@2.2.4:
dependencies:
loose-envify: 1.4.0
@ -9717,6 +9866,16 @@ snapshots:
natural-orderby@5.0.0: {}
negotiator@1.0.0: {}
next-intl@3.26.0(next@15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0):
dependencies:
'@formatjs/intl-localematcher': 0.5.9
negotiator: 1.0.0
next: 15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
use-intl: 3.26.0(react@19.0.0)
next@15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@next/env': 15.0.4
@ -10846,6 +11005,12 @@ snapshots:
urlpattern-polyfill@8.0.2: {}
use-intl@3.26.0(react@19.0.0):
dependencies:
'@formatjs/fast-memoize': 2.2.5
intl-messageformat: 10.7.10
react: 19.0.0
util-deprecate@1.0.2: {}
validate-npm-package-license@3.0.4: