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.