• Courses
    • Courses
    • Course bundles
  • Blog
  • Services
  • Resources
    • Youtube channel
    • E-books and Guides
    • GTM Recipes
    • View All Resources
    • GTM Community
    • GA4 community
  • About
    • About
    • Contact
  • Login
  • Courses
    • Courses
    • Course bundles
  • Blog
  • Services
  • Resources
    • Youtube channel
    • E-books and Guides
    • GTM Recipes
    • View All Resources
    • GTM Community
    • GA4 community
  • About
    • About
    • Contact
  • Login

April 8, 2019

Track Page Translations with Google Tag Manager and Google Analytics

Very recently I spotted a conversation somewhere online asking about page translations (unfortunately, I don’t even remember on which platform/community did this happen. Anyway, if I recall correctly, the request was to track the original title of the page in GA (rather than the translated one) and that way keep the data purity at a higher level. In opposite, others were defending that this is very valuable information that can signal the need for a localized website’s version (read: no need for purification).

And this conversation planted a seed in my head. Wouldn’t it be cool to track page translations with Google Tag Manager? Every time someone translates a page, we could track it as an event and later check the aggregated information. One thing led to another and here I am writing my take on this topic.

The context

When it comes to translating a page, there are two mainstream ways of doing that (as far as I know):

  1. By using the Translate to… feature in Google Chrome (or by having a language dropdown menu implemented on a page that is using the Google Translate)
  2. By using one of the translation websites (this blog post’s solution supports Google Translate website and Bing Translator).

There are probably more solutions but I found these two the most often used (sorry, no particular data, just a gut feeling). If you notice that something big is missing here, post a comment! I’ll be more than happy to update the solution.

When a visitor uses one of the aforementioned translation solutions, the language of the entire content of the website (text) is automatically changed to another language.

In this blog post, we’ll track exact moments when the translation has been activated and will send such data as events to Google Analytics (via GTM). Additionally, we’ll track the translation language and what kind of translation solution was used (Chrome’s built-in translation feature, Google Translate website or Bing Translator website).

I also tried to translate a website with Firefox and it looks like the Google Translate plugin just redirects a visitor to the Google Translate Website.

But before we dive into the actual solution on how to track page translations with Google Tag Manager, first let’s take a look at how the actual translations are working.

 

Translation Method #1: “Translate to…” feature in Google Chrome (or a Language dropdown embedded on a page)

If you’re on Chrome, do the right-click anywhere on a page and choose Translate to [some language]. 

Boom, the entire page’s text content is now translated to your chosen language.

If you want to change the translation options, click the Google Translate icon in the website’s address bar (right corner).

This translation is possible because the browser manipulated the website’s Document Object Model (DOM) (a quick introduction to the DOM is available here). Several modifications include:

  • translated-rtl or translated-ltr class is added to the <html> node of the website
  • lang attribute’s value in that very same <html> node is changed to the translation language of visitor’s choice

Luckily with JavaScript, this DOM manipulation can be tracked. Therefore, we can use it as a triggering condition to capture the exact moment when a visitor translates the page. More on that – a bit later.

Once you reload the page, the translation will be gone (unless, maybe, there are some settings that always for the translation. Not sure about that).

An alternative solution to how the page can be translated is by implementing the Google Translate language dropdown. You can learn more about it here. This dropdown is also supported by the solution (explained in this blog post). Such translations will be displayed as “on-page google translate” in your Data Layer and GA reports.

 

Translation Method #2: Google Translate or  Bing Translator websites

The second translation method that I stumbled upon is by simply using the web interface of the popular translation services. You just have to visit their websites, paste the URL you wish to translate and then the translator will generate a link to the translated page.

Once you click the URL, the translated page will be opened in the iFrame. If (at the same time) you open the GA Real-time reports, you’ll notice that the hostname of the page contains a totally different hostname (if you have implemented the Show Full URL filter):

  • Google’s translated page’s URL will belong to translate.googleusercontent.com
  • Bing’s translated page’s URL will belong to www.translatoruser-int.com

Also, the URLs of the translated page contain various query parameters. One of them indicates the translation language. We’ll use that in our tracking too.

To sum up, every time a page is loaded and the hostname belongs to Bing’s or Google’s translation services, we’ll track that as a page translation (+ read the value of the query parameter that contains the translation language).

Solution: Track Page Translations with Google Tag Manager

 

GTM Recipe

For your convenience, I have also prepared a GTM recipe that will automatically create all the items described below. Download the Page Translation Tracking Recipe here

Curious how this Page Translations tracking with Google Tag Manager is working in detail? Continue reading.

 

Page Translation Listener

And here is the listener written by yours truly. Compatibility: works on all modern browsers (IE10 and lower won’t be able to track the first translation method). But I think that if visitors are using IE 10 and lower, they deserve to suffer a bit.

<script>
(function() {
// Observe DOM mutations whether the <html> node was changed by Google Translate
if (window.MutationObserver) {
  var mutationObserver = new MutationObserver(function(mutations) {
    mutations.forEach(function (mutation) {
      var oldElementClass = mutation.oldValue;
      var currentElementClass = mutation.target.className;
      if (oldElementClass.indexOf('translated-') === -1 && currentElementClass.indexOf('translated-') > -1) {
        window.dataLayer.push({
          'event' : 'pageTranslated',
          'translationLanguage' : mutation.target.lang || document.getElementsByTagName('html')[0].getAttribute('xml:lang'),
          'translationService' : 'on-page google translate'
        });
      }
  })
})

  var htmlNode = document.querySelector('html');
  mutationObserver.observe(htmlNode, {
    attributes: true,
    attributeOldValue: true,
    attributeFilter: ['class']
  })

}



// Let's also track pageviews when the page is translated directly from translate.google.com or bing.com/translator
// A function that can return individual query parameter (borrowed from https://davidwalsh.name/query-string-javascript)
function getUrlParameter(name) {
  name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  var results = regex.exec(location.search);
  return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};

// Check if the page is being translated directly from translate.google.com (viewed within the iframe)
if (window.location.href.indexOf('translate.googleusercontent.com') > -1 ) {
  window.dataLayer.push({
    'event' : 'pageTranslated',
    'translationLanguage' : getUrlParameter('tl'),
    'translationService' : 'google translate website'
  });
}

// Check if the page is being translated directly from bing.com/translator (viewed within the iframe)
if (window.location.href.indexOf('translatoruser-int.com') > -1 ) {
  window.dataLayer.push({
    'event' : 'pageTranslated',
    'translationLanguage' : getUrlParameter('to'),
    'translationService' : 'bing translator website'
  });
}
})();
</script>

If you don’t trust my JS code’s quality, you’re not alone. I don’t trust my coding skills either. One of my goals in 2019 is to learn at least solid essentials of JavaScript, therefore, hopefully, that will improve. Luckily, tasks/problems (like the one explained in this blog post) are a great way of learning anything and feeling the joy of discovery.

Back to the quality of the listener. Since I also do not trust myself with JS (at least for now), I reached out to his excellence Simo Ahava to do a quick code review. Simo was kind to suggest some improvements and give some tips.

So don’t be afraid and feel free to use the code above for your GTM tracking needs. If you’re still not convinced, here is Simo’s Seal of Approval (I just totally made that up).

P.S. I think I need to seriously rethink my personal/work priorities because the design of this seal required way too much time (“thanks” to my limited photoshopping skills).

I’m slightly proud of myself that I was able to pull this seal off but at the same time, I’m horrified of how many other useful things I could have done instead.

Anyway, enough of self-loathing for this time. Let’s get back to business. First, we can take a quick look at the listener’s code and I’ll explain what each part does.

<script>
(function() {
// Observe DOM mutations whether the <html> node was changed by Google Translate
if (window.MutationObserver) {
  var mutationObserver = new MutationObserver(function(mutations) {
    mutations.forEach(function (mutation) {
      var oldElementClass = mutation.oldValue;
      var currentElementClass = mutation.target.className;
      if (oldElementClass.indexOf('translated-') === -1 && currentElementClass.indexOf('translated-') > -1) {
        window.dataLayer.push({
          'event' : 'pageTranslated',
          'translationLanguage' : mutation.target.lang,
          'translationService' : 'on-page google translate'
        });
      }
  })
})

  var htmlNode = document.querySelector('html');
  mutationObserver.observe(htmlNode, {
    attributes: true,
    attributeOldValue: true,
    attributeFilter: ['class']
  })

}
</script>

This part of the code is checking the DOM manipulations that are happening to the very top <html> node in the Document Object Model. If the change actually occurs and that change involves adding a class that contains “translated-” string, then an event pageTranslated is pushed to the Data Layer (together with the new value of lang attribute (which basically returns the translation language)).

The 2nd part of the listener (starting from the line 30) is responsible for tracking translations that are happening within the iframe (remember the Translation Method #2 mention in this blog post).

This part is optional and you could actually achieve the same result by using built-in GTM functionality (Pageview trigger and URL variable that reads a query parameter).

But I decided to include URL parsing in the listener in order to keep the number of variables and triggers at a minimum.

First I borrowed a function from David Walsh’s blog post that allows returning a value of a particular query parameter that is currently in the URL.

<script>
// Let's also track pageviews when the page is translated directly from translate.google.com or bing.com/translator
// A function that can return individual query parameter (borrowed from https://davidwalsh.name/query-string-javascript)
function getUrlParameter(name) {
  name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  var results = regex.exec(location.search);
  return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};

Then I created two IF statements that are checking the Page URL. If it contains translate.googleusercontent.com or translatoruser-int.com, it will fire an event pageTranslated to the Data Layer. Thanks to the function mentioned above, it’s easy to return the actual translation language that a visitor/user has chosen (because it is stored in “to” or “tl” query parameters).

<script>
// Check if the page is being translated directly from translate.google.com (viewed within the iframe)
if (window.location.href.indexOf('translate.googleusercontent.com') > -1 ) {
  window.dataLayer.push({
    'event' : 'pageTranslated',
    'translationLanguage' : getUrlParameter('tl'),
    'translationService' : 'google translate website'
  });
}

// Check if the page is being translated directly from bing.com/translator (viewed within the iframe)
if (window.location.href.indexOf('translatoruser-int.com') > -1 ) {
  window.dataLayer.push({
    'event' : 'pageTranslated',
    'translationLanguage' : getUrlParameter('to'),
    'translationService' : 'bing translator website'
  });
}
})();

 

Custom HTML tag

Copy the entire code of a listener, create a Custom HTML tag and paste the code there. Set the listener to fire on All Pages.

 

Trigger

Every time a translation is spotted, the listener will push its information to the Data Layer and the value of an event key will be pageTranslated.

That’s why we need to create a Custom Event trigger with the very same name.

 

Data Layer Variables

With every dataLayer.push, there will be two additional keys passed to the Data Layer, translationLanguage and translationService. Currently, the translationService will return one of the 3 possible options:

  • on-page google translate (this means Translate to… feature after doing a right-click OR if a website has an embedded language dropdown that is using Google Translate)
  • google translate website (when a visitor lands on a translate.google.com and pastes the URL of the page that he/she wants to translate)
  • bing translator website (when a visitor lands on a bing.com/translate and pastes the URL of the page that he/she wants to translate)

In order to create those variables in GTM interface, go to Variables > New > Data Layer Variable and and enter the following information (case-sensitive!).

Now repeat the same actions and enter the information of the translationService variable).

 

Universal Analytics Event Tag

Now, let’s send the translation information as an event to Google Analytics. In GTM interface, go to Tags > New > Universal Analytics and enter the following settings:

  • Track type – Event
  • Event Category – page translation
  • Event Action – {{dlv – translationLanguage}} (you should enter here the name of your Data Layer Variable)
  • Event Label – {{dlv – translationService}}  (you should enter here the name of your Data Layer Variable)
  • Non-interaction hit – true
  • Select the Google Analytics Settings Variable that you’re using in your GTM container
  • Choose the trigger – Custom – pageTranslated (this is the trigger you have created in one of the previous chapters of this blog post)

 

Preview and Debug

Now the time has come to test. Save all the changes, refresh/enable Preview and Debug mode and see whether everything works. Possible testing scenarios:

  • Open your website in Chrome. Do the right-click (anywhere on the content), choose Translate to [some language]

    and see if your GA Event is visible in the GA Real-time report. Important: GTM preview and debug mode does some crazy stuff when the page is translated, therefore, you should be checking events directly your GA Real-time reports.
  • Open a JavaScript console and enter the following commands in it:
    • google_tag_manager[“YOUR_CONTAINER_ID”].dataLayer.get(‘translationLanguage’)
    • google_tag_manager[“YOUR_CONTAINER_ID”].dataLayer.get(‘translationService’)
      If they return something similar to the screenshot below (of course, the values might be different) and not undefined, the listener did its job properly.

      With these commands you are checking what values do those two keys store in the internal Google Tag Manager Data Model.
  • Go to Google Translate, paste the URL of your page and then click the generated URL of a translated page. It will open an iFrame with the translated page. With the help of Preview and Debug mode, check whether the translation events were sent to Google Analytics.
  • Repeat the same thing with Bing Translator. Enter the URL, click the generated URL and then in the Preview and Debug mode, check whether the GA Event tag has fired. And, of course, check the Real-time reports in GTM.
  • IMPORTANT: Since the GA event is set to be as non-interaction hit: true, you won’t be able to see them in the Active Users tab of your GA Real-time event reports. Switch to Events (last 30 minutes), instead.

 

Track Page Translations with Google Tag Manager and Google Analytics: Final words

OK, now what? Sit back and be patient. The translation data will start coming into your Google Analytics reports. Thanks to it, you will be able to measure the need for a localized version of your website. If many people are coming to your pages and are translating them, maybe that’s a good sign to invest and make the website more accessible to residents of that particular country.

Also, it would be interesting to compare the conversion rate of those sessions/visitors when page translation is used vs is not used. It might have a huge impact (or it might not at all). So what are you waiting for? Be curious, try this solution and let’s see what comes out of it.

If you know more ways how a visitor/user can translate a page, feel free to contact me. I’m more than open to update the listener (if, of course, that translation method is actually popular).

Did I miss something related to page translations and Google Tag Manager? Let me know in the comments below!

Julius Fedorovicius
In Google Tag Manager Tips
14 COMMENTS
Ricardo Vales
  • Apr 8 2019
  • Reply

Hi Julius, this is fantastic.
More or less on the same subject, is there any easy way to avoid data in Events of being translated? I mean, image that you have an event that pull the following data from the DOM: "Brochure Download".
If the user translated the website to Portuguese, for example, it shows: "Brochura baixada". We understand what it means but, we can get lot of Events because if each user changes the language to their own language we can end up with 163 different languages, or more.
This doesn't happens always. So, we can always filter and only show the ones in English - for example; or, create a RegEx Table/Lookup Table and change the,m as they appear. Or event, Replace them on GA. But, this reveals a huge workload for the benefits.
Is there any way we can automate this from happening?
Just picking your brains!! ;-)

Thanks for this article! Very useful!

    Ricardo Vales
    • Apr 8 2019
    • Reply

    Sorry for the mistakes!
    *imagine
    *them
    *Or even,

    Julius Fedorovicius
    • Apr 9 2019
    • Reply

    Hey, Ricardo, good question. Two ideas come to mind:
    - Option A: You forbid translations on a page (applies at least to Google Translate). I haven't tried this but here's a conversation about it https://stackoverflow.com/questions/12238396/how-to-disable-google-translate-from-html-in-chrome. However, this does not sound like a good option.
    - Option B: data-* attributes. Ask a developer to add a certain data-* attribute to important website elements and use their values in your events (rather than element text).

Andrei Jach
  • Apr 9 2019
  • Reply

Hi Julius, once again another great post.

Quick question, would this also trigger on the older translate.google drop down selector that are resident on some sites?

    Julius Fedorovicius
    • Apr 9 2019
    • Reply

    Hi, don't know. Do you have an example of such a page? I'd like to test things out.

Andrei Jach
  • Apr 9 2019
  • Reply

Hi Julius, there is a language selector in the footer of this site: http://www.cockatooisland.gov.au/

Jason B. Hart
  • Apr 9 2019
  • Reply

oldElementClass.indexOf('translated-') === -1
Seems to be causing an issue for me. Upon further investigation it seems as if the old DOM data is not returned in Chrome unless the MutationObserver is initialized to do so. This is just a guess but removing this from the 'if' statement fixed the issue.

    Julius Fedorovicius
    • Apr 10 2019
    • Reply

    Hey Jason, That's odd, haven't noticed such behavior.

    That part of the IF statement is needed to verify whether the change included the addition of "translated-*" class.

    I would like to replicate this issue. Can you share a link where this issue happens? Does it always happen or only sometimes?

    Was it a manual translation of the page? Or have you configured the browser to always translate the page?

      Jason B. Hart
      • Apr 18 2019
      • Reply

      Hi Julius- Here is the usecase.

      1. Place code on the page using GTM
      2. Go to any page on the website using Chrome (on Windows)
      3. Right click on the page and choose "Translate to"... (I selected English; the page was already in English, in case this is the corner case causing this)
      4. Check Console and see error:
      VM440:1 Uncaught TypeError: Cannot read property 'indexOf' of null

        Julius Fedorovicius
        • May 8 2019
        • Reply

        Hey, sorry of the very late response. Can you share a link of a page where you tested this? Because your scenario is pretty standard and I have tested this on multiple websites (and it worked).

        So maybe there is something unique on your website that I should take into the account while writing the listener code.

Astrid Illum
  • Apr 11 2019
  • Reply

Great stuff. I spent a lot of time looking for this Simo Avava-person to no avail. Is he somehow related to Simo Ahava?

    Julius Fedorovicius
    • Apr 11 2019
    • Reply

    Yes, this is a cousin of Simo Ahava who helps him to cope with the GTM load in various forums and communities (because no mortal can do that alone).

    P.S. Thanks for noticing this. Fixed :)

Mark McLaren
  • May 17 2019
  • Reply

This is AWESOME Julius! So easy to install using the recipe. Thanks for all your resources.

Vigneshwaran J
  • Jan 10 2021
  • Reply

I have an issue when i m testing with Google translator and Bing translator. GTM preview mode not enabled and the Listener tag was not fired.
Could you please help me to resolve this issue?

Leave a comment Cancel reply

Your email address will not be published. Required fields are marked *

 

Hi, I'm Julius Fedorovicius and I'm here to help you learn Google Tag Manager and Google Analytics. Join thousands of other digital marketers and digital analysts in this exciting journey. Read more
Analytics Mania
  • Google Tag Manager Courses
  • Google Tag Manager Recipes
  • Google Tag Manager Resources
  • Google Tag Manager Community
  • Login to courses
Follow Analytics Mania
  • Subscribe to newsletter
Recent Posts
  • Google Analytics Client ID: What You Need to Know
  • Duplicate Events in Google Analytics 4 and How to Fix them
  • Track Videos with Google Analytics 4 and Google Tag Manager
Analytics Mania - Google Tag Manager and Google Analytics Blog | Privacy Policy
Manage Cookie Settings