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 (
+
+ );
+}