feat(i18n): 오디션 화면 하드코딩 문구를 I18n 키로 통일한다

This commit is contained in:
Yu Sung
2026-03-31 15:39:57 +09:00
parent 136bfc8eee
commit 222520d5e9
13 changed files with 406 additions and 87 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ struct AuditionDetailRoleItemView: View {
.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)
.foregroundColor(Color.white)
.padding(.horizontal, 9)

View File

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

View File

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