diff --git a/apps/web/app/(base-org)/layout.tsx b/apps/web/app/(base-org)/layout.tsx index b0fe65ce05e..9b9919af9c2 100644 --- a/apps/web/app/(base-org)/layout.tsx +++ b/apps/web/app/(base-org)/layout.tsx @@ -4,6 +4,7 @@ import { Footer } from 'apps/web/src/components/Layout/Footer/Footer'; import MobileNav from 'apps/web/src/components/Layout/Navigation/MobileNav'; import { DynamicWrappedGasPriceDropdown } from 'apps/web/src/components/Layout/Navigation/GasPriceDropdown'; import AnalyticsProvider from 'apps/web/contexts/Analytics'; +import GoogleTranslate from 'apps/web/src/components/Layout/GoogleTranslate'; export const metadata: Metadata = { metadataBase: new URL('https://base.org'), @@ -31,6 +32,7 @@ export default async function BaseOrgLayout({ }) { return (
+
diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 8054d6906c6..b411d50f80b 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -82,6 +82,8 @@ const contentSecurityPolicy = { 'https://fonts.gstatic.com/', // OCK styles loads google fonts via CSS 'https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com', 'https://jsv3.recruitics.com/0778138b-cc59-11ef-a514-fd1759833eec.js', // recruitics job analytics + 'https://translate.google.com', // Google Translate widget + 'https://translate.googleapis.com', // Google Translate API ], 'worker-src': ["'self'", 'blob:'], 'connect-src': [ @@ -133,7 +135,7 @@ const contentSecurityPolicy = { 'wss://metamask-sdk.api.cx.metamask.io', // MetaMask SDK websocket 'https://metamask-sdk.api.cx.metamask.io', // MetaMask SDK API ], - 'frame-src': ['https://p.datadoghq.com', walletconnectDomains], + 'frame-src': ['https://p.datadoghq.com', walletconnectDomains, 'https://translate.google.com'], 'frame-ancestors': ["'self'", baseXYZDomains], 'form-action': ["'self'", baseXYZDomains], 'img-src': [ @@ -151,6 +153,8 @@ const contentSecurityPolicy = { `https://${process.env.NEXT_PUBLIC_PINATA_GATEWAY_URL}`, 'https://img.reservoir.tools', // reservoir 'https://d3r81g40ycuhqg.cloudfront.net/', // OCK Earn component + 'https://translate.google.com', // Google Translate widget assets + 'https://www.google.com', // Google Translate widget assets ], }; diff --git a/apps/web/src/components/Layout/GoogleTranslate/index.tsx b/apps/web/src/components/Layout/GoogleTranslate/index.tsx new file mode 100644 index 00000000000..e199baffe93 --- /dev/null +++ b/apps/web/src/components/Layout/GoogleTranslate/index.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { useEffect, useCallback, useState } from 'react'; + +declare global { + interface Window { + googleTranslateElementInit: () => void; + google: { + translate: { + TranslateElement: new ( + options: { pageLanguage: string; autoDisplay: boolean }, + elementId: string, + ) => void; + }; + }; + } +} + +export default function GoogleTranslate() { + const [isLoaded, setIsLoaded] = useState(false); + + const initTranslate = useCallback(() => { + if (window.google?.translate?.TranslateElement) { + new window.google.translate.TranslateElement( + { pageLanguage: 'en', autoDisplay: false }, + 'google_translate_element', + ); + setIsLoaded(true); + } + }, []); + + useEffect(() => { + // Avoid re-initializing if already loaded + if (document.getElementById('google-translate-script')) { + if (window.google?.translate?.TranslateElement) { + initTranslate(); + } + return; + } + + window.googleTranslateElementInit = initTranslate; + + const script = document.createElement('script'); + script.id = 'google-translate-script'; + script.src = + 'https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'; + script.async = true; + document.head.appendChild(script); + + return () => { + // Cleanup callback on unmount + delete (window as Partial).googleTranslateElementInit; + }; + }, [initTranslate]); + + return ( +
+
+ +
+
+
+ ); +} + +function GlobeIcon(props: React.SVGProps) { + return ( + + + + ); +}