Kovati Docs

Localisation

A bank per language, dubbing, and why a translated line keeps its id.

Documented in depthSpeechForgeDeepL0.1.2Paid

Localisation is a sibling bank per language — SB_Scene_DE beside SB_Scenejoined by line id.

Paid. Localisation is production-layer work, the same tier as PerformanceForge and the garment pipeline.

The translation seam is in the open core, and it ships a keyless Pseudo provider, so the pipeline can be exercised with no account. The DeepL provider and the localised-bank behaviour are the paid part.

The Localize page

Four controls and a list, and the page states its own contract above them.

The Localize page of the Speech Library for SB_GeneratorBriefing. A Translator panel sets Service to DeepL and reports 'Key present. Translation is billed per character when sent.' A Languages panel explains: tick the languages to create; each becomes a sibling bank joined to this one by line id, with this bank's face banks cloned beside it; a language that already exists is updated, new and rewritten lines translate, current ones cost nothing. A scrollable tick list shows ar, bg, cs, da, de, el, en-GB, en-US, es — the row for 'de' annotated 'exists: SB_GeneratorBriefing_DE'. Below: an 'Other code' field, a 'Re-translate everything' checkbox and a greyed 'Create / Update Localized Banks' button. A 'Localized banks' panel lists one row: DE, SB_GeneratorBriefing_DE, '7 current, 0 stale, 0 missing | 7 with audio', 'faces: FB_GeneratorBriefing_Astronaut_Skeleton_DE, FB_GeneratorBriefing_DE', and an Open button.
One tick per language, and the page states the whole contract above the list: a sibling bank joined by line id, the face banks cloned beside it, and re-running costing nothing for lines that have not changed.

Three facts in that capture are worth reading off it directly.

A language you already have is annotated rather than hidden. de shows "exists: SB_GeneratorBriefing_DE", so ticking it again is an update, not a duplicate — and the panel says what updating means: "new and rewritten lines translate, current ones cost nothing."

The faces come too. The localised row names two cloned face banks, because this scene has two character standards. Cloning is structural — same solver, skeleton, mapping, vocabulary and slot, with output under a per-language folder so a German bake can never overwrite an English one.

The count is four numbers, not one. 7 current, 0 stale, 0 missing — 7 with audio answers four separate questions: translated and up to date, translated but the English moved underneath it, never translated at all, and has a voice. A single percentage would collapse all four into a number you could not act on.

ControlWhat it does
ServiceWhich translation provider. Reports whether a key is present and how it bills
The tick list"Translate this bank into every ticked language — a speech bank each, its face banks cloned — then generate, solve and bake them like any other bank."
Other code"A language the list does not offer. The translator decides whether it can."
Re-translate everything"Re-translate every line, current ones included. Bills every character again."
Open, per localised bank"Show this language's bank here — generate it, then solve its faces from Perform."

A translated bank is the same seven lines

The Produce page showing bank SB_GeneratorBriefing_DE. The same seven line ids as the English bank — GEN_Doric_Report, GEN_Renk_Relays, GEN_Astro_Offer, GEN_Doric_Rule, GEN_Renk_Beacon, GEN_Astro_Copy, DLG_GeneratorBriefing_Play… — now carrying German text such as 'Gestern Abend ist der Strom wieder ausgefallen' and 'Verstanden. Zehn Minuten. Halte den Kaffee warm.' The Voice column still names Roger, Callum, Will and Lily. Status reads Generated for every line, with '(words unverified)' added on two. Origin reads Generated for five and 'Recorded (dubbed, r…' for the two that were performances. Lengths differ from the English: 5.12s, 9.52s, 6.64s, 5.57s, 4.80s, 4.24s, 6.73s. The buttons at the foot are Generate Selected, Generate All, Dub from Source and Re-generate Selected.
Same ids, same cast, different words — and a button the English bank does not have. Note the lengths: German runs longer than English on almost every line, which is a fact your subtitle and cinematic timing has to survive.

The Produce page grows a button on a localised bank: Dub from Source. It is there because a sibling bank has something a source bank does not — an original recording to carry across — and the two lines whose origin reads "Recorded (dubbed, revoiced)" took that route rather than being spoken afresh by a synthesiser.

Generate All on this bank splits the two automatically, per line, according to what the English source actually was.

Dub from Source, in its own words

Carry the source language's recording into this one: the actor's own voice, their pacing, the new words. Keeps a captured face usable underneath, because the timing survives. Costs by the minute of source audio, well above synthesis, and takes about twenty seconds a line.

Pressing it asks first, and the question is the one worth reading:

Dub N line(s) from the source language?

Each one sends its source recording to be re-spoken in this language, keeping the actor's voice and pacing. This bills dubbing minutes — well above synthesis — and takes about twenty seconds a line.

N selected line(s) have a generated source and are left alone. Generate those.

No dollar figure is offered here, deliberately. Dubbing bills by the minute of source audio at a rate the panel has never measured, and a made-up number about money is worse than none. Compare Generate All, which quotes a figure because it can.

Select a line with no recording behind it and the button declines rather than charging you for the wrong thing: "None of the selected lines has a recording behind it in the source language. Generate those instead — it is cheaper and says the right words."

Dubs run one at a time, on purpose: the service renders a whole clip per request, and running a selection in parallel would spend the whole bill before the first failure could stop it.

Dub sits beside Generate on Produce rather than on the Localize page, and that placement is the model. Localize is about banks and languages; dubbing is a per-line choice. In a real localised bank most lines are generated and the performed few are dubbed.

A translated line keeps its id

Never suffix a line id with its language. A localised line is a new asset with the same id. That is the design, not a bug.

The id is the join that makes a bank-per-language work at all. The staleness hash, the dialogue node lookup, the source-to-translation pairing and the runtime's "same line, other culture" all key off it.

Suffixing it would reintroduce the mapping table that per-language banks were invented to delete.

The contract instead: identity is (container, line id), and the container carries the language.

This was decided the day a Spanish bank's Perform tab listed six face banks and offered an English mouth for Spanish words. A face bank's language is the language of the speech bank it claims.

Localised banks follow the performance

The problem this solves: a fresh localised bank used to read every line's origin as Generated, so Generate All paid for text-to-speech on lines whose English source was a recorded performance — throwing away the acting and paying for the privilege.

Now:

  • Generate All splits text-to-speech from dubbing, per line, according to what the source actually was.
  • A line whose source is a performance is dubbed, carrying the performance into the other language rather than re-synthesising it.
  • Cloned face banks are pointed at their audio rather than sitting empty until somebody remembers to press Create/Update Face Bank.
  • A dubbed line's face copies the performance's layers under the dubbed mouth, instead of solving from audio alone and losing the captured acting.

Dubbing

DubSpeechLine carries a recorded performance into another language through a provider's dubbing capability, instead of re-synthesising.

It tracks which source recording it was dubbed from, so a later re-record of the original is caught as stale in the dub.

Dubbing has its own, narrower language list than synthesis. The Localize page does not yet say which languages a performance take can be dubbed into versus falling back to text-to-speech. Check the provider's list before planning a language.

Known gaps

These are open, and named because planning around them is cheaper than discovering them.

  • Voice ↔ language validation. Nothing yet tells a director, per speaker, which languages their cast voice actually covers, or warns per ticked language which speakers would fall silent. It needs a supported-languages capability on the provider.
  • Dub ↔ language validation, as above.
  • The L10N folder layout is superseded but not migrated. The settled target is the engine's own /Game/L10N/<culture>/ asset mirror; today's per-language folders still exist.
  • Text identity is undecided. Whether dialogue text gets stable FText keys or moves into a string table is an open design question, and nothing yet keeps a translation key stable across re-authoring.
  • Text export to the Localization Dashboard is not built.

On this page