How I built a Translation Chrome Extension with Plasmo and Express

Building a Chrome Extension was a real challenge especially with Plasmo — a relatively new framework. It was a take-home assignment and I needed to finish it in less than 24 hours. What initially seemed impossible turned into a valuable learning experience. In this article, I will share how I overcame this challenge and the essential steps involved.

The project’s main objective was to create a backend API for a Chrome extension that offers real-time word translations, along with a basic Chrome extension to interface with the API. The extension should:
1. Capture a word selected by the user.
2. Communicate with the backend API to fetch the translation.
3. Display the translation to the user.

Here’s a short demo I highly recommend to take a look so you will have a clear idea of what we are building: https://youtu.be/9AtJY6IziK4

Plasmo is a new and potent tool that helps us build, test, and deploy powerful, cutting-edge products on top of the web browser. Resources and tutorials for learning this framework were limited which posed a significant challenge, and I had to admit that chatGPT wasn’t much helpful either.

That’s why it’s a good assessment to see our adaptability to new frameworks and problem-solving abilities.

Initially, I spent about 2 hours tofamiliarize myself with Plasmo, how it works, how to build an extension on top of it (I had no prior experience with building extensions).

Please check out my GitHub (link below) to see how I built the translation API with Express. For this article, we’ll assume that the translation API is already in place.

First, how can we capture the word by the user?

Before we delve into the implementation on Plasmo, it’s a good idea to Google and get a general understanding of the concept. This will provide us with a solid foundation before we tackle the implementation details.

chrome.contextMenus.create({
  title: "Capture Selected Text",
  contexts: ["selection"],
  onclick: function(info, tab) {
    // 'info.selectionText' contains the selected text.
    var selectedText = info.selectionText;
    // You can now use 'selectedText' in your extension.
    // For example, you can send it to your extension's popup or another UI component.
  }
});

This is a promising. With Plasmo, our next step involves creating a ‘background.ts’ file to store essential functionality. One of the key reasons for using ‘background.ts’ is to eliminate concerns about CORS (Cross-Origin Resource Sharing) and enable us to fetch resources from any origin. This aligns with our objective, as in the next step (Step 2), we’ll be making requests to the API we’ve built using Express.

Here’s what we will need to include in background.ts in Plasmo:

chrome.runtime.onInstalled.addListener(function () {
  chrome.contextMenus.create({
    title: 'Quick Translate For "%s"',
    contexts: ["selection"],
    id: "myContextMenuId"
  })
})

Second, how can we make a request to fetch the translation?

It appears that the process is relatively straightforward and doesn’t differ significantly from the typical requests we make with React.

const trans = async (text) => {
  const requestBody = {
    text: text,
    targetLanguage: "es" // Replace with your target language
  }

  const response = await fetch("http://localhost:8001/api/v1/translation", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(requestBody)
  })
  let translation
  if (response.ok) {
    const data = await response.json()
    translation = data.translation
  }
  return translation
}

In the scenario where the background acts as the server making requests to another server, the next step is to emit the retrieved data back to the front end and present it to the user.

chrome.contextMenus.onClicked.addListener(async function (info, tab) {
  const tldrText = await trans(info.selectionText)
  chrome.tabs.sendMessage(tab.id, {
    type: "lookup",
    text: tldrText
  })
})

To consume the data and display it, we will need to create a file called content.tsx.

The content file will help us interact with our current web page. We can also apply styling using CSS and in my case, I’m using Tailwind.

Consider the ‘content.tsx’ file as the face of our tool, where we have the ability to manipulate the Document Object Model (DOM) and approach it in a manner quite similar to how we construct user interfaces with React.

What’s most significant at this stage is establishing a connection between our ‘content’ script and the ‘background,’ allowing data exchange and interaction.

We can use useEffect and onMessage to listen to incoming data, as demonstrated below. The ‘type’ and ‘text’ properties correspond to the type and text we emit from ‘background.ts’.

After retrieving the data which is ‘text’ in this case, we can utilize React’s ‘useState’ to store and display it within our JSX.

const [translate, setTranslate] = useState(null)

  useEffect(() => {
    chrome.runtime.onMessage.addListener(function ({ type, text }) {
      setTranslate(text)
      return true
    })
  }, [])

  return (
    <div>
      <div className="p-6 bg-white rounded-lg border border-gray-200 shadow-md dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
        <h1 className="mb-2 text-3xl font-bold tracking-tight text-gray-900 dark:text-white">
          Hi I'm your translation bot!
        </h1>
        <h2 className="mb-2 text-2xl font-bold tracking-tight text-gray-600 dark:text-white">
          {translate}
        </h2>
      </div>
    </div>
  )

For me, developing the backend system for a Chrome Extension with Express.js and the Plasmo Framework has been an exciting and promising project. Plasmo enables developers to efficiently construct a production-ready Chrome Extension while facilitating the addition of new features.

If you need more information, please check out the resources below as well as my 2 repositories for both Plasmo and API Express.

Resouces: https://www.youtube.com/live/suDy5SNbXsI?si=rDv_IqJIwIMtz_TH

GitHub Plasmo: https://github.com/Miminiverse/plasmo

GitHub Plasmo-Express API: https://github.com/Miminiverse/plasmo-api

Published by

Leave a comment