Merge branch 'feature/v2.0.0' into main
Security Scan (reusable) / Security Scan (push) Failing after 24s
Security / scan (push) Failing after 24s
Forge Announce / Post changelog to Hellion Forge (push) Successful in 11s
Build / Build (Release) (push) Successful in 33s
Release / Build and attach release ZIP (push) Failing after 30s

This commit is contained in:
2026-08-19 22:36:26 +02:00
124 changed files with 1931 additions and 1148 deletions
+11
View File
@@ -0,0 +1,11 @@
---
subtitle: "Rebuilt, Repaired, Reset"
versionsnatur: "Major-Release mit Config-Reset"
---
- **Deine Einstellungen werden zurückgesetzt.** Neun Zyklen Umbau haben gespeicherte Werte hinterlassen, die auf Oberflächen zeigen, die es nicht mehr gibt. Neu anzufangen ist der einzige Weg, dass alle dieselben Vorgaben haben. **Dein Nachrichtenverlauf bleibt unberührt**, der liegt in einer eigenen Datenbank. Die alten Einstellungen liegen als `HellionChat.json.pre-2.0.0.bak` daneben.
- **Behoben, und mehrere davon haben Daten verloren oder versteckt:** Die rückwirkende Bereinigung ließ sich nie anwenden. Das Verdichten der Datenbank schlug fehl und meldete danach, es sei nichts gelöscht worden, während alles weg war. Gelöschte Nachrichten blieben im Suchindex. Angepinnte Flüster-Tabs kamen eine ganze Sitzung lang leer hoch. Der Export schrieb ungültiges JSON. Ein Auskoppelfenster mit Titelleiste ließ sich nicht schließen. Ein 403 eines Emote-Dienstes riss alle 65 funktionierenden Emotes mit.
- **Was sich am Speichern ändert:** Das Kanalraster entscheidet jetzt allein. Bisher griff die Unbekannt-Absicherung auch bei bekannten Kanälen, ein abgewählter Kanal wurde also trotzdem geschrieben. Wen das betraf, der speichert ab jetzt weniger. Nichts in der Datenbank wird angefasst.
- **Jedes Fenster zeichnet das Plugin selbst** und alle sprechen eine Sprache: Struktur trägt die Typografie, alles Drückbare bekommt eine Fläche, jede Farbe wird gegen ihren Untergrund gemessen. Dazu benannte Typo-Rollen, Zeitstempel in eigener Spalte, kursive Systemmeldungen.
- **Wieder erreichbar:** Export, Tab-Editor, Datenbankpflege und Anpinnen hatten beim Umbau ihre Zugänge verloren. Der Screenshot-Modus erreichte eine von vier Flächen mit Tab-Namen, jetzt alle vier. Der DSGVO-Hinweis beim Profil "Volle Historie" war in 25 Sprachen übersetzt und seit Mai unsichtbar.
- **Gestrichen:** sechs Einstellungen mit Regler, gespeichertem Wert und ohne jeden Leser. Ein Regex-Filter pro Tab, den der spieleigene Wortfilter abdeckt. Ein Assistenten-Häkchen, das abgefragt, als angewendet gemeldet und nie gelesen wurde.
- **Neu:** Emote-Tab im Standard-Layout (Testerwunsch), Orts- und Serverzeit in der Statusleiste, Screenshot-Modus aus der Eingabezeile erreichbar, `/hellion wizard`, ein Stil-Labor unter `/hellion lab`, und ein Hinweis im Assistenten, dass Plugins in FFXIV eine Grauzone sind und nicht in öffentliche Kanäle gehören.
+6 -6
View File
@@ -23,7 +23,7 @@ internal sealed class AutoTellTabsService : IDisposable
private readonly ILogger<AutoTellTabsService> _logger; private readonly ILogger<AutoTellTabsService> _logger;
// Tabs-list structure lock now lives on Plugin (neutral owner) so the // Tabs-list structure lock now lives on Plugin (neutral owner) so the
// MessageManager refilter can share it. See Plugin.TabsListLock / B3. // MessageManager refilter can share it. See Plugin.TabsListLock.
private object TabsListLock => _plugin.TabsListLock; private object TabsListLock => _plugin.TabsListLock;
// Bumped whenever something wipes unpinned temp tabs wholesale (logout). // Bumped whenever something wipes unpinned temp tabs wholesale (logout).
@@ -58,8 +58,8 @@ internal sealed class AutoTellTabsService : IDisposable
// Derived from the tab list on read. Pin/Unpin/Promote/Logout simply // Derived from the tab list on read. Pin/Unpin/Promote/Logout simply
// mutate IsPinned or remove tabs — the count adapts automatically. // mutate IsPinned or remove tabs — the count adapts automatically.
// Replaces the F2.1 Interlocked counter because the new pin-state // Replaces an Interlocked counter: the pin-state transitions are cold-path
// transitions are cold-path and don't need lock-free reads. // and don't need lock-free reads.
internal int ActiveTempTabCount => internal int ActiveTempTabCount =>
Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInUnpinnedPool); Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInUnpinnedPool);
@@ -179,7 +179,7 @@ internal sealed class AutoTellTabsService : IDisposable
} }
// Three steps, because building the tab pulls history out of the store and // Three steps, because building the tab pulls history out of the store and
// that must not happen under TabsListLock (B3 rule; the query sorts the whole // that must not happen under TabsListLock (the query sorts the whole
// receiver history). Step 1 and 3 are locked, step 2 is not. // receiver history). Step 1 and 3 are locked, step 2 is not.
int generation; int generation;
lock (TabsListLock) lock (TabsListLock)
@@ -303,7 +303,7 @@ internal sealed class AutoTellTabsService : IDisposable
internal void DropOldestTempTab() internal void DropOldestTempTab()
{ {
// B3: lock the list-structure ops so the (currently caller-less) Unpin path // Lock the list-structure ops so the (currently caller-less) Unpin path
// can't race the worker; re-entrant when HandleTell already holds the lock. // can't race the worker; re-entrant when HandleTell already holds the lock.
lock (TabsListLock) lock (TabsListLock)
{ {
@@ -577,7 +577,7 @@ internal sealed class AutoTellTabsService : IDisposable
// Count and flag under one lock so the cap can't be raced. SaveConfig stays // Count and flag under one lock so the cap can't be raced. SaveConfig stays
// OUTSIDE -- holding TabsListLock across a save would put an fsync on the // OUTSIDE -- holding TabsListLock across a save would put an fsync on the
// click path, which is what B6 just removed elsewhere. // click path, which is what a later cycle just removed elsewhere.
lock (TabsListLock) lock (TabsListLock)
{ {
if (PinnedTempTabCount >= MaxPinnedTempTabs) if (PinnedTempTabCount >= MaxPinnedTempTabs)
+3 -3
View File
@@ -1,7 +1,7 @@
namespace HellionChat; namespace HellionChat;
// Reduced CJK fallback coverage for the v1.5.3 NotoSansCjk fallback merge (B1). // Reduced CJK fallback coverage for the v1.5.3 NotoSansCjk fallback merge.
// Before B1 the fallback merged over the full `Ranges` array (Default + endonyms), // Before that the fallback merged over the full `Ranges` array (Default + endonyms),
// duplicating the Latin/Default work already done by the global/Japanese fonts. // duplicating the Latin/Default work already done by the global/Japanese fonts.
// This is the trimmed remainder the fallback is actually the sole source for: // This is the trimmed remainder the fallback is actually the sole source for:
// - Hangul Syllables (AC00-D7A3): no other merged font ships Korean glyphs. // - Hangul Syllables (AC00-D7A3): no other merged font ships Korean glyphs.
@@ -9,7 +9,7 @@ namespace HellionChat;
// font is Inter-Light (no CJK), so the fallback is the SOLE Han source. The JpRange // font is Inter-Light (no CJK), so the fallback is the SOLE Han source. The JpRange
// overlap is harmless (MergeMode: the Japanese font wins for shared kanji). // overlap is harmless (MergeMode: the Japanese font wins for shared kanji).
// Deliberately excluded: ONLY the ASCII/Latin Default block (0x20-0xFF), which the // Deliberately excluded: ONLY the ASCII/Latin Default block (0x20-0xFF), which the
// global font already owns -- that doubled Latin merge is the B1 waste being removed. // global font already owns -- that doubled Latin merge is the waste being removed.
// Kept as plain start/end pairs so it is unit-testable without the unsafe ImGui // Kept as plain start/end pairs so it is unit-testable without the unsafe ImGui
// glyph-range builder (mirrors FontSizeResolver's split-for-test rationale). // glyph-range builder (mirrors FontSizeResolver's split-for-test rationale).
internal static class CjkFallbackRange internal static class CjkFallbackRange
+64 -23
View File
@@ -35,7 +35,7 @@ public class ConfigKeyBind
[Serializable] [Serializable]
public class Configuration : IPluginConfiguration public class Configuration : IPluginConfiguration
{ {
internal const int LatestVersion = 26; internal const int LatestVersion = 27;
public int Version { get; set; } = LatestVersion; public int Version { get; set; } = LatestVersion;
@@ -45,16 +45,16 @@ public class Configuration : IPluginConfiguration
// Global window opacity, applied across all themes. // Global window opacity, applied across all themes.
public float WindowOpacity = 0.85f; public float WindowOpacity = 0.85f;
// UI-12: background opacity of the main chat window while unfocused. // Background opacity of the main chat window while unfocused.
// WindowOpacity above stays the focused value. // WindowOpacity above stays the focused value.
public float WindowOpacityInactive = 0.65f; public float WindowOpacityInactive = 0.75f;
// Reserved for future UI toggles; pre-declared to avoid a migration later. // Reserved for future UI toggles; pre-declared to avoid a migration later.
public bool ReduceMotion; public bool ReduceMotion;
// v1.2.1: default flipped false → true. Compact single-line layout is // v1.2.1: default flipped false → true. Compact single-line layout is
// more readable than the card-rows layout introduced in v1.2.0. // more readable than the card-rows layout introduced in v1.2.0.
public bool UseCompactDensity = true; public bool UseCompactDensity;
// Privacy by Default master switch. Set false to restore upstream behaviour. // Privacy by Default master switch. Set false to restore upstream behaviour.
public bool PrivacyFilterEnabled = true; public bool PrivacyFilterEnabled = true;
@@ -77,7 +77,7 @@ public class Configuration : IPluginConfiguration
.PrivacyDefaults .PrivacyDefaults
.DefaultPersistUnknownChannels; .DefaultPersistUnknownChannels;
// F3.2: dedup unknown-ChatType warnings so a chatty filter doesn't spam // Dedup unknown-ChatType warnings so a chatty filter doesn't spam
// the log every frame. NonSerialized so the warning fires once per // the log every frame. NonSerialized so the warning fires once per
// runtime, not once-ever-per-install. // runtime, not once-ever-per-install.
[NonSerialized] [NonSerialized]
@@ -107,7 +107,7 @@ public class Configuration : IPluginConfiguration
var known = Enum.IsDefined(typeof(ChatType), type); var known = Enum.IsDefined(typeof(ChatType), type);
// F3.2: log first occurrence of a ChatType the running build doesn't // Log the first occurrence of a ChatType the running build doesn't
// recognise — i.e. one a future FFXIV patch may have added. // recognise — i.e. one a future FFXIV patch may have added.
if (!known && !listed && _warnedUnknownChannels.Add(type)) if (!known && !listed && _warnedUnknownChannels.Add(type))
{ {
@@ -145,23 +145,31 @@ public class Configuration : IPluginConfiguration
// who don't care, and dodges the per-frame DrawList overhead on low-end // who don't care, and dodges the per-frame DrawList overhead on low-end
// hardware. Gradient (Color3 / GradientColourSet) is parsed but rendered // hardware. Gradient (Color3 / GradientColourSet) is parsed but rendered
// as the primary Color until a later cycle ports the animation. // as the primary Color until a later cycle ports the animation.
public bool ShowHonorificGlow; public bool ShowHonorificGlow = true;
public bool EnableAutoTellTabs = true; public bool EnableAutoTellTabs = true;
public int AutoTellTabsLimit = 15; public int AutoTellTabsLimit = 15;
public bool AutoTellTabsCompactDisplay; public bool AutoTellTabsCompactDisplay = true;
public int AutoTellTabsHistoryPreload = 20; public int AutoTellTabsHistoryPreload = 100;
// Sidebar width in pixels. Default 44 mirrors the icon-only layout from // Expanded sidebar width in pixels. 44 was carried over from the v1.2.0
// v1.2.0; users can widen up to 160 to fit a section-header line like // icon-only layout and stayed the default long after the sidebar started
// "Active Tells (3)" without truncation. // drawing labels beside those icons, so every tab name came out clipped --
public int SidebarWidth = 44; // it only went unnoticed because everyone had widened it by hand. 160 fits
// the German tab names, which are the longest of the 25 languages, and the
// floor below is set where they stop being readable rather than where the
// icons stop fitting.
public int SidebarWidth = 160;
public bool AutoTellTabsShowGreetedToggle; public bool AutoTellTabsShowGreetedToggle;
public bool SeenPopOutInputHint; public bool SeenPopOutInputHint;
public bool PopOutInputEnabled = true; public bool PopOutInputEnabled = true;
public bool SeenPopOutHeaderHint; public bool SeenPopOutHeaderHint;
public bool AutoTellTabsOpenAsPopout;
// UI-7: how sender names are rendered in the chat log. // On by default: the wizard's closing step tells the user to try /tell and
// watch a conversation open on its own, so the behaviour it describes has to
// be the behaviour they get.
public bool AutoTellTabsOpenAsPopout = true;
// How sender names are rendered in the chat log.
public WorldSuffixMode WorldSuffixMode = WorldSuffixMode.OtherWorldOnly; public WorldSuffixMode WorldSuffixMode = WorldSuffixMode.OtherWorldOnly;
public NameFormMode NameFormMode = NameFormMode.Full; public NameFormMode NameFormMode = NameFormMode.Full;
@@ -194,7 +202,7 @@ public class Configuration : IPluginConfiguration
// resource sets disagree on what PrettierTimestamps even means -- the wizard // resource sets disagree on what PrettierTimestamps even means -- the wizard
// called it "relative time", the settings tab "modern layout". // called it "relative time", the settings tab "modern layout".
public bool PrettierTimestamps = true; public bool PrettierTimestamps = true;
public bool MoreCompactPretty; public bool MoreCompactPretty = true;
public bool HideSameTimestamps = true; public bool HideSameTimestamps = true;
// No reader; see the reconnect backlog. // No reader; see the reconnect backlog.
@@ -211,12 +219,12 @@ public class Configuration : IPluginConfiguration
public bool OnlyPreviewIf; public bool OnlyPreviewIf;
public int PreviewMinimum = 1; public int PreviewMinimum = 1;
public PreviewPosition PreviewPosition = PreviewPosition.Inside; public PreviewPosition PreviewPosition = PreviewPosition.Inside;
public CommandHelpSide CommandHelpSide = CommandHelpSide.None; public CommandHelpSide CommandHelpSide = CommandHelpSide.Right;
public KeybindMode KeybindMode = KeybindMode.Strict; public KeybindMode KeybindMode = KeybindMode.Strict;
public LanguageOverride LanguageOverride = LanguageOverride.None; public LanguageOverride LanguageOverride = LanguageOverride.None;
public bool CanMove = true; public bool CanMove = true;
public bool CanResize = true; public bool CanResize = true;
public bool ShowTitleBar = true; public bool ShowTitleBar;
public bool ShowPopOutTitleBar = true; public bool ShowPopOutTitleBar = true;
public bool DatabaseBattleMessages; public bool DatabaseBattleMessages;
public bool FilterIncludePreviousSessions; public bool FilterIncludePreviousSessions;
@@ -232,7 +240,7 @@ public class Configuration : IPluginConfiguration
// Toast when a tell the user sent could not be delivered. // Toast when a tell the user sent could not be delivered.
public bool NotifyFailedTell = true; public bool NotifyFailedTell = true;
// UI-11: warn before sending a message that carries plugin-only glyphs. // Warn before sending a message that carries plugin-only glyphs.
public bool NotifyPluginDisclosure = true; public bool NotifyPluginDisclosure = true;
public bool KeepInputFocus = true; public bool KeepInputFocus = true;
public bool Use24HourClock = true; public bool Use24HourClock = true;
@@ -402,9 +410,9 @@ public class Tab
public bool IsTempTab; public bool IsTempTab;
// Pinned TempTabs survive plugin reload and logout — tester feedback from // Pinned TempTabs survive plugin reload and logout -- tester feedback in
// Jin (v1.4.7). Pinned tabs live in their own pool (MaxPinnedTempTabs) // v1.4.7. Pinned tabs live in their own pool (MaxPinnedTempTabs) separate
// separate from the AutoTellTabsLimit bucket. // from the AutoTellTabsLimit bucket.
public bool IsPinned; public bool IsPinned;
public bool AllSenderMessages; public bool AllSenderMessages;
public TellTarget TellTarget = TellTarget.Empty(); public TellTarget TellTarget = TellTarget.Empty();
@@ -462,7 +470,7 @@ public class Tab
[NonSerialized] [NonSerialized]
internal string? _cachedTellIcon; internal string? _cachedTellIcon;
// PM-3 hover-lerp state. Default 0f means "not hovered". Sidebar // hover-lerp state. Default 0f means "not hovered". Sidebar
// path animates per tab; card-mode-border path is tab-aggregate // path animates per tab; card-mode-border path is tab-aggregate
// (any card-row hover ramps the alpha for all cards in this tab). // (any card-row hover ramps the alpha for all cards in this tab).
// Lerp speed lives in the render loop, not here, so the same field // Lerp speed lives in the render loop, not here, so the same field
@@ -944,6 +952,39 @@ public static class LanguageOverrideExt
// Mutable.ExtraGlyphRanges so users do not need to know which range // Mutable.ExtraGlyphRanges so users do not need to know which range
// to tick manually. Returns 0 for locales fully covered by the default // to tick manually. Returns 0 for locales fully covered by the default
// ImGui glyph range (Latin-1) or by the separate Japanese font handle. // ImGui glyph range (Latin-1) or by the separate Japanese font handle.
// The same mapping keyed by culture code, for when the language override is
// None and the UI follows Dalamud. Without this the ranges only ever get
// filled by an explicit language pick -- installs that never touched the
// setting rendered their own locale in whatever the default range covers,
// which for Korean, Chinese, Cyrillic and Greek is boxes. It went unnoticed
// while configs accumulated ranges over time; a fresh config has none.
public static ExtraGlyphRanges RequiredGlyphRangesForCulture(string? cultureCode)
{
var code = (cultureCode ?? string.Empty).ToLowerInvariant();
// Longest first: zh-hant has to win over the zh prefix.
if (code.StartsWith("zh-hant") || code.StartsWith("zh-tw") || code.StartsWith("zh-hk"))
return ExtraGlyphRanges.ChineseFull;
if (code.StartsWith("zh"))
return ExtraGlyphRanges.ChineseSimplifiedCommon;
if (code.StartsWith("ko"))
return ExtraGlyphRanges.Korean;
if (code.StartsWith("uk") || code.StartsWith("ru") || code.StartsWith("be"))
return ExtraGlyphRanges.Cyrillic;
if (code.StartsWith("el"))
return ExtraGlyphRanges.Greek;
if (
code.StartsWith("cs")
|| code.StartsWith("pl")
|| code.StartsWith("ro")
|| code.StartsWith("hu")
|| code.StartsWith("tr")
)
return ExtraGlyphRanges.LatinExtended;
return 0;
}
public static ExtraGlyphRanges RequiredGlyphRanges(this LanguageOverride mode) => public static ExtraGlyphRanges RequiredGlyphRanges(this LanguageOverride mode) =>
mode switch mode switch
{ {
+6 -6
View File
@@ -94,13 +94,13 @@ public sealed class FontManager : IDisposable
private ushort[] Ranges = []; private ushort[] Ranges = [];
private ushort[] JpRange = []; private ushort[] JpRange = [];
// B1: trimmed remainder the NotoSansCjk fallback is the sole source for // Trimmed remainder the NotoSansCjk fallback is the sole source for
// (Hangul + full Han); excludes the Default/Latin block already merged // (Hangul + full Han); excludes the Default/Latin block already merged
// by the global font, so the fallback no longer re-merges the full Ranges array. // by the global font, so the fallback no longer re-merges the full Ranges array.
private ushort[] CjkFallbackGlyphRange = []; private ushort[] CjkFallbackGlyphRange = [];
// Report accessor for the ctor self-test: built glyph-range array lengths so // Report accessor for the ctor self-test: built glyph-range array lengths so
// the step can show the B1 dedup effect (a small trimmed fallback vs the large // the step can show the dedup effect (a small trimmed fallback vs the large
// primary range) in its on-disk report instead of a bare Pass. // primary range) in its on-disk report instead of a bare Pass.
internal (int Ranges, int JpRange, int CjkFallback) GlyphRangeLengths => internal (int Ranges, int JpRange, int CjkFallback) GlyphRangeLengths =>
(Ranges.Length, JpRange.Length, CjkFallbackGlyphRange.Length); (Ranges.Length, JpRange.Length, CjkFallbackGlyphRange.Length);
@@ -247,7 +247,7 @@ public sealed class FontManager : IDisposable
// Instance method so Ranges / JpRange are reachable without parameter // Instance method so Ranges / JpRange are reachable without parameter
// plumbing; PascalCase field names follow the existing class style. // plumbing; PascalCase field names follow the existing class style.
// B1: shared CJK + symbols tail for both the regular and italic delegate // Shared CJK + symbols tail for both the regular and italic delegate
// fonts. Earlier-merged fonts win for shared codepoints (imgui MergeMode), // fonts. Earlier-merged fonts win for shared codepoints (imgui MergeMode),
// so this runs AFTER the primary font is set as config.MergeFont. The CJK // so this runs AFTER the primary font is set as config.MergeFont. The CJK
// fallback is the sole Hangul/Simplified-Han source when UseHellionFont=true // fallback is the sole Hangul/Simplified-Han source when UseHellionFont=true
@@ -265,7 +265,7 @@ public sealed class FontManager : IDisposable
config.GlyphRanges = JpRange; config.GlyphRanges = JpRange;
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese"); AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
// NotoSansCjk fallback, trimmed to CjkFallbackGlyphRange (B1). Merged last so earlier fonts win. // NotoSansCjk fallback, trimmed to CjkFallbackGlyphRange. Merged last so earlier fonts win.
config.SizePt = basePt; config.SizePt = basePt;
config.GlyphRanges = CjkFallbackGlyphRange; config.GlyphRanges = CjkFallbackGlyphRange;
AddFontWithFallback( AddFontWithFallback(
@@ -427,7 +427,7 @@ public sealed class FontManager : IDisposable
// Common extras (Axis ingame glyphs, endonyms, enclosed alphanumerics) // Common extras (Axis ingame glyphs, endonyms, enclosed alphanumerics)
// belong to the primary/Japanese ranges only. The trimmed CJK fallback // belong to the primary/Japanese ranges only. The trimmed CJK fallback
// (B1) skips them so it stays a pure Hangul/Simplified-Han remainder and // skips them so it stays a pure Hangul/Simplified-Han remainder and
// does not re-merge the Default-block work the global font already did. // does not re-merge the Default-block work the global font already did.
if (includeCommonExtras) if (includeCommonExtras)
{ {
@@ -498,7 +498,7 @@ public sealed class FontManager : IDisposable
); );
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges, includeCommonExtras: true); JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges, includeCommonExtras: true);
// B1: the fallback gets only the trimmed Hangul/Simplified-Han remainder. // The fallback gets only the trimmed Hangul/Simplified-Han remainder.
// No Default block, no endonyms — those are already merged by the global and // No Default block, no endonyms — those are already merged by the global and
// Japanese fonts, so re-merging them on the fallback was wasted atlas work. // Japanese fonts, so re-merging them on the fallback was wasted atlas work.
CjkFallbackGlyphRange = BuildRange(CjkFallbackRange.Pairs, includeCommonExtras: false); CjkFallbackGlyphRange = BuildRange(CjkFallbackRange.Pairs, includeCommonExtras: false);
+5 -5
View File
@@ -234,7 +234,7 @@ internal sealed unsafe class Chat : IDisposable
// Seed the just-typed character into our input field and focus it, the // Seed the just-typed character into our input field and focus it, the
// same InputBar.AppendPending + Activate prefill path inventory item-links // same InputBar.AppendPending + Activate prefill path inventory item-links
// use. Prefill-only — no tab switch (Flo decision 2026-06-15). // use. Prefill only, deliberately: no tab switch.
if (input != null) if (input != null)
{ {
Plugin.InputBar.AppendPending(input); Plugin.InputBar.AppendPending(input);
@@ -355,9 +355,9 @@ internal sealed unsafe class Chat : IDisposable
if (playerName != null) if (playerName != null)
{ {
// Right-click -> Send Tell: prefill our input the same way our own // Right-click -> Send Tell: prefill our input the same way our own
// "Send Tell" payload menu does (PayloadHandler), then focus. Prefill- // "Send Tell" payload menu does (PayloadHandler), then focus. Prefill
// only — no tab switch, no ChatActivatedArgs revival (Flo decision // only, deliberately: no tab switch, no ChatActivatedArgs revival.
// 2026-06-15). The game supplies worldName here, so no sheet lookup. // The game supplies worldName here, so no sheet lookup.
PrefillTellInput( PrefillTellInput(
playerName->ToString(), playerName->ToString(),
worldName != null ? worldName->ToString() : null worldName != null ? worldName->ToString() : null
@@ -393,7 +393,7 @@ internal sealed unsafe class Chat : IDisposable
{ {
// In-foray right-click -> Send Tell: same prefill path as the non-foray // In-foray right-click -> Send Tell: same prefill path as the non-foray
// tell. The foray-specific TellSpecial channel routing stays deferred // tell. The foray-specific TellSpecial channel routing stays deferred
// (v1.8.1, SetEurekaTellChannel) — prefill-only here (Flo decision 2026-06-15). // (v1.8.1, SetEurekaTellChannel) -- prefill only here as well.
PrefillTellInput( PrefillTellInput(
playerName->ToString(), playerName->ToString(),
worldName != null ? worldName->ToString() : null worldName != null ? worldName->ToString() : null
+8 -8
View File
@@ -507,14 +507,14 @@ internal unsafe class KeybindManager : IDisposable
// Resolve the surface this keybind acts on FIRST: a focused pop-out otherwise // Resolve the surface this keybind acts on FIRST: a focused pop-out otherwise
// the main window. Channel-set/REPLY/prefill all write here so the action // the main window. Channel-set/REPLY/prefill all write here so the action
// follows the input the user is typing in (C3 full tail rebuild, OD-1). // follows the input the user is typing in.
var (targetWindow, targetTab) = ResolveKeybindTarget(); var (targetWindow, targetTab) = ResolveKeybindTarget();
// Surface + focus the resolved target ONCE, before routing. Main: ActivateChat // Surface + focus the resolved target ONCE, before routing. Main: ActivateChat
// re-surfaces it from a hide/closed state (the chat-activation entry point // re-surfaces it from a hide/closed state (the chat-activation entry point
// retired in v1.6.0). Pop-out: arm only its focus — NOT ActivateChat, which // retired in v1.6.0). Pop-out: arm only its focus — NOT ActivateChat, which
// would yank the main window to front and un-hide it on every pop-out-targeted // would yank the main window to front and un-hide it on every pop-out-targeted
// keybind (OD-1: stay where the user types). Exactly one window arms focus per // keybind: stay where the user types. Exactly one window arms focus per
// keybind, so the next frame has no SetKeyboardFocusHere race. // keybind, so the next frame has no SetKeyboardFocusHere race.
if (targetWindow is ChannelPopoutWindow) if (targetWindow is ChannelPopoutWindow)
targetWindow.RequestInputFocus(); targetWindow.RequestInputFocus();
@@ -530,7 +530,7 @@ internal unsafe class KeybindManager : IDisposable
{ {
// Direct channel-switch binds (CMD_SAY/PARTY/numbered linkshells/…): switch // Direct channel-switch binds (CMD_SAY/PARTY/numbered linkshells/…): switch
// the game channel AND mirror it onto the resolved tab so the input pill // the game channel AND mirror it onto the resolved tab so the input pill
// shows the real send target (pill-sync, Flo decision 2026-06-15). // shows the real send target (pill-sync).
Plugin.Instance.Functions.Chat.SetChannel(channel); Plugin.Instance.Functions.Chat.SetChannel(channel);
// Only mirror onto the tab when the game actually accepted the switch — an // Only mirror onto the tab when the game actually accepted the switch — an
// empty linkshell slot leaves the game channel untouched, so the pill must // empty linkshell slot leaves the game channel untouched, so the pill must
@@ -547,7 +547,7 @@ internal unsafe class KeybindManager : IDisposable
// Rotation binds (REPLY / linkshell-cycle). Ported from v1.5.6's // Rotation binds (REPLY / linkshell-cycle). Ported from v1.5.6's
// ChatLogWindow.Activated (1d3b429:240-334) without the ChatActivatedArgs // ChatLogWindow.Activated (1d3b429:240-334) without the ChatActivatedArgs
// indirection (gone in the rewrite). Writes onto the resolved surface's // indirection (gone in the rewrite). Writes onto the resolved surface's
// tab (C2/C3 shared target), not Plugin.CurrentTab. // tab, not Plugin.CurrentTab.
if (targetTab is { } rotTab) if (targetTab is { } rotTab)
{ {
var targetChannel = (InputChannel?)rotateChannel; var targetChannel = (InputChannel?)rotateChannel;
@@ -629,7 +629,7 @@ internal unsafe class KeybindManager : IDisposable
if (info.Permanent) if (info.Permanent)
{ {
// KB-01 (1.5.6 parity, ChatLogWindow.SetChannel 1d3b429:1476-1479): // 1.5.6 parity (ChatLogWindow.SetChannel, 1d3b429:1476-1479):
// committing the game channel also pre-targets the game's native input. // committing the game channel also pre-targets the game's native input.
// Forward the tab's reply target for Tell so the partner is armed // Forward the tab's reply target for Tell so the partner is armed
// game-side (ChangeChatChannel code 17); null for a linkshell — // game-side (ChangeChatChannel code 17); null for a linkshell —
@@ -654,7 +654,7 @@ internal unsafe class KeybindManager : IDisposable
// Prefill text binds (CMD_COMMAND seeds "/"): the token always goes to the // Prefill text binds (CMD_COMMAND seeds "/"): the token always goes to the
// main InputBar (the focus contract does not expose pop-out buffers); a // main InputBar (the focus contract does not expose pop-out buffers); a
// focused pop-out already received focus above, so only token routing matters // focused pop-out already received focus above, so only token routing matters
// here (documented scope limit, OD-1). // here -- a documented scope limit.
if (info.Text is { } text) if (info.Text is { } text)
Plugin.Instance.InputBar.SetPendingMessage(text); Plugin.Instance.InputBar.SetPendingMessage(text);
} }
@@ -665,7 +665,7 @@ internal unsafe class KeybindManager : IDisposable
} }
// Resolve which chat surface a keybind action targets: the open pop-out whose // Resolve which chat surface a keybind action targets: the open pop-out whose
// input currently has focus, otherwise the main window. C2/C3 share this so a // input currently has focus, otherwise the main window. Both paths share it so a
// channel-switch/REPLY/prefill follows the surface the user is typing in. The // channel-switch/REPLY/prefill follows the surface the user is typing in. The
// returned tab is that surface's bound tab (pop-out: Bound; main: ActiveTab). // returned tab is that surface's bound tab (pop-out: Bound; main: ActiveTab).
// Null tab => skip the tab-write (early-load window where no tab exists yet). // Null tab => skip the tab-write (early-load window where no tab exists yet).
@@ -682,7 +682,7 @@ internal unsafe class KeybindManager : IDisposable
} }
// Tab-delta keybinds (ChatTabForward/Backward) stay main-window-only by design: // Tab-delta keybinds (ChatTabForward/Backward) stay main-window-only by design:
// a channel-bound pop-out has no tab list to cycle (OD-1). The focus contract is // a channel-bound pop-out has no tab list to cycle. The focus contract is
// consumed by the channel-set/REPLY/prefill tail, not here. // consumed by the channel-set/REPLY/prefill tail, not here.
private void DispatchTabDelta(int delta) private void DispatchTabDelta(int delta)
{ {
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Dalamud.NET.Sdk/15.0.0"> <Project Sdk="Dalamud.NET.Sdk/15.0.0">
<PropertyGroup> <PropertyGroup>
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base --> <!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
<Version>1.14.0</Version> <Version>2.0.0</Version>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<!-- Use lock file to pin exact versions --> <!-- Use lock file to pin exact versions -->
+34 -62
View File
@@ -35,6 +35,39 @@ tags:
- Replacement - Replacement
- Privacy - Privacy
changelog: |- changelog: |-
**v2.0.0 — Rebuilt, Repaired, Reset (2026-08-19)**
Nine development cycles in one release. Everything built as v1.6.0 through v1.15.0 ships here; those versions were never published on their own.
**This update resets your settings.** Nine cycles of rebuilding left saved values pointing at surfaces that no longer exist, and starting over is the only way to be sure every install is on the same defaults. **Your message history is untouched** — it lives in a separate database. Your old settings are kept next to the config file as `HellionChat.json.pre-2.0.0.bak`.
Fixed, and several of these lost data or hid it:
- Retroactive cleanup could never be applied at all. The preview took the database lock itself and that counted as a change, so every preview went stale the instant it finished and the apply button never appeared.
- Compacting the database ran against an open reader and failed, after which the plugin reported that nothing had been deleted — while everything had.
- Deleting messages left them in the search index, so search kept returning rows that were already gone.
- Pinned tell tabs came up empty for a whole session: the history query ran at plugin start, before any character is logged in, and never tried again.
- Export wrote invalid JSON where a setting value was involved, and a byte-order mark that strict parsers reject.
- A pop-out with its title bar switched on could not be closed, and a tell from a popped-out partner hijacked the main window's active tab.
- One third-party emote service started returning 403, and that response took all 65 working global emotes down with it on every start.
- The GDPR notice for the full-history profile had been translated into 25 languages and shown nowhere since May.
Changed, and one of these changes what gets stored:
- **The channel grid is now authoritative.** Until now the unknown-channel failsafe was applied to known channels too, so a channel you had unticked was still being written while that failsafe was on. If that was you, this release stores less than before. Nothing already in the database is touched.
- Every window is drawn by the plugin rather than by ImGui defaults, and they share one visual language: structure carried by typography, a surface on anything you can press, and colours measured against what sits behind them.
- Typography has named roles — sender, body and timestamp mean the same thing everywhere, timestamps sit in their own column, system messages are italic.
- Export, the tab editor, database maintenance and pinning had lost their entry points during the rebuild and are reachable again.
- Screenshot mode reached one of four surfaces that draw a tab name. It reaches all four now.
Removed: six settings that had a control and a saved value but no reader anywhere in the plugin; a per-tab regex filter the game's own blackword filter covers; and a wizard checkbox that was collected, reported as applied and never read.
New: an Emote tab in the default layout, local and server clocks in the status bar, a screenshot mode reachable from the input row, `/hellion wizard` to reopen the setup wizard, a style lab under `/hellion lab`, and 25 UI languages with the settings window and wizard fully covered.
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). The two codebases have diverged far enough that they no longer line up.
---
**v1.5.6 — Settings Overhaul + Filter & Notification Polish (2026-05-23)** **v1.5.6 — Settings Overhaul + Filter & Notification Polish (2026-05-23)**
- Settings window reorganised: ten tabs down to seven (General, Appearance, Chat, Window, Channels, Data & Privacy, About). Each tab now uses collapsible sections grouped by control type. Sections start collapsed every time you open a tab — less noise, easier to find what you need. - Settings window reorganised: ten tabs down to seven (General, Appearance, Chat, Window, Channels, Data & Privacy, About). Each tab now uses collapsible sections grouped by control type. Sections start collapsed every time you open a tab — less noise, easier to find what you need.
@@ -116,65 +149,4 @@ changelog: |-
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2). Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
--- Earlier history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
**v1.5.3 — Localisation Wave + Bundled-Font Overhaul (2026-05-19)**
Multi-language pass plus a long-standing first-frame HITCH lands
as a side effect of a font-stack rewrite.
User-visible:
- 24 selectable UI languages (was 2). Catalan, Czech, Danish,
Dutch, English, Finnish, French, German, Greek, Hungarian,
Italian, Japanese, Korean, Norsk bokmål, Polish, Portuguese
(BR + PT), Romanian, Russian, Spanish, Swedish, Turkish,
Ukrainian, Simplified + Traditional Chinese. Sorted by endonym,
"None" pinned first. Non-native locales are AI-assisted and
flagged for native-speaker review via the Forge Discord.
- Bundled Inter Light replaces Exo 2 (SIL OFL 1.1, 343 KB). The
Inter font ships Latin Extended-A/B, Greek polytonic and
Cyrillic Supplement coverage; NotoSansCjkRegular joins as a
third merge layer for Hangul and Simplified-Han glyphs the
FFXIV Japanese game font does not ship.
- First-frame HITCH dropped from ~74 ms (v1.5.2 baseline that
held since v1.4.x) to a median of ~20 ms (5-reload sample
17.9-23.6 ms, Linux/Wine). The bundled-font path silently
fell back to the FFXIV Axis font for the entire v1.5.x series
because of an early-return in the draw loop. The fix that
routes RegularFont through draw also lands the defer-pattern
win the v1.5.1 cycle was reaching for.
- ExtraGlyphRanges auto-activates on language change. Korean,
ChineseFull and the two new flags (LatinExtended, Greek) toggle
on without a manual visit to Fonts and Colours.
- New WarningText under the language dropdown notes FFXIV's
chat input only fully supports EN/DE/FR/JA character sets.
Other languages render in HellionChat but may garble when
typed into in-game chat.
Under the hood:
- Three-layer font stack: Inter Light primary, FFXIV
JapaneseFont merge 1 for kana/kanji style, NotoSansCjkRegular
merge 2 for everything else CJK.
- LanguageOverride enum gains ten locales plus three previously
commented out (Italian, Korean, Norwegian as `nb`). New
values append to the enum so existing config integers stay
stable across update.
- Crowdin gap closed: four post-sync ChatTwo keys backfilled
into 13 legacy locales with per-key AI markers.
- Plugin.LoadAsync runs a one-shot migration that ORs in the
matching ExtraGlyphRanges flag for users already on a
non-default language. Settings.Apply auto-activates on
change going forward.
- Em-dash sweep across the EN source and 18 translations to the
house style. Russian and Ukrainian keep the typographic norm.
Migration v17 stays. UseHellionFont users transition from Exo 2
to Inter Light transparently on first reload.
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
---
Full history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
@@ -135,7 +135,7 @@ internal sealed class PayloadHandlerInitHostedService(
{ {
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {
// §6.2 cycle-resolution: both singletons exist by the time HostedServices // Cycle resolution: both singletons exist by the time HostedServices
// run, so this is the first safe point to wire the setter. // run, so this is the first safe point to wire the setter.
messageList.AttachPayloadHandler(payloadHandler); messageList.AttachPayloadHandler(payloadHandler);
@@ -172,7 +172,7 @@ internal sealed class PayloadHandlerInitHostedService(
// InputBar -> CommandHelpWindow -> MainWindow -> InputBar (MS.DI does not catch // InputBar -> CommandHelpWindow -> MainWindow -> InputBar (MS.DI does not catch
// it through FactoryCallSite registrations and the resolve recurses silently). // it through FactoryCallSite registrations and the resolve recurses silently).
// Both singletons exist by host.StartAsync time, so this is the first safe point // Both singletons exist by host.StartAsync time, so this is the first safe point
// to wire the setter — same §6.2 pattern as MessageList.AttachPayloadHandler. // to wire the setter — same setter-injection pattern as MessageList.AttachPayloadHandler.
internal sealed class CommandHelpWindowInitHostedService( internal sealed class CommandHelpWindowInitHostedService(
CommandHelpWindow commandHelpWindow, CommandHelpWindow commandHelpWindow,
MainWindow mainWindow MainWindow mainWindow
@@ -190,7 +190,7 @@ internal sealed class CommandHelpWindowInitHostedService(
// Attaches the singleton PayloadHandler to every pre-allocated pop-out // Attaches the singleton PayloadHandler to every pre-allocated pop-out
// window's MessageList post-container-build. Pool/window cannot take the // window's MessageList post-container-build. Pool/window cannot take the
// PayloadHandler via ctor (that would close the silent FactoryCallSite cycle — // PayloadHandler via ctor (that would close the silent FactoryCallSite cycle —
// same §6.2 reason as MessageList.AttachPayloadHandler / CommandHelpWindow. // same setter-injection reason as MessageList.AttachPayloadHandler / CommandHelpWindow.
// AttachMainWindow). Both singletons exist by host.StartAsync time. // AttachMainWindow). Both singletons exist by host.StartAsync time.
internal sealed class ChannelPopoutInitHostedService( internal sealed class ChannelPopoutInitHostedService(
ChannelPopoutPool pool, ChannelPopoutPool pool,
+1 -1
View File
@@ -31,7 +31,7 @@ public sealed class ExtraChat : IDisposable
// volatile: IPC callbacks fire on a Dalamud thread while ImGui reads these. // volatile: IPC callbacks fire on a Dalamud thread while ImGui reads these.
// Reference assignment is atomic on x64, but the barrier ensures visibility // Reference assignment is atomic on x64, but the barrier ensures visibility
// across threads (especially Mono/Wine). See AUDIT-2026-05-05 [SEC-01]. // across threads (especially Mono/Wine). Raised in the 2026-05-05 audit.
private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new(); private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new();
internal IReadOnlyDictionary<string, uint> ChannelCommandColours => internal IReadOnlyDictionary<string, uint> ChannelCommandColours =>
ChannelCommandColoursInternal; ChannelCommandColoursInternal;
+3 -3
View File
@@ -20,7 +20,7 @@ internal sealed class TypingIpc : IDisposable
private ICallGateProvider<ChatInputState> StateQueryGate { get; } private ICallGateProvider<ChatInputState> StateQueryGate { get; }
private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; } private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; }
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Some third-party plugins // v1.4.9: ChatTwo IPC compatibility mirror. Some third-party plugins
// have a no-fork policy and subscribe only to ChatTwo.*-prefixed IPC // have a no-fork policy and subscribe only to ChatTwo.*-prefixed IPC
// gates. HellionChat replaces ChatTwo (conflict detection prevents // gates. HellionChat replaces ChatTwo (conflict detection prevents
// parallel loading), so mirroring the ChatTwo provider slots lets those // parallel loading), so mirroring the ChatTwo provider slots lets those
@@ -50,7 +50,7 @@ internal sealed class TypingIpc : IDisposable
"HellionChat.ChatInputStateChanged" "HellionChat.ChatInputStateChanged"
); );
// v1.4.9 R4: ChatTwo-prefixed compatibility slots (see class-level comment). // v1.4.9: ChatTwo-prefixed compatibility slots (see class-level comment).
ChatTwoStateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>( ChatTwoStateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
"ChatTwo.GetChatInputState" "ChatTwo.GetChatInputState"
); );
@@ -102,7 +102,7 @@ internal sealed class TypingIpc : IDisposable
HasState = true; HasState = true;
LastState = state; LastState = state;
StateChangedGate.SendMessage(state); StateChangedGate.SendMessage(state);
// v1.4.9 R4: mirror on ChatTwo-prefixed slot for no-fork-policy plugins. // v1.4.9: mirror on ChatTwo-prefixed slot for no-fork-policy plugins.
ChatTwoStateChangedGate.SendMessage(state); ChatTwoStateChangedGate.SendMessage(state);
} }
+3 -3
View File
@@ -22,7 +22,7 @@ internal sealed class IpcManager : IDisposable
object? object?
> InvokeGate { get; } > InvokeGate { get; }
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Third-party plugins with // v1.4.9: ChatTwo IPC compatibility mirror. Third-party plugins with
// a no-fork policy (e.g. Artisan, AllaganTools) only subscribe to the // a no-fork policy (e.g. Artisan, AllaganTools) only subscribe to the
// ChatTwo.*-prefixed context-menu integration gates. Mirroring all four // ChatTwo.*-prefixed context-menu integration gates. Mirroring all four
// provider slots under the ChatTwo namespace lets those plugins keep // provider slots under the ChatTwo namespace lets those plugins keep
@@ -65,7 +65,7 @@ internal sealed class IpcManager : IDisposable
object? object?
>("HellionChat.Invoke"); >("HellionChat.Invoke");
// v1.4.9 R4: ChatTwo-prefixed mirrors of the four context-menu slots // v1.4.9: ChatTwo-prefixed mirrors of the four context-menu slots
// above. Share the same Register/Unregister backing methods so a // above. Share the same Register/Unregister backing methods so a
// plugin that subscribes via either namespace lands in the same // plugin that subscribes via either namespace lands in the same
// Registered list. SendMessage on Invoke fans out to both gates. // Registered list. SendMessage on Invoke fans out to both gates.
@@ -103,7 +103,7 @@ internal sealed class IpcManager : IDisposable
) )
{ {
InvokeGate.SendMessage(id, sender, contentId, payload, senderString, content); InvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
// v1.4.9 R4: fan out the same event to plugins listening on ChatTwo.Invoke. // v1.4.9: fan out the same event to plugins listening on ChatTwo.Invoke.
ChatTwoInvokeGate.SendMessage(id, sender, contentId, payload, senderString, content); ChatTwoInvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
} }
+9 -10
View File
@@ -163,7 +163,7 @@ internal class MessageManager : IAsyncDisposable
internal void ClearAllTabs() internal void ClearAllTabs()
{ {
// B3: snapshot the tab LIST under the shared lock so the worker-thread // Snapshot the tab LIST under the shared lock so the worker-thread
// add/remove can't tear the enumeration; tab.Clear() then runs lock-free // add/remove can't tear the enumeration; tab.Clear() then runs lock-free
// (each tab's Messages has its own SemaphoreSlim — lock order: list outer). // (each tab's Messages has its own SemaphoreSlim — lock order: list outer).
List<Tab> tabsSnapshot; List<Tab> tabsSnapshot;
@@ -184,8 +184,8 @@ internal class MessageManager : IAsyncDisposable
using var messages = Store.GetMostRecentMessages(CurrentContentId, since); using var messages = Store.GetMostRecentMessages(CurrentContentId, since);
// TempTabs excluded (live state from AutoTellTabsService). Bucket via the // TempTabs excluded (live state from AutoTellTabsService). Bucket via the
// pure MapMessagesToTabs so the assignment stays testable outside Dalamud (B3-1). // pure MapMessagesToTabs so the assignment stays testable outside Dalamud.
// B3: snapshot under the shared lock (list copy only — short critical // Snapshot under the shared lock (list copy only — short critical
// section). The Store query above and the AddSortPrune writes below stay // section). The Store query above and the AddSortPrune writes below stay
// OUTSIDE the lock (lock order: list outer, MessageList inner). // OUTSIDE the lock (lock order: list outer, MessageList inner).
List<Tab> nonTempTabs; List<Tab> nonTempTabs;
@@ -248,9 +248,8 @@ internal class MessageManager : IAsyncDisposable
_logger.LogError(ex, "Error in FilterAllTabs"); _logger.LogError(ex, "Error in FilterAllTabs");
} }
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this // Information, not Debug, so the xllog tail surfaces this without a
// without a Debug filter. Belt-and-suspenders for future plugin-load // filter. Kept as a guard against future plugin-load regressions.
// regressions; remains in place after Sub-Task 3.4 Befund.
_logger.LogInformation($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms"); _logger.LogInformation($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
}); });
} }
@@ -436,10 +435,10 @@ internal class MessageManager : IAsyncDisposable
// TEST-MIRROR: ../_Helpers/TabSoundDecision.cs // TEST-MIRROR: ../_Helpers/TabSoundDecision.cs
// Unseen ("count only what you haven't seen") suppresses unread on an inactive // Unseen ("count only what you haven't seen") suppresses unread on an inactive
// tab when the active tab ALSO shows this message — you already saw it in the // tab when the active tab ALSO shows this message — you already saw it in the
// tab you're looking at (1.5.6 / upstream ChatTwo behavior). Pre-F2 the "active // tab you're looking at (1.5.6 / upstream ChatTwo behavior). The "active tab"
// tab" was wrongly pinned to Tabs[0], so this fired against the wrong tab; F2 // used to be pinned to Tabs[0], so this fired against the wrong one until
// recoupled CurrentTab to the REAL active tab, so currentTabMatches is now // CurrentTab was recoupled to the real active tab, and currentTabMatches is
// measured against the tab you actually see. All -> always counts; None -> // now measured against the tab you see. All -> always counts; None ->
// counts here and is gated out at the display layer. Pure + SelfTest-able. // counts here and is gated out at the display layer. Pure + SelfTest-able.
internal static bool ShouldCountUnread(Tab tab, Tab currentTab, bool currentTabMatches) => internal static bool ShouldCountUnread(Tab tab, Tab currentTab, bool currentTabMatches) =>
!( !(
+3 -4
View File
@@ -245,7 +245,7 @@ internal class MessageStore : IDisposable
private SqliteConnection Connect() private SqliteConnection Connect()
{ {
// v1.4.9 R3 profiling: trace cost of SQLite open + pragma-apply. Paired // v1.4.9 profiling: trace cost of SQLite open + pragma-apply. Paired
// with the Migrate-Stopwatch below — Connect alone is the cheap half // with the Migrate-Stopwatch below — Connect alone is the cheap half
// (Open + a handful of PRAGMAs); the expensive half typically lives in // (Open + a handful of PRAGMAs); the expensive half typically lives in
// Migrate, especially on a large DB after a schema bump. // Migrate, especially on a large DB after a schema bump.
@@ -260,7 +260,7 @@ internal class MessageStore : IDisposable
private void Migrate() private void Migrate()
{ {
// v1.4.9 R3 profiling: trace cost of the schema-migration chain. On a // v1.4.9 profiling: trace cost of the schema-migration chain. On a
// large DB after a fresh schema bump this is the dominant SQLite cost // large DB after a fresh schema bump this is the dominant SQLite cost
// at plugin-load, not Connect. // at plugin-load, not Connect.
var migrateSw = System.Diagnostics.Stopwatch.StartNew(); var migrateSw = System.Diagnostics.Stopwatch.StartNew();
@@ -939,8 +939,7 @@ internal class MessageStore : IDisposable
// storage form on both sides so the IN(...) compare matches. SQLite has a // storage form on both sides so the IN(...) compare matches. SQLite has a
// hard parameter limit of 999 in default builds, so we chunk the input -- // hard parameter limit of 999 in default builds, so we chunk the input --
// a 1000-hit FTS query never explodes the SELECT. Result ordering is not // a 1000-hit FTS query never explodes the SELECT. Result ordering is not
// guaranteed; callers re-sort (e.g. DbViewer sorts by Date descending in // guaranteed; callers re-sort (DbViewer sorts by Date descending).
// Sub-Task 4.4).
public IReadOnlyList<Message> LoadByGuids(IReadOnlyList<string> guidStrings) public IReadOnlyList<Message> LoadByGuids(IReadOnlyList<string> guidStrings)
{ {
if (guidStrings.Count == 0) if (guidStrings.Count == 0)
+1 -1
View File
@@ -2,7 +2,7 @@ using HellionChat.Resources;
namespace HellionChat; namespace HellionChat;
// UI-7: how a sender's name is rendered in the chat log. Kept in its own file // How a sender's name is rendered in the chat log. Kept in its own file
// (no Dalamud usings) so the SenderNameFormatter pure-helper test stays // (no Dalamud usings) so the SenderNameFormatter pure-helper test stays
// AppDomain-isolated (feedback_dalamud_test_isolation). // AppDomain-isolated (feedback_dalamud_test_isolation).
+4 -4
View File
@@ -263,8 +263,8 @@ internal sealed class PayloadHandler
// Eureka, Bozja and Occult need special handling as tells work different // Eureka, Bozja and Occult need special handling as tells work different
if (!Sheets.IsInForay()) if (!Sheets.IsInForay())
{ {
// §6.9: single SetPendingMessage call; v1.5.6 used incremental Chat += writes. // Single SetPendingMessage call; v1.5.6 used incremental Chat += writes.
// XC-8: shares the /tell builder with the native detours. IsPublic (not // Shares the /tell builder with the native detours. IsPublic (not
// IsNullOrEmpty) is resolved HERE — a private/null world must NOT leak @World. // IsNullOrEmpty) is resolved HERE — a private/null world must NOT leak @World.
_inputBar.SetPendingMessage( _inputBar.SetPendingMessage(
GameFunctions.Chat.BuildTellCommand( GameFunctions.Chat.BuildTellCommand(
@@ -393,7 +393,7 @@ internal sealed class PayloadHandler
var inputChannel = chunk.Message?.Code.Type.ToInputChannel(); var inputChannel = chunk.Message?.Code.Type.ToInputChannel();
if (inputChannel != null && ImGui.Selectable(Language.Context_ReplyInSelectedChatMode)) if (inputChannel != null && ImGui.Selectable(Language.Context_ReplyInSelectedChatMode))
{ {
// §6.3: route channel-switch through MainWindow's active tab // Route the channel switch through MainWindow's active tab
_mainWindow.ActiveTab?.CurrentChannel?.SetChannel(inputChannel.Value); _mainWindow.ActiveTab?.CurrentChannel?.SetChannel(inputChannel.Value);
_inputBar.Activate = true; _inputBar.Activate = true;
} }
@@ -731,7 +731,7 @@ internal sealed class PayloadHandler
using (ImRaii.Tooltip()) using (ImRaii.Tooltip())
using (ImRaii.TextWrapPos(0.0f)) using (ImRaii.TextWrapPos(0.0f))
using ( using (
// §4.2: use active theme text colour instead of the former LogWindow.DefaultText static. // Use the active theme text colour instead of the former LogWindow.DefaultText static.
ImRaii.PushColor( ImRaii.PushColor(
ImGuiCol.Text, ImGuiCol.Text,
ColourUtil.RgbaToVector4(_themes.Active.Colors.TextPrimary) ColourUtil.RgbaToVector4(_themes.Active.Colors.TextPrimary)
+157 -75
View File
@@ -134,7 +134,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
internal Integrations.HonorificService HonorificService { get; private set; } = null!; internal Integrations.HonorificService HonorificService { get; private set; } = null!;
internal Integrations.CustomAudioPlayer CustomAudioPlayer { get; private set; } = null!; internal Integrations.CustomAudioPlayer CustomAudioPlayer { get; private set; } = null!;
// Ctor-smoke anchors (B0-2). Exposed so the Payload/Chunk ctor-smoke steps // Ctor-smoke anchors. Exposed so the Payload/Chunk ctor-smoke steps
// can drive the real per-frame Lender path (Borrow()) and the eager // can drive the real per-frame Lender path (Borrow()) and the eager
// singletons through the container, never via new(). Mirror of the // singletons through the container, never via new(). Mirror of the
// FontManager property pattern — every SelfTest reaches services this way. // FontManager property pattern — every SelfTest reaches services this way.
@@ -178,7 +178,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
internal bool ChatActivationRequested; internal bool ChatActivationRequested;
// Set in the first DisposeAsync statement so async callbacks scheduled // Set in the first DisposeAsync statement so async callbacks scheduled
// via Framework.RunOnTick (v1.4.8 B3 retention sweep) can early-bail // via Framework.RunOnTick (v1.4.8 retention sweep) can early-bail
// before they touch state that has already been torn down. Volatile // before they touch state that has already been torn down. Volatile
// because the tick reads it from a different thread than the writer. // because the tick reads it from a different thread than the writer.
private volatile bool _isDisposing; private volatile bool _isDisposing;
@@ -188,9 +188,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
// just unloaded belongs to nobody. // just unloaded belongs to nobody.
internal bool IsDisposing => _isDisposing; internal bool IsDisposing => _isDisposing;
// v1.9.0 B5: last full Draw() wall-time in ms, written once per frame at // v1.9.0: last full Draw() wall-time in ms, written once per frame at
// the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push // the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push
// and the font push (§7.5 First-Frame-HITCH must include atlas/style // and the font push (the first-frame hitch measurement must include atlas/style
// prologue cost), not just WindowSystem.Draw — measuring the inner call // prologue cost), not just WindowSystem.Draw — measuring the inner call
// alone would drop the prologue and make the figure non-comparable to the // alone would drop the prologue and make the figure non-comparable to the
// v1.5.6 baseline. Only accumulated here; the disk write happens in // v1.5.6 baseline. Only accumulated here; the disk write happens in
@@ -214,7 +214,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
// must never block doing so. // must never block doing so.
internal readonly Util.DbOperationGate DbOperations = new(); internal readonly Util.DbOperationGate DbOperations = new();
// B3: neutral owner of the Config.Tabs LIST-structure lock so both the // Neutral owner of the Config.Tabs LIST-structure lock so both the
// worker-thread mutator (AutoTellTabsService) and the framework-thread // worker-thread mutator (AutoTellTabsService) and the framework-thread
// refilter (MessageManager) share ONE monitor. Lock order: this outer, // refilter (MessageManager) share ONE monitor. Lock order: this outer,
// MessageList's SemaphoreSlim inner — never the reverse. // MessageList's SemaphoreSlim inner — never the reverse.
@@ -287,83 +287,118 @@ public sealed class Plugin : IAsyncDalamudPlugin
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10." + "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
); );
} }
// v23 migration: SidebarTabView was the 1.5.6 sidebar↔top-tabs switch, // 2.0.0 does not migrate, it starts over. Five cycles rebuilt the whole
// superseded by MainWindowLayoutMode in the v1.6.0 rewrite. A user who // window layer, and a config carried through them keeps values chosen
// set it false (only effective in 1.5.6) wanted top tabs — carry that // against surfaces that no longer exist -- an opacity picked for a
// intent forward. Runs only for pre-v23 configs; fresh configs load at // window that has been redrawn twice since, tabs laid out for a sidebar
// LatestVersion and skip it. Additive v20/v22 fields keep their // that works differently now. Every user of this build is a tester who
// initializer defaults as before. // was told this happens, and it is the only way to be sure everyone
if (Config.Version < 23 && !Config.SidebarTabView) // sees the same defaults.
//
// The file is copied aside first. Rolling back to 1.5.6 is a supported
// move here and it stays cheap: the old settings are a file copy away,
// rather than an evening of clicking them back in.
if (Config.Version < 27)
{ {
Config.MainWindowLayoutMode = MainWindowLayoutMode.TopTabs; BackUpConfigBeforeReset();
}
Config = Configuration.CreateFresh();
Config.Version = 27;
// Saved immediately: a crash between here and the first user-driven
// save would otherwise run the whole reset again on the next start,
// and the second run would back up the already-reset file over the
// real backup.
SaveConfig();
// v24 migration: the privacy filter used to route a known but unticked
// channel through the unknown-type failsafe, so the channel grid was
// inert whenever that failsafe was on. Corrected in v1.12.0. A config
// that never picked a channel was storing everything through that hole,
// and the corrected rule would store nothing at all -- so the intent is
// carried forward as a filter that is honestly switched off.
if (
Config.Version < 24
&& Privacy.StorageRule.ShouldDisableFilterOnV24(
Config.PrivacyFilterEnabled,
Config.PrivacyPersistUnknownChannels,
Config.PrivacyPersistChannels.Count
)
)
{
Config.PrivacyFilterEnabled = false;
// Log, not LogProxy: this runs in Phase-0 and the proxy is only
// resolved from the container further down.
Log.Information( Log.Information(
"Privacy filter switched off during the v24 migration: it was on with no channels " "Config reset to defaults for 2.0.0. Previous settings kept as "
+ "picked, which stored everything through the unknown-channel failsafe. Pick " + "HellionChat.json.pre-2.0.0.bak next to the config file."
+ "channels in Settings to switch it back on."
); );
} }
else
// v25 carried no migration step; the bump was documentation.
//
// v26 does. NameCameFromPartner is what screenshot mode reads to decide
// whether a tab name is a person, and a config written before it existed
// has it false on every tab -- including pinned tell tabs, which survive
// reloads and are named "Player@World". Anything still carrying a tell
// binding or the temp flag got its name from a partner, so the flag is
// set from those two.
//
// Tabs promoted before this version are past saving: promotion clears
// both markers and keeps the name, so nothing in the stored data says
// where that name came from. Renaming one clears the flag anyway, which
// is the same outcome the user gets by editing it.
if (Config.Version < 26)
{ {
var carried = 0; // v23 migration: SidebarTabView was the 1.5.6 sidebar↔top-tabs switch,
foreach (var tab in Config.Tabs) // superseded by MainWindowLayoutMode in the v1.6.0 rewrite. A user who
// set it false (only effective in 1.5.6) wanted top tabs — carry that
// intent forward. Runs only for pre-v23 configs; fresh configs load at
// LatestVersion and skip it. Additive v20/v22 fields keep their
// initializer defaults as before.
if (Config.Version < 23 && !Config.SidebarTabView)
{ {
if (tab.NameCameFromPartner || (!tab.IsTempTab && tab.TellTarget?.IsSet() != true)) Config.MainWindowLayoutMode = MainWindowLayoutMode.TopTabs;
continue;
tab.NameCameFromPartner = true;
carried++;
} }
if (carried > 0) // v24 migration: the privacy filter used to route a known but unticked
// channel through the unknown-type failsafe, so the channel grid was
// inert whenever that failsafe was on. Corrected in v1.12.0. A config
// that never picked a channel was storing everything through that hole,
// and the corrected rule would store nothing at all -- so the intent is
// carried forward as a filter that is honestly switched off.
if (
Config.Version < 24
&& Privacy.StorageRule.ShouldDisableFilterOnV24(
Config.PrivacyFilterEnabled,
Config.PrivacyPersistUnknownChannels,
Config.PrivacyPersistChannels.Count
)
)
{ {
Config.PrivacyFilterEnabled = false;
// Log, not LogProxy: this runs in Phase-0 and the proxy is only
// resolved from the container further down.
Log.Information( Log.Information(
$"Marked {carried} tab(s) as partner-named during the v26 migration, so " "Privacy filter switched off during the v24 migration: it was on with no channels "
+ "screenshot mode hides them in the channel header." + "picked, which stored everything through the unknown-channel failsafe. Pick "
+ "channels in Settings to switch it back on."
); );
} }
// v25 carried no migration step; the bump was documentation.
//
// v26 does. NameCameFromPartner is what screenshot mode reads to decide
// whether a tab name is a person, and a config written before it existed
// has it false on every tab -- including pinned tell tabs, which survive
// reloads and are named "Player@World". Anything still carrying a tell
// binding or the temp flag got its name from a partner, so the flag is
// set from those two.
//
// Tabs promoted before this version are past saving: promotion clears
// both markers and keeps the name, so nothing in the stored data says
// where that name came from. Renaming one clears the flag anyway, which
// is the same outcome the user gets by editing it.
if (Config.Version < 26)
{
var carried = 0;
foreach (var tab in Config.Tabs)
{
if (
tab.NameCameFromPartner
|| (!tab.IsTempTab && tab.TellTarget?.IsSet() != true)
)
continue;
tab.NameCameFromPartner = true;
carried++;
}
if (carried > 0)
{
Log.Information(
$"Marked {carried} tab(s) as partner-named during the v26 migration, so "
+ "screenshot mode hides them in the channel header."
);
}
}
} }
Config.Version = 26; Config.Version = 27;
// Unpinned TempTabs are session-only and dropped on every load. Pinned // Unpinned TempTabs are session-only and dropped on every load. Pinned
// TempTabs survive reload — Jin's tester feedback (v1.4.7). // TempTabs survive reload -- tester feedback in v1.4.7.
Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad); Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad);
// GP-04: clear stale Tab.PopOut flags now — the pool binds further down // Clear stale Tab.PopOut flags now — the pool binds further down
// (ChannelPopoutPool resolve below), so at this point no tab can own a // (ChannelPopoutPool resolve below), so at this point no tab can own a
// slot. A persisted PopOut=true (notably on surviving pinned TempTabs) // slot. A persisted PopOut=true (notably on surviving pinned TempTabs)
// would otherwise be a flag with no window. Runs after the strip, before // would otherwise be a flag with no window. Runs after the strip, before
@@ -378,7 +413,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
// that path. ORing in the required flag here lets the first atlas // that path. ORing in the required flag here lets the first atlas
// build pick it up, so an upgrade from v1.5.2 renders correctly // build pick it up, so an upgrade from v1.5.2 renders correctly
// without forcing the user to toggle the language twice. // without forcing the user to toggle the language twice.
var requiredRanges = Config.LanguageOverride.RequiredGlyphRanges(); var requiredRanges =
Config.LanguageOverride is LanguageOverride.None
? LanguageOverrideExt.RequiredGlyphRangesForCulture(Interface.UiLanguage)
: Config.LanguageOverride.RequiredGlyphRanges();
if (requiredRanges != 0 && !Config.ExtraGlyphRanges.HasFlag(requiredRanges)) if (requiredRanges != 0 && !Config.ExtraGlyphRanges.HasFlag(requiredRanges))
Config.ExtraGlyphRanges |= requiredRanges; Config.ExtraGlyphRanges |= requiredRanges;
@@ -462,7 +500,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
FirstRunWizard = _host.Services.GetRequiredService<FirstRunWizard>(); FirstRunWizard = _host.Services.GetRequiredService<FirstRunWizard>();
ChannelPopoutPool = _host.Services.GetRequiredService<Ui.Windows.ChannelPopoutPool>(); ChannelPopoutPool = _host.Services.GetRequiredService<Ui.Windows.ChannelPopoutPool>();
// Ctor-smoke anchors (B0-2). Resolved last, against the fully built // Ctor-smoke anchors. Resolved last, against the fully built
// container: every MakePayloadHandler dep (MainWindow, InputBar, // container: every MakePayloadHandler dep (MainWindow, InputBar,
// ChunkRenderer, ...) is resolvable here, and the ChunkRenderer resolve // ChunkRenderer, ...) is resolvable here, and the ChunkRenderer resolve
// below just reuses the same cached singleton. These are plain // below just reuses the same cached singleton. These are plain
@@ -485,6 +523,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
{ {
Config.Tabs.Add(TabsUtil.VanillaGeneral); Config.Tabs.Add(TabsUtil.VanillaGeneral);
Config.Tabs.Add(TabsUtil.HellionSystem); Config.Tabs.Add(TabsUtil.HellionSystem);
Config.Tabs.Add(TabsUtil.HellionEmote);
Config.Tabs.Add(TabsUtil.HellionFreeCompany); Config.Tabs.Add(TabsUtil.HellionFreeCompany);
Config.Tabs.Add(TabsUtil.HellionParty); Config.Tabs.Add(TabsUtil.HellionParty);
Config.Tabs.Add(TabsUtil.HellionLinkshell); Config.Tabs.Add(TabsUtil.HellionLinkshell);
@@ -521,7 +560,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
new SelfTests.SettingsWindowOpenStep(this), new SelfTests.SettingsWindowOpenStep(this),
new SelfTests.OnOpenMainUiRoutesMainWindowStep(this), new SelfTests.OnOpenMainUiRoutesMainWindowStep(this),
new SelfTests.TypingIpcStateStep(this), new SelfTests.TypingIpcStateStep(this),
new SelfTests.ConfigMigrationV26Step(this), new SelfTests.ConfigMigrationV27Step(this),
new SelfTests.DbGateWiringStep(this), new SelfTests.DbGateWiringStep(this),
new SelfTests.ChannelPopoutBindStep(this), new SelfTests.ChannelPopoutBindStep(this),
new SelfTests.HoverStateFootprintStep(), new SelfTests.HoverStateFootprintStep(),
@@ -599,7 +638,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
_ = Task.Run( _ = Task.Run(
async () => async () =>
{ {
// FQN: Plugin.Notification (Z.74) shadows the type name. // FQN: the Plugin.Notification property shadows the type name.
Dalamud.Interface.ImGuiNotification.IActiveNotification? notif = null; Dalamud.Interface.ImGuiNotification.IActiveNotification? notif = null;
try try
{ {
@@ -732,7 +771,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
return; return;
// Set before any cleanup so deferred Framework.RunOnTick callbacks // Set before any cleanup so deferred Framework.RunOnTick callbacks
// (B3 retention sweep) see the flag and bail out before they touch // (the retention sweep) see the flag and bail out before they touch
// MessageManager / Log / static fields that the rest of this method // MessageManager / Log / static fields that the rest of this method
// is about to tear down. // is about to tear down.
_isDisposing = true; _isDisposing = true;
@@ -840,6 +879,38 @@ public sealed class Plugin : IAsyncDalamudPlugin
return failure; return failure;
} }
// Copies the config aside before the 2.0.0 reset overwrites it. Best effort
// by design: a backup that cannot be written must not stop the plugin from
// starting, and the reset itself is what the user was told would happen.
//
// Overwrite=false, so a second run cannot bury the real backup under a copy
// of the already-reset file. The reset saves immediately for the same
// reason, but a crash in between is exactly when this matters.
private static void BackUpConfigBeforeReset()
{
try
{
var dir = Interface.ConfigDirectory.Parent?.FullName;
if (dir is null)
return;
var configFile = Path.Combine(dir, "HellionChat.json");
if (!File.Exists(configFile))
return;
var backup = Path.Combine(dir, "HellionChat.json.pre-2.0.0.bak");
if (File.Exists(backup))
return;
File.Copy(configFile, backup);
Log.Information($"HellionChat: config backed up to {backup} before the 2.0.0 reset");
}
catch (Exception e)
{
Log.Warning(e, "HellionChat: could not back up the config before the 2.0.0 reset");
}
}
private static void MigrateFromChatTwoLayout() private static void MigrateFromChatTwoLayout()
{ {
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName; var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
@@ -956,7 +1027,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
{ {
_hellionSettingsCmd = Commands.Register( _hellionSettingsCmd = Commands.Register(
"/hellion", "/hellion",
"Toggle Hellion Chat. /hellion settings opens settings, /hellion reset restores the default theme." "Toggle Hellion Chat. /hellion settings opens settings, /hellion wizard reopens the setup wizard, /hellion reset restores the default theme."
); );
_hellionSettingsCmd.Execute += OnHellionSettingsCommand; _hellionSettingsCmd.Execute += OnHellionSettingsCommand;
@@ -1051,11 +1122,22 @@ public sealed class Plugin : IAsyncDalamudPlugin
InputBarLab.Toggle(); InputBarLab.Toggle();
return; return;
} }
// The wizard had no way back into it: the reopen button the resource
// file still carries in 25 languages lost its call site in the
// four-step rewrite, and nothing replaced it. Without this the only
// route is closing the game and editing FirstRunCompleted by hand.
// Toggle, not IsOpen = true, so the same command closes it again.
if (arg.Equals("wizard", StringComparison.OrdinalIgnoreCase))
{
FirstRunWizard.Toggle();
return;
}
#if DEBUG #if DEBUG
#endif #endif
if (arg.Equals("reset", StringComparison.OrdinalIgnoreCase)) if (arg.Equals("reset", StringComparison.OrdinalIgnoreCase))
{ {
// Recovery path documented in the v2.x master spec — drops a // Documented recovery path -- drops a
// broken custom theme out of the loader cache without touching // broken custom theme out of the loader cache without touching
// the user's JSON on disk. // the user's JSON on disk.
ThemeRegistry.SwitchSilent(Themes.ThemeRegistry.DefaultSlug); ThemeRegistry.SwitchSilent(Themes.ThemeRegistry.DefaultSlug);
@@ -1170,7 +1252,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Schedule on the next framework tick to avoid the ~194ms // Schedule on the next framework tick to avoid the ~194ms
// hitch from blocking with .Wait() while the frame finishes. // hitch from blocking with .Wait() while the frame finishes.
// The Config.Tabs enumeration in ClearAllTabs/FilterAllTabs is // The Config.Tabs enumeration in ClearAllTabs/FilterAllTabs is
// now guarded by the shared Plugin.TabsListLock (B3), so this // now guarded by the shared Plugin.TabsListLock, so this
// tick scheduling is purely hitch-avoidance, not safety. // tick scheduling is purely hitch-avoidance, not safety.
// Pattern reference: SimpleTweaks // Pattern reference: SimpleTweaks
// Tweaks/Chat/CaseInsensitiveCommands.cs:45. // Tweaks/Chat/CaseInsensitiveCommands.cs:45.
@@ -1257,7 +1339,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
private void Draw() private void Draw()
{ {
// v1.9.0 B5: time the whole handler (style + font prologue included). // v1.9.0: time the whole handler (style + font prologue included).
// Bail before measuring once teardown has begun — a late Draw tick // Bail before measuring once teardown has begun — a late Draw tick
// must not touch ThemeRegistry / FontManager after DisposeAsync. // must not touch ThemeRegistry / FontManager after DisposeAsync.
if (_isDisposing) if (_isDisposing)
@@ -1266,7 +1348,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
var drawWatch = Stopwatch.StartNew(); var drawWatch = Stopwatch.StartNew();
try try
{ {
// v1.4.8 B2: pick up external edits of the active custom theme JSON // v1.4.8: pick up external edits of the active custom theme JSON
// without forcing the user to re-click the picker. The disk-stat is // without forcing the user to re-click the picker. The disk-stat is
// 1Hz-throttled inside RefreshActiveIfStale, so this is essentially // 1Hz-throttled inside RefreshActiveIfStale, so this is essentially
// free on built-in themes and ~1 stat/second on custom themes. // free on built-in themes and ~1 stat/second on custom themes.
@@ -1356,10 +1438,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Config.Tabs across the save so JSON includes them. Cloning only the // Config.Tabs across the save so JSON includes them. Cloning only the
// unpinned subset keeps the allocation proportional to // unpinned subset keeps the allocation proportional to
// AutoTellTabsLimit (<=15) instead of the full tab list. // AutoTellTabsLimit (<=15) instead of the full tab list.
// B3: the strip/restore mutates the tab LIST, so it shares TabsListLock // The strip/restore mutates the tab LIST, so it shares TabsListLock
// with the worker add/remove and the refilter snapshot. Re-entrant: the // with the worker add/remove and the refilter snapshot. Re-entrant: the
// one worker caller (HandleTell) already holds it; framework callers take // one worker caller (HandleTell) already holds it; framework callers take
// it here. SavePluginConfig runs inside (short, in-memory) — the §8 fallback // it here. SavePluginConfig runs inside (short, in-memory) — the documented fallback
// (serialize a copy outside the lock) is a tracked pre-beta to-do. // (serialize a copy outside the lock) is a tracked pre-beta to-do.
lock (TabsListLock) lock (TabsListLock)
{ {
+11 -8
View File
@@ -15,7 +15,7 @@ namespace HellionChat;
// Builds the generic-host DI container that drives v1.5.0+. The factory is // Builds the generic-host DI container that drives v1.5.0+. The factory is
// invoked synchronously from Plugin.ctor (after the schema gate clears) so the // invoked synchronously from Plugin.ctor (after the schema gate clears) so the
// container exists before PluginLifecycle.LoadAsync runs. See plan §1 for the // container exists before PluginLifecycle.LoadAsync runs. For the
// deliberate divergence from Lightless' deferred Func-delegate pattern. // deliberate divergence from Lightless' deferred Func-delegate pattern.
internal static class PluginHostFactory internal static class PluginHostFactory
{ {
@@ -48,7 +48,7 @@ internal static class PluginHostFactory
PluginHostDependencies dependencies PluginHostDependencies dependencies
) )
{ {
// Block A — Dalamud services (21 [PluginService] singletons). // Dalamud services (21 [PluginService] singletons).
services.AddSingleton(dependencies); services.AddSingleton(dependencies);
services.AddSingleton(dependencies.PluginInterface); services.AddSingleton(dependencies.PluginInterface);
services.AddSingleton(dependencies.PluginLog); services.AddSingleton(dependencies.PluginLog);
@@ -77,7 +77,7 @@ internal static class PluginHostFactory
services.AddSingleton(plugin.WindowSystem); services.AddSingleton(plugin.WindowSystem);
services.AddSingleton<PluginLifecycle>(); services.AddSingleton<PluginLifecycle>();
// Block B — HellionChat singletons. Factory lambdas because most // HellionChat singletons. Factory lambdas because most
// classes are internal-sealed and the default activator only sees // classes are internal-sealed and the default activator only sees
// public ctors. // public ctors.
services.AddSingleton<IPlatformUtil>(_ => new DalamudPlatformUtil()); services.AddSingleton<IPlatformUtil>(_ => new DalamudPlatformUtil());
@@ -307,7 +307,7 @@ internal static class PluginHostFactory
// Pop-out windows: each gets its OWN MessageList + InputBar so the // Pop-out windows: each gets its OWN MessageList + InputBar so the
// channel pill and message scroll are per-window. The PayloadHandler is // channel pill and message scroll are per-window. The PayloadHandler is
// attached post-build (ChannelPopoutInitHostedService), NEVER via ctor // attached post-build (ChannelPopoutInitHostedService), NEVER via ctor
// (plan §B.2 — would close a silent FactoryCallSite cycle). // (would close a silent FactoryCallSite cycle).
services.AddSingleton<Func<int, Ui.Windows.ChannelPopoutWindow>>(sp => services.AddSingleton<Func<int, Ui.Windows.ChannelPopoutWindow>>(sp =>
slot => new Ui.Windows.ChannelPopoutWindow( slot => new Ui.Windows.ChannelPopoutWindow(
slot, slot,
@@ -336,7 +336,7 @@ internal static class PluginHostFactory
sp.GetRequiredService<ILogger<Ui.Windows.ChannelPopoutPool>>() sp.GetRequiredService<ILogger<Ui.Windows.ChannelPopoutPool>>()
)); ));
// Block C — Windows. WindowSystem.AddWindow is called from // Windows. WindowSystem.AddWindow is called from
// PluginLifecycle.LoadAsync on the framework thread. // PluginLifecycle.LoadAsync on the framework thread.
services.AddSingleton(sp => new Ui.Windows.SettingsWindow( services.AddSingleton(sp => new Ui.Windows.SettingsWindow(
sp.GetRequiredService<Plugin>(), sp.GetRequiredService<Plugin>(),
@@ -380,8 +380,8 @@ internal static class PluginHostFactory
)); ));
#endif #endif
// The style lab: variants side by side, in-game, against the live theme. // The style lab: variants side by side, in-game, against the live theme.
// Permanent by Flo's call, and deliberately not behind DEBUG -- style // Permanent, and deliberately not behind DEBUG: style decisions get
// decisions happen in the build he actually runs. // made in the build that actually ships.
services.AddSingleton(sp => new Ui.Windows.InputBarLabWindow( services.AddSingleton(sp => new Ui.Windows.InputBarLabWindow(
sp.GetRequiredService<Plugin>() sp.GetRequiredService<Plugin>()
)); ));
@@ -389,7 +389,10 @@ internal static class PluginHostFactory
sp.GetRequiredService<Plugin>(), sp.GetRequiredService<Plugin>(),
sp.GetRequiredService<PayloadHandler>() sp.GetRequiredService<PayloadHandler>()
)); ));
services.AddSingleton(sp => new FirstRunWizard(sp.GetRequiredService<Plugin>())); services.AddSingleton(sp => new FirstRunWizard(
sp.GetRequiredService<Plugin>(),
sp.GetRequiredService<Ui.StyleEngine.SurfaceBackdrop>()
));
// Hosted-service adapters: thin wrappers around the existing init // Hosted-service adapters: thin wrappers around the existing init
// methods so the service class bodies stay unchanged. FontManager // methods so the service class bodies stay unchanged. FontManager
+1 -1
View File
@@ -4,7 +4,7 @@ namespace HellionChat.Privacy;
internal static class PrivacyDefaults internal static class PrivacyDefaults
{ {
// F3.1: failsafe for ChatTypes added by future FFXIV patches. New installs // Failsafe for ChatTypes added by future FFXIV patches. New installs
// persist unknown channels so a major patch's added ChatType isn't silently // persist unknown channels so a major patch's added ChatType isn't silently
// dropped before the user can opt in or out. Existing configs keep their // dropped before the user can opt in or out. Existing configs keep their
// explicit choice — see Configuration.cs PrivacyPersistUnknownChannels. // explicit choice — see Configuration.cs PrivacyPersistUnknownChannels.
+11 -8
View File
@@ -116,10 +116,12 @@ internal class HellionStrings
internal static string Wizard_Step1_Title => Get(nameof(Wizard_Step1_Title)); internal static string Wizard_Step1_Title => Get(nameof(Wizard_Step1_Title));
internal static string Wizard_Step1_Subtitle => Get(nameof(Wizard_Step1_Subtitle)); internal static string Wizard_Step1_Subtitle => Get(nameof(Wizard_Step1_Subtitle));
internal static string Wizard_Step1_Footer_Hint => Get(nameof(Wizard_Step1_Footer_Hint)); internal static string Wizard_Step1_Footer_Hint => Get(nameof(Wizard_Step1_Footer_Hint));
internal static string Wizard_Step1_PluginNotice => Get(nameof(Wizard_Step1_PluginNotice));
internal static string Wizard_Step1_Heritage => Get(nameof(Wizard_Step1_Heritage));
internal static string Wizard_Step1_Skip_Label => Get(nameof(Wizard_Step1_Skip_Label)); internal static string Wizard_Step1_Skip_Label => Get(nameof(Wizard_Step1_Skip_Label));
internal static string Wizard_Step1_Skip_Tooltip => Get(nameof(Wizard_Step1_Skip_Tooltip)); internal static string Wizard_Step1_Skip_Tooltip => Get(nameof(Wizard_Step1_Skip_Tooltip));
internal static string Wizard_Step2_Title => Get(nameof(Wizard_Step2_Title)); internal static string Wizard_Step2_Title => Get(nameof(Wizard_Step2_Title));
internal static string Wizard_Step2_RecommendedFooter => Get(nameof(Wizard_Step2_RecommendedFooter)); internal static string Wizard_Profile_Recommended_Badge => Get(nameof(Wizard_Profile_Recommended_Badge));
internal static string Wizard_Profile_Roleplay_Heading => Get(nameof(Wizard_Profile_Roleplay_Heading)); internal static string Wizard_Profile_Roleplay_Heading => Get(nameof(Wizard_Profile_Roleplay_Heading));
internal static string Wizard_Profile_Roleplay_Description => Get(nameof(Wizard_Profile_Roleplay_Description)); internal static string Wizard_Profile_Roleplay_Description => Get(nameof(Wizard_Profile_Roleplay_Description));
internal static string Wizard_Profile_Roleplay_Apply => Get(nameof(Wizard_Profile_Roleplay_Apply)); internal static string Wizard_Profile_Roleplay_Apply => Get(nameof(Wizard_Profile_Roleplay_Apply));
@@ -282,6 +284,7 @@ internal class HellionStrings
// Hellion Chat — Default tab presets (channel-themed) // Hellion Chat — Default tab presets (channel-themed)
internal static string Tabs_Presets_System => Get(nameof(Tabs_Presets_System)); internal static string Tabs_Presets_System => Get(nameof(Tabs_Presets_System));
internal static string Tabs_Presets_Emote => Get(nameof(Tabs_Presets_Emote));
internal static string Tabs_Presets_FreeCompany => Get(nameof(Tabs_Presets_FreeCompany)); internal static string Tabs_Presets_FreeCompany => Get(nameof(Tabs_Presets_FreeCompany));
internal static string Tabs_Presets_Party => Get(nameof(Tabs_Presets_Party)); internal static string Tabs_Presets_Party => Get(nameof(Tabs_Presets_Party));
internal static string Tabs_Presets_Beginner => Get(nameof(Tabs_Presets_Beginner)); internal static string Tabs_Presets_Beginner => Get(nameof(Tabs_Presets_Beginner));
@@ -343,7 +346,7 @@ internal class HellionStrings
// Hellion Chat — v1.2.1 Data Management tab section headings // Hellion Chat — v1.2.1 Data Management tab section headings
internal static string Settings_DataManagement_Advanced_Heading => Get(nameof(Settings_DataManagement_Advanced_Heading)); internal static string Settings_DataManagement_Advanced_Heading => Get(nameof(Settings_DataManagement_Advanced_Heading));
// v1.5.6: Data & Privacy tab section titles (R6) // v1.5.6: Data & Privacy tab section titles
internal static string Settings_Section_PrivacyFilter => Get(nameof(Settings_Section_PrivacyFilter)); internal static string Settings_Section_PrivacyFilter => Get(nameof(Settings_Section_PrivacyFilter));
internal static string Settings_Section_Storage => Get(nameof(Settings_Section_Storage)); internal static string Settings_Section_Storage => Get(nameof(Settings_Section_Storage));
internal static string Settings_Section_Retention => Get(nameof(Settings_Section_Retention)); internal static string Settings_Section_Retention => Get(nameof(Settings_Section_Retention));
@@ -535,14 +538,14 @@ internal class HellionStrings
internal static string Settings_General_CustomSoundVolume_Name => Get(nameof(Settings_General_CustomSoundVolume_Name)); internal static string Settings_General_CustomSoundVolume_Name => Get(nameof(Settings_General_CustomSoundVolume_Name));
internal static string Settings_General_CustomSoundVolume_Description => Get(nameof(Settings_General_CustomSoundVolume_Description)); internal static string Settings_General_CustomSoundVolume_Description => Get(nameof(Settings_General_CustomSoundVolume_Description));
// v1.5.6: General tab collapsible section titles (R6) // v1.5.6: General tab collapsible section titles
internal static string Settings_Section_Input => Get(nameof(Settings_Section_Input)); internal static string Settings_Section_Input => Get(nameof(Settings_Section_Input));
internal static string Settings_Section_Sound => Get(nameof(Settings_Section_Sound)); internal static string Settings_Section_Sound => Get(nameof(Settings_Section_Sound));
internal static string Settings_Section_Language => Get(nameof(Settings_Section_Language)); internal static string Settings_Section_Language => Get(nameof(Settings_Section_Language));
internal static string Settings_Section_Performance => Get(nameof(Settings_Section_Performance)); internal static string Settings_Section_Performance => Get(nameof(Settings_Section_Performance));
internal static string Settings_Section_Sound_TabsHint => Get(nameof(Settings_Section_Sound_TabsHint)); internal static string Settings_Section_Sound_TabsHint => Get(nameof(Settings_Section_Sound_TabsHint));
// v1.5.6: Chat tab collapsible section titles (R6) // v1.5.6: Chat tab collapsible section titles
internal static string Settings_Section_Messages => Get(nameof(Settings_Section_Messages)); internal static string Settings_Section_Messages => Get(nameof(Settings_Section_Messages));
internal static string Settings_Section_InputPreview => Get(nameof(Settings_Section_InputPreview)); internal static string Settings_Section_InputPreview => Get(nameof(Settings_Section_InputPreview));
internal static string Settings_Section_AutoTellTabs => Get(nameof(Settings_Section_AutoTellTabs)); internal static string Settings_Section_AutoTellTabs => Get(nameof(Settings_Section_AutoTellTabs));
@@ -550,7 +553,7 @@ internal class HellionStrings
internal static string Settings_Section_LinksTooltips => Get(nameof(Settings_Section_LinksTooltips)); internal static string Settings_Section_LinksTooltips => Get(nameof(Settings_Section_LinksTooltips));
internal static string Settings_Section_NoviceNetwork => Get(nameof(Settings_Section_NoviceNetwork)); internal static string Settings_Section_NoviceNetwork => Get(nameof(Settings_Section_NoviceNetwork));
// v1.5.6: Appearance tab collapsible section titles (R6) // v1.5.6: Appearance tab collapsible section titles
internal static string Settings_Section_Theme => Get(nameof(Settings_Section_Theme)); internal static string Settings_Section_Theme => Get(nameof(Settings_Section_Theme));
internal static string Settings_Section_Fonts => Get(nameof(Settings_Section_Fonts)); internal static string Settings_Section_Fonts => Get(nameof(Settings_Section_Fonts));
internal static string Settings_Section_Colours => Get(nameof(Settings_Section_Colours)); internal static string Settings_Section_Colours => Get(nameof(Settings_Section_Colours));
@@ -558,12 +561,12 @@ internal class HellionStrings
internal static string Settings_Section_Timestamps => Get(nameof(Settings_Section_Timestamps)); internal static string Settings_Section_Timestamps => Get(nameof(Settings_Section_Timestamps));
internal static string Settings_Section_Animations => Get(nameof(Settings_Section_Animations)); internal static string Settings_Section_Animations => Get(nameof(Settings_Section_Animations));
// v1.5.6: Window tab collapsible section titles (R6) // v1.5.6: Window tab collapsible section titles
internal static string Settings_Section_Hide => Get(nameof(Settings_Section_Hide)); internal static string Settings_Section_Hide => Get(nameof(Settings_Section_Hide));
internal static string Settings_Section_InactivityHide => Get(nameof(Settings_Section_InactivityHide)); internal static string Settings_Section_InactivityHide => Get(nameof(Settings_Section_InactivityHide));
internal static string Settings_Section_Frame => Get(nameof(Settings_Section_Frame)); internal static string Settings_Section_Frame => Get(nameof(Settings_Section_Frame));
// v1.5.6: Tabs tab per-tab-item sub-section titles (R6) // v1.5.6: Tabs tab per-tab-item sub-section titles
internal static string Settings_Section_Tab_Channels => Get(nameof(Settings_Section_Tab_Channels)); internal static string Settings_Section_Tab_Channels => Get(nameof(Settings_Section_Tab_Channels));
internal static string Settings_Section_Tab_Display => Get(nameof(Settings_Section_Tab_Display)); internal static string Settings_Section_Tab_Display => Get(nameof(Settings_Section_Tab_Display));
internal static string Settings_Section_Tab_Notification => Get(nameof(Settings_Section_Tab_Notification)); internal static string Settings_Section_Tab_Notification => Get(nameof(Settings_Section_Tab_Notification));
@@ -571,7 +574,7 @@ internal class HellionStrings
internal static string Settings_Section_Tab_PopOut => Get(nameof(Settings_Section_Tab_PopOut)); internal static string Settings_Section_Tab_PopOut => Get(nameof(Settings_Section_Tab_PopOut));
internal static string Settings_Section_Tab_Volume_AllTabsHint => Get(nameof(Settings_Section_Tab_Volume_AllTabsHint)); internal static string Settings_Section_Tab_Volume_AllTabsHint => Get(nameof(Settings_Section_Tab_Volume_AllTabsHint));
// v1.5.6: About tab collapsible section titles (R6) // v1.5.6: About tab collapsible section titles
internal static string Settings_Section_Extensions => Get(nameof(Settings_Section_Extensions)); internal static string Settings_Section_Extensions => Get(nameof(Settings_Section_Extensions));
internal static string Settings_Section_PluginInfo => Get(nameof(Settings_Section_PluginInfo)); internal static string Settings_Section_PluginInfo => Get(nameof(Settings_Section_PluginInfo));
internal static string Settings_Section_Project => Get(nameof(Settings_Section_Project)); internal static string Settings_Section_Project => Get(nameof(Settings_Section_Project));
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Tria un perfil inicial. Podràs ajustar-ho tot més tard a Configuració → Privadesa.</value> <value>Tria un perfil inicial. Podràs ajustar-ho tot més tard a Configuració → Privadesa.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimització de dades (recomanat)</value> <value>Minimització de dades</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Només es guarden les teves pròpies converses: tells, party, FC, linkshells, cross-world linkshells, alliance i ExtraChat. El xat públic, els diàlegs dels PNJ i el correu brossa del sistema es descarten al nivell d'emmagatzematge. La retenció segueix els valors per defecte de l'especificació (tells 365 dies, canals de conversa propis 90 dies).</value> <value>Només es guarden les teves pròpies converses: tells, party, FC, linkshells, cross-world linkshells, alliance i ExtraChat. El xat públic, els diàlegs dels PNJ i el correu brossa del sistema es descarten al nivell d'emmagatzematge. La retenció segueix els valors per defecte de l'especificació (tells 365 dies, canals de conversa propis 90 dies).</value>
@@ -214,7 +214,13 @@
<value>Benvingut a Hellion Chat</value> <value>Benvingut a Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Un fork de Chat 2 de Hellion Forge amb valors per defecte respectuosos amb la privadesa, visuals coherents amb la marca i alguns retocs de qualitat de vida.</value> <value>La teva finestra de xat, de Hellion Forge. Privadesa des del primer moment, en 25 idiomes, i l'organitzes com vulguis.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat va començar com un fork de Chat 2. Des d'aleshores tots dos s'han allunyat prou perquè les bases de codi ja no siguin compatibles.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Els plugins són una zona grisa a Final Fantasy XIV: les condicions d'ús de Square Enix no els cobreixen, i Naoki Yoshida ha demanat públicament que no se'n faci publicitat. Mantén, doncs, el tema fora de Say, Yell, Shout i de qualsevol altre canal públic.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Tres passos ràpids. Podràs canviar-ho tot més tard a Configuració → Hellion Chat.</value> <value>Tres passos ràpids. Podràs canviar-ho tot més tard a Configuració → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Què es guarda?</value> <value>Què es guarda?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = recomanat per a la majoria de jugadors.</value> <value>Recomanat</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(sense canvis)</value> <value>(sense canvis)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Prova-ho: escriu /tell &lt;Nom del jugador&gt; al xat. Hellion Chat obre una pestanya dedicada per a la conversa i precarrega els últims {0} missatges.</value> <value>Prova-ho: escriu /tell &lt;Nom del jugador&gt; al xat. Hellion Chat obre una pestanya dedicada per a la conversa i precarrega els últims {0} missatges.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Configuració → Hellion Chat per ajustar-ho més tard</value> <value>Configuració → Hellion Chat per ajustar-ho més tard</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Sistema</value> <value>Sistema</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Vyber si výchozí profil. Vše můžeš kdykoli později upravit v Nastavení → Soukromí.</value> <value>Vyber si výchozí profil. Vše můžeš kdykoli později upravit v Nastavení → Soukromí.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimalizace dat (doporučeno)</value> <value>Minimalizace dat</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Ukládají se pouze tvoje vlastní konverzace: telly, party, FC, linkshelly, cross-world linkshelly, aliance a ExtraChat. Veřejný chat, dialogy NPC a systémový spam se na úrovni úložiště zahodí. Uchovávání dle výchozích hodnot specifikace (telly 365 dní, vlastní konverzační kanály 90 dní).</value> <value>Ukládají se pouze tvoje vlastní konverzace: telly, party, FC, linkshelly, cross-world linkshelly, aliance a ExtraChat. Veřejný chat, dialogy NPC a systémový spam se na úrovni úložiště zahodí. Uchovávání dle výchozích hodnot specifikace (telly 365 dní, vlastní konverzační kanály 90 dní).</value>
@@ -214,7 +214,13 @@
<value>Vítej v Hellion Chat</value> <value>Vítej v Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Fork Chat 2 od Hellion Forge s výchozím nastavením ohleduplným ke soukromí, vizuálem odpovídajícím značce a pár vylepšeními kvality života.</value> <value>Tvoje okno chatu od Hellion Forge. Soukromí od prvního spuštění, 25 jazyků a rozvržení si nastavíš sám.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat vznikl jako fork Chat 2. Od té doby se oba projekty rozešly natolik, že jejich kódové základny už nejsou kompatibilní.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Pluginy jsou ve Final Fantasy XIV šedá zóna: podmínky užívání Square Enix je nepokrývají a Naoki Yoshida veřejně požádal, aby se nepropagovaly. Nezmiňuj proto toto téma na Say, Yell, Shout ani na žádném jiném veřejném kanálu.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Tři krátké kroky. Vše můžeš kdykoli změnit v Nastavení → Hellion Chat.</value> <value>Tři krátké kroky. Vše můžeš kdykoli změnit v Nastavení → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Co se bude ukládat?</value> <value>Co se bude ukládat?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = doporučeno pro většinu hráčů.</value> <value>Doporučeno</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(beze změny)</value> <value>(beze změny)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Vyzkoušej: napiš /tell &lt;Jméno hráče&gt; do chatu. Hellion Chat automaticky otevře vlastní záložku pro konverzaci a přednačte posledních {0} zpráv.</value> <value>Vyzkoušej: napiš /tell &lt;Jméno hráče&gt; do chatu. Hellion Chat automaticky otevře vlastní záložku pro konverzaci a přednačte posledních {0} zpráv.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Nastavení → Hellion Chat pro pozdější doladění</value> <value>Nastavení → Hellion Chat pro pozdější doladění</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Systém</value> <value>Systém</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emoty</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Vælg en startprofil. Du kan justere alt efterfølgende under Indstillinger → Privatliv.</value> <value>Vælg en startprofil. Du kan justere alt efterfølgende under Indstillinger → Privatliv.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Dataminimering (anbefalet)</value> <value>Dataminimering</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Kun dine egne samtaler gemmes: tells, gruppe, FC, linkshells, cross-world linkshells, alliance og ExtraChat. Offentlig chat, NPC-dialoger og systemstøj kasseres på lagerniveau. Opbevaring følger spec-standarder (tells 365 dage, egne samtalekanaler 90 dage).</value> <value>Kun dine egne samtaler gemmes: tells, gruppe, FC, linkshells, cross-world linkshells, alliance og ExtraChat. Offentlig chat, NPC-dialoger og systemstøj kasseres på lagerniveau. Opbevaring følger spec-standarder (tells 365 dage, egne samtalekanaler 90 dage).</value>
@@ -214,7 +214,13 @@
<value>Velkommen til Hellion Chat</value> <value>Velkommen til Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>En Chat 2 fork fra Hellion Forge med privatlivsbevidste standarder, brandkonsistent udseende og et par praktiske forbedringer.</value> <value>Dit chatvindue fra Hellion Forge. Privatliv fra første start, 25 sprog, og du sætter det op, som du vil.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat startede som en fork af Chat 2. De to har siden fjernet sig så meget fra hinanden, at kodebaserne ikke længere er kompatible.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Plugins er en gråzone i Final Fantasy XIV: Square Enix' brugsvilkår dækker dem ikke, og Naoki Yoshida har offentligt bedt om, at man ikke reklamerer for dem. Hold derfor emnet ude af Say, Yell, Shout og alle andre offentlige kanaler.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Tre korte trin. Du kan ændre alt efterfølgende under Indstillinger → Hellion Chat.</value> <value>Tre korte trin. Du kan ændre alt efterfølgende under Indstillinger → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Hvad gemmes?</value> <value>Hvad gemmes?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = anbefalet til de fleste spillere.</value> <value>Anbefalet</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(uændret)</value> <value>(uændret)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Prøv det: skriv /tell &lt;Spillernavn&gt; i chatten. Hellion Chat åbner en dedikeret tab til samtalen og forudindlæser de sidste {0} beskeder.</value> <value>Prøv det: skriv /tell &lt;Spillernavn&gt; i chatten. Hellion Chat åbner en dedikeret tab til samtalen og forudindlæser de sidste {0} beskeder.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Indstillinger → Hellion Chat for at finjustere senere</value> <value>Indstillinger → Hellion Chat for at finjustere senere</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Wähle ein Start-Profil. Du kannst später alles unter Einstellungen → Datenschutz anpassen.</value> <value>Wähle ein Start-Profil. Du kannst später alles unter Einstellungen → Datenschutz anpassen.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Datensparsamkeit (empfohlen)</value> <value>Datensparsamkeit</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Es werden nur deine eigenen Konversationen gespeichert: Flüsternachrichten, Gruppe, FC, Linkshells, Cross-World-Linkshells, Allianz und ExtraChat. Öffentlicher Chat, NPC-Dialoge und System-Spam werden auf der Storage-Ebene verworfen. Aufbewahrung nach Spec-Defaults (Flüsternachrichten 365 Tage, eigene Konversations-Kanäle 90 Tage).</value> <value>Es werden nur deine eigenen Konversationen gespeichert: Flüsternachrichten, Gruppe, FC, Linkshells, Cross-World-Linkshells, Allianz und ExtraChat. Öffentlicher Chat, NPC-Dialoge und System-Spam werden auf der Storage-Ebene verworfen. Aufbewahrung nach Spec-Defaults (Flüsternachrichten 365 Tage, eigene Konversations-Kanäle 90 Tage).</value>
@@ -214,7 +214,13 @@
<value>Willkommen bei Hellion Chat</value> <value>Willkommen bei Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Ein Chat 2 Fork von Hellion Forge mit DSGVO-konformen Defaults, brand-konsistentem Look und Quality-of-Life-Verbesserungen.</value> <value>Dein Chat-Fenster von Hellion Forge. Datensparsam ab Werk, in 25 Sprachen, und du richtest es dir ein wie du willst.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Plugins sind in Final Fantasy XIV eine Grauzone: Die Nutzungsbedingungen von Square Enix decken sie nicht ab, und Naoki Yoshida hat öffentlich darum gebeten, nicht damit zu werben. Halte das Thema deshalb aus Sagen, Rufen, Schreien und allen anderen öffentlichen Kanälen heraus.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat ging ursprünglich aus Chat 2 hervor. Beide haben sich seitdem so weit auseinanderentwickelt, dass die Codebasen nicht mehr kompatibel sind.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>3 kurze Schritte. Du kannst alles später unter Einstellungen → Hellion Chat ändern.</value> <value>3 kurze Schritte. Du kannst alles später unter Einstellungen → Hellion Chat ändern.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Was darf gespeichert werden?</value> <value>Was darf gespeichert werden?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = empfohlen für die meisten Spieler.</value> <value>Empfohlen</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(unverändert)</value> <value>(unverändert)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Probier's aus: Tipp /tell &lt;Spielername&gt; in den Chat. Hellion Chat öffnet automatisch einen eigenen Tab für die Unterhaltung und lädt die letzten {0} Messages mit.</value> <value>Probier's aus: Tipp /tell &lt;Spielername&gt; in den Chat. Hellion Chat öffnet automatisch einen eigenen Tab für die Unterhaltung und lädt die letzten {0} Messages mit.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Einstellungen → Hellion Chat zum späteren Anpassen</value> <value>Einstellungen → Hellion Chat zum späteren Anpassen</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Επίλεξε ένα αρχικό προφίλ. Μπορείς να ρυθμίσεις τα πάντα αργότερα στις Ρυθμίσεις → Απόρρητο.</value> <value>Επίλεξε ένα αρχικό προφίλ. Μπορείς να ρυθμίσεις τα πάντα αργότερα στις Ρυθμίσεις → Απόρρητο.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Ελαχιστοποίηση δεδομένων (συνιστάται)</value> <value>Ελαχιστοποίηση δεδομένων</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Αποθηκεύονται μόνο οι δικές σου συνομιλίες: tells, party, FC, linkshells, cross-world linkshells, alliance και ExtraChat. Το δημόσιο chat, οι διάλογοι NPC και το system spam απορρίπτονται σε επίπεδο αποθήκευσης. Η διατήρηση ακολουθεί τις προεπιλογές spec (tells 365 ημέρες, κανάλια ιδιωτικών συνομιλιών 90 ημέρες).</value> <value>Αποθηκεύονται μόνο οι δικές σου συνομιλίες: tells, party, FC, linkshells, cross-world linkshells, alliance και ExtraChat. Το δημόσιο chat, οι διάλογοι NPC και το system spam απορρίπτονται σε επίπεδο αποθήκευσης. Η διατήρηση ακολουθεί τις προεπιλογές spec (tells 365 ημέρες, κανάλια ιδιωτικών συνομιλιών 90 ημέρες).</value>
@@ -214,7 +214,13 @@
<value>Καλωσόρισες στο Hellion Chat</value> <value>Καλωσόρισες στο Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Ένα fork του Chat 2 από το Hellion Forge με προεπιλογές φιλικές στο απόρρητο, συνεπή εμφάνιση brand και μερικές βελτιώσεις ευχρηστίας.</value> <value>Το παράθυρο συνομιλίας σου από το Hellion Forge. Ιδιωτικότητα από την αρχή, σε 25 γλώσσες, και το στήνεις όπως θέλεις.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Το Hellion Chat ξεκίνησε ως fork του Chat 2. Έκτοτε τα δύο έχουν απομακρυνθεί τόσο ώστε οι κώδικές τους να μην είναι πλέον συμβατοί.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Τα plugin αποτελούν γκρίζα ζώνη στο Final Fantasy XIV: οι όροι χρήσης της Square Enix δεν τα καλύπτουν και ο Naoki Yoshida έχει ζητήσει δημόσια να μην διαφημίζονται. Κράτα λοιπόν το θέμα μακριά από τα Say, Yell, Shout και κάθε άλλο δημόσιο κανάλι.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Τρία σύντομα βήματα. Μπορείς να αλλάξεις τα πάντα αργότερα στις Ρυθμίσεις → Hellion Chat.</value> <value>Τρία σύντομα βήματα. Μπορείς να αλλάξεις τα πάντα αργότερα στις Ρυθμίσεις → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Τι αποθηκεύεται;</value> <value>Τι αποθηκεύεται;</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = συνιστάται για τους περισσότερους παίκτες.</value> <value>Συνιστάται</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(αμετάβλητο)</value> <value>(αμετάβλητο)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Δοκίμασέ το: πληκτρολόγησε /tell &lt;Όνομα Παίκτη&gt; στο chat. Το Hellion Chat ανοίγει αυτόματα μια αποκλειστική καρτέλα για τη συνομιλία και προφορτώνει τα τελευταία {0} μηνύματα.</value> <value>Δοκίμασέ το: πληκτρολόγησε /tell &lt;Όνομα Παίκτη&gt; στο chat. Το Hellion Chat ανοίγει αυτόματα μια αποκλειστική καρτέλα για τη συνομιλία και προφορτώνει τα τελευταία {0} μηνύματα.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Ρυθμίσεις → Hellion Chat για προσαρμογή αργότερα</value> <value>Ρυθμίσεις → Hellion Chat για προσαρμογή αργότερα</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Elige un perfil inicial. Puedes ajustarlo todo más tarde en Ajustes → Privacidad.</value> <value>Elige un perfil inicial. Puedes ajustarlo todo más tarde en Ajustes → Privacidad.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimización de datos (recomendado)</value> <value>Minimización de datos</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Solo se almacenan tus propias conversaciones: tells, escuadrón, FC, linkshells, linkshells cross-world, alianza y ExtraChat. El chat público, los diálogos de PNJ y el spam del sistema se descartan a nivel de almacenamiento. La retención sigue los valores predeterminados de spec (tells 365 días, canales de conversación propios 90 días).</value> <value>Solo se almacenan tus propias conversaciones: tells, escuadrón, FC, linkshells, linkshells cross-world, alianza y ExtraChat. El chat público, los diálogos de PNJ y el spam del sistema se descartan a nivel de almacenamiento. La retención sigue los valores predeterminados de spec (tells 365 días, canales de conversación propios 90 días).</value>
@@ -214,7 +214,13 @@
<value>Bienvenido a Hellion Chat</value> <value>Bienvenido a Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Un fork de Chat 2 de Hellion Forge con valores predeterminados respetuosos con la privacidad, diseño coherente con la marca y algunas mejoras de calidad de vida.</value> <value>Tu ventana de chat, de Hellion Forge. Privacidad desde el primer momento, traducida a 25 idiomas, y la organizas a tu gusto.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat nació como un fork de Chat 2. Desde entonces ambos se han separado lo suficiente como para que sus bases de código ya no sean compatibles.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Los plugins son una zona gris en Final Fantasy XIV: las condiciones de uso de Square Enix no los contemplan, y Naoki Yoshida ha pedido públicamente que no se promocionen. Mantén el tema fuera de Decir, Gritar, Vociferar y de cualquier otro canal público.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Tres pasos breves. Puedes cambiar todo más tarde en Ajustes → Hellion Chat.</value> <value>Tres pasos breves. Puedes cambiar todo más tarde en Ajustes → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>¿Qué se almacena?</value> <value>¿Qué se almacena?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = recomendado para la mayoría de jugadores.</value> <value>Recomendado</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(sin cambios)</value> <value>(sin cambios)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Pruébalo: escribe /tell &lt;Nombre del jugador&gt; en el chat. Hellion Chat abre una pestaña dedicada para la conversación y precarga los últimos {0} mensajes.</value> <value>Pruébalo: escribe /tell &lt;Nombre del jugador&gt; en el chat. Hellion Chat abre una pestaña dedicada para la conversación y precarga los últimos {0} mensajes.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Ajustes → Hellion Chat para personalizar más tarde</value> <value>Ajustes → Hellion Chat para personalizar más tarde</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Sistema</value> <value>Sistema</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Valitse aloitusprofiili. Voit muuttaa kaikkea myöhemmin kohdassa Asetukset → Tietosuoja.</value> <value>Valitse aloitusprofiili. Voit muuttaa kaikkea myöhemmin kohdassa Asetukset → Tietosuoja.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Tietojen minimointi (suositeltu)</value> <value>Tietojen minimointi</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Vain omat keskustelusi tallennetaan: tellit, ryhmä, FC, linkshells, cross-world linkshells, allianssi ja ExtraChat. Julkinen chat, NPC-dialogit ja järjestelmäroskaviestit hylätään tallennusvaiheessa. Säilytys noudattaa spec-oletuksia (tellit 365 päivää, omat keskustelukanavat 90 päivää).</value> <value>Vain omat keskustelusi tallennetaan: tellit, ryhmä, FC, linkshells, cross-world linkshells, allianssi ja ExtraChat. Julkinen chat, NPC-dialogit ja järjestelmäroskaviestit hylätään tallennusvaiheessa. Säilytys noudattaa spec-oletuksia (tellit 365 päivää, omat keskustelukanavat 90 päivää).</value>
@@ -214,7 +214,13 @@
<value>Tervetuloa Hellion Chatiin</value> <value>Tervetuloa Hellion Chatiin</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Chat 2 -haarautuma Hellion Forgelta, tietosuojatietoisilla oletuksilla, yhtenäisellä visuaalisella ilmeellä ja muutamilla käytännöllisillä parannuksilla.</value> <value>Chat-ikkunasi Hellion Forgelta. Yksityisyys heti alusta, 25 kieltä, ja järjestät sen kuten haluat.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat sai alkunsa Chat 2:n forkkina. Sittemmin ne ovat eronneet toisistaan niin paljon, etteivät koodipohjat ole enää yhteensopivia.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Pluginit ovat Final Fantasy XIV:ssä harmaata aluetta: Square Enixin käyttöehdot eivät kata niitä, ja Naoki Yoshida on julkisesti pyytänyt, ettei niitä mainostettaisi. Pidä aihe siis poissa kanavilta Say, Yell, Shout ja kaikilta muilta julkisilta kanavilta.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Kolme lyhyttä vaihetta. Voit muuttaa kaikkea myöhemmin kohdassa Asetukset → Hellion Chat.</value> <value>Kolme lyhyttä vaihetta. Voit muuttaa kaikkea myöhemmin kohdassa Asetukset → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Mitä tallennetaan?</value> <value>Mitä tallennetaan?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = suositeltu useimmille pelaajille.</value> <value>Suositeltu</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(ei muutosta)</value> <value>(ei muutosta)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Kokeile: kirjoita /tell &lt;Pelaajan nimi&gt; chattiin. Hellion Chat avaa erillisen välilehden keskustelulle ja esivalmistelee viimeiset {0} viestiä.</value> <value>Kokeile: kirjoita /tell &lt;Pelaajan nimi&gt; chattiin. Hellion Chat avaa erillisen välilehden keskustelulle ja esivalmistelee viimeiset {0} viestiä.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Asetukset → Hellion Chat hienosäätöä varten myöhemmin</value> <value>Asetukset → Hellion Chat hienosäätöä varten myöhemmin</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Järjestelmä</value> <value>Järjestelmä</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emootiot</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Choisissez un profil de départ. Vous pouvez tout ajuster par la suite dans Paramètres → Confidentialité.</value> <value>Choisissez un profil de départ. Vous pouvez tout ajuster par la suite dans Paramètres → Confidentialité.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimisation des données (recommandé)</value> <value>Minimisation des données</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Seules vos propres conversations sont enregistrées : messages privés, équipe, CL, linkshells, linkshells inter-mondes, alliance et ExtraChat. Le chat public, les dialogues PNJ et le spam système sont écartés au niveau du stockage. La conservation suit les valeurs par défaut de la spécification (messages privés 365 jours, vos canaux de conversation 90 jours).</value> <value>Seules vos propres conversations sont enregistrées : messages privés, équipe, CL, linkshells, linkshells inter-mondes, alliance et ExtraChat. Le chat public, les dialogues PNJ et le spam système sont écartés au niveau du stockage. La conservation suit les valeurs par défaut de la spécification (messages privés 365 jours, vos canaux de conversation 90 jours).</value>
@@ -214,7 +214,13 @@
<value>Bienvenue dans Hellion Chat</value> <value>Bienvenue dans Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Un fork de Chat 2 par Hellion Forge avec des valeurs par défaut axées sur la confidentialité, une identité visuelle cohérente et quelques améliorations de confort.</value> <value>Ta fenêtre de discussion, signée Hellion Forge. Confidentialité par défaut, traduite en 25 langues, et à agencer comme tu veux.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat est né d'un fork de Chat 2. Les deux se sont depuis suffisamment éloignés pour que les bases de code ne soient plus compatibles.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Les plugins sont une zone grise dans Final Fantasy XIV : les conditions d'utilisation de Square Enix ne les couvrent pas, et Naoki Yoshida a publiquement demandé de ne pas en faire la promotion. Évite donc le sujet dans Dire, Crier, Hurler et tout autre canal public.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Trois étapes courtes. Vous pouvez tout modifier plus tard dans Paramètres → Hellion Chat.</value> <value>Trois étapes courtes. Vous pouvez tout modifier plus tard dans Paramètres → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Qu'est-ce qui est enregistré ?</value> <value>Qu'est-ce qui est enregistré ?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = recommandé pour la plupart des joueurs.</value> <value>Recommandé</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(inchangé)</value> <value>(inchangé)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Essayez : tapez /tell &lt;Nom du joueur&gt; dans le chat. Hellion Chat ouvre un onglet dédié à la conversation et précharge les {0} derniers messages.</value> <value>Essayez : tapez /tell &lt;Nom du joueur&gt; dans le chat. Hellion Chat ouvre un onglet dédié à la conversation et précharge les {0} derniers messages.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Paramètres → Hellion Chat pour affiner plus tard</value> <value>Paramètres → Hellion Chat pour affiner plus tard</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Système</value> <value>Système</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Compagnie libre</value> <value>Compagnie libre</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Válassz egy kezdő profilt. Mindent később is módosíthatsz a Beállítások → Adatvédelem menüpontban.</value> <value>Válassz egy kezdő profilt. Mindent később is módosíthatsz a Beállítások → Adatvédelem menüpontban.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Adatminimalizálás (ajánlott)</value> <value>Adatminimalizálás</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Csak a saját beszélgetéseid tárolódnak: tellek, party, FC, linkshellек, cross-world linkshellек, szövetség és ExtraChat. A nyilvános chat, az NPC-párbeszédek és a rendszerüzenetek a tárolás szintjén eldobódnak. A megőrzés a spec-alapértelmezettet követi (tellek 365 nap, saját csatornák 90 nap).</value> <value>Csak a saját beszélgetéseid tárolódnak: tellek, party, FC, linkshellек, cross-world linkshellек, szövetség és ExtraChat. A nyilvános chat, az NPC-párbeszédek és a rendszerüzenetek a tárolás szintjén eldobódnak. A megőrzés a spec-alapértelmezettet követi (tellek 365 nap, saját csatornák 90 nap).</value>
@@ -214,7 +214,13 @@
<value>Üdvözöl a Hellion Chat</value> <value>Üdvözöl a Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Egy Chat 2 fork a Hellion Forge-tól, adatvédelmet szem előtt tartó alapértelmezésekkel, egységes arculattal és néhány kényelmi funkcióval.</value> <value>A te chatablakod a Hellion Forge-tól. Adatvédelem az első indítástól, 25 nyelven, és úgy rendezed be, ahogy szeretnéd.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>A Hellion Chat a Chat 2 forkjaként indult. A kettő azóta annyira eltávolodott egymástól, hogy a kódbázisok már nem kompatibilisek.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>A pluginek a Final Fantasy XIV-ben szürke zónát jelentenek: a Square Enix felhasználási feltételei nem terjednek ki rájuk, és Naoki Yoshida nyilvánosan kérte, hogy ne reklámozzák őket. Ne hozd tehát szóba a témát a Say, Yell, Shout és bármely más nyilvános csatornán.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Három rövid lépés. Mindent megváltoztathatsz később a Beállítások → Hellion Chat menüpontban.</value> <value>Három rövid lépés. Mindent megváltoztathatsz később a Beállítások → Hellion Chat menüpontban.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Mi tárolódjon?</value> <value>Mi tárolódjon?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = a legtöbb játékosnak ajánlott.</value> <value>Ajánlott</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(változatlan)</value> <value>(változatlan)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Próbáld ki: írj /tell &lt;Játékosnév&gt; a chatbe. A Hellion Chat automatikusan megnyit egy külön fület a beszélgetéshez, és előtölti az utolsó {0} üzenetet.</value> <value>Próbáld ki: írj /tell &lt;Játékosnév&gt; a chatbe. A Hellion Chat automatikusan megnyit egy külön fület a beszélgetéshez, és előtölti az utolsó {0} üzenetet.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Beállítások → Hellion Chat a finomhangoláshoz</value> <value>Beállítások → Hellion Chat a finomhangoláshoz</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Rendszer</value> <value>Rendszer</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emote-ok</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Scegli un profilo di partenza. Puoi regolare tutto in seguito in Impostazioni → Privacy.</value> <value>Scegli un profilo di partenza. Puoi regolare tutto in seguito in Impostazioni → Privacy.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimizzazione dei dati (consigliata)</value> <value>Minimizzazione dei dati</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Vengono salvate solo le tue conversazioni: tell, party, FC, linkshell, cross-world linkshell, alliance ed ExtraChat. La chat pubblica, i dialoghi NPC e lo spam di sistema vengono scartati a livello di archiviazione. La conservazione segue i valori predefiniti dello spec (tell 365 giorni, canali di conversazione propri 90 giorni).</value> <value>Vengono salvate solo le tue conversazioni: tell, party, FC, linkshell, cross-world linkshell, alliance ed ExtraChat. La chat pubblica, i dialoghi NPC e lo spam di sistema vengono scartati a livello di archiviazione. La conservazione segue i valori predefiniti dello spec (tell 365 giorni, canali di conversazione propri 90 giorni).</value>
@@ -214,7 +214,13 @@
<value>Benvenuto in Hellion Chat</value> <value>Benvenuto in Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Un fork di Chat 2 da Hellion Forge con impostazioni predefinite attente alla privacy, un'estetica coerente con il brand e qualche miglioramento alla qualità della vita.</value> <value>La tua finestra di chat, firmata Hellion Forge. Privacy fin da subito, tradotta in 25 lingue, e la sistemi come vuoi.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat è nato come fork di Chat 2. Da allora i due si sono allontanati al punto che le basi di codice non sono più compatibili.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>I plugin sono una zona grigia in Final Fantasy XIV: le condizioni d'uso di Square Enix non li contemplano, e Naoki Yoshida ha chiesto pubblicamente di non pubblicizzarli. Tieni quindi l'argomento fuori da Say, Yell, Shout e da ogni altro canale pubblico.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Tre brevi passaggi. Puoi cambiare tutto in seguito in Impostazioni → Hellion Chat.</value> <value>Tre brevi passaggi. Puoi cambiare tutto in seguito in Impostazioni → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Cosa viene salvato?</value> <value>Cosa viene salvato?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = consigliato per la maggior parte dei giocatori.</value> <value>Consigliato</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(invariato)</value> <value>(invariato)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Provalo: digita /tell &lt;Nome Giocatore&gt; in chat. Hellion Chat apre un tab dedicato alla conversazione e precarica gli ultimi {0} messaggi.</value> <value>Provalo: digita /tell &lt;Nome Giocatore&gt; in chat. Hellion Chat apre un tab dedicato alla conversazione e precarica gli ultimi {0} messaggi.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Impostazioni → Hellion Chat per regolazioni successive</value> <value>Impostazioni → Hellion Chat per regolazioni successive</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Sistema</value> <value>Sistema</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emote</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>開始プロファイルを選択してください。後で設定 → プライバシーからいつでも変更できます。</value> <value>開始プロファイルを選択してください。後で設定 → プライバシーからいつでも変更できます。</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>データ最小化(推奨)</value> <value>データ最小化</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>自分の会話のみ保存されます: テル、パーティ、フリーカンパニー、リンクシェル、クロスワールドリンクシェル、アライアンス、ExtraChat。パブリックチャット、NPCの台詞、システムスパムはストレージレベルで破棄されます。保持期間は仕様デフォルト(テル 365日、自分の会話チャンネル 90日)に従います。</value> <value>自分の会話のみ保存されます: テル、パーティ、フリーカンパニー、リンクシェル、クロスワールドリンクシェル、アライアンス、ExtraChat。パブリックチャット、NPCの台詞、システムスパムはストレージレベルで破棄されます。保持期間は仕様デフォルト(テル 365日、自分の会話チャンネル 90日)に従います。</value>
@@ -214,7 +214,13 @@
<value>Hellion Chat へようこそ</value> <value>Hellion Chat へようこそ</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Hellion Forge による Chat 2 フォーク。プライバシーに配慮したデフォルト設定、ブランド一貫のビジュアル、そしていくつかの利便性向上機能を備えています。</value> <value>Hellion Forge がお届けするチャットウィンドウ。初期設定からプライバシー重視、25言語対応、レイアウトは自由に組み替えられます。</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat は Chat 2 のフォークとして始まりました。その後、両者は大きく分かれ、コードベースはすでに互換性がありません。</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>ファイナルファンタジーXIVにおいてプラグインはグレーゾーンです。スクウェア・エニックスの利用規約は対象としておらず、吉田直樹氏も公の場で宣伝しないよう求めています。Say、Yell、Shout をはじめとする公開チャンネルでは話題にしないでください。</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>3つの短いステップです。後で設定 → Hellion Chat からすべて変更できます。</value> <value>3つの短いステップです。後で設定 → Hellion Chat からすべて変更できます。</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>何が保存されますか?</value> <value>何が保存されますか?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = ほとんどのプレイヤーに推奨。</value> <value>推奨</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>ロールプレイ</value> <value>ロールプレイ</value>
@@ -298,7 +304,7 @@
<value>(変更なし)</value> <value>(変更なし)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 試してみましょう: チャットに /tell &lt;プレイヤー名&gt; と入力してください。Hellion Chat が会話専用のタブを自動で開き、最新 {0} 件のメッセージをプリロードします。</value> <value>試してみましょう: チャットに /tell &lt;プレイヤー名&gt; と入力してください。Hellion Chat が会話専用のタブを自動で開き、最新 {0} 件のメッセージをプリロードします。</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>設定 → Hellion Chat で後から細かく調整できます</value> <value>設定 → Hellion Chat で後から細かく調整できます</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>システム</value> <value>システム</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>エモート</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>フリーカンパニー</value> <value>フリーカンパニー</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>시작 프로필을 선택하세요. 나중에 설정 → 개인정보에서 모두 조정할 수 있습니다.</value> <value>시작 프로필을 선택하세요. 나중에 설정 → 개인정보에서 모두 조정할 수 있습니다.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>데이터 최소화 (권장)</value> <value>데이터 최소화</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>자신의 대화만 저장됩니다. 귓속말, 파티, 자유부대, 링크셸, 서버 초월 링크셸, 연합 파티, ExtraChat이 포함됩니다. 공개 채팅, NPC 대화, 시스템 스팸은 저장 단계에서 제외됩니다. 보존 기간은 기본 스펙을 따릅니다 (귓속말 365일, 개인 대화 채널 90일).</value> <value>자신의 대화만 저장됩니다. 귓속말, 파티, 자유부대, 링크셸, 서버 초월 링크셸, 연합 파티, ExtraChat이 포함됩니다. 공개 채팅, NPC 대화, 시스템 스팸은 저장 단계에서 제외됩니다. 보존 기간은 기본 스펙을 따릅니다 (귓속말 365일, 개인 대화 채널 90일).</value>
@@ -214,7 +214,13 @@
<value>Hellion Chat에 오신 것을 환영합니다</value> <value>Hellion Chat에 오신 것을 환영합니다</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Hellion Forge에서 만든 Chat 2 포크입니다. 개인정보 보호 기본값, 브랜드 일관성 있는 디자인, 그리고 몇 가지 편의 기능을 제공합니다.</value> <value>Hellion Forge가 만든 채팅 창입니다. 기본값부터 개인정보 우선, 25개 언어 지원, 배치는 원하는 대로.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat은 Chat 2의 포크로 시작했습니다. 그 뒤로 둘은 코드베이스가 더 이상 호환되지 않을 만큼 멀어졌습니다.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>파이널 판타지 XIV에서 플러그인은 회색 지대입니다. 스퀘어 에닉스 이용약관은 이를 다루지 않으며, 요시다 나오키는 공개적으로 홍보하지 말아 달라고 요청했습니다. 말하기, 떠들기, 외치기를 비롯한 모든 공개 채널에서는 이 주제를 꺼내지 마세요.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>세 가지 간단한 단계입니다. 나중에 설정 → Hellion Chat에서 모두 변경할 수 있습니다.</value> <value>세 가지 간단한 단계입니다. 나중에 설정 → Hellion Chat에서 모두 변경할 수 있습니다.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>무엇을 저장할까요?</value> <value>무엇을 저장할까요?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = 대부분의 플레이어에게 권장됩니다.</value> <value>권장</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>롤플레이</value> <value>롤플레이</value>
@@ -298,7 +304,7 @@
<value>(변경 없음)</value> <value>(변경 없음)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 테스트해보세요. 채팅창에 /tell &lt;Player Name&gt;을 입력하면 Hellion Chat이 대화를 위한 전용 탭을 열고 마지막 {0}개의 메시지를 미리 불러옵니다.</value> <value>테스트해보세요. 채팅창에 /tell &lt;Player Name&gt;을 입력하면 Hellion Chat이 대화를 위한 전용 탭을 열고 마지막 {0}개의 메시지를 미리 불러옵니다.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>나중에 세부 조정은 설정 → Hellion Chat에서</value> <value>나중에 세부 조정은 설정 → Hellion Chat에서</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>시스템</value> <value>시스템</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>감정 표현</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>자유부대</value> <value>자유부대</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Velg en startprofil. Du kan justere alt senere under Innstillinger → Personvern.</value> <value>Velg en startprofil. Du kan justere alt senere under Innstillinger → Personvern.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Dataminimering (anbefalt)</value> <value>Dataminimering</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Bare dine egne samtaler lagres: tells, party, FC, linkshells, cross-world linkshells, alliance og ExtraChat. Offentlig chat, NPC-dialoger og systemspam forkastes på lagringsnivå. Oppbevaring følger spec-standarder (tells 365 dager, egne samtalekanaler 90 dager).</value> <value>Bare dine egne samtaler lagres: tells, party, FC, linkshells, cross-world linkshells, alliance og ExtraChat. Offentlig chat, NPC-dialoger og systemspam forkastes på lagringsnivå. Oppbevaring følger spec-standarder (tells 365 dager, egne samtalekanaler 90 dager).</value>
@@ -214,7 +214,13 @@
<value>Velkommen til Hellion Chat</value> <value>Velkommen til Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>En Chat 2-fork fra Hellion Forge med personvernvennlige standarder, merkevarekonsekvente visuals og noen livskvalitetsforbedringer.</value> <value>Chatvinduet ditt fra Hellion Forge. Personvern fra første start, 25 språk, og du setter det opp slik du vil.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat startet som en fork av Chat 2. De to har siden fjernet seg så mye fra hverandre at kodebasene ikke lenger er kompatible.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Plugins er en gråsone i Final Fantasy XIV: Square Enix' bruksvilkår dekker dem ikke, og Naoki Yoshida har offentlig bedt om at man ikke reklamerer for dem. Hold derfor temaet unna Say, Yell, Shout og alle andre offentlige kanaler.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Tre korte steg. Du kan endre alt senere under Innstillinger → Hellion Chat.</value> <value>Tre korte steg. Du kan endre alt senere under Innstillinger → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Hva blir lagret?</value> <value>Hva blir lagret?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = anbefalt for de fleste spillere.</value> <value>Anbefalt</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(uendret)</value> <value>(uendret)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Prøv det: skriv /tell &lt;Spillernavn&gt; i chatten. Hellion Chat åpner en dedikert fane for samtalen og forhåndslaster de siste {0} meldingene.</value> <value>Prøv det: skriv /tell &lt;Spillernavn&gt; i chatten. Hellion Chat åpner en dedikert fane for samtalen og forhåndslaster de siste {0} meldingene.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Innstillinger → Hellion Chat for å finjustere senere</value> <value>Innstillinger → Hellion Chat for å finjustere senere</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Kies een startprofiel. Je kunt later alles aanpassen via Instellingen → Privacy.</value> <value>Kies een startprofiel. Je kunt later alles aanpassen via Instellingen → Privacy.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Dataminimalisatie (aanbevolen)</value> <value>Dataminimalisatie</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Alleen je eigen gesprekken worden opgeslagen: tells, groep, FC, linkshells, cross-world linkshells, alliantie en ExtraChat. Openbare chat, NPC-dialogen en systeemspam worden op opslagniveau verwijderd. Retentie volgt spec-standaarden (tells 365 dagen, eigen gesprekkanalen 90 dagen).</value> <value>Alleen je eigen gesprekken worden opgeslagen: tells, groep, FC, linkshells, cross-world linkshells, alliantie en ExtraChat. Openbare chat, NPC-dialogen en systeemspam worden op opslagniveau verwijderd. Retentie volgt spec-standaarden (tells 365 dagen, eigen gesprekkanalen 90 dagen).</value>
@@ -214,7 +214,13 @@
<value>Welkom bij Hellion Chat</value> <value>Welkom bij Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Een Chat 2 fork van Hellion Forge met privacybewuste standaarden, merkconforme uitstraling en handige quality-of-life verbeteringen.</value> <value>Jouw chatvenster van Hellion Forge. Privacy vanaf de eerste start, in 25 talen, en je richt het in zoals jij wilt.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat begon als een fork van Chat 2. De twee zijn sindsdien zo ver uit elkaar gegroeid dat de codebases niet meer compatibel zijn.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Plugins zijn in Final Fantasy XIV een grijs gebied: de gebruiksvoorwaarden van Square Enix dekken ze niet, en Naoki Yoshida heeft publiekelijk gevraagd er geen reclame voor te maken. Houd het onderwerp dus buiten Zeg, Roep, Schreeuw en elk ander openbaar kanaal.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Drie korte stappen. Je kunt later alles aanpassen via Instellingen → Hellion Chat.</value> <value>Drie korte stappen. Je kunt later alles aanpassen via Instellingen → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Wat wordt er opgeslagen?</value> <value>Wat wordt er opgeslagen?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = aanbevolen voor de meeste spelers.</value> <value>Aanbevolen</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(ongewijzigd)</value> <value>(ongewijzigd)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Probeer het uit: typ /tell &lt;Spelernaam&gt; in de chat. Hellion Chat opent een eigen tabblad voor het gesprek en laadt de laatste {0} berichten vooraf.</value> <value>Probeer het uit: typ /tell &lt;Spelernaam&gt; in de chat. Hellion Chat opent een eigen tabblad voor het gesprek en laadt de laatste {0} berichten vooraf.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Instellingen → Hellion Chat om later te verfijnen</value> <value>Instellingen → Hellion Chat om later te verfijnen</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Systeem</value> <value>Systeem</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Wybierz profil startowy. Wszystko możesz zmienić później w Ustawieniach → Prywatność.</value> <value>Wybierz profil startowy. Wszystko możesz zmienić później w Ustawieniach → Prywatność.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimalizacja danych (zalecane)</value> <value>Minimalizacja danych</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Przechowywane są tylko twoje własne rozmowy: tells, grupa, FC, linkshells, cross-world linkshells, sojusz i ExtraChat. Czat publiczny, dialogi NPC i spam systemowy są odrzucane na poziomie zapisu. Czas przechowywania według domyślnych wartości specyfikacji (tells 365 dni, własne kanały rozmów 90 dni).</value> <value>Przechowywane są tylko twoje własne rozmowy: tells, grupa, FC, linkshells, cross-world linkshells, sojusz i ExtraChat. Czat publiczny, dialogi NPC i spam systemowy są odrzucane na poziomie zapisu. Czas przechowywania według domyślnych wartości specyfikacji (tells 365 dni, własne kanały rozmów 90 dni).</value>
@@ -214,7 +214,13 @@
<value>Witaj w Hellion Chat</value> <value>Witaj w Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Fork Chat 2 od Hellion Forge z domyślnymi ustawieniami chroniącymi prywatność, spójną identyfikacją wizualną i drobnymi usprawnieniami komfortu gry.</value> <value>Twoje okno czatu od Hellion Forge. Prywatność od pierwszego uruchomienia, 25 języków, a układ ustawiasz sam.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat powstał jako fork Chat 2. Od tamtej pory oba projekty rozeszły się na tyle, że ich bazy kodu nie są już zgodne.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Pluginy to w Final Fantasy XIV szara strefa: warunki korzystania Square Enix ich nie obejmują, a Naoki Yoshida publicznie prosił, by ich nie reklamować. Nie poruszaj więc tego tematu na Say, Yell, Shout ani na żadnym innym kanale publicznym.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Trzy krótkie kroki. Wszystko możesz zmienić później w Ustawieniach → Hellion Chat.</value> <value>Trzy krótkie kroki. Wszystko możesz zmienić później w Ustawieniach → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Co ma być zapisywane?</value> <value>Co ma być zapisywane?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = zalecane dla większości graczy.</value> <value>Zalecane</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(bez zmian)</value> <value>(bez zmian)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Wypróbuj: wpisz /tell &lt;Nazwa gracza&gt; w czacie. Hellion Chat otworzy dedykowaną zakładkę dla rozmowy i wstępnie wczyta ostatnie {0} wiadomości.</value> <value>Wypróbuj: wpisz /tell &lt;Nazwa gracza&gt; w czacie. Hellion Chat otworzy dedykowaną zakładkę dla rozmowy i wstępnie wczyta ostatnie {0} wiadomości.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Ustawienia → Hellion Chat, aby dostosować później</value> <value>Ustawienia → Hellion Chat, aby dostosować później</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emoty</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
@@ -172,7 +172,7 @@
<value>Escolha um perfil inicial. Você pode ajustar tudo depois em Configurações → Privacidade.</value> <value>Escolha um perfil inicial. Você pode ajustar tudo depois em Configurações → Privacidade.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimização de dados (recomendado)</value> <value>Minimização de dados</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Apenas suas próprias conversas são armazenadas: tells, grupo, FC, linkshells, cross-world linkshells, aliança e ExtraChat. Bate-papo público, diálogos de NPC e spam de sistema são descartados no nível de armazenamento. A retenção segue os padrões da spec (tells 365 dias, canais de conversa próprios 90 dias).</value> <value>Apenas suas próprias conversas são armazenadas: tells, grupo, FC, linkshells, cross-world linkshells, aliança e ExtraChat. Bate-papo público, diálogos de NPC e spam de sistema são descartados no nível de armazenamento. A retenção segue os padrões da spec (tells 365 dias, canais de conversa próprios 90 dias).</value>
@@ -214,7 +214,13 @@
<value>Bem-vindo ao Hellion Chat</value> <value>Bem-vindo ao Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Um fork do Chat 2 pela Hellion Forge com padrões voltados para privacidade, visual consistente com a marca e alguns toques de qualidade de vida.</value> <value>Sua janela de chat, da Hellion Forge. Privacidade desde o primeiro uso, 25 idiomas, e você organiza do seu jeito.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>O Hellion Chat começou como um fork do Chat 2. Desde então os dois se afastaram a ponto de as bases de código não serem mais compatíveis.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Plugins são uma zona cinzenta no Final Fantasy XIV: os termos de uso da Square Enix não os cobrem, e Naoki Yoshida pediu publicamente que não sejam divulgados. Mantenha o assunto fora de Falar, Grita, Berrar e de qualquer outro canal público.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Três passos rápidos. Você pode mudar tudo depois em Configurações → Hellion Chat.</value> <value>Três passos rápidos. Você pode mudar tudo depois em Configurações → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>O que será armazenado?</value> <value>O que será armazenado?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = recomendado para a maioria dos jogadores.</value> <value>Recomendado</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(sem alteração)</value> <value>(sem alteração)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Experimente: digite /tell &lt;Nome do Jogador&gt; no chat. O Hellion Chat abre uma aba dedicada para a conversa e pré-carrega as últimas {0} mensagens.</value> <value>Experimente: digite /tell &lt;Nome do Jogador&gt; no chat. O Hellion Chat abre uma aba dedicada para a conversa e pré-carrega as últimas {0} mensagens.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Configurações → Hellion Chat para ajustar depois</value> <value>Configurações → Hellion Chat para ajustar depois</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Sistema</value> <value>Sistema</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
@@ -172,7 +172,7 @@
<value>Escolhe um perfil inicial. Podes ajustar tudo depois em Definições → Privacidade.</value> <value>Escolhe um perfil inicial. Podes ajustar tudo depois em Definições → Privacidade.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimização de dados (recomendado)</value> <value>Minimização de dados</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Só as tuas próprias conversas são armazenadas: tells, grupo, FC, linkshells, cross-world linkshells, aliança e ExtraChat. O chat público, os diálogos de NPC e o spam de sistema são descartados ao nível do armazenamento. A retenção segue os valores predefinidos da spec (tells 365 dias, canais de conversas próprias 90 dias).</value> <value>Só as tuas próprias conversas são armazenadas: tells, grupo, FC, linkshells, cross-world linkshells, aliança e ExtraChat. O chat público, os diálogos de NPC e o spam de sistema são descartados ao nível do armazenamento. A retenção segue os valores predefinidos da spec (tells 365 dias, canais de conversas próprias 90 dias).</value>
@@ -214,7 +214,13 @@
<value>Bem-vindo ao Hellion Chat</value> <value>Bem-vindo ao Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Um fork do Chat 2 da Hellion Forge com predefinições com privacidade em mente, visuais consistentes com a marca e alguns retoques de qualidade de vida.</value> <value>A tua janela de conversa, da Hellion Forge. Privacidade desde o primeiro arranque, 25 idiomas, e organizas tudo como quiseres.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>O Hellion Chat começou como um fork do Chat 2. Desde então os dois afastaram-se ao ponto de as bases de código já não serem compatíveis.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Os plugins são uma zona cinzenta no Final Fantasy XIV: os termos de utilização da Square Enix não os abrangem e Naoki Yoshida pediu publicamente que não fossem promovidos. Mantém, portanto, o assunto fora de Say, Yell, Shout e de qualquer outro canal público.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Três passos rápidos. Podes mudar tudo depois em Definições → Hellion Chat.</value> <value>Três passos rápidos. Podes mudar tudo depois em Definições → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>O que fica armazenado?</value> <value>O que fica armazenado?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = recomendado para a maioria dos jogadores.</value> <value>Recomendado</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(sem alterações)</value> <value>(sem alterações)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Experimenta: escreve /tell &lt;Nome do Jogador&gt; no chat. O Hellion Chat abre um separador dedicado para a conversa e pré-carrega as últimas {0} mensagens.</value> <value>Experimenta: escreve /tell &lt;Nome do Jogador&gt; no chat. O Hellion Chat abre um separador dedicado para a conversa e pré-carrega as últimas {0} mensagens.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Definições → Hellion Chat para ajustar mais tarde</value> <value>Definições → Hellion Chat para ajustar mais tarde</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Sistema</value> <value>Sistema</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Choose a starting profile. You can adjust everything later under Settings → Privacy.</value> <value>Choose a starting profile. You can adjust everything later under Settings → Privacy.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Data minimisation (recommended)</value> <value>Data minimisation</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Only your own conversations are stored: tells, party, FC, linkshells, cross-world linkshells, alliance, and ExtraChat. Public chat, NPC dialogues, and system spam are discarded at the storage level. Retention follows spec defaults (tells 365 days, own conversation channels 90 days).</value> <value>Only your own conversations are stored: tells, party, FC, linkshells, cross-world linkshells, alliance, and ExtraChat. Public chat, NPC dialogues, and system spam are discarded at the storage level. Retention follows spec defaults (tells 365 days, own conversation channels 90 days).</value>
@@ -214,7 +214,13 @@
<value>Welcome to Hellion Chat</value> <value>Welcome to Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>A Chat 2 fork from Hellion Forge with privacy-aware defaults, brand-consistent visuals, and a few quality-of-life touches.</value> <value>Your chat window, from Hellion Forge. Privacy-first out of the box, translated into 25 languages, and yours to arrange.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Plugins are a grey area in Final Fantasy XIV: Square Enix's terms of service do not cover them, and Naoki Yoshida has publicly asked that people not advertise them. Keep the subject out of Say, Yell, Shout and every other public channel.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat started out as a fork of Chat 2. The two have drifted far enough apart since that the codebases are no longer compatible.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Three short steps. You can change everything later under Settings → Hellion Chat.</value> <value>Three short steps. You can change everything later under Settings → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>What gets stored?</value> <value>What gets stored?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = recommended for most players.</value> <value>Recommended</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(unchanged)</value> <value>(unchanged)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Try it: type /tell &lt;Player Name&gt; into chat. Hellion Chat opens a dedicated tab for the conversation and preloads the last {0} messages.</value> <value>Try it: type /tell &lt;Player Name&gt; into chat. Hellion Chat opens a dedicated tab for the conversation and preloads the last {0} messages.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Settings → Hellion Chat to fine-tune later</value> <value>Settings → Hellion Chat to fine-tune later</value>
@@ -598,6 +604,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Alege un profil de start. Poți ajusta orice mai târziu din Setări → Confidențialitate.</value> <value>Alege un profil de start. Poți ajusta orice mai târziu din Setări → Confidențialitate.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Minimizarea datelor (recomandat)</value> <value>Minimizarea datelor</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Sunt stocate doar propriile tale conversații: tells, party, FC, linkshells, cross-world linkshells, alliance și ExtraChat. Chatul public, dialogurile NPC și spam-ul de sistem sunt respinse la nivelul stocării. Retenția urmează implicite spec (tells 365 zile, canale de conversație proprii 90 zile).</value> <value>Sunt stocate doar propriile tale conversații: tells, party, FC, linkshells, cross-world linkshells, alliance și ExtraChat. Chatul public, dialogurile NPC și spam-ul de sistem sunt respinse la nivelul stocării. Retenția urmează implicite spec (tells 365 zile, canale de conversație proprii 90 zile).</value>
@@ -214,7 +214,13 @@
<value>Bun venit în Hellion Chat</value> <value>Bun venit în Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Un fork Chat 2 de la Hellion Forge cu setări implicite orientate spre confidențialitate, aspect consecvent cu brandul și câteva îmbunătățiri de calitate a vieții.</value> <value>Fereastra ta de chat, de la Hellion Forge. Confidențialitate din start, 25 de limbi, iar aranjarea îți aparține.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat a pornit ca un fork al Chat 2. De atunci cele două s-au îndepărtat suficient încât bazele de cod nu mai sunt compatibile.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Pluginurile sunt o zonă gri în Final Fantasy XIV: termenii de utilizare Square Enix nu le acoperă, iar Naoki Yoshida a cerut public să nu fie promovate. Ține deci subiectul departe de Say, Yell, Shout și de orice alt canal public.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Trei pași scurți. Poți schimba orice mai târziu din Setări → Hellion Chat.</value> <value>Trei pași scurți. Poți schimba orice mai târziu din Setări → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Ce se stochează?</value> <value>Ce se stochează?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = recomandat pentru cei mai mulți jucători.</value> <value>Recomandat</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(nemodificat)</value> <value>(nemodificat)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Încearcă: tastează /tell &lt;Nume Jucător&gt; în chat. Hellion Chat deschide un tab dedicat pentru conversație și preîncarcă ultimele {0} mesaje.</value> <value>Încearcă: tastează /tell &lt;Nume Jucător&gt; în chat. Hellion Chat deschide un tab dedicat pentru conversație și preîncarcă ultimele {0} mesaje.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Setări → Hellion Chat pentru ajustări ulterioare</value> <value>Setări → Hellion Chat pentru ajustări ulterioare</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Выберите начальный профиль. Всё можно изменить позже в разделе Настройки → Конфиденциальность.</value> <value>Выберите начальный профиль. Всё можно изменить позже в разделе Настройки → Конфиденциальность.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Минимизация данных (рекомендуется)</value> <value>Минимизация данных</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Сохраняются только ваши собственные разговоры: ЛС, группа, свободная компания, Linkshells, межмировые Linkshells, альянс и ExtraChat. Публичный чат, диалоги NPC и системный спам отбрасываются на уровне хранения. Сроки хранения соответствуют значениям по умолчанию (ЛС — 365 дней, собственные каналы разговора — 90 дней).</value> <value>Сохраняются только ваши собственные разговоры: ЛС, группа, свободная компания, Linkshells, межмировые Linkshells, альянс и ExtraChat. Публичный чат, диалоги NPC и системный спам отбрасываются на уровне хранения. Сроки хранения соответствуют значениям по умолчанию (ЛС — 365 дней, собственные каналы разговора — 90 дней).</value>
@@ -214,7 +214,13 @@
<value>Добро пожаловать в Hellion Chat</value> <value>Добро пожаловать в Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Форк Chat 2 от Hellion Forge с настройками конфиденциальности по умолчанию, фирменным оформлением и рядом улучшений удобства использования.</value> <value>Твоё окно чата от Hellion Forge. Приватность по умолчанию, 25 языков, и раскладка полностью в твоих руках.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat начинался как форк Chat 2. С тех пор проекты разошлись настолько, что кодовые базы больше не совместимы.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Плагины в Final Fantasy XIV находятся в серой зоне: условия использования Square Enix их не охватывают, а Наоки Ёсида публично просил не рекламировать их. Не поднимай эту тему в каналах Сказать, Вопль, Крик и любых других публичных.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Три коротких шага. Всё можно изменить позже в разделе Настройки → Hellion Chat.</value> <value>Три коротких шага. Всё можно изменить позже в разделе Настройки → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Что будет сохраняться?</value> <value>Что будет сохраняться?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = рекомендуется для большинства игроков.</value> <value>Рекомендовано</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(без изменений)</value> <value>(без изменений)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Попробуйте: введите /tell &lt;Имя игрока&gt; в чате. Hellion Chat откроет отдельную вкладку для разговора и предзагрузит последние {0} сообщений.</value> <value>Попробуйте: введите /tell &lt;Имя игрока&gt; в чате. Hellion Chat откроет отдельную вкладку для разговора и предзагрузит последние {0} сообщений.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Настройки → Hellion Chat для тонкой настройки позже</value> <value>Настройки → Hellion Chat для тонкой настройки позже</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Система</value> <value>Система</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Эмоции</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Свободная компания</value> <value>Свободная компания</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Välj en startprofil. Du kan justera allt senare under Inställningar → Sekretess.</value> <value>Välj en startprofil. Du kan justera allt senare under Inställningar → Sekretess.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Dataminimering (rekommenderas)</value> <value>Dataminimering</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Bara dina egna konversationer sparas: tells, grupp, FC, linkshells, flervärlds-linkshells, allians och ExtraChat. Offentlig chatt, NPC-dialoger och systemskräp kasseras på lagringsnivå. Lagring följer spec-standarder (tells 365 dagar, egna konversationskanaler 90 dagar).</value> <value>Bara dina egna konversationer sparas: tells, grupp, FC, linkshells, flervärlds-linkshells, allians och ExtraChat. Offentlig chatt, NPC-dialoger och systemskräp kasseras på lagringsnivå. Lagring följer spec-standarder (tells 365 dagar, egna konversationskanaler 90 dagar).</value>
@@ -214,7 +214,13 @@
<value>Välkommen till Hellion Chat</value> <value>Välkommen till Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>En Chat 2-fork från Hellion Forge med sekretessmedvetna standardinställningar, konsekvent varumärkesutseende och några livskvalitetsförbättringar.</value> <value>Ditt chattfönster från Hellion Forge. Integritet från första start, 25 språk, och du ställer in det som du vill.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat började som en fork av Chat 2. De två har sedan dess glidit isär så mycket att kodbaserna inte längre är kompatibla.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Plugins är en gråzon i Final Fantasy XIV: Square Enix användarvillkor täcker dem inte, och Naoki Yoshida har offentligt bett om att man inte gör reklam för dem. Håll därför ämnet borta från Säg, Skrik, Ropa och alla andra offentliga kanaler.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Tre korta steg. Du kan ändra allt senare under Inställningar → Hellion Chat.</value> <value>Tre korta steg. Du kan ändra allt senare under Inställningar → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Vad sparas?</value> <value>Vad sparas?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = rekommenderas för de flesta spelare.</value> <value>Rekommenderad</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(oförändrat)</value> <value>(oförändrat)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Prova: skriv /tell &lt;Spelarnamn&gt; i chatten. Hellion Chat öppnar en dedikerad flik för konversationen och förladdar de senaste {0} meddelandena.</value> <value>Prova: skriv /tell &lt;Spelarnamn&gt; i chatten. Hellion Chat öppnar en dedikerad flik för konversationen och förladdar de senaste {0} meddelandena.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Inställningar → Hellion Chat för att finjustera senare</value> <value>Inställningar → Hellion Chat för att finjustera senare</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>System</value> <value>System</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Bir başlangıç profili seç. Her şeyi daha sonra Ayarlar → Gizlilik altında değiştirebilirsin.</value> <value>Bir başlangıç profili seç. Her şeyi daha sonra Ayarlar → Gizlilik altında değiştirebilirsin.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Veri minimizasyonu (önerilen)</value> <value>Veri minimizasyonu</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Yalnızca kendi konuşmaların saklanır: tell'ler, parti, FC, linkshell'ler, cross-world linkshell'ler, alliance ve ExtraChat. Genel sohbet, NPC diyalogları ve sistem spam'i depolama düzeyinde atılır. Saklama süresi spec varsayılanlarına göre ayarlanır (tell'ler 365 gün, kendi konuşma kanalları 90 gün).</value> <value>Yalnızca kendi konuşmaların saklanır: tell'ler, parti, FC, linkshell'ler, cross-world linkshell'ler, alliance ve ExtraChat. Genel sohbet, NPC diyalogları ve sistem spam'i depolama düzeyinde atılır. Saklama süresi spec varsayılanlarına göre ayarlanır (tell'ler 365 gün, kendi konuşma kanalları 90 gün).</value>
@@ -214,7 +214,13 @@
<value>Hellion Chat'e hoş geldin</value> <value>Hellion Chat'e hoş geldin</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Hellion Forge'dan gizlilik odaklı varsayılanlar, marka tutarlı görsel tasarım ve birkaç kullanım kolaylığı dokunuşuyla Chat 2'nin bir fork'u.</value> <value>Hellion Forge'un sohbet penceresi. İlk açılıştan itibaren gizlilik öncelikli, 25 dilde, ve düzenini kendin kurarsın.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat, Chat 2'nin bir fork'u olarak başladı. İkisi o zamandan beri kod tabanları artık uyumlu olmayacak kadar birbirinden uzaklaştı.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Pluginler Final Fantasy XIV'te gri bir alandadır: Square Enix'in kullanım koşulları onları kapsamaz ve Naoki Yoshida bunların tanıtılmamasını açıkça rica etmiştir. Bu yüzden konuyu Say, Yell, Shout ve diğer tüm herkese açık kanalların dışında tut.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Üç kısa adım. Her şeyi daha sonra Ayarlar → Hellion Chat altında değiştirebilirsin.</value> <value>Üç kısa adım. Her şeyi daha sonra Ayarlar → Hellion Chat altında değiştirebilirsin.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Ne saklanacak?</value> <value>Ne saklanacak?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = çoğu oyuncu için önerilen.</value> <value>Önerilen</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(değiştirilmedi)</value> <value>(değiştirilmedi)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Dene: sohbete /tell &lt;Oyuncu Adı&gt; yaz. Hellion Chat konuşma için özel bir sekme açar ve son {0} mesajı önceden yükler.</value> <value>Dene: sohbete /tell &lt;Oyuncu Adı&gt; yaz. Hellion Chat konuşma için özel bir sekme açar ve son {0} mesajı önceden yükler.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Daha sonra ince ayar için Ayarlar → Hellion Chat</value> <value>Daha sonra ince ayar için Ayarlar → Hellion Chat</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Sistem</value> <value>Sistem</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Emote'lar</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
+14 -5
View File
@@ -172,7 +172,7 @@
<value>Виберіть початковий профіль. Все можна налаштувати пізніше в розділі Налаштування → Конфіденційність.</value> <value>Виберіть початковий профіль. Все можна налаштувати пізніше в розділі Налаштування → Конфіденційність.</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>Мінімізація даних (рекомендовано)</value> <value>Мінімізація даних</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>Зберігаються лише Ваші власні розмови: tells, група, FC, linkshells, cross-world linkshells, альянс і ExtraChat. Публічний чат, діалоги NPC та системний спам відкидаються на рівні зберігання. Термін зберігання за стандартними значеннями специфікації (tells — 365 днів, канали власних розмов — 90 днів).</value> <value>Зберігаються лише Ваші власні розмови: tells, група, FC, linkshells, cross-world linkshells, альянс і ExtraChat. Публічний чат, діалоги NPC та системний спам відкидаються на рівні зберігання. Термін зберігання за стандартними значеннями специфікації (tells — 365 днів, канали власних розмов — 90 днів).</value>
@@ -214,7 +214,13 @@
<value>Ласкаво просимо до Hellion Chat</value> <value>Ласкаво просимо до Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Форк Chat 2 від Hellion Forge з урахуванням конфіденційності, фірмовим оформленням та кількома зручними покращеннями.</value> <value>Твоє вікно чату від Hellion Forge. Приватність за замовчуванням, 25 мов, і розкладка цілком у твоїх руках.</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat починався як форк Chat 2. Відтоді проєкти розійшлися настільки, що кодові бази більше не сумісні.</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>Плагіни у Final Fantasy XIV перебувають у сірій зоні: умови використання Square Enix їх не охоплюють, а Наокі Йосіда публічно просив не рекламувати їх. Не піднімай цю тему в каналах Say, Yell, Shout та будь-яких інших публічних.</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>Три коротких кроки. Все можна змінити пізніше в розділі Налаштування → Hellion Chat.</value> <value>Три коротких кроки. Все можна змінити пізніше в розділі Налаштування → Hellion Chat.</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>Що зберігається?</value> <value>Що зберігається?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = рекомендовано для більшості гравців.</value> <value>Рекомендовано</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(без змін)</value> <value>(без змін)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 Спробуйте: введіть /tell &lt;Ім'я гравця&gt; у чат. Hellion Chat відкриє окрему вкладку для розмови й попередньо завантажить останні {0} повідомлень.</value> <value>Спробуйте: введіть /tell &lt;Ім'я гравця&gt; у чат. Hellion Chat відкриє окрему вкладку для розмови й попередньо завантажить останні {0} повідомлень.</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>Налаштування → Hellion Chat для подальшого тонкого налаштування</value> <value>Налаштування → Hellion Chat для подальшого тонкого налаштування</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>Система</value> <value>Система</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>Емоти</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>Free Company</value> <value>Free Company</value>
</data> </data>
@@ -172,7 +172,7 @@
<value>选择一个初始配置方案。之后可在设置 → 隐私中随时调整。</value> <value>选择一个初始配置方案。之后可在设置 → 隐私中随时调整。</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>数据最小化(推荐)</value> <value>数据最小化</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>仅保存你自己的对话:密语、小队、部队、通讯贝、跨服通讯贝、团队以及 ExtraChat。公共聊天、NPC 对话和系统垃圾信息将在存储层直接丢弃。保留期限遵循规格默认值(密语 365 天,自有对话频道 90 天)。</value> <value>仅保存你自己的对话:密语、小队、部队、通讯贝、跨服通讯贝、团队以及 ExtraChat。公共聊天、NPC 对话和系统垃圾信息将在存储层直接丢弃。保留期限遵循规格默认值(密语 365 天,自有对话频道 90 天)。</value>
@@ -214,7 +214,13 @@
<value>欢迎使用 Hellion Chat</value> <value>欢迎使用 Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>来自 Hellion Forge 的 Chat 2 分支,具备隐私友好的默认设置、品牌一致的视觉风格,以及若干实用改进。</value> <value>来自 Hellion Forge 的聊天窗口。默认即注重隐私,支持 25 种语言,界面由你自己安排。</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat 最初是 Chat 2 的分支。此后两者已相去甚远,代码库不再兼容。</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>插件在《最终幻想14》中处于灰色地带:史克威尔艾尼克斯的使用条款并未涵盖插件,吉田直树也曾公开呼吁不要宣传。请不要在说话、呼喊、喊话以及其他任何公开频道谈论此事。</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>共三个简短步骤。之后可在设置 → Hellion Chat 中随时修改。</value> <value>共三个简短步骤。之后可在设置 → Hellion Chat 中随时修改。</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>保存哪些内容?</value> <value>保存哪些内容?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = 推荐大多数玩家使用。</value> <value>推荐</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>Roleplay</value> <value>Roleplay</value>
@@ -298,7 +304,7 @@
<value>(未更改)</value> <value>(未更改)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 试一试:在聊天框输入 /tell &lt;玩家名称&gt;。Hellion Chat 会自动为该对话开启专属标签页,并预加载最近 {0} 条消息。</value> <value>试一试:在聊天框输入 /tell &lt;玩家名称&gt;。Hellion Chat 会自动为该对话开启专属标签页,并预加载最近 {0} 条消息。</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>进入设置 → Hellion Chat 可进一步微调</value> <value>进入设置 → Hellion Chat 可进一步微调</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>系统</value> <value>系统</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>情感动作</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>部队</value> <value>部队</value>
</data> </data>
@@ -172,7 +172,7 @@
<value>選擇一個起始設定檔。之後可在設定 → 隱私中調整所有選項。</value> <value>選擇一個起始設定檔。之後可在設定 → 隱私中調整所有選項。</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Heading" xml:space="preserve">
<value>資料最小化(推薦)</value> <value>資料最小化</value>
</data> </data>
<data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve"> <data name="Wizard_Profile_PrivacyFirst_Description" xml:space="preserve">
<value>只儲存你自己的對話:悄悄話、小隊、部隊、通訊貝、跨服通訊貝、團隊和 ExtraChat。公開聊天、NPC 對話和系統垃圾訊息在儲存層即被丟棄。保留期限遵循規格預設值(悄悄話 365 天,自己的對話頻道 90 天)。</value> <value>只儲存你自己的對話:悄悄話、小隊、部隊、通訊貝、跨服通訊貝、團隊和 ExtraChat。公開聊天、NPC 對話和系統垃圾訊息在儲存層即被丟棄。保留期限遵循規格預設值(悄悄話 365 天,自己的對話頻道 90 天)。</value>
@@ -214,7 +214,13 @@
<value>歡迎使用 Hellion Chat</value> <value>歡迎使用 Hellion Chat</value>
</data> </data>
<data name="Wizard_Step1_Subtitle" xml:space="preserve"> <data name="Wizard_Step1_Subtitle" xml:space="preserve">
<value>Hellion Forge 推出的 Chat 2 分支版本,具備重視隱私的預設值、品牌一致的視覺設計以及一些生活品質改善。</value> <value>來自 Hellion Forge 的聊天視窗。預設即重視隱私,支援 25 種語言,版面由你自己安排。</value>
</data>
<data name="Wizard_Step1_Heritage" xml:space="preserve">
<value>Hellion Chat 最初是 Chat 2 的分支。此後兩者已相去甚遠,程式碼庫不再相容。</value>
</data>
<data name="Wizard_Step1_PluginNotice" xml:space="preserve">
<value>外掛在《Final Fantasy XIV》中屬於灰色地帶:史克威爾艾尼克斯的使用條款並未涵蓋,吉田直樹也曾公開呼籲不要宣傳。請勿在説話、呼喊、喊話以及其他任何公開頻道談論此事。</value>
</data> </data>
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve"> <data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
<value>共三個簡短步驟。之後可在設定 → Hellion Chat 中變更所有設定。</value> <value>共三個簡短步驟。之後可在設定 → Hellion Chat 中變更所有設定。</value>
@@ -228,8 +234,8 @@
<data name="Wizard_Step2_Title" xml:space="preserve"> <data name="Wizard_Step2_Title" xml:space="preserve">
<value>哪些內容會被儲存?</value> <value>哪些內容會被儲存?</value>
</data> </data>
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve"> <data name="Wizard_Profile_Recommended_Badge" xml:space="preserve">
<value>★ = 推薦給大多數玩家。</value> <value>推薦</value>
</data> </data>
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve"> <data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
<value>角色扮演</value> <value>角色扮演</value>
@@ -298,7 +304,7 @@
<value>(未變更)</value> <value>(未變更)</value>
</data> </data>
<data name="Wizard_Step4_TestHint" xml:space="preserve"> <data name="Wizard_Step4_TestHint" xml:space="preserve">
<value>💡 試試看:在聊天中輸入 /tell &lt;玩家名稱&gt;。Hellion Chat 會為此對話開啟專屬標籤頁,並預載最後 {0} 則訊息。</value> <value>試試看:在聊天中輸入 /tell &lt;玩家名稱&gt;。Hellion Chat 會為此對話開啟專屬標籤頁,並預載最後 {0} 則訊息。</value>
</data> </data>
<data name="Wizard_Step4_SettingsHint" xml:space="preserve"> <data name="Wizard_Step4_SettingsHint" xml:space="preserve">
<value>設定 → Hellion Chat 可在之後進行細部調整</value> <value>設定 → Hellion Chat 可在之後進行細部調整</value>
@@ -605,6 +611,9 @@
<data name="Tabs_Presets_System" xml:space="preserve"> <data name="Tabs_Presets_System" xml:space="preserve">
<value>系統</value> <value>系統</value>
</data> </data>
<data name="Tabs_Presets_Emote" xml:space="preserve">
<value>情感動作</value>
</data>
<data name="Tabs_Presets_FreeCompany" xml:space="preserve"> <data name="Tabs_Presets_FreeCompany" xml:space="preserve">
<value>部隊</value> <value>部隊</value>
</data> </data>
+1 -1
View File
@@ -731,7 +731,7 @@
<value>Decir</value> <value>Decir</value>
</data> </data>
<data name="ChatType_Shout"> <data name="ChatType_Shout">
<value>Shout</value> <value>Vociferar</value>
</data> </data>
<data name="ChatType_TellOutgoing"> <data name="ChatType_TellOutgoing">
<value>Tell (saliente)</value> <value>Tell (saliente)</value>
+2 -2
View File
@@ -5,7 +5,7 @@ using HellionChat.Util;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B2: behavioural check that the card path feeds CardClipPlanner AND that a // Behavioural check that the card path feeds CardClipPlanner AND that a
// layout change clears the height cache — not a non-null check. Pure plan math // layout change clears the height cache — not a non-null check. Pure plan math
// is pinned headless by CardClipPlanTests; this drives the live accessors. // is pinned headless by CardClipPlanTests; this drives the live accessors.
internal sealed class CardClipPlanStep : ISelfTestStep internal sealed class CardClipPlanStep : ISelfTestStep
@@ -56,7 +56,7 @@ internal sealed class CardClipPlanStep : ISelfTestStep
int remaining; int remaining;
try try
{ {
// v1.10.0/A1: the fingerprint gate waits for the value to settle, so // v1.10.0: the fingerprint gate waits for the value to settle, so
// the step walks a synthetic clock past the window instead of sleeping. // the step walks a synthetic clock past the window instead of sleeping.
var clock = Environment.TickCount64; var clock = Environment.TickCount64;
messages.RunHeightCacheInvalidationForSelfTest(tab, 400f, clock); messages.RunHeightCacheInvalidationForSelfTest(tab, 400f, clock);
@@ -4,24 +4,37 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// Pins the post-migration shape of the config. By /xlperf time the schema gate // Pins the post-migration shape of the config. By /xlperf time the schema gate
// has already stamped Config.Version and run both migrations, so the fields // has stamped Config.Version and the 2.0.0 reset has run, so the fields below
// below must carry valid values here. This probe never rewrites config; the // must carry valid values here. This probe never rewrites config; the reset
// migrations themselves are load-time and verified by the prepared-config smoke // itself is load-time.
// in the plan. internal sealed class ConfigMigrationV27Step : ISelfTestStep
internal sealed class ConfigMigrationV26Step : ISelfTestStep
{ {
public ConfigMigrationV26Step(Plugin plugin) public ConfigMigrationV27Step(Plugin plugin)
{ {
_ = plugin; _ = plugin;
} }
public string Name => "Hellion Chat - Config v26 migration"; public string Name => "Hellion Chat - Config v27 migration";
public SelfTestStepResult RunStep() public SelfTestStepResult RunStep()
{ {
if (Plugin.Config.Version != 26) if (Plugin.Config.Version != 27)
{ {
ImGui.Text($"Config.Version is {Plugin.Config.Version}, expected 26"); ImGui.Text($"Config.Version is {Plugin.Config.Version}, expected 27");
return SelfTestStepResult.Fail;
}
// v27 replaces the config rather than migrating it, and CreateFresh
// hands back an empty tab list. The seeding in LoadAsync is what fills
// it, and it runs in a different method than the reset does -- if that
// ordering ever breaks, every user comes out of the update with no tabs
// at all and nothing else in the plugin would notice.
if (Plugin.Config.Tabs.Count == 0)
{
ImGui.Text(
"No tabs at all. The v27 reset empties the list and LoadAsync is what "
+ "seeds the presets back -- reaching this point empty means it did not."
);
return SelfTestStepResult.Fail; return SelfTestStepResult.Fail;
} }
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// F2: CurrentTab is coupled to MainWindow.ActiveTab (no longer the fixed index-0 // CurrentTab is coupled to MainWindow.ActiveTab (no longer the fixed index-0
// Tabs lookup). Asserts ReferenceEquals between the two, with false-green // Tabs lookup). Asserts ReferenceEquals between the two, with false-green
// defenses: (1) empty-config exercises the getter's fallback; (2) null ActiveTab // defenses: (1) empty-config exercises the getter's fallback; (2) null ActiveTab
// opens the window so the Draw-seed sets it and retries via Waiting (bounded so a // opens the window so the Draw-seed sets it and retries via Waiting (bounded so a
@@ -5,17 +5,17 @@ using HellionChat.GameFunctions.Types;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// F2 (guided): interactive, fires NO synthetic probes. Shows the full measured // Guided: interactive, fires NO synthetic probes. Shows the full measured
// state every frame so a result is observable, not a guess, and walks the user // state every frame so a result is observable, not a guess, and walks the user
// through the real switch-away-and-back flow. It verifies the PRIVACY-relevant // through the real switch-away-and-back flow. It verifies the PRIVACY-relevant
// effect, keyed on the tab type: // effect, keyed on the tab type:
// - a NORMAL tab carrying a game-side tell must lose its RUNTIME target // - a NORMAL tab carrying a game-side tell must lose its RUNTIME target
// (CurrentChannel.TellTarget) on switch-away-and-back (the F1 strip), so a // (CurrentChannel.TellTarget) on switch-away-and-back, so a
// typed line can't /tell the old partner; // typed line can't /tell the old partner;
// - a BOUND auto-tell tab keeps its partner by design (leg1) — its binding is // - a BOUND auto-tell tab keeps its partner by design (leg1) — its binding is
// Tab.TellTarget and is deliberately untouched by the strip. // Tab.TellTarget and is deliberately untouched by the strip.
// The channel label is intentionally NOT asserted: a tell tab re-derives back to // The channel label is intentionally NOT asserted: a tell tab re-derives back to
// Tell after the strip (spec TR-7); only the target matters for privacy. // Tell after the strip ; only the target matters for privacy.
internal sealed class CurrentTabGuidedStep : ISelfTestStep internal sealed class CurrentTabGuidedStep : ISelfTestStep
{ {
private readonly Plugin _plugin; private readonly Plugin _plugin;
+1 -1
View File
@@ -5,7 +5,7 @@ using HellionChat._Helpers;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B2-3: proves the plugin-disclosure arm-and-hold wires the (otherwise verwaist) // Proves the plugin-disclosure arm-and-hold wires the (otherwise verwaist)
// scanner into the REAL send entry InputBar.TrySend. Drives TrySend via the // scanner into the REAL send entry InputBar.TrySend. Drives TrySend via the
// arm-test-hook with a PUA glyph in the buffer and NotifyPluginDisclosure on: // arm-test-hook with a PUA glyph in the buffer and NotifyPluginDisclosure on:
// the first send must ARM and HOLD (no send), so PendingMessage stays the probe // the first send must ARM and HOLD (no send), so PendingMessage stays the probe
+1 -1
View File
@@ -7,7 +7,7 @@ using HellionChat.Util;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// v1.12.0/A2: the exporter now reads text from the chunk lists instead of the // v1.12.0: the exporter now reads text from the chunk lists instead of the
// raw SeStrings. That change is invisible to the build suite -- ExportToFile // raw SeStrings. That change is invisible to the build suite -- ExportToFile
// takes IEnumerable<Message>, Message needs SeString, and xUnit cannot load // takes IEnumerable<Message>, Message needs SeString, and xUnit cannot load
// Dalamud.dll, so even an empty list fails before the body runs. // Dalamud.dll, so even an empty list fails before the body runs.
@@ -112,7 +112,7 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
return SelfTestStepResult.Fail; return SelfTestStepResult.Fail;
} }
// B1: assert the atlas actually finished building all required handles, // Assert the atlas actually finished building all required handles,
// not just that the references are non-null. FontsReady is the observable // not just that the references are non-null. FontsReady is the observable
// state the trimmed-fallback rebuild must still reach; a half-built atlas // state the trimmed-fallback rebuild must still reach; a half-built atlas
// would pass the null/exception checks above but fail here. // would pass the null/exception checks above but fail here.
@@ -127,8 +127,8 @@ internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
return SelfTestStepResult.Fail; return SelfTestStepResult.Fail;
} }
// Report what was actually verified (Flo's request: don't just show Pass). // Report what was actually verified rather than a bare Pass.
// The glyph-range entry counts make the B1 dedup visible — the cjk-fallback // The glyph-range entry counts make the dedup visible — the cjk-fallback
// range is now a small trimmed remainder next to the large primary range. // range is now a small trimmed remainder next to the large primary range.
var counts = fm.GlyphRangeLengths; var counts = fm.GlyphRangeLengths;
var italicState = var italicState =
@@ -43,7 +43,7 @@ internal sealed class GlobalStyleScopeAllocStep : ISelfTestStep
GlobalStyleScope.Push(theme, registry, opacity).Dispose(); GlobalStyleScope.Push(theme, registry, opacity).Dispose();
var delta = GC.GetAllocatedBytesForCurrentThread() - before; var delta = GC.GetAllocatedBytesForCurrentThread() - before;
// Report the measured figure on BOTH outcomes (Flo's request: don't just // Report the measured figure on BOTH outcomes (a bare Pass hides
// show Pass) — the byte delta is the whole point of the GC-reserve probe. // show Pass) — the byte delta is the whole point of the GC-reserve probe.
var ok = delta <= AllocBudgetBytes; var ok = delta <= AllocBudgetBytes;
var status = ok ? "PASS" : "FAIL"; var status = ok ? "PASS" : "FAIL";
@@ -4,7 +4,7 @@ using HellionChat.Ui.Windows;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// P8 wiring: UserHide() suppresses DrawConditions; both ActivateChat() (Enter) and // wiring: UserHide() suppresses DrawConditions; both ActivateChat() (Enter) and
// Toggle() (/hellion) restore it. Pure window-state — the focus side is left to smoke. // Toggle() (/hellion) restore it. Pure window-state — the focus side is left to smoke.
internal sealed class HideRestoreSelfTestStep : ISelfTestStep internal sealed class HideRestoreSelfTestStep : ISelfTestStep
{ {
@@ -68,7 +68,7 @@ internal sealed class HonorificHeaderRenderStep : ISelfTestStep
); );
// Draw at a deliberately wide 420px so the title never hits the truncation // Draw at a deliberately wide 420px so the title never hits the truncation
// clamp — LastTitleRendered then reflects the GATE outcome, not the width. // clamp -- LastTitleRendered then reflects the gate outcome, not the width.
try try
{ {
// (a) available + valid title + toggle on -> title renders // (a) available + valid title + toggle on -> title renders
@@ -4,7 +4,7 @@ using HellionChat.Ui.StyleEngine;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// Master-spec §7.5 scope note: the hover registry must not grow frame by frame. // the hover registry must not grow frame by frame.
// Successor to HoverSheenAllocStep, which pinned the same contract against the // Successor to HoverSheenAllocStep, which pinned the same contract against the
// old sheen start-timestamp dictionary. // old sheen start-timestamp dictionary.
// //
+4 -4
View File
@@ -4,11 +4,11 @@ using HellionChat.Ui.Windows;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B1-2 window flags. Drives the REAL MainWindow.PreDraw and asserts it wired // window flags. Drives the REAL MainWindow.PreDraw and asserts it wired
// Window.Flags to ResolveFlags(CanMove, CanResize), then pins the pure // Window.Flags to ResolveFlags(CanMove, CanResize), then pins the pure
// fresh-base contract: false/false adds NoMove|NoResize, true/true clears them // fresh-base contract: false/false adds NoMove|NoResize, true/true clears them
// (the masterplan's "flags must rebuild from a fresh base, else NoMove sticks // -- flags must rebuild from a fresh base, or NoMove sticks after toggling
// after toggling back" risk). NoScrollbar|NoScrollWithMouse always present. // back. NoScrollbar|NoScrollWithMouse always present.
// Non-test caller of ResolveFlags: MainWindow.PreDraw. // Non-test caller of ResolveFlags: MainWindow.PreDraw.
internal sealed class MainWindowFlagsStep : ISelfTestStep internal sealed class MainWindowFlagsStep : ISelfTestStep
{ {
@@ -72,7 +72,7 @@ internal sealed class MainWindowFlagsStep : ISelfTestStep
return SelfTestStepResult.Fail; return SelfTestStepResult.Fail;
} }
// P7 title-bar contract: ShowTitleBar=false adds NoTitleBar from the // title-bar contract: ShowTitleBar=false adds NoTitleBar from the
// fresh base, true clears it (same no-accumulation guarantee). // fresh base, true clears it (same no-accumulation guarantee).
var barHidden = MainWindow.ResolveFlags(true, true, false); var barHidden = MainWindow.ResolveFlags(true, true, false);
var barShown = MainWindow.ResolveFlags(true, true, true); var barShown = MainWindow.ResolveFlags(true, true, true);
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// UI-12 focus opacity. Pins the pure ResolveBgAlpha contract (focused → // focus opacity. Pins the pure ResolveBgAlpha contract (focused →
// WindowOpacity, unfocused → WindowOpacityInactive). The PreDraw wiring // WindowOpacity, unfocused → WindowOpacityInactive). The PreDraw wiring
// (BgAlpha = ResolveBgAlpha(IsFocused) behind the main-viewport/!docked guard) // (BgAlpha = ResolveBgAlpha(IsFocused) behind the main-viewport/!docked guard)
// is NOT headless-deterministic — the guard may leave BgAlpha null when // is NOT headless-deterministic — the guard may leave BgAlpha null when
@@ -7,7 +7,7 @@ using HellionChat.Util;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B3-3: notification-sound selection. Drives the pure SelectNotificationSound // Notification-sound selection. Drives the pure SelectNotificationSound
// (the exact pick logic ProcessMessage runs per message) through its SelfTest // (the exact pick logic ProcessMessage runs per message) through its SelfTest
// wrapper with local synthetic tabs — Plugin.Config.Tabs is never touched, so // wrapper with local synthetic tabs — Plugin.Config.Tabs is never touched, so
// no real tab gains messages or unread state. The audible preview button is // no real tab gains messages or unread state. The audible preview button is
@@ -3,11 +3,11 @@ using System.IO;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// Disk sink for the B5 performance baseline. Kept separate from the SelfTest // Disk sink for the performance baseline. Kept separate from the SelfTest
// step so the per-frame hot path never references file IO. Writes one // step so the per-frame hot path never references file IO. Writes one
// perf-baseline.json into the plugin ConfigDirectory, atomically (tmp + move) // perf-baseline.json into the plugin ConfigDirectory, atomically (tmp + move)
// like ThemeRegistry's theme writer, so a mid-write crash leaves either the // like ThemeRegistry's theme writer, so a mid-write crash leaves either the
// old file or the new file, never a half JSON. Field names track §7.5: // old file or the new file, never a half JSON. Field names track the performance-baseline layout:
// steady-state Draw cost (avg/max ms), the quad-proxy draw-call count // steady-state Draw cost (avg/max ms), the quad-proxy draw-call count
// (avg/max), and frame delta (avg/max). First-frame-HITCH is read off // (avg/max), and frame delta (avg/max). First-frame-HITCH is read off
// drawMs max/avg by the human author, platform-annotated in the notes. // drawMs max/avg by the human author, platform-annotated in the notes.
@@ -6,19 +6,19 @@ namespace HellionChat.SelfTests;
// Optional metric capture. Accumulates 1000 steady-state frames of ImGui IO // Optional metric capture. Accumulates 1000 steady-state frames of ImGui IO
// counters plus the plugin's full-Draw wall-time (Plugin.LastDrawMs, B5-1), // counters plus the plugin's full-Draw wall-time (Plugin.LastDrawMs, B5-1),
// then writes a single perf-baseline.json into the plugin ConfigDirectory so // then writes a single perf-baseline.json into the plugin ConfigDirectory so
// the cycle-notes author can copy the §7.5 figures without a separate // the cycle-notes author can copy the baseline figures without a separate
// profiling harness. The step only records — it never fails on a threshold // profiling harness. The step only records — it never fails on a threshold
// (the budgets are evaluated by a human against the JSON, §7.5 "optional, // (the budgets are evaluated by a human against the JSON ("optional,
// manual"). It returns Waiting until the sample window fills, mirroring the // manual"). It returns Waiting until the sample window fills, mirroring the
// per-frame poll idiom of ThemeSwitchSelfTestStep. // per-frame poll idiom of ThemeSwitchSelfTestStep.
internal sealed class PerformanceBaselineStep : ISelfTestStep internal sealed class PerformanceBaselineStep : ISelfTestStep
{ {
// §7.5 steady-state window. 1000 frames ≈ 16s at 60fps, long enough to // Steady-state window. 1000 frames ≈ 16s at 60fps, long enough to
// average out GC blips without making the manual step tedious. // average out GC blips without making the manual step tedious.
private const int TargetFrames = 1000; private const int TargetFrames = 1000;
// Rough draw-call proxy: ImGui emits 6 indices per quad, so vertices/6 is an // Rough draw-call proxy: ImGui emits 6 indices per quad, so vertices/6 is an
// intentional under-count of draw work, not the exact quad count (API-3). // intentional under-count of draw work, not the exact quad count.
private const int VerticesPerQuadProxy = 6; private const int VerticesPerQuadProxy = 6;
private readonly Plugin _plugin; private readonly Plugin _plugin;
@@ -3,7 +3,7 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B3-5: only the snap decision is headless-testable. Scroll detection + bar + // Only the snap decision is headless-testable. Scroll detection + bar +
// hit-test are smoke-only (the scroll child exists only in-game; GetScrollY is // hit-test are smoke-only (the scroll child exists only in-game; GetScrollY is
// garbage headless). Drives ResolveSnapToBottom via the SelfTest accessor and // garbage headless). Drives ResolveSnapToBottom via the SelfTest accessor and
// asserts the OR + the request reset invariant. // asserts the OR + the request reset invariant.
@@ -5,7 +5,7 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B2-1/B2-2: proves the WorldSuffixMode/NameFormMode reformat reaches the REAL // Proves the WorldSuffixMode/NameFormMode reformat reaches the REAL
// render entry. Drives ChunkRenderer.DrawChunks (a SelfTests/README-sanctioned // render entry. Drives ChunkRenderer.DrawChunks (a SelfTests/README-sanctioned
// real entry that wires SenderNameDisplay.ForDisplay at ChunkRenderer.cs:54) // real entry that wires SenderNameDisplay.ForDisplay at ChunkRenderer.cs:54)
// with a synthetic ChunkSource.Sender chunk carrying a PlayerPayload, at a // with a synthetic ChunkSource.Sender chunk carrying a PlayerPayload, at a
@@ -4,7 +4,7 @@ using HellionChat.Code;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// v1.10.0/C3: the active row gets a surface and an accent bar, so exactly the // v1.10.0: the active row gets a surface and an accent bar, so exactly the
// row the user is on must be marked -- and only that one. Drives the real // row the user is on must be marked -- and only that one. Drives the real
// Sidebar.Draw and reads the render-observability counter, so a regression in // Sidebar.Draw and reads the render-observability counter, so a regression in
// the draw path fails rather than a parallel calculation passing. // the draw path fails rather than a parallel calculation passing.
@@ -5,7 +5,7 @@ using HellionChat.GameFunctions.Types;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B3-2: greeted glyph renders only for temp tabs when the toggle is on. Drives // Greeted glyph renders only for temp tabs when the toggle is on. Drives
// the REAL Sidebar.Draw (render precedent: HonorificHeaderRenderStep, the only // the REAL Sidebar.Draw (render precedent: HonorificHeaderRenderStep, the only
// real .Draw in this pool — NOT SidebarModeAutoSwitchStep which only calls // real .Draw in this pool — NOT SidebarModeAutoSwitchStep which only calls
// IsExpanded/GetWidth) inside the /xlperf window frame and reads the render // IsExpanded/GetWidth) inside the /xlperf window frame and reads the render
@@ -58,7 +58,7 @@ internal sealed class SidebarModeAutoSwitchStep : ISelfTestStep
return SelfTestStepResult.Fail; return SelfTestStepResult.Fail;
} }
// B1-3a: the expanded width must come from Config.SidebarWidth, not the // The expanded width must come from Config.SidebarWidth, not the
// old fixed 150 constant. Drive the REAL GetWidth (the single source // old fixed 150 constant. Drive the REAL GetWidth (the single source
// Sidebar.Draw consumes) with concrete values and assert the OBSERVED // Sidebar.Draw consumes) with concrete values and assert the OBSERVED
// effect — in-range passthrough plus clamping — instead of mirroring the // effect — in-range passthrough plus clamping — instead of mirroring the
@@ -5,7 +5,7 @@ using HellionChat.GameFunctions.Types;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B3-4: section headers render once per non-empty temp-tab pool, and compact // Section headers render once per non-empty temp-tab pool, and compact
// mode suppresses the header text (separators stay). Drives the REAL // mode suppresses the header text (separators stay). Drives the REAL
// Sidebar.Draw inside the /xlperf window frame (same render precedent as // Sidebar.Draw inside the /xlperf window frame (same render precedent as
// SidebarGreetedGlyphStep) and reads the render observability counter. // SidebarGreetedGlyphStep) and reads the render observability counter.
@@ -4,7 +4,7 @@ using HellionChat.Code;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// F3: the unread dot the v1.8.x sidebar rebuild dropped. Drives the REAL // The unread dot the v1.8.x sidebar rebuild dropped. Drives the REAL
// Sidebar.Draw (render precedent: SidebarGreetedGlyphStep) with a probe tab that // Sidebar.Draw (render precedent: SidebarGreetedGlyphStep) with a probe tab that
// is inactive and carries Unread>0, then reads the render-observability counter // is inactive and carries Unread>0, then reads the render-observability counter
// so a regressed/absent dot fails. Asserts: dot drawn for an inactive Unseen tab; // so a regressed/absent dot fails. Asserts: dot drawn for an inactive Unseen tab;
@@ -4,7 +4,7 @@ using HellionChat.Ui.Components;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// B3-1: rename must persist. Drives the real ApplyTabRename (the InputText // Rename must persist. Drives the real ApplyTabRename (the InputText
// callback path), then SaveConfig + reload from disk and asserts the new name // callback path), then SaveConfig + reload from disk and asserts the new name
// survived — a fresh-from-config tab, not the same reference (a reference check // survived — a fresh-from-config tab, not the same reference (a reference check
// would pass on a dead roundtrip). Uses a persistent (non-temp) tab: unpinned // would pass on a dead roundtrip). Uses a persistent (non-temp) tab: unpinned
@@ -7,10 +7,10 @@ using HellionChat.Util;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// F1: the activation strip. Drives the REAL OnTabActivated — the entry the // The activation strip. Drives the REAL OnTabActivated -- the entry the
// Sidebar/TopTabBar click handlers, the pop-out path and the Draw-seed all call // Sidebar/TopTabBar click handlers, the pop-out path and the Draw-seed all call
// — with local probe tabs (Plugin.Config.Tabs is never touched). Asserts the // — with local probe tabs (Plugin.Config.Tabs is never touched). Asserts the
// five contracts: strip-on-switch, no-strip-on-reclick (TR-4), leg1 preserve, // five contracts: strip-on-switch, no-strip-on-reclick, leg1 preserve,
// derive, and non-tell untouched. // derive, and non-tell untouched.
internal sealed class TellResetOnActivateStep : ISelfTestStep internal sealed class TellResetOnActivateStep : ISelfTestStep
{ {
@@ -41,7 +41,7 @@ internal sealed class TellResetOnActivateStep : ISelfTestStep
} }
// (b) re-clicking the already-active tab (previous == tab) must NOT strip // (b) re-clicking the already-active tab (previous == tab) must NOT strip
// a live game-tell conversation (TR-4 regression guard). // a live game-tell conversation (regression guard).
var reclick = MakeStaleTellTab(boundTellTarget: false, withLabel: false); var reclick = MakeStaleTellTab(boundTellTarget: false, withLabel: false);
TabLifecycleHelpers.OnTabActivated(reclick, reclick); TabLifecycleHelpers.OnTabActivated(reclick, reclick);
if (reclick.CurrentChannel.TellTarget is null) if (reclick.CurrentChannel.TellTarget is null)
@@ -4,7 +4,7 @@ using HellionChat.Themes;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// Verifies the v1.5.4 PM-1 crossfade contract: switching the active // Verifies the v1.5.4 crossfade contract: switching the active
// theme arms TryGetActiveCrossfade for ~300ms, then the registry // theme arms TryGetActiveCrossfade for ~300ms, then the registry
// returns to direct AbgrCache reads. A second switch within 100ms // returns to direct AbgrCache reads. A second switch within 100ms
// keeps the lerped path active (no identity-snap). CleanUp restores // keeps the lerped path active (no identity-snap). CleanUp restores
@@ -67,7 +67,7 @@ internal sealed class ThemeCrossfadeSelfTestStep : ISelfTestStep
// it as "saw the start" if more than 300ms have elapsed. // it as "saw the start" if more than 300ms have elapsed.
// Skip the mid-crossfade-switch phase in that case -- the // Skip the mid-crossfade-switch phase in that case -- the
// lerped path is no longer active, so a second switch would // lerped path is no longer active, so a second switch would
// re-arm a fresh crossfade and not exercise PM-1b's // re-arm a fresh crossfade and not exercise its
// mid-flight-origin override. // mid-flight-origin override.
if (Environment.TickCount64 - this.armedAtTickMs > 300) if (Environment.TickCount64 - this.armedAtTickMs > 300)
{ {
@@ -83,7 +83,7 @@ internal sealed class ThemeCrossfadeSelfTestStep : ISelfTestStep
if (!this.sawMidCrossfadeSwitch) if (!this.sawMidCrossfadeSwitch)
{ {
// PM-Test-3 mid-crossfade-switch phase: within ~100ms of the // mid-crossfade-switch phase: within ~100ms of the
// first observed crossfade, fire a second Switch to a THIRD // first observed crossfade, fire a second Switch to a THIRD
// theme. ArmCrossfade must compose the current lerped state // theme. ArmCrossfade must compose the current lerped state
// as the new origin -- TryGetActiveCrossfade still returns // as the new origin -- TryGetActiveCrossfade still returns
+1 -1
View File
@@ -4,7 +4,7 @@ using HellionChat.Code;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// v1.10.0/D1: the top-tab strip marks the active tab with a fill plus an accent // v1.10.0: the top-tab strip marks the active tab with a fill plus an accent
// underline. Drives the real TopTabBar.Draw and reads the render counter. // underline. Drives the real TopTabBar.Draw and reads the render counter.
// //
// "At most one", not "exactly one": the strip skips popped-out tabs, so zero // "At most one", not "exactly one": the strip skips popped-out tabs, so zero
+1 -1
View File
@@ -7,7 +7,7 @@ using HellionChat.Ui.StyleEngine;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// v1.13.0/A7: the type scale has no call site in the message list until block C, // v1.13.0: the type scale has no call site in the message list until block C,
// so without this step block A would end with nothing to look at and two helpers // so without this step block A would end with nothing to look at and two helpers
// (TypeScale, BaselineMath) with no caller at all. // (TypeScale, BaselineMath) with no caller at all.
// //
+7 -6
View File
@@ -3,12 +3,12 @@ using Dalamud.Plugin.SelfTest;
namespace HellionChat.SelfTests; namespace HellionChat.SelfTests;
// F3: the unread decision (MessageManager.ShouldCountUnread). Unseen suppresses // The unread decision (MessageManager.ShouldCountUnread). Unseen suppresses
// unread on an inactive tab only when the active tab ALSO shows the message (you // unread on an inactive tab only when the active tab ALSO shows the message (you
// saw it there) — 1.5.6/upstream semantics, now measured against the REAL active // saw it there) -- 1.5.6/upstream semantics, measured against the real active
// tab thanks to F2. Asserts the truth table: suppressed when active tab also // tab. Asserts the truth table: suppressed when the active tab also matches;
// matches; counts when it does not (the Carla/Jin case); All always counts; None // counts when it does not; All always counts; None counts at the increment
// counts at the increment layer (the display gate hides it). // layer (the display gate hides it).
internal sealed class UnreadDecisionStep : ISelfTestStep internal sealed class UnreadDecisionStep : ISelfTestStep
{ {
public string Name => "Hellion Chat - Unread decision (per active tab)"; public string Name => "Hellion Chat - Unread decision (per active tab)";
@@ -27,7 +27,8 @@ internal sealed class UnreadDecisionStep : ISelfTestStep
} }
// (b) inactive Unseen tab + the active tab does NOT show the message // (b) inactive Unseen tab + the active tab does NOT show the message
// (currentTabMatches=false) => counts (badge). The Carla/Jin case. // (currentTabMatches=false) => counts (badge). Two people talking in
// a channel the active tab does not carry.
if (!MessageManager.ShouldCountUnread(inactive, active, currentTabMatches: false)) if (!MessageManager.ShouldCountUnread(inactive, active, currentTabMatches: false))
{ {
ImGui.Text("(b) inactive Unseen tab must count when the active tab does not show it"); ImGui.Text("(b) inactive Unseen tab must count when the active tab does not show it");
@@ -64,7 +64,7 @@ internal sealed class WizardStateSmokeStep : ISelfTestStep
// jumps straight to Step 4 (no Step-3 entry → no seed for // jumps straight to Step 4 (no Step-3 entry → no seed for
// FilterIncludePreviousSessions), commits, and asserts the history // FilterIncludePreviousSessions), commits, and asserts the history
// toggle remained on its pre-test value. Pins the null-semantics // toggle remained on its pre-test value. Pins the null-semantics
// from Spec Z.176 so a regression in CommitPending that started // so a regression in CommitPending that started
// writing seeded recommendations unconditionally would surface // writing seeded recommendations unconditionally would surface
// here. // here.
// CommitPending → ApplyRoleplay overwrites six privacy / // CommitPending → ApplyRoleplay overwrites six privacy /
+1 -1
View File
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging;
namespace HellionChat.Services; namespace HellionChat.Services;
// Routes an incoming tell to the configured TellAutoOpenMode (Off/Sidebar/ // Routes an incoming tell to the configured TellAutoOpenMode (Off/Sidebar/
// TopTab/Popout). Decoupled from AutoTellTabsService (Flo decision 2026-06-15): // TopTab/Popout). Deliberately decoupled from AutoTellTabsService:
// that service owns tab CREATION + lifecycle; this only REVEALS/pops the tab it // that service owns tab CREATION + lifecycle; this only REVEALS/pops the tab it
// finds. Popout guards on pool.IsOpen so it never double-pops a tab the // finds. Popout guards on pool.IsOpen so it never double-pops a tab the
// AutoTellTabsOpenAsPopout path already opened. Subscribes to the resolved // AutoTellTabsOpenAsPopout path already opened. Subscribes to the resolved
+3 -3
View File
@@ -51,9 +51,9 @@ internal static class EventHorizon
ChatColors: new ThemeChatColors( ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint> new Dictionary<HellionChat.Code.ChatType, uint>
{ {
// Event Horizon — Cosmic-Purple-Drift: helle Pastelle bekommen // Cosmic purple drift: the pale pastels take a lavender tint and
// Lavender-Tinte, Akzent-Channels (Tell) ziehen Richtung Magenta- // the accent channels (tell) pull towards magenta-violet. Channel
// Lila. Channel-Identität bleibt klar erkennbar. // identity stays readable throughout.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E6E0F5"), [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E6E0F5"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F2C25C"), [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F2C25C"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF9050"), [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF9050"),
@@ -51,8 +51,8 @@ internal static class ForgeMerchantman
ChatColors: new ThemeChatColors( ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint> new Dictionary<HellionChat.Code.ChatType, uint>
{ {
// Forge Merchantman — Patina-Tinte in Party/FC, Bernstein-Tinte in // Patina tint on party and free company, amber on yell, alliance
// Yell/Alliance/CustomEmote. Channel-identity bleibt voll erhalten. // and custom emotes. Channel identity is left fully intact.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"), [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0C060"), [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0C060"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#E8902C"), [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#E8902C"),
+2 -2
View File
@@ -51,8 +51,8 @@ internal static class HellionArctic
ChatColors: new ThemeChatColors( ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint> new Dictionary<HellionChat.Code.ChatType, uint>
{ {
// Hellion Arctic — FFXIV-Standard mit dezenter Cyan-Tinte in den // The FFXIV defaults with a restrained cyan tint on the blue
// blauen Channels (Party/FC). Channel-Identität bleibt klar. // channels (party, free company). Channel identity stays clear.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"), [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFE066"), [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFE066"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"), [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
+4 -3
View File
@@ -51,9 +51,10 @@ internal static class IndigoViolet
ChatColors: new ThemeChatColors( ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint> new Dictionary<HellionChat.Code.ChatType, uint>
{ {
// Indigo Violet — Lavender-Pink-Drift in Tell und LS6/7. Türkis- // Lavender-pink drift on tell and linkshells 6 and 7, countered by
// Mint-Aurora-Counter in Party/FC und LS4. Glitter-Gold in Yell. // a turquoise-mint aurora on party, free company and linkshell 4.
// Differenzierung zu Event Horizon: dunkler, dichter, Türkis statt Gold. // Glitter gold on yell. What sets it apart from Event Horizon:
// darker, denser, and turquoise where that one goes gold.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0E6FF"), [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0E6FF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D880"), [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F0D880"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"), [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F09A60"),
+3 -3
View File
@@ -51,9 +51,9 @@ internal static class MintGrove
ChatColors: new ThemeChatColors( ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint> new Dictionary<HellionChat.Code.ChatType, uint>
{ {
// Mint Grove — Naturthemen-Tönung: Honey-Amber in Yell-Familie, // Nature-themed tint: honey amber across the yell family, a mint
// Mint-Drift in NoviceNetwork und Linkshell. Tell-Pink-Identität // drift in novice network and linkshell. Tell keeps its pink so
// bleibt erhalten für Erkennbarkeit. // the channel stays recognisable.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E8F5EA"), [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#E8F5EA"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F9D580"), [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#F9D580"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F0A050"), [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#F0A050"),
+3 -2
View File
@@ -51,8 +51,9 @@ internal static class NightBlue
ChatColors: new ThemeChatColors( ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint> new Dictionary<HellionChat.Code.ChatType, uint>
{ {
// Night Blue — Royal-Blue-Tinte in Party/FC, Bronze-Gold in Yell/ // Royal blue on party and free company, bronze gold on yell and
// Alliance. Channel-identity (Tell-Pink, NN-Lime) bleibt erhalten. // alliance. Channel identity is preserved -- tell stays pink,
// novice network stays lime.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"), [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#FFFFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFD060"), [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FFD060"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"), [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FFA040"),
@@ -51,8 +51,9 @@ internal static class SynthwaveSunset
ChatColors: new ThemeChatColors( ChatColors: new ThemeChatColors(
new Dictionary<HellionChat.Code.ChatType, uint> new Dictionary<HellionChat.Code.ChatType, uint>
{ {
// Synthwave Sunset — Magenta dominiert die warmen Channels (Yell/Shout/FC), // Magenta carries the warm channels (yell, shout, free company),
// Cyan dominiert die kühlen (Tell/Party). Neon-Akzente für Status-nahe Channels. // cyan the cool ones (tell, party). Neon accents on the status-
// adjacent channels.
[HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"), [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"),
[HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"), [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"),
[HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"), [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"),
+16 -16
View File
@@ -10,7 +10,7 @@ public sealed class ThemeRegistry
public const string DefaultSlug = HellionArctic.Slug; public const string DefaultSlug = HellionArctic.Slug;
// 1Hz throttle for the v1.4.8 B2 auto-refresh-on-active path. The // 1Hz throttle for the v1.4.8 auto-refresh-on-active path. The
// Plugin.Draw hook calls RefreshActiveIfStale every frame, but the // Plugin.Draw hook calls RefreshActiveIfStale every frame, but the
// actual File.GetLastWriteTimeUtc disk-stat only runs once per second // actual File.GetLastWriteTimeUtc disk-stat only runs once per second
// -- 60fps would otherwise mean 3600 stats/min on the same path (more // -- 60fps would otherwise mean 3600 stats/min on the same path (more
@@ -24,7 +24,7 @@ public sealed class ThemeRegistry
private readonly string? _customThemesDir; private readonly string? _customThemesDir;
private Theme _active; private Theme _active;
// v1.4.8 B2: source path of the currently active custom theme. Captured // v1.4.8: source path of the currently active custom theme. Captured
// at Switch() time so RefreshActiveIfStale does not have to reconstruct // at Switch() time so RefreshActiveIfStale does not have to reconstruct
// a filename from the slug -- custom theme filenames are not required // a filename from the slug -- custom theme filenames are not required
// to match the slug they declare in the JSON body. Null when the active // to match the slug they declare in the JSON body. Null when the active
@@ -33,7 +33,7 @@ public sealed class ThemeRegistry
private long _lastActiveStampCheckMs = -ActiveStampPollIntervalMs; private long _lastActiveStampCheckMs = -ActiveStampPollIntervalMs;
private DateTime _lastActiveStamp = DateTime.MinValue; private DateTime _lastActiveStamp = DateTime.MinValue;
// PM-1 crossfade state. Switch() captures the previous AbgrCache as a // crossfade state. Switch() captures the previous AbgrCache as a
// VALUE-COPY (not a Theme reference) -- the built-in singletons share // VALUE-COPY (not a Theme reference) -- the built-in singletons share
// their RecomputeAbgrCache identity, so a reference would mutate // their RecomputeAbgrCache identity, so a reference would mutate
// alongside the new active. _crossfadeStartTickMs == long.MinValue // alongside the new active. _crossfadeStartTickMs == long.MinValue
@@ -54,7 +54,7 @@ public sealed class ThemeRegistry
internal void SetActiveChangedCallback(Action callback) => _onActiveChanged = callback; internal void SetActiveChangedCallback(Action callback) => _onActiveChanged = callback;
// Shared slug guard for any code path that turns a slug into a filename. // Shared slug guard for any code path that turns a slug into a filename.
// Both SaveEditingBuffer (F1) and ImportFromPath (M6) call this so the // Both SaveEditingBuffer and ImportFromPath call this so the
// path-traversal/invalid-char rules live in exactly one place. // path-traversal/invalid-char rules live in exactly one place.
// //
// Whitespace rejection is intentional: Path.GetInvalidFileNameChars on // Whitespace rejection is intentional: Path.GetInvalidFileNameChars on
@@ -112,16 +112,16 @@ public sealed class ThemeRegistry
public Theme Active => _active; public Theme Active => _active;
// Read-only exposure of the configured custom themes directory. // Read-only exposure of the configured custom themes directory.
// M6 ThemeImportExportRow opens this path via Process.Start. // ThemeImportExportRow opens this path via Process.Start.
public string? CustomThemesDir => _customThemesDir; public string? CustomThemesDir => _customThemesDir;
// Read-only enumeration of all built-in theme slugs. T2 ThemePickerCategoryStep // Read-only enumeration of all built-in theme slugs. ThemePickerCategoryStep
// diffs this set against ThemePicker.CategoryMapSlugs to enforce coverage. // diffs this set against ThemePicker.CategoryMapSlugs to enforce coverage.
public IEnumerable<string> BuiltinSlugs => _builtIns.Keys; public IEnumerable<string> BuiltinSlugs => _builtIns.Keys;
// True try-pattern lookup: returns false when neither built-in nor custom // True try-pattern lookup: returns false when neither built-in nor custom
// cache holds the slug, no fallback to default. M3 ThemePicker uses this // cache holds the slug, no fallback to default. ThemePicker uses this
// for card-rendering, M6 ThemeImportExportRow for fork-slug collisions. // for card-rendering, ThemeImportExportRow for fork-slug collisions.
// Cold-cache fallback: see `LoadCustomBySlug` lookup-by-slug reverse // Cold-cache fallback: see `LoadCustomBySlug` lookup-by-slug reverse
// iteration — it only walks the pre-populated _customCache. If a freshly // iteration — it only walks the pre-populated _customCache. If a freshly
// imported file has not been enumerated yet (or no warm-up ran), the first // imported file has not been enumerated yet (or no warm-up ran), the first
@@ -292,8 +292,8 @@ public sealed class ThemeRegistry
// Switch() prefers built-ins over custom themes with the same slug // Switch() prefers built-ins over custom themes with the same slug
// (see `Switch` built-in-first lookup), so saving a custom file under // (see `Switch` built-in-first lookup), so saving a custom file under
// a built-in slug persists the file but leaves the built-in active — // a built-in slug persists the file but leaves the built-in active —
// looks green, behaves broken. M4 ColorPicker DrawIdleState forks // looks green, behaves broken. ColorPicker DrawIdleState forks
// built-in themes into a custom slug before BeginEditing, M6 // built-in themes into a custom slug before BeginEditing,
// ImportFromPath renames built-in-colliding imports to <slug>_imported. // ImportFromPath renames built-in-colliding imports to <slug>_imported.
// New call-sites must either fork first or rename to a non-built-in slug. // New call-sites must either fork first or rename to a non-built-in slug.
public bool SaveEditingBuffer(out string targetPath) public bool SaveEditingBuffer(out string targetPath)
@@ -308,7 +308,7 @@ public sealed class ThemeRegistry
// separators, parent-directory tokens, or platform-invalid filename chars. // separators, parent-directory tokens, or platform-invalid filename chars.
// Without this guard an imported theme with Slug "../../../etc/passwd" // Without this guard an imported theme with Slug "../../../etc/passwd"
// would let Path.Combine escape _customThemesDir entirely. Shared helper // would let Path.Combine escape _customThemesDir entirely. Shared helper
// so M6 ImportFromPath uses the exact same rule set. // so ImportFromPath uses the exact same rule set.
var safeSlug = _editingThemeBuffer.Slug; var safeSlug = _editingThemeBuffer.Slug;
if (!IsSafeThemeSlug(safeSlug)) if (!IsSafeThemeSlug(safeSlug))
{ {
@@ -326,8 +326,8 @@ public sealed class ThemeRegistry
// persist a custom file under a built-in slug — the file lands on disk, // persist a custom file under a built-in slug — the file lands on disk,
// Switch keeps the built-in active, and the post-save active-slug check // Switch keeps the built-in active, and the post-save active-slug check
// below returns false. The caller then sees "save failed" while a garbage // below returns false. The caller then sees "save failed" while a garbage
// file accumulates in the themes dir on every retry. M4 ColorPicker forks // file accumulates in the themes dir on every retry. ColorPicker forks
// built-in themes into a custom slug before BeginEditing, M6 ImportFromPath // built-in themes into a custom slug before BeginEditing, ImportFromPath
// renames built-in-colliding imports to <slug>_imported, so production // renames built-in-colliding imports to <slug>_imported, so production
// paths already steer clear; this guard catches everything else. // paths already steer clear; this guard catches everything else.
if (_builtIns.ContainsKey(safeSlug)) if (_builtIns.ContainsKey(safeSlug))
@@ -522,8 +522,8 @@ public sealed class ThemeRegistry
) )
{ {
var t = (float)(now - _crossfadeStartTickMs) / CrossfadeDurationMs; var t = (float)(now - _crossfadeStartTickMs) / CrossfadeDurationMs;
// A2: SmoothStep easing so the fade eases in/out instead of a // SmoothStep easing so the fade eases in and out instead of running
// linear ramp. MUST stay in lockstep with TryGetActiveCrossfade (K8). // linear. MUST stay in lockstep with TryGetActiveCrossfade.
var te = t * t * (3f - 2f * t); var te = t * t * (3f - 2f * t);
snapshot = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, te); snapshot = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, te);
} }
@@ -551,7 +551,7 @@ public sealed class ThemeRegistry
return false; return false;
var t = (float)elapsed / CrossfadeDurationMs; var t = (float)elapsed / CrossfadeDurationMs;
// A2: SmoothStep easing -- keep identical to ArmCrossfade (K8). // SmoothStep easing -- keep identical to ArmCrossfade.
var te = t * t * (3f - 2f * t); var te = t * t * (3f - 2f * t);
lerped = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, te); lerped = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, te);
return true; return true;
+1 -1
View File
@@ -1,6 +1,6 @@
namespace HellionChat.Themes; namespace HellionChat.Themes;
// Pure stale-check for the v1.4.8 B2 theme-auto-refresh-on-active path. // Pure stale-check for the v1.4.8 theme-auto-refresh-on-active path.
// Lives in a free helper class so the Build-Suite can exercise the diff // Lives in a free helper class so the Build-Suite can exercise the diff
// rules without instantiating ThemeRegistry (which touches the Dalamud // rules without instantiating ThemeRegistry (which touches the Dalamud
// log proxy and the filesystem). The rules: // log proxy and the filesystem). The rules:
+2 -2
View File
@@ -18,7 +18,7 @@ internal sealed class CommandHelpWindow : Window
// Setter-injected post-ctor to break the InputBar -> CommandHelpWindow -> // Setter-injected post-ctor to break the InputBar -> CommandHelpWindow ->
// MainWindow -> InputBar singleton cycle (MS.DI does not detect cycles // MainWindow -> InputBar singleton cycle (MS.DI does not detect cycles
// through FactoryCallSite registrations). Wired in // through FactoryCallSite registrations). Wired in
// CommandHelpWindowInitHostedService.StartAsync, same §6.2 pattern as // CommandHelpWindowInitHostedService.StartAsync, same setter-injection pattern as
// MessageList.AttachPayloadHandler. // MessageList.AttachPayloadHandler.
private Windows.MainWindow? _mainWindow; private Windows.MainWindow? _mainWindow;
@@ -41,7 +41,7 @@ internal sealed class CommandHelpWindow : Window
RespectCloseHotkey = false; RespectCloseHotkey = false;
DisableWindowSounds = true; DisableWindowSounds = true;
// Logger injected for future diagnostic hooks (no call-sites yet in R2). // Logger injected for future diagnostic hooks; no call sites yet.
_ = _logger; _ = _logger;
} }
+2 -2
View File
@@ -2,8 +2,8 @@ using System.Collections.Generic;
namespace HellionChat.Ui.Components; namespace HellionChat.Ui.Components;
// B2 (PERF-B2): variable-height clip plan. ImGuiListClipper needs a constant // Variable-height clip plan. ImGuiListClipper needs a constant
// row height, and since v1.10.0/A2 neither density has one (compact rows wrap // row height, and since v1.10.0 neither density has one (compact rows wrap
// too), so both compute a plan from the cached per-row heights: a lead dummy // too), so both compute a plan from the cached per-row heights: a lead dummy
// for the rows above // for the rows above
// the viewport, the [first..last] index range that overlaps the viewport, and // the viewport, the [first..last] index range that overlaps the viewport, and
+3 -3
View File
@@ -34,11 +34,11 @@ internal sealed class ChunkRenderer
// names change every plugin reload to avoid stable cross-session linkage. // names change every plugin reload to avoid stable cross-session linkage.
_salt = new Random().Next().ToString(); _salt = new Random().Next().ToString();
// Not yet consumed in C2/C3; E-task wiring will likely add log call-sites later. // No call sites yet; logging here will likely come later.
_ = _logger; _ = _logger;
} }
// B2-1/B2-2 render-observability: the formatted sender text the real draw // render-observability: the formatted sender text the real draw
// path actually produced (post-ForDisplay). A SelfTest reads this after // path actually produced (post-ForDisplay). A SelfTest reads this after
// driving DrawChunks to prove the WorldSuffixMode/NameFormMode reformat // driving DrawChunks to prove the WorldSuffixMode/NameFormMode reformat
// reached the real render entry — never the helper in isolation. null until // reached the real render entry — never the helper in isolation. null until
@@ -52,7 +52,7 @@ internal sealed class ChunkRenderer
float lineWidth = 0f float lineWidth = 0f
) )
{ {
// UI-7: render a copy with the sender name reformatted per the user's // Render a copy with the sender name reformatted per the user's
// display options. Skipped in screenshot mode so the name-anonymising // display options. Skipped in screenshot mode so the name-anonymising
// path in DrawChunk stays reliable (privacy wins). ForDisplay returns // path in DrawChunk stays reliable (privacy wins). ForDisplay returns
// the list unchanged when nothing applies, so non-sender lists and the // the list unchanged when nothing applies, so non-sender lists and the
+5 -5
View File
@@ -64,7 +64,7 @@ internal sealed class InputBar
private bool _wasInputTextHovered; private bool _wasInputTextHovered;
private bool? _isFocusedOverride; // Test-only; null = honour per-frame Draw() value. private bool? _isFocusedOverride; // Test-only; null = honour per-frame Draw() value.
// UI-11 plugin-disclosure arm-and-hold: holds the buffer that armed the // plugin-disclosure arm-and-hold: holds the buffer that armed the
// disclosure warning. null = not armed. Compared by value so an edit // disclosure warning. null = not armed. Compared by value so an edit
// re-arms and a resend on the identical buffer goes through. 1.5.6 parity // re-arms and a resend on the identical buffer goes through. 1.5.6 parity
// (ChatInputBar 1d3b429:27). // (ChatInputBar 1d3b429:27).
@@ -118,7 +118,7 @@ internal sealed class InputBar
// Note: when MainWindow is closed, DrawInputField never runs, so // Note: when MainWindow is closed, DrawInputField never runs, so
// _isFocused keeps the last value written by the previous draw pass. // _isFocused keeps the last value written by the previous draw pass.
// The consumer that actually pushes this state across the IPC boundary // The consumer that actually pushes this state across the IPC boundary
// (TypingIpc.BuildState, see F3 Step 2) gates on Plugin.MainWindow.IsOpen // (TypingIpc.BuildState) gates on Plugin.MainWindow.IsOpen
// itself, so the stale backing-field never leaks to subscribers. Mirroring // itself, so the stale backing-field never leaks to subscribers. Mirroring
// the gate here would require an extra Plugin-backref in InputBar that the // the gate here would require an extra Plugin-backref in InputBar that the
// rest of the component doesn't need. // rest of the component doesn't need.
@@ -196,7 +196,7 @@ internal sealed class InputBar
ImGui.SameLine(); ImGui.SameLine();
DrawQuickButtons(); DrawQuickButtons();
// UI-11: yellow inline warning while a plugin-only-glyph message is // Yellow inline warning while a plugin-only-glyph message is
// armed-and-held (buffer unchanged since it armed). Renders on its own // armed-and-held (buffer unchanged since it armed). Renders on its own
// line below the input row. 1.5.6 parity (ChatInputBar 1d3b429:93-103). // line below the input row. 1.5.6 parity (ChatInputBar 1d3b429:93-103).
if ( if (
@@ -589,7 +589,7 @@ internal sealed class InputBar
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
return; return;
// UI-11: plugin-disclosure arm-and-hold. Arm + scan on the RAW // Plugin-disclosure arm-and-hold. Arm + scan on the RAW
// _pendingMessage (NOT the trimmed `text`) so the Draw warning gate // _pendingMessage (NOT the trimmed `text`) so the Draw warning gate
// (_pendingMessage == _disclosureArmedBuffer) matches byte-for-byte even // (_pendingMessage == _disclosureArmedBuffer) matches byte-for-byte even
// when the buffer has leading/trailing whitespace. 1.5.6 armed/held/ // when the buffer has leading/trailing whitespace. 1.5.6 armed/held/
@@ -1183,7 +1183,7 @@ internal sealed class InputBar
} }
// DTO for an in-flight auto-translate completion. Lives as a companion type // DTO for an in-flight auto-translate completion. Lives as a companion type
// in this file because it is only consumed by InputBar (see v1.7.1 Fix #4 plan §2.4). // in this file because it is only consumed by InputBar.
internal sealed class AutoCompleteInfo internal sealed class AutoCompleteInfo
{ {
// ToComplete MUST be a mutable field (not an auto-property), because the // ToComplete MUST be a mutable field (not an auto-property), because the
+14 -14
View File
@@ -18,13 +18,13 @@ internal sealed class MessageList
private PayloadHandler? _handler; private PayloadHandler? _handler;
// B3-5: scroll-to-bottom state. Per-instance, so pop-out windows (own // Scroll-to-bottom state. Per-instance, so pop-out windows (own
// MessageList instance, PluginHostFactory.cs:263-266) isolate automatically — // MessageList instance, PluginHostFactory.cs:263-266) isolate automatically —
// the old 1.5.6 updateScrollState flag is NOT needed here. // the old 1.5.6 updateScrollState flag is NOT needed here.
private bool _scrolledUp; private bool _scrolledUp;
private bool _scrollToBottomRequested; private bool _scrollToBottomRequested;
// B2: the height cache is only valid while these inputs are unchanged. // The height cache is only valid while these inputs are unchanged.
// FontManager's own fingerprint covers font sizes only, not density / the two // FontManager's own fingerprint covers font sizes only, not density / the two
// name-display modes / width — a stale height would misplace the clipper dummies. // name-display modes / width — a stale height would misplace the clipper dummies.
// Per tab, not per list: the old single field let a width change in tab A mark // Per tab, not per list: the old single field let a width change in tab A mark
@@ -38,7 +38,7 @@ internal sealed class MessageList
private readonly Action<Message, string?> _drawCardRow; private readonly Action<Message, string?> _drawCardRow;
// Reused across frames: at MessageManager.MessageDisplayLimit a fresh array // Reused across frames: at MessageManager.MessageDisplayLimit a fresh array
// per frame is 40 KB of garbage, and A2 put the default density on this // per frame is 40 KB of garbage, and A later cycle put the default density on this
// path. The old comment named MaxLinesToRender and its 2500 default, a // path. The old comment named MaxLinesToRender and its 2500 default, a
// config field that had stopped bounding anything. // config field that had stopped bounding anything.
private float[] _heightScratch = []; private float[] _heightScratch = [];
@@ -49,7 +49,7 @@ internal sealed class MessageList
private bool _stampVisible; private bool _stampVisible;
private float _metaDrop; private float _metaDrop;
// §6.2: setter-injection breaks the PayloadHandler → MainWindow → MessageList → PayloadHandler 3-cycle. // Setter-injection breaks the PayloadHandler → MainWindow → MessageList → PayloadHandler 3-cycle.
// Wired by PayloadHandlerInitHostedService.StartAsync after both singletons exist. // Wired by PayloadHandlerInitHostedService.StartAsync after both singletons exist.
internal void AttachPayloadHandler(PayloadHandler handler) internal void AttachPayloadHandler(PayloadHandler handler)
{ {
@@ -73,12 +73,12 @@ internal sealed class MessageList
return snap; return snap;
} }
// SelfTest hook (B3-5 reset-invariant, REQUIRED — not optional). Lets // SelfTest hook (reset-invariant, REQUIRED — not optional). Lets
// ScrollSnapDecisionStep flip the request flag without a real click, so the // ScrollSnapDecisionStep flip the request flag without a real click, so the
// post-snap reset can be asserted; without it only the OR branch is testable. // post-snap reset can be asserted; without it only the OR branch is testable.
internal void RequestScrollToBottomForSelfTest() => _scrollToBottomRequested = true; internal void RequestScrollToBottomForSelfTest() => _scrollToBottomRequested = true;
// SelfTest hook (B2): runs the real planner against a caller fixture so the // SelfTest hook: runs the real planner against a caller fixture so the
// step asserts the plan without a live scroll child (GetScrollY is garbage headless). // step asserts the plan without a live scroll child (GetScrollY is garbage headless).
internal CardClipPlan PlanCardClipForSelfTest( internal CardClipPlan PlanCardClipForSelfTest(
IReadOnlyList<float> heights, IReadOnlyList<float> heights,
@@ -86,9 +86,9 @@ internal sealed class MessageList
float viewportHeight float viewportHeight
) => CardClipPlanner.Plan(heights, scrollY, viewportHeight); ) => CardClipPlanner.Plan(heights, scrollY, viewportHeight);
// SelfTest hook (B2): drives the live invalidation, returns the tab's remaining // SelfTest hook: drives the live invalidation, returns the tab's remaining
// cached-height count so the step can assert the drop. nowMs is a parameter so // cached-height count so the step can assert the drop. nowMs is a parameter so
// the step can step past the settle window without sleeping (v1.10.0/A1). // the step can step past the settle window without sleeping (v1.10.0).
internal int RunHeightCacheInvalidationForSelfTest(Tab tab, float contentWidth, long nowMs) internal int RunHeightCacheInvalidationForSelfTest(Tab tab, float contentWidth, long nowMs)
{ {
InvalidateHeightCacheIfLayoutChanged(tab, contentWidth, nowMs); InvalidateHeightCacheIfLayoutChanged(tab, contentWidth, nowMs);
@@ -165,7 +165,7 @@ internal sealed class MessageList
MeasureTimestampColumn(tab); MeasureTimestampColumn(tab);
// B2: drop stale cached heights before the snapshot draw. Both densities // Drop stale cached heights before the snapshot draw. Both densities
// need this now -- compact rows are not constant height either, they wrap. // need this now -- compact rows are not constant height either, they wrap.
// Width read here while it is valid. // Width read here while it is valid.
InvalidateHeightCacheIfLayoutChanged( InvalidateHeightCacheIfLayoutChanged(
@@ -187,7 +187,7 @@ internal sealed class MessageList
var frozen = _fingerprintGates[tab.Identifier].IsPending; var frozen = _fingerprintGates[tab.Identifier].IsPending;
DrawRows(tab, messages, compact ? _drawCompactRow : _drawCardRow, frozen); DrawRows(tab, messages, compact ? _drawCompactRow : _drawCardRow, frozen);
// B3-5: scroll values are frame-constant inside the child, so this // Scroll values are frame-constant inside the child, so this
// reflects the current frame's state wherever it runs; kept after the // reflects the current frame's state wherever it runs; kept after the
// render to mirror the 1.5.6 end-of-DrawMessageLog placement. // render to mirror the 1.5.6 end-of-DrawMessageLog placement.
_scrolledUp = ImGui.GetScrollMaxY() - ImGui.GetScrollY() > 1f; _scrolledUp = ImGui.GetScrollMaxY() - ImGui.GetScrollY() > 1f;
@@ -282,7 +282,7 @@ internal sealed class MessageList
ImGui.SetCursorPos(origin with { X = origin.X + _stampColumnWidth }); ImGui.SetCursorPos(origin with { X = origin.X + _stampColumnWidth });
} }
// B3-5: Discord-style full-width bar pinned to the bottom edge of the // Discord-style full-width bar pinned to the bottom edge of the
// visible region while the user is scrolled up. Geometry comes from window // visible region while the user is scrolled up. Geometry comes from window
// pos + size (visible region), never from the content flow: when scrolled // pos + size (visible region), never from the content flow: when scrolled
// up the visible bottom sits above the content bottom, so the // up the visible bottom sits above the content bottom, so the
@@ -335,7 +335,7 @@ internal sealed class MessageList
private void DrawCompactRow(Message message, string? previousStamp) private void DrawCompactRow(Message message, string? previousStamp)
{ {
// B2-1/B2-2: render the sender through DrawChunks (the name-aware path // Render the sender through DrawChunks (the name-aware path
// that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a // that applies WorldSuffixMode/NameFormMode via ForDisplay), not as a
// flat SenderSource.TextValue string. message.Sender already carries the // flat SenderSource.TextValue string. message.Sender already carries the
// channel brackets/colon as ChunkSource.None wrappers (MessageManager // channel brackets/colon as ChunkSource.None wrappers (MessageManager
@@ -566,10 +566,10 @@ internal sealed class MessageList
private void DrawCardRow(Message message, string? previousStamp) private void DrawCardRow(Message message, string? previousStamp)
{ {
// B2-1/B2-2: sender via DrawChunks (name-aware path), on its own line // Sender via DrawChunks (name-aware path), on its own line
// with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no // with content below — 1.5.6 card parity (ChatLogWindow.cs:1913, no
// SameLine after the sender). The 1.5.6 channel-colour push on the // SameLine after the sender). The 1.5.6 channel-colour push on the
// sender is deferred styling polish (masterplan §6 -> v1.9.0); plain // sender is deferred styling polish (deferred to v1.9.0); plain
// text here. // text here.
// A system message has no sender, so a header row would be a stamp on a // A system message has no sender, so a header row would be a stamp on a
// line of its own -- an empty gesture. Those stay single-line in both // line of its own -- an empty gesture. Those stay single-line in both
@@ -20,7 +20,7 @@ internal sealed class LivePreviewPanel : IDisposable
private static uint Abgr(StyleEngine.Token token, ThemeColors colors) => private static uint Abgr(StyleEngine.Token token, ThemeColors colors) =>
ColourUtil.RgbaToAbgr(Tokens.Resolve(token, colors)); ColourUtil.RgbaToAbgr(Tokens.Resolve(token, colors));
// Static counter for S5 reload-stress verification: after 10 reloads the // Static counter for reload-stress verification: after 10 reloads the
// counter must read 0 (plugin disabled) or 1 (plugin enabled). Anything // counter must read 0 (plugin disabled) or 1 (plugin enabled). Anything
// higher signals a Dispose skip and a subscriber leak against ThemeRegistry. // higher signals a Dispose skip and a subscriber leak against ThemeRegistry.
internal static int InstanceCount; internal static int InstanceCount;
@@ -133,7 +133,7 @@ internal sealed class AboutTab
LastHonorificStatusKey = kind.ToString(); LastHonorificStatusKey = kind.ToString();
var colors = _themes.Active.Colors; var colors = _themes.Active.Colors;
// Null-safety via the `is { } v` pattern, never `.Value` raw (spec SEC-2): // Null-safety via the `is { } v` pattern, never `.Value` raw :
// the version is bound only on the arms that have it; the impossible // the version is bound only on the arms that have it; the impossible
// Detected/Incompatible-without-version state falls through to default. // Detected/Incompatible-without-version state falls through to default.
switch (kind) switch (kind)
@@ -254,7 +254,7 @@ internal sealed class DataPrivacyTab
) )
{ {
// Read-only statement, not a switch. Do not promote it to one // Read-only statement, not a switch. Do not promote it to one
// without an explicit Sub-Spec change: a toggle implies there is // without an explicit design change: a toggle implies there is
// something to turn off. // something to turn off.
ImGuiUtil.HelpText(HellionStrings.Settings_Telemetry_None); ImGuiUtil.HelpText(HellionStrings.Settings_Telemetry_None);
} }
@@ -173,8 +173,7 @@ internal sealed class ThemeImportExportRow
// Slug sanitisation BEFORE BeginEditing — SaveEditingBuffer would // Slug sanitisation BEFORE BeginEditing — SaveEditingBuffer would
// reject too, but rejecting here means an unsafe slug never enters // reject too, but rejecting here means an unsafe slug never enters
// the editing buffer. Shared helper ThemeRegistry.IsSafeThemeSlug // the editing buffer. Shared helper ThemeRegistry.IsSafeThemeSlug
// keeps the rule set in sync with F1's save-side guard (see // keeps the rule set in sync with the save-side guard.
// ThemeRegistry.IsSafeThemeSlug shared helper).
var importSlug = theme.Slug; var importSlug = theme.Slug;
if (!ThemeRegistry.IsSafeThemeSlug(importSlug)) if (!ThemeRegistry.IsSafeThemeSlug(importSlug))
{ {
@@ -186,9 +185,9 @@ internal sealed class ThemeImportExportRow
return; return;
} }
// Pragmatic deviation from §1.6 wording ("File.Copy into themes/"): // Not a plain File.Copy into themes/: BeginEditing+SaveEditingBuffer
// BeginEditing+SaveEditingBuffer produces the same end-state and // produces the same end state and reuses the validated save
// reuses the validated F1 save pipeline. Trade-off: destination // pipeline. Trade-off: destination
// filename becomes the theme's slug, not the original filename. // filename becomes the theme's slug, not the original filename.
// //
// Slug-collision handling: // Slug-collision handling:
@@ -29,7 +29,7 @@ internal sealed class ThemePicker
(HellionStrings.Settings_Theme_Category_Retro, new[] { "synthwave-sunset" }, false), (HellionStrings.Settings_Theme_Category_Retro, new[] { "synthwave-sunset" }, false),
]; ];
// T2 ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs // ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs
// to enforce coverage. Kept on the static map so the test does not pierce instance state. // to enforce coverage. Kept on the static map so the test does not pierce instance state.
internal static IEnumerable<string> CategoryMapSlugs => CategoryMap.SelectMany(c => c.Slugs); internal static IEnumerable<string> CategoryMapSlugs => CategoryMap.SelectMany(c => c.Slugs);
+13 -9
View File
@@ -22,10 +22,14 @@ internal sealed class Sidebar
{ {
public const float IconOnlyWidth = 38f; public const float IconOnlyWidth = 38f;
// B1-3a: expanded sidebar width is user-configurable (Config.SidebarWidth), // Expanded sidebar width is user-configurable (Config.SidebarWidth),
// clamped to these bounds (matches the ChannelsTab slider range). Replaces // clamped to these bounds (matches the ChannelsTab slider range).
// the old fixed 150px ExpandedWidth constant. //
public const float MinSidebarWidth = 40f; // The floor is where a tab name stops being readable, not where the icons
// stop fitting: 40 let the expanded sidebar be narrower than the icon-only
// one, which drew labels into a column too narrow to hold them. Anyone who
// wants it that slim wants the collapsed layout, and that is IconOnlyWidth.
public const float MinSidebarWidth = 130f;
public const float MaxSidebarWidth = 300f; public const float MaxSidebarWidth = 300f;
private static float RowHeight => Metrics.SidebarRowHeight; private static float RowHeight => Metrics.SidebarRowHeight;
@@ -38,7 +42,7 @@ internal sealed class Sidebar
// previously active one -- that row was still active when it was painted. // previously active one -- that row was still active when it was painted.
internal int LastRenderedActiveSurfaceCount { get; private set; } internal int LastRenderedActiveSurfaceCount { get; private set; }
// B3-2 render observability: counts greeted glyphs actually drawn this frame. // render observability: counts greeted glyphs actually drawn this frame.
// Incremented ONLY in the real glyph branch in DrawRow; reset at Draw start. // Incremented ONLY in the real glyph branch in DrawRow; reset at Draw start.
// The SelfTest reads it after driving the real Draw — no dead service roundtrip. // The SelfTest reads it after driving the real Draw — no dead service roundtrip.
internal int LastRenderedGreetedGlyphCount; internal int LastRenderedGreetedGlyphCount;
@@ -48,7 +52,7 @@ internal sealed class Sidebar
// beside it. // beside it.
private const float PinGlyphScale = 0.6f; private const float PinGlyphScale = 0.6f;
// B3-4 render observability: section headers actually drawn this frame. // render observability: section headers actually drawn this frame.
// Incremented only in the real header branch; reset at Draw start. // Incremented only in the real header branch; reset at Draw start.
internal int LastDrawnSectionHeaderCount; internal int LastDrawnSectionHeaderCount;
@@ -149,7 +153,7 @@ internal sealed class Sidebar
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim); var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
var dl = ImGui.GetWindowDrawList(); var dl = ImGui.GetWindowDrawList();
// B3-4 sectioned render order (1.5.6 parity): persistent → pinned // sectioned render order (1.5.6 parity): persistent → pinned
// TempTabs → unpinned TempTabs. Only the display sequence regroups; // TempTabs → unpinned TempTabs. Only the display sequence regroups;
// the tab list itself stays untouched and every row keeps its // the tab list itself stays untouched and every row keeps its
// ORIGINAL list index for PushID, so an open context-menu popup // ORIGINAL list index for PushID, so an open context-menu popup
@@ -250,7 +254,7 @@ internal sealed class Sidebar
// Only split off a separate pop-out hit area when there's room for // Only split off a separate pop-out hit area when there's room for
// both buttons. Below that, the whole row stays as a single // both buttons. Below that, the whole row stays as a single
// selectable strip without the pop-out affordance. // selectable strip without the pop-out affordance.
// A3: gate the pop-out affordance on the expanded sidebar too. In // Gate the pop-out affordance on the expanded sidebar too. In
// icon-only mode avail still clears the width threshold, which used to // icon-only mode avail still clears the width threshold, which used to
// paint the pop-out glyph over the tab icon. The row stays a single // paint the pop-out glyph over the tab icon. The row stays a single
// selectable strip when collapsed; right-click pop-out is unaffected. // selectable strip when collapsed; right-click pop-out is unaffected.
@@ -476,7 +480,7 @@ internal sealed class Sidebar
// The hit area sits at the LEFT edge of the row, but the item must // The hit area sits at the LEFT edge of the row, but the item must
// be submitted AFTER TabContextMenu.Draw — any interactive item // be submitted AFTER TabContextMenu.Draw — any interactive item
// between the row button and the popup call would steal the // between the row button and the popup call would steal the
// right-click trigger (B3-1 ordering constraint). // right-click trigger (ordering constraint).
ImGui.SetCursorScreenPos(origin); ImGui.SetCursorScreenPos(origin);
// CheckCircle = greeted, plain Check = still pending (1.5.6 mapping). // CheckCircle = greeted, plain Check = still pending (1.5.6 mapping).
+1 -1
View File
@@ -82,7 +82,7 @@ internal static class TabContextMenu
ClearPendingRename(); ClearPendingRename();
} }
// Per-tab notification sound (B3-3). The checkbox gates the picker so // Per-tab notification sound. The checkbox gates the picker so
// tabs that never want a sound keep the popup short. // tabs that never want a sound keep the popup short.
if ( if (
ImGui.Checkbox( ImGui.Checkbox(
+2 -2
View File
@@ -36,7 +36,7 @@ public class DbViewer : Window
private int CurrentPage = 1; private int CurrentPage = 1;
private string SimpleSearchTerm = ""; private string SimpleSearchTerm = "";
// v1.4.8 H2: opt-in full-text search across the whole DB via FTS5. // v1.4.8: opt-in full-text search across the whole DB via FTS5.
// Transient UI state (per-session), not persisted -- users opt in fresh // Transient UI state (per-session), not persisted -- users opt in fresh
// every time so they always see the page-filter as the default mode. // every time so they always see the page-filter as the default mode.
private bool UseFullTextSearch; private bool UseFullTextSearch;
@@ -232,7 +232,7 @@ public class DbViewer : Window
tooltipRight: Language.Page_ArrowRight_Tooltip tooltipRight: Language.Page_ArrowRight_Tooltip
); );
// Full-text search toggle (v1.4.8 H2). IsFtsIndexBuilt is a cached // Full-text search toggle (v1.4.8). IsFtsIndexBuilt is a cached
// volatile bool in MessageStore -- single field read per frame, no // volatile bool in MessageStore -- single field read per frame, no
// SELECT count(*). ImRaii.Disabled blocks any click while the index // SELECT count(*). ImRaii.Disabled blocks any click while the index
// is still being built, so no defensive force-off branch needed // is still being built, so no defensive force-off branch needed

Some files were not shown because too many files have changed in this diff Show More