// // LanguageService.swift // SodaLive // // Created by Junie (AI) on 2025/12/16. // import Foundation enum LanguageHeaderProvider { private static let defaultsKey = "app.language" // 현재 헤더 코드(ko|en|ja). 앱 시작 시 initialize()로 동기 설정. static var current: String = { // 초기값은 OS 선호 기반으로 계산 return mapPreferredToSupported(Locale.preferredLanguages.first) }() /// 앱 시작 직후 동기 초기화: 저장된 설정이 있으면 그 값을, 없으면 OS 선호를 사용. static func initialize() { if let raw = UserDefaults.standard.string(forKey: defaultsKey), let option = LanguageOption(rawValue: raw), option != .system { // 명시 선택 값 current = option.rawValue return } // 시스템 기본 또는 저장값 없음 → OS 선호 매핑 current = mapPreferredToSupported(Locale.preferredLanguages.first) } private static func mapPreferredToSupported(_ tag: String?) -> String { guard let tag = tag else { return "en" } if tag.hasPrefix("ko") { return "ko" } if tag.hasPrefix("ja") { return "ja" } return "en" } } actor LanguageService { private let repository: LanguageRepository private let environment: LanguageEnvironment init(repository: LanguageRepository, environment: LanguageEnvironment) { self.repository = repository self.environment = environment } func bootstrap() async { let saved = (try? await repository.load()) ?? .system await applyLanguage(saved) } func applyLanguage(_ option: LanguageOption) async { do { try await repository.save(option) } catch { DEBUG_LOG("[Language] save failed: \(error)") } let locale = resolveLocale(option) let header = resolveHeader(option, locale: locale) LanguageHeaderProvider.current = header await MainActor.run { [weak environment] in environment?.locale = locale } DEBUG_LOG("[Language] changed to option=\(option.rawValue) locale=\(locale.identifier) header=\(header)") } func currentLocale() async -> Locale { await MainActor.run { environment.locale } } func currentLanguageCodeForHeader() async -> String { LanguageHeaderProvider.current } // MARK: - Private private func resolveLocale(_ option: LanguageOption) -> Locale { switch option { case .system: guard let first = Locale.preferredLanguages.first else { return Locale(identifier: "en_US") } return mapToSupported(first) case .ko: return Locale(identifier: "ko_KR") case .en: return Locale(identifier: "en_US") case .ja: return Locale(identifier: "ja_JP") } } private func resolveHeader(_ option: LanguageOption, locale: Locale) -> String { switch option { case .system: let id = locale.identifier if id.hasPrefix("ko") { return "ko" } if id.hasPrefix("ja") { return "ja" } return "en" case .ko: return "ko" case .en: return "en" case .ja: return "ja" } } private func mapToSupported(_ tag: String) -> Locale { if tag.hasPrefix("ko") { return .init(identifier: "ko_KR") } if tag.hasPrefix("ja") { return .init(identifier: "ja_JP") } return .init(identifier: "en_US") } } // Composition root for language feature enum LanguageContainer { static let environment = LanguageEnvironment() static let repository: LanguageRepository = UserDefaultsLanguageRepository() static let service = LanguageService(repository: repository, environment: environment) }