@php /* ── Temel değişkenler ─────────────────────────────────────── */ $siteName = settings('branding.site_name'); $siteUrl = rtrim(url('/'), '/'); /* i18n B20 (Day 29) — locale-aware SEO copy. İSİM = içerik (çevrilmez); * ŞABLON/SUFFIX = UI metni (çevrilir). $L aktif locale stringini seçer * (AR/ES/RU henüz yoksa EN'e düşer). TR çıktı byte-identical kalır. */ $locale = app()->getLocale() ?: 'tr'; $L = function (string $tr, string $en, ?string $ar = null, ?string $es = null, ?string $ru = null) use ($locale) { return match ($locale) { 'en' => $en, 'ar' => $ar ?? $en, 'es' => $es ?? $en, 'ru' => $ru ?? $en, default => $tr, }; }; $ogLocaleMap = ['tr' => 'tr_TR', 'en' => 'en_US', 'ar' => 'ar_AR', 'es' => 'es_ES', 'ru' => 'ru_RU']; $ogLocale = $ogLocaleMap[$locale] ?? 'tr_TR'; /* Tür adını aktif locale'e çevir (guarded — genre_translations yoksa TR'ye * düşer, 500 yok). Görünür title'da geçmez ama keywords/JSON-LD genre için. */ $trGenre = function ($name) use ($locale) { $name = trim((string) $name); if ($name === '' || $locale === 'tr') return $name; try { $t = \Illuminate\Support\Facades\DB::table('genre_translations as gt') ->join('genres as g', 'g.id', '=', 'gt.genre_id') ->where('gt.locale', $locale) ->whereRaw('LOWER(g.name) = ?', [mb_strtolower($name, 'UTF-8')]) ->value('gt.name'); return $t ?: $name; } catch (\Throwable $e) { return $name; } }; /* SEO (Problem 3) — anahtar kelime ETİKETLERİ de locale-doğru olmalı. * Eskiden keywords meta'sı ham TR tag adlarını ("mutluluk", "Sevinç", * "dördüncü duvarı yıkmak") TÜM dillerde basıyordu (genre çevriliyordu ama * keyword tag'ları değil) → RU/EN/AR/ES kaynak kodunda TR sızıntısı. * tag_translations (Day 29 i18n E1) ile çevir; non-TR'de çeviri YOKSA * kelimeyi DÜŞÜR (null) → asla TR sızmaz. TR (default): base ad, byte- * identical. genre_translations desenidir; şema yoksa (migrate-race) * try/catch → null (non-TR'de düşer, TR'de zaten base ad döner). */ $trTag = function (string $baseName) use ($locale) { $baseName = trim($baseName); if ($baseName === '') return null; try { $row = \Illuminate\Support\Facades\DB::table('tag_translations as tt') ->join('tags as tg', 'tg.id', '=', 'tt.tag_id') ->where('tt.locale', $locale) ->whereRaw('LOWER(tg.name) = ?', [mb_strtolower($baseName, 'UTF-8')]) ->first(['tt.name', 'tt.display_name']); } catch (\Throwable $e) { return null; } if (!$row) return null; $v = trim((string) ($row->display_name ?: $row->name)); return $v !== '' ? $v : null; }; /* Yönetmen ve oyuncular */ /* R13: kişi adlarını SEO çıktısında Latin alfabesinde göster * (film adı dokunulmaz, sadece oyuncu/yönetmen isimleri). */ $directorName = (isset($credits['directing']) && $credits['directing']->isNotEmpty()) ? latinizeName($credits['directing']->first()->name) : null; $topCastArr = (isset($credits['actors']) && $credits['actors']->isNotEmpty()) ? $credits['actors']->take(4)->map(fn($a) => latinizeName($a->name))->toArray() : []; $topCastStr = implode(', ', array_slice($topCastArr, 0, 3)); /* PUAN OVERHAUL C2.5 devamı (6 Tem, kod taraması bulgusu): eskiden TMDB * ortalaması meta description'da "IMDb X/10" diye ETİKETLENİYORDU (yanlış * kaynak etiketi). Artık gerçek imdb_rating varsa IMDb etiketi; yoksa * değer TMDB puanıysa dürüstçe "Puan X/10" yazılır. */ $imdbRatingCol = (float) ($title->imdb_rating ?? 0); if ($imdbRatingCol > 0) { $imdbStr = 'IMDb ' . number_format($imdbRatingCol, 1) . '/10'; } else { // "TMDB" marka adı 5 dilde de geçerli (non-TR meta'larda TR kelime // sızdırmaz) ve kaynağı dürüstçe söyler. $imdbStr = $title->rating ? 'TMDB ' . number_format((float) $title->rating, 1) . '/10' : null; } /* Orijinal başlık — META TITLE / DESC / KEYWORDS hepsinde gerekiyor, erken tanımla */ $origTitle = ($title->original_title && $title->original_title !== $title->name) ? $title->original_title : null; /* R5 (5 Tem) — non-TR sayfalarda META TITLE görünen adı locale-aware: * localized_name (title_translations, TMDB sync/admin override — * TitleLoader::applyLocalizedDescription setAttribute eder) > * original_title > name(TR). TR çıktı byte-identical kalır. */ $dispName = $title->name; if ($locale !== 'tr') { $localizedName = trim((string) ($title->localized_name ?? '')); $dispName = $localizedName !== '' ? $localizedName : (trim((string) ($title->original_title ?? '')) ?: $title->name); } /* Kelime sınırında kesen yardımcı (title için) */ $wordCut = function(string $s, int $max): string { if (mb_strlen($s, 'UTF-8') <= $max) return $s; $sub = mb_substr($s, 0, $max, 'UTF-8'); $pos = mb_strrpos($sub, ' ', 0, 'UTF-8'); if ($pos !== false && $pos >= intval($max * 0.55)) { $sub = mb_substr($sub, 0, $pos, 'UTF-8'); } return rtrim($sub, '.,;:!?- ') . '…'; }; /* Cümle/kelime sınırında kesen yardımcı (description için) */ $descLimit = function(string $text, int $max = 155): string { $len = mb_strlen($text, 'UTF-8'); if ($len <= $max) return $text; // 250 karaktere kadar cümle sonu ara (. ! ?) — böylece 155'i geçen cümleler de tam biter $window = mb_substr($text, 0, min(250, $len), 'UTF-8'); $sentPos = -1; foreach (['. ', '! ', '? ', ".\n", "!\n", "?\n"] as $p) { $found = mb_strrpos($window, $p, 0, 'UTF-8'); if ($found !== false && $found >= intval($max * 0.4)) { $sentPos = max($sentPos, (int)$found); } } // Metin nokta/ünlem/soru ile bitiyorsa (boşluk yok sonunda) da yakala $lastChar = mb_substr($window, -1, 1, 'UTF-8'); if ($sentPos === -1 && in_array($lastChar, ['.', '!', '?']) && mb_strlen($window, 'UTF-8') >= intval($max * 0.4)) { return $window; } if ($sentPos !== -1) { return mb_substr($window, 0, $sentPos + 1, 'UTF-8'); } // Cümle bulunamadı — 155 içinde kelime sınırında kes $sub = mb_substr($text, 0, $max, 'UTF-8'); $pos = mb_strrpos($sub, ' ', 0, 'UTF-8'); if ($pos !== false && $pos >= intval($max * 0.4)) { return rtrim(mb_substr($sub, 0, $pos, 'UTF-8'), '.,;:!?-') . '…'; } return rtrim($sub, '.,;:!?- ') . '…'; }; /* ── META TITLE ───────────────────────────────────────────── * Admin'de seo_title tanımlıysa onu kullan, yoksa otomatik oluştur. * ──────────────────────────────────────────────────────────────────── */ $customSeoTitle = trim((string) ($title->seo_title ?? '')); if ($customSeoTitle !== '') { $ogTitle = $customSeoTitle; } elseif ($title->is_series) { $ogTitle = $dispName . $L( " - Tüm Sezonlar, Konusu ve Nerede İzlenir | {$siteName}", " - All Seasons, Plot & Where to Watch | {$siteName}", " - جميع المواسم والقصة وأين تشاهد | {$siteName}", " - Todas las Temporadas, Trama y Dónde Ver | {$siteName}", " - Все сезоны, сюжет и где смотреть | {$siteName}", ); } else { $yearDisp = $title->year ? " ({$title->year})" : ''; $ogTitle = "{$dispName}{$yearDisp}" . $L( " - Konusu, Oyuncuları, Nerede İzlenir | {$siteName}", " - Plot, Cast & Where to Watch | {$siteName}", " - القصة والممثلون وأين تشاهد | {$siteName}", " - Trama, Reparto y Dónde Ver | {$siteName}", " - Сюжет, актёры и где смотреть | {$siteName}", ); } /* ── META DESCRIPTION ──────────────────────────────────────── */ /* Hierarchy: admin seo_description > AI ozet_tr (Day 12-15 enrichment) * > DB title->description > structured fallback. AI ozet is spoiler-safe * Turkish copy and beats raw TMDB ham translation in most cases. */ $customSeoDesc = trim((string) ($title->seo_description ?? '')); if ($customSeoDesc !== '') { $metaDesc = $descLimit($customSeoDesc); $rawDesc = $customSeoDesc; } else { try { $aiOzetTr = trim(strip_tags((string) ($title->aiContent->ozet_tr ?? ''))); } catch (\Throwable $e) { $aiOzetTr = ''; } $rawDesc = $aiOzetTr !== '' ? $aiOzetTr : trim(strip_tags((string) $title->description)); } if ($customSeoDesc !== '') { // zaten set edildi yukarıda } elseif ($rawDesc !== '') { /* Düzeltme 8: Diziler için "tüm bölümler/sezon detayları" ek cümlesi */ if ($title->is_series) { /* R27: dizi kapanışı "sezon ve bölüm rehberi" formatında */ $rawDesc = rtrim($rawDesc, ' .') . $L( ". Sezon ve bölüm rehberi: tüm bölümler, yayın tarihleri ve oyuncu kadrosu {$siteName}'de.", ". Season and episode guide: all episodes, air dates and cast on {$siteName}.", ". دليل المواسم والحلقات: جميع الحلقات وتواريخ العرض والممثلون على {$siteName}.", ". Guía de temporadas y episodios: todos los episodios, fechas de emisión y reparto en {$siteName}.", ". Гид по сезонам и сериям: все серии, даты выхода и актёры на {$siteName}.", ); } /* Uzun DB açıklamasını 155'e kıs */ $metaDesc = $descLimit($rawDesc); } elseif ($title->is_series) { /* Yapılandırılmış dizi açıklaması — Düzeltme 8: kapanış cümlesi * "tüm bölümler/sezon detayları" formatına alındı */ $parts = ["{$dispName}"]; if ($origTitle && $origTitle !== $title->name) $parts[] = "({$origTitle})"; if ($title->seasons_count) $parts[] = $L("{$title->seasons_count} sezon", "{$title->seasons_count} seasons", "{$title->seasons_count} مواسم", "{$title->seasons_count} temporadas", "{$title->seasons_count} сезонов"); if ($topCastStr) $parts[] = $L("Oyuncular: {$topCastStr}", "Cast: {$topCastStr}", "الممثلون: {$topCastStr}", "Reparto: {$topCastStr}", "Актёры: {$topCastStr}"); if ($imdbStr) $parts[] = $imdbStr; $fallback = implode(', ', $parts) . $L( ". Sezon ve bölüm rehberi: tüm bölümler, yayın tarihleri ve oyuncu kadrosu {$siteName}'de.", ". Season and episode guide: all episodes, air dates and cast on {$siteName}.", ". دليل المواسم والحلقات: جميع الحلقات وتواريخ العرض والممثلون على {$siteName}.", ". Guía de temporadas y episodios: todos los episodios, fechas de emisión y reparto en {$siteName}.", ". Гид по сезонам и сериям: все серии, даты выхода и актёры на {$siteName}.", ); $metaDesc = $descLimit($fallback); } else { /* Yapılandırılmış film açıklaması — "nerede izlenir" odaklı */ $dispOrig = ($origTitle && $origTitle !== $title->name) ? " ({$origTitle})" : ''; $fallback = $L( "{$dispName}{$dispOrig} filmi hakkında tüm detaylar: konusu, oyuncuları" . ($imdbStr ? ", {$imdbStr}" : '') . ($directorName ? ", yönetmen {$directorName}" : '') . ". Netflix, BluTV ve diğer platformlarda nerede izleneceğini {$siteName}'de öğren.", "Everything about {$dispName}{$dispOrig}: plot, cast" . ($imdbStr ? ", {$imdbStr}" : '') . ($directorName ? ", directed by {$directorName}" : '') . ". Find where to watch on Netflix, BluTV and more at {$siteName}.", "كل ما يخص {$dispName}{$dispOrig}: القصة والممثلون" . ($imdbStr ? "، {$imdbStr}" : '') . ($directorName ? "، إخراج {$directorName}" : '') . ". اعرف أين تشاهده على {$siteName}.", "Todo sobre {$dispName}{$dispOrig}: trama, reparto" . ($imdbStr ? ", {$imdbStr}" : '') . ($directorName ? ", dirigida por {$directorName}" : '') . ". Descubre dónde verla en {$siteName}.", "Всё о {$dispName}{$dispOrig}: сюжет, актёры" . ($imdbStr ? ", {$imdbStr}" : '') . ($directorName ? ", режиссёр {$directorName}" : '') . ". Узнайте, где смотреть, на {$siteName}.", ); $metaDesc = $descLimit($fallback); } /* ── META KEYWORDS ─────────────────────────────────────────── */ $pageUrl = urls()->title($title); $kwBase = collect($title->keywords ?? []) ->map(function ($k) use ($locale, $trTag) { // TR (default): mevcut görünen adı BİREBİR koru (regresyon yok). if ($locale === 'tr') return trim((string)($k->display_name ?: $k->name)); // non-TR: tag_translations çevirisi; yoksa null → filter() düşürür. return $trTag((string) $
Yanma Gast / Tombo Ohger has wandered into the world of Tokusou Sentai Dekaranger?! As soon as he saw Marika "Jasmine" Hiwatari/Deka Yellow, and met her for the first time, he said, "This is my first love. I'll do anything to protect you!" Unlike the previous Yanma Gast, there's something strange about him... what is the truth behind it?
Kaizoku Sentai: Ten Gokaiger
Bakuage Sentai Boonboomger VS King-Ohger
No. 1 Sentai Gozyuger: TegaSword of Resurrection
Avataro Sentai Donbrothers vs. Zenkaiger
Bakuryū Sentai Abarenjā 20th: Yurusa Rezaru Abare
A Choo
Bakuage Sentai Boonboomger GekijoBOON! Promise The Circuit
Ohsama Sentai King-Ohger IN SPACE
Kamen Rider 555 20th: Paradise Regained
Kishiryu Sentai Ryusoulger VS Lupinranger VS Patranger
Kamen Rider Zero-One: Real Time
Untitled Kamen Rider ZEZTZ Summer Movie