feat(i18n): database-backed translation loader with Redis cache
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
15f4b2c0d8
commit
ce9e53d206
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Translation\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Translation extends Model
|
||||||
|
{
|
||||||
|
protected $guarded = ['id'];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Translation\Providers;
|
||||||
|
|
||||||
|
use App\Domains\Translation\Models\Translation;
|
||||||
|
use Illuminate\Contracts\Translation\Loader;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
class DatabaseTranslationLoader implements Loader
|
||||||
|
{
|
||||||
|
public function load($locale, $group, $namespace = null): array
|
||||||
|
{
|
||||||
|
$cacheKey = "lang:{$locale}:{$group}";
|
||||||
|
|
||||||
|
$cached = Redis::hGetAll($cacheKey);
|
||||||
|
if (! empty($cached)) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations = Translation::where('locale', $locale)
|
||||||
|
->where('group', $group)
|
||||||
|
->pluck('value', 'key')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (! empty($translations)) {
|
||||||
|
Redis::hMSet($cacheKey, $translations);
|
||||||
|
Redis::expire($cacheKey, 3600);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addNamespace($namespace, $hint): void {}
|
||||||
|
public function addPath($path): void {}
|
||||||
|
public function addJsonPath($path): void {}
|
||||||
|
|
||||||
|
public function namespaces(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Translation\Services;
|
||||||
|
|
||||||
|
use App\Domains\Translation\Models\Translation;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
class TranslationService
|
||||||
|
{
|
||||||
|
public function set(string $locale, string $group, string $key, string $value): Translation
|
||||||
|
{
|
||||||
|
$translation = Translation::updateOrCreate(
|
||||||
|
['locale' => $locale, 'group' => $group, 'key' => $key],
|
||||||
|
['value' => $value, 'namespace' => '*'],
|
||||||
|
);
|
||||||
|
|
||||||
|
Redis::del("lang:{$locale}:{$group}");
|
||||||
|
|
||||||
|
return $translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flushCache(string $locale, string $group): void
|
||||||
|
{
|
||||||
|
Redis::del("lang:{$locale}:{$group}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Domains\Translation\Providers\DatabaseTranslationLoader;
|
||||||
use App\Domains\Workspace\Models\Workspace;
|
use App\Domains\Workspace\Models\Workspace;
|
||||||
use App\Domains\Workspace\Policies\WorkspacePolicy;
|
use App\Domains\Workspace\Policies\WorkspacePolicy;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
@ -14,7 +15,21 @@ class AppServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
//
|
$this->app->singleton('translation.loader', function ($app) {
|
||||||
|
return new DatabaseTranslationLoader;
|
||||||
|
});
|
||||||
|
|
||||||
|
// TranslationServiceProvider is deferred and re-binds translation.loader
|
||||||
|
// when it resolves. We extend 'translator' to swap in our DB loader after
|
||||||
|
// the translator instance is created.
|
||||||
|
$this->app->extend('translator', function ($translator, $app) {
|
||||||
|
$loader = new DatabaseTranslationLoader;
|
||||||
|
$reflection = new \ReflectionProperty($translator, 'loader');
|
||||||
|
$reflection->setAccessible(true);
|
||||||
|
$reflection->setValue($translator, $loader);
|
||||||
|
|
||||||
|
return $translator;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Translation\Models\Translation;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Redis::del('lang:de:auth');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
Redis::del('lang:de:auth');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads translation from database via __()', function () {
|
||||||
|
Translation::create([
|
||||||
|
'locale' => 'de',
|
||||||
|
'namespace' => '*',
|
||||||
|
'group' => 'auth',
|
||||||
|
'key' => 'login',
|
||||||
|
'value' => 'Anmelden',
|
||||||
|
]);
|
||||||
|
|
||||||
|
App::setLocale('de');
|
||||||
|
// Force reload
|
||||||
|
app('translator')->setLoaded([]);
|
||||||
|
|
||||||
|
expect(__('auth.login'))->toBe('Anmelden');
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue