wildwinter.LocaliserLib 0.0.4.4

dotnet add package wildwinter.LocaliserLib --version 0.0.4.4
                    
NuGet\Install-Package wildwinter.LocaliserLib -Version 0.0.4.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="wildwinter.LocaliserLib" Version="0.0.4.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="wildwinter.LocaliserLib" Version="0.0.4.4" />
                    
Directory.Packages.props
<PackageReference Include="wildwinter.LocaliserLib" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add wildwinter.LocaliserLib --version 0.0.4.4
                    
#r "nuget: wildwinter.LocaliserLib, 0.0.4.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package wildwinter.LocaliserLib@0.0.4.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=wildwinter.LocaliserLib&version=0.0.4.4
                    
Install as a Cake Addin
#tool nuget:?package=wildwinter.LocaliserLib&version=0.0.4.4
                    
Install as a Cake Tool

Ink-Localiser

A simple tool to make it easier to localise or attach voice lines to Ink projects.

Tagged Ink File

Generated CSV File

Contents

Overview

Inkle's Ink language is a great flow language for stitching together narrative-based games.

Because it's designed to mash small fragments of text together, it's not designed for localisation, or for associating lines of spoken audio to the source file.

But many studios don't use the more advanced text-manipulation features of Ink - they just use it for creating a flow of complete lines of text. It's a great solution for titles that care about branching dialogue. This means there's a problem - how do you translate each line? And how do you play the right audio for each line?

This tool takes a set of raw ink files, scans them for lines of text, and generates a localisation ID to associate with each line. It writes the ink files back out again with these IDs in the form of Ink tags at the end of each line.

This means that every line of meaningful text in the Ink file now has a unique ID attached, as a tag. That means you can use that ID for localisation or for triggering the correct audio.

The tool also optionally exports CSV, JSON, or PO/POT files containing the IDs and their associated text content from all the processed Ink files - which can then be used as a basis for localisation.

Each time the tool is run, it preserves the old IDs, just adding them to any newly appeared lines.

So for example, take this source file: Source Ink File

After the tool is run, the source file is rewritten like this: Tagged Ink File

It also creates an optional CSV fioe of strings like so: Generated Strings CSV File

And an optional JSON file of strings like so: Generated Strings JSON File

And an optional JSON file breaking down where each line comes from, like so: Generated Origin JSON File (This last one is handy for toolchains or debugging.)

PO Support

It can also produce industry-standard PO/POT files for use with translation tools like Poedit, Weblate, or Crowdin. A POT template file is generated fresh each run, while per-language PO files are merged to preserve existing translations.

Command-Line Tool

This is a command-line utility with a few arguments. A few simple examples:

Look for every Ink file in the inkFiles folder, process them for IDs, and output the data in the file output/strings.json:

LocaliserTool.exe --folder=inkFiles/ --json=output/strings.json

Look for every Ink file starting with start in the inkFiles folder, process them for IDs, and output the data in the file output/strings.csv:

LocaliserTool.exe --folder=inkFiles/ --filePattern=start*.ink --csv=output/strings.csv

Start with the demo.ink Ink file, process it and any included files for IDs, and output the data in the file output/strings.csv:

LocaliserTool.exe --file=demo.ink --csv=output/strings.csv

Generate a POT template and PO files for French and German:

LocaliserTool.exe --folder=inkFiles/ --pot=locale/messages.pot --po-dir=locale/ --po-langs=fr,de

Generate just a POT template (for use with external translation tools):

LocaliserTool.exe --folder=inkFiles/ --pot=locale/messages.pot

Is the utility failing to run on Windows? Check the security issues note here.

Arguments

  • --file=<inkFile>

    File to localise, including all its imports. If you specify --folder you don't need this.

  • --folder=<folder>

    Root folder to scan for Ink files to localise relative to working dir. If you specify --file you don't need this. e.g. --folder=inkFiles/ Default is the current working dir.

  • --filePattern=<pattern>

    Root folder to scan the --folder for Ink files to localise. e.g. --filePattern=start-*.ink Default is *.ink

  • --csv=<csvFile>

    Path to a CSV file to export all the strings to, relative to working dir. e.g. --csv=output/strings.csv Default is empty, so no CSV file will be exported.

  • --json=<jsonFile>

    Path to a JSON file to export all the strings to, relative to working dir. e.g. --json=output/strings.json Default is empty, so no JSON file will be exported.

  • --retag

    Regenerate all localisation tag IDs, rather than keep old IDs.

  • --origins=<originsFile>

    Output a file which lists which file and line each ID comes from.

  • --shortIDs

    Use short-form IDs which are just the hashcode instead of including the file/knot/stitch, if you don't care where they come from.

  • --pot=<potFile>

    Path to a POT template file to export, relative to working dir. e.g. --pot=locale/messages.pot Default is empty, so no POT file will be exported. The POT file is always regenerated from scratch.

  • --po-dir=<folder>

    Folder for per-language PO files, relative to working dir. e.g. --po-dir=locale/ Requires --po-langs to also be specified. If a PO file already exists, it will be merged rather than overwritten, preserving any existing translations. Strings that no longer exist in the source will be marked as obsolete.

  • --po-langs=<codes>

    Comma-separated target language codes for PO file generation. e.g. --po-langs=fr,de,es,ja Requires --po-dir to also be specified.

  • --po-source-lang=<code>

    Source language code, used in PO/POT file headers. e.g. --po-source-lang=en Default is en.

  • --help

    This help!

Limitations

As said above, Ink is fully capable of stitching together fragments of sentences, like so:

{shuffle:
- Here is a sentence <>
- Here is a different sentence <>
}
that will end up saying the same thing.

* I talked about sailing ships [] and the preponderance of seamonsters.
    -> MarineLife
* I didn't like monkeys [at all.] in any way whatsoever.
    -> MonkeyBusiness

This splicing of text fragments is not supported by the Localiser, as the Localiser is designed for two main use cases.

  • Producing strings for localisation. It is really hard as a translator to work stitching text fragments together, as English works very differently from other languages. So if you want your game localised, text fragments are, in general, not a good idea.

  • Producing strings for audio recording. It is almost impossible to splice together different sections of sentences for an actor to say, so again, we shouldn't be using text fragments.

Ink is still extremely powerful and we use it for lots of other flow use-cases. But for these reasons if you have multiple text fragments on a single line the Localiser will complain with an error.

(It should also complain for <> as well but I haven't got around to adding that behaviour.)

Use in Development

Develop your Ink as normal! Treat that as the 'master copy' of your game, the source of truth for the flow and your primary language content.

Use LocaliserTool to add IDs to your Ink file and to extract a file of the content. Get that file localised/translated as you need for your title. Remember that you can re-run LocaliserTool every time you alter your Ink files and everything will be updated.

At runtime, load your Ink content, and also load the appropriate JSON or CSV (which should depend on your localisation).

Use your Ink flow as normal, but when you progress the story instead of asking Ink for the text content at the current line or option, ask for the list of tags!

Look for any tag starting with #id:, parse the ID from that tag yourself, and ask your CSV or JSON file for the actual string. You can use the same ID to trigger an appropriate voice line, if you've recorded one.

In other words - during runtime, just use Ink for logic, not for content. Grab the tags from Ink, and use your external text file (or WAV filenames!) as appropriate for the relevant language.

Pseudocode:

var story = new Story(storyJsonAsset);
var stringTable = new StringTable(tableCSVAsset);

while (story.canContinue) {

    var textContent = story.Continue();
    
    // we Can actually IGNORE the textContent, we want the LOCALISED version, let's find it:

    // This function looks for a tag like #id:Main_Intro_45EW
    var stringID = extractIDFromTags(story.currentTags);

    var localisedTextContent = stringTable.GetStringByID(stringID);

    // We use that localisedTextContent instead!
    DisplayTextSomehow(localisedTextContent);

    // We could also trigger some dialogue...
    PlayAnAudioFileWithID(stringID);

    // Now let's do choices
    if(story.currentChoices.Count > 0)
    {
        for (int i = 0; i < story.currentChoices.Count; ++i) {
            Choice choice = story.currentChoices [i];

            var choiceText = choice.text;
            // Again, we can IGNORE choiceText...

            var choiceStringID = extractIDFromTags(choice.tags);

            var localisedChoiceTextContent = stringTable.GetStringByID(choiceStringID);

            // We use that localisedChoiceTextContent instead!
            DisplayChoiceTextSomehow(localisedChoiceTextContent);

        }
    }
}

The ID format

The IDs are constructed like this:

<filename>_<knot>(_<stitch>)_<code>

  • filename: The root name of the Ink file this string is in.
  • knot: The name of the containing knot this string is in.
  • stitch: If this is inside a stitch, the name of that stitch
  • code: A four-character random code which will be unique to this knot or knot/stitch combination.

This is mainly to make it easy during development to figure out where a line originated in the Ink files - it's fairly arbitrary, so IDs can be moved around safely without changing (even if the lookup will then be unhelpful). You can always delete an ID and let it regenerate if you want something more appropriate to the place where you've moved a line.

Short IDs

If you turn on --shortIDs the IDs will just be the four-character code, instead. YMMV. I prefer the full-length IDs so I can have a fair idea where a line is from when I see an ID.

Releases

You can find releases for various platforms in the release folder.

NuGet Package

If you want to use LocaliserLib as part of your own .NET toolchain, it is available on NuGet:

dotnet add package wildwinter.LocaliserLib

Note: The package does not bundle the Ink DLLs. You must separately reference ink_compiler.dll and ink-engine-runtime.dll from an inklecate release in your project.

DLL Distribution

A pre-built LocaliserLib.dll is also included in each GitHub release for use without NuGet. This also requires ink_compiler.dll and ink-engine-runtime.dll from inklecate.

Caveats

This isn't very complicated or sophisticated, so your mileage may vary!

WARNING: This rewrites your .ink files! And it might break, you never know! It's always good practice to use version control in case a process eats your content, and this is another reason why!

Inky might not notice: If for some reason you run this tool while Inky is open, Inky will probably not reload the rebuilt .ink file. Use Ctrl-R or CMD-R to reload the file Inky is working on.

Security Issues

Note on Windows Security

Because this is a hobbyist project, this app is currently not digitally signed* for Windows. If you receive an "Access Denied" or "Unauthorized" error when running this tool in PowerShell/CMD:

  1. Right-click the EXE in File Explorer.

  2. Select Properties.

  3. At the bottom, check the Unblock box and click OK.

Try running the command again.

* Because it costs a lot and seems to be impossible outside North America right now for individual developers. Thanks Microsoft!

Note on Mac Security

The app is signed. Because it's easier on Mac.

Under the Hood

Developed in .NET / C#.

The tool internally uses Inkle's Ink Parser to chunk up the ink file into useful tokens, then sifts through that for textual content. Be warned that this isn't tested in huge numbers of situations - if you spot any weirdness, let me sknow!

Acknowledgements

Obviously, huge thanks to Inkle (and Joseph Humfrey in particular) for Ink and the ecosystem around it, it's made my life way easier.

License and Attribution

This is licensed under the MIT license - you should find it in the root folder. If you're successfully or unsuccessfully using this tool, I'd love to hear about it!

You can find me on Medium, here.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.4.4 0 3/31/2026
0.0.4.3 83 3/30/2026
0.0.4.2 80 3/30/2026
0.0.4.1 81 3/29/2026