All to Lat — Latin Slugs by Transliteration
All to Lat for WordPress — Latin slugs by transliteration for posts, products, terms and uploads, with a bulk converter, journal, revert and 301s
by CatCode · github.com/catcodestudio/wp-all-to-lat · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/catcodestudio/wp-all-to-lat/archive/refs/heads/main.zipReadme
All to Lat — WordPress build
Plugin slug: all-to-lat · text domain: all-to-lat · namespace: CatCode\AllToLat
Constants and the journal table are prefixed cc_translit, the same as the OpenCart builds.
Where the work happens
| File | Role |
|---|---|
all-to-lat.php |
header, constants, autoloader, bootstrap |
includes/Translit.php |
the transliteration tables — shared verbatim with the OpenCart builds, keep in sync |
includes/Settings.php |
one option row, validated on save |
includes/Installer.php |
journal table, created on activation and repaired on load |
includes/Slugs.php |
sanitize_title (priority 9) and sanitize_file_name |
includes/Bulk.php |
batch conversion of posts and terms, journal, revert |
includes/Redirect.php |
301 for renamed terms — WordPress only redirects posts |
includes/Admin/* |
settings screen and the three ajax endpoints |
Decisions worth remembering
Priority 9 on sanitize_title. Core's own sanitize_title_with_dashes runs at 10 and is
what percent-encodes Cyrillic. Anything later than 9 sees %d0%b0 instead of а and has
nothing left to transliterate.
Posts are written with $wpdb->update, not wp_update_post. A full save re-runs every
save_post hook on the site — SEO plugins, caches, ERP syncs — for a change none of them care
about; on a 20 000-product catalogue that is the difference between a minute and an afternoon.
The _wp_old_slug meta that wp_update_post would have written is added by hand, so core's
redirect for renamed posts keeps working.
Terms go through wp_update_term instead, because term slugs live in two tables and the
caches around them are not worth reimplementing.
Drafts are never converted. WordPress leaves post_name empty on a draft on purpose and
fills it in from the title at publish time. Writing a slug there freezes the address at a
half-finished title, and the journal cannot undo it either — there is no previous slug to put
back. Every scope query therefore carries post_name <> ''.
The preview filters in SQL, a run does not. The preview has to show the first rows that
actually need work, or a site whose newest content is already Latin reads "nothing to change"
next to a counter saying 900. A run walks the same scope by LIMIT offset, n and must see a
stable set — filtering there would shrink the set under the walker and skip rows.
The 301 follows its own setting, not status. status governs slugs created from now on;
switching it off on a converted site must not 404 every address the conversion replaced.
Table names go through %i, which is why the plugin requires WordPress 6.2. That is what
keeps Plugin Check at 0/0 without blanket-ignoring the SQL sniffs.
Build
python build.py # → all-to-lat.zip, ready for wp.org or a manual upload