featL add localization for various UI strings and error messages
This commit is contained in:
@@ -33,6 +33,8 @@
|
|||||||
AA000001000024 /* SessionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000024 /* SessionsView.swift */; };
|
AA000001000024 /* SessionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000024 /* SessionsView.swift */; };
|
||||||
AA000001000025 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000025 /* Assets.xcassets */; };
|
AA000001000025 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000025 /* Assets.xcassets */; };
|
||||||
AA000001000026 /* PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000026A /* PreviewData.swift */; };
|
AA000001000026 /* PreviewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000026A /* PreviewData.swift */; };
|
||||||
|
AA000001000027 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000027 /* Localizable.xcstrings */; };
|
||||||
|
AA000001000028 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = AA000002000028 /* InfoPlist.xcstrings */; };
|
||||||
AA000001000030 /* MaydayLiveActivityBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000030 /* MaydayLiveActivityBundle.swift */; };
|
AA000001000030 /* MaydayLiveActivityBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000030 /* MaydayLiveActivityBundle.swift */; };
|
||||||
AA000001000031 /* MaydayLiveActivityLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000031 /* MaydayLiveActivityLiveActivity.swift */; };
|
AA000001000031 /* MaydayLiveActivityLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000031 /* MaydayLiveActivityLiveActivity.swift */; };
|
||||||
AA000001000032 /* AlertAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000008 /* AlertAttributes.swift */; };
|
AA000001000032 /* AlertAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000002000008 /* AlertAttributes.swift */; };
|
||||||
@@ -91,6 +93,8 @@
|
|||||||
AA000002000025 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
AA000002000025 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
AA000002000026 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
AA000002000026 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
AA000002000026A /* PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewData.swift; sourceTree = "<group>"; };
|
AA000002000026A /* PreviewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewData.swift; sourceTree = "<group>"; };
|
||||||
|
AA000002000027 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
|
||||||
|
AA000002000028 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
|
||||||
AA000002000030 /* MaydayLiveActivityBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityBundle.swift; sourceTree = "<group>"; };
|
AA000002000030 /* MaydayLiveActivityBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityBundle.swift; sourceTree = "<group>"; };
|
||||||
AA000002000031 /* MaydayLiveActivityLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityLiveActivity.swift; sourceTree = "<group>"; };
|
AA000002000031 /* MaydayLiveActivityLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaydayLiveActivityLiveActivity.swift; sourceTree = "<group>"; };
|
||||||
AA000002000033 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
AA000002000033 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@@ -133,6 +137,8 @@
|
|||||||
AA000002000003 /* AppDelegate.swift */,
|
AA000002000003 /* AppDelegate.swift */,
|
||||||
AA000002000025 /* Assets.xcassets */,
|
AA000002000025 /* Assets.xcassets */,
|
||||||
AA000002000026 /* Info.plist */,
|
AA000002000026 /* Info.plist */,
|
||||||
|
AA000002000027 /* Localizable.xcstrings */,
|
||||||
|
AA000002000028 /* InfoPlist.xcstrings */,
|
||||||
AA000011000003 /* Models */,
|
AA000011000003 /* Models */,
|
||||||
AA000011000004 /* Services */,
|
AA000011000004 /* Services */,
|
||||||
AA000011000005 /* ViewModels */,
|
AA000011000005 /* ViewModels */,
|
||||||
@@ -317,6 +323,8 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
AA000001000025 /* Assets.xcassets in Resources */,
|
AA000001000025 /* Assets.xcassets in Resources */,
|
||||||
|
AA000001000027 /* Localizable.xcstrings in Resources */,
|
||||||
|
AA000001000028 /* InfoPlist.xcstrings in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>NSUserNotificationsUsageDescription</key>
|
|
||||||
<string>Mayday использует уведомления для оповещения о критических событиях.</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"sourceLanguage" : "ru",
|
||||||
|
"strings" : {
|
||||||
|
"NSUserNotificationsUsageDescription" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Mayday использует уведомления для оповещения о критических событиях."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Mayday uses notifications to alert you about critical events."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version" : "1.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,982 @@
|
|||||||
|
{
|
||||||
|
"sourceLanguage" : "ru",
|
||||||
|
"strings" : {
|
||||||
|
"login_subtitle" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Мониторинг и уведомления"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Monitoring and notifications"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"password" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"login_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Войти"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Sign in"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"login_no_account" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Нет аккаунта? Зарегистрироваться"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "No account? Sign up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"demo_mode" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Демо-режим"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Demo mode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Регистрация"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Registration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"confirm_password" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Подтвердите пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Confirm password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"password_min_length" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Пароль должен содержать не менее 8 символов"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Password must be at least 8 characters"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"passwords_mismatch" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Пароли не совпадают"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Passwords don't match"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Создать аккаунт"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Create account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"register_has_account" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Уже есть аккаунт?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Already have an account?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verify_email_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Подтвердите email"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Verify email"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verify_code_sent_to" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Код отправлен на"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Code sent to"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verify_resend_cooldown %lld" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Отправить повторно (%lld сек)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resend (%lld sec)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verify_resend" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Отправить повторно"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resend"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verify_nav_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Подтверждение"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Verification"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loading_error" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Ошибка загрузки"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Loading error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no_notifications" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Нет уведомлений"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "No notifications"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no_notifications_description" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Новые уведомления появятся здесь"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "New notifications will appear here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Уведомления"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Notifications"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"demo_badge" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "ДЕМО"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "DEMO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications_active" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Активные"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Active"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications_completed" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Завершённые"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Completed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"open_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Открыть"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Open"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mark_as_read" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Отметить прочитанным"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Mark as read"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"details_section" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Подробности"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Details"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"info_section" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Информация"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status_section" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Статус"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Status"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status_read" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Прочитано"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Read"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status_new" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Новое"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "New"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channel_in_app" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "В приложении"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "In-app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"channel_label" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Канал"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Channel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"received_label" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Получено"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Received"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"read_at_label" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Прочитано"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Read"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification_read_at %@" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "прочитано %@"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "read %@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"account_section" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Аккаунт"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"change_password" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Сменить пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Change password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"push_notifications" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Push-уведомления"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Push notifications"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active_sessions" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Активные сессии"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Active sessions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logout_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Выйти из аккаунта"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Sign out"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logout_all_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Выйти на всех устройствах"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Sign out on all devices"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Настройки"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"done_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Готово"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Done"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logout_all_confirm" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Выйти на всех устройствах?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Sign out on all devices?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logout_all_action" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Выйти везде"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Sign out everywhere"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cancel" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Отмена"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Cancel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Ошибка"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"current_password" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Текущий пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Current password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"new_password" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Новый пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "New password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"confirm_new_password" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Подтвердите новый пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Confirm new password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"save_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Сохранить"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Save"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"change_password_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Сменить пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Change password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"current_session" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Текущая"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Current"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"session_created %@" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Создана: %@"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Created: %@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete_button" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Удалить"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Delete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active_sessions_title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Активные сессии"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Active sessions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"password_changed_success" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Пароль успешно изменён"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Password changed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error_invalid_url" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Неверный URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Invalid URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error_invalid_credentials" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Неверный email или пароль"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Invalid email or password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"alert_status_active" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "активен"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "active"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"alert_status_resolved" : {
|
||||||
|
"localizations" : {
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "завершён"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "resolved"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version" : "1.0"
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ enum APIError: Error, LocalizedError {
|
|||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .invalidURL: return "Invalid URL"
|
case .invalidURL: return String(localized: "error_invalid_url")
|
||||||
case .unauthorized: return "Неверный email или пароль"
|
case .unauthorized: return String(localized: "error_invalid_credentials")
|
||||||
case .validationError(let errors):
|
case .validationError(let errors):
|
||||||
return errors.values.flatMap { $0 }.joined(separator: ", ")
|
return errors.values.flatMap { $0 }.joined(separator: ", ")
|
||||||
case .serverError(let message): return message
|
case .serverError(let message): return message
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class SettingsViewModel: ObservableObject {
|
|||||||
func changePassword(current: String, new: String) async -> Bool {
|
func changePassword(current: String, new: String) async -> Bool {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if PreviewData.isPreviewMode {
|
if PreviewData.isPreviewMode {
|
||||||
successMessage = "Пароль успешно изменён"
|
successMessage = String(localized: "password_changed_success")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -53,7 +53,7 @@ class SettingsViewModel: ObservableObject {
|
|||||||
defer { isLoading = false }
|
defer { isLoading = false }
|
||||||
do {
|
do {
|
||||||
_ = try await service.changePassword(current: current, new: new)
|
_ = try await service.changePassword(current: current, new: new)
|
||||||
successMessage = "Пароль успешно изменён"
|
successMessage = String(localized: "password_changed_success")
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
self.error = error.localizedDescription
|
self.error = error.localizedDescription
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct LoginView: View {
|
|||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
Text("Mayday")
|
Text("Mayday")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
Text("Мониторинг и уведомления")
|
Text("login_subtitle")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ struct LoginView: View {
|
|||||||
.autocorrectionDisabled()
|
.autocorrectionDisabled()
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
|
|
||||||
SecureField("Пароль", text: $password)
|
SecureField("password", text: $password)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.textContentType(.password)
|
.textContentType(.password)
|
||||||
}
|
}
|
||||||
@@ -48,14 +48,14 @@ struct LoginView: View {
|
|||||||
ProgressView()
|
ProgressView()
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
} else {
|
} else {
|
||||||
Text("Войти")
|
Text("login_button")
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.disabled(email.isEmpty || password.isEmpty || authViewModel.isLoading)
|
.disabled(email.isEmpty || password.isEmpty || authViewModel.isLoading)
|
||||||
|
|
||||||
Button("Нет аккаунта? Зарегистрироваться") {
|
Button("login_no_account") {
|
||||||
showRegister = true
|
showRegister = true
|
||||||
}
|
}
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
@@ -64,7 +64,7 @@ struct LoginView: View {
|
|||||||
Button {
|
Button {
|
||||||
Task { await authViewModel.enterPreviewMode() }
|
Task { await authViewModel.enterPreviewMode() }
|
||||||
} label: {
|
} label: {
|
||||||
Label("Демо-режим", systemImage: "play.circle.fill")
|
Label("demo_mode", systemImage: "play.circle.fill")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct RegisterView: View {
|
|||||||
VStack(spacing: 24) {
|
VStack(spacing: 24) {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("Регистрация")
|
Text("register_title")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
|
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
@@ -25,23 +25,23 @@ struct RegisterView: View {
|
|||||||
.autocorrectionDisabled()
|
.autocorrectionDisabled()
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
|
|
||||||
SecureField("Пароль", text: $password)
|
SecureField("password", text: $password)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.textContentType(.newPassword)
|
.textContentType(.newPassword)
|
||||||
|
|
||||||
SecureField("Подтвердите пароль", text: $confirmPassword)
|
SecureField("confirm_password", text: $confirmPassword)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.textContentType(.newPassword)
|
.textContentType(.newPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
if password.count > 0 && password.count < 8 {
|
if password.count > 0 && password.count < 8 {
|
||||||
Text("Пароль должен содержать не менее 8 символов")
|
Text("password_min_length")
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
|
|
||||||
if confirmPassword.count > 0 && password != confirmPassword {
|
if confirmPassword.count > 0 && password != confirmPassword {
|
||||||
Text("Пароли не совпадают")
|
Text("passwords_mismatch")
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,13 @@ struct RegisterView: View {
|
|||||||
if authViewModel.isLoading {
|
if authViewModel.isLoading {
|
||||||
ProgressView().frame(maxWidth: .infinity)
|
ProgressView().frame(maxWidth: .infinity)
|
||||||
} else {
|
} else {
|
||||||
Text("Создать аккаунт").frame(maxWidth: .infinity)
|
Text("register_button").frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.disabled(!isFormValid || authViewModel.isLoading)
|
.disabled(!isFormValid || authViewModel.isLoading)
|
||||||
|
|
||||||
Button("Уже есть аккаунт?") { dismiss() }
|
Button("register_has_account") { dismiss() }
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -80,7 +80,7 @@ struct RegisterView: View {
|
|||||||
.navigationDestination(isPresented: $showVerify) {
|
.navigationDestination(isPresented: $showVerify) {
|
||||||
VerifyEmailView(email: registeredEmail)
|
VerifyEmailView(email: registeredEmail)
|
||||||
}
|
}
|
||||||
.navigationTitle("Регистрация")
|
.navigationTitle("register_title")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ struct VerifyEmailView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
Text("Подтвердите email")
|
Text("verify_email_title")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
Text("Код отправлен на")
|
Text("verify_code_sent_to")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Text(email)
|
Text(email)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
@@ -51,9 +51,9 @@ struct VerifyEmailView: View {
|
|||||||
Task { await resendCode() }
|
Task { await resendCode() }
|
||||||
} label: {
|
} label: {
|
||||||
if resendCooldown > 0 {
|
if resendCooldown > 0 {
|
||||||
Text("Отправить повторно (\(resendCooldown) сек)")
|
Text("verify_resend_cooldown \(resendCooldown)")
|
||||||
} else {
|
} else {
|
||||||
Text("Отправить повторно")
|
Text("verify_resend")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(resendCooldown > 0)
|
.disabled(resendCooldown > 0)
|
||||||
@@ -61,7 +61,7 @@ struct VerifyEmailView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.navigationTitle("Подтверждение")
|
.navigationTitle("verify_nav_title")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.onAppear { focusedIndex = 0 }
|
.onAppear { focusedIndex = 0 }
|
||||||
.onDisappear { cooldownTask?.cancel() }
|
.onDisappear { cooldownTask?.cancel() }
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct NotificationDetailView: View {
|
|||||||
Button {
|
Button {
|
||||||
Task { await viewModel.markAsRead(notification) }
|
Task { await viewModel.markAsRead(notification) }
|
||||||
} label: {
|
} label: {
|
||||||
Text("Отметить прочитанным")
|
Text("mark_as_read")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
@@ -45,7 +45,7 @@ struct NotificationDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(.systemGroupedBackground))
|
.background(Color(.systemGroupedBackground))
|
||||||
.navigationTitle("Подробности")
|
.navigationTitle("details_section")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.task {
|
.task {
|
||||||
await viewModel.markAsRead(notification)
|
await viewModel.markAsRead(notification)
|
||||||
@@ -89,8 +89,8 @@ struct NotificationDetailView: View {
|
|||||||
|
|
||||||
private var statusBadge: some View {
|
private var statusBadge: some View {
|
||||||
let (text, color): (String, Color) = notification.isRead
|
let (text, color): (String, Color) = notification.isRead
|
||||||
? ("Прочитано", .green)
|
? (String(localized: "status_read"), .green)
|
||||||
: ("Новое", .red)
|
: (String(localized: "status_new"), .red)
|
||||||
return Text(text)
|
return Text(text)
|
||||||
.font(.caption.bold())
|
.font(.caption.bold())
|
||||||
.foregroundStyle(color)
|
.foregroundStyle(color)
|
||||||
@@ -104,7 +104,7 @@ struct NotificationDetailView: View {
|
|||||||
|
|
||||||
private var detailsCard: some View {
|
private var detailsCard: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Label("Подробности", systemImage: "doc.text.fill")
|
Label("details_section", systemImage: "doc.text.fill")
|
||||||
.font(.subheadline.bold())
|
.font(.subheadline.bold())
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ struct NotificationDetailView: View {
|
|||||||
|
|
||||||
private func metadataCard(_ metadata: [String: String]) -> some View {
|
private func metadataCard(_ metadata: [String: String]) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Label("Информация", systemImage: "info.circle.fill")
|
Label("info_section", systemImage: "info.circle.fill")
|
||||||
.font(.subheadline.bold())
|
.font(.subheadline.bold())
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
|
|
||||||
@@ -170,17 +170,17 @@ struct NotificationDetailView: View {
|
|||||||
|
|
||||||
private var statusCard: some View {
|
private var statusCard: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Label("Статус", systemImage: "clock.fill")
|
Label("status_section", systemImage: "clock.fill")
|
||||||
.font(.subheadline.bold())
|
.font(.subheadline.bold())
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
|
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
infoRow(icon: "paperplane.fill", label: "Канал", value: channelLabel)
|
infoRow(icon: "paperplane.fill", label: String(localized: "channel_label"), value: channelLabel)
|
||||||
Divider()
|
Divider()
|
||||||
infoRow(icon: "clock", label: "Получено", value: notification.createdAt.formatted(date: .abbreviated, time: .shortened))
|
infoRow(icon: "clock", label: String(localized: "received_label"), value: notification.createdAt.formatted(date: .abbreviated, time: .shortened))
|
||||||
if let readAt = notification.readAt {
|
if let readAt = notification.readAt {
|
||||||
Divider()
|
Divider()
|
||||||
infoRow(icon: "checkmark.circle.fill", label: "Прочитано", value: readAt.formatted(date: .abbreviated, time: .shortened))
|
infoRow(icon: "checkmark.circle.fill", label: String(localized: "read_at_label"), value: readAt.formatted(date: .abbreviated, time: .shortened))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ struct NotificationDetailView: View {
|
|||||||
|
|
||||||
private var channelLabel: String {
|
private var channelLabel: String {
|
||||||
switch notification.channel {
|
switch notification.channel {
|
||||||
case .inApp: return "В приложении"
|
case .inApp: return String(localized: "channel_in_app")
|
||||||
case .push: return "Push"
|
case .push: return "Push"
|
||||||
case .email: return "Email"
|
case .email: return "Email"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,27 +20,27 @@ struct NotificationsView: View {
|
|||||||
ProgressView()
|
ProgressView()
|
||||||
} else if let error = viewModel.error, viewModel.notifications.isEmpty {
|
} else if let error = viewModel.error, viewModel.notifications.isEmpty {
|
||||||
ContentUnavailableView(
|
ContentUnavailableView(
|
||||||
"Ошибка загрузки",
|
"loading_error",
|
||||||
systemImage: "exclamationmark.triangle",
|
systemImage: "exclamationmark.triangle",
|
||||||
description: Text(error)
|
description: Text(error)
|
||||||
)
|
)
|
||||||
} else if viewModel.notifications.isEmpty {
|
} else if viewModel.notifications.isEmpty {
|
||||||
ContentUnavailableView(
|
ContentUnavailableView(
|
||||||
"Нет уведомлений",
|
"no_notifications",
|
||||||
systemImage: "bell.slash",
|
systemImage: "bell.slash",
|
||||||
description: Text("Новые уведомления появятся здесь")
|
description: Text("no_notifications_description")
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
notificationsList
|
notificationsList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(.systemGroupedBackground))
|
.background(Color(.systemGroupedBackground))
|
||||||
.navigationTitle("Уведомления")
|
.navigationTitle("notifications_title")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if PreviewData.isPreviewMode {
|
if PreviewData.isPreviewMode {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Text("ДЕМО")
|
Text("demo_badge")
|
||||||
.font(.caption2.bold())
|
.font(.caption2.bold())
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
@@ -80,7 +80,7 @@ struct NotificationsView: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
if !unreadNotifications.isEmpty {
|
if !unreadNotifications.isEmpty {
|
||||||
sectionHeader("Активные")
|
sectionHeader(String(localized: "notifications_active"))
|
||||||
ForEach(unreadNotifications) { notification in
|
ForEach(unreadNotifications) { notification in
|
||||||
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
||||||
ActiveNotificationCard(notification: notification)
|
ActiveNotificationCard(notification: notification)
|
||||||
@@ -97,7 +97,7 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !readNotifications.isEmpty {
|
if !readNotifications.isEmpty {
|
||||||
sectionHeader("Завершённые")
|
sectionHeader(String(localized: "notifications_completed"))
|
||||||
ForEach(readNotifications) { notification in
|
ForEach(readNotifications) { notification in
|
||||||
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
NavigationLink(destination: NotificationDetailView(notification: notification, viewModel: viewModel)) {
|
||||||
ResolvedNotificationCard(notification: notification)
|
ResolvedNotificationCard(notification: notification)
|
||||||
@@ -172,7 +172,7 @@ struct ActiveNotificationCard: View {
|
|||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Открыть")
|
Text("open_button")
|
||||||
.font(.subheadline.bold())
|
.font(.subheadline.bold())
|
||||||
.foregroundStyle(Color.red)
|
.foregroundStyle(Color.red)
|
||||||
.padding(.horizontal, 32)
|
.padding(.horizontal, 32)
|
||||||
@@ -238,7 +238,7 @@ struct ResolvedNotificationCard: View {
|
|||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
Text("прочитано \(readAt.formatted(date: .abbreviated, time: .shortened))")
|
Text("notification_read_at \(readAt.formatted(date: .abbreviated, time: .shortened))")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ struct ChangePasswordView: View {
|
|||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
SecureField("Текущий пароль", text: $currentPassword)
|
SecureField("current_password", text: $currentPassword)
|
||||||
.textContentType(.password)
|
.textContentType(.password)
|
||||||
SecureField("Новый пароль", text: $newPassword)
|
SecureField("new_password", text: $newPassword)
|
||||||
.textContentType(.newPassword)
|
.textContentType(.newPassword)
|
||||||
SecureField("Подтвердите новый пароль", text: $confirmPassword)
|
SecureField("confirm_new_password", text: $confirmPassword)
|
||||||
.textContentType(.newPassword)
|
.textContentType(.newPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ struct ChangePasswordView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button("Сохранить") {
|
Button("save_button") {
|
||||||
Task {
|
Task {
|
||||||
let success = await viewModel.changePassword(current: currentPassword, new: newPassword)
|
let success = await viewModel.changePassword(current: currentPassword, new: newPassword)
|
||||||
if success { dismiss() }
|
if success { dismiss() }
|
||||||
@@ -43,11 +43,11 @@ struct ChangePasswordView: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Сменить пароль")
|
.navigationTitle("change_password_title")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Button("Отмена") { dismiss() }
|
Button("cancel") { dismiss() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct SessionsView: View {
|
|||||||
.font(.body)
|
.font(.body)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
if session.isCurrent {
|
if session.isCurrent {
|
||||||
Text("Текущая")
|
Text("current_session")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.padding(.horizontal, 6)
|
.padding(.horizontal, 6)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
@@ -26,7 +26,7 @@ struct SessionsView: View {
|
|||||||
Text(session.ipAddress)
|
Text(session.ipAddress)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Text("Создана: \(session.createdAt.formatted(date: .abbreviated, time: .shortened))")
|
Text("session_created \(session.createdAt.formatted(date: .abbreviated, time: .shortened))")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
@@ -35,17 +35,17 @@ struct SessionsView: View {
|
|||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
Task { await viewModel.deleteSession(session) }
|
Task { await viewModel.deleteSession(session) }
|
||||||
} label: {
|
} label: {
|
||||||
Label("Удалить", systemImage: "trash")
|
Label("delete_button", systemImage: "trash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Активные сессии")
|
.navigationTitle("active_sessions_title")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button("Готово") { dismiss() }
|
Button("done_button") { dismiss() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ struct SettingsView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
Section("Аккаунт") {
|
Section("account_section") {
|
||||||
if let user = authViewModel.currentUser {
|
if let user = authViewModel.currentUser {
|
||||||
LabeledContent("Email", value: user.email)
|
LabeledContent("Email", value: user.email)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button("Сменить пароль") {
|
Button("change_password") {
|
||||||
showChangePassword = true
|
showChangePassword = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ struct SettingsView: View {
|
|||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(url)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Push-уведомления", systemImage: "bell.badge")
|
Label("push_notifications", systemImage: "bell.badge")
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ struct SettingsView: View {
|
|||||||
showSessions = true
|
showSessions = true
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Активные сессии")
|
Text("active_sessions")
|
||||||
Spacer()
|
Spacer()
|
||||||
if !viewModel.sessions.isEmpty {
|
if !viewModel.sessions.isEmpty {
|
||||||
Text("(\(viewModel.sessions.count))")
|
Text("(\(viewModel.sessions.count))")
|
||||||
@@ -52,20 +52,20 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button("Выйти из аккаунта", role: .destructive) {
|
Button("logout_button", role: .destructive) {
|
||||||
Task { await authViewModel.logout() }
|
Task { await authViewModel.logout() }
|
||||||
}
|
}
|
||||||
|
|
||||||
Button("Выйти на всех устройствах", role: .destructive) {
|
Button("logout_all_button", role: .destructive) {
|
||||||
showLogoutAllConfirm = true
|
showLogoutAllConfirm = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Настройки")
|
.navigationTitle("settings_title")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button("Готово") { dismiss() }
|
Button("done_button") { dismiss() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showChangePassword) {
|
.sheet(isPresented: $showChangePassword) {
|
||||||
@@ -76,11 +76,11 @@ struct SettingsView: View {
|
|||||||
.environmentObject(viewModel)
|
.environmentObject(viewModel)
|
||||||
}
|
}
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
"Выйти на всех устройствах?",
|
"logout_all_confirm",
|
||||||
isPresented: $showLogoutAllConfirm,
|
isPresented: $showLogoutAllConfirm,
|
||||||
titleVisibility: .visible
|
titleVisibility: .visible
|
||||||
) {
|
) {
|
||||||
Button("Выйти везде", role: .destructive) {
|
Button("logout_all_action", role: .destructive) {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
_ = try await NotificationsAPIService.shared.logoutAll()
|
_ = try await NotificationsAPIService.shared.logoutAll()
|
||||||
@@ -90,10 +90,10 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button("Отмена", role: .cancel) {}
|
Button("cancel", role: .cancel) {}
|
||||||
}
|
}
|
||||||
.alert(
|
.alert(
|
||||||
"Ошибка",
|
"error_title",
|
||||||
isPresented: Binding(
|
isPresented: Binding(
|
||||||
get: { logoutAllError != nil },
|
get: { logoutAllError != nil },
|
||||||
set: { if !$0 { logoutAllError = nil } }
|
set: { if !$0 { logoutAllError = nil } }
|
||||||
|
|||||||
@@ -123,8 +123,8 @@ struct MaydayLiveActivityLiveActivity: Widget {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func statusBadge(_ status: AlertStatus) -> some View {
|
func statusBadge(_ status: AlertStatus) -> some View {
|
||||||
let (text, color): (String, Color) = status == .active
|
let (text, color): (String, Color) = status == .active
|
||||||
? ("активен", .red)
|
? (String(localized: "alert_status_active"), .red)
|
||||||
: ("завершён", .green)
|
: (String(localized: "alert_status_resolved"), .green)
|
||||||
Text(text)
|
Text(text)
|
||||||
.font(.caption2.bold())
|
.font(.caption2.bold())
|
||||||
.textCase(.uppercase)
|
.textCase(.uppercase)
|
||||||
|
|||||||
Reference in New Issue
Block a user