Skip to content

Added string localization - #293

Open
barrcodes wants to merge 5 commits into
karashiiro:mainfrom
barrcodes:feature/localization
Open

Added string localization#293
barrcodes wants to merge 5 commits into
karashiiro:mainfrom
barrcodes:feature/localization

Conversation

@barrcodes

Copy link
Copy Markdown
Contributor

Adds localization to the 4 languages supported natively by the FFXIV client: English, French, German, and Japanese.

See the Demo Video for clarity, and a breakdown of where the translations come from and how they were validated. See also the attached translation TSV files referenced in the video.

Strings.fr.tsv
Strings.ja.tsv
Strings.de.tsv

@barrcodes

Copy link
Copy Markdown
Contributor Author

one note: Localizer.ClientLanguage prioritized the FFXIV client's selected UI language. It only uses the Dalamud language setting if it differs from the default (dalamud defaults system ui culture).

It's a little hack so I could test localization easily without reloading the ffxiv client. I could simplify this to always use the FFXIV client language.

Also, if a key does not exist for a given language, it defaults to english. so if you selected Italian in Dalamud, you'll see english UI localization.

@karashiiro

karashiiro commented Aug 30, 2026

Copy link
Copy Markdown
Owner

I could simplify this to always use the FFXIV client language.

Have we considered always following the Dalamud language instead, or are there issues with that? I think we should probably be consistent and always follow one or the other, and following Dalamud feels more consistent from a user expectation standpoint for a Dalamud plugin compared to following the game language, I think.

Comment thread src/TextToTalk.Tests/Localization/LocalizerTests.cs
Comment on lines +103 to +121
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,
};

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 get treated as equivalent for runtime TTS purposes? It looks like this inconsistently collapses these for the config UI but not actually in the TTS pipeline, if I'm not mistaken. If nothing else this probably benefits from a unit test.

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 validated with unit tests that these do not collapse - the current code now simply uses this to map {chatType} to GM {chatType} for display purposes for GM chat types.

{
internal static class ChatTypeMap
{
internal static readonly IReadOnlyDictionary<int, uint> LogFilterRowIds =

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.

Can these be resolved via a sheet join at runtime? If possible, I think that would be better long term than this.

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.

Done, I think. let me know if there's a better pattern, I'm somewhat new to the sheet data.

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.

Comment thread src/TextToTalk/Localization/ChatTypeMap.cs Outdated
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

}

return finalWords.Append(words.Last()).ToString();
return $"[{chatType}]";

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 should probably fall back to SplitWords(chatType) here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants