diff --git a/apps/web/package.json b/apps/web/package.json index 41145400..17ead27c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -30,6 +30,7 @@ "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.2.6", + "@repo/constants": "workspace:*", "@repo/open-api": "workspace:*", "@supabase/ssr": "^0.6.1", "@supabase/supabase-js": "^2.49.1", diff --git a/apps/web/src/constants/artistAlias.ts b/apps/web/src/constants/artistAlias.ts deleted file mode 100644 index f0cc0e0f..00000000 --- a/apps/web/src/constants/artistAlias.ts +++ /dev/null @@ -1,79 +0,0 @@ -export const artistAlias = { - // 나무위키 데이터 추가 - - Aimer: ['에이머', '에메'], - amazarashi: ['아마자라시'], - 'Bump of Chicken': ['범프 오브 치킨'], - ELLEGARDEN: ['엘레가든'], - 'King Gnu': ['킹 누'], - LISA: ['리사'], - 'Mrs. GREEN APPLE': ['미세스 그린 애플'], - RADWIMPS: ['래드윔프스'], - 'SEKAI NO OWARI': ['세카이노 오와리', '세카오와'], - SPYAIR: ['스파이에어'], - Vaundy: ['바운디'], - YOASOBI: ['요아소비'], - 'Creepy Nuts': ['크리피 너츠'], - - // [Gemini 추천 아티스트 추가] 한국 인기 최상위 - Ado: ['아도', '우세와', '신시대'], - imase: ['이마세', '나이트댄서'], - 'ONE OK ROCK': ['원오크락', '원오크', '원옥'], - 'X JAPAN': ['엑스재팬', '엑스제팬'], - 'tuki.': ['츠키', '투키', '만찬가'], - HoneyWorks: ['허니웍스'], - "L'Arc~en~Ciel": ['라르크', '라르크 앙 시엘'], - 松田聖子: ['마츠다 세이코'], - 椎名林檎: ['시이나 링고'], - Eve: ['eve', '이브'], - 美波: ['미나미', 'minami', '373'], - 'ASIAN KUNG-FU GENERATION': ['아시안 쿵푸 제너레이션', '아지캉', '아쿵제'], - - // 기존 데이터 - 嵐: ['아라시'], - あいみょん: ['아이묭'], - 米津玄師: ['요네즈 켄시'], - 浜崎あゆみ: ['하마사키 아유미'], - 水樹奈々: ['미즈키 나나'], - 'モーニング娘。': ['모닝구 무스메'], - Official髭男dism: ['오피셜히게단디즘', '히게단', '프리텐더'], - ヨルシカ: ['요루시카'], - 中島美嘉: ['나카시마 미카'], - 宇多田ヒカル: ['우타다 히카루'], - 西野カナ: ['니시노 카나'], - '関ジャニ∞': ['칸쟈니 에이트'], - 'ずっと真夜中でいいのに。': ['즛토마요', '계속 한밤중이면 좋을텐데.'], - 倉木麻衣: ['쿠라키 마이'], - 安室奈美惠: ['아무로 나미에'], - いきものがかり: ['이키모노가카리'], - 星野源: ['호시노 겐'], - 倖田來未: ['코다 쿠미'], - 緑黄色社会: ['녹황색사회'], - 坂本真綾: ['사카모토 마아야'], - 結束バンド: ['결속 밴드'], - 乃木坂46: ['노기자카46'], - 林原めぐみ: ['하야시바라 메구미'], - ポルノグラフィティ: ['포르노그라피티'], - 初音ミク: ['하츠네 미쿠'], - 'DECO*27': ['DECO27', '데코니나'], - 松浦亜弥: ['마츠우라 아야'], - 堀江由衣: ['호리에 유이'], - コブクロ: ['코부쿠로'], - きゃりーぱみゅぱみゅ: ['캬리 파뮤파뮤'], - ももいろクローバーZ: ['모모이로 클로버 Z'], - キタニタツヤ: ['키타니 타츠야'], - 優里: ['유우리'], - 鈴木このみ: ['스즈키 코노미'], - 菅田将暉: ['스다 마사키'], - スキマスイッチ: ['스키마스위치'], - '『ユイカ』': ['유이카'], - 福山雅治: ['후쿠야마 마사하루'], - 大塚愛: ['오오츠카 아이'], - 米倉千尋: ['요네쿠라 치히로'], - '平井 堅': ['히라이 켄'], - 藤井風: ['후지이 카제'], - 田村ゆかり: ['타무라 유카리'], - オーイシマサヨシ: ['오이시 마사요시'], - ちゃんみな: ['챤미나'], - なとり: ['나토리'], -}; diff --git a/apps/web/src/utils/getArtistAlias.ts b/apps/web/src/utils/getArtistAlias.ts index d1d37628..7d5a2fd5 100644 --- a/apps/web/src/utils/getArtistAlias.ts +++ b/apps/web/src/utils/getArtistAlias.ts @@ -1,7 +1,6 @@ +import { artistAlias } from '@repo/constants'; import { getChoseong } from 'es-hangul'; -import { artistAlias } from '@/constants/artistAlias'; - export type SearchCandidate = { label: string; value: string }; const createCandidateList = (): SearchCandidate[] => { diff --git a/packages/constants/package.json b/packages/constants/package.json new file mode 100644 index 00000000..19076769 --- /dev/null +++ b/packages/constants/package.json @@ -0,0 +1,18 @@ +{ + "name": "@repo/constants", + "version": "1.0.0", + "description": "Shared domain constants across apps/packages", + "main": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "type": "module", + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts" + }, + "devDependencies": { + "@types/node": "^22.13.10", + "tsup": "^8.4.0", + "typescript": "^5.8.2" + } +} diff --git a/packages/constants/src/artistAlias.ts b/packages/constants/src/artistAlias.ts new file mode 100644 index 00000000..00c48382 --- /dev/null +++ b/packages/constants/src/artistAlias.ts @@ -0,0 +1,79 @@ +export const artistAlias = { + // 나무위키 데이터 추가 + + Aimer: ["에메", "에이머"], + amazarashi: ["아마자라시"], + "Bump of Chicken": ["범프 오브 치킨"], + ELLEGARDEN: ["엘레가든"], + "King Gnu": ["킹 누"], + LISA: ["리사"], + "Mrs. GREEN APPLE": ["미세스 그린 애플"], + RADWIMPS: ["래드윔프스"], + "SEKAI NO OWARI": ["세카이노 오와리", "세카오와"], + SPYAIR: ["스파이에어"], + Vaundy: ["바운디"], + YOASOBI: ["요아소비"], + "Creepy Nuts": ["크리피 너츠"], + + // [Gemini 추천 아티스트 추가] 한국 인기 최상위 + Ado: ["아도", "우세와", "신시대"], + imase: ["이마세", "나이트댄서"], + "ONE OK ROCK": ["원오크락", "원오크", "원옥"], + "X JAPAN": ["엑스재팬", "엑스제팬"], + "tuki.": ["츠키", "투키", "만찬가"], + HoneyWorks: ["허니웍스"], + "L'Arc~en~Ciel": ["라르크 앙 시엘", "라르크"], + 松田聖子: ["마츠다 세이코"], + 椎名林檎: ["시이나 링고"], + Eve: ["이브", "eve"], + 美波: ["미나미", "minami", "373"], + "ASIAN KUNG-FU GENERATION": ["아시안 쿵푸 제너레이션", "아지캉", "아쿵제"], + + // 기존 데이터 + 嵐: ["아라시"], + あいみょん: ["아이묭"], + 米津玄師: ["요네즈 켄시"], + 浜崎あゆみ: ["하마사키 아유미"], + 水樹奈々: ["미즈키 나나"], + "モーニング娘。": ["모닝구 무스메"], + Official髭男dism: ["오피셜히게단디즘", "히게단", "프리텐더"], + ヨルシカ: ["요루시카"], + 中島美嘉: ["나카시마 미카"], + 宇多田ヒカル: ["우타다 히카루"], + 西野カナ: ["니시노 카나"], + "関ジャニ∞": ["칸쟈니 에이트"], + "ずっと真夜中でいいのに。": ["즛토마요", "계속 한밤중이면 좋을텐데."], + 倉木麻衣: ["쿠라키 마이"], + 安室奈美惠: ["아무로 나미에"], + いきものがかり: ["이키모노가카리"], + 星野源: ["호시노 겐"], + 倖田來未: ["코다 쿠미"], + 緑黄色社会: ["녹황색사회"], + 坂本真綾: ["사카모토 마아야"], + 結束バンド: ["결속 밴드"], + 乃木坂46: ["노기자카46"], + 林原めぐみ: ["하야시바라 메구미"], + ポルノグラフィティ: ["포르노그라피티"], + 初音ミク: ["하츠네 미쿠"], + "DECO*27": ["DECO27", "데코니나"], + 松浦亜弥: ["마츠우라 아야"], + 堀江由衣: ["호리에 유이"], + コブクロ: ["코부쿠로"], + きゃりーぱみゅぱみゅ: ["캬리 파뮤파뮤"], + ももいろクローバーZ: ["모모이로 클로버 Z"], + キタニタツヤ: ["키타니 타츠야"], + 優里: ["유우리"], + 鈴木このみ: ["스즈키 코노미"], + 菅田将暉: ["스다 마사키"], + スキマスイッチ: ["스키마스위치"], + "『ユイカ』": ["유이카"], + 福山雅治: ["후쿠야마 마사하루"], + 大塚愛: ["오오츠카 아이"], + 米倉千尋: ["요네쿠라 치히로"], + "平井 堅": ["히라이 켄"], + 藤井風: ["후지이 카제"], + 田村ゆかり: ["타무라 유카리"], + オーイシマサヨシ: ["오이시 마사요시"], + ちゃんみな: ["챤미나"], + なとり: ["나토리"], +}; diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts new file mode 100644 index 00000000..533baaa1 --- /dev/null +++ b/packages/constants/src/index.ts @@ -0,0 +1 @@ +export { artistAlias } from './artistAlias'; diff --git a/packages/constants/tsconfig.json b/packages/constants/tsconfig.json new file mode 100644 index 00000000..04f8c7d5 --- /dev/null +++ b/packages/constants/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext"], + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationDir": "./dist/types", + "declarationMap": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/crawling/package.json b/packages/crawling/package.json index ffdf3256..7b639baf 100644 --- a/packages/crawling/package.json +++ b/packages/crawling/package.json @@ -19,6 +19,7 @@ "format": "prettier --write \"**/*.{ts,tsx,md}\"" }, "dependencies": { + "@repo/constants": "workspace:*", "@repo/open-api": "workspace:*", "@supabase/supabase-js": "^2.49.1", "axios": "^1.5.0", diff --git a/packages/crawling/src/cron/taggingSongs.ts b/packages/crawling/src/cron/taggingSongs.ts index 4cedff73..c68e8c09 100644 --- a/packages/crawling/src/cron/taggingSongs.ts +++ b/packages/crawling/src/cron/taggingSongs.ts @@ -20,7 +20,7 @@ console.log('이미 태그된 곡 수:', taggedSongIds.size); let processedCount = 0; for (const song of allSongs) { - if (processedCount >= 5000) break; + if (processedCount >= 20000) break; if (taggedSongIds.has(song.id)) { resultsLog.skipped++; continue; diff --git a/packages/crawling/src/cron/translationJpn.ts b/packages/crawling/src/cron/translationJpn.ts index cb7b6c23..ece661c6 100644 --- a/packages/crawling/src/cron/translationJpn.ts +++ b/packages/crawling/src/cron/translationJpn.ts @@ -1,3 +1,5 @@ +import { artistAlias } from '@repo/constants'; + import { getJpopSongsForTranslationDB } from '@/supabase/getDB'; import { updateSongKoTranslationDB } from '@/supabase/updateDB'; import { translateJpnToKo } from '@/utils/translateJpnToKo'; @@ -6,6 +8,7 @@ const resultsLog = { success: 0, failed: 0, skipped: 0, + usedAlias: 0, }; // 히라가나, 카타카나, CJK 한자 범위로 일본어 포함 여부 판단 @@ -15,14 +18,18 @@ function containsJapanese(text: string): boolean { return JAPANESE_REGEX.test(text); } -// 1. J-POP 곡 조회 +// artistAlias 로부터 artist 원어 → 한국어 대표 표기(별명 배열의 0번째) 맵 생성 +const artistAliasMap = new Map( + Object.entries(artistAlias).map(([artist, aliases]) => [artist, aliases[0]]), +); + const songs = await getJpopSongsForTranslationDB(); console.log('J-POP 곡 수:', songs.length); let processedCount = 0; for (const song of songs) { - if (processedCount >= 5000) break; + if (processedCount >= 10000) break; // 이미 번역된 곡 스킵 if (song.title_ko && song.artist_ko) { @@ -46,15 +53,26 @@ for (const song of songs) { continue; } - console.log(result); + // artistAlias 에 등록된 아티스트면 artist_ko 를 고정 값(alias 배열 0번째)으로 덮어쓰기 + // title_ko 는 AI 번역 결과를 그대로 사용 + const aliasArtistKo = artistAliasMap.get(song.artist); + const finalArtistKo = aliasArtistKo ?? result.artist_ko; - const success = await updateSongKoTranslationDB(song.id, result.title_ko, result.artist_ko); - if (success) { - resultsLog.success++; - console.log(`[OK] ${song.title} → ${result.title_ko} / ${song.artist} → ${result.artist_ko}`); - } else { + const success = await updateSongKoTranslationDB(song.id, result.title_ko, finalArtistKo); + if (!success) { resultsLog.failed++; + continue; + } + + if (aliasArtistKo) { + resultsLog.usedAlias++; + } else { + resultsLog.success++; } + const logPrefix = aliasArtistKo ? '[ALIAS]' : '[OK]'; + console.log( + `${logPrefix} ${song.title} → ${result.title_ko} / ${song.artist} → ${finalArtistKo}`, + ); } catch (error) { resultsLog.failed++; console.error(`[ERROR] ${song.title} - ${song.artist}:`, error); @@ -70,6 +88,7 @@ for (const song of songs) { console.log(` 총 ${songs.length}곡 중: - 스킵 (이미 번역됨): ${resultsLog.skipped}곡 - - 성공: ${resultsLog.success}곡 + - 성공 (AI 번역): ${resultsLog.success}곡 + - 성공 (artist_ko alias 적용): ${resultsLog.usedAlias}곡 - 실패: ${resultsLog.failed}곡 `); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc368694..26d80b58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,9 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.6 version: 1.2.8(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@repo/constants': + specifier: workspace:* + version: link:../../packages/constants '@repo/open-api': specifier: workspace:* version: link:../../packages/open-api @@ -309,8 +312,23 @@ importers: specifier: ^5 version: 5.9.3 + packages/constants: + devDependencies: + '@types/node': + specifier: ^22.13.10 + version: 22.19.3 + tsup: + specifier: ^8.4.0 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3) + typescript: + specifier: ^5.8.2 + version: 5.9.3 + packages/crawling: dependencies: + '@repo/constants': + specifier: workspace:* + version: link:../constants '@repo/open-api': specifier: workspace:* version: link:../open-api