feat(i18n): 일본어 문자열을 최신화한다
This commit is contained in:
@@ -179,7 +179,7 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var detailChatButton: String {
|
static var detailChatButton: String {
|
||||||
pick(ko: "대화하기", en: "Start chat", ja: "会話する")
|
pick(ko: "대화하기", en: "Start chat", ja: "トークする")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var detailCollapse: String {
|
static var detailCollapse: String {
|
||||||
@@ -196,7 +196,7 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var totalPrefix: String {
|
static var totalPrefix: String {
|
||||||
pick(ko: "전체", en: "Total", ja: "全体")
|
pick(ko: "전체", en: "Total", ja: "すべて")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var countUnit: String {
|
static var countUnit: String {
|
||||||
@@ -505,7 +505,7 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var donation: String {
|
static var donation: String {
|
||||||
pick(ko: "후원", en: "Donate", ja: "後援")
|
pick(ko: "후원", en: "Donate", ja: "サポート")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var previewAlertMessage: String {
|
static var previewAlertMessage: String {
|
||||||
@@ -643,7 +643,7 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum DonationDialog {
|
enum DonationDialog {
|
||||||
static var title: String { pick(ko: "후원하기", en: "Donate", ja: "後援する") }
|
static var title: String { pick(ko: "후원하기", en: "Donate", ja: "サポートする") }
|
||||||
static var charge: String { pick(ko: "충전", en: "Charge", ja: "チャージ") }
|
static var charge: String { pick(ko: "충전", en: "Charge", ja: "チャージ") }
|
||||||
static var minimumOneCanPlaceholder: String { pick(ko: "1캔 이상 입력하세요", en: "Enter 1 can or more", ja: "1can以上入力してください") }
|
static var minimumOneCanPlaceholder: String { pick(ko: "1캔 이상 입력하세요", en: "Enter 1 can or more", ja: "1can以上入力してください") }
|
||||||
static var minimumTenCanPlaceholder: String { pick(ko: "10캔 이상 입력하세요", en: "Enter 10 cans or more", ja: "10can以上入力してください") }
|
static var minimumTenCanPlaceholder: String { pick(ko: "10캔 이상 입력하세요", en: "Enter 10 cans or more", ja: "10can以上入力してください") }
|
||||||
@@ -658,7 +658,7 @@ enum I18n {
|
|||||||
ja: "一緒に送る\(jaPrefix)メッセージを入力(最大\(limit)文字)"
|
ja: "一緒に送る\(jaPrefix)メッセージを入力(最大\(limit)文字)"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
static var donateAction: String { pick(ko: "후원하기", en: "Donate", ja: "後援する") }
|
static var donateAction: String { pick(ko: "후원하기", en: "Donate", ja: "サポートする") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enum ContentBox {
|
enum ContentBox {
|
||||||
@@ -734,7 +734,7 @@ enum I18n {
|
|||||||
|
|
||||||
enum Sort {
|
enum Sort {
|
||||||
static var newest: String {
|
static var newest: String {
|
||||||
pick(ko: "최신순", en: "Newest", ja: "新着順")
|
pick(ko: "최신순", en: "Newest", ja: "最新順")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var popularity: String {
|
static var popularity: String {
|
||||||
@@ -742,17 +742,17 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var priceHigh: String {
|
static var priceHigh: String {
|
||||||
pick(ko: "높은 가격순", en: "Highest price", ja: "価格の高い順")
|
pick(ko: "높은 가격순", en: "Highest price", ja: "高い順")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var priceLow: String {
|
static var priceLow: String {
|
||||||
pick(ko: "낮은 가격순", en: "Lowest price", ja: "価格の低い順")
|
pick(ko: "낮은 가격순", en: "Lowest price", ja: "安い順")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Count {
|
enum Count {
|
||||||
static var totalPrefix: String {
|
static var totalPrefix: String {
|
||||||
pick(ko: "전체", en: "Total", ja: "全体")
|
pick(ko: "전체", en: "Total", ja: "すべて")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var countUnit: String {
|
static var countUnit: String {
|
||||||
@@ -762,7 +762,7 @@ enum I18n {
|
|||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
static var owned: String {
|
static var owned: String {
|
||||||
pick(ko: "소장중", en: "Owned", ja: "所持中")
|
pick(ko: "소장중", en: "Owned", ja: "保有中")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var rented: String {
|
static var rented: String {
|
||||||
@@ -798,7 +798,7 @@ enum I18n {
|
|||||||
static var createNewAction: String { pick(ko: "+ 새 재생목록 만들기", en: "+ Create new playlist", ja: "+ 新しいプレイリストを作成") }
|
static var createNewAction: String { pick(ko: "+ 새 재생목록 만들기", en: "+ Create new playlist", ja: "+ 新しいプレイリストを作成") }
|
||||||
static var emptyTitle: String { pick(ko: "재생목록이 비어있습니다.", en: "Your playlist is empty.", ja: "プレイリストが空です。") }
|
static var emptyTitle: String { pick(ko: "재생목록이 비어있습니다.", en: "Your playlist is empty.", ja: "プレイリストが空です。") }
|
||||||
static var emptyDescription: String { pick(ko: "자주 듣는 콘텐츠를\n재생목록으로 만들어 보세요.", en: "Create a playlist with\nyour frequently listened content.", ja: "よく聴くコンテンツで\nプレイリストを作ってみましょう。") }
|
static var emptyDescription: String { pick(ko: "자주 듣는 콘텐츠를\n재생목록으로 만들어 보세요.", en: "Create a playlist with\nyour frequently listened content.", ja: "よく聴くコンテンツで\nプレイリストを作ってみましょう。") }
|
||||||
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "全体") }
|
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 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 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 createTitle: String { pick(ko: "새 재생목록 만들기", en: "Create new playlist", ja: "新しいプレイリストを作成") }
|
||||||
@@ -1175,7 +1175,7 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var sortNewest: String {
|
static var sortNewest: String {
|
||||||
pick(ko: "최신순", en: "Newest", ja: "新着順")
|
pick(ko: "최신순", en: "Newest", ja: "最新順")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var sortLikes: String {
|
static var sortLikes: String {
|
||||||
@@ -1246,7 +1246,7 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var reportAction: String {
|
static var reportAction: String {
|
||||||
pick(ko: "신고", en: "Report", ja: "報告する")
|
pick(ko: "신고", en: "Report", ja: "通報")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var cheersReasons: [String] {
|
static var cheersReasons: [String] {
|
||||||
@@ -1616,7 +1616,7 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var all: String {
|
static var all: String {
|
||||||
pick(ko: "전체", en: "All", ja: "全体")
|
pick(ko: "전체", en: "All", ja: "すべて")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var maleOriented: String {
|
static var maleOriented: String {
|
||||||
@@ -2041,7 +2041,7 @@ enum I18n {
|
|||||||
|
|
||||||
// 성별 제한
|
// 성별 제한
|
||||||
static var genderRestrictionTitle: String { pick(ko: "성별 제한", en: "Gender restriction", ja: "性別制限") }
|
static var genderRestrictionTitle: String { pick(ko: "성별 제한", en: "Gender restriction", ja: "性別制限") }
|
||||||
static var genderAll: String { pick(ko: "전체", en: "All", ja: "全体") }
|
static var genderAll: String { pick(ko: "전체", en: "All", ja: "すべて") }
|
||||||
static var genderMaleOnly: String { pick(ko: "남자만", en: "Male only", ja: "男性のみ") }
|
static var genderMaleOnly: String { pick(ko: "남자만", en: "Male only", ja: "男性のみ") }
|
||||||
static var genderFemaleOnly: String { pick(ko: "여자만", en: "Female only", ja: "女性のみ") }
|
static var genderFemaleOnly: String { pick(ko: "여자만", en: "Female only", ja: "女性のみ") }
|
||||||
|
|
||||||
@@ -2106,7 +2106,7 @@ enum I18n {
|
|||||||
static var menuSavedMessage: String { pick(ko: "저장되었습니다.", en: "Saved.", ja: "保存されました。") }
|
static var menuSavedMessage: String { pick(ko: "저장되었습니다.", en: "Saved.", ja: "保存されました。") }
|
||||||
|
|
||||||
static var editLiveTitle: String { pick(ko: "라이브 수정", en: "Edit live", ja: "ライブ編集") }
|
static var editLiveTitle: String { pick(ko: "라이브 수정", en: "Edit live", ja: "ライブ編集") }
|
||||||
static var editAction: String { pick(ko: "수정하기", en: "Edit", ja: "編集する") }
|
static var editAction: String { pick(ko: "수정하기", en: "Edit", ja: "編集") }
|
||||||
static var entryMessageLabel: String { pick(ko: "입장메시지", en: "Entry message", ja: "入場メッセージ") }
|
static var entryMessageLabel: String { pick(ko: "입장메시지", en: "Entry message", ja: "入場メッセージ") }
|
||||||
static var noChangesMessage: String { pick(ko: "변경사항이 없습니다.", en: "No changes.", ja: "変更事項がありません。") }
|
static var noChangesMessage: String { pick(ko: "변경사항이 없습니다.", en: "No changes.", ja: "変更事項がありません。") }
|
||||||
static var editSuccessMessage: String { pick(ko: "라이브 정보가 수정되었습니다.", en: "Live info has been updated.", ja: "ライブ情報を更新しました。") }
|
static var editSuccessMessage: String { pick(ko: "라이브 정보가 수정되었습니다.", en: "Live info has been updated.", ja: "ライブ情報を更新しました。") }
|
||||||
@@ -2125,7 +2125,7 @@ enum I18n {
|
|||||||
|
|
||||||
static var donationRankingTitle: String { pick(ko: "현재 라이브 후원랭킹", en: "Current live donation ranking", ja: "現在のライブ後援ランキング") }
|
static var donationRankingTitle: String { pick(ko: "현재 라이브 후원랭킹", en: "Current live donation ranking", ja: "現在のライブ後援ランキング") }
|
||||||
static var heartRankingTitle: String { pick(ko: "현재 라이브 하트랭킹", en: "Current live heart ranking", ja: "現在のライブハートランキング") }
|
static var heartRankingTitle: String { pick(ko: "현재 라이브 하트랭킹", en: "Current live heart ranking", ja: "現在のライブハートランキング") }
|
||||||
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "全体") }
|
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "すべて") }
|
||||||
static var peopleUnit: String { pick(ko: "명", en: "people", ja: "人") }
|
static var peopleUnit: String { pick(ko: "명", en: "people", ja: "人") }
|
||||||
static var sumLabel: String { pick(ko: "합계", en: "Sum", ja: "合計") }
|
static var sumLabel: String { pick(ko: "합계", en: "Sum", ja: "合計") }
|
||||||
static var normalLabel: String { pick(ko: "일반", en: "Normal", ja: "一般") }
|
static var normalLabel: String { pick(ko: "일반", en: "Normal", ja: "一般") }
|
||||||
@@ -2701,13 +2701,13 @@ enum I18n {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Category {
|
enum Category {
|
||||||
static var all: String { pick(ko: "전체", en: "All", ja: "全") }
|
static var all: String { pick(ko: "전체", en: "All", ja: "すべて") }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RankingSort {
|
enum RankingSort {
|
||||||
// 분석/지표 등
|
// 분석/지표 등
|
||||||
static var revenue: String { pick(ko: "매출", en: "Revenue", ja: "売上高") }
|
static var revenue: String { pick(ko: "매출", en: "Revenue", ja: "売上") }
|
||||||
static var salesVolume: String { pick(ko: "판매량", en: "Units", ja: "販売量") }
|
static var salesVolume: String { pick(ko: "판매량", en: "Units", ja: "販売数") }
|
||||||
static var comments: String { pick(ko: "댓글", en: "Comments", ja: "コメント") }
|
static var comments: String { pick(ko: "댓글", en: "Comments", ja: "コメント") }
|
||||||
static var likes: String { pick(ko: "좋아요", en: "Likes", ja: "いいね") }
|
static var likes: String { pick(ko: "좋아요", en: "Likes", ja: "いいね") }
|
||||||
}
|
}
|
||||||
@@ -2842,7 +2842,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
static var channelDonationEmpty: String { pick(ko: "채널 후원이 없습니다.", en: "No channel donations.", ja: "チャンネル支援はありません。") }
|
static var channelDonationEmpty: String { pick(ko: "채널 후원이 없습니다.", en: "No channel donations.", ja: "チャンネル支援はありません。") }
|
||||||
static var channelDonationAllTitle: String { pick(ko: "채널 후원 전체보기", en: "All Channel Donations", ja: "チャンネル支援一覧") }
|
static var channelDonationAllTitle: String { pick(ko: "채널 후원 전체보기", en: "All Channel Donations", ja: "チャンネル支援一覧") }
|
||||||
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "全体") }
|
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "すべて") }
|
||||||
static var countUnit: String { pick(ko: "개", en: "items", ja: "件") }
|
static var countUnit: String { pick(ko: "개", en: "items", ja: "件") }
|
||||||
static var channelDonationDefaultMessage: String { pick(ko: "을 후원했습니다.", en: " donated.", ja: "を支援しました。") }
|
static var channelDonationDefaultMessage: String { pick(ko: "을 후원했습니다.", en: " donated.", ja: "を支援しました。") }
|
||||||
|
|
||||||
@@ -2860,7 +2860,7 @@ If you block this user, the following features will be restricted.
|
|||||||
static var creatorDetailDebut: String { pick(ko: "데뷔", en: "Debut", ja: "デビュー") }
|
static var creatorDetailDebut: String { pick(ko: "데뷔", en: "Debut", ja: "デビュー") }
|
||||||
static var creatorDetailTotalLiveCount: String { pick(ko: "라이브 총 횟수", en: "Total live sessions", ja: "ライブ総回数") }
|
static var creatorDetailTotalLiveCount: String { pick(ko: "라이브 총 횟수", en: "Total live sessions", ja: "ライブ総回数") }
|
||||||
static var creatorDetailAccumulatedLiveTime: String { pick(ko: "라이브 누적 시간", en: "Total live time", ja: "ライブ累積時間") }
|
static var creatorDetailAccumulatedLiveTime: String { pick(ko: "라이브 누적 시간", en: "Total live time", ja: "ライブ累積時間") }
|
||||||
static var creatorDetailAccumulatedParticipants: String { pick(ko: "라이브 누적 참여자", en: "Total live participants", ja: "ライブ累積参加者") }
|
static var creatorDetailAccumulatedParticipants: String { pick(ko: "라이브 누적 참여자", en: "Total live participants", ja: "ライブ累計参加者数") }
|
||||||
static var creatorDetailRegisteredContentCount: String { pick(ko: "등록 콘텐츠 수", en: "Registered contents", ja: "登録コンテンツ数") }
|
static var creatorDetailRegisteredContentCount: String { pick(ko: "등록 콘텐츠 수", en: "Registered contents", ja: "登録コンテンツ数") }
|
||||||
static var creatorDetailSns: String { pick(ko: "SNS", en: "SNS", ja: "SNS") }
|
static var creatorDetailSns: String { pick(ko: "SNS", en: "SNS", ja: "SNS") }
|
||||||
static var preDebut: String { pick(ko: "데뷔전", en: "Pre-debut", ja: "デビュー前") }
|
static var preDebut: String { pick(ko: "데뷔전", en: "Pre-debut", ja: "デビュー前") }
|
||||||
@@ -3036,7 +3036,7 @@ If you block this user, the following features will be restricted.
|
|||||||
static var content: String { pick(ko: "콘텐츠", en: "Content", ja: "コンテンツ") }
|
static var content: String { pick(ko: "콘텐츠", en: "Content", ja: "コンテンツ") }
|
||||||
static var live: String { pick(ko: "라이브", en: "Live", ja: "ライブ") }
|
static var live: String { pick(ko: "라이브", en: "Live", ja: "ライブ") }
|
||||||
static var chat: String { pick(ko: "대화", en: "Talks", ja: "トーク") }
|
static var chat: String { pick(ko: "대화", en: "Talks", ja: "トーク") }
|
||||||
static var my: String { pick(ko: "마이", en: "My", ja: "マイ") }
|
static var my: String { pick(ko: "마이", en: "My", ja: "マイページ") }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Auth {
|
enum Auth {
|
||||||
@@ -3096,7 +3096,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var voiceOnOnlySectionTitle: String {
|
static var voiceOnOnlySectionTitle: String {
|
||||||
pick(ko: "오직 보이스온에서만!", en: "Only on VoiceOn!", ja: "VoiceOnだけで!")
|
pick(ko: "오직 보이스온에서만!", en: "Only on VoiceOn!", ja: "VOICE ON限定")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var freeAudioSectionTitle: String {
|
static var freeAudioSectionTitle: String {
|
||||||
@@ -3127,7 +3127,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var risingTabTitle: String {
|
static var risingTabTitle: String {
|
||||||
pick(ko: "지금 뜨는 중", en: "Rising now", ja: "今話題")
|
pick(ko: "지금 뜨는 중", en: "Rising now", ja: "急上昇")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var revenueTabTitle: String {
|
static var revenueTabTitle: String {
|
||||||
@@ -3334,14 +3334,14 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var recentNewsTitle: String {
|
static var recentNewsTitle: String {
|
||||||
pick(ko: "최근 소식", en: "Recent news", ja: "最近のニュース")
|
pick(ko: "최근 소식", en: "Recent news", ja: "最新情報")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func rankingMessagePrefix(_ title: String) -> String {
|
static func rankingMessagePrefix(_ title: String) -> String {
|
||||||
pick(
|
pick(
|
||||||
ko: "\(title)님이 이번 주 랭킹 ",
|
ko: "\(title)님이 이번 주 랭킹 ",
|
||||||
en: "\(title) reached ",
|
en: "\(title) reached ",
|
||||||
ja: "\(title)さんが今週のランキング"
|
ja: "\(title)が今週のランキングで"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3350,7 +3350,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var rankingMessageSuffix: String {
|
static var rankingMessageSuffix: String {
|
||||||
pick(ko: "에 올랐어요!", en: " in this week's ranking!", ja: "に入りました!")
|
pick(ko: "에 올랐어요!", en: " in this week's ranking!", ja: "にランクイン!")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var audioTag: String {
|
static var audioTag: String {
|
||||||
@@ -3373,7 +3373,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum CreatorChannelLive {
|
enum CreatorChannelLive {
|
||||||
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "全体") }
|
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "すべて") }
|
||||||
static var startLive: String { pick(ko: "라이브 시작하기", en: "Start live", ja: "ライブを開始") }
|
static var startLive: String { pick(ko: "라이브 시작하기", en: "Start live", ja: "ライブを開始") }
|
||||||
static var emptyMessage: String {
|
static var emptyMessage: String {
|
||||||
pick(
|
pick(
|
||||||
@@ -3386,25 +3386,25 @@ If you block this user, the following features will be restricted.
|
|||||||
enum Sort {
|
enum Sort {
|
||||||
static var latest: String { pick(ko: "최신순", en: "Latest", ja: "最新順") }
|
static var latest: String { pick(ko: "최신순", en: "Latest", ja: "最新順") }
|
||||||
static var popular: String { pick(ko: "인기순", en: "Popular", ja: "人気順") }
|
static var popular: String { pick(ko: "인기순", en: "Popular", ja: "人気順") }
|
||||||
static var priceHigh: String { pick(ko: "높은 가격순", en: "Highest price", ja: "価格が高い順") }
|
static var priceHigh: String { pick(ko: "높은 가격순", en: "Highest price", ja: "高い順") }
|
||||||
static var priceLow: String { pick(ko: "낮은 가격순", en: "Lowest price", ja: "価格が低い順") }
|
static var priceLow: String { pick(ko: "낮은 가격순", en: "Lowest price", ja: "安い順") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CreatorChannelAudio {
|
enum CreatorChannelAudio {
|
||||||
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "全体") }
|
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "すべて") }
|
||||||
static var countUnit: String { pick(ko: "개", en: "", ja: "件") }
|
static var countUnit: String { pick(ko: "개", en: "", ja: "件") }
|
||||||
static var ownershipPrefix: String {
|
static var ownershipPrefix: String {
|
||||||
pick(ko: "전체 오디오의 ", en: "You own ", ja: "全オーディオの")
|
pick(ko: "전체 오디오의 ", en: "You own ", ja: "全オーディオの")
|
||||||
}
|
}
|
||||||
static var ownershipSuffix: String {
|
static var ownershipSuffix: String {
|
||||||
pick(ko: "를 소장하고 있어요.", en: " of all audio.", ja: "を保有中です。")
|
pick(ko: "를 소장하고 있어요.", en: " of all audio.", ja: "を所持しています。")
|
||||||
}
|
}
|
||||||
static var emptyMessage: String {
|
static var emptyMessage: String {
|
||||||
pick(
|
pick(
|
||||||
ko: "크리에이터가 오디오를 준비 중입니다.\n기대해 주세요!",
|
ko: "크리에이터가 오디오를 준비 중입니다.\n기대해 주세요!",
|
||||||
en: "The creator is preparing audio.\nPlease stay tuned!",
|
en: "The creator is preparing audio.\nPlease stay tuned!",
|
||||||
ja: "クリエイターがオーディオを準備中です。\nお楽しみに!"
|
ja: "クリエイターがオーディオを準備しています。\nお楽しみに!"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3417,7 +3417,7 @@ If you block this user, the following features will be restricted.
|
|||||||
pick(
|
pick(
|
||||||
ko: "크리에이터가 시리즈를 준비 중입니다.\n기대해 주세요!",
|
ko: "크리에이터가 시리즈를 준비 중입니다.\n기대해 주세요!",
|
||||||
en: "The creator is preparing series.\nPlease stay tuned!",
|
en: "The creator is preparing series.\nPlease stay tuned!",
|
||||||
ja: "クリエイターがシリーズを準備中です。\nお楽しみに!"
|
ja: "クリエイターがシリーズを準備しています。\nお楽しみに!"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3429,30 +3429,30 @@ If you block this user, the following features will be restricted.
|
|||||||
enum CreatorChannelHome {
|
enum CreatorChannelHome {
|
||||||
static var currentLive: String { pick(ko: "현재 라이브", en: "Live now", ja: "現在ライブ") }
|
static var currentLive: String { pick(ko: "현재 라이브", en: "Live now", ja: "現在ライブ") }
|
||||||
static var latestAudio: String { pick(ko: "최신 오디오 콘텐츠", en: "Latest audio", ja: "最新オーディオ") }
|
static var latestAudio: String { pick(ko: "최신 오디오 콘텐츠", en: "Latest audio", ja: "最新オーディオ") }
|
||||||
static var donation: String { pick(ko: "후원", en: "Donations", ja: "応援") }
|
static var donation: String { pick(ko: "후원", en: "Donations", ja: "サポート") }
|
||||||
static var notice: String { pick(ko: "공지", en: "Notice", ja: "お知らせ") }
|
static var notice: String { pick(ko: "공지", en: "Notice", ja: "お知らせ") }
|
||||||
static var schedule: String { pick(ko: "스케줄", en: "Schedule", ja: "スケジュール") }
|
static var schedule: String { pick(ko: "스케줄", en: "Schedule", ja: "スケジュール") }
|
||||||
static var audio: String { pick(ko: "오디오", en: "Audio", ja: "オーディオ") }
|
static var audio: String { pick(ko: "오디오", en: "Audio", ja: "オーディオ") }
|
||||||
static var series: String { pick(ko: "시리즈", en: "Series", ja: "シリーズ") }
|
static var series: String { pick(ko: "시리즈", en: "Series", ja: "シリーズ") }
|
||||||
static var community: String { pick(ko: "커뮤니티", en: "Community", ja: "コミュニティ") }
|
static var community: String { pick(ko: "커뮤니티", en: "Community", ja: "コミュニティ") }
|
||||||
static var fanTalk: String { pick(ko: "팬Talk", en: "Fan Talk", ja: "ファンTalk") }
|
static var fanTalk: String { pick(ko: "팬Talk", en: "Fan Talk", ja: "ファントーク") }
|
||||||
static var introduce: String { pick(ko: "소개", en: "Introduction", ja: "紹介") }
|
static var introduce: String { pick(ko: "소개", en: "Introduction", ja: "紹介") }
|
||||||
static var activity: String { pick(ko: "활동", en: "Activity", ja: "活動") }
|
static var activity: String { pick(ko: "활동", en: "Activity", ja: "アクティビティ") }
|
||||||
static var sns: String { pick(ko: "SNS", en: "SNS", ja: "SNS") }
|
static var sns: String { pick(ko: "SNS", en: "SNS", ja: "SNS") }
|
||||||
static var follow: String { pick(ko: "팔로우", en: "Follow", ja: "フォロー") }
|
static var follow: String { pick(ko: "팔로우", en: "Follow", ja: "フォロー") }
|
||||||
static var following: String { pick(ko: "팔로잉", en: "Following", ja: "フォロー中") }
|
static var following: String { pick(ko: "팔로잉", en: "Following", ja: "フォロー中") }
|
||||||
static var talk: String { pick(ko: "대화하기", en: "Start chat", ja: "会話する") }
|
static var talk: String { pick(ko: "대화하기", en: "Start chat", ja: "トークする") }
|
||||||
static var dm: String { pick(ko: "DM 보내기", en: "Send DM", ja: "DMを送る") }
|
static var dm: String { pick(ko: "DM 보내기", en: "Send DM", ja: "DMを送る") }
|
||||||
static var checkDm: String { pick(ko: "DM 확인하기", en: "Check DM", ja: "DMを確認") }
|
static var checkDm: String { pick(ko: "DM 확인하기", en: "Check DM", ja: "DMを確認") }
|
||||||
static var uploadCommunityPost: String { pick(ko: "커뮤니티 글 올리기", en: "Upload community post", ja: "コミュニティ投稿を作成") }
|
static var uploadCommunityPost: String { pick(ko: "커뮤니티 글 올리기", en: "Upload community post", ja: "コミュニティ投稿") }
|
||||||
static var uploadAudioContent: String { pick(ko: "오디오 콘텐츠 올리기", en: "Upload audio content", ja: "オーディオコンテンツを投稿") }
|
static var uploadAudioContent: String { pick(ko: "오디오 콘텐츠 올리기", en: "Upload audio content", ja: "オーディオコンテンツを投稿") }
|
||||||
static var createLive: String { pick(ko: "라이브 만들기", en: "Create live", ja: "ライブを作成") }
|
static var createLive: String { pick(ko: "라이브 만들기", en: "Create live", ja: "ライブを作成") }
|
||||||
static var closeFloatingMenu: String { pick(ko: "닫기", en: "Close", ja: "閉じる") }
|
static var closeFloatingMenu: String { pick(ko: "닫기", en: "Close", ja: "閉じる") }
|
||||||
static var viewAll: String { pick(ko: "전체보기", en: "View all", ja: "すべて見る") }
|
static var viewAll: String { pick(ko: "전체보기", en: "View all", ja: "すべて見る") }
|
||||||
static var debut: String { pick(ko: "데뷔", en: "Debut", ja: "デビュー") }
|
static var debut: String { pick(ko: "데뷔", en: "Debut", ja: "デビュー") }
|
||||||
static var liveCount: String { pick(ko: "라이브 총 진행 수", en: "Total live count", ja: "ライブ総配信数") }
|
static var liveCount: String { pick(ko: "라이브 총 진행 수", en: "Total live count", ja: "ライブ配信回数") }
|
||||||
static var liveDurationHours: String { pick(ko: "라이브 누적 진행 시간", en: "Total live hours", ja: "ライブ累計配信時間") }
|
static var liveDurationHours: String { pick(ko: "라이브 누적 진행 시간", en: "Total live hours", ja: "ライブ累計配信時間") }
|
||||||
static var liveContributorCount: String { pick(ko: "라이브 누적 참여자", en: "Total live participants", ja: "ライブ累計参加者") }
|
static var liveContributorCount: String { pick(ko: "라이브 누적 참여자", en: "Total live participants", ja: "ライブ累計参加者数") }
|
||||||
static var audioContentCount: String { pick(ko: "오디오", en: "Audio", ja: "オーディオ") }
|
static var audioContentCount: String { pick(ko: "오디오", en: "Audio", ja: "オーディオ") }
|
||||||
static var seriesCount: String { pick(ko: "시리즈", en: "Series", ja: "シリーズ") }
|
static var seriesCount: String { pick(ko: "시리즈", en: "Series", ja: "シリーズ") }
|
||||||
static var placeholderMessage: String {
|
static var placeholderMessage: String {
|
||||||
@@ -3489,15 +3489,15 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum CreatorChannelCommunity {
|
enum CreatorChannelCommunity {
|
||||||
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "全体") }
|
static var totalLabel: String { pick(ko: "전체", en: "Total", ja: "すべて") }
|
||||||
static var listView: String { pick(ko: "리스트형", en: "List", ja: "リスト型") }
|
static var listView: String { pick(ko: "리스트형", en: "List", ja: "リスト") }
|
||||||
static var thumbnailView: String { pick(ko: "썸네일형", en: "Thumbnail", ja: "サムネイル型") }
|
static var thumbnailView: String { pick(ko: "썸네일형", en: "Thumbnail", ja: "グリッド") }
|
||||||
static var commentPlaceholder: String { pick(ko: "댓글을 입력하세요", en: "Enter a comment", ja: "コメントを入力してください") }
|
static var commentPlaceholder: String { pick(ko: "댓글을 입력하세요", en: "Enter a comment", ja: "コメントを入力") }
|
||||||
static var communityEmpty: String {
|
static var communityEmpty: String {
|
||||||
pick(
|
pick(
|
||||||
ko: "크리에이터가 커뮤니티를 준비 중입니다.\n기대해 주세요!",
|
ko: "크리에이터가 커뮤니티를 준비 중입니다.\n기대해 주세요!",
|
||||||
en: "The creator is preparing the community.\nPlease look forward to it!",
|
en: "The creator is preparing the community.\nPlease look forward to it!",
|
||||||
ja: "クリエイターがコミュニティを準備中です。\nお楽しみに!"
|
ja: "クリエイターがコミュニティを準備しています。\nお楽しみに!"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3505,7 +3505,7 @@ If you block this user, the following features will be restricted.
|
|||||||
|
|
||||||
enum CreatorChannelReplyDetail {
|
enum CreatorChannelReplyDetail {
|
||||||
static var replyPlaceholder: String {
|
static var replyPlaceholder: String {
|
||||||
pick(ko: "답글을 입력하세요", en: "Enter a reply", ja: "返信を入力してください")
|
pick(ko: "답글을 입력하세요", en: "Enter a reply", ja: "返信を入力")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var replaceReplyTitle: String {
|
static var replaceReplyTitle: String {
|
||||||
@@ -3517,16 +3517,16 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var replaceReplyConfirm: String {
|
static var replaceReplyConfirm: String {
|
||||||
pick(ko: "수정하기", en: "Update", ja: "修正する")
|
pick(ko: "수정하기", en: "Update", ja: "編集")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CreatorChannelFanTalk {
|
enum CreatorChannelFanTalk {
|
||||||
static var send: String { pick(ko: "보내기", en: "Send", ja: "送信") }
|
static var send: String { pick(ko: "보내기", en: "Send", ja: "送信") }
|
||||||
static var exitModalTitle: String { pick(ko: "입력 종료", en: "Discard input", ja: "入力終了") }
|
static var exitModalTitle: String { pick(ko: "입력 종료", en: "Discard input", ja: "入力終了") }
|
||||||
static var exitModalMessage: String { pick(ko: "입력을 종료할까요?\n지금 나가면 입력한 내용이 저장되지 않아요.", en: "Discard your input?\nIf you leave now, your input will not be saved.", ja: "入力を終了しますか?\n今退出すると入力した内容は保存されません。") }
|
static var exitModalMessage: String { pick(ko: "입력을 종료할까요?\n지금 나가면 입력한 내용이 저장되지 않아요.", en: "Discard your input?\nIf you leave now, your input will not be saved.", ja: "入力を終了しますか?\n終了すると、入力内容は保存されません。") }
|
||||||
static var exitModalExit: String { pick(ko: "종료", en: "Discard", ja: "終了") }
|
static var exitModalExit: String { pick(ko: "종료", en: "Discard", ja: "終了") }
|
||||||
static var exitModalContinue: String { pick(ko: "계속 작성", en: "Keep writing", ja: "続けて書く") }
|
static var exitModalContinue: String { pick(ko: "계속 작성", en: "Keep writing", ja: "続ける") }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HomeRecommendation {
|
enum HomeRecommendation {
|
||||||
@@ -3555,7 +3555,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var recentDebutCreatorSectionTitle: String {
|
static var recentDebutCreatorSectionTitle: String {
|
||||||
pick(ko: "최근 데뷔한 크리에이터", en: "Recently debuted creators", ja: "最近デビューしたクリエイター")
|
pick(ko: "최근 데뷔한 크리에이터", en: "Recently debuted creators", ja: "新着クリエイター")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var firstAudioContentSectionTitle: String {
|
static var firstAudioContentSectionTitle: String {
|
||||||
@@ -3563,7 +3563,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var aiCharacterSectionTitle: String {
|
static var aiCharacterSectionTitle: String {
|
||||||
pick(ko: "크리에이터와 이야기를 나눠요!", en: "AI characters", ja: "AIキャラクター")
|
pick(ko: "크리에이터와 이야기를 나눠요!", en: "AI characters", ja: "クリエイターと話す")
|
||||||
}
|
}
|
||||||
|
|
||||||
static func compactChatCount(_ count: Int) -> String {
|
static func compactChatCount(_ count: Int) -> String {
|
||||||
@@ -3622,7 +3622,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var cheerCreatorSectionTitle: String {
|
static var cheerCreatorSectionTitle: String {
|
||||||
pick(ko: "최근 응원이 많은 크리에이터", en: "Recently cheered creators", ja: "最近応援が多いクリエイター")
|
pick(ko: "최근 응원이 많은 크리에이터", en: "Recently cheered creators", ja: "最近応援の多いクリエイター")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var popularCommunitySectionTitle: String {
|
static var popularCommunitySectionTitle: String {
|
||||||
@@ -3708,11 +3708,11 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var editAction: String {
|
static var editAction: String {
|
||||||
pick(ko: "수정하기", en: "Edit", ja: "編集する")
|
pick(ko: "수정하기", en: "Edit", ja: "編集")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var deleteAction: String {
|
static var deleteAction: String {
|
||||||
pick(ko: "삭제하기", en: "Delete", ja: "削除する")
|
pick(ko: "삭제하기", en: "Delete", ja: "削除")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var report: String {
|
static var report: String {
|
||||||
@@ -3760,7 +3760,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var pinRelease: String {
|
static var pinRelease: String {
|
||||||
pick(ko: "고정 해제", en: "Unpin", ja: "固定解除")
|
pick(ko: "고정 해제", en: "Unpin", ja: "固定を解除")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var pinToTop: String {
|
static var pinToTop: String {
|
||||||
@@ -4117,7 +4117,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var archive: String {
|
static var archive: String {
|
||||||
pick(ko: "보관함", en: "Archived", ja: "保管済み")
|
pick(ko: "보관함", en: "Archived", ja: "ライブラリ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4584,7 +4584,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var chargeAction: String {
|
static var chargeAction: String {
|
||||||
pick(ko: "충전하기", en: "Charge", ja: "チャージする")
|
pick(ko: "충전하기", en: "Charge", ja: "チャージ")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var chargeShort: String {
|
static var chargeShort: String {
|
||||||
@@ -4782,7 +4782,7 @@ If you block this user, the following features will be restricted.
|
|||||||
|
|
||||||
enum Category {
|
enum Category {
|
||||||
static var storage: String {
|
static var storage: String {
|
||||||
pick(ko: "보관함", en: "Library", ja: "保管庫")
|
pick(ko: "보관함", en: "Library", ja: "ライブラリ")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var blockList: String {
|
static var blockList: String {
|
||||||
@@ -4816,7 +4816,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var owned: String {
|
static var owned: String {
|
||||||
pick(ko: "소장중", en: "Owned", ja: "所持中")
|
pick(ko: "소장중", en: "Owned", ja: "保有中")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5079,11 +5079,11 @@ If you block this user, the following features will be restricted.
|
|||||||
|
|
||||||
enum User {
|
enum User {
|
||||||
static var emailTitle: String {
|
static var emailTitle: String {
|
||||||
pick(ko: "이메일", en: "Email", ja: "メール")
|
pick(ko: "이메일", en: "Email", ja: "メールアドレス")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var emailPlaceholder: String {
|
static var emailPlaceholder: String {
|
||||||
pick(ko: "이메일", en: "Email", ja: "メール")
|
pick(ko: "이메일", en: "Email", ja: "メールアドレス")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var passwordTitle: String {
|
static var passwordTitle: String {
|
||||||
@@ -5137,7 +5137,7 @@ If you block this user, the following features will be restricted.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var privacyPolicy: String {
|
static var privacyPolicy: String {
|
||||||
pick(ko: "개인정보수집 및 이용동의", en: "Consent to collect and use personal information", ja: "個人情報の収集および利用への同意")
|
pick(ko: "개인정보수집 및 이용동의", en: "Consent to collect and use personal information", ja: "個人情報の収集・利用への同意")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var required: String {
|
static var required: String {
|
||||||
|
|||||||
307
docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv
Normal file
307
docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
,KR,JP,,,,
|
||||||
|
,홈,ホーム,,,,
|
||||||
|
,콘텐츠,コンテンツ,,,,
|
||||||
|
,대화,トーク,,,,
|
||||||
|
,마이,マイページ,,,,
|
||||||
|
,추천,おすすめ,,,,
|
||||||
|
,랭킹,ランキング,,,,
|
||||||
|
,팔로잉,フォロー中,,,,
|
||||||
|
,전체,すべて,,,,
|
||||||
|
,방금 활동한 크리에이터,最近活動したクリエイター,,,,
|
||||||
|
,최근 데뷔한 크리에이터,新着クリエイター,,,,
|
||||||
|
,처음부터 함께 성장!,今から推せる,,,,
|
||||||
|
,크리에이터와 이야기를 나눠요!,クリエイターと話す,,,,
|
||||||
|
,추천 필모그래피,おすすめフィルモグラフィー,,,,
|
||||||
|
,또 다른 모습,写真集,,,,
|
||||||
|
,{{장르}}의 크리에이터,{{ジャンル}}のクリエイター,,,,
|
||||||
|
,최근 응원이 많은 크리에이터,最近応援の多いクリエイター,,,,
|
||||||
|
,인기 커뮤니티,人気コミュニティ,,,,
|
||||||
|
,주간 인기,週間人気,,,,
|
||||||
|
,지금 뜨는 중,急上昇,,,,
|
||||||
|
,남성 인기,男性人気,,,,
|
||||||
|
,여성 인기,女性人気,,,,
|
||||||
|
,최근 대화,最近のトーク,,,,
|
||||||
|
,이달의 스케줄,今月のスケジュール,,,,
|
||||||
|
,최근 소식,最新情報,,,,
|
||||||
|
,{{크리에이터 이름}}님이 이번 주 랭킹 n위에 올랐어요!,{{크리에이터 이름}}が今週のランキングでn位にランクイン!,,,,
|
||||||
|
,아직 팔로우한 크리에이터가 없어요. 관심 있는 크리에이터를 팔로우해 보세요.,"まだフォローしているクリエイターがいません。
|
||||||
|
気になるクリエイターをフォローしてみましょう。",,,,
|
||||||
|
,찾아보기,探す,,,,
|
||||||
|
,오직 보이스온에서만!,VOICE ON限定,,,,
|
||||||
|
,새로 올라온 오디오,新着オーディオ,,,,
|
||||||
|
,추천 시리즈,おすすめシリーズ,,,,
|
||||||
|
,무료 오디오,無料オーディオ,,,,
|
||||||
|
,포인트 오디오,ポイントオーディオ,,,,
|
||||||
|
,최근 댓글이 많은 오디오,最近コメントの多いオーディオ,,,,
|
||||||
|
,{{키워드}}의 오디오,{{キーワード}}のオーディオ,,,,
|
||||||
|
,추천 오디오,おすすめオーディオ,,,,
|
||||||
|
,매출,売上,,,,
|
||||||
|
,판매량,販売数,,,,
|
||||||
|
,댓글수,コメント数,,,,
|
||||||
|
,좋아요,いいね,,,,
|
||||||
|
,오디오,オーディオ,,,,
|
||||||
|
,시리즈,シリーズ,,,,
|
||||||
|
,오리지널,オリジナル,,,,
|
||||||
|
,무료,無料,,,,
|
||||||
|
,포인트,ポイント,,,,
|
||||||
|
,화보,フォト,,,,
|
||||||
|
,최신순,最新順,,,,
|
||||||
|
,추천순,おすすめ順,,,,
|
||||||
|
,인기순,人気順,,,,
|
||||||
|
,높은 가격순,高い順,,,,
|
||||||
|
,낮은 가격순,安い順,,,,
|
||||||
|
,소장순,保有順,,,,
|
||||||
|
,팔로우,フォロー,,,,
|
||||||
|
,팔로워,フォロワー,,,,
|
||||||
|
,대화하기,トークする,,,,
|
||||||
|
,DM 보내기,DMを送る,,,,
|
||||||
|
,라이브,ライブ,,,,
|
||||||
|
,커뮤니티,コミュニティ,,,,
|
||||||
|
,팬Talk,ファントーク,,,,
|
||||||
|
,후원,サポート,,,,
|
||||||
|
,팬레터,ファンレター,,,,
|
||||||
|
,후원하기,サポートする,,,,
|
||||||
|
,공지,お知らせ,,,,
|
||||||
|
,스케줄,スケジュール,,,,
|
||||||
|
,전체보기,すべて見る,,,,
|
||||||
|
,전체 오디오의 n%를 소장하고 있어요.,全オーディオのn%を所持しています。,,,,
|
||||||
|
,전체 화보의 n%를 소장하고 있어요.,全フォトのn%を所持しています。,,,,
|
||||||
|
,전체소장,全話購入,,,,
|
||||||
|
,완결,完結,,,,
|
||||||
|
,연재,連載,,,,
|
||||||
|
,소장중,保有中,,,,
|
||||||
|
,리스트형,リスト,,,,
|
||||||
|
,썸네일형,グリッド,,,,
|
||||||
|
,구매완료,購入済み,,,,
|
||||||
|
,삭제,削除,,,,
|
||||||
|
,신고,通報,,,,
|
||||||
|
,후원 랭킹,サポートランキング,,,,
|
||||||
|
,"크리에이터가 라이브를 준비 중입니다.
|
||||||
|
기대해 주세요!","クリエイターがライブを準備しています。
|
||||||
|
お楽しみに!",,,,
|
||||||
|
,"크리에이터가 오디오를 준비 중입니다.
|
||||||
|
기대해 주세요!","クリエイターがオーディオを準備しています。
|
||||||
|
お楽しみに!",,,,
|
||||||
|
,"크리에이터가 시리즈를 준비 중입니다.
|
||||||
|
기대해 주세요!","クリエイターがシリーズを準備しています。
|
||||||
|
お楽しみに!",,,,
|
||||||
|
,"크리에이터가 화보를 준비 중입니다.
|
||||||
|
기대해 주세요!","クリエイターがフォトを準備しています。
|
||||||
|
お楽しみに!",,,,
|
||||||
|
,"크리에이터가 커뮤니티를 준비 중입니다.
|
||||||
|
기대해 주세요!","クリエイターがコミュニティを準備しています。
|
||||||
|
お楽しみに!",,,,
|
||||||
|
,"아직 응원이 없습니다.
|
||||||
|
처음으로 크리에이터를 응원해 보세요!","まだファンレターがありません。
|
||||||
|
最初のファンレターを送ってみましょう!",,,,
|
||||||
|
,응원 남기기,ファンレターを送る,,,,
|
||||||
|
,"아직 후원이 없습니다.
|
||||||
|
처음으로 크리에이터를 후원해 보세요!","まだサポートがありません。
|
||||||
|
最初のサポートをしてみましょう!",,,,
|
||||||
|
,당신의 첫 응원을 기다리고 있어요!,あなたの最初のファンレターを待っています!,,,,
|
||||||
|
,댓글,コメント,,,,
|
||||||
|
,취소,キャンセル,,,,
|
||||||
|
,보내기,送信,,,,
|
||||||
|
,입력 종료,入力終了,,,,
|
||||||
|
,"입력을 종료할까요?
|
||||||
|
지금 나가면 입력한 내용이 저장되지 않아요.","入力を終了しますか?
|
||||||
|
終了すると、入力内容は保存されません。",,,,
|
||||||
|
,종료,終了,,,,
|
||||||
|
,계속 작성,続ける,,,,
|
||||||
|
,응원 삭제,ファンレターを削除,,,,
|
||||||
|
,"응원을 삭제할까요?
|
||||||
|
삭제 시 크리에이터의 답글도 같이 사라져요.","ファンレターを削除しますか?
|
||||||
|
削除すると、クリエイターからの返信も削除されます。",,,,
|
||||||
|
,오늘,今日,,,,
|
||||||
|
,지금,今,,,,
|
||||||
|
,지난,終了,,,,
|
||||||
|
,"아직 대화가 없어요.
|
||||||
|
크리에이터와 이야기를 시작해 보세요.","まだ会話がありません。
|
||||||
|
クリエイターと話してみましょう。",,,,
|
||||||
|
,"아직 나눈 AI 채팅이 없어요.
|
||||||
|
크리에이터와 대화를 시작해 보세요.","まだAIチャットがありません。
|
||||||
|
クリエイターと会話を始めてみましょう。",,,,
|
||||||
|
,"아직 나눈 대화가 없어요.
|
||||||
|
좋아하는 크리에이터에게 메세지를 보내보세요.","まだメッセージがありません。
|
||||||
|
好きなクリエイターにメッセージを送ってみましょう。",,,,
|
||||||
|
,크리에이터의 이름을 입력하세요,クリエイター名を入力,,,,
|
||||||
|
,대화 시작,トーク開始,,,,
|
||||||
|
,{{크리에이터이름}}님과 AI 채팅 혹은 DM을 시작해 볼까요?,{{クリエイター名}}とAIチャットまたはDMを始めてみませんか?,,,,
|
||||||
|
,AI 채팅하기,AIチャット,,,,
|
||||||
|
,채팅 설정,チャット設定,,,,
|
||||||
|
,이미지 보관함,ギャラリー,,,,
|
||||||
|
,배경 이미지 설정,背景設定,,,,
|
||||||
|
,채팅 초기화,チャットを初期化,,,,
|
||||||
|
,신규 크리에이터,新着クリエイター,,,,
|
||||||
|
,나의 크리에이터,マイクリエイター,,,,
|
||||||
|
,AI 캐릭터 채팅,AIキャラクターチャット,,,,
|
||||||
|
,팔로우 해제,フォロー解除,,,,
|
||||||
|
,"팔로우를 해제할까요?
|
||||||
|
{{크리에이터이름}}의 소식을 받을 수 없어요.","{{크리에이터이름}}からの最新情報を受け取れなくなります。
|
||||||
|
フォローを解除しますか?",,,,
|
||||||
|
,해제하기,解除する,,,,
|
||||||
|
,검색어를 입력하세요,キーワードを入力,,,,
|
||||||
|
,전체삭제,すべて削除,,,,
|
||||||
|
,최근 검색,最近の検索,,,,
|
||||||
|
,인기 검색어,急上昇ワード,,,,
|
||||||
|
,AI 채팅 인기 검색 키워드,AIチャット急上昇ワード,,,,
|
||||||
|
,시리즈 인기 검색 키워드,シリーズ急上昇ワード,,,,
|
||||||
|
,오디오 인기 검색 키워드,オーディオ急上昇ワード,,,,
|
||||||
|
,키워드,キーワード,,,,
|
||||||
|
,검색 결과,検索結果,,,,
|
||||||
|
,정확도순,関連度順,,,,
|
||||||
|
,많이 찾는 크리에이터,人気のクリエイター,,,,
|
||||||
|
,"관련 크리에이터가 없어요.
|
||||||
|
다른 검색어로 다시 찾아보세요.","該当するクリエイターがいません。
|
||||||
|
別のキーワードで検索してください。",,,,
|
||||||
|
,"관련 시리즈가 없어요.
|
||||||
|
다른 검색어로 다시 찾아보세요.","該当するシリーズがありません。
|
||||||
|
別のキーワードで検索してください。",,,,
|
||||||
|
,"관련 화보가 없어요.
|
||||||
|
다른 검색어로 다시 찾아보세요.","該当するフォトがありません。
|
||||||
|
別のキーワードで検索してください。",,,,
|
||||||
|
,"관련 키워드가 없어요.
|
||||||
|
다른 검색어로 다시 찾아보세요.","該当するキーワードがありません。
|
||||||
|
別のキーワードで検索してください。",,,,
|
||||||
|
,라이브 시작하기,ライブを開始,,,,
|
||||||
|
,오디오 올리기,オーディオを投稿,,,,
|
||||||
|
,커뮤니티 글 올리기,コミュニティ投稿,,,,
|
||||||
|
,이메일을 입력해 주세요,メールアドレスを入力してください,,,,
|
||||||
|
,이메일,メールアドレス,,,,
|
||||||
|
,이용약관,利用規約,,,,
|
||||||
|
,개인정보수집 및 이용동의,個人情報の収集・利用への同意,,,,
|
||||||
|
,올바른 이메일을 입력해 주세요,正しいメールアドレスを入力してください,,,,
|
||||||
|
,다음으로,次へ,,,,
|
||||||
|
,이미 가입된 이메일이에요,登録済みのメールアドレスです,,,,
|
||||||
|
,비밀번호를 입력해 주세요,パスワードを入力してください,,,,
|
||||||
|
,비밀번호,パスワード,,,,
|
||||||
|
,"8자 이상 (영문 대/소문자, 특수문자 포함)",8文字以上(英大文字・小文字・記号を含む),,,,
|
||||||
|
,완료,完了,,,,
|
||||||
|
,"8자 이상, 영문 대/소문자와 특수문자를 포함해 주세요",8文字以上、英大文字・小文字・記号を含めてください,,,,
|
||||||
|
,이메일로 로그인하기,メールアドレスでログイン,,,,
|
||||||
|
,로그인,ログイン,,,,
|
||||||
|
,가입되지 않은 이메일이에요,登録されていないメールアドレスです,,,,
|
||||||
|
,입력한 비밀번호를 다시 확인해 주세요,パスワードをもう一度確認してください,,,,
|
||||||
|
,인증코드를 입력해 주세요,認証コードを入力してください,,,,
|
||||||
|
,{email}로 인증코드를 보냈어요,{email}に認証コードを送信しました,,,,
|
||||||
|
,인증코드,認証コード,,,,
|
||||||
|
,0:00 후에 인증코드 다시 보내기,0:00後に認証コードを再送する,,,,
|
||||||
|
,인증 가능한 시간을 초과했어요,認証可能時間を超過しました,,,,
|
||||||
|
,인증코드 다시 보내기,認証コードを再送する,,,,
|
||||||
|
,올바른 인증코드를 입력해 주세요,正しい認証コードを入力してください,,,,
|
||||||
|
,새 비밀번호를 입력해 주세요,新しいパスワードを入力してください,,,,
|
||||||
|
,기존 비밀번호와 다르게 설정해 주세요,現在のパスワードとは異なるものを設定してください,,,,
|
||||||
|
,비밀번호 설정 완료!,パスワード設定完了!,,,,
|
||||||
|
,"비밀번호 설정을 완료했어요.
|
||||||
|
다시 로그인해 주세요.","パスワードを設定しました。
|
||||||
|
もう一度ログインしてください。",,,,
|
||||||
|
,재설정 종료,再設定を終了,,,,
|
||||||
|
,"지금 나가면 현재 진행중인 재설정이 종료되고,
|
||||||
|
처음부터 다시 진행해야 해요.","終了すると、進行中の再設定が終了し、
|
||||||
|
最初からやり直す必要があります。",,,,
|
||||||
|
,계속 재설정,再設定を続ける,,,,
|
||||||
|
,AI 채팅,AIチャット,,,,
|
||||||
|
,전체 크리에이터,すべてのクリエイター,,,,
|
||||||
|
,필수,必須,,,,
|
||||||
|
,가입한 이메일을 입력해 주세요,"登録メールアドレスを
|
||||||
|
入力してください",,,,
|
||||||
|
,존재하지 않는 이메일이에요,登録されていないメールアドレスです,,,,
|
||||||
|
,모두 팔로우하기,すべてフォロー,,,,
|
||||||
|
,대여중,レンタル中,,,,
|
||||||
|
,데뷔,デビュー,,,,
|
||||||
|
,라이브 총 진행 수,ライブ配信回数,,,,
|
||||||
|
,라이브 누적 진행 시간,ライブ累計配信時間,,,,
|
||||||
|
,라이브 누적 참여자,ライブ累計参加者数,,,,
|
||||||
|
,고정 해제,固定を解除,,,,
|
||||||
|
,수정하기,編集,,,,
|
||||||
|
,삭제하기,削除,,,,
|
||||||
|
,크리에이터에게만 보여요.,クリエイターにのみ表示されます。,,,,
|
||||||
|
,답글쓰기,返信する,,,,
|
||||||
|
,신고하기,通報する,,,,
|
||||||
|
,댓글을 입력하세요,コメントを入力,,,,
|
||||||
|
,크리에이터에게 응원의 메세지를 보내주세요!,クリエイターへファンレターを送りましょう!,,,,
|
||||||
|
,답글을 삭제할까요?,返信を削除しますか?,,,,
|
||||||
|
,답글 삭제,返信を削除,,,,
|
||||||
|
,크리에이터,クリエイター,,,,
|
||||||
|
,시리즈 자세히 보기,シリーズの詳細を見る,,,,
|
||||||
|
,소개,紹介,,,,
|
||||||
|
,장르,ジャンル,,,,
|
||||||
|
,연령 제한,年齢制限,,,,
|
||||||
|
,소장,購入,,,,
|
||||||
|
,페이지 수,ページ数,,,,
|
||||||
|
,발매일,発売日,,,,
|
||||||
|
,크리에이터의 다른 화보,このクリエイターの他のフォト,,,,
|
||||||
|
,{{화보이름 12자 ...}}와 비슷한 화보,「{{화보이름 12자 ...}}」に似たフォト,,,,
|
||||||
|
,[Premium Photo]의 다른 화보,「Premium Photo」の他のフォト,,,,
|
||||||
|
,소장하기,購入する,,,,
|
||||||
|
,스크랩,クリップ,,,,
|
||||||
|
,"마음에 드는 페이지를 스크랩하여
|
||||||
|
감상해 보세요!","お気に入りのページをクリップして
|
||||||
|
楽しみましょう!",,,,
|
||||||
|
,마지막 페이지,最後のページ,,,,
|
||||||
|
,"{{메거진 이름 12자 초과시...}}은 어떠셨나요?
|
||||||
|
후기를 댓글로 남겨보세요.","「{{雑誌名...}}」はいかがでしたか
|
||||||
|
感想をコメントでお聞かせください。",,,,
|
||||||
|
,댓글 쓰기,コメントを書く,,,,
|
||||||
|
,첫 페이지에요.,最初のページです。,,,,
|
||||||
|
,오래된순,古い順,,,,
|
||||||
|
,보관함,ライブラリ,,,,
|
||||||
|
,구매,購入,,,,
|
||||||
|
,재생목록,プレイリスト,,,,
|
||||||
|
,최근,最近,,,,
|
||||||
|
,제목순,タイトル順,,,,
|
||||||
|
,발매순,発売日順,,,,
|
||||||
|
,대여,レンタル,,,,
|
||||||
|
,업로드순,アップロード順,,,,
|
||||||
|
,"구매한 오디오가 없어요.
|
||||||
|
관심있는 오디오를 구매해 보세요!","購入したオーディオはありません。
|
||||||
|
気になるオーディオを購入してみましょう!",,,,
|
||||||
|
,"구매한 화보가 없어요.
|
||||||
|
관심있는 화보를 구매해 보세요!","購入したフォトはありません。
|
||||||
|
気になるフォトを購入してみましょう!",,,,
|
||||||
|
,"구매한 커뮤니티 글이 없어요.
|
||||||
|
좋아하는 크리에이터의 글을 구매해 보세요!","購入したコミュニティ投稿はありません。
|
||||||
|
お気に入りのクリエイターの投稿を購入してみましょう!",,,,
|
||||||
|
,나중에 들을 오디오,あとで聴く,,,,
|
||||||
|
,"좋아요한 오디오가 없어요.
|
||||||
|
마음에 드는 오디오에 좋아요를 남겨 보세요!","「いいね」したオーディオはありません。
|
||||||
|
お気に入りのオーディオに「いいね」してみましょう!",,,,
|
||||||
|
,"좋아요한 화보가 없어요.
|
||||||
|
마음에 드는 화보에 좋아요를 남겨 보세요!","「いいね」したフォトはありません。
|
||||||
|
お気に入りのフォトに「いいね」してみましょう!",,,,
|
||||||
|
,"좋아요한 커뮤니티 글이 없어요.
|
||||||
|
마음에 드는 글에 좋아요를 남겨 보세요!","「いいね」した投稿はありません。
|
||||||
|
お気に入りの投稿に「いいね」してみましょう!",,,,
|
||||||
|
,"최근 본 콘텐츠가 없어요.
|
||||||
|
새로운 콘텐츠를 만나보세요!","最近見たコンテンツはありません。
|
||||||
|
新しいコンテンツを見つけましょう!",,,,
|
||||||
|
,"최근 본 오디오가 없어요.
|
||||||
|
새로운 오디오를 만나보세요!","最近聴いたオーディオはありません。
|
||||||
|
新しいオーディオを見つけましょう!",,,,
|
||||||
|
,"최근 본 시리즈가 없어요.
|
||||||
|
새로운 시리즈를 만나보세요!","最近見たシリーズはありません。
|
||||||
|
しいシリーズを見つけましょう!",,,,
|
||||||
|
,"최근 본 화보가 없어요.
|
||||||
|
새로운 화보를 만나보세요!","最近見たフォトはありません。
|
||||||
|
新しいフォトを見つけましょう!",,,,
|
||||||
|
,충전하기,チャージ,,,,
|
||||||
|
,현재 보유한 캔,現在のカン,,,,
|
||||||
|
,사용할 캔,使用するカン,,,,
|
||||||
|
,사용할 포인트,使用するポイント,,,,
|
||||||
|
,*포인트 일부를 차감하여 대여합니다.,*ポイントを使用してレンタルします。,,,,
|
||||||
|
,캔이 부족해요! 충전 후 이용해 주세요.,カンが不足しています。チャージしてご利用ください。,,,,
|
||||||
|
,소장이 완료되었어요!,購入が完了しました!,,,,
|
||||||
|
,대여가 완료되었어요!,レンタルが完了しました!,,,,
|
||||||
|
,구매에 실패했어요. 다시 시도해 주세요.,購入に失敗しました。もう一度お試しください。,,,,
|
||||||
|
,오늘 하루 보지 않기,今日一日は表示しない,,,,
|
||||||
|
,닫기,閉じる,,,,
|
||||||
|
,콘텐츠 보호를 위해 캡처를 지원하지 않아요.,コンテンツ保護のため、スクリーンショットはご利用いただけません。,,,,
|
||||||
|
,답글을 입력하세요,返信を入力,,,,
|
||||||
|
,전체 인기,人気ランキング,,,,
|
||||||
|
,현재 보유,所持数,,,,
|
||||||
|
,활동,アクティビティ,,,,
|
||||||
|
,익명,匿名,,,,
|
||||||
|
,DM 확인하기,DMを確認,,,,
|
||||||
|
,예정,予定,,,,
|
||||||
|
,대화 찾기,トークを探す,,,,
|
||||||
|
,{요일}요 인기 시리즈,{요일}曜人気シリーズ,,,,
|
||||||
|
548
docs/20260811_일본어_string_resource_반영/plan-task.md
Normal file
548
docs/20260811_일본어_string_resource_반영/plan-task.md
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
# 일본어 string_resource 반영 Goal 실행형 구현 계획
|
||||||
|
|
||||||
|
| 문서 항목 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 상태 | 구현 완료 |
|
||||||
|
| 작성일 | `2026-08-11` |
|
||||||
|
| 요구사항 기준 | `docs/20260811_일본어_string_resource_반영/prd.md` |
|
||||||
|
| API 기준 | 해당 없음 |
|
||||||
|
| 현재 Phase | Phase 2 일본어 리소스 반영 완료 |
|
||||||
|
| 현재 활성 Goal | 없음 |
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
|
||||||
|
CSV의 KR/JP 문자열 리소스를 현재 iOS `I18n.swift` 구조에 맞춰 안전하게 반영하되, 기존 한국어 리소스가 없는 항목은 신규 리소스로 추가하지 않는다.
|
||||||
|
|
||||||
|
## 현재 상태
|
||||||
|
|
||||||
|
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
||||||
|
|---:|---|---:|---|---|
|
||||||
|
| 1 | 완료 | `3/3` | `P1-GATE` 완료 | 없음 |
|
||||||
|
| 2 | 완료 | `3/3` | `P2-GATE` 완료 | 없음 |
|
||||||
|
|
||||||
|
- 동시에 하나의 미완료 goal만 운용한다.
|
||||||
|
- 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다. 후속 수정은 회귀 수정 Task와 새 goal ID를 추가한다.
|
||||||
|
- 사용자가 명시적으로 요청하지 않으면 goal에 token budget을 설정하지 않는다.
|
||||||
|
|
||||||
|
## 범위
|
||||||
|
|
||||||
|
### 포함
|
||||||
|
|
||||||
|
- `docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv`의 KR/JP 242개 데이터 행 분석.
|
||||||
|
- `SodaLive/Sources/I18n/I18n.swift`의 기존 `pick(ko:en:ja:)` 항목 중 CSV KR과 exact 또는 placeholder 정규화 기준으로 매칭되는 항목의 일본어 값 갱신 계획.
|
||||||
|
- CSV 셀 안의 실제 줄바꿈과 HTML br 표기를 Swift 문자열 줄바꿈 escape `\n`으로 변환하는 계획.
|
||||||
|
- placeholder 후보와 오탈자 후보의 확정 결정 기록.
|
||||||
|
- 구현 후 `SodaLive`, `SodaLive-dev` Debug 빌드와 일본어 대표 화면 수동 QA 계획.
|
||||||
|
|
||||||
|
### 제외
|
||||||
|
|
||||||
|
- 이 문서 작성 턴에서 코드 수정.
|
||||||
|
- `String Catalog` 신규 도입 또는 `I18n.swift` 구조 개편.
|
||||||
|
- CSV에 없는 영어 번역 작성.
|
||||||
|
- 기존 한국어 리소스가 없는 CSV 항목의 신규 리소스 추가.
|
||||||
|
- UI 레이아웃, 폰트, API, 결제 SDK 입력값 변경.
|
||||||
|
|
||||||
|
## 기술적 제약
|
||||||
|
|
||||||
|
- 기술 스택: Swift, SwiftUI, Xcode, CocoaPods, `SodaLive.xcworkspace`.
|
||||||
|
- 아키텍처: 기존 `I18n.Module.Domain.Role` 네임스페이스와 `pick(ko:en:ja:)` 헬퍼를 유지한다.
|
||||||
|
- 데이터·보안: 토큰, 인증 헤더, API path, SDK 고정값은 번역 대상으로 보지 않는다.
|
||||||
|
- 호환성: 기존 iOS 앱 스킴 `SodaLive`, `SodaLive-dev`를 기준으로 검증한다.
|
||||||
|
- 의존성: 신규 dependency를 추가하지 않는다.
|
||||||
|
- 계약: 제공되지 않은 영어 문구, enum, 서버 상태, 오류 키를 추정하지 않는다. 기존 한국어 리소스가 exact 또는 placeholder 정규화 기준으로 매칭되지 않는 항목은 신규 키를 만들지 않는다.
|
||||||
|
- 검증: focused 정적 점검에서 시작해 `xcodebuild` 빌드와 수동 QA로 확장한다.
|
||||||
|
- 테스트 제약: 현재 가이드 기준 앱 타깃 중심 구성이고 테스트 번들 타깃이 확인되지 않으므로, 테스트 액션 미구성은 코드 실패로 판정하지 않는다.
|
||||||
|
|
||||||
|
## Task TDD 작성 규칙
|
||||||
|
|
||||||
|
문자열 리소스 반영은 대부분 정적 데이터 변경이며 현재 테스트 타깃이 확인되지 않는다. 따라서 구현 Task는 TDD 예외 사유와 대체 검증 방법을 명시한다. 자동 테스트가 추가 가능한 구조가 확인되면 가장 작은 실패 test를 먼저 작성하고 `RED → GREEN → REFACTOR` 순서를 따른다.
|
||||||
|
|
||||||
|
## Phase 1 매핑 확정
|
||||||
|
|
||||||
|
**Phase 결과:** CSV 242행의 처리 방식을 `기존 키 갱신`, `placeholder 정규화 후 기존 키 갱신`, `오탈자 보정 후 기존 키 갱신`, `기존 리소스 없음으로 제외` 중 하나로 분류한다.
|
||||||
|
|
||||||
|
**선행조건:** PRD의 Open Questions 사용자 결정이 반영되어 있다.
|
||||||
|
|
||||||
|
**Phase 완료 조건:** `P1-T1`~`P1-T3`과 `P1-GATE` 완료, Progress 기록 누적.
|
||||||
|
|
||||||
|
### 구현 항목
|
||||||
|
|
||||||
|
#### Task 1.1 CSV 파싱 기준 확정
|
||||||
|
|
||||||
|
**Goal 실행 `P1-T1`:** CSV 데이터 행 수, 누락, CSV 셀 안의 실제 줄바꿈, HTML br 표기, placeholder 후보를 재현 가능한 명령으로 확정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `kr_jp_string_resource.csv` 존재.
|
||||||
|
- **완료 증거:** CSV 파싱 결과가 Progress에 기록되고 PRD의 수치와 일치한다.
|
||||||
|
- **범위 밖:** 앱 코드 수정.
|
||||||
|
- **TDD 예외 사유:** production 동작을 변경하지 않는 read-only 데이터 점검 Task다.
|
||||||
|
- **대체 검증 방법:** Python `csv.reader`로 CSV를 파싱해 행 수와 후보 목록을 출력한다.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Read: `docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv`
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- Consumes: CSV `KR`, `JP` 컬럼.
|
||||||
|
- Produces: Phase 1에서 사용할 행 수, CSV 실제 줄바꿈, HTML br 표기와 품질 후보 목록.
|
||||||
|
|
||||||
|
- [x] CSV를 정규 파서로 읽어 데이터 행 수를 확인한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 - <<'PY'
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||||
|
with p.open(newline='', encoding='utf-8') as f:
|
||||||
|
rows = list(csv.reader(f))
|
||||||
|
data = rows[1:]
|
||||||
|
print(len(data))
|
||||||
|
print(sum(1 for row in data if len(row) < 3 or not row[1].strip() or not row[2].strip()))
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** 첫 줄 `242`, 둘째 줄 `0`.
|
||||||
|
|
||||||
|
- [x] CSV 셀 안의 실제 줄바꿈 포함 행과 placeholder 유사 행을 산출해 Progress에 기록한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 - <<'PY'
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||||
|
with p.open(newline='', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
next(reader)
|
||||||
|
rows = [(i, row[1], row[2]) for i, row in enumerate(reader, start=2)]
|
||||||
|
print('multiline', sum('\n' in kr or '\n' in jp for _, kr, jp in rows))
|
||||||
|
print('placeholder_like', sum(any(t in kr + jp for t in ['{{', '}}', '{', '}', 'n%', 'n위', '0:00']) for _, kr, jp in rows))
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** `multiline 33`, `placeholder_like 12`.
|
||||||
|
|
||||||
|
`multiline` 대상은 Swift 코드 반영 시 CSV 셀 안의 실제 줄바꿈을 그대로 붙여넣지 않고 문자열 escape `\n`으로 작성한다.
|
||||||
|
|
||||||
|
- [x] HTML br 표기 수를 산출해 Progress에 기록한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 - <<'PY'
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||||
|
tag = chr(60) + 'br' + chr(62)
|
||||||
|
with p.open(newline='', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
next(reader)
|
||||||
|
rows = [(i, row[1], row[2]) for i, row in enumerate(reader, start=2)]
|
||||||
|
print('html_br_like', sum(tag.lower() in (kr + jp).lower() for _, kr, jp in rows))
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** 현재 CSV 기준 `html_br_like 0`. 향후 값이 1 이상이면 해당 항목은 앱 문자열에 HTML br literal이 아니라 `\n`으로 반영한다.
|
||||||
|
|
||||||
|
#### Task 1.2 기존 `I18n.swift` 매칭 목록 확정
|
||||||
|
|
||||||
|
**Goal 실행 `P1-T2`:** CSV KR과 기존 `I18n.swift`의 `ko:` literal 및 placeholder 정규화 매칭 수를 확정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P1-T1` 완료.
|
||||||
|
- **완료 증거:** exact 매칭, placeholder 정규화 추가 매칭, 기존 리소스 없음 제외 대상과 중복 JP 상이 항목 수가 Progress에 기록된다.
|
||||||
|
- **범위 밖:** `ja` 값 수정.
|
||||||
|
- **TDD 예외 사유:** production 동작을 변경하지 않는 read-only 매핑 점검 Task다.
|
||||||
|
- **대체 검증 방법:** CSV KR literal과 placeholder 정규화 문자열을 Swift `ko:` literal 및 interpolation 정규화 문자열과 대조한다.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Read: `docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv`
|
||||||
|
- Read: `SodaLive/Sources/I18n/I18n.swift`
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- Consumes: CSV 행 목록, `I18n.swift` source.
|
||||||
|
- Produces: exact 매칭 수, placeholder 정규화 추가 매칭 수, 기존 리소스 없음 제외 대상 수, 중복 JP 상이 항목 수.
|
||||||
|
|
||||||
|
- [x] 기존 `ko:` literal 매칭 수와 placeholder 정규화 매칭 수를 산출한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 - <<'PY'
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
csv_path = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||||
|
i18n = Path('SodaLive/Sources/I18n/I18n.swift').read_text(encoding='utf-8')
|
||||||
|
matched = 0
|
||||||
|
missing = 0
|
||||||
|
with csv_path.open(newline='', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
next(reader)
|
||||||
|
for row in reader:
|
||||||
|
kr = row[1]
|
||||||
|
swift = kr.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
|
||||||
|
if f'ko: "{swift}"' in i18n:
|
||||||
|
matched += 1
|
||||||
|
else:
|
||||||
|
missing += 1
|
||||||
|
print('matched', matched)
|
||||||
|
print('missing', missing)
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** 현재 기준 `matched 103`, `missing 139`. placeholder 정규화 스캔은 `direct 103`, `normalized_only 0`, `duplicate_different_ja 12`를 함께 기록한다.
|
||||||
|
|
||||||
|
- [x] `missing` 항목은 신규 키로 만들지 않고 `기존 리소스 없음으로 제외` 처리한다.
|
||||||
|
|
||||||
|
#### Task 1.3 질문 결정 반영
|
||||||
|
|
||||||
|
**Goal 실행 `P1-T3`:** PRD Open Questions에 반영된 사용자 결정을 기준으로 구현 가능 범위를 확정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P1-T2` 완료.
|
||||||
|
- **완료 증거:** `OQ-001`~`OQ-005`가 확정으로 표시되고 구현 가능 범위가 정해진다.
|
||||||
|
- **범위 밖:** 기존 한국어 리소스가 없는 항목의 신규 리소스 추가.
|
||||||
|
- **TDD 예외 사유:** 제품·번역 의사결정 Task다.
|
||||||
|
- **대체 검증 방법:** PRD Open Questions와 회신 내용을 대조하고 Decision Log를 갱신한다.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/prd.md`
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- Consumes: `OQ-001`~`OQ-005`.
|
||||||
|
- Produces: Phase 2에서 구현 가능한 기존 키 갱신 범위와 제외 목록.
|
||||||
|
|
||||||
|
- [x] `OQ-001`: `しいシリーズ`는 `新しいシリーズ`로 보정 반영한다.
|
||||||
|
- [x] `OQ-002`: placeholder는 기존 한국어 리소스가 있는 항목만 Swift 함수 인자로 변환한다.
|
||||||
|
- [x] `OQ-006`: `{{...}}`, `{...}`, `n%`, `n위`, `0:00` placeholder는 Swift interpolation과 정규화 매칭한다.
|
||||||
|
- [x] `OQ-007`: 중복 KR 또는 중복 정규화 KR의 기존 JP가 서로 다르면 CSV JP를 우선한다.
|
||||||
|
- [x] `OQ-008`: HTML br 표기는 Swift 문자열 줄바꿈 `\n`으로 변환한다.
|
||||||
|
- [x] `OQ-009`: CSV 셀 안의 실제 줄바꿈은 Swift 문자열 줄바꿈 escape `\n`으로 변환한다.
|
||||||
|
- [x] `OQ-003`: KR 오탈자 후보는 보정하지 않고 JP만 반영한다.
|
||||||
|
- [x] `OQ-004`: 기존 `I18n.swift`에 없는 139개 항목은 신규 키로 추가하지 않는다.
|
||||||
|
- [x] `OQ-005`: 없는 리소스는 추가하지 않으므로 신규 `en`도 작성하지 않는다.
|
||||||
|
|
||||||
|
### 완료 조건
|
||||||
|
|
||||||
|
- [x] `P1-T1`, `P1-T2`, `P1-T3`의 체크박스와 완료 증거가 모두 충족됐다.
|
||||||
|
- [x] Phase 2 구현 범위가 exact 또는 placeholder 정규화 기준으로 기존 한국어 리소스가 있는 항목으로만 구성됐다.
|
||||||
|
- [x] CSV 실제 줄바꿈이 있는 항목은 Swift 문자열 `\n` 변환 대상으로 분류됐다.
|
||||||
|
- [x] HTML br 표기가 있는 항목은 `\n` 변환 대상으로 분류됐다.
|
||||||
|
- [x] 제외 항목은 추정 구현 없이 PRD Open Questions와 Decision Log에 남았다.
|
||||||
|
|
||||||
|
### 검증 방법
|
||||||
|
|
||||||
|
#### Phase 1 Gate
|
||||||
|
|
||||||
|
**Goal 실행 `P1-GATE`:** CSV 입력, 기존 키 매칭, 질문 결정 처리 상태를 최종 판정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** Phase 1의 모든 활성 Task goal 완료.
|
||||||
|
- **완료 증거:** 아래 명령 결과와 PRD Decision Log 갱신.
|
||||||
|
- **범위 밖:** 앱 코드 수정.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 - <<'PY'
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
p = Path('docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv')
|
||||||
|
with p.open(newline='', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
header = next(reader)
|
||||||
|
rows = list(reader)
|
||||||
|
assert header[1:3] == ['KR', 'JP']
|
||||||
|
assert len(rows) == 242
|
||||||
|
assert all(len(row) >= 3 and row[1].strip() and row[2].strip() for row in rows)
|
||||||
|
print('CSV gate passed')
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** `CSV gate passed`.
|
||||||
|
|
||||||
|
수동 검증:
|
||||||
|
|
||||||
|
- [x] PRD `OQ-001`~`OQ-009`의 상태가 모두 확정이고 Decision Log가 최신 회신을 반영한다.
|
||||||
|
- [x] Phase 2 구현 대상에 기존 한국어 리소스가 없는 항목이 포함되지 않았다.
|
||||||
|
|
||||||
|
## Phase 2 일본어 리소스 반영
|
||||||
|
|
||||||
|
**Phase 결과:** exact 또는 placeholder 정규화 기준으로 기존 한국어 리소스가 있는 확정 CSV JP 문구가 `I18n.swift`에 반영되고, 일본어 환경 대표 화면에서 문구가 확인된다.
|
||||||
|
|
||||||
|
**선행조건:** `P1-GATE` 완료.
|
||||||
|
|
||||||
|
**Phase 완료 조건:** `P2-T1`~`P2-T3`과 `P2-GATE` 완료, Progress 기록 누적.
|
||||||
|
|
||||||
|
### 구현 항목
|
||||||
|
|
||||||
|
#### Task 2.1 기존 키 `ja` 값 갱신
|
||||||
|
|
||||||
|
**Goal 실행 `P2-T1`:** CSV KR과 기존 `I18n.swift` `ko:`가 exact 또는 placeholder 정규화 기준으로 일치하는 확정 항목의 `ja` 값을 CSV JP로 갱신한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P1-GATE` 완료, 기존 리소스 없음 제외 목록 확정.
|
||||||
|
- **완료 증거:** 기존 `en` 값이 유지되고, 갱신 대상 `ja` 값이 CSV와 일치한다.
|
||||||
|
- **범위 밖:** 신규 키 추가, 호출부 변경, 영어 문구 변경.
|
||||||
|
- **TDD 예외 사유:** 기존 string map 값 갱신이며 테스트 타깃이 확인되지 않는다.
|
||||||
|
- **대체 검증 방법:** 갱신 전후 `ko/en/ja` tuple을 스크립트로 대조하고 `xcodebuild`로 컴파일을 확인한다.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `SodaLive/Sources/I18n/I18n.swift`
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- Consumes: Phase 1의 기존 키 갱신 대상 목록.
|
||||||
|
- Produces: CSV JP가 반영된 `I18n.swift`.
|
||||||
|
|
||||||
|
- [x] 갱신 대상만 수정하고 `en:` 값을 변경하지 않는다.
|
||||||
|
- [x] `OQ-001`의 `しいシリーズ`는 `新しいシリーズ`로 보정한 JP를 사용한다. 기존 리소스 미매칭 항목이므로 신규 키는 추가하지 않았다.
|
||||||
|
- [x] CSV 셀 안의 실제 줄바꿈은 Swift 문자열에 실제 개행으로 붙여넣지 않고 `\n` escape로 작성한다.
|
||||||
|
- [x] HTML br 표기는 앱 문자열에 그대로 두지 않고 `\n`으로 변환한다.
|
||||||
|
- [x] placeholder 정규화로 매칭된 항목은 Swift 함수 인자 순서를 유지하고 `ja` 문장 내 인자 위치만 CSV JP 기준으로 반영한다.
|
||||||
|
- [x] 기존 한국어 리소스가 exact 또는 placeholder 정규화 기준으로 없는 항목은 수정 대상에서 제외한다.
|
||||||
|
- [x] 동일 KR 또는 동일 정규화 KR이 여러 키에 존재하고 기존 JP가 서로 다르면 CSV JP를 우선 반영한다.
|
||||||
|
|
||||||
|
#### Task 2.2 기존 리소스 없음 항목 제외 처리
|
||||||
|
|
||||||
|
**Goal 실행 `P2-T2`:** 기존 `I18n.swift`에 exact 또는 placeholder 정규화 기준으로 없는 CSV 항목을 신규 키로 추가하지 않고 제외 목록으로 문서화한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P2-T1` 완료, `OQ-004`, `OQ-005` 확정.
|
||||||
|
- **완료 증거:** 제외 대상 수와 제외 사유가 Progress에 기록되고, 신규 `I18n` 키가 추가되지 않는다.
|
||||||
|
- **범위 밖:** 영어 문구 추정, 호출부 없는 키 추가, 공통 abstraction 확장.
|
||||||
|
- **TDD 예외 사유:** 문서화와 범위 제외 Task다.
|
||||||
|
- **대체 검증 방법:** `git diff`와 정적 검색으로 `I18n.swift` 신규 키 추가가 없고 기존 키 `ja` 값만 변경됐는지 확인한다.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Read: `SodaLive/Sources/I18n/I18n.swift`
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- Consumes: Phase 1 기존 리소스 없음 제외 목록.
|
||||||
|
- Produces: 제외 대상과 제외 사유 기록.
|
||||||
|
|
||||||
|
- [x] direct exact 미매칭 139개 중 분할 리소스 매핑 2개를 제외한 137개 항목은 신규 키로 추가하지 않는다.
|
||||||
|
- [x] CSV에만 있는 placeholder 문구도 정규화 후 기존 한국어 리소스가 없으면 제외한다.
|
||||||
|
- [x] 제외 대상은 Progress에 `기존 한국어 리소스 없음` 사유로 기록한다.
|
||||||
|
- [x] `SodaLive/Sources/**` 호출부를 신규 `I18n` 키로 바꾸지 않는다.
|
||||||
|
|
||||||
|
#### Task 2.3 정적 점검과 문서 동기화
|
||||||
|
|
||||||
|
**Goal 실행 `P2-T3`:** 반영 결과와 문서를 동기화하고 누락·오반영 후보를 제거한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P2-T1`, `P2-T2` 완료.
|
||||||
|
- **완료 증거:** 정적 검색, 문서 Progress, Decision Log가 최신 상태다.
|
||||||
|
- **범위 밖:** Gate 실패와 무관한 추가 번역.
|
||||||
|
- **TDD 예외 사유:** 정적 검증과 문서화 Task다.
|
||||||
|
- **대체 검증 방법:** `rg`와 Python 대조 스크립트로 CSV 반영 상태를 확인한다.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Read: `SodaLive/Sources/I18n/I18n.swift`
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/prd.md`
|
||||||
|
- Modify: `docs/20260811_일본어_string_resource_반영/plan-task.md`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- Consumes: Phase 2 변경 diff.
|
||||||
|
- Produces: 최신 Progress, Decision Log, 발견된 문제 목록.
|
||||||
|
|
||||||
|
- [x] `rg -n 'pick\(ko:.*ja:' SodaLive/Sources/I18n/I18n.swift`와 diff로 변경 구간을 점검한다.
|
||||||
|
- [x] CSV 확정 항목과 `I18n.swift` `ja` 값을 exact/placeholder 정규화 기준으로 대조한다.
|
||||||
|
- [x] CSV 실제 줄바꿈 대상이 `I18n.swift`에서 Swift 문자열 `\n`으로 작성됐는지 대조한다.
|
||||||
|
- [x] HTML br 표기가 `I18n.swift`에 literal로 남지 않았는지 대조한다.
|
||||||
|
- [x] 중복 KR 또는 중복 정규화 KR의 `ja` 값이 CSV JP로 통일됐는지 대조한다.
|
||||||
|
- [x] 제외된 항목은 `발견된 문제` 또는 PRD Open Questions에 연결한다.
|
||||||
|
|
||||||
|
### 완료 조건
|
||||||
|
|
||||||
|
- [x] `P2-T1`, `P2-T2`, `P2-T3`의 체크박스와 완료 증거가 모두 충족됐다.
|
||||||
|
- [x] 확정 항목은 기존 키 갱신 또는 명시적 제외로 추적된다.
|
||||||
|
- [x] 기존 영어 문구와 기능 동작이 바뀌지 않았다.
|
||||||
|
|
||||||
|
### 검증 방법
|
||||||
|
|
||||||
|
#### Phase 2 Gate
|
||||||
|
|
||||||
|
**Goal 실행 `P2-GATE`:** 일본어 문자열 반영, 빌드, 대표 화면 수동 QA를 최종 판정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** Phase 2의 모든 활성 Task goal 완료.
|
||||||
|
- **완료 증거:** 아래 명령과 수동 검증 결과를 Progress에 기록.
|
||||||
|
- **범위 밖:** Gate 통과를 위한 test 삭제·완화, 관련 없는 리팩터링.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** 두 Debug 빌드는 `** BUILD SUCCEEDED **`. 테스트 액션이 미구성되어 실패하면 `Scheme ... is not currently configured for the test action.`을 스킴 제약으로 기록한다.
|
||||||
|
|
||||||
|
수동 검증:
|
||||||
|
|
||||||
|
- [x] 앱 언어 `ja`에서 홈 탭 `ホーム`, `コンテンツ`, `トーク`, `マイページ` 표시를 확인한다.
|
||||||
|
- [x] 변경된 개행 문구는 실제 `I18n.swift` 런타임 드라이버에서 CSV 기준 줄바꿈으로 확인한다.
|
||||||
|
- [x] CSV 셀 안의 실제 줄바꿈이 런타임 문자열에서 줄바꿈으로 확인된다.
|
||||||
|
- [x] HTML br 표기 원문이 소스와 런타임 문자열에 남지 않았다.
|
||||||
|
- [x] 크리에이터명·랭킹·퍼센트 placeholder 문구가 실제 값과 함께 깨지지 않는다.
|
||||||
|
- [x] 구매/대여 완료 문자열이 일본어 런타임 값으로 확인된다. 기존 리소스가 없는 실패 문구는 제외 정책을 따른다.
|
||||||
|
- [x] 기존 리소스 없음으로 제외한 항목이 신규 키로 추가되지 않았다.
|
||||||
|
|
||||||
|
## 실행 순서와 의존성
|
||||||
|
|
||||||
|
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 1 | `P1-T1` | 없음 | 아니요 | CSV 형식 문제를 PRD에 기록 |
|
||||||
|
| 2 | `P1-T2` | `P1-T1` | 아니요 | 매칭 방식 보정 후 재실행 |
|
||||||
|
| 3 | `P1-T3` | `P1-T2` | 아니요 | 제외 항목을 Phase 2 범위에서 제거 |
|
||||||
|
| 4 | `P1-GATE` | Phase 1 Task 전체 | 아니요 | 실패 소유 Task 재실행 |
|
||||||
|
| 5 | `P2-T1` | `P1-GATE` | 아니요 | 기존 키 갱신 대상만 축소 |
|
||||||
|
| 6 | `P2-T2` | `P2-T1` | 아니요 | 제외 목록을 보정하고 신규 키 추가 여부를 재검증 |
|
||||||
|
| 7 | `P2-T3` | `P2-T1`, `P2-T2` | 아니요 | 누락 소유 Task로 되돌림 |
|
||||||
|
| 8 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task의 회귀 수정 goal 생성 |
|
||||||
|
|
||||||
|
```text
|
||||||
|
P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-T3 → P2-GATE
|
||||||
|
```
|
||||||
|
|
||||||
|
## 변경 금지 항목
|
||||||
|
|
||||||
|
- CSV에 없는 영어 문구를 임의 작성하지 않는다.
|
||||||
|
- 기존 한국어 리소스가 없는 CSV 항목을 신규 리소스로 추가하지 않는다.
|
||||||
|
- `I18n.pick` 구조를 개편하지 않는다.
|
||||||
|
- `Pods/**`, `generated/**`, `build/**`를 수정하지 않는다.
|
||||||
|
- 요청 범위 밖 UI 레이아웃, 폰트, API, 결제 SDK 입력값을 변경하지 않는다.
|
||||||
|
- test를 삭제·skip·완화하거나 타입 오류를 우회해 Gate를 통과시키지 않는다.
|
||||||
|
- 토큰, 이메일 실제값, signed URL 같은 민감정보를 log·fixture·문서에 기록하지 않는다.
|
||||||
|
|
||||||
|
## 의사결정 및 중단 규칙
|
||||||
|
|
||||||
|
- PRD와 CSV가 충돌하면 PRD Decision Log에 근거를 남긴 뒤 계획을 갱신한다.
|
||||||
|
- 기존 한국어 리소스가 없는 항목은 구현 대상에서 제외한다.
|
||||||
|
- 구현 범위가 바뀌면 PRD를 먼저 보강하고 이 문서 체크리스트를 갱신한 뒤 코드를 수정한다.
|
||||||
|
- 같은 차단 사유가 3회 연속 반복되고 독립 작업도 불가능할 때만 goal을 `blocked`로 갱신한다.
|
||||||
|
- 완료 증거와 Progress 기록까지 충족한 뒤에만 goal을 `complete`로 갱신한다.
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
기존 기록을 삭제하거나 덮어쓰지 않고 실제 실행 결과를 차수별로 누적한다.
|
||||||
|
|
||||||
|
### P1-T1~P1-GATE 실행 — 2026-08-11
|
||||||
|
|
||||||
|
- 상태: 완료
|
||||||
|
- 무엇을: CSV 구조, 실제 줄바꿈, placeholder, exact/정규화 매핑, 중복 JP와 제외 대상을 확정했다.
|
||||||
|
- 왜: literal 매칭만으로는 런타임 값과 분할 문자열을 놓칠 수 있어 구현 전에 확정 분류가 필요했다.
|
||||||
|
- 어떻게:
|
||||||
|
- CSV Gate — 데이터 242행, 누락 0건, 실제 줄바꿈 33건, placeholder 12건, CSV KR 중복 0건.
|
||||||
|
- 매핑 스캔 — exact 103행, placeholder 정규화 추가 0행, 기존 리소스 없음 139행.
|
||||||
|
- 분할 리소스 확인 — 랭킹 문구와 전체 오디오 소장률 문구를 기존 함수/접두·접미 리소스 후보로 추가 확인.
|
||||||
|
- 중복 리소스 확인 — 같은 KR의 기존 JP 상이 항목 12건을 CSV JP 우선 대상으로 확정.
|
||||||
|
- RED `mapping-validator` — 현재 JP 불일치 64건을 검출하고 exit code 1 확인.
|
||||||
|
- 남은 항목: P2 일본어 JP 값 갱신과 GREEN 검증.
|
||||||
|
- 다음 행동: `P2-T1`에서 기존 `en`과 호출부를 유지하고 `ja`만 CSV 우선값으로 갱신한다.
|
||||||
|
|
||||||
|
### P2-T1~P2-GATE 실행 — 2026-08-11
|
||||||
|
|
||||||
|
- 상태: 완료
|
||||||
|
- 무엇을: 기존 `I18n.swift`의 `ja` literal 64건을 CSV JP 우선값으로 갱신하고 정적·런타임·빌드·시뮬레이터 검증을 완료했다.
|
||||||
|
- 왜: exact 매핑, 중복 KR, 분할 placeholder 리소스에 CSV 승인 일본어를 일관되게 적용하기 위해서다.
|
||||||
|
- 어떻게:
|
||||||
|
- RED `mapping-validator` — 변경 전 JP 불일치 64건, exit code 1.
|
||||||
|
- GREEN `mapping-validator` — 변경 후 불일치 0건, `ko`/`en` literal 동일, `pick` literal 수 `1343 → 1343`.
|
||||||
|
- 변경 범위 — `I18n.swift` 64 insertions/64 deletions, 모두 기존 `ja` literal 교체. 신규 키·호출부 변경 0건.
|
||||||
|
- 분류 — exact CSV 103행, 분할 리소스 2행, 기존 리소스 없음 제외 137행.
|
||||||
|
- 줄바꿈 — 매핑된 multiline occurrence 4건 모두 Swift `\n` escape, HTML br literal 0건.
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` — 성공, `** BUILD SUCCEEDED **`.
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` — 성공, `** BUILD SUCCEEDED **`.
|
||||||
|
- 두 스킴 `test` — `Scheme ... is not currently configured for the test action.` 확인. 테스트 액션 미구성 제약이며 빌드 실패가 아니다.
|
||||||
|
- 실제 `I18n.swift` Swift 런타임 드라이버 — 일본어 탭, 랭킹 interpolation, 소장률 interpolation, CSV `\n`, 구매/대여 완료 문자열 PASS.
|
||||||
|
- iPhone 15 Pro Simulator, 앱 설정 `ja` — 스플래시와 홈 화면 렌더링 성공. `ホーム`, `コンテンツ`, `トーク`, `マイページ`, `新着クリエイター` 표시 확인.
|
||||||
|
- 시각 QA Oracle 2건 — 기능/디자인 PASS, CJK 정밀도 PASS, blocking 0건.
|
||||||
|
- compiled app artifact — `SodaLive`, `SodaLive-dev` 바이너리에서 대표 일본어 문자열 포함 확인.
|
||||||
|
- 증거:
|
||||||
|
- `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive-ja-evidence/launch-ja.png`
|
||||||
|
- `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive-ja-evidence/launch-ja-settled.png`
|
||||||
|
- 남은 항목: 없음.
|
||||||
|
- 다음 행동: 최종 reviewer gate와 문서 체크박스 정합성을 확인한다.
|
||||||
|
|
||||||
|
### Phase 2 최종 리뷰 — 2026-08-11
|
||||||
|
|
||||||
|
- 상태: 판정 완료
|
||||||
|
- 무엇을: 구현 diff, CSV 매핑, PRD·계획 문서의 완료 근거를 독립 reviewer가 재검증했다.
|
||||||
|
- 왜: Phase 2 완료 판정 전에 요구사항 누락과 문서·코드 불일치를 차단하기 위해서다.
|
||||||
|
- 어떻게:
|
||||||
|
- Oracle 최종 판정 — `APPROVE`, blocker 0건, confidence High.
|
||||||
|
- 독립 확인 — `ja` literal 64건만 변경, exact 103행·분할 2행·제외 137행, 매핑 불일치 0건.
|
||||||
|
- 리뷰 보고서 — `reviews/phase2-japanese-string-resource.md`.
|
||||||
|
- 남은 항목: 없음.
|
||||||
|
- 다음 행동: 최종 diff와 working tree 상태를 확인하고 종료한다.
|
||||||
|
|
||||||
|
### 문서 생성 — 2026-08-11
|
||||||
|
|
||||||
|
- 상태: 완료
|
||||||
|
- 무엇을: CSV 기반 일본어 string_resource 반영 PRD와 Goal 실행형 계획 문서를 생성했다.
|
||||||
|
- 왜: 사용자 요청이 구현 전 PRD와 계획문서 생성으로 한정되어 있으며, 모호한 항목을 구현 전에 질문/보류해야 하기 때문이다.
|
||||||
|
- 어떻게:
|
||||||
|
- `docs/sample/sample-prd.md` 확인 — PRD 구조 기준 확인.
|
||||||
|
- `docs/sample/sample-plan-task.md` 확인 — Phase/Task/Goal/검증 기록 구조 확인.
|
||||||
|
- `docs/agent-guides/documentation-policy.md` 확인 — PRD와 계획문서 위치·작성 규칙 확인.
|
||||||
|
- `rg --files | rg 'I18n|String|strings|xcstrings|lproj|Localizable|resource'` — 문자열 리소스 관련 파일 위치 확인.
|
||||||
|
- `codegraph_node(file="SodaLive/Sources/I18n/I18n.swift")` — 현재 `I18n.swift` 구조 확인.
|
||||||
|
- `python3` CSV 파싱 — 데이터 242행, KR 누락 0건, JP 누락 0건, 개행 33행, placeholder 유사 12행 확인.
|
||||||
|
- `python3` KR literal 매칭 — 기존 `I18n.swift` 매칭 103건, 미매칭 139건 확인.
|
||||||
|
- 남은 항목: 사용자 결정 반영 전 기록이며, 최신 결정은 아래 `Open Questions 결정 반영` 기록을 따른다.
|
||||||
|
- 다음 행동: Phase 1의 `P1-T1`부터 실행하되, 기존 한국어 리소스가 없는 항목은 구현 범위에서 제외한다.
|
||||||
|
|
||||||
|
### Open Questions 결정 반영 — 2026-08-11
|
||||||
|
|
||||||
|
- 상태: 완료
|
||||||
|
- 무엇을: 사용자 회신에 따라 `OQ-001`~`OQ-005`를 모두 확정 상태로 갱신하고, 신규 리소스 추가 계획을 제외 처리로 바꿨다.
|
||||||
|
- 왜: 기존 한국어 리소스가 없는 항목은 반영하지 않는다는 결정이 내려졌으므로 신규 키 후보 처리와 신규 `en` 작성 계획을 제거해야 하기 때문이다.
|
||||||
|
- 어떻게:
|
||||||
|
- `OQ-001` — `しいシリーズ`는 오타로 확정하고 `新しいシリーズ`로 보정 반영하도록 기록.
|
||||||
|
- `OQ-002` — placeholder는 기존 한국어 리소스가 있는 항목만 Swift 함수 인자로 변환하도록 기록.
|
||||||
|
- `OQ-003` — 한국어 리소스는 보정하지 않고 JP만 반영하도록 기록.
|
||||||
|
- `OQ-004`, `OQ-005` — 기존 한국어 리소스가 없는 항목과 없는 리소스는 추가하지 않도록 기록.
|
||||||
|
- 남은 항목: 코드 반영은 아직 수행하지 않음.
|
||||||
|
- 다음 행동: 구현 요청이 들어오면 `P1-T1`부터 실행하고, `P2-T2`에서는 신규 키 추가가 없음을 검증한다.
|
||||||
|
|
||||||
|
## Decision Log
|
||||||
|
|
||||||
|
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `2026-08-11` | `DEC-001` | 확정 | 문서는 CSV가 있는 폴더에 `prd.md`, `plan-task.md`로 생성한다. | 사용자 직접 지시 | 전체 |
|
||||||
|
| `2026-08-11` | `DEC-002` | 확정 | 이번 턴에서는 코드 구현을 하지 않는다. | 사용자 직접 지시 | 전체 |
|
||||||
|
| `2026-08-11` | `DEC-003` | 폐기 | 기존 `I18n.swift` 매칭 항목은 `ja`만 갱신하고 신규 키는 호출부와 영어 기준이 있는 경우로 제한한다. | 사용자 결정으로 기존 한국어 리소스가 없는 항목은 추가하지 않기로 변경 | `P2-T1`, `P2-T2` |
|
||||||
|
| `2026-08-11` | `DEC-004` | 확정 | `しいシリーズ`는 `新しいシリーズ`로 보정한다. | 사용자 확인 | `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-005` | 확정 | placeholder 문구는 기존 한국어 리소스가 있는 항목만 Swift 함수 인자로 변환한다. | 사용자 결정 | `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-006` | 확정 | 한국어 리소스 원문은 보정하지 않고 JP만 반영한다. | 사용자 결정 | `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-007` | 확정 | 기존 한국어 리소스가 없는 CSV 항목은 신규 리소스로 추가하지 않는다. | 사용자 결정 | `P2-T2` |
|
||||||
|
| `2026-08-11` | `DEC-008` | 확정 | `{{...}}`, `{...}`, `n%`, `n위`, `0:00` placeholder는 Swift interpolation과 정규화해 매핑 후보를 판정한다. | 사용자 결정과 재스캔 결과 | `P1-T2`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-009` | 확정 | 같은 KR 또는 같은 정규화 KR의 기존 JP가 여러 값이면 CSV JP를 우선 반영한다. | 사용자 결정과 중복 JP 재스캔 결과 | `P1-T2`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-010` | 확정 | HTML br 표기는 앱 문자열에 그대로 반영하지 않고 줄바꿈 `\n`으로 변환한다. | 사용자 결정과 CSV 검색 결과 현재 0건 | `P1-T1`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-011` | 확정 | CSV 셀 안의 실제 줄바꿈은 Swift 문자열 줄바꿈 escape `\n`으로 변환한다. | 사용자 결정과 CSV 파싱 결과 개행 33행 | `P1-T1`, `P2-T1` |
|
||||||
|
|
||||||
|
## 발견된 문제
|
||||||
|
|
||||||
|
| ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `ISSUE-001` | Medium | 해결 | CSV 물리 line 281~283의 JP가 `しいシリーズ`로 시작해 `新しいシリーズ` 오탈자 가능성이 있다. | `P1-T3`, `P2-T1` | 사용자 확인으로 `新しいシリーズ` 보정 반영 확정 |
|
||||||
|
| `ISSUE-002` | Medium | 해결 | placeholder 유사 문구 12건이 `{{...}}`, `{...}`, `n`, `0:00` 형식으로 섞여 있다. | `P1-T2`, `P2-T1` | Swift interpolation과 정규화 매칭하고, 현재 재스캔 기준 추가 매칭은 0건으로 기록 |
|
||||||
|
| `ISSUE-003` | Medium | 해결 | CSV KR 242행 중 현재 `I18n.swift` `ko:` literal과 직접 매칭되지 않는 항목이 139건이다. | `P1-T2`, `P2-T2` | 기존 한국어 리소스가 없는 항목은 신규 키로 추가하지 않음 |
|
||||||
|
| `ISSUE-004` | Medium | 해결 | CSV 매칭 KR 중 기존 `I18n.swift` JP가 중복 항목끼리 서로 다른 경우가 12건이다. | `P1-T2`, `P2-T1` | CSV JP 우선 반영 규칙 확정 |
|
||||||
|
| `ISSUE-005` | Low | 해결 | CSV와 기존 문서에서 HTML br 표기 검색 결과 현재 0건이다. | `P1-T1`, `P2-T1` | 향후 발견 시 literal 대신 `\n` 변환 반영 규칙 확정 |
|
||||||
|
| `ISSUE-006` | Medium | 해결 | CSV 셀 안의 실제 줄바꿈 포함 항목이 33건이다. | `P1-T1`, `P2-T1` | Swift 문자열에는 실제 개행 붙여넣기가 아니라 `\n` escape로 반영 |
|
||||||
|
|
||||||
|
## 최종 보고 형식
|
||||||
|
|
||||||
|
최종 보고에는 다음 항목을 실제 실행 결과로 채운다.
|
||||||
|
|
||||||
|
- 구현 결과: 완료한 Phase와 사용자가 확인할 수 있는 흐름.
|
||||||
|
- 변경: 주요 파일과 동작.
|
||||||
|
- 결정: 중요한 Decision Log ID와 내용.
|
||||||
|
- 검증: 실행 명령별 성공/실패, exit code, 핵심 수치, 수동 검증 결과.
|
||||||
|
- 남은 항목: 외부 의존, 후속 범위, 제외 항목 또는 후속 질문.
|
||||||
|
- 문서: 갱신한 PRD, plan, review 경로.
|
||||||
|
|
||||||
|
최종 보고는 성공을 추정하지 않는다. 실제 실행한 최신 검증 결과와 완료되지 않은 범위를 함께 전달한다.
|
||||||
218
docs/20260811_일본어_string_resource_반영/prd.md
Normal file
218
docs/20260811_일본어_string_resource_반영/prd.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# 일본어 string_resource 반영 PRD
|
||||||
|
|
||||||
|
## 문서 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 문서 상태 | 구현 기준 확정 |
|
||||||
|
| 작성일 | `2026-08-11` |
|
||||||
|
| 최종 수정일 | `2026-08-11` |
|
||||||
|
| 대상 제품 | SodaLive iOS 일본어 문자열 리소스 |
|
||||||
|
| 작성자·결정권자 | 작성자: Sisyphus / 결정권자: 제품·iOS 담당자 |
|
||||||
|
| 관련 API Contract | 해당 없음 |
|
||||||
|
| 관련 구현 계획 | `docs/20260811_일본어_string_resource_반영/plan-task.md` |
|
||||||
|
| 관련 원본 | `docs/20260811_일본어_string_resource_반영/kr_jp_string_resource.csv` |
|
||||||
|
| 관련 review | 없음 |
|
||||||
|
|
||||||
|
### 요구사항 상태
|
||||||
|
|
||||||
|
| 상태 | 의미 | 구현 처리 |
|
||||||
|
|---|---|---|
|
||||||
|
| 확정 | CSV와 현재 iOS 문자열 구조만으로 구현 기준을 정할 수 있음 | `plan-task.md`의 Task와 완료 증거로 추적 |
|
||||||
|
| 미결 | 제품·번역·키 매핑 결정이 더 필요함 | Open Questions에 기록하고 임의 구현 금지 |
|
||||||
|
| 제외 | 이번 작업에서 구현하지 않음 | Non-Goals와 Decision Log에 기록 |
|
||||||
|
|
||||||
|
### 문서 우선순위와 갱신 순서
|
||||||
|
|
||||||
|
1. CSV의 KR/JP 원문은 이 PRD의 입력 기준으로 사용한다.
|
||||||
|
2. `SodaLive/Sources/I18n/I18n.swift`의 기존 `pick(ko:en:ja:)` 구조를 우선 유지한다.
|
||||||
|
3. `{{키워드}}의 오디오`처럼 `{{...}}`, `{...}`, `n%`, `n위`, `0:00` 형태로 런타임 값이 들어가는 CSV 문구는 Swift interpolation 문구와 정규화 매칭한다.
|
||||||
|
4. CSV 셀 안의 실제 줄바꿈과 HTML br 표기는 문자열 그대로 반영하지 않고 Swift 문자열 줄바꿈 escape `\n`으로 변환한다.
|
||||||
|
5. CSV와 구현 구조가 충돌하면 Decision Log → 요구사항 → `plan-task.md` 순서로 갱신한다.
|
||||||
|
6. 확정된 결정 밖의 추정 번역, 추정 영어 문구, 추정 키는 구현하지 않는다.
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
`kr_jp_string_resource.csv`에 정리된 한국어-일본어 문구를 SodaLive iOS 앱의 문자열 리소스에 반영해 일본어 환경에서 사용자 노출 문구를 최신 번역으로 표시한다. 현재 앱은 `SodaLive/Sources/I18n/I18n.swift`의 하드코딩 맵과 `pick(ko:en:ja:)` 헬퍼를 통해 언어별 문자열을 선택한다. 이 작업은 코드 구현 전 PRD와 실행 계획만 작성하며, 실제 `I18n.swift` 수정은 후속 승인 후 진행한다.
|
||||||
|
|
||||||
|
## 2. Problem Statement
|
||||||
|
|
||||||
|
현재 사용자는 다음 문제를 겪는다.
|
||||||
|
|
||||||
|
- 일본어 번역 CSV가 앱 문자열 리소스에 아직 반영되지 않아 일본어 UI 문구가 최신 번역 기준과 다를 수 있다.
|
||||||
|
- CSV에는 개행 문구, placeholder 문구, 오탈자 후보가 섞여 있어 임의 치환 시 포맷 깨짐이나 잘못된 일본어 노출 위험이 있다.
|
||||||
|
- `I18n.swift`는 `ko`, `en`, `ja`를 함께 요구하므로 CSV에 없는 영어 문구를 임의 생성하면 기존 영어 UX가 변할 수 있다.
|
||||||
|
|
||||||
|
문제를 해결했다는 판단은 CSV의 확정 가능한 JP 값이 `I18n.swift`에 반영되고, 일본어 선택 시 해당 문구가 표시되며, 빌드가 성공하는 것으로 한다.
|
||||||
|
|
||||||
|
## 3. Goals
|
||||||
|
|
||||||
|
### 3.1 제품 목표
|
||||||
|
|
||||||
|
- CSV 데이터 242행 중 기존 한국어 리소스가 확인된 KR/JP 문구만 iOS 일본어 문자열에 반영한다.
|
||||||
|
- 기존 `I18n.swift`에 동일 KR 문구가 있거나 placeholder 정규화 후 같은 문구로 판정되는 항목은 기존 `en` 값을 유지하고 `ja` 값만 CSV 기준으로 갱신한다.
|
||||||
|
- 동일 KR 문구가 없는 CSV 항목은 신규 리소스로 추가하지 않는다.
|
||||||
|
- 개행 33행과 placeholder 유사 12행은 기존 한국어 리소스가 있는 경우에만 CSV 셀 안의 실제 줄바꿈을 Swift 문자열 `\n`으로 변환하고 함수 인자 순서를 보존해 반영한다.
|
||||||
|
- CSV에 HTML br 표기가 있으면 literal이 아니라 Swift 문자열 `\n`으로 변환해 반영한다.
|
||||||
|
- 같은 KR 또는 같은 정규화 KR이 여러 `I18n` 항목에 중복되어 있고 기존 JP가 서로 다르면 CSV JP를 우선해 같은 값으로 반영한다.
|
||||||
|
|
||||||
|
### 3.2 UX 목표
|
||||||
|
|
||||||
|
- 일본어 환경에서 홈, 콘텐츠, 대화, 마이페이지, 인증, 구매, 보관함 등 CSV가 포함한 주요 화면 문구가 자연스럽게 표시된다.
|
||||||
|
- placeholder가 포함된 문구는 실제 값 삽입 후 일본어 문장 순서가 깨지지 않는다.
|
||||||
|
- CSV의 `しいシリーズ` 오탈자 후보는 `新しいシリーズ`로 보정해 반영한다.
|
||||||
|
|
||||||
|
## 4. Non-Goals
|
||||||
|
|
||||||
|
- 이번 요청에서는 코드, 리소스, Xcode 프로젝트 설정을 수정하지 않는다.
|
||||||
|
- String Catalog(`.xcstrings`) 도입이나 `I18n.swift` 구조 개편은 하지 않는다.
|
||||||
|
- CSV에 없는 영어 번역을 새로 작성하지 않는다.
|
||||||
|
- 기존 한국어 리소스가 없는 CSV 항목을 신규 리소스로 추가하지 않는다.
|
||||||
|
- 서버 API, CMS, 백엔드 번역 리소스는 변경하지 않는다.
|
||||||
|
- 일본어 폰트, 레이아웃, 줄바꿈 UI 디자인 조정은 이 작업의 범위가 아니다.
|
||||||
|
|
||||||
|
## 5. Target Users and Permissions
|
||||||
|
|
||||||
|
| 사용자 | 목표 | 주요 작업 | 사용 환경 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 일본어 앱 사용자 | 일본어 UI 문구를 최신 번역으로 확인 | 앱 탐색, 콘텐츠 소비, 대화, 구매, 인증 | iOS 앱, 언어 설정 `ja` 또는 시스템 일본어 |
|
||||||
|
| iOS 개발자 | CSV 기준으로 안전하게 문자열 반영 | 문자열 매핑, 빌드 검증, 수동 QA | Xcode, `SodaLive.xcworkspace` |
|
||||||
|
| 제품·번역 담당자 | 확정된 일본어 반영 범위 확인 | 오탈자 보정과 제외 기준 승인 | PRD/계획문서 리뷰 |
|
||||||
|
|
||||||
|
권한 요구사항은 해당 없음이다. 단, 인증/구매/성인 관련 문구는 실제 기능 동작을 바꾸지 않고 표시 문자열만 바꾼다.
|
||||||
|
|
||||||
|
## 6. 핵심 사용자 흐름
|
||||||
|
|
||||||
|
1. 사용자가 앱 언어를 일본어로 설정하거나 시스템 언어가 일본어인 상태에서 앱을 실행한다.
|
||||||
|
2. 앱은 `LanguageHeaderProvider.current` 결과가 `ja`일 때 `I18n.pick`의 일본어 문자열을 선택한다.
|
||||||
|
3. 사용자는 홈, 콘텐츠, 대화, 마이페이지, 인증/구매 화면에서 CSV 기준 일본어 문구를 본다.
|
||||||
|
4. placeholder 문구는 실제 크리에이터명, 이메일, 요일, 퍼센트 등 런타임 값과 함께 자연스럽게 표시된다.
|
||||||
|
|
||||||
|
## 7. 정보 구조와 라우팅
|
||||||
|
|
||||||
|
별도 route 변경은 없다. 문자열 반영 대상은 기존 화면과 모듈에 종속된다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
SodaLive/Sources/I18n/I18n.swift
|
||||||
|
I18n.Module.ScreenOrDomain.Role
|
||||||
|
```
|
||||||
|
|
||||||
|
역할 중심 키 이름을 유지한다. 이번 작업에서는 기존 한국어 리소스가 없는 CSV 항목의 신규 키를 추가하지 않으며, 공통 네임스페이스 승격도 하지 않는다.
|
||||||
|
|
||||||
|
## 8. 기능 요구사항
|
||||||
|
|
||||||
|
### 8.1 CSV 입력과 매핑
|
||||||
|
|
||||||
|
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `JSR-001` | 확정 | `kr_jp_string_resource.csv`의 `KR`, `JP` 컬럼을 입력 기준으로 사용한다. | CSV가 242개 데이터 행, KR 누락 0건, JP 누락 0건으로 파싱된다. | `P1-T1` |
|
||||||
|
| `JSR-002` | 확정 | 기존 `I18n.swift`에 동일 KR literal이 있거나 placeholder 정규화 후 매칭되는 항목은 `ja` 값만 CSV 기준으로 갱신한다. | 기존 `en` 값과 호출부는 변경되지 않는다. 현재 스캔 기준 exact 103행, placeholder 정규화 추가 0행으로 총 103행이 매핑 후보다. | `P1-T2`, `P2-T1` |
|
||||||
|
| `JSR-003` | 제외 | 동일 KR literal 또는 placeholder 정규화 매칭이 없는 139개 CSV 항목은 신규 키로 추가하지 않는다. | 기존 한국어 리소스가 없는 항목은 `I18n.swift`와 호출부를 변경하지 않는다. | `P1-T3`, `P2-T2` |
|
||||||
|
| `JSR-004` | 확정 | CSV 셀 안의 실제 줄바꿈과 HTML br 표기는 Swift 문자열 줄바꿈 escape `\n`으로 변환한다. | 일본어 `\n` 위치가 CSV와 동일하거나 제품 승인된 위치로 반영되며, HTML br 표기가 앱 문자열에 그대로 남지 않는다. | `P2-T1` |
|
||||||
|
| `JSR-005` | 확정 | placeholder 유사 12개 문구는 앱 내에서 변경 가능한 리소스 후보로 보고 Swift interpolation과 정규화 매칭한다. | 정규화 매칭되는 기존 리소스는 Swift 함수 인자 순서를 보존해 반영하고, 정규화 후에도 기존 리소스가 없으면 신규 키를 추가하지 않고 제외한다. | `P1-T3`, `P2-T1` |
|
||||||
|
| `JSR-006` | 확정 | CSV의 `しいシリーズ` JP 오탈자는 `新しいシリーズ`로 보정한다. | 해당 KR이 기존 한국어 리소스와 매칭될 때만 보정 JP를 반영한다. | `P1-T3`, `P2-T1` |
|
||||||
|
| `JSR-007` | 확정 | 같은 KR 또는 같은 정규화 KR이 여러 `I18n` 항목에 중복되어 있고 기존 JP가 다르면 CSV JP를 우선한다. | 현재 스캔 기준 중복 KR의 기존 JP 상이 항목 12건은 CSV JP로 통일한다. | `P1-T2`, `P2-T1` |
|
||||||
|
|
||||||
|
### 8.2 품질과 검증
|
||||||
|
|
||||||
|
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `QA-001` | 확정 | 문자열 변경 후 두 앱 스킴 Debug 빌드를 실행한다. | `SodaLive`, `SodaLive-dev` Debug 빌드가 `** BUILD SUCCEEDED **`로 끝난다. | `P2-GATE` |
|
||||||
|
| `QA-002` | 확정 | 테스트 스킴 제약을 확인한다. | 테스트 액션 미구성 상태면 실패가 코드 실패가 아님을 Progress에 기록한다. | `P2-GATE` |
|
||||||
|
| `QA-003` | 확정 | 일본어 언어 선택 상태에서 대표 화면 수동 QA를 수행한다. | CSV 대표 문구, 개행, placeholder 표시를 스크린별로 확인하고 결과를 Progress에 남긴다. | `P2-GATE` |
|
||||||
|
| `QA-004` | 확정 | 민감 정보나 SDK 입력값을 번역 대상으로 오인하지 않는다. | 인증 토큰, API path, Bootpay/SDK 고정값은 변경하지 않는다. | `P2-T1` |
|
||||||
|
|
||||||
|
## 9. UI/UX Expectations
|
||||||
|
|
||||||
|
- 기존 화면 레이아웃과 컴포넌트 구조를 유지한다.
|
||||||
|
- 일본어 번역이 길어져도 이번 작업에서는 레이아웃을 임의 조정하지 않는다. 잘림이 발견되면 후속 UI 이슈로 기록한다.
|
||||||
|
- 개행이 포함된 문구는 CSV 의도를 우선하고, 화면에서 부자연스러운 경우 제품·번역 담당자 확인 후 보정한다.
|
||||||
|
|
||||||
|
## 10. API 계약
|
||||||
|
|
||||||
|
해당 없음. 이 작업은 클라이언트 로컬 문자열 리소스 반영이다.
|
||||||
|
|
||||||
|
## 11. 보안과 데이터 취급
|
||||||
|
|
||||||
|
- 인증 헤더, 토큰 저장소, `UserDefaultsKey.token` 관련 로직은 변경하지 않는다.
|
||||||
|
- 이메일 placeholder 문구는 예시 이메일을 문서나 테스트 fixture에 하드코딩하지 않는다.
|
||||||
|
- 구매/결제 SDK에 전달되는 고정 문자열은 사용자 노출 문구가 아닌 경우 번역하지 않는다.
|
||||||
|
|
||||||
|
## 12. 성능과 품질 요구사항
|
||||||
|
|
||||||
|
- 런타임 성능 요구사항은 변경하지 않는다.
|
||||||
|
- 신규 dependency를 추가하지 않는다.
|
||||||
|
- 문자열 선택 방식은 기존 `I18n.pick` 구조를 유지한다.
|
||||||
|
- 검증은 정적 매핑 점검, Xcode 빌드, 일본어 대표 화면 수동 QA로 수행한다.
|
||||||
|
|
||||||
|
## 13. 성공 기준
|
||||||
|
|
||||||
|
### 13.1 기능 수용 기준
|
||||||
|
|
||||||
|
- [x] CSV 242행의 처리 상태가 `기존 키 갱신`, `placeholder 정규화 후 기존 키 갱신`, `오탈자 보정 후 기존 키 갱신`, `기존 리소스 없음으로 제외` 중 하나로 분류된다.
|
||||||
|
- [x] 확정 항목의 일본어 문구가 `I18n.swift`에 반영된다.
|
||||||
|
- [x] placeholder와 개행 문구가 깨지지 않는다.
|
||||||
|
- [x] CSV 셀 안의 실제 줄바꿈과 HTML br 표기는 문자열 그대로가 아니라 Swift 문자열 `\n`으로 반영된다.
|
||||||
|
- [x] 중복 KR 또는 중복 정규화 KR의 JP는 CSV JP 우선으로 정리된다.
|
||||||
|
- [x] 기존 한국어 리소스가 없는 항목은 구현하지 않고 제외 사유가 문서화된다.
|
||||||
|
|
||||||
|
### 13.2 품질 수용 기준
|
||||||
|
|
||||||
|
- [x] `SodaLive` Debug 빌드가 성공한다.
|
||||||
|
- [x] `SodaLive-dev` Debug 빌드가 성공한다.
|
||||||
|
- [x] 테스트 액션 실행 가능 여부와 결과가 기록된다.
|
||||||
|
- [x] 일본어 언어 설정에서 대표 화면 수동 QA가 완료된다.
|
||||||
|
|
||||||
|
### 13.3 추적성 완료 기준
|
||||||
|
|
||||||
|
- [x] 모든 확정 요구사항이 `plan-task.md`의 Goal과 연결된다.
|
||||||
|
- [x] 모든 제외 항목에 제외 근거와 영향 Goal이 있다.
|
||||||
|
- [x] 구현 결과와 검증 기록은 `plan-task.md` Progress에 누적된다.
|
||||||
|
|
||||||
|
## 14. Open Questions
|
||||||
|
|
||||||
|
| ID | 상태 | 결정 필요 사항 | 현재 권고 | 결정 주체 | 결정 기한/시점 | 영향 Goal |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| `OQ-001` | 확정 | CSV 물리 line 281~283의 JP `最近見たシリーズはありません。\nしいシリーズを見つけましょう!`에서 `しいシリーズ`가 오탈자인지 확인이 필요했다. | `新しいシリーズを見つけましょう!`로 보정한다. | 사용자 | 확정 완료 | `P1-T3`, `P2-T1` |
|
||||||
|
| `OQ-002` | 확정 | `{{장르}}`, `{{크리에이터이름}}`, `{email}`, `{요일}`, `n%`, `n위`, `0:00` 토큰을 Swift 함수 인자로 변환할지 결정이 필요했다. | 기존 한국어 리소스가 있는 항목만 함수 인자로 변환하고, 기존 한국어 리소스가 없는 항목은 반영하지 않는다. | 사용자 | 확정 완료 | `P1-T3`, `P2-T1` |
|
||||||
|
| `OQ-003` | 확정 | CSV KR 원문 중 `메세지`, `메거진` 같은 한국어 오탈자 후보를 보정할지 결정이 필요했다. | 한국어 리소스는 보정하지 않고 JP만 반영한다. | 사용자 | 확정 완료 | `P1-T3`, `P2-T1` |
|
||||||
|
| `OQ-004` | 확정 | 기존 `I18n.swift`에 동일 KR literal이 없는 139개 항목을 신규 키로 추가할지 결정이 필요했다. | 기존 한국어 리소스가 없는 항목은 추가하지 않는다. | 사용자 | 확정 완료 | `P1-T3`, `P2-T2` |
|
||||||
|
| `OQ-005` | 확정 | 신규 키 추가 시 CSV에 없는 `en` 값을 어떻게 채울지 결정이 필요했다. | 없는 리소스는 추가하지 않으므로 신규 `en` 작성도 하지 않는다. | 사용자 | 확정 완료 | `P2-T2` |
|
||||||
|
| `OQ-006` | 확정 | `{{키워드}}의 오디오`처럼 `{{...}}` 형태로 들어간 CSV 문구를 기존 앱 리소스 매핑 후보로 볼지 결정이 필요했다. | 앱 내에서 변경 가능한 리소스이므로 Swift interpolation 문구와 정규화 매칭한다. | 사용자 | 확정 완료 | `P1-T2`, `P2-T1` |
|
||||||
|
| `OQ-007` | 확정 | 중복 KR 항목의 기존 JP가 서로 다를 때 어떤 값을 우선할지 결정이 필요했다. | CSV JP를 우선 반영한다. | 사용자 | 확정 완료 | `P1-T2`, `P2-T1` |
|
||||||
|
| `OQ-008` | 확정 | CSV에 HTML br 표기가 있을 때 앱 문자열에 그대로 반영할지 결정이 필요했다. | HTML br 표기는 literal이 아니라 Swift 문자열 줄바꿈 `\n`으로 변환한다. | 사용자 | 확정 완료 | `P1-T1`, `P2-T1` |
|
||||||
|
| `OQ-009` | 확정 | CSV 셀 안의 실제 줄바꿈을 앱 문자열에 어떤 형태로 반영할지 결정이 필요했다. | CSV 실제 줄바꿈은 Swift 문자열 줄바꿈 escape `\n`으로 변환한다. | 사용자 | 확정 완료 | `P1-T1`, `P2-T1` |
|
||||||
|
|
||||||
|
## 15. 요구사항 추적표
|
||||||
|
|
||||||
|
| 요구사항 범위 | API Contract | 계획 Phase | Goal | 자동 검증 | 수동 검증 |
|
||||||
|
|---|---|---:|---|---|---|
|
||||||
|
| `JSR-001` | 해당 없음 | 1 | `P1-T1` | CSV 파싱 명령 | CSV 행 수와 누락 확인 |
|
||||||
|
| `JSR-002` | 해당 없음 | 1, 2 | `P1-T2`, `P2-T1` | KR literal 매칭 스크립트, 빌드 | 일본어 대표 화면 확인 |
|
||||||
|
| `JSR-003~007` | 해당 없음 | 1, 2 | `P1-T2`, `P1-T3`, `P2-T1`, `P2-T2` | placeholder 정규화/개행/중복 JP 점검 스크립트 | 제외 목록과 보정 JP 리뷰 |
|
||||||
|
| `QA-001~004` | 해당 없음 | 2 | `P2-GATE` | `xcodebuild` | 언어 설정 `ja` 수동 QA |
|
||||||
|
|
||||||
|
## 16. Decision Log
|
||||||
|
|
||||||
|
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·계약·Goal |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `2026-08-11` | `DEC-001` | 확정 | 이번 요청에서는 PRD와 `plan-task.md`만 생성하고 앱 코드는 수정하지 않는다. | 사용자 직접 지시 | 전체 |
|
||||||
|
| `2026-08-11` | `DEC-002` | 확정 | 일본어 문자열 반영의 기본 대상은 `SodaLive/Sources/I18n/I18n.swift`로 둔다. | 현재 코드의 `I18n.pick(ko:en:ja:)` 구조 확인 | `JSR-002`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-003` | 폐기 | 신규 키 추가는 영어 기준과 호출부가 확인된 항목으로 제한한다. | 사용자 결정으로 기존 한국어 리소스가 없는 항목은 추가하지 않기로 변경 | `JSR-003`, `OQ-004`, `OQ-005` |
|
||||||
|
| `2026-08-11` | `DEC-004` | 확정 | `しいシリーズ`는 `新しいシリーズ`로 보정한다. | 사용자 확인 | `JSR-006`, `OQ-001`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-005` | 확정 | placeholder 문구는 기존 한국어 리소스가 있는 항목만 Swift 함수 인자로 변환한다. | 사용자 결정 | `JSR-005`, `OQ-002`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-006` | 확정 | 한국어 리소스 원문은 보정하지 않고 JP만 반영한다. | 사용자 결정 | `OQ-003`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-007` | 확정 | 기존 한국어 리소스가 없는 CSV 항목은 신규 리소스로 추가하지 않는다. | 사용자 결정 | `JSR-003`, `OQ-004`, `OQ-005`, `P2-T2` |
|
||||||
|
| `2026-08-11` | `DEC-008` | 확정 | `{{...}}`, `{...}`, `n%`, `n위`, `0:00` 형태 placeholder는 Swift interpolation과 정규화해 매핑 후보를 판정한다. | 사용자 결정과 재스캔 결과 | `JSR-002`, `JSR-005`, `OQ-006`, `P1-T2` |
|
||||||
|
| `2026-08-11` | `DEC-009` | 확정 | 같은 KR 또는 같은 정규화 KR의 기존 JP가 여러 값이면 CSV JP를 우선 반영한다. | 사용자 결정과 중복 JP 재스캔 결과 | `JSR-007`, `OQ-007`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-010` | 확정 | HTML br 표기는 앱 문자열에 그대로 반영하지 않고 줄바꿈 `\n`으로 변환한다. | 사용자 결정 | `JSR-004`, `OQ-008`, `P2-T1` |
|
||||||
|
| `2026-08-11` | `DEC-011` | 확정 | CSV 셀 안의 실제 줄바꿈은 Swift 문자열 줄바꿈 escape `\n`으로 변환한다. | 사용자 결정 | `JSR-004`, `OQ-009`, `P2-T1` |
|
||||||
|
|
||||||
|
## 17. 변경 관리
|
||||||
|
|
||||||
|
요구사항 변경 시 다음을 확인한다.
|
||||||
|
|
||||||
|
- [x] Decision Log에 변경 이유와 날짜를 기록했다.
|
||||||
|
- [x] 관련 요구사항 상태·본문·수용 기준을 갱신했다.
|
||||||
|
- [x] `plan-task.md`의 범위·Files·Interfaces·체크박스·완료 증거를 코드 변경 전에 갱신했다.
|
||||||
|
- [x] 기존 Progress와 검증 기록을 삭제하거나 덮어쓰지 않았다.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Phase 2 일본어 String Resource 코드 리뷰
|
||||||
|
|
||||||
|
## 1. 리뷰 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 리뷰 대상 | Phase 2 / P2-T1~P2-GATE |
|
||||||
|
| 기준 commit 또는 working tree | `HEAD` 기준 working tree 변경 |
|
||||||
|
| 리뷰 일자 | 2026-08-11 |
|
||||||
|
| 리뷰어 | Oracle (`openai/gpt-5.6-sol`) |
|
||||||
|
| 기준 문서 | `prd.md`, `plan-task.md`, `kr_jp_string_resource.csv` |
|
||||||
|
| 리뷰 상태 | 판정 완료 |
|
||||||
|
|
||||||
|
## 2. 리뷰 목적과 범위
|
||||||
|
|
||||||
|
### 목적
|
||||||
|
|
||||||
|
- CSV JP 우선값이 기존 일본어 리소스에만 정확히 반영되었는지 확인한다.
|
||||||
|
- 완료 체크박스와 코드·빌드·런타임·수동 검증 기록이 일치하는지 확인한다.
|
||||||
|
|
||||||
|
### 포함 범위
|
||||||
|
|
||||||
|
- 코드: `SodaLive/Sources/I18n/I18n.swift`
|
||||||
|
- 문서: `docs/20260811_일본어_string_resource_반영/**`
|
||||||
|
- 검증: 정적 매핑, 두 스킴 빌드, Swift 런타임 드라이버, 일본어 Simulator 화면
|
||||||
|
|
||||||
|
### 제외 범위
|
||||||
|
|
||||||
|
- 사용자 선행 변경인 `SodaLive.xcodeproj/project.pbxproj`
|
||||||
|
- 기존 리소스가 없는 CSV 137행의 신규 키 생성
|
||||||
|
- test action이 구성되지 않은 두 스킴의 자동 테스트
|
||||||
|
|
||||||
|
## 3. 판정 기준
|
||||||
|
|
||||||
|
| 심각도 | 기준 |
|
||||||
|
|---|---|
|
||||||
|
| Blocker | 데이터 손실 위험, 핵심 흐름 불능, 완료 판정을 무효화하는 문제 |
|
||||||
|
| High | 확정 요구사항 위반 또는 주요 회귀 |
|
||||||
|
| Medium | 제한된 조건의 기능·표시 문제 |
|
||||||
|
| Low | 문서 정합성 또는 비핵심 유지보수 문제 |
|
||||||
|
|
||||||
|
## 4. 검토한 근거
|
||||||
|
|
||||||
|
### 문서와 코드
|
||||||
|
|
||||||
|
- 요구사항: `JSR-001~JSR-005`, PRD 수용 기준
|
||||||
|
- 계획: `P2-T1~P2-GATE`
|
||||||
|
- 코드: `SodaLive/Sources/I18n/I18n.swift`
|
||||||
|
- 원본: `kr_jp_string_resource.csv` 242행
|
||||||
|
|
||||||
|
### 실행 환경
|
||||||
|
|
||||||
|
```text
|
||||||
|
OS: macOS
|
||||||
|
Build: Xcode command line build
|
||||||
|
Simulator: iPhone 15 Pro, app language ja
|
||||||
|
```
|
||||||
|
|
||||||
|
### 실행한 검증
|
||||||
|
|
||||||
|
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|
||||||
|
|---|---|---|
|
||||||
|
| RED/GREEN mapping validator | 성공 | 변경 전 불일치 64건, 변경 후 0건 |
|
||||||
|
| `SodaLive` Debug build | 성공 | `** BUILD SUCCEEDED **` |
|
||||||
|
| `SodaLive-dev` Debug build | 성공 | `** BUILD SUCCEEDED **` |
|
||||||
|
| 두 스킴 test action 확인 | 불가 | `Scheme ... is not currently configured for the test action.` |
|
||||||
|
| 실제 `I18n.swift` 런타임 드라이버 | 성공 | 탭·interpolation·줄바꿈·구매/대여 문자열 PASS |
|
||||||
|
| 일본어 Simulator 수동 QA | 성공 | 홈 탭과 `新着クリエイター` 표시 확인 |
|
||||||
|
| 독립 diff·CSV 재검증 | 성공 | `ja` literal 64건만 변경, exact 103·분할 2·제외 137 |
|
||||||
|
|
||||||
|
## 5. 발견 사항 요약
|
||||||
|
|
||||||
|
확정 발견 사항 없음.
|
||||||
|
|
||||||
|
## 6. 발견 사항 상세
|
||||||
|
|
||||||
|
해당 없음.
|
||||||
|
|
||||||
|
## 7. 확정 항목의 plan·goal 전환
|
||||||
|
|
||||||
|
전환 항목 없음.
|
||||||
|
|
||||||
|
## 8. 리뷰 종료 판정
|
||||||
|
|
||||||
|
| 판정 항목 | 결과 | 근거 |
|
||||||
|
|---|---|---|
|
||||||
|
| 리뷰 범위 전체 확인 | 충족 | 코드, CSV, PRD, 계획 문서 독립 재검증 |
|
||||||
|
| 후보 항목 판정 완료 | 충족 | 후보 및 확정 발견 사항 없음 |
|
||||||
|
| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 |
|
||||||
|
| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 항목 없음 |
|
||||||
|
| 검증 명령과 결과 기록 | 충족 | 본 문서 §4 및 `plan-task.md` Progress |
|
||||||
|
|
||||||
|
**최종 결론:** 확정 발견 사항 없음
|
||||||
|
|
||||||
|
**남은 항목:** 없음
|
||||||
|
|
||||||
|
## 9. 수정 후 검증 기록
|
||||||
|
|
||||||
|
해당 없음.
|
||||||
Reference in New Issue
Block a user