fetchFromGitHub($perPage); }); } /** * 캐시 클리어 */ public function clearCache(): void { Cache::forget(self::CACHE_KEY); } /** * GitHub API에서 릴리즈 정보 가져오기 + 한국어 번역 */ private function fetchFromGitHub(int $perPage): array { try { $response = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', 'User-Agent' => 'SAM-MNG-App', ])->timeout(10)->get(self::API_URL, [ 'per_page' => $perPage, ]); if (! $response->successful()) { return []; } return collect($response->json()) ->map(function ($release) { $body = $release['body'] ?? ''; $bodyKo = $this->translateToKorean($body); return [ 'id' => $release['id'], 'tag_name' => $release['tag_name'], 'name' => $release['name'] ?? $release['tag_name'], 'body_html' => Str::markdown($body), 'body_html_ko' => Str::markdown($bodyKo), 'published_at' => $release['published_at'], 'author' => $release['author']['login'] ?? 'unknown', 'author_avatar' => $release['author']['avatar_url'] ?? '', 'html_url' => $release['html_url'], 'prerelease' => $release['prerelease'] ?? false, 'draft' => $release['draft'] ?? false, ]; }) ->toArray(); } catch (\Exception $e) { report($e); return []; } } /** * 영문 마크다운을 한국어로 번역 (코드 블록 보호) */ private function translateToKorean(string $text): string { if (empty(trim($text))) { return ''; } // 코드 블록/인라인 코드를 플레이스홀더로 보호 $protected = []; $safe = preg_replace_callback('/```[\s\S]*?```|`[^`\n]+`/', function ($match) use (&$protected) { $key = 'ZXCBLK'.count($protected).'QWE'; $protected[$key] = $match[0]; return $key; }, $text); // 번역 if (mb_strlen($safe) <= 4000) { $translated = $this->callGoogleTranslate($safe); } else { $translated = $this->translateLongText($safe); } // 코드 블록 복원 foreach ($protected as $key => $code) { $translated = str_replace($key, $code, $translated); } return $translated; } /** * 긴 텍스트를 단락 단위로 분할 번역 */ private function translateLongText(string $text): string { $parts = preg_split('/(\n\n+)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); $chunks = []; $current = ''; foreach ($parts as $part) { if (mb_strlen($current.$part) > 4000 && $current !== '') { $chunks[] = $current; $current = $part; } else { $current .= $part; } } if ($current !== '') { $chunks[] = $current; } $result = ''; foreach ($chunks as $chunk) { $result .= $this->callGoogleTranslate($chunk); } return $result; } /** * Google Translate API 호출 */ private function callGoogleTranslate(string $text): string { try { $response = Http::timeout(5) ->get('https://translate.googleapis.com/translate_a/single', [ 'client' => 'gtx', 'sl' => 'en', 'tl' => 'ko', 'dt' => 't', 'q' => $text, ]); if (! $response->successful()) { return $text; } $result = $response->json(); $translated = ''; if (is_array($result) && isset($result[0]) && is_array($result[0])) { foreach ($result[0] as $segment) { if (is_array($segment) && isset($segment[0])) { $translated .= $segment[0]; } } } return $translated !== '' ? $translated : $text; } catch (\Exception $e) { return $text; } } }