Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/TextToTalk.Tests/Localization/ChatTypeMapTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using Dalamud.Game.Text;
using TextToTalk.Localization;
using Xunit;

namespace TextToTalk.Tests.Localization;

public class ChatTypeMapTests
{
[Theory]
[InlineData(XivChatType.Say, XivChatType.GmSay)]
[InlineData(XivChatType.GmSay, XivChatType.Say)]
[InlineData(XivChatType.Party, XivChatType.GmParty)]
[InlineData(XivChatType.GmParty, XivChatType.Party)]
public void IsChatTypeEnabled_KeepsGmAndBaseChannelsSeparate(
XivChatType enabledChatType,
XivChatType receivedChatType)
{
Assert.False(ChatTypeMap.IsChatTypeEnabled(
new List<int> { (int)enabledChatType },
enableAllChatTypes: false,
receivedChatType));
}

[Fact]
public void IsChatTypeEnabled_DoesNotEnableUnrelatedChannels()
{
Assert.False(ChatTypeMap.IsChatTypeEnabled(
new List<int> { (int)XivChatType.Say },
enableAllChatTypes: false,
XivChatType.Party));
}

[Fact]
public void IsChatTypeEnabled_EnableAllAcceptsAnyChannel()
{
Assert.True(ChatTypeMap.IsChatTypeEnabled(
enabledChatTypes: null,
enableAllChatTypes: true,
XivChatType.GmLinkshell8));
}
}
38 changes: 38 additions & 0 deletions src/TextToTalk.Tests/Localization/LocalizerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Globalization;
using TextToTalk.Localization;
using TextToTalk.Resources;
using Xunit;

namespace TextToTalk.Tests.Localization;

public class LocalizerTests
{
[Theory]
[InlineData("en")]
[InlineData("fr")]
[InlineData("de")]
[InlineData("ja")]
public void UsesCultureForUiLanguage(string language)
{
var localizer = new Localizer(language);

Assert.Equal(CultureInfo.GetCultureInfo(language), localizer.Culture);
}

[Fact]
public void UsesEnglishResourcesWhenTranslationIsUnavailable()
{
_ = new Localizer("pt");

Assert.Equal("TextToTalk Configuration", Strings.Get("ConfigurationTitle"));
}
Comment thread
barrcodes marked this conversation as resolved.

[Fact]
public void UsesEnumNameForLocalizedChatTypeResource()
{
_ = new Localizer("it");

Assert.Equal("Dire", Strings.Get("ChatTypeSay"));
}

}
27 changes: 12 additions & 15 deletions src/TextToTalk/Backends/BackendUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using Dalamud.Bindings.ImGui;
using TextToTalk.UI;
using TextToTalk.Resources;

namespace TextToTalk.Backends;

Expand All @@ -15,39 +16,35 @@ public static void GenderedPresetConfig(string uniq, TTSBackend backend, PluginC
var maleVoices = voiceConfig.GetMalePresets(backend);
var femaleVoices = voiceConfig.GetFemalePresets(backend);

if (ImGuiPresetCombo($"Ungendered preset(s)##{MemoizedId.Create(uniq: uniq)}", ungenderedVoices, presets))
if (ImGuiPresetCombo($"{Strings.Get("BackendUngenderedPresets")}##{MemoizedId.Create(uniq: uniq)}", ungenderedVoices, presets))
{
config.Save();
}
Components.HelpTooltip("""
By default, NPCs in the game only have genders of 0 and 1, regardless of their canonical gender (or lack thereof).
As such, any ungendered characters need to be specified by us in order to be properly reflected in-game.
See https://github.com/karashiiro/TextToTalk/wiki/Adding-NPCs-to-the-Ungendered-Overrides-List for more information.
""");
Components.HelpTooltip(Strings.Get("BackendUngenderedHelp"));

if (!ungenderedVoices.Any())
{
ImGui.TextColored(ImColor.Red, "No ungendered voice preset(s) are selected.");
ImGui.TextColored(ImColor.Red, Strings.Get("BackendNoUngenderedPresets"));
}

if (ImGuiPresetCombo($"Male preset(s)##{MemoizedId.Create(uniq: uniq)}", maleVoices, presets))
if (ImGuiPresetCombo($"{Strings.Get("BackendMalePresets")}##{MemoizedId.Create(uniq: uniq)}", maleVoices, presets))
{
config.Save();
}

if (!maleVoices.Any())
{
ImGui.TextColored(ImColor.Red, "No male voice preset(s) are selected.");
ImGui.TextColored(ImColor.Red, Strings.Get("BackendNoMalePresets"));
}

if (ImGuiPresetCombo($"Female preset(s)##{MemoizedId.Create(uniq: uniq)}", femaleVoices, presets))
if (ImGuiPresetCombo($"{Strings.Get("BackendFemalePresets")}##{MemoizedId.Create(uniq: uniq)}", femaleVoices, presets))
{
config.Save();
}

if (!femaleVoices.Any())
{
ImGui.TextColored(ImColor.Red, "No female voice preset(s) are selected.");
ImGui.TextColored(ImColor.Red, Strings.Get("BackendNoFemalePresets"));
}

ImGuiMultiVoiceHint();
Expand Down Expand Up @@ -97,18 +94,18 @@ public static void DeletePresetButton(string label, VoicePreset preset, TTSBacke

public static void ImGuiVoiceNotSupported()
{
ImGui.TextColored(ImColor.Red, "Voice not supported on this engine");
ImGui.TextColored(ImColor.Red, Strings.Get("BackendVoiceUnsupported"));
}

public static void ImGuiVoiceNotSelected()
{
ImGui.TextColored(ImColor.Red, "No voice selected");
ImGui.TextColored(ImColor.Red, Strings.Get("BackendNoVoiceSelected"));
}

public static void ImGuiMultiVoiceHint()
{
ImGui.TextColored(ImColor.HintColor,
"If multiple presets are selected per gender, they will be randomly assigned to characters.");
Strings.Get("BackendMultiplePresetsHint"));
}

public static bool ImGuiPresetCombo(string label, SortedSet<int> selectedPresets, List<VoicePreset> presets)
Expand Down Expand Up @@ -143,4 +140,4 @@ public static bool ImGuiPresetCombo(string label, SortedSet<int> selectedPresets
ImGui.EndCombo();
return didPresetsChange;
}
}
}
20 changes: 12 additions & 8 deletions src/TextToTalk/Backends/Megaphone/MegaphoneBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Dalamud.Interface.Utility.Raii;
using TextToTalk.Backends.Websocket;
using TextToTalk.Events;
using TextToTalk.Resources;
using TextToTalk.Services;
using TextToTalk.UI;

Expand Down Expand Up @@ -132,13 +133,13 @@ private void DrawPortConfig()
var port = this.config.MegaphonePort;
var portStr = port.ToString();

var didUpdate = ImGui.InputText("Port", ref portStr, 5, ImGuiInputTextFlags.CharsDecimal);
var didUpdate = ImGui.InputText(Strings.Get("MegaphonePort"), ref portStr, 5, ImGuiInputTextFlags.CharsDecimal);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably inject Localizer for backends?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reworked this a little bit, but the intention of Localizer was to provide convenience methods like HasGameData, which allows us to gather info from lumina when necessary. It really wasn't intended to be the middleware for getting string values.

Localizer sets the culture based on dalamud's language setting, and that culture travels through to static Strings.Get(str) and Strings.KeyName calls.


if (int.TryParse(portStr, out var newPort))
{
if (!IsValidPort(newPort))
{
ImGui.TextColored(Red, "Port is out of range [0, 65535]");
ImGui.TextColored(Red, Strings.Get("MegaphonePortOutOfRange"));
}
else if (didUpdate)
{
Expand All @@ -149,14 +150,14 @@ private void DrawPortConfig()
}
else
{
ImGui.TextColored(Red, "Unable to parse port.");
ImGui.TextColored(Red, Strings.Get("MegaphonePortInvalid"));
}
}

private void DrawInstallLink()
{
ImGui.TextColored(Hint, "Install the Megaphone Dalamud plugin:");
if (ImGui.Button($"Open Install Guide##{MemoizedId.Create()}"))
ImGui.TextColored(Hint, Strings.Get("MegaphoneInstallPrompt"));
if (ImGui.Button($"{Strings.Get("MegaphoneOpenInstallGuide")}##{MemoizedId.Create()}"))
{
Dalamud.Utility.Util.OpenLink(InstallUrl);
}
Expand All @@ -165,15 +166,18 @@ private void DrawInstallLink()
private void DrawServerStatus()
{
var fullServiceUrl = this.wsServer.ServiceUrl + this.wsServer.ServicePath;
ImGui.TextColored(Hint, $"{(this.wsServer.Active ? "Started" : "Will start")} on {fullServiceUrl}");
var status = this.wsServer.Active
? Strings.Get("MegaphoneServerStarted")
: Strings.Get("MegaphoneServerWillStart");
ImGui.TextColored(Hint, string.Format(Strings.Get("MegaphoneServerStatusFormat"), status, fullServiceUrl));
}

private void DrawServerRestart()
{
using var bcAzure = ImRaii.PushColor(ImGuiCol.Button, Azure, this.dirtyConfig);
using var bcAzureHovered = ImRaii.PushColor(ImGuiCol.ButtonHovered, AzureHovered, this.dirtyConfig);
using var bcAzureActive = ImRaii.PushColor(ImGuiCol.ButtonActive, AzureActive, this.dirtyConfig);
if (ImGui.Button($"Restart server##{MemoizedId.Create()}"))
if (ImGui.Button($"{Strings.Get("MegaphoneRestartServer")}##{MemoizedId.Create()}"))
{
ImCatchServerRestart(() =>
{
Expand Down Expand Up @@ -247,4 +251,4 @@ public MegaphoneConfigProvider(PluginConfiguration config)

public IPAddress? GetAddress() => null;
}
}
}
9 changes: 5 additions & 4 deletions src/TextToTalk/Backends/VoiceBackendManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using TextToTalk.Backends.Uberduck;
using TextToTalk.Backends.Websocket;
using TextToTalk.Events;
using TextToTalk.Resources;
using TextToTalk.Services;

namespace TextToTalk.Backends
Expand Down Expand Up @@ -102,7 +103,7 @@ public void SetBackend(TTSBackend backendKind)
catch (Exception e)
{
this.notificationService.NotifyError(
$"Failed to switch to {kind.GetFormattedName()} backend.",
string.Format(Strings.Get("BackendSwitchFailed"), kind.GetFormattedName()),
e.Message);
}
finally
Expand All @@ -122,8 +123,8 @@ private void WarnIfNoPresetsConfiguredForBackend()
}

this.notificationService.NotifyWarning(
"You have no voice presets configured.",
"Please create a voice preset in the TextToTalk configuration.");
Strings.Get("BackendNoPresetsConfigured"),
Strings.Get("BackendCreatePresetHint"));
}

public Vector4 GetBackendTitleBarColor()
Expand Down Expand Up @@ -158,4 +159,4 @@ protected override void Dispose(bool disposing)
}
}
}
}
}
8 changes: 0 additions & 8 deletions src/TextToTalk/GameEnums/AdditionalChatType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@
// These should be added to Dalamud proper
public enum AdditionalChatType
{
LootMessage = 62,
CharacterProgress = 64,
Loot = 65,
Crafting = 66,
Gathering = 67,
FCAnnouncement = 69,
FCLogin = 70,
PartyFinderState = 72,
ActionUsedOnYou = 2091,
FailedActionUsedOnYou = 2218,
ActionReadiedByYou = 2219,
Expand Down
86 changes: 86 additions & 0 deletions src/TextToTalk/Localization/ChatTypeMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
using Lumina.Excel.Sheets;
using TextToTalk.GameEnums;
using TextToTalk.Resources;

namespace TextToTalk.Localization
{
internal static class ChatTypeMap
{
internal static readonly IReadOnlyDictionary<int, uint> AddonRowIds =
new Dictionary<int, uint>
{
[(int)XivChatType.Urgent] = 2251,
[(int)XivChatType.Notice] = 12874,
};

internal static readonly IReadOnlyDictionary<XivChatType, XivChatType> GmBaseChatTypes =
new Dictionary<XivChatType, XivChatType>
{
[XivChatType.GmTell] = XivChatType.TellIncoming,
[XivChatType.GmSay] = XivChatType.Say,
[XivChatType.GmShout] = XivChatType.Shout,
[XivChatType.GmYell] = XivChatType.Yell,
[XivChatType.GmParty] = XivChatType.Party,
[XivChatType.GmFreeCompany] = XivChatType.FreeCompany,
[XivChatType.GmLinkshell1] = XivChatType.Ls1,
[XivChatType.GmLinkshell2] = XivChatType.Ls2,
[XivChatType.GmLinkshell3] = XivChatType.Ls3,
[XivChatType.GmLinkshell4] = XivChatType.Ls4,
[XivChatType.GmLinkshell5] = XivChatType.Ls5,
[XivChatType.GmLinkshell6] = XivChatType.Ls6,
[XivChatType.GmLinkshell7] = XivChatType.Ls7,
[XivChatType.GmLinkshell8] = XivChatType.Ls8,
[XivChatType.GmNoviceNetwork] = XivChatType.NoviceNetwork,
};

internal static bool IsChatTypeEnabled(IList<int>? enabledChatTypes, bool enableAllChatTypes, XivChatType chatType)
{
return enableAllChatTypes || enabledChatTypes?.Contains((int)chatType) == true;
}

internal static bool TryGetResourceName(int chatType, out string name)
{
var enumName = Enum.GetName(typeof(XivChatType), (ushort)chatType)
?? Enum.GetName(typeof(AdditionalChatType), chatType);
name = enumName is null ? string.Empty : Strings.Get($"ChatType{enumName}");
return name.Length > 0 && !name.StartsWith("[", StringComparison.Ordinal);
}

internal static bool TryGetAdditionalChannelName(int chatType, out string name)
{
name = chatType switch
{
(int)XivChatType.Debug => Strings.ChatTypeDebug,
(int)XivChatType.CrossParty => Strings.ChatTypeCrossParty,
(int)XivChatType.Orchestrion => Strings.ChatTypeOrchestrion,
(int)XivChatType.TellIncoming => Strings.ChatTypeTellIncoming,
(int)XivChatType.TellOutgoing => Strings.ChatTypeTellOutgoing,
(int)AdditionalChatType.EnemyDefeatedByYou => Strings.ChatTypeEnemyDefeatedByYou,
_ => string.Empty,
};
return name.Length > 0;
}

internal static bool TryGetLogFilterName(IDataManager data, ClientLanguage language, int chatType, out string name)
{
name = string.Empty;
return data.GetExcelSheet<LogFilter>(language)?.FirstOrDefault(row =>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this seem to perform fine in-game? This comes out to what I think (?) is an O(m log(n)) scan over m supported/known chat types and n LogFilter rows each frame in ConfigurationWindow (FirstOrDefault iterates the table until hitting the matching LogKind, which gets further through in table for each chat type).

I think we should memoize this; if it's not really noticeable while the tab is open then it's fine for now and that can be a follow-up, otherwise we should do it here I think. Just a static Dictionary field should be fine for this either way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a good callout. in testing I did not notice issues, but I will address this

(int)row.LogKind == chatType && !string.IsNullOrWhiteSpace(row.Name.ToString())) is { } row
&& !string.IsNullOrWhiteSpace(name = row.Name.ToString());
}

internal static bool TryGetAddonName(IDataManager data, ClientLanguage language, int chatType, out string name)
{
name = string.Empty;
return AddonRowIds.TryGetValue(chatType, out var rowId)
&& data.GetExcelSheet<Addon>(language)?.TryGetRow(rowId, out var row) == true
&& !string.IsNullOrWhiteSpace(name = row.Text.ToString());
}
}
}
Loading