elforastero/transliterate
elforastero/transliterate is a Laravel package for converting Cyrillic text into Latin characters. It provides a simple helper and facade, configurable transliteration maps, and seamless integration for generating readable slugs, filenames, and search-friendly strings.
Install the package:
composer require elforastero/transliterate
The service provider and Transliterate facade are auto-discovered on Laravel 5.5+, so no manual registration is normally required.
Publish the config if you need to change the engine:
php artisan vendor:publish --provider="ElForastero\Transliterate\TransliterateServiceProvider"
This creates config/transliterate.php, which points at the default transliterator engine. In most apps you will not touch it.
First use case — Cyrillic-to-Latin strings (slugs, usernames, filenames):
use ElForastero\Transliterate\Facades\Transliterate;
Transliterate::make('Привет, мир!')->transliterate();
// "Privet, mir!"
Where to look first in the repo:
src/Transliterator.php — the engine and its public transliterate() method.src/Maps/ — per-language character maps. This is the file to read when output looks wrong for ё, й, ъ, ь, or Ukrainian/Bulgarian characters.src/Transliterable.php — the Eloquent trait if you want automatic slug fields.config/transliterate.php — engine binding and any map/language defaults.Fastest real win: drop it into a Post/Page model to build slugs from a Russian title without pulling in a full ICU/intl dependency.
Explicit service calls — inject or resolve the transliterator and call it at the point of use:
$slug = str(Transliterate::make($post->title)->transliterate())
->slug('-')
->lower()
->toString();
Chaining Str::slug() after transliteration is the most reliable pattern: transliteration handles script conversion, Str::slug() handles punctuation, whitespace, and case.
Model trait for slugs — the package ships a Transliterable trait that maps a source attribute to a target attribute on save. Check the trait source in your installed version for the exact protected property name, then:
use ElForastero\Transliterate\Transliterable;
class Post extends Model
{
use Transliterable;
protected $transliterable = [
'slug' => 'title',
];
}
This is best for simple CRUD. For anything with uniqueness rules or fallbacks, prefer a model observer or saving listener so you keep full control.
Form request / DTO normalization — transliterate on input rather than on output when the value is also used as a key (slugs, storage paths, cache keys). Doing it once at write time avoids inconsistent results from later config changes.
Custom engine — implement a class with the same make()/transliterate() contract and swap the engine class in config/transliterate.php. Useful if you need a different map set or want to delegate to intl's Transliterator on servers that have it.
Bulk backfill — for existing rows, chunk over the model and save each record so trait/observer hooks fire:
Post::whereNull('slug')->chunkById(500, function ($posts) {
$posts->each->save();
});
Avoid Post::whereNull('slug')->update([...]) — mass updates bypass model events, so the transliteration hook never runs.
Route model binding — once slug is transliterated, bind routes with getRouteKeyName() returning 'slug'. Transliterated slugs are stable and URL-safe, unlike raw Cyrillic in URLs (which browsers percent-encode into unreadable strings).
intl/voku/portable-ascii for broader coverage.ё/е, й/и, and ь/ъ frequently collapse to the same Latin string. Always pair transliteration with a uniqueness strategy (-2, -3 suffixes, or a UUID fragment) before persisting a slug.Str::slug() alone returns an empty string on Cyrillic. A common bug is adding Str::slug() and getting '' for Russian titles. This package exists precisely to fix that, so make sure both steps are present and in the right order.ё => e vs yo, й => i vs y, and word-final ий varies by language map. If a client cares about how a name is romanized, write a small test with realistic strings and assert the exact output.mb_convert_encoding($value, 'UTF-8', 'Windows-1251') before passing in.config/transliterate.php, run php artisan config:cache (or config:clear) — otherwise stale cached config will keep the old binding.boot() logic also hooks saving/creating, verify the order. Slug generation must happen before your uniqueness check, otherwise you will validate the pre-transliteration value.Transliterate::make() calls across the codebase. Put one SlugService or helper behind an interface, so replacing the engine later is a one-file change.Transliterate::make($value)->transliterate() directly in php artisan tinker with the exact string from the failing record — including invisible characters (soft hyphen, NBSP), which are the usual cause of "it worked in my test" slug bugs.How can I help you explore Laravel packages today?