Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Json Fallback Laravel Package

laravel-lang/json-fallback

Adds a JSON fallback translator to Laravel so missing JSON translation keys resolve to your fallback locale instead of showing raw keys. Drop-in service provider, no config required.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package via Composer:

composer require laravel-lang/json-fallback

The service provider is auto-discovered, so there is nothing to register manually. On the next request, the package swaps Laravel's default FileLoader (JSON side) for its fallback-aware loader. Confirm it is active by dumping the loader:

dd(app('translator')->getLoader());
// You should see the LaravelLang loader, not the default one

Prerequisites to check first:

  1. config/app.php → 'fallback_locale' => 'en' (or whatever your base language is). This is the locale the package falls back to.
  2. Your JSON files live in lang/{locale}.json (or resources/lang/{locale}.json on Laravel 10 with the classic layout). If you use a custom path, verify it in config/app.php → 'paths' or the loader's $paths.
  3. Your JSON files are flat key/value pairs, e.g.:
{
  "auth.login.title": "Sign in",
  "checkout.submit": "Place order"
}

First use case: put a key in lang/en.json but omit it from lang/fr.json. Then:

app()->setLocale('fr');
echo __('auth.login.title'); // "Sign in" — pulled from en.json

Where to look in the source if something does not work: src/ServiceProvider.php (the binding that replaces the loader), and the loader class that wraps Laravel's FileLoader and re-checks the fallback locale when get() returns the raw key.


Implementation Patterns

Drop-in default (recommended). Most teams install and forget. Because the package overrides the translator's loader rather than adding a macro, existing __() calls keep working exactly as before — you simply get a fallback string instead of seeing auth.login.title leaked into the UI.

Confirm the fallback tier in a test. Lock the behavior with a feature test so an accidental config change does not silently break the contract:

public function test_json_fallback_resolves_missing_french_key(): void
{
    app()->setLocale('fr');
    $this->assertSame('Sign in', __('auth.login.title'));
}

Keep a canary key in lang/en.json that is never translated, and assert the fallback returns it in every locale. This catches loader regressions cheaply.

Custom TranslationServiceProvider. If you have overridden the translator registration (common with translation-loader packages, DB-driven locales, or testing harnesses), the package's binding can lose the race. In those cases, extend the default TranslationServiceProvider and set the loader after parent::register(), or bind the language loader explicitly in your own provider's register():

$this->app->extend('translation.loader', function ($loader, $app) {
    return new \LaravelLang\JsonFallback\...Loader($app['files'], $app['path.lang']);
});

Check the package's own ServiceProvider to copy the exact class name and constructor signature — the loader's $paths argument matters if you moved lang/.

Frontend/SPA delivery. Pair this with a small endpoint that dumps a locale's JSON so your Vue/React/Inertia bundle can render strings. Because fallback is resolved server-side, the JSON you ship to the client is already complete — no JavaScript ?? 'default' guards needed in components. Alternatively, keep using Blade + __() and let the fallback handle it at render time.

Translation workflow. Keep publishing base strings via laravel-lang/common or php artisan lang:add, then let translators work in lang/{locale}.json incrementally. Every partially translated locale stays shippable — you can merge a half-finished de.json to main without a UI regression. Use a CI step (jq . lang/*.json or jsonlint) to make sure no file is malformed, since a broken JSON file aborts the loader entirely.

Cache invalidation. If you cache translations (Laravel's native translation cache in bootstrap/cache/translations.php on 11+, or laravel-lang/lang's lang:cache), rebuild or clear after adding keys:

php artisan cache:clear
php artisan lang:clear   # only if using laravel-lang/lang's commands

Deploy pipelines that cache during build should invalidate the cache whenever any lang/*.json changes.


Gotchas and Tips

  • This only affects JSON keys. PHP-array translations (__('messages.welcome') where lang/fr/messages.php exists) already have Laravel fallback behavior natively — the package does nothing there. Do not expect it to chain into package namespaces or vendor translations either.
  • Single tier only. It falls back to config('app.fallback_locale') once. There is no fr-CA → fr-FR → en chain. If you need multi-level fallbacks, subclass the loader and override the resolution logic, or normalize the locale at request time (setLocale('fr-FR') → look up fr) before resolving.
  • Flat keys only. The loader looks up the exact string you passed to __(). Nested JSON objects ({"auth": {"login": "..."}}) are never traversed. If your frontend already uses dotted a.b.c keys, you are fine; if it emits real nested objects, flatten them at build time.
  • Missing file vs. missing key. A completely absent lang/fr.json short-circuits before lookup; some behaviors are driven by Laravel's FileLoader returning the key itself. Test both cases so you know whether your fallback comes from the package or from fallback_locale at the outer Translator level.
  • Locale casing matters. fr-ca.json and fr_CA.json are not interchangeable; the loader constructs the filename from the locale string verbatim. Keep your config/app.php available_locales (if you have one) and file names byte-identical.
  • Provider order regressions. Any package that rebinds translation.loader after this one (translation caching, DB loaders, spatie/laravel-translation-loader, testing helpers) will silently disable the fallback. Add the loader assertion test above to your CI matrix so this never ships unnoticed.
  • Debugging a missing fallback. dd(app('translator')->getLoader()) tells you instantly whether the package is active. dd(app('translator')->get($key, [], 'fr')) isolates whether the loader or the outer Translator produced the raw key back.
  • JSON syntax is fatal. A single trailing comma makes the whole file unparseable and the loader throws before it ever gets to the fallback check. Always validate in CI (jq -e . lang/*.json > /dev/null).
  • No config file to publish. There is nothing to vendor:publish — behavior is entirely driven by config('app.fallback_locale') and your language path config. Do not go looking for a config/json-fallback.php.
  • Version pinning. Release line tracks Laravel majors (10 → 11 → 12 → 13). Pin to a major that matches your framework constraint; the composer solver will usually handle it, but composer show laravel-lang/json-fallback after install confirms you got the expected tag.
  • Extension point. For per-key overrides or logging when fallback is used (useful to measure translation debt), subclass the package loader, override get(), and rebind translation.loader in your own service provider. That is the cleanest way to add analytics without forking.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package