In this tech log, I'll be looking at how I built a custom language/region switcher in HubSpot's CMS Hub.
HubSpot offers multi-language capabilities and limited ways of working with multiple regions, but our client needed a site where they could freely choose which regions were used.
Here's a sneak peek of the solution:
You can also visit the full live example site to trial the solution:
The solution isn't perfect. In the end, it is a hack around HubSpot limitations. But it is flexible, and can be reused for any "region" type, not just for countries. Maybe you want to have different pages depending on the different markets your business has, for example, in cities or provinces?
This blog is a technical guide to help you implement this solution. It assumes prior developer knowledge of CMS Hub. Are you interested in this solution, but don't have the skills to make it work? Let us know if you need help via the form at the bottom of the page! HubSpot is one of the platforms we use to help our clients reach their goals.
CMS Hub already supports multiple languages by default, and in a way, regions too. However, these regions only reflect the official languages of a country. For example: in Finland, where both Finnish and Swedish are official languages, you can only select these two languages when making a new website page:
Moreover, HubSpot's default Language Switcher module is bare-bones, only displaying a plain list of languages. CSS hacks are possible, but its final source code is not accessible.
The screenshot is from here.
The approach in this article is to make a fully custom language switcher, with different pages per language/region pair. This approach makes sense when you need your website pages to vary depending on the region and language of the visitor. For example, you may intend to have a different layout and different modules installed for an English-speaking visitor in Sweden, compared to an English-speaking visitor in the UK.
If you need only small variations on a single page this solution might not be the best fit. It's probably better to look into IP-country based smart content, or a custom module that accesses the visitor's IP-country and achieves a similar effect.
The goal is to build a custom language and region switcher where:
- Any number of regions can be added
- Multiple languages can be added to a region
- A region and language can be separately selected by a visitor
- Website Pages are shown depending on the currently selected region and language
The solution technically comprises of a few parts:
- A custom menu module that includes a custom language region switcher, which can be configured via fields, and renders a header menu depending on the current language and region.
- A footer module that can render a footer menu depending on the current language and region pair.
- A set of regular (groups of) Website Pages that leverage HubSpot's built-in multi-language features.
- (optional) a HubDB database that tells our language switcher which pages across regions are conceptually related to each other, so you can directly switch between them.
In the next two sections, we'll show how to implement these parts.
- First, we implement what can be done without code and explain some of the concepts in more detail.
- Then, we add the coded modules that support the solution and configure them.
Before starting this hefty implementation endeavour though, you should be aware that there are some SEO limitations to the solution that might affect the discoverability of the site's multi-region variations in the search engines.
- As you will see, the solution forces you to use the
/<language_code>/<country_code>URL scheme. This is not the same as the conventional
- HubSpot's mandatory Header and Footer includes automatically includes a
hreflangtag for alternate languages, but it will not take into account our custom-built alternate regions (countries). Unfortunately, these includes can't be edited. It's also not possible to use
hreflangHTTP headers (since we can't change the HubSpot backend), and HubSpot's
sitemap.xmlcustomization is just not flexible enough.
So, is the solution worthless? Not at all! Utilising this solution in practice has shown that Google is smart enough to detect other signs that the site is multi-region, by looking at the country name in the path and the language content of pages. But, the performance is not guaranteed in the same way when using more explicit signals. If you're very strict on SEO and need a multi-region solution, HubSpot might not be your best choice right now!
Implementation: CMS preparation
This section guides you through the parts of the solution that don't require code. You don't have to implement these first, but reading about them first should help to clarify the idea behind the solution in more depth.
Creating the multi-language/region pages
The basis of our website will remain its HubSpot Website Pages. With this multi-region/multi-language solution, you typically will need to create one page per targeted language in a region (a language-region pair).
This guide (and the sample site!) does not cover what you should put inside the page, it just ensures that you have different pages per region and per language, and that you can switch between them.
Region and language slugs
We will need to define which regions and languages we want to add to the solution, and what slugs they will have. The slugs in this case will be based on language and region codes:
- We'll be using the HubSpot default multi-language codes, which seem to derive from ISO-639-1's 2-character codes (see supported languages here). Note that we'll use the HubSpot languages without region specification (So, in the following, when talking about "English", we'll select "English" and not "English - United Kingdom").
- For country codes, we'll use the standard ISO-3166 2-character codes.
In the sample, I used Sweden and Belgium with these languages:
- Sweden (
- Swedish (
- English (
- Swedish (
- Belgium. (
- French (
- Dutch (
- English (
- French (
Creating the pages
Next, we create the pages. We'll create one page variation per language-region pair.
For the sample, our website has 2 pages:
- A homepage
- An 'About Us' page
So for the sample site, we create (2 pages) * (2 + 3 language-region variations) pairs = 12 individual pages.
English should be the same in Sweden and Belgium and you don't want to create a duplicate, the best workaround for this is to just create one page in one region, and create a Custom Redirect from the other region's slug to the existing one)
Let's look at how to set up each individual page.
As an example, here is the English-language page for the 'Sweden' region.
When we create our pages, we will leverage the built-in CMS Hub multi-language features. Note the green markings in the page settings that reflect these settings.
However, assigning a custom region to a page is not supported by HubSpot (that's the problem we're tackling!), so we apply a custom workaround with **a naming convention:
- Since we can't edit the Language slug, we'll always add the region code as the first part of the content slug. This will result in URLs of the form
website.com/<language_code>/<country_code>/<page_slug>, for example
website.com/en/se/min-sida. This is not ideal, but better than nothing.
- We add a region tag
[SE]to the Internal page name for clarity.
- Optionally, we can also reflect the region in the Page title for visitors to see.
After having created all pages, we get a result like this:
Note that for each of the two pages (Home and About us), we now have two "region groups" with each their own language variations.
Creating Navigation Menus
Now that the pages are created, it's time to create their accompanying menus.
Each region-language pair has a separately configurable menu (in the sample, we will have 2 + 3 = 5!). They will be managed/linked to in the language switcher module (see the next section about configuring the header module).
Here are the menus from the sample website:
All that is needed here is to make sure that the menus link to page links for the same region and language. If your page names are all in English, you should check the URLs that pop up to make sure you're linking the right versions.
Building the HubDB for equivalent pages
Next, we build the HubDB for equivalent pages. The use case for this is the following: imagine you need to find a company's contact details, and you got a link from your international colleague that sends you to
company.com/en/se/about-us. However, you're in Belgium, and you need to see the Belgian office's contact details. On the Sweden-English site, you go to the language switcher, and click Belgium → Dutch. Now you expect it to arrive on the About Us page there as well:
But HubSpot templates (that will render the language/region switcher module) have no clue that
/nl/be/over-ons are related to each other! The template can't know that they are both "About Us" pages. So, we need to tell our module explicitly which pages are conceptually related. We do this with a HubDB.
This is somewhat optional: you don't actually have to use this database or fill it, but you should create it (even if you leave it empty) because for now, the sample code expects it to exist (if you're feeling brave, feel free to refactor the code and make it optional!).
If you don't fill this database, clicking Belgium → Dutch from
/en/se/about-us will lead you to the
/nl/be regional homepage, which might be just fine for your needs.
Here is an example database that has the needed entries to link the about-us pages in the sample site to each other:
Steps to create the database
- Create a new HubDB. Note down its ID somewhere.
- Add columns with the following names. They are all of the Text-type:
key, label: you choose (here it is "Concept") This is meant to group together pages with the same concept, but in different regions/languages
- Fill it with all the Website Pages that you want to switch between. If you give pages the same
key, they will be considered conceptually 'equivalent' (in this example: the "About Us" page across regions and languages).
Required HubSpot settings
Finally, here are some last things to configure before going to the code part:
- jQuery: The sample code uses jQuery. It should be enabled. It's not much, so if you're not using jQuery it shouldn't be too hard to convert it to plain JS.
- Disable Language-Specific Redirects: in Settings → Website → Domains & URLs → Language Settings, there is a checkbox for enabling Language-Specific Redirects. It should be disabled since this default functionality interferes with the custom functionality built here.
Implementation: code & configure
Here you'll have to get your code hands dirty. But don't worry, most of it can be copy-pasted from the sample.
Some notes before diving in
- This guide assumes we start from a fresh clone of the
@hubspot/sessiontheme. You can adapt these steps to your own theme if needed.
- The final resulting theme code can be found here, in the sample theme code: https://github.com/ColumbiaRoad/session-multiregion.
- This guide assumes you use Hub CLI (but it's not required).
Installing the header module
If you're using the Session base theme, go ahead and grab the readymade module from here (just make sure you replace the HubDB ID, see below in step II.2).
Otherwise, instructions to replicate it in another theme.
I. Clone your current menu module
- In your local theme, with the HubSpot CLI run
hs create moduleto create a new module. It should be a global module usable in pages, blog posts and blog listings.
- Copy the html, JS, CSS from the current menu module (menu-section.module in the sample theme) and paste it into your new module.
II. Adapt the menu module for multi-region use
- Refer to the fields.json of the sample module and make sure that all those settings are copy-pasted into your module.
- In the HTML:
- Make sure we render the menu for the current language/region/slug: code line
- To the right of the menu (or wherever you want to put it!), add the code that will render the language/region switcher: code section
- Refer to the switcher-specific CSS and copy-paste/adapt it: code section
- Refer to the switcher-specific JS and copy-paste/adapt it: code section
III. Render the module
- Make sure the multi-region menu module is rendered in all relevant templates. See these git changes
- Make sure the default HubSpot language switcher code is disabled (we uncommented it here)
Configuring the module
Open the module in the Design Manager. From there, you can edit its global contents.
Here is where we can use the language/region data model defined above!
See this example configuration for the Swedish-Sweden region-language pair. Make sure to hook up the right menu for the region-language pair here. You can also choose which localized labels the language switcher should have ('Select Country' and 'Select Label').
Installing the footer module
I won't cover this in the same detail as the header module above, since the edits are similar to the above ones in the header module.
Refer to the code changes needed for the Session theme in this commit.
In a nutshell, here is what is needed to happen:
- We create another global module for the Footer
- We make sure it is rendered in the desired templates
- We configure its fields in a similar way to the header modules. In this case, we just need to link to the right menu. For simplicity, I used the same menus as the header menus in the example site, but you could create separate footer menus.
And that was it! 😅 Not a simple workaround, but thanks to the built-in multi-language features, we didn't have to completely reinvent the wheel. If you're curious whether this solution would fit your business case, feel free to get in touch with us below.
I'll end with some extension possibilities of the solution:
- In this example, language switcher labels will be in the same language (always English).
- It's straightforward to change them to their own language versions (e.g. English, Français, Svenska, Nederlands).
- We can vary them depending on the current language of the visitor (e.g. Anglais, Franćais, Néerlandais, Suédois when you're on a
/frpage) by providing translations of the global module. This will give some overhead.
- Automatic region redirection based on the IP-country could be added in client-side JS.
- This might be refactored into an opening portal instead of a switcher.
- You could make it play well with multiple brand domains (.se, .be) rather than subdirectories (that might be a solution for the SEO problem!)
Enjoyed the blog? Maybe you would be interested in working at Columbia Road! We're constantly looking for nice and skilful people to work with – and to have some good banter with! If you are interested in reading more about the work we do at Columbia Road, have a look at our client cases.