Build a Language Learning Chrome Extension: Complete Developer’s Guide
Language learning has revolutionized in the digital age, and Chrome extensions have become one of the most powerful tools for learners worldwide. Whether you are studying Spanish, Japanese, Mandarin, or any other language, having a dedicated vocabulary chrome extension can dramatically accelerate your learning journey. In this comprehensive guide, we will walk you through building a fully functional language learning extension from scratch, featuring translate selection capabilities, vocabulary storage, and an intuitive user interface.
The demand for effective language learning tools has never been higher. With millions of people trying to acquire new languages for travel, career advancement, or personal enrichment, a well-designed language learning extension can fill a significant gap in the market. This project will not only teach you valuable Chrome extension development skills but also result in a genuinely useful tool that learners will appreciate.
Why Build a Language Learning Chrome Extension
Before diving into the code, let us explore why creating a language learning extension is an excellent project choice. The intersection of practicality and technical learning makes this an ideal development exercise.
Market Opportunity
The language learning industry generates billions of dollars annually, with applications like Duolingo, Babbel, and Rosetta Stone dominating the consumer market. However, browser-based tools remain underrepresented. A vocabulary chrome extension that integrates seamlessly with users’ daily browsing habits can capture an audience that desktop and mobile apps often miss. Users spend hours browsing foreign websites, reading international news, and consuming content in their target language. A translate selection feature transforms this passive consumption into an active learning opportunity.
Chrome extensions benefit from a unique advantage: they work exactly where users encounter foreign language content. Unlike standalone apps that require deliberate opening and navigation, a language learning extension activates contextually when users select text. This frictionless interaction model leads to higher engagement rates and more consistent vocabulary acquisition.
Learning Value
From a development perspective, building a language learning extension teaches you several essential Chrome extension concepts. You will work with the Selection API to capture user-selected text, implement storage solutions for vocabulary persistence, create browser action popups for user interaction, and potentially integrate external translation APIs. These skills transfer directly to other extension projects and general web development work.
Project Architecture and Features
Our language learning extension will include several key features that make it genuinely useful for learners. Let us outline the core functionality before writing any code.
Core Features
The extension will provide translate selection functionality that appears when users highlight text on any webpage. This feature will offer instant translation of selected words or phrases, giving learners immediate access to meanings without leaving their current page. The translate selection popup will be non-intrusive, appearing near the selected text and allowing users to dismiss it easily.
Vocabulary storage represents the second major feature. When users encounter useful words, they can save them to their personal vocabulary list with a single click. This collected vocabulary will persist across browsing sessions, allowing learners to review and reinforce their captured words later. The storage system will organize words with their translations, source URLs, and timestamps.
The third feature is a dedicated vocabulary management interface accessible through the extension’s popup. This interface will display saved words, allow searching through the vocabulary collection, and provide options for exporting or organizing saved terms. Users should be able to delete unwanted entries and mark words for focused review.
Technical Stack
We will build this extension using standard web technologies: HTML, CSS, and JavaScript. The entire project will follow Chrome’s Manifest V3 specification, ensuring compatibility with modern Chrome versions and adherence to current best practices. No external frameworks are required, keeping the extension lightweight and easy to understand.
For data storage, we will utilize Chrome’s chrome.storage API, which provides reliable persistence and synchronization capabilities across the user’s Chrome profile. This approach ensures that vocabulary data remains available regardless of which device the user signs into with their Google account.
Setting Up the Project Structure
Every Chrome extension requires a specific file structure and a manifest.json file that defines its configuration. Let us set up our project foundation.
Creating the Manifest File
The manifest.json file serves as the blueprint for our extension. It declares the extension’s name, version, permissions, and the various scripts and resources it requires. Create a new directory for your extension project, then add the following manifest.json file:
{
"manifest_version": 3,
"name": "Language Learning Assistant",
"version": "1.0",
"description": "Capture vocabulary and translate selections while browsing. A powerful language learning tool for Chrome.",
"permissions": [
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"]
}
],
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
This manifest configuration declares several critical permissions. The storage permission enables saving vocabulary data, activeTab allows access to the current tab’s content, and scripting permits executing our content script. The host permissions with <all_urls> ensure our extension works across all websites, which is essential for a language learning tool that needs to function wherever users encounter foreign text.
Project File Organization
Organize your project with the following file structure for clarity and maintainability:
language-learning-extension/
├── manifest.json
├── popup.html
├── popup.js
├── popup.css
├── content.js
├── content.css
├── background.js
├── icons/
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
└── vocabulary.json
Each file serves a specific purpose in our extension architecture. The popup files handle the interface users see when clicking the extension icon, content scripts run on web pages to handle text selection, and the background script manages long-running tasks and communication between components.
Implementing the Content Script
The content script is the heart of our translate selection functionality. This script runs on every webpage and monitors user text selection, displaying a translation popup when users highlight words or phrases.
Detecting Text Selection
We need to listen for the mouseup event, which fires when users release their mouse button after selecting text. This approach is more reliable than using the click event, as it captures the complete selection after the user finishes making it. Add the following code to your content.js file:
// content.js
document.addEventListener('mouseup', function(event) {
const selection = window.getSelection();
const selectedText = selection.toString().trim();
// Remove any existing popup
removeTranslationPopup();
// Only proceed if text is selected and it's not too long
if (selectedText.length > 0 && selectedText.length < 500) {
// Delay slightly to ensure selection is complete
setTimeout(() => {
showTranslationPopup(selectedText, event.clientX, event.clientY);
}, 10);
}
});
// Also handle selection changes for mobile/keyboard selection
document.addEventListener('selectionchange', function() {
const selection = window.getSelection();
const selectedText = selection.toString().trim();
if (selectedText.length > 0 && selectedText.length < 500) {
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
showTranslationPopup(selectedText, rect.left + window.scrollX, rect.bottom + window.scrollY);
}
});
This code establishes the core selection detection mechanism. The mouseup event listener captures traditional mouse-based selections, while the selectionchange event listener handles keyboard and touch-based selections. Both approaches trigger our popup display function with the selected text and positioning coordinates.
Creating the Translation Popup
Now we need the function that creates and displays the translation popup near the selected text:
function showTranslationPopup(text, x, y) {
const popup = document.createElement('div');
popup.id = 'language-learning-popup';
popup.className = 'll-popup';
popup.innerHTML = `
<div class="ll-popup-header">
<span class="ll-original-text">${escapeHtml(text)}</span>
<button class="ll-close-btn">×</button>
</div>
<div class="ll-popup-content">
<div class="ll-loading">Translating...</div>
</div>
<div class="ll-popup-actions">
<button class="ll-save-btn">Save to Vocabulary</button>
</div>
`;
document.body.appendChild(popup);
// Position the popup
positionPopup(popup, x, y);
// Add event listeners
popup.querySelector('.ll-close-btn').addEventListener('click', removeTranslationPopup);
popup.querySelector('.ll-save-btn').addEventListener('click', () => saveWord(text));
// Request translation
translateText(text, popup);
}
function positionPopup(popup, x, y) {
const rect = popup.getBoundingClientRect();
// Adjust if popup would go off screen
let left = x;
let top = y + 10;
if (left + rect.width > window.innerWidth) {
left = window.innerWidth - rect.width - 20;
}
if (top + rect.height > window.innerHeight + window.scrollY) {
top = y - rect.height - 10;
}
popup.style.left = `${left}px`;
popup.style.top = `${top}px`;
}
function removeTranslationPopup() {
const existing = document.getElementById('language-learning-popup');
if (existing) {
existing.remove();
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
This code creates a visually appealing popup that displays near the selected text. The positionPopup function ensures the popup remains visible within the viewport, adjusting its position if it would extend beyond the screen edges. We also include basic HTML escaping to prevent XSS vulnerabilities when displaying user-selected text.
Implementing the Translation Service
Now we need the actual translation functionality. For this example, we will use a free translation API. In production, you might integrate with paid services like Google Translate API or DeepL API, but we will use MyMemory Translation API for demonstration purposes as it requires no API key:
function translateText(text, popup) {
const contentDiv = popup.querySelector('.ll-popup-content');
// Using MyMemory Translation API (free, no key required)
const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=en|es`;
fetch(url)
.then(response => response.json())
.then(data => {
if (data.responseStatus === 200 && data.responseData) {
const translation = data.responseData.translatedText;
contentDiv.innerHTML = `
<div class="ll-translation">${escapeHtml(translation)}</div>
<div class="ll-detected-lang">Detected: ${escapeHtml(data.matches[0]?.segment || 'unknown')}</div>
`;
// Store translation for saving
popup.dataset.translation = translation;
} else {
contentDiv.innerHTML = '<div class="ll-error">Translation failed. Please try again.</div>';
}
})
.catch(error => {
console.error('Translation error:', error);
contentDiv.innerHTML = '<div class="ll-error">Connection error. Please check your internet.</div>';
});
}
This translation function makes a simple HTTP request to the MyMemory API and processes the response. The API returns translation data in a structured format that we parse to extract the translated text. Error handling ensures users receive helpful messages when translations fail.
Saving Vocabulary
The save functionality stores selected words and their translations for later review:
function saveWord(originalText) {
const popup = document.getElementById('language-learning-popup');
const translation = popup?.dataset.translation || '';
const wordEntry = {
id: Date.now(),
original: originalText,
translation: translation,
url: window.location.href,
timestamp: new Date().toISOString(),
context: getSelectionContext()
};
chrome.storage.sync.get(['vocabulary'], function(result) {
const vocabulary = result.vocabulary || [];
vocabulary.unshift(wordEntry); // Add to beginning
chrome.storage.sync.set({ vocabulary: vocabulary }, function() {
showSaveConfirmation(popup);
});
});
}
function getSelectionContext() {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const container = range.commonAncestorContainer;
// Get up to 50 characters of surrounding context
const text = container.textContent || '';
const start = Math.max(0, text.indexOf(selection.toString()) - 25);
const end = Math.min(text.length, text.indexOf(selection.toString()) + selection.toString().length + 25);
return text.substring(start, end);
}
return '';
}
function showSaveConfirmation(popup) {
const actionsDiv = popup.querySelector('.ll-popup-actions');
actionsDiv.innerHTML = '<div class="ll-saved-confirmation">✓ Saved to vocabulary!</div>';
setTimeout(() => {
removeTranslationPopup();
}, 1500);
}
The saveWord function creates a comprehensive vocabulary entry that includes not only the original word and translation but also the source URL and context. This additional metadata proves invaluable for learners who want to revisit words in their original context. The getSelectionContext function extracts surrounding text to provide helpful context for each saved word.
Creating the Extension Popup Interface
The popup interface provides access to the saved vocabulary list and management features. This is what users see when they click the extension icon in their browser toolbar.
Popup HTML Structure
Create popup.html with the following structure:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Language Learning Assistant</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="popup-container">
<header class="popup-header">
<h1>📚 Vocabulary</h1>
<div class="header-actions">
<button id="export-btn" class="icon-btn" title="Export">Export</button>
<button id="clear-btn" class="icon-btn" title="Clear All">Clear</button>
</div>
</header>
<div class="search-container">
<input type="text" id="search-input" placeholder="Search vocabulary...">
</div>
<div id="vocabulary-list" class="vocabulary-list">
<div class="loading">Loading vocabulary...</div>
</div>
<footer class="popup-footer">
<span id="word-count">0 words saved</span>
</footer>
</div>
<script src="popup.js"></script>
</body>
</html>
This HTML structure provides a clean, organized interface with a header containing action buttons, a search input for filtering vocabulary, a scrollable list area, and a footer showing the total word count.
Popup JavaScript Logic
The popup.js file handles loading and displaying vocabulary data:
document.addEventListener('DOMContentLoaded', function() {
loadVocabulary();
setupEventListeners();
});
function loadVocabulary() {
chrome.storage.sync.get(['vocabulary'], function(result) {
const vocabulary = result.vocabulary || [];
displayVocabulary(vocabulary);
updateWordCount(vocabulary.length);
});
}
function displayVocabulary(vocabulary) {
const listContainer = document.getElementById('vocabulary-list');
if (vocabulary.length === 0) {
listContainer.innerHTML = `
<div class="empty-state">
<p>No vocabulary saved yet.</p>
<p class="hint">Select text on any page and click "Save to Vocabulary"</p>
</div>
`;
return;
}
listContainer.innerHTML = vocabulary.map(word => `
<div class="vocab-item" data-id="${word.id}">
<div class="vocab-main">
<div class="vocab-original">${escapeHtml(word.original)}</div>
<div class="vocab-translation">${escapeHtml(word.translation)}</div>
</div>
<div class="vocab-meta">
<a href="${escapeHtml(word.url)}" target="_blank" class="vocab-source" title="View source">Source</a>
<button class="delete-btn" data-id="${word.id}" title="Delete">×</button>
</div>
</div>
`).join('');
}
function setupEventListeners() {
// Search functionality
document.getElementById('search-input').addEventListener('input', function(e) {
const query = e.target.value.toLowerCase();
filterVocabulary(query);
});
// Delete buttons
document.getElementById('vocabulary-list').addEventListener('click', function(e) {
if (e.target.classList.contains('delete-btn')) {
const id = parseInt(e.target.dataset.id);
deleteWord(id);
}
});
// Clear all button
document.getElementById('clear-btn').addEventListener('click', function() {
if (confirm('Are you sure you want to clear all vocabulary?')) {
chrome.storage.sync.set({ vocabulary: [] }, loadVocabulary);
}
});
// Export button
document.getElementById('export-btn').addEventListener('click', exportVocabulary);
}
function filterVocabulary(query) {
const items = document.querySelectorAll('.vocab-item');
items.forEach(item => {
const original = item.querySelector('.vocab-original').textContent.toLowerCase();
const translation = item.querySelector('.vocab-translation').textContent.toLowerCase();
const matches = original.includes(query) || translation.includes(query);
item.style.display = matches ? 'flex' : 'none';
});
}
function deleteWord(id) {
chrome.storage.sync.get(['vocabulary'], function(result) {
const vocabulary = result.vocabulary || [];
const filtered = vocabulary.filter(word => word.id !== id);
chrome.storage.sync.set({ vocabulary: filtered }, loadVocabulary);
});
}
function exportVocabulary() {
chrome.storage.sync.get(['vocabulary'], function(result) {
const vocabulary = result.vocabulary || [];
const csvContent = 'data:text/csv;charset=utf-8,'
+ 'Original,Translation,URL,Date\n'
+ vocabulary.map(w => `"${w.original}","${w.translation}","${w.url}","${w.timestamp}"`).join('\n');
const encodedUri = encodeURI(csvContent);
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', 'vocabulary_export.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
function updateWordCount(count) {
document.getElementById('word-count').textContent = `${count} word${count !== 1 ? 's' : ''} saved`;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
This popup JavaScript provides comprehensive vocabulary management. Users can search through their saved words, delete individual entries or clear everything, and export their vocabulary to a CSV file for use in other applications like Anki or spreadsheet programs.
Styling the Extension
Visual design significantly impacts user experience. Let us create styles that make our extension both attractive and functional.
Content Script CSS
The content.css file styles the translation popup that appears on web pages:
.ll-popup {
position: absolute;
z-index: 2147483647;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
min-width: 250px;
max-width: 400px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
overflow: hidden;
}
.ll-popup-header {
background: #f8f9fa;
padding: 10px 12px;
border-bottom: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.ll-original-text {
font-weight: 600;
color: #212529;
font-size: 15px;
}
.ll-close-btn {
background: none;
border: none;
font-size: 20px;
color: #6c757d;
cursor: pointer;
padding: 0 4px;
line-height: 1;
}
.ll-close-btn:hover {
color: #212529;
}
.ll-popup-content {
padding: 12px;
}
.ll-loading {
color: #6c757d;
font-style: italic;
}
.ll-translation {
color: #0d6efd;
font-size: 16px;
font-weight: 500;
}
.ll-detected-lang {
margin-top: 6px;
font-size: 12px;
color: #6c757d;
}
.ll-error {
color: #dc3545;
}
.ll-popup-actions {
padding: 10px 12px;
border-top: 1px solid #e9ecef;
}
.ll-save-btn {
width: 100%;
padding: 8px 16px;
background: #0d6efd;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background 0.2s;
}
.ll-save-btn:hover {
background: #0b5ed7;
}
.ll-saved-confirmation {
text-align: center;
color: #198754;
font-weight: 500;
padding: 8px;
}
Popup Interface CSS
The popup.css file styles the extension’s main interface:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
width: 350px;
min-height: 400px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f8f9fa;
}
.popup-container {
display: flex;
flex-direction: column;
height: 100%;
}
.popup-header {
background: #0d6efd;
color: white;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.popup-header h1 {
font-size: 18px;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 8px;
}
.icon-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 6px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.icon-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.search-container {
padding: 12px;
background: white;
border-bottom: 1px solid #e9ecef;
}
#search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
}
#search-input:focus {
outline: none;
border-color: #0d6efd;
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
}
.vocabulary-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.vocab-item {
background: white;
border-radius: 6px;
padding: 12px;
margin-bottom: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.vocab-original {
font-weight: 600;
color: #212529;
font-size: 15px;
}
.vocab-translation {
color: #0d6efd;
margin-top: 4px;
font-size: 14px;
}
.vocab-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #f1f3f5;
}
.vocab-source {
font-size: 12px;
color: #6c757d;
text-decoration: none;
}
.vocab-source:hover {
text-decoration: underline;
}
.delete-btn {
background: #fee2e2;
color: #dc3545;
border: none;
width: 24px;
height: 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
line-height: 1;
}
.delete-btn:hover {
background: #fecaca;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #6c757d;
}
.empty-state .hint {
margin-top: 8px;
font-size: 13px;
}
.popup-footer {
background: white;
padding: 10px 16px;
border-top: 1px solid #e9ecef;
font-size: 12px;
color: #6c757d;
}
.loading {
text-align: center;
padding: 20px;
color: #6c757d;
}
Testing Your Extension
Before publishing, you need to test the extension thoroughly to ensure it works correctly across different scenarios.
Loading the Extension in Chrome
To test your extension in Chrome, follow these steps:
- Open Chrome and navigate to
chrome://extensions/ - Enable “Developer mode” using the toggle in the top right corner
- Click the “Load unpacked” button
- Select your extension’s directory containing the manifest.json file
- The extension icon should appear in your Chrome toolbar
Once loaded, test the translate selection feature by visiting any webpage with foreign language content. Select some text and verify that the popup appears with a translation. Try saving words and then clicking the extension icon to see them in your vocabulary list.
Debugging Common Issues
Several common issues may arise during development. If the popup does not appear when selecting text, check the browser console for errors in the content script. Ensure the content script is properly registered in the manifest and that the host permissions include the website you are testing on.
If translations fail to load, verify your internet connection and check the API endpoint. The MyMemory API has rate limits, so you may need to implement caching or switch to a different translation service for production use.
Storage issues can occur if the extension lacks proper permissions. Ensure the storage permission is declared in your manifest and that you are using the correct storage API (chrome.storage.sync for synced storage or chrome.storage.local for local-only storage).
Publishing Your Extension
Once your extension is tested and working correctly, you can publish it to the Chrome Web Store for other users to discover and install.
Preparing for Publication
Before submitting to the Chrome Web Store, prepare several assets:
Your extension needs at least one icon in the 128x128 pixel format. Ideally, provide icons at 16x16, 48x48, and 128x128 pixels for different contexts. Create a visually appealing icon that clearly represents language learning, such as a book, globe, or speech bubble.
Write a compelling description that explains your extension’s features and benefits. Highlight the translate selection capability and vocabulary storage as key features. Include relevant keywords like “language learning extension” and “vocabulary builder” to improve discoverability.
Take screenshots of your extension in action. Show the translation popup on a foreign website and the vocabulary list in the extension popup. These screenshots help potential users understand the extension’s functionality.
Submitting to the Chrome Web Store
To publish your extension, create a developer account at the Chrome Web Store developer dashboard. Pay the one-time registration fee. Then, zip your extension’s files (excluding unnecessary files like .git directories) and upload them through the dashboard.
Google will review your extension to ensure it meets their policies. The review process typically takes a few days. Once approved, your extension becomes publicly available. Users can find it by searching the Chrome Web Store or through direct links.
Conclusion and Future Enhancements
You have now built a complete language learning Chrome extension with translate selection, vocabulary storage, and management features. This project demonstrates how to create meaningful browser functionality using standard web technologies.
The extension you built serves as a solid foundation that can be extended in many directions. Consider adding spaced repetition review sessions to help users memorize vocabulary more effectively. Integrate with dictionary APIs for more detailed definitions and pronunciation audio. Implement synchronization across devices for users who browse on multiple computers.
A vocabulary chrome extension like this fills a genuine need in the language learning ecosystem. By enabling users to capture vocabulary naturally while browsing, you remove friction from the learning process. The translate selection feature transforms passive content consumption into active vocabulary building.
This project showcases the power of Chrome extensions to solve real problems. The skills you have learned—working with content scripts, managing browser storage, creating popup interfaces, and handling user interactions—transfer directly to other extension projects. You are now equipped to build even more sophisticated browser tools.
Start building today, and help language learners worldwide achieve their goals with your extension.