phpmob/chang
phpmob/chang is a lightweight Laravel/PHP package offering a simple, focused utility for application tasks. It installs via Composer and follows Laravel conventions; see the repository README for setup, configuration, and usage examples.
The package no longer ships locale support as of v4.1.1 — locale was removed from the core dependencies. Everything else about the install flow is unchanged, but the upgrade path now has one extra decision point: do I actually need locale?
Minimal steps:
composer require <vendor>/<package>:^4.1
grep -rn "setLocale\|getLocale\|trans(\|__(" app/ resources/ routes/ config/
If you only translate static strings with Laravel's own lang/ files, you're fine — that's core Laravel, not this package.composer.json rather than depending on it transitively.php artisan vendor:publish --provider="<Vendor>\<Package>\<Package>ServiceProvider"
Treat locale as an explicit dependency. The single most important pattern after this release: anything locale-related belongs in your composer.json with a pinned constraint, not inherited from this package. This makes upgrades predictable and prevents a future transitive removal from silently breaking your app.
Upgrade path for existing 4.x installs. Do this in one PR, not two:
composer require <vendor>/<locale-package> # add the now-missing piece first
composer update <vendor>/<package> --with-dependencies
Adding the locale package before updating means the update never lands the app in a broken intermediate state on a shared branch.
Locale switching middleware. Standard pattern, unchanged by the release, but now worth owning outright since the package isn't doing it for you:
// app/Http/Middleware/SetLocale.php
public function handle($request, Closure $next)
{
$locale = $request->user()?->locale
?? $request->session()->get('locale')
?? config('app.locale');
if (in_array($locale, config('app.available_locales', []), true)) {
app()->setLocale($locale);
}
return $next($request);
}
Register it in the web group so it runs before anything that renders translated output.
Per-user vs. per-request locale. Keep them separate. Persist the user's choice on the users table (or a settings table) for authenticated requests; use session or Accept-Language negotiation for guests. Don't write to the user record from a GET — expose a POST /locale endpoint that validates against your allowlist, then redirects back.
API localization. For stateless endpoints, negotiate from Accept-Language and set the locale per-request without touching the session:
$locale = $request->getPreferredLanguage(config('app.available_locales'));
app()->setLocale($locale);
Testing. Any test that asserts translated output should set the locale explicitly in setUp() rather than relying on ambient config — this is exactly the kind of assumption the 4.1.1 change punishes:
protected function setUp(): void
{
parent::setUp();
app()->setLocale('en');
}
CI. If your pipeline runs composer install --no-dev or uses a locked, cached vendor directory, the new explicit locale dependency only takes effect after a lockfile refresh. Bump the lock in the same commit as the upgrade.
The upgrade is a hard failure, not a soft one. Code that reached locale functionality through this package will throw Class not found or Call to undefined method after updating without the manual install. There's no deprecation window here — v4.1.1 is a patch version doing a breaking thing. Pin to 4.1.0 if you need breathing room, but plan the move.
Patch version ≠ patch risk. 4.1.0 → 4.1.1 reads as a safe bump and will sail through most composer update workflows unattended. If you use Dependabot/Renovate with auto-merge on patch releases, this is the case that bites you. Consider gating auto-merge on composer install && php artisan test for packages that touch i18n.
Stale config and caches. After publishing config or changing locale settings, run php artisan config:clear (and cache:clear if you cache translations). A cached config from before the upgrade can mask both the breakage and the fix, which makes debugging confusing.
Check the fallback chain. config('app.fallback_locale') still works, but if your locale package brings its own fallback config, the two can disagree. Assert on a missing key in a non-default locale to see which one actually wins.
Validate the locale allowlist server-side. Once you own locale switching, an unvalidated ?lang= or session value can be set to an arbitrary string, which some locale packages turn into filesystem paths. Always check against config('app.available_locales').
Extension points to verify after upgrading. Service provider bindings, published config keys, and any locale-aware Blade directives or middleware the package previously registered. php artisan about and php artisan route:list are fast ways to spot a provider that silently failed to boot.
Watch the transitive tree. composer why <vendor>/<locale-package> tells you whether it's still arriving via another dependency. If it is, your "manual install" isn't actually load-bearing yet, and a future removal elsewhere will surprise you. Declare it directly.
How can I help you explore Laravel packages today?