- Added standard Laravel directory structure and configuration. - Included Svelte and Tailwind configuration for the admin interface. - Added core PHPUnit and testing scripts.
172 lines
5.3 KiB
PHP
172 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Translation;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
/**
|
|
* Service to handle site-wide translations with database-backed overrides.
|
|
*/
|
|
class TranslationManager
|
|
{
|
|
/**
|
|
* @var string The current locale.
|
|
*/
|
|
protected string $locale;
|
|
|
|
/**
|
|
* TranslationManager constructor.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->locale = config('app.locale');
|
|
}
|
|
|
|
/**
|
|
* Set the current locale for translations.
|
|
*
|
|
* @param string $locale The locale string (e.g., 'en', 'fr').
|
|
* @return void
|
|
*/
|
|
public function setLocale(string $locale): void
|
|
{
|
|
$this->locale = $locale;
|
|
}
|
|
|
|
/**
|
|
* Get the current locale used by the manager.
|
|
*
|
|
* @return string The current locale.
|
|
*/
|
|
public function getLocale(): string
|
|
{
|
|
return $this->locale;
|
|
}
|
|
|
|
/**
|
|
* Translate the given key using database overrides first, then Laravel's language files.
|
|
*
|
|
* @param string $key The translation key (e.g., 'pages.title' or 'cms::pages.title').
|
|
* @param array $replace Key-value pairs for placeholder replacements.
|
|
* @param string|null $locale Override locale for this specific translation.
|
|
* @return string The translated string or a formatted key.
|
|
*/
|
|
public function translate(string $key, array $replace = [], ?string $locale = null): string
|
|
{
|
|
$locale = $locale ?: $this->locale;
|
|
|
|
/**
|
|
* 1. Split key into group and item.
|
|
* SiteWeaver uses 'group.item' or 'package::group.item'.
|
|
* If no group is provided, we default to 'cms'.
|
|
*/
|
|
$group = 'cms';
|
|
$item = $key;
|
|
|
|
if (str_contains($key, '::')) {
|
|
[$group, $item] = explode('::', $key);
|
|
} elseif (str_contains($key, '.')) {
|
|
[$group, $item] = explode('.', $key, 2);
|
|
}
|
|
|
|
/**
|
|
* 2. Check Database for Override.
|
|
* We use Cache::rememberForever to avoid frequent database hits for static strings.
|
|
* The cache is cleared in updateOverride() when a value is changed.
|
|
*/
|
|
$cacheKey = "translation.{$locale}.{$group}.{$item}";
|
|
$value = Cache::rememberForever($cacheKey, function () use ($locale, $group, $item) {
|
|
$record = Translation::where('locale', $locale)
|
|
->where('group', $group)
|
|
->where('key', $item)
|
|
->first();
|
|
|
|
return $record ? $record->value : null;
|
|
});
|
|
|
|
/**
|
|
* 3. Fallback to Laravel's built-in __()
|
|
* if no database override exists.
|
|
*/
|
|
if ($value === null) {
|
|
$translated = __($key, $replace, $locale);
|
|
|
|
// If __() returns the key itself, it means it's not found in files either.
|
|
// In this case, we format the key into a human-readable fallback (e.g. 'my_key' -> 'My key').
|
|
return $translated === $key ? $this->formatKey($key) : $translated;
|
|
}
|
|
|
|
/**
|
|
* 4. Replace placeholders in DB value.
|
|
* We manually handle :placeholder replacement to mirror Laravel's behavior
|
|
* for database-sourced strings.
|
|
*/
|
|
return $this->makeReplacements($value, $replace);
|
|
}
|
|
|
|
/**
|
|
* Store or update a translation override in the database and clear relevant cache.
|
|
*
|
|
* @param string $locale The locale for the override.
|
|
* @param string $group The translation group/namespace.
|
|
* @param string $key The translation key within the group.
|
|
* @param string $value The override value.
|
|
* @return Translation The created or updated translation instance.
|
|
*/
|
|
public function updateOverride(string $locale, string $group, string $key, string $value): Translation
|
|
{
|
|
$translation = Translation::updateOrCreate(
|
|
['locale' => $locale, 'group' => $group, 'key' => $key],
|
|
['value' => $value]
|
|
);
|
|
|
|
Cache::forget("translation.{$locale}.{$group}.{$key}");
|
|
|
|
return $translation;
|
|
}
|
|
|
|
/**
|
|
* Format a missing translation key into a human-readable string as a fallback.
|
|
*
|
|
* @param string $key The original translation key.
|
|
* @return string The formatted fallback string.
|
|
*/
|
|
protected function formatKey(string $key): string
|
|
{
|
|
// Simple humanizing of the key if not found
|
|
$parts = explode('::', $key);
|
|
$item = end($parts);
|
|
|
|
$parts = explode('.', $item);
|
|
$last = end($parts);
|
|
|
|
return ucfirst(str_replace(['_', '-'], ' ', $last));
|
|
}
|
|
|
|
/**
|
|
* Replace placeholders in a translation string with actual values.
|
|
* Supports :name, :Name, and :NAME formats.
|
|
*
|
|
* @param string $line The translation line with placeholders.
|
|
* @param array $replace Key-value pairs of replacements.
|
|
* @return string The finalized translation string.
|
|
*/
|
|
protected function makeReplacements(string $line, array $replace): string
|
|
{
|
|
if (empty($replace)) {
|
|
return $line;
|
|
}
|
|
|
|
foreach ($replace as $key => $value) {
|
|
$line = str_replace(
|
|
[':'.$key, ':'.ucfirst($key), ':'.strtoupper($key)],
|
|
[$value, ucfirst($value), strtoupper($value)],
|
|
$line
|
|
);
|
|
}
|
|
|
|
return $line;
|
|
}
|
|
}
|