AI extended workkit
This commit is contained in:
parent
4b61653d2d
commit
84cc1d15d4
|
|
@ -16,21 +16,27 @@
|
|||
"autoload": {
|
||||
"psr-4": {
|
||||
"Blax\\Workkit\\": "src"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"laravel/framework": "*"
|
||||
"laravel/framework": "*",
|
||||
"spatie/once": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.22"
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": []
|
||||
"providers": [
|
||||
"Blax\\Workkit\\WorkkitServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Blax\Workkit;
|
||||
|
||||
class WorkkitServiceProvider extends \Illuminate\Support\ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap the application events.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use JsonException;
|
||||
|
||||
class IncompleteJsonService
|
||||
{
|
||||
private $parsers = [];
|
||||
|
||||
private $lastParseReminding = null;
|
||||
|
||||
private $onExtraToken;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->parsers = array_fill_keys([' ', "\r", "\n", "\t"], 'parseSpace');
|
||||
$this->parsers['['] = 'parseArray';
|
||||
$this->parsers['{'] = 'parseObject';
|
||||
$this->parsers['"'] = 'parseString';
|
||||
$this->parsers['t'] = 'parseTrue';
|
||||
$this->parsers['f'] = 'parseFalse';
|
||||
$this->parsers['n'] = 'parseNull';
|
||||
|
||||
foreach (str_split('0123456789.-') as $char) {
|
||||
$this->parsers[$char] = 'parseNumber';
|
||||
}
|
||||
|
||||
$this->onExtraToken = function ($text, $data, $reminding) {
|
||||
echo 'Parsed JSON with extra tokens: '.json_encode(['text' => $text, 'data' => $data, 'reminding' => $reminding]);
|
||||
};
|
||||
}
|
||||
|
||||
public function parse($s, bool $associative = true)
|
||||
{
|
||||
if (strlen($s) >= 1) {
|
||||
try {
|
||||
return json_decode($s, $associative, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
[$data, $reminding] = $this->parseAny($s, $e);
|
||||
$this->lastParseReminding = $reminding;
|
||||
if ($this->onExtraToken && $reminding) {
|
||||
call_user_func($this->onExtraToken, $s, $data, $reminding);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
} else {
|
||||
return json_decode('{}', $associative);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseAny($s, $e)
|
||||
{
|
||||
if (! $s) {
|
||||
throw $e;
|
||||
}
|
||||
$parser = $this->parsers[$s[0]] ?? null;
|
||||
if (! $parser) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->{$parser}($s, $e);
|
||||
}
|
||||
|
||||
private function parseSpace($s, $e)
|
||||
{
|
||||
return $this->parseAny(trim($s), $e);
|
||||
}
|
||||
|
||||
private function parseArray($s, $e)
|
||||
{
|
||||
$s = substr($s, 1); // skip starting '['
|
||||
$acc = [];
|
||||
$s = trim($s);
|
||||
while ($s) {
|
||||
if ($s[0] == ']') {
|
||||
$s = substr($s, 1); // skip ending ']'
|
||||
break;
|
||||
}
|
||||
[$res, $s] = $this->parseAny($s, $e);
|
||||
$acc[] = $res;
|
||||
$s = trim($s);
|
||||
if (strpos($s, ',') === 0) {
|
||||
$s = substr($s, 1);
|
||||
$s = trim($s);
|
||||
}
|
||||
}
|
||||
|
||||
return [$acc, $s];
|
||||
}
|
||||
|
||||
private function parseObject($s, $e)
|
||||
{
|
||||
$s = substr($s, 1); // skip starting '{'
|
||||
$acc = [];
|
||||
$s = trim($s);
|
||||
while ($s) {
|
||||
if ($s[0] == '}') {
|
||||
$s = substr($s, 1); // skip ending '}'
|
||||
break;
|
||||
}
|
||||
[$key, $s] = $this->parseAny($s, $e);
|
||||
$s = trim($s);
|
||||
|
||||
if (! $s || $s[0] == '}') {
|
||||
$acc[$key] = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($s[0] != ':') {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$s = substr($s, 1); // skip ':'
|
||||
$s = trim($s);
|
||||
|
||||
if (! $s || in_array($s[0], [',', '}'])) {
|
||||
$acc[$key] = null;
|
||||
if (strpos($s, ',') === 0) {
|
||||
$s = substr($s, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
[$value, $s] = $this->parseAny($s, $e);
|
||||
$acc[$key] = $value;
|
||||
$s = trim($s);
|
||||
if (strpos($s, ',') === 0) {
|
||||
$s = substr($s, 1);
|
||||
$s = trim($s);
|
||||
}
|
||||
}
|
||||
|
||||
return [$acc, $s];
|
||||
}
|
||||
|
||||
private function parseString($s, $e)
|
||||
{
|
||||
$end = strpos($s, '"', 1);
|
||||
while ($end !== false && $s[$end - 1] == '\\') { // Handle escaped quotes
|
||||
$end = strpos($s, '"', $end + 1);
|
||||
}
|
||||
if ($end === false) {
|
||||
// Return the incomplete string without the opening quote
|
||||
return [substr($s, 1), ''];
|
||||
}
|
||||
$strVal = substr($s, 0, $end + 1);
|
||||
$s = substr($s, $end + 1);
|
||||
|
||||
return [json_decode($strVal), $s];
|
||||
}
|
||||
|
||||
private function parseNumber($s, $e)
|
||||
{
|
||||
$i = 0;
|
||||
while ($i < strlen($s) && strpos('0123456789.-', $s[$i]) !== false) {
|
||||
$i++;
|
||||
}
|
||||
$numStr = substr($s, 0, $i);
|
||||
$s = substr($s, $i);
|
||||
if ($numStr == '' || substr($numStr, -1) == '.' || substr($numStr, -1) == '-') {
|
||||
// Return the incomplete number as is
|
||||
return [$numStr, ''];
|
||||
}
|
||||
if (strpos($numStr, '.') !== false || strpos($numStr, 'e') !== false || strpos($numStr, 'E') !== false) {
|
||||
$num = floatval($numStr);
|
||||
} else {
|
||||
$num = intval($numStr);
|
||||
}
|
||||
|
||||
return [$num, $s];
|
||||
}
|
||||
|
||||
private function parseTrue($s, $e)
|
||||
{
|
||||
if (substr($s, 0, 4) == 'true') {
|
||||
return [true, substr($s, 4)];
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
private function parseFalse($s, $e)
|
||||
{
|
||||
if (substr($s, 0, 5) == 'false') {
|
||||
return [false, substr($s, 5)];
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
private function parseNull($s, $e)
|
||||
{
|
||||
if (substr($s, 0, 4) == 'null') {
|
||||
return [null, substr($s, 4)];
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
namespace Blax\Workkit\Services;
|
||||
|
||||
use App\Services\IncompleteJsonService;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class MiscService
|
||||
{
|
||||
public static function asPaginated(
|
||||
$paginated,
|
||||
$resource_class,
|
||||
array $meta = []
|
||||
) {
|
||||
$data = $paginated->toArray();
|
||||
|
||||
$payload = [
|
||||
'data' => $resource_class::collection($paginated),
|
||||
'meta' => [
|
||||
'from' => @$data['from'],
|
||||
'to' => @$data['to'],
|
||||
'total' => @$data['total'],
|
||||
'last_page' => @$data['last_page'],
|
||||
'current_page' => @$data['current_page'],
|
||||
'options' => (object) request('options'),
|
||||
],
|
||||
];
|
||||
|
||||
if ($meta) {
|
||||
$payload['meta'] = array_merge($payload['meta'], $meta);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public static function bytesToHuman($bytes)
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
|
||||
for ($i = 0; $bytes > 1024; $i++) {
|
||||
$bytes /= 1024;
|
||||
}
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$i];
|
||||
}
|
||||
|
||||
public static function deterministicEncrypt($data)
|
||||
{
|
||||
return base64_encode(openssl_encrypt($data, 'AES-128-ECB', config('app.key'), OPENSSL_RAW_DATA));
|
||||
}
|
||||
|
||||
public static function deterministicDecrypt($encrypted)
|
||||
{
|
||||
return openssl_decrypt(base64_decode($encrypted), 'AES-128-ECB', config('app.key'), OPENSSL_RAW_DATA);
|
||||
}
|
||||
|
||||
public static function logExecutionTime(
|
||||
string $logtext,
|
||||
$callable = null
|
||||
) {
|
||||
$start = microtime(true);
|
||||
|
||||
if (!$callable) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $callable();
|
||||
$end = microtime(true);
|
||||
|
||||
$executionTime = $end - $start;
|
||||
|
||||
Log::debug($logtext, [
|
||||
'execution_time' => $executionTime
|
||||
]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getIpInformation($ip)
|
||||
{
|
||||
return once(function () use ($ip) {
|
||||
return cache()->flexible('ipapi-' . $ip, [60 * 60 * 24 * 2, 60 * 60 * 24 * 7], function () use ($ip) {
|
||||
$response = Http::get("https://ipapi.co/{$ip}/json/");
|
||||
|
||||
if ($response->failed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $response->json();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static function countryToCode($country_long): ?string
|
||||
{
|
||||
return match (str()->lower($country_long)) {
|
||||
'deutschland' => 'de',
|
||||
'österreich' => 'at',
|
||||
'schweiz' => 'ch',
|
||||
'spanien' => 'es',
|
||||
'luxemburg' => 'lu',
|
||||
'estland' => 'ee',
|
||||
'belgien' => 'be',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public static function codeToCountry($country_code, string|null $locale = null)
|
||||
{
|
||||
$country_code = str()->lower($country_code);
|
||||
$locale ??= app()->getLocale();
|
||||
|
||||
if ($locale === 'de') {
|
||||
return match ($country_code) {
|
||||
'de' => 'Deutschland',
|
||||
'at' => 'Österreich',
|
||||
'ch' => 'Schweiz',
|
||||
'es' => 'Spanien',
|
||||
'lu' => 'Luxemburg',
|
||||
'ee' => 'Estland',
|
||||
'be' => 'Belgien',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
if ($locale === 'es') {
|
||||
return match ($country_code) {
|
||||
'de' => 'Alemania',
|
||||
'at' => 'Austria',
|
||||
'ch' => 'Suiza',
|
||||
'es' => 'España',
|
||||
'lu' => 'Luxemburgo',
|
||||
'ee' => 'Estonia',
|
||||
'be' => 'Bélgica',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
if ($locale === 'uk') {
|
||||
return match ($country_code) {
|
||||
'de' => 'Німеччина',
|
||||
'at' => 'Австрія',
|
||||
'ch' => 'Швейцарія',
|
||||
'es' => 'Іспанія',
|
||||
'lu' => 'Люксембург',
|
||||
'ee' => 'Естонія',
|
||||
'be' => 'Бельгія',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
return match ($country_code) {
|
||||
'de' => 'Germany',
|
||||
'at' => 'Austria',
|
||||
'ch' => 'Switzerland',
|
||||
'es' => 'Spain',
|
||||
'lu' => 'Luxembourg',
|
||||
'ee' => 'Estonia',
|
||||
'be' => 'Belgium',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
public static function parseIncompleteJson(
|
||||
string $json,
|
||||
bool $associative = true
|
||||
): array|object|null {
|
||||
return (new IncompleteJsonService())->parse($json, $associative);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait HasMeta
|
||||
{
|
||||
public function getMeta(): object
|
||||
{
|
||||
return (object) $this->meta;
|
||||
}
|
||||
|
||||
public final function updateMetaKey($key, $value, bool $update = true): self
|
||||
{
|
||||
$meta = $this->getMeta();
|
||||
$meta->{$key} = $value;
|
||||
$this->meta = (object) $meta;
|
||||
|
||||
if ($update) {
|
||||
$this->update(['meta' => $this->meta]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Services\IncompleteJsonService;
|
||||
|
||||
trait HasMetaTranslation
|
||||
{
|
||||
use HasMeta;
|
||||
|
||||
protected array $__localizedFallbackGuard = [];
|
||||
|
||||
public function getMeta(): object
|
||||
{
|
||||
$r = (object) $this->meta;
|
||||
$r->i18n ??= (object) [];
|
||||
|
||||
if (is_array($r->i18n)) {
|
||||
$r->i18n = (object) $r->i18n;
|
||||
}
|
||||
|
||||
foreach (config('app.i18n.supporting') as $lang => $longlang) {
|
||||
if ($lang == 'en') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($r->i18n->{$lang})) {
|
||||
$r->i18n->{$lang} = (object) [];
|
||||
} elseif (is_array($r->i18n->{$lang})) {
|
||||
// Normalize any existing array locale bucket to object
|
||||
$r->i18n->{$lang} = (object) $r->i18n->{$lang};
|
||||
}
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
public function getLocalized($key, string|null $locale = null, bool $allowAttrFallback = true)
|
||||
{
|
||||
$locale = $locale ?: request()->get('locale') ?: app()->getLocale() ?: 'en';
|
||||
|
||||
$meta = $this->getMeta();
|
||||
$i18n = $meta->i18n ?? null;
|
||||
|
||||
$get = function ($container, $prop) {
|
||||
if ($container === null) {
|
||||
return null;
|
||||
}
|
||||
if (is_array($container)) {
|
||||
return $container[$prop] ?? null;
|
||||
}
|
||||
if (is_object($container)) {
|
||||
return $container->{$prop} ?? null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Build recursive fallback chain from config
|
||||
$fallbackMap = (array) config('app.i18n.fallback', []);
|
||||
$chain = [];
|
||||
$visited = [];
|
||||
|
||||
$expand = function ($loc) use (&$expand, &$chain, &$visited, $fallbackMap) {
|
||||
if (!$loc || isset($visited[$loc])) {
|
||||
return;
|
||||
}
|
||||
$visited[$loc] = true;
|
||||
$chain[] = $loc;
|
||||
if (!isset($fallbackMap[$loc])) {
|
||||
return;
|
||||
}
|
||||
$targets = (array) $fallbackMap[$loc];
|
||||
foreach ($targets as $t) {
|
||||
$expand($t);
|
||||
}
|
||||
};
|
||||
|
||||
$expand($locale);
|
||||
|
||||
// Always ensure 'en' ends up as last resort if not already included
|
||||
if (!in_array('en', $chain, true)) {
|
||||
$expand('en');
|
||||
}
|
||||
|
||||
$value = null;
|
||||
foreach ($chain as $loc) {
|
||||
// Prefer normalized meta object path
|
||||
$candidate = $get($get($i18n, $loc), $key);
|
||||
if ($candidate === null || $candidate === '') {
|
||||
// Try raw meta structure if different
|
||||
$candidate = $get($get($get($this->meta ?? null, 'i18n'), $loc), $key);
|
||||
}
|
||||
if ($candidate !== null && $candidate !== '') {
|
||||
$value = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Optional model attribute fallback (raw attribute array only to avoid recursion)
|
||||
if (($value === null || $value === '') && $allowAttrFallback) {
|
||||
$attr = $this->attributes[$key] ?? null;
|
||||
if ($attr !== null && $attr !== '') {
|
||||
$value = $attr;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply model casts (e.g., json) when present so localized values respect attribute casting
|
||||
if ($value !== null && $value !== '') {
|
||||
try {
|
||||
$casts = method_exists($this, 'getCasts') ? $this->getCasts() : (property_exists($this, 'casts') ? (array) $this->casts : []);
|
||||
$cast = $casts[$key] ?? null;
|
||||
$castStr = is_string($cast) ? strtolower($cast) : '';
|
||||
|
||||
$isJsonLike = (function ($t) {
|
||||
if (!$t) return false;
|
||||
$t = strtolower($t);
|
||||
return $t === 'array' || $t === 'object' || $t === 'collection' || str_contains($t, 'json');
|
||||
})($castStr);
|
||||
|
||||
if ($isJsonLike) {
|
||||
if (method_exists($this, 'hasCast') && method_exists($this, 'castAttribute') && $this->hasCast($key)) {
|
||||
// Let Eloquent do the casting for consistency
|
||||
$value = $this->castAttribute($key, $value);
|
||||
} else {
|
||||
// Minimal manual handling for json/array/object casts
|
||||
if (is_string($value)) {
|
||||
$assoc = ($castStr === 'array' || str_contains($castStr, 'json'));
|
||||
$decoded = json_decode($value, $assoc);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$value = $decoded;
|
||||
}
|
||||
} elseif (is_object($value) && ($castStr === 'array' || str_contains($castStr, 'json'))) {
|
||||
$value = json_decode(json_encode($value), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Swallow casting errors to avoid breaking localization
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function setLocalized($key, $value, string|null $locale = null, bool $save = false)
|
||||
{
|
||||
$locale = $locale ?: request()->get('locale') ?: app()->getLocale() ?: 'en';
|
||||
|
||||
$meta = $this->getMeta();
|
||||
$i18n = (object) ($meta->i18n ?? []);
|
||||
|
||||
if (
|
||||
$locale == 'en'
|
||||
&& array_key_exists($key, $this->attributes)
|
||||
) {
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
|
||||
if (!isset($i18n->{$locale})) {
|
||||
$i18n->{$locale} = (object) [];
|
||||
} elseif (is_array($i18n->{$locale})) {
|
||||
// Normalize existing array to object before property assignment
|
||||
$i18n->{$locale} = (object) $i18n->{$locale};
|
||||
}
|
||||
|
||||
$i18n->{$locale}->{$key} = $value;
|
||||
|
||||
$meta->i18n = $i18n;
|
||||
$this->meta = (object) $meta;
|
||||
|
||||
if ($save) {
|
||||
$this->update(['meta' => $this->meta]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function wipeTranslation($locale = 'all')
|
||||
{
|
||||
$meta = $this->getMeta();
|
||||
$i18n = (object) ($meta->i18n ?? []);
|
||||
|
||||
if ($locale === 'all') {
|
||||
// Wipe all translations
|
||||
$i18n = (object) [];
|
||||
} elseif (isset($i18n->{$locale})) {
|
||||
// Wipe specific locale
|
||||
$i18n->{$locale} = (object) [];
|
||||
}
|
||||
|
||||
$meta->i18n = $i18n;
|
||||
$this->meta = (object) $meta;
|
||||
|
||||
$this->update(['meta' => $this->meta]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all supported languages that are still an empty object
|
||||
* */
|
||||
public function getMissingTranslationLanguagesAttribute()
|
||||
{
|
||||
$meta = $this->getMeta();
|
||||
$i18n = (object) ($meta->i18n ?? []);
|
||||
|
||||
$missing = [];
|
||||
|
||||
foreach (config('app.i18n.supporting') as $lang => $longlang) {
|
||||
if ($lang == 'en') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$container = $i18n->{$lang} ?? null;
|
||||
|
||||
if ($container === null) {
|
||||
$missing[] = $lang;
|
||||
} elseif (is_array($container) && count($container) === 0) {
|
||||
$missing[] = $lang;
|
||||
} elseif (is_object($container) && count(get_object_vars($container)) === 0) {
|
||||
$missing[] = $lang;
|
||||
}
|
||||
}
|
||||
|
||||
return $missing;
|
||||
}
|
||||
|
||||
public final function getMissingTranslationKeysAttribute()
|
||||
{
|
||||
// assume static variable $translatables, where keys of translatable fillables are listed
|
||||
$missing = [];
|
||||
$translatables = property_exists($this, 'translatables') ? (array) $this->translatables : [];
|
||||
$supported_langs = array_keys(config('app.i18n.supporting', []));
|
||||
|
||||
// get translatables from meta i18n as well
|
||||
$meta = $this->getMeta();
|
||||
$i18n_keys = array_keys((array) (((object) ($meta->i18n ?? []))?->en ?? []));
|
||||
$translatables = array_unique(array_merge($translatables, $i18n_keys));
|
||||
|
||||
|
||||
foreach ($translatables as $key) {
|
||||
foreach ($supported_langs as $lang) {
|
||||
if ($lang == 'en') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->getLocalized($key, $lang, false);
|
||||
|
||||
if (
|
||||
$value == null
|
||||
|| $value == ''
|
||||
|| $value == $this->getLocalized($key, 'en', false)
|
||||
) {
|
||||
$missing[$key][] = $lang;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $missing;
|
||||
}
|
||||
|
||||
public final function getMissingKeyLanguagesAttribute()
|
||||
{
|
||||
$missing = $this->missing_translation_keys;
|
||||
$result = [];
|
||||
|
||||
foreach ($missing as $key => $langs) {
|
||||
foreach ($langs as $lang) {
|
||||
$result[$lang][] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Generic dynamic attribute fallback to localization (replaces specific title accessor)
|
||||
public function __get($key)
|
||||
{
|
||||
$value = parent::__get($key);
|
||||
|
||||
if (($value === null || $value === '')
|
||||
&& !in_array($key, ['meta', 'i18n'])
|
||||
&& empty($this->__localizedFallbackGuard[$key])
|
||||
) {
|
||||
$this->__localizedFallbackGuard[$key] = true;
|
||||
try {
|
||||
$localized = $this->getLocalized($key, null, false);
|
||||
} finally {
|
||||
unset($this->__localizedFallbackGuard[$key]);
|
||||
}
|
||||
if ($localized !== null && $localized !== '') {
|
||||
return $localized;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function scopeWhereI18n($query, $key, $value, $locale = null)
|
||||
{
|
||||
if ($locale) {
|
||||
$locale = $locale ?: app()->getLocale() ?: 'en';
|
||||
return $query->where("meta->i18n->{$locale}->{$key}", $value);
|
||||
} else {
|
||||
return $query->where(function ($q) use ($key, $value) {
|
||||
foreach (array_keys(config('app.i18n.supporting', [])) as $locale) {
|
||||
$q->orWhere("meta->i18n->{$locale}->{$key}", $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function scopeOrWhereI18n($query, $key, $value, $locale = null)
|
||||
{
|
||||
if ($locale) {
|
||||
$locale = $locale ?: app()->getLocale() ?: 'en';
|
||||
return $query->orWhere("meta->i18n->{$locale}->{$key}", $value);
|
||||
} else {
|
||||
return $query->orWhere(function ($q) use ($key, $value) {
|
||||
foreach (array_keys(config('app.i18n.supporting', [])) as $locale) {
|
||||
$q->orWhere("meta->i18n->{$locale}->{$key}", $value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
use Blax\Workkit\Services\MiscService;
|
||||
|
||||
if (!function_exists('misc')) {
|
||||
function misc(): MiscService
|
||||
{
|
||||
static $instance;
|
||||
|
||||
if (!$instance) {
|
||||
$instance = new MiscService();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue