A localization tool for Unreal Engine assets (.uasset, .uexp, .umap) and localization resources (.locres).
UassetLocalize is a ground-up rewrite of UE4LocalizationsTool, which is no longer maintained. The important difference is under the hood: asset parsing is done by UAssetAPI, the library behind UAssetGUI, rather than by hand-written byte splicing. That is why this tool follows new engine versions instead of guessing.
There is a GUI and a command line tool. Both do the same things.
| Format | Notes |
|---|---|
.uasset, .umap |
Anything UAssetAPI parses — roughly UE 4.13 through 5.7 |
.locres |
All four versions, including the CityHash64 one used by recent games |
Inside an asset it finds text in FText and FString properties (however deeply nested
in arrays, structs, maps and sets), string table exports, data table rows, and — if you
ask for it — FName properties and string literals compiled into blueprint bytecode.
No .usmap for the game? There's a raw text scan
that reads assets anyway.
git clone --recurse-submodules https://github.com/rm-NoobInCoding/UassetLocalize.git
cd UassetLocalize
dotnet build -c ReleaseYou need the .NET 10 SDK. If you
already cloned without --recurse-submodules, run git submodule update --init.
The GUI ends up at src/UassetLocalize.App/bin/Release/net10.0/UassetLocalize.exe and
the CLI at src/UassetLocalize.Cli/bin/Release/net10.0/UassetLocalize.Cli.exe.
The icon lives in assets/ as an .svg, a .png and a multi-resolution .ico, all
written by assets/make-icon.ps1 from one set of shapes so they can't drift apart. You
only need to run it if you change the design; the .svg has the letters as outlines, so
editing that needs no particular fonts installed.
Open a file, or drag one onto the window. The grid lists every piece of text under a heading for the export it came from, so a row only has to say where it sits inside that export rather than repeating the whole path. The pane underneath is where you actually write, because it handles line breaks and long dialogue properly.
- Kind says what a row is.
RawandCodeare marked in amber, because a raw scan hit is a guess and a bytecode literal can't safely change length. - Edited rows are marked by a rail down the left. The full path is a hover away, and the selected row's localization key sits in the status bar.
- Search matches translation, source, key and path at once — and takes the filter
syntax when you want to be more specific. The
?in the box spells it out. - Edited only narrows the list to what you have changed. It's the same as
edited:yes. - Replace works across whatever rows are currently shown.
- Translation → Export / Import moves text in and out of CSV, PO or plain text.
- Settings (
Ctrl ,) holds the engine version, the mappings file and the file type associations. You shouldn't need to go looking in there to read a file: when one needs a.usmapor a version, the window asks for it.
When an asset turns out to need a .usmap you get a dialog offering to open one, with
the engine version alongside it — already set to whatever the file's header looks like —
and a second button to guess without mappings instead. Turn that down and the offer stays
on a strip above the grid, so a half-read file never sits there unexplained.
Saving defaults to writing <name>_NEW.uasset next to the original rather than
overwriting it, because the original usually came out of a game archive.
Settings → File types registers .uasset, .umap and .locres, each on its own.
This adds UassetLocalize to the Open with menu for that extension, and makes it what
a double click opens when nothing else has claimed the extension — an existing
association is never taken over.
Everything is written under HKCU\Software\Classes, so it needs no administrator, affects
only your account, and unticking the box removes exactly what ticking it added.
# See what's in a file without changing anything
UassetLocalize.Cli info Game.locres
# Export for translation, then bring it back
UassetLocalize.Cli export DT_Dialogue.uasset lines.po --engine 5.3
UassetLocalize.Cli import DT_Dialogue.uasset lines.po --engine 5.3
# Everything under a folder, into one combined spreadsheet
UassetLocalize.Cli export-all Content strings.csv --single --engine 5.3
UassetLocalize.Cli import-all Content strings.csv --engine 5.3 --out Content_translatedRun it with no arguments for the full option list, and engines to list the engine
version names --engine accepts (5.3 and VER_UE5_3 both work).
Exit codes: 0 success, 1 error, 2 finished but some rows matched nothing or some
files were skipped.
The same syntax works in the GUI's search box and in --filter on the command line.
Plain words still just search, so there's nothing to learn until you want it.
# Only the English rows that nobody has translated yet
UassetLocalize.Cli export-all Content todo.csv --single --engine 5.4 \
--filter "key~_en$ empty:yes"
# Everything except raw-scan guesses
UassetLocalize.Cli export DT_Table.uasset text.csv --raw --filter "-kind:raw"| Term | Matches |
|---|---|
new game |
rows containing new and game, anywhere |
"new game" |
rows containing the phrase |
key:Item_ |
key contains, ignoring case |
key=Item_Tonic |
key equals, ignoring case |
key~^Item_.*Name$ |
key matches a regular expression, case sensitive |
-kind:raw |
everything that isn't a raw-scan hit |
kind:text|locres |
either kind |
key:a or key:b |
either term |
Fields are key, path, source, text, kind, edited, warned and empty. The
last four take yes or no, except kind, which takes text, string, name,
stringtable, bytecode, raw or locres. Terms are ANDed unless you write or.
Reusable filters go in a file, one per line, any of which is enough to keep an entry:
UassetLocalize.Cli export-all Content ui.csv --single --filter-file ui.filterA mistake — an unknown kind, a broken regular expression — is reported rather than quietly matching nothing.
How this differs from UE4LocalizationsTool's filter
The old tool had the same idea, with three limits this doesn't have: its switches
(match case, regex, reverse) applied to the whole list rather than to each term, it could
only match one column, and -filter on the command line read a FilterValues.txt that
only the GUI could write — so a headless machine couldn't filter at all. It also
swallowed invalid regular expressions and silently matched nothing.
| Format | Matched on import by | Good for |
|---|---|---|
| CSV | key, then path | spreadsheets, bulk editing |
| PO | key, then path | Poedit, Weblate, Crowdin, most CAT tools |
| Plain text | line position | dumps from the old tool |
Prefer CSV or PO. They carry each entry's identity, so a translation still lands in
the right place after the game updates and the asset gains or loses strings. Plain text
only knows about line numbers — insert one string upstream and everything after it
shifts by one, silently. That was the old tool's only option; it is kept here so
existing .txt translations still import, including the [~NAMES-INCLUDED~] form.
Because a .txt can be written either way, exporting one asks which you want: with
names writes name=text and can be re-imported after the asset changes, without
names writes the bare text one line per entry, which is what the old tool produced and
what other tooling expecting its output will want. On the CLI that choice is
--format txt or --format txt-keys.
Rows that match nothing are reported rather than guessed at.
Assets cooked by most UE5 games don't record the engine version they were built with, and store their properties unversioned. To read those properly you need two things:
- The engine version —
--engine 5.3, or the picker in Parsing options. - A
.usmapmappings file for the game, which you generate with a dumper such as Dumper-7 — or find one someone else has already dumped. A lone.usmapin the asset's folder or up to five folders above it is picked up automatically, which covers keeping one at the root of an extracted game; otherwise point at one explicitly.
If either is missing, or the version you picked doesn't match the file, you get a message
saying so rather than a corrupt read. In the GUI that message is the dialog described
above: pick a .usmap, or guess without one. On the CLI, pass --mappings and --engine,
or --raw.
Getting these right is worth the trouble. On one game's 18 MB localization table, a proper parse gives 139,294 text entries each with its real localization key, where the raw scan gives 417,010 hits that also include every row name and table name.
Unversioned properties carry neither names nor types in the stream, so without mappings the layout genuinely cannot be reconstructed. Nothing can guess it. But an FString is self-describing enough to find: a length, that many bytes or UTF-16 units, and a null terminator. The raw text scan skips parsing entirely and hunts for that shape in the export bytes — the same trick UE4LocalizationsTool called "method 2", done carefully.
UassetLocalize.Cli info DT_LocalizeTable.uasset --raw
UassetLocalize.Cli export DT_LocalizeTable.uasset text.csv --raw
UassetLocalize.Cli import DT_LocalizeTable.uasset text.csv --rawIn the GUI it's a checkbox under Parsing options, and you'll be offered it automatically when an asset turns out to need mappings you don't have.
The file header is still read normally — that's what gives you real export names and
class names to orient by. It needs an engine version, so if you don't pass --engine
the header layouts get tried newest-first and the first that reads is used; you'll get a
warning saying which. Pass --engine yourself if the result looks wrong. On an 18 MB
localization table this finds ~417,000 strings in under two seconds.
What to expect:
- Some false positives. Nothing here knows what the surrounding bytes mean, so runs that merely look like strings get picked up. Row keys and table names show up next to the actual text. Use the search box.
- Length changes shift things. Editing a string moves everything after it inside that export. That's fine for a table of strings and bad for an export that stores offsets into itself. Strings you don't touch are copied through byte for byte, so only what you edit can move — an unedited save is still byte-identical.
- Entries are numbered, not addressed. A path looks like
Export 0 (DT_Foo : DataTable) > #42— the 42nd string in that export, not a byte offset, so a translation still applies to a file you already edited. The numbering only shifts if an edit makes a string too short to be found again (under two characters).
Get a .usmap when you can. This is the fallback, not the good path.
- IoStore / Zen packages are not supported. UAssetAPI can only read legacy packages.
If a
.uassetcame out of a.utoc/.ucascontainer, convert it to legacy format first — retoc and ZenTools both do this — and open the converted file. UassetLocalize tells you when it sees one. - Blueprint bytecode text is off by default. Kismet jump instructions store absolute code offsets and nothing recalculates them, so changing the length of a literal can break the function. Same-length replacements are safe; anything else needs testing in-game. Entries carry a warning saying as much.
FNameproperties are off by default. They are identifiers at least as often as they are text.- Text that lives in a string table is not editable from the asset that references it. Open the string table asset itself.
Only entries you actually edited are written back, straight into the object UAssetAPI parsed. Everything else is re-serialized exactly as it was read — open a file, save it untouched, and you get back the same bytes. There is a test for that.
For .locres, each entry's source string hash is preserved rather than recomputed.
Unreal uses it to spot translations that have gone stale against the game's native text,
so recomputing it from the translated string makes the engine discard every entry.
CC BY-NC 4.0 — see LICENSE. Use it, change it, pass it on; credit where it came from; not for commercial purposes.
That last part is the point of choosing this licence, so to be plain about what it rules out: you may not sell localization mods you made with this tool, or anything else it produced. No price on the download, no paywall, no subscriber-only or early-access build, no key reselling, no bundling it into something people pay for, no paid localization work done with it.
UAssetAPI, in external/UAssetAPI, is a separate work by atenfyr under the MIT licence.
Its terms are unchanged by any of the above, and its notice is reproduced in
LICENSE.
