# 20260331 하드코딩 텍스트 I18n.swift 통일 계획 ## 작업 체크리스트 - [x] 코드베이스 하드코딩 문구 탐색 전략 수립 (병렬 탐색 + 정적 검색) - [x] `I18n.swift`와 `Localizable.xcstrings` 혼재 지점 식별 - [x] 하드코딩 문구 변경 대상 파일 인벤토리 산출 및 문서화 - [ ] 1차 전환: SwiftUI/View 레이어 하드코딩 문구를 `I18n.*` 참조로 교체 - [ ] 2차 전환: ViewModel/Manager/Repository 레이어의 사용자 노출 문구를 `I18n.*`로 이관 - [ ] 3차 정리: `Localizable.xcstrings` 의존 경로 제거 및 회귀 점검 - [ ] 최종 검증: 빌드/테스트/수동 QA 완료 후 체크리스트 마감 ## 커밋 전략 (권장) - **권장 방식: 모듈별 커밋** (일괄 커밋 비권장) - 이유: - 변경량이 커서(현재 대상 330개) 일괄 커밋 시 리뷰/롤백/원인추적 비용이 급증함. - 모듈별 커밋은 회귀가 발생해도 영향 범위를 모듈 단위로 제한할 수 있음. - 충돌 해결 시에도 모듈 경계가 명확해 병합 리스크가 낮아짐. - 권장 순서: 1. 공통 선행 커밋: `I18n.swift` 공통 키/네임스페이스 토대 정리 2. 모듈별 커밋: 각 모듈의 키 추가 + 호출부 교체 + 모듈 단위 검증 3. 마무리 커밋: `Localizable.xcstrings` 의존 제거/정리 및 최종 검증 결과 반영 ## 수용 기준 (Acceptance Criteria) - [ ] 사용자에게 노출되는 하드코딩 문자열이 `SodaLive/Sources/**`에 남아있지 않다. - [ ] 신규/기존 UI 문구 접근이 `I18n.swift` 네임스페이스를 통해 일관되게 이루어진다. - [ ] `Localizable.xcstrings` 기반 직접 참조(`LocalizedStringKey`/`String(localized:)`/`NSLocalizedString`)가 제거되거나 예외 사유가 문서화된다. - [ ] 문자열 치환 후 `SodaLive`, `SodaLive-dev` Debug 빌드가 성공한다. - [ ] 테스트 가능 범위(스킴 제약 감안)와 주요 화면 수동 QA 결과가 문서 하단 검증 기록에 누적된다. ## 탐지 기준 및 범위 - 범위: `SodaLive/Sources/**/*.swift` - 기준: - 문자열 리터럴 중 한글 포함(`"...가-힣..."`) 항목 - SwiftUI 하드코딩 패턴 (`Text("...")`, `Button("...")`, `placeholder: "..."`) - 사용자 노출 가능 메시지 패턴 (`errorMessage = "..."`, `showToast(...)`, `SodaDialog(...)`) - 제외 원칙(구현 단계에서 최종 필터링): 로그/디버그 전용 문자열, URL/키/식별자/이벤트명 등 비노출 텍스트 ## 현황 스냅샷 - `I18n.` 사용 파일: 93개 (`grep: \bI18n\.`) - String Catalog 계열 API 사용 파일: 21개 (`grep: NSLocalizedString|String(localized:)|LocalizedStringKey`) - 하드코딩 후보 파일(자동 탐지 합집합): 360개 - 제외 확정: 비노출 문자열(로그/채널 prefix/API path/아이콘명/색상코드/Preview 샘플) 30개 파일 - 최종 변경 대상: 330개 파일 ## 외부 레퍼런스 기반 전환 원칙 (librarian 반영) - `LocalizedStringResource`를 로컬라이즈 문자열 표현의 기본 타입으로 사용한다. (`I18n.swift` accessor 반환 타입 설계 시 우선 고려) - String Catalog(`.xcstrings`)와 레거시 포맷은 병존 가능하므로, 모듈 단위 점진 이관 전략을 사용한다. - Swift 문자열 자동 추출 설정(`Use Compiler to Extract Swift Strings`)을 점검해 누락 리스크를 줄인다. - 동적 키/런타임 생성 문자열은 자동 추출이 어려우므로, 수동 관리 대상(manual string)으로 별도 추적한다. - 복수형(pluralization), 포맷 토큰(`%d`, `%@`)은 이관 시 깨지기 쉬운 구간이므로 변환 검증 체크포인트를 별도 둔다. - 접근성 문자열(`accessibilityLabel`, `accessibilityHint`)도 i18n 적용 범위에 포함한다. ## 주요 리스크 체크포인트 - [ ] 포맷 토큰/인자 순서 불일치로 인한 문구 깨짐 여부 검증 - [ ] plural 규칙 손실 여부 검증 - [ ] 동적 문자열 누락 여부 검증(수동 관리 목록 대조) - [ ] 접근성 레이블/힌트 미전환 누락 검증 ## I18n 키 네이밍/네임스페이스 전략 (librarian 반영) - 최상위 네임스페이스는 모듈 기준(`I18n.Live`, `I18n.Chat`, `I18n.MyPage`)으로 고정한다. - 2차 네임스페이스는 화면/도메인 기준(`I18n.Live.Room`, `I18n.Chat.Talk`)으로 확장한다. - 3차 네임스페이스는 컴포넌트/다이얼로그 기준(`I18n.Live.Room.Dialog`)으로 분리한다. - 키 이름은 문구 원문이 아닌 역할 중심(`title`, `subtitle`, `confirmButton`, `emptyStateMessage`)으로 작성한다. - 파라미터가 필요한 문구는 프로퍼티가 아닌 함수(`func rankTitle(_ count: Int) -> String`)로 선언한다. - 중복 문구는 도메인 내부 중복 생성 대신 `I18n.Common`으로 승격해 재사용한다. - 대규모 이관 단계에서는 키 rename을 금지하고, 화면 단위 교체 + 화면 단위 QA 순서로 진행한다. ## 변경 대상 파일 요약 - 총 대상 파일 수: 330 - 상위 모듈 수: 21 | 모듈 | 파일 수 | |---|---:| | `Audition` | 13 | | `Chat` | 28 | | `Content` | 78 | | `CustomView` | 3 | | `Dialog` | 6 | | `Explorer` | 40 | | `Follow` | 1 | | `Home` | 9 | | `IAP` | 1 | | `ImagePicker` | 1 | | `Live` | 56 | | `Main` | 3 | | `Message` | 13 | | `MyPage` | 41 | | `Notification` | 2 | | `Onboarding` | 1 | | `Report` | 4 | | `SearchChannel` | 1 | | `Settings` | 15 | | `UI` | 6 | | `User` | 8 | ## 변경 대상 파일 전체 목록 ### Audition (13) - [x] `SodaLive/Sources/Audition/Applicant/ApplyMethodView.swift` - [x] `SodaLive/Sources/Audition/Applicant/AuditionApplicantItemView.swift` - [x] `SodaLive/Sources/Audition/Applicant/AuditionApplicantRecordingView.swift` - [x] `SodaLive/Sources/Audition/Applicant/AuditionApplyView.swift` - [x] `SodaLive/Sources/Audition/AuditionItemView.swift` - [x] `SodaLive/Sources/Audition/AuditionView.swift` - [x] `SodaLive/Sources/Audition/AuditionViewModel.swift` - [x] `SodaLive/Sources/Audition/Detail/AuditionDetailView.swift` - [x] `SodaLive/Sources/Audition/Detail/AuditionDetailViewModel.swift` - [x] `SodaLive/Sources/Audition/Detail/AuditionSoundManager.swift` - [x] `SodaLive/Sources/Audition/Role/AuditionDetailRoleItemView.swift` - [x] `SodaLive/Sources/Audition/Role/AuditionRoleDetailView.swift` - [x] `SodaLive/Sources/Audition/Role/AuditionRoleDetailViewModel.swift` ### Chat (28) - [ ] `SodaLive/Sources/Chat/Character/CharacterItemView.swift` - [ ] `SodaLive/Sources/Chat/Character/CharacterSectionView.swift` - [ ] `SodaLive/Sources/Chat/Character/CharacterView.swift` - [ ] `SodaLive/Sources/Chat/Character/CharacterViewModel.swift` - [ ] `SodaLive/Sources/Chat/Character/Detail/CharacterDetailView.swift` - [ ] `SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryView.swift` - [ ] `SodaLive/Sources/Chat/Character/New/ViewModels/NewCharacterListViewModel.swift` - [ ] `SodaLive/Sources/Chat/Character/New/Views/NewCharacterListView.swift` - [ ] `SodaLive/Sources/Chat/Character/Recent/RecentCharacterItemView.swift` - [ ] `SodaLive/Sources/Chat/Character/Recent/RecentCharacterSectionView.swift` - [ ] `SodaLive/Sources/Chat/ChatTabView.swift` - [ ] `SodaLive/Sources/Chat/Original/Detail/OriginalWorkDetailHeaderView.swift` - [ ] `SodaLive/Sources/Chat/Original/Detail/OriginalWorkDetailView.swift` - [ ] `SodaLive/Sources/Chat/Original/Detail/OriginalWorkDetailViewModel.swift` - [ ] `SodaLive/Sources/Chat/Original/OriginalTabItemView.swift` - [ ] `SodaLive/Sources/Chat/Original/OriginalWorkViewModel.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/ChatRoomView.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/ChatRoomViewModel.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/Message/AiMessageItemView.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/Message/TypingIndicatorItemView.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/Message/UserMessageItemView.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/Quota/ChatQuotaNoticeItemView.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/Settings/ChatBgSelectionView.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/Settings/ChatBgSelectionViewModel.swift` - [ ] `SodaLive/Sources/Chat/Talk/Room/Settings/ChatSettingsView.swift` - [ ] `SodaLive/Sources/Chat/Talk/TalkItemView.swift` - [ ] `SodaLive/Sources/Chat/Talk/TalkView.swift` - [ ] `SodaLive/Sources/Chat/Talk/TalkViewModel.swift` ### Content (78) - [ ] `SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeView.swift` - [ ] `SodaLive/Sources/Content/All/ByTheme/ContentAllByThemeViewModel.swift` - [ ] `SodaLive/Sources/Content/All/ContentAllView.swift` - [ ] `SodaLive/Sources/Content/All/ContentNewAllItemView.swift` - [ ] `SodaLive/Sources/Content/All/ContentNewAllView.swift` - [ ] `SodaLive/Sources/Content/All/ContentRankingAllView.swift` - [ ] `SodaLive/Sources/Content/All/ContentRankingAllViewModel.swift` - [ ] `SodaLive/Sources/Content/Category/ContentListCategoryView.swift` - [ ] `SodaLive/Sources/Content/ContentItemView.swift` - [ ] `SodaLive/Sources/Content/ContentListItemView.swift` - [ ] `SodaLive/Sources/Content/ContentListView.swift` - [ ] `SodaLive/Sources/Content/ContentPlayManager.swift` - [ ] `SodaLive/Sources/Content/ContentRepository.swift` - [ ] `SodaLive/Sources/Content/Create/ContentCreateSelectThemeView.swift` - [ ] `SodaLive/Sources/Content/Create/ContentCreateSelectThemeViewModel.swift` - [ ] `SodaLive/Sources/Content/Create/ContentCreateView.swift` - [ ] `SodaLive/Sources/Content/Create/ContentCreateViewModel.swift` - [ ] `SodaLive/Sources/Content/Create/QuarterTimePickerView.swift` - [ ] `SodaLive/Sources/Content/Create/SelectDatePicker.swift` - [ ] `SodaLive/Sources/Content/Curation/ContentCurationView.swift` - [ ] `SodaLive/Sources/Content/Curation/ContentCurationViewModel.swift` - [ ] `SodaLive/Sources/Content/Detail/AudioContentDeleteDialogView.swift` - [ ] `SodaLive/Sources/Content/Detail/AudioContentReportDialogView.swift` - [ ] `SodaLive/Sources/Content/Detail/Comment/AudioContentCommentItemView.swift` - [ ] `SodaLive/Sources/Content/Detail/Comment/AudioContentCommentListView.swift` - [ ] `SodaLive/Sources/Content/Detail/Comment/AudioContentCommentListViewModel.swift` - [ ] `SodaLive/Sources/Content/Detail/Comment/AudioContentListReplyView.swift` - [ ] `SodaLive/Sources/Content/Detail/Comment/AudioContentListReplyViewModel.swift` - [ ] `SodaLive/Sources/Content/Detail/Comment/ContentDetailCommentView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailInfoLimitedEditionView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailInfoView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailMenuView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailMosaicView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailOtherContentView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailPlayView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailPreviousNextContentButtonView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailPurchaseButton.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentDetailViewModel.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentOrderConfirmDialogView.swift` - [ ] `SodaLive/Sources/Content/Detail/ContentOrderDialogView.swift` - [ ] `SodaLive/Sources/Content/Detail/LiveRoomDonationDialogView.swift` - [ ] `SodaLive/Sources/Content/Main/Banner/ContentMainBannerViewModel.swift` - [ ] `SodaLive/Sources/Content/Main/V2/ContentMainContentThemeView.swift` - [ ] `SodaLive/Sources/Content/Modify/ContentModifyView.swift` - [ ] `SodaLive/Sources/Content/Modify/ContentModifyViewModel.swift` - [ ] `SodaLive/Sources/Content/Player/ContentPlayerView.swift` - [ ] `SodaLive/Sources/Content/Playlist/ContentPlaylistItemView.swift` - [ ] `SodaLive/Sources/Content/Playlist/ContentPlaylistListView.swift` - [ ] `SodaLive/Sources/Content/Playlist/ContentPlaylistListViewModel.swift` - [ ] `SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateView.swift` - [ ] `SodaLive/Sources/Content/Playlist/Create/ContentPlaylistCreateViewModel.swift` - [ ] `SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentItemView.swift` - [ ] `SodaLive/Sources/Content/Playlist/Create/PlaylistAddContentView.swift` - [ ] `SodaLive/Sources/Content/Playlist/Create/PlaylistCreateContentView.swift` - [ ] `SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift` - [ ] `SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift` - [ ] `SodaLive/Sources/Content/Playlist/Detail/PlaylistContentItemView.swift` - [ ] `SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyView.swift` - [ ] `SodaLive/Sources/Content/Playlist/Modify/ContentPlaylistModifyViewModel.swift` - [ ] `SodaLive/Sources/Content/Series/Content/SeriesContentAllView.swift` - [ ] `SodaLive/Sources/Content/Series/Content/SeriesContentAllViewModel.swift` - [ ] `SodaLive/Sources/Content/Series/Content/SeriesContentListItemView.swift` - [ ] `SodaLive/Sources/Content/Series/DayOfWeekSeriesView.swift` - [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailHomeView.swift` - [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailIntroductionView.swift` - [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailView.swift` - [ ] `SodaLive/Sources/Content/Series/Detail/SeriesDetailViewModel.swift` - [ ] `SodaLive/Sources/Content/Series/Main/ByGenre/SeriesMainByGenreViewModel.swift` - [ ] `SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekView.swift` - [ ] `SodaLive/Sources/Content/Series/Main/DayOfWeek/SeriesMainDayOfWeekViewModel.swift` - [ ] `SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeView.swift` - [ ] `SodaLive/Sources/Content/Series/Main/Home/SeriesMainHomeViewModel.swift` - [ ] `SodaLive/Sources/Content/Series/Main/SeriesMainItemView.swift` - [ ] `SodaLive/Sources/Content/Series/Main/SeriesMainView.swift` - [ ] `SodaLive/Sources/Content/Series/SeriesItemView.swift` - [ ] `SodaLive/Sources/Content/Series/SeriesListAllView.swift` - [ ] `SodaLive/Sources/Content/Series/SeriesListAllViewModel.swift` ### CustomView (3) - [ ] `SodaLive/Sources/CustomView/ChatTextFieldView.swift` - [ ] `SodaLive/Sources/CustomView/ExpandableTextView.swift` - [ ] `SodaLive/Sources/CustomView/IconAndTitleToggleButton.swift` ### Dialog (6) - [ ] `SodaLive/Sources/Dialog/ApplyAuditionCompleteDialog.swift` - [ ] `SodaLive/Sources/Dialog/CommunityPostPurchaseDialog.swift` - [ ] `SodaLive/Sources/Dialog/CreatorFollowNotifyDialog.swift` - [ ] `SodaLive/Sources/Dialog/LivePaymentDialog.swift` - [ ] `SodaLive/Sources/Dialog/LiveRoomPasswordDialog.swift` - [ ] `SodaLive/Sources/Dialog/MemberProfileDialog.swift` ### Explorer (40) - [ ] `SodaLive/Sources/Explorer/ExplorerSectionView.swift` - [ ] `SodaLive/Sources/Explorer/ExplorerView.swift` - [ ] `SodaLive/Sources/Explorer/ExplorerViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationAllView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/ChannelDonation/ChannelDonationItemView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllItemLockView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityAllViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/CreatorCommunityMenuView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Player/CreatorCommunityMediaPlayerManager.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityItemView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityMoreItemView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityNoPostsItemView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Modify/CreatorCommunityModifyViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityRecordingVoiceView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunitySoundManager.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/CreatorCommunity/Write/CreatorCommunityWriteViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/FollowerList/FollowerListView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/Series/UserProfileSeriesView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileActivitySummaryView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileContentView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileDonationView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileIntroduceView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileLiveView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileView.swift` - [ ] `SodaLive/Sources/Explorer/Profile/UserProfileViewModel.swift` ### Follow (1) - [ ] `SodaLive/Sources/Follow/FollowCreatorView.swift` ### Home (9) - [ ] `SodaLive/Sources/Home/HomeAuditionView.swift` - [ ] `SodaLive/Sources/Home/HomeCreatorRankingItemView.swift` - [ ] `SodaLive/Sources/Home/HomeLatestContentView.swift` - [ ] `SodaLive/Sources/Home/HomeLiveItemView.swift` - [ ] `SodaLive/Sources/Home/HomeTabView.swift` - [ ] `SodaLive/Sources/Home/HomeWeeklyChartItemView.swift` - [ ] `SodaLive/Sources/Home/HomeWeeklyChartView.swift` - [ ] `SodaLive/Sources/Home/RecommendChannel/RecommendChannelContentItemView.swift` - [ ] `SodaLive/Sources/Home/RecommendChannel/RecommendChannelItemView.swift` ### IAP (1) - [ ] `SodaLive/Sources/IAP/StoreManager.swift` ### ImagePicker (1) - [ ] `SodaLive/Sources/ImagePicker/ImagePicker.swift` ### Live (56) - [ ] `SodaLive/Sources/Live/Cancel/LiveCancelDialog.swift` - [ ] `SodaLive/Sources/Live/LatestFinishedLiveItemView.swift` - [ ] `SodaLive/Sources/Live/LiveReplayListView.swift` - [ ] `SodaLive/Sources/Live/LiveView.swift` - [ ] `SodaLive/Sources/Live/LiveViewModel.swift` - [ ] `SodaLive/Sources/Live/Now/All/LiveNowAllItemView.swift` - [ ] `SodaLive/Sources/Live/Now/All/LiveNowAllView.swift` - [ ] `SodaLive/Sources/Live/Now/LiveNowItemView.swift` - [ ] `SodaLive/Sources/Live/Now/SectionLiveNowView.swift` - [ ] `SodaLive/Sources/Live/RecommendChannel/SectionRecommendChannelView.swift` - [ ] `SodaLive/Sources/Live/Reservation/All/LiveReservationAllItemView.swift` - [ ] `SodaLive/Sources/Live/Reservation/All/LiveReservationAllView.swift` - [ ] `SodaLive/Sources/Live/Reservation/Complete/LiveReservationCompleteView.swift` - [ ] `SodaLive/Sources/Live/Reservation/LiveReservationItemView.swift` - [ ] `SodaLive/Sources/Live/Reservation/MyLiveReservationItemView.swift` - [ ] `SodaLive/Sources/Live/Reservation/SectionLiveReservationView.swift` - [ ] `SodaLive/Sources/Live/Room/Chat/LiveRoomChatItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Chat/LiveRoomDonationChatItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Chat/LiveRoomHeartDonationChatItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Chat/LiveRoomJoinChatItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Chat/LiveRoomRouletteDonationChatItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Create/LiveRoomCreateView.swift` - [ ] `SodaLive/Sources/Live/Room/Create/Tag/LiveRoomCreateTagView.swift` - [ ] `SodaLive/Sources/Live/Room/Create/Tag/LiveRoomCreateTagViewModel.swift` - [ ] `SodaLive/Sources/Live/Room/Detail/LiveDetailView.swift` - [ ] `SodaLive/Sources/Live/Room/Detail/LiveDetailViewModel.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationMessageDialog.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationMessageItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingDialog.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomDonationRankingTotalCanView.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomHeartRankingDialog.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomHeartRankingItemView.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomInfoEditDialog.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomNoChattingDialogView.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileDialog.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomProfileItemTitleView.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomProfilesDialogView.swift` - [ ] `SodaLive/Sources/Live/Room/Dialog/LiveRoomUserProfileDialogView.swift` - [ ] `SodaLive/Sources/Live/Room/Edit/LiveRoomEditView.swift` - [ ] `SodaLive/Sources/Live/Room/Edit/LiveRoomEditViewModel.swift` - [ ] `SodaLive/Sources/Live/Room/LiveRoomViewModel.swift` - [ ] `SodaLive/Sources/Live/Room/Menu/LiveRoomMenuSelectView.swift` - [ ] `SodaLive/Sources/Live/Room/Menu/MenuSettingsView.swift` - [ ] `SodaLive/Sources/Live/Room/Menu/MenuSettingsViewModel.swift` - [ ] `SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsOptionView.swift` - [ ] `SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsView.swift` - [ ] `SodaLive/Sources/Live/Room/Routlette/Config/RouletteSettingsViewModel.swift` - [ ] `SodaLive/Sources/Live/Room/Routlette/RoulettePreviewDialog.swift` - [ ] `SodaLive/Sources/Live/Room/V2/Component/Button/LiveRoomNewChatView.swift` - [ ] `SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoGuestView.swift` - [ ] `SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInfoHostView.swift` - [ ] `SodaLive/Sources/Live/Room/V2/Component/View/LiveRoomInputChatView.swift` - [ ] `SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift` - [ ] `SodaLive/Sources/Live/SectionCommunityPostView.swift` - [ ] `SodaLive/Sources/Live/SectionLatestFinishedLiveView.swift` ### Main (3) - [ ] `SodaLive/Sources/Main/EventPopupDialogView.swift` - [ ] `SodaLive/Sources/Main/Home/BottomTabView.swift` - [ ] `SodaLive/Sources/Main/Home/HomeView.swift` ### Message (13) - [ ] `SodaLive/Sources/Message/MessageFilterTabView.swift` - [ ] `SodaLive/Sources/Message/MessageView.swift` - [ ] `SodaLive/Sources/Message/Text/Detail/TextMessageDetailView.swift` - [ ] `SodaLive/Sources/Message/Text/Detail/TextMessageDetailViewModel.swift` - [ ] `SodaLive/Sources/Message/Text/SelectRecipient/SelectRecipientView.swift` - [ ] `SodaLive/Sources/Message/Text/SelectRecipient/SelectRecipientViewModel.swift` - [ ] `SodaLive/Sources/Message/Text/TextMessageView.swift` - [ ] `SodaLive/Sources/Message/Text/Write/TextMessageWriteView.swift` - [ ] `SodaLive/Sources/Message/Voice/SoundManager.swift` - [ ] `SodaLive/Sources/Message/Voice/VoiceMessageItemView.swift` - [ ] `SodaLive/Sources/Message/Voice/VoiceMessageView.swift` - [ ] `SodaLive/Sources/Message/Voice/VoiceMessageViewModel.swift` - [ ] `SodaLive/Sources/Message/Voice/Write/VoiceMessageWriteView.swift` ### MyPage (41) - [ ] `SodaLive/Sources/MyPage/Auth/AuthButtonView.swift` - [ ] `SodaLive/Sources/MyPage/Block/BlockMemberListView.swift` - [ ] `SodaLive/Sources/MyPage/Block/BlockedMemberListItemView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Charge/CanChargeView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Charge/CanChargeViewModel.swift` - [ ] `SodaLive/Sources/MyPage/Can/Coupon/CanChargeCouponButtonView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Coupon/CanCouponNoticeItemView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Payment/CanPaymentView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Payment/CanPaymentViewModel.swift` - [ ] `SodaLive/Sources/MyPage/Can/Payment/CanPgPaymentView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Payment/CanPgPaymentViewModel.swift` - [ ] `SodaLive/Sources/MyPage/Can/Payment/Temp/CanPaymentTempView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Payment/Temp/CanPaymentTempViewModel.swift` - [ ] `SodaLive/Sources/MyPage/Can/Status/CanStatusView.swift` - [ ] `SodaLive/Sources/MyPage/Can/Status/CanStatusViewModel.swift` - [ ] `SodaLive/Sources/MyPage/Can/Status/CanUseStatusView.swift` - [ ] `SodaLive/Sources/MyPage/CanCardView.swift` - [ ] `SodaLive/Sources/MyPage/MyPageView.swift` - [ ] `SodaLive/Sources/MyPage/MyPageViewModel.swift` - [ ] `SodaLive/Sources/MyPage/OrderList/OrderListAllInnerView.swift` - [ ] `SodaLive/Sources/MyPage/OrderList/OrderListAllView.swift` - [ ] `SodaLive/Sources/MyPage/OrderList/OrderListAllViewModel.swift` - [ ] `SodaLive/Sources/MyPage/OrderList/OrderListItemView.swift` - [ ] `SodaLive/Sources/MyPage/OrderList/OrderListView.swift` - [ ] `SodaLive/Sources/MyPage/Point/PointStatusView.swift` - [ ] `SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift` - [ ] `SodaLive/Sources/MyPage/Point/Use/PointUseStatusView.swift` - [ ] `SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateView.swift` - [ ] `SodaLive/Sources/MyPage/Profile/Nickname/NicknameUpdateViewModel.swift` - [ ] `SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift` - [ ] `SodaLive/Sources/MyPage/Profile/Tag/MemberTagView.swift` - [ ] `SodaLive/Sources/MyPage/Profile/Tag/MemberTagViewModel.swift` - [ ] `SodaLive/Sources/MyPage/ReservationStatus/Cancel/LiveReservationCancelView.swift` - [ ] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusItemView.swift` - [ ] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusView.swift` - [ ] `SodaLive/Sources/MyPage/ReservationStatus/LiveReservationStatusViewModel.swift` - [ ] `SodaLive/Sources/MyPage/ReservationStatusView.swift` - [ ] `SodaLive/Sources/MyPage/ServiceCenter/FaqView.swift` - [ ] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterButtonView.swift` - [ ] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterView.swift` - [ ] `SodaLive/Sources/MyPage/ServiceCenter/ServiceCenterViewModel.swift` ### Notification (2) - [ ] `SodaLive/Sources/Notification/List/PushNotificationListItemView.swift` - [ ] `SodaLive/Sources/Notification/List/PushNotificationListView.swift` ### Onboarding (1) - [ ] `SodaLive/Sources/Onboarding/OnboardingView.swift` ### Report (4) - [ ] `SodaLive/Sources/Report/CheersReportDialogView.swift` - [ ] `SodaLive/Sources/Report/ProfileReportDialogView.swift` - [ ] `SodaLive/Sources/Report/ProfileReportMenuView.swift` - [ ] `SodaLive/Sources/Report/UserReportDialogView.swift` ### SearchChannel (1) - [ ] `SodaLive/Sources/SearchChannel/SearchChannelView.swift` ### Settings (15) - [ ] `SodaLive/Sources/Settings/Content/ContentSettingsView.swift` - [ ] `SodaLive/Sources/Settings/Event/EventDetailView.swift` - [ ] `SodaLive/Sources/Settings/Event/EventListView.swift` - [ ] `SodaLive/Sources/Settings/Event/EventListViewModel.swift` - [ ] `SodaLive/Sources/Settings/Language/Models/LanguageOption.swift` - [ ] `SodaLive/Sources/Settings/Language/Views/LanguageSettingsView.swift` - [ ] `SodaLive/Sources/Settings/Notice/NoticeDetailView.swift` - [ ] `SodaLive/Sources/Settings/Notice/NoticeListView.swift` - [ ] `SodaLive/Sources/Settings/Notice/NoticeListViewModel.swift` - [ ] `SodaLive/Sources/Settings/Notification/NotificationSettingsDialog.swift` - [ ] `SodaLive/Sources/Settings/Notification/NotificationSettingsView.swift` - [ ] `SodaLive/Sources/Settings/Notification/NotificationSettingsViewModel.swift` - [ ] `SodaLive/Sources/Settings/SettingsView.swift` - [ ] `SodaLive/Sources/Settings/SignOut/SignOutView.swift` - [ ] `SodaLive/Sources/Settings/Terms/TermsViewModel.swift` ### UI (6) - [ ] `SodaLive/Sources/UI/Component/SelectedButtonView.swift` - [ ] `SodaLive/Sources/UI/Component/SeriesDetailTabView.swift` - [ ] `SodaLive/Sources/UI/Component/SeriesItemBadgeView.swift` - [ ] `SodaLive/Sources/UI/Component/SeriesKeywordChipView.swift` - [ ] `SodaLive/Sources/UI/Component/SeriesListBigItemView.swift` - [ ] `SodaLive/Sources/UI/Component/SeriesListItemView.swift` ### User (8) - [ ] `SodaLive/Sources/User/FindPassword/FindPasswordView.swift` - [ ] `SodaLive/Sources/User/FindPassword/FindPasswordViewModel.swift` - [ ] `SodaLive/Sources/User/Login/LoginView.swift` - [ ] `SodaLive/Sources/User/Login/LoginViewModel.swift` - [ ] `SodaLive/Sources/User/SignUp/SignUpView.swift` - [ ] `SodaLive/Sources/User/SignUp/SignUpViewModel.swift` - [ ] `SodaLive/Sources/User/UserTextField.swift` - [ ] `SodaLive/Sources/User/UserViewModel.swift` ## 검증 기록 ### 1차 계획 수립 (2026-03-31) - 무엇/왜/어떻게: - 무엇: 하드코딩 문구 전수 후보를 수집하고, `I18n.swift` 단일화 전환 계획 문서를 작성. - 왜: 현재 `Localizable.xcstrings`와 `I18n.swift` 혼재로 인해 유지보수/일관성 비용이 증가하고 있어 단일 경로 정리가 필요. - 어떻게: explore/librarian 병렬 탐색 + grep/ast-grep 직접 검색을 병행해 대상 파일 인벤토리를 생성. - 실행 명령/도구: - `task(subagent_type="explore", ... )` x3 (SwiftUI/UIKIt/혼재지점 탐색) - `task(subagent_type="librarian", ... )` x2 (외부 레퍼런스/네이밍 전략) - `grep("\"[^\"]*[가-힣][^\"]*\"", include=*.swift, path=SodaLive/Sources)` - `grep("\bI18n\.", include=*.swift, path=SodaLive/Sources)` - `grep("NSLocalizedString\(|String\(localized:|LocalizedStringKey\(", include=*.swift, path=SodaLive/Sources)` - `grep("Text\(\"[^\"]+\"\)", include=*.swift, path=SodaLive/Sources)` - `ast_grep_search(pattern="Text(\"$TEXT\")", lang=swift, paths=[SodaLive/Sources])` - 보조 집계 스크립트: `python3` (후보 파일 dedupe/모듈별 집계) - 결과: - 하드코딩 후보 파일 360개 산출 및 모듈별 분류 완료. - `rg` 명령은 환경에 설치되어 있지 않아(`command not found`) grep/ast-grep/파이썬 스캔으로 대체. ### 2차 계획 보강 (외부 i18n 레퍼런스 반영, 2026-03-31) - 무엇/왜/어떻게: - 무엇: iOS i18n 통합 시 주의점(포맷 토큰/복수형/접근성/동적 키)을 계획 문서에 체크포인트로 반영. - 왜: 파일 목록만으로는 전환 품질을 담보하기 어려워, 이행 단계별 검증 기준을 사전에 고정할 필요가 있음. - 어떻게: `librarian` 조사 결과를 기반으로 전환 원칙/리스크 항목을 문서 상단에 추가. - 실행 명령/도구: - `background_output(task_id="bg_535935bc")` - 결과: - 외부 레퍼런스 기반 전환 원칙 6개 및 리스크 체크포인트 4개 반영 완료. - 남은 `librarian` 1개(네이밍 전략)는 완료 알림 수신 후 동일 문서에 추가 반영 예정. ### 3차 계획 보강 (I18n 키 네이밍 전략 반영, 2026-03-31) - 무엇/왜/어떻게: - 무엇: `I18n.swift` 단일화 시 키 충돌/확장 혼선을 줄이기 위한 네이밍/계층 전략을 반영. - 왜: 파일 전수 목록만으로는 장기 유지보수 규칙이 부족해, 이관 이후에도 일관성을 유지할 기준이 필요함. - 어떻게: `librarian` 네이밍 전략 조사 결과를 정리해 문서 상단에 별도 전략 섹션으로 추가. - 실행 명령/도구: - `background_output(task_id="bg_3bd27691")` - 결과: - 모듈/화면/컴포넌트 3단계 네임스페이스와 역할 중심 키 네이밍 기준 반영 완료. - 파라미터 문자열 함수화, 공통 키 승격(`I18n.Common`), 키 rename 금지 원칙 반영 완료. - 중복 계획 문서(`20260331_I18n키네임스페이스구조계획.md`)는 단일 계획 문서 원칙에 맞춰 제거. ### 4차 계획 보강 (Agora 후보 제외 반영, 2026-03-31) - 무엇/왜/어떻게: - 무엇: `SodaLive/Sources/Agora/Agora.swift`를 변경 대상 목록에서 제외. - 왜: 해당 파일의 문자열은 `DEBUG_LOG` 메시지/내부 채널 prefix 등 사용자 노출 텍스트가 아니라 i18n 전환 범위와 맞지 않음. - 어떻게: 변경 대상 섹션에서 `Agora` 모듈과 파일 체크리스트 항목을 제거하고 요약 카운트를 재산정. - 실행 명령/도구: - `grep("\"[^\"]*\"", include=Agora.swift, path=SodaLive/Sources/Agora)` - `grep("Agora|총 대상 파일 수|상위 모듈 수", include=20260331_하드코딩텍스트_I18n통일계획.md, path=docs)` - `python3` (문서 내 체크리스트 파일 수/모듈 수 재계산) - 결과: - 변경 대상 파일 수 `360 → 359`, 상위 모듈 수 `25 → 24`로 갱신. - `Agora.swift` 체크리스트 항목 제거 및 문서 내 제외 사유 명시 완료. ### 5차 계획 보강 (비노출 문자열 전수 제외 반영, 2026-03-31) - 무엇/왜/어떻게: - 무엇: 변경 대상 359개를 재검토해 비노출 문자열만 가진 파일을 계획에서 제외. - 왜: i18n 전환 대상은 사용자 노출 문구여야 하며, 내부 로그/채널명/API 경로/프리뷰 샘플 문자열은 범위 외이기 때문. - 어떻게: 후보 전수 스캔 후 파일별 문자열 컨텍스트를 리뷰해 비노출 29개 파일을 추가 제외(이전 Agora 1개 제외 포함 총 30개 제외). - 실행 명령/도구: - `read(SodaLive/Sources/Common/TextViewWrapper.swift)` - `grep("\"[^\"]*\"", include=Agora.swift, path=SodaLive/Sources/Agora)` - `python3` (후보 파일 컨텍스트 분류/제외 리스트 산출/문서 섹션 재생성) - 결과: - `Common/TextViewWrapper.swift` 제외(실사용 문자열 없음, Preview 샘플만 존재). - 내부 로그/채널 prefix/API path/아이콘명/색상코드/Preview 샘플만 가진 29개 파일 추가 제외 완료. - 변경 대상 파일 수 `359 → 330`, 상위 모듈 수 `24 → 21`으로 갱신. - 예외 유지: `Content/Detail/ContentDetailPurchaseButton.swift`는 `"원으로"/"캔으로"` 사용자 노출 텍스트가 있어 유지. ### 6차 구현 (Audition 모듈 13개 i18n 전환, 2026-03-31) - 무엇/왜/어떻게: - 무엇: 변경 대상 목록의 `Audition` 모듈 13개 파일을 전수 처리해 사용자 노출 하드코딩 문구를 `I18n.*` 참조로 교체. - 왜: Audition 영역의 UI/토스트/다이얼로그/오류 메시지가 하드코딩 상태여서 다국어 일관성이 깨지고 유지보수 비용이 높았기 때문. - 어떻게: explore/librarian/Oracle 병렬 분석 + `grep`/`ast_grep_search` 직접 검증으로 누락 지점을 수집한 뒤, `I18n.swift`에 `I18n.Audition` 네임스페이스를 추가하고 호출부를 모듈 단위로 일괄 치환. - 실행 명령/도구: - `task(subagent_type="explore", ...)` x2 (`bg_f58a087c`, `bg_02b03b28`) - `task(subagent_type="librarian", ...)` x2 (`bg_f62866ac`, `bg_5cd8656b`) - `task(subagent_type="oracle", ...)` x1 (`bg_81c2c04e`) - `grep("\"[^\"]*[가-힣][^\"]*\"", include=*.swift, path=SodaLive/Sources/Audition)` - `ast_grep_search(pattern="Text(\"$TEXT\")", lang=swift, paths=[SodaLive/Sources/Audition])` - `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` - 결과: - Audition 호출부 치환 완료 파일: `ApplyMethodView`, `AuditionApplicantRecordingView`, `AuditionApplyView`, `AuditionView`, `AuditionViewModel`, `AuditionDetailView`, `AuditionDetailViewModel`, `AuditionSoundManager`, `AuditionDetailRoleItemView`, `AuditionRoleDetailView`, `AuditionRoleDetailViewModel`. - `I18n.swift`에 `I18n.Audition`(List/ApplyMethod/Apply/Recording/Detail/Vote/Sound) 키셋 추가 및 공통 오류는 `I18n.Common.commonError`로 통합. - 녹음 자동 파일명(`voiceon_now_voice_*`)이 사용자에게 그대로 보이던 문제를 `displayFileName` 처리(`I18n.Audition.Apply.recordedVoiceFileName`)로 보정. - Audition 모듈 하드코딩 한글 재검증 결과, 남은 문자열은 Preview 샘플/DEBUG_LOG/서버 메시지 분기 비교(비노출 로직)만 존재. - 빌드 검증: `SodaLive`, `SodaLive-dev` Debug 빌드 모두 성공(`** BUILD SUCCEEDED **`). - 테스트 검증: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 테스트 액션 미구성 확인(코드 실패 아님, 스킴 제약).