Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Google Translate customization under NextJS
Valor Labs profile imageVyacheslav Chub
Vyacheslav Chub forValor Labs

Posted on • Edited on

     

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.

  1. Not the fact that the solution above, as it is, matched with NextJS specific.
  2. 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
Enter fullscreen modeExit fullscreen mode
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
Enter fullscreen modeExit fullscreen mode

Also, install one extra dependency.

npm i nookies --save
Enter fullscreen modeExit fullscreen mode

Now we can run the app

npm run dev
Enter fullscreen modeExit fullscreen mode

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>);}
Enter fullscreen modeExit fullscreen mode

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 regardingLorem 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>);}
Enter fullscreen modeExit fullscreen mode

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 containsTranslateInit 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",};
Enter fullscreen modeExit fullscreen mode

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,});}
Enter fullscreen modeExit fullscreen mode

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};
Enter fullscreen modeExit fullscreen mode

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>);}
Enter fullscreen modeExit fullscreen mode

There are three scripts there

  1. The first one contains language configuration
  2. The second one contains a callback with the translation logic runner
  3. Standard Google's script gets the callback described before and runs it.

Pay attention to the following facts.

  • We useScript tag fromnext/script because of NextJS ;)
  • We usestrategy="beforeInteractive" for a couple of first scripts
  • We usestrategy="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
Enter fullscreen modeExit fullscreen mode

and switch the language, say, Deutsch. However, the issue is that the standard Google Translate bar is still on top.

1

We definitely ought to fix it. Let's add a couple of the following changes tosrc/styles/globals.css

2

Much better now!

3

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"},
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode
cd ./outhttp-server
Enter fullscreen modeExit fullscreen mode

4

The final solution ishere.

May the Google Translate, NextJS, and Force be with you!

Top comments(7)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
hmssameer55 profile image
Sameer Ahmed
  • Joined

Hats off man

CollapseExpand
 
buchslava profile image
Vyacheslav Chub
More than 25 years experienced software enthusiast. Loves a bunch of programming languages and technologies. Writes about tech React, Javascript, Rust...
  • Location
    Kharkiv, Ukraine
  • Education
    https://www.kpi.kharkov.ua/eng/
  • Work
    https://valor-software.com/
  • Joined

Thank you so much!

CollapseExpand
 
jeremiahjacob261 profile image
Jerry Codes
  • Joined

Still the best guide i could find.

CollapseExpand
 
imranbappy profile image
Imran Hossen Bappy
I'm Full Stack Developer
  • Email
  • Location
    Dhaka, Bangladesh
  • Education
    Diploma in computer engineering
  • Work
    I am studing computer engineer
  • Joined

it is not work in product server

CollapseExpand
 
zelal profile image
Zelal Hossain
MERN Stack Developer

Same here, it doesn't work on the main domain, but it works as expected locally and on any Vercel subdomain

CollapseExpand
 
ganeshmohane profile image
Ganesh Mohane
Data Analyst
  • Joined

I want help in one thing, when I hover after translation the popup comes like dictionary for that particular word, cant we disable it?

CollapseExpand
 
iamumair05 profile image
Umair Younas
  • Joined

It is working for just simple content translation.....giving nodeNotFoud error when taking user input for post API data

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Modernizing the web with Module Federation and Nx

More fromValor Labs

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp