Skip to content

Localization

You can localize almost every asset in your novel. Readers can choose separate languages for text, voice, and images, and each one starts out matching their operating system’s language.

First, list the locales your novel supports in your workspace’s project.json, using IETF language tags:

{
"locales": ["en-US", "fr-FR", "ja-JP"]
}

To localize a file, put the locale in its filename:

scene-i.en-US.fountain

Or put it in a locale folder:

screenplay/en-US/act-i/scene-i.fountain

You can use both approaches in the same novel. The only rule is that a file’s translations follow the same pattern as the original. If the original is in a locale folder, its translations need to be in locale folders too.

Only translate the text the reader sees: narration, dialogue, and link text. Leave everything else as it is in the original.

Every other part of the screenplay connects to your artwork and code, and changing it breaks that connection.

There are two ways to localize artwork, and you can mix them in the same novel.

  • Separate files. Make a complete copy of the PSD for each locale, named or placed in a folder like any other localized file.
  • Variation sets. Keep a single PSD, and add a layer for each locale inside a variation set. The reader’s current locale counts as a label, so a layer named en-US shows and hides the same way as any other variation.

A stem with lyrics is localized like any other file. Inkweaver picks the version that matches the reader’s voice language.

JavaScript files are the only assets that can’t be localized.

If a script needs a localized asset, load it with the SDK’s resolve function.

The Web UI plugin draws some text of its own, like the buttons in the controls dialog and the names of gamepad buttons. That text comes from the getI18nValue selector. It knows English out of the box, and you can add other languages by decorating it.

Most keys are the English text itself, like Cancel. The exception is button, the fallback name for a gamepad button, which is Button {{index}}. Any key without an entry is returned as written, with its placeholders filled in. So to translate, pass your translation to the base implementation as the key:

const JAPANESE = {
Cancel: "キャンセル",
button: "ボタン {{index}}",
};
decorateSelector("getI18nValue", (context, next) => {
const { resolveLocale } = getSelectors();
const { locale } = resolveLocale({ role: "text" });
const text = locale === "ja-JP" ? JAPANESE[context.key] : undefined;
return next(text == null ? context : { ...context, key: text });
});

Menu fields work differently. Each field carries its own translations in its locales property.