diff --git a/packages/interface/package.json b/packages/interface/package.json index 8b81a2f9..f7f13115 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -20,14 +20,16 @@ "@hatsprotocol/sdk-v1-core": "^0.10.0", "@hookform/resolvers": "^3.3.4", "@nivo/line": "^0.84.0", + "@openzeppelin/merkle-tree": "^1.0.7", "@pcd/eddsa-pcd": "^0.6.5", "@pcd/pcd-types": "^0.11.4", "@pcd/util": "^0.5.4", "@pcd/zk-eddsa-event-ticket-pcd": "^0.6.6", "@pcd/zuauth": "^1.4.5", - "@openzeppelin/merkle-tree": "^1.0.7", + "@radix-ui/colors": "^3.0.0", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-icons": "^1.3.0", "@rainbow-me/rainbowkit": "^2.0.1", "@semaphore-protocol/core": "4.0.3", "@semaphore-protocol/data": "4.0.3", @@ -43,6 +45,9 @@ "dotenv": "^16.4.1", "ethers": "^6.13.1", "graphql-request": "^6.1.0", + "i18next": "^23.15.1", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.6.1", "js-sha256": "^0.11.0", "lowdb": "^1.0.0", "lucide-react": "^0.316.0", @@ -56,6 +61,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.49.3", + "react-i18next": "^15.0.2", "react-icons": "^5.0.1", "react-markdown": "^9.0.1", "react-number-format": "^5.3.1", diff --git a/packages/interface/src/components/EligibilityDialog.tsx b/packages/interface/src/components/EligibilityDialog.tsx index fe5ec20a..22ef6981 100644 --- a/packages/interface/src/components/EligibilityDialog.tsx +++ b/packages/interface/src/components/EligibilityDialog.tsx @@ -5,6 +5,7 @@ import { zuAuthPopup } from "@pcd/zuauth"; import { GatekeeperTrait, getZupassGatekeeperData } from "maci-cli/sdk"; import { useRouter } from "next/router"; import { useState, useCallback, useEffect } from "react"; +import { useTranslation } from 'react-i18next'; import { toast } from "sonner"; import { useAccount, useDisconnect } from "wagmi"; @@ -34,6 +35,7 @@ export const EligibilityDialog = (): JSX.Element | null => { storeZupassProof, } = useMaci(); const router = useRouter(); + const { t } = useTranslation(); const appState = useAppState(); @@ -205,7 +207,7 @@ export const EligibilityDialog = (): JSX.Element | null => { description="The result is under tallying, please come back to check the result later." isOpen={openDialog} size="sm" - title="The result is under tallying" + title={t("tallying")} onOpenChange={handleCloseDialog} /> ); diff --git a/packages/interface/src/components/Header.tsx b/packages/interface/src/components/Header.tsx index 4895d962..55e06954 100644 --- a/packages/interface/src/components/Header.tsx +++ b/packages/interface/src/components/Header.tsx @@ -12,6 +12,7 @@ import { EAppState } from "~/utils/types"; import { ConnectButton } from "./ConnectButton"; import { IconButton } from "./ui/Button"; +import { LanguageButton } from "./LanguageButton"; import { Logo } from "./ui/Logo"; interface INavLinkProps extends ComponentPropsWithRef { @@ -100,9 +101,11 @@ const Header = ({ navLinks }: IHeaderProps) => { })} +
+ { + const { i18n } = useTranslation(); + const [language, setLanguage] = React.useState(i18n.language); + const router = useRouter(); + + const onChangeLanguage = (value: string) => { + setLanguage(value); + i18n.changeLanguage(value); + router.refresh(); + } + + return ( + + + + {language.toUpperCase()} + + + + + + + + + + + 🇺🇸 English + + + + + + 🇪🇸 Español + + + + + + + + ); +}; diff --git a/packages/interface/src/components/ui/Navigation.tsx b/packages/interface/src/components/ui/Navigation.tsx index 6f50c170..b16e645b 100644 --- a/packages/interface/src/components/ui/Navigation.tsx +++ b/packages/interface/src/components/ui/Navigation.tsx @@ -1,19 +1,23 @@ import Link from "next/link"; +import { useTranslation } from "react-i18next"; interface INavigationProps { projectName: string; } -export const Navigation = ({ projectName }: INavigationProps): JSX.Element => ( -
- - Projects - +export const Navigation = ({ projectName }: INavigationProps): JSX.Element => { + const { t } = useTranslation(); + return( +
+ + {t("projects")} + - {">"} + {">"} - - {projectName} - -
-); + + {projectName} + +
+ ); +} diff --git a/packages/interface/src/layouts/DefaultLayout.tsx b/packages/interface/src/layouts/DefaultLayout.tsx index 562d09f2..77e5b23f 100644 --- a/packages/interface/src/layouts/DefaultLayout.tsx +++ b/packages/interface/src/layouts/DefaultLayout.tsx @@ -1,6 +1,7 @@ import { GatekeeperTrait } from "maci-cli/sdk"; import { type ReactNode, type PropsWithChildren, useMemo } from "react"; import { useAccount } from "wagmi"; +import { useTranslation } from "react-i18next"; import Header from "~/components/Header"; import { Info } from "~/components/Info"; @@ -26,33 +27,34 @@ export const Layout = ({ children = null, ...props }: ILayoutProps): JSX.Element const appState = useAppState(); const { ballot } = useBallot(); const { isRegistered, gatekeeperTrait } = useMaci(); + const { t } = useTranslation(); const navLinks = useMemo(() => { const links = [ { href: "/projects", - children: "Projects", + children: t("links.projects"), }, ]; if (appState === EAppState.VOTING && isRegistered) { links.push({ href: "/ballot", - children: "My Ballot", + children: t("links.ballot"), }); } if ((appState === EAppState.TALLYING || appState === EAppState.RESULTS) && ballot.published) { links.push({ href: "/ballot/confirmation", - children: "Submitted Ballot", + children: t("links.ballot_submitted"), }); } if (appState === EAppState.RESULTS) { links.push({ href: "/stats", - children: "Stats", + children: t("links.stats"), }); } @@ -61,7 +63,7 @@ export const Layout = ({ children = null, ...props }: ILayoutProps): JSX.Element ...[ { href: "/applications", - children: "Applications", + children: t("links.applications"), }, ], ); @@ -72,7 +74,7 @@ export const Layout = ({ children = null, ...props }: ILayoutProps): JSX.Element ...[ { href: "/voters", - children: "Voters", + children: t("links.voters"), }, ], ); @@ -93,6 +95,7 @@ export const LayoutWithSidebar = ({ ...props }: ILayoutProps): JSX.Element => { const { address } = useAccount(); const { ballot } = useBallot(); const appState = useAppState(); + const { t } = useTranslation(); const { showInfo, showBallot, showSubmitButton } = props; @@ -114,7 +117,7 @@ export const LayoutWithSidebar = ({ ...props }: ILayoutProps): JSX.Element => {
)} diff --git a/packages/interface/src/locales/en/translations.json b/packages/interface/src/locales/en/translations.json new file mode 100644 index 00000000..81758ce1 --- /dev/null +++ b/packages/interface/src/locales/en/translations.json @@ -0,0 +1,13 @@ +{ + "links":{ + "projects": "Projects", + "ballot": "My Ballot", + "ballot_submitted": "Submitted Ballot", + "stats": "Stats", + "applications": "Applications", + "voters": "Voters", + "showSubmitBallot": "This is not a final submission, you can edit your ballot and resubmit it anytime during the voting period." + + }, + "tallying": "The result is under tallying" +} diff --git a/packages/interface/src/locales/es/translations.json b/packages/interface/src/locales/es/translations.json new file mode 100644 index 00000000..d6eae9b1 --- /dev/null +++ b/packages/interface/src/locales/es/translations.json @@ -0,0 +1,12 @@ +{ + "links": { + "projects": "Proyectos", + "ballot": "Mi Boleta", + "ballot_submitted": "Boleta Enviada", + "stats": "Estadísticas", + "applications": "Aplicaciones", + "voters": "Votantes", + "showSubmitBallot": "Esto no es una presentación final, puedes editar tu boleta y enviarla nuevamente en cualquier momento durante el período de votación." + }, + "tallying": "El resultado está siendo contabilizado" +} diff --git a/packages/interface/src/pages/_app.tsx b/packages/interface/src/pages/_app.tsx index 3e51562d..d3a03659 100644 --- a/packages/interface/src/pages/_app.tsx +++ b/packages/interface/src/pages/_app.tsx @@ -3,9 +3,11 @@ import { Inter } from "next/font/google"; import { Providers } from "~/providers"; import "~/styles/globals.css"; +import "~/styles/languageButton.css"; import { api } from "~/utils/api"; import type { AppProps } from "next/app"; +import './i18n'; const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); diff --git a/packages/interface/src/pages/i18n.ts b/packages/interface/src/pages/i18n.ts new file mode 100644 index 00000000..49debd3a --- /dev/null +++ b/packages/interface/src/pages/i18n.ts @@ -0,0 +1,27 @@ +import i18n from "i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import HttpApi from "i18next-http-backend"; +import { initReactI18next } from "react-i18next"; +import enTranslation from "../locales/en/translations.json"; +import esTranslation from "../locales/es/translations.json"; + +const resources = { + en: { translation: enTranslation }, + es: { translation: esTranslation }, + }; + +i18n + .use(HttpApi) // Enables loading translation files over HTTP. + .use(LanguageDetector) // Detects user language. + .use(initReactI18next) // Initializes the react-i18next plugin. + .init({ + resources, + supportedLngs: ["en", "es"], // Languages we're supporting. + fallbackLng: "en", // Fallback language if user's language isn't supported. + detection: { + order: ["cookie", "htmlTag", "localStorage", "path", "subdomain"], // Order of language detection. + caches: ["cookie"], // Cache the detected language in cookies. + } + }); + +export default i18n; diff --git a/packages/interface/src/styles/languageButton.css b/packages/interface/src/styles/languageButton.css new file mode 100644 index 00000000..8051940a --- /dev/null +++ b/packages/interface/src/styles/languageButton.css @@ -0,0 +1,162 @@ +@import '@radix-ui/colors/black-alpha.css'; +@import '@radix-ui/colors/gray.css'; +@import '@radix-ui/colors/violet.css'; + +.DropdownMenuContent, +.DropdownMenuSubContent { + min-width: 220px; + background-color: white; + border-radius: 6px; + padding: 5px; + box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); + animation-duration: 400ms; + animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); + will-change: transform, opacity; +} +.DropdownMenuContent[data-side='top'], +.DropdownMenuSubContent[data-side='top'] { + animation-name: slideDownAndFade; +} +.DropdownMenuContent[data-side='right'], +.DropdownMenuSubContent[data-side='right'] { + animation-name: slideLeftAndFade; +} +.DropdownMenuContent[data-side='bottom'], +.DropdownMenuSubContent[data-side='bottom'] { + animation-name: slideUpAndFade; +} +.DropdownMenuContent[data-side='left'], +.DropdownMenuSubContent[data-side='left'] { + animation-name: slideRightAndFade; +} + +.DropdownMenuItem, +.DropdownMenuCheckboxItem, +.DropdownMenuRadioItem, +.DropdownMenuSubTrigger { + font-size: 13px; + line-height: 1; + color: var(--gray-11); + border-radius: 3px; + display: flex; + align-items: center; + height: 25px; + padding: 0 5px; + position: relative; + padding-left: 25px; + user-select: none; + outline: none; +} +.DropdownMenuSubTrigger[data-state='open'] { + background-color: var(--gray-4); + color: var(--gray-11); +} +.DropdownMenuItem[data-disabled], +.DropdownMenuCheckboxItem[data-disabled], +.DropdownMenuRadioItem[data-disabled], +.DropdownMenuSubTrigger[data-disabled] { + color: var(--gray-8); + pointer-events: none; +} +.DropdownMenuItem[data-highlighted], +.DropdownMenuCheckboxItem[data-highlighted], +.DropdownMenuRadioItem[data-highlighted], +.DropdownMenuSubTrigger[data-highlighted] { + background-color: #579bea; + color: var(--gray-1); +} + +.DropdownMenuLabel { + padding-left: 25px; + font-size: 12px; + line-height: 25px; + color: var(--gray-11); +} + +.DropdownMenuSeparator { + height: 1px; + background-color: #579bea; + margin: 5px; +} + +.DropdownMenuItemIndicator { + position: absolute; + left: 0; + width: 25px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.DropdownMenuArrow { + fill: white; +} + +.IconButton { + font-family: inherit; + border-radius: 100%; + height: 35px; + width: 35px; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--gray-11); + background-color: white; + box-shadow: 0 2px 10px var(--black-a7); +} + +.RightSlot { + margin-left: auto; + padding-left: 20px; + color: var(--gray-11); +} +[data-highlighted] > .RightSlot { + color: white; +} +[data-disabled] .RightSlot { + color: var(--gray-8); +} + +@keyframes slideUpAndFade { + from { + opacity: 0; + transform: translateY(2px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideRightAndFade { + from { + opacity: 0; + transform: translateX(-2px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slideDownAndFade { + from { + opacity: 0; + transform: translateY(-2px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideLeftAndFade { + from { + opacity: 0; + transform: translateX(2px); + } + to { + opacity: 1; + transform: translateX(0); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57f5f457..3bea7504 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -340,12 +340,18 @@ importers: '@pcd/zuauth': specifier: ^1.4.5 version: 1.4.5(bufferutil@4.0.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(utf-8-validate@5.0.10) + '@radix-ui/colors': + specifier: ^3.0.0 + version: 3.0.0 '@radix-ui/react-dialog': specifier: ^1.0.5 version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-dropdown-menu': specifier: ^2.0.6 version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-icons': + specifier: ^1.3.0 + version: 1.3.0(react@18.2.0) '@rainbow-me/rainbowkit': specifier: ^2.0.1 version: 2.1.4(@tanstack/react-query@5.51.21(react@18.2.0))(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))(wagmi@2.12.4(@tanstack/query-core@5.51.21)(@tanstack/react-query@5.51.21(react@18.2.0))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(immer@10.0.2)(react-dom@18.2.0(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.2.0)(utf-8-validate@5.0.10))(react@18.2.0)(rollup@4.20.0)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.19.1(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.22.4))(zod@3.22.4)) @@ -391,6 +397,15 @@ importers: graphql-request: specifier: ^6.1.0 version: 6.1.0(encoding@0.1.13)(graphql@16.9.0) + i18next: + specifier: ^23.15.1 + version: 23.15.1 + i18next-browser-languagedetector: + specifier: ^8.0.0 + version: 8.0.0 + i18next-http-backend: + specifier: ^2.6.1 + version: 2.6.1(encoding@0.1.13) js-sha256: specifier: ^0.11.0 version: 0.11.0 @@ -430,6 +445,9 @@ importers: react-hook-form: specifier: ^7.49.3 version: 7.52.2(react@18.2.0) + react-i18next: + specifier: ^15.0.2 + version: 15.0.2(i18next@23.15.1)(react-dom@18.2.0(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.2.0)(utf-8-validate@5.0.10))(react@18.2.0) react-icons: specifier: ^5.0.1 version: 5.2.1(react@18.2.0) @@ -3048,6 +3066,9 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/colors@3.0.0': + resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==} + '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} @@ -3165,6 +3186,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-icons@1.3.0': + resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x + '@radix-ui/react-id@1.1.0': resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: @@ -8187,6 +8213,9 @@ packages: engines: {node: '>=12'} hasBin: true + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + html-url-attributes@3.0.0: resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==} @@ -8309,9 +8338,18 @@ packages: i18next-browser-languagedetector@7.1.0: resolution: {integrity: sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA==} + i18next-browser-languagedetector@8.0.0: + resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==} + + i18next-http-backend@2.6.1: + resolution: {integrity: sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==} + i18next@23.11.5: resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==} + i18next@23.15.1: + resolution: {integrity: sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -11335,6 +11373,19 @@ packages: peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 + react-i18next@15.0.2: + resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + react-icons@5.2.1: resolution: {integrity: sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==} peerDependencies: @@ -13239,6 +13290,10 @@ packages: vlq@1.0.1: resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + vscode-json-languageservice@4.2.1: resolution: {integrity: sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA==} @@ -17692,6 +17747,8 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@radix-ui/colors@3.0.0': {} + '@radix-ui/primitive@1.1.0': {} '@radix-ui/react-arrow@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': @@ -17800,6 +17857,10 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-icons@1.3.0(react@18.2.0)': + dependencies: + react: 18.2.0 + '@radix-ui/react-id@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) @@ -20546,7 +20607,7 @@ snapshots: axe-core@4.10.0: {} - axios-debug-log@1.0.0(axios@1.7.3(debug@4.3.6)): + axios-debug-log@1.0.0(axios@1.7.3): dependencies: '@types/debug': 4.1.12 axios: 1.7.3(debug@4.3.6) @@ -24740,6 +24801,10 @@ snapshots: relateurl: 0.2.7 terser: 5.31.3 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + html-url-attributes@3.0.0: {} html-webpack-plugin@4.5.2(webpack@5.92.1): @@ -24885,10 +24950,24 @@ snapshots: dependencies: '@babel/runtime': 7.25.0 + i18next-browser-languagedetector@8.0.0: + dependencies: + '@babel/runtime': 7.25.0 + + i18next-http-backend@2.6.1(encoding@0.1.13): + dependencies: + cross-fetch: 4.0.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + i18next@23.11.5: dependencies: '@babel/runtime': 7.25.0 + i18next@23.15.1: + dependencies: + '@babel/runtime': 7.25.0 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -28973,6 +29052,16 @@ snapshots: dependencies: react: 18.2.0 + react-i18next@15.0.2(i18next@23.15.1)(react-dom@18.2.0(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.2.0)(utf-8-validate@5.0.10))(react@18.2.0): + dependencies: + '@babel/runtime': 7.25.0 + html-parse-stringify: 3.0.1 + i18next: 23.15.1 + react: 18.2.0 + optionalDependencies: + react-dom: 18.2.0(react@18.2.0) + react-native: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.3(@babel/core@7.25.2))(@types/react@18.3.3)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.2.0)(utf-8-validate@5.0.10) + react-icons@5.2.1(react@18.2.0): dependencies: react: 18.2.0 @@ -29857,7 +29946,7 @@ snapshots: '@aduh95/viz.js': 3.7.0 '@solidity-parser/parser': 0.16.2 axios: 1.7.3(debug@4.3.6) - axios-debug-log: 1.0.0(axios@1.7.3(debug@4.3.6)) + axios-debug-log: 1.0.0(axios@1.7.3) cli-color: 2.0.4 commander: 11.1.0 convert-svg-to-png: 0.6.4(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) @@ -31249,6 +31338,8 @@ snapshots: vlq@1.0.1: {} + void-elements@3.1.0: {} + vscode-json-languageservice@4.2.1: dependencies: jsonc-parser: 3.3.1