Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35e8d3a7fe | |||
| 38586db9d8 | |||
| c357873604 | |||
| 67bec11f10 | |||
| 35efdd4628 | |||
| 271a6ae650 | |||
| 003bd5c695 | |||
| e1f84a9b10 | |||
| 9745abea0c | |||
| 1e418ab86f | |||
| 1c820b7f53 | |||
| 2cc260170e | |||
| de86084dbc | |||
| f56b968768 |
@@ -0,0 +1,10 @@
|
||||
---
|
||||
subtitle: "First-Run Wizard — neu in 4 Steps, Roleplay-Profil neu"
|
||||
versionsnatur: "UX-Patch"
|
||||
---
|
||||
- **Vier Steps statt Single-Page.** Der First-Run-Wizard öffnet jetzt in vier Bühnen: Willkommen → Privacy-Profil → Power-Settings → Fertig. Pagination-Dots in Forge-Bronze oben rechts, Back/Skip/Next im Footer. Standardgröße 720×480 (Min 600×400) und der Fuchs-Banner sitzt als zugeklappter TreeNode oben in Step 1, damit die Einleitung im Fokus bleibt.
|
||||
- **Neues Privacy-Profil „Roleplay".** Datensparsamkeit plus Sagen und beide Emote-Typen für Story-Logs. Schreien und Rufen bleiben außen vor, Public-Distance-Lärm von Fremden ist kein Story-Inhalt. Aufbewahrung: Sagen 30 Tage, Emotes 90 Tage. Privacy-Picker wird zum 2×2-Grid, Casual bleibt mit ★-Marker als Empfehlung.
|
||||
- **Power-Settings sichtbar.** Bislang versteckte Defaults bekommen eine eigene Bühne: Vorherige Session laden, Filter inkl. alter Messages, N Tell-Messages vorladen, Compact-Density, Prettier-Timestamps und Theme-Picker für die 10 Built-in-Themes. Keine neuen Settings, nur das Bestehende sauber sichtbar.
|
||||
- **Staged-Commit und Test-Hint auf der Fertig-Bühne.** Auswahl wird erst beim Klick auf „Fertig ✓" geschrieben. „Später entscheiden" oder X-Close lässt die bestehende Config unangetastet, ein nicht angefasster Step behält die alten Werte. Direkt darunter sichtbar: „Tipp /tell <Spielername>", plus die aktuelle Preload-Zahl aus Step 3 als Hinweis auf den Auto-Tell-Tab-Spawn.
|
||||
- **Bestehende User sehen den neuen Wizard einmal.** Wer schon v1.5.1 hatte, bekommt den Multi-Step-Flow beim ersten v1.5.2-Boot aufgepoppt. Neues Config-Feld `WizardLastShownVersion` triggert das einmalig pro Wizard-Rework; Skip oder Finish reicht und danach öffnet er nicht mehr automatisch.
|
||||
- **Unter der Haube.** Pure-Helper-Tests für alle vier Profile-Sets in der Build-Suite (zwölf neue Facts), plus ein WizardStateSmokeStep für `/xlperf`. Migration v17 bleibt, nur ein optionales Config-Feld kommt dazu.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
subtitle: "24 Sprachen, Inter Light statt Exo 2, HITCH 74 → 20 ms"
|
||||
versionsnatur: "Localisation + Font-Stack"
|
||||
---
|
||||
- **24 wählbare UI-Sprachen.** Aus dem ursprünglich nur als FR-Lokalisierung geplanten Cycle ist eine breite Welle geworden: Catalan, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hungarian, Italian, Japanese, Korean, Norsk bokmål, Polish, Portuguese (BR), Portuguese (PT), Romanian, Russian, Spanish, Swedish, Turkish, Ukrainian, Simplified Chinese, Traditional Chinese. Dropdown sortiert alphabetisch nach Endonym, „None" oben angepinnt. Nicht-native Übersetzungen sind AI-assisted und für Community-Review im Forge-Discord markiert.
|
||||
- **Inter Light statt Exo 2 als bundled Schrift.** Plus NotoSansCjkRegular als dritte Merge-Schicht. Damit deckt der Stack Latin Extended-A/B, Greek polytonic, Cyrillic Supplement und CJK (inkl. Hangul, Simplified-Han nach Reform) ab — die nicht-vanilla-FFXIV-Sprachen waren mit Exo 2 nicht lesbar.
|
||||
- **HITCH 74 → ~20 ms als Side-Effect.** Der UiBuilder-First-Frame-Lag lag seit v1.4.x stabil bei 74 ms; v1.5.1 wollte ihn in Richtung 7 ms ziehen, fiel als „Hypothese zu optimistisch" durch. Echter Grund: `Plugin.cs:937` push'te `RegularFont` nur wenn `FontsEnabled` true war — die „Mitgelieferte Schrift verwenden"-Logik setzte `FontsEnabled = false` mit, der bundled-Pfad war die ganze v1.5.x-Reihe tot, FFXIVs Axis-Font übernahm und kostete ~50 ms extra. Fix routet `RegularFont` jetzt auch über `UseHellionFont`. Median ~20 ms im 5-Reload-Stresstest (17.9-23.6 ms, Linux/Wine; Windows-Baseline steht aus).
|
||||
- **Glyph-Ranges aktivieren sich automatisch beim Sprachwechsel** plus eine One-Shot-Migration für User die schon eine non-Latin-Sprache eingestellt hatten. Neue WarningText unter dem Sprach-Dropdown weist darauf hin, dass FFXIVs Chat-Engine offiziell nur EN/DE/FR/JA-Glyphen rendert — andere Schriften können in der Game-Eingabe Garbled-Output zeigen.
|
||||
- **Unter der Haube.** Drei-Layer-Font-Stack, zwei neue ExtraGlyphRanges-Flags (`LatinExtended`, `Greek`), `LanguageOverride`-Enum wächst um zehn Locales plus drei reaktivierte (Italian, Korean, Norwegian mit `nb`). Append-only damit User-Configs stabil bleiben. Migration v17 bleibt.
|
||||
@@ -100,6 +100,15 @@ public class Configuration : IPluginConfiguration
|
||||
public Dictionary<ChatType, int> RetentionPerChannelDays = [];
|
||||
public DateTimeOffset RetentionLastRunAt = DateTimeOffset.MinValue;
|
||||
public bool FirstRunCompleted;
|
||||
|
||||
// Tracks which plugin version last surfaced the first-run wizard.
|
||||
// When the running version is newer than this, Plugin.LoadAsync
|
||||
// re-opens the wizard once so existing users see major UX reworks
|
||||
// (e.g. the v1.5.2 multi-step rewrite). Skip path and Finish both
|
||||
// set FirstRunCompleted = true on close, so the wizard only fires
|
||||
// once per version bump even if the user dismisses it.
|
||||
public string WizardLastShownVersion = string.Empty;
|
||||
|
||||
public bool UseHellionFont = true;
|
||||
public bool ShowHonorificTitleInHeader = true;
|
||||
|
||||
@@ -336,6 +345,7 @@ public class Configuration : IPluginConfiguration
|
||||
RetentionLastRunAt = other.RetentionLastRunAt;
|
||||
|
||||
FirstRunCompleted = other.FirstRunCompleted;
|
||||
WizardLastShownVersion = other.WizardLastShownVersion;
|
||||
UseHellionFont = other.UseHellionFont;
|
||||
ShowHonorificTitleInHeader = other.ShowHonorificTitleInHeader;
|
||||
ShowHonorificGlow = other.ShowHonorificGlow;
|
||||
@@ -823,17 +833,27 @@ public enum LanguageOverride
|
||||
French,
|
||||
German,
|
||||
Greek,
|
||||
|
||||
// Italian,
|
||||
Japanese,
|
||||
|
||||
// Korean,
|
||||
// Norwegian,
|
||||
PortugueseBrazil,
|
||||
Romanian,
|
||||
Russian,
|
||||
Spanish,
|
||||
Swedish,
|
||||
|
||||
// v1.5.3: Crowdin-heritage activated and Forge-maintained additions.
|
||||
// Append-only to preserve serialized integer values of existing user configs.
|
||||
Italian,
|
||||
Korean,
|
||||
Norwegian,
|
||||
Catalan,
|
||||
Czech,
|
||||
Danish,
|
||||
Finnish,
|
||||
Hungarian,
|
||||
Polish,
|
||||
PortuguesePortugal,
|
||||
Turkish,
|
||||
Ukrainian,
|
||||
}
|
||||
|
||||
public static class LanguageOverrideExt
|
||||
@@ -849,15 +869,24 @@ public static class LanguageOverrideExt
|
||||
LanguageOverride.French => "Français",
|
||||
LanguageOverride.German => "Deutsch",
|
||||
LanguageOverride.Greek => "Ελληνικά",
|
||||
// LanguageOverride.Italian => "Italiano",
|
||||
LanguageOverride.Italian => "Italiano",
|
||||
LanguageOverride.Japanese => "日本語",
|
||||
// LanguageOverride.Korean => "한국어 (Korean)",
|
||||
// LanguageOverride.Norwegian => "Norsk",
|
||||
LanguageOverride.Korean => "한국어",
|
||||
LanguageOverride.Norwegian => "Norsk bokmål",
|
||||
LanguageOverride.PortugueseBrazil => "Português do Brasil",
|
||||
LanguageOverride.Romanian => "Română",
|
||||
LanguageOverride.Russian => "Русский",
|
||||
LanguageOverride.Spanish => "Español",
|
||||
LanguageOverride.Swedish => "Svenska",
|
||||
LanguageOverride.Catalan => "Català",
|
||||
LanguageOverride.Czech => "Čeština",
|
||||
LanguageOverride.Danish => "Dansk",
|
||||
LanguageOverride.Finnish => "Suomi",
|
||||
LanguageOverride.Hungarian => "Magyar",
|
||||
LanguageOverride.Polish => "Polski",
|
||||
LanguageOverride.PortuguesePortugal => "Português (Portugal)",
|
||||
LanguageOverride.Turkish => "Türkçe",
|
||||
LanguageOverride.Ukrainian => "Українська",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
||||
};
|
||||
|
||||
@@ -872,17 +901,47 @@ public static class LanguageOverrideExt
|
||||
LanguageOverride.French => "fr",
|
||||
LanguageOverride.German => "de",
|
||||
LanguageOverride.Greek => "el",
|
||||
// LanguageOverride.Italian => "it",
|
||||
LanguageOverride.Italian => "it",
|
||||
LanguageOverride.Japanese => "ja",
|
||||
// LanguageOverride.Korean => "ko",
|
||||
// LanguageOverride.Norwegian => "no",
|
||||
LanguageOverride.Korean => "ko",
|
||||
LanguageOverride.Norwegian => "nb",
|
||||
LanguageOverride.PortugueseBrazil => "pt-br",
|
||||
LanguageOverride.Romanian => "ro",
|
||||
LanguageOverride.Russian => "ru",
|
||||
LanguageOverride.Spanish => "es",
|
||||
LanguageOverride.Swedish => "sv",
|
||||
LanguageOverride.Catalan => "ca",
|
||||
LanguageOverride.Czech => "cs",
|
||||
LanguageOverride.Danish => "da",
|
||||
LanguageOverride.Finnish => "fi",
|
||||
LanguageOverride.Hungarian => "hu",
|
||||
LanguageOverride.Polish => "pl",
|
||||
LanguageOverride.PortuguesePortugal => "pt-pt",
|
||||
LanguageOverride.Turkish => "tr",
|
||||
LanguageOverride.Ukrainian => "uk",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
||||
};
|
||||
|
||||
// Maps a language to the ExtraGlyphRanges flag required for full UI
|
||||
// rendering in that locale. The settings save path ORs this into
|
||||
// Mutable.ExtraGlyphRanges so users do not need to know which range
|
||||
// to tick manually. Returns 0 for locales fully covered by the default
|
||||
// ImGui glyph range (Latin-1) or by the separate Japanese font handle.
|
||||
public static ExtraGlyphRanges RequiredGlyphRanges(this LanguageOverride mode) =>
|
||||
mode switch
|
||||
{
|
||||
LanguageOverride.Korean => ExtraGlyphRanges.Korean,
|
||||
LanguageOverride.ChineseSimplified => ExtraGlyphRanges.ChineseSimplifiedCommon,
|
||||
LanguageOverride.ChineseTraditional => ExtraGlyphRanges.ChineseFull,
|
||||
LanguageOverride.Ukrainian => ExtraGlyphRanges.Cyrillic,
|
||||
LanguageOverride.Greek => ExtraGlyphRanges.Greek,
|
||||
LanguageOverride.Czech
|
||||
or LanguageOverride.Polish
|
||||
or LanguageOverride.Romanian
|
||||
or LanguageOverride.Hungarian
|
||||
or LanguageOverride.Turkish => ExtraGlyphRanges.LatinExtended,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -896,10 +955,23 @@ public enum ExtraGlyphRanges
|
||||
Korean = 1 << 4,
|
||||
Thai = 1 << 5,
|
||||
Vietnamese = 1 << 6,
|
||||
|
||||
// v1.5.3: Custom ranges for languages with Latin Extended-A glyphs (Czech,
|
||||
// Polish, Romanian, Turkish, Hungarian) and Greek polytonic accents.
|
||||
LatinExtended = 1 << 7,
|
||||
Greek = 1 << 8,
|
||||
}
|
||||
|
||||
public static class ExtraGlyphRangesExt
|
||||
{
|
||||
// Custom (start, end) inclusive pair lists for ranges that ImGui does
|
||||
// not ship a built-in helper for. SetUpRanges() feeds these into
|
||||
// ImFontGlyphRangesBuilder.AddChar via the `chars` parameter of
|
||||
// BuildRange so we avoid the lifetime/pinning question that the native
|
||||
// GetGlyphRanges*-pointer pathway papers over.
|
||||
internal static readonly ushort[] LatinExtendedPairs = { 0x0100, 0x024F };
|
||||
internal static readonly ushort[] GreekPairs = { 0x0370, 0x03FF, 0x1F00, 0x1FFF };
|
||||
|
||||
public static string Name(this ExtraGlyphRanges ranges) =>
|
||||
ranges switch
|
||||
{
|
||||
@@ -911,6 +983,8 @@ public static class ExtraGlyphRangesExt
|
||||
ExtraGlyphRanges.Korean => Language.ExtraGlyphRanges_Korean_Name,
|
||||
ExtraGlyphRanges.Thai => Language.ExtraGlyphRanges_Thai_Name,
|
||||
ExtraGlyphRanges.Vietnamese => Language.ExtraGlyphRanges_Vietnamese_Name,
|
||||
ExtraGlyphRanges.LatinExtended => Language.ExtraGlyphRanges_LatinExtended_Name,
|
||||
ExtraGlyphRanges.Greek => Language.ExtraGlyphRanges_Greek_Name,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
|
||||
};
|
||||
|
||||
@@ -925,6 +999,10 @@ public static class ExtraGlyphRangesExt
|
||||
ExtraGlyphRanges.Korean => (nint)ImGui.GetIO().Fonts.GetGlyphRangesKorean(),
|
||||
ExtraGlyphRanges.Thai => (nint)ImGui.GetIO().Fonts.GetGlyphRangesThai(),
|
||||
ExtraGlyphRanges.Vietnamese => (nint)ImGui.GetIO().Fonts.GetGlyphRangesVietnamese(),
|
||||
// LatinExtended and Greek are applied via builder.AddChar in
|
||||
// FontManager.SetUpRanges, not through a native pointer range.
|
||||
ExtraGlyphRanges.LatinExtended => 0,
|
||||
ExtraGlyphRanges.Greek => 0,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
|
||||
};
|
||||
}
|
||||
|
||||
+73
-17
@@ -9,7 +9,7 @@ using Dalamud.Plugin;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// Two LogProxy sites live in static methods (TryGetHellionFontBytes,
|
||||
// Two LogProxy sites live in static methods (TryGetBundledFontBytes,
|
||||
// AddFontWithFallback); a ctor-injected ILogger would not be reachable
|
||||
// from those scopes, so the class stays on Plugin.LogProxy.
|
||||
//
|
||||
@@ -62,8 +62,8 @@ public sealed class FontManager : IDisposable
|
||||
90f,
|
||||
];
|
||||
|
||||
// Hellion font bytes (Exo 2, OFL-1.1); lazily loaded from manifest resources
|
||||
private static byte[]? HellionFontBytes;
|
||||
// Bundled UI font bytes (Inter Light, OFL-1.1); lazily loaded from manifest resources
|
||||
private static byte[]? BundledFontBytes;
|
||||
|
||||
public FontManager(IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
@@ -122,7 +122,7 @@ public sealed class FontManager : IDisposable
|
||||
e.OnPreBuild(tk =>
|
||||
{
|
||||
// UseHellionFont swaps the source font but keeps the size
|
||||
// selector tied to FontSizeV2 (the Hellion font ships as
|
||||
// selector tied to FontSizeV2 (the bundled font ships as
|
||||
// a single weight).
|
||||
var basePt = Plugin.Config.UseHellionFont
|
||||
? Plugin.Config.FontSizeV2
|
||||
@@ -130,15 +130,28 @@ public sealed class FontManager : IDisposable
|
||||
var config = new SafeFontConfig { SizePt = basePt, GlyphRanges = Ranges };
|
||||
// Missing embedded resource falls back to the configured
|
||||
// system font instead of taking the whole UiBuilder down.
|
||||
var hellionBytes = Plugin.Config.UseHellionFont ? TryGetHellionFontBytes() : null;
|
||||
config.MergeFont = hellionBytes is not null
|
||||
? tk.AddFontFromMemory(hellionBytes, config, "Hellion-Exo2")
|
||||
var bundledBytes = Plugin.Config.UseHellionFont ? TryGetBundledFontBytes() : null;
|
||||
config.MergeFont = bundledBytes is not null
|
||||
? tk.AddFontFromMemory(bundledBytes, config, "Inter-Light")
|
||||
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// v1.5.3: NotoSansCjk fallback covers Hangul, Simplified-Chinese
|
||||
// -specific Han (e.g. 简) and other CJK glyphs that the primary
|
||||
// (Inter Light / global font) and the FFXIV Japanese font do not
|
||||
// ship. Merged last so earlier fonts win for shared codepoints.
|
||||
config.SizePt = basePt;
|
||||
config.GlyphRanges = Ranges;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
config,
|
||||
"noto-cjk-fallback"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
@@ -166,6 +179,16 @@ public sealed class FontManager : IDisposable
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// v1.5.3: NotoSansCjk fallback (see BuildRegularFontHandle).
|
||||
config.SizePt = Plugin.Config.ItalicFontV2.SizePt;
|
||||
config.GlyphRanges = Ranges;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
config,
|
||||
"noto-cjk-fallback"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
@@ -187,26 +210,26 @@ public sealed class FontManager : IDisposable
|
||||
// happen on a signed release build, but a broken csproj or hand-rolled
|
||||
// dev build can land here. Caller falls back to the system font path
|
||||
// so the plugin still loads instead of crashing the whole UiBuilder.
|
||||
private static byte[]? TryGetHellionFontBytes()
|
||||
private static byte[]? TryGetBundledFontBytes()
|
||||
{
|
||||
if (HellionFontBytes is not null)
|
||||
return HellionFontBytes;
|
||||
if (BundledFontBytes is not null)
|
||||
return BundledFontBytes;
|
||||
|
||||
using var stream = typeof(FontManager).Assembly.GetManifestResourceStream(
|
||||
"HellionFont.ttf"
|
||||
"Inter-Light.ttf"
|
||||
);
|
||||
if (stream is null)
|
||||
{
|
||||
Plugin.LogProxy.Warning(
|
||||
"Hellion font resource missing — falling back to system default font."
|
||||
"Bundled Inter Light font resource missing, falling back to system default font."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
HellionFontBytes = ms.ToArray();
|
||||
return HellionFontBytes;
|
||||
BundledFontBytes = ms.ToArray();
|
||||
return BundledFontBytes;
|
||||
}
|
||||
|
||||
private unsafe void SetUpRanges()
|
||||
@@ -239,6 +262,18 @@ public sealed class FontManager : IDisposable
|
||||
builder.AddText("Œœ");
|
||||
builder.AddText("ĂăÂâÎîȘșȚț");
|
||||
|
||||
// v1.5.3: language-dropdown endonyms. The dropdown renders
|
||||
// with the currently active font range; without these glyphs
|
||||
// a user on an English UI cannot read non-Latin language names
|
||||
// before switching. Auto-activation in Settings.Apply then
|
||||
// pulls in the full ExtraGlyphRange for the chosen locale.
|
||||
builder.AddText(
|
||||
"Català Čeština Dansk Deutsch Ελληνικά English Español Suomi"
|
||||
+ " Français Magyar Italiano 日本語 한국어 Norsk bokmål Nederlands"
|
||||
+ " Polski Português Brasil (Portugal) Română Русский Svenska"
|
||||
+ " Türkçe Українська 简体中文 繁體中文"
|
||||
);
|
||||
|
||||
// "Enclosed Alphanumerics" (partial) https://www.compart.com/en/unicode/block/U+2460
|
||||
for (var i = 0x2460; i <= 0x24B5; i++)
|
||||
builder.AddChar((char)i);
|
||||
@@ -248,11 +283,32 @@ public sealed class FontManager : IDisposable
|
||||
}
|
||||
|
||||
var ranges = new List<nint> { (nint)ImGui.GetIO().Fonts.GetGlyphRangesDefault() };
|
||||
var customChars = new List<ushort>();
|
||||
foreach (var extraRange in Enum.GetValues<ExtraGlyphRanges>())
|
||||
if (Plugin.Config.ExtraGlyphRanges.HasFlag(extraRange))
|
||||
ranges.Add(extraRange.Range());
|
||||
{
|
||||
if (!Plugin.Config.ExtraGlyphRanges.HasFlag(extraRange))
|
||||
continue;
|
||||
|
||||
Ranges = BuildRange(null, ranges.ToArray());
|
||||
// LatinExtended and Greek use AddChar pairs because they have no
|
||||
// built-in ImGui range helper; everything else points to a native
|
||||
// ImGui glyph-range table.
|
||||
switch (extraRange)
|
||||
{
|
||||
case ExtraGlyphRanges.LatinExtended:
|
||||
customChars.AddRange(ExtraGlyphRangesExt.LatinExtendedPairs);
|
||||
break;
|
||||
case ExtraGlyphRanges.Greek:
|
||||
customChars.AddRange(ExtraGlyphRangesExt.GreekPairs);
|
||||
break;
|
||||
default:
|
||||
var ptr = extraRange.Range();
|
||||
if (ptr != 0)
|
||||
ranges.Add(ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ranges = BuildRange(customChars.Count > 0 ? customChars : null, ranges.ToArray());
|
||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
||||
<PropertyGroup>
|
||||
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
|
||||
<Version>1.5.1</Version>
|
||||
<Version>1.5.3</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- Use lock file to pin exact versions -->
|
||||
@@ -50,13 +50,13 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Embedded resources: Hellion font (Exo 2, OFL-1.1) + manifest resource -->
|
||||
<!-- Embedded resources: bundled UI font (Inter Light, OFL-1.1) + manifest resource -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\HellionFont.ttf">
|
||||
<LogicalName>HellionFont.ttf</LogicalName>
|
||||
<EmbeddedResource Include="Resources\Inter-Light.ttf">
|
||||
<LogicalName>Inter-Light.ttf</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\HellionFont-OFL.txt">
|
||||
<LogicalName>HellionFont-OFL.txt</LogicalName>
|
||||
<EmbeddedResource Include="Resources\Inter-OFL.txt">
|
||||
<LogicalName>Inter-OFL.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\Branding\fox-banner.txt">
|
||||
<LogicalName>HellionChat.Branding.fox-banner.txt</LogicalName>
|
||||
|
||||
+111
-84
@@ -15,8 +15,8 @@ description: |-
|
||||
- Per-channel retention with a daily background sweep
|
||||
- Retroactive cleanup (Ctrl+Shift confirm)
|
||||
- Export to Markdown, JSON or CSV
|
||||
- First-run wizard with three preset profiles
|
||||
- Bilingual UI (EN/DE) with live language switching
|
||||
- First-run wizard with four preset profiles
|
||||
- Multi-language UI (24 locales) with live language switching
|
||||
- Own config and database — no shared state with other plugins
|
||||
|
||||
Based on Chat 2 by Infi and Anna (EUPL-1.2).
|
||||
@@ -35,6 +35,115 @@ tags:
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**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).
|
||||
|
||||
---
|
||||
|
||||
**v1.5.2 — First-Run Wizard Rework (2026-05-18)**
|
||||
|
||||
UX patch. The first-run wizard becomes a four-step flow with a
|
||||
new Roleplay privacy profile and a power-settings step that
|
||||
surfaces previously-hidden defaults. Existing v1.5.1 users see
|
||||
the new wizard once on first v1.5.2 boot.
|
||||
|
||||
What changes user-visible:
|
||||
|
||||
- Wizard navigation: Welcome → Privacy profile → Power settings
|
||||
→ Done. Forge-Bronze pagination dots, dedicated stage for the
|
||||
power settings so they are no longer buried in Settings.
|
||||
- Fourth privacy profile "Roleplay": Privacy-First plus Say and
|
||||
both emote types, with a 30-day window for Say and a 90-day
|
||||
window for emotes. Shout, Yell and Novice Network stay out.
|
||||
- Privacy picker becomes a 2x2 grid. Casual stays the
|
||||
recommended option with a ★ marker.
|
||||
- Power-settings step covers Load Previous Session, Filter
|
||||
Include Previous Sessions, Auto-Tell-Tabs History Preload,
|
||||
Compact Density, Prettier Timestamps and a built-in theme
|
||||
picker. All six map to existing Configuration fields — no new
|
||||
settings introduced.
|
||||
- Staged commit: the wizard only writes to Config on the Finish
|
||||
step. Decide-later or X-close at any point leaves the existing
|
||||
config untouched.
|
||||
- Inline test hint on the done step: "type /tell <Player Name>
|
||||
into chat" surfaces the auto-tell-tab spawn mechanism.
|
||||
- Window starts at 720x480 (was 900x560) and can shrink to
|
||||
600x400; Step 1 keeps the fox banner in a folded TreeNode so
|
||||
the onboarding copy stays primary.
|
||||
- Existing users get the new wizard surfaced once on first boot
|
||||
after the update via the new WizardLastShownVersion config
|
||||
field. Future cycles bump the constant only when the wizard
|
||||
itself changes shape.
|
||||
|
||||
Under the hood:
|
||||
|
||||
- WizardStateSmokeStep added to /xlperf alongside the FontManager
|
||||
and ThemeSwitch self-tests.
|
||||
- Twelve new pure-helper xUnit Facts in the Build Suite cover
|
||||
all four privacy profile sets and their retention overrides.
|
||||
|
||||
Migration v17 stays (no schema bump). The Configuration grows
|
||||
one optional string field (WizardLastShownVersion) which
|
||||
defaults to empty for legacy users.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.5.1 — FontAtlas Refactor and Hellion Forge Signature (2026-05-17)**
|
||||
|
||||
Hybrid FontManager refactor plus an embedded provenance mark.
|
||||
@@ -133,86 +242,4 @@ changelog: |-
|
||||
|
||||
---
|
||||
|
||||
**v1.4.10 — Symbol-Picker and Tell-History Fix (2026-05-16)**
|
||||
|
||||
Eleventh and final sub-patch of the v1.4.x polish-sweep series.
|
||||
Symbol picker for the chat input, a tell-history reload fix for
|
||||
users with many active partners, and a closing cleanup sweep
|
||||
before v1.5.0 picks up the DI-container adoption.
|
||||
|
||||
- Symbol picker: a small smile-icon button left of the channel
|
||||
indicator opens a popup with two tabs. The first lists all 161
|
||||
FFXIV PUA glyphs (Dalamud's SeIconChar enum); the second
|
||||
carries 97 server-verified BMP symbols (latin marks, currency,
|
||||
the full Greek alphabet, geometric shapes, suits, notes) —
|
||||
every one of them round-tripped through /echo and /say in a
|
||||
four-round probe so the in-channel render matches what the
|
||||
picker shows. Click drops the glyph at the caret, multi-insert
|
||||
keeps the popup open, and a recent-used strip floats the last
|
||||
sixteen picks across both tabs. Toggle in Settings → Chat →
|
||||
Message behaviour, default on.
|
||||
- Pinned auto-tell tabs reload their full history again: a
|
||||
hidden 500-row scan cap in PreloadHistory used to override the
|
||||
user-configurable AutoTellTabsHistoryPreload setting, so
|
||||
less-frequent pinned partners (rare /tell sessions in an
|
||||
otherwise busy week) lost their backlog. The cap is removed;
|
||||
the (Receiver, Date) index keeps SQL fast, the client-side
|
||||
loop still respects your setting as the upper bound.
|
||||
- Slash-command teardown: /hellion, /hellionView,
|
||||
/hellionDebugger (and #if DEBUG /hellionSeString) wrappers are
|
||||
now cached as private fields. Plugin teardown detaches the
|
||||
live registration instead of re-Register'ing with identical
|
||||
args — closes a latent maintenance hazard from v1.4.9.
|
||||
- v1.4.x polish-sweep wraps up here. The ImGuiListClipper render
|
||||
refactor that was on the v1.4.10 reserve list got dropped
|
||||
after cross-platform smoke showed the scroll rubber-band is a
|
||||
Wine / Linux render-pipeline quirk, not universal — Windows
|
||||
users never saw it. It will get its own platform-targeted
|
||||
spike in a later patch. Next major cycle is v1.5.0 with the
|
||||
DI-container adoption (Microsoft.Extensions.Hosting +
|
||||
ILogger<T>) modelled on Lightless.
|
||||
- Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.4.9 — Plugin-Load Render Polish (2026-05-15)**
|
||||
|
||||
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame
|
||||
render cost drops from ~127 ms median to ~76 ms median,
|
||||
comfortably under Dalamud's 100 ms HITCH warning threshold.
|
||||
|
||||
- First-frame defer: six non-essential rendering sections inside
|
||||
ChatLogWindow skip their first Draw and run one frame later
|
||||
(bottom status bar, channel-name SeString chunks, window bounds
|
||||
check, v0.6.1 hint banner, autocomplete, input-preview
|
||||
calculation). User-visible delay is ~17 ms at 60 fps, hidden
|
||||
inside the post-reload font-atlas build window.
|
||||
- Slash-command centralisation: /hellion, /hellionView,
|
||||
/hellionSeString and /hellionDebugger are registered in
|
||||
LoadAsync instead of inside the corresponding window
|
||||
constructors. The plugin-manager Open and configuration buttons
|
||||
hang on the same path.
|
||||
- Plugin-load profiling logs stay on at Information level
|
||||
(MessageStore connect/migrate, FilterAllTabs, auto-translate
|
||||
warmup) as a regression tripwire — a future load past 100 ms
|
||||
will show up in /xllog without a Debug filter.
|
||||
- ChatTwo IPC compatibility layer: HellionChat now mirrors
|
||||
ChatTwo's full IPC surface (GetChatInputState,
|
||||
ChatInputStateChanged, Register, Unregister, Available,
|
||||
Invoke) under the ChatTwo.* namespace in addition to our
|
||||
existing HellionChat.* provider gates. Third-party
|
||||
integrations that historically only subscribe to ChatTwo's
|
||||
IPC — for example Artisan's and AllaganTools' context-menu
|
||||
hooks — keep working without requiring a code change on their
|
||||
side. Conflict detection prevents ChatTwo from loading in
|
||||
parallel with HellionChat, so there is no slot-collision risk
|
||||
at runtime.
|
||||
- Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
Full history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases
|
||||
|
||||
+31
-1
@@ -216,6 +216,17 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad);
|
||||
|
||||
LanguageChanged(Interface.UiLanguage);
|
||||
|
||||
// v1.5.3 migration: Settings.Apply auto-activates the matching
|
||||
// ExtraGlyphRanges flag on a language CHANGE; a config that already
|
||||
// has e.g. Czech selected from a previous version never goes through
|
||||
// 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
|
||||
// without forcing the user to toggle the language twice.
|
||||
var requiredRanges = Config.LanguageOverride.RequiredGlyphRanges();
|
||||
if (requiredRanges != 0 && !Config.ExtraGlyphRanges.HasFlag(requiredRanges))
|
||||
Config.ExtraGlyphRanges |= requiredRanges;
|
||||
|
||||
ImGuiUtil.Initialize(this);
|
||||
|
||||
DeferredSaveFrames = -1;
|
||||
@@ -321,8 +332,22 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
new SelfTests.ThemeSwitchSelfTestStep(this),
|
||||
new SelfTests.FontManagerCtorSmokeStep(this),
|
||||
new SelfTests.FontPushSmokeStep(this),
|
||||
new SelfTests.WizardStateSmokeStep(this),
|
||||
]);
|
||||
|
||||
// Re-surface the wizard for existing users when a major UX
|
||||
// rework ships. The constant tracks the most recent version
|
||||
// whose wizard should be shown once; bump it in future cycles
|
||||
// that reshape the onboarding flow. Saved immediately so a
|
||||
// pre-Finish crash doesn't loop the prompt forever.
|
||||
const string WizardReshowVersion = "1.5.2";
|
||||
if (Config.WizardLastShownVersion != WizardReshowVersion)
|
||||
{
|
||||
Config.FirstRunCompleted = false;
|
||||
Config.WizardLastShownVersion = WizardReshowVersion;
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
if (!Config.FirstRunCompleted)
|
||||
FirstRunWizard.IsOpen = true;
|
||||
|
||||
@@ -920,7 +945,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// RegularFont is nullable only because the live rebuild path
|
||||
// disposes it before reassigning; both ends of that swap happen on
|
||||
// this same draw thread, so it cannot be null here.
|
||||
using ((Config.FontsEnabled ? FontManager.RegularFont! : FontManager.Axis).Push())
|
||||
// v1.5.3 fix: also push RegularFont when the bundled Inter Light is
|
||||
// selected. Without this, UseHellionFont=true silently fell back to
|
||||
// the FFXIV Axis font because FontsAndColours forces FontsEnabled
|
||||
// off in that branch, and the bundled font never made it into draw.
|
||||
var useRegularFont = Config.FontsEnabled || Config.UseHellionFont;
|
||||
using ((useRegularFont ? FontManager.RegularFont! : FontManager.Axis).Push())
|
||||
WindowSystem.Draw();
|
||||
|
||||
ChatLogWindow.FinalizeFrame();
|
||||
|
||||
@@ -114,4 +114,29 @@ internal static class PrivacyDefaults
|
||||
[ChatType.StandardEmote] = 1,
|
||||
[ChatType.NoviceNetwork] = 1,
|
||||
};
|
||||
|
||||
// Roleplay: Privacy-First + Say + both emote types. Public-distance
|
||||
// channels (Shout, Yell) stay out — they are public-noise from
|
||||
// strangers, not story content. Novice Network also stays out;
|
||||
// it is not RP-adjacent and would dilute the profile's intent.
|
||||
internal static readonly IReadOnlySet<ChatType> RoleplayWhitelist = new HashSet<ChatType>(
|
||||
PrivacyFirstWhitelist
|
||||
)
|
||||
{
|
||||
ChatType.Say,
|
||||
ChatType.CustomEmote,
|
||||
ChatType.StandardEmote,
|
||||
};
|
||||
|
||||
// RP sessions function as story logs: Say + emotes need a longer
|
||||
// window than Casual's 1-day public-chat window. 30 days for Say
|
||||
// keeps in-character dialogue scrollable across multiple sessions,
|
||||
// 90 days for emotes mirrors the Privacy-First conversation default.
|
||||
internal static readonly IReadOnlyDictionary<ChatType, int> RoleplayRetentionOverrides =
|
||||
new Dictionary<ChatType, int>
|
||||
{
|
||||
[ChatType.Say] = 30,
|
||||
[ChatType.CustomEmote] = 90,
|
||||
[ChatType.StandardEmote] = 90,
|
||||
};
|
||||
}
|
||||
|
||||
Binary file not shown.
+33
@@ -116,6 +116,38 @@ internal class HellionStrings
|
||||
internal static string Wizard_Reopen_Button => Get(nameof(Wizard_Reopen_Button));
|
||||
internal static string Wizard_Cancel_Label => Get(nameof(Wizard_Cancel_Label));
|
||||
internal static string Wizard_Cancel_Tooltip => Get(nameof(Wizard_Cancel_Tooltip));
|
||||
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_Footer_Hint => Get(nameof(Wizard_Step1_Footer_Hint));
|
||||
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_Step2_Title => Get(nameof(Wizard_Step2_Title));
|
||||
internal static string Wizard_Step2_RecommendedFooter => Get(nameof(Wizard_Step2_RecommendedFooter));
|
||||
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_Apply => Get(nameof(Wizard_Profile_Roleplay_Apply));
|
||||
internal static string Wizard_Nav_Back => Get(nameof(Wizard_Nav_Back));
|
||||
internal static string Wizard_Nav_Next => Get(nameof(Wizard_Nav_Next));
|
||||
internal static string Wizard_Nav_Finish => Get(nameof(Wizard_Nav_Finish));
|
||||
internal static string Wizard_Step3_Title => Get(nameof(Wizard_Step3_Title));
|
||||
internal static string Wizard_Step3_Section_History => Get(nameof(Wizard_Step3_Section_History));
|
||||
internal static string Wizard_Step3_Section_TellTabs => Get(nameof(Wizard_Step3_Section_TellTabs));
|
||||
internal static string Wizard_Step3_Section_Visual => Get(nameof(Wizard_Step3_Section_Visual));
|
||||
internal static string Wizard_Step3_LoadPreviousSession_Label => Get(nameof(Wizard_Step3_LoadPreviousSession_Label));
|
||||
internal static string Wizard_Step3_FilterIncludePreviousSessions_Label => Get(nameof(Wizard_Step3_FilterIncludePreviousSessions_Label));
|
||||
internal static string Wizard_Step3_AutoTellTabsHistoryPreload_Label => Get(nameof(Wizard_Step3_AutoTellTabsHistoryPreload_Label));
|
||||
internal static string Wizard_Step3_UseCompactDensity_Label => Get(nameof(Wizard_Step3_UseCompactDensity_Label));
|
||||
internal static string Wizard_Step3_PrettierTimestamps_Label => Get(nameof(Wizard_Step3_PrettierTimestamps_Label));
|
||||
internal static string Wizard_Step3_Theme_Label => Get(nameof(Wizard_Step3_Theme_Label));
|
||||
internal static string Wizard_Step4_Title => Get(nameof(Wizard_Step4_Title));
|
||||
internal static string Wizard_Step4_SummaryHeading => Get(nameof(Wizard_Step4_SummaryHeading));
|
||||
internal static string Wizard_Step4_Summary_Profile => Get(nameof(Wizard_Step4_Summary_Profile));
|
||||
internal static string Wizard_Step4_Summary_History => Get(nameof(Wizard_Step4_Summary_History));
|
||||
internal static string Wizard_Step4_Summary_TellTabs => Get(nameof(Wizard_Step4_Summary_TellTabs));
|
||||
internal static string Wizard_Step4_Summary_Visual => Get(nameof(Wizard_Step4_Summary_Visual));
|
||||
internal static string Wizard_Step4_Summary_Unchanged => Get(nameof(Wizard_Step4_Summary_Unchanged));
|
||||
internal static string Wizard_Step4_TestHint => Get(nameof(Wizard_Step4_TestHint));
|
||||
internal static string Wizard_Step4_SettingsHint => Get(nameof(Wizard_Step4_SettingsHint));
|
||||
|
||||
internal static string Export_Heading => Get(nameof(Export_Heading));
|
||||
internal static string Export_Help => Get(nameof(Export_Help));
|
||||
@@ -251,6 +283,7 @@ internal class HellionStrings
|
||||
internal static string Settings_General_Audio_Heading => Get(nameof(Settings_General_Audio_Heading));
|
||||
internal static string Settings_General_Performance_Heading => Get(nameof(Settings_General_Performance_Heading));
|
||||
internal static string Settings_General_Language_Heading => Get(nameof(Settings_General_Language_Heading));
|
||||
internal static string Settings_Language_FFXIVCoverage_Warning => Get(nameof(Settings_Language_FFXIVCoverage_Warning));
|
||||
|
||||
// Hellion Chat — Appearance-Tab section headings
|
||||
internal static string Settings_Appearance_Theme_Heading => Get(nameof(Settings_Appearance_Theme_Heading));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -223,11 +223,107 @@
|
||||
<value>Wizard erneut zeigen</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Label" xml:space="preserve">
|
||||
<value>Später — Defaults behalten</value>
|
||||
<value>Später: Defaults behalten</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Tooltip" xml:space="preserve">
|
||||
<value>Schließt den Wizard ohne Profil-Auswahl. Die Plugin-Defaults bleiben aktiv und der Wizard erscheint beim nächsten Plugin-Reload erneut.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Title" xml:space="preserve">
|
||||
<value>Willkommen bei Hellion Chat</value>
|
||||
</data>
|
||||
<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>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
|
||||
<value>3 kurze Schritte. Du kannst alles später unter Einstellungen → Hellion Chat ändern.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Skip_Label" xml:space="preserve">
|
||||
<value>Später entscheiden</value>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Skip_Tooltip" xml:space="preserve">
|
||||
<value>Assistenten schließen. Die Plugin-Standardwerte bleiben aktiv. Du kannst den Assistenten über Einstellungen → Hellion Chat erneut öffnen.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step2_Title" xml:space="preserve">
|
||||
<value>Was darf gespeichert werden?</value>
|
||||
</data>
|
||||
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve">
|
||||
<value>★ = empfohlen für die meisten Spieler.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
|
||||
<value>Roleplay</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Description" xml:space="preserve">
|
||||
<value>Wie Datensparsamkeit, plus Sagen und beide Emote-Typen für deine Story-Logs. Schreien und Rufen bleiben außen vor. Public-Distance-Lärm von Fremden ist kein Story-Inhalt. Aufbewahrung: Sagen 30 Tage, Emotes 90 Tage.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Apply" xml:space="preserve">
|
||||
<value>Roleplay übernehmen</value>
|
||||
</data>
|
||||
<data name="Wizard_Nav_Back" xml:space="preserve">
|
||||
<value>‹ Zurück</value>
|
||||
</data>
|
||||
<data name="Wizard_Nav_Next" xml:space="preserve">
|
||||
<value>Weiter ›</value>
|
||||
</data>
|
||||
<data name="Wizard_Nav_Finish" xml:space="preserve">
|
||||
<value>Fertig ✓</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Title" xml:space="preserve">
|
||||
<value>Versteckte Defaults</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Section_History" xml:space="preserve">
|
||||
<value>Verlauf</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Section_TellTabs" xml:space="preserve">
|
||||
<value>Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Section_Visual" xml:space="preserve">
|
||||
<value>Optik</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_LoadPreviousSession_Label" xml:space="preserve">
|
||||
<value>Vorherige Session beim Start laden</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_FilterIncludePreviousSessions_Label" xml:space="preserve">
|
||||
<value>Filter auch auf alte Messages anwenden</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_AutoTellTabsHistoryPreload_Label" xml:space="preserve">
|
||||
<value>N Tell-Messages beim Öffnen eines Auto-Tabs vorladen</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_UseCompactDensity_Label" xml:space="preserve">
|
||||
<value>Kompakter Density-Modus</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_PrettierTimestamps_Label" xml:space="preserve">
|
||||
<value>Schönere Timestamps (relative Zeit)</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Theme_Label" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Title" xml:space="preserve">
|
||||
<value>Du bist startklar</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_SummaryHeading" xml:space="preserve">
|
||||
<value>Deine Konfiguration</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Profile" xml:space="preserve">
|
||||
<value>Profil: {0}</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_History" xml:space="preserve">
|
||||
<value>Verlauf: {0}</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_TellTabs" xml:space="preserve">
|
||||
<value>Tell-Tabs: {0} Messages vorladen</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Visual" xml:space="preserve">
|
||||
<value>Optik: {0}</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Unchanged" xml:space="preserve">
|
||||
<value>(unverändert)</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_TestHint" xml:space="preserve">
|
||||
<value>💡 Probier's aus: Tipp /tell <Spielername> 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 name="Wizard_Step4_SettingsHint" xml:space="preserve">
|
||||
<value>Einstellungen → Hellion Chat zum späteren Anpassen</value>
|
||||
</data>
|
||||
<data name="Export_Heading" xml:space="preserve">
|
||||
<value>Export (DSGVO Art. 15 — Auskunftsrecht)</value>
|
||||
</data>
|
||||
@@ -289,10 +385,10 @@
|
||||
<value>Wie deckend die Plugin-Fenster sind. Niedrigere Werte lassen das Spiel durchscheinen, Form-Felder und Dialoge bleiben oben drauf deckend und gut lesbar.</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Name" xml:space="preserve">
|
||||
<value>Mitgelieferte Hellion-Schrift (Exo 2) verwenden</value>
|
||||
<value>Mitgelieferte Inter Light verwenden</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Description" xml:space="preserve">
|
||||
<value>Rendert Chat und UI in Exo 2 (SIL Open Font License 1.1), die mit dem Plugin ausgeliefert wird. Deaktivieren, um auf die unter Einstellungen → Schrift gewählte Schriftart zurückzufallen.</value>
|
||||
<value>Stellt Chat und UI in Inter Light (SIL Open Font License 1.1) dar, die mit dem Plugin geliefert wird. Deaktivieren, um zur Schrift aus Einstellungen → Schriftart zurückzukehren.</value>
|
||||
</data>
|
||||
|
||||
<data name="About_Maintainer_Heading" xml:space="preserve">
|
||||
@@ -399,7 +495,7 @@
|
||||
<value>Maximal {0} angepinnte Tell-Tabs erreicht. Erst einen lösen oder dauerhaft behalten.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinnedTooltip" xml:space="preserve">
|
||||
<value>Angepinnt — überlebt Relog.</value>
|
||||
<value>Angepinnt: überlebt Relog.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||
<value>Angepinnte Tabs überleben Relog und behalten die Bindung an die Tell-Person.</value>
|
||||
@@ -440,7 +536,7 @@
|
||||
<value>„Als begrüßt markieren"-Button anzeigen</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Fügt neben jedem Auto-Tell-Tab einen Klick-Button hinzu, um einen Gesprächspartner als bereits begrüßt zu markieren — der Tab-Name wird dann gedimmt. Nützlich für Club-Greeter, die parallel viele Konversationen führen. Standardmäßig aus.</value>
|
||||
<value>Fügt neben jedem Auto-Tell-Tab einen Klick-Button hinzu, um einen Gesprächspartner als bereits begrüßt zu markieren: der Tab-Name wird dann gedimmt. Nützlich für Club-Greeter, die parallel viele Konversationen führen. Standardmäßig aus.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Name" xml:space="preserve">
|
||||
<value>Neue /tell-Tabs direkt als Pop-Out öffnen</value>
|
||||
@@ -809,7 +905,7 @@
|
||||
<value>Fenster-Transparenz</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Description" xml:space="preserve">
|
||||
<value>Wie durchsichtig der Fensterhintergrund ist. Niedrigere Werte lassen mehr vom Spiel durchscheinen. Tipp: Dalamud's Per-Window-Menü (Hamburger in der Titelleiste) bietet pro Fenster eigene Overrides für Deckkraft, Hintergrund-Blur, Durchklick und Anpinnen — die haben Vorrang über diesen Slider für das jeweilige Fenster.</value>
|
||||
<value>Wie durchsichtig der Fensterhintergrund ist. Niedrigere Werte lassen mehr vom Spiel durchscheinen. Tipp: Dalamud's Per-Window-Menü (Hamburger in der Titelleiste) bietet pro Fenster eigene Overrides für Deckkraft, Hintergrund-Blur, Durchklick und Anpinnen: die haben Vorrang über diesen Slider für das jeweilige Fenster.</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Fonts_Heading" xml:space="preserve">
|
||||
<value>Schriftarten</value>
|
||||
@@ -934,4 +1030,7 @@
|
||||
<data name="DbViewer_FullTextToggle_Hint_PhraseMode" xml:space="preserve">
|
||||
<value>Sucht nach der exakten Wortfolge. Mehrere Wörter werden nur gefunden, wenn sie zusammen und in dieser Reihenfolge stehen. Wer rohe FTS5-MATCH-Syntax nutzen will, setzt eigene Anführungszeichen um den Suchbegriff.</value>
|
||||
</data>
|
||||
<data name="Settings_Language_FFXIVCoverage_Warning" xml:space="preserve">
|
||||
<value>HellionChat zeigt alle 24 Sprachen, aber FFXIVs Chat-Eingabe unterstützt nur EN, DE, FR und JA vollständig. Andere Schriften können beim Tippen in den Spiel-Chat oder beim Senden von Nachrichten als unleserliche Zeichen erscheinen.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@
|
||||
<value>Enable privacy filter</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_Description" xml:space="preserve">
|
||||
<value>When enabled, only messages from allowed channels are written to the database. When disabled, the default behaviour applies — everything except battle logs is stored.</value>
|
||||
<value>When enabled, only messages from allowed channels are written to the database. When disabled, the default behaviour applies. Everything except battle logs is stored.</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_StorageOnly_Help" xml:space="preserve">
|
||||
<value>The filter only controls what is written to the local database. The chat log still shows every message live; excluded channels are simply no longer stored. If you also want to remove channels from the visible display, use the normal chat-tab filters in the game.</value>
|
||||
@@ -82,7 +82,7 @@
|
||||
<value>The manual run uses your SAVED retention policy, not the slider values above. Click Save first if you want the run to apply your current changes.</value>
|
||||
</data>
|
||||
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||
<value>Preview is stale — your whitelist has changed since the last refresh. Click Refresh to recalculate.</value>
|
||||
<value>Preview is stale: your whitelist has changed since the last refresh. Click Refresh to recalculate.</value>
|
||||
</data>
|
||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||
<value>Refresh preview</value>
|
||||
@@ -133,7 +133,7 @@
|
||||
<value>Automatically delete messages past their channel retention window</value>
|
||||
</data>
|
||||
<data name="Retention_Enabled_Description" xml:space="preserve">
|
||||
<value>When enabled, messages older than the configured window are deleted on each plugin start (at most once every 24 hours). Default is OFF — the plugin never deletes anything without your explicit consent.</value>
|
||||
<value>When enabled, messages older than the configured window are deleted on each plugin start (at most once every 24 hours). Default is OFF. The plugin never deletes anything without your explicit consent.</value>
|
||||
</data>
|
||||
<data name="Retention_Default_Label" xml:space="preserve">
|
||||
<value>Default retention (days, 0 = never)</value>
|
||||
@@ -223,11 +223,107 @@
|
||||
<value>Show wizard again</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Label" xml:space="preserve">
|
||||
<value>Later — keep defaults</value>
|
||||
<value>Later: keep defaults</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Tooltip" xml:space="preserve">
|
||||
<value>Close the wizard without selecting a profile. The plugin defaults stay active and the wizard returns on next plugin load.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Title" xml:space="preserve">
|
||||
<value>Welcome to Hellion Chat</value>
|
||||
</data>
|
||||
<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>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Footer_Hint" xml:space="preserve">
|
||||
<value>Three short steps. You can change everything later under Settings → Hellion Chat.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Skip_Label" xml:space="preserve">
|
||||
<value>Decide later</value>
|
||||
</data>
|
||||
<data name="Wizard_Step1_Skip_Tooltip" xml:space="preserve">
|
||||
<value>Close the wizard. The plugin defaults stay active. You can reopen the wizard from Settings → Hellion Chat.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step2_Title" xml:space="preserve">
|
||||
<value>What gets stored?</value>
|
||||
</data>
|
||||
<data name="Wizard_Step2_RecommendedFooter" xml:space="preserve">
|
||||
<value>★ = recommended for most players.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Heading" xml:space="preserve">
|
||||
<value>Roleplay</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Description" xml:space="preserve">
|
||||
<value>Like Privacy First, plus Say and both emote types for your story logs. Shout and Yell stay out. Public-distance noise from strangers is not story content. Retention: Say 30 days, emotes 90 days.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Apply" xml:space="preserve">
|
||||
<value>Apply roleplay</value>
|
||||
</data>
|
||||
<data name="Wizard_Nav_Back" xml:space="preserve">
|
||||
<value>‹ Back</value>
|
||||
</data>
|
||||
<data name="Wizard_Nav_Next" xml:space="preserve">
|
||||
<value>Next ›</value>
|
||||
</data>
|
||||
<data name="Wizard_Nav_Finish" xml:space="preserve">
|
||||
<value>Finish ✓</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Title" xml:space="preserve">
|
||||
<value>Hidden defaults</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Section_History" xml:space="preserve">
|
||||
<value>History</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Section_TellTabs" xml:space="preserve">
|
||||
<value>Tell tabs</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Section_Visual" xml:space="preserve">
|
||||
<value>Visual</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_LoadPreviousSession_Label" xml:space="preserve">
|
||||
<value>Load previous session on startup</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_FilterIncludePreviousSessions_Label" xml:space="preserve">
|
||||
<value>Apply filters to messages from previous sessions</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_AutoTellTabsHistoryPreload_Label" xml:space="preserve">
|
||||
<value>Preload N tell messages when an auto-tab opens</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_UseCompactDensity_Label" xml:space="preserve">
|
||||
<value>Compact density</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_PrettierTimestamps_Label" xml:space="preserve">
|
||||
<value>Prettier timestamps (relative time)</value>
|
||||
</data>
|
||||
<data name="Wizard_Step3_Theme_Label" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Title" xml:space="preserve">
|
||||
<value>You're all set</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_SummaryHeading" xml:space="preserve">
|
||||
<value>Your configuration</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Profile" xml:space="preserve">
|
||||
<value>Profile: {0}</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_History" xml:space="preserve">
|
||||
<value>History: {0}</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_TellTabs" xml:space="preserve">
|
||||
<value>Tell tabs: preload {0} messages</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Visual" xml:space="preserve">
|
||||
<value>Visual: {0}</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_Summary_Unchanged" xml:space="preserve">
|
||||
<value>(unchanged)</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_TestHint" xml:space="preserve">
|
||||
<value>💡 Try it: type /tell <Player Name> into chat. Hellion Chat opens a dedicated tab for the conversation and preloads the last {0} messages.</value>
|
||||
</data>
|
||||
<data name="Wizard_Step4_SettingsHint" xml:space="preserve">
|
||||
<value>Settings → Hellion Chat to fine-tune later</value>
|
||||
</data>
|
||||
<data name="Export_Heading" xml:space="preserve">
|
||||
<value>Export (GDPR Art. 15 — Right of access)</value>
|
||||
</data>
|
||||
@@ -289,10 +385,10 @@
|
||||
<value>How opaque the plugin windows are. Lower values let the game show through; form fields and dialogs stay fully opaque and readable on top.</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Name" xml:space="preserve">
|
||||
<value>Use bundled Hellion font (Exo 2)</value>
|
||||
<value>Use bundled Inter Light</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Description" xml:space="preserve">
|
||||
<value>Renders chat and UI in Exo 2 (SIL Open Font License 1.1), which ships with the plugin. Disable to fall back to the font selected under Settings → Font.</value>
|
||||
<value>Renders chat and UI in Inter Light (SIL Open Font License 1.1), which ships with the plugin. Disable to fall back to the font selected under Settings → Font.</value>
|
||||
</data>
|
||||
|
||||
<data name="About_Maintainer_Heading" xml:space="preserve">
|
||||
@@ -325,7 +421,7 @@
|
||||
<value>Hellion Chat is a fork of Chat 2 by Infi and Anna (ascclemens). The chat-replacement window, IPC integration, render engine, and the entire storage core all come from the original.</value>
|
||||
</data>
|
||||
<data name="About_BuiltOn_P2" xml:space="preserve">
|
||||
<value>The web interface is the only major piece I removed. It is built for remote access to the chat from a second device — a different focus from the smaller default footprint this fork pursues. Adapting it to these defaults would have required significant rework, so removing it was the clean path for this particular fork.</value>
|
||||
<value>The web interface is the only major piece I removed. It is built for remote access to the chat from a second device: a different focus from the smaller default footprint this fork pursues. Adapting it to these defaults would have required significant rework, so removing it was the clean path for this particular fork.</value>
|
||||
</data>
|
||||
<data name="About_BuiltOn_Upstream_Label" xml:space="preserve">
|
||||
<value>Upstream repository:</value>
|
||||
@@ -393,7 +489,7 @@
|
||||
<value>Promote to permanent</value>
|
||||
</data>
|
||||
<data name="PinTab_PromoteTooltip" xml:space="preserve">
|
||||
<value>Turns this TempTell into a regular tab. The tell binding to the partner is dropped — the tab will catch messages by its channel filters from now on. For "tab survives relog while staying bound to this partner", use Pin Tab instead.</value>
|
||||
<value>Turns this TempTell into a regular tab. The tell binding to the partner is dropped. The tab will catch messages by its channel filters from now on. For "tab survives relog while staying bound to this partner", use Pin Tab instead.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||
<value>Pinned tabs survive relog and stay bound to this conversation partner.</value>
|
||||
@@ -411,7 +507,7 @@
|
||||
<value>Maximum of {0} pinned tell tabs reached. Unpin one first, or use Promote to permanent.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinnedTooltip" xml:space="preserve">
|
||||
<value>Pinned — survives relog.</value>
|
||||
<value>Pinned: survives relog.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Chat settings tab) -->
|
||||
@@ -440,7 +536,7 @@
|
||||
<value>Show "Mark as greeted" button</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Adds a click button next to each auto-tell tab to mark a conversation partner as already greeted — the tab name is then dimmed. Useful for club greeters managing many conversations in parallel. Off by default.</value>
|
||||
<value>Adds a click button next to each auto-tell tab to mark a conversation partner as already greeted. The tab name is then dimmed. Useful for club greeters managing many conversations in parallel. Off by default.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Name" xml:space="preserve">
|
||||
<value>Open new /tell tabs directly as pop-outs</value>
|
||||
@@ -809,7 +905,7 @@
|
||||
<value>Window transparency</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Description" xml:space="preserve">
|
||||
<value>How transparent the window background is. Lower values let more of the game show through. Tip: Dalamud's per-window menu (hamburger in the title bar) offers per-window overrides for opacity, background blur, click-through, and pinning — those take precedence over this slider for the respective window.</value>
|
||||
<value>How transparent the window background is. Lower values let more of the game show through. Tip: Dalamud's per-window menu (hamburger in the title bar) offers per-window overrides for opacity, background blur, click-through, and pinning. Those take precedence over this slider for the respective window.</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Fonts_Heading" xml:space="preserve">
|
||||
<value>Fonts</value>
|
||||
@@ -934,4 +1030,7 @@
|
||||
<data name="DbViewer_FullTextToggle_Hint_PhraseMode" xml:space="preserve">
|
||||
<value>Searches for the exact phrase. Multi-word queries match only when the words appear together in order. To use raw FTS5 MATCH syntax, wrap your term in double quotes yourself.</value>
|
||||
</data>
|
||||
<data name="Settings_Language_FFXIVCoverage_Warning" xml:space="preserve">
|
||||
<value>HellionChat renders all 24 languages, but FFXIV's chat input only fully supports EN, DE, FR and JA. Other scripts may display as garbled characters when typed into the in-game chat or sent as messages.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Executable → Regular
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright 2013 The Exo 2 Project Authors (https://github.com/googlefonts/Exo-2.0)
|
||||
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
+18
@@ -1860,6 +1860,24 @@ namespace HellionChat.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Latin Extended.
|
||||
/// </summary>
|
||||
internal static string ExtraGlyphRanges_LatinExtended_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("ExtraGlyphRanges_LatinExtended_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Greek.
|
||||
/// </summary>
|
||||
internal static string ExtraGlyphRanges_Greek_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("ExtraGlyphRanges_Greek_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pick a folder location for export..
|
||||
/// </summary>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Tenyeix el selector de canal amb el color del canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>El botó selector de canal al costat del camp d'entrada es tenyeix amb el color del canal actiu. Coincideix amb la tonalitat del text d'entrada.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Amaga mentre el menú New Game+ estigui obert</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Amaga el xat mentre el menú New Game+ estigui obert. En tancar el menú, el xat torna a aparèixer.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Llatí estès</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grec</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+20
@@ -1,4 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Language.de.resx — Hellion Forge maintainer-extended translation
|
||||
|
||||
Locale: de (German)
|
||||
Maintainer: Hellion Forge / Hellion Online Media
|
||||
Status: Native-speaker maintained
|
||||
Review: Continuous (native maintainer)
|
||||
|
||||
Hellion Forge maintains this file with native-speaker quality,
|
||||
including the keys post-dating the last upstream Chat 2 Crowdin sync.
|
||||
|
||||
Corrections welcome via the Hellion Forge Discord:
|
||||
https://discord.gg/X9V7Kcv5gR
|
||||
-->
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -1481,4 +1495,10 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latein erweitert</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Griechisch</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Teñir el selector de canal con el color del canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>El botón selector de canal junto al campo de entrada se tiñe con el color del canal activo. Coincide con el tinte del texto de entrada.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Ocultar mientras el menú New Game+ esté abierto</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Oculta el chat mientras el menú New Game+ esté abierto. Al cerrar el menú, el chat se muestra de nuevo.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latín extendido</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Griego</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Teinter le sélecteur de canal avec la couleur du canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Le bouton sélecteur de canal à côté du champ de saisie est teinté avec la couleur du canal actif. Correspond à la teinte du texte de saisie.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Masquer pendant que le menu New Game+ est ouvert</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Masque le chat pendant que le menu New Game+ est ouvert. Fermer le menu réaffiche le chat.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latin étendu</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grec</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Colora il selettore di canale con il colore del canale</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Il pulsante selettore di canale accanto al campo di input viene colorato con il colore del canale attivo. Corrisponde alla colorazione del testo di input.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Nascondi mentre il menu New Game+ è aperto</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Nasconde la chat mentre il menu New Game+ è aperto. Chiudendo il menu, la chat riappare.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latino esteso</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Greco</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>チャンネルセレクターをチャンネル色で着色する</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>入力フィールドの隣のチャンネルセレクターボタンが、現在アクティブなチャンネルの色で着色されます。入力テキスト自体の色合いと一致します。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>ニューゲーム+メニューが開いている間は非表示</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>ニューゲーム+メニューが開いている間、チャットを非表示にします。メニューを閉じるとチャットが再表示されます。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>拡張ラテン</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>ギリシャ語</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>채널 선택기를 채널 색상으로 채색</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>입력 필드 옆의 채널 선택기 버튼이 현재 활성 채널 색상으로 채색됩니다. 입력 텍스트 자체의 색조와 일치합니다.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>뉴게임+ 메뉴가 열려 있는 동안 숨김</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>뉴게임+ 메뉴가 열려 있는 동안 채팅을 숨깁니다. 메뉴를 닫으면 채팅이 다시 표시됩니다.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>확장 라틴</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>그리스어</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Kanaalkiezer kleuren met kanaalkleur</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>De kanaalkiezerknop naast het invoerveld krijgt de kleur van het actieve kanaal. Komt overeen met de tint van de invoertekst zelf.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Verbergen terwijl het New Game+ menu open is</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Verberg de chat terwijl het New Game+ menu open is. Het sluiten van het menu toont de chat weer.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latijn uitgebreid</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grieks</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Colorir o seletor de canal com a cor do canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>O botão seletor de canal ao lado do campo de entrada é colorido com a cor do canal ativo. Combina com a coloração do próprio texto de entrada.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Ocultar enquanto o menu New Game+ estiver aberto</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Oculta o chat enquanto o menu New Game+ estiver aberto. Fechar o menu mostra o chat novamente.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latim estendido</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grego</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
+1503
File diff suppressed because it is too large
Load Diff
@@ -1478,4 +1478,10 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latin Extended</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Greek</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Colorează selectorul de canal cu culoarea canalului</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Butonul selector de canal de lângă câmpul de intrare este colorat cu culoarea canalului activ. Se potrivește cu nuanța textului de intrare.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Ascunde cât timp meniul New Game+ este deschis</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Ascunde chatul cât timp meniul New Game+ este deschis. Închiderea meniului afișează chatul din nou.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latină extinsă</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Greacă</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Окрашивать кнопку выбора канала в цвет канала</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Кнопка выбора канала рядом с полем ввода окрашивается в цвет активного канала. Совпадает с окраской самого текста ввода.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Скрывать, пока открыто меню New Game+</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Скрывать чат, пока открыто меню New Game+. При закрытии меню чат снова отображается.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Расширенная латиница</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Греческий</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Färga kanalväljaren med kanalens färg</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Kanalväljarknappen bredvid inmatningsfältet färgas med den aktiva kanalens färg. Matchar färgningen av själva inmatningstexten.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Dölj medan New Game+ menyn är öppen</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Dölj chatten medan New Game+ menyn är öppen. När menyn stängs visas chatten igen.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Utökat latin</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grekiska</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+1503
File diff suppressed because it is too large
Load Diff
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>用频道颜色为频道选择器染色</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>输入框旁边的频道选择器按钮将以当前活动频道的颜色着色。与输入文本本身的着色相匹配。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>在新游戏+菜单打开时隐藏</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>在新游戏+菜单打开时隐藏聊天。关闭菜单时聊天会再次显示。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>拉丁文扩展</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>希腊语</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
+24
@@ -1467,4 +1467,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>用頻道顏色為頻道選擇器染色</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>輸入框旁邊的頻道選擇器按鈕將以當前活動頻道的顏色著色。與輸入文字本身的著色相匹配。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>在新遊戲+選單開啟時隱藏</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>在新遊戲+選單開啟時隱藏聊天。關閉選單時聊天會再次顯示。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>拉丁文擴展</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>希臘文</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Plugin.SelfTest;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Ui;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// Drives the FirstRunWizard state machine through every step and
|
||||
// commits a no-op pending state (Variant 1), then re-runs picking
|
||||
// Roleplay on Step 2 and skipping Step 3 (Variant 2). Verifies
|
||||
// that the staged-commit path does not throw under any combination
|
||||
// of Pending* values and that CommitPending leaves Config in a
|
||||
// readable shape. Variant 2's Roleplay commit would normally
|
||||
// mutate the six PrivacyFilter / Retention fields ApplyRoleplay
|
||||
// touches, so the step snapshots them before Variant 2 runs and
|
||||
// CleanUp() restores them — the self-test stays idempotent across
|
||||
// repeated /xlperf runs and does not overwrite an active privacy
|
||||
// profile.
|
||||
internal sealed class WizardStateSmokeStep : ISelfTestStep
|
||||
{
|
||||
private readonly Plugin plugin;
|
||||
|
||||
// Snapshot slots for the six Configuration fields ApplyRoleplay
|
||||
// writes in Variant 2. Populated right before Variant 2 mutates
|
||||
// Config, consumed by CleanUp(). Reference-typed snapshots
|
||||
// (HashSet, Dictionary) capture the existing slot by reference,
|
||||
// which is safe because ApplyRoleplay reassigns the slot with
|
||||
// a fresh instance instead of mutating in place.
|
||||
private bool? snapshotPrivacyFilterEnabled;
|
||||
private HashSet<ChatType>? snapshotPrivacyPersistChannels;
|
||||
private bool? snapshotPrivacyPersistUnknownChannels;
|
||||
private bool? snapshotRetentionEnabled;
|
||||
private int? snapshotRetentionDefaultDays;
|
||||
private Dictionary<ChatType, int>? snapshotRetentionPerChannelDays;
|
||||
|
||||
public WizardStateSmokeStep(Plugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public string Name => "Hellion Chat - FirstRunWizard state smoke";
|
||||
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
var wizard = this.plugin.FirstRunWizard;
|
||||
if (wizard is null)
|
||||
{
|
||||
ImGui.Text("Plugin.FirstRunWizard is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Variant 1: no-op CommitPending. Walks the state machine and
|
||||
// verifies the empty-pending write-back path does not throw.
|
||||
wizard.TestOnly_AdvanceTo(1);
|
||||
wizard.TestOnly_AdvanceTo(2);
|
||||
wizard.TestOnly_AdvanceTo(3);
|
||||
wizard.TestOnly_AdvanceTo(4);
|
||||
wizard.CommitPending();
|
||||
|
||||
// Variant 2: skip Step 3 explicitly. Picks Roleplay on Step 2,
|
||||
// jumps straight to Step 4 (no Step-3 entry → no seed for
|
||||
// LoadPreviousSession / FilterIncludePreviousSessions), commits,
|
||||
// and asserts the two coupled history toggles remained on their
|
||||
// pre-test value. Pins the null-semantics from Spec Z.176 so a
|
||||
// regression in CommitPending that started writing seeded
|
||||
// recommendations unconditionally would surface here.
|
||||
// CommitPending → ApplyRoleplay overwrites six privacy /
|
||||
// retention fields, so snapshot them first and let CleanUp
|
||||
// restore them after the assert. Keeps /xlperf idempotent.
|
||||
this.snapshotPrivacyFilterEnabled = Plugin.Config.PrivacyFilterEnabled;
|
||||
this.snapshotPrivacyPersistChannels = Plugin.Config.PrivacyPersistChannels;
|
||||
this.snapshotPrivacyPersistUnknownChannels = Plugin
|
||||
.Config
|
||||
.PrivacyPersistUnknownChannels;
|
||||
this.snapshotRetentionEnabled = Plugin.Config.RetentionEnabled;
|
||||
this.snapshotRetentionDefaultDays = Plugin.Config.RetentionDefaultDays;
|
||||
this.snapshotRetentionPerChannelDays = Plugin.Config.RetentionPerChannelDays;
|
||||
|
||||
var loadPrevBefore = Plugin.Config.LoadPreviousSession;
|
||||
var filterPrevBefore = Plugin.Config.FilterIncludePreviousSessions;
|
||||
wizard.TestOnly_AdvanceTo(2);
|
||||
wizard.TestOnly_SetPendingProfile(FirstRunWizard.PrivacyProfile.Roleplay);
|
||||
wizard.TestOnly_AdvanceTo(4);
|
||||
wizard.CommitPending();
|
||||
if (Plugin.Config.LoadPreviousSession != loadPrevBefore)
|
||||
{
|
||||
ImGui.Text("Skip-Step-3 path overwrote LoadPreviousSession");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
if (Plugin.Config.FilterIncludePreviousSessions != filterPrevBefore)
|
||||
{
|
||||
ImGui.Text("Skip-Step-3 path overwrote FilterIncludePreviousSessions");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ImGui.Text($"Wizard state smoke threw: {ex.GetType().Name}: {ex.Message}");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
return SelfTestStepResult.Pass;
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
// Restore the six Variant-2 snapshots so back-to-back /xlperf
|
||||
// runs don't drift the active privacy profile. If Variant 2
|
||||
// never ran (Variant 1 threw early), the slots stay null and
|
||||
// restore is a no-op. After restore the slots are nulled so a
|
||||
// future RunStep starts fresh.
|
||||
if (this.snapshotPrivacyFilterEnabled is { } privacyFilter)
|
||||
Plugin.Config.PrivacyFilterEnabled = privacyFilter;
|
||||
if (this.snapshotPrivacyPersistChannels is { } persistChannels)
|
||||
Plugin.Config.PrivacyPersistChannels = persistChannels;
|
||||
if (this.snapshotPrivacyPersistUnknownChannels is { } persistUnknown)
|
||||
Plugin.Config.PrivacyPersistUnknownChannels = persistUnknown;
|
||||
if (this.snapshotRetentionEnabled is { } retentionEnabled)
|
||||
Plugin.Config.RetentionEnabled = retentionEnabled;
|
||||
if (this.snapshotRetentionDefaultDays is { } retentionDays)
|
||||
Plugin.Config.RetentionDefaultDays = retentionDays;
|
||||
if (this.snapshotRetentionPerChannelDays is { } retentionPolicy)
|
||||
Plugin.Config.RetentionPerChannelDays = retentionPolicy;
|
||||
|
||||
this.snapshotPrivacyFilterEnabled = null;
|
||||
this.snapshotPrivacyPersistChannels = null;
|
||||
this.snapshotPrivacyPersistUnknownChannels = null;
|
||||
this.snapshotRetentionEnabled = null;
|
||||
this.snapshotRetentionDefaultDays = null;
|
||||
this.snapshotRetentionPerChannelDays = null;
|
||||
}
|
||||
}
|
||||
+567
-103
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
@@ -6,13 +7,34 @@ using HellionChat.Branding;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Privacy;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Themes;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
// Multi-step first-run wizard. public sealed because Plugin.cs has a
|
||||
// public-typed property on this class — narrowing to internal would
|
||||
// be a build break across the assembly boundary. State lives in a
|
||||
// nested WizardState record; every step writes nullable Pending*
|
||||
// fields, and CommitPending() applies only the non-null ones so
|
||||
// users who skip a step never get their existing config overwritten.
|
||||
public sealed class FirstRunWizard : Window
|
||||
{
|
||||
// Forge-Bronze (#C2410C). The same constant lives in ThemeRegistry
|
||||
// and the forge-announce workflow; pinning it locally keeps the
|
||||
// wizard render path free of registry lookups during draw.
|
||||
private static readonly Vector4 ForgeBronze = new(0xC2 / 255f, 0x41 / 255f, 0x0C / 255f, 1f);
|
||||
private static readonly Vector4 ForgeBronzeDim = new(
|
||||
0xC2 / 255f,
|
||||
0x41 / 255f,
|
||||
0x0C / 255f,
|
||||
0.3f
|
||||
);
|
||||
|
||||
private const int TotalSteps = 4;
|
||||
|
||||
private readonly Plugin Plugin;
|
||||
private readonly WizardState _state = new();
|
||||
|
||||
internal FirstRunWizard(Plugin plugin)
|
||||
: base($"{HellionStrings.Wizard_Title}###hellion-firstrun")
|
||||
@@ -21,10 +43,10 @@ public sealed class FirstRunWizard : Window
|
||||
|
||||
Flags = ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoDocking;
|
||||
SizeCondition = ImGuiCond.Appearing;
|
||||
Size = new Vector2(900, 560);
|
||||
Size = new Vector2(720, 480);
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
MinimumSize = new Vector2(720, 480),
|
||||
MinimumSize = new Vector2(600, 400),
|
||||
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
|
||||
};
|
||||
}
|
||||
@@ -32,138 +54,536 @@ public sealed class FirstRunWizard : Window
|
||||
public override void OnClose()
|
||||
{
|
||||
// OnClose fires on explicit X-click and on plugin dispose. We never
|
||||
// implicitly accept the defaults here — the explicit "Later" button
|
||||
// does that. If the user hasn't picked a profile yet, the wizard
|
||||
// reopens on the next plugin load.
|
||||
// implicitly accept the defaults here — both the explicit "Decide
|
||||
// later" footer link and a successful "Finish ✓" set FirstRunCompleted
|
||||
// = true, so the wizard does not reopen on the next plugin load
|
||||
// regardless of which path the user took.
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
DrawHellionForgeAnchor();
|
||||
DrawPagination();
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextWrapped(HellionStrings.Wizard_Intro);
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var cardWidth = (avail.X - ImGui.GetStyle().ItemSpacing.X * 2) / 3f;
|
||||
// Reserve room for the footer separator + cancel button below the cards.
|
||||
var footerReserve =
|
||||
ImGui.GetStyle().ItemSpacing.Y * 3
|
||||
+ ImGui.GetTextLineHeight()
|
||||
+ ImGui.GetFrameHeightWithSpacing();
|
||||
var cardHeight = avail.Y - footerReserve;
|
||||
|
||||
DrawCard(
|
||||
"privacy-first",
|
||||
cardWidth,
|
||||
cardHeight,
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Heading,
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Description,
|
||||
null,
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Apply,
|
||||
ApplyPrivacyFirst
|
||||
);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
DrawCard(
|
||||
"casual",
|
||||
cardWidth,
|
||||
cardHeight,
|
||||
HellionStrings.Wizard_Profile_Casual_Heading,
|
||||
HellionStrings.Wizard_Profile_Casual_Description,
|
||||
null,
|
||||
HellionStrings.Wizard_Profile_Casual_Apply,
|
||||
ApplyCasual
|
||||
);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
DrawCard(
|
||||
"full-history",
|
||||
cardWidth,
|
||||
cardHeight,
|
||||
HellionStrings.Wizard_Profile_FullHistory_Heading,
|
||||
HellionStrings.Wizard_Profile_FullHistory_Description,
|
||||
HellionStrings.Wizard_Profile_FullHistory_GdprWarning,
|
||||
HellionStrings.Wizard_Profile_FullHistory_Apply,
|
||||
ApplyFullHistory
|
||||
);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.Button(HellionStrings.Wizard_Cancel_Label))
|
||||
switch (_state.CurrentStep)
|
||||
{
|
||||
Plugin.Config.FirstRunCompleted = true;
|
||||
Plugin.SaveConfig();
|
||||
IsOpen = false;
|
||||
case 1:
|
||||
DrawStepWelcome();
|
||||
break;
|
||||
case 2:
|
||||
DrawStepPrivacy();
|
||||
break;
|
||||
case 3:
|
||||
DrawStepPowerSettings();
|
||||
break;
|
||||
case 4:
|
||||
DrawStepDone();
|
||||
break;
|
||||
default:
|
||||
_state.CurrentStep = 1;
|
||||
DrawStepWelcome();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(HellionStrings.Wizard_Cancel_Tooltip);
|
||||
}
|
||||
|
||||
private void DrawCard(
|
||||
string id,
|
||||
float width,
|
||||
float height,
|
||||
private void DrawPagination()
|
||||
{
|
||||
var draw = ImGui.GetWindowDrawList();
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var cursor = ImGui.GetCursorScreenPos();
|
||||
const float radius = 5f;
|
||||
const float spacing = 16f;
|
||||
var totalWidth = (TotalSteps - 1) * spacing;
|
||||
var startX = cursor.X + avail.X - totalWidth - radius;
|
||||
|
||||
for (var i = 0; i < TotalSteps; i++)
|
||||
{
|
||||
var color = (i + 1) == _state.CurrentStep ? ForgeBronze : ForgeBronzeDim;
|
||||
var packed = ImGui.GetColorU32(color);
|
||||
draw.AddCircleFilled(
|
||||
new Vector2(startX + i * spacing, cursor.Y + radius),
|
||||
radius,
|
||||
packed
|
||||
);
|
||||
}
|
||||
|
||||
// Reserve vertical space the circles consumed so the next widget starts below them.
|
||||
ImGui.Dummy(new Vector2(0, radius * 2));
|
||||
}
|
||||
|
||||
private void DrawFooter(bool showBack, bool showSkip, string primaryLabel, Action onPrimary)
|
||||
{
|
||||
var spacing = ImGui.GetStyle().ItemSpacing.Y;
|
||||
var primaryWidth =
|
||||
ImGui.CalcTextSize(primaryLabel).X + ImGui.GetStyle().FramePadding.X * 2 + 16f;
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
|
||||
// Push the footer to the bottom of the window so step contents
|
||||
// above can size themselves with GetContentRegionAvail().
|
||||
var lineHeight = ImGui.GetFrameHeightWithSpacing();
|
||||
var pushDown = avail.Y - lineHeight - spacing;
|
||||
if (pushDown > 0)
|
||||
ImGui.Dummy(new Vector2(0, pushDown));
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
if (showBack)
|
||||
{
|
||||
if (ImGui.Button(HellionStrings.Wizard_Nav_Back))
|
||||
_state.CurrentStep = Math.Max(1, _state.CurrentStep - 1);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
if (showSkip)
|
||||
{
|
||||
if (ImGui.Button(HellionStrings.Wizard_Step1_Skip_Label))
|
||||
{
|
||||
// Skip path = matches today's Cancel path: mark first-run
|
||||
// complete, save, close. No CommitPending — the user said
|
||||
// 'decide later', so existing config stays as-is.
|
||||
Plugin.Config.FirstRunCompleted = true;
|
||||
Plugin.SaveConfig();
|
||||
IsOpen = false;
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(HellionStrings.Wizard_Step1_Skip_Tooltip);
|
||||
}
|
||||
|
||||
// Right-align the primary action button.
|
||||
var rightX = ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X - primaryWidth;
|
||||
if (rightX > ImGui.GetCursorPosX())
|
||||
ImGui.SameLine(rightX);
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Button, ForgeBronze))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, ForgeBronze))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonActive, ForgeBronze))
|
||||
{
|
||||
if (ImGui.Button($"{primaryLabel}##wizard-primary"))
|
||||
onPrimary();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawStepWelcome()
|
||||
{
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step1_Title);
|
||||
ImGui.Spacing();
|
||||
|
||||
// Banner is opt-in: the full silhouette dominates the wizard window
|
||||
// at the default size, so the TreeNode is folded by default and the
|
||||
// onboarding copy stays the primary focus. Mirrors the pre-rewrite
|
||||
// collapsible anchor from v1.5.1.
|
||||
using (var tree = ImRaii.TreeNode("Hellion Forge"))
|
||||
{
|
||||
if (tree.Success)
|
||||
{
|
||||
using (Plugin.Interface.UiBuilder.MonoFontHandle.Push())
|
||||
{
|
||||
// CalcTextSize must run inside the MonoFont push so the
|
||||
// measurement matches the glyph width actually used for
|
||||
// rendering.
|
||||
var bannerSize = ImGui.CalcTextSize(HellionForgeAscii.FoxBanner);
|
||||
ImGui.SetCursorPosX((ImGui.GetContentRegionAvail().X - bannerSize.X) * 0.5f);
|
||||
ImGui.TextUnformatted(HellionForgeAscii.FoxBanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.TextWrapped(HellionStrings.Wizard_Step1_Subtitle);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextWrapped(HellionStrings.Wizard_Step1_Footer_Hint);
|
||||
|
||||
DrawFooter(
|
||||
showBack: false,
|
||||
showSkip: true,
|
||||
HellionStrings.Wizard_Nav_Next,
|
||||
() => _state.CurrentStep = 2
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawStepPrivacy()
|
||||
{
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step2_Title);
|
||||
ImGui.Spacing();
|
||||
|
||||
// Reserve footer height (separator + spacing + button row) so the
|
||||
// 2x2 grid uses the rest of the window.
|
||||
var footerReserve =
|
||||
ImGui.GetFrameHeightWithSpacing()
|
||||
+ ImGui.GetStyle().ItemSpacing.Y * 3
|
||||
+ ImGui.GetTextLineHeight();
|
||||
var grid = ImGui.GetContentRegionAvail();
|
||||
var cardWidth = (grid.X - ImGui.GetStyle().ItemSpacing.X) / 2f;
|
||||
var cardHeight = (grid.Y - footerReserve - ImGui.GetStyle().ItemSpacing.Y) / 2f;
|
||||
|
||||
// Top row.
|
||||
DrawProfileCard(
|
||||
PrivacyProfile.PrivacyFirst,
|
||||
"🔒",
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Heading,
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Description,
|
||||
recommended: false,
|
||||
cardWidth,
|
||||
cardHeight
|
||||
);
|
||||
ImGui.SameLine();
|
||||
DrawProfileCard(
|
||||
PrivacyProfile.Casual,
|
||||
"💬",
|
||||
HellionStrings.Wizard_Profile_Casual_Heading,
|
||||
HellionStrings.Wizard_Profile_Casual_Description,
|
||||
recommended: true,
|
||||
cardWidth,
|
||||
cardHeight
|
||||
);
|
||||
|
||||
// Bottom row.
|
||||
DrawProfileCard(
|
||||
PrivacyProfile.Roleplay,
|
||||
"🎭",
|
||||
HellionStrings.Wizard_Profile_Roleplay_Heading,
|
||||
HellionStrings.Wizard_Profile_Roleplay_Description,
|
||||
recommended: false,
|
||||
cardWidth,
|
||||
cardHeight
|
||||
);
|
||||
ImGui.SameLine();
|
||||
DrawProfileCard(
|
||||
PrivacyProfile.FullHistory,
|
||||
"📚",
|
||||
HellionStrings.Wizard_Profile_FullHistory_Heading,
|
||||
HellionStrings.Wizard_Profile_FullHistory_Description,
|
||||
recommended: false,
|
||||
cardWidth,
|
||||
cardHeight
|
||||
);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.TextDisabled(HellionStrings.Wizard_Step2_RecommendedFooter);
|
||||
|
||||
DrawFooter(
|
||||
showBack: true,
|
||||
showSkip: true,
|
||||
HellionStrings.Wizard_Nav_Next,
|
||||
() => _state.CurrentStep = 3
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawProfileCard(
|
||||
PrivacyProfile profile,
|
||||
string emoji,
|
||||
string heading,
|
||||
string description,
|
||||
string? warning,
|
||||
string buttonLabel,
|
||||
Action onApply
|
||||
bool recommended,
|
||||
float width,
|
||||
float height
|
||||
)
|
||||
{
|
||||
using var child = ImRaii.Child($"##wizard-card-{id}", new Vector2(width, height), true);
|
||||
var isSelected = _state.PendingProfile == profile;
|
||||
// GetStyleColorVec4 returns a pointer to the live style entry in
|
||||
// Dalamud.Bindings.ImGui, which would require unsafe. Use the U32
|
||||
// packed-colour overload of PushColor for the default branch so we
|
||||
// can stay in safe code while still matching the current border.
|
||||
var borderColor = isSelected
|
||||
? ImGui.GetColorU32(ForgeBronze)
|
||||
: ImGui.GetColorU32(ImGuiCol.Border);
|
||||
|
||||
using var _border = ImRaii.PushColor(ImGuiCol.Border, borderColor);
|
||||
using var child = ImRaii.Child(
|
||||
$"##profile-card-{profile}",
|
||||
new Vector2(width, height),
|
||||
true
|
||||
);
|
||||
if (!child.Success)
|
||||
return;
|
||||
|
||||
ImGui.TextUnformatted(heading);
|
||||
// InvisibleButton over the full card area, then SetCursorScreenPos
|
||||
// back to draw the heading/description content on top. Selectable
|
||||
// would be semantically wrong here — the card is a standalone
|
||||
// choice tile, not a list-item inside a list/menu. The button
|
||||
// takes the click for the entire card area, and IsItemHovered()
|
||||
// on it (if we wire one up later) would naturally cover the full
|
||||
// tile. Visual feedback comes from the border colour above.
|
||||
var startPos = ImGui.GetCursorScreenPos();
|
||||
var cardArea = ImGui.GetContentRegionAvail();
|
||||
if (ImGui.InvisibleButton($"##profile-hit-{profile}", cardArea))
|
||||
_state.PendingProfile = profile;
|
||||
|
||||
ImGui.SetCursorScreenPos(startPos);
|
||||
|
||||
ImGui.TextUnformatted($"{emoji} {heading}{(recommended ? " ★" : string.Empty)}");
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
ImGui.TextWrapped(description);
|
||||
}
|
||||
|
||||
private void DrawStepPowerSettings()
|
||||
{
|
||||
// Seed only the two recommendation fields here. Other fields remain
|
||||
// null until the user touches the corresponding control.
|
||||
// Spec FR-4: the wizard explicitly recommends LoadPreviousSession =
|
||||
// true and FilterIncludePreviousSessions = true (Config defaults are
|
||||
// false). The other four fields (AutoTellTabsHistoryPreload,
|
||||
// UseCompactDensity, PrettierTimestamps, Theme) follow the generic
|
||||
// null-semantics from Spec Z.176: a null pending means the user did
|
||||
// not touch that control, so CommitPending must not write back. They
|
||||
// are read live from Plugin.Config below for the ImGui ref-binding
|
||||
// but never seeded into Pending* without a user gesture.
|
||||
_state.PendingLoadPreviousSession ??= true;
|
||||
_state.PendingFilterIncludePreviousSessions ??= true;
|
||||
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step3_Title);
|
||||
ImGui.Spacing();
|
||||
|
||||
// History section.
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ForgeBronze))
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step3_Section_History);
|
||||
|
||||
var loadPrev = _state.PendingLoadPreviousSession ?? true;
|
||||
if (ImGui.Checkbox(HellionStrings.Wizard_Step3_LoadPreviousSession_Label, ref loadPrev))
|
||||
{
|
||||
_state.PendingLoadPreviousSession = loadPrev;
|
||||
// Mirror the DataManagement coupling: turning load-previous on
|
||||
// also turns filter-include on (otherwise old messages bypass
|
||||
// the filter chain), and turning filter-include off forces
|
||||
// load-previous off. Same idiom as Ui/SettingsTabs/DataManagement.cs:182-200.
|
||||
if (loadPrev)
|
||||
_state.PendingFilterIncludePreviousSessions = true;
|
||||
}
|
||||
|
||||
var filterPrev = _state.PendingFilterIncludePreviousSessions ?? true;
|
||||
if (
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Wizard_Step3_FilterIncludePreviousSessions_Label,
|
||||
ref filterPrev
|
||||
)
|
||||
)
|
||||
{
|
||||
_state.PendingFilterIncludePreviousSessions = filterPrev;
|
||||
if (!filterPrev)
|
||||
_state.PendingLoadPreviousSession = false;
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
// Tell-Tabs section.
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ForgeBronze))
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step3_Section_TellTabs);
|
||||
|
||||
var preload =
|
||||
_state.PendingAutoTellTabsHistoryPreload ?? Plugin.Config.AutoTellTabsHistoryPreload;
|
||||
if (
|
||||
ImGui.SliderInt(
|
||||
HellionStrings.Wizard_Step3_AutoTellTabsHistoryPreload_Label,
|
||||
ref preload,
|
||||
0,
|
||||
100
|
||||
)
|
||||
)
|
||||
_state.PendingAutoTellTabsHistoryPreload = preload;
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
// Visual section.
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ForgeBronze))
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step3_Section_Visual);
|
||||
|
||||
var compact = _state.PendingUseCompactDensity ?? Plugin.Config.UseCompactDensity;
|
||||
if (ImGui.Checkbox(HellionStrings.Wizard_Step3_UseCompactDensity_Label, ref compact))
|
||||
_state.PendingUseCompactDensity = compact;
|
||||
|
||||
var pretty = _state.PendingPrettierTimestamps ?? Plugin.Config.PrettierTimestamps;
|
||||
if (ImGui.Checkbox(HellionStrings.Wizard_Step3_PrettierTimestamps_Label, ref pretty))
|
||||
_state.PendingPrettierTimestamps = pretty;
|
||||
|
||||
// Theme dropdown — built-ins only. Custom themes are power-user
|
||||
// territory and would clutter the first-run flow.
|
||||
var currentSlug = _state.PendingTheme ?? Plugin.Config.Theme;
|
||||
var builtIns = Plugin.ThemeRegistry.AllBuiltIns().ToList();
|
||||
var currentIndex = builtIns.FindIndex(t =>
|
||||
string.Equals(t.Slug, currentSlug, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
if (currentIndex < 0)
|
||||
currentIndex = 0;
|
||||
|
||||
using (
|
||||
var combo = ImRaii.Combo(
|
||||
HellionStrings.Wizard_Step3_Theme_Label,
|
||||
builtIns[currentIndex].Name
|
||||
)
|
||||
)
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
for (var i = 0; i < builtIns.Count; i++)
|
||||
{
|
||||
var isSelected = i == currentIndex;
|
||||
if (ImGui.Selectable(builtIns[i].Name, isSelected))
|
||||
_state.PendingTheme = builtIns[i].Slug;
|
||||
if (isSelected)
|
||||
ImGui.SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawFooter(
|
||||
showBack: true,
|
||||
showSkip: true,
|
||||
HellionStrings.Wizard_Nav_Next,
|
||||
() => _state.CurrentStep = 4
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawStepDone()
|
||||
{
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step4_Title);
|
||||
ImGui.Spacing();
|
||||
|
||||
// ✓ symbol, centred-ish via dummy padding.
|
||||
var checkmark = "✓";
|
||||
var checkSize = ImGui.CalcTextSize(checkmark);
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
ImGui.Dummy(new Vector2((avail.X - checkSize.X) * 0.5f, 0));
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ForgeBronze))
|
||||
ImGui.TextUnformatted(checkmark);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextWrapped(description);
|
||||
|
||||
if (warning is not null)
|
||||
// Summary card.
|
||||
using (var summary = ImRaii.Child("##wizard-summary", new Vector2(-1, 130), true))
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.WarningText(warning);
|
||||
if (summary.Success)
|
||||
{
|
||||
ImGui.TextUnformatted(HellionStrings.Wizard_Step4_SummaryHeading);
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var profileLabel = _state.PendingProfile switch
|
||||
{
|
||||
PrivacyProfile.PrivacyFirst =>
|
||||
HellionStrings.Wizard_Profile_PrivacyFirst_Heading,
|
||||
PrivacyProfile.Casual => HellionStrings.Wizard_Profile_Casual_Heading,
|
||||
PrivacyProfile.Roleplay => HellionStrings.Wizard_Profile_Roleplay_Heading,
|
||||
PrivacyProfile.FullHistory => HellionStrings.Wizard_Profile_FullHistory_Heading,
|
||||
_ => HellionStrings.Wizard_Step4_Summary_Unchanged,
|
||||
};
|
||||
ImGui.TextWrapped(
|
||||
string.Format(HellionStrings.Wizard_Step4_Summary_Profile, profileLabel)
|
||||
);
|
||||
|
||||
var historyLabel =
|
||||
(_state.PendingLoadPreviousSession ?? false)
|
||||
? HellionStrings.Wizard_Step3_LoadPreviousSession_Label
|
||||
: HellionStrings.Wizard_Step4_Summary_Unchanged;
|
||||
ImGui.TextWrapped(
|
||||
string.Format(HellionStrings.Wizard_Step4_Summary_History, historyLabel)
|
||||
);
|
||||
|
||||
var preloadValue =
|
||||
_state.PendingAutoTellTabsHistoryPreload
|
||||
?? Plugin.Config.AutoTellTabsHistoryPreload;
|
||||
ImGui.TextWrapped(
|
||||
string.Format(HellionStrings.Wizard_Step4_Summary_TellTabs, preloadValue)
|
||||
);
|
||||
|
||||
var compact = _state.PendingUseCompactDensity ?? Plugin.Config.UseCompactDensity;
|
||||
var pretty = _state.PendingPrettierTimestamps ?? Plugin.Config.PrettierTimestamps;
|
||||
var themeSlug = _state.PendingTheme ?? Plugin.Config.Theme;
|
||||
var themeName = Plugin.ThemeRegistry.Get(themeSlug).Name;
|
||||
var visualParts = new List<string>();
|
||||
if (compact)
|
||||
visualParts.Add(HellionStrings.Wizard_Step3_UseCompactDensity_Label);
|
||||
if (pretty)
|
||||
visualParts.Add(HellionStrings.Wizard_Step3_PrettierTimestamps_Label);
|
||||
visualParts.Add(themeName);
|
||||
ImGui.TextWrapped(
|
||||
string.Format(
|
||||
HellionStrings.Wizard_Step4_Summary_Visual,
|
||||
string.Join(", ", visualParts)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Push the button to the bottom of the card.
|
||||
var lineHeight = ImGui.GetFrameHeightWithSpacing();
|
||||
var remaining = ImGui.GetContentRegionAvail().Y - lineHeight;
|
||||
if (remaining > 0)
|
||||
ImGui.Dummy(new Vector2(0, remaining));
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.Button($"{buttonLabel}##{id}", new Vector2(-1, 0)))
|
||||
{
|
||||
onApply();
|
||||
Plugin.Config.FirstRunCompleted = true;
|
||||
Plugin.SaveConfig();
|
||||
IsOpen = false;
|
||||
}
|
||||
// Inline FR-3 hint with placeholder for preload count.
|
||||
var preloadForHint =
|
||||
_state.PendingAutoTellTabsHistoryPreload ?? Plugin.Config.AutoTellTabsHistoryPreload;
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ForgeBronze))
|
||||
ImGui.TextWrapped(string.Format(HellionStrings.Wizard_Step4_TestHint, preloadForHint));
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.TextDisabled(HellionStrings.Wizard_Step4_SettingsHint);
|
||||
|
||||
DrawFooter(
|
||||
showBack: true,
|
||||
showSkip: false,
|
||||
HellionStrings.Wizard_Nav_Finish,
|
||||
() =>
|
||||
{
|
||||
CommitPending();
|
||||
Plugin.Config.FirstRunCompleted = true;
|
||||
Plugin.SaveConfig();
|
||||
IsOpen = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Collapsible because the full silhouette is taller than the wizard
|
||||
// window — folded by default so the privacy cards stay the primary
|
||||
// focus, expandable for whoever wants the "about the makers" anchor.
|
||||
private void DrawHellionForgeAnchor()
|
||||
// Writes only non-null pending values back to Config. A null pending
|
||||
// means the user did not touch that step's control, so the existing
|
||||
// Config value is preserved. Theme switch goes through ThemeRegistry
|
||||
// so the active palette updates live for the rest of the session.
|
||||
internal void CommitPending()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode("Hellion Forge");
|
||||
if (!tree.Success)
|
||||
return;
|
||||
switch (_state.PendingProfile)
|
||||
{
|
||||
case PrivacyProfile.PrivacyFirst:
|
||||
ApplyPrivacyFirst();
|
||||
break;
|
||||
case PrivacyProfile.Casual:
|
||||
ApplyCasual();
|
||||
break;
|
||||
case PrivacyProfile.Roleplay:
|
||||
ApplyRoleplay();
|
||||
break;
|
||||
case PrivacyProfile.FullHistory:
|
||||
ApplyFullHistory();
|
||||
break;
|
||||
}
|
||||
|
||||
using (Plugin.Interface.UiBuilder.MonoFontHandle.Push())
|
||||
ImGui.TextUnformatted(HellionForgeAscii.FoxBanner);
|
||||
if (_state.PendingLoadPreviousSession.HasValue)
|
||||
Plugin.Config.LoadPreviousSession = _state.PendingLoadPreviousSession.Value;
|
||||
|
||||
if (_state.PendingFilterIncludePreviousSessions.HasValue)
|
||||
Plugin.Config.FilterIncludePreviousSessions = _state
|
||||
.PendingFilterIncludePreviousSessions
|
||||
.Value;
|
||||
|
||||
if (_state.PendingAutoTellTabsHistoryPreload.HasValue)
|
||||
Plugin.Config.AutoTellTabsHistoryPreload = _state
|
||||
.PendingAutoTellTabsHistoryPreload
|
||||
.Value;
|
||||
|
||||
if (_state.PendingUseCompactDensity.HasValue)
|
||||
Plugin.Config.UseCompactDensity = _state.PendingUseCompactDensity.Value;
|
||||
|
||||
if (_state.PendingPrettierTimestamps.HasValue)
|
||||
Plugin.Config.PrettierTimestamps = _state.PendingPrettierTimestamps.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_state.PendingTheme))
|
||||
{
|
||||
Plugin.Config.Theme = _state.PendingTheme;
|
||||
Plugin.ThemeRegistry.Switch(_state.PendingTheme);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyPrivacyFirst()
|
||||
@@ -194,6 +614,20 @@ public sealed class FirstRunWizard : Window
|
||||
Plugin.Config.RetentionPerChannelDays = policy;
|
||||
}
|
||||
|
||||
private void ApplyRoleplay()
|
||||
{
|
||||
Plugin.Config.PrivacyFilterEnabled = true;
|
||||
Plugin.Config.PrivacyPersistChannels = [.. PrivacyDefaults.RoleplayWhitelist];
|
||||
Plugin.Config.PrivacyPersistUnknownChannels = false;
|
||||
|
||||
Plugin.Config.RetentionEnabled = true;
|
||||
Plugin.Config.RetentionDefaultDays = 30;
|
||||
var policy = PrivacyDefaults.DefaultRetentionDays.ToDictionary(p => p.Key, p => p.Value);
|
||||
foreach (var (type, days) in PrivacyDefaults.RoleplayRetentionOverrides)
|
||||
policy[type] = days;
|
||||
Plugin.Config.RetentionPerChannelDays = policy;
|
||||
}
|
||||
|
||||
private void ApplyFullHistory()
|
||||
{
|
||||
// Full history = upstream Chat 2 behavior. Filter off, retention off,
|
||||
@@ -205,4 +639,34 @@ public sealed class FirstRunWizard : Window
|
||||
Plugin.Config.RetentionEnabled = false;
|
||||
Plugin.Config.RetentionPerChannelDays.Clear();
|
||||
}
|
||||
|
||||
// Test-only entry point so SelfTests/WizardStateSmokeStep can advance
|
||||
// the state machine without spawning ImGui input events.
|
||||
internal void TestOnly_AdvanceTo(int step) =>
|
||||
_state.CurrentStep = Math.Clamp(step, 1, TotalSteps);
|
||||
|
||||
// Test-only setter so the smoke-test can pin a profile selection
|
||||
// without driving the ImGui card-click path.
|
||||
internal void TestOnly_SetPendingProfile(PrivacyProfile profile) =>
|
||||
_state.PendingProfile = profile;
|
||||
|
||||
internal enum PrivacyProfile
|
||||
{
|
||||
PrivacyFirst,
|
||||
Casual,
|
||||
Roleplay,
|
||||
FullHistory,
|
||||
}
|
||||
|
||||
private sealed class WizardState
|
||||
{
|
||||
public int CurrentStep { get; set; } = 1;
|
||||
public PrivacyProfile? PendingProfile { get; set; }
|
||||
public bool? PendingLoadPreviousSession { get; set; }
|
||||
public bool? PendingFilterIncludePreviousSessions { get; set; }
|
||||
public int? PendingAutoTellTabsHistoryPreload { get; set; }
|
||||
public bool? PendingUseCompactDensity { get; set; }
|
||||
public bool? PendingPrettierTimestamps { get; set; }
|
||||
public string? PendingTheme { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +201,18 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
|
||||
var hideChanged = !Mutable.HideChat && Mutable.HideChat != Plugin.Config.HideChat;
|
||||
var languageChanged = Mutable.LanguageOverride != Plugin.Config.LanguageOverride;
|
||||
|
||||
// v1.5.3: Auto-enable the ExtraGlyphRanges flag matching the new
|
||||
// locale so non-Latin scripts render immediately. Without this,
|
||||
// a user switching to Korean would see "===" until they manually
|
||||
// tick the Korean range in Fonts & Colours.
|
||||
if (languageChanged)
|
||||
{
|
||||
var required = Mutable.LanguageOverride.RequiredGlyphRanges();
|
||||
if (required != 0)
|
||||
Mutable.ExtraGlyphRanges |= required;
|
||||
}
|
||||
|
||||
var fontChanged =
|
||||
Mutable.GlobalFontV2 != Plugin.Config.GlobalFontV2
|
||||
|| Mutable.JapaneseFontV2 != Plugin.Config.JapaneseFontV2
|
||||
|
||||
@@ -54,28 +54,23 @@ internal sealed class FontsAndColours : ISettingsTab
|
||||
|
||||
if (Mutable.UseHellionFont)
|
||||
{
|
||||
// Bundled-font path: only the base font size matters; the
|
||||
// global / japanese / italic chooser pickers do not apply.
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.FontSizeCombo(
|
||||
Language.Options_SymbolsFontSize_Name,
|
||||
ref Mutable.SymbolsFontSizeV2
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled);
|
||||
ImGui.Spacing();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled);
|
||||
ImGui.Spacing();
|
||||
|
||||
var unused = false;
|
||||
if (!Mutable.FontsEnabled)
|
||||
if (!Mutable.UseHellionFont && !Mutable.FontsEnabled)
|
||||
{
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
}
|
||||
else
|
||||
else if (!Mutable.UseHellionFont)
|
||||
{
|
||||
var globalChooser = ImGuiUtil.FontChooser(
|
||||
Language.Options_Font_Name,
|
||||
@@ -164,23 +159,25 @@ internal sealed class FontsAndColours : ISettingsTab
|
||||
string.Format(Language.Options_Italic_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
|
||||
// v1.5.3: ExtraGlyphRanges is an atlas-wide property and stays
|
||||
// reachable regardless of UseHellionFont / FontsEnabled state so
|
||||
// users can verify or override the auto-activation on language change.
|
||||
ImGui.Spacing();
|
||||
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
|
||||
{
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName)
|
||||
);
|
||||
|
||||
var range = (int)Mutable.ExtraGlyphRanges;
|
||||
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
|
||||
{
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName)
|
||||
);
|
||||
|
||||
var range = (int)Mutable.ExtraGlyphRanges;
|
||||
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
|
||||
{
|
||||
ImGui.CheckboxFlags(extra.Name(), ref range, (int)extra);
|
||||
}
|
||||
|
||||
Mutable.ExtraGlyphRanges = (ExtraGlyphRanges)range;
|
||||
ImGui.CheckboxFlags(extra.Name(), ref range, (int)extra);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
Mutable.ExtraGlyphRanges = (ExtraGlyphRanges)range;
|
||||
}
|
||||
|
||||
ImGuiUtil.FontSizeCombo(
|
||||
|
||||
@@ -139,7 +139,12 @@ internal sealed class General : ISettingsTab
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var language in Enum.GetValues<LanguageOverride>())
|
||||
// None pinned first, then alphabetical by endonym so source order
|
||||
// (append-only for serialisation safety) is not visible to users.
|
||||
var sortedLanguages = Enum.GetValues<LanguageOverride>()
|
||||
.OrderBy(l => l == LanguageOverride.None ? 0 : 1)
|
||||
.ThenBy(l => l.Name(), StringComparer.InvariantCulture);
|
||||
foreach (var language in sortedLanguages)
|
||||
{
|
||||
if (ImGui.Selectable(language.Name()))
|
||||
{
|
||||
@@ -151,6 +156,9 @@ internal sealed class General : ISettingsTab
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_Language_Description, Plugin.PluginName)
|
||||
);
|
||||
// v1.5.3: HellionChat's font stack covers 24 languages but FFXIV's
|
||||
// engine only supports EN/DE/FR/JA for chat input/sending.
|
||||
ImGuiUtil.WarningText(HellionStrings.Settings_Language_FFXIVCoverage_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
using (
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
|
||||
[](LICENSE)
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
|
||||
[](https://github.com/goatcorp/Dalamud)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://www.finalfantasyxiv.com/)
|
||||
@@ -11,7 +11,7 @@
|
||||
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
|
||||
</p>
|
||||
|
||||
**Version 1.5.1** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
|
||||
**Version 1.5.3** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
|
||||
[Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
|
||||
|
||||
Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine
|
||||
@@ -55,7 +55,7 @@ Hellion Chat is developed under **Hellion Forge**, the specialized modding and p
|
||||
| UI | Dear ImGui (Dalamud bindings) |
|
||||
| Database | SQLite (Microsoft.Data.Sqlite, MessagePack storage) |
|
||||
| Localization | ResX (HellionStrings.resx, .de.resx; PR-based) |
|
||||
| Font | Exo 2 (SIL Open Font License 1.1, bundled) |
|
||||
| Font | Inter Light (SIL Open Font License 1.1, bundled) |
|
||||
| Toolchain | dotnet 10 SDK, VS Code with C# Dev Kit |
|
||||
| Deployment | GitHub Releases + custom repo (`repo.json`) |
|
||||
|
||||
@@ -103,7 +103,7 @@ Hellion Chat is developed under **Hellion Forge**, the specialized modding and p
|
||||
Colors: Classic (Chat 2 default), High Contrast, Pastel, Dark Mode Tuned, Hellion (brand), plus
|
||||
bonus moods Night Blue and Indigo Violet. One-click apply, battle channels remain untouched.
|
||||
- **Window opacity slider** for combat-friendly transparency.
|
||||
- **Bundled Hellion font** (Exo 2, OFL-1.1) as an optional default instead of the system font.
|
||||
- **Bundled UI font** (Inter Light, OFL-1.1) as an optional default instead of the system font.
|
||||
- **Hellion logo** bundled in the plugin and displayed in the Dalamud plugin list.
|
||||
|
||||
#### Custom Themes (v1.1.0)
|
||||
@@ -164,8 +164,8 @@ HellionChat/
|
||||
│ ├── HellionStrings.de.resx # German translation
|
||||
│ ├── HellionStrings.Designer.cs # Hand-maintained accessor
|
||||
│ ├── ChatColourPresets.cs # Seven built-in color presets (v0.6.0)
|
||||
│ ├── HellionFont.ttf # Exo 2 variable font
|
||||
│ ├── HellionFont-OFL.txt # OFL-1.1 license text (bundled with font)
|
||||
│ ├── Inter-Light.ttf # Inter Light static font (bundled UI font)
|
||||
│ ├── Inter-OFL.txt # OFL-1.1 license text (bundled with font)
|
||||
│ └── Language*.resx # Upstream localization (Crowdin)
|
||||
├── Ui/
|
||||
│ ├── FirstRunWizard.cs # Three-profile onboarding
|
||||
@@ -299,6 +299,44 @@ An optional submission to the Dalamud main plugin repo (in addition to the custo
|
||||
|
||||
## Project Status
|
||||
|
||||
**Version 1.5.3** — Localisation Wave + Bundled-Font Overhaul. Twenty-four selectable UI languages
|
||||
(Catalan, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hungarian, Italian,
|
||||
Japanese, Korean, Norsk bokmål, Polish, Portuguese (Brazil), Portuguese (Portugal), Romanian,
|
||||
Russian, Spanish, Swedish, Turkish, Ukrainian, Simplified Chinese, Traditional Chinese); dropdown
|
||||
sorts alphabetically by endonym, "None" pinned first. Non-native translations are AI-assisted and
|
||||
flagged for community native-speaker review. The bundled UI font swaps from Exo 2 to **Inter
|
||||
Light** (SIL OFL 1.1, 343 KB) for wider Latin Extended-A/B, Greek polytonic and Cyrillic Supplement
|
||||
coverage. **NotoSansCjkRegular** joins as a third merge layer so Hangul and Simplified-Chinese
|
||||
glyphs the FFXIV Japanese game font does not ship now render correctly. 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) as a side effect: the bundled-font path was silently falling back to the
|
||||
FFXIV Axis game font for the entire v1.5.x series because of an early-return in `Plugin.cs:937`.
|
||||
The fix routes `RegularFont` through draw whenever either `FontsEnabled` or `UseHellionFont` is on,
|
||||
and lands the defer-pattern win v1.5.1 was reaching for. `ExtraGlyphRanges` auto-activates the
|
||||
matching flag on language change; two new flags (`LatinExtended`, `Greek`) join the existing set.
|
||||
A WarningText under the language dropdown notes that FFXIV's own chat input only fully supports
|
||||
EN/DE/FR/JA — other languages may garble when typed in-game. Migration v17 stays.
|
||||
|
||||
---
|
||||
|
||||
### Project status (pre-v1.5.3, kept for context)
|
||||
|
||||
**Version 1.5.2** — First-Run Wizard Rework. The single-page wizard becomes a four-step
|
||||
staged-commit flow (Welcome → Privacy → Power Settings → Done). The privacy picker becomes a 2×2
|
||||
grid with a fourth profile "Roleplay" that extends Privacy-First with `Say` and both emote types
|
||||
under a 30-/90-day retention window. A power-settings stage surfaces six previously-hidden
|
||||
`Configuration` defaults in one place without introducing any new settings. The wizard window
|
||||
shrinks to 720×480 default (was 900×560, MinimumSize 600×400) after smoke feedback and Step 1
|
||||
keeps the fox banner in a folded TreeNode so the onboarding copy stays primary. Existing v1.5.1
|
||||
users see the new flow once on first v1.5.2 boot via a new `WizardLastShownVersion` config marker.
|
||||
Under the hood: a `WizardStateSmokeStep` joins `/xlperf`, the Build Suite gains twelve pure-helper
|
||||
xUnit Facts pinning all four privacy profile sets and the new Roleplay retention overrides.
|
||||
Migration v17 stays — `Configuration` only grows one optional string field.
|
||||
|
||||
---
|
||||
|
||||
### Project status (pre-v1.5.2, kept for context)
|
||||
|
||||
**Version 1.5.1** — FontAtlas Refactor and Hellion Forge Signature. The FontManager moves from the
|
||||
inherited Chat 2 anti-pattern (null! fields + a separate BuildFonts method) to a hybrid model where
|
||||
the game fonts and FontAwesome are init-only handles and only the user-configurable delegate fonts
|
||||
@@ -318,10 +356,6 @@ defer their font-atlas build to land at ~7 ms; Chat 2 + HellionChat were ~75 ms)
|
||||
cost lives in the UiBuilder first-frame render path, not in the atlas build. A first-frame render
|
||||
investigation is reserved for a later cycle.
|
||||
|
||||
---
|
||||
|
||||
### Project status (pre-v1.5.1, kept for context)
|
||||
|
||||
**Version 1.5.0** — DI Foundation and Service Refactor. Major architecture cycle: the plugin
|
||||
bootstrap moves to a generic-host DI container (`Microsoft.Extensions.Hosting` +
|
||||
`IServiceCollection`) modelled on Lightless Sync. All 18 instance-class services migrate from a
|
||||
@@ -345,7 +379,7 @@ Hellion Chat is a standalone plugin, no longer a fork in the repository sense. F
|
||||
- First-run wizard with three profiles
|
||||
- Plugin identity: own `HellionChat` slot, layout migration from Chat 2, Migrate3 recovery
|
||||
- Bilingual UI (EN and DE) with live language switching
|
||||
- Hellion theme, Hellion logo, bundled Exo 2 font
|
||||
- Hellion theme, Hellion logo, bundled Inter Light font
|
||||
- Custom repo pipeline with automated GitHub Release distribution
|
||||
- Slash commands consolidated to the `/hellionchat` family
|
||||
- Web interface removed (v0.2.0)
|
||||
|
||||
@@ -11,6 +11,141 @@ releases as an overview and links to the release pages for details.
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.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. The bundled UI font swaps from Exo 2 to Inter Light. HellionChat now ships strings and
|
||||
renderable glyph coverage for 24 languages.
|
||||
|
||||
### User-visible
|
||||
|
||||
- Twenty-four selectable UI languages: Catalan, Czech, Danish, Dutch, English, Finnish, French,
|
||||
German, Greek, Hungarian, Italian, Japanese, Korean, Norsk bokmål, Polish, Portuguese (Brazil),
|
||||
Portuguese (Portugal), Romanian, Russian, Spanish, Swedish, Turkish, Ukrainian, Simplified
|
||||
Chinese, Traditional Chinese. The dropdown sorts alphabetically by endonym, "None" pinned first.
|
||||
Non-native translations are AI-assisted and flagged for community native-speaker review via the
|
||||
Hellion Forge Discord.
|
||||
- Bundled **Inter Light** replaces Exo 2 as the in-plugin font. Wider European coverage (Latin
|
||||
Extended-A/B, Greek polytonic, Cyrillic Supplement) so Czech, Polish, Romanian, Turkish,
|
||||
Hungarian, Greek and Ukrainian render without manual font configuration. SIL OFL 1.1, 343 KB.
|
||||
- **NotoSansCjkRegular fallback** layer added as a merge-on-top so Hangul, Simplified-Chinese
|
||||
characters specific to the post-1956 reform, and other CJK glyphs the FFXIV Japanese game font
|
||||
does not ship now render correctly inside the HellionChat UI.
|
||||
- First-frame **HITCH dropped from ~74 ms** (the v1.5.2 baseline that has held since v1.4.x) to a
|
||||
**median of ~20 ms** (5-reload sample: 23.6 / 20.4 / 17.9 / 20.1 / 19.2 ms, Linux/Wine; Windows
|
||||
baseline pending Jin's verification per the cross-platform-pflicht). The bundled-font path
|
||||
silently fell back to the FFXIV Axis game 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 activates automatically** when the user picks a language that needs a non-Latin
|
||||
script. Selecting Korean enables the Korean glyph range and rebuilds the atlas without a manual
|
||||
toggle in Fonts & Colours.
|
||||
- New **WarningText under the language dropdown** notes that FFXIV's own chat input only fully
|
||||
supports EN, DE, FR and JA character sets. Other languages render inside HellionChat but may
|
||||
garble when typed into in-game chat or sent as messages.
|
||||
|
||||
### Under the hood
|
||||
|
||||
- Three-layer font stack in `FontManager.BuildRegularFontHandle` and `BuildItalicFontHandle`:
|
||||
Inter Light (or the user-selected global font) as primary, FFXIV JapaneseFont as merge 1 for
|
||||
native FFXIV kana/kanji style, NotoSansCjkRegular as merge 2 for everything else CJK.
|
||||
- Two new `ExtraGlyphRanges` flags: `LatinExtended` (U+0100-U+024F) and `Greek` (U+0370-U+03FF +
|
||||
U+1F00-U+1FFF). Implemented as `builder.AddChar` pair lists in `SetUpRanges` (no managed-pointer
|
||||
pinning needed).
|
||||
- `LanguageOverride` enum gains ten locales (Catalan, Czech, Danish, Finnish, Hungarian,
|
||||
Norwegian, Polish, Portuguese (Portugal), Turkish, Ukrainian) plus three previously
|
||||
commented-out entries (Italian, Korean, Norwegian re-enabled with code `nb` instead of `no`).
|
||||
New values are appended to the enum to keep existing user-config integer serialisation stable.
|
||||
- **Crowdin gap closed:** four ChatTwo keys added after the last community sync
|
||||
(`Options_ColorSelectedInputChannelButton_Name` / `_Description`,
|
||||
`Options_HideInNewGamePlusMenu_Name` / `_Description`) are now backfilled into the thirteen
|
||||
legacy Crowdin locales with per-key AI-translated markers.
|
||||
- Plugin init runs a one-shot migration that ORs in the matching `ExtraGlyphRanges` flag based on
|
||||
the user's current `LanguageOverride`. An update from v1.5.2 picks up the new coverage without
|
||||
the user having to toggle the language twice.
|
||||
- `Plugin.cs:937` draw-path fixed: `RegularFont` is now pushed whenever **either** `FontsEnabled`
|
||||
**or** `UseHellionFont` is on. The previous `Config.FontsEnabled`-only check meant the bundled
|
||||
font path was silently dead whenever `FontsAndColours.cs:50` force-set `FontsEnabled = false` on
|
||||
the UseHellionFont-toggle. Source of the HITCH win.
|
||||
- `ExtraGlyphRanges` settings panel is now reachable in **all** UseHellionFont / FontsEnabled
|
||||
combinations. The bundled-font branch used to short-circuit past it.
|
||||
- **Resource bundle split:** fork-added strings live in `HellionStrings.resx` (24 locales, 328
|
||||
keys each) alongside the ChatTwo-Crowdin-heritage `Language.resx` (24 locales, 456 keys each).
|
||||
The `Language` siblings for the ten brand-new locales and Greek carry a Hellion Forge maintainer
|
||||
header that points reviewers at the Discord rather than the standalone-hosted Gitea.
|
||||
- **Em-dash sweep** across the EN source and 18 translations: in-prose em-dashes replaced with
|
||||
period or colon per the house style guide. Russian and Ukrainian keep their typographic norm
|
||||
where the em-dash is orthographically required (subject-predicate separator).
|
||||
- **Bundled font asset rotation:** `HellionFont.ttf` (Exo 2) plus its OFL notice removed from
|
||||
`Resources/`. `Inter-Light.ttf` plus `Inter-OFL.txt` take their place. `FontManager`
|
||||
references rename to `BundledFontBytes` / `TryGetBundledFontBytes()` for clarity (config field
|
||||
`UseHellionFont` keeps its name so existing user configs deserialize cleanly).
|
||||
|
||||
### Migration
|
||||
|
||||
- Migration v17 stays (no schema bump).
|
||||
- Existing `UseHellionFont = true` users transition transparently from Exo 2 to Inter Light on
|
||||
first reload.
|
||||
- Existing users with `LanguageOverride != None` get their matching `ExtraGlyphRanges` flag set
|
||||
on the first plugin init after the v1.5.3 update (Plugin.cs LoadAsync migration step).
|
||||
|
||||
### Reserved for follow-up cycles
|
||||
|
||||
- Native-speaker review pass for AI-assisted translations in the 13 legacy Crowdin locales (ca,
|
||||
es, fr, it, ja, ko, nl, pt-BR, ro, ru, sv, zh-Hans, zh-Hant) — corrections via the Hellion
|
||||
Forge Discord.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.5.2 — First-Run Wizard Rework (2026-05-18)
|
||||
|
||||
UX patch. The single-page first-run wizard becomes a four-step staged-commit flow, the privacy
|
||||
profile catalogue gains a fourth entry "Roleplay", and a new power-settings stage surfaces six
|
||||
previously-hidden Configuration defaults. Existing v1.5.1 users see the new wizard once on first
|
||||
v1.5.2 boot via a new `WizardLastShownVersion` config marker.
|
||||
|
||||
User-visible:
|
||||
|
||||
- Wizard layout: Welcome → Privacy profile → Power settings → Done. Forge-Bronze pagination dots,
|
||||
per-step Back / Decide later / Next footer. Decide-later and X-close both leave the existing
|
||||
config untouched; only the Finish ✓ click commits pending choices.
|
||||
- Fourth privacy profile "Roleplay": Privacy-First whitelist plus `Say` and both emote types, with a
|
||||
30-day retention window for `Say` and 90 days for the two emote channels. `Shout`, `Yell` and
|
||||
`NoviceNetwork` stay out — public-distance noise from strangers is not story content.
|
||||
- Privacy picker becomes a 2×2 grid. Casual stays the recommended option with a ★ marker.
|
||||
- Power-settings stage surfaces six existing `Configuration` fields in one place: Load Previous
|
||||
Session, Filter Include Previous Sessions, Auto-Tell-Tabs History Preload, Compact Density,
|
||||
Prettier Timestamps, plus a built-in theme picker. No new settings are introduced — the stage just
|
||||
collects what was previously buried in Settings → Privacy / Chat / Data Management / Appearance.
|
||||
- Inline test hint on the done stage: `type /tell <Player Name> into chat` surfaces the auto-tell-tab
|
||||
spawn mechanism for new users.
|
||||
- Wizard window starts at 720×480 (was 900×560) and can shrink to 600×400. Step 1 wraps the fox
|
||||
banner in a collapsible TreeNode, folded by default — onboarding copy stays primary.
|
||||
- Existing v1.5.1 users get the new wizard surfaced once on first v1.5.2 boot. A new
|
||||
`WizardLastShownVersion` config field tracks the most recent version whose wizard was shown;
|
||||
Plugin.LoadAsync resets `FirstRunCompleted` once when the constant `1.5.2` doesn't match.
|
||||
|
||||
Under the hood:
|
||||
|
||||
- `WizardStateSmokeStep` registered with `/xlperf`. Variant 1 walks the four steps with empty
|
||||
pending state to pin the no-op CommitPending path. Variant 2 picks Roleplay on Step 2, skips
|
||||
Step 3, commits, and asserts `LoadPreviousSession` / `FilterIncludePreviousSessions` stayed on
|
||||
their pre-test value — pinning the null-semantics contract. The step snapshots six privacy /
|
||||
retention fields before Variant 2 and `CleanUp()` restores them, so back-to-back runs don't drift
|
||||
the active profile.
|
||||
- Twelve pure-helper xUnit Facts in the Build Suite (`Privacy/PrivacyDefaultsTests.cs`) cover all
|
||||
four profile whitelists plus the new Roleplay retention overrides.
|
||||
- `Configuration` grows one optional string field `WizardLastShownVersion` (default empty). No
|
||||
schema bump — migration v17 still applies.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
[Full release notes on the Gitea release page.](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.5.2)
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.5.1 — FontAtlas Refactor and Hellion Forge Signature (2026-05-17)
|
||||
|
||||
Hybrid FontManager refactor plus an embedded Hellion Forge provenance mark.
|
||||
|
||||
+56
-7
@@ -10,14 +10,63 @@ be a poor fit for the plugin's privacy-first scope during brainstorming.
|
||||
|
||||
---
|
||||
|
||||
## Next Cycle (v1.5.2)
|
||||
## Next Cycle
|
||||
|
||||
**First-Run-Wizard rework with curated defaults beyond the three privacy profiles.** Jin's discovery
|
||||
in v1.4.10 surfaced the wizard's three-card layout as too thin — power users want richer presets out
|
||||
of the box. After that, FR localisation (Hezcal native-speaker review confirmed), then the Plugin
|
||||
Integrations Wave 2-6 (Context-Menu, NotificationMaster, Moodles, ExtraChat, XIVIM Quick-DM). The
|
||||
UiBuilder first-frame HITCH investigation that v1.5.1 surfaced sits as a separate spike near the
|
||||
Wine/Linux scroll-rubber-band investigation at the tail.
|
||||
**Plugin Integrations Wave 2-6** (Context-Menu, NotificationMaster, Moodles, ExtraChat, XIVIM
|
||||
Quick-DM) is the next planned scope. The UiBuilder first-frame HITCH investigation that v1.5.1
|
||||
queued is now closed as a side effect of v1.5.3's font-stack fix — HITCH dropped from ~74 ms into
|
||||
the 15-25 ms range. The Wine/Linux scroll-rubber-band spike remains at the tail.
|
||||
|
||||
Native-speaker review of the AI-assisted v1.5.3 translations (13 legacy Crowdin locales) runs in
|
||||
parallel as a continuous correction pass, gathered via the Hellion Forge Discord.
|
||||
|
||||
---
|
||||
|
||||
## v1.5.3 — Localisation Wave + Bundled-Font Overhaul (released 2026-05-19)
|
||||
|
||||
Twenty-four selectable UI languages: from FR-only as the original plan scope, the cycle expanded to
|
||||
cover Catalan, Czech, Danish, Finnish, Greek, Hungarian, Italian, Korean, Norwegian, Polish,
|
||||
Portuguese (Portugal), Turkish and Ukrainian alongside the existing Crowdin-heritage locales, all
|
||||
AI-translated and flagged for community review. Bundled font swaps from Exo 2 to **Inter Light**
|
||||
for wider European glyph coverage (Latin Extended-A/B, Greek polytonic, Cyrillic Supplement);
|
||||
**NotoSansCjkRegular** joins as a third merge layer so Hangul and Simplified-Chinese-specific Han
|
||||
glyphs render correctly inside the HellionChat UI.
|
||||
|
||||
First-frame HITCH dropped from **~74 ms to a median of ~20 ms** (5-reload sample 17.9-23.6 ms,
|
||||
Linux/Wine) as a side effect: the bundled-font path was silently falling back to the FFXIV Axis
|
||||
game font for the entire v1.5.x series because of an early-return in `Plugin.cs:937`. The fix
|
||||
routes `RegularFont` through draw whenever either `FontsEnabled` or `UseHellionFont` is on, and
|
||||
lands the defer-pattern win v1.5.1 was reaching for.
|
||||
|
||||
`ExtraGlyphRanges` auto-activates the matching flag on language change. Two new flags
|
||||
(`LatinExtended`, `Greek`) join the existing set. Plugin init runs a one-shot migration that ORs
|
||||
the required flag into the saved config for users updating from v1.5.2 with a non-default language
|
||||
already selected. A WarningText under the language dropdown notes that FFXIV's own chat input only
|
||||
fully supports EN/DE/FR/JA — other languages may garble when typed into in-game chat.
|
||||
|
||||
Migration v17 stays. `LanguageOverride` enum grows by ten locales plus three previously
|
||||
commented-out (Italian, Korean, Norwegian with code `nb`); all new values append to keep existing
|
||||
user-config integer serialisation stable.
|
||||
|
||||
---
|
||||
|
||||
## v1.5.2 — First-Run Wizard Rework (released 2026-05-18)
|
||||
|
||||
Multi-step wizard replacement: Welcome → Privacy → Power Settings → Done with staged-commit so
|
||||
Decide-later or X-close at any point leaves the existing config untouched. New fourth privacy
|
||||
profile "Roleplay" extends Privacy-First with `Say` and both emote types under a 30-/90-day
|
||||
retention window. Privacy picker becomes a 2×2 grid; Casual keeps the ★ recommended marker. A new
|
||||
power-settings stage surfaces six previously-hidden `Configuration` fields (Load Previous Session,
|
||||
Filter Include Previous Sessions, Auto-Tell-Tabs History Preload, Compact Density, Prettier
|
||||
Timestamps, built-in theme picker) without introducing any new fields.
|
||||
|
||||
Window default size shrinks from 900×560 to 720×480 (MinimumSize 600×400) and Step 1 wraps the fox
|
||||
banner in a folded TreeNode after smoke feedback. Existing v1.5.1 users see the new wizard once on
|
||||
first v1.5.2 boot via a new `WizardLastShownVersion` config marker.
|
||||
|
||||
Under the hood: `WizardStateSmokeStep` joins the `/xlperf` lineup, the Build Suite gains twelve
|
||||
pure-helper xUnit Facts pinning all four privacy profile sets and the new Roleplay retention
|
||||
overrides. Migration v17 stays — `Configuration` only grows one optional string field.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user