diff --git a/SodaLive/Resources/Localizable.xcstrings b/SodaLive/Resources/Localizable.xcstrings index 600c3ab..452c185 100644 --- a/SodaLive/Resources/Localizable.xcstrings +++ b/SodaLive/Resources/Localizable.xcstrings @@ -855,6 +855,7 @@ } }, "%@님이" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -982,6 +983,16 @@ } } }, + "%lld%@" : { + "localizations" : { + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld%2$@" + } + } + } + }, "%lld%% 보유중" : { "localizations" : { "en" : { @@ -1031,6 +1042,7 @@ } }, "%lld원" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3090,6 +3102,7 @@ } }, "님의 룰렛 결과?" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3266,6 +3279,7 @@ } }, "답글쓰기" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -3890,6 +3904,7 @@ } }, "리스너로 변경" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -4146,8 +4161,21 @@ } } }, - "모서리 원을 드래그해서 크롭 영역 크기를 조정하세요" : { - + "모든 기기에서 로그아웃" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log out from all devices" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "全端末からログアウト" + } + } + } }, "목" : { "localizations" : { @@ -4165,21 +4193,8 @@ } } }, - "모든 기기에서 로그아웃" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Log out from all devices" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "全端末からログアウト" - } - } - } + "모서리 원을 드래그해서 크롭 영역 크기를 조정하세요" : { + }, "모집완료" : { "localizations" : { @@ -5532,6 +5547,7 @@ } }, "스피커 초대" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -8682,22 +8698,6 @@ } } }, - "캔" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cans" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "CAN" - } - } - } - }, "캐릭터 정보" : { "localizations" : { "en" : { @@ -8714,6 +8714,22 @@ } } }, + "캔" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cans" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CAN" + } + } + } + }, "캔 충전" : { "localizations" : { "en" : { @@ -8988,6 +9004,7 @@ } }, "콘텐츠를 %@하시겠습니까?" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { diff --git a/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateView.swift b/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateView.swift index 4782723..aedbfd5 100644 --- a/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateView.swift +++ b/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateView.swift @@ -26,14 +26,14 @@ struct ContentPlaylistCreateView: View { .resizable() .frame(width: 20, height: 20) - Text("새 재생목록 만들기") + Text(I18n.Content.Playlist.createTitle) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.grayee) } Spacer() - Text("저장") + Text(I18n.Content.Playlist.createSave) .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.grayee) .frame(minHeight: 48) @@ -50,7 +50,7 @@ struct ContentPlaylistCreateView: View { .background(Color.black) HStack(spacing: 0) { - Text("재생목록 제목") + Text(I18n.Content.Playlist.titleLabel) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) @@ -83,7 +83,7 @@ struct ContentPlaylistCreateView: View { .padding(.horizontal, 13.3) HStack(spacing: 0) { - Text("재생목록 설명을 입력해 주세요") + Text(I18n.Content.Playlist.descriptionLabel) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) @@ -118,7 +118,7 @@ struct ContentPlaylistCreateView: View { HStack(spacing: 8) { Image("btn_plus_round") - Text("새로운 콘텐츠 추가/제거") + Text(I18n.Content.Playlist.addContentAction) .appFont(size: 14.7, weight: .bold) .foregroundColor(Color.button) } diff --git a/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateViewModel.swift b/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateViewModel.swift index b7669e5..be3df8d 100644 --- a/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateViewModel.swift +++ b/SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateViewModel.swift @@ -57,13 +57,13 @@ final class ContentPlaylistCreateViewModel: 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 self.isLoading = false } @@ -74,13 +74,13 @@ final class ContentPlaylistCreateViewModel: ObservableObject { private func validate() -> Bool { if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) { - errorMessage = "제목을 3자 이상 입력하세요" + errorMessage = I18n.Content.Playlist.titleValidation isShowPopup = true return false } if (contentList.isEmpty) { - errorMessage = "콘텐츠를 1개 이상 추가하세요" + errorMessage = I18n.Content.Playlist.contentValidation isShowPopup = true return false } diff --git a/SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentView.swift b/SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentView.swift index 02b573b..a72ec4e 100644 --- a/SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentView.swift +++ b/SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentView.swift @@ -17,14 +17,14 @@ struct PlaylistAddContentView: View { var body: some View { VStack(alignment: .leading, spacing: 13.3) { ZStack { - Text("새로운 콘텐츠 추가/제거") + Text(I18n.Content.Playlist.addContentAction) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.grayee) HStack(spacing: 0) { Spacer() - Text("닫기") + Text(I18n.Content.Playlist.close) .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.grayee) .frame(minHeight: 48) @@ -36,11 +36,11 @@ struct PlaylistAddContentView: View { .background(Color.black) HStack(alignment: .center, spacing: 5.3) { - Text("전체") + Text(I18n.Content.Playlist.totalLabel) .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.white) - Text("\(viewModel.totalCount)개") + Text(I18n.Content.Playlist.selectionCount(viewModel.totalCount)) .appFont(size: 12, weight: .medium) .foregroundColor(Color.gray90) } diff --git a/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift b/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift index e9e99a4..55fc882 100644 --- a/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift +++ b/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift @@ -125,13 +125,13 @@ struct ContentPlaylistDetailView: View { } HStack(spacing: 0) { - Text("만든 날짜 \(response.createdDate)") + Text(I18n.Content.Playlist.createdDate(response.createdDate)) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.gray90) Spacer() - Text("\(response.contentCount)개") + Text(I18n.Content.Playlist.contentCount(response.contentCount)) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) } @@ -141,7 +141,7 @@ struct ContentPlaylistDetailView: View { HStack(spacing: 5.3) { Image("ic_playlist_play") - Text("Play") + Text(I18n.Content.Playlist.play) .appFont(size: 14.7, weight: .bold) .foregroundColor(Color.white) } @@ -159,7 +159,7 @@ struct ContentPlaylistDetailView: View { HStack(spacing: 5.3) { Image("ic_playlist_shuffle") - Text("Shuffle") + Text(I18n.Content.Playlist.shuffle) .appFont(size: 14.7, weight: .bold) .foregroundColor(Color.white) } @@ -255,7 +255,7 @@ struct ContentPlaylistDetailView: View { Spacer() HStack(spacing: 13.3) { - Text("삭제") + Text(I18n.Common.delete) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.grayee) diff --git a/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift b/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift index 4e88e22..b9738e1 100644 --- a/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift +++ b/SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift @@ -50,13 +50,13 @@ final class ContentPlaylistDetailViewModel: 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 } @@ -84,7 +84,7 @@ final class ContentPlaylistDetailViewModel: ObservableObject { let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) if decoded.success { - self.errorMessage = "삭제되었습니다." + self.errorMessage = I18n.Playlist.deleteCompleted self.isShowPopup = true DispatchQueue.main.asyncAfter(deadline: .now() + 1) { @@ -94,13 +94,13 @@ final class ContentPlaylistDetailViewModel: 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/Content/Playlist/Modify/ContentPlaylistModifyView.swift b/SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyView.swift index c502c36..8a16542 100644 --- a/SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyView.swift +++ b/SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyView.swift @@ -27,14 +27,14 @@ struct ContentPlaylistModifyView: View { .resizable() .frame(width: 20, height: 20) - Text("재생목록 수정") + Text(I18n.Content.Playlist.modifyTitle) .appFont(size: 18.3, weight: .bold) .foregroundColor(Color.grayee) } Spacer() - Text("수정") + Text(I18n.Content.Playlist.modifyAction) .appFont(size: 14.7, weight: .medium) .foregroundColor(Color.grayee) .frame(minHeight: 48) @@ -51,7 +51,7 @@ struct ContentPlaylistModifyView: View { .background(Color.black) HStack(spacing: 0) { - Text("재생목록 제목") + Text(I18n.Content.Playlist.titleLabel) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) @@ -84,7 +84,7 @@ struct ContentPlaylistModifyView: View { .padding(.horizontal, 13.3) HStack(spacing: 0) { - Text("재생목록 설명을 입력해 주세요") + Text(I18n.Content.Playlist.descriptionLabel) .appFont(size: 16.7, weight: .bold) .foregroundColor(Color.grayee) @@ -119,7 +119,7 @@ struct ContentPlaylistModifyView: View { HStack(spacing: 8) { Image("btn_plus_round") - Text("새로운 콘텐츠 추가/제거") + Text(I18n.Content.Playlist.addContentAction) .appFont(size: 14.7, weight: .bold) .foregroundColor(Color.button) } diff --git a/SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyViewModel.swift b/SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyViewModel.swift index f409dc9..c4c6d6f 100644 --- a/SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyViewModel.swift +++ b/SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyViewModel.swift @@ -57,13 +57,13 @@ final class ContentPlaylistModifyViewModel: 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 } @@ -112,13 +112,13 @@ final class ContentPlaylistModifyViewModel: 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 self.isLoading = false } @@ -129,13 +129,13 @@ final class ContentPlaylistModifyViewModel: ObservableObject { private func validate() -> Bool { if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) { - errorMessage = "제목을 3자 이상 입력하세요" + errorMessage = I18n.Content.Playlist.titleValidation isShowPopup = true return false } if (contentList.isEmpty) { - errorMessage = "콘텐츠를 1개 이상 추가하세요" + errorMessage = I18n.Content.Playlist.contentValidation isShowPopup = true return false } diff --git a/SodaLive/Sources/Content/Series/Content/SeriesContentAllView.swift b/SodaLive/Sources/Content/Series/Content/SeriesContentAllView.swift index f54b67d..a314298 100644 --- a/SodaLive/Sources/Content/Series/Content/SeriesContentAllView.swift +++ b/SodaLive/Sources/Content/Series/Content/SeriesContentAllView.swift @@ -18,12 +18,12 @@ struct SeriesContentAllView: View { VStack(spacing: 0) { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { - DetailNavigationBar(title: "\(seriesTitle) - 전체회차 듣기") + DetailNavigationBar(title: I18n.Series.allEpisodesTitle(seriesTitle)) HStack(spacing: 13.3) { Spacer() - Text("최신순") + Text(I18n.Content.Sort.newest) .appFont(size: 13.3, weight: .medium) .foregroundColor( Color.graye2 @@ -35,7 +35,7 @@ struct SeriesContentAllView: View { } } - Text("등록순") + Text(I18n.Series.registeredOrder) .appFont(size: 13.3, weight: .medium) .foregroundColor( Color.graye2 diff --git a/SodaLive/Sources/Content/Series/Content/SeriesContentAllViewModel.swift b/SodaLive/Sources/Content/Series/Content/SeriesContentAllViewModel.swift index 6ec1b08..d100a7a 100644 --- a/SodaLive/Sources/Content/Series/Content/SeriesContentAllViewModel.swift +++ b/SodaLive/Sources/Content/Series/Content/SeriesContentAllViewModel.swift @@ -66,13 +66,13 @@ final class SeriesContentAllViewModel: 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/Content/Series/Content/SeriesContentListItemView.swift b/SodaLive/Sources/Content/Series/Content/SeriesContentListItemView.swift index bc7f1bb..4180358 100644 --- a/SodaLive/Sources/Content/Series/Content/SeriesContentListItemView.swift +++ b/SodaLive/Sources/Content/Series/Content/SeriesContentListItemView.swift @@ -33,7 +33,7 @@ struct SeriesContentListItemView: View { .cornerRadius(2.6) if item.isPointAvailable { - Text("포인트") + Text(I18n.Series.point) .appFont(size: 8, weight: .medium) .foregroundColor(.white) .padding(2.6) @@ -50,7 +50,7 @@ struct SeriesContentListItemView: View { Spacer() if item.isOwned { - Text("소장중") + Text(I18n.Content.Status.owned) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.gray11) .padding(.horizontal, 5.3) @@ -58,7 +58,7 @@ struct SeriesContentListItemView: View { .background(Color(hex: "b1ef2c")) .cornerRadius(2.6) } else if item.isRented { - Text("대여중") + Text(I18n.Content.Status.rented) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.white) .padding(.horizontal, 5.3) @@ -74,7 +74,7 @@ struct SeriesContentListItemView: View { .foregroundColor(Color(hex: "909090")) } } else { - Text("무료") + Text(I18n.Series.free) .appFont(size: 13.3, weight: .medium) .foregroundColor(Color.white) .padding(.horizontal, 5.3) diff --git a/SodaLive/Sources/Content/Series/DayOfWeekSeriesView.swift b/SodaLive/Sources/Content/Series/DayOfWeekSeriesView.swift index ef4f196..92e18e9 100644 --- a/SodaLive/Sources/Content/Series/DayOfWeekSeriesView.swift +++ b/SodaLive/Sources/Content/Series/DayOfWeekSeriesView.swift @@ -8,7 +8,7 @@ import SwiftUI struct DayOfWeek { - let dayOfWeekStr: LocalizedStringResource + let dayOfWeekStr: String let dayOfWeek: SeriesPublishedDaysOfWeek } @@ -20,14 +20,14 @@ struct DayOfWeekSeriesView: View { @State private var dayOfWeek: SeriesPublishedDaysOfWeek = .FRI private let dayOfWeekItems: [DayOfWeek] = [ - DayOfWeek(dayOfWeekStr: "월", dayOfWeek: .MON), - DayOfWeek(dayOfWeekStr: "화", dayOfWeek: .TUE), - DayOfWeek(dayOfWeekStr: "수", dayOfWeek: .WED), - DayOfWeek(dayOfWeekStr: "목", dayOfWeek: .THU), - DayOfWeek(dayOfWeekStr: "금", dayOfWeek: .FRI), - DayOfWeek(dayOfWeekStr: "토", dayOfWeek: .SAT), - DayOfWeek(dayOfWeekStr: "일", dayOfWeek: .SUN), - DayOfWeek(dayOfWeekStr: "랜덤", dayOfWeek: .RANDOM), + DayOfWeek(dayOfWeekStr: I18n.Series.monday, dayOfWeek: .MON), + DayOfWeek(dayOfWeekStr: I18n.Series.tuesday, dayOfWeek: .TUE), + DayOfWeek(dayOfWeekStr: I18n.Series.wednesday, dayOfWeek: .WED), + DayOfWeek(dayOfWeekStr: I18n.Series.thursday, dayOfWeek: .THU), + DayOfWeek(dayOfWeekStr: I18n.Series.friday, dayOfWeek: .FRI), + DayOfWeek(dayOfWeekStr: I18n.Series.saturday, dayOfWeek: .SAT), + DayOfWeek(dayOfWeekStr: I18n.Series.sunday, dayOfWeek: .SUN), + DayOfWeek(dayOfWeekStr: I18n.Series.random, dayOfWeek: .RANDOM), ] // 요일 숫자에 맞춰 배열 @@ -45,13 +45,13 @@ struct DayOfWeekSeriesView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { HStack(spacing: 0) { - Text("요일별 시리즈") + Text(I18n.Series.byDaySectionTitle) .appFont(size: 24, weight: .bold) .foregroundColor(.white) Spacer() - Text("전체보기") + Text(I18n.Common.viewAll) .appFont(size: 14, weight: .regular) .foregroundColor(.init(hex: "78909C")) .onTapGesture { @@ -93,7 +93,7 @@ struct DayOfWeekSeriesView: View { } struct DayOfWeekDayView: View { - let dayOfWeek: LocalizedStringResource + let dayOfWeek: String let isSelected: Bool var body: some View { diff --git a/SodaLive/Sources/Content/Series/Detail/SeriesDetailHomeView.swift b/SodaLive/Sources/Content/Series/Detail/SeriesDetailHomeView.swift index f3b3ec5..704d6c3 100644 --- a/SodaLive/Sources/Content/Series/Detail/SeriesDetailHomeView.swift +++ b/SodaLive/Sources/Content/Series/Detail/SeriesDetailHomeView.swift @@ -17,7 +17,7 @@ struct SeriesDetailHomeView: View { var body: some View { VStack(spacing: 0) { HStack(spacing: 0) { - Text("전체회차 듣기") + Text(I18n.Series.allEpisodesListen) .appFont(size: 16, weight: .bold) .foregroundColor(Color.button) diff --git a/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift b/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift index c35daea..c2c0541 100644 --- a/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift +++ b/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift @@ -90,7 +90,7 @@ struct SeriesDetailView: View { .cornerRadius(2.6) if seriesDetail.isAdult { - Text("19세") + Text(I18n.Series.age19Badge) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "f1291c")) .padding(.horizontal, 5.3) @@ -98,7 +98,7 @@ struct SeriesDetailView: View { .background(Color(hex: "312827")) .cornerRadius(2.6) } else { - Text("전체연령가") + Text(I18n.SeriesDetail.ageAll) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "d2d2d2")) .padding(.horizontal, 5.3) @@ -107,7 +107,7 @@ struct SeriesDetailView: View { .cornerRadius(2.6) } - Text("\(seriesDetail.publishedDaysOfWeek) 연재") + Text(I18n.Series.publishing(seriesDetail.publishedDaysOfWeek)) .appFont(size: 12, weight: .medium) .foregroundColor(Color(hex: "909090")) } diff --git a/SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift b/SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift index f1fbb1f..597609a 100644 --- a/SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift +++ b/SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift @@ -50,13 +50,13 @@ final class SeriesDetailViewModel: 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 } @@ -91,13 +91,13 @@ final class SeriesDetailViewModel: 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 } @@ -131,13 +131,13 @@ final class SeriesDetailViewModel: 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/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift b/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift index d71d869..524357b 100644 --- a/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift +++ b/SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift @@ -62,13 +62,13 @@ final class SeriesMainByGenreViewModel: 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 } } @@ -109,13 +109,13 @@ final class SeriesMainByGenreViewModel: 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/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift b/SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift index 659e8a7..5d7a722 100644 --- a/SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift +++ b/SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift @@ -15,14 +15,14 @@ struct SeriesMainDayOfWeekView: View { @State private var dayOfWeek: SeriesPublishedDaysOfWeek = .SAT private let dayOfWeekItems: [DayOfWeek] = [ - DayOfWeek(dayOfWeekStr: "월", dayOfWeek: .MON), - DayOfWeek(dayOfWeekStr: "화", dayOfWeek: .TUE), - DayOfWeek(dayOfWeekStr: "수", dayOfWeek: .WED), - DayOfWeek(dayOfWeekStr: "목", dayOfWeek: .THU), - DayOfWeek(dayOfWeekStr: "금", dayOfWeek: .FRI), - DayOfWeek(dayOfWeekStr: "토", dayOfWeek: .SAT), - DayOfWeek(dayOfWeekStr: "일", dayOfWeek: .SUN), - DayOfWeek(dayOfWeekStr: "랜덤", dayOfWeek: .RANDOM), + DayOfWeek(dayOfWeekStr: I18n.Series.monday, dayOfWeek: .MON), + DayOfWeek(dayOfWeekStr: I18n.Series.tuesday, dayOfWeek: .TUE), + DayOfWeek(dayOfWeekStr: I18n.Series.wednesday, dayOfWeek: .WED), + DayOfWeek(dayOfWeekStr: I18n.Series.thursday, dayOfWeek: .THU), + DayOfWeek(dayOfWeekStr: I18n.Series.friday, dayOfWeek: .FRI), + DayOfWeek(dayOfWeekStr: I18n.Series.saturday, dayOfWeek: .SAT), + DayOfWeek(dayOfWeekStr: I18n.Series.sunday, dayOfWeek: .SUN), + DayOfWeek(dayOfWeekStr: I18n.Series.random, dayOfWeek: .RANDOM), ] // 요일 숫자에 맞춰 배열 diff --git a/SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekViewModel.swift b/SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekViewModel.swift index 18eb474..a96b36e 100644 --- a/SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekViewModel.swift +++ b/SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekViewModel.swift @@ -62,13 +62,13 @@ final class SeriesMainDayOfWeekViewModel: 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/Content/Series/Main/Home/SeriesMainHomeView.swift b/SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeView.swift index 8f260a8..c491875 100644 --- a/SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeView.swift +++ b/SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeView.swift @@ -24,13 +24,13 @@ struct SeriesMainHomeView: View { if !viewModel.completedSeriesList.isEmpty { VStack(alignment: .leading, spacing: 16) { HStack(spacing: 0) { - Text("완결 시리즈") + Text(I18n.Series.completedSectionTitle) .appFont(size: 24, weight: .bold) .foregroundColor(.white) Spacer() - Text("전체보기") + Text(I18n.Common.viewAll) .appFont(size: 14, weight: .regular) .foregroundColor(.init(hex: "78909C")) .onTapGesture { @@ -58,7 +58,7 @@ struct SeriesMainHomeView: View { if !viewModel.recommendSeriesList.isEmpty { VStack(alignment: .leading, spacing: 16) { HStack(spacing: 0) { - Text("추천 시리즈") + Text(I18n.Series.recommendedSectionTitle) .appFont(size: 24, weight: .bold) .foregroundColor(.white) diff --git a/SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeViewModel.swift b/SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeViewModel.swift index 13d7ae9..b1793f6 100644 --- a/SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeViewModel.swift +++ b/SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeViewModel.swift @@ -45,13 +45,13 @@ final class SeriesMainHomeViewModel: 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 } @@ -83,13 +83,13 @@ final class SeriesMainHomeViewModel: 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/Content/Series/Main/SeriesMainItemView.swift b/SodaLive/Sources/Content/Series/Main/SeriesMainItemView.swift index 2b16290..96c2427 100644 --- a/SodaLive/Sources/Content/Series/Main/SeriesMainItemView.swift +++ b/SodaLive/Sources/Content/Series/Main/SeriesMainItemView.swift @@ -27,7 +27,7 @@ struct SeriesMainItemView: View { VStack(alignment: .leading, spacing: 0) { HStack(spacing: 0) { if item.isPopular { - Text("인기") + Text(I18n.Series.popular) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) @@ -37,7 +37,7 @@ struct SeriesMainItemView: View { } if item.isNew { - Text("신작") + Text(I18n.Series.new) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) @@ -56,7 +56,7 @@ struct SeriesMainItemView: View { } if item.isComplete { - Text("완결") + Text(I18n.Series.complete) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) @@ -71,7 +71,7 @@ struct SeriesMainItemView: View { HStack { Spacer() - Text("총 \(item.numberOfContent)화") + Text(I18n.Series.totalEpisodes(item.numberOfContent)) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) diff --git a/SodaLive/Sources/Content/Series/Main/SeriesMainView.swift b/SodaLive/Sources/Content/Series/Main/SeriesMainView.swift index 512440b..d0e7bee 100644 --- a/SodaLive/Sources/Content/Series/Main/SeriesMainView.swift +++ b/SodaLive/Sources/Content/Series/Main/SeriesMainView.swift @@ -28,7 +28,7 @@ struct SeriesMainView: View { Group { BaseView { VStack(spacing: 0) { - DetailNavigationBar(title: "시리즈 전체보기") + DetailNavigationBar(title: I18n.Series.title) // 내부 탭 (캐릭터 / 톡) HStack(spacing: 0) { ChatInnerTab( diff --git a/SodaLive/Sources/Content/Series/SeriesItemView.swift b/SodaLive/Sources/Content/Series/SeriesItemView.swift index a717993..57acd78 100644 --- a/SodaLive/Sources/Content/Series/SeriesItemView.swift +++ b/SodaLive/Sources/Content/Series/SeriesItemView.swift @@ -27,7 +27,7 @@ struct SeriesItemView: View { VStack(alignment: .leading, spacing: 0) { HStack(spacing: 0) { if item.isPopular { - Text("인기") + Text(I18n.Series.popular) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) @@ -37,7 +37,7 @@ struct SeriesItemView: View { } if item.isNew { - Text("신작") + Text(I18n.Series.new) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) @@ -56,7 +56,7 @@ struct SeriesItemView: View { } if item.isComplete { - Text("완결") + Text(I18n.Series.complete) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) @@ -71,7 +71,7 @@ struct SeriesItemView: View { HStack { Spacer() - Text("총 \(item.numberOfContent)화") + Text(I18n.Series.totalEpisodes(item.numberOfContent)) .appFont(size: 12, weight: .regular) .foregroundColor(.white) .padding(.horizontal, 10) diff --git a/SodaLive/Sources/Content/Series/SeriesListAllView.swift b/SodaLive/Sources/Content/Series/SeriesListAllView.swift index 17caa4d..bae91ef 100644 --- a/SodaLive/Sources/Content/Series/SeriesListAllView.swift +++ b/SodaLive/Sources/Content/Series/SeriesListAllView.swift @@ -23,9 +23,9 @@ struct SeriesListAllView: View { BaseView(isLoading: $viewModel.isLoading) { VStack(spacing: 0) { if isCompleted { - DetailNavigationBar(title: "완결 시리즈") + DetailNavigationBar(title: I18n.Series.completedSectionTitle) } else if isOriginal { - DetailNavigationBar(title: "오직 보이스온에서만") + DetailNavigationBar(title: I18n.Series.voiceOnOnlyTitle) } else { DetailNavigationBar(title: I18n.Series.viewAllByCreator(creatorNickname ?? "")) } diff --git a/SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift b/SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift index 07b5818..9351b26 100644 --- a/SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift +++ b/SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift @@ -63,13 +63,13 @@ final class SeriesListAllViewModel: 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/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index d16a0ec..65a6c0e 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -767,6 +767,21 @@ enum I18n { static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "全体") } static func totalCount(_ count: Int) -> String { pick(ko: "총 \(count)개", en: "Total \(count)", ja: "全\(count)件") } static func itemCount(_ count: Int) -> String { pick(ko: "총 \(count)개", en: "Total \(count)", ja: "全\(count)件") } + static var createTitle: String { pick(ko: "새 재생목록 만들기", en: "Create new playlist", ja: "新しいプレイリストを作成") } + static var createSave: String { pick(ko: "저장", en: "Save", ja: "保存") } + static var modifyTitle: String { pick(ko: "재생목록 수정", en: "Edit playlist", ja: "プレイリストを編集") } + static var modifyAction: String { pick(ko: "수정", en: "Edit", ja: "編集") } + static var titleLabel: String { pick(ko: "재생목록 제목", en: "Playlist title", ja: "プレイリストタイトル") } + static var descriptionLabel: String { pick(ko: "재생목록 설명을 입력해 주세요", en: "Please enter a playlist description", ja: "プレイリストの説明を入力してください") } + static var addContentAction: String { pick(ko: "새로운 콘텐츠 추가/제거", en: "Add/remove content", ja: "新しいコンテンツを追加・削除") } + static var close: String { pick(ko: "닫기", en: "Close", ja: "閉じる") } + static func selectionCount(_ count: Int) -> String { pick(ko: "\(count)개", en: "\(count)", ja: "\(count)件") } + static func createdDate(_ date: String) -> String { pick(ko: "만든 날짜 \(date)", en: "Created on \(date)", ja: "作成日 \(date)") } + static func contentCount(_ count: Int) -> String { pick(ko: "\(count)개", en: "\(count)", ja: "\(count)件") } + static var play: String { pick(ko: "재생", en: "Play", ja: "再生") } + static var shuffle: String { pick(ko: "셔플", en: "Shuffle", ja: "シャッフル") } + static var titleValidation: String { pick(ko: "제목을 3자 이상 입력하세요", en: "Enter at least 3 characters for the title.", ja: "タイトルを3文字以上入力してください") } + static var contentValidation: String { pick(ko: "콘텐츠를 1개 이상 추가하세요", en: "Add at least one content item.", ja: "コンテンツを1件以上追加してください") } } } @@ -2663,6 +2678,10 @@ enum I18n { ja: "\(title)を削除しますか?" ) } + + static var deleteCompleted: String { + pick(ko: "삭제되었습니다.", en: "Deleted.", ja: "削除されました。") + } } enum Category { @@ -3670,6 +3689,26 @@ If you block this user, the following features will be restricted. static var new: String { pick(ko: "신작", en: "New", ja: "新作") } static var complete: String { pick(ko: "완결", en: "Completed", ja: "完結") } static var popular: String { pick(ko: "인기", en: "Popular", ja: "人気") } + static var title: String { pick(ko: "시리즈 전체보기", en: "All series", ja: "シリーズ一覧") } + static var completedSectionTitle: String { pick(ko: "완결 시리즈", en: "Completed series", ja: "完結シリーズ") } + static var recommendedSectionTitle: String { pick(ko: "추천 시리즈", en: "Recommended series", ja: "おすすめシリーズ") } + static var byDaySectionTitle: String { pick(ko: "요일별 시리즈", en: "Series by day", ja: "曜日別シリーズ") } + static var voiceOnOnlyTitle: String { pick(ko: "오직 보이스온에서만", en: "Only on VoiceOn", ja: "VoiceOn限定") } + static var allEpisodesListen: String { pick(ko: "전체회차 듣기", en: "Listen to all episodes", ja: "全話を聴く") } + static func allEpisodesTitle(_ seriesTitle: String) -> String { pick(ko: "\(seriesTitle) - 전체회차 듣기", en: "\(seriesTitle) - All episodes", ja: "\(seriesTitle) - 全話を聴く") } + static var registeredOrder: String { pick(ko: "등록순", en: "Oldest", ja: "登録順") } + static var point: String { pick(ko: "포인트", en: "Points", ja: "ポイント") } + static var free: String { pick(ko: "무료", en: "Free", ja: "無料") } + static var monday: String { pick(ko: "월", en: "Mon", ja: "月") } + static var tuesday: String { pick(ko: "화", en: "Tue", ja: "火") } + static var wednesday: String { pick(ko: "수", en: "Wed", ja: "水") } + static var thursday: String { pick(ko: "목", en: "Thu", ja: "木") } + static var friday: String { pick(ko: "금", en: "Fri", ja: "金") } + static var saturday: String { pick(ko: "토", en: "Sat", ja: "土") } + static var sunday: String { pick(ko: "일", en: "Sun", ja: "日") } + static var random: String { pick(ko: "랜덤", en: "Random", ja: "ランダム") } + static var age19Badge: String { pick(ko: "19세", en: "19+", ja: "19+") } + static func publishing(_ days: String) -> String { pick(ko: "\(days) 연재", en: "\(days)", ja: "\(days)連載") } static var totalEpisodes: (Int) -> String = { count in pick(ko: "총 \(count)화", en: "Total \(count) episodes", ja: "全\(count)話") } diff --git a/docs/20260331_하드코딩텍스트_I18n통일계획.md b/docs/20260331_하드코딩텍스트_I18n통일계획.md index ae0c085..c613980 100644 --- a/docs/20260331_하드코딩텍스트_I18n통일계획.md +++ b/docs/20260331_하드코딩텍스트_I18n통일계획.md @@ -201,38 +201,38 @@ - [x] `SodaLive/Sources/Content/Playlist/ContentPlaylistListViewModel.swift` #### Group 6 (51-60) -- [ ] `SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateView.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateViewModel.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentItemView.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentView.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Create/PlaylistCreateContentView.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Detail/PlaylistContentItemView.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyView.swift` -- [ ] `SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyViewModel.swift` +- [x] `SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateView.swift` +- [x] `SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateViewModel.swift` +- [x] `SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentItemView.swift` +- [x] `SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentView.swift` +- [x] `SodaLive/Sources/Content/Playlist/Create/PlaylistCreateContentView.swift` +- [x] `SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift` +- [x] `SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift` +- [x] `SodaLive/Sources/Content/Playlist/Detail/PlaylistContentItemView.swift` +- [x] `SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyView.swift` +- [x] `SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyViewModel.swift` #### Group 7 (61-70) -- [ ] `SodaLive/Sources/Content/Series/Content/SeriesContentAllView.swift` -- [ ] `SodaLive/Sources/Content/Series/Content/SeriesContentAllViewModel.swift` -- [ ] `SodaLive/Sources/Content/Series/Content/SeriesContentListItemView.swift` -- [ ] `SodaLive/Sources/Content/Series/DayOfWeekSeriesView.swift` -- [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailHomeView.swift` -- [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailIntroductionView.swift` -- [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift` -- [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift` -- [ ] `SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift` -- [ ] `SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift` +- [x] `SodaLive/Sources/Content/Series/Content/SeriesContentAllView.swift` +- [x] `SodaLive/Sources/Content/Series/Content/SeriesContentAllViewModel.swift` +- [x] `SodaLive/Sources/Content/Series/Content/SeriesContentListItemView.swift` +- [x] `SodaLive/Sources/Content/Series/DayOfWeekSeriesView.swift` +- [x] `SodaLive/Sources/Content/Series/Detail/SeriesDetailHomeView.swift` +- [x] `SodaLive/Sources/Content/Series/Detail/SeriesDetailIntroductionView.swift` +- [x] `SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift` +- [x] `SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift` +- [x] `SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift` +- [x] `SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift` #### Group 8 (71-78) -- [ ] `SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekViewModel.swift` -- [ ] `SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeView.swift` -- [ ] `SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeViewModel.swift` -- [ ] `SodaLive/Sources/Content/Series/Main/SeriesMainItemView.swift` -- [ ] `SodaLive/Sources/Content/Series/Main/SeriesMainView.swift` -- [ ] `SodaLive/Sources/Content/Series/SeriesItemView.swift` -- [ ] `SodaLive/Sources/Content/Series/SeriesListAllView.swift` -- [ ] `SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift` +- [x] `SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekViewModel.swift` +- [x] `SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeView.swift` +- [x] `SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeViewModel.swift` +- [x] `SodaLive/Sources/Content/Series/Main/SeriesMainItemView.swift` +- [x] `SodaLive/Sources/Content/Series/Main/SeriesMainView.swift` +- [x] `SodaLive/Sources/Content/Series/SeriesItemView.swift` +- [x] `SodaLive/Sources/Content/Series/SeriesListAllView.swift` +- [x] `SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift` ### CustomView (3) - [x] `SodaLive/Sources/CustomView/ChatTextFieldView.swift` @@ -1087,3 +1087,40 @@ - 테스트 검증: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 test action 미구성 확인(코드 실패 아님, 스킴 제약). - 수동 QA(문구 경로 수동 점검): Group 3~5 대상 파일 재스캔에서 사용자 노출 문자열의 `I18n.*` 경유를 확인했고, 예외(Preview/비노출 비교 문자열)만 잔존함을 확인했다. - LSP 진단 참고: SourceKit 단독 해석 환경에서 외부 모듈/프로젝트 심볼(`Kingfisher`, `Moya`, `I18n`, `AppState` 등) 미해결 오류가 보고되나, 동일 변경셋은 `xcodebuild` 실컴파일 통과로 검증했다. + +### 24차 구현 (Content 모듈 Group 6~8, 28개 파일 처리, 2026-04-01) +- 무엇/왜/어떻게: + - 무엇: `변경 대상 파일 전체 목록`의 `Content` Group 6~8(플레이리스트 생성/수정/상세 + 시리즈 목록/상세/메인, 28개 파일)을 전수 점검하고, 런타임 사용자 노출 하드코딩 문구를 `I18n.*` 참조로 전환했다. + - 왜: Playlist/Series 구간에 화면 타이틀, 버튼, 상태 배지, 정렬 라벨, ViewModel 공통 오류 문구가 하드코딩 상태로 남아 있어 `I18n.swift` 단일 접근 원칙과 불일치했기 때문이다. + - 어떻게: `explore`/`librarian` 병렬 탐색 + `grep`/`ast_grep_search`/`read` 직접 점검으로 런타임 문자열과 Preview 샘플 문자열을 분리한 뒤, `I18n.swift`의 `I18n.Content.Playlist`, `I18n.Playlist`, `I18n.Series`를 확장하고 Group 6~8 호출부를 최소 변경으로 치환했다. +- 실행 명령/도구: + - `task(subagent_type="explore", ...)` x2 (`bg_fa2a12ab`, `bg_56444808`) + - `task(subagent_type="librarian", ...)` x1 (`bg_fd44cf0e`) + - `background_output(task_id=...)` x3 (위 3개 task 결과 수집) + - `grep("\"[^\"]*[가-힣][^\"]*\"", include=*.swift, path=SodaLive/Sources/Content/{Playlist,Series})` + - `grep("\\bI18n\\.(Content|Series|Common)", include=*.swift, path=SodaLive/Sources/Content)` + - `ast_grep_search(pattern="Text(\"$TEXT\")", lang=swift, paths=[SodaLive/Sources/Content/Playlist,SodaLive/Sources/Content/Series])` + - `read(SodaLive/Sources/I18n/I18n.swift)` + 대상 파일 병렬 `read` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test` + - `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test` +- 결과: + - `I18n.swift` 확장: + - `I18n.Content.Playlist`에 생성/수정/상세 플로우용 키 추가(`createTitle`, `createSave`, `modifyTitle`, `modifyAction`, `titleLabel`, `descriptionLabel`, `addContentAction`, `close`, `selectionCount(_:)`, `createdDate(_:)`, `contentCount(_:)`, `play`, `shuffle`, 검증 문구 2종) + - `I18n.Playlist.deleteCompleted` 추가 + - `I18n.Series`에 메인/상세/요일/상태 배지 키 추가(`title`, `completedSectionTitle`, `recommendedSectionTitle`, `byDaySectionTitle`, `voiceOnOnlyTitle`, `allEpisodesListen`, `allEpisodesTitle(_:)`, `registeredOrder`, `point`, `free`, 요일 8종, `age19Badge`, `publishing(_:)`) + - 치환 완료 파일(실치환 20개): + - Playlist: `ContentPlaylistCreateView`, `ContentPlaylistCreateViewModel`, `PlaylistAddContentView`, `ContentPlaylistDetailView`, `ContentPlaylistDetailViewModel`, `ContentPlaylistModifyView`, `ContentPlaylistModifyViewModel` + - Series: `SeriesContentAllView`, `SeriesContentAllViewModel`, `SeriesContentListItemView`, `DayOfWeekSeriesView`, `SeriesDetailHomeView`, `SeriesDetailView`, `SeriesDetailViewModel`, `SeriesMainByGenreViewModel`, `SeriesMainDayOfWeekView`, `SeriesMainDayOfWeekViewModel`, `SeriesMainHomeView`, `SeriesMainHomeViewModel`, `SeriesMainItemView`, `SeriesMainView`, `SeriesItemView`, `SeriesListAllView`, `SeriesListAllViewModel` + - 점검만 수행(실치환 없음, 체크 완료 8개): + - `PlaylistAddContentItemView.swift`, `PlaylistCreateContentView.swift`, `PlaylistContentItemView.swift` (런타임 하드코딩 없음, Preview 샘플만 존재) + - `SeriesDetailIntroductionView.swift` (런타임 사용자 노출 하드코딩 없음; 잔여 한글은 서버값 비교용 `"랜덤"` 분기만 존재) + - Group 6~8 체크박스 28개 `- [x]` 완료 반영. + - 재탐지 결과, 남은 한글 리터럴은 Playlist/Series Preview 샘플 데이터와 `SeriesDetailIntroductionView.swift`의 비노출 비교 문자열(`publishedDaysOfWeek == "랜덤"`)만 존재. + - 빌드 검증: + - `SodaLive-dev` Debug 빌드 성공(`** BUILD SUCCEEDED **`). + - `SodaLive` Debug 빌드는 병렬 실행 시 `XCBuildData/build.db` lock으로 1회 실패했으나, 단독 재실행에서 성공(`** BUILD SUCCEEDED **`). + - 테스트 검증: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 test action 미구성 확인(코드 실패 아님, 스킴 제약). + - 수동 QA(문구 경로 수동 점검): Group 6~8 대상 파일 재스캔에서 사용자 노출 문자열의 `I18n.*` 경유를 확인했고, 예외는 Preview/비노출 비교 문자열만 남김. + - LSP 진단 참고: SourceKit 단독 해석 환경에서 외부 모듈/프로젝트 심볼 미해결 오류(`Kingfisher`, `I18n`, `BaseView`, `AppState` 등)와 `I18n.swift`의 기존 `LanguageHeaderProvider` 미해결이 보고되나, 동일 변경셋은 `xcodebuild` 실컴파일 통과로 검증했다.