=== TranslateWP ===
Contributors: translatewp
Tags: translation, multilingual, language, localization, AI translation
Requires at least: 6.2
Tested up to: 6.7
Requires PHP: 7.4
Stable tag: 0.3.8
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Modern, LLM-first WordPress translation plugin. Translates content and gettext strings using AI-powered engines and WordPress native filters.

== Description ==

TranslateWP is a modern WordPress translation plugin that uses large language models (LLMs) to translate your site content automatically.

**Key Features:**

* AI-powered translation using Gemini, OpenAI, or any OpenAI-compatible engine
* Visual click-to-translate editor on the frontend
* Translates post content, menus, widgets, gettext strings, and theme/plugin text
* Language switcher with multiple display options: floating widget, menu item, shortcode
* Individual language menu items for full control over navigation
* Output buffer catch-all for theme strings and dynamic content
* Frontend crawl for automatic string discovery
* SEO-friendly URL structure with language prefixes

**Language Switcher Options:**

* Floating widget (configurable position and style)
* Menu integration — dropdown switcher or individual language links
* Shortcode — embed anywhere with `[language-switcher]`
* Show opposite language mode for bilingual sites

**Supported Engines:**

* Google Gemini
* OpenAI (GPT-4, GPT-3.5)
* Any OpenAI-compatible API endpoint

== Installation ==

1. Upload the `translatewp` folder to `/wp-content/plugins/`
2. Activate the plugin through the Plugins menu in WordPress
3. Go to TranslateWP settings and configure your languages
4. Add your AI engine API key under the Engine settings
5. Add the language switcher to your menu via Appearance > Menus

== Frequently Asked Questions ==

= How do I add a language switcher to my menu? =

Go to Appearance > Menus. You will see a "TranslateWP" meta box on the left side. You can add either a "Language Switcher" dropdown or individual language items to your menu.

= Can I add individual languages as menu items? =

Yes. In the TranslateWP meta box under Appearance > Menus, each active language is listed as a separate checkbox. Check the languages you want and click "Add to Menu". Each becomes a direct link to that language version of the current page.

= What AI engines are supported? =

TranslateWP supports Google Gemini, OpenAI, and any OpenAI-compatible API endpoint. Configure your preferred engine and API key in the plugin settings.

= Does it work with Elementor? =

Yes. TranslateWP translates Elementor content through WordPress native filters and an output buffer catch-all for dynamic content.

= How do I exclude content from translation? =

Add the `twp-no-translate` attribute to any HTML element to prevent both text translation and URL rewriting inside it:

`<div twp-no-translate>This text will not be translated</div>`

You can also use the standard `notranslate` CSS class to prevent text translation (URLs will still be rewritten):

`<span class="notranslate">Keep this as-is</span>`

Both work with any HTML element and can be added via Elementor's Custom Attributes field, theme templates, or custom HTML widgets.

== Changelog ==

= 0.3.8 =
* Security/Stability: the front-end output buffer now skips non-HTML responses (feeds, WP sitemap, embeds, REST/AJAX/JSON) so they can no longer be corrupted or cached as HTML
* Stability: the HTML translation and discovery buffers are now fatal-safe — any error degrades to the untranslated page instead of white-screening the site
* Security: the engine API key is no longer autoloaded into memory on every request; a one-time migration flips existing installs (uses wp_set_option_autoload() on WP 6.6+)
* Privacy: anonymous usage reporting (contribute_logs) is now opt-in (off by default)
* Security: language-switcher custom colors are sanitized before being written into inline CSS (prevents stored CSS injection)
* Security: internal loopback crawls now verify TLS by default (filterable via twp_loopback_sslverify); OpenAI-compatible engine requests reject unsafe URLs unless TWP_ALLOW_LOCAL_ENGINES is defined
* Performance: removed redundant double-processing of content images (the output-buffer DOM pass already covers them); daily character-limit checks are now cached; auto-translate-on-publish honors a per-tick time budget
* Fix: background translate-job cursor no longer rewinds over already-processed rows
* Fix: PHP 8.1 trim(null) deprecation in the locale path; editor/admin-bar URLs are built from home_url() instead of the raw Host header

= 0.3.7 =
* Performance: Translate Frontend is 5-10x faster — processes 10 crawl URLs per tick instead of 1, and loops translation batches within the same tick until time limit (25s)
* Performance: crawl timeout reduced from 30s to 15s per URL
* Performance: added batch mode (begin_batch/end_batch) to job translation saves
* Visual editor: multi-language translation panel — one textarea per target language, Save All writes all at once
* Visual editor: light theme default with dark mode toggle (persisted in localStorage)

= 0.3.6 =
* New iframe-based visual editor — sidebar is fully isolated from theme CSS, site loads in an iframe
* Vue 3 reactive sidebar with Composition API — string list, search, language switching, save/discard
* PHP-side string detection — stamps `data-twp-id` attributes during output buffer translation (zero overhead on normal pages)
* Hover pencil icon on translatable elements in iframe preview
* Keyboard shortcuts (Ctrl+S to save, Escape to deselect) work in both sidebar and iframe
* Batch translation loading — fetches all translations for visible strings in a single REST call
* Live preview — saved translations update immediately in the iframe without reload

= 0.3.5 =
* Performance: deferred cache flush during batch translation — 1 flush instead of N per batch (major speedup for auto-translate)
* Performance: added missing database index on gettext translations (language_id, status)

= 0.3.4 =
* Fixed media translations not matching protocol-relative image URLs (`//domain.com/...`) — hash now normalized to match `https://` form used when saving

= 0.3.3 =
* Added Smart Slider 3 compatibility — `data-href` slide links now rewritten with language prefix
* Added `aria-label` to translatable attributes for accessibility text (slider bullets, navigation elements)

= 0.3.2 =
* Visual editor now opens on the actual page language instead of always redirecting to the default language
* Language dropdown shows all languages and pre-selects the current page language
* Switching language in the dropdown navigates to that language version of the page
* Single-language sites no longer show a redundant "All languages" option

= 0.3.1 =
* Fixed media translations not appearing on the frontend — image replacement was only applied inside `the_content`, not the full page output buffer
* Media translations now replace `<img src>`, `srcset`, and CSS `background-image` URLs across the entire page via DOM-based output buffer
* Added dedicated "Translate Media" admin submenu for quick access to media translation management

= 0.3.0 =
* Added "Translate Page" link in the WordPress admin bar for one-click access to the visual editor on any frontend page
* Link only visible to admins, hidden when already in the visual editor

= 0.2.9 =
* Fixed infinite LLM re-translation loop — orphan HTML tags like `</value>` were sent to the engine every page visit, sanitized to empty by `wp_kses_post`, and re-sent indefinitely
* Added orphan HTML tag detection to `is_non_translatable()` — fragments like `</value>`, `<br>`, `</div>` are now skipped during string discovery
* Added safety net in `save_translation()` — if `wp_kses_post` strips a translation to empty, the raw text is kept to prevent re-translation loops
* Cleaned up translation pollution — removed foreign-language text (German, French, English, Danish) incorrectly stored as originals from previously-active languages

= 0.2.8 =
* Fixed shortcode language switcher "show opposite language" link not working — was rendering a button with JS navigation that broke when multiple switchers existed on the page
* Opposite language mode now renders a proper `<a>` link instead of a button with inline script
* Fixed opposite language link being rewritten by the output buffer URL rewriter (e.g. BG link on /es/ page incorrectly pointed to /es/ instead of /)
* Added `twp-no-translate` protection to all language switcher dropdown links to prevent URL rewriting

= 0.2.7 =
* Unified language switcher design settings across all three types (Floating, Shortcode, Menu Item)
* Shortcode and Menu Item switchers now have the same design options as Floating: colors, hover colors, border width, per-corner border radius, flag size
* Added "Show opposite language" option to the Shortcode switcher
* Each switcher type now has its own independent show-opposite setting
* Removed `/no_think` directive from OpenAI-compatible engine prompt

= 0.2.6 =
* Fixed cancel race condition — cancelling a job during an in-flight API call now works correctly
* Previously, cancelling while waiting for an API response would be overwritten when the response returned

= 0.2.5 =
* Added configurable Engine Timeout setting (default 120s, range 30–300s) for slow or self-hosted models
* Added batch-in-progress lock to prevent overlapping API calls during background translation jobs
* Added `/no_think` directive for OpenAI-compatible engines to disable reasoning mode (Qwen, DeepSeek)
* Added `<think>` tag stripping from reasoning model responses to prevent translation parse failures
* Background jobs now wait for the current API call to complete before sending the next batch

= 0.2.4 =
* Fixed Translate Frontend crawl timeout errors (cURL error 28) on sites with many languages
* Crawl now only fetches default language pages for string discovery — no longer fetches every language URL
* Reduced crawl timeout from 120s to 30s per page
* Eliminates N×L HTTP requests (pages × languages), making crawl 5-10x faster
* Translation of discovered strings handled by the batch translate phase after crawl

= 0.2.3 =
* Fixed string discovery broken on default language pages after v0.2.1 performance optimization
* Added discovery-only output buffer for default language — scans HTML for translatable strings without any translation overhead
* Translate Frontend crawl now correctly discovers new strings on first pass
* Discovery covers text nodes and translatable attributes (alt, title, placeholder, meta description)

= 0.2.2 =
* Added menu auto-add toggle — enable or disable automatic language switcher injection into menus
* Added menu location picker — choose which theme menu locations display the language switcher
* Changed plugin author to INI Software (inisoftware.com)

= 0.2.1 =
* Major performance optimization — 3.5x faster translated pages (1.24s → 0.35s TTFB)
* Replaced 14-pass regex translation with single-pass DOM parser (simple_html_dom)
* DOM-based URL rewriting replaces 6 regex passes
* Added reverse lookup map to eliminate double-processing of already-translated strings
* Added page-level HTML cache for sites with persistent object cache (Redis/Memcached)
* Lightweight single-regex pass for WP filter hooks instead of full DOM parse
* Bundled simple_html_dom library (MIT licensed) with TranslateWP namespace isolation

= 0.2.0 =
* Added WordPress object cache integration for translations and gettext lookups
* Added API auto-translate on publish — automatically translates new/updated posts via background API call
* Added debug mode with detailed logging for troubleshooting
* Added crawl + translate workflow — discover and translate all frontend strings in one operation
* Improved batch translation with configurable batch size and daily character limit

= 0.1.9 =
* Added translation coverage percentage badges per language next to the Translate Frontend button
* Fixed double-prefixing bug where language switcher URLs got nested prefixes (e.g. /en/fr/)
* Added `twp-no-translate` HTML attribute to exclude elements from translation and URL rewriting
* Added `notranslate` CSS class support to exclude elements from text translation
* Badges auto-refresh after Translate Frontend, Translate All, and Delete All operations

= 0.1.8 =
* Fixed menu item URLs not including language prefix when browsing in non-default language
* Added output buffer URL rewriting for all internal links (Elementor widgets, theme links, logo links)
* Relative URLs (e.g. href="/") and absolute URLs are now rewritten to include the active language prefix
* Skips wp-admin, wp-content, wp-includes, and wp-json paths

= 0.1.7 =
* Added individual language menu items — add specific languages as separate menu items in Appearance > Menus
* Each language item links directly to that language version of the current page
* Active language gets `current-language` CSS class for custom styling
* Fixed language switcher dropdown not inheriting theme width (added `menu-item-has-children` class)
* Fixed individual language sub-menu items not filling full dropdown width
* Fixed language labels being incorrectly translated by output buffer (added `notranslate` protection)

= 0.1.6 =
* Fixed inline auto-translate not reporting stats to TWP server

= 0.1.5 =
* Added "Show opposite language" option for menu item switcher (single link mode for bilingual sites)

= 0.1.4 =
* Added frontend crawl feature for automatic string discovery
* Fixed empty translations not being handled correctly
* Improved content filter for better string detection

= 0.1.3 =
* Security audit: fixed XSS, SSRF, SQL injection vulnerabilities
* Improved input sanitization across all endpoints

= 0.1.2 =
* Fixed batch translation misalignment bug in all engines

= 0.1.0 =
* Initial release
* AI-powered translation with Gemini, OpenAI, and OpenAI-compatible engines
* Visual click-to-translate editor
* Language switcher: floating widget, menu dropdown, shortcode
* REST API for translations and auto-translate
* Gettext string translation support

== Upgrade Notice ==

= 0.1.9 =
Adds translation coverage badges, fixes double-prefixing of language URLs, and adds twp-no-translate attribute for excluding content from translation.

= 0.1.8 =
Fixes internal URLs not staying in the active language. Menu links, logo links, and Elementor widget URLs now correctly include the language prefix.
