cms/app/Services/TranslationManager.php
Funky Waddle 37ed997989 feat(cms): initialize Laravel project structure and core CMS files
- Added standard Laravel directory structure and configuration.

- Included Svelte and Tailwind configuration for the admin interface.

- Added core PHPUnit and testing scripts.
2026-04-13 12:48:06 -05:00

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;
}
}