overtrue/laravel-pinyin
Laravel wrapper around overtrue/pinyin that converts Chinese characters into pinyin, supporting tone marks, initials, and custom separators. Easily generate slugs, sortable keys, or searchable text from Chinese input via a simple facade and configurable options.
Install
composer require overtrue/laravel-pinyin
Laravel package auto-discovery registers the service provider and the Pinyin facade — no manual registration in config/app.php for L13 (or L10–L12).
First use
use Overtrue\LaravelPinyin\Facades\Pinyin;
Pinyin::permalink('北京欢迎你'); // "bei-jing-huan-ying-ni"
Pinyin::sentence('北京欢迎你'); // "bei jing huan ying ni"
Pinyin::convert('北京欢迎你'); // ['bei','jing','huan','ying','ni']
Pinyin::abbr('北京欢迎你'); // "bjhyn"
Where to look first
vendor/overtrue/pinyin/README.md — the facade is a thin wrapper; almost every method, option and edge case lives in the underlying library.vendor/overtrue/laravel-pinyin/src/ — usually just a ServiceProvider and a Facades/Pinyin.php; read it to see exactly which class is bound and how it is resolved from the container.php artisan vendor:publish --tag=laravel-pinyin
First real use case
Generating URL slugs for Chinese-titled content — this is the 80% case. Create a slug column, populate on save, and index it. Do not compute it in a Blade loop.
Store pinyin, don't compute it at render time
Pinyin conversion is CPU-bound and the package does no caching. For any list, search or sort feature, persist the result:
// migration
$table->string('slug')->unique();
$table->string('pinyin_index')->nullable()->index();
$table->string('initials', 32)->nullable();
// model observer / booted()
protected static function booted(): void
{
static::saving(function (self $post) {
$post->slug ??= Pinyin::permalink($post->title);
$post->pinyin_index = Pinyin::sentence($post->title);
$post->initials = Pinyin::abbr($post->title);
});
}
Slug strategy
Compose with Laravel's Str, because permalink() only transliterates Han characters and passes Latin/digits/punctuation through:
$slug = Str::limit(Str::slug(Pinyin::permalink($title)), 80, '');
$slug = $slug ?: Str::random(8);
$slug .= '-' . Str::lower(Str::random(5)); // uniqueness guard
Names and polyphones
Context-free conversion gets polyphonic characters wrong. For people, use the dedicated name mode; for known ambiguous phrases, feed whole phrases rather than single characters:
Pinyin::name('单雄信'); // surname-aware, "shan" not "dan"
Pinyin::polyphones('重'); // inspect all readings
Rule of thumb: convert the longest meaningful unit (full name, full phrase). Per-character conversion is where most wrong output comes from.
Indexing / grouping Chinese by letter
Use abbr() or the first syllable to build an A–Z contact index or "alphabetical" sort for Chinese names, then sort in SQL on the stored column. This is far more reliable than ORDER BY name under a Chinese collation.
Wrap it in your own class
Don't call the facade from controllers and views. Bind a small Transliterator service and inject it — it gives you a single seam to change separators, add caching, or mock in tests:
final class Transliterator
{
public function slug(string $value): string { /* ... */ }
public function index(string $value): string { /* ... */ }
}
Backfilling existing rows
For large tables, run an artisan command with chunkById() and queue the work. Never run a full-table conversion inside a migration.
Swap the implementation
The config publishes a class binding. Point it at your own subclass/decorator if you need custom dictionaries or memoization — this is the intended extension point.
overtrue/pinyin version is compatible with composer why-not overtrue/pinyin; the wrapper release does not pin the core for you.Pinyin::permalink('iPhone 15 发布') mixes scripts — always run the result through Str::slug() and add a fallback for the all-non-Han case (empty slug).ü handling. Decide once whether you want lv or lü, and make sure column collation and existing slugs agree. Changing this later means re-slugging and breaking every URL.Str::of-style cache or a request-scoped array) if you convert in bulk.$post->slug accessor that calls Pinyin::permalink() will silently melt a 100-row index page.Pinyin::shouldReceive('permalink')->andReturn('stub'), or swap the bound class with $this->instance(...). Do this so tests don't depend on library output strings.overtrue/pinyin, not the Laravel wrapper. Check the core README for the dictionary API before assuming a config key exists here.Pinyin::convert($input) (the array form) first — it shows exactly which characters were split and which were passed through, which usually explains the problem faster than reading the permalink string.How can I help you explore Laravel packages today?