From 982a17bb41dfebb0605ecda0a5a7a59125dbc45a Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Thu, 18 Dec 2025 15:25:13 +0900 Subject: [PATCH] =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=AC=B8=EA=B5=AC=EB=A5=BC=20I18n=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 라이브 생성 화면 문구를 다국어 리소스로 통합한다.\n공지 입력 검증을 5자 이상으로 적용한다. --- SodaLive/Sources/I18n/I18n.swift | 38 +++++++++++++++++++ .../Live/Room/Create/LiveRoomCreateView.swift | 18 ++++----- .../Room/Create/LiveRoomCreateViewModel.swift | 38 +++++++++---------- .../Create/Tag/LiveRoomCreateTagView.swift | 4 +- 4 files changed, 67 insertions(+), 31 deletions(-) diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index 6c5160b..e4d7895 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -33,6 +33,44 @@ enum I18n { static var menu1: String { pick(ko: "메뉴 1", en: "Menu 1", ja: "メニュー1") } static var menu2: String { pick(ko: "메뉴 2", en: "Menu 2", ja: "メニュー2") } static var menu3: String { pick(ko: "메뉴 3", en: "Menu 3", ja: "メニュー3") } + + static var needMenu1First: String { pick(ko: "메뉴 1을 먼저 설정하세요", en: "Please set Menu 1 first", ja: "まずメニュー1を設定してください") } + static var needMenu1And2First: String { pick(ko: "메뉴 1과 메뉴 2를 먼저 설정하세요", en: "Please set Menu 1 and Menu 2 first", ja: "まずメニュー1とメニュー2を設定してください") } + } + + enum CreateLive { + // 라이브 공지 입력 힌트 + static var noticePlaceholder: String { + pick(ko: "라이브 공지를 입력하세요", en: "Enter live notice", ja: "ライブのお知らせを入力してください") + } + + // 시간 설정 + static var startNow: String { pick(ko: "지금 즉시", en: "Start now", ja: "今すぐ開始") } + static var schedule: String { pick(ko: "예약 설정", en: "Schedule", ja: "予約設定") } + + // 공개 범위 + static var publicRoom: String { pick(ko: "공개", en: "Public", ja: "公開") } + static var privateRoom: String { pick(ko: "비공개", en: "Private", ja: "非公開") } + + // 참여 가능 여부 + static var joinAllowed: String { pick(ko: "가능", en: "Allowed", ja: "可能") } + static var joinNotAllowed: String { pick(ko: "불가능", en: "Not allowed", ja: "不可") } + + // 연령 제한 + static var allAges: String { pick(ko: "전체 연령", en: "All ages", ja: "全年齢") } + static var over19: String { pick(ko: "19세 이상", en: "19+", ja: "19歳以上") } + + // 최근 데이터 관련 토스트/알림 + static var recentDataLoaded: String { pick(ko: "최근데이터를 불러왔습니다.", en: "Recent data has been loaded.", ja: "最新データを読み込みました。") } + static var recentDataLoadFailed: String { pick(ko: "최근데이터를 불러오지 못했습니다.\n다시 시도해 주세요.", en: "Failed to load recent data.\nPlease try again.", ja: "最新データを読み込めませんでした。\nもう一度お試しください。") } + static var createLiveFailedGeneric: String { pick(ko: "라이브를 만들지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다.", en: "Could not create the live.\nPlease try again.\nIf the problem persists, please contact customer support.", ja: "ライブを作成できませんでした。\nもう一度お試しください。\n問題が続く場合はカスタマーサポートにお問い合わせください。") } + + // 검증 에러 메시지 + static var selectCoverImage: String { pick(ko: "커버이미지를 선택해주세요.", en: "Please select a cover image.", ja: "カバー画像を選択してください。") } + static var enterTitle: String { pick(ko: "제목을 입력해 주세요.", en: "Please enter a title.", ja: "タイトルを入力してください。") } + static var enterNoticeMin5: String { pick(ko: "공지를 5자 이상 입력해주세요.", en: "Please enter at least 5 characters for the notice.", ja: "お知らせは5文字以上で入力してください。") } + static var enterPeopleRange: String { pick(ko: "인원을 3~999명 사이로 입력해주세요.", en: "Please enter the number of people between 3 and 999.", ja: "参加人数は3〜999の範囲で入力してください。") } + static var enterPassword6: String { pick(ko: "방 입장 비밀번호 6자리를 입력해 주세요.", en: "Please enter a 6-digit room entry password.", ja: "入室パスワード(6桁)を入力してください。") } } enum CreateContent { diff --git a/SodaLive/Sources/Live/Room/Create/LiveRoomCreateView.swift b/SodaLive/Sources/Live/Room/Create/LiveRoomCreateView.swift index 3cf8f0b..95ca3d5 100644 --- a/SodaLive/Sources/Live/Room/Create/LiveRoomCreateView.swift +++ b/SodaLive/Sources/Live/Room/Create/LiveRoomCreateView.swift @@ -161,10 +161,10 @@ struct LiveRoomCreateView: View { .frame(width: screenSize().width - 26.7, alignment: .leading) HStack(spacing: 13.3) { - SelectedButtonView(title: "가능", isActive: true, isSelected: viewModel.isAvailableJoinCreator) + SelectedButtonView(title: I18n.CreateLive.joinAllowed, isActive: true, isSelected: viewModel.isAvailableJoinCreator) .onTapGesture { viewModel.isAvailableJoinCreator = true } - SelectedButtonView(title: "불가능", isActive: true, isSelected: !viewModel.isAvailableJoinCreator) + SelectedButtonView(title: I18n.CreateLive.joinNotAllowed, isActive: true, isSelected: !viewModel.isAvailableJoinCreator) .onTapGesture { viewModel.isAvailableJoinCreator = false } } } @@ -374,7 +374,7 @@ struct LiveRoomCreateView: View { TextViewWrapper( text: $viewModel.content, - placeholder: viewModel.placeholder, + placeholder: I18n.CreateLive.noticePlaceholder, textColorHex: "eeeeee", backgroundColorHex: "303030" ) @@ -394,13 +394,13 @@ struct LiveRoomCreateView: View { HStack(spacing: 13.3) { TimeSettingSelectButton( - title: "지금 즉시", + title: I18n.CreateLive.startNow, timeSettingMode: .NOW, buttonWidth: (screenSize().width - 40) / 2 ) TimeSettingSelectButton( - title: "예약 설정", + title: I18n.CreateLive.schedule, timeSettingMode: .RESERVATION, buttonWidth: (screenSize().width - 40) / 2 ) @@ -587,13 +587,13 @@ struct LiveRoomCreateView: View { HStack(spacing: 13.3) { RoomTypeSelectButton( - title: "공개", + title: I18n.CreateLive.publicRoom, type: .OPEN, buttonWidth: (screenSize().width - 40) / 2 ) RoomTypeSelectButton( - title: "비공개", + title: I18n.CreateLive.privateRoom, type: .PRIVATE, buttonWidth: (screenSize().width - 40) / 2 ) @@ -663,13 +663,13 @@ struct LiveRoomCreateView: View { HStack(spacing: 13.3) { AdultSettingSelectButton( - title: "전체 연령", + title: I18n.CreateLive.allAges, isAdult: false, buttonWidth: (screenSize().width - 40) / 2 ) AdultSettingSelectButton( - title: "19세 이상", + title: I18n.CreateLive.over19, isAdult: true, buttonWidth: (screenSize().width - 40) / 2 ) diff --git a/SodaLive/Sources/Live/Room/Create/LiveRoomCreateViewModel.swift b/SodaLive/Sources/Live/Room/Create/LiveRoomCreateViewModel.swift index 1c8d541..2974f09 100644 --- a/SodaLive/Sources/Live/Room/Create/LiveRoomCreateViewModel.swift +++ b/SodaLive/Sources/Live/Room/Create/LiveRoomCreateViewModel.swift @@ -107,8 +107,6 @@ final class LiveRoomCreateViewModel: ObservableObject { var coverImagePath: String? = nil - let placeholder = "라이브 공지를 입력하세요" - func getRecentInfo() { isLoading = true @@ -137,7 +135,7 @@ final class LiveRoomCreateViewModel: ObservableObject { self.coverImagePath = data.coverImagePath self.numberOfPeople = String(data.numberOfPeople) - self.errorMessage = "최근데이터를 불러왔습니다." + self.errorMessage = I18n.CreateLive.recentDataLoaded self.isShowPopup = true self.isShowGetRecentInfoButton = false } @@ -146,7 +144,7 @@ final class LiveRoomCreateViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "최근데이터를 불러오지 못했습니다.\n다시 시도해 주세요." + self.errorMessage = I18n.CreateLive.recentDataLoadFailed } self.isShowPopup = true @@ -155,7 +153,7 @@ final class LiveRoomCreateViewModel: ObservableObject { } catch { print(error) DispatchQueue.main.async { - self.errorMessage = "최근데이터를 불러오지 못했습니다.\n다시 시도해 주세요." + self.errorMessage = I18n.CreateLive.recentDataLoadFailed self.isShowPopup = true } } @@ -170,7 +168,7 @@ final class LiveRoomCreateViewModel: ObservableObject { var request = CreateLiveRoomRequest( title: title, - content: content.trimmingCharacters(in: .whitespacesAndNewlines) != placeholder ? content : "", + content: content.trimmingCharacters(in: .whitespacesAndNewlines) != I18n.CreateLive.noticePlaceholder ? content : "", coverImageUrl: coverImagePath, tags: tags, numberOfPeople: Int(numberOfPeople)!, @@ -231,19 +229,19 @@ final class LiveRoomCreateViewModel: 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 = "라이브을 만들지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.CreateLive.createLiveFailedGeneric self.isShowPopup = true self.isLoading = false } @@ -252,13 +250,13 @@ final class LiveRoomCreateViewModel: ObservableObject { func selectMenuPreset(selectedMenuPreset: SelectedMenu) { if menuList.isEmpty && (selectedMenuPreset == .MENU_2 || selectedMenuPreset == .MENU_3) { - errorMessage = "메뉴 1을 먼저 설정하세요" + errorMessage = I18n.MissionMenu.needMenu1First isShowPopup = true return } if menuList.count == 1 && selectedMenuPreset == .MENU_3 { - errorMessage = "메뉴 1과 메뉴 2를 먼저 설정하세요" + errorMessage = I18n.MissionMenu.needMenu1And2First isShowPopup = true return } @@ -304,13 +302,13 @@ final class LiveRoomCreateViewModel: 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 } } @@ -319,32 +317,32 @@ final class LiveRoomCreateViewModel: ObservableObject { private func validate() -> Bool { if coverImage == nil && coverImagePath == nil { - self.errorMessage = "커버이미지를 선택해주세요." + self.errorMessage = I18n.CreateLive.selectCoverImage self.isShowPopup = true return false } if title.trimmingCharacters(in: .whitespaces).isEmpty { - self.errorMessage = "제목을 입력해 주세요." + self.errorMessage = I18n.CreateLive.enterTitle self.isShowPopup = true return false } - let notice = content.trimmingCharacters(in: .whitespacesAndNewlines) != placeholder ? content : "" - if notice.isEmpty && notice.count < 5 { - self.errorMessage = "공지를 5자 이상 입력해주세요." + let notice = content.trimmingCharacters(in: .whitespacesAndNewlines) != I18n.CreateLive.noticePlaceholder ? content : "" + if notice.isEmpty || notice.count < 5 { + self.errorMessage = I18n.CreateLive.enterNoticeMin5 self.isShowPopup = true return false } guard let numberOfPeople = Int(numberOfPeople), (numberOfPeople >= 3 && numberOfPeople <= 999) else { - self.errorMessage = "인원을 3~999명 사이로 입력해주세요." + self.errorMessage = I18n.CreateLive.enterPeopleRange self.isShowPopup = true return false } if roomType == .PRIVATE && (password.trimmingCharacters(in: .whitespaces).isEmpty || password.count != 6) { - self.errorMessage = "방 입장 비밀번호 6자리를 입력해 주세요." + self.errorMessage = I18n.CreateLive.enterPassword6 self.isShowPopup = true return false } diff --git a/SodaLive/Sources/Live/Room/Create/Tag/LiveRoomCreateTagView.swift b/SodaLive/Sources/Live/Room/Create/Tag/LiveRoomCreateTagView.swift index c074012..dad16c3 100644 --- a/SodaLive/Sources/Live/Room/Create/Tag/LiveRoomCreateTagView.swift +++ b/SodaLive/Sources/Live/Room/Create/Tag/LiveRoomCreateTagView.swift @@ -79,7 +79,7 @@ struct LiveRoomCreateTagView: View { .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor( selectedTags.contains(tag.tag) ? - Color(hex: "9970ff") : + Color.button : Color(hex: "bbbbbb") ) } @@ -105,7 +105,7 @@ struct LiveRoomCreateTagView: View { .foregroundColor(.white) .padding(.vertical, 16) .frame(width: screenSize().width - 26.7) - .background(Color(hex: "9970ff")) + .background(Color.button) .cornerRadius(10) .padding(.bottom, 26.7) .onTapGesture {