Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef12e0ff3a | |||
| e7e201758e | |||
| 55ae6e1f3a | |||
| 3ea8b8ac46 | |||
| 8fe40e9fe3 | |||
| 6f82cd4f26 | |||
| 81e28129b7 | |||
| 39cde10fab | |||
| ed54eb0284 | |||
| d66015bebd | |||
| 890bfc84f1 | |||
| 59505d6985 | |||
| 09820dda71 | |||
| c0023614ad | |||
| e0f517e993 | |||
| 66c556b587 | |||
| 877fb0dbd4 | |||
| 2ea65dc556 | |||
| 23dce29ff8 | |||
| 9292c963d5 | |||
| 2b1676a4f7 | |||
| d85812ed7b | |||
| 54148716bb | |||
| 3bd0b69429 | |||
| 496c7735f7 | |||
| bfbc3d40b3 |
@@ -45,6 +45,6 @@ public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
|
||||
return GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets()
|
||||
{
|
||||
ClientId = ClientId
|
||||
}, AuthenticatorConfig.GmailScope, account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
|
||||
}, AuthenticatorConfig.GetGmailScope(account?.IsMailAccessGranted != false, account?.IsCalendarAccessGranted == true), account.Id.ToString(), CancellationToken.None, new FileDataStore(AuthenticatorConfig.GmailTokenStoreIdentifier));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,10 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
_publicClientApplication = outlookAppBuilder.Build();
|
||||
}
|
||||
|
||||
public string[] Scope => AuthenticatorConfig.OutlookScope;
|
||||
private string[] GetScope(MailAccount account)
|
||||
=> AuthenticatorConfig.GetOutlookScope(
|
||||
account?.IsMailAccessGranted != false,
|
||||
account?.IsCalendarAccessGranted == true);
|
||||
|
||||
private async Task EnsureTokenCacheAttachedAsync()
|
||||
{
|
||||
@@ -91,7 +94,7 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
|
||||
try
|
||||
{
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(Scope, storedAccount).ExecuteAsync();
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(GetScope(account), storedAccount).ExecuteAsync();
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
@@ -103,10 +106,6 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
|
||||
return await GenerateTokenInformationAsync(account);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<TokenInformationEx> GenerateTokenInformationAsync(MailAccount account)
|
||||
@@ -122,17 +121,11 @@ public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
if (_nativeAppService.GetCoreWindowHwnd == null) throw new AuthenticationAttentionException(account);
|
||||
|
||||
AuthenticationResult authResult = await _publicClientApplication
|
||||
.AcquireTokenInteractive(Scope)
|
||||
.AcquireTokenInteractive(GetScope(account))
|
||||
.ExecuteAsync();
|
||||
|
||||
// If the account is null, it means it's the initial creation of it.
|
||||
// If not, make sure the authenticated user address matches the username.
|
||||
// When people refresh their token, accounts must match.
|
||||
|
||||
if (account?.Address != null && !account.Address.Equals(authResult.Account.Username, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new AuthenticationException("Authenticated address does not match with your account address. If you are signing with a Office365, it is not officially supported yet.");
|
||||
}
|
||||
// Microsoft 365 work/school tenants can use a sign-in UPN that differs from
|
||||
// the mailbox primary SMTP address, so interactive reauth must not reject them.
|
||||
|
||||
return new TokenInformationEx(authResult.AccessToken, authResult.Account.Username);
|
||||
}
|
||||
|
||||
@@ -299,6 +299,9 @@ public partial class CalendarAppShellViewModel : CalendarBaseViewModel,
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
if (!GroupedAccountCalendarViewModel.SupportsCalendar(account))
|
||||
continue;
|
||||
|
||||
var accountCalendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||
var calendarViewModels = accountCalendars.Select(calendar => new AccountCalendarViewModel(account, calendar)).ToList();
|
||||
var groupedAccountCalendarViewModel = new GroupedAccountCalendarViewModel(account, calendarViewModels);
|
||||
|
||||
@@ -408,6 +408,9 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
if (!GroupedAccountCalendarViewModel.SupportsCalendar(account))
|
||||
continue;
|
||||
|
||||
var calendars = await _calendarService.GetAccountCalendarsAsync(account.Id).ConfigureAwait(false);
|
||||
var viewModels = calendars
|
||||
.Select(calendar => new AccountCalendarViewModel(account, calendar))
|
||||
|
||||
@@ -17,6 +17,9 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
public MailAccount Account { get; }
|
||||
public ObservableCollection<AccountCalendarViewModel> AccountCalendars { get; }
|
||||
|
||||
public static bool SupportsCalendar(MailAccount account)
|
||||
=> account?.IsCalendarAccessGranted == true;
|
||||
|
||||
public GroupedAccountCalendarViewModel(MailAccount account, IEnumerable<AccountCalendarViewModel> calendarViewModels)
|
||||
{
|
||||
Account = account;
|
||||
@@ -95,6 +98,7 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
public bool CanSynchronize => !IsSynchronizationInProgress;
|
||||
public bool IsSynchronizationProgressVisible => IsSynchronizationInProgress;
|
||||
public bool IsProgressIndeterminate => IsSynchronizationInProgress && TotalItemsToSync <= 0;
|
||||
public string AccountAddressDisplay => string.IsNullOrWhiteSpace(Account?.Address) ? string.Empty : $" ({Account.Address})";
|
||||
|
||||
public double SynchronizationProgress
|
||||
{
|
||||
@@ -198,5 +202,6 @@ public partial class GroupedAccountCalendarViewModel : ObservableObject
|
||||
Account.MergedInboxId = updatedAccount.MergedInboxId;
|
||||
AccountColorHex = updatedAccount.AccountColorHex;
|
||||
OnPropertyChanged(nameof(Account));
|
||||
OnPropertyChanged(nameof(AccountAddressDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,11 @@ public class MailCopy
|
||||
/// </summary>
|
||||
public bool IsFlagged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this mail should stay pinned to the top locally.
|
||||
/// </summary>
|
||||
public bool IsPinned { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// To support Outlook.
|
||||
/// Gmail doesn't use it.
|
||||
@@ -167,6 +172,9 @@ public class MailCopy
|
||||
[Ignore]
|
||||
public Guid? ReadReceiptMessageUniqueId { get; set; }
|
||||
|
||||
[Ignore]
|
||||
public List<MailCategory> Categories { get; set; } = [];
|
||||
|
||||
public IEnumerable<Guid> GetContainingIds() => [UniqueId];
|
||||
public override string ToString() => $"{Subject} <-> {Id}";
|
||||
}
|
||||
|
||||
@@ -78,6 +78,13 @@ public class MailAccount
|
||||
/// </summary>
|
||||
public SpecialImapProvider SpecialImapProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether mail access is granted for this account.
|
||||
/// When false, mail folders, aliases, compose flows, and mail synchronization are unavailable.
|
||||
/// Default is true for legacy accounts to preserve existing behavior.
|
||||
/// </summary>
|
||||
public bool IsMailAccessGranted { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether calendar access is granted for this account.
|
||||
/// When false, synchronizers will not process EventMessages or calendar invitations.
|
||||
|
||||
@@ -16,5 +16,6 @@ public enum AppLanguage
|
||||
Greek,
|
||||
PortugeseBrazil,
|
||||
Italian,
|
||||
Romanian
|
||||
Romanian,
|
||||
Korean
|
||||
}
|
||||
|
||||
@@ -20,17 +20,19 @@ public enum MailCopyChangeFlags
|
||||
Importance = 1 << 11,
|
||||
IsRead = 1 << 12,
|
||||
IsFlagged = 1 << 13,
|
||||
IsFocused = 1 << 14,
|
||||
HasAttachments = 1 << 15,
|
||||
ItemType = 1 << 16,
|
||||
DraftId = 1 << 17,
|
||||
IsDraft = 1 << 18,
|
||||
FileId = 1 << 19,
|
||||
AssignedFolder = 1 << 20,
|
||||
AssignedAccount = 1 << 21,
|
||||
SenderContact = 1 << 22,
|
||||
UniqueId = 1 << 23,
|
||||
ReadReceiptState = 1 << 24,
|
||||
IsPinned = 1 << 14,
|
||||
IsFocused = 1 << 15,
|
||||
HasAttachments = 1 << 16,
|
||||
ItemType = 1 << 17,
|
||||
DraftId = 1 << 18,
|
||||
IsDraft = 1 << 19,
|
||||
FileId = 1 << 20,
|
||||
AssignedFolder = 1 << 21,
|
||||
AssignedAccount = 1 << 22,
|
||||
SenderContact = 1 << 23,
|
||||
UniqueId = 1 << 24,
|
||||
ReadReceiptState = 1 << 25,
|
||||
Categories = 1 << 26,
|
||||
All = Id |
|
||||
FolderId |
|
||||
ThreadId |
|
||||
@@ -45,6 +47,7 @@ public enum MailCopyChangeFlags
|
||||
Importance |
|
||||
IsRead |
|
||||
IsFlagged |
|
||||
IsPinned |
|
||||
IsFocused |
|
||||
HasAttachments |
|
||||
ItemType |
|
||||
@@ -55,5 +58,6 @@ public enum MailCopyChangeFlags
|
||||
AssignedAccount |
|
||||
SenderContact |
|
||||
UniqueId |
|
||||
ReadReceiptState
|
||||
ReadReceiptState |
|
||||
Categories
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Wino.Core.Domain.Enums;
|
||||
|
||||
public enum ThreeButtonDialogResult
|
||||
{
|
||||
Primary,
|
||||
Secondary,
|
||||
Cancel
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
public interface IAuthenticatorConfig
|
||||
{
|
||||
string OutlookAuthenticatorClientId { get; }
|
||||
string[] OutlookScope { get; }
|
||||
string[] GetOutlookScope(bool isMailAccessGranted, bool isCalendarAccessGranted);
|
||||
string GmailAuthenticatorClientId { get; }
|
||||
string[] GmailScope { get; }
|
||||
string[] GetGmailScope(bool isMailAccessGranted, bool isCalendarAccessGranted);
|
||||
string GmailTokenStoreIdentifier { get; }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
public interface IConfigurationService
|
||||
{
|
||||
bool Contains(string key);
|
||||
|
||||
void Set(string key, object value);
|
||||
T Get<T>(string key, T defaultValue = default);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public interface IMailCategoryService
|
||||
Task AssignCategoryAsync(Guid categoryId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||
Task UnassignCategoryAsync(Guid categoryId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||
Task<List<MailCategory>> GetCategoriesForMailAsync(Guid accountId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||
Task<IReadOnlyDictionary<Guid, IReadOnlyList<MailCategory>>> GetCategoriesByMailAsync(Guid accountId, IEnumerable<Guid> mailCopyUniqueIds);
|
||||
Task<List<Guid>> GetAssignedCategoryIdsForAllAsync(IEnumerable<Guid> mailCopyUniqueIds);
|
||||
Task<List<string>> GetCategoryNamesForMailAsync(Guid mailCopyUniqueId);
|
||||
Task<List<MailCopy>> GetMailCopiesForCategoryAsync(Guid categoryId);
|
||||
|
||||
@@ -19,6 +19,12 @@ public interface IMailDialogService : IDialogServiceBase
|
||||
{
|
||||
void ShowReadOnlyCalendarMessage();
|
||||
Task<bool> ShowHardDeleteConfirmationAsync();
|
||||
Task<ThreeButtonDialogResult> ShowThreeButtonDialogAsync(string title,
|
||||
string description,
|
||||
string primaryButtonText,
|
||||
string secondaryButtonText,
|
||||
string cancelButtonText,
|
||||
WinoCustomMessageDialogIcon? icon = null);
|
||||
Task HandleSystemFolderConfigurationDialogAsync(Guid accountId, IFolderService folderService);
|
||||
|
||||
// Custom dialogs
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
@@ -27,4 +29,6 @@ public interface IMailItemDisplayInformation : INotifyPropertyChanged
|
||||
bool HasReadReceiptTracking { get; }
|
||||
bool IsReadReceiptAcknowledged { get; }
|
||||
string ReadReceiptDisplayText { get; }
|
||||
IReadOnlyList<MailCategory> Categories { get; }
|
||||
bool HasCategories { get; }
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ public interface IMailListItemSorting
|
||||
{
|
||||
DateTime SortingDate { get; }
|
||||
string SortingName { get; }
|
||||
bool IsPinned { get; }
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public interface IMailService
|
||||
/// </summary>
|
||||
Task<List<MailCopy>> GetMailItemsAsync(IEnumerable<string> mailCopyIds);
|
||||
Task<List<MailCopy>> FetchMailsAsync(MailListInitializationOptions options, CancellationToken cancellationToken = default);
|
||||
Task<List<MailCopy>> FetchPinnedMailsAsync(MailListInitializationOptions options, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all mail copies for all folders.
|
||||
@@ -33,14 +34,20 @@ public interface IMailService
|
||||
/// <param name="accountId">Account to remove from</param>
|
||||
/// <param name="mailCopyId">Mail copy id to remove.</param>
|
||||
Task DeleteMailAsync(Guid accountId, string mailCopyId);
|
||||
Task DeleteMailsAsync(Guid accountId, IEnumerable<string> mailCopyIds);
|
||||
|
||||
Task ChangeReadStatusAsync(string mailCopyId, bool isRead);
|
||||
Task ChangeFlagStatusAsync(string mailCopyId, bool isFlagged);
|
||||
Task ChangePinnedStatusAsync(IEnumerable<Guid> uniqueMailIds, bool isPinned);
|
||||
Task ApplyMailStateUpdatesAsync(IEnumerable<MailCopyStateUpdate> updates);
|
||||
|
||||
Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
|
||||
Task DeleteAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
|
||||
Task CreateAssignmentsAsync(Guid accountId, IEnumerable<MailFolderAssignmentUpdate> assignments);
|
||||
Task DeleteAssignmentsAsync(Guid accountId, IEnumerable<MailFolderAssignmentUpdate> assignments);
|
||||
|
||||
Task<bool> CreateMailAsync(Guid accountId, NewMailItemPackage package);
|
||||
Task CreateMailsAsync(Guid accountId, IReadOnlyList<NewMailItemPackage> packages);
|
||||
|
||||
/// <summary>
|
||||
/// Maps new mail item with the existing local draft copy.
|
||||
|
||||
@@ -51,6 +51,11 @@ public interface INotificationBuilder
|
||||
/// </summary>
|
||||
void CreateStoreUpdateNotification();
|
||||
|
||||
/// <summary>
|
||||
/// Shows the one-time release migration notification.
|
||||
/// </summary>
|
||||
void CreateReleaseMigrationNotification();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a calendar reminder toast for the specified calendar item.
|
||||
/// </summary>
|
||||
|
||||
@@ -62,6 +62,11 @@ public interface IPreferencesService : INotifyPropertyChanged
|
||||
/// </summary>
|
||||
bool IsStoreUpdateNotificationsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Whether the system tray icon should be created while the shell is available.
|
||||
/// </summary>
|
||||
bool IsSystemTrayIconEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Whether the Wino account profile button in the shell title bar should be hidden.
|
||||
/// </summary>
|
||||
@@ -122,11 +127,21 @@ public interface IPreferencesService : INotifyPropertyChanged
|
||||
/// </summary>
|
||||
bool IsHardDeleteProtectionEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Show the empty-folder command for junk/spam folders.
|
||||
/// </summary>
|
||||
bool IsShowEmptyJunkFolderEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Thread mails into conversations.
|
||||
/// </summary>
|
||||
bool IsThreadingEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Whether the newest message in a conversation should appear first.
|
||||
/// </summary>
|
||||
bool IsNewestThreadMailFirst { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Setting: Show sender pictures in mail list.
|
||||
/// </summary>
|
||||
|
||||
@@ -7,4 +7,6 @@ public record AccountCreationDialogResult(
|
||||
string AccountName,
|
||||
SpecialImapProviderDetails SpecialImapProviderDetails,
|
||||
string AccountColorHex,
|
||||
InitialSynchronizationRange InitialSynchronizationRange);
|
||||
InitialSynchronizationRange InitialSynchronizationRange,
|
||||
bool IsMailAccessGranted,
|
||||
bool IsCalendarAccessGranted);
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Domain.Models.Calendar;
|
||||
|
||||
public sealed class CalDavCalendar
|
||||
{
|
||||
public string RemoteCalendarId { get; init; } = string.Empty;
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public string CTag { get; init; } = string.Empty;
|
||||
public string SyncToken { get; init; } = string.Empty;
|
||||
public string TimeZone { get; init; } = string.Empty;
|
||||
public string BackgroundColorHex { get; init; } = string.Empty;
|
||||
public bool IsReadOnly { get; init; }
|
||||
public bool SupportsEvents { get; init; } = true;
|
||||
public CalendarItemShowAs DefaultShowAs { get; init; } = CalendarItemShowAs.Busy;
|
||||
public double? Order { get; init; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
public class ListItemComparer : IComparer<object>
|
||||
{
|
||||
@@ -9,14 +10,48 @@ public class ListItemComparer : IComparer<object>
|
||||
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
if (x is MailListGroupKey xGroupKey && y is MailListGroupKey yGroupKey)
|
||||
{
|
||||
if (xGroupKey.IsPinned != yGroupKey.IsPinned)
|
||||
return yGroupKey.IsPinned.CompareTo(xGroupKey.IsPinned);
|
||||
|
||||
if (xGroupKey.IsPinned && yGroupKey.IsPinned)
|
||||
return 0;
|
||||
|
||||
return CompareSortValues(xGroupKey.Value, yGroupKey.Value);
|
||||
}
|
||||
|
||||
if (x is IMailListItemSorting xSorting && y is IMailListItemSorting ySorting)
|
||||
return SortByName ? string.Compare(xSorting.SortingName, ySorting.SortingName, StringComparison.OrdinalIgnoreCase) : DateTime.Compare(ySorting.SortingDate, xSorting.SortingDate);
|
||||
else if (x is MailCopy xMail && y is MailCopy yMail)
|
||||
return SortByName ? string.Compare(xMail.FromName, yMail.FromName, StringComparison.OrdinalIgnoreCase) : DateTime.Compare(yMail.CreationDate, xMail.CreationDate);
|
||||
else if (x is DateTime dateX && y is DateTime dateY)
|
||||
{
|
||||
if (xSorting.IsPinned != ySorting.IsPinned)
|
||||
return ySorting.IsPinned.CompareTo(xSorting.IsPinned);
|
||||
|
||||
return SortByName
|
||||
? string.Compare(xSorting.SortingName, ySorting.SortingName, StringComparison.OrdinalIgnoreCase)
|
||||
: DateTime.Compare(ySorting.SortingDate, xSorting.SortingDate);
|
||||
}
|
||||
|
||||
if (x is MailCopy xMail && y is MailCopy yMail)
|
||||
{
|
||||
if (xMail.IsPinned != yMail.IsPinned)
|
||||
return yMail.IsPinned.CompareTo(xMail.IsPinned);
|
||||
|
||||
return SortByName
|
||||
? string.Compare(xMail.FromName, yMail.FromName, StringComparison.OrdinalIgnoreCase)
|
||||
: DateTime.Compare(yMail.CreationDate, xMail.CreationDate);
|
||||
}
|
||||
|
||||
return CompareSortValues(x, y);
|
||||
}
|
||||
|
||||
private static int CompareSortValues(object x, object y)
|
||||
{
|
||||
if (x is DateTime dateX && y is DateTime dateY)
|
||||
return DateTime.Compare(dateY, dateX);
|
||||
else if (x is string stringX && y is string stringY)
|
||||
|
||||
if (x is string stringX && y is string stringY)
|
||||
return stringY.CompareTo(stringX);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class HtmlPreviewVisitor : MimeVisitor
|
||||
{
|
||||
private static readonly HashSet<string> BlockedTags = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"script", "iframe", "frame", "frameset", "object", "embed", "applet", "base", "meta", "form"
|
||||
"script", "iframe", "frame", "frameset", "object", "embed", "applet", "base", "meta", "form", "link"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> AllowedDataImageMimeTypes = new(StringComparer.OrdinalIgnoreCase)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
public sealed record MailCopyStateUpdate(string MailCopyId, bool? IsRead = null, bool? IsFlagged = null);
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
public sealed record MailFolderAssignmentUpdate(string MailCopyId, string RemoteFolderId);
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
public sealed record MailListGroupKey(bool IsPinned, object Value)
|
||||
{
|
||||
public static MailListGroupKey Pinned { get; } = new(true, null);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace Wino.Core.Domain.Models.Navigation;
|
||||
|
||||
public enum ProviderSelectionHostMode
|
||||
{
|
||||
Wizard,
|
||||
SettingsAddAccount
|
||||
}
|
||||
|
||||
public sealed class ProviderSelectionNavigationContext
|
||||
{
|
||||
public ProviderSelectionHostMode HostMode { get; init; } = ProviderSelectionHostMode.Wizard;
|
||||
|
||||
public static ProviderSelectionNavigationContext CreateForWizard()
|
||||
=> new()
|
||||
{
|
||||
HostMode = ProviderSelectionHostMode.Wizard
|
||||
};
|
||||
|
||||
public static ProviderSelectionNavigationContext CreateForSettingsAddAccount()
|
||||
=> new()
|
||||
{
|
||||
HostMode = ProviderSelectionHostMode.SettingsAddAccount
|
||||
};
|
||||
|
||||
public bool IsWizardHost => HostMode == ProviderSelectionHostMode.Wizard;
|
||||
}
|
||||
@@ -44,6 +44,6 @@ public class BatchCollection<TRequestType> : List<TRequestType>, IUIChangeReques
|
||||
public BatchCollection(IEnumerable<TRequestType> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
public void ApplyUIChanges() => ForEach(x => x.ApplyUIChanges());
|
||||
public void RevertUIChanges() => ForEach(x => x.RevertUIChanges());
|
||||
public virtual void ApplyUIChanges() => ForEach(x => x.ApplyUIChanges());
|
||||
public virtual void RevertUIChanges() => ForEach(x => x.RevertUIChanges());
|
||||
}
|
||||
|
||||
@@ -141,7 +141,9 @@ public static class SettingsNavigationInfoProvider
|
||||
public static SettingsNavigationItemInfo GetInfo(WinoPage pageType, string manageAccountsDescription = "")
|
||||
{
|
||||
var rootPage = GetRootPage(pageType);
|
||||
return GetNavigationItems(manageAccountsDescription).First(item => item.PageType == rootPage);
|
||||
return GetNavigationItems(manageAccountsDescription)
|
||||
.FirstOrDefault(item => item.PageType == rootPage)
|
||||
?? GetNavigationItems(manageAccountsDescription).First(item => item.PageType == WinoPage.SettingOptionsPage);
|
||||
}
|
||||
|
||||
public static string GetPageTitle(WinoPage pageType)
|
||||
@@ -180,6 +182,9 @@ public static class SettingsNavigationInfoProvider
|
||||
WinoPage.MailCategoryManagementPage => WinoPage.ManageAccountsPage,
|
||||
WinoPage.SignatureManagementPage => WinoPage.ManageAccountsPage,
|
||||
WinoPage.ImapCalDavSettingsPage => WinoPage.ManageAccountsPage,
|
||||
WinoPage.ProviderSelectionPage => WinoPage.ManageAccountsPage,
|
||||
WinoPage.SpecialImapCredentialsPage => WinoPage.ManageAccountsPage,
|
||||
WinoPage.AccountSetupProgressPage => WinoPage.ManageAccountsPage,
|
||||
WinoPage.CreateEmailTemplatePage => WinoPage.EmailTemplatesPage,
|
||||
WinoPage.CalendarSettingsPage => WinoPage.CalendarPreferenceSettingsPage,
|
||||
WinoPage.CalendarAccountSettingsPage => WinoPage.CalendarPreferenceSettingsPage,
|
||||
|
||||
@@ -45,6 +45,7 @@ public class WinoTranslationDictionary : Dictionary<string, string>
|
||||
AppLanguage.Greek => "el_GR",
|
||||
AppLanguage.PortugeseBrazil => "pt_BR",
|
||||
AppLanguage.Romanian => "ro_RO",
|
||||
AppLanguage.Korean => "ko_KR",
|
||||
_ => "en_US",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Изберете календар, за да конфигурирате неговите настройки.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Начална синхронизация",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino синхронизира вашите имейли до {0} назад.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Свързани функции",
|
||||
"AccountDetailsPage_CapabilityDescription": "Изберете дали този акаунт да се използва за поща, календар или и двете. Активирането на нова функция може да ви подтикне да се впишете отново.",
|
||||
"AccountCapability_MailOnly": "Само поща",
|
||||
"AccountCapability_CalendarOnly": "Само календар",
|
||||
"AccountCapability_MailAndCalendar": "Поща и календар",
|
||||
"AddHyperlink": "Добавяне",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Синхронизация на заден план",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Приложението не е настроено да се стартира при стартиране на Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Прилагане на темата",
|
||||
"Buttons_PopOut": "Извън прозореца",
|
||||
"Buttons_Browse": "Преглед",
|
||||
"Buttons_Back": "Назад",
|
||||
"Buttons_Cancel": "Отказ",
|
||||
"Buttons_Close": "Затваряне",
|
||||
"Buttons_Copy": "Копиране",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Изтриване на повтаряща се серия",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Тази чернова ще бъде отхвърлена. Искате ли да продължите?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Отхвърляне на черновата",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Черновикът все още е отворен. Запазете го преди да затворите прозореца?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Затвори прозореца",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Липсваща тема",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Съобщението няма тема. Искате ли да продължите?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Можете да активирате автоматично стартиране от Настройки -> Предпочитания за приложението.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Не е избрано съобщение",
|
||||
"NoMessageCrieteria": "Няма съобщения, които да отговарят на критериите ви за търсене",
|
||||
"NoMessageEmptyFolder": "Тази папка е празна",
|
||||
"MailEmptyState_Title": "Няма акаунти за поща",
|
||||
"MailEmptyState_Message": "Имате свързани акаунти за календар, но нито един от тях не е активен за поща. Добавете акаунт за поща или обновете съществуващ акаунт, за да използва поща.",
|
||||
"MailEmptyState_AddAccount": "Добави акаунт",
|
||||
"MailEmptyState_ManageAccounts": "Управление на акаунти",
|
||||
"Notifications_MultipleNotificationsMessage": "Имате {0} нови съобщения.",
|
||||
"Notifications_MultipleNotificationsTitle": "Нов имейл",
|
||||
"Notifications_WinoUpdatedMessage": "Вижте новата версия {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Изтриване на този акаунт",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Защита от окончателно изтриване",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Покажи командата за изпразване на папка в папките Спам. Това действие няма да изисква потвърждение и ще изтрие всички имейли в папката за спам веднага.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Покажи командата за изпразване на папка Спам",
|
||||
"SettingsDiagnostics_Description": "За разработчици",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Споделете този идентификационен номер с разработчиците, когато ви помолят, за да получите помощ за проблемите, с които се сблъсквате в Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Идентификатор за диагностика",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Значка на лентата на задачите",
|
||||
"SettingsThreads_Description": "Организиране на съобщенията в разговори.",
|
||||
"SettingsThreads_Title": "Групиране в разговори",
|
||||
"SettingsThreads_Enabled_Description": "Групирайте свързаните съобщения в един разговор.",
|
||||
"SettingsThreads_Enabled_Title": "Включване на групиране на разговорите по нишки",
|
||||
"SettingsThreadOrder_Description": "Изберете как се подреждат елементите в разговорната нишка.",
|
||||
"SettingsThreadOrder_Title": "Подреждане на елементи в нишката",
|
||||
"SettingsThreadOrder_LastItemFirst": "Последният елемент първо",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Първият елемент първо",
|
||||
"SettingsUnlinkAccounts_Description": "Премахване на връзката между акаунтите. Това няма да доведе до изтриване на акаунтите ви.",
|
||||
"SettingsUnlinkAccounts_Title": "Премахване на връзката между акаунтите",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Добави акаунт",
|
||||
"WelcomeWindow_GetStartedDescription": "Добавете своя акаунт Outlook, Gmail или IMAP, за да започнете с Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Импортирайте от вашия Wino акаунт.",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Импортирай от JSON файл",
|
||||
"WelcomeWindow_ImportInProgress": "Импортиране на синхронизирани настройки и акаунти...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Не са намерени синхронизирани акаунти във вашия Wino акаунт. Ако имаше налични настройки, те са възстановени. Използвайте Започнете, за да добавите акаунт ръчно.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} синхронизирани акаунти вече са налични на това устройство. Ако е необходимо, използвайте Започнете, за да добавите още акаунт ръчно.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Паролите, токените и друга чувствителна информация не се синхронират.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Импортираните акаунти на друг компютър ще трябва да влезете отново, преди да могат да се използват.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Експортът на избраните от вас Wino данни...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Прехвърляне с JSON файл",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Импортирайте от или експортирайте към локален JSON файл. Пароли, токени и друга чувствителна информация не са включени.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Импортиране",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Експортиране",
|
||||
"WinoAccount_Management_LocalDataSaved": "Запазихте експортираните данни на Wino в {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Избраният JSON файл не съдържа валиден Wino експорт.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino не успя да зареди най-новата информация за Wino акаунта.",
|
||||
"WinoAccount_Management_ActionFailed": "Заявката за Wino акаунт не може да бъде завършена.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino акаунт",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Завършване на настройката",
|
||||
"ProviderSelection_Title": "Изберете вашия имейл доставчик",
|
||||
"ProviderSelection_Subtitle": "Изберете доставчик по-долу, за да добавите вашия имейл акаунт към Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Стъпка {0} от 3",
|
||||
"ProviderSelection_IdentityTitle": "Идентичност на акаунта",
|
||||
"ProviderSelection_IdentityDescription": "Изберете как тази акаунт се появява във Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Провайдер",
|
||||
"ProviderSelection_ProviderSectionDescription": "Изберете услугата, която искате да свържете.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Използвайте този акаунт за",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Изберете дали искате поща, календар или и двете.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "В следващата стъпка сигурното вписване ще свърже акаунта ви. Ако активирате календар, Wino ще свърже автоматично Outlook Calendar или Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "В следващата стъпка ще въведете идентификационните данни за доставчика. Пощата използва IMAP/SMTP, а календарът може да използва CalDAV или да остане локално на това устройство.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "В следващата стъпка ще въведете данните за сървъра си. Пощата използва IMAP/SMTP, а календарът може да използва CalDAV или да остане локално на това устройство.",
|
||||
"ProviderSelection_AccountNameHeader": "Име на акаунт",
|
||||
"ProviderSelection_AccountNamePlaceholder": "напр. Personal, Work",
|
||||
"ProviderSelection_UseForMail": "Поща",
|
||||
"ProviderSelection_UseForCalendar": "Календар",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Изберете поне една възможност преди да продължите.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Ако продължите само с календар, следващата страница няма да изисква имейл адрес.",
|
||||
"ProviderSelection_DisplayNameHeader": "Име за показване",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "напр. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Имейл адрес",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Selecciona un calendari per configurar la seva configuració.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Sincronització inicial",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino ha sincronitzat els teus correus fins a {0} enrere.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Característiques connectades",
|
||||
"AccountDetailsPage_CapabilityDescription": "Trieu si aquest compte s'utilitza per a correu, calendari o tots dos. Habilitar una nova funció pot exigir que tornis a iniciar sessió.",
|
||||
"AccountCapability_MailOnly": "Només correu",
|
||||
"AccountCapability_CalendarOnly": "Només calendari",
|
||||
"AccountCapability_MailAndCalendar": "Correu i Calendari",
|
||||
"AddHyperlink": "Add",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Apply Theme",
|
||||
"Buttons_PopOut": "Obrir en una finestra separada",
|
||||
"Buttons_Browse": "Browse",
|
||||
"Buttons_Back": "Enrere",
|
||||
"Buttons_Cancel": "Cancel",
|
||||
"Buttons_Close": "Close",
|
||||
"Buttons_Copy": "Copy",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Eliminar sèrie recurrent",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "This draft will be discarded. Do you want to continue?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Discard Draft",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Encara hi ha un esborrany obert. Desa'l abans de tancar la finestra?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Tanca la finestra",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Missing Subject",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Message has no subject. Do you want to continue?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "No message selected",
|
||||
"NoMessageCrieteria": "No messages match your search criteria",
|
||||
"NoMessageEmptyFolder": "This folder is empty",
|
||||
"MailEmptyState_Title": "No hi ha cap compte habilitat per al correu",
|
||||
"MailEmptyState_Message": "Tens comptes connectats per al calendari, però cap d'ells està habilitat per al correu. Afegeix un compte de correu o actualitza un compte existent per utilitzar el correu.",
|
||||
"MailEmptyState_AddAccount": "Afegeix un compte",
|
||||
"MailEmptyState_ManageAccounts": "Gestiona els comptes",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Delete this account",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Mostra la comanda per buidar la carpeta de correu brossa. Aquesta acció no demanarà confirmació i eliminarà immediatament tots els correus de la carpeta de correu brossa.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Mostra la comanda per buidar la carpeta de correu brossa",
|
||||
"SettingsDiagnostics_Description": "For developers",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Organize messages into conversation threads.",
|
||||
"SettingsThreads_Title": "Conversation Threading",
|
||||
"SettingsThreads_Enabled_Description": "Agrupa els missatges relacionats en una única conversa.",
|
||||
"SettingsThreads_Enabled_Title": "Activa el fil de converses",
|
||||
"SettingsThreadOrder_Description": "Trieu com s'ordenen els elements dins d'un fil de conversa.",
|
||||
"SettingsThreadOrder_Title": "Ordenació d'elements del fil de conversa",
|
||||
"SettingsThreadOrder_LastItemFirst": "Últim element primer",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Primer element primer",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Comença afegint un compte",
|
||||
"WelcomeWindow_GetStartedDescription": "Afegeix el teu compte de Outlook, Gmail o IMAP per començar a utilitzar Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importa des del teu compte Wino.",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importa des d'un fitxer JSON",
|
||||
"WelcomeWindow_ImportInProgress": "S'importen les teves preferències i comptes sincronitzats.",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "No s'han trobat comptes sincronitzats al teu compte Wino. Si hi havia preferències disponibles, s'han restaurat. Utilitza Comença per afegir un compte manualment.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} comptes sincronitzats ja estan disponibles en aquest dispositiu. Utilitza Comença per afegir un altre compte manualment si és necessari.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Contrasenyes, tokens i altra informació sensible no es sincronitzen.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Els comptes importats en un altre PC encara necessitaran que inicis sessió de nou abans de poder utilitzar-los.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "S'està exportant les teves dades de Wino seleccionades.",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transferir amb un fitxer JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importa des d'un fitxer JSON local o exporta a un fitxer JSON local. Contrasenyes, tokens i altra informació sensible no s'inclou.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importa",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exporta",
|
||||
"WinoAccount_Management_LocalDataSaved": "Les teves dades exportades de Wino s'han desat a {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "El fitxer JSON seleccionat no conté una exportació vàlida de Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "No s'ha pogut carregar la darrera informació del compte Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "La sol·licitud del compte Wino no s'ha pogut completar.",
|
||||
"WinoAccount_SettingsSection_Title": "Compte Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Finalitza la configuració",
|
||||
"ProviderSelection_Title": "Trieu el vostre proveïdor de correu electrònic",
|
||||
"ProviderSelection_Subtitle": "Seleccioneu un proveïdor a continuació per afegir el vostre compte de correu a Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Pas {0} de 3",
|
||||
"ProviderSelection_IdentityTitle": "Identitat del compte",
|
||||
"ProviderSelection_IdentityDescription": "Trieu com apareix aquest compte dins de Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Proveïdor",
|
||||
"ProviderSelection_ProviderSectionDescription": "Trieu el servei que voleu connectar.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Utilitza aquest compte per a",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Trieu si vols correu, calendar, o tots dos.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "En el pròxim pas, l'inici de sessió segur connectarà el vostre compte. Si activeu el calendari, Wino també connectarà automàticament Outlook Calendar o Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "En el proper pas, introduïu les vostres credencials del proveïdor. El correu fa servir IMAP/SMTP, i el calendari pot utilitzar CalDAV o quedar-se local en aquest dispositiu.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "En el proper pas, introduïu les dades del vostre servidor. El correu fa servir IMAP/SMTP, i el calendari pot utilitzar CalDAV o quedar-se local en aquest dispositiu.",
|
||||
"ProviderSelection_AccountNameHeader": "Nom del compte",
|
||||
"ProviderSelection_AccountNamePlaceholder": "p. ex. Personal, Treball",
|
||||
"ProviderSelection_UseForMail": "Correu",
|
||||
"ProviderSelection_UseForCalendar": "Calendari",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Trieu almenys una capacitat abans de continuar.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Si continues amb només el calendari, la següent pàgina no requerirà una adreça de correu.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nom a mostrar",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "p. ex. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Adreça de correu electrònic",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Vyberte kalendář pro konfiguraci jeho nastavení.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "První synchronizace",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino synchronizoval vaše e-maily až do {0}.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Připojené funkce",
|
||||
"AccountDetailsPage_CapabilityDescription": "Vyberte, zda se tento účet použije pro poštu, kalendář nebo obojí. Aktivace nové funkce může vyžadovat opětovné přihlášení.",
|
||||
"AccountCapability_MailOnly": "Pouze pošta",
|
||||
"AccountCapability_CalendarOnly": "Pouze kalendář",
|
||||
"AccountCapability_MailAndCalendar": "Pošta a Kalendář",
|
||||
"AddHyperlink": "Přidat",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Synchronizace na pozadí",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Použít motiv",
|
||||
"Buttons_PopOut": "Otevřít v novém okně",
|
||||
"Buttons_Browse": "Procházet",
|
||||
"Buttons_Back": "Zpět",
|
||||
"Buttons_Cancel": "Zrušit",
|
||||
"Buttons_Close": "Zavřít",
|
||||
"Buttons_Copy": "Kopírovat",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Smazat opakující se sérii",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Tento koncept bude zahozen. Chcete pokračovat?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Zahodit koncept",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Rozpracovaný návrh je stále otevřený. Chcete jej před zavřením okna uložit?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Zavřít okno",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Chybějící Předmět",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Zpráva nemá Předmět. Chcete pokračovat?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Nebyly vybrány žádné zprávy",
|
||||
"NoMessageCrieteria": "Žádná zpráva neodpovídá kritériím vyhledávání",
|
||||
"NoMessageEmptyFolder": "Tato složka je prázdná",
|
||||
"MailEmptyState_Title": "Žádné účty povolené pro poštu",
|
||||
"MailEmptyState_Message": "Máte účty pro kalendář, ale žádný z nich není povolen pro poštu. Přidejte poštovní účet nebo upravte existující účet, aby používal poštu.",
|
||||
"MailEmptyState_AddAccount": "Přidat účet",
|
||||
"MailEmptyState_ManageAccounts": "Spravovat účty",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Vyzkoušejte novou verzi {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Smazat tento účet",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Ochrana proti trvalému smazání",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Zobrazit příkaz pro vyprázdnění složky ve složkách Nevyžádaná pošta (spam). Tato akce nepožádá o potvrzení a ihned smaže všechny e-maily ve složce spamu.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Zobrazit příkaz pro vyprázdnění složky spamu",
|
||||
"SettingsDiagnostics_Description": "Pro vývojáře",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Uspořádat zprávy do konverzačních vláken.",
|
||||
"SettingsThreads_Title": "Vlákna konverzací",
|
||||
"SettingsThreads_Enabled_Description": "Seskupovat související zprávy do jedné konverzace.",
|
||||
"SettingsThreads_Enabled_Title": "Povolit vlákna konverací",
|
||||
"SettingsThreadOrder_Description": "Zvolte, jak budou položky seřazeny v konverzačním vláknu.",
|
||||
"SettingsThreadOrder_Title": "Řazení položek konverzačního vlákna",
|
||||
"SettingsThreadOrder_LastItemFirst": "Poslední položka jako první",
|
||||
"SettingsThreadOrder_FirstItemFirst": "První položka jako první",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Rozpojit účty",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Začněte přidáním účtu",
|
||||
"WelcomeWindow_GetStartedDescription": "Přidejte svůj účet Outlook, Gmail nebo IMAP a začněte s Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importovat z vašeho Wino účtu",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importovat z JSON souboru",
|
||||
"WelcomeWindow_ImportInProgress": "Importuji synchronizované preference a účty...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Ve vašem Wino účtu nebyly nalezeny žádné synchronizované účty. Pokud byly k dispozici preference, byly obnoveny. Pro ruční přidání účtu použijte možnost Začít.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} synchronizovaných účtů je již k dispozici na tomto zařízení. Pokud je potřeba, použijte možnost Začít pro ruční přidání dalšího účtu.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Hesla, tokeny a další citlivé údaje se nesynchronizují.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Importované účty na jiném počítači budou i nadále vyžadovat opětovné přihlášení před jejich použitím.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Probíhá export vybraných dat Wino.",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Přenos pomocí JSON souboru",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importovat z nebo exportovat do místního JSON souboru. Hesla, tokeny a další citlivé údaje nejsou zahrnuty.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importovat",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exportovat",
|
||||
"WinoAccount_Management_LocalDataSaved": "Exportovaná data Wino byla uložena do {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Vybraný JSON soubor neobsahuje platný export Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Nepodařilo se načíst nejnovější informace o účtu Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "Požadavek na účet Wino nebyl dokončen.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino účet",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Dokončit nastavení",
|
||||
"ProviderSelection_Title": "Zvolte poskytovatele e-mailu",
|
||||
"ProviderSelection_Subtitle": "Vyberte níže uvedeného poskytovatele a přidejte si svůj e-mailový účet do Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Krok {0} z 3",
|
||||
"ProviderSelection_IdentityTitle": "Identita účtu",
|
||||
"ProviderSelection_IdentityDescription": "Zvolte, jak se tento účet bude zobrazovat ve Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Poskytovatel",
|
||||
"ProviderSelection_ProviderSectionDescription": "Vyberte službu, ke které se chcete připojit.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Použít tento účet pro",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Vyberte, zda chcete používat poštu, kalendář, nebo obojí.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "V dalším kroku zabezpečené přihlášení propojí váš účet. Pokud povolíte kalendář, Wino také automaticky propojí Kalendář Outlooku nebo Kalendář Google.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "V dalším kroku zadáte údaje poskytovatele. Pošta používá IMAP/SMTP a kalendář může používat CalDAV nebo zůstat místní na tomto zařízení.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "V dalším kroku zadáte údaje o serveru. Pošta používá IMAP/SMTP a kalendář může používat CalDAV nebo zůstat místní na tomto zařízení.",
|
||||
"ProviderSelection_AccountNameHeader": "Název účtu",
|
||||
"ProviderSelection_AccountNamePlaceholder": "např. Osobní, Pracovní",
|
||||
"ProviderSelection_UseForMail": "Pošta",
|
||||
"ProviderSelection_UseForCalendar": "Kalendář",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Zvolte alespoň jednu možnost před pokračováním.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Pokud budete pokračovat pouze s kalendářem, další stránka nebude vyžadovat e-mailovou adresu.",
|
||||
"ProviderSelection_DisplayNameHeader": "Zobrazené jméno",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "např. Jan Novák",
|
||||
"ProviderSelection_EmailHeader": "E-mailová adresa",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Vælg en kalender for at konfigurere dens indstillinger.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Første synkronisering",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino har synkroniseret dine mails helt tilbage til {0}.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Forbundne funktioner",
|
||||
"AccountDetailsPage_CapabilityDescription": "Vælg om denne konto skal bruges til mail, kalender eller begge dele. Hvis du aktiverer en ny funktion, kan det bede dig logge ind igen.",
|
||||
"AccountCapability_MailOnly": "Kun mail",
|
||||
"AccountCapability_CalendarOnly": "Kun kalender",
|
||||
"AccountCapability_MailAndCalendar": "Mail og Kalender",
|
||||
"AddHyperlink": "Add",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Apply Theme",
|
||||
"Buttons_PopOut": "Pop ud",
|
||||
"Buttons_Browse": "Browse",
|
||||
"Buttons_Back": "Tilbage",
|
||||
"Buttons_Cancel": "Cancel",
|
||||
"Buttons_Close": "Close",
|
||||
"Buttons_Copy": "Copy",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Slet tilbagevendende serie",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "This draft will be discarded. Do you want to continue?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Discard Draft",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Et udkast er stadig åbent. Gem det, før du lukker vinduet?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Luk vinduet",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Missing Subject",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Message has no subject. Do you want to continue?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "No message selected",
|
||||
"NoMessageCrieteria": "No messages match your search criteria",
|
||||
"NoMessageEmptyFolder": "This folder is empty",
|
||||
"MailEmptyState_Title": "Ingen konti med mail aktiveret",
|
||||
"MailEmptyState_Message": "Du har konti tilsluttet til kalender, men ingen af dem er aktiveret til mail. Tilføj en mailkonto eller opdater en eksisterende konto for at bruge mail.",
|
||||
"MailEmptyState_AddAccount": "Tilføj konto",
|
||||
"MailEmptyState_ManageAccounts": "Administrer konti",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Delete this account",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Vis kommandoen til at tøm mappen i Junk/Spam-mapperne. Denne handling vil ikke bede om bekræftelse og vil straks slette alle mails i spam-mappen.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Vis kommandoen til at tømme spam-mappen",
|
||||
"SettingsDiagnostics_Description": "For developers",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Organize messages into conversation threads.",
|
||||
"SettingsThreads_Title": "Conversation Threading",
|
||||
"SettingsThreads_Enabled_Description": "Grupper relaterede meddelelser i en enkelt samtale.",
|
||||
"SettingsThreads_Enabled_Title": "Aktivér samtale-trådning",
|
||||
"SettingsThreadOrder_Description": "Vælg hvordan elementer ordnes i en samtale-tråd.",
|
||||
"SettingsThreadOrder_Title": "Sortering af tråd-elementer",
|
||||
"SettingsThreadOrder_LastItemFirst": "Sidste element først",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Første element først",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Kom i gang ved at tilføje en konto",
|
||||
"WelcomeWindow_GetStartedDescription": "Tilføj din Outlook-, Gmail- eller IMAP-konto for at komme i gang med Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importer fra din Wino-konto",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importér fra en JSON-fil",
|
||||
"WelcomeWindow_ImportInProgress": "Importerere dine synkroniserede præferencer og konti...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Ingen synkroniserede konti blev fundet i din Wino-konto. Hvis præferencer var tilgængelige, blev de gendannet. Brug Kom i gang til manuelt at tilføje en konto.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} synkroniserede konti er allerede tilgængelige på denne enhed. Brug Kom i gang til manuelt at tilføje en anden konto, hvis det er nødvendigt.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Adgangskoder, tokens og andre følsomme oplysninger synkroniseres ikke.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Importerede konti på en anden PC kræver stadig, at du logger ind igen, før de kan bruges.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Eksporterer dine valgte Wino-data...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Overfør data med en JSON-fil",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importér fra eller eksporter til en lokal JSON-fil. Adgangskoder, tokens og anden følsom information er ikke inkluderet.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importér",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Eksportér",
|
||||
"WinoAccount_Management_LocalDataSaved": "Dine eksporterede Wino-data er gemt til {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Den valgte JSON-fil indeholder ikke en gyldig Wino-eksport.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino kunne ikke indlæse de seneste oplysninger om Wino-kontoen.",
|
||||
"WinoAccount_Management_ActionFailed": "Anmodningen til Wino-kontoen kunne ikke gennemføres.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino-konto",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Afslut opsætningen",
|
||||
"ProviderSelection_Title": "Vælg din e-mail-udbyder",
|
||||
"ProviderSelection_Subtitle": "Vælg en udbyder nedenfor for at tilføje din e-mail-konto til Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Trin {0} af 3",
|
||||
"ProviderSelection_IdentityTitle": "Kontoidentitet",
|
||||
"ProviderSelection_IdentityDescription": "Vælg hvordan denne konto vises i Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Udbyder",
|
||||
"ProviderSelection_ProviderSectionDescription": "Vælg den tjeneste, du vil oprette forbindelse til.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Brug denne konto til",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Vælg om du vil bruge mail, kalender eller begge dele.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "På næste trin vil et sikkert login forbinde din konto. Hvis du aktiverer kalender, vil Wino også automatisk oprette forbindelse til Outlook Kalender eller Google Kalender.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "På næste trin indtaster du dine udbyderoplysninger. Mail bruger IMAP/SMTP, og kalender kan bruge CalDAV eller forblive lokalt på denne enhed.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "På næste trin indtaster du dine serveroplysninger. Mail bruger IMAP/SMTP, og kalender kan bruge CalDAV eller forblive lokalt på denne enhed.",
|
||||
"ProviderSelection_AccountNameHeader": "Konto navn",
|
||||
"ProviderSelection_AccountNamePlaceholder": "f.eks. Personlig, Arbejde",
|
||||
"ProviderSelection_UseForMail": "Mail",
|
||||
"ProviderSelection_UseForCalendar": "Kalender",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Vælg mindst én funktion, før du fortsætter.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Hvis du fortsætter med kalender alene, kræver næste side ikke en e-mailadresse.",
|
||||
"ProviderSelection_DisplayNameHeader": "Vis navn",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "f.eks. John Doe",
|
||||
"ProviderSelection_EmailHeader": "E-mailadresse",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Wähle einen Kalender aus, um dessen Einstellungen zu konfigurieren.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Erste Synchronisierung",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino hat Ihre E-Mails bis {0} synchronisiert.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Verbundene Funktionen",
|
||||
"AccountDetailsPage_CapabilityDescription": "Wählen Sie aus, ob dieses Konto für Mail, Kalender oder beides verwendet wird. Wenn Sie eine neue Funktion aktivieren, müssen Sie sich möglicherweise erneut anmelden.",
|
||||
"AccountCapability_MailOnly": "Nur Mail",
|
||||
"AccountCapability_CalendarOnly": "Nur Kalender",
|
||||
"AccountCapability_MailAndCalendar": "Mail + Kalender",
|
||||
"AddHyperlink": "Hinzufügen",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Hintergrundsynchronisierung",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Die Anwendung wird beim Start von Windows nicht gestartet.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Thema anwenden",
|
||||
"Buttons_PopOut": "In neues Fenster öffnen",
|
||||
"Buttons_Browse": "Durchsuchen",
|
||||
"Buttons_Back": "Zurück",
|
||||
"Buttons_Cancel": "Abbrechen",
|
||||
"Buttons_Close": "Schließen",
|
||||
"Buttons_Copy": "Kopieren",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Wiederkehrende Serie löschen",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Dieser Entwurf wird verworfen. Möchten Sie fortfahren?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Entwurf verwerfen",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Ein Entwurf ist noch geöffnet. Speichern Sie ihn, bevor Sie das Fenster schließen?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Fenster schließen",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Betreff fehlt",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Nachricht hat keinen Betreff. Möchten Sie fortfahren?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Sie können den Autostart unter Einstellungen -> App-Einstellungen aktivieren.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Keine Nachricht ausgewählt",
|
||||
"NoMessageCrieteria": "Keine Nachrichten entsprechen Ihren Suchkriterien",
|
||||
"NoMessageEmptyFolder": "Dieser Ordner ist leer",
|
||||
"MailEmptyState_Title": "Keine Mail-fähigen Konten",
|
||||
"MailEmptyState_Message": "Sie haben Konten verbunden, die für den Kalender verwendet werden, aber keines davon ist für Mail aktiviert. Fügen Sie ein Mail-Konto hinzu oder aktualisieren Sie ein bestehendes Konto, um Mail zu verwenden.",
|
||||
"MailEmptyState_AddAccount": "Konto hinzufügen",
|
||||
"MailEmptyState_ManageAccounts": "Konten verwalten",
|
||||
"Notifications_MultipleNotificationsMessage": "Sie haben {0} neue Nachrichten.",
|
||||
"Notifications_MultipleNotificationsTitle": "Neue Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Neue Version {0} herunterladen",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Dieses Konto löschen",
|
||||
"SettingsDeleteProtection_Description": "Sollte Wino jedes Mal nachfragen, wenn Sie eine Mail mit Umschalten + Entfernen permanent löschen möchten?",
|
||||
"SettingsDeleteProtection_Title": "Schutz vor permanenter Löschung",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Zeige den Befehl zum Leeren des Junk-/Spam-Ordners an. Diese Aktion wird keine Bestätigung erfordern und löscht sofort alle E-Mails im Spam-Ordner.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Befehl zum Leeren des Spam-Ordners anzeigen",
|
||||
"SettingsDiagnostics_Description": "Für Entwickler",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Teilen Sie diese ID mit den Entwicklern, wenn Sie um Hilfe bei Problemen in Wino Mail gebeten werden.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnose-ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskleisten-Symbol",
|
||||
"SettingsThreads_Description": "Nachrichten in Unterhaltungsthreads organisieren.",
|
||||
"SettingsThreads_Title": "Unterhaltungsthreading",
|
||||
"SettingsThreads_Enabled_Description": "Verwandte Nachrichten zu einer einzigen Unterhaltung bündeln.",
|
||||
"SettingsThreads_Enabled_Title": "Unterhaltungen bündeln aktivieren",
|
||||
"SettingsThreadOrder_Description": "Wählen Sie aus, wie Elemente in einem Thread sortiert werden.",
|
||||
"SettingsThreadOrder_Title": "Thread-Elemente sortieren",
|
||||
"SettingsThreadOrder_LastItemFirst": "Letztes Element zuerst",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Erstes Element zuerst",
|
||||
"SettingsUnlinkAccounts_Description": "Entfernen Sie den Link zwischen den Konten. Dies wird Ihre Konten nicht löschen.",
|
||||
"SettingsUnlinkAccounts_Title": "Konten trennen",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Aktions-Labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Konto hinzufügen und loslegen",
|
||||
"WelcomeWindow_GetStartedDescription": "Fügen Sie Ihr Outlook-, Gmail- oder IMAP-Konto hinzu, um mit Wino Mail zu beginnen.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Aus Ihrem Wino-Konto importieren",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Aus einer JSON-Datei importieren",
|
||||
"WelcomeWindow_ImportInProgress": "Import Ihrer synchronisierten Einstellungen und Konten läuft.",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Es wurden keine synchronisierten Konten in Ihrem Wino-Konto gefunden. Falls Einstellungen verfügbar waren, wurden sie wiederhergestellt. Verwenden Sie Loslegen, um manuell ein Konto hinzuzufügen.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} synchronisierte Konten sind auf diesem Gerät bereits verfügbar. Verwenden Sie Loslegen, um bei Bedarf manuell ein weiteres Konto hinzuzufügen.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Passwörter, Tokens und andere sensible Informationen werden nicht synchronisiert.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Auf einem anderen PC importierte Konten müssen sich weiterhin erneut anmelden, bevor sie verwendet werden können.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exportieren Sie Ihre ausgewählten Wino-Daten.",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Übertragung mit einer JSON-Datei",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importieren Sie aus oder exportieren Sie in eine lokale JSON-Datei. Passwörter, Tokens und andere sensible Informationen sind nicht enthalten.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importieren",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exportieren",
|
||||
"WinoAccount_Management_LocalDataSaved": "Ihre exportierten Wino-Daten wurden in {0} gespeichert.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Die ausgewählte JSON-Datei enthält keinen gültigen Wino-Export.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino konnte die neuesten Informationen zum Wino-Konto nicht laden.",
|
||||
"WinoAccount_Management_ActionFailed": "Die Wino-Konto-Anfrage konnte nicht abgeschlossen werden.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino-Konto",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Einrichtung abschließen",
|
||||
"ProviderSelection_Title": "E-Mail-Anbieter auswählen",
|
||||
"ProviderSelection_Subtitle": "Wählen Sie unten einen Anbieter aus, um Ihr E-Mail-Konto zu Wino Mail hinzuzufügen.",
|
||||
"ProviderSelection_StepProgress": "Schritt {0} von 3",
|
||||
"ProviderSelection_IdentityTitle": "Kontenidentität",
|
||||
"ProviderSelection_IdentityDescription": "Wählen Sie aus, wie dieses Konto in Wino angezeigt wird.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Anbieter",
|
||||
"ProviderSelection_ProviderSectionDescription": "Wählen Sie den Dienst aus, den Sie verbinden möchten.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Verwendung dieses Kontos für",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Wählen Sie aus, ob Sie Mail, Kalender oder beides verwenden möchten.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Im nächsten Schritt wird sicheres Sign-in Ihr Konto verbinden. Wenn Sie Kalender aktivieren, verbindet Wino automatisch Outlook-Kalender oder Google Kalender.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Im nächsten Schritt geben Sie Ihre Anmeldeinformationen des Anbieters ein. Mail verwendet IMAP/SMTP, und Kalender kann CalDAV verwenden oder lokal auf diesem Gerät bleiben.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Im nächsten Schritt geben Sie Ihre Serverdaten ein. Mail verwendet IMAP/SMTP, und Kalender kann CalDAV verwenden oder lokal auf diesem Gerät bleiben.",
|
||||
"ProviderSelection_AccountNameHeader": "Kontoname",
|
||||
"ProviderSelection_AccountNamePlaceholder": "z. B. Privat, Arbeit",
|
||||
"ProviderSelection_UseForMail": "Mail",
|
||||
"ProviderSelection_UseForCalendar": "Kalender",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Wählen Sie vor dem Fortfahren mindestens eine Funktion.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Wenn Sie nur Kalender verwenden, ist auf der nächsten Seite keine E-Mail-Adresse erforderlich.",
|
||||
"ProviderSelection_DisplayNameHeader": "Anzeigename",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "z. B. John Doe",
|
||||
"ProviderSelection_EmailHeader": "E-Mail-Adresse",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Επιλέξτε ένα ημερολόγιο για να ορίσετε τις ρυθμίσεις του.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Αρχικός συγχρονισμός",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Ο Wino συγχρόνισε τα μηνύματά σας μέχρι το {0} πίσω.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Συνδεδεμένες δυνατότητες",
|
||||
"AccountDetailsPage_CapabilityDescription": "Επιλέξτε αν ο λογαριασμός αυτός χρησιμοποιείται για αλληλογραφία, ημερολόγιο ή και τα δύο. Η ενεργοποίηση μιας νέας δυνατότητας ενδέχεται να σας ζητήσει να συνδεθείτε ξανά.",
|
||||
"AccountCapability_MailOnly": "Μόνο Αλληλογραφία",
|
||||
"AccountCapability_CalendarOnly": "Μόνο Ημερολόγιο",
|
||||
"AccountCapability_MailAndCalendar": "Αλληλογραφία + Ημερολόγιο",
|
||||
"AddHyperlink": "Προσθήκη",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Συγχρονισμός Παρασκηνίου",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Η εφαρμογή δεν έχει οριστεί για εκκίνηση κατά την έναρξη των Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Εφαρμογή Θέματος",
|
||||
"Buttons_PopOut": "Άνοιγμα σε νέο παράθυρο",
|
||||
"Buttons_Browse": "Περιήγηση",
|
||||
"Buttons_Back": "Πίσω",
|
||||
"Buttons_Cancel": "Ακύρωση",
|
||||
"Buttons_Close": "Κλείσιμο",
|
||||
"Buttons_Copy": "Αντιγραφή",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Διαγραφή Επαναλαμβανόμενης Σειράς",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Αυτό το πρόχειρο θα απορριφθεί. Θέλετε να συνεχίσετε;",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Απόρριψη προχείρου",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Ένα προσχέδιο παραμένει ανοιχτό. Αποθηκεύστε το προτού κλείσετε το παράθυρο;",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Κλείσιμο Παραθύρου",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Χωρίς Θέμα",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Το μήνυμα δεν έχει θέμα. Θέλετε να συνεχίσετε;",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Μπορείτε να ενεργοποιήσετε την εκκίνηση κατά την έναρξη από τις Ρυθμίσεις -> Προτιμήσεις εφαρμογών.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Δεν επιλέχθηκε μήνυμα",
|
||||
"NoMessageCrieteria": "Κανένα μήνυμα δεν ταιριάζει με τα κριτήρια αναζήτησής σας",
|
||||
"NoMessageEmptyFolder": "Αυτός ο φάκελος είναι κενός",
|
||||
"MailEmptyState_Title": "Δεν υπάρχουν λογαριασμοί με δυνατότητα αλληλογραφίας",
|
||||
"MailEmptyState_Message": "Έχετε λογαριασμούς συνδεδεμένους για το ημερολόγιο, αλλά κανένας από αυτούς δεν είναι ενεργοποιημένος για αλληλογραφία. Προσθέστε έναν λογαριασμό αλληλογραφίας ή ενημερώστε έναν υπάρχοντα λογαριασμό ώστε να χρησιμοποιεί αλληλογραφία.",
|
||||
"MailEmptyState_AddAccount": "Προσθήκη λογαριασμού",
|
||||
"MailEmptyState_ManageAccounts": "Διαχείριση λογαριασμών",
|
||||
"Notifications_MultipleNotificationsMessage": "Έχετε {0} νέα μηνύματα.",
|
||||
"Notifications_MultipleNotificationsTitle": "Νέα Αλληλογραφία",
|
||||
"Notifications_WinoUpdatedMessage": "Ρίξτε μια ματιά στη νέα έκδοση {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Διαγραφή αυτού του λογαριασμού",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Προστασία Μόνιμης Διαγραφής",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Εμφάνιση της εντολής εκκένωσης φακέλου στους φακέλους Junk/Spam. Αυτή η ενέργεια δεν θα ζητήσει επιβεβαίωση και θα διαγράψει αμέσως όλα τα ηλεκτρονικά μηνύματα στον φάκελο Spam.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Εμφάνιση εντολής εκκένωσης φακέλου Spam",
|
||||
"SettingsDiagnostics_Description": "Για προγραμματιστές",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Μοιραστείτε αυτό το ID με τους προγραμματιστές όταν σας ζητηθεί να λάβετε βοήθεια για τα θέματα που αντιμετωπίζετε στο Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Διαγνωστικό ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Σήμα Γραμμής Εργασιών",
|
||||
"SettingsThreads_Description": "Οργάνωση μηνυμάτων σε νήματα συνομιλίας.",
|
||||
"SettingsThreads_Title": "Νηματοποίηση Συζήτησης",
|
||||
"SettingsThreads_Enabled_Description": "Ομαδοποιήστε σχετικά μηνύματα σε μια ενιαία συνομιλία.",
|
||||
"SettingsThreads_Enabled_Title": "Ενεργοποίηση ομαδοποίησης συνομιλιών",
|
||||
"SettingsThreadOrder_Description": "Επιλέξτε πώς ταξινομούνται τα στοιχεία εντός μιας συνομιλίας.",
|
||||
"SettingsThreadOrder_Title": "Ταξινόμηση στοιχείων συνομιλίας",
|
||||
"SettingsThreadOrder_LastItemFirst": "Τελευταίο στοιχείο πρώτο",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Πρώτο στοιχείο πρώτο",
|
||||
"SettingsUnlinkAccounts_Description": "Αφαιρέστε τη σύνδεση μεταξύ των λογαριασμών. Δε θα διαγράψει τους λογαριασμούς σας.",
|
||||
"SettingsUnlinkAccounts_Title": "Αποδέσμευση Λογαριασμών",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Ξεκινήστε προσθέτοντας έναν λογαριασμό",
|
||||
"WelcomeWindow_GetStartedDescription": "Προσθέστε τον λογαριασμό σας Outlook, Gmail ή IMAP για να ξεκινήσετε με το Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Εισαγωγή από τον λογαριασμό σας Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Εισαγωγή από αρχείο JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Εισαγωγή των συγχρονισμένων προτιμήσεων και λογαριασμών σας...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Δεν βρέθηκαν συγχρονισμένοι λογαριασμοί σε αυτόν τον λογαριασμό Wino. Εάν υπήρχαν προτιμήσεις, επαναφέρθηκαν. Χρησιμοποιήστε το Ξεκινήστε για να προσθέσετε έναν λογαριασμό χειροκίνητα.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} συγχρονισμένοι λογαριασμοί είναι ήδη διαθέσιμοι σε αυτήν τη συσκευή. Χρησιμοποιήστε το Ξεκινήστε για να προσθέσετε ακόμη έναν λογαριασμό χειροκίνητα εάν χρειάζεται.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Κωδικοί πρόσβασης, διακριτικά και άλλες ευαίσθητες πληροφορίες δεν συγχρονίζονται.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Οι εισαγόμενοι λογαριασμοί σε άλλον υπολογιστή θα χρειαστεί να συνδεθείτε ξανά πριν μπορέσουν να χρησιμοποιηθούν.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Εξάγονται τα επιλεγμένα δεδομένα Wino.",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Μεταφορά με αρχείο JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Εισαγωγή από ή εξαγωγή σε ένα τοπικό αρχείο JSON. Οι κωδικοί πρόσβασης, τα διακριτικά και άλλες ευαίσθητες πληροφορίες δεν περιλαμβάνονται.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Εισαγωγή",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Εξαγωγή",
|
||||
"WinoAccount_Management_LocalDataSaved": "Αποθηκεύσατε τα εξαγόμενα δεδομένα Wino στο {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Το επιλεγμένο αρχείο JSON δεν περιέχει έγκυρη εξαγωγή του Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Το Wino δεν μπόρεσε να φορτώσει τις τελευταίες πληροφορίες του Λογαριασμού Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "Το αίτημα Λογαριασμού Wino δεν μπόρεσε να ολοκληρωθεί.",
|
||||
"WinoAccount_SettingsSection_Title": "Λογαριασμός Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Ολοκλήρωση Ρύθμισης",
|
||||
"ProviderSelection_Title": "Επιλέξτε τον πάροχο email σας",
|
||||
"ProviderSelection_Subtitle": "Επιλέξτε έναν πάροχο παρακάτω για να προσθέσετε τον λογαριασμό email σας στο Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Βήμα {0} από 3",
|
||||
"ProviderSelection_IdentityTitle": "Ταυτότητα λογαριασμού",
|
||||
"ProviderSelection_IdentityDescription": "Επιλέξτε πώς θα εμφανίζεται αυτός ο λογαριασμός μέσα στο Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Πάροχος",
|
||||
"ProviderSelection_ProviderSectionDescription": "Επιλέξτε την υπηρεσία που θέλετε να συνδέσετε.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Χρήση αυτού του λογαριασμού για",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Επιλέξτε αν θέλετε αλληλογραφία, ημερολόγιο ή και τα δύο.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Στο επόμενο βήμα, μια ασφαλής σύνδεση θα συνδέσει τον λογαριασμό σας. Εάν ενεργοποιήσετε το ημερολόγιο, το Wino θα συνδέσει αυτόματα το Outlook Calendar ή το Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Στο επόμενο βήμα θα εισάγετε τα διαπιστευτήρια παροχέα σας. Το IMAP/SMTP χρησιμοποιείται για το mail, και το ημερολόγιο μπορεί να χρησιμοποιήσει CalDAV ή να παραμείνει τοπικά σε αυτή τη συσκευή.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Στο επόμενο βήμα θα εισάγετε τις ρυθμίσεις διακομιστή σας. Το Mail χρησιμοποιεί IMAP/SMTP, και το ημερολόγιο μπορεί να χρησιμοποιήσει CalDAV ή να παραμείνει τοπικά σε αυτή τη συσκευή.",
|
||||
"ProviderSelection_AccountNameHeader": "Όνομα Λογαριασμού",
|
||||
"ProviderSelection_AccountNamePlaceholder": "π.χ. Προσωπικό, Εργασία",
|
||||
"ProviderSelection_UseForMail": "Αλληλογραφία",
|
||||
"ProviderSelection_UseForCalendar": "Ημερολόγιο",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Επιλέξτε τουλάχιστον μια δυνατότητα πριν συνεχίσετε.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Αν συνεχίσετε μόνο με το ημερολόγιο, η επόμενη σελίδα δεν θα απαιτεί διεύθυνση email.",
|
||||
"ProviderSelection_DisplayNameHeader": "Όνομα εμφάνισης",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "π.χ. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Διεύθυνση Ηλεκτρονικού Ταχυδρομείου",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Select a calendar to configure its settings",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Initial synchronization",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino synchronized your mails until {0} going back.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Connected features",
|
||||
"AccountDetailsPage_CapabilityDescription": "Choose whether this account is used for mail, calendar, or both. Enabling a new feature may ask you to sign in again.",
|
||||
"AccountCapability_MailOnly": "Mail only",
|
||||
"AccountCapability_CalendarOnly": "Calendar only",
|
||||
"AccountCapability_MailAndCalendar": "Mail + Calendar",
|
||||
"AddHyperlink": "Add",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Apply Theme",
|
||||
"Buttons_PopOut": "Pop out",
|
||||
"Buttons_Browse": "Browse",
|
||||
"Buttons_Back": "Back",
|
||||
"Buttons_Cancel": "Cancel",
|
||||
"Buttons_Close": "Close",
|
||||
"Buttons_Copy": "Copy",
|
||||
@@ -86,11 +92,13 @@
|
||||
"Buttons_Discard": "Discard",
|
||||
"Buttons_Dismiss": "Dismiss",
|
||||
"Buttons_Edit": "Edit",
|
||||
"Buttons_EML": "EML",
|
||||
"Buttons_EnableImageRendering": "Enable",
|
||||
"Buttons_Multiselect": "Select Multiple",
|
||||
"Buttons_Manage": "Manage",
|
||||
"Buttons_No": "No",
|
||||
"Buttons_Open": "Open",
|
||||
"Buttons_PDF": "PDF",
|
||||
"Buttons_Purchase": "Purchase",
|
||||
"Buttons_RateWino": "Rate Wino",
|
||||
"Buttons_Reset": "Reset",
|
||||
@@ -299,6 +307,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Delete Recurring Series",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "This draft will be discarded. Do you want to continue?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Discard Draft",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "A draft is still open. Save it before closing the window?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Close Window",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Missing Subject",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Message has no subject. Do you want to continue?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -438,7 +448,7 @@
|
||||
"IMAPAdvancedSetupDialog_ValidationAuthMethodRequired": "Authentication method is required",
|
||||
"IMAPAdvancedSetupDialog_ValidationConnectionSecurityRequired": "Connection security type is required",
|
||||
"IMAPAdvancedSetupDialog_ValidationDisplayNameRequired": "Display name is required",
|
||||
"IMAPAdvancedSetupDialog_ValidationEmailInvalid": "Please enter a valid email address",
|
||||
"IMAPAdvancedSetupDialog_ValidationEmailInvalid": "Please enter a valid mailbox address, such as user@example.com or user@localhost",
|
||||
"IMAPAdvancedSetupDialog_ValidationEmailRequired": "Email address is required",
|
||||
"IMAPAdvancedSetupDialog_ValidationErrorTitle": "Please check the following:",
|
||||
"IMAPAdvancedSetupDialog_ValidationIncomingPortInvalid": "Incoming port must be between 1-65535",
|
||||
@@ -485,7 +495,7 @@
|
||||
"IMAPSetupDialog_IMAPSettings": "IMAP Server Settings",
|
||||
"IMAPSetupDialog_SMTPSettings": "SMTP Server Settings",
|
||||
"IMAPSetupDialog_MailAddress": "Email address",
|
||||
"IMAPSetupDialog_MailAddressPlaceholder": "someone@example.com",
|
||||
"IMAPSetupDialog_MailAddressPlaceholder": "someone@example.com or user@localhost",
|
||||
"IMAPSetupDialog_OutgoingMailServer": "Outgoing (SMTP) mail server",
|
||||
"IMAPSetupDialog_OutgoingMailServerPassword": "Outgoing server password",
|
||||
"IMAPSetupDialog_OutgoingMailServerPort": "Port",
|
||||
@@ -502,7 +512,7 @@
|
||||
"ImapCalDavSettingsPage_TitleEdit": "Edit IMAP and Calendar Settings",
|
||||
"ImapCalDavSettingsPage_Subtitle": "Configure IMAP/SMTP and optional calendar synchronization for this account.",
|
||||
"ImapCalDavSettingsPage_BasicSectionTitle": "Basic setup",
|
||||
"ImapCalDavSettingsPage_BasicSectionDescription": "Enter your identity and credentials. Wino can try to detect server settings automatically.",
|
||||
"ImapCalDavSettingsPage_BasicSectionDescription": "Enter your identity and credentials. Wino supports manual addresses such as user@localhost and can try to detect server settings automatically.",
|
||||
"ImapCalDavSettingsPage_BasicTab": "Basic",
|
||||
"ImapCalDavSettingsPage_EnableCalendarSupport": "Enable calendar support",
|
||||
"ImapCalDavSettingsPage_AutoDiscoverButton": "Autodiscover mail settings",
|
||||
@@ -587,6 +597,9 @@
|
||||
"Info_MessageCorruptedTitle": "Error",
|
||||
"Info_MissingFolderMessage": "{0} doesn't exist for this account.",
|
||||
"Info_MissingFolderTitle": "Missing Folder",
|
||||
"Info_EMLSaveFailedTitle": "Failed to save EML file",
|
||||
"Info_EMLSaveSuccessMessage": "EML file is saved to {0}",
|
||||
"Info_EMLSaveSuccessTitle": "Success",
|
||||
"Info_PDFSaveFailedTitle": "Failed to save PDF file",
|
||||
"Info_PDFSaveSuccessMessage": "PDF file is saved to {0}",
|
||||
"Info_PDFSaveSuccessTitle": "Success",
|
||||
@@ -681,12 +694,18 @@
|
||||
"NoMailSelected": "No message selected",
|
||||
"NoMessageCrieteria": "No messages match your search criteria",
|
||||
"NoMessageEmptyFolder": "This folder is empty",
|
||||
"MailEmptyState_Title": "No mail-enabled accounts",
|
||||
"MailEmptyState_Message": "You have accounts connected for calendar, but none of them are enabled for mail. Add a mail account or update an existing account to use mail.",
|
||||
"MailEmptyState_AddAccount": "Add account",
|
||||
"MailEmptyState_ManageAccounts": "Manage accounts",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
|
||||
"Notifications_WinoUpdatedTitle": "Wino Mail has been updated.",
|
||||
"Notifications_StoreUpdateAvailableTitle": "Update available",
|
||||
"Notifications_StoreUpdateAvailableMessage": "A newer version of Wino Mail is ready to install from Microsoft Store.",
|
||||
"Notifications_ReleaseMigrationTitle": "New Wino Mail & Calendar",
|
||||
"Notifications_ReleaseMigrationMessage": "Wino Mail got updated to the next version. Please re-create your accounts and start using the next version including calendar, mail templates, shortcuts, and a bunch of other improvements.",
|
||||
"OnlineSearchFailed_Message": "Failed to perform search\n{0}\n\nListing offline mails.",
|
||||
"OnlineSearchTry_Line1": "Can't find what you are looking for?",
|
||||
"OnlineSearchTry_Line2": "Try online search.",
|
||||
@@ -702,8 +721,8 @@
|
||||
"ProviderDetail_Gmail_Description": "Google Account",
|
||||
"ProviderDetail_iCloud_Description": "Apple iCloud Account",
|
||||
"ProviderDetail_iCloud_Title": "iCloud",
|
||||
"ProviderDetail_IMAP_Description": "Custom IMAP/SMTP server",
|
||||
"ProviderDetail_IMAP_Title": "IMAP Server",
|
||||
"ProviderDetail_IMAP_Description": "IMAP/SMTP mail with CalDAV or local calendar",
|
||||
"ProviderDetail_IMAP_Title": "Custom server",
|
||||
"ProviderDetail_Yahoo_Description": "Yahoo Account",
|
||||
"ProviderDetail_Yahoo_Title": "Yahoo Mail",
|
||||
"QuickEventDialog_EventName": "Event name",
|
||||
@@ -824,6 +843,8 @@
|
||||
"SettingsDeleteAccount_Title": "Delete this account",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Show the empty-folder command in Junk/Spam folders. This action will not ask for confirmation and will delete all mails in the spam folder immediately.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Show empty spam folder command",
|
||||
"SettingsDiagnostics_Description": "For developers",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1095,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Organize messages into conversation threads.",
|
||||
"SettingsThreads_Title": "Conversation Threading",
|
||||
"SettingsThreads_Enabled_Description": "Group related messages into a single conversation.",
|
||||
"SettingsThreads_Enabled_Title": "Enable conversation threading",
|
||||
"SettingsThreadOrder_Description": "Choose how items are ordered inside a conversation thread.",
|
||||
"SettingsThreadOrder_Title": "Thread item sorting",
|
||||
"SettingsThreadOrder_LastItemFirst": "Last item first",
|
||||
"SettingsThreadOrder_FirstItemFirst": "First item first",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1421,8 +1448,8 @@
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exporting your selected Wino data...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transfer with a JSON file",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Import from or export to a local JSON file. Passwords, tokens, and other sensitive information are not included.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Import JSON",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Export JSON",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Import",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Export",
|
||||
"WinoAccount_Management_LocalDataSaved": "Saved your exported Wino data to {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "The selected JSON file doesn't contain a valid Wino export.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino could not load the latest Wino Account information.",
|
||||
@@ -1521,8 +1548,22 @@
|
||||
"WelcomeWizard_Step3Title": "Finish Setup",
|
||||
"ProviderSelection_Title": "Choose your email provider",
|
||||
"ProviderSelection_Subtitle": "Select a provider below to add your email account to Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Step {0} of 3",
|
||||
"ProviderSelection_IdentityTitle": "Account identity",
|
||||
"ProviderSelection_IdentityDescription": "Choose how this account appears inside Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Provider",
|
||||
"ProviderSelection_ProviderSectionDescription": "Select the service you want to connect.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Use this account for",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Choose whether you want mail, calendar, or both.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "On the next step, secure sign-in will connect your account. If you enable calendar, Wino will also connect Outlook Calendar or Google Calendar automatically.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "On the next step, you'll enter your provider credentials. Mail uses IMAP/SMTP, and calendar can use CalDAV or stay local on this device.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "On the next step, you'll enter your server details. Mail uses IMAP/SMTP, and calendar can use CalDAV or stay local on this device.",
|
||||
"ProviderSelection_AccountNameHeader": "Account Name",
|
||||
"ProviderSelection_AccountNamePlaceholder": "e.g. Personal, Work",
|
||||
"ProviderSelection_UseForMail": "Mail",
|
||||
"ProviderSelection_UseForCalendar": "Calendar",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Choose at least one capability before continuing.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "If you continue with calendar only, the next page will not require an email address.",
|
||||
"ProviderSelection_DisplayNameHeader": "Display Name",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "e.g. John Doe",
|
||||
"ProviderSelection_EmailHeader": "E-mail Address",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Selecciona un calendario para configurar sus ajustes",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Sincronización inicial",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino ha sincronizado tus correos hasta {0} atrás.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Características conectadas",
|
||||
"AccountDetailsPage_CapabilityDescription": "Elija si esta cuenta se utilizará para correo, calendario o ambos. Al habilitar una nueva función podría pedirle que vuelva a iniciar sesión.",
|
||||
"AccountCapability_MailOnly": "Solo correo",
|
||||
"AccountCapability_CalendarOnly": "Solo calendario",
|
||||
"AccountCapability_MailAndCalendar": "Correo y Calendario",
|
||||
"AddHyperlink": "Añadir",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Sincronización en segundo plano",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "La aplicación no está configurada para iniciarse con Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Aplicar Tema",
|
||||
"Buttons_PopOut": "Desanclar",
|
||||
"Buttons_Browse": "Buscar",
|
||||
"Buttons_Back": "Atrás",
|
||||
"Buttons_Cancel": "Cancelar",
|
||||
"Buttons_Close": "Cerrar",
|
||||
"Buttons_Copy": "Copiar",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Eliminar serie recurrente",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Este borrador se descartará. ¿Desea continuar?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Descartar borrador",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Aún hay un borrador abierto. ¿Desea guardarlo antes de cerrar la ventana?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Cerrar ventana",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Sin asunto",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "El mensaje no tiene asunto. ¿Desea continuar?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Puedes habilitar esta opción en cualquier momento desde Ajustes > Configuración de la aplicación.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Ningún mensaje seleccionado",
|
||||
"NoMessageCrieteria": "Ningún mensaje coincide con su criterio de búsqueda",
|
||||
"NoMessageEmptyFolder": "Esta carpeta está vacía",
|
||||
"MailEmptyState_Title": "No hay cuentas habilitadas para correo",
|
||||
"MailEmptyState_Message": "Tiene cuentas conectadas para el calendario, pero ninguna está habilitada para el correo. Añada una cuenta de correo o actualice una cuenta existente para usar el correo.",
|
||||
"MailEmptyState_AddAccount": "Añadir cuenta",
|
||||
"MailEmptyState_ManageAccounts": "Gestionar cuentas",
|
||||
"Notifications_MultipleNotificationsMessage": "Tienes {0} mensaje(s) nuevo(s).",
|
||||
"Notifications_MultipleNotificationsTitle": "Mensajes nuevos",
|
||||
"Notifications_WinoUpdatedMessage": "Comprobar nueva versión {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Eliminar esta cuenta",
|
||||
"SettingsDeleteProtection_Description": "¿Debería Wino pedirte confirmación cada vez que intentas eliminar un correo usando las teclas Shift + Supr?",
|
||||
"SettingsDeleteProtection_Title": "Protección de Eliminación Permanente",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Mostrar el comando para vaciar la carpeta en las carpetas de correo no deseado. Esta acción no solicitará confirmación y eliminará todos los correos de la carpeta de correo no deseado de inmediato.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Mostrar el comando para vaciar la carpeta de correo no deseado",
|
||||
"SettingsDiagnostics_Description": "Para desarrolladores",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Comparte este ID con los desarrolladores cuando se les pida ayuda para los problemas que experimentas en Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "ID de Diagnóstico",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Insignia de la barra de tareas",
|
||||
"SettingsThreads_Description": "Organizar mensajes en hilos de conversación.",
|
||||
"SettingsThreads_Title": "Hilos de conversación",
|
||||
"SettingsThreads_Enabled_Description": "Agrupa mensajes relacionados en una sola conversación.",
|
||||
"SettingsThreads_Enabled_Title": "Habilitar el hilo de conversación",
|
||||
"SettingsThreadOrder_Description": "Elija cómo se ordenan los elementos dentro de un hilo de conversación.",
|
||||
"SettingsThreadOrder_Title": "Orden de elementos del hilo",
|
||||
"SettingsThreadOrder_LastItemFirst": "Último elemento primero",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Primer elemento primero",
|
||||
"SettingsUnlinkAccounts_Description": "Eliminar el enlace entre cuentas. No eliminará sus cuentas.",
|
||||
"SettingsUnlinkAccounts_Title": "Desvincular Cuentas",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Etiquetas de acción",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Comienza añadiendo una cuenta",
|
||||
"WelcomeWindow_GetStartedDescription": "Agrega tu cuenta de Outlook, Gmail o IMAP para empezar a usar Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importar desde tu cuenta de Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importar desde un archivo JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Importando tus preferencias y cuentas sincronizadas...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "No se encontraron cuentas sincronizadas en su Cuenta de Wino. Si las preferencias estaban disponibles, se restauraron. Usa Comenzar para añadir una cuenta manualmente.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} cuentas sincronizadas ya están disponibles en este dispositivo. Usa Comenzar para añadir otra cuenta manualmente si es necesario.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Las contraseñas, los tokens y otra información sensible no se sincronizan.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Las cuentas importadas en otro PC seguirán requiriéndote iniciar sesión de nuevo antes de poder usarlas.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exportando tus datos de Wino seleccionados...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transferir con un archivo JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importar desde o exportar a un archivo JSON local. Contraseñas, tokens y otra información sensible no se incluyen.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importar",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exportar",
|
||||
"WinoAccount_Management_LocalDataSaved": "Guardó sus datos exportados de Wino en {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "El archivo JSON seleccionado no contiene una exportación válida de Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "No se pudo cargar la información más reciente de la cuenta de Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "No se pudo completar la solicitud de la cuenta de Wino.",
|
||||
"WinoAccount_SettingsSection_Title": "Cuenta de Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Finalizar configuración",
|
||||
"ProviderSelection_Title": "Selecciona tu proveedor de correo",
|
||||
"ProviderSelection_Subtitle": "Selecciona un proveedor a continuación para añadir tu cuenta de correo a Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Paso {0} de 3",
|
||||
"ProviderSelection_IdentityTitle": "Identidad de la cuenta",
|
||||
"ProviderSelection_IdentityDescription": "Elija cómo aparecerá esta cuenta dentro de Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Proveedor",
|
||||
"ProviderSelection_ProviderSectionDescription": "Seleccione el servicio que desea conectar.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Usar esta cuenta para",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Elija si desea correo, calendario o ambos.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "En el siguiente paso, el inicio de sesión seguro conectará su cuenta. Si habilita el calendario, Wino también conectará automáticamente Outlook Calendar o Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "En el siguiente paso, introducirá sus credenciales del proveedor. El correo utiliza IMAP/SMTP, y el calendario puede usar CalDAV o permanecer local en este dispositivo.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "En el siguiente paso, introducirá los detalles de su servidor. El correo utiliza IMAP/SMTP, y el calendario puede usar CalDAV o permanecer local en este dispositivo.",
|
||||
"ProviderSelection_AccountNameHeader": "Nombre de la cuenta",
|
||||
"ProviderSelection_AccountNamePlaceholder": "p. ej. Personal, Trabajo",
|
||||
"ProviderSelection_UseForMail": "Correo",
|
||||
"ProviderSelection_UseForCalendar": "Calendario",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Elija al menos una capacidad antes de continuar.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Si continúa solo con el calendario, la página siguiente no requerirá una dirección de correo electrónico.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nombre para mostrar",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "p. ej. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Correo electrónico",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Valitse kalenteri määrittääksesi sen asetukset",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Ensisynkronointi",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino synkronoi sähköpostisi {0} taaksepäin.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Yhdistetyt ominaisuudet",
|
||||
"AccountDetailsPage_CapabilityDescription": "Valitse, käytetäänkö tätä tiliä sähköpostiin, kalenteriin vai molempiin. Ominaisuuden käyttöönotto saattaa pyytää sinua kirjautumaan sisään uudelleen.",
|
||||
"AccountCapability_MailOnly": "Vain sähköposti",
|
||||
"AccountCapability_CalendarOnly": "Vain kalenteri",
|
||||
"AccountCapability_MailAndCalendar": "Sähköposti ja Kalenteri",
|
||||
"AddHyperlink": "Lisää",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Vaihda teemaa",
|
||||
"Buttons_PopOut": "Avaa erillisessä ikkunassa",
|
||||
"Buttons_Browse": "Selaa",
|
||||
"Buttons_Back": "Takaisin",
|
||||
"Buttons_Cancel": "Peruuta",
|
||||
"Buttons_Close": "Sulje",
|
||||
"Buttons_Copy": "Kopioi",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Poista toistuva sarja",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Luonnos hylätään. Haluatko jatkaa?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Hylkää luonnos",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Luonnos on vielä auki. Tallenna se ennen ikkunan sulkemista?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Sulje ikkuna",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Aihe puuttuu",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Viestillä ei ole aihetta. Haluatko jatkaa?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Ei viestiä valittuna",
|
||||
"NoMessageCrieteria": "Mikään viesti ei vastaa hakuasi",
|
||||
"NoMessageEmptyFolder": "Kansio tyhjä",
|
||||
"MailEmptyState_Title": "Ei sähköpostia tukevia tilejä",
|
||||
"MailEmptyState_Message": "Sinulla on tilejä, jotka on yhdistetty kalenteriin, mutta mikään niistä ei ole käytössä sähköpostin kannalta. Lisää sähköpostitili tai päivitä jokin olemassa oleva tili käyttämään sähköpostia.",
|
||||
"MailEmptyState_AddAccount": "Lisää tili",
|
||||
"MailEmptyState_ManageAccounts": "Hallitse tilejä",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Tarkasta uusi versio {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Delete this account",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Näytä roskapostikansioiden tyhjennyskomento. Tämä toimenpide ei kysy vahvistusta ja poistaa välittömästi kaikki sähköpostit roskapostikansioista.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Näytä roskapostikansioiden tyhjennyskomento",
|
||||
"SettingsDiagnostics_Description": "For developers",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Organize messages into conversation threads.",
|
||||
"SettingsThreads_Title": "Conversation Threading",
|
||||
"SettingsThreads_Enabled_Description": "Ryhmittele liittyvät viestit yhdeksi keskusteluksi.",
|
||||
"SettingsThreads_Enabled_Title": "Ota keskusteluketjutus käyttöön",
|
||||
"SettingsThreadOrder_Description": "Valitse, miten kohteet järjestetään keskusteluketjussa.",
|
||||
"SettingsThreadOrder_Title": "Ketjun kohteiden lajittelu",
|
||||
"SettingsThreadOrder_LastItemFirst": "Viimeinen kohde ensin",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Ensimmäinen kohde ensin",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Aloita lisäämällä tilin",
|
||||
"WelcomeWindow_GetStartedDescription": "Lisää Outlook-, Gmail- tai IMAP-tili aloittaaksesi Wino Mailin käytön.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Tuo Wino-tililtäsi",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Tuo JSON-tiedosto",
|
||||
"WelcomeWindow_ImportInProgress": "Tuodaan synkronoitujen asetusten ja tilien tiedot...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Wino-tililläsi ei löytynyt synkronoituja tilejä. Jos asetukset olivat käytettävissä, ne palautettiin. Käytä Aloita tilin lisäämisen manuaalisesti.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} synkronoitua tiliä on jo käytettävissä tässä laitteessa. Käytä Aloita tilin lisääminen manuaalisesti, jos tarvitset toisen tilin.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Salasanat, tunnukset ja muut arkaluonteiset tiedot eivät synkronoidu.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Toisella tietokoneella tuodut tilit vaativat vielä uudelleen kirjautumisen ennen kuin niitä voidaan käyttää.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Viedään valitut Wino-tiedot...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Siirrä JSON-tiedoston avulla",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Tuo tai vie paikalliseen JSON-tiedostoon. Salasanat, tokenit ja muut arkaluontoiset tiedot eivät sisälly.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Tuo",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Vie",
|
||||
"WinoAccount_Management_LocalDataSaved": "Tallensin vietyt Wino-tiedot kohteeseen {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Valittu JSON-tiedosto ei sisällä kelvollista Wino-vientiä.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino ei voinut ladata uusinta Wino-tilin tietoa.",
|
||||
"WinoAccount_Management_ActionFailed": "Wino-tilin pyyntöä ei voitu suorittaa.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino-tili",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Viimeistele asennus",
|
||||
"ProviderSelection_Title": "Valitse sähköpostipalveluntarjoajasi",
|
||||
"ProviderSelection_Subtitle": "Valitse alla oleva palveluntarjoaja lisätäksesi sähköpostitilisi Wino Mailiin.",
|
||||
"ProviderSelection_StepProgress": "Vaihe {0} / 3",
|
||||
"ProviderSelection_IdentityTitle": "Tilin identiteetti",
|
||||
"ProviderSelection_IdentityDescription": "Valitse, miltä tili näyttää Wino-sovelluksessa.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Palveluntarjoaja",
|
||||
"ProviderSelection_ProviderSectionDescription": "Valitse yhdistettävä palvelu.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Käytä tätä tiliä seuraaviin",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Valitse, haluatko käyttää sähköpostia, kalenteria vai molempia.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Seuraavassa vaiheessa turvallinen sisäänkirjautuminen yhdistää tilisi. Jos otat kalenterin käyttöön, Wino yhdistää automaattisesti Outlook Kalenterin tai Google Kalenterin.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Seuraavassa vaiheessa syötät palveluntarjoajan tunnistetiedot. Sähköposti käyttää IMAP/SMTP, ja kalenteri voi käyttää CalDAV:ia tai pysyä paikallisena tässä laitteessa.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Seuraavassa vaiheessa syötät palvelimen tiedot. Sähköposti käyttää IMAP/SMTP, ja kalenteri voi käyttää CalDAV:ia tai pysyä paikallisena tässä laitteessa.",
|
||||
"ProviderSelection_AccountNameHeader": "Tilin nimi",
|
||||
"ProviderSelection_AccountNamePlaceholder": "esim. Henkilökohtainen, Työ",
|
||||
"ProviderSelection_UseForMail": "Sähköposti",
|
||||
"ProviderSelection_UseForCalendar": "Kalenteri",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Jatkaaksesi valitse vähintään yksi ominaisuus.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Jos jatkat vain kalenterin kanssa, seuraavalla sivulla sähköpostiosoitetta ei tarvita.",
|
||||
"ProviderSelection_DisplayNameHeader": "Näytettävä nimi",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "esim. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Sähköpostiosoite",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Sélectionnez un calendrier pour configurer ses paramètres.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Synchronisation initiale",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino a synchronisé vos messages jusqu'au {0} en remontant.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Fonctionnalités connectées",
|
||||
"AccountDetailsPage_CapabilityDescription": "Choisissez si ce compte est utilisé pour le courrier, le calendrier, ou les deux. L'activation d'une nouvelle fonctionnalité peut vous demander de vous reconnecter.",
|
||||
"AccountCapability_MailOnly": "Courrier uniquement",
|
||||
"AccountCapability_CalendarOnly": "Calendrier uniquement",
|
||||
"AccountCapability_MailAndCalendar": "Courrier + Calendrier",
|
||||
"AddHyperlink": "Ajouter",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Synchronisation en arrière-plan",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "L'application n'a pas été configurée pour être lancée au démarrage de Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Appliquer le thème",
|
||||
"Buttons_PopOut": "Détacher",
|
||||
"Buttons_Browse": "Parcourir",
|
||||
"Buttons_Back": "Retour",
|
||||
"Buttons_Cancel": "Annuler",
|
||||
"Buttons_Close": "Fermer",
|
||||
"Buttons_Copy": "Copier",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Supprimer la série récurrente",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Ce brouillon sera supprimé. Voulez-vous continuer ?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Supprimer le brouillon",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Un brouillon est encore ouvert. Enregistrez-le avant de fermer la fenêtre ?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Fermer la fenêtre",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Objet manquant",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Le message n'a pas d'objet. Voulez-vous continuer ?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Vous pouvez activer le lancement au démarrage depuis Paramètres -> Préférences de l'application.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Aucun message sélectionné",
|
||||
"NoMessageCrieteria": "Aucun message ne correspond à vos critères de recherche",
|
||||
"NoMessageEmptyFolder": "Ce dossier est vide",
|
||||
"MailEmptyState_Title": "Aucun compte activé pour le courrier",
|
||||
"MailEmptyState_Message": "Vous avez des comptes connectés au calendrier, mais aucun d’entre eux n’est activé pour le courrier. Ajoutez un compte de courrier ou mettez à jour un compte existant pour utiliser le courrier.",
|
||||
"MailEmptyState_AddAccount": "Ajouter un compte",
|
||||
"MailEmptyState_ManageAccounts": "Gérer les comptes",
|
||||
"Notifications_MultipleNotificationsMessage": "Vous avez {0} nouveaux messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "Nouveau message",
|
||||
"Notifications_WinoUpdatedMessage": "Vérifier la nouvelle version {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Supprimer ce compte",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Protection contre la suppression permanente",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Afficher la commande de vidage du dossier dans les dossiers de courrier indésirable. Cette action ne demandera pas de confirmation et supprimera immédiatement tous les courriels du dossier de courrier indésirable.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Afficher la commande pour vider le dossier de courrier indésirable",
|
||||
"SettingsDiagnostics_Description": "Pour les développeurs",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Partagez cet identifiant avec les développeurs lorsqu'ils vous aideront à résoudre les problèmes que vous rencontrez dans Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "ID de diagnostic",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Badge de la barre de tâches",
|
||||
"SettingsThreads_Description": "Organiser les messages en fils de conversation.",
|
||||
"SettingsThreads_Title": "Affichage en mode conversation",
|
||||
"SettingsThreads_Enabled_Description": "Regrouper les messages liés en une seule conversation.",
|
||||
"SettingsThreads_Enabled_Title": "Activer le fil de discussion",
|
||||
"SettingsThreadOrder_Description": "Choisissez l'ordre des éléments dans un fil de discussion.",
|
||||
"SettingsThreadOrder_Title": "Tri des éléments du fil de discussion",
|
||||
"SettingsThreadOrder_LastItemFirst": "Dernier élément en premier",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Premier élément en premier",
|
||||
"SettingsUnlinkAccounts_Description": "Supprimer le lien entre les comptes. Cela ne supprimera pas vos comptes.",
|
||||
"SettingsUnlinkAccounts_Title": "Dissocier les comptes",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Commencer en ajoutant un compte",
|
||||
"WelcomeWindow_GetStartedDescription": "Ajoutez votre compte Outlook, Gmail ou IMAP pour commencer avec Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importer depuis votre compte Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importer à partir d'un fichier JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Importation de vos préférences et comptes synchronisés...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Aucun compte synchronisé n'a été trouvé dans votre compte Wino. Si des préférences étaient disponibles, elles ont été restaurées. Utilisez Commencer pour ajouter manuellement un compte.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} comptes synchronisés sont déjà disponibles sur cet appareil. Utilisez Commencer pour en ajouter un autre manuellement si nécessaire.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Les mots de passe, jetons et autres informations sensibles ne sont pas synchronisés.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Les comptes importés sur un autre PC devront quand même être reconnectés avant de pouvoir être utilisés.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exportation de vos données Wino sélectionnées...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transfert via un fichier JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importer ou exporter vers un fichier JSON local. Les mots de passe, jetons et autres informations sensibles ne sont pas incluses.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importer",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exporter",
|
||||
"WinoAccount_Management_LocalDataSaved": "Vos données Wino exportées ont été sauvegardées dans {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Le fichier JSON sélectionné ne contient pas d'export Wino valide.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino n'a pas pu charger les dernières informations du compte Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "La requête du compte Wino n'a pas pu être complétée.",
|
||||
"WinoAccount_SettingsSection_Title": "Compte Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Terminer la configuration",
|
||||
"ProviderSelection_Title": "Choisissez votre fournisseur de messagerie",
|
||||
"ProviderSelection_Subtitle": "Sélectionnez un fournisseur ci-dessous pour ajouter votre compte de messagerie à Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Étape {0} sur 3",
|
||||
"ProviderSelection_IdentityTitle": "Identité du compte",
|
||||
"ProviderSelection_IdentityDescription": "Choisissez comment ce compte apparaît dans Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Fournisseur",
|
||||
"ProviderSelection_ProviderSectionDescription": "Sélectionnez le service que vous souhaitez connecter.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Utiliser ce compte pour",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Choisissez si vous souhaitez le courrier, le calendrier, ou les deux.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "À l'étape suivante, une connexion sécurisée reliera votre compte. Si vous activez le calendrier, Wino connectera automatiquement Outlook Calendar ou Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "À l'étape suivante, vous saisirez vos identifiants du fournisseur. Le courrier utilise IMAP/SMTP, et le calendrier peut utiliser CalDAV ou rester local sur cet appareil.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "À l'étape suivante, vous saisirez les détails du serveur. Le courrier utilise IMAP/SMTP, et le calendrier peut utiliser CalDAV ou rester local sur cet appareil.",
|
||||
"ProviderSelection_AccountNameHeader": "Nom du compte",
|
||||
"ProviderSelection_AccountNamePlaceholder": "par ex. Personnel, Professionnel",
|
||||
"ProviderSelection_UseForMail": "Courrier",
|
||||
"ProviderSelection_UseForCalendar": "Calendrier",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Choisissez au moins une capacité avant de continuer.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Si vous continuez avec le calendrier uniquement, la page suivante ne nécessitera pas d'adresse e-mail.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nom affiché",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "par ex. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Adresse e-mail",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Selecciona un calendario para configurar as súas configuracións.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Sincronización inicial",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino sincronizou os teus correos ata {0} no pasado.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Características conectadas",
|
||||
"AccountDetailsPage_CapabilityDescription": "Elixe se esta conta se usa para correo, calendario ou para ambos. Activar unha nova función pode pedirche que inicies sesión de novo.",
|
||||
"AccountCapability_MailOnly": "Só correo",
|
||||
"AccountCapability_CalendarOnly": "Só calendario",
|
||||
"AccountCapability_MailAndCalendar": "Correo e Calendario",
|
||||
"AddHyperlink": "Engadir",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Sincronización en segundo plano",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Aplicar tema",
|
||||
"Buttons_PopOut": "Abrir en nova xanela",
|
||||
"Buttons_Browse": "Procurar",
|
||||
"Buttons_Back": "Atrás",
|
||||
"Buttons_Cancel": "Anular",
|
||||
"Buttons_Close": "Pechar",
|
||||
"Buttons_Copy": "Copiar",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Eliminar serie recurrente",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "O borrador será eliminado. Quere continuar?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Eliminar borrador",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Un borrador aínda está aberto. Garda-o antes de pechar a xanela?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Pechar a xanela",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Missing Subject",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "A mensaxe non ten asunto. Queres continuar?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "No message selected",
|
||||
"NoMessageCrieteria": "No messages match your search criteria",
|
||||
"NoMessageEmptyFolder": "This folder is empty",
|
||||
"MailEmptyState_Title": "Non hai contas habilitadas para correo",
|
||||
"MailEmptyState_Message": "Tes contas conectadas para calendario, pero ningunha delas está habilitada para correo. Engade unha conta de correo ou actualiza unha conta existente para usar correo.",
|
||||
"MailEmptyState_AddAccount": "Engadir conta",
|
||||
"MailEmptyState_ManageAccounts": "Xestionar contas",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Delete this account",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Mostrar o comando para baleirar a carpeta de correo lixo. Esta acción non pedirá confirmación e eliminará de inmediato todos os correos da carpeta de correo lixo.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Mostrar o comando para baleirar a carpeta de correo lixo",
|
||||
"SettingsDiagnostics_Description": "For developers",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Organize messages into conversation threads.",
|
||||
"SettingsThreads_Title": "Conversation Threading",
|
||||
"SettingsThreads_Enabled_Description": "Agrupa as mensaxes relacionadas nunha única conversa.",
|
||||
"SettingsThreads_Enabled_Title": "Habilitar o fío da conversa",
|
||||
"SettingsThreadOrder_Description": "Elixe como se ordenan os elementos dentro do fío de conversa.",
|
||||
"SettingsThreadOrder_Title": "Ordenación de elementos do fío",
|
||||
"SettingsThreadOrder_LastItemFirst": "O último elemento primeiro",
|
||||
"SettingsThreadOrder_FirstItemFirst": "O primeiro elemento primeiro",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Comeza engadindo unha conta",
|
||||
"WelcomeWindow_GetStartedDescription": "Engade a túa conta de Outlook, Gmail ou IMAP para comezar a usar Wino Mail",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importar desde a túa Conta Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importar desde un arquivo JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Importando as túas preferencias e contas sincronizadas...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Non se atoparon contas sincronizadas na túa Conta Wino. Se as preferencias estaban dispoñibles, foron restauradas. Usa Comeza para engadir unha conta manualmente",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} contas sincronizadas xa están dispoñibles neste dispositivo. Usa Comeza para engadir outra conta manualmente se é necesario",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Contrasinais, tokens e outra información sensible non se sincronizan.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "As contas importadas noutro PC seguirán a necesitar que inicies sesión de novo antes de poder usalas.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exportando os datos de Wino seleccionados",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transferir cun arquivo JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importar desde ou exportar a un arquivo JSON local. Contrasinais, tokens e outras informacións sensibles non están incluídas.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importar",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exportar",
|
||||
"WinoAccount_Management_LocalDataSaved": "Os teus datos exportados de Wino gardáronse en {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "O ficheiro JSON seleccionado non contén unha exportación válida de Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino non conseguiu carregar a información máis recente da Conta Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "A solicitude da Conta Wino non puido completarse.",
|
||||
"WinoAccount_SettingsSection_Title": "Conta Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Rematar a configuración",
|
||||
"ProviderSelection_Title": "Elixe o teu provedor de correo",
|
||||
"ProviderSelection_Subtitle": "Selecciona un provedor a continuación para engadir a túa conta de correo a Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Fase {0} de 3",
|
||||
"ProviderSelection_IdentityTitle": "Identidade da conta",
|
||||
"ProviderSelection_IdentityDescription": "Elixe como aparecerá esta conta dentro de Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Provedor",
|
||||
"ProviderSelection_ProviderSectionDescription": "Selecciona o servizo que queres conectar.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Usar esta conta para",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Elixe se queres correo, calendario ou ambos.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "No seguinte paso, o inicio de sesión seguro conectará a túa conta. Se habilitas o calendario, Wino conectará automaticamente Outlook Calendar ou Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "No seguinte paso, introducirás as credenciais do provedor. O correo usa IMAP/SMTP, e o calendario pode usar CalDAV ou permanecer local neste dispositivo.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "No seguinte paso, introducirás os datos do servidor. O correo usa IMAP/SMTP, e o calendario pode usar CalDAV ou permanecer local neste dispositivo.",
|
||||
"ProviderSelection_AccountNameHeader": "Nome da conta",
|
||||
"ProviderSelection_AccountNamePlaceholder": "p. ex. Persoal, Laboral",
|
||||
"ProviderSelection_UseForMail": "Correo",
|
||||
"ProviderSelection_UseForCalendar": "Calendario",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Elixe polo menos unha capacidade antes de continuar.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Se continúas só co calendario, a seguinte páxina non requirirá unha dirección de correo.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nome para amosar",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "p. ex. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Enderezo de correo electrónico",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Pilih kalender untuk mengonfigurasi pengaturannya",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Sinkronisasi awal",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino telah menyinkronkan surel Anda hingga {0} ke belakang.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Fitur yang Terhubung",
|
||||
"AccountDetailsPage_CapabilityDescription": "Pilih apakah akun ini digunakan untuk email, kalender, atau keduanya. Mengaktifkan fitur baru mungkin meminta Anda untuk masuk lagi.",
|
||||
"AccountCapability_MailOnly": "Hanya Email",
|
||||
"AccountCapability_CalendarOnly": "Hanya Kalender",
|
||||
"AccountCapability_MailAndCalendar": "Email dan Kalender",
|
||||
"AddHyperlink": "Tambahkan",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Penyelarasan Latar Belakang",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Aplikasi tidak diatur untuk berjalan saat Windows dimulai.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Terapkan Tema",
|
||||
"Buttons_PopOut": "Buka jendela terpisah",
|
||||
"Buttons_Browse": "Telusuri",
|
||||
"Buttons_Back": "Kembali",
|
||||
"Buttons_Cancel": "Batal",
|
||||
"Buttons_Close": "Tutup",
|
||||
"Buttons_Copy": "Salin",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Hapus Seri Berulang",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Konsep ini akan dibuang. Apakah Anda ingin melanjutkan?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Buang Konsep",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Sebuah draf masih terbuka. Simpan sebelum menutup jendela?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Tutup Jendela",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Tidak Ada Perihal",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Pesan tidak memiliki perihal. Apakah Anda ingin melanjutkan?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Anda dapat menyalakan Luncurkan ketika Mulai di Pengaturan -> Pengaturan Aplikasi.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Tidak ada pesan yang dipilih",
|
||||
"NoMessageCrieteria": "Tidak ada pesan yang sesuai kriteria",
|
||||
"NoMessageEmptyFolder": "Folder ini kosong",
|
||||
"MailEmptyState_Title": "Tidak ada akun yang mendukung email",
|
||||
"MailEmptyState_Message": "Anda memiliki akun yang terhubung untuk kalender, tetapi tidak ada yang diaktifkan untuk email.Tambahkan akun email atau perbarui akun yang ada untuk menggunakan email.",
|
||||
"MailEmptyState_AddAccount": "Tambahkan akun",
|
||||
"MailEmptyState_ManageAccounts": "Kelola akun",
|
||||
"Notifications_MultipleNotificationsMessage": "Anda memiliki {0} pesan baru.",
|
||||
"Notifications_MultipleNotificationsTitle": "Pesan Baru",
|
||||
"Notifications_WinoUpdatedMessage": "Versi baru terpasang {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Hapus akun ini",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Perlindungan Hapus Permanen",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Tampilkan perintah kosongkan folder pada folder Junk/Spam. Tindakan ini tidak akan meminta konfirmasi dan akan menghapus semua email di folder spam segera.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Perintah kosongkan folder Spam",
|
||||
"SettingsDiagnostics_Description": "Untuk pengembang",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Lencana Bilah Tugas",
|
||||
"SettingsThreads_Description": "Kelompokkan pesan menjadi utas.",
|
||||
"SettingsThreads_Title": "Utas Percakapan",
|
||||
"SettingsThreads_Enabled_Description": "Gabungkan pesan terkait menjadi satu percakapan.",
|
||||
"SettingsThreads_Enabled_Title": "Aktifkan pengelompokan percakapan",
|
||||
"SettingsThreadOrder_Description": "Pilih bagaimana item diurutkan di dalam percakapan.",
|
||||
"SettingsThreadOrder_Title": "Pengurutan item percakapan",
|
||||
"SettingsThreadOrder_LastItemFirst": "Item terakhir pertama",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Item pertama dulu",
|
||||
"SettingsUnlinkAccounts_Description": "Hapus tautan antara akun. Ini tidak akan menghapus akun Anda.",
|
||||
"SettingsUnlinkAccounts_Title": "Lepaskan Tautan Akun",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Mulai dengan menambahkan akun",
|
||||
"WelcomeWindow_GetStartedDescription": "Tambahkan akun Outlook, Gmail, atau IMAP Anda untuk memulai dengan Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Impor dari Akun Wino Anda",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Impor dari file JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Mengimpor preferensi dan akun yang tersinkronkan...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Tidak ada akun yang tersinkronisasi ditemukan di Akun Wino Anda. Jika preferensi tersedia, mereka telah dipulihkan. Gunakan Mulai untuk menambahkan akun secara manual.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} akun yang tersinkronisasi sudah tersedia di perangkat ini. Gunakan Mulai untuk menambahkan akun lain secara manual jika diperlukan.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Kata sandi, token, dan informasi sensitif lainnya tidak disinkronkan.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Akun yang diimpor di PC lain masih perlu Anda masuk lagi sebelum dapat digunakan.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Sedang mengekspor data Wino yang Anda pilih...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transfer dengan file JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Impor dari atau ekspor ke file JSON lokal. Kata sandi, token, dan informasi sensitif lainnya tidak disertakan.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Impor",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Ekspor",
|
||||
"WinoAccount_Management_LocalDataSaved": "Data Wino yang diekspor telah disimpan ke {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "File JSON yang dipilih tidak berisi ekspor Wino yang valid.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino tidak dapat memuat informasi Akun Wino terbaru.",
|
||||
"WinoAccount_Management_ActionFailed": "Permintaan Akun Wino tidak dapat diselesaikan.",
|
||||
"WinoAccount_SettingsSection_Title": "Akun Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Selesaikan Pengaturan",
|
||||
"ProviderSelection_Title": "Pilih penyedia email Anda",
|
||||
"ProviderSelection_Subtitle": "Pilih penyedia di bawah ini untuk menambahkan akun email Anda ke Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Langkah {0} dari 3",
|
||||
"ProviderSelection_IdentityTitle": "Identitas akun",
|
||||
"ProviderSelection_IdentityDescription": "Pilih bagaimana akun ini ditampilkan di dalam Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Penyedia",
|
||||
"ProviderSelection_ProviderSectionDescription": "Pilih layanan yang ingin Anda hubungkan.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Gunakan akun ini untuk",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Pilih apakah Anda ingin email, kalender, atau keduanya.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Pada langkah berikutnya, proses masuk yang aman akan menghubungkan akun Anda. Jika Anda mengaktifkan kalender, Wino juga akan secara otomatis menghubungkan Outlook Calendar atau Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Pada langkah berikutnya, Anda akan memasukkan kredensial penyedia Anda. Email menggunakan IMAP/SMTP, dan kalender dapat menggunakan CalDAV atau tetap lokal di perangkat ini.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Pada langkah berikutnya, Anda akan memasukkan detail server Anda. Email menggunakan IMAP/SMTP, dan kalender dapat menggunakan CalDAV atau tetap lokal di perangkat ini.",
|
||||
"ProviderSelection_AccountNameHeader": "Nama Akun",
|
||||
"ProviderSelection_AccountNamePlaceholder": "contoh: Pribadi, Kerja",
|
||||
"ProviderSelection_UseForMail": "Email",
|
||||
"ProviderSelection_UseForCalendar": "Kalender",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Pilih setidaknya satu kemampuan sebelum melanjutkan.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Jika Anda melanjutkan dengan kalender saja, halaman berikutnya tidak akan memerlukan alamat email.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nama Tampilan",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "contoh: John Doe",
|
||||
"ProviderSelection_EmailHeader": "Alamat Email",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Seleziona un calendario per configurarne le impostazioni.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Sincronizzazione iniziale",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino ha sincronizzato la tua posta fino a {0} nel passato.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Funzionalità connesse",
|
||||
"AccountDetailsPage_CapabilityDescription": "Scegli se questo account viene utilizzato per la posta, il calendario o entrambi. Attivando una nuova funzionalità potrebbe chiederti di accedere nuovamente.",
|
||||
"AccountCapability_MailOnly": "Solo posta",
|
||||
"AccountCapability_CalendarOnly": "Solo calendario",
|
||||
"AccountCapability_MailAndCalendar": "Posta e Calendario",
|
||||
"AddHyperlink": "Aggiungi",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Sincronizzazione dietro le quinte",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "L'applicazione non è stata impostata per avviarsi all'avvio di Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Applica tema",
|
||||
"Buttons_PopOut": "Apri in una nuova finestra",
|
||||
"Buttons_Browse": "Sfoglia",
|
||||
"Buttons_Back": "Indietro",
|
||||
"Buttons_Cancel": "Annulla",
|
||||
"Buttons_Close": "Chiudi",
|
||||
"Buttons_Copy": "Copia",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Elimina serie ricorrente",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Questa bozza sarà scartata. Vuoi continuare?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Scarta bozza",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Una bozza è ancora aperta. Salvala prima di chiudere la finestra?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Chiudi finestra",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Oggetto mancante",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Il messaggio non ha oggetto. Vuoi continuare?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "È possibile abilitare l'avvio da Impostazioni -> Preferenze app.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Nessun messaggio selezionato",
|
||||
"NoMessageCrieteria": "Nessun messaggio corrisponde ai tuoi criteri di ricerca",
|
||||
"NoMessageEmptyFolder": "Questa cartella è vuota",
|
||||
"MailEmptyState_Title": "Nessun account abilitato per la posta",
|
||||
"MailEmptyState_Message": "Hai account collegati al calendario, ma nessuno è abilitato per la posta. Aggiungi un account di posta o aggiorna un account esistente per utilizzare la posta.",
|
||||
"MailEmptyState_AddAccount": "Aggiungi account",
|
||||
"MailEmptyState_ManageAccounts": "Gestisci account",
|
||||
"Notifications_MultipleNotificationsMessage": "Hai {0} nuovi messaggi.",
|
||||
"Notifications_MultipleNotificationsTitle": "Nuova posta",
|
||||
"Notifications_WinoUpdatedMessage": "Controlla la nuova versione {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Elimina questo account",
|
||||
"SettingsDeleteProtection_Description": "Dovrebbe Wino chiederti la conferma ogni volta che provi a eliminare definitivamente un messaggio utilizzando Maiusc + Canc?",
|
||||
"SettingsDeleteProtection_Title": "Protezione eliminazione permanente",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Mostra il comando per svuotare le cartelle Junk/Spam. Questa azione non richiederà alcuna conferma e eliminerà immediatamente tutte le email nelle cartelle Junk/Spam.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Mostra il comando per svuotare la cartella Junk/Spam",
|
||||
"SettingsDiagnostics_Description": "Per gli sviluppatori",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Condividi questo ID con gli sviluppatori quando richiesto per ricevere aiuto in merito ai problemi che hai con Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "ID diagnostica",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "In barra attività come",
|
||||
"SettingsThreads_Description": "Organizza i messaggi in gruppi di conversazione.",
|
||||
"SettingsThreads_Title": "Raggruppamento conversazioni",
|
||||
"SettingsThreads_Enabled_Description": "Raggruppa i messaggi correlati in una singola conversazione.",
|
||||
"SettingsThreads_Enabled_Title": "Abilita la visualizzazione a thread delle conversazioni.",
|
||||
"SettingsThreadOrder_Description": "Scegli come vengono ordinati gli elementi all'interno di una conversazione a thread.",
|
||||
"SettingsThreadOrder_Title": "Ordinamento degli elementi della conversazione",
|
||||
"SettingsThreadOrder_LastItemFirst": "Ultimo elemento per primo",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Primo elemento per primo",
|
||||
"SettingsUnlinkAccounts_Description": "Rimuovi il collegamento tra gli account. Questo non eliminerà i tuoi account.",
|
||||
"SettingsUnlinkAccounts_Title": "Scollega account",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Etichette azioni",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Inizia aggiungendo un account",
|
||||
"WelcomeWindow_GetStartedDescription": "Aggiungi il tuo account Outlook, Gmail o IMAP per iniziare a usare Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importa dal tuo account Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importa da un file JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Importazione delle preferenze e degli account sincronizzati in corso...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Nessun account sincronizzato è stato trovato nel tuo account Wino. Se erano disponibili le preferenze, sono state ripristinate. Usa Inizia per aggiungere manualmente un account.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} account sincronizzati sono già disponibili su questo dispositivo. Usa Inizia per aggiungere manualmente un altro account se necessario.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Le password, i token e altre informazioni sensibili non sono sincronizzati.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Gli account importati su un altro PC richiederanno comunque di effettuare nuovamente l'accesso prima che possano essere utilizzati.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Esportazione dei dati Wino selezionati in corso...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Trasferisci con un file JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importa o esporta in un file JSON locale. password, token e altre informazioni sensibili non sono inclusi.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importa",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Esporta",
|
||||
"WinoAccount_Management_LocalDataSaved": "Hai salvato i dati esportati di Wino in {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Il file JSON selezionato non contiene un'esportazione Wino valida.",
|
||||
"WinoAccount_Management_LoadFailed": "Impossibile caricare le ultime informazioni dell'Account Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "La richiesta dell'Account Wino non può essere completata.",
|
||||
"WinoAccount_SettingsSection_Title": "Account Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Completa la configurazione",
|
||||
"ProviderSelection_Title": "Scegli il provider di posta elettronica",
|
||||
"ProviderSelection_Subtitle": "Seleziona un provider qui sotto per aggiungere il tuo account di posta a Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Fase {0} di 3",
|
||||
"ProviderSelection_IdentityTitle": "Identità dell'account",
|
||||
"ProviderSelection_IdentityDescription": "Scegli come apparirà questo account in Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Fornitore",
|
||||
"ProviderSelection_ProviderSectionDescription": "Seleziona il servizio che vuoi connettere.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Usa questo account per",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Scegli se vuoi posta, calendario o entrambi.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Nel passaggio successivo, l'autenticazione sicura collegherà il tuo account. Se abiliti il calendario, Wino collegherà automaticamente anche Outlook Calendar o Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Nel passaggio successivo inserirai le credenziali del provider. La posta utilizza IMAP/SMTP, e il calendario può utilizzare CalDAV o rimanere locale su questo dispositivo.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Nel passaggio successivo inserirai i dettagli del server. La posta utilizza IMAP/SMTP, e il calendario può utilizzare CalDAV o rimanere locale su questo dispositivo.",
|
||||
"ProviderSelection_AccountNameHeader": "Nome account",
|
||||
"ProviderSelection_AccountNamePlaceholder": "ad es. Personale, Lavoro",
|
||||
"ProviderSelection_UseForMail": "Posta",
|
||||
"ProviderSelection_UseForCalendar": "Calendario",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Seleziona almeno una funzionalità prima di procedere.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Se prosegui con solo calendario, la pagina successiva non richiederà un indirizzo email.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nome visualizzato",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "es. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Indirizzo e-mail",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "設定を構成するカレンダーを選択してください。",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "初期同期",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino は {0} まで遡ってメールを同期しました。",
|
||||
"AccountDetailsPage_CapabilityTitle": "接続機能",
|
||||
"AccountDetailsPage_CapabilityDescription": "このアカウントをメール、カレンダー、または両方に使用するかを選択します。新機能を有効にすると、再度サインインを求められることがあります。",
|
||||
"AccountCapability_MailOnly": "メールのみ",
|
||||
"AccountCapability_CalendarOnly": "カレンダーのみ",
|
||||
"AccountCapability_MailAndCalendar": "メール+カレンダー",
|
||||
"AddHyperlink": "追加",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "テーマを適用",
|
||||
"Buttons_PopOut": "ポップアウト",
|
||||
"Buttons_Browse": "閲覧",
|
||||
"Buttons_Back": "戻る",
|
||||
"Buttons_Cancel": "キャンセル",
|
||||
"Buttons_Close": "閉じる",
|
||||
"Buttons_Copy": "コピー",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "繰り返しシリーズを削除",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "この下書きは破棄されます。続行しますか?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "下書きを破棄",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "下書きがまだ開いています。ウィンドウを閉じる前に保存しますか?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "ウィンドウを閉じる",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Missing Subject",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Message has no subject. Do you want to continue?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "No message selected",
|
||||
"NoMessageCrieteria": "No messages match your search criteria",
|
||||
"NoMessageEmptyFolder": "This folder is empty",
|
||||
"MailEmptyState_Title": "メールが有効なアカウントがありません",
|
||||
"MailEmptyState_Message": "カレンダー用に接続されているアカウントはありますが、メール用として有効になっているものはありません。メールアカウントを追加するか、既存のアカウントを更新してメールを使用してください。",
|
||||
"MailEmptyState_AddAccount": "アカウントを追加",
|
||||
"MailEmptyState_ManageAccounts": "アカウントを管理",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Delete this account",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "迷惑メール フォルダーの「空にする」コマンドを表示します。この操作は確認を求めず、迷惑メール フォルダー内のすべてのメールを即座に削除します。",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "迷惑メール フォルダーを空にするコマンドを表示",
|
||||
"SettingsDiagnostics_Description": "For developers",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Organize messages into conversation threads.",
|
||||
"SettingsThreads_Title": "Conversation Threading",
|
||||
"SettingsThreads_Enabled_Description": "関連するメッセージを1つの会話にまとめます。",
|
||||
"SettingsThreads_Enabled_Title": "会話のスレッド化を有効にする",
|
||||
"SettingsThreadOrder_Description": "会話スレッド内のアイテムの並べ替え方法を選択します。",
|
||||
"SettingsThreadOrder_Title": "スレッド内アイテムの並べ替え",
|
||||
"SettingsThreadOrder_LastItemFirst": "最後のアイテムを先頭に表示",
|
||||
"SettingsThreadOrder_FirstItemFirst": "最初のアイテムを先頭に表示",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "アカウントを追加して始める",
|
||||
"WelcomeWindow_GetStartedDescription": "Wino Mail を始めるには、Outlook、Gmail、または IMAP アカウントを追加してください。",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Wino アカウントからインポート",
|
||||
"WelcomeWindow_ImportFromJsonFile": "JSONファイルからインポート",
|
||||
"WelcomeWindow_ImportInProgress": "同期された設定とアカウントをインポートしています...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Wino アカウントに同期されたアカウントは見つかりませんでした。設定が利用可能だった場合、それらは復元されました。アカウントを手動で追加するには、はじめるを使ってください。",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} 個の同期済みアカウントはすでにこのデバイスにあります。必要に応じて、別のアカウントを手動で追加するにははじめるを使用してください。",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "パスワード、トークン、その他の機密情報は同期されません。",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "他のPCでインポートされたアカウントを使用するには、再度サインインする必要があります。",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "選択した Wino データをエクスポートしています...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "JSONファイルで転送",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "ローカルJSONファイルからのインポートまたはエクスポートを行います。パスワード、トークン、その他の機密情報は含まれません。",
|
||||
"WinoAccount_Management_LocalDataImportAction": "インポート",
|
||||
"WinoAccount_Management_LocalDataExportAction": "エクスポート",
|
||||
"WinoAccount_Management_LocalDataSaved": "エクスポートしたWinoデータを {0} に保存しました。",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "選択したJSONファイルには有効なWinoエクスポートが含まれていません。",
|
||||
"WinoAccount_Management_LoadFailed": "Wino は最新の Wino アカウント情報を読み込めませんでした。",
|
||||
"WinoAccount_Management_ActionFailed": "Wino アカウントのリクエストを完了できませんでした。",
|
||||
"WinoAccount_SettingsSection_Title": "Wino アカウント",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "設定を完了",
|
||||
"ProviderSelection_Title": "メールプロバイダを選択",
|
||||
"ProviderSelection_Subtitle": "以下のプロバイダを選択して、Wino Mail にメールアカウントを追加します。",
|
||||
"ProviderSelection_StepProgress": "ステップ {0} / 3",
|
||||
"ProviderSelection_IdentityTitle": "アカウントの識別情報",
|
||||
"ProviderSelection_IdentityDescription": "このアカウントがWino内でどのように表示されるかを選択します。",
|
||||
"ProviderSelection_ProviderSectionTitle": "プロバイダ",
|
||||
"ProviderSelection_ProviderSectionDescription": "接続したいサービスを選択します。",
|
||||
"ProviderSelection_CapabilitySectionTitle": "このアカウントを使用する用途",
|
||||
"ProviderSelection_CapabilitySectionDescription": "メール、カレンダー、または両方を使用するかを選択します。",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "次のステップで安全なサインインがあなたのアカウントを接続します。カレンダーを有効にすると、Winoは自動的にOutlook CalendarまたはGoogle Calendarにも接続します。",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "次のステップで、プロバイダの認証情報を入力します。メールはIMAP/SMTPを使用します。カレンダーはCalDAVを使用するか、このデバイスでローカルに保存します。",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "次のステップでサーバー情報を入力します。メールはIMAP/SMTPを使用します。カレンダーはCalDAVを使用するか、このデバイスにローカルで保存します。",
|
||||
"ProviderSelection_AccountNameHeader": "アカウント名",
|
||||
"ProviderSelection_AccountNamePlaceholder": "例: Personal、Work",
|
||||
"ProviderSelection_UseForMail": "メール",
|
||||
"ProviderSelection_UseForCalendar": "カレンダー",
|
||||
"ProviderSelection_CapabilityValidationMessage": "続行する前に、少なくとも1つの機能を選択してください。",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "カレンダーのみで続行する場合、次のページではメールアドレスを必要としません。",
|
||||
"ProviderSelection_DisplayNameHeader": "表示名",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "例: John Doe",
|
||||
"ProviderSelection_EmailHeader": "メールアドレス",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Pasirinkite kalendorių, kad sukonfigūruotumėte jo nustatymus.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Pradinė sinchronizacija",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino sinchronizavo jūsų laiškus iki {0}.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Prisijungtos funkcijos",
|
||||
"AccountDetailsPage_CapabilityDescription": "Pasirinkite, ar ši paskyra bus naudojama el. paštu, kalendoriui ar abiem. Įjungus naują funkciją gali tekti prisijungti dar kartą.",
|
||||
"AccountCapability_MailOnly": "Tik el. paštas",
|
||||
"AccountCapability_CalendarOnly": "Tik kalendorius",
|
||||
"AccountCapability_MailAndCalendar": "El. paštas ir kalendorius",
|
||||
"AddHyperlink": "Add",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Apply Theme",
|
||||
"Buttons_PopOut": "Iššokti",
|
||||
"Buttons_Browse": "Browse",
|
||||
"Buttons_Back": "Atgal",
|
||||
"Buttons_Cancel": "Cancel",
|
||||
"Buttons_Close": "Close",
|
||||
"Buttons_Copy": "Copy",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Ištrinti pakartojančią seriją",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "This draft will be discarded. Do you want to continue?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Discard Draft",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Juodraštis vis dar atviras. Išsaugoti prieš uždarant langą?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Uždaryti langą",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Missing Subject",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Message has no subject. Do you want to continue?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "No message selected",
|
||||
"NoMessageCrieteria": "No messages match your search criteria",
|
||||
"NoMessageEmptyFolder": "This folder is empty",
|
||||
"MailEmptyState_Title": "Nėra el. paštą palaikančių paskyrų",
|
||||
"MailEmptyState_Message": "Turite paskyras, skirtas kalendoriui, bet nė viena iš jų nepalaiko el. pašto. Pridėkite el. pašto paskyrą arba atnaujinkite esamą paskyrą, kad naudotumėte el. paštą.",
|
||||
"MailEmptyState_AddAccount": "Pridėti paskyrą",
|
||||
"MailEmptyState_ManageAccounts": "Valdyti paskyras",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Checkout new version {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Delete this account",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Permanent Delete Protection",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Rodyti komandą Išvalyti šlamšto aplanką šlamšto aplankuose. Šis veiksmas nepaprašys patvirtinimo ir nedelsiant ištrins visus el. laiškus šlamšto aplanke.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Rodyti komandą Išvalyti šlamšto aplanką",
|
||||
"SettingsDiagnostics_Description": "For developers",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Organize messages into conversation threads.",
|
||||
"SettingsThreads_Title": "Conversation Threading",
|
||||
"SettingsThreads_Enabled_Description": "Susijusius pranešimus sugrupuoti į vieną pokalbį.",
|
||||
"SettingsThreads_Enabled_Title": "Įjungti pokalbių gijas",
|
||||
"SettingsThreadOrder_Description": "Pasirinkite, kaip elementai rūšiuojami pokalbių gijoje.",
|
||||
"SettingsThreadOrder_Title": "Pokalbių gijų rūšiavimas",
|
||||
"SettingsThreadOrder_LastItemFirst": "Paskutinis elementas pirmas",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Pirmasis elementas pirmas",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Unlink Accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Pradėti pridėdami paskyrą",
|
||||
"WelcomeWindow_GetStartedDescription": "Pridėkite paskyrą Outlook, Gmail arba IMAP, kad pradėtumėte naudotis Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importuoti iš jūsų Wino paskyros",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importuoti iš JSON failo",
|
||||
"WelcomeWindow_ImportInProgress": "Importuojami jūsų sinchronizuoti nustatymai ir paskyros...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Nerasta sinchronizuotų paskyrų jūsų Wino paskyroje. Jei buvo nustatymų, jie buvo atstatyti. Naudokite Pradėti, kad pridėtumėte paskyrą rankiniu būdu.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} sinchronizuotų paskyrų jau yra šiame įrenginyje. Jei reikia, naudokite Pradėti, kad pridėtumėte dar vieną paskyrą rankiniu būdu.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Slaptažodžiai, tokenai ir kita jautri informacija nėra sinchronizuojama.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Importuotos paskyros kituose kompiuteriuose vis tiek reikės prisijungti iš naujo prieš jų naudojimą.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Eksportuojami jūsų pasirinkti Wino duomenys...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Perduoti su JSON failu",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importuoti iš arba eksportuoti į vietinį JSON failą. Slaptažodžiai, tokenai ir kita jautri informacija nėra įtraukiama.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importuoti",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Eksportuoti",
|
||||
"WinoAccount_Management_LocalDataSaved": "Išsaugojau jūsų eksportuotus Wino duomenis į {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Pasirinktas JSON failas neapima galiojančio Wino eksporto.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino negalėjo įkelti naujausios Wino paskyros informacijos.",
|
||||
"WinoAccount_Management_ActionFailed": "Prašymas Wino paskyrai negalėjo būti užbaigtas.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino paskyra",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Nustatymo užbaigimas",
|
||||
"ProviderSelection_Title": "Pasirinkite savo el. pašto teikėją",
|
||||
"ProviderSelection_Subtitle": "Pasirinkite teikėją žemiau, kad pridėtumėte savo el. pašto paskyrą prie Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Žingsnis {0} iš 3",
|
||||
"ProviderSelection_IdentityTitle": "Paskyros tapatybė",
|
||||
"ProviderSelection_IdentityDescription": "Pasirinkite, kaip ši paskyra pasirodys Wino viduje.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Paslaugų teikėjas",
|
||||
"ProviderSelection_ProviderSectionDescription": "Pasirinkite paslaugą, kurią norite prijungti.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Naudoti šią paskyrą",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Pasirinkite, ar norite el. paštą, kalendorių ar abiejų.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Kitoje pakopoje saugus prisijungimas sujungs jūsų paskyrą. Jei įjungs kalendorių, Wino automatiškai sujungs Outlook Calendar arba Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Kitoje pakopoje įvesite savo teikėjo prisijungimo duomenis. Paštas naudoja IMAP/SMTP, o kalendorius gali naudoti CalDAV arba likti šiame įrenginyje.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Kitoje pakopoje įvesite serverio duomenis. El. paštas naudoja IMAP/SMTP, o kalendorius gali naudoti CalDAV arba likti šiame įrenginyje.",
|
||||
"ProviderSelection_AccountNameHeader": "Paskyros pavadinimas",
|
||||
"ProviderSelection_AccountNamePlaceholder": "pvz. Asmeninė, Darbo",
|
||||
"ProviderSelection_UseForMail": "El. paštas",
|
||||
"ProviderSelection_UseForCalendar": "Kalendorius",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Prieš tęsiant pasirinkite bent vieną galimybę.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Jei tęsite tik kalendoriui, kitame puslapyje el. pašto adreso nereikės.",
|
||||
"ProviderSelection_DisplayNameHeader": "Rodomas vardas",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "pvz. Jonas Doe",
|
||||
"ProviderSelection_EmailHeader": "El. pašto adresas",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Selecteer een kalender om de instellingen ervan te configureren.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Initiële synchronisatie",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino heeft je e-mails gesynchroniseerd tot {0} terug.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Verbonden functies",
|
||||
"AccountDetailsPage_CapabilityDescription": "Kies of dit account wordt gebruikt voor e-mail, agenda, of beide. Het inschakelen van een nieuwe functie kan vereisen dat je opnieuw inlogt.",
|
||||
"AccountCapability_MailOnly": "Alleen e-mail",
|
||||
"AccountCapability_CalendarOnly": "Alleen agenda",
|
||||
"AccountCapability_MailAndCalendar": "Mail en Agenda",
|
||||
"AddHyperlink": "Toevoegen",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Achtergrondsynchronisatie",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Applicatie is niet ingesteld om te laden bij het opstarten van Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Thema toepassen",
|
||||
"Buttons_PopOut": "Pop-out",
|
||||
"Buttons_Browse": "Bladeren",
|
||||
"Buttons_Back": "Terug",
|
||||
"Buttons_Cancel": "Annuleren",
|
||||
"Buttons_Close": "Sluiten",
|
||||
"Buttons_Copy": "Kopiëren",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Doorlopende serie verwijderen",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Dit concept zal worden verwijderd. Wilt u doorgaan?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Concept verwijderen",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Er is nog een concept geopend. Sla het op voordat je het venster sluit?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Venster sluiten",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Onderwerp ontbreekt",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Het bericht heeft geen onderwerp. Wilt u doorgaan?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "U kunt laden bij opstarten inschakelen via Instellingen -> App voorkeuren.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Geen berichten geselecteerd",
|
||||
"NoMessageCrieteria": "Er zijn geen berichten die voldoen aan u zoekcriteria",
|
||||
"NoMessageEmptyFolder": "Deze map is leeg",
|
||||
"MailEmptyState_Title": "Geen e-mailaccounts ingeschakeld.",
|
||||
"MailEmptyState_Message": "Je hebt accounts verbonden voor agenda, maar geen van deze accounts is ingeschakeld voor e-mail. Voeg een e-mailaccount toe of werk een bestaand account bij om e-mail te gebruiken.",
|
||||
"MailEmptyState_AddAccount": "Account toevoegen",
|
||||
"MailEmptyState_ManageAccounts": "Accounts beheren",
|
||||
"Notifications_MultipleNotificationsMessage": "U heeft {0} nieuwe berichten.",
|
||||
"Notifications_MultipleNotificationsTitle": "Nieuwe berichten",
|
||||
"Notifications_WinoUpdatedMessage": "Bekijk de nieuwe versie {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Verwijder dit account",
|
||||
"SettingsDeleteProtection_Description": "Moet Wino u om bevestiging vragen elke keer wanneer u een e-mail permanent verwijdert met de Shift + Del toetsen?",
|
||||
"SettingsDeleteProtection_Title": "Bescherming tegen permanent verwijderen",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Toon het lege-map-commando in de Junk/Spam-mappen. Deze actie vraagt geen bevestiging en verwijdert onmiddellijk alle e-mails in de spam-map.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Toon leeg-map-commando",
|
||||
"SettingsDiagnostics_Description": "Voor ontwikkelaars",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Deel dit ID met de ontwikkelaars wanneer er om hulp gevraagd wordt voor de problemen die u in Wino Mail ervaart.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostische ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taakbalk badge",
|
||||
"SettingsThreads_Description": "Voeg berichten samen tot gesprekken.",
|
||||
"SettingsThreads_Title": "Gesprekken",
|
||||
"SettingsThreads_Enabled_Description": "Groepeer gerelateerde berichten in één gesprek.",
|
||||
"SettingsThreads_Enabled_Title": "Gespreksdraad inschakelen",
|
||||
"SettingsThreadOrder_Description": "Kies hoe items binnen een gesprek worden geordend.",
|
||||
"SettingsThreadOrder_Title": "Sortering van gesprek-items",
|
||||
"SettingsThreadOrder_LastItemFirst": "Laatste item eerst",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Eerste item eerst",
|
||||
"SettingsUnlinkAccounts_Description": "Verwijder de koppeling tussen accounts. Dit zal niet uw accounts verwijderen.",
|
||||
"SettingsUnlinkAccounts_Title": "Ontkoppel accounts",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Actielabels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Beginnen met het toevoegen van een account.",
|
||||
"WelcomeWindow_GetStartedDescription": "Voeg uw Outlook-, Gmail- of IMAP-account toe om aan de slag te gaan met Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importeren vanuit uw Wino-account",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importeren vanuit een JSON-bestand",
|
||||
"WelcomeWindow_ImportInProgress": "Uw gesynchroniseerde voorkeuren en accounts worden geïmporteerd...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Er zijn geen gesynchroniseerde accounts gevonden in uw Wino-account. Als er voorkeuren beschikbaar waren, zijn ze hersteld. Gebruik Aan de slag om handmatig een account toe te voegen.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} gesynchroniseerde accounts zijn al beschikbaar op dit apparaat. Gebruik Aan de slag om indien nodig handmatig nog een account toe te voegen.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Wachtwoorden, tokens en andere gevoelige informatie worden niet gesynchroniseerd.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Geïmporteerde accounts op een andere pc vereisen nog steeds dat u zich opnieuw aanmeldt voordat ze kunnen worden gebruikt.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Uw geselecteerde Wino-gegevens worden geëxporteerd...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Overzetten met een JSON-bestand",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importeer vanuit of exporteer naar een lokaal JSON-bestand. Wachtwoorden, tokens en andere gevoelige informatie worden niet opgenomen.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importeren",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exporteren",
|
||||
"WinoAccount_Management_LocalDataSaved": "Je geëxporteerde Wino-gegevens zijn opgeslagen naar {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Het geselecteerde JSON-bestand bevat geen geldige Wino-export.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino kon de nieuwste Wino Account-informatie niet laden.",
|
||||
"WinoAccount_Management_ActionFailed": "Het Wino Account-verzoek kon niet worden voltooid.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino-account",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Configuratie voltooien",
|
||||
"ProviderSelection_Title": "Kies uw e-mailprovider",
|
||||
"ProviderSelection_Subtitle": "Selecteer hieronder een provider om uw e-mailaccount toe te voegen aan Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Stap {0} van 3",
|
||||
"ProviderSelection_IdentityTitle": "Accountidentiteit",
|
||||
"ProviderSelection_IdentityDescription": "Kies hoe dit account in Wino wordt weergegeven.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Provider",
|
||||
"ProviderSelection_ProviderSectionDescription": "Selecteer de dienst die je wilt verbinden.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Gebruik dit account voor",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Kies of je e-mail, agenda, of beide wilt gebruiken.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "In de volgende stap wordt een beveiligde aanmelding uitgevoerd die je account verbindt. Als je agenda inschakelt, verbindt Wino automatisch Outlook Agenda of Google Agenda.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "In de volgende stap voer je je provider-inloggegevens in. Mail gebruikt IMAP/SMTP, en agenda kan CalDAV gebruiken of lokaal op dit apparaat blijven.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "In de volgende stap voer je je servergegevens in. Mail gebruikt IMAP/SMTP, en agenda kan CalDAV gebruiken of lokaal op dit apparaat blijven.",
|
||||
"ProviderSelection_AccountNameHeader": "Accountnaam",
|
||||
"ProviderSelection_AccountNamePlaceholder": "bijv. Persoonlijk, Werk",
|
||||
"ProviderSelection_UseForMail": "E-mail",
|
||||
"ProviderSelection_UseForCalendar": "Agenda",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Kies ten minste één mogelijkheid voordat je verdergaat.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Als je doorgaat met alleen agenda, vereist de volgende pagina geen e-mailadres.",
|
||||
"ProviderSelection_DisplayNameHeader": "Weergavenaam",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "bijv. John Doe",
|
||||
"ProviderSelection_EmailHeader": "E-mailadres",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Wybierz kalendarz, aby skonfigurować jego ustawienia.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Początkowa synchronizacja",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino zsynchronizował maile do {0} wstecz.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Funkcje połączone",
|
||||
"AccountDetailsPage_CapabilityDescription": "Wybierz, czy to konto ma być używane do poczty, kalendarza, czy obu. Włączenie nowej funkcji może wymagać ponownego zalogowania.",
|
||||
"AccountCapability_MailOnly": "Tylko Poczta",
|
||||
"AccountCapability_CalendarOnly": "Tylko Kalendarz",
|
||||
"AccountCapability_MailAndCalendar": "Poczta i Kalendarz",
|
||||
"AddHyperlink": "Dodaj",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Synchronizacja w tle",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Aplikacja nie została ustawiona do uruchamiania przy starcie systemu Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Zastosuj motyw",
|
||||
"Buttons_PopOut": "Wysuń",
|
||||
"Buttons_Browse": "Przeglądaj",
|
||||
"Buttons_Back": "Wstecz",
|
||||
"Buttons_Cancel": "Anuluj",
|
||||
"Buttons_Close": "Zamknij",
|
||||
"Buttons_Copy": "Kopiuj",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Usuń serię cykliczną",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Ta wersja robocza zostanie odrzucona. Czy chcesz kontynuować?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Porzuć wersje roboczą",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Szkic jest nadal otwarty. Zapisz go przed zamknięciem okna?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Zamknij okno",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Brak tematu",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Wiadomość nie ma tematu. Czy chcesz kontynuować?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Możesz włączyć uruchamianie automatyczne w Ustawieniach -> Preferencje Aplikacji.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Nie wybrano wiadomości",
|
||||
"NoMessageCrieteria": "Żadne wiadomości nie spełniają kryteriów wyszukiwania",
|
||||
"NoMessageEmptyFolder": "Ten katalog jest pusty",
|
||||
"MailEmptyState_Title": "Brak kont obsługujących pocztę",
|
||||
"MailEmptyState_Message": "Masz konta podłączone do kalendarza, ale żadne z nich nie ma włączonej obsługi poczty. Dodaj konto pocztowe lub zaktualizuj istniejące konto, aby korzystać z poczty.",
|
||||
"MailEmptyState_AddAccount": "Dodaj konto",
|
||||
"MailEmptyState_ManageAccounts": "Zarządzaj kontami",
|
||||
"Notifications_MultipleNotificationsMessage": "Masz {0} nowych wiadomości.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Sprawdź nową wersję {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Usuń to konto",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Ochrona przed trwałym usunięciem",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Pokaż polecenie opróżniania pustego folderu w folderach Junk/Spam. Ta akcja nie poprosi o potwierdzenie i natychmiast usunie wszystkie wiadomości z folderu Spam.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Pokaż polecenie opróżniania pustego folderu Spam",
|
||||
"SettingsDiagnostics_Description": "Dla programistów",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Ikona paska zadań",
|
||||
"SettingsThreads_Description": "Organizuj wiadomości w wątki konwersacji.",
|
||||
"SettingsThreads_Title": "Wątkowanie konwersacji",
|
||||
"SettingsThreads_Enabled_Description": "Łącz powiązane wiadomości w jedną rozmowę.",
|
||||
"SettingsThreads_Enabled_Title": "Włącz wątki konwersacyjne",
|
||||
"SettingsThreadOrder_Description": "Wybierz, jak elementy będą uporządkowane w wątku konwersacyjnym.",
|
||||
"SettingsThreadOrder_Title": "Sortowanie elementów wątku",
|
||||
"SettingsThreadOrder_LastItemFirst": "Ostatni element na początku",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Pierwszy element na początku",
|
||||
"SettingsUnlinkAccounts_Description": "Usuń łącze pomiędzy kontami. To nie spowoduje usunięcie Twoich kont.",
|
||||
"SettingsUnlinkAccounts_Title": "Odłącz konto",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Rozpocznij od dodania konta",
|
||||
"WelcomeWindow_GetStartedDescription": "Dodaj swoje konta Outlook, Gmail lub IMAP, aby rozpocząć z Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importuj z konta Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importuj z pliku JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Importowanie zsynchronizowanych ustawień i kont...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Nie znaleziono zsynchronizowanych kont w Twoim koncie Wino. Jeśli ustawienia były dostępne, zostały przywrócone. Aby dodać konto ręcznie, użyj Rozpocznij.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} zsynchronizowanych kont są już dostępne na tym urządzeniu. Aby dodać inne konto ręcznie, jeśli to potrzebne, użyj Rozpocznij.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Hasła, tokeny i inne wrażliwe dane nie są synchronizowane.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Zaimportowane konta na innym komputerze będą nadal wymagały ponownego zalogowania przed użyciem.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Eksportowanie wybranych danych Wino...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Przenoszenie danych za pomocą pliku JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importuj z lokalnego pliku JSON lub eksportuj do niego. Hasła, tokeny i inne poufne informacje nie są dołączane.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importuj",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Eksportuj",
|
||||
"WinoAccount_Management_LocalDataSaved": "Zapisano wyeksportowane dane Wino do {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Wybrany plik JSON nie zawiera prawidłowego eksportu danych Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Nie udało się załadować najnowszych informacji o koncie Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "Żądanie konta Wino nie mogło zostać zakończone.",
|
||||
"WinoAccount_SettingsSection_Title": "Konto Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Zakończ konfigurację",
|
||||
"ProviderSelection_Title": "Wybierz dostawcę poczty e-mail",
|
||||
"ProviderSelection_Subtitle": "Wybierz dostawcę poniżej, aby dodać swoje konto e-mail do Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Krok {0} z 3",
|
||||
"ProviderSelection_IdentityTitle": "Tożsamość konta",
|
||||
"ProviderSelection_IdentityDescription": "Wybierz, jak to konto będzie wyświetlane w Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Dostawca",
|
||||
"ProviderSelection_ProviderSectionDescription": "Wybierz usługę, którą chcesz połączyć.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Użyj tego konta do",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Wybierz, czy chcesz używać poczty, kalendarza, czy obu.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "W następnym kroku bezpieczne logowanie połączy twoje konto. Jeśli włączysz kalendarz, Wino automatycznie połączy Outlook Calendar lub Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "W kolejnym kroku wprowadzisz dane uwierzytelniające dostawcy. Poczta używa IMAP/SMTP, a kalendarz może korzystać z CalDAV lub pozostawać lokalny na tym urządzeniu.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "W następnym kroku wprowadzisz szczegóły serwera. Poczta używa IMAP/SMTP, a kalendarz może używać CalDAV lub zostać lokalny na tym urządzeniu.",
|
||||
"ProviderSelection_AccountNameHeader": "Nazwa konta",
|
||||
"ProviderSelection_AccountNamePlaceholder": "np. Osobiste, Służbowe",
|
||||
"ProviderSelection_UseForMail": "Poczta",
|
||||
"ProviderSelection_UseForCalendar": "Kalendarz",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Wybierz co najmniej jedną funkcję przed kontynuowaniem.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Jeśli kontynuujesz wyłącznie z kalendarzem, na następnym kroku nie będzie wymagany adres e-mail.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nazwa wyświetlana",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "np. Jan Kowalski",
|
||||
"ProviderSelection_EmailHeader": "Adres e-mail",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Selecione um calendário para configurar suas configurações.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Sincronização inicial",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino sincronizou seus e-mails até {0} no passado.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Recursos conectados",
|
||||
"AccountDetailsPage_CapabilityDescription": "Escolha se esta conta será usada para E-mail, Calendário ou ambos. Ativar um novo recurso pode exigir que você faça login novamente.",
|
||||
"AccountCapability_MailOnly": "Somente E-mail",
|
||||
"AccountCapability_CalendarOnly": "Somente Calendário",
|
||||
"AccountCapability_MailAndCalendar": "E-mail + Calendário",
|
||||
"AddHyperlink": "Adicionar",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Sincronização em segundo plano",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "O aplicativo não foi configurado para iniciar com o Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Aplicar Tema",
|
||||
"Buttons_PopOut": "Abrir em nova janela",
|
||||
"Buttons_Browse": "Navegar",
|
||||
"Buttons_Back": "Voltar",
|
||||
"Buttons_Cancel": "Cancelar",
|
||||
"Buttons_Close": "Fechar",
|
||||
"Buttons_Copy": "Copiar",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Excluir Série Recorrente",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Este rascunho será descartado. Você quer continuar?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Descartar Rascunho",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Há um rascunho aberto. Salve-o antes de fechar a janela?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Fechar Janela",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Faltando Assunto",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Mensagem não possui assunto. Você deseja continuar?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Você pode habilitar a inicialização em Configurações -> Preferências do Aplicativo.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Nenhuma mensagem selecionada",
|
||||
"NoMessageCrieteria": "Nenhuma mensagem corresponde aos critérios da sua pesquisa",
|
||||
"NoMessageEmptyFolder": "Esta pasta está vazia",
|
||||
"MailEmptyState_Title": "Nenhuma conta habilitada para E-mail",
|
||||
"MailEmptyState_Message": "Você tem contas conectadas para calendário, mas nenhuma delas está habilitada para e-mail. Adicione uma conta de e-mail ou atualize uma conta existente para usar e-mail.",
|
||||
"MailEmptyState_AddAccount": "Adicionar conta",
|
||||
"MailEmptyState_ManageAccounts": "Gerenciar contas",
|
||||
"Notifications_MultipleNotificationsMessage": "Você tem {0} novos emails.",
|
||||
"Notifications_MultipleNotificationsTitle": "Novo E-mail",
|
||||
"Notifications_WinoUpdatedMessage": "Veja a nova versão {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Apagar esta conta",
|
||||
"SettingsDeleteProtection_Description": "O Wino deve solicitar confirmação sempre que você tentar excluir permanentemente um e-mail usando as teclas Shift + Del?",
|
||||
"SettingsDeleteProtection_Title": "Proteção de exclusão permanente",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Mostrar o comando para esvaziar a pasta nas pastas de Lixo/Spam. Esta ação não solicitará confirmação e excluirá imediatamente todas as mensagens na pasta de spam.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Mostrar o comando para esvaziar a pasta de Spam",
|
||||
"SettingsDiagnostics_Description": "Para desenvolvedores",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Compartilhe esta identificação com os desenvolvedores quando solicitado para obter ajuda para os problemas que você experimentar no Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "ID de diagnóstico",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Ícone da Barra de Tarefas",
|
||||
"SettingsThreads_Description": "Organizar mensagens em tópicos de conversa.",
|
||||
"SettingsThreads_Title": "* Encadeamento de conversas",
|
||||
"SettingsThreads_Enabled_Description": "Agrupar mensagens relacionadas em uma única conversa.",
|
||||
"SettingsThreads_Enabled_Title": "Ativar o encadeamento de conversas",
|
||||
"SettingsThreadOrder_Description": "Escolha como os itens são ordenados dentro de uma conversa.",
|
||||
"SettingsThreadOrder_Title": "Ordenação de itens da conversa",
|
||||
"SettingsThreadOrder_LastItemFirst": "Último item primeiro",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Primeiro item primeiro",
|
||||
"SettingsUnlinkAccounts_Description": "Remova o vínculo entre as contas. Isso não excluirá suas contas.",
|
||||
"SettingsUnlinkAccounts_Title": "Desvincular Contas",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Rótulos de ação",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Comece adicionando uma conta",
|
||||
"WelcomeWindow_GetStartedDescription": "Adicione sua conta do Outlook, Gmail ou IMAP para começar com o Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importar da sua Conta Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importar de um arquivo JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Importando suas preferências e contas sincronizadas...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Nenhuma conta sincronizada foi encontrada na sua Conta Wino. Se havia preferências disponíveis, elas foram restauradas. Use Começar para adicionar uma conta manualmente.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} contas sincronizadas já estão disponíveis neste dispositivo. Use Começar para adicionar outra conta manualmente, se necessário.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Senhas, tokens e outras informações sensíveis não são sincronizados.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Contas importadas em outro PC ainda precisarão que você faça login novamente antes que possam ser usadas.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exportando seus dados Wino selecionados...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transferir com um arquivo JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importar de ou exportar para um arquivo JSON local. Senhas, tokens e outras informações sensíveis não são incluídas.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importar",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exportar",
|
||||
"WinoAccount_Management_LocalDataSaved": "Seus dados do Wino exportados foram salvos em {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "O arquivo JSON selecionado não contém uma exportação válida do Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Não foi possível carregar as informações mais recentes da Conta Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "A solicitação da Conta Wino não pôde ser concluída.",
|
||||
"WinoAccount_SettingsSection_Title": "Conta Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Concluir Configuração",
|
||||
"ProviderSelection_Title": "Escolha seu provedor de e-mail",
|
||||
"ProviderSelection_Subtitle": "Selecione um provedor abaixo para adicionar sua conta de e-mail ao Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Etapa {0} de 3",
|
||||
"ProviderSelection_IdentityTitle": "Identidade da conta",
|
||||
"ProviderSelection_IdentityDescription": "Escolha como esta conta aparece dentro do Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Provedor",
|
||||
"ProviderSelection_ProviderSectionDescription": "Selecione o serviço que deseja conectar.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Usar esta conta para",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Escolha se deseja E-mail, Calendário ou ambos.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Na próxima etapa, o login seguro conectará sua conta. Se você ativar o calendário, o Wino também conectará automaticamente o Outlook Calendar ou Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Na próxima etapa, você inserirá as credenciais do provedor. O e-mail usa IMAP/SMTP, e o calendário pode usar CalDAV ou permanecer local neste dispositivo.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Na próxima etapa, você informará os detalhes do servidor. O e-mail usa IMAP/SMTP, e o calendário pode usar CalDAV ou permanecer local neste dispositivo.",
|
||||
"ProviderSelection_AccountNameHeader": "Nome da Conta",
|
||||
"ProviderSelection_AccountNamePlaceholder": "ex.: Pessoal, Trabalho",
|
||||
"ProviderSelection_UseForMail": "E-mail",
|
||||
"ProviderSelection_UseForCalendar": "Calendário",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Escolha pelo menos uma capacidade antes de continuar.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Se você continuar apenas com o calendário, a próxima página não exigirá um endereço de e-mail.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nome para exibição",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "ex.: João da Silva",
|
||||
"ProviderSelection_EmailHeader": "Endereço de E-mail",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Selectați un calendar pentru a-i configura setările.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Sincronizare inițială",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino și-a sincronizat e-mailurile până la {0} înapoi.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Funcționalități conectate",
|
||||
"AccountDetailsPage_CapabilityDescription": "Alege dacă acest cont este utilizat pentru mail, calendar sau ambele. Activarea unei noi funcționalități poate solicita autentificarea din nou.",
|
||||
"AccountCapability_MailOnly": "Doar Mail",
|
||||
"AccountCapability_CalendarOnly": "Doar Calendar",
|
||||
"AccountCapability_MailAndCalendar": "Mail și Calendar",
|
||||
"AddHyperlink": "Adăugare",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Sincronizare în Fundal",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Aplicația nu a fost setată să pornească la pornirea Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Aplicați Tema",
|
||||
"Buttons_PopOut": "Deschide într-o fereastră separată",
|
||||
"Buttons_Browse": "Răsfoire",
|
||||
"Buttons_Back": "Înapoi",
|
||||
"Buttons_Cancel": "Anulare",
|
||||
"Buttons_Close": "Închidere",
|
||||
"Buttons_Copy": "Copiere",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Șterge seria repetitivă",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Această schiță va fi eliminat. Doriți să continuați?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Eliminare Schiță",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "O schiță este încă deschisă. Salvați-o înainte să închideți fereastra?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Închide fereastra",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Subiect Lipsă",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Mesajul nu are subiect. Doriți să continuați?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Puteți activa lansarea la pornire din Setări -> Preferințe Aplicație.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Niciun mesaj selectat",
|
||||
"NoMessageCrieteria": "Niciun mesaj nu corespunde criteriilor dvs. de căutare",
|
||||
"NoMessageEmptyFolder": "Acest folder este gol",
|
||||
"MailEmptyState_Title": "Nu există conturi cu funcționalitate de mail activată.",
|
||||
"MailEmptyState_Message": "Aveți conturi conectate pentru calendar, dar niciunul dintre ele nu are funcționalitatea de mail activată. Adăugați un cont de mail sau actualizați un cont existent pentru a utiliza mail.",
|
||||
"MailEmptyState_AddAccount": "Adaugă cont",
|
||||
"MailEmptyState_ManageAccounts": "Gestionați conturi",
|
||||
"Notifications_MultipleNotificationsMessage": "Aveți {0} mesaje noi.",
|
||||
"Notifications_MultipleNotificationsTitle": "Mesaj E-Mail Nou",
|
||||
"Notifications_WinoUpdatedMessage": "Verificați versiunea nouă {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Ștergeți acest cont",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Protecție Ștergere Permanentă",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Afișează comanda de golire a folderelor Junk/Spam. Această acțiune nu va solicita confirmare și va șterge imediat toate mesajele din folderul de spam.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Afișează comanda pentru golirea folderului de spam.",
|
||||
"SettingsDiagnostics_Description": "Pentru dezvoltatori",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Partajați acest ID cu dezvoltatorii atunci când le cereți ajutor pentru problemele pe care le întâmpinați în Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "ID de Diagnosticare",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Insignă Bară de activități",
|
||||
"SettingsThreads_Description": "Organizați mesajele în fire de conversație.",
|
||||
"SettingsThreads_Title": "Aranjare mesaje în conversație",
|
||||
"SettingsThreads_Enabled_Description": "Grupați mesajele legate într-o singură conversație.",
|
||||
"SettingsThreads_Enabled_Title": "Activează firul de conversații",
|
||||
"SettingsThreadOrder_Description": "Alegeți cum sunt ordonate elementele într-un fir de conversație.",
|
||||
"SettingsThreadOrder_Title": "Sortarea elementelor firului de conversație",
|
||||
"SettingsThreadOrder_LastItemFirst": "Ultimul element, primul.",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Primul element, primul.",
|
||||
"SettingsUnlinkAccounts_Description": "Eliminați legătura dintre conturi. Acest lucru nu va șterge conturile dvs.",
|
||||
"SettingsUnlinkAccounts_Title": "Deasociere Conturi",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Începe",
|
||||
"WelcomeWindow_GetStartedDescription": "Adăugați contul dvs. Outlook, Gmail sau IMAP pentru a începe cu Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importă din contul tău Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importă dintr-un fișier JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Se importă preferințele și conturile sincronizate...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Nu au fost găsite conturi sincronizate în contul tău Wino. Dacă preferințele erau disponibile, acestea au fost restaurate. Folosește Începe pentru a adăuga un cont manual.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} conturi sincronizate sunt deja disponibile pe acest dispozitiv. Folosește Începe pentru a adăuga manual un alt cont, dacă este necesar.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "/** placeholder **/",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "/** placeholder **/",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "/** placeholder **/",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Transfer cu un fișier JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importați din sau exportați într-un fișier JSON local. Parolele, token-urile și alte informații sensibile nu sunt incluse.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importă",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exportă",
|
||||
"WinoAccount_Management_LocalDataSaved": "Datele exportate ale Wino au fost salvate în {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Fișierul JSON selectat nu conține un export Wino valid.",
|
||||
"WinoAccount_Management_LoadFailed": "/** placeholder **/",
|
||||
"WinoAccount_Management_ActionFailed": "/** placeholder **/",
|
||||
"WinoAccount_SettingsSection_Title": "/** placeholder **/",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Finalizați configurarea",
|
||||
"ProviderSelection_Title": "Alegeți furnizorul de e-mail",
|
||||
"ProviderSelection_Subtitle": "Selectați un furnizor mai jos pentru a adăuga contul dvs. de e-mail în Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Pasul {0} din 3",
|
||||
"ProviderSelection_IdentityTitle": "Identitatea contului",
|
||||
"ProviderSelection_IdentityDescription": "Alegeți cum va apărea acest cont în Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Furnizor",
|
||||
"ProviderSelection_ProviderSectionDescription": "Selectați serviciul pe care doriți să îl conectați.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Folosirea acestui cont pentru",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Alegeți dacă doriți mail, calendar sau ambele.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "În pasul următor, autentificarea securizată vă va conecta contul. Dacă activați calendarul, Wino se va conecta automat și la Outlook Calendar sau Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "În pasul următor veți introduce acreditările furnizorului. Mail utilizează IMAP/SMTP, iar calendarul poate folosi CalDAV sau poate rămâne local pe acest dispozitiv.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "În pasul următor veți introduce detaliile serverului. Mail utilizează IMAP/SMTP, iar calendarul poate folosi CalDAV sau poate rămâne local pe acest dispozitiv.",
|
||||
"ProviderSelection_AccountNameHeader": "Nume cont",
|
||||
"ProviderSelection_AccountNamePlaceholder": "ex. Personal, Birou",
|
||||
"ProviderSelection_UseForMail": "Poștă",
|
||||
"ProviderSelection_UseForCalendar": "Calendar",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Alegeți cel puțin o capabilitate înainte de a continua.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Dacă continuați doar cu calendarul, pagina următoare nu va solicita o adresă de e-mail.",
|
||||
"ProviderSelection_DisplayNameHeader": "Nume afișat",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "ex. John Doe",
|
||||
"ProviderSelection_EmailHeader": "Adresă de e-mail",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Выберите календарь, чтобы настроить его параметры.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Начальная синхронизация",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino синхронизировал вашу почту до {0} назад.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Подключенные функции",
|
||||
"AccountDetailsPage_CapabilityDescription": "Выберите, будет ли эта учетная запись использоваться для почты, календаря или обоих. Включение новой функции может потребовать повторного входа в систему.",
|
||||
"AccountCapability_MailOnly": "Только почта",
|
||||
"AccountCapability_CalendarOnly": "Только календарь",
|
||||
"AccountCapability_MailAndCalendar": "Почта и календарь",
|
||||
"AddHyperlink": "Добавить",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Background Synchronization",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Application has not been set to launch on Windows startup.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Применить тему",
|
||||
"Buttons_PopOut": "Открыть в отдельном окне",
|
||||
"Buttons_Browse": "Обзор",
|
||||
"Buttons_Back": "Назад",
|
||||
"Buttons_Cancel": "Отмена",
|
||||
"Buttons_Close": "Закрыть",
|
||||
"Buttons_Copy": "Копировать",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Удалить повторяющуюся серию",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Этот черновик будет удален. Вы хотите продолжить?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Удалить черновик",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Черновик всё ещё открыт. Сохранить его перед закрытием окна?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Закрыть окно",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Missing Subject",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Сообщение не имеет темы. Вы хотите продолжить?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "You can enable startup launch from Settings -> App Preferences.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Не выбрано сообщение",
|
||||
"NoMessageCrieteria": "Нет сообщений, соответствующих критериям поиска",
|
||||
"NoMessageEmptyFolder": "Эта папка пуста",
|
||||
"MailEmptyState_Title": "Нет учетных записей, поддерживающих почту",
|
||||
"MailEmptyState_Message": "У вас подключены учетные записи для календаря, но ни одна из них не поддерживает почту. Добавьте учетную запись почты или обновите существующую учетную запись, чтобы использовать почту.",
|
||||
"MailEmptyState_AddAccount": "Добавить учетную запись",
|
||||
"MailEmptyState_ManageAccounts": "Управление учетными записями",
|
||||
"Notifications_MultipleNotificationsMessage": "You have {0} new messages.",
|
||||
"Notifications_MultipleNotificationsTitle": "New Mail",
|
||||
"Notifications_WinoUpdatedMessage": "Ознакомьтесь с новой версией {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Удалить эту учетную запись",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Защита от окончательного удаления",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Показать команду очистки пустой папки в папках Спам. Это действие не будет запрашивать подтверждение и сразу удалит все письма в папке спама.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Показать команду очистки пустой папки в папках Спам",
|
||||
"SettingsDiagnostics_Description": "Для разработчиков",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Share this ID with the developers when asked to get help for the issues you experience in Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostic ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Taskbar Badge",
|
||||
"SettingsThreads_Description": "Организуйте сообщения в беседы.",
|
||||
"SettingsThreads_Title": "Беседы",
|
||||
"SettingsThreads_Enabled_Description": "Сгруппировать связанные сообщения в одну переписку.",
|
||||
"SettingsThreads_Enabled_Title": "Включить группировку переписки",
|
||||
"SettingsThreadOrder_Description": "Выберите порядок сортировки элементов внутри переписки.",
|
||||
"SettingsThreadOrder_Title": "Сортировка элементов переписки",
|
||||
"SettingsThreadOrder_LastItemFirst": "Последний элемент первым",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Первый элемент первым",
|
||||
"SettingsUnlinkAccounts_Description": "Remove the link between accounts. his will not delete your accounts.",
|
||||
"SettingsUnlinkAccounts_Title": "Отвязать учетные записи",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Начать, добавив учетную запись",
|
||||
"WelcomeWindow_GetStartedDescription": "Добавьте учетную запись Outlook, Gmail или IMAP, чтобы начать работу с Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Импорт из вашей учетной записи Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Импорт из файла JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Импорт синхронизированных настроек и учетных записей...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "В вашей учетной записи Wino не найдено синхронизированных учетных записей. Если настройки были доступны, они были восстановлены. Используйте Начать, чтобы добавить учетную запись вручную.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} синхронизированных учетных записей уже доступны на этом устройстве. При необходимости используйте Начать, чтобы вручную добавить еще одну учетную запись.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Пароли, токены и другие конфиденциальные данные не синхронизируются.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Импортированные на другом ПК учетные записи по-прежнему потребуют повторного входа в систему перед использованием.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Выполняется экспорт выбранных данных Wino...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Перенос с помощью файла JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Импортируйте или экспортируйте локальный файл JSON. Пароли, токены и другая конфиденциальная информация не включаются.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Импорт",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Экспорт",
|
||||
"WinoAccount_Management_LocalDataSaved": "Сохранены экспортированные данные Wino в {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Выбранный файл JSON не содержит допустимого экспорта Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Не удалось загрузить последнюю информацию об учетной записи Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "Запрос к учетной записи Wino не удалось выполнить.",
|
||||
"WinoAccount_SettingsSection_Title": "Учетная запись Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Завершение настройки",
|
||||
"ProviderSelection_Title": "Выберите поставщика электронной почты",
|
||||
"ProviderSelection_Subtitle": "Выберите ниже поставщика, чтобы добавить вашу учетную запись электронной почты в Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Шаг {0} из 3",
|
||||
"ProviderSelection_IdentityTitle": "Идентификатор учетной записи",
|
||||
"ProviderSelection_IdentityDescription": "Выберите, как эта учетная запись будет отображаться в Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Провайдер",
|
||||
"ProviderSelection_ProviderSectionDescription": "Выберите сервис, который вы хотите подключить.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Использовать эту учетную запись для",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Выберите, хотите ли вы использовать почту, календарь или оба.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "На следующем шаге безопасный вход подключит вашу учетную запись. Если вы включите календарь, Wino автоматически подключит календарь Outlook или Google Календарь.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "На следующем шаге вы введёте учётные данные вашего провайдера. Почта использует IMAP/SMTP, а календарь может использовать CalDAV или оставаться локальным на этом устройстве.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "На следующем шаге вы введёте данные сервера. Почта использует IMAP/SMTP, а календарь может использовать CalDAV или оставаться локальным на этом устройстве.",
|
||||
"ProviderSelection_AccountNameHeader": "Имя учетной записи",
|
||||
"ProviderSelection_AccountNamePlaceholder": "например, Личное, Рабочее",
|
||||
"ProviderSelection_UseForMail": "Почта",
|
||||
"ProviderSelection_UseForCalendar": "Календарь",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Выберите как минимум одну возможность перед продолжением.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Если вы продолжите только с календарём, на следующей странице адрес электронной почты не потребуется.",
|
||||
"ProviderSelection_DisplayNameHeader": "Отображаемое имя",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "например, Иван Иванов",
|
||||
"ProviderSelection_EmailHeader": "Адрес электронной почты",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Vyberte kalendár na konfiguráciu jeho nastavení.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Počiatočná synchronizácia",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino synchronizoval vašu poštu až do {0} späť.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Pripojené funkcie",
|
||||
"AccountDetailsPage_CapabilityDescription": "Vyberte, či sa tento účet používa na poštu, kalendár alebo oboje. Aktivácia novej funkcie môže vyžadovať opätovné prihlásenie.",
|
||||
"AccountCapability_MailOnly": "Iba pošta",
|
||||
"AccountCapability_CalendarOnly": "Iba kalendár",
|
||||
"AccountCapability_MailAndCalendar": "Pošta a kalendár",
|
||||
"AddHyperlink": "Pridať",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Synchronizácia na pozadí",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Aplikácia sa nebude spúšťať pri spustení systému Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Použiť motív",
|
||||
"Buttons_PopOut": "Otvoriť v novom okne",
|
||||
"Buttons_Browse": "Prehľadávať",
|
||||
"Buttons_Back": "Späť",
|
||||
"Buttons_Cancel": "Zrušiť",
|
||||
"Buttons_Close": "Zavrieť",
|
||||
"Buttons_Copy": "Kopírovať",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Odstrániť opakovanú sériu",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Tento koncept sa zahodí. Chcete pokračovať?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Zrušiť koncept",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Návrh je stále otvorený. Uložiť ho pred zatvorením okna?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Zatvoriť okno",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Chýbajúci predmet",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Správa nemá vyplnený predmet. Chcete pokračovať?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Spúšťanie pri štarte môžete zapnúť v Nastavenia -> Nastavenia aplikácie.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Nie je vybraná žiadna správa",
|
||||
"NoMessageCrieteria": "Žiadne správy nezodpovedajú vašim kritériám vyhľadávania",
|
||||
"NoMessageEmptyFolder": "Tento priečinok je prázdny",
|
||||
"MailEmptyState_Title": "Žiadne účty s poštou nie sú zapojené",
|
||||
"MailEmptyState_Message": "Máte účty pripojené k kalendáru, ale žiadny z nich nemá povolenú poštu. Pridajte poštový účet alebo aktualizujte existujúci účet, aby používal poštu.",
|
||||
"MailEmptyState_AddAccount": "Pridať účet",
|
||||
"MailEmptyState_ManageAccounts": "Spravovať účty",
|
||||
"Notifications_MultipleNotificationsMessage": "Máte nové správy: {0}.",
|
||||
"Notifications_MultipleNotificationsTitle": "Nová pošta",
|
||||
"Notifications_WinoUpdatedMessage": "Pozrite si novú verziu {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Odstrániť tento účet",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Ochrana pred trvalým odstránením",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Zobraziť príkaz na vyprázdnenie priečinka v priečinkoch Nevyžiadaná pošta / Spam. Táto akcia nepožaduje potvrdenie a okamžite vymaže všetky e-maily zo spamového priečinka.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Zobraziť príkaz na vyprázdnenie spamového priečinka",
|
||||
"SettingsDiagnostics_Description": "Pre vývojárov",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Zdieľajte toto ID s vývojármi, keď budete požiadaní o pomoc pri problémoch, ktoré sa vám vyskytnú v aplikácii Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Diagnostické ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Odznak na paneli úloh",
|
||||
"SettingsThreads_Description": "Usporiadanie správ do konverzácií.",
|
||||
"SettingsThreads_Title": "Zobrazenie v režime konverzácie",
|
||||
"SettingsThreads_Enabled_Description": "Zoskupiť súvisiace správy do jednej konverzácie.",
|
||||
"SettingsThreads_Enabled_Title": "Povoliť vlákna konverzácie",
|
||||
"SettingsThreadOrder_Description": "Vyberte spôsob zoradenia položiek vo vlákne konverzácie.",
|
||||
"SettingsThreadOrder_Title": "Zoradenie položiek vlákna",
|
||||
"SettingsThreadOrder_LastItemFirst": "Posledná položka ako prvá",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Prvá položka ako prvá",
|
||||
"SettingsUnlinkAccounts_Description": "Odstránenie prepojenia medzi účtami. Tým sa vaše účty neodstránia.",
|
||||
"SettingsUnlinkAccounts_Title": "Odpojenie účtov",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Začať pridaním účtu",
|
||||
"WelcomeWindow_GetStartedDescription": "Pridajte svoj účet Outlook, Gmail alebo IMAP a začnite používať Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Importovať zo svojho Wino účtu",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Importovať zo súboru JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Importujem synchronizované preferencie a účty...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Nenašli sa žiadne synchronizované účty vo vašom Wino účte. Ak boli k dispozícii preferencie, boli obnovené. Použite možnosť Začať na manuálne pridanie účtu.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} synchronizovaných účtov je už k dispozícii na tomto zariadení. Ak potrebujete, použite možnosť Začať na ručné pridanie ďalšieho účtu.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Heslá, tokeny a ďalšie citlivé údaje nie sú synchronizované.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Importované účty na inom PC budú vyžadovať opätovné prihlásenie pred ich použitím.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Exportujem vybrané údaje Wino.",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Prenos pomocou súboru JSON",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Importovať alebo exportovať do lokálneho súboru JSON. Heslá, tokeny a ďalšie citlivé informácie nie sú zahrnuté.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Importovať",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Exportovať",
|
||||
"WinoAccount_Management_LocalDataSaved": "Uložili ste exportované dáta Wino do {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Vybraný JSON súbor neobsahuje platný export Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino nemohol načítať najnovšie informácie o Wino účte.",
|
||||
"WinoAccount_Management_ActionFailed": "Žiadosť o Wino účet nemohla byť dokončená.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino účet",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Dokončiť nastavenie",
|
||||
"ProviderSelection_Title": "Vyberte si svojho poskytovateľa e-mailu",
|
||||
"ProviderSelection_Subtitle": "Vyberte nižšie uvedeného poskytovateľa a pridajte svoj e-mailový účet do Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Krok {0} z 3",
|
||||
"ProviderSelection_IdentityTitle": "Identita účtu",
|
||||
"ProviderSelection_IdentityDescription": "Vyberte, ako sa tento účet zobrazí vo Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Poskytovateľ",
|
||||
"ProviderSelection_ProviderSectionDescription": "Vyberte službu, ktorú chcete pripojiť.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Použiť tento účet pre",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Vyberte, či chcete poštu, kalendár alebo oboje.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Na ďalšom kroku zabezpečené prihlásenie pripojí váš účet. Ak povolíte kalendár, Wino tiež automaticky pripojí Kalendár Outlook alebo Kalendár Google.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Na ďalšom kroku zadáte prihlasovacie údaje poskytovateľa. Pošta používa IMAP/SMTP a kalendár môže používať CalDav alebo zostať lokálny na tomto zariadení.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Na ďalšom kroku zadáte údaje o serveri. Pošta používa IMAP/SMTP a kalendár môže používať CalDav alebo zostať lokálny na tomto zariadení.",
|
||||
"ProviderSelection_AccountNameHeader": "Názov účtu",
|
||||
"ProviderSelection_AccountNamePlaceholder": "napr. Osobný, Práca",
|
||||
"ProviderSelection_UseForMail": "Pošta",
|
||||
"ProviderSelection_UseForCalendar": "Kalendár",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Pred pokračovaním vyberte aspoň jednu možnosť.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Ak budete pokračovať iba s kalendárom, na ďalšej stránke nebude vyžadovaná e-mailová adresa.",
|
||||
"ProviderSelection_DisplayNameHeader": "Zobrazené meno",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "napr. Ján Novak",
|
||||
"ProviderSelection_EmailHeader": "E-mailová adresa",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Ayarlarını yapılandırmak için bir takvim seçin.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "İlk senkronizasyon",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino, {0} tarihine kadar geriye giderek maillerinizi senkronize etti.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Bağlı Özellikler",
|
||||
"AccountDetailsPage_CapabilityDescription": "Bu hesabın posta, takvim için mi yoksa her ikisi için mi kullanılacağını seçin. Yeni bir özellik etkinleştirildiğinde yeniden oturum açmanız istenebilir.",
|
||||
"AccountCapability_MailOnly": "Sadece Posta",
|
||||
"AccountCapability_CalendarOnly": "Sadece Takvim",
|
||||
"AccountCapability_MailAndCalendar": "Posta + Takvim",
|
||||
"AddHyperlink": "Ekle",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Arkaplan Senkronizasyonu",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Uygulama Windows açılırken çalıştırılacak şekilde ayarlanmadı.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Temayı Uygula",
|
||||
"Buttons_PopOut": "Yeni pencerede aç",
|
||||
"Buttons_Browse": "Gözat",
|
||||
"Buttons_Back": "Geri",
|
||||
"Buttons_Cancel": "İptal Et",
|
||||
"Buttons_Close": "Kapat",
|
||||
"Buttons_Copy": "Kopyala",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Tekrarlayan Seriyi Sil",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Taslak yoksayılacaktır. Devam etmek istiyor musunuz?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Taslağı Sil",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Bir taslak hâlâ açık. Pencereyi kapatmadan önce kaydetmek ister misiniz?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Pencereyi Kapat",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Konu Eksik",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Mesajın bir konusu yok. Devam etmek istiyor musunuz?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Oturum açma seçeneklerini Ayarlar -> Uygulama Tercihleri kısmından değiştirebilirsiniz.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "İleti seçilmedi",
|
||||
"NoMessageCrieteria": "Kriterlere uygun mesaj bulunamadı",
|
||||
"NoMessageEmptyFolder": "Bu klasör boş",
|
||||
"MailEmptyState_Title": "Posta için etkinleştirilmiş hesap yok",
|
||||
"MailEmptyState_Message": "Takvim için bağlı hesaplarınız var, ancak bunlardan hiçbiri posta için etkinleştirilmedi. Bir posta hesabı ekleyin veya mevcut hesabı posta kullanacak şekilde güncelleyin.",
|
||||
"MailEmptyState_AddAccount": "Hesap Ekle",
|
||||
"MailEmptyState_ManageAccounts": "Hesapları Yönet",
|
||||
"Notifications_MultipleNotificationsMessage": "{0} yeni mesajınız var.",
|
||||
"Notifications_MultipleNotificationsTitle": "Yeni E-Posta",
|
||||
"Notifications_WinoUpdatedMessage": "{0} sürümünü deneyin",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Bu hesabı sil",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Kalıcı Silme Koruması",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Gereksiz Posta/Spam klasörlerinde boş klasör komutunu göster. Bu işlem onay istemeyecek ve spam klasöründeki tüm mailleri hemen silecektir.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Boş Spam Klasörü Komutunu Göster",
|
||||
"SettingsDiagnostics_Description": "Geliştiriciler için",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Bu ID'yi geliştiriciler ile paylaşarak Wino Mail ile yaşadığınız sorunlar hakkında çözümlere ulaşabilirsiniz.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "Hata Ayıklama ID'si",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Sistem Çubuğu Balonu",
|
||||
"SettingsThreads_Description": "Bağlı mesajları tek bir liste halinde kategorize eder.",
|
||||
"SettingsThreads_Title": "Mesaj Gruplama",
|
||||
"SettingsThreads_Enabled_Description": "İlişkili mesajları tek bir konuşmada grupla.",
|
||||
"SettingsThreads_Enabled_Title": "İletilerin konuşma zincirini etkinleştir.",
|
||||
"SettingsThreadOrder_Description": "Bir konuşma içindeki öğelerin nasıl sıralanacağını seçin.",
|
||||
"SettingsThreadOrder_Title": "İleti Sıralaması",
|
||||
"SettingsThreadOrder_LastItemFirst": "Son öğe önce",
|
||||
"SettingsThreadOrder_FirstItemFirst": "İlk öğe önce",
|
||||
"SettingsUnlinkAccounts_Description": "Hesap bağlantısını koparın. Bu işlem hesaplarınızı silmez.",
|
||||
"SettingsUnlinkAccounts_Title": "Hesap Bağlantısını Kopar",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Bir hesap ekleyerek başlayın",
|
||||
"WelcomeWindow_GetStartedDescription": "Wino Mail'e başlamak için Outlook, Gmail veya IMAP hesabınızı ekleyin.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Wino Hesabınızdan İçe Aktar",
|
||||
"WelcomeWindow_ImportFromJsonFile": "JSON dosyasından içe aktar",
|
||||
"WelcomeWindow_ImportInProgress": "Senkronize edilmiş tercihleriniz ve hesaplarınız içe aktarılıyor...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "Wino Hesabınızda senkronize edilen hesap bulunamadı. Tercihler mevcutsa geri yüklendi. Bir hesabı manuel olarak eklemek için Başla'ya tıklayın.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} senkronize edilmiş hesap bu cihazda zaten mevcut. Gerekirse başka bir hesap eklemek için Başla'ya tıklayın.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Şifreler, erişim belirteçleri ve diğer hassas bilgiler senkronize edilmez.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Başka bir PC'de içe aktarılan hesaplar, kullanılabilir hale gelmeden önce yine oturum açmanızı gerektirecek.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Seçili Wino verileri dışa aktarılıyor...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "JSON Dosyası ile Aktarım",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Yerel bir JSON dosyasından içe aktarın veya dışa aktarın. Parolalar, erişim belirteçleri ve diğer hassas bilgiler dahil değildir.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "İçe aktar",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Dışa aktar",
|
||||
"WinoAccount_Management_LocalDataSaved": "Dışa aktarılan Wino verinizi {0} konumuna kaydettiniz.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Seçilen JSON dosyasında geçerli bir Wino dışa aktarımı bulunmuyor.",
|
||||
"WinoAccount_Management_LoadFailed": "Wino en son Wino Hesap bilgilerini yükleyemedi.",
|
||||
"WinoAccount_Management_ActionFailed": "Wino Hesap isteği tamamlanamadı.",
|
||||
"WinoAccount_SettingsSection_Title": "Wino Hesabı",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Kurulumu Tamamla",
|
||||
"ProviderSelection_Title": "E-posta sağlayıcınızı seçin",
|
||||
"ProviderSelection_Subtitle": "Aşağıdaki sağlayıcıyı seçerek e-posta hesabınızı Wino Mail'e ekleyin.",
|
||||
"ProviderSelection_StepProgress": "3 adımın {0}. Adımı",
|
||||
"ProviderSelection_IdentityTitle": "Hesap Kimliği",
|
||||
"ProviderSelection_IdentityDescription": "Bu hesabın Wino'da nasıl görüneceğini seçin.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Sağlayıcı",
|
||||
"ProviderSelection_ProviderSectionDescription": "Bağlamak istediğiniz hizmeti seçin.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Bu hesap için kullanın",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Posta, takvim veya her ikisini birden kullanmak istiyorsanız seçin.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "Bir sonraki adımda güvenli oturum açma hesabınızı bağlayacaktır. Takvim etkinleştirildiğinde, Wino Outlook Takvimi veya Google Takvimi'ni otomatik olarak bağlayacaktır.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "Bir sonraki adımda sağlayıcı kimlik bilgilerinizi gireceksiniz. Posta IMAP/SMTP kullanır; takvim CalDAV kullanabilir veya bu cihazda yerel olarak kalabilir.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "Bir sonraki adımda sunucu bilgilerinizi gireceksiniz. Posta IMAP/SMTP kullanır; takvim CalDAV kullanabilir veya bu cihazda yerel olarak kalabilir.",
|
||||
"ProviderSelection_AccountNameHeader": "Hesap Adı",
|
||||
"ProviderSelection_AccountNamePlaceholder": "örn. Kişisel, İş",
|
||||
"ProviderSelection_UseForMail": "Posta",
|
||||
"ProviderSelection_UseForCalendar": "Takvim",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Devam etmeden önce en az bir özellik seçin.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Yalnızca takvim ile devam ederseniz, sonraki sayfada bir e-posta adresi gerekmeyecektir.",
|
||||
"ProviderSelection_DisplayNameHeader": "Görünen Ad",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "örn. John Doe",
|
||||
"ProviderSelection_EmailHeader": "E-posta Adresi",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "Виберіть календар, щоб налаштувати його параметри.",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "Початкова синхронізація",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino синхронізував ваші листи до {0} назад.",
|
||||
"AccountDetailsPage_CapabilityTitle": "Підключені функції",
|
||||
"AccountDetailsPage_CapabilityDescription": "Виберіть, чи використовується цей обліковий запис для пошти, календаря або обох. Увімкнення нової функції може вимагати повторного входу.",
|
||||
"AccountCapability_MailOnly": "Лише пошта",
|
||||
"AccountCapability_CalendarOnly": "Лише календар",
|
||||
"AccountCapability_MailAndCalendar": "Пошта та календар",
|
||||
"AddHyperlink": "Додати",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "Фонова синхронізація",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "Програму не налаштовано на автозапуск під час запуску Windows.",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "Застосувати тему",
|
||||
"Buttons_PopOut": "Відкрити у окремому вікні",
|
||||
"Buttons_Browse": "Огляд",
|
||||
"Buttons_Back": "Назад",
|
||||
"Buttons_Cancel": "Скасувати",
|
||||
"Buttons_Close": "Закрити",
|
||||
"Buttons_Copy": "Копіювати",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "Видалити повторювану серію",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "Цю чернетку буде відкинуто. Продовжити?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "Відкинути чернетку",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "Чернетку ще відкрито. Зберегти її перед закриттям вікна?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "Закрити вікно",
|
||||
"DialogMessage_EmptySubjectConfirmation": "Теми не вказано",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "Повідомлення не має теми. Продовжити?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "Увімкнути автозапуск можна в Налаштуваннях -> Параметри програми.",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "Повідомлення не вибрано",
|
||||
"NoMessageCrieteria": "Немає повідомлень, що відповідають критеріям пошуку",
|
||||
"NoMessageEmptyFolder": "Ця тека порожня",
|
||||
"MailEmptyState_Title": "Немає облікових записів, увімкнених для пошти",
|
||||
"MailEmptyState_Message": "У вас підключені облікові записи для календаря, але жоден з них не увімкнено для пошти. Додайте обліковий запис пошти або оновіть існуючий обліковий запис, щоб використовувати пошту.",
|
||||
"MailEmptyState_AddAccount": "Додати обліковий запис",
|
||||
"MailEmptyState_ManageAccounts": "Керувати обліковими записами",
|
||||
"Notifications_MultipleNotificationsMessage": "У вас {0} нових повідомлень.",
|
||||
"Notifications_MultipleNotificationsTitle": "Свіжа пошта",
|
||||
"Notifications_WinoUpdatedMessage": "Погляньте на нову версію {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "Видалити цей обліковий запис",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "Захист від остаточного видалення",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "Показати команду очищення порожньої папки у папках Небажана пошта/Спам. Ця дія не вимагатиме підтвердження і негайно видалить усі листи з папки зі спамом.",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "Показати команду очищення порожньої папки Спам",
|
||||
"SettingsDiagnostics_Description": "Для розробників",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "Поділіться цим ID з розробниками, коли попросять, щоб отримати допомогу щодо проблем, з якими Ви зіткнулися у Wino Mail.",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "ID діагностики",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "Значок на панелі завдань",
|
||||
"SettingsThreads_Description": "Організуйте повідомлення в ланцюжки розмов.",
|
||||
"SettingsThreads_Title": "Ланцюжки розмов",
|
||||
"SettingsThreads_Enabled_Description": "Групуйте пов'язані повідомлення в одну розмову.",
|
||||
"SettingsThreads_Enabled_Title": "Увімкнути групування розмов.",
|
||||
"SettingsThreadOrder_Description": "Виберіть порядок сортування елементів у розмові.",
|
||||
"SettingsThreadOrder_Title": "Сортування елементів розмови",
|
||||
"SettingsThreadOrder_LastItemFirst": "Останній елемент першим",
|
||||
"SettingsThreadOrder_FirstItemFirst": "Перший елемент першим",
|
||||
"SettingsUnlinkAccounts_Description": "Видалити зв'язку між обліковими записами. Це не видалить самі облікові записи.",
|
||||
"SettingsUnlinkAccounts_Title": "Відв'язати облікові записи",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "Почати, додавши обліковий запис",
|
||||
"WelcomeWindow_GetStartedDescription": "Додайте ваш Outlook, Gmail або IMAP обліковий запис, щоб почати з Wino Mail.",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "Імпортувати з вашого облікового запису Wino",
|
||||
"WelcomeWindow_ImportFromJsonFile": "Імпортувати з файлу JSON",
|
||||
"WelcomeWindow_ImportInProgress": "Імпорт ваших синхронізованих налаштувань та облікових записів...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "У вашому обліковому записі Wino не знайдено синхронізованих облікових записів. Якщо налаштування були доступні, вони відновлені. Використайте 'Почати', щоб додати обліковий запис вручну.",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} синхронізованих облікових записів вже доступні на цьому пристрої. Використайте 'Почати', щоб додати ще один обліковий запис вручну, якщо потрібно.",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Паролі, токени та інша чутлива інформація не синхронізуються.",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Імпортовані облікові записи на іншому ПК все ще потребуватимуть повторного входу перед їх використанням.",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "Експорт ваших вибраних даних Wino...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "Перенесення за допомогою JSON-файлу",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "Імпорт або експорт до локального файлу JSON. Паролі, токени та інша чутлива інформація не включаються.",
|
||||
"WinoAccount_Management_LocalDataImportAction": "Імпортувати",
|
||||
"WinoAccount_Management_LocalDataExportAction": "Експортувати",
|
||||
"WinoAccount_Management_LocalDataSaved": "Збережено ваші дані Wino, експортовані до {0}.",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "Вибраний файл JSON не містить дійсного експорту Wino.",
|
||||
"WinoAccount_Management_LoadFailed": "Не вдалося завантажити останню інформацію про обліковий запис Wino.",
|
||||
"WinoAccount_Management_ActionFailed": "Запит до облікового запису Wino не вдалося виконати.",
|
||||
"WinoAccount_SettingsSection_Title": "Обліковий запис Wino",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "Завершити налаштування",
|
||||
"ProviderSelection_Title": "Виберіть постачальника електронної пошти",
|
||||
"ProviderSelection_Subtitle": "Виберіть постачальника нижче, щоб додати свій обліковий ящик електронної пошти до Wino Mail.",
|
||||
"ProviderSelection_StepProgress": "Крок {0} з 3",
|
||||
"ProviderSelection_IdentityTitle": "Ідентифікація облікового запису",
|
||||
"ProviderSelection_IdentityDescription": "Виберіть, як цей обліковий запис відображатиметься в Wino.",
|
||||
"ProviderSelection_ProviderSectionTitle": "Провайдер",
|
||||
"ProviderSelection_ProviderSectionDescription": "Виберіть службу, яку хочете підключити.",
|
||||
"ProviderSelection_CapabilitySectionTitle": "Використовувати цей обліковий запис для",
|
||||
"ProviderSelection_CapabilitySectionDescription": "Виберіть, чи хочете використовувати пошту, календар або обидва.",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "На наступному кроці безпечний вхід з'єднає ваш обліковий запис. Якщо увімкнено календар, Wino також автоматично підключить календар Outlook або Google Calendar.",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "На наступному кроці ви введете облікові дані провайдера. Пошта використовує IMAP/SMTP, а календар може використовувати CalDAV або залишитися локальним на цьому пристрої.",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "На наступному кроці ви введете дані сервера. Пошта використовує IMAP/SMTP, а календар може використовувати CalDAV або залишитися локальним на цьому пристрої.",
|
||||
"ProviderSelection_AccountNameHeader": "Назва облікового запису",
|
||||
"ProviderSelection_AccountNamePlaceholder": "наприклад, Особистий, Робочий",
|
||||
"ProviderSelection_UseForMail": "Пошта",
|
||||
"ProviderSelection_UseForCalendar": "Календар",
|
||||
"ProviderSelection_CapabilityValidationMessage": "Виберіть хоча б одну можливість перед продовженням.",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "Якщо продовжити лише зcalendar, на наступній сторінці адреса електронної пошти не буде потрібна.",
|
||||
"ProviderSelection_DisplayNameHeader": "Відображуване ім'я",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "наприклад, Іван Іванов",
|
||||
"ProviderSelection_EmailHeader": "Електронна адреса",
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
"AccountDetailsPage_CalendarListDescription": "选择日历以配置其设置",
|
||||
"AccountDetailsPage_InitialSynchronization_Title": "初次同步",
|
||||
"AccountDetailsPage_InitialSynchronization_Description": "Wino 已将邮件同步到回溯到 {0} 的时间点。",
|
||||
"AccountDetailsPage_CapabilityTitle": "已连接的功能",
|
||||
"AccountDetailsPage_CapabilityDescription": "选择此账户用于邮件、日历,或两者皆用。启用新功能可能会要求您重新登录。",
|
||||
"AccountCapability_MailOnly": "仅邮件",
|
||||
"AccountCapability_CalendarOnly": "仅日历",
|
||||
"AccountCapability_MailAndCalendar": "邮件与日历",
|
||||
"AddHyperlink": "添加",
|
||||
"AppCloseBackgroundSynchronizationWarningTitle": "后台同步",
|
||||
"AppCloseStartupLaunchDisabledWarningMessageFirstLine": "应用尚未设置为随 Windows 自启。",
|
||||
@@ -76,6 +81,7 @@
|
||||
"Buttons_ApplyTheme": "应用主题",
|
||||
"Buttons_PopOut": "弹出",
|
||||
"Buttons_Browse": "浏览",
|
||||
"Buttons_Back": "返回",
|
||||
"Buttons_Cancel": "取消",
|
||||
"Buttons_Close": "关闭",
|
||||
"Buttons_Copy": "复制",
|
||||
@@ -299,6 +305,8 @@
|
||||
"DialogMessage_DeleteRecurringSeriesTitle": "删除重复系列",
|
||||
"DialogMessage_DiscardDraftConfirmationMessage": "草稿将被删除。你想要继续吗?",
|
||||
"DialogMessage_DiscardDraftConfirmationTitle": "舍弃草稿",
|
||||
"DialogMessage_CloseDraftWindowConfirmationMessage": "草稿仍未保存。在关闭窗口前保存吗?",
|
||||
"DialogMessage_CloseDraftWindowConfirmationTitle": "关闭窗口",
|
||||
"DialogMessage_EmptySubjectConfirmation": "缺少标题",
|
||||
"DialogMessage_EmptySubjectConfirmationMessage": "邮件没有主题。您想要继续吗?",
|
||||
"DialogMessage_EnableStartupLaunchDeniedMessage": "可以在「设置」-「应用设置」中启用自启动。",
|
||||
@@ -681,6 +689,10 @@
|
||||
"NoMailSelected": "未选择任何邮件",
|
||||
"NoMessageCrieteria": "没有符合搜索条件的邮件",
|
||||
"NoMessageEmptyFolder": "此文件夹为空",
|
||||
"MailEmptyState_Title": "没有启用邮件的账户",
|
||||
"MailEmptyState_Message": "您已连接日历账户,但没有任何账户启用邮件。请添加邮件账户,或更新现有账户以使用邮件。",
|
||||
"MailEmptyState_AddAccount": "添加账户",
|
||||
"MailEmptyState_ManageAccounts": "管理账户",
|
||||
"Notifications_MultipleNotificationsMessage": "您有 {0} 条新消息。",
|
||||
"Notifications_MultipleNotificationsTitle": "新邮件",
|
||||
"Notifications_WinoUpdatedMessage": "检查新版本 {0}",
|
||||
@@ -824,6 +836,8 @@
|
||||
"SettingsDeleteAccount_Title": "删除此账户",
|
||||
"SettingsDeleteProtection_Description": "Should Wino ask you for confirmation every time you try to permanently delete a mail using Shift + Del keys?",
|
||||
"SettingsDeleteProtection_Title": "永久性删除保护",
|
||||
"SettingsEmptyJunkFolderCommand_Description": "在垃圾邮件/垃圾箱文件夹中显示清空文件夹命令。此操作不会要求确认,将立即删除垃圾邮件文件夹中的所有邮件。",
|
||||
"SettingsEmptyJunkFolderCommand_Title": "显示清空垃圾邮件文件夹的命令",
|
||||
"SettingsDiagnostics_Description": "开发者选项",
|
||||
"SettingsDiagnostics_DiagnosticId_Description": "如需联系开发人员请求帮助,请提供此 ID。",
|
||||
"SettingsDiagnostics_DiagnosticId_Title": "诊断 ID",
|
||||
@@ -1074,6 +1088,12 @@
|
||||
"SettingsTaskbarBadge_Title": "任务栏徽标",
|
||||
"SettingsThreads_Description": "将邮件组织成对话主题。",
|
||||
"SettingsThreads_Title": "邮件会话",
|
||||
"SettingsThreads_Enabled_Description": "将相关邮件聚合为一个对话。",
|
||||
"SettingsThreads_Enabled_Title": "启用对话串联",
|
||||
"SettingsThreadOrder_Description": "选择对话中条目的排序方式。",
|
||||
"SettingsThreadOrder_Title": "对话项排序",
|
||||
"SettingsThreadOrder_LastItemFirst": "最后一项在前",
|
||||
"SettingsThreadOrder_FirstItemFirst": "第一项在前",
|
||||
"SettingsUnlinkAccounts_Description": "删除账户之间的链接。这不会删除您的账户。",
|
||||
"SettingsUnlinkAccounts_Title": "取消链接账户",
|
||||
"SettingsMailRendering_ActionLabels_Title": "Action labels",
|
||||
@@ -1339,6 +1359,7 @@
|
||||
"WelcomeWindow_GetStartedButton": "开始添加账户",
|
||||
"WelcomeWindow_GetStartedDescription": "添加您的 Outlook、Gmail 或 IMAP 账户来开始使用 Wino Mail。",
|
||||
"WelcomeWindow_ImportFromWinoAccount": "从您的 Wino 账户导入",
|
||||
"WelcomeWindow_ImportFromJsonFile": "从 JSON 文件导入",
|
||||
"WelcomeWindow_ImportInProgress": "正在导入同步的偏好设置和账户...",
|
||||
"WelcomeWindow_ImportNoAccountsFound": "在 Wino 账户中未找到同步的账户。如果存在偏好设置,它们已被还原。使用“开始使用”手动添加账户。",
|
||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} 同步账户已在此设备上可用。如有需要,请使用“开始使用”手动再添加一个账户。",
|
||||
@@ -1418,6 +1439,12 @@
|
||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "密码、令牌和其他敏感信息不会同步。",
|
||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "在另一台电脑上导入的账户仍需要您再次登录后才能使用。",
|
||||
"WinoAccount_Management_ExportDialog_InProgress": "正在导出您选择的 Wino 数据...",
|
||||
"WinoAccount_Management_LocalDataSectionTitle": "通过 JSON 文件传输",
|
||||
"WinoAccount_Management_LocalDataSectionDescription": "从本地 JSON 文件导入或导出。密码、令牌和其他敏感信息不包含在内。",
|
||||
"WinoAccount_Management_LocalDataImportAction": "导入",
|
||||
"WinoAccount_Management_LocalDataExportAction": "导出",
|
||||
"WinoAccount_Management_LocalDataSaved": "已将导出的 Wino 数据保存到 {0}。",
|
||||
"WinoAccount_Management_LocalDataInvalidFile": "所选 JSON 文件不包含有效的 Wino 导出。",
|
||||
"WinoAccount_Management_LoadFailed": "无法加载最新的 Wino 账户信息。",
|
||||
"WinoAccount_Management_ActionFailed": "Wino 账户请求无法完成。",
|
||||
"WinoAccount_SettingsSection_Title": "Wino 账户",
|
||||
@@ -1514,8 +1541,22 @@
|
||||
"WelcomeWizard_Step3Title": "完成设置",
|
||||
"ProviderSelection_Title": "选择您的电子邮件提供商",
|
||||
"ProviderSelection_Subtitle": "在下方选择一个提供商,以将您的电子邮件账户添加到 Wino Mail。",
|
||||
"ProviderSelection_StepProgress": "步骤 {0} / 3",
|
||||
"ProviderSelection_IdentityTitle": "账户身份",
|
||||
"ProviderSelection_IdentityDescription": "选择此账户在 Wino 中的显示方式。",
|
||||
"ProviderSelection_ProviderSectionTitle": "提供商",
|
||||
"ProviderSelection_ProviderSectionDescription": "选择要连接的服务。",
|
||||
"ProviderSelection_CapabilitySectionTitle": "将此账户用于",
|
||||
"ProviderSelection_CapabilitySectionDescription": "选择要使用邮件、日历,还是两者都使用。",
|
||||
"ProviderSelection_CapabilityProviderDescription_OAuth": "在下一步中,安全登录将连接您的账户。如果启用日历,Wino 也会自动连接 Outlook 日历或 Google 日历。",
|
||||
"ProviderSelection_CapabilityProviderDescription_SpecialImap": "在下一步中,您将输入提供商凭据。邮件使用 IMAP/SMTP,日历可以使用 CalDAV,或在此设备上本地存储。",
|
||||
"ProviderSelection_CapabilityProviderDescription_CustomServer": "在下一步中,您将输入服务器详细信息。邮件使用 IMAP/SMTP,日历可以使用 CalDAV,或在此设备上本地存储。",
|
||||
"ProviderSelection_AccountNameHeader": "账户名",
|
||||
"ProviderSelection_AccountNamePlaceholder": "例如 个人、工作",
|
||||
"ProviderSelection_UseForMail": "邮件",
|
||||
"ProviderSelection_UseForCalendar": "日历",
|
||||
"ProviderSelection_CapabilityValidationMessage": "继续前请至少选择一个功能。",
|
||||
"ProviderSelection_CalendarOnlyServerHint": "如果仅使用日历继续,下一页将不需要电子邮件地址。",
|
||||
"ProviderSelection_DisplayNameHeader": "显示名称",
|
||||
"ProviderSelection_DisplayNamePlaceholder": "例如 约翰·多伊",
|
||||
"ProviderSelection_EmailHeader": "电子邮件地址",
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace Wino.Core.Domain.Validation;
|
||||
|
||||
public static class MailAccountAddressValidator
|
||||
{
|
||||
public static bool IsValid(string address)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address))
|
||||
return false;
|
||||
|
||||
var trimmedAddress = address.Trim();
|
||||
|
||||
if (trimmedAddress.Contains('\r') || trimmedAddress.Contains('\n'))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var parsedAddress = new MailAddress(trimmedAddress);
|
||||
return parsedAddress.Address.Equals(trimmedAddress, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetDomain(string address, out string domain)
|
||||
{
|
||||
domain = string.Empty;
|
||||
|
||||
if (!IsValid(address))
|
||||
return false;
|
||||
|
||||
var trimmedAddress = address.Trim();
|
||||
var separatorIndex = trimmedAddress.LastIndexOf('@');
|
||||
|
||||
if (separatorIndex <= 0 || separatorIndex >= trimmedAddress.Length - 1)
|
||||
return false;
|
||||
|
||||
domain = trimmedAddress[(separatorIndex + 1)..];
|
||||
return !string.IsNullOrWhiteSpace(domain);
|
||||
}
|
||||
|
||||
public static bool IsImplicitlyResolvableHost(string host)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(host))
|
||||
return false;
|
||||
|
||||
var normalizedHost = host.Trim().TrimEnd('.');
|
||||
if (string.IsNullOrWhiteSpace(normalizedHost))
|
||||
return false;
|
||||
|
||||
if (normalizedHost.Equals("localhost", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
var hostType = Uri.CheckHostName(normalizedHost);
|
||||
if (hostType is UriHostNameType.IPv4 or UriHostNameType.IPv6)
|
||||
return true;
|
||||
|
||||
return normalizedHost.IndexOf('.') < 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||
@@ -15,6 +15,7 @@ public class HtmlPreviewVisitorTests
|
||||
<html>
|
||||
<body onload="alert('x')">
|
||||
<h1 onclick="evil()">hello</h1>
|
||||
<link rel="stylesheet" href="https://tracker.example/mail.css" />
|
||||
<script>alert('xss')</script>
|
||||
<iframe src="https://malicious.example"></iframe>
|
||||
<object data="https://malicious.example/file.swf"></object>
|
||||
@@ -34,6 +35,7 @@ public class HtmlPreviewVisitorTests
|
||||
|
||||
// Assert
|
||||
output.Should().NotContain("<script", "script tags must be blocked in rendered html");
|
||||
output.Should().NotContain("<link", "external stylesheet tags must be blocked in rendered html");
|
||||
output.Should().NotContain("<iframe", "iframe tags must be blocked in rendered html");
|
||||
output.Should().NotContain("<object", "object tags must be blocked in rendered html");
|
||||
output.Should().NotContain("onload=", "event handler attributes must be stripped");
|
||||
|
||||
@@ -83,7 +83,7 @@ public class AccountServiceTests : IAsyncLifetime
|
||||
var secondAccountId = Guid.NewGuid();
|
||||
|
||||
await _accountService.CreateAccountAsync(
|
||||
CreateImapAccount(firstAccountId),
|
||||
CreateImapAccount(firstAccountId, "IMAP Test Account 1", "imap1@test.local"),
|
||||
new CustomServerInformation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -92,7 +92,7 @@ public class AccountServiceTests : IAsyncLifetime
|
||||
});
|
||||
|
||||
await _accountService.CreateAccountAsync(
|
||||
CreateImapAccount(secondAccountId),
|
||||
CreateImapAccount(secondAccountId, "IMAP Test Account 2", "imap2@test.local"),
|
||||
new CustomServerInformation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -119,13 +119,13 @@ public class AccountServiceTests : IAsyncLifetime
|
||||
.BeGreaterThanOrEqualTo(50);
|
||||
}
|
||||
|
||||
private static MailAccount CreateImapAccount(Guid accountId)
|
||||
private static MailAccount CreateImapAccount(Guid accountId, string name = "IMAP Test Account", string address = "imap@test.local")
|
||||
{
|
||||
return new MailAccount
|
||||
{
|
||||
Id = accountId,
|
||||
Name = "IMAP Test Account",
|
||||
Address = "imap@test.local",
|
||||
Name = name,
|
||||
Address = address,
|
||||
SenderName = "IMAP Test",
|
||||
ProviderType = MailProviderType.IMAP4
|
||||
};
|
||||
|
||||
@@ -150,6 +150,41 @@ public class AutoDiscoveryServiceTests
|
||||
uri.Should().Be(new Uri("https://dav.example.net/caldav/"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAutoDiscoverySettings_ReturnsGuessedLocalhostSettings_ForManualLocalAccounts()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler(request =>
|
||||
{
|
||||
var uri = request.RequestUri!.ToString();
|
||||
|
||||
if (uri.StartsWith("https://dns.google/resolve", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return CreateJsonResponse("{\"Status\":0}", request);
|
||||
}
|
||||
|
||||
return CreateStatusResponse(HttpStatusCode.NotFound, request);
|
||||
});
|
||||
|
||||
using var client = new HttpClient(handler);
|
||||
var sut = new AutoDiscoveryService(client);
|
||||
|
||||
var settings = await sut.GetAutoDiscoverySettings(new AutoDiscoveryMinimalSettings
|
||||
{
|
||||
Email = "user@localhost",
|
||||
DisplayName = "User",
|
||||
Password = "secret"
|
||||
});
|
||||
|
||||
settings.Should().NotBeNull();
|
||||
settings!.Domain.Should().Be("localhost");
|
||||
settings.GetImapSettings()!.Address.Should().Be("localhost");
|
||||
settings.GetImapSettings()!.Port.Should().Be(993);
|
||||
settings.GetImapSettings()!.Username.Should().Be("user@localhost");
|
||||
settings.GetSmptpSettings()!.Address.Should().Be("localhost");
|
||||
settings.GetSmptpSettings()!.Port.Should().Be(587);
|
||||
settings.GetSmptpSettings()!.Username.Should().Be("user@localhost");
|
||||
}
|
||||
|
||||
private static HttpResponseMessage CreateXmlResponse(string xml, HttpRequestMessage request)
|
||||
=> new(HttpStatusCode.OK)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using FluentAssertions;
|
||||
using HtmlAgilityPack;
|
||||
using Wino.Services.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Wino.Core.Tests.Services;
|
||||
|
||||
public class HtmlAgilityPackExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ClearImages_Should_Block_Remote_Image_References_But_Keep_Embedded_Ones()
|
||||
{
|
||||
// Arrange
|
||||
var document = new HtmlDocument();
|
||||
document.LoadHtml("""
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.hero { background-image: url('https://tracker.example/bg.png'); color: red; }
|
||||
</style>
|
||||
</head>
|
||||
<body background="https://tracker.example/body.png">
|
||||
<img id="remote" src="https://tracker.example/pixel.png" />
|
||||
<img id="embedded" src="data:image/png;base64,AAAA" />
|
||||
<img id="responsive" srcset="https://tracker.example/1x.png 1x, data:image/png;base64,BBBB 2x" />
|
||||
<div id="inline-style" style="background-image:url('https://tracker.example/inline.png');color:blue;">hello</div>
|
||||
<v:fill id="vml" src="https://tracker.example/vml.png"></v:fill>
|
||||
<svg>
|
||||
<image id="svg-remote" href="https://tracker.example/vector.svg"></image>
|
||||
<use id="svg-local" href="#icon"></use>
|
||||
</svg>
|
||||
</body>
|
||||
</html>
|
||||
""");
|
||||
|
||||
// Act
|
||||
document.ClearImages();
|
||||
var output = document.DocumentNode.OuterHtml;
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("id=\"embedded\" src=\"data:image/png;base64,AAAA\"", "embedded inline images should still render");
|
||||
output.Should().NotContain("id=\"remote\" src=", "remote img sources should be removed");
|
||||
output.Should().NotContain("background=\"https://tracker.example/body.png\"", "background attributes can be used as trackers");
|
||||
output.Should().NotContain("srcset=", "responsive image candidates should be removed because they may fetch remote trackers");
|
||||
output.Should().NotContain("https://tracker.example/inline.png", "inline CSS should not be allowed to fetch remote images");
|
||||
output.Should().Contain("color:blue", "non-image inline styling should be preserved");
|
||||
output.Should().NotContain("https://tracker.example/bg.png", "style blocks should not be allowed to fetch remote images");
|
||||
output.Should().Contain("color: red", "safe CSS declarations should remain");
|
||||
output.Should().NotContain("id=\"vml\" src=", "VML image references should be removed");
|
||||
output.Should().NotContain("id=\"svg-remote\" href=", "SVG image references should not fetch remote content");
|
||||
output.Should().Contain("id=\"svg-local\" href=\"#icon\"", "local fragment references should remain");
|
||||
}
|
||||
}
|
||||
@@ -259,6 +259,26 @@ public class MailFetchingTests : IAsyncLifetime
|
||||
result.Single().FolderId.Should().Be(_inboxFolder.Id, "a copy from the actively searched folder should win over newer non-searched copies");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchPinnedMailsAsync_ReturnsPinnedMailsOutsideRegularPage()
|
||||
{
|
||||
var oldPinned = BuildMail(_inboxFolder.Id, DateTime.UtcNow.AddDays(-5));
|
||||
oldPinned.IsPinned = true;
|
||||
|
||||
var recentMails = Enumerable.Range(0, 120)
|
||||
.Select(i => BuildMail(_inboxFolder.Id, DateTime.UtcNow.AddMinutes(-i)))
|
||||
.ToList();
|
||||
|
||||
await _databaseService.Connection.InsertAsync(oldPinned, typeof(MailCopy));
|
||||
await _databaseService.Connection.InsertAllAsync(recentMails, typeof(MailCopy));
|
||||
|
||||
var options = BuildOptions([_inboxFolder], createThreads: false, take: 20);
|
||||
|
||||
var result = await _mailService.FetchPinnedMailsAsync(options);
|
||||
|
||||
result.Should().ContainSingle(mail => mail.UniqueId == oldPinned.UniqueId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAssignmentAsync_ExistingAssignment_IsIgnored()
|
||||
{
|
||||
@@ -297,6 +317,27 @@ public class MailFetchingTests : IAsyncLifetime
|
||||
insertedCopies.Select(mail => mail.FolderId).Should().BeEquivalentTo([_inboxFolder.Id, archiveFolder.Id]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateMailAsync_PreservesLocalPinnedState()
|
||||
{
|
||||
var existingMail = BuildMail(_inboxFolder.Id, DateTime.UtcNow.AddHours(-1));
|
||||
existingMail.IsPinned = true;
|
||||
|
||||
await _databaseService.Connection.InsertAsync(existingMail, typeof(MailCopy));
|
||||
|
||||
var refreshedMail = BuildMail(_inboxFolder.Id, DateTime.UtcNow, id: existingMail.Id);
|
||||
refreshedMail.UniqueId = existingMail.UniqueId;
|
||||
refreshedMail.FileId = existingMail.FileId;
|
||||
refreshedMail.Subject = "Updated subject";
|
||||
|
||||
await _mailService.UpdateMailAsync(refreshedMail);
|
||||
|
||||
var storedMail = await _databaseService.Connection.FindAsync<MailCopy>(existingMail.UniqueId);
|
||||
storedMail.Should().NotBeNull();
|
||||
storedMail!.IsPinned.Should().BeTrue();
|
||||
storedMail.Subject.Should().Be("Updated subject");
|
||||
}
|
||||
|
||||
// ── Performance: 1 000 mails / ~70 threads ─────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,6 +13,8 @@ public sealed class MailRequestStateTests
|
||||
[Fact]
|
||||
public void MarkReadRequest_RevertUiChanges_RestoresOriginalReadState()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
|
||||
var mailCopy = CreateMailCopy(isRead: false, isFlagged: false);
|
||||
var request = new MarkReadRequest(mailCopy, IsRead: true);
|
||||
var recipient = new MailRequestRecipient();
|
||||
@@ -27,20 +29,23 @@ public sealed class MailRequestStateTests
|
||||
request.RevertUIChanges();
|
||||
|
||||
mailCopy.IsRead.Should().BeFalse();
|
||||
recipient.Updated.Should().HaveCount(2);
|
||||
recipient.Updated[0].Source.Should().Be(EntityUpdateSource.ClientUpdated);
|
||||
recipient.Updated[1].Source.Should().Be(EntityUpdateSource.ClientReverted);
|
||||
recipient.Updated[1].UpdatedMail.IsRead.Should().BeFalse();
|
||||
recipient.StateUpdates.Should().HaveCount(2);
|
||||
recipient.StateUpdates[0].Source.Should().Be(EntityUpdateSource.ClientUpdated);
|
||||
recipient.StateUpdates[1].Source.Should().Be(EntityUpdateSource.ClientReverted);
|
||||
recipient.StateUpdates[1].UpdatedState.IsRead.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.UnregisterAll(recipient);
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangeFlagRequest_RevertUiChanges_RestoresOriginalFlagState()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
|
||||
var mailCopy = CreateMailCopy(isRead: true, isFlagged: false);
|
||||
var request = new ChangeFlagRequest(mailCopy, IsFlagged: true);
|
||||
var recipient = new MailRequestRecipient();
|
||||
@@ -55,14 +60,15 @@ public sealed class MailRequestStateTests
|
||||
request.RevertUIChanges();
|
||||
|
||||
mailCopy.IsFlagged.Should().BeFalse();
|
||||
recipient.Updated.Should().HaveCount(2);
|
||||
recipient.Updated[0].Source.Should().Be(EntityUpdateSource.ClientUpdated);
|
||||
recipient.Updated[1].Source.Should().Be(EntityUpdateSource.ClientReverted);
|
||||
recipient.Updated[1].UpdatedMail.IsFlagged.Should().BeFalse();
|
||||
recipient.StateUpdates.Should().HaveCount(2);
|
||||
recipient.StateUpdates[0].Source.Should().Be(EntityUpdateSource.ClientUpdated);
|
||||
recipient.StateUpdates[1].Source.Should().Be(EntityUpdateSource.ClientReverted);
|
||||
recipient.StateUpdates[1].UpdatedState.IsFlagged.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.UnregisterAll(recipient);
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +82,10 @@ public sealed class MailRequestStateTests
|
||||
IsFlagged = isFlagged
|
||||
};
|
||||
|
||||
internal sealed class MailRequestRecipient : IRecipient<MailUpdatedMessage>
|
||||
internal sealed class MailRequestRecipient : IRecipient<MailStateUpdatedMessage>
|
||||
{
|
||||
public List<MailUpdatedMessage> Updated { get; } = [];
|
||||
public List<MailStateUpdatedMessage> StateUpdates { get; } = [];
|
||||
|
||||
public void Receive(MailUpdatedMessage message) => Updated.Add(message);
|
||||
public void Receive(MailStateUpdatedMessage message) => StateUpdates.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using FluentAssertions;
|
||||
using MimeKit;
|
||||
using Moq;
|
||||
@@ -8,6 +9,7 @@ using Wino.Core.Domain.Extensions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Tests.Helpers;
|
||||
using Wino.Messaging.UI;
|
||||
using Wino.Services;
|
||||
using Xunit;
|
||||
|
||||
@@ -229,6 +231,190 @@ public class MailThreadingTests : IAsyncLifetime
|
||||
mimeMessage.ReplyTo.Mailboxes.Should().ContainSingle(m => m.Address == "support@test.local");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplyMailStateUpdatesAsync_ForBatchReadStateChange_SendsBulkMailUpdatedMessage()
|
||||
{
|
||||
var mail1 = new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = _draftFolder.Id,
|
||||
IsRead = true,
|
||||
Subject = "First"
|
||||
};
|
||||
var mail2 = new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = _draftFolder.Id,
|
||||
IsRead = true,
|
||||
Subject = "Second"
|
||||
};
|
||||
|
||||
await _databaseService.Connection.InsertAllAsync(new[] { mail1, mail2 }, typeof(MailCopy));
|
||||
|
||||
var recipient = new MailUpdateRecipient();
|
||||
WeakReferenceMessenger.Default.Register<MailUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Register<BulkMailUpdatedMessage>(recipient);
|
||||
|
||||
try
|
||||
{
|
||||
await _mailService.ApplyMailStateUpdatesAsync(
|
||||
[
|
||||
new MailCopyStateUpdate(mail1.Id, IsRead: false),
|
||||
new MailCopyStateUpdate(mail2.Id, IsRead: false)
|
||||
]);
|
||||
|
||||
recipient.SingleUpdates.Should().BeEmpty();
|
||||
recipient.BulkUpdates.Should().ContainSingle();
|
||||
recipient.BulkUpdates[0].Source.Should().Be(EntityUpdateSource.Server);
|
||||
recipient.BulkUpdates[0].ChangedProperties.Should().Be(MailCopyChangeFlags.IsRead);
|
||||
recipient.BulkUpdates[0].UpdatedMails.Should().HaveCount(2);
|
||||
recipient.BulkUpdates[0].UpdatedMails.Should().OnlyContain(x => !x.IsRead);
|
||||
|
||||
(await _databaseService.Connection.FindAsync<MailCopy>(mail1.UniqueId))!.IsRead.Should().BeFalse();
|
||||
(await _databaseService.Connection.FindAsync<MailCopy>(mail2.UniqueId))!.IsRead.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<MailUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Unregister<BulkMailUpdatedMessage>(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplyMailStateUpdatesAsync_ForBatchMarkRead_SendsBulkMailReadStatusChanged()
|
||||
{
|
||||
var mail1 = new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = _draftFolder.Id,
|
||||
IsRead = false,
|
||||
Subject = "First unread"
|
||||
};
|
||||
var mail2 = new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = _draftFolder.Id,
|
||||
IsRead = false,
|
||||
Subject = "Second unread"
|
||||
};
|
||||
|
||||
await _databaseService.Connection.InsertAllAsync(new[] { mail1, mail2 }, typeof(MailCopy));
|
||||
|
||||
var recipient = new MailReadStatusRecipient();
|
||||
WeakReferenceMessenger.Default.Register<MailReadStatusChanged>(recipient);
|
||||
WeakReferenceMessenger.Default.Register<BulkMailReadStatusChanged>(recipient);
|
||||
|
||||
try
|
||||
{
|
||||
await _mailService.ApplyMailStateUpdatesAsync(
|
||||
[
|
||||
new MailCopyStateUpdate(mail1.Id, IsRead: true),
|
||||
new MailCopyStateUpdate(mail2.Id, IsRead: true)
|
||||
]);
|
||||
|
||||
recipient.SingleUpdates.Should().BeEmpty();
|
||||
recipient.BulkUpdates.Should().ContainSingle();
|
||||
recipient.BulkUpdates[0].UniqueIds.Should().BeEquivalentTo([mail1.UniqueId, mail2.UniqueId]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<MailReadStatusChanged>(recipient);
|
||||
WeakReferenceMessenger.Default.Unregister<BulkMailReadStatusChanged>(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChangePinnedStatusAsync_SendsHydratedBulkMailUpdatedMessage()
|
||||
{
|
||||
var mail = new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = _draftFolder.Id,
|
||||
IsPinned = false,
|
||||
Subject = "Pinned draft"
|
||||
};
|
||||
|
||||
await _databaseService.Connection.InsertAsync(mail, typeof(MailCopy));
|
||||
|
||||
var recipient = new MailUpdateRecipient();
|
||||
WeakReferenceMessenger.Default.Register<MailUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Register<BulkMailUpdatedMessage>(recipient);
|
||||
|
||||
try
|
||||
{
|
||||
await _mailService.ChangePinnedStatusAsync([mail.UniqueId], true);
|
||||
|
||||
recipient.SingleUpdates.Should().BeEmpty();
|
||||
recipient.BulkUpdates.Should().ContainSingle();
|
||||
recipient.BulkUpdates[0].ChangedProperties.Should().Be(MailCopyChangeFlags.IsPinned);
|
||||
recipient.BulkUpdates[0].UpdatedMails.Should().ContainSingle();
|
||||
|
||||
var updatedMail = recipient.BulkUpdates[0].UpdatedMails[0];
|
||||
updatedMail.IsPinned.Should().BeTrue();
|
||||
updatedMail.AssignedFolder.Should().NotBeNull();
|
||||
updatedMail.AssignedFolder!.Id.Should().Be(_draftFolder.Id);
|
||||
updatedMail.AssignedAccount.Should().NotBeNull();
|
||||
updatedMail.AssignedAccount!.Id.Should().Be(_account.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<MailUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Unregister<BulkMailUpdatedMessage>(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAssignmentAsync_SendsHydratedMailAddedMessage()
|
||||
{
|
||||
var archiveFolder = new MailItemFolder
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
MailAccountId = _account.Id,
|
||||
FolderName = "Archive",
|
||||
RemoteFolderId = "archive",
|
||||
SpecialFolderType = SpecialFolderType.Archive,
|
||||
IsSystemFolder = true,
|
||||
IsSynchronizationEnabled = true
|
||||
};
|
||||
|
||||
var mail = new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = "assignment-mail",
|
||||
FolderId = _draftFolder.Id,
|
||||
Subject = "Assigned copy"
|
||||
};
|
||||
|
||||
await _databaseService.Connection.InsertAsync(archiveFolder, typeof(MailItemFolder));
|
||||
await _databaseService.Connection.InsertAsync(mail, typeof(MailCopy));
|
||||
|
||||
var recipient = new MailAddRecipient();
|
||||
WeakReferenceMessenger.Default.Register<MailAddedMessage>(recipient);
|
||||
|
||||
try
|
||||
{
|
||||
await _mailService.CreateAssignmentAsync(_account.Id, mail.Id, archiveFolder.RemoteFolderId);
|
||||
|
||||
recipient.Added.Should().ContainSingle();
|
||||
|
||||
var addedMail = recipient.Added[0].AddedMail;
|
||||
addedMail.UniqueId.Should().NotBe(mail.UniqueId);
|
||||
addedMail.AssignedFolder.Should().NotBeNull();
|
||||
addedMail.AssignedFolder!.Id.Should().Be(archiveFolder.Id);
|
||||
addedMail.AssignedAccount.Should().NotBeNull();
|
||||
addedMail.AssignedAccount!.Id.Should().Be(_account.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<MailAddedMessage>(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
private static MimeMessage CreateReferencedMimeMessage(string subject, string? messageId = null)
|
||||
{
|
||||
var message = new MimeMessage();
|
||||
@@ -243,6 +429,31 @@ public class MailThreadingTests : IAsyncLifetime
|
||||
return message;
|
||||
}
|
||||
|
||||
internal sealed class MailUpdateRecipient : IRecipient<MailUpdatedMessage>, IRecipient<BulkMailUpdatedMessage>
|
||||
{
|
||||
public List<MailUpdatedMessage> SingleUpdates { get; } = [];
|
||||
public List<BulkMailUpdatedMessage> BulkUpdates { get; } = [];
|
||||
|
||||
public void Receive(MailUpdatedMessage message) => SingleUpdates.Add(message);
|
||||
public void Receive(BulkMailUpdatedMessage message) => BulkUpdates.Add(message);
|
||||
}
|
||||
|
||||
internal sealed class MailAddRecipient : IRecipient<MailAddedMessage>
|
||||
{
|
||||
public List<MailAddedMessage> Added { get; } = [];
|
||||
|
||||
public void Receive(MailAddedMessage message) => Added.Add(message);
|
||||
}
|
||||
|
||||
internal sealed class MailReadStatusRecipient : IRecipient<MailReadStatusChanged>, IRecipient<BulkMailReadStatusChanged>
|
||||
{
|
||||
public List<MailReadStatusChanged> SingleUpdates { get; } = [];
|
||||
public List<BulkMailReadStatusChanged> BulkUpdates { get; } = [];
|
||||
|
||||
public void Receive(MailReadStatusChanged message) => SingleUpdates.Add(message);
|
||||
public void Receive(BulkMailReadStatusChanged message) => BulkUpdates.Add(message);
|
||||
}
|
||||
|
||||
private static MailService BuildMailService(InMemoryDatabaseService db)
|
||||
{
|
||||
var signatureService = new Mock<ISignatureService>();
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using FluentAssertions;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Wino.Core.Tests.Services;
|
||||
|
||||
public class SpecialImapProviderConfigResolverTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetServerInformation_ICloud_UsesMailboxLocalPartForIncomingAndOutgoingUsernames()
|
||||
{
|
||||
var sut = new SpecialImapProviderConfigResolver();
|
||||
var account = new MailAccount
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Address = "tester@icloud.com"
|
||||
};
|
||||
var dialogResult = new AccountCreationDialogResult(
|
||||
MailProviderType.IMAP4,
|
||||
"iCloud",
|
||||
new SpecialImapProviderDetails(
|
||||
"tester@icloud.com",
|
||||
"app-password",
|
||||
"Tester",
|
||||
SpecialImapProvider.iCloud,
|
||||
ImapCalendarSupportMode.CalDav),
|
||||
"#0078D4",
|
||||
InitialSynchronizationRange.SixMonths,
|
||||
true,
|
||||
true);
|
||||
|
||||
var serverInformation = sut.GetServerInformation(account, dialogResult);
|
||||
|
||||
serverInformation.IncomingServerUsername.Should().Be("tester");
|
||||
serverInformation.OutgoingServerUsername.Should().Be("tester");
|
||||
serverInformation.CalDavUsername.Should().Be("tester@icloud.com");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using FluentAssertions;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Wino.Core.Tests.Services;
|
||||
|
||||
public class TranslationServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("pl-PL", AppLanguage.Polish)]
|
||||
[InlineData("de-AT", AppLanguage.Deutsch)]
|
||||
[InlineData("pt-PT", AppLanguage.PortugeseBrazil)]
|
||||
[InlineData("zh-TW", AppLanguage.Chinese)]
|
||||
[InlineData("tr_TR", AppLanguage.Turkish)]
|
||||
[InlineData("ko-KR", AppLanguage.Korean)]
|
||||
[InlineData("nl-NL", AppLanguage.English)]
|
||||
public void ResolveSupportedLanguage_ReturnsExpectedLanguage(string languageTag, AppLanguage expectedLanguage)
|
||||
{
|
||||
var result = TranslationService.ResolveSupportedLanguage([languageTag]);
|
||||
|
||||
result.Should().Be(expectedLanguage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using FluentAssertions;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Requests.Bundles;
|
||||
using Wino.Core.Requests.Mail;
|
||||
using Wino.Core.Synchronizers;
|
||||
using Wino.Messaging.UI;
|
||||
using Xunit;
|
||||
|
||||
namespace Wino.Core.Tests.Synchronizers;
|
||||
|
||||
public sealed class BaseSynchronizerUiChangeTests
|
||||
{
|
||||
[Fact]
|
||||
public void ApplyOptimisticUiChanges_UsesBundleUiChangeRequest_ForBatchBundle()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
|
||||
var folderId = Guid.NewGuid();
|
||||
var account = new MailAccount { Id = Guid.NewGuid(), Name = "Test account" };
|
||||
var synchronizer = new TestSynchronizer(account);
|
||||
var recipient = new UiChangeRecipient();
|
||||
|
||||
var request1 = new MarkReadRequest(CreateMailCopy(folderId, isRead: false), IsRead: true);
|
||||
var request2 = new MarkReadRequest(CreateMailCopy(folderId, isRead: false), IsRead: true);
|
||||
var batchRequest = new BatchMarkReadRequest([request1, request2]);
|
||||
var bundle = new HttpRequestBundle<object>(new object(), batchRequest, request1);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<MailStateUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Register<BulkMailStateUpdatedMessage>(recipient);
|
||||
|
||||
try
|
||||
{
|
||||
synchronizer.ApplyUiChanges([bundle]);
|
||||
|
||||
recipient.SingleUpdates.Should().BeEmpty();
|
||||
recipient.BulkUpdates.Should().ContainSingle();
|
||||
recipient.BulkUpdates[0].UpdatedStates.Should().HaveCount(2);
|
||||
request1.Item.IsRead.Should().BeFalse();
|
||||
request2.Item.IsRead.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
WeakReferenceMessenger.Default.Unregister<MailStateUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Unregister<BulkMailStateUpdatedMessage>(recipient);
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private static MailCopy CreateMailCopy(Guid folderId, bool isRead) =>
|
||||
new()
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = folderId,
|
||||
IsRead = isRead
|
||||
};
|
||||
|
||||
private sealed class TestSynchronizer : BaseSynchronizer<object>
|
||||
{
|
||||
public TestSynchronizer(MailAccount account)
|
||||
: base(account, WeakReferenceMessenger.Default)
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyUiChanges(List<IRequestBundle<object>> bundles) => ApplyOptimisticUiChanges(bundles);
|
||||
|
||||
public override Task ExecuteNativeRequestsAsync(List<IRequestBundle<object>> batchedRequests, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal sealed class UiChangeRecipient : IRecipient<MailStateUpdatedMessage>, IRecipient<BulkMailStateUpdatedMessage>
|
||||
{
|
||||
public List<MailStateUpdatedMessage> SingleUpdates { get; } = [];
|
||||
public List<BulkMailStateUpdatedMessage> BulkUpdates { get; } = [];
|
||||
|
||||
public void Receive(MailStateUpdatedMessage message) => SingleUpdates.Add(message);
|
||||
public void Receive(BulkMailStateUpdatedMessage message) => BulkUpdates.Add(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using Wino.Core.Domain.Entities.Calendar;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Integration.Processors;
|
||||
using Wino.Core.Misc;
|
||||
using Wino.Core.Synchronizers.ImapSync;
|
||||
using Wino.Core.Synchronizers.Mail;
|
||||
using Wino.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Wino.Core.Tests.Synchronizers;
|
||||
|
||||
public class CalDavCalendarMetadataTests
|
||||
{
|
||||
[Fact]
|
||||
public void ParseCalendarCollection_MapsCollectionMetadataAndSkipsNonEventCalendars()
|
||||
{
|
||||
var xml = XDocument.Parse(
|
||||
"""
|
||||
<D:multistatus xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/" xmlns:ICAL="http://apple.com/ns/ical/">
|
||||
<D:response>
|
||||
<D:href>/calendars/work/</D:href>
|
||||
<D:propstat>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:collection />
|
||||
<C:calendar />
|
||||
</D:resourcetype>
|
||||
<D:displayname>Work</D:displayname>
|
||||
<C:calendar-description>Team calendar</C:calendar-description>
|
||||
<CS:getctag>"ctag-1"</CS:getctag>
|
||||
<D:sync-token>sync-1</D:sync-token>
|
||||
<D:current-user-privilege-set>
|
||||
<D:privilege>
|
||||
<D:read />
|
||||
</D:privilege>
|
||||
</D:current-user-privilege-set>
|
||||
<C:calendar-timezone><![CDATA[
|
||||
BEGIN:VCALENDAR
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Warsaw
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR
|
||||
]]></C:calendar-timezone>
|
||||
<C:supported-calendar-component-set>
|
||||
<C:comp name="VEVENT" />
|
||||
<C:comp name="VTODO" />
|
||||
</C:supported-calendar-component-set>
|
||||
<C:schedule-calendar-transp>
|
||||
<C:transparent />
|
||||
</C:schedule-calendar-transp>
|
||||
<ICAL:calendar-color>#5b617aff</ICAL:calendar-color>
|
||||
<ICAL:calendar-order>2</ICAL:calendar-order>
|
||||
</D:prop>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
<D:response>
|
||||
<D:href>/calendars/tasks/</D:href>
|
||||
<D:propstat>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
<D:prop>
|
||||
<D:resourcetype>
|
||||
<D:collection />
|
||||
<C:calendar />
|
||||
</D:resourcetype>
|
||||
<D:displayname>Tasks</D:displayname>
|
||||
<C:supported-calendar-component-set>
|
||||
<C:comp name="VTODO" />
|
||||
</C:supported-calendar-component-set>
|
||||
</D:prop>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>
|
||||
""");
|
||||
|
||||
var calendars = ParseCalendars(xml, new Uri("https://calendar.example.com/"));
|
||||
|
||||
calendars.Should().ContainSingle();
|
||||
|
||||
var calendar = calendars[0];
|
||||
calendar.RemoteCalendarId.Should().Be("https://calendar.example.com/calendars/work");
|
||||
calendar.Name.Should().Be("Work");
|
||||
calendar.Description.Should().Be("Team calendar");
|
||||
calendar.CTag.Should().Be("\"ctag-1\"");
|
||||
calendar.SyncToken.Should().Be("sync-1");
|
||||
calendar.TimeZone.Should().Be("Europe/Warsaw");
|
||||
calendar.BackgroundColorHex.Should().Be("#5B617A");
|
||||
calendar.IsReadOnly.Should().BeTrue();
|
||||
calendar.SupportsEvents.Should().BeTrue();
|
||||
calendar.DefaultShowAs.Should().Be(CalendarItemShowAs.Free);
|
||||
calendar.Order.Should().Be(2d);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SynchronizeCalendarMetadataAsync_UpdatesServerBackedSettingsAndPreservesUserColorOverride()
|
||||
{
|
||||
var tempDirectory = CreateTempDirectory();
|
||||
|
||||
var serverInformation = new CustomServerInformation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IncomingServer = "imap.example.com",
|
||||
IncomingServerPort = "993",
|
||||
IncomingServerUsername = "user@example.com",
|
||||
IncomingServerPassword = "password",
|
||||
OutgoingServer = "smtp.example.com",
|
||||
OutgoingServerPort = "587",
|
||||
OutgoingServerUsername = "user@example.com",
|
||||
OutgoingServerPassword = "password",
|
||||
MaxConcurrentClients = 5,
|
||||
CalendarSupportMode = ImapCalendarSupportMode.CalDav
|
||||
};
|
||||
|
||||
var account = new MailAccount
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "IMAP Test",
|
||||
Address = "test@example.com",
|
||||
ProviderType = MailProviderType.IMAP4,
|
||||
IsCalendarAccessGranted = true,
|
||||
ServerInformation = serverInformation
|
||||
};
|
||||
|
||||
var localCalendar = new AccountCalendar
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
AccountId = account.Id,
|
||||
RemoteCalendarId = "https://calendar.example.com/calendars/work",
|
||||
Name = "Local",
|
||||
BackgroundColorHex = "#123456",
|
||||
TextColorHex = "#FFFFFF",
|
||||
IsBackgroundColorUserOverridden = true,
|
||||
TimeZone = "UTC",
|
||||
IsReadOnly = false,
|
||||
DefaultShowAs = CalendarItemShowAs.Busy
|
||||
};
|
||||
|
||||
var changeProcessor = new Mock<IImapChangeProcessor>();
|
||||
changeProcessor
|
||||
.Setup(x => x.GetAccountCalendarsAsync(account.Id))
|
||||
.ReturnsAsync(new List<AccountCalendar> { localCalendar });
|
||||
changeProcessor
|
||||
.Setup(x => x.UpdateAccountCalendarAsync(It.IsAny<AccountCalendar>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
changeProcessor
|
||||
.Setup(x => x.DeleteCalendarIcsForCalendarAsync(It.IsAny<Guid>(), It.IsAny<Guid>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
changeProcessor
|
||||
.Setup(x => x.DeleteAccountCalendarAsync(It.IsAny<AccountCalendar>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
changeProcessor
|
||||
.Setup(x => x.InsertAccountCalendarAsync(It.IsAny<AccountCalendar>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var synchronizer = CreateSynchronizer(tempDirectory, account, changeProcessor.Object);
|
||||
|
||||
try
|
||||
{
|
||||
await InvokePrivateAsync(
|
||||
synchronizer,
|
||||
"SynchronizeCalendarMetadataAsync",
|
||||
new List<CalDavCalendar>
|
||||
{
|
||||
new()
|
||||
{
|
||||
RemoteCalendarId = localCalendar.RemoteCalendarId,
|
||||
Name = "Remote",
|
||||
TimeZone = "Europe/Warsaw",
|
||||
BackgroundColorHex = "#ABCDEF",
|
||||
IsReadOnly = true,
|
||||
DefaultShowAs = CalendarItemShowAs.Free,
|
||||
Order = 0
|
||||
}
|
||||
});
|
||||
|
||||
localCalendar.Name.Should().Be("Remote");
|
||||
localCalendar.TimeZone.Should().Be("Europe/Warsaw");
|
||||
localCalendar.IsReadOnly.Should().BeTrue();
|
||||
localCalendar.DefaultShowAs.Should().Be(CalendarItemShowAs.Free);
|
||||
localCalendar.IsPrimary.Should().BeTrue();
|
||||
localCalendar.BackgroundColorHex.Should().Be("#123456");
|
||||
localCalendar.TextColorHex.Should().Be(ColorHelpers.GetReadableTextColorHex("#123456"));
|
||||
|
||||
changeProcessor.Verify(x => x.UpdateAccountCalendarAsync(localCalendar), Times.Once);
|
||||
changeProcessor.Verify(x => x.InsertAccountCalendarAsync(It.IsAny<AccountCalendar>()), Times.Never);
|
||||
changeProcessor.Verify(x => x.DeleteAccountCalendarAsync(It.IsAny<AccountCalendar>()), Times.Never);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await synchronizer.KillSynchronizerAsync();
|
||||
DeleteDirectory(tempDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<CalDavCalendar> ParseCalendars(XDocument xml, Uri baseUri)
|
||||
{
|
||||
var parseMethod = typeof(CalDavClient).GetMethod(
|
||||
"ParseCalendarCollection",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
parseMethod.Should().NotBeNull();
|
||||
|
||||
var result = parseMethod!.Invoke(null, [xml, baseUri]);
|
||||
return result.Should().BeOfType<List<CalDavCalendar>>().Subject;
|
||||
}
|
||||
|
||||
private static ImapSynchronizer CreateSynchronizer(string appDataFolder, MailAccount account, IImapChangeProcessor changeProcessor)
|
||||
{
|
||||
var applicationConfiguration = new Mock<IApplicationConfiguration>();
|
||||
applicationConfiguration.SetupProperty(x => x.ApplicationDataFolderPath, appDataFolder);
|
||||
applicationConfiguration.SetupProperty(x => x.PublisherSharedFolderPath, appDataFolder);
|
||||
applicationConfiguration.SetupProperty(x => x.ApplicationTempFolderPath, appDataFolder);
|
||||
applicationConfiguration.SetupGet(x => x.SentryDNS).Returns(string.Empty);
|
||||
|
||||
var unifiedSynchronizer = new UnifiedImapSynchronizer(
|
||||
Mock.Of<IFolderService>(),
|
||||
Mock.Of<IMailService>(),
|
||||
Mock.Of<IImapSynchronizerErrorHandlerFactory>());
|
||||
|
||||
return new ImapSynchronizer(
|
||||
account,
|
||||
changeProcessor,
|
||||
applicationConfiguration.Object,
|
||||
unifiedSynchronizer,
|
||||
Mock.Of<IImapSynchronizerErrorHandlerFactory>(),
|
||||
Mock.Of<ICalDavClient>(),
|
||||
Mock.Of<IAutoDiscoveryService>(),
|
||||
Mock.Of<ICalendarService>());
|
||||
}
|
||||
|
||||
private static string CreateTempDirectory()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), "wino-caldav-calendar-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void DeleteDirectory(string path)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
Directory.Delete(path, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task InvokePrivateAsync(object instance, string methodName, params object[] parameters)
|
||||
{
|
||||
var method = instance.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException($"Method '{methodName}' not found.");
|
||||
|
||||
var task = (Task)method.Invoke(instance, parameters)!;
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,32 @@ namespace Wino.Core.Tests.Synchronizers;
|
||||
|
||||
public sealed class GmailSynchronizerRequestSuccessTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task UpdateAccountSyncIdentifierAsync_EmptyStoredIdentifier_PersistsFirstHistoryCursor()
|
||||
{
|
||||
var changeProcessor = new Mock<IGmailChangeProcessor>(MockBehavior.Strict);
|
||||
changeProcessor
|
||||
.Setup(x => x.UpdateAccountDeltaSynchronizationIdentifierAsync(It.IsAny<Guid>(), "123"))
|
||||
.ReturnsAsync("123");
|
||||
|
||||
var synchronizer = CreateSynchronizer(changeProcessor.Object, synchronizationDeltaIdentifier: string.Empty);
|
||||
|
||||
await InvokeUpdateAccountSyncIdentifierAsync(synchronizer, 123);
|
||||
|
||||
changeProcessor.Verify(x => x.UpdateAccountDeltaSynchronizationIdentifierAsync(It.IsAny<Guid>(), "123"), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAccountSyncIdentifierAsync_OlderHistoryCursor_DoesNotRegressStoredCursor()
|
||||
{
|
||||
var changeProcessor = new Mock<IGmailChangeProcessor>(MockBehavior.Strict);
|
||||
var synchronizer = CreateSynchronizer(changeProcessor.Object, synchronizationDeltaIdentifier: "456");
|
||||
|
||||
await InvokeUpdateAccountSyncIdentifierAsync(synchronizer, 123);
|
||||
|
||||
changeProcessor.Verify(x => x.UpdateAccountDeltaSynchronizationIdentifierAsync(It.IsAny<Guid>(), It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessSingleNativeRequestResponseAsync_BatchMarkReadRequest_PersistsLocalReadStateForEachMail()
|
||||
{
|
||||
@@ -209,13 +235,15 @@ public sealed class GmailSynchronizerRequestSuccessTests
|
||||
|
||||
private static GmailSynchronizer CreateSynchronizer(
|
||||
IGmailChangeProcessor changeProcessor,
|
||||
IGmailSynchronizerErrorHandlerFactory? errorFactory = null)
|
||||
IGmailSynchronizerErrorHandlerFactory? errorFactory = null,
|
||||
string? synchronizationDeltaIdentifier = null)
|
||||
{
|
||||
var account = new MailAccount
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Gmail",
|
||||
Address = "user@example.com"
|
||||
Address = "user@example.com",
|
||||
SynchronizationDeltaIdentifier = synchronizationDeltaIdentifier
|
||||
};
|
||||
|
||||
var authenticator = new Mock<IGmailAuthenticator>(MockBehavior.Loose);
|
||||
@@ -249,4 +277,17 @@ public sealed class GmailSynchronizerRequestSuccessTests
|
||||
task.Should().NotBeNull();
|
||||
await task!;
|
||||
}
|
||||
|
||||
private static async Task InvokeUpdateAccountSyncIdentifierAsync(GmailSynchronizer synchronizer, ulong historyId)
|
||||
{
|
||||
var method = typeof(GmailSynchronizer).GetMethod(
|
||||
"UpdateAccountSyncIdentifierAsync",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
method.Should().NotBeNull();
|
||||
|
||||
var task = method!.Invoke(synchronizer, [historyId]) as Task;
|
||||
task.Should().NotBeNull();
|
||||
await task!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ using FluentAssertions;
|
||||
using MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using Moq;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Synchronizers.ImapSync;
|
||||
using Wino.Services.Extensions;
|
||||
using Xunit;
|
||||
using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
||||
|
||||
@@ -223,4 +225,61 @@ public class UnifiedImapSynchronizerTests
|
||||
capturedPackage.Should().NotBeNull();
|
||||
capturedPackage!.MimeMessage.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessSummariesAsync_ShouldBatchStateUpdates_ForExistingMailCopies()
|
||||
{
|
||||
var localFolder = new MailItemFolder
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
MailAccountId = Guid.NewGuid(),
|
||||
FolderName = "Inbox",
|
||||
RemoteFolderId = "INBOX"
|
||||
};
|
||||
|
||||
var summaryMock = new Mock<IMessageSummary>();
|
||||
summaryMock.SetupGet(x => x.UniqueId).Returns(new UniqueId(42));
|
||||
summaryMock.SetupGet(x => x.Flags).Returns(MessageFlags.Seen | MessageFlags.Flagged);
|
||||
|
||||
var existingMailCopy = new MailCopy
|
||||
{
|
||||
Id = MailkitClientExtensions.CreateUid(localFolder.Id, 42),
|
||||
IsRead = false,
|
||||
IsFlagged = false
|
||||
};
|
||||
|
||||
var mailServiceMock = new Mock<IMailService>();
|
||||
mailServiceMock
|
||||
.Setup(x => x.GetExistingMailsAsync(localFolder.Id, It.IsAny<IEnumerable<UniqueId>>()))
|
||||
.ReturnsAsync([existingMailCopy]);
|
||||
mailServiceMock
|
||||
.Setup(x => x.ApplyMailStateUpdatesAsync(It.IsAny<IEnumerable<MailCopyStateUpdate>>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var sut = new UnifiedImapSynchronizer(
|
||||
Mock.Of<IFolderService>(),
|
||||
mailServiceMock.Object,
|
||||
Mock.Of<IImapSynchronizerErrorHandlerFactory>());
|
||||
|
||||
var imapSynchronizerMock = new Mock<IImapSynchronizer>();
|
||||
|
||||
var processMethod = typeof(UnifiedImapSynchronizer).GetMethod("ProcessSummariesAsync", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
processMethod.Should().NotBeNull();
|
||||
|
||||
var task = (Task<List<string>>)processMethod!.Invoke(
|
||||
sut,
|
||||
[imapSynchronizerMock.Object, localFolder, new List<IMessageSummary> { summaryMock.Object }, CancellationToken.None])!;
|
||||
|
||||
var result = await task;
|
||||
|
||||
result.Should().BeEmpty();
|
||||
mailServiceMock.Verify(
|
||||
x => x.ApplyMailStateUpdatesAsync(It.Is<IEnumerable<MailCopyStateUpdate>>(updates =>
|
||||
updates.Count() == 1
|
||||
&& updates.First().MailCopyId == existingMailCopy.Id
|
||||
&& updates.First().IsRead == true
|
||||
&& updates.First().IsFlagged == true)),
|
||||
Times.Once);
|
||||
mailServiceMock.Verify(x => x.CreateMailAsync(It.IsAny<Guid>(), It.IsAny<NewMailItemPackage>()), Times.Never);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Requests.Mail;
|
||||
using Wino.Core.Requests.Folder;
|
||||
using Wino.Core.Synchronizers;
|
||||
using Xunit;
|
||||
@@ -49,6 +50,38 @@ public sealed class WinoSynchronizerMailRequestTests
|
||||
synchronizer.ExecuteNativeRequestsInvocationCount.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteRequests_should_dispatch_grouped_mark_read_requests_with_composite_grouping_key()
|
||||
{
|
||||
var synchronizer = new TestMailSynchronizer();
|
||||
var folderId = Guid.NewGuid();
|
||||
|
||||
synchronizer.QueueRequest(new MarkReadRequest(new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = folderId
|
||||
}, IsRead: true));
|
||||
|
||||
synchronizer.QueueRequest(new MarkReadRequest(new MailCopy
|
||||
{
|
||||
UniqueId = Guid.NewGuid(),
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
FolderId = folderId
|
||||
}, IsRead: true));
|
||||
|
||||
var result = await synchronizer.SynchronizeMailsAsync(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = synchronizer.Account.Id,
|
||||
Type = MailSynchronizationType.ExecuteRequests
|
||||
});
|
||||
|
||||
result.CompletedState.Should().Be(SynchronizationCompletedState.Success);
|
||||
synchronizer.MarkReadInvocationCount.Should().Be(1);
|
||||
synchronizer.LastMarkReadBatchCount.Should().Be(2);
|
||||
synchronizer.ExecuteNativeRequestsInvocationCount.Should().Be(1);
|
||||
}
|
||||
|
||||
private sealed class TestMailSynchronizer
|
||||
: WinoSynchronizer<object, object, object>
|
||||
{
|
||||
@@ -60,6 +93,8 @@ public sealed class WinoSynchronizerMailRequestTests
|
||||
public override uint BatchModificationSize => 1;
|
||||
public override uint InitialMessageDownloadCountPerFolder => 0;
|
||||
public int CreateRootFolderInvocationCount { get; private set; }
|
||||
public int MarkReadInvocationCount { get; private set; }
|
||||
public int LastMarkReadBatchCount { get; private set; }
|
||||
public int ExecuteNativeRequestsInvocationCount { get; private set; }
|
||||
|
||||
public override List<IRequestBundle<object>> CreateRootFolder(CreateRootFolderRequest request)
|
||||
@@ -68,6 +103,13 @@ public sealed class WinoSynchronizerMailRequestTests
|
||||
return [new TestRequestBundle(new object(), request)];
|
||||
}
|
||||
|
||||
public override List<IRequestBundle<object>> MarkRead(BatchMarkReadRequest request)
|
||||
{
|
||||
MarkReadInvocationCount++;
|
||||
LastMarkReadBatchCount = request.Count;
|
||||
return [new TestRequestBundle(new object(), request[0])];
|
||||
}
|
||||
|
||||
public override Task ExecuteNativeRequestsAsync(List<IRequestBundle<object>> batchedRequests, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ExecuteNativeRequestsInvocationCount++;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -9,6 +10,7 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.ViewModels.Data;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Wino.Messaging.Client.Navigation;
|
||||
|
||||
@@ -17,6 +19,7 @@ namespace Wino.Core.ViewModels;
|
||||
public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewModel
|
||||
{
|
||||
public ObservableCollection<IAccountProviderDetailViewModel> Accounts { get; set; } = [];
|
||||
public IEnumerable<IAccountProviderDetailViewModel> StartupAccounts => Accounts.Where(IsStartupEligible);
|
||||
|
||||
public bool IsPurchasePanelVisible => !HasUnlimitedAccountProduct;
|
||||
public bool IsAccountCreationAlmostOnLimit => Accounts != null && Accounts.Count == FREE_ACCOUNT_COUNT - 1;
|
||||
@@ -130,10 +133,21 @@ public abstract partial class AccountManagementPageViewModelBase : CoreBaseViewM
|
||||
private void AccountsChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(HasAccountsDefined));
|
||||
OnPropertyChanged(nameof(StartupAccounts));
|
||||
}
|
||||
|
||||
private static string GetAccountDetailsTitle(MailAccount account)
|
||||
=> !string.IsNullOrWhiteSpace(account?.Address)
|
||||
? string.Format(Translator.SettingsAccountDetails_NavigationTitle, account.Address)
|
||||
: account?.Name ?? Translator.AccountDetailsPage_Title;
|
||||
|
||||
private static bool IsStartupEligible(IAccountProviderDetailViewModel account)
|
||||
{
|
||||
return account switch
|
||||
{
|
||||
AccountProviderDetailViewModel accountViewModel => accountViewModel.Account.IsMailAccessGranted,
|
||||
MergedAccountProviderDetailViewModel mergedAccountViewModel => mergedAccountViewModel.HoldingAccounts.Any(a => a.Account.IsMailAccessGranted),
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
@@ -9,6 +10,8 @@ public partial class AccountProviderDetailViewModel : ObservableObject, IAccount
|
||||
{
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CapabilitySummary))]
|
||||
[NotifyPropertyChangedFor(nameof(DescriptionText))]
|
||||
private MailAccount account;
|
||||
|
||||
public IProviderDetail ProviderDetail { get; set; }
|
||||
@@ -20,6 +23,10 @@ public partial class AccountProviderDetailViewModel : ObservableObject, IAccount
|
||||
public int Order => Account.Order;
|
||||
|
||||
public string StartupEntityAddresses => Account.Address;
|
||||
public string CapabilitySummary => BuildCapabilitySummary(Account);
|
||||
public string DescriptionText => string.IsNullOrWhiteSpace(Account.Address)
|
||||
? CapabilitySummary
|
||||
: $"{CapabilitySummary} | {Account.Address}";
|
||||
|
||||
public int HoldingAccountCount => 1;
|
||||
|
||||
@@ -30,4 +37,15 @@ public partial class AccountProviderDetailViewModel : ObservableObject, IAccount
|
||||
ProviderDetail = providerDetail;
|
||||
Account = account;
|
||||
}
|
||||
|
||||
private static string BuildCapabilitySummary(MailAccount account)
|
||||
{
|
||||
if (account?.IsMailAccessGranted == true && account.IsCalendarAccessGranted)
|
||||
return Translator.AccountCapability_MailAndCalendar;
|
||||
|
||||
if (account?.IsMailAccessGranted == true)
|
||||
return Translator.AccountCapability_MailOnly;
|
||||
|
||||
return Translator.AccountCapability_CalendarOnly;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,24 @@ public partial class PersonalizationPageViewModel : CoreBaseViewModel
|
||||
public bool HasReadReceiptTracking { get; } = false;
|
||||
public bool IsReadReceiptAcknowledged { get; } = false;
|
||||
public string ReadReceiptDisplayText { get; } = string.Empty;
|
||||
public IReadOnlyList<MailCategory> Categories { get; } =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Follow Up",
|
||||
BackgroundColorHex = "#DCEBFF",
|
||||
TextColorHex = "#0B5CAD"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "Planning",
|
||||
BackgroundColorHex = "#DDF5D7",
|
||||
TextColorHex = "#236A1E"
|
||||
}
|
||||
];
|
||||
public bool HasCategories => Categories.Count > 0;
|
||||
public AccountContact SenderContact { get; } = null;
|
||||
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
|
||||
{
|
||||
|
||||
@@ -160,7 +160,10 @@ public partial class SettingOptionsPageViewModel : CoreBaseViewModel
|
||||
public void NavigateToAddAccount()
|
||||
{
|
||||
Messenger.Send(new BreadcrumbNavigationRequested(Translator.SettingsManageAccountSettings_Title, WinoPage.ManageAccountsPage));
|
||||
Messenger.Send(new BreadcrumbNavigationRequested(Translator.WelcomeWizard_Step2Title, WinoPage.ProviderSelectionPage));
|
||||
Messenger.Send(new BreadcrumbNavigationRequested(
|
||||
Translator.WelcomeWizard_Step2Title,
|
||||
WinoPage.ProviderSelectionPage,
|
||||
ProviderSelectionNavigationContext.CreateForSettingsAddAccount()));
|
||||
}
|
||||
|
||||
public void NavigateToManageAccounts()
|
||||
|
||||
@@ -571,6 +571,19 @@ public partial class WinoAccountManagementPageViewModel : CoreBaseViewModel,
|
||||
}
|
||||
|
||||
var aiStatus = aiStatusResponse.Result;
|
||||
if (IsExpiredAiEntitlement(aiStatus.EntitlementStatus))
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
_aiPackAddOn.IsPurchased = false;
|
||||
_aiPackAddOn.HasUsageData = false;
|
||||
_aiPackAddOn.ErrorText = string.Empty;
|
||||
_aiPackAddOn.RenewalText = string.Empty;
|
||||
_aiPackAddOn.UsageResetText = string.Empty;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (aiStatus.MonthlyLimit is not int usageLimit || usageLimit <= 0 || aiStatus.Used is not int usageCount)
|
||||
{
|
||||
await ExecuteUIThread(() =>
|
||||
@@ -613,4 +626,7 @@ public partial class WinoAccountManagementPageViewModel : CoreBaseViewModel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsExpiredAiEntitlement(string? entitlementStatus)
|
||||
=> string.Equals(entitlementStatus, "Expired", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ public class ImapClientPool : IDisposable
|
||||
private readonly int _targetMinimumConnections;
|
||||
|
||||
private DateTime _lastKeepAliveSentUtc = DateTime.MinValue;
|
||||
private Exception _lastConnectionException;
|
||||
private WinoImapClient _dedicatedIdleClient;
|
||||
private bool _disposedValue;
|
||||
private bool _initialized;
|
||||
@@ -114,7 +115,7 @@ public class ImapClientPool : IDisposable
|
||||
var initialClient = await CreateAndConnectClientAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (initialClient == null)
|
||||
{
|
||||
throw CreatePoolException("Failed to create initial IMAP connection for the pool.");
|
||||
throw CreatePoolException("Failed to create initial IMAP connection for the pool.", _lastConnectionException);
|
||||
}
|
||||
|
||||
_clientStates[initialClient] = ImapClientState.Available;
|
||||
@@ -192,6 +193,7 @@ public class ImapClientPool : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_lastConnectionException = ex;
|
||||
_logger.Warning(ex, "Pooled IMAP client was not ready. Marking as failed.");
|
||||
MarkClientAsFailed(pooledClient);
|
||||
}
|
||||
@@ -215,12 +217,12 @@ public class ImapClientPool : IDisposable
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw CreatePoolException($"Timed out while acquiring an IMAP client after {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.");
|
||||
throw CreatePoolException($"Timed out while acquiring an IMAP client after {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.", _lastConnectionException);
|
||||
}
|
||||
|
||||
throw cancellationToken.IsCancellationRequested
|
||||
? new OperationCanceledException(cancellationToken)
|
||||
: CreatePoolException($"Failed to acquire IMAP client within {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.");
|
||||
: CreatePoolException($"Failed to acquire IMAP client within {timeout.TotalSeconds:F1} seconds. Failures: {createFailures}.", _lastConnectionException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -516,14 +518,17 @@ public class ImapClientPool : IDisposable
|
||||
private async Task<WinoImapClient> CreateAndConnectClientAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var client = CreateNewClient();
|
||||
_lastConnectionException = null;
|
||||
|
||||
try
|
||||
{
|
||||
await EnsureClientReadyAsync(client, cancellationToken).ConfigureAwait(false);
|
||||
_lastConnectionException = null;
|
||||
return client;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_lastConnectionException = ex;
|
||||
_logger.Warning(ex, "Failed to create and connect IMAP client.");
|
||||
DisposeClient(client);
|
||||
return null;
|
||||
|
||||
@@ -30,7 +30,9 @@ public interface IDefaultChangeProcessor
|
||||
Task ChangeMailReadStatusAsync(string mailCopyId, bool isRead);
|
||||
Task ChangeFlagStatusAsync(string mailCopyId, bool isFlagged);
|
||||
Task<bool> CreateMailAsync(Guid AccountId, NewMailItemPackage package);
|
||||
Task CreateMailsAsync(Guid accountId, IReadOnlyList<NewMailItemPackage> packages);
|
||||
Task DeleteMailAsync(Guid accountId, string mailId);
|
||||
Task DeleteMailsAsync(Guid accountId, IEnumerable<string> mailIds);
|
||||
Task<List<MailCopy>> GetDownloadedUnreadMailsAsync(Guid accountId, IEnumerable<string> downloadedMailCopyIds);
|
||||
Task SaveMimeFileAsync(Guid fileId, MimeMessage mimeMessage, Guid accountId);
|
||||
Task DeleteFolderAsync(Guid accountId, string remoteFolderId);
|
||||
@@ -56,6 +58,9 @@ public interface IDefaultChangeProcessor
|
||||
Task UpdateCalendarDeltaSynchronizationToken(Guid calendarId, string deltaToken);
|
||||
Task<List<MailCopy>> GetMailCopiesAsync(IEnumerable<string> mailCopyIds);
|
||||
Task CreateMailRawAsync(MailAccount account, MailItemFolder mailItemFolder, NewMailItemPackage package);
|
||||
Task ApplyMailStateUpdatesAsync(IEnumerable<MailCopyStateUpdate> updates);
|
||||
Task CreateAssignmentsAsync(Guid accountId, IEnumerable<MailFolderAssignmentUpdate> assignments);
|
||||
Task DeleteAssignmentsAsync(Guid accountId, IEnumerable<MailFolderAssignmentUpdate> assignments);
|
||||
Task DeleteUserMailCacheAsync(Guid accountId);
|
||||
Task UpsertMailInvitationCalendarMappingAsync(MailInvitationCalendarMapping mapping);
|
||||
Task<MailInvitationCalendarMapping> GetMailInvitationCalendarMappingAsync(Guid accountId, string mailCopyId);
|
||||
@@ -156,18 +161,33 @@ public class DefaultChangeProcessor(IDatabaseService databaseService,
|
||||
public Task ChangeMailReadStatusAsync(string mailCopyId, bool isRead)
|
||||
=> MailService.ChangeReadStatusAsync(mailCopyId, isRead);
|
||||
|
||||
public Task ApplyMailStateUpdatesAsync(IEnumerable<MailCopyStateUpdate> updates)
|
||||
=> MailService.ApplyMailStateUpdatesAsync(updates);
|
||||
|
||||
public Task DeleteAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId)
|
||||
=> MailService.DeleteAssignmentAsync(accountId, mailCopyId, remoteFolderId);
|
||||
|
||||
public Task DeleteAssignmentsAsync(Guid accountId, IEnumerable<MailFolderAssignmentUpdate> assignments)
|
||||
=> MailService.DeleteAssignmentsAsync(accountId, assignments);
|
||||
|
||||
public Task DeleteMailAsync(Guid accountId, string mailId)
|
||||
=> MailService.DeleteMailAsync(accountId, mailId);
|
||||
|
||||
public Task DeleteMailsAsync(Guid accountId, IEnumerable<string> mailIds)
|
||||
=> MailService.DeleteMailsAsync(accountId, mailIds);
|
||||
|
||||
public Task<bool> CreateMailAsync(Guid accountId, NewMailItemPackage package)
|
||||
=> MailService.CreateMailAsync(accountId, package);
|
||||
|
||||
public Task CreateMailsAsync(Guid accountId, IReadOnlyList<NewMailItemPackage> packages)
|
||||
=> MailService.CreateMailsAsync(accountId, packages);
|
||||
|
||||
public Task CreateMailRawAsync(MailAccount account, MailItemFolder mailItemFolder, NewMailItemPackage package)
|
||||
=> MailService.CreateMailRawAsync(account, mailItemFolder, package);
|
||||
|
||||
public Task CreateAssignmentsAsync(Guid accountId, IEnumerable<MailFolderAssignmentUpdate> assignments)
|
||||
=> MailService.CreateAssignmentsAsync(accountId, assignments);
|
||||
|
||||
public Task<bool> MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId)
|
||||
=> MailService.MapLocalDraftAsync(accountId, localDraftCopyUniqueId, newMailCopyId, newDraftId, newThreadId);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -14,18 +15,26 @@ public record EmptyFolderRequest(MailItemFolder Folder, List<MailCopy> MailsToDe
|
||||
public bool ExcludeMustHaveFolders => false;
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
foreach (var item in MailsToDelete)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item, EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
var removedMails = MailsToDelete?
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
|
||||
if (removedMails == null || removedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailRemovedMessage(removedMails, EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
foreach (var item in MailsToDelete)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send(new MailAddedMessage(item, EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
var addedMails = MailsToDelete?
|
||||
.Where(item => item != null)
|
||||
.ToList();
|
||||
|
||||
if (addedMails == null || addedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailAddedMessage(addedMails, EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
|
||||
public List<Guid> SynchronizationFolderIds => [Folder.Id];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -17,31 +18,31 @@ public record MarkFolderAsReadRequest(MailItemFolder Folder, List<MailCopy> Mail
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
if (MailsToMarkRead == null || MailsToMarkRead.Count == 0) return;
|
||||
var updatedMails = MailsToMarkRead?
|
||||
.Where(item => item != null && !item.IsRead)
|
||||
.Select(item => new MailStateChange(item.UniqueId, IsRead: true))
|
||||
.ToList();
|
||||
|
||||
foreach (var item in MailsToMarkRead)
|
||||
{
|
||||
// Skip if already read
|
||||
if (item.IsRead) continue;
|
||||
if (updatedMails == null || updatedMails.Count == 0)
|
||||
return;
|
||||
|
||||
item.IsRead = true;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item, EntityUpdateSource.ClientUpdated, MailCopyChangeFlags.IsRead));
|
||||
}
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailStateUpdatedMessage(
|
||||
updatedMails,
|
||||
EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
if (MailsToMarkRead == null || MailsToMarkRead.Count == 0) return;
|
||||
var updatedMails = MailsToMarkRead?
|
||||
.Where(item => item != null && !item.IsRead)
|
||||
.Select(item => new MailStateChange(item.UniqueId, IsRead: false))
|
||||
.ToList();
|
||||
|
||||
foreach (var item in MailsToMarkRead)
|
||||
{
|
||||
// Skip if already unread (wasn't changed by ApplyUIChanges)
|
||||
if (!item.IsRead) continue;
|
||||
if (updatedMails == null || updatedMails.Count == 0)
|
||||
return;
|
||||
|
||||
item.IsRead = false;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(item, EntityUpdateSource.ClientReverted, MailCopyChangeFlags.IsRead));
|
||||
}
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailStateUpdatedMessage(
|
||||
updatedMails,
|
||||
EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -38,6 +39,7 @@ public record ArchiveRequest(bool IsArchiving, MailCopy Item, MailItemFolder Fro
|
||||
}
|
||||
|
||||
public override MailSynchronizerOperation Operation => MailSynchronizerOperation.Archive;
|
||||
public override object GroupingKey() => (Operation, IsArchiving, FromFolder.Id, ToFolder?.Id ?? Guid.Empty);
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
@@ -55,4 +57,22 @@ public class BatchArchiveRequest : BatchCollection<ArchiveRequest>
|
||||
public BatchArchiveRequest(IEnumerable<ArchiveRequest> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
var removedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (removedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailRemovedMessage(removedMails, EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
var addedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (addedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailAddedMessage(addedMails, EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -25,25 +26,26 @@ public record ChangeFlagRequest(MailCopy Item, bool IsFlagged) : MailRequestBase
|
||||
/// If the mail is already in the desired flagged state, no change is needed.
|
||||
/// </summary>
|
||||
public bool IsNoOp { get; } = Item.IsFlagged == IsFlagged;
|
||||
public bool OriginalIsFlagged => _originalIsFlagged;
|
||||
|
||||
public override object GroupingKey() => (Operation, Item.FolderId, IsFlagged);
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
// Skip UI update if the mail is already in the desired state
|
||||
if (IsNoOp) return;
|
||||
|
||||
Item.IsFlagged = IsFlagged;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item, EntityUpdateSource.ClientUpdated, MailCopyChangeFlags.IsFlagged));
|
||||
WeakReferenceMessenger.Default.Send(new MailStateUpdatedMessage(
|
||||
new MailStateChange(Item.UniqueId, IsFlagged: IsFlagged),
|
||||
EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
// Skip UI revert if this was a no-op request
|
||||
if (IsNoOp) return;
|
||||
|
||||
Item.IsFlagged = _originalIsFlagged;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item, EntityUpdateSource.ClientReverted, MailCopyChangeFlags.IsFlagged));
|
||||
WeakReferenceMessenger.Default.Send(new MailStateUpdatedMessage(
|
||||
new MailStateChange(Item.UniqueId, IsFlagged: _originalIsFlagged),
|
||||
EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +54,34 @@ public class BatchChangeFlagRequest : BatchCollection<ChangeFlagRequest>
|
||||
public BatchChangeFlagRequest(IEnumerable<ChangeFlagRequest> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
var updatedMails = this
|
||||
.Where(x => !x.IsNoOp)
|
||||
.Select(x => new MailStateChange(x.Item.UniqueId, IsFlagged: x.IsFlagged))
|
||||
.ToList();
|
||||
|
||||
if (updatedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailStateUpdatedMessage(
|
||||
updatedMails,
|
||||
EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
var updatedMails = this
|
||||
.Where(x => !x.IsNoOp)
|
||||
.Select(x => new MailStateChange(x.Item.UniqueId, IsFlagged: x.OriginalIsFlagged))
|
||||
.ToList();
|
||||
|
||||
if (updatedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailStateUpdatedMessage(
|
||||
updatedMails,
|
||||
EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -30,6 +31,7 @@ public record ChangeJunkStateRequest(bool IsJunk, MailCopy Item, MailItemFolder
|
||||
public bool ExcludeMustHaveFolders => false;
|
||||
|
||||
public override MailSynchronizerOperation Operation => MailSynchronizerOperation.ChangeJunkState;
|
||||
public override object GroupingKey() => (Operation, IsJunk, FromFolder.Id, TargetFolder?.Id ?? Guid.Empty);
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
@@ -47,4 +49,22 @@ public class BatchChangeJunkStateRequest : BatchCollection<ChangeJunkStateReques
|
||||
public BatchChangeJunkStateRequest(IEnumerable<ChangeJunkStateRequest> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
var removedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (removedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailRemovedMessage(removedMails, EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
var addedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (addedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailAddedMessage(addedMails, EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -19,6 +20,7 @@ public record DeleteRequest(MailCopy MailItem) : MailRequestBase(MailItem),
|
||||
public List<Guid> SynchronizationFolderIds => [Item.FolderId];
|
||||
public bool ExcludeMustHaveFolders => false;
|
||||
public override MailSynchronizerOperation Operation => MailSynchronizerOperation.Delete;
|
||||
public override object GroupingKey() => (Operation, Item.FolderId);
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
@@ -36,4 +38,22 @@ public class BatchDeleteRequest : BatchCollection<DeleteRequest>
|
||||
public BatchDeleteRequest(IEnumerable<DeleteRequest> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
var removedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (removedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailRemovedMessage(removedMails, EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
var addedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (addedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailAddedMessage(addedMails, EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -24,25 +25,26 @@ public record MarkReadRequest(MailCopy Item, bool IsRead) : MailRequestBase(Item
|
||||
/// If the mail is already in the desired read state, no change is needed.
|
||||
/// </summary>
|
||||
public bool IsNoOp { get; } = Item.IsRead == IsRead;
|
||||
public bool OriginalIsRead => _originalIsRead;
|
||||
|
||||
public override object GroupingKey() => (Operation, Item.FolderId, IsRead);
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
// Skip UI update if the mail is already in the desired state
|
||||
if (IsNoOp) return;
|
||||
|
||||
Item.IsRead = IsRead;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item, EntityUpdateSource.ClientUpdated, MailCopyChangeFlags.IsRead));
|
||||
WeakReferenceMessenger.Default.Send(new MailStateUpdatedMessage(
|
||||
new MailStateChange(Item.UniqueId, IsRead: IsRead),
|
||||
EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
// Skip UI revert if this was a no-op request
|
||||
if (IsNoOp) return;
|
||||
|
||||
Item.IsRead = _originalIsRead;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new MailUpdatedMessage(Item, EntityUpdateSource.ClientReverted, MailCopyChangeFlags.IsRead));
|
||||
WeakReferenceMessenger.Default.Send(new MailStateUpdatedMessage(
|
||||
new MailStateChange(Item.UniqueId, IsRead: _originalIsRead),
|
||||
EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,4 +53,34 @@ public class BatchMarkReadRequest : BatchCollection<MarkReadRequest>
|
||||
public BatchMarkReadRequest(IEnumerable<MarkReadRequest> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
var updatedMails = this
|
||||
.Where(x => !x.IsNoOp)
|
||||
.Select(x => new MailStateChange(x.Item.UniqueId, IsRead: x.IsRead))
|
||||
.ToList();
|
||||
|
||||
if (updatedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailStateUpdatedMessage(
|
||||
updatedMails,
|
||||
EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
var updatedMails = this
|
||||
.Where(x => !x.IsNoOp)
|
||||
.Select(x => new MailStateChange(x.Item.UniqueId, IsRead: x.OriginalIsRead))
|
||||
.ToList();
|
||||
|
||||
if (updatedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailStateUpdatedMessage(
|
||||
updatedMails,
|
||||
EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -15,6 +16,7 @@ public record MoveRequest(MailCopy Item, MailItemFolder FromFolder, MailItemFold
|
||||
public List<Guid> SynchronizationFolderIds => new() { FromFolder.Id, ToFolder.Id };
|
||||
public bool ExcludeMustHaveFolders => false;
|
||||
public override MailSynchronizerOperation Operation => MailSynchronizerOperation.Move;
|
||||
public override object GroupingKey() => (Operation, FromFolder.Id, ToFolder?.Id ?? Guid.Empty);
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
@@ -32,4 +34,22 @@ public class BatchMoveRequest : BatchCollection<MoveRequest>, IUIChangeRequest
|
||||
public BatchMoveRequest(IEnumerable<MoveRequest> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ApplyUIChanges()
|
||||
{
|
||||
var removedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (removedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailRemovedMessage(removedMails, EntityUpdateSource.ClientUpdated));
|
||||
}
|
||||
|
||||
public override void RevertUIChanges()
|
||||
{
|
||||
var addedMails = this.Select(x => x.Item).Where(x => x != null).ToList();
|
||||
if (addedMails.Count == 0)
|
||||
return;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new BulkMailAddedMessage(addedMails, EntityUpdateSource.ClientReverted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Serilog;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models;
|
||||
using Wino.Core.Domain.Models.AutoDiscovery;
|
||||
using Wino.Core.Domain.Validation;
|
||||
|
||||
namespace Wino.Core.Services;
|
||||
|
||||
@@ -450,6 +451,9 @@ public class AutoDiscoveryService : IAutoDiscoveryService
|
||||
|
||||
private async Task<bool> HasAnyDnsAddressRecordAsync(string host, CancellationToken cancellationToken)
|
||||
{
|
||||
if (MailAccountAddressValidator.IsImplicitlyResolvableHost(host))
|
||||
return true;
|
||||
|
||||
var aRecords = await QueryDnsAsync(host, "A", cancellationToken).ConfigureAwait(false);
|
||||
if (aRecords.Count > 0)
|
||||
return true;
|
||||
|
||||
@@ -123,7 +123,7 @@ public class SynchronizationManager : ISynchronizationManager, IRecipient<Accoun
|
||||
catch (ImapClientPoolException clientPoolException)
|
||||
{
|
||||
_logger.Error(clientPoolException, "IMAP connectivity test failed");
|
||||
return ImapConnectivityTestResults.Failure(clientPoolException);
|
||||
return ImapConnectivityTestResults.Failure(clientPoolException.InnerException ?? clientPoolException);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -259,25 +259,48 @@ public class SynchronizationManager : ISynchronizationManager, IRecipient<Accoun
|
||||
/// <param name="accountId">Account ID to queue the request for</param>
|
||||
/// <param name="triggerSynchronization">Whether to automatically trigger synchronization after queuing the request</param>
|
||||
public async Task QueueRequestAsync(IRequestBase request, Guid accountId, bool triggerSynchronization)
|
||||
=> await QueueRequestsAsync([request], accountId, triggerSynchronization).ConfigureAwait(false);
|
||||
|
||||
public async Task QueueRequestsAsync(IEnumerable<IRequestBase> requests, Guid accountId, bool triggerSynchronization)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
var requestList = requests?.Where(request => request != null).ToList() ?? [];
|
||||
if (requestList.Count == 0)
|
||||
return;
|
||||
|
||||
var synchronizer = await GetOrCreateSynchronizerAsync(accountId);
|
||||
if (synchronizer == null)
|
||||
{
|
||||
_logger.Error("Could not find or create synchronizer for account {AccountId} to queue request", accountId);
|
||||
_logger.Error("Could not find or create synchronizer for account {AccountId} to queue {RequestCount} request(s)", accountId, requestList.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug("Queuing request {RequestType} for account {AccountId}",
|
||||
request.GetType().Name, accountId);
|
||||
if (requestList.Count == 1)
|
||||
{
|
||||
_logger.Debug("Queuing request {RequestType} for account {AccountId}",
|
||||
requestList[0].GetType().Name, accountId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var requestSummary = string.Join(", ", requestList
|
||||
.GroupBy(request => request.GetType().Name)
|
||||
.OrderBy(group => group.Key)
|
||||
.Select(group => $"{group.Key} x{group.Count()}"));
|
||||
|
||||
synchronizer.QueueRequest(request);
|
||||
_logger.Debug("Queuing {RequestCount} requests for account {AccountId}: {RequestSummary}",
|
||||
requestList.Count, accountId, requestSummary);
|
||||
}
|
||||
|
||||
foreach (var request in requestList)
|
||||
{
|
||||
synchronizer.QueueRequest(request);
|
||||
}
|
||||
|
||||
if (triggerSynchronization)
|
||||
{
|
||||
// Determine if this is a calendar or mail operation
|
||||
bool isCalendarOperation = request is ICalendarActionRequest;
|
||||
bool isCalendarOperation = requestList.All(request => request is ICalendarActionRequest);
|
||||
|
||||
if (isCalendarOperation)
|
||||
{
|
||||
|
||||
@@ -93,13 +93,11 @@ public class WinoRequestDelegator : IWinoRequestDelegator
|
||||
// Queue requests for each account and start synchronization.
|
||||
foreach (var accountGroup in accountIds)
|
||||
{
|
||||
foreach (var accountRequest in accountGroup)
|
||||
{
|
||||
await QueueRequestAsync(accountRequest, accountGroup.Key);
|
||||
}
|
||||
var groupedRequests = accountGroup.Cast<IRequestBase>().ToList();
|
||||
await QueueRequestsAsync(groupedRequests, accountGroup.Key).ConfigureAwait(false);
|
||||
|
||||
var account = accountGroup.First().Item.AssignedAccount;
|
||||
var actionItems = SynchronizationActionHelper.CreateActionItems(accountGroup, accountGroup.Key, account.Name);
|
||||
var actionItems = SynchronizationActionHelper.CreateActionItems(groupedRequests, accountGroup.Key, account.Name);
|
||||
|
||||
if (actionItems.Count > 0)
|
||||
WeakReferenceMessenger.Default.Send(new SynchronizationActionsAdded(accountGroup.Key, account.Name, actionItems));
|
||||
@@ -214,10 +212,7 @@ public class WinoRequestDelegator : IWinoRequestDelegator
|
||||
if (requestList.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var request in requestList)
|
||||
{
|
||||
await QueueRequestAsync(request, accountId).ConfigureAwait(false);
|
||||
}
|
||||
await QueueRequestsAsync(requestList, accountId).ConfigureAwait(false);
|
||||
|
||||
await SendSyncActionsAddedAsync(requestList, accountId).ConfigureAwait(false);
|
||||
await QueueSynchronizationAsync(accountId).ConfigureAwait(false);
|
||||
@@ -274,7 +269,12 @@ public class WinoRequestDelegator : IWinoRequestDelegator
|
||||
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||
{
|
||||
// Don't trigger synchronization for individual requests - we'll trigger it once for all requests
|
||||
await SynchronizationManager.Instance.QueueRequestAsync(request, accountId, triggerSynchronization: false);
|
||||
await SynchronizationManager.Instance.QueueRequestAsync(request, accountId, triggerSynchronization: false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task QueueRequestsAsync(IEnumerable<IRequestBase> requests, Guid accountId)
|
||||
{
|
||||
await SynchronizationManager.Instance.QueueRequestsAsync(requests, accountId, triggerSynchronization: false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task QueueSynchronizationAsync(Guid accountId)
|
||||
|
||||
@@ -54,6 +54,10 @@ public class WinoRequestProcessor : IWinoRequestProcessor
|
||||
{
|
||||
var action = preperationRequest.Action;
|
||||
var moveTargetStructure = preperationRequest.MoveTargetFolder;
|
||||
var mailItems = preperationRequest.MailItems?.Where(item => item != null).ToList() ?? [];
|
||||
|
||||
if (mailItems.Count == 0)
|
||||
return [];
|
||||
|
||||
// Ask confirmation for permanent delete operation.
|
||||
// Drafts are always hard deleted without any protection.
|
||||
@@ -78,12 +82,12 @@ public class WinoRequestProcessor : IWinoRequestProcessor
|
||||
// Handle the case when user is trying to move multiple mails that belong to different accounts.
|
||||
// We can't handle this with only 1 picker dialog.
|
||||
|
||||
bool isInvalidMoveTarget = preperationRequest.MailItems.Select(a => a.AssignedAccount.Id).Distinct().Count() > 1;
|
||||
bool isInvalidMoveTarget = mailItems.Select(a => a.AssignedAccount.Id).Distinct().Count() > 1;
|
||||
|
||||
if (isInvalidMoveTarget)
|
||||
throw new InvalidMoveTargetException(InvalidMoveTargetReason.MultipleAccounts);
|
||||
|
||||
var accountId = preperationRequest.MailItems.FirstOrDefault().AssignedAccount.Id;
|
||||
var accountId = mailItems[0].AssignedAccount.Id;
|
||||
|
||||
moveTargetStructure = await _dialogService.PickFolderAsync(accountId, PickFolderReason.Move, _folderService);
|
||||
|
||||
@@ -94,7 +98,7 @@ public class WinoRequestProcessor : IWinoRequestProcessor
|
||||
var requests = new List<IMailActionRequest>();
|
||||
|
||||
// TODO: Fix: Collection was modified; enumeration operation may not execute
|
||||
foreach (var item in preperationRequest.MailItems.ToList())
|
||||
foreach (var item in mailItems)
|
||||
{
|
||||
var singleRequest = await GetSingleRequestAsync(item, action, moveTargetStructure, preperationRequest.ToggleExecution);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Requests.Mail;
|
||||
using Wino.Core.Requests.Bundles;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
@@ -262,4 +263,75 @@ public abstract partial class BaseSynchronizer<TBaseRequest> : ObservableObject,
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected void ApplyOptimisticUiChanges(IEnumerable<IRequestBundle<TBaseRequest>> bundles, Func<IRequestBase, bool> shouldApply = null)
|
||||
{
|
||||
var bundleList = bundles?
|
||||
.Where(b => b?.Request != null && (shouldApply?.Invoke(b.Request) ?? true))
|
||||
.ToList() ?? [];
|
||||
|
||||
if (bundleList.Count == 0)
|
||||
return;
|
||||
|
||||
var requestList = new List<IRequestBase>(bundleList.Count);
|
||||
|
||||
foreach (var bundle in bundleList)
|
||||
{
|
||||
if (bundle.UIChangeRequest != null && !ReferenceEquals(bundle.UIChangeRequest, bundle.Request))
|
||||
{
|
||||
bundle.UIChangeRequest.ApplyUIChanges();
|
||||
continue;
|
||||
}
|
||||
|
||||
requestList.Add(bundle.Request);
|
||||
}
|
||||
|
||||
if (requestList.Count == 0)
|
||||
return;
|
||||
|
||||
var appliedBatchRequestKeys = new HashSet<object>();
|
||||
|
||||
foreach (var group in requestList.GroupBy(r => r.GroupingKey()))
|
||||
{
|
||||
var groupRequests = group.ToList();
|
||||
if (groupRequests.Count <= 1)
|
||||
continue;
|
||||
|
||||
if (!TryApplyBatchUiChanges(groupRequests))
|
||||
continue;
|
||||
|
||||
appliedBatchRequestKeys.Add(group.Key);
|
||||
}
|
||||
|
||||
foreach (var request in requestList)
|
||||
{
|
||||
if (!appliedBatchRequestKeys.Contains(request.GroupingKey()))
|
||||
{
|
||||
request.ApplyUIChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryApplyBatchUiChanges(IReadOnlyList<IRequestBase> requests)
|
||||
{
|
||||
if (requests == null || requests.Count <= 1)
|
||||
return false;
|
||||
|
||||
return requests[0] switch
|
||||
{
|
||||
MarkReadRequest => ApplyBatch(new BatchMarkReadRequest(requests.Cast<MarkReadRequest>())),
|
||||
ChangeFlagRequest => ApplyBatch(new BatchChangeFlagRequest(requests.Cast<ChangeFlagRequest>())),
|
||||
DeleteRequest => ApplyBatch(new BatchDeleteRequest(requests.Cast<DeleteRequest>())),
|
||||
MoveRequest => ApplyBatch(new BatchMoveRequest(requests.Cast<MoveRequest>())),
|
||||
ArchiveRequest => ApplyBatch(new BatchArchiveRequest(requests.Cast<ArchiveRequest>())),
|
||||
ChangeJunkStateRequest => ApplyBatch(new BatchChangeJunkStateRequest(requests.Cast<ChangeJunkStateRequest>())),
|
||||
_ => false
|
||||
};
|
||||
|
||||
static bool ApplyBatch(IUIChangeRequest request)
|
||||
{
|
||||
request.ApplyUIChanges();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1050,6 +1050,11 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
{
|
||||
_logger.Debug("Processing delta change {HistoryId} for {Name}", listHistoryResponse.HistoryId.GetValueOrDefault(), Account.Name);
|
||||
|
||||
var pendingStateUpdates = new List<MailCopyStateUpdate>();
|
||||
var pendingAssignmentCreates = new Dictionary<string, MailFolderAssignmentUpdate>(StringComparer.Ordinal);
|
||||
var pendingAssignmentDeletes = new Dictionary<string, MailFolderAssignmentUpdate>(StringComparer.Ordinal);
|
||||
var deletedMessageIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var history in listHistoryResponse.History)
|
||||
{
|
||||
// Handle label additions.
|
||||
@@ -1057,7 +1062,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
{
|
||||
foreach (var addedLabel in history.LabelsAdded)
|
||||
{
|
||||
await HandleLabelAssignmentAsync(addedLabel);
|
||||
await HandleLabelAssignmentAsync(addedLabel, pendingStateUpdates, pendingAssignmentCreates, pendingAssignmentDeletes).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1066,7 +1071,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
{
|
||||
foreach (var removedLabel in history.LabelsRemoved)
|
||||
{
|
||||
await HandleLabelRemovalAsync(removedLabel);
|
||||
await HandleLabelRemovalAsync(removedLabel, pendingStateUpdates, pendingAssignmentCreates, pendingAssignmentDeletes).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1079,36 +1084,108 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
|
||||
_logger.Debug("Processing message deletion for {MessageId}", messageId);
|
||||
|
||||
await _gmailChangeProcessor.DeleteMailAsync(Account.Id, messageId).ConfigureAwait(false);
|
||||
deletedMessageIds.Add(messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingStateUpdates.Count > 0)
|
||||
{
|
||||
await _gmailChangeProcessor.ApplyMailStateUpdatesAsync(pendingStateUpdates).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (pendingAssignmentCreates.Count > 0)
|
||||
{
|
||||
await _gmailChangeProcessor.CreateAssignmentsAsync(Account.Id, pendingAssignmentCreates.Values.ToList()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (pendingAssignmentDeletes.Count > 0)
|
||||
{
|
||||
await _gmailChangeProcessor.DeleteAssignmentsAsync(Account.Id, pendingAssignmentDeletes.Values.ToList()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (deletedMessageIds.Count > 0)
|
||||
{
|
||||
await _gmailChangeProcessor.DeleteMailsAsync(Account.Id, deletedMessageIds).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleArchiveAssignmentAsync(string archivedMessageId)
|
||||
private static string GetAssignmentChangeKey(string messageId, string labelId)
|
||||
=> $"{messageId}\u001f{labelId}";
|
||||
|
||||
private static void QueueAssignmentChange(
|
||||
Dictionary<string, MailFolderAssignmentUpdate> creates,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> deletes,
|
||||
MailFolderAssignmentUpdate assignment,
|
||||
bool shouldCreate)
|
||||
{
|
||||
if (assignment == null ||
|
||||
string.IsNullOrWhiteSpace(assignment.MailCopyId) ||
|
||||
string.IsNullOrWhiteSpace(assignment.RemoteFolderId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = GetAssignmentChangeKey(assignment.MailCopyId, assignment.RemoteFolderId);
|
||||
|
||||
if (shouldCreate)
|
||||
{
|
||||
deletes.Remove(key);
|
||||
creates[key] = assignment;
|
||||
}
|
||||
else
|
||||
{
|
||||
creates.Remove(key);
|
||||
deletes[key] = assignment;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleArchiveAssignmentAsync(
|
||||
string archivedMessageId,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentCreates,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentDeletes)
|
||||
{
|
||||
if (!archiveFolderId.HasValue)
|
||||
return;
|
||||
|
||||
// Ignore if the message is already in the archive.
|
||||
bool archived = await _gmailChangeProcessor.IsMailExistsInFolderAsync(archivedMessageId, archiveFolderId.Value);
|
||||
bool archived = await _gmailChangeProcessor.IsMailExistsInFolderAsync(archivedMessageId, archiveFolderId.Value).ConfigureAwait(false);
|
||||
|
||||
if (archived) return;
|
||||
|
||||
_logger.Debug("Processing archive assignment for message {Id}", archivedMessageId);
|
||||
|
||||
await _gmailChangeProcessor.CreateAssignmentAsync(Account.Id, archivedMessageId, ServiceConstants.ARCHIVE_LABEL_ID).ConfigureAwait(false);
|
||||
QueueAssignmentChange(
|
||||
pendingAssignmentCreates,
|
||||
pendingAssignmentDeletes,
|
||||
new MailFolderAssignmentUpdate(archivedMessageId, ServiceConstants.ARCHIVE_LABEL_ID),
|
||||
shouldCreate: true);
|
||||
}
|
||||
|
||||
private async Task HandleUnarchiveAssignmentAsync(string unarchivedMessageId)
|
||||
private async Task HandleUnarchiveAssignmentAsync(
|
||||
string unarchivedMessageId,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentCreates,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentDeletes)
|
||||
{
|
||||
if (!archiveFolderId.HasValue)
|
||||
return;
|
||||
|
||||
// Ignore if the message is not in the archive.
|
||||
bool archived = await _gmailChangeProcessor.IsMailExistsInFolderAsync(unarchivedMessageId, archiveFolderId.Value);
|
||||
bool archived = await _gmailChangeProcessor.IsMailExistsInFolderAsync(unarchivedMessageId, archiveFolderId.Value).ConfigureAwait(false);
|
||||
if (!archived) return;
|
||||
|
||||
_logger.Debug("Processing un-archive assignment for message {Id}", unarchivedMessageId);
|
||||
|
||||
await _gmailChangeProcessor.DeleteAssignmentAsync(Account.Id, unarchivedMessageId, ServiceConstants.ARCHIVE_LABEL_ID).ConfigureAwait(false);
|
||||
QueueAssignmentChange(
|
||||
pendingAssignmentCreates,
|
||||
pendingAssignmentDeletes,
|
||||
new MailFolderAssignmentUpdate(unarchivedMessageId, ServiceConstants.ARCHIVE_LABEL_ID),
|
||||
shouldCreate: false);
|
||||
}
|
||||
|
||||
private async Task HandleLabelAssignmentAsync(HistoryLabelAdded addedLabel)
|
||||
private async Task HandleLabelAssignmentAsync(
|
||||
HistoryLabelAdded addedLabel,
|
||||
List<MailCopyStateUpdate> pendingStateUpdates,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentCreates,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentDeletes)
|
||||
{
|
||||
var messageId = addedLabel.Message.Id;
|
||||
|
||||
@@ -1119,23 +1196,31 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
// ARCHIVE is a virtual folder - handle it separately
|
||||
if (labelId == ServiceConstants.ARCHIVE_LABEL_ID)
|
||||
{
|
||||
await HandleArchiveAssignmentAsync(messageId).ConfigureAwait(false);
|
||||
await HandleArchiveAssignmentAsync(messageId, pendingAssignmentCreates, pendingAssignmentDeletes).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// When UNREAD label is added mark the message as un-read.
|
||||
if (labelId == ServiceConstants.UNREAD_LABEL_ID)
|
||||
await _gmailChangeProcessor.ChangeMailReadStatusAsync(messageId, false).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(messageId, IsRead: false));
|
||||
|
||||
// When STARRED label is added mark the message as flagged.
|
||||
if (labelId == ServiceConstants.STARRED_LABEL_ID)
|
||||
await _gmailChangeProcessor.ChangeFlagStatusAsync(messageId, true).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(messageId, IsFlagged: true));
|
||||
|
||||
await _gmailChangeProcessor.CreateAssignmentAsync(Account.Id, messageId, labelId).ConfigureAwait(false);
|
||||
QueueAssignmentChange(
|
||||
pendingAssignmentCreates,
|
||||
pendingAssignmentDeletes,
|
||||
new MailFolderAssignmentUpdate(messageId, labelId),
|
||||
shouldCreate: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleLabelRemovalAsync(HistoryLabelRemoved removedLabel)
|
||||
private async Task HandleLabelRemovalAsync(
|
||||
HistoryLabelRemoved removedLabel,
|
||||
List<MailCopyStateUpdate> pendingStateUpdates,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentCreates,
|
||||
Dictionary<string, MailFolderAssignmentUpdate> pendingAssignmentDeletes)
|
||||
{
|
||||
var messageId = removedLabel.Message.Id;
|
||||
|
||||
@@ -1146,20 +1231,23 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
// ARCHIVE is a virtual folder - handle it separately
|
||||
if (labelId == ServiceConstants.ARCHIVE_LABEL_ID)
|
||||
{
|
||||
await HandleUnarchiveAssignmentAsync(messageId).ConfigureAwait(false);
|
||||
await HandleUnarchiveAssignmentAsync(messageId, pendingAssignmentCreates, pendingAssignmentDeletes).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// When UNREAD label is removed mark the message as read.
|
||||
if (labelId == ServiceConstants.UNREAD_LABEL_ID)
|
||||
await _gmailChangeProcessor.ChangeMailReadStatusAsync(messageId, true).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(messageId, IsRead: true));
|
||||
|
||||
// When STARRED label is removed mark the message as un-flagged.
|
||||
if (labelId == ServiceConstants.STARRED_LABEL_ID)
|
||||
await _gmailChangeProcessor.ChangeFlagStatusAsync(messageId, false).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(messageId, IsFlagged: false));
|
||||
|
||||
// For other labels remove the mail assignment.
|
||||
await _gmailChangeProcessor.DeleteAssignmentAsync(Account.Id, messageId, labelId).ConfigureAwait(false);
|
||||
QueueAssignmentChange(
|
||||
pendingAssignmentCreates,
|
||||
pendingAssignmentDeletes,
|
||||
new MailFolderAssignmentUpdate(messageId, labelId),
|
||||
shouldCreate: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1542,6 +1630,8 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
await Task.WhenAll(batchTasks).ConfigureAwait(false);
|
||||
|
||||
// Process all downloaded messages
|
||||
var pendingPackages = new List<NewMailItemPackage>();
|
||||
|
||||
foreach (var gmailMessage in downloadedMessages)
|
||||
{
|
||||
try
|
||||
@@ -1552,12 +1642,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
var packages = await CreateNewMailPackagesAsync(gmailMessage, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (packages != null)
|
||||
{
|
||||
foreach (var package in packages)
|
||||
{
|
||||
await _gmailChangeProcessor.CreateMailAsync(Account.Id, package).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
pendingPackages.AddRange(packages);
|
||||
|
||||
// Update sync identifier if available
|
||||
if (gmailMessage.HistoryId.HasValue)
|
||||
@@ -1570,6 +1655,11 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
_logger.Error(ex, "Failed to process downloaded message {MessageId}", gmailMessage.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingPackages.Count > 0)
|
||||
{
|
||||
await _gmailChangeProcessor.CreateMailsAsync(Account.Id, pendingPackages).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1592,12 +1682,9 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
// Create mail packages from metadata
|
||||
var packages = await CreateNewMailPackagesAsync(gmailMessage, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (packages != null)
|
||||
if (packages != null && packages.Count > 0)
|
||||
{
|
||||
foreach (var package in packages)
|
||||
{
|
||||
await _gmailChangeProcessor.CreateMailAsync(Account.Id, package).ConfigureAwait(false);
|
||||
}
|
||||
await _gmailChangeProcessor.CreateMailsAsync(Account.Id, packages).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Update sync identifier if available
|
||||
@@ -1897,9 +1984,19 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
if (historyId == null) return false;
|
||||
|
||||
var newHistoryId = historyId.Value;
|
||||
var currentSynchronizationIdentifier = Account.SynchronizationDeltaIdentifier;
|
||||
|
||||
return Account.SynchronizationDeltaIdentifier == null ||
|
||||
(ulong.TryParse(Account.SynchronizationDeltaIdentifier, out ulong currentIdentifier) && newHistoryId > currentIdentifier);
|
||||
if (string.IsNullOrWhiteSpace(currentSynchronizationIdentifier))
|
||||
return true;
|
||||
|
||||
if (!ulong.TryParse(currentSynchronizationIdentifier, out ulong currentIdentifier))
|
||||
{
|
||||
_logger.Warning("Current Gmail history ID '{HistoryId}' is invalid for {Name}. Replacing it with {NewHistoryId}.",
|
||||
currentSynchronizationIdentifier, Account.Name, newHistoryId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return newHistoryId > currentIdentifier;
|
||||
}
|
||||
|
||||
private async Task UpdateAccountSyncIdentifierAsync(ulong? historyId)
|
||||
@@ -1932,12 +2029,9 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
// Create mail packages from the downloaded message
|
||||
var packages = await CreateNewMailPackagesAsync(gmailMessage, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (packages != null)
|
||||
if (packages != null && packages.Count > 0)
|
||||
{
|
||||
foreach (var package in packages)
|
||||
{
|
||||
await _gmailChangeProcessor.CreateMailAsync(Account.Id, package).ConfigureAwait(false);
|
||||
}
|
||||
await _gmailChangeProcessor.CreateMailsAsync(Account.Id, packages).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await UpdateAccountSyncIdentifierAsync(gmailMessage.HistoryId).ConfigureAwait(false);
|
||||
@@ -1989,25 +2083,27 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
switch (bundle.UIChangeRequest)
|
||||
{
|
||||
case BatchMarkReadRequest batchMarkReadRequest:
|
||||
foreach (var request in batchMarkReadRequest)
|
||||
{
|
||||
await _gmailChangeProcessor.ChangeMailReadStatusAsync(request.Item.Id, request.IsRead).ConfigureAwait(false);
|
||||
}
|
||||
await _gmailChangeProcessor.ApplyMailStateUpdatesAsync(
|
||||
batchMarkReadRequest.Select(request => new MailCopyStateUpdate(request.Item.Id, IsRead: request.IsRead)))
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case MarkReadRequest markReadRequest:
|
||||
await _gmailChangeProcessor.ChangeMailReadStatusAsync(markReadRequest.Item.Id, markReadRequest.IsRead).ConfigureAwait(false);
|
||||
await _gmailChangeProcessor.ApplyMailStateUpdatesAsync(
|
||||
[new MailCopyStateUpdate(markReadRequest.Item.Id, IsRead: markReadRequest.IsRead)])
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case BatchChangeFlagRequest batchChangeFlagRequest:
|
||||
foreach (var request in batchChangeFlagRequest)
|
||||
{
|
||||
await _gmailChangeProcessor.ChangeFlagStatusAsync(request.Item.Id, request.IsFlagged).ConfigureAwait(false);
|
||||
}
|
||||
await _gmailChangeProcessor.ApplyMailStateUpdatesAsync(
|
||||
batchChangeFlagRequest.Select(request => new MailCopyStateUpdate(request.Item.Id, IsFlagged: request.IsFlagged)))
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
|
||||
case ChangeFlagRequest changeFlagRequest:
|
||||
await _gmailChangeProcessor.ChangeFlagStatusAsync(changeFlagRequest.Item.Id, changeFlagRequest.IsFlagged).ConfigureAwait(false);
|
||||
await _gmailChangeProcessor.ApplyMailStateUpdatesAsync(
|
||||
[new MailCopyStateUpdate(changeFlagRequest.Item.Id, IsFlagged: changeFlagRequest.IsFlagged)])
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2065,16 +2161,31 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
|
||||
}
|
||||
|
||||
var existingAfterDownload = await _gmailChangeProcessor.AreMailsExistsAsync(addedArchiveIds).ConfigureAwait(false);
|
||||
var pendingArchiveCreates = new Dictionary<string, MailFolderAssignmentUpdate>(StringComparer.Ordinal);
|
||||
var pendingArchiveDeletes = new Dictionary<string, MailFolderAssignmentUpdate>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var archiveAddedItem in existingAfterDownload)
|
||||
{
|
||||
await HandleArchiveAssignmentAsync(archiveAddedItem).ConfigureAwait(false);
|
||||
await HandleArchiveAssignmentAsync(archiveAddedItem, pendingArchiveCreates, pendingArchiveDeletes).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (pendingArchiveCreates.Count > 0)
|
||||
{
|
||||
await _gmailChangeProcessor.CreateAssignmentsAsync(Account.Id, pendingArchiveCreates.Values.ToList()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var pendingArchiveRemovals = new Dictionary<string, MailFolderAssignmentUpdate>(StringComparer.Ordinal);
|
||||
var pendingArchiveCreateOverrides = new Dictionary<string, MailFolderAssignmentUpdate>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var unAarchivedRemovedItem in removedArchiveIds)
|
||||
{
|
||||
await HandleUnarchiveAssignmentAsync(unAarchivedRemovedItem).ConfigureAwait(false);
|
||||
await HandleUnarchiveAssignmentAsync(unAarchivedRemovedItem, pendingArchiveCreateOverrides, pendingArchiveRemovals).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (pendingArchiveRemovals.Count > 0)
|
||||
{
|
||||
await _gmailChangeProcessor.DeleteAssignmentsAsync(Account.Id, pendingArchiveRemovals.Values.ToList()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ namespace Wino.Core.Synchronizers.ImapSync;
|
||||
public class UnifiedImapSynchronizer
|
||||
{
|
||||
private static readonly TimeSpan UidReconcileInterval = TimeSpan.FromHours(12);
|
||||
private const int NewMessageFetchBatchSize = 50;
|
||||
private const int ExistingMessageFlagFetchBatchSize = 250;
|
||||
|
||||
private readonly ILogger _logger = Log.ForContext<UnifiedImapSynchronizer>();
|
||||
private readonly IFolderService _folderService;
|
||||
@@ -47,6 +49,9 @@ public class UnifiedImapSynchronizer
|
||||
MessageSummaryItems.References |
|
||||
MessageSummaryItems.ModSeq |
|
||||
MessageSummaryItems.BodyStructure;
|
||||
private readonly MessageSummaryItems _existingMailSynchronizationFlags =
|
||||
MessageSummaryItems.Flags |
|
||||
MessageSummaryItems.UniqueId;
|
||||
|
||||
public UnifiedImapSynchronizer(
|
||||
IFolderService folderService,
|
||||
@@ -182,15 +187,35 @@ public class UnifiedImapSynchronizer
|
||||
|
||||
var downloadedMessageIds = new List<string>();
|
||||
|
||||
foreach (var batch in uids.Distinct().OrderBy(a => a.Id).Batch(50))
|
||||
foreach (var batch in uids.Distinct().OrderBy(a => a.Id).Batch(ExistingMessageFlagFetchBatchSize))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var summaryBatch = await remoteFolder
|
||||
.FetchAsync(new UniqueIdSet(batch.ToList(), SortOrder.Ascending), _mailSynchronizationFlags, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var batchUids = batch.ToList();
|
||||
var existingMails = await _mailService.GetExistingMailsAsync(localFolder.Id, batchUids).ConfigureAwait(false);
|
||||
var existingByUid = CreateExistingMailLookup(existingMails);
|
||||
var existingUids = batchUids.Where(uid => existingByUid.ContainsKey(uid.Id)).ToList();
|
||||
var newUids = batchUids.Where(uid => !existingByUid.ContainsKey(uid.Id)).ToList();
|
||||
|
||||
downloadedMessageIds.AddRange(await ProcessSummariesAsync(synchronizer, localFolder, summaryBatch, cancellationToken).ConfigureAwait(false));
|
||||
if (existingUids.Count > 0)
|
||||
{
|
||||
var existingSummaryBatch = await remoteFolder
|
||||
.FetchAsync(new UniqueIdSet(existingUids, SortOrder.Ascending), _existingMailSynchronizationFlags, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await ApplySummaryFlagUpdatesAsync(existingByUid, existingSummaryBatch).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var newBatch in newUids.Batch(NewMessageFetchBatchSize))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var newSummaryBatch = await remoteFolder
|
||||
.FetchAsync(new UniqueIdSet(newBatch.ToList(), SortOrder.Ascending), _mailSynchronizationFlags, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
downloadedMessageIds.AddRange(await ProcessSummariesCoreAsync(synchronizer, localFolder, newSummaryBatch, existingByUid, cancellationToken).ConfigureAwait(false));
|
||||
}
|
||||
}
|
||||
|
||||
UpdateHighestKnownUid(localFolder, remoteFolder, uids.Select(a => a.Id));
|
||||
@@ -268,7 +293,29 @@ public class UnifiedImapSynchronizer
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, changedUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
||||
var existingMails = await _mailService.GetExistingMailsAsync(folder.Id, changedUids).ConfigureAwait(false);
|
||||
var existingByUid = CreateExistingMailLookup(existingMails);
|
||||
var newOrUnknownUids = changedUids.Where(uid => !existingByUid.ContainsKey(uid.Id)).ToList();
|
||||
var existingUidsWithoutFlagEvents = changedUids
|
||||
.Where(uid => existingByUid.ContainsKey(uid.Id) && !changedFlags.ContainsKey(uid.Id))
|
||||
.ToList();
|
||||
|
||||
if (existingUidsWithoutFlagEvents.Count > 0)
|
||||
{
|
||||
var missingEventSummaries = await remoteFolder
|
||||
.FetchAsync(new UniqueIdSet(existingUidsWithoutFlagEvents, SortOrder.Ascending), _existingMailSynchronizationFlags, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
foreach (var summary in missingEventSummaries)
|
||||
{
|
||||
if (summary.UniqueId != UniqueId.Invalid && summary.Flags != null)
|
||||
{
|
||||
changedFlags[summary.UniqueId.Id] = summary.Flags.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadedMessageIds = await DownloadMessagesByUidsAsync(client, remoteFolder, folder, newOrUnknownUids, synchronizer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
folder.HighestModeSeq = unchecked((long)remoteFolder.HighestModSeq);
|
||||
|
||||
@@ -456,11 +503,19 @@ public class UnifiedImapSynchronizer
|
||||
folder.UidValidity = remoteFolder.UidValidity;
|
||||
}
|
||||
|
||||
private async Task<List<string>> ProcessSummariesAsync(
|
||||
private Task<List<string>> ProcessSummariesAsync(
|
||||
IImapSynchronizer synchronizer,
|
||||
MailItemFolder localFolder,
|
||||
IList<IMessageSummary> summaries,
|
||||
CancellationToken cancellationToken)
|
||||
=> ProcessSummariesCoreAsync(synchronizer, localFolder, summaries, existingByUid: null, cancellationToken);
|
||||
|
||||
private async Task<List<string>> ProcessSummariesCoreAsync(
|
||||
IImapSynchronizer synchronizer,
|
||||
MailItemFolder localFolder,
|
||||
IList<IMessageSummary> summaries,
|
||||
IReadOnlyDictionary<uint, MailCopy> existingByUid,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var downloadedMessageIds = new List<string>();
|
||||
|
||||
@@ -475,10 +530,8 @@ public class UnifiedImapSynchronizer
|
||||
if (uniqueIds.Count == 0)
|
||||
return downloadedMessageIds;
|
||||
|
||||
var existingMails = await _mailService.GetExistingMailsAsync(localFolder.Id, uniqueIds).ConfigureAwait(false);
|
||||
var existingByUid = existingMails
|
||||
.Select(m => (Uid: MailkitClientExtensions.ResolveUidStruct(m.Id), Mail: m))
|
||||
.ToDictionary(a => a.Uid.Id, a => a.Mail);
|
||||
existingByUid ??= CreateExistingMailLookup(await _mailService.GetExistingMailsAsync(localFolder.Id, uniqueIds).ConfigureAwait(false));
|
||||
var pendingStateUpdates = new List<MailCopyStateUpdate>();
|
||||
|
||||
foreach (var summary in summaries)
|
||||
{
|
||||
@@ -491,7 +544,11 @@ public class UnifiedImapSynchronizer
|
||||
{
|
||||
if (summary.Flags != null)
|
||||
{
|
||||
await UpdateMailFlagsAsync(existingMail, summary.Flags.Value).ConfigureAwait(false);
|
||||
var pendingStateUpdate = CreateMailStateUpdate(existingMail, summary.Flags.Value);
|
||||
if (pendingStateUpdate != null)
|
||||
{
|
||||
pendingStateUpdates.Add(pendingStateUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -516,23 +573,79 @@ public class UnifiedImapSynchronizer
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingStateUpdates.Count > 0)
|
||||
{
|
||||
await _mailService.ApplyMailStateUpdatesAsync(pendingStateUpdates).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return downloadedMessageIds;
|
||||
}
|
||||
|
||||
private async Task UpdateMailFlagsAsync(MailCopy mailCopy, MessageFlags flags)
|
||||
private async Task ApplySummaryFlagUpdatesAsync(
|
||||
IReadOnlyDictionary<uint, MailCopy> existingByUid,
|
||||
IList<IMessageSummary> summaries)
|
||||
{
|
||||
if (existingByUid == null || existingByUid.Count == 0 || summaries == null || summaries.Count == 0)
|
||||
return;
|
||||
|
||||
var pendingStateUpdates = new List<MailCopyStateUpdate>();
|
||||
|
||||
foreach (var summary in summaries)
|
||||
{
|
||||
if (summary.UniqueId == UniqueId.Invalid || summary.Flags == null)
|
||||
continue;
|
||||
|
||||
if (!existingByUid.TryGetValue(summary.UniqueId.Id, out var existingMail))
|
||||
continue;
|
||||
|
||||
var pendingStateUpdate = CreateMailStateUpdate(existingMail, summary.Flags.Value);
|
||||
if (pendingStateUpdate != null)
|
||||
{
|
||||
pendingStateUpdates.Add(pendingStateUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingStateUpdates.Count > 0)
|
||||
{
|
||||
await _mailService.ApplyMailStateUpdatesAsync(pendingStateUpdates).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<uint, MailCopy> CreateExistingMailLookup(IEnumerable<MailCopy> existingMails)
|
||||
{
|
||||
var lookup = new Dictionary<uint, MailCopy>();
|
||||
|
||||
foreach (var mail in existingMails ?? [])
|
||||
{
|
||||
if (mail == null || string.IsNullOrEmpty(mail.Id))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
lookup[MailkitClientExtensions.ResolveUidStruct(mail.Id).Id] = mail;
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
private static MailCopyStateUpdate CreateMailStateUpdate(MailCopy mailCopy, MessageFlags flags)
|
||||
{
|
||||
var isFlagged = MailkitClientExtensions.GetIsFlagged(flags);
|
||||
var isRead = MailkitClientExtensions.GetIsRead(flags);
|
||||
|
||||
if (isFlagged != mailCopy.IsFlagged)
|
||||
{
|
||||
await _mailService.ChangeFlagStatusAsync(mailCopy.Id, isFlagged).ConfigureAwait(false);
|
||||
}
|
||||
bool shouldUpdateFlagged = isFlagged != mailCopy.IsFlagged;
|
||||
bool shouldUpdateRead = isRead != mailCopy.IsRead;
|
||||
|
||||
if (isRead != mailCopy.IsRead)
|
||||
{
|
||||
await _mailService.ChangeReadStatusAsync(mailCopy.Id, isRead).ConfigureAwait(false);
|
||||
}
|
||||
return !shouldUpdateFlagged && !shouldUpdateRead
|
||||
? null
|
||||
: new MailCopyStateUpdate(
|
||||
mailCopy.Id,
|
||||
shouldUpdateRead ? isRead : null,
|
||||
shouldUpdateFlagged ? isFlagged : null);
|
||||
}
|
||||
|
||||
private async Task ApplyDeletedUidsAsync(MailItemFolder folder, IList<UniqueId> uniqueIds)
|
||||
@@ -552,15 +665,14 @@ public class UnifiedImapSynchronizer
|
||||
if (changedFlags == null || changedFlags.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var changed in changedFlags)
|
||||
{
|
||||
var localMailCopyId = MailkitClientExtensions.CreateUid(folder.Id, changed.Key);
|
||||
var isFlagged = MailkitClientExtensions.GetIsFlagged(changed.Value);
|
||||
var isRead = MailkitClientExtensions.GetIsRead(changed.Value);
|
||||
var stateUpdates = changedFlags
|
||||
.Select(changed => new MailCopyStateUpdate(
|
||||
MailkitClientExtensions.CreateUid(folder.Id, changed.Key),
|
||||
MailkitClientExtensions.GetIsRead(changed.Value),
|
||||
MailkitClientExtensions.GetIsFlagged(changed.Value)))
|
||||
.ToList();
|
||||
|
||||
await _mailService.ChangeReadStatusAsync(localMailCopyId, isRead).ConfigureAwait(false);
|
||||
await _mailService.ChangeFlagStatusAsync(localMailCopyId, isFlagged).ConfigureAwait(false);
|
||||
}
|
||||
await _mailService.ApplyMailStateUpdatesAsync(stateUpdates).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ReconcileUidBasedFlagChangesAsync(MailItemFolder localFolder, IMailFolder remoteFolder, CancellationToken cancellationToken)
|
||||
@@ -613,13 +725,14 @@ public class UnifiedImapSynchronizer
|
||||
|
||||
var existingMarkReadCandidates = await FilterExistingRemoteUidsAsync(remoteFolder, markReadCandidates, cancellationToken).ConfigureAwait(false);
|
||||
var existingUnflagCandidates = await FilterExistingRemoteUidsAsync(remoteFolder, unflagCandidates, cancellationToken).ConfigureAwait(false);
|
||||
var pendingStateUpdates = new List<MailCopyStateUpdate>();
|
||||
|
||||
foreach (var uid in existingMarkReadCandidates)
|
||||
{
|
||||
if (!localByUid.TryGetValue(uid, out var localMail) || localMail.IsRead)
|
||||
continue;
|
||||
|
||||
await _mailService.ChangeReadStatusAsync(localMail.Id, true).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(localMail.Id, IsRead: true));
|
||||
}
|
||||
|
||||
foreach (var uid in remoteUnreadUids)
|
||||
@@ -627,7 +740,7 @@ public class UnifiedImapSynchronizer
|
||||
if (!localByUid.TryGetValue(uid, out var localMail) || !localMail.IsRead)
|
||||
continue;
|
||||
|
||||
await _mailService.ChangeReadStatusAsync(localMail.Id, false).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(localMail.Id, IsRead: false));
|
||||
}
|
||||
|
||||
foreach (var uid in existingUnflagCandidates)
|
||||
@@ -635,7 +748,7 @@ public class UnifiedImapSynchronizer
|
||||
if (!localByUid.TryGetValue(uid, out var localMail) || !localMail.IsFlagged)
|
||||
continue;
|
||||
|
||||
await _mailService.ChangeFlagStatusAsync(localMail.Id, false).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(localMail.Id, IsFlagged: false));
|
||||
}
|
||||
|
||||
foreach (var uid in remoteFlaggedUids)
|
||||
@@ -643,7 +756,12 @@ public class UnifiedImapSynchronizer
|
||||
if (!localByUid.TryGetValue(uid, out var localMail) || localMail.IsFlagged)
|
||||
continue;
|
||||
|
||||
await _mailService.ChangeFlagStatusAsync(localMail.Id, true).ConfigureAwait(false);
|
||||
pendingStateUpdates.Add(new MailCopyStateUpdate(localMail.Id, IsFlagged: true));
|
||||
}
|
||||
|
||||
if (pendingStateUpdates.Count > 0)
|
||||
{
|
||||
await _mailService.ApplyMailStateUpdatesAsync(pendingStateUpdates).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,56 +112,104 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
|
||||
public override List<IRequestBundle<ImapRequest>> Move(BatchMoveRequest requests)
|
||||
{
|
||||
return CreateTaskBundle(async (client, item) =>
|
||||
{
|
||||
var sourceFolder = await client.GetFolderAsync(item.FromFolder.RemoteFolderId);
|
||||
var destinationFolder = await client.GetFolderAsync(item.ToFolder.RemoteFolderId);
|
||||
if (requests == null || requests.Count == 0)
|
||||
return [];
|
||||
|
||||
return CreateSingleTaskBundle(async (client, _) =>
|
||||
{
|
||||
var sourceFolder = await client.GetFolderAsync(requests[0].FromFolder.RemoteFolderId).ConfigureAwait(false);
|
||||
var destinationFolder = await client.GetFolderAsync(requests[0].ToFolder.RemoteFolderId).ConfigureAwait(false);
|
||||
var uniqueIds = requests.Select(item => GetUniqueId(item.Item.Id)).ToList();
|
||||
|
||||
// Only opening source folder is enough.
|
||||
await sourceFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
|
||||
await sourceFolder.MoveToAsync(GetUniqueId(item.Item.Id), destinationFolder).ConfigureAwait(false);
|
||||
await sourceFolder.CloseAsync().ConfigureAwait(false);
|
||||
}, requests);
|
||||
try
|
||||
{
|
||||
await sourceFolder.MoveToAsync(uniqueIds, destinationFolder).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await sourceFolder.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}, requests[0], requests);
|
||||
}
|
||||
|
||||
public override List<IRequestBundle<ImapRequest>> ChangeFlag(BatchChangeFlagRequest requests)
|
||||
{
|
||||
return CreateTaskBundle(async (client, item) =>
|
||||
if (requests == null || requests.Count == 0)
|
||||
return [];
|
||||
|
||||
return CreateSingleTaskBundle(async (client, _) =>
|
||||
{
|
||||
var folder = item.Item.AssignedFolder;
|
||||
var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId);
|
||||
var folder = requests[0].Item.AssignedFolder;
|
||||
var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId).ConfigureAwait(false);
|
||||
var uniqueIds = requests.Select(item => GetUniqueId(item.Item.Id)).ToList();
|
||||
var request = new StoreFlagsRequest(requests[0].IsFlagged ? StoreAction.Add : StoreAction.Remove, MessageFlags.Flagged)
|
||||
{
|
||||
Silent = true
|
||||
};
|
||||
|
||||
await remoteFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
|
||||
await remoteFolder.StoreAsync(GetUniqueId(item.Item.Id), new StoreFlagsRequest(item.IsFlagged ? StoreAction.Add : StoreAction.Remove, MessageFlags.Flagged) { Silent = true }).ConfigureAwait(false);
|
||||
await remoteFolder.CloseAsync().ConfigureAwait(false);
|
||||
}, requests);
|
||||
try
|
||||
{
|
||||
await remoteFolder.StoreAsync(uniqueIds, request).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await remoteFolder.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}, requests[0], requests);
|
||||
}
|
||||
|
||||
public override List<IRequestBundle<ImapRequest>> Delete(BatchDeleteRequest requests)
|
||||
{
|
||||
return CreateTaskBundle(async (client, request) =>
|
||||
if (requests == null || requests.Count == 0)
|
||||
return [];
|
||||
|
||||
return CreateSingleTaskBundle(async (client, _) =>
|
||||
{
|
||||
var folder = request.Item.AssignedFolder;
|
||||
var folder = requests[0].Item.AssignedFolder;
|
||||
var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId).ConfigureAwait(false);
|
||||
var uniqueIds = requests.Select(request => GetUniqueId(request.Item.Id)).ToList();
|
||||
var storeRequest = new StoreFlagsRequest(StoreAction.Add, MessageFlags.Deleted) { Silent = true };
|
||||
|
||||
await remoteFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
|
||||
await remoteFolder.AddFlagsAsync(GetUniqueId(request.Item.Id), MessageFlags.Deleted, true);
|
||||
await remoteFolder.ExpungeAsync().ConfigureAwait(false);
|
||||
await remoteFolder.CloseAsync().ConfigureAwait(false);
|
||||
}, requests);
|
||||
try
|
||||
{
|
||||
await remoteFolder.StoreAsync(uniqueIds, storeRequest).ConfigureAwait(false);
|
||||
await remoteFolder.ExpungeAsync(uniqueIds).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await remoteFolder.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}, requests[0], requests);
|
||||
}
|
||||
|
||||
public override List<IRequestBundle<ImapRequest>> MarkRead(BatchMarkReadRequest requests)
|
||||
{
|
||||
return CreateTaskBundle(async (client, request) =>
|
||||
if (requests == null || requests.Count == 0)
|
||||
return [];
|
||||
|
||||
return CreateSingleTaskBundle(async (client, _) =>
|
||||
{
|
||||
var folder = request.Item.AssignedFolder;
|
||||
var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId);
|
||||
var folder = requests[0].Item.AssignedFolder;
|
||||
var remoteFolder = await client.GetFolderAsync(folder.RemoteFolderId).ConfigureAwait(false);
|
||||
var uniqueIds = requests.Select(request => GetUniqueId(request.Item.Id)).ToList();
|
||||
var storeRequest = new StoreFlagsRequest(requests[0].IsRead ? StoreAction.Add : StoreAction.Remove, MessageFlags.Seen)
|
||||
{
|
||||
Silent = true
|
||||
};
|
||||
|
||||
await remoteFolder.OpenAsync(FolderAccess.ReadWrite).ConfigureAwait(false);
|
||||
await remoteFolder.StoreAsync(GetUniqueId(request.Item.Id), new StoreFlagsRequest(request.IsRead ? StoreAction.Add : StoreAction.Remove, MessageFlags.Seen) { Silent = true }).ConfigureAwait(false);
|
||||
await remoteFolder.CloseAsync().ConfigureAwait(false);
|
||||
}, requests);
|
||||
try
|
||||
{
|
||||
await remoteFolder.StoreAsync(uniqueIds, storeRequest).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await remoteFolder.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
}, requests[0], requests);
|
||||
}
|
||||
|
||||
public override List<IRequestBundle<ImapRequest>> CreateDraft(CreateDraftRequest request)
|
||||
@@ -718,13 +766,7 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
// First apply the UI changes for each bundle.
|
||||
// This is important to reflect changes to the UI before the network call is done.
|
||||
|
||||
foreach (var item in batchedRequests)
|
||||
{
|
||||
if (ShouldApplyOptimisticUIChanges(item.Request))
|
||||
{
|
||||
item.Request.ApplyUIChanges();
|
||||
}
|
||||
}
|
||||
ApplyOptimisticUiChanges(batchedRequests, ShouldApplyOptimisticUIChanges);
|
||||
|
||||
// All task bundles will execute on the same client.
|
||||
// Tasks themselves don't pull the client from the pool
|
||||
@@ -754,7 +796,7 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
|
||||
if (ShouldApplyOptimisticUIChanges(item.Request))
|
||||
{
|
||||
item.Request.RevertUIChanges();
|
||||
item.UIChangeRequest?.RevertUIChanges();
|
||||
}
|
||||
|
||||
isCrashed = true;
|
||||
@@ -795,7 +837,7 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
|
||||
if (ShouldApplyOptimisticUIChanges(item.Request))
|
||||
{
|
||||
item.Request.RevertUIChanges();
|
||||
item.UIChangeRequest?.RevertUIChanges();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
@@ -1480,7 +1522,7 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||||
var usedCalendarColors = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var remotePrimaryCalendarId = remoteCalendars.FirstOrDefault()?.RemoteCalendarId;
|
||||
var remotePrimaryCalendarId = GetPrimaryCalDavCalendarId(remoteCalendars);
|
||||
|
||||
foreach (var localCalendar in localCalendars.ToList())
|
||||
{
|
||||
@@ -1503,6 +1545,7 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
|
||||
if (existingLocal == null)
|
||||
{
|
||||
var insertedCalendarColor = ResolveSynchronizedCalendarBackgroundColor(remoteCalendar.BackgroundColorHex, null, usedCalendarColors);
|
||||
var newCalendar = new AccountCalendar
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -1510,10 +1553,12 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
RemoteCalendarId = remoteCalendar.RemoteCalendarId,
|
||||
Name = remoteCalendar.Name,
|
||||
IsPrimary = isPrimary,
|
||||
IsReadOnly = remoteCalendar.IsReadOnly,
|
||||
IsSynchronizationEnabled = true,
|
||||
IsExtended = true,
|
||||
BackgroundColorHex = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors),
|
||||
TimeZone = "UTC",
|
||||
DefaultShowAs = remoteCalendar.DefaultShowAs,
|
||||
BackgroundColorHex = insertedCalendarColor,
|
||||
TimeZone = remoteCalendar.TimeZone,
|
||||
SynchronizationDeltaToken = string.Empty
|
||||
};
|
||||
|
||||
@@ -1523,10 +1568,15 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
continue;
|
||||
}
|
||||
|
||||
var resolvedColor = ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors, existingLocal.BackgroundColorHex);
|
||||
var resolvedColor = ResolveSynchronizedCalendarBackgroundColor(remoteCalendar.BackgroundColorHex, existingLocal, usedCalendarColors);
|
||||
var resolvedTextColor = ColorHelpers.GetReadableTextColorHex(resolvedColor);
|
||||
var shouldUpdate = !string.Equals(existingLocal.Name, remoteCalendar.Name, StringComparison.Ordinal)
|
||||
|| !string.Equals(existingLocal.TimeZone, remoteCalendar.TimeZone, StringComparison.OrdinalIgnoreCase)
|
||||
|| existingLocal.IsReadOnly != remoteCalendar.IsReadOnly
|
||||
|| existingLocal.DefaultShowAs != remoteCalendar.DefaultShowAs
|
||||
|| existingLocal.IsPrimary != isPrimary
|
||||
|| !string.Equals(existingLocal.BackgroundColorHex, resolvedColor, StringComparison.OrdinalIgnoreCase);
|
||||
|| !string.Equals(existingLocal.BackgroundColorHex, resolvedColor, StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(existingLocal.TextColorHex, resolvedTextColor, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!shouldUpdate)
|
||||
{
|
||||
@@ -1535,14 +1585,54 @@ public class ImapSynchronizer : WinoSynchronizer<ImapRequest, ImapMessageCreatio
|
||||
}
|
||||
|
||||
existingLocal.Name = remoteCalendar.Name;
|
||||
existingLocal.TimeZone = remoteCalendar.TimeZone;
|
||||
existingLocal.IsReadOnly = remoteCalendar.IsReadOnly;
|
||||
existingLocal.DefaultShowAs = remoteCalendar.DefaultShowAs;
|
||||
existingLocal.IsPrimary = isPrimary;
|
||||
existingLocal.BackgroundColorHex = resolvedColor;
|
||||
existingLocal.TextColorHex = ColorHelpers.GetReadableTextColorHex(existingLocal.BackgroundColorHex);
|
||||
existingLocal.TextColorHex = resolvedTextColor;
|
||||
usedCalendarColors.Add(existingLocal.BackgroundColorHex);
|
||||
await _imapChangeProcessor.UpdateAccountCalendarAsync(existingLocal).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPrimaryCalDavCalendarId(IReadOnlyList<CalDavCalendar> remoteCalendars)
|
||||
{
|
||||
if (remoteCalendars == null || remoteCalendars.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
if (remoteCalendars.Any(calendar => calendar.Order.HasValue))
|
||||
{
|
||||
return remoteCalendars
|
||||
.OrderBy(calendar => calendar.Order ?? double.MaxValue)
|
||||
.ThenBy(calendar => calendar.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(calendar => calendar.RemoteCalendarId)
|
||||
.FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
|
||||
return remoteCalendars.First().RemoteCalendarId;
|
||||
}
|
||||
|
||||
private static string ResolveSynchronizedCalendarBackgroundColor(
|
||||
string remoteBackgroundColor,
|
||||
AccountCalendar accountCalendar,
|
||||
ISet<string> usedCalendarColors = null)
|
||||
{
|
||||
if (accountCalendar?.IsBackgroundColorUserOverridden == true)
|
||||
return accountCalendar.BackgroundColorHex;
|
||||
|
||||
var preferredColor = string.IsNullOrWhiteSpace(remoteBackgroundColor)
|
||||
? accountCalendar?.BackgroundColorHex
|
||||
: remoteBackgroundColor;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(remoteBackgroundColor) && usedCalendarColors != null)
|
||||
return ColorHelpers.GetDistinctFlatColorHex(usedCalendarColors, preferredColor);
|
||||
|
||||
return string.IsNullOrWhiteSpace(preferredColor)
|
||||
? ColorHelpers.GenerateFlatColorHex()
|
||||
: preferredColor;
|
||||
}
|
||||
|
||||
private interface IImapCalendarOperationHandler
|
||||
{
|
||||
bool RequiresConnectedClient { get; }
|
||||
|
||||
@@ -2020,10 +2020,7 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
|
||||
{
|
||||
// First apply all UI changes immediately before any batching.
|
||||
// This ensures UI reflects changes right away, regardless of batch processing.
|
||||
foreach (var bundle in batchedRequests)
|
||||
{
|
||||
bundle.UIChangeRequest?.ApplyUIChanges();
|
||||
}
|
||||
ApplyOptimisticUiChanges(batchedRequests);
|
||||
|
||||
// SendDraft requests may include large attachments, which require upload sessions.
|
||||
// Upload these attachments before the batched patch/send sequence.
|
||||
|
||||
@@ -161,11 +161,11 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
||||
|
||||
foreach (var group in keys)
|
||||
{
|
||||
var key = group.Key;
|
||||
var firstRequest = group.FirstOrDefault();
|
||||
|
||||
if (key is MailSynchronizerOperation mailSynchronizerOperation)
|
||||
if (firstRequest is IMailActionRequest mailActionRequest)
|
||||
{
|
||||
switch (mailSynchronizerOperation)
|
||||
switch (mailActionRequest.Operation)
|
||||
{
|
||||
case MailSynchronizerOperation.MarkRead:
|
||||
nativeRequests.AddRange(MarkRead(new BatchMarkReadRequest(group.Cast<MarkReadRequest>())));
|
||||
@@ -204,9 +204,9 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (key is FolderSynchronizerOperation folderSynchronizerOperation)
|
||||
else if (firstRequest is IFolderActionRequest folderActionRequest)
|
||||
{
|
||||
switch (folderSynchronizerOperation)
|
||||
switch (folderActionRequest.Operation)
|
||||
{
|
||||
case FolderSynchronizerOperation.RenameFolder:
|
||||
nativeRequests.AddRange(RenameFolder(group.ElementAt(0) as RenameFolderRequest));
|
||||
@@ -230,9 +230,9 @@ public abstract class WinoSynchronizer<TBaseRequest, TMessageType, TCalendarEven
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (key is CategorySynchronizerOperation categorySynchronizerOperation)
|
||||
else if (firstRequest is ICategoryActionRequest categoryActionRequest)
|
||||
{
|
||||
switch (categorySynchronizerOperation)
|
||||
switch (categoryActionRequest.Operation)
|
||||
{
|
||||
case CategorySynchronizerOperation.CreateCategory:
|
||||
nativeRequests.AddRange(CreateCategory(group.ElementAt(0) as MailCategoryCreateRequest));
|
||||
|
||||
@@ -6,6 +6,7 @@ using FluentAssertions;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Mail.ViewModels.Collections;
|
||||
using Wino.Mail.ViewModels.Data;
|
||||
using Xunit;
|
||||
@@ -167,7 +168,7 @@ public class WinoMailCollectionTests
|
||||
groupItems.Add(item);
|
||||
}
|
||||
|
||||
groups.Add(((DateTime)group.Key, groupItems));
|
||||
groups.Add((((MailListGroupKey)group.Key).Value is DateTime keyDate ? keyDate : default, groupItems));
|
||||
}
|
||||
|
||||
groups.Should().NotBeEmpty();
|
||||
@@ -188,6 +189,45 @@ public class WinoMailCollectionTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddRangeAsync_ShouldPlacePinnedItemsBeforeUnpinnedItems()
|
||||
{
|
||||
var sut = CreateCollection();
|
||||
var olderPinned = CreateMailCopy(threadId: "pinned", creationDate: DateTime.UtcNow.AddDays(-3));
|
||||
olderPinned.IsPinned = true;
|
||||
|
||||
var newerUnpinned = CreateMailCopy(threadId: "regular", creationDate: DateTime.UtcNow);
|
||||
|
||||
await sut.AddRangeAsync(
|
||||
[
|
||||
new MailItemViewModel(newerUnpinned),
|
||||
new MailItemViewModel(olderPinned)
|
||||
],
|
||||
clearIdCache: true);
|
||||
|
||||
var firstItem = FlattenItems(sut).First().Should().BeOfType<MailItemViewModel>().Subject;
|
||||
firstItem.MailCopy.UniqueId.Should().Be(olderPinned.UniqueId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateMailCopy_ShouldMovePinnedItemToTop()
|
||||
{
|
||||
var sut = CreateCollection();
|
||||
var older = CreateMailCopy(threadId: "older", creationDate: DateTime.UtcNow.AddDays(-2));
|
||||
var newer = CreateMailCopy(threadId: "newer", creationDate: DateTime.UtcNow);
|
||||
|
||||
await sut.AddAsync(older);
|
||||
await sut.AddAsync(newer);
|
||||
|
||||
var updatedOlder = CloneMailCopy(older);
|
||||
updatedOlder.IsPinned = true;
|
||||
|
||||
await sut.UpdateMailCopy(updatedOlder, EntityUpdateSource.Server, MailCopyChangeFlags.IsPinned);
|
||||
|
||||
var firstItem = FlattenItems(sut).First().Should().BeOfType<MailItemViewModel>().Subject;
|
||||
firstItem.MailCopy.UniqueId.Should().Be(older.UniqueId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateMailCopy_ShouldMergeExistingSingles_WhenThreadIdChangesToMatch()
|
||||
{
|
||||
@@ -371,6 +411,7 @@ public class WinoMailCollectionTests
|
||||
Importance = source.Importance,
|
||||
IsRead = source.IsRead,
|
||||
IsFlagged = source.IsFlagged,
|
||||
IsPinned = source.IsPinned,
|
||||
IsFocused = source.IsFocused,
|
||||
HasAttachments = source.HasAttachments,
|
||||
ItemType = source.ItemType,
|
||||
|
||||
@@ -59,6 +59,29 @@ public class MailItemViewModelUpdateTests
|
||||
nameof(MailItemViewModel.SortingName));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateFrom_ShouldNotifyPinnedState_WhenPinnedChanges()
|
||||
{
|
||||
var original = CreateMailCopy("thread-1", DateTime.UtcNow);
|
||||
var updated = CloneMailCopy(original);
|
||||
updated.IsPinned = true;
|
||||
|
||||
var sut = new MailItemViewModel(original);
|
||||
var raisedProperties = new List<string>();
|
||||
|
||||
sut.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.PropertyName))
|
||||
{
|
||||
raisedProperties.Add(e.PropertyName);
|
||||
}
|
||||
};
|
||||
|
||||
sut.UpdateFrom(updated);
|
||||
|
||||
raisedProperties.Should().Contain(nameof(MailItemViewModel.IsPinned));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateMailCopy_ShouldNotifyThreadOnlyForReadState_WhenReadStateChanges()
|
||||
{
|
||||
@@ -125,6 +148,7 @@ public class MailItemViewModelUpdateTests
|
||||
Importance = MailImportance.Normal,
|
||||
IsRead = false,
|
||||
IsFlagged = false,
|
||||
IsPinned = false,
|
||||
IsFocused = false,
|
||||
HasAttachments = false,
|
||||
ItemType = MailItemType.Mail,
|
||||
@@ -151,6 +175,7 @@ public class MailItemViewModelUpdateTests
|
||||
Importance = source.Importance,
|
||||
IsRead = source.IsRead,
|
||||
IsFlagged = source.IsFlagged,
|
||||
IsPinned = source.IsPinned,
|
||||
IsFocused = source.IsFocused,
|
||||
HasAttachments = source.HasAttachments,
|
||||
ItemType = source.ItemType,
|
||||
|
||||
@@ -17,6 +17,7 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Misc;
|
||||
using Wino.Core.Services;
|
||||
using Wino.Core.ViewModels.Data;
|
||||
@@ -96,8 +97,14 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
[ObservableProperty]
|
||||
private bool isTaskbarBadgeEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AccountCapabilityOption SelectedCapabilityOption { get; set; }
|
||||
|
||||
public bool IsFocusedInboxSupportedForAccount => Account != null && Account.Preferences.IsFocusedInboxEnabled != null;
|
||||
public bool IsImapServer => ServerInformation != null;
|
||||
public bool HasMailAccess => Account?.IsMailAccessGranted == true;
|
||||
public bool HasCalendarAccess => Account?.IsCalendarAccessGranted == true;
|
||||
public bool IsOAuthCapabilityEditable => Account?.ProviderType is MailProviderType.Outlook or MailProviderType.Gmail;
|
||||
public string ProviderIconPath => Account?.SpecialImapProvider != SpecialImapProvider.None
|
||||
? $"ms-appx:///Assets/Providers/{Account.SpecialImapProvider}.png"
|
||||
: $"ms-appx:///Assets/Providers/{Account?.ProviderType}.png";
|
||||
@@ -130,6 +137,13 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
new ImapConnectionSecurityModel(Core.Domain.Enums.ImapConnectionSecurity.None, Translator.ImapConnectionSecurity_None)
|
||||
];
|
||||
|
||||
public List<AccountCapabilityOption> CapabilityOptions { get; } =
|
||||
[
|
||||
new(true, false, Translator.AccountCapability_MailOnly),
|
||||
new(false, true, Translator.AccountCapability_CalendarOnly),
|
||||
new(true, true, Translator.AccountCapability_MailAndCalendar)
|
||||
];
|
||||
|
||||
|
||||
public AccountDetailsPageViewModel(IMailDialogService dialogService,
|
||||
IAccountService accountService,
|
||||
@@ -262,6 +276,7 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
AccountName = Account.Name;
|
||||
SenderName = Account.SenderName;
|
||||
ServerInformation = Account.ServerInformation;
|
||||
SelectedCapabilityOption = ResolveCapabilityOption(Account.IsMailAccessGranted, Account.IsCalendarAccessGranted);
|
||||
|
||||
IsFocusedInboxEnabled = Account.Preferences.IsFocusedInboxEnabled.GetValueOrDefault();
|
||||
AreNotificationsEnabled = Account.Preferences.IsNotificationsEnabled;
|
||||
@@ -288,7 +303,11 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
SelectedOutgoingServerConnectionSecurityIndex = AvailableConnectionSecurities.FindIndex(a => a.ImapConnectionSecurity == ServerInformation.OutgoingServerSocketOption);
|
||||
}
|
||||
|
||||
SelectedTabIndex = _statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar ? 2 : 1;
|
||||
SelectedTabIndex = _statePersistanceService.ApplicationMode == WinoApplicationMode.Calendar && HasCalendarAccess
|
||||
? 2
|
||||
: HasMailAccess
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
var folderStructures = (await _folderService.GetFolderStructureForAccountAsync(Account.Id, true)).Folders;
|
||||
|
||||
@@ -382,11 +401,15 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
|
||||
partial void OnAccountChanged(MailAccount value)
|
||||
{
|
||||
SelectedCapabilityOption = ResolveCapabilityOption(value?.IsMailAccessGranted == true, value?.IsCalendarAccessGranted == true);
|
||||
OnPropertyChanged(nameof(IsFocusedInboxSupportedForAccount));
|
||||
OnPropertyChanged(nameof(ProviderIconPath));
|
||||
OnPropertyChanged(nameof(Address));
|
||||
OnPropertyChanged(nameof(IsInitialSynchronizationSummaryVisible));
|
||||
OnPropertyChanged(nameof(InitialSynchronizationSummary));
|
||||
OnPropertyChanged(nameof(HasMailAccess));
|
||||
OnPropertyChanged(nameof(HasCalendarAccess));
|
||||
OnPropertyChanged(nameof(IsOAuthCapabilityEditable));
|
||||
}
|
||||
|
||||
protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
@@ -417,6 +440,21 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
Account.Preferences.IsTaskbarBadgeEnabled = IsTaskbarBadgeEnabled;
|
||||
await _accountService.UpdateAccountAsync(Account);
|
||||
break;
|
||||
case nameof(SelectedCapabilityOption) when IsOAuthCapabilityEditable && SelectedCapabilityOption != null:
|
||||
if (Account.IsMailAccessGranted == SelectedCapabilityOption.IsMailAccessGranted &&
|
||||
Account.IsCalendarAccessGranted == SelectedCapabilityOption.IsCalendarAccessGranted)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
await UpdateOAuthCapabilityAsync(SelectedCapabilityOption);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ExecuteUIThread(() => SelectedCapabilityOption = ResolveCapabilityOption(Account.IsMailAccessGranted, Account.IsCalendarAccessGranted));
|
||||
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, InfoBarMessageType.Error);
|
||||
}
|
||||
break;
|
||||
case nameof(SelectedPrimaryCalendar) when SelectedPrimaryCalendar != null:
|
||||
foreach (var calendar in AccountCalendars)
|
||||
{
|
||||
@@ -427,6 +465,111 @@ public partial class AccountDetailsPageViewModel : MailBaseViewModel
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private AccountCapabilityOption ResolveCapabilityOption(bool isMailAccessGranted, bool isCalendarAccessGranted)
|
||||
=> CapabilityOptions.First(option =>
|
||||
option.IsMailAccessGranted == isMailAccessGranted &&
|
||||
option.IsCalendarAccessGranted == isCalendarAccessGranted);
|
||||
|
||||
private async Task UpdateOAuthCapabilityAsync(AccountCapabilityOption selectedOption)
|
||||
{
|
||||
var previousMailAccess = Account.IsMailAccessGranted;
|
||||
var previousCalendarAccess = Account.IsCalendarAccessGranted;
|
||||
var requiresReauthorization = (selectedOption.IsMailAccessGranted && !previousMailAccess) ||
|
||||
(selectedOption.IsCalendarAccessGranted && !previousCalendarAccess);
|
||||
|
||||
try
|
||||
{
|
||||
if (requiresReauthorization)
|
||||
{
|
||||
Account.IsMailAccessGranted = selectedOption.IsMailAccessGranted;
|
||||
Account.IsCalendarAccessGranted = selectedOption.IsCalendarAccessGranted;
|
||||
|
||||
await SynchronizationManager.Instance.HandleAuthorizationAsync(
|
||||
Account.ProviderType,
|
||||
Account,
|
||||
Account.ProviderType == MailProviderType.Gmail);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Account.IsMailAccessGranted = previousMailAccess;
|
||||
Account.IsCalendarAccessGranted = previousCalendarAccess;
|
||||
throw;
|
||||
}
|
||||
|
||||
Account.IsMailAccessGranted = selectedOption.IsMailAccessGranted;
|
||||
Account.IsCalendarAccessGranted = selectedOption.IsCalendarAccessGranted;
|
||||
|
||||
await _accountService.UpdateAccountAsync(Account);
|
||||
|
||||
if (selectedOption.IsMailAccessGranted && !previousMailAccess)
|
||||
{
|
||||
await SynchronizationManager.Instance.SynchronizeProfileAsync(Account.Id);
|
||||
await SynchronizationManager.Instance.SynchronizeMailAsync(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = MailSynchronizationType.FullFolders
|
||||
});
|
||||
|
||||
if (Account.ProviderType == MailProviderType.Outlook)
|
||||
{
|
||||
await SynchronizationManager.Instance.SynchronizeMailAsync(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = MailSynchronizationType.Categories
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Account.Address))
|
||||
{
|
||||
var aliases = await _accountService.GetAccountAliasesAsync(Account.Id);
|
||||
var hasRootAlias = aliases.Any(alias => alias.IsRootAlias);
|
||||
|
||||
if (!hasRootAlias)
|
||||
{
|
||||
await _accountService.CreateRootAliasAsync(Account.Id, Account.Address);
|
||||
}
|
||||
}
|
||||
|
||||
await SynchronizationManager.Instance.SynchronizeMailAsync(new MailSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = MailSynchronizationType.Alias
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedOption.IsCalendarAccessGranted && !previousCalendarAccess)
|
||||
{
|
||||
await SynchronizationManager.Instance.SynchronizeCalendarAsync(new CalendarSynchronizationOptions
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = CalendarSynchronizationType.CalendarMetadata
|
||||
});
|
||||
}
|
||||
|
||||
var refreshedAccount = await _accountService.GetAccountAsync(Account.Id);
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
Account = refreshedAccount;
|
||||
AccountName = refreshedAccount.Name;
|
||||
SenderName = refreshedAccount.SenderName;
|
||||
EnsureSelectedTabForCapabilities();
|
||||
});
|
||||
}
|
||||
|
||||
private void EnsureSelectedTabForCapabilities()
|
||||
{
|
||||
if (SelectedTabIndex == 1 && !HasMailAccess)
|
||||
{
|
||||
SelectedTabIndex = HasCalendarAccess ? 2 : 0;
|
||||
}
|
||||
else if (SelectedTabIndex == 2 && !HasCalendarAccess)
|
||||
{
|
||||
SelectedTabIndex = HasMailAccess ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AccountCalendarShowAsOption
|
||||
@@ -441,6 +584,20 @@ public sealed class AccountCalendarShowAsOption
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AccountCapabilityOption
|
||||
{
|
||||
public bool IsMailAccessGranted { get; }
|
||||
public bool IsCalendarAccessGranted { get; }
|
||||
public string DisplayText { get; }
|
||||
|
||||
public AccountCapabilityOption(bool isMailAccessGranted, bool isCalendarAccessGranted, string displayText)
|
||||
{
|
||||
IsMailAccessGranted = isMailAccessGranted;
|
||||
IsCalendarAccessGranted = isCalendarAccessGranted;
|
||||
DisplayText = displayText;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class AccountCalendarSettingsItemViewModel : ObservableObject
|
||||
{
|
||||
public AccountCalendar Calendar { get; }
|
||||
|
||||
@@ -103,7 +103,10 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
return;
|
||||
}
|
||||
|
||||
Messenger.Send(new BreadcrumbNavigationRequested(Translator.WelcomeWizard_Step2Title, WinoPage.ProviderSelectionPage));
|
||||
Messenger.Send(new BreadcrumbNavigationRequested(
|
||||
Translator.WelcomeWizard_Step2Title,
|
||||
WinoPage.ProviderSelectionPage,
|
||||
ProviderSelectionNavigationContext.CreateForSettingsAddAccount()));
|
||||
}
|
||||
|
||||
public Task StartAddNewAccountAsync() => AddNewAccountAsync();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user