Howdy Folks, In this tutorial, you are going to learn how to create a Minion Language Translator Google Chrome extension by just using HTML, CSS, Javascript, and a little bit of JSON.

You can download the completed project from GitHub --> Minion Translator

Or you can get the final code at the end of each section too...

Prerequisites

I assume you are already familiar with basic Javascript. And you should have a decent understanding of HTML, CSS, and JSON. You should also know how to make a request to an API using fetch().

But If you don't know some of them, don't worry, just continue. I will try my best to keep this tutorial simple. 

I am going to use "visual studio code" as my text editor. You are free to use whatever text editor you like.

I will explain in short what we are going to build as a Chrome extension. But before that let me tell you what a Google Chrome Extension is.

What is a Chrome Extension?

You may have used some of the popular Chrome Extensions like:

  • Grammarly - Which helps you to write error-free contents
  • Colorzilla - Which helps you to pick a color from the current web page
  • Wappalyzer - Which uncovers the technologies used on websites.
  • Momentum - Which customizes your new browser tab with quotes and images.

But do you know that, at a very basic level, Chrome extensions are zipped bundles of HTML, CSS, JavaScript, and other files? Yeah, you heard that right.

extension breakdown

Extensions are small software programs that customize the browsing experience. When you add a new extension to your browser, you are literally adding some HTML and Javascript files. Extensions are built using web technology and can use the same APIs the browser provides to the open web. After installation, the extensions also became a part of your browser. So, they have a wide range of functional possibilities. They can modify web content the users see and interact with.

They can even extend or modify the behavior of the browser itself by using the API chrome provides. 

Mainly there are two types of chrome extensions:

  1. Browser Action - These types of chrome extensions will be active on all pages you visit. And their icon will be always visible on the browser toolbar.
  2. Page Action - But the page action extensions will be only active in certain URLs/pages you visit. At other times, their icons will be grayed out.

 

two types of chrome extension

Here we are going to make a Browser Action extension. It means that it will be active and will work on all pages.

Now you know what type of extension you are going to make. Let's take a look at what are the necessary components that make an extension:

  • mainfest.json
  • background scripts
  • content_scripts
  • UI Elements - popup.html, popup.js, popup.css
  • Some Icons.

 

  • manifest.json file - This file is a must-have one. This file gives the browser information about the extension, such as the most important files and the capabilities the extension might use. This is like a "settings" file. Our manifest.json file looks like this:

manifest json

The manifest file contains:

  • "manifest_version"
  • "name" of the extension
  • "version" of the extension 
  • "icons"
  • "browser_action" and the files belong to that.
  • Some "content_scripts" and "background" scripts.

Another important thing to note here is that, as you can see the script files are divided into two categories:

  • background script - List of scripts that run on the background. If you have a script that makes an API call or something like that in the "background", or script that needs to be fired when the extension is first installed or things like that, then you should put that script in the background script section.
  • content_scripts - List of scripts that read/manipulates the content on the web page. If you have a script that reads/modifies the "content" on the web page, then you should put that script in the content_scripts section. This is because not every file has the right to do everything. Their functionalities are restricted and limited.

content scripts and background scripts

Please don't get confused. Simply if you have code that needs to be run on the "background", then put that script inside the "background" section and if you have code that needs to access/modify the "content" on the webpage then put that script inside the "content_scripts" section. That's it!

You can have so many script files (javascript files). But you should put each script into its corresponding section according to what they perform. That's what Google expects you to do.

One final thing to know here is that our different script files can communicate with each other. They communicate by sending and receiving "messages". The following image from the official Google chrome documentation explains it very well:

message passing between different script files

What we are going to make

We are going to make an English to Minion Language Translator :) For that, I am going to use the Funtranslations API. One thing to note here is that we are going to use their free public version of the API. It means that we can only send 60 requests a day and 5 requests an hour.

Our extension works like this:

  • You can select any text on the web page using the cursor.

selecting the text

  • Then if you click on the icon of our extension, a "popup" with a "Translate" button will pop up.

popup with translate button

  • When you click on that "Translate" button, we will send an API request to the Funtranslation API.
  • Then if the request went success, we replace the text we selected and also all the text similar to that with the value returned by API.
  • If the request failed, we simply console.log() the error message.

animation or gif like preview of what our extension do

Now Let's Get Started...

Initial Setups

As I said earlier, we need:

  • manifest.json
  • background_scripts
  •  content_scripts
  • UI Elements - popup.html, popup.css, popup.js
  • Some Icons

Create a folder structure similar to below:

folder strucutre

First, create a root folder that holds everything together and name it as "Minion Translator".

Then open this folder inside visual studio code.

Then directly inside this root folder, create the following files:

  • manifest.json --> our settings file
  • popup.html --> Holds HTML belongs to our popup
  • popup.css --> Holds CSS belongs to our popup
  • popup.js --> Holds Javascript belongs to our popup
  • background_script.js --> Holds Javascript code that runs on the background
  • content_script.js --> Holds Javascript code which reads/manipulates the content on the web page
  • Finally, download this whole "img" folder --> img. Unzip it. Then put it directly inside our root folder. The "img" folder holds all our icons.

That's it. Now you should have a folder structure similar to above. Now let's first fill up our manifest.json file...

manifest.json

Open the "manifest.json" file and type the following:

{
  "manifest_version": 2,
  "name": "Minion Translator",
  "version": "1.0",
}

Here, we had the following:

  • "manifest_version". Currently, it is 2. (This is not our extension's version)
  • "name" of our manifest.
  • "version" of our extension.

Now we need some icons. Tell the manifest file, as an object, the "size" and the "path_to_the_corresponding_image" like below:

"icons": {
    "16": "img/icon16.png",
    "48": "img/icon48.png",
    "128": "img/icon128.png"
  },

As I told earlier, ours is going to be a "browser_action" extension. Remember this shows up on all pages. So let's specify it in the manifest:

"browser_action": {
    "default_popup": "popup.html",
    "default_icon": "img/icon48.png"
  },
  • The value of "browser_action" key is an object which holds :
    •  the "default_icon" path
    • And the "default_popup" file to show when the icon is clicked. It is an HTML file.

We are going to use both content_scripts and background scripts. That's what we are going to specify next:

"content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content_script.js"]
    }
  ],
  "background": {
    "scripts": ["background_script.js"],
    "persistent": false
  }
  • The "content_scripts" value is a list of objects. Each object has the following properties:
    • "matches" : ["<all_urls>"] means, our content_script has the capability to execute on all URLs. You can limit it by giving a list of specific URLs.
    • "js" --> Holds a list of all our Javascript files which are going to be "content scripts".
  • The "background" property holds an object with the properties:
    • "scripts" --> Holds a list of all our Javascript files which are going to be "background" scripts.
    • "persistent" --> Tells whether to always persist these scripts or not. Always use the false value.

Here is the final code for the "manifest.json" file. Make sure the code you typed up to this point inside the "manifest.json" is exactly like the following: 

{
  "manifest_version": 2,
  "name": "Minion Translator",
  "version": "1.0",
  "icons": {
    "16": "img/icon16.png",
    "48": "img/icon48.png",
    "128": "img/icon128.png"
  },
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": "img/icon48.png"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content_script.js"]
    }
  ],
  "background": {
    "scripts": ["background_script.js"],
    "persistent": false
  }
}

Initial Testing

Let's try to load our chrome extension into our browser. 

  • Open a new tab inside the Google Chrome browser.
  • Type this into the address bar and hit Enter--> chrome://extensions/

Now you should see all of your installed extensions.

  • Now switch on the "Developer mode":

developer mode

  • Then click on the "Load unpacked":

load unpacked

  • Now select the root folder of our project. In my case, it is the "Minion Translator" folder.
  • Now click on the "Extensions" button next to the address bar:

extension icon next to address bar

  • Now you should be able to see your extension in the list:

options

  • Just click on the "pin" icon next to it so that you can easily access it. 

Now, if you click on the Minion Translator extension, you will see nothing. That's because we hadn't done anything yet. We need a popup to show when you click on the icon, right? Let's create that.

popup.html, popup.css, and popup.js

Open the "popup.html" you created and type the following:

<!DOCTYPE html>
<html>
<head>
  <title>Minion Translator</title>
  <!-- linking our css stylesheet -->
  <link rel="stylesheet" type="text/css" href="popup.css"/>
</head>
<body>

  <!-- translate button -->
  <button id="translate-btn">Translate</button>
  
  <!-- linking our js file -->
  <script type="text/javascript" src="popup.js"></script>
</body>
</html>

Here we linked the "popup.css" and "popup.js" files and created a single #translate-btn button. Nothing more.

Now open the "popup.css" file and add some styles:

button {
  background-color: #0095ff;
  border: none;
  outline: none;
  padding: 5px;
  margin-top: 4px;
  border-radius: 5px;
  font-size: 1.1rem;
  color: #fff;
}

button:hover {
  background-color: #0077CC;
  cursor: pointer;
}

Open the "popup.js" file and give the javascript logic for the #translate-btn to work:

// wait for the DOM to completely load
document.addEventListener('DOMContentLoaded', function() {
  // grab the #translate-btn
  const translateBtn = document.getElementById('translate-btn');

  // attach an 'click' eventListener to #translate-btn
  translateBtn.addEventListener('click', function() {
    // lets activate the content_script.js by sending a "message"
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
      // send message to current active tab
      chrome.tabs.sendMessage(tabs[0].id, {"message": "translate"});
    });
  });
});
  • Here, we are waiting for the DOM to completely load.
  • After that, we are grabbing the reference for #translate-btn.
  • Then attach a 'click' event listener to the button.
  • When someone clicks on the #translate-btn, the "content_script.js" is the file that is going to handle the things afterward. So we need to activate that file. We do it by passing a "message". That's what we are doing in the last few lines.

Our message is a simple JSON object. Here I am sending the message object with key "message" and "translate" as its value. This message will be sent to the "currently active tab".

content_script.js & background_script.js

Now, from within the "content_script.js" file let's listen for the incoming message.

Open the "content_script.js" and type the following:

// some global variables


// listen for any "messages"
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  // check the value of the "message"
  
});

// replaceText function definition

The above code will listen for incoming messages. When we get some messages, the callback function inside addListener() will get executed.

The callback function has 3 arguments:

  • request --> This will store the incoming message object.
  • sender --> holds the information about the sender.
  • sendResponse --> You can use this to send back a message.

Here we are going to only use the request arguments value.

Once we get a message, we can understand that someone had clicked the #translate-btn, right? The next thing we need to do is to grab the text the user highlighted on the webpage. Then only we can send an API request to translate it. 

Type this below // check the value of the "message" comment in "content_script.js" file:

if (request.message === "translate") {
    // get the highlighted text on the web page (content)
    const word = window.getSelection().toString();

    // make sure if the "word" selected is not empty
    if (word.length > 0) {
      // it's time to perform api request to translate English to Minionese
      // send "message" to background_script.js
      chrome.runtime.sendMessage({"word": word});
    }
  } else if (request.message === "replace") {
    // get the "find" (actual word) and "replace" (translatedText)
    
    
  } else if (request.message === "error") {
    // just console log it
    
  }
  • Here we are checking if request.message === "translate", because this is not the only message we are going to receive. We will also receive two messages called "replace" and "error" from the "background_script.js" after translation.
  • In the if condition's body:
    • First, get the text the user had highlighted and convert it into String using window.getSelection().toString(). Then store it in a constant called word.
    • Then, make sure the word is not empty.
    • If the word is not empty, it's time to make the API request.

We are not going to make an API request from this "content_script.js" file. We can do that, but we are not going to do it. Instead, we give that job to the "background_script.js". So let's activate the "background_script.js" file by sending a message with the word the user had selected. 

The message sending format differs from file to file. To send a message from "content_script" to "background_script", use the format chrome.runtime.sendMessage({"msg": msg}). And that's what we are doing in the inner if statement.

For more things related to Message Passing, check this article - Message Passing.

 Now open the "background_script.js" and type the following:

// listen for any "messages"
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  // get the "word" from message
  let word = request.word;

  // url 
  const url = `https://api.funtranslations.com/translate/minion.json?text=${word}`; 

  // use "fetch" to perform api request
  fetch(url)
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    // get the translated text. It's in the "translated" key in "contents"
    const translatedText = data.contents.translated;

    // again activate content_script.js and pass a message with "translatedText" and actual "word".
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
      chrome.tabs.sendMessage(tabs[0].id, {"message": "replace", "find": word, "replace": translatedText});
    });
  })
  .catch(function(error) {
    // if some error happened
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
      chrome.tabs.sendMessage(tabs[0].id, {"message": "error"});
    }); 
  });
});

The explanation for the above code:

  • First, listen for any incoming messages.
  • If got any message, get the word from request.word
  • Perform GET request using fetch() to the Funtranslations API. If our request went success, the API will send back a JSON object. The JSON data we get back from the API looks like this:

JSON response from API

  • The data we needed is in the data.contents.translated. After grabbing it, send the translatedText, actual word, and message with the name "replace" (you can give any name to the message) back to the "content_script.js" file. Why? Because we need to find and replace all the occurrences of the text the user had highlighted with the translated word we got from the API. That's why we are sending a message with all these data:

succes message with data

  • If you got some error as a response from API, then instead of sending the above message, send "error" as a message:

error message

Now Again, come back to "content_script.js" and type the following at the very top of the file below this "// some global variables" comment:

let find = "";
let replace = "";

They are some global variables.

Now, at the very bottom of the file and below "// replaceText function definition" comment, create a function called replaceText(), which will replace all the occurrence of the find text (the actual word) with the replace text (the translatedText):

function replaceText(element) {
  if (element.hasChildNodes()) {
    // if our root element has childNodes, then for each childNode, repeat this function
    element.childNodes.forEach(replaceText);
  } else {
    // make a regular expression
    const re = new RegExp(find, "gi");
    // then replace the word
    element.textContent = element.textContent.replace(re, replace);
  }
}

Now below this "// get the "find" (actual word) and "replace" (translatedText)" comment, type this:

find = request.find;
replace = request.replace;

// replaceText function to replace all instances of "find" word with the "replace" word
replaceText(document.body);

Here, we are just assigning some of the values we got from the message to the global variables "find" and "replace". Then we activate the replaceText() function.

Finally below this "// just console log it" comment, type this:

console.log("Sorry some error happened :(");

Here, if some error happened, then just console.log() it. That's what the above line does.

Here is the final code for the "content_script.js" file. Make sure the code you typed up to this point inside the "content_script.js" is exactly like the following:

// some global variables
let find = "";
let replace = "";

// listen for any "messages"
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  // check the value of the "message"
  if (request.message === "translate") {
    // get the highlighted text on the web page (content)
    const word = window.getSelection().toString();

    // make sure if the "word" selected is not empty
    if (word.length > 0) {
      // it's time to perform api request to translate English to Minionese
      // send "message" to background_script.js
      chrome.runtime.sendMessage({"word": word});
    }
  } else if (request.message === "replace") {
    // get the "find" (actual word) and "replace" (translatedText)
    find = request.find;
    replace = request.replace;

    // replaceText function to replace all instances of "find" word with the "replace" word
    replaceText(document.body);
  } else if (request.message === "error") {
    // just console log it
    console.log("Sorry some error happened :(");
  }
});

// replaceText function definition
function replaceText(element) {
  if (element.hasChildNodes()) {
    // if our root element has childNodes, then for each childNode, repeat this function
    element.childNodes.forEach(replaceText);
  } else {
    // make a regular expression
    const re = new RegExp(find, "gi");
    // then replace the word
    element.textContent = element.textContent.replace(re, replace);
  }
}

Final Testing :)

We had successfully build our Chrome extension. Let's do a final test.

Here are some English words in Minion Language:

English words in Minion language

To test them out:

  • Open a new tab and visit this URL again --> chrome://extensions
  • Hit the refresh button of our extension:

refresh button on our extension

  • Open any webpage and highlight some text and click on our extensions icon. Then hit the "Translate" button:

final testing

Hooray, we did it :) If you want to publish your chrome extension to Chrome Web Store, then try this link - https://developer.chrome.com/webstore/publish

If you got some doubts or comments, then please put them below. I am always here to help you...

Wrapping Up

I hope you enjoyed this tutorial. Here are some useful links for further explorations: