bretterer/laravel-pseudoloc
Composer 安装命令:
composer require bretterer/laravel-pseudoloc
包简介
Runtime pseudolocalization for Laravel. Flip a flag, see every i18n bug: translated strings render accented and expanded, hardcoded strings stay plain English and jump out. Zero artifacts, no generated locale files.
关键字:
README 文档
README
Flip a flag, see every i18n bug. Turn pseudolocalization on and every translated string in your Laravel app renders accented and expanded at runtime — while anything hardcoded in Blade stays plain English and screams "I'm not translated."
No generated files. It just works. You do not create a pseudo locale, run a
generator, or add a lang/ file to translate, commit, and later delete. There is
nothing to build and nothing to clean up. Set one env var (PSEUDOLOC_ENABLED=true)
— or flip a cookie on staging — and the package decorates Laravel's translator binding
so every string transforms as it is resolved. Turn the flag off and you are exactly
where you started. That is the whole install.
Account Settings → [!! Àççôûñţ Šéţţîñĝš ôñé ţŵô !!]
Why
Every peer ecosystem ships runtime pseudolocalization — Symfony has it in core, Rails has Shopify's gem, i18next has a plugin, iOS and Android have OS-level support. Laravel had nothing. This fills that gap, and the side effect is the killer feature:
- Accented + expanded strings prove a string is going through
__()/trans(). - Plain English strings prove it is not — hardcoded text, a forgotten
__(), a string built in JavaScript. Finding those is normally a grep-and-guess chore. - The ~35% expansion surfaces every layout that breaks when real translations (German, Finnish, …) run longer than English.
Requirements
- PHP 8.2+
- Laravel 12 or 13
Installation
composer require bretterer/laravel-pseudoloc --dev
The package auto-registers its service provider. Turn it on in your local or staging environment only:
PSEUDOLOC_ENABLED=true
That's it. It is a hard no-op in production regardless of configuration, and is force-disabled during tests so it can never corrupt your assertions.
Optionally publish the config file:
php artisan vendor:publish --tag=pseudoloc-config
Configuration
| Option | Env | Default | Description |
|---|---|---|---|
enabled |
PSEUDOLOC_ENABLED |
false |
Master switch. Hard no-op in production regardless. |
expansion_factor |
PSEUDOLOC_EXPANSION_FACTOR |
1.35 |
Grow strings to ~this multiple of their length. 1.0 disables expansion. |
markers |
— | ['[!! ', ' !!]'] |
[prefix, suffix] wrapped around each string. [] disables. |
accents |
— | null |
Override the character → accented-character map. null uses the built-in Latin map. |
allow_request_toggle |
PSEUDOLOC_ALLOW_REQUEST_TOGGLE |
true |
Allow the per-request cookie toggle (see below). |
enable_in_tests |
— | false |
Let pseudolocalization run under runningUnitTests(). Leave off unless testing the tool itself. |
Per-request toggle (the two-tab demo)
Instead of redeploying to flip the flag, you can toggle pseudolocalization per browser with a query string — perfect for A/B'ing a normal tab against a pseudolocalized one on a shared staging URL.
Register the middleware manually in bootstrap/app.php, after EncryptCookies
(it reads a cookie), which the default web group ordering already guarantees:
->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \Bretterer\Pseudoloc\ToggleMiddleware::class, ]); })
Then:
- Visit
?pseudoloc=1to turn it on for your browser. - Visit
?pseudoloc=0to turn it off.
The choice is stored in a cookie (not the query string) so it survives query-less Livewire/AJAX requests, and the toggling request itself already renders in the new state.
Effective state
Production is always off. Otherwise, with enabled = PSEUDOLOC_ENABLED:
enabled |
allow_request_toggle |
cookie | Result |
|---|---|---|---|
| any | false |
any | = enabled (cookie ignored) |
| any | true |
absent | = enabled |
| any | true |
1 |
on |
| any | true |
0 |
off |
The cookie overrides enabled in both directions, which is what lets one tab be
pseudolocalized while another is normal.
What it covers
Because the transform hooks the translator's makeReplacements(), it covers everything
that flows through Laravel translation in one place:
__(),trans(),@lang, and Blade{{ __('...') }}- JSON translations (
__('Save changes')) and group/dotted keys (trans('messages.welcome')) trans_choice()/ pluralization — the correct plural branch is chosen first, then pseudolocalized (pipe syntax is never mangled)- Validation messages — with
:attributeand other replacement values kept readable
It is careful about three things that a naive implementation gets wrong, each covered by tests:
- Validation isn't broken. Laravel detects custom messages via
trans($key) === $key. Unresolved dotted keys are returned byte-identical, so custom-message detection and default messages keep working. - Plurals aren't broken. The raw
{0}...|[1,*]...string reaches the message selector untransformed; only the chosen segment is pseudolocalized, exactly once. Lang::has()isn't broken. Existence checks never report a missing key as present.
Limitations (by design, in v1)
- Queued mail/notifications follow the env flag only — there is no request context on a worker, so the cookie toggle doesn't apply.
- Strings that bypass the translator are (correctly) not transformed:
Carbon'sdiffForHumans(), theNumber::formatters, and anything hardcoded or built in JavaScript/Alpine. That is the point — those are exactly the strings you want to spot. - Response/page caches will capture pseudolocalized output; clear them when toggling.
- Lang lines containing raw HTML are accented naively, tags included (only
<tag>...</tag>replacement tokens are preserved). A Symfony-styleparse_htmlmode may come later.
Testing
composer test
Credits
- The default accent map is ported from Symfony's PseudoLocalizationTranslator (MIT); see NOTICE.md.
- Brian Retterer
License
The MIT License (MIT). See LICENSE.md.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-15
