When building web applications for a global audience, it is important to consider localization—the process of presenting your web application's content in the local language or dialect of your visitors. This can be achieved using i18n frameworks. However, relying solely on i18n frameworks can be overwhelming to manage, especially in fast-paced environments where features and content are frequently shipped.
Lingo.dev is an AI-powered localization tool that enables us to set up localization once and have it run on autopilot.
In this tutorial, we will build a simple web page in English and translate it to 2 other languages using Lingo.dev and i18next.
Prerequisites
To follow along with this tutorial, you should install NodeJS, and have a solid understanding of at least one front-end framework or library.
Setting Up Localization in a React Web App
In order to demonstrate how Lingo.dev works in this tutorial, we will build a simple landing page that translates from English to French and Spanish. You can find a demo of the finalized app on Netlify.
Since React is the most popular frontend framework, we’ll use it to build the demo. However, the same steps can be replicated across different frameworks.
1. Create a React Application
To create the React application in the demo, we’ll follow these steps:
-
Create a React project with Vite by running this command in your Command Line Interface (CLI):
npm create vite@latest localization-app -- --template react
-
Next, run the following commands one after the other:
cd localization-app npm install
-
Now, open up your project in VScode (or your preferred editor):
code .
-
Next, open your
src/App.jsx
file and replace it with the code below. It is a simple React component with some Tailwind CSS classes.
// src/App.jsx export default function App() { return ( <div className="font-sans text-gray-900 bg-gray-50 w-screen"> {/* Hero Section */} <section className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white text-center py-24 px-6"> <h1 className="text-5xl font-extrabold">Deploy Faster, Pay Less</h1> <p className="mt-4 text-lg max-w-2xl mx-auto"> A scalable, developer-friendly cloud hosting platform with seamless deployments. </p> <button className="mt-6 bg-white text-blue-600 hover:bg-gray-200 px-8 py-3 rounded-lg font-semibold shadow-md transition"> Get Started </button> </section> {/* Features Section */} <section className="py-20 px-6 text-center bg-gray-900 text-white"> <h2 className="text-3xl font-bold mb-8">Why Choose Us?</h2> <div className="grid md:grid-cols-3 gap-10"> <div className="p-8 bg-gray-800 rounded-lg shadow-lg"> <h3 className="text-2xl font-semibold">🚀 Fast Deployments</h3> <p className="mt-3 text-gray-300"> Deploy your apps in seconds with zero downtime. </p> </div> <div className="p-8 bg-gray-800 rounded-lg shadow-lg"> <h3 className="text-2xl font-semibold">💰 Affordable Pricing</h3> <p className="mt-3 text-gray-300"> Pay only for what you use, with no hidden fees. </p> </div> <div className="p-8 bg-gray-800 rounded-lg shadow-lg"> <h3 className="text-2xl font-semibold"> 📈 Scalable Infrastructure </h3> <p className="mt-3 text-gray-300"> Seamless auto-scaling to handle any workload. </p> </div> </div> </section> {/* Pricing Section */} <section className="bg-gray-100 py-20 px-6 text-center"> <h2 className="text-3xl font-bold mb-8">Pricing Plans</h2> <div className="grid md:grid-cols-3 gap-10"> <div className="p-8 bg-white rounded-lg shadow-md border border-gray-200"> <h3 className="text-2xl font-semibold">Basic</h3> <p className="mt-3 text-gray-700 text-lg">$5/month</p> </div> <div className="p-8 bg-white rounded-lg shadow-md border border-gray-200"> <h3 className="text-2xl font-semibold">Pro</h3> <p className="mt-3 text-gray-700 text-lg">$15/month</p> </div> <div className="p-8 bg-white rounded-lg shadow-md border border-gray-200"> <h3 className="text-2xl font-semibold">Enterprise</h3> <p className="mt-3 text-gray-700 text-lg">Custom Pricing</p> </div> </div> </section> </div> ); }
-
Now it is time to install and configure Tailwind CSS for styling. Run the following command in your CLI to install Tailwind CSS:
npm install tailwindcss @tailwindcss/vite
Open up
vite.config.js
in your text editor and replace it with the following:
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [react(), tailwindcss()], });
Next, we’ll include this at the top of our
src/index.css
file:
@import "tailwindcss";
-
Now we can visit our page in the browser. Run the following command and visit
http://localhost:5173/
to view the web page:
npm run dev
Your web page should look like the image above.
Now that we have our React application, it is time to modify it for localization and translations.
2. Install Localization Dependencies
In this section, we will translate our previous web page to French and Spanish. The first step to achieve this is to install the i18next and react-i18next packages. i18next is an internationalization framework that makes localization possible for JavaScript, and react-i18next is specifically for React.
Run the following command in your CLI to install the dependencies:
npm install react-i18next i18next
3. Create Translation Files
After installing our dependencies (i18next and react-i18next), we need to create translation files. Translation files allow us to store key-value pairs for different languages. They basically contain the same information in different international languages. For example, we might have:
en.json
for English translationsfr.json
for French translations
To create translation files we should do the following:
Create a
i18n
folder in our root directory.Open the
i18n
folder and create the following files:
a.en.json
b.fr.json
c.es.json
Follow the steps below to add the right JSON objects to your translation files:
-
Paste the following JSON object in your
en.json
file:
{ "hero": { "title": "Deploy Faster, Pay Less", "subtitle": "A scalable, developer-friendly cloud hosting platform with seamless deployments.", "cta": "Get Started" }, "features": { "title": "Why Choose Us?", "fastDeploy": { "title": "🚀 Fast Deployments", "description": "Deploy your apps in seconds with zero downtime." }, "affordablePricing": { "title": "💰 Affordable Pricing", "description": "Pay only for what you use, with no hidden fees." }, "scalableInfra": { "title": "📈 Scalable Infrastructure", "description": "Seamless auto-scaling to handle any workload." } } }
The JSON object represents key-value pairs for the content in our web page. For example, to write “Deploy Faster, Pay Less“ on our web page, we will access it with
hero.title
. -
Now, we’ll paste the same JSON object for French and Spanish in their respective files.
For French (i18n/fr.json
), paste the following:
{ "hero": { "title": "Déployez plus vite, payez moins", "subtitle": "Une plateforme d'hébergement cloud évolutive et conviviale pour les développeurs, avec des déploiements fluides.", "cta": "Commencer" }, "features": { "title": "Pourquoi nous choisir ?", "fastDeploy": { "title": "🚀 Déploiements rapides", "description": "Déployez vos applications en quelques secondes sans temps d'arrêt." }, "affordablePricing": { "title": "💰 Prix abordables", "description": "Payez uniquement ce que vous utilisez, sans frais cachés." }, "scalableInfra": { "title": "📈 Infrastructure évolutive", "description": "Mise à l'échelle automatique fluide pour gérer toute charge de travail." } } }
For Spanish (
i18n/es.json
), paste the following:
{ "hero": { "title": "Despliega más rápido, paga menos", "subtitle": "Una plataforma de alojamiento en la nube escalable y amigable para desarrolladores con implementaciones sin problemas.", "cta": "Comenzar" }, "features": { "title": "¿Por qué elegirnos?", "fastDeploy": { "title": "🚀 Despliegues rápidos", "description": "Despliega tus aplicaciones en segundos sin tiempo de inactividad." }, "affordablePricing": { "title": "💰 Precios asequibles", "description": "Paga solo por lo que usas, sin cargos ocultos." }, "scalableInfra": { "title": "📈 Infraestructura escalable", "description": "Escalado automático sin problemas para manejar cualquier carga de trabajo." } } }
4. Initialize and Configure i18next For Translations
We need to configure i18next in our React app for translations to happen.
First, we must create a file called i18n.js
for configuration. Second, we need to modify our main.jsx
file to use the I18nextProvider
. The following steps will help us achieve this.
-
In our root directory, let’s create a
i18n.js
file to handle translations and initialise it with React. Paste this code into youri18n.js
file:
import i18n from "i18next"; import { initReactI18next } from "react-i18next"; // import translation files import en from "./i18n/en.json"; import fr from "./i18n/fr.json"; import es from "./i18n/es.json"; i18n.use(initReactI18next).init({ resources: { en: { translation: en }, // this tells it to use our en.json file (imported as en) for English translations. fr: { translation: fr }, es: { translation: es }, }, lng: "en", fallbackLng: "en", interpolation: { escapeValue: false }, // this helps to avoid unnecessary HTML escaping }); export default i18n;
-
Open your
main.jsx
file and replace it with this code:
import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "./index.css"; import { I18nextProvider } from "react-i18next"; import i18next from "i18next"; import App from "./App.jsx"; import "../i18n.js" createRoot(document.getElementById("root")).render( <StrictMode> <I18nextProvider i18n={i18next}> <App /> </I18nextProvider> </StrictMode> );
5. Modify Your React Component For Translations
We need to update our React component to use the translation configurations we set up in previous steps. Since our component is in the App.jsx
file, open up the file and follow the steps below:
-
Import the
useTranslation
hook into your App.jsx file:
import { useTranslation } from "react-i18next";
-
Initialize the
useTranslation
as shown below:
const { t } = useTranslation();
-
Now we can use the JSON object we created in our translation files. For the hero title, we will do this:
<h1 className="text-5xl font-extrabold">{t("hero.title")}</h1>
We will use a similar pattern for other text content in our component. The final result of our code will look like this:
import { useTranslation } from "react-i18next"; export default function App() { const { t } = useTranslation(); return ( <div className="font-sans text-gray-900 bg-gray-50 w-screen"> {/* Hero Section */} <section className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white text-center py-24 px-6"> <h1 className="text-5xl font-extrabold">{t("hero.title")}</h1> <p className="mt-4 text-lg max-w-2xl mx-auto">{t("hero.subtitle")}</p> <button className="mt-6 bg-white text-blue-600 hover:bg-gray-200 px-8 py-3 rounded-lg font-semibold shadow-md transition"> {t("hero.cta")} </button> </section> {/* Features Section */} <section className="py-20 px-6 text-center bg-gray-900 text-white"> <h2 className="text-3xl font-bold mb-8">{t("features.title")}</h2> <div className="grid md:grid-cols-3 gap-10"> <div className="p-8 bg-gray-800 rounded-lg shadow-lg"> <h3 className="text-2xl font-semibold"> {t("features.fastDeploy.title")} </h3> <p className="mt-3 text-gray-300"> {t("features.fastDeploy.description")} </p> </div> <div className="p-8 bg-gray-800 rounded-lg shadow-lg"> <h3 className="text-2xl font-semibold"> {t("features.affordablePricing.title")} </h3> <p className="mt-3 text-gray-300"> {t("features.affordablePricing.description")} </p> </div> <div className="p-8 bg-gray-800 rounded-lg shadow-lg"> <h3 className="text-2xl font-semibold"> {t("features.scalableInfra.title")} </h3> <p className="mt-3 text-gray-300"> {t("features.scalableInfra.description")} </p> </div> </div> </section> </div> ); }
Save your files, restart your server by running npm run dev
, and view your page in the browser. It will still look the same. This is because we set the primary language to English in our i18n.js
file. Let us change this to French.
Open your i18n.js
file and look for this line of code:
lng: "en",
Change it to this:
lng: "fr",
Now, save your file and refresh your page on the web browser. Your page will look like this:
Congrats! You have successfully localized your web app to French. To translate it to Spanish, you can update your i18n.js
file like this:
lng: "es",
6. Switch Languages Dynamically
To improve the user experience of our app, we will create a simple language switcher to allow users to switch languages at will.
In your
src
folder, create a new file and call itLanguageSwitcher.jsx
.-
Open the new file and paste this code in it:
import { useTranslation } from "react-i18next"; const LanguageSwitcher = () => { const { i18n } = useTranslation(); return ( <select onChange={(e) => i18n.changeLanguage(e.target.value)}> <option value="en">English</option> <option value="fr">Français</option> <option value="es">Español</option> </select> ); }; export default LanguageSwitcher;
The piece of code is a dropdown button. It sets the language by using
i18n.changeLanguage
-
Open your
App.jsx
file and add this import statement:
import LanguageSwitcher from "./LanguageSwitcher";
-
Still in your
App.jsx
file, add this piece of code right before the hero section:
{/* Language switcher */} <div className="p-4 text-right"> <LanguageSwitcher /> </div>
-
Now save your file, restart your server, and view the page on the browser. It will look like this:
You can test the language switcher by selecting different languages. If you face any issues, you can go back a few steps to catch your error.
Localizing & Translating a Web Application With Lingo.dev
From the example above, it is clear that we will run into maintenance problems if we decide to scale our app or to support more languages.
We will quickly get overwhelmed if we have to manage multiple translation files for the same project. With Lingo.dev, we only need to manage one translation file. Lingo will handle the translation to different languages while preserving the context of the original language. In this section, we will learn how to localize our React app with Lingo.
1. Create an Account on Lingo.dev
Visit Lingo.dev’s official website to create a hobby account and log in. This account will allow you to translate up to 10,000 words per month, for free.
2. Delete Tranlation Files
Since Lingo will handle automatic file translations, we no longer need to manually manage the fr.json
and es.json
files. Open your i18n
folder and delete them.
3. Authenticate Your React App With Lingo
For Lingo to work in our React app, we need to perform authentication by running the command in a CLI:
npx lingo.dev@latest auth --login
When you run the command above for the first time, it will request to install Lingo, type “y” into the CLI to proceed. After that, you should follow the instructions in the CLI to continue the login process.
If you follow the instructions, the page below should open in your browser. Click the Grant access button to complete the process.
If you are on a Windows machine, the above command might not work for you because npx lingo.dev@latest auth --login
will try to run a bash script, which can cause problems on Windows.
To fix this issue, you should try running the command inside the Git Bash terminal or Windows Subsystem for Linux (WSL). Ensure that you run it from your project directory.
For WSL, open up your WSL instance and navigate to your project directory like this:
cd /mnt/c/Users/yourUsername/path/to/project
Once you are in, you can run the authentication command above.
4. Initialize Lingo.dev in Your React App
Now, we need to initialize Lingo.dev in our project. We can do that by running the command below:
npx lingo.dev@latest init
The command above will ask you a series of questions listed below:
Source locale: Type “en” to select English.
List of target locales: You should type “fr es” for Lingo to translate to French and Spanish.
Type of bucket: Select JSON for this option
Select the path to use: Type space to select our
i18n
folder and then enter to proceed.
Lingo will ask if you want to configure it for CI/CD. Type “n” to select no. It will also give you the option to view the official Lingo.dev documentation.
If you view your project in VScode, you will notice that Lingo.dev has created a new file called i18n.json
in your root directory. This file is important for Lingo.dev to work properly.
5. Run Lingo.dev
Now that we are done with the configurations, we can run Lingo.dev in our project. Run the command below in your terminal:
npx lingo.dev@latest i18n
The command above will create all our translation files. If you open the i18n
folder, you will find the es.json
and fr.json
files back in it. So whenever we need to update our app, we only need to do it in one file and run the above command.
You can view the web page on the browser. The result will be the same, but we achieved it with less effort.
Conclusion
Congratulations! You have successfully created a web page and translated it to both French and Spanish by using Lingo.dev.
Lingo.dev has a lot of potential and features. I recommend taking some time to understand the platform and try building cool things with it.
Resources
You can use the following resource(s) to keep learning:
If you end up building something cool with Lingo, be sure to tag me. Cheers!
Top comments (8)
Fantastic article! Definitely opinionated, but Lingo.dev is fire ;) 🔥
By the way, quick tip:
To extract translation strings, one can just use the following prompt with claude sonnet:
Works like magic!
Ouuuuu! I didn't know that. Thank you for the tip, Max!
That prompt is a lifesaver 🚀 I often use similar one directly in Cursor IDE 🙃
Nice article 🚀🚀🚀
Well, if you are, give me a shout and we can have a look at how the
auth
command works together.Or you can just put your Lingo.dev API key into
LINGODOTDEV_API_KEY
env variable. Windows does have those, right? Genuinely asking 🫥Hi, Matej. Thank you!
I wouldn't mind looking at how the auth command works with you.
Yes, Windows have those. 😅 But then, the problem persists when you run other commands like,
npx lingo.dev@latest init
. So I figured it was better to run the commands from a Unix-like environment.Did you try running the CLI directly on Windows? Do you have node installed? It is written in typescript / node, not bash. But honestly we did not check on Windows.
I am happy to hear you got it running via WSL. Is that the standard for developing on Windows? Or is direct support for Windows preferred?
I initially tried running the CLI directly on Windows and have Node installed. However, each command attempts to run a Bash script located in
node_modules/.bin/lingo.dev
for some reason. Since Windows does not natively support running Bash files, it simply opens the file in a text editor, browser, or whichever default program the user has set. I can paste the file's content if you want.From my experience, most developers use WSL or Git Bash frequently because this issue is not unique to Lingo. Windows support would be nice, but I wouldn’t prioritize it too much. There’s a high chance developers will encounter the same issue with another package and eventually switch to WSL.
I tried to run this on my gaming machine out of curiosity. I learned that while via
npx
it tried to run the bash script, installinglingo.dev
via npm worked (it created a.cmd
comamnd). However not all of the CLI commands worked.If this works via WLS, I think we can check the Windows platform support ✅
Thank you for your help 🙌