diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj index d2b3ab9..0aef2f9 100755 --- a/ChatTwo/ChatTwo.csproj +++ b/ChatTwo/ChatTwo.csproj @@ -35,6 +35,11 @@ + + + diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs index 84aef30..28c1af0 100755 --- a/ChatTwo/Plugin.cs +++ b/ChatTwo/Plugin.cs @@ -129,8 +129,8 @@ public sealed class Plugin : IDalamudPlugin Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification { - Title = "Hellion Chat", - Content = "Privacy filter activated by default. Settings → Privacy to adjust.", + Title = HellionStrings.Migration_Notification_Title, + Content = HellionStrings.Migration_Notification_Content, Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info, InitialDuration = TimeSpan.FromSeconds(15), }); @@ -392,6 +392,7 @@ public sealed class Plugin : IDalamudPlugin : new CultureInfo(Config.LanguageOverride.Code()); Language.Culture = info; + HellionStrings.Culture = info; } private static readonly string[] ChatAddonNames = diff --git a/ChatTwo/Resources/HellionStrings.Designer.cs b/ChatTwo/Resources/HellionStrings.Designer.cs new file mode 100644 index 0000000..a8e9864 --- /dev/null +++ b/ChatTwo/Resources/HellionStrings.Designer.cs @@ -0,0 +1,102 @@ +//------------------------------------------------------------------------------ +// +// Hand-maintained strongly-typed accessor for HellionStrings.resx. +// Mirrors the layout of Language.Designer.cs so the same Plugin.cs +// LanguageChanged handler can update Culture for both classes. +// +//------------------------------------------------------------------------------ + +#nullable enable + +namespace ChatTwo.Resources; + +[global::System.Diagnostics.DebuggerNonUserCodeAttribute] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] +internal class HellionStrings +{ + private static global::System.Resources.ResourceManager? resourceMan; + private static global::System.Globalization.CultureInfo? resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal HellionStrings() { } + + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (resourceMan is null) + resourceMan = new global::System.Resources.ResourceManager("ChatTwo.Resources.HellionStrings", typeof(HellionStrings).Assembly); + return resourceMan; + } + } + + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo? Culture + { + get => resourceCulture; + set => resourceCulture = value; + } + + private static string Get(string key) + => ResourceManager.GetString(key, resourceCulture) ?? key; + + internal static string Privacy_Tab_Title => Get(nameof(Privacy_Tab_Title)); + internal static string Privacy_FilterEnabled_Name => Get(nameof(Privacy_FilterEnabled_Name)); + internal static string Privacy_FilterEnabled_Description => Get(nameof(Privacy_FilterEnabled_Description)); + internal static string Privacy_Whitelist_Help => Get(nameof(Privacy_Whitelist_Help)); + internal static string Privacy_Preset_PrivacyFirst => Get(nameof(Privacy_Preset_PrivacyFirst)); + internal static string Privacy_Preset_ClearAll => Get(nameof(Privacy_Preset_ClearAll)); + internal static string Privacy_Preset_SelectAll => Get(nameof(Privacy_Preset_SelectAll)); + internal static string Privacy_Group_DirectMessages => Get(nameof(Privacy_Group_DirectMessages)); + internal static string Privacy_Group_PartyAlliance => Get(nameof(Privacy_Group_PartyAlliance)); + internal static string Privacy_Group_FreeCompany => Get(nameof(Privacy_Group_FreeCompany)); + internal static string Privacy_Group_Linkshells => Get(nameof(Privacy_Group_Linkshells)); + internal static string Privacy_Group_CrossLinkshells => Get(nameof(Privacy_Group_CrossLinkshells)); + internal static string Privacy_Group_ExtraChat => Get(nameof(Privacy_Group_ExtraChat)); + internal static string Privacy_Group_PublicChat => Get(nameof(Privacy_Group_PublicChat)); + internal static string Privacy_Group_SystemLogs => Get(nameof(Privacy_Group_SystemLogs)); + internal static string Privacy_PersistUnknown_Name => Get(nameof(Privacy_PersistUnknown_Name)); + internal static string Privacy_PersistUnknown_Description => Get(nameof(Privacy_PersistUnknown_Description)); + + internal static string Cleanup_Heading => Get(nameof(Cleanup_Heading)); + internal static string Cleanup_Help_Intro => Get(nameof(Cleanup_Help_Intro)); + internal static string Cleanup_Help_SavedNote => Get(nameof(Cleanup_Help_SavedNote)); + internal static string Cleanup_RefreshPreview => Get(nameof(Cleanup_RefreshPreview)); + internal static string Cleanup_NoPreview => Get(nameof(Cleanup_NoPreview)); + internal static string Cleanup_TotalStored => Get(nameof(Cleanup_TotalStored)); + internal static string Cleanup_WillKeep => Get(nameof(Cleanup_WillKeep)); + internal static string Cleanup_WillDelete => Get(nameof(Cleanup_WillDelete)); + internal static string Cleanup_Breakdown => Get(nameof(Cleanup_Breakdown)); + internal static string Cleanup_Marker_Keep => Get(nameof(Cleanup_Marker_Keep)); + internal static string Cleanup_Marker_Delete => Get(nameof(Cleanup_Marker_Delete)); + internal static string Cleanup_Apply_Label => Get(nameof(Cleanup_Apply_Label)); + internal static string Cleanup_Apply_Tooltip => Get(nameof(Cleanup_Apply_Tooltip)); + internal static string Cleanup_Running => Get(nameof(Cleanup_Running)); + internal static string Cleanup_PreviewError => Get(nameof(Cleanup_PreviewError)); + internal static string Cleanup_Success => Get(nameof(Cleanup_Success)); + internal static string Cleanup_Error => Get(nameof(Cleanup_Error)); + + internal static string Retention_Heading => Get(nameof(Retention_Heading)); + internal static string Retention_Enabled_Name => Get(nameof(Retention_Enabled_Name)); + internal static string Retention_Enabled_Description => Get(nameof(Retention_Enabled_Description)); + internal static string Retention_Default_Label => Get(nameof(Retention_Default_Label)); + internal static string Retention_Default_Help => Get(nameof(Retention_Default_Help)); + internal static string Retention_Reset_Spec => Get(nameof(Retention_Reset_Spec)); + internal static string Retention_Clear_Overrides => Get(nameof(Retention_Clear_Overrides)); + internal static string Retention_Tree_Heading => Get(nameof(Retention_Tree_Heading)); + internal static string Retention_Tag_Override => Get(nameof(Retention_Tag_Override)); + internal static string Retention_Tag_Spec => Get(nameof(Retention_Tag_Spec)); + internal static string Retention_Tag_Global => Get(nameof(Retention_Tag_Global)); + internal static string Retention_Reset_Button => Get(nameof(Retention_Reset_Button)); + internal static string Retention_Apply_Label => Get(nameof(Retention_Apply_Label)); + internal static string Retention_Apply_Tooltip => Get(nameof(Retention_Apply_Tooltip)); + internal static string Retention_Running => Get(nameof(Retention_Running)); + internal static string Retention_LastRun_Never => Get(nameof(Retention_LastRun_Never)); + internal static string Retention_LastRun_At => Get(nameof(Retention_LastRun_At)); + internal static string Retention_Success => Get(nameof(Retention_Success)); + internal static string Retention_Error => Get(nameof(Retention_Error)); + + internal static string Migration_Notification_Title => Get(nameof(Migration_Notification_Title)); + internal static string Migration_Notification_Content => Get(nameof(Migration_Notification_Content)); +} diff --git a/ChatTwo/Resources/HellionStrings.de.resx b/ChatTwo/Resources/HellionStrings.de.resx new file mode 100644 index 0000000..067db01 --- /dev/null +++ b/ChatTwo/Resources/HellionStrings.de.resx @@ -0,0 +1,180 @@ + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Datenschutz + + + Datenschutz-Filter aktivieren + + + Wenn aktiviert, werden nur Nachrichten aus den erlaubten Kanälen in die Datenbank gespeichert. Beim Deaktivieren gilt wieder das Standard-Verhalten von ChatTwo, also alles außer Battle-Logs wird gespeichert. + + + Wähle aus, welche Kanäle in die lokale Datenbank gespeichert werden. Standard nach Datensparsamkeit: nur deine eigenen Konversationen. Über die Buttons unten kannst du eine Voreinstellung anwenden. + + + Datensparsamkeit (empfohlen) + + + Alle abwählen + + + Alle auswählen + + + Direktnachrichten + + + Gruppe & Allianz + + + Free Company + + + Linkshells + + + Cross-World-Linkshells + + + ExtraChat (verschlüsselt) + + + Öffentlicher Chat (Daten Dritter) + + + System & Spiel-Logs + + + Unbekannte Kanal-Typen speichern + + + Sicherheitsnetz für ChatTypes, die durch zukünftige FFXIV-Patches dazukommen und dem Plugin noch nicht bekannt sind. Standard ist AUS (Datensparsamkeit). Aktivieren, wenn du auch zukünftige Kanäle vollständig mitloggen willst. + + + Filter auf bestehende Datenbank anwenden + + + Der Datenschutz-Filter wirkt nur auf neue Nachrichten. Über das Aufräumen unten kannst du bereits gespeicherte Nachrichten nachträglich entfernen, die nicht zu deiner gespeicherten Whitelist passen. + + + Das Aufräumen nutzt deine GESPEICHERTE Whitelist (Plugin.Config), nicht ungespeicherte Änderungen oben. Klicke zuerst Speichern, wenn du deine aktuellen Änderungen anwenden willst. + + + Vorschau aktualisieren + + + Noch keine Vorschau. Klicke Aktualisieren, um die Auswirkung zu berechnen. + + + Gespeicherte Nachrichten gesamt: {0:N0} + + + Behalten: {0:N0} + + + Löschen: {0:N0} + + + Aufschlüsselung pro Kanal + + + [BEHALTEN] + + + [LÖSCHEN] + + + Aktuellen Filter auf Datenbank anwenden + + + Strg+Umschalt: Löscht {0:N0} Nachrichten unwiderruflich und führt danach VACUUM aus. Nicht rückgängig zu machen. + + + Aufräumen läuft im Hintergrund… + + + Vorschau konnte nicht berechnet werden, siehe /xllog + + + Aufräumen abgeschlossen, {0:N0} Nachrichten entfernt. + + + Aufräumen fehlgeschlagen, siehe /xllog + + + Aufbewahrung von Nachrichten + + + Nachrichten nach Kanal-Aufbewahrung automatisch löschen + + + Wenn aktiviert, werden Nachrichten älter als das eingestellte Fenster bei jedem Plugin-Start gelöscht (höchstens einmal pro 24 Stunden). Standard ist AUS, das Plugin löscht ohne deine ausdrückliche Zustimmung nichts. + + + Standard-Aufbewahrung (Tage, 0 = nie) + + + Gilt für Kanäle, die unten keine eigene Vorgabe haben. + + + Vorgaben auf Spec-Defaults setzen + + + Alle Vorgaben entfernen + + + Aufbewahrung pro Kanal + + + [eigen] + + + [spec] + + + [global] + + + zurück + + + Aufbewahrung jetzt anwenden + + + Strg+Umschalt: Führt die Aufbewahrungs-Bereinigung sofort mit der GESPEICHERTEN Vorgabe aus. Speichere deine Änderungen vorher. + + + Aufbewahrungs-Bereinigung läuft im Hintergrund… + + + Letzter Lauf: nie + + + Letzter Lauf: {0:yyyy-MM-dd HH:mm} + + + Aufbewahrungs-Bereinigung abgeschlossen, {0:N0} Nachrichten entfernt. + + + Aufbewahrungs-Bereinigung fehlgeschlagen, siehe /xllog + + + Hellion Chat + + + Datenschutz-Filter ist standardmäßig aktiviert. Einstellungen → Datenschutz zum Anpassen. + + diff --git a/ChatTwo/Resources/HellionStrings.resx b/ChatTwo/Resources/HellionStrings.resx new file mode 100644 index 0000000..c8a791d --- /dev/null +++ b/ChatTwo/Resources/HellionStrings.resx @@ -0,0 +1,180 @@ + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Privacy + + + Enable privacy filter + + + When enabled, only messages from whitelisted channels are persisted to the database. Disabling restores upstream ChatTwo behavior (everything except battle messages is stored). + + + Pick which channels are stored in the local database. Privacy-First default: only your own conversations. Use the buttons below to apply a preset. + + + Privacy-First (recommended) + + + Clear all + + + Select all + + + Direct Messages + + + Party & Alliance + + + Free Company + + + Linkshells + + + Cross-World Linkshells + + + ExtraChat (Encrypted) + + + Public Chat (third-party data) + + + System & Game Logs + + + Persist unknown channel types + + + Failsafe for ChatTypes added by future FFXIV patches that this plugin does not yet know about. Default OFF (Privacy-First). Turn ON if you want a complete log including future channels. + + + Apply filter to existing database + + + The privacy filter only applies to new messages. Use the cleanup below to retroactively remove already-stored messages that don't match your saved whitelist. + + + Cleanup uses your SAVED whitelist (Plugin.Config), not unsaved edits above. Click Save first if you want to apply your current edits. + + + Refresh preview + + + No preview yet. Click Refresh to compute the impact. + + + Total stored messages: {0:N0} + + + Will keep: {0:N0} + + + Will delete: {0:N0} + + + Per-channel breakdown + + + [KEEP] + + + [DELETE] + + + Apply current filter to database + + + Ctrl+Shift: Hard-deletes {0:N0} messages, then runs VACUUM. Cannot be undone. + + + Cleanup running in background… + + + Failed to compute cleanup preview, see /xllog + + + Privacy cleanup complete: {0:N0} messages removed. + + + Privacy cleanup failed, see /xllog + + + Message retention + + + Auto-delete messages after a per-channel retention window + + + When enabled, messages older than the configured window are deleted on every plugin start (at most once per 24 hours). Off by default — the plugin never deletes history without your explicit consent. + + + Default retention (days, 0 = never) + + + Applies to channels without an explicit override below. + + + Reset overrides to spec defaults + + + Clear all overrides + + + Per-channel retention overrides + + + [override] + + + [spec] + + + [global] + + + reset + + + Apply retention policy now + + + Ctrl+Shift: runs the retention sweep immediately using the SAVED policy. Save your changes first. + + + Retention sweep running in background… + + + Last run: never + + + Last run: {0:yyyy-MM-dd HH:mm} + + + Retention sweep complete: {0:N0} messages removed. + + + Retention sweep failed, see /xllog + + + Hellion Chat + + + Privacy filter activated by default. Settings → Privacy to adjust. + + diff --git a/ChatTwo/Ui/SettingsTabs/Privacy.cs b/ChatTwo/Ui/SettingsTabs/Privacy.cs index 2ae1fd4..c2eefca 100644 --- a/ChatTwo/Ui/SettingsTabs/Privacy.cs +++ b/ChatTwo/Ui/SettingsTabs/Privacy.cs @@ -1,5 +1,6 @@ using ChatTwo.Code; using ChatTwo.Privacy; +using ChatTwo.Resources; using ChatTwo.Util; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility.Raii; @@ -12,7 +13,7 @@ internal sealed class Privacy : ISettingsTab private Plugin Plugin { get; } private Configuration Mutable { get; } - public string Name => "Privacy###tabs-privacy"; + public string Name => HellionStrings.Privacy_Tab_Title + "###tabs-privacy"; internal Privacy(Plugin plugin, Configuration mutable) { @@ -20,26 +21,27 @@ internal sealed class Privacy : ISettingsTab Mutable = mutable; } - // Channels grouped for the UI. Order = display order. - private static readonly (string Heading, ChatType[] Types)[] Groups = + // (HeadingKey lookup, ChatType list). Heading is resolved per-frame so + // a runtime LanguageChanged call updates the labels immediately. + private static readonly (Func Heading, ChatType[] Types)[] Groups = [ - ("Direct Messages", [ChatType.TellIncoming, ChatType.TellOutgoing]), - ("Party & Alliance", [ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam]), - ("Free Company", [ChatType.FreeCompany, ChatType.FreeCompanyAnnouncement, ChatType.FreeCompanyLoginLogout]), - ("Linkshells", [ + (() => HellionStrings.Privacy_Group_DirectMessages, [ChatType.TellIncoming, ChatType.TellOutgoing]), + (() => HellionStrings.Privacy_Group_PartyAlliance, [ChatType.Party, ChatType.CrossParty, ChatType.Alliance, ChatType.PvpTeam]), + (() => HellionStrings.Privacy_Group_FreeCompany, [ChatType.FreeCompany, ChatType.FreeCompanyAnnouncement, ChatType.FreeCompanyLoginLogout]), + (() => HellionStrings.Privacy_Group_Linkshells, [ ChatType.Linkshell1, ChatType.Linkshell2, ChatType.Linkshell3, ChatType.Linkshell4, ChatType.Linkshell5, ChatType.Linkshell6, ChatType.Linkshell7, ChatType.Linkshell8, ]), - ("Cross-World Linkshells", [ + (() => HellionStrings.Privacy_Group_CrossLinkshells, [ ChatType.CrossLinkshell1, ChatType.CrossLinkshell2, ChatType.CrossLinkshell3, ChatType.CrossLinkshell4, ChatType.CrossLinkshell5, ChatType.CrossLinkshell6, ChatType.CrossLinkshell7, ChatType.CrossLinkshell8, ]), - ("ExtraChat (Encrypted)", [ + (() => HellionStrings.Privacy_Group_ExtraChat, [ ChatType.ExtraChatLinkshell1, ChatType.ExtraChatLinkshell2, ChatType.ExtraChatLinkshell3, ChatType.ExtraChatLinkshell4, ChatType.ExtraChatLinkshell5, ChatType.ExtraChatLinkshell6, ChatType.ExtraChatLinkshell7, ChatType.ExtraChatLinkshell8, ]), - ("Public Chat (third-party data)", [ChatType.Say, ChatType.Shout, ChatType.Yell, ChatType.NoviceNetwork, ChatType.CustomEmote, ChatType.StandardEmote]), - ("System & Game Logs", [ + (() => HellionStrings.Privacy_Group_PublicChat, [ChatType.Say, ChatType.Shout, ChatType.Yell, ChatType.NoviceNetwork, ChatType.CustomEmote, ChatType.StandardEmote]), + (() => HellionStrings.Privacy_Group_SystemLogs, [ ChatType.System, ChatType.Notice, ChatType.Urgent, ChatType.Echo, ChatType.NpcDialogue, ChatType.NpcAnnouncement, ChatType.LootNotice, ChatType.LootRoll, ChatType.RetainerSale, @@ -47,9 +49,6 @@ internal sealed class Privacy : ISettingsTab ]), ]; - // Cleanup preview state. Held in the tab so the user can refresh and - // inspect before confirming. Resets when the tab is reopened (acceptable — - // a stale preview against a freshly-edited whitelist would be misleading). private Dictionary? CleanupCounts; private long CleanupKeepCount; private long CleanupDeleteCount; @@ -61,9 +60,8 @@ internal sealed class Privacy : ISettingsTab { ImGuiUtil.OptionCheckbox( ref Mutable.PrivacyFilterEnabled, - "Enable privacy filter", - "When enabled, only messages from whitelisted channels are persisted to the database. " + - "Disabling restores upstream ChatTwo behavior (everything except battle messages is stored)."); + HellionStrings.Privacy_FilterEnabled_Name, + HellionStrings.Privacy_FilterEnabled_Description); ImGui.Spacing(); ImGui.Separator(); @@ -71,22 +69,19 @@ internal sealed class Privacy : ISettingsTab using (ImRaii.Disabled(!Mutable.PrivacyFilterEnabled)) { - ImGuiUtil.HelpText( - "Pick which channels are stored in the local database. " + - "Privacy-First default: only your own conversations. " + - "Use the buttons below to apply a preset."); + ImGuiUtil.HelpText(HellionStrings.Privacy_Whitelist_Help); ImGui.Spacing(); - if (ImGui.Button("Privacy-First (recommended)")) + if (ImGui.Button(HellionStrings.Privacy_Preset_PrivacyFirst)) Mutable.PrivacyPersistChannels = [..PrivacyDefaults.PrivacyFirstWhitelist]; ImGui.SameLine(); - if (ImGui.Button("Clear all")) + if (ImGui.Button(HellionStrings.Privacy_Preset_ClearAll)) Mutable.PrivacyPersistChannels.Clear(); ImGui.SameLine(); - if (ImGui.Button("Select all")) + if (ImGui.Button(HellionStrings.Privacy_Preset_SelectAll)) foreach (var group in Groups) foreach (var t in group.Types) Mutable.PrivacyPersistChannels.Add(t); @@ -97,7 +92,7 @@ internal sealed class Privacy : ISettingsTab foreach (var (heading, types) in Groups) { - using var tree = ImRaii.TreeNode(heading); + using var tree = ImRaii.TreeNode(heading()); if (!tree.Success) continue; @@ -124,9 +119,8 @@ internal sealed class Privacy : ISettingsTab ImGuiUtil.OptionCheckbox( ref Mutable.PrivacyPersistUnknownChannels, - "Persist unknown channel types", - "Failsafe for ChatTypes added by future FFXIV patches that this plugin does not yet know about. " + - "Default OFF (Privacy-First). Turn ON if you want a complete log including future channels."); + HellionStrings.Privacy_PersistUnknown_Name, + HellionStrings.Privacy_PersistUnknown_Description); } ImGui.Spacing(); @@ -144,45 +138,44 @@ internal sealed class Privacy : ISettingsTab private void DrawRetentionSection() { - ImGui.TextUnformatted("Message retention"); + ImGui.TextUnformatted(HellionStrings.Retention_Heading); using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) { ImGuiUtil.OptionCheckbox( ref Mutable.RetentionEnabled, - "Auto-delete messages after a per-channel retention window", - "When enabled, messages older than the configured window are deleted on every plugin start (at most once per 24 hours). " + - "Off by default — the plugin never deletes history without your explicit consent."); + HellionStrings.Retention_Enabled_Name, + HellionStrings.Retention_Enabled_Description); using (ImRaii.Disabled(!Mutable.RetentionEnabled)) { ImGui.Spacing(); var defaultDays = Mutable.RetentionDefaultDays; - if (ImGui.InputInt("Default retention (days, 0 = never)", ref defaultDays)) + if (ImGui.InputInt(HellionStrings.Retention_Default_Label, ref defaultDays)) Mutable.RetentionDefaultDays = Math.Max(0, defaultDays); - ImGuiUtil.HelpText("Applies to channels without an explicit override below."); + ImGuiUtil.HelpText(HellionStrings.Retention_Default_Help); ImGui.Spacing(); - if (ImGui.Button("Reset overrides to spec defaults")) + if (ImGui.Button(HellionStrings.Retention_Reset_Spec)) { Mutable.RetentionPerChannelDays = PrivacyDefaults.DefaultRetentionDays.ToDictionary(p => p.Key, p => p.Value); } ImGui.SameLine(); - if (ImGui.Button("Clear all overrides")) + if (ImGui.Button(HellionStrings.Retention_Clear_Overrides)) Mutable.RetentionPerChannelDays.Clear(); ImGui.Spacing(); - using (var tree = ImRaii.TreeNode("Per-channel retention overrides")) + using (var tree = ImRaii.TreeNode(HellionStrings.Retention_Tree_Heading)) { if (tree.Success) { using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) foreach (var (heading, types) in Groups) { - using var subTree = ImRaii.TreeNode(heading); + using var subTree = ImRaii.TreeNode(heading()); if (!subTree.Success) continue; @@ -195,10 +188,10 @@ internal sealed class Privacy : ISettingsTab days = hasSpecDefault ? specDays : Mutable.RetentionDefaultDays; var tag = hasOverride - ? "[override]" + ? HellionStrings.Retention_Tag_Override : hasSpecDefault - ? "[spec]" - : "[global]"; + ? HellionStrings.Retention_Tag_Spec + : HellionStrings.Retention_Tag_Global; if (ImGui.InputInt($"{type} {tag}##retention-{(int)type}", ref days)) { days = Math.Max(0, days); @@ -208,7 +201,7 @@ internal sealed class Privacy : ISettingsTab if (hasOverride) { ImGui.SameLine(); - if (ImGui.Button($"reset##retention-reset-{(int)type}")) + if (ImGui.Button($"{HellionStrings.Retention_Reset_Button}##retention-reset-{(int)type}")) Mutable.RetentionPerChannelDays.Remove(type); } } @@ -220,19 +213,18 @@ internal sealed class Privacy : ISettingsTab using (ImRaii.Disabled(RetentionRunning)) { - if (ImGuiUtil.CtrlShiftButton("Apply retention policy now", - "Ctrl+Shift: runs the retention sweep immediately using the SAVED policy. Save your changes first.")) + if (ImGuiUtil.CtrlShiftButton(HellionStrings.Retention_Apply_Label, HellionStrings.Retention_Apply_Tooltip)) StartRetentionRun(); } if (RetentionRunning) - ImGuiUtil.HelpText("Retention sweep running in background…"); + ImGuiUtil.HelpText(HellionStrings.Retention_Running); ImGui.Spacing(); var lastRun = Plugin.Config.RetentionLastRunAt; ImGuiUtil.HelpText(lastRun == DateTimeOffset.MinValue - ? "Last run: never" - : $"Last run: {lastRun.ToLocalTime():yyyy-MM-dd HH:mm}"); + ? HellionStrings.Retention_LastRun_Never + : string.Format(HellionStrings.Retention_LastRun_At, lastRun.ToLocalTime())); } } } @@ -265,12 +257,12 @@ internal sealed class Privacy : ISettingsTab }).Wait(); } - WrapperUtil.AddNotification($"Retention sweep complete: {deleted:N0} messages removed.", NotificationType.Success); + WrapperUtil.AddNotification(string.Format(HellionStrings.Retention_Success, deleted), NotificationType.Success); } catch (Exception e) { Plugin.Log.Error(e, "Manual retention run failed"); - WrapperUtil.AddNotification("Retention sweep failed, see /xllog", NotificationType.Error); + WrapperUtil.AddNotification(HellionStrings.Retention_Error, NotificationType.Error); } finally { @@ -281,37 +273,32 @@ internal sealed class Privacy : ISettingsTab private void DrawCleanupSection() { - ImGui.TextUnformatted("Apply filter to existing database"); + ImGui.TextUnformatted(HellionStrings.Cleanup_Heading); using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) { - ImGuiUtil.HelpText( - "The privacy filter only applies to new messages. " + - "Use the cleanup below to retroactively remove already-stored messages " + - "that don't match your saved whitelist."); - ImGuiUtil.HelpText( - "Cleanup uses your SAVED whitelist (Plugin.Config), not unsaved edits above. " + - "Click Save first if you want to apply your current edits."); + ImGuiUtil.HelpText(HellionStrings.Cleanup_Help_Intro); + ImGuiUtil.HelpText(HellionStrings.Cleanup_Help_SavedNote); ImGui.Spacing(); using (ImRaii.Disabled(CleanupRunning)) { - if (ImGui.Button("Refresh preview")) + if (ImGui.Button(HellionStrings.Cleanup_RefreshPreview)) RefreshCleanupPreview(); } if (CleanupCounts is null) { - ImGuiUtil.HelpText("No preview yet. Click Refresh to compute the impact."); + ImGuiUtil.HelpText(HellionStrings.Cleanup_NoPreview); return; } ImGui.Spacing(); - ImGuiUtil.HelpText($"Total stored messages: {CleanupKeepCount + CleanupDeleteCount:N0}"); - ImGuiUtil.HelpText($"Will keep: {CleanupKeepCount:N0}"); - ImGuiUtil.HelpText($"Will delete: {CleanupDeleteCount:N0}"); + ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_TotalStored, CleanupKeepCount + CleanupDeleteCount)); + ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount)); + ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount)); - using (var tree = ImRaii.TreeNode("Per-channel breakdown")) + using (var tree = ImRaii.TreeNode(HellionStrings.Cleanup_Breakdown)) { if (tree.Success) { @@ -322,7 +309,7 @@ internal sealed class Privacy : ISettingsTab ? ((ChatType)(ushort)chatType).ToString() : $"Unknown({chatType})"; var keeps = WouldBeKept(chatType); - var marker = keeps ? "[KEEP] " : "[DELETE]"; + var marker = keeps ? HellionStrings.Cleanup_Marker_Keep : HellionStrings.Cleanup_Marker_Delete; ImGuiUtil.HelpText($"{marker} {name} — {count:N0}"); } } @@ -332,13 +319,13 @@ internal sealed class Privacy : ISettingsTab using (ImRaii.Disabled(CleanupRunning || CleanupDeleteCount == 0)) { - if (ImGuiUtil.CtrlShiftButton("Apply current filter to database", - $"Ctrl+Shift: Hard-deletes {CleanupDeleteCount:N0} messages, then runs VACUUM. Cannot be undone.")) + if (ImGuiUtil.CtrlShiftButton(HellionStrings.Cleanup_Apply_Label, + string.Format(HellionStrings.Cleanup_Apply_Tooltip, CleanupDeleteCount))) StartCleanup(); } if (CleanupRunning) - ImGuiUtil.HelpText("Cleanup running in background…"); + ImGuiUtil.HelpText(HellionStrings.Cleanup_Running); } } @@ -369,7 +356,7 @@ internal sealed class Privacy : ISettingsTab catch (Exception e) { Plugin.Log.Error(e, "Failed to compute cleanup preview"); - WrapperUtil.AddNotification("Failed to compute cleanup preview, see /xllog", NotificationType.Error); + WrapperUtil.AddNotification(HellionStrings.Cleanup_PreviewError, NotificationType.Error); } } @@ -394,12 +381,12 @@ internal sealed class Privacy : ISettingsTab Plugin.MessageManager.FilterAllTabsAsync(); }).Wait(); - WrapperUtil.AddNotification($"Privacy cleanup complete: {deleted:N0} messages removed.", NotificationType.Success); + WrapperUtil.AddNotification(string.Format(HellionStrings.Cleanup_Success, deleted), NotificationType.Success); } catch (Exception e) { Plugin.Log.Error(e, "Privacy cleanup failed"); - WrapperUtil.AddNotification("Privacy cleanup failed, see /xllog", NotificationType.Error); + WrapperUtil.AddNotification(HellionStrings.Cleanup_Error, NotificationType.Error); } finally {