=== All to Lat — Latin Slugs by Transliteration ===
Contributors: catcodestudio
Tags: transliteration, permalinks, slug, cyrillic, seo
Requires at least: 6.2
Tested up to: 7.0
Requires PHP: 7.2
Stable tag: 1.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Latin slugs by transliteration for posts, products, categories and uploaded files, with a bulk converter for the URLs you already have.

== Description ==

WordPress builds a slug out of the title. Give it a Ukrainian, Russian, Greek or Polish title and what lands in the address bar is `%d0%b0%d0%b1%d0%b2` — a percent-encoded string that looks like spam in search results, breaks when it is copied into an email or a messenger, and tells a reader nothing about the page.

This plugin transliterates the title first, so the slug reads `upravlinnia-nerukhomistiu` instead.

**One filter covers everything.** The hook sits on `sanitize_title`, the single point every slug in WordPress goes through. Posts, pages, custom post types, categories, tags, WooCommerce products and product attributes are all covered by the same code — and so are the editor, an XML importer, the REST API and WP-CLI, because none of them can build a slug without passing through it. Uploaded file names go through `sanitize_file_name` and keep their extension.

**Transliteration in three layers.**

1. A Cyrillic table chosen by standard. Ukrainian (КМУ 55/2010) spells `и→y`, `г→h`, `х→kh`, `зг→zgh`, with word-initial `я→ya`; Russian BGN/PCGN spells `и→i`, `г→g`. Running a Ukrainian site through the Russian table produces *hodinnik* instead of *hodynnyk* and buries it in search for its own titles. Bulgarian, Serbian/Macedonian and Belarusian have their own tables.
2. A universal table for Greek and for Latin letters with diacritics — Polish, Czech, Slovak, German, French, Spanish, Portuguese, Turkish, Romanian, Hungarian, Baltic, Nordic, Vietnamese. These need no per-language choice: `ł` is `l` wherever it occurs. German umlauts are the exception and are a setting, because `ü→ue` reads right in German and wrong in Turkish.
3. `iconv('ASCII//TRANSLIT')` for anything the tables missed, so an alphabet nobody listed degrades to its closest ASCII form instead of collapsing into hyphens.

**The addresses you already have.** The Bulk conversion tab converts existing posts and terms in batches, with a dry-run preview that writes nothing, a journal of every change and one-click revert. The safe default touches only slugs that are not already Latin.

**Nothing 404s afterwards.** A renamed post keeps working through WordPress's own `_wp_old_slug` redirect. A renamed category or tag does not — WordPress has no such mechanism for terms — so this plugin sends the 301 itself, from its journal, and only for requests that have already failed to match anything.

== Installation ==

1. Upload the plugin to `/wp-content/plugins/` or install it from the Plugins screen.
2. Activate it.
3. Open **Settings → All to Lat**, tick *Transliterate new slugs*, choose the standard for your language and save.
4. For a site that already has content, go to the **Bulk conversion** tab, press *Preview*, and then *Start conversion*.

== Frequently Asked Questions ==

= Will my current URLs change? =

Not on their own. Switching the plugin on only affects slugs created from that moment. Existing addresses change only when you run the bulk converter, and it warns you first, shows a preview, and keeps a journal you can roll back.

= Do old links keep working after a conversion? =

Yes. Posts are covered by WordPress's own redirect for renamed slugs; categories and tags are covered by this plugin's 301, which reads the journal. Both send a permanent redirect, which is what search engines need to move the ranking across.

= Does it work with WooCommerce? =

Yes. Products are a post type and product categories are a taxonomy, so both go through the same filter and both appear in the bulk converter.

= Does it work with a multilingual site? =

Yes — the plugin transliterates whatever text it is given, so each language's own titles produce their own slugs. Pick the standard that matches the script you write in; it only affects Cyrillic.

= What happens to file names? =

An uploaded `звіт.pdf` is stored as `zvit.pdf`. The extension is preserved untouched.

== Screenshots ==

1. Settings: standard, separator, length and what to apply the rules to.
2. Bulk conversion with a dry-run preview of what each slug becomes.
3. The journal, with one-click revert of any run.

== Changelog ==

= 1.1.0 =
* Fixed: the preview only looked at the first rows of a scope, so a site whose newest content is already Latin was told "nothing to change" while the counter beside it said otherwise.
* Fixed: the converter wrote slugs onto drafts. WordPress leaves those empty on purpose and fills them in on publish, and the journal could not undo them — drafts are now left alone.
* Fixed: the 301 for converted category and tag addresses died as soon as transliteration of new slugs was switched off. It now follows its own setting only.
* Fixed: when two objects had shared one address — the same word as a category and as a tag — both old addresses redirected to whichever was converted last. The path of the request now decides.
* Fixed: "Revert" dropped a journal entry even when the slug could not be put back, taking its redirect with it. Entries are now cleared one at a time, and only on success.
* Fixed: a run that hit the batch ceiling stopped silently. It now says where it stopped and can be resumed.
* Counting the rows of a scope is done by the database instead of reading every slug into PHP, which is what made the settings page slow on a large catalogue.
* The admin script and styles are enqueued files rather than an inline block, so they cache and survive a Content-Security-Policy.
* Requires WordPress 6.2 (for `%i` table placeholders in queries).

= 1.0.0 =
* First release.

== Upgrade Notice ==

= 1.1.0 =
Fixes the bulk preview, leaves drafts alone, and keeps the 301 for converted category URLs alive when transliteration is switched off.

= 1.0.0 =
First release.
