inkle / inkle/ink

Discussion: Shipped game with localisation of Ink script in 7 languages

Open
#529 0 comments 12 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
4.9k
Forks
540
PR merge metrics
No merged PRs in 30d

Description

Hi

A week ago I shipped a game (https://www.roadtoguangdong.com/) into Steam Early Access which uses Ink for its story sections.

The game's localised into in 6 languages (so far!) & since none of the extant threads about localisation I can find on here seem to reach any resolution I thought this might be a good time to share my experience & offer the details of a shipped localisation solution which works :)

One huge caveat here - I clocked early on that localising was going to be a big risk for this project and so we placed some limits on how we were going to use ink (I honestly was convinced I'd chimed in on one of the existing localisation threads, but I can't find my replies if I did...).

**Caveats**:
- (probably a deal breaker for many) we decided to abandon any ink which was going to generatively create text from grammars
- I'm using quite an old version of ink, I think it's from last September so there may be new stuff which breaks this (though I'd be surprised...).

The most we do is text substitutions e.g. we might insert an adjective and noun in a sentence. This is typically easy enough to manage with (for example) the C# way of doing formatting in strings - e.g.
"Take the {0} {1}?", "red", "book"
"Prenez le {1} {0}", "rouge", "livre" etc.

Assuming that all your text is "pre-canned", or near enough that you can manage it with some notes to the localisation people then you're good to go with this solution :)

It turns out that by far the simplest way to extract all the displayed text from an ink script is to extract it from the compiled .json files.

All the strings which get displayed by Ink are stored in the json as a string with a '^' as the first character. It's not quite that simple but this was my starting point. I think I managed to strip away anything except the strings which needed localising with 7 regexps.

I have a whole home-brew inline command system which I use to sync the ink script up with the game's 3D scenes, trigger character animations etc. and I also have a tonne of library ink code so I think at least 3 of those regexps are just to strip out my custom stuff from the strings after they've been extracted.

In any case, that's the basic starting point.

WRT to storing and fetching localised text in the game I use a pretty standard approach which is to use a "LOCs ID" (i.e. a string) to proxy each unique text string in the game, and then these are looked up in a dictionary to get the correctly localised version of the string.

A representative row from a LOCS spreadsheet might look like this (the 1st line is the top row)
```
LOCsID | Translation Notes | English | French | German | ...

ID_BACK | go back in a menu | Back | Retour | Zurück | ...
```
Keeping it all in spreadsheets makes it easy to send stuff off to localisation companies & is pretty convenient for lots of other reasons.

Our game is in Unity and I use google sheets as the spreadsheet. An exporter I wrote in google script (pretty much js) spits out our localised strings in a json blob that I can load straight into our game with JsonUtility.FromJson (https://docs.unity3d.com/ScriptReference/JsonUtility.FromJson.html) in a way which uses the LOCsID as the key to a Dictionary< string, string> to get at the correctly localised text. There are plenty of other ways to do it, but this works well.

So, to do localised text I load the correct localisation json file based on the player's choice / system language. Once this is done, the text display functions take the LOCs ID (e.g. "ID_BACK") as a string parameter and use it to get the actual string for that language from the dictionary. All very standard stuff.

A precis of our game's full LOCs pipeline follows, if anyone wants to ask questions about specific bits of it please do and I'll be happy to share anything short of the actual sourcecode - the only reason I can't share the source is that technically it belongs to a client of mine, and also it's fairly heavily tied into the existing localisation library and toolchain which are in my multiplatform game framework.

**Pipeline**

1) Extract english strings from compiled Ink .json files and keep a cache of the exact version the strings were extracted from.

2) Export the extracted strings with auto-generated LOCs IDs (I use ID_CompiledInkFilename_HashOfString as the ID for each string) - I use Unity's Animator.StringToHash() to generate the hash. I put these in a tab separated format which copy-pastes nicely into both Excel and google sheets.

3) Send the spreadsheets off to the localisation company.

4) When the localisations come back I export them from google sheets into the custom json dictionary format I use for the game

5) Here's the really cheeky bit instead of using the LOCsID at runtime to get the correct localised strings like the rest of the game, the game loads localised versions of the compiled ink scripts!

6) ... the way I generate these this is text substitution of each English string for the equivalent in the language corresponding to that file. At run time the game just checks the language setting and loads the localised compiled ink script for that language instead of the English one.

Of course, it's not quite that simple.

Literal text substitution will get you into all kinds of trouble - for example any localised strings which are substrings of others will cause trouble (especially the word "no" by itself - this will completely break the compiled ink script as the string "nop" is used as a command in the json description of the ink script)...

To fix this I had to write custom search and replace code which:
- sorts the localised English strings by length and does the search and replace on the longest first (this single thing prevents most substring issues)
- only replaces if the 1st letter of the matched string is within the "^ ... " & starts with the 1st legal world character after the '^'.

Oh, a final note: this solution is quite brittle! Any edits to the Englsh source Ink scripts used to generate the compiled ink are likely to break the auto-localisation. This is why we kept a cache of the original English compiled json used to generate the strings for localisation in step 1 above.

My tool which extracts the English strings from the compiled json also takes the previous versions of the extracted strings as input and does a diff of the old vs. new extracted strings to highlight anywhere edits since the previous version have created new strings and/or deprecated old ones. Recommend!

So, that's it - sorry for the wall of text! I hope it's useful to someone...

Once I get Road to Guangdong out of Early Access I'd quite like to do an open source version of this toolchain to help other people, but I'll have to see if my client's cool with it too...

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.