Website localization with a JavaScript library
In this blog post, I present a reusable JavaScript library I developed to simplify website localization. Building on my previous work with HTML5 application localization, this library streamlines the management of translations and enables dynamic language switching. I will explain the library’s architecture, demonstrating how it fetches and merges translation data from JSON files, updates page content, and handles metadata. My approach uses i18next and Lodash to enhance performance and flexibility. I also provide insights into helper scripts I created to further automate the localization process, including a Bash script for extracting translatable content from HTML and a Python script leveraging LLMs for automated translation.
My library revolves around the efficient loading and application of translations. The process begins by retrieving the path to the locale files from a <meta> tag in the HTML:
<meta name="locale-path" content="/path/to/locales/">
This configuration makes the script adaptable to different project structures. The core function, loadLanguage(lang), is responsible for fetching and processing translation data:
const loadLanguage = async function(lang) {
if (!i18next.hasResourceBundle(lang)) {
try {
const fetchPromises = [];
if (optionalBaseEnabled) {
fetchPromises.push(
fetchJson(`/data/json/loc/base/${lang}.min.json?id=fdc3af`).catch(() => {
console.log(`Resource at /data/json/loc/base/{lang}.min.json?id=fdc3af for language {lang} was not found`);
return null;
})
);
}
fetchPromises.push(
fetchJson(`{localesPath}{lang}.min.json?id=fe5cf3`).catch(() => {
console.log(`Resource at {localesPath}{lang}.min.json?id=fe5cf3 for language ${lang} was not found`);
return null;
})
);
const results = await Promise.all(fetchPromises);
const resources = optionalBaseEnabled ? _.merge({}, results[0] || {}, results[1]) : results[0];
if (!resources) {
console.log(`Skipping loading language ${lang} due to missing resource.`);
return;
}
Object.keys(resources).forEach(namespace => {
i18next.addResourceBundle(lang, namespace, resources[namespace], true, true);
});
} catch (error) {
console.error(`An error occurred while loading resources for lang: ${lang}`, error);
return;
}
}
await i18next.changeLanguage(lang);
updateContent();
};
This function first checks if the language resources are already loaded. If not, it uses Promise.all to fetch both base and path-specific translation files concurrently. I then use Lodash’s _.merge to combine these data sets. This combined data is then added to i18next using addResourceBundle. Finally, the updateContent() function applies the translations to the page.
The updateContent() function is responsible for applying the loaded translations to the page. This includes updating metadata:
document.documentElement.lang = i18next.t('htmlLang');
document.querySelector('meta[http-equiv="content-language"]').content = i18next.t('contentLanguage');
document.title = i18next.t('title');
document.querySelector('meta[name="description"]').content = i18next.t('description');
And updating text and attributes of elements:
const text = i18next.getResourceBundle(i18next.language, 'text');
Object.keys(text).forEach(key => {
const element = document.getElementById(key);
if (element) {
element.innerHTML = i18next.t(`text:${key}`);
}
});
// Similar logic for updating link attributes
Automating the Workflow
To streamline the localization workflow, I developed two helper scripts. A Bash script extracts all data-local-* attributes from HTML, providing a list of translatable content. For example, the HTML snippet:
<p id="my_paragraph" data-local-text="welcomeMessage"></p>
Would result in the extraction of welcomeMessage, which can then be used as a key in the JSON translation files.
I also developed a Python script that leverages LLMs to automatically translate the content of the JSON files. This significantly reduces the manual translation effort. The typical workflow involves using the bash script to generate the keys, creating a base en.json file and then using my Python script to generate the other language files starting from the English one.
Example of JSON structure
An example of a JSON file for the english language would be:
{
"text": {
"welcomeMessage": "Welcome to my website"
},
"links": {
"aboutLink": {
"href": "/about",
"alt": "About us page"
}
}
}
For more insights into this topic, you can find the details here.