From 49df4365ca32335319ccc5ba606f1bde9c55a043 Mon Sep 17 00:00:00 2001 From: Vlad Chikalkin Date: Wed, 11 Dec 2024 16:00:55 +0300 Subject: [PATCH] Feature/telegram integration (#4) * add files from official template * remove all official template trash --- apps/web/app/layout.tsx | 28 ++-- apps/web/app/page.test.tsx | 8 - apps/web/app/page.tsx | 17 +- apps/web/components/telegram/Root.tsx | 45 +++++ apps/web/components/telegram/index.ts | 1 + apps/web/hooks/telegram/index.ts | 3 + apps/web/hooks/telegram/use-client-once.ts | 9 + apps/web/hooks/telegram/use-did-mount.ts | 14 ++ apps/web/hooks/telegram/use-telegram-mock.ts | 76 +++++++++ apps/web/next.config.js | 9 +- apps/web/package.json | 3 + apps/web/public/locales/en.json | 6 + apps/web/public/locales/ru.json | 6 + apps/web/tsconfig.json | 6 +- apps/web/utils/i18n/config.ts | 10 ++ apps/web/utils/i18n/i18n.ts | 18 ++ apps/web/utils/i18n/locale.ts | 20 +++ apps/web/utils/i18n/provider.tsx | 14 ++ apps/web/utils/i18n/types.ts | 5 + apps/web/utils/init.ts | 45 +++++ pnpm-lock.yaml | 165 +++++++++++++++++++ 21 files changed, 473 insertions(+), 35 deletions(-) delete mode 100644 apps/web/app/page.test.tsx create mode 100644 apps/web/components/telegram/Root.tsx create mode 100644 apps/web/components/telegram/index.ts create mode 100644 apps/web/hooks/telegram/index.ts create mode 100644 apps/web/hooks/telegram/use-client-once.ts create mode 100644 apps/web/hooks/telegram/use-did-mount.ts create mode 100644 apps/web/hooks/telegram/use-telegram-mock.ts create mode 100644 apps/web/public/locales/en.json create mode 100644 apps/web/public/locales/ru.json create mode 100644 apps/web/utils/i18n/config.ts create mode 100644 apps/web/utils/i18n/i18n.ts create mode 100644 apps/web/utils/i18n/locale.ts create mode 100644 apps/web/utils/i18n/provider.tsx create mode 100644 apps/web/utils/i18n/types.ts create mode 100644 apps/web/utils/init.ts diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index e305402..1fb90c2 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -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) { + const locale = await getLocale(); + return ( - - {children} + + + + {children} + + ); } diff --git a/apps/web/app/page.test.tsx b/apps/web/app/page.test.tsx deleted file mode 100644 index 756448a..0000000 --- a/apps/web/app/page.test.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import Page from './page'; -import { render, screen } from '@testing-library/react'; -import { expect, test } from 'vitest'; - -test('Page', () => { - render(); - expect(screen.getByRole('button', { name: 'Click me' })).toBeDefined(); -}); diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 9903988..a21501b 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -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 ( -
- -
+ + {JSON.stringify(initDataState, null, 2)} + ); } diff --git a/apps/web/components/telegram/Root.tsx b/apps/web/components/telegram/Root.tsx new file mode 100644 index 0000000..d479a8a --- /dev/null +++ b/apps/web/components/telegram/Root.tsx @@ -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) { + // 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
Loading
; + + return ; +} + +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; +} diff --git a/apps/web/components/telegram/index.ts b/apps/web/components/telegram/index.ts new file mode 100644 index 0000000..c78e011 --- /dev/null +++ b/apps/web/components/telegram/index.ts @@ -0,0 +1 @@ +export * from './Root'; diff --git a/apps/web/hooks/telegram/index.ts b/apps/web/hooks/telegram/index.ts new file mode 100644 index 0000000..36c0b34 --- /dev/null +++ b/apps/web/hooks/telegram/index.ts @@ -0,0 +1,3 @@ +export * from './use-client-once'; +export * from './use-did-mount'; +export * from './use-telegram-mock'; diff --git a/apps/web/hooks/telegram/use-client-once.ts b/apps/web/hooks/telegram/use-client-once.ts new file mode 100644 index 0000000..4ae00ae --- /dev/null +++ b/apps/web/hooks/telegram/use-client-once.ts @@ -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(); + } +} diff --git a/apps/web/hooks/telegram/use-did-mount.ts b/apps/web/hooks/telegram/use-did-mount.ts new file mode 100644 index 0000000..0c4c392 --- /dev/null +++ b/apps/web/hooks/telegram/use-did-mount.ts @@ -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; +} diff --git a/apps/web/hooks/telegram/use-telegram-mock.ts b/apps/web/hooks/telegram/use-telegram-mock.ts new file mode 100644 index 0000000..181b881 --- /dev/null +++ b/apps/web/hooks/telegram/use-telegram-mock.ts @@ -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.', + ); + }); +} diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 0f656ae..d36077c 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -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; diff --git a/apps/web/package.json b/apps/web/package.json index 9e81b07..069c7c4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -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:", diff --git a/apps/web/public/locales/en.json b/apps/web/public/locales/en.json new file mode 100644 index 0000000..1d633ec --- /dev/null +++ b/apps/web/public/locales/en.json @@ -0,0 +1,6 @@ +{ + "i18n": { + "header": "Application supports i18n", + "footer": "You can select a different language from the dropdown menu." + } +} \ No newline at end of file diff --git a/apps/web/public/locales/ru.json b/apps/web/public/locales/ru.json new file mode 100644 index 0000000..fbaf2c3 --- /dev/null +++ b/apps/web/public/locales/ru.json @@ -0,0 +1,6 @@ +{ + "i18n": { + "header": "Поддержка i18n", + "footer": "Вы можете выбрать другой язык в выпадающем меню." + } +} \ No newline at end of file diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 24e7548..4f8e45b 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -5,7 +5,11 @@ { "name": "next" } - ] + ], + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } }, "include": [ "next-env.d.ts", diff --git a/apps/web/utils/i18n/config.ts b/apps/web/utils/i18n/config.ts new file mode 100644 index 0000000..915d153 --- /dev/null +++ b/apps/web/utils/i18n/config.ts @@ -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: 'Русский' }, +]; diff --git a/apps/web/utils/i18n/i18n.ts b/apps/web/utils/i18n/i18n.ts new file mode 100644 index 0000000..68ea186 --- /dev/null +++ b/apps/web/utils/i18n/i18n.ts @@ -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; diff --git a/apps/web/utils/i18n/locale.ts b/apps/web/utils/i18n/locale.ts new file mode 100644 index 0000000..eeab8e8 --- /dev/null +++ b/apps/web/utils/i18n/locale.ts @@ -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 }; diff --git a/apps/web/utils/i18n/provider.tsx b/apps/web/utils/i18n/provider.tsx new file mode 100644 index 0000000..c5294db --- /dev/null +++ b/apps/web/utils/i18n/provider.tsx @@ -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) { + const messages = await getMessages(); + return ( + + {children} + + ); +} diff --git a/apps/web/utils/i18n/types.ts b/apps/web/utils/i18n/types.ts new file mode 100644 index 0000000..30f3bff --- /dev/null +++ b/apps/web/utils/i18n/types.ts @@ -0,0 +1,5 @@ +import { type locales } from './config'; + +type Locale = (typeof locales)[number]; + +export type { Locale }; diff --git a/apps/web/utils/init.ts b/apps/web/utils/init.ts new file mode 100644 index 0000000..eddded6 --- /dev/null +++ b/apps/web/utils/init.ts @@ -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); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 881c079..60cae90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: