
Google Translate customization under NextJS
Traditionally, I'd like to start the article with the following. A few months ago, I faced a situation when my customer asked for a multi-language feature on a NextJS-based solution. The issue is that providing all local content with its vast volume and limited budget is impossible.
In other words, we have only one local version, say, English, and we need to translate it automatically to some others, say, Italian, Spanish, French, etc. But this isn't over. The future language switcher should be friendly with the current UI and 100% under the developer's control.
I started thinking and found that onlyone approach was suitable. It doesn't require additional settings on the Google Console side and allows us to translate to any language without pain.
You cantry the solution, by the way.
However, the following problems still need to be solved.
- Not the fact that the solution above, as it is, matched with NextJS specific.
- The standard dropdown component looks too generic and is not customizable as the customer requested.
I don't want to put my routine of the research process on your plate, but describe the final decision step by step. If you want to face with my final solution now, please look athttps://github.com/buchslava/nextjs-gtrans-demo.
Let's get started with the explanation!
Bootstrapping
Create a new NextJS project.
npx create-next-app@latest
What is your project named? -> nextjs-gtrans-demoWould you like to use TypeScript? -> YesWould you like to use ESLint? -> NoWould you like to use Tailwind CSS? -> YesWould you like to use `src/` directory? -> YesWould you like to use App Router? -> NoWould you like to customize the default import alias? -> No
Also, install one extra dependency.
npm i nookies --save
Now we can run the app
npm run dev
It's time to implement the solution into the app. Please don't worry if you don't find some expected components during placing the code. Future steps will resolve it.
The main part
Let's change content insrc/pages/index.tsx
import{LanguageSwitcher}from"./lang-switcher";exportdefaultfunctionHome(){return(<divclassName="h-screen flex flex-col"><headerclassName="w-full pt-4"><LanguageSwitcher/></header><divclassName="flex flex-col flex-1 overflow-auto"><hrclassName="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700"/><article><h2className="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-3xl dark:text-white">WhatisLoremIpsum?</h2><pclassName="mb-7">LoremIpsumissimplydummytextoftheprintingandtypesettingindustry....</p></article>// This is a part of the content. Please take the full version for the original solution!</div><footer><pclassName="mt-3"><ahref="https://www.lipsum.com/"target="_blank"className="inline-flex items-center justify-center px-5 py-3 text-base font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-900">Source<svgclassName="w-3.5 h-3.5 ml-2"aria-hidden="true"xmlns="http://www.w3.org/2000/svg"fill="none"viewBox="0 0 14 10"><pathstroke="currentColor"stroke-linecap="round"stroke-linejoin="round"stroke-width="2"d="M1 5h12m0 0L9 1m4 4L9 9"/></svg></a></p></footer></div>);}
I recommend temporarily forgetting theLanguageSwitcher
component and focusing on the content mentioned above. The file contains three logical parts.
- The header includes the language switcher component (will be described later)
- The central part includes four paragraphs regarding
Lorem Ipsum
explanation - The footer contains a button as a link to the source of the content
Let's changesrc/pages/_document.tsx
import{Html,Head,Main,NextScript}from"next/document";importScriptfrom"next/script";exportdefaultfunctionDocument(){return(<Html><Head><Scriptsrc="/assets/scripts/lang-config.js"strategy="beforeInteractive"/><Scriptsrc="/assets/scripts/translation.js"strategy="beforeInteractive"/><Scriptsrc="//translate.google.com/translate_a/element.js?cb=TranslateInit"strategy="afterInteractive"/></Head><body><Main/><NextScript/></body></Html>);}
The main difference between this file and the default one is a set of three scripts below.
- public/assets/scripts/lang-config.js contains custom languages settings
- public/assets/scripts/translation.js contains
TranslateInit
callback function definition that will be used as a parameter to the main translation script //translate.google.com/translate_a/element.js?cb=TranslateInit
- the main translation script by Google. Pay attention oncb=TranslateInit
. The callback function must be passed here.
The scripts
It's time to provide the code of the scripts mentioned above.
public/assets/scripts/lang-config.js
window.__GOOGLE_TRANSLATION_CONFIG__={languages:[{title:"English",name:"en"},{title:"Deutsch",name:"de"},{title:"Español",name:"es"},{title:"Français",name:"fr"},],defaultLanguage:"en",};
In this example, we declared four languages to use.
public/assets/scripts/translation.js
functionTranslateInit(){if(!window.__GOOGLE_TRANSLATION_CONFIG__){return;}newgoogle.translate.TranslateElement({pageLanguage:window.__GOOGLE_TRANSLATION_CONFIG__.defaultLanguage,});}
Here is a callback definition that includesgoogle.translate.TranslateElement
call. If we don't have the configuration, we pass it to Google's script nothing i.e. an empty callback. Otherwise, we callgoogle.translate.TranslateElement
and pass the original content language.
And it's finally time to provide and explain the most critical part of the solution. I'm talking about theLanguageSwitcher
mentioned before.
The LanguageSwitcher component
src/components/lang-switcher.tsx
Please, pay attention to the comments inside the code below.
import{useEffect,useState}from"react";import{parseCookies,setCookie}from"nookies";// The following cookie name is important because it's Google-predefined for the translation engine purposeconstCOOKIE_NAME="googtrans";// We should know a predefined nickname of a language and provide its title (the name for displaying)interfaceLanguageDescriptor{name:string;title:string;}// The following definition describes typings for JS-based declarations in public/assets/scripts/lang-config.jsdeclareglobal{namespaceglobalThis{var__GOOGLE_TRANSLATION_CONFIG__:{languages:LanguageDescriptor[];defaultLanguage:string;};}}constLanguageSwitcher=()=>{const[currentLanguage,setCurrentLanguage]=useState<string>();const[languageConfig,setLanguageConfig]=useState<any>();// When the component has initialized, we must activate the translation engine the following way.useEffect(()=>{// 1. Read the cookieconstcookies=parseCookies()constexistingLanguageCookieValue=cookies[COOKIE_NAME];letlanguageValue;if(existingLanguageCookieValue){// 2. If the cookie is defined, extract a language nickname from there.constsp=existingLanguageCookieValue.split("/");if(sp.length>2){languageValue=sp[2];}}// 3. If __GOOGLE_TRANSLATION_CONFIG__ is defined and we still not decided about languageValue, let's take a current language from the predefined defaultLanguage below.if(global.__GOOGLE_TRANSLATION_CONFIG__&&!languageValue){languageValue=global.__GOOGLE_TRANSLATION_CONFIG__.defaultLanguage;}if(languageValue){// 4. Set the current language if we have a related decision.setCurrentLanguage(languageValue);}// 5. Set the language config.if(global.__GOOGLE_TRANSLATION_CONFIG__){setLanguageConfig(global.__GOOGLE_TRANSLATION_CONFIG__);}},[]);// Don't display anything if current language information is unavailable.if(!currentLanguage||!languageConfig){returnnull;}// The following function switches the current languageconstswitchLanguage=(lang:string)=>()=>{// We just need to set the related cookie and reload the page// "/auto/" prefix is Google's definition as far as a cookie namesetCookie(null,COOKIE_NAME,"/auto/"+lang)window.location.reload();};return(<divclassName="text-center notranslate">{languageConfig.languages.map((ld:LanguageDescriptor,i:number)=>(<>{currentLanguage===ld.name||(currentLanguage==="auto"&&languageConfig.defaultLanguage===ld)?(<spankey={`l_s_${ld}`}className="mx-3 text-orange-300">{ld.title}</span>):(<akey={`l_s_${ld}`}onClick={switchLanguage(ld.name)}className="mx-3 text-blue-300 cursor-pointer hover:underline">{ld.title}</a>)}</>))}</div>);};export{LanguageSwitcher,COOKIE_NAME};
Pay attention tonotranslate
class in the root div before. This is also Google's definition. It means that all of the content inside should not be translated. It's crucial because language titles should stay untouched, i.e., as they are.
Working principles
It's time to gather all the information above and explain how the solution works.
The start point is placed insrc/pages/_document.tsx
import{Html,Head,Main,NextScript}from"next/document";importScriptfrom"next/script";exportdefaultfunctionDocument(){return(<Html><Head><Scriptsrc="/assets/scripts/lang-config.js"strategy="beforeInteractive"/><Scriptsrc="/assets/scripts/translation.js"strategy="beforeInteractive"/><Scriptsrc="//translate.google.com/translate_a/element.js?cb=TranslateInit"strategy="afterInteractive"/></Head><body><Main/><NextScript/></body></Html>);}
There are three scripts there
- The first one contains language configuration
- The second one contains a callback with the translation logic runner
- Standard Google's script gets the callback described before and runs it.
Pay attention to the following facts.
- We use
Script
tag fromnext/script
because of NextJS ;) - We use
strategy="beforeInteractive"
for a couple of first scripts - We use
strategy="afterInteractive"
for the last one
It's important. More information regarding the above you can findhere. Let me provide you some related theory.
beforeInteractive: Load the script before any Next.js code and before any page hydration occurs.
afterInteractive: (default) Load the script early but after some hydration on the page occurs.
What happens if the user presses a language onLanguageSwitcher
?
It's very easy. When the user presses a new language link, say, for Spanish language,switchLanguage
function described above sets/auto/es
value forgoogtrans
cookie. This is a message to the translation engine that Spain-translated content is expected. After thatswitchLanguage
reloads the page, and we will see the Spanish content. Google Translate did this job!
That's it regarding the main flow. But let me focus on some additional important stuff.
Conclusion
Let's run the solution
npm run dev
and switch the language, say, Deutsch. However, the issue is that the standard Google Translate bar is still on top.
We definitely ought to fix it. Let's add a couple of the following changes tosrc/styles/globals.css
Much better now!
One of the tastiest features of NextJS isStatic Site Generation (SSG). Let's test SSG on this solution.
We need to addssg
script intopackage.json
"scripts":{"dev":"next dev","build":"next build","ssg":"next build && next export","start":"next start","lint":"next lint"},
Let's build a static version.
npm run ssg> nextjs-gtrans-demo@0.1.0 ssg> next build && next export ✓ Linting and checking validity of types ✓ Creating an optimized production build ✓ Compiled successfully ✓ Collecting page data ✓ Generating static pages (3/3) ✓ Finalizing page optimizationRoute (pages) Size First Load JS┌ ○ / 4.24 kB 82.1 kB├ /_app 0 B 77.9 kB├ ○ /404 181 B 78 kB└ λ /api/hello 0 B 77.9 kB+ First Load JS shared by all 80.1 kB ├ chunks/framework-66d32731bdd20e83.js 45.2 kB ├ chunks/main-12e9c77dbbe57e7c.js 31.5 kB ├ chunks/pages/_app-3cfebadf4e2e7ae1.js 298 B ├ chunks/webpack-5c046346608af636.js 807 B └ css/24fee595fee43abd.css 2.29 kBλ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)○ (Static) automatically rendered as static HTML (uses no initial props)........... Copying "public" directory ✓ Exporting (3/3)Export successful. Files written to /Users/slava/Desktop/projects11/nextjs-gtrans-demo/out
You can find the static version inout
folder.
Let's test it. If don't havehttp-server
installed, please install it.
npm i -g http-server
cd ./outhttp-server
The final solution ishere.
May the Google Translate, NextJS, and Force be with you!
Top comments(7)

- LocationKharkiv, Ukraine
- Educationhttps://www.kpi.kharkov.ua/eng/
- Workhttps://valor-software.com/
- Joined
Thank you so much!

- Email
- LocationDhaka, Bangladesh
- EducationDiploma in computer engineering
- WorkI am studing computer engineer
- Joined
it is not work in product server

Same here, it doesn't work on the main domain, but it works as expected locally and on any Vercel subdomain
For further actions, you may consider blocking this person and/orreporting abuse