*/ protected array $skipFiles = [ 'faqs', 'sections', 'search_index', 'validation', 'auth', 'passwords', 'pagination', ]; /** * Keys to skip within specific groups (handled by dedicated models). * * @var array> */ protected array $skipKeys = [ 'components' => ['news_band', 'industries_band'], ]; public function run(): void { $locales = ['de', 'en']; $deFiles = glob(lang_path('de/*.php')); if (! $deFiles) { return; } foreach ($deFiles as $filePath) { $group = pathinfo($filePath, PATHINFO_FILENAME); if (in_array($group, $this->skipFiles)) { continue; } $translations = []; foreach ($locales as $locale) { $localePath = lang_path("{$locale}/{$group}.php"); if (file_exists($localePath)) { $translations[$locale] = require $localePath; } } if (empty($translations)) { continue; } $deData = $translations['de'] ?? []; $enData = $translations['en'] ?? []; $flatDe = $this->flatten($deData); $flatEn = $this->flatten($enData); $allKeys = array_keys($flatDe); foreach (array_keys($flatEn) as $enKey) { if (! in_array($enKey, $allKeys)) { $allKeys[] = $enKey; } } $order = 0; foreach ($allKeys as $key) { if ($this->shouldSkipKey($group, $key)) { continue; } $deValue = $flatDe[$key] ?? null; $enValue = $flatEn[$key] ?? null; $type = $this->detectType($deValue ?? $enValue); $translatedValue = []; if ($deValue !== null) { $translatedValue['de'] = is_string($deValue) ? $this->cleanHtml($deValue) : $deValue; } if ($enValue !== null) { $translatedValue['en'] = is_string($enValue) ? $this->cleanHtml($enValue) : $enValue; } CmsContent::updateOrCreate( ['group' => $group, 'key' => $key], [ 'type' => $type, 'value' => $translatedValue, 'order' => $order++, ] ); } } } /** * Flatten a nested array into dot-notation keys. * Arrays of objects (indexed arrays) are stored as JSON type. * * @return array */ protected function flatten(array $array, string $prefix = ''): array { $result = []; foreach ($array as $key => $value) { $fullKey = $prefix ? "{$prefix}.{$key}" : (string) $key; if (is_array($value) && ! Arr::isAssoc($value)) { $result[$fullKey] = $value; } elseif (is_array($value)) { $result = array_merge($result, $this->flatten($value, $fullKey)); } else { $result[$fullKey] = $value; } } return $result; } protected function detectType(mixed $value): string { if (is_array($value)) { return 'json'; } if (! is_string($value)) { return 'text'; } if (preg_match('/<[a-z][\s\S]*>/i', $value)) { return 'html'; } if (preg_match('/\.(jpg|jpeg|png|gif|webp|svg)(\?.*)?$/i', $value)) { return 'image'; } if (preg_match('/\.(pdf|doc|docx)$/i', $value)) { return 'link'; } return 'text'; } /** * Clean HTML by converting font-weight spans to tags. * Preserves text-gradient-premium spans and email-protection spans. */ protected function cleanHtml(string $value): string { $value = preg_replace( '/(\s*)(.*?)<\/span>/si', '$1$2', $value ); return $value; } protected function shouldSkipKey(string $group, string $key): bool { if (! isset($this->skipKeys[$group])) { return false; } foreach ($this->skipKeys[$group] as $skipPrefix) { if ($key === $skipPrefix || str_starts_with($key, "{$skipPrefix}.")) { return true; } } return false; } }