diff --git a/Freecrawl.gif b/Freecrawl.gif index d15a2d2..2e74064 100644 Binary files a/Freecrawl.gif and b/Freecrawl.gif differ diff --git a/README.md b/README.md index ad94ce2..96f05a3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # FreeCrawl SEO Tool -FreeCrawl SEO Tool in action: dense table view, live crawl progress, and detail panel +FreeCrawl SEO Tool in action: a live crawl streaming into the table, issue filters in the Overview, analysis tabs, URL Details, the export dialog, per-project issue settings and the light theme ### Open-source desktop SEO crawler. A free, cross-platform alternative to Screaming Frog. @@ -21,7 +21,7 @@
-A high-performance website crawler built for serious SEO audits. Targets 1M+ URLs on a single machine, with a dense Screaming Frog-style UI, **172 SEO issue checks**, an interface in **11 languages** with dark and light themes, and zero native dependencies. +A high-performance website crawler built for serious SEO audits. Targets 1M+ URLs on a single machine, with a dense Screaming Frog-style UI, **181 SEO issue checks**, an interface in **11 languages** with dark and light themes, and zero native dependencies. @@ -38,11 +38,13 @@ A high-performance website crawler built for serious SEO audits. Targets 1M FreeCrawl crawls your website and checks it for SEO problems. It is a free desktop alternative to Screaming Frog that handles very large sites (**1M+ URLs on one machine**) and streams the results into a fast, dense table while the crawl is still running. -It runs **172 SEO checks across 32 tabs**: page titles, meta descriptions, headings, duplicate content, broken links, redirects, canonicals, hreflang, structured data, security headers, readability, mobile usability, and more. It can also render JavaScript pages with a real headless browser to capture the final DOM, screenshots, and Core Web Vitals. Any check you do not care about can be switched off per project. +It runs **181 SEO checks across 33 tabs**: page titles, meta descriptions, headings, duplicate content, broken links, redirects, canonicals, hreflang, structured data, security headers, readability, mobile usability, soft 404s, and more. It can also render JavaScript pages with a real headless browser to capture the final DOM, screenshots, and Core Web Vitals. Any check you do not care about can be switched off per project. Every link type (images, CSS, JavaScript, media, canonicals, pagination, hreflang, AMP, iframes) has its own **Crawl** and **Store** switch, so you decide exactly what gets fetched and what gets reported. Right-click any table selection to copy cells, rows, or columns straight to the clipboard. -Pull in real data from **Google Search Console, Analytics 4, PageSpeed Insights, and Chrome UX Report**, check spelling and grammar with **LanguageTool** (with a **bundled offline dictionary** for languages LanguageTool has no rules for, such as Turkish), and run your own **AI prompts** on each page (OpenAI, Claude, or Ollama). Open several projects side by side, each in its own window running its own crawl, and manage them from a Projects dialog with search, tags, and starter templates. +The crawler is built for real sites, not only clean ones. It recognises bot-protection challenges (Cloudflare, AWS WAF, Vercel, Sucuri, DataDome) and marks those URLs as **blocked** instead of reporting hundreds of false broken links, re-checks external links with a GET and a browser User-Agent before calling them broken, probes the site for **soft-404** behaviour after every spider crawl, and can slow itself down on 429 / Retry-After responses with the opt-in **Adaptive speed** switch. `srcset` image variants are listed but not fetched unless you turn them on, the status bar shows every post-crawl phase (measuring images, running checks) as it happens, and stored page bodies are **zstd-compressed** with a per-project dictionary so large projects stay small on disk. + +Pull in real data from **Google Search Console, Analytics 4, PageSpeed Insights, Chrome UX Report, and Yandex Metrica**, check spelling and grammar with **LanguageTool** (with a **bundled offline dictionary** for languages LanguageTool has no rules for, such as Turkish), and run your own **AI prompts** on each page (OpenAI, Claude, or Ollama). Open several projects side by side, each in its own window running its own crawl, and manage them from a Projects dialog with search, tags, and starter templates. Export to **Excel, CSV, JSON, XML**, an HTML or brandable **PDF report**, sitemaps, a Screaming Frog-compatible **SEO Audit** folder, **Google Sheets**, or **BigQuery**. Save each project as **one compressed file** (or a **password-protected encrypted file**), and export any URL Details sub-table on its own. A built-in **MCP server** (100+ tools) lets AI agents like Claude drive crawls live and read every result. Every action a human can take in the desktop is callable from an agent, and **several agents can run in parallel**, each in its own isolated crawl session. @@ -62,13 +64,15 @@ Grab the prebuilt app from the [Releases page](https://github.com/kemalai/FreeCr | 🪟 **Windows, no install** | `FreeCrawl-SEO-Tool--Windows-Portable.exe` | Single file, runs from anywhere, including a USB stick | | 🍎 **Mac with Apple Silicon** (M1, M2, M3, M4 and newer) | `FreeCrawl-SEO-Tool--macOS-Apple-Silicon.dmg` | Native build, fastest on these Macs | | 🍎 **Mac with an Intel processor** | `FreeCrawl-SEO-Tool--macOS-Intel.dmg` | For Intel-based Macs (2020 and earlier models) | -| 🐧 **Linux, any distribution** | `FreeCrawl-SEO-Tool--Linux-x86_64.AppImage` | Make it executable and run, no install | +| 🐧 **Linux, any distribution** | `FreeCrawl-SEO-Tool--Linux-x86_64.AppImage` | Make it executable and run, no install (needs FUSE 2, see below) | | 🐧 **Linux, Debian / Ubuntu** | `FreeCrawl-SEO-Tool--Linux-amd64.deb` | `sudo apt install ./FreeCrawl-SEO-Tool--Linux-amd64.deb` | | 🐧 **Linux, Fedora / RHEL** | `FreeCrawl-SEO-Tool--Linux-x86_64.rpm` | `sudo dnf install ./FreeCrawl-SEO-Tool--Linux-x86_64.rpm` | > **Not sure which Mac you have?** Open the Apple menu and choose **About This Mac**. A line that says **Chip: Apple M...** means Apple Silicon; a line that says **Processor: Intel...** means Intel. If you already run the Intel build on an Apple Silicon Mac, **Help → Check for Updates** offers the native build. -> **macOS Gatekeeper:** the builds are not yet notarized. On first launch, right-click the app and choose **Open**, then confirm. If macOS still reports the app as damaged, run `xattr -cr "/Applications/FreeCrawl SEO Tool.app"` once in Terminal. +> **macOS Gatekeeper:** the builds are not yet notarized. On **macOS 15 Sequoia and newer** the first launch is blocked: open **System Settings → Privacy & Security** and click **Open Anyway** (the old right-click → Open override no longer works there). On macOS 12 to 14, right-click the app and choose **Open**. If macOS reports the app as damaged, run `xattr -cr "/Applications/FreeCrawl SEO Tool.app"` once in Terminal. + +> **System requirements** (set by Electron 41): Windows 10 or newer (64-bit); macOS 12 Monterey or newer; Linux on Ubuntu 18.04 / Debian 10 / Fedora 32 or newer. The AppImage needs FUSE 2 (`sudo apt install libfuse2`, on Ubuntu 24.04 `libfuse2t64`). On Ubuntu 24.04+ the kernel denies unprivileged user namespaces to apps without an AppArmor profile, which an AppImage cannot install, so there the AppImage starts with Chromium's sandbox disabled (it tells you so on stderr); the `.deb` keeps the sandbox on, because its installer registers an AppArmor profile, so prefer it on Ubuntu. The Playwright Chromium browser is bundled in every installer, so JavaScript rendering works on first launch without an extra download. All files are built by the same [GitHub Actions workflow](.github/workflows/release.yml) on every release, and the `.yml` and `.blockmap` files listed next to them are build metadata you can ignore. @@ -92,7 +96,7 @@ The Playwright Chromium browser is bundled in every installer, so JavaScript ren | Layer | Choice | | :--- | :--- | -| 🟢 **Runtime** | Node.js 22 LTS+ (ESM-first) | +| 🟢 **Runtime** | Node.js 22.13+ (ESM-first; `node:sqlite` is unflagged from 22.13, zstd body compression from 22.15) | | 📘 **Language** | TypeScript 5.7+ strict | | 🪟 **Desktop shell** | Electron 41 | | ⚡ **Build** | electron-vite 5 / Vite 7 | @@ -205,6 +209,12 @@ npm run dev npx tsc -b apps/cli # builds the CLI + its workspace packages node apps/cli/dist/index.js https://example.com --depth 2 --max 500 --out out.csv node apps/cli/dist/index.js --list urls.txt --out out.json # list mode + JSON +node apps/cli/dist/index.js https://example.com --adaptive-speed --image-variants # back off on 429 / Retry-After, crawl srcset variants +node apps/cli/dist/index.js compare before.seoproject after.seoproject # diff two crawls + +# Yandex Metrica: pull per-page pageviews / visits / users into a project (YANDEX_METRICA_TOKEN in the env) +node apps/cli/dist/index.js metrica-counters +node apps/cli/dist/index.js metrica-fetch --counter 12345678 --days 28 --project crawl.seoproject # Log file analysis: parse an access log into the project for crawl x log reporting node apps/cli/dist/index.js analyze-logs samples/apache-access.txt --project crawl.seoproject @@ -362,7 +372,7 @@ By default the server reads `/projects/default.seoproject` (the same f | Component | Minimum | Where | | :--- | :--- | :--- | -| **Node.js** | 22 LTS (24 also OK) | [nodejs.org](https://nodejs.org/) | +| **Node.js** | 22.13 or newer (24 also OK) | [nodejs.org](https://nodejs.org/) | | **npm** | 10+ (ships with Node) | bundled | | **Git** | any recent | [git-scm.com](https://git-scm.com/) | @@ -440,7 +450,7 @@ graph LR ## 📈 Status > [!NOTE] -> **v1.0.0, the full release.** All 32 analysis tabs, 172 issue checks (each one can be disabled per project), JavaScript rendering (screenshots, LCP candidate, mobile usability), multi-window projects, the link visualiser, every Google integration (Search Console / GA4 / PageSpeed / CrUX), LanguageTool spelling, AI per-URL prompts, SEO Authority providers, Google Sheets + BigQuery export, HTML and PDF reports, the Screaming Frog-compatible SEO Audit export, scheduled crawls with automatic exports and webhooks, encrypted projects, the MCP server with live crawl control, the standalone Log File Analyzer, dark and light themes, and an interface in 11 languages all ship today. +> **v1.0.0, the full release.** All 33 analysis tabs, 181 issue checks (each one can be disabled per project), JavaScript rendering (screenshots, LCP candidate, mobile usability), multi-window projects, the link visualiser, every Google integration (Search Console / GA4 / PageSpeed / CrUX) plus Yandex Metrica, bot-protection and soft-404 detection, LanguageTool spelling, AI per-URL prompts, SEO Authority providers, Google Sheets + BigQuery export, HTML and PDF reports, the Screaming Frog-compatible SEO Audit export, scheduled crawls with automatic exports and webhooks, encrypted projects, the MCP server with live crawl control, the standalone Log File Analyzer, dark and light themes, and an interface in 11 languages all ship today. > > Cross-platform installers ship for Windows (setup + portable), macOS (Apple Silicon + Intel), and Linux (`.AppImage` / `.deb` / `.rpm`), with Playwright Chromium bundled so JS rendering works on first launch. The canvas-rendered URL table stays smooth on 25K+ row crawls, streams the first row in about 1 s, and long crawls are tuned for Apple Silicon (see [docs](docs/macos-apple-silicon-performance.md)). > diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index fd2f5b5..a42ffe1 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -69,6 +69,10 @@ Options: (one request every N seconds). Off by default — the directive is not part of RFC 9309 and published values are often crippling. --external Follow external links + --image-variants Also fetch and measure srcset / image variants (off by default; + a WordPress blog can go from ~2k to ~40k image requests with this on) + --adaptive-speed Back off automatically on HTTP 429/503 and Retry-After, then recover + (off by default; the fixed --rps / --concurrency hold otherwise) --header Extra request header; repeatable (e.g. --header "Authorization: Bearer X") --include Only crawl URLs matching this regex; repeatable --exclude Skip URLs matching this regex; repeatable @@ -123,6 +127,8 @@ async function main(): Promise { 'no-robots': { type: 'boolean' }, 'respect-crawl-delay': { type: 'boolean' }, external: { type: 'boolean' }, + 'image-variants': { type: 'boolean' }, + 'adaptive-speed': { type: 'boolean' }, header: { type: 'string', multiple: true }, include: { type: 'string', multiple: true }, exclude: { type: 'string', multiple: true }, @@ -287,6 +293,8 @@ async function main(): Promise { ...(values['no-robots'] ? { respectRobotsTxt: false } : {}), ...(values['respect-crawl-delay'] ? { respectCrawlDelay: true } : {}), ...(values.external ? { crawlExternal: true } : {}), + ...(values['image-variants'] ? { crawlImageVariants: true } : {}), + ...(values['adaptive-speed'] ? { adaptiveSpeed: true } : {}), ...(values.header !== undefined ? { customHeaders: parseHeaders(values.header) } : {}), ...(values.include !== undefined ? { includePatterns: values.include } : {}), ...(values.exclude !== undefined ? { excludePatterns: values.exclude } : {}), diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index 54876b0..46ba6cb 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -76,6 +76,11 @@ mac: target: - dmg category: public.app-category.developer-tools +# Linux AppImage only: wraps the binary in a launcher that adds --no-sandbox +# on kernels that deny unprivileged user namespaces (Ubuntu 23.10+ AppArmor), +# where Chromium would otherwise abort before the app starts. The hook is a +# no-op for every other platform and target; see the file for the reasoning. +afterPack: scripts/after-pack.mjs linux: executableName: freecrawl-seo artifactName: FreeCrawl-SEO-Tool-${version}-Linux-${arch}.${ext} diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ef3395b..1aefa37 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -24,7 +24,7 @@ "prepare:playwright-bundle": "node scripts/prepare-playwright-bundle.mjs", "build:win": "npm run prepare:playwright-bundle && electron-vite build && electron-builder --win --publish never", "build:mac": "npm run prepare:playwright-bundle && electron-vite build && electron-builder --mac --publish never", - "build:linux": "npm run prepare:playwright-bundle && electron-vite build && electron-builder --linux --publish never", + "build:linux": "npm run prepare:playwright-bundle && electron-vite build && electron-builder --linux deb rpm --publish never && electron-builder --linux AppImage --publish never", "typecheck:node": "tsc -p tsconfig.node.json --noEmit", "typecheck:web": "tsc -p tsconfig.web.json --noEmit", "typecheck": "npm run typecheck:node && npm run typecheck:web" diff --git a/apps/desktop/scripts/after-pack.mjs b/apps/desktop/scripts/after-pack.mjs new file mode 100644 index 0000000..b21e58c --- /dev/null +++ b/apps/desktop/scripts/after-pack.mjs @@ -0,0 +1,84 @@ +// electron-builder `afterPack` hook, wired up in electron-builder.yml. +// +// Linux AppImage only: puts a small shell launcher in front of the Electron +// binary that adds `--no-sandbox` on systems where Chromium's sandbox cannot +// work, and leaves the sandbox on everywhere else. +// +// Why a launcher and not app code: Chromium's sandbox needs unprivileged user +// namespaces. Ubuntu 23.10+ denies them to every process that has no AppArmor +// profile (kernel.apparmor_restrict_unprivileged_userns=1), Chromium then +// falls back to the setuid `chrome-sandbox` helper, and that helper can never +// be setuid inside an AppImage (the FUSE mount is nosuid). The check runs in +// content's ContentMainRunnerImpl::Initialize, before the main process has +// executed a single line of JavaScript, so `app.commandLine.appendSwitch( +// 'no-sandbox')` is too late; the flag has to come from whatever execs the +// binary. electron-builder's own AppRun just execs `$APPDIR/` +// with the user's arguments, so a script at that path is the one place that +// can decide. +// +// The .deb/.rpm are deliberately not wrapped: electron-builder's after-install +// script installs an AppArmor profile (Ubuntu 24.04+) and chmods +// chrome-sandbox 4755 where user namespaces are unavailable, so the sandbox +// keeps working there. That is why `build:linux` runs the AppImage in its own +// electron-builder invocation and this hook refuses mixed target lists. +import { chmodSync, renameSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; + +const REAL_SUFFIX = '.bin'; + +function launcherScript(exe) { + // Written with LF regardless of this file's own line endings: a CRLF shebang + // is "bad interpreter" on Linux. + return `#!/bin/sh +# FreeCrawl SEO AppImage launcher (generated by scripts/after-pack.mjs). +# +# Chromium's sandbox needs unprivileged user namespaces. When the kernel denies +# them to ordinary processes, Chromium falls back to a setuid chrome-sandbox +# helper that cannot exist inside an AppImage, and aborts with "No usable +# sandbox!" before the app starts. On such systems start with --no-sandbox; +# everywhere else the sandbox stays on. +here=$(dirname "$(readlink -f "$0")") +bin="$here/${exe}${REAL_SUFFIX}" + +sysctl_is() { + [ -r "$1" ] && [ "$(cat "$1" 2>/dev/null)" = "$2" ] +} + +for arg in "$@"; do + case "$arg" in + --no-sandbox) exec "$bin" "$@" ;; + esac +done + +# Ubuntu 23.10+ (AppArmor), Debian/Arch style kernels, RHEL 7 style kernels. +if sysctl_is /proc/sys/kernel/apparmor_restrict_unprivileged_userns 1 \\ + || sysctl_is /proc/sys/kernel/unprivileged_userns_clone 0 \\ + || sysctl_is /proc/sys/user/max_user_namespaces 0; then + echo "FreeCrawl SEO: this system restricts unprivileged user namespaces; starting with --no-sandbox." >&2 + exec "$bin" --no-sandbox "$@" +fi + +exec "$bin" "$@" +`.replace(/\r\n/g, '\n'); +} + +export default async function afterPack(context) { + if (context.electronPlatformName !== 'linux') return; + const targets = context.targets.map((t) => t.name); + if (!targets.includes('appImage')) return; + const others = targets.filter((t) => t !== 'appImage'); + if (others.length > 0) { + throw new Error( + `after-pack: the AppImage must be built in its own electron-builder run ` + + `(got targets: ${targets.join(', ')}). The launcher must not end up in the ` + + `.deb/.rpm; see build:linux in apps/desktop/package.json.`, + ); + } + const exe = context.packager.executableName; + const binPath = path.join(context.appOutDir, exe); + const realPath = binPath + REAL_SUFFIX; + renameSync(binPath, realPath); + writeFileSync(binPath, launcherScript(exe), { mode: 0o755 }); + chmodSync(binPath, 0o755); // the mode in writeFileSync is subject to umask + console.log(` • after-pack: AppImage launcher installed as ${exe}, Electron binary is now ${exe}${REAL_SUFFIX}`); +} diff --git a/apps/desktop/src/main/logger.ts b/apps/desktop/src/main/logger.ts index 2b1b77b..3908eb6 100644 Binary files a/apps/desktop/src/main/logger.ts and b/apps/desktop/src/main/logger.ts differ diff --git a/apps/desktop/src/main/menu-locales/az.ts b/apps/desktop/src/main/menu-locales/az.ts index d61fd65..0e69ac1 100644 --- a/apps/desktop/src/main/menu-locales/az.ts +++ b/apps/desktop/src/main/menu-locales/az.ts @@ -227,7 +227,7 @@ export const MENU_AZ: MenuLabels = { detailDownloadSmartScreen: 'Quraşdırıcı kod imzalı olmadığı üçün Windows SmartScreen "Tanınmayan tətbiq" xəbərdarlığı göstərə bilər. Davam etmək üçün "Ətraflı məlumat → Hər halda işlət" seçin.', detailDownloadGatekeeper: - 'Tətbiq notarial təsdiqdən keçmədiyi üçün macOS Gatekeeper ilk açılışda onu bağlaya bilər. Yan keçmək üçün .dmg faylına sağ düymə ilə klikləyib → Aç seçin.', + 'Tətbiq notarial təsdiqdən keçmədiyi üçün macOS ilk açılışda onu bloklaya bilər. macOS 15 Sequoia və daha yeni: Sistem Ayarları → Məxfilik və Təhlükəsizlik bölməsini açıb "Yenə də Aç" düyməsinə klikləyin. macOS 12–14: tətbiqə sağ klik → Aç.', msgDownloadFailed: '{name} endirilə bilmədi', detailDownloadFailed: 'Endirmə vəziyyəti: {state}\n\nGitHub Releases səhifəsindən yenidən cəhd edə bilərsiniz.', diff --git a/apps/desktop/src/main/menu-locales/en.ts b/apps/desktop/src/main/menu-locales/en.ts index eacd8d7..8f520c9 100644 --- a/apps/desktop/src/main/menu-locales/en.ts +++ b/apps/desktop/src/main/menu-locales/en.ts @@ -220,7 +220,7 @@ export const MENU_EN: MenuLabels = { detailDownloadSmartScreen: 'Windows SmartScreen may show "Unrecognized app" because the installer is not code-signed. Click "More info → Run anyway" to proceed.', detailDownloadGatekeeper: - 'macOS Gatekeeper may block the app on first open because it is not notarised. Right-click the .dmg → Open to bypass.', + 'macOS may block the app on first open because it is not notarised. macOS 15 Sequoia and newer: open System Settings → Privacy & Security and click "Open Anyway". macOS 12–14: right-click the app → Open.', msgDownloadFailed: 'Could not download {name}', detailDownloadFailed: 'Download state: {state}\n\nYou can retry from the GitHub Releases page.', diff --git a/apps/desktop/src/main/menu-locales/es.ts b/apps/desktop/src/main/menu-locales/es.ts index 2f85d58..d3499c5 100644 --- a/apps/desktop/src/main/menu-locales/es.ts +++ b/apps/desktop/src/main/menu-locales/es.ts @@ -222,7 +222,7 @@ export const MENU_ES: MenuLabels = { detailDownloadSmartScreen: 'Windows SmartScreen puede mostrar "Aplicación no reconocida" porque el instalador no está firmado digitalmente. Haz clic en "Más información → Ejecutar de todas formas" para continuar.', detailDownloadGatekeeper: - 'Gatekeeper de macOS puede bloquear la aplicación al abrirla por primera vez porque no está notarizada. Haz clic derecho en el .dmg → Abrir para omitirlo.', + 'macOS puede bloquear la aplicación al abrirla por primera vez porque no está notarizada. macOS 15 Sequoia o posterior: abre Ajustes del Sistema → Privacidad y seguridad y pulsa "Abrir de todos modos". macOS 12–14: clic derecho en la app → Abrir.', msgDownloadFailed: 'No se pudo descargar {name}', detailDownloadFailed: 'Estado de la descarga: {state}\n\nPuedes reintentarlo desde la página de GitHub Releases.', diff --git a/apps/desktop/src/main/menu-locales/fr.ts b/apps/desktop/src/main/menu-locales/fr.ts index 8b0459f..7cf7080 100644 --- a/apps/desktop/src/main/menu-locales/fr.ts +++ b/apps/desktop/src/main/menu-locales/fr.ts @@ -222,7 +222,7 @@ export const MENU_FR: MenuLabels = { detailDownloadSmartScreen: 'Windows SmartScreen peut afficher « Application non reconnue » car le programme d\'installation n\'est pas signé. Cliquez sur « Informations complémentaires → Exécuter quand même » pour continuer.', detailDownloadGatekeeper: - 'Gatekeeper de macOS peut bloquer l\'application à la première ouverture car elle n\'est pas notariée. Clic droit sur le .dmg → Ouvrir pour contourner.', + 'macOS peut bloquer l\'application à la première ouverture car elle n\'est pas notariée. macOS 15 Sequoia et ultérieur : ouvrez Réglages Système → Confidentialité et sécurité puis cliquez sur « Ouvrir quand même ». macOS 12–14 : clic droit sur l\'app → Ouvrir.', msgDownloadFailed: 'Impossible de télécharger {name}', detailDownloadFailed: 'État du téléchargement : {state}\n\nVous pouvez réessayer depuis la page GitHub Releases.', diff --git a/apps/desktop/src/main/menu-locales/hi.ts b/apps/desktop/src/main/menu-locales/hi.ts index daf42e3..c1b6272 100644 --- a/apps/desktop/src/main/menu-locales/hi.ts +++ b/apps/desktop/src/main/menu-locales/hi.ts @@ -222,7 +222,7 @@ export const MENU_HI: MenuLabels = { detailDownloadSmartScreen: 'इंस्टॉलर कोड-साइन्ड नहीं है, इसलिए Windows SmartScreen "अपरिचित ऐप" दिखा सकता है। आगे बढ़ने के लिए "अधिक जानकारी → फिर भी चलाएँ" पर क्लिक करें।', detailDownloadGatekeeper: - 'ऐप नोटराइज़ नहीं है, इसलिए macOS Gatekeeper पहली बार खोलने पर इसे रोक सकता है। बायपास करने के लिए .dmg पर राइट-क्लिक → खोलें चुनें।', + 'ऐप नोटराइज़ नहीं है, इसलिए macOS पहली बार खोलने पर इसे रोक सकता है। macOS 15 Sequoia और नया: System Settings → Privacy & Security खोलें और "Open Anyway" पर क्लिक करें। macOS 12–14: ऐप पर राइट-क्लिक → Open।', msgDownloadFailed: '{name} डाउनलोड नहीं हो सका', detailDownloadFailed: 'डाउनलोड स्थिति: {state}\n\nआप GitHub Releases पृष्ठ से दोबारा कोशिश कर सकते हैं।', diff --git a/apps/desktop/src/main/menu-locales/it.ts b/apps/desktop/src/main/menu-locales/it.ts index 8ad9e91..77c30c3 100644 --- a/apps/desktop/src/main/menu-locales/it.ts +++ b/apps/desktop/src/main/menu-locales/it.ts @@ -222,7 +222,7 @@ export const MENU_IT: MenuLabels = { detailDownloadSmartScreen: 'Windows SmartScreen potrebbe mostrare "App non riconosciuta" perché l\'installer non è firmato digitalmente. Fai clic su "Ulteriori informazioni → Esegui comunque" per procedere.', detailDownloadGatekeeper: - 'Gatekeeper di macOS potrebbe bloccare l\'app alla prima apertura perché non è notarizzata. Clic destro sul .dmg → Apri per aggirare il blocco.', + 'macOS potrebbe bloccare l\'app alla prima apertura perché non è notarizzata. macOS 15 Sequoia o successivo: apri Impostazioni di Sistema → Privacy e sicurezza e fai clic su "Apri comunque". macOS 12–14: clic destro sull\'app → Apri.', msgDownloadFailed: 'Impossibile scaricare {name}', detailDownloadFailed: 'Stato del download: {state}\n\nPuoi riprovare dalla pagina GitHub Releases.', diff --git a/apps/desktop/src/main/menu-locales/ko.ts b/apps/desktop/src/main/menu-locales/ko.ts index a4193bc..9546918 100644 --- a/apps/desktop/src/main/menu-locales/ko.ts +++ b/apps/desktop/src/main/menu-locales/ko.ts @@ -219,7 +219,7 @@ export const MENU_KO: MenuLabels = { detailDownloadSmartScreen: '설치 파일에 코드 서명이 없어 Windows SmartScreen이 "알 수 없는 앱"이라고 표시할 수 있습니다. "추가 정보 → 실행"을 클릭하면 계속됩니다.', detailDownloadGatekeeper: - '앱이 공증되지 않았기 때문에 macOS Gatekeeper가 첫 실행을 차단할 수 있습니다. .dmg 파일을 마우스 오른쪽 버튼으로 클릭 → 열기를 선택하면 우회됩니다.', + '앱이 공증되지 않았기 때문에 macOS가 첫 실행을 차단할 수 있습니다. macOS 15 Sequoia 이상: 시스템 설정 → 개인정보 보호 및 보안에서 "그래도 열기"를 클릭하세요. macOS 12–14: 앱을 마우스 오른쪽 버튼으로 클릭 → 열기.', msgDownloadFailed: '{name}을(를) 다운로드할 수 없습니다', detailDownloadFailed: '다운로드 상태: {state}\n\nGitHub Releases 페이지에서 다시 시도할 수 있습니다.', diff --git a/apps/desktop/src/main/menu-locales/pt-BR.ts b/apps/desktop/src/main/menu-locales/pt-BR.ts index 32866a8..ac8f9d2 100644 --- a/apps/desktop/src/main/menu-locales/pt-BR.ts +++ b/apps/desktop/src/main/menu-locales/pt-BR.ts @@ -222,7 +222,7 @@ export const MENU_PT_BR: MenuLabels = { detailDownloadSmartScreen: 'O Windows SmartScreen pode exibir "Aplicativo não reconhecido" porque o instalador não tem assinatura digital. Clique em "Mais informações → Executar assim mesmo" para continuar.', detailDownloadGatekeeper: - 'O Gatekeeper do macOS pode bloquear o aplicativo na primeira abertura porque ele não é notarizado. Clique com o botão direito no .dmg → Abrir para contornar.', + 'O macOS pode bloquear o aplicativo na primeira abertura porque ele não é notarizado. macOS 15 Sequoia ou mais recente: abra Ajustes do Sistema → Privacidade e Segurança e clique em "Abrir Mesmo Assim". macOS 12–14: clique com o botão direito no app → Abrir.', msgDownloadFailed: 'Não foi possível baixar {name}', detailDownloadFailed: 'Estado do download: {state}\n\nVocê pode tentar novamente na página do GitHub Releases.', diff --git a/apps/desktop/src/main/menu-locales/ru.ts b/apps/desktop/src/main/menu-locales/ru.ts index 8a6fa35..1c66379 100644 --- a/apps/desktop/src/main/menu-locales/ru.ts +++ b/apps/desktop/src/main/menu-locales/ru.ts @@ -222,7 +222,7 @@ export const MENU_RU: MenuLabels = { detailDownloadSmartScreen: 'Windows SmartScreen может показать «Нераспознанное приложение», так как установщик не подписан. Нажмите «Подробнее → Выполнить в любом случае», чтобы продолжить.', detailDownloadGatekeeper: - 'Gatekeeper в macOS может заблокировать приложение при первом открытии, так как оно не нотаризовано. Щёлкните .dmg правой кнопкой → «Открыть», чтобы обойти блокировку.', + 'macOS может заблокировать приложение при первом открытии, так как оно не нотаризовано. macOS 15 Sequoia и новее: откройте «Системные настройки» → «Конфиденциальность и безопасность» и нажмите «Всё равно открыть». macOS 12–14: правый клик по приложению → «Открыть».', msgDownloadFailed: 'Не удалось загрузить {name}', detailDownloadFailed: 'Состояние загрузки: {state}\n\nПовторить попытку можно на странице GitHub Releases.', diff --git a/apps/desktop/src/main/menu-locales/tr.ts b/apps/desktop/src/main/menu-locales/tr.ts index 9c167f6..88cc68a 100644 --- a/apps/desktop/src/main/menu-locales/tr.ts +++ b/apps/desktop/src/main/menu-locales/tr.ts @@ -220,7 +220,7 @@ export const MENU_TR: MenuLabels = { detailDownloadSmartScreen: 'Yükleyici kod imzalı olmadığı için Windows SmartScreen "Tanınmayan uygulama" uyarısı gösterebilir. Devam etmek için "Ek bilgi → Yine de çalıştır"a tıklayın.', detailDownloadGatekeeper: - 'Uygulama notarize edilmediği için macOS Gatekeeper ilk açılışta engelleyebilir. Atlamak için .dmg dosyasına sağ tıklayıp → Aç seçin.', + 'Uygulama notarize edilmediği için macOS ilk açılışta engelleyebilir. macOS 15 Sequoia ve üzeri: Sistem Ayarları → Gizlilik ve Güvenlik bölümünden "Yine de Aç"a tıklayın. macOS 12–14: uygulamaya sağ tıklayıp Aç seçin.', msgDownloadFailed: '{name} indirilemedi', detailDownloadFailed: 'İndirme durumu: {state}\n\nGitHub Releases sayfasından yeniden deneyebilirsiniz.', diff --git a/apps/desktop/src/main/menu-locales/zh-CN.ts b/apps/desktop/src/main/menu-locales/zh-CN.ts index dbe890d..00ea4d0 100644 --- a/apps/desktop/src/main/menu-locales/zh-CN.ts +++ b/apps/desktop/src/main/menu-locales/zh-CN.ts @@ -217,7 +217,7 @@ export const MENU_ZH_CN: MenuLabels = { detailDownloadSmartScreen: '由于安装程序未进行代码签名,Windows SmartScreen 可能提示“无法识别的应用”。点击“更多信息 → 仍要运行”即可继续。', detailDownloadGatekeeper: - '由于应用未经过公证,macOS Gatekeeper 可能在首次打开时拦截它。右键单击 .dmg → 打开,即可绕过。', + '由于应用未经过公证,macOS 可能在首次打开时拦截它。macOS 15 Sequoia 及更新版本:打开"系统设置" → "隐私与安全性",点击"仍要打开"。macOS 12–14:右键单击应用 → 打开。', msgDownloadFailed: '无法下载 {name}', detailDownloadFailed: '下载状态:{state}\n\n您可以在 GitHub Releases 页面重试。', diff --git a/apps/desktop/src/renderer/src/grid/canvas-grid-layout.ts b/apps/desktop/src/renderer/src/grid/canvas-grid-layout.ts index ed18cc2..4e4050b 100644 Binary files a/apps/desktop/src/renderer/src/grid/canvas-grid-layout.ts and b/apps/desktop/src/renderer/src/grid/canvas-grid-layout.ts differ diff --git a/apps/desktop/src/renderer/src/i18n/locales/az.json b/apps/desktop/src/renderer/src/i18n/locales/az.json index 66b546b..98dcf3c 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/az.json +++ b/apps/desktop/src/renderer/src/i18n/locales/az.json @@ -2360,7 +2360,7 @@ "allowAlways": "Bu alətə hər zaman icazə ver", "deny": "Rədd et", "resultChars": "{{n}} simvol", - "placeholder": "məsələn: Hansı səhifələrdə başlıq problemləri ən ağırdır və niyə? (göndərmək üçün Ctrl+Enter)", + "placeholder": "məsələn: Hansı səhifələrdə başlıq problemləri ən ağırdır və niyə? (göndərmək üçün {{mod}}+Enter)", "stop": "Dayandır", "send": "Göndər", "toolCount_one": "{{count}} alət", diff --git a/apps/desktop/src/renderer/src/i18n/locales/en.json b/apps/desktop/src/renderer/src/i18n/locales/en.json index b68201b..9d316a9 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/en.json +++ b/apps/desktop/src/renderer/src/i18n/locales/en.json @@ -2362,7 +2362,7 @@ "allowAlways": "Always allow this tool", "deny": "Deny", "resultChars": "{{n}} chars", - "placeholder": "e.g. Which pages have the worst title problems, and why? (Ctrl+Enter to send)", + "placeholder": "e.g. Which pages have the worst title problems, and why? ({{mod}}+Enter to send)", "stop": "Stop", "send": "Send" } diff --git a/apps/desktop/src/renderer/src/i18n/locales/es.json b/apps/desktop/src/renderer/src/i18n/locales/es.json index 53756f6..805cca2 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/es.json +++ b/apps/desktop/src/renderer/src/i18n/locales/es.json @@ -2404,7 +2404,7 @@ "allowAlways": "Permitir siempre esta herramienta", "deny": "Denegar", "resultChars": "{{n}} caracteres", - "placeholder": "p. ej. ¿Qué páginas tienen los peores problemas de título, y por qué? (Ctrl+Intro para enviar)", + "placeholder": "p. ej. ¿Qué páginas tienen los peores problemas de título, y por qué? ({{mod}}+Intro para enviar)", "stop": "Detener", "send": "Enviar", "toolCount_one": "{{count}} herramienta", diff --git a/apps/desktop/src/renderer/src/i18n/locales/fr.json b/apps/desktop/src/renderer/src/i18n/locales/fr.json index 1d830d3..aaa68c2 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/fr.json +++ b/apps/desktop/src/renderer/src/i18n/locales/fr.json @@ -2404,7 +2404,7 @@ "allowAlways": "Toujours autoriser cet outil", "deny": "Refuser", "resultChars": "{{n}} caractères", - "placeholder": "p. ex. Quelles pages ont les pires problèmes de titre, et pourquoi ? (Ctrl+Entrée pour envoyer)", + "placeholder": "p. ex. Quelles pages ont les pires problèmes de titre, et pourquoi ? ({{mod}}+Entrée pour envoyer)", "stop": "Arrêter", "send": "Envoyer", "toolCount_one": "{{count}} outil", diff --git a/apps/desktop/src/renderer/src/i18n/locales/hi.json b/apps/desktop/src/renderer/src/i18n/locales/hi.json index e1177c5..acfc50f 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/hi.json +++ b/apps/desktop/src/renderer/src/i18n/locales/hi.json @@ -2360,7 +2360,7 @@ "allowAlways": "इस औज़ार को हमेशा अनुमति दें", "deny": "अस्वीकार करें", "resultChars": "{{n}} अक्षर", - "placeholder": "जैसे: किन पृष्ठों में शीर्षक की सबसे गंभीर समस्याएँ हैं और क्यों? (भेजने के लिए Ctrl+Enter)", + "placeholder": "जैसे: किन पृष्ठों में शीर्षक की सबसे गंभीर समस्याएँ हैं और क्यों? (भेजने के लिए {{mod}}+Enter)", "stop": "रोकें", "send": "भेजें", "toolCount_one": "{{count}} औज़ार", diff --git a/apps/desktop/src/renderer/src/i18n/locales/it.json b/apps/desktop/src/renderer/src/i18n/locales/it.json index 13d26fe..d4360bd 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/it.json +++ b/apps/desktop/src/renderer/src/i18n/locales/it.json @@ -2404,7 +2404,7 @@ "allowAlways": "Consenti sempre questo strumento", "deny": "Nega", "resultChars": "{{n}} caratteri", - "placeholder": "es. Quali pagine hanno i peggiori problemi di titolo, e perché? (Ctrl+Invio per inviare)", + "placeholder": "es. Quali pagine hanno i peggiori problemi di titolo, e perché? ({{mod}}+Invio per inviare)", "stop": "Interrompi", "send": "Invia", "toolCount_one": "{{count}} strumento", diff --git a/apps/desktop/src/renderer/src/i18n/locales/ko.json b/apps/desktop/src/renderer/src/i18n/locales/ko.json index d6dca38..3886595 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/ko.json +++ b/apps/desktop/src/renderer/src/i18n/locales/ko.json @@ -2316,7 +2316,7 @@ "allowAlways": "이 도구 항상 허용", "deny": "거부", "resultChars": "{{n}}자", - "placeholder": "예: 제목 문제가 가장 심각한 페이지는 어디이고 왜 그런가요? (Ctrl+Enter로 전송)", + "placeholder": "예: 제목 문제가 가장 심각한 페이지는 어디이고 왜 그런가요? ({{mod}}+Enter로 전송)", "stop": "중지", "send": "보내기", "toolCount_other": "도구 {{count}}개" diff --git a/apps/desktop/src/renderer/src/i18n/locales/pt-BR.json b/apps/desktop/src/renderer/src/i18n/locales/pt-BR.json index 89ba0aa..25f4ec2 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/pt-BR.json +++ b/apps/desktop/src/renderer/src/i18n/locales/pt-BR.json @@ -2404,7 +2404,7 @@ "allowAlways": "Sempre permitir esta ferramenta", "deny": "Negar", "resultChars": "{{n}} caracteres", - "placeholder": "ex.: Quais páginas têm os piores problemas de título, e por quê? (Ctrl+Enter para enviar)", + "placeholder": "ex.: Quais páginas têm os piores problemas de título, e por quê? ({{mod}}+Enter para enviar)", "stop": "Parar", "send": "Enviar", "toolCount_one": "{{count}} ferramenta", diff --git a/apps/desktop/src/renderer/src/i18n/locales/ru.json b/apps/desktop/src/renderer/src/i18n/locales/ru.json index d581e84..e91554a 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/ru.json +++ b/apps/desktop/src/renderer/src/i18n/locales/ru.json @@ -2448,7 +2448,7 @@ "allowAlways": "Всегда разрешать этот инструмент", "deny": "Отклонить", "resultChars": "символов: {{n}}", - "placeholder": "напр. У каких страниц самые серьёзные проблемы с заголовками и почему? (Ctrl+Enter — отправить)", + "placeholder": "напр. У каких страниц самые серьёзные проблемы с заголовками и почему? ({{mod}}+Enter — отправить)", "stop": "Остановить", "send": "Отправить", "toolCount_one": "{{count}} инструмент", diff --git a/apps/desktop/src/renderer/src/i18n/locales/tr.json b/apps/desktop/src/renderer/src/i18n/locales/tr.json index dad080f..b3f1469 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/tr.json +++ b/apps/desktop/src/renderer/src/i18n/locales/tr.json @@ -2362,7 +2362,7 @@ "allowAlways": "Bu araca her zaman izin ver", "deny": "Reddet", "resultChars": "{{n}} karakter", - "placeholder": "ör. Başlık sorunları en kötü olan sayfalar hangileri, neden? (Göndermek için Ctrl+Enter)", + "placeholder": "ör. Başlık sorunları en kötü olan sayfalar hangileri, neden? (Göndermek için {{mod}}+Enter)", "stop": "Durdur", "send": "Gönder" } diff --git a/apps/desktop/src/renderer/src/i18n/locales/zh-CN.json b/apps/desktop/src/renderer/src/i18n/locales/zh-CN.json index ee262bf..1ec8100 100644 --- a/apps/desktop/src/renderer/src/i18n/locales/zh-CN.json +++ b/apps/desktop/src/renderer/src/i18n/locales/zh-CN.json @@ -2316,7 +2316,7 @@ "allowAlways": "始终允许此工具", "deny": "拒绝", "resultChars": "{{n}} 个字符", - "placeholder": "例如:哪些页面的标题问题最严重,为什么?(Ctrl+Enter 发送)", + "placeholder": "例如:哪些页面的标题问题最严重,为什么?({{mod}}+Enter 发送)", "stop": "停止", "send": "发送", "toolCount_other": "{{count}} 个工具" diff --git a/apps/desktop/src/renderer/src/tabs/AssistantTab.tsx b/apps/desktop/src/renderer/src/tabs/AssistantTab.tsx index 95ad977..7afd26b 100644 --- a/apps/desktop/src/renderer/src/tabs/AssistantTab.tsx +++ b/apps/desktop/src/renderer/src/tabs/AssistantTab.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { IS_MAC } from '../utils/clipboard.js'; import { AlertTriangle, Check, @@ -449,7 +450,8 @@ export function AssistantTab() { }} placeholder={t('assistant.placeholder', { defaultValue: - 'e.g. Which pages have the worst title problems, and why? (Ctrl+Enter to send)', + 'e.g. Which pages have the worst title problems, and why? ({{mod}}+Enter to send)', + mod: IS_MAC ? '⌘' : 'Ctrl', })} /> {running ? ( diff --git a/package.json b/package.json index 38d83a9..08c616d 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "type": "module", "engines": { - "node": ">=22.0.0" + "node": ">=22.13.0" }, "workspaces": [ "apps/*",