2025-09-23 13:20:23 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Helpers;
|
|
|
|
|
|
|
|
|
|
use App\Contracts\TranslatableModel;
|
2025-09-30 15:51:54 +02:00
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
|
use Illuminate\Support\Arr;
|
2025-09-23 13:20:23 +02:00
|
|
|
use Illuminate\Support\Facades\App;
|
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
|
|
|
|
|
|
class TranslationHelper
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Get all cached translations.
|
|
|
|
|
*
|
|
|
|
|
* @param string|null $locale
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
public static function getCachedTranslations(?string $locale = null): array
|
|
|
|
|
{
|
|
|
|
|
if (is_null($locale)) {
|
|
|
|
|
$locale = app()->getLocale();
|
|
|
|
|
}
|
|
|
|
|
return Cache::remember(sprintf('translations_%s', app()->getLocale()), 86400, function () {
|
|
|
|
|
$translator = App::make('translator');
|
|
|
|
|
$property = (new \ReflectionClass($translator))->getProperty('loaded');
|
|
|
|
|
$property->setAccessible(true);
|
|
|
|
|
return $property->getValue($translator);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assert fallback locale translation is present.
|
|
|
|
|
*
|
|
|
|
|
* @param \App\Contracts\TranslatableModel $model
|
2026-02-10 15:43:47 +01:00
|
|
|
* @param array $excludeKeys Key of the model we need to keep nullable.
|
2025-09-23 13:20:23 +02:00
|
|
|
* @return void
|
|
|
|
|
* @throws \Illuminate\Validation\ValidationException Translations missing.
|
|
|
|
|
*/
|
2026-01-09 17:03:30 +01:00
|
|
|
public static function validateFallbackTranslations(TranslatableModel $model, array $excludeKeys = []): void
|
2025-09-23 13:20:23 +02:00
|
|
|
{
|
|
|
|
|
$localeFallback = config('app.fallback_locale');
|
|
|
|
|
$missingTranslations = [];
|
2026-02-10 15:43:47 +01:00
|
|
|
$excludeFields = array_merge(['slug'], $excludeKeys);
|
2026-01-09 17:03:30 +01:00
|
|
|
foreach (Arr::except($model->getTranslations(), $excludeFields) as $key => $value) {
|
2025-09-23 13:20:23 +02:00
|
|
|
if (!is_null($model->{$key}) && !$model->hasTranslation($key, $localeFallback)) {
|
|
|
|
|
$validationTranslations = self::getCachedTranslations()['validation'];
|
|
|
|
|
$field = (array_key_exists($key, $validationTranslations['attributes']))
|
|
|
|
|
? trans("validation.attributes.$key")
|
|
|
|
|
: trans("validation.custom.$key");
|
|
|
|
|
$missingTranslations = array_merge($missingTranslations, [
|
|
|
|
|
$key => trans_choice('back.missing_translations', 1, [
|
|
|
|
|
'field' => $field,
|
|
|
|
|
'locales' => Str::upper($localeFallback),
|
|
|
|
|
]),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ($missingTranslations) {
|
|
|
|
|
throw ValidationException::withMessages($missingTranslations);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-30 15:51:54 +02:00
|
|
|
|
|
|
|
|
/**
|
2026-02-19 14:06:28 +01:00
|
|
|
* Assert that a value is unique inside a JSON translation field.
|
|
|
|
|
*
|
|
|
|
|
* The uniqueness can optionally be scoped to another attribute/value pair.
|
|
|
|
|
* If $anotherDbAttribute and $anotherValue are provided, the uniqueness
|
|
|
|
|
* will be checked only within that scope. Otherwise, the value must be
|
|
|
|
|
* globally unique.
|
|
|
|
|
*
|
|
|
|
|
* @param \Illuminate\Database\Eloquent\Model $model The model instance being validated.
|
|
|
|
|
* @param string $value The translated value to check.
|
|
|
|
|
* @param string $dbAttribute The JSON column containing translations.
|
|
|
|
|
* @param string|null $anotherValue Optional value used to scope uniqueness.
|
|
|
|
|
* @param string|null $anotherDbAttribute Optional database column used to scope uniqueness.
|
|
|
|
|
* @param string|null $errorAttribute Optional attribute name used in the validation error.
|
|
|
|
|
* @param string|null $errorTranslation Optional translated attribute label for the error message.
|
2025-09-30 15:51:54 +02:00
|
|
|
*
|
|
|
|
|
* @return void
|
2026-02-19 14:06:28 +01:00
|
|
|
*
|
|
|
|
|
* @throws \Illuminate\Validation\ValidationException If the value is already used.
|
2025-09-30 15:51:54 +02:00
|
|
|
*/
|
|
|
|
|
public static function assertTranslationFieldIsUnique(
|
|
|
|
|
Model $model,
|
|
|
|
|
string $value,
|
|
|
|
|
string $dbAttribute,
|
2026-02-19 14:06:28 +01:00
|
|
|
?string $anotherValue = null,
|
|
|
|
|
?string $anotherDbAttribute = null,
|
2025-09-30 15:51:54 +02:00
|
|
|
?string $errorAttribute = null,
|
|
|
|
|
?string $errorTranslation = null,
|
|
|
|
|
): void {
|
|
|
|
|
$modelClass = get_class($model);
|
2026-02-19 14:06:28 +01:00
|
|
|
$query = $modelClass::query()
|
2025-09-30 15:51:54 +02:00
|
|
|
->whereKeyNot($model->getKey())
|
2026-02-19 14:06:28 +01:00
|
|
|
->whereRaw("JSON_SEARCH(`{$dbAttribute}`, 'one', ?) IS NOT NULL", [$value]);
|
|
|
|
|
|
2026-07-02 12:16:43 +02:00
|
|
|
if ($anotherDbAttribute !== null) {
|
2026-02-19 14:06:28 +01:00
|
|
|
$query->where($anotherDbAttribute, $anotherValue);
|
|
|
|
|
}
|
|
|
|
|
if ($query->exists()) {
|
2025-09-30 15:51:54 +02:00
|
|
|
throw ValidationException::withMessages([
|
|
|
|
|
$errorAttribute ?? $dbAttribute => trans("validation.unique", [
|
|
|
|
|
'attribute' => $errorTranslation ?? trans("validation.attributes.$dbAttribute"),
|
|
|
|
|
])
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-23 13:20:23 +02:00
|
|
|
}
|