From 5b33a21d15fcc5489ea15640794cc9bca03a2400 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Fri, 1 May 2026 20:03:18 +0200 Subject: [PATCH] Localize the Hellion Chat surface area (EN + DE) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add HellionStrings.resx as the English source and HellionStrings.de.resx for German, with a hand-maintained Designer.cs that mirrors the layout of Language.Designer.cs. Resource files live next to the upstream Language.resx but are kept entirely separate so upstream cherry-picks never collide with our translations and any future Hellion-only translation tooling (Crowdin, manual contribution) can target this file without touching the Chat 2 dictionary. Plugin.LanguageChanged now updates HellionStrings.Culture alongside Language.Culture so every UI string flips to the active locale at the same moment. The Privacy tab title, master switch, channel groups (now resolved per frame so the language can change without restart), preset buttons, failsafe toggle, retention section, cleanup section, status messages and notification bodies all read from HellionStrings. The migration toast also takes its title and body from there. Translations follow the project's German style: Du-Form, full diacritics (ä, ö, ü), no em-dashes inside flowing prose, "Whitelist" and "Linkshell" kept as-is because they are the established terms. --- ChatTwo/ChatTwo.csproj | 5 + ChatTwo/Plugin.cs | 5 +- ChatTwo/Resources/HellionStrings.Designer.cs | 102 +++++++++++ ChatTwo/Resources/HellionStrings.de.resx | 180 +++++++++++++++++++ ChatTwo/Resources/HellionStrings.resx | 180 +++++++++++++++++++ ChatTwo/Ui/SettingsTabs/Privacy.cs | 127 ++++++------- 6 files changed, 527 insertions(+), 72 deletions(-) create mode 100644 ChatTwo/Resources/HellionStrings.Designer.cs create mode 100644 ChatTwo/Resources/HellionStrings.de.resx create mode 100644 ChatTwo/Resources/HellionStrings.resx 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 {