From 19251ed52ffa24186a3cf5f82dc3d4858228948b Mon Sep 17 00:00:00 2001 From: cheykrym Date: Tue, 7 Oct 2025 02:42:44 +0300 Subject: [PATCH] translate to english --- yobble/Network/AuthService.swift | 70 +- yobble/Resources/Localizable.xcstrings | 743 ++++++++++++++++-- .../Tab/Settings/ChangePasswordView.swift | 2 +- yobble/Views/Tab/Settings/SettingsView.swift | 4 +- 4 files changed, 736 insertions(+), 83 deletions(-) diff --git a/yobble/Network/AuthService.swift b/yobble/Network/AuthService.swift index 8054deb..47aeab3 100644 --- a/yobble/Network/AuthService.swift +++ b/yobble/Network/AuthService.swift @@ -151,7 +151,7 @@ final class AuthService { return } - completion(true, apiResponse.data.message) + completion(true, NSLocalizedString(apiResponse.data.message, comment: "")) } catch { completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: "")) } @@ -280,20 +280,28 @@ final class AuthService { case .network(let err): return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription) case .server(let statusCode, let data): - if let message = extractMessage(from: data) { - return message - } + let message = extractMessage(from: data) switch statusCode { case 401: return NSLocalizedString("Необходимо авторизоваться заново.", comment: "") case 403: + if let message, + Self.changePasswordForbiddenMessages.contains(message) { + return NSLocalizedString(message, comment: "") + } return NSLocalizedString("Старый пароль указан неверно или совпадает с новым.", comment: "") case 422: + if let message { + return message + } return NSLocalizedString("Проверьте данные и повторите попытку.", comment: "") case 429: return NSLocalizedString("Слишком много попыток. Попробуйте позже.", comment: "") default: + if let message { + return message + } return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)") } case .unauthorized: @@ -305,7 +313,8 @@ final class AuthService { private func extractMessage(from data: Data?) -> String? { guard let data else { return nil } - if let response = try? JSONDecoder().decode(ErrorResponse.self, from: data) { + let decoder = JSONDecoder() + if let response = try? decoder.decode(ErrorResponse.self, from: data) { if let message = response.data?.message, !message.isEmpty { return message } @@ -313,10 +322,61 @@ final class AuthService { return detail } } + + if let jsonObject = try? JSONSerialization.jsonObject(with: data) { + if let dictionary = jsonObject as? [String: Any] { + if let detail = Self.normalizedMessage(dictionary["detail"] as? String) { + return detail + } + if let dataDict = dictionary["data"] as? [String: Any], + let message = Self.normalizedMessage(dataDict["message"] as? String) { + return message + } + if let errors = dictionary["errors"] as? [[String: Any]], + let firstMessage = errors.compactMap({ Self.normalizedMessage($0["message"] as? String) }).first { + return firstMessage + } + } else if let array = jsonObject as? [[String: Any]] { + if let firstMessage = array.compactMap({ item -> String? in + if let detail = Self.normalizedMessage(item["detail"] as? String) { + return detail + } + if let message = Self.normalizedMessage(item["message"] as? String) { + return message + } + if let msg = Self.normalizedMessage(item["msg"] as? String) { + return msg + } + return nil + }).first { + return firstMessage + } + } + } + + if let string = Self.normalizedMessage(String(data: data, encoding: .utf8)) { + return string + } + return nil } } +private extension AuthService { + static let changePasswordForbiddenMessages: Set = [ + "Неверный текущий пароль", + "Пароль должен отличаться от старого", + "Пароль не удовлетворяет требованиям" + ] + + static func normalizedMessage(_ raw: String?) -> String? { + guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { + return nil + } + return raw + } +} + private struct LoginRequest: Encodable { let login: String let password: String diff --git a/yobble/Resources/Localizable.xcstrings b/yobble/Resources/Localizable.xcstrings index 7a37d2a..19247ec 100644 --- a/yobble/Resources/Localizable.xcstrings +++ b/yobble/Resources/Localizable.xcstrings @@ -80,6 +80,24 @@ } } }, + "Password updated" : { + "comment" : "ответ сервера (change password)", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password updated" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Пароль обновлен" + } + } + } + }, "profile_down_text_1" : { }, @@ -90,7 +108,14 @@ }, "Push-уведомления" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Push-notifications" + } + } + } }, "Yobble" : { "localizations" : { @@ -99,11 +124,24 @@ "state" : "translated", "value" : "Yobble" } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Йоббле" + } } } }, "Активные сессии" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Active sessions" + } + } + } }, "Без звука (скоро)" : { @@ -128,13 +166,27 @@ } }, "Вы" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You" + } + } + } }, "Вы предложили: %@" : { }, "Выйти из аккаунта" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Log out of your account" + } + } + } }, "Где найти сохранённые черновики?" : { "comment" : "FAQ question: drafts" @@ -143,7 +195,14 @@ }, "Двухфакторная аутентификация" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Two-factor authentication" + } + } + } }, "Добавить друзей" : { "comment" : "Add friends", @@ -157,7 +216,14 @@ } }, "Другое" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Other" + } + } + } }, "Заглушка: Push-уведомления" : { @@ -178,7 +244,14 @@ }, "Загружаем чаты…" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading chats…" + } + } + } }, "Загрузка..." : { "localizations" : { @@ -227,16 +300,37 @@ } }, "Здесь не будут чаты" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There will be no chats here." + } + } + } }, "Идеи" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ideas" + } + } + } }, "Избранные сообщения" : { }, "Изменение пароля" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Change password" + } + } + } }, "Инвайт-код (необязательно)" : { "comment" : "Инвайт-код", @@ -320,7 +414,14 @@ } }, "Логин уже занят." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The login is already taken." + } + } + } }, "Мини-приложения" : { "comment" : "Applets", @@ -376,46 +477,157 @@ } }, "Не удалось загрузить список чатов." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to load chat list." + } + } + } }, "Не удалось загрузить чаты." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to load chats." + } + } + } }, "Не удалось обновить пароль." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to update password." + } + } + } }, "Не удалось обработать данные чатов." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to process chat data." + } + } + } }, "Не удалось обработать ответ сервера." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to process server response." + } + } + } }, "Не удалось сериализовать данные запроса." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Failed to serialize request data." + } + } + } }, "Неверный запрос (400)." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid request (400)." + } + } + } }, "Неверный код приглашения." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid invitation code." + } + } + } }, "Неверный логин" : { - "comment" : "Неверный логин" + "comment" : "Неверный логин", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid login" + } + } + } }, "Неверный логин или пароль." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Incorrect login or password." + } + } + } }, "Неверный пароль" : { - "comment" : "Неверный пароль" + "comment" : "Неверный пароль", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Incorrect password" + } + } + } + }, + "Неверный текущий пароль" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The current password is incorrect" + } + } + } }, "Неизвестная ошибка" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown error" + } + } + } }, "Неизвестная ошибка." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown error." + } + } + } }, "Неизвестная ошибка. Попробуйте позже." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown error. Please try again later." + } + } + } }, "Неизвестный" : { @@ -430,19 +642,56 @@ }, "Нет аккаунта? Регистрация" : { - "comment" : "Регистрация" + "comment" : "Регистрация", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Don't have an account? Register" + } + } + } }, "Нет сообщений" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No messages" + } + } + } }, "Новый пароль" : { - "comment" : "Новый пароль" + "comment" : "Новый пароль", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New Password" + } + } + } }, "О приложении" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "About the app" + } + } + } }, "Обновить" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Refresh" + } + } + } }, "Обратная связь" : { @@ -457,61 +706,197 @@ }, "Ошибка" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error" + } + } + } }, "Ошибка авторизации" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Authorization error" + } + } + } }, "Ошибка при деавторизации." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error while deauthorizing." + } + } + } }, "Ошибка регистрация" : { - "comment" : "Ошибка" + "comment" : "Ошибка", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registration error" + } + } + } }, "Ошибка сервера (%@)." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server error (%@)." + } + } + } }, "Ошибка сервера: %@" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server error: %@" + } + } + } }, "Ошибка сети: %@" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Network error: %@" + } + } + } }, "Ошибка соединения с сервером." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Server connection error." + } + } + } }, "Пароли не совпадают" : { - "comment" : "Пароли не совпадают" + "comment" : "Пароли не совпадают", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The passwords do not match" + } + } + } }, "Пароли не совпадают." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The passwords do not match." + } + } + } }, "Пароль" : { - "comment" : "Пароль" + "comment" : "Пароль", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password" + } + } + } }, "Пароль должен быть от 8 до 128 символов" : { - "comment" : "Пароль должен быть от 6 до 32 символов" + "comment" : "Пароль должен быть от 6 до 32 символов", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The password must be between 8 and 128 characters" + } + } + } }, - "Пароль обновлен" : { - + "Пароль должен отличаться от старого" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The password must be different from the old one" + } + } + } + }, + "Пароль не удовлетворяет требованиям" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The password does not meet the requirements" + } + } + } }, "Пароль успешно обновлен." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password updated successfully." + } + } + } }, "Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : { "comment" : "FAQ answer: reset password" }, "Повторить" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Repeat" + } + } + } }, "Поддержка" : { }, "Подтверждение пароля" : { - "comment" : "Подтверждение пароля" + "comment" : "Подтверждение пароля", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm password" + } + } + } }, "Пока что у вас нет чатов" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You don't have any chats yet." + } + } + } }, "Помощь" : { "comment" : "Help Center", @@ -525,52 +910,151 @@ } }, "Приглашение достигло лимита использования." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The invitation has reached its usage limit." + } + } + } }, "Приглашение истекло." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The invitation has expired." + } + } + } }, "Приглашение не активно." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The invitation is not active." + } + } + } }, "Приложение" : { }, "Применить" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apply" + } + } + } }, "Проверьте данные и повторите попытку." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please check your data and try again." + } + } + } }, "Произошла ошибка." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "An error occurred." + } + } + } }, "Публичная информация" : { }, "Регистрация" : { - "comment" : "Регистрация" + "comment" : "Регистрация", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registration" + } + } + } }, "Регистрация временно недоступна." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registration is temporarily unavailable." + } + } + } }, "Регистрация выполнена, но вход не удался." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registration completed, but login failed." + } + } + } }, "Регистрация запрещена." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registration is prohibited." + } + } + } }, "Регистрация и вход выполнены успешно." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Registration and login completed successfully." + } + } + } }, "Редактировать профиль" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Edit profile" + } + } + } }, "Сервер не отвечает. Попробуйте позже." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The server is not responding. Try again later." + } + } + } }, "Сессия истекла. Войдите снова." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Session expired. Please log in again." + } + } + } }, "Скан" : { "comment" : "Scan", @@ -584,13 +1068,40 @@ } }, "Слишком много запросов." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Too many requests." + } + } + } }, "Слишком много попыток. Попробуйте позже." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "You want too much. Take a rest." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Слишком много хочешь. Отдохни." + } + } + } }, "Сменить пароль" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Change password" + } + } + } }, "Сообщение" : { @@ -599,22 +1110,75 @@ }, "Старый пароль" : { - "comment" : "Старый пароль" + "comment" : "Старый пароль", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Old Password" + } + } + } }, "Старый пароль указан неверно или совпадает с новым." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The old password is incorrect or the same as the new one." + } + } + } }, "Темы" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Themes" + } + } + } }, "Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого." : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Are you fucking nuts? It's okay that the new password has to be different from the old one." + } + } + } }, "Уведомления" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Notifications" + } + } + } }, "Удалить чат (скоро)" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete chat (cuming soon)" + } + } + } + }, + "Ура!" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fine!" + } + } + } }, "Центр авторов" : { "comment" : "Creator Center", @@ -628,13 +1192,35 @@ } }, "Частые вопросы" : { - "comment" : "FAQ navigation title" + "comment" : "FAQ navigation title", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "FAQ" + } + } + } }, "Чат" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat" + } + } + } }, "Чаты" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chats" + } + } + } }, "Черновики" : { "comment" : "Drafts", @@ -654,7 +1240,14 @@ }, "Язык" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Language" + } + } + } } }, "version" : "1.0" diff --git a/yobble/Views/Tab/Settings/ChangePasswordView.swift b/yobble/Views/Tab/Settings/ChangePasswordView.swift index 9e0a4e5..826e082 100644 --- a/yobble/Views/Tab/Settings/ChangePasswordView.swift +++ b/yobble/Views/Tab/Settings/ChangePasswordView.swift @@ -176,7 +176,7 @@ struct ChangePasswordView: View { .alert(item: $alertData) { data in Alert( title: Text(data.kind == .success - ? NSLocalizedString("Пароль обновлен", comment: "") + ? NSLocalizedString("Ура!", comment: "") : NSLocalizedString("Ошибка", comment: "")), message: Text(data.message), dismissButton: .default(Text(NSLocalizedString("OK", comment: ""))) { diff --git a/yobble/Views/Tab/Settings/SettingsView.swift b/yobble/Views/Tab/Settings/SettingsView.swift index 70b4ff4..992fd57 100644 --- a/yobble/Views/Tab/Settings/SettingsView.swift +++ b/yobble/Views/Tab/Settings/SettingsView.swift @@ -21,9 +21,9 @@ struct SettingsView: View { // } // MARK: - Безопасность - Section(header: Text("Безопасность")) { + Section(header: Text(NSLocalizedString("Безопасность", comment: ""))) { NavigationLink(destination: ChangePasswordView()) { - Label("Сменить пароль", systemImage: "key") + Label(NSLocalizedString("Сменить пароль", comment: ""), systemImage: "key") } NavigationLink(destination: Text("Заглушка: Двухфакторная аутентификация")) { Label("Двухфакторная аутентификация", systemImage: "lock.shield")