You are viewing a potentially older version of this package. View all versions.
Paxton0505-Localization-1.0.1 icon

Localization

Runtime translation/localization helper for Valheim mods. For personal server use only; the author accepts no responsibility for use or modification.

Date uploaded 2 months ago
Version 1.0.1
Download link Paxton0505-Localization-1.0.1.zip
Downloads 19
Dependency string Paxton0505-Localization-1.0.1

This mod requires the following mods to function

denikson-BepInExPack_Valheim-5.4.2333 icon
denikson-BepInExPack_Valheim

BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.

Preferred version: 5.4.2333
ValheimModding-Jotunn-2.29.0 icon
ValheimModding-Jotunn

Jötunn (/ˈjɔːtʊn/, 'giant'), the Valheim Library was created with the goal of making the lives of mod developers easier. It enables you to create mods for Valheim using an abstracted API so you can focus on the actual content creation.

Preferred version: 2.29.0
ValheimModding-JsonDotNET-13.0.4 icon
ValheimModding-JsonDotNET

Shared version 13.0.3 of Json.NET from Newtonsoft, net45 package for use in Valheim mods. Maintained by the ValheimModding team.

Preferred version: 13.0.4

README

Localization

Localization is a BepInEx plugin for Valheim that translates hardcoded mod text at the final UI display layer.

Current behavior

  1. The plugin patches final text assignment for TMPro.TMP_Text and UnityEngine.UI.Text.
  2. Every displayed string is checked against an in-memory rule set before it is rendered.
  3. Rules are loaded from BepInEx/config/com.paxton0505.com/<Language>/*.json.
  4. All English/*.json files are merged into the fallback baseline.
  5. When the active game language changes, the plugin reloads the matching language folder such as Chinese_Trad/*.json and overlays it on top of the English baseline.
  6. Duplicate rules inside the same language merge are rejected: the plugin logs an error and ignores the later definition.

Config layout

config/
  com.paxton0505.com/
    English/
      core.json
      ui.json
    Chinese_Trad/
      core.json
      ui.json

If the game language is Chinese_Trad, the runtime merges:

  1. config/com.paxton0505.com/English/*.json
  2. config/com.paxton0505.com/Chinese_Trad/*.json

The active language rules override English rules with the same match key.

Installation

  1. Build the plugin:
dotnet build -c Release
  1. Copy bin/Release/net48/Localization.dll into your Valheim BepInEx plugins folder.

Example:

BepInEx/
  plugins/
    Localization.dll
  1. Create the config root if it does not exist yet:
BepInEx/
  config/
    com.paxton0505.com/
      English/
      Chinese_Trad/
  1. Add one or more JSON files under English and under the language folders you want to support.

Usage

1. Create English baseline rules

Create a file such as BepInEx/config/com.paxton0505.com/English/core.json:

{
  "exact": [
    {
      "match": "PackHorse",
      "replace": "PackHorse"
    }
  ],
  "templates": [
    {
      "match": "Player {{$playername}} joined server",
      "replace": "Player {{$playername}} joined server"
    }
  ]
}

English is the fallback baseline. It should contain the source strings you want the runtime to recognize.

2. Add translated rules for the target language

If the game language is Traditional Chinese, create a file such as BepInEx/config/com.paxton0505.com/Chinese_Trad/core.json:

{
  "exact": [
    {
      "match": "PackHorse",
      "replace": "負重"
    }
  ],
  "templates": [
    {
      "match": "Player {{$playername}} joined server",
      "replace": "{{$playername}} 進入了伺服器"
    }
  ]
}

If you want a captured template parameter to be resolved through the game's own localization system, use {{localize($name)}} in the replacement string instead of {{$name}}.

Example:

{
  "templates": [
    {
      "match": "Found {{$amount}} items matching '{{$item}}' in nearby containers.",
      "replace": "已在附近的容器中找到 {{$amount}} 個 {{localize($item)}}"
    }
  ]
}

When the game language is Chinese_Trad, the runtime loads all files from English/*.json, then all files from Chinese_Trad/*.json, and overlays the second set on top of the first.

3. Split rules across multiple files

You can split rules by topic.

Example:

config/
  com.paxton0505.com/
    English/
      items.json
      chat.json
    Chinese_Trad/
      items.json
      chat.json

All *.json files in the same language folder are merged automatically.

4. Duplicate behavior

If two files in the same language folder define the same exact match text or the same template match text, the plugin logs an error and ignores the later file entry.

This means file ordering is deterministic, but duplicates are not used as an override mechanism inside the same language folder.

5. Language switching

The plugin reloads rules when the game writes a new language value into PlayerPrefs.

  • If the language stays the same, it keeps using the cached compiled rules.
  • If the language changes, it reloads the matching language folder and clears translation caches.

6. What gets translated

This plugin only changes strings that reach the final UI text layer through TMPro.TMP_Text or UnityEngine.UI.Text.

That is why it can catch many hardcoded mod strings, but it also means:

  • it does not know the source mod reliably
  • it does not translate non-text UI assets
  • it only sees the final displayed string, not the original semantic meaning

Rule file format

Each JSON file can contain exact-match rules and template rules.

{
  "exact": [
    {
      "match": "PackHorse",
      "replace": "負重"
    }
  ],
  "templates": [
    {
      "match": "Player {{$playername}} joined server",
      "replace": "{{$playername}} 進入了伺服器"
    }
  ]
}

Exact rules

  • match must be a full exact string match.
  • replace is the translated result.

Template rules

  • Use {{$name}} tokens for captured parameters.
  • Use {{localize($name)}} tokens when the captured parameter should be resolved through the game's own localization or item-name lookup.
  • Placeholder names may contain letters, digits, and _, and must start with a letter or _.
  • Adjacent placeholders are rejected to keep matching deterministic and fast.
  • Function tokens are only supported in the replacement template, not in the match template.
  • Supported modes are explicit now: parameter tokens {{$name}} and function tokens {{localize($name)}}.
  • localize(...) currently supports exactly one placeholder argument.
  • Raw regex is intentionally not supported in the first version.

Avoiding token conflicts

{{...}} is reserved syntax inside template rules. If the source text itself literally contains {{$item}} or another token-shaped fragment, do not model it as a template rule.

Use an exact rule instead.

Example: a mod literally shows Use {{$item}} to continue on screen, and you only want to translate the sentence, not treat {{$item}} as a captured parameter.

{
  "exact": [
    {
      "match": "Use {{$item}} to continue",
      "replace": "使用 {{$item}} 繼續"
    }
  ]
}

That avoids a syntax collision because exact rules do not parse token syntax.

If the text is actually dynamic, keep the variable part only where it is really needed.

Good:

{
  "templates": [
    {
      "match": "Found {{$amount}} items matching '{{$item}}' in nearby containers.",
      "replace": "已在附近的容器中找到 {{$amount}} 個 {{localize($item)}}"
    }
  ]
}

This keeps {{amount}} and {{item}} as explicit parameters, while the surrounding sentence stays fixed and unambiguous.

Performance model

  • Exact rules use dictionary lookup.
  • Template rules are compiled once at load time.
  • Successful translations are cached.
  • Repeated misses are cached separately so noisy dynamic text does not flood the main translation cache.
  • Language changes are handled by the game's PlayerPrefs("language") update path instead of per-frame or timed polling.

Why Harmony

This plugin uses Harmony because hardcoded mod text usually bypasses normal localization systems.

If a mod directly does one of these:

  • tmpText.text = "PackHorse"
  • tmpText.SetText("Player Bob joined server")
  • uiText.text = "Some hardcoded text"

there is no clean configuration hook or official API callback to intercept that text before display.

Harmony solves that by patching the final text assignment methods at runtime.

Why harmony.PatchAll();

harmony.PatchAll(); scans this plugin assembly for classes marked with HarmonyPatch and applies all of them during startup.

In this project that is important for two reasons:

  1. The interception logic lives in dedicated patch classes such as TextInterceptionPatches, not inside PluginMain.
  2. As the plugin grows, you can add more patch classes without writing manual patch registration code for each method.

In practice, PatchAll() gives you one startup line that says: load every patch declared in this plugin assembly.

That is simpler and safer than manually calling Harmony patch methods one by one.

Why not avoid Harmony entirely

For hardcoded mod text, avoiding Harmony usually means you miss the real interception point.

The whole point of this plugin is to catch text at the bottom layer right before it is shown. For that, runtime method patching is the practical tool:

  • no game DLL modification
  • no need to edit third-party mods
  • one shared interception layer for many mods
  • works even when the mod never uses Jotunn or Valheim localization APIs

Limits

  • This plugin targets final UI text, so it does not reliably know which source mod originally produced the string.
  • The first version does not perform context-sensitive translation by UI hierarchy.
  • Rich-text canonicalization is not implemented yet.
  • Grammar-aware inflection is out of scope for the current template system.

Source layout

  • src/PluginMain.cs: plugin bootstrap and Harmony initialization.
  • src/TranslationRuntime.cs: active language tracking, caches, and translation entry point.
  • src/TranslationRuleLoader.cs: folder-based JSON loading and merge logic.
  • src/TextInterceptionPatches.cs: final UI text interception hooks.
  • src/CompiledTemplateRule.cs: safe placeholder-template matcher.

Build

Requirements:

  • .NET SDK with .NET Framework build support
  • Valheim managed assemblies available at the paths configured in Localization.csproj
  • BepInEx core assemblies available at the paths configured in Localization.csproj

Build command:

dotnet build -c Release

The resulting plugin assembly is written to bin/Release/net48/Localization.dll.

CHANGELOG

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[v2.0.1] - 2026-05-16

Fixed

  • Fixed the devtools Playground rebuild flow so rebuilding the page clears stale UI references instead of reusing dead controls, and clicking a scrollable input field now re-activates the underlying InputField so text entry keeps working after scrolling.

[v2.0.0] - 2026-05-16

Added

  • Added the built-in in-game Localization devtools overlay with Plugin, Performance, Trace, Browser, and Playground pages for live inspection and rule drafting.
  • Added root-level plugin localization catalogs (Localization-English.json and Localization-Chinese_Trad.json) with hot reload so overlay labels, plugin-owned UI strings, and fallback log text can be localized separately from mod translation rules.
  • Added runtime performance metrics, translation-analysis views, and rule-browser snapshots for loaded local and ServerSync rule files.
  • Added trace_feed_max_entries, devtools_enabled, and devtools_toggle_shortcut to BepInEx/config/com.paxton0505.localization/config.yaml, with Ctrl + F1 as the default devtools toggle in the UI.

Changed

  • config.yaml hot reload now also updates trace-feed capacity and the devtools overlay enable/shortcut state without restarting the game.
  • Root-level plugin localization files are now auto-generated and hot-reloaded alongside the language rule folders.
  • Updated the repository README and Thunderstore README to document the built-in devtools overlay, plugin-owned localization files, live runtime settings, and current Thunderstore packaging output.

Removed

  • Removed the unused DevToolsSectionedPage scaffold left over from earlier devtools layout experiments.

[v1.3.0] - 2026-05-14

Added

  • Added translation_enabled: true as the top-most runtime setting in BepInEx/config/com.paxton0505.localization/config.yaml.
  • Added deterministic validate-artifact workflow support for the translation agent, plus restored Python/runtime checks around sanitizer and artifact validation behavior.
  • Added an optional runtime settings file at BepInEx/config/com.paxton0505.localization/config.yaml with debug: false by default.
  • Added configurable whole-string longest-match exact replacement restrictions through whole_string_longest_match_mode and whole_string_longest_match_min_length in config.yaml.
  • Added constrained template placeholders for word, words(...), int(...), float(...), and oneof(...), including numeric where= predicates such as >0 && <10.

Changed

  • When translation_enabled: false, the runtime now bypasses UI translation and skips rule reloads until translation is enabled again.
  • Clarified that translation_enabled, debug, and other runtime knobs remain local client-side settings even when translation rules are synced through ServerSync.
  • Restored rich-text tag stripping for exact, template, and whole-string longest-match exact replacement while keeping OrdinalIgnoreCase behavior.
  • Normalized exact and template matches now re-apply only the outer tags immediately wrapping the matched visible span. Whole-string longest-match exact replacement keeps only the full string's outermost tags; internal word-level tag positions are not remapped.
  • Whole-string longest-match exact replacement now defaults to per_word mode, and runtime changes to its restriction settings immediately clear translation caches.
  • Updated the repository README and Thunderstore README to document tag-stripped matching with outer-tag reapplication.
  • Refreshed the repository README and Thunderstore README to document the current project layout, validation commands, runtime toggle behavior, and ServerSync hot reload semantics.
  • Signed int and float constrained placeholders now accept optional leading + or - by default, with further numeric filtering available through where= predicates.
  • When debug: true is enabled, the plugin now logs intercepted UI strings before translation, rule-file load details, cache hits and miss-cache hits, winning match stages, placeholder resolution, and loop or duplicate diagnostics.

[v1.2.3] - 2026-05-13

Added

  • Added normalized exact-rule matching for all translation hops by stripping rich-text tags from match input and comparing exact rules with OrdinalIgnoreCase.
  • Added normalized template matching so template literals also compare against tag-stripped input case-insensitively.

Changed

  • Matched output now always comes from the rule replace value, so intercepted source tags are not re-applied after translation.
  • Active-language translation hops now participate in the same relaxed normalized matching instead of limiting ignore-case behavior to the English bridge layer.
  • Updated the repository README and Thunderstore README to document normalized matching, tag stripping, and replace-first rendering behavior.

[v1.2.1] - 2026-05-13

Added

  • Added an English-only OrdinalIgnoreCase exact-rule fallback so English baseline normalization can still bridge casing variants such as PACKHORSE -> PackHorse -> 馱馬.

Changed

  • Kept active-language exact rules and all template matching case-sensitive, limiting ignore-case behavior to the English exact normalization layer.
  • Updated the repository README and Thunderstore README to document the new English-only ignore-case fallback and its scope.

[v1.2.0] - 2026-05-13

Added

  • Added chained runtime translation passes so one intercepted string can flow through multiple rule matches, such as German hardcoded text -> English baseline alias -> Chinese_Trad active translation.
  • Added chained exact-rule resolution for {{localize(...)}} template placeholders so placeholder values can also normalize through English aliases before landing on the final target language.

Changed

  • Updated the repository README and Thunderstore README to document chained translation behavior, English normalization patterns, and the new placeholder resolution flow.

[v1.1.1] - 2026-05-13

Changed

  • Lowered duplicate exact-rule and template-rule diagnostics from error level to debug level. Later duplicate definitions are still ignored, but they no longer surface as load errors.
  • Expanded the Thunderstore README into a fuller usage guide covering installation, config layout, rule format, load precedence, server/client behavior, and hot-reload details.
  • Reformatted this changelog to the Keep a Changelog structure.

[v1.1.0] - 2026-05-13

Added

  • Server-side hot reload logs so dedicated server config updates now show when a debounced snapshot is republished.
  • Debounced config file watcher reloads so rapid JSON saves only publish one local snapshot.
  • ServerSync-based session overlays so clients can receive the server's localization JSON tree without rewriting local files.
  • Remote/local layered rule merging so synced rules can override the local English and active-language files in a predictable order.

Changed

  • Dedicated servers now run in sync-only mode and skip the local translation runtime plus UI Harmony patches.
  • Bundled ServerSync into the plugin build and updated the release/docs for the new sync flow.

[v1.0.4] - 2026-05-13

Fixed

  • Fixed the release zip output and cleaned up the Thunderstore packaging flow.

Changed

  • Polished release metadata and trimmed package assets/docs for distribution.

[v1.0.3] - 2026-05-13

Changed

  • Optimized template rule dispatch and loading performance.

Added

  • GitHub Copilot-assisted translation workflow, prompts, agents, and Python tooling.
  • Recorded mod version metadata in the translation workflow and reorganized Thunderstore release files.

[v1.0.2] - 2026-05-10

Added

  • Thunderstore packaging basics including manifest and icon assets.

Changed

  • Refined the project/build setup and refreshed the README for initial release packaging.

[v1.0.1] - 2026-05-10

Added

  • Split the initial implementation into focused runtime, loader, template, and interception components.
  • Added exact/template rule loading, placeholder handling, and final UI text interception.

[v1.0.0] - 2026-05-10

Added

  • Initial project scaffolding, BepInEx plugin entry point, build file, and README.
  • First working prototype of the runtime localization plugin.