diff --git a/README.md b/README.md index 37d93f7..3db6d05 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Built by [@ctala](https://github.com/ctala) | 🌐 [cristiantala.com](https://cristiantala.com) -![Version](https://img.shields.io/badge/version-1.2.3-22c55e) +![Version](https://img.shields.io/badge/version-1.3.0-22c55e) ![Manifest](https://img.shields.io/badge/manifest-v3-3b82f6) ![License](https://img.shields.io/badge/license-MIT-94a3b8) @@ -56,6 +56,12 @@ Get the extension directly from the Chrome Web Store: ## Changelog +### v1.3.0 (2026-06-19) +**Added:** +- English locale — popup UI now follows `chrome.i18n` and the browser's language, with English and Spanish supported out of the box +- `_locales/en` and `_locales/es` message catalogs +- `default_locale` set in `manifest.json` + ### v1.2.3 (2026-02-20) **Fixed:** - CSP bypass for ultra-strict sites (Skool, etc.) — now uses `chrome.scripting.executeScript` with `world: 'MAIN'` instead of DOM script injection diff --git a/_locales/en/messages.json b/_locales/en/messages.json new file mode 100644 index 0000000..4bb01e8 --- /dev/null +++ b/_locales/en/messages.json @@ -0,0 +1,59 @@ +{ + "extName": { + "message": "API Reverse Engineer" + }, + "extDescription": { + "message": "Capture every API call as you browse. Record → use the site → download JSON." + }, + "popupTitle": { + "message": "API Reverse Engineer" + }, + "popupSubtitle": { + "message": "Capture requests on any site" + }, + "statRequests": { + "message": "Requests" + }, + "statUnique": { + "message": "Unique" + }, + "filterLabel": { + "message": "Filter by URL (optional)" + }, + "filterPlaceholder": { + "message": "e.g. api2.skool.com, /api/v1, graphql" + }, + "btnStart": { + "message": "▶ Start" + }, + "btnStop": { + "message": "⏹ Stop" + }, + "btnDownload": { + "message": "⬇ Download JSON" + }, + "btnClearTitle": { + "message": "Clear" + }, + "recordingIndicator": { + "message": "Recording..." + }, + "endpointsCapturedTitle": { + "message": "Captured endpoints" + }, + "emptyState": { + "message": "Press Start and use the site normally" + }, + "recordingOnTab": { + "message": "Recording on" + }, + "useTheSiteNormally": { + "message": "Use the site normally" + }, + "currentTabFallback": { + "message": "current tab" + }, + "confirmClearData": { + "message": "Clear all captured data?" + } +} diff --git a/_locales/es/messages.json b/_locales/es/messages.json new file mode 100644 index 0000000..40d4707 --- /dev/null +++ b/_locales/es/messages.json @@ -0,0 +1,59 @@ +{ + "extName": { + "message": "API Reverse Engineer" + }, + "extDescription": { + "message": "Captura todas las llamadas API mientras navegas. Record → usa el sitio → descarga JSON." + }, + "popupTitle": { + "message": "API Reverse Engineer" + }, + "popupSubtitle": { + "message": "Captura requests en cualquier sitio" + }, + "statRequests": { + "message": "Requests" + }, + "statUnique": { + "message": "Únicos" + }, + "filterLabel": { + "message": "Filtrar por URL (opcional)" + }, + "filterPlaceholder": { + "message": "ej: api2.skool.com, /api/v1, graphql" + }, + "btnStart": { + "message": "▶ Iniciar" + }, + "btnStop": { + "message": "⏹ Detener" + }, + "btnDownload": { + "message": "⬇ Descargar JSON" + }, + "btnClearTitle": { + "message": "Limpiar" + }, + "recordingIndicator": { + "message": "Grabando..." + }, + "endpointsCapturedTitle": { + "message": "Endpoints capturados" + }, + "emptyState": { + "message": "Presiona Iniciar y usa el sitio normalmente" + }, + "recordingOnTab": { + "message": "Grabando en" + }, + "useTheSiteNormally": { + "message": "Usa el sitio normalmente" + }, + "currentTabFallback": { + "message": "tab actual" + }, + "confirmClearData": { + "message": "¿Limpiar todos los datos capturados?" + } +} diff --git a/manifest.json b/manifest.json index 090c9b2..e2dd181 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,9 @@ { "manifest_version": 3, - "name": "API Reverse Engineer", - "version": "1.2.3", - "description": "Captura todas las llamadas API mientras navegas. Record \u2192 usa el sitio \u2192 descarga JSON.", + "name": "__MSG_extName__", + "version": "1.3.0", + "description": "__MSG_extDescription__", + "default_locale": "en", "permissions": [ "storage", "activeTab", diff --git a/popup.html b/popup.html index 06910e2..1e181e0 100644 --- a/popup.html +++ b/popup.html @@ -1,5 +1,5 @@ - + @@ -229,46 +229,47 @@
-

🔬 API Reverse Engineer

-
Captura requests en cualquier sitio
+

🔬 API Reverse Engineer

+
Capture requests on any site
0
-
Requests
+
Requests
0
-
Únicos
+
Unique
- +
- - - + + +
- Grabando... + Recording...
-
Endpoints capturados
+
Captured endpoints
-
Presiona Iniciar y usa el sitio normalmente
+
Press Start and use the site normally
diff --git a/src/popup.js b/src/popup.js index 3d0db3e..ce703ff 100644 --- a/src/popup.js +++ b/src/popup.js @@ -13,7 +13,30 @@ const recordingIndicator = document.getElementById('recordingIndicator'); let isRecording = false; -// Cargar estado al abrir popup +// Populate all [data-i18n] elements with the active locale's strings +function applyI18n() { + document.querySelectorAll('[data-i18n]').forEach((el) => { + const key = el.getAttribute('data-i18n'); + const msg = chrome.i18n.getMessage(key); + if (msg) el.textContent = msg; + }); + + document.querySelectorAll('[data-i18n-placeholder]').forEach((el) => { + const key = el.getAttribute('data-i18n-placeholder'); + const msg = chrome.i18n.getMessage(key); + if (msg) el.setAttribute('placeholder', msg); + }); + + document.querySelectorAll('[data-i18n-title]').forEach((el) => { + const key = el.getAttribute('data-i18n-title'); + const msg = chrome.i18n.getMessage(key); + if (msg) el.setAttribute('title', msg); + }); + + document.title = chrome.i18n.getMessage('popupTitle') || document.title; +} + +// Load state on popup open function loadState() { chrome.runtime.sendMessage({ type: 'GET_STATE' }, (res) => { if (!res) return; @@ -32,11 +55,11 @@ function updateUI(total, unique) { uniqueCount.textContent = unique || 0; if (isRecording) { - btnRecord.textContent = '⏹ Detener'; + btnRecord.textContent = chrome.i18n.getMessage('btnStop'); btnRecord.classList.add('recording'); recordingIndicator.classList.add('active'); } else { - btnRecord.textContent = '▶ Iniciar'; + btnRecord.textContent = chrome.i18n.getMessage('btnStart'); btnRecord.classList.remove('recording'); recordingIndicator.classList.remove('active'); } @@ -46,7 +69,7 @@ function updateUI(total, unique) { function renderEndpoints(endpoints) { if (!endpoints || endpoints.length === 0) { - endpointList.innerHTML = '
Presiona Iniciar y usa el sitio normalmente
'; + endpointList.innerHTML = `
${chrome.i18n.getMessage('emptyState')}
`; return; } @@ -76,11 +99,11 @@ function refreshPreview() { }); } -// Botón Record / Stop +// Record / Stop button btnRecord.addEventListener('click', async () => { if (!isRecording) { const filter = filterInput.value.trim(); - // Obtener el tab activo para grabar solo en él + // Get the active tab so we only record on it const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const tabId = tab?.id || null; @@ -88,9 +111,9 @@ btnRecord.addEventListener('click', async () => { isRecording = true; chrome.storage.local.set({ filter }); updateUI(0, 0); - // Mostrar en qué tab está grabando - const hostname = tab?.url ? (() => { try { return new URL(tab.url).hostname; } catch { return tab.url; } })() : 'tab actual'; - endpointList.innerHTML = `
Grabando en ${hostname}
Usa el sitio normalmente
`; + // Show which tab it's recording on + const hostname = tab?.url ? (() => { try { return new URL(tab.url).hostname; } catch { return tab.url; } })() : chrome.i18n.getMessage('currentTabFallback'); + endpointList.innerHTML = `
${chrome.i18n.getMessage('recordingOnTab')} ${hostname}
${chrome.i18n.getMessage('useTheSiteNormally')}
`; }); } else { chrome.runtime.sendMessage({ type: 'STOP' }, () => { @@ -100,7 +123,7 @@ btnRecord.addEventListener('click', async () => { } }); -// Botón Descargar +// Download button btnDownload.addEventListener('click', async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const site = tab?.url ? new URL(tab.url).hostname : 'unknown'; @@ -118,19 +141,20 @@ btnDownload.addEventListener('click', async () => { }); }); -// Botón Limpiar +// Clear button btnClear.addEventListener('click', () => { - if (!confirm('¿Limpiar todos los datos capturados?')) return; + if (!confirm(chrome.i18n.getMessage('confirmClearData'))) return; chrome.runtime.sendMessage({ type: 'CLEAR' }, () => { updateUI(0, 0); - endpointList.innerHTML = '
Presiona Iniciar y usa el sitio normalmente
'; + endpointList.innerHTML = `
${chrome.i18n.getMessage('emptyState')}
`; }); }); -// Auto-refresh mientras está grabando +// Auto-refresh while recording setInterval(() => { if (isRecording) refreshPreview(); }, 1500); -// Iniciar +// Start +applyI18n(); loadState();