To state that everyone maintains at least one pet peeve is not an exaggeration. Years ago when I used to watch "That 70's Show" I felt like the Red Foreman character was a bit over the top with his opinions, pet peeves, and things that seemed to irritate him without warning.
Now, several decades later, the older version of me can relate to his pearls of wisdom. Well, one or two pearls, maybe.
One of my biggest pet peeves is when I see anything hard-coded into the source code of an application. While program code style scanners and linters do a great job at keeping developers honest by avoiding things like magic numbers and presentation layer items—such as form labels and text —often find their way into the source code repositories.
I noticed this first-hand back in 2015 when I took a new job after working on several multilingual applications. Imagine my struggle when I could not locate any resource files in the code and eventually found all of the text hard-coded (in English) directly in the HTML template of the Angular application. When I asked a QA Analyst why that was the case, I was told "we only support the English language." Interestingly enough, just before I left that job, an emerging priority was hitting the feature teams: support French-Canadian users. It turns out that English was no longer the only language their application needed to support.
Here I will work through an example to illustrate just how easy this can be accomplished with a Lightning Web Component (LWC).
The Importance of Internationalization (i18n)
Internationalization (i18n) externalizes the natural language aspects of your application, which includes items such as:
- Informational text
- Help
- Labels
- Options and values
Consider the following HTML:
<html>
<body>
<h1>Welcome</h1>
<p>This is welcome text</p>
</body>
</html>
Using i18n, it would appear as shown below:
<html>
<body>
<h1>{{ welcomeHeader }}</h1>
<p>{{ welcomeMessage }}</p>
</body>
</html>
As a result of this change, the text being displayed can be easily replaced for most languages. (Right-to-left languages are an exception and could be a topic for another publication.)
Another benefit to using i18n is that the language-based content for the application is extracted away from the source code. This means that product owners have the option to make language changes often without requiring a deployment of the application.
Including i18n in LWC
Let's assume the following use case for our LWC:
- welcome header
- welcome text
- field label for a text field (Name)
- numeric field for currency (Amount Due)
- date field (Due Date)
- drop-down list options (Favorite Season)
- support for 3 languages (English, Spanish, and French)
Using Salesforce for Custom Labels and Translations
There are a number of different ways to externalize elements of an application for i18n. Since we will be using LWC, using Custom Labels in Salesforce is a built-in solution.
If you don't already have a Salesforce org to use, simply use the following URL to get started:
https://developer.salesforce.com/signup
Next, we need to make sure all three languages are configured by visiting the Setup | Translations section as shown below:
With English, Spanish, and French enabled, we can now visit the Setup | Custom Labels section to create the table as shown below:
The following table provides a summary of the labels and values we need to configure:
Now that we have everything ready, it is time to use externalized elements within a new component.
Using VS Code for LWC Development
For more detailed instructions on using Visual Studio (VS) Code to create a new LWC, please review the following publication:
Adding Barcode Support to a Salesforce Mobile Application
With VS Code running and the Salesforce Extension Pack installed, getting started with the i18n example app is as simple as using the Cmd+Shift+P (on my MacBook Pro) or Ctrl+Shift+P (Windows machines) and typing the following command:
SFDX: Create Project
To make things quick and easy, I selected the Standard option and called my project LWCi18n:
Next, we need to connect to the Salesforce org used above. Use the following Cmd+Shift+P/Ctrl+Shift+P command to connect VS Code to that org:
SFDX: Authorize an Org
VS Code will prompt for a login URL option. I selected the Project Default option. When a browser window appeared, I logged into my sandbox org. VS Code is now connected to my sandbox, and we are ready to get started with the LWCi18n component.
Retrieving Labels from Salesforce
Existing objects and configurations can easily be downloaded into VS Code by simply clicking the Cloud icon on the left-hand toolbar. In this case, I located the Custom Labels section and downloaded everything:
I also retrieved all the Translations using the same approach.
Creating the i18nExample Component
We can use the following Cmd+Shift+P/Ctrl+Shift+P command to create the i18nExample Lightning Web component:
SFDX: Create Lightning Web Component
The first thing we need to do is update the i18nExample.js-meta.xml
to make this component available for use on a Lightning page:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Next, we need to update i18nExample.js
as shown below:
import { LightningElement, track } from 'lwc';
import welcomeHeader from '@salesforce/label/c.Welcome_Header';
import welcomeText from '@salesforce/label/c.Welcome_Text';
import labelName from '@salesforce/label/c.Label_Name';
import placeholderName from '@salesforce/label/c.Placeholder_Name';
import labelAmountDue from '@salesforce/label/c.Label_Amount_Due';
import placeholderAmountDue from '@salesforce/label/c.Placeholder_Amount_Due';
import labelDueDate from '@salesforce/label/c.Label_Due_Date';
import placeholderDueDate from '@salesforce/label/c.Placeholder_Due_Date';
import labelSeason from '@salesforce/label/c.Label_Season';
import placeholderSeason from '@salesforce/label/c.Placeholder_Season';
import valueSpring from '@salesforce/label/c.Value_Spring';
import valueSummer from '@salesforce/label/c.Value_Summer';
import valueFall from '@salesforce/label/c.Value_Fall';
import valueWinter from '@salesforce/label/c.Value_Winter';
export default class I18nExample extends LightningElement {
label = {
welcomeHeader,
welcomeText,
labelName,
labelAmountDue,
labelDueDate,
labelSeason
};
placeholder = {
placeholderName,
placeholderAmountDue,
placeholderDueDate,
placeholderSeason
};
option = {
valueSpring,
valueSummer,
valueFall,
valueWinter
};
name;
amountDue;
dueDate;
season;
seasons = [
{value: "1", label: valueSpring},
{value: "2", label: valueSummer},
{value: "3", label: valueFall},
{value: "4", label: valueWinter},
];
}
The code above accomplishes the following tasks:
- Import references made to all the Custom Labels created in Salesforce
- Establish
label
,placeholder
, andoption
objects to house the custom labels. - Create variables for the four form fields that will be used by the component:
- name
- amountDue
- dueDate
- season
- Create a seasons array for the drop-down list of choices for the season field
Please note - to remain focused on internationalization, I intentionally do not have an object in Salesforce that is linked to this form. This is merely a high-level example to show the appropriate information, based upon the user’s locale.
Next, we update the i18nExample.html
template as shown below:
<template>
<div class="slds-text-heading_large slds-border_bottom">{label.welcomeHeader}</div>
<div class="slds-text-body_regular slds-m-top_xx-small slds-m-bottom_medium">{label.welcomeText}</div>
<lightning-input
type="text"
label={label.labelName}
value={name}
placeholder={placeholder.placeholderName}></lightning-input>
<lightning-input
type="number"
label={label.labelAmountDue}
value={amountDue}
placeholder={placeholder.placeholderAmountDue}
step="0.01"
formatter="currency"></lightning-input>
<lightning-input
type="date"
label={label.labelDueDate}
value={dueDate}
placeholder={placeholder.placeholderDueDate}></lightning-input>
<lightning-combobox
name="season"
label={label.labelSeason}
value={season}
placeholder={placeholder.placeholderSeason}
options={seasons}></lightning-combobox>
</template>
As you can see, there is not a single hard-coded item in the template. Instead, the Custom Labels are referenced and returned based upon the user’s locale information.
Now, we are ready to deploy the app to Salesforce.
Deploying to Salesforce
Pushing all my code from the local machine to Salesforce is simple. All I need to do is right click on the force-app/main/default in the navigator and select the SFDX: Deploy Source to Org option.
Once completed, we are ready to add the Lightning Web Component to a new app in Salesforce.
I switched over to the browser tab logged into my Salesforce org and opened the Setup perspective. Next, I navigated to the Apps | App Manager page and clicked the New Lightning App button.
I decided to call the new app i18n Example and even found a nice little icon to use. I used the rest of the default settings from the wizard, except the last screen, where I granted all users access to this app.
Then, I navigated to the User Interface | Lightning App Builder screen. Here, I created a new Lightning App Page called i18n Example, which was designed as an App Page with a single region.
On the left side of the screen, I could see my i18nExample
LWC under the Custom section. All I had to do was drag that component over and drop it into the single region for the Lightning-based page.
After saving the component, I used the activation process to expose the Lightning page for clients to utilize.
During the activation phase, I set the App Name to i18n Example and found the best icon on the list. For the Mobile Navigation, I added the i18n Example Lighting app and made sure it was near the top of the list
After hitting the Save button, the i18n Example app was ready for use.
Validating the i18nExample Component
Using Salesforce, I opened the i18n Example application, which presented the labels and placeholders as expected:
Even the favorite season drop-down options appeared exactly as I needed:
Using the mobile app, I opened the i18n Example application and populated the form as shown below:
I updated my Salesforce profile to change my language to Spanish and reloaded the app in Salesforce, which shows the correct labels and values:
Here is the same form with sample data and the drop-down list items showing correctly:
I then changed my language to French and reloaded the app again, which showed the French version of my app:
The currency and date formatting also adjusted here too, along with the drop-down list options:
What is really cool about including i18n support for Lightning Web Components is that Salesforce is already designed to accommodate the necessary externalized labels and values.
Going forward, updates can be made within Salesforce without requiring a code change or deployment.
Conclusion
Starting in 2021, I have been trying to live by the following mission statement, which I feel can apply to any IT professional:
“Focus your time on delivering features/functionality which extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.”
- J. Vester
By introducing the ability to externalize the natural language aspects of an application, you are actually adhering to my personal mission statement. The fact that the Salesforce platform allows Lightning Web Components to easily take this approach demonstrates another example of how Salesforce adheres to my mission statement.
Something that I recently expressed to my oldest son—who is finishing his college degree in Computer Science in a matter of weeks—is that you won't ever regret doing things the right way the first time. If given the opportunity, that extra time you allocate to plan appropriately will be met with a great reward in the long-term supportability of whatever you are producing.
While it is quick and easy to simply start adding text and labels to the presentation layer in your primary language or framework, the technical debt to convert to an externalized model will be painful for everyone forced to participate in the exercise.
Think of it like trying to repair a tire on your vehicle using Fix-A-Flat. While the can of solution will provide enough air to get you back up and running, the mechanic you hire to clean up the mess inside your tire will certainly bill you extra to fix it the “right” way.
If you are interested in the source code for this publication, you can find it on GitLab at the following address:
https://gitlab.com/johnjvester/lwc-i18n
Have a really great day!