In our previous discussions, we explored localization strategies for backend frameworks like Rails and Phoenix. Today, we shift our focus to the front-end and talk about JavaScript translation and localization. The landscape here is packed with options, which makes many developers ask: "Which library should I use?" The real answer, even if it’s not the most satisfying, is: "It depends."
JavaScript localization needs can vary a lot depending on your project, and every library comes with its own strengths. Good software internationalization is key to making the right choice for your app.
To help you out, this article gives a clear overview of several popular JavaScript localization libraries, so you can pick the one that fits your project best. Most of these libraries use translation keys to organize multilingual content and keep things consistent across your app.
So, let’s begin our exploration into the world of JavaScript translation, localized time, and localization. Shall we dive in?
If you are interested in how to translate HTML easily, please check my other tutorial on the topic.
Internationalization (often shortened to i18n) is the process of designing and building your app in a way that makes it easy to adapt to different languages, regions, and cultures. It’s about setting up your code to handle things like different date formats, number formats, currencies, plural rules, and text direction (like right-to-left for Arabic). The main idea is: do the hard preparation once, so you don't need to rip your app apart later when you add new languages.
Localization (l10n) comes after that. It’s the actual work of translating your app’s text, adjusting layouts if needed, formatting numbers and dates correctly for the user's region, and even tweaking images or cultural references so everything feels natural to the user.
A common mistake is thinking localization is just translation. It’s not. Localization is about the whole user experience. For example, a joke that works in English might not make any sense in Japanese. Or a "Buy now" button might need different wording or styling depending on the culture. Good localization makes your app feel like it was built for the user, not just translated for them.
In short:
Internationalization is making your app ready for different languages and cultures.
Localization is fully adapting your app to a specific audience.
Both are important if you want your app to truly connect with users around the world.
File-based vs fileless approach
When it comes to handling translations in your app, there are two main ways to organize things: file-based and fileless.
File-based localization means you store your translations in separate files, usually one per language. These files are often in formats like JSON, YAML, or JavaScript modules. Your app loads the right file depending on the user's language. This approach keeps translation keys and values organized, makes it easy to update them without touching your code, and works great for bigger apps with lots of content.
Fileless localization means your translations are built directly into your code. Instead of loading a separate file, you define your translation strings right inside your components or scripts. This can be faster and simpler for small projects, but it gets messy quickly if your app grows or needs to support many languages.
In short:
File-based: clean, scalable, better for bigger or multi-language projects.
Fileless: quick and simple, better for small apps or quick demos.
Choosing the right approach depends on your project size, how many languages you need, and how you want to manage updates down the road.
JavaScript translation and localization solutions
We will cover these notable solutions:
Globalize: Well-rooted in the JavaScript ecosystem with strong formatting features.
I18next: Flexible, extensible, and integrates easily with different frameworks.
Polyglot.js: A minimalist library from Airbnb, perfect for simpler setups.
Intl-messageformat: A powerful formatter for text, plurals, dates, and currencies, following the ICU MessageFormat standard. Ideal when you need advanced formatting without a full i18n framework.
We’re focusing on vanilla JavaScript apps here. The goal is to give you a broad overview of translation and localization solutions without diving too deep into the guts of each library—keeping it clear and useful. By the end, you’ll have a good sense of which option might work best for you.
Setting up the project for JavaScript localization
Before we dive into localization libraries, let's set up a simple project that we can reuse across all examples. We'll create a Node.js app with a minimal server and a basic template.
First, create a new project folder and initialize it:
When you open that page, the server renders the index.ejs template
The message variable is inserted into the template using <%= message %>
To run the server:
node server.js
Open your browser and you should see: "Welcome, John!".
Adding a basic language switcher
Before we dive into actual translations, it's a good idea to prepare a simple language switcher right from the start. This will allow users to pick a language, and later we'll hook this selection for real localization.
Right now, we'll only:
Accept a lang query parameter in the URL
Keep track of the current language
Render a list of available languages
Highlight the active one
Open up the i18n.js file and paste the following code:
The other languages are shown as links you can click to switch.
Handling RTL (right-to-left) languages
When building apps that support multiple languages, it's important to handle RTL (right-to-left) languages properly. Languages like Hebrew, Arabic, and Persian are read from right to left, which affects how the entire page layout should behave.
For now, we'll focus on the basics:
Detect if the current language is RTL: We’ll extend our availableLocales list to include a isRTL flag for each language.
Set the dir attribute on the <html> tag: If the current language is RTL, we set dir="rtl"; otherwise, we use dir="ltr".
Let’s modify the availableLocales list in the i18n.js file like this:
Now when you open the page with ?lang=he, the entire page layout will switch to right-to-left automatically.
JavaScript translation and localization with Globalize
Globalize, developed by the jQuery team, is a powerful solution for translating and localizing JavaScript apps. It uses the Unicode Common Locale Data Repository (CLDR) under the hood, giving it a strong foundation for handling a wide range of localization needs.
Here’s what Globalize can help you with:
Message formatting: Craft localized messages with ease.
Date/time parsing: Handle dates and times efficiently, including support for relative times.
Pluralization support: Accurately manage singular and plural forms.
Numerical parsing and currency formatting: Work smoothly with numbers and monetary values.
Unit handling: Convert and format units like days, minutes, and miles per hour.
Globalize ensures consistent performance across both browser and Node.js environments. Its modular architecture means you only load what you need, keeping your application lightweight. Unlike other libraries that hardcode locale data, Globalize allows you to dynamically load CLDR data. This means you can update your localization data on-the-fly without waiting for library updates.
To start using Globalize, check out the Getting started guide which details the installation process.
Installing Globalize
So, it's time to install Globalize and the extra data it needs.
Globalize doesn’t come alone — it relies on external locale data from the Unicode CLDR (Common Locale Data Repository) to work properly. Thus, we’ll install a few packages at once.
In your project folder, run:
npm install globalize cldr-data iana-tz-data
Here's what each package does:
globalize: the main library that handles translations, date formatting, number formatting, and more
cldr-data: provides the raw locale data needed for different languages and regions
iana-tz-data: optional, but needed if you want to format dates for different time zones (we'll use it later)
Setting up Globalize in the project
Now that Globalize and its dependencies are installed, let's hook it into our project. First, we’ll load Globalize and the necessary CLDR data.
Globalize lets you load custom translation messages. We’ll prepare the file structure structure now. Inside your project, create a folder called messages, and inside it, create en.json, fr.json, and he.json:
JavaScript localization: Dates and times in Globalize
To localize date formats using Globalize, you first need to load the necessary CLDR data related to dates. Then, you can use Globalize to format dates according to the local standards of the user's region.
For currency formatting, Globalize can also make your app adaptive to different economic regions. You'll need to load some currency data and then use Globalize to format numbers as currency values. Update the switchLocale() function:
While the initial setup of Globalize might take some time, it's a robust solution designed for comprehensive JavaScript application internationalization. Its detailed documentation and flexible features make it an excellent choice for developers aiming to enhance their applications' global reach.
Streamlining JavaScript translation with I18next
I18next is a comprehensive JavaScript localization framework designed to translate and localize applications efficiently. It's compatible with multiple front-end frameworks like React, Angular, and Vue, making it versatile for various development environments.
Key features:
Framework support: Integrates smoothly with major front-end frameworks.
Format flexibility: Handles different data formats, including Polyglot.
Dynamic message formatting and pluralization: Easily manage singular and plural forms.
Resource loading: Load translation data from multiple sources, enhancing flexibility.
Extensive plugin system: Offers numerous plugins and utilities for advanced localization needs.
A simple language switcher and switchLocale() function that picks the current language
Now we'll add I18next on top of that foundation to start translating messages dynamically. First, install I18next via npm:
npm install i18next i18next-resources-to-backend
We're also installing a plugin to easily fetch translation files in lazy manner.
Setting up i18next with lazy loading
Now that we installed I18next, let’s hook it properly into our project. We start by updating the i18n.js file. First, we import I18next and a helper plugin called i18next-resources-to-backend, which makes it easy to lazy load translation files only when they are needed.
import i18next from 'i18next';import resourcesToBackend from 'i18next-resources-to-backend';
After that, we initialize I18next. We tell it to use the resourcesToBackend plugin to dynamically import translation JSON files based on the selected language and namespace. In our case, translations are organized under the locales/ folder.
resourcesToBackend automatically loads translation files on demand.
We handle loading errors by logging them to the console.
We initialize I18next with a fallback language (en) in case the requested language is missing.
We tell I18next that we will use a single namespace called main.
Finally, we update the switchLocale() function. Its job is to switch the language based on the incoming request and return a translation helper function.
We check if the requested locale is supported. If not, we fall back to the default locale.
We tell I18next to switch to the selected language.
We return both the selected locale and a translate() helper function, which simplifies translating keys with optional parameters.
Creating translation files for i18next
I18next uses a concept called "namespaces" to organize translations. Namespaces allow you to split translations into multiple files, which is especially helpful for larger applications where different sections of the app might have their own translations.
In our setup, we are using a single namespace called main. Therefore, translation files should be structured like this:
Sometimes a single word can have different meanings depending on the situation. For example, gender-specific translations — like "friend" vs "boyfriend" vs "girlfriend".
I18next handles this easily by using contexts. You create different versions of the key by adding a context suffix:
{ "friend": "A friend", "friend_male": "A boyfriend", "friend_female": "A girlfriend"}
Here:
friend_female is the translation when context is female.
friend is the neutral/default translation.
friend_male is the translation when context is male.
When calling translate() (or i18next.t()), you just pass a context option:
I18next also makes it easy to localize dates and times. You just define a translation key where the date will be inserted, and pass a JavaScript Date object during translation. In your English translations (/locales/en/main.json), add:
{ "intlDateTime": "On the {{val, datetime}}",}
In your server.js route, you can now translate a date like this:
I18next can also be used to format currencies automatically based on the selected locale and currency type. You define placeholders for currency values inside your translation files and pass numbers when translating.
In your English translations (/locales/en/main.json), add:
{ "intlCurrencyWithOptionsSimplified": "The value is {{val, currency(USD)}}", "intlCurrencyWithOptions": "The value is {{val, currency(currency: USD)}}", "twoIntlCurrencyWithUniqueFormatOptions": "The value is {{localValue, currency}} or {{altValue, currency}}",}
Here:
{{val, currency(USD)}} formats the value as a USD amount.
{{val, currency(currency: USD)}} gives you even more flexible control.
{{localValue, currency}} and {{altValue, currency}} allow formatting two different amounts with potentially different currencies.
Now update your root route to localize and format the currencies:
I18next is not just powerful but also adaptable, fitting well into modern JavaScript projects. While it's packed with features, its initial setup is simple and the learning curve is moderate, making it an excellent choice for applications requiring robust localization capabilities.
JavaScript localization with Polyglot.js
Polyglot.js is a small JavaScript library for simple localization tasks. Created by Airbnb, it handles string interpolation and pluralization without managing your full translation process. It works in both browsers and Node.js, and is backend-agnostic — meaning you load and manage translations yourself.
Polyglot.js is a good fit for smaller projects that need basic multilingual support without the overhead of a full i18n framework.
Installing Polyglot.js
To add Polyglot.js to your project, simply run:
npm install node-polyglot
At this point, Polyglot is ready to use — you just need to load translations and start working with phrases.
Creating translation files for Polyglot.js
Polyglot.js doesn’t manage loading or organizing translations by itself. It simply expects you to provide a set of phrases when you initialize it or when you extend it later. Because of that, we manually organize our translation files inside a locales/ folder, like this:
locales/├── en.json├── fr.json├── he.json
Each JSON file will hold the translations for a single language.
Create /locales/en.json:
{ "welcome": "Welcome, %{name}!"}
Then add /locales/fr.json:
{ "welcome": "Bienvenue, %{name} !"}
And /locales/he.json:
{ "welcome": "ברוך הבא, %{name}!"}
Notice that Polyglot expects interpolation variables to use the %{} syntax by default, so we stick to %{name} for inserting the user's name dynamically.
Lazy-loading translations with Polyglot.js
Since Polyglot doesn’t manage loading translation files for you, we need to handle that ourselves. We will dynamically import the correct JSON file based on the requested locale, and then initialize a Polyglot instance with the loaded phrases.
We update switchLocale() in the i18n.js file to do three things:
Find the right locale (or fallback to English if not found).
Dynamically import the matching translation file from /locales/.
Create and return a configured Polyglot instance along with locale info.
Now the welcome message will be correctly translated based on the selected language.
Handling pluralization with Polyglot.js
Polyglot.js has built-in support for basic pluralization rules. To use plural forms, you define a special translation string that contains multiple forms separated by |||| (four pipe characters). The library will automatically pick the correct form based on the smart_count value you pass during translation.
smart_count decides whether the singular or plural form is used.
If smart_count is 1, Polyglot picks the singular part.
If smart_count is 0 or more than 1, it picks the plural part.
Add this line inside your index.ejs file to display the message info:
<p><%= messagesInfo %></p>
%{smart_count} must be inside your translation string, otherwise pluralization won’t work. You can also pass the count directly without wrapping it in an object:
polyglot.t('messages', 5);
JavaScript localization: Date and time with Polyglot.js
Polyglot.js focuses only on JavaScript translation and managing plurals. It does not provide any formatting helpers for dates, times, or numbers. For date localization, we simply use the native JavaScript Intl API alongside our translations.
Add a new translation key for date formatting inside /locales/en.json:
{ "today": "Today is %{date}"}
French /locales/fr.json:
{ "today": "Nous sommes le %{date}"}
Hebrew /locales/he.json:
{ "today": "היום הוא %{date}"}
Inside your route handler, you use Intl.DateTimeFormat to format the date for the current locale, then pass it into the translation:
Polyglot.js is perfect for apps that need basic translations, interpolation, and pluralization without the weight of a full i18n framework. It’s great when you want a tiny, easy-to-use library andr ready to manage translations manually. It’s ideal for small to medium projects where full-scale i18n solutions would be overkill.
JavaScript localization and message formatting with FormatJS
FormatJS is a modular library for rich text formatting in JavaScript apps. Its core piece, intl-messageformat, lets you handle:
Plurals
Genders
Select cases
Nested messages
It follows ICU MessageFormat standards but doesn’t manage translation loading — you handle that yourself. Great for apps needing powerful, flexible formatting without the weight of a full i18n framework.
Installing intl-messageformat
To start using FormatJS for message formatting, install the intl-messageformat package:
npm install intl-messageformat
That's it — ready to format messages manually based on the user's locale.
Creating translation files for intl-messageformat
Intl-messageformat only formats messages — it doesn’t manage translation loading. Just like with Polyglot.js, we handle storing and loading translations ourselves.
We'll organize the translation files inside a locales/ folder:
locales/├── en.json├── fr.json├── he.json
Each file will store plain message strings following ICU MessageFormat syntax.
Create /locales/en.json:
{ "welcome": "Welcome, {name}!"}
Then /locales/fr.json:
{ "welcome": "Bienvenue, {name} !"}
And /locales/he.json:
{ "welcome": "ברוך הבא, {name}!"}
Lazy-loading translations with intl-messageformat
We'll update switchLocale() so it:
Selects the correct locale (or defaults to English).
Dynamically loads the right JSON file.
Prepares a simple translation function using IntlMessageFormat.
intl-messageformat natively supports ICU MessageFormat syntax, which makes pluralization super simple. You just define plural forms inside your translation strings, and pass a count value when formatting.
Make sure your /locales/en.json includes a pluralized message:
{ "welcome": "Welcome, {name}!", "messages": "{count, plural, one {You have one message} other {You have {count} messages}}"}
Same for /locales/fr.json:
{ "welcome": "Bienvenue, {name} !", "messages": "{count, plural, one {Vous avez un message} other {Vous avez {count} messages}}"}
And for /locales/he.json:
{ "welcome": "ברוך הבא, {name}!", "messages": "{count, plural, one {יש לך הודעה אחת} other {יש לך {count} הודעות}}"}
You don't need to change switchLocale() — because our translate() function already works for any message, including plurals.
In your Fastify route, add pluralized usage like this:
count is passed into the message, and ICU syntax handles the switching between singular and plural forms.
If needed, ICU also supports more complex plural cases like zero, few, many, but basic one/other is enough for most languages like English and French.
No extra logic needed — just make sure your translations are correctly written.
Localizing dates with intl-messageformat
Intl-messageformat also supports formatting dates and times directly inside your messages, following ICU standards. No need to manually format with Intl.DateTimeFormat — you just pass a Date object during translation.
Add a new key to your /locales/en.json:
{ "welcome": "Welcome, {name}!", "messages": "{count, plural, one {You have one message} other {You have {count} messages}}", "today": "Today is {date, date, long}"}
Same for /locales/fr.json:
{ "welcome": "Bienvenue, {name} !", "messages": "{count, plural, one {Vous avez un message} other {Vous avez {count} messages}}", "today": "Nous sommes le {date, date, long}"}
And /locales/he.json:
{ "welcome": "ברוך הבא, {name}!", "messages": "{count, plural, one {יש לך הודעה אחת} other {יש לך {count} הודעות}}", "today": "היום הוא {date, date, long}"}
Now you can pass the Date object directly into translate():
{date, date, long} uses ICU formatting types. You can customize it:
short - 1/1/21
medium - Jan 1, 2021
long - January 1, 2021
full - Friday, January 1, 2021
intl-messageformat takes care of formatting correctly based on the active locale.
Localizing currencies with intl-messageformat
Just like dates, currencies are natively supported in ICU message syntax with intl-messageformat. You pass a number and currency code, and it formats everything based on the locale.
Add currency-related key to /locales/en.json:
{ "welcome": "Welcome, {name}!", "messages": "{count, plural, one {You have one message} other {You have {count} messages}}", "today": "Today is {date, date, long}", "price": "The total is {amount, number, ::currency/USD}"}
Same for /locales/fr.json:
{ "welcome": "Bienvenue, {name} !", "messages": "{count, plural, one {Vous avez un message} other {Vous avez {count} messages}}", "today": "Nous sommes le {date, date, long}", "price": "Le total est de {amount, number, ::currency/EUR}"}
And for /locales/he.json:
{ "welcome": "ברוך הבא, {name}!", "messages": "{count, plural, one {יש לך הודעה אחת} other {יש לך {count} הודעות}}", "today": "היום הוא {date, date, long}", "price": "הסכום הכולל הוא {amount, number, ::currency/ILS}"}
Notice:
We use {amount, number, ::currency/USD} format.
You specify which currency to show directly inside the message.
ICU formatting will handle decimal separators, symbol placement, and number formats properly for each locale.
No extra libraries needed — all formatting is built into Intl API and intl-messageformat parsing.
Intl-messageformat: Summary
Intl-messageformat is a good fit when you need powerful text, plural, date, and currency formatting without using a full internationalization framework. It’s lightweight, flexible, and lets you fully control how and when translations are loaded. Perfect for apps that want rich ICU-style formatting but stay modular and simple.
Translate JavaScript apps with Lokalise!
Supporting multiple languages on a big website may become a serious pain. You must make sure that all the keys are translated for each and every locale. Luckily, there is a solution to this problem: the Lokalise platform that makes working with the localization files much simpler. Let me guide you through the initial setup which is nothing complex really.
Create a new project, give it some name, and set English as a base language
Click “Upload”
Upload translation files for all your languages
Proceed to the project, and edit your translations as needed
You may also contact professional translator to do the job for you
Next simply download your files back
Profit!
Lokalise has many more features including support for dozens of platforms and formats, and even the possibility to upload screenshots in order to read texts from them. So, stick with Lokalise and make your life easier!
Translate and localize JavaScript frameworks
While this article focuses on translating plain JavaScript apps, we also have separate tutorials covering how to handle localization in popular JavaScript frameworks. If you're working with React, Angular, Vue, or others — check out these resources:
React and Next
React i18n: A step-by-step guide to React-intl — learn how to introduce internationalization into React apps using react-intl. Covers key topics like storing translations, adding a language switcher, localizing dates, times, currencies, and handling pluralization.
Angular i18n: Performing translations with a built-in module — learn how to implement internationalization using Angular’s built-in i18n support. Includes working with XLIFF translation files and supporting multiple languages, updated for Angular v11+.
Angular localization with Transloco — explore an alternative approach using Transloco, a powerful Angular library for handling translations. Covers dynamic loading, language switching, and storing user preferences.
Vue and Nuxt
Vue 3 i18n: Building a multi-lingual app — deep dive into setting up multilingual support in Vue 3 using vue-i18n. Learn about auto-detecting user locales, storing language settings, lazy-loading translations, and integrating with Vue Router.
Vue 2 i18n: A complete tutorial for Vue 2 projects — walks through the basics and more advanced topics like localized routing and creating a custom translation plugin.
Svelte i18n: A step-by-step guide — see how to prepare your Svelte application for localization. Covers creating and loading translations, detecting languages, and switching between locales dynamically.
EmberJS i18n: A beginner’s guide — Get started with translating Ember apps. Learn how to organize translation files, switch languages, and handle pluralization.
In this article, we explored a range of tools for translating and localizing JavaScript applications. From the full-featured Globalize and I18next libraries to the lightweight efficiency of Polyglot.js and intl-messageformat, we looked at different solutions, each suited to specific project needs.
Each library comes with its own strengths, trade-offs, and level of complexity. Choosing the right one depends heavily on what your project demands — whether it’s multi-framework support, simplicity, advanced localization features, or something in between.
Hopefully, this overview gave you a solid starting point to pick an internationalization (i18n) tool that fits your goals. It’s always a good idea to research carefully and experiment early, because switching localization frameworks halfway through a project can be messy and expensive.
Thanks for following along on this dive into JavaScript translation and localization. Until next time — happy coding, and may your apps speak fluently to the whole world!
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Building an AI-powered translation flow using Lokalise API and webhooks
Managing translations in a growing product can quickly become repetitive and error-prone, especially when dealing with frequent content updates or multiple languages. Lokalise helps automate this process, and with the right setup you can build a full AI-powered translation pipeline that runs with minimal manual input. In this guide, you’ll learn how to: Upload translation files to Lokalise automaticallyCreate AI-based translation tasksUse webhooks to downloa
Syncing Lokalise translations with GitLab pipelines
In this guide, we’ll walk through building a fully automated translation pipeline using GitLab CI/CD and Lokalise. From upload to download, with tagging, version control, and merge requests. Here’s the high-level flow: Upload your source language files (e.g. English JSON files) to Lokalise from GitLab using a CI pipeline.Tag each uploaded key with your Git branch name. This helps keep translations isolated per feature or pull request
Build a smooth translation pipeline with Lokalise and Vercel
Internationalization can sometimes feel like a massive headache. Juggling multiple JSON files, keeping translations in sync, and redeploying every time you tweak a string… What if you could offload most of that grunt work to a modern toolchain and let your CI/CD do the heavy lifting? In this guide, we’ll wire up a Next.js 15 project hosted on Vercel. It will load translation files on demand f