40 Commits

Author SHA1 Message Date
Yu Sung
243da1eb7d fix(i18n): 예약 날짜와 시간 표시 언어를 앱 설정에 맞춘다 2026-04-01 18:44:05 +09:00
Yu Sung
39700d3b39 chore(i18n): 미사용 String Catalog를 정리한다 2026-04-01 18:09:29 +09:00
Yu Sung
43c86a627b feat(i18n): 온보딩 시작하기 문구를 I18n 키로 통일한다 2026-04-01 17:53:03 +09:00
Yu Sung
1ec56a1f15 feat(i18n): 시리즈/재생목록 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 17:33:13 +09:00
Yu Sung
c039931f34 feat(i18n): 콘텐츠 상세/댓글 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 16:50:56 +09:00
Yu Sung
a90996603b feat(i18n): 콘텐츠 모듈 그룹2 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 16:08:26 +09:00
Yu Sung
49e2487617 feat(i18n): 콘텐츠 모듈 그룹1 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 15:36:39 +09:00
Yu Sung
038d66e363 feat(i18n): 탐색 프로필 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 14:40:37 +09:00
Yu Sung
bceec46ebc feat(i18n): 라이브 룸 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 13:52:02 +09:00
Yu Sung
540238eb48 feat(i18n): 라이브 모듈 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 11:58:41 +09:00
Yu Sung
201f4c8139 feat(i18n): 홈 화면 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 11:33:26 +09:00
Yu Sung
7285c5367d feat(i18n): 마이페이지 그룹 3~5 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 23:32:05 +09:00
Yu Sung
b53614836f feat(i18n): 마이페이지 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 23:04:33 +09:00
Yu Sung
25fccbaa07 feat(i18n): 음성 메시지 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 22:21:41 +09:00
Yu Sung
9369a52ba2 feat(i18n): 메시지 모듈 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 22:01:30 +09:00
Yu Sung
4c170e0f97 feat(i18n): 설정 화면 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 19:46:21 +09:00
Yu Sung
3f61a08a04 feat(i18n): 시리즈 리스트 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 18:50:22 +09:00
Yu Sung
9216db51da docs(i18n): 미완료 체크리스트를 10개 단위로 재배치한다 2026-03-31 18:35:04 +09:00
Yu Sung
8e4fe7a534 feat(i18n): 사용자 화면 문구를 I18n 키로 통일한다 2026-03-31 17:37:29 +09:00
Yu Sung
b2f66cf408 feat(i18n): 주요 UI 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 17:09:01 +09:00
Yu Sung
47085dc1ca feat(chat): 채팅 모듈 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 16:30:48 +09:00
Yu Sung
222520d5e9 feat(i18n): 오디션 화면 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 15:39:57 +09:00
Yu Sung
136bfc8eee docs(i18n): 하드코딩 텍스트 I18n 통일 계획을 추가한다 2026-03-31 14:48:11 +09:00
Yu Sung
178e0849dc feat(live-room): 라이브 캡쳐 녹화 허용 설정을 생성 시청 흐름에 반영한다 2026-03-30 21:49:34 +09:00
Yu Sung
3a4df173d2 fix(live-room): 스탭 해제 후 방 정보 갱신 타이밍을 보정한다 2026-03-30 18:24:40 +09:00
Yu Sung
ec7e9cc71c fix(live-room): 스탭 캡쳐 녹화 보호 예외를 반영한다 2026-03-30 16:58:44 +09:00
Yu Sung
8370f1ead1 fix(home): 라이브 19금 안내 토스트를 설정 화면에서 표시한다 2026-03-28 23:25:17 +09:00
Yu Sung
e067531a3f fix(live-room): 방장 캡쳐 보호를 해제하고 비방장 보호를 유지한다 2026-03-28 20:04:41 +09:00
Yu Sung
d369bc11f7 fix(api): 콘텐츠 설정 PATCH 제외 API 파라미터를 제거한다 2026-03-27 22:28:21 +09:00
Yu Sung
f542191d46 fix(age-setting): 국가별 연령제한 노출 조건을 통일한다 2026-03-27 18:22:53 +09:00
Yu Sung
1d120b58bd fix(content): 성인 콘텐츠 설정 동기화와 국가별 인증 분기를 적용한다 2026-03-27 17:34:02 +09:00
Yu Sung
44daabdcae fix(live-room): 캡쳐 보호 음소거 동기화 2026-03-24 19:19:29 +09:00
Yu Sung
0844c6f4d7 fix(live-room): 라이브 상세 SNS 링크 아이콘 매핑을 신규 필드에 맞춘다 2026-03-24 13:36:10 +09:00
Yu Sung
c6a6b3c79e fix(live-room): 라이브 상세 복귀 시 DIM만 보이는 상태를 방지한다 2026-03-24 12:05:25 +09:00
Yu Sung
4f66ffb595 docs(live-room): 채팅 얼림 아이콘 이동 점검 문서를 추가한다 2026-03-20 15:59:09 +09:00
Yu Sung
91b5ed974f fix(live-room): 채팅 얼림 버튼 위치와 차단 문구를 정렬한다 2026-03-20 15:58:31 +09:00
Yu Sung
af31444f0f fix(live-room): 채팅창 얼림 버튼 위치와 안내 문구를 조정한다 2026-03-20 14:27:10 +09:00
Yu Sung
8eca5df62b feat(live-room): 라이브룸 채팅 삭제 기능 구현 2026-03-20 10:51:22 +09:00
Yu Sung
793b5dd95a fix(live-room): 채팅 금지 입력 차단 안내를 즉시 노출한다 2026-03-19 18:46:41 +09:00
Yu Sung
70003af82b feat(live-room): 채팅창 얼리기 기능을 추가한다
채팅 입력 제어와 룸 상태 동기화를 통합해 지연 입장자도 동일 상태를 적용한다.
2026-03-19 18:20:13 +09:00
360 changed files with 9713 additions and 12378 deletions

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_ice.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -74,6 +74,7 @@ class AppState: ObservableObject {
@Published var isShowErrorPopup = false @Published var isShowErrorPopup = false
@Published var errorMessage = "" @Published var errorMessage = ""
@Published private var pendingContentSettingsGuideMessage: String? = nil
@Published var liveDetailSheet: LiveDetailSheetState? = nil @Published var liveDetailSheet: LiveDetailSheetState? = nil
private func syncStepWithNavigationPath() { private func syncStepWithNavigationPath() {
@@ -181,6 +182,16 @@ class AppState: ObservableObject {
pendingCommunityCommentPostId = 0 pendingCommunityCommentPostId = 0
} }
func setPendingContentSettingsGuideMessage(_ message: String) {
pendingContentSettingsGuideMessage = message
}
func consumePendingContentSettingsGuideMessage() -> String? {
let message = pendingContentSettingsGuideMessage
pendingContentSettingsGuideMessage = nil
return message
}
// ( -> ) UI // ( -> ) UI
func softRestart() { func softRestart() {
isRestartApp = true isRestartApp = true

View File

@@ -75,6 +75,9 @@ final class AppViewModel: ObservableObject {
UserDefaults.set(data.isAuth, forKey: .auth) UserDefaults.set(data.isAuth, forKey: .auth)
UserDefaults.set(data.role.rawValue, forKey: .role) UserDefaults.set(data.role.rawValue, forKey: .role)
UserDefaults.set(data.auditionNotice ?? false, forKey: .isAuditionNotification) UserDefaults.set(data.auditionNotice ?? false, forKey: .isAuditionNotification)
UserDefaults.set(data.countryCode, forKey: .countryCode)
UserDefaults.set(data.isAdultContentVisible, forKey: .isAdultContentVisible)
UserDefaults.set(data.contentType, forKey: .contentPreference)
if data.followingChannelLiveNotice == nil && data.followingChannelUploadContentNotice == nil && data.messageNotice == nil { if data.followingChannelLiveNotice == nil && data.followingChannelUploadContentNotice == nil && data.messageNotice == nil {
AppState.shared.isShowNotificationSettingsDialog = true AppState.shared.isShowNotificationSettingsDialog = true
} }

View File

@@ -30,7 +30,7 @@ struct ApplyMethodView: View {
} }
} }
Text("오디션 지원방식") Text(I18n.Audition.ApplyMethod.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
.padding(.top, 33.3) .padding(.top, 33.3)
@@ -39,7 +39,7 @@ struct ApplyMethodView: View {
HStack(spacing: 3) { HStack(spacing: 3) {
Image("ic_upload") Image("ic_upload")
Text("파일 업로드") Text(I18n.Audition.ApplyMethod.fileUpload)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.button) .foregroundColor(Color.button)
} }
@@ -60,7 +60,7 @@ struct ApplyMethodView: View {
HStack(spacing: 3) { HStack(spacing: 3) {
Image("ic_mic_color_button") Image("ic_mic_color_button")
Text("바로 녹음") Text(I18n.Audition.ApplyMethod.recordNow)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.button) .foregroundColor(Color.button)
} }
@@ -81,7 +81,7 @@ struct ApplyMethodView: View {
.padding(.top, 21.3) .padding(.top, 21.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("※ 파일은 mp3, aac만 업로드 가능") Text(I18n.Audition.ApplyMethod.fileFormatNotice)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
.padding(.top, 13.3) .padding(.top, 13.3)

View File

@@ -30,7 +30,7 @@ struct AuditionApplicantRecordingView: View {
VStack { VStack {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("오디션 녹음") Text(I18n.Audition.Recording.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
@@ -76,7 +76,7 @@ struct AuditionApplicantRecordingView: View {
HStack(spacing: 0) { HStack(spacing: 0) {
Spacer() Spacer()
Text("삭제") Text(I18n.Common.delete)
.appFont(size: 15.3, weight: .medium) .appFont(size: 15.3, weight: .medium)
.foregroundColor(Color.graybb.opacity(0)) .foregroundColor(Color.graybb.opacity(0))
@@ -99,7 +99,7 @@ struct AuditionApplicantRecordingView: View {
Spacer() Spacer()
Text("삭제") Text(I18n.Common.delete)
.appFont(size: 15.3, weight: .medium) .appFont(size: 15.3, weight: .medium)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
.onTapGesture { .onTapGesture {
@@ -113,7 +113,7 @@ struct AuditionApplicantRecordingView: View {
.padding(.vertical, 52.3) .padding(.vertical, 52.3)
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Text("다시 녹음") Text(I18n.Audition.Recording.recordAgain)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)
.frame(width: (proxy.size.width - 40) / 3, height: 50) .frame(width: (proxy.size.width - 40) / 3, height: 50)
@@ -129,7 +129,7 @@ struct AuditionApplicantRecordingView: View {
soundManager.recordMode = .RECORD soundManager.recordMode = .RECORD
} }
Text("녹음완료") Text(I18n.Audition.Recording.recordComplete)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
.frame(width: (proxy.size.width - 40) * 2 / 3, height: 50) .frame(width: (proxy.size.width - 40) * 2 / 3, height: 50)
@@ -140,7 +140,7 @@ struct AuditionApplicantRecordingView: View {
let soundData = try Data(contentsOf: soundManager.getAudioFileURL()) let soundData = try Data(contentsOf: soundManager.getAudioFileURL())
onClickCompleteRecording(tempFileName, soundData) onClickCompleteRecording(tempFileName, soundData)
} catch { } catch {
errorMessage = "녹음파일을 생성하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." errorMessage = I18n.Audition.Recording.createFileFailed
isShowPopup = true isShowPopup = true
} }
} }

View File

@@ -33,7 +33,7 @@ struct AuditionApplyView: View {
if isShow { if isShow {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("오디션 지원") Text(I18n.Audition.Apply.title)
.appFont(size: 18.3, weight: .medium) .appFont(size: 18.3, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
@@ -45,7 +45,7 @@ struct AuditionApplyView: View {
} }
} }
Text("녹음파일") Text(I18n.Audition.Apply.recordingFile)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(.grayee) .foregroundColor(.grayee)
.padding(.top, 20) .padding(.top, 20)
@@ -53,7 +53,7 @@ struct AuditionApplyView: View {
HStack(spacing: 4) { HStack(spacing: 4) {
Image("ic_note_square") Image("ic_note_square")
Text(filename) Text(displayFileName)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(.grayd2) .foregroundColor(.grayd2)
@@ -66,12 +66,12 @@ struct AuditionApplyView: View {
.cornerRadius(5.3) .cornerRadius(5.3)
.padding(.top, 10) .padding(.top, 10)
Text("연락처") Text(I18n.Audition.Apply.contact)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(.grayee) .foregroundColor(.grayee)
.padding(.top, 15) .padding(.top, 15)
TextField("합격시 받을 연락처를 남겨주세요", text: $phoneNumber) TextField(I18n.Audition.Apply.contactPlaceholder, text: $phoneNumber)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
@@ -89,7 +89,7 @@ struct AuditionApplyView: View {
.resizable() .resizable()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("보이스온 오디오 드라마 오디션 합격시 개인 연락을 위한 개인 정보(연락처) 수집 및 활용에 동의합니다.\n오디션 지원자는 개인정보 수집 및 활용 동의에 거부할 권리가 있으며 비동의시 오디션 지원은 취소 됩니다.") Text(I18n.Audition.Apply.privacyAgreement)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.lineSpacing(3) .lineSpacing(3)
@@ -100,7 +100,7 @@ struct AuditionApplyView: View {
isAgree.toggle() isAgree.toggle()
} }
Text("오디션 지원하기") Text(I18n.Audition.Apply.submit)
.appFont(size: 13.3, weight: .bold) .appFont(size: 13.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.padding(.vertical, 13.3) .padding(.vertical, 13.3)
@@ -110,7 +110,7 @@ struct AuditionApplyView: View {
.padding(.top, 35) .padding(.top, 35)
.onTapGesture { .onTapGesture {
if !isAgree { if !isAgree {
errorMessage = "연락처 수집 및 활용에 동의하셔야 오디션 지원이 가능합니다." errorMessage = I18n.Audition.Apply.requireAgreement
isShowPopup = true isShowPopup = true
return return
} }
@@ -137,6 +137,14 @@ struct AuditionApplyView: View {
} }
} }
} }
private var displayFileName: String {
if filename.hasPrefix("voiceon_now_voice_") {
return I18n.Audition.Apply.recordedVoiceFileName
}
return filename
}
} }
#Preview { #Preview {

View File

@@ -24,7 +24,7 @@ struct AuditionView: View {
.resizable() .resizable()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("오디션") Text(I18n.Audition.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
} }
@@ -43,13 +43,13 @@ struct AuditionView: View {
.background(Color.black) .background(Color.black)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("보이스온 오디션 이용방법") Text(I18n.Audition.List.usageGuide)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
Spacer() Spacer()
Text("자세히>") Text(I18n.Audition.List.detail)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
} }
@@ -73,17 +73,17 @@ struct AuditionView: View {
if $0 == 0 && !item.isOff { if $0 == 0 && !item.isOff {
VStack(alignment: .leading, spacing: 25) { VStack(alignment: .leading, spacing: 25) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("오디션") Text(I18n.Audition.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
Text(" ON") Text(I18n.Audition.List.onStatus)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.mainRed) .foregroundColor(Color.mainRed)
Spacer() Spacer()
Text("\(viewModel.inProgressCount)") Text(I18n.Audition.List.totalCount(viewModel.inProgressCount))
.appFont(size: 11.3, weight: .medium) .appFont(size: 11.3, weight: .medium)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
} }
@@ -111,17 +111,17 @@ struct AuditionView: View {
.padding(.top, 5) .padding(.top, 5)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("오디션") Text(I18n.Audition.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
Text(" OFF") Text(I18n.Audition.List.offStatus)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
Spacer() Spacer()
Text("\(viewModel.completedCount)") Text(I18n.Audition.List.totalCount(viewModel.completedCount))
.appFont(size: 11.3, weight: .medium) .appFont(size: 11.3, weight: .medium)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
} }

View File

@@ -66,13 +66,13 @@ final class AuditionViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }

View File

@@ -30,7 +30,7 @@ struct AuditionDetailView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.cornerRadius(6.7) .cornerRadius(6.7)
Text("오디션 정보") Text(I18n.Audition.Detail.informationTitle)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.padding(.top, 15) .padding(.top, 15)
@@ -38,7 +38,7 @@ struct AuditionDetailView: View {
ExpandableTextView(text: response.information) ExpandableTextView(text: response.information)
.padding(.top, 13.3) .padding(.top, 13.3)
Text("오디션 캐릭터") Text(I18n.Audition.Detail.characterTitle)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.padding(.top, 15) .padding(.top, 15)

View File

@@ -18,7 +18,7 @@ final class AuditionDetailViewModel: ObservableObject {
@Published var isLoading = false @Published var isLoading = false
@Published var response: GetAuditionDetailResponse? = nil @Published var response: GetAuditionDetailResponse? = nil
@Published var title: String = "보이스온" @Published var title: String = I18n.Audition.defaultTitle
func getAuditionDetail(auditionId: Int, onFailure: @escaping () -> Void) { func getAuditionDetail(auditionId: Int, onFailure: @escaping () -> Void) {
isLoading = true isLoading = true
@@ -45,7 +45,7 @@ final class AuditionDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
@@ -54,7 +54,7 @@ final class AuditionDetailViewModel: ObservableObject {
} }
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
onFailure() onFailure()

View File

@@ -64,7 +64,7 @@ class AuditionSoundManager: NSObject, ObservableObject {
private func setupPlayer(with url: String) { private func setupPlayer(with url: String) {
guard let url = URL(string: url) else { guard let url = URL(string: url) else {
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요." self.errorMessage = I18n.Audition.Sound.playbackFailed
self.isShowPopup = true self.isShowPopup = true
return return
} }
@@ -92,7 +92,7 @@ class AuditionSoundManager: NSObject, ObservableObject {
} }
} catch { } catch {
DispatchQueue.main.async { DispatchQueue.main.async {
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요." self.errorMessage = I18n.Audition.Sound.playbackFailed
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }

View File

@@ -28,7 +28,7 @@ struct AuditionDetailRoleItemView: View {
.opacity(item.isComplete ? 0.7 : 0.0) .opacity(item.isComplete ? 0.7 : 0.0)
) )
Text(item.isComplete ? "모집완료" : "모집중") Text(item.isComplete ? I18n.Audition.Detail.recruitmentClosed : I18n.Audition.Detail.recruiting)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.horizontal, 9) .padding(.horizontal, 9)

View File

@@ -45,7 +45,7 @@ struct AuditionRoleDetailView: View {
HStack(spacing: 14) { HStack(spacing: 14) {
if let url = URL(string: roleDetail.originalWorkUrl), UIApplication.shared.canOpenURL(url) { if let url = URL(string: roleDetail.originalWorkUrl), UIApplication.shared.canOpenURL(url) {
Text("원작 보러가기") Text(I18n.Audition.Detail.viewOriginalWork)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)
.padding(.vertical, 12) .padding(.vertical, 12)
@@ -59,7 +59,7 @@ struct AuditionRoleDetailView: View {
} }
if let url = URL(string: roleDetail.auditionScriptUrl), UIApplication.shared.canOpenURL(url) { if let url = URL(string: roleDetail.auditionScriptUrl), UIApplication.shared.canOpenURL(url) {
Text("오디션 대본 확인") Text(I18n.Audition.Detail.checkScript)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)
.padding(.vertical, 12) .padding(.vertical, 12)
@@ -74,7 +74,7 @@ struct AuditionRoleDetailView: View {
} }
VStack(alignment: .leading, spacing: 13.3) { VStack(alignment: .leading, spacing: 13.3) {
Text("오디션 캐릭터 정보") Text(I18n.Audition.Detail.characterInfo)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -83,13 +83,13 @@ struct AuditionRoleDetailView: View {
} }
if viewModel.applicantList.isEmpty { if viewModel.applicantList.isEmpty {
Text("지원자가 없습니다.") Text(I18n.Audition.Detail.noApplicants)
.appFont(size: 13, weight: .medium) .appFont(size: 13, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.padding(.top, 15) .padding(.top, 15)
} else { } else {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("참여자") Text(I18n.Audition.Detail.participants)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
@@ -98,13 +98,13 @@ struct AuditionRoleDetailView: View {
.foregroundColor(Color.button) .foregroundColor(Color.button)
.padding(.leading, 2.3) .padding(.leading, 2.3)
Text("") Text(I18n.Audition.Detail.personUnit)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
Spacer() Spacer()
Text("최신순") Text(I18n.Audition.Detail.sortNewest)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
viewModel.sortType == .NEWEST ? Color.button : Color.graybb viewModel.sortType == .NEWEST ? Color.button : Color.graybb
@@ -113,7 +113,7 @@ struct AuditionRoleDetailView: View {
viewModel.setSortType(sortType: .NEWEST) viewModel.setSortType(sortType: .NEWEST)
} }
Text("좋아요순") Text(I18n.Audition.Detail.sortLikes)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
viewModel.sortType == .LIKES ? Color.button : Color.graybb viewModel.sortType == .LIKES ? Color.button : Color.graybb
@@ -161,7 +161,7 @@ struct AuditionRoleDetailView: View {
} }
if let roleDetail = viewModel.auditionRoleDetail { if let roleDetail = viewModel.auditionRoleDetail {
Text(roleDetail.isAlreadyApplicant ? "오디션 재지원" : "오디션 지원") Text(roleDetail.isAlreadyApplicant ? I18n.Audition.Apply.reapply : I18n.Audition.Apply.apply)
.appFont(size: 15.3, weight: .bold) .appFont(size: 15.3, weight: .bold)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(14) .padding(14)
@@ -241,9 +241,9 @@ struct AuditionRoleDetailView: View {
if isShowNoticeReapply { if isShowNoticeReapply {
SodaDialog( SodaDialog(
title: "재지원 안내", title: I18n.Audition.Apply.reapplyNoticeTitle,
desc: "재지원 시 이전 지원 내역은 삭제되며 받은 투표수는 무효 처리됩니다.", desc: I18n.Audition.Apply.reapplyNoticeDesc,
confirmButtonTitle: "확인" confirmButtonTitle: I18n.Common.confirm
) { ) {
isShowNoticeReapply = false isShowNoticeReapply = false
isShowApplyMethodView = true isShowApplyMethodView = true
@@ -252,9 +252,9 @@ struct AuditionRoleDetailView: View {
if isShowNoticeAuthView { if isShowNoticeAuthView {
SodaDialog( SodaDialog(
title: "- 본인인증 -", title: I18n.Audition.Apply.authRequiredTitle,
desc: "마이페이지에서 '본인인증'을 하고 다시 오디션에 지원해 주세요.", desc: I18n.Audition.Apply.authRequiredDesc,
confirmButtonTitle: "확인" confirmButtonTitle: I18n.Common.confirm
) { ) {
isShowNoticeAuthView = false isShowNoticeAuthView = false
} }
@@ -264,7 +264,7 @@ struct AuditionRoleDetailView: View {
SodaDialog( SodaDialog(
title: viewModel.dialogTitle, title: viewModel.dialogTitle,
desc: viewModel.dialogDesc, desc: viewModel.dialogDesc,
confirmButtonTitle: "확인" confirmButtonTitle: I18n.Common.confirm
) { ) {
viewModel.isShowVoteCompleteView = false viewModel.isShowVoteCompleteView = false
viewModel.isShowNotifyVote = false viewModel.isShowNotifyVote = false
@@ -300,17 +300,17 @@ struct AuditionRoleDetailView: View {
viewModel.fileName = fileUrl.lastPathComponent viewModel.fileName = fileUrl.lastPathComponent
isShowApplyView = true isShowApplyView = true
} else { } else {
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요" viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
viewModel.isShowPopup = true viewModel.isShowPopup = true
} }
} else { } else {
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요" viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
viewModel.isShowPopup = true viewModel.isShowPopup = true
} }
case .failure(let error): case .failure(let error):
DEBUG_LOG("error: \(error.localizedDescription)") DEBUG_LOG("error: \(error.localizedDescription)")
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요" viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
viewModel.isShowPopup = true viewModel.isShowPopup = true
} }
} }

View File

@@ -21,7 +21,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
@Published var totalCount = 0 @Published var totalCount = 0
@Published var applicantList = [GetAuditionRoleApplicantItem]() @Published var applicantList = [GetAuditionRoleApplicantItem]()
@Published var name = "보이스온" @Published var name = I18n.Audition.defaultTitle
@Published var auditionRoleDetail: GetAuditionRoleDetailResponse? = nil @Published var auditionRoleDetail: GetAuditionRoleDetailResponse? = nil
@Published private(set) var sortType = AuditionApplicantSortType.NEWEST { @Published private(set) var sortType = AuditionApplicantSortType.NEWEST {
@@ -93,13 +93,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
if let message = roleDetailDecoded.message { if let message = roleDetailDecoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
@@ -118,7 +118,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
if let message = applicantListDecoded.message { if let message = applicantListDecoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
@@ -127,7 +127,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
} }
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.onFailure() self.onFailure()
@@ -172,13 +172,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -188,13 +188,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
func applyAudition(onSuccess: @escaping () -> Void) { func applyAudition(onSuccess: @escaping () -> Void) {
if phoneNumber.count != 11 { if phoneNumber.count != 11 {
errorMessage = "잘못된 연락처 입니다.\n다시 입력해 주세요." errorMessage = I18n.Audition.Apply.invalidContact
isShowPopup = true isShowPopup = true
return return
} }
guard let soundData = soundData else { guard let soundData = soundData else {
errorMessage = "잘못된 녹음 파일 입니다.\n다시 선택해 주세요." errorMessage = I18n.Audition.Apply.invalidRecordingFile
isShowPopup = true isShowPopup = true
return return
} }
@@ -248,19 +248,19 @@ final class AuditionRoleDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Audition.Apply.applyFailed
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Audition.Apply.applyFailed
self.isShowPopup = true self.isShowPopup = true
} }
} }
.store(in: &subscription) .store(in: &subscription)
} else { } else {
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Audition.Apply.applyFailed
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }
@@ -287,8 +287,8 @@ final class AuditionRoleDetailViewModel: ObservableObject {
if decoded.success { if decoded.success {
if self.isShowNotifyVote { if self.isShowNotifyVote {
self.dialogTitle = "[오디션 응원]" self.dialogTitle = I18n.Audition.Vote.cheerTitle
self.dialogDesc = "오디션을 응원하셨습니다\n(무료응원 : 1계정당 1일 1회)\n1캔으로 추가 응원을 해보세요." self.dialogDesc = I18n.Audition.Vote.cheerDescription
self.isShowVoteCompleteView = true self.isShowVoteCompleteView = true
} }
@@ -302,20 +302,20 @@ final class AuditionRoleDetailViewModel: ObservableObject {
} else { } else {
if let message = decoded.message { if let message = decoded.message {
if message.contains("오늘 응원은 여기까지") { if message.contains("오늘 응원은 여기까지") {
self.dialogTitle = "[오늘 응원 제한]" self.dialogTitle = I18n.Audition.Vote.limitTitle
self.dialogDesc = "오늘 응원은 여기까지!\n하루 최대 10회까지 이용이 가능합니다.\n내일 다시 이용해주세요." self.dialogDesc = I18n.Audition.Vote.limitDescription
self.isShowVoteCompleteView = true self.isShowVoteCompleteView = true
} else { } else {
self.errorMessage = message self.errorMessage = message
self.isShowPopup = true self.isShowPopup = true
} }
} else { } else {
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." self.errorMessage = I18n.Audition.Vote.unknownError
self.isShowPopup = true self.isShowPopup = true
} }
} }
} catch { } catch {
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." self.errorMessage = I18n.Audition.Vote.unknownError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -33,7 +33,7 @@ struct CharacterItemView: View {
HStack { HStack {
Spacer() Spacer()
Text("N") Text(I18n.Chat.Character.newBadge)
.appFont(size: 18, weight: .regular) .appFont(size: 18, weight: .regular)
.foregroundColor(.white) .foregroundColor(.white)
.frame(width: 30, height: 30) .frame(width: 30, height: 30)

View File

@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
struct CharacterSectionView: View { struct CharacterSectionView: View {
let title: LocalizedStringResource let title: String
let items: [Character] let items: [Character]
let isShowRank: Bool let isShowRank: Bool
var trailingTitle: String? = nil var trailingTitle: String? = nil
@@ -52,7 +52,7 @@ struct CharacterSectionView: View {
#Preview { #Preview {
CharacterSectionView( CharacterSectionView(
title: "신규 캐릭터", title: I18n.Chat.Character.newSectionTitle,
items: [ items: [
Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300", isNew: true), Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300", isNew: true),
Character(characterId: 2, name: "데이지", description: "", imageUrl: "https://picsum.photos/300", isNew: false) Character(characterId: 2, name: "데이지", description: "", imageUrl: "https://picsum.photos/300", isNew: false)

View File

@@ -39,7 +39,7 @@ struct CharacterView: View {
// //
if !viewModel.popularCharacters.isEmpty { if !viewModel.popularCharacters.isEmpty {
CharacterSectionView( CharacterSectionView(
title: "인기 캐릭터", title: I18n.Chat.Character.popularSectionTitle,
items: viewModel.popularCharacters, items: viewModel.popularCharacters,
isShowRank: true, isShowRank: true,
onTap: { ch in onTap: { ch in
@@ -51,7 +51,7 @@ struct CharacterView: View {
// //
if !viewModel.newCharacters.isEmpty { if !viewModel.newCharacters.isEmpty {
CharacterSectionView( CharacterSectionView(
title: "신규 캐릭터", title: I18n.Chat.Character.newSectionTitle,
items: viewModel.newCharacters, items: viewModel.newCharacters,
isShowRank: false, isShowRank: false,
trailingTitle: I18n.Common.viewAll, trailingTitle: I18n.Common.viewAll,
@@ -67,7 +67,7 @@ struct CharacterView: View {
if !viewModel.recommendCharacters.isEmpty { if !viewModel.recommendCharacters.isEmpty {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
HStack { HStack {
Text("추천 캐릭터") Text(I18n.Chat.Character.recommendSectionTitle)
.appFont(size: 24, weight: .bold) .appFont(size: 24, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)

View File

@@ -54,7 +54,7 @@ final class CharacterViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
@@ -62,7 +62,7 @@ final class CharacterViewModel: ObservableObject {
self.isLoading = false self.isLoading = false
} catch { } catch {
self.isLoading = false self.isLoading = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -93,7 +93,7 @@ final class CharacterViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
@@ -101,11 +101,10 @@ final class CharacterViewModel: ObservableObject {
self.isLoading = false self.isLoading = false
} catch { } catch {
self.isLoading = false self.isLoading = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
.store(in: &subscription) .store(in: &subscription)
} }
} }

View File

@@ -34,7 +34,7 @@ struct CharacterDetailView: View {
var body: some View { var body: some View {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: String(localized: "캐릭터 정보")) { DetailNavigationBar(title: I18n.Chat.Character.detailTitle) {
if presentationMode.wrappedValue.isPresented { if presentationMode.wrappedValue.isPresented {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
} else { } else {
@@ -77,7 +77,7 @@ struct CharacterDetailView: View {
if let others = viewModel.characterDetail?.others, !others.isEmpty { if let others = viewModel.characterDetail?.others, !others.isEmpty {
VStack(spacing: 16) { VStack(spacing: 16) {
HStack { HStack {
Text("장르의 다른 캐릭터") Text(I18n.Chat.Character.detailOtherCharactersTitle)
.appFont(size: 26, weight: .bold) .appFont(size: 26, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
@@ -178,6 +178,13 @@ extension CharacterDetailView {
// MARK: - Profile Section // MARK: - Profile Section
extension CharacterDetailView { extension CharacterDetailView {
private func isMaleGender(_ gender: String) -> Bool {
let normalizedGender = gender
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
return normalizedGender == "남성" || normalizedGender == "male"
}
private var profileSection: some View { private var profileSection: some View {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
if viewModel.characterDetail?.mbti != nil || if viewModel.characterDetail?.mbti != nil ||
@@ -189,7 +196,7 @@ extension CharacterDetailView {
Text(viewModel.characterDetail?.translated?.gender ?? gender) Text(viewModel.characterDetail?.translated?.gender ?? gender)
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor( .foregroundColor(
gender == "남성" ? isMaleGender(gender) ?
Color.button : Color.button :
Color.mainRed Color.mainRed
) )
@@ -201,7 +208,7 @@ extension CharacterDetailView {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)
.stroke(lineWidth: 1) .stroke(lineWidth: 1)
.foregroundColor( .foregroundColor(
gender == "남성" ? isMaleGender(gender) ?
Color.button : Color.button :
Color.mainRed Color.mainRed
) )
@@ -209,7 +216,7 @@ extension CharacterDetailView {
} }
if let age = viewModel.characterDetail?.age { if let age = viewModel.characterDetail?.age {
Text("\(age)") Text(I18n.Chat.Character.age(age))
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
.padding(.horizontal, 7) .padding(.horizontal, 7)
@@ -252,7 +259,7 @@ extension CharacterDetailView {
if let characterType = viewModel.characterDetail?.characterType { if let characterType = viewModel.characterDetail?.characterType {
HStack(spacing: 8) { HStack(spacing: 8) {
Text(characterType.rawValue) Text(characterType == .Clone ? I18n.Chat.Character.typeClone : I18n.Chat.Character.typeCharacter)
.appFont(size: 12, weight: .regular) .appFont(size: 12, weight: .regular)
.foregroundColor(.white) .foregroundColor(.white)
.padding(.horizontal, 5) .padding(.horizontal, 5)
@@ -282,7 +289,7 @@ extension CharacterDetailView {
private func worldViewSection(backgrounds: String) -> some View { private func worldViewSection(backgrounds: String) -> some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
HStack { HStack {
Text("[세계관 및 작품 소개]") Text(I18n.Chat.Character.detailWorldViewTitle)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
@@ -300,7 +307,7 @@ extension CharacterDetailView {
private func originalWorkSection(title: String, link: String) -> some View { private func originalWorkSection(title: String, link: String) -> some View {
VStack(spacing: 8) { VStack(spacing: 8) {
HStack { HStack {
Text("원작") Text(I18n.Chat.Character.detailOriginalTitle)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.fontWeight(.bold) .fontWeight(.bold)
.foregroundColor(.white) .foregroundColor(.white)
@@ -321,7 +328,7 @@ extension CharacterDetailView {
UIApplication.shared.open(url) UIApplication.shared.open(url)
} }
}) { }) {
Text("원작 보러가기") Text(I18n.Chat.Character.detailOriginalLinkButton)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.fontWeight(.bold) .fontWeight(.bold)
.foregroundColor(Color(hex: "3BB9F1")) .foregroundColor(Color(hex: "3BB9F1"))
@@ -342,7 +349,7 @@ extension CharacterDetailView {
private func personalitySection(personalities: String) -> some View { private func personalitySection(personalities: String) -> some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
HStack { HStack {
Text("[성격 및 특징]") Text(I18n.Chat.Character.detailPersonalityTitle)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
@@ -354,24 +361,19 @@ extension CharacterDetailView {
// //
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
HStack { HStack {
Text("⚠️ 캐릭터톡 대화 가이드") Text(I18n.Chat.Character.detailConversationGuideTitle)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
Spacer() Spacer()
} }
Text(""" Text(I18n.Chat.Character.detailConversationGuideDescription1)
보이스온의 오픈월드 캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다. 세계관 속 연관 캐릭터가 되어 대화를 하거나 완전히 새로운 인물이 되어 캐릭터와 당신만의 스토리를 만들어 갈 수 있습니다.
""")
.appFont(size: 16, weight: .regular) .appFont(size: 16, weight: .regular)
.foregroundColor(Color(hex: "AEAEB2")) .foregroundColor(Color(hex: "AEAEB2"))
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
Text(""" Text(I18n.Chat.Character.detailConversationGuideDescription2)
오픈월드 캐릭터톡은 캐릭터를 정교하게 설계하였지만, 대화가 어색하거나 불완전할 수도 있습니다.
대화 도중 캐릭터의 대화가 이상하거나 새로운 캐릭터로 대화를 나누고 싶다면 대화를 초기화 하고 새롭게 캐릭터와 대화를 나눠보세요.
""")
.appFont(size: 16, weight: .regular) .appFont(size: 16, weight: .regular)
.foregroundColor(Color(hex: "AEAEB2")) .foregroundColor(Color(hex: "AEAEB2"))
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
@@ -393,7 +395,7 @@ extension CharacterDetailView {
// MARK: - Chat Button // MARK: - Chat Button
extension CharacterDetailView { extension CharacterDetailView {
private var chatButton: some View { private var chatButton: some View {
Text("대화하기") Text(I18n.Chat.Character.detailChatButton)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -450,7 +452,7 @@ struct CharacterExpandableTextView: View {
.foregroundColor(Color(hex: "607D8B")) .foregroundColor(Color(hex: "607D8B"))
.rotationEffect(.degrees(isExpanded ? 180 : 0)) .rotationEffect(.degrees(isExpanded ? 180 : 0))
Text(isExpanded ? "간략히" : "더보기") Text(isExpanded ? I18n.Chat.Character.detailCollapse : I18n.Chat.Character.detailExpand)
.appFont(size: 16, weight: .regular) .appFont(size: 16, weight: .regular)
.foregroundColor(Color(hex: "607D8B")) .foregroundColor(Color(hex: "607D8B"))
} }

View File

@@ -76,7 +76,7 @@ struct CharacterDetailGalleryView: View {
VStack(spacing: 8) { VStack(spacing: 8) {
// ( % , , ) // ( % , , )
HStack { HStack {
Text("\(viewModel.ownershipPercentage)% 보유중") Text(I18n.Chat.Character.DetailGallery.ownership(viewModel.ownershipPercentage))
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
@@ -91,7 +91,7 @@ struct CharacterDetailGalleryView: View {
.appFont(size: 16, weight: .regular) .appFont(size: 16, weight: .regular)
.foregroundColor(.white) .foregroundColor(.white)
Text("\(viewModel.totalCount)") Text(I18n.Chat.Character.DetailGallery.totalCount(viewModel.totalCount))
.appFont(size: 16, weight: .regular) .appFont(size: 16, weight: .regular)
.foregroundColor(.white) .foregroundColor(.white)
} }

View File

@@ -68,7 +68,7 @@ final class NewCharacterListViewModel: ObservableObject {
} else { } else {
self?.isLoading = false self?.isLoading = false
} }
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} receiveValue: { [weak self] response in } receiveValue: { [weak self] response in
@@ -93,7 +93,7 @@ final class NewCharacterListViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
if isLoadMore { if isLoadMore {
@@ -108,7 +108,7 @@ final class NewCharacterListViewModel: ObservableObject {
} else { } else {
self.isLoading = false self.isLoading = false
} }
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -17,18 +17,18 @@ struct NewCharacterListView: View {
Group { BaseView(isLoading: $viewModel.isLoading) { Group { BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 8) { VStack(spacing: 8) {
// Toolbar // Toolbar
DetailNavigationBar(title: String(localized: "신규 캐릭터 전체보기")) DetailNavigationBar(title: I18n.Chat.Character.NewList.title)
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
// n // n
HStack(spacing: 0) { HStack(spacing: 0) {
Text("전체") Text(I18n.Chat.Character.NewList.totalPrefix)
.appFont(size: 12, weight: .regular) .appFont(size: 12, weight: .regular)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
Text(" \(viewModel.totalCount)") Text(" \(viewModel.totalCount)")
.appFont(size: 12, weight: .regular) .appFont(size: 12, weight: .regular)
.foregroundColor(Color(hex: "ff5c49")) .foregroundColor(Color(hex: "ff5c49"))
Text("") Text(I18n.Chat.Character.NewList.countUnit)
.appFont(size: 12, weight: .regular) .appFont(size: 12, weight: .regular)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
Spacer() Spacer()

View File

@@ -14,8 +14,8 @@ struct RecentCharacterSectionView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) { HStack(spacing: 4) {
Text("최근 대화한 캐릭터 ") Text(I18n.Chat.Character.recentSectionTitle)
.appFont(size: 20, weight: .bold) .appFont(size: 20, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)

View File

@@ -41,7 +41,14 @@ struct ChatTabView: View {
AppState.shared.setAppStep(step: .login) AppState.shared.setAppStep(step: .login)
return return
} }
if auth == false {
let normalizedCountryCode = UserDefaults
.string(forKey: .countryCode)
.trimmingCharacters(in: .whitespacesAndNewlines)
.uppercased()
let isKoreanCountry = normalizedCountryCode.isEmpty || normalizedCountryCode == "KR"
if isKoreanCountry && auth == false {
pendingAction = { pendingAction = {
AppState.shared AppState.shared
.setAppStep(step: .characterDetail(characterId: characterId)) .setAppStep(step: .characterDetail(characterId: characterId))
@@ -49,9 +56,21 @@ struct ChatTabView: View {
isShowAuthConfirmView = true isShowAuthConfirmView = true
return return
} }
if !UserDefaults.isAdultContentVisible() {
pendingAction = nil
moveToContentSettingsWithGuideToast()
return
}
AppState.shared.setAppStep(step: .characterDetail(characterId: characterId)) AppState.shared.setAppStep(step: .characterDetail(characterId: characterId))
} }
private func moveToContentSettingsWithGuideToast() {
AppState.shared.setPendingContentSettingsGuideMessage(I18n.Settings.adultContentEnableGuide)
AppState.shared.setAppStep(step: .contentViewSettings)
}
private func handleCharacterSelection() { private func handleCharacterSelection() {
let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { guard !trimmed.isEmpty else {
@@ -149,7 +168,7 @@ struct ChatTabView: View {
isShowAuthView = false isShowAuthView = false
} }
.onError { _ in .onError { _ in
AppState.shared.errorMessage = "본인인증 중 오류가 발생했습니다." AppState.shared.errorMessage = I18n.Chat.Auth.authenticationError
AppState.shared.isShowErrorPopup = true AppState.shared.isShowErrorPopup = true
isShowAuthView = false isShowAuthView = false
} }
@@ -171,15 +190,14 @@ struct ChatTabView: View {
if isShowAuthConfirmView { if isShowAuthConfirmView {
SodaDialog( SodaDialog(
title: "본인인증", title: I18n.Chat.Auth.dialogTitle,
desc: "보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n" + desc: I18n.Chat.Auth.dialogDescription,
"캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.", confirmButtonTitle: I18n.Chat.Auth.goToVerification,
confirmButtonTitle: "본인인증 하러가기",
confirmButtonAction: { confirmButtonAction: {
isShowAuthConfirmView = false isShowAuthConfirmView = false
isShowAuthView = true isShowAuthView = true
}, },
cancelButtonTitle: "취소", cancelButtonTitle: I18n.Common.cancel,
cancelButtonAction: { cancelButtonAction: {
isShowAuthConfirmView = false isShowAuthConfirmView = false
pendingAction = nil pendingAction = nil

View File

@@ -58,7 +58,7 @@ struct OriginalWorkDetailHeaderView: View {
} }
if item.isAdult { if item.isAdult {
Text("19+") Text(I18n.Chat.Original.adultBadge)
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor(Color(hex: "ff5c49")) .foregroundColor(Color(hex: "ff5c49"))
.padding(.horizontal, 7) .padding(.horizontal, 7)

View File

@@ -151,7 +151,7 @@ struct OriginalWorkInfoView: View {
ZStack { ZStack {
VStack(spacing: 16) { VStack(spacing: 16) {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("작품 소개") Text(I18n.Chat.Original.workIntroductionTitle)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
@@ -170,7 +170,7 @@ struct OriginalWorkInfoView: View {
.cornerRadius(16) .cornerRadius(16)
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("원작 보러 가기") Text(I18n.Chat.Original.viewOriginalLinksTitle)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
@@ -197,26 +197,26 @@ struct OriginalWorkInfoView: View {
.cornerRadius(16) .cornerRadius(16)
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("상세 정보") Text(I18n.Chat.Original.detailInfoTitle)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
HStack(spacing: 16) { HStack(spacing: 16) {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
if let _ = response.writer { if let _ = response.writer {
Text("작가") Text(I18n.Chat.Original.writerLabel)
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
} }
if let _ = response.studio { if let _ = response.studio {
Text("제작사") Text(I18n.Chat.Original.studioLabel)
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
} }
if let _ = response.originalWork { if let _ = response.originalWork {
Text("원작") Text(I18n.Chat.Original.originalLabel)
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
} }

View File

@@ -45,7 +45,7 @@ final class OriginalWorkDetailViewModel: ObservableObject {
case .failure(let error): case .failure(let error):
ERROR_LOG(error.localizedDescription) ERROR_LOG(error.localizedDescription)
self?.isLoading = false self?.isLoading = false
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} receiveValue: { [weak self] response in } receiveValue: { [weak self] response in
@@ -61,14 +61,14 @@ final class OriginalWorkDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }
} catch { } catch {
self.isLoading = false self.isLoading = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -67,7 +67,7 @@ final class OriginalWorkViewModel: ObservableObject {
} else { } else {
self?.isLoading = false self?.isLoading = false
} }
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} receiveValue: { [weak self] response in } receiveValue: { [weak self] response in
@@ -92,7 +92,7 @@ final class OriginalWorkViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
if isLoadMore { if isLoadMore {
@@ -107,7 +107,7 @@ final class OriginalWorkViewModel: ObservableObject {
} else { } else {
self.isLoading = false self.isLoading = false
} }
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -51,7 +51,11 @@ struct ChatRoomView: View {
.lineLimit(1) .lineLimit(1)
.truncationMode(.tail) .truncationMode(.tail)
Text(viewModel.characterType.rawValue) Text(
viewModel.characterType == .Clone
? I18n.Chat.Character.typeClone
: I18n.Chat.Character.typeCharacter
)
.appFont(size: 10, weight: .bold) .appFont(size: 10, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
.padding(.horizontal, 4) .padding(.horizontal, 4)
@@ -100,8 +104,8 @@ struct ChatRoomView: View {
Text( Text(
viewModel.characterType == .Character viewModel.characterType == .Character
? "보이스온 AI캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다.\n세계관 속 캐릭터로 대화를 하거나 새로운 인물로 캐릭터와 당신만의 스토리를 만들어보세요.\n※ AI캐릭터톡은 오픈베타 서비스 중이며, 캐릭터의 대화가 어색하거나 불완전할 수 있습니다." ? I18n.Chat.Room.noticeForCharacter
: "AI Clone은 크리에이터의 정보를 기반으로 대화하지만, 모든 정보를 완벽하게 반영하거나 실제 대화와 일치하지 않을 수 있습니다." : I18n.Chat.Room.noticeForClone
) )
.appFont(size: 12, weight: .regular) .appFont(size: 12, weight: .regular)
.foregroundColor(.white) .foregroundColor(.white)
@@ -186,7 +190,7 @@ struct ChatRoomView: View {
HStack(spacing: 0) { HStack(spacing: 0) {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
if viewModel.messageText.isEmpty { if viewModel.messageText.isEmpty {
Text("메시지를 입력하세요.") Text(I18n.Chat.Room.messagePlaceholder)
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor(Color(hex: "78909C")) .foregroundColor(Color(hex: "78909C"))
} }
@@ -289,7 +293,7 @@ struct ChatRoomView: View {
ActivityIndicatorView() ActivityIndicatorView()
.frame(width: 100, height: 100) .frame(width: 100, height: 100)
Text("대화 초기화 중...") Text(I18n.Chat.Room.resettingMessage)
} }
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)

View File

@@ -19,7 +19,7 @@ final class ChatRoomViewModel: ObservableObject {
@Published var chatRoomBgImageId: Int = 0 @Published var chatRoomBgImageId: Int = 0
@Published private(set) var characterId: Int64 = 0 @Published private(set) var characterId: Int64 = 0
@Published private(set) var characterProfileUrl: String = "" @Published private(set) var characterProfileUrl: String = ""
@Published private(set) var characterName: String = "Character Name" @Published private(set) var characterName: String = I18n.Chat.Room.defaultCharacterName
@Published private(set) var characterType: CharacterType = .Character @Published private(set) var characterType: CharacterType = .Character
@Published private(set) var chatRoomBgImageUrl: String? = nil @Published private(set) var chatRoomBgImageUrl: String? = nil
@Published private(set) var roomId: Int = 0 { @Published private(set) var roomId: Int = 0 {
@@ -113,7 +113,7 @@ final class ChatRoomViewModel: ObservableObject {
DEBUG_LOG("finish") DEBUG_LOG("finish")
case .failure(let error): case .failure(let error):
self.showSendingMessage = false // self.showSendingMessage = false //
self.errorMessage = error.localizedDescription self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
ERROR_LOG(error.localizedDescription) ERROR_LOG(error.localizedDescription)
} }
@@ -127,14 +127,13 @@ final class ChatRoomViewModel: ObservableObject {
self.messages.append(contentsOf: data.messages) self.messages.append(contentsOf: data.messages)
self.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch) self.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
} else { } else {
self.errorMessage = decoded.message ?? self.errorMessage = decoded.message ?? I18n.Common.commonError
"다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true self.isShowPopup = true
} }
self.showSendingMessage = false // self.showSendingMessage = false //
} catch { } catch {
self.showSendingMessage = false self.showSendingMessage = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -183,7 +182,7 @@ final class ChatRoomViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self?.errorMessage = message self?.errorMessage = message
} else { } else {
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
} }
self?.isShowPopup = true self?.isShowPopup = true
@@ -192,7 +191,7 @@ final class ChatRoomViewModel: ObservableObject {
self?.isLoading = false self?.isLoading = false
} catch { } catch {
self?.isLoading = false self?.isLoading = false
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} }
@@ -260,7 +259,7 @@ final class ChatRoomViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self?.errorMessage = message self?.errorMessage = message
} else { } else {
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
} }
self?.isShowPopup = true self?.isShowPopup = true
@@ -269,7 +268,7 @@ final class ChatRoomViewModel: ObservableObject {
self?.isLoading = false self?.isLoading = false
} catch { } catch {
self?.isLoading = false self?.isLoading = false
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} }
@@ -304,7 +303,7 @@ final class ChatRoomViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self?.errorMessage = message self?.errorMessage = message
} else { } else {
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
} }
self?.isShowPopup = true self?.isShowPopup = true
@@ -313,7 +312,7 @@ final class ChatRoomViewModel: ObservableObject {
self?.isLoading = false self?.isLoading = false
} catch { } catch {
self?.isLoading = false self?.isLoading = false
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} }
@@ -348,14 +347,14 @@ final class ChatRoomViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self?.errorMessage = message self?.errorMessage = message
} else { } else {
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
} }
self?.isShowPopup = true self?.isShowPopup = true
} }
} catch { } catch {
ERROR_LOG(String(describing: error)) ERROR_LOG(String(describing: error))
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} }
@@ -381,7 +380,7 @@ final class ChatRoomViewModel: ObservableObject {
private func resetData() { private func resetData() {
characterProfileUrl = "" characterProfileUrl = ""
characterName = "Character Name" characterName = I18n.Chat.Room.defaultCharacterName
characterType = .Character characterType = .Character
chatRoomBgImageUrl = nil chatRoomBgImageUrl = nil
roomId = 0 roomId = 0
@@ -427,7 +426,7 @@ final class ChatRoomViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self?.errorMessage = message self?.errorMessage = message
} else { } else {
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
} }
self?.isShowPopup = true self?.isShowPopup = true
@@ -436,7 +435,7 @@ final class ChatRoomViewModel: ObservableObject {
self?.isLoading = false self?.isLoading = false
} catch { } catch {
self?.isLoading = false self?.isLoading = false
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} }

View File

@@ -130,7 +130,7 @@ struct AiMessageItemView: View {
.foregroundColor(.button) .foregroundColor(.button)
} }
Text("눌러서 잠금해제") Text(I18n.Chat.Room.unlockImagePrompt)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
} }

View File

@@ -59,7 +59,7 @@ struct TypingIndicatorItemView: View {
} }
} }
} }
.accessibilityLabel(Text("입력 중")) .accessibilityLabel(Text(I18n.Chat.Room.typingAccessibilityLabel))
} }
.padding(.horizontal, 10) .padding(.horizontal, 10)
.padding(.vertical, 8) .padding(.vertical, 8)

View File

@@ -23,7 +23,7 @@ struct ChatQuotaNoticeItemView: View {
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
Text("기다리면 무료 이용이 가능합니다.") Text(I18n.Chat.Room.quotaWaitForFreeNotice)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
} }
@@ -39,7 +39,7 @@ struct ChatQuotaNoticeItemView: View {
.appFont(size: 24, weight: .bold) .appFont(size: 24, weight: .bold)
.foregroundColor(Color(hex: "263238")) .foregroundColor(Color(hex: "263238"))
Text("(채팅 12개) 바로 대화 시작") Text(I18n.Chat.Room.quotaPurchaseAction(chatCount: 12))
.appFont(size: 24, weight: .bold) .appFont(size: 24, weight: .bold)
.foregroundColor(Color(hex: "263238")) .foregroundColor(Color(hex: "263238"))
.padding(.leading, 4) .padding(.leading, 4)

View File

@@ -26,7 +26,7 @@ struct ChatBgSelectionView: View {
var body: some View { var body: some View {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: String(localized: "배경 이미지 선택")) { DetailNavigationBar(title: I18n.Chat.Room.backgroundSelectionTitle) {
isShowing = false isShowing = false
} }
// //
@@ -79,7 +79,7 @@ struct ChatBgSelectionView: View {
} }
if selectedBgImageId == item.id { if selectedBgImageId == item.id {
Text("현재 배경") Text(I18n.Chat.Room.currentBackground)
.appFont(size: 12, weight: .regular) .appFont(size: 12, weight: .regular)
.foregroundColor(.white) .foregroundColor(.white)
.padding(.horizontal, 6) .padding(.horizontal, 6)

View File

@@ -75,14 +75,14 @@ final class ChatBgSelectionViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self?.errorMessage = message self?.errorMessage = message
} else { } else {
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
} }
self?.isShowPopup = true self?.isShowPopup = true
} }
} catch { } catch {
ERROR_LOG(String(describing: error)) ERROR_LOG(String(describing: error))
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self?.errorMessage = I18n.Common.commonError
self?.isShowPopup = true self?.isShowPopup = true
} }
} }

View File

@@ -17,7 +17,7 @@ struct ChatSettingsView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: String(localized: "대화 설정")) { DetailNavigationBar(title: I18n.Chat.Room.settingsTitle) {
isShowing = false isShowing = false
} }
@@ -25,7 +25,7 @@ struct ChatSettingsView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
VStack(spacing: 0) { VStack(spacing: 0) {
Toggle(isOn: $isHideBg) { Toggle(isOn: $isHideBg) {
Text("배경 이미지 끄기") Text(I18n.Chat.Room.hideBackgroundImage)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
} }
@@ -42,7 +42,7 @@ struct ChatSettingsView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack { HStack {
Text("배경 이미지 변경") Text(I18n.Chat.Room.changeBackgroundImage)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
.padding(.horizontal, 24) .padding(.horizontal, 24)
@@ -61,16 +61,16 @@ struct ChatSettingsView: View {
HStack(spacing: 0) { HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
Text("대화 초기화") Text(I18n.Chat.Room.resetConversationTitle)
.appFont(size: 18, weight: .bold) .appFont(size: 18, weight: .bold)
.foregroundColor(Color(hex: "B0BEC5")) .foregroundColor(Color(hex: "B0BEC5"))
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
Text("⚠️ ") Text(I18n.Chat.Room.resetWarningPrefix)
.appFont(size: 16, weight: .regular) .appFont(size: 16, weight: .regular)
.foregroundColor(.white.opacity(0.7)) .foregroundColor(.white.opacity(0.7))
Text("지금까지의 대화가 모두 초기화 되고, 이용자가 새로운 캐릭터가 되어 새롭게 대화를 시작합니다.") Text(I18n.Chat.Room.resetWarningDescription)
.appFont(size: 16, weight: .regular) .appFont(size: 16, weight: .regular)
.foregroundColor(.white.opacity(0.7)) .foregroundColor(.white.opacity(0.7))
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)

View File

@@ -14,7 +14,7 @@ struct TalkView: View {
var body: some View { var body: some View {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
if viewModel.talkRooms.isEmpty { if viewModel.talkRooms.isEmpty {
Text("대화 중인 톡이 없습니다") Text(I18n.Chat.Talk.emptyMessage)
.appFont(size: 20, weight: .regular) .appFont(size: 20, weight: .regular)
.foregroundColor(.white) .foregroundColor(.white)
} else { } else {

View File

@@ -61,7 +61,7 @@ final class TalkViewModel: ObservableObject {
if case let .failure(error) = completion { if case let .failure(error) = completion {
ERROR_LOG(error.localizedDescription) ERROR_LOG(error.localizedDescription)
DispatchQueue.main.async { DispatchQueue.main.async {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} else { } else {
@@ -90,16 +90,15 @@ final class TalkViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
.store(in: &subscription) .store(in: &subscription)
} }
} }

View File

@@ -22,7 +22,7 @@ struct ContentAllByThemeView: View {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Spacer() Spacer()
Text("최신순") Text(I18n.Content.Sort.newest)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -34,7 +34,7 @@ struct ContentAllByThemeView: View {
} }
} }
Text("높은 가격순") Text(I18n.Content.Sort.priceHigh)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -46,7 +46,7 @@ struct ContentAllByThemeView: View {
} }
} }
Text("낮은 가격순") Text(I18n.Content.Sort.priceLow)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -64,7 +64,7 @@ struct ContentAllByThemeView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("전체") Text(I18n.Content.Count.totalPrefix)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
@@ -73,7 +73,7 @@ struct ContentAllByThemeView: View {
.foregroundColor(Color(hex: "ff5c49")) .foregroundColor(Color(hex: "ff5c49"))
.padding(.leading, 8) .padding(.leading, 8)
Text("") Text(I18n.Content.Count.countUnit)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
.padding(.leading, 2) .padding(.leading, 2)

View File

@@ -78,13 +78,13 @@ final class ContentAllByThemeViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }

View File

@@ -19,7 +19,11 @@ struct ContentAllView: View {
Group { Group {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: isFree ? String(localized: "무료 콘텐츠 전체") : isPointAvailableOnly ? String(localized: "포인트 대여 전체") : String(localized: "콘텐츠 전체")) DetailNavigationBar(
title: isFree ?
I18n.Content.All.freeTitle :
isPointAvailableOnly ? I18n.Content.All.pointRentalTitle : I18n.Content.All.title
)
if !viewModel.themeList.isEmpty { if !viewModel.themeList.isEmpty {
ContentMainContentThemeView( ContentMainContentThemeView(
@@ -32,7 +36,7 @@ struct ContentAllView: View {
HStack(spacing: 12) { HStack(spacing: 12) {
Spacer() Spacer()
Text("최신순") Text(I18n.Content.Sort.newest)
.appFont(size: 16, weight: .medium) .appFont(size: 16, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -44,7 +48,7 @@ struct ContentAllView: View {
} }
} }
Text("인기순") Text(I18n.Content.Sort.popularity)
.appFont(size: 16, weight: .medium) .appFont(size: 16, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")

View File

@@ -41,7 +41,7 @@ struct ContentNewAllItemView: View {
.appFont(size: 8.5, weight: .medium) .appFont(size: 8.5, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
} else { } else {
Text("무료") Text(I18n.CreateContent.free)
.appFont(size: 8.5, weight: .medium) .appFont(size: 8.5, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
} }

View File

@@ -18,9 +18,9 @@ struct ContentNewAllView: View {
Group { Group {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
VStack(alignment: .leading, spacing: 13.3) { VStack(alignment: .leading, spacing: 13.3) {
DetailNavigationBar(title: isFree ? "최신 무료 콘텐츠" : "최신 콘텐츠") DetailNavigationBar(title: isFree ? I18n.Content.New.freeTitle : I18n.Content.New.title)
Text("※ 최근 2주간 등록된 새로운 콘텐츠 입니다.") Text(I18n.Content.New.recentTwoWeeksNotice)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(.graybb) .foregroundColor(.graybb)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
@@ -37,7 +37,7 @@ struct ContentNewAllView: View {
) )
HStack(spacing: 0) { HStack(spacing: 0) {
Text("전체") Text(I18n.Content.Count.totalPrefix)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
@@ -46,7 +46,7 @@ struct ContentNewAllView: View {
.foregroundColor(Color(hex: "ff5c49")) .foregroundColor(Color(hex: "ff5c49"))
.padding(.leading, 8) .padding(.leading, 8)
Text("") Text(I18n.Content.Count.countUnit)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
.padding(.leading, 2) .padding(.leading, 2)

View File

@@ -17,14 +17,14 @@ struct ContentRankingAllView: View {
Group { Group {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: "인기 콘텐츠") DetailNavigationBar(title: I18n.Content.Ranking.title)
VStack(spacing: 8) { VStack(spacing: 8) {
Text("\(viewModel.dateString)") Text("\(viewModel.dateString)")
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.") Text(I18n.Content.Ranking.weeklyUpdateNotice)
.appFont(size: 13.3, weight: .light) .appFont(size: 13.3, weight: .light)
.foregroundColor(Color(hex: "bbbbbb")) .foregroundColor(Color(hex: "bbbbbb"))
} }
@@ -82,7 +82,7 @@ struct ContentRankingAllView: View {
.cornerRadius(2.6) .cornerRadius(2.6)
if item.isPointAvailable { if item.isPointAvailable {
Text("포인트") Text(I18n.Common.points)
.appFont(size: 8, weight: .medium) .appFont(size: 8, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
.padding(2.6) .padding(2.6)
@@ -116,7 +116,7 @@ struct ContentRankingAllView: View {
.foregroundColor(Color(hex: "909090")) .foregroundColor(Color(hex: "909090"))
} }
} else { } else {
Text("무료") Text(I18n.CreateContent.free)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color(hex: "ffffff")) .foregroundColor(Color(hex: "ffffff"))
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)

View File

@@ -71,13 +71,13 @@ final class ContentRankingAllViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }
@@ -109,13 +109,13 @@ final class ContentRankingAllViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -49,11 +49,11 @@ struct ContentListCategoryView: View {
ContentListCategoryView( ContentListCategoryView(
categoryList: [ categoryList: [
GetCategoryListResponse(categoryId: 0, category: "전체"), GetCategoryListResponse(categoryId: 0, category: I18n.Category.all),
GetCategoryListResponse(categoryId: 1, category: "test"), GetCategoryListResponse(categoryId: 1, category: "test"),
GetCategoryListResponse(categoryId: 0, category: "test2") GetCategoryListResponse(categoryId: 0, category: "test2")
], ],
selectCategory: { _ in }, selectCategory: { _ in },
selectedCategory: .constant("전체") selectedCategory: .constant(I18n.Category.all)
) )
} }

View File

@@ -9,7 +9,7 @@ import Foundation
import Moya import Moya
enum ContentApi { enum ContentApi {
case getAudioContentList(userId: Int, categoryId: Int, isAdultContentVisible: Bool, page: Int, size: Int, sort: ContentListViewModel.Sort) case getAudioContentList(userId: Int, categoryId: Int, page: Int, size: Int, sort: ContentListViewModel.Sort)
case getAudioContentDetail(audioContentId: Int) case getAudioContentDetail(audioContentId: Int)
case likeContent(request: PutAudioContentLikeRequest) case likeContent(request: PutAudioContentLikeRequest)
case registerComment(request: RegisterAudioContentCommentRequest) case registerComment(request: RegisterAudioContentCommentRequest)
@@ -25,51 +25,51 @@ enum ContentApi {
case getNewContentUploadCreatorList case getNewContentUploadCreatorList
case getMainBannerList case getMainBannerList
case getMainOrderList case getMainOrderList
case getNewContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType) case getNewContentOfTheme(theme: String)
case getCurationList(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) case getCurationList(page: Int, size: Int)
case donation(request: AudioContentDonationRequest) case donation(request: AudioContentDonationRequest)
case modifyComment(request: ModifyCommentRequest) case modifyComment(request: ModifyCommentRequest)
case getNewContentThemeList(isAdultContentVisible: Bool, contentType: ContentType) case getNewContentThemeList
case getNewContentAllOfTheme(isFree: Bool, theme: String, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) case getNewContentAllOfTheme(isFree: Bool, theme: String, page: Int, size: Int)
case getAudioContentListByCurationId(curationId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sort: ContentCurationViewModel.Sort) case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort)
case getContentRanking(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sortType: String) case getContentRanking(page: Int, size: Int, sortType: String)
case getContentRankingSortType case getContentRankingSortType
case pinContent(contentId: Int) case pinContent(contentId: Int)
case unpinContent(contentId: Int) case unpinContent(contentId: Int)
case getAudioContentByTheme(themeId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort) case getAudioContentByTheme(themeId: Int, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort)
case generateUrl(contentId: Int) case generateUrl(contentId: Int)
case getContentMainHome(isAdultContentVisible: Bool, contentType: ContentType) case getContentMainHome
case getPopularContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType) case getPopularContentByCreator(creatorId: Int)
case getContentMainHomeContentRanking(isAdultContentVisible: Bool, contentType: ContentType, sortType: String) case getContentMainHomeContentRanking(sortType: String)
case getContentMainSeries(isAdultContentVisible: Bool, contentType: ContentType) case getContentMainSeries
case getRecommendSeriesListByGenre(genreId: Int, isAdultContentVisible: Bool, contentType: ContentType) case getRecommendSeriesListByGenre(genreId: Int)
case getRecommendSeriesByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType) case getRecommendSeriesByCreator(creatorId: Int)
case getCompletedSeries(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) case getCompletedSeries(page: Int, size: Int)
case getContentMainContent(isAdultContentVisible: Bool, contentType: ContentType) case getContentMainContent
case getContentMainNewContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType) case getContentMainNewContentOfTheme(theme: String)
case getDailyContentRanking(sortType: String, isAdultContentVisible: Bool, contentType: ContentType) case getDailyContentRanking(sortType: String)
case getRecommendContentByTag(tag: String, contentType: ContentType) case getRecommendContentByTag(tag: String)
case getContentMainContentPopularContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType) case getContentMainContentPopularContentByCreator(creatorId: Int)
case getContentMainAlarm(isAdultContentVisible: Bool, contentType: ContentType) case getContentMainAlarm
case getContentMainAlarmAll(theme: String, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) case getContentMainAlarmAll(theme: String, page: Int, size: Int)
case getContentMainAsmr(isAdultContentVisible: Bool, contentType: ContentType) case getContentMainAsmr
case getPopularAsmrContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType) case getPopularAsmrContentByCreator(creatorId: Int)
case getContentMainReplay(isAdultContentVisible: Bool, contentType: ContentType) case getContentMainReplay
case getPopularReplayContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType) case getPopularReplayContentByCreator(creatorId: Int)
case getContentMainFree(isAdultContentVisible: Bool, contentType: ContentType) case getContentMainFree
case getIntroduceCreatorList(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) case getIntroduceCreatorList(page: Int, size: Int)
case getNewFreeContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int) case getNewFreeContentOfTheme(theme: String, page: Int, size: Int)
case getPopularFreeContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType) case getPopularFreeContentByCreator(creatorId: Int)
case getAllAudioContents(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, isFree: Bool?, isPointAvailableOnly: Bool?, sortType: ContentAllViewModel.Sort = .NEWEST, theme: String? = nil) case getAllAudioContents(page: Int, size: Int, isFree: Bool?, isPointAvailableOnly: Bool?, sortType: ContentAllViewModel.Sort = .NEWEST, theme: String? = nil)
case getAudioContentActiveThemeList(isAdultContentVisible: Bool, contentType: ContentType, isFree: Bool?, isPointAvailableOnly: Bool?) case getAudioContentActiveThemeList(isFree: Bool?, isPointAvailableOnly: Bool?)
} }
extension ContentApi: TargetType { extension ContentApi: TargetType {
@@ -145,7 +145,7 @@ extension ContentApi: TargetType {
case .getNewContentAllOfTheme: case .getNewContentAllOfTheme:
return "/audio-content/main/new/all" return "/audio-content/main/new/all"
case .getAudioContentListByCurationId(let curationId, _, _, _, _, _): case .getAudioContentListByCurationId(let curationId, _, _, _):
return "/audio-content/curation/\(curationId)" return "/audio-content/curation/\(curationId)"
case .getContentRanking: case .getContentRanking:
@@ -160,7 +160,7 @@ extension ContentApi: TargetType {
case .unpinContent(let contentId): case .unpinContent(let contentId):
return "/audio-content/unpin-at-the-top/\(contentId)" return "/audio-content/unpin-at-the-top/\(contentId)"
case .getAudioContentByTheme(let themeId, _, _, _, _, _): case .getAudioContentByTheme(let themeId, _, _, _):
return "/audio-content/theme/\(themeId)/content" return "/audio-content/theme/\(themeId)/content"
case .generateUrl(let contentId): case .generateUrl(let contentId):
@@ -273,11 +273,10 @@ extension ContentApi: TargetType {
var task: Moya.Task { var task: Moya.Task {
switch self { switch self {
case .getAudioContentList(let userId, let categoryId, let isAdultContentVisible, let page, let size, let sort): case .getAudioContentList(let userId, let categoryId, let page, let size, let sort):
let parameters = [ let parameters = [
"creator-id": userId, "creator-id": userId,
"category-id": categoryId, "category-id": categoryId,
"isAdultContentVisible": isAdultContentVisible,
"page": page - 1, "page": page - 1,
"size": size, "size": size,
"sort-type": sort "sort-type": sort
@@ -339,11 +338,9 @@ extension ContentApi: TargetType {
case .deleteAudioContent: case .deleteAudioContent:
return .requestPlain return .requestPlain
case .getNewContentOfTheme(let theme, let isAdultContentVisible, let contentType): case .getNewContentOfTheme(let theme):
let parameters = [ let parameters = [
"theme": theme, "theme": theme
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
@@ -354,30 +351,21 @@ extension ContentApi: TargetType {
case .modifyComment(let request): case .modifyComment(let request):
return .requestJSONEncodable(request) return .requestJSONEncodable(request)
case .getNewContentThemeList(let isAdultContentVisible, let contentType): case .getNewContentThemeList:
let parameters = [ return .requestPlain
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) case .getNewContentAllOfTheme(let isFree, let theme, let page, let size):
case .getNewContentAllOfTheme(let isFree, let theme, let isAdultContentVisible, let contentType, let page, let size):
let parameters = [ let parameters = [
"isFree": isFree, "isFree": isFree,
"theme": theme, "theme": theme,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size "size": size
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getAudioContentListByCurationId(_, let isAdultContentVisible, let contentType, let page, let size, let sort): case .getAudioContentListByCurationId(_, let page, let size, let sort):
let parameters = [ let parameters = [
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size, "size": size,
"sort-type": sort "sort-type": sort
@@ -385,10 +373,8 @@ extension ContentApi: TargetType {
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getContentRanking(let isAdultContentVisible, let contentType, let page, let size, let sortType): case .getContentRanking(let page, let size, let sortType):
let parameters = [ let parameters = [
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size, "size": size,
"sort-type": sortType "sort-type": sortType
@@ -399,10 +385,8 @@ extension ContentApi: TargetType {
case .getContentRankingSortType: case .getContentRankingSortType:
return .requestPlain return .requestPlain
case .getCurationList(let isAdultContentVisible, let contentType, let page, let size): case .getCurationList(let page, let size):
let parameters = [ let parameters = [
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size "size": size
] as [String : Any] ] as [String : Any]
@@ -412,10 +396,8 @@ extension ContentApi: TargetType {
case .pinContent, .unpinContent: case .pinContent, .unpinContent:
return .requestPlain return .requestPlain
case .getAudioContentByTheme(_, let isAdultContentVisible, let contentType, let page, let size, let sort): case .getAudioContentByTheme(_, let page, let size, let sort):
let parameters = [ let parameters = [
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size, "size": size,
"sort-type": sort "sort-type": sort
@@ -423,141 +405,109 @@ extension ContentApi: TargetType {
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getContentMainHome(let isAdultContentVisible, let contentType), case .getContentMainHome,
.getContentMainSeries(let isAdultContentVisible, let contentType), .getContentMainSeries,
.getContentMainContent(let isAdultContentVisible, let contentType), .getContentMainContent,
.getContentMainAlarm(let isAdultContentVisible, let contentType), .getContentMainAlarm,
.getContentMainAsmr(let isAdultContentVisible, let contentType), .getContentMainAsmr,
.getContentMainReplay(let isAdultContentVisible, let contentType), .getContentMainReplay,
.getContentMainFree(let isAdultContentVisible, let contentType): .getContentMainFree:
return .requestPlain
case .getRecommendSeriesListByGenre(let genreId):
let parameters = [ let parameters = [
"isAdultContentVisible": isAdultContentVisible, "genreId": genreId
"contentType": contentType
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getRecommendSeriesListByGenre(let genreId, let isAdultContentVisible, let contentType): case .getPopularContentByCreator(let creatorId):
let parameters = [ let parameters = [
"genreId": genreId, "creatorId": creatorId
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getPopularContentByCreator(let creatorId, let isAdultContentVisible, let contentType): case .getContentMainHomeContentRanking(let sortType):
let parameters = [ let parameters = [
"creatorId": creatorId, "sort-type": sortType
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getContentMainHomeContentRanking(let isAdultContentVisible, let contentType, let sortType): case .getRecommendSeriesByCreator(let creatorId):
let parameters = [ let parameters = [
"sort-type": sortType, "creatorId": creatorId
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getRecommendSeriesByCreator(let creatorId, let isAdultContentVisible, let contentType): case .getContentMainNewContentOfTheme(let theme):
let parameters = [ let parameters = [
"creatorId": creatorId, "theme": theme
"isAdultContentVisible": isAdultContentVisible, ] as [String : Any]
"contentType": contentType return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getDailyContentRanking(let sortType):
let parameters = [
"sort-type": sortType
] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getRecommendContentByTag(let tag):
let parameters = [
"tag": tag
] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getContentMainContentPopularContentByCreator(let creatorId):
let parameters = [
"creatorId": creatorId
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getContentMainNewContentOfTheme(let theme, let isAdultContentVisible, let contentType): case .getNewFreeContentOfTheme(let theme, let page, let size):
let parameters = [ let parameters = [
"theme": theme, "theme": theme,
"isAdultContentVisible": isAdultContentVisible, "page": page - 1,
"contentType": contentType "size": size
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getDailyContentRanking(let sortType, let isAdultContentVisible, let contentType): case .getContentMainAlarmAll(let theme, let page, let size):
let parameters = [
"sort-type": sortType,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getRecommendContentByTag(let tag, let contentType):
let parameters = [
"tag": tag,
"contentType": contentType
] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getContentMainContentPopularContentByCreator(let creatorId, let isAdultContentVisible, let contentType):
let parameters = [
"creatorId": creatorId,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getNewFreeContentOfTheme(let theme, let isAdultContentVisible, let contentType, let page, let size):
let parameters = [ let parameters = [
"theme": theme, "theme": theme,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size "size": size
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getContentMainAlarmAll(let theme, let isAdultContentVisible, let contentType, let page, let size): case .getIntroduceCreatorList(let page, let size):
let parameters = [ let parameters = [
"theme": theme,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size "size": size
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getIntroduceCreatorList(let isAdultContentVisible, let contentType, let page, let size): case .getPopularAsmrContentByCreator(let creatorId),
.getPopularReplayContentByCreator(let creatorId),
.getPopularFreeContentByCreator(let creatorId):
let parameters = [ let parameters = [
"isAdultContentVisible": isAdultContentVisible, "creatorId": creatorId
"contentType": contentType,
"page": page - 1,
"size": size
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getPopularAsmrContentByCreator(let creatorId, let isAdultContentVisible, let contentType), case .getCompletedSeries(let page, let size):
.getPopularReplayContentByCreator(let creatorId, let isAdultContentVisible, let contentType),
.getPopularFreeContentByCreator(let creatorId, let isAdultContentVisible, let contentType):
let parameters = [ let parameters = [
"creatorId": creatorId,
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType
] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getCompletedSeries(let isAdultContentVisible, let contentType, let page, let size):
let parameters = [
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"page": page - 1, "page": page - 1,
"size": size "size": size
] as [String : Any] ] as [String : Any]
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getAllAudioContents(let isAdultContentVisible, let contentType, let page, let size, let isFree, let isPointAvailableOnly, let sortType, let theme): case .getAllAudioContents(let page, let size, let isFree, let isPointAvailableOnly, let sortType, let theme):
var parameters = [ var parameters = [
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
"sort-type": sortType, "sort-type": sortType,
"page": page - 1, "page": page - 1,
"size": size "size": size
@@ -577,11 +527,8 @@ extension ContentApi: TargetType {
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
case .getAudioContentActiveThemeList(let isAdultContentVisible, let contentType, let isFree, let isPointAvailableOnly): case .getAudioContentActiveThemeList(let isFree, let isPointAvailableOnly):
var parameters = [ var parameters = [String : Any]()
"isAdultContentVisible": isAdultContentVisible,
"contentType": contentType,
] as [String : Any]
if let isFree = isFree { if let isFree = isFree {
parameters["isFree"] = isFree parameters["isFree"] = isFree

View File

@@ -29,7 +29,7 @@ struct ContentListItemView: View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) { HStack(spacing: 8) {
if item.isScheduledToOpen { if item.isScheduledToOpen {
Text("오픈예정") Text(I18n.Common.openScheduled)
.appFont(size: 11, weight: .medium) .appFont(size: 11, weight: .medium)
.foregroundColor(Color(hex: "3bb9f1")) .foregroundColor(Color(hex: "3bb9f1"))
.padding(2.6) .padding(2.6)
@@ -52,7 +52,7 @@ struct ContentListItemView: View {
.cornerRadius(2.6) .cornerRadius(2.6)
if item.isPointAvailable { if item.isPointAvailable {
Text("포인트") Text(I18n.Common.points)
.appFont(size: 11, weight: .medium) .appFont(size: 11, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
.padding(2.6) .padding(2.6)
@@ -98,7 +98,7 @@ struct ContentListItemView: View {
Spacer() Spacer()
if item.isOwned { if item.isOwned {
Text("소장중") Text(I18n.Content.Status.owned)
.appFont(size: 14, weight: .medium) .appFont(size: 14, weight: .medium)
.foregroundColor(Color.gray11) .foregroundColor(Color.gray11)
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)
@@ -106,7 +106,7 @@ struct ContentListItemView: View {
.background(Color(hex: "b1ef2c")) .background(Color(hex: "b1ef2c"))
.cornerRadius(2.6) .cornerRadius(2.6)
} else if item.isRented { } else if item.isRented {
Text("대여중") Text(I18n.Content.Status.rented)
.appFont(size: 14, weight: .medium) .appFont(size: 14, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)
@@ -114,7 +114,7 @@ struct ContentListItemView: View {
.background(Color(hex: "660fd4")) .background(Color(hex: "660fd4"))
.cornerRadius(2.6) .cornerRadius(2.6)
} else if item.isSoldOut { } else if item.isSoldOut {
Text("Sold Out") Text(I18n.Content.Status.soldOut)
.appFont(size: 14, weight: .medium) .appFont(size: 14, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)
@@ -135,7 +135,7 @@ struct ContentListItemView: View {
.foregroundColor(.white) .foregroundColor(.white)
} }
} else { } else {
Text("무료") Text(I18n.CreateContent.free)
.appFont(size: 14, weight: .medium) .appFont(size: 14, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
} }

View File

@@ -25,7 +25,7 @@ struct ContentListView: View {
.resizable() .resizable()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("콘텐츠 전체보기") Text(I18n.Content.List.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
} }
@@ -46,7 +46,7 @@ struct ContentListView: View {
} }
if userId == UserDefaults.int(forKey: .userId) { if userId == UserDefaults.int(forKey: .userId) {
Text("새로운 콘텐츠 등록하기") Text(I18n.Content.List.createNewContentAction)
.appFont(size: 15, weight: .bold) .appFont(size: 15, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.padding(.vertical, 17) .padding(.vertical, 17)
@@ -61,7 +61,7 @@ struct ContentListView: View {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Spacer() Spacer()
Text("최신순") Text(I18n.Content.Sort.newest)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -73,7 +73,7 @@ struct ContentListView: View {
} }
} }
Text("높은 가격순") Text(I18n.Content.Sort.priceHigh)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -85,7 +85,7 @@ struct ContentListView: View {
} }
} }
Text("낮은 가격순") Text(I18n.Content.Sort.priceLow)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -103,7 +103,7 @@ struct ContentListView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("전체") Text(I18n.Content.Count.totalPrefix)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
@@ -112,7 +112,7 @@ struct ContentListView: View {
.foregroundColor(Color(hex: "ff5c49")) .foregroundColor(Color(hex: "ff5c49"))
.padding(.leading, 8) .padding(.leading, 8)
Text("") Text(I18n.Content.Count.countUnit)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
.padding(.leading, 2) .padding(.leading, 2)

View File

@@ -200,7 +200,7 @@ extension ContentPlayManager {
} }
private func showError() { private func showError() {
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요." self.errorMessage = I18n.Content.Playback.playFailed
self.isShowPopup = true self.isShowPopup = true
self.resetAudioData() self.resetAudioData()
} }

View File

@@ -20,7 +20,6 @@ final class ContentRepository {
.getAudioContentList( .getAudioContentList(
userId: userId, userId: userId,
categoryId: categoryId, categoryId: categoryId,
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
page: page, page: page,
size: size, size: size,
sort: sort) sort: sort)
@@ -89,19 +88,13 @@ final class ContentRepository {
func getNewContentOfTheme(theme: String) -> AnyPublisher<Response, MoyaError> { func getNewContentOfTheme(theme: String) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher( return api.requestPublisher(
.getNewContentOfTheme( .getNewContentOfTheme(theme: theme)
theme: theme,
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL
)
) )
} }
func getCurationList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> { func getCurationList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher( return api.requestPublisher(
.getCurationList( .getCurationList(
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
page: page, page: page,
size: size size: size
) )
@@ -117,12 +110,7 @@ final class ContentRepository {
} }
func getNewContentThemeList() -> AnyPublisher<Response, MoyaError> { func getNewContentThemeList() -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher( return api.requestPublisher(.getNewContentThemeList)
.getNewContentThemeList(
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL
)
)
} }
func getNewContentAllOfTheme(isFree: Bool, theme: String, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> { func getNewContentAllOfTheme(isFree: Bool, theme: String, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
@@ -130,8 +118,6 @@ final class ContentRepository {
.getNewContentAllOfTheme( .getNewContentAllOfTheme(
isFree: isFree, isFree: isFree,
theme: theme, theme: theme,
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
page: page, page: page,
size: size size: size
) )
@@ -142,8 +128,6 @@ final class ContentRepository {
return api.requestPublisher( return api.requestPublisher(
.getAudioContentListByCurationId( .getAudioContentListByCurationId(
curationId: curationId, curationId: curationId,
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
page: page, page: page,
size: size, size: size,
sort: sort sort: sort
@@ -158,8 +142,6 @@ final class ContentRepository {
func getContentRanking(page: Int, size: Int, sortType: String = "매출") -> AnyPublisher<Response, MoyaError> { func getContentRanking(page: Int, size: Int, sortType: String = "매출") -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher( return api.requestPublisher(
.getContentRanking( .getContentRanking(
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
page: page, page: page,
size: size, size: size,
sortType: sortType sortType: sortType
@@ -183,8 +165,6 @@ final class ContentRepository {
return api.requestPublisher( return api.requestPublisher(
.getAudioContentByTheme( .getAudioContentByTheme(
themeId: themeId, themeId: themeId,
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
page: page, page: page,
size: size, size: size,
sort: sort sort: sort
@@ -206,8 +186,6 @@ final class ContentRepository {
) -> AnyPublisher<Response, MoyaError> { ) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher( return api.requestPublisher(
.getAllAudioContents( .getAllAudioContents(
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
page: page, page: page,
size: size, size: size,
isFree: isFree, isFree: isFree,
@@ -221,8 +199,6 @@ final class ContentRepository {
func getAudioContentActiveThemeList(isFree: Bool, isPointAvailableOnly: Bool) -> AnyPublisher<Response, MoyaError> { func getAudioContentActiveThemeList(isFree: Bool, isPointAvailableOnly: Bool) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher( return api.requestPublisher(
.getAudioContentActiveThemeList( .getAudioContentActiveThemeList(
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
isFree: isFree, isFree: isFree,
isPointAvailableOnly: isPointAvailableOnly isPointAvailableOnly: isPointAvailableOnly
) )

View File

@@ -28,7 +28,7 @@ struct ContentCreateSelectThemeView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
Text("테마 선택") Text(I18n.CreateContent.selectTheme)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)

View File

@@ -44,13 +44,13 @@ final class ContentCreateSelectThemeViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -9,6 +9,7 @@ import SwiftUI
import Kingfisher import Kingfisher
struct ContentCreateView: View { struct ContentCreateView: View {
@Environment(\.locale) private var locale
@StateObject var keyboardHandler = KeyboardHandler() @StateObject var keyboardHandler = KeyboardHandler()
@StateObject private var viewModel = ContentCreateViewModel() @StateObject private var viewModel = ContentCreateViewModel()
@@ -24,15 +25,23 @@ struct ContentCreateView: View {
@State private var isShowSelectTimeView = false @State private var isShowSelectTimeView = false
var body: some View { var body: some View {
let normalizedCountryCode = UserDefaults
.string(forKey: .countryCode)
.trimmingCharacters(in: .whitespacesAndNewlines)
.uppercased()
let isKoreanCountry = normalizedCountryCode.isEmpty || normalizedCountryCode == "KR"
let isAdultContentVisible = UserDefaults.isAdultContentVisible()
let shouldShowAdultSetting = isAdultContentVisible && (!isKoreanCountry || UserDefaults.bool(forKey: .auth))
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
GeometryReader { proxy in GeometryReader { proxy in
ZStack { ZStack {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: String(localized: "콘텐츠 등록")) DetailNavigationBar(title: I18n.CreateContent.registerTitle)
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) { VStack(spacing: 0) {
Text("썸네일") Text(I18n.CreateContent.thumbnail)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -65,7 +74,7 @@ struct ContentCreateView: View {
.frame(alignment: .bottomTrailing) .frame(alignment: .bottomTrailing)
.onTapGesture { isShowPhotoPicker = true } .onTapGesture { isShowPhotoPicker = true }
Text("등록") Text(I18n.CreateContent.registerSectionTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -95,12 +104,12 @@ struct ContentCreateView: View {
.padding(.top, 26.7) .padding(.top, 26.7)
VStack(spacing: 0) { VStack(spacing: 0) {
Text("제목") Text(I18n.CreateContent.titleLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
TextField("제목을 입력하세요", text: $viewModel.title) TextField(I18n.CreateContent.titlePlaceholder, text: $viewModel.title)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
@@ -113,16 +122,16 @@ struct ContentCreateView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("내용") Text(I18n.CreateContent.contentLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
Spacer() Spacer()
Text("\(viewModel.detail.count)") Text(I18n.CreateContent.characterCount(viewModel.detail.count))
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.mainRed) .foregroundColor(Color.mainRed)
Text(" / 최대 500자") Text(I18n.CreateContent.max500CharactersSuffix)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
} }
@@ -138,7 +147,7 @@ struct ContentCreateView: View {
.cornerRadius(6.7) .cornerRadius(6.7)
.padding(.top, 13.3) .padding(.top, 13.3)
Text("테마") Text(I18n.CreateContent.themeLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -180,13 +189,13 @@ struct ContentCreateView: View {
hideKeyboard() hideKeyboard()
} }
Text("태그") Text(I18n.CreateContent.tagLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 26.7) .padding(.top, 26.7)
TextField("예: #연애 #커버곡", text: $viewModel.hashtags) TextField(I18n.CreateContent.tagPlaceholderExample, text: $viewModel.hashtags)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
@@ -207,7 +216,7 @@ struct ContentCreateView: View {
.padding(.top, 26.7) .padding(.top, 26.7)
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("가격 설정") Text(I18n.CreateContent.priceSettingsTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -228,7 +237,7 @@ struct ContentCreateView: View {
if !viewModel.isFree { if !viewModel.isFree {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("소장 설정") Text(I18n.CreateContent.ownershipSettingsTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -256,13 +265,13 @@ struct ContentCreateView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
VStack(spacing: 0) { VStack(spacing: 0) {
Text(viewModel.purchaseOption == .RENT_ONLY ? "대여 가격" : "소장 가격") Text(viewModel.purchaseOption == .RENT_ONLY ? I18n.CreateContent.rentPriceLabel : I18n.CreateContent.purchasePriceLabel)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 0) { HStack(spacing: 0) {
TextField("가격을 입력하세요(5캔 이상)", text: $viewModel.priceString) TextField(I18n.CreateContent.priceInputPlaceholder, text: $viewModel.priceString)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
@@ -273,7 +282,7 @@ struct ContentCreateView: View {
Spacer() Spacer()
Text("") Text(I18n.CreateContent.canUnit)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
} }
@@ -288,18 +297,18 @@ struct ContentCreateView: View {
.frame(height: 1) .frame(height: 1)
.padding(.top, 11) .padding(.top, 11)
Text("※ 이용기간 대여 (5일) | 소장 (서비스종료시까지)") Text(I18n.CreateContent.rentalPeriodNotice)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 13.3) .padding(.top, 13.3)
Text("※ 대여가격은 소장가격의 70%로 자동 반영") Text(I18n.CreateContent.rentalPriceAutoNotice)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
Text("※ 콘텐츠의 최소금액은 5캔 입니다") Text(I18n.CreateContent.minimumPriceNotice)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -308,7 +317,7 @@ struct ContentCreateView: View {
if viewModel.price > 0 && viewModel.purchaseOption != .RENT_ONLY { if viewModel.price > 0 && viewModel.purchaseOption != .RENT_ONLY {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("한정판 설정") Text(I18n.CreateContent.limitedEditionSettingsTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -328,7 +337,7 @@ struct ContentCreateView: View {
} }
if viewModel.isLimited { if viewModel.isLimited {
TextField("한정판 개수를 입력하세요", text: $viewModel.limitedString) TextField(I18n.CreateContent.limitedCountPlaceholder, text: $viewModel.limitedString)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
@@ -345,7 +354,7 @@ struct ContentCreateView: View {
} }
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("포인트 사용") Text(I18n.CreateContent.pointUsageTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -367,7 +376,7 @@ struct ContentCreateView: View {
.padding(.top, 26.7) .padding(.top, 26.7)
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("미리듣기") Text(I18n.CreateContent.previewTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -390,19 +399,19 @@ struct ContentCreateView: View {
if viewModel.isGeneratePreview { if viewModel.isGeneratePreview {
VStack(spacing: 10) { VStack(spacing: 10) {
Text("미리듣기 시간 설정") Text(I18n.CreateContent.previewTimeSettingsTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
Text("미리듣기 시간을 직접 설정하지 않으면 콘텐츠 앞부분 15초가 자동으로 설정됩니다. 미리듣기의 시간제한은 없습니다.") Text(I18n.CreateContent.previewTimeGuide)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
VStack(spacing: 5.3) { VStack(spacing: 5.3) {
Text("시작 시간") Text(I18n.CreateContent.previewStartTimeLabel)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -421,7 +430,7 @@ struct ContentCreateView: View {
} }
VStack(spacing: 5.3) { VStack(spacing: 5.3) {
Text("종료 시간") Text(I18n.CreateContent.previewEndTimeLabel)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -448,8 +457,9 @@ struct ContentCreateView: View {
.padding(.top, 26.7) .padding(.top, 26.7)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
if shouldShowAdultSetting {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("연령 제한") Text(I18n.CreateContent.ageRestrictionTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -468,7 +478,7 @@ struct ContentCreateView: View {
} }
} }
Text("성인콘텐츠를 전체관람가로 등록할 시 발생하는 법적 책임은 회사와 상관없이 콘텐츠를 등록한 본인에게 있습니다.\n콘텐츠 내용은 물론 제목도 19금 여부를 체크해 주시기 바랍니다.") Text(I18n.CreateContent.adultLegalNotice)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.mainRed3) .foregroundColor(Color.mainRed3)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -476,9 +486,10 @@ struct ContentCreateView: View {
} }
.padding(.top, 26.7) .padding(.top, 26.7)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
}
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("댓글 가능 여부") Text(I18n.CreateContent.commentAvailabilityTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -523,7 +534,7 @@ struct ContentCreateView: View {
if viewModel.isActiveReservation { if viewModel.isActiveReservation {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
VStack(alignment: .leading, spacing: 6.7) { VStack(alignment: .leading, spacing: 6.7) {
Text("예약 날짜") Text(I18n.CreateContent.reservationDateLabel)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -531,7 +542,7 @@ struct ContentCreateView: View {
hideKeyboard() hideKeyboard()
self.isShowSelectDateView = true self.isShowSelectDateView = true
}) { }) {
Text(viewModel.releaseDateString) Text(viewModel.releaseDate.convertDateFormat(dateFormat: "yyyy.MM.dd", locale: locale))
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -544,7 +555,7 @@ struct ContentCreateView: View {
} }
VStack(alignment: .leading, spacing: 6.7) { VStack(alignment: .leading, spacing: 6.7) {
Text("예약 시간") Text(I18n.CreateContent.reservationTimeLabel)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -552,7 +563,7 @@ struct ContentCreateView: View {
hideKeyboard() hideKeyboard()
self.isShowSelectTimeView = true self.isShowSelectTimeView = true
}) { }) {
Text(viewModel.releaseTimeString) Text(viewModel.releaseTime.convertDateFormat(dateFormat: "a hh:mm", locale: locale))
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -576,7 +587,7 @@ struct ContentCreateView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
Text("등록") Text(I18n.CreateContent.registerButton)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.frame(height: 50) .frame(height: 50)

View File

@@ -154,7 +154,7 @@ final class ContentCreateViewModel: ObservableObject {
mimeType: "image/*") mimeType: "image/*")
) )
} else { } else {
errorMessage = "커버이미지를 업로드 하지 못했습니다.\n다시 선택해 주세요" errorMessage = I18n.CreateContent.coverImageUploadFailed
isShowPopup = true isShowPopup = true
isLoading = false isLoading = false
return return
@@ -176,19 +176,19 @@ final class ContentCreateViewModel: ObservableObject {
) )
) )
} else { } else {
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요" errorMessage = I18n.CreateContent.contentFileUploadFailed
isShowPopup = true isShowPopup = true
isLoading = false isLoading = false
return return
} }
} else { } else {
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요" errorMessage = I18n.CreateContent.contentFileUploadFailed
isShowPopup = true isShowPopup = true
isLoading = false isLoading = false
return return
} }
} else { } else {
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요" errorMessage = I18n.CreateContent.contentFileUploadFailed
isShowPopup = true isShowPopup = true
isLoading = false isLoading = false
return return
@@ -219,19 +219,19 @@ final class ContentCreateViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
.store(in: &subscription) .store(in: &subscription)
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }
@@ -240,37 +240,37 @@ final class ContentCreateViewModel: ObservableObject {
private func validateData() -> Bool { private func validateData() -> Bool {
if title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { if title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
errorMessage = "제목을 입력해 주세요." errorMessage = I18n.CreateContent.titleRequired
isShowPopup = true isShowPopup = true
return false return false
} }
if detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5 { if detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5 {
errorMessage = "내용을 5자 이상 입력해 주세요." errorMessage = I18n.CreateContent.detailMinLengthRequired
isShowPopup = true isShowPopup = true
return false return false
} }
if theme == nil { if theme == nil {
errorMessage = "테마를 선택해 주세요." errorMessage = I18n.CreateContent.themeRequired
isShowPopup = true isShowPopup = true
return false return false
} }
if coverImage == nil { if coverImage == nil {
errorMessage = "커버이미지를 선택해 주세요." errorMessage = I18n.CreateContent.coverImageRequired
isShowPopup = true isShowPopup = true
return false return false
} }
if selectedFileUrl == nil { if selectedFileUrl == nil {
errorMessage = "오디오 콘텐츠를 선택해 주세요." errorMessage = I18n.CreateContent.audioContentRequired
isShowPopup = true isShowPopup = true
return false return false
} }
if !isFree && price < 5 { if !isFree && price < 5 {
errorMessage = "콘텐츠의 최소금액은 5캔 입니다." errorMessage = I18n.CreateContent.minimumPriceRequired
isShowPopup = true isShowPopup = true
return false return false
} }
@@ -278,14 +278,14 @@ final class ContentCreateViewModel: ObservableObject {
if previewStartTime.count > 0 && previewEndTime.count > 0 { if previewStartTime.count > 0 && previewEndTime.count > 0 {
let startTimeArray = previewStartTime.split(separator: ":") let startTimeArray = previewStartTime.split(separator: ":")
if startTimeArray.count != 3 { if startTimeArray.count != 3 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다" errorMessage = I18n.CreateContent.previewTimeFormatInvalid
isShowPopup = true isShowPopup = true
return false return false
} }
for time in startTimeArray { for time in startTimeArray {
if time.count != 2 { if time.count != 2 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다" errorMessage = I18n.CreateContent.previewTimeFormatInvalid
isShowPopup = true isShowPopup = true
return false return false
} }
@@ -293,14 +293,14 @@ final class ContentCreateViewModel: ObservableObject {
let endTimeArray = previewStartTime.split(separator: ":") let endTimeArray = previewStartTime.split(separator: ":")
if endTimeArray.count != 3 { if endTimeArray.count != 3 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다" errorMessage = I18n.CreateContent.previewTimeFormatInvalid
isShowPopup = true isShowPopup = true
return false return false
} }
for time in endTimeArray { for time in endTimeArray {
if time.count != 2 { if time.count != 2 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다" errorMessage = I18n.CreateContent.previewTimeFormatInvalid
isShowPopup = true isShowPopup = true
return false return false
} }
@@ -308,13 +308,13 @@ final class ContentCreateViewModel: ObservableObject {
let timeDifference = timeDifference(startTime: previewStartTime, endTime: previewEndTime) let timeDifference = timeDifference(startTime: previewStartTime, endTime: previewEndTime)
if timeDifference < 15.0 { if timeDifference < 15.0 {
errorMessage = "미리 듣기의 최소 시간은 15초 입니다" errorMessage = I18n.CreateContent.previewMinimumDurationError
isShowPopup = true isShowPopup = true
return false return false
} }
} else { } else {
if previewStartTime.count > 0 || previewEndTime.count > 0 { if previewStartTime.count > 0 || previewEndTime.count > 0 {
errorMessage = "미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다." errorMessage = I18n.CreateContent.previewStartEndBothOrNone
isShowPopup = true isShowPopup = true
return false return false
} }

View File

@@ -8,6 +8,7 @@
import SwiftUI import SwiftUI
struct QuarterTimePickerView: View { struct QuarterTimePickerView: View {
@Environment(\.locale) private var locale
@Binding var selectedTime: Date @Binding var selectedTime: Date
@Binding var isShowing: Bool @Binding var isShowing: Bool
@@ -28,7 +29,7 @@ struct QuarterTimePickerView: View {
) )
.datePickerStyle(WheelDatePickerStyle()) .datePickerStyle(WheelDatePickerStyle())
.labelsHidden() .labelsHidden()
.environment(\.locale, Locale.init(identifier: "ko")) .environment(\.locale, locale)
.frame(width: proxy.size.width - 53.4) .frame(width: proxy.size.width - 53.4)
.onAppear { .onAppear {
UIDatePicker.appearance().minuteInterval = 15 UIDatePicker.appearance().minuteInterval = 15
@@ -38,7 +39,7 @@ struct QuarterTimePickerView: View {
} }
Button(action: { self.isShowing = false }) { Button(action: { self.isShowing = false }) {
Text("확인") Text(I18n.Common.confirm)
.appFont(size: 16) .appFont(size: 16)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.padding(.vertical, 10) .padding(.vertical, 10)

View File

@@ -8,6 +8,7 @@
import SwiftUI import SwiftUI
struct SelectDatePicker: View { struct SelectDatePicker: View {
@Environment(\.locale) private var locale
@Binding var selectedDate: Date @Binding var selectedDate: Date
@Binding var isShowing: Bool @Binding var isShowing: Bool
@@ -24,11 +25,11 @@ struct SelectDatePicker: View {
DatePicker("", selection: $selectedDate, in: Date()..., displayedComponents: .date) DatePicker("", selection: $selectedDate, in: Date()..., displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle()) .datePickerStyle(WheelDatePickerStyle())
.labelsHidden() .labelsHidden()
.environment(\.locale, Locale.init(identifier: "ko")) .environment(\.locale, locale)
.frame(width: proxy.size.width) .frame(width: proxy.size.width)
Button(action: { self.isShowing = false }) { Button(action: { self.isShowing = false }) {
Text("확인") Text(I18n.Common.confirm)
.appFont(size: 16) .appFont(size: 16)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.padding(.vertical, 10) .padding(.vertical, 10)

View File

@@ -30,7 +30,7 @@ struct ContentCurationView: View {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Spacer() Spacer()
Text("최신순") Text(I18n.Content.Sort.newest)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -42,7 +42,7 @@ struct ContentCurationView: View {
} }
} }
Text("높은 가격순") Text(I18n.Content.Sort.priceHigh)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -54,7 +54,7 @@ struct ContentCurationView: View {
} }
} }
Text("낮은 가격순") Text(I18n.Content.Sort.priceLow)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color(hex: "e2e2e2") Color(hex: "e2e2e2")
@@ -72,7 +72,7 @@ struct ContentCurationView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("전체") Text(I18n.Content.Count.totalPrefix)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
@@ -81,7 +81,7 @@ struct ContentCurationView: View {
.foregroundColor(Color(hex: "ff5c49")) .foregroundColor(Color(hex: "ff5c49"))
.padding(.leading, 8) .padding(.leading, 8)
Text("") Text(I18n.Content.Count.countUnit)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "e2e2e2")) .foregroundColor(Color(hex: "e2e2e2"))
.padding(.leading, 2) .padding(.leading, 2)

View File

@@ -77,13 +77,13 @@ final class ContentCurationViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }

View File

@@ -19,11 +19,11 @@ struct AudioContentDeleteDialogView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
Text("콘텐츠 삭제") Text(I18n.ContentDetail.DeleteDialog.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
Text("[\(title)]을 삭제하시겠습니까?") Text(I18n.ContentDetail.DeleteDialog.confirmQuestion(title))
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.padding(.top, 21.3) .padding(.top, 21.3)
@@ -36,7 +36,7 @@ struct AudioContentDeleteDialogView: View {
isAgree.toggle() isAgree.toggle()
} }
Text("삭제된 콘텐츠는 되돌릴 수 없음을 알고 있습니다.") Text(I18n.ContentDetail.DeleteDialog.irreversibleAcknowledgement)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.onTapGesture { .onTapGesture {
@@ -48,7 +48,7 @@ struct AudioContentDeleteDialogView: View {
.cornerRadius(6.7) .cornerRadius(6.7)
.padding(.top, 13.3) .padding(.top, 13.3)
Text("콘텐츠를 삭제하더라도 이미 구매한\n사용자는 콘텐츠를 이용할 수 있습니다.") Text(I18n.ContentDetail.DeleteDialog.purchasedUserNotice)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color(hex: "dd4500")) .foregroundColor(Color(hex: "dd4500"))
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -56,7 +56,7 @@ struct AudioContentDeleteDialogView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 12) { HStack(spacing: 12) {
Text("취소") Text(I18n.Common.cancel)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "9970ff")) .foregroundColor(Color(hex: "9970ff"))
.padding(.horizontal, 55) .padding(.horizontal, 55)
@@ -70,7 +70,7 @@ struct AudioContentDeleteDialogView: View {
isShowing = false isShowing = false
} }
Text("확인") Text(I18n.Common.confirm)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.padding(.horizontal, 55) .padding(.horizontal, 55)

View File

@@ -13,15 +13,7 @@ struct AudioContentReportDialogView: View {
let confirmAction: (String) -> Void let confirmAction: (String) -> Void
@State private var selectedIndex: Int? = nil @State private var selectedIndex: Int? = nil
let reasons = [ let reasons = I18n.ContentDetail.ReportDialog.reasons
"괴롭힘 및 사이버 폭력",
"개인정보 침해",
"명의도용",
"폭력적 위협",
"아동학대",
"보호대상 집단에 대한 증오심 표현",
"스팸 및 사기"
]
var body: some View { var body: some View {
ZStack { ZStack {
@@ -31,7 +23,7 @@ struct AudioContentReportDialogView: View {
.onTapGesture { isShowing = false } .onTapGesture { isShowing = false }
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("콘텐츠 신고") Text(I18n.ContentDetail.ReportDialog.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
@@ -59,13 +51,13 @@ struct AudioContentReportDialogView: View {
.cornerRadius(6.7) .cornerRadius(6.7)
.padding(.vertical, 21.3) .padding(.vertical, 21.3)
Text("신고한 콘텐츠를 관리자가 확인 후, 서비스정책을\n위반한 경우 삭제 조치할 예정입니다.") Text(I18n.ContentDetail.ReportDialog.notice)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "dd4500")) .foregroundColor(Color(hex: "dd4500"))
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
HStack(spacing: 12) { HStack(spacing: 12) {
Text("취소") Text(I18n.Common.cancel)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "9970ff")) .foregroundColor(Color(hex: "9970ff"))
.padding(.vertical, 16) .padding(.vertical, 16)
@@ -79,7 +71,7 @@ struct AudioContentReportDialogView: View {
isShowing = false isShowing = false
} }
Text("신고") Text(I18n.ContentDetail.ReportDialog.reportAction)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.padding(.vertical, 16) .padding(.vertical, 16)

View File

@@ -51,7 +51,7 @@ struct AudioContentCommentItemView: View {
.foregroundColor(Color.gray90) .foregroundColor(Color.gray90)
if commentItem.isSecret { if commentItem.isSecret {
Text("비밀댓글") Text(I18n.ContentDetail.Comment.secretComment)
.appFont(size: 11, weight: .medium) .appFont(size: 11, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.padding(.horizontal, 4) .padding(.horizontal, 4)
@@ -104,7 +104,7 @@ struct AudioContentCommentItemView: View {
HStack(spacing: 0) { HStack(spacing: 0) {
if isModeModify { if isModeModify {
HStack(spacing: 0) { HStack(spacing: 0) {
TextField("댓글을 입력해 보세요.", text: $comment) TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $comment)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
@@ -151,7 +151,11 @@ struct AudioContentCommentItemView: View {
parentComment: commentItem parentComment: commentItem
) )
) { ) {
Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)" : "답글 쓰기") Text(
commentItem.replyCount > 0 ?
I18n.ContentDetail.Comment.replyCount(commentItem.replyCount) :
I18n.ContentDetail.Comment.writeReply
)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.button) .foregroundColor(Color.button)
} }
@@ -172,7 +176,7 @@ struct AudioContentCommentItemView: View {
if isShowPopupMenu { if isShowPopupMenu {
VStack(spacing: 10) { VStack(spacing: 10) {
if commentItem.writerId == UserDefaults.int(forKey: .userId) { if commentItem.writerId == UserDefaults.int(forKey: .userId) {
Text("수정") Text(I18n.ContentDetail.Comment.edit)
.appFont(size: 14, weight: .medium) .appFont(size: 14, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
.onTapGesture { .onTapGesture {
@@ -184,7 +188,7 @@ struct AudioContentCommentItemView: View {
if contentCreatorId == UserDefaults.int(forKey: .userId) || if contentCreatorId == UserDefaults.int(forKey: .userId) ||
commentItem.writerId == UserDefaults.int(forKey: .userId) commentItem.writerId == UserDefaults.int(forKey: .userId)
{ {
Text("삭제") Text(I18n.Common.delete)
.appFont(size: 14, weight: .medium) .appFont(size: 14, weight: .medium)
.foregroundColor(Color.gray77) .foregroundColor(Color.gray77)
.onTapGesture { .onTapGesture {

View File

@@ -29,7 +29,7 @@ struct AudioContentCommentListView: View {
ZStack { ZStack {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("댓글") Text(I18n.ContentDetail.Comment.title)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
.padding(.leading, 13.3) .padding(.leading, 13.3)
@@ -65,7 +65,7 @@ struct AudioContentCommentListView: View {
viewModel.isSecret.toggle() viewModel.isSecret.toggle()
} }
Text("비밀댓글") Text(I18n.ContentDetail.Comment.secretComment)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(viewModel.isSecret ? Color.button : Color.grayee) .foregroundColor(viewModel.isSecret ? Color.button : Color.grayee)
.onTapGesture { .onTapGesture {
@@ -85,7 +85,7 @@ struct AudioContentCommentListView: View {
.clipShape(Circle()) .clipShape(Circle())
HStack(spacing: 0) { HStack(spacing: 0) {
TextField("댓글을 입력해 보세요.", text: $viewModel.comment) TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $viewModel.comment)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)

View File

@@ -64,13 +64,13 @@ class AudioContentCommentListViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
@@ -115,13 +115,13 @@ class AudioContentCommentListViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -135,13 +135,13 @@ class AudioContentCommentListViewModel: ObservableObject {
isActive: Bool? = nil isActive: Bool? = nil
) { ) {
if comment == nil && isActive == nil { if comment == nil && isActive == nil {
errorMessage = "변경사항이 없습니다." errorMessage = I18n.ContentDetail.Comment.noChanges
isShowPopup = true isShowPopup = true
return return
} }
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty { if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
errorMessage = "내용을 입력하세요." errorMessage = I18n.ContentDetail.Comment.inputContent
isShowPopup = true isShowPopup = true
return return
} }
@@ -187,14 +187,14 @@ class AudioContentCommentListViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.isLoading = false self.isLoading = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -29,7 +29,7 @@ struct AudioContentListReplyView: View {
HStack(spacing: 6.7) { HStack(spacing: 6.7) {
Image("ic_back") Image("ic_back")
Text("답글") Text(I18n.ContentDetail.Comment.replyTitle)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
@@ -55,7 +55,7 @@ struct AudioContentListReplyView: View {
.clipShape(Circle()) .clipShape(Circle())
HStack(spacing: 0) { HStack(spacing: 0) {
TextField("댓글을 입력해 보세요.", text: $viewModel.comment) TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $viewModel.comment)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)

View File

@@ -62,13 +62,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
@@ -113,13 +113,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -133,13 +133,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
isActive: Bool? = nil isActive: Bool? = nil
) { ) {
if comment == nil && isActive == nil { if comment == nil && isActive == nil {
errorMessage = "변경사항이 없습니다." errorMessage = I18n.ContentDetail.Comment.noChanges
isShowPopup = true isShowPopup = true
return return
} }
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty { if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
errorMessage = "내용을 입력하세요." errorMessage = I18n.ContentDetail.Comment.inputContent
isShowPopup = true isShowPopup = true
return return
} }
@@ -185,14 +185,14 @@ final class AudioContentListReplyViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.isLoading = false self.isLoading = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }

View File

@@ -22,7 +22,7 @@ struct ContentDetailCommentView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 10.3) { VStack(alignment: .leading, spacing: 10.3) {
HStack(spacing: 5.3) { HStack(spacing: 5.3) {
Text("댓글") Text(I18n.ContentDetail.Comment.title)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
@@ -38,7 +38,7 @@ struct ContentDetailCommentView: View {
.resizable() .resizable()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("비밀댓글") Text(I18n.ContentDetail.Comment.secretComment)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(isSecret ? Color.button : Color.grayee) .foregroundColor(isSecret ? Color.button : Color.grayee)
} }
@@ -71,7 +71,7 @@ struct ContentDetailCommentView: View {
.padding(.leading, 3) .padding(.leading, 3)
} else { } else {
HStack(spacing: 0) { HStack(spacing: 0) {
TextField("댓글을 입력해 보세요.", text: $comment) TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $comment)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)

View File

@@ -18,7 +18,7 @@ struct ContentDetailInfoLimitedEditionView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 13.3) { VStack(alignment: .leading, spacing: 13.3) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("한정판") Text(I18n.ContentDetail.LimitedEdition.title)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.button) .foregroundColor(Color.button)
@@ -40,7 +40,7 @@ struct ContentDetailInfoLimitedEditionView: View {
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
.padding(.leading, 2.3) .padding(.leading, 2.3)
} else if (remainingContentCount <= 0) { } else if (remainingContentCount <= 0) {
Text("Sold Out") Text(I18n.Content.Status.soldOut)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)
@@ -51,7 +51,7 @@ struct ContentDetailInfoLimitedEditionView: View {
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
) )
} else { } else {
Text("잔여수량") Text(I18n.ContentDetail.LimitedEdition.remainingCount)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
@@ -69,7 +69,7 @@ struct ContentDetailInfoLimitedEditionView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
if !buyerList.isEmpty { if !buyerList.isEmpty {
Text("구매자") Text(I18n.ContentDetail.LimitedEdition.buyers)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)

View File

@@ -22,7 +22,7 @@ struct ContentDetailInfoView: View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 5.3) { HStack(spacing: 5.3) {
if let _ = audioContent.releaseDate { if let _ = audioContent.releaseDate {
Text("오픈예정") Text(I18n.Common.openScheduled)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color(hex: "3bb9f1")) .foregroundColor(Color(hex: "3bb9f1"))
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)
@@ -62,7 +62,7 @@ struct ContentDetailInfoView: View {
} }
} }
Text(orderType == .KEEP ? "소장중" : "대여중") Text(orderType == .KEEP ? I18n.Content.Status.owned : I18n.Content.Status.rented)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor( .foregroundColor(
orderType == .KEEP ? orderType == .KEEP ?
@@ -113,7 +113,7 @@ struct ContentDetailInfoView: View {
HStack(spacing: 4) { HStack(spacing: 4) {
Image("ic_audio_content_share") Image("ic_audio_content_share")
Text("공유") Text(I18n.ContentDetail.Info.share)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
} }
@@ -129,7 +129,7 @@ struct ContentDetailInfoView: View {
.resizable() .resizable()
.frame(width: 13.3, height: 13.3) .frame(width: 13.3, height: 13.3)
Text("후원") Text(I18n.ContentDetail.Info.donation)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayd2) .foregroundColor(Color.grayd2)
} }
@@ -174,7 +174,7 @@ struct ContentDetailInfoView: View {
if isShowingPreviewAlert() { if isShowingPreviewAlert() {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("미리듣기 중입니다.\n콘텐츠 구매 후 전체를 감상해 보세요.") Text(I18n.ContentDetail.Info.previewAlertMessage)
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color.graybb) .foregroundColor(Color.graybb)
.lineSpacing(5) .lineSpacing(5)

View File

@@ -34,7 +34,7 @@ struct ContentDetailMenuView: View {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Image(isPin ? "ic_pin_cancel" : "ic_pin") Image(isPin ? "ic_pin_cancel" : "ic_pin")
Text(isPin ? "내 채널에 고정 취소" : "내 채널에 고정") Text(isPin ? I18n.ContentDetail.Menu.unpinFromMyChannel : I18n.ContentDetail.Menu.pinToMyChannel)
.appFont(size: 16.7, weight: .medium) .appFont(size: 16.7, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
@@ -51,7 +51,7 @@ struct ContentDetailMenuView: View {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Image("ic_make_message") Image("ic_make_message")
Text("수정") Text(I18n.ContentDetail.Menu.edit)
.appFont(size: 16.7, weight: .medium) .appFont(size: 16.7, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
@@ -68,7 +68,7 @@ struct ContentDetailMenuView: View {
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Image("ic_trash_can") Image("ic_trash_can")
Text("삭제") Text(I18n.Common.delete)
.appFont(size: 16.7, weight: .medium) .appFont(size: 16.7, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
@@ -83,7 +83,7 @@ struct ContentDetailMenuView: View {
} }
} else { } else {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("신고") Text(I18n.ContentDetail.Menu.report)
.appFont(size: 16.7, weight: .medium) .appFont(size: 16.7, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)

View File

@@ -16,12 +16,12 @@ struct ContentDetailMosaicView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
Image("ic_notice_exclamation_mark") Image("ic_notice_exclamation_mark")
Text("본 콘텐츠는 만 19세 미만의 청소년이\n이용할 수 없습니다.\n본인인증 후 콘텐츠를 이용해 주세요.") Text(I18n.ContentDetail.Mosaic.adultRestrictionNotice)
.appFont(size: 18.7, weight: .medium) .appFont(size: 18.7, weight: .medium)
.foregroundColor(Color(hex: "bbbbbb")) .foregroundColor(Color(hex: "bbbbbb"))
.padding(.top, 21.7) .padding(.top, 21.7)
Text("본인인증") Text(I18n.ContentDetail.Mosaic.verifyIdentity)
.appFont(size: 18.7, weight: .medium) .appFont(size: 18.7, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)

View File

@@ -36,7 +36,7 @@ struct ContentDetailOtherContentView: View {
.resizable() .resizable()
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
Text("\(title)를 준비중입니다.\n조금만 기다려주세요.") Text(I18n.ContentDetail.OtherContent.preparingMessage(title))
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.appFont(size: 10.7, weight: .medium) .appFont(size: 10.7, weight: .medium)
.foregroundColor(Color(hex: "bbbbbb")) .foregroundColor(Color(hex: "bbbbbb"))

View File

@@ -62,7 +62,7 @@ struct ContentDetailPlayView: View {
) )
if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0, audioContent.creator.creatorId != UserDefaults.int(forKey: .userId), !audioContent.existOrdered { if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0, audioContent.creator.creatorId != UserDefaults.int(forKey: .userId), !audioContent.existOrdered {
Text("Sold Out") Text(I18n.Content.Status.soldOut)
.appFont(size: 36.7, weight: .bold) .appFont(size: 36.7, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
.frame( .frame(

View File

@@ -32,7 +32,7 @@ struct ContentDetailPreviousNextContentButtonView: View {
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.lineLimit(2) .lineLimit(2)
Text("이전화") Text(I18n.ContentDetail.Navigation.previousEpisode)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(.button) .foregroundColor(.button)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
@@ -71,7 +71,7 @@ struct ContentDetailPreviousNextContentButtonView: View {
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.lineLimit(2) .lineLimit(2)
Text("다음화") Text(I18n.ContentDetail.Navigation.nextEpisode)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(.button) .foregroundColor(.button)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)

View File

@@ -26,7 +26,11 @@ struct ContentDetailPurchaseButton: View {
.foregroundColor(.white) .foregroundColor(.white)
.padding(.leading, 5.3) .padding(.leading, 5.3)
Text(UserDefaults.int(forKey: .userId) == 17958 ? "원으로": "캔으로") Text(
UserDefaults.int(forKey: .userId) == 17958 ?
I18n.ContentDetail.Purchase.withWon :
I18n.ContentDetail.Purchase.withCan
)
.appFont(size: 12, weight: .light) .appFont(size: 12, weight: .light)
.foregroundColor(.white) .foregroundColor(.white)

View File

@@ -36,7 +36,7 @@ struct ContentDetailView: View {
.resizable() .resizable()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("콘텐츠 상세") Text(I18n.ContentDetail.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
} }
@@ -120,7 +120,7 @@ struct ContentDetailView: View {
audioContent.orderType == nil && audioContent.orderType == nil &&
audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) { audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0 { if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0 {
Text("해당 콘텐츠가 매진되었습니다.") Text(I18n.ContentDetail.soldOutNotice)
.appFont(size: 13.3, weight: .bold) .appFont(size: 13.3, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -365,7 +365,7 @@ struct ContentDetailView: View {
} }
}, },
showToast: { showToast: {
viewModel.errorMessage = "동의하셔야 삭제할 수 있습니다." viewModel.errorMessage = I18n.ContentDetail.deleteAgreementRequired
viewModel.isShowPopup = true viewModel.isShowPopup = true
} }
) )

View File

@@ -69,14 +69,14 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
print(error) print(error)
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
@@ -110,13 +110,13 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -148,13 +148,13 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -186,13 +186,13 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -212,7 +212,7 @@ final class ContentDetailViewModel: ObservableObject {
self.shareMessage = shareUrl self.shareMessage = shareUrl
self.isShowShareView = true self.isShowShareView = true
} else { } else {
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요." self.errorMessage = I18n.ContentDetail.shareLinkCreateFailed
self.isShowPopup = true self.isShowPopup = true
} }
@@ -248,13 +248,13 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -286,26 +286,26 @@ final class ContentDetailViewModel: ObservableObject {
AppState.shared.purchasedContentOrderType = .KEEP AppState.shared.purchasedContentOrderType = .KEEP
self.orderType = nil self.orderType = nil
self.errorMessage = orderType == .RENTAL ? "대여가 완료되었습니다." : "구매가 완료되었습니다." self.errorMessage = orderType == .RENTAL ? I18n.ContentDetail.rentalCompleted : I18n.ContentDetail.purchaseCompleted
self.isShowPopup = true self.isShowPopup = true
self.getAudioContentDetail() self.getAudioContentDetail()
ContentPlayManager.shared.conditionalStopAudio(contentId: contentId) ContentPlayManager.shared.conditionalStopAudio(contentId: contentId)
} else { } else {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
if message.contains("캔이 부족합니다") { if isInsufficientCanError(message: message, errorProperty: decoded.errorProperty) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
AppState.shared.setAppStep(step: .canCharge(refresh: {}, afterCompletionToGoBack: true)) AppState.shared.setAppStep(step: .canCharge(refresh: {}, afterCompletionToGoBack: true))
} }
} }
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -335,12 +335,12 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -368,20 +368,20 @@ final class ContentDetailViewModel: ObservableObject {
if decoded.success { if decoded.success {
self.orderType = nil self.orderType = nil
self.errorMessage = "삭제되었습니다" self.errorMessage = I18n.ContentDetail.deleteCompleted
self.isShowPopup = true self.isShowPopup = true
onSuccess() onSuccess()
} else { } else {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -390,10 +390,10 @@ final class ContentDetailViewModel: ObservableObject {
func donation(can: Int, comment: String) { func donation(can: Int, comment: String) {
if can <= 0 { if can <= 0 {
self.errorMessage = "1캔 이상 후원하실 수 있습니다." self.errorMessage = I18n.LiveRoom.atLeastOneCanDonationMessage
self.isShowPopup = true self.isShowPopup = true
} else if comment.trimmingCharacters(in: .whitespaces).isEmpty { } else if comment.trimmingCharacters(in: .whitespaces).isEmpty {
self.errorMessage = "함께 보낼 메시지를 입력하세요." self.errorMessage = I18n.ContentDetail.donationMessageRequired
self.isShowPopup = true self.isShowPopup = true
} else { } else {
isLoading = true isLoading = true
@@ -415,7 +415,7 @@ final class ContentDetailViewModel: ObservableObject {
if decoded.success { if decoded.success {
UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can) UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can)
self.errorMessage = "\(can)캔을 후원하셨습니다." self.errorMessage = I18n.ContentDetail.donationCompleted(can)
self.isShowPopup = true self.isShowPopup = true
self.getAudioContentDetail() self.getAudioContentDetail()
@@ -423,13 +423,13 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -456,7 +456,7 @@ final class ContentDetailViewModel: ObservableObject {
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success { if decoded.success {
self.errorMessage = "고정되었습니다" self.errorMessage = I18n.ContentDetail.pinCompleted
self.isShowPopup = true self.isShowPopup = true
self.getAudioContentDetail() self.getAudioContentDetail()
@@ -464,13 +464,13 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -496,7 +496,7 @@ final class ContentDetailViewModel: ObservableObject {
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success { if decoded.success {
self.errorMessage = "해제되었습니다" self.errorMessage = I18n.ContentDetail.unpinCompleted
self.isShowPopup = true self.isShowPopup = true
self.getAudioContentDetail() self.getAudioContentDetail()
@@ -504,16 +504,40 @@ final class ContentDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
.store(in: &subscription) .store(in: &subscription)
} }
private func isInsufficientCanError(message: String, errorProperty: String?) -> Bool {
let normalizedErrorProperty = errorProperty?.lowercased() ?? ""
if normalizedErrorProperty.contains("not_enough_can") ||
normalizedErrorProperty.contains("insufficient_can") ||
normalizedErrorProperty.contains("lack_of_can")
{
return true
}
let normalizedMessage = message.lowercased()
let insufficientCanPatterns = [
"캔이 부족",
"캔 부족",
"not enough can",
"insufficient can",
"not enough cans",
"insufficient cans",
"canが不足"
]
return insufficientCanPatterns.contains { normalizedMessage.contains($0) }
}
} }

View File

@@ -35,7 +35,7 @@ struct ContentOrderConfirmDialogView: View {
.ignoresSafeArea() .ignoresSafeArea()
VStack(spacing: 0) { VStack(spacing: 0) {
Text("구매확인") Text(I18n.ContentDetail.OrderConfirmDialog.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -100,7 +100,11 @@ struct ContentOrderConfirmDialogView: View {
.cornerRadius(5.3) .cornerRadius(5.3)
.padding(.top, 21.3) .padding(.top, 21.3)
Text("콘텐츠를 \(orderType == .RENTAL ? "대여" : "소장")하시겠습니까?") Text(
orderType == .RENTAL ?
I18n.ContentDetail.OrderConfirmDialog.rentQuestion :
I18n.ContentDetail.OrderConfirmDialog.buyQuestion
)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -108,7 +112,7 @@ struct ContentOrderConfirmDialogView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
if UserDefaults.int(forKey: .userId) != 17958 { if UserDefaults.int(forKey: .userId) != 17958 {
Text("아래 금액이 차감됩니다.") Text(I18n.ContentDetail.OrderConfirmDialog.deductionNotice)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -150,7 +154,7 @@ struct ContentOrderConfirmDialogView: View {
} }
} }
} else { } else {
Text("\(price * 110)") Text("\(price * 110)\(I18n.ContentDetail.Purchase.wonUnit)")
.appFont(size: 13.3, weight: .bold) .appFont(size: 13.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
} }
@@ -168,7 +172,7 @@ struct ContentOrderConfirmDialogView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 12) { HStack(spacing: 12) {
Text("취소") Text(I18n.Common.cancel)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)
.padding(.vertical, 15.7) .padding(.vertical, 15.7)
@@ -181,7 +185,7 @@ struct ContentOrderConfirmDialogView: View {
) )
.onTapGesture { isShowing = false } .onTapGesture { isShowing = false }
Text("확인") Text(I18n.Common.confirm)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
.padding(.vertical, 15.7) .padding(.vertical, 15.7)

View File

@@ -27,11 +27,11 @@ struct ContentOrderDialogView: View {
VStack(spacing: 26.7) { VStack(spacing: 26.7) {
HStack(spacing: 0) { HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 5.3) { VStack(alignment: .leading, spacing: 5.3) {
Text("대여") Text(I18n.ContentDetail.OrderDialog.rent)
.appFont(size: 13.3, weight: .bold) .appFont(size: 13.3, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
Text("(이용기간 5일)") Text(I18n.ContentDetail.OrderDialog.rentPeriod)
.appFont(size: 12, weight: .light) .appFont(size: 12, weight: .light)
.foregroundColor(.white) .foregroundColor(.white)
} }
@@ -56,7 +56,7 @@ struct ContentOrderDialogView: View {
} }
if UserDefaults.int(forKey: .userId) == 17958 { if UserDefaults.int(forKey: .userId) == 17958 {
Text("") Text(I18n.ContentDetail.Purchase.wonUnit)
.appFont(size: 13.3, weight: .bold) .appFont(size: 13.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
} }
@@ -73,11 +73,11 @@ struct ContentOrderDialogView: View {
HStack(spacing: 0) { HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 5.3) { VStack(alignment: .leading, spacing: 5.3) {
Text("소장") Text(I18n.ContentDetail.OrderDialog.buy)
.appFont(size: 13.3, weight: .bold) .appFont(size: 13.3, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
Text("(서비스 종료시까지)") Text(I18n.ContentDetail.OrderDialog.buyPeriod)
.appFont(size: 12, weight: .light) .appFont(size: 12, weight: .light)
.foregroundColor(.white) .foregroundColor(.white)
} }
@@ -102,7 +102,7 @@ struct ContentOrderDialogView: View {
} }
if UserDefaults.int(forKey: .userId) == 17958 { if UserDefaults.int(forKey: .userId) == 17958 {
Text("") Text(I18n.ContentDetail.Purchase.wonUnit)
.appFont(size: 13.3, weight: .bold) .appFont(size: 13.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
} }

View File

@@ -65,7 +65,7 @@ struct LiveRoomDonationDialogView: View {
.resizable() .resizable()
.frame(width: 26.7, height: 26.7) .frame(width: 26.7, height: 26.7)
Text("후원하기") Text(I18n.ContentDetail.DonationDialog.title)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -80,7 +80,7 @@ struct LiveRoomDonationDialogView: View {
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
Text("충전") Text(I18n.ContentDetail.DonationDialog.charge)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.main) .foregroundColor(Color.main)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
@@ -126,7 +126,12 @@ struct LiveRoomDonationDialogView: View {
.padding(.top, 16) .padding(.top, 16)
} }
TextField(isSecret ? "10캔 이상 입력하세요" : "1캔 이상 입력하세요", text: $donationCan) TextField(
isSecret ?
I18n.ContentDetail.DonationDialog.minimumTenCanPlaceholder :
I18n.ContentDetail.DonationDialog.minimumOneCanPlaceholder,
text: $donationCan
)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.padding(13.3) .padding(13.3)
@@ -227,7 +232,10 @@ struct LiveRoomDonationDialogView: View {
) )
TextField( TextField(
"함께 보낼 \((isSecret && shouldPrefixSecretInMessagePlaceholder) ? "비밀 " : "")메시지 입력(최대 \(messageLimit)자)", I18n.ContentDetail.DonationDialog.messagePlaceholder(
isSecret: isSecret && shouldPrefixSecretInMessagePlaceholder,
limit: messageLimit
),
text: $donationMessage text: $donationMessage
) )
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
@@ -242,7 +250,7 @@ struct LiveRoomDonationDialogView: View {
.padding(.horizontal, 20) .padding(.horizontal, 20)
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Text("취소") Text(I18n.Common.cancel)
.appFont(size: 15, weight: .bold) .appFont(size: 15, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)
.padding(.vertical, 16) .padding(.vertical, 16)
@@ -258,7 +266,7 @@ struct LiveRoomDonationDialogView: View {
isShowing = false isShowing = false
} }
Text("후원하기") Text(I18n.ContentDetail.DonationDialog.donateAction)
.appFont(size: 15, weight: .bold) .appFont(size: 15, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
.padding(.vertical, 16) .padding(.vertical, 16)
@@ -271,14 +279,14 @@ struct LiveRoomDonationDialogView: View {
errorMessage = secretMinimumCanMessage errorMessage = secretMinimumCanMessage
isShowErrorPopup = true isShowErrorPopup = true
} else if can < 1 { } else if can < 1 {
errorMessage = "1캔 이상 후원하실 수 있습니다." errorMessage = I18n.LiveRoom.atLeastOneCanDonationMessage
isShowErrorPopup = true isShowErrorPopup = true
} else { } else {
onClickDonation(can, donationMessage, isSecret) onClickDonation(can, donationMessage, isSecret)
isShowing = false isShowing = false
} }
} else { } else {
errorMessage = "1캔 이상 후원하실 수 있습니다." errorMessage = I18n.LiveRoom.atLeastOneCanDonationMessage
isShowErrorPopup = true isShowErrorPopup = true
} }
} }

View File

@@ -49,13 +49,13 @@ final class ContentMainBannerViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "배너를 불러오지 못했습니다. 다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Content.Banner.loadFailed
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "배너를 불러오지 못했습니다. 다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Content.Banner.loadFailed
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }

View File

@@ -21,11 +21,11 @@ struct ContentModifyView: View {
GeometryReader { proxy in GeometryReader { proxy in
ZStack { ZStack {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: "콘텐츠 수정") DetailNavigationBar(title: I18n.CreateContent.modifyTitle)
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) { VStack(spacing: 0) {
Text("썸네일") Text(I18n.CreateContent.thumbnail)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -82,12 +82,12 @@ struct ContentModifyView: View {
.padding(.top, 26.7) .padding(.top, 26.7)
VStack(spacing: 0) { VStack(spacing: 0) {
Text("제목") Text(I18n.CreateContent.titleLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
TextField("제목을 입력하세요", text: $viewModel.title) TextField(I18n.CreateContent.titlePlaceholder, text: $viewModel.title)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
@@ -100,16 +100,16 @@ struct ContentModifyView: View {
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("내용") Text(I18n.CreateContent.contentLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
Spacer() Spacer()
Text("\(viewModel.detail.count)") Text(I18n.CreateContent.characterCount(viewModel.detail.count))
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "ff5c49")) .foregroundColor(Color(hex: "ff5c49"))
Text(" / 최대 500자") Text(I18n.CreateContent.max500CharactersSuffix)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color(hex: "777777"))
} }
@@ -125,13 +125,13 @@ struct ContentModifyView: View {
.cornerRadius(6.7) .cornerRadius(6.7)
.padding(.top, 13.3) .padding(.top, 13.3)
Text("태그") Text(I18n.CreateContent.tagLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 26.7) .padding(.top, 26.7)
TextField("예: #연애 #커버곡", text: $viewModel.hashtags) TextField(I18n.CreateContent.tagPlaceholderExample, text: $viewModel.hashtags)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
@@ -152,19 +152,19 @@ struct ContentModifyView: View {
.padding(.top, 26.7) .padding(.top, 26.7)
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("포인트 사용") Text(I18n.CreateContent.pointUsageTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
SelectButtonView(title: "가능", isChecked: viewModel.isPointAvailable) { SelectButtonView(title: I18n.CreateContent.available, isChecked: viewModel.isPointAvailable) {
if !viewModel.isPointAvailable { if !viewModel.isPointAvailable {
viewModel.isPointAvailable = true viewModel.isPointAvailable = true
} }
} }
SelectButtonView(title: "불가능", isChecked: !viewModel.isPointAvailable) { SelectButtonView(title: I18n.CreateContent.unavailable, isChecked: !viewModel.isPointAvailable) {
if viewModel.isPointAvailable { if viewModel.isPointAvailable {
viewModel.isPointAvailable = false viewModel.isPointAvailable = false
} }
@@ -176,26 +176,26 @@ struct ContentModifyView: View {
if viewModel.isAdultShowUi { if viewModel.isAdultShowUi {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("연령 제한") Text(I18n.CreateContent.ageRestrictionTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
SelectButtonView(title: "전체 연령", isChecked: !viewModel.isAdult) { SelectButtonView(title: I18n.CreateContent.allAges, isChecked: !viewModel.isAdult) {
if viewModel.isAdult { if viewModel.isAdult {
viewModel.isAdult = false viewModel.isAdult = false
} }
} }
SelectButtonView(title: "19세 이상", isChecked: viewModel.isAdult) { SelectButtonView(title: I18n.CreateContent.over19, isChecked: viewModel.isAdult) {
if !viewModel.isAdult { if !viewModel.isAdult {
viewModel.isAdult = true viewModel.isAdult = true
} }
} }
} }
Text("성인콘텐츠를 전체관람가로 등록할 시 발생하는 법적 책임은 회사와 상관없이 콘텐츠를 등록한 본인에게 있습니다.\n콘텐츠 내용은 물론 제목도 19금 여부를 체크해 주시기 바랍니다.") Text(I18n.CreateContent.adultLegalNotice)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color(hex: "DD4500")) .foregroundColor(Color(hex: "DD4500"))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -206,19 +206,19 @@ struct ContentModifyView: View {
} }
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("댓글 가능 여부") Text(I18n.CreateContent.commentAvailabilityTitle)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color(hex: "eeeeee"))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
SelectButtonView(title: "댓글 가능", isChecked: viewModel.isAvailableComment) { SelectButtonView(title: I18n.CreateContent.commentAllowed, isChecked: viewModel.isAvailableComment) {
if !viewModel.isAvailableComment { if !viewModel.isAvailableComment {
viewModel.isAvailableComment = true viewModel.isAvailableComment = true
} }
} }
SelectButtonView(title: "댓글 불가", isChecked: !viewModel.isAvailableComment) { SelectButtonView(title: I18n.CreateContent.commentNotAllowed, isChecked: !viewModel.isAvailableComment) {
if viewModel.isAvailableComment { if viewModel.isAvailableComment {
viewModel.isAvailableComment = false viewModel.isAvailableComment = false
} }
@@ -230,7 +230,7 @@ struct ContentModifyView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(alignment: .top, spacing: 0) { HStack(alignment: .top, spacing: 0) {
Text("수정") Text(I18n.CreateContent.modifyAction)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.frame(height: 50) .frame(height: 50)

View File

@@ -31,7 +31,7 @@ final class ContentModifyViewModel: ObservableObject {
@Published var isAdultShowUi = false @Published var isAdultShowUi = false
var contentId: Int = 0 var contentId: Int = 0
var placeholder = "내용을 입력하세요" var placeholder = I18n.CreateContent.uploadContentDescriptionHint
func getAudioContentDetail(onFailure: (() -> Void)? = nil) { func getAudioContentDetail(onFailure: (() -> Void)? = nil) {
audioContent = nil audioContent = nil
@@ -68,13 +68,13 @@ final class ContentModifyViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
@@ -112,7 +112,7 @@ final class ContentModifyViewModel: ObservableObject {
mimeType: "image/*") mimeType: "image/*")
) )
} else { } else {
errorMessage = "커버이미지를 업로드 하지 못했습니다.\n다시 선택해 주세요" errorMessage = I18n.CreateContent.coverImageUploadFailed
isShowPopup = true isShowPopup = true
isLoading = false isLoading = false
return return
@@ -139,26 +139,26 @@ final class ContentModifyViewModel: ObservableObject {
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success { if decoded.success {
self.errorMessage = "콘텐츠가 수정되었습니다." self.errorMessage = I18n.CreateContent.modifySuccess
self.isShowPopup = true self.isShowPopup = true
onSuccess() onSuccess()
} else { } else {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
} }
.store(in: &subscription) .store(in: &subscription)
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }
@@ -167,13 +167,13 @@ final class ContentModifyViewModel: ObservableObject {
private func validateData() -> Bool { private func validateData() -> Bool {
if title != audioContent!.title && title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { if title != audioContent!.title && title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
errorMessage = "제목을 입력해 주세요." errorMessage = I18n.CreateContent.titleRequired
isShowPopup = true isShowPopup = true
return false return false
} }
if detail != audioContent!.detail && (detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5) { if detail != audioContent!.detail && (detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5) {
errorMessage = "내용을 5자 이상 입력해 주세요." errorMessage = I18n.CreateContent.detailMinLengthRequired
isShowPopup = true isShowPopup = true
return false return false
} }

View File

@@ -37,7 +37,7 @@ struct ContentPlaylistItemView: View {
.lineLimit(1) .lineLimit(1)
} }
Text("\(item.contentCount)") Text(I18n.Content.Playlist.itemCount(item.contentCount))
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color.gray90) .foregroundColor(Color.gray90)
.lineLimit(1) .lineLimit(1)

View File

@@ -17,7 +17,7 @@ struct ContentPlaylistListView: View {
var body: some View { var body: some View {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("+ 새 재생목록 만들기") Text(I18n.Content.Playlist.createNewAction)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.vertical, 13.3) .padding(.vertical, 13.3)
@@ -31,11 +31,11 @@ struct ContentPlaylistListView: View {
if viewModel.playlists.isEmpty { if viewModel.playlists.isEmpty {
VStack(spacing: 13.3) { VStack(spacing: 13.3) {
Text("재생목록이 비어있습니다.") Text(I18n.Content.Playlist.emptyTitle)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
Text("자주 듣는 콘텐츠를\n재생목록으로 만들어 보세요.") Text(I18n.Content.Playlist.emptyDescription)
.appFont(size: 11, weight: .medium) .appFont(size: 11, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -45,11 +45,11 @@ struct ContentPlaylistListView: View {
.cornerRadius(4.7) .cornerRadius(4.7)
} else { } else {
HStack(spacing: 5.3) { HStack(spacing: 5.3) {
Text("전체") Text(I18n.Content.Playlist.totalLabel)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
Text("\(viewModel.totalCount)") Text("\(viewModel.totalCount)\(I18n.Content.Count.countUnit)")
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color.gray90) .foregroundColor(Color.gray90)

View File

@@ -44,13 +44,13 @@ final class ContentPlaylistListViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }

View File

@@ -26,14 +26,14 @@ struct ContentPlaylistCreateView: View {
.resizable() .resizable()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("새 재생목록 만들기") Text(I18n.Content.Playlist.createTitle)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
} }
Spacer() Spacer()
Text("저장") Text(I18n.Content.Playlist.createSave)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(minHeight: 48) .frame(minHeight: 48)
@@ -50,7 +50,7 @@ struct ContentPlaylistCreateView: View {
.background(Color.black) .background(Color.black)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("재생목록 제목") Text(I18n.Content.Playlist.titleLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -83,7 +83,7 @@ struct ContentPlaylistCreateView: View {
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("재생목록 설명을 입력해 주세요") Text(I18n.Content.Playlist.descriptionLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -118,7 +118,7 @@ struct ContentPlaylistCreateView: View {
HStack(spacing: 8) { HStack(spacing: 8) {
Image("btn_plus_round") Image("btn_plus_round")
Text("새로운 콘텐츠 추가/제거") Text(I18n.Content.Playlist.addContentAction)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)
} }

View File

@@ -57,13 +57,13 @@ final class ContentPlaylistCreateViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }
@@ -74,13 +74,13 @@ final class ContentPlaylistCreateViewModel: ObservableObject {
private func validate() -> Bool { private func validate() -> Bool {
if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) { if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) {
errorMessage = "제목을 3자 이상 입력하세요" errorMessage = I18n.Content.Playlist.titleValidation
isShowPopup = true isShowPopup = true
return false return false
} }
if (contentList.isEmpty) { if (contentList.isEmpty) {
errorMessage = "콘텐츠를 1개 이상 추가하세요" errorMessage = I18n.Content.Playlist.contentValidation
isShowPopup = true isShowPopup = true
return false return false
} }

View File

@@ -17,14 +17,14 @@ struct PlaylistAddContentView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 13.3) { VStack(alignment: .leading, spacing: 13.3) {
ZStack { ZStack {
Text("새로운 콘텐츠 추가/제거") Text(I18n.Content.Playlist.addContentAction)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
HStack(spacing: 0) { HStack(spacing: 0) {
Spacer() Spacer()
Text("닫기") Text(I18n.Content.Playlist.close)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(minHeight: 48) .frame(minHeight: 48)
@@ -36,11 +36,11 @@ struct PlaylistAddContentView: View {
.background(Color.black) .background(Color.black)
HStack(alignment: .center, spacing: 5.3) { HStack(alignment: .center, spacing: 5.3) {
Text("전체") Text(I18n.Content.Playlist.totalLabel)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
Text("\(viewModel.totalCount)") Text(I18n.Content.Playlist.selectionCount(viewModel.totalCount))
.appFont(size: 12, weight: .medium) .appFont(size: 12, weight: .medium)
.foregroundColor(Color.gray90) .foregroundColor(Color.gray90)
} }

View File

@@ -125,13 +125,13 @@ struct ContentPlaylistDetailView: View {
} }
HStack(spacing: 0) { HStack(spacing: 0) {
Text("만든 날짜 \(response.createdDate)") Text(I18n.Content.Playlist.createdDate(response.createdDate))
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray90) .foregroundColor(Color.gray90)
Spacer() Spacer()
Text("\(response.contentCount)") Text(I18n.Content.Playlist.contentCount(response.contentCount))
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
} }
@@ -141,7 +141,7 @@ struct ContentPlaylistDetailView: View {
HStack(spacing: 5.3) { HStack(spacing: 5.3) {
Image("ic_playlist_play") Image("ic_playlist_play")
Text("Play") Text(I18n.Content.Playlist.play)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.white) .foregroundColor(Color.white)
} }
@@ -159,7 +159,7 @@ struct ContentPlaylistDetailView: View {
HStack(spacing: 5.3) { HStack(spacing: 5.3) {
Image("ic_playlist_shuffle") Image("ic_playlist_shuffle")
Text("Shuffle") Text(I18n.Content.Playlist.shuffle)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.white) .foregroundColor(Color.white)
} }
@@ -255,7 +255,7 @@ struct ContentPlaylistDetailView: View {
Spacer() Spacer()
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Text("삭제") Text(I18n.Common.delete)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)

View File

@@ -50,13 +50,13 @@ final class ContentPlaylistDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
@@ -84,7 +84,7 @@ final class ContentPlaylistDetailViewModel: ObservableObject {
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
if decoded.success { if decoded.success {
self.errorMessage = "삭제되었습니다." self.errorMessage = I18n.Playlist.deleteCompleted
self.isShowPopup = true self.isShowPopup = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
@@ -94,13 +94,13 @@ final class ContentPlaylistDetailViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }

View File

@@ -27,14 +27,14 @@ struct ContentPlaylistModifyView: View {
.resizable() .resizable()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
Text("재생목록 수정") Text(I18n.Content.Playlist.modifyTitle)
.appFont(size: 18.3, weight: .bold) .appFont(size: 18.3, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
} }
Spacer() Spacer()
Text("수정") Text(I18n.Content.Playlist.modifyAction)
.appFont(size: 14.7, weight: .medium) .appFont(size: 14.7, weight: .medium)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
.frame(minHeight: 48) .frame(minHeight: 48)
@@ -51,7 +51,7 @@ struct ContentPlaylistModifyView: View {
.background(Color.black) .background(Color.black)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("재생목록 제목") Text(I18n.Content.Playlist.titleLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -84,7 +84,7 @@ struct ContentPlaylistModifyView: View {
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
Text("재생목록 설명을 입력해 주세요") Text(I18n.Content.Playlist.descriptionLabel)
.appFont(size: 16.7, weight: .bold) .appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee) .foregroundColor(Color.grayee)
@@ -119,7 +119,7 @@ struct ContentPlaylistModifyView: View {
HStack(spacing: 8) { HStack(spacing: 8) {
Image("btn_plus_round") Image("btn_plus_round")
Text("새로운 콘텐츠 추가/제거") Text(I18n.Content.Playlist.addContentAction)
.appFont(size: 14.7, weight: .bold) .appFont(size: 14.7, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)
} }

View File

@@ -57,13 +57,13 @@ final class ContentPlaylistModifyViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }
@@ -112,13 +112,13 @@ final class ContentPlaylistModifyViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
self.isLoading = false self.isLoading = false
} }
@@ -129,13 +129,13 @@ final class ContentPlaylistModifyViewModel: ObservableObject {
private func validate() -> Bool { private func validate() -> Bool {
if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) { if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) {
errorMessage = "제목을 3자 이상 입력하세요" errorMessage = I18n.Content.Playlist.titleValidation
isShowPopup = true isShowPopup = true
return false return false
} }
if (contentList.isEmpty) { if (contentList.isEmpty) {
errorMessage = "콘텐츠를 1개 이상 추가하세요" errorMessage = I18n.Content.Playlist.contentValidation
isShowPopup = true isShowPopup = true
return false return false
} }

View File

@@ -18,12 +18,12 @@ struct SeriesContentAllView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) { VStack(spacing: 0) {
DetailNavigationBar(title: "\(seriesTitle) - 전체회차 듣기") DetailNavigationBar(title: I18n.Series.allEpisodesTitle(seriesTitle))
HStack(spacing: 13.3) { HStack(spacing: 13.3) {
Spacer() Spacer()
Text("최신순") Text(I18n.Content.Sort.newest)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color.graye2 Color.graye2
@@ -35,7 +35,7 @@ struct SeriesContentAllView: View {
} }
} }
Text("등록순") Text(I18n.Series.registeredOrder)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor( .foregroundColor(
Color.graye2 Color.graye2

View File

@@ -66,13 +66,13 @@ final class SeriesContentAllViewModel: ObservableObject {
if let message = decoded.message { if let message = decoded.message {
self.errorMessage = message self.errorMessage = message
} else { } else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
} }
self.isShowPopup = true self.isShowPopup = true
} }
} catch { } catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." self.errorMessage = I18n.Common.commonError
self.isShowPopup = true self.isShowPopup = true
} }

View File

@@ -33,7 +33,7 @@ struct SeriesContentListItemView: View {
.cornerRadius(2.6) .cornerRadius(2.6)
if item.isPointAvailable { if item.isPointAvailable {
Text("포인트") Text(I18n.Series.point)
.appFont(size: 8, weight: .medium) .appFont(size: 8, weight: .medium)
.foregroundColor(.white) .foregroundColor(.white)
.padding(2.6) .padding(2.6)
@@ -50,7 +50,7 @@ struct SeriesContentListItemView: View {
Spacer() Spacer()
if item.isOwned { if item.isOwned {
Text("소장중") Text(I18n.Content.Status.owned)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.gray11) .foregroundColor(Color.gray11)
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)
@@ -58,7 +58,7 @@ struct SeriesContentListItemView: View {
.background(Color(hex: "b1ef2c")) .background(Color(hex: "b1ef2c"))
.cornerRadius(2.6) .cornerRadius(2.6)
} else if item.isRented { } else if item.isRented {
Text("대여중") Text(I18n.Content.Status.rented)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)
@@ -74,7 +74,7 @@ struct SeriesContentListItemView: View {
.foregroundColor(Color(hex: "909090")) .foregroundColor(Color(hex: "909090"))
} }
} else { } else {
Text("무료") Text(I18n.Series.free)
.appFont(size: 13.3, weight: .medium) .appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.padding(.horizontal, 5.3) .padding(.horizontal, 5.3)

View File

@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
struct DayOfWeek { struct DayOfWeek {
let dayOfWeekStr: LocalizedStringResource let dayOfWeekStr: String
let dayOfWeek: SeriesPublishedDaysOfWeek let dayOfWeek: SeriesPublishedDaysOfWeek
} }
@@ -20,14 +20,14 @@ struct DayOfWeekSeriesView: View {
@State private var dayOfWeek: SeriesPublishedDaysOfWeek = .FRI @State private var dayOfWeek: SeriesPublishedDaysOfWeek = .FRI
private let dayOfWeekItems: [DayOfWeek] = [ private let dayOfWeekItems: [DayOfWeek] = [
DayOfWeek(dayOfWeekStr: "", dayOfWeek: .MON), DayOfWeek(dayOfWeekStr: I18n.Series.monday, dayOfWeek: .MON),
DayOfWeek(dayOfWeekStr: "", dayOfWeek: .TUE), DayOfWeek(dayOfWeekStr: I18n.Series.tuesday, dayOfWeek: .TUE),
DayOfWeek(dayOfWeekStr: "", dayOfWeek: .WED), DayOfWeek(dayOfWeekStr: I18n.Series.wednesday, dayOfWeek: .WED),
DayOfWeek(dayOfWeekStr: "", dayOfWeek: .THU), DayOfWeek(dayOfWeekStr: I18n.Series.thursday, dayOfWeek: .THU),
DayOfWeek(dayOfWeekStr: "", dayOfWeek: .FRI), DayOfWeek(dayOfWeekStr: I18n.Series.friday, dayOfWeek: .FRI),
DayOfWeek(dayOfWeekStr: "", dayOfWeek: .SAT), DayOfWeek(dayOfWeekStr: I18n.Series.saturday, dayOfWeek: .SAT),
DayOfWeek(dayOfWeekStr: "", dayOfWeek: .SUN), DayOfWeek(dayOfWeekStr: I18n.Series.sunday, dayOfWeek: .SUN),
DayOfWeek(dayOfWeekStr: "랜덤", dayOfWeek: .RANDOM), DayOfWeek(dayOfWeekStr: I18n.Series.random, dayOfWeek: .RANDOM),
] ]
// //
@@ -45,13 +45,13 @@ struct DayOfWeekSeriesView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("요일별 시리즈") Text(I18n.Series.byDaySectionTitle)
.appFont(size: 24, weight: .bold) .appFont(size: 24, weight: .bold)
.foregroundColor(.white) .foregroundColor(.white)
Spacer() Spacer()
Text("전체보기") Text(I18n.Common.viewAll)
.appFont(size: 14, weight: .regular) .appFont(size: 14, weight: .regular)
.foregroundColor(.init(hex: "78909C")) .foregroundColor(.init(hex: "78909C"))
.onTapGesture { .onTapGesture {
@@ -93,7 +93,7 @@ struct DayOfWeekSeriesView: View {
} }
struct DayOfWeekDayView: View { struct DayOfWeekDayView: View {
let dayOfWeek: LocalizedStringResource let dayOfWeek: String
let isSelected: Bool let isSelected: Bool
var body: some View { var body: some View {

View File

@@ -17,7 +17,7 @@ struct SeriesDetailHomeView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("전체회차 듣기") Text(I18n.Series.allEpisodesListen)
.appFont(size: 16, weight: .bold) .appFont(size: 16, weight: .bold)
.foregroundColor(Color.button) .foregroundColor(Color.button)

Some files were not shown because too many files have changed in this diff Show More