From bec0df29e1934f76f36dd3f1e04243e747002a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Mon, 2 Mar 2026 10:55:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[claude-code]=20=EB=89=B4=EC=8A=A4=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=95=9C/=EC=98=81=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Google Translate API 연동으로 릴리즈 노트 한국어 자동 번역 - 코드 블록 보호 처리 (번역 대상에서 제외) - 긴 텍스트 단락 분할 번역 지원 - Alpine.js 한국어/English 토글 버튼 (localStorage 저장) - 기본값: 한국어 --- app/Services/ClaudeCodeNewsService.php | 108 +++++++++++++++++- .../views/claude-code/news/index.blade.php | 23 +++- 2 files changed, 125 insertions(+), 6 deletions(-) diff --git a/app/Services/ClaudeCodeNewsService.php b/app/Services/ClaudeCodeNewsService.php index 1bb4be4b..023b5b34 100644 --- a/app/Services/ClaudeCodeNewsService.php +++ b/app/Services/ClaudeCodeNewsService.php @@ -15,7 +15,7 @@ class ClaudeCodeNewsService private const API_URL = 'https://api.github.com/repos/anthropics/claude-code/releases'; /** - * GitHub Releases 목록 조회 (캐싱) + * GitHub Releases 목록 조회 (캐싱, 한국어 번역 포함) */ public function getReleases(int $perPage = 20): array { @@ -33,7 +33,7 @@ public function clearCache(): void } /** - * GitHub API에서 릴리즈 정보 가져오기 + * GitHub API에서 릴리즈 정보 가져오기 + 한국어 번역 */ private function fetchFromGitHub(int $perPage): array { @@ -51,11 +51,15 @@ private function fetchFromGitHub(int $perPage): array 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($release['body'] ?? ''), + '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'] ?? '', @@ -71,4 +75,102 @@ private function fetchFromGitHub(int $perPage): array 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; + } + } } diff --git a/resources/views/claude-code/news/index.blade.php b/resources/views/claude-code/news/index.blade.php index 2403e5a2..bf6967a6 100644 --- a/resources/views/claude-code/news/index.blade.php +++ b/resources/views/claude-code/news/index.blade.php @@ -26,14 +26,30 @@ @endpush @section('content') -
+
+ {{-- 페이지 헤더 --}}

Claude Code 뉴스

GitHub Releases에서 최신 업데이트를 확인합니다.

-
+
+ {{-- 한/영 토글 --}} +
+ + +
+
@csrf