PHPackages                             bretterer/laravel-pseudoloc - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. bretterer/laravel-pseudoloc

ActiveLibrary[Localization &amp; i18n](/categories/localization)

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.

v0.1.0(1mo ago)8742MITPHPPHP ^8.2CI passing

Since Jul 15Pushed 1mo agoCompare

[ Source](https://github.com/bretterer/laravel-pseudoloc)[ Packagist](https://packagist.org/packages/bretterer/laravel-pseudoloc)[ Docs](https://github.com/bretterer/laravel-pseudoloc)[ RSS](/packages/bretterer-laravel-pseudoloc/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

Laravel Pseudoloc
=================

[](#laravel-pseudoloc)

 [![Latest Version on Packagist](https://camo.githubusercontent.com/3fcd54b6b357f998e18a73e5400d0d2971440dd4fd9698266577ca7aa6fcbf4c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6272657474657265722f6c61726176656c2d70736575646f6c6f632e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bretterer/laravel-pseudoloc) [![Tests](https://camo.githubusercontent.com/d14cc688e0b2641fc11e05091e67dff66780a72ed5de4ad98374c464d08b7b85/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6272657474657265722f6c61726176656c2d70736575646f6c6f632f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/bretterer/laravel-pseudoloc/actions/workflows/tests.yml) [![Code Style](https://camo.githubusercontent.com/c4e9f5642d8fdfc6394ebad98df7b9047f3bc373211051551b6faa15f81895bf/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6272657474657265722f6c61726176656c2d70736575646f6c6f632f6c696e742e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/bretterer/laravel-pseudoloc/actions/workflows/lint.yml) [![Total Downloads](https://camo.githubusercontent.com/5c3ade26d660c5a9763e04cecf675aebff651c81647564c9b479504ac85b5940/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6272657474657265722f6c61726176656c2d70736575646f6c6f632e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bretterer/laravel-pseudoloc)

**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   →   [!! Àççôûñţ Šéţţîñĝš ôñé ţŵô !!]

```

 [![A dashboard toggling into pseudolocalization: translated strings render accented and expanded, while hardcoded strings (a promo banner, the Export CSV button) stay plain English.](art/demo.gif)](art/demo.gif)

Why
---

[](#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
------------

[](#requirements)

- PHP 8.2+
- Laravel 12 or 13

Installation
------------

[](#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
-------------

[](#configuration)

OptionEnvDefaultDescription`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)
-------------------------------------

[](#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=1` to turn it **on** for your browser.
- Visit `?pseudoloc=0` to 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

[](#effective-state)

Production is always **off**. Otherwise, with `enabled` = `PSEUDOLOC_ENABLED`:

`enabled``allow_request_toggle`cookieResultany`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
--------------

[](#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 `:attribute` and other replacement values kept readable

It is careful about three things that a naive implementation gets wrong, each covered by tests:

1. **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.
2. **Plurals aren't broken.** The raw `{0}...|[1,*]...` string reaches the message selector untransformed; only the chosen segment is pseudolocalized, exactly once.
3. **`Lang::has()` isn't broken.** Existence checks never report a missing key as present.

Limitations (by design, in v1)
------------------------------

[](#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`'s `diffForHumans()`, the `Number::` 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 `...` replacement tokens are preserved). A Symfony-style `parse_html`mode may come later.

Testing
-------

[](#testing)

```
composer test
```

Credits
-------

[](#credits)

- The default accent map is ported from [Symfony's PseudoLocalizationTranslator](https://github.com/symfony/translation) (MIT); see [NOTICE.md](NOTICE.md).
- [Brian Retterer](https://github.com/bretterer)

License
-------

[](#license)

The MIT License (MIT). See [LICENSE.md](LICENSE.md).

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance90

Actively maintained with recent releases

Popularity25

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

47d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/d1d1ced324a1671b1814017525fde4873faa1d7c5c29c4fd766e77bf376b6aff?d=identicon)[bretterer](/maintainers/bretterer)

---

Top Contributors

[![bretterer](https://avatars.githubusercontent.com/u/1906920?v=4)](https://github.com/bretterer "bretterer (3 commits)")

---

Tags

i18nl10nlaravellocalizationphppseudolocalizationtranslationlaravellocalizationinternationalizationi18nl10ntranslationqapseudolocalizationpseudoloc

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/bretterer-laravel-pseudoloc/health.svg)

```
[![Health](https://phpackages.com/badges/bretterer-laravel-pseudoloc/health.svg)](https://phpackages.com/packages/bretterer-laravel-pseudoloc)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3365.5M359](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

80732.6M270](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58190.1k22](/packages/api-platform-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k31.8M166](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k17.6M165](/packages/laravel-pulse)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

1.0k2.5M152](/packages/roots-acorn)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
