import SwiftUI private let WEATHER_STATUS_MAP_RU: [String: String] = [ "Rain": "дождливо", "Clouds": "облачно", "Clear": "солнечно", "Thunderstorm": "гроза", "Snow": "снег", "Drizzle": "мелкий дождь", "Fog": "туман" ] private let WEATHER_STATUS_MAP_ZH: [String: String] = [ "Rain": "下雨", "Clouds": "多云", "Clear": "晴朗", "Thunderstorm": "雷雨", "Snow": "下雪", "Drizzle": "毛毛雨", "Fog": "雾" ] private let WEATHER_STATUS_MAP_EN: [String: String] = [ "Rain": "Rain", "Clouds": "Cloudy", "Clear": "Sunny", "Thunderstorm": "Thunderstorm", "Snow": "Snow", "Drizzle": "Drizzle", "Fog": "Fog" ] struct FormattedWeather { let temperature: Int let statusCode: String let precipitation: Int? let windSpeed: Double? var dayOfWeek: String? let originalDate: Date? // добавлено для пересчета дня недели func localizedStatus(language: String) -> String { switch language { case "ru": return WEATHER_STATUS_MAP_RU[statusCode] ?? statusCode case "zh": return WEATHER_STATUS_MAP_ZH[statusCode] ?? statusCode default: return WEATHER_STATUS_MAP_EN[statusCode] ?? statusCode } } } struct WeatherView: View { @EnvironmentObject var appState: AppState @State private var todayWeather: FormattedWeather? @State private var forecast: [FormattedWeather] = [] var body: some View { HStack(alignment: .center, spacing: 4) { if let today = todayWeather { VStack(alignment: .center, spacing: 2) { Image(getWeatherIconName(for: today.localizedStatus(language: appState.selectedLanguage))) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 36, height: 36) .padding(.bottom, 5) Text("\(today.temperature)°") .font(.system(size: 30, weight: .bold)) .foregroundColor(.white) Text(today.localizedStatus(language: appState.selectedLanguage)) .font(.system(size: 12)) .foregroundColor(.white) .multilineTextAlignment(.center) } VStack(alignment: .leading, spacing: 6) { ForEach(forecast.prefix(3).indices, id: \.self) { index in let day = forecast[index] HStack(spacing: 4) { Image(getWeatherIconName(for: day.localizedStatus(language: appState.selectedLanguage))) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 14, height: 14) if let date = day.originalDate { Text(getDayOfWeek(from: date)) .font(.system(size: 10)) .foregroundColor(.white) } Text("\(day.temperature)°") .font(.system(size: 12)) .foregroundColor(.white) .fontWeight(.bold) } } Divider() .background(Color.white.opacity(0.8)) .padding(.vertical, 1) if let precipitation = today.precipitation { HStack(spacing: 4) { Image("det_humidity") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 14, height: 14) Text("\(precipitation)%") .font(.system(size: 12)) .foregroundColor(.white) } } if let windSpeed = today.windSpeed { HStack(spacing: 5) { Image("det_wind_speed") .resizable() .aspectRatio(contentMode: .fit) .frame(width: 14, height: 14) Text(appState.selectedLanguage == "ru" ? "\(Int(windSpeed)) м/с" : appState.selectedLanguage == "zh" ? "\(Int(windSpeed)) 米/秒" : "\(Int(windSpeed)) m/s") .font(.system(size: 12)) .foregroundColor(.white) } } } } else { Text(appState.selectedLanguage == "ru" ? "Загрузка погоды..." : appState.selectedLanguage == "zh" ? "正在加载天气..." : "Loading weather...") .foregroundColor(.white) .padding() } } .padding(.horizontal, 10) .frame(maxWidth: .infinity, maxHeight: .infinity) .aspectRatio(1, contentMode: .fit) .background( ZStack { Color(hex: 0x806C59) LinearGradient( stops: [ .init(color: .white.opacity(0.0), location: 0.0871), .init(color: .white.opacity(0.16), location: 0.6969) ], startPoint: .bottomLeading, endPoint: .topTrailing ) } ) .cornerRadius(25) .shadow(color: Color.black.opacity(0.10), radius: 8, x: 0, y: 2) .task { await fetchAndFormatWeather() } .onChange(of: appState.selectedLanguage) { _ in // при смене языка день недели пересчитывается динамически в View } } private func fetchAndFormatWeather() async { let lat = 59.938784 let lng = 30.314997 guard let url = URL(string: "https://white-nights.krbl.ru/services/weather?lang=\(appState.selectedLanguage)") else { return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let parameters: [String: Any] = [ "coordinates": ["latitude": lat, "longitude": lng] ] do { request.httpBody = try JSONSerialization.data(withJSONObject: parameters) let (data, _) = try await URLSession.shared.data(for: request) let weatherResponse = try JSONDecoder().decode(WeatherResponse.self, from: data) formatWeatherData(data: weatherResponse) } catch { print("Ошибка загрузки погоды: \(error)") } } private func formatWeatherData(data: WeatherResponse) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) dateFormatter.locale = Locale(identifier: "en_US_POSIX") var calendar = Calendar.current calendar.timeZone = TimeZone(secondsFromGMT: 0)! let middayForecast = data.forecast.filter { item in guard let date = dateFormatter.date(from: item.date) else { return false } return calendar.component(.hour, from: date) == 12 } if let today = data.currentWeather { todayWeather = FormattedWeather( temperature: Int(today.temperatureCelsius.rounded()), statusCode: today.description, precipitation: today.humidity, windSpeed: today.windSpeed, dayOfWeek: nil, originalDate: nil ) } var formattedForecast: [FormattedWeather] = [] for item in middayForecast.prefix(3) { guard let date = dateFormatter.date(from: item.date) else { continue } let averageTemp = (item.minTemperatureCelsius + item.maxTemperatureCelsius) / 2 formattedForecast.append(FormattedWeather( temperature: Int(averageTemp.rounded()), statusCode: item.description, precipitation: item.humidity, windSpeed: item.windSpeed, dayOfWeek: getDayOfWeek(from: date), originalDate: date )) } let daysToFill = 3 - formattedForecast.count if daysToFill > 0 { let baseDate = Date() for i in 1...daysToFill { guard let nextDay = Calendar.current.date(byAdding: .day, value: formattedForecast.count + i, to: baseDate) else { continue } formattedForecast.append(FormattedWeather( temperature: 0, statusCode: "N/A", precipitation: nil, windSpeed: nil, dayOfWeek: getDayOfWeek(from: nextDay), originalDate: nextDay )) } } self.forecast = formattedForecast } private func getDayOfWeek(from date: Date) -> String { let formatter = DateFormatter() formatter.locale = appState.selectedLanguage == "ru" ? Locale(identifier: "ru_RU") : appState.selectedLanguage == "zh" ? Locale(identifier: "zh_CN") : Locale(identifier: "en_US") formatter.dateFormat = "E" return formatter.string(from: date).capitalized } private func getWeatherIconName(for status: String) -> String { let normalizedStatus = status.lowercased() switch normalizedStatus { case "солнечно", "晴朗", "sunny": return "cond_sunny" case "облачно", "多云", "cloudy": return "cond_cloudy" case "дождливо", "мелкий дождь", "下雨", "毛毛雨", "rainy", "drizzle": return "cond_rainy" case "снег", "下雪", "snowy": return "cond_snowy" case "гроза", "雷雨", "thunderstorm": return "cond_thunder" case "туман", "雾", "fog": return "det_humidity" default: return "cond_sunny" } } }