The phrase “localizing content” reminds me of the British comedy “Fawlty Towers,” where we see Manuel, a well-meaning but disorganized and confused waiter who is frequently verbally abused by his boss, to which his response is often “¿Qué?” (“What?”).
Joking aside, localizing content in this age of global access via the Internet is essential – not everyone speaks the same language, and we might end up with customers saying “¿Qué?” if they can’t understand the content that hasn’t been written in their language!
Fortunately, we can address this using the internationalization feature from within Eleventy; we can write language files based around core content, and these will be automatically transpiled by Eleventy during publication.
We have a lot to cover, so we will split this over two chapters – we’ ’ll explore updating content in Chapter 9, but let’s begin with refactoring the layout, ready for those upcoming changes. The first task is to explore some of the background to internationalizing support and understand why it can trip us up if we are not careful planning the process.
Setting the Background
Internationalizing or localizing a site is more than just adding content in a local language – we need not just support different languages but also regional variations in those languages. Our UI must also support other languages and display labels, dates, and more in the correct format for a chosen language. If truth be told, we could almost write a book in its own right on the subject!
Localizing a site – Often referred to as i10n, this is providing copy text in the suitable languages for our site.
Internationalization (i18n) – Adding items such as labels, dates, and prices in the correct format for a particular country.
With this in mind, and as there is so much we could cover, we’ll focus on the latter – providing international support.
Localizing and internationalizing are effectively two sides of the same coin, but as it is such a big topic, it’s essential to be clear which one we mean!
Adding internationalization support needs planning and a strategy – don’t try to do it all at once, but focus on key areas and build out once the basics are in place.
We need to determine which languages to support – we clearly can’t provide every language available, so we must focus on those that bring in the most revenue.
We need to consider how to host the site and provide access to the content – adding internationalization support changes the structure of the site, so this will have a bearing on matters such as SEO.
There is one crucial point to keep in mind: there is no single right or wrong answer when it comes to internationalization (or localization, for that matter). Sites frequently use a mixture of options – which we will touch on more later – the choice of which is dictated by the site owner’s requirements.
Okay – enough theory for the moment: let’s turn our attention to the various options available to help with adding internationalization support to our Eleventy site.
Exploring Options
As mentioned just now, there is no one right way to add internationalization support; developers frequently mix and match different options, depending on the requirements.
eleventy-plugin-i18n – Available from https://github.com/adamduncan/eleventy-plugin-i18n.
eleventy-plugin-i18n-gettext – Available from https://github.com/sgissinger/eleventy-plugin-i18n-gettext (note though that the plugin last had an update two years ago, so it may be out of date now).
A set of helpers available as a plugin for adding internationalization support – This can be downloaded from https://www.npmjs.com/package/eleventy-plugin-i18n-helpers.
We can even go as far as to use an Eleventy starter project to provide internationalization support – this is ideal for projects that are not yet established, with any existing code in production use. However, I know this isn’t always feasible for all sites, so adding support manually over time will often be the best (or only) option!
For example, take a look at https://creasoft.dev/en/posts/2020/2020-07-21-fundamenty-building-eleeventy-starter-project-1/ – this includes internationalization support for three languages but also includes a lot more.
Okay – let’s crack on: we’re almost at the point where we can start to make changes, but before we do so, we have a little housekeeping to do, I’m afraid! I know – it’s not everyone’s favorite task, but hey: it’s essential for this chapter. So without further ado, let’s dive in and take a look at what we need to do in more detail.
Some Housekeeping
Although we could make changes to our existing site, I will work on a copy of the site. Localizing content will require quite a few changes, so taking this approach means we can test things while not breaking our existing site.
We will create a folder called myblog-localized for this copy site; please feel free to use a different name if desired.
Although we will make quite a few changes to our new site, it’s essential to understand that localizing should be a continuous process; it will only stop when the site is shut down! With this in mind, we will focus on the principles behind localizing content for our site; the result may not be perfect, but it will give us a good grounding in what to expect when working on our projects.
For this chapter, I’m not going to localize the content of each post’s text – it is just placeholder text, so it’s not critical to the process for now. If we were to go into production, we would have already prepared content localized in the correct language.
If needed, we could use a package such as https://www.npmjs.com/package/lorem-ipsum-french to generate French style Lorem Ipsum text.
You will notice that for the timeToRead function, we display the words “A X minute read”. To keep things simple, and (in part) due to an issue with the format of the text, we will tweak the content, so we don’t need to translate all of the original text.
To localize content, I will use English and French as our test languages – feel free to use something else, but you need to have a minimum of two languages present to get the right effect.
Okay – now that we have laid down some assumptions, there are a couple of tasks to complete before localizing our site. Let’s take a look at what we need to do.
- 1.
We first need to take a copy of the site and set it up so it runs – for this, crack open your file explorer, copy the myblog folder in its entirety, and paste it in as myblog-localized.
- 2.
Before setting up dependencies on our copy site, we need to amend the package.json and remove a plugin. Go ahead and open package.json, then remove this line:
- 3.
At the same time, move eleventy-plugin-youtube-video out of the node_modules folder and store it somewhere safe for now.
- 4.
We also need to remove these two lines from .eleventy.js to prevent it breaking the build:
- 5.
Switch to post2.md in your editor, and comment out or remove this line too:
- 6.
Next, open a Node.js command prompt, then change the working folder to our new project area.
- 7.
At the prompt, enter npm install (or npm i if you prefer the short form – either is fine), and press Enter.
- 8.
Node will now install the dependencies – once completed, we now have a site ready for use.
Great – we’ve done the boring part: now we can crack on with localizing the site! This may have been a mundane task, but an important one: localizing content can take time and require a lot of changes, so it’s best to have one we can work on while running our existing site in parallel.
I want to point out one thing, though – why did we remove the YouTube plugin? It’s a good question, and there is a good reason for doing so: npm looks for all published packages when installing and will complain if it can’t find any in its registry.
Strictly speaking, I could have included it as I have published my version; it would require changing the entry slightly to get it to work. However, excluding it for now is no bad thing – it’s one thing we would want to localize anyway, which we could do at a later date.
Okay – let’s move on: we have a site set up ready to use. Before we make any changes to our new site, we need to have a good strategy in place; let’s take a moment to explore how we will localize our content in more detail.
Creating a Strategy
We started this chapter by exploring some of the reasons why, and the benefits gained from, localizing is an essential part of any website. However, there is a crucial part we need to consider in all of this – it’s the strategy: How are we going to localize our content? Localizing our website will require quite a few changes, so it’s essential we get it right!
Which markets or languages do you want to localize for? Are there any which are of a higher priority? We can’t cover all needs immediately, so we should prioritize those with the most impact, from sales or analytics.
How far do you want to localize content – we should work out whether this might be just labels and text, or if (as is likely to be the case) we need to update currency formats, dates, and so on.
Are there any label/code/technical changes we could/need to make that would help simplify the process? For example, the timeToRead function we used has a label that doesn’t localize well; changing the text we display will make it easier to render the correct result.
Titles, descriptions, and tags
Header/navigation and footer labels
Any labels used in the main body, which do not feature in post files, such as date of the post, and next and previous links
Any permalinks used in the site
Auxiliary pages and posts – enough to ensure they are displayed for the correct locale, but not the content (see earlier)
Provide different domains – these might be TLDs or subdomains for different regions and languages, such as https://www.example.com and https://www.example.de, or https://en.example.com and https://de.example.com.
Have localized subdirectories for content, such as https://www.example.com/en or https://www.example.com/de.
Serve localized content based on URL parameters, such as https://www.example.com?loc=en or https://www.example.com?loc=de.
Each option has its own merits and constraints – for this project, we will use the middle one, which tends to be the most reliable in determining where customers are located (IP addresses don’t always match the physical location, for one!). We will revisit the reasons for taking this approach, but for now, let’s start with setting the locales for our project.
Setting the Locale
The first step in updating our site to support different languages is to add support for locales. There are three ways we might do this: recognizing the locale from an IP address, using the value set in the accept-language header in the browser, or using an identifier in the URL. Each option has its own merits, but (for reasons we will cover later) the defined value in the URL is usually the most reliable method.
This is the method we will use in our project – I’ve chosen to use English and French as my example languages, but feel free to adapt to other languages as the principles will be the same. Let’s dive in and look at the steps required to add locale support in more detail, beginning with the now-familiar step of stopping our Eleventy server.
The changes we make in this exercise will get the site ready to support both English and French localization. We will then add English translations in this chapter and finish with the French equivalents in Chapter 9.
- 1.
First, go ahead and make sure the Eleventy development server is stopped before continuing with the next step.
- 2.
Go ahead and create two folders at the root of our new project folder – one called en and the other fr.
- 3.
Next, create a new file at the root of the en folder, then add this code:
- 4.
Save this file as en.json, and close it. Now move the posts folder from the root of our project folder into the en folder.
Don’t be tempted to copy this folder: it will produce a duplicate permalink error.
- 5.
Go ahead and create a new pages folder inside the en folder, then move the about.njk, archive.njk, and contact.njk pages into this folder.
- 6.
Next, create a new file called books.njk, and add this code:
You may remember back in Chapter 3, we used the code to source data from DatoCMS, but on the index.njk page; it’s better served from its own page.
- 7.
Save and close books.njk. Next, move the index.njk from the root of our project folder into the root of the en folder.
- 8.
We need to amend the permalink in post2.md, as this has a different structure. Go ahead and open this file in your editor, then change the permalink value as shown:
- 9.
We also need to update the permalink values in the front matter for the Archives pages – go ahead and open archive.njk in the en folder, then make sure the permalink value shows this:
- 10.
We should also do this for the French version too - copy the archive.njk file into the fr folder, then make sure it shows the same permalink value as in step 9.
- 11.
Save and close any file open. Switch to a Node.js command prompt, then set the working folder to our project folder.
- 12.
At the prompt, enter npm run watch:eleventy and press Enter.
- 13.
Once the server is running, browse to http://localhost:8080/en and then try clicking on any link from the site’s homepage. You will notice that the page URL now shows /en in the link, which means our content is now localized.
Note The header and footer are not displayed: don’t worry! We will fix this later in this chapter.
- 14.
If all is well, we should see our site content displayed similar to Figure 8-1.
Yikes – our site has lost its styling, so it looks a little raw! Don’t worry, though: as I alluded to earlier, internationalization will require making quite a few changes, and things will get worse before they get better. This last exercise is probably the most critical change we need to make in terms of structure, so let’s take a moment to review what we achieved in changing code.
Understanding the Changes Made
At the start of this chapter, I mentioned that adding internationalization support will require a fair few changes – many of these will break an existing site, no matter how careful we are! This is why we’re working on a copy of the site – this allows us to test changes (naturally) and get a better feel for how our site will appear once we have added support for other languages.
As the first part of that process, we focused on setting up the locale – we could have done this in any one of three different ways but chose to use one with solid indicators in the URL, such as those I outlined earlier in this chapter, in the Strategy section.
The main reason for using a solid indicator is reliability; headers can be set to indicate languages, but this doesn’t always happen. We could use an IP address, but this won’t cover instances where the IP address doesn’t match the user’s physical location. For example, if they were part of a wide area network that didn’t have a local gateway to the outside world, the IP address may be registered for a different location and not the user’s country.
This aside, we worked our way through some relatively straightforward changes – we began with the usual stopping of the Eleventy server before creating two folders (en and fr) in preparation for moving content into the relative folder. We then set up an en.json file to tell Eleventy which language would be used in the en folder. We moved the original pages and posts into this folder to segregate them from French content.
We then updated the permalink example in post2.md to reflect the new locale – while Eleventy can automatically handle updating locales for regular posts, we need to make sure any permalinks added reflect the new structure. We then rounded out the demo by recompiling the site and previewing the results in the browser; we noted that it lost styling, but that this is something we will correct later in this chapter.
Okay – let’s move on: it’s time to start making changes to the labels displayed on our site. We will have a fair few to update, but we’ll start with reformatting the dates shown on posts, ready for localizing later in this chapter.
I will focus on adding English content first but come back to adding French content later; we will be able to duplicate the English content and adapt it for French, which will make the process easier.
Localizing Dates
With our basic site structure now in place, we can update the format of dates shown on our site. Not every site will use dates in the same way – we might use them to indicate when a post was added or the availability of new stock for an e-commerce outlet.
It doesn’t matter what we use the data for; the principles for adding dates for a localized site remain the same, so let’s dive in and take a look.
For this exercise, I will focus on the mechanics of adding a formatted date; we will come back later and tweak the format to suit our locales better.
- 1.
The first task is to stop the Eleventy server – this will make sure any changes we make are applied correctly when we restart the server.
- 2.
Next, crack open a Node.js command prompt and change the working folder to our new project area.
- 3.
At the prompt, enter npm install moment --save and press Enter – this will install the moment date library.
- 4.
Once installed, open .eleventy.js, then add this line below the last of statement at the top of the file:
- 5.
Next, scroll down to the date addFilters, then leave a line blank after the htmlDateString filter, and add this:
- 6.
Save and close the file. Go ahead and open _includes/posts/post.njk, then comment out this line, as shown:
- 7.
Immediately below that line, add this replacement:
- 8.
Save and close the file. Before we can compile our code, we need to remove one YouTube plugin entry in our pages – go to page2.md, then remove the line starting {%youtube…%}. This is to prevent our build from breaking when we recompile the code.
- 9.
With that out of the way, we can now compile – switch to a Node.js command prompt, then make sure the working folder is our new project area.
- 10.
At the prompt, enter this command:
Excellent – we’re a step closer to localizing our site! Okay, I know it’s not a significant visual change, but it’s essential to get the correct format of date for our chosen locales, as this will prevent confusion for those readers or customers who are used to dates being in a different form. This change highlights some key points, particularly around internationalization, so let’s take a moment to review the code changes in detail before moving on to the next task in this chapter.
Understanding What Happened
Telling the date and time is an essential part of any website – yes, I know that might sound a little obvious, but you’ll be surprised at how many sites don’t show any dates (or even times for that matter!). In our case, it’s essential to show when posts have been added; if they contain technical content, then it’s a good indicator as to whether they may still be valid or have become out of date.
To update the date format, we first stopped the Eleventy server to make sure we could apply any changes once we restarted it. We then installed the Moment library before importing it into the Eleventy configuration file (.eleventy.js) and adding a new date filter via the addFilter command to display the date based on the locale being targeted in our site (in this case, en, or English).
This filter first determines if a locale value is present; if it is, we use either this value or default to en if one is unavailable. We then set an instance of moment to use this locale before returning the date specified in the post in the correct format for our locale.
Next up, we then updated the <time...></time> element rendered in the post.njk layout before removing the call to the YouTube plugin so that we can recompile and preview the results in our browser.
The use of moment – Although Moment works perfectly fine as a library, it has technically been deprecated in favor of other solutions such as luxon (https://moment.github.io/luxon/), which make better use of native functions within JavaScript and the browser. I’ve used Moment here to illustrate that we can localize the date format; we’ll take a look in Chapter 10 to see if we can change it to use a library such as luxon.
The format of how the date is displayed – I’ve focused on the mechanics of how to update and display a localized date in this exercise; this may not necessarily match what you want to see, so you should bear this in mind as part of any internationalization process. I’ve included English for now, but we will adapt this format to suit both locales when we add French content later.
It’s interesting to note that although we made our changes manually, we could have used a plugin to help with formatting dates. A good example is https://www.npmjs.com/package/eleventy-plugin-i18n-helpers.
Right: with that out of the way, let’s turn our attention to the next task. This is where things will start to get interesting, as we’re going to update the layout used to show content in the correct language! It will require quite a few changes, so we’ll fix some of the issues created by moving pages and posts before updating labels on our site.
Localizing Layouts
At first glance, it might seem a complex task to fix some of the issues created by relocating the content – indeed, I wouldn’t be surprised if you were thinking, “yikes… where do I start?”
Footer – No layout changes are needed; we just need to add placeholder labels ready for when localized content is displayed.
We need to update labels across the site – We’ll focus on the main content such as post navigation in the next exercise before dealing with elements such as the header later.
Header – The location of the main image needs updating.
We also need to adapt the collections used in our demo – So far, we’ve used a single collection but need to split this into two so that localized content appears in the right collection.
Let’s make a start – we’ll begin with reinstating the style sheet and image links, so our site starts to look whole again.
- 1.
First, stop the Eleventy server as before – this will make sure we apply the correct changes when we restart the server.
- 2.
Next, crack open a copy of base.njk from the layouts folder, then update the location of the CSS file as indicated:
- 3.
At the same time, we need to update any URLs that relate to images – we have at least one in post2.md, so open that file and amend as shown:
You might have added URLs to other images – make sure these are updated in the same way.
- 4.
Switch to the base.njk file we had opened a moment ago, then find and amend this block, as indicated:
- 5.
Next, crack open the header.njk component, then amend both the title and logo lines, as shown:
- 6.
Save and close this file. Next, crack open post.njk from the layouts folder, and scroll down to the block starting with this line:
- 7.
Inside this if...endif statement, change the references to previousPost as highlighted:
- 8.
Do the same for the nextPost entries too, as shown:
- 9.
Scroll to the bottom of the file, then amend the back to main index link as indicated:
- 10.
Go ahead and open footer.njk from the components folder, then amend this line as indicated:
- 11.
Save and close all files open. Now that we have added localized placeholder labels, it’s time to fill these out with text! For this, open a new file in your editor and add this code:
- 12.
We also need to add in the translations for the French version of our site – for this, delete the last two lines of the previous step, then add this block:
- 13.
Save this as site.js in the _data folder – it will become available globally when we compile the code.
- 14.
Next, open .eleventy.js – as a finishing touch, we need to add a command that creates a collection based purely on posts that fall under the ./en/posts folder structure. Scroll down until you see this line:
- 15.
Leave a line blank, then add these methods:
- 16.
We need to make one last change – crack open the archive.njk file in the /en/pages folder, and amend the set postslists line as follows:
- 17.
Repeat step 16, but this time amend the archive.njk file in the /fr/pages folder and replace _en on the end with _fr.
- 18.
Save and close all files – once done, switch to a Node.js command prompt, then change the working folder to our new project area.
- 19.
At the prompt, enter this command and press Enter:
- 20.
Wait for Eleventy to compile our code and fire up the site. Browse to http://localhost:8080/en – if all is well, we should see something akin to the screenshot shown at the start of this exercise.
Wow – that was certainly a lengthy exercise! It couldn’t be helped, though – there are points where we just have to make those changes. Completing this exercise means we have made significant progress: we still have a few areas to cover, which we will attend to shortly. For now, though, let’s pause for a moment to explore the changes we made in the last demo, so we can see how they all hang together.
Exploring the Changes
Updating our site’s layout isn’t a five-minute job – indeed, this last exercise is probably one of the longest in this book! Over the last couple of pages, we made many changes to our site, so let’s review those changes in more detail.
We began with the now-familiar stopping of the server before updating the URL to the style sheet in base.njk so that our site would look a little more presentable. At the same time, we then edited the Markdown link to the image previously inserted in post2.md so that it will be restored when we restart the server.
Next up, we reverted to base.njk to change the current placeholder labels for new ones; this is in preparation for implementing a data dictionary file (site.js), with the language equivalents for each label. We made changes that included the site title, image logo, previous and next post links, and back to main index link.
By themselves, these labels will not do anything; to fix that, we added a site.js file (the data dictionary, stored in the _data folder), which contains an object with all of the language texts present. So, for example, where we had site[locale].metaTitle, Eleventy will transform this into “Welcome | Creative42” on compilation (using locale, or in this case, en to retrieve the appropriate label from site.js).
We then rounded out the demo by adding an updated collection – this effectively splits the original posts collection in two. The posts_en object will contain just English posts, while a posts_fr collection (which we will create later) will host the French language posts. At this point, we ran through the usual compilation and restart process to preview the results in our browser.
Phew – if you managed to get here, then well done: that was a long one! We’re making good progress but still have plenty to do; let’s move on to the next task to update the header for our project.
Localizing the Header
At face value, the header for our site looks very simple – we have the logo, name of the site, and three menu options: indeed, is it a case of adding in more labels, such as site[locale].about? Unfortunately, it’s not as straightforward as one might think: we already have the titles from within the front matter sections on each page.
The difficulty here is that we need to create collections for each locale (EN and FR) to keep the content separate throughout the site. The nature of our site means that we can’t use a single “wildcard” style component (more on this later), so we have to use a little trickery – let’s dive in and take a look at what is involved in more detail.
- 1.
First, stop the Eleventy server, as we have done in previous exercises. This will ensure we apply the latest changes when restarting the server at the end of the exercise.
- 2.
Next, go ahead and create two folders within the _includescomponents folder – one called en and the other called fr.
- 3.
Go ahead and open header.njk from the en folder, then amend the for... line as shown:
- 4.
Repeat the same exercise for the header.njk file in components/fr, as shown:
- 5.
The header is called in from the base.njk layout, so open this file and replace the existing {% include…%} line after the opening <body> tag with these two lines, like this:
- 6.
We need to add in two more files to tie the localized headers into the site – go ahead and open a new file, then add this code:
- 7.
Save this file as en.11tydata.js within the root of the en folder.
- 8.
Save a copy of the file into the root of the fr folder and change the locale value within it to "fr".
- 9.
Save and close all files – once done, switch to a Node.js command prompt, then change the working folder to our new project area.
- 10.
At the prompt, enter this command, press Enter, and wait for Eleventy to compile our code and fire up the site.
- 11.
If all is well, we should see something akin to the screenshot shown in Figure 8-4, when browsing to the English site at http://localhost:8080/en.
- 12.
To give you a flavor of what our site will look like, once we add the French localizations (which we will do in the next chapter), I’ve gone on ahead and converted the site to show the French equivalents. You can see what it will look like in Figure 8-5.
Excellent – we now have a localized header for each site, so things are shaping up nicely! Although the code changes required to get us to this point were relatively straightforward, the demo does bring up an interesting point about the site structure – let’s take a closer look at the code in more detail.
Breaking Apart the Changes
Localizing the header for our site is probably one of the more critical tasks we needed to perform – not only does it contain a visual identity for the site and the navigation to each page. It’s therefore essential we get it right!
With this in mind, we started by stopping the Eleventy server, as we have done in previous exercises – this will ensure we apply the latest changes when we restart the server. You will find with Eleventy that it’s not always necessary to do this, but trying to remember when it’s required can be a bit of a minefield, so restarting means we play safe.
That aside, the first change we made was to add two new folders – en and fr – to the components folder for our demo. This will house a copy of the header component; we need to use two separate files to display the right content.
With the header file in the right places, we then updated each to use the pages_locale collection, where locale is either en or fr, depending on which header is displayed on the screen. To call in the header, we altered the base.njk layout – inside this, we set a variable localeHeader to point to the relevant header component, depending on which site is displayed. We then changed the first include statement to use this new value; Eleventy will convert this into a relative URL during compilation. To tie the headers into the updated base.njk layout, we add a new en.11tydata.js data dictionary to the en folder (and repeat this for the fr folder too). It tells Eleventy which locale we are in and the layout to use from that locale folder.
As the final step, we recompiled the code and previewed the results in our browser to make sure that we could see either the English or French headers, depending on which site we selected in our browser.
Summary
Localizing a site isn’t a five-minute job – there is more to just providing localized copy text; we have to consider labels, date formats, displaying the right language content for the site, and more. We’ve made good progress, though, on updating the website in this respect: let’s take a moment to review what we have learned in this chapter.
We kicked off by setting the background to this part of the project – this explored some of the points we have to think about, defining the difference between internationalization and localization and creating a strategy for adding different languages. At the same time, we took a look at some of the plugin options to help with adding international support – we will focus on setting this up manually for this project but can explore using these plugins once the basics are in place.
Next up, we set up locale support so that our site is configured to display content from different languages. I’ve chosen French and English for now, but the actual language choices aren’t crucial here: it’s more about the changes we have to make to support different languages, not the language itself.
We then moved on to localizing dates – we added support using the Moment library, but note that while it illustrates how we can format dates, we should equally consider using a different library as Moment has been deprecated.
We then worked on updating the layout; we started with fixing the styling and missing images before setting up and adding placeholder labels to our site. This we complimented with a new data dictionary to act as the source for our labels. At the same time, we also worked on localizing the header – the header configuration is different from the rest of the site, so we had to use another method to display headers in the correct language.
Localizing a site requires making substantial changes. We’re only halfway through this part of the project – we still need to work on updating tags, making sure content is filtered for the correct locale (so we don’t display both languages simultaneously), and more! There’s plenty to keep us busy, so let’s crack on with the next chapter, where we will add the second batch of changes to our internationalized site.