diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index 8a0ac0b..5ced527 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -2969,6 +2969,276 @@ If you block this user, the following features will be restricted. pick(ko: "고객센터", en: "Customer center", ja: "カスタマーセンター") } } + + enum OrderList { + static var contentStorageTitle: String { + pick(ko: "콘텐츠 보관함", en: "Content library", ja: "コンテンツ保管庫") + } + + static var renting: String { + pick(ko: "대여중", en: "Renting", ja: "レンタル中") + } + + static var owned: String { + pick(ko: "소장중", en: "Owned", ja: "所持中") + } + } + + enum Point { + static var historyTitle: String { + pick(ko: "포인트 내역", en: "Point history", ja: "ポイント履歴") + } + + static var expirationNotice: String { + pick( + ko: "※ 획득한 포인트는 72시간이 지나면 자동소멸 됩니다.", + en: "※ Earned points expire automatically after 72 hours.", + ja: "※ 獲得したポイントは72時間後に自動で失効します。" + ) + } + + static var rewardHistory: String { + pick(ko: "받은내역", en: "Received", ja: "獲得履歴") + } + + static var useHistory: String { + pick(ko: "사용내역", en: "Used", ja: "使用履歴") + } + } + + enum Nickname { + static var paidChangeNotice: String { + pick( + ko: "닉네임 변경으로 인해 피해를 입는 사용자가 지속적으로 발생하여 닉네임 변경을 부득이하게 유료로 전환합니다.", + en: "Due to repeated abuse from nickname changes, changing nicknames is now paid.", + ja: "ニックネーム変更による被害が継続して発生しているため、ニックネーム変更は有料に変更されました。" + ) + } + + static var firstFreeNotice: String { + pick( + ko: "최초 1회에 한해서 무료로 변경이 가능하고, 그 이후부터는 유료로 전환됩니다.", + en: "You can change your nickname for free only once. After that, it becomes paid.", + ja: "初回1回のみ無料で変更でき、それ以降は有料となります。" + ) + } + + static var duplicateCheckAction: String { + pick(ko: "중복확인", en: "Check duplicate", ja: "重複確認") + } + + static var changeAction: String { + pick(ko: "닉네임 변경하기", en: "Change nickname", ja: "ニックネームを変更する") + } + + static func changeActionWithCans(_ cans: Int) -> String { + pick( + ko: "\(cans)캔으로 닉네임 변경하기", + en: "Change nickname with \(cans) cans", + ja: "\(cans)canでニックネームを変更する" + ) + } + + static var available: String { + pick(ko: "사용가능한 닉네임 입니다.", en: "This nickname is available.", ja: "使用可能なニックネームです。") + } + + static var empty: String { + pick(ko: "닉네임을 입력하세요.", en: "Please enter a nickname.", ja: "ニックネームを入力してください。") + } + + static var changed: String { + pick(ko: "닉네임이 변경되었습니다.", en: "Nickname has been changed.", ja: "ニックネームが変更されました。") + } + + static var duplicateCheckRequired: String { + pick(ko: "닉네임 중복체크를 해주세요.", en: "Please check nickname duplication.", ja: "ニックネームの重複確認をしてください。") + } + } + + enum Profile { + static var changeNicknameAction: String { + pick(ko: "닉네임 변경", en: "Change nickname", ja: "ニックネーム変更") + } + + static var genderTitle: String { + pick(ko: "성별", en: "Gender", ja: "性別") + } + + static var female: String { + pick(ko: "여자", en: "Female", ja: "女性") + } + + static var male: String { + pick(ko: "남자", en: "Male", ja: "男性") + } + + static var notPublic: String { + pick(ko: "공개 안 함", en: "Private", ja: "非公開") + } + + static var interestsTitle: String { + pick(ko: "관심사", en: "Interests", ja: "関心事") + } + + static var selectInterestsAction: String { + pick(ko: "관심사 선택", en: "Select interests", ja: "関心事を選択") + } + + static var introductionTitle: String { + pick(ko: "소개글", en: "Introduction", ja: "自己紹介") + } + + static var saveAction: String { + pick(ko: "저장하기", en: "Save", ja: "保存する") + } + } + + enum ReservationStatus { + static var title: String { + pick(ko: "예약현황", en: "Reservation status", ja: "予約状況") + } + + static var live: String { + pick(ko: "라이브", en: "Live", ja: "ライブ") + } + } + + enum Reservation { + static func cansUnit(_ cans: Int) -> String { + pick(ko: "\(cans)캔", en: "\(cans) cans", ja: "\(cans)can") + } + + enum LiveStatus { + static var title: String { + pick(ko: "라이브 예약 현황", en: "Live reservation status", ja: "ライブ予約状況") + } + + static var emptyMessage: String { + pick(ko: "예약한 라이브가 없습니다.", en: "No reserved live sessions.", ja: "予約したライブがありません。") + } + + static var cancelUnavailable: String { + pick(ko: "예약 취소 불가", en: "Cancellation unavailable", ja: "予約キャンセル不可") + } + + static var cancelAction: String { + pick(ko: "예약\n취소", en: "Cancel\nreservation", ja: "予約\nキャンセル") + } + + static var free: String { + pick(ko: "무료", en: "Free", ja: "無料") + } + } + + enum Cancel { + static var title: String { + pick(ko: "예약취소", en: "Cancel reservation", ja: "予約キャンセル") + } + + static var completedTitle: String { + pick(ko: "예약취소 확인", en: "Cancellation complete", ja: "予約キャンセル確認") + } + + static var completedMessage: String { + pick(ko: "예약취소가 완료되었습니다.", en: "Reservation cancellation is complete.", ja: "予約キャンセルが完了しました。") + } + + static func refundedCanMessage(_ cans: Int) -> String { + pick( + ko: "결제한 \(cans)캔이\n환불처리 되었습니다.", + en: "Your paid \(cans) cans\nhave been refunded.", + ja: "決済した\(cans)canは\n返金処理されました。" + ) + } + + static var reserveAnotherLiveAction: String { + pick(ko: "다른 라이브 예약하기", en: "Reserve another live", ja: "別のライブを予約する") + } + + static var checkCanHistoryAction: String { + pick(ko: "캔내역 확인하기", en: "Check can history", ja: "can履歴を確認する") + } + + static var confirmQuestion: String { + pick(ko: "예약을 취소하시겠습니까?", en: "Do you want to cancel this reservation?", ja: "予約をキャンセルしますか?") + } + + static var reasonDescription: String { + pick( + ko: "예약취소 이유를 선택해주세요. 서비스 개선에 중요한 자료로 활용하겠습니다.", + en: "Please select a reason for cancellation. It will be used to improve our service.", + ja: "予約キャンセル理由を選択してください。サービス改善の重要な資料として活用します。" + ) + } + + static var reasonPlaceholder: String { + pick(ko: "입력해주세요", en: "Please enter", ja: "入力してください") + } + + static var notice: String { + pick( + ko: "취소요청시, 차감했던 캔은 환불처리 됩니다. 수다방 참여인원 제한에 따라 재예약이 불가할 수 있습니다.", + en: "When you request cancellation, deducted cans will be refunded. Re-reservation may be unavailable depending on participant limits.", + ja: "キャンセル申請時、差し引かれたcanは返金されます。参加人数制限により再予約できない場合があります。" + ) + } + + static var requestCancelAction: String { + pick(ko: "예약취소", en: "Request cancellation", ja: "予約キャンセル") + } + + static var invalidReservationInfo: String { + pick(ko: "잘못된 예약정보 입니다.", en: "Invalid reservation information.", ja: "無効な予約情報です。") + } + + static var reasonRequired: String { + pick(ko: "취소이유를 선택해주세요.", en: "Please select a cancellation reason.", ja: "キャンセル理由を選択してください。") + } + + static var reasonPersonalSchedule: String { + pick(ko: "중요한 개인일정이 생겨서", en: "I have an important personal schedule", ja: "重要な個人的予定ができたため") + } + + static var reasonOtherLive: String { + pick(ko: "다른 라이브에 참여하고 싶어서", en: "I want to join another live", ja: "他のライブに参加したいため") + } + + static var reasonUncomfortableParticipant: String { + pick(ko: "라이브 참여자 중 불편한 사람이 있어서", en: "There is an uncomfortable participant", ja: "参加者の中に不快な人がいるため") + } + + static var reasonPriceBurden: String { + pick(ko: "참여비용이 부담되서", en: "Participation cost is burdensome", ja: "参加費が負担になるため") + } + + static var reasonEtc: String { + pick(ko: "기타", en: "Other", ja: "その他") + } + } + } + + enum ServiceCenter { + static var buttonTitle: String { + pick(ko: "보이스온 고객센터", en: "VoiceOn customer center", ja: "VoiceOnカスタマーセンター") + } + + static var talkInquiry: String { + pick(ko: "TALK 문의", en: "Talk inquiry", ja: "TALK お問い合わせ") + } + + static var faqTitle: String { + pick(ko: "자주 묻는 질문", en: "Frequently asked questions", ja: "よくある質問") + } + + static var questionPrefix: String { + pick(ko: "Q", en: "Q", ja: "Q") + } + + static var answerPrefix: String { + pick(ko: "A", en: "A", ja: "A") + } + } } enum User { diff --git a/SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift b/SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift index 758316d..329d457 100644 --- a/SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift +++ b/SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift @@ -20,7 +20,7 @@ struct OrderListAllView: View { .resizable() .frame(width: 20, height: 20) - Text("콘텐츠 보관함") + Text(I18n.MyPage.OrderList.contentStorageTitle) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) } diff --git a/SodaLive/Sources/MyPage/OrderList/OrderListAllViewModel.swift b/SodaLive/Sources/MyPage/OrderList/OrderListAllViewModel.swift index 8ace5eb..474067a 100644 --- a/SodaLive/Sources/MyPage/OrderList/OrderListAllViewModel.swift +++ b/SodaLive/Sources/MyPage/OrderList/OrderListAllViewModel.swift @@ -62,13 +62,13 @@ final class OrderListAllViewModel: 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 } diff --git a/SodaLive/Sources/MyPage/OrderList/OrderListItemView.swift b/SodaLive/Sources/MyPage/OrderList/OrderListItemView.swift index d1c4bcf..1f026f9 100644 --- a/SodaLive/Sources/MyPage/OrderList/OrderListItemView.swift +++ b/SodaLive/Sources/MyPage/OrderList/OrderListItemView.swift @@ -82,7 +82,7 @@ struct OrderListItemView: View { Spacer() - Text(item.orderType == .RENTAL ? "대여중" : "소장중") + Text(item.orderType == .RENTAL ? I18n.MyPage.OrderList.renting : I18n.MyPage.OrderList.owned) .appFont(size: 10.3, weight: .medium) .foregroundColor(item.orderType == .RENTAL ? .white : .black) .padding(.horizontal, 5.3) diff --git a/SodaLive/Sources/MyPage/OrderList/OrderListView.swift b/SodaLive/Sources/MyPage/OrderList/OrderListView.swift index 4a09dd7..937d1d8 100644 --- a/SodaLive/Sources/MyPage/OrderList/OrderListView.swift +++ b/SodaLive/Sources/MyPage/OrderList/OrderListView.swift @@ -13,13 +13,13 @@ struct OrderListView: View { var body: some View { VStack(spacing: 0) { HStack(spacing: 0) { - Text("콘텐츠 보관함") + Text(I18n.MyPage.OrderList.contentStorageTitle) .appFont(size: 18, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) Spacer() - Text("전체보기") + Text(I18n.Common.viewAll) .appFont(size: 11, weight: .medium) .foregroundColor(Color.graybb) .onTapGesture { diff --git a/SodaLive/Sources/MyPage/Point/PointStatusView.swift b/SodaLive/Sources/MyPage/Point/PointStatusView.swift index 3fa929d..e0580cf 100644 --- a/SodaLive/Sources/MyPage/Point/PointStatusView.swift +++ b/SodaLive/Sources/MyPage/Point/PointStatusView.swift @@ -17,7 +17,7 @@ struct PointStatusView: View { BaseView(isLoading: $viewModel.isLoading) { GeometryReader { proxy in VStack(spacing: 0) { - DetailNavigationBar(title: String(localized: "포인트 내역")) { + DetailNavigationBar(title: I18n.MyPage.Point.historyTitle) { AppState.shared.setAppStep(step: .main) } @@ -40,7 +40,7 @@ struct PointStatusView: View { .cornerRadius(16.7) .padding(.top, 13.3) - Text("※ 획득한 포인트는 72시간이 지나면 자동소멸 됩니다.") + Text(I18n.MyPage.Point.expirationNotice) .appFont(size: 13.3, weight: .medium) .foregroundColor(.grayee) .padding(.top, 13.3) @@ -48,7 +48,7 @@ struct PointStatusView: View { HStack(spacing: 0) { VStack(spacing: 0) { Spacer() - Text("받은내역") + Text(I18n.MyPage.Point.rewardHistory) .appFont(size: 13.3, weight: .medium) .foregroundColor(viewModel.currentTab == .reward ? .grayee : .gray77) Spacer() @@ -69,7 +69,7 @@ struct PointStatusView: View { VStack(spacing: 0) { Spacer() - Text("사용내역") + Text(I18n.MyPage.Point.useHistory) .appFont(size: 13.3, weight: .medium) .foregroundColor(viewModel.currentTab == .use ? .grayee : .gray77) Spacer() diff --git a/SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift b/SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift index 63d157a..022849b 100644 --- a/SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift +++ b/SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift @@ -48,13 +48,13 @@ final class PointStatusViewModel: 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 } } @@ -86,13 +86,13 @@ final class PointStatusViewModel: 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 } } @@ -124,13 +124,13 @@ final class PointStatusViewModel: 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 } } diff --git a/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateView.swift b/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateView.swift index a621367..992bc0c 100644 --- a/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateView.swift +++ b/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateView.swift @@ -16,16 +16,16 @@ struct NicknameUpdateView: View { BaseView(isLoading: $viewModel.isLoading) { GeometryReader { proxy in VStack(spacing: 0) { - DetailNavigationBar(title: "프로필 수정") + DetailNavigationBar(title: I18n.MyPage.Main.editProfile) - Text("닉네임 변경으로 인해 피해를 입는 사용자가 지속적으로 발생하여 닉네임 변경을 부득이하게 유료로 전환합니다.") + Text(I18n.MyPage.Nickname.paidChangeNotice) .fixedSize(horizontal: false, vertical: true) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 40, alignment: .leading) .padding(.top, 40) - Text("최초 1회에 한해서 무료로 변경이 가능하고, 그 이후부터는 유료로 전환됩니다.") + Text(I18n.MyPage.Nickname.firstFreeNotice) .fixedSize(horizontal: false, vertical: true) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "dd4500")) @@ -40,7 +40,7 @@ struct NicknameUpdateView: View { .frame(width: screenSize().width - 40) .padding(.top, 40) - Text("중복확인") + Text(I18n.MyPage.Nickname.duplicateCheckAction) .appFont(size: 13.3, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 40) @@ -57,7 +57,7 @@ struct NicknameUpdateView: View { Spacer() - Text(viewModel.price > 0 ? "\(viewModel.price)캔으로 닉네임 변경하기" : "닉네임 변경하기") + Text(viewModel.price > 0 ? I18n.MyPage.Nickname.changeActionWithCans(viewModel.price) : I18n.MyPage.Nickname.changeAction) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.white) .padding(.vertical, 16) diff --git a/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateViewModel.swift b/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateViewModel.swift index 864b3d9..382e8f5 100644 --- a/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateViewModel.swift +++ b/SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateViewModel.swift @@ -56,14 +56,14 @@ final class NicknameUpdateViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError } self.isShowPopup = true } } catch { ERROR_LOG(error.localizedDescription) - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true } } @@ -92,25 +92,25 @@ final class NicknameUpdateViewModel: ObservableObject { if decoded.success { self.isCheckedNickname = true - self.errorMessage = "사용가능한 닉네임 입니다." + self.errorMessage = I18n.MyPage.Nickname.available self.isShowPopup = true } else { 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 } } .store(in: &subscription) } else { - self.errorMessage = "닉네임을 입력하세요." + self.errorMessage = I18n.MyPage.Nickname.empty self.isShowPopup = true } } @@ -138,7 +138,7 @@ final class NicknameUpdateViewModel: ObservableObject { if decoded.success { UserDefaults.set(nickname, forKey: .nickname) - self.errorMessage = "닉네임이 변경되었습니다." + self.errorMessage = I18n.MyPage.Nickname.changed self.isShowPopup = true DispatchQueue.main.asyncAfter(deadline: .now() + 1) { @@ -148,20 +148,20 @@ final class NicknameUpdateViewModel: 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 } } .store(in: &subscription) } else { - self.errorMessage = "닉네임 중복체크를 해주세요." + self.errorMessage = I18n.MyPage.Nickname.duplicateCheckRequired self.isShowPopup = true } } diff --git a/SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift b/SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift index 025e39c..6eb4cb9 100644 --- a/SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift +++ b/SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift @@ -26,7 +26,7 @@ struct ProfileUpdateView: View { func EmailAndPasswordView() -> some View { VStack(spacing: 26.7) { VStack(alignment: .leading, spacing: 0) { - Text("이메일") + Text(I18n.User.emailTitle) .appFont(size: 12, weight: .medium) .foregroundColor(Color.grayee) .padding(.leading, 6.7) @@ -45,7 +45,7 @@ struct ProfileUpdateView: View { HStack(alignment: .bottom, spacing: 13.3) { VStack(alignment: .leading, spacing: 0) { - Text("비밀번호") + Text(I18n.User.passwordTitle) .appFont(size: 12, weight: .medium) .foregroundColor(Color.grayee) .padding(.leading, 6.7) @@ -63,7 +63,7 @@ struct ProfileUpdateView: View { } Button(action: { AppState.shared.setAppStep(step: .modifyPassword) }) { - Text("비밀번호 변경") + Text(I18n.ProfileUpdate.modifyPasswordTitle) .appFont(size: 13.3, weight: .bold) .foregroundColor(Color.white) .padding(.vertical, 13.3) @@ -85,7 +85,7 @@ struct ProfileUpdateView: View { VStack(spacing: 16.7) { HStack(alignment: .bottom, spacing: 13.3) { VStack(alignment: .leading, spacing: 0) { - Text("닉네임") + Text(I18n.ProfileUpdate.nicknameHint) .appFont(size: 12, weight: .medium) .foregroundColor(Color.grayee) .padding(.leading, 6.7) @@ -103,7 +103,7 @@ struct ProfileUpdateView: View { } Button(action: { AppState.shared.setAppStep(step: .changeNickname) }) { - Text("닉네임 변경") + Text(I18n.MyPage.Profile.changeNicknameAction) .appFont(size: 13.3, weight: .bold) .foregroundColor(Color.white) .padding(.vertical, 13.3) @@ -114,7 +114,7 @@ struct ProfileUpdateView: View { } VStack(alignment: .leading, spacing: 13.3) { - Text("성별") + Text(I18n.MyPage.Profile.genderTitle) .appFont(size: 12, weight: .bold) .foregroundColor(Color.button) .padding(.leading, 6.7) @@ -126,7 +126,7 @@ struct ProfileUpdateView: View { .resizable() .frame(width: 20, height: 20) - Text("여자") + Text(I18n.MyPage.Profile.female) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) } @@ -140,7 +140,7 @@ struct ProfileUpdateView: View { .resizable() .frame(width: 20, height: 20) - Text("남자") + Text(I18n.MyPage.Profile.male) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) } @@ -154,7 +154,7 @@ struct ProfileUpdateView: View { .resizable() .frame(width: 20, height: 20) - Text("공개 안 함") + Text(I18n.MyPage.Profile.notPublic) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) } @@ -220,7 +220,7 @@ struct ProfileUpdateView: View { @ViewBuilder func TagSelectView() -> some View { VStack(alignment: .leading, spacing: 13.3) { - Text("관심사") + Text(I18n.MyPage.Profile.interestsTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) @@ -228,7 +228,7 @@ struct ProfileUpdateView: View { hideKeyboard() isShowSelectTagView = true }) { - Text("관심사 선택") + Text(I18n.MyPage.Profile.selectInterestsAction) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.button) .padding(.vertical, 13.7) @@ -275,7 +275,7 @@ struct ProfileUpdateView: View { @ViewBuilder func ContentInputView() -> some View { VStack(alignment: .leading, spacing: 13.3) { - Text("소개글") + Text(I18n.MyPage.Profile.introductionTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) @@ -296,7 +296,7 @@ struct ProfileUpdateView: View { GeometryReader { proxy in ZStack { VStack(spacing: 0) { - DetailNavigationBar(title: "프로필 수정") + DetailNavigationBar(title: I18n.MyPage.Main.editProfile) ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { @@ -364,7 +364,7 @@ struct ProfileUpdateView: View { } if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue { - Text("저장하기") + Text(I18n.MyPage.Profile.saveAction) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.white) .frame(width: screenSize().width - 26.7, height: 50) @@ -394,7 +394,7 @@ struct ProfileUpdateView: View { if UserDefaults.string(forKey: .role) != MemberRole.CREATOR.rawValue { Spacer() - Text("저장하기") + Text(I18n.MyPage.Profile.saveAction) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.white) .frame(width: screenSize().width - 26.7, height: 50) diff --git a/SodaLive/Sources/MyPage/Profile/Tag/MemberTagView.swift b/SodaLive/Sources/MyPage/Profile/Tag/MemberTagView.swift index d0264fb..2cf115d 100644 --- a/SodaLive/Sources/MyPage/Profile/Tag/MemberTagView.swift +++ b/SodaLive/Sources/MyPage/Profile/Tag/MemberTagView.swift @@ -29,7 +29,7 @@ struct MemberTagView: View { VStack(spacing: 0) { HStack(alignment: .top, spacing: 0) { - Text("관심사 선택") + Text(I18n.MyPage.Profile.selectInterestsAction) .appFont(size: 18.3, weight: .bold) .foregroundColor(.white) @@ -87,7 +87,7 @@ struct MemberTagView: View { .padding(.horizontal, 20) .padding(.top, 26.7) - Text("확인") + Text(I18n.Common.confirm) .appFont(size: 18.3, weight: .bold) .foregroundColor(.white) .padding(.vertical, 16) diff --git a/SodaLive/Sources/MyPage/Profile/Tag/MemberTagViewModel.swift b/SodaLive/Sources/MyPage/Profile/Tag/MemberTagViewModel.swift index 3af589a..bf4b609 100644 --- a/SodaLive/Sources/MyPage/Profile/Tag/MemberTagViewModel.swift +++ b/SodaLive/Sources/MyPage/Profile/Tag/MemberTagViewModel.swift @@ -44,13 +44,13 @@ final class MemberTagViewModel: 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 } } diff --git a/SodaLive/Sources/MyPage/ReservationStatus/Cancel/LiveReservationCancelView.swift b/SodaLive/Sources/MyPage/ReservationStatus/Cancel/LiveReservationCancelView.swift index f306cb2..bf10eb8 100644 --- a/SodaLive/Sources/MyPage/ReservationStatus/Cancel/LiveReservationCancelView.swift +++ b/SodaLive/Sources/MyPage/ReservationStatus/Cancel/LiveReservationCancelView.swift @@ -17,20 +17,20 @@ struct LiveReservationCancelView: View { var body: some View { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { - DetailNavigationBar(title: viewModel.isCancelComplete ? "예약취소 확인" : "예약취소") + DetailNavigationBar(title: viewModel.isCancelComplete ? I18n.MyPage.Reservation.Cancel.completedTitle : I18n.MyPage.Reservation.Cancel.title) ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { if let item = viewModel.selectedReservationStatusItem { if viewModel.isCancelComplete { - Text("예약취소가 완료되었습니다.") + Text(I18n.MyPage.Reservation.Cancel.completedMessage) .appFont(size: 20, weight: .bold) .foregroundColor(Color(hex: "a285eb")) .frame(width: screenSize().width - 26.7, alignment: .leading) .padding(.top, 33.3) if item.price > 0 { - Text("결제한 \(item.price)캔이\n환불처리 되었습니다.") + Text(I18n.MyPage.Reservation.Cancel.refundedCanMessage(item.price)) .appFont(size: 20, weight: .medium) .foregroundColor(Color(hex: "eeeeee")) .frame(width: screenSize().width - 26.7, alignment: .leading) @@ -38,7 +38,7 @@ struct LiveReservationCancelView: View { } HStack(spacing: 13.3) { - Text("다른 라이브 예약하기") + Text(I18n.MyPage.Reservation.Cancel.reserveAnotherLiveAction) .appFont(size: 15, weight: .medium) .foregroundColor(Color.button) .padding(.vertical, 16) @@ -56,7 +56,7 @@ struct LiveReservationCancelView: View { AppState.shared.setAppStep(step: .main) } - Text("캔내역 확인하기") + Text(I18n.MyPage.Reservation.Cancel.checkCanHistoryAction) .appFont(size: 15, weight: .medium) .foregroundColor(.white) .padding(.vertical, 16) @@ -105,8 +105,8 @@ struct LiveReservationCancelView: View { Text( item.price > 0 ? - "\(item.price)캔" : - "무료" + I18n.MyPage.Reservation.cansUnit(item.price) : + I18n.MyPage.Reservation.LiveStatus.free ) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "e2e2e2").opacity(0.4)) @@ -124,12 +124,12 @@ struct LiveReservationCancelView: View { .padding(.top, 13.3) VStack(spacing: 13.3) { - Text("예약을 취소하시겠습니까?") + Text(I18n.MyPage.Reservation.Cancel.confirmQuestion) .appFont(size: 20, weight: .bold) .foregroundColor(Color(hex: "a285eb")) .frame(width: screenSize().width - 26.7, alignment: .leading) - Text("예약취소 이유를 선택해주세요. 서비스 개선에 중요한 자료로 활용하겠습니다.") + Text(I18n.MyPage.Reservation.Cancel.reasonDescription) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) .frame(width: screenSize().width - 26.7, alignment: .leading) @@ -154,7 +154,7 @@ struct LiveReservationCancelView: View { if index == viewModel.cancelReasons.count - 1 { VStack(spacing: 6.7) { - TextField("입력해주세요", text: $viewModel.reason) + TextField(I18n.MyPage.Reservation.Cancel.reasonPlaceholder, text: $viewModel.reason) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) @@ -180,12 +180,12 @@ struct LiveReservationCancelView: View { .foregroundColor(Color(hex: "232323")) .padding(.vertical, 20) - Text("취소요청시, 차감했던 캔은 환불처리 됩니다. 수다방 참여인원 제한에 따라 재예약이 불가할 수 있습니다.") + Text(I18n.MyPage.Reservation.Cancel.notice) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "ff5c49")) .frame(width: screenSize().width - 53.4, alignment: .leading) - Text("예약취소") + Text(I18n.MyPage.Reservation.Cancel.requestCancelAction) .appFont(size: 15, weight: .bold) .foregroundColor(.white) .padding(.vertical, 16) @@ -206,7 +206,7 @@ struct LiveReservationCancelView: View { .sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 1) .onAppear { if reservationId <= 0 { - viewModel.errorMessage = "잘못된 예약정보 입니다." + viewModel.errorMessage = I18n.MyPage.Reservation.Cancel.invalidReservationInfo viewModel.isShowPopup = true } else { viewModel.getReservation(reservationId: reservationId) diff --git a/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusItemView.swift b/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusItemView.swift index 563c925..f6573c4 100644 --- a/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusItemView.swift +++ b/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusItemView.swift @@ -50,8 +50,8 @@ struct LiveReservationStatusItemView: View { HStack(spacing: 0) { Text( item.price > 0 ? - "\(item.price)캔" : - "무료" + I18n.MyPage.Reservation.cansUnit(item.price) : + I18n.MyPage.Reservation.LiveStatus.free ) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "e2e2e2").opacity(0.4)) @@ -59,7 +59,7 @@ struct LiveReservationStatusItemView: View { Spacer() if !item.cancelable { - Text("예약 취소 불가") + Text(I18n.MyPage.Reservation.LiveStatus.cancelUnavailable) .appFont(size: 10.7, weight: .light) .foregroundColor(Color(hex: "777777")) } @@ -72,7 +72,7 @@ struct LiveReservationStatusItemView: View { if item.cancelable { Spacer() - Text("예약\n취소") + Text(I18n.MyPage.Reservation.LiveStatus.cancelAction) .appFont(size: 12, weight: .bold) .foregroundColor(Color(hex: "9970ff")) .padding(10.7) diff --git a/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusView.swift b/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusView.swift index ba5f0f8..f1daddc 100644 --- a/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusView.swift +++ b/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusView.swift @@ -14,7 +14,7 @@ struct LiveReservationStatusView: View { var body: some View { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { - DetailNavigationBar(title: "라이브 예약 현황") + DetailNavigationBar(title: I18n.MyPage.Reservation.LiveStatus.title) if viewModel.reservationStatusItems.count > 0 { ScrollView(.vertical, showsIndicators: false) { @@ -26,7 +26,7 @@ struct LiveReservationStatusView: View { .padding(.vertical, 13.3) } } else { - Text("예약한 라이브가 없습니다.") + Text(I18n.MyPage.Reservation.LiveStatus.emptyMessage) .appFont(size: 15, weight: .medium) .foregroundColor(Color(hex: "bbbbbb")) .frame(maxHeight: .infinity) diff --git a/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusViewModel.swift b/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusViewModel.swift index 306aff9..ccac69a 100644 --- a/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusViewModel.swift +++ b/SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusViewModel.swift @@ -20,7 +20,13 @@ final class LiveReservationStatusViewModel: ObservableObject { @Published var reservationStatusItems = [GetLiveReservationResponse]() @Published var selectedReservationStatusItem: GetLiveReservationResponse? - let cancelReasons = ["중요한 개인일정이 생겨서", "다른 라이브에 참여하고 싶어서", "라이브 참여자 중 불편한 사람이 있어서", "참여비용이 부담되서", "기타"] + let cancelReasons = [ + I18n.MyPage.Reservation.Cancel.reasonPersonalSchedule, + I18n.MyPage.Reservation.Cancel.reasonOtherLive, + I18n.MyPage.Reservation.Cancel.reasonUncomfortableParticipant, + I18n.MyPage.Reservation.Cancel.reasonPriceBurden, + I18n.MyPage.Reservation.Cancel.reasonEtc + ] @Published var cancelReasonSelectedIndex = -1 @Published var reason = "" @@ -51,13 +57,13 @@ final class LiveReservationStatusViewModel: 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 } } @@ -89,13 +95,13 @@ final class LiveReservationStatusViewModel: 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 } } @@ -104,7 +110,7 @@ final class LiveReservationStatusViewModel: ObservableObject { func cancelReservation(reservationId: Int) { if cancelReasonSelectedIndex < 0 || (cancelReasonSelectedIndex == self.cancelReasons.count - 1 && self.reason.trimmingCharacters(in: .whitespaces).count <= 0) { - self.errorMessage = "취소이유를 선택해주세요." + self.errorMessage = I18n.MyPage.Reservation.Cancel.reasonRequired self.isShowPopup = true return } @@ -134,13 +140,13 @@ final class LiveReservationStatusViewModel: 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 } } diff --git a/SodaLive/Sources/MyPage/ReservationStatusView.swift b/SodaLive/Sources/MyPage/ReservationStatusView.swift index 2dc0e5a..3f85f10 100644 --- a/SodaLive/Sources/MyPage/ReservationStatusView.swift +++ b/SodaLive/Sources/MyPage/ReservationStatusView.swift @@ -12,7 +12,7 @@ struct ReservationStatusView: View { var body: some View { VStack(alignment: .leading, spacing: 13.3) { - Text("예약현황") + Text(I18n.MyPage.ReservationStatus.title) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) @@ -24,7 +24,7 @@ struct ReservationStatusView: View { .resizable() .frame(width: 20, height: 20) - Text("라이브") + Text(I18n.MyPage.ReservationStatus.live) .appFont(size: 14.7, weight: .bold) .foregroundColor(Color(hex: "3bb9f1")) diff --git a/SodaLive/Sources/MyPage/ServiceCenter/FaqView.swift b/SodaLive/Sources/MyPage/ServiceCenter/FaqView.swift index 6607ff4..40b85ce 100644 --- a/SodaLive/Sources/MyPage/ServiceCenter/FaqView.swift +++ b/SodaLive/Sources/MyPage/ServiceCenter/FaqView.swift @@ -21,7 +21,7 @@ struct FaqView: View { VStack(spacing: 0) { HStack(alignment: .top, spacing: 6.7) { - Text("Q") + Text(I18n.MyPage.ServiceCenter.questionPrefix) .appFont(size: 13.3, weight: .bold) .foregroundColor(Color.button) @@ -41,7 +41,7 @@ struct FaqView: View { if openIndex == index { HStack(alignment: .top, spacing: 6.7) { - Text("A") + Text(I18n.MyPage.ServiceCenter.answerPrefix) .appFont(size: 13.3, weight: .bold) .foregroundColor(Color.button) .padding(.top, 13.3) diff --git a/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterButtonView.swift b/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterButtonView.swift index 922afc3..de879c9 100644 --- a/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterButtonView.swift +++ b/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterButtonView.swift @@ -14,7 +14,7 @@ struct ServiceCenterButtonView: View { .resizable() .frame(width: 26.7, height: 26.7) - Text("보이스온 고객센터") + Text(I18n.MyPage.ServiceCenter.buttonTitle) .appFont(size: 15.3, weight: .bold) .foregroundColor(.white) diff --git a/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterView.swift b/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterView.swift index a060f42..4358012 100644 --- a/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterView.swift +++ b/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterView.swift @@ -14,7 +14,7 @@ struct ServiceCenterView: View { var body: some View { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { - DetailNavigationBar(title: "고객센터") + DetailNavigationBar(title: I18n.MyPage.Category.customerCenter) ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { @@ -23,7 +23,7 @@ struct ServiceCenterView: View { .scaledToFill() .frame(width: 106.7, height: 106.7, alignment: .top) - Text("고객센터") + Text(I18n.MyPage.Category.customerCenter) .appFont(size: 20, weight: .bold) .foregroundColor(.grayee) .padding(.top, 20) @@ -34,7 +34,7 @@ struct ServiceCenterView: View { .scaledToFill() .frame(width: 21, height: 18.8, alignment: .top) - Text("TALK 문의") + Text(I18n.MyPage.ServiceCenter.talkInquiry) .appFont(size: 13.3, weight: .bold) .foregroundColor(.black) } @@ -53,7 +53,7 @@ struct ServiceCenterView: View { .foregroundColor(.gray23) .padding(.vertical, 20) - Text("자주 묻는 질문") + Text(I18n.MyPage.ServiceCenter.faqTitle) .appFont(size: 18.3, weight: .bold) .foregroundColor(.grayee) .frame(maxWidth: .infinity, alignment: .leading) diff --git a/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterViewModel.swift b/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterViewModel.swift index b7cc95a..05f06ae 100644 --- a/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterViewModel.swift +++ b/SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterViewModel.swift @@ -54,13 +54,13 @@ final class ServiceCenterViewModel: 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 } } @@ -93,13 +93,13 @@ final class ServiceCenterViewModel: 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 } } diff --git a/docs/20260331_하드코딩텍스트_I18n통일계획.md b/docs/20260331_하드코딩텍스트_I18n통일계획.md index 9da1291..418dc40 100644 --- a/docs/20260331_하드코딩텍스트_I18n통일계획.md +++ b/docs/20260331_하드코딩텍스트_I18n통일계획.md @@ -435,31 +435,31 @@ - [x] `SodaLive/Sources/MyPage/OrderList/OrderListAllInnerView.swift` #### Group 3 (21-30) -- [ ] `SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift` -- [ ] `SodaLive/Sources/MyPage/OrderList/OrderListAllViewModel.swift` -- [ ] `SodaLive/Sources/MyPage/OrderList/OrderListItemView.swift` -- [ ] `SodaLive/Sources/MyPage/OrderList/OrderListView.swift` -- [ ] `SodaLive/Sources/MyPage/Point/PointStatusView.swift` -- [ ] `SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift` -- [ ] `SodaLive/Sources/MyPage/Point/Use/PointUseStatusView.swift` -- [ ] `SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateView.swift` -- [ ] `SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateViewModel.swift` -- [ ] `SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift` +- [x] `SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift` +- [x] `SodaLive/Sources/MyPage/OrderList/OrderListAllViewModel.swift` +- [x] `SodaLive/Sources/MyPage/OrderList/OrderListItemView.swift` +- [x] `SodaLive/Sources/MyPage/OrderList/OrderListView.swift` +- [x] `SodaLive/Sources/MyPage/Point/PointStatusView.swift` +- [x] `SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift` +- [x] `SodaLive/Sources/MyPage/Point/Use/PointUseStatusView.swift` +- [x] `SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateView.swift` +- [x] `SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateViewModel.swift` +- [x] `SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift` #### Group 4 (31-40) -- [ ] `SodaLive/Sources/MyPage/Profile/Tag/MemberTagView.swift` -- [ ] `SodaLive/Sources/MyPage/Profile/Tag/MemberTagViewModel.swift` -- [ ] `SodaLive/Sources/MyPage/ReservationStatus/Cancel/LiveReservationCancelView.swift` -- [ ] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusItemView.swift` -- [ ] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusView.swift` -- [ ] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusViewModel.swift` -- [ ] `SodaLive/Sources/MyPage/ReservationStatusView.swift` -- [ ] `SodaLive/Sources/MyPage/ServiceCenter/FaqView.swift` -- [ ] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterButtonView.swift` -- [ ] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterView.swift` +- [x] `SodaLive/Sources/MyPage/Profile/Tag/MemberTagView.swift` +- [x] `SodaLive/Sources/MyPage/Profile/Tag/MemberTagViewModel.swift` +- [x] `SodaLive/Sources/MyPage/ReservationStatus/Cancel/LiveReservationCancelView.swift` +- [x] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusItemView.swift` +- [x] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusView.swift` +- [x] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusViewModel.swift` +- [x] `SodaLive/Sources/MyPage/ReservationStatusView.swift` +- [x] `SodaLive/Sources/MyPage/ServiceCenter/FaqView.swift` +- [x] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterButtonView.swift` +- [x] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterView.swift` #### Group 5 (41-41) -- [ ] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterViewModel.swift` +- [x] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterViewModel.swift` ### Notification (2) - [x] `SodaLive/Sources/Notification/List/PushNotificationListItemView.swift` @@ -830,3 +830,39 @@ - LSP 진단: SourceKit 단독 해석 환경에서 외부 모듈/프로젝트 심볼 미해결 오류(`Bootpay`, `Kingfisher`, `I18n` 등)가 보고되나, 동일 변경셋은 `xcodebuild` 실컴파일 통과로 검증 완료. - 빌드 검증: `SodaLive`, `SodaLive-dev` Debug 빌드 모두 성공(`** BUILD SUCCEEDED **`). - 테스트 검증: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 테스트 액션 미구성 확인(코드 실패 아님, 스킴 제약). + +### 16차 구현 (MyPage 모듈 Group 3~5, 21개 파일 처리, 2026-03-31) +- 무엇/왜/어떻게: + - 무엇: `변경 대상 파일 전체 목록`의 `MyPage` Group 3~5(21개 파일)에서 사용자 노출 하드코딩 문구를 `I18n.*` 참조로 전환하고 체크박스를 완료 처리. + - 왜: OrderList/Point/Profile/ReservationStatus/ServiceCenter 구간에 뷰 리터럴과 ViewModel 공통 오류 문구가 남아 있어 `I18n.swift` 단일 접근 원칙과 불일치했기 때문. + - 어떻게: explore/librarian 병렬 탐색 + `grep`/`ast_grep_search` 직접 검증으로 런타임 노출 문자열만 추출한 뒤, `I18n.swift`에 `I18n.MyPage` 하위 네임스페이스(`OrderList`, `Point`, `Nickname`, `Profile`, `ReservationStatus`, `Reservation`, `ServiceCenter`)를 추가하고 호출부를 일괄 치환. +- 실행 명령/도구: + - `task(subagent_type="explore", ...)` x2 (`bg_df9bea1f`, `bg_a7a90096`) + - `task(subagent_type="librarian", ...)` x2 (`bg_a3ce40e4`, `bg_0d380792`) + - `background_output(task_id=...)` x4 (위 4개 task 결과 수집) + - `grep("\"[^\"]*[가-힣][^\"]*\"", include=*.swift, path=SodaLive/Sources/MyPage/**)` + - `grep("String\\(localized:|NSLocalizedString\\(|LocalizedStringKey\\(", include=*.swift, path=SodaLive/Sources/MyPage)` + - `ast_grep_search(pattern="Text(\"$TEXT\")", lang=swift, paths=[SodaLive/Sources/MyPage/{OrderList,Point,Profile,ReservationStatus,ServiceCenter}])` + - `bash: rg -n ...` (`command not found` 확인) + - `lsp_diagnostics(filePath=변경 파일 전체)` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` +- 결과: + - `I18n.swift`에 `I18n.MyPage` Group 3~5 대응 키셋 추가/확장 완료. + - 추가: `OrderList`, `Point`, `Nickname`, `Profile`, `ReservationStatus`, `Reservation.LiveStatus`, `Reservation.Cancel`, `ServiceCenter`. + - 재사용: 공통 실패 문구는 `I18n.Common.commonError`로 통합. + - 치환 완료 파일(20개): + - `OrderListAllView`, `OrderListAllViewModel`, `OrderListItemView`, `OrderListView` + - `PointStatusView`, `PointStatusViewModel` + - `NicknameUpdateView`, `NicknameUpdateViewModel`, `ProfileUpdateView` + - `MemberTagView`, `MemberTagViewModel` + - `LiveReservationCancelView`, `LiveReservationStatusItemView`, `LiveReservationStatusView`, `LiveReservationStatusViewModel`, `ReservationStatusView` + - `FaqView`, `ServiceCenterButtonView`, `ServiceCenterView`, `ServiceCenterViewModel` + - 점검만 수행(실치환 없음, 체크 완료): `PointUseStatusView.swift` (런타임 노출 하드코딩 문자열 없음). + - 대상 재탐지 결과, Group 3~5 영역의 잔여 한글 리터럴은 Preview 샘플 데이터(`"여행"`, `"질문1"` 등)만 존재. + - Group 3~5 체크박스 21개 `- [x]` 완료 반영. + - LSP 진단: SourceKit 단독 해석 환경에서 외부 모듈/프로젝트 심볼 미해결 오류(`Kingfisher`, `RichText`, `AppState` 등)가 보고되나, 동일 변경셋은 `xcodebuild` 실컴파일 통과로 검증 완료. + - 빌드 검증: `SodaLive`, `SodaLive-dev` Debug 빌드 모두 성공(`** BUILD SUCCEEDED **`). + - 테스트 검증: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 테스트 액션 미구성 확인(코드 실패 아님, 스킴 제약).