diff --git a/SodaLive/Resources/Localizable.xcstrings b/SodaLive/Resources/Localizable.xcstrings index ff576ab..069ab87 100644 --- a/SodaLive/Resources/Localizable.xcstrings +++ b/SodaLive/Resources/Localizable.xcstrings @@ -722,6 +722,7 @@ } }, "%@ (5일)" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -753,6 +754,16 @@ } } }, + "%@ %@" : { + "localizations" : { + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ %2$@" + } + } + } + }, "%@ 님의 시리즈 전체보기" : { "localizations" : { "en" : { @@ -4065,22 +4076,6 @@ } } }, - "목" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thu" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "木" - } - } - } - }, "모집완료" : { "localizations" : { "en" : { @@ -4113,6 +4108,22 @@ } } }, + "목" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thu" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "木" + } + } + } + }, "무료" : { "localizations" : { "en" : { @@ -6769,6 +6780,54 @@ } } }, + "이벤트" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Events" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "イベント" + } + } + } + }, + "인기 캐릭터" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Popular Characters" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "人気キャラクター" + } + } + } + }, + "일" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sun" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日" + } + } + } + }, "이메일" : { "localizations" : { "en" : { @@ -6801,22 +6860,6 @@ } } }, - "일" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sun" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "日" - } - } - } - }, "이미지" : { "localizations" : { "en" : { @@ -6849,22 +6892,6 @@ } } }, - "이벤트" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Events" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "イベント" - } - } - } - }, "이벤트 참여하기" : { "localizations" : { "en" : { @@ -6945,22 +6972,6 @@ } } }, - "인기 캐릭터" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Popular Characters" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "人気キャラクター" - } - } - } - }, "인기 캐릭터 채팅" : { "localizations" : { "en" : { @@ -8513,22 +8524,6 @@ } } }, - "캐릭터 정보" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Character info" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "キャラクター情報" - } - } - } - }, "캔" : { "localizations" : { "en" : { @@ -8545,6 +8540,22 @@ } } }, + "캐릭터 정보" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Character info" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "キャラクター情報" + } + } + } + }, "캔 충전" : { "localizations" : { "en" : { @@ -9795,4 +9806,4 @@ } }, "version" : "1.1" -} +} \ No newline at end of file diff --git a/SodaLive/Sources/Content/Series/Detail/SeriesDetailIntroductionView.swift b/SodaLive/Sources/Content/Series/Detail/SeriesDetailIntroductionView.swift index 44e95e3..6f3974c 100644 --- a/SodaLive/Sources/Content/Series/Detail/SeriesDetailIntroductionView.swift +++ b/SodaLive/Sources/Content/Series/Detail/SeriesDetailIntroductionView.swift @@ -15,7 +15,7 @@ struct SeriesDetailIntroductionView: View { var body: some View { VStack(alignment: .leading, spacing: 16) { - Text("키워드") + Text(I18n.SeriesDetail.keywords) .font(.custom(Font.bold.rawValue, size: 14.7)) .foregroundColor(Color.grayee) .padding(.top, 16) @@ -31,7 +31,7 @@ struct SeriesDetailIntroductionView: View { .foregroundColor(Color.gray22) VStack(alignment: .leading, spacing: 13.3) { - Text("작품소개") + Text(I18n.SeriesDetail.workIntro) .font(.custom(Font.bold.rawValue, size: 14.7)) .foregroundColor(Color.grayee) @@ -47,37 +47,37 @@ struct SeriesDetailIntroductionView: View { .foregroundColor(Color.gray22) VStack(alignment: .leading, spacing: 16) { - Text("상세정보") + Text(I18n.SeriesDetail.details) .font(.custom(Font.bold.rawValue, size: 14.7)) .foregroundColor(Color.grayee) HStack(spacing: 30) { VStack(alignment: .leading, spacing: 13.3) { - Text("장르") + Text(I18n.SeriesDetail.genre) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) - Text("연령제한") + Text(I18n.SeriesDetail.ageLimit) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) if let _ = seriesDetail.writer { - Text("작가") + Text(I18n.SeriesDetail.writer) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) } if let _ = seriesDetail.studio { - Text("제작사") + Text(I18n.SeriesDetail.studio) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) } - Text("연재") + Text(I18n.SeriesDetail.schedule) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) - Text("출시일") + Text(I18n.SeriesDetail.releaseDate) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) } @@ -87,7 +87,7 @@ struct SeriesDetailIntroductionView: View { .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.white) - Text(seriesDetail.isAdult ? "19세 이상" : "전체연령가") + Text(seriesDetail.isAdult ? I18n.SeriesDetail.age19Plus : I18n.SeriesDetail.ageAll) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.white) @@ -103,7 +103,7 @@ struct SeriesDetailIntroductionView: View { .foregroundColor(Color.white) } - Text(seriesDetail.publishedDaysOfWeek == "랜덤" ? seriesDetail.publishedDaysOfWeek : "\(seriesDetail.publishedDaysOfWeek)") + Text(seriesDetail.publishedDaysOfWeek == "랜덤" ? I18n.SeriesDetail.random : "\(seriesDetail.publishedDaysOfWeek)") .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.white) @@ -116,23 +116,23 @@ struct SeriesDetailIntroductionView: View { .padding(.horizontal, 13.3) VStack(alignment: .leading, spacing: 13.3) { - Text("가격") + Text(I18n.SeriesDetail.price) .font(.custom(Font.bold.rawValue, size: 14.7)) .foregroundColor(Color.grayee) HStack(spacing: 30) { VStack(alignment: .leading, spacing: 13.3) { - Text("대여") + Text(I18n.SeriesDetail.rentLabel) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) - Text("소장") + Text(I18n.SeriesDetail.buyLabel) .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.gray77) } VStack(alignment: .leading, spacing: 13.3) { - Text("\(calculatePriceInfo(seriesDetail.rentalMinPrice, seriesDetail.rentalMaxPrice)) (5일)") + Text("\(calculatePriceInfo(seriesDetail.rentalMinPrice, seriesDetail.rentalMaxPrice)) \(I18n.SeriesDetail.daysSuffix(5))") .font(.custom(Font.medium.rawValue, size: 14.7)) .foregroundColor(Color.button) @@ -147,14 +147,6 @@ struct SeriesDetailIntroductionView: View { } func calculatePriceInfo(_ minPrice: Int, _ maxPrice: Int) -> String { - if minPrice == maxPrice { - if maxPrice == 0 { - return "무료" - } else { - return "\(maxPrice)" - } - } else { - return "\(minPrice == 0 ? "무료" : "\(minPrice)") ~ \(maxPrice)캔" - } + I18n.SeriesDetail.priceInfo(min: minPrice, max: maxPrice) } } diff --git a/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift b/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift index ce11a42..a60a0fa 100644 --- a/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift +++ b/SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift @@ -174,7 +174,7 @@ struct SeriesDetailView: View { HStack(spacing: 0) { SeriesDetailTabView( - title: "홈", + title: I18n.SeriesDetail.home, width: screenSize().width / 2, isSelected: viewModel.currentTab == .home ) { @@ -182,9 +182,9 @@ struct SeriesDetailView: View { viewModel.currentTab = .home } } - + SeriesDetailTabView( - title: "작품소개", + title: I18n.SeriesDetail.introduction, width: screenSize().width / 2, isSelected: viewModel.currentTab == .introduction ) { diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index b2917d6..137f15e 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -269,6 +269,60 @@ enum I18n { } } + // 시리즈 상세 화면 관련 문자열 모음 + enum SeriesDetail { + // 탭 + static var home: String { pick(ko: "홈", en: "Home", ja: "ホーム") } + static var introduction: String { pick(ko: "작품소개", en: "Introduction", ja: "作品紹介") } + + // 섹션 타이틀 + static var keywords: String { pick(ko: "키워드", en: "Keywords", ja: "キーワード") } + static var workIntro: String { pick(ko: "작품소개", en: "Introduction", ja: "作品紹介") } + static var details: String { pick(ko: "상세정보", en: "Details", ja: "詳細情報") } + static var price: String { pick(ko: "가격", en: "Price", ja: "価格") } + + // 상세 라벨 + static var genre: String { pick(ko: "장르", en: "Genre", ja: "ジャンル") } + static var ageLimit: String { pick(ko: "연령제한", en: "Age rating", ja: "年齢制限") } + static var writer: String { pick(ko: "작가", en: "Writer", ja: "作家") } + static var studio: String { pick(ko: "제작사", en: "Studio", ja: "制作会社") } + static var schedule: String { pick(ko: "연재", en: "Schedule", ja: "連載") } + static var releaseDate: String { pick(ko: "출시일", en: "Release date", ja: "リリース日") } + + // 가격 라벨(명사형) + static var rentLabel: String { pick(ko: "대여", en: "Rent", ja: "レンタル") } + static var buyLabel: String { pick(ko: "소장", en: "Buy", ja: "購入") } + + // 값 표기 + static var ageAll: String { pick(ko: "전체연령가", en: "All ages", ja: "全年齢") } + static var age19Plus: String { pick(ko: "19세 이상", en: "19+", ja: "19歳以上") } + static var random: String { pick(ko: "랜덤", en: "Random", ja: "ランダム") } + + // 단위 및 포맷 + static func cansUnit(_ value: Int) -> String { + pick(ko: "\(value)캔", en: "\(value) cans", ja: "\(value)缶") + } + + static func daysSuffix(_ days: Int) -> String { + pick(ko: "(\(days)일)", en: "(\(days) day\(days == 1 ? "" : "s"))", ja: "(\(days)日)") + } + + // 가격 범위 표기: min==max==0 -> 무료, min==max>0 -> N캔, 범위 -> (무료|min) ~ max캔 + static func priceInfo(min: Int, max: Int) -> String { + if min == max { + if max == 0 { + return CreateContent.free + } else { + return cansUnit(max) + } + } else { + let left = (min == 0) ? CreateContent.free : "\(min)" + // 범위 구분자는 로케일과 무관하게 시각적 구분자 유지 + return "\(left) ~ \(cansUnit(max))" + } + } + } + // 콘텐츠 구매/대여 관련 공통 액션 라벨 enum Purchase { static var rent: String { pick(ko: "대여하기", en: "Rent", ja: "レンタルする") }