Skip to content

fix(localization): show translations instead of serialized localized strings - #1245

Merged
sven-n merged 1 commit into
masterfrom
claude/fix-localized-string-display
Oct 18, 2026
Merged

sven-n merged 1 commit into
masterfrom
claude/fix-localized-string-display

Conversation

@sven-n

@sven-n sven-n commented Oct 18, 2026

Copy link
Copy Markdown
Member

Summary

LocalizedString converts implicitly to string, but that conversion returns the serialized value, including all translations and metadata. For example, the map editor heading showed Lorencia||zh-CN=勇者大陆||@src=MapNames/Lorencia||@stamp=47cb3df4. The conversion is picked silently by Razor @… output, StringBuilder.Append, string parameters and == comparisons.

To find every usage, I temporarily marked both implicit LocalizedString → string operators [Obsolete] and built the whole solution, including the tests. That gave 55 sites. I evaluated each one; the fix depends on what the text is used for:

Purpose Fix Sites
Captions in the admin panel / live map translation for the current culture (ToString()) map editor heading, master skill root headers (SkillListField), merchant heading, map page title, live map names (GameMapInfoAdapter.MapName), cash shop item caption, socket option captions, and the ToString() of Item, ItemCraftingResultItem and MiniGameReward (used as captions in the admin panel)
Messages to players translation for each player's culture Blood Castle / Chaos Castle / … entrance opened/closed announcements (MiniGameStartBasePlugIn). They went out as one raw string to everybody, so configured translations were shown verbatim. The new IGameContext.SendGlobalNotificationAsync(LocalizedString, params object?[]) overload formats the text once per culture, like ShowGlobalLocalizedMessageAsync does for resource keys.
Published game events (GameEventPublisherPlugIn) neutral text (ValueInNeutralLanguage) map, monster, item and mini game names in the events, which carried all translations and the @src/@stamp metadata
Lookups in update plugins (x.Name == "Spirit", etc., 17 lines in 10 plugins) neutral text Once an admin links the captions (Captions page), the raw value becomes Spirit||de=…. A still pending optional update would then not find its data, or throw in Single(). Applies the same pattern other updates already use. Behavior for not-yet-linked data is unchanged.
Item drops search (EditItemDrops) displayed text or neutral text It searched the raw value, so it also matched the metadata (searching src matched everything).
Debug message (TerrainUpdateHelper) neutral text —

Left unchanged on purpose:

  • BaseInvasionPlugIn: string.IsNullOrWhiteSpace(EndMessage) is an emptiness check of the raw value, which is the right semantics.
  • Test assertion messages in GensRewardItemsTest and MasterSkillPassivePowerUpsTest, and the operator tests in LocalizedStringTests.

Noticed, not changed

EventTicketItems (Season 6 and 0.95d initializers) builds the drop item group descriptions as name + "+" + itemLevel. name comes from LocalizedString.FromResource(...), so the result is Scroll of Archangel||de=…||@stamp=abcd+1. The level ends up glued to the stamp, and every level of a ticket shows the same caption. Fixing it needs an initializer change plus a configuration update plugin for existing databases (rule 4), so it belongs in its own PR.

Testing

  • Full solution build: no new warnings, no errors. A second run with the [Obsolete] marker only reports the intentional sites listed above (plus EventTicketItems).
  • dotnet test: MUnique.OpenMU.Tests (1883 + 2 new), MUnique.OpenMU.Web.Tests (242), MUnique.OpenMU.Persistence.Initialization.Tests (643, 7 skipped as before) and MUnique.OpenMU.PlugIns.Tests (49) all pass.
  • New LocalizedCaptionTests check that the captions of ItemCraftingResultItem and MiniGameReward show the German translation under a German culture. Both fail without the fix (e.g. "Undefined 0 Spirit||de=Geist||@src=ItemNames/Spirit").
  • The mini game announcement tests now capture the new localized overload. They passing shows that the announcements go through it.
  • In the browser (Startup -demo, Playwright): the map editor heading shows Lorencia, and 勇者大陆 with the zh-CN UI culture. The merchant heading shows Alex.

Generated by Claude Code

…strings

A LocalizedString converts implicitly to string, but that conversion returns
the serialized value with all translations and metadata, e.g.
"Lorencia||zh-CN=勇者大陆||@src=MapNames/Lorencia||@stamp=…". Razor output,
StringBuilder.Append, string parameters and comparisons pick it silently.

All usages were found by temporarily marking the implicit conversions as
obsolete, and fixed depending on their purpose:

* Admin panel and live map captions (map editor heading, master skill root
  headers, merchant heading, map page title, live map names, cash shop item
  captions, socket option captions) and the ToString() of Item,
  ItemCraftingResultItem and MiniGameReward use the translation of the
  current culture.
* The mini game entrance announcements are sent in the language of each
  player, through a new IGameContext.SendGlobalNotificationAsync overload
  for localized strings.
* Published game events contain the neutral text instead of all
  translations and metadata.
* The update plugins and the item drop search compare with the neutral
  text, so that they also work after the captions were linked to their
  translations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KESqfok2EbBMX41efgZUvw
@sven-n
sven-n merged commit d54b6b2 into master Oct 18, 2026
2 checks passed
@sven-n
sven-n deleted the claude/fix-localized-string-display branch October 18, 2026 10:06
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