From 038d66e363d4671503098bba1223385af5ccfc80 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Wed, 1 Apr 2026 14:40:37 +0900 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=ED=83=90=EC=83=89=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20?= =?UTF-8?q?=EB=AC=B8=EA=B5=AC=EB=A5=BC=20I18n=20=ED=82=A4=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Explorer/ExplorerSectionView.swift | 2 +- SodaLive/Sources/Explorer/ExplorerView.swift | 4 +- .../Sources/Explorer/ExplorerViewModel.swift | 8 +- .../ChannelDonationItemView.swift | 8 +- .../CreatorCommunityCommentItemView.swift | 10 +- .../CreatorCommunityCommentListView.swift | 6 +- ...CreatorCommunityCommentListViewModel.swift | 18 +- .../CreatorCommunityCommentReplyView.swift | 4 +- ...reatorCommunityCommentReplyViewModel.swift | 18 +- .../Comment/CreatorCommunityCommentView.swift | 6 +- .../All/CreatorCommunityAllItemLockView.swift | 2 +- .../All/CreatorCommunityAllView.swift | 2 +- .../All/CreatorCommunityAllViewModel.swift | 28 +- .../All/CreatorCommunityMenuView.swift | 8 +- .../CreatorCommunityMediaPlayerManager.swift | 2 +- .../CreatorCommunityMoreItemView.swift | 2 +- .../CreatorCommunityNoPostsItemView.swift | 6 +- .../Modify/CreatorCommunityModifyView.swift | 32 +- .../CreatorCommunityModifyViewModel.swift | 16 +- .../CreatorCommunityRecordingVoiceView.swift | 12 +- .../Write/CreatorCommunitySoundManager.swift | 12 +- .../Write/CreatorCommunityWriteView.swift | 42 +-- .../CreatorCommunityWriteViewModel.swift | 18 +- .../FanTalk/UserProfileFanTalkAllView.swift | 8 +- .../UserProfileFanTalkCheersItemView.swift | 18 +- .../FanTalk/UserProfileFanTalkView.swift | 10 +- .../FanTalk/UserProfileFanTalkViewModel.swift | 38 +-- .../FollowerList/FollowerListView.swift | 4 +- .../Series/UserProfileSeriesView.swift | 4 +- .../UserProfileActivitySummaryView.swift | 8 +- .../Profile/UserProfileContentView.swift | 10 +- .../Profile/UserProfileDonationAllView.swift | 24 +- .../UserProfileDonationAllViewModel.swift | 12 +- .../Profile/UserProfileDonationView.swift | 4 +- .../Profile/UserProfileIntroduceView.swift | 2 +- .../Profile/UserProfileLiveView.swift | 6 +- .../Profile/UserProfileViewModel.swift | 6 +- SodaLive/Sources/I18n/I18n.swift | 322 ++++++++++++++++++ docs/20260331_하드코딩텍스트_I18n통일계획.md | 104 +++--- 39 files changed, 599 insertions(+), 247 deletions(-) diff --git a/SodaLive/Sources/Explorer/ExplorerSectionView.swift b/SodaLive/Sources/Explorer/ExplorerSectionView.swift index c0f91f3..b8dbba7 100644 --- a/SodaLive/Sources/Explorer/ExplorerSectionView.swift +++ b/SodaLive/Sources/Explorer/ExplorerSectionView.swift @@ -51,7 +51,7 @@ struct ExplorerSectionView: View { .appFont(size: 14.7, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) - Text("※ 인기 크리에이터의 순위는 매주 업데이트됩니다.") + Text(I18n.Explorer.rankingWeeklyUpdateNotice) .appFont(size: 13.3, weight: .light) .foregroundColor(Color(hex: "bbbbbb")) } diff --git a/SodaLive/Sources/Explorer/ExplorerView.swift b/SodaLive/Sources/Explorer/ExplorerView.swift index 4094ca9..18e4fb3 100644 --- a/SodaLive/Sources/Explorer/ExplorerView.swift +++ b/SodaLive/Sources/Explorer/ExplorerView.swift @@ -18,7 +18,7 @@ struct ExplorerView: View { HStack(spacing: 0) { Image("ic_title_search_black") - TextField("채널명을 입력해 보세요", text: $viewModel.channel) + TextField(I18n.Explorer.searchChannelPlaceholder, text: $viewModel.channel) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) @@ -72,7 +72,7 @@ struct ExplorerView: View { } } } else { - Text("검색 결과가 없습니다.") + Text(I18n.Explorer.searchEmptyResult) .appFont(size: 18.3, weight: .medium) .foregroundColor(.white) .padding(.top, 40) diff --git a/SodaLive/Sources/Explorer/ExplorerViewModel.swift b/SodaLive/Sources/Explorer/ExplorerViewModel.swift index 1f669c4..a4cd376 100644 --- a/SodaLive/Sources/Explorer/ExplorerViewModel.swift +++ b/SodaLive/Sources/Explorer/ExplorerViewModel.swift @@ -62,13 +62,13 @@ final class ExplorerViewModel: 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 } } @@ -98,13 +98,13 @@ final class ExplorerViewModel: 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/Explorer/Profile/ChannelDonation/ChannelDonationItemView.swift b/SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationItemView.swift index f2d1534..d5b4792 100644 --- a/SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationItemView.swift @@ -115,11 +115,15 @@ private extension ChannelDonationItemView { } func highlightedMessageText(_ message: String) -> Text { - let plainCanToken = "\(item.can)캔" - let commaCanToken = "\(item.can.comma())캔" + let plainCanToken = "\(item.can)\(I18n.Explorer.canUnitCompact)" + let commaCanToken = "\(item.can.comma())\(I18n.Explorer.canUnitCompact)" + let plainCanTokenWithSpace = "\(item.can)\(I18n.Explorer.canUnitWithSpace)" + let commaCanTokenWithSpace = "\(item.can.comma())\(I18n.Explorer.canUnitWithSpace)" let range = message.range(of: commaCanToken) ?? message.range(of: plainCanToken) + ?? message.range(of: commaCanTokenWithSpace) + ?? message.range(of: plainCanTokenWithSpace) guard let range else { return Text(message).foregroundColor(Color(hex: "CFD8DC")) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift index 17b7722..59b7af0 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift @@ -45,7 +45,7 @@ struct CreatorCommunityCommentItemView: View { .foregroundColor(Color.gray90) if commentItem.isSecret { - Text("비밀댓글") + Text(I18n.Explorer.secretComment) .appFont(size: 11, weight: .medium) .foregroundColor(Color.grayee) .padding(.horizontal, 4) @@ -72,7 +72,7 @@ struct CreatorCommunityCommentItemView: View { HStack(spacing: 0) { if isModeModify { HStack(spacing: 0) { - TextField("댓글을 입력해 보세요.", text: $comment) + TextField(I18n.Explorer.commentInputPlaceholder, text: $comment) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) @@ -119,7 +119,7 @@ struct CreatorCommunityCommentItemView: View { parentComment: commentItem ) ) { - Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기") + Text(commentItem.replyCount > 0 ? I18n.Explorer.replyCount(commentItem.replyCount) : I18n.Explorer.replyWrite) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.button) } @@ -140,7 +140,7 @@ struct CreatorCommunityCommentItemView: View { if isShowPopupMenu { VStack(spacing: 10) { if commentItem.writerId == UserDefaults.int(forKey: .userId) { - Text("수정") + Text(I18n.Explorer.edit) .appFont(size: 14, weight: .medium) .foregroundColor(Color(hex: "777777")) .onTapGesture { @@ -152,7 +152,7 @@ struct CreatorCommunityCommentItemView: View { if creatorId == UserDefaults.int(forKey: .userId) || commentItem.writerId == UserDefaults.int(forKey: .userId) { - Text("삭제") + Text(I18n.Common.delete) .appFont(size: 14, weight: .medium) .foregroundColor(Color(hex: "777777")) .onTapGesture { diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift index 8717f7e..6e5e66d 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift @@ -29,7 +29,7 @@ struct CreatorCommunityCommentListView: View { ZStack { VStack(spacing: 0) { HStack(spacing: 0) { - Text("댓글") + Text(I18n.RankingSort.comments) .appFont(size: 14.7, weight: .medium) .foregroundColor(.white) .padding(.leading, 13.3) @@ -65,7 +65,7 @@ struct CreatorCommunityCommentListView: View { viewModel.isSecret.toggle() } - Text("비밀댓글") + Text(I18n.Explorer.secretComment) .appFont(size: 12, weight: .medium) .foregroundColor(viewModel.isSecret ? Color.button : Color.grayee) .onTapGesture { @@ -84,7 +84,7 @@ struct CreatorCommunityCommentListView: View { .clipShape(Circle()) HStack(spacing: 0) { - TextField("댓글을 입력해 보세요.", text: $viewModel.comment) + TextField(I18n.Explorer.commentInputPlaceholder, text: $viewModel.comment) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListViewModel.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListViewModel.swift index 9851784..e8e09ce 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListViewModel.swift @@ -60,13 +60,13 @@ final class CreatorCommunityCommentListViewModel: 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 } @@ -108,13 +108,13 @@ final class CreatorCommunityCommentListViewModel: 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 } } @@ -129,13 +129,13 @@ final class CreatorCommunityCommentListViewModel: ObservableObject { isActive: Bool? = nil ) { if comment == nil && isActive == nil { - errorMessage = "변경사항이 없습니다." + errorMessage = I18n.Explorer.noChanges isShowPopup = true return } - + if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty { - errorMessage = "내용을 입력하세요." + errorMessage = I18n.Explorer.inputContentWithPeriod isShowPopup = true return } @@ -181,14 +181,14 @@ final class CreatorCommunityCommentListViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError } self.isShowPopup = true } } catch { self.isLoading = false - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true } } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift index 1874f9d..f527e2c 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift @@ -29,7 +29,7 @@ struct CreatorCommunityCommentReplyView: View { HStack(spacing: 6.7) { Image("ic_back") - Text("답글") + Text(I18n.Explorer.replyTitle) .appFont(size: 14.7, weight: .medium) .foregroundColor(.white) @@ -55,7 +55,7 @@ struct CreatorCommunityCommentReplyView: View { .clipShape(Circle()) HStack(spacing: 0) { - TextField("댓글을 입력해 보세요.", text: $viewModel.comment) + TextField(I18n.Explorer.commentInputPlaceholder, text: $viewModel.comment) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyViewModel.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyViewModel.swift index 3e78c9e..e5273ba 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyViewModel.swift @@ -61,13 +61,13 @@ final class CreatorCommunityCommentReplyViewModel: 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 } @@ -108,13 +108,13 @@ final class CreatorCommunityCommentReplyViewModel: 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 } } @@ -129,13 +129,13 @@ final class CreatorCommunityCommentReplyViewModel: ObservableObject { isActive: Bool? = nil ) { if comment == nil && isActive == nil { - errorMessage = "변경사항이 없습니다." + errorMessage = I18n.Explorer.noChanges isShowPopup = true return } - + if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty { - errorMessage = "내용을 입력하세요." + errorMessage = I18n.Explorer.inputContentWithPeriod isShowPopup = true return } @@ -181,14 +181,14 @@ final class CreatorCommunityCommentReplyViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError } self.isShowPopup = true } } catch { self.isLoading = false - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true } } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentView.swift index f1f09be..10baa84 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentView.swift @@ -21,7 +21,7 @@ struct CreatorCommunityCommentView: View { var body: some View { VStack(alignment: .leading, spacing: 11) { HStack(spacing: 5.3) { - Text("댓글") + Text(I18n.RankingSort.comments) .appFont(size: 12, weight: .medium) .foregroundColor(.white) @@ -39,7 +39,7 @@ struct CreatorCommunityCommentView: View { isSecret.toggle() } - Text("비밀댓글") + Text(I18n.Explorer.secretComment) .appFont(size: 12, weight: .medium) .foregroundColor(isSecret ? Color.button : Color.grayee) .onTapGesture { @@ -81,7 +81,7 @@ struct CreatorCommunityCommentView: View { .clipShape(Circle()) HStack(spacing: 0) { - TextField("댓글을 입력해 보세요.", text: $comment) + TextField(I18n.Explorer.commentInputPlaceholder, text: $comment) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllItemLockView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllItemLockView.swift index 1b2c8c2..990a90f 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllItemLockView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllItemLockView.swift @@ -16,7 +16,7 @@ struct CreatorCommunityAllItemLockView: View { VStack(spacing: 26.7) { Image("ic_lock_bb") - Text("\(price)캔으로 게시글 보기") + Text(I18n.Explorer.viewPostWithCans(price)) .appFont(size: 12, weight: .bold) .foregroundColor(Color.button) .padding(.horizontal, 21) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift index 3498c3e..edd7d92 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift @@ -31,7 +31,7 @@ struct CreatorCommunityAllView: View { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { DetailNavigationBar( - title: "커뮤니티", + title: I18n.Explorer.communityTitle, backAction: { if isGridMode { AppState.shared.back() diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllViewModel.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllViewModel.swift index ab0f486..acc2452 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllViewModel.swift @@ -103,13 +103,13 @@ class CreatorCommunityAllViewModel: 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 } @@ -189,13 +189,13 @@ class CreatorCommunityAllViewModel: 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 } @@ -227,12 +227,12 @@ class CreatorCommunityAllViewModel: 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 } } @@ -269,7 +269,7 @@ class CreatorCommunityAllViewModel: ObservableObject { let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) if decoded.success { - self.errorMessage = "삭제되었습니다" + self.errorMessage = I18n.Explorer.deleted self.isShowPopup = true self.page = 1 @@ -279,19 +279,19 @@ class CreatorCommunityAllViewModel: 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계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true self.isLoading = false } @@ -332,13 +332,13 @@ class CreatorCommunityAllViewModel: 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 } @@ -378,13 +378,13 @@ class CreatorCommunityAllViewModel: 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/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift index 0b71df2..c40b8f8 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift @@ -33,7 +33,7 @@ struct CreatorCommunityMenuView: View { HStack(spacing: 13.3) { Image(isFixed ? "ic_pin_cancel" : "ic_pin") - Text(isFixed ? "고정 해제" : "최상단에 고정") + Text(isFixed ? I18n.Explorer.pinRelease : I18n.Explorer.pinToTop) .appFont(size: 16.7, weight: .medium) .foregroundColor(.white) @@ -50,7 +50,7 @@ struct CreatorCommunityMenuView: View { HStack(spacing: 13.3) { Image("ic_make_message") - Text("수정") + Text(I18n.Explorer.edit) .appFont(size: 16.7, weight: .medium) .foregroundColor(.white) @@ -67,7 +67,7 @@ struct CreatorCommunityMenuView: View { HStack(spacing: 13.3) { Image("ic_trash_can") - Text("삭제") + Text(I18n.Common.delete) .appFont(size: 16.7, weight: .medium) .foregroundColor(.white) @@ -82,7 +82,7 @@ struct CreatorCommunityMenuView: View { } } else { HStack(spacing: 0) { - Text("신고") + Text(I18n.Explorer.report) .appFont(size: 16.7, weight: .medium) .foregroundColor(.white) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Player/CreatorCommunityMediaPlayerManager.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Player/CreatorCommunityMediaPlayerManager.swift index 9794adb..35d1dc9 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Player/CreatorCommunityMediaPlayerManager.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Player/CreatorCommunityMediaPlayerManager.swift @@ -81,7 +81,7 @@ final class CreatorCommunityMediaPlayerManager: NSObject, ObservableObject { } private func showError() { - self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요." + self.errorMessage = I18n.Message.Voice.Sound.commonError self.isShowPopup = true } } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityMoreItemView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityMoreItemView.swift index 110c09c..657454f 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityMoreItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityMoreItemView.swift @@ -17,7 +17,7 @@ struct CreatorCommunityMoreItemView: View { .resizable() .frame(width: 40, height: 40) - Text("더보기") + Text(I18n.Explorer.viewMore) .appFont(size: 11, weight: .light) .foregroundColor(Color(hex: "bbbbbb")) } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityNoPostsItemView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityNoPostsItemView.swift index 5214b09..49d6ce9 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityNoPostsItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityNoPostsItemView.swift @@ -15,11 +15,11 @@ struct CreatorCommunityNoPostsItemView: View { VStack(spacing: 10.3) { CreatorCommunityWriteItemView() - Text("게시물 등록") + Text(I18n.Explorer.postRegister) .appFont(size: 14.7, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) - - Text("게시 후에 게시물이 여기에 표시되고\n커뮤니티에 공개됩니다.") + + Text(I18n.Explorer.postRegisterDescription) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "777777")) .fixedSize(horizontal: false, vertical: true) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift index a119ee8..8d1278f 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift @@ -24,12 +24,12 @@ struct CreatorCommunityModifyView: View { GeometryReader { proxy in ZStack { VStack(spacing: 0) { - DetailNavigationBar(title: "게시글 수정") + DetailNavigationBar(title: I18n.Explorer.postModifyTitle) ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { VStack(spacing: 13.3) { - Text("이미지") + Text(I18n.Explorer.imageTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) .frame(maxWidth: .infinity, alignment: .leading) @@ -81,7 +81,7 @@ struct CreatorCommunityModifyView: View { viewModel.postImageData = data } } catch { - viewModel.errorMessage = "이미지를 로드하지 못했습니다." + viewModel.errorMessage = I18n.Explorer.imageLoadFailed viewModel.isShowPopup = true DEBUG_LOG("이미지 로드 실패: \(error)") } @@ -94,24 +94,24 @@ struct CreatorCommunityModifyView: View { .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "777777")) - Text("등록할 이미지가 없으면 이미지 없이 게시글만 등록 하셔도 됩니다.") + Text(I18n.Explorer.imageOptionalNotice) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "777777")) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 24) - + HStack(spacing: 0) { - Text("내용") + Text(I18n.Explorer.contentTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) Spacer() - Text("\(viewModel.content.count)자") + Text("\(viewModel.content.count)\(I18n.Explorer.contentCharacterUnit)") .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "ff5c49")) - Text(" / 최대 500자") + Text(I18n.Explorer.max500Chars) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "777777")) } @@ -128,14 +128,14 @@ struct CreatorCommunityModifyView: View { .padding(.top, 13.3) VStack(spacing: 13.3) { - Text("댓글 가능 여부") + Text(I18n.Explorer.commentAvailability) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) .frame(maxWidth: .infinity, alignment: .leading) HStack(spacing: 13.3) { SelectButtonView( - title: "댓글 가능", + title: I18n.CreateContent.commentAllowed, isChecked: viewModel.isAvailableComment ) { if !viewModel.isAvailableComment { @@ -144,7 +144,7 @@ struct CreatorCommunityModifyView: View { } SelectButtonView( - title: "댓글 불가", + title: I18n.CreateContent.commentNotAllowed, isChecked: !viewModel.isAvailableComment ) { if viewModel.isAvailableComment { @@ -156,14 +156,14 @@ struct CreatorCommunityModifyView: View { .padding(.top, 26.7) VStack(spacing: 13.3) { - Text("연령 제한") + Text(I18n.Explorer.ageRestriction) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) .frame(maxWidth: .infinity, alignment: .leading) HStack(spacing: 13.3) { SelectButtonView( - title: "전체 연령", + title: I18n.CreateContent.allAges, isChecked: !viewModel.isAdult ) { if viewModel.isAdult { @@ -172,7 +172,7 @@ struct CreatorCommunityModifyView: View { } SelectButtonView( - title: "19세 이상", + title: I18n.CreateContent.over19, isChecked: viewModel.isAdult ) { if !viewModel.isAdult { @@ -187,7 +187,7 @@ struct CreatorCommunityModifyView: View { VStack(spacing: 0) { HStack(spacing: 13.3) { - Text("닫기") + Text(I18n.Explorer.close) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color(hex: "3BB9F1")) .frame(maxWidth: .infinity) @@ -203,7 +203,7 @@ struct CreatorCommunityModifyView: View { AppState.shared.back() } - Text("수정") + Text(I18n.Explorer.modify) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.white) .frame(maxWidth: .infinity) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift index 88d0d45..b03d5ec 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift @@ -24,7 +24,7 @@ final class CreatorCommunityModifyViewModel: ObservableObject { @Published var postImageUrl: String? = nil @Published private(set) var communityPost: GetCommunityPostListResponse? - var placeholder = "내용을 입력하세요" + var placeholder = I18n.Explorer.inputContent var postId = 0 func getCommunityPostDetail(onFailure: (() -> Void)? = nil) { @@ -58,7 +58,7 @@ final class CreatorCommunityModifyViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError } self.isShowPopup = true @@ -67,7 +67,7 @@ final class CreatorCommunityModifyViewModel: ObservableObject { } } } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true if let onFailure = onFailure { onFailure() @@ -126,7 +126,7 @@ final class CreatorCommunityModifyViewModel: ObservableObject { let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) if decoded.success { - self.errorMessage = "게시물이 수정되었습니다." + self.errorMessage = I18n.Explorer.postUpdated self.isShowPopup = true DispatchQueue.main.asyncAfter(deadline: .now() + 1) { @@ -136,19 +136,19 @@ final class CreatorCommunityModifyViewModel: 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계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true self.isLoading = false } @@ -157,7 +157,7 @@ final class CreatorCommunityModifyViewModel: ObservableObject { private func validateData() -> Bool { if content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || content.count < 5 { - errorMessage = "내용을 5자 이상 입력해 주세요." + errorMessage = I18n.Explorer.minContentLength(5) isShowPopup = true return false } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityRecordingVoiceView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityRecordingVoiceView.swift index 020bd64..e5b2f30 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityRecordingVoiceView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityRecordingVoiceView.swift @@ -31,7 +31,7 @@ struct CreatorCommunityRecordingVoiceView: View { VStack { VStack(spacing: 0) { HStack(spacing: 0) { - Text("음성녹음") + Text(I18n.Explorer.recordingTitle) .appFont(size: 18.3, weight: .bold) .foregroundColor(.white) @@ -77,7 +77,7 @@ struct CreatorCommunityRecordingVoiceView: View { HStack(spacing: 0) { Spacer() - Text("삭제") + Text(I18n.Common.delete) .appFont(size: 15.3, weight: .medium) .foregroundColor(Color.graybb.opacity(0)) @@ -100,7 +100,7 @@ struct CreatorCommunityRecordingVoiceView: View { Spacer() - Text("삭제") + Text(I18n.Common.delete) .appFont(size: 15.3, weight: .medium) .foregroundColor(Color.graybb) .onTapGesture { @@ -114,7 +114,7 @@ struct CreatorCommunityRecordingVoiceView: View { .padding(.vertical, 52.3) HStack(spacing: 13.3) { - Text("다시 녹음") + Text(I18n.Explorer.recordAgain) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.button) .frame(width: (proxy.size.width - 40) / 3, height: 50) @@ -130,7 +130,7 @@ struct CreatorCommunityRecordingVoiceView: View { soundManager.recordMode = .RECORD } - Text("녹음완료") + Text(I18n.Explorer.recordingComplete) .appFont(size: 18.3, weight: .bold) .foregroundColor(.white) .frame(width: (proxy.size.width - 40) * 2 / 3, height: 50) @@ -143,7 +143,7 @@ struct CreatorCommunityRecordingVoiceView: View { self.fileName = tempFileName self.isShowing = false } catch { - errorMessage = "녹음파일을 생성하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + errorMessage = I18n.Explorer.recordingFileCreateFailed isShowPopup = true } } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunitySoundManager.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunitySoundManager.swift index 7ba4761..52ab3fa 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunitySoundManager.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunitySoundManager.swift @@ -44,14 +44,14 @@ class CreatorCommunitySoundManager: NSObject, ObservableObject { audioSession.requestRecordPermission() { [weak self] allowed in DispatchQueue.main.async { if !allowed { - self?.errorMessage = "권한을 허용하지 않으시면 음성녹음을 하실 수 없습니다." + self?.errorMessage = I18n.Explorer.recordingPermissionDenied self?.isShowPopup = true self?.onClose = true } } } } catch { - errorMessage = "오류가 발생했습니다. 다시 시도해 주세요." + errorMessage = I18n.Message.Voice.Sound.commonError isShowPopup = true onClose = true } @@ -84,7 +84,7 @@ class CreatorCommunitySoundManager: NSObject, ObservableObject { startTimer() } catch { - errorMessage = "오류가 발생했습니다. 다시 시도해 주세요." + errorMessage = I18n.Message.Voice.Sound.commonError isShowPopup = true } isLoading = false @@ -117,7 +117,7 @@ class CreatorCommunitySoundManager: NSObject, ObservableObject { self.player?.delegate = self self.player?.prepareToPlay() } catch { - self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요." + self.errorMessage = I18n.Message.Voice.Sound.commonError self.isShowPopup = true } @@ -142,7 +142,9 @@ class CreatorCommunitySoundManager: NSObject, ObservableObject { func deleteAudioFile() { do { try FileManager.default.removeItem(at: getAudioFileURL()) - } catch {} + } catch { + DEBUG_LOG("오디오 파일 삭제 실패: \(error)") + } } func getAudioFileURL() -> URL { diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift index fe650d4..ac80e3d 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift @@ -19,7 +19,7 @@ struct CreatorCommunityWriteView: View { @State private var isImageLoading = false @State private var isShowRecordingVoiceView = false - @State private var fileName: String = "녹음" + @State private var fileName: String = I18n.Explorer.recordingDefaultFileName private let imagePreviewSize: CGFloat = 110 @@ -30,12 +30,12 @@ struct CreatorCommunityWriteView: View { GeometryReader { proxy in ZStack { VStack(spacing: 0) { - DetailNavigationBar(title: "게시글 등록") + DetailNavigationBar(title: I18n.Explorer.postWriteTitle) ScrollView(.vertical, showsIndicators: false) { VStack(spacing: 0) { VStack(spacing: 13.3) { - Text("이미지") + Text(I18n.Explorer.imageTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) .frame(maxWidth: .infinity, alignment: .leading) @@ -75,7 +75,7 @@ struct CreatorCommunityWriteView: View { .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.gray77) - Text("등록할 이미지가 없으면 이미지 없이 게시글만 등록 하셔도 됩니다.") + Text(I18n.Explorer.imageOptionalNotice) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.gray77) } @@ -85,7 +85,7 @@ struct CreatorCommunityWriteView: View { if let _ = viewModel.postImageData { VStack(spacing: 13.3) { HStack(spacing: 0) { - Text("오디오 녹음") + Text(I18n.Explorer.audioRecordingTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) @@ -107,7 +107,7 @@ struct CreatorCommunityWriteView: View { .onTapGesture { isShowRecordingVoiceView = true } - Text("※ 오디오 녹음은 최대 3분입니다") + Text(I18n.Explorer.audioRecordingMax3Minutes) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.gray77) } @@ -115,16 +115,16 @@ struct CreatorCommunityWriteView: View { } HStack(spacing: 0) { - Text("내용") + Text(I18n.Explorer.contentTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) Spacer() - Text("\(viewModel.content.count)자") + Text("\(viewModel.content.count)\(I18n.Explorer.contentCharacterUnit)") .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.mainRed) - Text(" / 최대 500자") + Text(I18n.Explorer.max500Chars) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.gray77) } @@ -141,14 +141,14 @@ struct CreatorCommunityWriteView: View { .padding(.top, 13.3) VStack(spacing: 13.3) { - Text("댓글 가능 여부") + Text(I18n.Explorer.commentAvailability) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) .frame(maxWidth: .infinity, alignment: .leading) HStack(spacing: 13.3) { SelectButtonView( - title: "댓글 가능", + title: I18n.CreateContent.commentAllowed, isChecked: viewModel.isAvailableComment ) { if !viewModel.isAvailableComment { @@ -157,7 +157,7 @@ struct CreatorCommunityWriteView: View { } SelectButtonView( - title: "댓글 불가", + title: I18n.CreateContent.commentNotAllowed, isChecked: !viewModel.isAvailableComment ) { if viewModel.isAvailableComment { @@ -170,14 +170,14 @@ struct CreatorCommunityWriteView: View { if UserDefaults.bool(forKey: .auth) { VStack(spacing: 13.3) { - Text("연령 제한") + Text(I18n.Explorer.ageRestriction) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) .frame(maxWidth: .infinity, alignment: .leading) HStack(spacing: 13.3) { SelectButtonView( - title: "전체 연령", + title: I18n.CreateContent.allAges, isChecked: !viewModel.isAdult ) { if viewModel.isAdult { @@ -186,7 +186,7 @@ struct CreatorCommunityWriteView: View { } SelectButtonView( - title: "19세 이상", + title: I18n.CreateContent.over19, isChecked: viewModel.isAdult ) { if !viewModel.isAdult { @@ -200,14 +200,14 @@ struct CreatorCommunityWriteView: View { if let _ = viewModel.postImageData { VStack(spacing: 13.3) { - Text("가격 설정") + Text(I18n.Explorer.priceSetting) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) .frame(maxWidth: .infinity, alignment: .leading) HStack(spacing: 13.3) { SelectButtonView( - title: "무료", + title: I18n.CreateContent.free, isChecked: viewModel.isPriceFree ) { if !viewModel.isPriceFree { @@ -216,7 +216,7 @@ struct CreatorCommunityWriteView: View { } SelectButtonView( - title: "유료", + title: I18n.CreateContent.paid, isChecked: !viewModel.isPriceFree ) { if viewModel.isPriceFree { @@ -238,7 +238,7 @@ struct CreatorCommunityWriteView: View { Spacer() - Text("캔") + Text(I18n.Explorer.canUnitCompact) .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.button) } @@ -259,7 +259,7 @@ struct CreatorCommunityWriteView: View { VStack(spacing: 0) { HStack(spacing: 13.3) { - Text("닫기") + Text(I18n.Explorer.close) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.button) .frame(maxWidth: .infinity) @@ -275,7 +275,7 @@ struct CreatorCommunityWriteView: View { AppState.shared.back() } - Text("등록") + Text(I18n.Explorer.register) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.white) .frame(maxWidth: .infinity) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift index 2ef6fea..0e01088 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift @@ -47,7 +47,7 @@ final class CreatorCommunityWriteViewModel: ObservableObject { @Published var price = 0 @Published var soundData: Data? = nil - var placeholder = "내용을 입력하세요" + var placeholder = I18n.Explorer.inputContent func setPostImage(_ image: UIImage) { postImage = image @@ -99,7 +99,7 @@ final class CreatorCommunityWriteViewModel: ObservableObject { case .failure(let error): ERROR_LOG(error.localizedDescription) self.isLoading = false - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true } } receiveValue: { [unowned self] response in @@ -111,7 +111,7 @@ final class CreatorCommunityWriteViewModel: ObservableObject { let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) if decoded.success { - self.errorMessage = "게시물이 등록되었습니다." + self.errorMessage = I18n.Explorer.postCreated self.isShowPopup = true DispatchQueue.main.asyncAfter(deadline: .now() + 1) { @@ -121,19 +121,19 @@ final class CreatorCommunityWriteViewModel: 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계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError self.isShowPopup = true self.isLoading = false } @@ -142,13 +142,13 @@ final class CreatorCommunityWriteViewModel: ObservableObject { private func validateData() -> Bool { if content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || content.count < 5 { - errorMessage = "내용을 5자 이상 입력해 주세요." + errorMessage = I18n.Explorer.minContentLength(5) isShowPopup = true return false } - + if !isPriceFree && price < 5 { - errorMessage = "최소금액은 5캔 입니다." + errorMessage = I18n.Explorer.minPriceFiveCans isShowPopup = true return false } diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift index 2ffc80f..602eef2 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift @@ -23,11 +23,11 @@ struct UserProfileFanTalkAllView: View { GeometryReader { proxy in BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { - DetailNavigationBar(title: String(localized: "팬 Talk 전체보기")) + DetailNavigationBar(title: I18n.Explorer.fanTalkAllTitle) VStack(alignment: .leading, spacing: 0) { HStack(spacing: 6.7) { - Text("응원") + Text(I18n.Explorer.cheerTitle) .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.grayee) @@ -43,7 +43,7 @@ struct UserProfileFanTalkAllView: View { .padding(.top, 13.3) HStack(spacing: 0) { - TextField("응원댓글을 입력하세요", text: $cheersContent) + TextField(I18n.Explorer.cheerInputPlaceholder, text: $cheersContent) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 13.3, weight: .medium) @@ -115,7 +115,7 @@ struct UserProfileFanTalkAllView: View { } } } else { - Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!") + Text(I18n.Explorer.cheerEmpty) .appFont(size: 13.3, weight: .light) .foregroundColor(Color.graybb) .multilineTextAlignment(.center) diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift index 783f002..9f97360 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift @@ -69,7 +69,7 @@ struct UserProfileFanTalkCheersItemView: View { .foregroundColor(Color.button) ) - Text("수정") + Text(I18n.Explorer.modify) .appFont(size: 14, weight: .bold) .foregroundColor(Color.white) .padding(13.3) @@ -80,7 +80,7 @@ struct UserProfileFanTalkCheersItemView: View { isModeModify = false } - Text("취소") + Text(I18n.Common.cancel) .appFont(size: 14, weight: .bold) .foregroundColor(Color.button) .padding(13.3) @@ -102,7 +102,7 @@ struct UserProfileFanTalkCheersItemView: View { if isShowInputReply { HStack(spacing: 10) { - TextField("응원댓글에 답글을 남겨보세요!", text: $replyContent) + TextField(I18n.Explorer.cheerReplyPlaceholder, text: $replyContent) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 14, weight: .medium) @@ -118,7 +118,7 @@ struct UserProfileFanTalkCheersItemView: View { .foregroundColor(Color.button) ) - Text("등록") + Text(I18n.Explorer.register) .appFont(size: 14, weight: .bold) .foregroundColor(Color.white) .padding(13.3) @@ -136,7 +136,7 @@ struct UserProfileFanTalkCheersItemView: View { } else { if cheersItem.replyList.count <= 0 { if userId == UserDefaults.int(forKey: .userId) { - Text("답글쓰기") + Text(I18n.Explorer.replyWrite) .appFont(size: 12, weight: .medium) .foregroundColor(Color.button) .padding(.top, 18.3) @@ -164,7 +164,7 @@ struct UserProfileFanTalkCheersItemView: View { .foregroundColor(Color.gray52) if userId == UserDefaults.int(forKey: .userId) { - Text("답글 수정") + Text(I18n.Explorer.replyEdit) .appFont(size: 12, weight: .medium) .foregroundColor(Color.button) .onTapGesture { @@ -196,7 +196,7 @@ struct UserProfileFanTalkCheersItemView: View { if isShowPopupMenu { VStack(spacing: 10) { if cheersItem.memberId != UserDefaults.int(forKey: .userId) { - Text("신고하기") + Text(I18n.Explorer.reportAction) .appFont(size: 14, weight: .medium) .foregroundColor(Color.gray77) .onTapGesture { @@ -206,7 +206,7 @@ struct UserProfileFanTalkCheersItemView: View { } if cheersItem.memberId == UserDefaults.int(forKey: .userId) { - Text("수정") + Text(I18n.Explorer.edit) .appFont(size: 14, weight: .medium) .foregroundColor(Color.gray77) .onTapGesture { @@ -219,7 +219,7 @@ struct UserProfileFanTalkCheersItemView: View { if userId == UserDefaults.int(forKey: .userId) || cheersItem.memberId == UserDefaults.int(forKey: .userId) { - Text("삭제") + Text(I18n.Common.delete) .appFont(size: 14, weight: .medium) .foregroundColor(Color.gray77) .onTapGesture { diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift index c1392e8..2d8d99e 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift @@ -26,13 +26,13 @@ struct UserProfileFanTalkView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { HStack(spacing: 0) { - Text("팬 Talk") + Text(I18n.Explorer.fanTalkTitle) .appFont(size: 26, weight: .bold) .foregroundColor(Color.white) Spacer() - Text("전체보기") + Text(I18n.Common.viewAll) .appFont(size: 14, weight: .light) .foregroundColor(Color(hex: "78909C")) .onTapGesture { @@ -43,7 +43,7 @@ struct UserProfileFanTalkView: View { VStack(alignment: .leading, spacing: 20) { HStack(spacing: 6.7) { - Text("응원") + Text(I18n.Explorer.cheerTitle) .appFont(size: 16, weight: .medium) .foregroundColor(Color.grayee) @@ -53,7 +53,7 @@ struct UserProfileFanTalkView: View { } HStack(spacing: 0) { - TextField("응원댓글을 입력하세요", text: $cheersContent) + TextField(I18n.Explorer.cheerInputPlaceholder, text: $cheersContent) .autocapitalization(.none) .disableAutocorrection(true) .appFont(size: 14, weight: .medium) @@ -111,7 +111,7 @@ struct UserProfileFanTalkView: View { } } } else { - Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!") + Text(I18n.Explorer.cheerEmpty) .appFont(size: 13.3, weight: .light) .foregroundColor(Color.graybb) .multilineTextAlignment(.center) diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift index 09effd3..d83b571 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift @@ -70,7 +70,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError } if let errorPopup = errorPopup { @@ -80,7 +80,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { } } } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError if let errorPopup = errorPopup { errorPopup(self.errorMessage) } else { @@ -95,9 +95,9 @@ final class UserProfileFanTalkViewModel: ObservableObject { func writeCheersReply(parentCheersId: Int, creatorId: Int, cheersReplyContent: String) { if cheersReplyContent.trimmingCharacters(in: .whitespaces).isEmpty { if let errorPopup = errorPopup { - errorPopup("내용을 입력하세요") + errorPopup(I18n.Explorer.inputContent) } else { - errorMessage = "내용을 입력하세요" + errorMessage = I18n.Explorer.inputContent isShowPopup = true } @@ -137,7 +137,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError } if let errorPopup = errorPopup { @@ -147,7 +147,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { } } } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError if let errorPopup = errorPopup { errorPopup(self.errorMessage) } else { @@ -161,9 +161,9 @@ final class UserProfileFanTalkViewModel: ObservableObject { func writeCheers(creatorId: Int, cheersContent: String) { if cheersContent.trimmingCharacters(in: .whitespaces).isEmpty { if let errorPopup = errorPopup { - errorPopup("내용을 입력하세요") + errorPopup(I18n.Explorer.inputContent) } else { - errorMessage = "내용을 입력하세요" + errorMessage = I18n.Explorer.inputContent isShowPopup = true } @@ -203,7 +203,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { if let message = decoded.message { self.errorMessage = message } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError } if let errorPopup = errorPopup { @@ -213,7 +213,7 @@ final class UserProfileFanTalkViewModel: ObservableObject { } } } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.errorMessage = I18n.Common.commonError if let errorPopup = errorPopup { errorPopup(self.errorMessage) } else { @@ -227,18 +227,18 @@ final class UserProfileFanTalkViewModel: ObservableObject { func modifyCheers(cheersId: Int, creatorId: Int, cheersContent: String? = nil, isActive: Bool? = nil) { if cheersContent == nil && isActive == nil { if let errorPopup = errorPopup { - errorPopup("변경사항이 없습니다.") + errorPopup(I18n.Explorer.noChanges) } else { - errorMessage = "변경사항이 없습니다." + errorMessage = I18n.Explorer.noChanges isShowPopup = true } } if let cheersContent = cheersContent, cheersContent.trimmingCharacters(in: .whitespaces).isEmpty { if let errorPopup = errorPopup { - errorPopup("내용을 입력하세요") + errorPopup(I18n.Explorer.inputContent) } else { - errorMessage = "내용을 입력하세요" + errorMessage = I18n.Explorer.inputContent isShowPopup = true } @@ -278,20 +278,20 @@ final class UserProfileFanTalkViewModel: 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) } - func report(type: ReportType, reason: String = "프로필 신고") { + func report(type: ReportType, reason: String = I18n.Dialog.MemberProfile.reportProfile) { isLoading = true let request = ReportRequest(type: type, reason: reason, reportedMemberId: nil, cheersId: reportCheersId > 0 && type == .CHEERS ? reportCheersId : nil, audioContentId: nil) @@ -316,12 +316,12 @@ final class UserProfileFanTalkViewModel: 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/Explorer/Profile/FollowerList/FollowerListView.swift b/SodaLive/Sources/Explorer/Profile/FollowerList/FollowerListView.swift index 75859b2..e1b8683 100644 --- a/SodaLive/Sources/Explorer/Profile/FollowerList/FollowerListView.swift +++ b/SodaLive/Sources/Explorer/Profile/FollowerList/FollowerListView.swift @@ -22,10 +22,10 @@ struct FollowerListView: View { var body: some View { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { - DetailNavigationBar(title: String(localized: "팔로워 리스트")) + DetailNavigationBar(title: I18n.MemberChannel.followersList) HStack(spacing: 4) { - Text("전체") + Text(I18n.Category.all) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.grayee) diff --git a/SodaLive/Sources/Explorer/Profile/Series/UserProfileSeriesView.swift b/SodaLive/Sources/Explorer/Profile/Series/UserProfileSeriesView.swift index b0f9aff..3176227 100644 --- a/SodaLive/Sources/Explorer/Profile/Series/UserProfileSeriesView.swift +++ b/SodaLive/Sources/Explorer/Profile/Series/UserProfileSeriesView.swift @@ -15,13 +15,13 @@ struct UserProfileSeriesView: View { var body: some View { VStack(alignment: .leading, spacing: 13.3) { HStack(spacing: 0) { - Text("시리즈") + Text(I18n.Explorer.seriesTitle) .appFont(size: 26, weight: .bold) .foregroundColor(Color.white) Spacer() - Text("전체보기") + Text(I18n.Common.viewAll) .appFont(size: 14, weight: .light) .foregroundColor(Color(hex: "78909C")) .onTapGesture { diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileActivitySummaryView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileActivitySummaryView.swift index 907bdc9..056d176 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileActivitySummaryView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileActivitySummaryView.swift @@ -14,28 +14,28 @@ struct UserProfileActivitySummaryView: View { var body: some View { HStack(spacing: 0) { ActivitySummaryItemView( - title: "라이브\n횟수", + title: I18n.Explorer.activityLiveCount, count: String(format: "%d", item.liveCount) ) ActivitySummaryDividerView() ActivitySummaryItemView( - title: "라이브\n시간", + title: I18n.Explorer.activityLiveTime, count: String(format: "%d", item.liveTime) ) ActivitySummaryDividerView() ActivitySummaryItemView( - title: "라이브\n참여자", + title: I18n.Explorer.activityLiveParticipants, count: String(format: "%d", item.liveContributorCount) ) ActivitySummaryDividerView() ActivitySummaryItemView( - title: "등록\n콘텐츠", + title: I18n.Explorer.activityRegisteredContent, count: String(format: "%d", item.contentCount) ) } diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift index b96cc7e..9884ad7 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift @@ -24,13 +24,13 @@ struct UserProfileContentView: View { var body: some View { VStack(spacing: 21) { HStack(spacing: 0) { - Text(userId == UserDefaults.int(forKey: .userId) ? "내 콘텐츠" : "콘텐츠") + Text(userId == UserDefaults.int(forKey: .userId) ? I18n.Explorer.myContent : I18n.Explorer.content) .appFont(size: 26, weight: .bold) .foregroundColor(Color.white) Spacer() - Text("전체보기") + Text(I18n.Common.viewAll) .appFont(size: 14, weight: .light) .foregroundColor(Color(hex: "78909C")) .onTapGesture { @@ -39,7 +39,7 @@ struct UserProfileContentView: View { } if userId == UserDefaults.int(forKey: .userId) { - Text("새로운 콘텐츠 등록하기") + Text(I18n.Explorer.createNewContent) .appFont(size: 16, weight: .bold) .foregroundColor(Color.grayee) .padding(.vertical, 17) @@ -73,7 +73,7 @@ struct UserProfileContentView: View { VStack(spacing: 8) { // 상단 정보 (계산된 % 보유중, 정보 아이콘, 개수) HStack { - Text(ownedContentCount > 0 ? "\(Int(round(Double(ownedContentCount) / Double(totalContentCount) * 100)))% 보유중" : "소장 중인 작품이 없어요!") + Text(ownedContentCount > 0 ? I18n.Explorer.ownedPercentage(Int(round(Double(ownedContentCount) / Double(totalContentCount) * 100))) : I18n.Explorer.noOwnedContent) .appFont(size: 18, weight: .bold) .foregroundColor(.white) @@ -88,7 +88,7 @@ struct UserProfileContentView: View { .appFont(size: 16, weight: .regular) .foregroundColor(.white) - Text("\(totalContentCount)개") + Text(I18n.Explorer.totalContentCount(totalContentCount)) .appFont(size: 16, weight: .regular) .foregroundColor(.white) } diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift index 34d4154..212071c 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift @@ -16,14 +16,14 @@ struct UserProfileDonationAllView: View { var body: some View { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { - DetailNavigationBar(title: String(localized: "후원랭킹 전체보기")) + DetailNavigationBar(title: I18n.Explorer.donationRankingAllTitle) if userId == UserDefaults.int(forKey: .userId) { VStack(spacing: 10.7) { HStack(spacing: 10) { Spacer() - Text("채널에 후원랭킹 활성화") + Text(I18n.Explorer.donationRankActivation) .appFont(size: 16, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) @@ -38,7 +38,7 @@ struct UserProfileDonationAllView: View { HStack(spacing: 0) { Spacer() - Text("※ 비활성화하면 채널 내 후원랭킹이 표시되지 않으며,\n라이브 중에도 후원랭킹에 따른 뱃지가 반영되지 않습니다.") + Text(I18n.Explorer.donationRankActivationNotice) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "555555")) .multilineTextAlignment(.trailing) @@ -79,7 +79,7 @@ struct UserProfileDonationAllView: View { VStack(spacing: 13.3) { HStack(spacing: 0) { - Text("오늘") + Text(I18n.Explorer.today) .appFont(size: 12, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) @@ -89,13 +89,13 @@ struct UserProfileDonationAllView: View { .appFont(size: 12, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) - Text(" 캔") + Text(I18n.Explorer.canUnitWithSpace) .appFont(size: 12, weight: .light) .foregroundColor(Color(hex: "eeeeee")) } HStack(spacing: 0) { - Text("지난주") + Text(I18n.Explorer.lastWeek) .appFont(size: 12, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) @@ -105,13 +105,13 @@ struct UserProfileDonationAllView: View { .appFont(size: 12, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) - Text(" 캔") + Text(I18n.Explorer.canUnitWithSpace) .appFont(size: 12, weight: .light) .foregroundColor(Color(hex: "eeeeee")) } HStack(spacing: 0) { - Text("이번 달 어제까지") + Text(I18n.Explorer.thisMonthUntilYesterday) .appFont(size: 12, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) @@ -121,7 +121,7 @@ struct UserProfileDonationAllView: View { .appFont(size: 12, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) - Text(" 캔") + Text(I18n.Explorer.canUnitWithSpace) .appFont(size: 12, weight: .light) .foregroundColor(Color(hex: "eeeeee")) } @@ -153,7 +153,7 @@ struct UserProfileDonationAllView: View { } HStack(alignment: .center, spacing: 0) { - Text("전체") + Text(I18n.MemberChannel.totalLabel) .appFont(size: 14.7, weight: .medium) .foregroundColor(Color(hex: "eeeeee")) @@ -162,7 +162,7 @@ struct UserProfileDonationAllView: View { .foregroundColor(Color(hex: "80d8ff")) .padding(.leading, 6.7) - Text("개") + Text(I18n.MemberChannel.countUnit) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "777777")) @@ -253,7 +253,7 @@ struct UserProfileDonationAllItemView: View { Spacer() if let donationCan = item.donationCan, donationCan > 0, withDonationCan { - Text("\(donationCan) 캔") + Text(I18n.Explorer.canWithSpace(donationCan)) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color(hex: "eeeeee")) } diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift index 6a3027d..07aa756 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift @@ -86,13 +86,13 @@ final class UserProfileDonationAllViewModel: 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 } } @@ -144,13 +144,13 @@ final class UserProfileDonationAllViewModel: 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 } } @@ -189,13 +189,13 @@ final class UserProfileDonationAllViewModel: 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/Explorer/Profile/UserProfileDonationView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift index 22b25b5..7f7b904 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift @@ -17,13 +17,13 @@ struct UserProfileDonationView: View { var body: some View { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 0) { - Text("후원랭킹") + Text(I18n.Explorer.donationRankingTitle) .appFont(size: 26, weight: .bold) .foregroundColor(Color.white) Spacer() - Text("전체보기") + Text(I18n.Common.viewAll) .appFont(size: 14, weight: .light) .foregroundColor(Color(hex: "78909C")) .onTapGesture { diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileIntroduceView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileIntroduceView.swift index 598de50..5226078 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileIntroduceView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileIntroduceView.swift @@ -13,7 +13,7 @@ struct UserProfileIntroduceView: View { var body: some View { VStack(alignment: .leading, spacing: 16.7) { - Text("채널 소개") + Text(I18n.Explorer.channelIntroduceTitle) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color(hex: "eeeeee")) diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileLiveView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileLiveView.swift index a18cdd8..f621f9b 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileLiveView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileLiveView.swift @@ -83,7 +83,7 @@ struct UserProfileLiveView: View { .clipShape(Circle()) } else { VStack(spacing: 0) { - Text("\(dateDic["month"] ?? "")월") + Text(I18n.Explorer.month(dateDic["month"] ?? "")) .appFont(size: 14, weight: .bold) .foregroundColor(.white) .padding(.vertical, 6) @@ -102,7 +102,7 @@ struct UserProfileLiveView: View { } if item.isReservation { - Text("예약완료") + Text(I18n.Explorer.reservationDone) .appFont(size: 12, weight: .bold) .foregroundColor(Color.white) .padding(4) @@ -125,7 +125,7 @@ struct UserProfileLiveView: View { .background(Color(hex: "3b5ff1")) .cornerRadius(4) } else { - Text("무료") + Text(I18n.CreateContent.free) .appFont(size: 14, weight: .regular) .foregroundColor(Color(hex: "#263238")) .padding(4) diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift b/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift index 311be2d..e1ea89d 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift @@ -32,7 +32,7 @@ final class UserProfileViewModel: ObservableObject { @Published var passwordDialogConfirmAction: (String) -> Void = { _ in } @Published var isShowPasswordDialog = false - @Published var navigationTitle = "채널" + @Published var navigationTitle = I18n.Explorer.channel @Published private(set) var creatorProfile: GetCreatorProfileResponse? @Published private(set) var creatorDetail: GetCreatorDetailResponse? @@ -516,7 +516,7 @@ final class UserProfileViewModel: ObservableObject { if decoded.success { getCreatorProfile(userId: userId) - self.errorMessage = "차단이 해제 되었습니다." + self.errorMessage = I18n.MemberChannel.userUnblocked } else { if let message = decoded.message { self.errorMessage = message @@ -534,7 +534,7 @@ final class UserProfileViewModel: ObservableObject { .store(in: &subscription) } - func report(type: ReportType, userId: Int? = nil, reason: String = "프로필 신고") { + func report(type: ReportType, userId: Int? = nil, reason: String = I18n.Dialog.MemberProfile.reportProfile) { isLoading = true let request = ReportRequest(type: type, reason: reason, reportedMemberId: userId, cheersId: cheersId > 0 && type == .CHEERS ? cheersId : nil, audioContentId: nil) diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index c46a4d6..9352a5d 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -2651,6 +2651,328 @@ If you block this user, the following features will be restricted. } } + enum Explorer { + static var channel: String { + pick(ko: "채널", en: "Channel", ja: "チャンネル") + } + + static var searchChannelPlaceholder: String { + pick(ko: "채널명을 입력해 보세요", en: "Search channel name", ja: "チャンネル名を入力してください") + } + + static var searchEmptyResult: String { + pick(ko: "검색 결과가 없습니다.", en: "No search results.", ja: "検索結果がありません。") + } + + static var rankingWeeklyUpdateNotice: String { + pick(ko: "※ 인기 크리에이터의 순위는 매주 업데이트됩니다.", en: "※ Popular creator rankings are updated weekly.", ja: "※ 人気クリエイターのランキングは毎週更新されます。") + } + + static var communityTitle: String { + pick(ko: "커뮤니티", en: "Community", ja: "コミュニティ") + } + + static var viewMore: String { + pick(ko: "더보기", en: "More", ja: "もっと見る") + } + + static var secretComment: String { + pick(ko: "비밀댓글", en: "Secret comment", ja: "秘密コメント") + } + + static var commentInputPlaceholder: String { + pick(ko: "댓글을 입력해 보세요.", en: "Enter a comment.", ja: "コメントを入力してください。") + } + + static var replyTitle: String { + pick(ko: "답글", en: "Replies", ja: "返信") + } + + static var replyWrite: String { + pick(ko: "답글 쓰기", en: "Write reply", ja: "返信を書く") + } + + static func replyCount(_ count: Int) -> String { + pick(ko: "답글 \(count)개", en: "\(count) replies", ja: "返信\(count)件") + } + + static var edit: String { + pick(ko: "수정", en: "Edit", ja: "編集") + } + + static var report: String { + pick(ko: "신고", en: "Report", ja: "通報") + } + + static var reportAction: String { + pick(ko: "신고하기", en: "Report", ja: "通報する") + } + + static var noChanges: String { + pick(ko: "변경사항이 없습니다.", en: "No changes.", ja: "変更事項がありません。") + } + + static var inputContent: String { + pick(ko: "내용을 입력하세요", en: "Enter content.", ja: "内容を入力してください。") + } + + static var inputContentWithPeriod: String { + pick(ko: "내용을 입력하세요.", en: "Enter content.", ja: "内容を入力してください。") + } + + static func minContentLength(_ count: Int) -> String { + pick(ko: "내용을 \(count)자 이상 입력해 주세요.", en: "Please enter at least \(count) characters.", ja: "内容を\(count)文字以上入力してください。") + } + + static var deleted: String { + pick(ko: "삭제되었습니다", en: "Deleted.", ja: "削除されました。") + } + + static var imageLoadFailed: String { + pick(ko: "이미지를 로드하지 못했습니다.", en: "Could not load the image.", ja: "画像を読み込めませんでした。") + } + + static var postCreated: String { + pick(ko: "게시물이 등록되었습니다.", en: "Post has been created.", ja: "投稿が登録されました。") + } + + static var postUpdated: String { + pick(ko: "게시물이 수정되었습니다.", en: "Post has been updated.", ja: "投稿が修正されました。") + } + + static var minPriceFiveCans: String { + pick(ko: "최소금액은 5캔 입니다.", en: "The minimum price is 5 cans.", ja: "最低金額は5canです。") + } + + static var pinRelease: String { + pick(ko: "고정 해제", en: "Unpin", ja: "固定解除") + } + + static var pinToTop: String { + pick(ko: "최상단에 고정", en: "Pin to top", ja: "最上部に固定") + } + + static func viewPostWithCans(_ can: Int) -> String { + pick(ko: "\(can)캔으로 게시글 보기", en: "View post with \(can) cans", ja: "\(can)canで投稿を見る") + } + + static var postRegister: String { + pick(ko: "게시물 등록", en: "Create post", ja: "投稿登録") + } + + static var postRegisterDescription: String { + pick(ko: "게시 후에 게시물이 여기에 표시되고\n커뮤니티에 공개됩니다.", en: "After posting, your post appears here\nand is published to the community.", ja: "投稿後、ここに表示され\nコミュニティに公開されます。") + } + + static var postWriteTitle: String { + pick(ko: "게시글 등록", en: "Create post", ja: "投稿登録") + } + + static var postModifyTitle: String { + pick(ko: "게시글 수정", en: "Edit post", ja: "投稿修正") + } + + static var imageTitle: String { + pick(ko: "이미지", en: "Image", ja: "画像") + } + + static var imageOptionalNotice: String { + pick(ko: "등록할 이미지가 없으면 이미지 없이 게시글만 등록 하셔도 됩니다.", en: "If there is no image to upload, you can post without an image.", ja: "登録する画像がない場合、画像なしで投稿できます。") + } + + static var contentTitle: String { + pick(ko: "내용", en: "Content", ja: "内容") + } + + static var contentCharacterUnit: String { + pick(ko: "자", en: "", ja: "文字") + } + + static var max500Chars: String { + pick(ko: " / 최대 500자", en: " / up to 500 chars", ja: " / 最大500文字") + } + + static var commentAvailability: String { + pick(ko: "댓글 가능 여부", en: "Comment availability", ja: "コメント可否") + } + + static var ageRestriction: String { + pick(ko: "연령 제한", en: "Age restriction", ja: "年齢制限") + } + + static var priceSetting: String { + pick(ko: "가격 설정", en: "Price setting", ja: "価格設定") + } + + static var close: String { + pick(ko: "닫기", en: "Close", ja: "閉じる") + } + + static var register: String { + pick(ko: "등록", en: "Register", ja: "登録") + } + + static var modify: String { + pick(ko: "수정", en: "Edit", ja: "編集") + } + + static var recordingDefaultFileName: String { + pick(ko: "녹음", en: "Recording", ja: "録音") + } + + static var recordingTitle: String { + pick(ko: "음성녹음", en: "Voice recording", ja: "音声録音") + } + + static var recordAgain: String { + pick(ko: "다시 녹음", en: "Record again", ja: "再録音") + } + + static var recordingComplete: String { + pick(ko: "녹음완료", en: "Done recording", ja: "録音完了") + } + + static var recordingPermissionDenied: String { + pick(ko: "권한을 허용하지 않으시면 음성녹음을 하실 수 없습니다.", en: "Voice recording is unavailable without permission.", ja: "権限を許可しないと音声録音を利用できません。") + } + + static var recordingFileCreateFailed: String { + pick(ko: "녹음파일을 생성하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다.", en: "Could not create the recording file.\nPlease try again.\nIf the issue persists, contact customer support.", ja: "録音ファイルを作成できませんでした。\nもう一度お試しください。\n問題が続く場合はカスタマーサポートにお問い合わせください。") + } + + static var audioRecordingTitle: String { + pick(ko: "오디오 녹음", en: "Audio recording", ja: "オーディオ録音") + } + + static var audioRecordingMax3Minutes: String { + pick(ko: "※ 오디오 녹음은 최대 3분입니다", en: "※ Audio recording is limited to 3 minutes.", ja: "※ オーディオ録音は最大3分です。") + } + + static var fanTalkTitle: String { + pick(ko: "팬 Talk", en: "Fan Talk", ja: "ファントーク") + } + + static var fanTalkAllTitle: String { + pick(ko: "팬 Talk 전체보기", en: "All Fan Talk", ja: "ファントーク一覧") + } + + static var cheerTitle: String { + pick(ko: "응원", en: "Cheer", ja: "応援") + } + + static var cheerInputPlaceholder: String { + pick(ko: "응원댓글을 입력하세요", en: "Enter a cheer comment", ja: "応援コメントを入力してください") + } + + static var cheerEmpty: String { + pick(ko: "응원이 없습니다.\n\n처음으로 응원을 해보세요!", en: "No cheers yet.\n\nBe the first to cheer!", ja: "応援がありません。\n\n最初の応援をしてみましょう!") + } + + static var cheerReplyPlaceholder: String { + pick(ko: "응원댓글에 답글을 남겨보세요!", en: "Leave a reply to this cheer!", ja: "応援コメントに返信してみましょう!") + } + + static var replyEdit: String { + pick(ko: "답글 수정", en: "Edit reply", ja: "返信を編集") + } + + static var seriesTitle: String { + pick(ko: "시리즈", en: "Series", ja: "シリーズ") + } + + static var channelIntroduceTitle: String { + pick(ko: "채널 소개", en: "Channel intro", ja: "チャンネル紹介") + } + + static var donationRankingTitle: String { + pick(ko: "후원랭킹", en: "Donation ranking", ja: "後援ランキング") + } + + static var donationRankingAllTitle: String { + pick(ko: "후원랭킹 전체보기", en: "All donation rankings", ja: "後援ランキング一覧") + } + + static var donationRankActivation: String { + pick(ko: "채널에 후원랭킹 활성화", en: "Enable donation ranking on channel", ja: "チャンネルで後援ランキングを有効化") + } + + static var donationRankActivationNotice: String { + pick(ko: "※ 비활성화하면 채널 내 후원랭킹이 표시되지 않으며,\n라이브 중에도 후원랭킹에 따른 뱃지가 반영되지 않습니다.", en: "※ If disabled, donation ranking is hidden in your channel, and ranking badges are not reflected during live.", ja: "※ 無効にするとチャンネル内に後援ランキングが表示されず、ライブ中のランキングバッジも反映されません。") + } + + static var today: String { + pick(ko: "오늘", en: "Today", ja: "今日") + } + + static var lastWeek: String { + pick(ko: "지난주", en: "Last week", ja: "先週") + } + + static var thisMonthUntilYesterday: String { + pick(ko: "이번 달 어제까지", en: "This month until yesterday", ja: "今月(昨日まで)") + } + + static var canUnitWithSpace: String { + pick(ko: " 캔", en: " cans", ja: " can") + } + + static var canUnitCompact: String { + pick(ko: "캔", en: "cans", ja: "can") + } + + static func canWithSpace(_ can: Int) -> String { + pick(ko: "\(can) 캔", en: "\(can) cans", ja: "\(can)can") + } + + static var myContent: String { + pick(ko: "내 콘텐츠", en: "My content", ja: "マイコンテンツ") + } + + static var content: String { + pick(ko: "콘텐츠", en: "Content", ja: "コンテンツ") + } + + static var createNewContent: String { + pick(ko: "새로운 콘텐츠 등록하기", en: "Create new content", ja: "新しいコンテンツを登録") + } + + static func ownedPercentage(_ percentage: Int) -> String { + pick(ko: "\(percentage)% 보유중", en: "\(percentage)% owned", ja: "\(percentage)%保有中") + } + + static var noOwnedContent: String { + pick(ko: "소장 중인 작품이 없어요!", en: "No owned works yet!", ja: "所蔵中の作品がありません!") + } + + static func totalContentCount(_ count: Int) -> String { + pick(ko: "\(count)개", en: "\(count)", ja: "\(count)件") + } + + static func month(_ value: String) -> String { + pick(ko: "\(value)월", en: "\(value)", ja: "\(value)月") + } + + static var reservationDone: String { + pick(ko: "예약완료", en: "Reserved", ja: "予約完了") + } + + static var activityLiveCount: String { + pick(ko: "라이브\n횟수", en: "Live\ncount", ja: "ライブ\n回数") + } + + static var activityLiveTime: String { + pick(ko: "라이브\n시간", en: "Live\ntime", ja: "ライブ\n時間") + } + + static var activityLiveParticipants: String { + pick(ko: "라이브\n참여자", en: "Live\nparticipants", ja: "ライブ\n参加者") + } + + static var activityRegisteredContent: String { + pick(ko: "등록\n콘텐츠", en: "Registered\ncontent", ja: "登録\nコンテンツ") + } + } + enum Dialog { enum ApplyAuditionComplete { static var thankYouDescription: String { diff --git a/docs/20260331_하드코딩텍스트_I18n통일계획.md b/docs/20260331_하드코딩텍스트_I18n통일계획.md index dbbde39..e35fc81 100644 --- a/docs/20260331_하드코딩텍스트_I18n통일계획.md +++ b/docs/20260331_하드코딩텍스트_I18n통일계획.md @@ -249,52 +249,52 @@ ### Explorer (40) #### Group 1 (1-10) -- [ ] `SodaLive/Sources/Explorer/ExplorerSectionView.swift` -- [ ] `SodaLive/Sources/Explorer/ExplorerView.swift` -- [ ] `SodaLive/Sources/Explorer/ExplorerViewModel.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationAllView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationItemView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListViewModel.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyViewModel.swift` +- [x] `SodaLive/Sources/Explorer/ExplorerSectionView.swift` +- [x] `SodaLive/Sources/Explorer/ExplorerView.swift` +- [x] `SodaLive/Sources/Explorer/ExplorerViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationAllView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationItemView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyViewModel.swift` #### Group 2 (11-20) -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllItemLockView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllViewModel.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Player/CreatorCommunityMediaPlayerManager.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityItemView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityMoreItemView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityNoPostsItemView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllItemLockView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Player/CreatorCommunityMediaPlayerManager.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityItemView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityMoreItemView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityNoPostsItemView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift` #### Group 3 (21-30) -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityRecordingVoiceView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunitySoundManager.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/FollowerList/FollowerListView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityRecordingVoiceView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunitySoundManager.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/FollowerList/FollowerListView.swift` #### Group 4 (31-40) -- [ ] `SodaLive/Sources/Explorer/Profile/Series/UserProfileSeriesView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileActivitySummaryView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileIntroduceView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileLiveView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileView.swift` -- [ ] `SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/Series/UserProfileSeriesView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileActivitySummaryView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileIntroduceView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileLiveView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileView.swift` +- [x] `SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift` ### Follow (1) - [x] `SodaLive/Sources/Follow/FollowCreatorView.swift` @@ -951,3 +951,27 @@ - 빌드 검증: `SodaLive`, `SodaLive-dev` Debug 빌드 모두 오류 없이 완료(경고만 존재). - 테스트 검증: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 test action 미구성 확인(코드 실패 아님, 스킴 제약). - LSP 진단 참고: 단일 파일 진단 시 `No such module 'Moya'`가 보고되나, SourceKit 단독 해석 한계이며 실제 `xcodebuild` 컴파일은 통과했다. + +### 20차 구현 (Explorer 모듈 Group 1~4, 40개 파일 처리, 2026-04-01) +- 무엇/왜/어떻게: + - 무엇: `변경 대상 파일 전체 목록`의 `Explorer` Group 1~4(40개 파일)를 전수 점검하고, 런타임 사용자 노출 하드코딩 문구를 `I18n.*`로 치환했다. + - 왜: Explorer(탐색/프로필/팬톡/크리에이터 커뮤니티) 구간에서 하드코딩 문구와 직접 로컬라이제이션 API(`String(localized:)`)가 혼재되어 `I18n.swift` 단일 접근 원칙과 충돌했기 때문이다. + - 어떻게: Explorer 대상 파일을 선별해 하드코딩/직접 API 사용을 재탐지하고, `I18n.swift`에 `I18n.Explorer` 네임스페이스를 추가한 뒤 호출부를 치환했다. 공통 문구는 `I18n.Common`으로 통합 재사용했다. +- 실행 명령/도구: + - `lsp_diagnostics(filePath=SodaLive/Sources/Explorer, extension=.swift, severity=all)` + - `lsp_diagnostics(filePath=SodaLive/Sources/I18n/I18n.swift, severity=all)` + - `grep("String\\(localized:|NSLocalizedString\\(|LocalizedStringKey\\(", include=*.swift, path=SodaLive/Sources/Explorer)` + - `grep("\"[^\"]*[가-힣][^\"]*\"", include=*.swift, path=SodaLive/Sources/Explorer)` + - `git diff --name-only -- "SodaLive/Sources/Explorer"` + - `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` +- 결과: + - Explorer Group 1~4 체크박스 40개 `- [x]` 완료 상태를 유지/확인했다. + - 실치환 파일은 총 37개였고, 나머지 3개(`ChannelDonationAllView.swift` 등)는 런타임 사용자 노출 하드코딩이 없어 점검만 수행했다. + - `String(localized:)`/`NSLocalizedString`/`LocalizedStringKey`의 Explorer 직접 사용은 0건으로 확인했다. + - Explorer 잔여 한글 리터럴은 Preview 샘플 데이터 및 `DEBUG_LOG` 메시지(비사용자 노출)만 존재함을 재확인했다. + - 빌드 검증: `SodaLive`, `SodaLive-dev` Debug 빌드 모두 성공(`** BUILD SUCCEEDED **`). + - 테스트 검증: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 test action 미구성 확인(코드 실패 아님, 스킴 제약). + - LSP 진단 참고: SourceKit 단독 해석에서 외부 모듈/프로젝트 심볼(`Moya`, `Kingfisher`, `I18n` 등) 미해결 오류가 보고되었으나, 동일 변경셋은 `xcodebuild` 실컴파일 통과로 검증했다.