Another observation
Lua Code:
function ZO_GuildRanks_Keyboard:RefreshRankInfo()
...
self.rankNameEdit:SetText(rank.name)
...
end
SetText() triggers "OnTextChanged" event and leads to ZO_GuildRanks_Keyboard:GuildRankNameEdit_OnTextChanged() calling ZO_GuildRank_Shared:SetName(), which is superfluous. You're filling rankNameEdit from rank.name, and it ends up setting rank.name from rankNameEdit (and consequently setting rank.hasCustomName differently from how it was set in ZO_GuildRank_Shared:New()).
If I disable the "OnTextChanged" handler during the SetText() call, I get no freeze and Save/Cancel buttons don't show up. All four ranks have hasCustomName == true. Seems like this little thing fixes it... almost. There's still an issue with the first two ranks. When I select 'Trader' (rank #4 with custom name) and change it to 'Trader123', Save/Cancel buttons show up; and when I change it back to 'Trader', the buttons disappear. Great. But if I do the same with 'Officer' (rank #2 with default name), the buttons don't disappear after I change the name back to original 'Officer'. And now the funny part: I switch language to "de", rename rank 'Officer' to 'Officer123', Save/Cancel buttons show up, rename back to 'Officer', and the buttons disappear!
Now let me guess. The first two ranks -- 'Guildmaster' and 'Officer' -- are the default English names. But they're not default in German/French. So when the actual guildmaster saved these ranks running a German client, they were deemed custom and saved as such. Now GetGuildRankCustomName() returns these names verbatim, instead of empty strings you'd expect on English client.
In short,
if (GetGuildRankCustomName(gid, rid) == GetDefaultGuildRankName(gid, rid)), then hasCustomName logic fails, and coupled with the undesirable OnTextChanged behaviour and recursive RefreshRanksFromGuildData, the game freezes.