updated styling and translations

This commit is contained in:
2026-02-06 02:49:31 +03:00
parent 01963218ab
commit f756c665fe
26 changed files with 3059 additions and 749 deletions

View File

@@ -53,7 +53,7 @@ export class HighlightEngine {
popup.className = 'goose-highlighter-textarea-popup';
popup.innerHTML = `
<div class="gh-popup-titlebar">
<button class="gh-popup-close" title="Close">&times;</button>
<button class="gh-popup-close" title="${chrome.i18n.getMessage('close') || 'Close'}">&times;</button>
</div>
<pre class="gh-popup-pre">${HighlightEngine.renderHighlighted(text, pattern)}</pre>
`;

View File

@@ -45,8 +45,6 @@ async initialize(): Promise<void> {
document.getElementById('duplicateListBtn')?.addEventListener('click', () => this.duplicateCurrentList());
document.getElementById('mergeListsBtn')?.addEventListener('click', () => this.mergeSelectedLists());
document.getElementById('deleteListsBtn')?.addEventListener('click', () => this.deleteSelectedLists());
document.getElementById('activateListsBtn')?.addEventListener('click', () => this.setSelectedListsActive(true));
document.getElementById('deactivateListsBtn')?.addEventListener('click', () => this.setSelectedListsActive(false));
document.getElementById('editListNameBtn')?.addEventListener('click', () => this.toggleListSettings());
document.getElementById('applyListSettingsBtn')?.addEventListener('click', () => this.applyListSettings());
document.getElementById('importListBtn')?.addEventListener('click', () => this.triggerImport());
@@ -74,6 +72,7 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const listsContainer = document.getElementById('listsContainer');
listsContainer?.addEventListener('click', (e) => this.handleListClick(e));
listsContainer?.addEventListener('keydown', (e) => this.handleListsKeydown(e));
listsContainer?.addEventListener('dragstart', (e) => this.handleDragStart(e));
listsContainer?.addEventListener('dragover', (e) => this.handleDragOver(e));
listsContainer?.addEventListener('drop', (e) => this.handleDrop(e));
@@ -140,7 +139,7 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
private mergeSelectedLists(): void {
const selected = this.getSelectedListIndices();
if (selected.length < 2) {
alert('Select at least two lists to merge.');
alert(chrome.i18n.getMessage('merge_lists_min_two') || 'Select at least two lists to merge.');
return;
}
@@ -148,7 +147,10 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const target = this.lists[targetIndex];
if (!target) return;
const confirmMessage = `Merge ${selected.length - 1} list(s) into "${target.name}"? Source lists will be removed.`;
const confirmMessage = chrome.i18n.getMessage('merge_lists_confirm')
?.replace('{count}', String(selected.length - 1))
.replace('{target}', target.name)
|| `Merge ${selected.length - 1} list(s) into "${target.name}"? Source lists will be removed.`;
if (!confirm(confirmMessage)) return;
const sourceIndices = selected.filter(index => index !== targetIndex).sort((a, b) => b - a);
@@ -169,10 +171,13 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const selected = this.getSelectedListIndices();
if (selected.length === 0) {
if (!this.lists[this.currentListIndex]) return;
if (!confirm('Delete current list?')) return;
if (!confirm(chrome.i18n.getMessage('delete_current_list') || 'Delete current list?')) return;
this.lists.splice(this.currentListIndex, 1);
} else {
if (!confirm(`Delete ${selected.length} selected list(s)?`)) return;
const confirmMessage = chrome.i18n.getMessage('delete_lists_confirm')
?.replace('{count}', String(selected.length))
|| `Delete ${selected.length} selected list(s)?`;
if (!confirm(confirmMessage)) return;
selected.sort((a, b) => b - a).forEach(index => this.lists.splice(index, 1));
}
@@ -186,17 +191,12 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
this.save();
}
private setSelectedListsActive(active: boolean): void {
const selected = this.getSelectedListIndices();
if (selected.length === 0) {
const list = this.lists[this.currentListIndex];
if (list) list.active = active;
} else {
selected.forEach(index => {
if (this.lists[index]) this.lists[index].active = active;
});
private toggleListActive(index: number): void {
const list = this.lists[index];
if (list) {
list.active = !list.active;
this.save();
}
this.save();
}
private applyListSettings(): void {
@@ -265,13 +265,14 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const data = JSON.parse(text);
if (!this.validateImportData(data)) {
alert('Invalid file format. Please select a valid Goose Highlighter export file.');
alert(chrome.i18n.getMessage('invalid_import_format') || 'Invalid file format. Please select a valid Goose Highlighter export file.');
return;
}
this.importLists(data);
} catch (error) {
console.error('Import error:', error);
alert(chrome.i18n.getMessage('import_failed') || 'Failed to import file. Please ensure it is a valid JSON file.');
alert('Failed to import file. Please ensure it is a valid JSON file.');
}
}
@@ -361,7 +362,10 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
private deleteSelectedWords(): void {
const list = this.lists[this.currentListIndex];
if (!list || this.selectedWords.size === 0) return;
if (!confirm(`Delete ${this.selectedWords.size} selected word(s)?`)) return;
const confirmMessage = chrome.i18n.getMessage('confirm_delete_words')
?.replace('{count}', String(this.selectedWords.size))
|| `Delete ${this.selectedWords.size} selected word(s)?`;
if (!confirm(confirmMessage)) return;
list.words = list.words.filter((_, i) => !this.selectedWords.has(i));
this.selectedWords.clear();
@@ -429,6 +433,14 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const index = Number(listItem.dataset.index);
if (Number.isNaN(index)) return;
// Click on active/paused badge toggles that list's active state
if (target.closest('.list-badge')) {
event.preventDefault();
event.stopPropagation();
this.toggleListActive(index);
return;
}
const mouseEvent = event as MouseEvent;
// Ctrl/Cmd + click for multi-select
if (mouseEvent.ctrlKey || mouseEvent.metaKey) {
@@ -449,6 +461,18 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
this.render();
}
private handleListsKeydown(event: KeyboardEvent): void {
const target = event.target as HTMLElement;
const badge = target.closest('.list-badge');
if (!badge) return;
const listItem = badge.closest('.list-item') as HTMLElement | null;
if (!listItem) return;
if (event.key !== 'Enter' && event.key !== ' ') return;
event.preventDefault();
const index = Number(listItem.dataset.index);
if (!Number.isNaN(index)) this.toggleListActive(index);
}
private handleDragStart(event: DragEvent): void {
const target = (event.target as HTMLElement).closest('.list-item') as HTMLElement | null;
if (!target) return;
@@ -595,16 +619,8 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
return;
}
// Handle toggle button click
if (target.classList.contains('toggle-btn')) {
event.stopPropagation();
const index = Number(target.dataset.index);
if (Number.isNaN(index)) return;
const word = list.words[index];
if (word) {
word.active = !word.active;
this.save();
}
// Don't select if clicking on eye toggle
if (target.closest('.word-item-eye-toggle')) {
return;
}
@@ -655,6 +671,22 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
const list = this.lists[this.currentListIndex];
if (!list) return;
// Handle eye toggle (active/disabled)
if (target.classList.contains('word-item-eye-input')) {
const wordItem = target.closest('.word-item') as HTMLElement;
if (wordItem) {
const index = Number(wordItem.dataset.index);
if (!Number.isNaN(index)) {
const word = list.words[index];
if (word) {
word.active = target.checked;
this.save();
}
}
}
return;
}
const editIndex = Number(target.dataset.bgEdit ?? target.dataset.fgEdit ?? -1);
if (Number.isNaN(editIndex) || editIndex < 0) return;
@@ -749,12 +781,20 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
if (this.selectedLists.has(index)) item.classList.add('selected');
item.draggable = true;
item.dataset.index = index.toString();
const wordsLabel = chrome.i18n.getMessage('words_label') || 'words';
const activeLabel = chrome.i18n.getMessage('active_label') || 'active';
const badgeText = list.active
? (chrome.i18n.getMessage('list_active_badge') || 'Active')
: (chrome.i18n.getMessage('list_paused_badge') || 'Paused');
const toggleBadgeTitle = chrome.i18n.getMessage('toggle_active') || 'Toggle active';
item.innerHTML = `
<div class="list-meta">
<div class="list-name">${DOMUtils.escapeHtml(list.name)}</div>
<div class="list-stats">${total} words${activeCount} active</div>
<div class="list-stats">${total} ${wordsLabel}${activeCount} ${activeLabel}</div>
</div>
<div class="list-badge">${list.active ? 'Active' : 'Paused'}</div>
<div class="list-badge" role="button" title="${DOMUtils.escapeHtml(toggleBadgeTitle)}" tabindex="0" aria-label="${DOMUtils.escapeHtml(toggleBadgeTitle)}">${badgeText}</div>
`;
container.appendChild(item);
});
@@ -777,7 +817,10 @@ const wordSearch = document.getElementById('wordSearch') as HTMLInputElement;
if (stats) {
const activeCount = list.words.filter(word => word.active).length;
const inactiveCount = list.words.length - activeCount;
stats.textContent = `${list.words.length} words • ${activeCount} active • ${inactiveCount} inactive`;
const wordsLabel = chrome.i18n.getMessage('words_label') || 'words';
const activeLabel = chrome.i18n.getMessage('active_label') || 'active';
const inactiveLabel = chrome.i18n.getMessage('inactive_label') || 'inactive';
stats.textContent = `${list.words.length} ${wordsLabel}${activeCount} ${activeLabel}${inactiveCount} ${inactiveLabel}`;
}
// Collapse settings panel when switching lists
@@ -811,7 +854,8 @@ private renderWords(): void {
this.totalWords = entries.length;
if (entries.length === 0) {
wordList.innerHTML = '<div class="empty">No words in this list.</div>';
const emptyMessage = chrome.i18n.getMessage('no_words_in_list') || 'No words in this list.';
wordList.innerHTML = `<div class="empty">${emptyMessage}</div>`;
this.renderPaginationControls();
return;
}
@@ -820,6 +864,11 @@ private renderWords(): void {
const endIndex = Math.min(startIndex + this.pageSize, this.totalWords);
const paginatedEntries = entries.slice(startIndex, endIndex);
const editWordTitle = chrome.i18n.getMessage('edit_word') || 'Edit word';
const bgColorTitle = chrome.i18n.getMessage('background_color_title') || 'Background color';
const fgColorTitle = chrome.i18n.getMessage('text_color_title') || 'Text color';
const toggleActiveTitle = chrome.i18n.getMessage('toggle_active') || 'Toggle active';
wordList.innerHTML = paginatedEntries.map(entry => {
const word = entry.word;
const index = entry.index;
@@ -829,12 +878,18 @@ private renderWords(): void {
<span class="word-text">${DOMUtils.escapeHtml(word.wordStr)}</span>
<input type="text" class="word-edit-input" value="${DOMUtils.escapeHtml(word.wordStr)}" data-word-edit="${index}">
<div class="word-actions">
<button class="icon-btn edit-word-btn" data-index="${index}" title="Edit word">
<button class="icon-btn edit-word-btn" data-index="${index}" title="${editWordTitle}">
<i class="fa-solid fa-pen"></i>
</button>
<input type="color" value="${word.background || list.background}" data-bg-edit="${index}" title="Background color">
<input type="color" value="${word.foreground || list.foreground}" data-fg-edit="${index}" title="Foreground color">
<button class="toggle-btn ${word.active ? 'active' : ''}" data-index="${index}" title="Toggle active"></button>
<input type="color" value="${word.background || list.background}" data-bg-edit="${index}" title="${bgColorTitle}">
<input type="color" value="${word.foreground || list.foreground}" data-fg-edit="${index}" title="${fgColorTitle}">
<label class="word-item-eye-toggle" title="${toggleActiveTitle}" aria-label="${toggleActiveTitle}">
<input type="checkbox" class="word-item-eye-input" ${word.active ? 'checked' : ''} data-index="${index}">
<span class="word-item-eye-icon">
<i class="fa-solid fa-eye eye-active"></i>
<i class="fa-solid fa-eye-slash eye-disabled"></i>
</span>
</label>
</div>
</div>
`;
@@ -849,38 +904,55 @@ private renderWords(): void {
const totalPages = Math.ceil(this.totalWords / this.pageSize);
if (totalPages <= 1) {
if (totalPages <= 1) {
paginationContainer.style.display = 'none';
return;
}
const startItem = (this.currentPage - 1) * this.pageSize + 1;
const startItem = (this.currentPage - 1) * this.pageSize + 1;
const endItem = Math.min(this.currentPage * this.pageSize, this.totalWords);
const showingText = chrome.i18n.getMessage('showing_items')
?.replace('{start}', String(startItem))
.replace('{end}', String(endItem))
.replace('{total}', String(this.totalWords))
|| `Showing ${startItem}-${endItem} of ${this.totalWords} words`;
const pageInfoText = chrome.i18n.getMessage('page_info')
?.replace('{current}', String(this.currentPage))
.replace('{total}', String(totalPages))
|| `Page ${this.currentPage} of ${totalPages}`;
const itemsPerPageLabel = chrome.i18n.getMessage('items_per_page') || 'Items per page:';
const firstPageTitle = chrome.i18n.getMessage('first_page') || 'First page';
const prevPageTitle = chrome.i18n.getMessage('previous_page') || 'Previous page';
const nextPageTitle = chrome.i18n.getMessage('next_page') || 'Next page';
const lastPageTitle = chrome.i18n.getMessage('last_page') || 'Last page';
paginationContainer.style.display = 'flex';
paginationContainer.innerHTML = `
<div class="pagination-info">
Showing ${startItem}-${endItem} of ${this.totalWords} words
${showingText}
</div>
<div class="pagination-controls">
<button class="pagination-btn" id="firstPageBtn" ${this.currentPage === 1 ? 'disabled' : ''} title="First page">
<button class="pagination-btn" id="firstPageBtn" ${this.currentPage === 1 ? 'disabled' : ''} title="${firstPageTitle}">
<i class="fa-solid fa-angles-left"></i>
</button>
<button class="pagination-btn" id="prevPageBtn" ${this.currentPage === 1 ? 'disabled' : ''} title="Previous page">
<button class="pagination-btn" id="prevPageBtn" ${this.currentPage === 1 ? 'disabled' : ''} title="${prevPageTitle}">
<i class="fa-solid fa-angle-left"></i>
</button>
<div class="pagination-pages">
<span class="page-info">Page ${this.currentPage} of ${totalPages}</span>
<span class="page-info">${pageInfoText}</span>
</div>
<button class="pagination-btn" id="nextPageBtn" ${this.currentPage === totalPages ? 'disabled' : ''} title="Next page">
<button class="pagination-btn" id="nextPageBtn" ${this.currentPage === totalPages ? 'disabled' : ''} title="${nextPageTitle}">
<i class="fa-solid fa-angle-right"></i>
</button>
<button class="pagination-btn" id="lastPageBtn" ${this.currentPage === totalPages ? 'disabled' : ''} title="Last page">
<button class="pagination-btn" id="lastPageBtn" ${this.currentPage === totalPages ? 'disabled' : ''} title="${lastPageTitle}">
<i class="fa-solid fa-angles-right"></i>
</button>
</div>
<div class="page-size-controls">
<label for="pageSizeSelect">Items per page:</label>
<label for="pageSizeSelect">${itemsPerPageLabel}</label>
<select id="pageSizeSelect" class="page-size-select">
<option value="25" ${this.pageSize === 25 ? 'selected' : ''}>25</option>
<option value="50" ${this.pageSize === 50 ? 'selected' : ''}>50</option>

View File

@@ -1,5 +1,31 @@
import { ListManagerController } from './ListManagerController.js';
function localizePage(): void {
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(element => {
const message = (element as HTMLElement).dataset.i18n!;
const localizedText = chrome.i18n.getMessage(message);
if (localizedText) {
if ((element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') && (element as HTMLInputElement).hasAttribute('placeholder')) {
(element as HTMLInputElement).placeholder = localizedText;
} else {
element.textContent = localizedText;
}
}
});
const titleElements = document.querySelectorAll('[data-i18n-title]');
titleElements.forEach(element => {
const key = element.getAttribute('data-i18n-title');
if (key) {
const translation = chrome.i18n.getMessage(key);
if (translation) {
element.setAttribute('title', translation);
}
}
});
}
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.documentElement.classList.add('light');
@@ -8,6 +34,7 @@ if (savedTheme === 'light') {
}
document.addEventListener('DOMContentLoaded', async () => {
localizePage();
const controller = new ListManagerController();
await controller.initialize();
});

View File

@@ -69,7 +69,7 @@ export class PopupController {
private loadActiveTab(): void {
const saved = localStorage.getItem('goose-highlighter-active-tab');
if (saved) {
if (saved && saved !== 'options') {
this.activeTab = saved;
}
}
@@ -109,6 +109,7 @@ export class PopupController {
private setupEventListeners(): void {
this.setupTabs();
this.setupSettingsOverlay();
this.setupListManagement();
this.setupWordManagement();
this.setupSettings();
@@ -119,6 +120,26 @@ export class PopupController {
this.setupStorageSync();
}
private setupSettingsOverlay(): void {
const overlay = document.getElementById('settingsOverlay');
const settingsBtn = document.getElementById('settingsBtn');
const closeBtn = document.getElementById('settingsCloseBtn');
settingsBtn?.addEventListener('click', () => {
overlay?.classList.add('open');
});
closeBtn?.addEventListener('click', () => {
overlay?.classList.remove('open');
});
overlay?.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.classList.remove('open');
}
});
}
private setupTabs(): void {
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
@@ -257,20 +278,6 @@ export class PopupController {
const list = this.lists[this.currentListIndex];
if (!list) return;
// Handle checkbox click
if (target.classList.contains('word-item-checkbox')) {
e.stopPropagation();
const wordItem = target.closest('.word-item') as HTMLElement;
if (wordItem) {
const index = Number(wordItem.dataset.index);
if (!Number.isNaN(index)) {
const mouseEvent = e as MouseEvent;
this.toggleWordSelection(index, mouseEvent.ctrlKey || mouseEvent.metaKey);
}
}
return;
}
// Handle edit button click
const editBtn = target.closest('.word-item-icon-btn.edit-word-btn') as HTMLElement | null;
if (editBtn) {
@@ -299,8 +306,8 @@ export class PopupController {
return;
}
// Don't select if clicking on switch
if (target.closest('.switch-wrapper')) {
// Don't select if clicking on eye toggle
if (target.closest('.word-item-eye-toggle')) {
return;
}
@@ -318,9 +325,9 @@ export class PopupController {
wordList.addEventListener('change', (e) => {
const target = e.target as HTMLInputElement;
const list = this.lists[this.currentListIndex];
// Handle switch toggle
if (target.classList.contains('switch-input')) {
// Handle eye toggle (active/disabled)
if (target.classList.contains('word-item-eye-input')) {
const wordItem = target.closest('.word-item') as HTMLElement;
if (wordItem) {
const index = Number(wordItem.dataset.index);
@@ -393,17 +400,16 @@ export class PopupController {
private toggleWordSelection(index: number, multiSelect: boolean): void {
if (multiSelect) {
// Ctrl/Cmd + click for multi-select
if (this.selectedCheckboxes.has(index)) {
this.selectedCheckboxes.delete(index);
} else {
this.selectedCheckboxes.add(index);
}
} else {
if (this.selectedCheckboxes.has(index)) {
this.selectedCheckboxes.delete(index);
} else {
this.selectedCheckboxes.add(index);
}
// Regular click - clear all and select only this one
this.selectedCheckboxes.clear();
this.selectedCheckboxes.add(index);
}
this.renderWords();
}
@@ -598,10 +604,10 @@ export class PopupController {
${highlight.count > 1 ? `
<div class="page-highlight-nav">
<button class="highlight-prev" title="${chrome.i18n.getMessage('previous') || 'Previous'}">
<i class="lucide lucide-chevron-up"></i>
<i class="fa-solid fa-chevron-up"></i>
</button>
<button class="highlight-next" title="${chrome.i18n.getMessage('next') || 'Next'}">
<i class="lucide lucide-chevron-down"></i>
<i class="fa-solid fa-chevron-down"></i>
</button>
</div>
` : ''}
@@ -705,16 +711,32 @@ export class PopupController {
}
private setupTheme(): void {
// Theme is now controlled by system/browser preference
// Remove the manual theme toggle from the UI
const themeToggle = document.getElementById('themeToggle') as HTMLInputElement;
// Load saved theme
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light') {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
if (themeToggle) themeToggle.checked = false;
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
if (themeToggle) themeToggle.checked = true;
}
// Setup toggle listener
themeToggle?.addEventListener('change', () => {
if (themeToggle.checked) {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
localStorage.setItem('theme', 'light');
}
});
}
private applyListSettings(): void {
@@ -904,7 +926,6 @@ export class PopupController {
return `
<div class="word-item ${isSelected ? 'selected' : ''}" data-index="${realIndex}">
<div class="word-item-checkbox ${isSelected ? 'checked' : ''}"></div>
<span class="word-item-text">${DOMUtils.escapeHtml(word.wordStr)}</span>
<input type="text" class="word-item-edit-input" value="${DOMUtils.escapeHtml(word.wordStr)}" data-word-edit="${realIndex}" style="display: none;">
<div class="word-item-actions">
@@ -913,9 +934,12 @@ export class PopupController {
</button>
<input type="color" value="${bgColor}" data-bg-edit="${realIndex}" class="word-item-color-picker" title="${chrome.i18n.getMessage('background_color_title') || 'Background color'}">
<input type="color" value="${fgColor}" data-fg-edit="${realIndex}" class="word-item-color-picker" title="${chrome.i18n.getMessage('text_color_title') || 'Text color'}">
<label class="switch-wrapper word-item-switch">
<input type="checkbox" class="switch-input" ${word.active !== false ? 'checked' : ''} title="${chrome.i18n.getMessage('toggle_active') || 'Toggle active'}">
<span class="switch-slider"></span>
<label class="word-item-eye-toggle" title="${chrome.i18n.getMessage('toggle_active') || 'Toggle active'}" aria-label="${chrome.i18n.getMessage('toggle_active') || 'Toggle active'}">
<input type="checkbox" class="word-item-eye-input" ${word.active !== false ? 'checked' : ''} data-index="${realIndex}">
<span class="word-item-eye-icon">
<i class="fa-solid fa-eye eye-active"></i>
<i class="fa-solid fa-eye-slash eye-disabled"></i>
</span>
</label>
</div>
</div>
@@ -955,7 +979,10 @@ export class PopupController {
container.innerHTML = this.exceptionsList.map(domain =>
`<div class="exception-item">
<span class="exception-domain">${DOMUtils.escapeHtml(domain)}</span>
<button class="exception-remove" data-domain="${DOMUtils.escapeHtml(domain)}">${chrome.i18n.getMessage('remove') || 'Remove'}</button>
<button class="exception-remove" data-domain="${DOMUtils.escapeHtml(domain)}">
<i class="fa-solid fa-trash"></i>
${chrome.i18n.getMessage('remove') || 'Remove'}
</button>
</div>`
).join('');
}

View File

@@ -20,6 +20,17 @@ function localizePage(): void {
}
}
});
const titleElements = document.querySelectorAll('[data-i18n-title]');
titleElements.forEach(element => {
const key = element.getAttribute('data-i18n-title');
if (key) {
const translation = chrome.i18n.getMessage(key);
if (translation) {
element.setAttribute('title', translation);
}
}
});
}
function displayVersion(): void {