You know, sometimes our first feelings let us down because the world around us is not so simple. I was going to stop with Google Translate customization under NextJS. This article reveals an approach to translate any content freely without pain via Google Translate on a NextJS-based project. But a couple of occasions became a game changer.
First, I found one essential improvement regarding language configuration. Second, my colleague Bruno Silva provided me with valuable code improvements. I decided to make a new series by analyzing the factors above. I recommend reading the previous series if you want to understand my following thoughts.
Let's get started.
The central part of the previous solution is public/assets/scripts/lang-config.js contains custom languages settings
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", };
The solution above is legal, but it doesn't look like a NextJS-pure. I found a more elegant way to define global data through NextJS config. Let's add env section to next.config.js and remove public/assets/scripts/lang-config.js
/** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, env: { GOOGLE_TRANSLATION_CONFIG: JSON.stringify({ languages: [ { title: "English", name: "en" }, { title: "Deutsch", name: "de" }, { title: "Español", name: "es" }, { title: "Français", name: "fr" }, ], defaultLanguage: "en", }), }, }; module.exports = nextConfig;
I also changed public/assets/scripts/translation.js the following way because pageLanguage parameter is not mandatory.
function TranslateInit() { new google.translate.TranslateElement(); }
According to Bruno's proposal, I encapsulated most of the logic into a custom hook, src/hooks/useLanguageSwitcher.ts.
import { useEffect, useState } from "react"; import { parseCookies, setCookie } from "nookies"; import { NextPageContext } from "next"; export const COOKIE_NAME = "googtrans"; export interface LanguageDescriptor { name: string; title: string; } export interface LanguageConfig { languages: LanguageDescriptor[]; defaultLanguage: string; } export type UseLanguageSwitcherResult = { currentLanguage: string; switchLanguage: (lang: string) => () => void; languageConfig: LanguageConfig | undefined; }; export type UseLanguageSwitcherOptions = { context?: NextPageContext; }; export const getLanguageConfig = (): LanguageConfig | undefined => { let cfg: LanguageConfig | undefined; if (process.env.GOOGLE_TRANSLATION_CONFIG) { try { cfg = JSON.parse(process.env.GOOGLE_TRANSLATION_CONFIG ?? "{}"); } catch (e) {} } return cfg; }; export const useLanguageSwitcher = ({ context, }: UseLanguageSwitcherOptions = {}): UseLanguageSwitcherResult => { const [currentLanguage, setCurrentLanguage] = useState<string>(""); useEffect(() => { const cfg = getLanguageConfig(); const cookies = parseCookies(context); const existingLanguageCookieValue = cookies[COOKIE_NAME]; let languageValue = ""; if (existingLanguageCookieValue) { const sp = existingLanguageCookieValue.split("/"); if (sp.length > 2) { languageValue = sp[2]; } } if (cfg && !languageValue) { languageValue = cfg.defaultLanguage; } setCurrentLanguage(languageValue); }, []); const switchLanguage = (lang: string) => () => { setCookie(context, COOKIE_NAME, "/auto/" + lang); window.location.reload(); }; return { currentLanguage, switchLanguage, languageConfig: getLanguageConfig(), }; }; export default useLanguageSwitcher;
Important note. process.env.GOOGLE_TRANSLATION_CONFIG allows us to get GOOGLE_TRANSLATION_CONFIG variable from the above mentioned NextJS config.
A couple of final stitches.
src/components/lang-switcher.tsx
import { NextPageContext } from "next"; import useLanguageSwitcher, { LanguageDescriptor, } from "@/hooks/useLanguageSwitcher"; import React from "react"; export type LanguageSwitcherProps = { context?: NextPageContext; }; export const LanguageSwitcher = ({ context }: LanguageSwitcherProps = {}) => { const { currentLanguage, switchLanguage, languageConfig } = useLanguageSwitcher({ context }); if (!languageConfig) { return null; } return ( <div className="text-center notranslate"> {languageConfig.languages.map((ld: LanguageDescriptor, i: number) => ( <React.Fragment key={`l_s_${ld}`}> {currentLanguage === ld.name || (currentLanguage === "auto" && languageConfig.defaultLanguage === ld.name) ? ( <span className="mx-3 text-orange-300">{ld.title}</span> ) : ( <a onClick={switchLanguage(ld.name)} className="mx-3 text-blue-300 cursor-pointer hover:underline" > {ld.title} </a> )} </React.Fragment> ))} </div> ); }; export default LanguageSwitcher;
useLanguageSwitcher looks elegant 🙂
src/pages/_document.tsx
import { Html, Head, Main, NextScript } from "next/document"; import Script from "next/script"; export default function Document() { return ( <Html> <Head> <Script src="/assets/scripts/translation.js" strategy="beforeInteractive" /> {process.env.GOOGLE_TRANSLATION_CONFIG && ( <Script src="//translate.google.com/translate_a/element.js?cb=TranslateInit" strategy="afterInteractive" /> )} </Head> <body> <Main /> <NextScript /> </body> </Html> ); }
We don't even physically include the translation engine if the config is missing.
You can find the final solution here.