VulgatAI — Full changelog For each release, see the entries below. The latest version is always at the top. The current release notes also live in readme.txt under "== Changelog ==". = 3.1.2 = * Fix (Polylang): translating a post that already had a translation left the new one out of the group and unlinked the one that was there. It happened when translating into a second language, when the post already had a translation made by hand, and through Polylang's "+" button. Every translation now stays linked (an original that has no language in Polylang still cannot be linked, as before). Links that were already lost are not restored on their own: a translation VulgatAI made is linked again the next time you translate that post, and one made by hand has to be linked again in Polylang's Languages box. * Fix (Polylang): a new translation into a language other than the site's default one could keep its categories and tags in the default language, so an English post ended up filed under the Spanish category. Translating it again corrected it. The translation now gets its language before its terms. * Fix (WPML): a translation took the categories and tags of its original as they were, in the language of the original, and kept them after translating again. It now takes the translation of each term. * Fix: a term that has no translation in the target language is no longer given to the translation. Until now it stayed there in the language of the original, and with Polylang translating the post again created a copy of the term with the same untranslated name. A translation left with no category gets the default category of its language, when that language has one. To carry a term over, translate it first in Polylang or WPML. * Fix: a translation that already existed and VulgatAI had not created (written by hand, or made by another tool) went unseen. VulgatAI created a second post, which took its place: with Polylang the first one was unlinked, and with WPML it lost its language. VulgatAI now stops before sending anything to the AI and leaves that translation as it was. The post counts as failed and the reason says that the translation exists: it shows in the editor panel, next to the post in the list when it was translated in the background, and always in the activity log. A translation that was already half done when the plugin is updated stops later, when it is about to be saved. To replace such a translation with an AI one, open it and use the VulgatAI panel there, which asks first. * Fix (WPML): WPML kept no record of a translation made by VulgatAI, so it marked it as needing an update the first time the original was saved, even with no changes. VulgatAI now has WPML record it the way it records one written in the WordPress editor, by whoever ran it, which for an administrator includes WPML listing them as a translator of that pair of languages. WPML then marks the translation only when the original really changes. Translations made before get their record the next time they are translated or saved. * Fix (WPML): on a site where WPML's code for a language is longer than the one set in VulgatAI ("en-us" and "en"), translations and imports were filed under a language the site does not have, and a translation from an original in such a language recorded the wrong source language. VulgatAI now uses WPML's code whenever exactly one of its languages matches. * Fix: with a target language that Polylang or WPML does not have, the translation was created anyway, with no language in Polylang and under an unknown one in WPML. VulgatAI now stops before translating and says which language is missing. A language that WPML keeps hidden counts as one it has. Imported posts are not checked. = 3.1.1 = * Fix (security): the title and excerpt of an imported post were saved without any cleanup. A title written at the source as text that looks like HTML arrived here as real markup and ran on the published post, an excerpt kept any script it carried, and the title of an imported image had the same problem when an administrator ran the import. Anyone able to publish on a source you import from could use it. A title now arrives as the plain text the source showed and is stored so that it stays text (one with bold or italics at the source arrives without them), an excerpt keeps only the HTML allowed in posts, and the same cleanup applies to the title and excerpt the AI writes when it translates, which is why translating one of your own posts now sends its title as plain text too. After the update, the titles and excerpts already saved get the same cleanup once, in the background: only posts and images VulgatAI created are looked at (not those in the trash), and only a title with markup, or an excerpt that the HTML cleanup WordPress applies to posts would change, is rewritten. That includes an excerpt with a bare ampersand, which the cleanup encodes. A rewritten post gets a new modified date. * Fix: updating an imported post from its source (Update or Re-import in the source content list, Re-translate in the editor, or the scheduled check set to overwrite with the source) gave it again the status chosen for new imports, whatever status it had. A post imported as a draft or pending and then published by hand went off the site, and with Published it went the other way: a post hidden by hand as a draft or private went back online. An update now brings the content over and leaves the status alone; the import status only applies to new posts. A scheduled post also keeps its date, since the source date, already past, would have published it at once. * Fix: translating an original again turned its translation back into a draft, even a published one. It now keeps its status. * Fix: an update could also move a published post to a new address. The slug comes from the source, or from the translated title, so it can change with any edit or new translation, and WordPress only redirects the old address of a post that stays published and is not a page: the old one answered 404, and the post came back at a different address once published again. A published, scheduled or private post now keeps its slug on every update; drafts and pending posts still follow the source. If an update already took a post offline, publish it again, and if its address changed, put the previous slug back in the editor. * Fix: imports and translations lost every backslash, because WordPress removes them from data that is not slashed first. A code sample such as echo "a\nb" arrived as echo "anb", a pattern such as /\d+/ as /d+/ and a path such as C:\Users as C:Users, in the content, in the title and in the title of imported images. They now arrive intact, except in some WooCommerce products, which WooCommerce saves again without them; that case is left for a later version. A post imported before keeps them missing until it is updated from its source. = 3.1.0 = * New: a Terms tab maps the terms of each source: where each one lands on this site and how that was decided (assigned by you, chosen by the AI, matched by name, taken from the glossary, created by VulgatAI, ignored, or not decided yet), with filters (All, To review, Automatic, Assigned by you, Ignored, Undecided), search, a taxonomy filter, bulk actions (Assign to..., Mark as reviewed, Ignore) and the posts imported with each term. A dot in the side index flags a source with decisions waiting, and a term can be decided before it ever arrives. * New: reassigning a term that VulgatAI created merges it into the term you chose, and the same "Merge into..." bulk action is now available on the Categories and Tags screens (and any other taxonomy screen) for any term. Both ask for confirmation first, listing every source term and source involved, the posts that move, and the archive URL that stops existing; the surviving term keeps every mapping that pointed at the one it absorbed. * New: imported terms are now matched with the ones you already have. VulgatAI checks, in order, your own assignment, your fixed translations, and a term with the same name or slug; only then does it ask the AI, which sees your existing terms (all categories, or similar candidates for tags) and picks the equivalent, translating and creating a new term only when none fits. This is decided once per term, and each source and taxonomy can instead be set to always translate and create (how it worked before), use only existing terms, or always file into one chosen term. * New: the glossary now has two lists: "Do not translate" (the previous glossary, now editable as rows) and "Fixed translations" per target language, pairing an original term with the exact translation it must always get (the AI still adjusts number and capitalization). The fidelity report flags a translation that did not use one, and the flag clears once the post is corrected and saved. Both lists also help name incoming terms during import. * New: Translate with AI is a floating window you can move and resize, opened from a link in the admin bar on any admin screen (just an icon on small screens) or from the command palette, so it also works inside the full-screen block editor. It does not dim the page or close on an outside click, remembers its position and size per person, and has a ⇄ button between the two boxes that translates, plus a "Copy translation" button; it replaces the old "Translate text" tab, and its menu entry now opens this window. Administrators can always use it, editors can by default, and you choose which other roles get access from the Dashboard tab; everyone other than administrators and editors gets a daily character allowance, and only administrators can pick a different model. * New: automatic import, per source and in general, is now two independent switches: "Bring in new posts" and "Keep the imported posts up to date" (with "keep my edits" or "overwrite with the source" for posts you changed here). With both off, a source does nothing on its own and is imported by hand; they replace the old "Never (manual only)" interval and the "Import once; never update" option, and the frequency and per-check limit are dimmed rather than hidden when neither switch is on. New posts only come in automatically once published on or after a start date, which defaults to the day a new source is added or, for an existing source, the day of this update; older posts stay one click away in the source's Content list, and updates to already imported posts ignore the start date. * New: every source previews its next scheduled check: what it would import, update or skip and why, in the order it would run, what is left waiting for a later check because of the per-run limit, and when that check is due. "Run it now" queues exactly what the preview showed. * New: a third choice for the publication date, "the date of its last change at the source", next to the existing "publication date at the source" (default) and "date of the import", available generally and per source. * New: the source content list now shows the publication date plus "Updated ... ago" when an item changed later (marked when that happened in the last week), can be ordered by recent changes (the default, as before) or by publication date, and can be filtered to "All", "Not imported" or "Changed at the source" (a filtered view looks a few pages ahead at a time with "Look further"). An imported post whose original changed shows a "Changed at the source" note with an Update button, and one older than the source's start date shows "Before the start date: by hand only". * New: a new settings layout. Every tab has a side index of its groups with "Expand all" / "Collapse all" and the section on screen marked as you scroll, and folded sections say in one line what they hold. Each source opens on its content, with its settings folded below in groups (Content, Connection, What to bring, Automatic, How posts arrive, Terms, Cleanup and links) and a sentence saying what the source does; a setting a source takes from the general options is marked "General", with "Change here only" to override it for that source and "Back to the general setting" to undo it. * New: the installed version now shows next to the plugin name. * Improved: "Find its equivalent" is now the default term-matching mode for every site, existing sites included. A term arriving for the first time after this update is matched against your existing terms instead of being translated and created blindly; to keep the previous behavior, set a source's Terms group to "Always translate and create". * Improved: the settings no longer tie their description to a specific WordPress version. * Improved: "Check source" now reads a real post and says how posts will arrive. A site built with blocks and added without a username and Application Password gets a warning that its posts will land as a single Classic block, since WordPress only shares blocks with a user who can edit them. With credentials, it checks that they really open the posts for editing and, when they do not, says why (Application Passwords turned off at the source, a site without HTTPS, a server that drops the Authorization header, or a user who may not edit those posts) instead of letting every import fail. The comparison screen of each import also notes when an original built with blocks arrived as HTML and why, without adding the post to the review list. * Fix: on a multisite network, the administrator of any site could use "Check source", or a source, to make the server request addresses of its own internal network (loopback, the private network or the cloud metadata address), and "Check source" showed the start of the answer. On multisite, sources are now read only on the public internet, with the same test WordPress applies to addresses its users type, redirects included, and the sites of the same network can still be imported from. A network that needs an internal source can allow its host with the WordPress http_request_host_is_external filter, or turn the protection off with the vulgatai_safe_source_requests filter. Single sites are unchanged. * Fix: "Keep the source date (default)" never actually kept the publication date. REST imports were taking the date of the post's last change at the source instead, so a 2020 post edited in 2026 arrived dated 2026; it now arrives with its real publication date. A post already imported that gets synced or re-imported after this update moves to its correct publication date (WordPress keeps redirecting the old dated URL); anyone who wants the previous behavior can choose "The date of its last change at the source" instead. * Fix: RSS feeds: the publication date (in RFC 2822 format) was not understood, so imported posts, including published ones, were saved with no GMT date. Atom feeds used the "updated" date instead of "published"; the published date is now used for the post date and the updated date for its modified date. * Fix: the scheduled check could lose posts when more of them changed than its per-check limit allowed. It took the newest ones and skipped past the rest, which then never arrived unless they changed again. It now walks forward from where it left off and stops exactly where it got to, and posts it skips for other reasons (before the start date, set to never import, or edited by hand) no longer use up the limit. * Fix: the scheduled check compared its "changed since" time in the source site's own timezone. With a source behind UTC, changes made during the gap never arrived; with one ahead, posts were fetched twice and used up the limit for nothing. That time now travels together with its timezone. * Fix: a term on this site could only remember one source term. When two source terms pointed at the same one (synonyms in a source, or the same category used in two sources), each new mapping erased the previous one, so the AI was asked again on the next import and near-duplicate terms appeared. Each term now keeps every mapping that points to it. = 3.0.2 = * Improved: the licence notice can be postponed for fifteen days instead of switched off for ever, and it goes for good on its own the moment a key is saved, which is the only thing that says it is no longer needed. Anyone who dismissed the previous one is told once more. * Fix: the notice no longer claims that a key came with your purchase, which was not true for anyone who bought before licences existed, nor that nothing changes, which gave every reason to ignore it. It now says that updates are moving to a yearly licence, what happens to a site without a key, and where each person finds theirs: in the account on the store if they already bought, or on the plugin page if they have not. * Fix: the Add my licence key button opens the settings screen on the Updates card, where the field is, instead of at the top of the Dashboard. * Fix: the links to the store now say where they go. A link reading plugins.ayudawp.com reads as the shop front however right its address is, so it pointed at this plugin page and looked like it did not. = 3.0.1 = * Improved: the Licence key on the Updates card now says what it is for and links to where you buy or renew it, and a notice, shown once, explains that updates are moving to a licence and that nothing changes yet. The field itself has been there since 3.0.0, where it read as an optional extra nobody had a reason to fill in. Each administrator dismisses the notice for themselves, and it goes for good in the version that closes the channel. * Fix: saving the settings can no longer wipe a stored licence key. WordPress hands the sanitizer a null for any option of the group that did not travel in the request, and that null used to be cast to an empty string. The field is printed inside the settings form, so nothing was being lost today; the guard is there for the day the card moves. = 3.0.0 = * New: when Polylang or WPML create a blank translation slot, either the "+" in Polylang or WPML's "WordPress Editor" mode (not the Advanced Translation Editor, which stays a separate thing), the VulgatAI box in that empty draft now offers to fill it from the linked original with one click, instead of the dead-end "already in the target language" message it showed before. It lands on that exact post, never a duplicate. * Fix: a WPML site whose language code carries a region, en-us rather than en, the common case when you add "English (US)" as a language, now correctly recognizes content already in the target language. Before this, VulgatAI compared its own bare-code setting against WPML's own code directly, so a post already translated read as if it were not, on every screen that depends on that check. * Improved: the VulgatAI box in the classic editor now sits first in the sidebar, above Publish. A "What does this do?" toggle inside the box, in both editors, explains it on demand, alongside the one-time hint shown the first time (a pointer in the classic editor, the panel opened for you in the block editor). The "Translate text" tab now says where to translate an actual post instead, and its glossary notice links straight to the glossary field. = 2.1.2 = * Fix: the updater no longer needs any other part of the plugin in order to work. It is wired before the check that stops VulgatAI from starting on a WordPress or a PHP older than it supports, so on a site in that state it was the one piece still running, and it was reading the name of its settings group from a class of the plugin's own. That group is now handed to it from outside, and only when the plugin did start and there is actually a settings screen to show the licence field on. Nothing changes for a site that meets the requirements; on one that stops meeting them, the update that fixes it can still arrive. = 2.1.1 = * Fix: the filter that keeps this plugin out of the list of installed plugins your site reports to WordPress.org now identifies that request by its host and its path, instead of by looking for a fragment of the URL. It runs on every outbound request the site makes, so a request to somebody else site that happened to carry that text in its query string was walking into the same code, and one written with the host in capitals was not recognised at all. Neither had any visible effect, and nothing else about the update channel changes. = 2.1.0 = * New: VulgatAI updates itself. It is not on the WordPress.org directory, so until now every site stayed on whatever version it installed and the next one had to be sent over and installed by hand. From this version on the update comes from the plugin own site, and everything around it is WordPress doing what it always does: the notice on the plugins screen, the Update now button, the progress bar, the version details window and automatic updates if you have them switched on. There is a new Updates card at the bottom of the Dashboard tab with the installed version, a shortcut to the Updates screen and an optional licence key field, which is there for a future paid channel and can be left empty: with it empty the check reads a public file and carries nothing at all, and it is stored separately so it never travels inside a settings export. * Improved: your site no longer tells WordPress.org that it has this plugin installed. WordPress sends the full list of installed plugins to the directory on every update check, and it did so with this one too even though the directory has nothing to say about it; VulgatAI now takes itself out of that list before it leaves. It is a second lock as much as a privacy fix: WordPress hands an update to whatever the directory answered first, so a plugin published there under this same folder name would be installed straight over VulgatAI. WordPress.org already declines to answer about a plugin that names its own update site, which is what the Update URI header is for; this makes the point moot rather than trusting somebody else server to keep making it. = 2.0.0 = * New: a "Translate text" tab. Paste or type anything and translate it on the spot with the connector, the model and the glossary this site already uses, without leaving WordPress and without any of it being saved. Plain text keeps its paragraphs, and HTML or block markup comes back with its tags, attributes and code exactly as it went in. It doubles as the bench nothing else gave you: trying a different model, or a new glossary term, on one paragraph in a couple of seconds instead of judging it from the far side of a whole import. * New: in the block editor VulgatAI is a panel now, not a box at the bottom of the screen. Everything it did stays: the target language, the translate and re-translate buttons, the model picker and the note about where a post came from. It just sits in the document sidebar with the rest of the post settings, where you were already looking. The classic editor keeps the meta box it always had. * New: "Never import" on any item of a source. Until now the viewer could only ever say yes, and the way people say no, deleting the local copy, is the one thing that guarantees the item comes back: the scheduled sync looks for a copy, finds none, and imports it again on the next pass. An item marked this way is left alone by the sync from then on and shows as skipped in the list, and you can still import it by hand whenever you like, which takes the mark off. * New: exclusions can name a taxonomy. "Exclude posts in these taxonomies" resolved every slug against categories and tags and nothing else, so a product category or a brand could not be excluded at all, and a slug that existed in both a category and a tag excluded by both with no way to pick one. Writing product_cat:offers, product_brand:acme or category:news now says exactly which. A bare slug still means what it always meant, so nothing already configured changes. Product attributes cannot be filtered on, because WordPress does not offer them as a filter over its REST API, and the screen now says so instead of letting you find out. * New: imported categories keep their place in the tree. A source term names its parent by an ID that means nothing on this site, so everything arrived at the top level however carefully it had been arranged at the source. The missing ancestors are now fetched along with the term and the tree is rebuilt here, while the post is still assigned only the terms it actually carried. Terms that already exist on this site are used as they are and never moved: where they sit is this site's decision, not the source's. * Improved: tested with WordPress 7.1, and the plugin follows two things 7.1 changed in the admin. Its own tables put the row header on the title, which is where a screen reader expects to find what a row is about, and the reason a translation failed stopped being repeated in a tooltip that only a mouse could reach, since it is already printed next to the badge in plain sight. * Fix: an import flagged for a look can finally stop being flagged. It never could: the only thing that cleared the flag was a re-import that came out clean, so fixing the post by hand left it on the list for good, and an image the source no longer serves could never come out clean at all, because every re-import loses it again. Saving the post now re-checks it and drops whatever the content no longer shows, and whatever has no fix can be marked as reviewed, one at a time or all at once, which closes it without touching the post and leaves its comparison readable. A later import of the same post flags it again if it loses something. * Fix: the import fidelity line of the Status card and the dashboard widget updates the moment that list changes, instead of standing by a count up to five minutes old and telling you there is still something to look at right after you emptied it. = 1.9.0 = * New: WooCommerce products now arrive as products. Until now an imported product was a post with a title and a description and nothing else: no price, no SKU, no stock, no attributes, no gallery and no variations, because none of that travels in the WordPress REST response. VulgatAI now also reads the source's WooCommerce data and writes it through WooCommerce itself, so the imported product is complete and buyable: type, SKU, regular and sale price with its dates, stock and backorders, weight and dimensions, tax setup, shipping class, virtual and downloadable products with their files, external products, purchase note, catalog visibility, featured flag, gallery, attributes (both the global ones and those typed into a single product) and every variation with its own price, stock, SKU and image. A re-import updates the same product and its variations instead of duplicating them, and a combination removed at the source is removed here. * New: it works with or without credentials on the source. With a username and an Application Password VulgatAI reads the WooCommerce REST API and brings everything, private fields included: stock quantities, tax setup, sale dates and downloads. Without them it falls back to the shop's public Store API, which still carries the type, price, SKU, stock status, weight, dimensions, images, attributes and every variation with its own price. Only what a shop never shows a visitor stays behind, and if anything is missing the import says so on its comparison screen instead of leaving you with a product that looks fine and cannot be bought. * Fix: changing what a source imports now changes what its Contents list shows. The listing is cached for three minutes to keep paging instant, but the cache was keyed only by the source and the page, so after picking another content type and saving you kept getting the previous type's items and the setting looked like it did nothing. The key now covers everything that decides which items come back. And because that list is served from the saved settings, changing the selector without saving says so instead of silently showing the old type. * Fix: no more "Translation loading for the vulgatai domain was triggered too early" notice on WordPress 6.7 and later. The default wording of the link back to the original was translated while the plugin was still booting, long before translations are meant to be loaded. It is now resolved when an import actually needs it, which also fixes something quieter: that default used to be written into the database the first time you saved the settings, freezing it in whatever language saved it for readers of every other one. A prefix already stored that way is cleared once on update, and the one you typed yourself is kept untouched. * Fix: an empty answer from the AI no longer wipes the field it was translating. The title, content and excerpt each took whatever the model returned, so a model that answered with nothing (a hiccup, a refusal, a response cut short) left the post with no title at all. An empty answer now keeps the original text, which is the honest fallback, and the failure is reported as it always was. * New: a source can now read several remote collections at once, not just one, and each one says where it lands. "Content to import" is a table instead of a single choice, one row per collection with its own tick box, how much the source holds there and its own destination, so one source can bring posts in as posts and products in as products in the same run. That matters beyond tidiness: a remote product filed as a post arrives with no price, no stock and no attributes, because a post has nowhere to put them. There is also a new "Match the source" destination, general and per source, which files each item under the post type of the same name whenever this site has one. Each collection is checked on its own schedule and keeps its own place in the queue, so a quiet one never holds the others back, and the Contents list browses them one at a time with a switcher. "Check source" now also counts what each collection holds and remembers the answer, so the list of what a source offers survives a page reload instead of falling back to Posts and Pages, and an empty collection reads as empty rather than as something gone wrong. Sources saved before this keep reading exactly what they read before. * Improved: the "Taxonomies to import" list no longer shows the same name twice. With WooCommerce active it listed "Category" and "Tag" twice over, one of each for posts and for products, with nothing to tell them apart; they now read "Categories" and "Product categories", and every entry shows its slug, in the general list and in each source's own. * Improved: the setup notice shown after activating stays until you dismiss it. It used to be pinned to the plugins screen, so the first click anywhere else made it disappear for good without ever having been read. * Fix: opening "Sources & import" no longer leaves "Dashboard" as the highlighted menu entry. Both submenus open the same screen, so WordPress marked the first one whichever tab you were on; the highlight now follows the tab, including when you switch tabs on the screen itself. = 1.8.1 = * Fix: the link that opens every import flagged for a look now works. "See them all", in the Status card and the dashboard widget, pointed at a post list filtered by a post type of "any", which is not a post type WordPress can list, so it answered "Invalid post type" and there was no way to reach the flagged imports as a group. That list is now a screen of the plugin's own, which also fixes what the old link could never do anyway: imports land in whichever post type each source is set to create, and a post list shows one post type at a time. * Fix: the comparison screen loaded without the plugin's stylesheet, so the table of numbers, the verdict above it and the colour marking a row that lost something all arrived unstyled. * Fix: the links in the Status card and the dashboard widget no longer die as expired for every administrator but the first. Both are built from a rollup cached for five minutes, and the cache held the finished links, nonce included; a nonce belongs to the user who made it, so the next administrator to look got links that failed verification. Only the counts are cached now, and each reader gets links of their own. = 1.8.0 = * New: every import is now checked against its original and tells you what it found. VulgatAI counts the images, galleries, links, headings, list items, shortcodes and code blocks on the way in and on the way out, and stores the comparison on the post. The Status card and the dashboard widget carry a line that is green while imports match their originals and turns amber when one does not, and the post lists gain a "Compare result" action next to the others, separate from the activity log, opening a screen with the numbers side by side. Losses you asked for are told apart from the rest: removing origin links, excluding parts of the content and the security pass that strips iframes are reported without being treated as a problem. * New: classic galleries now arrive with their images. A `[gallery ids="…"]` carries no ``, so nothing in the pipeline used to bring those images over and the gallery landed pointing at the source site's attachment IDs. Each ID is now looked up on the source's media API and imported, and the gallery is rebuilt with the new IDs. It also fixes block galleries whose images had lost their wp-image class, which had no way of being matched either. Whatever still cannot be resolved (an RSS source has no media API, an image may be gone from the origin) is left in the content untouched and listed in the comparison, so you can see what was meant to be there. * New: origin links whose target is not imported can now be kept pointing at the original instead of being removed. Off by default, general and per source, next to "Remove origin links". Until now they lost their `` and only the text stayed, which is right when the aim is to stop sending readers to the source, but not when the original is worth linking to. * Improved: the sources box remembers what you left open. Each source, and its Settings and Contents sections, keep their state per browser, keyed to the source rather than to its position in the list, so adding or removing a source no longer hands one source the state of another. * Improved: the activity log keeps 200 entries instead of 50, so a bulk sync no longer pushes out the start of its own run before it finishes. * Fix: the attachment ID remapping no longer touches code shown inside the post. It rewrote `wp-image-N`, block `"id"` values and `[gallery]` shortcodes anywhere in the content, code blocks included, so an article explaining WordPress markup could have the very snippet it was teaching silently rewritten on import. Found on a real import of an article about Googlebot, whose three `wp-image-16` inside a `
` survived only because that ID happened not to be in the map. `
`, ``, ``, `` and `