i18n: the five spots the smoke test found, and a BOM in the export

Reported from a real pass through the window.

The theme categories were a static readonly array, so the five names
froze at whatever language the plugin started in and a runtime switch
relabelled the entire window except them. Same shape as the layout
labels earlier in this cycle; this one got missed because replacing the
literals with resource lookups looks finished until you actually switch.

The status bar built its counts from English literals -- tab, tabs, msg,
tell, tells -- and the privacy pill said "Privacy-First" in all 25
files. The thousands separator follows the user's culture now too, so
German reads 1,2k rather than 1.2k.

The live preview claims to show what the window will look like. It was
showing English channel names next to a translated placeholder, which is
worse than either. Channel labels come from ChatType.Name() now and the
status slots share the strings with the real status bar. The four mock
chat lines stay English on the earlier decision.

And the export wrote a byte order mark. Encoding.UTF8 emits one, and a
leading U+FEFF makes the JSON invalid for every strict parser --
confirmed against a real export from the game, where python's json.load
refused the file. CSV keeps its BOM, because without one Excel guesses
the codepage and mangles every non-ASCII name.

The self-test that was supposed to catch that read the file with
File.ReadAllText, which strips a BOM while detecting the encoding. It
reads bytes now.

The status bar tests asserted English literals and started failing on a
German machine -- they pin a fixed culture now instead of inheriting the
locale of whoever runs them.
This commit is contained in:
2026-08-19 08:24:31 +02:00
parent fdb1a98519
commit e0c9efca05
31 changed files with 699 additions and 49 deletions
+10 -1
View File
@@ -49,6 +49,8 @@ internal static class ExportFormatExt
// substring is applied here. // substring is applied here.
internal static class MessageExporter internal static class MessageExporter
{ {
private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false);
internal record FilterDescription( internal record FilterDescription(
IReadOnlyCollection<int>? ChatTypes, IReadOnlyCollection<int>? ChatTypes,
DateTimeOffset? From, DateTimeOffset? From,
@@ -82,7 +84,14 @@ internal static class MessageExporter
int written; int written;
try try
{ {
using (var writer = new StreamWriter(temp, append: false, encoding: Encoding.UTF8)) // Encoding.UTF8 writes a byte order mark, and that is not a
// cosmetic detail here: a leading U+FEFF makes the JSON invalid for
// every strict parser, Python's json.load included. CSV is the one
// format that wants it -- without a BOM Excel guesses the codepage
// and mangles every non-ASCII name in the file.
var encoding = format == ExportFormat.Csv ? Encoding.UTF8 : Utf8NoBom;
using (var writer = new StreamWriter(temp, append: false, encoding))
{ {
written = format switch written = format switch
{ {
+8
View File
@@ -380,6 +380,14 @@ internal class HellionStrings
internal static string Settings_Fonts_Global => Get(nameof(Settings_Fonts_Global)); internal static string Settings_Fonts_Global => Get(nameof(Settings_Fonts_Global));
internal static string Settings_Fonts_Active => Get(nameof(Settings_Fonts_Active)); internal static string Settings_Fonts_Active => Get(nameof(Settings_Fonts_Active));
internal static string Settings_Preview_TypeAMessage => Get(nameof(Settings_Preview_TypeAMessage)); internal static string Settings_Preview_TypeAMessage => Get(nameof(Settings_Preview_TypeAMessage));
internal static string StatusBar_Tabs_One => Get(nameof(StatusBar_Tabs_One));
internal static string StatusBar_Tabs_Other => Get(nameof(StatusBar_Tabs_Other));
internal static string StatusBar_Tells_One => Get(nameof(StatusBar_Tells_One));
internal static string StatusBar_Tells_Other => Get(nameof(StatusBar_Tells_Other));
internal static string StatusBar_Messages => Get(nameof(StatusBar_Messages));
internal static string StatusBar_MessagesThousands => Get(nameof(StatusBar_MessagesThousands));
internal static string Settings_Preview_TitleMock => Get(nameof(Settings_Preview_TitleMock));
internal static string Settings_Preview_StatusOpen => Get(nameof(Settings_Preview_StatusOpen));
internal static string Settings_Section_Links => Get(nameof(Settings_Section_Links)); internal static string Settings_Section_Links => Get(nameof(Settings_Section_Links));
internal static string Settings_Section_Behaviour => Get(nameof(Settings_Section_Behaviour)); internal static string Settings_Section_Behaviour => Get(nameof(Settings_Section_Behaviour));
internal static string Settings_Section_Keybinds => Get(nameof(Settings_Section_Keybinds)); internal static string Settings_Section_Keybinds => Get(nameof(Settings_Section_Keybinds));
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Conserva</value> <value>Conserva</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Privadesa primer</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Obert</value> <value>Obert</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>manteniment</value> <value>manteniment</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} pestanya</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} pestanyes</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} xiuxiueig</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} xiuxiueigs</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} msg</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k msg</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Campió» Vista prèvia</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>obert</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Ponechat</value> <value>Ponechat</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Soukromí především</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Otevřeno</value> <value>Otevřeno</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>údržba</value> <value>údržba</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} karta</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} karty</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} šeptání</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} šeptání</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} zpr.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k zpr.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Šampion» Náhled</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>otevřeno</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Behold</value> <value>Behold</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Privatliv først</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Åben</value> <value>Åben</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>vedligeholdelse</value> <value>vedligeholdelse</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} tab</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} tabs</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} tells</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} besk.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k besk.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Champion» Forhåndsvisning</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>åben</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Behalten</value> <value>Behalten</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Datenschutz zuerst</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Offen</value> <value>Offen</value>
@@ -1328,4 +1328,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>Wartung</value> <value>Wartung</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} Tab</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} Tabs</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} Flüstern</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} Flüstern</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} Nachr.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k Nachr.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Champion» Vorschau</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>offen</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Διατήρηση</value> <value>Διατήρηση</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Απόρρητο πρώτα</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Ανοιχτό</value> <value>Ανοιχτό</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>συντήρηση</value> <value>συντήρηση</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} καρτέλα</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} καρτέλες</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} tells</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} μην.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k μην.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Πρωταθλητής» Προεπισκόπηση</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>ανοιχτό</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Mantener</value> <value>Mantener</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Privacidad primero</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Abierto</value> <value>Abierto</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>mantenimiento</value> <value>mantenimiento</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} pestaña</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} pestañas</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} susurro</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} susurros</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} msj</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k msj</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Campeón» Vista previa</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>abierto</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Säilytä</value> <value>Säilytä</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Yksityisyys ensin</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Avoin</value> <value>Avoin</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>ylläpito</value> <value>ylläpito</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} välilehti</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} välilehteä</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} kuiskaus</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} kuiskausta</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} vie.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k vie.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Mestari» Esikatselu</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>auki</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Conserver</value> <value>Conserver</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Confidentialité</value> <value>Confidentialité d'abord</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Ouvert</value> <value>Ouvert</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>maintenance</value> <value>maintenance</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} onglet</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} onglets</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} MP</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} MP</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} msg</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k msg</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Champion» Aperçu</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>ouvert</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Megtartás</value> <value>Megtartás</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Adatvédelem elöl</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Nyitott</value> <value>Nyitott</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>karbantartás</value> <value>karbantartás</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} lap</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} lap</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} suttogás</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} suttogás</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} üz.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k üz.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Bajnok» Előnézet</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>nyitva</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Mantieni</value> <value>Mantieni</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Privacy prima</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Aperto</value> <value>Aperto</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>manutenzione</value> <value>manutenzione</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} scheda</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} schede</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} sussurro</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} sussurri</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} msg</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k msg</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Campione» Anteprima</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>aperto</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>保持</value> <value>保持</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>プライバシー優先</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>オープン</value> <value>オープン</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>メンテナンス</value> <value>メンテナンス</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} タブ</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} タブ</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} テル</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} テル</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} 件</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k 件</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Champion» プレビュー</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>開いています</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>유지</value> <value>유지</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>개인정보 우선</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>전체 공개</value> <value>전체 공개</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>유지 관리</value> <value>유지 관리</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} 탭</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} 탭</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} 귓속말</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} 귓속말</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} 개</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k 개</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Champion» 미리보기</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>열림</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Behold</value> <value>Behold</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Personvern først</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Åpen</value> <value>Åpen</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>vedlikehold</value> <value>vedlikehold</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} fane</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} faner</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} tells</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} meld.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k meld.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Champion» Forhåndsvisning</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>åpen</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Bewaren</value> <value>Bewaren</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Privacy voorop</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Open</value> <value>Open</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>onderhoud</value> <value>onderhoud</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} tabblad</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} tabbladen</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} tells</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} ber.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k ber.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Kampioen» Voorbeeld</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>open</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Zachowaj</value> <value>Zachowaj</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Prywatność przede wszystkim</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Otwarty</value> <value>Otwarty</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>konserwacja</value> <value>konserwacja</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} zakładka</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} zakładki</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} szept</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} szepty</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} wiad.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k wiad.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Mistrz» Podgląd</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>otwarte</value>
</data>
</root> </root>
@@ -704,7 +704,7 @@
<value>Manter</value> <value>Manter</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Privacidade primeiro</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Aberto</value> <value>Aberto</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>manutenção</value> <value>manutenção</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} aba</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} abas</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} sussurro</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} sussurros</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} msg</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k msg</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Campeão» Pré-visualização</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>aberto</value>
</data>
</root> </root>
@@ -704,7 +704,7 @@
<value>Manter</value> <value>Manter</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Privacidade primeiro</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Aberto</value> <value>Aberto</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>manutenção</value> <value>manutenção</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} separador</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} separadores</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} sussurro</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} sussurros</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} msg</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k msg</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Campeão» Pré-visualização</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>aberto</value>
</data>
</root> </root>
+24
View File
@@ -1345,4 +1345,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>maintenance</value> <value>maintenance</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} tab</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} tabs</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} tells</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} msg</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k msg</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Champion» Vorschau</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>open</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Păstrează</value> <value>Păstrează</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Confidențialitate întâi</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Deschis</value> <value>Deschis</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>întreținere</value> <value>întreținere</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} tab</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} tab-uri</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} tell-uri</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} mes.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k mes.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Campion» Previzualizare</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>deschis</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Оставить</value> <value>Оставить</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Приватность прежде всего</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Открыто</value> <value>Открыто</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>обслуживание</value> <value>обслуживание</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} вкладка</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} вкладок</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} шёпот</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} шёпотов</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} сообщ.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k сообщ.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Чемпион» Предпросмотр</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>открыт</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Behåll</value> <value>Behåll</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Integritet först</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Öppen</value> <value>Öppen</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>underhåll</value> <value>underhåll</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} flik</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} flikar</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} viskning</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} viskningar</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} medd.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k medd.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Mästare» Förhandsgranskning</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>öppen</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Koru</value> <value>Koru</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Önce gizlilik</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Açık</value> <value>Açık</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>bakım</value> <value>bakım</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} sekme</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} sekme</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} tell</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} ileti</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k ileti</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Şampiyon» Önizleme</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>açık</value>
</data>
</root> </root>
+25 -1
View File
@@ -704,7 +704,7 @@
<value>Залишити</value> <value>Залишити</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>Приватність передусім</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>Відкрито</value> <value>Відкрито</value>
@@ -1333,4 +1333,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>обслуговування</value> <value>обслуговування</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} вкладка</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} вкладок</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} шепіт</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} шепотів</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} повід.</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k повід.</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«Чемпіон» Перегляд</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>відкрито</value>
</data>
</root> </root>
@@ -704,7 +704,7 @@
<value>保留</value> <value>保留</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>隐私优先</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>开放</value> <value>开放</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>维护</value> <value>维护</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} 标签</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} 标签</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} 密语</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} 密语</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} 条</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k 条</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«冠军» 预览</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>打开</value>
</data>
</root> </root>
@@ -704,7 +704,7 @@
<value>保留</value> <value>保留</value>
</data> </data>
<data name="StatusBar_Privacy_Enabled" xml:space="preserve"> <data name="StatusBar_Privacy_Enabled" xml:space="preserve">
<value>Privacy-First</value> <value>隱私優先</value>
</data> </data>
<data name="StatusBar_Privacy_Open" xml:space="preserve"> <data name="StatusBar_Privacy_Open" xml:space="preserve">
<value>開放</value> <value>開放</value>
@@ -1334,4 +1334,28 @@
<data name="Settings_Database_Op_Maintenance" xml:space="preserve"> <data name="Settings_Database_Op_Maintenance" xml:space="preserve">
<value>維護</value> <value>維護</value>
</data> </data>
<data name="StatusBar_Tabs_One" xml:space="preserve">
<value>{0} 分頁</value>
</data>
<data name="StatusBar_Tabs_Other" xml:space="preserve">
<value>{0} 分頁</value>
</data>
<data name="StatusBar_Tells_One" xml:space="preserve">
<value>{0} 悄悄話</value>
</data>
<data name="StatusBar_Tells_Other" xml:space="preserve">
<value>{0} 悄悄話</value>
</data>
<data name="StatusBar_Messages" xml:space="preserve">
<value>{0} 則</value>
</data>
<data name="StatusBar_MessagesThousands" xml:space="preserve">
<value>{0:0.0}k 則</value>
</data>
<data name="Settings_Preview_TitleMock" xml:space="preserve">
<value>«冠軍» 預覽</value>
</data>
<data name="Settings_Preview_StatusOpen" xml:space="preserve">
<value>開啟</value>
</data>
</root> </root>
@@ -126,6 +126,13 @@ internal sealed class ExportRoundTripStep : ISelfTestStep
if (File.Exists(path + ".part")) if (File.Exists(path + ".part"))
failures.Add("temporary file left behind after a successful export"); failures.Add("temporary file left behind after a successful export");
// Bytes, not text. File.ReadAllText strips a byte order mark while
// detecting the encoding, so a BOM that breaks every strict JSON parser
// is invisible to the check below -- and it shipped exactly that way.
var head = File.ReadAllBytes(path);
if (head.Length >= 3 && head[0] == 0xEF && head[1] == 0xBB && head[2] == 0xBF)
failures.Add("export JSON starts with a byte order mark");
// Parsed, not merely counted. The writer builds JSON by hand, and it // Parsed, not merely counted. The writer builds JSON by hand, and it
// shipped a build where the chat relation kinds were interpolated as // shipped a build where the chat relation kinds were interpolated as
// enum names -- "source_kind":LocalPlayer -- which every parser // enum names -- "source_kind":LocalPlayer -- which every parser
@@ -3,6 +3,7 @@ using System.Threading;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using HellionChat.Code;
using HellionChat.Resources; using HellionChat.Resources;
using HellionChat.Themes; using HellionChat.Themes;
using HellionChat.Ui.StyleEngine; using HellionChat.Ui.StyleEngine;
@@ -153,7 +154,7 @@ internal sealed class LivePreviewPanel : IDisposable
// unchanged — but both paths now share one resolver. No truncation here: // unchanged — but both paths now share one resolver. No truncation here:
// the preview draws a fixed, centred "«Champion» Preview" string. // the preview draws a fixed, centred "«Champion» Preview" string.
var textAbgr = HonorificTitleColor.ResolveTitleAbgr(null, theme); var textAbgr = HonorificTitleColor.ResolveTitleAbgr(null, theme);
var title = "«Champion» Preview"; var title = HellionStrings.Settings_Preview_TitleMock;
var crownGlyph = FontAwesomeIcon.Crown.ToIconString(); var crownGlyph = FontAwesomeIcon.Crown.ToIconString();
// Crown is a FontAwesome glyph (matches the real header); measure + draw // Crown is a FontAwesome glyph (matches the real header); measure + draw
@@ -196,7 +197,14 @@ internal sealed class LivePreviewPanel : IDisposable
// sat on different rows here, so the preview promised a layout the // sat on different rows here, so the preview promised a layout the
// sidebar never delivered -- and then the sidebar caught up. // sidebar never delivered -- and then the sidebar caught up.
var borderAbgr = ColourUtil.RgbaToAbgr(theme.Colors.Border); var borderAbgr = ColourUtil.RgbaToAbgr(theme.Colors.Border);
ReadOnlySpan<string> labels = ["Linkshell", "Tell", "FC"]; // Real channel names, not literals: the preview claims to show what the
// sidebar will look like, and the sidebar is localised.
ReadOnlySpan<string> labels =
[
ChatType.Linkshell1.Name(),
ChatType.TellIncoming.Name(),
ChatType.FreeCompany.Name(),
];
for (var i = 0; i < 3; i++) for (var i = 0; i < 3; i++)
{ {
var rowMin = new Vector2(origin.X, origin.Y + i * rowHeight); var rowMin = new Vector2(origin.X, origin.Y + i * rowHeight);
@@ -300,7 +308,7 @@ internal sealed class LivePreviewPanel : IDisposable
var pillMax = new Vector2(origin.X + pillWidth, max.Y - 4f); var pillMax = new Vector2(origin.X + pillWidth, max.Y - 4f);
draw.AddRectFilled(pillMin, pillMax, ColourUtil.RgbaToAbgr(theme.Colors.Primary), 6f); draw.AddRectFilled(pillMin, pillMax, ColourUtil.RgbaToAbgr(theme.Colors.Primary), 6f);
var pillLabel = "Say"; var pillLabel = ChatType.Say.Name();
var pillLabelSize = ImGui.CalcTextSize(pillLabel); var pillLabelSize = ImGui.CalcTextSize(pillLabel);
var pillTextPos = new Vector2( var pillTextPos = new Vector2(
pillMin.X + ((pillMax.X - pillMin.X) - pillLabelSize.X) * 0.5f, pillMin.X + ((pillMax.X - pillMin.X) - pillLabelSize.X) * 0.5f,
@@ -353,7 +361,12 @@ internal sealed class LivePreviewPanel : IDisposable
var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary); var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
var pillY = origin.Y + (height - pillH) * 0.5f; var pillY = origin.Y + (height - pillH) * 0.5f;
ReadOnlySpan<string> slots = ["Say", "open", "3 tabs"]; ReadOnlySpan<string> slots =
[
ChatType.Say.Name(),
HellionStrings.Settings_Preview_StatusOpen,
string.Format(HellionStrings.StatusBar_Tabs_Other, 3),
];
ReadOnlySpan<uint> dots = ReadOnlySpan<uint> dots =
[ [
theme.Colors.StatusSuccess, theme.Colors.StatusSuccess,
@@ -9,22 +9,25 @@ namespace HellionChat.Ui.Components.Settings;
internal sealed class ThemePicker internal sealed class ThemePicker
{ {
private static readonly (string Category, string[] Slugs, bool DefaultExpanded)[] CategoryMap = // Built per call, not once. As a static readonly array the category names
{ // froze at whatever language the plugin started in -- a runtime switch
( // relabelled the whole window except these five.
"Hellion Brand", private static (string Category, string[] Slugs, bool DefaultExpanded)[] CategoryMap =>
new[] { "hellion-arctic", "hellion-spectrum", "forge-merchantman" }, [
true (
), "Hellion Brand",
( new[] { "hellion-arctic", "hellion-spectrum", "forge-merchantman" },
HellionStrings.Settings_Theme_Category_Cool, true
new[] { "night-blue", "event-horizon", "indigo-violet", "crystal-nocturne" }, ),
false (
), HellionStrings.Settings_Theme_Category_Cool,
(HellionStrings.Settings_Theme_Category_Natural, new[] { "mint-grove" }, false), new[] { "night-blue", "event-horizon", "indigo-violet", "crystal-nocturne" },
(HellionStrings.Settings_Theme_Category_Classic, new[] { "chat2-classic" }, false), false
(HellionStrings.Settings_Theme_Category_Retro, new[] { "synthwave-sunset" }, false), ),
}; (HellionStrings.Settings_Theme_Category_Natural, new[] { "mint-grove" }, false),
(HellionStrings.Settings_Theme_Category_Classic, new[] { "chat2-classic" }, false),
(HellionStrings.Settings_Theme_Category_Retro, new[] { "synthwave-sunset" }, false),
];
// T2 ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs // T2 ThemePickerCategoryStep diffs this against ThemeRegistry.BuiltinSlugs
// to enforce coverage. Kept on the static map so the test does not pierce instance state. // to enforce coverage. Kept on the static map so the test does not pierce instance state.
+14 -4
View File
@@ -56,9 +56,16 @@ internal sealed class StatusBar
{ {
var msgPart = var msgPart =
messages >= 1000 messages >= 1000
? string.Format(CultureInfo.InvariantCulture, "{0:0.0}k msg", messages / 1000.0) ? string.Format(
: $"{messages} msg"; CultureInfo.CurrentCulture,
var tabsPart = $"{tabs} {(tabs == 1 ? "tab" : "tabs")}"; HellionStrings.StatusBar_MessagesThousands,
messages / 1000.0
)
: string.Format(HellionStrings.StatusBar_Messages, messages);
var tabsPart = string.Format(
tabs == 1 ? HellionStrings.StatusBar_Tabs_One : HellionStrings.StatusBar_Tabs_Other,
tabs
);
return $"{tabsPart} · {msgPart}"; return $"{tabsPart} · {msgPart}";
} }
@@ -66,7 +73,10 @@ internal sealed class StatusBar
{ {
if (count <= 0) if (count <= 0)
return string.Empty; return string.Empty;
return $"{count} {(count == 1 ? "tell" : "tells")}"; return string.Format(
count == 1 ? HellionStrings.StatusBar_Tells_One : HellionStrings.StatusBar_Tells_Other,
count
);
} }
// Single-pass aggregator — same shape as the previous helper so the // Single-pass aggregator — same shape as the previous helper so the