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

Laravel Translation Loader Laravel Package

juzaweb/laravel-translation-loader

Load and manage Laravel translation strings from the database. Extends Laravel's translation loader so you can store, edit, and serve language lines dynamically instead of relying only on file-based lang files. Great for admin panels and CMS apps needing runtime translation management.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, Spatie-derived translation loader: it swaps Laravel's file-only translation loader for one that can also read language lines from a database table (and, in this fork, YAML/other sources). Day-to-day, you use it when content editors need to change UI strings without a deploy.

Minimal setup

  1. Install and confirm the actual package name — the repository slug (juzaweb/laravel-translation) and package name (juzaweb/laravel-translation-loader) differ:
    composer require juzaweb/laravel-translation-loader
    
  2. Find the provider and config before publishing. The reliable source of truth is the package's composer.json:
    cat vendor/juzaweb/laravel-translation-loader/composer.json
    # look at extra.laravel.providers and extra.laravel.aliases
    
  3. Publish config/migrations using the provider you found (names below are typical for this family of packages — verify):
    php artisan vendor:publish --provider="Juzaweb\Translation\TranslationServiceProvider"
    php artisan migrate
    
  4. Inspect the published config (usually config/translation-loader.php or config/translation.php). The key setting is the source/loader — file, database, YAML, or a custom class.
  5. Smoke-test the first use case: move one existing group out of lang/en/messages.php into the DB, then call it normally.
use Juzaweb\Translation\Models\LanguageLine; // confirm namespace in src/Models

LanguageLine::create([
    'group' => 'messages',
    'key'   => 'welcome',
    'text'  => ['en' => 'Welcome', 'fr' => 'Bienvenue'],
]);
{{ __('messages.welcome') }}   {{-- now resolves from the configured source --}}

Where to look first

  • src/TranslationServiceProvider.php — bindings, loader registration, config merge.
  • config/*.php — source selection and fallback order.
  • src/TranslationLoaders/ (or similar) — the actual loader implementations; this is the extension point.
  • src/Models/ — the Eloquent model backing DB lines.
  • database/migrations/ — schema for the language_lines table (group, key, JSON text).

Because this package has ~1 star and 0 dependents, treat it as a fork you may need to read rather than a heavily documented dependency. Budget 30 minutes to read the provider and loader before wiring it into a project.

Implementation Patterns

1. Loader chaining / fallback

The usual pattern is: database (or YAML) first, then Laravel's file loader as fallback. That lets you override only the strings editors care about while keeping the rest in lang/:

// config/translation-loader.php (illustrative)
return [
    'source' => Juzaweb\Translation\TranslationLoaders\Database::class,
    // or an array/chain if the fork supports multiple sources
];

If the config only accepts a single source, keep the file loader as the ultimate fallback by ensuring the package's loader delegates misses to parent::load() (check the loader implementation).

2. CRUD through the model, not raw SQL

Create a small admin service or use the package model directly. Wrap writes so cache invalidation and text array normalization happen in one place:

class TranslationRepository
{
    public function put(string $group, string $key, array $text): void
    {
        LanguageLine::updateOrCreate(
            ['group' => $group, 'key' => $key],
            ['text' => $text]
        );
    }
}

3. Import/export existing lang files

The highest-value first workflow is migrating existing lang/{locale}/*.php files into the chosen source. A one-off artisan command using File::files(lang_path('en')) and require per file is usually enough. Keep the import idempotent via updateOrCreate. Export back to PHP/YAML for version control if editors need Git review.

4. Group/key conventions matter more than with files

Since group and key are now columns, standardize early:

  • group = the old filename (messages, auth, validation).
  • key = the dotted key without the group prefix (welcome, not messages.welcome).
  • Avoid slashes and locale codes in keys; locales belong in the text JSON.

5. Runtime overrides for tests and local dev

Point the source at the file loader in phpunit.xml / .env.testing so tests don't touch the translation table:

<env name="TRANSLATION_SOURCE" value="file"/>

If the config doesn't read an env var, add one in config/translation-loader.php yourself.

6. Custom source integration

The main extension point is a loader class. Implement whatever interface the package's built-in loaders implement (inspect src/TranslationLoaders/), then reference it in config. This is how you'd add Redis, an external TMS, or a tenant-scoped source:

class RedisTranslationLoader implements TranslationLoader
{
    public function loadTranslations(string $locale, string $group): array
    {
        return Cache::remember("translations:$locale:$group", 3600, function () use ($locale, $group) {
            // return ['key' => 'value', ...]
        });
    }
}

7. Blade, validation, and packages

Because it hooks Laravel's translator, __(), @lang, trans_choice(), validation messages, and lang:: package keys all keep working. You do not change call sites — that's the main selling point. Just don't mix DB keys with vendor package keys (package::file.key); the loader typically only handles non-namespaced groups.

8. Caching in production

If you cache translations (Laravel's optimize or your own layer), invalidate on write. Hook the model:

static::saved(fn () => Cache::forget("translations:{$this->group}"));
static::deleted(fn () => Cache::forget("translations:{$this->group}"));

Gotchas and Tips

  • Package identity is ambiguous. Repo juzaweb/laravel-translation vs package juzaweb/laravel-translation-loader. Confirm with composer show and composer.json before copying code from Spatie docs; namespace (Juzaweb\Translation vs Spatie\TranslationLoader) and class names may differ.
  • It's a fork of a well-known package. Most public documentation and Stack Overflow answers refer to spatie/laravel-translation-loader. APIs are close, but verify model namespace, config filename, and migration columns rather than assuming.
  • Pin the version. With one star, zero dependents, and a recent release (2026-03-13), the API can move. Use an exact constraint ("juzaweb/laravel-translation-loader": "1.2.*") and commit the lockfile.
  • Loader order determines behavior. If the file loader wins, DB edits appear to do nothing. If the DB loader wins without a file fallback, every key you didn't migrate returns the raw key. Test a key that exists only in files and one that exists only in DB.
  • Missing keys fail silently as the key string. __('messages.foo') returning messages.foo is the normal Laravel miss behavior, but with DB sources it's easy to misdiagnose as a loader bug. Log misses (app('translator')->getLoader() or a missing-translation listener) during migration.
  • Cache is the #1 "my edit didn't apply" cause. After changing a line, run php artisan cache:clear (or optimize:clear) and confirm no opcache/view cache is involved. Wire model events to cache invalidation early.
  • Schema and JSON column type. The text column stores a locale→string map. On MySQL use json (or at least text with json_encode); on SQLite/Postgres confirm the migration matches your driver. Malformed JSON silently becomes a missing translation.
  • Query volume. A naive loader does one query per group+locale per request. For pages with many groups, either eager-load all lines for the locale into the translator once, or add a cache layer in a custom loader. Watch DB::listen during a page render.
  • Don't edit vendor/. Publish config and migrations; if you need behavior changes, subclass the loader/model in app/ and bind it in a service provider. That keeps upgrades survivable.
  • Multi-tenant and permission concerns. Once translations are DB rows, they're data: back them up, include them in seeders, and decide who can edit. There is no built-in audit trail — add updated_by/timestamps if editors are non-technical.
  • YAML source needs the parser. If you use the YAML loader, confirm symfony/yaml is installed and that YAML files live where the loader expects (usually lang/{locale}/*.yml). Flat dotted keys and nested keys behave differently — pick one convention.
  • Testing extension point. The cleanest test setup is the file source. If the package doesn't expose an env-driven source switch, add one in the published config; it's a one-line change and saves DB fixtures in every test.
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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log