Transleti Translator — full changelog
=====================================

The readme.txt only carries the most recent releases, because WordPress.org
truncates that section at 5,000 characters. The complete history lives here.

= 1.9.46 =
* New: **works properly alongside caching plugins.** Serving pages from a cache is what makes a site fast, but a translated site has to be careful about which pages get stored. When a page finishes translating, its cached copy is now cleared straight away, so visitors see the new text instead of waiting for the cache to expire on its own.
* Fix: **the "translation in progress" page could be cached and outlive the translation it was waiting for.** A visitor could be shown that holding page for as long as the cache entry lived — up to a week on some setups — with the finished page sitting right behind it. It is now kept out of the cache in all four setups: on its own, and alongside WPML, Polylang or TranslatePress.
* Fix: **the entry page could get stuck in the first visitor's language.** With automatic language detection and a cache in front, the first visitor's redirect could be stored and then handed to everyone else, so a German visitor landed in Spanish. The entry page is no longer cached while detection is switched on.
* Fix: **the "Test connection" button reported a failure when running alongside WPML, Polylang or TranslatePress**, against a translation server that was answering perfectly.

= 1.9.45 =
* New: **four more English variants — Canada, Ireland, New Zealand and Singapore.** Each gets its own address, its own hreflang tag and its own stored translations, so a search engine can serve the right one to each market instead of collapsing them into a single page. Canadian English is treated properly rather than as a copy of British: it takes "colour", "centre", "travelled" and "licence", but keeps "organize", "realize" and "analyze", which is how Canada actually writes.
* Fix: **text your theme or plugins had already translated was being translated a second time.** If a theme ships its own Spanish, German or French wording, that wording was being picked up as if it were your original English and sent to be translated again — so a menu item could drift from the theme's own phrasing into a second-hand version of it, and the same text piled up in the stored translations of every language. Interface text now goes through a single path, whichever theme you use.
* Fix: **currency symbols were being translated.** The currency selector lists every currency in every language, so a bare symbol like "kr" reached the engine with no context and came back as something else entirely — in some languages a whole unrelated word. Symbols and currency abbreviations are now left exactly as they are.
* Fix: **a translation could arrive cut off half-way and still be published.** When a translation contained quotation marks, everything after the first one could be lost, and the shortened text was stored and shown as if it were complete — a headline could end mid-sentence. Incomplete translations are now rejected and translated again.
* Fix: text captured while a page was being served in the wrong language is no longer stored as new source text, which was the other way the problem above could start.

= 1.9.44 =
* Fix: **the whole site could start returning 404 on every page except the front page.** WordPress rebuilds the rewrite rules in .htaccess from your site address, and while a translated page was being served the language ended up written into that file (`RewriteBase /pl/`), so every pretty permalink pointed at a folder that does not exist. Anything that regenerates those rules could trigger it: saving your permalink settings, activating a plugin, or any theme or plugin that refreshes them. Two things made it hard to spot — nothing is written to any log, and the web server keeps serving from the rules it already had in memory, so the site carries on working and only breaks at the next server restart, hours or days later. The language is now kept out of that file whatever regenerates it. **If your site is currently showing 404 on inner pages, open Settings → Permalinks and press Save once after updating; that rewrites the file correctly.**

= 1.9.43 =
* Fix: **picking a language was ignored when automatic visitor-language detection was on.** Someone who deliberately chose English, or opened any address carrying a language prefix, was sent straight back to whatever language their browser happened to be set to — so the language switcher looked broken. Detection now runs only where the visitor has not chosen yet, on the plain address of the site; once a language is in the address, it is respected. Affects sites with "Auto-detect visitor language" enabled.
* Fix: **the language you were reading is now remembered.** Until now the plugin only recorded that it had redirected you once, not which language you ended up on — so anything that dropped you back on the plain address of the site (a currency switcher, a cached link, a plugin sending you home) started guessing from your browser again and could hand you a different language mid-visit. Your choice is now kept for 30 days and wins over the browser setting; genuinely new visitors are still sent to their own language as before.
* Fix: **a few strings could stay stuck in the original language forever.** When the higher-quality engine returned a text almost unchanged — a different quote mark, a decoded character — the plugin took it for a real translation and stored it as final, which then outranked the regular engine so it could never be corrected. On this site it left the cookie-consent notice in English on the Spanish, German, Italian and Brazilian home pages. Long texts that come back unchanged are now handed to the regular engine instead; short ones are unaffected, since a brand, an address or a word that is the same in both languages legitimately comes back as it went in.

= 1.9.42 =
* New: **Connector Mode now supports Polylang.** If your site already runs Polylang, Transleti works as its automatic translation engine — Polylang keeps languages, URLs, the switcher and the stored translations, and Transleti fills them in. Posts, pages, custom post types, categories and tags, SEO titles and descriptions, interface strings and navigation menus are all covered, with the same per-language progress panel and the same "translation in progress" page the other modes use. Detected and enabled automatically.
* New: **your blog index and category, tag, author and date archives are no longer served in the source language.** Until a language had something translated, those pages showed the original text to every visitor — and they are usually the first pages a search engine crawls. They now return the same "translation in progress" page as the rest of the site, and open on their own as soon as the first translation is ready.
* Fix: **buttons and headings written in capitals came back untranslated, or wrong.** The translation engine handles shouted text badly: "READ MORE", "ADD TO CART" and "LEARN MORE" were returned unchanged, and "SEND NOW" came back as an unrelated phrase. Capitalised text is now translated as a normal sentence and shouted back afterwards, so it reads correctly and still looks the way your theme designed it.
* Fix: some common button labels are now guaranteed correct in the languages where the engine got them wrong — a Spanish contact form no longer says "Submit", and a German one no longer says "Bitte" where it should say "Senden". Only the words that were actually mistranslated are overridden, so everything the engine already gets right is left alone.
* Fix: **the submit button and confirmation message of your forms stayed in the source language** even when the rest of the form was translated, because those texts live in the form's settings rather than its content. They are translated now, and support was widened from SureForms to WPForms, Contact Form 7, Forminator and Everest Forms.
* Fix: Contact Form 7 field tags such as `[text* your-name]` were not recognised as code and could be altered by the engine, which breaks the form. They are now protected like any other shortcode.
* Fix: placeholders written in braces — `{cart_total}`, `{{post_title}}` and similar tokens used by themes and page builders — were translated word by word and stopped working. They are now left untouched.
* Fix (WPML): **some posts were never translated, and nothing said so.** A post whose ID happened to match that of an already translated category, tag or comment was skipped by the search for pending work, silently and permanently. The progress bar sitting just short of 100% was the only clue.
* Fix (WPML): the progress bar could show a green 100% on a site where nothing had been translated at all, because posts still waiting to be duplicated were left out of the count. It now reports real progress from the first run.
* Fix: **Elementor pages could appear unstyled the first time they were opened after being translated.** Translating a page invalidates its stylesheet, and Elementor only rebuilt it when someone loaded the page — so the first visitor, often a search engine, got the page without its design. The stylesheet is now rebuilt as soon as the translation is written.
* Fix: switching to Connector Mode left the Standalone background task scheduled, running every minute with nothing to do. It is now removed automatically.

= 1.9.41 =
* Fix: **words in your "Do not translate" list were still being altered in Vietnamese, Thai and Urdu.** The protection worked by hiding each word behind a marker while the sentence was translated, and for those languages the engine rewrote the marker itself, so the word could no longer be put back — brand names came out garbled or replaced by leftover characters. The marker was changed for one those engines never touch, verified across every supported language. Brand names, product names and email addresses now survive in all of them.
* New: the Images tab is now paginated and searchable. Sites with hundreds of discovered images no longer render one endless list: images are shown in pages of ten, with a search box that matches a file name or any part of the URL.
* Fix: saving or deleting an image on the Images tab returned you to the first page, losing your place and your search. It now keeps both.
* Fix: asking for an Images page that no longer exists (after deleting images, or from an old bookmark) showed an empty list with no way back. It now falls to the last available page.
* Fix: the label on primary buttons in the settings screens sat too low inside the button.

= 1.9.40 =
* New: the higher-quality home page translation now also works in Connector Mode. If the plugin runs behind WPML or TranslatePress, your home page, its SEO title and description and your navigation menu get the same premium treatment they already had in Standalone, while the rest of the site keeps using the regular engine. Nothing to configure.
* Fix: **Elementor pages were re-translated but the change never appeared.** Elementor serves a stored copy of the rendered page, so an updated translation stayed invisible until that copy expired. This affected every re-translation in Connector Mode — edit a page, and the new translation was saved correctly and never shown. The stored copy is now cleared whenever a translation is written.
* Fix: short phrases built from small words ("Hi. I am a") were skipped in Elementor content and stayed in the source language, because they were mistaken for code.
* Fix: brand names, people's names and email addresses on the home page are no longer altered. When the translation engine returns a word unchanged because it is the same in the target language, that answer is now kept instead of being replaced by a second, worse attempt — which is what turned menu items like "Contact" into a full sentence.
* Fix: home page translation could start from a page that had not finished loading, right after installing or resetting the site. It now waits for the real page.
* Fix: the monthly premium allowance was charged for the whole home page even when a single line had changed, so editing your headline a few times could use it up without actually translating anything. Only new text counts now.
* Change: the monthly premium allowance is counted in translations rather than in batches, so the ceiling no longer depends on how large your home page is or how many languages you run.
* Change: the free trial has been replaced by a 14-day money-back guarantee, and the plugin no longer contains the code that cleared translations when a trial ended unpaid. Your translations are yours.

= 1.9.39 =
* New: **TranslatePress Connector Mode.** Already using TranslatePress? Keep it in charge of your languages, URLs and stored translations, and let Transleti work as its automatic translation engine. Transleti appears as a translation engine inside TranslatePress and, on top of that, translates your pages in the background instead of waiting for a visitor to open each one — so your site gets translated on its own. Detected and enabled automatically; you can force plain Standalone mode from the Connector tab. WooCommerce shops are supported.
* New: the "translation in progress" page now also covers your blog index and category, tag and author archives, so visitors and search engines never land on a listing that is still in the source language.
* Fix: pages that could never leave the "translation in progress" state. Several checks were waiting for text that can never be translated — SEO templates such as `%%title%% %%sep%% %%sitename%%`, leftover settings from an SEO plugin that is no longer active, and strings the translation layer never records. Those no longer hold a page back, while the SEO title and description are still required before a page goes live.
* Fix: a page or product published after the plugin had already scanned your site could stay untranslated indefinitely, and the progress bar showed 100% because it only counted the pages it knew about. New content is now picked up immediately.
* Fix: the browser tab title is now checked before a page is published in a new language, so pages no longer appear with the title still in the source language.
* Fix: submitting a form on a translated page could hit the "translation in progress" screen instead of being processed.
* Fix: several items were retried forever when their translation could not change (brand names, words that are identical in the target language, URL slugs that WordPress cannot reuse). They are now accepted after a few attempts, which also stops the wasted translation requests they caused.
* Fix: switching between WPML and TranslatePress now leaves no background task behind from the previous plugin, in either direction.
* Fix: premium home translation no longer re-runs when you simply publish a post. Titles listed on the home page (for example a "latest posts" block) are no longer treated as home content, and there is now a monthly cap as a safeguard.

= 1.9.38 =
* New: **WPML Connector Mode.** If WPML is active, the plugin now detects it and works as WPML's automatic translation engine instead of managing languages itself. WPML keeps full control of your languages, URLs, switcher and stored translations, so there is no conflict, no duplicate switcher and no duplicate URLs. It translates posts, pages, custom post types, taxonomy terms, menus, slugs, SEO meta and interface strings into every WPML language. Enabled automatically; you can force plain Standalone mode from the new Connector tab. Sites without WPML are unaffected and keep running exactly as before.
* New: **WooCommerce + WPML support.** With WooCommerce Multilingual & Multicurrency (WCML) installed, products, categories and tags are translated automatically, and each shop page correctly shows only the products for the language being viewed (no more all-languages product lists). A translated product stays blocked with a temporary notice until its title and description are ready, so shoppers never see a half-translated page.
* New: form fields (SureForms) are now translated per language, including placeholder text, instead of falling back to another language.
* Fix: a newly added language no longer stays stuck behind the "translation in progress" page — the readiness check now ignores strings that are handled elsewhere, so the language goes live as soon as its content is done.
* Change: the automatic translation API is now a flat-rate subscription with a 14-day money-back guarantee (the previous 7-day trial has been retired). The plugin remains free and fully usable in manual mode.

= 1.9.37 =
* Fix: corrections made in the visual (in-page) translation editor now reliably apply. Previously, editing some text directly on an already-translated page could save your change against the wrong entry and appear to do nothing; the editor now matches the edit to the correct source string and updates it.

= 1.9.36 =
* Fix: per-language progress now reaches 100% correctly. Entries that were intentionally set aside — untranslatable strings and text detected as belonging to another language — no longer hold the bar below 100%, since there is no further work to do on them.
* Fix: extended the cross-language cleanup so text already stored in a wrong language (including entries produced by earlier versions) is removed even after it was translated, across both the page and interface string stores. Legitimate labels that mix a few foreign characters with your own language (for example a place name written as "Beijing / 北京") are always kept.

= 1.9.35 =
* Fix: much stronger protection against one language's text mixing into another. Text that your theme or plugin already displayed in the visitor's language is now reliably detected and never re-translated or copied to other languages — including European (Latin-script) languages that earlier checks could not catch — so it simply shows in that language as intended. This runs automatically and cleans up any entries that had already accumulated.
* Fix: a translation that comes back with characters from the wrong writing system (for example a few stray characters from another alphabet mixed into an otherwise correct result) is now rejected and translated again cleanly, instead of being stored. Your original content is never modified.
* Fix: brand names such as "WordPress" and "WooCommerce" are now kept intact in every language, including scripts where some engines previously mangled or truncated them (e.g. Thai dropping the final letter). A protected brand that a low-resource model corrupts mid-translation is now recovered automatically instead of leaking a placeholder.
* New: the language switcher's country flags now load a bundled flag font, so they render on platforms whose system fonts omit flag emoji (Windows and some Linux) instead of showing empty boxes or letters. Scoped to flag characters only; can be turned off with the `transleti_load_flag_font` filter.

= 1.9.34 =
* New: pages translated into a language whose script your theme font doesn't cover (Hindi and other Indic scripts, Chinese, Japanese, Korean, Arabic, Hebrew, Thai and more) now automatically load a matching high-quality webfont, so the text is fully readable instead of showing empty boxes. Latin, Greek and Cyrillic languages are left untouched, icon fonts are preserved, and it can be turned off with the `transleti_load_webfonts` filter.

= 1.9.33 =
* Fix: prevents translations from one language leaking into another. When a theme or plugin displayed text that was already in a different language (for example a native navigation menu or shop labels), earlier versions could mistake it for source text and copy it into every language, creating a few wrong or foreign-looking entries and wasting translation quota. Such text is now ignored when detected and never copied between languages, and any entries that had already accumulated are cleared automatically when you update. Your original content is never modified.

= 1.9.32 =
* Change: automatic translations created during the 7-day free trial are now cleared if the trial ends without starting a subscription. This only affects trials that end unpaid — once you have made at least one payment, your translations always stay in your own site and keep working for free, even after a cancellation (see the "Who owns the translations" FAQ). Your original content is never modified.

= 1.9.31 =
* Fix: a page whose SEO title or description can't be machine-translated into a particular language (e.g. a brand-only or malformed title) no longer retries forever or stays flagged as "blocked" — after a few attempts the page is finalized with the source-language text for that string, the same graceful give-up already used for individual dictionary entries.
* Fix: the "translations blocked for over an hour" notice and its email digest no longer list internal, non-public post types (e.g. WooCommerce support-ticket messages); stale entries are cleaned up automatically and remaining ones are labelled clearly even when the item has no title.

= 1.9.30 =
* Improvement: when the higher-quality home and SEO translation is temporarily unavailable, the translated home page is now held on the friendly "translation pending" page instead of being shown at lower machine-translation quality — so visitors and search engines never see a lower-quality home. It restores automatically as soon as the higher-quality translation is ready.

= 1.9.29 =
* Improvement: higher-quality automatic translation of the home page and its SEO fields (page title, meta description and navigation menus), for a stronger first impression and better search-engine and social previews on translated pages.
* Fix: secondary languages could get stuck below 100%. Strings discovered while rendering one language are now propagated to every active language automatically, so low-traffic languages no longer lag behind; the per-language progress indicator is also more robust when one language happens to carry extra strings.
* Fix: internal, non-public post types (for example WooCommerce support-ticket messages) are no longer reported as "stuck" translations or held back by the "translation pending" page — only genuinely public pages are gated and crawled.
* Fix: the logged-in admin toolbar is no longer captured as translatable content, keeping per-language string counts and progress accurate.
* Fix: navigation menus now translate from the correct source menu, preventing already-translated labels from being re-queued into the wrong language.
* Fix: duplicate dictionary entries for the same source string are de-duplicated, so a stray automatic translation can no longer shadow a better one (menus and home strings render correctly).

= 1.9.28 =
* Fix: one-time recovery of SEO titles, descriptions and other strings that were left in the source language after an earlier translation-engine outage. Before 1.9.27 a connection timeout could permanently flag pending strings as failed; those rows stayed excluded from the queue even after the engine recovered. On upgrade the plugin now requeues them automatically (runs once), so previously stuck translations complete on the next translation cycles. No action required.
* Fix: API keys, JWT auth tokens and hash/digest strings (e.g. on account or API-key pages) are no longer queued for translation. They were never translatable, so the engine kept rejecting them and they piled up in the pending list. They are now detected and stored as-is, keeping the queue and the "pending strings" notice clean. Guarded so real text — including non-Latin scripts — is never affected.

= 1.9.27 =
* Fix: on translated pages the `<title>` could render empty and `<html lang>` could stay on the default language when an SEO plugin (e.g. Rank Math) built the document head before the language context was active. The output buffer now rewrites `<html lang>` to the current language and rebuilds an empty/missing `<title>` from the translated title, so search engines never see a blank title on a translated page.
* Fix: fatal TypeError in the Rank Math sitemap integration (`rankmath_sitemap_url()` received an array, not a string) that could break the Rank Math sitemap and its hreflang alternates. The handler now accepts both, matching the SEOPress integration.
* Fix: the 503 "translation pending" page could fatal (and serve a raw 503 error instead of the friendly template) when triggered from the render layer, because it started a new output buffer inside the page output buffer. The pending page is now built as a string and renders correctly in every context.
* Fix: a temporary translation-engine outage (connection timeout or server error) no longer counts against a string's retry limit. Previously a few minutes of downtime could permanently flag pending strings as failed, leaving them in the source language even after the engine recovered. Transient failures are now retried indefinitely; only genuine per-string rejections count toward the limit.
* Fix: hardened the background translator against a missing source-language setting that could throw a fatal error on every cron run and silently stop all translation; it now falls back to the default source language.

= 1.9.26 =
* Doc: improved the WordPress.org listing title, tags and short description for better discoverability (no functional changes).

= 1.9.25 =
* SEO: the real composite page title (`Post - Site - Tagline`), og:title and meta description are now fetched from the rendered page and translated proactively in the background batch, before the first visit — so search engines and social previews never see an English title on a translated page. A render-side gate holds a secondary-language page at 503 while its `<title>` is still untranslated, with a fail-open grace period so a title that never translates can't strand the page.
* Fix: homepage redirect loop on sites served over both www and non-www. Automatic language detection now reads the visitor's current language from a host-agnostic signal instead of the rewritten request URI, so `www.` secondary-language pages no longer bounce in a redirect loop.
* Fix: cookie-consent notice mistranslated in Urdu (ur) — the translation engine rendered "cookies" as پکوان ("food dishes"). A per-language term lock now forces the correct word (کوکیز) so the notice translates correctly, with a safeguard that falls back cleanly if the engine corrupts the protected term.

= 1.9.24 =
* UI: fixed vertical alignment of dashicons inside admin buttons on the plugin settings page.
* Doc: refreshed screenshots and captions for every plugin tab on the WordPress.org listing.

= 1.9.23 =
* Fix: WooCommerce Blocks cart / checkout — product short descriptions now translate correctly. WC Blocks pre-renders the cart server-side via a hydration path that bypasses the REST response filter; the plugin now hooks `woocommerce_hydration_request_after_callbacks` so hydrated responses are translated alongside live Store API calls.
* Fix: WooCommerce Blocks Summary truncation — product short descriptions in cart / checkout are no longer clipped to ~15 characters on locales whose WordPress translation breaks the `Word count type. Do not translate!` hint (e.g. pt_BR returning "palavras"). Both the server-side `_x()` value and the client-side `wp.i18n` translation file entry are now sanitised back to a valid value.
* Fix: Store API translation — WC Blocks calls `/wc/store/v1/*` without a language URL prefix from the localized React bundle. The plugin now falls back to `HTTP_REFERER` to detect the visitor's current language so Store API responses match the page they were requested from.
* Doc: readme repositioned to make clear the plugin is free and fully usable in manual mode; the Transleti API (automatic translation) is now described as optional.
* Doc: trial messaging updated to reflect that the 7-day API trial no longer requires a card or payment method. After the trial, the API stops automatically unless you subscribe and the plugin keeps working in manual mode for free.

= 1.9.22 =
* Fix: per-language progress no longer gets stuck below 100% on mixed-script content. Dictionary rows now retire after 3 failed translation attempts (mirroring the existing gettext mechanism), so untranslatable strings stop blocking the percentage indicator.

= 1.9.21 =
* Fix: visual editor — clicks on theme menu items (GeneratePress, Astra, Divi, Elementor Pro mega-menus, etc.) now open the translation panel reliably. Listener moved to `window` capture + `pointerdown` so theme menu togglers that call `stopPropagation()` can no longer swallow the selection click.

= 1.9.20 =
* Fix: visual editor now detects menu items in Hebrew, Greek, Hindi, Thai, Georgian and Armenian (menu translation now bails during preview so the renderer can add translatable markers regardless of script).
* Fix: visual editor — clicking on a string with duplicated text on the same page (e.g. a heading and a menu link with the same label) now opens the translation panel for the selected occurrence.
* Fix: ignore RSS feed `pubDate`/`lastBuildDate` strings in the "stuck translations" notice.
* Improvement: automatic cleanup of orphan sentence segments left over from long-paragraph batching.

= 1.9.x =
* New: visual translation editor — click any text in the rendered front-end and edit its translation in a live side panel. Works on menus, buttons, image alts and theme strings.
* New: 49+ languages supported via the Transleti translation API.
* New: HTML entity and shortcode protection across all translation entry points.
* New: "Do not translate" word list (brand names, product codes, acronyms).
* New: SEO meta translation for SEOPress, Yoast, AIOSEO and RankMath.
* Improvement: manual edit protection — your manual fixes survive re-translation cycles.
* Improvement: parallel translation across all target languages.

= 1.4.4 =
* Feature: drag-and-drop reordering of active languages in the Languages tab (also affects the language switcher order).
* i18n: added 503 "pending translation" page strings for 16 additional languages (Lithuanian, Albanian, Azerbaijani, Basque, Bengali, Catalan, Esperanto, Galician, Irish, Kyrgyz, Latvian, Malay, Persian, Slovenian, Tagalog, Urdu).

= 1.4.3 =
* Compliance: wrapped `language_has_translations()` and `find_original_slug()` direct `$wpdb` probes with `wp_cache_get` / `wp_cache_set` (5-minute TTL, `transleti` cache group).
* Compliance: documented every legitimate direct DB call against core meta tables with `phpcs:ignore` comments explaining why the existing core API can't replace them.

= 1.4.2 =
* Hardening: escaped flag-emoji output in the language switcher shortcode (`render_switcher`, `render_language_list`) via `esc_html()`.
* Hardening: every `ob_start()` callback buffer is now explicitly closed via a paired `ob_end_flush()` registered through `register_shutdown_function()` in the same function scope (frontend page buffer, AJAX response buffer, sitemap xhtml-namespace buffer).

= 1.2.1 =
* Hardening: replaced `fopen`/`fread`/`fclose` with `WP_Filesystem::get_contents()` for the diagnostic log reader.
* Hardening: switched all custom-table existence checks to `$wpdb->prepare( 'SHOW TABLES LIKE %s', ... )` (no more single-quote interpolation).
* Hardening: switched several `$_POST` reads on HTML-bearing fields to `wp_kses_post( wp_unslash( ... ) )`; integer reads now use `absint( wp_unslash( ... ) )`.
* Hardening: added explicit nonce checks (`check_admin_referer`/`wp_nonce_url`) to the string-export download link.
* Compliance: removed redundant `load_plugin_textdomain()` call (WP 4.6+ auto-loads translations from the WP.org directory).
* Compliance: documented every legitimate direct-DB query (custom plugin tables) with localised `phpcs:disable`/`enable` blocks.

= 1.2.0 =
* Compliance: removed comparative/superlative marketing phrasing from the readme.
* Compliance: extracted inline `<script>` blocks to enqueued JS files (language switcher, editor preview).
* Hardening: all `$_GET` / `$_POST` / `$_SERVER` / `$_COOKIE` reads now go through `wp_unslash()` plus a context-appropriate sanitiser (`sanitize_text_field`, `esc_url_raw`).
* Hardening: AJAX nonce check in `ajax_empty_language()` now unslashes and sanitises the nonce before passing it to `wp_verify_nonce()`.

= 1.0.6 =
* Removed redundant `load_plugin_textdomain()` call — translations are now auto-loaded by WordPress 4.6+ for plugins hosted in the directory.
* Switched front-end language redirect from `wp_redirect()` to `wp_safe_redirect()` for additional protection against open-redirect attempts.
* Defense-in-depth: explicit allowlist validation for the language storage key before SQL identifier interpolation in the cache-clearing endpoint.
* Packaging: included a `.pot` template in `languages/` so the Domain Path folder is present in the distributed package.

= 1.0.5 =
* New: separate `<url>` entry per language in the sitemap, matching Google's documented hreflang format.
* New: AIOSEO sitemap compatibility (`aioseo_sitemap_post`/`_term`/`_archive_entry`/`_addl_pages` filters).
* Fix: `xmlns:xhtml` namespace now declared in `<urlset>` so `<xhtml:link>` alternates are valid XML.
* Fix: `/sitemap.xml` no longer redirects to `/en/sitemap.xml` when `add_subdirectory_to_default=yes`.
* Fix: sitemap `<loc>` URLs now use the canonical default-language URL instead of the redirecting unprefixed permalink.
* Fix: HTML head and sitemap hreflang codes now match exactly for bidirectional confirmation.

= 1.0.4 =
* Parallel translation crawling — O(1) in number of languages.
* Render substitution fixes for raw `%1$s` placeholders and `[cookie_*]` shortcodes leaking to public pages.

= 1.0.3 =
* HTML entity and shortcode protection in all translation entry points.
* "Do Not Translate" word list.
* Manual edit protection improvements.
