import en from '../en/index.js' import auth from './auth.js' import accountPoints from './account.js' import payPoints from './pay.js' import generatedVisualGame from './visual-game.generated.js' import legal from './legal.js' const common = { ...en.common, cancel: '취소', confirm: '확인', back: '뒤로', next: '다음', retry: '다시 시도', loading: '불러오는 중…', save: '저장', close: '닫기', language: { label: '언어', options: { en: '영어', zh: '중국어', ja: '일본어', ko: '한국어', es: '스페인어', pt: '포르투갈어' }, }, } const account = { ...en.account, ...accountPoints, account: { ...en.account.account, notifications: '알림', }, } const visualGame = mergeMessages({ mobileCredit: { title: '내 크레딧', buyCredits: '크레딧 구매', history: '크레딧 내역', purchaseHistory: '구매 내역', usageHistory: '사용 내역', emptyHistory: '크레딧 내역이 없습니다.', loadMore: '더 보기', balanceAfter: '잔액: {points}', pending: '처리 중', paid: '결제 완료', canceled: '취소됨', failed: '실패', refunded: '환불됨', disputed: '분쟁 중', autoReload: '자동 충전', bonusCredits: '보너스 크레딧', buyNow: '지금 구매', lowBalance: '잔액 부족', enabled: '사용 중', whenBelow: '잔액이 다음보다 낮을 때', choosePack: '충전 팩 선택', consent: '잔액이 기준에 도달할 때 선택한 팩을 자동 구매하도록 Voko에 승인합니다.', terms: '이용 약관', continueSecurely: '안전하게 계속', save: '설정 저장', disable: '자동 충전 끄기', setupComplete: '결제 수단이 저장되고 자동 충전이 켜졌습니다.', setupCanceled: '자동 충전 설정이 취소되었습니다.', actionRequired: '마지막 충전에 실패했습니다. 결제 수단을 다시 설정해 주세요.', howTitle: '자동 충전 작동 방식', howBalance: '잔액이 기준 이하가 되면 선택한 팩을 구매합니다.', howSecure: '결제 정보는 Stripe가 안전하게 보관하며 Voko는 카드 번호를 저장하지 않습니다.', howCancel: '언제든 끌 수 있으며 이전 구매는 환불되지 않습니다.', }, ...generatedVisualGame, productBrandSub: '당신의 kosmo가 부르고 있어요.', actions: { ...en.visualGame.actions, cancel: '취소', confirm: '확인', }, mobileProfile: { title: '마이', settings: '설정', content: '콘텐츠', benefits: '혜택', creator: '크리에이터', support: '문의 및 지원', contact: '고객 지원', terms: '이용 약관', privacy: '개인정보 처리방침', social: '소셜 미디어', }, myGames: { recent: '최근 플레이', popular: '인기 스토리', emptyRecent: '최근 플레이한 스토리가 여기에 표시됩니다.', loadError: '게임을 불러오지 못했습니다. 다시 시도해 주세요.', }, storyDetail: { title: '스토리 정보', description: '스토리 설명', episodes: '{count}개 에피소드', endings: '{count}개 엔딩', by: '작가: {name}', lastUpdate: '최근 업데이트', newPlay: '새로 플레이', notFound: '스토리를 찾을 수 없습니다.', loadError: '스토리를 불러오지 못했습니다.', }, creator: { nav: '만들기', queued: '대기 중', attachmentTypeError: 'TXT, PDF, Word, PNG, JPG 또는 WEBP 파일 하나만 업로드할 수 있습니다.', attachmentSizeError: '첨부 파일은 10MB 이하여야 합니다.', attachmentReadError: '첨부 파일을 읽을 수 없습니다.', generationFailed: '생성 실패', reviewRejected: '심사 거절', published: '공개됨', archived: '보관됨', composerTitle: '오늘은 어떤 세계를 만들어 볼까요?', submitIdea: '아이디어 제출', examplesNote: '실제 공개 게임을 레이아웃 참고용으로 표시합니다.', exampleStatus: '공개 예시', projectsEyebrow: 'Visual Game Creator', projectsTitle: '내 게임', projectsBody: '내가 만든 게임과 생성 및 심사 상태를 관리합니다.', newGame: '새 게임 만들기', draftStatus: '초안', continueEditing: '계속 편집', generatedScopes: '개 생성 항목', emptyTitle: '첫 게임 만들기', emptyBody: '아이디어, 대본 또는 세계관에서 시작해 플레이할 수 있는 게임을 만드세요.', eyebrow: 'Visual Game Creator', title: '당신의 이야기를 플레이할 수 있는 세계로', subtitle: '아이디어, 대본 또는 세계관을 공유하세요. 제작 시스템이 게임 설계, 미디어 제작, 전체 경로 검증과 심사 제출을 진행합니다.', stepIdea: '아이디어', stepGenerate: '생성', stepReview: '심사', ideaTitle: '만들고 싶은 게임을 설명하세요', ideaBody: '자연어로 작성하세요. JSON이나 상태 머신을 알 필요가 없습니다.', ideaLabel: '게임 아이디어 또는 대본', ideaHint: '세계, 인물, 갈등과 플레이어에게 주고 싶은 경험을 포함하세요.', ideaPlaceholder: '예: 폭우가 내리는 밤, 한 여행자가 여관에서 깨어나지만 모두가 지난밤을 다르게 기억한다…', ideaError: '20자 이상 입력해 주세요.', languageLabel: '게임 언어', languages: { zh: '중국어', en: '영어', ja: '일본어', ko: '한국어', }, scopeTitle: '이번 생성 범위', scopeStory: '스토리와 레벨', scopeStoryBody: '인물, 임무, 상태와 엔딩', scopeVisuals: '비주얼 미디어', scopeVisualsBody: '캐릭터, 장면, 소품과 영상', scopeVoice: '음성', scopeVoiceBody: '플레이어, NPC와 엔딩 내레이션', scopeError: '생성 항목을 하나 이상 선택하세요.', assetsTitle: '참고 자료', assetsBody: '선택 사항입니다. 인물과 세계의 일관성을 높입니다.', uploadAction: '참고 파일 추가', uploadHint: '파일 1개 · TXT, PDF, Word, PNG, JPG 또는 WEBP · 최대 10MB', supportedTitle: '지원 자료', supportedBody: '인물, 장면, 소품, 화풍 이미지, 대본과 세계관 문서, 영상 또는 오디오 참고.', removeFile: '{name} 삭제', saved: '이 기기에 초안을 저장했습니다.', draftNote: '첫 버전은 로컬 초안 저장을 제공합니다. 생성 연결은 다음 단계에서 진행합니다.', save: '초안 저장', }, billing: { ...en.visualGame.billing, testModeTitle: 'Stripe 테스트 환경', testModeBody: '테스트 결제를 사용하므로 실제 요금은 청구되지 않습니다.', testPurchase: '테스트 구매', }, storyTree: { ...generatedVisualGame.storyTree, turnVideos: '일치한 동영상', playActionVideo: '동작 동영상', playPlayerVideo: '플레이어 동영상', playNpcVideo: 'NPC 동영상', replayTurn: '다시 보기', replayTurnTitle: '{turn}턴 다시 보기', replayProgress: '{current}/{total} 클립', actionSegment: '동작', playerSpeechSegment: '플레이어 대사', npcSpeechSegment: 'NPC 대사', pauseReplay: '일시 정지', continueReplay: '계속 재생', restartReplay: '처음부터 재생', exportReplay: '전체 다시 보기 내보내기', replayQueued: '대기 중', replayExporting: '내보내는 중', playFullReplay: '전체 다시 보기 재생', retryReplayExport: '다시 내보내기', downloadReplay: 'MP4 다운로드', downloadSegment: '이 클립 다운로드', downloadTurn: '이 턴 전체 다운로드', turnExporting: '턴 동영상 내보내는 중', turnExportError: '턴 동영상을 내보내지 못했습니다.', segmentExporting: '클립 내보내는 중', segmentExportError: '클립을 내보내지 못했습니다.', fullReplayTitle: '{title} · 전체 다시 보기', replayExportError: '전체 다시 보기를 내보내지 못했습니다.', videoLoadError: '동영상을 불러올 수 없습니다.', }, runVideoGeneration: { title: '전체 동영상 생성', description: '이 플레이의 전체 동영상을 생성합니다. 확인 후 포인트가 차감되며 생성 실패 시 자동으로 환불됩니다.', generate: '동영상 생성', regenerate: '다시 생성', retry: '다시 시도', queued: '대기 중', running: '생성 중', price: '필요 포인트', balance: '현재 잔액', balanceAfter: '차감 후 잔액', }, account: { ...en.visualGame.account, signIn: '로그인', signedIn: '로그인됨', email: '이메일', emailPlaceholder: '이메일 주소', password: '비밀번호', passwordPlaceholder: '비밀번호 입력', confirmPassword: '비밀번호 확인', confirmPasswordPlaceholder: '비밀번호 다시 입력', verificationCode: '인증 코드', verificationCodePlaceholder: '6자리 이메일 코드', inviteToken: '초대 코드', inviteTokenPlaceholder: '8자리 초대 코드', sendCode: '코드 보내기', codeSent: '코드를 보냈습니다. 이메일을 확인하세요.', codeRequired: '먼저 인증 코드를 입력하세요.', emailRequired: '먼저 이메일을 입력하세요.', emailInvalid: '올바른 이메일 주소를 입력하세요.', passwordRequired: '비밀번호를 입력하세요.', passwordMismatch: '비밀번호가 일치하지 않습니다.', loginFieldsRequired: '이메일과 비밀번호를 입력하세요.', createAccount: '계정 만들기', noAccount: '계정이 없으신가요?', login: '로그인', register: '가입하기', reset: '재설정', forgotPassword: '비밀번호를 잊으셨나요?', resetPassword: '비밀번호 재설정', resetTitle: '비밀번호 재설정', resetCodeSent: '재설정 코드를 보냈습니다. 이메일을 확인하세요.', passwordResetDone: '비밀번호가 재설정되었습니다. 새 비밀번호로 로그인하세요.', logout: '로그아웃', logoutAll: '모든 기기에서 로그아웃', loginTitle: '로그인', registerTitle: '계정 만들기', registerBody: '초대 코드로 계정을 만들고 이메일을 인증하세요.', guestStart: '게스트로 체험하기', savedHint: '로그인하면 진행 상황이 자동으로 저장됩니다.', currentAccount: '현재 계정', saveKicker: '클라우드 저장', }, waitingList: { ...en.visualGame.waitingList, apply: '대기자 명단 신청', useInvite: '초대 코드 사용', invitedRegister: '초대장이 있나요? 계정 만들기', join: '대기자 명단 가입', }, landing: { ...en.visualGame.landing, homeLabel: 'voko.world 홈', navHome: '홈', navScripts: '스토리', navVideos: '드라마', featuredStories: '추천 스토리', featuredStoriesEyebrow: '게임 시작', allStories: '모든 스토리', dramaTitle: '드라마', brandSub: '인터랙티브 스토리', navLabel: '주요 탐색', loadError: '홈페이지를 불러오지 못했습니다. 다시 시도하세요.', retry: '다시 시도', emptyGames: '아직 이용할 수 있는 게임이 없습니다.', scenarioFallback: '이 세계에 들어가 선택으로 스토리를 바꿔 보세요.', watchDrama: '스토리 보기', enterPlay: '지금 플레이', comingSoon: '공개 예정', previousGame: '이전 게임', nextGame: '다음 게임', searchLabel: '스토리 검색', searchPlaceholder: '게임 또는 에피소드 제목 검색', sortLabel: '스토리 정렬', latest: '최신순', popular: '인기순', noSearchResults: '검색 조건에 맞는 스토리가 없습니다.', clearSearch: '필터 초기화', footerSocial: '커뮤니티', footerLegal: '법적 고지 및 문의', privacy: '개인정보 처리방침', terms: '이용약관', contact: '문의하기', }, landingCinema: { ...en.visualGame.landingCinema, navHome: '홈', navDramas: '게임', navPlayerStories: '플레이어 스토리', navMy: '마이', featuredDramas: '게임', playerStories: '플레이어 스토리', }, searchPage: { placeholder: '스토리, 드라마 또는 계정 검색', popular: '인기 검색어', history: '최근 검색', clearHistory: '지우기', resultTypes: '검색 결과 유형', all: '전체', accounts: '계정', accountStats: '작품 {titles}개 · 재생 {plays}회' }, storiesPage: { swipeForNextStory: '위로 스와이프하여 다음 스토리 보기', tags: '태그', allTags: '모든 태그', }, runHistory: { ...en.visualGame.runHistory, videoReady: '비디오 준비 완료', videoFailed: '비디오 생성 실패', }, accountCenter: { ...en.visualGame.accountCenter, title: '계정', videos: '내 비디오', emptyVideos: '아직 생성한 비디오가 없습니다.', }, profileEditor: { ...en.visualGame.profileEditor, editProfile: '프로필 편집', nameSection: '프로필 이름', nameHint: '게임과 비디오에 표시되는 이름입니다.', name: '이름', saveName: '이름 저장', nameRequired: '이름을 입력해 주세요.', nameSaved: '이름이 업데이트되었습니다.', passwordSection: '비밀번호 변경', passwordHint: '이메일로 전송된 인증 코드로 본인 확인을 진행합니다.', email: '이메일', verificationCode: '이메일 인증 코드', sendCode: '코드 보내기', codeSent: '인증 코드를 이메일로 보냈습니다.', newPassword: '새 비밀번호', confirmPassword: '새 비밀번호 확인', savePassword: '비밀번호 변경', passwordRequirements: '인증 코드와 8자 이상의 새 비밀번호를 입력해 주세요.', passwordMismatch: '비밀번호가 일치하지 않습니다.', passwordSaved: '비밀번호가 변경되었습니다.', }, visual: { ...en.visualGame.visual, audioFailed: '음성 재생에 실패했습니다. 다시 시도해 주세요.', guide: { ...en.visualGame.visual.guide, keyboardTitle: '키보드 입력으로 전환', keyboardText: '기본은 음성 입력입니다. 키보드 버튼을 누르면 말하거나 행동할 내용을 직접 입력할 수 있습니다.', inputTitle: '말하기 / 행동하기 선택', inputText: '입력하기 전에 말할지 행동할지 선택하세요. 두 선택 모두 KP가 판정합니다.', videoTitle: '스토리를 완성하면 이번 모험이 나만의 영상이 됩니다', videoExampleLabel: '예시 영상', videoExampleNotice: '이 영상은 예시일 뿐이며, 실제 플레이에서 생성될 내용을 나타내지 않습니다.', videoLoading: '예시 영상을 불러오는 중…', videoUnavailable: '이 스토리에는 아직 예시 영상이 없습니다.', videoDone: '나의 스토리 시작하기', }, close: '닫기', inputMethod: '입력 방식', turnVideoMatching: '스마트 CG 매칭', turnVideoMatchingHint: '켜면 비슷한 장면의 CG를 이번 턴에 매칭합니다. 끄면 정확히 연결된 CG만 재생합니다.', turnVideoMatchingOn: '켜짐', turnVideoMatchingOff: '꺼짐', textInputMode: '텍스트', voiceInputMode: '음성', voiceShortcutLabel: '단축키', }, videoCenter: { ...en.visualGame.videoCenter, share: '공유', shareCopied: '링크를 복사했습니다', shareFailed: '영상을 공유할 수 없습니다', }, landingIntro: { ...en.visualGame.landingIntro, title: 'voko.world', apply: '액세스 신청', watchVideo: '스토리 보기', start: '게임 시작', continue: '게임 계속', viewJourney: '플레이 기록', continueProgress: '시즌 {season} · {scenario}', storyLine: '스토리라인', viewMore: '더 보기', }, landingCarousel: { ...en.visualGame.landingCarousel, introEyebrow: '생성형 드라마 게임', introSummary: '숏 드라마를 보고 역할 속으로 들어가, 모든 선택으로 스토리를 바꿔 보세요.', dramaEyebrow: '추천 드라마 {count}', controls: '홈 캐러셀', goToSlide: '{count}번 슬라이드로 이동', }, landingMetrics: { playCount: '플레이', likeCount: '좋아요' }, landingCard: { ...en.visualGame.landingCard, level: '에피소드', searchPlaceholder: '스토리, 비디오 또는 플레이어 찾기', scriptFilter: '드라마', scriptAll: '모든 드라마', playerVideoCount: '공개 비디오: {count}', publishedAt: '공개일', publishedAll: '전체 기간', publishedToday: '오늘', publishedWeek: '지난 7일', publishedMonth: '지난 30일', expandSynopsis: '더 보기', collapseSynopsis: '접기', }, }, generatedVisualGame) function mergeMessages(base, overrides) { const output = { ...base } for (const [key, value] of Object.entries(overrides)) { output[key] = value && typeof value === 'object' && !Array.isArray(value) ? mergeMessages(output[key], value) : value } return output } export default { ...en, common, auth, account, pay: { ...en.pay, ...payPoints }, visualGame, legal }