Frontend localization is different than .NET backend localization in that there is not an established “correct way” to accomplish translating text in JS and related frameworks. This is a basic example using JavaScript, but more customization can be done with methods and helpers for accessing text, especially if you are using a JS framework like React or Material UI.
Prerequisites
- Visual Studio Code installed on your machine
- Node.js version 20.17.0 installed on your machine (other versions of Node will likely work as well)
- General understanding of navigation in Visual Studio Code IDE
Steps
- Open Visual Studio Code and create an empty folder “LocalizationExampleJS”:
- Add the file “LocalizationExample.js” at the top level of your new folder:
- Add a new subfolder "Resources" with empty files “en-US.json”, “es-MX.json”, and “fr-FR.json”:
- Add the following code to each JSON translation file. It’s important for the text keys in all 3 files to be identical – in this case, the sample text we are using has the key “HelloWorld”.
en-US.json
{
"HelloWorld": "Hello World!"
}
es-MX.json
{
"HelloWorld": "¡Hola Mundo!"
}
fr-FR.json
{
"HelloWorld": "Bonjour le monde!"
}
- Add the following code to “LocalizationExample.js”. This will enable you to supply a command line argument containing your desired locale string during testing. (Note that in an actual web frontend solution, the frontend code would need to access the current user’s culture from the server, NOT via command line argument.)
const args = process.argv.slice(2);
const currentCulture = args[0];
console.log("Your supplied culture is: " + currentCulture);
- Below the code to extract current culture from command line arguments, add the following code to access the “HelloWorld” string for the supplied culture. If a culture is supplied that is not present in the application, then an error message will log accordingly, and the application will default to the “en-US” locale.
var pathToJson = "./Resources/" + args[0] + ".json";
try {
const jsonObj = require(pathToJson);
console.log(jsonObj.HelloWorld);
}
catch (err) {
console.log("Culture \"" + currentCulture + "\" does not exist in this application - using default locale en-US.")
pathToJson = "./Resources/en-US.json";
const jsonObj = require(pathToJson);
console.log(jsonObj.HelloWorld);
}
- Your code is ready for testing! It should look something like this:
const args = process.argv.slice(2);
const currentCulture = args[0];
console.log("Your supplied culture is: " + currentCulture);
var pathToJson = "./Resources/" + args[0] + ".json";
try {
const jsonObj = require(pathToJson);
console.log(jsonObj.HelloWorld);
}
catch (err) {
console.log("Culture \"" + currentCulture + "\" does not exist in this application - using default locale en-US.")
pathToJson = "./Resources/en-US.json";
const jsonObj = require(pathToJson);
console.log(jsonObj.HelloWorld);
}
- Open a terminal and test out the LocalizationExample solution using the command node
.\LocalizationExample.js
xx-XX, where “xx-XX” is a locale string representing the combination of a language and a region. Try out “es-MX”, “fr-FR”, “en-US”, and “pt-BR” (Brazilian Portuguese) as command line arguments.
Note that the HelloWorld resource for the cultures “en-US”, “es-MX”, and “fr-FR” all appear translated as expected. However, the “pt-BR” locale yields the HelloWorld string in English, because this culture is not present in the collection of translations, and our code explicitly defaults to the “en-US” U.S. English locale in this case.
Localization Tips and Tricks
When it comes to localization, there is a lot more to consider than simply translating text and accessing it appropriately from code. Read on to learn some tips and tricks for elevating your localization solution!
Text Key Organization
Generally, DMC has found that it is beneficial to organize text keys into separate files based not only on language, but also based on what page of your application the text belongs on. For backend localization, this is likely best done by splitting text by controller. Here is an example of a backend localization file structure that includes two controllers, “Login.cs” and “Home.cs”, and two cultures, a default culture and “es-MX”:
- Resources folder
- Login folder
- Login.resx
- Login.es-MX.resx
- Home folder
- Home.resx
- Home.es-MX.resx
This separation organizes your translated text in a way that is easily modifiable for each individual page of your application.
In the event that identical text appears in multiple places in your application in English, it is still beneficial to duplicate that text key/value entry across pages, rather than maintaining a single key/value item. For example, if the word “Email” exists on both the “Login” and “Home” pages in the example above, this text key/value should exist in both the “Login” resource files and “Home” resource files. This enables you to update the text in one place without modifying the same text elsewhere, if desired. Also, translations of the same English word or phrase could be different across different languages, depending on the context of how the word or phrase is used – in this case, separate text/key values are necessary for translators to appropriately translate your application text.
Variables and Pluralization
Variables and pluralization can be quite tricky to handle in any localization solution. For a backend solution with .resx files, if you have a sentence to translate with a variable in the middle, you need to separate any given string key/value into separate parts that precede and follow the variable.
For example, say you want to include the phrase “Hello, {name} – welcome to my site!” The following key/value structure would need to be implemented in each .resx file to handle this phrase:
Key
|
Value (en-US)
|
Hello_BeforeName
|
“Hello, “
|
Hello_AfterName
|
“ – welcome to my site!”
|
Additionally, numeric variables add an extra layer of complexity to a localization solution, because pluralization must be implemented for these. The way words are pluralized can actually differ significantly across languages. For example, Slavic languages (e.g. Russian, Czech) have a different style of pluralization for 5 or more of an item than they do for zero, one, or 2-4 items. So a solution that translates to Slavic languages must implement separate logic for pluralizing 5+ items than for other items. For example, the sentence “You have {number} items in your cart!” may have a key/value structure like this in English:
Key
|
Value (en-US)
|
ItemsInCart_BeforeValue_0
|
“You have “
|
ItemsInCart_AfterValue_0
|
“ items in your cart!”
|
ItemsInCart_BeforeValue_1
|
“You have “
|
ItemsInCart_AfterValue_1
|
“ item in your cart!”
|
ItemsInCart_BeforeValue_2to4
|
“You have “
|
ItemsInCart_AfterValue_2to4
|
“ items in your cart!”
|
ItemsInCart_BeforeValue_5up
|
“You have “
|
ItemsInCart_AfterValue_5up
|
“ items in your cart!”
|
As you can see, this is a complex case to translate for – there are 8 total key/value pairs to accommodate just one sentence that integrates a numeric variable. To avoid the need for this, one solution is to reduce or eliminate the need for accommodating plurals – for example, you could rephrase “You have {number} items in your cart!” to instead become “Number of Items in Cart: {number}”. This would only require one key/value pair to be translated instead of eight.
Fallback Cultures
When a user’s specific locale does not have a translation present in your application, it can be beneficial to implement fallback cultures so this user is given a more suitable approximation of their desired language. For example, say a Spanish speaker lives in Spain and therefore has their browser locale set to “es-ES”. However, your application only has resources for Mexican Spanish “es-MX”. Ideally this person in Spain would see the application in Mexican Spanish rather than the application default of English, because Mexican Spanish more closely matches their desired language than English does.
Using .NET backend localization, fallback cultures can be implemented by simply renaming any files ending in “es-MX.resx” to instead end in “es.resx”. Per .NET’s culture fallback scenarios, upon receiving a request from the Spain Spanish user, .NET will still search for “es-ES” first. But upon not finding this specific culture, .NET will “fall back” to just the “es” portion of the locale string and will access the text as it exists in this new general Spanish file. Users using Mexican Spanish “es-MX” will see the same behavior.
Common Pitfalls
Localization can be complicated, and there are some common pitfalls you may encounter if you are implementing your own localization solution. Here are some examples:
- Logging / error messaging: if you are displaying raw exception messages to your users, then these are not able to be localized because they are generally hardcoded in English by the entities providing the error messages. In this case, your application should be updated to display custom error messages to your users instead of raw messages.
- Identifying EVERY source of user-facing text: There are many possible sources of text in your application that can go unnoticed, which can then become difficult to handle late in the localization process. Some examples are images, email templates, and messages from external libraries/packages/APIs. If you are doing frontend only localization, then it becomes necessary to make 100% certain that there is NO user facing text coming from the backend – if you discover late in the process that some user facing text comes from the server, then some “hacky” solutions may be necessary to preserve the frontend-only localization scheme.
- Not enforcing a single source of truth for text: If there is not a very solid system in place for establishing and updating the “source or truth” for translated text, then the localization process can quickly become messy. Things are always changing during development, and if there is not an established system to deliver text updates to translators and other stakeholders, then any one party can modify text without other stakeholders’ knowledge and introduce confusion. Enforce a single source of truth for text that all stakeholders can agree on. (Resource files are likely NOT the appropriate single source of truth, since non-developer stakeholders should also have control over text in the application.)
DMC is a full-service application development company focused on custom web, mobile, cloud, and desktop applications. Contact us today to discuss your application development needs.